Skip to content

Conversation

@amol-
Copy link
Collaborator

@amol- amol- commented Sep 18, 2025

Intent

Allows users to pass a --package-installer=pip|uv option to force a specific package manager.
By default it's up to the server (and it's backward compatible with server that didn't accept uv)

Closes #707

Type of Change

  • Bug Fix
  • New Feature
  • Breaking Change

Approach

The package_manager setting is now propagated in the Environment object and can be used by both deploy and write manifest processes.

Automated Tests

Added tests for the 3 cases

  • =uv
  • =pip
  • omitted

Directions for Reviewers

If in doubt see https://docs.posit.co/connect/user/manifest/#python

Checklist

  • I have updated CHANGELOG.md to cover notable changes.
  • I have updated all related GitHub issues to reflect their current state.

@github-actions
Copy link

github-actions bot commented Sep 18, 2025

PR Preview Action v1.6.3

🚀 View preview at
https://posit-dev.github.io/rsconnect-python/pr-preview/pr-708/

Built to branch gh-pages at 2025-12-11 15:25 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@amol- amol- changed the title first import feat: --package-manager option Sep 18, 2025
@github-actions
Copy link

github-actions bot commented Sep 18, 2025

☂️ Python Coverage

current status: ✅

Overall Coverage

Lines Covered Coverage Threshold Status
5107 3869 76% 0% 🟢

New Files

No new covered files...

Modified Files

File Coverage Status
rsconnect/bundle.py 80% 🟢
rsconnect/environment.py 87% 🟢
rsconnect/main.py 67% 🟢
TOTAL 78% 🟢

updated for commit: 7cf1ee6 by action🐍

@nealrichardson
Copy link
Contributor

I apologize in advance for the driveby comment about naming, but: given that "Package Manager" is one of Posit's products, I worry that calling this argument --package-manager is ambiguous or confusing. My instinct, without reading the docs, would be that I would provide a URL to the repository (Posit Package Manager) where I want packages to be downloaded from, not that this is how I would specify whether to use pip or uv pip.

I don't have an alternative to suggest, just wanted to flag this. Maybe this is fine?

@tdstein
Copy link
Collaborator

tdstein commented Sep 24, 2025

I apologize in advance for the driveby comment about naming, but: given that "Package Manager" is one of Posit's products, I worry that calling this argument --package-manager is ambiguous or confusing. My instinct, without reading the docs, would be that I would provide a URL to the repository (Posit Package Manager) where I want packages to be downloaded from, not that this is how I would specify whether to use pip or uv pip.

I don't have an alternative to suggest, just wanted to flag this. Maybe this is fine?

I think "--package-installer" could work since this is strictly about installation and not other package management operations.

PyPI references "package installer":
https://pypi.org/project/pip/

pip is the package installer for Python.

The Python Packaging guide also generally qualifies "installation"
https://packaging.python.org/en/latest/guides/tool-recommendations/#installing-packages

pip is the standard tool to install packages from PyPI.

The UV documentation is less precise:
https://docs.astral.sh/uv/getting-started/features/#the-pip-interface

uv pip install: Install packages into the current environment.

@amol-
Copy link
Collaborator Author

amol- commented Sep 24, 2025

I apologize in advance for the driveby comment about naming, but: given that "Package Manager" is one of Posit's products, I worry that calling this argument --package-manager is ambiguous or confusing. My instinct, without reading the docs, would be that I would provide a URL to the repository (Posit Package Manager) where I want packages to be downloaded from, not that this is how I would specify whether to use pip or uv pip.

in Python specific terms that would probably be the "Package Index": https://pip.pypa.io/en/latest/cli/pip_search/#cmdoption-i

I guess that anyone using the PPM would be used to already specify that as the "index" both in requirements.txt and command line, so I feel it wouldn't confuse them much.
If we were in the R ecosystem I think your concern would have been very concrete, but in Python there is consistency from all tools to use the word Index and thus people are generally already exposed to it.

@amol-
Copy link
Collaborator Author

amol- commented Sep 24, 2025

I think "--package-installer" could work since this is strictly about installation and not other package management operations.

I don't have a strong opinion, on this side there is a lot of confusion 🤷🏻

  • UV docs -> An extremely fast Python package and project manager, written in Rust.
  • Pip docs -> pip is the package installer for Python
  • poetry docs -> Poetry is a tool for dependency management and packaging in Python
  • conda docs -> Conda is a powerful command line tool for package and environment management
  • pipx docs -> pix is a tool ... focused on installing and managing Python packages

@amol- amol- marked this pull request as ready for review September 24, 2025 15:05
@jonkeane
Copy link
Collaborator

jonkeane commented Oct 3, 2025

