From 28f3a57255dc7918f187121382ca9d4173bd526b Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Tue, 27 May 2025 14:51:15 -0400 Subject: [PATCH 01/14] `air format .` --- r-package/R/prompt.R | 31 ++++++++++++++++++++++--------- r-package/R/querychat.R | 21 +++++++++++++-------- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/r-package/R/prompt.R b/r-package/R/prompt.R index fc0933e35..75ac68b65 100644 --- a/r-package/R/prompt.R +++ b/r-package/R/prompt.R @@ -1,5 +1,5 @@ #' Create a system prompt for the chat model -#' +#' #' This function generates a system prompt for the chat model based on a data frame's #' schema and optional additional context and instructions. #' @@ -8,11 +8,17 @@ #' @param data_description Optional description of the data, in plain text or Markdown format. #' @param extra_instructions Optional additional instructions for the chat model, in plain text or Markdown format. #' @param categorical_threshold The maximum number of unique values for a text column to be considered categorical. -#' +#' #' @return A string containing the system prompt for the chat model. #' #' @export -querychat_system_prompt <- function(df, name, data_description = NULL, extra_instructions = NULL, categorical_threshold = 10) { +querychat_system_prompt <- function( + df, + name, + data_description = NULL, + extra_instructions = NULL, + categorical_threshold = 10 +) { schema <- df_to_schema(df, name, categorical_threshold) if (!is.null(data_description)) { @@ -27,14 +33,21 @@ querychat_system_prompt <- function(df, name, data_description = NULL, extra_ins prompt_content <- readLines(prompt_path, warn = FALSE) prompt_text <- paste(prompt_content, collapse = "\n") - whisker::whisker.render(prompt_text, list( - schema = schema, - data_description = data_description, - extra_instructions = extra_instructions - )) + whisker::whisker.render( + prompt_text, + list( + schema = schema, + data_description = data_description, + extra_instructions = extra_instructions + ) + ) } -df_to_schema <- function(df, name = deparse(substitute(df)), categorical_threshold) { +df_to_schema <- function( + df, + name = deparse(substitute(df)), + categorical_threshold +) { schema <- c(paste("Table:", name), "Columns:") column_info <- lapply(names(df), function(column) { diff --git a/r-package/R/querychat.R b/r-package/R/querychat.R index 5196b1e8d..891081dda 100644 --- a/r-package/R/querychat.R +++ b/r-package/R/querychat.R @@ -30,7 +30,7 @@ #' @returns An object that can be passed to `querychat_server()` as the #' `querychat_config` argument. By convention, this object should be named #' `querychat_config`. -#' +#' #' @export querychat_init <- function( df, @@ -39,7 +39,12 @@ querychat_init <- function( data_description = NULL, extra_instructions = NULL, create_chat_func = purrr::partial(ellmer::chat_openai, model = "gpt-4o"), - system_prompt = querychat_system_prompt(df, tbl_name, data_description = data_description, extra_instructions = extra_instructions) + system_prompt = querychat_system_prompt( + df, + tbl_name, + data_description = data_description, + extra_instructions = extra_instructions + ) ) { is_tbl_name_ok <- is.character(tbl_name) && length(tbl_name) == 1 && @@ -124,28 +129,28 @@ querychat_ui <- function(id) { ns <- shiny::NS(id) htmltools::tagList( # TODO: Make this into a proper HTML dependency - shiny::includeCSS(system.file("www","styles.css", package = "querychat")), + shiny::includeCSS(system.file("www", "styles.css", package = "querychat")), shinychat::chat_ui(ns("chat"), height = "100%", fill = TRUE) ) } #' Initalize the querychat server -#' +#' #' @param id The ID of the module instance. Must match the ID passed to #' the corresponding call to `querychat_ui()`. #' @param querychat_config An object created by `querychat_init()`. -#' +#' #' @returns A querychat instance, which is a named list with the following #' elements: -#' +#' #' - `sql`: A reactive that returns the current SQL query. #' - `title`: A reactive that returns the current title. #' - `df`: A reactive that returns the data frame, filtered and sorted by the #' current SQL query. #' - `chat`: The [ellmer::Chat] object that powers the chat interface. -#' +#' #' By convention, this object should be named `querychat_config`. -#' +#' #' @export querychat_server <- function(id, querychat_config) { shiny::moduleServer(id, function(input, output, session) { From c6d5d4588604214bfc609c03140899427bece61f Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Tue, 27 May 2025 15:06:49 -0400 Subject: [PATCH 02/14] Autosave python code --- .vscode/settings.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 76c2ea3a6..5c5e9d63c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,12 @@ { - "python.autoComplete.extraPaths": ["${workspaceFolder}/python-package"] + "python.autoComplete.extraPaths": ["${workspaceFolder}/python-package"], + "[python]": { + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll": "explicit", + "source.organizeImports": "explicit" + }, + "editor.defaultFormatter": "charliermarsh.ruff", + }, + "flake8.args": ["--max-line-length=120"] } From 279229353a0be3ddd15ea2cd81075189246c1006 Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Tue, 27 May 2025 15:08:26 -0400 Subject: [PATCH 03/14] Init makefile and uv usage --- python-package/.gitignore | 1 + python-package/Makefile | 48 ++++++++++++++++++++++ python-package/pyproject.toml | 77 ++++++++++++++++++++++++++++++----- 3 files changed, 116 insertions(+), 10 deletions(-) create mode 100644 python-package/.gitignore create mode 100644 python-package/Makefile diff --git a/python-package/.gitignore b/python-package/.gitignore new file mode 100644 index 000000000..07df930ad --- /dev/null +++ b/python-package/.gitignore @@ -0,0 +1 @@ +uv.lock diff --git a/python-package/Makefile b/python-package/Makefile new file mode 100644 index 000000000..96a748367 --- /dev/null +++ b/python-package/Makefile @@ -0,0 +1,48 @@ +# Inspired by https://github.com/posit-dev/chatlas/blob/main/Makefile + +.PHONY: setup +setup: ## [py] Setup python environment + uv sync --all-extras + +.PHONY: build +build: ## [py] Build python package + @echo "๐Ÿงณ Building python package" + @[ -d dist ] && rm -r dist || true + uv build + +.PHONY: check +check: check-format check-types ## [py] Run python checks + +.PHONY: check-types +check-types: ## [py] Run python type checks + @echo "" + @echo "๐Ÿ“ Checking types with pyright" + uv run pyright + +.PHONY: check-format +check-format: + @echo "" + @echo "๐Ÿ“ Checking format with ruff" + uv run --with ruff ruff check querychat --config pyproject.toml + +.PHONY: format +format: ## [py] Format python code + uv run --with ruff ruff check --fix querychat --config pyproject.toml + uv run --with ruff ruff format querychat --config pyproject.toml + +.PHONY: help +help: ## Show help messages for make targets + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; { \ + printf "\033[32m%-18s\033[0m", $$1; \ + if ($$2 ~ /^\[docs\]/) { \ + printf "\033[34m[docs]\033[0m%s\n", substr($$2, 7); \ + } else if ($$2 ~ /^\[py\]/) { \ + printf " \033[33m[py]\033[0m%s\n", substr($$2, 5); \ + } else if ($$2 ~ /^\[r\]/) { \ + printf " \033[31m[r]\033[0m%s\n", substr($$2, 4); \ + } else { \ + printf " %s\n", $$2; \ + } \ + }' + +.DEFAULT_GOAL := help diff --git a/python-package/pyproject.toml b/python-package/pyproject.toml index c709ee050..93ef2f8e7 100644 --- a/python-package/pyproject.toml +++ b/python-package/pyproject.toml @@ -7,11 +7,9 @@ name = "querychat" version = "0.1.0" description = "Chat with your data using natural language" readme = "README.md" -requires-python = ">=3.8" -license = {file = "LICENSE"} -authors = [ - {name = "Posit", email = "info@posit.co"}, -] +requires-python = ">=3.9" +license = { file = "LICENSE" } +authors = [{ name = "Posit", email = "info@posit.co" }] dependencies = [ "duckdb", "pandas", @@ -30,8 +28,67 @@ Issues = "https://github.com/posit-dev/querychat/issues" packages = ["querychat"] [tool.hatch.build.targets.sdist] -include = [ - "querychat", - "LICENSE", - "README.md", -] \ No newline at end of file +include = ["querychat", "LICENSE", "README.md"] + +[tool.uv] +dev-dependencies = ["ruff>=0.6.5"] + +[tool.ruff] +src = ["querychat"] +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv", + "app.py", # ignore examples for now +] + +line-length = 88 +indent-width = 4 + +target-version = "py39" + +[tool.ruff.lint] +select = ['E', 'F', 'W', 'A', 'PLC', 'PLE', 'PLW', 'I'] +ignore = ["E501", "A002"] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" +skip-magic-trailing-comma = false +line-ending = "auto" +docstring-code-format = true +docstring-code-line-length = "dynamic" + +[tool.pyright] +include = ["querychat"] +# exclude = ["examples", ".venv"] From 5d720a1126f19fa40aa8c780ba605e32b2909277 Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Tue, 27 May 2025 15:08:35 -0400 Subject: [PATCH 04/14] fix lints --- python-package/querychat/__init__.py | 2 +- python-package/querychat/querychat.py | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/python-package/querychat/__init__.py b/python-package/querychat/__init__.py index 5ad87cf65..931d65c90 100644 --- a/python-package/querychat/__init__.py +++ b/python-package/querychat/__init__.py @@ -1,3 +1,3 @@ -from querychat.querychat import init, ui, sidebar, server +from querychat.querychat import init, server, sidebar, ui __all__ = ["init", "ui", "sidebar", "server"] diff --git a/python-package/querychat/querychat.py b/python-package/querychat/querychat.py index 58bb56071..2357a88ed 100644 --- a/python-package/querychat/querychat.py +++ b/python-package/querychat/querychat.py @@ -1,19 +1,17 @@ from __future__ import annotations -import sys import os import re -import pandas as pd -import duckdb -import json +import sys from functools import partial -from typing import List, Dict, Any, Callable, Optional, Union, Protocol +from typing import Any, Dict, Optional, Protocol import chatlas -from htmltools import TagList, tags, HTML -from shiny import module, reactive, ui, Inputs, Outputs, Session +import duckdb import narwhals as nw +import pandas as pd from narwhals.typing import IntoFrame +from shiny import Inputs, Outputs, Session, module, reactive, ui def system_prompt( @@ -136,7 +134,9 @@ def df_to_html(df: IntoFrame, maxrows: int = 5) -> str: df_short = nw.from_native(df).head(maxrows) # Generate HTML table - table_html = df_short.to_pandas().to_html(index=False, classes="table table-striped") + table_html = df_short.to_pandas().to_html( + index=False, classes="table table-striped" + ) # Add note about truncated rows if needed if len(df_short) != len(ndf): @@ -299,7 +299,7 @@ def server( - chat: The chat object """ - @reactive.Effect + @reactive.effect def _(): # This will be triggered when the module is initialized # Here we would set up the chat interface, initialize the chat model, etc. @@ -313,10 +313,10 @@ def _(): create_chat_callback = querychat_config.create_chat_callback # Reactive values to store state - current_title = reactive.Value(None) - current_query = reactive.Value("") + current_title = reactive.value[str | None](None) + current_query = reactive.value("") - @reactive.Calc + @reactive.calc def filtered_df(): if current_query.get() == "": return df From d824749e90e79b9d9b44b1fc3b44cb9869d90455 Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Tue, 27 May 2025 15:26:34 -0400 Subject: [PATCH 05/14] Init GHA workflows from chatlas --- .github/workflows/py-release.yml | 58 ++++++++++++++++++++++++++++++++ .github/workflows/py-test.yml | 52 ++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 .github/workflows/py-release.yml create mode 100644 .github/workflows/py-test.yml diff --git a/.github/workflows/py-release.yml b/.github/workflows/py-release.yml new file mode 100644 index 000000000..1bb93ed1d --- /dev/null +++ b/.github/workflows/py-release.yml @@ -0,0 +1,58 @@ +name: Python - Release + +on: + release: + types: [published] + +env: + PYTHON_VERSION: 3.12 + +jobs: + pypi-release: + name: Build and release Python package + runs-on: ubuntu-latest + + if: startsWith(github.ref, 'refs/tags/py-v') + + environment: + name: pypi + url: https://pypi.org/project/querychat/ + + permissions: # for trusted publishing + id-token: write + + steps: + - uses: actions/checkout@v4 + + - name: ๐Ÿš€ Install uv + uses: astral-sh/setup-uv@v3 + + - name: ๐Ÿ Set up Python ${{ env.PYTHON_VERSION }} + working-directory: ./python-package + run: uv python install ${{ env.PYTHON_VERSION }} + + - name: ๐Ÿ“ฆ Install the project + working-directory: ./python-package + run: uv sync --python ${{ env.PYTHON_VERSION }} --all-extras + + # - name: ๐Ÿงช Check tests + # working-directory: ./python-package + # run: make check-tests + + - name: ๐Ÿ“ Check types + working-directory: ./python-package + run: make check-types + + - name: ๐Ÿ“ Check formatting + working-directory: ./python-package + run: make check-format + + - name: ๐Ÿงณ Build package + working-directory: ./python-package + run: make build + + # TODO: https://pypi.org/manage/project/querychat/settings/publishing/ + - name: ๐Ÿšข Publish release on PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: ./python-package/dist diff --git a/.github/workflows/py-test.yml b/.github/workflows/py-test.yml new file mode 100644 index 000000000..5ca95f37b --- /dev/null +++ b/.github/workflows/py-test.yml @@ -0,0 +1,52 @@ +name: Test - Python + +on: + workflow_dispatch: + push: + branches: ["main", "rc-*"] + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + release: + types: [published] + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + config: + - { python-version: "3.9", test_google: false, test_azure: false } + - { python-version: "3.10", test_google: false, test_azure: false } + - { python-version: "3.11", test_google: false, test_azure: false } + - { python-version: "3.12", test_google: true, test_azure: true } + - { python-version: "3.13", test_google: false, test_azure: false } + + steps: + - uses: actions/checkout@v4 + + - name: ๐Ÿš€ Install uv + uses: astral-sh/setup-uv@v3 + + - name: ๐Ÿ Set up Python ${{ matrix.config.python-version }} + working-directory: ./python-package + run: uv python install ${{matrix.config.python-version }} + + - name: ๐Ÿ“ฆ Install the project + working-directory: ./python-package + run: uv sync --python ${{ matrix.config.python-version }} --all-extras + + # - name: ๐Ÿงช Check tests + # working-directory: ./python-package + # run: make check-tests + + - name: ๐Ÿ“ Check types + # if: ${{ matrix.config.python-version != '3.9' }} + working-directory: ./python-package + run: make check-types + + - name: ๐Ÿ“ Check formatting + working-directory: ./python-package + run: make check-format From b48cf36013a292a96ce8142718513ee9e806de34 Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Tue, 27 May 2025 15:30:35 -0400 Subject: [PATCH 06/14] Don't fail fast --- .github/workflows/py-test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/py-test.yml b/.github/workflows/py-test.yml index 5ca95f37b..2e9734358 100644 --- a/.github/workflows/py-test.yml +++ b/.github/workflows/py-test.yml @@ -23,6 +23,7 @@ jobs: - { python-version: "3.11", test_google: false, test_azure: false } - { python-version: "3.12", test_google: true, test_azure: true } - { python-version: "3.13", test_google: false, test_azure: false } + fail-fast: false steps: - uses: actions/checkout@v4 From 093f00fd73fc1ecdb24b63ba1740281e4b974331 Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Tue, 27 May 2025 15:30:44 -0400 Subject: [PATCH 07/14] Use latest pyright --- python-package/Makefile | 2 +- python-package/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python-package/Makefile b/python-package/Makefile index 96a748367..39db70031 100644 --- a/python-package/Makefile +++ b/python-package/Makefile @@ -17,7 +17,7 @@ check: check-format check-types ## [py] Run python checks check-types: ## [py] Run python type checks @echo "" @echo "๐Ÿ“ Checking types with pyright" - uv run pyright + uv run --with pyright pyright .PHONY: check-format check-format: diff --git a/python-package/pyproject.toml b/python-package/pyproject.toml index 93ef2f8e7..879e21d86 100644 --- a/python-package/pyproject.toml +++ b/python-package/pyproject.toml @@ -31,7 +31,7 @@ packages = ["querychat"] include = ["querychat", "LICENSE", "README.md"] [tool.uv] -dev-dependencies = ["ruff>=0.6.5"] +dev-dependencies = ["ruff>=0.6.5", "pyright>=1.1.401"] [tool.ruff] src = ["querychat"] From 0fb4d952b2bbe8071e7236611b408a02a5123c29 Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Tue, 27 May 2025 16:13:27 -0400 Subject: [PATCH 08/14] add R CMD check --- .github/workflows/R-CMD-check.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/R-CMD-check.yml diff --git a/.github/workflows/R-CMD-check.yml b/.github/workflows/R-CMD-check.yml new file mode 100644 index 000000000..dbc973811 --- /dev/null +++ b/.github/workflows/R-CMD-check.yml @@ -0,0 +1,27 @@ +# Workflow derived from https://github.com/rstudio/shiny-workflows +# +# NOTE: This Shiny team GHA workflow is overkill for most R packages. +# For most R packages it is better to use https://github.com/r-lib/actions +on: + push: + branches: [main, rc-**] + pull_request: + schedule: + - cron: "0 8 * * 1" # every monday + +name: Package checks + +jobs: + website: + uses: rstudio/shiny-workflows/.github/workflows/website.yaml@v1 + with: + working-directory: ./r-package + routine: + uses: rstudio/shiny-workflows/.github/workflows/routine.yaml@v1 + with: + format-r-code: true + working-directory: ./r-package + R-CMD-check: + uses: rstudio/shiny-workflows/.github/workflows/R-CMD-check.yaml@v1 + with: + working-directory: ./r-package From 6beb6befdf80215b80b24e1ebad55d1325c99cc9 Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Wed, 28 May 2025 10:12:22 -0400 Subject: [PATCH 09/14] More ruff rules / fixes --- python-package/pyproject.toml | 75 ++++++++++++++++++++++++++- python-package/querychat/querychat.py | 13 +++-- 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/python-package/pyproject.toml b/python-package/pyproject.toml index 879e21d86..d77e93486 100644 --- a/python-package/pyproject.toml +++ b/python-package/pyproject.toml @@ -70,9 +70,80 @@ indent-width = 4 target-version = "py39" +# [tool.ruff.lint] +# select = ['E', 'F', 'W', 'A', 'PLC', 'PLE', 'PLW', 'I'] [tool.ruff.lint] -select = ['E', 'F', 'W', 'A', 'PLC', 'PLE', 'PLW', 'I'] -ignore = ["E501", "A002"] +extend-ignore = [ + "E501", # Line too long + "PT011", # `pytest.raises(ValueError)` is too broad + "PT022", # No teardown in fixture + "F841", # Local variable is assigned but never used + "COM812", # missing-trailing-comma + "ISC001", # single-line-implicit-string-concatenation + "ISC002", # multi-line-implicit-string-concatenation + "ARG001", # Unused argument + "A002", # Shadowing a built-in +] +extend-select = [ + # "C90", # Many false positives # C90; mccabe: https://docs.astral.sh/ruff/rules/complex-structure/ + # "DTZ", # Dates with timezones are different from dates without timezones # DTZ; flake8-datetimez: https://docs.astral.sh/ruff/rules/#flake8-datetimez-dtz + + # flake8-builtins + # https://docs.astral.sh/ruff/rules/#flake8-builtins-a + # + # Check for builtin shadowing (i.e., naming a variable 'for', which is a builtin.) + "A", + + # # pydocstyle + # # https://docs.astral.sh/ruff/rules/#pydocstyle-d + # # https://docs.astral.sh/ruff/faq/#does-ruff-support-numpy-or-google-style-docstrings + # # + # # Check docstring formatting. Many of these rules are intentionally ignored below. + # "D", + + "ARG", # ARG; flake8-argparse: https://docs.astral.sh/ruff/rules/#flake8-unused-arguments-arg + "E", # E; pycodestyle: https://docs.astral.sh/ruff/rules/#pycodestyle-e-w + "F", # F; Pyflakes: https://docs.astral.sh/ruff/rules/#pyflakes-f + "I", # I; isort: https://docs.astral.sh/ruff/rules/#isort-i + "B", # B; flake8-bugbear: https://docs.astral.sh/ruff/rules/#flake8-bugbear-b + "Q", # Q; flake8-quotes: https://docs.astral.sh/ruff/rules/#flake8-quotes-q + "COM", # COM; Commas: https://docs.astral.sh/ruff/rules/#flake8-commas-com + "C4", # C4; flake8-comprehensions: https://docs.astral.sh/ruff/rules/#flake8-comprehensions-c4 + "FA102", # FA102; flake8-future-annotations: https://docs.astral.sh/ruff/rules/#flake8-future-annotations-fa + "ISC", # ISC; flake8-implicit-str-concat: https://docs.astral.sh/ruff/rules/#flake8-implicit-str-concat-isc + "ICN", # ICN; flake8-import-conventions: https://docs.astral.sh/ruff/rules/#flake8-import-conventions-icn + "PIE", # PIE; flake8-pie: https://docs.astral.sh/ruff/rules/#flake8-pie-pie + "PYI013", # PYI013; flake8-pyi Non-empty class body must not contain `...`: https://docs.astral.sh/ruff/rules/#flake8-pyi-pyi + "PYI030", # PYI030; flake8-pyi Multiple literal members in a union: https://docs.astral.sh/ruff/rules/#flake8-pyi-pyi + "PYI034", # PYI034; flake8-pyi `__new__` methods usually reutrn `Self`: https://docs.astral.sh/ruff/rules/#flake8-pyi-pyi + "PT", # PT; flake8-pytest-style: https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt + "SIM118", # SIM118; flake8-simplify Use `key {operator} dict`: https://docs.astral.sh/ruff/rules/#flake8-simplify-sim + "TCH", # TCH; flake8-type-checking: https://docs.astral.sh/ruff/rules/#flake8-type-checking-tch + # "FIX", # FIX; flake8-fixme: https://docs.astral.sh/ruff/rules/#flake8-fixme-fix + # "PGH", # PGH; pygrep-hooks: https://docs.astral.sh/ruff/rules/#pygrep-hooks-pgh + "NPY", # NPY; NumPy-specific rules: https://docs.astral.sh/ruff/rules/#numpy-specific-rules-npy + "RUF005", # RUF005; Ruff specific rules Consider {expression} instead of concatenation: https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf + "RUF100", # RUF100; Ruff specific rules Unused `noqa` directive https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf +] +# ignore = [ +# # # NumPy style docstring convention with noted exceptions. +# # # https://docs.astral.sh/ruff/faq/#does-ruff-support-numpy-or-google-style-docstrings +# # # +# # # This docstring style works with [quartodoc](https://machow.github.io/quartodoc/get-started/overview.html). +# # # +# # 'D101', # TODO(#135) implement docstring for public class +# # 'D103', # TODO(#135) implement docstring for public functions +# # 'D104', # TODO(#135) implement docstring for public package +# # 'D105', # TODO(#135) implement docstring for magic methods +# # 'D100', # TODO(#135) implement docstring for public modules +# # 'D102', # TODO(#135) implement docstring for public methods +# # 'D401', # TODO(#135) fix imperative mood warnings + +# # 'D418', # D418 contradicts the overload convention used in this project to describe different endpoints across Connect versions. + +# "E501", +# "A002", +# ] # Allow fix for all enabled rules (when `--fix`) is provided. fixable = ["ALL"] diff --git a/python-package/querychat/querychat.py b/python-package/querychat/querychat.py index 2357a88ed..191c0d56d 100644 --- a/python-package/querychat/querychat.py +++ b/python-package/querychat/querychat.py @@ -4,15 +4,17 @@ import re import sys from functools import partial -from typing import Any, Dict, Optional, Protocol +from typing import TYPE_CHECKING, Any, Dict, Optional, Protocol import chatlas import duckdb import narwhals as nw -import pandas as pd -from narwhals.typing import IntoFrame from shiny import Inputs, Outputs, Session, module, reactive, ui +if TYPE_CHECKING: + import pandas as pd + from narwhals.typing import IntoFrame + def system_prompt( df: IntoFrame, @@ -282,7 +284,10 @@ def sidebar(id: str, width: int = 400, height: str = "100%", **kwargs) -> ui.Sid @module.server def server( - input: Inputs, output: Outputs, session: Session, querychat_config: QueryChatConfig + input: Inputs, + output: Outputs, + session: Session, + querychat_config: QueryChatConfig, ) -> Dict[str, Any]: """ Initialize the querychat server. From 85312ffcf09fb444e850fc3f58d6e6d6a634bb2c Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Wed, 28 May 2025 10:46:31 -0400 Subject: [PATCH 10/14] More rules --- python-package/pyproject.toml | 69 ++++++++++++--------------- python-package/querychat/querychat.py | 36 +++++++++----- 2 files changed, 55 insertions(+), 50 deletions(-) diff --git a/python-package/pyproject.toml b/python-package/pyproject.toml index d77e93486..3d77a8ccd 100644 --- a/python-package/pyproject.toml +++ b/python-package/pyproject.toml @@ -75,18 +75,26 @@ target-version = "py39" [tool.ruff.lint] extend-ignore = [ "E501", # Line too long - "PT011", # `pytest.raises(ValueError)` is too broad - "PT022", # No teardown in fixture - "F841", # Local variable is assigned but never used - "COM812", # missing-trailing-comma "ISC001", # single-line-implicit-string-concatenation "ISC002", # multi-line-implicit-string-concatenation "ARG001", # Unused argument "A002", # Shadowing a built-in + "D200", # One-line docstring should fit on one line with quotes + "D203", # 1 blank line required before class docstring + "D212", # Multi-line docstring summary should start at the first line + "RET504", # Unnecessary assignment to `{name}` before `return` statement + "RET505", # Unnecessary branch after `return` statement + # TODO: Remove in the future, when we have docstrings. + "D100", # Missing docstring in public module + "D101", # Missing docstring in public class + "D102", # Missing docstring in public method + "D104", # Missing docstring in public package + "D107", # Missing docstring in __init__ + "D205", # 1 blank line required between summary line and description ] extend-select = [ - # "C90", # Many false positives # C90; mccabe: https://docs.astral.sh/ruff/rules/complex-structure/ - # "DTZ", # Dates with timezones are different from dates without timezones # DTZ; flake8-datetimez: https://docs.astral.sh/ruff/rules/#flake8-datetimez-dtz + # "C90", # C90; mccabe: https://docs.astral.sh/ruff/rules/complex-structure/ + "DTZ", # Dates with timezones are different from dates without timezones # DTZ; flake8-datetimez: https://docs.astral.sh/ruff/rules/#flake8-datetimez-dtz # flake8-builtins # https://docs.astral.sh/ruff/rules/#flake8-builtins-a @@ -94,14 +102,16 @@ extend-select = [ # Check for builtin shadowing (i.e., naming a variable 'for', which is a builtin.) "A", - # # pydocstyle - # # https://docs.astral.sh/ruff/rules/#pydocstyle-d - # # https://docs.astral.sh/ruff/faq/#does-ruff-support-numpy-or-google-style-docstrings - # # - # # Check docstring formatting. Many of these rules are intentionally ignored below. - # "D", + # pydocstyle + # https://docs.astral.sh/ruff/rules/#pydocstyle-d + # https://docs.astral.sh/ruff/faq/#does-ruff-support-numpy-or-google-style-docstrings + # + # Check docstring formatting. Many of these rules are intentionally ignored below. + "D", + + "ASYNC", # ASYNC; flake8-async: https://docs.astral.sh/ruff/rules/#flake8-async-async + - "ARG", # ARG; flake8-argparse: https://docs.astral.sh/ruff/rules/#flake8-unused-arguments-arg "E", # E; pycodestyle: https://docs.astral.sh/ruff/rules/#pycodestyle-e-w "F", # F; Pyflakes: https://docs.astral.sh/ruff/rules/#pyflakes-f "I", # I; isort: https://docs.astral.sh/ruff/rules/#isort-i @@ -109,41 +119,24 @@ extend-select = [ "Q", # Q; flake8-quotes: https://docs.astral.sh/ruff/rules/#flake8-quotes-q "COM", # COM; Commas: https://docs.astral.sh/ruff/rules/#flake8-commas-com "C4", # C4; flake8-comprehensions: https://docs.astral.sh/ruff/rules/#flake8-comprehensions-c4 - "FA102", # FA102; flake8-future-annotations: https://docs.astral.sh/ruff/rules/#flake8-future-annotations-fa + "FA", # FA; flake8-future-annotations: https://docs.astral.sh/ruff/rules/#flake8-future-annotations-fa "ISC", # ISC; flake8-implicit-str-concat: https://docs.astral.sh/ruff/rules/#flake8-implicit-str-concat-isc "ICN", # ICN; flake8-import-conventions: https://docs.astral.sh/ruff/rules/#flake8-import-conventions-icn "PIE", # PIE; flake8-pie: https://docs.astral.sh/ruff/rules/#flake8-pie-pie - "PYI013", # PYI013; flake8-pyi Non-empty class body must not contain `...`: https://docs.astral.sh/ruff/rules/#flake8-pyi-pyi - "PYI030", # PYI030; flake8-pyi Multiple literal members in a union: https://docs.astral.sh/ruff/rules/#flake8-pyi-pyi - "PYI034", # PYI034; flake8-pyi `__new__` methods usually reutrn `Self`: https://docs.astral.sh/ruff/rules/#flake8-pyi-pyi + "PYI", # PYI; flake8-pyi : https://docs.astral.sh/ruff/rules/#flake8-pyi-pyi "PT", # PT; flake8-pytest-style: https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt - "SIM118", # SIM118; flake8-simplify Use `key {operator} dict`: https://docs.astral.sh/ruff/rules/#flake8-simplify-sim - "TCH", # TCH; flake8-type-checking: https://docs.astral.sh/ruff/rules/#flake8-type-checking-tch + "RET", # RET; flake8-return: https://docs.astral.sh/ruff/rules/#flake8-return-ret + "SIM", # SIM; flake8-simplify: https://docs.astral.sh/ruff/rules/#flake8-simplify-sim + "TID253", # banned-module-level-imports: https://docs.astral.sh/ruff/rules/banned-module-level-imports/#banned-module-level-imports-tid253 + "T10", # T10; flake8-dbugger: https://docs.astral.sh/ruff/rules/#flake8-debugger-t10 + "TC", # TC; flake8-type-checking: https://docs.astral.sh/ruff/rules/#flake8-type-checking-tch + "ARG", # ARG; flake8-argparse: https://docs.astral.sh/ruff/rules/#flake8-unused-arguments-arg # "FIX", # FIX; flake8-fixme: https://docs.astral.sh/ruff/rules/#flake8-fixme-fix # "PGH", # PGH; pygrep-hooks: https://docs.astral.sh/ruff/rules/#pygrep-hooks-pgh "NPY", # NPY; NumPy-specific rules: https://docs.astral.sh/ruff/rules/#numpy-specific-rules-npy "RUF005", # RUF005; Ruff specific rules Consider {expression} instead of concatenation: https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf "RUF100", # RUF100; Ruff specific rules Unused `noqa` directive https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf ] -# ignore = [ -# # # NumPy style docstring convention with noted exceptions. -# # # https://docs.astral.sh/ruff/faq/#does-ruff-support-numpy-or-google-style-docstrings -# # # -# # # This docstring style works with [quartodoc](https://machow.github.io/quartodoc/get-started/overview.html). -# # # -# # 'D101', # TODO(#135) implement docstring for public class -# # 'D103', # TODO(#135) implement docstring for public functions -# # 'D104', # TODO(#135) implement docstring for public package -# # 'D105', # TODO(#135) implement docstring for magic methods -# # 'D100', # TODO(#135) implement docstring for public modules -# # 'D102', # TODO(#135) implement docstring for public methods -# # 'D401', # TODO(#135) fix imperative mood warnings - -# # 'D418', # D418 contradicts the overload convention used in this project to describe different endpoints across Connect versions. - -# "E501", -# "A002", -# ] # Allow fix for all enabled rules (when `--fix`) is provided. fixable = ["ALL"] diff --git a/python-package/querychat/querychat.py b/python-package/querychat/querychat.py index 191c0d56d..326ad5bfe 100644 --- a/python-package/querychat/querychat.py +++ b/python-package/querychat/querychat.py @@ -24,8 +24,8 @@ def system_prompt( categorical_threshold: int = 10, ) -> str: """ - Create a system prompt for the chat model based on a data frame's - schema and optional additional context and instructions. + Create a system prompt for the chat model based on a data frame's schema and + optional additional context and instructions. Args: df: A DataFrame to generate schema information from @@ -36,6 +36,7 @@ def system_prompt( Returns: A string containing the system prompt for the chat model + """ schema = df_to_schema(df, table_name, categorical_threshold) @@ -59,7 +60,8 @@ def system_prompt( prompt_text = prompt_text.replace("{{schema}}", schema) prompt_text = prompt_text.replace("{{data_description}}", data_description_section) prompt_text = prompt_text.replace( - "{{extra_instructions}}", extra_instructions or "" + "{{extra_instructions}}", + extra_instructions or "", ) return prompt_text @@ -76,8 +78,8 @@ def df_to_schema(df: IntoFrame, table_name: str, categorical_threshold: int) -> Returns: A string containing the schema information - """ + """ ndf = nw.from_native(df) schema = [f"Table: {table_name}", "Columns:"] @@ -131,13 +133,15 @@ def df_to_html(df: IntoFrame, maxrows: int = 5) -> str: Returns: HTML string representation of the table + """ ndf = nw.from_native(df) df_short = nw.from_native(df).head(maxrows) # Generate HTML table table_html = df_short.to_pandas().to_html( - index=False, classes="table table-striped" + index=False, + classes="table table-striped", ) # Add note about truncated rows if needed @@ -198,11 +202,12 @@ def init( Returns: A QueryChatConfig object that can be passed to server() + """ # Validate table name (must begin with letter, contain only letters, numbers, underscores) if not re.match(r"^[a-zA-Z][a-zA-Z0-9_]*$", table_name): raise ValueError( - "Table name must begin with a letter and contain only letters, numbers, and underscores" + "Table name must begin with a letter and contain only letters, numbers, and underscores", ) # Process greeting @@ -216,7 +221,10 @@ def init( # Create the system prompt if system_prompt_override is None: _system_prompt = system_prompt( - df, table_name, data_description, extra_instructions + df, + table_name, + data_description, + extra_instructions, ) else: _system_prompt = system_prompt_override @@ -227,7 +235,8 @@ def init( # Default chat function if none provided create_chat_callback = create_chat_callback or partial( - chatlas.ChatOpenAI, model="gpt-4o" + chatlas.ChatOpenAI, + model="gpt-4o", ) return QueryChatConfig( @@ -249,6 +258,7 @@ def mod_ui() -> ui.TagList: Returns: A UI component + """ # Include CSS css_path = os.path.join(os.path.dirname(__file__), "static", "css", "styles.css") @@ -273,6 +283,7 @@ def sidebar(id: str, width: int = 400, height: str = "100%", **kwargs) -> ui.Sid Returns: A sidebar UI component + """ return ui.sidebar( mod_ui(id), @@ -283,7 +294,7 @@ def sidebar(id: str, width: int = 400, height: str = "100%", **kwargs) -> ui.Sid @module.server -def server( +def server( # noqa: D417 input: Inputs, output: Outputs, session: Session, @@ -302,6 +313,7 @@ def server( - title: A reactive that returns the current title - df: A reactive that returns the filtered data frame - chat: The chat object + """ @reactive.effect @@ -336,7 +348,7 @@ async def append_output(text): # The function that updates the dashboard with a new SQL query async def update_dashboard(query: str, title: str): """ - Modifies the data presented in the data dashboard, based on the given SQL query, and also updates the title. + Modify the data presented in the data dashboard, based on the given SQL query, and also updates the title. Parameters ---------- @@ -344,8 +356,8 @@ async def update_dashboard(query: str, title: str): A DuckDB SQL query; must be a SELECT statement. title A title to display at the top of the data dashboard, summarizing the intent of the SQL query. - """ + """ await append_output(f"\n```sql\n{query}\n```\n\n") try: @@ -370,8 +382,8 @@ async def query(query: str): ---------- query A DuckDB SQL query; must be a SELECT statement. - """ + """ await append_output(f"\n```sql\n{query}\n```\n\n") try: From 7fd42e634cb9beecce7f0e4830c44dfaef85238f Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Wed, 28 May 2025 10:49:13 -0400 Subject: [PATCH 11/14] PTH rules --- python-package/pyproject.toml | 1 + python-package/querychat/querychat.py | 9 ++++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/python-package/pyproject.toml b/python-package/pyproject.toml index 3d77a8ccd..914eb819a 100644 --- a/python-package/pyproject.toml +++ b/python-package/pyproject.toml @@ -131,6 +131,7 @@ extend-select = [ "T10", # T10; flake8-dbugger: https://docs.astral.sh/ruff/rules/#flake8-debugger-t10 "TC", # TC; flake8-type-checking: https://docs.astral.sh/ruff/rules/#flake8-type-checking-tch "ARG", # ARG; flake8-argparse: https://docs.astral.sh/ruff/rules/#flake8-unused-arguments-arg + "PTH", # PTH; flake8-use-pathlib: https://docs.astral.sh/ruff/rules/#flake8-use-pathlib-pth # "FIX", # FIX; flake8-fixme: https://docs.astral.sh/ruff/rules/#flake8-fixme-fix # "PGH", # PGH; pygrep-hooks: https://docs.astral.sh/ruff/rules/#pygrep-hooks-pgh "NPY", # NPY; NumPy-specific rules: https://docs.astral.sh/ruff/rules/#numpy-specific-rules-npy diff --git a/python-package/querychat/querychat.py b/python-package/querychat/querychat.py index 326ad5bfe..508806030 100644 --- a/python-package/querychat/querychat.py +++ b/python-package/querychat/querychat.py @@ -1,9 +1,9 @@ from __future__ import annotations -import os import re import sys from functools import partial +from pathlib import Path from typing import TYPE_CHECKING, Any, Dict, Optional, Protocol import chatlas @@ -41,9 +41,8 @@ def system_prompt( schema = df_to_schema(df, table_name, categorical_threshold) # Read the prompt file - prompt_path = os.path.join(os.path.dirname(__file__), "prompt", "prompt.md") - with open(prompt_path, "r") as f: - prompt_text = f.read() + prompt_path = Path(__file__).parent / "prompt" / "prompt.md" + prompt_text = prompt_path.read_text() # Simple template replacement (a more robust template engine could be used) if data_description: @@ -261,7 +260,7 @@ def mod_ui() -> ui.TagList: """ # Include CSS - css_path = os.path.join(os.path.dirname(__file__), "static", "css", "styles.css") + css_path = Path(__file__).parent / "static" / "css" / "styles.css" return ui.TagList( ui.include_css(css_path), From d9700192338ec2129d09b098614e27a2360c9a06 Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Wed, 28 May 2025 12:17:15 -0400 Subject: [PATCH 12/14] Add final set of ruff rules. Fix lints --- python-package/pyproject.toml | 72 +++++++++++++-------------- python-package/querychat/__init__.py | 2 +- python-package/querychat/querychat.py | 4 +- 3 files changed, 38 insertions(+), 40 deletions(-) diff --git a/python-package/pyproject.toml b/python-package/pyproject.toml index 914eb819a..a88a6d12a 100644 --- a/python-package/pyproject.toml +++ b/python-package/pyproject.toml @@ -74,16 +74,20 @@ target-version = "py39" # select = ['E', 'F', 'W', 'A', 'PLC', 'PLE', 'PLW', 'I'] [tool.ruff.lint] extend-ignore = [ - "E501", # Line too long - "ISC001", # single-line-implicit-string-concatenation - "ISC002", # multi-line-implicit-string-concatenation - "ARG001", # Unused argument - "A002", # Shadowing a built-in - "D200", # One-line docstring should fit on one line with quotes - "D203", # 1 blank line required before class docstring - "D212", # Multi-line docstring summary should start at the first line - "RET504", # Unnecessary assignment to `{name}` before `return` statement - "RET505", # Unnecessary branch after `return` statement + "A002", # Shadowing a built-in + "ARG001", # Unused argument + "D200", # One-line docstring should fit on one line with quotes + "D203", # 1 blank line required before class docstring + "D212", # Multi-line docstring summary should start at the first line + "E501", # Line too long + "ISC001", # single-line-implicit-string-concatenation + "ISC002", # multi-line-implicit-string-concatenation + "PD901", # Avoid using the generic variable name `df` for DataFrames + "PLR0913", # Too many arguments in function definition + "PLR0915", # Too many statements in function + "RET504", # Unnecessary assignment to `{name}` before `return` statement + "RET505", # Unnecessary branch after `return` statement + "UP007", # Use `X | Y` for type annotations (or Optional[X]) # TODO: Remove in the future, when we have docstrings. "D100", # Missing docstring in public module "D101", # Missing docstring in public class @@ -94,49 +98,43 @@ extend-ignore = [ ] extend-select = [ # "C90", # C90; mccabe: https://docs.astral.sh/ruff/rules/complex-structure/ - "DTZ", # Dates with timezones are different from dates without timezones # DTZ; flake8-datetimez: https://docs.astral.sh/ruff/rules/#flake8-datetimez-dtz - - # flake8-builtins - # https://docs.astral.sh/ruff/rules/#flake8-builtins-a - # - # Check for builtin shadowing (i.e., naming a variable 'for', which is a builtin.) - "A", - - # pydocstyle - # https://docs.astral.sh/ruff/rules/#pydocstyle-d - # https://docs.astral.sh/ruff/faq/#does-ruff-support-numpy-or-google-style-docstrings - # - # Check docstring formatting. Many of these rules are intentionally ignored below. - "D", - - "ASYNC", # ASYNC; flake8-async: https://docs.astral.sh/ruff/rules/#flake8-async-async - - - "E", # E; pycodestyle: https://docs.astral.sh/ruff/rules/#pycodestyle-e-w - "F", # F; Pyflakes: https://docs.astral.sh/ruff/rules/#pyflakes-f - "I", # I; isort: https://docs.astral.sh/ruff/rules/#isort-i + "ASYNC", # ASYNC; flake8-async: https://docs.astral.sh/ruff/rules/#flake8-async-async + "S", # S; flake8-bandit: https://docs.astral.sh/ruff/rules/#flake8-bandit-s + "FBT", # FBT; flake8-boolean-trap: https://docs.astral.sh/ruff/rules/#flake8-boolean-trap-fbt "B", # B; flake8-bugbear: https://docs.astral.sh/ruff/rules/#flake8-bugbear-b - "Q", # Q; flake8-quotes: https://docs.astral.sh/ruff/rules/#flake8-quotes-q + "A", # A; flake8-builtins: https://docs.astral.sh/ruff/rules/#flake8-builtins-a "COM", # COM; Commas: https://docs.astral.sh/ruff/rules/#flake8-commas-com "C4", # C4; flake8-comprehensions: https://docs.astral.sh/ruff/rules/#flake8-comprehensions-c4 + "DTZ", # DTZ; flake8-datetimez: https://docs.astral.sh/ruff/rules/#flake8-datetimez-dtz + "T10", # T10; flake8-dbugger: https://docs.astral.sh/ruff/rules/#flake8-debugger-t10 "FA", # FA; flake8-future-annotations: https://docs.astral.sh/ruff/rules/#flake8-future-annotations-fa "ISC", # ISC; flake8-implicit-str-concat: https://docs.astral.sh/ruff/rules/#flake8-implicit-str-concat-isc "ICN", # ICN; flake8-import-conventions: https://docs.astral.sh/ruff/rules/#flake8-import-conventions-icn "PIE", # PIE; flake8-pie: https://docs.astral.sh/ruff/rules/#flake8-pie-pie "PYI", # PYI; flake8-pyi : https://docs.astral.sh/ruff/rules/#flake8-pyi-pyi "PT", # PT; flake8-pytest-style: https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt + "Q", # Q; flake8-quotes: https://docs.astral.sh/ruff/rules/#flake8-quotes-q "RET", # RET; flake8-return: https://docs.astral.sh/ruff/rules/#flake8-return-ret "SIM", # SIM; flake8-simplify: https://docs.astral.sh/ruff/rules/#flake8-simplify-sim "TID253", # banned-module-level-imports: https://docs.astral.sh/ruff/rules/banned-module-level-imports/#banned-module-level-imports-tid253 - "T10", # T10; flake8-dbugger: https://docs.astral.sh/ruff/rules/#flake8-debugger-t10 "TC", # TC; flake8-type-checking: https://docs.astral.sh/ruff/rules/#flake8-type-checking-tch + "TD", # TD; flake8-todos: https://docs.astral.sh/ruff/rules/#flake8-todosimports-td "ARG", # ARG; flake8-argparse: https://docs.astral.sh/ruff/rules/#flake8-unused-arguments-arg "PTH", # PTH; flake8-use-pathlib: https://docs.astral.sh/ruff/rules/#flake8-use-pathlib-pth - # "FIX", # FIX; flake8-fixme: https://docs.astral.sh/ruff/rules/#flake8-fixme-fix - # "PGH", # PGH; pygrep-hooks: https://docs.astral.sh/ruff/rules/#pygrep-hooks-pgh + "I", # I; isort: https://docs.astral.sh/ruff/rules/#isort-i "NPY", # NPY; NumPy-specific rules: https://docs.astral.sh/ruff/rules/#numpy-specific-rules-npy - "RUF005", # RUF005; Ruff specific rules Consider {expression} instead of concatenation: https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf - "RUF100", # RUF100; Ruff specific rules Unused `noqa` directive https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf + "PD", # PD; pandas-vet: https://docs.astral.sh/ruff/rules/#pandas-vet-pd + "N", # N; pep8-naming: https://docs.astral.sh/ruff/rules/#pep8-naming-n + "PERF", # PERF; flake8-performance: https://docs.astral.sh/ruff/rules/#flake8-performance-perf + "E", # E; pycodestyle: https://docs.astral.sh/ruff/rules/#pycodestyle-e-w + "W", # W; pycodestyle: https://docs.astral.sh/ruff/rules/#pycodestyle-e-w + "D", # D; pydocstyle: https://docs.astral.sh/ruff/rules/#pydocstyle-d + "F", # F; Pyflakes: https://docs.astral.sh/ruff/rules/#pyflakes-f + "PGH", # PGH; pygrep-hooks: https://docs.astral.sh/ruff/rules/#pygrep-hooks-pgh + "PL", # PL; pylint: https://docs.astral.sh/ruff/rules/#pylint-pl + "UP", # UP; pyupgrade: https://docs.astral.sh/ruff/rules/#pyupgrade-up + "FURB", # FURB; refurb: https://docs.astral.sh/ruff/rules/#refurb-furb + "RUF", # RUF; Ruff specific rules: https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf ] # Allow fix for all enabled rules (when `--fix`) is provided. diff --git a/python-package/querychat/__init__.py b/python-package/querychat/__init__.py index 931d65c90..3aa1ef834 100644 --- a/python-package/querychat/__init__.py +++ b/python-package/querychat/__init__.py @@ -1,3 +1,3 @@ from querychat.querychat import init, server, sidebar, ui -__all__ = ["init", "ui", "sidebar", "server"] +__all__ = ["init", "server", "sidebar", "ui"] diff --git a/python-package/querychat/querychat.py b/python-package/querychat/querychat.py index 508806030..892febdf7 100644 --- a/python-package/querychat/querychat.py +++ b/python-package/querychat/querychat.py @@ -4,7 +4,7 @@ import sys from functools import partial from pathlib import Path -from typing import TYPE_CHECKING, Any, Dict, Optional, Protocol +from typing import TYPE_CHECKING, Any, Optional, Protocol import chatlas import duckdb @@ -298,7 +298,7 @@ def server( # noqa: D417 output: Outputs, session: Session, querychat_config: QueryChatConfig, -) -> Dict[str, Any]: +) -> dict[str, Any]: """ Initialize the querychat server. From 94e99ea0c98762a38e907fbdd17e3cab9fb09115 Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Wed, 28 May 2025 14:52:51 -0400 Subject: [PATCH 13/14] Copy `make check-tox` from chatlas; Fix lints --- python-package/Makefile | 6 ++++++ python-package/pyproject.toml | 18 ++++++++++++++++-- python-package/querychat/querychat.py | 4 ++-- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/python-package/Makefile b/python-package/Makefile index 39db70031..8832df5a7 100644 --- a/python-package/Makefile +++ b/python-package/Makefile @@ -30,6 +30,12 @@ format: ## [py] Format python code uv run --with ruff ruff check --fix querychat --config pyproject.toml uv run --with ruff ruff format querychat --config pyproject.toml +.PHONY: check-tox +check-tox: ## [py] Run python 3.9 - 3.12 checks with tox + @echo "" + @echo "๐Ÿ”„ Running tests and type checking with tox for Python 3.9--3.12" + uv run tox run-parallel + .PHONY: help help: ## Show help messages for make targets @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; { \ diff --git a/python-package/pyproject.toml b/python-package/pyproject.toml index a88a6d12a..77a03df3c 100644 --- a/python-package/pyproject.toml +++ b/python-package/pyproject.toml @@ -31,7 +31,7 @@ packages = ["querychat"] include = ["querychat", "LICENSE", "README.md"] [tool.uv] -dev-dependencies = ["ruff>=0.6.5", "pyright>=1.1.401"] +dev-dependencies = ["ruff>=0.6.5", "pyright>=1.1.401", "tox-uv>=1.11.4"] [tool.ruff] src = ["querychat"] @@ -154,4 +154,18 @@ docstring-code-line-length = "dynamic" [tool.pyright] include = ["querychat"] -# exclude = ["examples", ".venv"] + + +# For more tox testing usage (in addition to typing), see: +# https://github.com/posit-dev/chatlas/blob/b91c020a555917c1be5ae2462496de25d82c529d/pyproject.toml#L122-L139 +[tool.tox] +legacy_tox_ini = """ +[tox] +env_list = py3{9,10,11,12} +isolated_build = True + +[testenv] +package = wheel +wheel_build_env = .pkg +commands = pyright +""" diff --git a/python-package/querychat/querychat.py b/python-package/querychat/querychat.py index 892febdf7..e286527ba 100644 --- a/python-package/querychat/querychat.py +++ b/python-package/querychat/querychat.py @@ -4,7 +4,7 @@ import sys from functools import partial from pathlib import Path -from typing import TYPE_CHECKING, Any, Optional, Protocol +from typing import TYPE_CHECKING, Any, Optional, Protocol, Union import chatlas import duckdb @@ -329,7 +329,7 @@ def _(): create_chat_callback = querychat_config.create_chat_callback # Reactive values to store state - current_title = reactive.value[str | None](None) + current_title = reactive.value[Union[str, None]](None) current_query = reactive.value("") @reactive.calc From bf772a04d1d27623b91bf14cb8ba5a3321b04241 Mon Sep 17 00:00:00 2001 From: Barret Schloerke Date: Wed, 28 May 2025 15:04:30 -0400 Subject: [PATCH 14/14] Add min R version of 4.1 --- r-package/DESCRIPTION | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/r-package/DESCRIPTION b/r-package/DESCRIPTION index de15fd5a8..6319c056a 100644 --- a/r-package/DESCRIPTION +++ b/r-package/DESCRIPTION @@ -5,15 +5,14 @@ Authors@R: c( person("Joe", "Cheng", , "joe@posit.co", role = c("aut", "cre")), person("Posit Software, PBC", role = c("cph", "fnd")) ) -Description: Adds an LLM-powered chatbot to your 'shiny' app, that can turn your - users' natural language questions into SQL queries that run against your data, - and return the result as a reactive dataframe. Use it to drive reactive - calculations, visualizations, downloads, etc. +Description: Adds an LLM-powered chatbot to your 'shiny' app, that can + turn your users' natural language questions into SQL queries that run + against your data, and return the result as a reactive dataframe. Use + it to drive reactive calculations, visualizations, downloads, etc. License: MIT + file LICENSE -Encoding: UTF-8 -Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.2 -Imports: +Depends: + R (>= 4.1.0) +Imports: bslib, DBI, duckdb, @@ -27,3 +26,6 @@ Imports: shinychat, whisker, xtable +Encoding: UTF-8 +Roxygen: list(markdown = TRUE) +RoxygenNote: 7.3.2