mostlyright-data 0.31.1__tar.gz → 0.31.2__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 (136) hide show
  1. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/PKG-INFO +1 -1
  2. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/transport.py +128 -1
  3. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/v4_public.py +17 -1
  4. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/.gitignore +0 -0
  5. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/README.md +0 -0
  6. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/pyproject.toml +0 -0
  7. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/scripts/hatch_build.py +0 -0
  8. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/SKILL.md +0 -0
  9. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/agents/openai.yaml +0 -0
  10. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/1-open-the-page-and-the-link-to-it-in-the-first-message.md +0 -0
  11. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/2-brief-two-to-four-questions-each-with-a-recommended-answer.md +0 -0
  12. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/3-probe-read-a-source-before-committing-to-it.md +0 -0
  13. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/4-decide-say-what-you-chose-what-you-refused-and-ask-one-question.md +0 -0
  14. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/5-draft-one-recipe-document-one-call.md +0 -0
  15. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/6-build-one-run-sized-to-acquire-every-measured-source-whole.md +0 -0
  16. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/7-interrogate-ask-the-run-what-it-actually-delivered.md +0 -0
  17. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/8-fix-revise-the-document-and-register-it-again.md +0 -0
  18. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/9-present-only-what-survived-inspection-with-caveats.md +0 -0
  19. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/agent-protocol.md +0 -0
  20. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/autonomous-delivery.md +0 -0
  21. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/before-the-first-tool-call.md +0 -0
  22. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/boundaries.md +0 -0
  23. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/cloud-authentication-preflight.md +0 -0
  24. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/commands.md +0 -0
  25. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/cross-repository-protocol-reference.md +0 -0
  26. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/enriching-with-small-models.md +0 -0
  27. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/installation-parity.md +0 -0
  28. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/live-run.md +0 -0
  29. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/making-a-table-incremental.md +0 -0
  30. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/narrating-the-run.md +0 -0
  31. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/not-hosted-yet.md +0 -0
  32. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/one-install.md +0 -0
  33. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/patterns.md +0 -0
  34. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/prediction-labels.md +0 -0
  35. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/preparing-table-search.md +0 -0
  36. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/promote.md +0 -0
  37. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/readers.md +0 -0
  38. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/receipts.md +0 -0
  39. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/recipe-test.md +0 -0
  40. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/recording-a-stream-venue.md +0 -0
  41. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/recovering-an-import-failure.md +0 -0
  42. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/reference-pages.md +0 -0
  43. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/required-protocol.md +0 -0
  44. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/source-credentials.md +0 -0
  45. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/sources.md +0 -0
  46. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/the-one-thing-to-say-about-the-skill-itself.md +0 -0
  47. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/transforms.md +0 -0
  48. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/user-communication-contract.md +0 -0
  49. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/references/writing-a-decision-record.md +0 -0
  50. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/skills/mr-data-build/scripts/write_research_notebook.py +0 -0
  51. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/__init__.py +0 -0
  52. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/agent_protocol.py +0 -0
  53. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/canonical.py +0 -0
  54. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/formats.py +0 -0
  55. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/hosted_crawler_protocol.py +0 -0
  56. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/key_seam.py +0 -0
  57. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/page_coverage.py +0 -0
  58. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/part_check_evidence.py +0 -0
  59. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/session_probes.py +0 -0
  60. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/skill_assets.py +0 -0
  61. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/table_manifest.py +0 -0
  62. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/__init__.py +0 -0
  63. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/acquire.py +0 -0
  64. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/acquire_cancel.py +0 -0
  65. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/activity.py +0 -0
  66. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/approvals.py +0 -0
  67. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/backoff.py +0 -0
  68. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/categories.py +0 -0
  69. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/commands.py +0 -0
  70. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/dataset-categories-v1.json +0 -0
  71. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/download.py +0 -0
  72. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/enrichment.py +0 -0
  73. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/fast_features.py +0 -0
  74. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/matching_benchmark.py +0 -0
  75. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/narrative.py +0 -0
  76. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/parity.py +0 -0
  77. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/probe.py +0 -0
  78. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/progress_rate.py +0 -0
  79. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/progress_vocabulary.py +0 -0
  80. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/propose.py +0 -0
  81. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/recipe.py +0 -0
  82. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/recipe_brief.py +0 -0
  83. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/recipe_explain.py +0 -0
  84. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/recipe_forecast.py +0 -0
  85. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/recipe_lint.py +0 -0
  86. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/recipe_test.py +0 -0
  87. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/refresh_rehearsal.py +0 -0
  88. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/release_check.py +0 -0
  89. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/research.py +0 -0
  90. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/router.py +0 -0
  91. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/run_diagnose.py +0 -0
  92. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/run_prefix.py +0 -0
  93. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/runs.py +0 -0
  94. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/session.py +0 -0
  95. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/stream.py +0 -0
  96. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/stream_venue.py +0 -0
  97. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/studio_capabilities.py +0 -0
  98. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/support.py +0 -0
  99. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/table_search_contract.json +0 -0
  100. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/user_agent.py +0 -0
  101. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/v4.py +0 -0
  102. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/v4_artifacts.py +0 -0
  103. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/v4_catalog.py +0 -0
  104. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/v4_connected.py +0 -0
  105. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/v4_connections.py +0 -0
  106. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/v4_dataset_covers.py +0 -0
  107. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/v4_datasets.py +0 -0
  108. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/v4_handoff.py +0 -0
  109. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/v4_matching.py +0 -0
  110. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/v4_narrative.py +0 -0
  111. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/v4_query.py +0 -0
  112. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/v4_reader.py +0 -0
  113. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/v4_runs.py +0 -0
  114. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/v4_search.py +0 -0
  115. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/v4_search_contract.py +0 -0
  116. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/v4_secrets.py +0 -0
  117. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/v4_stream.py +0 -0
  118. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/v4_tables.py +0 -0
  119. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/thin/vocabulary.py +0 -0
  120. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/ux/__init__.py +0 -0
  121. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/ux/attendance.py +0 -0
  122. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/ux/clarification.py +0 -0
  123. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/ux/cloud_auth.py +0 -0
  124. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/ux/commands/__init__.py +0 -0
  125. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/ux/commands/auth.py +0 -0
  126. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/ux/commands/clarify.py +0 -0
  127. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/ux/commands/login.py +0 -0
  128. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/ux/commands/whoami.py +0 -0
  129. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/ux/credential_native.py +0 -0
  130. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/ux/credential_store.py +0 -0
  131. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/ux/credentials.py +0 -0
  132. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/ux/login.py +0 -0
  133. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/ux/path_kind.py +0 -0
  134. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/ux/plain_file.py +0 -0
  135. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/ux/remediation.py +0 -0
  136. {mostlyright_data-0.31.1 → mostlyright_data-0.31.2}/src/mostlyright/data_harness/ux/render.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mostlyright-data
