Skip to content

Evaluate Python dependency source precedence #975

Open

Description

@bschwedler

I’d prefer a lockfile or pyproject.toml over requirements.txt, which can be a stale export. Currently, the command determines the default source.

✓ = present; — = absent. No source overrides. deploy pyproject also requires valid [tool.rsconnect] configuration.

requirements.txt pyproject.toml uv.lock deploy <framework> deploy pyproject
— — — Error Error
✓ — — Requirements Error
— ✓ — Error Pyproject
— — ✓ Error Error
✓ ✓ — Requirements Pyproject
✓ — ✓ Requirements Error
— ✓ ✓ Error Pyproject
✓ ✓ ✓ Requirements Pyproject

-r uv.lock explicitly selects the lockfile. Could we use consistent automatic selection across commands, while keeping explicit overrides?

Related issues

Activity

  1. tdstein commented on Nov 12, 2026

    @tdstein
    Collaborator

    This was generated by AI during triage.

    Agent Brief

    Category: enhancement
    Summary: Select Python dependency sources consistently across deployment and manifest commands, preserving explicit choices and user-managed sources.

    Prerequisite: Blocked by #974, implemented by PR #976. Use [tool.posit.connect] and preserve its legacy [tool.rsconnect] support. PR #978 is related context, not a blocker: keep Content Python selection independent of dependency-source selection.

    Current behavior:
    Framework commands default to requirements.txt. Pyproject commands use CLI -r, then the deployment table's requirements_file, then pyproject.toml. A nearby uv.lock is not automatically selected. Framework commands do not honor the deployment table's requirements_file.

    The issue's eight file-presence combinations were confirmed locally using the deployment source resolvers with the real environment inspector and the write-manifest api / write-manifest pyproject CLI commands. Seven existing requirements/freeze tests passed. Explicit CLI and pyproject configuration overrides were also exercised with real offline lock export. Verification did not upload to a live Connect server.

    An additional failure was reproduced: write-manifest api -r pyproject.toml with an existing requirements.txt leaves that file unchanged while recording the checksum for newly generated pyproject dependencies. The resulting manifest checksum does not match the file on disk.

    Current uv export --frozen --offline can export stale dependencies. In the tested dependency-removal case, --locked --offline rejected the stale lock without modifying it. Export with --locked --offline succeeded with a fresh lock. A stale lock can instead fail because required resolver metadata is absent from the uv cache; distinguish inability to validate offline from a confirmed stale lock.

    Desired behavior:

    • All Python dependency-consuming deploy and write-manifest commands use the same selection policy. Commands that do not inspect Python dependencies keep their existing behavior.
    • --force-generate explicitly snapshots the selected Content Python environment and bypasses configured and automatically discovered dependency sources. Reject combining it with -r / --requirements-file. Add --force-generate to both pyproject commands.
    • Otherwise, select CLI -r first, then the applicable deployment table's requirements_file, then automatically select uv.lock, pyproject.toml, or requirements.txt, in that order.
    • For automatic selection, pyproject dependencies qualify only when [project].dependencies is explicitly declared as a static list. An empty list is valid. A pyproject containing only tooling/build metadata, or only dynamically declared dependencies, does not qualify. If pyproject dependencies are selected, a malformed declared dependency list is an error, not a reason to silently fall back.
    • Honor requirements_file in framework commands without requiring the deployment table's app_mode or entrypoint; their content type and entrypoint continue to come from the existing command interface. Pyproject commands retain their full deployment-target configuration requirements.
    • Preserve feat: support [tool.posit.connect] pyproject settings #976's legacy table handling, deprecation warning, and both-tables conflict behavior when reading deployment configuration. A configured requirements source must be a non-empty string when it is used.
    • A selected source that is missing, unreadable, malformed, or unsupported fails with actionable guidance. Do not silently select a different source after selection fails. If automatic selection finds no eligible source, fail with guidance to provide one or use --force-generate.
    • Every selected uv.lock, including one selected by CLI or configuration, must be current with its companion pyproject.toml. Fail on a stale lock with guidance to refresh it. Never rewrite sources or refresh the lock as part of deployment or manifest generation.
    • Preserve offline lock export. If freshness validation cannot complete offline, fail with an explanation of that limitation and guidance to prepare the lock/environment before retrying; do not silently access the network or claim that the lock is confirmed stale.
    • Preserve user-managed dependency source files. Derived requirements from pyproject, lock export, or an environment snapshot use a separate generated artifact. Never overwrite a user-managed file to create that artifact. Its naming and placement are implementation choices.
    • Refresh generated outputs on subsequent manifest writes when the command permits overwriting the manifest. Generated artifacts must not become automatic source candidates or change source selection on a subsequent run.
    • The manifest's package-file reference and checksum must match the actual requirements file consumed by a deployment, including deployments from a saved manifest.
    • Keep existing Content Python discovery and version checks. In particular, a snapshot must describe the selected Content Python environment, not substitute packages from CLI Python.

    Key interfaces:

    • The shared requirements-source resolver: distinguish CLI selection, optional project configuration, automatic selection, and explicit environment snapshots.
    • The deployment configuration reader and PyprojectDeployTarget: share optional source configuration without making framework commands require full deployment-target metadata.
    • Environment.create_python_environment() and the environment inspector: preserve Content Python selection and validate the selected source, including lock freshness.
    • Manifest writers and bundle builders: preserve source files, refresh generated requirements artifacts, and keep package-file references and checksums consistent.

    Acceptance criteria:

    • Framework and pyproject command families apply the same source-selection policy whenever their other required inputs are valid; both deployment and manifest behavior are covered.
    • Cover all eight combinations of requirements.txt, pyproject.toml, and uv.lock, including pyproject automatic eligibility and missing companion metadata. Pyproject commands still require their deployment configuration.
    • CLI -r wins over project configuration and automatic selection; configured requirements_file wins over automatic selection for both command families.
    • Framework commands accept a deployment table containing only requirements_file; pyproject commands still validate their required app_mode and entrypoint.
    • A static empty dependency list qualifies; metadata-only and dynamic-only pyprojects are skipped automatically; malformed dependencies in a selected pyproject fail without fallback.
    • Missing/invalid selected sources and absence of an eligible automatic source produce actionable failures without implicit environment snapshots.
    • Both pyproject commands support --force-generate; it bypasses source configuration and discovery, uses Content Python, and conflicts with -r.
    • Fresh locks export successfully; stale locks fail for CLI, configuration, and automatic selection; offline validation failures are reported accurately. Lock export never modifies source files or silently accesses the network.
    • With user-managed requirements present beside pyproject or lock sources, generated output is separate and source bytes remain unchanged. A conflicting user-managed output file is preserved.
    • Repeated manifest generation refreshes generated dependencies without changing source selection. On-disk requirements checksums match the manifest, and a bundle made from that saved manifest contains the expected dependencies.
    • A Content Python environment distinct from CLI Python remains the source of --force-generate snapshots.
    • Documentation and release notes explain the new default order, explicit overrides, static-pyproject eligibility, lock validation, and generated artifacts.
    • Relevant focused tests and the repository's required lint/test checks pass.

    Out of scope:

    • Adding support for new lockfile formats, legacy Poetry dependency tables, or dynamic dependency evaluation.
    • Adding server-based Python discovery to dependency selection, changing quickstart's interpreter policy, or changing CLI Python support.
    • Changing commands that deploy an existing manifest to rediscover dependency sources.
    • Changing R, Node.js, or Python-free static deployment dependency behavior.
    • Updating dependency declarations or resolving a new lock during deployment.
    • Removing legacy [tool.rsconnect] support or changing which configuration table names are accepted beyond feat: support [tool.posit.connect] pyproject settings #976.
  2. added
    ready-for-agentFully specified, ready for an autonomous coding agent
    and removed
    needs-triageMaintainer needs to evaluate this issue
    on Nov 12, 2026
  3. self-assigned this
    on Nov 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or requestready-for-agentFully specified, ready for an autonomous coding agent

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions