rsconnect-python 1.33.0__tar.gz → 1.33.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 (69) hide show
  1. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/PKG-INFO +1 -1
  2. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/pyproject.toml +1 -1
  3. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/pyproject.toml.orig +1 -1
  4. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/environment.py +6 -2
  5. rsconnect_python-1.33.2/rsconnect/version_check.py +169 -0
  6. rsconnect_python-1.33.0/rsconnect/version_check.py +0 -154
  7. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/LICENSE.md +0 -0
  8. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/README.md +0 -0
  9. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/__init__.py +0 -0
  10. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/actions.py +0 -0
  11. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/actions_content.py +0 -0
  12. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/actions_environment.py +0 -0
  13. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/actions_integration.py +0 -0
  14. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/api.py +0 -0
  15. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/bundle.py +0 -0
  16. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/certificates.py +0 -0
  17. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/connect_cloud.py +0 -0
  18. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/device_login.py +0 -0
  19. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/environment_node.py +0 -0
  20. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/environment_r.py +0 -0
  21. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/exception.py +0 -0
  22. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/git_metadata.py +0 -0
  23. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/http_support.py +0 -0
  24. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/json_web_token.py +0 -0
  25. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/log.py +0 -0
  26. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/main.py +0 -0
  27. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/metadata.py +0 -0
  28. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/models.py +0 -0
  29. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/oauth.py +0 -0
  30. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/preflight.py +0 -0
  31. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/preflight_node.py +0 -0
  32. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/py.typed +0 -0
  33. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/pyproject.py +0 -0
  34. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/quickstart/__init__.py +0 -0
  35. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/quickstart/quickstart.py +0 -0
  36. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/quickstart/templates/__init__.py +0 -0
  37. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/quickstart/templates/api/README.md.tmpl +0 -0
  38. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/quickstart/templates/api/__connect__.py.tmpl +0 -0
  39. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/quickstart/templates/api/__init__.py.tmpl +0 -0
  40. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/quickstart/templates/api/__main__.py.tmpl +0 -0
  41. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/quickstart/templates/api/app.py.tmpl +0 -0
  42. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/quickstart/templates/api/pyproject.toml.tmpl +0 -0
  43. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/quickstart/templates/fastapi/README.md.tmpl +0 -0
  44. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/quickstart/templates/fastapi/__connect__.py.tmpl +0 -0
  45. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/quickstart/templates/fastapi/__init__.py.tmpl +0 -0
  46. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/quickstart/templates/fastapi/__main__.py.tmpl +0 -0
  47. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/quickstart/templates/fastapi/app.py.tmpl +0 -0
  48. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/quickstart/templates/fastapi/pyproject.toml.tmpl +0 -0
  49. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/quickstart/templates/notebook/README.md.tmpl +0 -0
  50. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/quickstart/templates/notebook/notebook.ipynb.tmpl +0 -0
  51. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/quickstart/templates/notebook/pyproject.toml.tmpl +0 -0
  52. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/quickstart/templates/quarto/README.md.tmpl +0 -0
  53. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/quickstart/templates/quarto/pyproject.toml.tmpl +0 -0
  54. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/quickstart/templates/quarto/report.qmd.tmpl +0 -0
  55. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/quickstart/templates/shiny/README.md.tmpl +0 -0
  56. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/quickstart/templates/shiny/app.py.tmpl +0 -0
  57. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/quickstart/templates/shiny/pyproject.toml.tmpl +0 -0
  58. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/quickstart/templates/streamlit/README.md.tmpl +0 -0
  59. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/quickstart/templates/streamlit/app.py.tmpl +0 -0
  60. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/quickstart/templates/streamlit/pyproject.toml.tmpl +0 -0
  61. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/quickstart/templates/voila/README.md.tmpl +0 -0
  62. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/quickstart/templates/voila/pyproject.toml.tmpl +0 -0
  63. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/shiny_express.py +0 -0
  64. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/snowflake.py +0 -0
  65. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/subprocesses/__init__.py +0 -0
  66. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/subprocesses/inspect_environment.py +0 -0
  67. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/timeouts.py +0 -0
  68. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/utils_package.py +0 -0
  69. {rsconnect_python-1.33.0 → rsconnect_python-1.33.2}/rsconnect/validation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: rsconnect_python
3
- Version: 1.33.0
3
+ Version: 1.33.2
4
4
  Summary: The Posit Connect command-line interface.
5
5
  Author: Posit, PBC
6
6
  Author-email: Posit, PBC <rsconnect@posit.co>
@@ -2,7 +2,7 @@
2
2
  name = "rsconnect_python"
3
3
  description = "The Posit Connect command-line interface."
4
4
  requires-python = ">=3.8"
