rsconnect-python 1.30.0__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.
- rsconnect/__init__.py +13 -0
- rsconnect/actions.py +565 -0
- rsconnect/actions_content.py +508 -0
- rsconnect/actions_environment.py +160 -0
- rsconnect/actions_integration.py +118 -0
- rsconnect/api.py +2582 -0
- rsconnect/bundle.py +2481 -0
- rsconnect/certificates.py +39 -0
- rsconnect/environment.py +390 -0
- rsconnect/environment_node.py +115 -0
- rsconnect/environment_r.py +300 -0
- rsconnect/exception.py +15 -0
- rsconnect/git_metadata.py +180 -0
- rsconnect/http_support.py +595 -0
- rsconnect/json_web_token.py +178 -0
- rsconnect/log.py +253 -0
- rsconnect/main.py +5889 -0
- rsconnect/metadata.py +879 -0
- rsconnect/models.py +835 -0
- rsconnect/oauth.py +623 -0
- rsconnect/py.typed +0 -0
- rsconnect/pyproject.py +283 -0
- rsconnect/quickstart/__init__.py +16 -0
- rsconnect/quickstart/quickstart.py +486 -0
- rsconnect/quickstart/templates/__init__.py +16 -0
- rsconnect/quickstart/templates/api/README.md.tmpl +15 -0
- rsconnect/quickstart/templates/api/__connect__.py.tmpl +3 -0
- rsconnect/quickstart/templates/api/__init__.py.tmpl +1 -0
- rsconnect/quickstart/templates/api/__main__.py.tmpl +14 -0
- rsconnect/quickstart/templates/api/app.py.tmpl +11 -0
- rsconnect/quickstart/templates/api/pyproject.toml.tmpl +13 -0
- rsconnect/quickstart/templates/fastapi/README.md.tmpl +15 -0
- rsconnect/quickstart/templates/fastapi/__connect__.py.tmpl +3 -0
- rsconnect/quickstart/templates/fastapi/__init__.py.tmpl +1 -0
- rsconnect/quickstart/templates/fastapi/__main__.py.tmpl +16 -0
- rsconnect/quickstart/templates/fastapi/app.py.tmpl +11 -0
- rsconnect/quickstart/templates/fastapi/pyproject.toml.tmpl +14 -0
- rsconnect/quickstart/templates/notebook/README.md.tmpl +15 -0
- rsconnect/quickstart/templates/notebook/notebook.ipynb.tmpl +34 -0
- rsconnect/quickstart/templates/notebook/pyproject.toml.tmpl +13 -0
- rsconnect/quickstart/templates/quarto/README.md.tmpl +19 -0
- rsconnect/quickstart/templates/quarto/pyproject.toml.tmpl +11 -0
- rsconnect/quickstart/templates/quarto/report.qmd.tmpl +8 -0
- rsconnect/quickstart/templates/shiny/README.md.tmpl +15 -0
- rsconnect/quickstart/templates/shiny/app.py.tmpl +3 -0
- rsconnect/quickstart/templates/shiny/pyproject.toml.tmpl +13 -0
- rsconnect/quickstart/templates/streamlit/README.md.tmpl +15 -0
- rsconnect/quickstart/templates/streamlit/app.py.tmpl +3 -0
- rsconnect/quickstart/templates/streamlit/pyproject.toml.tmpl +13 -0
- rsconnect/quickstart/templates/voila/README.md.tmpl +15 -0
- rsconnect/quickstart/templates/voila/pyproject.toml.tmpl +14 -0
- rsconnect/shiny_express.py +136 -0
- rsconnect/snowflake.py +93 -0
- rsconnect/subprocesses/__init__.py +0 -0
- rsconnect/subprocesses/inspect_environment.py +362 -0
- rsconnect/timeouts.py +89 -0
- rsconnect/utils_package.py +261 -0
- rsconnect/validation.py +156 -0
- rsconnect/version_check.py +154 -0
- rsconnect_python-1.30.0.dist-info/METADATA +89 -0
- rsconnect_python-1.30.0.dist-info/RECORD +63 -0
- rsconnect_python-1.30.0.dist-info/WHEEL +4 -0
- rsconnect_python-1.30.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
Environment data class abstraction that is usable as an executable module
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
python -m rsconnect.subprocesses.inspect_environment
|
|
7
|
+
```
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import datetime
|
|
14
|
+
import json
|
|
15
|
+
import locale
|
|
16
|
+
import os
|
|
17
|
+
import tempfile
|
|
18
|
+
import re
|
|
19
|
+
import subprocess
|
|
20
|
+
import sys
|
|
21
|
+
from dataclasses import asdict, dataclass, replace
|
|
22
|
+
from typing import Callable, Optional
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
import tomllib
|
|
26
|
+
except ImportError:
|
|
27
|
+
# Python <3.11 doesn't have tomllib in the standard library
|
|
28
|
+
import toml as tomllib # type: ignore[no-redef]
|
|
29
|
+
|
|
30
|
+
version_re = re.compile(r"\d+\.\d+(\.\d+)?")
|
|
31
|
+
exec_dir = os.path.dirname(sys.executable)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True)
|
|
35
|
+
class EnvironmentData:
|
|
36
|
+
contents: str
|
|
37
|
+
filename: str
|
|
38
|
+
locale: str
|
|
39
|
+
package_manager: str
|
|
40
|
+
pip: str
|
|
41
|
+
python: str
|
|
42
|
+
source: str
|
|
43
|
+
python_requires: Optional[str]
|
|
44
|
+
error: Optional[str]
|
|
45
|
+
|
|
46
|
+
def _asdict(self):
|
|
47
|
+
return asdict(self)
|
|
48
|
+
|
|
49
|
+
def _replace(self, **kwargs: object):
|
|
50
|
+
return replace(self, **kwargs)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def MakeEnvironmentData(
|
|
54
|
+
contents: str,
|
|
55
|
+
filename: str,
|
|
56
|
+
locale: str,
|
|
57
|
+
package_manager: str,
|
|
58
|
+
pip: str,
|
|
59
|
+
python: str,
|
|
60
|
+
source: str,
|
|
61
|
+
python_requires: Optional[str] = None,
|
|
62
|
+
error: Optional[str] = None,
|
|
63
|
+
**kwargs: object, # provides compatibility where we no longer support some older properties
|
|
64
|
+
) -> EnvironmentData:
|
|
65
|
+
return EnvironmentData(contents, filename, locale, package_manager, pip, python, source, python_requires, error)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class EnvironmentException(Exception):
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def detect_environment(dirname: str, requirements_file: Optional[str] = "requirements.txt") -> EnvironmentData:
|
|
73
|
+
"""Determine the python dependencies in the environment.
|
|
74
|
+
|
|
75
|
+
`pip freeze` will be used to introspect the environment.
|
|
76
|
+
|
|
77
|
+
:param: dirname Directory name
|
|
78
|
+
:param: requirements_file The requirements file to read. If None, generate using pip freeze.
|
|
79
|
+
:return: a dictionary containing the package spec filename and contents if successful,
|
|
80
|
+
or a dictionary containing `error` on failure.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
if requirements_file is None:
|
|
84
|
+
# --force-generate sets requirements_file to None
|
|
85
|
+
result = pip_freeze()
|
|
86
|
+
elif os.path.basename(requirements_file) == "uv.lock":
|
|
87
|
+
result = uv_export(dirname, requirements_file)
|
|
88
|
+
elif os.path.basename(requirements_file) == "pyproject.toml":
|
|
89
|
+
result = pyproject_dependencies(dirname, requirements_file)
|
|
90
|
+
else:
|
|
91
|
+
result = output_file(dirname, requirements_file, "pip")
|
|
92
|
+
if result is None:
|
|
93
|
+
raise EnvironmentException(
|
|
94
|
+
"The requirements file '%s' was not found in '%s'. "
|
|
95
|
+
"Please create it or use --force-generate to use pip freeze." % (requirements_file, dirname)
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
if result is not None:
|
|
99
|
+
result["python"] = get_python_version()
|
|
100
|
+
result["pip"] = get_version("pip")
|
|
101
|
+
result["locale"] = get_default_locale()
|
|
102
|
+
|
|
103
|
+
return MakeEnvironmentData(**result)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def get_python_version() -> str:
|
|
107
|
+
v = sys.version_info
|
|
108
|
+
return "%d.%d.%d" % (v[0], v[1], v[2])
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def get_default_locale(locale_source: Callable[..., tuple[str | None, str | None]] = locale.getlocale):
|
|
112
|
+
result = ".".join([item or "" for item in locale_source()])
|
|
113
|
+
return "" if result == "." else result
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def get_version(module: str):
|
|
117
|
+
try:
|
|
118
|
+
args = [sys.executable, "-m", module, "--version"]
|
|
119
|
+
proc = subprocess.Popen(
|
|
120
|
+
args,
|
|
121
|
+
stdout=subprocess.PIPE,
|
|
122
|
+
stderr=subprocess.PIPE,
|
|
123
|
+
universal_newlines=True,
|
|
124
|
+
)
|
|
125
|
+
stdout, _stderr = proc.communicate()
|
|
126
|
+
match = version_re.search(stdout)
|
|
127
|
+
if match:
|
|
128
|
+
return match.group()
|
|
129
|
+
|
|
130
|
+
msg = "Failed to get version of '%s' from the output of: %s" % (
|
|
131
|
+
module,
|
|
132
|
+
" ".join(args),
|
|
133
|
+
)
|
|
134
|
+
raise EnvironmentException(msg)
|
|
135
|
+
except Exception as exception:
|
|
136
|
+
raise EnvironmentException("Error getting '%s' version: %s" % (module, str(exception)))
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def output_file(dirname: str, filename: str, package_manager: str):
|
|
140
|
+
"""Read an existing package spec file.
|
|
141
|
+
|
|
142
|
+
Returns a dictionary containing the filename and contents
|
|
143
|
+
if successful, None if the file does not exist,
|
|
144
|
+
or a dictionary containing 'error' on failure.
|
|
145
|
+
"""
|
|
146
|
+
try:
|
|
147
|
+
path = os.path.join(dirname, filename)
|
|
148
|
+
if not os.path.exists(path):
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
with open(path, "r") as f:
|
|
152
|
+
data = f.read()
|
|
153
|
+
|
|
154
|
+
data = "\n".join([line for line in data.split("\n") if "rsconnect" not in line])
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
"filename": filename,
|
|
158
|
+
"contents": data,
|
|
159
|
+
"source": "file",
|
|
160
|
+
"package_manager": package_manager,
|
|
161
|
+
}
|
|
162
|
+
except Exception as exception:
|
|
163
|
+
raise EnvironmentException("Error reading %s: %s" % (filename, str(exception)))
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def pip_freeze():
|
|
167
|
+
"""Inspect the environment using `pip freeze --disable-pip-version-check version`.
|
|
168
|
+
|
|
169
|
+
Returns a dictionary containing the filename
|
|
170
|
+
(always 'requirements.txt') and contents if successful,
|
|
171
|
+
or a dictionary containing 'error' on failure.
|
|
172
|
+
"""
|
|
173
|
+
try:
|
|
174
|
+
proc = subprocess.Popen(
|
|
175
|
+
[sys.executable, "-m", "pip", "freeze", "--disable-pip-version-check"],
|
|
176
|
+
stdout=subprocess.PIPE,
|
|
177
|
+
stderr=subprocess.PIPE,
|
|
178
|
+
universal_newlines=True,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
pip_stdout, pip_stderr = proc.communicate()
|
|
182
|
+
pip_status = proc.returncode
|
|
183
|
+
except Exception as exception:
|
|
184
|
+
raise EnvironmentException("Error during pip freeze: %s" % str(exception))
|
|
185
|
+
|
|
186
|
+
if pip_status != 0:
|
|
187
|
+
msg = pip_stderr or ("exited with code %d" % pip_status)
|
|
188
|
+
raise EnvironmentException("Error during pip freeze: %s" % msg)
|
|
189
|
+
|
|
190
|
+
pip_stdout = filter_pip_freeze_output(pip_stdout)
|
|
191
|
+
|
|
192
|
+
pip_stdout = (
|
|
193
|
+
"# requirements.txt generated by rsconnect-python on "
|
|
194
|
+
+ str(datetime.datetime.now(datetime.timezone.utc))
|
|
195
|
+
+ "\n"
|
|
196
|
+
+ pip_stdout
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
"filename": "requirements.txt",
|
|
201
|
+
"contents": pip_stdout,
|
|
202
|
+
"source": "pip_freeze",
|
|
203
|
+
"package_manager": "pip",
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def uv_export(dirname: str, lock_filename: str):
|
|
208
|
+
"""
|
|
209
|
+
Export requirements from a uv.lock file using `uv export`.
|
|
210
|
+
"""
|
|
211
|
+
lock_path = lock_filename
|
|
212
|
+
if not os.path.isabs(lock_filename):
|
|
213
|
+
lock_path = os.path.join(dirname, lock_filename)
|
|
214
|
+
|
|
215
|
+
if not os.path.exists(lock_path):
|
|
216
|
+
raise EnvironmentException("uv.lock not found: %s" % lock_filename)
|
|
217
|
+
|
|
218
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
219
|
+
output_path = os.path.join(tmpdir, "requirements.txt.lock")
|
|
220
|
+
try:
|
|
221
|
+
result = subprocess.run(
|
|
222
|
+
[
|
|
223
|
+
"uv",
|
|
224
|
+
"export",
|
|
225
|
+
"--format",
|
|
226
|
+
"requirements-txt",
|
|
227
|
+
"--frozen",
|
|
228
|
+
"--no-hashes",
|
|
229
|
+
"--no-annotate",
|
|
230
|
+
"--offline",
|
|
231
|
+
"--no-header",
|
|
232
|
+
"--no-emit-project",
|
|
233
|
+
"--output-file",
|
|
234
|
+
output_path,
|
|
235
|
+
],
|
|
236
|
+
cwd=os.path.dirname(lock_path),
|
|
237
|
+
stdout=sys.stderr,
|
|
238
|
+
stderr=sys.stderr,
|
|
239
|
+
check=False,
|
|
240
|
+
)
|
|
241
|
+
except Exception as exception:
|
|
242
|
+
raise EnvironmentException("Error during uv export: %s" % str(exception))
|
|
243
|
+
|
|
244
|
+
if result.returncode != 0:
|
|
245
|
+
raise EnvironmentException("Error during uv export: exited with code %d" % result.returncode)
|
|
246
|
+
|
|
247
|
+
with open(output_path, mode="r", encoding="utf-8") as output_file:
|
|
248
|
+
exported = output_file.read()
|
|
249
|
+
|
|
250
|
+
requirements = filter_pip_freeze_output(exported)
|
|
251
|
+
requirements = (
|
|
252
|
+
"# requirements.txt.lock generated from uv.lock by rsconnect-python on "
|
|
253
|
+
+ str(datetime.datetime.now(datetime.timezone.utc))
|
|
254
|
+
+ "\n"
|
|
255
|
+
+ requirements
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
"filename": "requirements.txt.lock",
|
|
260
|
+
"contents": requirements,
|
|
261
|
+
"source": "uv_lock",
|
|
262
|
+
"package_manager": "uv",
|
|
263
|
+
"pip": None,
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def pyproject_dependencies(dirname: str, pyproject_filename: str):
|
|
268
|
+
"""Read project.dependencies from a pyproject.toml file as a requirements spec.
|
|
269
|
+
|
|
270
|
+
Emits a requirements.txt file listing the top-level dependencies declared
|
|
271
|
+
in ``[project].dependencies``. Unlike ``uv.lock``, the result is not a
|
|
272
|
+
fully resolved lock and Connect will perform dependency resolution at
|
|
273
|
+
deploy time.
|
|
274
|
+
"""
|
|
275
|
+
pyproject_path = pyproject_filename
|
|
276
|
+
if not os.path.isabs(pyproject_filename):
|
|
277
|
+
pyproject_path = os.path.join(dirname, pyproject_filename)
|
|
278
|
+
|
|
279
|
+
if not os.path.exists(pyproject_path):
|
|
280
|
+
raise EnvironmentException(f"pyproject.toml not found: {pyproject_filename}")
|
|
281
|
+
|
|
282
|
+
try:
|
|
283
|
+
with open(pyproject_path, "r", encoding="utf-8") as f:
|
|
284
|
+
pyproject = tomllib.loads(f.read())
|
|
285
|
+
except Exception as exception:
|
|
286
|
+
raise EnvironmentException(f"Error reading {pyproject_filename}: {exception}")
|
|
287
|
+
|
|
288
|
+
project = pyproject.get("project", {})
|
|
289
|
+
dependencies: list[object] = project.get("dependencies", [])
|
|
290
|
+
if not isinstance(dependencies, list):
|
|
291
|
+
raise EnvironmentException(f"Invalid project.dependencies in {pyproject_filename}: expected a list of strings.")
|
|
292
|
+
|
|
293
|
+
requirements = filter_pip_freeze_output("\n".join(str(dep) for dep in dependencies))
|
|
294
|
+
requirements = (
|
|
295
|
+
f"# requirements.txt generated from pyproject.toml by rsconnect-python on "
|
|
296
|
+
f"{datetime.datetime.now(datetime.timezone.utc)}\n"
|
|
297
|
+
f"{requirements}"
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
"filename": "requirements.txt",
|
|
302
|
+
"contents": requirements,
|
|
303
|
+
"source": "pyproject_toml",
|
|
304
|
+
"package_manager": "pip",
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def filter_pip_freeze_output(pip_stdout: str):
|
|
309
|
+
# Filter out dependency on `rsconnect` and ignore output lines from pip which start with `[notice]`
|
|
310
|
+
return "\n".join(
|
|
311
|
+
[line for line in pip_stdout.split("\n") if (("rsconnect" not in line) and (line.find("[notice]") != 0))]
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def strip_ref(line: str):
|
|
316
|
+
# remove erroneous conda build paths that will break pip install
|
|
317
|
+
return line.split(" @ file:", 1)[0].strip()
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def exclude(line: str):
|
|
321
|
+
return line and line.startswith("setuptools") and "post" in line
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def main():
|
|
325
|
+
"""
|
|
326
|
+
Run `detect_environment` and dump the result as JSON.
|
|
327
|
+
"""
|
|
328
|
+
try:
|
|
329
|
+
parser = argparse.ArgumentParser(
|
|
330
|
+
description="Inspect python environment and return dependency metadata.", add_help=True
|
|
331
|
+
)
|
|
332
|
+
parser.add_argument(
|
|
333
|
+
"-r",
|
|
334
|
+
"--requirements-file",
|
|
335
|
+
dest="requirements_file",
|
|
336
|
+
default="requirements.txt",
|
|
337
|
+
help="Requirements file name (relative to the directory). Use 'none' to capture via pip freeze.",
|
|
338
|
+
)
|
|
339
|
+
parser.add_argument("directory", help="Directory to inspect.")
|
|
340
|
+
args = parser.parse_args()
|
|
341
|
+
|
|
342
|
+
requirements_file = args.requirements_file
|
|
343
|
+
if requirements_file.lower() == "none":
|
|
344
|
+
requirements_file = None
|
|
345
|
+
|
|
346
|
+
envinfo = detect_environment(args.directory, requirements_file=requirements_file)._asdict()
|
|
347
|
+
if "contents" in envinfo:
|
|
348
|
+
keepers = list(map(strip_ref, envinfo["contents"].split("\n")))
|
|
349
|
+
keepers = [line for line in keepers if not exclude(line)]
|
|
350
|
+
envinfo["contents"] = "\n".join(keepers)
|
|
351
|
+
|
|
352
|
+
json.dump(
|
|
353
|
+
envinfo,
|
|
354
|
+
sys.stdout,
|
|
355
|
+
indent=4,
|
|
356
|
+
)
|
|
357
|
+
except EnvironmentException as exception:
|
|
358
|
+
json.dump(dict(error=str(exception)), sys.stdout, indent=4)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
if __name__ == "__main__":
|
|
362
|
+
main()
|
rsconnect/timeouts.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import textwrap
|
|
3
|
+
|
|
4
|
+
from typing import Union
|
|
5
|
+
|
|
6
|
+
from rsconnect.exception import RSConnectException
|
|
7
|
+
|
|
8
|
+
_CONNECT_REQUEST_TIMEOUT_KEY: str = "CONNECT_REQUEST_TIMEOUT"
|
|
9
|
+
_CONNECT_REQUEST_TIMEOUT_DEFAULT_VALUE: str = "300"
|
|
10
|
+
|
|
11
|
+
_CONNECT_TASK_TIMEOUT_KEY: str = "CONNECT_TASK_TIMEOUT"
|
|
12
|
+
_CONNECT_TASK_TIMEOUT_DEFAULT_VALUE: str = "86400"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_request_timeout() -> int:
|
|
16
|
+
"""Gets the timeout from the CONNECT_REQUEST_TIMEOUT env variable.
|
|
17
|
+
|
|
18
|
+
The timeout value is intended to be interpreted in seconds. A value of 60 is equal to sixty seconds, or one minute.
|
|
19
|
+
|
|
20
|
+
If CONNECT_REQUEST_TIMEOUT is unset, a default value of 300 is used.
|
|
21
|
+
|
|
22
|
+
If CONNECT_REQUEST_TIMEOUT is set to a value less than 0, an `RSConnectException` is raised.
|
|
23
|
+
|
|
24
|
+
A CONNECT_REQUEST_TIMEOUT set to 0 is logically equivalent to no timeout.
|
|
25
|
+
|
|
26
|
+
The primary intent for this method is for usage with the `http` module. Specifically, for setting the timeout
|
|
27
|
+
parameter with an `http.client.HTTPConnection` or `http.client.HTTPSConnection`.
|
|
28
|
+
|
|
29
|
+
:raises: `RSConnectException` if CONNECT_REQUEST_TIMEOUT is not a natural number.
|
|
30
|
+
:return: the timeout value
|
|
31
|
+
"""
|
|
32
|
+
timeout: Union[int, str] = os.environ.get(_CONNECT_REQUEST_TIMEOUT_KEY, _CONNECT_REQUEST_TIMEOUT_DEFAULT_VALUE)
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
timeout = int(timeout)
|
|
36
|
+
except ValueError:
|
|
37
|
+
raise RSConnectException(
|
|
38
|
+
f"'CONNECT_REQUEST_TIMEOUT' is set to '{timeout}'. The value must be a non-negative integer."
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
if timeout < 0:
|
|
42
|
+
raise RSConnectException(
|
|
43
|
+
f"'CONNECT_REQUEST_TIMEOUT' is set to '{timeout}'. The value must be a non-negative integer."
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
return timeout
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def get_task_timeout() -> int:
|
|
50
|
+
"""Gets the timeout from the CONNECT_TASK_TIMEOUT env variable.
|
|
51
|
+
|
|
52
|
+
The timeout value is intended to be interpreted in seconds. A value of 60 is equal to sixty seconds, or one minute.
|
|
53
|
+
|
|
54
|
+
If CONNECT_TASK_TIMEOUT is unset, a default value of 86,400 (1 day) is used.
|
|
55
|
+
|
|
56
|
+
If CONNECT_TASK_TIMEOUT is set to a value less or equal to 0, an `RSConnectException` is raised.
|
|
57
|
+
|
|
58
|
+
The primary intent for this method is for usage with the `api` module. Specifically, for setting the timeout
|
|
59
|
+
parameter in the method `wait_for_task`.
|
|
60
|
+
|
|
61
|
+
:raises: `RSConnectException` if CONNECT_TASK_TIMEOUT is not a positive integer.
|
|
62
|
+
:return: the timeout value
|
|
63
|
+
"""
|
|
64
|
+
timeout: Union[int, str] = os.environ.get(_CONNECT_TASK_TIMEOUT_KEY, _CONNECT_TASK_TIMEOUT_DEFAULT_VALUE)
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
timeout = int(timeout)
|
|
68
|
+
except ValueError:
|
|
69
|
+
raise RSConnectException(f"'CONNECT_TASK_TIMEOUT' is set to '{timeout}'. The value must be a positive integer.")
|
|
70
|
+
|
|
71
|
+
if timeout <= 0:
|
|
72
|
+
raise RSConnectException(f"'CONNECT_TASK_TIMEOUT' is set to '{timeout}'. The value must be a positive integer.")
|
|
73
|
+
|
|
74
|
+
return timeout
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def get_task_timeout_help_message(timeout: int = get_task_timeout()) -> str:
|
|
78
|
+
"""Gets a human friendly help message for adjusting the task timeout value."""
|
|
79
|
+
|
|
80
|
+
return f"The task timed out after {timeout} seconds." + textwrap.dedent(
|
|
81
|
+
f"""
|
|
82
|
+
|
|
83
|
+
You may try increasing the task timeout value using the {_CONNECT_TASK_TIMEOUT_KEY} environment variable. The default value is {_CONNECT_TASK_TIMEOUT_DEFAULT_VALUE} seconds. The current value is {get_task_timeout()} seconds.
|
|
84
|
+
|
|
85
|
+
Example:
|
|
86
|
+
|
|
87
|
+
CONNECT_TASK_TIMEOUT={_CONNECT_TASK_TIMEOUT_DEFAULT_VALUE} rsconnect deploy api --server <your-server> --api-key <your-api-key> ./
|
|
88
|
+
""" # noqa: E501
|
|
89
|
+
)
|