diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 48ce412c65..20ed1d80b7 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -52,7 +52,7 @@ Always reference these instructions first and fallback to search or bash command # Timeout: Use 60 seconds # E2E tests require Cypress download - may not work in restricted environments - # If available: pnpm test (takes variable time depending on tests) + # If available: pnpm test:e2e (takes variable time depending on tests) ``` 4. **Run Development Servers**: diff --git a/.github/workflows/check-backend.yaml b/.github/workflows/check-backend.yaml new file mode 100644 index 0000000000..6d9cc6b62b --- /dev/null +++ b/.github/workflows/check-backend.yaml @@ -0,0 +1,24 @@ +name: LintBackend + +on: [workflow_call] + +permissions: read-all + +jobs: + check-backend: + runs-on: ubuntu-latest + env: + BACKEND_DIR: ./backend + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/uv-python-install + name: Install Python, uv and Python dependencies + with: + extra-dependencies: --extra tests --extra mypy --extra custom-data + working-directory: ${{ env.BACKEND_DIR }} + - name: Lint backend + run: uv run scripts/lint.py + - name: Check backend formatting + run: uv run scripts/format.py --check + - name: Typecheck backend + run: uv run scripts/type-check.py diff --git a/.github/workflows/check-frontend.yaml b/.github/workflows/check-frontend.yaml new file mode 100644 index 0000000000..88a0d4229e --- /dev/null +++ b/.github/workflows/check-frontend.yaml @@ -0,0 +1,19 @@ +name: Lint frontend & libs + +on: [workflow_call] + +permissions: read-all + +jobs: + check-frontend: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/pnpm-node-install + name: Install Node, pnpm and dependencies. + - name: Lint frontend & libs + run: pnpm lint + - name: Check frontend & libs formatting + run: pnpm format + - name: Typecheck frontend & libs + run: pnpm type-check diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a0f1b80279..d3b95ed2d2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -18,23 +18,23 @@ on: permissions: read-all jobs: - pytest: - uses: ./.github/workflows/pytest.yaml + check-backend: + uses: ./.github/workflows/check-backend.yaml secrets: inherit - lint-backend: - uses: ./.github/workflows/lint-backend.yaml + check-frontend: + uses: ./.github/workflows/check-frontend.yaml + secrets: inherit + tests: + uses: ./.github/workflows/tests.yaml secrets: inherit e2e-tests: uses: ./.github/workflows/e2e-tests.yaml secrets: inherit - lint-ui: - uses: ./.github/workflows/lint-ui.yaml - secrets: inherit ci: runs-on: ubuntu-latest name: Run CI if: always() # This ensures the job always runs - needs: [lint-backend, pytest, lint-ui, e2e-tests] + needs: [check-backend, check-frontend, tests, e2e-tests] steps: # Propagate failure - name: Check dependent jobs diff --git a/.github/workflows/e2e-tests.yaml b/.github/workflows/e2e-tests.yaml index 9c509a13b8..491c3317c3 100644 --- a/.github/workflows/e2e-tests.yaml +++ b/.github/workflows/e2e-tests.yaml @@ -5,7 +5,7 @@ on: [workflow_call] permissions: read-all jobs: - ci: + e2e-tests: runs-on: ${{ matrix.os }} strategy: matrix: @@ -31,7 +31,7 @@ jobs: - name: Run tests env: CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} - run: pnpm test + run: pnpm test:e2e shell: bash - name: Upload screenshots uses: actions/upload-artifact@v4 diff --git a/.github/workflows/lint-backend.yaml b/.github/workflows/lint-backend.yaml deleted file mode 100644 index e6628819a6..0000000000 --- a/.github/workflows/lint-backend.yaml +++ /dev/null @@ -1,32 +0,0 @@ -name: LintBackend - -on: [workflow_call] - -permissions: read-all - -jobs: - lint-backend: - runs-on: ubuntu-latest - env: - BACKEND_DIR: ./backend - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/uv-python-install - name: Install Python, uv and Python dependencies - with: - extra-dependencies: --extra tests --extra mypy --extra custom-data - working-directory: ${{ env.BACKEND_DIR }} - - name: Lint with ruff - uses: astral-sh/ruff-action@v1 - with: - src: ${{ env.BACKEND_DIR }} - changed-files: "true" - - name: Check formatting with ruff - uses: astral-sh/ruff-action@v1 - with: - src: ${{ env.BACKEND_DIR }} - changed-files: "true" - args: "format --check" - - name: Run Mypy - run: uv run --no-project mypy chainlit/ tests/ - working-directory: ${{ env.BACKEND_DIR }} diff --git a/.github/workflows/lint-ui.yaml b/.github/workflows/lint-ui.yaml deleted file mode 100644 index c4562def0b..0000000000 --- a/.github/workflows/lint-ui.yaml +++ /dev/null @@ -1,17 +0,0 @@ -name: LintUI - -on: [workflow_call] - -permissions: read-all - -jobs: - ci: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/pnpm-node-install - name: Install Node, pnpm and dependencies. - - name: Build UI - run: pnpm run buildUi - - name: Lint UI - run: pnpm run lintUi diff --git a/.github/workflows/pytest.yaml b/.github/workflows/tests.yaml similarity index 74% rename from .github/workflows/pytest.yaml rename to .github/workflows/tests.yaml index cbf0f08fab..dc95650e30 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/tests.yaml @@ -5,7 +5,7 @@ on: [workflow_call] permissions: read-all jobs: - pytest: + tests: runs-on: ubuntu-latest strategy: matrix: @@ -21,10 +21,7 @@ jobs: with: python-version: ${{ matrix.python-version }} extra-dependencies: --extra tests --extra mypy --extra custom-data - working-directory: ${{ env.BACKEND_DIR }} - - name: Build UI components - run: pnpm run buildUi - timeout-minutes: 5 - - name: Run Pytest + - name: Run backend tests run: uv run --no-project pytest --cov=chainlit/ - working-directory: ${{ env.BACKEND_DIR }} + - name: Run frontend tests + run: pnpm run test diff --git a/.gitignore b/.gitignore index f2272e91eb..fe6668e33c 100644 --- a/.gitignore +++ b/.gitignore @@ -59,7 +59,6 @@ dist-ssr .aider* .coverage -backend/README.md -backend/.dmypy.json +.dmypy.json .history diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9d4e088bb5..c488429685 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,13 +15,12 @@ I've copy/pasted the whole document there, and then formatted it with prettier. - [Requirements](#requirements) - [Set up the repo](#set-up-the-repo) - [Install dependencies](#install-dependencies) - - [Build Frontend](#build-frontend) - [Start the Chainlit server from source](#start-the-chainlit-server-from-source) - [Start the UI from source](#start-the-ui-from-source) - [Run the tests](#run-the-tests) - [Backend unit tests](#backend-unit-tests) + - [Frontend unit tests](#frontend-unit-tests) - [E2E tests](#e2e-tests) - - [Headed/debugging](#headeddebugging) ## Local setup @@ -116,6 +115,14 @@ cd backend uv run pytest --cov=chainlit ``` +### Frontend unit tests + +This will run the frontend's unit tests. + +``` +pnpm test +``` + ### E2E tests You may need additional configuration or dependency installation to run Cypress. See the [Cypress system requirements](https://docs.cypress.io/app/get-started/install-cypress#System-requirements) for details. @@ -124,12 +131,12 @@ This will run end to end tests, assessing both the frontend, the backend and the ```sh // from root -pnpm test // will do cypress run -pnpm test -- --spec cypress/e2e/copilot // will run single test with the name copilot -pnpm test -- --spec "cypress/e2e/copilot,cypress/e2e/data_layer" // will run two tests with the names copilot and data_layer -pnpm test -- --spec "cypress/e2e/**/async-*" // will run all async tests -pnpm test -- --spec "cypress/e2e/**/sync-*" // will run all sync tests -pnpm test -- --spec "cypress/e2e/**/spec.cy.ts" // will run all usual tests +pnpm test:e2e // will do cypress run +pnpm test:e2e -- --spec cypress/e2e/copilot // will run single test with the name copilot +pnpm test:e2e -- --spec "cypress/e2e/copilot,cypress/e2e/data_layer" // will run two tests with the names copilot and data_layer +pnpm test:e2e -- --spec "cypress/e2e/**/async-*" // will run all async tests +pnpm test:e2e -- --spec "cypress/e2e/**/sync-*" // will run all sync tests +pnpm test:e2e -- --spec "cypress/e2e/**/spec.cy.ts" // will run all usual tests ``` (Go grab a cup of something, this will take a while.) @@ -137,18 +144,9 @@ pnpm test -- --spec "cypress/e2e/**/spec.cy.ts" // will run all usual tests For debugging purposes, you can use the **interactive mode** (Cypress UI). Run: ``` -pnpm test:interactive // runs `cypress open` +pnpm test:e2e:interactive // runs `cypress open` ``` Once you create a pull request, the tests will automatically run. It is a good practice to run the tests locally before pushing. -Make sure to run `uv sync` again whenever you've updated the frontend! - -### Headed/debugging - -Causes the Electron browser to be shown on screen and keeps it open after tests are done. -Extremely useful for debugging! - -```sh -SINGLE_TEST=password_auth CYPRESS_OPTIONS='--headed --no-exit' pnpm test -``` \ No newline at end of file +Make sure to run `uv sync` again whenever you've updated the frontend! \ No newline at end of file diff --git a/backend/build.py b/backend/build.py index a5c294cdc7..9103257d6e 100644 --- a/backend/build.py +++ b/backend/build.py @@ -32,7 +32,7 @@ def pnpm_install(project_root: pathlib.Path, pnpm_path: str): def pnpm_buildui(project_root: pathlib.Path, pnpm_path: str): - run_subprocess([pnpm_path, "buildUi"], project_root) + run_subprocess([pnpm_path, "build"], project_root) def copy_directory(src: pathlib.Path, dst: pathlib.Path, description: str): diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 037c254bfc..484cd5692b 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -87,7 +87,7 @@ tests = [ "moto>=5.0.14,<6.0.0", ] dev = [ - "ruff>=0.9.0,<1.0.0", + "ruff>=0.13.3,<1.0.0", ] mypy = [ "mypy>=1.13,<2.0.0", @@ -164,7 +164,7 @@ testpaths = ["tests"] asyncio_mode = "auto" [tool.ruff] -target-version = "py39" +target-version = "py310" [tool.ruff.lint] select = [ diff --git a/frontend/package.json b/frontend/package.json index 67f24e62fb..e410179440 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -7,10 +7,10 @@ "dev": "vite", "build": "vite build", "preview": "vite preview", - "lint": "eslint ./src --ext .ts,.tsx && tsc --noemit", - "format": "prettier 'src/**/*.{ts,tsx,css}' --write", - "test": "vitest run", - "prepublishOnly": "pnpm run build && pnpm test" + "lint": "eslint ./src --ext .ts,.tsx", + "format": "prettier 'src/**/*.{ts,tsx,css}'", + "type-check": "tsc --noemit", + "test": "vitest run" }, "dependencies": { "@chainlit/react-client": "workspace:^", diff --git a/libs/copilot/package.json b/libs/copilot/package.json index 4d0bc78389..97b5939761 100644 --- a/libs/copilot/package.json +++ b/libs/copilot/package.json @@ -8,7 +8,9 @@ "build": "vite build", "preview": "vite preview", "lint": "eslint ./src --ext .ts,.tsx", - "format": "prettier 'src/**/*.{ts,tsx}' --write", + "format": "prettier 'src/**/*.{ts,tsx}'", + "type-check": "tsc --noemit", + "test": "echo no tests yet", "storybook": "storybook dev -p 6006", "build-storybook": "storybook build" }, diff --git a/libs/react-client/package.json b/libs/react-client/package.json index 528d615de7..d8e507cf3a 100644 --- a/libs/react-client/package.json +++ b/libs/react-client/package.json @@ -3,10 +3,12 @@ "description": "Websocket client to connect to your chainlit app.", "version": "0.3.0", "scripts": { - "build": "tsup src/index.ts --tsconfig tsconfig.build.json --clean --format esm,cjs --dts --external react --external recoil --minify --sourcemap --treeshake", + "preinstall": "npx only-allow pnpm", "dev": "tsup src/index.ts --clean --format esm,cjs --dts --external react --external recoil --minify --sourcemap --treeshake", - "lint": "eslint ./src --ext ts,tsx --report-unused-disable-directives --max-warnings 0 && tsc --noemit", - "format": "prettier '**/*.{ts,tsx}' --write", + "build": "tsup src/index.ts --tsconfig tsconfig.build.json --clean --format esm,cjs --dts --external react --external recoil --minify --sourcemap --treeshake", + "lint": "eslint ./src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "format": "prettier '**/*.{ts,tsx}'", + "type-check": "tsc --noemit", "test": "echo no tests yet" }, "repository": { diff --git a/lint-staged.config.js b/lint-staged.config.js index 4ccf50985b..279cfb3de4 100644 --- a/lint-staged.config.js +++ b/lint-staged.config.js @@ -1,11 +1,11 @@ // eslint-disable-next-line no-undef module.exports = { - '**/*.{js,jsx,ts,tsx}': ['npx prettier --write', 'npx eslint --fix'], - '**/*.{ts,tsx}': [() => 'tsc --skipLibCheck --noEmit'], + '**/*.{js,jsx,ts,tsx}': ['pnpm lint --fix', 'pnpm format --fix'], + '**/*.{ts,tsx}': [() => 'pnpm type-check'], '**/*.py': [ - 'uv run --project backend ruff check --fix', - 'uv run --project backend ruff format', - () => 'pnpm run lintPython' + 'uv run scripts/lint.py', + 'uv run scripts/format.py --check', + 'uv run scripts/type_check.py', ], '.github/workflows/**': ['actionlint'] }; diff --git a/package.json b/package.json index af44b763d6..31f2388c22 100644 --- a/package.json +++ b/package.json @@ -19,17 +19,14 @@ }, "scripts": { "preinstall": "npx only-allow pnpm", - "test": "cypress run", - "test:interactive": "cypress open", - "test:ui": "cd frontend && pnpm test", "prepare": "husky", - "lint": "pnpm run lintUi && pnpm run lintPython", - "lintUi": "pnpm run --parallel lint", - "formatUi": "pnpm run --parallel format", - "lintPython": "cd backend && uv run dmypy run -- chainlit/ tests/", - "formatPython": "black `git ls-files | grep '.py$'` && isort --profile=black .", - "build:libs": "cd libs/react-client && pnpm run build && cd ../copilot && pnpm run build", - "buildUi": "pnpm build:libs && cd frontend && pnpm run build" + "build": "pnpm run --recursive build", + "lint": "pnpm run --parallel lint", + "format": "pnpm run --parallel format", + "type-check": "pnpm run --parallel type-check", + "test": "pnpm run --recursive test", + "test:e2e": "cypress run", + "test:e2e:interactive": "cypress open" }, "pnpm": { "overrides": { diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000..cbf3b8f88a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,12 @@ +[dependency-groups] +backend = ["chainlit"] + +[tool.uv] +default-groups = ["backend"] +package = false + +[tool.uv.sources] +chainlit = { workspace = true } + +[tool.uv.workspace] +members = ["backend"] \ No newline at end of file diff --git a/scripts/format.py b/scripts/format.py new file mode 100644 index 0000000000..c41d1fb72c --- /dev/null +++ b/scripts/format.py @@ -0,0 +1,16 @@ +#!/usr/bin/env -S uv run +"""Test runner script for the project.""" + +import subprocess +import sys + + +def main(): + """Run pytest on the test suite.""" + cmd = ["ruff", "format"] + sys.argv[1:] + result = subprocess.run(cmd) + return result.returncode + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/lint.py b/scripts/lint.py new file mode 100644 index 0000000000..1192e155d8 --- /dev/null +++ b/scripts/lint.py @@ -0,0 +1,16 @@ +#!/usr/bin/env -S uv run +"""Test runner script for the project.""" + +import subprocess +import sys + + +def main(): + """Run pytest on the test suite.""" + cmd = ["ruff", "check"] + sys.argv[1:] + result = subprocess.run(cmd) + return result.returncode + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/type-check.py b/scripts/type-check.py new file mode 100644 index 0000000000..478b37141c --- /dev/null +++ b/scripts/type-check.py @@ -0,0 +1,16 @@ +#!/usr/bin/env -S uv run +"""Test runner script for the project.""" + +import subprocess +import sys + + +def main(): + """Run pytest on the test suite.""" + cmd = ["dmypy", "run", "--", "backend/"] + sys.argv[1:] + result = subprocess.run(cmd) + return result.returncode + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/backend/uv.lock b/uv.lock similarity index 99% rename from backend/uv.lock rename to uv.lock index 4d4517d255..74d8369dbb 100644 --- a/backend/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.10, <4.0.0" resolution-markers = [ "python_full_version >= '3.13'", @@ -9,6 +9,14 @@ resolution-markers = [ "python_full_version < '3.11'", ] +[manifest] +members = [ + "chainlit", +] + +[manifest.dependency-groups] +backend = [{ name = "chainlit", editable = "backend" }] + [[package]] name = "aiofiles" version = "24.1.0" @@ -679,7 +687,7 @@ wheels = [ [[package]] name = "chainlit" -source = { editable = "." } +source = { editable = "backend" } dependencies = [ { name = "aiofiles" }, { name = "asyncer" }, @@ -790,7 +798,7 @@ requires-dist = [ { name = "python-dotenv", specifier = ">=1.0.0,<2.0.0" }, { name = "python-multipart", specifier = ">=0.0.18,<1.0.0" }, { name = "python-socketio", specifier = ">=5.11.0,<6.0.0" }, - { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.9.0,<1.0.0" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.13.3,<1.0.0" }, { name = "semantic-kernel", marker = "extra == 'tests'", specifier = ">=1.24.0,<2.0.0" }, { name = "slack-bolt", marker = "extra == 'tests'", specifier = ">=1.18.1,<2.0.0" }, { name = "sqlalchemy", marker = "extra == 'custom-data'", specifier = ">=2.0.28,<3.0.0" }, @@ -4972,28 +4980,28 @@ wheels = [ [[package]] name = "ruff" -version = "0.12.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4a/45/2e403fa7007816b5fbb324cb4f8ed3c7402a927a0a0cb2b6279879a8bfdc/ruff-0.12.9.tar.gz", hash = "sha256:fbd94b2e3c623f659962934e52c2bea6fc6da11f667a427a368adaf3af2c866a", size = 5254702, upload-time = "2025-08-14T16:08:55.2Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/20/53bf098537adb7b6a97d98fcdebf6e916fcd11b2e21d15f8c171507909cc/ruff-0.12.9-py3-none-linux_armv6l.whl", hash = "sha256:fcebc6c79fcae3f220d05585229463621f5dbf24d79fdc4936d9302e177cfa3e", size = 11759705, upload-time = "2025-08-14T16:08:12.968Z" }, - { url = "https://files.pythonhosted.org/packages/20/4d/c764ee423002aac1ec66b9d541285dd29d2c0640a8086c87de59ebbe80d5/ruff-0.12.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:aed9d15f8c5755c0e74467731a007fcad41f19bcce41cd75f768bbd687f8535f", size = 12527042, upload-time = "2025-08-14T16:08:16.54Z" }, - { url = "https://files.pythonhosted.org/packages/8b/45/cfcdf6d3eb5fc78a5b419e7e616d6ccba0013dc5b180522920af2897e1be/ruff-0.12.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5b15ea354c6ff0d7423814ba6d44be2807644d0c05e9ed60caca87e963e93f70", size = 11724457, upload-time = "2025-08-14T16:08:18.686Z" }, - { url = "https://files.pythonhosted.org/packages/72/e6/44615c754b55662200c48bebb02196dbb14111b6e266ab071b7e7297b4ec/ruff-0.12.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d596c2d0393c2502eaabfef723bd74ca35348a8dac4267d18a94910087807c53", size = 11949446, upload-time = "2025-08-14T16:08:21.059Z" }, - { url = "https://files.pythonhosted.org/packages/fd/d1/9b7d46625d617c7df520d40d5ac6cdcdf20cbccb88fad4b5ecd476a6bb8d/ruff-0.12.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1b15599931a1a7a03c388b9c5df1bfa62be7ede6eb7ef753b272381f39c3d0ff", size = 11566350, upload-time = "2025-08-14T16:08:23.433Z" }, - { url = "https://files.pythonhosted.org/packages/59/20/b73132f66f2856bc29d2d263c6ca457f8476b0bbbe064dac3ac3337a270f/ruff-0.12.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d02faa2977fb6f3f32ddb7828e212b7dd499c59eb896ae6c03ea5c303575756", size = 13270430, upload-time = "2025-08-14T16:08:25.837Z" }, - { url = "https://files.pythonhosted.org/packages/a2/21/eaf3806f0a3d4c6be0a69d435646fba775b65f3f2097d54898b0fd4bb12e/ruff-0.12.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:17d5b6b0b3a25259b69ebcba87908496e6830e03acfb929ef9fd4c58675fa2ea", size = 14264717, upload-time = "2025-08-14T16:08:27.907Z" }, - { url = "https://files.pythonhosted.org/packages/d2/82/1d0c53bd37dcb582b2c521d352fbf4876b1e28bc0d8894344198f6c9950d/ruff-0.12.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:72db7521860e246adbb43f6ef464dd2a532ef2ef1f5dd0d470455b8d9f1773e0", size = 13684331, upload-time = "2025-08-14T16:08:30.352Z" }, - { url = "https://files.pythonhosted.org/packages/3b/2f/1c5cf6d8f656306d42a686f1e207f71d7cebdcbe7b2aa18e4e8a0cb74da3/ruff-0.12.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a03242c1522b4e0885af63320ad754d53983c9599157ee33e77d748363c561ce", size = 12739151, upload-time = "2025-08-14T16:08:32.55Z" }, - { url = "https://files.pythonhosted.org/packages/47/09/25033198bff89b24d734e6479e39b1968e4c992e82262d61cdccaf11afb9/ruff-0.12.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fc83e4e9751e6c13b5046d7162f205d0a7bac5840183c5beebf824b08a27340", size = 12954992, upload-time = "2025-08-14T16:08:34.816Z" }, - { url = "https://files.pythonhosted.org/packages/52/8e/d0dbf2f9dca66c2d7131feefc386523404014968cd6d22f057763935ab32/ruff-0.12.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:881465ed56ba4dd26a691954650de6ad389a2d1fdb130fe51ff18a25639fe4bb", size = 12899569, upload-time = "2025-08-14T16:08:36.852Z" }, - { url = "https://files.pythonhosted.org/packages/a0/bd/b614d7c08515b1428ed4d3f1d4e3d687deffb2479703b90237682586fa66/ruff-0.12.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:43f07a3ccfc62cdb4d3a3348bf0588358a66da756aa113e071b8ca8c3b9826af", size = 11751983, upload-time = "2025-08-14T16:08:39.314Z" }, - { url = "https://files.pythonhosted.org/packages/58/d6/383e9f818a2441b1a0ed898d7875f11273f10882f997388b2b51cb2ae8b5/ruff-0.12.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:07adb221c54b6bba24387911e5734357f042e5669fa5718920ee728aba3cbadc", size = 11538635, upload-time = "2025-08-14T16:08:41.297Z" }, - { url = "https://files.pythonhosted.org/packages/20/9c/56f869d314edaa9fc1f491706d1d8a47747b9d714130368fbd69ce9024e9/ruff-0.12.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f5cd34fabfdea3933ab85d72359f118035882a01bff15bd1d2b15261d85d5f66", size = 12534346, upload-time = "2025-08-14T16:08:43.39Z" }, - { url = "https://files.pythonhosted.org/packages/bd/4b/d8b95c6795a6c93b439bc913ee7a94fda42bb30a79285d47b80074003ee7/ruff-0.12.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f6be1d2ca0686c54564da8e7ee9e25f93bdd6868263805f8c0b8fc6a449db6d7", size = 13017021, upload-time = "2025-08-14T16:08:45.889Z" }, - { url = "https://files.pythonhosted.org/packages/c7/c1/5f9a839a697ce1acd7af44836f7c2181cdae5accd17a5cb85fcbd694075e/ruff-0.12.9-py3-none-win32.whl", hash = "sha256:cc7a37bd2509974379d0115cc5608a1a4a6c4bff1b452ea69db83c8855d53f93", size = 11734785, upload-time = "2025-08-14T16:08:48.062Z" }, - { url = "https://files.pythonhosted.org/packages/fa/66/cdddc2d1d9a9f677520b7cfc490d234336f523d4b429c1298de359a3be08/ruff-0.12.9-py3-none-win_amd64.whl", hash = "sha256:6fb15b1977309741d7d098c8a3cb7a30bc112760a00fb6efb7abc85f00ba5908", size = 12840654, upload-time = "2025-08-14T16:08:50.158Z" }, - { url = "https://files.pythonhosted.org/packages/ac/fd/669816bc6b5b93b9586f3c1d87cd6bc05028470b3ecfebb5938252c47a35/ruff-0.12.9-py3-none-win_arm64.whl", hash = "sha256:63c8c819739d86b96d500cce885956a1a48ab056bbcbc61b747ad494b2485089", size = 11949623, upload-time = "2025-08-14T16:08:52.233Z" }, +version = "0.13.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/8e/f9f9ca747fea8e3ac954e3690d4698c9737c23b51731d02df999c150b1c9/ruff-0.13.3.tar.gz", hash = "sha256:5b0ba0db740eefdfbcce4299f49e9eaefc643d4d007749d77d047c2bab19908e", size = 5438533, upload-time = "2025-10-02T19:29:31.582Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/33/8f7163553481466a92656d35dea9331095122bb84cf98210bef597dd2ecd/ruff-0.13.3-py3-none-linux_armv6l.whl", hash = "sha256:311860a4c5e19189c89d035638f500c1e191d283d0cc2f1600c8c80d6dcd430c", size = 12484040, upload-time = "2025-10-02T19:28:49.199Z" }, + { url = "https://files.pythonhosted.org/packages/b0/b5/4a21a4922e5dd6845e91896b0d9ef493574cbe061ef7d00a73c61db531af/ruff-0.13.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2bdad6512fb666b40fcadb65e33add2b040fc18a24997d2e47fee7d66f7fcae2", size = 13122975, upload-time = "2025-10-02T19:28:52.446Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/15649af836d88c9f154e5be87e64ae7d2b1baa5a3ef317cb0c8fafcd882d/ruff-0.13.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fc6fa4637284708d6ed4e5e970d52fc3b76a557d7b4e85a53013d9d201d93286", size = 12346621, upload-time = "2025-10-02T19:28:54.712Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/bcbccb8141305f9a6d3f72549dd82d1134299177cc7eaf832599700f95a7/ruff-0.13.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c9e6469864f94a98f412f20ea143d547e4c652f45e44f369d7b74ee78185838", size = 12574408, upload-time = "2025-10-02T19:28:56.679Z" }, + { url = "https://files.pythonhosted.org/packages/ce/19/0f3681c941cdcfa2d110ce4515624c07a964dc315d3100d889fcad3bfc9e/ruff-0.13.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5bf62b705f319476c78891e0e97e965b21db468b3c999086de8ffb0d40fd2822", size = 12285330, upload-time = "2025-10-02T19:28:58.79Z" }, + { url = "https://files.pythonhosted.org/packages/10/f8/387976bf00d126b907bbd7725219257feea58650e6b055b29b224d8cb731/ruff-0.13.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78cc1abed87ce40cb07ee0667ce99dbc766c9f519eabfd948ed87295d8737c60", size = 13980815, upload-time = "2025-10-02T19:29:01.577Z" }, + { url = "https://files.pythonhosted.org/packages/0c/a6/7c8ec09d62d5a406e2b17d159e4817b63c945a8b9188a771193b7e1cc0b5/ruff-0.13.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4fb75e7c402d504f7a9a259e0442b96403fa4a7310ffe3588d11d7e170d2b1e3", size = 14987733, upload-time = "2025-10-02T19:29:04.036Z" }, + { url = "https://files.pythonhosted.org/packages/97/e5/f403a60a12258e0fd0c2195341cfa170726f254c788673495d86ab5a9a9d/ruff-0.13.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:17b951f9d9afb39330b2bdd2dd144ce1c1335881c277837ac1b50bfd99985ed3", size = 14439848, upload-time = "2025-10-02T19:29:06.684Z" }, + { url = "https://files.pythonhosted.org/packages/39/49/3de381343e89364c2334c9f3268b0349dc734fc18b2d99a302d0935c8345/ruff-0.13.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6052f8088728898e0a449f0dde8fafc7ed47e4d878168b211977e3e7e854f662", size = 13421890, upload-time = "2025-10-02T19:29:08.767Z" }, + { url = "https://files.pythonhosted.org/packages/ab/b5/c0feca27d45ae74185a6bacc399f5d8920ab82df2d732a17213fb86a2c4c/ruff-0.13.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc742c50f4ba72ce2a3be362bd359aef7d0d302bf7637a6f942eaa763bd292af", size = 13444870, upload-time = "2025-10-02T19:29:11.234Z" }, + { url = "https://files.pythonhosted.org/packages/50/a1/b655298a1f3fda4fdc7340c3f671a4b260b009068fbeb3e4e151e9e3e1bf/ruff-0.13.3-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:8e5640349493b378431637019366bbd73c927e515c9c1babfea3e932f5e68e1d", size = 13691599, upload-time = "2025-10-02T19:29:13.353Z" }, + { url = "https://files.pythonhosted.org/packages/32/b0/a8705065b2dafae007bcae21354e6e2e832e03eb077bb6c8e523c2becb92/ruff-0.13.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6b139f638a80eae7073c691a5dd8d581e0ba319540be97c343d60fb12949c8d0", size = 12421893, upload-time = "2025-10-02T19:29:15.668Z" }, + { url = "https://files.pythonhosted.org/packages/0d/1e/cbe7082588d025cddbb2f23e6dfef08b1a2ef6d6f8328584ad3015b5cebd/ruff-0.13.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:6b547def0a40054825de7cfa341039ebdfa51f3d4bfa6a0772940ed351d2746c", size = 12267220, upload-time = "2025-10-02T19:29:17.583Z" }, + { url = "https://files.pythonhosted.org/packages/a5/99/4086f9c43f85e0755996d09bdcb334b6fee9b1eabdf34e7d8b877fadf964/ruff-0.13.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9cc48a3564423915c93573f1981d57d101e617839bef38504f85f3677b3a0a3e", size = 13177818, upload-time = "2025-10-02T19:29:19.943Z" }, + { url = "https://files.pythonhosted.org/packages/9b/de/7b5db7e39947d9dc1c5f9f17b838ad6e680527d45288eeb568e860467010/ruff-0.13.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1a993b17ec03719c502881cb2d5f91771e8742f2ca6de740034433a97c561989", size = 13618715, upload-time = "2025-10-02T19:29:22.527Z" }, + { url = "https://files.pythonhosted.org/packages/28/d3/bb25ee567ce2f61ac52430cf99f446b0e6d49bdfa4188699ad005fdd16aa/ruff-0.13.3-py3-none-win32.whl", hash = "sha256:f14e0d1fe6460f07814d03c6e32e815bff411505178a1f539a38f6097d3e8ee3", size = 12334488, upload-time = "2025-10-02T19:29:24.782Z" }, + { url = "https://files.pythonhosted.org/packages/cf/49/12f5955818a1139eed288753479ba9d996f6ea0b101784bb1fe6977ec128/ruff-0.13.3-py3-none-win_amd64.whl", hash = "sha256:621e2e5812b691d4f244638d693e640f188bacbb9bc793ddd46837cea0503dd2", size = 13455262, upload-time = "2025-10-02T19:29:26.882Z" }, + { url = "https://files.pythonhosted.org/packages/fe/72/7b83242b26627a00e3af70d0394d68f8f02750d642567af12983031777fc/ruff-0.13.3-py3-none-win_arm64.whl", hash = "sha256:9e9e9d699841eaf4c2c798fa783df2fabc680b72059a02ca0ed81c460bc58330", size = 12538484, upload-time = "2025-10-02T19:29:28.951Z" }, ] [[package]]