deepctl-cmd-debug-network 0.1.12__tar.gz → 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (14) hide show
  1. {deepctl_cmd_debug_network-0.1.12 → deepctl_cmd_debug_network-0.5.0}/PKG-INFO +11 -12
  2. {deepctl_cmd_debug_network-0.1.12 → deepctl_cmd_debug_network-0.5.0}/README.md +4 -5
  3. {deepctl_cmd_debug_network-0.1.12 → deepctl_cmd_debug_network-0.5.0}/pyproject.toml +11 -7
  4. {deepctl_cmd_debug_network-0.1.12 → deepctl_cmd_debug_network-0.5.0}/src/deepctl_cmd_debug_network/command.py +136 -54
  5. {deepctl_cmd_debug_network-0.1.12 → deepctl_cmd_debug_network-0.5.0}/src/deepctl_cmd_debug_network/models.py +3 -1
  6. {deepctl_cmd_debug_network-0.1.12 → deepctl_cmd_debug_network-0.5.0}/src/deepctl_cmd_debug_network.egg-info/PKG-INFO +11 -12
  7. deepctl_cmd_debug_network-0.5.0/src/deepctl_cmd_debug_network.egg-info/requires.txt +6 -0
  8. deepctl_cmd_debug_network-0.1.12/src/deepctl_cmd_debug_network.egg-info/requires.txt +0 -6
  9. {deepctl_cmd_debug_network-0.1.12 → deepctl_cmd_debug_network-0.5.0}/setup.cfg +0 -0
  10. {deepctl_cmd_debug_network-0.1.12 → deepctl_cmd_debug_network-0.5.0}/src/deepctl_cmd_debug_network/__init__.py +0 -0
  11. {deepctl_cmd_debug_network-0.1.12 → deepctl_cmd_debug_network-0.5.0}/src/deepctl_cmd_debug_network.egg-info/SOURCES.txt +0 -0
  12. {deepctl_cmd_debug_network-0.1.12 → deepctl_cmd_debug_network-0.5.0}/src/deepctl_cmd_debug_network.egg-info/dependency_links.txt +0 -0
  13. {deepctl_cmd_debug_network-0.1.12 → deepctl_cmd_debug_network-0.5.0}/src/deepctl_cmd_debug_network.egg-info/entry_points.txt +0 -0
  14. {deepctl_cmd_debug_network-0.1.12 → deepctl_cmd_debug_network-0.5.0}/src/deepctl_cmd_debug_network.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepctl-cmd-debug-network
3
- Version: 0.1.12
3
+ Version: 0.5.0
4
4
  Summary: Network debug subcommand for deepctl
5
5
  Author-email: Deepgram <devrel@deepgram.com>
6
6
  Maintainer-email: Deepgram <devrel@deepgram.com>
@@ -14,12 +14,12 @@ Classifier: Programming Language :: Python :: 3.11
14
14
  Classifier: Programming Language :: Python :: 3.12
15
15
  Requires-Python: >=3.10
16
16
  Description-Content-Type: text/markdown
17
- Requires-Dist: deepctl-core>=0.1.10
18
- Requires-Dist: click>=8.0.0
19
- Requires-Dist: rich>=13.0.0
20
- Requires-Dist: pydantic>=2.0.0
21
- Requires-Dist: httpx>=0.24.0
22
- Requires-Dist: requests<3.0,>=2.31
17
+ Requires-Dist: deepctl==0.5.0
18
+ Requires-Dist: deepctl-core==0.5.0
19
+ Requires-Dist: click<9,>=8.2.0
20
+ Requires-Dist: rich<16,>=13.0.0
21
+ Requires-Dist: pydantic<3,>=2.0.0
22
+ Requires-Dist: httpx<1,>=0.24.0
23
23
 
24
24
  # deepctl-cmd-debug-network
25
25
 
@@ -58,11 +58,10 @@ pipx run deepctl --help
58
58
 
59
59
  ## Dependencies
60
60
 