5
- version = "1.33.0"
5
+ version = "1.33.2"
6
6
  dependencies = [
7
7
  "typing-extensions>=4.8.0",
8
8
  "pip>=20.3",
@@ -2,7 +2,7 @@
2
2
  name = "rsconnect_python"
3
3
  description = "The Posit Connect command-line interface."
4
4
  requires-python = ">=3.8"
5
- version = "1.33.0"
5
+ version = "1.33.2"
6
6
  dependencies = [ "typing-extensions>=4.8.0", "pip>=20.3", "uv>=0.9.0", "semver>=2.0.0,<4.0.0", "pyjwt>=2.4.0; python_version < '3.9'", "pyjwt>=2.15.0; python_version >= '3.9'", "click>=8.0.0", "packaging>=20.0", "toml>=0.10; python_version < '3.11'",]
7
7
  [[project.authors]]
8
8
  name = "Posit, PBC"
@@ -15,7 +15,6 @@ import json
15
15
  import pathlib
16
16
  import os.path
17
17
  import enum
18
- import uv
19
18
 
20
19
  from . import pyproject
21
20
  from .subprocesses import inspect_environment
@@ -273,7 +272,12 @@ class Environment:
273
272
 
274
273
  def _uv_bin() -> str:
275
274
  try:
276
- return uv.find_uv_bin()
275
+ # conda-forge's uv package installs only the executable, not this module.
276
+ from uv import find_uv_bin
277
+ except ImportError:
278
+ return "uv"
279
+ try:
280
+ return find_uv_bin()
277
281
  except FileNotFoundError:
278
282
  return "uv"
279
283
 
@@ -0,0 +1,169 @@
1
+ """Background version update check against PyPI.
2
+
3
+ Checked only on deploy commands. The latest known version is cached on disk and
4
+ refreshed at most once per :data:`_CACHE_TTL_SECONDS` in a background thread, so a
5
+ warm cache adds no network traffic and no latency and prints on every exit path
6
+ (including fast failures). Only a cold cache -- the first run, or an ephemeral
7
+ environment where the cache never persists -- waits briefly on the fetch so it can
8
+ still warn.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import os
15
+ import platform
16
+ import sys
17
+ import threading
18
+ import time
19
+ from http.client import HTTPSConnection
20
+ from os.path import join
21
+ from typing import NamedTuple, Optional, Tuple
22
+
23
+ from packaging.specifiers import InvalidSpecifier, SpecifierSet
24
+ from packaging.version import Version
25
+
26
+ from . import VERSION
27
+ from .metadata import config_dirname, makedirs
28
+
29
+ RSCONNECT_DISABLE_VERSION_CHECK = "RSCONNECT_DISABLE_VERSION_CHECK"
30
+ _PYPI_HOST = "pypi.org"
31
+ _PYPI_PATH = "/pypi/rsconnect-python/json"
32
+ _PYPI_TIMEOUT_SECONDS = 2
33
+ _CACHE_TTL_SECONDS = 24 * 60 * 60
34
+ _CACHE_FILENAME = "version_check.json"
35
+
36
+
37
+ class _Release(NamedTuple):
38
+ version: str
39
+ requires_python: Optional[str]
40
+
41
+
42
+ def _is_check_disabled() -> bool:
43
+ value = os.environ.get(RSCONNECT_DISABLE_VERSION_CHECK, "").strip().lower()
44
+ return value in ("1", "true", "yes")
45
+
46
+
47
+ def _is_dev_version(version_str: str) -> bool:
48
+ try:
49
+ return Version(version_str).is_devrelease
50
+ except Exception:
51
+ return True
52
+
53
+
54
+ def _cache_path() -> str:
55
+ return join(config_dirname(), _CACHE_FILENAME)
56
+
57
+
58
+ def _read_cache() -> Tuple[bool, Optional[_Release]]:
59
+ """Return freshness and the cached release, retaining stale data for the hint."""
60
+ try:
61
+ with open(_cache_path()) as f:
62
+ data = json.load(f)
63
+ latest = data.get("latest")
64
+ requires_python = data.get("requires_python")
65
+ release = (
66
+ _Release(latest, requires_python if isinstance(requires_python, str) else None)
67
+ if isinstance(latest, str)
68
+ else None
69
+ )
70
+ is_fresh = "requires_python" in data and time.time() - float(data["checked_at"]) <= _CACHE_TTL_SECONDS
71
+ return (is_fresh, release)
72
+ except Exception:
73
+ return (False, None)
74
+
75
+
76
+ def _write_cache(release: Optional[_Release]) -> None:
77
+ """Cache failures too, so they do not trigger another request until the TTL expires."""
78
+ try:
79
+ path = _cache_path()
80
+ makedirs(path)
81
+ with open(path, "w") as f:
82
+ json.dump(
83
+ {
84
+ "checked_at": time.time(),
85
+ "latest": release.version if release else None,
86
+ "requires_python": release.requires_python if release else None,
87
+ },
88
+ f,
89
+ )
90
+ except Exception:
91
+ pass
92
+
93
+
94
+ def _fetch_latest_version() -> Optional[_Release]:
95
+ conn = None
96
+ try:
97
+ conn = HTTPSConnection(_PYPI_HOST, timeout=_PYPI_TIMEOUT_SECONDS)
98
+ conn.request("GET", _PYPI_PATH, headers={"Accept": "application/json"})
99
+ response = conn.getresponse()
100
+ if response.status != 200:
101
+ return None
102
+ info = json.loads(response.read()).get("info", {})
103
+ latest = info.get("version")
104
+ requires_python = info.get("requires_python")
105
+ if isinstance(latest, str):
106
+ return _Release(latest, requires_python if isinstance(requires_python, str) else None)
107
+ return None
108
+ except Exception:
109
+ return None
110
+ finally:
111
+ if conn is not None:
112
+ conn.close()
113
+
114
+
115
+ def _update_message(release: Optional[_Release]) -> Optional[str]:
116
+ """Return an upgrade warning if the release is newer than the running version."""
117
+ try:
118
+ if release is None or Version(release.version) <= Version(VERSION):
119
+ return None
120
+ python_version = platform.python_version()
121
+ try:
122
+ compatible = not release.requires_python or SpecifierSet(release.requires_python).contains(
123
+ python_version, prereleases=True
124
+ )
125
+ except InvalidSpecifier:
126
+ compatible = True
127
+ if not compatible:
128
+ force = "" if os.path.isfile(join(sys.prefix, "uv-receipt.toml")) else "--force "
129
+ return (
130
+ f"The latest version of rsconnect-python ({release.version}) requires Python "
131
+ f"{release.requires_python}, but you are running Python {python_version}.\n"
132
+ f'Install it with: uv tool install {force}--python "{release.requires_python}" rsconnect-python'
133
+ )
134
+ return (
135
+ f"A new version of rsconnect-python is available: {release.version} "
136
+ f"(you have {VERSION}).\n"
137
+ "Upgrade with: pip install --upgrade rsconnect-python"
138
+ )
139
+ except Exception:
140
+ return None
141
+
142
+
143
+ class BackgroundVersionCheck:
144
+ """Resolves the latest available version, using a cache and a background fetch."""
145
+
146
+ def __init__(self) -> None:
147
+ self._thread: Optional[threading.Thread] = None
148
+ self._release: Optional[_Release] = None
149
+
150
+ def start(self) -> None:
151
+ if _is_check_disabled() or _is_dev_version(VERSION):
152
+ return
153
+ is_fresh, self._release = _read_cache()
154
+ if not is_fresh:
155
+ self._thread = threading.Thread(target=self._run, daemon=True)
156
+ self._thread.start()
157
+
158
+ def _run(self) -> None:
159
+ release = _fetch_latest_version()
160
+ _write_cache(release)
161
+ # Keep a usable stale value when a refresh fails.
162
+ if release is not None:
163
+ self._release = release
164
+
165
+ def get_warning_message(self) -> Optional[str]:
166
+ # Only a cold cache waits briefly for the background fetch.
167
+ if self._release is None and self._thread is not None:
168
+ self._thread.join(timeout=_PYPI_TIMEOUT_SECONDS)
169
+ return _update_message(self._release)
@@ -1,154 +0,0 @@
1
- """Background version update check against PyPI.
2
-
3
- Checked only on deploy commands. The latest known version is cached on disk and
4
- refreshed at most once per :data:`_CACHE_TTL_SECONDS` in a background thread, so a
5
- warm cache adds no network traffic and no latency and prints on every exit path
6
- (including fast failures). Only a cold cache -- the first run, or an ephemeral
7
- environment where the cache never persists -- waits briefly on the fetch so it can
8
- still warn.
9
- """
10
-
11
- from __future__ import annotations
12
-
13
- import json
14
- import os
15
- import threading
16
- import time
17
- from http.client import HTTPSConnection
18
- from os.path import join
19
- from typing import Optional, Tuple
20
-
21
- from packaging.version import Version
22
-
23
- from . import VERSION
24
- from .metadata import config_dirname, makedirs
25
-
26
- RSCONNECT_DISABLE_VERSION_CHECK = "RSCONNECT_DISABLE_VERSION_CHECK"
27
- _PYPI_HOST = "pypi.org"
28
- _PYPI_PATH = "/pypi/rsconnect-python/json"
29
- _PYPI_TIMEOUT_SECONDS = 2
30
- _CACHE_TTL_SECONDS = 24 * 60 * 60 # Re-check PyPI at most once a day.
31
- _CACHE_FILENAME = "version_check.json"
32
-
33
-
34
- def _is_check_disabled() -> bool:
35
- value = os.environ.get(RSCONNECT_DISABLE_VERSION_CHECK, "").strip().lower()
36
- return value in ("1", "true", "yes")
37
-
38
-
39
- def _is_dev_version(version_str: str) -> bool:
40
- try:
41
- return Version(version_str).is_devrelease
42
- except Exception:
43
- return True
44
-
45
-
46
- def _cache_path() -> str:
47
- return join(config_dirname(), _CACHE_FILENAME)
48
-
49
-
50
- def _read_cache() -> Tuple[bool, Optional[str]]:
51
- """Return ``(is_fresh, latest)``.
52
-
53
- ``latest`` is the last known PyPI version recorded in the cache (or None when
54
- the cache is missing/unreadable or the last fetch failed). It is returned
55
- regardless of freshness so even a stale value can drive the upgrade hint while
56
- a refresh runs in the background. ``is_fresh`` is True only when the entry is
57
- within the TTL, signalling that no background refresh is needed.
58
- """
59
- try:
60
- with open(_cache_path()) as f:
61
- data = json.load(f)
62
- latest = data.get("latest")
63
- latest = latest if isinstance(latest, str) else None
64
- is_fresh = time.time() - float(data["checked_at"]) <= _CACHE_TTL_SECONDS
65
- return (is_fresh, latest)
66
- except Exception:
67
- return (False, None)
68
-
69
-
70
- def _write_cache(latest: Optional[str]) -> None:
71
- """Persist the latest known version (or None) with the current timestamp.
72
-
73
- A None value is still cached so repeated failures don't re-hit PyPI on every
74
- deploy until the TTL expires.
75
- """
76
- try:
77
- path = _cache_path()
78
- makedirs(path)
79
- with open(path, "w") as f:
80
- json.dump({"checked_at": time.time(), "latest": latest}, f)
81
- except Exception:
82
- pass
83
-
84
-
85
- def _fetch_latest_version() -> Optional[str]:
86
- conn = None
87
- try:
88
- conn = HTTPSConnection(_PYPI_HOST, timeout=_PYPI_TIMEOUT_SECONDS)
89
- conn.request("GET", _PYPI_PATH, headers={"Accept": "application/json"})
90
- response = conn.getresponse()
91
- if response.status != 200:
92
- return None
93
- data = json.loads(response.read())
94
- return data.get("info", {}).get("version")
95
- except Exception:
96
- return None
97
- finally:
98
- if conn is not None:
99
- conn.close()
100
-
101
-
102
- def _update_message(latest: Optional[str]) -> Optional[str]:
103
- """Return an upgrade warning if ``latest`` is newer than the running version."""
104
- try:
105
- if latest is not None and Version(latest) > Version(VERSION):
106
- return (
107
- f"A new version of rsconnect-python is available: {latest} "
108
- f"(you have {VERSION}).\n"
109
- f"Upgrade with: pip install --upgrade rsconnect-python"
110
- )
111
- except Exception:
112
- pass
113
- return None
114
-
115
-
116
- class BackgroundVersionCheck:
117
- """Resolves the latest available version, using a cache and a background fetch."""
118
-
119
- def __init__(self) -> None:
120
- self._thread: Optional[threading.Thread] = None
121
- self._latest: Optional[str] = None
122
-
123
- def start(self) -> None:
124
- if _is_check_disabled() or _is_dev_version(VERSION):
125
- return
126
- is_fresh, latest = _read_cache()
127
- # Seed the hint from the cached value (even a stale one is good enough to
128
- # suggest an upgrade), so a warm cache prints instantly on every exit path
129
- # -- including commands that fail fast.
130
- self._latest = latest
131
- if not is_fresh:
132
- # Refresh in the background so the fetch overlaps the command's own
133
- # work. A warm or stale cache never waits on this; only a cold cache
134
- # waits briefly at exit -- see get_warning_message.
135
- self._thread = threading.Thread(target=self._run, daemon=True)
136
- self._thread.start()
137
-
138
- def _run(self) -> None:
139
- latest = _fetch_latest_version()
140
- _write_cache(latest)
141
- # Adopt a successful result so a cold-cache run can warn this time, but
142
- # never overwrite a usable cached value with a failed (None) refresh.
143
- if latest is not None:
144
- self._latest = latest
145
-
146
- def get_warning_message(self) -> Optional[str]:
147
- # With no cached value yet (a cold cache: first run, or an ephemeral
148
- # environment where the cache never persists), give the in-flight fetch a
149
- # bounded chance to finish so this run can still warn. The bound is the
150
- # fetch's own time budget -- a slower network just means no hint this run.
151
- # A warm or stale cache already has a value here and never waits.
152
- if self._latest is None and self._thread is not None:
153
- self._thread.join(timeout=_PYPI_TIMEOUT_SECONDS)
154
- return _update_message(self._latest)