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.
Files changed (63) hide show
  1. rsconnect/__init__.py +13 -0
  2. rsconnect/actions.py +565 -0
  3. rsconnect/actions_content.py +508 -0
  4. rsconnect/actions_environment.py +160 -0
  5. rsconnect/actions_integration.py +118 -0
  6. rsconnect/api.py +2582 -0
  7. rsconnect/bundle.py +2481 -0
  8. rsconnect/certificates.py +39 -0
  9. rsconnect/environment.py +390 -0
  10. rsconnect/environment_node.py +115 -0
  11. rsconnect/environment_r.py +300 -0
  12. rsconnect/exception.py +15 -0
  13. rsconnect/git_metadata.py +180 -0
  14. rsconnect/http_support.py +595 -0
  15. rsconnect/json_web_token.py +178 -0
  16. rsconnect/log.py +253 -0
  17. rsconnect/main.py +5889 -0
  18. rsconnect/metadata.py +879 -0
  19. rsconnect/models.py +835 -0
  20. rsconnect/oauth.py +623 -0
  21. rsconnect/py.typed +0 -0
  22. rsconnect/pyproject.py +283 -0
  23. rsconnect/quickstart/__init__.py +16 -0
  24. rsconnect/quickstart/quickstart.py +486 -0
  25. rsconnect/quickstart/templates/__init__.py +16 -0
  26. rsconnect/quickstart/templates/api/README.md.tmpl +15 -0
  27. rsconnect/quickstart/templates/api/__connect__.py.tmpl +3 -0
  28. rsconnect/quickstart/templates/api/__init__.py.tmpl +1 -0
  29. rsconnect/quickstart/templates/api/__main__.py.tmpl +14 -0
  30. rsconnect/quickstart/templates/api/app.py.tmpl +11 -0
  31. rsconnect/quickstart/templates/api/pyproject.toml.tmpl +13 -0
  32. rsconnect/quickstart/templates/fastapi/README.md.tmpl +15 -0
  33. rsconnect/quickstart/templates/fastapi/__connect__.py.tmpl +3 -0
  34. rsconnect/quickstart/templates/fastapi/__init__.py.tmpl +1 -0
  35. rsconnect/quickstart/templates/fastapi/__main__.py.tmpl +16 -0
  36. rsconnect/quickstart/templates/fastapi/app.py.tmpl +11 -0
  37. rsconnect/quickstart/templates/fastapi/pyproject.toml.tmpl +14 -0
  38. rsconnect/quickstart/templates/notebook/README.md.tmpl +15 -0
  39. rsconnect/quickstart/templates/notebook/notebook.ipynb.tmpl +34 -0
  40. rsconnect/quickstart/templates/notebook/pyproject.toml.tmpl +13 -0
  41. rsconnect/quickstart/templates/quarto/README.md.tmpl +19 -0
  42. rsconnect/quickstart/templates/quarto/pyproject.toml.tmpl +11 -0
  43. rsconnect/quickstart/templates/quarto/report.qmd.tmpl +8 -0
  44. rsconnect/quickstart/templates/shiny/README.md.tmpl +15 -0
  45. rsconnect/quickstart/templates/shiny/app.py.tmpl +3 -0
  46. rsconnect/quickstart/templates/shiny/pyproject.toml.tmpl +13 -0
  47. rsconnect/quickstart/templates/streamlit/README.md.tmpl +15 -0
  48. rsconnect/quickstart/templates/streamlit/app.py.tmpl +3 -0
  49. rsconnect/quickstart/templates/streamlit/pyproject.toml.tmpl +13 -0
  50. rsconnect/quickstart/templates/voila/README.md.tmpl +15 -0
  51. rsconnect/quickstart/templates/voila/pyproject.toml.tmpl +14 -0
  52. rsconnect/shiny_express.py +136 -0
  53. rsconnect/snowflake.py +93 -0
  54. rsconnect/subprocesses/__init__.py +0 -0
  55. rsconnect/subprocesses/inspect_environment.py +362 -0
  56. rsconnect/timeouts.py +89 -0
  57. rsconnect/utils_package.py +261 -0
  58. rsconnect/validation.py +156 -0
  59. rsconnect/version_check.py +154 -0
  60. rsconnect_python-1.30.0.dist-info/METADATA +89 -0
  61. rsconnect_python-1.30.0.dist-info/RECORD +63 -0
  62. rsconnect_python-1.30.0.dist-info/WHEEL +4 -0
  63. rsconnect_python-1.30.0.dist-info/entry_points.txt +3 -0
