Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ jobs:
fail-fast: false
matrix:
python-version: ["3.10", "3.14"]
runs-on: [ubuntu-latest]
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0

Expand Down
14 changes: 0 additions & 14 deletions src/twyn/dependency_managers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,15 +1 @@
from twyn.dependency_managers.managers.npm_dependency_manager import NpmDependencyManager
from twyn.dependency_managers.managers.pypi_dependency_manager import PypiDependencyManager
from twyn.dependency_managers.utils import (
PACKAGE_ECOSYSTEMS,
get_dependency_manager_from_file,
get_dependency_manager_from_name,
)

__all__ = [
"NpmDependencyManager",
"PypiDependencyManager",
"get_dependency_manager_from_file",
"get_dependency_manager_from_name",
"PACKAGE_ECOSYSTEMS",
]
73 changes: 73 additions & 0 deletions src/twyn/dependency_managers/managers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from dataclasses import dataclass
from pathlib import Path

from twyn.dependency_managers.exceptions import NoMatchingDependencyManagerError
from twyn.dependency_parser.parsers.constants import (
PACKAGE_LOCK_JSON,
POETRY_LOCK,
REQUIREMENTS_TXT,
UV_LOCK,
YARN_LOCK,
)
from twyn.trusted_packages.references.base import AbstractPackageReference
from twyn.trusted_packages.references.top_npm_reference import TopNpmReference
from twyn.trusted_packages.references.top_pypi_reference import TopPyPiReference


@dataclass
class DependencyManager:
"""Base class for all `DependencyManagers`.

It acts as a repository, linking programming languages with trusted packages sources and dependency file names.
"""

name: str
"""Name identifier for the package ecosystem."""

trusted_packages_source: type[AbstractPackageReference]
"""Reference class for trusted packages source."""

dependency_files: set[str]
"""Set of supported dependency file names."""

def matches_dependency_file(self, dependency_file: str) -> bool:
"""Check if this manager can handle the given dependency file."""
return Path(dependency_file).name in self.dependency_files

def get_alternative_source(self, sources: dict[str, str]) -> str | None:
"""Get alternative source URL for this ecosystem from sources dict."""
return sources.get(self.name)


npm_dependency_manager = DependencyManager(
name="npm",
trusted_packages_source=TopNpmReference,
dependency_files={PACKAGE_LOCK_JSON, YARN_LOCK},
)
pypi_dependency_manager = DependencyManager(
name="pypi",
trusted_packages_source=TopPyPiReference,
dependency_files={UV_LOCK, POETRY_LOCK, REQUIREMENTS_TXT},
)

DEPENDENCY_MANAGERS: list[DependencyManager] = [pypi_dependency_manager, npm_dependency_manager]
"""List of available dependency manager classes."""

PACKAGE_ECOSYSTEMS = {x.name for x in DEPENDENCY_MANAGERS}
"""Set of package ecosystem names from available dependency managers."""


def get_dependency_manager_from_file(dependency_file: str) -> DependencyManager:
"""Get dependency manager that can handle the given file."""
for manager in DEPENDENCY_MANAGERS:
if manager.matches_dependency_file(dependency_file):
return manager
raise NoMatchingDependencyManagerError


def get_dependency_manager_from_name(name: str) -> DependencyManager:
"""Get dependency manager by ecosystem name."""
for manager in DEPENDENCY_MANAGERS:
if manager.name == name:
return manager
raise NoMatchingDependencyManagerError
Empty file.
36 changes: 0 additions & 36 deletions src/twyn/dependency_managers/managers/base.py

This file was deleted.

15 changes: 0 additions & 15 deletions src/twyn/dependency_managers/managers/npm_dependency_manager.py

This file was deleted.

This file was deleted.

29 changes: 0 additions & 29 deletions src/twyn/dependency_managers/utils.py

This file was deleted.

20 changes: 10 additions & 10 deletions src/twyn/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
)
from twyn.config.config_handler import ConfigHandler, TwynConfiguration
from twyn.config.exceptions import InvalidSelectorMethodError
from twyn.dependency_managers.managers.base import BaseDependencyManager
from twyn.dependency_managers.utils import (
from twyn.dependency_managers.managers import (
PACKAGE_ECOSYSTEMS,
get_dependency_manager_from_file,
get_dependency_manager_from_name,
Expand Down Expand Up @@ -172,9 +171,10 @@ def _analyze_packages_from_source(
typos_by_file = TyposquatCheckResults()

dependency_managers = _get_dependency_managers_and_parsers_mapping(dependency_files)
for dependency_manager, parsers in dependency_managers.items():
source = dependency_manager.get_alternative_source({"pypi": pypi_source, "npm": npm_source})
top_package_reference = dependency_manager.trusted_packages_source(source, maybe_cache_handler)
for ecosystem_name, parsers in dependency_managers.items():
manager = get_dependency_manager_from_name(ecosystem_name)
source = manager.get_alternative_source({"pypi": pypi_source, "npm": npm_source})
top_package_reference = manager.trusted_packages_source(source, maybe_cache_handler)

packages_from_source = top_package_reference.get_packages()
trusted_packages = TrustedPackages(
Expand Down Expand Up @@ -262,9 +262,9 @@ def _get_selector_method(selector_method: str) -> SelectorMethod:

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

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

if manager not in dependency_managers:
dependency_managers[manager] = []
dependency_managers[manager].append(parser)
if manager.name not in dependency_managers:
dependency_managers[manager.name] = []
dependency_managers[manager.name].append(parser)
return dependency_managers


Expand Down
22 changes: 8 additions & 14 deletions tests/dependency_managers/test_dependency_managers.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,18 @@
from unittest.mock import Mock

from twyn.dependency_managers.managers.base import BaseDependencyManager
from twyn.dependency_managers.managers import DependencyManager


class DummyManager(BaseDependencyManager):
name = "pypi"
trusted_packages_source = Mock()
dependency_files = {"requirements.txt", "poetry.lock"}
manager = DependencyManager(
name="pypi", trusted_packages_source=Mock(), dependency_files={"requirements.txt", "poetry.lock"}
)


class TestDependencyManager:
def test_matches_dependency_file(self) -> None:
assert DummyManager.matches_dependency_file("requirements.txt")
assert DummyManager.matches_dependency_file("/some/path/poetry.lock")
assert not DummyManager.matches_dependency_file("setup.py")

def test_matches_language_name(self) -> None:
assert DummyManager.matches_ecosystem_name("pypi")
assert not DummyManager.matches_ecosystem_name("npm")
assert manager.matches_dependency_file("requirements.txt")
assert manager.matches_dependency_file("/some/path/poetry.lock")
assert not manager.matches_dependency_file("setup.py")

def test_get_alternative_source_none(self) -> None:
sources = {"npm": "npmjs.com"}
assert DummyManager.get_alternative_source(sources) is None
assert manager.get_alternative_source(sources) is None
2 changes: 1 addition & 1 deletion tests/main/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from twyn.config.config_handler import ConfigHandler, TwynConfiguration
from twyn.config.exceptions import InvalidSelectorMethodError
from twyn.dependency_managers.exceptions import NoMatchingDependencyManagerError
from twyn.dependency_managers.utils import (
from twyn.dependency_managers.managers import (
get_dependency_manager_from_file,
get_dependency_manager_from_name,
)
Expand Down
Loading