61
- - `click>=8.0.0`
62
- - `rich>=13.0.0`
63
- - `pydantic>=2.0.0`
64
- - `httpx>=0.24.0`
65
- - `requests>=2.31,<3.0`
61
+ - `click>=8.2.0,<9`
62
+ - `rich>=13.0.0,<16`
63
+ - `pydantic>=2.0.0,<3`
64
+ - `httpx>=0.24.0,<1`
66
65
 
67
66
  ## License
68
67
 
@@ -35,11 +35,10 @@ pipx run deepctl --help
35
35
 
36
36
  ## Dependencies
37
37
 
38
- - `click>=8.0.0`
39
- - `rich>=13.0.0`
40
- - `pydantic>=2.0.0`
41
- - `httpx>=0.24.0`
42
- - `requests>=2.31,<3.0`
38
+ - `click>=8.2.0,<9`
39
+ - `rich>=13.0.0,<16`
40
+ - `pydantic>=2.0.0,<3`
41
+ - `httpx>=0.24.0,<1`
43
42
 
44
43
  ## License
45
44
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "deepctl-cmd-debug-network"
7
- version = "0.1.12" # x-release-please-version
7
+ version = "0.5.0" # x-release-please-version
8
8
  description = "Network debug subcommand for deepctl"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -21,12 +21,12 @@ classifiers = [
21
21
  keywords = ["deepgram", "cli", "debug", "network", "diagnostics"]
22
22
  requires-python = ">=3.10"
23
23
  dependencies = [
24
- "deepctl-core>=0.1.10",
25
- "click>=8.0.0",
26
- "rich>=13.0.0",
27
- "pydantic>=2.0.0",
28
- "httpx>=0.24.0",
29
- "requests>=2.31,<3.0",
24
+ "deepctl==0.5.0", # x-release-please-version
25
+ "deepctl-core==0.5.0", # x-release-please-version
26
+ "click>=8.2.0,<9",
27
+ "rich>=13.0.0,<16",
28
+ "pydantic>=2.0.0,<3",
29
+ "httpx>=0.24.0,<1",
30
30
  ]
31
31
 
32
32
  [project.scripts]
@@ -41,3 +41,7 @@ package-dir = { "" = "src" }
41
41
  [tool.setuptools.packages.find]
42
42
  where = ["src"]
43
43
  include = ["deepctl_cmd_debug_network*"]
44
+
45
+ [tool.uv.sources]
46
+ deepctl = { workspace = true }
47
+ deepctl-core = { workspace = true }
@@ -6,12 +6,9 @@ import socket
6
6
  import subprocess
7
7
  import sys
8
8
  import time
9
- import warnings
10
- from typing import Any
9
+ from typing import TYPE_CHECKING, Any
11
10
  from urllib.parse import urlparse
12
11
 
13
- import requests
14
- import urllib3
15
12
  from deepctl_core import AuthManager, BaseCommand, Config, DeepgramClient
16
13
  from rich.console import Console
17
14
  from rich.panel import Panel
@@ -28,18 +25,92 @@ from .models import (
28
25
  TLSTestResult,
29
26
  )
30
27
 
28
+ if TYPE_CHECKING:
29
+ import ssl
30
+
31
+ import httpx
32
+
31
33
  console = Console()
32
34
 
33
- # Default Deepgram endpoints to test
34
- DEEPGRAM_ENDPOINTS = {
35
- "api": "https://api.deepgram.com",
36
- "auth": "https://auth.dx.deepgram.com",
37
- }
35
+
36
+ def _default_host() -> str:
37
+ """The Deepgram API host (``hosts.DEFAULT_BASE_URL``, via the Transport).
38
+
39
+ The default endpoint only: this command sends no credentials anywhere.
40
+ """
41
+ from deepctl_core import transport
42
+
43
+ return transport.default_host()
44
+
45
+
46
+ def _is_ssl_error(exc: BaseException) -> bool:
47
+ """True when an httpx error was caused by a TLS failure (the case
48
+ requests raised as SSLError)."""
49
+ import ssl
50
+
51
+ seen: set[int] = set()
52
+ current: BaseException | None = exc
53
+ while current is not None and id(current) not in seen:
54
+ if isinstance(current, ssl.SSLError):
55
+ return True
56
+ seen.add(id(current))
57
+ current = current.__cause__ or current.__context__
58
+ return False
59
+
60
+
61
+ def _requests_ca_bundle() -> str | None:
62
+ """The CA bundle requests would have used from the environment.
63
+
64
+ httpx reads SSL_CERT_FILE and SSL_CERT_DIR itself; when either is set it
65
+ wins. Otherwise REQUESTS_CA_BUNDLE, then CURL_CA_BUNDLE, as requests
66
+ resolved them (an empty value counts as unset).
67
+ """
68
+ if os.environ.get("SSL_CERT_FILE") or os.environ.get("SSL_CERT_DIR"):
69
+ return None
70
+ return (
71
+ os.environ.get("REQUESTS_CA_BUNDLE") or os.environ.get("CURL_CA_BUNDLE") or None
72
+ )
73
+
74
+
75
+ def _verify_setting(verify: bool) -> "bool | ssl.SSLContext":
76
+ """httpx's verify argument: False stays False; True becomes an SSL
77
+ context on the requests-style bundle when one is set in the
78
+ environment, and otherwise stays True (httpx defaults, which honour
79
+ SSL_CERT_FILE / SSL_CERT_DIR)."""
80
+ import ssl
81
+
82
+ if not verify:
83
+ return False
84
+ bundle = _requests_ca_bundle()
85
+ if bundle is None:
86
+ return True
87
+ if os.path.isdir(bundle):
88
+ return ssl.create_default_context(capath=bundle)
89
+ if not os.path.isfile(bundle):
90
+ # requests' wording for the same mistake.
91
+ raise OSError(
92
+ "Could not find a suitable TLS CA certificate bundle, "
93
+ f"invalid path: {bundle}"
94
+ )
95
+ return ssl.create_default_context(cafile=bundle)
96
+
97
+
98
+ def _dist_version(name: str) -> str:
99
+ """Installed version of a distribution, read without importing it."""
100
+ from importlib.metadata import PackageNotFoundError, version
101
+
102
+ try:
103
+ return version(name)
104
+ except PackageNotFoundError:
105
+ return "not installed"
38
106
 
39
107
 
40
108
  class NetworkCommand(BaseCommand):
41
109
  """Debug network connectivity issues with Deepgram services."""
42
110
 
111
+ # Tests swap in an httpx.MockTransport; None means the real network.
112
+ _transport: "httpx.BaseTransport | None" = None
113
+
43
114
  name = "network"
44
115
  help = "Debug network connectivity issues with Deepgram services"
45
116
  short_help = "Debug network issues"
@@ -64,7 +135,7 @@ class NetworkCommand(BaseCommand):
64
135
  return [
65
136
  {
66
137
  "names": ["--endpoint", "-e"],
67
- "help": ("Specific endpoint to test (default: api.deepgram.com)"),
138
+ "help": f"Specific endpoint to test (default: {_default_host()})",
68
139
  "type": str,
69
140
  "is_option": True,
70
141
  },
@@ -107,6 +178,29 @@ class NetworkCommand(BaseCommand):
107
178
  },
108
179
  ]