3
- Version: 0.31.1
3
+ Version: 0.31.2
4
4
  Summary: Mostly Right hosted CLI for reviewed datasets
5
5
  Project-URL: Homepage, https://mostlyright.md/
6
6
  Project-URL: Documentation, https://mostlyright.md/docs/guides/cli/
@@ -10,8 +10,12 @@ to relax any of it.
10
10
 
11
11
  from __future__ import annotations
12
12
 
13
+ import http.client
13
14
  import ipaddress
15
+ import math
14
16
  import ssl
17
+ import threading
18
+ import time
15
19
  from collections.abc import Callable, Iterator, Mapping
16
20
  from contextlib import contextmanager
17
21
  from typing import Any, Protocol
@@ -119,8 +123,9 @@ class UrlLibTransport:
119
123
 
120
124
  def __init__(self, *, timeout: float | Callable[[], float] = REQUEST_TIMEOUT_SECONDS) -> None:
121
125
  self._timeout = timeout if callable(timeout) else float(timeout)
126
+ self._context = ssl.create_default_context()
122
127
  self._opener = build_opener(
123
- ProxyHandler({}), HTTPSHandler(context=ssl.create_default_context()), _NoRedirect()
128
+ ProxyHandler({}), HTTPSHandler(context=self._context), _NoRedirect()
124
129
  )
125
130
 
126
131
  def _clock(self) -> float:
@@ -171,6 +176,128 @@ class UrlLibTransport:
171
176
  response.close()
172
177
 
173
178
 
