diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2cf10d30..0b25bc7c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -4,19 +4,14 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true -env: - UV_SYSTEM_PYTHON: true jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: 3.x - check-latest: true - cache: 'pip' - - run: make deps + - uses: astral-sh/setup-uv@v3 + - run: uv python install + - run: make dev - run: make lint - run: make fmt @@ -26,21 +21,18 @@ jobs: fail-fast: false matrix: python-version: - - '3.8' - - '3.9' - - '3.10' - - '3.11' - - '3.12' + - "3.8" + - "3.9" + - "3.10" + - "3.11" + - "3.12" + - "3.13" steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - check-latest: true - cache: 'pip' - - run: make deps - - run: make dev - - run: make test + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v3 + - run: uv python install ${{ matrix.python-version }} + - run: make dev + - run: make test integration-test: runs-on: ubuntu-latest @@ -68,18 +60,20 @@ jobs: - 2022.12.0 - 2022.11.0 steps: - - uses: actions/checkout@v4 - - uses: docker/setup-buildx-action@v3 - - name: Write Posit Connect license to disk - run: echo "$CONNECT_LICENSE" > ./integration/license.lic - env: - CONNECT_LICENSE: ${{ secrets.CONNECT_LICENSE }} - - run: make -C ./integration ${{ matrix.CONNECT_VERSION }} - - uses: actions/upload-artifact@v4 - if: always() - with: - name: ${{ matrix.CONNECT_VERSION }} - Integration Test Report - path: integration/reports/*.xml + - uses: actions/checkout@v4 + - uses: docker/setup-buildx-action@v3 + - name: Write Posit Connect license to disk + run: echo "$CONNECT_LICENSE" > ./integration/license.lic + env: + CONNECT_LICENSE: ${{ secrets.CONNECT_LICENSE }} + - uses: astral-sh/setup-uv@v3 + - run: uv python install + - run: make -C ./integration ${{ matrix.CONNECT_VERSION }} + - uses: actions/upload-artifact@v4 + if: always() + with: + name: ${{ matrix.CONNECT_VERSION }} - Integration Test Report + path: integration/reports/*.xml integration-test-report: needs: integration-test @@ -99,15 +93,11 @@ jobs: files: "artifacts/**/*.xml" report_individual_runs: true - build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: 3.x - check-latest: true - cache: 'pip' - - run: make deps + - uses: astral-sh/setup-uv@v3 + - run: uv python install + - run: make dev - run: make build diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index 611494cd..bdd492f2 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -4,24 +4,18 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true -env: - UV_SYSTEM_PYTHON: true jobs: cov: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: 3.x - check-latest: true - cache: 'pip' - - run: make deps + - uses: astral-sh/setup-uv@v3 + - run: uv python install - run: make dev - run: make test - run: make cov-xml - if: ${{ ! github.event.pull_request.head.repo.fork }} uses: orgoro/coverage@v3.2 with: - coverageFile: coverage.xml - token: ${{ secrets.GITHUB_TOKEN }} + coverageFile: coverage.xml + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 38bd98d8..f641a1af 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -3,25 +3,19 @@ on: push: tags: - "v*.*.*" -env: - UV_SYSTEM_PYTHON: true jobs: default: runs-on: ubuntu-latest permissions: id-token: write steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - uses: actions/setup-python@v5 - with: - python-version: 3.x - check-latest: true - cache: 'pip' - - uses: actions/setup-node@v4 - - run: make deps - - run: make build - - run: make install - - id: release - uses: pypa/gh-action-pypi-publish@release/v1 + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: astral-sh/setup-uv@v3 + - run: uv python install + - uses: actions/setup-node@v4 + - run: make build + - run: make install + - id: release + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/site.yaml b/.github/workflows/site.yaml index 6e3c96af..2b6063bc 100644 --- a/.github/workflows/site.yaml +++ b/.github/workflows/site.yaml @@ -6,9 +6,6 @@ on: - "v*.*.*" pull_request: -env: - UV_SYSTEM_PYTHON: true - permissions: id-token: write pages: write @@ -25,14 +22,9 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-python@v5 - with: - python-version: 3.x - check-latest: true - cache: 'pip' - - run: make deps - - run: make build - - run: make install + - uses: astral-sh/setup-uv@v3 + - run: uv python install + - run: make build install - uses: quarto-dev/quarto-actions/setup@v2 - run: make docs - uses: actions/configure-pages@v3 @@ -48,14 +40,10 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-python@v5 - with: - python-version: 3.x - check-latest: true - cache: 'pip' + - uses: astral-sh/setup-uv@v3 + - run: uv python install - uses: actions/setup-node@v4 - uses: quarto-dev/quarto-actions/setup@v2 - - run: make deps - run: make dev - run: make docs - id: preview diff --git a/.gitignore b/.gitignore index d2f46c2a..c5b32315 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ share/python-wheels/ .installed.cfg *.egg MANIFEST +uv.lock # PyInstaller # Usually these files are written by a python script from a template @@ -166,3 +167,5 @@ cython_debug/ # Ruff .ruff_cache/ + +/.luarc.json diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5c290734..6ab2696d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ The `posit-sdk` is a software development kit (SDK) for working with Posit's pro Before contributing to the `posit-sdk`, ensure that the following prerequisites are met: -- Python >=3.8 +- Python >=3.9 > [!INFO] > We require using virtual environments to maintain a clean and consistent development environment. @@ -17,7 +17,7 @@ Before contributing to the `posit-sdk`, ensure that the following prerequisites ## Instructions > [!WARNING] -> Executing `make` will install third-party packages in your Python environment. Please review the [`Makefile`](./Makefile) to verify behavior before executing any commands. +> Executing `make` will install third-party packages in your `.venv` virtual Python environment. Please review the [`Makefile`](./Makefile) to verify behavior before executing any commands. 1. Fork the repository and open it in your development environment. 2. Activate your Python environment (e.g., `source .venv/bin/activate`) @@ -33,7 +33,7 @@ Use the default make target to execute the full build pipeline. For details on s ## Style Guide -We use [Ruff](https://docs.astral.sh/ruff/) for linting and code formatting. Run `make deps` to install it. +We use [Ruff](https://docs.astral.sh/ruff/) for linting and code formatting. All proposed changes must successfully pass the `make lint` rules prior to merging. diff --git a/Dockerfile b/Dockerfile index a2014369..84c92634 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,13 @@ FROM python:3 -ENV UV_SYSTEM_PYTHON=true - RUN apt-get update && apt-get install -y make WORKDIR /sdk -COPY Makefile pyproject.toml requirements.txt requirements-dev.txt vars.mk ./ +COPY Makefile pyproject.toml vars.mk uv.lock ./ -RUN --mount=type=cache,mode=0755,target=/root/.cache/pip make deps +# Run before `COPY src src` to cache dependencies for faster iterative builds +RUN --mount=type=cache,mode=0755,target=/root/.cache/pip make docker-deps COPY .git .git COPY src src diff --git a/Makefile b/Makefile index 00922626..7a1e46ee 100644 --- a/Makefile +++ b/Makefile @@ -2,11 +2,11 @@ include vars.mk .DEFAULT_GOAL := all -.PHONY: build clean cov default deps dev docs ensure-uv fmt fix install it lint test uninstall version help +.PHONY: build clean cov default dev docker-deps docs ensure-uv fmt fix install it lint test uninstall version help -all: deps dev test lint build +all: dev test lint build -build: +build: dev $(UV) build clean: @@ -19,62 +19,73 @@ clean: find . -name "__pycache__" -exec rm -rf {} + find . -type d -empty -delete -cov: - $(PYTHON) -m coverage report +cov: dev + $(UV) run coverage report -cov-html: - $(PYTHON) -m coverage html +cov-html: dev + $(UV) run coverage html open htmlcov/index.html -cov-xml: - $(PYTHON) -m coverage xml - -deps: ensure-uv - $(UV) pip install --upgrade pip setuptools wheel -r requirements.txt -r requirements-dev.txt +cov-xml: dev + $(UV) run coverage xml dev: ensure-uv $(UV) pip install -e . -docs: +docker-deps: ensure-uv + # Sync given the `uv.lock` file + # --frozen : assert that the lock file exists + # --no-install-project : do not install the project itself, but install its dependencies + $(UV) sync --frozen --no-install-project + +docs: ensure-uv $(MAKE) -C ./docs +$(VIRTUAL_ENV): + $(UV) venv $(VIRTUAL_ENV) ensure-uv: @if ! command -v $(UV) >/dev/null; then \ - $(PYTHON) -m ensurepip && $(PYTHON) -m pip install uv; \ + $(PYTHON) -m ensurepip && $(PYTHON) -m pip install "uv >= 0.4.27"; \ fi + @# Install virtual environment (before calling `uv pip install ...`) + @$(MAKE) $(VIRTUAL_ENV) 1>/dev/null + @# Be sure recent uv is installed + @$(UV) pip install "uv >= 0.4.27" --quiet -fmt: - $(PYTHON) -m ruff check --fix - $(PYTHON) -m ruff format +fmt: dev + $(UV) run ruff check --fix + $(UV) run ruff format -install: ensure-uv +install: build $(UV) pip install dist/*.whl -it: +$(UV_LOCK): dev + $(UV) lock +it: $(UV_LOCK) $(MAKE) -C ./integration -lint: - $(PYTHON) -m pyright - $(PYTHON) -m ruff check +lint: dev + $(UV) run pyright + $(UV) run ruff check -test: - $(PYTHON) -m coverage run --source=src -m pytest tests +test: dev + $(UV) run coverage run --source=src -m pytest tests uninstall: ensure-uv $(UV) pip uninstall $(PROJECT_NAME) version: - @$(PYTHON) -m setuptools_scm + @$(MAKE) ensure-uv &>/dev/null + @$(UV) run --quiet --with "setuptools_scm" python -m setuptools_scm help: @echo "Makefile Targets" - @echo " all Run deps, dev, test, lint, and build" + @echo " all Run dev, test, lint, and build" @echo " build Build the project" @echo " clean Clean up project artifacts" @echo " cov Generate a coverage report" @echo " cov-html Generate an HTML coverage report and open it" @echo " cov-xml Generate an XML coverage report" - @echo " deps Install dependencies" @echo " dev Install the project in editable mode" @echo " docs Build the documentation" @echo " ensure-uv Ensure 'uv' is installed" diff --git a/docs/Makefile b/docs/Makefile index ae4deb92..51a41792 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -6,7 +6,8 @@ CURRENT_YEAR ?= $(shell date +%Y) # Quarto settings QUARTO ?= quarto -QUARTODOC ?= quartodoc +# quartodoc doesn't like py3.8; Run using `--with` as it can conflict with the project's dependencies +QUARTODOC ?= --with "quartodoc==0.8.1" quartodoc # Netlify settings NETLIFY_SITE_ID ?= 5cea1f56-7935-4387-975a-18a7905d15ee @@ -21,28 +22,38 @@ endif all: deps api build -api: - $(QUARTODOC) build - $(QUARTODOC) interlinks +ensure-dev: + $(MAKE) -C .. dev + +api: ensure-dev + @echo "::group::quartodoc interlinks" + $(UV) tool run --with ../ $(QUARTODOC) interlinks + @echo "::endgroup::" + @echo "::group::quartodoc build" + $(UV) tool run --with ../ $(QUARTODOC) build --verbose + @echo "::endgroup::" cp -r _extensions/ reference/_extensions # Required to render footer -build: +build: ensure-dev CURRENT_YEAR=$(CURRENT_YEAR) \ PROJECT_VERSION=$(PROJECT_VERSION) \ + $(UV) tool run --with ../ \ $(QUARTO) render clean: rm -rf _extensions _inv _site .quarto reference objects.json find . -type d -empty -delete -deps: - $(UV) pip install --upgrade pip -r requirements-site.txt +_extensions/posit-dev/posit-docs/_extension.yml: $(QUARTO) add --no-prompt posit-dev/product-doc-theme@v4.0.2 +_extensions/machow/interlinks/_extension.yml: $(QUARTO) add --no-prompt machow/quartodoc +deps: ensure-dev _extensions/posit-dev/posit-docs/_extension.yml _extensions/machow/interlinks/_extension.yml -preview: +preview: ensure-dev CURRENT_YEAR=$(CURRENT_YEAR) \ PROJECT_VERSION=$(PROJECT_VERSION) \ + $(UV) tool run --with ../ \ $(QUARTO) preview deploy: diff --git a/docs/_quarto.yml b/docs/_quarto.yml index 4cacdfce..f77c78ba 100644 --- a/docs/_quarto.yml +++ b/docs/_quarto.yml @@ -82,7 +82,6 @@ quartodoc: include_empty: true sections: - title: Clients - package: posit desc: > The `Client` is the entrypoint for each Posit product. Initialize a `Client` to get started. contents: @@ -96,7 +95,6 @@ quartodoc: - patch - delete - title: Posit Connect Resources - package: posit contents: - connect.bundles - connect.content @@ -106,7 +104,6 @@ quartodoc: - connect.users - connect.vanities - title: Posit Connect Metrics - package: posit contents: - connect.metrics - connect.metrics.usage diff --git a/docs/requirements-site.txt b/docs/requirements-site.txt deleted file mode 100644 index b3aa648b..00000000 --- a/docs/requirements-site.txt +++ /dev/null @@ -1 +0,0 @@ -quartodoc diff --git a/integration/Makefile b/integration/Makefile index 36a496bf..edc0ac50 100644 --- a/integration/Makefile +++ b/integration/Makefile @@ -71,6 +71,7 @@ preview: # Build Dockerfile build: + make -C .. $(UV_LOCK) docker build -t $(DOCKER_PROJECT_IMAGE_TAG) .. # Tear down resources. @@ -134,4 +135,7 @@ help: test: mkdir -p logs set -o pipefail; \ - CONNECT_VERSION=${CONNECT_VERSION} CONNECT_API_KEY="$(shell rsconnect bootstrap -i -s http://connect:3939 --raw)" $(PYTHON) -m pytest -s --junit-xml=./reports/$(CONNECT_VERSION).xml | tee ./logs/$(CONNECT_VERSION).log; + CONNECT_VERSION=${CONNECT_VERSION} \ + CONNECT_API_KEY="$(shell $(UV) run rsconnect bootstrap -i -s http://connect:3939 --raw)" \ + $(UV) run pytest -s --junit-xml=./reports/$(CONNECT_VERSION).xml | \ + tee ./logs/$(CONNECT_VERSION).log; diff --git a/pyproject.toml b/pyproject.toml index 42d418af..70a06c2a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,11 +19,7 @@ classifiers = [ "Typing :: Typed", ] dynamic = ["version"] -dependencies = [ - "requests>=2.31.0,<3", - "packaging", - "typing-extensions" -] +dependencies = ["requests>=2.31.0,<3", "packaging", "typing-extensions"] [project.urls] Source = "https://github.com/posit-dev/posit-sdk-py" @@ -72,7 +68,7 @@ select = [ # https://docs.astral.sh/ruff/rules/#isort-i # # Sort imports. - "I" + "I", ] ignore = [ # NumPy style docstring convention with noted exceptions. @@ -98,3 +94,24 @@ ignore = [ [tool.ruff.lint.pydocstyle] convention = "numpy" + + +[dependency-groups] +build = ["build"] +coverage = ["coverage"] +examples = ["rsconnect-python", "pandas"] +git = ["pre-commit"] +lint = ["ruff", "pyright"] +test = ["rsconnect-python", "responses", "pytest", "pyjson5"] +# Default install group by `uv`: `dev` +dev = [ + { include-group = "build" }, + { include-group = "coverage" }, + # # Must comment out `examples` group until only python>3.8 is supported + # # `numpy==1.24.4` requires `distutils`, which is not available in python3.12 + # # `numpy>1.24.4` does not require `distutils` + # { include-group = "examples" }, + { include-group = "git" }, + { include-group = "lint" }, + { include-group = "test" }, +] diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index 52466717..00000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,13 +0,0 @@ -build -coverage -pandas -pre-commit -pyjson5 -pyright -pytest -responses -rsconnect-python -ruff -setuptools -setuptools-scm -uv diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 4dc65af9..00000000 --- a/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -requests==2.32.2 -packaging==24.1 -typing-extensions==4.12.2 diff --git a/src/posit/connect/paginator.py b/src/posit/connect/paginator.py index a85c5e64..2308e42c 100644 --- a/src/posit/connect/paginator.py +++ b/src/posit/connect/paginator.py @@ -38,7 +38,7 @@ class Paginator: url (str): The URL of the paginated API endpoint. """ - def __init__(self, session: requests.Session, url: str, params = {}) -> None: + def __init__(self, session: requests.Session, url: str, params={}) -> None: self.session = session self.url = url self.params = params diff --git a/vars.mk b/vars.mk index 36cec07c..9077852f 100644 --- a/vars.mk +++ b/vars.mk @@ -18,3 +18,7 @@ PROJECT_NAME := posit-sdk # Python settings PYTHON ?= $(shell command -v python || command -v python3) UV ?= uv +# uv defaults virtual environment to `$VIRTUAL_ENV` if set; otherwise .venv +VIRTUAL_ENV ?= .venv + +UV_LOCK := uv.lock