poetry-plugin-hook 0.0.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ from poetry.plugins.application_plugin import ApplicationPlugin
2
+
3
+ from .latest import LatestCommand
4
+ from .sync import SyncCommand
5
+
6
+
7
+ class HookPlugin(ApplicationPlugin):
8
+ def activate(self, application):
9
+
10
+ application.command_loader.register_factory(
11
+ LatestCommand.name,
12
+ lambda: LatestCommand(),
13
+ )
14
+ application.command_loader.register_factory(
15
+ SyncCommand.name,
16
+ lambda: SyncCommand(),
17
+ )
@@ -0,0 +1,72 @@
1
+ import re
2
+
3
+ from poetry.console.commands.show import ShowCommand
4
+
5
+ from .redirect import buffered_io, strip_ansi
6
+
7
+
8
+ class LatestCommand(ShowCommand):
9
+ name = "hook latest"
10
+ description = "Check if all top-level dependencies are up-to-date."
11
+ help = ""
12
+
13
+ _dependencies = re.compile(
14
+ r"^(?P<package>\w\S+)\s+"
15
+ r"(?P<current>\d\S+)\s+"
16
+ r"(?P<latest>\d\S+)\s+"
17
+ r"(?P<description>\w.*?)$",
18
+ re.MULTILINE,
19
+ )
20
+
21
+ _true_options = ["latest", "outdated", "top-level"]
22
+ _del_options = ["no-dev", "tree", "all", "why"]
23
+
24
+ def configure(self) -> None:
25
+ """
26
+ Modifiy all options from `poetry show` to fit the `poetry latest` command.
27
+
28
+ Returns:
29
+ None
30
+ """
31
+
32
+ self.options = [
33
+ option for option in self.options if option.name not in self._del_options
34
+ ]
35
+
36
+ for opt in filter(lambda o: o.name in self._true_options, self.options):
37
+ opt._description += " <warning>(option is always True)</warning>"
38
+
39
+ super().configure()
40
+
41
+ def handle(self) -> int:
42
+ """
43
+ Executes `poetry show -o -T` to check for outdated dependencies.
44
+
45
+ Catches stdout to check for dependencies and returns non-zero.
46
+
47
+ Returns:
48
+ int: Non-zero if there are outdated dependencies, zero otherwise.
49
+ """
50
+
51
+ # force options to True, `poetry show -o -T`
52
+ for option in self._true_options:
53
+ self.io.input.set_option(option, True)
54
+
55
+ # redirect output to check for outdated dependencies
56
+ with buffered_io(self) as io:
57
+ super().handle()
58
+ text = io.fetch_output()
59
+
60
+ # count outdated dependencies
61
+ outdated = len(
62
+ self._dependencies.findall(
63
+ strip_ansi(text),
64
+ )
65
+ )
66
+
67
+ if outdated == 0:
68
+ self.line("All top-level dependencies are up-to-date.")
69
+ else:
70
+ self.line(text)
71
+
72
+ return outdated
@@ -0,0 +1,82 @@
1
+ import contextlib
2
+ import re
3
+ from abc import ABC, abstractmethod
4
+ from typing import Generator, List
5
+
6
+ from cleo.io.buffered_io import BufferedIO
7
+ from cleo.io.io import IO
8
+
9
+
10
+ class CommandCleo(ABC):
11
+ @property
12
+ @abstractmethod
13
+ def _io(self) -> IO:
14
+ pass
15
+
16
+
17
+ def strip_ansi(text: str) -> str:
18
+ """
19
+ Remove ANSI escape sequences from a string.
20
+
21
+ Args:
22
+ text (str): The string to remove ANSI escape sequences from.
23
+
24
+ Returns:
25
+ str: The string without ANSI escape sequences.
26
+ """
27
+ return re.sub(r"\x1B[@-_][0-?]*[ -/]*[@-~]", "", text)
28
+
29
+
30
+ @contextlib.contextmanager
31
+ def buffered_io(
32
+ *args: CommandCleo,
33
+ **kwargs,
34
+ ) -> Generator[BufferedIO, None, None]:
35
+ """
36
+ Context manager that temporarily replaces the I/O of multiple Poetry commands with
37
+ the same buffered I/O to capture their output.
38
+
39
+ Args:
40
+ *cmds (List[CommandCleo]): The Poetry commands whose I/O will be captured.
41
+ **kwargs: Additional keyword arguments to pass to the BufferedIO constructor.
42
+
43
+ Yields:
44
+ BufferedIO: The buffered I/O object.
45
+
46
+ Raises:
47
+ ValueError: If any of the commands does not have an I/O attribute.
48
+
49
+ Example:
50
+ ```python
51
+ with buffered_ios(cmd1, cmd2, decorated=False) as io:
52
+ # Perform operations with the buffered I/O
53
+ output = io.fetch_output()
54
+ ```
55
+ """
56
+
57
+ # perform check if all commands have a `_io` attribute
58
+ original: List[IO] = []
59
+
60
+ for cmd in args:
61
+ if not hasattr(cmd, "_io"):
62
+ raise ValueError(f"Command {cmd} does not have an I/O attribute.")
63
+
64
+ original.append(cmd._io)
65
+
66
+ # create a new buffered I/O object
67
+ io = BufferedIO(
68
+ input=kwargs.pop("input", original[0].input),
69
+ decorated=kwargs.pop("decorated", original[0].output.is_decorated()),
70
+ **kwargs,
71
+ )
72
+
73
+ try:
74
+ # assign the buffered I/O object to all commands
75
+ for cmd in args:
76
+ cmd._io = io
77
+
78
+ yield io
79
+ finally:
80
+ # restore the original I/O objects
81
+ for cmd, original_io in zip(args, original):
82
+ cmd._io = original_io
@@ -0,0 +1,90 @@
1
+ import re
2
+
3
+ from cleo.exceptions import CleoError
4
+ from cleo.helpers import option
5
+ from poetry.console.commands.install import InstallCommand
6
+
7
+ from .redirect import buffered_io, strip_ansi
8
+
9
+
10
+ class SyncCommand(InstallCommand):
11
+ name = "hook sync"
12
+ description = "Install the current project and synchronize the environment."
13
+ help = ""
14
+
15
+ _true_options = ["sync"]
16
+ _del_options = ["no-dev", "remove-untracked", "compile"]
17
+ _exit_codes = ["any", "installs", "updates", "removals"]
18
+
19
+ _operations = re.compile(
20
+ r"^Package operations: "
21
+ r"(?P<installs>\d+)\D+"
22
+ r"(?P<updates>\d+)\D+"
23
+ r"(?P<removals>\d+)\D+"
24
+ r"(?P<skipped>\d+)\D+$",
25
+ re.MULTILINE,
26
+ )
27
+
28
+ def configure(self) -> None:
29
+
30
+ self.options = [
31
+ option(
32
+ "exit",
33
+ description=(
34
+ "Specify the value to return as exitcode. "
35
+ f"<info>choices={str(self._exit_codes)}</info>"
36
+ ),
37
+ flag=False,
38
+ default="any",
39
+ )
40
+ ] + [option for option in self.options if option.name not in self._del_options]
41
+
42
+ for opt in filter(lambda o: o.name in self._true_options, self.options):
43
+ opt._description += " <warning>(option is always True)</warning>"
44
+
45
+ super().configure()
46
+
47
+ def handle(self) -> int:
48
+
49
+ # check if the exit option is valid
50
+ exit = self.io.input.option("exit")
51
+ if exit not in self._exit_codes:
52
+ raise CleoError(f"Invalid option: {exit=}")
53
+
54
+ # force options to `poetry install --sync`
55
+ for opt in self._true_options:
56
+ self.io.input.set_option(opt, True)
57
+
58
+ with buffered_io(
59
+ self.installer.executor,
60
+ self.installer,
61
+ self,
62
+ ) as io:
63
+ super().handle()
64
+ stdout = io.fetch_output()
65
+
66
+ # parse the output for matching lines and take the last one
67
+ match: re.Match = list(
68
+ self._operations.finditer(
69
+ strip_ansi(stdout),
70
+ )
71
+ )[-1]
72
+
73
+ # retrive the exit code
74
+ try:
75
+ result = int(match.group(exit))
76
+ except IndexError:
77
+ result = 0
78
+
79
+ for code in self._exit_codes:
80
+ try:
81
+ result += int(match.group(code))
82
+ except IndexError:
83
+ pass
84
+
85
+ if result == 0:
86
+ self.line(match.group(0))
87
+ else:
88
+ self.line(stdout)
89
+
90
+ return result
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Christoph Dörrer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,55 @@
1
+ Metadata-Version: 2.1
2
+ Name: poetry-plugin-hook
3
+ Version: 0.0.0
4
+ Summary: poetry plugin to register new command to check if all dependencies are up-to-date
5
+ Author: Christoph Dörrer
6
+ Author-email: d-chris@web.de
7
+ Requires-Python: >=3.9,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.9
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Requires-Dist: cleo (>=2.1.0,<3.0.0)
14
+ Requires-Dist: poetry (>=1.8.0,<2.0.0)
15
+ Description-Content-Type: text/markdown
16
+
17
+ # poetry-plugin-latest
18
+
19
+ poetry plugin to add a command to check if all dependencies are up-to-date
20
+
21
+ ```cmd
22
+ $ poetry latest --help
23
+
24
+ Description:
25
+ Check if all top-level dependencies are up-to-date
26
+
27
+ Usage:
28
+ latest [options] [--] [<package>]
29
+
30
+ Arguments:
31
+ package The package to inspect
32
+
33
+ Options:
34
+ --without=WITHOUT The dependency groups to ignore. (multiple values allowed)
35
+ --with=WITH The optional dependency groups to include. (multiple values allowed)
36
+ --only=ONLY The only dependency groups to include. (multiple values allowed)
37
+ -l, --latest Show the latest version. (option is always True)
38
+ -o, --outdated Show the latest version but only for packages that are outdated. (option is always True)
39
+ -T, --top-level Show only top-level dependencies. (option is always True)
40
+ -h, --help Display help for the given command. When no command is given display help for the list command.
41
+ -q, --quiet Do not output any message.
42
+ -V, --version Display this application version.
43
+ --ansi Force ANSI output.
44
+ --no-ansi Disable ANSI output.
45
+ -n, --no-interaction Do not ask any interactive question.
46
+ --no-plugins Disables plugins.
47
+ --no-cache Disables Poetry source caches.
48
+ -C, --directory=DIRECTORY The working directory for the Poetry command (defaults to the current working directory).
49
+ -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug.
50
+
51
+ Help:
52
+ The show command displays detailed information about a package, or
53
+ lists all packages available.
54
+ ```
55
+
@@ -0,0 +1,9 @@
1
+ poetry_plugin_hook/__init__.py,sha256=voDfXAXN5fseTNp3WoYqmws0RuXlbpwaWCI4BlFTtIg,483
2
+ poetry_plugin_hook/latest.py,sha256=3Bq_Lp_VN7N47lcAd4eNRnT3yxuKokZpLQHAhvcD7Pg,2053
3
+ poetry_plugin_hook/redirect.py,sha256=gauxL6c7Zy33hwvOqYCBng83ajdELJcRTWDpU2J4J5k,2215
4
+ poetry_plugin_hook/sync.py,sha256=g22_JTJfWL9RM21h_jWncGnRa_KlDIIHyl_9PSV3ZW0,2640
5
+ poetry_plugin_hook-0.0.0.dist-info/entry_points.txt,sha256=kl0zCMvjQc4wRSKYgkhJYdTacDKVxgBuc27bBMwlqdc,64
6
+ poetry_plugin_hook-0.0.0.dist-info/LICENSE,sha256=r4et7kLDvZTYdQubvsPzH-Mq2FVYd6u4VsVBkiAuuf8,1095
7
+ poetry_plugin_hook-0.0.0.dist-info/METADATA,sha256=ufgjCPaR32BqPae_8QwxWAssq5FgTxyzWrWIKna_ieg,2358
8
+ poetry_plugin_hook-0.0.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
9
+ poetry_plugin_hook-0.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 1.8.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [poetry.application.plugin]
2
+ hook=poetry_plugin_hook:HookPlugin
3
+