Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM mcr.microsoft.com/devcontainers/python:1-3.12-bookworm
FROM mcr.microsoft.com/devcontainers/python:3.14-bookworm

RUN rm -f /etc/apt/sources.list.d/yarn.list \
&& apt-get update && apt-get install -y --no-install-recommends zstd \
Expand Down
5 changes: 3 additions & 2 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@
"ghcr.io/va-h/devcontainers-features/uv:1": {},
"ghcr.io/prulloac/devcontainer-features/ollama:1": {}
},
"postCreateCommand": "uv sync --dev && uv run pre-commit install",
"postCreateCommand": "uv sync --dev && uv run prek install",
"customizations": {
"vscode": {
"extensions": [
"ms-python.python",
"GitHub.copilot",
"GitHub.vscode-pull-request-github",
"charliermarsh.ruff"
"charliermarsh.ruff",
"Anthropic.claude-code"
],
"settings": {
"python.defaultInterpreter": "${containerWorkspaceFolder}/.venv/bin/python",
Expand Down
73 changes: 63 additions & 10 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12"]
python-version: ["3.14"]

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Install uv and set Python version
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
with:
enable-cache: true
python-version: ${{ matrix.python-version }}
Expand All @@ -31,29 +31,53 @@ jobs:
- name: Install package in development mode
run: uv pip install -e .

- name: Lint and format with Ruff
uses: astral-sh/ruff-action@v3
- name: Cache prek hook environments
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
version: latest
path: ~/.cache/prek
key: prek-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

- name: Run Ruff linter with fixes
run: uv run ruff check --fix .
# Inline annotations on the "Files changed" tab for any lint finding, independent
# of whether prek below also catches it -- this is just for in-PR visibility.
- name: Annotate Ruff findings
if: always()
continue-on-error: true
run: uv run ruff check --output-format=github .

- name: Run Ruff formatter
run: uv run ruff format .
# Runs the same checks as the local pre-commit hook (yaml/json/toml checks,
# pyupgrade, trailing commas, ruff lint+format, keep-sorted) so CI fails on
# anything a contributor's local hook would have caught. mypy and pyrefly are
# skipped here and run as separate steps below against the project's own venv,
# instead of letting prek build a second, duplicate dependency set for them.
# continue-on-error lets every check below run even if this one fails, so a
# single push surfaces all problems instead of just the first one encountered;
# the "Fail if any check failed" step at the end still gates the job overall.
- name: Run pre-commit checks (prek)
id: prek
continue-on-error: true
run: SKIP=mypy,pyrefly uv run prek run --all-files

- name: Run type checking with Mypy
id: mypy
continue-on-error: true
run: uv run mypy tapio

- name: Run Pyrefly code check
id: pyrefly
continue-on-error: true
run: uv run pyrefly check

- name: Test with pytest and coverage
id: pytest
continue-on-error: true
run: uv run pytest --cov=tapio --cov-report=xml --cov-report=term

- name: Check coverage threshold
id: coverage
continue-on-error: true
run: |
coverage_result=$(uv run coverage report | tail -1 | awk '{print $NF}' | sed 's/%//')
echo "coverage_pct=$coverage_result" >> "$GITHUB_OUTPUT"
echo "Coverage: $coverage_result%"
if (( $(echo "$coverage_result < 80" | bc -l) )); then
echo "❌ Coverage threshold not met: $coverage_result% is less than 80%"
Expand All @@ -62,6 +86,35 @@ jobs:
echo "✅ Coverage threshold met: $coverage_result% is greater than or equal to 80%"
fi

# Aggregated pass/fail dashboard rendered on the workflow run's Summary tab,
# so contributors don't need to open individual step logs to see what failed.
- name: Write job summary
if: always()
run: |
{
echo "## CI Results"
echo
echo "| Check | Result |"
echo "|-------|--------|"
echo "| prek (lint, format, yaml/json/toml, etc.) | ${{ steps.prek.outcome == 'success' && '✅ Passed' || '❌ Failed' }} |"
echo "| mypy | ${{ steps.mypy.outcome == 'success' && '✅ Passed' || '❌ Failed' }} |"
echo "| pyrefly | ${{ steps.pyrefly.outcome == 'success' && '✅ Passed' || '❌ Failed' }} |"
echo "| pytest | ${{ steps.pytest.outcome == 'success' && '✅ Passed' || '❌ Failed' }} |"
echo "| coverage (≥80%) | ${{ steps.coverage.outcome == 'success' && '✅ Passed' || '❌ Failed' }} (${{ steps.coverage.outputs.coverage_pct }}%) |"
} >> "$GITHUB_STEP_SUMMARY"

- name: Fail if any check failed
if: always()
run: |
if [ "${{ steps.prek.outcome }}" != "success" ] || \
[ "${{ steps.mypy.outcome }}" != "success" ] || \
[ "${{ steps.pyrefly.outcome }}" != "success" ] || \
[ "${{ steps.pytest.outcome }}" != "success" ] || \
[ "${{ steps.coverage.outcome }}" != "success" ]; then
echo "One or more checks failed. See the job summary above and the failing step's log for details."
exit 1
fi

# - name: Upload coverage to Codecov
# uses: codecov/codecov-action@v4
# with:
Expand Down
10 changes: 6 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
default_language_version:
python: python3.14
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
Expand All @@ -11,18 +13,18 @@ repos:
- id: check-toml
- id: check-merge-conflict
- repo: https://github.com/asottile/pyupgrade
rev: v3.19.1
rev: v3.21.2
hooks:
- id: pyupgrade
args: [--py311-plus]
args: [--py313-plus]
- repo: https://github.com/asottile/add-trailing-comma
rev: v3.1.0
hooks:
- id: add-trailing-comma
args: [--py36-plus]
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.9.8
rev: v0.15.20
hooks:
# Run the linter.
- id: ruff
Expand All @@ -44,7 +46,7 @@ repos:
- chromadb
- langchain-chroma
- langchain-core
- langchain-huggingface
- langchain-huggingface[full]
- langchain-text-splitters
- langchain-community
- langchain
Expand Down
2 changes: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.12
3.14
9 changes: 9 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"recommendations": [
"anthropic.claude-code",
"ms-vscode-remote.remote-containers",
"github.vscode-pull-request-github",
"ms-python.python",
"charliermarsh.ruff"
]
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,4 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d

<!-- ALL-CONTRIBUTORS-LIST:END -->

This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind are welcome!
3 changes: 3 additions & 0 deletions mise.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
[tools]
gh = "latest"
ollama = "latest"
python = "3.14"
uv = "latest"
2 changes: 1 addition & 1 deletion mypy.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[mypy]
python_version = 3.11
python_version = 3.14
warn_redundant_casts = True
warn_unreachable = True
pretty = True
Expand Down
111 changes: 101 additions & 10 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "tapio"
version = "2.0.0"
description = "An assistant for Finnish immigrants"
readme = "README.md"
requires-python = ">=3.10"
requires-python = ">=3.14"
license = "MIT"
authors = [
{name = "Finntegrate Team", email = "info@finntegrate.org"}
Expand All @@ -18,10 +18,10 @@ dependencies = [
"langchain-community>=0.3.21",
"langchain>=0.3.23",
"langchain-chroma>=0.2.3",
"langchain-huggingface>=0.1.2",
"gradio>=5.25.2",
"langchain-huggingface[full]>=0.1.2",
"gradio>=6.0.0",
"ollama>=0.4.8",
"pydantic>=2.11.3",
"pydantic>=2.13.4",
"httpx>=0.28.1",
"beautifulsoup4>=4.13.4",
]
Expand All @@ -35,11 +35,102 @@ python_files = "test_*.py"

[tool.ruff]
line-length = 120
target-version = "py310"
target-version = "py314"

[tool.ruff.lint]
select = ["E", "F", "I", "UP"]
ignore = []
select = [
"A", # flake8-builtins
"ANN", # flake8-annotations
"ARG", # flake8-unused-arguments
"ASYNC", # flake8-async
"B", # flake8-bugbear
"BLE", # flake8-blind-except
"C4", # flake8-comprehensions
"C90", # mccabe complexity
"D", # pydocstyle
"DTZ", # flake8-datetimez
"E", # pycodestyle errors
"EM", # flake8-errmsg
"EXE", # flake8-executable
"F", # pyflakes
"FA", # flake8-future-annotations
"FBT", # flake8-boolean-trap
"FIX", # flake8-fixme
"FLY", # flynt
"FURB", # refurb
"G", # flake8-logging-format
"I", # isort
"ICN", # flake8-import-conventions
"INP", # flake8-no-pep420
"ISC", # flake8-implicit-str-concat
"LOG", # flake8-logging
"N", # pep8-naming
"PERF", # perflint
"PGH", # pygrep-hooks
"PIE", # flake8-pie
"PL", # pylint
"PT", # flake8-pytest-style
"PTH", # flake8-use-pathlib
"RET", # flake8-return
"RSE", # flake8-raise
"RUF", # ruff-specific rules
"S", # flake8-bandit
"SIM", # flake8-simplify
"SLF", # flake8-self
"SLOT", # flake8-slots
"T10", # flake8-debugger
"T20", # flake8-print
"TC", # flake8-type-checking
"TD", # flake8-todos
"TID", # flake8-tidy-imports
"TRY", # tryceratops
"UP", # pyupgrade
"W", # pycodestyle warnings
"YTT", # flake8-2020
]
ignore = [
"ANN401", # Any is used intentionally for flexible metadata dicts
"BLE001", # broad except-and-log-and-continue is intentional throughout the crawl/parse/vectorize pipelines
"COM812", # trailing-comma rules conflict with the ruff formatter
"FBT001", # bool positional params are idiomatic for typer.Option-based CLI flags
"FBT002", # bool default params are idiomatic for typer.Option-based CLI flags
"ISC001", # implicit-str-concat conflicts with the ruff formatter
"TC001", # moving first-party imports behind TYPE_CHECKING breaks at runtime: no
# `from __future__ import annotations` here, and Python 3.14's deferred
# annotations (PEP 649) only postpone evaluation -- anything that actually
# resolves them (e.g. gradio's event-handler introspection, already hit a
# NameError on this) still needs the name to exist at call time
"TC002", # same risk as TC001, for third-party imports
"TC003", # same risk as TC001, for standard-library imports
"TRY301", # raise typer.Exit(...) inside a try block is a deliberate CLI control-flow
# signal for validation failures, not an error needing a dedicated wrapper
# function -- tryceratops' heuristic doesn't fit this idiom
]

[tool.ruff.lint.per-file-ignores]
"tests/*" = [
"ANN", # tests are excluded from mypy and don't need type annotations
"ARG001", # unused fixture/mock arguments are normal in tests
"ARG002", # unused mock-override arguments are normal in tests
"D", # docstrings aren't required on test functions
"INP001", # tests intentionally use implicit namespace packages
"PLC0415", # local imports for mocking/patching timing are idiomatic in tests
"PLR0913", # test functions naturally take many fixture arguments
"PLR2004", # magic values in test assertions/fixtures are fine
"PT009", # some test classes intentionally use unittest.TestCase-style assertions
"PTH", # os.path-based temp file/dir handling in test fixtures is fine
"S101", # assert is the standard pytest idiom
"SLF001", # tests intentionally exercise private members
]
Comment on lines +110 to +124

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In Ruff, does a per-file-ignorespattern oftests/match nested files liketests/services/test_llm_service.py, or is tests/**/.py required?

💡 Result:

In Ruff, a per-file-ignores pattern of tests/* is generally insufficient for matching files in nested subdirectories because it is a non-recursive, single-level glob pattern [1]. While some users have reported behavior where single-level patterns occasionally match subdirectories depending on specific implementation details or environment conditions [2], the standard and recommended way to ensure a pattern applies to an entire directory tree (including all nested files) is to use the recursive wildcard `` [3][1]. To ensure files like tests/services/test_llm_service.py are matched, you should use: [tool.ruff.lint.per-file-ignores] "tests//.py" = ["<RULE_CODE>"] Using `tests//.py` explicitly enables recursive matching, ensuring that all Python files within the `tests/` directory and its subdirectories are covered [1]. For debugging or verifying whether a specific file is being matched as you expect, you can run `ruff check --show-settings <path/to/file.py>` to see how the patterns are resolved [4].

Citations:


🌐 Web query:

Ruff documentation per-file-ignores glob patterns recursive directory matching tests/* tests/**/*.py

💡 Result:

In Ruff, the per-file-ignores setting supports glob patterns to match files or directories for which specific lint rules should be disabled [1][2]. For recursive directory matching—where you want to apply ignores to all files within a directory and its subdirectories—the standard approach is to use the double wildcard pattern [2][3]. Recommended Patterns: 1. Recursive Matching (Recommended for most test directories): To ignore rules for all Python files throughout an entire directory tree, use the pattern "tests//.py" [2]. [tool.ruff.lint.per-file-ignores] "tests//.py" = ["D"] 2. Non-Recursive Matching: Using "tests/" targets files directly within the "tests/" directory [2]. Depending on your specific Ruff version and configuration, this may not consistently cover nested subdirectories [2][4]. Key Implementation Details: - Location: This setting is typically configured in your pyproject.toml or ruff.toml file under the [tool.ruff.lint.per-file-ignores] section [5][1][2]. - Syntax: The patterns are evaluated as glob patterns [1][2]. You can specify a list of rule codes or prefixes to ignore for the matched files [1]. - Negation: You can use an initial '!' to negate a file pattern if needed [1]. - Consistency: While some simpler glob patterns might behave differently across tool settings (like exclude vs. per-file-ignores), using the explicit recursive syntax (/.py) is the most reliable way to target all files in a directory hierarchy [2][6].

Citations:


🌐 Web query:

site:docs.astral.sh/ruff per-file-ignores glob pattern recursive nested subdirectories tests/*

💡 Result:

To apply per-file-ignores recursively to all files within a tests directory and its nested subdirectories, you should use the globstar pattern in your configuration. In your configuration file (e.g., pyproject.toml or ruff.toml), define the per-file-ignores setting using a pattern like the following [1][2][3]: [tool.ruff.lint.per-file-ignores] "tests/" = ["RULE_CODE"] The pattern ensures that the rule is applied recursively to all files within the tests directory and any of its nested subdirectories [4]. If you need to target specific file types within those directories, you can append the extension, for example: "tests//*.py" = ["RULE_CODE"] This configuration instructs the linter to exclude the specified rule codes for any matching file found at any depth under the tests directory [2].

Citations:


🌐 Web query:

Ruff glob rules recursive pattern per-file-ignores tests/**/*.py tests/** documentation

💡 Result:

In Ruff, the per-file-ignores setting uses glob patterns to identify files for which specific lint rules should be disabled [1]. You can achieve recursive matching using the double-asterisk () wildcard [2]. For recursive matching in your examples, the following patterns are correct: - To target all Python files recursively within a `tests` directory: `"tests//*.py" = [...]` [2]. - To target an entire directory tree (such as `tests` or `documentation`), you can use `"tests/" = [...]` or `"documentation/" = [...]` [3][2]. Key details for configuring `per-file-ignores`: Glob Pattern Behavior: - Single-level vs. Recursive: Patterns like `tests/*` are non-recursive and match only files immediately within the `tests/` directory [2]. Patterns using (e.g., tests//*.py or tests/) are recursive and match the entire directory hierarchy [2]. - Brace Expansion: You can group multiple paths using brace expansion to avoid repetition, for example: "/{tests,documentation}/*" = [...] [3][4]. - Negation: An initial ! negates the pattern [1]. For instance, "!src/.py" = ["D"] ignores D rules everywhere except for the src/ directory [1]. Configuration: - You define these mappings in your pyproject.toml or ruff.toml file under [tool.ruff.lint.per-file-ignores] [1][4]. - Patterns are generally resolved relative to the configuration file [5]. Note on path matching: Ruff's globbing implementation for per-file-ignores may occasionally behave differently than standard exclude patterns regarding directory boundaries [6][7]. If you encounter unexpected matching, verify your settings by running ruff check --show-settings to inspect how the glob patterns are resolved [6][5].

Citations:


🌐 Web query:

Ruff per-file-ignores glob syntax tests/** tests/ tests/**/*.py official docs

💡 Result:

Ruff's per-file-ignores setting supports glob patterns to match files or directories for which specific lint rules should be ignored [1][2][3]. These patterns are defined in your configuration file (e.g., pyproject.toml or ruff.toml) under the [tool.ruff.lint.per-file-ignores] section [1][4][5]. Key syntax points: - Recursive matching: Use to match files recursively through subdirectories [3][5]. For example, "tests//.py" matches all Python files within the tests directory and any of its subdirectories [5]. - Brace expansion: Ruff supports brace expansion, allowing you to group multiple paths or extensions [1][6]. For example, "/{tests,docs,tools}/" ignores violations in any of those directories [1][4]. - Negation: You can use a leading! to negate a pattern [2][3]. This is useful for "ignore everywhere except" scenarios, such as "!src/.py" = ["D"] to ignore Pydocstyle (D) rules in every file except those in the src directory [2][3]. - Path context: Patterns are relative to the project root (the directory containing your configuration file) [7]. - Configuration: You map these patterns to a list of rule codes or prefixes [2][5]. Example configuration (pyproject.toml): [tool.ruff.lint.per-file-ignores] # Ignore D (Pydocstyle) in all files within the tests directory recursively "tests//.py" = ["D"] # Ignore E402 in specific directories using brace expansion "/{tests,docs,tools}/" = ["E402"] # Ignore D everywhere except the src directory "!src/.py" = ["D"] Note that file pattern syntax in Ruff can occasionally behave differently depending on whether you are using per-file-ignores or the top-level exclude setting [8]. It is recommended to verify your configuration using ruff show-settings if you encounter unexpected matching behavior [8].

Citations:


🌐 Web query:

site:docs.astral.sh/ruff per-file-ignores tests/* recursive glob official

💡 Result:

In the context of the Ruff linter, you can use recursive globbing within the per-file-ignores setting [1][2]. Ruff supports standard glob patterns for file matching in the per-file-ignores configuration [3][1]. To ignore rules recursively across all files within a specific directory, you can use the double asterisk globstar pattern [1][2]. For example, to ignore rule E402 for all files within a tests directory and its subdirectories, you can use the following configuration in your pyproject.toml: [tool.ruff.lint.per-file-ignores] "/tests/" = ["E402"] Alternatively, you can group multiple directories: [tool.ruff.lint.per-file-ignores] "/{tests,docs}/" = ["E402"] Note that while the original Flake8 tool also supports per-file-ignores, its pattern matching capabilities are more limited compared to Ruff, and it does not natively support recursive globstar patterns in the same way. If you are specifically looking for recursive support, Ruff is the recommended, modern alternative that provides this functionality [1][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- pyproject.toml excerpt ---'
sed -n '110,124p' pyproject.toml

printf '\n%s\n' '--- tests tree (depth 3) ---'
find tests -maxdepth 3 -type f | sort | sed -n '1,200p'

Repository: Finntegrate/tapio

Length of output: 1895


🌐 Web query:

site:docs.astral.sh/ruff glob patterns recursive double asterisk per-file-ignores official docs

💡 Result:

In glob patterns, the double asterisk is known as a globstar or recursive wildcard [1]. It matches any directory, subdirectory, or file recursively within the specified path [1]. When used in the per-file-ignores configuration of tools like the Ruff linter, the pattern allows you to apply rule exclusions to files across a directory tree [2][3]. For example, a pattern like "/{tests,docs,tools}/" will match and apply the specified ignore rules to all files nested within the tests, docs, or tools directories, regardless of their depth in the project structure [2][3]. Key points regarding the recursive double asterisk in this context: - Recursive Matching: Unlike a single asterisk (), which typically matches files only within a single directory level, enables deep, recursive traversal of the file system [1]. - Configuration Usage: It is commonly used in configuration files (such as pyproject.toml or ruff.toml) to selectively disable specific linting rules for large portions of a project, such as test suites or generated documentation, without needing to list every individual file or subdirectory [2][3]. - Paths: Patterns are generally treated as relative to the project root, meaning at the start of a pattern refers to the entire directory structure relative to that root [4][2].

Citations:


🌐 Web query:

site:docs.astral.sh/ruff "per-file-ignores" "tests/*" Ruff

💡 Result:

In Ruff, you can use the lint.per-file-ignores setting to disable specific lint rules for files or directories that match a given pattern [1][2]. To ignore rules for files within the tests directory, you can add an entry to your pyproject.toml or ruff.toml file under the [tool.ruff.lint.per-file-ignores] section [1][2]. Example configuration: [tool.ruff.lint.per-file-ignores] "tests/" = ["E402", "F401"] In this example, the rules E402 and F401 will be ignored for all files contained within the tests directory [1][2]. Key details: - Pattern Matching: Ruff uses glob patterns [2]. "tests/" will match files directly inside the tests directory, while "/{tests}/" or "tests//" can be used to match files recursively within the tests directory and its subdirectories [1][2]. - Configuration: This setting must be placed under [tool.ruff.lint.per-file-ignores] if using pyproject.toml [1][2]. - CLI: While there is no dedicated CLI flag for every configuration, you can use the --per-file-ignores flag or the --config flag to override these settings from the command line [1].

Citations:


🌐 Web query:

Ruff glob matching does * match slash per-file-ignores official docs

💡 Result:

In Ruff, the behavior of glob matching—specifically whether the asterisk () character matches path separators (slashes)—can be complex and has been a subject of ongoing discussion and refinement [1][2]. Officially, Ruff's glob implementation uses the globset crate [1]. Historically, the default behavior of the globset library allows * to match path separators, which means a pattern like src/.py might inadvertently match files in subdirectories (e.g., src/subdir/file.py) in addition to files directly within the directory [1][2]. Key points regarding Ruff's glob matching in per-file-ignores: 1. Path Separator Matching: Because * matches path separators by default, patterns intended to be single-level often behave as multi-level, recursive matches [1][2]. Users have reported this as an issue where, for example, src/.py matches files deeper in the directory tree than expected [2]. 2. Limitations and Configuration: There is no simple toggle in the current user-facing configuration to disable this "match-slash" behavior [1]. While internal efforts to apply literal_separator(true) to glob patterns (which restricts * from matching slashes) have been discussed and implemented in some parts of the codebase, behavior may remain inconsistent across different settings like exclude and per-file-ignores [2][3]. 3. Recommended Practice: To avoid ambiguity or unexpected matches, use explicit recursive patterns like /.py if you intend to match all files in a tree, or target files more specifically [4][5]. If you need to match files only at a specific depth, the current implementation's tendency to treat * as matching slashes necessitates caution [2]. Because Ruff's internal glob handling is evolving, and behaviors can vary depending on the specific version and configuration context (e.g., CLI vs. file-based settings), it is best to verify your patterns using the specific Ruff version you are running if you require strict path-level matching [6][7][2].

Citations:


🌐 Web query:

site:docs.astral.sh/ruff/settings/ "per-file-ignores" "tests/*" "subdirectories"

💡 Result:

In tools like Ruff, the per-file-ignores setting supports recursive matching within subdirectories by using glob patterns [1]. To apply ignore rules to all files within a specific directory and its subdirectories, you can use the double-star glob pattern [1]. For example, to ignore specific rule codes for all files within a tests directory and any of its subdirectories, you would use the following configuration in your pyproject.toml or ruff.toml file [1]: [tool.ruff.lint.per-file-ignores] "tests//.py" = ["RULE1", "RULE2"] Using "tests/" typically matches files directly within the tests directory, whereas "tests//*.py" ensures that the pattern recurses into all nested subdirectories [1]. You can also use the! prefix to negate patterns or define complex inclusion/exclusion logic as needed [1].

Citations:


Make the Ruff test ignore recursive. tests/* only matches files directly under tests/; nested test files under tests/config/, tests/services/, etc. still need these ignores. Use tests/**/*.py here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pyproject.toml` around lines 110 - 124, The Ruff per-file ignores for tests
are only applied to files directly under tests, so nested test modules still get
linted. Update the [tool.ruff.lint.per-file-ignores] pattern from tests/* to a
recursive tests/**/*.py pattern so all test files under tests, including
subdirectories, inherit the same ignores.

"tapio/cli.py" = [
"FBT003", # typer.Option(False, ...) passes the default positionally by convention
]

[tool.ruff.lint.pydocstyle]
convention = "google"

[tool.ruff.lint.pylint]
max-args = 6

[tool.ruff.format]
quote-style = "double"
Expand All @@ -49,12 +140,12 @@ line-ending = "auto"
[dependency-groups]
dev = [
"mypy>=1.15.0",
"pre-commit>=4.2.0",
"pyrefly>=0.17.0",
"prek>=0.2.3",
"pyrefly>=1.1.1",
"pytest>=8.3.5",
"pytest-asyncio>=1.0.0",
"pytest-cov>=6.1.1",
"ruff>=0.11.5",
"ruff>=0.13.1",
"types-lxml>=2025.3.30",
"types-pyyaml>=6.0.12.20250402",
]
2 changes: 1 addition & 1 deletion pyrefly.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
project_includes = ["tapio/", ]
project_excludes = ["**/.[!/.]*", "**/*venv/**", ".venv/**", "tests/"]
search_path = ["tapio/", "tests/"]
python_version = "3.11.0"
python_version = "3.14.0"
1 change: 0 additions & 1 deletion pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,3 @@ filterwarnings =

markers =
integration: Integration tests that use real components (slower, may download models)

Loading
Loading