argocli 0.1.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.
argocli/__init__.py ADDED
@@ -0,0 +1,67 @@
1
+ # pylint: disable=broad-except, line-too-long
2
+
3
+ """
4
+ module docstring
5
+ """
6
+
7
+ # import os
8
+ import sys
9
+ from importlib import metadata
10
+ import cac_core as cac
11
+
12
+ # import yaml
13
+ # import keyring
14
+ import argocli.core.client as client
15
+
16
+ if sys.version_info < (3, 9):
17
+ print("This project requires Python 3.9 or higher.", file=sys.stderr)
18
+ sys.exit(1)
19
+
20
+ cac.updatechecker.check_package_for_updates(__name__)
21
+
22
+ try:
23
+ __version__ = metadata.version(__package__)
24
+ except Exception:
25
+ __version__ = "#N/A"
26
+
27
+ log = cac.logger.new(__name__)
28
+ log.debug("Initializing %s version %s", __name__, __version__)
29
+
30
+ CONFIG = cac.config.Config(__name__)
31
+
32
+ log.debug("user config path: %s", CONFIG.config_file)
33
+
34
+ # TODO: prompt user for server and username if not set
35
+ argo_server = CONFIG.get("server", "INVALID_DEFAULT") #.replace("https://", "")
36
+ if argo_server == "INVALID_DEFAULT":
37
+ log.error("Invalid server in %s: %s", CONFIG.config_file, argo_server)
38
+ sys.exit(1)
39
+
40
+ argo_namespace = CONFIG.get("namespace", "INVALID_DEFAULT")
41
+ if argo_namespace == "INVALID_DEFAULT":
42
+ log.error("Invalid namespace in %s: %s", CONFIG.config_file, argo_namespace)
43
+ sys.exit(1)
44
+
45
+ argo_username = CONFIG.get("username", "INVALID_DEFAULT")
46
+ if argo_username == "INVALID_DEFAULT":
47
+ log.error("Invalid username in %s: %s", CONFIG.config_file, argo_username)
48
+ sys.exit(1)
49
+
50
+ credentialmanager = cac.credentialmanager.CredentialManager(__name__)
51
+ argo_api_token = credentialmanager.get_credential(
52
+ argo_username,
53
+ "Argo API key",
54
+ )
55
+
56
+ if not argo_api_token:
57
+ # TODO: update the docs
58
+ log.error(
59
+ "API token not found for %s; see https://github.com/rpunt/%s/blob/main/README.md#authentication",
60
+ argo_username,
61
+ __name__.replace("_", "-"),
62
+ )
63
+ sys.exit(1)
64
+
65
+ ARGO_CLIENT = client.ArgoClient(argo_server, argo_namespace, argo_api_token)
66
+
67
+ __all__ = ["ARGO_CLIENT", "CONFIG", "log"]
argocli/cli/main.py ADDED
@@ -0,0 +1,232 @@
1
+ #!/usr/bin/env python
2
+ # pylint: disable=line-too-long
3
+
4
+ import argparse
5
+ import importlib
6
+ import sys
7
+ import logging
8
+ import os
9
+
10
+ # import pkgutil
11
+ import cac_core as cac
12
+
13
+
14
+ def discover_commands():
15
+ """
16
+ Discover available commands by scanning the commands directory.
17
+
18
+ Returns:
19
+ list: A list of command names.
20
+ """
21
+ commands = []
22
+ commands_dir = os.path.abspath(
23
+ os.path.join(os.path.dirname(__file__), "..", "commands")
24
+ )
25
+
26
+ # Check if the commands directory exists
27
+ if not os.path.exists(commands_dir) or not os.path.isdir(commands_dir):
28
+ return commands
29
+
30
+ # Get all subdirectories (which are packages) in the commands directory
31
+ for item in os.listdir(commands_dir):
32
+ item_path = os.path.join(commands_dir, item)
33
+ # Only consider directories that have an __init__.py file (Python packages)
34
+ if (
35
+ os.path.isdir(item_path)
36
+ and os.path.exists(os.path.join(item_path, "__init__.py"))
37
+ and item != "__pycache__"
38
+ ):
39
+ commands.append(item)
40
+
41
+ return sorted(commands)
42
+
43
+
44
+ def discover_actions(command):
45
+ """
46
+ Discover available actions for a given command by scanning its directory.
47
+
48
+ Args:
49
+ command (str): The command name.
50
+
51
+ Returns:
52
+ list: A list of action names.
53
+ """
54
+ actions = []
55
+ command_dir = os.path.abspath(
56
+ os.path.join(os.path.dirname(__file__), "..", "commands", command)
57
+ )
58
+
59
+ # Check if the command directory exists
60
+ if not os.path.exists(command_dir) or not os.path.isdir(command_dir):
61
+ return actions
62
+
63
+ # Get all Python modules in the command directory
64
+ for item in os.listdir(command_dir):
65
+ # Skip __init__.py, __pycache__, and non-Python files
66
+ if item == "__init__.py" or item == "__pycache__" or not item.endswith(".py"):
67
+ continue
68
+
69
+ # Extract action name (filename without .py extension)
70
+ action = item[:-3]
71
+ actions.append(action)
72
+
73
+ return sorted(actions)
74
+
75
+
76
+ def show_command_help(command):
77
+ """
78
+ Show help for a specific command, listing all available actions.
79
+
80
+ Args:
81
+ command (str): The command to show help for
82
+ """
83
+ actions = discover_actions(command)
84
+ print(f"\nAvailable actions for '{command}':")
85
+ for action in sorted(actions):
86
+ try:
87
+ module_path = f"{__name__}.commands.{command}.{action}"
88
+ module = importlib.import_module(module_path)
89
+ doc = module.__doc__ or "No description available"
90
+ doc = doc.strip().split("\n")[0] # Get first line of docstring
91
+ print(f" {action.ljust(15)} - {doc}")
92
+ except Exception: # pylint: disable=broad-except
93
+ print(f" {action.ljust(15)} - No description available")
94
+
95
+
96
+ # def register_autocomplete(parser):
97
+ # """Set up command autocompletion if supported environment"""
98
+ # try:
99
+ # import argcomplete
100
+ # argcomplete.autocomplete(parser)
101
+ # except ImportError:
102
+ # # argcomplete is not installed, skip autocomplete setup
103
+ # pass
104
+
105
+
106
+ def setup_logging(verbose: bool) -> logging.Logger:
107
+ """Configure logging based on command line arguments"""
108
+ log = cac.logger.new(__name__)
109
+ if verbose:
110
+ log.setLevel(logging.DEBUG)
111
+ # if getattr(args, 'show_log_format', False):
112
+ # print(f"Log format: {cac.logger.get_formatter_string()}")
113
+ # sys.exit(0)
114
+ return log
115
+
116
+
117
+ def main():
118
+ """
119
+ Entry point for the Argo CLI tool.
120
+
121
+ This function sets up the argument parser with nested commands, and dynamically
122
+ loads and executes the appropriate module and action based on user input.
123
+ """
124
+ log = cac.logger.new(__name__)
125
+
126
+ # Create parent parser for global arguments
127
+ parent_parser = argparse.ArgumentParser(add_help=False)
128
+
129
+ # Main parser that inherits from parent
130
+ parser = argparse.ArgumentParser(
131
+ prog="argocli", description="Argo CLI tool", parents=[parent_parser]
132
+ )
133
+ subparsers = parser.add_subparsers(dest="command", required=True)
134
+
135
+ # Discover available commands by scanning the commands directory
136
+ commands = discover_commands()
137
+ log.debug("Discovered commands: %s", commands)
138
+
139
+ # Dictionary to map command names to their subparsers
140
+ command_subparsers = {}
141
+
142
+ # Set up command structure based on discovered commands
143
+ for command in commands:
144
+ command_parser = subparsers.add_parser(
145
+ command,
146
+ help=f"{command.capitalize()}-related commands",
147
+ parents=[parent_parser],
148
+ )
149
+ command_subparsers[command] = command_parser.add_subparsers(
150
+ dest="action", required=True
151
+ )
152
+
153
+ # Add all available action parsers up front by scanning directories
154
+ for command, subparser in command_subparsers.items():
155
+ actions = discover_actions(command)
156
+ log.debug("Discovered actions for %s: %s", command, actions)
157
+
158
+ for action in actions:
159
+ try:
160
+ # Load the module and class for this action
161
+ module_path = f"argocli.commands.{command}.{action}"
162
+ module = importlib.import_module(module_path)
163
+
164
+ class_name = f"{command.capitalize()}{action.capitalize()}"
165
+ action_class = getattr(module, class_name, None)
166
+
167
+ if action_class is None:
168
+ log.warning(
169
+ "Class '%s' not found in module '%s'", class_name, module_path
170
+ )
171
+ continue
172
+
173
+ # Instantiate the action class
174
+ action_instance = action_class()
175
+
176
+ # Create parser for this action and let the action define its arguments
177
+ action_parser = subparser.add_parser(
178
+ action, help=f"{action} {command}", parents=[parent_parser]
179
+ )
180
+ action_instance.define_arguments(action_parser)
181
+
182
+ # Store the class for later execution
183
+ action_parser.set_defaults(action_class=action_class)
184
+
185
+ except ModuleNotFoundError:
186
+ log.warning("Command module '%s' not found", module_path)
187
+ except Exception as e: # pylint: disable=broad-except
188
+ log.warning("Error setting up %s %s: %s", command, action, e)
189
+
190
+ # # Add autocomplete setup
191
+ # register_autocomplete(parser)
192
+
193
+ # Parse arguments
194
+ args = parser.parse_args()
195
+
196
+ log = setup_logging(args.verbose)
197
+ # log = setup_logging(False)
198
+ log.debug("Parsed arguments: %s", args)
199
+
200
+ # Add to main function, after argument parsing but before execution
201
+ if args.command is None:
202
+ parser.print_help()
203
+ print("\nAvailable commands:")
204
+ for cmd in sorted(commands):
205
+ print(f" {cmd}")
206
+ sys.exit(1)
207
+
208
+ # Execute the appropriate action
209
+ try:
210
+ # Get the action class from the parser defaults
211
+ action_class = getattr(args, "action_class", None)
212
+
213
+ if action_class is None:
214
+ log.error("No handler found for %s %s", args.command, args.action)
215
+ sys.exit(1)
216
+
217
+ # Instantiate and execute
218
+ if callable(action_class):
219
+ action_instance = action_class()
220
+ else:
221
+ log.error("Invalid action class for %s %s", args.command, args.action)
222
+ sys.exit(1)
223
+
224
+ log.debug("Executing action: %s %s", args.command, args.action)
225
+ action_instance.execute(args)
226
+
227
+ except Exception as e: # pylint: disable=broad-except
228
+ log.error("Error executing command: %s", e)
229
+
230
+
231
+ if __name__ == "__main__":
232
+ main()
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env python
2
+
3
+ """
4
+ Base class for all Argo CLI commands.
5
+
6
+ This module provides a base class that all command actions should inherit from,
7
+ allowing for common functionality and arguments to be shared across different
8
+ command actions.
9
+ """
10
+
11
+ import abc
12
+ from cac_core.command import Command
13
+ from argocli import CONFIG, ARGO_CLIENT, log
14
+
15
+
16
+ class ArgoCommand(Command):
17
+ """
18
+ Base class for all Argo CLI commands.
19
+
20
+ This class defines common methods and properties that should be shared
21
+ across all command actions, such as common arguments, authentication,
22
+ and utility functions.
23
+ """
24
+
25
+ def __init__(self):
26
+ """
27
+ Initialize the command with a logger and Argo client.
28
+ """
29
+ super().__init__()
30
+ self.log = log
31
+ self.argo_client = ARGO_CLIENT
32
+ self.config = CONFIG
33
+
34
+ @abc.abstractmethod
35
+ def define_arguments(self, parser):
36
+ """
37
+ Define command-specific arguments.
38
+
39
+ This method must be implemented by subclasses to add
40
+ command-specific arguments to the parser.
41
+
42
+ Args:
43
+ parser: The argument parser to add arguments to
44
+
45
+ Returns:
46
+ The updated argument parser
47
+ """
48
+ super().define_arguments(parser)
49
+ return parser
50
+
51
+ @abc.abstractmethod
52
+ def execute(self, args):
53
+ """
54
+ Execute the command with the provided arguments.
55
+
56
+ This method must be implemented by subclasses.
57
+
58
+ Args:
59
+ args: The parsed arguments
60
+ """
61
+ raise NotImplementedError("Command subclasses must implement execute()")
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env python
2
+
3
+ """
4
+ Base module for all issue-related commands.
5
+
6
+ This module defines the base ArgoCommand class that all issue-related
7
+ action classes should inherit from.
8
+ """
9
+
10
+ import abc
11
+ from argocli.commands.command import ArgoCommand
12
+
13
+
14
+ class ArgoWorkflowCommand(ArgoCommand):
15
+ """
16
+ Base class for all workflow-related actions.
17
+
18
+ This class defines common methods and properties that should be shared
19
+ across all workflow actions, such as workflow-specific arguments and utilities.
20
+ """
21
+
22
+ @abc.abstractmethod
23
+ def define_arguments(self, parser):
24
+ """
25
+ Define arguments specific to this command.
26
+
27
+ Args:
28
+ parser: The argument parser to add arguments to
29
+
30
+ Returns:
31
+ The parser with arguments added
32
+ """
33
+ super().define_arguments(parser)
34
+ # Add workflow-specific common arguments
35
+ has_name = any(action.dest == "name" for action in parser._actions)
36
+ if not has_name:
37
+ parser.add_argument(
38
+ "-n",
39
+ "--name",
40
+ help="Workflow name",
41
+ required=True,
42
+ )
43
+ return parser
44
+
45
+ @abc.abstractmethod
46
+ def execute(self, args):
47
+ """
48
+ Execute the command with the given arguments.
49
+
50
+ Args:
51
+ args: The parsed arguments
52
+
53
+ Returns:
54
+ The result of the command execution
55
+ """
56
+ raise NotImplementedError("Subclasses must implement execute()")
@@ -0,0 +1,28 @@
1
+ # pylint: disable=no-member
2
+
3
+ from argocli.commands.workflow import ArgoWorkflowCommand
4
+
5
+ class WorkflowStatus(ArgoWorkflowCommand):
6
+ """
7
+ Command to check the status of a workflow.
8
+ """
9
+
10
+ def define_arguments(self, parser):
11
+ """
12
+ Define command-specific arguments for checking workflow status.
13
+ """
14
+ super().define_arguments(parser)
15
+ return parser
16
+
17
+ def execute(self, args):
18
+ """
19
+ Execute the command to check the workflow status.
20
+ """
21
+ # print(f"Checking status for workflow: {args.name}")
22
+ client = self.argo_client
23
+ workflow_status = client.get_workflow(args.name)
24
+ print(
25
+ workflow_status["status"]["phase"]
26
+ if workflow_status
27
+ else "Workflow not found or error occurred."
28
+ )
argocli/core/client.py ADDED
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env python
2
+ # pylint: disable=no-member
3
+
4
+ """
5
+ Argo client module.
6
+ """
7
+
8
+ import cac_core as cac
9
+ import requests
10
+
11
+ log = cac.logger.new(__name__)
12
+
13
+
14
+ class ArgoClient:
15
+ """
16
+ Argo client class.
17
+ """
18
+
19
+ def __init__(self, server, namespace, api_token=None):
20
+ """
21
+ Initialize the Argo client.
22
+
23
+ Args:
24
+ server: The Argo server
25
+ username: The Argo username
26
+ api_token: The Argo API token
27
+ """
28
+ self.server = server
29
+ self.namespace = namespace
30
+ self.api_token = api_token
31
+
32
+ def get_workflow(self, name):
33
+ """
34
+ Get a workflow by name.
35
+
36
+ Args:
37
+ name: The name of the workflow
38
+
39
+ Returns:
40
+ The workflow object
41
+ """
42
+ log.debug(f"Getting workflow {name} from server {self.server}")
43
+ response = requests.get(
44
+ f"{self.server}/api/v1/workflows/{self.namespace}/{name}",
45
+ headers={"Authorization": f"Bearer {self.api_token}"},
46
+ timeout=10
47
+ )
48
+ if response.status_code == 200:
49
+ return response.json()
50
+ else:
51
+ log.error(f"Failed to get workflow {name}: {response.status_code} {response.text}")
52
+ return None
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.4
2
+ Name: argocli
3
+ Version: 0.1.0
4
+ Summary: A command-line interface for Argo Workflows
5
+ Requires-Python: >=3.12
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: cac-core>=0.4.1
9
+ Requires-Dist: packaging>=25.0
10
+ Requires-Dist: requests>=2.32.4
11
+ Dynamic: license-file
@@ -0,0 +1,12 @@
1
+ argocli/__init__.py,sha256=weB2OKhqXNYNMZ9i23mwC99shvbIM1eochbN91Okakw,1874
2
+ argocli/cli/main.py,sha256=SGDmWCuR2XQS71g1p3p-BYQT1ivxmgrIpfO0G4ZEzJ0,7589
3
+ argocli/commands/command.py,sha256=K9ehMVba9_Rg_TsEPnFec4GP24BpXvHWdfp2A7wvmTc,1599
4
+ argocli/commands/workflow/__init__.py,sha256=6f-gZGuBNw_YM4iK8-gviJ20uDSfmhEtC7Lx8TpWMP8,1489
5
+ argocli/commands/workflow/status.py,sha256=YxNFmieCiBaqOBt6D-uyCDTVr6SC4cLxhgtqxJyxcLE,819
6
+ argocli/core/client.py,sha256=00NMaRbEFmgHGHea2OJPdg7bMAn9wa0gLuJT_kdJ_fU,1246
7
+ argocli-0.1.0.dist-info/licenses/LICENSE,sha256=9zKZw6P2NbYxuA1UuxzvAvTc3VhTW07CAgN3bLxCkqo,1066
8
+ argocli-0.1.0.dist-info/METADATA,sha256=5dwg-OC9EsVNFARFla9mZ02aWmZJ5E3iKWV-0kMeiF4,306
9
+ argocli-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
10
+ argocli-0.1.0.dist-info/entry_points.txt,sha256=cAplzeUUaQ8IXiVEBR_9p5_7iRHyAGZQgxl0hITGqrg,50
11
+ argocli-0.1.0.dist-info/top_level.txt,sha256=k62Rfx-hxDMUXTnXBBpjRw16LlSfB6I7rTai_DPoJuw,8
12
+ argocli-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ argocli = argocli.cli.main:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Ryan Punt
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 @@
1
+ argocli