posit-cli 0.1.1__tar.gz → 0.2.0__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.
- posit_cli-0.2.0/.github/dependabot.yml +14 -0
- {posit_cli-0.1.1 → posit_cli-0.2.0}/.github/workflows/ci.yaml +3 -3
- {posit_cli-0.1.1 → posit_cli-0.2.0}/.github/workflows/release.yaml +3 -3
- posit_cli-0.2.0/CONTRIBUTING.md +91 -0
- {posit_cli-0.1.1 → posit_cli-0.2.0}/Justfile +14 -2
- posit_cli-0.1.1/README.md → posit_cli-0.2.0/PKG-INFO +53 -0
- posit_cli-0.1.1/PKG-INFO → posit_cli-0.2.0/README.md +33 -18
- {posit_cli-0.1.1 → posit_cli-0.2.0}/pyproject.toml +2 -1
- {posit_cli-0.1.1 → posit_cli-0.2.0}/skills/posit-cli/SKILL.md +6 -0
- {posit_cli-0.1.1 → posit_cli-0.2.0}/src/posit_cli/_version.py +2 -2
- {posit_cli-0.1.1 → posit_cli-0.2.0}/src/posit_cli/connect/__init__.py +3 -0
- posit_cli-0.2.0/src/posit_cli/connect/version.py +68 -0
- {posit_cli-0.1.1 → posit_cli-0.2.0}/tests/test_rsconnect_contract.py +1 -0
- posit_cli-0.2.0/tests/test_version.py +97 -0
- posit_cli-0.2.0/uv.lock +3186 -0
- posit_cli-0.1.1/uv.lock +0 -1142
- {posit_cli-0.1.1 → posit_cli-0.2.0}/.gitignore +0 -0
- {posit_cli-0.1.1 → posit_cli-0.2.0}/CLAUDE.md +0 -0
- {posit_cli-0.1.1 → posit_cli-0.2.0}/LICENSE +0 -0
- {posit_cli-0.1.1 → posit_cli-0.2.0}/RELEASE.md +0 -0
- {posit_cli-0.1.1 → posit_cli-0.2.0}/src/posit_cli/__init__.py +0 -0
- {posit_cli-0.1.1 → posit_cli-0.2.0}/src/posit_cli/__main__.py +0 -0
- {posit_cli-0.1.1 → posit_cli-0.2.0}/src/posit_cli/connect/api.py +0 -0
- {posit_cli-0.1.1 → posit_cli-0.2.0}/tests/test_api.py +0 -0
- {posit_cli-0.1.1 → posit_cli-0.2.0}/tests/test_cli.py +0 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: uv
|
|
4
|
+
directory: /
|
|
5
|
+
schedule:
|
|
6
|
+
interval: weekly
|
|
7
|
+
commit-message:
|
|
8
|
+
prefix: chore(deps)
|
|
9
|
+
- package-ecosystem: github-actions
|
|
10
|
+
directory: /
|
|
11
|
+
schedule:
|
|
12
|
+
interval: weekly
|
|
13
|
+
commit-message:
|
|
14
|
+
prefix: chore(deps)
|
|
@@ -12,7 +12,7 @@ jobs:
|
|
|
12
12
|
runs-on: ubuntu-latest
|
|
13
13
|
steps:
|
|
14
14
|
- uses: actions/checkout@v7
|
|
15
|
-
- uses: astral-sh/setup-uv@
|
|
15
|
+
- uses: astral-sh/setup-uv@v10.3.3
|
|
16
16
|
- uses: extractions/setup-just@v4
|
|
17
17
|
# Fail fast if uv.lock has drifted from pyproject.toml.
|
|
18
18
|
- run: uv lock --locked
|
|
@@ -33,7 +33,7 @@ jobs:
|
|
|
33
33
|
- "3.14"
|
|
34
34
|
steps:
|
|
35
35
|
- uses: actions/checkout@v7
|
|
36
|
-
- uses: astral-sh/setup-uv@
|
|
36
|
+
- uses: astral-sh/setup-uv@v10.3.3
|
|
37
37
|
with:
|
|
38
38
|
python-version: ${{ matrix.python-version }}
|
|
39
39
|
- uses: extractions/setup-just@v4
|
|
@@ -46,7 +46,7 @@ jobs:
|
|
|
46
46
|
# hatch-vcs reads the version from the tags, so fetch them.
|
|
47
47
|
with:
|
|
48
48
|
fetch-depth: 0
|
|
49
|
-
- uses: astral-sh/setup-uv@
|
|
49
|
+
- uses: astral-sh/setup-uv@v10.3.3
|
|
50
50
|
- uses: extractions/setup-just@v4
|
|
51
51
|
- run: just build
|
|
52
52
|
- run: just smoke
|
|
@@ -13,7 +13,7 @@ jobs:
|
|
|
13
13
|
- uses: actions/checkout@v7
|
|
14
14
|
with:
|
|
15
15
|
fetch-depth: 0
|
|
16
|
-
- uses: astral-sh/setup-uv@
|
|
16
|
+
- uses: astral-sh/setup-uv@v10.3.3
|
|
17
17
|
- uses: extractions/setup-just@v4
|
|
18
18
|
# The tag is the only source of the version, so this only guards against a
|
|
19
19
|
# build that did not pick the tag up (shallow clone, dirty tree, two tags on
|
|
@@ -37,7 +37,7 @@ jobs:
|
|
|
37
37
|
fi
|
|
38
38
|
- run: just build
|
|
39
39
|
- run: just smoke
|
|
40
|
-
- uses: actions/upload-artifact@
|
|
40
|
+
- uses: actions/upload-artifact@v7
|
|
41
41
|
with:
|
|
42
42
|
name: dist
|
|
43
43
|
path: dist/
|
|
@@ -51,7 +51,7 @@ jobs:
|
|
|
51
51
|
contents: write
|
|
52
52
|
steps:
|
|
53
53
|
- uses: actions/checkout@v7
|
|
54
|
-
- uses: actions/download-artifact@
|
|
54
|
+
- uses: actions/download-artifact@v8
|
|
55
55
|
with:
|
|
56
56
|
name: dist
|
|
57
57
|
path: dist/
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
This guide covers local development for `posit-cli`. End-user installation and CLI usage are documented in [README.md](README.md).
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
Install these tools before starting:
|
|
8
|
+
|
|
9
|
+
- Git
|
|
10
|
+
- Python 3.8 or newer
|
|
11
|
+
- [uv](https://docs.astral.sh/uv/)
|
|
12
|
+
- [just](https://github.com/casey/just)
|
|
13
|
+
|
|
14
|
+
The project uses uv for dependency management and virtual environments. The supported Python versions are 3.8 through 3.14, which are tested in CI.
|
|
15
|
+
|
|
16
|
+
## Set Up
|
|
17
|
+
|
|
18
|
+
Clone the repository and change into its directory:
|
|
19
|
+
|
|
20
|
+
```console
|
|
21
|
+
git clone https://github.com/posit-dev/posit-cli.git
|
|
22
|
+
cd posit-cli
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Sync the project, test, and lint dependencies:
|
|
26
|
+
|
|
27
|
+
```console
|
|
28
|
+
just deps
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
This runs `uv sync --all-extras`, using `uv.lock` to create or update the project environment in `.venv`. You do not need to activate `.venv` when using the `just` recipes.
|
|
32
|
+
|
|
33
|
+
To put an editable `posit` command on your `PATH`, run:
|
|
34
|
+
|
|
35
|
+
```console
|
|
36
|
+
just dev
|
|
37
|
+
posit --help
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`just dev` installs the checkout as an editable uv tool. Changes to the source are therefore available through `posit` without rebuilding or reinstalling the package. If the command is not found, add the directory printed by `uv tool dir --bin` to your `PATH`, or run `uv tool update-shell`.
|
|
41
|
+
|
|
42
|
+
## Common Commands
|
|
43
|
+
|
|
44
|
+
Run these commands from the repository root:
|
|
45
|
+
|
|
46
|
+
| Command | Purpose |
|
|
47
|
+
| --- | --- |
|
|
48
|
+
| `just deps` | Sync runtime, test, and lint dependencies from `uv.lock`. |
|
|
49
|
+
| `just dev` | Install the checkout as an editable uv tool. |
|
|
50
|
+
| `just test` | Run tests against Python 3.13. |
|
|
51
|
+
| `just test 3.8` | Run tests against a specific Python version. |
|
|
52
|
+
| `just lint` | Check formatting and run Ruff. |
|
|
53
|
+
| `just fmt` | Format files and apply Ruff fixes. |
|
|
54
|
+
| `just build` | Build the wheel and source distribution in `dist/`. |
|
|
55
|
+
| `just smoke` | Run `posit --help` from the latest built wheel. |
|
|
56
|
+
| `just install` | Install the latest built wheel as a non-editable uv tool. |
|
|
57
|
+
| `just uninstall` | Remove the uv-managed `posit-cli` tool. |
|
|
58
|
+
| `just clean` | Remove build, test, and lint artifacts. |
|
|
59
|
+
|
|
60
|
+
`just smoke` expects a wheel to exist in `dist/`, so run `just build` first unless another command has already built one.
|
|
61
|
+
|
|
62
|
+
## Dependency Changes
|
|
63
|
+
|
|
64
|
+
Runtime dependencies and optional test/lint dependencies are declared in `pyproject.toml`. When changing them:
|
|
65
|
+
|
|
66
|
+
1. Update `pyproject.toml`.
|
|
67
|
+
2. Run `uv lock` to update `uv.lock`.
|
|
68
|
+
3. Run `just deps` to sync the local environment.
|
|
69
|
+
4. Include both `pyproject.toml` and `uv.lock` in the change.
|
|
70
|
+
|
|
71
|
+
CI checks that the lockfile is current with `uv lock --locked`.
|
|
72
|
+
|
|
73
|
+
## Before Opening A Pull Request
|
|
74
|
+
|
|
75
|
+
Run the checks relevant to your change. The normal local validation sequence is:
|
|
76
|
+
|
|
77
|
+
```console
|
|
78
|
+
just deps
|
|
79
|
+
just lint
|
|
80
|
+
just test
|
|
81
|
+
just build
|
|
82
|
+
just smoke
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
CI also runs the test suite against Python 3.8, 3.9, 3.10, 3.11, 3.12, 3.13, and 3.14. Add or update tests when changing behavior, and update user-facing documentation in `README.md` when the CLI changes.
|
|
86
|
+
|
|
87
|
+
Keep commits focused and use Conventional Commit messages, such as `fix: handle expired Connect tokens` or `docs: clarify local setup`.
|
|
88
|
+
|
|
89
|
+
## Releases
|
|
90
|
+
|
|
91
|
+
Release preparation and publishing are documented in [RELEASE.md](RELEASE.md). In particular, do not add a hard-coded version to `pyproject.toml`; release versions come from Git tags through `hatch-vcs`.
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# posit-cli task runner. Run `just --list` to see recipes.
|
|
2
2
|
|
|
3
|
+
# Sync project and development dependencies into the project environment
|
|
4
|
+
deps:
|
|
5
|
+
uv sync --all-extras
|
|
6
|
+
|
|
3
7
|
# Run the test suite against a single Python version (default 3.13)
|
|
4
8
|
test py="3.13":
|
|
5
9
|
uv run --python {{py}} --extra test pytest tests
|
|
@@ -25,9 +29,17 @@ smoke:
|
|
|
25
29
|
WHL=$(ls -t dist/*.whl | head -1)
|
|
26
30
|
uv run --no-project --with "$WHL" posit --help
|
|
27
31
|
|
|
28
|
-
# Install the most recently built wheel
|
|
32
|
+
# Install the most recently built wheel as a standalone uv tool
|
|
29
33
|
install: build
|
|
30
|
-
uv
|
|
34
|
+
uv tool install --force "$(ls -t dist/*.whl | head -1)"
|
|
35
|
+
|
|
36
|
+
# Install the project as an editable standalone uv tool
|
|
37
|
+
dev:
|
|
38
|
+
uv tool install --editable --force .
|
|
39
|
+
|
|
40
|
+
# Remove the installed standalone uv tool
|
|
41
|
+
uninstall:
|
|
42
|
+
uv tool uninstall posit-cli
|
|
31
43
|
|
|
32
44
|
# Print the version that a build gets from the current git state
|
|
33
45
|
version:
|
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: posit-cli
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A single, friendly command-line interface for Posit Connect, in the spirit of gh.
|
|
5
|
+
Author: Posit Software, PBC
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.8
|
|
9
|
+
Requires-Dist: click>=8.0
|
|
10
|
+
Requires-Dist: jq>=1.4
|
|
11
|
+
Requires-Dist: keyring>=23.0
|
|
12
|
+
Requires-Dist: rsconnect-python<2,>=1.33
|
|
13
|
+
Provides-Extra: lint
|
|
14
|
+
Requires-Dist: ruff>=0.6; extra == 'lint'
|
|
15
|
+
Provides-Extra: snowflake
|
|
16
|
+
Requires-Dist: rsconnect-python[snowflake]; extra == 'snowflake'
|
|
17
|
+
Provides-Extra: test
|
|
18
|
+
Requires-Dist: pytest>=7; extra == 'test'
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
1
21
|
# posit-cli
|
|
2
22
|
|
|
3
23
|
A friendly command-line interface for Posit products, in the spirit of [`gh`](https://cli.github.com/).
|
|
@@ -37,6 +57,22 @@ Each of these puts the `posit` executable on your `PATH`. To upgrade later, run
|
|
|
37
57
|
[Git authentication docs](https://docs.astral.sh/uv/concepts/authentication/git/)
|
|
38
58
|
for tokens and other hosts.
|
|
39
59
|
|
|
60
|
+
### Connect on Snowflake
|
|
61
|
+
|
|
62
|
+
To deploy to Posit Connect on Snowflake, install the Snowflake extra instead:
|
|
63
|
+
|
|
64
|
+
```console
|
|
65
|
+
uv tool install 'posit-cli[snowflake]'
|
|
66
|
+
# or: pip install 'posit-cli[snowflake]'
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
This includes the Snowflake CLI. Before deploying, configure a
|
|
70
|
+
[Snowflake CLI connection](https://docs.snowflake.com/en/developer-guide/snowflake-cli/connecting/configure-connections)
|
|
71
|
+
with access to your Connect instance and `authenticator` set to `snowflake_jwt`
|
|
72
|
+
(key-pair authentication) or `oauth`. Select it with
|
|
73
|
+
`--snowflake-connection-name` when adding the Connect server with
|
|
74
|
+
`posit connect add`; see `posit connect add --help` for options.
|
|
75
|
+
|
|
40
76
|
## Getting started
|
|
41
77
|
|
|
42
78
|
**1. Log in.** Point `posit` at your Connect server and authenticate. This runs
|
|
@@ -114,10 +150,27 @@ $ posit connect api v1/users -X GET -f page_size=5 # or force GET
|
|
|
114
150
|
$ posit connect api v1/content -f name=my-app # POST body (creates content)
|
|
115
151
|
```
|
|
116
152
|
|
|
153
|
+
## `posit connect version`
|
|
154
|
+
|
|
155
|
+
Print the Connect server version using your saved credentials:
|
|
156
|
+
|
|
157
|
+
```console
|
|
158
|
+
$ posit connect version
|
|
159
|
+
$ posit connect version --name production
|
|
160
|
+
$ posit connect version --server https://connect.example.com --api-key "$CONNECT_API_KEY"
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
The command supports `-n`/`--name`, `-s`/`--server`, `-k`/`--api-key`,
|
|
164
|
+
`--no-tls-verify`, and `-c`/`--cacert`, with the same connection environment
|
|
165
|
+
variables as `posit connect api`. It prints an empty line if the server hides
|
|
166
|
+
its version. Rsconnect's `CONNECT_SERVER_VERSION` environment variable overrides
|
|
167
|
+
the reported version.
|
|
168
|
+
|
|
117
169
|
## `posit connect *`
|
|
118
170
|
|
|
119
171
|
`posit` wraps [`rsconnect-python`](https://github.com/posit-dev/rsconnect-python):
|
|
120
172
|
it re-exposes the full `rsconnect` command set under `posit connect`, in addition to the `api` utility.
|
|
173
|
+
The `version` command is overridden to report the Connect server version.
|
|
121
174
|
|
|
122
175
|
## Using `posit` with Claude Code
|
|
123
176
|
|
|
@@ -1,21 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: posit-cli
|
|
3
|
-
Version: 0.1.1
|
|
4
|
-
Summary: A single, friendly command-line interface for Posit Connect, in the spirit of gh.
|
|
5
|
-
Author: Posit Software, PBC
|
|
6
|
-
License-Expression: MIT
|
|
7
|
-
License-File: LICENSE
|
|
8
|
-
Requires-Python: >=3.8
|
|
9
|
-
Requires-Dist: click>=8.0
|
|
10
|
-
Requires-Dist: jq>=1.4
|
|
11
|
-
Requires-Dist: keyring>=23.0
|
|
12
|
-
Requires-Dist: rsconnect-python<2,>=1.30
|
|
13
|
-
Provides-Extra: lint
|
|
14
|
-
Requires-Dist: ruff>=0.6; extra == 'lint'
|
|
15
|
-
Provides-Extra: test
|
|
16
|
-
Requires-Dist: pytest>=7; extra == 'test'
|
|
17
|
-
Description-Content-Type: text/markdown
|
|
18
|
-
|
|
19
1
|
# posit-cli
|
|
20
2
|
|
|
21
3
|
A friendly command-line interface for Posit products, in the spirit of [`gh`](https://cli.github.com/).
|
|
@@ -55,6 +37,22 @@ Each of these puts the `posit` executable on your `PATH`. To upgrade later, run
|
|
|
55
37
|
[Git authentication docs](https://docs.astral.sh/uv/concepts/authentication/git/)
|
|
56
38
|
for tokens and other hosts.
|
|
57
39
|
|
|
40
|
+
### Connect on Snowflake
|
|
41
|
+
|
|
42
|
+
To deploy to Posit Connect on Snowflake, install the Snowflake extra instead:
|
|
43
|
+
|
|
44
|
+
```console
|
|
45
|
+
uv tool install 'posit-cli[snowflake]'
|
|
46
|
+
# or: pip install 'posit-cli[snowflake]'
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
This includes the Snowflake CLI. Before deploying, configure a
|
|
50
|
+
[Snowflake CLI connection](https://docs.snowflake.com/en/developer-guide/snowflake-cli/connecting/configure-connections)
|
|
51
|
+
with access to your Connect instance and `authenticator` set to `snowflake_jwt`
|
|
52
|
+
(key-pair authentication) or `oauth`. Select it with
|
|
53
|
+
`--snowflake-connection-name` when adding the Connect server with
|
|
54
|
+
`posit connect add`; see `posit connect add --help` for options.
|
|
55
|
+
|
|
58
56
|
## Getting started
|
|
59
57
|
|
|
60
58
|
**1. Log in.** Point `posit` at your Connect server and authenticate. This runs
|
|
@@ -132,10 +130,27 @@ $ posit connect api v1/users -X GET -f page_size=5 # or force GET
|
|
|
132
130
|
$ posit connect api v1/content -f name=my-app # POST body (creates content)
|
|
133
131
|
```
|
|
134
132
|
|
|
133
|
+
## `posit connect version`
|
|
134
|
+
|
|
135
|
+
Print the Connect server version using your saved credentials:
|
|
136
|
+
|
|
137
|
+
```console
|
|
138
|
+
$ posit connect version
|
|
139
|
+
$ posit connect version --name production
|
|
140
|
+
$ posit connect version --server https://connect.example.com --api-key "$CONNECT_API_KEY"
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The command supports `-n`/`--name`, `-s`/`--server`, `-k`/`--api-key`,
|
|
144
|
+
`--no-tls-verify`, and `-c`/`--cacert`, with the same connection environment
|
|
145
|
+
variables as `posit connect api`. It prints an empty line if the server hides
|
|
146
|
+
its version. Rsconnect's `CONNECT_SERVER_VERSION` environment variable overrides
|
|
147
|
+
the reported version.
|
|
148
|
+
|
|
135
149
|
## `posit connect *`
|
|
136
150
|
|
|
137
151
|
`posit` wraps [`rsconnect-python`](https://github.com/posit-dev/rsconnect-python):
|
|
138
152
|
it re-exposes the full `rsconnect` command set under `posit connect`, in addition to the `api` utility.
|
|
153
|
+
The `version` command is overridden to report the Connect server version.
|
|
139
154
|
|
|
140
155
|
## Using `posit` with Claude Code
|
|
141
156
|
|
|
@@ -12,7 +12,7 @@ dependencies = [
|
|
|
12
12
|
# posit-cli reuses rsconnect-python's *internal* API (RSConnectExecutor,
|
|
13
13
|
# RSConnectClient). These have no stability contract, so re-verify the internals
|
|
14
14
|
# on bumps. See tests/test_rsconnect_contract.py.
|
|
15
|
-
"rsconnect-python>=1.
|
|
15
|
+
"rsconnect-python>=1.33,<2",
|
|
16
16
|
# rsconnect imports `keyring` optionally; we depend on it directly so OAuth
|
|
17
17
|
# tokens land in the OS keyring.
|
|
18
18
|
"keyring>=23.0",
|
|
@@ -25,6 +25,7 @@ dependencies = [
|
|
|
25
25
|
posit = "posit_cli.__main__:cli"
|
|
26
26
|
|
|
27
27
|
[project.optional-dependencies]
|
|
28
|
+
snowflake = ["rsconnect-python[snowflake]"]
|
|
28
29
|
test = ["pytest>=7"]
|
|
29
30
|
lint = ["ruff>=0.6"]
|
|
30
31
|
|
|
@@ -175,6 +175,10 @@ posit connect deploy quarto ./report
|
|
|
175
175
|
posit connect deploy manifest ./manifest.json # deploy a prepared bundle
|
|
176
176
|
```
|
|
177
177
|
|
|
178
|
+
To update existing Connect content instead of creating new content, pass its
|
|
179
|
+
GUID with `--guid <guid>`. Find the GUID with
|
|
180
|
+
`posit connect api v1/content -q '.[] | "\(.guid) \(.title)"'`.
|
|
181
|
+
|
|
178
182
|
Available deploy targets include `api`, `bokeh`, `bundle`, `dash`, `fastapi`,
|
|
179
183
|
`flask`, `gradio`, `html`, `manifest`, `nodejs`, `notebook`, `panel`, `pyproject`,
|
|
180
184
|
`quarto`, `shiny`, `streamlit`, `tensorflow`, `voila`. Use `quickstart` to
|
|
@@ -191,6 +195,8 @@ These are rsconnect commands — run `posit connect <command> --help` for detail
|
|
|
191
195
|
- `add` / `list` / `remove` / `details` / `info` — manage saved servers and
|
|
192
196
|
deployment metadata.
|
|
193
197
|
- `login` / `logout` — OAuth credential management.
|
|
198
|
+
- `token` — print the OAuth access token from `login` (for the Connect SDKs or
|
|
199
|
+
`curl`). `--refresh` gets a new one after a 401; exit status 4 means log in again.
|
|
194
200
|
- `bootstrap` — create an initial admin user on a fresh Connect instance.
|
|
195
201
|
|
|
196
202
|
## When something fails
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.
|
|
22
|
-
__version_tuple__ = version_tuple = (0,
|
|
21
|
+
__version__ = version = '0.2.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -9,6 +9,7 @@ import click
|
|
|
9
9
|
from rsconnect.main import cli as rsconnect_cli
|
|
10
10
|
|
|
11
11
|
from .api import api as api_cmd
|
|
12
|
+
from .version import version as version_cmd
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
_epilog = (
|
|
@@ -29,3 +30,5 @@ for _name, _cmd in rsconnect_cli.commands.items():
|
|
|
29
30
|
connect.add_command(_cmd, name=_name)
|
|
30
31
|
|
|
31
32
|
connect.add_command(api_cmd, name="api")
|
|
33
|
+
# The mounted command reports the rsconnect package version.
|
|
34
|
+
connect.add_command(version_cmd, name="version")
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Print the selected Connect server's version."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from rsconnect.api import RSConnectException, RSConnectExecutor
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.command(
|
|
10
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
11
|
+
short_help="Show the Connect server version.",
|
|
12
|
+
)
|
|
13
|
+
@click.option("--name", "-n", default=None, help="Nickname of a saved server.")
|
|
14
|
+
@click.option(
|
|
15
|
+
"--server",
|
|
16
|
+
"-s",
|
|
17
|
+
default=None,
|
|
18
|
+
envvar="CONNECT_SERVER",
|
|
19
|
+
help="Connect server URL [env: CONNECT_SERVER].",
|
|
20
|
+
)
|
|
21
|
+
@click.option(
|
|
22
|
+
"--api-key",
|
|
23
|
+
"-k",
|
|
24
|
+
default=None,
|
|
25
|
+
envvar="CONNECT_API_KEY",
|
|
26
|
+
help="Connect API key [env: CONNECT_API_KEY].",
|
|
27
|
+
)
|
|
28
|
+
@click.option(
|
|
29
|
+
"--no-tls-verify",
|
|
30
|
+
"insecure",
|
|
31
|
+
is_flag=True,
|
|
32
|
+
default=False,
|
|
33
|
+
envvar="CONNECT_INSECURE",
|
|
34
|
+
help="Skip TLS certificate verification (still uses TLS) [env: CONNECT_INSECURE].",
|
|
35
|
+
)
|
|
36
|
+
@click.option(
|
|
37
|
+
"--cacert",
|
|
38
|
+
"-c",
|
|
39
|
+
type=click.Path(exists=True, dir_okay=False),
|
|
40
|
+
default=None,
|
|
41
|
+
envvar="CONNECT_CA_CERTIFICATE",
|
|
42
|
+
help="Path to trusted TLS CA certificate.",
|
|
43
|
+
)
|
|
44
|
+
def version(
|
|
45
|
+
name: Optional[str],
|
|
46
|
+
server: Optional[str],
|
|
47
|
+
api_key: Optional[str],
|
|
48
|
+
insecure: bool,
|
|
49
|
+
cacert: Optional[str],
|
|
50
|
+
) -> None:
|
|
51
|
+
"""Print the Connect server version using saved credentials by default.
|
|
52
|
+
|
|
53
|
+
CONNECT_SERVER_VERSION overrides the reported version. If the server hides
|
|
54
|
+
its version, print an empty line.
|
|
55
|
+
"""
|
|
56
|
+
try:
|
|
57
|
+
executor = RSConnectExecutor(
|
|
58
|
+
ctx=None,
|
|
59
|
+
name=name,
|
|
60
|
+
url=server,
|
|
61
|
+
api_key=api_key,
|
|
62
|
+
insecure=insecure,
|
|
63
|
+
cacert=cacert,
|
|
64
|
+
)
|
|
65
|
+
server_version = executor.client.server_version()
|
|
66
|
+
except RSConnectException as exc:
|
|
67
|
+
raise click.ClickException(str(exc)) from exc
|
|
68
|
+
click.echo(server_version)
|
|
@@ -17,6 +17,7 @@ def test_executor_surface():
|
|
|
17
17
|
assert expected in params, f"RSConnectExecutor lost __init__ param {expected!r}"
|
|
18
18
|
|
|
19
19
|
assert hasattr(RSConnectClient, "request")
|
|
20
|
+
assert callable(getattr(RSConnectClient, "server_version", None))
|
|
20
21
|
req_params = inspect.signature(RSConnectClient.request).parameters
|
|
21
22
|
for expected in ("method", "path", "query_params", "body", "headers"):
|
|
22
23
|
assert expected in req_params, f"RSConnectClient.request lost {expected!r}"
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Tests for Connect server version output and connection selection."""
|
|
2
|
+
|
|
3
|
+
from unittest.mock import patch
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from click.testing import CliRunner
|
|
7
|
+
from rsconnect.api import RSConnectClient, RSConnectException, RSConnectServer
|
|
8
|
+
|
|
9
|
+
from posit_cli.__main__ import cli
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.mark.parametrize(
|
|
13
|
+
"settings, output", [({"version": "2026.10.0"}, "2026.10.0\n"), ({}, "\n")]
|
|
14
|
+
)
|
|
15
|
+
def test_server_version(settings, output, monkeypatch):
|
|
16
|
+
monkeypatch.delenv("CONNECT_SERVER_VERSION", raising=False)
|
|
17
|
+
client = RSConnectClient(RSConnectServer("https://connect.example.com", api_key="key"))
|
|
18
|
+
with patch("posit_cli.connect.version.RSConnectExecutor") as executor:
|
|
19
|
+
with patch.object(client, "get", return_value=settings) as get:
|
|
20
|
+
executor.return_value.client = client
|
|
21
|
+
result = CliRunner().invoke(cli, ["connect", "version"])
|
|
22
|
+
assert result.exit_code == 0, result.output
|
|
23
|
+
assert result.output == output
|
|
24
|
+
get.assert_called_once_with("server_settings")
|
|
25
|
+
executor.assert_called_once_with(
|
|
26
|
+
ctx=None, name=None, url=None, api_key=None, insecure=False, cacert=None
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_server_version_override(monkeypatch):
|
|
31
|
+
monkeypatch.setenv("CONNECT_SERVER_VERSION", "2026.09.0")
|
|
32
|
+
client = RSConnectClient(RSConnectServer("https://connect.example.com", api_key="key"))
|
|
33
|
+
with patch("posit_cli.connect.version.RSConnectExecutor") as executor:
|
|
34
|
+
with patch.object(client, "get") as get:
|
|
35
|
+
executor.return_value.client = client
|
|
36
|
+
result = CliRunner().invoke(cli, ["connect", "version"])
|
|
37
|
+
assert result.exit_code == 0, result.output
|
|
38
|
+
assert result.output == "2026.09.0\n"
|
|
39
|
+
get.assert_not_called()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.mark.parametrize("use_env", [False, True])
|
|
43
|
+
def test_connection_options(tmp_path, use_env):
|
|
44
|
+
cacert = tmp_path / "ca.pem"
|
|
45
|
+
cacert.write_text("certificate")
|
|
46
|
+
args = ["connect", "version", "--name", "production"]
|
|
47
|
+
env = {}
|
|
48
|
+
if use_env:
|
|
49
|
+
env = {
|
|
50
|
+
"CONNECT_SERVER": "https://connect.example.com",
|
|
51
|
+
"CONNECT_API_KEY": "key",
|
|
52
|
+
"CONNECT_INSECURE": "true",
|
|
53
|
+
"CONNECT_CA_CERTIFICATE": str(cacert),
|
|
54
|
+
}
|
|
55
|
+
else:
|
|
56
|
+
args += [
|
|
57
|
+
"-s",
|
|
58
|
+
"https://connect.example.com",
|
|
59
|
+
"-k",
|
|
60
|
+
"key",
|
|
61
|
+
"--no-tls-verify",
|
|
62
|
+
"-c",
|
|
63
|
+
str(cacert),
|
|
64
|
+
]
|
|
65
|
+
with patch("posit_cli.connect.version.RSConnectExecutor") as executor:
|
|
66
|
+
executor.return_value.client.server_version.return_value = "2026.10.0"
|
|
67
|
+
result = CliRunner().invoke(cli, args, env=env)
|
|
68
|
+
assert result.exit_code == 0, result.output
|
|
69
|
+
executor.assert_called_once_with(
|
|
70
|
+
ctx=None,
|
|
71
|
+
name="production",
|
|
72
|
+
url="https://connect.example.com",
|
|
73
|
+
api_key="key",
|
|
74
|
+
insecure=True,
|
|
75
|
+
cacert=str(cacert),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@pytest.mark.parametrize("stage", ["selection", "request"])
|
|
80
|
+
def test_errors(stage):
|
|
81
|
+
with patch("posit_cli.connect.version.RSConnectExecutor") as executor:
|
|
82
|
+
error = RSConnectException("unable to retrieve server version")
|
|
83
|
+
if stage == "selection":
|
|
84
|
+
executor.side_effect = error
|
|
85
|
+
else:
|
|
86
|
+
executor.return_value.client.server_version.side_effect = error
|
|
87
|
+
result = CliRunner().invoke(cli, ["connect", "version"])
|
|
88
|
+
assert result.exit_code == 1
|
|
89
|
+
assert result.output == "Error: unable to retrieve server version\n"
|
|
90
|
+
assert isinstance(result.exception, SystemExit)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_help():
|
|
94
|
+
result = CliRunner().invoke(cli, ["connect", "version", "-h"])
|
|
95
|
+
assert result.exit_code == 0
|
|
96
|
+
assert "Connect server version" in result.output
|
|
97
|
+
assert "--server" in result.output
|