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 +1 -0
- pspm/cli.py +24 -0
- pspm/entities/__init__.py +1 -0
- pspm/entities/installer.py +69 -0
- pspm/entities/pyproject.py +50 -0
- pspm/entities/resolver.py +59 -0
- pspm/entities/toml.py +62 -0
- pspm/errors/__init__.py +1 -0
- pspm/errors/dependencies.py +15 -0
- pspm/services/__init__.py +1 -0
- pspm/services/dependencies.py +57 -0
- pspm-1.0.0.dist-info/METADATA +722 -0
- pspm-1.0.0.dist-info/RECORD +16 -0
- pspm-1.0.0.dist-info/WHEEL +4 -0
- pspm-1.0.0.dist-info/entry_points.txt +2 -0
- pspm-1.0.0.dist-info/licenses/LICENSE +674 -0
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)
|
pspm/errors/__init__.py
ADDED
|
@@ -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)
|