Skip to content

Commit aeb8e6d

Browse files
committed
refactor: make DependencyManagers objects of base class
1 parent b73f7af commit aeb8e6d

File tree

10 files changed

+99
-131
lines changed

10 files changed

+99
-131
lines changed
Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1 @@
1-
from twyn.dependency_managers.managers.npm_dependency_manager import NpmDependencyManager
2-
from twyn.dependency_managers.managers.pypi_dependency_manager import PypiDependencyManager
3-
from twyn.dependency_managers.utils import (
4-
PACKAGE_ECOSYSTEMS,
5-
get_dependency_manager_from_file,
6-
get_dependency_manager_from_name,
7-
)
81

9-
__all__ = [
10-
"NpmDependencyManager",
11-
"PypiDependencyManager",
12-
"get_dependency_manager_from_file",
13-
"get_dependency_manager_from_name",
14-
"PACKAGE_ECOSYSTEMS",
15-
]
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
from dataclasses import dataclass
2+
from pathlib import Path
3+
4+
from twyn.dependency_managers.exceptions import NoMatchingDependencyManagerError
5+
from twyn.dependency_parser.parsers.constants import (
6+
PACKAGE_LOCK_JSON,
7+
POETRY_LOCK,
8+
REQUIREMENTS_TXT,
9+
UV_LOCK,
10+
YARN_LOCK,
11+
)
12+
from twyn.trusted_packages.references.base import AbstractPackageReference
13+
from twyn.trusted_packages.references.top_npm_reference import TopNpmReference
14+
from twyn.trusted_packages.references.top_pypi_reference import TopPyPiReference
15+
16+
17+
@dataclass
18+
class DependencyManager:
19+
"""Base class for all `DependencyManagers`.
20+
21+
It acts as a repository, linking programming languages with trusted packages sources and dependency file names.
22+
"""
23+
24+
name: str
25+
"""Name identifier for the package ecosystem."""
26+
27+
trusted_packages_source: type[AbstractPackageReference]
28+
"""Reference class for trusted packages source."""
29+
30+
dependency_files: set[str]
31+
"""Set of supported dependency file names."""
32+
33+
def matches_dependency_file(self, dependency_file: str) -> bool:
34+
"""Check if this manager can handle the given dependency file."""
35+
return Path(dependency_file).name in self.dependency_files
36+
37+
def matches_ecosystem_name(self, name: str) -> bool:
38+
"""Check if this manager matches the given ecosystem name."""
39+
return self.name == Path(name).name.lower()
40+
41+
def get_alternative_source(self, sources: dict[str, str]) -> str | None:
42+
"""Get alternative source URL for this ecosystem from sources dict."""
43+
match = [x for x in sources if x == self.name]
44+
45+
return sources[match[0]] if match else None
46+
47+
48+
npm_dependency_manager = DependencyManager(
49+
name="npm",
50+
trusted_packages_source=TopNpmReference,
51+
dependency_files={PACKAGE_LOCK_JSON, YARN_LOCK},
52+
)
53+
pypi_dependency_manager = DependencyManager(
54+
name="pypi",
55+
trusted_packages_source=TopPyPiReference,
56+
dependency_files={UV_LOCK, POETRY_LOCK, REQUIREMENTS_TXT},
57+
)
58+
59+
DEPENDENCY_MANAGERS: list[DependencyManager] = [pypi_dependency_manager, npm_dependency_manager]
60+
"""List of available dependency manager classes."""
61+
62+
PACKAGE_ECOSYSTEMS = {x.name for x in DEPENDENCY_MANAGERS}
63+
"""Set of package ecosystem names from available dependency managers."""
64+
65+
66+
def get_dependency_manager_from_file(dependency_file: str) -> DependencyManager:
67+
"""Get dependency manager that can handle the given file."""
68+
for manager in DEPENDENCY_MANAGERS:
69+
if manager.matches_dependency_file(dependency_file):
70+
return manager
71+
raise NoMatchingDependencyManagerError
72+
73+
74+
def get_dependency_manager_from_name(name: str) -> DependencyManager:
75+
"""Get dependency manager by ecosystem name."""
76+
for manager in DEPENDENCY_MANAGERS:
77+
if manager.matches_ecosystem_name(name):
78+
return manager
79+
raise NoMatchingDependencyManagerError

src/twyn/dependency_managers/managers/__init__.py

Whitespace-only changes.

src/twyn/dependency_managers/managers/base.py

Lines changed: 0 additions & 36 deletions
This file was deleted.

src/twyn/dependency_managers/managers/npm_dependency_manager.py

Lines changed: 0 additions & 15 deletions
This file was deleted.

src/twyn/dependency_managers/managers/pypi_dependency_manager.py

Lines changed: 0 additions & 15 deletions
This file was deleted.

