diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 434f876158..f5dab93751 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -17,5 +17,6 @@ updates: schedule: interval: weekly ignore: - - dependency-name: ruff - dependency-name: bandit + - dependency-name: ruff + - dependency-name: sphinx-lint diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 0512796b58..1c3fdd08c5 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -10,6 +10,20 @@ on: permissions: read-all +env: + CACHE_GLOBS: | + **/pyproject.toml + **/requirements*.txt + **/setup.py + **/uv.lock + ENDPOINT_WHITELIST: >- + pypi.org:443 + github.com:443 + releases.astral.sh + files.pythonhosted.org:443 + *.github.com:443 + *.githubusercontent.com:443 + jobs: build: runs-on: ubuntu-latest @@ -19,26 +33,24 @@ jobs: with: disable-sudo: true egress-policy: block - allowed-endpoints: > - files.pythonhosted.org:443 - github.com:443 - pypi.org:443 + allowed-endpoints: ${{ env.ENDPOINT_WHITELIST}} - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: - # If it's a push (main or major-release) or workflow_dispatch, get full history. - fetch-depth: ${{ (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && 0 || 1 }} + # If it's a pull request, don't get full history. + fetch-depth: ${{ github.event_name == 'pull_request' && 1 || 0 }} - - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 + - uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 with: - python-version: '3.13' - cache: pip - - - uses: install-pinned/uv@259f91feb61b6e94766d7a1dbcd5f17335370e64 + python-version: 3.13 + activate-environment: true + cache-dependency-glob: ${{ env.CACHE_GLOBS }} - - run: | - uv pip install --system -e .[all] - uv pip install --system -r requirements-dev.txt + - run: >- + uv sync + --upgrade + --no-default-groups + --group docs - name: configure git run: | @@ -49,6 +61,7 @@ jobs: with: path: docs/html key: >- + uv run sphinx -${{ hashFiles('pyproject.toml') }} -${{ hashFiles('setup.py') }} @@ -65,8 +78,8 @@ jobs: deploy: needs: build - # Only run this job if triggered by updating the main branch - if: github.ref == 'refs/heads/main' && github.event_name == 'push' + # Don't run this job on pull requests + if: github.event_name != 'pull_request' runs-on: ubuntu-latest @@ -82,6 +95,7 @@ jobs: - uses: step-security/harden-runner@e3f713f2d8f53843e71c69a996d56f51aa9adfb9 with: disable-sudo: true - egress-policy: audit + egress-policy: block + allowed-endpoints: ${{ env.ENDPOINT_WHITELIST}} - uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e diff --git a/.github/workflows/python-checks.yml b/.github/workflows/python-checks.yml index ff1eacf6e7..63edca6a69 100644 --- a/.github/workflows/python-checks.yml +++ b/.github/workflows/python-checks.yml @@ -57,13 +57,14 @@ jobs: egress-policy: block allowed-endpoints: auth.docker.io:443 ${{ env.ENDPOINT_WHITELIST}} - - run: | + - id: pytest-run-key + run: | echo "\ RUN_KEY=\ - ${{ hashFiles('pyproject.toml') }}-\ - ${{ matrix.os }}-\ - ${{ matrix.python-version }}-\ - ${{ matrix.resolution }}\ + ${{ hashFiles('pyproject.toml') }}\ + -${{ matrix.os }}\ + -${{ matrix.python-version }}\ + -${{ matrix.resolution }}\ " >> $GITHUB_ENV - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd @@ -74,12 +75,24 @@ jobs: activate-environment: true cache-dependency-glob: ${{ env.CACHE_GLOBS }} - - run: | - uv pip install -r requirements-dev.txt - uv pip install .[all] - env: + - if: matrix.resolution != 'limited-dependencies' + run: >- + uv sync + --upgrade + --no-default-groups + --group test-coverage + --all-extras + --resolution ${{ matrix.resolution }} + + - env: UV_RESOLUTION: ${{ matrix.resolution == 'limited-dependencies' && 'highest' || matrix.resolution }} PARSONS_LIMITED_DEPENDENCIES: ${{ matrix.resolution == 'limited-dependencies' && 'TRUE' || 'FALSE' }} + run: >- + uv sync + --upgrade + --no-default-groups + --group test-coverage + --all-extras - id: cache-pytest uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 @@ -87,9 +100,9 @@ jobs: path: .pytest_cache key: pytest-${{ env.RUN_KEY }} - - run: pytest - env: + - env: COVERAGE_FILE: .coverage.${{ env.RUN_KEY }} + run: uv run pytest --failed-first - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f with: @@ -114,18 +127,23 @@ jobs: - uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 with: - python-version: '3.13' + python-version: 3.13 activate-environment: true cache-dependency-glob: ${{ env.CACHE_GLOBS }} - - run: uv pip install -r requirements-dev.txt + - run: >- + uv sync + --upgrade + --no-default-groups + --group ci + --all-extras - name: get-extras id: get-extras env: PARSONS_LIMITED_DEPENDENCIES: 'TRUE' run: | - python -c " + uv run python -c " import json from build.util import project_wheel_metadata from pathlib import Path @@ -162,6 +180,16 @@ jobs: egress-policy: block allowed-endpoints: ${{ env.ENDPOINT_WHITELIST}} + - id: pytest-run-key + run: | + echo "\ + RUN_KEY=\ + ${{ hashFiles('pyproject.toml') }}\ + -${{ matrix.os }}\ + -${{ matrix.python-version }}\ + -${{ matrix.resolution }}\ + " >> $GITHUB_ENV + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 @@ -170,19 +198,23 @@ jobs: activate-environment: true cache-dependency-glob: ${{ env.CACHE_GLOBS }} - - run: uv pip install -r requirements-dev.txt - - - run: uv pip install .[${{ matrix.extra }}] - env: - PARSONS_LIMITED_DEPENDENCIES: 'TRUE' + - env: + UV_RESOLUTION: ${{ matrix.resolution }} + PARSONS_LIMITED_DEPENDENCIES: 'TRUE' + run: >- + uv sync + --upgrade + --no-default-groups + --group test + --extra ${{ matrix.extra }} - id: cache-pytest uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 with: path: .pytest_cache - key: pytest-${{ hashFiles('pyproject.toml') }}-${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.resolution }} + key: pytest-${{ env.RUN_KEY }} - - run: pytest test/test_${{ matrix.extra }} + - run: uv run pytest test/test_${{ matrix.extra }} ruff-format: runs-on: ubuntu-latest @@ -198,20 +230,30 @@ jobs: - uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 with: - python-version: '3.13' + python-version: 3.13 activate-environment: true cache-dependency-glob: ${{ env.CACHE_GLOBS }} - - run: uv pip install -r requirements-dev.txt + - run: >- + uv sync + --upgrade + --no-default-groups + --group lint - id: cache-ruff uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 with: path: .ruff_cache - key: ruff-${{ hashFiles('pyproject.toml') }} + key: >- + ruff + -${{ hashFiles('pyproject.toml') }} - id: run-ruff - run: ruff format --diff . + run: >- + uv run + ruff format + --diff + . ruff-check: runs-on: ubuntu-latest @@ -230,20 +272,31 @@ jobs: - uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 with: - python-version: '3.13' + python-version: 3.13 activate-environment: true cache-dependency-glob: ${{ env.CACHE_GLOBS }} - - run: uv pip install -r requirements-dev.txt + - run: >- + uv sync + --upgrade + --no-default-groups + --group lint - id: cache-ruff uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 with: path: .ruff_cache - key: ruff-${{ hashFiles('pyproject.toml') }} + key: >- + ruff + -${{ hashFiles('pyproject.toml') }} - id: run-ruff-sarif - run: ruff check --output-format=sarif -o results.sarif . + run: >- + uv run + ruff check + --output-format=sarif + -o results.sarif + . - uses: github/codeql-action/upload-sarif@c793b717bc78562f491db7b0e93a3a178b099162 if: ( success() || failure() ) && contains('["success", "failure"]', steps.run-ruff-sarif.outcome) @@ -252,7 +305,11 @@ jobs: - id: run-ruff if: failure() && contains('["failure"]', steps.run-ruff-sarif.outcome) - run: ruff check --output-format=github . + run: >- + uv run + ruff check + --output-format=github + . bandit: runs-on: ubuntu-latest @@ -271,15 +328,25 @@ jobs: - uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 with: - python-version: '3.13' + python-version: 3.13 activate-environment: true cache-dependency-glob: ${{ env.CACHE_GLOBS }} - - run: uv pip install -r requirements-dev.txt + - run: >- + uv sync + --upgrade + --no-default-groups + --group security - id: run-bandit-sarif - run: | - bandit --confidence-level 'medium' --severity-level 'medium' --recursive 'parsons' --exclude '**/vendor/*' --format 'sarif' --output 'results.sarif' + run: >- + uv run + bandit + --confidence-level 'medium' + --severity-level 'medium' + --recursive 'parsons' + --format 'sarif' + --output 'results.sarif' - uses: github/codeql-action/upload-sarif@c793b717bc78562f491db7b0e93a3a178b099162 if: ( success() || failure() ) && contains('["success", "failure"]', steps.run-bandit-sarif.outcome) @@ -288,8 +355,12 @@ jobs: - id: run-bandit if: failure() && contains('["failure"]', steps.run-bandit-sarif.outcome) - run: | - bandit --confidence-level 'medium' --severity-level 'medium' --recursive 'parsons' --exclude '**/vendor/*' + run: >- + uv run + bandit + --confidence-level 'medium' + --severity-level 'medium' + --recursive 'parsons' coverage: runs-on: ubuntu-latest @@ -335,27 +406,25 @@ jobs: with: disable-sudo: true egress-policy: block - allowed-endpoints: > - files.pythonhosted.org:443 - github.com:443 - pypi.org:443 + allowed-endpoints: ${{ env.ENDPOINT_WHITELIST}} - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 + - uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 with: - python-version: '3.13' - cache: pip - - - uses: install-pinned/uv@259f91feb61b6e94766d7a1dbcd5f17335370e64 + python-version: 3.13 + activate-environment: true + cache-dependency-glob: ${{ env.CACHE_GLOBS }} - - run: | - uv pip install --system -e .[all] - uv pip install --system -r requirements-dev.txt + - run: >- + uv sync + --upgrade + --no-default-groups + --group docs - - run: sphinx-lint docs + - run: uv run sphinx-lint docs - - run: sphinx-lint parsons + - run: uv run sphinx-lint parsons pre-commit: runs-on: ubuntu-latest @@ -380,16 +449,28 @@ jobs: - uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 with: - python-version: '3.13' + python-version: 3.13 activate-environment: true cache-dependency-glob: ${{ env.CACHE_GLOBS }} - - run: uv pip install -r requirements-dev.txt + - run: >- + uv sync + --upgrade + --no-default-groups + --group pre-commit - id: cache-pre-commit uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 with: - path: ~/.cache/pre-commit - key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - - - run: pre-commit run --all-files --show-diff-on-failure --color=always + path: .pre-commit-cache + key: >- + pre-commit + -${{ hashFiles('.pre-commit-config.yaml') }} + -${{ hashFiles('pyproject.toml') }} + + - env: + PRE_COMMIT_HOME: .pre-commit-cache + run: >- + uv run + pre-commit run + --all-files diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e3095429c2..406a0bd924 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,33 +17,37 @@ jobs: build: runs-on: ubuntu-latest - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + env: + CACHE_GLOBS: | + **/pyproject.toml + **/requirements*.txt + **/setup.py + **/uv.lock - - name: Set up Python 3.13 - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 + steps: + - uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b with: - python-version: "3.13" - cache: pip + disable-sudo: true + egress-policy: audit - - name: Install uv - uses: install-pinned/uv@4d66dd6355704140c330bc2a2e6291cbf3bcb67f + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - - name: Install dependencies - run: | - uv pip install --system -r requirements-dev.txt - uv pip install --system -e .[all] + - uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 + with: + python-version: 3.13 + activate-environment: true + cache-dependency-glob: ${{ env.CACHE_GLOBS }} - - name: Check setup.py - run: | - python setup.py check + - run: >- + uv sync + --upgrade + --all-extras - - name: Build src dist - run: | - python -m build --sdist --outdir dist + - run: >- + uv build + --sdist - - name: Upload dist directory - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f + - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f with: name: parsons-dist path: dist/ @@ -67,32 +71,36 @@ jobs: runs-on: ${{ matrix.os }} steps: - - name: Download package - uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 + - uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b + with: + disable-sudo: true + egress-policy: audit + + - uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 with: name: parsons-dist path: dist - - name: Extract requirements for cache - shell: bash + - shell: bash run: | file=$(find ./dist -name 'parsons-*.tar.gz' | head -1) + # Extract to a temp directory first mkdir -p temp_extract tar -xzf "$file" -C temp_extract + # Find and move requirements files to root find temp_extract -name 'requirements.txt' -exec cp {} . \; 2>/dev/null || true + # Clean up rm -rf temp_extract - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 with: python-version: ${{ matrix.python-version }} cache: pip - - name: Install package - shell: bash + - shell: bash run: | file=$(find ./dist -name 'parsons-*.tar.gz' | head -1) pip install "$file" @@ -108,16 +116,19 @@ jobs: id-token: write # IMPORTANT: this permission is mandatory for trusted publishing steps: + - uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b + with: + disable-sudo: true + egress-policy: audit + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - - name: Download package - uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 + - uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 with: name: parsons-dist path: dist - - name: Publish - uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e + - uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e with: verify-metadata: true skip-existing: true diff --git a/.gitignore b/.gitignore index 5d9a4f8047..8419556e11 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ __pycache__/ *.so # Distribution / packaging +uv.lock .Python build/ develop-eggs/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1c5cc2b72d..ea3771f4c4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: ruff-format - repo: https://github.com/PyCQA/bandit - rev: 1.8.6 + rev: 1.9.4 hooks: - id: bandit args: ['--confidence-level', 'medium', '--severity-level', 'medium', '--exclude', '**/vendor/*'] diff --git a/Dockerfile b/Dockerfile index 335afcd86b..7ccf31cd9f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,17 +9,18 @@ FROM --platform=linux/amd64 python:3.11 # Much of this was pulled from examples at https://github.com/joyzoursky/docker-python-chromedriver # install google chrome -RUN wget -qO- https://dl.google.com/linux/linux_signing_key.pub \ +RUN wget -qO- https://dl.google.com/linux/linux_signing_key.pub \ | gpg --dearmor -o /etc/apt/keyrings/google.gpg; \ - chmod 0644 /etc/apt/keyrings/google.gpg; \ - echo 'deb [arch=amd64 signed-by=/etc/apt/keyrings/google.gpg] https://dl.google.com/linux/chrome/deb/ stable main' \ + chmod 0644 /etc/apt/keyrings/google.gpg; \ + echo 'deb [arch=amd64 signed-by=/etc/apt/keyrings/google.gpg] https://dl.google.com/linux/chrome/deb/ stable main' \ > /etc/apt/sources.list.d/google-chrome.list RUN apt-get -y update RUN apt-get install -y google-chrome-stable # install chromedriver RUN apt-get install -yqq unzip -RUN wget -O /tmp/chromedriver.zip http://chromedriver.storage.googleapis.com/`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE`/chromedriver_linux64.zip +RUN wget -O /tmp/chromedriver.zip \ + https://chromedriver.storage.googleapis.com/`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE`/chromedriver_linux64.zip RUN unzip /tmp/chromedriver.zip chromedriver -d /usr/local/bin/ # set display port to avoid crash @@ -30,20 +31,22 @@ ENV DISPLAY=:99 ################### RUN mkdir /src - -COPY requirements.txt /src/ -RUN pip install uv -RUN uv pip install --system -r /src/requirements.txt - COPY . /src/ WORKDIR /src -RUN python setup.py develop +# Install uv +COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ + +# Install parsons +RUN uv sync \ + --upgrade \ + --all-extras # The /app directory can house the scripts that will actually execute on this Docker image. # Eg. If using this image in a Civis container script, Civis will install your script repo # (from Github) to /app. RUN mkdir /app WORKDIR /app + # Useful for importing modules that are associated with your python scripts: ENV PYTHONPATH=.:/app diff --git a/pyproject.toml b/pyproject.toml index 1a055a158c..28b60dea9c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,9 @@ [build-system] -requires = [ "setuptools" ] +requires = [ + "build>=1.4.0", + "setuptools>=80.10.2", + "twine>=6.2.0", +] build-backend = "setuptools.build_meta" [project] @@ -38,6 +42,54 @@ issues = "https://github.com/move-coop/parsons/issues" github = "https://github.com/move-coop/parsons" repository = "https://github.com/move-coop/parsons.git" +[dependency-groups] +dev = [ + {include-group = "docs"}, + {include-group = "lint"}, + {include-group = "pre-commit"}, + {include-group = "security"}, + {include-group = "test-coverage"}, +] +ci = [ + "build>=1.4.0", +] +docs = [ + "furo~=2025.12.19", + "myst-parser~=4.0.1", + "packaging~=26.0", + "sphinxcontrib-googleanalytics~=0.5", + "sphinx-lint==1.0.2", + "sphinx-multiversion~=0.2.4", + "Sphinx~=7.4.7;python_version<'3.11'", + "Sphinx~=8.2.3;python_version>='3.11'", +] +lint = [ + "ruff==0.13.0", +] +pre-commit = [ + "pre-commit~=4.5.1", +] +security = [ + "bandit[sarif]==1.9.4", +] +test-coverage = [ + "coverage~=7.13.3", + "pytest-cov~=7.0.0", + {include-group = "test"}, +] +test = [ + "pytest~=8.4.2", + "pytest-datadir~=1.8.0", + "pytest-mock~=3.15.1", + "pytest-xdist~=3.8.0", + "requests-mock~=1.12.1", + "testfixtures~=8.3.0;python_version<'3.11'", + "testfixtures~=9.1.0;python_version>='3.11'", + "dbt-duckdb~=1.8.0", +] + +[tool] + [tool.setuptools] packages = ["parsons"] @@ -74,16 +126,10 @@ exclude = [ "venv", "**/vendor/*", ] - -# Default line length is 88 -# Changed to 100 to match previous pyproject black config line-length = 100 indent-width = 4 [tool.ruff.lint] -# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. -# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or -# McCabe complexity (`C901`) by default. select = [ "B", # flake8-bugbear (B) "E", # pycodestyle errors (E) @@ -125,8 +171,6 @@ ignore = [ "D407", # missing-dashed-underline-after-section "D408", # missing-section-underline-after-name ] - -# Allow fix for all enabled rules (when `--fix`) is provided. fixable = ["ALL"] unfixable = [] @@ -134,30 +178,11 @@ unfixable = [] convention = "numpy" [tool.ruff.format] -# Like Black, use double quotes for strings. quote-style = "double" - -# Like Black, indent with spaces, rather than tabs. indent-style = "space" - -# Like Black, respect magic trailing commas. skip-magic-trailing-comma = false - -# Like Black, automatically detect the appropriate line ending. line-ending = "auto" - -# Enable auto-formatting of code examples in docstrings. Markdown, -# reStructuredText code/literal blocks and doctests are all supported. -# -# This is currently disabled by default, but it is planned for this -# to be opt-out in the future. docstring-code-format = false - -# Set the line length limit used when formatting code snippets in -# docstrings. -# -# This only has an effect when the `docstring-code-format` setting is -# enabled. docstring-code-line-length = "dynamic" [tool.pytest.ini_options] diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index f4da542bfd..0000000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,29 +0,0 @@ -# Testing Requirements -bandit[sarif]==1.8.6 -coverage==7.13.3 -pre-commit==4.5.1 -pytest-cov==7.0.0 -pytest-datadir==1.8.0 -pytest-mock==3.15.1 -pytest-xdist==3.8.0 -pytest==8.4.2 -requests-mock==1.12.1 -ruff==0.13.0 -sphinx-lint==1.0.2 -testfixtures==8.3.0;python_version<'3.11' -testfixtures==9.1.0;python_version>='3.11' -dbt-duckdb>=1.8.0 - -# Build and publish requirements -build>=1.4.0 -setuptools>=80.10.2 -twine>=6.2.0 - -# Docs Requirements -furo~=2025.12.19 -myst-parser~=4.0.1 -packaging~=26.0 -Sphinx~=7.4.7;python_version<'3.11' -Sphinx~=8.2.3;python_version>='3.11' -sphinx-multiversion~=0.2.4 -sphinxcontrib-googleanalytics~=0.5