What about having --use-pip and the mutually exclusive --use-uv? I know it's a little funky to have two options that are mutually exclusive, but in this case it gets us out of this bind. I don't think we need to worry too much that there will be large numbers of these so having many many options won't happen. And I also suspect that folks will have pip or uv closer at hand / as rigid designators rather than the specific class of things those things are. (eg: I'm pretty sure I hear more people say things like "Do you use pip or uv?" rather than hearing "Which package [manager|installer] do you use?").

I share Neal's concern that the unfortunate collision with the Posit product is likely to lead to confusion. If we had to we could recover from that being super explicit if someone puts something other than uv or pip in there, but this is an area that is already mildly confusing general, I would like to not add a log on that fire if we don't have to.

@amol-
Copy link
Collaborator Author

amol- commented Dec 9, 2025

@jonkeane this has been open for a while. I'm not fond of --use-pip because it's specific and we might end up growing further possible values in the future. But I'm happy to change the option to be --package-installer=pip|uv to avoid the clash with the posit package manager name. If that's ok I'll rebase the PR and get it out.

@jonkeane
Copy link
Collaborator

jonkeane commented Dec 9, 2025

@jonkeane this has been open for a while. I'm not fond of --use-pip because it's specific and we might end up growing further possible values in the future. But I'm happy to change the option to be --package-installer=pip|uv to avoid the clash with the posit package manager name. If that's ok I'll rebase the PR and get it out.

Sure. I still truly believe that my original suggestion (--use-pip and --use-uv) is much more likely to be something the resonates with our users, but if the way to move this forward is to do --package-installer=pip|uv, that's fine.

@amol- amol- force-pushed the set-required-package-manager branch from 2748dae to d93dc53 Compare December 11, 2025 15:19
@amol- amol- changed the title feat: --package-manager option feat: --package-installer option Dec 11, 2025
@tdstein tdstein self-requested a review December 12, 2025 15:04
Copy link
Collaborator

@tdstein tdstein left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this looks good overall. The various possible configuration states is a bit confusing to me, but that may be unavoidable due to the server requirements.

I haven't run this code yet, so please let me know if you'd like me to sanity check that it works as expected.

Comment on lines -59 to +62
self._data._replace(**{name: value})
self._data = self._data._replace(**{name: value})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😱 This was a bug?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep... somehow we never faced it, but the data was indeed never updated

# Override the package manager name recorded by inspector
environment.package_manager = package_manager # type: ignore[attr-defined]
# Derive allow_uv from selection
environment.package_manager_allow_uv = True if package_manager == "uv" else False
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to confirm my understanding... Is package_manager_allow_uv the same as "set the package manager to uv"?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

python.package_manager.allow_uv in the manifest means: "Allow use of uv, if the server is configured to use it as the default package manager". When allow_uv=True if you configured python.package_manager.name=pip you allow the server to override your preference for "pip" if the server is configured to prefer "uv".

python: typing.Optional[str] = None,
override_python_version: typing.Optional[str] = None,
app_file: typing.Optional[str] = None,
package_manager: typing.Optional[str] = None,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would a Literal be better here? It looks like click is enforcing that the value is uv or pip at the command line. So, internally we could just defer to the type system to make sure we aren't passing around other values.

Comment on lines +161 to +162
if package_manager not in ("pip", "uv"):
raise RSConnectException("Unsupported package manager: %s" % package_manager)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing package_manager to a Literal['uv', 'pip'] might remove the need for this check.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair, I retained the pattern the code has of checking values at runtime,
I'll switch to a validated value. But in this case I'd consider StrEnum a much better choice as it's a domain specific value, it benefits from being reusable and it's runtime safe too.

help='Force generating "requirements.txt", even if it already exists.',
)
@click.option(
"--package-installer",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we discussed this elsewhere, but having "package installer" here and then "package manager" in the source code is a little confusing.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

package_manager is the actual name it has in the manifest.json, so the code related to generating the manifest uses that name. I agree that this is very confusing, but I feel it's even more surprising if you assign one value to the environment but then it writes a different one in the manifest.

) as bundle, tarfile.open(mode="r:gz", fileobj=bundle) as tar:
manifest = json.loads(tar.extractfile("manifest.json").read().decode("utf-8"))
assert manifest["python"]["package_manager"]["name"] == "uv"
assert manifest["python"]["package_manager"]["allow_uv"] is True
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, I'm not sure why "allow_uv" is required here alongside "name" = "uv". It seems like the server should respond with an error if ["python"]["package_manager"]["name"] == "uv" but it doesn't support uv.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not required as far as I remember, you are correct. I just preferred to avoid any gray areas and set both values to "use uv" and be consistent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

allow enforcing a specific package manager

5 participants