109
180
 
181
+ def _http_client(
182
+ self, *, timeout: float, verify: bool = True, follow_redirects: bool = False
183
+ ) -> "httpx.Client":
184
+ """A throwaway client for one diagnostic request.
185
+
186
+ Never the shared CLI client: it carries no credentials (httpx does
187
+ not read ~/.netrc), and a verify=False client must not outlive its
188
+ one test. trust_env applies HTTP(S)_PROXY / NO_PROXY as requests did.
189
+
190
+ CA trust, first match wins (verify=False skips all of it):
191
+ SSL_CERT_FILE or SSL_CERT_DIR (read by httpx), then
192
+ REQUESTS_CA_BUNDLE, then CURL_CA_BUNDLE, then httpx's default bundle.
193
+ """
194
+ import httpx
195
+
196
+ return httpx.Client(
197
+ timeout=timeout,
198
+ verify=_verify_setting(verify),
199
+ follow_redirects=follow_redirects,
200
+ trust_env=True,
201
+ transport=self._transport,
202
+ )
203
+
110
204
  def handle(
111
205
  self,
112
206
  config: Config,
@@ -115,7 +209,7 @@ class NetworkCommand(BaseCommand):
115
209
  **kwargs: Any,
116
210
  ) -> Any:
117
211
  """Handle network debug command execution."""