179
+ class ReusableHttpsTransport(UrlLibTransport):
180
+ """One serialized, verified HTTPS connection for repeated query requests.
181
+
182
+ urllib deliberately sends Connection: close. A query client reuses its
183
+ own connection instead, never a global credential-bearing pool. Changing
184
+ origin closes the old socket. Responses are bounded and fully framed;
185
+ any failure discards the socket and is raised without replaying a POST.
186
+ Streams retain the original urllib/proxy-free/redirect-free behavior.
187
+ """
188
+
189
+ def __init__(self, *, timeout: float | Callable[[], float] = REQUEST_TIMEOUT_SECONDS) -> None:
190
+ super().__init__(timeout=timeout)
191
+ self._lock = threading.Lock()
192
+ self._connection: http.client.HTTPSConnection | None = None
193
+ self._connection_origin: tuple[str, str, int] | None = None
194
+
195
+ def _discard(self) -> None:
196
+ connection, self._connection = self._connection, None
197
+ self._connection_origin = None
198
+ if connection is not None:
199
+ connection.close()
200
+
201
+ def close(self) -> None:
202
+ with self._lock:
203
+ self._discard()
204
+
205
+ def __enter__(self) -> ReusableHttpsTransport:
206
+ return self
207
+
208
+ def __exit__(self, *_args: Any) -> None:
209
+ self.close()
210
+
211
+ def __del__(self) -> None:
212
+ connection = getattr(self, "_connection", None)
213
+ if connection is not None:
214
+ connection.close()
215
+
216
+ def request(
217
+ self, method: str, url: str, headers: Mapping[str, str], body: bytes | None, maximum: int
218
+ ) -> tuple[int, bytes, Mapping[str, str]]:
219
+ if type(maximum) is not int or maximum < 0:
220
+ raise ThinLaneError("THIN_REQUEST_INVALID", "the response ceiling must be nonnegative")
221
+ parts = urlsplit(url)
222
+ service_origin(
223
+ f"{parts.scheme}://{parts.netloc}", "request origin", allow_loopback_http=False
224
+ )
225
+ if parts.scheme != "https" or parts.fragment or parts.hostname is None:
226
+ raise ThinLaneError(
227
+ "THIN_REQUEST_INVALID", "a reusable request requires a valid HTTPS URL"
228
+ )
229
+ origin = (parts.scheme, parts.hostname.lower(), parts.port or 443)
230
+ if any(
231
+ name.lower() == "host" and value.lower() != parts.netloc.lower()
232
+ for name, value in headers.items()
233
+ ):
234
+ raise ThinLaneError(
235
+ "THIN_REQUEST_INVALID", "the Host header differs from the request origin"
236
+ )
237
+ budget = self._clock()
238
+ if not math.isfinite(budget) or budget <= 0:
239
+ raise TimeoutError("the request deadline has elapsed")
240
+ deadline = time.monotonic() + budget
241
+ if not self._lock.acquire(timeout=budget):
242
+ raise TimeoutError("the request deadline elapsed waiting for its connection")
243
+
244
+ def remaining() -> float:
245
+ value = deadline - time.monotonic()
246
+ if value <= 0:
247
+ raise TimeoutError("the request deadline has elapsed")
248
+ return value
249
+
250
+ try:
251
+ if self._connection_origin != origin:
252
+ self._discard()
253
+ if self._connection is None:
254
+ self._connection = http.client.HTTPSConnection(
255
+ origin[1], origin[2], timeout=remaining(), context=self._context
256
+ )
257
+ self._connection_origin = origin
258
+ connection = self._connection
259
+ connection.timeout = remaining()
260
+ if connection.sock is not None:
261
+ connection.sock.settimeout(remaining())
262
+ path = parts.path or "/"
263
+ if parts.query:
264
+ path += "?" + parts.query
265
+ connection.request(method, path, body=body, headers=dict(with_user_agent(headers)))
266
+ if connection.sock is not None:
267
+ connection.sock.settimeout(remaining())
268
+ response = connection.getresponse()
269
+ expected = response.length
270
+ chunks: list[bytes] = []
271
+ length = 0
272
+ while length <= maximum:
273
+ if connection.sock is not None:
274
+ connection.sock.settimeout(remaining())
275
+ chunk = response.read1(min(64 * 1024, maximum + 1 - length))
276
+ if not chunk:
277
+ break
278
+ chunks.append(chunk)
279
+ length += len(chunk)
280
+ raw = b"".join(chunks)
281
+ if length > maximum:
282
+ raise ThinLaneError(
283
+ "THIN_RESPONSE_TOO_LARGE",
284
+ "the response exceeded the byte ceiling for its route",
285
+ )
286
+ if expected is not None and length != expected:
287
+ raise http.client.IncompleteRead(raw, expected - length)
288
+ status = int(response.status)
289
+ response_headers = {key.lower(): value for key, value in response.headers.items()}
290
+ response.close()
291
+ if response.will_close or connection.sock is None or status < 200:
292
+ self._discard()
293
+ return status, raw, response_headers
294
+ except BaseException:
295
+ self._discard()
296
+ raise
297
+ finally:
298
+ self._lock.release()
299
+
300
+
174
301
  def service_origin(value: str, label: str, *, allow_loopback_http: bool) -> str:
175
302
  """Return ``value`` when it is a bare HTTPS origin, and refuse it otherwise.
176
303
 
@@ -46,6 +46,7 @@ from mostlyright.data_harness.thin.session import (
46
46
  )
47
47
  from mostlyright.data_harness.thin.transport import (
48
48
  MAX_JSON_RESPONSE_BYTES,
49
+ ReusableHttpsTransport,
49
50
  ThinLaneError,
50
51
  Transport,
51
52
  UrlLibTransport,
@@ -896,10 +897,25 @@ class PublicQueryClient(CloudClient):
896
897
  def __init__(self, credentials, *, transport=None) -> None:
897
898
  # Let Cloud's gateway deadline return its own refusal and request id first.
898
899
  super().__init__(credentials, transport=transport)
900
+ self._owned_query_transport = (
901
+ ReusableHttpsTransport(timeout=150.0) if transport is None else None
902
+ )
899
903
  self._query_client = CloudClient(
900
- credentials, transport=transport or UrlLibTransport(timeout=150.0)
904
+ credentials,
905
+ transport=transport if transport is not None else self._owned_query_transport,
901
906
  )
902
907
 
908
+ def close(self) -> None:
909
+ """Close this client's owned query connection; injected transports stay caller-owned."""
910
+ if self._owned_query_transport is not None:
911
+ self._owned_query_transport.close()
912
+
913
+ def __enter__(self) -> PublicQueryClient:
914
+ return self
915
+
916
+ def __exit__(self, *_args: Any) -> None:
917
+ self.close()
918
+
903
919
  def query_table(self, table_id: str, body: Mapping[str, Any]) -> dict[str, Any]:
904
920
  answer = self._query_client.call(
905
921
  "POST",