onvif-python 0.1.2__py3-none-any.whl → 0.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- onvif/cli/interactive.py +202 -6
- onvif/cli/main.py +77 -14
- onvif/cli/utils.py +803 -0
- onvif/operator.py +6 -0
- {onvif_python-0.1.2.dist-info → onvif_python-0.1.3.dist-info}/METADATA +14 -7
- {onvif_python-0.1.2.dist-info → onvif_python-0.1.3.dist-info}/RECORD +10 -10
- {onvif_python-0.1.2.dist-info → onvif_python-0.1.3.dist-info}/WHEEL +0 -0
- {onvif_python-0.1.2.dist-info → onvif_python-0.1.3.dist-info}/entry_points.txt +0 -0
- {onvif_python-0.1.2.dist-info → onvif_python-0.1.3.dist-info}/licenses/LICENSE.md +0 -0
- {onvif_python-0.1.2.dist-info → onvif_python-0.1.3.dist-info}/top_level.txt +0 -0
onvif/cli/interactive.py
CHANGED
|
@@ -21,6 +21,7 @@ from .utils import (
|
|
|
21
21
|
format_capabilities_as_services,
|
|
22
22
|
format_services_list,
|
|
23
23
|
get_method_documentation,
|
|
24
|
+
get_operation_type_info,
|
|
24
25
|
)
|
|
25
26
|
|
|
26
27
|
|
|
@@ -61,6 +62,7 @@ class InteractiveShell(cmd.Cmd):
|
|
|
61
62
|
"pwd",
|
|
62
63
|
"shortcuts",
|
|
63
64
|
"desc",
|
|
65
|
+
"type",
|
|
64
66
|
]
|
|
65
67
|
|
|
66
68
|
# For background health check
|
|
@@ -179,7 +181,7 @@ class InteractiveShell(cmd.Cmd):
|
|
|
179
181
|
" / __ \\/ | / / | / / _/ ____/",
|
|
180
182
|
" / / / / |/ /| | / // // /_ ",
|
|
181
183
|
"/ /_/ / /| / | |/ // // __/ ",
|
|
182
|
-
"\\____/_/ |_/ |___/___/_/ v0.1.
|
|
184
|
+
"\\____/_/ |_/ |___/___/_/ v0.1.3",
|
|
183
185
|
" ",
|
|
184
186
|
]
|
|
185
187
|
|
|
@@ -766,8 +768,9 @@ class InteractiveShell(cmd.Cmd):
|
|
|
766
768
|
|
|
767
769
|
if not self.current_service:
|
|
768
770
|
print(
|
|
769
|
-
f"{colorize('Error:', 'red')}
|
|
771
|
+
f"{colorize('Error:', 'red')} You must be in a service mode to use 'desc'."
|
|
770
772
|
)
|
|
773
|
+
print(f"Enter a service first (e.g., {colorize('devicemgmt', 'yellow')})")
|
|
771
774
|
return
|
|
772
775
|
|
|
773
776
|
if not method_name:
|
|
@@ -808,8 +811,175 @@ class InteractiveShell(cmd.Cmd):
|
|
|
808
811
|
f"No documentation or parameter info found for method '{method_name}'."
|
|
809
812
|
)
|
|
810
813
|
|
|
811
|
-
def
|
|
812
|
-
|
|
814
|
+
def complete_desc(self, text, line, begidx, endidx):
|
|
815
|
+
"""Autocomplete method names for desc command"""
|
|
816
|
+
if not self.current_service:
|
|
817
|
+
return []
|
|
818
|
+
methods = get_service_methods(self.current_service)
|
|
819
|
+
return [m for m in methods if m.lower().startswith(text.lower())]
|
|
820
|
+
|
|
821
|
+
def do_type(self, line):
|
|
822
|
+
"""Show input and output types for an operation. Usage: type <operation_name>"""
|
|
823
|
+
operation_name = line.strip()
|
|
824
|
+
|
|
825
|
+
if not self.current_service:
|
|
826
|
+
print(
|
|
827
|
+
f"{colorize('Error:', 'red')} You must be in a service mode to use 'type'."
|
|
828
|
+
)
|
|
829
|
+
print(f"Enter a service first (e.g., {colorize('d', 'yellow')})")
|
|
830
|
+
return
|
|
831
|
+
|
|
832
|
+
if not operation_name:
|
|
833
|
+
print(f"Usage: {colorize('type <operation_name>', 'yellow')}")
|
|
834
|
+
return
|
|
835
|
+
|
|
836
|
+
if not hasattr(self.current_service, operation_name):
|
|
837
|
+
print(
|
|
838
|
+
f"{colorize('Error:', 'red')} Operation '{operation_name}' not found in service '{self.current_service_name}'."
|
|
839
|
+
)
|
|
840
|
+
print(f"Use {colorize('ls', 'yellow')} to see available operations.")
|
|
841
|
+
return
|
|
842
|
+
|
|
843
|
+
type_info = get_operation_type_info(self.current_service, operation_name)
|
|
844
|
+
|
|
845
|
+
if type_info:
|
|
846
|
+
# Helper function to display parameters recursively with tree-style indentation
|
|
847
|
+
def display_params(params, prefix_lines=None, is_root_level=False):
|
|
848
|
+
"""
|
|
849
|
+
Display parameters with tree-style formatting.
|
|
850
|
+
|
|
851
|
+
Args:
|
|
852
|
+
params: List of parameter dictionaries
|
|
853
|
+
prefix_lines: List of strings representing the tree prefix for each line
|
|
854
|
+
is_root_level: If True, don't show tree characters at this level
|
|
855
|
+
"""
|
|
856
|
+
if prefix_lines is None:
|
|
857
|
+
prefix_lines = []
|
|
858
|
+
|
|
859
|
+
for idx, param in enumerate(params):
|
|
860
|
+
is_last = idx == len(params) - 1
|
|
861
|
+
|
|
862
|
+
# Determine tree characters (only if not root level)
|
|
863
|
+
if is_root_level:
|
|
864
|
+
tree_branch = ""
|
|
865
|
+
tree_continue = ""
|
|
866
|
+
else:
|
|
867
|
+
if is_last:
|
|
868
|
+
tree_branch = "└── "
|
|
869
|
+
tree_continue = " "
|
|
870
|
+
else:
|
|
871
|
+
tree_branch = "├── "
|
|
872
|
+
tree_continue = "│ "
|
|
873
|
+
|
|
874
|
+
# Determine prefix symbol: + for attributes, - for elements
|
|
875
|
+
prefix_symbol = "+" if param.get("is_attribute", False) else "-"
|
|
876
|
+
|
|
877
|
+
# Format occurrences based on type
|
|
878
|
+
occurs = ""
|
|
879
|
+
if param.get("is_attribute", False):
|
|
880
|
+
# For attributes: only show "required" if use="required"
|
|
881
|
+
# Default for attributes is optional (no label needed)
|
|
882
|
+
if param["minOccurs"] == "1": # This means use="required"
|
|
883
|
+
occurs = f" - {colorize('required', 'yellow')}"
|
|
884
|
+
# If minOccurs == "0", it's optional (default), so no label
|
|
885
|
+
else:
|
|
886
|
+
# For elements: show unbounded, optional, or required
|
|
887
|
+
if param["maxOccurs"] == "unbounded":
|
|
888
|
+
occurs = f" - {colorize('unbounded', 'magenta')}"
|
|
889
|
+
elif param["minOccurs"] == "0":
|
|
890
|
+
occurs = f" - {colorize('optional', 'green')}"
|
|
891
|
+
else:
|
|
892
|
+
occurs = f" - {colorize('required', 'yellow')}"
|
|
893
|
+
|
|
894
|
+
# Format type
|
|
895
|
+
type_str = f"[{param['type']}]" if param["type"] else ""
|
|
896
|
+
|
|
897
|
+
# Build the current line prefix from all previous levels
|
|
898
|
+
current_prefix = "".join(prefix_lines)
|
|
899
|
+
|
|
900
|
+
# Add spacing for root level items
|
|
901
|
+
if is_root_level:
|
|
902
|
+
current_prefix = " "
|
|
903
|
+
|
|
904
|
+
# Display parameter name with tree structure, prefix, occurrence, and type
|
|
905
|
+
# Only add semicolon separator if there's an occurrence label
|
|
906
|
+
separator = ";" if occurs else ""
|
|
907
|
+
param_line = f"{current_prefix}{tree_branch}{prefix_symbol}{colorize(param['name'], 'white')}{occurs}{separator} {colorize(type_str, 'cyan')}"
|
|
908
|
+
print(param_line)
|
|
909
|
+
|
|
910
|
+
# Display documentation if available
|
|
911
|
+
if param.get("documentation"):
|
|
912
|
+
doc_lines = param["documentation"].split("\n")
|
|
913
|
+
for doc_line in doc_lines:
|
|
914
|
+
if doc_line.strip():
|
|
915
|
+
# Add tree continuation for documentation
|
|
916
|
+
if is_root_level:
|
|
917
|
+
doc_prefix = " "
|
|
918
|
+
else:
|
|
919
|
+
doc_prefix = current_prefix + tree_continue
|
|
920
|
+
wrapped_doc = textwrap.fill(
|
|
921
|
+
doc_line.strip(),
|
|
922
|
+
width=96, # Slightly less to account for tree chars
|
|
923
|
+
initial_indent=doc_prefix + " ",
|
|
924
|
+
subsequent_indent=doc_prefix + " ",
|
|
925
|
+
)
|
|
926
|
+
print(colorize(wrapped_doc, "reset"))
|
|
927
|
+
|
|
928
|
+
# Display children recursively with updated prefix
|
|
929
|
+
if param.get("children"):
|
|
930
|
+
if is_root_level:
|
|
931
|
+
# Start tree structure from children
|
|
932
|
+
new_prefix_lines = [" "]
|
|
933
|
+
else:
|
|
934
|
+
new_prefix_lines = prefix_lines + [tree_continue]
|
|
935
|
+
display_params(
|
|
936
|
+
param["children"], new_prefix_lines, is_root_level=False
|
|
937
|
+
)
|
|
938
|
+
|
|
939
|
+
# Display Input
|
|
940
|
+
if type_info["input"]:
|
|
941
|
+
input_msg = type_info["input"]
|
|
942
|
+
print(f"\n{colorize('Input:', 'cyan')}")
|
|
943
|
+
msg_name = f"[{input_msg['name']}]"
|
|
944
|
+
print(f"{colorize(msg_name, 'yellow')}")
|
|
945
|
+
|
|
946
|
+
if input_msg["parameters"]:
|
|
947
|
+
display_params(input_msg["parameters"], is_root_level=True)
|
|
948
|
+
else:
|
|
949
|
+
print(f" {colorize('(no parameters)', 'reset')}")
|
|
950
|
+
else:
|
|
951
|
+
print(
|
|
952
|
+
f"\n{colorize('Input:', 'cyan')} {colorize('(not defined)', 'reset')}"
|
|
953
|
+
)
|
|
954
|
+
|
|
955
|
+
# Display Output
|
|
956
|
+
if type_info["output"]:
|
|
957
|
+
output_msg = type_info["output"]
|
|
958
|
+
print(f"\n{colorize('Output:', 'cyan')}")
|
|
959
|
+
msg_name = f"[{output_msg['name']}]"
|
|
960
|
+
print(f"{colorize(msg_name, 'yellow')}")
|
|
961
|
+
|
|
962
|
+
if output_msg["parameters"]:
|
|
963
|
+
display_params(output_msg["parameters"], is_root_level=True)
|
|
964
|
+
else:
|
|
965
|
+
print(f" {colorize('(no parameters)', 'reset')}")
|
|
966
|
+
else:
|
|
967
|
+
print(
|
|
968
|
+
f"\n{colorize('Output:', 'cyan')} {colorize('(not defined)', 'reset')}"
|
|
969
|
+
)
|
|
970
|
+
|
|
971
|
+
print() # Add newline for spacing
|
|
972
|
+
else:
|
|
973
|
+
print(
|
|
974
|
+
f"{colorize('Error:', 'red')} Could not retrieve type information for '{operation_name}'."
|
|
975
|
+
)
|
|
976
|
+
|
|
977
|
+
def complete_type(self, text, line, begidx, endidx):
|
|
978
|
+
"""Autocomplete method names for type command"""
|
|
979
|
+
if not self.current_service:
|
|
980
|
+
return []
|
|
981
|
+
methods = get_service_methods(self.current_service)
|
|
982
|
+
return [m for m in methods if m.lower().startswith(text.lower())]
|
|
813
983
|
|
|
814
984
|
def do_cd(self, line):
|
|
815
985
|
"""Change to service directory (alias for entering service)"""
|
|
@@ -821,6 +991,11 @@ class InteractiveShell(cmd.Cmd):
|
|
|
821
991
|
return
|
|
822
992
|
return self.do_enter_service(line)
|
|
823
993
|
|
|
994
|
+
def complete_cd(self, text, line, begidx, endidx):
|
|
995
|
+
"""Autocomplete service names for cd command"""
|
|
996
|
+
services = get_device_available_services(self.client)
|
|
997
|
+
return [s for s in services if s.lower().startswith(text.lower())]
|
|
998
|
+
|
|
824
999
|
def do_pwd(self, line):
|
|
825
1000
|
"""Show current service context"""
|
|
826
1001
|
if self.current_service_name:
|
|
@@ -844,7 +1019,11 @@ class InteractiveShell(cmd.Cmd):
|
|
|
844
1019
|
up - Go up one level
|
|
845
1020
|
pwd - Show current context
|
|
846
1021
|
clear - Clear terminal screen
|
|
847
|
-
|
|
1022
|
+
help <command> - Show help for a command
|
|
1023
|
+
|
|
1024
|
+
{colorize('Service Mode Commands:', 'yellow')}
|
|
1025
|
+
desc <method> - Show method documentation
|
|
1026
|
+
type <method> - Show input/output types from WSDL
|
|
848
1027
|
|
|
849
1028
|
{colorize('Quick Access:', 'yellow')}
|
|
850
1029
|
caps - Show capabilities (same as 'capabilities')
|
|
@@ -925,6 +1104,10 @@ class InteractiveShell(cmd.Cmd):
|
|
|
925
1104
|
else:
|
|
926
1105
|
print(f"{colorize('Error:', 'red')} No stored data named '{line}'")
|
|
927
1106
|
|
|
1107
|
+
def complete_rm(self, text, line, begidx, endidx):
|
|
1108
|
+
"""Autocomplete stored variable names for rm command"""
|
|
1109
|
+
return [name for name in self.stored_data.keys() if name.startswith(text)]
|
|
1110
|
+
|
|
928
1111
|
def do_show(self, line):
|
|
929
1112
|
"""Show stored data: show <name> or show <name>.<attribute> or show <name>[index]"""
|
|
930
1113
|
if not line:
|
|
@@ -947,6 +1130,15 @@ class InteractiveShell(cmd.Cmd):
|
|
|
947
1130
|
else:
|
|
948
1131
|
print(f"{colorize('Error:', 'red')} Cannot resolve '{line}'")
|
|
949
1132
|
|
|
1133
|
+
def complete_show(self, text, line, begidx, endidx):
|
|
1134
|
+
"""Autocomplete stored variable names for show command"""
|
|
1135
|
+
# Get the part being completed
|
|
1136
|
+
parts = line.split()
|
|
1137
|
+
if len(parts) <= 1 or (len(parts) == 2 and not line.endswith(" ")):
|
|
1138
|
+
# Completing the variable name
|
|
1139
|
+
return [name for name in self.stored_data.keys() if name.startswith(text)]
|
|
1140
|
+
return []
|
|
1141
|
+
|
|
950
1142
|
def do_clear(self, line):
|
|
951
1143
|
"""Clear terminal screen"""
|
|
952
1144
|
import os
|
|
@@ -1109,7 +1301,11 @@ class InteractiveShell(cmd.Cmd):
|
|
|
1109
1301
|
up - Exit current service mode (go up one level)
|
|
1110
1302
|
pwd - Show current service context
|
|
1111
1303
|
clear - Clear terminal screen
|
|
1112
|
-
|
|
1304
|
+
help <command> - Show help for a specific command
|
|
1305
|
+
|
|
1306
|
+
{colorize('Service Mode Commands:', 'yellow')}
|
|
1307
|
+
desc <method> - Show method documentation
|
|
1308
|
+
type <method> - Show input/output types from WSDL
|
|
1113
1309
|
|
|
1114
1310
|
{colorize('Method Execution:', 'yellow')}
|
|
1115
1311
|
<method> - Execute method without parameters
|
onvif/cli/main.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
4
|
import sys
|
|
5
|
+
import warnings
|
|
5
6
|
import getpass
|
|
6
7
|
from typing import Any, Optional, Tuple
|
|
7
8
|
|
|
@@ -111,6 +112,9 @@ Examples:
|
|
|
111
112
|
|
|
112
113
|
def main():
|
|
113
114
|
"""Main CLI entry point"""
|
|
115
|
+
# Setup custom warning format for cleaner output
|
|
116
|
+
setup_warning_format()
|
|
117
|
+
|
|
114
118
|
parser = create_parser()
|
|
115
119
|
|
|
116
120
|
# Check if no arguments provided at all
|
|
@@ -127,8 +131,8 @@ def main():
|
|
|
127
131
|
f"{colorize('--discover', 'white')} cannot be used with {colorize('--host', 'white')}"
|
|
128
132
|
)
|
|
129
133
|
|
|
130
|
-
# Discover devices
|
|
131
|
-
devices = discover_devices(timeout=4)
|
|
134
|
+
# Discover devices (pass --https flag to prioritize HTTPS XAddrs)
|
|
135
|
+
devices = discover_devices(timeout=4, prefer_https=args.https)
|
|
132
136
|
|
|
133
137
|
if not devices:
|
|
134
138
|
print(f"\n{colorize('No ONVIF devices discovered. Exiting.', 'red')}")
|
|
@@ -141,8 +145,11 @@ def main():
|
|
|
141
145
|
print(f"{colorize('Device selection cancelled.', 'cyan')}")
|
|
142
146
|
sys.exit(0)
|
|
143
147
|
|
|
144
|
-
# Set host and
|
|
145
|
-
args.host, args.port = selected
|
|
148
|
+
# Set host, port, and HTTPS from selected device
|
|
149
|
+
args.host, args.port, device_use_https = selected
|
|
150
|
+
|
|
151
|
+
# Use device's detected protocol (already filtered by prefer_https in discover_devices)
|
|
152
|
+
# No need to override - device info already has correct protocol based on --https flag
|
|
146
153
|
|
|
147
154
|
# Validate that host is provided (either via --host or --discover)
|
|
148
155
|
if not args.host:
|
|
@@ -253,8 +260,16 @@ def execute_command(
|
|
|
253
260
|
return method(**params)
|
|
254
261
|
|
|
255
262
|
|
|
256
|
-
def discover_devices(timeout: int = 4) -> list:
|
|
257
|
-
"""Discover ONVIF devices on the network using WS-Discovery.
|
|
263
|
+
def discover_devices(timeout: int = 4, prefer_https: bool = False) -> list:
|
|
264
|
+
"""Discover ONVIF devices on the network using WS-Discovery.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
timeout: Discovery timeout in seconds
|
|
268
|
+
prefer_https: If True, prioritize HTTPS XAddrs when available
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
List of discovered devices with connection info
|
|
272
|
+
"""
|
|
258
273
|
import socket
|
|
259
274
|
import uuid
|
|
260
275
|
import xml.etree.ElementTree as ET
|
|
@@ -360,6 +375,7 @@ def discover_devices(timeout: int = 4) -> list:
|
|
|
360
375
|
"xaddrs": [],
|
|
361
376
|
"host": None,
|
|
362
377
|
"port": 80,
|
|
378
|
+
"use_https": False, # Auto-detect HTTPS from XAddrs
|
|
363
379
|
}
|
|
364
380
|
|
|
365
381
|
epr = probe_match.find(".//wsa:EndpointReference/wsa:Address", namespaces)
|
|
@@ -384,17 +400,39 @@ def discover_devices(timeout: int = 4) -> list:
|
|
|
384
400
|
if xaddrs_elem is not None and xaddrs_elem.text:
|
|
385
401
|
device_info["xaddrs"] = xaddrs_elem.text.split()
|
|
386
402
|
|
|
387
|
-
# Extract host and
|
|
403
|
+
# Extract host, port, and protocol from XAddrs
|
|
404
|
+
# Use prefer_https parameter to decide which XAddr to use
|
|
388
405
|
if device_info["xaddrs"]:
|
|
389
|
-
|
|
406
|
+
# Select XAddr based on prefer_https flag
|
|
407
|
+
if prefer_https:
|
|
408
|
+
# Try to find HTTPS XAddr first if prefer_https=True
|
|
409
|
+
https_xaddr = next(
|
|
410
|
+
(
|
|
411
|
+
x
|
|
412
|
+
for x in device_info["xaddrs"]
|
|
413
|
+
if x.startswith("https://")
|
|
414
|
+
),
|
|
415
|
+
None,
|
|
416
|
+
)
|
|
417
|
+
xaddr = https_xaddr or device_info["xaddrs"][0]
|
|
418
|
+
else:
|
|
419
|
+
# Use first XAddr (usually HTTP) if prefer_https=False
|
|
420
|
+
xaddr = device_info["xaddrs"][0]
|
|
421
|
+
|
|
390
422
|
if "://" in xaddr:
|
|
423
|
+
# Detect protocol
|
|
424
|
+
protocol = xaddr.split("://")[0]
|
|
425
|
+
device_info["use_https"] = protocol == "https"
|
|
426
|
+
|
|
427
|
+
# Extract host and port
|
|
391
428
|
parts = xaddr.split("://")[1].split("/")[0]
|
|
392
429
|
if ":" in parts:
|
|
393
430
|
device_info["host"] = parts.split(":")[0]
|
|
394
431
|
device_info["port"] = int(parts.split(":")[1])
|
|
395
432
|
else:
|
|
396
433
|
device_info["host"] = parts
|
|
397
|
-
|
|
434
|
+
# Set default port based on protocol
|
|
435
|
+
device_info["port"] = 443 if protocol == "https" else 80
|
|
398
436
|
|
|
399
437
|
if device_info["host"]:
|
|
400
438
|
devices.append(device_info)
|
|
@@ -405,8 +443,12 @@ def discover_devices(timeout: int = 4) -> list:
|
|
|
405
443
|
return devices
|
|
406
444
|
|
|
407
445
|
|
|
408
|
-
def select_device_interactive(devices: list) -> Optional[Tuple[str, int]]:
|
|
409
|
-
"""Display devices and allow user to select one interactively.
|
|
446
|
+
def select_device_interactive(devices: list) -> Optional[Tuple[str, int, bool]]:
|
|
447
|
+
"""Display devices and allow user to select one interactively.
|
|
448
|
+
|
|
449
|
+
Returns:
|
|
450
|
+
Tuple of (host, port, use_https) or None if cancelled
|
|
451
|
+
"""
|
|
410
452
|
if not devices:
|
|
411
453
|
print(f"\n{colorize('No ONVIF devices found.', 'red')}")
|
|
412
454
|
return None
|
|
@@ -415,8 +457,14 @@ def select_device_interactive(devices: list) -> Optional[Tuple[str, int]]:
|
|
|
415
457
|
|
|
416
458
|
for idx, device in enumerate(devices, 1):
|
|
417
459
|
idx_str = colorize(f"[{idx}]", "yellow")
|
|
460
|
+
protocol = "https" if device.get("use_https", False) else "http"
|
|
418
461
|
host_port = f"{device['host']}:{device['port']}"
|
|
419
|
-
|
|
462
|
+
protocol_indicator = (
|
|
463
|
+
colorize("🔒 HTTPS", "green")
|
|
464
|
+
if device.get("use_https", False)
|
|
465
|
+
else colorize("HTTP", "white")
|
|
466
|
+
)
|
|
467
|
+
print(f"\n{idx_str} {colorize(host_port, 'yellow')} ({protocol_indicator})")
|
|
420
468
|
print(f" [id] {device['epr']}")
|
|
421
469
|
|
|
422
470
|
if device["xaddrs"]:
|
|
@@ -454,11 +502,16 @@ def select_device_interactive(devices: list) -> Optional[Tuple[str, int]]:
|
|
|
454
502
|
idx = int(selection)
|
|
455
503
|
if 1 <= idx <= len(devices):
|
|
456
504
|
selected = devices[idx - 1]
|
|
505
|
+
protocol = "https" if selected.get("use_https", False) else "http"
|
|
457
506
|
host_port = f"{selected['host']}:{selected['port']}"
|
|
458
507
|
print(
|
|
459
|
-
f"\n{colorize('Selected:', 'green')} {colorize(host_port, 'yellow')}"
|
|
508
|
+
f"\n{colorize('Selected:', 'green')} {colorize(protocol, 'cyan')}://{colorize(host_port, 'yellow')}"
|
|
509
|
+
)
|
|
510
|
+
return (
|
|
511
|
+
selected["host"],
|
|
512
|
+
selected["port"],
|
|
513
|
+
selected.get("use_https", False),
|
|
460
514
|
)
|
|
461
|
-
return (selected["host"], selected["port"])
|
|
462
515
|
else:
|
|
463
516
|
print(f"{colorize('Invalid selection. Please try again.', 'red')}")
|
|
464
517
|
|
|
@@ -468,5 +521,15 @@ def select_device_interactive(devices: list) -> Optional[Tuple[str, int]]:
|
|
|
468
521
|
return None
|
|
469
522
|
|
|
470
523
|
|
|
524
|
+
def setup_warning_format():
|
|
525
|
+
"""Setup custom warning format to show clean, concise warnings"""
|
|
526
|
+
|
|
527
|
+
def custom_warning_format(message, category, filename, lineno, line=None):
|
|
528
|
+
# Show only the warning message without file path and line number
|
|
529
|
+
return f"{category.__name__}: {message}\n"
|
|
530
|
+
|
|
531
|
+
warnings.formatwarning = custom_warning_format
|
|
532
|
+
|
|
533
|
+
|
|
471
534
|
if __name__ == "__main__":
|
|
472
535
|
main()
|