118
- endpoint = kwargs.get("endpoint") or "api.deepgram.com"
212
+ endpoint = kwargs.get("endpoint") or _default_host()
119
213
  verbose = kwargs.get("verbose", False)
120
214
  skip_commands = kwargs.get("skip_commands", False)
121
215
  timeout = kwargs.get("timeout", 10)
@@ -171,7 +265,7 @@ class NetworkCommand(BaseCommand):
171
265
  progress.remove_task(task)
172
266
 
173
267
  # 5. Python requests tests
174
- task = progress.add_task("Testing Python requests library...", total=None)
268
+ task = progress.add_task("Testing Python HTTP client...", total=None)
175
269
  self._test_python_requests(endpoint, result, timeout)
176
270
  progress.remove_task(task)
177
271
 
@@ -213,8 +307,9 @@ class NetworkCommand(BaseCommand):
213
307
  result.environment_info = {
214
308
  "python_version": sys.version,
215
309
  "platform": sys.platform,
216
- "requests_version": requests.__version__,
217
- "urllib3_version": getattr(urllib3, "__version__", "unknown"),
310
+ "requests_version": _dist_version("requests"),
311
+ "urllib3_version": _dist_version("urllib3"),
312
+ "httpx_version": _dist_version("httpx"),
218
313
  "proxy_env_vars": {
219
314
  "http_proxy": os.environ.get("http_proxy", "Not set"),
220
315
  "https_proxy": os.environ.get("https_proxy", "Not set"),
@@ -270,20 +365,19 @@ class NetworkCommand(BaseCommand):
270
365
 
271
366
  try:
272
367
  start_time = time.time()
273
- response = requests.get(
274
- f"https://{hostname}/", timeout=timeout, allow_redirects=False
275
- )
368
+ with self._http_client(timeout=timeout) as http:
369
+ response = http.get(f"https://{hostname}/")
276
370
  endpoint_test.response_time_ms = (time.time() - start_time) * 1000
277
371
  endpoint_test.status_code = response.status_code
278
372
  endpoint_test.reachable = True
279
373
  endpoint_test.ssl_valid = True
280
374
 
281
- except requests.exceptions.SSLError as e:
282
- endpoint_test.error = f"SSL Error: {e!s}"
283
- endpoint_test.ssl_valid = False
284
- result.network_issues_detected = True
285
375
  except Exception as e:
286
- endpoint_test.error = str(e)
376
+ if _is_ssl_error(e):
377
+ endpoint_test.error = f"SSL Error: {e!s}"
378
+ endpoint_test.ssl_valid = False
379
+ else:
380
+ endpoint_test.error = str(e)
287
381
  result.network_issues_detected = True
288
382
 
289
383
  result.endpoint_results.append(endpoint_test)
@@ -446,7 +540,8 @@ class NetworkCommand(BaseCommand):
446
540
  else:
447
541
  try:
448
542
  start_time = time.time()
449
- response = requests.head(url, timeout=5, allow_redirects=True)
543
+ with self._http_client(timeout=5, follow_redirects=True) as http:
544
+ response = http.head(url)
450
545
  endpoint_test.response_time_ms = (time.time() - start_time) * 1000
451
546
  endpoint_test.status_code = response.status_code
452
547
  endpoint_test.accessible = response.status_code < 400
@@ -460,7 +555,8 @@ class NetworkCommand(BaseCommand):
460
555
  def _test_python_requests(
461
556
  self, hostname: str, result: NetworkDebugResult, timeout: int
462
557
  ) -> None:
463
- """Test connectivity using Python requests library."""
558
+ """Test connectivity with Python's HTTP client (httpx), with and
559
+ without certificate verification. Each request gets its own client."""
464
560
  # Test with SSL verification enabled
465
561
  test_with_verify = PythonRequestsTest(
466
562
  url=f"https://{hostname}/", success=False, ssl_verify_enabled=True
@@ -468,21 +564,18 @@ class NetworkCommand(BaseCommand):
468
564
 
469
565
  try:
470
566
  start_time = time.time()
471
- response = requests.get(
472
- f"https://{hostname}/",
473
- timeout=timeout,
474
- verify=True,
475
- allow_redirects=False,
476
- )
567
+ with self._http_client(timeout=timeout, verify=True) as http:
568
+ response = http.get(f"https://{hostname}/")
477
569
  test_with_verify.response_time_ms = (time.time() - start_time) * 1000
478
570
  test_with_verify.status_code = response.status_code
479
571
  test_with_verify.success = True
480
572
 
481
- except requests.exceptions.SSLError as e:
482
- test_with_verify.error = f"SSL verification failed: {e!s}"
483
- result.network_issues_detected = True
484
573
  except Exception as e:
485
- test_with_verify.error = str(e)
574
+ if _is_ssl_error(e):
575
+ test_with_verify.error = f"SSL verification failed: {e!s}"
576
+ result.network_issues_detected = True
577
+ else:
578
+ test_with_verify.error = str(e)
486
579
 
487
580
  result.python_requests_tests.append(test_with_verify)
488
581
 
@@ -492,25 +585,17 @@ class NetworkCommand(BaseCommand):
492
585
  )
493
586
 
494
587
  try:
495
- # Disable SSL warnings temporarily
496
- urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
497
-
588
+ # An isolated, credential-free client that is closed right after
589
+ # this one request; nothing else ever sees verify=False.
498
590
  start_time = time.time()
499
- response = requests.get(
500
- f"https://{hostname}/",
501
- timeout=timeout,
502
- verify=False,
503
- allow_redirects=False,
504
- )
591
+ with self._http_client(timeout=timeout, verify=False) as http:
592
+ response = http.get(f"https://{hostname}/")
505
593
  test_without_verify.response_time_ms = (time.time() - start_time) * 1000
506
594
  test_without_verify.status_code = response.status_code
507
595
  test_without_verify.success = True
508
596
 
509
597
  except Exception as e:
510
598
  test_without_verify.error = str(e)
511
- finally:
512
- # Re-enable warnings
513
- warnings.resetwarnings()
514
599
 
515
600
  result.python_requests_tests.append(test_without_verify)
516
601
 
@@ -663,7 +748,7 @@ class NetworkCommand(BaseCommand):
663
748
  ) -> None:
664
749
  """Test latency to known Deepgram endpoints."""
665
750
  endpoints = [
666
- ("api.deepgram.com", "Primary API"),
751
+ (_default_host(), "Primary API"),
667
752
  ]
668
753
 
669
754
  console.print("\n[bold]Multi-Region Latency:[/bold]")
@@ -694,11 +779,8 @@ class NetworkCommand(BaseCommand):
694
779
 
695
780
  # HTTPS connection timing
696
781
  connect_start = time.time()
697
- resp = requests.get(
698
- f"https://{hostname}/",
699
- timeout=timeout,
700
- allow_redirects=False,
701
- )
782
+ with self._http_client(timeout=timeout) as http:
783
+ resp = http.get(f"https://{hostname}/")
702
784
  total_ms = (time.time() - connect_start) * 1000
703
785
  connect_ms = total_ms - dns_ms if total_ms and dns_ms else None
704
786
  tls_ms = connect_ms * 0.6 if connect_ms else None
@@ -845,7 +927,7 @@ class NetworkCommand(BaseCommand):
845
927
 
846
928
  # Python Requests Tests
847
929
  if result.python_requests_tests:
848
- console.print("[bold]Python Requests Library Tests:[/bold]")
930
+ console.print("[bold]Python HTTP Client Tests (httpx):[/bold]")
849
931
  for test in result.python_requests_tests:
850
932
  verify_status = (
851
933
  "with SSL verification"
@@ -976,7 +1058,7 @@ class NetworkCommand(BaseCommand):
976
1058
  recommendations.append(
977
1059
  "ℹ️ Proxy configuration detected. Ensure your proxy allows access to:"
978
1060
  )
979
- recommendations.append(" • api.deepgram.com (port 443)")
1061
+ recommendations.append(f" • {_default_host()} (port 443)")
980
1062
  recommendations.append(" • Certificate validation endpoints (port 80)")
981
1063
 
982
1064
  # Display recommendations
@@ -70,9 +70,11 @@ class TLSTestResult(BaseModel):
70
70
 
71
71
 
72
72
  class PythonRequestsTest(BaseModel):
73
- """Result of testing with Python requests library."""
73
+ """Result of one Python HTTP client request (the payload keeps its
74
+ requests-era name; ``library`` says which client made it)."""
74
75
 
75
76
  url: str
77
+ library: str = "httpx"
76
78
  success: bool
77
79
  status_code: int | None = None
78
80
  response_time_ms: float | None = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepctl-cmd-debug-network
3
- Version: 0.1.12
3
+ Version: 0.5.0
4
4
  Summary: Network debug subcommand for deepctl
5
5
  Author-email: Deepgram <devrel@deepgram.com>
6
6
  Maintainer-email: Deepgram <devrel@deepgram.com>
@@ -14,12 +14,12 @@ Classifier: Programming Language :: Python :: 3.11
14
14
  Classifier: Programming Language :: Python :: 3.12
15
15
  Requires-Python: >=3.10
16
16
  Description-Content-Type: text/markdown
17
- Requires-Dist: deepctl-core>=0.1.10
18
- Requires-Dist: click>=8.0.0
19
- Requires-Dist: rich>=13.0.0
20
- Requires-Dist: pydantic>=2.0.0
21
- Requires-Dist: httpx>=0.24.0
22
- Requires-Dist: requests<3.0,>=2.31
17
+ Requires-Dist: deepctl==0.5.0
18
+ Requires-Dist: deepctl-core==0.5.0
19
+ Requires-Dist: click<9,>=8.2.0
20
+ Requires-Dist: rich<16,>=13.0.0
21
+ Requires-Dist: pydantic<3,>=2.0.0
22
+ Requires-Dist: httpx<1,>=0.24.0
23
23
 
24
24
  # deepctl-cmd-debug-network
25
25
 
@@ -58,11 +58,10 @@ pipx run deepctl --help
58
58
 
59
59
  ## Dependencies
60
60
 
61
- - `click>=8.0.0`
62
- - `rich>=13.0.0`
63
- - `pydantic>=2.0.0`
64
- - `httpx>=0.24.0`
65
- - `requests>=2.31,<3.0`
61
+ - `click>=8.2.0,<9`
62
+ - `rich>=13.0.0,<16`
63
+ - `pydantic>=2.0.0,<3`
64
+ - `httpx>=0.24.0,<1`
66
65
 
67
66
  ## License
68
67
 
@@ -0,0 +1,6 @@
1
+ deepctl==0.5.0
2
+ deepctl-core==0.5.0
3
+ click<9,>=8.2.0
4
+ rich<16,>=13.0.0
5
+ pydantic<3,>=2.0.0
6
+ httpx<1,>=0.24.0
@@ -1,6 +0,0 @@
1
- deepctl-core>=0.1.10
2
- click>=8.0.0
3
- rich>=13.0.0
4
- pydantic>=2.0.0
5
- httpx>=0.24.0
6
- requests<3.0,>=2.31