fix: send the Python version to Connect Cloud - #849
Conversation
Connect Cloud picks the interpreter from the revision's python_version field
on POST /v1/contents and PATCH /v1/contents/{id}. rsconnect never sent it, and
Connect Cloud does not read the version out of the bundle's manifest, so every
deploy built with its fallback of Python 3.9 no matter what the project asked
for. Content needing 3.10+ failed during dependency resolution.
Resolve a MAJOR.MINOR version from the manifest the bundle already carries and
send it on both requests. Reading the bundle rather than the environment covers
`deploy manifest` too, which does no environment inspection of its own.
The interpreter the bundle was built against wins when Connect Cloud offers
that MAJOR.MINOR and the constraint allows it. Otherwise the lowest offered
version satisfying the constraint is used, so ">=3.10" stays on 3.10 as Connect
Cloud adds newer ones. A constraint no offered version satisfies now fails
before the bundle is uploaded.
Both payloads omit the field when there is no Python version to send, so R
content deploys unchanged and a redeploy does not clear a version picked in the
Connect Cloud UI.
r_version and quarto_version are accepted on the same payloads and are still
never sent; that is left for a separate change.
|
☂️ Python Coverage
Overall Coverage
New FilesNo new covered files... Modified Files
|
| raise RSConnectException( | ||
| "This content requires Python %s, which Posit Connect Cloud does not offer. " | ||
| "Supported versions are %s. Change the requirement in .python-version, " | ||
| "pyproject.toml, or setup.cfg." % (requires, ", ".join(PYTHON_VERSIONS)) |
There was a problem hiding this comment.
or, the user might need to upgrade their rsconnect-python version (if we add a new python version to the PYTHON_VERSIONS list)
There was a problem hiding this comment.
good point. i'm going to get rid of the list of versions
The resolver held the whole RequestPythonVersion enum and refused anything outside it. That list goes stale in the direction that hurts: the day Connect Cloud adds 3.15, a project requiring it would be told to change a requirement that is in fact fine, and the only real remedy would be upgrading rsconnect. Take the version from the constraint's lower bound rather than by enumerating a candidate set, and send it without judging whether Connect Cloud offers it. A version added after this release now deploys as it is. Keep only the floor. Requests below it are rejected outright rather than normalized -- the fallback to the lowest supported version applies to versions already stored, not to ones arriving over the API -- and rsconnect still runs on Pythons older than Connect Cloud offers, so a 3.8 user takes the platform default with a warning instead of a failed deploy. This drops the patch enumeration along with the list, since the lower bound carries the same information without a bound to pick.
karawoo
left a comment
There was a problem hiding this comment.
I think there are some cases here where someone could get a Python version they've explicitly forbidden which will be confusing. If they've set .python-version to >3.11.5, we request 3.11, and if Connect Cloud only has 3.11.2 they'll be running on a version <3.11.5. Another scenario is if the version constraint isn't satisfiable by Connect Cloud, e.g. >=3.8,<3.9. 3.8 is the only version that satisfies this but it's lower than Connect Cloud's floor so it resolves to None and then Connect Cloud will pick a higher version that's expliclty not allowed according to the constraint.
| Connect Cloud picks the patch itself, so a constraint that names one ("==3.11.14") | ||
| can only be honored as far as its minor line. |
There was a problem hiding this comment.
Is there no way to respect the patch version? If not, can we include some sort of warning or info logging if the .python-version specifies a patch version so users know it won't be respected?
There was a problem hiding this comment.
no, Connect Cloud doesn't have all the patch versions available. we can provide the detailed patch but connect cloud will basically ignore it and choose the latest patch available. We could simplify this and just send the full patch version, then let connect cloud decide what to do. that might be less complicated
There was a problem hiding this comment.
turns out this doesn't work (Connect Cloud doesn't accept it). i'll add some logging
There was a problem hiding this comment.
I see we're now logging Requesting Python <major>.<minor> but I don't think that is enough to convey that the patch version is being ignored.
| Whether Connect Cloud offers the result is left to Connect Cloud, so a version | ||
| it adds after this release still deploys. The one version rule kept here is the | ||
| floor: below it the request would be rejected outright, and omitting the field | ||
| to take the platform default is more useful than failing. |
There was a problem hiding this comment.
This seems like context for decisions within the PR more than external facing documentation
There was a problem hiding this comment.
agreed... will clean this up
Say which version each deploy asks for, and warn when nothing Connect Cloud offers can satisfy the requirement. A .python-version of 3.11.5 is widened to the 3.11 line before the manifest is written, so the request was previously silent about dropping the patch. Also resolve from the higher of the requirement's lowest version and Connect Cloud's, so a requirement it can meet no longer falls back to the platform default: ">=3.8" resolves to 3.9 instead of nothing.
|
@karawoo i think i've addressed all the issues you brought up. thanks! |
| Connect Cloud picks the patch itself, so a constraint that names one ("==3.11.14") | ||
| can only be honored as far as its minor line. |
There was a problem hiding this comment.
I see we're now logging Requesting Python <major>.<minor> but I don't think that is enough to convey that the patch version is being ignored.
| nothing usable to send, which leaves the field off the request so Connect | ||
| Cloud keeps whatever the content already has. |
There was a problem hiding this comment.
This assumes a redeploy, can we also note what happens on first deploy?
There was a problem hiding this comment.
i updated all of these... the log about patch version being ignored doesn't actually say what patch version is being ignored, since at the time that log happens, the info isn't readily available, so it require a little more surgery than i thought was needed. let me know if you think that is still not strong enough. (but if thats the only thing left, maybe we could do that in a future PR)
| # with one, the search below may still find a version it does offer. | ||
| if specifier is None: | ||
| logger.warning( | ||
| "Posit Connect Cloud does not offer Python %s; it will choose a version for this content." % minor |
There was a problem hiding this comment.
This warning message seems specific to first deploys; redeploys behave differently right (reuse the current python version for the content)?
| "No Python version Posit Connect Cloud offers satisfies the requirement %s. " | ||
| "It will choose a version, and the content will run on one the requirement " | ||
| "does not allow." % requires | ||
| ) |
There was a problem hiding this comment.
This also seems inaccurate in the case of redeploys.
| None means the request omits the field, leaving the version Connect Cloud | ||
| already has on the content. Content with no Python at all lands here too. |
There was a problem hiding this comment.
What if this is a first deploy?
| if minor is not None and Version(minor) >= MINIMUM_PYTHON_VERSION: | ||
| return _log_requested(minor) |
There was a problem hiding this comment.
What happens if the local version we send is higher than the highest one Connect Cloud has?
There was a problem hiding this comment.
the build will fail with an error message that the requested python version is unsupported. I thought this was better than maintaining a list of supported versions in the client and requiring a patch when new versions are added. Do you think we should maintain a list here?
There was a problem hiding this comment.
No I don't think we should maintain a list, I think it makes sense to fail if the requested version isn't supported, but it seems odd that the behavior is different depending on if the user's python version is too low or too high.
There was a problem hiding this comment.
true... i considering dropping the min too 🤷 there is no chance we will ever support python 3.8 or 3.7 (or anything lower) so its possible to short circuit in that case
The warnings and docstrings described only a redeploy, where leaving python_version off the request keeps the version already on the content. On a first deploy there is no stored version and Connect Cloud uses its own default, which the messages did not mention. Both warnings now end with the same sentence naming the two outcomes. resolve_python_version runs before prepare_deploy has looked the content up, so it cannot tell which case a deploy is in. Also say that Connect Cloud chooses the patch release when logging the request. "Requesting Python 3.11" alone did not convey that a patch the user asked for was dropped.
Intent
Resolves #848.
Deploys to Posit Connect Cloud always built with Connect Cloud's fallback of Python 3.9, whatever the project asked for. rsconnect detects the Python version requirement correctly and writes it into
manifest.json, but never sent it to the Connect Cloud API — and Connect Cloud does not read it out of the bundle. Content needing 3.10+ failed during dependency resolution.Reported on the forum: https://forum.posit.co/t/set-python-version-on-connect-cloud-when-deploying-using-rsconnect-via-command-line/217824
Type of Change
Approach
Connect Cloud takes the interpreter from the revision's
python_versionfield —next_revision.python_versiononPOST /v1/contents,revision_overrides.python_versiononPATCH /v1/contents/{id}. It is MAJOR.MINOR only, from a fixed set (RequestPythonVersionin https://api.connect.posit.cloud/openapi.json, currently 3.9 through 3.14). Posit Publisher already sends it; neither rsconnect client did.resolve_python_version()derives the value from the manifest the bundle already carries —environment.python.requiresandpython.version. Reading the built bundle rather than the environment meansdeploy manifestis covered too, which does no environment inspection of its own, and no per-command plumbing was needed.Which version wins:
>=3.10stays on 3.10 as Connect Cloud adds newer ones rather than drifting upward.Two trade-offs worth flagging:
Patch-level constraints can only be honored as far as their minor line. The API cannot express a patch, so
==3.11.14deploys onto whatever 3.11.x Connect Cloud runs. Rejecting such constraints would refuse content Connect Cloud can run, and there is no endpoint exposing the patch behind each line. This matches Connect's own default of MAJOR.MINOR matching.Judging a constraint against a minor line needs care. Testing
3.11alone treats it as3.11.0, which would wrongly reject>=3.11.3,<3.12._line_satisfies()instead tests the patches the constraint names, one above each, and the bottom of the line — every edge of the region a PEP 440 constraint admits falls on one of those. This avoids an arbitrary patch cap, so==3.11.99and==3.11.100behave identically.Both payloads omit the field when there is no version to send, so R content deploys unchanged and a redeploy does not clear a version picked in the Connect Cloud UI.
python_versionsits at the end of the optional parameters oncreate_content,update_content, andprepare_deployso it cannot capture a positional argument.r_versionandquarto_versionare accepted on the same payloads and are still never sent — likely the same bug for R and Quarto content, left for a separate change since confirming it needs a real deploy.Automated Tests
25 new tests in
tests/test_connect_cloud.py:TestResolvePythonVersion— constraint shapes (~=,>=,==X.Y.*, exact patch,!=), local-interpreter preference, versions outside the offered range, unparsable constraints, and the high-patch cases.TestConnectCloudPythonVersionFromBundle— reads the version out of real gzipped bundles, including a manifest with no Python section.create_contentandupdate_content.prepare_deploypassthrough on the create and update paths._line_satisfies()was additionally checked against exhaustive enumeration over patches 0–400, across 9,996 line/constraint pairs generated from 1,666 constraints, with no disagreements. That check is not committed; it is a one-off verification of the boundary-point argument.Full suite: 1240 passed, 12 skipped.
ruffclean. Pyright unchanged from baseline.Directions for Reviewers
The Python version rsconnect runs under is the one it inspects, so install the branch into the project's venv rather than running it from this repo:
-vvlogs request bodies. Look for"python_version": "3.12"innext_revisionon a first deploy, orrevision_overrideson a redeploy, and confirm the build log shows 3.12 rather than 3.9.17.Worth exercising:
.python-versionset to3.8, which should fail before the upload with "Posit Connect Cloud does not offer".null, so a version set in the UI survives.deploy manifest, which is the path from the forum report.Checklist
rsconnect-python-tests-at-nightworkflow in Connect against this feature branch.