API Reference
The names below are the public exports from hostctl.__all__ — everything a
caller has to be able to write: a host or config to construct, an exception
to catch, a type to annotate with, an argument to pass, or a contract to
implement.
Concrete backends and result types the library only ever hands back are not
listed here and are not part of the stable surface. They remain importable from
the module that defines them — hostctl.host.qemu, hostctl.host.container_path,
hostctl.host._winrm, hostctl.process, hostctl.executor, and
hostctl.provider.transports — but their names and signatures may change
without a deprecation cycle.
Hosts and configuration
hostctl.Host
Bases: ABC
Protocol-independent operational interface to a machine.
capabilities
abstractmethod
property
Operations supported by this host.
executor
property
The command executor used when binding this host's shell.
executor_capabilities
property
Native context/argument features of the underlying executor.
Derived from the host's own executor so a host that does not compose
providers still reports truthfully. Host.executor's default wrapper
reads this property, so it is skipped here to avoid recursing; a host
that overrides executor with a real executor reports that executor's
capabilities.
last_selection
property
The redacted provider trace for the most recent :meth:run.
The run-side counterpart of CompositePosixPath.selection_trace.
Entries appear in provider precedence order and carry provider,
availability, reason, capabilities, chosen,
generation, policy, and pin.
The trace accumulates across the failover attempts of a single
run(), so a call that fell through from one provider to another
reports every provider tried and every refusal reason -- including on
the very call that suffered them.
Empty for a host whose run() does not select between providers
(QemuHost, SerialHost), and empty before the first run().
Values are redacted on the way in; see
:meth:~hostctl.provider.ProviderSelector.redact for the limits of
that guarantee.
shell_flavour
property
The explicitly known shell language used by this host.
close()
Close transport resources; must be safe to call repeatedly.
Source code in src/hostctl/host/_common.py
772 773 | |
connect()
Open transport resources; local/default implementations are no-op.
Source code in src/hostctl/host/_common.py
769 770 | |
info()
abstractmethod
Return normalized system information without inferred values.
Source code in src/hostctl/host/_common.py
765 766 767 | |
path(*segments, backend=None)
Return a pathlib-compatible path for this host.
Source code in src/hostctl/host/_common.py
795 796 797 798 799 | |
run(*cmds, bufsize=-1, executable=None, stdin=None, stdout=None, stderr=None, cwd=None, env=None, capture_output=True, check=True, encoding=None, errors=None, input=None, timeout=None, text=None)
Run commands and return a subprocess-compatible result.
A string is verbatim shell text. A tuple/list is one quoted argv
command. A leading :class:pathlib.PurePath/pathlib_next path is
a direct executable and all trailing values are its argv arguments.
Otherwise multiple top-level commands are joined by the selected
shell's command separator.
Source code in src/hostctl/host/_common.py
816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 | |
shell()
A shell bound to this host's executor.
Used directly -- host.shell.run(...), host.shell.session(...), or
with host.shell as session: -- it carries no defaults.
Called with keywords -- host.shell(cwd="/srv/app", env={"TZ": "UTC"})
-- it returns a shell carrying those defaults, applied to every later
run, execute, and session that does not pass its own value.
env merges per key; cwd, encoding, and errors override.
Source code in src/hostctl/host/_common.py
710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 | |
spawn(*cmds, executable=None, cwd=None, env=None, terminal=None, encoding=None, errors=None)
Start a persistent process controlled through a synchronous facade.
Source code in src/hostctl/host/_common.py
801 802 803 804 805 806 807 808 809 810 811 812 813 814 | |
hostctl.HostConfig()
Bases: ABC
Secret-safe connection configuration and extensible URI dispatch.
Source code in src/hostctl/host/_common.py
362 363 364 | |
connection_uri
abstractmethod
property
Credential-safe canonical connection URI.
open()
Return a host context manager for this configuration.
Source code in src/hostctl/host/_common.py
392 393 394 | |
hostctl.HostInfo(hostname=None, os_family=None, os_name=None, os_version=None, architecture=None)
dataclass
Normalized system information; unavailable values remain None.
hostctl.LocalConfig()
Bases: HostConfig
Source code in src/hostctl/host/_local.py
32 33 | |
hostctl.LocalHost(config=None, *, executor_providers=(), path_providers=())
Bases: Host
A host whose commands and paths are local to this process.
The public surface is unchanged, but execution and filesystem access are
assembled from ordered providers (:class:LocalExecutorProvider and
:class:LocalPathProvider) rather than a hard-wired executor. A subclass
may supply its own providers to reuse the local semantics over a different
access mechanism.
Source code in src/hostctl/host/_local.py
60 61 62 63 64 65 66 67 68 69 70 71 72 73 | |
executor_providers
property
The ordered command providers backing :meth:run.
path_providers
property
The ordered filesystem providers backing :meth:path.
hostctl.SshConfig(host, port=22, username='root', password=None, client_keys=None, executable=None, known_hosts=(), dialect=POSIX_SHELL, path_flavor=PosixPathname)
dataclass
hostctl.WinRMConfig(host, username, password=None, transport='ntlm', port=None, ssl=False, server_cert_validation='validate', message_encryption='auto', operation_timeout_sec=20, read_timeout_sec=30, provider='auto')
dataclass
hostctl.WinRMPath(*segments, backend=None)
Bases: WindowsPathname, Path
A Windows path whose I/O executes through a WinRM host.
Source code in src/hostctl/host/_winrm.py
813 814 815 816 817 | |
symlink_to(target, target_is_directory=False)
Create this path as a symbolic link to target.
Windows only permits this from an elevated session or with
Developer Mode enabled; otherwise the backend raises
:class:PermissionError. target_is_directory is accepted for
:meth:pathlib.Path.symlink_to signature parity and ignored --
New-Item -ItemType SymbolicLink infers the kind from the target.
Source code in src/hostctl/host/_winrm.py
902 903 904 905 906 907 908 909 910 911 | |
hostctl.ContainerConfig(container, engine_url=None, user=None, workdir=None, executable=None, dialect='auto', path_flavor='auto', client_factory=None)
dataclass
hostctl.ContainerHost(config, *, executor_providers=(), path_providers=())
Bases: Host
A running container reached through the Docker Engine API.
Commands and paths are assembled from ordered providers
(:class:ContainerExecutorProvider and
:class:ContainerArchivePathProvider) without changing the public API.
The archive provider declares only the operations the Docker archive API
can perform, so an unsupported mutation is rejected outright instead of
falling through to another provider.
Source code in src/hostctl/host/container.py
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 | |
executor_providers
property
The ordered command providers backing :meth:run.
path_providers
property
The ordered filesystem providers backing :meth:path.
spawn(*cmds, executable=None, cwd=None, env=None, terminal=None, encoding=None, errors=None)
Start a persistent Docker exec process with an optional TTY.
Source code in src/hostctl/host/container.py
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 587 588 589 590 591 592 593 594 595 596 597 598 599 600 | |
hostctl.QemuConfig(domain, transport='libvirt', connection=None, socket_path=None, ssh=None, agent_timeout=10.0, dialect='auto', path_flavor='auto', transport_factory=None, serial_console=None)
dataclass
hostctl.QemuHost(config)
Bases: Host
A guest OS reached through QEMU Guest Agent.
Source code in src/hostctl/host/qemu.py
248 249 250 251 252 253 254 255 256 257 | |
path_provider
property
The QGA path provider, once :meth:path has assembled it.
open_serial()
Open the explicitly configured raw VM console.
Source code in src/hostctl/host/qemu.py
321 322 323 324 325 | |
hostctl.SerialConfig(port, baudrate=115200, bytesize=8, parity='N', stopbits=1, xonxoff=False, rtscts=False, dsrdtr=False, read_timeout=0.1, write_timeout=10, inter_byte_timeout=None, exclusive=None, protocol=RawConsoleProfile(), username=None, password=None, serial_factory=None, serial_port=None)
dataclass
hostctl.SerialHost(config)
Bases: Host
Host facade over one exclusive serial console byte stream.
Source code in src/hostctl/host/serial.py
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 | |
hostctl.SystemConfig(authority='localhost', *, shell=None, executor=(), path=(), **options)
Bases: HostConfig
Base configuration for a logical system with one or more providers.
Abstract: concrete system families are :class:PosixConfig,
:class:WindowsConfig, and :class:IosConfig. Each binds host_type
and uri_scheme; this class binds neither and cannot be instantiated
into a host on its own.
Source code in src/hostctl/host/system.py
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | |
hostctl.SystemHost(config=None, *, executor_providers=(), path_providers=(), shell=None, info=None, initializer=None)
Bases: Host
Host orchestration shared by POSIX, Windows, and IOS systems.
Source code in src/hostctl/host/system.py
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 | |
provider_details
property
Return deterministic, non-dispatching provider availability details.
runspace()
Return a provider-owned typed runspace when one is available.
Source code in src/hostctl/host/system.py
743 744 745 746 747 748 749 750 751 752 | |
hostctl.PosixConfig(authority='localhost', *, shell=None, executor=(), path=(), **options)
Bases: SystemConfig
Source code in src/hostctl/host/system.py
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | |
hostctl.PosixHost(config=None, *, executor_providers=(), path_providers=(), shell=None, info=None, initializer=None)
Bases: SystemHost
Source code in src/hostctl/host/system.py
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 | |
from_ssh(config)
classmethod
Compose POSIX semantics over an existing :class:SshConfig.
Source code in src/hostctl/host/system.py
759 760 761 762 763 764 765 766 767 | |
hostctl.WindowsConfig(authority='localhost', *, shell=None, executor=(), path=(), **options)
Bases: SystemConfig
Source code in src/hostctl/host/system.py
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | |
hostctl.WindowsHost(config=None, *, executor_providers=(), path_providers=(), shell=None, info=None, initializer=None)
Bases: SystemHost
Source code in src/hostctl/host/system.py
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 | |
from_winrm(config)
classmethod
Compose Windows semantics over an existing :class:WinRMConfig.
Source code in src/hostctl/host/system.py
774 775 776 777 778 779 780 781 782 | |
hostctl.IosConfig(authority='localhost', *, shell=None, executor=(), path=(), **options)
Bases: SystemConfig
Source code in src/hostctl/host/system.py
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | |
hostctl.IosHost(config=None, *, executor_providers=(), path_providers=(), shell=None, info=None, initializer=None)
Bases: SystemHost
Source code in src/hostctl/host/system.py
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 | |
Executors and processes
hostctl.Exec(program, *args)
dataclass
One command executed directly, with no shell layer.
Exec(program, *args) names a program and its argv. The program may be
an absolute path or a bare name the target resolves through PATH; a
bare name has no other spelling, since a plain string is always shell text.
This is the explicit marker for direct execution. A path used anywhere else is an ordinary value that stringifies, so several commands can be written without one of them silently becoming an executable::
host.run(Exec("/bin/ls", "-l")) # one direct command
host.run(Exec("ls", "-l")) # PATH-resolved, no shell
host.run(Exec("/bin/a"), Exec("/bin/b")) # two direct commands
host.run(PurePosixPath("/bin/a"), PurePosixPath("/bin/b"))
# two ordinary shell commands
Arguments are argv values, never nested commands: a list or tuple raises
TypeError rather than blurring argv and shell semantics.
A deliberately non-iterable container. ShellFlavour.command_text
dispatches structured commands on Iterable, so an iterable marker
would be quoted into an argv string instead of taking the direct branch.
Source code in src/hostctl/host/_common.py
74 75 76 77 78 79 80 81 82 83 84 85 | |
hostctl.Executor
Bases: Protocol[_Result]
Callable executor receiving one command string and execution options.
hostctl.ExecutionOptions
Bases: TypedDict
Shell-agnostic subprocess-style options understood by executors.
hostctl.LocalExecutor
hostctl.Process
Bases: Protocol
Synchronous control surface for a persistent child process.
hostctl.TerminalOptions(term_type='xterm-256color', columns=80, rows=24, pixel_width=0, pixel_height=0)
dataclass
Requested pseudo-terminal type and initial dimensions.
hostctl.QgaCommandError(error_class, description, *, data=None)
Bases: QgaError
A structured QGA command error returned by the guest.
Source code in src/hostctl/executor/_qga.py
36 37 38 39 40 41 42 43 44 45 46 | |
hostctl.QgaProtocolError
Bases: QgaError, ConnectionError
The guest agent returned malformed or inconsistent protocol data.
Shells and sessions
hostctl.ShellFlavour
Bases: ABC
Construct scripts and commands for one explicitly selected target shell.
change_directory(cwd)
abstractmethod
Render a directory change which fails if the directory is absent.
Source code in src/hostctl/shell/_common.py
291 292 293 | |
command(cmds, *, executable=None, cwd=None, env=None)
abstractmethod
Build the command submitted to an SSH exec channel.
Source code in src/hostctl/shell/_common.py
299 300 301 302 303 304 305 306 307 308 | |
command_path(value)
Return a direct-command marker using the target shell's path syntax.
Source code in src/hostctl/shell/_common.py
156 157 158 | |
environment_assignment(key, value)
abstractmethod
Render one validated environment assignment.
Source code in src/hostctl/shell/_common.py
195 196 197 | |
environment_script(env)
Convert an environment mapping into a standalone shell script.
Source code in src/hostctl/shell/_common.py
199 200 201 202 203 204 205 206 207 208 | |
invocation(script, *, executable=None)
abstractmethod
Build local-process argv which invokes this shell for one script.
Source code in src/hostctl/shell/_common.py
310 311 312 313 314 315 316 317 | |
join(values)
Join commands, preserving raw strings and explicit operators.
Source code in src/hostctl/shell/_common.py
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 | |
join_cwd(changed, command)
Join cwd setup and payload with the shell's AND operator.
Source code in src/hostctl/shell/_common.py
295 296 297 | |
operator(value)
abstractmethod
Render one supported command operator.
Source code in src/hostctl/shell/_common.py
186 187 188 | |
quote(value)
abstractmethod
Quote one structured argument for this shell.
Source code in src/hostctl/shell/_common.py
182 183 184 | |
script(cmds, *, cwd=None, env=None, for_session=False)
Build a script, applying environment and cwd consistently.
Source code in src/hostctl/shell/_common.py
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 | |
hostctl.Shell(flavour, executor, *, cwd=None, env=None, encoding=None, errors=None)
Bases: Executor[_Result], Generic[_Result]
Bind one shell language to a one-string callable or host executor.
Source code in src/hostctl/shell/_common.py
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 | |
__enter__()
Open a default session, so with host.shell as session: works.
The session is closed on exit. session(...) remains the way to pass
a command, cwd, env, terminal, or encoding; this is the no-argument
shorthand. A Shell is not reusable as a context manager while a
session it opened is still active -- each with opens its own.
Source code in src/hostctl/shell/_common.py
632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 | |
configure(*, cwd=None, env=_INHERIT_ENV, encoding=None, errors=None)
Return a copy of this shell with additional defaults applied.
env merges over this shell's default the same way a per-call env
does, so configuring twice layers rather than replaces, and None
produces a copy carrying no environment default at all. The original
shell is left unchanged, which keeps host.shell -- a fresh object
per access -- safe to configure without surprising another caller.
Source code in src/hostctl/shell/_common.py
608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 | |
execute(command, *args, stdin=None, stdout=None, stderr=None, cwd=None, env=None, capture_output=None, check=None, encoding=None, errors=None, input=None, timeout=None, text=None)
Execute one command and forward supported execution context.
Source code in src/hostctl/shell/_common.py
440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 | |
run(*cmds, stdin=None, stdout=None, stderr=None, cwd=None, env=_INHERIT_ENV, capture_output=None, check=None, encoding=None, errors=None, input=None, timeout=None, text=None)
Build one script from all commands and pass it to the executor.
env merges over the shell's default per key; the default merges
nothing and so inherits it. Pass None to run without the shell's
configured environment, keeping whatever the host provides itself.
Source code in src/hostctl/shell/_common.py
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 | |
session(*cmds, executable=None, cwd=None, env=_INHERIT_ENV, terminal=None, encoding=None, errors=None)
Open a persistent process using this shell language.
env merges over the shell's default per key; the default merges
nothing and so inherits it. Pass None to open the session without
the shell's configured environment, keeping whatever the host
provides itself.
Source code in src/hostctl/shell/_common.py
568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 | |
hostctl.ShellSession(flavour, process)
Bases: Process
A persistent process with shell-aware command submission.
Source code in src/hostctl/shell/_common.py
76 77 78 | |
send(*cmds, cwd=None, env=None)
Render and submit commands using this session's shell language.
Source code in src/hostctl/shell/_common.py
84 85 86 87 88 89 90 91 92 93 94 95 96 | |
hostctl.ShellOperator
Bases: Enum
hostctl.register_shell_flavour(value, *, name=None, replace=False)
Register a configured flavour instance for string-based selection.
Source code in src/hostctl/shell/__init__.py
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | |
hostctl.shell_flavour(value)
Normalize a public shell selection without inferring from the transport.
Source code in src/hostctl/shell/__init__.py
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 | |
Serial consoles
hostctl.SerialConsoleProtocol
Bases: Protocol
Runtime contract implemented by serial console profiles.
hostctl.RawConsoleProfile
Raw byte console; it supports interactive sessions but not run.
hostctl.PromptConsoleProfile(prompt, *, login=(), line_terminator=b'\r\n', error_patterns=(), status_marker=None, reliable_status=False, max_buffer=64 * 1024, read_size=4096, wakeup=b'\r', echo=True, paging_prompt=None, paging_continue=b' ', paging_disable=None, max_paging_pages=32, terminal_setup=None, status_parser=None)
Configurable prompt/login framing for terminal-like devices.
A profile only advertises run when reliable_status is explicitly
enabled. Without a real status marker a prompt can delimit output, but it
cannot truthfully represent a process exit status.
Source code in src/hostctl/serial/__init__.py
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 | |
hostctl.LoginStep(expect, send, secret=False)
dataclass
One bounded expect/send step used by :class:PromptConsoleProfile.
hostctl.ConsoleProtocolError
Bases: ConnectionError
The console did not complete the expected protocol exchange.
Provider composition
hostctl.ExecutorProvider(name, executor, *, capabilities=None, probe=None)
Named callable executor with a conservative probe hook.
Source code in src/hostctl/provider/_common.py
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | |
hostctl.PathProvider(name, factory, *, capabilities=None, probe=None)
Named path factory. path must return a pathlib_next.Path.
Source code in src/hostctl/provider/_common.py
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 | |
hostctl.ProviderProbe(availability, reason='', capabilities=frozenset(), system_hint=None)
dataclass
hostctl.ProviderSelection(provider, trace, generation=0, policy='ordered', pinned=False)
dataclass
hostctl.ProviderSelector(providers=())
Ordered selector which never retries a dispatched operation.
Source code in src/hostctl/provider/_common.py
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 | |
declines
property
Providers that refused before dispatch this generation, by name.
Maps provider name to the redacted refusal reason. A copy: mutating the result does not change selector state.
generation
property
The current connection generation; probes are cached per value.
trace
property
The accumulated trace for the operation currently being selected.
decline(name, reason='declined before dispatch')
Record that a provider refused before dispatch this generation.
A provider which raised OperationNotStarted proved that nothing
started, so it is safe to skip it for the rest of this generation
rather than re-attempting it on every later operation. The record is
cleared by :meth:invalidate along with the probe cache.
Source code in src/hostctl/provider/_common.py
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 | |
invalidate()
Start a new generation, dropping cached probes and declines.
Source code in src/hostctl/provider/_common.py
445 446 447 448 449 450 451 452 | |
probe(provider)
Return a provider probe cached for this selector generation.
A provider that declined before dispatch this generation reports as
unavailable with the refusal reason, overriding the cached probe.
The probe describes whether the transport could work; the decline is
the newer and stronger evidence that right now it does not, so any
caller reading availability (provider_details, capabilities)
sees the refusal instead of a stale available.
Source code in src/hostctl/provider/_common.py
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 | |
redact(value)
classmethod
Return a diagnostic string with credential-like values removed.
Best effort, and deliberately so. This recognizes credentials that
announce themselves -- a password=/token=/api_key=
assignment, or the userinfo half of a scheme://user:secret@host
URI. A bare positional secret (mysql -p hunter2, an argv element
that is only a token) carries no marker and cannot be detected by
inspection. Treat debug output as sensitive.
Source code in src/hostctl/provider/_common.py
288 289 290 291 292 293 294 295 296 297 298 299 | |
hostctl.OperationNotStarted(reason='operation was not started', *, cause=None)
Bases: RuntimeError
A provider declined before a remote operation could start.
Source code in src/hostctl/provider/_common.py
38 39 40 41 42 43 44 45 46 | |
hostctl.SessionInitializer(initialize, timeout=None)
dataclass
Optional post-connect bootstrap hook receiving the connected system host.
hostctl.register_system_provider(name, resolver)
Register a provider descriptor resolver for :class:SystemConfig.
Source code in src/hostctl/host/system.py
35 36 37 38 39 40 | |
hostctl.CompositePosixPath(*segments, **kwargs)
Bases: _CompositePathMixin, PosixPathname, Path
A provider-selecting path with POSIX syntax on every client OS.
Source code in src/hostctl/host/composite_path.py
504 505 506 | |
hostctl.CompositeWindowsPath(*segments, **kwargs)
Bases: _CompositePathMixin, WindowsPathname, Path
A provider-selecting path with Windows syntax on every client OS.
Source code in src/hostctl/host/composite_path.py
565 566 567 | |
Connection strings
hostctl.ConnectionString(value, *, scheme=None, host=None, port=None, username=None, password=None, extras=None, path=None, query=None, fragment=None, defaults=None)
dataclass
A connection target parsed from whatever a user typed.
ConnectionString("nas"), ConnectionString("nas:8443"), and
ConnectionString("wss://root:pw@nas:8443/api") all parse. A bare host is
not an invalid URI -- it is a URI with the scheme left off, which is what
people type on a command line.
Every field can also be supplied directly, and three layers decide each
one: an explicit argument wins, then whatever the string carried, then
defaults.
So scheme=, port=, and the rest are overrides, not defaults --
ConnectionString("wss://nas", scheme="ssh") is ssh, the same way
port= beats a port written in the string. Supplying a value only when
the string omits one is exactly what defaults is for:
ConnectionString("nas", scheme="wss") # wss://nas
ConnectionString("wss://nas", scheme="ssh") # ssh://nas
ConnectionString("wss://nas", defaults={"scheme": "ssh"}) # wss://nas
ConnectionString("nas:9", scheme="wss", port=1) # port=1 beats :9
defaults accepts a mapping or another ConnectionString, so a partially
filled one expresses "these settings unless the string says otherwise":
profile = ConnectionString("wss://root@nas", port=443)
ConnectionString("other", defaults=profile) # wss://root@other:443
Only fields a ConnectionString actually carries count as defaults, so an
empty path/query/fragment never overrides.
A port carries its own resolution strategy, wherever one is accepted --
as port=, in defaults, or written in the string. It may be:
- an
int, the port itself; - a callable
scheme -> port | None, asked once the scheme is settled; - anything indexable by scheme (a plain mapping included), so a caller passes its whole table without pre-selecting an entry;
None, meaning nothing was supplied here, so the next layer decides;False, meaning no port and no lookup -- stop here.
When no layer supplies one, netimps.get_default_port resolves the
scheme. It knows schemes the system services database does not
(ws/wss, socks5), and an application can register more with
netimps.register_port.
ConnectionString("nas", scheme="wss") # :443
ConnectionString("nas", scheme="wss", port=8443) # :8443
ConnectionString("nas", scheme="wss", port={"wss": 9}) # :9
ConnectionString("nas", scheme="wss", port=lambda s: 9) # :9
ConnectionString("nas", scheme="wss", port=False) # no port
A scheme a table or callable does not know is not an error -- it means
"no conventional port for this one", and the netimps fallback then
applies. A resolver that answers False declines that fallback, the same
as passing False directly.
The host keeps the spelling it was given. urlsplit().hostname is
case-folded, which is right for resolution and wrong for text rendered
back to a caller, so nasA stays nasA in host and in str(). DNS
treats the two as one name, so nothing about reaching the target changes.
Credentials never render. str() and repr() both emit the redacted
form -- the password is removed rather than masked, so the output stays a
valid, reusable connection string that cannot round-trip a wrong
credential. password is excluded from the generated repr, so a value
reaching a log line or a traceback frame does not leak it.
A password may carry credential extras after a newline, exactly as
:func:parse_credentials describes; extras holds them and password is
just the password. The separator may be written raw, since characters
urlsplit would silently delete are percent-encoded before parsing.
Source code in src/hostctl/host/_connection.py
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 | |
authority
property
The authority as it renders, without any password.
is_local
property
Whether this names the local machine, without resolving anything.
Deliberately does no I/O: this is called while a configuration is built, and that is network-free. Resolving would block, and would raise on a name that does not resolve -- so it could not even be used defensively.
An address literal is answered by netimps.is_local_address, which
covers loopback and addresses actually assigned to this machine --
10.0.0.5 is local when it is one of this host's own. A name is only
compared against the loopback spellings, since deciding anything more
about a name requires a lookup.
qsl
property
Query pairs in order, keeping repeats.
geturl()
The credential-free connection string, valid and reusable.
Source code in src/hostctl/host/_connection.py
275 276 277 278 279 | |
query_val(name, default=None)
The last value for name, or default. Last wins, as in a URI.
Source code in src/hostctl/host/_connection.py
292 293 294 295 296 297 298 | |
replace(**changes)
A copy with changes applied.
Built field by field rather than through dataclasses.replace, which
would re-invoke __init__ -- and __init__ parses a string, so it
does not accept the field names.
Source code in src/hostctl/host/_connection.py
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 | |
hostctl.redact_uri(uri)
Strip any password from uri, leaving a valid, reusable URI.
scheme://user:secret@host becomes scheme://user@host. The password is
removed rather than masked: a placeholder would make the URI round-trip
into a wrong credential if anything fed the rendered form back in, and
would not be a real password anyway. What comes out is the same canonical,
credential-free string a config renders, so it is safe to log, repr, or
hand back to HostConfig.
A URI with no password is returned unchanged.
This never raises. It is meant for error messages and log records, where
failing to render a diagnostic would be worse than rendering an odd one.
Characters urlsplit would delete (tab, CR, LF) are percent-encoded first,
the same as during dispatch, so a password written with a raw newline is
still recognized and removed rather than partly surviving into the output.
Source code in src/hostctl/host/_common.py
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 | |
hostctl.parse_credentials(password)
Split a password field into the password and any trailing extras.
A newline separates the password from additional credential values, one
per line, each key:value:
"hunter2" -> ("hunter2", {})
"hunter2\notp:123456" -> ("hunter2", {"otp": "123456"})
"hunter2\notp:123456\nrealm:CORP" -> ("hunter2", {"otp": ..., "realm": ...})
A bare name with no : is a flag -- it maps to the empty string, exactly
as name: does:
"hunter2\ninteractive" -> ("hunter2", {"interactive": ""})
This is how a second factor reaches a transport through a single password field -- a URI's userinfo, an environment variable, a prompt -- without every caller inventing its own encoding. A newline is assumed not to be a valid password character, which is the same assumption pytruenas makes.
Names are casefolded and stripped of surrounding whitespace. A value is
everything after the first :, taken verbatim -- it may contain
colons, and its leading and trailing whitespace is preserved, because a
secret may legitimately begin or end with a space and silently trimming
one would fail authentication with no visible cause. Blank lines are
ignored.
Source code in src/hostctl/host/_common.py
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 | |
hostctl.strict_uri_credentials(credentials, allowed)
Reject credentials which the selected implementation does not accept.
Source code in src/hostctl/host/_common.py
862 863 864 865 866 867 868 | |
hostctl.strict_uri_query(parsed, allowed)
Parse one selected implementation's query without ambiguity.
Source code in src/hostctl/host/_common.py
847 848 849 850 851 852 853 854 855 856 857 858 859 | |
hostctl.uri_host(host)
Bracket an IPv6 literal for use in a URI authority.
Source code in src/hostctl/host/_common.py
156 157 158 | |
hostctl.uri_hostname(parsed)
The host as it was written, not as urlsplit case-folded it.
urlsplit().hostname is lowercased by design -- DNS is case-insensitive,
so folding is right for resolution. It is wrong for text a caller will
see again. A config built from a URI stores this value and renders it back
through connection_uri, so reading .hostname there would make the
library echo a spelling the operator never typed, and nasA would reach
logs and HostInfo as nasa.
Use this in _from_parsed_uri wherever a host is stored. Presence checks
(if not parsed.hostname) can keep using .hostname -- emptiness does not
depend on case. Resolution is unaffected either way: DNS, SSH, and WinRM
all treat the two spellings as one name.
Returns "" for a URI with no host, matching hostname or "".
hostname is a case-folded substring of the authority (after any
userinfo), so locating it recovers the original text.
Source code in src/hostctl/host/_common.py
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 | |
Composing transports
hostctl.ssh_providers(config)
Build an executor and path provider sharing one SSH transport.
This is the supported way to compose SSH into a host you assemble
yourself, rather than taking the finished PosixHost that
SshConfig._create_host() returns:
executors, paths = [], []
run_provider, path_provider = ssh_providers(config.ssh)
executors.append(run_provider)
paths.append(path_provider)
Both providers must share one transport: that is what makes them share
a connection and a lifecycle, and SshExecutorProvider is the one that
owns close. Assembling them by hand from two transports type-checks and
silently opens two connections, only one of which is ever closed -- which
is precisely why this returns a pair instead of exposing the transport.
Source code in src/hostctl/host/_ssh.py
567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 | |
hostctl.winrm_providers(config)
Build an executor and path provider sharing one WinRM transport.
The SSH counterpart, hostctl.host.ssh_providers, documents why this
returns a pair rather than exposing the transport: both providers must
share one, and building them separately silently opens two.
Source code in src/hostctl/host/_winrm.py
993 994 995 996 997 998 999 1000 1001 1002 1003 | |
Transfers and checksums
hostctl.ProgressReader(reader, callback, *, total=None)
Wrap a binary reader and report (bytes_read, total_bytes).
Source code in src/hostctl/sync.py
102 103 104 105 106 107 108 109 110 111 112 113 114 | |
hostctl.host_checksum(*hosts, algorithm='md5', chunk_size=1024 * 1024)
Build a PathSyncer checksum using execution beside owned paths.
More than one host may be supplied for a cross-host sync. Paths which do
not belong to any supplied host are hashed through their binary open()
contract, preserving interoperability with arbitrary pathlib_next
implementations.
Source code in src/hostctl/sync.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 | |
hostctl.stat_checksum(entry)
Return the cached size and modification time without reading content.
This is rsync's quick check: no content is read and no command is run. Two caveats decide whether it suits a given sync.
It can miss a change which preserves both size and modification time.
It can also report a change which is not one, on interpreters where
pathlib_next.Path.copy() preserves st_mode but not timestamps: a
file this helper copies lands with a fresh modification time and compares
unequal on the next run, so the sync never settles. Python 3.14 added a
stdlib Path.copy() which does preserve timestamps, and a local path
resolves to it there, so the same sync converges. Because that difference
is version- and backend-dependent, use this helper where source
modification times are meaningful on both sides -- a tree replicated by
something which preserves them -- and prefer :func:host_checksum when
hostctl's own copies must converge to a no-op on every supported
interpreter.
Source code in src/hostctl/sync.py
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | |
Aliases and constants
HostPath is pathlib_next.Path — the type every usable host.path() returns,
re-exported so callers can annotate against it without depending on
pathlib_next by name.
ExecutorCommand (str | pathlib.PurePath | pathlib_next.Pathname) is the
command type accepted by Executor and Shell.execute().
ExecutorCapability is the enum whose .ARGS, .CWD, and .ENV members
declare native executor support. Its members subclass str, so
ExecutorCapability.CWD == "cwd": capability sets are compared as strings
throughout, which lets providers advertise transport-specific tokens
("runspace") alongside the named members in one vocabulary.
POSIX_SHELL and POWERSHELL are the built-in shell-flavour instances;
BASH, ZSH, FISH, CMD, and PWSH are the other ready-to-use flavours.
Any of them can be passed wherever a ShellFlavour is expected.
pypsrp_available() reports whether the optional PSRP extra is importable and
require_pypsrp() raises an actionable ImportError naming the extra when it
is not. __version__ contains the installed package version.