src/twyn/dependency_managers/utils.py

Lines changed: 0 additions & 29 deletions
This file was deleted.

src/twyn/main.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99
)
1010
from twyn.config.config_handler import ConfigHandler, TwynConfiguration
1111
from twyn.config.exceptions import InvalidSelectorMethodError
12-
from twyn.dependency_managers.managers.base import BaseDependencyManager
13-
from twyn.dependency_managers.utils import (
12+
from twyn.dependency_managers.managers import (
1413
PACKAGE_ECOSYSTEMS,
1514
get_dependency_manager_from_file,
1615
get_dependency_manager_from_name,
@@ -173,8 +172,9 @@ def _analyze_packages_from_source(
173172

174173
dependency_managers = _get_dependency_managers_and_parsers_mapping(dependency_files)
175174
for dependency_manager, parsers in dependency_managers.items():
176-
source = dependency_manager.get_alternative_source({"pypi": pypi_source, "npm": npm_source})
177-
top_package_reference = dependency_manager.trusted_packages_source(source, maybe_cache_handler)
175+
manager = get_dependency_manager_from_name(dependency_manager)
176+
source = manager.get_alternative_source({"pypi": pypi_source, "npm": npm_source})
177+
top_package_reference = manager.trusted_packages_source(source, maybe_cache_handler)
178178

179179
packages_from_source = top_package_reference.get_packages()
180180
trusted_packages = TrustedPackages(
@@ -262,9 +262,9 @@ def _get_selector_method(selector_method: str) -> SelectorMethod:
262262

263263
def _get_dependency_managers_and_parsers_mapping(
264264
dependency_files: set[str] | None,
265-
) -> dict[type[BaseDependencyManager], list[AbstractParser]]:
265+
) -> dict[str, list[AbstractParser]]:
266266
"""Return a dictionary, grouping all files to parse by their DependencyManager."""
267-
dependency_managers: dict[type[BaseDependencyManager], list[AbstractParser]] = {}
267+
dependency_managers: dict[str, list[AbstractParser]] = {}
268268

269269
# No dependencies introduced via the CLI, so the dependecy file was either given or will be auto-detected
270270
dependency_selector = DependencySelector(dependency_files)
@@ -273,9 +273,9 @@ def _get_dependency_managers_and_parsers_mapping(
273273
for parser in dependency_parsers:
274274
manager = get_dependency_manager_from_file(parser.file_path)
275275

276-
if manager not in dependency_managers:
277-
dependency_managers[manager] = []
278-
dependency_managers[manager].append(parser)
276+
if manager.name not in dependency_managers:
277+
dependency_managers[manager.name] = []
278+
dependency_managers[manager.name].append(parser)
279279
return dependency_managers
280280

281281

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

3-
from twyn.dependency_managers.managers.base import BaseDependencyManager
3+
from twyn.dependency_managers.managers import DependencyManager
44

5-
6-
class DummyManager(BaseDependencyManager):
7-
name = "pypi"
8-
trusted_packages_source = Mock()
9-
dependency_files = {"requirements.txt", "poetry.lock"}
5+
manager = DependencyManager(
6+
name="pypi", trusted_packages_source=Mock(), dependency_files={"requirements.txt", "poetry.lock"}
7+
)
108

119

1210
class TestDependencyManager:
1311
def test_matches_dependency_file(self) -> None:
14-
assert DummyManager.matches_dependency_file("requirements.txt")
15-
assert DummyManager.matches_dependency_file("/some/path/poetry.lock")
16-
assert not DummyManager.matches_dependency_file("setup.py")
12+
assert manager.matches_dependency_file("requirements.txt")
13+
assert manager.matches_dependency_file("/some/path/poetry.lock")
14+
assert not manager.matches_dependency_file("setup.py")
1715

1816
def test_matches_language_name(self) -> None:
19-
assert DummyManager.matches_ecosystem_name("pypi")
20-
assert not DummyManager.matches_ecosystem_name("npm")
17+
assert manager.matches_ecosystem_name("pypi")
18+
assert not manager.matches_ecosystem_name("npm")
2119

2220
def test_get_alternative_source_none(self) -> None:
2321
sources = {"npm": "npmjs.com"}
24-
assert DummyManager.get_alternative_source(sources) is None
22+
assert manager.get_alternative_source(sources) is None

tests/main/test_main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from twyn.config.config_handler import ConfigHandler, TwynConfiguration
99
from twyn.config.exceptions import InvalidSelectorMethodError
1010
from twyn.dependency_managers.exceptions import NoMatchingDependencyManagerError
11-
from twyn.dependency_managers.utils import (
11+
from twyn.dependency_managers.managers import (
1212
get_dependency_manager_from_file,
1313
get_dependency_manager_from_name,
1414
)

0 commit comments

Comments
 (0)