Skip to content

cloudmesh-ai-common API Reference

This page provides the API reference for the cloudmesh-ai-common library.

API Documentation

Unified entry point for cloudmesh-ai-common. Exports the most commonly used utilities for easier access.

Attributes

__all__ module-attribute

__all__ = ['console', 'Console', 'readfile', 'writefile', 'path_expand', 'banner', 'flatten', 'backup_name', 'yn_choice', 'HEADING', 'DotDict', 'SystemInfo', 'DateTime', 'time', 'Shell', 'VERBOSE']

console module-attribute

console = Console()

time module-attribute

time = DateTime

Classes

Console

Bases: BaseIO, Console

Unified Console for cloudmesh-ai providing styled output, I/O, and table printing.

Source code in src/cloudmesh/ai/common/io.py
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
class Console(BaseIO, RichConsole):
    """Unified Console for cloudmesh-ai providing styled output, I/O, and table printing."""

    def error(self, message: str):
        """Prints an error message in red."""
        self.print(f"[red]ERROR: {message}[/red]")

    def warning(self, message: str):
        """Prints a warning message in yellow."""
        self.print(f"[yellow]WARNING: {message}[/yellow]")

    def msg(self, message: str):
        """Prints a message in blue."""
        self.print(f"[blue]MSG: {message}[/blue]")

    def info(self, message: str):
        """Prints an info message in magenta."""
        self.print(f"[magenta]INFO: {message}[/magenta]")

    def note(self, message: str):
        """Prints a note in cyan."""
        self.print(f"[cyan]NOTE: {message}[/cyan]")

    def ok(self, message: str):
        """Prints a success message in green."""
        self.print(f"[green]OK: {message}[/green]")

    def bold(self, message: str):
        """Prints a message in bold."""
        self.print(f"[bold]{message}[/bold]")

    def create_banner(self, title: str, content: Optional[str] = None) -> Panel:
        """Creates a banner Panel without printing it."""
        if not content and title:
            panel_content = Align.center(f"[bold magenta]{title}[/bold magenta]")
            styled_title = ""
        else:
            panel_content = content if content else ""
            styled_title = f"[bold magenta]{title}[/bold magenta]" if title else ""

        return Panel(
            panel_content,
            title=styled_title,
            box=box.ROUNDED,
            expand=True,
            border_style="bold blue"
        )

    def banner(
        self,
        label: Optional[str] = None,
        txt: Optional[str] = None,
        c: str = "-",
        prefix: str = "#",
        debug: bool = True,
        color: str = "blue",
        padding: bool = False,
        figlet: bool = False,
        font: str = "big",
    ) -> None:
        """Prints a banner of the form with a frame of # around the txt"""
        if not debug:
            return

        output = "\n"
        output += f"{prefix} {70 * c}\n"
        if padding:
            output += f"{prefix}\n"
        if label is not None:
            output += f"{prefix} {label}\n"
            output += f"{prefix} {70 * c}\n"

        if txt is not None:
            if figlet:
                txt = pyfiglet.figlet_format(txt, font=font)

            for line in txt.splitlines():
                output += f"{prefix} {line}\n"
            if padding:
                output += f"{prefix}\n"
            output += f"{prefix} {70 * c}\n"

        self.cprint(output, color, "")

    def cprint(self, text: str, color: str, style: str = ""):
        """Helper to print with color."""
        self.print(f"[{color}]{text}[/{color}]", style=style)

    def ynchoice(self, message: str, default: bool = True) -> bool:
        """Asks a yes/no question and returns a boolean."""
        suffix = " [Y/n]" if default else " [y/N]"
        while True:
            response = input(f"{message}{suffix} ").strip().lower()
            if not response:
                return default
            if response in ("y", "yes"):
                return True
            if response in ("n", "no"):
                return False
            self.print("[red]Please enter 'y' or 'n'.[/red]")

    def print_attributes(self, d: Dict[str, Any], header: Optional[List[str]] = None, 
                         order: Optional[List[str]] = None, sort_keys: bool = True, 
                         humanize: bool = False, output: str = "table"):
        """Prints a dictionary of attributes in various formats."""
        if not d:
            self.print("No attributes to display.")
            return

        if output == "json":
            self.print_json(d)
            return
        if output == "yaml":
            self.print_yaml(d)
            return
        if output == "csv":
            self.print_csv(d, order)
            return

        # Default: Table output
        if header is None:
            header = ["Attribute", "Value"]

        table = Table(title="Attributes", box=box.ROUNDED, expand=True)
        table.add_column(header[0])
        table.add_column(header[1])

        sorted_keys = order if order else (sorted(d.keys()) if sort_keys else list(d.keys()))

        for key in sorted_keys:
            if key not in d:
                continue
            val = d[key]

            if humanize:
                val = self._humanize(val)

            if isinstance(val, dict):
                table.add_row(key, "+")
                for k, v in val.items():
                    table.add_row(f"  - {k}", str(v))
            elif isinstance(val, list):
                table.add_row(key, "+")
                for item in val:
                    table.add_row("  -", str(item))
            else:
                table.add_row(key, str(val or ""))

        self.print(table)

    def print_table(self, headers: list, data: list, title: Optional[str] = None, expand: bool = False):
        """Prints a formatted table. By default, it is compact (expand=False)."""
        styled_title = f"[bold]{title}[/bold]" if title else None
        table = Table(title=styled_title, box=box.ROUNDED, expand=expand, header_style="bold")
        for header in headers:
            table.add_column(header)
        for row in data:
            table.add_row(*[str(item) for item in row])
        self.print(Align.center(table) if expand else Align.left(table))

    table = print_table

    def print_json(self, data: Any):
        """Prints data as formatted JSON."""
        self.print(json.dumps(data, indent=4))

    def print_yaml(self, data: Any):
        """Prints data as formatted YAML."""
        self.print(yaml.dump(data, default_flow_style=False))

    def print_csv(self, d: Dict[str, Any], order: Optional[List[str]] = None):
        """Prints a dictionary as CSV."""
        keys = order if order else sorted(d.keys())
        output = []
        output.append(",".join(keys))
        row = [str(d.get(k, "")) for k in keys]
        output.append(",".join(row))
        self.print("\n".join(output))

    def print_markdown(self, text: str):
        """Renders and prints markdown text."""
        self.print(Markdown(text))

    def _humanize(self, value: Any) -> str:
        """Basic humanization of values."""
        if isinstance(value, (int, float)) and abs(value) >= 1000000:
            return f"{value/1000000:.2f}M"
        if isinstance(value, (int, float)) and abs(value) >= 1000:
            return f"{value/1000:.2f}K"
        return str(value)

    def status(self, message: str) -> Status:
        """Returns a status spinner context manager."""
        return Status(f"[bold blue]{message}[/bold blue]", console=self)

    def top(self, lines: int):
        """Moves the cursor up by the specified number of lines."""
        if lines > 0:
            sys.stdout.write(f"\033[{lines}F")
            sys.stdout.flush()

    def left(self):
        """Moves the cursor to the beginning of the current line."""
        sys.stdout.write("\r")
        sys.stdout.flush()

    def ai_response(self, text: str, title: str = "AI Response", style: str = "cyan"):
        """Displays a standardized AI response box."""
        panel = Panel(
            text,
            title=title,
            title_style=style,
            border_style=style,
            expand=False,
            box=box.ROUNDED
        )
        self.print(panel)

    def telemetry_table(self, records: List[Dict[str, Any]], title: str = "Telemetry Records"):
        """Displays a standardized telemetry records table."""
        if not records:
            self.print("[yellow]No records to display.[/yellow]")
            return

        table = Table(
            title=title,
            box=box.ROUNDED,
            header_style="bold magenta"
        )

        headers = records[0].keys()
        for header in headers:
            table.add_column(header.capitalize(), style="cyan")

        for r in records:
            row = [str(r.get(h, "N/A")) for h in headers]
            table.add_row(*row)

        self.print(table)

    def print_status(self, message: str, style: str = "yellow"):
        """Prints a simple status message."""
        self.print(f"[{style}] {message}[/{style}]")

    def print_error(self, message: str):
        """Prints a standardized error message."""
        self.print(f"[bold red]Error:[/bold red] {message}")

    def print_success(self, message: str):
        """Prints a standardized success message."""
        self.print(f"[bold green]Success:[/bold green] {message}")

Functions

ai_response
ai_response(text, title='AI Response', style='cyan')

Displays a standardized AI response box.

Source code in src/cloudmesh/ai/common/io.py
309
310
311
312
313
314
315
316
317
318
319
def ai_response(self, text: str, title: str = "AI Response", style: str = "cyan"):
    """Displays a standardized AI response box."""
    panel = Panel(
        text,
        title=title,
        title_style=style,
        border_style=style,
        expand=False,
        box=box.ROUNDED
    )
    self.print(panel)
banner
banner(label=None, txt=None, c='-', prefix='#', debug=True, color='blue', padding=False, figlet=False, font='big')

Prints a banner of the form with a frame of # around the txt

Source code in src/cloudmesh/ai/common/io.py
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
def banner(
    self,
    label: Optional[str] = None,
    txt: Optional[str] = None,
    c: str = "-",
    prefix: str = "#",
    debug: bool = True,
    color: str = "blue",
    padding: bool = False,
    figlet: bool = False,
    font: str = "big",
) -> None:
    """Prints a banner of the form with a frame of # around the txt"""
    if not debug:
        return

    output = "\n"
    output += f"{prefix} {70 * c}\n"
    if padding:
        output += f"{prefix}\n"
    if label is not None:
        output += f"{prefix} {label}\n"
        output += f"{prefix} {70 * c}\n"

    if txt is not None:
        if figlet:
            txt = pyfiglet.figlet_format(txt, font=font)

        for line in txt.splitlines():
            output += f"{prefix} {line}\n"
        if padding:
            output += f"{prefix}\n"
        output += f"{prefix} {70 * c}\n"

    self.cprint(output, color, "")
bold
bold(message)

Prints a message in bold.

Source code in src/cloudmesh/ai/common/io.py
130
131
132
def bold(self, message: str):
    """Prints a message in bold."""
    self.print(f"[bold]{message}[/bold]")
cprint
cprint(text, color, style='')

Helper to print with color.

Source code in src/cloudmesh/ai/common/io.py
187
188
189
def cprint(self, text: str, color: str, style: str = ""):
    """Helper to print with color."""
    self.print(f"[{color}]{text}[/{color}]", style=style)
create_banner
create_banner(title, content=None)

Creates a banner Panel without printing it.

Source code in src/cloudmesh/ai/common/io.py
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
def create_banner(self, title: str, content: Optional[str] = None) -> Panel:
    """Creates a banner Panel without printing it."""
    if not content and title:
        panel_content = Align.center(f"[bold magenta]{title}[/bold magenta]")
        styled_title = ""
    else:
        panel_content = content if content else ""
        styled_title = f"[bold magenta]{title}[/bold magenta]" if title else ""

    return Panel(
        panel_content,
        title=styled_title,
        box=box.ROUNDED,
        expand=True,
        border_style="bold blue"
    )
error
error(message)

Prints an error message in red.

Source code in src/cloudmesh/ai/common/io.py
106
107
108
def error(self, message: str):
    """Prints an error message in red."""
    self.print(f"[red]ERROR: {message}[/red]")
info
info(message)

Prints an info message in magenta.

Source code in src/cloudmesh/ai/common/io.py
118
119
120
def info(self, message: str):
    """Prints an info message in magenta."""
    self.print(f"[magenta]INFO: {message}[/magenta]")
left
left()

Moves the cursor to the beginning of the current line.

Source code in src/cloudmesh/ai/common/io.py
304
305
306
307
def left(self):
    """Moves the cursor to the beginning of the current line."""
    sys.stdout.write("\r")
    sys.stdout.flush()
msg
msg(message)

Prints a message in blue.

Source code in src/cloudmesh/ai/common/io.py
114
115
116
def msg(self, message: str):
    """Prints a message in blue."""
    self.print(f"[blue]MSG: {message}[/blue]")
note
note(message)

Prints a note in cyan.

Source code in src/cloudmesh/ai/common/io.py
122
123
124
def note(self, message: str):
    """Prints a note in cyan."""
    self.print(f"[cyan]NOTE: {message}[/cyan]")
ok
ok(message)

Prints a success message in green.

Source code in src/cloudmesh/ai/common/io.py
126
127
128
def ok(self, message: str):
    """Prints a success message in green."""
    self.print(f"[green]OK: {message}[/green]")
print_attributes
print_attributes(d, header=None, order=None, sort_keys=True, humanize=False, output='table')

Prints a dictionary of attributes in various formats.

Source code in src/cloudmesh/ai/common/io.py
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
def print_attributes(self, d: Dict[str, Any], header: Optional[List[str]] = None, 
                     order: Optional[List[str]] = None, sort_keys: bool = True, 
                     humanize: bool = False, output: str = "table"):
    """Prints a dictionary of attributes in various formats."""
    if not d:
        self.print("No attributes to display.")
        return

    if output == "json":
        self.print_json(d)
        return
    if output == "yaml":
        self.print_yaml(d)
        return
    if output == "csv":
        self.print_csv(d, order)
        return

    # Default: Table output
    if header is None:
        header = ["Attribute", "Value"]

    table = Table(title="Attributes", box=box.ROUNDED, expand=True)
    table.add_column(header[0])
    table.add_column(header[1])

    sorted_keys = order if order else (sorted(d.keys()) if sort_keys else list(d.keys()))

    for key in sorted_keys:
        if key not in d:
            continue
        val = d[key]

        if humanize:
            val = self._humanize(val)

        if isinstance(val, dict):
            table.add_row(key, "+")
            for k, v in val.items():
                table.add_row(f"  - {k}", str(v))
        elif isinstance(val, list):
            table.add_row(key, "+")
            for item in val:
                table.add_row("  -", str(item))
        else:
            table.add_row(key, str(val or ""))

    self.print(table)
print_csv
print_csv(d, order=None)

Prints a dictionary as CSV.

Source code in src/cloudmesh/ai/common/io.py
273
274
275
276
277
278
279
280
def print_csv(self, d: Dict[str, Any], order: Optional[List[str]] = None):
    """Prints a dictionary as CSV."""
    keys = order if order else sorted(d.keys())
    output = []
    output.append(",".join(keys))
    row = [str(d.get(k, "")) for k in keys]
    output.append(",".join(row))
    self.print("\n".join(output))
print_error
print_error(message)

Prints a standardized error message.

Source code in src/cloudmesh/ai/common/io.py
347
348
349
def print_error(self, message: str):
    """Prints a standardized error message."""
    self.print(f"[bold red]Error:[/bold red] {message}")
print_json
print_json(data)

Prints data as formatted JSON.

Source code in src/cloudmesh/ai/common/io.py
265
266
267
def print_json(self, data: Any):
    """Prints data as formatted JSON."""
    self.print(json.dumps(data, indent=4))
print_markdown
print_markdown(text)

Renders and prints markdown text.

Source code in src/cloudmesh/ai/common/io.py
282
283
284
def print_markdown(self, text: str):
    """Renders and prints markdown text."""
    self.print(Markdown(text))
print_status
print_status(message, style='yellow')

Prints a simple status message.

Source code in src/cloudmesh/ai/common/io.py
343
344
345
def print_status(self, message: str, style: str = "yellow"):
    """Prints a simple status message."""
    self.print(f"[{style}] {message}[/{style}]")
print_success
print_success(message)

Prints a standardized success message.

Source code in src/cloudmesh/ai/common/io.py
351
352
353
def print_success(self, message: str):
    """Prints a standardized success message."""
    self.print(f"[bold green]Success:[/bold green] {message}")
print_table
print_table(headers, data, title=None, expand=False)

Prints a formatted table. By default, it is compact (expand=False).

Source code in src/cloudmesh/ai/common/io.py
253
254
255
256
257
258
259
260
261
def print_table(self, headers: list, data: list, title: Optional[str] = None, expand: bool = False):
    """Prints a formatted table. By default, it is compact (expand=False)."""
    styled_title = f"[bold]{title}[/bold]" if title else None
    table = Table(title=styled_title, box=box.ROUNDED, expand=expand, header_style="bold")
    for header in headers:
        table.add_column(header)
    for row in data:
        table.add_row(*[str(item) for item in row])
    self.print(Align.center(table) if expand else Align.left(table))
print_yaml
print_yaml(data)

Prints data as formatted YAML.

Source code in src/cloudmesh/ai/common/io.py
269
270
271
def print_yaml(self, data: Any):
    """Prints data as formatted YAML."""
    self.print(yaml.dump(data, default_flow_style=False))
status
status(message)

Returns a status spinner context manager.

Source code in src/cloudmesh/ai/common/io.py
294
295
296
def status(self, message: str) -> Status:
    """Returns a status spinner context manager."""
    return Status(f"[bold blue]{message}[/bold blue]", console=self)
telemetry_table
telemetry_table(records, title='Telemetry Records')

Displays a standardized telemetry records table.

Source code in src/cloudmesh/ai/common/io.py
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
def telemetry_table(self, records: List[Dict[str, Any]], title: str = "Telemetry Records"):
    """Displays a standardized telemetry records table."""
    if not records:
        self.print("[yellow]No records to display.[/yellow]")
        return

    table = Table(
        title=title,
        box=box.ROUNDED,
        header_style="bold magenta"
    )

    headers = records[0].keys()
    for header in headers:
        table.add_column(header.capitalize(), style="cyan")

    for r in records:
        row = [str(r.get(h, "N/A")) for h in headers]
        table.add_row(*row)

    self.print(table)
top
top(lines)

Moves the cursor up by the specified number of lines.

Source code in src/cloudmesh/ai/common/io.py
298
299
300
301
302
def top(self, lines: int):
    """Moves the cursor up by the specified number of lines."""
    if lines > 0:
        sys.stdout.write(f"\033[{lines}F")
        sys.stdout.flush()
warning
warning(message)

Prints a warning message in yellow.

Source code in src/cloudmesh/ai/common/io.py
110
111
112
def warning(self, message: str):
    """Prints a warning message in yellow."""
    self.print(f"[yellow]WARNING: {message}[/yellow]")
ynchoice
ynchoice(message, default=True)

Asks a yes/no question and returns a boolean.

Source code in src/cloudmesh/ai/common/io.py
191
192
193
194
195
196
197
198
199
200
201
202
def ynchoice(self, message: str, default: bool = True) -> bool:
    """Asks a yes/no question and returns a boolean."""
    suffix = " [Y/n]" if default else " [y/N]"
    while True:
        response = input(f"{message}{suffix} ").strip().lower()
        if not response:
            return default
        if response in ("y", "yes"):
            return True
        if response in ("n", "no"):
            return False
        self.print("[red]Please enter 'y' or 'n'.[/red]")

DotDict

Bases: OrderedDict

A dictionary subclass that supports dot-notation for nested access and assignment.

Source code in src/cloudmesh/ai/common/dotdict.py
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
class DotDict(OrderedDict):
    """A dictionary subclass that supports dot-notation for nested access and assignment.

    Attributes:
        None
    """

    def get(self, key, default=None):
        """Retrieves a value from the dictionary, supporting dot-notation for nested access.

        Args:
            key (str): The key to look up. If it contains dots, it is treated as a path.
            default (Any, optional): The value to return if the key or path is not found.

        Returns:
            Any: The value associated with the key or path, or the default value.
        """
        if isinstance(key, str) and "." in key:
            try:
                return self[key]
            except (KeyError, TypeError):
                return default
        return super().get(key, default)

    def __repr__(self):
        """Returns a string representation of the DotDict as YAML."""
        return self.yaml

    def __init__(self, data=None, **kwargs):
        """Initializes the DotDict with optional data and keyword arguments.

        Args:
            data (dict, optional): Initial dictionary data. Defaults to None.
            **kwargs: Additional key-value pairs to initialize the dictionary.

        Raises:
            TypeError: If the provided data is not a dictionary.
        """
        if data is None:
            data = {}
        if not isinstance(data, dict):
            raise TypeError("Data must be a dictionary")

        # Recursively convert nested dictionaries to DotDict
        converted_data = OrderedDict()
        for k, v in data.items():
            if isinstance(v, dict):
                converted_data[k] = DotDict(v)
            else:
                converted_data[k] = v

        super().__init__(converted_data)
        self.update(kwargs)

    @property
    def yaml(self):
        """Returns a YAML dump of the dictionary using literal block style for multi-line strings.

        Returns:
            str: The YAML representation of the DotDict.
        """
        # Use a local dumper to avoid polluting global yaml state if possible,
        # but for simplicity we use the global representer.
        yaml.add_representer(str, str_presenter)
        return yaml.dump(self.to_dict(), default_flow_style=False)

    def expand(self, d=None):
        """Expands placeholders in a dictionary using this DotDict's values.

        If a value in the target dictionary is a string containing {attribute}, 
        it is replaced by the value of the corresponding attribute found in this DotDict.

        Args:
            d (dict, optional): The dictionary to expand. If None, this DotDict 
                itself is expanded. Defaults to None.

        Returns:
            DotDict: A new DotDict with expanded values.
        """
        # If d is None, we expand self. Otherwise, we expand d.
        target = d if d is not None else self

        if not isinstance(target, dict):
            raise TypeError("Target to expand must be a dictionary")

        result = OrderedDict()

        # Simple expansion: iterate over keys and replace {key} placeholders.
        # This avoids regex issues with shell variables like ${VAR}.

        # Start with a copy of the target
        result = OrderedDict(target) if isinstance(target, dict) else OrderedDict()

        # Iterate over all keys in this DotDict to use as replacement values
        for key in self.keys():
            val = self[key]
            if val is None:
                continue

            placeholder = f"{{{key}}}"
            replacement = str(val)

            for k, v in result.items():
                if isinstance(v, str) and placeholder in v:
                    result[k] = v.replace(placeholder, replacement)

        # Also handle cases where the target has keys that should be expanded 
        # but aren't in 'self' (local scope expansion)
        if isinstance(target, dict):
            for key in target.keys():
                val = target[key]
                if val is None:
                    continue

                placeholder = f"{{{key}}}"
                replacement = str(val)

                for k, v in result.items():
                    if isinstance(v, str) and placeholder in v:
                        result[k] = v.replace(placeholder, replacement)

        return DotDict(result)

    def __getitem__(self, key):
        """Retrieves a value from the dictionary, supporting dot-notation for nested access.

        Args:
            key (str): The key to look up. If it contains dots, it is treated as a path.

        Returns:
            Any: The value associated with the key or path.

        Raises:
            KeyError: If the key or any part of the path is not found.
        """
        if isinstance(key, str) and "." in key:
            parts = key.split(".")
            current = self
            for part in parts:
                if isinstance(current, dict):
                    current = current[part]
                else:
                    raise KeyError(f"Path {key} is broken at {part}")
            return current
        return super().__getitem__(key)

    def __setitem__(self, key, value):
        """Sets a value in the dictionary, supporting dot-notation for nested assignment.

        Args:
            key (str): The key to set. If it contains dots, it is treated as a path.
            value (Any): The value to assign. Dictionaries are automatically converted to DotDict.
        """
        if isinstance(key, str) and "." in key:
            parts = key.split(".")
            current = self
            for part in parts[:-1]:
                if part not in current or not isinstance(current[part], dict):
                    current[part] = DotDict()
                current = current[part]

            last_part = parts[-1]
            if isinstance(value, dict) and not isinstance(value, DotDict):
                value = DotDict(value)
            current[last_part] = value
        else:
            if isinstance(value, dict) and not isinstance(value, DotDict):
                value = DotDict(value)
            super().__setitem__(key, value)

    def __delitem__(self, key):
        """Deletes a value from the dictionary, supporting dot-notation for nested deletion.

        Args:
            key (str): The key to delete. If it contains dots, it is treated as a path.

        Raises:
            KeyError: If the key or any part of the path is not found.
        """
        if isinstance(key, str) and "." in key:
            parts = key.split(".")
            current = self
            for part in parts[:-1]:
                if part not in current or not isinstance(current[part], dict):
                    raise KeyError(f"Path {key} is broken at {part}")
                current = current[part]
            del current[parts[-1]]
        else:
            super().__delitem__(key)

    def __getattr__(self, attr):
        """Returns an element using attribute access.

        Args:
            attr (str): The attribute name to look up.

        Returns:
            Any: The value associated with the attribute.

        Raises:
            AttributeError: If the attribute is not found.
        """
        try:
            return self[attr]
        except KeyError:
            raise AttributeError(f"'DotDict' object has no attribute '{attr}'")

    def __setattr__(self, key, value):
        """Sets an attribute value, which is stored as a dictionary item.

        Args:
            key (str): The attribute name.
            value (Any): The value to set.
        """
        self[key] = value

    def __delattr__(self, key):
        """Deletes an attribute, which removes the corresponding dictionary item.

        Args:
            key (str): The attribute name to delete.

        Raises:
            AttributeError: If the attribute is not found.
        """
        try:
            del self[key]
        except KeyError:
            raise AttributeError(f"'DotDict' object has no attribute '{key}'")

    def merge(self, data):
        """Deep merges the provided data into this DotDict.

        If a key exists in both and both values are dictionaries, they are merged recursively.
        Standard dictionaries are automatically converted to DotDict.

        Args:
            data (dict|DotDict): The data to merge into this object.
        """
        if not isinstance(data, dict):
            return

        for k, v in data.items():
            if isinstance(v, dict) and k in self and isinstance(self[k], dict):
                # Recursive merge for nested dictionaries
                if not isinstance(self[k], DotDict):
                    self[k] = DotDict(self[k])
                self[k].merge(v)
            else:
                if isinstance(v, dict) and not isinstance(v, DotDict):
                    v = DotDict(v)
                self[k] = v

    def smart_get(self, key, default=None):
        """Retrieves a value using a smart lookup.

        First attempts a direct lookup (supporting dot-notation). If not found,
        performs a recursive search for the key in the nested structure.

        Args:
            key (str): The key to look up.
            default (Any, optional): The value to return if the key is not found.

        Returns:
            Any: The value found in the configuration, or the default value.
        """
        # 1. Try direct lookup (DotDict.__getitem__ handles dot-notation)
        try:
            return self[key]
        except (KeyError, TypeError):
            pass

        # 2. Fallback: Recursive search for the key in the nested structure
        def find_in_dict(d, target_key):
            if not isinstance(d, (dict, DotDict)):
                return None

            # Try to see if the target_key is a path within this dict
            try:
                return d[target_key]
            except (KeyError, TypeError):
                pass

            # Otherwise, search deeper
            for k, v in d.items():
                if isinstance(v, (dict, DotDict)):
                    res = find_in_dict(v, target_key)
                    if res is not None:
                        return res
            return None

        val = find_in_dict(self, key)
        return val if val is not None else default

    def to_dict(self):
        """Recursively converts the DotDict and all nested DotDicts to standard dictionaries.

        Returns:
            dict: A plain Python dictionary representation of the DotDict.
        """
        result = {}
        for k, v in self.items():
            if isinstance(v, DotDict):
                result[k] = v.to_dict()
            elif isinstance(v, dict):
                # Handle cases where a standard dict might have been inserted
                result[k] = DotDict(v).to_dict()
            else:
                result[k] = v
        return result

    def to_json(self, indent=None):
        """Returns a JSON string representation of the DotDict.

        Args:
            indent (int, optional): Number of spaces for indentation. Defaults to None.

        Returns:
            str: The JSON representation of the DotDict.
        """
        return json.dumps(self.to_dict(), indent=indent)

    @property
    def dict(self):
        """Returns the DotDict as a plain Python dictionary.

        Returns:
            dict: A plain Python dictionary representation of the DotDict.
        """
        return self.to_dict()

Attributes

dict property
dict

Returns the DotDict as a plain Python dictionary.

Returns:

Name Type Description
dict

A plain Python dictionary representation of the DotDict.

yaml property
yaml

Returns a YAML dump of the dictionary using literal block style for multi-line strings.

Returns:

Name Type Description
str

The YAML representation of the DotDict.

Functions

__delattr__
__delattr__(key)

Deletes an attribute, which removes the corresponding dictionary item.

Parameters:

Name Type Description Default
key str

The attribute name to delete.

required

Raises:

Type Description
AttributeError

If the attribute is not found.

Source code in src/cloudmesh/ai/common/dotdict.py
282
283
284
285
286
287
288
289
290
291
292
293
294
def __delattr__(self, key):
    """Deletes an attribute, which removes the corresponding dictionary item.

    Args:
        key (str): The attribute name to delete.

    Raises:
        AttributeError: If the attribute is not found.
    """
    try:
        del self[key]
    except KeyError:
        raise AttributeError(f"'DotDict' object has no attribute '{key}'")
__delitem__
__delitem__(key)

Deletes a value from the dictionary, supporting dot-notation for nested deletion.

Parameters:

Name Type Description Default
key str

The key to delete. If it contains dots, it is treated as a path.

required

Raises:

Type Description
KeyError

If the key or any part of the path is not found.

Source code in src/cloudmesh/ai/common/dotdict.py
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
def __delitem__(self, key):
    """Deletes a value from the dictionary, supporting dot-notation for nested deletion.

    Args:
        key (str): The key to delete. If it contains dots, it is treated as a path.

    Raises:
        KeyError: If the key or any part of the path is not found.
    """
    if isinstance(key, str) and "." in key:
        parts = key.split(".")
        current = self
        for part in parts[:-1]:
            if part not in current or not isinstance(current[part], dict):
                raise KeyError(f"Path {key} is broken at {part}")
            current = current[part]
        del current[parts[-1]]
    else:
        super().__delitem__(key)
__getattr__
__getattr__(attr)

Returns an element using attribute access.

Parameters:

Name Type Description Default
attr str

The attribute name to look up.

required

Returns:

Name Type Description
Any

The value associated with the attribute.

Raises:

Type Description
AttributeError

If the attribute is not found.

Source code in src/cloudmesh/ai/common/dotdict.py
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
def __getattr__(self, attr):
    """Returns an element using attribute access.

    Args:
        attr (str): The attribute name to look up.

    Returns:
        Any: The value associated with the attribute.

    Raises:
        AttributeError: If the attribute is not found.
    """
    try:
        return self[attr]
    except KeyError:
        raise AttributeError(f"'DotDict' object has no attribute '{attr}'")
__getitem__
__getitem__(key)

Retrieves a value from the dictionary, supporting dot-notation for nested access.

Parameters:

Name Type Description Default
key str

The key to look up. If it contains dots, it is treated as a path.

required

Returns:

Name Type Description
Any

The value associated with the key or path.

Raises:

Type Description
KeyError

If the key or any part of the path is not found.

Source code in src/cloudmesh/ai/common/dotdict.py
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
def __getitem__(self, key):
    """Retrieves a value from the dictionary, supporting dot-notation for nested access.

    Args:
        key (str): The key to look up. If it contains dots, it is treated as a path.

    Returns:
        Any: The value associated with the key or path.

    Raises:
        KeyError: If the key or any part of the path is not found.
    """
    if isinstance(key, str) and "." in key:
        parts = key.split(".")
        current = self
        for part in parts:
            if isinstance(current, dict):
                current = current[part]
            else:
                raise KeyError(f"Path {key} is broken at {part}")
        return current
    return super().__getitem__(key)
__init__
__init__(data=None, **kwargs)

Initializes the DotDict with optional data and keyword arguments.

Parameters:

Name Type Description Default
data dict

Initial dictionary data. Defaults to None.

None
**kwargs

Additional key-value pairs to initialize the dictionary.

{}

Raises:

Type Description
TypeError

If the provided data is not a dictionary.

Source code in src/cloudmesh/ai/common/dotdict.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
def __init__(self, data=None, **kwargs):
    """Initializes the DotDict with optional data and keyword arguments.

    Args:
        data (dict, optional): Initial dictionary data. Defaults to None.
        **kwargs: Additional key-value pairs to initialize the dictionary.

    Raises:
        TypeError: If the provided data is not a dictionary.
    """
    if data is None:
        data = {}
    if not isinstance(data, dict):
        raise TypeError("Data must be a dictionary")

    # Recursively convert nested dictionaries to DotDict
    converted_data = OrderedDict()
    for k, v in data.items():
        if isinstance(v, dict):
            converted_data[k] = DotDict(v)
        else:
            converted_data[k] = v

    super().__init__(converted_data)
    self.update(kwargs)
__repr__
__repr__()

Returns a string representation of the DotDict as YAML.

Source code in src/cloudmesh/ai/common/dotdict.py
90
91
92
def __repr__(self):
    """Returns a string representation of the DotDict as YAML."""
    return self.yaml
__setattr__
__setattr__(key, value)

Sets an attribute value, which is stored as a dictionary item.

Parameters:

Name Type Description Default
key str

The attribute name.

required
value Any

The value to set.

required
Source code in src/cloudmesh/ai/common/dotdict.py
273
274
275
276
277
278
279
280
def __setattr__(self, key, value):
    """Sets an attribute value, which is stored as a dictionary item.

    Args:
        key (str): The attribute name.
        value (Any): The value to set.
    """
    self[key] = value
__setitem__
__setitem__(key, value)

Sets a value in the dictionary, supporting dot-notation for nested assignment.

Parameters:

Name Type Description Default
key str

The key to set. If it contains dots, it is treated as a path.

required
value Any

The value to assign. Dictionaries are automatically converted to DotDict.

required
Source code in src/cloudmesh/ai/common/dotdict.py
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
def __setitem__(self, key, value):
    """Sets a value in the dictionary, supporting dot-notation for nested assignment.

    Args:
        key (str): The key to set. If it contains dots, it is treated as a path.
        value (Any): The value to assign. Dictionaries are automatically converted to DotDict.
    """
    if isinstance(key, str) and "." in key:
        parts = key.split(".")
        current = self
        for part in parts[:-1]:
            if part not in current or not isinstance(current[part], dict):
                current[part] = DotDict()
            current = current[part]

        last_part = parts[-1]
        if isinstance(value, dict) and not isinstance(value, DotDict):
            value = DotDict(value)
        current[last_part] = value
    else:
        if isinstance(value, dict) and not isinstance(value, DotDict):
            value = DotDict(value)
        super().__setitem__(key, value)
expand
expand(d=None)

Expands placeholders in a dictionary using this DotDict's values.

If a value in the target dictionary is a string containing {attribute}, it is replaced by the value of the corresponding attribute found in this DotDict.

Parameters:

Name Type Description Default
d dict

The dictionary to expand. If None, this DotDict itself is expanded. Defaults to None.

None

Returns:

Name Type Description
DotDict

A new DotDict with expanded values.

Source code in src/cloudmesh/ai/common/dotdict.py
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
def expand(self, d=None):
    """Expands placeholders in a dictionary using this DotDict's values.

    If a value in the target dictionary is a string containing {attribute}, 
    it is replaced by the value of the corresponding attribute found in this DotDict.

    Args:
        d (dict, optional): The dictionary to expand. If None, this DotDict 
            itself is expanded. Defaults to None.

    Returns:
        DotDict: A new DotDict with expanded values.
    """
    # If d is None, we expand self. Otherwise, we expand d.
    target = d if d is not None else self

    if not isinstance(target, dict):
        raise TypeError("Target to expand must be a dictionary")

    result = OrderedDict()

    # Simple expansion: iterate over keys and replace {key} placeholders.
    # This avoids regex issues with shell variables like ${VAR}.

    # Start with a copy of the target
    result = OrderedDict(target) if isinstance(target, dict) else OrderedDict()

    # Iterate over all keys in this DotDict to use as replacement values
    for key in self.keys():
        val = self[key]
        if val is None:
            continue

        placeholder = f"{{{key}}}"
        replacement = str(val)

        for k, v in result.items():
            if isinstance(v, str) and placeholder in v:
                result[k] = v.replace(placeholder, replacement)

    # Also handle cases where the target has keys that should be expanded 
    # but aren't in 'self' (local scope expansion)
    if isinstance(target, dict):
        for key in target.keys():
            val = target[key]
            if val is None:
                continue

            placeholder = f"{{{key}}}"
            replacement = str(val)

            for k, v in result.items():
                if isinstance(v, str) and placeholder in v:
                    result[k] = v.replace(placeholder, replacement)

    return DotDict(result)
get
get(key, default=None)

Retrieves a value from the dictionary, supporting dot-notation for nested access.

Parameters:

Name Type Description Default
key str

The key to look up. If it contains dots, it is treated as a path.

required
default Any

The value to return if the key or path is not found.

None

Returns:

Name Type Description
Any

The value associated with the key or path, or the default value.

Source code in src/cloudmesh/ai/common/dotdict.py
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
def get(self, key, default=None):
    """Retrieves a value from the dictionary, supporting dot-notation for nested access.

    Args:
        key (str): The key to look up. If it contains dots, it is treated as a path.
        default (Any, optional): The value to return if the key or path is not found.

    Returns:
        Any: The value associated with the key or path, or the default value.
    """
    if isinstance(key, str) and "." in key:
        try:
            return self[key]
        except (KeyError, TypeError):
            return default
    return super().get(key, default)
merge
merge(data)

Deep merges the provided data into this DotDict.

If a key exists in both and both values are dictionaries, they are merged recursively. Standard dictionaries are automatically converted to DotDict.

Parameters:

Name Type Description Default
data dict | DotDict

The data to merge into this object.

required
Source code in src/cloudmesh/ai/common/dotdict.py
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
def merge(self, data):
    """Deep merges the provided data into this DotDict.

    If a key exists in both and both values are dictionaries, they are merged recursively.
    Standard dictionaries are automatically converted to DotDict.

    Args:
        data (dict|DotDict): The data to merge into this object.
    """
    if not isinstance(data, dict):
        return

    for k, v in data.items():
        if isinstance(v, dict) and k in self and isinstance(self[k], dict):
            # Recursive merge for nested dictionaries
            if not isinstance(self[k], DotDict):
                self[k] = DotDict(self[k])
            self[k].merge(v)
        else:
            if isinstance(v, dict) and not isinstance(v, DotDict):
                v = DotDict(v)
            self[k] = v
smart_get
smart_get(key, default=None)

Retrieves a value using a smart lookup.

First attempts a direct lookup (supporting dot-notation). If not found, performs a recursive search for the key in the nested structure.

Parameters:

Name Type Description Default
key str

The key to look up.

required
default Any

The value to return if the key is not found.

None

Returns:

Name Type Description
Any

The value found in the configuration, or the default value.

Source code in src/cloudmesh/ai/common/dotdict.py
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
def smart_get(self, key, default=None):
    """Retrieves a value using a smart lookup.

    First attempts a direct lookup (supporting dot-notation). If not found,
    performs a recursive search for the key in the nested structure.

    Args:
        key (str): The key to look up.
        default (Any, optional): The value to return if the key is not found.

    Returns:
        Any: The value found in the configuration, or the default value.
    """
    # 1. Try direct lookup (DotDict.__getitem__ handles dot-notation)
    try:
        return self[key]
    except (KeyError, TypeError):
        pass

    # 2. Fallback: Recursive search for the key in the nested structure
    def find_in_dict(d, target_key):
        if not isinstance(d, (dict, DotDict)):
            return None

        # Try to see if the target_key is a path within this dict
        try:
            return d[target_key]
        except (KeyError, TypeError):
            pass

        # Otherwise, search deeper
        for k, v in d.items():
            if isinstance(v, (dict, DotDict)):
                res = find_in_dict(v, target_key)
                if res is not None:
                    return res
        return None

    val = find_in_dict(self, key)
    return val if val is not None else default
to_dict
to_dict()

Recursively converts the DotDict and all nested DotDicts to standard dictionaries.

Returns:

Name Type Description
dict

A plain Python dictionary representation of the DotDict.

Source code in src/cloudmesh/ai/common/dotdict.py
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
def to_dict(self):
    """Recursively converts the DotDict and all nested DotDicts to standard dictionaries.

    Returns:
        dict: A plain Python dictionary representation of the DotDict.
    """
    result = {}
    for k, v in self.items():
        if isinstance(v, DotDict):
            result[k] = v.to_dict()
        elif isinstance(v, dict):
            # Handle cases where a standard dict might have been inserted
            result[k] = DotDict(v).to_dict()
        else:
            result[k] = v
    return result
to_json
to_json(indent=None)

Returns a JSON string representation of the DotDict.

Parameters:

Name Type Description Default
indent int

Number of spaces for indentation. Defaults to None.

None

Returns:

Name Type Description
str

The JSON representation of the DotDict.

Source code in src/cloudmesh/ai/common/dotdict.py
377
378
379
380
381
382
383
384
385
386
def to_json(self, indent=None):
    """Returns a JSON string representation of the DotDict.

    Args:
        indent (int, optional): Number of spaces for indentation. Defaults to None.

    Returns:
        str: The JSON representation of the DotDict.
    """
    return json.dumps(self.to_dict(), indent=indent)

Functions

HEADING

HEADING(txt=None, c='#', color='magenta')

Prints a message to stdout with #### surrounding it.

Source code in src/cloudmesh/ai/common/util.py
228
229
230
231
232
233
234
235
236
237
238
239
240
241
def HEADING(txt: Optional[str] = None, c: str = "#", color: str = "magenta") -> None:
    """Prints a message to stdout with #### surrounding it."""
    frame = inspect.getouterframes(inspect.currentframe())
    filename = frame[1][1].replace(os.getcwd(), "")
    line = frame[1][2] - 1
    method = frame[1][3]

    if txt is None:
        msg = f"{method} {filename} {line}"
    else:
        msg = f"{txt}\n {method} {filename} {line}"

    print()
    banner(msg, c=c, color=color)

SystemInfo

SystemInfo(info=None, user=None, node=None, realtime=False)

Collects comprehensive system metadata into a dictionary.

Parameters:

Name Type Description Default
info Optional[Dict[str, Any]]

Optional dictionary of additional information to merge into the result.

None
user Optional[str]

Optional override for the current system user.

None
node Optional[str]

Optional override for the system node name.

None
realtime bool

If True, includes real-time CPU, memory, and disk utilization metrics.

False

Returns:

Type Description
Dict[str, Any]

A dictionary containing system hardware, OS, and (optionally) real-time performance data.

Source code in src/cloudmesh/ai/common/sys.py
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
def systeminfo(info: Optional[Dict[str, Any]] = None, user: Optional[str] = None, node: Optional[str] = None, realtime: bool = False) -> Dict[str, Any]:
    """Collects comprehensive system metadata into a dictionary.

    Args:
        info: Optional dictionary of additional information to merge into the result.
        user: Optional override for the current system user.
        node: Optional override for the system node name.
        realtime: If True, includes real-time CPU, memory, and disk utilization metrics.

    Returns:
        A dictionary containing system hardware, OS, and (optionally) real-time performance data.
    """
    uname = platform.uname()
    mem = psutil.virtual_memory()

    data = OrderedDict({
        "cpu": get_cpu_description(),
        "cpu_count": multiprocessing.cpu_count(),
        "cpu_cores": psutil.cpu_count(logical=False) or "unknown",
        "cpu_threads": psutil.cpu_count(logical=True) or "unknown",
        "uname.system": uname.system,
        "uname.node": node or uname.node,
        "uname.release": uname.release,
        "uname.version": uname.version,
        "uname.machine": uname.machine,
        "uname.processor": uname.processor,
        "sys.platform": sys.platform,
        "python": sys.version,
        "python.version": sys.version.split(" ", 1)[0],
        "python.pip": pip.__version__,
        "user": user or sys_user(),
        "mem.percent": f"{mem.percent}%",
    })

    try:
        data["frequency"] = psutil.cpu_freq().current
    except Exception:
        data["frequency"] = None

    mem_fields = ["total", "available", "used", "free", "active", "inactive", "wired"]
    for attr in mem_fields:
        val = getattr(mem, attr, None)
        if val is not None:
            data[f"mem.{attr}"] = humanize.naturalsize(val, binary=True)

    # GPU Info
    data.update(get_gpu_info())

    # Thermal Info
    data.update(get_thermal_info())

    # NUMA Info
    data.update(get_numa_info())

    # Network Info
    data.update(get_network_info())

    # Container Info
    data.update(get_container_info())

    if os_is_mac():
        data["platform.version"] = platform.mac_ver()[0]
    elif os_is_windows():
        data["platform.version"] = platform.win32_ver()
    else:
        try:
            for path in Path("/etc").glob("*release"):
                for line in path.read_text().splitlines():
                    if "=" in line:
                        k, v = line.split("=", 1)
                        clean_k = k.strip().replace(" ", "_")
                        clean_v = v.strip().strip('"\'')
                        data[clean_k] = clean_v
        except OSError:
            data["platform.version"] = uname.version

    if realtime:
        data.update(get_cpu_metrics())
        data.update(get_memory_metrics())
        data.update(get_disk_metrics())

    if info:
        data.update(info)

    data["date"] = str(datetime.datetime.now())
    return dict(data)

VERBOSE

VERBOSE(arguments)

Prints the arguments if debug is enabled.

Maintains backward compatibility with the original VERBOSE function.

Source code in src/cloudmesh/ai/common/debug.py
64
65
66
67
68
69
70
71
def VERBOSE(arguments: Any):
    """Prints the arguments if debug is enabled.

    Maintains backward compatibility with the original VERBOSE function.
    """
    if arguments and getattr(arguments, "verbose", False):
        Debug.enable(True)
        console.print(f"[dim blue]VERBOSE: {arguments}[/dim blue]")

backup_name

backup_name(filename)

creates a backup name of the form filename.bak.1

Source code in src/cloudmesh/ai/common/util.py
250
251
252
253
254
255
256
257
258
def backup_name(filename: Union[str, Path]) -> str:
    """creates a backup name of the form filename.bak.1"""
    location = Path(path_expand(str(filename)))
    n = 0
    while True:
        n += 1
        backup = location.with_suffix(f"{location.suffix}.bak.{n}")
        if not backup.exists():
            return str(backup)

banner

banner(label=None, txt=None, c='-', prefix='#', debug=True, color='blue', padding=False, figlet=False, font='big')

Standalone wrapper for console.banner.

Source code in src/cloudmesh/ai/common/io.py
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
def banner(
    label: Optional[str] = None,
    txt: Optional[str] = None,
    c: str = "-",
    prefix: str = "#",
    debug: bool = True,
    color: str = "blue",
    padding: bool = False,
    figlet: bool = False,
    font: str = "big",
):
    """Standalone wrapper for console.banner."""
    console.banner(
        label=label,
        txt=txt,
        c=c,
        prefix=prefix,
        debug=debug,
        color=color,
        padding=padding,
        figlet=figlet,
        font=font,
    )

flatten

flatten(d, parent_key='', sep='.')

Flattens a multidimensional dict into a one-dimensional dictionary.

Parameters:

Name Type Description Default
d Any

The multidimensional dictionary or list to flatten.

required
parent_key str

The prefix to use for the keys in the flattened dictionary. Defaults to "".

''
sep str

The separation character used to join nested keys. Defaults to "__".

'.'

Returns:

Type Description
Union[Dict, List]

A flattened dictionary if the input was a dictionary, a list of flattened

Union[Dict, List]

dictionaries if the input was a list, or the original object if it was

Union[Dict, List]

neither.

Source code in src/cloudmesh/ai/common/flatdict.py
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
def flatten(d: Any, parent_key: str = "", sep: str = ".") -> Union[Dict, List]:
    """Flattens a multidimensional dict into a one-dimensional dictionary.

    Args:
        d: The multidimensional dictionary or list to flatten.
        parent_key: The prefix to use for the keys in the flattened dictionary. 
            Defaults to "".
        sep: The separation character used to join nested keys. Defaults to "__".

    Returns:
        A flattened dictionary if the input was a dictionary, a list of flattened 
        dictionaries if the input was a list, or the original object if it was 
        neither.
    """
    if isinstance(d, list):
        flat = []
        for entry in d:
            flat.append(flatten(entry, parent_key=parent_key, sep=sep))
        return flat
    elif isinstance(d, collections.abc.MutableMapping):
        items = []
        for k, v in list(d.items()):
            new_key = parent_key + sep + k if parent_key else k
            if isinstance(v, collections.abc.MutableMapping):
                items.extend(list(flatten(v, new_key, sep=sep).items()))
            else:
                items.append((new_key, v))
        return dict(items)
    else:
        return d

path_expand

path_expand(text, slashreplace=True)

Standalone wrapper for console.expand_path.

Source code in src/cloudmesh/ai/common/io.py
421
422
423
def path_expand(text: str, slashreplace: bool = True) -> str:
    """Standalone wrapper for console.expand_path."""
    return console.expand_path(text, slashreplace)

readfile

readfile(path)

Standalone wrapper for console.readfile.

Source code in src/cloudmesh/ai/common/io.py
409
410
411
def readfile(path: str) -> str:
    """Standalone wrapper for console.readfile."""
    return console.readfile(path)

writefile

writefile(path, content)

Standalone wrapper for console.writefile.

Source code in src/cloudmesh/ai/common/io.py
413
414
415
def writefile(path: str, content: str) -> None:
    """Standalone wrapper for console.writefile."""
    console.writefile(path, content)

yn_choice

yn_choice(message, default='y', tries=None)

asks for a yes/no question.

Parameters:

Name Type Description Default
message str

the message containing the question

required
default str

the default answer

'y'
tries Optional[int]

the number of tries

None
Source code in src/cloudmesh/ai/common/util.py
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
def yn_choice(message: str, default: str = "y", tries: Optional[int] = None) -> bool:
    """asks for a yes/no question.

    Args:
        message: the message containing the question
        default: the default answer
        tries: the number of tries
    """
    choices = "Y/n" if default.lower() in ("y", "yes") else "y/N"
    if tries is None:
        choice = input(f"{message} ({choices}) ")
        values = ("y", "yes", "") if default == "y" else ("y", "yes")
        return choice.strip().lower() in values
    else:
        while tries > 0:
            choice = input(f"{message} ({choices}) ('q' to discard)").strip().lower()
            if choice in ("y", "yes"):
                return True
            elif choice in ("n", "no", "q"):
                return False
            print("Invalid input...")
            tries -= 1
        return False

Modules

DateTime

Classes

DateTime

Bases: object

This class provides some simple date time functions so we can use all the same format. Here is a simple example

start = DateTime.now()
stop = DateTime.now() + DateTime.delta(1)

print ("START", start)
print ("STOP", stop)
print("HUMANIZE STOP", DateTime.humanize(stop - start))
print ("LOCAL", DateTime.local(start))
print("UTC", DateTime.utc(start))
print("NATURAL", DateTime.natural(start))
print("WORDS", DateTime.words(start))
print("TIMEZONE", DateTime.timezone)

This will result in

START 2019-08-03 21:34:14.019147
STOP 2019-08-03 21:34:15.019150
HUMANIZE STOP a second ago
LOCAL 2019-08-03 17:34:14 EST
UTC 2019-08-03 21:34:14.019147 UTC
NATURAL 2019-08-03 21:34:14.019147 UTC
WORDS Sat 6 Aug 2019, 21:34:14 UTC
TIMEZONE EST
Source code in src/cloudmesh/ai/common/DateTime.py
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
class DateTime(object):
    """This class provides some simple date time functions so we can use all the
    same format. Here is a simple example

        start = DateTime.now()
        stop = DateTime.now() + DateTime.delta(1)

        print ("START", start)
        print ("STOP", stop)
        print("HUMANIZE STOP", DateTime.humanize(stop - start))
        print ("LOCAL", DateTime.local(start))
        print("UTC", DateTime.utc(start))
        print("NATURAL", DateTime.natural(start))
        print("WORDS", DateTime.words(start))
        print("TIMEZONE", DateTime.timezone)

    This will result in

        START 2019-08-03 21:34:14.019147
        STOP 2019-08-03 21:34:15.019150
        HUMANIZE STOP a second ago
        LOCAL 2019-08-03 17:34:14 EST
        UTC 2019-08-03 21:34:14.019147 UTC
        NATURAL 2019-08-03 21:34:14.019147 UTC
        WORDS Sat 6 Aug 2019, 21:34:14 UTC
        TIMEZONE EST

    """

    timezone = TIME.datetime.now().astimezone().tzinfo

    @staticmethod
    def now():
        return str(TIME.datetime.now())

    @staticmethod
    def utc_now():
        return str(TIME.datetime.now(TIME.UTC))

    @staticmethod
    def natural(time):
        if type(time) == TIME.datetime:
            time = str(time)
        return str(parser.parse(time)) + " UTC"

    @staticmethod
    def words(time):
        if type(time) == TIME.datetime:
            t = time
        else:
            t = DateTime.datetime(time)
        return TIME.datetime.strftime(t, "%a %w %b %Y, %H:%M:%S UTC")

    @staticmethod
    def datetime(time):
        if type(time) == TIME:
            return time
        else:
            return DateTime.humanize(time)

    @staticmethod
    def humanize(time):
        return HUMANIZE.naturaltime(time)

    @staticmethod
    def string(time):
        if type(time) == TIME:
            d = str(time)
        else:
            try:
                d = parser.parse(time)
            except:
                d = DateTime.utc_to_local(time)
        return str(d)

    @staticmethod
    def get(time_str):
        return parser.parse(time_str)

    @staticmethod
    def delta(n):
        """
        Returns a timedelta object representing a time duration of `n` seconds.

        Parameters:
        n (int): The number of seconds for the time duration.

        Returns:
        timedelta: A timedelta object representing the time duration in datetime and not string format.
        """
        return TIME.timedelta(seconds=n)

    @staticmethod
    def utc(time):
        return str(time) + " UTC"

    @staticmethod
    def local(time):
        return DateTime.utc_to_local(time)

    @staticmethod
    def utc_to_local(time):
        if "." in str(time):
            TIME_FORMAT = "%Y-%m-%d %H:%M:%S.%f"
        else:
            TIME_FORMAT = "%Y-%m-%d %H:%M:%S"

        utc = TIME.datetime.strptime(str(time), TIME_FORMAT)
        local_dt = utc.replace(tzinfo=ZoneInfo("UTC")).astimezone(
            tzlocal.get_localzone()
        )
        return local_dt.strftime(TIME_FORMAT) + " " + str(DateTime.timezone)

    def datetime(time_str):
        d = parser.parse(time_str)
        return d

    @staticmethod
    def add(one, two):
        return DateTime.datetime(DateTime.datetime(one)) + DateTime.datetime(two)

    @staticmethod
    def timezone(default: str = "America/New_York") -> str:
        """Returns the local timezone name."""
        try:
            return tzlocal.get_localzone_name()
        except Exception:
            return default

    @staticmethod
    def locale_name() -> str:
        """Detects system locale (e.g., 'en_us')."""
        try:
            lang_code, _ = locale.getlocale()
            return lang_code.split('_')[0].lower() if lang_code else "us"
        except Exception:
            return "us"
Functions
delta staticmethod
delta(n)

Returns a timedelta object representing a time duration of n seconds.

Parameters: n (int): The number of seconds for the time duration.

Returns: timedelta: A timedelta object representing the time duration in datetime and not string format.

Source code in src/cloudmesh/ai/common/DateTime.py
88
89
90
91
92
93
94
95
96
97
98
99
@staticmethod
def delta(n):
    """
    Returns a timedelta object representing a time duration of `n` seconds.

    Parameters:
    n (int): The number of seconds for the time duration.

    Returns:
    timedelta: A timedelta object representing the time duration in datetime and not string format.
    """
    return TIME.timedelta(seconds=n)
locale_name staticmethod
locale_name()

Detects system locale (e.g., 'en_us').

Source code in src/cloudmesh/ai/common/DateTime.py
138
139
140
141
142
143
144
145
@staticmethod
def locale_name() -> str:
    """Detects system locale (e.g., 'en_us')."""
    try:
        lang_code, _ = locale.getlocale()
        return lang_code.split('_')[0].lower() if lang_code else "us"
    except Exception:
        return "us"
timezone staticmethod
timezone(default='America/New_York')

Returns the local timezone name.

Source code in src/cloudmesh/ai/common/DateTime.py
130
131
132
133
134
135
136
@staticmethod
def timezone(default: str = "America/New_York") -> str:
    """Returns the local timezone name."""
    try:
        return tzlocal.get_localzone_name()
    except Exception:
        return default

Shell

A convenient method to execute shell commands and return their output.

Classes

Shell

Bases: object

The shell class allowing us to conveniently access many operating system commands.

Source code in src/cloudmesh/ai/common/Shell.py
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
class Shell(object):
    """The shell class allowing us to conveniently access many operating system commands."""

    @staticmethod
    def _filter_noise(text: str) -> str:
        """Filters out common SSH noise and warnings from the output."""
        if not text:
            return ""

        noise_patterns = {
            "** WARNING: connection is not using a post-quantum key exchange algorithm.",
            "** This session may be vulnerable to \"store now, decrypt later\" attacks.",
            "** The server may need to be upgraded. See https://openssh.com/pq.html"
        }

        lines = text.splitlines()
        filtered_lines = []

        for line in lines:
            if line.strip() in noise_patterns:
                continue
            filtered_lines.append(line)

        return "\n".join(filtered_lines).strip()

    @staticmethod
    def run(command, exitcode="", encoding="utf-8", replace=True, timeout=None):
        """executes the command and returns the output as string"""
        if sys.platform == "win32":
            if replace:
                c = "&"
            else:
                c = ";"
            command = f"{command}".replace(";", c)
        elif exitcode:
            command = f"{command} {exitcode}"

        try:
            if timeout is not None:
                r = subprocess.check_output(
                    command, stderr=subprocess.STDOUT, shell=True, timeout=timeout
                )
            else:
                r = subprocess.check_output(
                    command, stderr=subprocess.STDOUT, shell=True
                )
        except subprocess.CalledProcessError as e:
            raise RuntimeError(f"{e.returncode} {e.output.decode()}")

        if encoding is None or encoding == "utf-8":
            result = str(r, "utf-8")
            return Shell._filter_noise(result)
        else:
            return r

    @classmethod
    def execute(
        cls, cmd, arguments="", shell=False, cwd=None, traceflag=True, witherror=True
    ):
        """Run Shell command"""
        result = None
        os_command = [cmd]

        if isinstance(arguments, list):
            os_command = os_command + arguments
        elif isinstance(arguments, tuple):
            os_command = os_command + list(arguments)
        elif isinstance(arguments, str):
            os_command = os_command + arguments.split()

        if cwd is None:
            cwd = os.getcwd()

        try:
            if shell:
                result = subprocess.check_output(
                    os_command, stderr=subprocess.STDOUT, shell=True, cwd=cwd
                )
            else:
                result = subprocess.check_output(
                    os_command, stderr=subprocess.STDOUT, cwd=cwd,
                )
        except Exception:
            if witherror:
                Console.error("problem executing subprocess", traceflag=traceflag)

        if result is not None:
            result = result.strip().decode()
        return result

    @staticmethod
    def live(command):
        """Executes a command and prints output in real time"""
        process = subprocess.Popen(
            command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
        )
        output = []
        for line in process.stdout:
            print(line, end="")
            output.append(line)
        process.wait()
        return "".join(output)

    @staticmethod
    def browser(filename=None):
        """Opens a file or URL in the browser"""
        if not os.path.isabs(filename) and "http" not in filename:
            # Minimal map_filename implementation
            filename = os.path.abspath(filename)
        webbrowser.open(filename, new=2, autoraise=False)

    @staticmethod
    def rm(top):
        """Removes a directory tree"""
        p = Path(top)
        if not p.exists():
            return
        try:
            shutil.rmtree(p)
        except Exception as e:
            print(e)

    @staticmethod
    def install_chocolatey():
        """Install chocolatey windows package manager"""
        if not os_is_windows():
            Console.error("chocolatey can only be installed in Windows")
            return False
        try:
            Shell.run("choco --version")
            Console.ok("Chocolatey already installed")
            return True
        except RuntimeError:
            Console.msg("Installing chocolatey...")
            # Minimal implementation: assume user has admin or handles it
            url = "https://raw.githubusercontent.com/cloudmesh/cloudmesh-common/main/src/cloudmesh/common/bin/win-setup.bat"
            response = requests.get(url)
            if response.status_code == 200:
                bin_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "bin")
                os.makedirs(bin_dir, exist_ok=True)
                with open(os.path.join(bin_dir, "win-setup.bat"), "w") as f:
                    f.write(response.text)
                subprocess.run(f"powershell Start-Process -Wait -FilePath '{os.path.join(bin_dir, 'win-setup.bat')}'", shell=True)
                Console.ok("Chocolatey installed")
                return True
            return False

    @staticmethod
    def install_brew():
        """Installs Homebrew on macOS"""
        if not os_is_mac():
            Console.error("Homebrew can only be installed on mac")
            return False
        try:
            subprocess.check_output("brew --version", stderr=subprocess.STDOUT, shell=True)
            Console.ok("Homebrew already installed")
            return True
        except subprocess.CalledProcessError:
            Console.info("Installing Homebrew...")
            command = 'osascript -e \'tell application "Terminal" to do script "/bin/bash -c \\"export NONINTERACTIVE=1 ; $(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\\""\''
            subprocess.run(command, shell=True, check=True)
            return True
Functions
browser staticmethod
browser(filename=None)

Opens a file or URL in the browser

Source code in src/cloudmesh/ai/common/Shell.py
169
170
171
172
173
174
175
@staticmethod
def browser(filename=None):
    """Opens a file or URL in the browser"""
    if not os.path.isabs(filename) and "http" not in filename:
        # Minimal map_filename implementation
        filename = os.path.abspath(filename)
    webbrowser.open(filename, new=2, autoraise=False)
execute classmethod
execute(cmd, arguments='', shell=False, cwd=None, traceflag=True, witherror=True)

Run Shell command

Source code in src/cloudmesh/ai/common/Shell.py
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
@classmethod
def execute(
    cls, cmd, arguments="", shell=False, cwd=None, traceflag=True, witherror=True
):
    """Run Shell command"""
    result = None
    os_command = [cmd]

    if isinstance(arguments, list):
        os_command = os_command + arguments
    elif isinstance(arguments, tuple):
        os_command = os_command + list(arguments)
    elif isinstance(arguments, str):
        os_command = os_command + arguments.split()

    if cwd is None:
        cwd = os.getcwd()

    try:
        if shell:
            result = subprocess.check_output(
                os_command, stderr=subprocess.STDOUT, shell=True, cwd=cwd
            )
        else:
            result = subprocess.check_output(
                os_command, stderr=subprocess.STDOUT, cwd=cwd,
            )
    except Exception:
        if witherror:
            Console.error("problem executing subprocess", traceflag=traceflag)

    if result is not None:
        result = result.strip().decode()
    return result
install_brew staticmethod
install_brew()

Installs Homebrew on macOS

Source code in src/cloudmesh/ai/common/Shell.py
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
@staticmethod
def install_brew():
    """Installs Homebrew on macOS"""
    if not os_is_mac():
        Console.error("Homebrew can only be installed on mac")
        return False
    try:
        subprocess.check_output("brew --version", stderr=subprocess.STDOUT, shell=True)
        Console.ok("Homebrew already installed")
        return True
    except subprocess.CalledProcessError:
        Console.info("Installing Homebrew...")
        command = 'osascript -e \'tell application "Terminal" to do script "/bin/bash -c \\"export NONINTERACTIVE=1 ; $(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\\""\''
        subprocess.run(command, shell=True, check=True)
        return True
install_chocolatey staticmethod
install_chocolatey()

Install chocolatey windows package manager

Source code in src/cloudmesh/ai/common/Shell.py
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
@staticmethod
def install_chocolatey():
    """Install chocolatey windows package manager"""
    if not os_is_windows():
        Console.error("chocolatey can only be installed in Windows")
        return False
    try:
        Shell.run("choco --version")
        Console.ok("Chocolatey already installed")
        return True
    except RuntimeError:
        Console.msg("Installing chocolatey...")
        # Minimal implementation: assume user has admin or handles it
        url = "https://raw.githubusercontent.com/cloudmesh/cloudmesh-common/main/src/cloudmesh/common/bin/win-setup.bat"
        response = requests.get(url)
        if response.status_code == 200:
            bin_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "bin")
            os.makedirs(bin_dir, exist_ok=True)
            with open(os.path.join(bin_dir, "win-setup.bat"), "w") as f:
                f.write(response.text)
            subprocess.run(f"powershell Start-Process -Wait -FilePath '{os.path.join(bin_dir, 'win-setup.bat')}'", shell=True)
            Console.ok("Chocolatey installed")
            return True
        return False
live staticmethod
live(command)

Executes a command and prints output in real time

Source code in src/cloudmesh/ai/common/Shell.py
156
157
158
159
160
161
162
163
164
165
166
167
@staticmethod
def live(command):
    """Executes a command and prints output in real time"""
    process = subprocess.Popen(
        command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
    )
    output = []
    for line in process.stdout:
        print(line, end="")
        output.append(line)
    process.wait()
    return "".join(output)
rm staticmethod
rm(top)

Removes a directory tree

Source code in src/cloudmesh/ai/common/Shell.py
177
178
179
180
181
182
183
184
185
186
@staticmethod
def rm(top):
    """Removes a directory tree"""
    p = Path(top)
    if not p.exists():
        return
    try:
        shutil.rmtree(p)
    except Exception as e:
        print(e)
run staticmethod
run(command, exitcode='', encoding='utf-8', replace=True, timeout=None)

executes the command and returns the output as string

Source code in src/cloudmesh/ai/common/Shell.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
@staticmethod
def run(command, exitcode="", encoding="utf-8", replace=True, timeout=None):
    """executes the command and returns the output as string"""
    if sys.platform == "win32":
        if replace:
            c = "&"
        else:
            c = ";"
        command = f"{command}".replace(";", c)
    elif exitcode:
        command = f"{command} {exitcode}"

    try:
        if timeout is not None:
            r = subprocess.check_output(
                command, stderr=subprocess.STDOUT, shell=True, timeout=timeout
            )
        else:
            r = subprocess.check_output(
                command, stderr=subprocess.STDOUT, shell=True
            )
    except subprocess.CalledProcessError as e:
        raise RuntimeError(f"{e.returncode} {e.output.decode()}")

    if encoding is None or encoding == "utf-8":
        result = str(r, "utf-8")
        return Shell._filter_noise(result)
    else:
        return r
SubprocessError

Bases: Exception

Manages the formatting of the error and stdout.

Source code in src/cloudmesh/ai/common/Shell.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
class SubprocessError(Exception):
    """Manages the formatting of the error and stdout."""
    def __init__(self, cmd, returncode, stderr, stdout):
        self.cmd = cmd
        self.returncode = returncode
        self.stderr = stderr
        self.stdout = stdout

    def __str__(self):
        def indent(lines, amount, ch=" "):
            padding = amount * ch
            return padding + ("\n" + padding).join(lines.split("\n"))

        cmd = " ".join(map(quote, self.cmd))
        s = ""
        s += "Command: %s\n" % cmd
        s += "Exit code: %s\n" % self.returncode

        if self.stderr:
            s += "Stderr:\n" + indent(self.stderr, 4)
        if self.stdout:
            s += "Stdout:\n" + indent(self.stdout, 4)
        return s

Functions

windows_not_supported
windows_not_supported(f)

This is a decorator function that checks if the current platform is Windows. If it is, it prints an error message and returns an empty string.

Source code in src/cloudmesh/ai/common/Shell.py
26
27
28
29
30
31
32
33
34
35
36
37
38
def windows_not_supported(f):
    """
    This is a decorator function that checks if the current platform is Windows.
    If it is, it prints an error message and returns an empty string.
    """
    def wrapper(*args, **kwargs):
        host = get_platform()
        if host == "windows":
            Console.error("Not supported on windows")
            return ""
        else:
            return f(*args, **kwargs)
    return wrapper

aggregation

Telemetry aggregation utility for cloudmesh-ai. Provides tools to analyze and summarize telemetry data from various backends.

Classes

TelemetryAggregator

Analyzes telemetry records to provide summaries and statistics. Supports loading data from both JSONL files and SQLite databases.

Source code in src/cloudmesh/ai/common/aggregation.py
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
class TelemetryAggregator:
    """
    Analyzes telemetry records to provide summaries and statistics.
    Supports loading data from both JSONL files and SQLite databases.
    """

    def __init__(self, source: Union[str, Path]):
        """
        Initialize the TelemetryAggregator.

        Args:
            source: Path to the telemetry source (JSONL file or SQLite .db file).
        """
        self.source = Path(source).expanduser()
        self.records: List[Dict[str, Any]] = []
        self._load_data()

    def _load_data(self) -> None:
        """Detects source type and loads records into memory.
        """
        if self.source.suffix == ".db":
            self._load_from_sqlite()
        else:
            self._load_from_jsonl()

    def _load_from_jsonl(self) -> None:
        """Loads records from a JSONL file.
        """
        try:
            if not self.source.exists():
                return
            with open(self.source, "r") as f:
                for line in f:
                    if line.strip():
                        self.records.append(json.loads(line))
        except Exception as e:
            print(f"Error loading JSONL telemetry: {e}")

    def _load_from_sqlite(self) -> None:
        """Loads records from a SQLite database.
        """
        try:
            with sqlite3.connect(self.source) as conn:
                conn.row_factory = sqlite3.Row
                cursor = conn.execute("SELECT * FROM telemetry")
                for row in cursor:
                    record = dict(row)
                    # Convert JSON strings back to dicts
                    record["metrics"] = json.loads(record["metrics"]) if isinstance(record["metrics"], str) else record["metrics"]
                    record["system"] = json.loads(record["system"]) if isinstance(record["system"], str) else record["system"]
                    self.records.append(record)
        except Exception as e:
            print(f"Error loading SQLite telemetry: {e}")

    def get_summary(self) -> Dict[str, Any]:
        """
        Calculates a high-level summary of the telemetry data.

        Returns:
            A dictionary containing total records, success rate, 
            status distribution, and command distribution.
        """
        if not self.records:
            return {"error": "No records found"}

        total = len(self.records)
        status_counts = defaultdict(int)
        command_counts = defaultdict(int)

        for r in self.records:
            status_counts[r.get("status", "unknown")] += 1
            command_counts[r.get("command", "unknown")] += 1

        success_count = status_counts.get("completed", 0)

        return {
            "total_records": total,
            "success_rate": f"{(success_count / total) * 100:.2f}%",
            "status_distribution": dict(status_counts),
            "command_distribution": dict(command_counts),
        }

    def aggregate_metric(self, metric_name: str) -> Dict[str, Any]:
        """
        Calculates average, min, and max for a specific metric across all records.

        Args:
            metric_name: The key of the metric to aggregate from the 'metrics' dictionary.

        Returns:
            A dictionary containing the count, average, minimum, and maximum values.
        """
        values = []
        for r in self.records:
            metrics = r.get("metrics", {})
            if metric_name in metrics:
                val = metrics[metric_name]
                if isinstance(val, (int, float)):
                    values.append(val)

        if not values:
            return {"metric": metric_name, "status": "no data"}

        return {
            "metric": metric_name,
            "count": len(values),
            "avg": sum(values) / len(values),
            "min": min(values),
            "max": max(values),
        }
Functions
__init__
__init__(source)

Initialize the TelemetryAggregator.

Parameters:

Name Type Description Default
source Union[str, Path]

Path to the telemetry source (JSONL file or SQLite .db file).

required
Source code in src/cloudmesh/ai/common/aggregation.py
18
19
20
21
22
23
24
25
26
27
def __init__(self, source: Union[str, Path]):
    """
    Initialize the TelemetryAggregator.

    Args:
        source: Path to the telemetry source (JSONL file or SQLite .db file).
    """
    self.source = Path(source).expanduser()
    self.records: List[Dict[str, Any]] = []
    self._load_data()
aggregate_metric
aggregate_metric(metric_name)

Calculates average, min, and max for a specific metric across all records.

Parameters:

Name Type Description Default
metric_name str

The key of the metric to aggregate from the 'metrics' dictionary.

required

Returns:

Type Description
Dict[str, Any]

A dictionary containing the count, average, minimum, and maximum values.

Source code in src/cloudmesh/ai/common/aggregation.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
def aggregate_metric(self, metric_name: str) -> Dict[str, Any]:
    """
    Calculates average, min, and max for a specific metric across all records.

    Args:
        metric_name: The key of the metric to aggregate from the 'metrics' dictionary.

    Returns:
        A dictionary containing the count, average, minimum, and maximum values.
    """
    values = []
    for r in self.records:
        metrics = r.get("metrics", {})
        if metric_name in metrics:
            val = metrics[metric_name]
            if isinstance(val, (int, float)):
                values.append(val)

    if not values:
        return {"metric": metric_name, "status": "no data"}

    return {
        "metric": metric_name,
        "count": len(values),
        "avg": sum(values) / len(values),
        "min": min(values),
        "max": max(values),
    }
get_summary
get_summary()

Calculates a high-level summary of the telemetry data.

Returns:

Type Description
Dict[str, Any]

A dictionary containing total records, success rate,

Dict[str, Any]

status distribution, and command distribution.

Source code in src/cloudmesh/ai/common/aggregation.py
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
def get_summary(self) -> Dict[str, Any]:
    """
    Calculates a high-level summary of the telemetry data.

    Returns:
        A dictionary containing total records, success rate, 
        status distribution, and command distribution.
    """
    if not self.records:
        return {"error": "No records found"}

    total = len(self.records)
    status_counts = defaultdict(int)
    command_counts = defaultdict(int)

    for r in self.records:
        status_counts[r.get("status", "unknown")] += 1
        command_counts[r.get("command", "unknown")] += 1

    success_count = status_counts.get("completed", 0)

    return {
        "total_records": total,
        "success_rate": f"{(success_count / total) * 100:.2f}%",
        "status_distribution": dict(status_counts),
        "command_distribution": dict(command_counts),
    }

config

Classes

Config

Handles configuration for AI packages from a YAML file.

Source code in src/cloudmesh/ai/common/config.py
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
class Config:
    """Handles configuration for AI packages from a YAML file."""

    # Default path can be overridden by subclasses or during initialization
    DEFAULT_CONFIG_PATH = Path("~/.config/cloudmesh/cmc.yaml").expanduser()

    DEFAULTS = {}
    SCHEMA = {}

    def __init__(self, config_path: Optional[Path] = None):
        """Initialize the Config object.

        Args:
            config_path: Optional path to the configuration file. 
                Defaults to DEFAULT_CONFIG_PATH.
        """
        self.path = config_path or self.DEFAULT_CONFIG_PATH
        self.data = DotDict(copy.deepcopy(self.DEFAULTS))
        self._load_config()

    def _load_config(self):
        """Loads the configuration from the YAML file into the data dictionary."""
        if self.path.exists():
            try:
                with open(self.path, "r") as f:
                    user_config = yaml.safe_load(f)
                    if user_config:
                        self.data.update(user_config)
            except Exception as e:
                logger.warning(f"Could not load config file {self.path}: {e}")

    def get(self, key_path: str, default: Any = None) -> Any:
        """Get a value from the config using a dot-separated path (e.g., 'telemetry.enabled').

        Environment variables can override config values.

        Args:
            key_path: Dot-separated path to the configuration value.
            default: Value to return if the key is not found. Defaults to None.

        Returns:
            The configuration value if found, otherwise the default value.
        """
        # 1. Check for environment variable override
        env_var = f"AI_{key_path.replace('.', '_').upper()}"
        env_val = os.environ.get(env_var)
        if env_val is not None:
            if key_path in self.SCHEMA:
                expected_type = self.SCHEMA[key_path]["type"]
                try:
                    if expected_type is bool:
                        return env_val.lower() in ("true", "1", "yes")
                    return expected_type(env_val)
                except (ValueError, TypeError):
                    logger.warning(f"Environment variable {env_var} has invalid value '{env_val}' for type {expected_type.__name__}. Using config value.")
            else:
                return env_val

        # 2. Fallback to config data using DotDict's nested access
        try:
            # We can't use getattr(self.data, key_path) because key_path has dots
            # But we can use the same logic as YamlDB or just iterate
            val = self.data
            for k in key_path.split("."):
                val = val[k]
            return val
        except (KeyError, TypeError):
            return default

    def validate(self, key_path: str, value: Any):
        """Validates a configuration value against the schema if it exists.

        Args:
            key_path: Dot-separated path to the configuration value.
            value: The value to validate.

        Raises:
            TypeError: If the value does not match the expected type in the schema.
        """
        if key_path in self.SCHEMA:
            expected_type = self.SCHEMA[key_path]["type"]
            if not isinstance(value, expected_type):
                raise TypeError(f"Invalid type for '{key_path}'. Expected {expected_type.__name__}, got {type(value).__name__}.")

    def set(self, key_path: str, value: Any):
        """Set a value in the config using a dot-separated path (e.g., 'telemetry.enabled').

        Args:
            key_path: Dot-separated path to the configuration value.
            value: The value to set.
        """
        self.validate(key_path, value)
        keys = key_path.split(".")
        val = self.data
        for k in keys[:-1]:
            if k not in val or not isinstance(val[k], dict):
                val[k] = DotDict()
            val = val[k]
        val[keys[-1]] = value

    def save(self):
        """Saves the current configuration to the YAML file.

        Raises:
            OSError: If the configuration file cannot be written.
        """
        try:
            self.path.parent.mkdir(parents=True, exist_ok=True)
            with open(self.path, "w") as f:
                yaml.dump(self.data, f, default_flow_style=False)
        except Exception as e:
            logger.error(f"Could not save config file {self.path}: {e}")
            raise
Functions
__init__
__init__(config_path=None)

Initialize the Config object.

Parameters:

Name Type Description Default
config_path Optional[Path]

Optional path to the configuration file. Defaults to DEFAULT_CONFIG_PATH.

None
Source code in src/cloudmesh/ai/common/config.py
28
29
30
31
32
33
34
35
36
37
def __init__(self, config_path: Optional[Path] = None):
    """Initialize the Config object.

    Args:
        config_path: Optional path to the configuration file. 
            Defaults to DEFAULT_CONFIG_PATH.
    """
    self.path = config_path or self.DEFAULT_CONFIG_PATH
    self.data = DotDict(copy.deepcopy(self.DEFAULTS))
    self._load_config()
get
get(key_path, default=None)

Get a value from the config using a dot-separated path (e.g., 'telemetry.enabled').

Environment variables can override config values.

Parameters:

Name Type Description Default
key_path str

Dot-separated path to the configuration value.

required
default Any

Value to return if the key is not found. Defaults to None.

None

Returns:

Type Description
Any

The configuration value if found, otherwise the default value.

Source code in src/cloudmesh/ai/common/config.py
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
def get(self, key_path: str, default: Any = None) -> Any:
    """Get a value from the config using a dot-separated path (e.g., 'telemetry.enabled').

    Environment variables can override config values.

    Args:
        key_path: Dot-separated path to the configuration value.
        default: Value to return if the key is not found. Defaults to None.

    Returns:
        The configuration value if found, otherwise the default value.
    """
    # 1. Check for environment variable override
    env_var = f"AI_{key_path.replace('.', '_').upper()}"
    env_val = os.environ.get(env_var)
    if env_val is not None:
        if key_path in self.SCHEMA:
            expected_type = self.SCHEMA[key_path]["type"]
            try:
                if expected_type is bool:
                    return env_val.lower() in ("true", "1", "yes")
                return expected_type(env_val)
            except (ValueError, TypeError):
                logger.warning(f"Environment variable {env_var} has invalid value '{env_val}' for type {expected_type.__name__}. Using config value.")
        else:
            return env_val

    # 2. Fallback to config data using DotDict's nested access
    try:
        # We can't use getattr(self.data, key_path) because key_path has dots
        # But we can use the same logic as YamlDB or just iterate
        val = self.data
        for k in key_path.split("."):
            val = val[k]
        return val
    except (KeyError, TypeError):
        return default
save
save()

Saves the current configuration to the YAML file.

Raises:

Type Description
OSError

If the configuration file cannot be written.

Source code in src/cloudmesh/ai/common/config.py
119
120
121
122
123
124
125
126
127
128
129
130
131
def save(self):
    """Saves the current configuration to the YAML file.

    Raises:
        OSError: If the configuration file cannot be written.
    """
    try:
        self.path.parent.mkdir(parents=True, exist_ok=True)
        with open(self.path, "w") as f:
            yaml.dump(self.data, f, default_flow_style=False)
    except Exception as e:
        logger.error(f"Could not save config file {self.path}: {e}")
        raise
set
set(key_path, value)

Set a value in the config using a dot-separated path (e.g., 'telemetry.enabled').

Parameters:

Name Type Description Default
key_path str

Dot-separated path to the configuration value.

required
value Any

The value to set.

required
Source code in src/cloudmesh/ai/common/config.py
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
def set(self, key_path: str, value: Any):
    """Set a value in the config using a dot-separated path (e.g., 'telemetry.enabled').

    Args:
        key_path: Dot-separated path to the configuration value.
        value: The value to set.
    """
    self.validate(key_path, value)
    keys = key_path.split(".")
    val = self.data
    for k in keys[:-1]:
        if k not in val or not isinstance(val[k], dict):
            val[k] = DotDict()
        val = val[k]
    val[keys[-1]] = value
validate
validate(key_path, value)

Validates a configuration value against the schema if it exists.

Parameters:

Name Type Description Default
key_path str

Dot-separated path to the configuration value.

required
value Any

The value to validate.

required

Raises:

Type Description
TypeError

If the value does not match the expected type in the schema.

Source code in src/cloudmesh/ai/common/config.py
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
def validate(self, key_path: str, value: Any):
    """Validates a configuration value against the schema if it exists.

    Args:
        key_path: Dot-separated path to the configuration value.
        value: The value to validate.

    Raises:
        TypeError: If the value does not match the expected type in the schema.
    """
    if key_path in self.SCHEMA:
        expected_type = self.SCHEMA[key_path]["type"]
        if not isinstance(value, expected_type):
            raise TypeError(f"Invalid type for '{key_path}'. Expected {expected_type.__name__}, got {type(value).__name__}.")

Modules

debug

Classes

Debug

Utility class for enhanced debugging and tracing in cloudmesh-ai.

Source code in src/cloudmesh/ai/common/debug.py
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Debug:
    """Utility class for enhanced debugging and tracing in cloudmesh-ai."""

    _enabled = False

    @classmethod
    def enable(cls, value: bool = True):
        """Enables or disables debug output."""
        cls._enabled = value

    @classmethod
    def is_enabled(cls) -> bool:
        """Returns whether debug output is enabled."""
        return cls._enabled

    @classmethod
    def log(cls, message: str, level: str = "debug"):
        """Logs a message using the unified console if debug is enabled."""
        if not cls._enabled:
            return

        if level == "debug":
            console.print(f"[dim blue]DEBUG: {message}[/dim blue]")
        elif level == "trace":
            console.print(f"[dim cyan]TRACE: {message}[/dim cyan]")
        else:
            console.print(f"[bold blue]{level.upper()}: {message}[/bold blue]")
Functions
enable classmethod
enable(value=True)

Enables or disables debug output.

Source code in src/cloudmesh/ai/common/debug.py
11
12
13
14
@classmethod
def enable(cls, value: bool = True):
    """Enables or disables debug output."""
    cls._enabled = value
is_enabled classmethod
is_enabled()

Returns whether debug output is enabled.

Source code in src/cloudmesh/ai/common/debug.py
16
17
18
19
@classmethod
def is_enabled(cls) -> bool:
    """Returns whether debug output is enabled."""
    return cls._enabled
log classmethod
log(message, level='debug')

Logs a message using the unified console if debug is enabled.

Source code in src/cloudmesh/ai/common/debug.py
21
22
23
24
25
26
27
28
29
30
31
32
@classmethod
def log(cls, message: str, level: str = "debug"):
    """Logs a message using the unified console if debug is enabled."""
    if not cls._enabled:
        return

    if level == "debug":
        console.print(f"[dim blue]DEBUG: {message}[/dim blue]")
    elif level == "trace":
        console.print(f"[dim cyan]TRACE: {message}[/dim cyan]")
    else:
        console.print(f"[bold blue]{level.upper()}: {message}[/bold blue]")

Functions

VERBOSE
VERBOSE(arguments)

Prints the arguments if debug is enabled.

Maintains backward compatibility with the original VERBOSE function.

Source code in src/cloudmesh/ai/common/debug.py
64
65
66
67
68
69
70
71
def VERBOSE(arguments: Any):
    """Prints the arguments if debug is enabled.

    Maintains backward compatibility with the original VERBOSE function.
    """
    if arguments and getattr(arguments, "verbose", False):
        Debug.enable(True)
        console.print(f"[dim blue]VERBOSE: {arguments}[/dim blue]")
trace
trace(fn)

Decorator that logs the entry and exit of a function, including arguments and return value.

Only logs if Debug.is_enabled() is True.

Source code in src/cloudmesh/ai/common/debug.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def trace(fn: Callable):
    """Decorator that logs the entry and exit of a function, including arguments and return value.

    Only logs if Debug.is_enabled() is True.
    """
    @functools.wraps(fn)
    def wrapper(*args, **kwargs):
        if not Debug.is_enabled():
            return fn(*args, **kwargs)

        # Get function name and module
        fn_name = fn.__qualname__

        # Format arguments
        arg_str = ", ".join([repr(a) for a in args])
        kwarg_str = ", ".join([f"{k}={v!r}" for k, v in kwargs.items()])
        params = ", ".join(filter(None, [arg_str, kwarg_str]))

        Debug.log(f"Entering {fn_name}({params})", level="trace")

        try:
            result = fn(*args, **kwargs)
            Debug.log(f"Exiting {fn_name} -> {result!r}", level="trace")
            return result
        except Exception as e:
            Debug.log(f"Exception in {fn_name}: {type(e).__name__}: {e}", level="error")
            raise

    return wrapper

dotdict

DotDict provides a dictionary-like object that allows accessing and setting nested values using both attribute notation and dot-separated string keys.

Examples:

>>> data = {"cloudmesh": {"ai": {"server": "uva"}}}
>>> config = DotDict(data)

1. Attribute access (chaining)

>>> config.cloudmesh.ai.server
'uva'

2. Dot-notation bracket access

>>> config["cloudmesh.ai.server"]
'uva'

3. Dot-notation bracket assignment

>>> config["cloudmesh.ai.port"] = 8000
>>> config.cloudmesh.ai.port
8000

4. Nested attribute assignment

>>> config.new_section = DotDict()
>>> config.new_section.key = "value"
>>> config["new_section.key"]
'value'

5. Dot-notation deletion

>>> del config["cloudmesh.ai.server"]
>>> "server" in config.cloudmesh.ai
False

6. Expanding placeholders

>>> config = DotDict({"name": "gemma", "path": "/models/{name}"})
>>> expanded = config.expand()
>>> expanded.path
'/models/gemma'
>>> data = {"path": "/models/{name}"}
>>> expanded_external = config.expand(d=data)
>>> expanded_external["path"]
'/models/gemma'

7. YAML dump with literal blocks

>>> config = DotDict({"script": "line1

line2"}) >>> print(config.yaml) script: | line1 line2

Classes

DotDict

Bases: OrderedDict

A dictionary subclass that supports dot-notation for nested access and assignment.

Source code in src/cloudmesh/ai/common/dotdict.py
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
class DotDict(OrderedDict):
    """A dictionary subclass that supports dot-notation for nested access and assignment.

    Attributes:
        None
    """

    def get(self, key, default=None):
        """Retrieves a value from the dictionary, supporting dot-notation for nested access.

        Args:
            key (str): The key to look up. If it contains dots, it is treated as a path.
            default (Any, optional): The value to return if the key or path is not found.

        Returns:
            Any: The value associated with the key or path, or the default value.
        """
        if isinstance(key, str) and "." in key:
            try:
                return self[key]
            except (KeyError, TypeError):
                return default
        return super().get(key, default)

    def __repr__(self):
        """Returns a string representation of the DotDict as YAML."""
        return self.yaml

    def __init__(self, data=None, **kwargs):
        """Initializes the DotDict with optional data and keyword arguments.

        Args:
            data (dict, optional): Initial dictionary data. Defaults to None.
            **kwargs: Additional key-value pairs to initialize the dictionary.

        Raises:
            TypeError: If the provided data is not a dictionary.
        """
        if data is None:
            data = {}
        if not isinstance(data, dict):
            raise TypeError("Data must be a dictionary")

        # Recursively convert nested dictionaries to DotDict
        converted_data = OrderedDict()
        for k, v in data.items():
            if isinstance(v, dict):
                converted_data[k] = DotDict(v)
            else:
                converted_data[k] = v

        super().__init__(converted_data)
        self.update(kwargs)

    @property
    def yaml(self):
        """Returns a YAML dump of the dictionary using literal block style for multi-line strings.

        Returns:
            str: The YAML representation of the DotDict.
        """
        # Use a local dumper to avoid polluting global yaml state if possible,
        # but for simplicity we use the global representer.
        yaml.add_representer(str, str_presenter)
        return yaml.dump(self.to_dict(), default_flow_style=False)

    def expand(self, d=None):
        """Expands placeholders in a dictionary using this DotDict's values.

        If a value in the target dictionary is a string containing {attribute}, 
        it is replaced by the value of the corresponding attribute found in this DotDict.

        Args:
            d (dict, optional): The dictionary to expand. If None, this DotDict 
                itself is expanded. Defaults to None.

        Returns:
            DotDict: A new DotDict with expanded values.
        """
        # If d is None, we expand self. Otherwise, we expand d.
        target = d if d is not None else self

        if not isinstance(target, dict):
            raise TypeError("Target to expand must be a dictionary")

        result = OrderedDict()

        # Simple expansion: iterate over keys and replace {key} placeholders.
        # This avoids regex issues with shell variables like ${VAR}.

        # Start with a copy of the target
        result = OrderedDict(target) if isinstance(target, dict) else OrderedDict()

        # Iterate over all keys in this DotDict to use as replacement values
        for key in self.keys():
            val = self[key]
            if val is None:
                continue

            placeholder = f"{{{key}}}"
            replacement = str(val)

            for k, v in result.items():
                if isinstance(v, str) and placeholder in v:
                    result[k] = v.replace(placeholder, replacement)

        # Also handle cases where the target has keys that should be expanded 
        # but aren't in 'self' (local scope expansion)
        if isinstance(target, dict):
            for key in target.keys():
                val = target[key]
                if val is None:
                    continue

                placeholder = f"{{{key}}}"
                replacement = str(val)

                for k, v in result.items():
                    if isinstance(v, str) and placeholder in v:
                        result[k] = v.replace(placeholder, replacement)

        return DotDict(result)

    def __getitem__(self, key):
        """Retrieves a value from the dictionary, supporting dot-notation for nested access.

        Args:
            key (str): The key to look up. If it contains dots, it is treated as a path.

        Returns:
            Any: The value associated with the key or path.

        Raises:
            KeyError: If the key or any part of the path is not found.
        """
        if isinstance(key, str) and "." in key:
            parts = key.split(".")
            current = self
            for part in parts:
                if isinstance(current, dict):
                    current = current[part]
                else:
                    raise KeyError(f"Path {key} is broken at {part}")
            return current
        return super().__getitem__(key)

    def __setitem__(self, key, value):
        """Sets a value in the dictionary, supporting dot-notation for nested assignment.

        Args:
            key (str): The key to set. If it contains dots, it is treated as a path.
            value (Any): The value to assign. Dictionaries are automatically converted to DotDict.
        """
        if isinstance(key, str) and "." in key:
            parts = key.split(".")
            current = self
            for part in parts[:-1]:
                if part not in current or not isinstance(current[part], dict):
                    current[part] = DotDict()
                current = current[part]

            last_part = parts[-1]
            if isinstance(value, dict) and not isinstance(value, DotDict):
                value = DotDict(value)
            current[last_part] = value
        else:
            if isinstance(value, dict) and not isinstance(value, DotDict):
                value = DotDict(value)
            super().__setitem__(key, value)

    def __delitem__(self, key):
        """Deletes a value from the dictionary, supporting dot-notation for nested deletion.

        Args:
            key (str): The key to delete. If it contains dots, it is treated as a path.

        Raises:
            KeyError: If the key or any part of the path is not found.
        """
        if isinstance(key, str) and "." in key:
            parts = key.split(".")
            current = self
            for part in parts[:-1]:
                if part not in current or not isinstance(current[part], dict):
                    raise KeyError(f"Path {key} is broken at {part}")
                current = current[part]
            del current[parts[-1]]
        else:
            super().__delitem__(key)

    def __getattr__(self, attr):
        """Returns an element using attribute access.

        Args:
            attr (str): The attribute name to look up.

        Returns:
            Any: The value associated with the attribute.

        Raises:
            AttributeError: If the attribute is not found.
        """
        try:
            return self[attr]
        except KeyError:
            raise AttributeError(f"'DotDict' object has no attribute '{attr}'")

    def __setattr__(self, key, value):
        """Sets an attribute value, which is stored as a dictionary item.

        Args:
            key (str): The attribute name.
            value (Any): The value to set.
        """
        self[key] = value

    def __delattr__(self, key):
        """Deletes an attribute, which removes the corresponding dictionary item.

        Args:
            key (str): The attribute name to delete.

        Raises:
            AttributeError: If the attribute is not found.
        """
        try:
            del self[key]
        except KeyError:
            raise AttributeError(f"'DotDict' object has no attribute '{key}'")

    def merge(self, data):
        """Deep merges the provided data into this DotDict.

        If a key exists in both and both values are dictionaries, they are merged recursively.
        Standard dictionaries are automatically converted to DotDict.

        Args:
            data (dict|DotDict): The data to merge into this object.
        """
        if not isinstance(data, dict):
            return

        for k, v in data.items():
            if isinstance(v, dict) and k in self and isinstance(self[k], dict):
                # Recursive merge for nested dictionaries
                if not isinstance(self[k], DotDict):
                    self[k] = DotDict(self[k])
                self[k].merge(v)
            else:
                if isinstance(v, dict) and not isinstance(v, DotDict):
                    v = DotDict(v)
                self[k] = v

    def smart_get(self, key, default=None):
        """Retrieves a value using a smart lookup.

        First attempts a direct lookup (supporting dot-notation). If not found,
        performs a recursive search for the key in the nested structure.

        Args:
            key (str): The key to look up.
            default (Any, optional): The value to return if the key is not found.

        Returns:
            Any: The value found in the configuration, or the default value.
        """
        # 1. Try direct lookup (DotDict.__getitem__ handles dot-notation)
        try:
            return self[key]
        except (KeyError, TypeError):
            pass

        # 2. Fallback: Recursive search for the key in the nested structure
        def find_in_dict(d, target_key):
            if not isinstance(d, (dict, DotDict)):
                return None

            # Try to see if the target_key is a path within this dict
            try:
                return d[target_key]
            except (KeyError, TypeError):
                pass

            # Otherwise, search deeper
            for k, v in d.items():
                if isinstance(v, (dict, DotDict)):
                    res = find_in_dict(v, target_key)
                    if res is not None:
                        return res
            return None

        val = find_in_dict(self, key)
        return val if val is not None else default

    def to_dict(self):
        """Recursively converts the DotDict and all nested DotDicts to standard dictionaries.

        Returns:
            dict: A plain Python dictionary representation of the DotDict.
        """
        result = {}
        for k, v in self.items():
            if isinstance(v, DotDict):
                result[k] = v.to_dict()
            elif isinstance(v, dict):
                # Handle cases where a standard dict might have been inserted
                result[k] = DotDict(v).to_dict()
            else:
                result[k] = v
        return result

    def to_json(self, indent=None):
        """Returns a JSON string representation of the DotDict.

        Args:
            indent (int, optional): Number of spaces for indentation. Defaults to None.

        Returns:
            str: The JSON representation of the DotDict.
        """
        return json.dumps(self.to_dict(), indent=indent)

    @property
    def dict(self):
        """Returns the DotDict as a plain Python dictionary.

        Returns:
            dict: A plain Python dictionary representation of the DotDict.
        """
        return self.to_dict()
Attributes
dict property
dict

Returns the DotDict as a plain Python dictionary.

Returns:

Name Type Description
dict

A plain Python dictionary representation of the DotDict.

yaml property
yaml

Returns a YAML dump of the dictionary using literal block style for multi-line strings.

Returns:

Name Type Description
str

The YAML representation of the DotDict.

Functions
__delattr__
__delattr__(key)

Deletes an attribute, which removes the corresponding dictionary item.

Parameters:

Name Type Description Default
key str

The attribute name to delete.

required

Raises:

Type Description
AttributeError

If the attribute is not found.

Source code in src/cloudmesh/ai/common/dotdict.py
282
283
284
285
286
287
288
289
290
291
292
293
294
def __delattr__(self, key):
    """Deletes an attribute, which removes the corresponding dictionary item.

    Args:
        key (str): The attribute name to delete.

    Raises:
        AttributeError: If the attribute is not found.
    """
    try:
        del self[key]
    except KeyError:
        raise AttributeError(f"'DotDict' object has no attribute '{key}'")
__delitem__
__delitem__(key)

Deletes a value from the dictionary, supporting dot-notation for nested deletion.

Parameters:

Name Type Description Default
key str

The key to delete. If it contains dots, it is treated as a path.

required

Raises:

Type Description
KeyError

If the key or any part of the path is not found.

Source code in src/cloudmesh/ai/common/dotdict.py
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
def __delitem__(self, key):
    """Deletes a value from the dictionary, supporting dot-notation for nested deletion.

    Args:
        key (str): The key to delete. If it contains dots, it is treated as a path.

    Raises:
        KeyError: If the key or any part of the path is not found.
    """
    if isinstance(key, str) and "." in key:
        parts = key.split(".")
        current = self
        for part in parts[:-1]:
            if part not in current or not isinstance(current[part], dict):
                raise KeyError(f"Path {key} is broken at {part}")
            current = current[part]
        del current[parts[-1]]
    else:
        super().__delitem__(key)
__getattr__
__getattr__(attr)

Returns an element using attribute access.

Parameters:

Name Type Description Default
attr str

The attribute name to look up.

required

Returns:

Name Type Description
Any

The value associated with the attribute.

Raises:

Type Description
AttributeError

If the attribute is not found.

Source code in src/cloudmesh/ai/common/dotdict.py
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
def __getattr__(self, attr):
    """Returns an element using attribute access.

    Args:
        attr (str): The attribute name to look up.

    Returns:
        Any: The value associated with the attribute.

    Raises:
        AttributeError: If the attribute is not found.
    """
    try:
        return self[attr]
    except KeyError:
        raise AttributeError(f"'DotDict' object has no attribute '{attr}'")
__getitem__
__getitem__(key)

Retrieves a value from the dictionary, supporting dot-notation for nested access.

Parameters:

Name Type Description Default
key str

The key to look up. If it contains dots, it is treated as a path.

required

Returns:

Name Type Description
Any

The value associated with the key or path.

Raises:

Type Description
KeyError

If the key or any part of the path is not found.

Source code in src/cloudmesh/ai/common/dotdict.py
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
def __getitem__(self, key):
    """Retrieves a value from the dictionary, supporting dot-notation for nested access.

    Args:
        key (str): The key to look up. If it contains dots, it is treated as a path.

    Returns:
        Any: The value associated with the key or path.

    Raises:
        KeyError: If the key or any part of the path is not found.
    """
    if isinstance(key, str) and "." in key:
        parts = key.split(".")
        current = self
        for part in parts:
            if isinstance(current, dict):
                current = current[part]
            else:
                raise KeyError(f"Path {key} is broken at {part}")
        return current
    return super().__getitem__(key)
__init__
__init__(data=None, **kwargs)

Initializes the DotDict with optional data and keyword arguments.

Parameters:

Name Type Description Default
data dict

Initial dictionary data. Defaults to None.

None
**kwargs

Additional key-value pairs to initialize the dictionary.

{}

Raises:

Type Description
TypeError

If the provided data is not a dictionary.

Source code in src/cloudmesh/ai/common/dotdict.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
def __init__(self, data=None, **kwargs):
    """Initializes the DotDict with optional data and keyword arguments.

    Args:
        data (dict, optional): Initial dictionary data. Defaults to None.
        **kwargs: Additional key-value pairs to initialize the dictionary.

    Raises:
        TypeError: If the provided data is not a dictionary.
    """
    if data is None:
        data = {}
    if not isinstance(data, dict):
        raise TypeError("Data must be a dictionary")

    # Recursively convert nested dictionaries to DotDict
    converted_data = OrderedDict()
    for k, v in data.items():
        if isinstance(v, dict):
            converted_data[k] = DotDict(v)
        else:
            converted_data[k] = v

    super().__init__(converted_data)
    self.update(kwargs)
__repr__
__repr__()

Returns a string representation of the DotDict as YAML.

Source code in src/cloudmesh/ai/common/dotdict.py
90
91
92
def __repr__(self):
    """Returns a string representation of the DotDict as YAML."""
    return self.yaml
__setattr__
__setattr__(key, value)

Sets an attribute value, which is stored as a dictionary item.

Parameters:

Name Type Description Default
key str

The attribute name.

required
value Any

The value to set.

required
Source code in src/cloudmesh/ai/common/dotdict.py
273
274
275
276
277
278
279
280
def __setattr__(self, key, value):
    """Sets an attribute value, which is stored as a dictionary item.

    Args:
        key (str): The attribute name.
        value (Any): The value to set.
    """
    self[key] = value
__setitem__
__setitem__(key, value)

Sets a value in the dictionary, supporting dot-notation for nested assignment.

Parameters:

Name Type Description Default
key str

The key to set. If it contains dots, it is treated as a path.

required
value Any

The value to assign. Dictionaries are automatically converted to DotDict.

required
Source code in src/cloudmesh/ai/common/dotdict.py
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
def __setitem__(self, key, value):
    """Sets a value in the dictionary, supporting dot-notation for nested assignment.

    Args:
        key (str): The key to set. If it contains dots, it is treated as a path.
        value (Any): The value to assign. Dictionaries are automatically converted to DotDict.
    """
    if isinstance(key, str) and "." in key:
        parts = key.split(".")
        current = self
        for part in parts[:-1]:
            if part not in current or not isinstance(current[part], dict):
                current[part] = DotDict()
            current = current[part]

        last_part = parts[-1]
        if isinstance(value, dict) and not isinstance(value, DotDict):
            value = DotDict(value)
        current[last_part] = value
    else:
        if isinstance(value, dict) and not isinstance(value, DotDict):
            value = DotDict(value)
        super().__setitem__(key, value)
expand
expand(d=None)

Expands placeholders in a dictionary using this DotDict's values.

If a value in the target dictionary is a string containing {attribute}, it is replaced by the value of the corresponding attribute found in this DotDict.

Parameters:

Name Type Description Default
d dict

The dictionary to expand. If None, this DotDict itself is expanded. Defaults to None.

None

Returns:

Name Type Description
DotDict

A new DotDict with expanded values.

Source code in src/cloudmesh/ai/common/dotdict.py
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
def expand(self, d=None):
    """Expands placeholders in a dictionary using this DotDict's values.

    If a value in the target dictionary is a string containing {attribute}, 
    it is replaced by the value of the corresponding attribute found in this DotDict.

    Args:
        d (dict, optional): The dictionary to expand. If None, this DotDict 
            itself is expanded. Defaults to None.

    Returns:
        DotDict: A new DotDict with expanded values.
    """
    # If d is None, we expand self. Otherwise, we expand d.
    target = d if d is not None else self

    if not isinstance(target, dict):
        raise TypeError("Target to expand must be a dictionary")

    result = OrderedDict()

    # Simple expansion: iterate over keys and replace {key} placeholders.
    # This avoids regex issues with shell variables like ${VAR}.

    # Start with a copy of the target
    result = OrderedDict(target) if isinstance(target, dict) else OrderedDict()

    # Iterate over all keys in this DotDict to use as replacement values
    for key in self.keys():
        val = self[key]
        if val is None:
            continue

        placeholder = f"{{{key}}}"
        replacement = str(val)

        for k, v in result.items():
            if isinstance(v, str) and placeholder in v:
                result[k] = v.replace(placeholder, replacement)

    # Also handle cases where the target has keys that should be expanded 
    # but aren't in 'self' (local scope expansion)
    if isinstance(target, dict):
        for key in target.keys():
            val = target[key]
            if val is None:
                continue

            placeholder = f"{{{key}}}"
            replacement = str(val)

            for k, v in result.items():
                if isinstance(v, str) and placeholder in v:
                    result[k] = v.replace(placeholder, replacement)

    return DotDict(result)
get
get(key, default=None)

Retrieves a value from the dictionary, supporting dot-notation for nested access.

Parameters:

Name Type Description Default
key str

The key to look up. If it contains dots, it is treated as a path.

required
default Any

The value to return if the key or path is not found.

None

Returns:

Name Type Description
Any

The value associated with the key or path, or the default value.

Source code in src/cloudmesh/ai/common/dotdict.py
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
def get(self, key, default=None):
    """Retrieves a value from the dictionary, supporting dot-notation for nested access.

    Args:
        key (str): The key to look up. If it contains dots, it is treated as a path.
        default (Any, optional): The value to return if the key or path is not found.

    Returns:
        Any: The value associated with the key or path, or the default value.
    """
    if isinstance(key, str) and "." in key:
        try:
            return self[key]
        except (KeyError, TypeError):
            return default
    return super().get(key, default)
merge
merge(data)

Deep merges the provided data into this DotDict.

If a key exists in both and both values are dictionaries, they are merged recursively. Standard dictionaries are automatically converted to DotDict.

Parameters:

Name Type Description Default
data dict | DotDict

The data to merge into this object.

required
Source code in src/cloudmesh/ai/common/dotdict.py
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
def merge(self, data):
    """Deep merges the provided data into this DotDict.

    If a key exists in both and both values are dictionaries, they are merged recursively.
    Standard dictionaries are automatically converted to DotDict.

    Args:
        data (dict|DotDict): The data to merge into this object.
    """
    if not isinstance(data, dict):
        return

    for k, v in data.items():
        if isinstance(v, dict) and k in self and isinstance(self[k], dict):
            # Recursive merge for nested dictionaries
            if not isinstance(self[k], DotDict):
                self[k] = DotDict(self[k])
            self[k].merge(v)
        else:
            if isinstance(v, dict) and not isinstance(v, DotDict):
                v = DotDict(v)
            self[k] = v
smart_get
smart_get(key, default=None)

Retrieves a value using a smart lookup.

First attempts a direct lookup (supporting dot-notation). If not found, performs a recursive search for the key in the nested structure.

Parameters:

Name Type Description Default
key str

The key to look up.

required
default Any

The value to return if the key is not found.

None

Returns:

Name Type Description
Any

The value found in the configuration, or the default value.

Source code in src/cloudmesh/ai/common/dotdict.py
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
def smart_get(self, key, default=None):
    """Retrieves a value using a smart lookup.

    First attempts a direct lookup (supporting dot-notation). If not found,
    performs a recursive search for the key in the nested structure.

    Args:
        key (str): The key to look up.
        default (Any, optional): The value to return if the key is not found.

    Returns:
        Any: The value found in the configuration, or the default value.
    """
    # 1. Try direct lookup (DotDict.__getitem__ handles dot-notation)
    try:
        return self[key]
    except (KeyError, TypeError):
        pass

    # 2. Fallback: Recursive search for the key in the nested structure
    def find_in_dict(d, target_key):
        if not isinstance(d, (dict, DotDict)):
            return None

        # Try to see if the target_key is a path within this dict
        try:
            return d[target_key]
        except (KeyError, TypeError):
            pass

        # Otherwise, search deeper
        for k, v in d.items():
            if isinstance(v, (dict, DotDict)):
                res = find_in_dict(v, target_key)
                if res is not None:
                    return res
        return None

    val = find_in_dict(self, key)
    return val if val is not None else default
to_dict
to_dict()

Recursively converts the DotDict and all nested DotDicts to standard dictionaries.

Returns:

Name Type Description
dict

A plain Python dictionary representation of the DotDict.

Source code in src/cloudmesh/ai/common/dotdict.py
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
def to_dict(self):
    """Recursively converts the DotDict and all nested DotDicts to standard dictionaries.

    Returns:
        dict: A plain Python dictionary representation of the DotDict.
    """
    result = {}
    for k, v in self.items():
        if isinstance(v, DotDict):
            result[k] = v.to_dict()
        elif isinstance(v, dict):
            # Handle cases where a standard dict might have been inserted
            result[k] = DotDict(v).to_dict()
        else:
            result[k] = v
    return result
to_json
to_json(indent=None)

Returns a JSON string representation of the DotDict.

Parameters:

Name Type Description Default
indent int

Number of spaces for indentation. Defaults to None.

None

Returns:

Name Type Description
str

The JSON representation of the DotDict.

Source code in src/cloudmesh/ai/common/dotdict.py
377
378
379
380
381
382
383
384
385
386
def to_json(self, indent=None):
    """Returns a JSON string representation of the DotDict.

    Args:
        indent (int, optional): Number of spaces for indentation. Defaults to None.

    Returns:
        str: The JSON representation of the DotDict.
    """
    return json.dumps(self.to_dict(), indent=indent)

Functions

str_presenter
str_presenter(dumper, data)

Custom YAML representer to use literal block style for multi-line strings.

Source code in src/cloudmesh/ai/common/dotdict.py
58
59
60
61
62
def str_presenter(dumper, data):
    """Custom YAML representer to use literal block style for multi-line strings."""
    if len(data.splitlines()) > 1:
        return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|")
    return dumper.represent_scalar("tag:yaml.org,2002:str", data)

exceptions

Classes

CommonAIError

Bases: Exception

Base exception for all cloudmesh-ai-common errors.

Source code in src/cloudmesh/ai/common/exceptions.py
 9
10
11
class CommonAIError(Exception):
    """Base exception for all cloudmesh-ai-common errors."""
    pass
IOErrorBase

Bases: CommonAIError

Base exception for IO related errors.

Source code in src/cloudmesh/ai/common/exceptions.py
25
26
27
class IOErrorBase(CommonAIError):
    """Base exception for IO related errors."""
    pass
IOReadError

Bases: IOErrorBase

Raised when reading a file or stream fails.

Source code in src/cloudmesh/ai/common/exceptions.py
29
30
31
class IOReadError(IOErrorBase):
    """Raised when reading a file or stream fails."""
    pass
IOWriteError

Bases: IOErrorBase

Raised when writing to a file or stream fails.

Source code in src/cloudmesh/ai/common/exceptions.py
33
34
35
class IOWriteError(IOErrorBase):
    """Raised when writing to a file or stream fails."""
    pass
SSHAuthenticationError

Bases: SSHError

Raised when authentication fails.

Source code in src/cloudmesh/ai/common/exceptions.py
21
22
23
class SSHAuthenticationError(SSHError):
    """Raised when authentication fails."""
    pass
SSHConnectionError

Bases: SSHError

Raised when a connection to a remote host fails.

Source code in src/cloudmesh/ai/common/exceptions.py
17
18
19
class SSHConnectionError(SSHError):
    """Raised when a connection to a remote host fails."""
    pass
SSHError

Bases: CommonAIError

Base exception for SSH related errors.

Source code in src/cloudmesh/ai/common/exceptions.py
13
14
15
class SSHError(CommonAIError):
    """Base exception for SSH related errors."""
    pass
SecurityAuthError

Bases: SecurityError

Raised when privilege escalation (sudo) fails.

Source code in src/cloudmesh/ai/common/exceptions.py
41
42
43
class SecurityAuthError(SecurityError):
    """Raised when privilege escalation (sudo) fails."""
    pass
SecurityError

Bases: CommonAIError

Base exception for security and privilege escalation errors.

Source code in src/cloudmesh/ai/common/exceptions.py
37
38
39
class SecurityError(CommonAIError):
    """Base exception for security and privilege escalation errors."""
    pass

flatdict

FlatDict provides utilities for flattening nested dictionaries and managing them as a single-level map.

This module is useful for configuration management where nested structures need to be represented as flat key-value pairs (e.g., for environment variables or simple lookups).

Variable Expansion

The expand_config_parameters function (used by FlatDict.load) supports four types of expansion: 1. Internal YAML Expansion: Replaces {key} with the value of another key in the same dict. This is recursive, allowing chained references. 2. OS Environment Expansion: Replaces {os.VARIABLE} with the value of the system environment variable. 3. Cloudmesh Variable Expansion: Replaces {cloudmesh.VAR} or {cm.VAR} using the Variables registry. 4. Math Evaluation: Replaces eval(expression) with the result of a restricted Python evaluation (e.g., eval(1 + 1) becomes 2).

Examples:

>>> # 1. Basic Flattening
>>> data = {"cloudmesh": {"ai": {"server": "uva"}}}
>>> flat = FlatDict(data)
>>> print(flat["cloudmesh.ai.server"])
'uva'
>>> # 2. Unflattening
>>> nested = flat.unflatten()
>>> print(nested["cloudmesh"]["ai"]["server"])
'uva'
>>> # 3. Attribute-style access
>>> print(flat.cloudmesh.ai.server)
'uva'
>>> # 4. Creating from a Python Object
>>> class Server:
...     def __init__(self):
...         self.name = "uva"
...         self.port = 8000
>>> s = Server()
>>> flat_obj = FlatDict.from_object(s)
>>> print(flat_obj.name)
'uva'
>>> # 5. Variable Expansion
>>> data = {"user": "grey", "path": "/home/{user}/models"}
>>> flat_exp = FlatDict(data)
>>> flat_exp.load(content=data, expand=True)
>>> print(flat_exp.path)
'/home/grey/models'
>>> # 6. Applying to strings
>>> template = "Connecting to {cloudmesh.ai.server}..."
>>> print(flat.apply_in_string(template))
'Connecting to uva...'

Classes

FlatDict

Bases: dict

A data structure to manage a flattened dictionary.

This class provides a way to handle nested dictionaries as a single-level dictionary with keys joined by a separator.

Source code in src/cloudmesh/ai/common/flatdict.py
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
class FlatDict(dict):
    """A data structure to manage a flattened dictionary.

    This class provides a way to handle nested dictionaries as a single-level 
    dictionary with keys joined by a separator.
    """

    @staticmethod
    def _is_primitive(thing: Any) -> bool:
        """Checks if an object is a primitive type.

        Args:
            thing (Any): The object to check.

        Returns:
            bool: True if the object is a primitive type, False otherwise.
        """
        return type(thing) in (int, str, bool, bytes, dict, list)

    @classmethod
    def _object_to_dict(cls, obj: Any) -> Any:
        """Recursively converts an object's attributes into a dictionary.

        Args:
            obj (Any): The object to convert.

        Returns:
            Any: A dictionary representation of the object, or the object itself 
                if it is primitive.
        """
        if obj is None:
            return {}

        if cls._is_primitive(obj):
            return obj

        if isinstance(obj, list):
            return [cls._object_to_dict(inst) for inst in obj]

        dict_obj = {}
        for key in getattr(obj, "__dict__", {}):
            val = getattr(obj, key)
            dict_obj[key] = cls._object_to_dict(val)
        return dict_obj

    @classmethod
    def from_object(cls, obj: Any, **kwargs) -> 'FlatDict':
        """Creates a FlatDict from a Python object.

        Args:
            obj (Any): The object to convert and flatten.
            **kwargs: Additional arguments passed to the FlatDict constructor 
                (e.g., sep, expand).

        Returns:
            FlatDict: A FlatDict instance created from the object.
        """
        dict_result = cls._object_to_dict(obj)
        return cls(dict_result, **kwargs)

    def __init__(self, d: Optional[Dict] = None, expand: List[str] = ["os.", "cm.", "cloudmesh."], sep: str = "."):
        """Initializes the FlatDict.

        Args:
            d (Optional[Dict]): The dictionary data to flatten. Defaults to None.
            expand (List[str], optional): List of prefixes to expand. 
                Defaults to ["os.", "cm.", "cloudmesh."].
            sep (str, optional): The character used to indicate a hierarchy. 
                Defaults to "__".
        """
        data = d if d is not None else {}
        flattened = flatten(data, sep=sep)

        super().__init__(flattened)
        self.sep = sep

        if "all" in expand:
            self.expand_os = True
            self.expand_cloudmesh = True
            self.expand_cm = True
        else:
            self.expand_os = "os." in expand
            self.expand_cloudmesh = "cloudmesh." in expand
            self.expand_cm = "cm." in expand

    def __getattr__(self, attr):
        """Allow attribute-style access to keys.

        Args:
            attr (str): The attribute name (key) to retrieve.

        Returns:
            Any: The value associated with the key, or None if not found.
        """
        return self.get(attr)

    def search(self, key: str, value: Any = None) -> List[str]:
        """Returns keys that match the given regex pattern and value.

        Args:
            key (str): The regex pattern to search for in keys.
            value (Any, optional): The value to match against. Defaults to None.

        Returns:
            List[str]: A list of keys that match the pattern and value.
        """
        # Use a temporary FlatDict with dot separator for searching
        flat = FlatDict(self, sep=".")
        r = re.compile(key)
        result = list(filter(r.match, flat))
        if value is None:
            return result

        return [entry for entry in result if str(flat[entry]) == str(value)]

    def unflatten(self) -> Dict:
        """Unflattens the flat dict back to a regular nested dict.

        Returns:
            Dict: The unflattened nested dictionary.
        """
        result = {}
        for k, v in self.items():
            self._unflatten_entry(k, v, result)
        return result

    def _unflatten_entry(self, k: str, v: Any, out: Dict):
        """Helper to recursively unflatten a single key-value pair.

        Args:
            k (str): The flattened key.
            v (Any): The value.
            out (Dict): The dictionary to populate.
        """
        parts = k.split(self.sep, 1)
        key = parts[0]
        if len(parts) > 1:
            self._unflatten_entry(parts[1], v, out.setdefault(key, {}))
        else:
            out[key] = v

    def loadf(self, filename: str = None, sep: Optional[str] = None):
        """Loads configuration from a YAML file.

        Args:
            filename (str, optional): Path to the YAML configuration file.
            sep (str, optional): The separation character to use for flattening. 
                Defaults to self.sep.
        """
        actual_sep = sep if sep else self.sep
        config = read_config_parameters(filename=filename, sep=actual_sep)
        self.update(config)

    def loads(self, content: Any = None, sep: Optional[str] = None):
        """Loads configuration from a YAML string.

        Args:
            content (Any, optional): The YAML string to load.
            sep (str, optional): The separation character to use for flattening. 
                Defaults to self.sep.
        """
        actual_sep = sep if sep else self.sep
        config = read_config_parameters_from_string(content=content, sep=actual_sep)
        self.update(config)

    def loadd(self, content: Any = None, sep: Optional[str] = None):
        """Loads configuration from a dictionary.

        Args:
            content (Any, optional): The dictionary to load.
            sep (str, optional): The separation character to use for flattening. 
                Defaults to self.sep.
        """
        actual_sep = sep if sep else self.sep
        config = read_config_parameters_from_dict(content=content, sep=actual_sep)
        self.update(config)

    def load(self, content: Any = None, expand: bool = True, sep: str = "."):
        """Reads in the dict based on the values and types provided.

        Args:
            content (Any, optional): The content to load (file path, string, or dict).
            expand (bool, optional): Whether to expand variables. Defaults to True.
            sep (str, optional): The separation character to use. Defaults to ".".
        """
        if content is None:
            self.loads(None)
        elif isinstance(content, dict):
            self.loadd(content=content, sep=sep)
        elif os.path.isfile(str(content)):
            self.loadf(filename=content, sep=sep)
        elif isinstance(content, str):
            self.loads(content=content, sep=sep)
        else:
            # Re-initialize with None to reset
            self.clear()
            self.__init__(None, sep=sep)

        if expand:
            expanded = expand_config_parameters(
                flat=self,
                expand_yaml=True,
                expand_os=self.expand_os,
                expand_cloudmesh=self.expand_cloudmesh or self.expand_cm,
            )
            self.clear()
            self.update(expanded)

    def apply_in_string(self, content: str) -> str:
        """Replaces placeholders in a string with values from the flat dict.

        Placeholders should be in the format {key}.

        Args:
            content (str): The string containing placeholders.

        Returns:
            str: The string with placeholders replaced by their corresponding values.
        """
        result = content
        for key, value in self.items():
            result = result.replace(f"{{{key}}}", str(value))
        return result

    def apply(self, content: Any, write: bool = True) -> Optional[str]:
        """Converts a string or the contents of a file with the values of the flatdict.

        Args:
            content (Any): The string or file path to process.
            write (bool, optional): Whether to write the result back to the file. 
                Defaults to True.

        Returns:
            Optional[str]: The processed string, or None if content was invalid.
        """
        if content is None:
            return None
        elif os.path.isfile(str(content)):
            data = readfile(content)
            result = self.apply_in_string(data)
            if write:
                writefile(content, result)
            return result
        elif isinstance(content, str):
            return self.apply_in_string(content)
        return None
Functions
__getattr__
__getattr__(attr)

Allow attribute-style access to keys.

Parameters:

Name Type Description Default
attr str

The attribute name (key) to retrieve.

required

Returns:

Name Type Description
Any

The value associated with the key, or None if not found.

Source code in src/cloudmesh/ai/common/flatdict.py
278
279
280
281
282
283
284
285
286
287
def __getattr__(self, attr):
    """Allow attribute-style access to keys.

    Args:
        attr (str): The attribute name (key) to retrieve.

    Returns:
        Any: The value associated with the key, or None if not found.
    """
    return self.get(attr)
__init__
__init__(d=None, expand=['os.', 'cm.', 'cloudmesh.'], sep='.')

Initializes the FlatDict.

Parameters:

Name Type Description Default
d Optional[Dict]

The dictionary data to flatten. Defaults to None.

None
expand List[str]

List of prefixes to expand. Defaults to ["os.", "cm.", "cloudmesh."].

['os.', 'cm.', 'cloudmesh.']
sep str

The character used to indicate a hierarchy. Defaults to "__".

'.'
Source code in src/cloudmesh/ai/common/flatdict.py
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
def __init__(self, d: Optional[Dict] = None, expand: List[str] = ["os.", "cm.", "cloudmesh."], sep: str = "."):
    """Initializes the FlatDict.

    Args:
        d (Optional[Dict]): The dictionary data to flatten. Defaults to None.
        expand (List[str], optional): List of prefixes to expand. 
            Defaults to ["os.", "cm.", "cloudmesh."].
        sep (str, optional): The character used to indicate a hierarchy. 
            Defaults to "__".
    """
    data = d if d is not None else {}
    flattened = flatten(data, sep=sep)

    super().__init__(flattened)
    self.sep = sep

    if "all" in expand:
        self.expand_os = True
        self.expand_cloudmesh = True
        self.expand_cm = True
    else:
        self.expand_os = "os." in expand
        self.expand_cloudmesh = "cloudmesh." in expand
        self.expand_cm = "cm." in expand
apply
apply(content, write=True)

Converts a string or the contents of a file with the values of the flatdict.

Parameters:

Name Type Description Default
content Any

The string or file path to process.

required
write bool

Whether to write the result back to the file. Defaults to True.

True

Returns:

Type Description
Optional[str]

Optional[str]: The processed string, or None if content was invalid.

Source code in src/cloudmesh/ai/common/flatdict.py
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
def apply(self, content: Any, write: bool = True) -> Optional[str]:
    """Converts a string or the contents of a file with the values of the flatdict.

    Args:
        content (Any): The string or file path to process.
        write (bool, optional): Whether to write the result back to the file. 
            Defaults to True.

    Returns:
        Optional[str]: The processed string, or None if content was invalid.
    """
    if content is None:
        return None
    elif os.path.isfile(str(content)):
        data = readfile(content)
        result = self.apply_in_string(data)
        if write:
            writefile(content, result)
        return result
    elif isinstance(content, str):
        return self.apply_in_string(content)
    return None
apply_in_string
apply_in_string(content)

Replaces placeholders in a string with values from the flat dict.

Placeholders should be in the format {key}.

Parameters:

Name Type Description Default
content str

The string containing placeholders.

required

Returns:

Name Type Description
str str

The string with placeholders replaced by their corresponding values.

Source code in src/cloudmesh/ai/common/flatdict.py
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
def apply_in_string(self, content: str) -> str:
    """Replaces placeholders in a string with values from the flat dict.

    Placeholders should be in the format {key}.

    Args:
        content (str): The string containing placeholders.

    Returns:
        str: The string with placeholders replaced by their corresponding values.
    """
    result = content
    for key, value in self.items():
        result = result.replace(f"{{{key}}}", str(value))
    return result
from_object classmethod
from_object(obj, **kwargs)

Creates a FlatDict from a Python object.

Parameters:

Name Type Description Default
obj Any

The object to convert and flatten.

required
**kwargs

Additional arguments passed to the FlatDict constructor (e.g., sep, expand).

{}

Returns:

Name Type Description
FlatDict FlatDict

A FlatDict instance created from the object.

Source code in src/cloudmesh/ai/common/flatdict.py
238
239
240
241
242
243
244
245
246
247
248
249
250
251
@classmethod
def from_object(cls, obj: Any, **kwargs) -> 'FlatDict':
    """Creates a FlatDict from a Python object.

    Args:
        obj (Any): The object to convert and flatten.
        **kwargs: Additional arguments passed to the FlatDict constructor 
            (e.g., sep, expand).

    Returns:
        FlatDict: A FlatDict instance created from the object.
    """
    dict_result = cls._object_to_dict(obj)
    return cls(dict_result, **kwargs)
load
load(content=None, expand=True, sep='.')

Reads in the dict based on the values and types provided.

Parameters:

Name Type Description Default
content Any

The content to load (file path, string, or dict).

None
expand bool

Whether to expand variables. Defaults to True.

True
sep str

The separation character to use. Defaults to ".".

'.'
Source code in src/cloudmesh/ai/common/flatdict.py
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
def load(self, content: Any = None, expand: bool = True, sep: str = "."):
    """Reads in the dict based on the values and types provided.

    Args:
        content (Any, optional): The content to load (file path, string, or dict).
        expand (bool, optional): Whether to expand variables. Defaults to True.
        sep (str, optional): The separation character to use. Defaults to ".".
    """
    if content is None:
        self.loads(None)
    elif isinstance(content, dict):
        self.loadd(content=content, sep=sep)
    elif os.path.isfile(str(content)):
        self.loadf(filename=content, sep=sep)
    elif isinstance(content, str):
        self.loads(content=content, sep=sep)
    else:
        # Re-initialize with None to reset
        self.clear()
        self.__init__(None, sep=sep)

    if expand:
        expanded = expand_config_parameters(
            flat=self,
            expand_yaml=True,
            expand_os=self.expand_os,
            expand_cloudmesh=self.expand_cloudmesh or self.expand_cm,
        )
        self.clear()
        self.update(expanded)
loadd
loadd(content=None, sep=None)

Loads configuration from a dictionary.

Parameters:

Name Type Description Default
content Any

The dictionary to load.

None
sep str

The separation character to use for flattening. Defaults to self.sep.

None
Source code in src/cloudmesh/ai/common/flatdict.py
358
359
360
361
362
363
364
365
366
367
368
def loadd(self, content: Any = None, sep: Optional[str] = None):
    """Loads configuration from a dictionary.

    Args:
        content (Any, optional): The dictionary to load.
        sep (str, optional): The separation character to use for flattening. 
            Defaults to self.sep.
    """
    actual_sep = sep if sep else self.sep
    config = read_config_parameters_from_dict(content=content, sep=actual_sep)
    self.update(config)
loadf
loadf(filename=None, sep=None)

Loads configuration from a YAML file.

Parameters:

Name Type Description Default
filename str

Path to the YAML configuration file.

None
sep str

The separation character to use for flattening. Defaults to self.sep.

None
Source code in src/cloudmesh/ai/common/flatdict.py
334
335
336
337
338
339
340
341
342
343
344
def loadf(self, filename: str = None, sep: Optional[str] = None):
    """Loads configuration from a YAML file.

    Args:
        filename (str, optional): Path to the YAML configuration file.
        sep (str, optional): The separation character to use for flattening. 
            Defaults to self.sep.
    """
    actual_sep = sep if sep else self.sep
    config = read_config_parameters(filename=filename, sep=actual_sep)
    self.update(config)
loads
loads(content=None, sep=None)

Loads configuration from a YAML string.

Parameters:

Name Type Description Default
content Any

The YAML string to load.

None
sep str

The separation character to use for flattening. Defaults to self.sep.

None
Source code in src/cloudmesh/ai/common/flatdict.py
346
347
348
349
350
351
352
353
354
355
356
def loads(self, content: Any = None, sep: Optional[str] = None):
    """Loads configuration from a YAML string.

    Args:
        content (Any, optional): The YAML string to load.
        sep (str, optional): The separation character to use for flattening. 
            Defaults to self.sep.
    """
    actual_sep = sep if sep else self.sep
    config = read_config_parameters_from_string(content=content, sep=actual_sep)
    self.update(config)
search
search(key, value=None)

Returns keys that match the given regex pattern and value.

Parameters:

Name Type Description Default
key str

The regex pattern to search for in keys.

required
value Any

The value to match against. Defaults to None.

None

Returns:

Type Description
List[str]

List[str]: A list of keys that match the pattern and value.

Source code in src/cloudmesh/ai/common/flatdict.py
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
def search(self, key: str, value: Any = None) -> List[str]:
    """Returns keys that match the given regex pattern and value.

    Args:
        key (str): The regex pattern to search for in keys.
        value (Any, optional): The value to match against. Defaults to None.

    Returns:
        List[str]: A list of keys that match the pattern and value.
    """
    # Use a temporary FlatDict with dot separator for searching
    flat = FlatDict(self, sep=".")
    r = re.compile(key)
    result = list(filter(r.match, flat))
    if value is None:
        return result

    return [entry for entry in result if str(flat[entry]) == str(value)]
unflatten
unflatten()

Unflattens the flat dict back to a regular nested dict.

Returns:

Name Type Description
Dict Dict

The unflattened nested dictionary.

Source code in src/cloudmesh/ai/common/flatdict.py
308
309
310
311
312
313
314
315
316
317
def unflatten(self) -> Dict:
    """Unflattens the flat dict back to a regular nested dict.

    Returns:
        Dict: The unflattened nested dictionary.
    """
    result = {}
    for k, v in self.items():
        self._unflatten_entry(k, v, result)
    return result
Variables

A lightweight class for managing variables used in configuration expansion.

Attributes:

Name Type Description
_vars Dict

Internal storage for variables.

Source code in src/cloudmesh/ai/common/flatdict.py
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
class Variables:
    """A lightweight class for managing variables used in configuration expansion.

    Attributes:
        _vars (Dict): Internal storage for variables.
    """
    def __init__(self):
        """Initializes the Variables object."""
        self._vars = {}

    def __getitem__(self, key):
        """Retrieves a variable value by key.

        Args:
            key (str): The variable name.

        Returns:
            Any: The value of the variable, or an empty string if not found.
        """
        return self._vars.get(key, "")

    def __iter__(self):
        """Iterates over the variable keys.

        Returns:
            Iterator: An iterator over the keys of the variables dictionary.
        """
        return iter(self._vars)

    def __contains__(self, key):
        """Checks if a variable exists.

        Args:
            key (str): The variable name to check.

        Returns:
            bool: True if the variable exists, False otherwise.
        """
        return key in self._vars

    def add(self, key, value):
        """Adds or updates a variable.

        Args:
            key (str): The variable name.
            value (Any): The value to assign to the variable.
        """
        self._vars[key] = value

    def items(self):
        """Returns the variables as key-value pairs.

        Returns:
            ItemsView: A view of the variables dictionary items.
        """
        return self._vars.items()
Functions
__contains__
__contains__(key)

Checks if a variable exists.

Parameters:

Name Type Description Default
key str

The variable name to check.

required

Returns:

Name Type Description
bool

True if the variable exists, False otherwise.

Source code in src/cloudmesh/ai/common/flatdict.py
101
102
103
104
105
106
107
108
109
110
def __contains__(self, key):
    """Checks if a variable exists.

    Args:
        key (str): The variable name to check.

    Returns:
        bool: True if the variable exists, False otherwise.
    """
    return key in self._vars
__getitem__
__getitem__(key)

Retrieves a variable value by key.

Parameters:

Name Type Description Default
key str

The variable name.

required

Returns:

Name Type Description
Any

The value of the variable, or an empty string if not found.

Source code in src/cloudmesh/ai/common/flatdict.py
82
83
84
85
86
87
88
89
90
91
def __getitem__(self, key):
    """Retrieves a variable value by key.

    Args:
        key (str): The variable name.

    Returns:
        Any: The value of the variable, or an empty string if not found.
    """
    return self._vars.get(key, "")
__init__
__init__()

Initializes the Variables object.

Source code in src/cloudmesh/ai/common/flatdict.py
78
79
80
def __init__(self):
    """Initializes the Variables object."""
    self._vars = {}
__iter__
__iter__()

Iterates over the variable keys.

Returns:

Name Type Description
Iterator

An iterator over the keys of the variables dictionary.

Source code in src/cloudmesh/ai/common/flatdict.py
93
94
95
96
97
98
99
def __iter__(self):
    """Iterates over the variable keys.

    Returns:
        Iterator: An iterator over the keys of the variables dictionary.
    """
    return iter(self._vars)
add
add(key, value)

Adds or updates a variable.

Parameters:

Name Type Description Default
key str

The variable name.

required
value Any

The value to assign to the variable.

required
Source code in src/cloudmesh/ai/common/flatdict.py
112
113
114
115
116
117
118
119
def add(self, key, value):
    """Adds or updates a variable.

    Args:
        key (str): The variable name.
        value (Any): The value to assign to the variable.
    """
    self._vars[key] = value
items
items()

Returns the variables as key-value pairs.

Returns:

Name Type Description
ItemsView

A view of the variables dictionary items.

Source code in src/cloudmesh/ai/common/flatdict.py
121
122
123
124
125
126
127
def items(self):
    """Returns the variables as key-value pairs.

    Returns:
        ItemsView: A view of the variables dictionary items.
    """
    return self._vars.items()

Functions

expand_config_parameters
expand_config_parameters(flat=None, expand_yaml=True, expand_os=True, expand_cloudmesh=True, debug=False, depth=100)

Expands all variables in the flat dict if they are specified in the values.

Supports expansion of YAML variables, OS environment variables, and cloudmesh-specific variables.

Parameters:

Name Type Description Default
flat Dict

The flattened dictionary to expand. Defaults to None.

None
expand_yaml bool

Whether to expand variables defined within the dict itself. Defaults to True.

True
expand_os bool

Whether to expand OS environment variables (e.g., {os.HOME}). Defaults to True.

True
expand_cloudmesh bool

Whether to expand cloudmesh variables (e.g., {cm.USER}). Defaults to True.

True
debug bool

Whether to enable debug logging. Defaults to False.

False
depth int

Maximum recursion depth for YAML expansion. Defaults to 100.

100

Returns:

Name Type Description
Dict Dict

A new dictionary with all variables expanded.

Source code in src/cloudmesh/ai/common/flatdict.py
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
def expand_config_parameters(
    flat: Dict = None,
    expand_yaml: bool = True,
    expand_os: bool = True,
    expand_cloudmesh: bool = True,
    debug: bool = False,
    depth: int = 100,
) -> Dict:
    """Expands all variables in the flat dict if they are specified in the values.

    Supports expansion of YAML variables, OS environment variables, and 
    cloudmesh-specific variables.

    Args:
        flat (Dict, optional): The flattened dictionary to expand. Defaults to None.
        expand_yaml (bool, optional): Whether to expand variables defined within 
            the dict itself. Defaults to True.
        expand_os (bool, optional): Whether to expand OS environment variables 
            (e.g., {os.HOME}). Defaults to True.
        expand_cloudmesh (bool, optional): Whether to expand cloudmesh variables 
            (e.g., {cm.USER}). Defaults to True.
        debug (bool, optional): Whether to enable debug logging. Defaults to False.
        depth (int, optional): Maximum recursion depth for YAML expansion. 
            Defaults to 100.

    Returns:
        Dict: A new dictionary with all variables expanded.
    """
    if flat is None:
        return {}

    # Work on a copy to avoid mutating the original
    result = dict(flat)

    # 1. Expand internal YAML variables
    if expand_yaml:
        for _ in range(depth):
            changed = False
            for key, value in result.items():
                if not isinstance(value, str):
                    continue

                # Find all {var} patterns
                pattern = r"\{([^}]+)\}"
                matches = re.findall(pattern, value)

                for var_name in matches:
                    if var_name in result:
                        replacement = str(result[var_name])
                        result[key] = result[key].replace(f"{{{var_name}}}", replacement)
                        changed = True
            if not changed:
                break

    # 2. Expand OS environment variables
    if expand_os:
        for key, value in result.items():
            if isinstance(value, str) and "{os." in value:
                pattern = r"\{os\.([^}]+)\}"
                def replace_os(match):
                    var_name = match.group(1)
                    return os.environ.get(var_name, f"{{{var_name}}}")
                result[key] = re.sub(pattern, replace_os, value)

    # 3. Expand Cloudmesh variables
    if expand_cloudmesh:
        cm_vars = Variables()
        for key, value in result.items():
            if isinstance(value, str) and ("{cloudmesh." in value or "{cm." in value):
                pattern = r"\{(cloudmesh\.|cm\.)([^}]+)\}"
                def replace_cm(match):
                    var_name = match.group(2)
                    return str(cm_vars[var_name])
                result[key] = re.sub(pattern, replace_cm, value)

    # 4. Handle eval() expressions for basic math
    for key, value in result.items():
        if isinstance(value, str) and "eval(" in value:
            try:
                expr = value.replace("eval(", "").strip()
                if expr.endswith(")"):
                    expr = expr[:-1]
                # Use eval with restricted globals/locals for basic math
                result[key] = eval(expr, {"__builtins__": {}}, {})
            except Exception:
                pass

    return result
flatme
flatme(d)

Flattens all values in a dictionary if they are dictionaries.

Parameters:

Name Type Description Default
d Dict

The dictionary whose values should be flattened.

required

Returns:

Type Description
Dict

A new dictionary where all dictionary values have been flattened.

Source code in src/cloudmesh/ai/common/flatdict.py
179
180
181
182
183
184
185
186
187
188
189
190
191
def flatme(d: Dict) -> Dict:
    """Flattens all values in a dictionary if they are dictionaries.

    Args:
        d: The dictionary whose values should be flattened.

    Returns:
        A new dictionary where all dictionary values have been flattened.
    """
    o = {}
    for element in d:
        o[element] = flatten(d[element])
    return o
flatten
flatten(d, parent_key='', sep='.')

Flattens a multidimensional dict into a one-dimensional dictionary.

Parameters:

Name Type Description Default
d Any

The multidimensional dictionary or list to flatten.

required
parent_key str

The prefix to use for the keys in the flattened dictionary. Defaults to "".

''
sep str

The separation character used to join nested keys. Defaults to "__".

'.'

Returns:

Type Description
Union[Dict, List]

A flattened dictionary if the input was a dictionary, a list of flattened

Union[Dict, List]

dictionaries if the input was a list, or the original object if it was

Union[Dict, List]

neither.

Source code in src/cloudmesh/ai/common/flatdict.py
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
def flatten(d: Any, parent_key: str = "", sep: str = ".") -> Union[Dict, List]:
    """Flattens a multidimensional dict into a one-dimensional dictionary.

    Args:
        d: The multidimensional dictionary or list to flatten.
        parent_key: The prefix to use for the keys in the flattened dictionary. 
            Defaults to "".
        sep: The separation character used to join nested keys. Defaults to "__".

    Returns:
        A flattened dictionary if the input was a dictionary, a list of flattened 
        dictionaries if the input was a list, or the original object if it was 
        neither.
    """
    if isinstance(d, list):
        flat = []
        for entry in d:
            flat.append(flatten(entry, parent_key=parent_key, sep=sep))
        return flat
    elif isinstance(d, collections.abc.MutableMapping):
        items = []
        for k, v in list(d.items()):
            new_key = parent_key + sep + k if parent_key else k
            if isinstance(v, collections.abc.MutableMapping):
                items.extend(list(flatten(v, new_key, sep=sep).items()))
            else:
                items.append((new_key, v))
        return dict(items)
    else:
        return d
key_prefix_replace
key_prefix_replace(d, prefix, new_prefix='')

Replaces the list of prefixes in keys of a flattened dict.

Parameters:

Name Type Description Default
d Dict

The flattened dict.

required
prefix List[str]

A list of prefixes that are replaced with a new prefix.

required
new_prefix str

The new prefix.

''

Returns:

Type Description
Dict

The dict with the keys replaced as specified.

Source code in src/cloudmesh/ai/common/flatdict.py
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
def key_prefix_replace(d: Dict, prefix: List[str], new_prefix: str = "") -> Dict:
    """Replaces the list of prefixes in keys of a flattened dict.

    Args:
        d: The flattened dict.
        prefix: A list of prefixes that are replaced with a new prefix.
        new_prefix: The new prefix.

    Returns:
        The dict with the keys replaced as specified.
    """
    items = []
    for k, v in list(d.items()):
        new_key = k
        for p in prefix:
            new_key = new_key.replace(p, new_prefix, 1)
        items.append((new_key, v))
    return dict(items)
read_config_parameters
read_config_parameters(filename=None, d=None, sep='.')

Reads configuration parameters from a YAML file and produces a flattened dict.

Parameters:

Name Type Description Default
filename str

Path to the YAML configuration file.

None
d str

Optional YAML string to merge into the configuration.

None
sep str

The separation character used for flattening. Defaults to ".".

'.'

Returns:

Type Description
Dict

A flattened dictionary of the configuration parameters.

Source code in src/cloudmesh/ai/common/flatdict.py
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
def read_config_parameters(filename: str = None, d: str = None, sep: str = ".") -> Dict:
    """Reads configuration parameters from a YAML file and produces a flattened dict.

    Args:
        filename: Path to the YAML configuration file.
        d: Optional YAML string to merge into the configuration.
        sep: The separation character used for flattening. Defaults to ".".

    Returns:
        A flattened dictionary of the configuration parameters.
    """
    config = {}
    if filename:
        content = readfile(filename)
        config = yaml.safe_load(content) or {}
    if d:
        data = yaml.safe_load(d) or {}
        config.update(data)
    return flatten(config, sep=sep)
read_config_parameters_from_dict
read_config_parameters_from_dict(content=None, d=None, sep='.')

Reads configuration parameters from a dictionary and produces a flattened dict.

Parameters:

Name Type Description Default
content Dict

The dictionary to parse.

None
d str

Optional YAML string to merge into the configuration.

None
sep str

The separation character used for flattening. Defaults to ".".

'.'

Returns:

Type Description
Dict

A flattened dictionary of the configuration parameters.

Source code in src/cloudmesh/ai/common/flatdict.py
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
def read_config_parameters_from_dict(content: Dict = None, d: str = None, sep: str = ".") -> Dict:
    """Reads configuration parameters from a dictionary and produces a flattened dict.

    Args:
        content: The dictionary to parse.
        d: Optional YAML string to merge into the configuration.
        sep: The separation character used for flattening. Defaults to ".".

    Returns:
        A flattened dictionary of the configuration parameters.
    """
    config = {}
    if content:
        config = dict(content)
    if d:
        data = yaml.safe_load(d) or {}
        config.update(data)
    return flatten(config, sep=sep)
read_config_parameters_from_string
read_config_parameters_from_string(content=None, d=None, sep='.')

Reads configuration parameters from a YAML string and produces a flattened dict.

Parameters:

Name Type Description Default
content str

The YAML string to parse.

None
d str

Optional YAML string to merge into the configuration.

None
sep str

The separation character used for flattening. Defaults to ".".

'.'

Returns:

Type Description
Dict

A flattened dictionary of the configuration parameters.

Source code in src/cloudmesh/ai/common/flatdict.py
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
def read_config_parameters_from_string(content: str = None, d: str = None, sep: str = ".") -> Dict:
    """Reads configuration parameters from a YAML string and produces a flattened dict.

    Args:
        content: The YAML string to parse.
        d: Optional YAML string to merge into the configuration.
        sep: The separation character used for flattening. Defaults to ".".

    Returns:
        A flattened dictionary of the configuration parameters.
    """
    config = {}
    if content:
        config = yaml.safe_load(content) or {}
    if d:
        data = yaml.safe_load(d) or {}
        config.update(data)
    return flatten(config, sep=sep)

github

Classes

GitHub

Bases: GitHubBase

Main entry point for GitHub CLI operations.

Source code in src/cloudmesh/ai/common/github.py
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
class GitHub(GitHubBase):
    """Main entry point for GitHub CLI operations."""

    def repo(self, name: str) -> GitHubRepo:
        """Returns a GitHubRepo object for the given repository name."""
        return GitHubRepo(self, name)

    def user(self, username: str) -> GitHubUser:
        """Returns a GitHubUser object for the given username."""
        return GitHubUser(self, username)

    def org(self, org_name: str) -> GitHubOrg:
        """Returns a GitHubOrg object for the given organization name."""
        return GitHubOrg(self, org_name)

    def get_authenticated_user(self) -> Optional[str]:
        """Returns the login of the currently authenticated user."""
        return self._run(["api", "user", "-q", ".login"])
Functions
get_authenticated_user
get_authenticated_user()

Returns the login of the currently authenticated user.

Source code in src/cloudmesh/ai/common/github.py
203
204
205
def get_authenticated_user(self) -> Optional[str]:
    """Returns the login of the currently authenticated user."""
    return self._run(["api", "user", "-q", ".login"])
org
org(org_name)

Returns a GitHubOrg object for the given organization name.

Source code in src/cloudmesh/ai/common/github.py
199
200
201
def org(self, org_name: str) -> GitHubOrg:
    """Returns a GitHubOrg object for the given organization name."""
    return GitHubOrg(self, org_name)
repo
repo(name)

Returns a GitHubRepo object for the given repository name.

Source code in src/cloudmesh/ai/common/github.py
191
192
193
def repo(self, name: str) -> GitHubRepo:
    """Returns a GitHubRepo object for the given repository name."""
    return GitHubRepo(self, name)
user
user(username)

Returns a GitHubUser object for the given username.

Source code in src/cloudmesh/ai/common/github.py
195
196
197
def user(self, username: str) -> GitHubUser:
    """Returns a GitHubUser object for the given username."""
    return GitHubUser(self, username)
GitHubBase

Base class for GitHub CLI operations.

Source code in src/cloudmesh/ai/common/github.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class GitHubBase:
    """Base class for GitHub CLI operations."""

    def _run(self, args: List[str]) -> Any:
        """Executes a gh command and returns the result with rate limit handling."""
        max_retries = 2
        retry_delay = 3600 # 60 minutes

        for attempt in range(max_retries):
            try:
                # Use GH_NO_PROMPT=1 to prevent gh from hanging on interactive prompts
                env = os.environ.copy()
                env["GH_NO_PROMPT"] = "1"

                result = subprocess.run(
                    ["gh"] + args,
                    capture_output=True,
                    text=True,
                    check=True,
                    env=env,
                    timeout=30 # Prevent indefinite hangs
                )
                stdout = result.stdout.strip()
                if not stdout:
                    return None

                # Try to parse as JSON if it looks like it
                if stdout.startswith(("{", "[")):
                    try:
                        return json.loads(stdout)
                    except json.JSONDecodeError:
                        return stdout
                return stdout
            except subprocess.CalledProcessError as e:
                stderr = e.stderr or ""
                if "rate limit exceeded" in stderr.lower() or "HTTP 403" in stderr:
                    if attempt < max_retries - 1:
                        console.warning(f"GitHub API rate limit exceeded. Retrying in {retry_delay // 60} minutes...")
                        time.sleep(retry_delay)
                        continue
                raise GitHubError(f"GitHub CLI command failed: {stderr or e}")
            except FileNotFoundError:
                raise GitHubError("GitHub CLI ('gh') not found. Please install it.")
        return None
GitHubError

Bases: Exception

Exception raised for errors in GitHub CLI operations.

Source code in src/cloudmesh/ai/common/github.py
 9
10
11
class GitHubError(Exception):
    """Exception raised for errors in GitHub CLI operations."""
    pass
GitHubOrg

Bases: GitHubBase

Operations for a GitHub organization.

Source code in src/cloudmesh/ai/common/github.py
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
class GitHubOrg(GitHubBase):
    """Operations for a GitHub organization."""

    def __init__(self, gh: 'GitHub', org_name: str):
        self.gh = gh
        self.org_name = org_name

    def list_repos(self, limit: int = 1000, json_fields: Optional[str] = None) -> List[Dict]:
        """Lists repositories for the organization."""
        args = ["repo", "list", self.org_name, "--limit", str(limit)]
        if json_fields:
            args.extend(["--json", json_fields])

        res = self._run(args)
        return res if isinstance(res, list) else []

    def get_info(self) -> Optional[Dict]:
        """Gets organization information."""
        return self._run(["api", f"orgs/{self.org_name}"])

    def get_public_repos_count(self) -> int:
        """Returns the number of public repositories in the organization."""
        res = self._run(["api", f"orgs/{self.org_name}", "--jq", ".public_repos"])
        return int(res) if res and str(res).isdigit() else 0
Functions
get_info
get_info()

Gets organization information.

Source code in src/cloudmesh/ai/common/github.py
179
180
181
def get_info(self) -> Optional[Dict]:
    """Gets organization information."""
    return self._run(["api", f"orgs/{self.org_name}"])
get_public_repos_count
get_public_repos_count()

Returns the number of public repositories in the organization.

Source code in src/cloudmesh/ai/common/github.py
183
184
185
186
def get_public_repos_count(self) -> int:
    """Returns the number of public repositories in the organization."""
    res = self._run(["api", f"orgs/{self.org_name}", "--jq", ".public_repos"])
    return int(res) if res and str(res).isdigit() else 0
list_repos
list_repos(limit=1000, json_fields=None)

Lists repositories for the organization.

Source code in src/cloudmesh/ai/common/github.py
170
171
172
173
174
175
176
177
def list_repos(self, limit: int = 1000, json_fields: Optional[str] = None) -> List[Dict]:
    """Lists repositories for the organization."""
    args = ["repo", "list", self.org_name, "--limit", str(limit)]
    if json_fields:
        args.extend(["--json", json_fields])

    res = self._run(args)
    return res if isinstance(res, list) else []
GitHubRepo

Bases: GitHubBase

Operations for a specific GitHub repository.

Source code in src/cloudmesh/ai/common/github.py
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
class GitHubRepo(GitHubBase):
    """Operations for a specific GitHub repository."""

    def __init__(self, gh: 'GitHub', repo_name: str):
        self.gh = gh
        self.repo_name = repo_name

    def get(self) -> Optional[Dict]:
        """Gets general repository information."""
        return self._run(["api", f"repos/{self.repo_name}"])

    def list_repos(self, limit: int = 1000, json_fields: Optional[str] = None) -> List[Dict]:
        """Lists repositories for the owner of this repo (not typically used this way)."""
        # This is usually done via GitHub.repo(owner).list()
        return []

    def get_pull_requests_count(self) -> int:
        """Returns the number of open pull requests."""
        res = self._run(["api", f"repos/{self.repo_name}/pulls", "--jq", "length"])
        return int(res) if res and str(res).isdigit() else 0

    def get_branches_count(self) -> int:
        """Returns the number of branches."""
        res = self._run(["api", f"repos/{self.repo_name}/branches", "--jq", "length"])
        return int(res) if res and str(res).isdigit() else 0

    def get_tags_count(self) -> int:
        """Returns the number of tags."""
        res = self._run(["api", f"repos/{self.repo_name}/tags", "--jq", "length"])
        return int(res) if res and str(res).isdigit() else 0

    def get_latest_commit_date(self) -> Optional[str]:
        """Returns the date of the latest commit."""
        res = self._run(["api", f"repos/{self.repo_name}/commits", "--jq", ".[0].commit.committer.date"])
        return res.strip('"') if res else None

    def get_contributors_count(self) -> int:
        """Returns the number of contributors."""
        res = self._run(["api", f"repos/{self.repo_name}/contributors", "--jq", "length"])
        return int(res) if res and str(res).isdigit() else 0

    def get_latest_release(self) -> Optional[str]:
        """Returns the latest release tag name."""
        res = self._run(["release", "view", "-R", self.repo_name, "--json", "tagName"])
        if isinstance(res, dict):
            return res.get("tagName")
        return None

    def get_size(self) -> Optional[int]:
        """Returns the repository size in KB."""
        data = self.get()
        if isinstance(data, dict):
            return data.get("size")
        return None
Functions
get
get()

Gets general repository information.

Source code in src/cloudmesh/ai/common/github.py
65
66
67
def get(self) -> Optional[Dict]:
    """Gets general repository information."""
    return self._run(["api", f"repos/{self.repo_name}"])
get_branches_count
get_branches_count()

Returns the number of branches.

Source code in src/cloudmesh/ai/common/github.py
79
80
81
82
def get_branches_count(self) -> int:
    """Returns the number of branches."""
    res = self._run(["api", f"repos/{self.repo_name}/branches", "--jq", "length"])
    return int(res) if res and str(res).isdigit() else 0
get_contributors_count
get_contributors_count()

Returns the number of contributors.

Source code in src/cloudmesh/ai/common/github.py
94
95
96
97
def get_contributors_count(self) -> int:
    """Returns the number of contributors."""
    res = self._run(["api", f"repos/{self.repo_name}/contributors", "--jq", "length"])
    return int(res) if res and str(res).isdigit() else 0
get_latest_commit_date
get_latest_commit_date()

Returns the date of the latest commit.

Source code in src/cloudmesh/ai/common/github.py
89
90
91
92
def get_latest_commit_date(self) -> Optional[str]:
    """Returns the date of the latest commit."""
    res = self._run(["api", f"repos/{self.repo_name}/commits", "--jq", ".[0].commit.committer.date"])
    return res.strip('"') if res else None
get_latest_release
get_latest_release()

Returns the latest release tag name.

Source code in src/cloudmesh/ai/common/github.py
 99
100
101
102
103
104
def get_latest_release(self) -> Optional[str]:
    """Returns the latest release tag name."""
    res = self._run(["release", "view", "-R", self.repo_name, "--json", "tagName"])
    if isinstance(res, dict):
        return res.get("tagName")
    return None
get_pull_requests_count
get_pull_requests_count()

Returns the number of open pull requests.

Source code in src/cloudmesh/ai/common/github.py
74
75
76
77
def get_pull_requests_count(self) -> int:
    """Returns the number of open pull requests."""
    res = self._run(["api", f"repos/{self.repo_name}/pulls", "--jq", "length"])
    return int(res) if res and str(res).isdigit() else 0
get_size
get_size()

Returns the repository size in KB.

Source code in src/cloudmesh/ai/common/github.py
106
107
108
109
110
111
def get_size(self) -> Optional[int]:
    """Returns the repository size in KB."""
    data = self.get()
    if isinstance(data, dict):
        return data.get("size")
    return None
get_tags_count
get_tags_count()

Returns the number of tags.

Source code in src/cloudmesh/ai/common/github.py
84
85
86
87
def get_tags_count(self) -> int:
    """Returns the number of tags."""
    res = self._run(["api", f"repos/{self.repo_name}/tags", "--jq", "length"])
    return int(res) if res and str(res).isdigit() else 0
list_repos
list_repos(limit=1000, json_fields=None)

Lists repositories for the owner of this repo (not typically used this way).

Source code in src/cloudmesh/ai/common/github.py
69
70
71
72
def list_repos(self, limit: int = 1000, json_fields: Optional[str] = None) -> List[Dict]:
    """Lists repositories for the owner of this repo (not typically used this way)."""
    # This is usually done via GitHub.repo(owner).list()
    return []
GitHubUser

Bases: GitHubBase

Operations for a GitHub user.

Source code in src/cloudmesh/ai/common/github.py
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
class GitHubUser(GitHubBase):
    """Operations for a GitHub user."""

    def __init__(self, gh: 'GitHub', username: str):
        self.gh = gh
        self.username = username

    def list_repos(self, limit: int = 1000, json_fields: Optional[str] = None, include_username: bool = True) -> List[Dict]:
        """Lists repositories for the user."""
        args = ["repo", "list"]
        if include_username:
            args.append(self.username)
        args.extend(["--limit", str(limit)])
        if json_fields:
            args.extend(["--json", json_fields])

        res = self._run(args)
        return res if isinstance(res, list) else []

    def get_orgs(self) -> List[str]:
        """Lists organizations the user belongs to with pagination."""
        all_orgs = []
        page = 1
        while True:
            res = self._run(["api", f"user/orgs?per_page=100&page={page}", "--jq", ".[].login"])
            if not res:
                break

            if isinstance(res, str):
                lines = res.splitlines()
                if not lines:
                    break
                all_orgs.extend(lines)
            elif isinstance(res, list):
                if not res:
                    break
                all_orgs.extend(res)
            else:
                break

            # If we got fewer than 100, we've reached the last page
            if isinstance(res, str) and len(res.splitlines()) < 100:
                break
            if isinstance(res, list) and len(res) < 100:
                break

            page += 1

        return all_orgs
Functions
get_orgs
get_orgs()

Lists organizations the user belongs to with pagination.

Source code in src/cloudmesh/ai/common/github.py
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
def get_orgs(self) -> List[str]:
    """Lists organizations the user belongs to with pagination."""
    all_orgs = []
    page = 1
    while True:
        res = self._run(["api", f"user/orgs?per_page=100&page={page}", "--jq", ".[].login"])
        if not res:
            break

        if isinstance(res, str):
            lines = res.splitlines()
            if not lines:
                break
            all_orgs.extend(lines)
        elif isinstance(res, list):
            if not res:
                break
            all_orgs.extend(res)
        else:
            break

        # If we got fewer than 100, we've reached the last page
        if isinstance(res, str) and len(res.splitlines()) < 100:
            break
        if isinstance(res, list) and len(res) < 100:
            break

        page += 1

    return all_orgs
list_repos
list_repos(limit=1000, json_fields=None, include_username=True)

Lists repositories for the user.

Source code in src/cloudmesh/ai/common/github.py
120
121
122
123
124
125
126
127
128
129
130
def list_repos(self, limit: int = 1000, json_fields: Optional[str] = None, include_username: bool = True) -> List[Dict]:
    """Lists repositories for the user."""
    args = ["repo", "list"]
    if include_username:
        args.append(self.username)
    args.extend(["--limit", str(limit)])
    if json_fields:
        args.extend(["--json", json_fields])

    res = self._run(args)
    return res if isinstance(res, list) else []

io

Classes

BaseIO

Base class for I/O operations providing path expansion and file utilities.

Source code in src/cloudmesh/ai/common/io.py
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
class BaseIO:
    """Base class for I/O operations providing path expansion and file utilities."""

    def expand_path(self, text: str, slashreplace: bool = True) -> str:
        """Expands a path string by resolving '~', environment variables, and relative links."""
        if not text:
            return ""
        expanded = os.path.expandvars(os.path.expanduser(text))
        path_obj = Path(expanded).resolve()
        if slashreplace and os.name == 'nt':
            return str(path_obj)
        return path_obj.as_posix()

    def readfile(self, path: str) -> str:
        """Reads the content of a file."""
        try:
            location = self.expand_path(path)
            with open(location, 'r', encoding='utf-8') as f:
                return f.read()
        except Exception as e:
            logger.error(f"Failed to read file {path}: {e}")
            raise IOReadError(f"Could not read file {path}: {e}")

    def writefile(self, path: str, content: str) -> None:
        """Writes content to a file."""
        try:
            location = self.expand_path(path)
            path_obj = Path(location)
            path_obj.parent.mkdir(parents=True, exist_ok=True)
            path_obj.write_text(content, encoding='utf-8')
        except Exception as e:
            logger.error(f"Failed to write file {path}: {e}")
            raise IOWriteError(f"Could not write file {path}: {e}")

    def appendfile(self, path: str, content: str) -> None:
        """Appends content to a file."""
        try:
            location = self.expand_path(path)
            path_obj = Path(location)
            path_obj.parent.mkdir(parents=True, exist_ok=True)
            with open(path_obj, "a", encoding="utf-8") as outfile:
                outfile.write(content)
        except Exception as e:
            logger.error(f"Failed to append to file {path}: {e}")
            raise IOWriteError(f"Could not append to file {path}: {e}")

    def load_yaml(self, path: Union[str, Path]) -> Optional[Dict[str, Any]]:
        """Safely loads a YAML file from the given path."""
        try:
            location = self.expand_path(str(path))
            path_obj = Path(location)
            if not path_obj.exists():
                return None
            with open(path_obj, 'r', encoding='utf-8') as f:
                return yaml.safe_load(f)
        except (yaml.YAMLError, OSError) as e:
            logger.error(f"YAML load error for {path}: {e}")
            return None

    def dump_yaml(self, path: Union[str, Path], data: Dict[str, Any]) -> None:
        """Safely writes a dictionary to a YAML file."""
        try:
            location = self.expand_path(str(path))
            path_obj = Path(location)
            path_obj.parent.mkdir(parents=True, exist_ok=True)
            with open(path_obj, 'w', encoding='utf-8') as f:
                yaml.dump(data, f, default_flow_style=False)
        except Exception as e:
            logger.error(f"YAML dump error for {path}: {e}")
            raise IOWriteError(f"Could not dump YAML to {path}: {e}")
Functions
appendfile
appendfile(path, content)

Appends content to a file.

Source code in src/cloudmesh/ai/common/io.py
66
67
68
69
70
71
72
73
74
75
76
def appendfile(self, path: str, content: str) -> None:
    """Appends content to a file."""
    try:
        location = self.expand_path(path)
        path_obj = Path(location)
        path_obj.parent.mkdir(parents=True, exist_ok=True)
        with open(path_obj, "a", encoding="utf-8") as outfile:
            outfile.write(content)
    except Exception as e:
        logger.error(f"Failed to append to file {path}: {e}")
        raise IOWriteError(f"Could not append to file {path}: {e}")
dump_yaml
dump_yaml(path, data)

Safely writes a dictionary to a YAML file.

Source code in src/cloudmesh/ai/common/io.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
def dump_yaml(self, path: Union[str, Path], data: Dict[str, Any]) -> None:
    """Safely writes a dictionary to a YAML file."""
    try:
        location = self.expand_path(str(path))
        path_obj = Path(location)
        path_obj.parent.mkdir(parents=True, exist_ok=True)
        with open(path_obj, 'w', encoding='utf-8') as f:
            yaml.dump(data, f, default_flow_style=False)
    except Exception as e:
        logger.error(f"YAML dump error for {path}: {e}")
        raise IOWriteError(f"Could not dump YAML to {path}: {e}")
expand_path
expand_path(text, slashreplace=True)

Expands a path string by resolving '~', environment variables, and relative links.

Source code in src/cloudmesh/ai/common/io.py
35
36
37
38
39
40
41
42
43
def expand_path(self, text: str, slashreplace: bool = True) -> str:
    """Expands a path string by resolving '~', environment variables, and relative links."""
    if not text:
        return ""
    expanded = os.path.expandvars(os.path.expanduser(text))
    path_obj = Path(expanded).resolve()
    if slashreplace and os.name == 'nt':
        return str(path_obj)
    return path_obj.as_posix()
load_yaml
load_yaml(path)

Safely loads a YAML file from the given path.

Source code in src/cloudmesh/ai/common/io.py
78
79
80
81
82
83
84
85
86
87
88
89
def load_yaml(self, path: Union[str, Path]) -> Optional[Dict[str, Any]]:
    """Safely loads a YAML file from the given path."""
    try:
        location = self.expand_path(str(path))
        path_obj = Path(location)
        if not path_obj.exists():
            return None
        with open(path_obj, 'r', encoding='utf-8') as f:
            return yaml.safe_load(f)
    except (yaml.YAMLError, OSError) as e:
        logger.error(f"YAML load error for {path}: {e}")
        return None
readfile
readfile(path)

Reads the content of a file.

Source code in src/cloudmesh/ai/common/io.py
45
46
47
48
49
50
51
52
53
def readfile(self, path: str) -> str:
    """Reads the content of a file."""
    try:
        location = self.expand_path(path)
        with open(location, 'r', encoding='utf-8') as f:
            return f.read()
    except Exception as e:
        logger.error(f"Failed to read file {path}: {e}")
        raise IOReadError(f"Could not read file {path}: {e}")
writefile
writefile(path, content)

Writes content to a file.

Source code in src/cloudmesh/ai/common/io.py
55
56
57
58
59
60
61
62
63
64
def writefile(self, path: str, content: str) -> None:
    """Writes content to a file."""
    try:
        location = self.expand_path(path)
        path_obj = Path(location)
        path_obj.parent.mkdir(parents=True, exist_ok=True)
        path_obj.write_text(content, encoding='utf-8')
    except Exception as e:
        logger.error(f"Failed to write file {path}: {e}")
        raise IOWriteError(f"Could not write file {path}: {e}")
Console

Bases: BaseIO, Console

Unified Console for cloudmesh-ai providing styled output, I/O, and table printing.

Source code in src/cloudmesh/ai/common/io.py
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
class Console(BaseIO, RichConsole):
    """Unified Console for cloudmesh-ai providing styled output, I/O, and table printing."""

    def error(self, message: str):
        """Prints an error message in red."""
        self.print(f"[red]ERROR: {message}[/red]")

    def warning(self, message: str):
        """Prints a warning message in yellow."""
        self.print(f"[yellow]WARNING: {message}[/yellow]")

    def msg(self, message: str):
        """Prints a message in blue."""
        self.print(f"[blue]MSG: {message}[/blue]")

    def info(self, message: str):
        """Prints an info message in magenta."""
        self.print(f"[magenta]INFO: {message}[/magenta]")

    def note(self, message: str):
        """Prints a note in cyan."""
        self.print(f"[cyan]NOTE: {message}[/cyan]")

    def ok(self, message: str):
        """Prints a success message in green."""
        self.print(f"[green]OK: {message}[/green]")

    def bold(self, message: str):
        """Prints a message in bold."""
        self.print(f"[bold]{message}[/bold]")

    def create_banner(self, title: str, content: Optional[str] = None) -> Panel:
        """Creates a banner Panel without printing it."""
        if not content and title:
            panel_content = Align.center(f"[bold magenta]{title}[/bold magenta]")
            styled_title = ""
        else:
            panel_content = content if content else ""
            styled_title = f"[bold magenta]{title}[/bold magenta]" if title else ""

        return Panel(
            panel_content,
            title=styled_title,
            box=box.ROUNDED,
            expand=True,
            border_style="bold blue"
        )

    def banner(
        self,
        label: Optional[str] = None,
        txt: Optional[str] = None,
        c: str = "-",
        prefix: str = "#",
        debug: bool = True,
        color: str = "blue",
        padding: bool = False,
        figlet: bool = False,
        font: str = "big",
    ) -> None:
        """Prints a banner of the form with a frame of # around the txt"""
        if not debug:
            return

        output = "\n"
        output += f"{prefix} {70 * c}\n"
        if padding:
            output += f"{prefix}\n"
        if label is not None:
            output += f"{prefix} {label}\n"
            output += f"{prefix} {70 * c}\n"

        if txt is not None:
            if figlet:
                txt = pyfiglet.figlet_format(txt, font=font)

            for line in txt.splitlines():
                output += f"{prefix} {line}\n"
            if padding:
                output += f"{prefix}\n"
            output += f"{prefix} {70 * c}\n"

        self.cprint(output, color, "")

    def cprint(self, text: str, color: str, style: str = ""):
        """Helper to print with color."""
        self.print(f"[{color}]{text}[/{color}]", style=style)

    def ynchoice(self, message: str, default: bool = True) -> bool:
        """Asks a yes/no question and returns a boolean."""
        suffix = " [Y/n]" if default else " [y/N]"
        while True:
            response = input(f"{message}{suffix} ").strip().lower()
            if not response:
                return default
            if response in ("y", "yes"):
                return True
            if response in ("n", "no"):
                return False
            self.print("[red]Please enter 'y' or 'n'.[/red]")

    def print_attributes(self, d: Dict[str, Any], header: Optional[List[str]] = None, 
                         order: Optional[List[str]] = None, sort_keys: bool = True, 
                         humanize: bool = False, output: str = "table"):
        """Prints a dictionary of attributes in various formats."""
        if not d:
            self.print("No attributes to display.")
            return

        if output == "json":
            self.print_json(d)
            return
        if output == "yaml":
            self.print_yaml(d)
            return
        if output == "csv":
            self.print_csv(d, order)
            return

        # Default: Table output
        if header is None:
            header = ["Attribute", "Value"]

        table = Table(title="Attributes", box=box.ROUNDED, expand=True)
        table.add_column(header[0])
        table.add_column(header[1])

        sorted_keys = order if order else (sorted(d.keys()) if sort_keys else list(d.keys()))

        for key in sorted_keys:
            if key not in d:
                continue
            val = d[key]

            if humanize:
                val = self._humanize(val)

            if isinstance(val, dict):
                table.add_row(key, "+")
                for k, v in val.items():
                    table.add_row(f"  - {k}", str(v))
            elif isinstance(val, list):
                table.add_row(key, "+")
                for item in val:
                    table.add_row("  -", str(item))
            else:
                table.add_row(key, str(val or ""))

        self.print(table)

    def print_table(self, headers: list, data: list, title: Optional[str] = None, expand: bool = False):
        """Prints a formatted table. By default, it is compact (expand=False)."""
        styled_title = f"[bold]{title}[/bold]" if title else None
        table = Table(title=styled_title, box=box.ROUNDED, expand=expand, header_style="bold")
        for header in headers:
            table.add_column(header)
        for row in data:
            table.add_row(*[str(item) for item in row])
        self.print(Align.center(table) if expand else Align.left(table))

    table = print_table

    def print_json(self, data: Any):
        """Prints data as formatted JSON."""
        self.print(json.dumps(data, indent=4))

    def print_yaml(self, data: Any):
        """Prints data as formatted YAML."""
        self.print(yaml.dump(data, default_flow_style=False))

    def print_csv(self, d: Dict[str, Any], order: Optional[List[str]] = None):
        """Prints a dictionary as CSV."""
        keys = order if order else sorted(d.keys())
        output = []
        output.append(",".join(keys))
        row = [str(d.get(k, "")) for k in keys]
        output.append(",".join(row))
        self.print("\n".join(output))

    def print_markdown(self, text: str):
        """Renders and prints markdown text."""
        self.print(Markdown(text))

    def _humanize(self, value: Any) -> str:
        """Basic humanization of values."""
        if isinstance(value, (int, float)) and abs(value) >= 1000000:
            return f"{value/1000000:.2f}M"
        if isinstance(value, (int, float)) and abs(value) >= 1000:
            return f"{value/1000:.2f}K"
        return str(value)

    def status(self, message: str) -> Status:
        """Returns a status spinner context manager."""
        return Status(f"[bold blue]{message}[/bold blue]", console=self)

    def top(self, lines: int):
        """Moves the cursor up by the specified number of lines."""
        if lines > 0:
            sys.stdout.write(f"\033[{lines}F")
            sys.stdout.flush()

    def left(self):
        """Moves the cursor to the beginning of the current line."""
        sys.stdout.write("\r")
        sys.stdout.flush()

    def ai_response(self, text: str, title: str = "AI Response", style: str = "cyan"):
        """Displays a standardized AI response box."""
        panel = Panel(
            text,
            title=title,
            title_style=style,
            border_style=style,
            expand=False,
            box=box.ROUNDED
        )
        self.print(panel)

    def telemetry_table(self, records: List[Dict[str, Any]], title: str = "Telemetry Records"):
        """Displays a standardized telemetry records table."""
        if not records:
            self.print("[yellow]No records to display.[/yellow]")
            return

        table = Table(
            title=title,
            box=box.ROUNDED,
            header_style="bold magenta"
        )

        headers = records[0].keys()
        for header in headers:
            table.add_column(header.capitalize(), style="cyan")

        for r in records:
            row = [str(r.get(h, "N/A")) for h in headers]
            table.add_row(*row)

        self.print(table)

    def print_status(self, message: str, style: str = "yellow"):
        """Prints a simple status message."""
        self.print(f"[{style}] {message}[/{style}]")

    def print_error(self, message: str):
        """Prints a standardized error message."""
        self.print(f"[bold red]Error:[/bold red] {message}")

    def print_success(self, message: str):
        """Prints a standardized success message."""
        self.print(f"[bold green]Success:[/bold green] {message}")
Functions
ai_response
ai_response(text, title='AI Response', style='cyan')

Displays a standardized AI response box.

Source code in src/cloudmesh/ai/common/io.py
309
310
311
312
313
314
315
316
317
318
319
def ai_response(self, text: str, title: str = "AI Response", style: str = "cyan"):
    """Displays a standardized AI response box."""
    panel = Panel(
        text,
        title=title,
        title_style=style,
        border_style=style,
        expand=False,
        box=box.ROUNDED
    )
    self.print(panel)
banner
banner(label=None, txt=None, c='-', prefix='#', debug=True, color='blue', padding=False, figlet=False, font='big')

Prints a banner of the form with a frame of # around the txt

Source code in src/cloudmesh/ai/common/io.py
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
def banner(
    self,
    label: Optional[str] = None,
    txt: Optional[str] = None,
    c: str = "-",
    prefix: str = "#",
    debug: bool = True,
    color: str = "blue",
    padding: bool = False,
    figlet: bool = False,
    font: str = "big",
) -> None:
    """Prints a banner of the form with a frame of # around the txt"""
    if not debug:
        return

    output = "\n"
    output += f"{prefix} {70 * c}\n"
    if padding:
        output += f"{prefix}\n"
    if label is not None:
        output += f"{prefix} {label}\n"
        output += f"{prefix} {70 * c}\n"

    if txt is not None:
        if figlet:
            txt = pyfiglet.figlet_format(txt, font=font)

        for line in txt.splitlines():
            output += f"{prefix} {line}\n"
        if padding:
            output += f"{prefix}\n"
        output += f"{prefix} {70 * c}\n"

    self.cprint(output, color, "")
bold
bold(message)

Prints a message in bold.

Source code in src/cloudmesh/ai/common/io.py
130
131
132
def bold(self, message: str):
    """Prints a message in bold."""
    self.print(f"[bold]{message}[/bold]")
cprint
cprint(text, color, style='')

Helper to print with color.

Source code in src/cloudmesh/ai/common/io.py
187
188
189
def cprint(self, text: str, color: str, style: str = ""):
    """Helper to print with color."""
    self.print(f"[{color}]{text}[/{color}]", style=style)
create_banner
create_banner(title, content=None)

Creates a banner Panel without printing it.

Source code in src/cloudmesh/ai/common/io.py
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
def create_banner(self, title: str, content: Optional[str] = None) -> Panel:
    """Creates a banner Panel without printing it."""
    if not content and title:
        panel_content = Align.center(f"[bold magenta]{title}[/bold magenta]")
        styled_title = ""
    else:
        panel_content = content if content else ""
        styled_title = f"[bold magenta]{title}[/bold magenta]" if title else ""

    return Panel(
        panel_content,
        title=styled_title,
        box=box.ROUNDED,
        expand=True,
        border_style="bold blue"
    )
error
error(message)

Prints an error message in red.

Source code in src/cloudmesh/ai/common/io.py
106
107
108
def error(self, message: str):
    """Prints an error message in red."""
    self.print(f"[red]ERROR: {message}[/red]")
info
info(message)

Prints an info message in magenta.

Source code in src/cloudmesh/ai/common/io.py
118
119
120
def info(self, message: str):
    """Prints an info message in magenta."""
    self.print(f"[magenta]INFO: {message}[/magenta]")
left
left()

Moves the cursor to the beginning of the current line.

Source code in src/cloudmesh/ai/common/io.py
304
305
306
307
def left(self):
    """Moves the cursor to the beginning of the current line."""
    sys.stdout.write("\r")
    sys.stdout.flush()
msg
msg(message)

Prints a message in blue.

Source code in src/cloudmesh/ai/common/io.py
114
115
116
def msg(self, message: str):
    """Prints a message in blue."""
    self.print(f"[blue]MSG: {message}[/blue]")
note
note(message)

Prints a note in cyan.

Source code in src/cloudmesh/ai/common/io.py
122
123
124
def note(self, message: str):
    """Prints a note in cyan."""
    self.print(f"[cyan]NOTE: {message}[/cyan]")
ok
ok(message)

Prints a success message in green.

Source code in src/cloudmesh/ai/common/io.py
126
127
128
def ok(self, message: str):
    """Prints a success message in green."""
    self.print(f"[green]OK: {message}[/green]")
print_attributes
print_attributes(d, header=None, order=None, sort_keys=True, humanize=False, output='table')

Prints a dictionary of attributes in various formats.

Source code in src/cloudmesh/ai/common/io.py
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
def print_attributes(self, d: Dict[str, Any], header: Optional[List[str]] = None, 
                     order: Optional[List[str]] = None, sort_keys: bool = True, 
                     humanize: bool = False, output: str = "table"):
    """Prints a dictionary of attributes in various formats."""
    if not d:
        self.print("No attributes to display.")
        return

    if output == "json":
        self.print_json(d)
        return
    if output == "yaml":
        self.print_yaml(d)
        return
    if output == "csv":
        self.print_csv(d, order)
        return

    # Default: Table output
    if header is None:
        header = ["Attribute", "Value"]

    table = Table(title="Attributes", box=box.ROUNDED, expand=True)
    table.add_column(header[0])
    table.add_column(header[1])

    sorted_keys = order if order else (sorted(d.keys()) if sort_keys else list(d.keys()))

    for key in sorted_keys:
        if key not in d:
            continue
        val = d[key]

        if humanize:
            val = self._humanize(val)

        if isinstance(val, dict):
            table.add_row(key, "+")
            for k, v in val.items():
                table.add_row(f"  - {k}", str(v))
        elif isinstance(val, list):
            table.add_row(key, "+")
            for item in val:
                table.add_row("  -", str(item))
        else:
            table.add_row(key, str(val or ""))

    self.print(table)
print_csv
print_csv(d, order=None)

Prints a dictionary as CSV.

Source code in src/cloudmesh/ai/common/io.py
273
274
275
276
277
278
279
280
def print_csv(self, d: Dict[str, Any], order: Optional[List[str]] = None):
    """Prints a dictionary as CSV."""
    keys = order if order else sorted(d.keys())
    output = []
    output.append(",".join(keys))
    row = [str(d.get(k, "")) for k in keys]
    output.append(",".join(row))
    self.print("\n".join(output))
print_error
print_error(message)

Prints a standardized error message.

Source code in src/cloudmesh/ai/common/io.py
347
348
349
def print_error(self, message: str):
    """Prints a standardized error message."""
    self.print(f"[bold red]Error:[/bold red] {message}")
print_json
print_json(data)

Prints data as formatted JSON.

Source code in src/cloudmesh/ai/common/io.py
265
266
267
def print_json(self, data: Any):
    """Prints data as formatted JSON."""
    self.print(json.dumps(data, indent=4))
print_markdown
print_markdown(text)

Renders and prints markdown text.

Source code in src/cloudmesh/ai/common/io.py
282
283
284
def print_markdown(self, text: str):
    """Renders and prints markdown text."""
    self.print(Markdown(text))
print_status
print_status(message, style='yellow')

Prints a simple status message.

Source code in src/cloudmesh/ai/common/io.py
343
344
345
def print_status(self, message: str, style: str = "yellow"):
    """Prints a simple status message."""
    self.print(f"[{style}] {message}[/{style}]")
print_success
print_success(message)

Prints a standardized success message.

Source code in src/cloudmesh/ai/common/io.py
351
352
353
def print_success(self, message: str):
    """Prints a standardized success message."""
    self.print(f"[bold green]Success:[/bold green] {message}")
print_table
print_table(headers, data, title=None, expand=False)

Prints a formatted table. By default, it is compact (expand=False).

Source code in src/cloudmesh/ai/common/io.py
253
254
255
256
257
258
259
260
261
def print_table(self, headers: list, data: list, title: Optional[str] = None, expand: bool = False):
    """Prints a formatted table. By default, it is compact (expand=False)."""
    styled_title = f"[bold]{title}[/bold]" if title else None
    table = Table(title=styled_title, box=box.ROUNDED, expand=expand, header_style="bold")
    for header in headers:
        table.add_column(header)
    for row in data:
        table.add_row(*[str(item) for item in row])
    self.print(Align.center(table) if expand else Align.left(table))
print_yaml
print_yaml(data)

Prints data as formatted YAML.

Source code in src/cloudmesh/ai/common/io.py
269
270
271
def print_yaml(self, data: Any):
    """Prints data as formatted YAML."""
    self.print(yaml.dump(data, default_flow_style=False))
status
status(message)

Returns a status spinner context manager.

Source code in src/cloudmesh/ai/common/io.py
294
295
296
def status(self, message: str) -> Status:
    """Returns a status spinner context manager."""
    return Status(f"[bold blue]{message}[/bold blue]", console=self)
telemetry_table
telemetry_table(records, title='Telemetry Records')

Displays a standardized telemetry records table.

Source code in src/cloudmesh/ai/common/io.py
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
def telemetry_table(self, records: List[Dict[str, Any]], title: str = "Telemetry Records"):
    """Displays a standardized telemetry records table."""
    if not records:
        self.print("[yellow]No records to display.[/yellow]")
        return

    table = Table(
        title=title,
        box=box.ROUNDED,
        header_style="bold magenta"
    )

    headers = records[0].keys()
    for header in headers:
        table.add_column(header.capitalize(), style="cyan")

    for r in records:
        row = [str(r.get(h, "N/A")) for h in headers]
        table.add_row(*row)

    self.print(table)
top
top(lines)

Moves the cursor up by the specified number of lines.

Source code in src/cloudmesh/ai/common/io.py
298
299
300
301
302
def top(self, lines: int):
    """Moves the cursor up by the specified number of lines."""
    if lines > 0:
        sys.stdout.write(f"\033[{lines}F")
        sys.stdout.flush()
warning
warning(message)

Prints a warning message in yellow.

Source code in src/cloudmesh/ai/common/io.py
110
111
112
def warning(self, message: str):
    """Prints a warning message in yellow."""
    self.print(f"[yellow]WARNING: {message}[/yellow]")
ynchoice
ynchoice(message, default=True)

Asks a yes/no question and returns a boolean.

Source code in src/cloudmesh/ai/common/io.py
191
192
193
194
195
196
197
198
199
200
201
202
def ynchoice(self, message: str, default: bool = True) -> bool:
    """Asks a yes/no question and returns a boolean."""
    suffix = " [Y/n]" if default else " [y/N]"
    while True:
        response = input(f"{message}{suffix} ").strip().lower()
        if not response:
            return default
        if response in ("y", "yes"):
            return True
        if response in ("n", "no"):
            return False
        self.print("[red]Please enter 'y' or 'n'.[/red]")
Editor

Bases: BaseIO

Utility to open files in the default system editor.

Source code in src/cloudmesh/ai/common/io.py
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
class Editor(BaseIO):
    """Utility to open files in the default system editor."""
    def edit(self, path: str):
        expanded_path = self.expand_path(path)
        editor = os.environ.get("EDITOR")
        if os.name == 'posix':
            import subprocess
            try:
                if sys.platform == 'darwin':
                    cmd = ['open', '-a', editor, expanded_path] if editor and '/' not in editor else \
                           [editor, expanded_path] if editor else ['open', expanded_path]
                    subprocess.run(cmd, check=True)
                else:
                    cmd = [editor, expanded_path] if editor else ['xdg-open', expanded_path]
                    subprocess.run(cmd, check=True)
            except Exception as e:
                logger.error(f"Failed to open editor: {e}")
        elif os.name == 'nt':
            try:
                if editor:
                    import subprocess
                    subprocess.run([editor, expanded_path], check=True)
                else:
                    os.startfile(expanded_path)
            except Exception as e:
                logger.error(f"Failed to open editor: {e}")

Functions

appendfile
appendfile(path, content)

Standalone wrapper for console.appendfile.

Source code in src/cloudmesh/ai/common/io.py
417
418
419
def appendfile(path: str, content: str) -> None:
    """Standalone wrapper for console.appendfile."""
    console.appendfile(path, content)
async_readfile async
async_readfile(path)

Asynchronously reads the content of a file.

Source code in src/cloudmesh/ai/common/io.py
448
449
450
451
452
async def async_readfile(path: str) -> str:
    """Asynchronously reads the content of a file."""
    import aiofiles
    async with aiofiles.open(path, mode='r', encoding='utf-8') as f:
        return await f.read()
async_writefile async
async_writefile(path, content)

Asynchronously writes content to a file.

Source code in src/cloudmesh/ai/common/io.py
454
455
456
457
458
459
460
461
async def async_writefile(path: str, content: str) -> None:
    """Asynchronously writes content to a file."""
    import aiofiles
    location = console.expand_path(path)
    path_obj = Path(location)
    path_obj.parent.mkdir(parents=True, exist_ok=True)
    async with aiofiles.open(path_obj, mode='w', encoding='utf-8') as f:
        await f.write(content)
banner
banner(label=None, txt=None, c='-', prefix='#', debug=True, color='blue', padding=False, figlet=False, font='big')

Standalone wrapper for console.banner.

Source code in src/cloudmesh/ai/common/io.py
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
def banner(
    label: Optional[str] = None,
    txt: Optional[str] = None,
    c: str = "-",
    prefix: str = "#",
    debug: bool = True,
    color: str = "blue",
    padding: bool = False,
    figlet: bool = False,
    font: str = "big",
):
    """Standalone wrapper for console.banner."""
    console.banner(
        label=label,
        txt=txt,
        c=c,
        prefix=prefix,
        debug=debug,
        color=color,
        padding=padding,
        figlet=figlet,
        font=font,
    )
create_benchmark_file
create_benchmark_file(path, n)

Creates a file of a given size in binary megabytes.

Source code in src/cloudmesh/ai/common/io.py
440
441
442
443
444
445
446
def create_benchmark_file(path: str, n: int) -> int:
    """Creates a file of a given size in binary megabytes."""
    location = console.expand_path(path)
    size = 1048576 * n
    with open(location, "wb") as f:
        f.write(os.urandom(size))
    return int(os.path.getsize(location) / 1048576.0)
create_benchmark_yaml
create_benchmark_yaml(path, n)

Creates a Cloudmesh service YAML test file.

Source code in src/cloudmesh/ai/common/io.py
433
434
435
436
437
438
def create_benchmark_yaml(path: str, n: int) -> None:
    """Creates a Cloudmesh service YAML test file."""
    cm = {"cloudmesh": {}}
    for i in range(0, n):
        cm["cloudmesh"][f"service{i}"] = {"attribute": f"service{i}"}
    console.dump_yaml(path, cm)
dump_yaml
dump_yaml(path, data)

Standalone wrapper for console.dump_yaml.

Source code in src/cloudmesh/ai/common/io.py
429
430
431
def dump_yaml(path: Union[str, Path], data: Dict[str, Any]) -> None:
    """Standalone wrapper for console.dump_yaml."""
    console.dump_yaml(path, data)
load_yaml
load_yaml(path)

Standalone wrapper for console.load_yaml.

Source code in src/cloudmesh/ai/common/io.py
425
426
427
def load_yaml(path: Union[str, Path]) -> Optional[Dict[str, Any]]:
    """Standalone wrapper for console.load_yaml."""
    return console.load_yaml(path)
path_expand
path_expand(text, slashreplace=True)

Standalone wrapper for console.expand_path.

Source code in src/cloudmesh/ai/common/io.py
421
422
423
def path_expand(text: str, slashreplace: bool = True) -> str:
    """Standalone wrapper for console.expand_path."""
    return console.expand_path(text, slashreplace)
readfile
readfile(path)

Standalone wrapper for console.readfile.

Source code in src/cloudmesh/ai/common/io.py
409
410
411
def readfile(path: str) -> str:
    """Standalone wrapper for console.readfile."""
    return console.readfile(path)
writefile
writefile(path, content)

Standalone wrapper for console.writefile.

Source code in src/cloudmesh/ai/common/io.py
413
414
415
def writefile(path: str, content: str) -> None:
    """Standalone wrapper for console.writefile."""
    console.writefile(path, content)

logging

Logging utility for cloudmesh-ai components. Provides centralized management of log directories, file naming, and logger configuration. Includes support for JSON logging, log rotation, and request tracing.

Classes

ContextFilter

Bases: Filter

Filter that injects the current context_id into the log record.

Source code in src/cloudmesh/ai/common/logging.py
36
37
38
39
40
41
42
43
44
45
46
47
48
class ContextFilter(logging.Filter):
    """Filter that injects the current context_id into the log record."""
    def filter(self, record: logging.LogRecord) -> bool:
        """Injects the current context_id into the log record.

        Args:
            record: The log record to be filtered.

        Returns:
            True to indicate the record should be logged.
        """
        record.context_id = get_context_id() or "system"
        return True
Functions
filter
filter(record)

Injects the current context_id into the log record.

Parameters:

Name Type Description Default
record LogRecord

The log record to be filtered.

required

Returns:

Type Description
bool

True to indicate the record should be logged.

Source code in src/cloudmesh/ai/common/logging.py
38
39
40
41
42
43
44
45
46
47
48
def filter(self, record: logging.LogRecord) -> bool:
    """Injects the current context_id into the log record.

    Args:
        record: The log record to be filtered.

    Returns:
        True to indicate the record should be logged.
    """
    record.context_id = get_context_id() or "system"
    return True
JsonFormatter

Bases: Formatter

Formatter that outputs log records in JSON format.

Source code in src/cloudmesh/ai/common/logging.py
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
class JsonFormatter(logging.Formatter):
    """Formatter that outputs log records in JSON format."""
    def format(self, record: logging.LogRecord) -> str:
        """Formats the log record as a JSON string.

        Args:
            record: The log record to format.

        Returns:
            A JSON string representation of the log record.
        """
        log_record = {
            "timestamp": self.formatTime(record, self.datefmt),
            "level": record.levelname,
            "logger": record.name,
            "message": record.getMessage(),
            "context_id": getattr(record, "context_id", "system"),
        }
        if record.exc_info:
            log_record["exception"] = self.formatException(record.exc_info)
        return json.dumps(log_record)
Functions
format
format(record)

Formats the log record as a JSON string.

Parameters:

Name Type Description Default
record LogRecord

The log record to format.

required

Returns:

Type Description
str

A JSON string representation of the log record.

Source code in src/cloudmesh/ai/common/logging.py
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
def format(self, record: logging.LogRecord) -> str:
    """Formats the log record as a JSON string.

    Args:
        record: The log record to format.

    Returns:
        A JSON string representation of the log record.
    """
    log_record = {
        "timestamp": self.formatTime(record, self.datefmt),
        "level": record.levelname,
        "logger": record.name,
        "message": record.getMessage(),
        "context_id": getattr(record, "context_id", "system"),
    }
    if record.exc_info:
        log_record["exception"] = self.formatException(record.exc_info)
    return json.dumps(log_record)

Functions

ensure_log_dir
ensure_log_dir()

Ensures the log directory exists and returns it.

Returns:

Type Description
Path

The Path object pointing to the ensured logs directory.

Source code in src/cloudmesh/ai/common/logging.py
109
110
111
112
113
114
115
116
117
def ensure_log_dir() -> Path:
    """Ensures the log directory exists and returns it.

    Returns:
        The Path object pointing to the ensured logs directory.
    """
    log_dir = get_log_dir()
    log_dir.mkdir(parents=True, exist_ok=True)
    return log_dir
get_context_id
get_context_id()

Retrieves the context ID for the current thread.

Returns:

Type Description
Optional[str]

The current context ID if set, otherwise None.

Source code in src/cloudmesh/ai/common/logging.py
28
29
30
31
32
33
34
def get_context_id() -> Optional[str]:
    """Retrieves the context ID for the current thread.

    Returns:
        The current context ID if set, otherwise None.
    """
    return getattr(_thread_local, "context_id", None)
get_log_dir
get_log_dir()

Returns the expanded path to the AI logs directory, using config if available.

Returns:

Type Description
Path

The Path object pointing to the logs directory.

Source code in src/cloudmesh/ai/common/logging.py
100
101
102
103
104
105
106
107
def get_log_dir() -> Path:
    """Returns the expanded path to the AI logs directory, using config if available.

    Returns:
        The Path object pointing to the logs directory.
    """
    log_dir = _logging_config.get("log_dir", "~/.config/cloudmesh/ai/logs")
    return Path(log_dir).expanduser()
get_log_file_path
get_log_file_path(script_name)

Generates a timestamped log file path for a given script.

Example: ~/.config/cloudmesh/ai/logs/test_20260412_124500.log

Parameters:

Name Type Description Default
script_name str

The name of the script for which to generate the log path.

required

Returns:

Type Description
Path

The Path object pointing to the generated log file.

Source code in src/cloudmesh/ai/common/logging.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
def get_log_file_path(script_name: str) -> Path:
    """Generates a timestamped log file path for a given script.

    Example: ~/.config/cloudmesh/ai/logs/test_20260412_124500.log

    Args:
        script_name: The name of the script for which to generate the log path.

    Returns:
        The Path object pointing to the generated log file.
    """
    log_dir = ensure_log_dir()

    # Use custom filename if provided in config, otherwise use script_name
    prefix = _logging_config.get("log_prefix", script_name)
    timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
    return log_dir / f"{prefix}_{timestamp}.log"
get_logger
get_logger(script_name, level=None, json_format=None, max_bytes=None, backup_count=None)

Returns a configured logger instance for the given script.

Configures both a rotating file handler and a stream handler.

Parameters:

Name Type Description Default
script_name str

Name of the logger/script.

required
level Optional[int]

Logging level.

None
json_format Optional[bool]

If True, uses JSON formatting for logs.

None
max_bytes Optional[int]

Max size of a log file before rotation.

None
backup_count Optional[int]

Number of backup log files to keep.

None

Returns:

Type Description
Logger

A configured logging.Logger instance.

Source code in src/cloudmesh/ai/common/logging.py
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
def get_logger(
    script_name: str, 
    level: Optional[int] = None, 
    json_format: Optional[bool] = None,
    max_bytes: Optional[int] = None,
    backup_count: Optional[int] = None
) -> logging.Logger:
    """Returns a configured logger instance for the given script.

    Configures both a rotating file handler and a stream handler.

    Args:
        script_name: Name of the logger/script.
        level: Logging level.
        json_format: If True, uses JSON formatting for logs.
        max_bytes: Max size of a log file before rotation.
        backup_count: Number of backup log files to keep.

    Returns:
        A configured logging.Logger instance.
    """
    if script_name in _loggers:
        return _loggers[script_name]

    # Resolve settings: Argument > Config File > Default
    final_level = level if level is not None else _logging_config.get("level", logging.INFO)
    if isinstance(final_level, str):
        final_level = getattr(logging, final_level.upper(), logging.INFO)

    final_json = json_format if json_format is not None else _logging_config.get("json_format", False)
    final_max_bytes = max_bytes if max_bytes is not None else _logging_config.get("max_bytes", 10 * 1024 * 1024)
    final_backup_count = backup_count if backup_count is not None else _logging_config.get("backup_count", 5)

    logger = logging.getLogger(script_name)
    logger.setLevel(final_level)

    # Prevent adding handlers if they already exist
    if not logger.handlers:
        # Add Context Filter for request tracing
        logger.addFilter(ContextFilter())

        if final_json:
            formatter = JsonFormatter()
        else:
            formatter = logging.Formatter('%(asctime)s - %(name)s - [%(context_id)s] - %(levelname)s - %(message)s')

        # 1. Rotating File Handler
        log_file = get_log_file_path(script_name)
        try:
            fh = logging.handlers.RotatingFileHandler(
                log_file, maxBytes=final_max_bytes, backupCount=final_backup_count
            )
            fh.setFormatter(formatter)
            logger.addHandler(fh)
        except Exception as e:
            print(f"Failed to initialize rotating file logger at {log_file}: {e}")

        # 2. Stream Handler (Console)
        sh = logging.StreamHandler()
        sh.setFormatter(formatter)
        logger.addHandler(sh)

    _loggers[script_name] = logger
    return logger
load_logging_config
load_logging_config(config_path)

Loads logging configuration from a JSON file.

Example config: {"log_dir": "/var/log/cloudmesh", "level": "DEBUG", "json_format": true}

Parameters:

Name Type Description Default
config_path Union[str, Path]

Path to the JSON configuration file.

required
Source code in src/cloudmesh/ai/common/logging.py
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
def load_logging_config(config_path: Union[str, Path]) -> None:
    """Loads logging configuration from a JSON file.

    Example config: {"log_dir": "/var/log/cloudmesh", "level": "DEBUG", "json_format": true}

    Args:
        config_path: Path to the JSON configuration file.

    """
    global _logging_config
    try:
        path = Path(config_path).expanduser()
        if path.exists():
            with open(path, "r") as f:
                _logging_config = json.load(f)
    except Exception as e:
        # Use print here because logger might not be configured yet
        print(f"Failed to load logging config from {config_path}: {e}")
progress
progress(filename=None, status='ready', progress=0, pid=None, timestamp=False, stdout=True, stderr=True, append=None, **kwargs)

Creates a printed line of the form "# cloudmesh status=ready progress=0 pid=$$ time='2022-08-05 16:29:40.228901'".

Parameters:

Name Type Description Default
filename Optional[str]

Optional file to append the progress line to.

None
status str

The current status string.

'ready'
progress Union[int, str, float]

The current progress value (int, float, or string).

0
pid Optional[Union[int, str]]

Process ID. If None, it will be automatically detected.

None
timestamp bool

If True, includes a timestamp in the output.

False
stdout bool

If True, prints to stdout.

True
stderr bool

If True, prints to stderr.

True
append Optional[str]

Optional string to append to the end of the line.

None
**kwargs

Additional key-value pairs to include in the progress line.

{}

Returns:

Type Description
str

The formatted progress string.

Source code in src/cloudmesh/ai/common/logging.py
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
def progress(
    filename: Optional[str] = None,
    status: str = "ready",
    progress: Union[int, str, float] = 0,
    pid: Optional[Union[int, str]] = None,
    timestamp: bool = False,
    stdout: bool = True,
    stderr: bool = True,
    append: Optional[str] = None,
    **kwargs,
) -> str:
    """Creates a printed line of the form
    "# cloudmesh status=ready progress=0 pid=$$ time='2022-08-05 16:29:40.228901'".

    Args:
        filename: Optional file to append the progress line to.
        status: The current status string.
        progress: The current progress value (int, float, or string).
        pid: Process ID. If None, it will be automatically detected.
        timestamp: If True, includes a timestamp in the output.
        stdout: If True, prints to stdout.
        stderr: If True, prints to stderr.
        append: Optional string to append to the end of the line.
        **kwargs: Additional key-value pairs to include in the progress line.

    Returns:
        The formatted progress string.
    """
    if isinstance(progress, (int, float)):
        progress = str(progress)

    if pid is None:
        if "SLURM_JOB_ID" in os.environ:
            pid = os.environ["SLURM_JOB_ID"]
        elif "LSB_JOBID" in os.environ:
            pid = os.environ["LSB_JOBID"]
        else:
            pid = os.getpid()

    variables = ""
    msg = f"# cloudmesh status={status} progress={progress} pid={pid}"

    if timestamp:
        t = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')
        msg = msg + f" time='{t}'"

    if kwargs:
        for name, value in kwargs.items():
            variables = variables + f" {name}={value}"
        msg = msg + variables

    if append is not None:
        msg = msg + " " + append

    if stdout:
        print(msg, file=sys.stdout)
    if stderr:
        print(msg, file=sys.stderr)
    if filename is not None:
        with open(filename, "a") as f:
            f.write(msg + "\n")

    return msg
set_context_id
set_context_id(context_id)

Sets the context ID for the current thread to enable request tracing.

Parameters:

Name Type Description Default
context_id str

The unique identifier for the current request or context.

required
Source code in src/cloudmesh/ai/common/logging.py
20
21
22
23
24
25
26
def set_context_id(context_id: str):
    """Sets the context ID for the current thread to enable request tracing.

    Args:
        context_id: The unique identifier for the current request or context.
    """
    _thread_local.context_id = context_id

logging_utils

Classes

ContextualLogger

Bases: LoggerAdapter

Logger adapter that adds contextual information to log messages.

This is particularly useful for parallel operations where you need to distinguish logs by host, user, or operation ID.

Source code in src/cloudmesh/ai/common/logging_utils.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class ContextualLogger(logging.LoggerAdapter):
    """Logger adapter that adds contextual information to log messages.

    This is particularly useful for parallel operations where you need to 
    distinguish logs by host, user, or operation ID.
    """

    def process(self, msg: Any, kwargs: Optional[Dict[str, Any]] = None) -> tuple:
        """Inject context into the log message.

        Args:
            msg: The log message.
            kwargs: The log arguments.

        Returns:
            tuple: (msg, kwargs)
        """
        if kwargs is None:
            kwargs = {}

        context = self.extra
        if context:
            # Format context as [key=value] pairs
            ctx_str = " ".join([f"[{k}={v}]" for k, v in context.items()])
            msg = f"{ctx_str} {msg}"

        return msg, kwargs
Functions
process
process(msg, kwargs=None)

Inject context into the log message.

Parameters:

Name Type Description Default
msg Any

The log message.

required
kwargs Optional[Dict[str, Any]]

The log arguments.

None

Returns:

Name Type Description
tuple tuple

(msg, kwargs)

Source code in src/cloudmesh/ai/common/logging_utils.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def process(self, msg: Any, kwargs: Optional[Dict[str, Any]] = None) -> tuple:
    """Inject context into the log message.

    Args:
        msg: The log message.
        kwargs: The log arguments.

    Returns:
        tuple: (msg, kwargs)
    """
    if kwargs is None:
        kwargs = {}

    context = self.extra
    if context:
        # Format context as [key=value] pairs
        ctx_str = " ".join([f"[{k}={v}]" for k, v in context.items()])
        msg = f"{ctx_str} {msg}"

    return msg, kwargs

Functions

get_contextual_logger
get_contextual_logger(name, initial_context=None)

Factory function to create a ContextualLogger.

Parameters:

Name Type Description Default
name str

The name of the logger.

required
initial_context Optional[Dict[str, Any]]

Initial context to associate with the logger.

None

Returns:

Name Type Description
ContextualLogger ContextualLogger

A logger adapter with the specified context.

Source code in src/cloudmesh/ai/common/logging_utils.py
40
41
42
43
44
45
46
47
48
49
50
51
def get_contextual_logger(name: str, initial_context: Optional[Dict[str, Any]] = None) -> ContextualLogger:
    """Factory function to create a ContextualLogger.

    Args:
        name: The name of the logger.
        initial_context: Initial context to associate with the logger.

    Returns:
        ContextualLogger: A logger adapter with the specified context.
    """
    logger = logging.getLogger(name)
    return ContextualLogger(logger, initial_context or {})

remote

Remote execution utilities for cloudmesh-ai. Provides a unified interface for SSH command execution and file transfers.

Classes

RemoteExecutor

A unified executor for remote operations via SSH. Supports command execution, file uploads, and direct remote file writing.

Source code in src/cloudmesh/ai/common/remote.py
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
class RemoteExecutor:
    """
    A unified executor for remote operations via SSH.
    Supports command execution, file uploads, and direct remote file writing.
    """

    def __init__(self, host: str, username: Optional[str] = None, key_filename: Optional[str] = None):
        """Initialize the RemoteExecutor.

        Args:
            host: The hostname or IP address of the remote host.
            username: The SSH username. Defaults to None.
            key_filename: Path to the private key file. Defaults to None.
        """
        self.host = host
        self.username = username
        self.key_filename = key_filename
        self.client: Optional[paramiko.SSHClient] = None

    def __enter__(self):
        """Establishes the SSH connection.

        Returns:
            The RemoteExecutor instance.

        Raises:
            paramiko.SSHException: If the connection fails.
        """
        try:
            self.client = paramiko.SSHClient()
            self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            self.client.connect(
                hostname=self.host, 
                username=self.username, 
                key_filename=self.key_filename
            )
            return self
        except Exception as e:
            console.error(f"Failed to connect to {self.host}: {e}")
            raise

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Closes the SSH connection.

        Args:
            exc_type: The type of the exception that occurred.
            exc_val: The instance of the exception that occurred.
            exc_tb: The traceback of the exception that occurred.
        """
        if self.client:
            self.client.close()

    def execute(self, command: str, timeout: int = 60) -> Tuple[int, str, str]:
        """
        Executes a command on the remote host.

        Args:
            command: The shell command to execute.
            timeout: Command timeout in seconds.

        Returns:
            A tuple of (exit_status, stdout, stderr).
        """
        if not self.client:
            raise RuntimeError("RemoteExecutor must be used as a context manager.")

        try:
            stdin, stdout, stderr = self.client.exec_command(command, timeout=timeout)
            exit_status = stdout.channel.recv_exit_status()
            return exit_status, stdout.read().decode('utf-8'), stderr.read().decode('utf-8')
        except Exception as e:
            console.error(f"Execution failed on {self.host}: {e}")
            raise

    def upload(self, local_path: str, remote_path: str):
        """Uploads a local file to the remote host using SFTP.

        Args:
            local_path: Path to the local file to upload.
            remote_path: Path on the remote host where the file should be saved.

        Raises:
            RuntimeError: If the executor is not used as a context manager.
            IOError: If the upload fails.
        """
        if not self.client:
            raise RuntimeError("RemoteExecutor must be used as a context manager.")

        try:
            sftp = self.client.open_sftp()
            sftp.put(local_path, remote_path)
            sftp.close()
        except Exception as e:
            console.error(f"Upload failed to {self.host}:{remote_path}: {e}")
            raise

    def download(self, remote_path: str, local_path: str):
        """Downloads a remote file to the local host using SFTP.

        Args:
            remote_path: Path to the file on the remote host.
            local_path: Path on the local host where the file should be saved.

        Raises:
            RuntimeError: If the executor is not used as a context manager.
            IOError: If the download fails.
        """
        if not self.client:
            raise RuntimeError("RemoteExecutor must be used as a context manager.")

        try:
            sftp = self.client.open_sftp()
            sftp.get(remote_path, local_path)
            sftp.close()
        except Exception as e:
            console.error(f"Download failed from {self.host}:{remote_path}: {e}")
            raise

    def write_remote_file(self, content: str, remote_path: str):
        """Writes a string directly to a remote file.

        Useful for creating scripts or config files on the fly.

        Args:
            content: The string content to write.
            remote_path: Path on the remote host where the file should be created.

        Raises:
            RuntimeError: If the executor is not used as a context manager.
            IOError: If the write operation fails.
        """
        if not self.client:
            raise RuntimeError("RemoteExecutor must be used as a context manager.")

        try:
            sftp = self.client.open_sftp()
            with sftp.file(remote_path, 'w') as f:
                f.write(content)
            sftp.close()
        except Exception as e:
            console.error(f"Failed to write remote file {self.host}:{remote_path}: {e}")
            raise
Functions
__enter__
__enter__()

Establishes the SSH connection.

Returns:

Type Description

The RemoteExecutor instance.

Raises:

Type Description
SSHException

If the connection fails.

Source code in src/cloudmesh/ai/common/remote.py
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
def __enter__(self):
    """Establishes the SSH connection.

    Returns:
        The RemoteExecutor instance.

    Raises:
        paramiko.SSHException: If the connection fails.
    """
    try:
        self.client = paramiko.SSHClient()
        self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        self.client.connect(
            hostname=self.host, 
            username=self.username, 
            key_filename=self.key_filename
        )
        return self
    except Exception as e:
        console.error(f"Failed to connect to {self.host}: {e}")
        raise
__exit__
__exit__(exc_type, exc_val, exc_tb)

Closes the SSH connection.

Parameters:

Name Type Description Default
exc_type

The type of the exception that occurred.

required
exc_val

The instance of the exception that occurred.

required
exc_tb

The traceback of the exception that occurred.

required
Source code in src/cloudmesh/ai/common/remote.py
51
52
53
54
55
56
57
58
59
60
def __exit__(self, exc_type, exc_val, exc_tb):
    """Closes the SSH connection.

    Args:
        exc_type: The type of the exception that occurred.
        exc_val: The instance of the exception that occurred.
        exc_tb: The traceback of the exception that occurred.
    """
    if self.client:
        self.client.close()
__init__
__init__(host, username=None, key_filename=None)

Initialize the RemoteExecutor.

Parameters:

Name Type Description Default
host str

The hostname or IP address of the remote host.

required
username Optional[str]

The SSH username. Defaults to None.

None
key_filename Optional[str]

Path to the private key file. Defaults to None.

None
Source code in src/cloudmesh/ai/common/remote.py
16
17
18
19
20
21
22
23
24
25
26
27
def __init__(self, host: str, username: Optional[str] = None, key_filename: Optional[str] = None):
    """Initialize the RemoteExecutor.

    Args:
        host: The hostname or IP address of the remote host.
        username: The SSH username. Defaults to None.
        key_filename: Path to the private key file. Defaults to None.
    """
    self.host = host
    self.username = username
    self.key_filename = key_filename
    self.client: Optional[paramiko.SSHClient] = None
download
download(remote_path, local_path)

Downloads a remote file to the local host using SFTP.

Parameters:

Name Type Description Default
remote_path str

Path to the file on the remote host.

required
local_path str

Path on the local host where the file should be saved.

required

Raises:

Type Description
RuntimeError

If the executor is not used as a context manager.

IOError

If the download fails.

Source code in src/cloudmesh/ai/common/remote.py
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
def download(self, remote_path: str, local_path: str):
    """Downloads a remote file to the local host using SFTP.

    Args:
        remote_path: Path to the file on the remote host.
        local_path: Path on the local host where the file should be saved.

    Raises:
        RuntimeError: If the executor is not used as a context manager.
        IOError: If the download fails.
    """
    if not self.client:
        raise RuntimeError("RemoteExecutor must be used as a context manager.")

    try:
        sftp = self.client.open_sftp()
        sftp.get(remote_path, local_path)
        sftp.close()
    except Exception as e:
        console.error(f"Download failed from {self.host}:{remote_path}: {e}")
        raise
execute
execute(command, timeout=60)

Executes a command on the remote host.

Parameters:

Name Type Description Default
command str

The shell command to execute.

required
timeout int

Command timeout in seconds.

60

Returns:

Type Description
Tuple[int, str, str]

A tuple of (exit_status, stdout, stderr).

Source code in src/cloudmesh/ai/common/remote.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
def execute(self, command: str, timeout: int = 60) -> Tuple[int, str, str]:
    """
    Executes a command on the remote host.

    Args:
        command: The shell command to execute.
        timeout: Command timeout in seconds.

    Returns:
        A tuple of (exit_status, stdout, stderr).
    """
    if not self.client:
        raise RuntimeError("RemoteExecutor must be used as a context manager.")

    try:
        stdin, stdout, stderr = self.client.exec_command(command, timeout=timeout)
        exit_status = stdout.channel.recv_exit_status()
        return exit_status, stdout.read().decode('utf-8'), stderr.read().decode('utf-8')
    except Exception as e:
        console.error(f"Execution failed on {self.host}: {e}")
        raise
upload
upload(local_path, remote_path)

Uploads a local file to the remote host using SFTP.

Parameters:

Name Type Description Default
local_path str

Path to the local file to upload.

required
remote_path str

Path on the remote host where the file should be saved.

required

Raises:

Type Description
RuntimeError

If the executor is not used as a context manager.

IOError

If the upload fails.

Source code in src/cloudmesh/ai/common/remote.py
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
def upload(self, local_path: str, remote_path: str):
    """Uploads a local file to the remote host using SFTP.

    Args:
        local_path: Path to the local file to upload.
        remote_path: Path on the remote host where the file should be saved.

    Raises:
        RuntimeError: If the executor is not used as a context manager.
        IOError: If the upload fails.
    """
    if not self.client:
        raise RuntimeError("RemoteExecutor must be used as a context manager.")

    try:
        sftp = self.client.open_sftp()
        sftp.put(local_path, remote_path)
        sftp.close()
    except Exception as e:
        console.error(f"Upload failed to {self.host}:{remote_path}: {e}")
        raise
write_remote_file
write_remote_file(content, remote_path)

Writes a string directly to a remote file.

Useful for creating scripts or config files on the fly.

Parameters:

Name Type Description Default
content str

The string content to write.

required
remote_path str

Path on the remote host where the file should be created.

required

Raises:

Type Description
RuntimeError

If the executor is not used as a context manager.

IOError

If the write operation fails.

Source code in src/cloudmesh/ai/common/remote.py
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
def write_remote_file(self, content: str, remote_path: str):
    """Writes a string directly to a remote file.

    Useful for creating scripts or config files on the fly.

    Args:
        content: The string content to write.
        remote_path: Path on the remote host where the file should be created.

    Raises:
        RuntimeError: If the executor is not used as a context manager.
        IOError: If the write operation fails.
    """
    if not self.client:
        raise RuntimeError("RemoteExecutor must be used as a context manager.")

    try:
        sftp = self.client.open_sftp()
        with sftp.file(remote_path, 'w') as f:
            f.write(content)
        sftp.close()
    except Exception as e:
        console.error(f"Failed to write remote file {self.host}:{remote_path}: {e}")
        raise

security

Classes

BaseSecurity

Base class for security and privilege escalation utilities.

Source code in src/cloudmesh/ai/common/security.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
class BaseSecurity:
    """Base class for security and privilege escalation utilities."""

    def __init__(self, debug: bool = False):
        self.debug = debug

    def is_root(self) -> bool:
        """Check if the current process is running as root."""
        return os.geteuid() == 0

    def sudo_execute_local(self, command: Union[str, List[str]], input_data: Optional[str] = None) -> str:
        """Execute a command locally with sudo.

        Args:
            command: The command to execute.
            input_data: Optional data to pass to stdin (e.g., password).

        Returns:
            str: The output of the command.

        Raises:
            SecurityAuthError: If sudo authentication fails.
        """
        if isinstance(command, str):
            cmd = ["sudo", "-S"] + command.split()
        else:
            cmd = ["sudo", "-S"] + command

        if self.debug:
            logger.debug(f"Executing local sudo command: {' '.join(cmd)}")

        try:
            result = subprocess.run(
                cmd,
                input=input_data,
                capture_output=True,
                text=True,
                check=True
            )
            return result.stdout
        except subprocess.CalledProcessError as e:
            if "incorrect password" in e.stderr.lower() or "sudo: a password is required" in e.stderr.lower():
                raise SecurityAuthError(f"Sudo authentication failed: {e.stderr}")
            logger.error(f"Sudo command failed: {e.stderr}")
            raise SecurityError(f"Sudo execution failed: {e.stderr}")

    def verify_file_permissions(self, path: Union[str, Path], readable: bool = True, writable: bool = False) -> bool:
        """Verify if the current user has the required permissions for a file."""
        path_obj = Path(path)
        if not path_obj.exists():
            return False

        try:
            if readable and not os.access(path_obj, os.R_OK):
                return False
            if writable and not os.access(path_obj, os.W_OK):
                return False
            return True
        except Exception as e:
            logger.error(f"Error verifying permissions for {path}: {e}")
            return False

    def secure_write(self, path: Union[str, Path], content: str, mode: int = 0o600):
        """Write content to a file with restricted permissions (e.g., for private keys)."""
        path_obj = Path(path)
        try:
            # Create file with restricted permissions from the start
            with os.fdopen(os.open(path_obj, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, mode), 'w') as f:
                f.write(content)
        except Exception as e:
            logger.error(f"Secure write failed for {path}: {e}")
            raise SecurityError(f"Could not write secure file {path}: {e}")
Functions
is_root
is_root()

Check if the current process is running as root.

Source code in src/cloudmesh/ai/common/security.py
25
26
27
def is_root(self) -> bool:
    """Check if the current process is running as root."""
    return os.geteuid() == 0
secure_write
secure_write(path, content, mode=384)

Write content to a file with restricted permissions (e.g., for private keys).

Source code in src/cloudmesh/ai/common/security.py
81
82
83
84
85
86
87
88
89
90
def secure_write(self, path: Union[str, Path], content: str, mode: int = 0o600):
    """Write content to a file with restricted permissions (e.g., for private keys)."""
    path_obj = Path(path)
    try:
        # Create file with restricted permissions from the start
        with os.fdopen(os.open(path_obj, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, mode), 'w') as f:
            f.write(content)
    except Exception as e:
        logger.error(f"Secure write failed for {path}: {e}")
        raise SecurityError(f"Could not write secure file {path}: {e}")
sudo_execute_local
sudo_execute_local(command, input_data=None)

Execute a command locally with sudo.

Parameters:

Name Type Description Default
command Union[str, List[str]]

The command to execute.

required
input_data Optional[str]

Optional data to pass to stdin (e.g., password).

None

Returns:

Name Type Description
str str

The output of the command.

Raises:

Type Description
SecurityAuthError

If sudo authentication fails.

Source code in src/cloudmesh/ai/common/security.py
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
def sudo_execute_local(self, command: Union[str, List[str]], input_data: Optional[str] = None) -> str:
    """Execute a command locally with sudo.

    Args:
        command: The command to execute.
        input_data: Optional data to pass to stdin (e.g., password).

    Returns:
        str: The output of the command.

    Raises:
        SecurityAuthError: If sudo authentication fails.
    """
    if isinstance(command, str):
        cmd = ["sudo", "-S"] + command.split()
    else:
        cmd = ["sudo", "-S"] + command

    if self.debug:
        logger.debug(f"Executing local sudo command: {' '.join(cmd)}")

    try:
        result = subprocess.run(
            cmd,
            input=input_data,
            capture_output=True,
            text=True,
            check=True
        )
        return result.stdout
    except subprocess.CalledProcessError as e:
        if "incorrect password" in e.stderr.lower() or "sudo: a password is required" in e.stderr.lower():
            raise SecurityAuthError(f"Sudo authentication failed: {e.stderr}")
        logger.error(f"Sudo command failed: {e.stderr}")
        raise SecurityError(f"Sudo execution failed: {e.stderr}")
verify_file_permissions
verify_file_permissions(path, readable=True, writable=False)

Verify if the current user has the required permissions for a file.

Source code in src/cloudmesh/ai/common/security.py
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def verify_file_permissions(self, path: Union[str, Path], readable: bool = True, writable: bool = False) -> bool:
    """Verify if the current user has the required permissions for a file."""
    path_obj = Path(path)
    if not path_obj.exists():
        return False

    try:
        if readable and not os.access(path_obj, os.R_OK):
            return False
        if writable and not os.access(path_obj, os.W_OK):
            return False
        return True
    except Exception as e:
        logger.error(f"Error verifying permissions for {path}: {e}")
        return False

Functions

ssh

Classes

SSHConfig

Bases: SSHBase

Managing the SSH config file (usually ~/.ssh/config).

Source code in src/cloudmesh/ai/common/ssh/ssh_config.py
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
class SSHConfig(SSHBase):
    """Managing the SSH config file (usually ~/.ssh/config)."""

    def __init__(self, filename: Optional[Union[str, Path]] = None, debug: bool = False):
        super().__init__(debug=debug)
        if filename is not None:
            self.filename = self.resolve_path(str(filename))
        else:
            self.filename = self.resolve_path("~/.ssh/config")

        self.conf: Optional[SshConf] = None
        self.load()

    def names(self) -> List[str]:
        """The names defined in the SSH config.

        Returns:
            List[str]: the host names.
        """
        return self.list()

    def load(self):
        """Parse the SSH config file using sshconf."""
        try:
            self.conf = SshConf(str(self.filename))
        except Exception as e:
            logger.error(f"Could not load ssh config file {self.filename}: {e}")
            self.conf = None

    def list(self) -> List[str]:
        """List the hosts defined in the config file.

        Returns:
            List[str]: list of host names.
        """
        if not self.conf:
            return []
        return self.conf.hosts()

    def __str__(self) -> str:
        """The string representation of the config as JSON."""
        if not self.conf:
            return "{}"

        # Convert sshconf to a dictionary for JSON representation
        hosts_dict = {}
        for host in self.conf.hosts():
            hosts_dict[host] = self.conf.get_all(host)
        return json.dumps(hosts_dict, indent=4)

    def login(self, name: str):
        """Login to the host defined in .ssh/config by name.

        Args:
            name: the name of the host as defined in the config file.
        """
        logger.info(f"Logging into host: {name}")
        try:
            self._execute(["ssh", name], capture_output=False)
        except Exception as e:
            logger.error(f"Failed to login to {name}: {e}")

    def execute(self, name: str, command: str, use_pty: bool = False) -> Union[CommandResult, str]:
        """Execute the command on the named host.

        Args:
            name: the name of the host in config.
            command: the command to be executed.
            use_pty: whether to allocate a pseudo-terminal.

        Returns:
            Union[CommandResult, str]: CommandResult for remote, stdout for local.
        """
        if name == "localhost":
            # Execute locally
            result = self._execute(["sh", "-c", command])
            return result.stdout if result.stdout else result.stderr

        # Execute via Fabric
        user = self.username(name)
        return self._run_remote(name, command, user=user, use_pty=use_pty)

    def sudo_execute(self, name: str, command: str, use_pty: bool = False) -> CommandResult:
        """Execute the command on the named host with sudo.

        Args:
            name: the name of the host in config.
            command: the command to be executed.
            use_pty: whether to allocate a pseudo-terminal.

        Returns:
            CommandResult: structured result of the execution.
        """
        user = self.username(name)
        return self._run_remote(name, command, user=user, use_sudo=True, use_pty=use_pty)

    def execute_parallel(self, hosts: List[str], command: str) -> Dict[str, CommandResult]:
        """Execute the same command on multiple hosts in parallel.

        Args:
            hosts: list of host names.
            command: the command to execute.

        Returns:
            Dict[str, CommandResult]: mapping of host to its result.
        """
        from concurrent.futures import ThreadPoolExecutor

        results = {}
        with ThreadPoolExecutor() as executor:
            future_to_host = {executor.submit(self.execute, host, command): host for host in hosts}
            for future in future_to_host:
                host = future_to_host[future]
                try:
                    results[host] = future.result()
                except Exception as e:
                    logger.error(f"Parallel execution failed for {host}: {e}")

        return results

    def local(self, command: str) -> str:
        """Execute the command on the localhost.

        Args:
            command: the command to execute.

        Returns:
            str: the output of the command.
        """
        return self.execute("localhost", command)

    def username(self, host: str) -> Optional[str]:
        """Returns the username for a given host, falling back to global config or local user.

        Args:
            host: the hostname.

        Returns:
            Optional[str]: the username associated with the host, the global user, 
            or the local system user.
        """
        if not self.conf:
            return os.environ.get("USER", "user")

        # sshconf handles the hierarchy (specific -> global) automatically
        user = self.conf.get(host, 'user')
        if user:
            return user

        return os.environ.get("USER", "user")

    def hostname(self, host: str) -> str:
        """Returns the actual HostName for the given host.

        Args:
            host: the host identifier to look up.

        Returns:
            The actual hostname or IP address associated with the host identifier.
        """
        if not self.conf:
            return host

        hostname = self.conf.get(host, 'hostname')
        return hostname if hostname else host

    def yaml(self) -> str:
        """Returns the parsed SSH configuration in YAML format.

        Returns:
            A YAML string representation of the parsed hosts dictionary.
        """
        if not self.conf:
            return "{}"

        hosts_dict = {}
        for host in self.conf.hosts():
            hosts_dict[host] = self.conf.get_all(host)
        return yaml.dump(hosts_dict, default_flow_style=False)

    def delete(self, name: str):
        """Removes a host entry from the SSH config file.

        Args:
            name: the name of the host to remove.
        """
        if not self.conf:
            return

        try:
            self.conf.remove(name)
            self.conf.save()
        except Exception as e:
            logger.error(f"Failed to delete host {name} from {self.filename}: {e}")

    def generate(
        self,
        host: str = "uva",
        hostname: str = "login.hpc.virginia.edu",
        identity: str = "~/.ssh/id_rsa.pub",
        user: Optional[str] = None,
        verbose: bool = False,
    ):
        """Adds a host to the config file with given parameters.

        Args:
            host: the alias for the host.
            hostname: the actual hostname or IP.
            identity: the path to the identity file.
            user: the username for the host.
            verbose: prints debug messages.
        """
        if not self.conf:
            return

        if verbose and host in self.names():
            logger.warning(f"{host} already in {self.filename}")
            return

        try:
            self.conf.add(host, {
                "hostname": hostname,
                "user": user if user else "",
                "identityfile": identity
            })
            self.conf.save()
            if verbose:
                logger.info(f"Added {host} to {self.filename}")
        except Exception as e:
            logger.error(f"Failed to generate ssh config for {host}: {e}")
Functions
__str__
__str__()

The string representation of the config as JSON.

Source code in src/cloudmesh/ai/common/ssh/ssh_config.py
61
62
63
64
65
66
67
68
69
70
def __str__(self) -> str:
    """The string representation of the config as JSON."""
    if not self.conf:
        return "{}"

    # Convert sshconf to a dictionary for JSON representation
    hosts_dict = {}
    for host in self.conf.hosts():
        hosts_dict[host] = self.conf.get_all(host)
    return json.dumps(hosts_dict, indent=4)
delete
delete(name)

Removes a host entry from the SSH config file.

Parameters:

Name Type Description Default
name str

the name of the host to remove.

required
Source code in src/cloudmesh/ai/common/ssh/ssh_config.py
202
203
204
205
206
207
208
209
210
211
212
213
214
215
def delete(self, name: str):
    """Removes a host entry from the SSH config file.

    Args:
        name: the name of the host to remove.
    """
    if not self.conf:
        return

    try:
        self.conf.remove(name)
        self.conf.save()
    except Exception as e:
        logger.error(f"Failed to delete host {name} from {self.filename}: {e}")
execute
execute(name, command, use_pty=False)

Execute the command on the named host.

Parameters:

Name Type Description Default
name str

the name of the host in config.

required
command str

the command to be executed.

required
use_pty bool

whether to allocate a pseudo-terminal.

False

Returns:

Type Description
Union[CommandResult, str]

Union[CommandResult, str]: CommandResult for remote, stdout for local.

Source code in src/cloudmesh/ai/common/ssh/ssh_config.py
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
def execute(self, name: str, command: str, use_pty: bool = False) -> Union[CommandResult, str]:
    """Execute the command on the named host.

    Args:
        name: the name of the host in config.
        command: the command to be executed.
        use_pty: whether to allocate a pseudo-terminal.

    Returns:
        Union[CommandResult, str]: CommandResult for remote, stdout for local.
    """
    if name == "localhost":
        # Execute locally
        result = self._execute(["sh", "-c", command])
        return result.stdout if result.stdout else result.stderr

    # Execute via Fabric
    user = self.username(name)
    return self._run_remote(name, command, user=user, use_pty=use_pty)
execute_parallel
execute_parallel(hosts, command)

Execute the same command on multiple hosts in parallel.

Parameters:

Name Type Description Default
hosts List[str]

list of host names.

required
command str

the command to execute.

required

Returns:

Type Description
Dict[str, CommandResult]

Dict[str, CommandResult]: mapping of host to its result.

Source code in src/cloudmesh/ai/common/ssh/ssh_config.py
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
def execute_parallel(self, hosts: List[str], command: str) -> Dict[str, CommandResult]:
    """Execute the same command on multiple hosts in parallel.

    Args:
        hosts: list of host names.
        command: the command to execute.

    Returns:
        Dict[str, CommandResult]: mapping of host to its result.
    """
    from concurrent.futures import ThreadPoolExecutor

    results = {}
    with ThreadPoolExecutor() as executor:
        future_to_host = {executor.submit(self.execute, host, command): host for host in hosts}
        for future in future_to_host:
            host = future_to_host[future]
            try:
                results[host] = future.result()
            except Exception as e:
                logger.error(f"Parallel execution failed for {host}: {e}")

    return results
generate
generate(host='uva', hostname='login.hpc.virginia.edu', identity='~/.ssh/id_rsa.pub', user=None, verbose=False)

Adds a host to the config file with given parameters.

Parameters:

Name Type Description Default
host str

the alias for the host.

'uva'
hostname str

the actual hostname or IP.

'login.hpc.virginia.edu'
identity str

the path to the identity file.

'~/.ssh/id_rsa.pub'
user Optional[str]

the username for the host.

None
verbose bool

prints debug messages.

False
Source code in src/cloudmesh/ai/common/ssh/ssh_config.py
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
def generate(
    self,
    host: str = "uva",
    hostname: str = "login.hpc.virginia.edu",
    identity: str = "~/.ssh/id_rsa.pub",
    user: Optional[str] = None,
    verbose: bool = False,
):
    """Adds a host to the config file with given parameters.

    Args:
        host: the alias for the host.
        hostname: the actual hostname or IP.
        identity: the path to the identity file.
        user: the username for the host.
        verbose: prints debug messages.
    """
    if not self.conf:
        return

    if verbose and host in self.names():
        logger.warning(f"{host} already in {self.filename}")
        return

    try:
        self.conf.add(host, {
            "hostname": hostname,
            "user": user if user else "",
            "identityfile": identity
        })
        self.conf.save()
        if verbose:
            logger.info(f"Added {host} to {self.filename}")
    except Exception as e:
        logger.error(f"Failed to generate ssh config for {host}: {e}")
hostname
hostname(host)

Returns the actual HostName for the given host.

Parameters:

Name Type Description Default
host str

the host identifier to look up.

required

Returns:

Type Description
str

The actual hostname or IP address associated with the host identifier.

Source code in src/cloudmesh/ai/common/ssh/ssh_config.py
173
174
175
176
177
178
179
180
181
182
183
184
185
186
def hostname(self, host: str) -> str:
    """Returns the actual HostName for the given host.

    Args:
        host: the host identifier to look up.

    Returns:
        The actual hostname or IP address associated with the host identifier.
    """
    if not self.conf:
        return host

    hostname = self.conf.get(host, 'hostname')
    return hostname if hostname else host
list
list()

List the hosts defined in the config file.

Returns:

Type Description
List[str]

List[str]: list of host names.

Source code in src/cloudmesh/ai/common/ssh/ssh_config.py
51
52
53
54
55
56
57
58
59
def list(self) -> List[str]:
    """List the hosts defined in the config file.

    Returns:
        List[str]: list of host names.
    """
    if not self.conf:
        return []
    return self.conf.hosts()
load
load()

Parse the SSH config file using sshconf.

Source code in src/cloudmesh/ai/common/ssh/ssh_config.py
43
44
45
46
47
48
49
def load(self):
    """Parse the SSH config file using sshconf."""
    try:
        self.conf = SshConf(str(self.filename))
    except Exception as e:
        logger.error(f"Could not load ssh config file {self.filename}: {e}")
        self.conf = None
local
local(command)

Execute the command on the localhost.

Parameters:

Name Type Description Default
command str

the command to execute.

required

Returns:

Name Type Description
str str

the output of the command.

Source code in src/cloudmesh/ai/common/ssh/ssh_config.py
142
143
144
145
146
147
148
149
150
151
def local(self, command: str) -> str:
    """Execute the command on the localhost.

    Args:
        command: the command to execute.

    Returns:
        str: the output of the command.
    """
    return self.execute("localhost", command)
login
login(name)

Login to the host defined in .ssh/config by name.

Parameters:

Name Type Description Default
name str

the name of the host as defined in the config file.

required
Source code in src/cloudmesh/ai/common/ssh/ssh_config.py
72
73
74
75
76
77
78
79
80
81
82
def login(self, name: str):
    """Login to the host defined in .ssh/config by name.

    Args:
        name: the name of the host as defined in the config file.
    """
    logger.info(f"Logging into host: {name}")
    try:
        self._execute(["ssh", name], capture_output=False)
    except Exception as e:
        logger.error(f"Failed to login to {name}: {e}")
names
names()

The names defined in the SSH config.

Returns:

Type Description
List[str]

List[str]: the host names.

Source code in src/cloudmesh/ai/common/ssh/ssh_config.py
35
36
37
38
39
40
41
def names(self) -> List[str]:
    """The names defined in the SSH config.

    Returns:
        List[str]: the host names.
    """
    return self.list()
sudo_execute
sudo_execute(name, command, use_pty=False)

Execute the command on the named host with sudo.

Parameters:

Name Type Description Default
name str

the name of the host in config.

required
command str

the command to be executed.

required
use_pty bool

whether to allocate a pseudo-terminal.

False

Returns:

Name Type Description
CommandResult CommandResult

structured result of the execution.

Source code in src/cloudmesh/ai/common/ssh/ssh_config.py
104
105
106
107
108
109
110
111
112
113
114
115
116
def sudo_execute(self, name: str, command: str, use_pty: bool = False) -> CommandResult:
    """Execute the command on the named host with sudo.

    Args:
        name: the name of the host in config.
        command: the command to be executed.
        use_pty: whether to allocate a pseudo-terminal.

    Returns:
        CommandResult: structured result of the execution.
    """
    user = self.username(name)
    return self._run_remote(name, command, user=user, use_sudo=True, use_pty=use_pty)
username
username(host)

Returns the username for a given host, falling back to global config or local user.

Parameters:

Name Type Description Default
host str

the hostname.

required

Returns:

Type Description
Optional[str]

Optional[str]: the username associated with the host, the global user,

Optional[str]

or the local system user.

Source code in src/cloudmesh/ai/common/ssh/ssh_config.py
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
def username(self, host: str) -> Optional[str]:
    """Returns the username for a given host, falling back to global config or local user.

    Args:
        host: the hostname.

    Returns:
        Optional[str]: the username associated with the host, the global user, 
        or the local system user.
    """
    if not self.conf:
        return os.environ.get("USER", "user")

    # sshconf handles the hierarchy (specific -> global) automatically
    user = self.conf.get(host, 'user')
    if user:
        return user

    return os.environ.get("USER", "user")
yaml
yaml()

Returns the parsed SSH configuration in YAML format.

Returns:

Type Description
str

A YAML string representation of the parsed hosts dictionary.

Source code in src/cloudmesh/ai/common/ssh/ssh_config.py
188
189
190
191
192
193
194
195
196
197
198
199
200
def yaml(self) -> str:
    """Returns the parsed SSH configuration in YAML format.

    Returns:
        A YAML string representation of the parsed hosts dictionary.
    """
    if not self.conf:
        return "{}"

    hosts_dict = {}
    for host in self.conf.hosts():
        hosts_dict[host] = self.conf.get_all(host)
    return yaml.dump(hosts_dict, default_flow_style=False)
Tunnel

Manages an SSH tunnel for port forwarding.

Source code in src/cloudmesh/ai/common/ssh/tunnel.py
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
class Tunnel:
    """Manages an SSH tunnel for port forwarding."""

    def __init__(self, local_port: int, remote_host: str, remote_port: int, ssh_host: str):
        self.local_port = local_port
        self.remote_host = remote_host
        self.remote_port = remote_port
        self.ssh_host = ssh_host
        self.process = None

    def start(self):
        """Starts the SSH tunnel in the background."""
        if self.process and self.process.poll() is None:
            console.warn(f"Tunnel for port {self.local_port} is already running.")
            return True

        try:
            # -L local_port:remote_host:remote_port ssh_host -N
            # -N tells SSH not to execute a remote command, which is used for just forwarding ports.
            cmd = [
                "ssh",
                "-L",
                f"{self.local_port}:{self.remote_host}:{self.remote_port}",
                self.ssh_host,
                "-N",
            ]
            self.process = subprocess.Popen(
                cmd, 
                stdout=subprocess.DEVNULL, 
                stderr=subprocess.DEVNULL, 
                preexec_fn=os.setpgrp # Create a new process group to make killing easier
            )
            console.ok(f"SSH tunnel established: localhost:{self.local_port} -> {self.remote_host}:{self.remote_port} via {self.ssh_host}")
            return True
        except Exception as e:
            console.error(f"Failed to start SSH tunnel: {e}")
            return False

    def stop(self):
        """Stops the SSH tunnel process."""
        if not self.process or self.process.poll() is not None:
            console.warn(f"No active tunnel found for port {self.local_port}.")
            return False

        try:
            # Kill the process group
            os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
            self.process = None
            console.ok(f"SSH tunnel on port {self.local_port} stopped.")
            return True
        except Exception as e:
            console.error(f"Failed to stop SSH tunnel: {e}")
            return False

    def is_active(self) -> bool:
        """Checks if the tunnel process is still running."""
        return self.process is not None and self.process.poll() is None
Functions
is_active
is_active()

Checks if the tunnel process is still running.

Source code in src/cloudmesh/ai/common/ssh/tunnel.py
60
61
62
def is_active(self) -> bool:
    """Checks if the tunnel process is still running."""
    return self.process is not None and self.process.poll() is None
start
start()

Starts the SSH tunnel in the background.

Source code in src/cloudmesh/ai/common/ssh/tunnel.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
def start(self):
    """Starts the SSH tunnel in the background."""
    if self.process and self.process.poll() is None:
        console.warn(f"Tunnel for port {self.local_port} is already running.")
        return True

    try:
        # -L local_port:remote_host:remote_port ssh_host -N
        # -N tells SSH not to execute a remote command, which is used for just forwarding ports.
        cmd = [
            "ssh",
            "-L",
            f"{self.local_port}:{self.remote_host}:{self.remote_port}",
            self.ssh_host,
            "-N",
        ]
        self.process = subprocess.Popen(
            cmd, 
            stdout=subprocess.DEVNULL, 
            stderr=subprocess.DEVNULL, 
            preexec_fn=os.setpgrp # Create a new process group to make killing easier
        )
        console.ok(f"SSH tunnel established: localhost:{self.local_port} -> {self.remote_host}:{self.remote_port} via {self.ssh_host}")
        return True
    except Exception as e:
        console.error(f"Failed to start SSH tunnel: {e}")
        return False
stop
stop()

Stops the SSH tunnel process.

Source code in src/cloudmesh/ai/common/ssh/tunnel.py
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
def stop(self):
    """Stops the SSH tunnel process."""
    if not self.process or self.process.poll() is not None:
        console.warn(f"No active tunnel found for port {self.local_port}.")
        return False

    try:
        # Kill the process group
        os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
        self.process = None
        console.ok(f"SSH tunnel on port {self.local_port} stopped.")
        return True
    except Exception as e:
        console.error(f"Failed to stop SSH tunnel: {e}")
        return False

Modules

authorized_keys
Classes
AuthorizedKeys

Bases: SSHBase

Class to manage authorized keys.

Source code in src/cloudmesh/ai/common/ssh/authorized_keys.py
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
class AuthorizedKeys(SSHBase):
    """Class to manage authorized keys."""

    def __init__(self, debug: bool = False):
        super().__init__(debug=debug)
        self._order: Dict[int, str] = {}
        self._keys: Dict[str, str] = {}

    def get_fingerprint_from_public_key(self, pubkey: str) -> str:
        """Generate the fingerprint of a public key.

        Args:
            pubkey (str): the value of the public key

        Returns:
            str: fingerprint
        """
        try:
            # Use ssh-keygen -l -f /dev/stdin to avoid creating temporary files
            process = self._execute(
                ["ssh-keygen", "-l", "-f", "/dev/stdin"],
                input_data=pubkey
            )
            output = process.stdout.strip()
            # Output format: "2048 SHA256:abc... user@host (RSA)"
            parts = output.split(' ')
            if len(parts) >= 2:
                return parts[1]
            return output
        except Exception as e:
            logger.error(f"Failed to get fingerprint for public key: {e}")
            return ""

    def load(self, path: Union[str, Path]):
        """Load the keys from a path.

        Args:
            path: the filename (path) in which we find the keys
        """
        path = self.resolve_path(str(path))
        if not path.exists():
            logger.warning(f"Authorized keys file not found: {path}")
            return

        try:
            with path.open('r') as fd:
                for pubkey in map(str.strip, fd):
                    # skip empty lines and comments
                    if not pubkey or pubkey.startswith('#'):
                        continue
                    self.add(pubkey)
        except Exception as e:
            logger.error(f"Error loading authorized keys from {path}: {e}")

    def add(self, pubkey: str):
        """Add a public key.

        Args:
            pubkey: the public key string.
        """
        fingerprint = self.get_fingerprint_from_public_key(pubkey)
        if not fingerprint:
            logger.warning("Could not generate fingerprint for public key; skipping.")
            return

        if fingerprint not in self._keys:
            self._order[len(self._keys)] = fingerprint
            self._keys[fingerprint] = pubkey

    def remove(self, fingerprint: str):
        """Removes the public key by its fingerprint.

        Args:
            fingerprint: the fingerprint of the public key to remove.
        """
        if fingerprint in self._keys:
            del self._keys[fingerprint]
            # Rebuild order to keep it contiguous
            new_order = {}
            for i, f in enumerate(self._order.values()):
                if f != fingerprint:
                    new_order[i] = f
            self._order = new_order
        else:
            logger.warning(f"Fingerprint {fingerprint} not found in authorized keys.")

    def __str__(self) -> str:
        with io.StringIO() as sio:
            for fingerprint in self._order.values():
                key = self._keys.get(fingerprint)
                if key:
                    sio.write(key)
                    sio.write('\n')
            return sio.getvalue().strip()

    def __repr__(self) -> str:
        return f"AuthorizedKeys(keys={len(self._keys)})"
Functions
add
add(pubkey)

Add a public key.

Parameters:

Name Type Description Default
pubkey str

the public key string.

required
Source code in src/cloudmesh/ai/common/ssh/authorized_keys.py
73
74
75
76
77
78
79
80
81
82
83
84
85
86
def add(self, pubkey: str):
    """Add a public key.

    Args:
        pubkey: the public key string.
    """
    fingerprint = self.get_fingerprint_from_public_key(pubkey)
    if not fingerprint:
        logger.warning("Could not generate fingerprint for public key; skipping.")
        return

    if fingerprint not in self._keys:
        self._order[len(self._keys)] = fingerprint
        self._keys[fingerprint] = pubkey
get_fingerprint_from_public_key
get_fingerprint_from_public_key(pubkey)

Generate the fingerprint of a public key.

Parameters:

Name Type Description Default
pubkey str

the value of the public key

required

Returns:

Name Type Description
str str

fingerprint

Source code in src/cloudmesh/ai/common/ssh/authorized_keys.py
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
def get_fingerprint_from_public_key(self, pubkey: str) -> str:
    """Generate the fingerprint of a public key.

    Args:
        pubkey (str): the value of the public key

    Returns:
        str: fingerprint
    """
    try:
        # Use ssh-keygen -l -f /dev/stdin to avoid creating temporary files
        process = self._execute(
            ["ssh-keygen", "-l", "-f", "/dev/stdin"],
            input_data=pubkey
        )
        output = process.stdout.strip()
        # Output format: "2048 SHA256:abc... user@host (RSA)"
        parts = output.split(' ')
        if len(parts) >= 2:
            return parts[1]
        return output
    except Exception as e:
        logger.error(f"Failed to get fingerprint for public key: {e}")
        return ""
load
load(path)

Load the keys from a path.

Parameters:

Name Type Description Default
path Union[str, Path]

the filename (path) in which we find the keys

required
Source code in src/cloudmesh/ai/common/ssh/authorized_keys.py
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
def load(self, path: Union[str, Path]):
    """Load the keys from a path.

    Args:
        path: the filename (path) in which we find the keys
    """
    path = self.resolve_path(str(path))
    if not path.exists():
        logger.warning(f"Authorized keys file not found: {path}")
        return

    try:
        with path.open('r') as fd:
            for pubkey in map(str.strip, fd):
                # skip empty lines and comments
                if not pubkey or pubkey.startswith('#'):
                    continue
                self.add(pubkey)
    except Exception as e:
        logger.error(f"Error loading authorized keys from {path}: {e}")
remove
remove(fingerprint)

Removes the public key by its fingerprint.

Parameters:

Name Type Description Default
fingerprint str

the fingerprint of the public key to remove.

required
Source code in src/cloudmesh/ai/common/ssh/authorized_keys.py
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
def remove(self, fingerprint: str):
    """Removes the public key by its fingerprint.

    Args:
        fingerprint: the fingerprint of the public key to remove.
    """
    if fingerprint in self._keys:
        del self._keys[fingerprint]
        # Rebuild order to keep it contiguous
        new_order = {}
        for i, f in enumerate(self._order.values()):
            if f != fingerprint:
                new_order[i] = f
        self._order = new_order
    else:
        logger.warning(f"Fingerprint {fingerprint} not found in authorized keys.")
Modules
base
Classes
CommandResult dataclass

Structured result of a remote command execution.

Source code in src/cloudmesh/ai/common/ssh/base.py
18
19
20
21
22
23
24
25
@dataclass
class CommandResult:
    """Structured result of a remote command execution."""
    stdout: str
    stderr: str
    exit_code: int
    command: str
    host: str
SSHBase

Base class for SSH utilities providing shared execution and path logic.

Source code in src/cloudmesh/ai/common/ssh/base.py
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
class SSHBase:
    """Base class for SSH utilities providing shared execution and path logic."""

    def __init__(self, debug: bool = False):
        self.debug = debug
        self._connection_pool: Dict[str, Connection] = {}

    def _execute(self, command: List[str], input_data: Optional[str] = None, capture_output: bool = True) -> subprocess.CompletedProcess:
        """Execute a system command using subprocess.run.

        Args:
            command: The command and arguments as a list.
            input_data: Optional string to pass to stdin.
            capture_output: Whether to capture stdout and stderr.

        Returns:
            The CompletedProcess object.
        """
        if self.debug:
            logger.debug(f"Executing command: {' '.join(command)}")

        try:
            return subprocess.run(
                command,
                input=input_data,
                capture_output=capture_output,
                text=True,
                check=True
            )
        except subprocess.CalledProcessError as e:
            logger.error(f"Command failed: {e.stderr}")
            raise

    def resolve_path(self, path: str) -> Path:
        """Expand and resolve a path."""
        return Path(path).expanduser().resolve()

    def _get_connection(self, host: str, user: Optional[str] = None) -> Connection:
        """Get a Fabric connection from the pool or create a new one, with health check.

        Args:
            host: the hostname or alias.
            user: optional username.

        Returns:
            Connection: a Fabric connection object.
        """
        pool_key = f"{user}@{host}" if user else host
        conn = self._connection_pool.get(pool_key)

        if conn:
            try:
                # Heartbeat check: run a lightweight command to verify connection
                conn.run("true", hide=True, timeout=5)
            except Exception:
                if self.debug:
                    logger.debug(f"Connection for {pool_key} is stale, recreating...")
                conn = None

        if conn is None:
            if self.debug:
                logger.debug(f"Creating new connection for {pool_key}")
            conn = Connection(host=host, user=user)
            self._connection_pool[pool_key] = conn

        return conn

    def _run_remote(self, host: str, command: str, user: Optional[str] = None, use_sudo: bool = False, use_pty: bool = False) -> CommandResult:
        """Execute a command on a remote host using Fabric.

        Args:
            host: the hostname or alias.
            command: the command to execute.
            user: optional username.
            use_sudo: whether to use sudo for execution.
            use_pty: whether to allocate a pseudo-terminal.

        Returns:
            CommandResult: structured result of the execution.
        """
        if self.debug:
            logger.debug(f"Executing {'sudo ' if use_sudo else ''}remote command on {host} (pty={use_pty}): {command}")

        try:
            conn = self._get_connection(host, user)
            if use_sudo:
                result = conn.sudo(command, hide=True, pty=use_pty)
            else:
                result = conn.run(command, hide=True, pty=use_pty)

            return CommandResult(
                stdout=result.stdout.strip(),
                stderr=result.stderr.strip(),
                exit_code=result.exited,
                command=command,
                host=host
            )
        except Exception as e:
            logger.error(f"Fabric execution failed on {host}: {e}")
            raise e

    def put(self, local_path: str, remote_path: str, host: str, user: Optional[str] = None) -> None:
        """Upload a local file to a remote host."""
        if self.debug:
            logger.debug(f"Uploading {local_path} to {host}:{remote_path}")
        try:
            conn = self._get_connection(host, user)
            conn.put(local_path, remote_path)
        except Exception as e:
            logger.error(f"Fabric put failed on {host}: {e}")
            raise e

    def get(self, remote_path: str, local_path: str, host: str, user: Optional[str] = None) -> None:
        """Download a remote file to a local path."""
        if self.debug:
            logger.debug(f"Downloading {remote_path} from {host} to {local_path}")
        try:
            conn = self._get_connection(host, user)
            conn.get(remote_path, local_path)
        except Exception as e:
            logger.error(f"Fabric get failed on {host}: {e}")
            raise e
Functions
get
get(remote_path, local_path, host, user=None)

Download a remote file to a local path.

Source code in src/cloudmesh/ai/common/ssh/base.py
139
140
141
142
143
144
145
146
147
148
def get(self, remote_path: str, local_path: str, host: str, user: Optional[str] = None) -> None:
    """Download a remote file to a local path."""
    if self.debug:
        logger.debug(f"Downloading {remote_path} from {host} to {local_path}")
    try:
        conn = self._get_connection(host, user)
        conn.get(remote_path, local_path)
    except Exception as e:
        logger.error(f"Fabric get failed on {host}: {e}")
        raise e
put
put(local_path, remote_path, host, user=None)

Upload a local file to a remote host.

Source code in src/cloudmesh/ai/common/ssh/base.py
128
129
130
131
132
133
134
135
136
137
def put(self, local_path: str, remote_path: str, host: str, user: Optional[str] = None) -> None:
    """Upload a local file to a remote host."""
    if self.debug:
        logger.debug(f"Uploading {local_path} to {host}:{remote_path}")
    try:
        conn = self._get_connection(host, user)
        conn.put(local_path, remote_path)
    except Exception as e:
        logger.error(f"Fabric put failed on {host}: {e}")
        raise e
resolve_path
resolve_path(path)

Expand and resolve a path.

Source code in src/cloudmesh/ai/common/ssh/base.py
60
61
62
def resolve_path(self, path: str) -> Path:
    """Expand and resolve a path."""
    return Path(path).expanduser().resolve()
Modules
encryption
Classes
SSHEncryption

Bases: SSHBase

Utility to encrypt and decrypt files using RSA keys via OpenSSL.

Source code in src/cloudmesh/ai/common/ssh/encryption.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
class SSHEncryption(SSHBase):
    """Utility to encrypt and decrypt files using RSA keys via OpenSSL."""

    def __init__(
        self, 
        file_in: str, 
        file_out: str, 
        key_path: str = "~/.ssh/id_rsa", 
        pem_path: str = "~/.ssh/id_rsa.pub.pem", 
        debug: bool = False
    ):
        super().__init__(debug=debug)
        self.file_in = self.resolve_path(file_in)
        self.file_out = self.resolve_path(file_out)
        self.key = self.resolve_path(key_path)
        self.pem = self.resolve_path(pem_path)

    def pem_create(self):
        """Create a PEM public key from the private key."""
        # openssl rsa -in <key> -pubout -out <pem>
        self._execute([
            "openssl", "rsa", 
            "-in", str(self.key), 
            "-pubout", 
            "-out", str(self.pem)
        ])

    def pem_cat(self):
        """Print the PEM public key to stdout."""
        result = self._execute(["cat", str(self.pem)])
        print(result.stdout)

    def encrypt(self):
        """Encrypt the input file using the public PEM key."""
        # Use pkeyutl (modern) instead of rsautl (deprecated)
        # openssl pkeyutl -encrypt -pubin -inkey <pem> -in <file> -out <secret>
        self._execute([
            "openssl", "pkeyutl", 
            "-encrypt", 
            "-pubin", 
            "-inkey", str(self.pem), 
            "-in", str(self.file_in), 
            "-out", str(self.file_out)
        ])

    def decrypt(self, filename: Optional[str] = None):
        """Decrypt the secret file using the private key."""
        secret_file = self.resolve_path(filename) if filename else self.file_out

        # openssl pkeyutl -decrypt -inkey <key> -in <secret>
        result = self._execute([
            "openssl", "pkeyutl", 
            "-decrypt", 
            "-inkey", str(self.key), 
            "-in", str(secret_file)
        ])
        print(result.stdout)
Functions
decrypt
decrypt(filename=None)

Decrypt the secret file using the private key.

Source code in src/cloudmesh/ai/common/ssh/encryption.py
61
62
63
64
65
66
67
68
69
70
71
72
def decrypt(self, filename: Optional[str] = None):
    """Decrypt the secret file using the private key."""
    secret_file = self.resolve_path(filename) if filename else self.file_out

    # openssl pkeyutl -decrypt -inkey <key> -in <secret>
    result = self._execute([
        "openssl", "pkeyutl", 
        "-decrypt", 
        "-inkey", str(self.key), 
        "-in", str(secret_file)
    ])
    print(result.stdout)
encrypt
encrypt()

Encrypt the input file using the public PEM key.

Source code in src/cloudmesh/ai/common/ssh/encryption.py
48
49
50
51
52
53
54
55
56
57
58
59
def encrypt(self):
    """Encrypt the input file using the public PEM key."""
    # Use pkeyutl (modern) instead of rsautl (deprecated)
    # openssl pkeyutl -encrypt -pubin -inkey <pem> -in <file> -out <secret>
    self._execute([
        "openssl", "pkeyutl", 
        "-encrypt", 
        "-pubin", 
        "-inkey", str(self.pem), 
        "-in", str(self.file_in), 
        "-out", str(self.file_out)
    ])
pem_cat
pem_cat()

Print the PEM public key to stdout.

Source code in src/cloudmesh/ai/common/ssh/encryption.py
43
44
45
46
def pem_cat(self):
    """Print the PEM public key to stdout."""
    result = self._execute(["cat", str(self.pem)])
    print(result.stdout)
pem_create
pem_create()

Create a PEM public key from the private key.

Source code in src/cloudmesh/ai/common/ssh/encryption.py
33
34
35
36
37
38
39
40
41
def pem_create(self):
    """Create a PEM public key from the private key."""
    # openssl rsa -in <key> -pubout -out <pem>
    self._execute([
        "openssl", "rsa", 
        "-in", str(self.key), 
        "-pubout", 
        "-out", str(self.pem)
    ])
Modules
ssh_config
Classes
SSHConfig

Bases: SSHBase

Managing the SSH config file (usually ~/.ssh/config).

Source code in src/cloudmesh/ai/common/ssh/ssh_config.py
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
class SSHConfig(SSHBase):
    """Managing the SSH config file (usually ~/.ssh/config)."""

    def __init__(self, filename: Optional[Union[str, Path]] = None, debug: bool = False):
        super().__init__(debug=debug)
        if filename is not None:
            self.filename = self.resolve_path(str(filename))
        else:
            self.filename = self.resolve_path("~/.ssh/config")

        self.conf: Optional[SshConf] = None
        self.load()

    def names(self) -> List[str]:
        """The names defined in the SSH config.

        Returns:
            List[str]: the host names.
        """
        return self.list()

    def load(self):
        """Parse the SSH config file using sshconf."""
        try:
            self.conf = SshConf(str(self.filename))
        except Exception as e:
            logger.error(f"Could not load ssh config file {self.filename}: {e}")
            self.conf = None

    def list(self) -> List[str]:
        """List the hosts defined in the config file.

        Returns:
            List[str]: list of host names.
        """
        if not self.conf:
            return []
        return self.conf.hosts()

    def __str__(self) -> str:
        """The string representation of the config as JSON."""
        if not self.conf:
            return "{}"

        # Convert sshconf to a dictionary for JSON representation
        hosts_dict = {}
        for host in self.conf.hosts():
            hosts_dict[host] = self.conf.get_all(host)
        return json.dumps(hosts_dict, indent=4)

    def login(self, name: str):
        """Login to the host defined in .ssh/config by name.

        Args:
            name: the name of the host as defined in the config file.
        """
        logger.info(f"Logging into host: {name}")
        try:
            self._execute(["ssh", name], capture_output=False)
        except Exception as e:
            logger.error(f"Failed to login to {name}: {e}")

    def execute(self, name: str, command: str, use_pty: bool = False) -> Union[CommandResult, str]:
        """Execute the command on the named host.

        Args:
            name: the name of the host in config.
            command: the command to be executed.
            use_pty: whether to allocate a pseudo-terminal.

        Returns:
            Union[CommandResult, str]: CommandResult for remote, stdout for local.
        """
        if name == "localhost":
            # Execute locally
            result = self._execute(["sh", "-c", command])
            return result.stdout if result.stdout else result.stderr

        # Execute via Fabric
        user = self.username(name)
        return self._run_remote(name, command, user=user, use_pty=use_pty)

    def sudo_execute(self, name: str, command: str, use_pty: bool = False) -> CommandResult:
        """Execute the command on the named host with sudo.

        Args:
            name: the name of the host in config.
            command: the command to be executed.
            use_pty: whether to allocate a pseudo-terminal.

        Returns:
            CommandResult: structured result of the execution.
        """
        user = self.username(name)
        return self._run_remote(name, command, user=user, use_sudo=True, use_pty=use_pty)

    def execute_parallel(self, hosts: List[str], command: str) -> Dict[str, CommandResult]:
        """Execute the same command on multiple hosts in parallel.

        Args:
            hosts: list of host names.
            command: the command to execute.

        Returns:
            Dict[str, CommandResult]: mapping of host to its result.
        """
        from concurrent.futures import ThreadPoolExecutor

        results = {}
        with ThreadPoolExecutor() as executor:
            future_to_host = {executor.submit(self.execute, host, command): host for host in hosts}
            for future in future_to_host:
                host = future_to_host[future]
                try:
                    results[host] = future.result()
                except Exception as e:
                    logger.error(f"Parallel execution failed for {host}: {e}")

        return results

    def local(self, command: str) -> str:
        """Execute the command on the localhost.

        Args:
            command: the command to execute.

        Returns:
            str: the output of the command.
        """
        return self.execute("localhost", command)

    def username(self, host: str) -> Optional[str]:
        """Returns the username for a given host, falling back to global config or local user.

        Args:
            host: the hostname.

        Returns:
            Optional[str]: the username associated with the host, the global user, 
            or the local system user.
        """
        if not self.conf:
            return os.environ.get("USER", "user")

        # sshconf handles the hierarchy (specific -> global) automatically
        user = self.conf.get(host, 'user')
        if user:
            return user

        return os.environ.get("USER", "user")

    def hostname(self, host: str) -> str:
        """Returns the actual HostName for the given host.

        Args:
            host: the host identifier to look up.

        Returns:
            The actual hostname or IP address associated with the host identifier.
        """
        if not self.conf:
            return host

        hostname = self.conf.get(host, 'hostname')
        return hostname if hostname else host

    def yaml(self) -> str:
        """Returns the parsed SSH configuration in YAML format.

        Returns:
            A YAML string representation of the parsed hosts dictionary.
        """
        if not self.conf:
            return "{}"

        hosts_dict = {}
        for host in self.conf.hosts():
            hosts_dict[host] = self.conf.get_all(host)
        return yaml.dump(hosts_dict, default_flow_style=False)

    def delete(self, name: str):
        """Removes a host entry from the SSH config file.

        Args:
            name: the name of the host to remove.
        """
        if not self.conf:
            return

        try:
            self.conf.remove(name)
            self.conf.save()
        except Exception as e:
            logger.error(f"Failed to delete host {name} from {self.filename}: {e}")

    def generate(
        self,
        host: str = "uva",
        hostname: str = "login.hpc.virginia.edu",
        identity: str = "~/.ssh/id_rsa.pub",
        user: Optional[str] = None,
        verbose: bool = False,
    ):
        """Adds a host to the config file with given parameters.

        Args:
            host: the alias for the host.
            hostname: the actual hostname or IP.
            identity: the path to the identity file.
            user: the username for the host.
            verbose: prints debug messages.
        """
        if not self.conf:
            return

        if verbose and host in self.names():
            logger.warning(f"{host} already in {self.filename}")
            return

        try:
            self.conf.add(host, {
                "hostname": hostname,
                "user": user if user else "",
                "identityfile": identity
            })
            self.conf.save()
            if verbose:
                logger.info(f"Added {host} to {self.filename}")
        except Exception as e:
            logger.error(f"Failed to generate ssh config for {host}: {e}")
Functions
__str__
__str__()

The string representation of the config as JSON.

Source code in src/cloudmesh/ai/common/ssh/ssh_config.py
61
62
63
64
65
66
67
68
69
70
def __str__(self) -> str:
    """The string representation of the config as JSON."""
    if not self.conf:
        return "{}"

    # Convert sshconf to a dictionary for JSON representation
    hosts_dict = {}
    for host in self.conf.hosts():
        hosts_dict[host] = self.conf.get_all(host)
    return json.dumps(hosts_dict, indent=4)
delete
delete(name)

Removes a host entry from the SSH config file.

Parameters:

Name Type Description Default
name str

the name of the host to remove.

required
Source code in src/cloudmesh/ai/common/ssh/ssh_config.py
202
203
204
205
206
207
208
209
210
211
212
213
214
215
def delete(self, name: str):
    """Removes a host entry from the SSH config file.

    Args:
        name: the name of the host to remove.
    """
    if not self.conf:
        return

    try:
        self.conf.remove(name)
        self.conf.save()
    except Exception as e:
        logger.error(f"Failed to delete host {name} from {self.filename}: {e}")
execute
execute(name, command, use_pty=False)

Execute the command on the named host.

Parameters:

Name Type Description Default
name str

the name of the host in config.

required
command str

the command to be executed.

required
use_pty bool

whether to allocate a pseudo-terminal.

False

Returns:

Type Description
Union[CommandResult, str]

Union[CommandResult, str]: CommandResult for remote, stdout for local.

Source code in src/cloudmesh/ai/common/ssh/ssh_config.py
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
def execute(self, name: str, command: str, use_pty: bool = False) -> Union[CommandResult, str]:
    """Execute the command on the named host.

    Args:
        name: the name of the host in config.
        command: the command to be executed.
        use_pty: whether to allocate a pseudo-terminal.

    Returns:
        Union[CommandResult, str]: CommandResult for remote, stdout for local.
    """
    if name == "localhost":
        # Execute locally
        result = self._execute(["sh", "-c", command])
        return result.stdout if result.stdout else result.stderr

    # Execute via Fabric
    user = self.username(name)
    return self._run_remote(name, command, user=user, use_pty=use_pty)
execute_parallel
execute_parallel(hosts, command)

Execute the same command on multiple hosts in parallel.

Parameters:

Name Type Description Default
hosts List[str]

list of host names.

required
command str

the command to execute.

required

Returns:

Type Description
Dict[str, CommandResult]

Dict[str, CommandResult]: mapping of host to its result.

Source code in src/cloudmesh/ai/common/ssh/ssh_config.py
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
def execute_parallel(self, hosts: List[str], command: str) -> Dict[str, CommandResult]:
    """Execute the same command on multiple hosts in parallel.

    Args:
        hosts: list of host names.
        command: the command to execute.

    Returns:
        Dict[str, CommandResult]: mapping of host to its result.
    """
    from concurrent.futures import ThreadPoolExecutor

    results = {}
    with ThreadPoolExecutor() as executor:
        future_to_host = {executor.submit(self.execute, host, command): host for host in hosts}
        for future in future_to_host:
            host = future_to_host[future]
            try:
                results[host] = future.result()
            except Exception as e:
                logger.error(f"Parallel execution failed for {host}: {e}")

    return results
generate
generate(host='uva', hostname='login.hpc.virginia.edu', identity='~/.ssh/id_rsa.pub', user=None, verbose=False)

Adds a host to the config file with given parameters.

Parameters:

Name Type Description Default
host str

the alias for the host.

'uva'
hostname str

the actual hostname or IP.

'login.hpc.virginia.edu'
identity str

the path to the identity file.

'~/.ssh/id_rsa.pub'
user Optional[str]

the username for the host.

None
verbose bool

prints debug messages.

False
Source code in src/cloudmesh/ai/common/ssh/ssh_config.py
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
def generate(
    self,
    host: str = "uva",
    hostname: str = "login.hpc.virginia.edu",
    identity: str = "~/.ssh/id_rsa.pub",
    user: Optional[str] = None,
    verbose: bool = False,
):
    """Adds a host to the config file with given parameters.

    Args:
        host: the alias for the host.
        hostname: the actual hostname or IP.
        identity: the path to the identity file.
        user: the username for the host.
        verbose: prints debug messages.
    """
    if not self.conf:
        return

    if verbose and host in self.names():
        logger.warning(f"{host} already in {self.filename}")
        return

    try:
        self.conf.add(host, {
            "hostname": hostname,
            "user": user if user else "",
            "identityfile": identity
        })
        self.conf.save()
        if verbose:
            logger.info(f"Added {host} to {self.filename}")
    except Exception as e:
        logger.error(f"Failed to generate ssh config for {host}: {e}")
hostname
hostname(host)

Returns the actual HostName for the given host.

Parameters:

Name Type Description Default
host str

the host identifier to look up.

required

Returns:

Type Description
str

The actual hostname or IP address associated with the host identifier.

Source code in src/cloudmesh/ai/common/ssh/ssh_config.py
173
174
175
176
177
178
179
180
181
182
183
184
185
186
def hostname(self, host: str) -> str:
    """Returns the actual HostName for the given host.

    Args:
        host: the host identifier to look up.

    Returns:
        The actual hostname or IP address associated with the host identifier.
    """
    if not self.conf:
        return host

    hostname = self.conf.get(host, 'hostname')
    return hostname if hostname else host
list
list()

List the hosts defined in the config file.

Returns:

Type Description
List[str]

List[str]: list of host names.

Source code in src/cloudmesh/ai/common/ssh/ssh_config.py
51
52
53
54
55
56
57
58
59
def list(self) -> List[str]:
    """List the hosts defined in the config file.

    Returns:
        List[str]: list of host names.
    """
    if not self.conf:
        return []
    return self.conf.hosts()
load
load()

Parse the SSH config file using sshconf.

Source code in src/cloudmesh/ai/common/ssh/ssh_config.py
43
44
45
46
47
48
49
def load(self):
    """Parse the SSH config file using sshconf."""
    try:
        self.conf = SshConf(str(self.filename))
    except Exception as e:
        logger.error(f"Could not load ssh config file {self.filename}: {e}")
        self.conf = None
local
local(command)

Execute the command on the localhost.

Parameters:

Name Type Description Default
command str

the command to execute.

required

Returns:

Name Type Description
str str

the output of the command.

Source code in src/cloudmesh/ai/common/ssh/ssh_config.py
142
143
144
145
146
147
148
149
150
151
def local(self, command: str) -> str:
    """Execute the command on the localhost.

    Args:
        command: the command to execute.

    Returns:
        str: the output of the command.
    """
    return self.execute("localhost", command)
login
login(name)

Login to the host defined in .ssh/config by name.

Parameters:

Name Type Description Default
name str

the name of the host as defined in the config file.

required
Source code in src/cloudmesh/ai/common/ssh/ssh_config.py
72
73
74
75
76
77
78
79
80
81
82
def login(self, name: str):
    """Login to the host defined in .ssh/config by name.

    Args:
        name: the name of the host as defined in the config file.
    """
    logger.info(f"Logging into host: {name}")
    try:
        self._execute(["ssh", name], capture_output=False)
    except Exception as e:
        logger.error(f"Failed to login to {name}: {e}")
names
names()

The names defined in the SSH config.

Returns:

Type Description
List[str]

List[str]: the host names.

Source code in src/cloudmesh/ai/common/ssh/ssh_config.py
35
36
37
38
39
40
41
def names(self) -> List[str]:
    """The names defined in the SSH config.

    Returns:
        List[str]: the host names.
    """
    return self.list()
sudo_execute
sudo_execute(name, command, use_pty=False)

Execute the command on the named host with sudo.

Parameters:

Name Type Description Default
name str

the name of the host in config.

required
command str

the command to be executed.

required
use_pty bool

whether to allocate a pseudo-terminal.

False

Returns:

Name Type Description
CommandResult CommandResult

structured result of the execution.

Source code in src/cloudmesh/ai/common/ssh/ssh_config.py
104
105
106
107
108
109
110
111
112
113
114
115
116
def sudo_execute(self, name: str, command: str, use_pty: bool = False) -> CommandResult:
    """Execute the command on the named host with sudo.

    Args:
        name: the name of the host in config.
        command: the command to be executed.
        use_pty: whether to allocate a pseudo-terminal.

    Returns:
        CommandResult: structured result of the execution.
    """
    user = self.username(name)
    return self._run_remote(name, command, user=user, use_sudo=True, use_pty=use_pty)
username
username(host)

Returns the username for a given host, falling back to global config or local user.

Parameters:

Name Type Description Default
host str

the hostname.

required

Returns:

Type Description
Optional[str]

Optional[str]: the username associated with the host, the global user,

Optional[str]

or the local system user.

Source code in src/cloudmesh/ai/common/ssh/ssh_config.py
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
def username(self, host: str) -> Optional[str]:
    """Returns the username for a given host, falling back to global config or local user.

    Args:
        host: the hostname.

    Returns:
        Optional[str]: the username associated with the host, the global user, 
        or the local system user.
    """
    if not self.conf:
        return os.environ.get("USER", "user")

    # sshconf handles the hierarchy (specific -> global) automatically
    user = self.conf.get(host, 'user')
    if user:
        return user

    return os.environ.get("USER", "user")
yaml
yaml()

Returns the parsed SSH configuration in YAML format.

Returns:

Type Description
str

A YAML string representation of the parsed hosts dictionary.

Source code in src/cloudmesh/ai/common/ssh/ssh_config.py
188
189
190
191
192
193
194
195
196
197
198
199
200
def yaml(self) -> str:
    """Returns the parsed SSH configuration in YAML format.

    Returns:
        A YAML string representation of the parsed hosts dictionary.
    """
    if not self.conf:
        return "{}"

    hosts_dict = {}
    for host in self.conf.hosts():
        hosts_dict[host] = self.conf.get_all(host)
    return yaml.dump(hosts_dict, default_flow_style=False)
Modules
transfer
Classes
SSHFileTransfer

Bases: SSHBase

Utility to transfer files to and from remote hosts using Fabric/SFTP.

Source code in src/cloudmesh/ai/common/ssh/transfer.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
class SSHFileTransfer(SSHBase):
    """Utility to transfer files to and from remote hosts using Fabric/SFTP."""

    def upload(
        self, 
        local_path: Union[str, Path], 
        remote_path: Union[str, Path], 
        host: str, 
        user: Optional[str] = None
    ) -> bool:
        """Upload a file to a remote host.

        Args:
            local_path: path to the local file.
            remote_path: path to the destination on the remote host.
            host: the remote host.
            user: optional username.

        Returns:
            bool: True if successful, False otherwise.
        """
        local_path = self.resolve_path(str(local_path))
        if not local_path.exists():
            logger.error(f"Local file not found: {local_path}")
            return False

        if self.debug:
            logger.debug(f"Uploading {local_path} to {host}:{remote_path}")

        try:
            conn = self._get_connection(host, user)
            conn.put(str(local_path), str(remote_path))
            return True
        except Exception as e:
            logger.error(f"Failed to upload file to {host}: {e}")
            return False

    def download(
        self, 
        remote_path: Union[str, Path], 
        local_path: Union[str, Path], 
        host: str, 
        user: Optional[str] = None
    ) -> bool:
        """Download a file from a remote host.

        Args:
            remote_path: path to the file on the remote host.
            local_path: path to the destination on the local system.
            host: the remote host.
            user: optional username.

        Returns:
            bool: True if successful, False otherwise.
        """
        local_path = self.resolve_path(str(local_path))
        if self.debug:
            logger.debug(f"Downloading {remote_path} from {host} to {local_path}")

        try:
            conn = self._get_connection(host, user)
            conn.get(str(remote_path), str(local_path))
            return True
        except Exception as e:
            logger.error(f"Failed to download file from {host}: {e}")
            return False
Functions
download
download(remote_path, local_path, host, user=None)

Download a file from a remote host.

Parameters:

Name Type Description Default
remote_path Union[str, Path]

path to the file on the remote host.

required
local_path Union[str, Path]

path to the destination on the local system.

required
host str

the remote host.

required
user Optional[str]

optional username.

None

Returns:

Name Type Description
bool bool

True if successful, False otherwise.

Source code in src/cloudmesh/ai/common/ssh/transfer.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def download(
    self, 
    remote_path: Union[str, Path], 
    local_path: Union[str, Path], 
    host: str, 
    user: Optional[str] = None
) -> bool:
    """Download a file from a remote host.

    Args:
        remote_path: path to the file on the remote host.
        local_path: path to the destination on the local system.
        host: the remote host.
        user: optional username.

    Returns:
        bool: True if successful, False otherwise.
    """
    local_path = self.resolve_path(str(local_path))
    if self.debug:
        logger.debug(f"Downloading {remote_path} from {host} to {local_path}")

    try:
        conn = self._get_connection(host, user)
        conn.get(str(remote_path), str(local_path))
        return True
    except Exception as e:
        logger.error(f"Failed to download file from {host}: {e}")
        return False
upload
upload(local_path, remote_path, host, user=None)

Upload a file to a remote host.

Parameters:

Name Type Description Default
local_path Union[str, Path]

path to the local file.

required
remote_path Union[str, Path]

path to the destination on the remote host.

required
host str

the remote host.

required
user Optional[str]

optional username.

None

Returns:

Name Type Description
bool bool

True if successful, False otherwise.

Source code in src/cloudmesh/ai/common/ssh/transfer.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
def upload(
    self, 
    local_path: Union[str, Path], 
    remote_path: Union[str, Path], 
    host: str, 
    user: Optional[str] = None
) -> bool:
    """Upload a file to a remote host.

    Args:
        local_path: path to the local file.
        remote_path: path to the destination on the remote host.
        host: the remote host.
        user: optional username.

    Returns:
        bool: True if successful, False otherwise.
    """
    local_path = self.resolve_path(str(local_path))
    if not local_path.exists():
        logger.error(f"Local file not found: {local_path}")
        return False

    if self.debug:
        logger.debug(f"Uploading {local_path} to {host}:{remote_path}")

    try:
        conn = self._get_connection(host, user)
        conn.put(str(local_path), str(remote_path))
        return True
    except Exception as e:
        logger.error(f"Failed to upload file to {host}: {e}")
        return False
Modules
tunnel
Classes
Tunnel

Manages an SSH tunnel for port forwarding.

Source code in src/cloudmesh/ai/common/ssh/tunnel.py
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
class Tunnel:
    """Manages an SSH tunnel for port forwarding."""

    def __init__(self, local_port: int, remote_host: str, remote_port: int, ssh_host: str):
        self.local_port = local_port
        self.remote_host = remote_host
        self.remote_port = remote_port
        self.ssh_host = ssh_host
        self.process = None

    def start(self):
        """Starts the SSH tunnel in the background."""
        if self.process and self.process.poll() is None:
            console.warn(f"Tunnel for port {self.local_port} is already running.")
            return True

        try:
            # -L local_port:remote_host:remote_port ssh_host -N
            # -N tells SSH not to execute a remote command, which is used for just forwarding ports.
            cmd = [
                "ssh",
                "-L",
                f"{self.local_port}:{self.remote_host}:{self.remote_port}",
                self.ssh_host,
                "-N",
            ]
            self.process = subprocess.Popen(
                cmd, 
                stdout=subprocess.DEVNULL, 
                stderr=subprocess.DEVNULL, 
                preexec_fn=os.setpgrp # Create a new process group to make killing easier
            )
            console.ok(f"SSH tunnel established: localhost:{self.local_port} -> {self.remote_host}:{self.remote_port} via {self.ssh_host}")
            return True
        except Exception as e:
            console.error(f"Failed to start SSH tunnel: {e}")
            return False

    def stop(self):
        """Stops the SSH tunnel process."""
        if not self.process or self.process.poll() is not None:
            console.warn(f"No active tunnel found for port {self.local_port}.")
            return False

        try:
            # Kill the process group
            os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
            self.process = None
            console.ok(f"SSH tunnel on port {self.local_port} stopped.")
            return True
        except Exception as e:
            console.error(f"Failed to stop SSH tunnel: {e}")
            return False

    def is_active(self) -> bool:
        """Checks if the tunnel process is still running."""
        return self.process is not None and self.process.poll() is None
Functions
is_active
is_active()

Checks if the tunnel process is still running.

Source code in src/cloudmesh/ai/common/ssh/tunnel.py
60
61
62
def is_active(self) -> bool:
    """Checks if the tunnel process is still running."""
    return self.process is not None and self.process.poll() is None
start
start()

Starts the SSH tunnel in the background.

Source code in src/cloudmesh/ai/common/ssh/tunnel.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
def start(self):
    """Starts the SSH tunnel in the background."""
    if self.process and self.process.poll() is None:
        console.warn(f"Tunnel for port {self.local_port} is already running.")
        return True

    try:
        # -L local_port:remote_host:remote_port ssh_host -N
        # -N tells SSH not to execute a remote command, which is used for just forwarding ports.
        cmd = [
            "ssh",
            "-L",
            f"{self.local_port}:{self.remote_host}:{self.remote_port}",
            self.ssh_host,
            "-N",
        ]
        self.process = subprocess.Popen(
            cmd, 
            stdout=subprocess.DEVNULL, 
            stderr=subprocess.DEVNULL, 
            preexec_fn=os.setpgrp # Create a new process group to make killing easier
        )
        console.ok(f"SSH tunnel established: localhost:{self.local_port} -> {self.remote_host}:{self.remote_port} via {self.ssh_host}")
        return True
    except Exception as e:
        console.error(f"Failed to start SSH tunnel: {e}")
        return False
stop
stop()

Stops the SSH tunnel process.

Source code in src/cloudmesh/ai/common/ssh/tunnel.py
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
def stop(self):
    """Stops the SSH tunnel process."""
    if not self.process or self.process.poll() is not None:
        console.warn(f"No active tunnel found for port {self.local_port}.")
        return False

    try:
        # Kill the process group
        os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
        self.process = None
        console.ok(f"SSH tunnel on port {self.local_port} stopped.")
        return True
    except Exception as e:
        console.error(f"Failed to stop SSH tunnel: {e}")
        return False

stopwatch

Class for starting and stopping named timers. Provides a simple way to benchmark code execution and track events.

Classes

StopWatch

A class to measure times between events.

Source code in src/cloudmesh/ai/common/stopwatch.py
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
class StopWatch:
    """A class to measure times between events."""

    debug = False
    verbose = True
    _local = threading.local()

    @classmethod
    def _get_local(cls):
        """Ensures thread-local storage is initialized for the current thread.

        Returns:
            The thread-local storage object.
        """
        if not hasattr(cls._local, "timer_start"):
            cls._local.timer_start = {}
            cls._local.timer_end = {}
            cls._local.timer_elapsed = {}
            cls._local.timer_status = {}
            cls._local.timer_sum = {}
            cls._local.timer_msg = {}
            cls._local.timer_values = {}
        return cls._local

    @classmethod
    def debug_mode(cls, value: bool = True):
        """Enables or disables debug mode for timers.

        Args:
            value: True to enable debug mode, False to disable it. Defaults to True.
        """
        cls.debug = value
        if value:
            print("StopWatch: Debug mode enabled.")
        else:
            print("StopWatch: Debug mode disabled.")

    @classmethod
    def _get_caller_name(cls, with_class: bool = True) -> str:
        """Retrieves the name of the calling method.

        Args:
            with_class: If True, includes the class name in the returned string. 
                Defaults to True.

        Returns:
            The name of the calling method.
        """
        # frame[0] is this method, frame[1] is the caller of this method, 
        # frame[2] is the caller of the StopWatch method (Start/Stop/etc)
        frame = inspect.getouterframes(inspect.currentframe())
        method = frame[2][3]

        if with_class:
            classname = os.path.basename(frame[2].filename).replace(".py", "")
            method = f"{classname}/{method}"
        return method

    @classmethod
    @contextmanager
    def timer(cls, name: Optional[str] = None, values: Any = None) -> Generator[Optional[float], None, None]:
        """Context manager to time a block of code.

        Example:
            with StopWatch.timer("my_operation"):
                do_work()

        Args:
            name: The name of the timer. If None, it is automatically detected.
            values: Optional values to associate with the timer.
        """
        cls.start(name, values=values)
        try:
            yield None
        finally:
            cls.stop(name)
    @classmethod
    def keys(cls) -> List[str]:
        """Returns the names of the timers.

        Returns:
            A list of timer names.
        """
        return list(cls._get_local().timer_end.keys())

    @classmethod
    def status(cls, name: str, value: Any):
        """Records a status for the timer.

        Args:
            name: The name of the timer.
            value: The status value to record.
        """
        if cls.debug:
            print(f"Timer {name} status {value}")
        cls._get_local().timer_status[name] = value

    @classmethod
    def get_message(cls, name: str) -> Optional[str]:
        """Returns the message of the timer.

        Args:
            name: The name of the timer.

        Returns:
            The message associated with the timer, or None if not set.
        """
        return cls._get_local().timer_msg.get(name)

    @classmethod
    def message(cls, name: str, value: str):
        """Records a message for the timer.

        Args:
            name: The name of the timer.
            value: The message string to record.
        """
        cls._get_local().timer_msg[name] = value

    @classmethod
    def event(cls, name: str, msg: Optional[str] = None, values: Any = None):
        """Adds an event with a given name, where start and stop is the same time.

        Args:
            name: The name of the event.
            msg: Optional message to associate with the event.
            values: Optional values to associate with the event.
        """
        cls.start(name, values=values)
        cls.stop(name)
        local = cls._get_local()
        local.timer_end[name] = local.timer_start[name]
        if values:
            local.timer_values[name] = values
        if msg is not None:
            cls.message(name, str(msg))
        if cls.debug:
            print(f"Timer {name} event ...")

    @classmethod
    def start(cls, name: Optional[str] = None, values: Any = None):
        """Starts a timer with the given name.

        If name is None, it is automatically detected from the caller.

        Args:
            name: The name of the timer.
            values: Optional values to associate with the timer.
        """
        if name is None:
            name = cls._get_caller_name()

        local = cls._get_local()
        if cls.debug:
            print(f"Timer {name} started ...")
        if name not in local.timer_sum:
            local.timer_sum[name] = 0.0
        local.timer_start[name] = time.time()
        local.timer_end[name] = None
        local.timer_status[name] = None
        local.timer_msg[name] = None
        if values:
            local.timer_values[name] = values

    @classmethod
    def stop(cls, name: Optional[str] = None, state: Any = True, values: Any = None):
        """Stops the timer with a given name.

        If name is None, it is automatically detected from the caller.

        Args:
            name: The name of the timer.
            state: The final state of the timer (e.g., True for success).
            values: Optional values to associate with the timer.
        """
        if name is None:
            name = cls._get_caller_name()

        local = cls._get_local()
        local.timer_end[name] = time.time()
        local.timer_sum[name] = (
            local.timer_sum[name] + local.timer_end[name] - local.timer_start[name]
        )
        local.timer_status[name] = state
        if values:
            local.timer_values[name] = values
        if cls.debug:
            print(f"Timer {name} stopped ...")

    @classmethod
    def get_status(cls, name: str) -> Any:
        """Returns the status of the timer.

        Args:
            name: The name of the timer.

        Returns:
            The status value associated with the timer.
        """
        return cls._get_local().timer_status.get(name)

    @classmethod
    def get(cls, name: str, digits: int = 4) -> Union[float, str, None]:
        """Returns the elapsed time of the timer.

        Args:
            name: The name of the timer.
            digits: Number of decimal places to round to. Defaults to 4.

        Returns:
            The elapsed time as a float, "undefined" if not found, or None on error.
        """
        local = cls._get_local()
        if name in local.timer_end and local.timer_end[name] is not None:
            try:
                diff = local.timer_end[name] - local.timer_start[name]
                local.timer_elapsed[name] = round(diff, digits)
                return local.timer_elapsed[name]
            except Exception:
                return None
        return "undefined"

    @classmethod
    def sum(cls, name: str, digits: int = 4) -> Union[float, str, None]:
        """Returns the sum of the timer if used multiple times.

        Args:
            name: The name of the timer.
            digits: Number of decimal places to round to. Defaults to 4.

        Returns:
            The total elapsed time as a float, "undefined" if not found, or None on error.
        """
        local = cls._get_local()
        if name in local.timer_end:
            try:
                diff = local.timer_sum[name]
                return round(diff, digits)
            except Exception:
                return None
        return "undefined"

    @classmethod
    def clear(cls):
        """Clears all timers in the current thread."""
        local = cls._get_local()
        local.timer_start.clear()
        local.timer_end.clear()
        local.timer_sum.clear()
        local.timer_status.clear()
        local.timer_elapsed.clear()
        local.timer_msg.clear()
        local.timer_values.clear()

    @classmethod
    def print(cls, label: str, name: str):
        """Prints a timer with a label.

        Args:
            label: The label to print before the timer value.
            name: The name of the timer to print.
        """
        if cls.verbose:
            val = cls.get(name)
            if isinstance(val, float):
                print(f"{label} {val:.2f} s")
            else:
                print(f"{label} {val}")

    @classmethod
    def benchmark(cls, sysinfo: bool = True, tag: Optional[str] = None, filename: Optional[str] = None):
        """Prints out all timers in a convenient benchmark format for the current thread.

        Args:
            sysinfo: If True, includes system information in the output. Defaults to True.
            tag: Optional tag to include in the benchmark.
            filename: Optional file path to write the benchmark results to.
        """
        content = "\n--- Benchmark Results (Current Thread) ---\n"

        if sysinfo:
            info = ai_sys.systeminfo()
            content += "System Info:\n"
            for k, v in info.items():
                content += f"  {k}: {v}\n"
            content += "\n"

        timers = cls.keys()
        if not timers:
            content += "No timers found.\n"
        else:
            content += f"{'Timer':<30} {'Status':<10} {'Time (s)':<10} {'Sum (s)':<10} {'Msg'}\n"
            content += "-" * 70 + "\n"
            for t in timers:
                status = cls.get_status(t)
                status_str = "ok" if status is True else ("failed" if status is False else "unknown")
                msg = cls.get_message(t) or ""
                content += f"{t:<30} {status_str:<10} {cls.get(t, 3):<10} {cls.sum(t, 3):<10} {msg}\n"

        print(content)
        if filename:
            with open(filename, "w") as f:
                f.write(content)
Functions
benchmark classmethod
benchmark(sysinfo=True, tag=None, filename=None)

Prints out all timers in a convenient benchmark format for the current thread.

Parameters:

Name Type Description Default
sysinfo bool

If True, includes system information in the output. Defaults to True.

True
tag Optional[str]

Optional tag to include in the benchmark.

None
filename Optional[str]

Optional file path to write the benchmark results to.

None
Source code in src/cloudmesh/ai/common/stopwatch.py
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
@classmethod
def benchmark(cls, sysinfo: bool = True, tag: Optional[str] = None, filename: Optional[str] = None):
    """Prints out all timers in a convenient benchmark format for the current thread.

    Args:
        sysinfo: If True, includes system information in the output. Defaults to True.
        tag: Optional tag to include in the benchmark.
        filename: Optional file path to write the benchmark results to.
    """
    content = "\n--- Benchmark Results (Current Thread) ---\n"

    if sysinfo:
        info = ai_sys.systeminfo()
        content += "System Info:\n"
        for k, v in info.items():
            content += f"  {k}: {v}\n"
        content += "\n"

    timers = cls.keys()
    if not timers:
        content += "No timers found.\n"
    else:
        content += f"{'Timer':<30} {'Status':<10} {'Time (s)':<10} {'Sum (s)':<10} {'Msg'}\n"
        content += "-" * 70 + "\n"
        for t in timers:
            status = cls.get_status(t)
            status_str = "ok" if status is True else ("failed" if status is False else "unknown")
            msg = cls.get_message(t) or ""
            content += f"{t:<30} {status_str:<10} {cls.get(t, 3):<10} {cls.sum(t, 3):<10} {msg}\n"

    print(content)
    if filename:
        with open(filename, "w") as f:
            f.write(content)
clear classmethod
clear()

Clears all timers in the current thread.

Source code in src/cloudmesh/ai/common/stopwatch.py
289
290
291
292
293
294
295
296
297
298
299
@classmethod
def clear(cls):
    """Clears all timers in the current thread."""
    local = cls._get_local()
    local.timer_start.clear()
    local.timer_end.clear()
    local.timer_sum.clear()
    local.timer_status.clear()
    local.timer_elapsed.clear()
    local.timer_msg.clear()
    local.timer_values.clear()
debug_mode classmethod
debug_mode(value=True)

Enables or disables debug mode for timers.

Parameters:

Name Type Description Default
value bool

True to enable debug mode, False to disable it. Defaults to True.

True
Source code in src/cloudmesh/ai/common/stopwatch.py
71
72
73
74
75
76
77
78
79
80
81
82
@classmethod
def debug_mode(cls, value: bool = True):
    """Enables or disables debug mode for timers.

    Args:
        value: True to enable debug mode, False to disable it. Defaults to True.
    """
    cls.debug = value
    if value:
        print("StopWatch: Debug mode enabled.")
    else:
        print("StopWatch: Debug mode disabled.")
event classmethod
event(name, msg=None, values=None)

Adds an event with a given name, where start and stop is the same time.

Parameters:

Name Type Description Default
name str

The name of the event.

required
msg Optional[str]

Optional message to associate with the event.

None
values Any

Optional values to associate with the event.

None
Source code in src/cloudmesh/ai/common/stopwatch.py
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
@classmethod
def event(cls, name: str, msg: Optional[str] = None, values: Any = None):
    """Adds an event with a given name, where start and stop is the same time.

    Args:
        name: The name of the event.
        msg: Optional message to associate with the event.
        values: Optional values to associate with the event.
    """
    cls.start(name, values=values)
    cls.stop(name)
    local = cls._get_local()
    local.timer_end[name] = local.timer_start[name]
    if values:
        local.timer_values[name] = values
    if msg is not None:
        cls.message(name, str(msg))
    if cls.debug:
        print(f"Timer {name} event ...")
get classmethod
get(name, digits=4)

Returns the elapsed time of the timer.

Parameters:

Name Type Description Default
name str

The name of the timer.

required
digits int

Number of decimal places to round to. Defaults to 4.

4

Returns:

Type Description
Union[float, str, None]

The elapsed time as a float, "undefined" if not found, or None on error.

Source code in src/cloudmesh/ai/common/stopwatch.py
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
@classmethod
def get(cls, name: str, digits: int = 4) -> Union[float, str, None]:
    """Returns the elapsed time of the timer.

    Args:
        name: The name of the timer.
        digits: Number of decimal places to round to. Defaults to 4.

    Returns:
        The elapsed time as a float, "undefined" if not found, or None on error.
    """
    local = cls._get_local()
    if name in local.timer_end and local.timer_end[name] is not None:
        try:
            diff = local.timer_end[name] - local.timer_start[name]
            local.timer_elapsed[name] = round(diff, digits)
            return local.timer_elapsed[name]
        except Exception:
            return None
    return "undefined"
get_message classmethod
get_message(name)

Returns the message of the timer.

Parameters:

Name Type Description Default
name str

The name of the timer.

required

Returns:

Type Description
Optional[str]

The message associated with the timer, or None if not set.

Source code in src/cloudmesh/ai/common/stopwatch.py
144
145
146
147
148
149
150
151
152
153
154
@classmethod
def get_message(cls, name: str) -> Optional[str]:
    """Returns the message of the timer.

    Args:
        name: The name of the timer.

    Returns:
        The message associated with the timer, or None if not set.
    """
    return cls._get_local().timer_msg.get(name)
get_status classmethod
get_status(name)

Returns the status of the timer.

Parameters:

Name Type Description Default
name str

The name of the timer.

required

Returns:

Type Description
Any

The status value associated with the timer.

Source code in src/cloudmesh/ai/common/stopwatch.py
236
237
238
239
240
241
242
243
244
245
246
@classmethod
def get_status(cls, name: str) -> Any:
    """Returns the status of the timer.

    Args:
        name: The name of the timer.

    Returns:
        The status value associated with the timer.
    """
    return cls._get_local().timer_status.get(name)
keys classmethod
keys()

Returns the names of the timers.

Returns:

Type Description
List[str]

A list of timer names.

Source code in src/cloudmesh/ai/common/stopwatch.py
123
124
125
126
127
128
129
130
@classmethod
def keys(cls) -> List[str]:
    """Returns the names of the timers.

    Returns:
        A list of timer names.
    """
    return list(cls._get_local().timer_end.keys())
message classmethod
message(name, value)

Records a message for the timer.

Parameters:

Name Type Description Default
name str

The name of the timer.

required
value str

The message string to record.

required
Source code in src/cloudmesh/ai/common/stopwatch.py
156
157
158
159
160
161
162
163
164
@classmethod
def message(cls, name: str, value: str):
    """Records a message for the timer.

    Args:
        name: The name of the timer.
        value: The message string to record.
    """
    cls._get_local().timer_msg[name] = value
print classmethod
print(label, name)

Prints a timer with a label.

Parameters:

Name Type Description Default
label str

The label to print before the timer value.

required
name str

The name of the timer to print.

required
Source code in src/cloudmesh/ai/common/stopwatch.py
301
302
303
304
305
306
307
308
309
310
311
312
313
314
@classmethod
def print(cls, label: str, name: str):
    """Prints a timer with a label.

    Args:
        label: The label to print before the timer value.
        name: The name of the timer to print.
    """
    if cls.verbose:
        val = cls.get(name)
        if isinstance(val, float):
            print(f"{label} {val:.2f} s")
        else:
            print(f"{label} {val}")
start classmethod
start(name=None, values=None)

Starts a timer with the given name.

If name is None, it is automatically detected from the caller.

Parameters:

Name Type Description Default
name Optional[str]

The name of the timer.

None
values Any

Optional values to associate with the timer.

None
Source code in src/cloudmesh/ai/common/stopwatch.py
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
@classmethod
def start(cls, name: Optional[str] = None, values: Any = None):
    """Starts a timer with the given name.

    If name is None, it is automatically detected from the caller.

    Args:
        name: The name of the timer.
        values: Optional values to associate with the timer.
    """
    if name is None:
        name = cls._get_caller_name()

    local = cls._get_local()
    if cls.debug:
        print(f"Timer {name} started ...")
    if name not in local.timer_sum:
        local.timer_sum[name] = 0.0
    local.timer_start[name] = time.time()
    local.timer_end[name] = None
    local.timer_status[name] = None
    local.timer_msg[name] = None
    if values:
        local.timer_values[name] = values
status classmethod
status(name, value)

Records a status for the timer.

Parameters:

Name Type Description Default
name str

The name of the timer.

required
value Any

The status value to record.

required
Source code in src/cloudmesh/ai/common/stopwatch.py
132
133
134
135
136
137
138
139
140
141
142
@classmethod
def status(cls, name: str, value: Any):
    """Records a status for the timer.

    Args:
        name: The name of the timer.
        value: The status value to record.
    """
    if cls.debug:
        print(f"Timer {name} status {value}")
    cls._get_local().timer_status[name] = value
stop classmethod
stop(name=None, state=True, values=None)

Stops the timer with a given name.

If name is None, it is automatically detected from the caller.

Parameters:

Name Type Description Default
name Optional[str]

The name of the timer.

None
state Any

The final state of the timer (e.g., True for success).

True
values Any

Optional values to associate with the timer.

None
Source code in src/cloudmesh/ai/common/stopwatch.py
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
@classmethod
def stop(cls, name: Optional[str] = None, state: Any = True, values: Any = None):
    """Stops the timer with a given name.

    If name is None, it is automatically detected from the caller.

    Args:
        name: The name of the timer.
        state: The final state of the timer (e.g., True for success).
        values: Optional values to associate with the timer.
    """
    if name is None:
        name = cls._get_caller_name()

    local = cls._get_local()
    local.timer_end[name] = time.time()
    local.timer_sum[name] = (
        local.timer_sum[name] + local.timer_end[name] - local.timer_start[name]
    )
    local.timer_status[name] = state
    if values:
        local.timer_values[name] = values
    if cls.debug:
        print(f"Timer {name} stopped ...")
sum classmethod
sum(name, digits=4)

Returns the sum of the timer if used multiple times.

Parameters:

Name Type Description Default
name str

The name of the timer.

required
digits int

Number of decimal places to round to. Defaults to 4.

4

Returns:

Type Description
Union[float, str, None]

The total elapsed time as a float, "undefined" if not found, or None on error.

Source code in src/cloudmesh/ai/common/stopwatch.py
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
@classmethod
def sum(cls, name: str, digits: int = 4) -> Union[float, str, None]:
    """Returns the sum of the timer if used multiple times.

    Args:
        name: The name of the timer.
        digits: Number of decimal places to round to. Defaults to 4.

    Returns:
        The total elapsed time as a float, "undefined" if not found, or None on error.
    """
    local = cls._get_local()
    if name in local.timer_end:
        try:
            diff = local.timer_sum[name]
            return round(diff, digits)
        except Exception:
            return None
    return "undefined"
timer classmethod
timer(name=None, values=None)

Context manager to time a block of code.

Example

with StopWatch.timer("my_operation"): do_work()

Parameters:

Name Type Description Default
name Optional[str]

The name of the timer. If None, it is automatically detected.

None
values Any

Optional values to associate with the timer.

None
Source code in src/cloudmesh/ai/common/stopwatch.py
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
@classmethod
@contextmanager
def timer(cls, name: Optional[str] = None, values: Any = None) -> Generator[Optional[float], None, None]:
    """Context manager to time a block of code.

    Example:
        with StopWatch.timer("my_operation"):
            do_work()

    Args:
        name: The name of the timer. If None, it is automatically detected.
        values: Optional values to associate with the timer.
    """
    cls.start(name, values=values)
    try:
        yield None
    finally:
        cls.stop(name)
StopWatchBlock

Context manager for StopWatch.

Attributes:

Name Type Description
name

The name of the timer.

data

Optional data to associate with the timer.

log

The log destination (file path or stream).

is_file

Boolean indicating if the log is a file.

start_time

The time when the block was entered.

Source code in src/cloudmesh/ai/common/stopwatch.py
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
class StopWatchBlock:
    """Context manager for StopWatch.

    Attributes:
        name: The name of the timer.
        data: Optional data to associate with the timer.
        log: The log destination (file path or stream).
        is_file: Boolean indicating if the log is a file.
        start_time: The time when the block was entered.
    """
    def __init__(self, name: str, data: Any = None, log=sys.stdout, mode: str = "w"):
        """Initializes the StopWatchBlock.

        Args:
            name: The name of the timer.
            data: Optional data to associate with the timer.
            log: The log destination. Defaults to sys.stdout.
            mode: The mode to open the log file in. Defaults to "w".
        """
        self.name = name
        self.data = data
        self.log = log
        self.is_file = False
        self.start_time = None
        if isinstance(log, str):
            self.is_file = True
            self.log = open(log, mode)

    def __enter__(self):
        """Starts the timer and returns the current elapsed time.

        Returns:
            The current elapsed time of the timer.
        """
        StopWatch.start(self.name)
        self.start_time = datetime.datetime.now()
        return StopWatch.get(self.name)

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Stops the timer and logs the result.

        Args:
            exc_type: The type of the exception that occurred.
            exc_val: The instance of the exception that occurred.
            exc_tb: The traceback of the exception that occurred.
        """
        self.stop_time = datetime.datetime.now()
        StopWatch.stop(self.name)
        entry = StopWatch.get(self.name)

        msg = f"# {self.name}, {entry}, {self.start_time}, {self.stop_time}"
        if self.data:
            msg += f", {self.data}"

        print(msg, file=self.log)
        if self.is_file:
            self.log.close()
Functions
__enter__
__enter__()

Starts the timer and returns the current elapsed time.

Returns:

Type Description

The current elapsed time of the timer.

Source code in src/cloudmesh/ai/common/stopwatch.py
379
380
381
382
383
384
385
386
387
def __enter__(self):
    """Starts the timer and returns the current elapsed time.

    Returns:
        The current elapsed time of the timer.
    """
    StopWatch.start(self.name)
    self.start_time = datetime.datetime.now()
    return StopWatch.get(self.name)
__exit__
__exit__(exc_type, exc_val, exc_tb)

Stops the timer and logs the result.

Parameters:

Name Type Description Default
exc_type

The type of the exception that occurred.

required
exc_val

The instance of the exception that occurred.

required
exc_tb

The traceback of the exception that occurred.

required
Source code in src/cloudmesh/ai/common/stopwatch.py
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
def __exit__(self, exc_type, exc_val, exc_tb):
    """Stops the timer and logs the result.

    Args:
        exc_type: The type of the exception that occurred.
        exc_val: The instance of the exception that occurred.
        exc_tb: The traceback of the exception that occurred.
    """
    self.stop_time = datetime.datetime.now()
    StopWatch.stop(self.name)
    entry = StopWatch.get(self.name)

    msg = f"# {self.name}, {entry}, {self.start_time}, {self.stop_time}"
    if self.data:
        msg += f", {self.data}"

    print(msg, file=self.log)
    if self.is_file:
        self.log.close()
__init__
__init__(name, data=None, log=sys.stdout, mode='w')

Initializes the StopWatchBlock.

Parameters:

Name Type Description Default
name str

The name of the timer.

required
data Any

Optional data to associate with the timer.

None
log

The log destination. Defaults to sys.stdout.

stdout
mode str

The mode to open the log file in. Defaults to "w".

'w'
Source code in src/cloudmesh/ai/common/stopwatch.py
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
def __init__(self, name: str, data: Any = None, log=sys.stdout, mode: str = "w"):
    """Initializes the StopWatchBlock.

    Args:
        name: The name of the timer.
        data: Optional data to associate with the timer.
        log: The log destination. Defaults to sys.stdout.
        mode: The mode to open the log file in. Defaults to "w".
    """
    self.name = name
    self.data = data
    self.log = log
    self.is_file = False
    self.start_time = None
    if isinstance(log, str):
        self.is_file = True
        self.log = open(log, mode)

Functions

benchmark
benchmark(func)

Decorator to benchmark a function.

Parameters:

Name Type Description Default
func

The function to be benchmarked.

required

Returns:

Type Description

A wrapper function that starts and stops a StopWatch timer.

Source code in src/cloudmesh/ai/common/stopwatch.py
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
def benchmark(func):
    """Decorator to benchmark a function.

    Args:
        func: The function to be benchmarked.

    Returns:
        A wrapper function that starts and stops a StopWatch timer.
    """
    @rename(func.__name__)
    def wrapper(*args, **kwargs):
        StopWatch.start(func.__name__)
        result = func(*args, **kwargs)
        StopWatch.stop(func.__name__)
        return result
    return wrapper
rename
rename(newname)

Decorator to rename a function.

Parameters:

Name Type Description Default
newname

The new name to assign to the function.

required

Returns:

Type Description

A decorator function.

Source code in src/cloudmesh/ai/common/stopwatch.py
16
17
18
19
20
21
22
23
24
25
26
27
28
def rename(newname):
    """Decorator to rename a function.

    Args:
        newname: The new name to assign to the function.

    Returns:
        A decorator function.
    """
    def decorator(f):
        f.__name__ = newname
        return f
    return decorator

Modules

sudo

Classes

Sudo

A utility class for executing commands with sudo privileges and performing file operations.

Methods:

Name Description
password

"): Prompt the user for the sudo password.

execute

Execute the specified command with sudo. Args: command (list or str): The command to run. decode (bool, optional): If True, decode the output from bytes to ASCII. debug (bool, optional): If True, print command execution details. msg (str, optional): Message to print before executing the command. Returns: subprocess.CompletedProcess: The result of the command execution.

readfile

Read the content of the file with sudo privileges and return the result. Args: filename (str): The filename. split (bool, optional): If True, return a list of lines. trim (bool, optional): If True, trim trailing whitespace. decode (bool, optional): If True, decode the output from bytes to ASCII. Returns: str or list: The content of the file.

writefile

Write the content to the specified file with sudo privileges. Args: filename (str): The filename. content (str): The content to write. append (bool, optional): If True, append the content at the end; otherwise, overwrite the file. Returns: str: The output created by the write process.

Source code in src/cloudmesh/ai/common/sudo.py
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
class Sudo:
    """
     A utility class for executing commands with sudo privileges and performing file operations.

    Methods:
        password(msg="sudo password: "):
            Prompt the user for the sudo password.

        execute(command, decode=True, debug=False, msg=None):
            Execute the specified command with sudo.
            Args:
                command (list or str): The command to run.
                decode (bool, optional): If True, decode the output from bytes to ASCII.
                debug (bool, optional): If True, print command execution details.
                msg (str, optional): Message to print before executing the command.
            Returns:
                subprocess.CompletedProcess: The result of the command execution.

        readfile(filename, split=False, trim=False, decode=True):
            Read the content of the file with sudo privileges and return the result.
            Args:
                filename (str): The filename.
                split (bool, optional): If True, return a list of lines.
                trim (bool, optional): If True, trim trailing whitespace.
                decode (bool, optional): If True, decode the output from bytes to ASCII.
            Returns:
                str or list: The content of the file.

        writefile(filename, content, append=False):
            Write the content to the specified file with sudo privileges.
            Args:
                filename (str): The filename.
                content (str): The content to write.
                append (bool, optional): If True, append the content at the end;
                                          otherwise, overwrite the file.
            Returns:
                str: The output created by the write process.
    """

    @staticmethod
    def password(msg="sudo password: "):
        """Prompt the user for the sudo password.

        Args:
            msg (str, optional): The message to display when prompting for the password.
        """
        try:
            # Use subprocess.run without capturing output to allow sudo to prompt the user via TTY
            result = subprocess.run(
                ["sudo", "-p", msg, "echo", ""],
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL
            )
            return result.returncode
        except Exception:
            return 1

    @staticmethod
    def expire():
        """Expires the password"""
        os.system("sudo -k")

    @staticmethod
    def execute(command, decode="True", debug=False, msg=None):
        """Execute the specified command with sudo.

        Args:
            command (list or str): The command to run.
            decode (bool, optional): If True, decode the output from bytes to ASCII.
            debug (bool, optional): If True, print command execution details.
            msg (str, optional): Message to print before executing the command.

        Returns:
            subprocess.CompletedProcess: The result of the command execution.
        """
        Sudo.password()
        if type(command) == str:
            sudo_command = "sudo " + command
            sudo_command = sudo_command.split(" ")
        else:
            sudo_command = ["sudo"] + command
        if msg is None:
            pass
        elif msg == "command":
            print("Executing:", " ".join(sudo_command))
        else:
            print("Executing:", " ".join(msg))
        os.system("sync")
        result = subprocess.run(sudo_command, capture_output=True)
        os.system("sync")
        if decode:
            result.stdout = result.stdout.decode("ascii")
            result.stderr = result.stderr.decode("ascii")

        if debug:
            banner("stdout")
            print(result.stdout)
            banner("stderr")
            print(result.stderr)
            banner("result")
            print(result)
        return result

    @staticmethod
    def readfile(filename, split=False, trim=False, decode=True):
        """Read the content of the file with sudo privileges and return the result.

        Args:
            filename (str): The filename.
            split (bool, optional): If True, return a list of lines.
            trim (bool, optional): If True, trim trailing whitespace.
            decode (bool, optional): If True, decode the output from bytes to ASCII.

        Returns:
            str or list: The content of the file.
        """
        Sudo.password()
        os.system("sync")
        result = Sudo.execute(f"cat {filename}", decode=decode)

        content = result.stdout

        if trim:
            content = content.rstrip()

        if split:
            content = content.splitlines()

        return content

    @staticmethod
    def writefile(filename, content, append=False):
        """Write the content to the specified file with sudo privileges.

        Args:
            filename (str): The filename.
            content (str): The content to write.
            append (bool, optional): If True, append the content at the end; otherwise, overwrite the file.

        Returns:
            str: The output created by the write process.
        """

        os.system("sync")
        Sudo.password()
        if append:
            content = Sudo.readfile(filename, split=False, decode=True) + content

        os.system(f"echo '{content}' | sudo cp /dev/stdin {filename}")
        os.system("sync")

        return content
Functions
execute staticmethod
execute(command, decode='True', debug=False, msg=None)

Execute the specified command with sudo.

Parameters:

Name Type Description Default
command list or str

The command to run.

required
decode bool

If True, decode the output from bytes to ASCII.

'True'
debug bool

If True, print command execution details.

False
msg str

Message to print before executing the command.

None

Returns:

Type Description

subprocess.CompletedProcess: The result of the command execution.

Source code in src/cloudmesh/ai/common/sudo.py
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
@staticmethod
def execute(command, decode="True", debug=False, msg=None):
    """Execute the specified command with sudo.

    Args:
        command (list or str): The command to run.
        decode (bool, optional): If True, decode the output from bytes to ASCII.
        debug (bool, optional): If True, print command execution details.
        msg (str, optional): Message to print before executing the command.

    Returns:
        subprocess.CompletedProcess: The result of the command execution.
    """
    Sudo.password()
    if type(command) == str:
        sudo_command = "sudo " + command
        sudo_command = sudo_command.split(" ")
    else:
        sudo_command = ["sudo"] + command
    if msg is None:
        pass
    elif msg == "command":
        print("Executing:", " ".join(sudo_command))
    else:
        print("Executing:", " ".join(msg))
    os.system("sync")
    result = subprocess.run(sudo_command, capture_output=True)
    os.system("sync")
    if decode:
        result.stdout = result.stdout.decode("ascii")
        result.stderr = result.stderr.decode("ascii")

    if debug:
        banner("stdout")
        print(result.stdout)
        banner("stderr")
        print(result.stderr)
        banner("result")
        print(result)
    return result
expire staticmethod
expire()

Expires the password

Source code in src/cloudmesh/ai/common/sudo.py
64
65
66
67
@staticmethod
def expire():
    """Expires the password"""
    os.system("sudo -k")
password staticmethod
password(msg='sudo password: ')

Prompt the user for the sudo password.

Parameters:

Name Type Description Default
msg str

The message to display when prompting for the password.

'sudo password: '
Source code in src/cloudmesh/ai/common/sudo.py
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
@staticmethod
def password(msg="sudo password: "):
    """Prompt the user for the sudo password.

    Args:
        msg (str, optional): The message to display when prompting for the password.
    """
    try:
        # Use subprocess.run without capturing output to allow sudo to prompt the user via TTY
        result = subprocess.run(
            ["sudo", "-p", msg, "echo", ""],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL
        )
        return result.returncode
    except Exception:
        return 1
readfile staticmethod
readfile(filename, split=False, trim=False, decode=True)

Read the content of the file with sudo privileges and return the result.

Parameters:

Name Type Description Default
filename str

The filename.

required
split bool

If True, return a list of lines.

False
trim bool

If True, trim trailing whitespace.

False
decode bool

If True, decode the output from bytes to ASCII.

True

Returns:

Type Description

str or list: The content of the file.

Source code in src/cloudmesh/ai/common/sudo.py
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
@staticmethod
def readfile(filename, split=False, trim=False, decode=True):
    """Read the content of the file with sudo privileges and return the result.

    Args:
        filename (str): The filename.
        split (bool, optional): If True, return a list of lines.
        trim (bool, optional): If True, trim trailing whitespace.
        decode (bool, optional): If True, decode the output from bytes to ASCII.

    Returns:
        str or list: The content of the file.
    """
    Sudo.password()
    os.system("sync")
    result = Sudo.execute(f"cat {filename}", decode=decode)

    content = result.stdout

    if trim:
        content = content.rstrip()

    if split:
        content = content.splitlines()

    return content
writefile staticmethod
writefile(filename, content, append=False)

Write the content to the specified file with sudo privileges.

Parameters:

Name Type Description Default
filename str

The filename.

required
content str

The content to write.

required
append bool

If True, append the content at the end; otherwise, overwrite the file.

False

Returns:

Name Type Description
str

The output created by the write process.

Source code in src/cloudmesh/ai/common/sudo.py
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
@staticmethod
def writefile(filename, content, append=False):
    """Write the content to the specified file with sudo privileges.

    Args:
        filename (str): The filename.
        content (str): The content to write.
        append (bool, optional): If True, append the content at the end; otherwise, overwrite the file.

    Returns:
        str: The output created by the write process.
    """

    os.system("sync")
    Sudo.password()
    if append:
        content = Sudo.readfile(filename, split=False, decode=True) + content

    os.system(f"echo '{content}' | sudo cp /dev/stdin {filename}")
    os.system("sync")

    return content

Functions

sys

System utility functions for cloudmesh-ai. Provides tools for OS detection, hardware information retrieval, and real-time system metrics collection.

Functions

get_container_info
get_container_info()

Detects if the system is running inside a container (Docker, Kubernetes).

Returns:

Type Description
Dict[str, Any]

A dictionary with container type and metadata.

Source code in src/cloudmesh/ai/common/sys.py
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
def get_container_info() -> Dict[str, Any]:
    """Detects if the system is running inside a container (Docker, Kubernetes).

    Returns:
        A dictionary with container type and metadata.
    """
    container_data = {"container.present": False}

    # 1. Docker detection
    if Path("/.dockerenv").exists():
        container_data["container.present"] = True
        container_data["container.type"] = "docker"

    # 2. Cgroup detection (Linux)
    if os_is_linux():
        try:
            cgroup_content = Path("/proc/1/cgroup").read_text()
            if "docker" in cgroup_content.lower():
                container_data["container.present"] = True
                container_data["container.type"] = "docker"
            elif "kubepods" in cgroup_content.lower():
                container_data["container.present"] = True
                container_data["container.type"] = "kubernetes"
        except OSError:
            pass

    # 3. Kubernetes specific detection
    if os.environ.get("KUBERNETES_SERVICE_HOST"):
        container_data["container.present"] = True
        container_data["container.type"] = "kubernetes"

        # Try to extract namespace
        ns_path = Path("/var/run/secrets/kubernetes.io/serviceaccount/namespace")
        if ns_path.exists():
            container_data["container.namespace"] = ns_path.read_text().strip()

    return container_data
get_cpu_description
get_cpu_description()

Safely retrieves the CPU model name across platforms.

Returns:

Type Description
str

The CPU model description as a string.

Source code in src/cloudmesh/ai/common/sys.py
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
def get_cpu_description() -> str:
    """Safely retrieves the CPU model name across platforms.

    Returns:
        The CPU model description as a string.
    """
    plat = get_platform()
    try:
        if plat == "macos":
            return subprocess.check_output(["sysctl", "-n", "machdep.cpu.brand_string"]).decode().strip()
        elif plat == "windows":
            return platform.processor()
        else:
            content = Path("/proc/cpuinfo").read_text()
            for line in content.splitlines():
                if "model name" in line or "Hardware" in line:
                    return line.split(":", 1)[1].strip()
    except (subprocess.SubprocessError, OSError):
        return "Unknown Processor"
    return "Unknown"
get_cpu_metrics
get_cpu_metrics()

Retrieves real-time CPU utilization and load averages.

Returns:

Type Description
Dict[str, Any]

A dictionary containing 'cpu.utilization_overall', 'cpu.utilization_per_core',

Dict[str, Any]

and 'cpu.load_avg'.

Source code in src/cloudmesh/ai/common/sys.py
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
def get_cpu_metrics() -> Dict[str, Any]:
    """Retrieves real-time CPU utilization and load averages.

    Returns:
        A dictionary containing 'cpu.utilization_overall', 'cpu.utilization_per_core', 
        and 'cpu.load_avg'.
    """
    try:
        return {
            "cpu.utilization_overall": f"{psutil.cpu_percent(interval=None)}%",
            "cpu.utilization_per_core": [f"{x}%" for x in psutil.cpu_percent(interval=None, percpu=True)],
            "cpu.load_avg": psutil.getloadavg() if hasattr(psutil, "getloadavg") else None,
        }
    except Exception:
        return {}
get_disk_metrics
get_disk_metrics()

Retrieves real-time disk usage and I/O statistics for the root partition.

Returns:

Type Description
Dict[str, Any]

A dictionary containing total, used, and free space, as well as

Dict[str, Any]

read/write counts and bytes.

Source code in src/cloudmesh/ai/common/sys.py
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
def get_disk_metrics() -> Dict[str, Any]:
    """Retrieves real-time disk usage and I/O statistics for the root partition.

    Returns:
        A dictionary containing total, used, and free space, as well as 
        read/write counts and bytes.
    """
    try:
        usage = psutil.disk_usage("/")
        io = psutil.disk_io_counters()
        return {
            "disk.total": humanize.naturalsize(usage.total, binary=True),
            "disk.used": humanize.naturalsize(usage.used, binary=True),
            "disk.free": humanize.naturalsize(usage.free, binary=True),
            "disk.percent": f"{usage.percent}%",
            "disk.read_count": io.read_count if io else None,
            "disk.write_count": io.write_count if io else None,
            "disk.read_bytes": humanize.naturalsize(io.read_bytes, binary=True) if io else None,
            "disk.write_bytes": humanize.naturalsize(io.write_bytes, binary=True) if io else None,
        }
    except Exception:
        return {}
get_disk_read_speed
get_disk_read_speed(path, size_mb=100)

Measures the read speed of a file to profile disk throughput.

Parameters:

Name Type Description Default
path str

Path to the file to read.

required
size_mb int

Amount of data to read in megabytes. Defaults to 100.

100

Returns:

Type Description
Optional[str]

The read speed as a string (e.g., "150.25 MB/s"), or None if an error occurs.

Source code in src/cloudmesh/ai/common/sys.py
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
def get_disk_read_speed(path: str, size_mb: int = 100) -> Optional[str]:
    """Measures the read speed of a file to profile disk throughput.

    Args:
        path: Path to the file to read.
        size_mb: Amount of data to read in megabytes. Defaults to 100.

    Returns:
        The read speed as a string (e.g., "150.25 MB/s"), or None if an error occurs.
    """
    try:
        file_path = Path(path)
        if not file_path.exists():
            return None

        import time
        start_time = time.perf_counter()
        with open(file_path, "rb") as f:
            # Read a specific amount of data to avoid loading huge files entirely into RAM
            _ = f.read(size_mb * 1024 * 1024)
        end_time = time.perf_counter()

        duration = end_time - start_time
        speed = size_mb / duration
        return f"{speed:.2f} MB/s"
    except Exception:
        return None
get_gpu_info
get_gpu_info()

Attempts to retrieve GPU information from multiple vendors.

Returns:

Type Description
Dict[str, Any]

A dictionary with VRAM, temperature, and power draw if available,

Dict[str, Any]

otherwise {'gpu.present': False}.

Source code in src/cloudmesh/ai/common/sys.py
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
def get_gpu_info() -> Dict[str, Any]:
    """Attempts to retrieve GPU information from multiple vendors.

    Returns:
        A dictionary with VRAM, temperature, and power draw if available, 
        otherwise {'gpu.present': False}.
    """
    # 1. Try NVIDIA
    info = _get_nvidia_gpu_info()
    if info["gpu.present"]:
        return info

    # 2. Try AMD
    info = _get_amd_gpu_info()
    if info["gpu.present"]:
        return info

    # 3. Try Apple (macOS only)
    if os_is_mac():
        info = _get_apple_gpu_info()
        if info["gpu.present"]:
            return info

    # 4. Try Intel (Linux only)
    if os_is_linux():
        info = _get_intel_gpu_info()
        if info["gpu.present"]:
            return info

    return {"gpu.present": False}
get_memory_metrics
get_memory_metrics()

Retrieves detailed real-time memory and swap usage.

Returns:

Type Description
Dict[str, Any]

A dictionary containing utilization percentages and human-readable

Dict[str, Any]

sizes for available, used, and swap memory.

Source code in src/cloudmesh/ai/common/sys.py
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
def get_memory_metrics() -> Dict[str, Any]:
    """Retrieves detailed real-time memory and swap usage.

    Returns:
        A dictionary containing utilization percentages and human-readable 
        sizes for available, used, and swap memory.
    """
    try:
        vm = psutil.virtual_memory()
        sw = psutil.swap_memory()
        return {
            "mem.utilization": f"{vm.percent}%",
            "mem.available": humanize.naturalsize(vm.available, binary=True),
            "mem.used": humanize.naturalsize(vm.used, binary=True),
            "swap.total": humanize.naturalsize(sw.total, binary=True),
            "swap.used": humanize.naturalsize(sw.used, binary=True),
            "swap.percent": f"{sw.percent}%",
        }
    except Exception:
        return {}
get_network_info
get_network_info()

Detects high-speed network interfaces (InfiniBand, RoCE) on Linux.

Returns:

Type Description
Dict[str, Any]

A dictionary indicating if high-speed networking is present and

Dict[str, Any]

listing the interfaces.

Source code in src/cloudmesh/ai/common/sys.py
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
def get_network_info() -> Dict[str, Any]:
    """Detects high-speed network interfaces (InfiniBand, RoCE) on Linux.

    Returns:
        A dictionary indicating if high-speed networking is present and 
        listing the interfaces.
    """
    net_data = {"net.high_speed": False, "net.interfaces": []}
    if os_is_linux():
        try:
            net_path = Path("/sys/class/net")
            if net_path.exists():
                for iface in net_path.iterdir():
                    # Check for InfiniBand/RoCE by looking for 'infiniband' in the device class or type
                    # A common way is checking /sys/class/net/<iface>/type or looking for ib* interfaces
                    if "ib" in iface.name or (iface / "device").exists():
                        # Heuristic: check if it's an InfiniBand device
                        # In many systems, IB devices have a specific type or are named ib0, ib1...
                        if "ib" in iface.name:
                            net_data["net.high_speed"] = True
                            net_data["net.interfaces"].append(iface.name)
        except OSError:
            pass
    return net_data
get_numa_info
get_numa_info()

Detects NUMA nodes on Linux systems.

Returns:

Type Description
Dict[str, Any]

A dictionary containing NUMA presence and node details.

Source code in src/cloudmesh/ai/common/sys.py
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
def get_numa_info() -> Dict[str, Any]:
    """Detects NUMA nodes on Linux systems.

    Returns:
        A dictionary containing NUMA presence and node details.
    """
    numa_data = {"numa.present": False}
    if os_is_linux():
        try:
            node_path = Path("/sys/devices/system/node")
            if node_path.exists():
                nodes = [d.name for d in node_path.iterdir() if d.name.startswith("node")]
                numa_data.update({
                    "numa.present": True,
                    "numa.count": len(nodes),
                    "numa.nodes": nodes,
                })
        except OSError:
            pass
    return numa_data
get_platform
get_platform()

Returns a simplified string representing the OS platform.

Returns:

Type Description
str

A string representing the platform (e.g., 'macos', 'windows', 'raspberry').

Source code in src/cloudmesh/ai/common/sys.py
102
103
104
105
106
107
108
109
110
111
112
113
114
def get_platform() -> str:
    """Returns a simplified string representing the OS platform.

    Returns:
        A string representing the platform (e.g., 'macos', 'windows', 'raspberry').
    """
    if os_is_mac():
        return "macos"
    if os_is_windows():
        return "windows"
    if os_is_pi():
        return "raspberry"
    return sys.platform
get_thermal_info
get_thermal_info()

Retrieves system thermal information across platforms.

Returns:

Type Description
Dict[str, Any]

A dictionary with CPU and GPU temperatures.

Source code in src/cloudmesh/ai/common/sys.py
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
def get_thermal_info() -> Dict[str, Any]:
    """Retrieves system thermal information across platforms.

    Returns:
        A dictionary with CPU and GPU temperatures.
    """
    thermal_data = {"thermal.present": False, "cpu.temp": None}

    if os_is_linux():
        try:
            # Try thermal_zone first
            zones = list(Path("/sys/class/thermal").glob("thermal_zone*"))
            if zones:
                # Usually zone0 is the package temp
                temp_raw = Path(zones[0]) / "temp"
                if temp_raw.exists():
                    temp_c = int(temp_raw.read_text().strip()) / 1000.0
                    thermal_data["cpu.temp"] = f"{temp_c:.1f}°C"
                    thermal_data["thermal.present"] = True

            # Try hwmon for more detailed sensors if zone failed or for backup
            if not thermal_data["thermal.present"]:
                hwmon_paths = list(Path("/sys/class/hwmon").glob("hwmon*"))
                for path in hwmon_paths:
                    temp_files = list(path.glob("temp*_input"))
                    if temp_files:
                        temp_c = int(temp_files[0].read_text().strip()) / 1000.0
                        thermal_data["cpu.temp"] = f"{temp_c:.1f}°C"
                        thermal_data["thermal.present"] = True
                        break
        except (OSError, ValueError):
            pass

    elif os_is_mac():
        try:
            # macOS doesn't have a simple /sys path. 
            # We can try sysctl, though it's often restricted on Apple Silicon.
            # Use stderr=subprocess.DEVNULL to suppress "unknown oid" messages
            output = subprocess.check_output(
                ["sysctl", "-n", "machdep.cpu.temperature"], 
                stderr=subprocess.DEVNULL
            ).decode().strip()
            if output:
                thermal_data["cpu.temp"] = f"{output}°C"
                thermal_data["thermal.present"] = True
        except (subprocess.SubprocessError, OSError):
            pass

    return thermal_data
has_window_manager
has_window_manager()

Checks if a GUI environment is likely available.

Returns:

Type Description
bool

True if a window manager is likely present, False otherwise.

Source code in src/cloudmesh/ai/common/sys.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
def has_window_manager() -> bool:
    """Checks if a GUI environment is likely available.

    Returns:
        True if a window manager is likely present, False otherwise.
    """
    if os_is_mac() or os_is_windows():
        return True
    return any(env in os.environ for env in [
        "DISPLAY",
        "WAYLAND_DISPLAY",
        "GNOME_TERMINAL_SCREEN",
        "GNOME_TERMINAL_SERVICE"
    ])
os_is_linux
os_is_linux()

Checks if the os is linux (excluding Raspberry Pi).

Returns:

Type Description
bool

True if the OS is Linux and not Raspbian, False otherwise.

Source code in src/cloudmesh/ai/common/sys.py
53
54
55
56
57
58
59
def os_is_linux() -> bool:
    """Checks if the os is linux (excluding Raspberry Pi).

    Returns:
        True if the OS is Linux and not Raspbian, False otherwise.
    """
    return platform.system() == "Linux" and "raspbian" not in _get_os_release_data()
os_is_mac
os_is_mac()

Checks if the operating system is macOS.

Returns:

Type Description
bool

True if the OS is macOS, False otherwise.

Source code in src/cloudmesh/ai/common/sys.py
45
46
47
48
49
50
51
def os_is_mac() -> bool:
    """Checks if the operating system is macOS.

    Returns:
        True if the OS is macOS, False otherwise.
    """
    return platform.system() == "Darwin"
os_is_pi
os_is_pi()

Checks if the os is Raspberry OS.

Returns:

Type Description
bool

True if the OS is Raspbian, False otherwise.

Source code in src/cloudmesh/ai/common/sys.py
61
62
63
64
65
66
67
def os_is_pi() -> bool:
    """Checks if the os is Raspberry OS.

    Returns:
        True if the OS is Raspbian, False otherwise.
    """
    return platform.system() == "Linux" and "raspbian" in _get_os_release_data()
os_is_windows
os_is_windows()

Checks if the operating system is Windows.

Returns:

Type Description
bool

True if the OS is Windows, False otherwise.

Source code in src/cloudmesh/ai/common/sys.py
37
38
39
40
41
42
43
def os_is_windows() -> bool:
    """Checks if the operating system is Windows.

    Returns:
        True if the OS is Windows, False otherwise.
    """
    return platform.system() == "Windows"
resolve_package_path
resolve_package_path(anchor, relative_path)

Resolves a path relative to a given anchor file.

Parameters:

Name Type Description Default
anchor str

The anchor file path.

required
relative_path str

The relative path to resolve.

required

Returns:

Type Description
Path

The resolved absolute Path.

Source code in src/cloudmesh/ai/common/sys.py
488
489
490
491
492
493
494
495
496
497
498
def resolve_package_path(anchor: str, relative_path: str) -> Path:
    """Resolves a path relative to a given anchor file.

    Args:
        anchor: The anchor file path.
        relative_path: The relative path to resolve.

    Returns:
        The resolved absolute Path.
    """
    return Path(anchor).parent / relative_path
sys_user
sys_user()

Returns the current username with Colab and Root detection.

Returns:

Type Description
str

The detected username as a string.

Source code in src/cloudmesh/ai/common/sys.py
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
def sys_user() -> str:
    """Returns the current username with Colab and Root detection.

    Returns:
        The detected username as a string.
    """
    if "COLAB_GPU" in os.environ:
        return "colab"
    try:
        user = getpass.getuser()
        if user == "root" or os.environ.get("HOME") == "/root":
            return "root"
        return user
    except Exception:
        return os.environ.get("USER", os.environ.get("USERNAME", "None"))
systeminfo
systeminfo(info=None, user=None, node=None, realtime=False)

Collects comprehensive system metadata into a dictionary.

Parameters:

Name Type Description Default
info Optional[Dict[str, Any]]

Optional dictionary of additional information to merge into the result.

None
user Optional[str]

Optional override for the current system user.

None
node Optional[str]

Optional override for the system node name.

None
realtime bool

If True, includes real-time CPU, memory, and disk utilization metrics.

False

Returns:

Type Description
Dict[str, Any]

A dictionary containing system hardware, OS, and (optionally) real-time performance data.

Source code in src/cloudmesh/ai/common/sys.py
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
def systeminfo(info: Optional[Dict[str, Any]] = None, user: Optional[str] = None, node: Optional[str] = None, realtime: bool = False) -> Dict[str, Any]:
    """Collects comprehensive system metadata into a dictionary.

    Args:
        info: Optional dictionary of additional information to merge into the result.
        user: Optional override for the current system user.
        node: Optional override for the system node name.
        realtime: If True, includes real-time CPU, memory, and disk utilization metrics.

    Returns:
        A dictionary containing system hardware, OS, and (optionally) real-time performance data.
    """
    uname = platform.uname()
    mem = psutil.virtual_memory()

    data = OrderedDict({
        "cpu": get_cpu_description(),
        "cpu_count": multiprocessing.cpu_count(),
        "cpu_cores": psutil.cpu_count(logical=False) or "unknown",
        "cpu_threads": psutil.cpu_count(logical=True) or "unknown",
        "uname.system": uname.system,
        "uname.node": node or uname.node,
        "uname.release": uname.release,
        "uname.version": uname.version,
        "uname.machine": uname.machine,
        "uname.processor": uname.processor,
        "sys.platform": sys.platform,
        "python": sys.version,
        "python.version": sys.version.split(" ", 1)[0],
        "python.pip": pip.__version__,
        "user": user or sys_user(),
        "mem.percent": f"{mem.percent}%",
    })

    try:
        data["frequency"] = psutil.cpu_freq().current
    except Exception:
        data["frequency"] = None

    mem_fields = ["total", "available", "used", "free", "active", "inactive", "wired"]
    for attr in mem_fields:
        val = getattr(mem, attr, None)
        if val is not None:
            data[f"mem.{attr}"] = humanize.naturalsize(val, binary=True)

    # GPU Info
    data.update(get_gpu_info())

    # Thermal Info
    data.update(get_thermal_info())

    # NUMA Info
    data.update(get_numa_info())

    # Network Info
    data.update(get_network_info())

    # Container Info
    data.update(get_container_info())

    if os_is_mac():
        data["platform.version"] = platform.mac_ver()[0]
    elif os_is_windows():
        data["platform.version"] = platform.win32_ver()
    else:
        try:
            for path in Path("/etc").glob("*release"):
                for line in path.read_text().splitlines():
                    if "=" in line:
                        k, v = line.split("=", 1)
                        clean_k = k.strip().replace(" ", "_")
                        clean_v = v.strip().strip('"\'')
                        data[clean_k] = clean_v
        except OSError:
            data["platform.version"] = uname.version

    if realtime:
        data.update(get_cpu_metrics())
        data.update(get_memory_metrics())
        data.update(get_disk_metrics())

    if info:
        data.update(info)

    data["date"] = str(datetime.datetime.now())
    return dict(data)

telemetry

Telemetry utility for cloudmesh-ai components. Provides a standardized way to emit structured performance and status data.

Classes

AsyncTelemetry

Bases: Telemetry

Asynchronous version of Telemetry. Prevents telemetry I/O from blocking the main execution thread by offloading backend emission to a thread pool.

Source code in src/cloudmesh/ai/common/telemetry.py
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
class AsyncTelemetry(Telemetry):
    """
    Asynchronous version of Telemetry.
    Prevents telemetry I/O from blocking the main execution thread by 
    offloading backend emission to a thread pool.
    """

    async def emit(
        self, 
        status: str, 
        metrics: Optional[Dict[str, Any]] = None, 
        message: Optional[str] = None,
        stdout: bool = False,
        **kwargs: Any
    ) -> None:
        """Asynchronously emits a structured telemetry record.

        Args:
            status: The current status of the command.
            metrics: A dictionary of KPIs.
            message: An optional human-readable message.
            stdout: If True, prints the JSON record to stdout.
        """
        if os.environ.get("CLOUDMESH_AI_TELEMETRY_DISABLED", "").lower() in ("1", "true", "yes"):
            return
        # We reuse the record creation logic from the base class
        # but we wrap the blocking I/O calls in asyncio.to_thread

        all_metrics = (metrics or {}).copy()
        all_metrics.update(kwargs)
        record = {
            "timestamp": datetime.now().isoformat(),
            "command": self.command_name,
            "status": status,
            "metrics": all_metrics,
            "system": self._get_system_context(),
        }
        if message:
            record["message"] = message

        json_record = json.dumps(record)

        # 1. Log (usually non-blocking enough, but we can wrap it)
        self.logger.info(f"TELEMETRY: {json_record}")

        # 2. Emit to backends asynchronously
        tasks = [asyncio.to_thread(backend.emit, record) for backend in self.backends]
        if tasks:
            await asyncio.gather(*tasks)

        # 3. Optional stdout
        if stdout:
            print(json_record, file=sys.stdout)

    async def start(self, message: Optional[str] = None, **kwargs: Any) -> None:
        """Async helper to emit a 'started' status.

        Args:
            message: Optional message for the start event.
            **kwargs: Additional metrics to include in the record.

        """
        await self.emit("started", message=message, **kwargs)

    async def complete(self, metrics: Optional[Dict[str, Any]] = None, message: Optional[str] = "Command completed successfully", **kwargs: Any) -> None:
        """Async helper to emit a 'completed' status.

        Args:
            metrics: A dictionary of final KPIs and measurements.
            message: An optional human-readable message.
            **kwargs: Additional metrics to include in the record.

        """
        await self.emit("completed", metrics=metrics, message=message, **kwargs)

    async def fail(self, error: str, metrics: Optional[Dict[str, Any]] = None, **kwargs: Any) -> None:
        """Async helper to emit a 'failed' status.

        Args:
            error: The error message to record.
            metrics: A dictionary of KPIs at the time of failure.
            **kwargs: Additional metrics to include in the record.

        """
        await self.emit("failed", metrics=metrics, message=error, **kwargs)

    @asynccontextmanager
    async def track(self, message: Optional[str] = None, metrics: Optional[Dict[str, Any]] = None):
        """
        Async context manager to automatically track the start and completion of a task.

        Args:
            message: Optional message for the start event.
            metrics: Initial metrics to include.
        """
        await self.start(message=message, **(metrics or {}))
        start_time = time.perf_counter()
        try:
            yield
            duration = time.perf_counter() - start_time
            final_metrics = (metrics or {}).copy()
            final_metrics["duration_sec"] = duration
            await self.complete(metrics=final_metrics)
        except Exception as e:
            duration = time.perf_counter() - start_time
            final_metrics = (metrics or {}).copy()
            final_metrics["duration_sec"] = duration
            await self.fail(error=str(e), metrics=final_metrics)
            raise
Functions
complete async
complete(metrics=None, message='Command completed successfully', **kwargs)

Async helper to emit a 'completed' status.

Parameters:

Name Type Description Default
metrics Optional[Dict[str, Any]]

A dictionary of final KPIs and measurements.

None
message Optional[str]

An optional human-readable message.

'Command completed successfully'
**kwargs Any

Additional metrics to include in the record.

{}
Source code in src/cloudmesh/ai/common/telemetry.py
364
365
366
367
368
369
370
371
372
373
async def complete(self, metrics: Optional[Dict[str, Any]] = None, message: Optional[str] = "Command completed successfully", **kwargs: Any) -> None:
    """Async helper to emit a 'completed' status.

    Args:
        metrics: A dictionary of final KPIs and measurements.
        message: An optional human-readable message.
        **kwargs: Additional metrics to include in the record.

    """
    await self.emit("completed", metrics=metrics, message=message, **kwargs)
emit async
emit(status, metrics=None, message=None, stdout=False, **kwargs)

Asynchronously emits a structured telemetry record.

Parameters:

Name Type Description Default
status str

The current status of the command.

required
metrics Optional[Dict[str, Any]]

A dictionary of KPIs.

None
message Optional[str]

An optional human-readable message.

None
stdout bool

If True, prints the JSON record to stdout.

False
Source code in src/cloudmesh/ai/common/telemetry.py
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
async def emit(
    self, 
    status: str, 
    metrics: Optional[Dict[str, Any]] = None, 
    message: Optional[str] = None,
    stdout: bool = False,
    **kwargs: Any
) -> None:
    """Asynchronously emits a structured telemetry record.

    Args:
        status: The current status of the command.
        metrics: A dictionary of KPIs.
        message: An optional human-readable message.
        stdout: If True, prints the JSON record to stdout.
    """
    if os.environ.get("CLOUDMESH_AI_TELEMETRY_DISABLED", "").lower() in ("1", "true", "yes"):
        return
    # We reuse the record creation logic from the base class
    # but we wrap the blocking I/O calls in asyncio.to_thread

    all_metrics = (metrics or {}).copy()
    all_metrics.update(kwargs)
    record = {
        "timestamp": datetime.now().isoformat(),
        "command": self.command_name,
        "status": status,
        "metrics": all_metrics,
        "system": self._get_system_context(),
    }
    if message:
        record["message"] = message

    json_record = json.dumps(record)

    # 1. Log (usually non-blocking enough, but we can wrap it)
    self.logger.info(f"TELEMETRY: {json_record}")

    # 2. Emit to backends asynchronously
    tasks = [asyncio.to_thread(backend.emit, record) for backend in self.backends]
    if tasks:
        await asyncio.gather(*tasks)

    # 3. Optional stdout
    if stdout:
        print(json_record, file=sys.stdout)
fail async
fail(error, metrics=None, **kwargs)

Async helper to emit a 'failed' status.

Parameters:

Name Type Description Default
error str

The error message to record.

required
metrics Optional[Dict[str, Any]]

A dictionary of KPIs at the time of failure.

None
**kwargs Any

Additional metrics to include in the record.

{}
Source code in src/cloudmesh/ai/common/telemetry.py
375
376
377
378
379
380
381
382
383
384
async def fail(self, error: str, metrics: Optional[Dict[str, Any]] = None, **kwargs: Any) -> None:
    """Async helper to emit a 'failed' status.

    Args:
        error: The error message to record.
        metrics: A dictionary of KPIs at the time of failure.
        **kwargs: Additional metrics to include in the record.

    """
    await self.emit("failed", metrics=metrics, message=error, **kwargs)
start async
start(message=None, **kwargs)

Async helper to emit a 'started' status.

Parameters:

Name Type Description Default
message Optional[str]

Optional message for the start event.

None
**kwargs Any

Additional metrics to include in the record.

{}
Source code in src/cloudmesh/ai/common/telemetry.py
354
355
356
357
358
359
360
361
362
async def start(self, message: Optional[str] = None, **kwargs: Any) -> None:
    """Async helper to emit a 'started' status.

    Args:
        message: Optional message for the start event.
        **kwargs: Additional metrics to include in the record.

    """
    await self.emit("started", message=message, **kwargs)
track async
track(message=None, metrics=None)

Async context manager to automatically track the start and completion of a task.

Parameters:

Name Type Description Default
message Optional[str]

Optional message for the start event.

None
metrics Optional[Dict[str, Any]]

Initial metrics to include.

None
Source code in src/cloudmesh/ai/common/telemetry.py
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
@asynccontextmanager
async def track(self, message: Optional[str] = None, metrics: Optional[Dict[str, Any]] = None):
    """
    Async context manager to automatically track the start and completion of a task.

    Args:
        message: Optional message for the start event.
        metrics: Initial metrics to include.
    """
    await self.start(message=message, **(metrics or {}))
    start_time = time.perf_counter()
    try:
        yield
        duration = time.perf_counter() - start_time
        final_metrics = (metrics or {}).copy()
        final_metrics["duration_sec"] = duration
        await self.complete(metrics=final_metrics)
    except Exception as e:
        duration = time.perf_counter() - start_time
        final_metrics = (metrics or {}).copy()
        final_metrics["duration_sec"] = duration
        await self.fail(error=str(e), metrics=final_metrics)
        raise
JSONFileBackend

Writes telemetry records to a JSON Lines (JSONL) file. Each record is stored as a single JSON object per line.

Source code in src/cloudmesh/ai/common/telemetry.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class JSONFileBackend:
    """
    Writes telemetry records to a JSON Lines (JSONL) file.
    Each record is stored as a single JSON object per line.
    """
    def __init__(self, path: Union[str, Path]) -> None:
        """Initialize the JSONL backend.

        Args:
            path: Path to the output file.

        """
        self.path = Path(path).expanduser()

    def emit(self, record: Dict[str, Any]) -> None:
        """Append a telemetry record to the JSONL file.

        Args:
            record: The telemetry data dictionary to export.

        """
        try:
            with open(self.path, "a") as f:
                f.write(json.dumps(record) + "\n")
        except Exception as e:
            print(f"JSONFileBackend error: {e}")
Functions
__init__
__init__(path)

Initialize the JSONL backend.

Parameters:

Name Type Description Default
path Union[str, Path]

Path to the output file.

required
Source code in src/cloudmesh/ai/common/telemetry.py
38
39
40
41
42
43
44
45
def __init__(self, path: Union[str, Path]) -> None:
    """Initialize the JSONL backend.

    Args:
        path: Path to the output file.

    """
    self.path = Path(path).expanduser()
emit
emit(record)

Append a telemetry record to the JSONL file.

Parameters:

Name Type Description Default
record Dict[str, Any]

The telemetry data dictionary to export.

required
Source code in src/cloudmesh/ai/common/telemetry.py
47
48
49
50
51
52
53
54
55
56
57
58
def emit(self, record: Dict[str, Any]) -> None:
    """Append a telemetry record to the JSONL file.

    Args:
        record: The telemetry data dictionary to export.

    """
    try:
        with open(self.path, "a") as f:
            f.write(json.dumps(record) + "\n")
    except Exception as e:
        print(f"JSONFileBackend error: {e}")
SQLiteBackend

Writes telemetry records to a SQLite database. Provides structured storage for easier querying and analysis.

Source code in src/cloudmesh/ai/common/telemetry.py
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
class SQLiteBackend:
    """
    Writes telemetry records to a SQLite database.
    Provides structured storage for easier querying and analysis.
    """
    def __init__(self, db_path: Union[str, Path] = "telemetry.db") -> None:
        """Initialize the SQLite backend.

        Args:
            db_path: Path to the SQLite database file.

        """
        self.db_path = Path(db_path).expanduser()
        self._init_db()

    def _init_db(self) -> None:
        """Creates the telemetry table if it does not exist.

        """
        with sqlite3.connect(self.db_path) as conn:
            conn.execute("""
                CREATE TABLE IF NOT EXISTS telemetry (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    timestamp TEXT,
                    command TEXT,
                    status TEXT,
                    metrics TEXT,
                    system TEXT,
                    message TEXT
                )
            """)

    def emit(self, record: Dict[str, Any]) -> None:
        """Insert a telemetry record into the database.

        Args:
            record: The telemetry data dictionary to export.

        """
        try:
            with sqlite3.connect(self.db_path) as conn:
                conn.execute(
                    "INSERT INTO telemetry (timestamp, command, status, metrics, system, message) VALUES (?, ?, ?, ?, ?, ?)",
                    (
                        record.get("timestamp"),
                        record.get("command"),
                        record.get("status"),
                        json.dumps(record.get("metrics")),
                        json.dumps(record.get("system")),
                        record.get("message"),
                    )
                )
        except Exception as e:
            print(f"SQLiteBackend error: {e}")
Functions
__init__
__init__(db_path='telemetry.db')

Initialize the SQLite backend.

Parameters:

Name Type Description Default
db_path Union[str, Path]

Path to the SQLite database file.

'telemetry.db'
Source code in src/cloudmesh/ai/common/telemetry.py
65
66
67
68
69
70
71
72
73
def __init__(self, db_path: Union[str, Path] = "telemetry.db") -> None:
    """Initialize the SQLite backend.

    Args:
        db_path: Path to the SQLite database file.

    """
    self.db_path = Path(db_path).expanduser()
    self._init_db()
emit
emit(record)

Insert a telemetry record into the database.

Parameters:

Name Type Description Default
record Dict[str, Any]

The telemetry data dictionary to export.

required
Source code in src/cloudmesh/ai/common/telemetry.py
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
def emit(self, record: Dict[str, Any]) -> None:
    """Insert a telemetry record into the database.

    Args:
        record: The telemetry data dictionary to export.

    """
    try:
        with sqlite3.connect(self.db_path) as conn:
            conn.execute(
                "INSERT INTO telemetry (timestamp, command, status, metrics, system, message) VALUES (?, ?, ?, ?, ?, ?)",
                (
                    record.get("timestamp"),
                    record.get("command"),
                    record.get("status"),
                    json.dumps(record.get("metrics")),
                    json.dumps(record.get("system")),
                    record.get("message"),
                )
            )
    except Exception as e:
        print(f"SQLiteBackend error: {e}")
Telemetry

Handles structured telemetry emission for AI commands. Supports multiple backends for flexible data export.

Source code in src/cloudmesh/ai/common/telemetry.py
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
class Telemetry:
    """
    Handles structured telemetry emission for AI commands.
    Supports multiple backends for flexible data export.
    """

    def __init__(
        self, 
        command_name: str, 
        telemetry_file: Optional[Union[str, Path]] = None,
        backends: Optional[List[TelemetryBackend]] = None
    ) -> None:
        """Initialize the Telemetry collector.

        Args:
            command_name: Name of the command emitting telemetry.
            telemetry_file: Backward compatibility: path to a JSONL file.
            backends: List of TelemetryBackend implementations to use.

        """
        self.command_name = command_name
        self.logger = ai_log.get_logger(f"{command_name}.telemetry")
        self.backends: List[TelemetryBackend] = backends or []

        # Maintain backward compatibility with telemetry_file
        if telemetry_file:
            self.backends.append(JSONFileBackend(telemetry_file))

    def _get_system_context(self) -> Dict[str, Any]:
        """Gathers basic system context to accompany telemetry metrics.

        Returns:
            A dictionary containing basic system information.
        """
        info = ai_sys.systeminfo()
        return {
            "cpu": info.get("cpu"),
            "gpu_present": info.get("gpu.present"),
            "gpu_model": info.get("gpu.model"),
            "memory_total": info.get("memory.total"),
        }

    def emit(
        self, 
        status: str, 
        metrics: Optional[Dict[str, Any]] = None, 
        message: Optional[str] = None,
        stdout: bool = False,
        **kwargs: Any
    ) -> None:
        """Emits a structured telemetry record to all configured backends.

        Args:
            status: The current status of the command (e.g., 'started', 'completed', 'failed').
            metrics: A dictionary of KPIs and measurements.
            message: An optional human-readable message.
            stdout: If True, prints the JSON record to stdout.
        """
        if os.environ.get("CLOUDMESH_AI_TELEMETRY_DISABLED", "").lower() in ("1", "true", "yes"):
            return
        all_metrics = (metrics or {}).copy()
        all_metrics.update(kwargs)
        record = {
            "timestamp": datetime.now().isoformat(),
            "command": self.command_name,
            "status": status,
            "metrics": all_metrics,
            "system": self._get_system_context(),
        }
        if message:
            record["message"] = message

        json_record = json.dumps(record)

        # 1. Log via the standard logging system
        self.logger.info(f"TELEMETRY: {json_record}")

        # 2. Emit to all configured backends
        for backend in self.backends:
            backend.emit(record)

        # 3. Optional stdout for direct ingestion/piping
        if stdout:
            print(json_record, file=sys.stdout)

    def start(self, message: Optional[str] = None, **kwargs: Any) -> None:
        """Helper to emit a 'started' status.

        Args:
            message: Optional message for the start event.
            **kwargs: Additional metrics to include in the record.

        """
        self.emit("started", message=message, **kwargs)

    def complete(self, metrics: Optional[Dict[str, Any]] = None, message: Optional[str] = "Command completed successfully", **kwargs: Any) -> None:
        """Helper to emit a 'completed' status with final metrics.

        Args:
            metrics: A dictionary of final KPIs and measurements.
            message: An optional human-readable message.
            **kwargs: Additional metrics to include in the record.

        """
        self.emit("completed", metrics=metrics, message=message, **kwargs)

    def fail(self, error: str, metrics: Optional[Dict[str, Any]] = None, **kwargs: Any) -> None:
        """Helper to emit a 'failed' status.

        Args:
            error: The error message to record.
            metrics: A dictionary of KPIs at the time of failure.
            **kwargs: Additional metrics to include in the record.

        """
        self.emit("failed", metrics=metrics, message=error, **kwargs)

    @contextmanager
    def track(self, message: Optional[str] = None, metrics: Optional[Dict[str, Any]] = None):
        """
        Context manager to automatically track the start and completion of a task.

        Args:
            message: Optional message for the start event.
            metrics: Initial metrics to include.
        """
        self.start(message=message, **(metrics or {}))
        start_time = time.perf_counter()
        try:
            yield
            duration = time.perf_counter() - start_time
            final_metrics = (metrics or {}).copy()
            final_metrics["duration_sec"] = duration
            self.complete(metrics=final_metrics)
        except Exception as e:
            duration = time.perf_counter() - start_time
            final_metrics = (metrics or {}).copy()
            final_metrics["duration_sec"] = duration
            self.fail(error=str(e), metrics=final_metrics)
            raise
Functions
__init__
__init__(command_name, telemetry_file=None, backends=None)

Initialize the Telemetry collector.

Parameters:

Name Type Description Default
command_name str

Name of the command emitting telemetry.

required
telemetry_file Optional[Union[str, Path]]

Backward compatibility: path to a JSONL file.

None
backends Optional[List[TelemetryBackend]]

List of TelemetryBackend implementations to use.

None
Source code in src/cloudmesh/ai/common/telemetry.py
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
def __init__(
    self, 
    command_name: str, 
    telemetry_file: Optional[Union[str, Path]] = None,
    backends: Optional[List[TelemetryBackend]] = None
) -> None:
    """Initialize the Telemetry collector.

    Args:
        command_name: Name of the command emitting telemetry.
        telemetry_file: Backward compatibility: path to a JSONL file.
        backends: List of TelemetryBackend implementations to use.

    """
    self.command_name = command_name
    self.logger = ai_log.get_logger(f"{command_name}.telemetry")
    self.backends: List[TelemetryBackend] = backends or []

    # Maintain backward compatibility with telemetry_file
    if telemetry_file:
        self.backends.append(JSONFileBackend(telemetry_file))
complete
complete(metrics=None, message='Command completed successfully', **kwargs)

Helper to emit a 'completed' status with final metrics.

Parameters:

Name Type Description Default
metrics Optional[Dict[str, Any]]

A dictionary of final KPIs and measurements.

None
message Optional[str]

An optional human-readable message.

'Command completed successfully'
**kwargs Any

Additional metrics to include in the record.

{}
Source code in src/cloudmesh/ai/common/telemetry.py
254
255
256
257
258
259
260
261
262
263
def complete(self, metrics: Optional[Dict[str, Any]] = None, message: Optional[str] = "Command completed successfully", **kwargs: Any) -> None:
    """Helper to emit a 'completed' status with final metrics.

    Args:
        metrics: A dictionary of final KPIs and measurements.
        message: An optional human-readable message.
        **kwargs: Additional metrics to include in the record.

    """
    self.emit("completed", metrics=metrics, message=message, **kwargs)
emit
emit(status, metrics=None, message=None, stdout=False, **kwargs)

Emits a structured telemetry record to all configured backends.

Parameters:

Name Type Description Default
status str

The current status of the command (e.g., 'started', 'completed', 'failed').

required
metrics Optional[Dict[str, Any]]

A dictionary of KPIs and measurements.

None
message Optional[str]

An optional human-readable message.

None
stdout bool

If True, prints the JSON record to stdout.

False
Source code in src/cloudmesh/ai/common/telemetry.py
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
def emit(
    self, 
    status: str, 
    metrics: Optional[Dict[str, Any]] = None, 
    message: Optional[str] = None,
    stdout: bool = False,
    **kwargs: Any
) -> None:
    """Emits a structured telemetry record to all configured backends.

    Args:
        status: The current status of the command (e.g., 'started', 'completed', 'failed').
        metrics: A dictionary of KPIs and measurements.
        message: An optional human-readable message.
        stdout: If True, prints the JSON record to stdout.
    """
    if os.environ.get("CLOUDMESH_AI_TELEMETRY_DISABLED", "").lower() in ("1", "true", "yes"):
        return
    all_metrics = (metrics or {}).copy()
    all_metrics.update(kwargs)
    record = {
        "timestamp": datetime.now().isoformat(),
        "command": self.command_name,
        "status": status,
        "metrics": all_metrics,
        "system": self._get_system_context(),
    }
    if message:
        record["message"] = message

    json_record = json.dumps(record)

    # 1. Log via the standard logging system
    self.logger.info(f"TELEMETRY: {json_record}")

    # 2. Emit to all configured backends
    for backend in self.backends:
        backend.emit(record)

    # 3. Optional stdout for direct ingestion/piping
    if stdout:
        print(json_record, file=sys.stdout)
fail
fail(error, metrics=None, **kwargs)

Helper to emit a 'failed' status.

Parameters:

Name Type Description Default
error str

The error message to record.

required
metrics Optional[Dict[str, Any]]

A dictionary of KPIs at the time of failure.

None
**kwargs Any

Additional metrics to include in the record.

{}
Source code in src/cloudmesh/ai/common/telemetry.py
265
266
267
268
269
270
271
272
273
274
def fail(self, error: str, metrics: Optional[Dict[str, Any]] = None, **kwargs: Any) -> None:
    """Helper to emit a 'failed' status.

    Args:
        error: The error message to record.
        metrics: A dictionary of KPIs at the time of failure.
        **kwargs: Additional metrics to include in the record.

    """
    self.emit("failed", metrics=metrics, message=error, **kwargs)
start
start(message=None, **kwargs)

Helper to emit a 'started' status.

Parameters:

Name Type Description Default
message Optional[str]

Optional message for the start event.

None
**kwargs Any

Additional metrics to include in the record.

{}
Source code in src/cloudmesh/ai/common/telemetry.py
244
245
246
247
248
249
250
251
252
def start(self, message: Optional[str] = None, **kwargs: Any) -> None:
    """Helper to emit a 'started' status.

    Args:
        message: Optional message for the start event.
        **kwargs: Additional metrics to include in the record.

    """
    self.emit("started", message=message, **kwargs)
track
track(message=None, metrics=None)

Context manager to automatically track the start and completion of a task.

Parameters:

Name Type Description Default
message Optional[str]

Optional message for the start event.

None
metrics Optional[Dict[str, Any]]

Initial metrics to include.

None
Source code in src/cloudmesh/ai/common/telemetry.py
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
@contextmanager
def track(self, message: Optional[str] = None, metrics: Optional[Dict[str, Any]] = None):
    """
    Context manager to automatically track the start and completion of a task.

    Args:
        message: Optional message for the start event.
        metrics: Initial metrics to include.
    """
    self.start(message=message, **(metrics or {}))
    start_time = time.perf_counter()
    try:
        yield
        duration = time.perf_counter() - start_time
        final_metrics = (metrics or {}).copy()
        final_metrics["duration_sec"] = duration
        self.complete(metrics=final_metrics)
    except Exception as e:
        duration = time.perf_counter() - start_time
        final_metrics = (metrics or {}).copy()
        final_metrics["duration_sec"] = duration
        self.fail(error=str(e), metrics=final_metrics)
        raise
TelemetryBackend

Bases: Protocol

Interface for telemetry export backends. Any class implementing this protocol can be used as a telemetry sink.

Source code in src/cloudmesh/ai/common/telemetry.py
19
20
21
22
23
24
25
26
27
28
29
30
31
class TelemetryBackend(Protocol):
    """
    Interface for telemetry export backends.
    Any class implementing this protocol can be used as a telemetry sink.
    """
    def emit(self, record: Dict[str, Any]) -> None:
        """Emit a single telemetry record.

        Args:
            record: The telemetry data dictionary to export.

        """
        ...
Functions
emit
emit(record)

Emit a single telemetry record.

Parameters:

Name Type Description Default
record Dict[str, Any]

The telemetry data dictionary to export.

required
Source code in src/cloudmesh/ai/common/telemetry.py
24
25
26
27
28
29
30
31
def emit(self, record: Dict[str, Any]) -> None:
    """Emit a single telemetry record.

    Args:
        record: The telemetry data dictionary to export.

    """
    ...
TextBackend

Presents telemetry records in a human-readable text format. Can output to a file or directly to the console.

Source code in src/cloudmesh/ai/common/telemetry.py
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
class TextBackend:
    """
    Presents telemetry records in a human-readable text format.
    Can output to a file or directly to the console.
    """
    def __init__(self, path: Optional[Union[str, Path]] = None) -> None:
        """Initialize the Text backend.

        Args:
            path: Optional path to a text file. If None, outputs to stdout.

        """
        self.path = Path(path).expanduser() if path else None

    def emit(self, record: Dict[str, Any]) -> None:
        """Format and emit a telemetry record as a human-readable string.

        Args:
            record: The telemetry data dictionary to export.

        """
        try:
            timestamp = record.get("timestamp", "N/A")
            command = record.get("command", "unknown")
            status = record.get("status", "unknown").upper()
            message = record.get("message", "")
            metrics = record.get("metrics", {})

            metrics_str = ", ".join([f"{k}={v}" for k, v in metrics.items()]) if metrics else "None"

            output = (
                f"[{timestamp}] {command} | STATUS: {status} | Metrics: {metrics_str}\n"
                f"Message: {message}\n"
                + "-" * 40 + "\n"
            )

            if self.path:
                with open(self.path, "a") as f:
                    f.write(output)
            else:
                print(output)
        except Exception as e:
            print(f"TextBackend error: {e}")
Functions
__init__
__init__(path=None)

Initialize the Text backend.

Parameters:

Name Type Description Default
path Optional[Union[str, Path]]

Optional path to a text file. If None, outputs to stdout.

None
Source code in src/cloudmesh/ai/common/telemetry.py
120
121
122
123
124
125
126
127
def __init__(self, path: Optional[Union[str, Path]] = None) -> None:
    """Initialize the Text backend.

    Args:
        path: Optional path to a text file. If None, outputs to stdout.

    """
    self.path = Path(path).expanduser() if path else None
emit
emit(record)

Format and emit a telemetry record as a human-readable string.

Parameters:

Name Type Description Default
record Dict[str, Any]

The telemetry data dictionary to export.

required
Source code in src/cloudmesh/ai/common/telemetry.py
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
def emit(self, record: Dict[str, Any]) -> None:
    """Format and emit a telemetry record as a human-readable string.

    Args:
        record: The telemetry data dictionary to export.

    """
    try:
        timestamp = record.get("timestamp", "N/A")
        command = record.get("command", "unknown")
        status = record.get("status", "unknown").upper()
        message = record.get("message", "")
        metrics = record.get("metrics", {})

        metrics_str = ", ".join([f"{k}={v}" for k, v in metrics.items()]) if metrics else "None"

        output = (
            f"[{timestamp}] {command} | STATUS: {status} | Metrics: {metrics_str}\n"
            f"Message: {message}\n"
            + "-" * 40 + "\n"
        )

        if self.path:
            with open(self.path, "a") as f:
                f.write(output)
        else:
            print(output)
    except Exception as e:
        print(f"TextBackend error: {e}")

Modules

user

Utility module for retrieving user-related system information and permissions.

This module provides cross-platform support for checking root/admin status, identifying the current user, and verifying user existence on the system.

Functions

exists
exists(username)

Verifies if a specific user exists on the local system.

Parameters:

Name Type Description Default
username str

The login name of the user to verify.

required

Returns:

Type Description

True if the user exists, False otherwise.

Source code in src/cloudmesh/ai/common/user.py
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
def exists(username):
    """Verifies if a specific user exists on the local system.

    Args:
        username (str): The login name of the user to verify.

    Returns:
        True if the user exists, False otherwise.
    """
    if os.name == 'nt':
        # Simple Windows check using the 'net user' command
        import subprocess
        return subprocess.run(
            ["net", "user", username], 
            stdout=subprocess.DEVNULL, 
            stderr=subprocess.DEVNULL
        ).returncode == 0
    else:
        import pwd
        try:
            pwd.getpwnam(username)
            return True
        except KeyError:
            return False
get
get()

Retrieves the login name of the current user.

This function looks at environment variables (LOGNAME, USER, LNAME, USERNAME) to provide a reliable username across different platforms.

Returns:

Type Description

The username of the current user.

Source code in src/cloudmesh/ai/common/user.py
29
30
31
32
33
34
35
36
37
38
def get():
    """Retrieves the login name of the current user.

    This function looks at environment variables (LOGNAME, USER, LNAME, USERNAME) 
    to provide a reliable username across different platforms.

    Returns:
        The username of the current user.
    """
    return getpass.getuser()
groups
groups()

Retrieves a list of group names that the current user belongs to.

Note

This function is currently only fully supported on Unix-like systems. On Windows, this will return an empty list.

Returns:

Type Description

A list of strings representing the group names.

Source code in src/cloudmesh/ai/common/user.py
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def groups():
    """Retrieves a list of group names that the current user belongs to.

    Note:
        This function is currently only fully supported on Unix-like systems.
        On Windows, this will return an empty list.

    Returns:
        A list of strings representing the group names.
    """
    if os.name == 'nt':  # Windows
        return []  # Windows handles groups differently (via SIDs)
    import grp
    return [grp.getgrgid(g).gr_name for g in os.getgroups()]
home
home()

Retrieves the path to the current user's home directory.

Returns:

Type Description

A Path object representing the user's home directory

(e.g., /home/user or C:\Users\user).

Source code in src/cloudmesh/ai/common/user.py
40
41
42
43
44
45
46
47
def home():
    """Retrieves the path to the current user's home directory.

    Returns:
        A Path object representing the user's home directory 
        (e.g., /home/user or C:\\Users\\user).
    """
    return Path.home()
is_root
is_root()

Checks if the current process has administrative or root privileges.

On Unix-like systems (Linux, macOS), it checks if the Effective User ID is 0. On Windows, it utilizes shell32 to check for administrative elevation.

Returns:

Type Description

True if the process is running with root/admin rights, False otherwise.

Source code in src/cloudmesh/ai/common/user.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def is_root():
    """Checks if the current process has administrative or root privileges.

    On Unix-like systems (Linux, macOS), it checks if the Effective User ID is 0.
    On Windows, it utilizes shell32 to check for administrative elevation.

    Returns:
        True if the process is running with root/admin rights, False otherwise.
    """
    try:
        # Unix/Linux/macOS
        return os.geteuid() == 0
    except AttributeError:
        # Windows
        import ctypes
        return ctypes.windll.shell32.IsUserAnAdmin() != 0

util

Functions

FUNCTIONNAME
FUNCTIONNAME()

Returns the name of a function.

Source code in src/cloudmesh/ai/common/util.py
244
245
246
247
def FUNCTIONNAME() -> str:
    """Returns the name of a function."""
    frame = inspect.getouterframes(inspect.currentframe())
    return frame[1][3]
HEADING
HEADING(txt=None, c='#', color='magenta')

Prints a message to stdout with #### surrounding it.

Source code in src/cloudmesh/ai/common/util.py
228
229
230
231
232
233
234
235
236
237
238
239
240
241
def HEADING(txt: Optional[str] = None, c: str = "#", color: str = "magenta") -> None:
    """Prints a message to stdout with #### surrounding it."""
    frame = inspect.getouterframes(inspect.currentframe())
    filename = frame[1][1].replace(os.getcwd(), "")
    line = frame[1][2] - 1
    method = frame[1][3]

    if txt is None:
        msg = f"{method} {filename} {line}"
    else:
        msg = f"{txt}\n {method} {filename} {line}"

    print()
    banner(msg, c=c, color=color)
auto_create_requirements
auto_create_requirements(requirements)

creates a requirement.txt file from the requirements in the list.

Source code in src/cloudmesh/ai/common/util.py
271
272
273
274
275
276
277
278
279
def auto_create_requirements(requirements: List[str]) -> None:
    """creates a requirement.txt file from the requirements in the list."""
    banner("Creating requirements.txt file")
    req_file = Path("requirements.txt")
    file_content = req_file.read_text() if req_file.exists() else ""
    setup_requirements = "\n".join(requirements)

    if setup_requirements != file_content:
        req_file.write_text(setup_requirements)
auto_create_version
auto_create_version(class_name, version, filename='__init__.py')

creates a version number in the init.py file.

Source code in src/cloudmesh/ai/common/util.py
261
262
263
264
265
266
267
268
def auto_create_version(class_name: str, version: str, filename: str = "__init__.py") -> None:
    """creates a version number in the __init__.py file."""
    version_filename = Path(class_name) / filename
    if version_filename.exists():
        content = version_filename.read_text()
        if content != f'__version__ = "{version}"':
            banner(f"Updating version to {version}")
            version_filename.write_text(f'__version__ = "{version}"')
backup_name
backup_name(filename)

creates a backup name of the form filename.bak.1

Source code in src/cloudmesh/ai/common/util.py
250
251
252
253
254
255
256
257
258
def backup_name(filename: Union[str, Path]) -> str:
    """creates a backup name of the form filename.bak.1"""
    location = Path(path_expand(str(filename)))
    n = 0
    while True:
        n += 1
        backup = location.with_suffix(f"{location.suffix}.bak.{n}")
        if not backup.exists():
            return str(backup)
check_root
check_root(dryrun=False, terminate=True)

check if I am the root user. If not, simply exits the program.

Parameters:

Name Type Description Default
dryrun bool

if set to true, does not terminate if not root user

False
terminate bool

terminates if not root user and dryrun is False

True
Source code in src/cloudmesh/ai/common/util.py
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
def check_root(dryrun: bool = False, terminate: bool = True) -> None:
    """check if I am the root user. If not, simply exits the program.

    Args:
        dryrun (bool): if set to true, does not terminate if not root user
        terminate (bool): terminates if not root user and dryrun is False
    """
    try:
        uid = os.getuid()
    except AttributeError:
        uid = -1  # Not on a POSIX system

    if uid == 0:
        Console.ok("You are executing as a root user")
    else:
        Console.error("You do not run as root")
        if terminate and not dryrun:
            sys.exit()
convert_from_unicode
convert_from_unicode(data)

Converts unicode data to a string

Source code in src/cloudmesh/ai/common/util.py
189
190
191
192
193
194
195
196
197
198
def convert_from_unicode(data: Any) -> Any:
    """Converts unicode data to a string"""
    if isinstance(data, str):
        return str(data)
    elif isinstance(data, Mapping):
        return {k: convert_from_unicode(v) for k, v in data.items()}
    elif isinstance(data, Iterable) and not isinstance(data, (str, bytes)):
        return type(data)(map(convert_from_unicode, data))
    else:
        return data
copy_files
copy_files(files_glob, source_dir, dest_dir)

copies the files to the destination

Source code in src/cloudmesh/ai/common/util.py
282
283
284
285
286
287
288
def copy_files(files_glob: str, source_dir: Union[str, Path], dest_dir: Union[str, Path]) -> None:
    """copies the files to the destination"""
    src = Path(source_dir)
    dst = Path(dest_dir)
    dst.mkdir(parents=True, exist_ok=True)
    for filename in src.glob(files_glob):
        shutil.copy2(filename, dst)
csv_to_list
csv_to_list(csv_string, sep=',')

Converts a CSV table from a string to a list of lists

Parameters:

Name Type Description Default
csv_string string

The CSV table

required
sep string

The separator

','

Returns:

Name Type Description
list List[List[str]]

list of lists

Source code in src/cloudmesh/ai/common/util.py
 98
 99
100
101
102
103
104
105
106
107
108
109
def csv_to_list(csv_string: str, sep: str = ",") -> List[List[str]]:
    """Converts a CSV table from a string to a list of lists

    Args:
        csv_string (string): The CSV table
        sep (string): The separator

    Returns:
        list: list of lists
    """
    reader = csv.reader(csv_string.splitlines(), delimiter=sep)
    return list(reader)
download
download(source, destination, force=False)

Downloads the file from source to destination

Parameters:

Name Type Description Default
source str

The http source

required
destination Union[str, Path]

The destination in the file system

required
force bool

If True the file will be downloaded even if it already exists

False
Source code in src/cloudmesh/ai/common/util.py
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
def download(source: str, destination: Union[str, Path], force: bool = False) -> None:
    """Downloads the file from source to destination

    Args:
        source: The http source
        destination: The destination in the file system
        force: If True the file will be downloaded even if it already exists
    """
    dest_path = Path(destination)
    if dest_path.exists() and not force:
        Console.warning(f"File {dest_path} already exists. Skipping download ...")
    else:
        dest_path.parent.mkdir(parents=True, exist_ok=True)
        r = requests.get(source, allow_redirects=True)
        dest_path.write_bytes(r.content)
exponential_backoff
exponential_backoff(fn, sleeptime_s_max=30 * 60)

Calls fn until it returns True, with an exponentially increasing wait time between calls

Parameters:

Name Type Description Default
fn callable

the function to be called that returns True or False

required
sleeptime_s_max int

the maximum sleep time in seconds

30 * 60

Returns:

Name Type Description
bool bool

True if fn() returned True, False if max sleep time was reached

Source code in src/cloudmesh/ai/common/util.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
def exponential_backoff(fn, sleeptime_s_max: int = 30 * 60) -> bool:
    """Calls `fn` until it returns True, with an exponentially increasing wait time between calls

    Args:
        fn (callable): the function to be called that returns True or False
        sleeptime_s_max (int): the maximum sleep time in seconds

    Returns:
        bool: True if fn() returned True, False if max sleep time was reached
    """
    sleeptime_ms = 500
    while True:
        if fn():
            return True
        else:
            print(f"Sleeping {sleeptime_ms} ms")
            time.sleep(sleeptime_ms / 1000.0)
            sleeptime_ms *= 2

        if sleeptime_ms / 1000.0 > sleeptime_s_max:
            return False
generate_password
generate_password(length=8, lower=True, upper=True, number=True)

generates a simple password.

Source code in src/cloudmesh/ai/common/util.py
305
306
307
308
309
310
311
312
313
314
315
316
317
318
def generate_password(length: int = 8, lower: bool = True, upper: bool = True, number: bool = True) -> str:
    """generates a simple password."""
    lletters = "abcdefghijklmnopqrstuvwxyz"
    uletters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    alphabet = lletters + uletters if lower and upper else (lletters if lower else uletters)
    digit = "0123456789"
    mypw = ""

    for i in range(length):
        if number and i >= int(length / 2):
            mypw += random.choice(digit)
        else:
            mypw += random.choice(alphabet)
    return mypw
get_password
get_password(prompt)

gets a password from the user securely

Source code in src/cloudmesh/ai/common/util.py
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
def get_password(prompt: str) -> str:
    """gets a password from the user securely"""
    from cloudmesh.ai.common.sys import os_is_windows

    try:
        if os_is_windows() and is_gitbash():
            while True:
                sys.stdout.write(prompt)
                sys.stdout.flush()
                subprocess.check_call(["stty", "-echo"])
                password = input()
                subprocess.check_call(["stty", "echo"])
                sys.stdout.write("Please retype the password:\n")
                sys.stdout.flush()
                subprocess.check_call(["stty", "-echo"])
                password2 = input()
                subprocess.check_call(["stty", "echo"])
                if password == password2:
                    return password
                Console.error("Passwords do not match\n")
        else:
            while True:
                password = getpass(prompt)
                password2 = getpass("Please retype the password:\n")
                if password == password2:
                    return password
                Console.error("Passwords do not match\n")
    except KeyboardInterrupt:
        if is_gitbash():
            subprocess.check_call(["stty", "-echo"])
        raise ValueError("Detected Ctrl + C. Quitting...")
grep
grep(pattern, filename)

Very simple grep that returns the first matching line in a file.

Parameters:

Name Type Description Default
pattern str

the pattern to search for

required
filename Union[str, Path]

the file to search in

required

Returns:

Name Type Description
str str

the first matching line or empty string

Source code in src/cloudmesh/ai/common/util.py
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
def grep(pattern: str, filename: Union[str, Path]) -> str:
    """Very simple grep that returns the first matching line in a file.

    Args:
        pattern: the pattern to search for
        filename: the file to search in

    Returns:
        str: the first matching line or empty string
    """
    try:
        with open(filename, 'r', encoding='utf-8') as f:
            return next((L for L in f if pattern in L), "")
    except (StopIteration, OSError):
        return ""
is_cmd_exe
is_cmd_exe()

return True if you run in a Windows CMD

Source code in src/cloudmesh/ai/common/util.py
180
181
182
183
184
def is_cmd_exe() -> bool:
    """return True if you run in a Windows CMD"""
    if is_gitbash():
        return False
    return os.environ.get("OS") == "Windows_NT"
is_gitbash
is_gitbash()

returns True if you run in a Windows gitbash

Source code in src/cloudmesh/ai/common/util.py
161
162
163
164
165
166
def is_gitbash() -> bool:
    """returns True if you run in a Windows gitbash"""
    try:
        return "Git" in os.environ.get("EXEPATH", "")
    except Exception:
        return False
is_local
is_local(host)

Checks if the host is the localhost

Parameters:

Name Type Description Default
host str

The hostname or ip

required

Returns:

Name Type Description
bool bool

True if local, False otherwise

Source code in src/cloudmesh/ai/common/util.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
def is_local(host: str) -> bool:
    """Checks if the host is the localhost

    Args:
        host: The hostname or ip

    Returns:
        bool: True if local, False otherwise
    """
    return host in [
        "127.0.0.1",
        "localhost",
        socket.gethostname(),
        platform.node(),
    ]
is_powershell
is_powershell()

True if you run in powershell

Source code in src/cloudmesh/ai/common/util.py
169
170
171
172
173
174
175
176
177
def is_powershell() -> bool:
    """True if you run in powershell"""
    if platform.system() == "Windows":
        try:
            import psutil
            return psutil.Process(os.getppid()).name() == "powershell.exe"
        except ImportError:
            return False
    return False
search
search(lines, pattern)

return all lines that match the pattern

Parameters:

Name Type Description Default
lines List[str]

list of strings to search

required
pattern str

the pattern to search for (supports * as wildcard)

required

Returns:

Name Type Description
list List[str]

matching lines

Source code in src/cloudmesh/ai/common/util.py
112
113
114
115
116
117
118
119
120
121
122
123
124
def search(lines: List[str], pattern: str) -> List[str]:
    """return all lines that match the pattern

    Args:
        lines: list of strings to search
        pattern: the pattern to search for (supports * as wildcard)

    Returns:
        list: matching lines
    """
    p = pattern.replace("*", ".*")
    test = re.compile(p)
    return [l for l in lines if test.search(l)]
tempdir
tempdir(*args, **kwargs)

A contextmanager to work in an auto-removed temporary directory

Arguments are passed through to tempfile.mkdtemp

Source code in src/cloudmesh/ai/common/util.py
25
26
27
28
29
30
31
32
33
34
35
@contextmanager
def tempdir(*args, **kwargs) -> Path:
    """A contextmanager to work in an auto-removed temporary directory

    Arguments are passed through to tempfile.mkdtemp
    """
    d = Path(tempfile.mkdtemp(*args, **kwargs))
    try:
        yield d
    finally:
        shutil.rmtree(d)
writefd
writefd(filename, content, mode='w', flags=os.O_RDWR | os.O_CREAT, mask=384)

writes the content into the file and control permissions

Source code in src/cloudmesh/ai/common/util.py
293
294
295
296
297
298
299
300
def writefd(filename: str, content: str, mode: str = "w", flags: int = os.O_RDWR | os.O_CREAT, mask: int = 0o600) -> None:
    """writes the content into the file and control permissions"""
    if mode not in ("w", "wb"):
        Console.error(f"incorrect mode : expected 'w' or 'wb' given {mode}")

    with os.fdopen(os.open(filename, flags, mask), mode) as outfile:
        outfile.write(content)
        outfile.truncate()
yn_choice
yn_choice(message, default='y', tries=None)

asks for a yes/no question.

Parameters:

Name Type Description Default
message str

the message containing the question

required
default str

the default answer

'y'
tries Optional[int]

the number of tries

None
Source code in src/cloudmesh/ai/common/util.py
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
def yn_choice(message: str, default: str = "y", tries: Optional[int] = None) -> bool:
    """asks for a yes/no question.

    Args:
        message: the message containing the question
        default: the default answer
        tries: the number of tries
    """
    choices = "Y/n" if default.lower() in ("y", "yes") else "y/N"
    if tries is None:
        choice = input(f"{message} ({choices}) ")
        values = ("y", "yes", "") if default == "y" else ("y", "yes")
        return choice.strip().lower() in values
    else:
        while tries > 0:
            choice = input(f"{message} ({choices}) ('q' to discard)").strip().lower()
            if choice in ("y", "yes"):
                return True
            elif choice in ("n", "no", "q"):
                return False
            print("Invalid input...")
            tries -= 1
        return False