diff --git a/AGENTS.md b/AGENTS.md index 6c40b35d..dc32fa85 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -120,6 +120,15 @@ please follow these guidelines: - **Exception**: Explicit re-export patterns like `from ... import X as X` or marked with "# For export" - This prevents circular imports and makes dependencies clear +## Python Import Style Guidelines + +* **Default to Module-Qualified Imports:** Prefer importing whole modules and using qualified calls (e.g., `import math; math.sqrt(16)` or `import pandas as pd; pd.DataFrame()`) to prevent namespace pollution, avoid name clashes, and provide immediate context for where functions or objects originate. +* **Use Direct Symbol Imports Cautiously:** Restrict direct imports (`from module import symbol`) to specific scenarios where they genuinely improve readability or adhere to standard conventions: + * Importing classes, exceptions, or constants (e.g., `from my_project.models import User`). + * Avoiding severe, repetitive visual clutter in heavy mathematical or algorithmic code. + * Standard library patterns (e.g., `from collections import defaultdict, Counter`). +* **Prohibit Wildcard Imports:** Never use wildcard imports (`from module import *`) in normal code. **Exception**: the same explicit re-export aggregator pattern allowed by the Import Architecture Rules above (e.g. `interfaces.py` re-exporting `interfaces_core`, `interfaces_indexes`, etc. with `__all__`) — enforced by `make ruff` (see Makefile), with that file listed in `[tool.ruff.lint.per-file-ignores]`. + * Order imports alphabetically after lowercasing; group them as follows (with a blank line between groups): 1. standard library imports diff --git a/Makefile b/Makefile index 3c435f7d..5345e755 100644 --- a/Makefile +++ b/Makefile @@ -11,13 +11,19 @@ format: venv uv run isort src tests tools examples $(FLAGS) uv run black -tpy312 src tests tools examples $(FLAGS) -# intentionally running pyright only for the lowest and the highest version +# intentionally running pyright only for the lowest and the highest version # running it for all versions takes too much time and doesn't add enough diagnostic power .PHONY: check check: venv uv run pyright --pythonversion 3.12 src tests tools examples uv run pyright --pythonversion 3.15 src tests tools examples +# Not wired into 'all'/CI yet -- see #116. Narrowly scoped for now to +# deterministically banning wildcard imports (AGENTS.md's import guidelines). +.PHONY: ruff +ruff: venv + uv run ruff check src tests tools examples + .PHONY: test test: venv uv run pytest $(FLAGS) @@ -105,6 +111,7 @@ help: @echo "make all # venv, format, check, test, build" @echo "make format # Run isort and black" @echo "make check # Run pyright" + @echo "make ruff # Run ruff (wildcard-import checks only, not part of 'all' yet)" @echo "make test # Run pytest (tests are in tests/)" @echo "make coverage # Run tests with coverage" @echo "make build # Build the wheel (under dist/)" diff --git a/make.bat b/make.bat index 62b3cf6f..77e840d6 100644 --- a/make.bat +++ b/make.bat @@ -28,6 +28,7 @@ if defined ARGS set "ARGS=%ARGS:~1%" :dispatch if /I "%CMD%"=="format" goto format if /I "%CMD%"=="check" goto check +if /I "%CMD%"=="ruff" goto ruff if /I "%CMD%"=="test" goto test if /I "%CMD%"=="coverage" goto coverage if /I "%CMD%"=="demo" goto demo @@ -59,6 +60,14 @@ uv run pyright --pythonversion 3.12 src tests tools examples || exit /b 1 uv run pyright --pythonversion 3.15 src tests tools examples || exit /b 1 goto end +:: Not wired into CI yet -- see #116. Narrowly scoped for now to +:: deterministically banning wildcard imports (AGENTS.md's import guidelines). +:ruff +if not exist ".venv\" call make.bat venv +echo Running ruff... +uv run ruff check src tests tools examples || exit /b 1 +goto end + :test if not exist ".venv\" call make.bat venv echo Running unit tests... @@ -123,7 +132,7 @@ if exist .pytest_cache rmdir /s /q .pytest_cache goto end :help -echo Usage: .\make [format^|check^|test^|coverage^|demo^|build^|venv^|sync^|install-uv^|clean^|help] +echo Usage: .\make [format^|check^|ruff^|test^|coverage^|demo^|build^|venv^|sync^|install-uv^|clean^|help] goto end :end diff --git a/pyproject.toml b/pyproject.toml index 9f4fff1b..1d84bbd9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,6 +97,19 @@ known_ai = ["mcp", "openai", "pydantic", "pydantic_ai", "tiktoken", "typechat"] known_first_party = ["typeagent", "tests", "tools", "gmail", "demo"] known_local_folder = ["conftest"] +# isort/black/pyright already cover ordering, formatting and unused/duplicate +# imports (see above). Ruff is scoped narrowly to the one gap those don't +# fill: deterministically banning wildcard imports, per AGENTS.md's "Python +# Import Style Guidelines". Broader adoption of ruff as an isort/black +# replacement was considered and deferred in #116. +[tool.ruff.lint] +select = ["F403", "F405"] + +[tool.ruff.lint.per-file-ignores] +# Explicit re-export aggregator (see AGENTS.md's "Import Architecture Rules" +# and "Prohibit Wildcard Imports" exceptions). +"src/typeagent/knowpro/interfaces.py" = ["F403", "F405"] + [dependency-groups] # Dev tooling never reaches the wheel metadata -- these bounds only pick which @@ -118,4 +131,5 @@ dev = [ "pytest>=9.1.1", "pytest-asyncio>=1.4.0", "pytest-mock>=3.15.1", + "ruff>=0.16.4", ] diff --git a/uv.lock b/uv.lock index aa7dac6a..923b784d 100644 --- a/uv.lock +++ b/uv.lock @@ -2581,6 +2581,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8c/97/d855d6b3c322d1f27e26f5241c42016b56cf01377ea8ed348285f54652f0/rpds_py-2026.6.3-cp315-cp315t-win_amd64.whl", hash = "sha256:ae3d4fe8c0b9213624fdce7279d70e3b148b682ca20719ebd193a23ebfa47324", size = 220719, upload-time = "2026-06-30T07:17:31.788Z" }, ] +[[package]] +name = "ruff" +version = "0.16.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/8f/d8074b1f25e003164087a8bfe79a0f1a3945135764dbb6aaab04103dcaf9/ruff-0.16.4.tar.gz", hash = "sha256:13171aa9d9af2240ee3504e639de73122c67e74036de5ba2e1d01422cd17e3dc", size = 4899731, upload-time = "2026-08-20T17:43:59.196Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/80/779895ef584e089d22f2c6df0d0e99a65ec2df0805f1fffd439415b8c1f0/ruff-0.16.4-py3-none-linux_armv6l.whl", hash = "sha256:df4075f71ddac40b9934af60c3ec8a53047dd5a5fdc43224e6e4e8e9a27cb6f7", size = 10006909, upload-time = "2026-08-20T17:43:16.888Z" }, + { url = "https://files.pythonhosted.org/packages/a9/e6/f553199b5e8927a05cb5c422d921fd0656b29ab976e91c44802107c6b0da/ruff-0.16.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:0c95538517af68004306b0fb3214ff2f2af67a65092aee77cd9eb86db6656604", size = 10240201, upload-time = "2026-08-20T17:43:19.337Z" }, + { url = "https://files.pythonhosted.org/packages/1c/70/4a6dc4bb34da4dee35e30f09bbd1bfbdd26f33b62fb9b8df31f08a199cd2/ruff-0.16.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:963f83df8e69e575b64d67dd447ebbc917db41a14bf38d4593a4183e7aaa8255", size = 9835122, upload-time = "2026-08-20T17:43:21.708Z" }, + { url = "https://files.pythonhosted.org/packages/24/12/c6e22d686372c15bcb7af99831f1a1be96df696491babf4f24e4f942c527/ruff-0.16.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32a5057c7ff3f6e6480a48fccfb3a412a690f48a3d03ac5cf08177d6c2da3ade", size = 9977162, upload-time = "2026-08-20T17:43:24.236Z" }, + { url = "https://files.pythonhosted.org/packages/46/49/72b10ec912f5ab5854992eaf7aa7cd36729b6937d9dc4e0fb41b3bf428ec/ruff-0.16.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b3dce8d9b0c57c265b91885a66a567d8ea1372e8eb4e250fa8e5e3f579e99cff", size = 9829789, upload-time = "2026-08-20T17:43:26.966Z" }, + { url = "https://files.pythonhosted.org/packages/fa/80/0f30e32e7f6ee26edc39075502db9d368d788a44a79b55f763eb4ab03796/ruff-0.16.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7dc651db49283c69f8e72c834eec4fe5573e4c646856aebece0ce385dceb2a80", size = 10527949, upload-time = "2026-08-20T17:43:29.384Z" }, + { url = "https://files.pythonhosted.org/packages/52/3d/86e8ad3542169e56cac3859a343afdb9df2ad54d35a59ce1e67baee83421/ruff-0.16.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3817b87dbcabc92f13b05019257c5b89b5b4d51b5fb20f56fb5235ceb723cd07", size = 11333695, upload-time = "2026-08-20T17:43:31.872Z" }, + { url = "https://files.pythonhosted.org/packages/d0/16/481c29b380c20a0054a8261066665e1b3488e23636c49d0a43e75975b9bb/ruff-0.16.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9fce1499134b2c8c68e5166f95705a5812062bb93aacc5f9873bb1a27084bc7", size = 10727741, upload-time = "2026-08-20T17:43:34.596Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b6/56bc0b8cf45b54b28b3a5e6381c8945d51b5b18adf659454c32295209a31/ruff-0.16.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2d812e482f5a7e02eee26cd73d2a37ebbdf47d795ea63ba1b89110ae93e9fb3", size = 10286522, upload-time = "2026-08-20T17:43:37.288Z" }, + { url = "https://files.pythonhosted.org/packages/e8/8b/b345b4fb110f2fbe2bd31eabd271e5e8b3b7e4ee6c0e02f2dc6be78db000/ruff-0.16.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:6baaf984aa7976edf93d3b627fe2d1d22ee94bbca05fa6f90fc76d73924e3454", size = 10584182, upload-time = "2026-08-20T17:43:39.984Z" }, + { url = "https://files.pythonhosted.org/packages/29/e5/827b34041c35f58774a9681a4213994c164fc987800f4dddabcf451da0bf/ruff-0.16.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:bdfcf0b28662eb890372d50f92c283bb94e67e7635ed93c7fd533970acff7b2b", size = 10134195, upload-time = "2026-08-20T17:43:42.351Z" }, + { url = "https://files.pythonhosted.org/packages/0f/10/d0bffcdd6729b87afc82ba0ef377173356a7dc8e972f5179968cf2fdf98c/ruff-0.16.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b66b02cb9b04f537643cadf5768e5f98dc461890d530cb67113d71c8c76e605d", size = 9825821, upload-time = "2026-08-20T17:43:44.532Z" }, + { url = "https://files.pythonhosted.org/packages/f5/32/0db2a863b796ca62d83e92a07a3ccf00921b14db02059347576a2fda3d4b/ruff-0.16.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8528bf9a4b291a60bf02ea453511e8ce6215bd2b982ee80405b66b008b6c30a0", size = 10267658, upload-time = "2026-08-20T17:43:46.989Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a0/fbdeb59e48c6261f523e56c8f12e9c08fbe693786595cc7e3959207a9232/ruff-0.16.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:fbd85d2875fdd67e833213a651f613bbf25303abf6aa822a5121f4531195678d", size = 10697071, upload-time = "2026-08-20T17:43:49.891Z" }, + { url = "https://files.pythonhosted.org/packages/aa/28/0c6dd865859c6d17bc8ccc34cb72b0e02d6c7eb25e8a1e22b5bea681e2c0/ruff-0.16.4-py3-none-win32.whl", hash = "sha256:312769988007aaeb8e189b443ccdd03c0e6374489e053467be6d96518ebff76e", size = 10021687, upload-time = "2026-08-20T17:43:52.281Z" }, + { url = "https://files.pythonhosted.org/packages/a3/03/e724450f621698117f9aa6dd241c94d0274ae96781378dc86745ae29f0e7/ruff-0.16.4-py3-none-win_amd64.whl", hash = "sha256:05d9d27a18c4bcbefada602480ec9e01e0bc949d432e0ced5df77edac195919c", size = 10567657, upload-time = "2026-08-20T17:43:54.78Z" }, + { url = "https://files.pythonhosted.org/packages/0e/fe/da8b9e1347696bb22120b77280ec5ce25d500ca5cb39d5ad6e5c18de19c1/ruff-0.16.4-py3-none-win_arm64.whl", hash = "sha256:a3a61621c9b6f6a89573e938a080e648f1695baa3f58570a3a707bc51ff65a21", size = 10451579, upload-time = "2026-08-20T17:43:57.135Z" }, +] + [[package]] name = "shellingham" version = "1.5.4" @@ -2775,6 +2800,7 @@ dev = [ { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-mock" }, + { name = "ruff" }, ] [package.metadata] @@ -2817,6 +2843,7 @@ dev = [ { name = "pytest", specifier = ">=9.1.1" }, { name = "pytest-asyncio", specifier = ">=1.4.0" }, { name = "pytest-mock", specifier = ">=3.15.1" }, + { name = "ruff", specifier = ">=0.16.4" }, ] [[package]]