zabbix-cli-uio 3.1.1__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.
Files changed (89) hide show
  1. zabbix_cli/__about__.py +5 -0
  2. zabbix_cli/__init__.py +5 -0
  3. zabbix_cli/__main__.py +6 -0
  4. zabbix_cli/_patches/__init__.py +10 -0
  5. zabbix_cli/_patches/click_repl.py +149 -0
  6. zabbix_cli/_patches/common.py +80 -0
  7. zabbix_cli/_patches/typer.py +317 -0
  8. zabbix_cli/_types.py +12 -0
  9. zabbix_cli/_v2_compat.py +102 -0
  10. zabbix_cli/app/__init__.py +32 -0
  11. zabbix_cli/app/app.py +232 -0
  12. zabbix_cli/app/plugins.py +180 -0
  13. zabbix_cli/auth.py +366 -0
  14. zabbix_cli/bulk.py +226 -0
  15. zabbix_cli/cache.py +86 -0
  16. zabbix_cli/commands/__init__.py +13 -0
  17. zabbix_cli/commands/cli.py +298 -0
  18. zabbix_cli/commands/common/__init__.py +0 -0
  19. zabbix_cli/commands/common/args.py +24 -0
  20. zabbix_cli/commands/export.py +688 -0
  21. zabbix_cli/commands/host.py +846 -0
  22. zabbix_cli/commands/hostgroup.py +494 -0
  23. zabbix_cli/commands/item.py +76 -0
  24. zabbix_cli/commands/macro.py +258 -0
  25. zabbix_cli/commands/maintenance.py +238 -0
  26. zabbix_cli/commands/problem.py +284 -0
  27. zabbix_cli/commands/proxy.py +658 -0
  28. zabbix_cli/commands/results/__init__.py +11 -0
  29. zabbix_cli/commands/results/cli.py +149 -0
  30. zabbix_cli/commands/results/export.py +53 -0
  31. zabbix_cli/commands/results/host.py +59 -0
  32. zabbix_cli/commands/results/hostgroup.py +177 -0
  33. zabbix_cli/commands/results/item.py +122 -0
  34. zabbix_cli/commands/results/macro.py +115 -0
  35. zabbix_cli/commands/results/maintenance.py +105 -0
  36. zabbix_cli/commands/results/problem.py +20 -0
  37. zabbix_cli/commands/results/proxy.py +184 -0
  38. zabbix_cli/commands/results/template.py +123 -0
  39. zabbix_cli/commands/results/templategroup.py +110 -0
  40. zabbix_cli/commands/results/user.py +236 -0
  41. zabbix_cli/commands/template.py +609 -0
  42. zabbix_cli/commands/templategroup.py +377 -0
  43. zabbix_cli/commands/user.py +861 -0
  44. zabbix_cli/config/__init__.py +0 -0
  45. zabbix_cli/config/__main__.py +9 -0
  46. zabbix_cli/config/constants.py +40 -0
  47. zabbix_cli/config/model.py +524 -0
  48. zabbix_cli/config/run.py +30 -0
  49. zabbix_cli/config/utils.py +128 -0
  50. zabbix_cli/dirs.py +80 -0
  51. zabbix_cli/exceptions.py +308 -0
  52. zabbix_cli/logs.py +171 -0
  53. zabbix_cli/main.py +256 -0
  54. zabbix_cli/models.py +302 -0
  55. zabbix_cli/output/__init__.py +1 -0
  56. zabbix_cli/output/console.py +201 -0
  57. zabbix_cli/output/formatting/__init__.py +1 -0
  58. zabbix_cli/output/formatting/bytes.py +11 -0
  59. zabbix_cli/output/formatting/constants.py +5 -0
  60. zabbix_cli/output/formatting/dates.py +38 -0
  61. zabbix_cli/output/formatting/grammar.py +51 -0
  62. zabbix_cli/output/formatting/path.py +15 -0
  63. zabbix_cli/output/prompts.py +385 -0
  64. zabbix_cli/output/render.py +155 -0
  65. zabbix_cli/output/style.py +207 -0
  66. zabbix_cli/py.typed +0 -0
  67. zabbix_cli/pyzabbix/__init__.py +3 -0
  68. zabbix_cli/pyzabbix/client.py +2509 -0
  69. zabbix_cli/pyzabbix/compat.py +134 -0
  70. zabbix_cli/pyzabbix/enums.py +527 -0
  71. zabbix_cli/pyzabbix/types.py +1285 -0
  72. zabbix_cli/pyzabbix/utils.py +36 -0
  73. zabbix_cli/scripts/__init__.py +1 -0
  74. zabbix_cli/scripts/bulk_execution.py +60 -0
  75. zabbix_cli/scripts/init.py +65 -0
  76. zabbix_cli/state.py +260 -0
  77. zabbix_cli/table.py +29 -0
  78. zabbix_cli/utils/__init__.py +3 -0
  79. zabbix_cli/utils/args.py +164 -0
  80. zabbix_cli/utils/commands.py +47 -0
  81. zabbix_cli/utils/fs.py +90 -0
  82. zabbix_cli/utils/rich.py +35 -0
  83. zabbix_cli/utils/utils.py +330 -0
  84. zabbix_cli_uio-3.1.1.dist-info/METADATA +404 -0
  85. zabbix_cli_uio-3.1.1.dist-info/RECORD +89 -0
  86. zabbix_cli_uio-3.1.1.dist-info/WHEEL +4 -0
  87. zabbix_cli_uio-3.1.1.dist-info/entry_points.txt +4 -0
  88. zabbix_cli_uio-3.1.1.dist-info/licenses/AUTHORS +32 -0
  89. zabbix_cli_uio-3.1.1.dist-info/licenses/LICENSE +674 -0
