pspm 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pspm/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """Initial docstring."""
pspm/cli.py ADDED
@@ -0,0 +1,24 @@
1
+ """main."""
2
+
3
+ from typing import Annotated
4
+
5
+ import typer
6
+ from rich import print as rprint
7
+
8
+ from pspm.services.dependencies import add_dependency
9
+
10
+ app = typer.Typer()
11
+
12
+
13
+ @app.callback()
14
+ def callback() -> None:
15
+ """Python simple package manager."""
16
+
17
+
18
+ @app.command()
19
+ def add(
20
+ package: str, group: Annotated[str, typer.Option("--group", "-g")] = ""
21
+ ) -> None:
22
+ """Add package to pyproject, install it and lock version."""
23
+ rprint(f"Adding package {package}")
24
+ add_dependency(package, group or None)
@@ -0,0 +1 @@
1
+ """Modules to put entities."""
@@ -0,0 +1,69 @@
1
+ """Module with classes to deal with packages."""
2
+
3
+ import abc
4
+ import subprocess
5
+ from shutil import which
6
+
7
+ import uv
8
+
9
+ from pspm.errors.dependencies import InstallError
10
+
11
+
12
+ class BaseInstaller(abc.ABC):
13
+ """Package installer."""
14
+
15
+ @abc.abstractmethod
16
+ def install(self, package: str) -> None:
17
+ """Install package.
18
+
19
+ Args:
20
+ ----
21
+ package: Package to install
22
+
23
+ """
24
+ raise NotImplementedError
25
+
26
+ @abc.abstractmethod
27
+ def uninstall(self, package: str) -> None:
28
+ """Uninstall package.
29
+
30
+ Args:
31
+ ----
32
+ package: Package to uninstall
33
+
34
+ """
35
+ raise NotImplementedError
36
+
37
+
38
+ class UVInstaller(BaseInstaller):
39
+ """Install packages with UV."""
40
+
41
+ def __init__(self) -> None:
42
+ """Initialize UV Installer."""
43
+ self._uv_path = which("uv") or uv.find_uv_bin()
44
+
45
+ def install(self, package: str) -> None:
46
+ """Install package.
47
+
48
+ Args:
49
+ ----
50
+ package: Package to install
51
+
52
+ Raises:
53
+ ------
54
+ InstallError: If can't install package
55
+
56
+ """
57
+ retcode = subprocess.call([self._uv_path, "pip", "install", package]) # noqa: S603
58
+ if retcode != 0:
59
+ raise InstallError(package)
60
+
61
+ def uninstall(self, package: str) -> None:
62
+ """Uninstall package.
63
+
64
+ Args:
65
+ ----
66
+ package: Package to uninstall
67
+
68
+ """
69
+ raise NotImplementedError
@@ -0,0 +1,50 @@
1
+ """Module to interact with pyproject.toml file."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ if TYPE_CHECKING:
8
+ from pspm.entities.toml import BaseToml
9
+
10
+
11
+ class Pyproject:
12
+ """Class for manipulating pyproject.toml file."""
13
+
14
+ def __init__(self, toml_parser: BaseToml) -> None:
15
+ """Init Pyproject.
16
+
17
+ Args:
18
+ toml_parser: Parser to be used for parsing TOML
19
+ """
20
+ self._parser = toml_parser
21
+
22
+ def add_dependency(self, package: str) -> None:
23
+ """Add dependency to project.
24
+
25
+ Args:
26
+ package: Package to download
27
+ """
28
+ data = self._parser.load()
29
+ dependencies: list[str] = data["project"].get("dependencies", [])
30
+ dependencies.append(package)
31
+ data["project"]["dependencies"] = dependencies
32
+ self._parser.dump(data)
33
+
34
+ def add_group_dependency(self, package: str, group: str) -> None:
35
+ """Add optional-dependency with group to project.
36
+
37
+ Args:
38
+ package: Package to install
39
+ group: Group that package will be inserted
40
+ """
41
+ data = self._parser.load()
42
+ optional_dependencies = data["project"].get(
43
+ "optional-dependencies",
44
+ {},
45
+ )
46
+ dependencies = optional_dependencies.get(group, [])
47
+ dependencies.append(package)
48
+ optional_dependencies[group] = dependencies
49
+ data["project"]["optional-dependencies"] = optional_dependencies
50
+ self._parser.dump(data)
@@ -0,0 +1,59 @@
1
+ """Module with classes to deal with dependecy versions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import abc
6
+ import subprocess
7
+ from shutil import which
8
+
9
+ import uv
10
+
11
+
12
+ class BaseResolver(abc.ABC):
13
+ """Base class for resolving dependencies.
14
+
15
+ Attributes:
16
+ pyproject_path: Path to pyproject.
17
+ """
18
+
19
+ def __init__(self, pyproject_path: str) -> None:
20
+ """Initialize BaseResolver."""
21
+ self.pyproject_path = pyproject_path
22
+
23
+ @abc.abstractmethod
24
+ def compile(self, output_file: str, group: str | None = None) -> None:
25
+ """Compiles requirements into a lock file.
26
+
27
+ Args:
28
+ output_file: File to write output
29
+ group: Group to include dependencies from
30
+ """
31
+ raise NotImplementedError
32
+
33
+
34
+ class UVResolver(BaseResolver):
35
+ """Class for resolving dependencies with UV."""
36
+
37
+ def __init__(self, pyproject_path: str) -> None:
38
+ """Initialize UV Compiler."""
39
+ self.pyproject_path = pyproject_path
40
+ self._uv_path = which("uv") or uv.find_uv_bin()
41
+
42
+ def compile(self, output_file: str, group: str | None = None) -> None:
43
+ """Compiles requirements into a lock file.
44
+
45
+ Args:
46
+ output_file: File to write output
47
+ group: Group to include dependencies from
48
+ """
49
+ extra_arguments = ["--extra", group] if group else []
50
+ subprocess.call([ # noqa: S603
51
+ self._uv_path,
52
+ "pip",
53
+ "compile",
54
+ "-q",
55
+ *extra_arguments,
56
+ "-o",
57
+ output_file,
58
+ self.pyproject_path,
59
+ ])
pspm/entities/toml.py ADDED
@@ -0,0 +1,62 @@
1
+ """Module for interacting with toml files."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import abc
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ import tomli
10
+ import tomli_w
11
+
12
+
13
+ class BaseToml(abc.ABC):
14
+ """TOML Parser and writer.
15
+
16
+ Attributes:
17
+ path: TOML file path
18
+ """
19
+
20
+ def __init__(self, path: str) -> None:
21
+ """Initialize TOML.
22
+
23
+ Args:
24
+ path: File path to TOML file
25
+ """
26
+ self.path = path
27
+
28
+ @abc.abstractmethod
29
+ def load(self) -> dict[str, Any]:
30
+ """Load TOML file."""
31
+ raise NotImplementedError
32
+
33
+ @abc.abstractmethod
34
+ def dump(self, data: dict[str, Any]) -> None:
35
+ """Write a dictionary to a file containing TOML-formatted data.
36
+
37
+ Args:
38
+ data: TOML data
39
+ """
40
+ raise NotImplementedError
41
+
42
+
43
+ class Toml(BaseToml):
44
+ """TOML Parser and writer."""
45
+
46
+ def load(self) -> dict[str, Any]:
47
+ """Load TOML file.
48
+
49
+ Returns:
50
+ A dictionary containing parsed TOML
51
+ """
52
+ with Path(self.path).open("rb") as f:
53
+ return tomli.load(f)
54
+
55
+ def dump(self, data: dict[str, Any]) -> None:
56
+ """Write a dictionary to a file containing TOML-formatted data.
57
+
58
+ Args:
59
+ data: TOML data
60
+ """
61
+ with Path(self.path).open("wb") as f:
62
+ tomli_w.dump(data, f)
@@ -0,0 +1 @@
1
+ """Error modules."""
@@ -0,0 +1,15 @@
1
+ """Module with errors related to dependency management."""
2
+
3
+
4
+ class InstallError(Exception):
5
+ """Can't install package."""
6
+
7
+ def __init__(self, package: str) -> None:
8
+ """Initialize InstallError.
9
+
10
+ Args:
11
+ package: Package that failed to be installed
12
+ """
13
+ self.package = package
14
+ self.message = f"Error installing package {package}"
15
+ super().__init__(self.message)
@@ -0,0 +1 @@
1
+ """Modules to put logic."""
@@ -0,0 +1,57 @@
1
+ """Module to handle dependencies."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from rich import print as rprint
8
+
9
+ from pspm.entities.installer import BaseInstaller, UVInstaller
10
+ from pspm.entities.pyproject import Pyproject
11
+ from pspm.entities.resolver import BaseResolver, UVResolver
12
+ from pspm.entities.toml import Toml
13
+ from pspm.errors.dependencies import InstallError
14
+
15
+
16
+ def _get_pyproject_path() -> str:
17
+ path = Path(Path.cwd()) / "pyproject.toml"
18
+ return str(path)
19
+
20
+
21
+ def _get_pyproject() -> Pyproject:
22
+ path = _get_pyproject_path()
23
+ parser = Toml(str(path))
24
+ return Pyproject(parser)
25
+
26
+
27
+ def _get_resolver() -> BaseResolver:
28
+ path = _get_pyproject_path()
29
+ return UVResolver(path)
30
+
31
+
32
+ def _get_installer() -> BaseInstaller:
33
+ return UVInstaller()
34
+
35
+
36
+ def add_dependency(package: str, group: str | None = None) -> None:
37
+ """Add dependency to pyproject.
38
+
39
+ Args:
40
+ package: Package to install
41
+ group: Group to insert package
42
+ """
43
+ installer = _get_installer()
44
+ try:
45
+ installer.install(package)
46
+ except InstallError:
47
+ rprint(f":boom: [red]Failed to install {package}[/red]")
48
+ return
49
+
50
+ pyproject = _get_pyproject()
51
+ if not group:
52
+ pyproject.add_dependency(package)
53
+ else:
54
+ pyproject.add_group_dependency(package, group)
55
+ resolver = _get_resolver()
56
+ output_file = f"requirements{'-' + group if group else ''}.lock"
57
+ resolver.compile(output_file, group)