snowflake-sandbox-python 0.2.1a1__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.
- snowflake/cli_sandbox/__init__.py +13 -0
- snowflake/cli_sandbox/_adapter.py +170 -0
- snowflake/cli_sandbox/_common.py +77 -0
- snowflake/cli_sandbox/_egress_flags.py +121 -0
- snowflake/cli_sandbox/_get_command.py +109 -0
- snowflake/cli_sandbox/_run_command.py +1091 -0
- snowflake/cli_sandbox/_shell_command.py +666 -0
- snowflake/cli_sandbox/_upload_plan.py +187 -0
- snowflake/cli_sandbox/commands.py +556 -0
- snowflake/cli_sandbox/plugin_spec.py +28 -0
- snowflake/cli_sandbox/py.typed +0 -0
- snowflake/sandbox/__init__.py +317 -0
- snowflake/sandbox/__main__.py +225 -0
- snowflake/sandbox/_ansi.py +206 -0
- snowflake/sandbox/_args.py +208 -0
- snowflake/sandbox/_assemble.py +256 -0
- snowflake/sandbox/_bundle.py +240 -0
- snowflake/sandbox/_connection_resolve.py +328 -0
- snowflake/sandbox/_deploy_spec.py +56 -0
- snowflake/sandbox/_diagnostics.py +501 -0
- snowflake/sandbox/_env.py +143 -0
- snowflake/sandbox/_files_mixin.py +280 -0
- snowflake/sandbox/_fs_ops.py +304 -0
- snowflake/sandbox/_globs.py +176 -0
- snowflake/sandbox/_hosts.py +110 -0
- snowflake/sandbox/_mcp_discovery.py +288 -0
- snowflake/sandbox/_mcp_status.py +183 -0
- snowflake/sandbox/_retry.py +94 -0
- snowflake/sandbox/_runtime/__init__.py +42 -0
- snowflake/sandbox/_runtime/_fs_helper.py +93 -0
- snowflake/sandbox/_runtime/_job_runner.py +111 -0
- snowflake/sandbox/_runtime/_protocol.py +53 -0
- snowflake/sandbox/_runtime/_shims.py +267 -0
- snowflake/sandbox/_sandbox_state.py +303 -0
- snowflake/sandbox/_session_registry.py +222 -0
- snowflake/sandbox/_sse.py +160 -0
- snowflake/sandbox/_stage.py +270 -0
- snowflake/sandbox/_sync_files_mixin.py +272 -0
- snowflake/sandbox/_sync_fs_ops.py +185 -0
- snowflake/sandbox/_sync_transport.py +737 -0
- snowflake/sandbox/_sync_watch.py +99 -0
- snowflake/sandbox/_transport.py +1366 -0
- snowflake/sandbox/_transport_errors.py +270 -0
- snowflake/sandbox/_upload_plan.py +497 -0
- snowflake/sandbox/_version.py +37 -0
- snowflake/sandbox/_watch.py +164 -0
- snowflake/sandbox/_wire.py +348 -0
- snowflake/sandbox/app.py +256 -0
- snowflake/sandbox/client.py +2356 -0
- snowflake/sandbox/config.py +1133 -0
- snowflake/sandbox/connect.py +288 -0
- snowflake/sandbox/deploy.py +499 -0
- snowflake/sandbox/egress.py +388 -0
- snowflake/sandbox/exceptions.py +253 -0
- snowflake/sandbox/exec_stream.py +264 -0
- snowflake/sandbox/files.py +547 -0
- snowflake/sandbox/function.py +567 -0
- snowflake/sandbox/image.py +46 -0
- snowflake/sandbox/jobs.py +649 -0
- snowflake/sandbox/lifecycle.py +67 -0
- snowflake/sandbox/log_stream.py +219 -0
- snowflake/sandbox/mcp.py +480 -0
- snowflake/sandbox/mount.py +161 -0
- snowflake/sandbox/py.typed +0 -0
- snowflake/sandbox/secret.py +244 -0
- snowflake/sandbox/session_app.py +244 -0
- snowflake/sandbox/shell.py +556 -0
- snowflake/sandbox/sync_client.py +2245 -0
- snowflake/sandbox/sync_exec_stream.py +238 -0
- snowflake/sandbox/sync_files.py +377 -0
- snowflake/sandbox/sync_log_stream.py +142 -0
- snowflake/sandbox/sync_shell.py +413 -0
- snowflake/sandbox/types.py +193 -0
- snowflake/sandbox/warm_session.py +700 -0
- snowflake_sandbox_python-0.2.1a1.dist-info/METADATA +339 -0
- snowflake_sandbox_python-0.2.1a1.dist-info/RECORD +80 -0
- snowflake_sandbox_python-0.2.1a1.dist-info/WHEEL +5 -0
- snowflake_sandbox_python-0.2.1a1.dist-info/entry_points.txt +2 -0
- snowflake_sandbox_python-0.2.1a1.dist-info/licenses/LICENSE +202 -0
- snowflake_sandbox_python-0.2.1a1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""Snowflake connector helper for code running *inside* a Snowflake/gVisor sandbox.
|
|
2
|
+
|
|
3
|
+
`get_snowflake_connection()` is the single public entry point, and it is supported
|
|
4
|
+
public API — it is what workload code inside a sandbox calls to reach Snowflake. It
|
|
5
|
+
reads the injected env vars and returns a live ``snowflake.connector`` connection.
|
|
6
|
+
|
|
7
|
+
**Its contract is what the platform provides, not the caller's intent.** The credential
|
|
8
|
+
is resolved in order: ``SNOWFLAKE_PAT``/``SNOWFLAKE_TOKEN`` in the environment, then a
|
|
9
|
+
token file (``SNOWFLAKE_TOKEN_FILE_PATH``, else ``/snowflake/session/token``) with
|
|
10
|
+
``SNOWFLAKE_ACCOUNT``, then the platform-rendered ``$SNOWFLAKE_HOME/connections.toml``.
|
|
11
|
+
Which one is present varies by deployment and is not the caller's business. It also
|
|
12
|
+
relies on the platform's egress-proxy CA at ``/etc/cng/proxy-ca.crt``. The token is **not** a real credential and is **not**
|
|
13
|
+
injected by Snowflake: it is a per-app placeholder minted by Snowflake's ``AppCredential``, and the
|
|
14
|
+
platform's outbound egress proxy swaps it for the account's real token on the way out
|
|
15
|
+
(the same brokering that keeps secrets out of the sandbox). None of that exists outside
|
|
16
|
+
a Snowflake sandbox, so this is **not** a general-purpose connector factory: calling it from
|
|
17
|
+
laptop or service code that is not running inside a sandbox is unsupported and will
|
|
18
|
+
either fail or, worse, widen that process's TLS trust (see `_patch_proxy_ca`) for no
|
|
19
|
+
reason. Use ``snowflake.connector.connect`` directly there.
|
|
20
|
+
|
|
21
|
+
So the narrowness here is a scope of *deployment*, not a scope of *seriousness*: this
|
|
22
|
+
is in-sandbox-only, not dev/test-only, and in-sandbox workload code is a production
|
|
23
|
+
caller. Please do not re-add a "dev / test use only" disclaimer — it would contradict
|
|
24
|
+
this module's place in the package's public ``__all__``.
|
|
25
|
+
|
|
26
|
+
This module has no framework dependency (no crewai, no langchain, no agents
|
|
27
|
+
layer) so it can be imported from any sandbox workload regardless of which
|
|
28
|
+
agent framework — or no framework at all — the workload uses.
|
|
29
|
+
|
|
30
|
+
``snowflake-connector-python`` is a **core** dependency of the SDK, so it is
|
|
31
|
+
always installed. Importing this module stays cheap regardless: the connector is
|
|
32
|
+
loaded lazily inside `get_snowflake_connection()`, not at import time.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
import os
|
|
38
|
+
import pathlib
|
|
39
|
+
from typing import Any
|
|
40
|
+
|
|
41
|
+
__all__ = ["get_snowflake_connection"]
|
|
42
|
+
|
|
43
|
+
# Connection parameters a caller may NOT override through get_snowflake_connection's
|
|
44
|
+
# **kwargs. These define *who* the connection authenticates as and *where* it
|
|
45
|
+
# connects; letting a caller (often an LLM assembling kwargs) redirect them would
|
|
46
|
+
# send the sandbox's brokered credential to an arbitrary host or swap the auth
|
|
47
|
+
# scheme. Everything else (warehouse, database, schema, role, session params, ...)
|
|
48
|
+
# is passed through.
|
|
49
|
+
_PROTECTED_CONNECT_KWARGS = frozenset(
|
|
50
|
+
{
|
|
51
|
+
"account",
|
|
52
|
+
"token",
|
|
53
|
+
"password",
|
|
54
|
+
"authenticator",
|
|
55
|
+
"host",
|
|
56
|
+
# connection_name / connections_file_path / token_file_path became
|
|
57
|
+
# live once the connections.toml path started passing connection_name:
|
|
58
|
+
# a caller-supplied TOML with a [default] entry would otherwise redirect
|
|
59
|
+
# account/host/token/authenticator wholesale.
|
|
60
|
+
"connection_name",
|
|
61
|
+
"connections_file_path",
|
|
62
|
+
"token_file_path",
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Default connection timeout (seconds). Without one the connector blocks
|
|
67
|
+
# indefinitely when egress is closed; a sandbox under a restrictive Egress policy
|
|
68
|
+
# would hang instead of failing.
|
|
69
|
+
_DEFAULT_LOGIN_TIMEOUT_S = 30
|
|
70
|
+
|
|
71
|
+
# Where a system CA bundle may live, in preference order. Debian/Ubuntu first,
|
|
72
|
+
# then RHEL/Fedora, then the BSD/macOS layout Alpine also uses.
|
|
73
|
+
_SYSTEM_CA_CANDIDATES = (
|
|
74
|
+
"/etc/ssl/certs/ca-certificates.crt",
|
|
75
|
+
"/etc/pki/tls/certs/ca-bundle.crt",
|
|
76
|
+
"/etc/ssl/cert.pem",
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _system_ca_bundle() -> str | None:
|
|
81
|
+
"""Path to the OS's CA bundle, or ``None`` if none of the known ones exist.
|
|
82
|
+
|
|
83
|
+
Falls back to ``certifi`` (a hard dependency of the connector) so a slim /
|
|
84
|
+
distroless / RHEL / Alpine image without the Debian bundle still resolves a
|
|
85
|
+
real trust store rather than silently dropping the system CAs.
|
|
86
|
+
"""
|
|
87
|
+
for path in _SYSTEM_CA_CANDIDATES:
|
|
88
|
+
if os.path.exists(path):
|
|
89
|
+
return path
|
|
90
|
+
try:
|
|
91
|
+
import certifi
|
|
92
|
+
|
|
93
|
+
return certifi.where()
|
|
94
|
+
except Exception: # noqa: BLE001 - certifi absent/broken; no system bundle
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _patch_proxy_ca() -> None:
|
|
99
|
+
"""Trust the Snowflake hostagent MITM CA alongside the system bundle.
|
|
100
|
+
|
|
101
|
+
When code runs inside a gVisor sandbox, the Snowflake hostagent intercepts
|
|
102
|
+
all outbound HTTPS and re-signs with its own CA at /etc/cng/proxy-ca.crt.
|
|
103
|
+
snowflake-connector-python uses requests/urllib3 internally; without this
|
|
104
|
+
patch every connector HTTPS call raises an SSL verification error.
|
|
105
|
+
|
|
106
|
+
**This widens TLS trust process-wide**: it sets ``REQUESTS_CA_BUNDLE`` and
|
|
107
|
+
``SSL_CERT_FILE`` for the whole process, so *every* library that reads those
|
|
108
|
+
env vars — not just this connection — will additionally trust the Snowflake MITM CA.
|
|
109
|
+
That is intended inside a Snowflake sandbox (all egress is proxied) but is why this
|
|
110
|
+
module is not for general use outside one.
|
|
111
|
+
|
|
112
|
+
Called once per process: if REQUESTS_CA_BUNDLE / SSL_CERT_FILE is already set,
|
|
113
|
+
or the proxy CA file doesn't exist (outside a Snowflake sandbox), this is a no-op.
|
|
114
|
+
"""
|
|
115
|
+
if os.environ.get("REQUESTS_CA_BUNDLE") or os.environ.get("SSL_CERT_FILE"):
|
|
116
|
+
return
|
|
117
|
+
proxy_ca = "/etc/cng/proxy-ca.crt"
|
|
118
|
+
if not os.path.exists(proxy_ca):
|
|
119
|
+
return
|
|
120
|
+
import tempfile
|
|
121
|
+
|
|
122
|
+
sys_ca = _system_ca_bundle()
|
|
123
|
+
if sys_ca is None:
|
|
124
|
+
# Trusting ONLY the proxy CA would silently drop every public root, so a
|
|
125
|
+
# Snowflake TLS handshake to a non-proxied host would fail with an opaque
|
|
126
|
+
# SSL error. Fail loudly instead — the fix (install ca-certificates or set
|
|
127
|
+
# REQUESTS_CA_BUNDLE) is actionable.
|
|
128
|
+
raise RuntimeError(
|
|
129
|
+
"get_snowflake_connection: no system CA bundle found (looked in "
|
|
130
|
+
f"{', '.join(_SYSTEM_CA_CANDIDATES)} and certifi). Cannot trust the "
|
|
131
|
+
"Snowflake proxy CA without dropping the system roots. Install a CA bundle "
|
|
132
|
+
"(e.g. ca-certificates) or set REQUESTS_CA_BUNDLE to a bundle that "
|
|
133
|
+
"already includes /etc/cng/proxy-ca.crt."
|
|
134
|
+
)
|
|
135
|
+
with open(sys_ca) as f:
|
|
136
|
+
combined = f.read()
|
|
137
|
+
with open(proxy_ca) as f:
|
|
138
|
+
combined += "\n" + f.read()
|
|
139
|
+
# delete=False so the bundle outlives this block; the context manager just
|
|
140
|
+
# closes the handle, and tmp.name stays valid for the env vars below.
|
|
141
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".pem", delete=False) as tmp:
|
|
142
|
+
tmp.write(combined)
|
|
143
|
+
os.environ["REQUESTS_CA_BUNDLE"] = tmp.name
|
|
144
|
+
os.environ["SSL_CERT_FILE"] = tmp.name
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# Where the platform leaves the sandbox's session token when it names no other path,
|
|
148
|
+
# and the connection entry it renders. Both are platform contracts, not guesses:
|
|
149
|
+
# sandbox-server's _render_snowflake_connection writes exactly one [default] entry.
|
|
150
|
+
_DEFAULT_TOKEN_FILE = "/snowflake/session/token"
|
|
151
|
+
_PLATFORM_CONNECTION_NAME = "default"
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _resolve_token() -> str | None:
|
|
155
|
+
"""The sandbox's session token, by whichever mechanism the platform used.
|
|
156
|
+
|
|
157
|
+
The env VALUE comes first (unchanged). Otherwise the token is a FILE: cng-explore
|
|
158
|
+
#1571 names it in SNOWFLAKE_TOKEN_FILE_PATH, and the well-known path is the
|
|
159
|
+
fallback. It is a per-app AppCredential placeholder, swapped for the real token by
|
|
160
|
+
the egress proxy on the way out — so reading it here is not handling a secret.
|
|
161
|
+
"""
|
|
162
|
+
env_value = os.environ.get("SNOWFLAKE_PAT") or os.environ.get("SNOWFLAKE_TOKEN")
|
|
163
|
+
if env_value:
|
|
164
|
+
return env_value
|
|
165
|
+
path = os.environ.get("SNOWFLAKE_TOKEN_FILE_PATH") or _DEFAULT_TOKEN_FILE
|
|
166
|
+
try:
|
|
167
|
+
token = pathlib.Path(path).read_text(encoding="utf-8").strip()
|
|
168
|
+
except (OSError, UnicodeDecodeError):
|
|
169
|
+
return None
|
|
170
|
+
return token or None
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _host_from_account_url() -> str | None:
|
|
174
|
+
"""SNOWFLAKE_ACCOUNT_URL as a bare host, since the connector wants no scheme."""
|
|
175
|
+
url = os.environ.get("SNOWFLAKE_ACCOUNT_URL", "").strip()
|
|
176
|
+
if not url:
|
|
177
|
+
return None
|
|
178
|
+
return url.split("://", 1)[-1].rstrip("/").split("/", 1)[0] or None
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _connections_toml() -> pathlib.Path | None:
|
|
182
|
+
"""The platform-rendered connections.toml, if SNOWFLAKE_HOME holds one."""
|
|
183
|
+
home = os.environ.get("SNOWFLAKE_HOME", "").strip()
|
|
184
|
+
if not home:
|
|
185
|
+
return None
|
|
186
|
+
path = pathlib.Path(home) / "connections.toml"
|
|
187
|
+
return path if path.exists() else None
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _make_connection(**kwargs: Any) -> Any:
|
|
191
|
+
"""Open a new Snowflake connection from env vars, merging ``kwargs`` overrides."""
|
|
192
|
+
protected = _PROTECTED_CONNECT_KWARGS & kwargs.keys()
|
|
193
|
+
if protected:
|
|
194
|
+
raise ValueError(
|
|
195
|
+
"get_snowflake_connection does not accept "
|
|
196
|
+
f"{sorted(protected)} as keyword overrides: the account, host, and "
|
|
197
|
+
"credential come from the sandbox's injected environment and must not "
|
|
198
|
+
"be redirected. Pass only non-credential options (warehouse, database, "
|
|
199
|
+
"schema, role, session parameters, ...)."
|
|
200
|
+
)
|
|
201
|
+
import snowflake.connector
|
|
202
|
+
|
|
203
|
+
account = os.environ.get("SNOWFLAKE_ACCOUNT")
|
|
204
|
+
token = _resolve_token()
|
|
205
|
+
if account and token:
|
|
206
|
+
params: dict[str, Any] = {
|
|
207
|
+
"account": account,
|
|
208
|
+
"token": token,
|
|
209
|
+
"authenticator": "oauth",
|
|
210
|
+
"login_timeout": _DEFAULT_LOGIN_TIMEOUT_S,
|
|
211
|
+
}
|
|
212
|
+
# SNOWFLAKE_HOST first, but ACCOUNT_URL is what today's platform actually sets
|
|
213
|
+
# and the host is not optional: without it the connector builds
|
|
214
|
+
# <account>.snowflakecomputing.com and fails 540001 on any non-default region.
|
|
215
|
+
host = os.environ.get("SNOWFLAKE_HOST") or _host_from_account_url()
|
|
216
|
+
if host:
|
|
217
|
+
params["host"] = host
|
|
218
|
+
for k, env in (
|
|
219
|
+
("user", "SNOWFLAKE_USER"),
|
|
220
|
+
("role", "SNOWFLAKE_ROLE"),
|
|
221
|
+
("warehouse", "SNOWFLAKE_WAREHOUSE"),
|
|
222
|
+
):
|
|
223
|
+
v = os.environ.get(env)
|
|
224
|
+
if v:
|
|
225
|
+
params[k] = v
|
|
226
|
+
params.update(kwargs)
|
|
227
|
+
return snowflake.connector.connect(**params)
|
|
228
|
+
|
|
229
|
+
# The platform's own rendered config. connection_name is passed explicitly because
|
|
230
|
+
# ANY kwarg otherwise suppresses the connector's default-connection resolution, and
|
|
231
|
+
# the caller may well pass warehouse= — which failed as 251005 "User is empty".
|
|
232
|
+
if _connections_toml() is not None:
|
|
233
|
+
# Merge like the branch above rather than splatting **kwargs alongside
|
|
234
|
+
# hard-coded keywords: a caller passing login_timeout= (documented as
|
|
235
|
+
# overridable) would otherwise be a duplicate-keyword TypeError.
|
|
236
|
+
params = {
|
|
237
|
+
"connection_name": _PLATFORM_CONNECTION_NAME,
|
|
238
|
+
"login_timeout": _DEFAULT_LOGIN_TIMEOUT_S,
|
|
239
|
+
}
|
|
240
|
+
params.update(kwargs)
|
|
241
|
+
return snowflake.connector.connect(**params)
|
|
242
|
+
|
|
243
|
+
raise RuntimeError(
|
|
244
|
+
"get_snowflake_connection found no usable credential. Looked for: "
|
|
245
|
+
"SNOWFLAKE_PAT/SNOWFLAKE_TOKEN in the environment; a token file at "
|
|
246
|
+
f"$SNOWFLAKE_TOKEN_FILE_PATH or {_DEFAULT_TOKEN_FILE} (with SNOWFLAKE_ACCOUNT "
|
|
247
|
+
f"set); and a [{_PLATFORM_CONNECTION_NAME}] entry in "
|
|
248
|
+
"$SNOWFLAKE_HOME/connections.toml. This helper is for code running INSIDE a "
|
|
249
|
+
"Snowflake sandbox, where the platform provides one of those."
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def get_snowflake_connection(**kwargs: Any) -> Any:
|
|
254
|
+
"""Return a Snowflake connection for code running inside a Snowflake sandbox.
|
|
255
|
+
|
|
256
|
+
Resolves the credential the way the platform provides it, in order: the
|
|
257
|
+
``SNOWFLAKE_PAT``/``SNOWFLAKE_TOKEN`` env value; a token file named by
|
|
258
|
+
``SNOWFLAKE_TOKEN_FILE_PATH`` or at ``/snowflake/session/token``, with
|
|
259
|
+
``SNOWFLAKE_ACCOUNT``; then a ``[default]`` entry in the platform-rendered
|
|
260
|
+
``$SNOWFLAKE_HOME/connections.toml``. Deployments differ in which they set, so
|
|
261
|
+
reading only one made this raise where a credential was in fact present.
|
|
262
|
+
|
|
263
|
+
The token is Snowflake's ``AppCredential`` placeholder, swapped for the real account
|
|
264
|
+
token by the platform's outbound egress proxy — not a credential Snowflake hands the
|
|
265
|
+
container. Non-credential keyword arguments override the corresponding default, e.g.
|
|
266
|
+
``get_snowflake_connection(warehouse="MY_WH", database="MY_DB")``.
|
|
267
|
+
|
|
268
|
+
The identity-bearing parameters — ``account``, ``host``, ``token``,
|
|
269
|
+
``password``, ``authenticator`` — cannot be overridden via ``**kwargs`` and
|
|
270
|
+
raise ``ValueError`` if passed: they come from the sandbox's injected
|
|
271
|
+
environment and must not be redirected to another host or auth scheme.
|
|
272
|
+
|
|
273
|
+
The platform's egress-proxy CA (``/etc/cng/proxy-ca.crt``) is automatically
|
|
274
|
+
trusted so HTTPS verification succeeds inside gVisor. **This widens TLS trust
|
|
275
|
+
process-wide** — it sets ``REQUESTS_CA_BUNDLE``/``SSL_CERT_FILE`` for the whole
|
|
276
|
+
process (system roots + the Snowflake CA), affecting every library that reads them,
|
|
277
|
+
which is why this helper is for in-sandbox use only. Outside a Snowflake sandbox the
|
|
278
|
+
proxy CA file doesn't exist and normal system CA verification is used. If no
|
|
279
|
+
system CA bundle can be found it raises rather than trust only the proxy CA
|
|
280
|
+
(which would drop the public roots).
|
|
281
|
+
|
|
282
|
+
A default ``login_timeout`` is applied so a closed-egress sandbox fails fast
|
|
283
|
+
instead of hanging; override it with ``login_timeout=`` if needed.
|
|
284
|
+
|
|
285
|
+
Each call returns a *new* connection; callers are responsible for closing it.
|
|
286
|
+
"""
|
|
287
|
+
_patch_proxy_ca()
|
|
288
|
+
return _make_connection(**kwargs)
|