depkeeper 0.1.0.dev0__tar.gz → 0.1.0.dev1__tar.gz
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.
- {depkeeper-0.1.0.dev0 → depkeeper-0.1.0.dev1}/PKG-INFO +7 -10
- {depkeeper-0.1.0.dev0 → depkeeper-0.1.0.dev1}/README.md +0 -1
- depkeeper-0.1.0.dev1/depkeeper/__init__.py +32 -0
- depkeeper-0.1.0.dev1/depkeeper/__main__.py +45 -0
- depkeeper-0.1.0.dev1/depkeeper/__version__.py +12 -0
- depkeeper-0.1.0.dev1/depkeeper/cli.py +157 -0
- depkeeper-0.1.0.dev1/depkeeper/constants.py +94 -0
- depkeeper-0.1.0.dev1/depkeeper/context.py +37 -0
- depkeeper-0.1.0.dev1/depkeeper/exceptions.py +189 -0
- {depkeeper-0.1.0.dev0 → depkeeper-0.1.0.dev1}/depkeeper.egg-info/PKG-INFO +7 -10
- {depkeeper-0.1.0.dev0 → depkeeper-0.1.0.dev1}/depkeeper.egg-info/SOURCES.txt +4 -0
- {depkeeper-0.1.0.dev0 → depkeeper-0.1.0.dev1}/depkeeper.egg-info/requires.txt +6 -8
- {depkeeper-0.1.0.dev0 → depkeeper-0.1.0.dev1}/pyproject.toml +8 -42
- depkeeper-0.1.0.dev0/depkeeper/__init__.py +0 -43
- depkeeper-0.1.0.dev0/depkeeper/__main__.py +0 -50
- depkeeper-0.1.0.dev0/depkeeper/__version__.py +0 -72
- {depkeeper-0.1.0.dev0 → depkeeper-0.1.0.dev1}/LICENSE +0 -0
- {depkeeper-0.1.0.dev0 → depkeeper-0.1.0.dev1}/depkeeper.egg-info/dependency_links.txt +0 -0
- {depkeeper-0.1.0.dev0 → depkeeper-0.1.0.dev1}/depkeeper.egg-info/entry_points.txt +0 -0
- {depkeeper-0.1.0.dev0 → depkeeper-0.1.0.dev1}/depkeeper.egg-info/top_level.txt +0 -0
- {depkeeper-0.1.0.dev0 → depkeeper-0.1.0.dev1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: depkeeper
|
|
3
|
-
Version: 0.1.0.
|
|
3
|
+
Version: 0.1.0.dev1
|
|
4
4
|
Summary: Modern Python dependency management for requirements.txt files
|
|
5
5
|
Author: depkeeper Contributors
|
|
6
6
|
License: Apache-2.0
|
|
@@ -25,18 +25,16 @@ Classifier: Topic :: System :: Software Distribution
|
|
|
25
25
|
Requires-Python: >=3.8
|
|
26
26
|
Description-Content-Type: text/markdown
|
|
27
27
|
License-File: LICENSE
|
|
28
|
-
Requires-Dist: click>=8.1.
|
|
29
|
-
Requires-Dist: packaging>=23.
|
|
28
|
+
Requires-Dist: click>=8.1.8
|
|
29
|
+
Requires-Dist: packaging>=23.2
|
|
30
30
|
Requires-Dist: httpx>=0.24.0
|
|
31
|
-
Requires-Dist: rich>=13.
|
|
32
|
-
Requires-Dist: pydantic>=2.0.0
|
|
31
|
+
Requires-Dist: rich>=13.9.4
|
|
33
32
|
Provides-Extra: dev
|
|
34
33
|
Requires-Dist: pytest>=7.4.0; extra == "dev"
|
|
35
34
|
Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
|
|
36
35
|
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
37
36
|
Requires-Dist: pytest-mock>=3.11.0; extra == "dev"
|
|
38
37
|
Requires-Dist: pytest-httpx>=0.22.0; extra == "dev"
|
|
39
|
-
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
40
38
|
Requires-Dist: mypy>=1.5.0; extra == "dev"
|
|
41
39
|
Requires-Dist: types-setuptools; extra == "dev"
|
|
42
40
|
Requires-Dist: pre-commit>=3.4.0; extra == "dev"
|
|
@@ -47,9 +45,9 @@ Requires-Dist: pytest-asyncio>=0.21.0; extra == "test"
|
|
|
47
45
|
Requires-Dist: pytest-mock>=3.11.0; extra == "test"
|
|
48
46
|
Requires-Dist: pytest-httpx>=0.22.0; extra == "test"
|
|
49
47
|
Provides-Extra: docs
|
|
50
|
-
Requires-Dist: mkdocs>=1.
|
|
51
|
-
Requires-Dist: mkdocs-material>=9.
|
|
52
|
-
Requires-Dist: mkdocstrings[python]>=0.
|
|
48
|
+
Requires-Dist: mkdocs>=1.6.1; extra == "docs"
|
|
49
|
+
Requires-Dist: mkdocs-material>=9.7.1; extra == "docs"
|
|
50
|
+
Requires-Dist: mkdocstrings[python]>=0.26.1; extra == "docs"
|
|
53
51
|
|
|
54
52
|
# depkeeper 🛡️🐍
|
|
55
53
|
|
|
@@ -167,7 +165,6 @@ This project is licensed under the **Apache-2.0 License** — see the [LICENSE](
|
|
|
167
165
|
|
|
168
166
|
## ❤️ Support
|
|
169
167
|
|
|
170
|
-
- 📬 Email: [support@depkeeper.dev](mailto:support@depkeeper.dev)
|
|
171
168
|
- 💬 GitHub Discussions: [https://github.com/rahulkaushal04/depkeeper/discussions](https://github.com/rahulkaushal04/depkeeper/discussions)
|
|
172
169
|
- 🐞 Issues: [https://github.com/rahulkaushal04/depkeeper/issues](https://github.com/rahulkaushal04/depkeeper/issues)
|
|
173
170
|
|
|
@@ -114,7 +114,6 @@ This project is licensed under the **Apache-2.0 License** — see the [LICENSE](
|
|
|
114
114
|
|
|
115
115
|
## ❤️ Support
|
|
116
116
|
|
|
117
|
-
- 📬 Email: [support@depkeeper.dev](mailto:support@depkeeper.dev)
|
|
118
117
|
- 💬 GitHub Discussions: [https://github.com/rahulkaushal04/depkeeper/discussions](https://github.com/rahulkaushal04/depkeeper/discussions)
|
|
119
118
|
- 🐞 Issues: [https://github.com/rahulkaushal04/depkeeper/issues](https://github.com/rahulkaushal04/depkeeper/issues)
|
|
120
119
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
depkeeper — modern Python dependency management for requirements.txt.
|
|
3
|
+
|
|
4
|
+
depkeeper is an intelligent dependency management tool that helps developers
|
|
5
|
+
keep their ``requirements.txt`` files up to date, secure, and conflict-free.
|
|
6
|
+
|
|
7
|
+
Quick start
|
|
8
|
+
-----------
|
|
9
|
+
Check for available updates::
|
|
10
|
+
|
|
11
|
+
$ depkeeper check
|
|
12
|
+
|
|
13
|
+
Update packages to newer versions::
|
|
14
|
+
|
|
15
|
+
$ depkeeper update
|
|
16
|
+
|
|
17
|
+
Programmatic usage::
|
|
18
|
+
|
|
19
|
+
>>> from depkeeper.core.parser import RequirementsParser
|
|
20
|
+
>>> from depkeeper.core.checker import VersionChecker
|
|
21
|
+
>>> parser = RequirementsParser()
|
|
22
|
+
>>> requirements = parser.parse_file("requirements.txt")
|
|
23
|
+
|
|
24
|
+
For more information, see:
|
|
25
|
+
https://docs.depkeeper.dev
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
from depkeeper.__version__ import __version__
|
|
31
|
+
|
|
32
|
+
__all__ = ["__version__"]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module entry point for depkeeper.
|
|
3
|
+
|
|
4
|
+
This file enables execution via ``python -m depkeeper`` and delegates
|
|
5
|
+
control to the main CLI implementation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main() -> int:
|
|
14
|
+
"""Entry point for ``python -m depkeeper``.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Exit code:
|
|
18
|
+
0 Success
|
|
19
|
+
1 Import or runtime error
|
|
20
|
+
130 Interrupted by user
|
|
21
|
+
"""
|
|
22
|
+
try:
|
|
23
|
+
from depkeeper.cli import main as cli_main
|
|
24
|
+
except ImportError as exc:
|
|
25
|
+
_print_startup_error(exc)
|
|
26
|
+
return 1
|
|
27
|
+
|
|
28
|
+
return cli_main()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _print_startup_error(exc: ImportError) -> None:
|
|
32
|
+
"""Print a helpful error message if the CLI cannot be imported."""
|
|
33
|
+
try:
|
|
34
|
+
from depkeeper.__version__ import __version__
|
|
35
|
+
|
|
36
|
+
sys.stderr.write(f"depkeeper version: {__version__}\n")
|
|
37
|
+
except Exception:
|
|
38
|
+
sys.stderr.write("depkeeper version: <unknown>\n")
|
|
39
|
+
|
|
40
|
+
sys.stderr.write("\n")
|
|
41
|
+
sys.stderr.write(f"ImportError: {exc}\n")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
if __name__ == "__main__":
|
|
45
|
+
sys.exit(main())
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Version information for depkeeper.
|
|
3
|
+
|
|
4
|
+
This module exposes the canonical version string for the depkeeper
|
|
5
|
+
package. It is intentionally isolated to avoid import cycles and to
|
|
6
|
+
allow tools (CLI, packaging, docs) to query the version reliably.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
#: Current depkeeper version (PEP 440 compliant).
|
|
12
|
+
__version__: str = "0.1.0.dev1"
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command-line interface for depkeeper.
|
|
3
|
+
|
|
4
|
+
This module defines the main Click entry point, global options, command
|
|
5
|
+
registration, and top-level error handling for the depkeeper CLI.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
import logging
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
import click
|
|
17
|
+
|
|
18
|
+
from depkeeper.__version__ import __version__
|
|
19
|
+
from depkeeper.context import DepKeeperContext
|
|
20
|
+
from depkeeper.exceptions import DepKeeperError
|
|
21
|
+
from depkeeper.utils.console import print_error, print_warning
|
|
22
|
+
from depkeeper.utils.logger import get_logger, setup_logging
|
|
23
|
+
|
|
24
|
+
logger = get_logger("cli")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@click.group(context_settings={"help_option_names": ["-h", "--help"]})
|
|
28
|
+
@click.option(
|
|
29
|
+
"--config",
|
|
30
|
+
"-c",
|
|
31
|
+
type=click.Path(exists=True, dir_okay=False, path_type=Path),
|
|
32
|
+
help="Path to configuration file.",
|
|
33
|
+
envvar="DEPKEEPER_CONFIG",
|
|
34
|
+
)
|
|
35
|
+
@click.option(
|
|
36
|
+
"--verbose",
|
|
37
|
+
"-v",
|
|
38
|
+
count=True,
|
|
39
|
+
help="Increase verbosity (can be repeated: -v, -vv).",
|
|
40
|
+
)
|
|
41
|
+
@click.option(
|
|
42
|
+
"--color/--no-color",
|
|
43
|
+
default=True,
|
|
44
|
+
help="Enable or disable colored output.",
|
|
45
|
+
envvar="DEPKEEPER_COLOR",
|
|
46
|
+
)
|
|
47
|
+
@click.version_option(
|
|
48
|
+
version=__version__,
|
|
49
|
+
prog_name="depkeeper",
|
|
50
|
+
message="%(prog)s %(version)s",
|
|
51
|
+
)
|
|
52
|
+
@click.pass_context
|
|
53
|
+
def cli(
|
|
54
|
+
ctx: click.Context,
|
|
55
|
+
config: Optional[Path],
|
|
56
|
+
verbose: int,
|
|
57
|
+
color: bool,
|
|
58
|
+
) -> None:
|
|
59
|
+
"""depkeeper — modern dependency management for requirements.txt files.
|
|
60
|
+
|
|
61
|
+
\b
|
|
62
|
+
Available commands:
|
|
63
|
+
depkeeper check Check for available updates
|
|
64
|
+
depkeeper update Update packages to newer versions
|
|
65
|
+
|
|
66
|
+
\b
|
|
67
|
+
Examples:
|
|
68
|
+
depkeeper check
|
|
69
|
+
depkeeper update
|
|
70
|
+
depkeeper -v check
|
|
71
|
+
|
|
72
|
+
Use ``depkeeper COMMAND --help`` for command-specific options.
|
|
73
|
+
"""
|
|
74
|
+
_configure_logging(verbose)
|
|
75
|
+
|
|
76
|
+
depkeeper_ctx = DepKeeperContext()
|
|
77
|
+
depkeeper_ctx.config_path = config
|
|
78
|
+
depkeeper_ctx.verbose = verbose
|
|
79
|
+
depkeeper_ctx.color = color
|
|
80
|
+
ctx.obj = depkeeper_ctx
|
|
81
|
+
|
|
82
|
+
# Respect NO_COLOR for downstream libraries
|
|
83
|
+
if color:
|
|
84
|
+
os.environ.pop("NO_COLOR", None)
|
|
85
|
+
else:
|
|
86
|
+
os.environ["NO_COLOR"] = "1"
|
|
87
|
+
|
|
88
|
+
logger.debug("depkeeper v%s", __version__)
|
|
89
|
+
logger.debug("Config path: %s", config)
|
|
90
|
+
logger.debug("Verbosity: %s | Color: %s", verbose, color)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _configure_logging(verbose: int) -> None:
|
|
94
|
+
"""Configure logging level based on verbosity flags."""
|
|
95
|
+
if verbose <= 0:
|
|
96
|
+
level = logging.WARNING
|
|
97
|
+
elif verbose == 1:
|
|
98
|
+
level = logging.INFO
|
|
99
|
+
else:
|
|
100
|
+
level = logging.DEBUG
|
|
101
|
+
|
|
102
|
+
setup_logging(level=level)
|
|
103
|
+
logger.debug("Logging initialized at %s level", logging.getLevelName(level))
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# Register CLI subcommands
|
|
107
|
+
try:
|
|
108
|
+
from depkeeper.commands.check import check
|
|
109
|
+
from depkeeper.commands.update import update
|
|
110
|
+
|
|
111
|
+
cli.add_command(check)
|
|
112
|
+
cli.add_command(update)
|
|
113
|
+
|
|
114
|
+
except ImportError as exc:
|
|
115
|
+
sys.stderr.write(f"FATAL: Failed to import CLI commands: {exc}\n")
|
|
116
|
+
sys.exit(1)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def main() -> int:
|
|
120
|
+
"""Main entry point for the depkeeper CLI.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Exit code:
|
|
124
|
+
0 Success
|
|
125
|
+
1 Unhandled or application error
|
|
126
|
+
2 Usage error (Click)
|
|
127
|
+
130 Interrupted by user (Ctrl+C)
|
|
128
|
+
"""
|
|
129
|
+
try:
|
|
130
|
+
cli(standalone_mode=False)
|
|
131
|
+
return 0
|
|
132
|
+
|
|
133
|
+
except click.ClickException as exc:
|
|
134
|
+
exc.show()
|
|
135
|
+
return exc.exit_code
|
|
136
|
+
|
|
137
|
+
except DepKeeperError as exc:
|
|
138
|
+
print_error(str(exc))
|
|
139
|
+
logger.debug(
|
|
140
|
+
"DepKeeperError details: %s",
|
|
141
|
+
exc.details or "<none>",
|
|
142
|
+
exc_info=True,
|
|
143
|
+
)
|
|
144
|
+
return 1
|
|
145
|
+
|
|
146
|
+
except KeyboardInterrupt:
|
|
147
|
+
print_warning("\nOperation cancelled by user")
|
|
148
|
+
return 130
|
|
149
|
+
|
|
150
|
+
except Exception as exc:
|
|
151
|
+
print_error(f"Unexpected error: {exc}")
|
|
152
|
+
logger.exception("Unhandled exception in CLI")
|
|
153
|
+
return 1
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
if __name__ == "__main__":
|
|
157
|
+
sys.exit(main())
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Centralized constants for depkeeper.
|
|
3
|
+
|
|
4
|
+
This module defines immutable configuration values used across depkeeper,
|
|
5
|
+
including network settings, file patterns, CLI directives, and logging
|
|
6
|
+
formats. All values are intended to be treated as read-only.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Final, Mapping, Sequence
|
|
10
|
+
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
12
|
+
# Project metadata
|
|
13
|
+
# ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
#: HTTP User-Agent template used for outbound requests.
|
|
16
|
+
USER_AGENT_TEMPLATE: Final[str] = (
|
|
17
|
+
"depkeeper/{version} (https://github.com/rahulkaushal04/depkeeper)"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
# PyPI endpoints
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
#: Base URL for the PyPI JSON API.
|
|
25
|
+
PYPI_JSON_API: Final[str] = "https://pypi.org/pypi/{package}/json"
|
|
26
|
+
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
# HTTP configuration
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
#: Default network timeout in seconds.
|
|
32
|
+
DEFAULT_TIMEOUT: Final[int] = 30
|
|
33
|
+
|
|
34
|
+
#: Maximum number of retries for failed HTTP requests.
|
|
35
|
+
DEFAULT_MAX_RETRIES: Final[int] = 3
|
|
36
|
+
|
|
37
|
+
# ---------------------------------------------------------------------------
|
|
38
|
+
# Requirement file patterns and directives
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
#: Glob patterns used to detect supported requirement-related files.
|
|
42
|
+
REQUIREMENT_FILE_PATTERNS: Final[Mapping[str, Sequence[str]]] = {
|
|
43
|
+
"requirements": (
|
|
44
|
+
"requirements.txt",
|
|
45
|
+
"requirements-*.txt",
|
|
46
|
+
"requirements/*.txt",
|
|
47
|
+
),
|
|
48
|
+
"constraints": (
|
|
49
|
+
"constraints.txt",
|
|
50
|
+
"constraints-*.txt",
|
|
51
|
+
),
|
|
52
|
+
"backup": ("*.backup",),
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#: Short include directive for requirement files.
|
|
56
|
+
INCLUDE_DIRECTIVE: Final[str] = "-r"
|
|
57
|
+
|
|
58
|
+
#: Long include directive for requirement files.
|
|
59
|
+
INCLUDE_DIRECTIVE_LONG: Final[str] = "--requirement"
|
|
60
|
+
|
|
61
|
+
#: Short constraint directive.
|
|
62
|
+
CONSTRAINT_DIRECTIVE: Final[str] = "-c"
|
|
63
|
+
|
|
64
|
+
#: Long constraint directive.
|
|
65
|
+
CONSTRAINT_DIRECTIVE_LONG: Final[str] = "--constraint"
|
|
66
|
+
|
|
67
|
+
#: Short editable-install directive.
|
|
68
|
+
EDITABLE_DIRECTIVE: Final[str] = "-e"
|
|
69
|
+
|
|
70
|
+
#: Long editable-install directive.
|
|
71
|
+
EDITABLE_DIRECTIVE_LONG: Final[str] = "--editable"
|
|
72
|
+
|
|
73
|
+
#: Hash-checking directive.
|
|
74
|
+
HASH_DIRECTIVE: Final[str] = "--hash"
|
|
75
|
+
|
|
76
|
+
# ---------------------------------------------------------------------------
|
|
77
|
+
# Security constraints
|
|
78
|
+
# ---------------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
#: Maximum allowed file size (in bytes) when reading requirement files.
|
|
81
|
+
MAX_FILE_SIZE: Final[int] = 10 * 1024 * 1024 # 10 MB
|
|
82
|
+
|
|
83
|
+
# ---------------------------------------------------------------------------
|
|
84
|
+
# Logging configuration
|
|
85
|
+
# ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
#: Timestamp format for verbose logging.
|
|
88
|
+
LOG_DATE_FORMAT: Final[str] = "%Y-%m-%d %H:%M:%S"
|
|
89
|
+
|
|
90
|
+
#: Default log format (non-verbose).
|
|
91
|
+
LOG_DEFAULT_FORMAT: Final[str] = "%(levelname)s: %(message)s"
|
|
92
|
+
|
|
93
|
+
#: Verbose log format including timestamp and logger name.
|
|
94
|
+
LOG_VERBOSE_FORMAT: Final[str] = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared context object for depkeeper CLI commands.
|
|
3
|
+
|
|
4
|
+
This module defines the global Click context used to share configuration
|
|
5
|
+
and runtime options across CLI subcommands.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
import click
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DepKeeperContext:
|
|
17
|
+
"""Global context object for depkeeper CLI commands.
|
|
18
|
+
|
|
19
|
+
An instance of this class is created once per CLI invocation and
|
|
20
|
+
passed to commands using Click's context mechanism.
|
|
21
|
+
|
|
22
|
+
Attributes:
|
|
23
|
+
config_path: Path to the depkeeper configuration file, if provided.
|
|
24
|
+
verbose: Verbosity level (0=WARNING, 1=INFO, 2+=DEBUG).
|
|
25
|
+
color: Whether colored terminal output is enabled.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
__slots__ = ("config_path", "verbose", "color")
|
|
29
|
+
|
|
30
|
+
def __init__(self) -> None:
|
|
31
|
+
self.config_path: Optional[Path] = None
|
|
32
|
+
self.verbose: int = 0
|
|
33
|
+
self.color: bool = True
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
#: Click decorator for injecting :class:`DepKeeperContext` into commands.
|
|
37
|
+
pass_context = click.make_pass_decorator(DepKeeperContext, ensure=True)
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom exception hierarchy for depkeeper.
|
|
3
|
+
|
|
4
|
+
This module defines structured exception types used across depkeeper.
|
|
5
|
+
All exceptions inherit from :class:`DepKeeperError` and support optional
|
|
6
|
+
structured metadata via the ``details`` attribute to improve diagnostics
|
|
7
|
+
and logging.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Any, Mapping, MutableMapping, Optional
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DepKeeperError(Exception):
|
|
16
|
+
"""Base exception for all depkeeper errors.
|
|
17
|
+
|
|
18
|
+
All depkeeper-specific exceptions should inherit from this class.
|
|
19
|
+
It supports structured metadata via ``details`` for richer error
|
|
20
|
+
reporting and debugging.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
message: Human-readable error message.
|
|
24
|
+
details: Optional structured metadata describing the error.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
__slots__ = ("message", "details")
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
message: str,
|
|
32
|
+
details: Optional[Mapping[str, Any]] = None,
|
|
33
|
+
) -> None:
|
|
34
|
+
self.message: str = message
|
|
35
|
+
# Internally normalize to a mutable dict
|
|
36
|
+
self.details: MutableMapping[str, Any] = dict(details) if details else {}
|
|
37
|
+
super().__init__(message)
|
|
38
|
+
|
|
39
|
+
def __str__(self) -> str:
|
|
40
|
+
if not self.details:
|
|
41
|
+
return self.message
|
|
42
|
+
formatted = ", ".join(f"{k}={v}" for k, v in self.details.items())
|
|
43
|
+
return f"{self.message} ({formatted})"
|
|
44
|
+
|
|
45
|
+
def __repr__(self) -> str:
|
|
46
|
+
return (
|
|
47
|
+
f"{self.__class__.__name__}("
|
|
48
|
+
f"message={self.message!r}, details={dict(self.details)!r})"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _add_if(details: MutableMapping[str, Any], key: str, value: Any) -> None:
|
|
53
|
+
"""Add a key to ``details`` only if ``value`` is not ``None``."""
|
|
54
|
+
if value is not None:
|
|
55
|
+
details[key] = value
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _truncate(text: str, max_length: int = 200) -> str:
|
|
59
|
+
"""Truncate long text for safe logging or error reporting."""
|
|
60
|
+
if len(text) <= max_length:
|
|
61
|
+
return text
|
|
62
|
+
return text[:max_length] + "..."
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ParseError(DepKeeperError):
|
|
66
|
+
"""Raised when a requirements file cannot be parsed.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
message: Error description.
|
|
70
|
+
line_number: Line number where parsing failed.
|
|
71
|
+
line_content: Raw content of the problematic line.
|
|
72
|
+
file_path: Path to the file being parsed.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
__slots__ = ("line_number", "line_content", "file_path")
|
|
76
|
+
|
|
77
|
+
def __init__(
|
|
78
|
+
self,
|
|
79
|
+
message: str,
|
|
80
|
+
*,
|
|
81
|
+
line_number: Optional[int] = None,
|
|
82
|
+
line_content: Optional[str] = None,
|
|
83
|
+
file_path: Optional[str] = None,
|
|
84
|
+
) -> None:
|
|
85
|
+
details: MutableMapping[str, Any] = {}
|
|
86
|
+
_add_if(details, "line", line_number)
|
|
87
|
+
_add_if(details, "content", line_content)
|
|
88
|
+
_add_if(details, "file", file_path)
|
|
89
|
+
|
|
90
|
+
super().__init__(message, details)
|
|
91
|
+
|
|
92
|
+
self.line_number = line_number
|
|
93
|
+
self.line_content = line_content
|
|
94
|
+
self.file_path = file_path
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class NetworkError(DepKeeperError):
|
|
98
|
+
"""Raised when HTTP or network operations fail.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
message: Error description.
|
|
102
|
+
url: URL being accessed.
|
|
103
|
+
status_code: HTTP status code, if available.
|
|
104
|
+
response_body: Raw response body, truncated for safety.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
__slots__ = ("url", "status_code", "response_body")
|
|
108
|
+
|
|
109
|
+
def __init__(
|
|
110
|
+
self,
|
|
111
|
+
message: str,
|
|
112
|
+
*,
|
|
113
|
+
url: Optional[str] = None,
|
|
114
|
+
status_code: Optional[int] = None,
|
|
115
|
+
response_body: Optional[str] = None,
|
|
116
|
+
) -> None:
|
|
117
|
+
details: MutableMapping[str, Any] = {}
|
|
118
|
+
_add_if(details, "url", url)
|
|
119
|
+
_add_if(details, "status_code", status_code)
|
|
120
|
+
|
|
121
|
+
if response_body is not None:
|
|
122
|
+
details["response"] = _truncate(response_body)
|
|
123
|
+
|
|
124
|
+
super().__init__(message, details)
|
|
125
|
+
|
|
126
|
+
self.url = url
|
|
127
|
+
self.status_code = status_code
|
|
128
|
+
self.response_body = response_body
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class PyPIError(NetworkError):
|
|
132
|
+
"""Raised for failures related to the PyPI API.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
message: Error description.
|
|
136
|
+
package_name: Name of the package involved.
|
|
137
|
+
**kwargs: Additional arguments forwarded to ``NetworkError``.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
__slots__ = ("package_name",)
|
|
141
|
+
|
|
142
|
+
def __init__(
|
|
143
|
+
self,
|
|
144
|
+
message: str,
|
|
145
|
+
*,
|
|
146
|
+
package_name: Optional[str] = None,
|
|
147
|
+
**kwargs: Any,
|
|
148
|
+
) -> None:
|
|
149
|
+
super().__init__(message, **kwargs)
|
|
150
|
+
|
|
151
|
+
self.package_name = package_name
|
|
152
|
+
if package_name is not None:
|
|
153
|
+
self.details["package"] = package_name
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class FileOperationError(DepKeeperError):
|
|
157
|
+
"""Raised when file system operations fail.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
message: Error description.
|
|
161
|
+
file_path: Path to the file involved.
|
|
162
|
+
operation: Operation being performed (read/write/delete).
|
|
163
|
+
original_error: Original exception that triggered this error.
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
__slots__ = ("file_path", "operation", "original_error")
|
|
167
|
+
|
|
168
|
+
def __init__(
|
|
169
|
+
self,
|
|
170
|
+
message: str,
|
|
171
|
+
*,
|
|
172
|
+
file_path: Optional[str] = None,
|
|
173
|
+
operation: Optional[str] = None,
|
|
174
|
+
original_error: Optional[Exception] = None,
|
|
175
|
+
) -> None:
|
|
176
|
+
details: MutableMapping[str, Any] = {}
|
|
177
|
+
_add_if(details, "path", file_path)
|
|
178
|
+
_add_if(details, "operation", operation)
|
|
179
|
+
_add_if(
|
|
180
|
+
details,
|
|
181
|
+
"original_error",
|
|
182
|
+
str(original_error) if original_error else None,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
super().__init__(message, details)
|
|
186
|
+
|
|
187
|
+
self.file_path = file_path
|
|
188
|
+
self.operation = operation
|
|
189
|
+
self.original_error = original_error
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: depkeeper
|
|
3
|
-
Version: 0.1.0.
|
|
3
|
+
Version: 0.1.0.dev1
|
|
4
4
|
Summary: Modern Python dependency management for requirements.txt files
|
|
5
5
|
Author: depkeeper Contributors
|
|
6
6
|
License: Apache-2.0
|
|
@@ -25,18 +25,16 @@ Classifier: Topic :: System :: Software Distribution
|
|
|
25
25
|
Requires-Python: >=3.8
|
|
26
26
|
Description-Content-Type: text/markdown
|
|
27
27
|
License-File: LICENSE
|
|
28
|
-
Requires-Dist: click>=8.1.
|
|
29
|
-
Requires-Dist: packaging>=23.
|
|
28
|
+
Requires-Dist: click>=8.1.8
|
|
29
|
+
Requires-Dist: packaging>=23.2
|
|
30
30
|
Requires-Dist: httpx>=0.24.0
|
|
31
|
-
Requires-Dist: rich>=13.
|
|
32
|
-
Requires-Dist: pydantic>=2.0.0
|
|
31
|
+
Requires-Dist: rich>=13.9.4
|
|
33
32
|
Provides-Extra: dev
|
|
34
33
|
Requires-Dist: pytest>=7.4.0; extra == "dev"
|
|
35
34
|
Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
|
|
36
35
|
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
37
36
|
Requires-Dist: pytest-mock>=3.11.0; extra == "dev"
|
|
38
37
|
Requires-Dist: pytest-httpx>=0.22.0; extra == "dev"
|
|
39
|
-
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
40
38
|
Requires-Dist: mypy>=1.5.0; extra == "dev"
|
|
41
39
|
Requires-Dist: types-setuptools; extra == "dev"
|
|
42
40
|
Requires-Dist: pre-commit>=3.4.0; extra == "dev"
|
|
@@ -47,9 +45,9 @@ Requires-Dist: pytest-asyncio>=0.21.0; extra == "test"
|
|
|
47
45
|
Requires-Dist: pytest-mock>=3.11.0; extra == "test"
|
|
48
46
|
Requires-Dist: pytest-httpx>=0.22.0; extra == "test"
|
|
49
47
|
Provides-Extra: docs
|
|
50
|
-
Requires-Dist: mkdocs>=1.
|
|
51
|
-
Requires-Dist: mkdocs-material>=9.
|
|
52
|
-
Requires-Dist: mkdocstrings[python]>=0.
|
|
48
|
+
Requires-Dist: mkdocs>=1.6.1; extra == "docs"
|
|
49
|
+
Requires-Dist: mkdocs-material>=9.7.1; extra == "docs"
|
|
50
|
+
Requires-Dist: mkdocstrings[python]>=0.26.1; extra == "docs"
|
|
53
51
|
|
|
54
52
|
# depkeeper 🛡️🐍
|
|
55
53
|
|
|
@@ -167,7 +165,6 @@ This project is licensed under the **Apache-2.0 License** — see the [LICENSE](
|
|
|
167
165
|
|
|
168
166
|
## ❤️ Support
|
|
169
167
|
|
|
170
|
-
- 📬 Email: [support@depkeeper.dev](mailto:support@depkeeper.dev)
|
|
171
168
|
- 💬 GitHub Discussions: [https://github.com/rahulkaushal04/depkeeper/discussions](https://github.com/rahulkaushal04/depkeeper/discussions)
|
|
172
169
|
- 🐞 Issues: [https://github.com/rahulkaushal04/depkeeper/issues](https://github.com/rahulkaushal04/depkeeper/issues)
|
|
173
170
|
|
|
@@ -4,6 +4,10 @@ pyproject.toml
|
|
|
4
4
|
depkeeper/__init__.py
|
|
5
5
|
depkeeper/__main__.py
|
|
6
6
|
depkeeper/__version__.py
|
|
7
|
+
depkeeper/cli.py
|
|
8
|
+
depkeeper/constants.py
|
|
9
|
+
depkeeper/context.py
|
|
10
|
+
depkeeper/exceptions.py
|
|
7
11
|
depkeeper.egg-info/PKG-INFO
|
|
8
12
|
depkeeper.egg-info/SOURCES.txt
|
|
9
13
|
depkeeper.egg-info/dependency_links.txt
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
click>=8.1.
|
|
2
|
-
packaging>=23.
|
|
1
|
+
click>=8.1.8
|
|
2
|
+
packaging>=23.2
|
|
3
3
|
httpx>=0.24.0
|
|
4
|
-
rich>=13.
|
|
5
|
-
pydantic>=2.0.0
|
|
4
|
+
rich>=13.9.4
|
|
6
5
|
|
|
7
6
|
[dev]
|
|
8
7
|
pytest>=7.4.0
|
|
@@ -10,15 +9,14 @@ pytest-cov>=4.1.0
|
|
|
10
9
|
pytest-asyncio>=0.21.0
|
|
11
10
|
pytest-mock>=3.11.0
|
|
12
11
|
pytest-httpx>=0.22.0
|
|
13
|
-
ruff>=0.1.0
|
|
14
12
|
mypy>=1.5.0
|
|
15
13
|
types-setuptools
|
|
16
14
|
pre-commit>=3.4.0
|
|
17
15
|
|
|
18
16
|
[docs]
|
|
19
|
-
mkdocs>=1.
|
|
20
|
-
mkdocs-material>=9.
|
|
21
|
-
mkdocstrings[python]>=0.
|
|
17
|
+
mkdocs>=1.6.1
|
|
18
|
+
mkdocs-material>=9.7.1
|
|
19
|
+
mkdocstrings[python]>=0.26.1
|
|
22
20
|
|
|
23
21
|
[test]
|
|
24
22
|
pytest>=7.4.0
|
|
@@ -13,7 +13,7 @@ build-backend = "setuptools.build_meta"
|
|
|
13
13
|
|
|
14
14
|
[project]
|
|
15
15
|
name = "depkeeper"
|
|
16
|
-
version = "0.1.0.
|
|
16
|
+
version = "0.1.0.dev1"
|
|
17
17
|
description = "Modern Python dependency management for requirements.txt files"
|
|
18
18
|
readme = "README.md"
|
|
19
19
|
requires-python = ">=3.8"
|
|
@@ -49,11 +49,10 @@ classifiers = [
|
|
|
49
49
|
|
|
50
50
|
# Library dependencies (minimum versions only)
|
|
51
51
|
dependencies = [
|
|
52
|
-
"click>=8.1.
|
|
53
|
-
"packaging>=23.
|
|
52
|
+
"click>=8.1.8",
|
|
53
|
+
"packaging>=23.2",
|
|
54
54
|
"httpx>=0.24.0",
|
|
55
|
-
"rich>=13.
|
|
56
|
-
"pydantic>=2.0.0",
|
|
55
|
+
"rich>=13.9.4",
|
|
57
56
|
]
|
|
58
57
|
|
|
59
58
|
|
|
@@ -69,7 +68,6 @@ dev = [
|
|
|
69
68
|
"pytest-asyncio>=0.21.0",
|
|
70
69
|
"pytest-mock>=3.11.0",
|
|
71
70
|
"pytest-httpx>=0.22.0",
|
|
72
|
-
"ruff>=0.1.0",
|
|
73
71
|
"mypy>=1.5.0",
|
|
74
72
|
"types-setuptools",
|
|
75
73
|
"pre-commit>=3.4.0",
|
|
@@ -82,9 +80,9 @@ test = [
|
|
|
82
80
|
"pytest-httpx>=0.22.0",
|
|
83
81
|
]
|
|
84
82
|
docs = [
|
|
85
|
-
"mkdocs>=1.
|
|
86
|
-
"mkdocs-material>=9.
|
|
87
|
-
"mkdocstrings[python]>=0.
|
|
83
|
+
"mkdocs>=1.6.1",
|
|
84
|
+
"mkdocs-material>=9.7.1",
|
|
85
|
+
"mkdocstrings[python]>=0.26.1",
|
|
88
86
|
]
|
|
89
87
|
|
|
90
88
|
|
|
@@ -119,39 +117,6 @@ packages = ["depkeeper"]
|
|
|
119
117
|
depkeeper = ["py.typed"]
|
|
120
118
|
|
|
121
119
|
|
|
122
|
-
# ============================================================
|
|
123
|
-
# Ruff (formatter + linter)
|
|
124
|
-
# ============================================================
|
|
125
|
-
|
|
126
|
-
[tool.ruff]
|
|
127
|
-
line-length = 100
|
|
128
|
-
target-version = "py38"
|
|
129
|
-
|
|
130
|
-
[tool.ruff.lint]
|
|
131
|
-
select = [
|
|
132
|
-
"E","W","F","I","N","UP","B","C4","DTZ","T10","EM","ISC",
|
|
133
|
-
"ICN","G","PIE","T20","PT","Q","RSE","RET","SIM","TID",
|
|
134
|
-
"TCH","ARG","PTH","ERA","PL","TRY","RUF"
|
|
135
|
-
]
|
|
136
|
-
ignore = [
|
|
137
|
-
"E501", # line too long (handled by formatter)
|
|
138
|
-
"ERA001", # commented-out code
|
|
139
|
-
"PLC0415", # import at top-level (allow lazy imports)
|
|
140
|
-
"PLR0913", # too many arguments
|
|
141
|
-
"TRY003", # avoid long exception messages
|
|
142
|
-
]
|
|
143
|
-
|
|
144
|
-
[tool.ruff.lint.per-file-ignores]
|
|
145
|
-
"tests/**/*.py" = [
|
|
146
|
-
"PLR2004", # magic numbers in tests
|
|
147
|
-
"S101", # assert usage
|
|
148
|
-
"ARG", # unused args OK in fixtures
|
|
149
|
-
]
|
|
150
|
-
|
|
151
|
-
[tool.ruff.lint.isort]
|
|
152
|
-
known-first-party = ["depkeeper"]
|
|
153
|
-
|
|
154
|
-
|
|
155
120
|
# ============================================================
|
|
156
121
|
# Mypy configuration
|
|
157
122
|
# ============================================================
|
|
@@ -201,6 +166,7 @@ markers = [
|
|
|
201
166
|
"integration: integration tests",
|
|
202
167
|
"e2e: end-to-end tests",
|
|
203
168
|
"network: tests requiring network",
|
|
169
|
+
"asyncio: asyncio tests",
|
|
204
170
|
]
|
|
205
171
|
|
|
206
172
|
asyncio_mode = "auto"
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
depkeeper — Modern Python Dependency Management Tool
|
|
3
|
-
|
|
4
|
-
depkeeper is an intelligent, safety-focused dependency manager that helps
|
|
5
|
-
developers keep their Python environments up-to-date, secure, and
|
|
6
|
-
reproducible.
|
|
7
|
-
|
|
8
|
-
Features include:
|
|
9
|
-
• Smart dependency updates with semantic versioning
|
|
10
|
-
• Security vulnerability scanning (OSV / PyPA)
|
|
11
|
-
• Lock file generation for reproducible builds
|
|
12
|
-
• Dependency health scoring
|
|
13
|
-
• Multi-format support (requirements.txt, pyproject.toml, Pipfile, etc.)
|
|
14
|
-
• CI/CD integration and automation
|
|
15
|
-
|
|
16
|
-
For documentation, examples, and API reference:
|
|
17
|
-
https://docs.depkeeper.dev
|
|
18
|
-
"""
|
|
19
|
-
|
|
20
|
-
from __future__ import annotations
|
|
21
|
-
|
|
22
|
-
from depkeeper.__version__ import __version__
|
|
23
|
-
|
|
24
|
-
# ---------------------------------------------------------------------------
|
|
25
|
-
# Package Metadata
|
|
26
|
-
# ---------------------------------------------------------------------------
|
|
27
|
-
|
|
28
|
-
__author__ = "depkeeper Contributors"
|
|
29
|
-
__license__ = "Apache-2.0"
|
|
30
|
-
__url__ = "https://github.com/rahulkaushal04/depkeeper"
|
|
31
|
-
__description__ = "Modern Python dependency management for requirements.txt and beyond."
|
|
32
|
-
|
|
33
|
-
# ---------------------------------------------------------------------------
|
|
34
|
-
# Public API (Phase 1+)
|
|
35
|
-
#
|
|
36
|
-
# Only expose stable, documented interfaces here.
|
|
37
|
-
# Once core API classes are implemented, they should be exported via __all__.
|
|
38
|
-
# ---------------------------------------------------------------------------
|
|
39
|
-
|
|
40
|
-
__all__ = [
|
|
41
|
-
"__version__",
|
|
42
|
-
# "DepKeeperClient", # Example (future)
|
|
43
|
-
]
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Executable module for depkeeper.
|
|
3
|
-
|
|
4
|
-
Running:
|
|
5
|
-
python -m depkeeper
|
|
6
|
-
|
|
7
|
-
is equivalent to:
|
|
8
|
-
depkeeper
|
|
9
|
-
|
|
10
|
-
This module simply forwards execution to the CLI entrypoint defined in
|
|
11
|
-
`depkeeper.cli`.
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
from __future__ import annotations
|
|
15
|
-
|
|
16
|
-
import sys
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def main() -> int:
|
|
20
|
-
"""
|
|
21
|
-
Main entrypoint when executing `python -m depkeeper`.
|
|
22
|
-
|
|
23
|
-
Returns:
|
|
24
|
-
Exit code returned by the CLI.
|
|
25
|
-
"""
|
|
26
|
-
try:
|
|
27
|
-
# Import lazily so dependencies are only loaded during CLI use
|
|
28
|
-
from depkeeper.cli import main as cli_main
|
|
29
|
-
except ImportError as exc:
|
|
30
|
-
# CLI not implemented yet (Phase 0) or import error during development
|
|
31
|
-
sys.stderr.write("depkeeper CLI is not available yet.")
|
|
32
|
-
sys.stderr.write("This is expected during early development (Phase 0).")
|
|
33
|
-
sys.stderr.write()
|
|
34
|
-
sys.stderr.write(f"Python version : {sys.version}")
|
|
35
|
-
try:
|
|
36
|
-
from depkeeper.__version__ import __version__
|
|
37
|
-
|
|
38
|
-
sys.stderr.write(f"depkeeper version: {__version__}")
|
|
39
|
-
except Exception:
|
|
40
|
-
sys.stderr.write("depkeeper version: <unknown>")
|
|
41
|
-
sys.stderr.write()
|
|
42
|
-
sys.stderr.write(f"ImportError: {exc}")
|
|
43
|
-
return 0
|
|
44
|
-
|
|
45
|
-
# Execute the CLI handler
|
|
46
|
-
return cli_main()
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if __name__ == "__main__":
|
|
50
|
-
sys.exit(main())
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
depkeeper version information.
|
|
3
|
-
|
|
4
|
-
This module provides a single source of truth for the package version.
|
|
5
|
-
It follows Semantic Versioning: https://semver.org/
|
|
6
|
-
|
|
7
|
-
Version format:
|
|
8
|
-
MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]
|
|
9
|
-
|
|
10
|
-
Examples:
|
|
11
|
-
0.1.0
|
|
12
|
-
0.1.0.dev0
|
|
13
|
-
1.0.0-rc1
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
from __future__ import annotations
|
|
17
|
-
|
|
18
|
-
import re
|
|
19
|
-
|
|
20
|
-
# ---------------------------------------------------------------------------
|
|
21
|
-
# Main version (single source of truth)
|
|
22
|
-
# ---------------------------------------------------------------------------
|
|
23
|
-
|
|
24
|
-
__version__ = "0.1.0.dev0"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
# ---------------------------------------------------------------------------
|
|
28
|
-
# Structured version metadata
|
|
29
|
-
# ---------------------------------------------------------------------------
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def _parse_version(version: str):
|
|
33
|
-
"""
|
|
34
|
-
Internal helper to break a semantic version into components.
|
|
35
|
-
Avoids manual duplication and mistakes.
|
|
36
|
-
|
|
37
|
-
Returns:
|
|
38
|
-
dict: {
|
|
39
|
-
"major": int,
|
|
40
|
-
"minor": int,
|
|
41
|
-
"patch": int,
|
|
42
|
-
"prerelease": str | None,
|
|
43
|
-
"is_dev": bool,
|
|
44
|
-
}
|
|
45
|
-
"""
|
|
46
|
-
|
|
47
|
-
pattern = r"^(\d+)\.(\d+)\.(\d+)(?:[.-]([a-zA-Z0-9]+))?$"
|
|
48
|
-
match = re.match(pattern, version)
|
|
49
|
-
|
|
50
|
-
if not match:
|
|
51
|
-
error_message = f"Invalid version string: {version}"
|
|
52
|
-
raise ValueError(error_message)
|
|
53
|
-
|
|
54
|
-
major, minor, patch, pre = match.groups()
|
|
55
|
-
|
|
56
|
-
return {
|
|
57
|
-
"major": int(major),
|
|
58
|
-
"minor": int(minor),
|
|
59
|
-
"patch": int(patch),
|
|
60
|
-
"prerelease": pre,
|
|
61
|
-
"is_dev": pre is not None and pre.startswith("dev"),
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
VERSION_INFO = _parse_version(__version__)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
# ---------------------------------------------------------------------------
|
|
69
|
-
# Human-readable version (for CLI)
|
|
70
|
-
# ---------------------------------------------------------------------------
|
|
71
|
-
|
|
72
|
-
VERSION_STRING = f"depkeeper {__version__}"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|