diff --git a/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml
new file mode 100644
index 00000000..719e54ca
--- /dev/null
+++ b/.github/workflows/code_quality.yml
@@ -0,0 +1,45 @@
+name: Code Quality Checks
+on:
+ workflow_dispatch:
+ inputs:
+ git_ref:
+ type: string
+ description: Git ref of the DuckDB python package
+ required: false
+ workflow_call:
+ inputs:
+ git_ref:
+ type: string
+ description: Git ref of the DuckDB python package
+ required: false
+
+defaults:
+ run:
+ shell: bash
+
+jobs:
+ run_checks:
+ name: Run linting, formatting and static type checker
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.git_ref }}
+ fetch-depth: 0
+ persist-credentials: false
+
+ - name: Install Astral UV
+ uses: astral-sh/setup-uv@v6
+ with:
+ version: "0.7.14"
+ python-version: 3.9
+
+ - name: pre-commit (cache)
+ uses: actions/cache@v4
+ with:
+ path: ~/.cache/pre-commit
+ key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
+
+ - name: pre-commit (--all-files)
+ run: |
+ uvx pre-commit run --show-diff-on-failure --color=always --all-files
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
index ab696897..ce78df1c 100644
--- a/.github/workflows/coverage.yml
+++ b/.github/workflows/coverage.yml
@@ -151,4 +151,4 @@ jobs:
echo "### C++ Coverage Summary" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "$SUMMARY_CPP" >> $GITHUB_STEP_SUMMARY
- echo '```' >> $GITHUB_STEP_SUMMARY
\ No newline at end of file
+ echo '```' >> $GITHUB_STEP_SUMMARY
diff --git a/.github/workflows/on_pr.yml b/.github/workflows/on_pr.yml
index 7a4669cb..85c8904a 100644
--- a/.github/workflows/on_pr.yml
+++ b/.github/workflows/on_pr.yml
@@ -23,9 +23,14 @@ jobs:
name: Make sure submodule is in a sane state
uses: ./.github/workflows/submodule_sanity.yml
+ code_quality:
+ name: Code-quality checks
+ needs: submodule_sanity_guard
+ uses: ./.github/workflows/code_quality.yml
+
packaging_test:
name: Build a minimal set of packages and run all tests on them
- needs: submodule_sanity_guard
+ needs: code_quality
# Skip packaging tests for draft PRs
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
uses: ./.github/workflows/packaging.yml
@@ -36,7 +41,7 @@ jobs:
coverage_test:
name: Run coverage tests
- needs: submodule_sanity_guard
+ needs: code_quality
# Only run coverage test for draft PRs
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.draft == true }}
uses: ./.github/workflows/coverage.yml
diff --git a/.github/workflows/on_push.yml b/.github/workflows/on_push.yml
index 1a282d69..706f8789 100644
--- a/.github/workflows/on_push.yml
+++ b/.github/workflows/on_push.yml
@@ -18,8 +18,13 @@ concurrency:
cancel-in-progress: true
jobs:
+ code_quality:
+ name: Code-quality checks
+ uses: ./.github/workflows/code_quality.yml
+
test:
name: Run coverage tests
+ needs: code_quality
uses: ./.github/workflows/coverage.yml
with:
git_ref: ${{ github.ref }}
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 00000000..0010a4fa
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,37 @@
+repos:
+ - repo: https://github.com/astral-sh/ruff-pre-commit
+ # Ruff version.
+ rev: v0.13.3
+ hooks:
+ # Run the linter.
+ - id: ruff-check
+ # Run the formatter.
+ - id: ruff-format
+
+ - repo: https://github.com/pre-commit/mirrors-clang-format
+ rev: v21.1.2 # pick the version of clang-format you want
+ hooks:
+ - id: clang-format
+ files: \.(c|cpp|cc|h|hpp|cxx|hxx)$
+
+ - repo: https://github.com/cheshirekow/cmake-format-precommit
+ rev: v0.6.13
+ hooks:
+ - id: cmake-format
+
+ - repo: https://github.com/pre-commit/mirrors-mypy
+ rev: v1.18.2
+ hooks:
+ - id: mypy
+ entry: mypy -v
+ files: ^(duckdb/|_duckdb-stubs/)
+ exclude: ^duckdb/(experimental|query_graph)/
+ additional_dependencies: [ numpy, polars ]
+
+ - repo: local
+ hooks:
+ - id: post-checkout-submodules
+ name: Update submodule post-checkout
+ entry: .githooks/post-checkout
+ language: script
+ stages: [ post-checkout ]
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..d4f4b61b
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,19 @@
+# Changelog
+
+## v1.4.1
+**DuckDB Core**: v1.4.1
+
+### Bug Fixes
+- **ADBC Driver**: Fixed ADBC driver implementation (#81)
+- **SQLAlchemy compatibility**: Added `__hash__` method overload (#61)
+- **Error Handling**: Reset PyErr before throwing Python exceptions (#69)
+- **Polars Lazyframes**: Fixed Polars expression pushdown (#102)
+
+### Code Quality Improvements & Developer Experience
+- **MyPy Support**: MyPy is functional again and better integrated with the dev workflow
+- **Stubs**: Re-created and manually curated stubs for the binary extension
+- **Type Shadowing**: Deprecated `typing` and `functional` modules
+- **Linting & Formatting**: Comprehensive code quality improvements with Ruff
+- **Type Annotations**: Added missing overloads and improved type coverage
+- **Pre-commit Integration**: Added ruff, clang-format, cmake-format and mypy configs
+- **CI/CD**: Added code quality workflow
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a9bc047d..ab9e1cee 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -48,13 +48,12 @@ duckdb_add_library(duckdb_target)
# Bundle in INTERFACE library
add_library(_duckdb_dependencies INTERFACE)
-target_link_libraries(_duckdb_dependencies INTERFACE
- pybind11::pybind11
- duckdb_target
-)
+target_link_libraries(_duckdb_dependencies INTERFACE pybind11::pybind11
+ duckdb_target)
# Also add include directory
-target_include_directories(_duckdb_dependencies INTERFACE
- $
{new_name}
time: {formatted_num} seconds
" - body += f" {extra_info} " - if (width > 0): + body += f' {extra_info} ' + if width > 0: body += f"cardinality: {card}
" body += f"estimate: {est}
" body += f"width: {width} bytes
" - # TODO: Expand on timing. Usually available from a detailed profiling + # TODO: Expand on timing. Usually available from a detailed profiling # noqa: TD002, TD003 body += "| Phase | @@ -224,7 +227,7 @@ def generate_timing_html(graph_json: object, query_timings: object) -> object: all_phases = query_timings.get_phases() query_timings.add_node_timing(NodeTiming("TOTAL TIME", total_time)) query_timings.add_node_timing(NodeTiming("Execution Time", execution_time)) - all_phases = ["TOTAL TIME", "Execution Time"] + all_phases + all_phases = ["TOTAL TIME", "Execution Time", *all_phases] for phase in all_phases: summarized_phase = query_timings.get_summary_phase_timings(phase) summarized_phase.calculate_percentage(total_time) @@ -240,55 +243,48 @@ def generate_timing_html(graph_json: object, query_timings: object) -> object: return table_head + table_body -def generate_tree_html(graph_json: object) -> str: +def generate_tree_html(graph_json: object) -> str: # noqa: D103 json_graph = json.loads(graph_json) - cpu_time = float(json_graph['cpu_time']) - tree_prefix = "
|---|