@@ -0,0 +1,5 @@
1
+ from __future__ import annotations
2
+
3
+ __version__ = "3.1.1"
4
+ APP_NAME = "zabbix-cli"
5
+ AUTHOR = "unioslo"
zabbix_cli/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ from __future__ import annotations
2
+
3
+ from zabbix_cli._patches import patch_all
4
+
5
+ patch_all()
zabbix_cli/__main__.py ADDED
@@ -0,0 +1,6 @@
1
+ from __future__ import annotations
2
+
3
+ from zabbix_cli.main import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1,10 @@
1
+ from __future__ import annotations
2
+
3
+ from zabbix_cli._patches import typer
4
+
5
+
6
+ def patch_all() -> None:
7
+ """Apply all patches to all modules."""
8
+ typer.patch()
9
+ # NOTE: we patch click_repl only when we actually launch the REPL
10
+ # See: zabbix_cli.main.start_repl
@@ -0,0 +1,149 @@
1
+ # type: ignore
2
+ """Patches for click_repl package."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import shlex
7
+ import sys
8
+ from typing import TYPE_CHECKING
9
+ from typing import Any
10
+ from typing import Dict
11
+ from typing import Optional
12
+
13
+ import click
14
+ import click_repl
15
+ from click.exceptions import Exit as ClickExit
16
+ from click_repl import ExitReplException
17
+ from click_repl import bootstrap_prompt
18
+ from click_repl import dispatch_repl_commands
19
+ from click_repl import handle_internal_commands
20
+ from prompt_toolkit.shortcuts import prompt
21
+
22
+ from zabbix_cli._patches.common import get_patcher
23
+ from zabbix_cli.exceptions import handle_exception
24
+
25
+ if TYPE_CHECKING:
26
+ from click.core import Context
27
+
28
+ from zabbix_cli.app import StatefulApp
29
+
30
+ patcher = get_patcher(f"click_repl version: {click_repl.__version__}")
31
+
32
+
33
+ def repl( # noqa: C901
34
+ old_ctx: Context,
35
+ prompt_kwargs: Dict[str, Any] = None,
36
+ allow_system_commands: bool = True,
37
+ allow_internal_commands: bool = True,
38
+ app: Optional[StatefulApp] = None,
39
+ ) -> None:
40
+ """Start an interactive shell. All subcommands are available in it.
41
+
42
+ :param old_ctx: The current Click context.
43
+ :param prompt_kwargs: Parameters passed to
44
+ :py:func:`prompt_toolkit.shortcuts.prompt`.
45
+
46
+ If stdin is not a TTY, no prompt will be printed, but only commands read
47
+ from stdin.
48
+ """
49
+ # parent should be available, but we're not going to bother if not
50
+ group_ctx = old_ctx.parent or old_ctx
51
+ group = group_ctx.command
52
+ isatty = sys.stdin.isatty()
53
+
54
+ # Delete the REPL command from those available, as we don't want to allow
55
+ # nesting REPLs (note: pass `None` to `pop` as we don't want to error if
56
+ # REPL command already not present for some reason).
57
+ repl_command_name = old_ctx.command.name
58
+ if isinstance(group_ctx.command, click.CommandCollection):
59
+ available_commands = {
60
+ cmd_name: cmd_obj
61
+ for source in group_ctx.command.sources
62
+ for cmd_name, cmd_obj in source.commands.items()
63
+ }
64
+ else:
65
+ available_commands = group_ctx.command.commands
66
+ available_commands.pop(repl_command_name, None)
67
+
68
+ prompt_kwargs = bootstrap_prompt(prompt_kwargs, group)
69
+
70
+ if isatty:
71
+
72
+ def get_command():
73
+ return prompt(**prompt_kwargs)
74
+
75
+ else:
76
+ get_command = sys.stdin.readline
77
+
78
+ while True:
79
+ try:
80
+ command = get_command()
81
+ except KeyboardInterrupt:
82
+ continue
83
+ except EOFError:
84
+ break
85
+
86
+ if not command:
87
+ if isatty:
88
+ continue
89
+ else:
90
+ break
91
+
92
+ if allow_system_commands and dispatch_repl_commands(command):
93
+ continue
94
+
95
+ if allow_internal_commands:
96
+ try:
97
+ result = handle_internal_commands(command)
98
+ if isinstance(result, str):
99
+ click.echo(result)
100
+ continue
101
+ except ExitReplException:
102
+ break
103
+
104
+ try:
105
+ args = shlex.split(command)
106
+ except ValueError as e:
107
+ click.echo(f"{type(e).__name__}: {e}")
108
+ continue
109
+
110
+ try:
111
+ if app:
112
+ group = app.as_click_group()
113
+ with group.make_context(None, args, parent=group_ctx) as ctx:
114
+ group.invoke(ctx)
115
+ ctx.exit()
116
+ except click.ClickException as e:
117
+ e.show()
118
+ except ClickExit:
119
+ pass
120
+ except SystemExit:
121
+ pass
122
+ except ExitReplException:
123
+ break
124
+ # PATCH: Handle zabbix-cli exceptions
125
+ except Exception as e:
126
+ try:
127
+ handle_exception(e)
128
+ except SystemExit:
129
+ pass
130
+ # PATCH: Continue on keyboard interrupt
131
+ except KeyboardInterrupt:
132
+ from zabbix_cli.output.console import err_console
133
+
134
+ # User likely pressed Ctrl+C during a prompt or when a spinner
135
+ # was active. Ensure message is printed on a new line.
136
+ # TODO: determine if last char in terminal was newline somehow! Can we?
137
+ err_console.print("\n[red]Aborted.[/]")
138
+ pass
139
+
140
+
141
+ def patch_exception_handling() -> None:
142
+ """Patch click_repl's exception handling to fall back on zabbix-cli exception handlers."""
143
+ with patcher("click_repl.repl"):
144
+ click_repl.repl = repl
145
+
146
+
147
+ def patch() -> None:
148
+ """Apply all patches."""
149
+ patch_exception_handling()
@@ -0,0 +1,80 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC
4
+ from abc import abstractmethod
5
+ from typing import TYPE_CHECKING
6
+
7
+ if TYPE_CHECKING:
8
+ from types import TracebackType
9
+ from typing import Optional
10
+ from typing import Type
11
+
12
+
13
+ class BasePatcher(ABC):
14
+ """Context manager that logs and prints diagnostic info if an exception
15
+ occurs.
16
+ """
17
+
18
+ def __init__(self, description: str) -> None:
19
+ self.description = description
20
+
21
+ @abstractmethod
22
+ def __package_info__(self) -> str:
23
+ raise NotImplementedError
24
+
25
+ def __enter__(self) -> BasePatcher:
26
+ return self
27
+
28
+ def __exit__(
29
+ self,
30
+ exc_type: Optional[Type[BaseException]],
31
+ exc_val: Optional[BaseException],
32
+ exc_tb: Optional[TracebackType],
33
+ ) -> bool:
34
+ if not exc_type:
35
+ return True
36
+ import sys
37
+
38
+ import rich
39
+ from rich.table import Table
40
+
41
+ from zabbix_cli.__about__ import __version__
42
+
43
+ # Rudimentary, but provides enough info to debug and fix the issue
44
+ console = rich.console.Console(stderr=True)
45
+ console.print_exception()
46
+ console.print()
47
+ table = Table(
48
+ title="Diagnostics",
49
+ show_header=False,
50
+ show_lines=False,
51
+ )
52
+ table.add_row(
53
+ "[b]Package [/]",
54
+ self.__package_info__(),
55
+ )
56
+ table.add_row(
57
+ "[b]zabbix-cli [/]",
58
+ __version__,
59
+ )
60
+ table.add_row(
61
+ "[b]Python [/]",
62
+ sys.version,
63
+ )
64
+ table.add_row(
65
+ "[b]Platform [/]",
66
+ sys.platform,
67
+ )
68
+ console.print(table)
69
+ console.print(f"[bold red]ERROR: Failed to patch {self.description}[/]")
70
+ raise SystemExit(1)
71
+
72
+
73
+ def get_patcher(info: str) -> Type[BasePatcher]:
74
+ """Returns a patcher for a given package."""
75
+
76
+ class Patcher(BasePatcher):
77
+ def __package_info__(self) -> str:
78
+ return info
79
+
80
+ return Patcher
@@ -0,0 +1,317 @@
1
+ # type: ignore
2
+ """Patching of Typer to extend functionality and change styling.
3
+
4
+ Will probably break for some version of Typer at some point.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import inspect
10
+ from datetime import datetime
11
+ from enum import Enum
12
+ from pathlib import Path
13
+ from typing import TYPE_CHECKING
14
+ from typing import Any
15
+ from typing import Callable
16
+ from typing import Iterable
17
+ from typing import Type
18
+ from typing import Union
19
+ from typing import cast
20
+ from uuid import UUID
21
+
22
+ import click
23
+ import typer
24
+ from typer.main import lenient_issubclass
25
+ from typer.models import ParameterInfo
26
+
27
+ from zabbix_cli._patches.common import get_patcher
28
+ from zabbix_cli.pyzabbix.enums import APIStrEnum
29
+
30
+ if TYPE_CHECKING:
31
+ from typing import Dict
32
+
33
+ from rich.style import Style
34
+
35
+ patcher = get_patcher(f"Typer version: {typer.__version__}")
36
+
37
+
38
+ def patch_help_text_style() -> None:
39
+ """Remove dimming of help text.
40
+
41
+ https://github.com/tiangolo/typer/issues/437#issuecomment-1224149402
42
+ """
43
+ with patcher("typer.rich_utils.STYLE_HELPTEXT"):
44
+ typer.rich_utils.STYLE_HELPTEXT = ""
45
+
46
+
47
+ def patch_help_text_spacing() -> None:
48
+ """Adds a single blank line between short and long help text of a command when using `--help`.
49
+
50
+ As of Typer 0.9.0, the short and long help text is printed without any
51
+ blank lines between them. This is bad for readability (IMO).
52
+ """
53
+ from rich.console import group
54
+ from rich.markdown import Markdown
55
+ from rich.text import Text
56
+ from typer.rich_utils import DEPRECATED_STRING
57
+ from typer.rich_utils import MARKUP_MODE_MARKDOWN
58
+ from typer.rich_utils import MARKUP_MODE_RICH
59
+ from typer.rich_utils import STYLE_DEPRECATED
60
+ from typer.rich_utils import STYLE_HELPTEXT
61
+ from typer.rich_utils import STYLE_HELPTEXT_FIRST_LINE
62
+ from typer.rich_utils import MarkupMode
63
+ from typer.rich_utils import _make_rich_rext
64
+
65
+ @group()
66
+ def _get_help_text(
67
+ *,
68
+ obj: Union[click.Command, click.Group],
69
+ markup_mode: MarkupMode,
70
+ ) -> Iterable[Union[Markdown, Text]]:
71
+ """Build primary help text for a click command or group.
72
+
73
+ Returns the prose help text for a command or group, rendered either as a
74
+ Rich Text object or as Markdown.
75
+ If the command is marked as deprecated, the deprecated string will be prepended.
76
+ """
77
+ # Prepend deprecated status
78
+ if obj.deprecated:
79
+ yield Text(DEPRECATED_STRING, style=STYLE_DEPRECATED)
80
+
81
+ # Fetch and dedent the help text
82
+ help_text = inspect.cleandoc(obj.help or "")
83
+
84
+ # Trim off anything that comes after \f on its own line
85
+ help_text = help_text.partition("\f")[0]
86
+
87
+ # Get the first paragraph
88
+ first_line = help_text.split("\n\n")[0]
89
+ # Remove single linebreaks
90
+ if markup_mode != MARKUP_MODE_MARKDOWN and not first_line.startswith("\b"):
91
+ first_line = first_line.replace("\n", " ")
92
+ yield _make_rich_rext(
93
+ text=first_line.strip(),
94
+ style=STYLE_HELPTEXT_FIRST_LINE,
95
+ markup_mode=markup_mode,
96
+ )
97
+
98
+ # Get remaining lines, remove single line breaks and format as dim
99
+ remaining_paragraphs = help_text.split("\n\n")[1:]
100
+ if remaining_paragraphs:
101
+ if markup_mode != MARKUP_MODE_RICH:
102
+ # Remove single linebreaks
103
+ remaining_paragraphs = [
104
+ x.replace("\n", " ").strip()
105
+ if not x.startswith("\b")
106
+ else "{}\n".format(x.strip("\b\n"))
107
+ for x in remaining_paragraphs
108
+ ]
109
+ # Join back together
110
+ remaining_lines = "\n".join(remaining_paragraphs)
111
+ else:
112
+ # Join with double linebreaks if markdown
113
+ remaining_lines = "\n\n".join(remaining_paragraphs)
114
+ yield _make_rich_rext(
115
+ text="\n",
116
+ style=STYLE_HELPTEXT,
117
+ markup_mode=markup_mode,
118
+ )
119
+ yield _make_rich_rext(
120
+ text=remaining_lines,
121
+ style=STYLE_HELPTEXT,
122
+ markup_mode=markup_mode,
123
+ )
124
+
125
+ with patcher("typer.rich_utils._get_help_text"):
126
+ typer.rich_utils._get_help_text = _get_help_text
127
+
128
+
129
+ def patch_generate_enum_convertor() -> None:
130
+ """Patches enum value converter with an additional fallback to
131
+ instantiating the enum with the value directly.
132
+ """
133
+
134
+ def generate_enum_convertor(enum: Type[Enum]) -> Callable[[Any], Any]:
135
+ lower_val_map = {str(val.value).lower(): val for val in enum}
136
+
137
+ def convertor(value: Any) -> Any:
138
+ if value is not None:
139
+ low = str(value).lower()
140
+ if low in lower_val_map:
141
+ key = lower_val_map[low]
142
+ return enum(key)
143
+ # Fall back to passing in the value as-is
144
+ try:
145
+ return enum(value)
146
+ except ValueError:
147
+ return None
148
+
149
+ return convertor
150
+
151
+ with patcher("typer.main.generate_enum_convertor"):
152
+ typer.main.generate_enum_convertor = generate_enum_convertor
153
+
154
+
155
+ def patch_get_click_type() -> None:
156
+ """Adds support for our custom `APIStrEnum` type.
157
+
158
+ Used in conjunction with our custom generate_enum_convertor to support
159
+ instantiating `APIStrEnum` with both the human-readable value and the API value
160
+ (e.g. `"Enabled"` and `0`).
161
+
162
+ Uses the `APIStrEnum.all_choices()` method to get the list of choices.
163
+ """
164
+
165
+ def get_click_type(
166
+ *, annotation: Any, parameter_info: ParameterInfo
167
+ ) -> click.ParamType:
168
+ if parameter_info.click_type is not None:
169
+ return parameter_info.click_type
170
+
171
+ elif parameter_info.parser is not None:
172
+ return click.types.FuncParamType(parameter_info.parser)
173
+
174
+ elif annotation == str: # noqa: E721
175
+ return click.STRING
176
+ elif annotation == int: # noqa: E721
177
+ if parameter_info.min is not None or parameter_info.max is not None:
178
+ min_ = None
179
+ max_ = None
180
+ if parameter_info.min is not None:
181
+ min_ = int(parameter_info.min)
182
+ if parameter_info.max is not None:
183
+ max_ = int(parameter_info.max)
184
+ return click.IntRange(min=min_, max=max_, clamp=parameter_info.clamp)
185
+ else:
186
+ return click.INT
187
+ elif annotation == float: # noqa: E721
188
+ if parameter_info.min is not None or parameter_info.max is not None:
189
+ return click.FloatRange(
190
+ min=parameter_info.min,
191
+ max=parameter_info.max,
192
+ clamp=parameter_info.clamp,
193
+ )
194
+ else:
195
+ return click.FLOAT
196
+ elif annotation == bool: # noqa: E721
197
+ return click.BOOL
198
+ elif annotation == UUID:
199
+ return click.UUID
200
+ elif annotation == datetime:
201
+ return click.DateTime(formats=parameter_info.formats)
202
+ elif (
203
+ annotation == Path
204
+ or parameter_info.allow_dash
205
+ or parameter_info.path_type
206
+ or parameter_info.resolve_path
207
+ ):
208
+ return click.Path(
209
+ exists=parameter_info.exists,
210
+ file_okay=parameter_info.file_okay,
211
+ dir_okay=parameter_info.dir_okay,
212
+ writable=parameter_info.writable,
213
+ readable=parameter_info.readable,
214
+ resolve_path=parameter_info.resolve_path,
215
+ allow_dash=parameter_info.allow_dash,
216
+ path_type=parameter_info.path_type,
217
+ )
218
+ elif lenient_issubclass(annotation, typer.FileTextWrite):
219
+ return click.File(
220
+ mode=parameter_info.mode or "w",
221
+ encoding=parameter_info.encoding,
222
+ errors=parameter_info.errors,
223
+ lazy=parameter_info.lazy,
224
+ atomic=parameter_info.atomic,
225
+ )
226
+ elif lenient_issubclass(annotation, typer.FileText):
227
+ return click.File(
228
+ mode=parameter_info.mode or "r",
229
+ encoding=parameter_info.encoding,
230
+ errors=parameter_info.errors,
231
+ lazy=parameter_info.lazy,
232
+ atomic=parameter_info.atomic,
233
+ )
234
+ elif lenient_issubclass(annotation, typer.FileBinaryRead):
235
+ return click.File(
236
+ mode=parameter_info.mode or "rb",
237
+ encoding=parameter_info.encoding,
238
+ errors=parameter_info.errors,
239
+ lazy=parameter_info.lazy,
240
+ atomic=parameter_info.atomic,
241
+ )
242
+ elif lenient_issubclass(annotation, typer.FileBinaryWrite):
243
+ return click.File(
244
+ mode=parameter_info.mode or "wb",
245
+ encoding=parameter_info.encoding,
246
+ errors=parameter_info.errors,
247
+ lazy=parameter_info.lazy,
248
+ atomic=parameter_info.atomic,
249
+ )
250
+ # our patch for APIStrEnum
251
+ elif lenient_issubclass(annotation, APIStrEnum):
252
+ annotation = cast(Type[APIStrEnum], annotation)
253
+ return click.Choice(
254
+ annotation.all_choices(),
255
+ case_sensitive=parameter_info.case_sensitive,
256
+ )
257
+ elif lenient_issubclass(annotation, Enum):
258
+ return click.Choice(
259
+ [item.value for item in annotation],
260
+ case_sensitive=parameter_info.case_sensitive,
261
+ )
262
+ raise RuntimeError(f"Type not yet supported: {annotation}") # pragma no cover
263
+
264
+ """Patch typer's get_click_type to support more types."""
265
+ with patcher("typer.main.get_click_type"):
266
+ typer.main.get_click_type = get_click_type
267
+
268
+
269
+ def patch__get_rich_console() -> None:
270
+ from rich.console import Console
271
+ from typer.rich_utils import COLOR_SYSTEM
272
+ from typer.rich_utils import FORCE_TERMINAL
273
+ from typer.rich_utils import MAX_WIDTH
274
+ from typer.rich_utils import STYLE_METAVAR
275
+ from typer.rich_utils import STYLE_METAVAR_SEPARATOR
276
+ from typer.rich_utils import STYLE_NEGATIVE_OPTION
277
+ from typer.rich_utils import STYLE_NEGATIVE_SWITCH
278
+ from typer.rich_utils import STYLE_OPTION
279
+ from typer.rich_utils import STYLE_SWITCH
280
+ from typer.rich_utils import STYLE_USAGE
281
+ from typer.rich_utils import highlighter
282
+
283
+ from zabbix_cli.output.style import RICH_THEME
284
+
285
+ theme: Dict[str, Union[str, Style]] = RICH_THEME.styles.copy() # type: ignore[assignment]
286
+ theme.update(
287
+ {
288
+ "option": STYLE_OPTION,
289
+ "switch": STYLE_SWITCH,
290
+ "negative_option": STYLE_NEGATIVE_OPTION,
291
+ "negative_switch": STYLE_NEGATIVE_SWITCH,
292
+ "metavar": STYLE_METAVAR,
293
+ "metavar_sep": STYLE_METAVAR_SEPARATOR,
294
+ "usage": STYLE_USAGE,
295
+ },
296
+ )
297
+
298
+ def _get_rich_console(stderr: bool = False) -> Console:
299
+ return Console(
300
+ theme=RICH_THEME,
301
+ highlighter=highlighter,
302
+ color_system=COLOR_SYSTEM,
303
+ force_terminal=FORCE_TERMINAL,
304
+ width=MAX_WIDTH,
305
+ stderr=stderr,
306
+ )
307
+
308
+ typer.rich_utils._get_rich_console = _get_rich_console
309
+
310
+
311
+ def patch() -> None:
312
+ """Apply all patches."""
313
+ patch_help_text_style()
314
+ patch_help_text_spacing()
315
+ patch_generate_enum_convertor()
316
+ patch_get_click_type()
317
+ patch__get_rich_console()
zabbix_cli/_types.py ADDED
@@ -0,0 +1,12 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+
5
+ if sys.version_info >= (3, 10):
6
+ from types import EllipsisType
7
+
8
+ EllipsisType = EllipsisType
9
+ else:
10
+ from typing import Any
11
+
12
+ EllipsisType = Any
@@ -0,0 +1,102 @@
1
+ """Compatibility functions going from Zabbix-CLI v2 to v3.
2
+
3
+ The functions in this module are intended to ease the transition by
4
+ providing fallbacks to deprecated functionality in Zabbix-CLI v2.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ from pathlib import Path
11
+ from typing import TYPE_CHECKING
12
+ from typing import List
13
+ from typing import Optional
14
+
15
+ import typer
16
+
17
+ if TYPE_CHECKING:
18
+ from click.core import CommandCollection
19
+ from click.core import Group
20
+
21
+
22
+ CONFIG_FILENAME = "zabbix-cli.conf"
23
+ CONFIG_FIXED_NAME = "zabbix-cli.fixed.conf"
24
+
25
+ # Config file locations
26
+ CONFIG_DEFAULT_DIR = "/usr/share/zabbix-cli"
27
+ CONFIG_SYSTEM_DIR = "/etc/zabbix-cli"
28
+ CONFIG_USER_DIR = os.path.expanduser("~/.zabbix-cli")
29
+
30
+ # Any item will overwrite values from the previous (NYI)
31
+ CONFIG_PRIORITY = tuple(
32
+ Path(os.path.join(d, f))
33
+ for d, f in (
34
+ (CONFIG_DEFAULT_DIR, CONFIG_FIXED_NAME),
35
+ (CONFIG_SYSTEM_DIR, CONFIG_FIXED_NAME),
36
+ (CONFIG_USER_DIR, CONFIG_FILENAME),
37
+ (CONFIG_SYSTEM_DIR, CONFIG_FILENAME),
38
+ (CONFIG_DEFAULT_DIR, CONFIG_FILENAME),
39
+ )
40
+ )
41
+
42
+
43
+ AUTH_FILE = Path.home() / ".zabbix-cli_auth"
44
+ AUTH_TOKEN_FILE = Path.home() / ".zabbix-cli_auth_token"
45
+
46
+
47
+ def run_command_from_option(ctx: typer.Context, command: str) -> None:
48
+ """Runs a command via old-style --command/-C option."""
49
+ from zabbix_cli.output.console import error
50
+ from zabbix_cli.output.console import exit_err
51
+ from zabbix_cli.output.console import warning
52
+
53
+ warning(
54
+ "The [i]--command/-C[/] option is deprecated and will be removed in a future release. "
55
+ "Invoke command directly instead."
56
+ )
57
+ if not isinstance(ctx.command, (CommandCollection, Group)):
58
+ exit_err( # TODO: find out if this could ever happen?
59
+ f"Cannot run command {command!r}. Ensure it is a valid command and try again."
60
+ )
61
+ cmd_obj = ctx.command.get_command(ctx, command)
62
+ if not cmd_obj:
63
+ exit_err(
64
+ f"Cannot run command {command!r}. Ensure it is a valid command and try again."
65
+ )
66
+ try:
67
+ ctx.invoke(cmd_obj, *ctx.args)
68
+ except typer.Exit:
69
+ pass
70
+ except Exception as e:
71
+ error(
72
+ f"Command {command!r} failed with error: {e}. Try re-running without --command."
73
+ )
74
+
75
+
76
+ def args_callback(
77
+ ctx: typer.Context, value: Optional[List[str]]
78
+ ) -> Optional[List[str]]:
79
+ if ctx.resilient_parsing:
80
+ return # for auto-completion
81
+ if value:
82
+ from zabbix_cli.output.console import warning
83
+
84
+ warning(
85
+ f"Detected deprecated positional arguments {value}. Use options instead."
86
+ )
87
+ # NOTE: Must NEVER return None. The "fix" in Typer 0.10.0 for None defaults
88
+ # somehow broke the parsing of callback values by causing values returned by
89
+ # callbacks to be passed to the internal converter, which then fails
90
+ # because it expects a list but gets None.
91
+ # https://github.com/tiangolo/typer/pull/664
92
+ # https://github.com/tiangolo/typer/blob/142422a14ca4c6a8ad579e9bd0fd0728364d86e3/typer/main.py#L639
93
+ return value or []
94
+
95
+
96
+ ARGS_POSITIONAL = typer.Argument(
97
+ None,
98
+ help="DEPRECATED: V2-style positional arguments.",
99
+ show_default=False,
100
+ hidden=True,
101
+ callback=args_callback,
102
+ )