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
@@ -0,0 +1,34 @@
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
6
+ "metadata": {},
7
+ "source": [
8
+ "# Hello from $name"
9
+ ]
10
+ },
11
+ {
12
+ "cell_type": "code",
13
+ "id": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
14
+ "execution_count": null,
15
+ "metadata": {},
16
+ "outputs": [],
17
+ "source": [
18
+ "print('Hello from $name!')"
19
+ ]
20
+ }
21
+ ],
22
+ "metadata": {
23
+ "kernelspec": {
24
+ "display_name": "Python 3",
25
+ "language": "python",
26
+ "name": "python3"
27
+ },
28
+ "language_info": {
29
+ "name": "python"
30
+ }
31
+ },
32
+ "nbformat": 4,
33
+ "nbformat_minor": 5
34
+ }
@@ -0,0 +1,13 @@
1
+ [project]
2
+ name = "$name"
3
+ version = "0.0.1"
4
+ requires-python = "$requires_python"
5
+ dependencies = [
6
+ "jupyter",
7
+ ]
8
+
9
+ [tool.rsconnect]
10
+ app_mode = "jupyter-static"
11
+ entrypoint = "notebook.ipynb"
12
+ title = "$name"
13
+ requirements_file = "uv.lock"
@@ -0,0 +1,19 @@
1
+ # $name
2
+
3
+ A Posit Connect project scaffolded by `rsconnect quickstart`.
4
+
5
+ ## Run locally
6
+
7
+ ```
8
+ uv run quarto preview report.qmd
9
+ ```
10
+
11
+ ## Deploy to Posit Connect
12
+
13
+ ```
14
+ rsconnect deploy pyproject .
15
+ ```
16
+
17
+ ## Notes
18
+
19
+ - Quarto must be installed separately: https://quarto.org
@@ -0,0 +1,11 @@
1
+ [project]
2
+ name = "$name"
3
+ version = "0.0.1"
4
+ requires-python = "$requires_python"
5
+ dependencies = []
6
+
7
+ [tool.rsconnect]
8
+ app_mode = "quarto-static"
9
+ entrypoint = "report.qmd"
10
+ title = "$name"
11
+ requirements_file = "uv.lock"
@@ -0,0 +1,8 @@
1
+ ---
2
+ title: "$name"
3
+ format: html
4
+ ---
5
+
6
+ # Hello from $name
7
+
8
+ This is a minimal Quarto document.
@@ -0,0 +1,15 @@
1
+ # $name
2
+
3
+ A Posit Connect project scaffolded by `rsconnect quickstart`.
4
+
5
+ ## Run locally
6
+
7
+ ```
8
+ uv run shiny run app.py
9
+ ```
10
+
11
+ ## Deploy to Posit Connect
12
+
13
+ ```
14
+ rsconnect deploy pyproject .
15
+ ```
@@ -0,0 +1,3 @@
1
+ from shiny.express import ui
2
+
3
+ ui.h1("Hello from $name!")
@@ -0,0 +1,13 @@
1
+ [project]
2
+ name = "$name"
3
+ version = "0.0.1"
4
+ requires-python = "$requires_python"
5
+ dependencies = [
6
+ "shiny",
7
+ ]
8
+
9
+ [tool.rsconnect]
10
+ app_mode = "python-shiny"
11
+ entrypoint = "app.py"
12
+ title = "$name"
13
+ requirements_file = "uv.lock"
@@ -0,0 +1,15 @@
1
+ # $name
2
+
3
+ A Posit Connect project scaffolded by `rsconnect quickstart`.
4
+
5
+ ## Run locally
6
+
7
+ ```
8
+ uv run streamlit run app.py
9
+ ```
10
+
11
+ ## Deploy to Posit Connect
12
+
13
+ ```
14
+ rsconnect deploy pyproject .
15
+ ```
@@ -0,0 +1,3 @@
1
+ import streamlit as st
2
+
3
+ st.write("Hello from $name!")
@@ -0,0 +1,13 @@
1
+ [project]
2
+ name = "$name"
3
+ version = "0.0.1"
4
+ requires-python = "$requires_python"
5
+ dependencies = [
6
+ "streamlit",
7
+ ]
8
+
9
+ [tool.rsconnect]
10
+ app_mode = "python-streamlit"
11
+ entrypoint = "app.py"
12
+ title = "$name"
13
+ requirements_file = "uv.lock"
@@ -0,0 +1,15 @@
1
+ # $name
2
+
3
+ A Posit Connect project scaffolded by `rsconnect quickstart`.
4
+
5
+ ## Run locally
6
+
7
+ ```
8
+ uv run voila notebook.ipynb
9
+ ```
10
+
11
+ ## Deploy to Posit Connect
12
+
13
+ ```
14
+ rsconnect deploy pyproject .
15
+ ```
@@ -0,0 +1,14 @@
1
+ [project]
2
+ name = "$name"
3
+ version = "0.0.1"
4
+ requires-python = "$requires_python"
5
+ dependencies = [
6
+ "voila",
7
+ "jupyter",
8
+ ]
9
+
10
+ [tool.rsconnect]
11
+ app_mode = "jupyter-voila"
12
+ entrypoint = "notebook.ipynb"
13
+ title = "$name"
14
+ requirements_file = "uv.lock"
@@ -0,0 +1,136 @@
1
+ # The contents of this file are copied from:
2
+ # https://github.com/posit-dev/py-shiny/blob/feb4cb7f872922717c39753514ae2d7fa32f10a1/shiny/express/_is_express.py
3
+
4
+ from __future__ import annotations
5
+
6
+ import ast
7
+ import re
8
+ import sys
9
+ from pathlib import Path
10
+ from typing import Literal, cast
11
+
12
+ __all__ = ("is_express_app",)
13
+
14
+
15
+ def is_express_app(app: str, app_dir: str | None) -> bool:
16
+ """Detect whether an app file is a Shiny express app
17
+
18
+ Parameters
19
+ ----------
20
+ app
21
+ App filename, like "app.py". It may be a relative path or absolute path.
22
+ app_dir
23
+ Directory containing the app file. If this is `None`, then `app` must be an
24
+ absolute path.
25
+
26
+ Returns
27
+ -------
28
+ :
29
+ `True` if it is a Shiny express app, `False` otherwise.
30
+ """
31
+ if not app.lower().endswith(".py"):
32
+ return False
33
+
34
+ if app_dir is not None:
35
+ app_path = Path(app_dir) / app
36
+ else:
37
+ app_path = Path(app)
38
+
39
+ if not app_path.exists():
40
+ return False
41
+
42
+ try:
43
+ # Read the file, parse it, and look for any imports of shiny.express.
44
+ with open(app_path, encoding="utf-8") as f:
45
+ content = f.read()
46
+
47
+ # Check for magic comment in the first 1000 characters
48
+ forced_mode = find_magic_comment_mode(content[:1000])
49
+ if forced_mode == "express":
50
+ return True
51
+ elif forced_mode == "core":
52
+ return False
53
+
54
+ tree = ast.parse(content, app_path)
55
+ detector = DetectShinyExpressVisitor()
56
+ detector.visit(tree)
57
+
58
+ except Exception:
59
+ return False
60
+
61
+ return detector.found_shiny_express_import
62
+
63
+
64
+ class DetectShinyExpressVisitor(ast.NodeVisitor):
65
+ def __init__(self):
66
+ super().__init__()
67
+ self.found_shiny_express_import = False
68
+
69
+ def visit_Import(self, node: ast.Import) -> None:
70
+ if any(alias.name == "shiny.express" for alias in node.names):
71
+ self.found_shiny_express_import = True
72
+
73
+ def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
74
+ if node.module == "shiny.express":
75
+ self.found_shiny_express_import = True
76
+ elif node.module == "shiny" and any(alias.name == "express" for alias in node.names):
77
+ self.found_shiny_express_import = True
78
+
79
+ # Visit top-level nodes.
80
+ def visit_Module(self, node: ast.Module) -> None:
81
+ super().generic_visit(node)
82
+
83
+ # Don't recurse into any nodes, so the we'll only ever look at top-level nodes.
84
+ def generic_visit(self, node: ast.AST) -> None:
85
+ pass
86
+
87
+
88
+ def find_magic_comment_mode(content: str) -> Literal["core", "express"] | None:
89
+ """
90
+ Look for a magic comment of the form "# shiny_mode: express" or "# shiny_mode:
91
+ core".
92
+
93
+ If a line of the form "# shiny_mode: x" is found, where "x" is not "express" or
94
+ "core", then a message will be printed to stderr.
95
+
96
+ Returns
97
+ -------
98
+ :
99
+ `"express"` if Shiny Express comment is found, `"core"` if Shiny Core comment is
100
+ found, and `None` if no magic comment is found.
101
+ """
102
+ m = re.search(r"^#[ \t]*shiny_mode:[ \t]*(\S*)[ \t]*$", content, re.MULTILINE)
103
+ if m is not None:
104
+ shiny_mode = cast(str, m.group(1))
105
+ if shiny_mode in ("express", "core"):
106
+ # The "type: ignore" is needed for mypy, which is used on some projects that
107
+ # use duplicates of this code.
108
+ return shiny_mode # type: ignore
109
+ else:
110
+ print(f'Invalid shiny_mode: "{shiny_mode}"', file=sys.stderr)
111
+
112
+ return None
113
+
114
+
115
+ def escape_to_var_name(x: str) -> str:
116
+ """
117
+ Given a string, escape it to a valid Python variable name which contains
118
+ [a-zA-Z0-9_]. All other characters will be escaped to _<hex>_. Also, if the first
119
+ character is a digit, it will be escaped to _<hex>_, because Python variable names
120
+ can't begin with a digit.
121
+ """
122
+ encoded = ""
123
+ is_first = True
124
+
125
+ for char in x:
126
+ if is_first and re.match("[0-9]", char):
127
+ encoded += f"_{ord(char):x}_"
128
+ elif re.match("[a-zA-Z0-9]", char):
129
+ encoded += char
130
+ else:
131
+ encoded += f"_{ord(char):x}_"
132
+
133
+ if is_first:
134
+ is_first = False
135
+
136
+ return encoded
rsconnect/snowflake.py ADDED
@@ -0,0 +1,93 @@
1
+ # pyright: reportMissingTypeStubs=false, reportUnusedImport=false
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ from subprocess import CalledProcessError, CompletedProcess, run
6
+ from typing import Any, Dict, List, Optional
7
+
8
+ from .exception import RSConnectException
9
+ from .log import logger
10
+
11
+
12
+ def snow(*args: str) -> CompletedProcess[str]:
13
+ ensure_snow_installed()
14
+ return run(["snow"] + list(args), capture_output=True, text=True, check=True)
15
+
16
+
17
+ def ensure_snow_installed() -> None:
18
+ try:
19
+ import snowflake.cli # noqa: F401
20
+
21
+ logger.debug("snowflake-cli is installed.")
22
+
23
+ except ImportError:
24
+ logger.warning("snowflake-cli is not installed.")
25
+ try:
26
+ run(["snow", "--version"], capture_output=True, check=True)
27
+ except CalledProcessError:
28
+ raise RSConnectException("snow is installed but could not be run.")
29
+ except FileNotFoundError:
30
+ raise RSConnectException("snow cannot be found.")
31
+
32
+
33
+ def list_connections() -> List[Dict[str, Any]]:
34
+
35
+ try:
36
+ res = snow("connection", "list", "--format", "json")
37
+ connection_list = json.loads(res.stdout)
38
+ return connection_list
39
+ except CalledProcessError:
40
+ raise RSConnectException("Could not list snowflake connections.")
41
+
42
+
43
+ def get_parameters(name: Optional[str] = None) -> Dict[str, Any]:
44
+ """Get Snowflake connection parameters.
45
+ Args:
46
+ name: The name of the connection to retrieve. If None, returns the default connection.
47
+
48
+ Returns:
49
+ A dictionary of connection parameters.
50
+ """
51
+ try:
52
+ from snowflake.connector.config_manager import CONFIG_MANAGER
53
+ except ImportError:
54
+ raise RSConnectException("snowflake-cli is not installed.")
55
+ try:
56
+ connections = CONFIG_MANAGER["connections"]
57
+ if not isinstance(connections, dict):
58
+ raise TypeError("connections is not a dictionary")
59
+
60
+ if name is None:
61
+ def_connection_name = CONFIG_MANAGER["default_connection_name"]
62
+ if not isinstance(def_connection_name, str):
63
+ raise TypeError("default_connection_name is not a string")
64
+ params = connections[def_connection_name]
65
+ else:
66
+ params = connections[name]
67
+
68
+ if not isinstance(params, dict):
69
+ raise TypeError("connection parameters is not a dictionary")
70
+
71
+ return {str(k): v for k, v in params.items()}
72
+
73
+ except (KeyError, AttributeError) as e:
74
+ raise RSConnectException(f"Could not get Snowflake connection: {e}")
75
+
76
+
77
+ def generate_jwt(name: Optional[str] = None) -> str:
78
+
79
+ _ = get_parameters(name)
80
+ connection_name = "" if name is None else name
81
+
82
+ try:
83
+ res = snow("connection", "generate-jwt", "--connection", connection_name, "--format", "json")
84
+ try:
85
+ output = json.loads(res.stdout)
86
+ except json.JSONDecodeError:
87
+ raise RSConnectException(f"Failed to parse JSON from snow-cli: {res.stdout}")
88
+ jwt = output.get("message")
89
+ if jwt is None:
90
+ raise RSConnectException(f"Failed to generate JWT: Missing 'message' field in response: {output}")
91
+ return jwt
92
+ except CalledProcessError as e:
93
+ raise RSConnectException(f"Failed to generate JWT for connection '{name}': {e.stderr}")
File without changes