rsconnect/__init__.py ADDED
@@ -0,0 +1,13 @@
1
+ from importlib.metadata import PackageNotFoundError, version
2
+
3
+
4
+ def _resolve_version() -> str:
5
+ for distribution in ("rsconnect_python", "rsconnect"):
6
+ try:
7
+ return version(distribution)
8
+ except PackageNotFoundError:
9
+ continue
10
+ return "NOTSET"
11
+
12
+
13
+ VERSION = _resolve_version()
rsconnect/actions.py ADDED
@@ -0,0 +1,565 @@
1
+ """
2
+ Public API for managing settings and deploying content.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import contextlib
8
+ import json
9
+ import logging
10
+ import re
11
+ import shutil
12
+ import subprocess
13
+ import sys
14
+ import traceback
15
+ import typing
16
+ from os.path import basename, exists, join, relpath
17
+ from typing import Optional, Sequence, cast
18
+ from warnings import warn
19
+
20
+ # Even though TypedDict is available in Python 3.8, because it's used with NotRequired,
21
+ # they should both come from the same typing module.
22
+ # https://peps.python.org/pep-0655/#usage-in-python-3-11
23
+ if sys.version_info >= (3, 11):
24
+ from typing import NotRequired, TypedDict
25
+ else:
26
+ from typing_extensions import NotRequired, TypedDict
27
+
28
+ from urllib.parse import urlparse
29
+
30
+ import click
31
+
32
+ from . import api
33
+ from .bundle import (
34
+ get_default_entrypoint,
35
+ make_api_bundle,
36
+ make_quarto_source_bundle,
37
+ read_manifest_file,
38
+ )
39
+ from .environment import Environment
40
+ from .environment_r import REnvironment
41
+ from .exception import RSConnectException
42
+ from .log import VERBOSE, logger
43
+ from .models import AppMode, AppModes
44
+
45
+ line_width = 45
46
+ _module_pattern = re.compile(r"^[A-Za-z0-9_]+:[A-Za-z0-9_]+$")
47
+ _name_sub_pattern = re.compile(r"[^A-Za-z0-9_ -]+")
48
+ _repeating_sub_pattern = re.compile(r"_+")
49
+
50
+
51
+ @contextlib.contextmanager
52
+ def cli_feedback(label: str, stderr: bool = False):
53
+ """Context manager for OK/ERROR feedback from the CLI.
54
+
55
+ If the enclosed block succeeds, OK will be emitted.
56
+ If it fails, ERROR will be emitted.
57
+ Errors will also be classified as operational errors (prefixed with 'Error')
58
+ vs. internal errors (prefixed with 'Internal Error'). In verbose mode,
59
+ tracebacks will be emitted for internal errors.
60
+ """
61
+ if label:
62
+ pad = line_width - len(label)
63
+ click.secho(label + "... " + " " * pad, nl=False, err=stderr)
64
+ logger.set_in_feedback(True)
65
+
66
+ def passed():
67
+ if label:
68
+ click.secho("[OK]", fg="green", err=stderr)
69
+
70
+ def failed(err: str):
71
+ if label:
72
+ click.secho("[ERROR]", fg="red", err=stderr)
73
+ click.secho(str(err), fg="bright_red", err=stderr)
74
+ sys.exit(1)
75
+
76
+ try:
77
+ yield
78
+ passed()
79
+ except RSConnectException as exc:
80
+ failed("Error: " + exc.message)
81
+ except Exception as exc:
82
+ traceback.print_exc()
83
+ failed("Internal error: " + str(exc))
84
+ finally:
85
+ logger.set_in_feedback(False)
86
+
87
+
88
+ def set_verbosity(verbose: int):
89
+ """Set the verbosity level based on a passed flag
90
+
91
+ :param verbose: boolean specifying verbose or not
92
+ """
93
+ if verbose == 0:
94
+ logger.setLevel(logging.INFO)
95
+ elif verbose == 1:
96
+ logger.setLevel(VERBOSE)
97
+ else:
98
+ logger.setLevel(logging.DEBUG)
99
+
100
+
101
+ def _verify_server(connect_server: api.RSConnectServer):
102
+ """
103
+ Test whether the server identified by the given full URL can be reached and is
104
+ running Connect.
105
+
106
+ :param connect_server: the Connect server information.
107
+ :return: the server settings from the Connect server.
108
+ """
109
+ uri = urlparse(connect_server.url)
110
+ if not uri.netloc:
111
+ raise RSConnectException('Invalid server URL: "%s"' % connect_server.url)
112
+ return api.verify_server(connect_server)
113
+
114
+
115
+ def _to_server_check_list(url: str) -> list[str]:
116
+ """
117
+ Build a list of servers to check from the given one. If the specified server
118
+ appears not to have a scheme, then we'll provide https and http variants to test.
119
+
120
+ :param url: the server URL text to start with.
121
+ :return: a list of server strings to test.
122
+ """
123
+ warn("This method has been moved and will be deprecated.", DeprecationWarning, stacklevel=2)
124
+ # urlparse will end up with an empty netloc in this case.
125
+ if "//" not in url:
126
+ items = ["https://%s", "http://%s"]
127
+ # urlparse would parse this correctly and end up with an empty scheme.
128
+ elif url.startswith("//"):
129
+ items = ["https:%s", "http:%s"]
130
+ else:
131
+ items = ["%s"]
132
+
133
+ return [item % url for item in items]
134
+
135
+
136
+ def test_server(connect_server: api.RSConnectServer) -> tuple[api.RSConnectServer, object]:
137
+ """
138
+ Test whether the given server can be reached and is running Connect. The server
139
+ may be provided with or without a scheme. If a scheme is omitted, the server will
140
+ be tested with both `https` and `http` until one of them works.
141
+
142
+ :param connect_server: the Connect server information.
143
+ :return: a second server object with any scheme expansions applied and the server
144
+ settings from the server.
145
+ """
146
+ warn("This method has been moved and will be deprecated.", DeprecationWarning, stacklevel=2)
147
+ url = connect_server.url
148
+ key = connect_server.api_key
149
+ insecure = connect_server.insecure
150
+ ca_data = connect_server.ca_data
151
+ failures: list[str] = []
152
+ for test in _to_server_check_list(url):
153
+ try:
154
+ connect_server = api.RSConnectServer(test, key, insecure, ca_data)
155
+ result = _verify_server(connect_server)
156
+ return connect_server, result
157
+ except RSConnectException as exc:
158
+ failures.append(" %s - failed to verify as Posit Connect (%s)." % (test, str(exc)))
159
+
160
+ # In case the user may need https instead of http...
161
+ if len(failures) == 1 and url.startswith("http://"):
162
+ failures.append(' Do you need to use "https://%s"?' % url[7:])
163
+
164
+ # If we're here, nothing worked.
165
+ raise RSConnectException("\n".join(failures))
166
+
167
+
168
+ def test_rstudio_server(server: api.PositServer):
169
+ with api.PositClient(server) as client:
170
+ try:
171
+ client.get_current_user()
172
+ except RSConnectException as exc:
173
+ raise RSConnectException("Failed to verify with {} ({}).".format(server.remote_name, exc))
174
+
175
+
176
+ def test_spcs_server(server: api.SPCSConnectServer):
177
+ with api.RSConnectClient(server) as client:
178
+ try:
179
+ client.me()
180
+ except RSConnectException as exc:
181
+ raise RSConnectException("Failed to verify with {} ({}).".format(server.remote_name, exc))
182
+
183
+
184
+ def test_api_key(connect_server: api.RSConnectServer) -> str:
185
+ """
186
+ Test that an API Key may be used to authenticate with the given Posit Connect server.
187
+ If the API key verifies, we return the username of the associated user.
188
+
189
+ :param connect_server: the Connect server information.
190
+ :return: the username of the user to whom the API key belongs.
191
+ """
192
+ warn("This method has been moved and will be deprecated.", DeprecationWarning, stacklevel=2)
193
+ return api.verify_api_key(connect_server)
194
+
195
+
196
+ def which_quarto(quarto: Optional[str] = None) -> str:
197
+ """
198
+ Identify a valid Quarto executable. When a Quarto location is not provided
199
+ as input, an attempt is made to discover Quarto from the PATH and other
200
+ well-known locations.
201
+ """
202
+ if quarto:
203
+ found = shutil.which(quarto)
204
+ if not found:
205
+ raise RSConnectException('The Quarto installation, "%s", does not exist or is not executable.' % quarto)
206
+ return found
207
+
208
+ # Fallback -- try to find Quarto when one was not supplied.
209
+ locations = [
210
+ # Discover using $PATH
211
+ "quarto",
212
+ # Location used by some installers, and often-added symbolic link.
213
+ "/usr/local/bin/quarto",
214
+ # Location used by some installers.
215
+ "/opt/quarto/bin/quarto",
216
+ # macOS RStudio IDE embedded installation
217
+ "/Applications/RStudio.app/Contents/MacOS/quarto/bin/quarto",
218
+ # macOS RStudio IDE electron embedded installation; location not final.
219
+ # see: https://github.com/rstudio/rstudio/issues/10674
220
+ ]
221
+
222
+ for each in locations:
223
+ found = shutil.which(each)
224
+ if found:
225
+ return found
226
+ raise RSConnectException("Unable to locate a Quarto installation.")
227
+
228
+
229
+ class QuartoInspectResultQuarto(TypedDict):
230
+ version: str
231
+
232
+
233
+ class QuartoInspectResultConfigProject(TypedDict):
234
+ render: list[str]
235
+
236
+
237
+ class QuartoInspectResultConfig(TypedDict):
238
+ project: QuartoInspectResultConfigProject
239
+
240
+
241
+ class QuartoInspectResult(TypedDict):
242
+ quarto: QuartoInspectResultQuarto
243
+ engines: list[str]
244
+ config: NotRequired[QuartoInspectResultConfig]
245
+
246
+
247
+ def quarto_inspect(
248
+ quarto: str,
249
+ target: str,
250
+ check_output: typing.Callable[..., bytes] = subprocess.check_output,
251
+ ) -> QuartoInspectResult:
252
+ """
253
+ Runs 'quarto inspect' against the target and returns its output as a
254
+ parsed JSON object.
255
+
256
+ The JSON result has different structure depending on whether or not the
257
+ target is a directory or a file.
258
+ """
259
+
260
+ args = [quarto, "inspect", target]
261
+ try:
262
+ inspect_json = check_output(args, universal_newlines=True, stderr=subprocess.STDOUT)
263
+ except subprocess.CalledProcessError as e:
264
+ raise RSConnectException("Error inspecting target: %s" % e.output)
265
+ return cast(QuartoInspectResult, json.loads(inspect_json))
266
+
267
+
268
+ def validate_quarto_engines(inspect: QuartoInspectResult):
269
+ """
270
+ The markdown and jupyter engines are supported. Not knitr.
271
+ """
272
+ supported = ["markdown", "jupyter"]
273
+ engines = inspect.get("engines", [])
274
+ unsupported = [engine for engine in engines if engine not in supported]
275
+ if unsupported:
276
+ raise RSConnectException("The following Quarto engine(s) are not supported: %s" % ", ".join(unsupported))
277
+ return engines
278
+
279
+
280
+ # ===============================================================================
281
+ # START: Compatibility entry point used by the vetiver-python package.
282
+ # vetiver's `deploy_connect` calls `deploy_python_fastapi` (below), which routes
283
+ # through `deploy_app` and the local `validate_*` helpers. This is a supported
284
+ # shim; keep these signatures stable. The `pyright: ignore` comments remain
285
+ # because the kwargs-forwarding style predates strict typing.
286
+ # ===============================================================================
287
+ def validate_extra_files(directory: str, extra_files: Sequence[str]):
288
+ """
289
+ If the user specified a list of extra files, validate that they all exist and are
290
+ beneath the given directory and, if so, return a list of them made relative to that
291
+ directory.
292
+
293
+ :param directory: the directory that the extra files must be relative to.
294
+ :param extra_files: the list of extra files to qualify and validate.
295
+ :return: the extra files qualified by the directory.
296
+ """
297
+ warn("This method has been moved and will be deprecated.", DeprecationWarning, stacklevel=2)
298
+ result: list[str] = []
299
+ if extra_files:
300
+ for extra in extra_files:
301
+ extra_file = relpath(extra, directory)
302
+ # It's an error if we have to leave the given dir to get to the extra
303
+ # file.
304
+ if extra_file.startswith("../"):
305
+ raise RSConnectException("%s must be under %s." % (extra_file, directory))
306
+ if not exists(join(directory, extra_file)):
307
+ raise RSConnectException("Could not find file %s under %s" % (extra, directory))
308
+ result.append(extra_file)
309
+ return result
310
+
311
+
312
+ def validate_entry_point(entry_point: Optional[str], directory: str):
313
+ """
314
+ Validates the entry point specified by the user, expanding as necessary. If the
315
+ user specifies nothing, a module of "app" is assumed. If the user specifies a
316
+ module only, the object is assumed to be the same name as the module.
317
+
318
+ :param entry_point: the entry point as specified by the user.
319
+ :return: the fully expanded and validated entry point and the module file name..
320
+ """
321
+ warn("This method has been moved and will be deprecated.", DeprecationWarning, stacklevel=2)
322
+ if not entry_point:
323
+ entry_point = get_default_entrypoint(directory)
324
+
325
+ parts = entry_point.split(":")
326
+
327
+ if len(parts) > 2:
328
+ raise RSConnectException('Entry point is not in "module:object" format.')
329
+
330
+ return entry_point
331
+
332
+
333
+ def deploy_app(
334
+ name: Optional[str] = None,
335
+ server: Optional[str] = None,
336
+ api_key: Optional[str] = None,
337
+ insecure: Optional[bool] = None,
338
+ cacert: Optional[typing.IO[str]] = None,
339
+ ca_data: Optional[str] = None,
340
+ entry_point: Optional[str] = None,
341
+ excludes: Optional[list[str]] = None,
342
+ new: bool = False,
343
+ app_id: Optional[str] = None,
344
+ title: Optional[str] = None,
345
+ python: Optional[str] = None,
346
+ force_generate: bool = False,
347
+ verbose: Optional[bool] = None,
348
+ directory: Optional[str] = None,
349
+ extra_files: Optional[list[str]] = None,
350
+ env_vars: Optional[dict[str, str]] = None,
351
+ image: Optional[str] = None,
352
+ env_management_py: Optional[bool] = None,
353
+ env_management_r: Optional[bool] = None,
354
+ account: Optional[str] = None,
355
+ token: Optional[str] = None,
356
+ secret: Optional[str] = None,
357
+ app_mode: Optional[AppMode] = None,
358
+ connect_server: Optional[api.TargetableServer] = None,
359
+ **kws: object,
360
+ ):
361
+ kwargs = locals()
362
+ kwargs["entry_point"] = entry_point = validate_entry_point(entry_point, directory) # pyright: ignore
363
+ kwargs["extra_files"] = extra_files = validate_extra_files(directory, extra_files) # pyright: ignore
364
+
365
+ if isinstance(connect_server, api.RSConnectServer):
366
+ kwargs.update(
367
+ dict(
368
+ url=connect_server.url,
369
+ api_key=connect_server.api_key,
370
+ insecure=connect_server.insecure,
371
+ ca_data=connect_server.ca_data,
372
+ cookies=connect_server.cookie_jar,
373
+ )
374
+ )
375
+ elif isinstance(connect_server, api.ShinyappsServer):
376
+ kwargs.update(
377
+ dict(
378
+ url=connect_server.url,
379
+ account=connect_server.account_name,
380
+ token=connect_server.token,
381
+ secret=connect_server.secret,
382
+ )
383
+ )
384
+
385
+ environment = Environment.create_python_environment(
386
+ directory, # pyright: ignore
387
+ requirements_file="requirements.txt" if not force_generate else None,
388
+ python=python,
389
+ )
390
+
391
+ # At this point, kwargs has a lot of things, but we can need to prune it down to just the things that
392
+ # the RSConnectExecutor constructor knows about.
393
+ executor_params = [
394
+ "ctx",
395
+ "name",
396
+ "url",
397
+ "api_key",
398
+ "insecure",
399
+ "cacert",
400
+ "ca_data",
401
+ "cookies",
402
+ "account",
403
+ "token",
404
+ "secret",
405
+ "timeout",
406
+ "logger",
407
+ "path",
408
+ "server",
409
+ "exclude",
410
+ "new",
411
+ "app_id",
412
+ "title",
413
+ "visibility",
414
+ "disable_env_management",
415
+ "env_vars",
416
+ ]
417
+ shared_keys = set(executor_params).intersection(kwargs.keys())
418
+ kwargs = {key: kwargs[key] for key in shared_keys}
419
+
420
+ ce = api.RSConnectExecutor(**kwargs)
421
+ (
422
+ ce.validate_server()
423
+ .validate_app_mode(app_mode=app_mode) # pyright: ignore
424
+ .make_bundle(
425
+ make_api_bundle,
426
+ directory, # pyright: ignore
427
+ entry_point,
428
+ app_mode, # pyright: ignore
429
+ environment,
430
+ extra_files,
431
+ excludes, # pyright: ignore
432
+ image=image,
433
+ env_management_py=env_management_py,
434
+ env_management_r=env_management_r,
435
+ )
436
+ .deploy_bundle()
437
+ .save_deployed_info()
438
+ .emit_task_log()
439
+ )
440
+
441
+
442
+ # ===============================================================================
443
+ # END compatibility entry point for the vetiver-python package
444
+ # ===============================================================================
445
+
446
+
447
+ def deploy_python_fastapi(
448
+ connect_server: api.TargetableServer,
449
+ directory: str,
450
+ extra_files: typing.List[str],
451
+ excludes: typing.List[str],
452
+ entry_point: str,
453
+ new: bool,
454
+ app_id: int,
455
+ title: str,
456
+ python: str,
457
+ conda_mode: bool,
458
+ force_generate: bool,
459
+ log_callback: typing.Callable[..., None],
460
+ image: Optional[str] = None,
461
+ env_management_py: Optional[bool] = None,
462
+ env_management_r: Optional[bool] = None,
463
+ ):
464
+ """
465
+ A function to deploy a Python ASGI API module to Posit Connect. Depending on the files involved
466
+ and network latency, this may take a bit of time.
467
+ :param connect_server: the Connect server information.
468
+ :param directory: the app directory to deploy.
469
+ :param extra_files: any extra files that should be included in the deploy.
470
+ :param excludes: a sequence of glob patterns that will exclude matched files.
471
+ :param entry_point: the module/executable object for the WSGi framework.
472
+ :param new: a flag to force this as a new deploy. Previous default = False.
473
+ :param app_id: the ID of an existing application to deploy new files for. Previous default = None.
474
+ :param title: an optional title for the deploy. If this is not provided, one will
475
+ be generated. Previous default = None.
476
+ :param python: the optional name of a Python executable. Previous default = None.
477
+ :param conda_mode: depricated parameter, included for compatibility. Ignored.
478
+ :param force_generate: force generating "requirements.txt" or "environment.yml",
479
+ even if it already exists. Previous default = False.
480
+ :param log_callback: the callback to use to write the log to. If this is None
481
+ (the default) the lines from the deployment log will be returned as a sequence.
482
+ If a log callback is provided, then None will be returned for the log lines part
483
+ of the return tuple. Previous default = None.
484
+ :param image: the optional docker image to be specified for off-host execution. Default = None.
485
+ :param env_management_py: False prevents Connect from managing the Python environment for this bundle.
486
+ The server administrator is responsible for installing packages in the runtime environment. Default = None.
487
+ :param env_management_r: False prevents Connect from managing the R environment for this bundle.
488
+ The server administrator is responsible for installing packages in the runtime environment. Default = None.
489
+ :return: the ultimate URL where the deployed app may be accessed and the sequence
490
+ of log lines. The log lines value will be None if a log callback was provided.
491
+ """
492
+ return deploy_app(app_mode=AppModes.PYTHON_FASTAPI, **locals())
493
+
494
+
495
+ def create_quarto_deployment_bundle(
496
+ file_or_directory: str,
497
+ extra_files: Sequence[str],
498
+ excludes: Sequence[str],
499
+ app_mode: AppMode,
500
+ inspect: QuartoInspectResult,
501
+ environment: Optional[Environment],
502
+ image: Optional[str] = None,
503
+ env_management_py: Optional[bool] = None,
504
+ env_management_r: Optional[bool] = None,
505
+ r_environment: Optional[REnvironment] = None,
506
+ ) -> typing.IO[bytes]:
507
+ """
508
+ Create an in-memory bundle, ready to deploy.
509
+
510
+ :param file_or_directory: The Quarto document or the directory containing the Quarto project.
511
+ :param extra_files: a sequence of any extra files to include in the bundle.
512
+ :param excludes: a sequence of glob patterns that will exclude matched files.
513
+ :param entry_point: the module/executable object for the WSGi framework.
514
+ :param app_mode: the mode of the app being deployed.
515
+ :param environment: environmental information.
516
+ :param extra_files_need_validating: a flag indicating whether the list of extra
517
+ files should be validated or not. Part of validating includes qualifying each
518
+ with the specified directory. If you provide False here, make sure the names
519
+ are properly qualified first. Previous default = True.
520
+ :param image: the optional docker image to be specified for off-host execution. Default = None.
521
+ :param env_management_py: False prevents Connect from managing the Python environment for this bundle.
522
+ The server administrator is responsible for installing packages in the runtime environment. Default = None.
523
+ :param env_management_r: False prevents Connect from managing the R environment for this bundle.
524
+ The server administrator is responsible for installing packages in the runtime environment. Default = None.
525
+ :param r_environment: optional R dependencies detected from renv.lock to add to the manifest.
526
+ :return: the bundle.
527
+ """
528
+ if app_mode is None:
529
+ app_mode = AppModes.STATIC_QUARTO
530
+
531
+ return make_quarto_source_bundle(
532
+ file_or_directory,
533
+ inspect,
534
+ app_mode,
535
+ environment,
536
+ extra_files,
537
+ excludes,
538
+ image,
539
+ env_management_py,
540
+ env_management_r,
541
+ r_environment,
542
+ )
543
+
544
+
545
+ def describe_manifest(file_name: str) -> tuple[str | None, str | None]:
546
+ """
547
+ Determine the entry point and/or primary file from the given manifest file.
548
+ If no entry point is recorded in the manifest, then None will be returned for
549
+ that. The same is true for the primary document. None will be returned for
550
+ both if the file doesn't exist or doesn't look like a manifest file.
551
+
552
+ :param file_name: the name of the manifest file to read.
553
+ :return: the entry point and primary document from the manifest.
554
+ """
555
+ warn("This method has been moved and will be deprecated.", DeprecationWarning, stacklevel=2)
556
+ if basename(file_name) == "manifest.json" and exists(file_name):
557
+ manifest, _ = read_manifest_file(file_name)
558
+ metadata = manifest.get("metadata")
559
+ if metadata:
560
+ # noinspection SpellCheckingInspection
561
+ return (
562
+ metadata.get("entrypoint"),
563
+ metadata.get("primary_rmd") or metadata.get("primary_html"),
564
+ )
565
+ return None, None