[
  {
    "path": ".cargo/config",
    "content": "[target.x86_64-apple-darwin]\nrustflags = [\n  \"-C\", \"link-arg=-undefined\",\n  \"-C\", \"link-arg=dynamic_lookup\",\n]\n\n[target.aarch64-apple-darwin]\nrustflags = [\n  \"-C\", \"link-arg=-undefined\",\n  \"-C\", \"link-arg=dynamic_lookup\",\n]\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [sansyrox] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\n#patreon: # Replace with a single Patreon username\nopen_collective: robyn_oss # Replace with a single Open Collective username\n#ko_fi: # Replace with a single Ko-fi username\n#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\n#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\n# liberapay: # Replace with a single Liberapay username\n# issuehunt: # Replace with a single IssueHunt username\n# otechie: # Replace with a single Otechie username\n# lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry\n# custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/blank_issue.md",
    "content": "---\nname: 📝 Blank Issue\nabout: Create a blank issue.\n---"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: 🐛 Bug Report\ndescription: Create a bug report\nlabels: [bug]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thank you for taking the time to fill out this bug report!\n        Please fill out the form below...\n  - type: textarea\n    id: description\n    attributes:\n      label: Bug Description\n      description: Please provide a clear and concise description of what the bug is, and what you would expect to happen.\n      placeholder: The bug is...\n    validations:\n      required: true\n  - type: textarea\n    id: reproduce\n    attributes:\n      label: Steps to Reproduce\n      description: Please provide the steps to reproduce this bug. You can include your code here if it is relevant.\n      placeholder: |\n        1.\n        2.\n        3.\n    validations:\n      required: false\n  - type: dropdown\n    id: os\n    attributes:\n      label: Your operating system\n      options:\n        - Windows\n        - MacOS\n        - Linux\n        - Other (specify below)\n    validations:\n      required: false\n  - type: dropdown\n    id: py_version\n    attributes:\n      label: Your Python version (`python --version`)\n      options:\n        - 3.9\n        - 3.10\n        - 3.11\n        - 3.12\n        - 3.13\n        - Other (specify below)\n    validations:\n      required: false\n  - type: dropdown\n    id: robyn_version\n    attributes:\n      label: Your Robyn version\n      options:\n        - main branch\n        - latest\n        - Other (specify below)\n    validations:\n      required: false\n  - type: textarea\n    id: additional-info\n    attributes:\n      label: Additional Info\n      description: Any additional info that you think might be useful or relevant to this bug\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: true\ncontact_links:\n  - name: Discord Community Support\n    url: https://discord.gg/rkERZ5eNU8\n    about: You can ask and answer questions here.\n  - name: Documentation\n    url: https://robyn.tech\n    about: The Robyn documentation.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: 💡 Feature request\nabout: Suggest an idea to improve Robyn\nlabels: [enhancement]\n---\n\n<!-- \nThank you for considering improving Robyn!\n\nPlease describe your idea in depth. If you're not sure what to write, imagine the following:\n  - How is this important to you? How would you use it?\n  - Can you think of any alternatives?\n  - Do you have any ideas about how it can be implemented? Are you willing/able to implement it? Do you need mentoring?\n-->"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# Keep GitHub Actions up to date with GitHub's Dependabot...\n# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot\n# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem\nversion: 2\nupdates:\n  - package-ecosystem: github-actions\n    directory: /\n    groups:\n      github-actions:\n        patterns:\n          - \"*\"  # Group all Actions updates into a single larger pull request\n    schedule:\n      interval: weekly\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "## Description\n\nThis PR fixes #\n\n## Summary\n\nThis PR does....\n\n<!--\nThank you for contributing to Robyn!\n\n-->\n\n## PR Checklist\n\nPlease ensure that:\n\n- [ ] The PR contains a descriptive title\n- [ ] The PR contains a descriptive summary of the changes\n- [ ] You build and test your changes before submitting a PR.\n- [ ] You have added relevant documentation\n- [ ] You have added relevant tests. We prefer integration tests wherever possible\n\n## Pre-Commit Instructions:\n\n- [ ] Ensure that you have run the [pre-commit hooks](https://github.com/sparckles/robyn#%EF%B8%8F-to-develop-locally) on your PR.\n\n"
  },
  {
    "path": ".github/workflows/codspeed.yml",
    "content": "name: codspeed-benchmarks\n\non:\n  push:\n    branches:\n      - \"main\" # or \"master\"\n  pull_request:\n  # `workflow_dispatch` allows CodSpeed to trigger backtest\n  # performance analysis in order to generate initial data.\n  workflow_dispatch:\n\n\njobs:\n  benchmarks:\n    runs-on: ubuntu-22.04\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Set up Python\n        uses: actions/setup-python@v6\n        with:\n          python-version: \"3.10\"\n\n      - name: Install uv\n        uses: astral-sh/setup-uv@v6\n        with:\n          version: \"0.7.5\"\n\n      - name: Install the project + deps.\n        run: |\n          echo \"# Syncing Project + Installing project.\"\n          uv sync --dev --group test --verbose\n\n      - name: Run benchmarks\n        uses: CodSpeedHQ/action@v3.5.0\n        with:\n          token: ${{ secrets.CODSPEED_TOKEN }}\n          run: uv run pytest integration_tests --codspeed\n\n"
  },
  {
    "path": ".github/workflows/lint-pr.yml",
    "content": "name: Lint PR\n\non:\n  pull_request:\n    branches: [main]\n\n\nenv:\n  UV_SYSTEM_PYTHON: 1\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v4\n    - name: Install uv\n      uses: astral-sh/setup-uv@v3\n\n    - uses: actions/setup-python@v6\n      with:\n        python-version: \"3.11\"\n\n    - name: Install dependencies with uv\n      run: |\n        uv pip install ruff isort black\n    - name: Run linters\n      run: |\n        uv run ruff check .\n        uv run isort --check-only --diff .\n"
  },
  {
    "path": ".github/workflows/preview-deployments.yml",
    "content": "# CI to release the project for Linux, Windows, and MacOS\n# The purpose of this action is to verify if the release builds are working or not.\n\nname: Preview Release\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\n\nenv:\n  UV_SYSTEM_PYTHON: 1\n\njobs:\n  macos:\n    runs-on: macos-latest\n    strategy:\n      matrix:\n        python-version: [\"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\"]\n    steps:\n      - uses: actions/checkout@v4\n      - name: Install uv\n        uses: astral-sh/setup-uv@v3\n      - uses: actions/setup-python@v6\n        with:\n          python-version: ${{ matrix.python-version }}\n      - uses: dtolnay/rust-toolchain@stable\n        with:\n          targets: aarch64-apple-darwin\n      - name: Build wheels - x86_64\n        uses: PyO3/maturin-action@v1\n        with:\n          target: x86_64\n          args: -i python --release --out dist\n      - name: Build wheels - universal2\n        uses: PyO3/maturin-action@v1\n        with:\n          target: universal2-apple-darwin\n          args: -i python --release --out dist\n      - name: Install build wheel - universal2\n        run: |\n          uv pip install --force-reinstall dist/robyn*_universal2.whl\n          cd ~ && python -c 'import robyn'\n\n  windows:\n    runs-on: windows-latest\n    strategy:\n      matrix:\n        python-version: [\"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\"]\n        target: [x64, x86]\n    steps:\n      - uses: actions/checkout@v4\n      - name: Install uv\n        uses: astral-sh/setup-uv@v3\n      - uses: actions/setup-python@v6\n        with:\n          python-version: ${{ matrix.python-version }}\n          architecture: ${{ matrix.target }}\n      - uses: dtolnay/rust-toolchain@stable\n      - name: Build wheels\n        uses: PyO3/maturin-action@v1\n        with:\n          target: ${{ matrix.target }}\n          args: -i python --release --out dist\n      - name: Install build wheel\n        shell: bash\n        run: |\n          uv pip install --force-reinstall dist/robyn*.whl\n          cd ~ && python -c 'import robyn'\n\n  linux:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        python-version: [\"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\"]\n        target: [x86_64, i686]\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dtolnay/rust-toolchain@stable\n      - name: Install uv\n        uses: astral-sh/setup-uv@v3\n      - uses: actions/setup-python@v6\n        with:\n          python-version: ${{ matrix.python-version }}\n      - name: Build Wheels\n        uses: PyO3/maturin-action@v1\n        with:\n          target: ${{ matrix.target }}\n          manylinux: auto\n          args: -i python${{ matrix.python-version }} --release --out dist\n      - name: Install build wheel\n        if: matrix.target == 'x86_64'\n        run: |\n          uv pip install --force-reinstall dist/robyn*.whl\n          cd ~ && python -c 'import robyn'\n\n  linux-cross:\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        python:\n          [\n            { version: \"3.10\", abi: \"cp310-cp310\" },\n            { version: \"3.11\", abi: \"cp311-cp311\" },\n            { version: \"3.12\", abi: \"cp312-cp312\" },\n            { version: \"3.13\", abi: \"cp313-cp313\" },\n            { version: \"3.14\", abi: \"cp314-cp314\" },\n          ]\n        target: [aarch64, armv7]\n    steps:\n      - uses: actions/checkout@v3\n      - name: Build Wheels\n        uses: PyO3/maturin-action@v1\n        env:\n          PYO3_CROSS_LIB_DIR: /opt/python/${{ matrix.python.abi }}/lib\n        with:\n          target: ${{ matrix.target }}\n          manylinux: auto\n          args: -i python${{matrix.python.version}} --release --out dist\n      - uses: uraimo/run-on-arch-action@v2\n        name: Install build wheel\n        with:\n          arch: ${{ matrix.target }}\n          distro: ubuntu22.04\n          githubToken: ${{ github.token }}\n          # Mount the dist directory as /artifacts in the container\n          dockerRunArgs: |\n            --volume \"${PWD}/dist:/artifacts\"\n          install: |\n            apt update -y\n            apt install -y software-properties-common\n            add-apt-repository -y ppa:deadsnakes/ppa\n            apt update -y\n            apt install -y gcc musl-dev python3-dev python${{ matrix.python.version }} python${{ matrix.python.version }}-venv\n          run: |\n            ls -lrth /artifacts\n            python${{ matrix.python.version }} -m venv venv\n            source venv/bin/activate\n            python -m pip install --upgrade pip setuptools wheel\n            python -m pip install --force-reinstall /artifacts/robyn*.whl\n            cd ~ && python -c 'import robyn'\n"
  },
  {
    "path": ".github/workflows/python-CI.yml",
    "content": "# CI to test Robyn on major Linux, MacOS and Windows\n\non: [push, pull_request]\n\nname: Python Continuous integration\n\njobs:\n  tests:\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [\"windows\", \"ubuntu\", \"macos\"]\n        python-version: [\"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\"]\n    name: ${{ matrix.os }} tests with python ${{ matrix.python-version }}\n    runs-on: ${{ matrix.os }}-latest\n    steps:\n      - uses: actions/checkout@v4\n      - name: Set up Python ${{ matrix.python-version }}\n        uses: actions/setup-python@v6\n        with:\n          python-version: ${{ matrix.python-version }}\n      - name: Set up Nox\n        uses: wntrblm/nox@2024.03.02\n        with:\n          python-versions: ${{ matrix.python-version }}\n      - name: Test with Nox\n        run: nox --non-interactive --error-on-missing-interpreter -p ${{ matrix.python-version }}\n"
  },
  {
    "path": ".github/workflows/release-CI.yml",
    "content": "# CI to release the project for Linux, Windows, and MacOS\nname: Release CI\non:\n  push:\n    tags:\n      - v*\n  workflow_dispatch:\n    inputs:\n      enable_linux_cross:\n        description: 'Enable Linux cross-compilation (ARM64/ARMv7)'\n        required: false\n        default: true\n        type: boolean\n  \nenv:\n  UV_SYSTEM_PYTHON: 1\njobs:\n  macos:\n    runs-on: macos-latest\n    strategy:\n      matrix:\n        python-version: [\"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\"]\n    steps:\n      - uses: actions/checkout@v3\n      - name: Install uv\n        uses: astral-sh/setup-uv@v3\n      - uses: actions/setup-python@v6\n        with:\n          python-version: ${{ matrix.python-version }}\n      - uses: dtolnay/rust-toolchain@stable\n        with:\n          targets: aarch64-apple-darwin\n      - name: Build wheels - x86_64\n        uses: PyO3/maturin-action@v1\n        with:\n          target: x86_64\n          args: -i python --release --out dist\n      - name: Build wheels - universal2\n        uses: PyO3/maturin-action@v1\n        with:\n          target: universal2-apple-darwin\n          args: -i python --release --out dist\n      - name: Install build wheel - universal2\n        run: |\n          uv pip install --force-reinstall dist/robyn*_universal2.whl\n          cd ~ && python -c 'import robyn'\n      - name: Upload wheels\n        uses: actions/upload-artifact@v4\n        with:\n          name: wheels-${{ github.job }}-universal-${{ matrix.python-version }}\n          path: dist\n  windows:\n    runs-on: windows-latest\n    strategy:\n      matrix:\n        python-version: [\"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\"]\n        target: [x64, x86]\n    steps:\n      - uses: actions/checkout@v3\n      - name: Install uv\n        uses: astral-sh/setup-uv@v3\n      - uses: actions/setup-python@v6\n        with:\n          python-version: ${{ matrix.python-version }}\n          architecture: ${{ matrix.target }}\n      - uses: dtolnay/rust-toolchain@stable\n      - name: Build wheels\n        uses: PyO3/maturin-action@v1\n        with:\n          target: ${{ matrix.target }}\n          args: -i python --release --out dist\n      - name: Install build wheel\n        shell: bash\n        run: |\n          uv pip install --force-reinstall dist/robyn*.whl\n          cd ~ && python -c 'import robyn'\n      - name: Upload wheels\n        uses: actions/upload-artifact@v4\n        with:\n          name: wheels-${{ github.job }}-${{ matrix.target }}-${{ matrix.python-version }}\n          path: dist\n  linux:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        python-version: [\"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\"]\n        target: [x86_64, i686]\n    steps:\n      - uses: actions/checkout@v3\n      - uses: dtolnay/rust-toolchain@stable\n      - name: Install uv\n        uses: astral-sh/setup-uv@v3\n      - uses: actions/setup-python@v6\n        with:\n          python-version: ${{ matrix.python-version }}\n      - name: Build Wheels\n        uses: PyO3/maturin-action@v1\n        with:\n          target: ${{ matrix.target }}\n          manylinux: auto\n          args: -i python${{ matrix.python-version }} --release --out dist\n      - name: Install build wheel\n        if: matrix.target == 'x86_64'\n        run: |\n          uv pip install --force-reinstall dist/robyn*.whl\n          cd ~ && python -c 'import robyn'\n      - name: Upload wheels\n        uses: actions/upload-artifact@v4\n        with:\n          name: wheels-${{ github.job }}-${{ matrix.target }}-${{ matrix.python-version }}\n          path: dist\n  linux-cross:\n    runs-on: ubuntu-latest\n    if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.enable_linux_cross)\n    strategy:\n      matrix:\n        python:\n          [\n            { version: \"3.10\", abi: \"cp310-cp310\" },\n            { version: \"3.11\", abi: \"cp311-cp311\" },\n            { version: \"3.12\", abi: \"cp312-cp312\" },\n            { version: \"3.13\", abi: \"cp313-cp313\" },\n          ]\n        target: [aarch64, armv7]\n    steps:\n      - uses: actions/checkout@v3\n      - name: Build Wheels\n        uses: PyO3/maturin-action@v1\n        env:\n          PYO3_CROSS_LIB_DIR: /opt/python/${{ matrix.python.abi }}/lib\n        with:\n          target: ${{ matrix.target }}\n          manylinux: auto\n          maturin-version: \"v1.12.0\"\n          args: -i python${{matrix.python.version}} --release --out dist\n      - uses: uraimo/run-on-arch-action@v2\n        name: Install build wheel\n        with:\n          arch: ${{ matrix.target }}\n          distro: ubuntu22.04\n          githubToken: ${{ github.token }}\n          # Mount the dist directory as /artifacts in the container\n          dockerRunArgs: |\n            --volume \"${PWD}/dist:/artifacts\"\n          install: |\n            apt update -y\n            apt install -y software-properties-common\n            add-apt-repository -y ppa:deadsnakes/ppa\n            apt update -y\n            apt install -y gcc musl-dev python3-dev python${{ matrix.python.version }} python${{ matrix.python.version }}-venv\n          run: |\n            ls -lrth /artifacts\n            python${{ matrix.python.version }} -m venv venv\n            source venv/bin/activate\n            python -m pip install --upgrade pip setuptools wheel\n            python -m pip install --force-reinstall /artifacts/robyn*.whl\n            cd ~ && python -c 'import robyn'\n      - name: Upload wheels\n        uses: actions/upload-artifact@v4\n        with:\n          name: wheels-${{ github.job }}-${{ matrix.target }}-${{ matrix.python.version }}-${{ matrix.python.abi }}\n          path: dist\n  merge:\n    name: Building Single Artifact\n    runs-on: ubuntu-latest\n    needs: [macos, windows, linux, linux-cross]\n    if: always() && needs.macos.result == 'success' && needs.windows.result == 'success' && needs.linux.result == 'success' && (needs.linux-cross.result == 'success' || needs.linux-cross.result == 'skipped')\n    steps:\n      - name: Downloading all Artifacts\n        uses: actions/download-artifact@v4\n        with:\n          path: artifacts\n          pattern: wheels-*\n          merge-multiple: true\n      - run: |\n          echo \"Listing directories\"\n          ls -R\n      - name: Uploading Artifact's Bundle\n        uses: actions/upload-artifact@v4\n        with:\n          name: wheels\n          path: artifacts\n\n  release:\n    name: Release\n    runs-on: ubuntu-latest\n    needs: [macos, windows, linux, linux-cross, merge]\n    if: always() && needs.merge.result == 'success'\n    steps:\n      - uses: actions/download-artifact@v4\n        with:\n          name: wheels\n      - name: Install uv\n        uses: astral-sh/setup-uv@v3\n      - uses: actions/setup-python@v6\n        with:\n          python-version: 3.x\n      - name: Publish to PyPi\n        env:\n          TWINE_USERNAME: __token__\n          TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}\n        run: |\n          uv pip install --upgrade twine\n          twine upload --skip-existing *.whl\n"
  },
  {
    "path": ".github/workflows/rust-CI.yml",
    "content": "# CI to build, test, format, and lint the Rust code\n\non: [push, pull_request]\n\nname: Rust Continuous integration\n\njobs:\n  check:\n    name: Check\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dtolnay/rust-toolchain@stable\n      - run: cargo check\n\n  test:\n    name: Test Suite\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dtolnay/rust-toolchain@stable\n      - run: cargo test\n\n  fmt:\n    name: Rustfmt\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dtolnay/rust-toolchain@stable\n        with:\n          components: rustfmt\n      - run: cargo fmt --check\n\n  clippy:\n    name: Clippy\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dtolnay/rust-toolchain@stable\n        with:\n          components: clippy\n      - run: cargo clippy\n        # -- -D warnings\n\n"
  },
  {
    "path": ".gitignore",
    "content": ".python-version\n/target\n\n# ignore pre compiled binaries\n*.so\ndist/\n\n#python cache\n*__pycache__\n*tags\n\n# DS_Store\n*.DS_Store\n.vscode\n\n# pycharm\n.idea\n\n# python venv\n.venv\nvenv\nrobyn.code-workspace\nhello_robyn.py\nrobyn.env\n\n# nox\n.nox/\n\n# new docs dependencies\n# dependencies\ndocs_src/node_modules\ndocs_src/.pnp\ndocs_src/.pnp.js\n\n# testing\ndocs_src/coverage\n\n# next.js\ndocs_src/.next/\ndocs_src/out/\n\n# production\ndocs_src/build\n\n# misc\ndocs_src/.DS_Store\ndocs_src/*.pem\n\n# debug\ndocs_src/npm-debug.log*\ndocs_src/yarn-debug.log*\ndocs_src/yarn-error.log*\ndocs_src/.pnpm-debug.log*\n\n# local env files\ndocs_src/.env*.local\n\n# vercel\ndocs_src/.vercel\n\n# generated files\ndocs_src/public/rss/\n\n#https://github.com/PyO3/maturin/issues/2141\n*.pyd"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "repos:\n  - repo: https://github.com/astral-sh/ruff-pre-commit\n    rev: v0.14.13\n    hooks:\n      - id: ruff\n        args:\n          - --fix\n      - id: ruff-format\n\nci:\n  autoupdate_schedule: weekly\n"
  },
  {
    "path": ".well-known/funding-manifest-urls",
    "content": "https://robyn.tech/funding.json\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## [Unreleased](https://github.com/sparckles/robyn/tree/HEAD)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.26.1...HEAD)\n\n**Implemented enhancements:**\n\n- Allow empty returns on websocket handling [\\#1263](https://github.com/sparckles/robyn/issues/1263)\n\n**Closed issues:**\n\n- Payload reached size limit. [\\#463](https://github.com/sparckles/robyn/issues/463)\n- Proposal to rename `params` with `path_params` [\\#457](https://github.com/sparckles/robyn/issues/457)\n\n**Merged pull requests:**\n\n- feat: allow configurable payload sizes [\\#465](https://github.com/sparckles/robyn/pull/465) ([sansyrox](https://github.com/sansyrox))\n- docs: remove test pypi instructions from pr template [\\#462](https://github.com/sparckles/robyn/pull/462) ([sansyrox](https://github.com/sansyrox))\n- Rename params with path\\_params [\\#460](https://github.com/sparckles/robyn/pull/460) ([carlosm27](https://github.com/carlosm27))\n- feat: Implement global CORS [\\#458](https://github.com/sparckles/robyn/pull/458) ([sansyrox](https://github.com/sansyrox))\n\n## [v0.26.1](https://github.com/sparckles/robyn/tree/v0.26.1) (2023-04-05)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.26.0...v0.26.1)\n\n**Fixed bugs:**\n\n- Can't access new or updated route while on dev option [\\#439](https://github.com/sparckles/robyn/issues/439)\n\n**Closed issues:**\n\n- Add documentation for `robyn.env` file [\\#454](https://github.com/sparckles/robyn/issues/454)\n\n**Merged pull requests:**\n\n- Release v0.26.1 [\\#461](https://github.com/sparckles/robyn/pull/461) ([sansyrox](https://github.com/sansyrox))\n- \\[pre-commit.ci\\] pre-commit autoupdate [\\#459](https://github.com/sparckles/robyn/pull/459) ([pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci))\n- \\[pre-commit.ci\\] pre-commit autoupdate [\\#452](https://github.com/sparckles/robyn/pull/452) ([pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci))\n- docs: Add docs for v0.26.0 [\\#451](https://github.com/sparckles/robyn/pull/451) ([sansyrox](https://github.com/sansyrox))\n- fix\\(dev\\): fix hot reloading with dev flag [\\#446](https://github.com/sparckles/robyn/pull/446) ([AntoineRR](https://github.com/AntoineRR))\n\n## [v0.26.0](https://github.com/sparckles/robyn/tree/v0.26.0) (2023-03-24)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.25.0...v0.26.0)\n\n**Implemented enhancements:**\n\n- \\[Feature Request\\] Robyn providing Status Codes? [\\#423](https://github.com/sparckles/robyn/issues/423)\n- \\[Feature Request\\] Allow global level Response headers [\\#335](https://github.com/sparckles/robyn/issues/335)\n\n**Fixed bugs:**\n\n- \\[BUG\\] `uvloop` ModuleNotFoundError: No module named 'uvloop' on Ubuntu Docker Image [\\#395](https://github.com/sparckles/robyn/issues/395)\n\n**Closed issues:**\n\n- \\[Feature Request\\] When Robyn can have a middleware mechanism like flask or django [\\#350](https://github.com/sparckles/robyn/issues/350)\n- Forced shutdown locks console. \\[BUG\\] [\\#317](https://github.com/sparckles/robyn/issues/317)\n\n**Merged pull requests:**\n\n- \\[pre-commit.ci\\] pre-commit autoupdate [\\#449](https://github.com/sparckles/robyn/pull/449) ([pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci))\n- fix: Implement auto installation of uvloop on linux arm [\\#445](https://github.com/sparckles/robyn/pull/445) ([sansyrox](https://github.com/sansyrox))\n- chore: update rust dependencies [\\#444](https://github.com/sparckles/robyn/pull/444) ([AntoineRR](https://github.com/AntoineRR))\n- feat: Implement performance benchmarking [\\#443](https://github.com/sparckles/robyn/pull/443) ([sansyrox](https://github.com/sansyrox))\n- feat: expose request/connection info [\\#441](https://github.com/sparckles/robyn/pull/441) ([r3b-fish](https://github.com/r3b-fish))\n- Install the CodeSee workflow. [\\#438](https://github.com/sparckles/robyn/pull/438) ([codesee-maps[bot]](https://github.com/apps/codesee-maps))\n- \\[pre-commit.ci\\] pre-commit autoupdate [\\#437](https://github.com/sparckles/robyn/pull/437) ([pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci))\n- Replace integer status codes with Enum values of StatusCodes [\\#436](https://github.com/sparckles/robyn/pull/436) ([Noborita9](https://github.com/Noborita9))\n- added `star-history` [\\#434](https://github.com/sparckles/robyn/pull/434) ([hemangjoshi37a](https://github.com/hemangjoshi37a))\n- \\[pre-commit.ci\\] pre-commit autoupdate [\\#433](https://github.com/sparckles/robyn/pull/433) ([pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci))\n- feat: Robyn providing status codes [\\#429](https://github.com/sparckles/robyn/pull/429) ([carlosm27](https://github.com/carlosm27))\n- feat: Allow global level Response headers [\\#410](https://github.com/sparckles/robyn/pull/410) ([ParthS007](https://github.com/ParthS007))\n- feat: get rid of intermediate representations of requests and responses [\\#397](https://github.com/sparckles/robyn/pull/397) ([AntoineRR](https://github.com/AntoineRR))\n\n## [v0.25.0](https://github.com/sparckles/robyn/tree/v0.25.0) (2023-02-20)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.24.1...v0.25.0)\n\n**Implemented enhancements:**\n\n- using robyn with some frameworks [\\#420](https://github.com/sparckles/robyn/issues/420)\n\n**Fixed bugs:**\n\n- Template Rendering is not working in some browsers [\\#426](https://github.com/sparckles/robyn/issues/426)\n\n**Closed issues:**\n\n- \\[Feature Request\\] Show support for Python versions in the README [\\#396](https://github.com/sparckles/robyn/issues/396)\n- \\[BUG\\] The dev flag doesn't set the log level to DEBUG [\\#385](https://github.com/sparckles/robyn/issues/385)\n- \\[BUG\\] All tests are not passing on windows [\\#372](https://github.com/sparckles/robyn/issues/372)\n- \\[Feature Request\\] Add  views/view controllers [\\#221](https://github.com/sparckles/robyn/issues/221)\n\n**Merged pull requests:**\n\n- fix: Add proper headers to the templates return types [\\#427](https://github.com/sparckles/robyn/pull/427) ([sansyrox](https://github.com/sansyrox))\n- \\[pre-commit.ci\\] pre-commit autoupdate [\\#425](https://github.com/sparckles/robyn/pull/425) ([pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci))\n- docs: Add documentation for views [\\#424](https://github.com/sparckles/robyn/pull/424) ([sansyrox](https://github.com/sansyrox))\n- better way to compare type [\\#421](https://github.com/sparckles/robyn/pull/421) ([jmishra01](https://github.com/jmishra01))\n- style\\(landing\\_page\\): fix the style of github logo on the landing page [\\#419](https://github.com/sparckles/robyn/pull/419) ([sansyrox](https://github.com/sansyrox))\n- docs: improve readme [\\#418](https://github.com/sparckles/robyn/pull/418) ([AntoineRR](https://github.com/AntoineRR))\n- docs: add dark mode to website [\\#416](https://github.com/sparckles/robyn/pull/416) ([AntoineRR](https://github.com/AntoineRR))\n- chore: improve issue templates [\\#413](https://github.com/sparckles/robyn/pull/413) ([AntoineRR](https://github.com/AntoineRR))\n- \\[pre-commit.ci\\] pre-commit autoupdate [\\#412](https://github.com/sparckles/robyn/pull/412) ([pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci))\n- fix: fixed CONTRIBUTE.md link into docs/README.md file, changing it f… [\\#411](https://github.com/sparckles/robyn/pull/411) ([Kop3sh](https://github.com/Kop3sh))\n- chore\\(ci\\): fix rust ci warnings [\\#408](https://github.com/sparckles/robyn/pull/408) ([AntoineRR](https://github.com/AntoineRR))\n- feat: Add view controllers [\\#407](https://github.com/sparckles/robyn/pull/407) ([mikaeelghr](https://github.com/mikaeelghr))\n- Fix docs: support version [\\#404](https://github.com/sparckles/robyn/pull/404) ([Oluwaseun241](https://github.com/Oluwaseun241))\n- fix: Fix Windows tests [\\#402](https://github.com/sparckles/robyn/pull/402) ([sansyrox](https://github.com/sansyrox))\n- docs: Update PyPi metadata [\\#401](https://github.com/sparckles/robyn/pull/401) ([sansyrox](https://github.com/sansyrox))\n- fix\\(test\\): fix tests on windows [\\#400](https://github.com/sparckles/robyn/pull/400) ([AntoineRR](https://github.com/AntoineRR))\n- fix: various improvements around the dev flag [\\#388](https://github.com/sparckles/robyn/pull/388) ([AntoineRR](https://github.com/AntoineRR))\n\n## [v0.24.1](https://github.com/sparckles/robyn/tree/v0.24.1) (2023-02-09)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.24.0...v0.24.1)\n\n**Closed issues:**\n\n- \\[BUG\\] \\[Windows\\] Terminal hanging after Ctrl+C is pressed on Robyn server [\\#373](https://github.com/sparckles/robyn/issues/373)\n\n**Merged pull requests:**\n\n- \\[pre-commit.ci\\] pre-commit autoupdate [\\#394](https://github.com/sparckles/robyn/pull/394) ([pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci))\n- docs: add documentation regarding byte response [\\#392](https://github.com/sparckles/robyn/pull/392) ([sansyrox](https://github.com/sansyrox))\n- fix: fix terminal hijacking in windows [\\#391](https://github.com/sparckles/robyn/pull/391) ([sansyrox](https://github.com/sansyrox))\n- chore: fix requirements files and update packages [\\#389](https://github.com/sparckles/robyn/pull/389) ([AntoineRR](https://github.com/AntoineRR))\n- small correction in docs [\\#387](https://github.com/sparckles/robyn/pull/387) ([tkanhe](https://github.com/tkanhe))\n- \\[pre-commit.ci\\] pre-commit autoupdate [\\#384](https://github.com/sparckles/robyn/pull/384) ([pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci))\n- ci: build artifacts on every push and pull [\\#378](https://github.com/sparckles/robyn/pull/378) ([sansyrox](https://github.com/sansyrox))\n- test: organize and add tests [\\#377](https://github.com/sparckles/robyn/pull/377) ([AntoineRR](https://github.com/AntoineRR))\n- Changed Response to use body: bytes [\\#375](https://github.com/sparckles/robyn/pull/375) ([madhavajay](https://github.com/madhavajay))\n\n## [v0.24.0](https://github.com/sparckles/robyn/tree/v0.24.0) (2023-02-06)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.23.1...v0.24.0)\n\n**Closed issues:**\n\n- \\[BUG\\] Release builds are not working [\\#386](https://github.com/sparckles/robyn/issues/386)\n- \\[BUG\\] Can't send raw bytes [\\#374](https://github.com/sparckles/robyn/issues/374)\n\n## [v0.23.1](https://github.com/sparckles/robyn/tree/v0.23.1) (2023-01-30)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.23.0...v0.23.1)\n\n**Closed issues:**\n\n- \\[BUG\\] Return 500 status code when route is raising [\\#381](https://github.com/sparckles/robyn/issues/381)\n- \\[BUG\\] Return 404 status code when route isn't set [\\#376](https://github.com/sparckles/robyn/issues/376)\n- Add Appwrite as a sponsor in the README [\\#348](https://github.com/sparckles/robyn/issues/348)\n- \\[BUG\\] Get Stared failed on Windows [\\#340](https://github.com/sparckles/robyn/issues/340)\n- \\[BUG\\] Fix CI/CD pipeline [\\#310](https://github.com/sparckles/robyn/issues/310)\n\n**Merged pull requests:**\n\n- chore\\(ci\\): fix robyn installation in test CI [\\#383](https://github.com/sparckles/robyn/pull/383) ([AntoineRR](https://github.com/AntoineRR))\n- fix: return 500 status code when route raise [\\#382](https://github.com/sparckles/robyn/pull/382) ([AntoineRR](https://github.com/AntoineRR))\n- fix: return 404 status code when route isn't found [\\#380](https://github.com/sparckles/robyn/pull/380) ([AntoineRR](https://github.com/AntoineRR))\n- ci: enable precommit hooks on everything [\\#371](https://github.com/sparckles/robyn/pull/371) ([sansyrox](https://github.com/sansyrox))\n- chore: run tests on linux, macos and windows and release builds on ta… [\\#370](https://github.com/sparckles/robyn/pull/370) ([AntoineRR](https://github.com/AntoineRR))\n- docs: add appwrite logo as sponsors [\\#369](https://github.com/sparckles/robyn/pull/369) ([sansyrox](https://github.com/sansyrox))\n- test: improve pytest fixtures [\\#368](https://github.com/sparckles/robyn/pull/368) ([AntoineRR](https://github.com/AntoineRR))\n- Move pre-commit hooks to use Ruff [\\#364](https://github.com/sparckles/robyn/pull/364) ([patrick91](https://github.com/patrick91))\n\n## [v0.23.0](https://github.com/sparckles/robyn/tree/v0.23.0) (2023-01-21)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.22.1...v0.23.0)\n\n**Closed issues:**\n\n- \\[Feature Request\\] Improve the release and testing pipeline [\\#341](https://github.com/sparckles/robyn/issues/341)\n\n**Merged pull requests:**\n\n- ci: delete the test pypi workflow  [\\#367](https://github.com/sparckles/robyn/pull/367) ([sansyrox](https://github.com/sansyrox))\n- docs: Add page icon to index page [\\#365](https://github.com/sparckles/robyn/pull/365) ([Abdur-rahmaanJ](https://github.com/Abdur-rahmaanJ))\n- test: speed up tests [\\#362](https://github.com/sparckles/robyn/pull/362) ([AntoineRR](https://github.com/AntoineRR))\n- Replace the default port with 8080 [\\#352](https://github.com/sparckles/robyn/pull/352) ([sansyrox](https://github.com/sansyrox))\n\n## [v0.22.1](https://github.com/sparckles/robyn/tree/v0.22.1) (2023-01-16)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.22.0...v0.22.1)\n\n**Closed issues:**\n\n- \\[BUG\\] Python 3.11 error: metadata-generation-failed [\\#357](https://github.com/sparckles/robyn/issues/357)\n\n**Merged pull requests:**\n\n- ci: update precommit config [\\#361](https://github.com/sparckles/robyn/pull/361) ([sansyrox](https://github.com/sansyrox))\n- \\[pre-commit.ci\\] pre-commit autoupdate [\\#359](https://github.com/sparckles/robyn/pull/359) ([pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci))\n- chore\\(ci\\): add python 3.11 to the build and test CI [\\#358](https://github.com/sparckles/robyn/pull/358) ([AntoineRR](https://github.com/AntoineRR))\n- Updates prose to format code block and docs [\\#356](https://github.com/sparckles/robyn/pull/356) ([rachfop](https://github.com/rachfop))\n\n## [v0.22.0](https://github.com/sparckles/robyn/tree/v0.22.0) (2023-01-14)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.21.0...v0.22.0)\n\n**Closed issues:**\n\n- AttributeError: 'Robyn' object has no attribute 'headers'\\[BUG\\] [\\#353](https://github.com/sparckles/robyn/issues/353)\n- \\[Feature Request\\] Allow support for multiple file types [\\#344](https://github.com/sparckles/robyn/issues/344)\n- \\[Feature Request\\] Investigate if we need an unit tests for Python functions created in Rust [\\#311](https://github.com/sparckles/robyn/issues/311)\n- \\[Experimental Feature Request\\] Story driven programming [\\#258](https://github.com/sparckles/robyn/issues/258)\n\n**Merged pull requests:**\n\n- fix: windows support [\\#354](https://github.com/sparckles/robyn/pull/354) ([sansyrox](https://github.com/sansyrox))\n- fix: better handling of route return type [\\#349](https://github.com/sparckles/robyn/pull/349) ([AntoineRR](https://github.com/AntoineRR))\n\n## [v0.21.0](https://github.com/sparckles/robyn/tree/v0.21.0) (2023-01-06)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.20.0...v0.21.0)\n\n**Closed issues:**\n\n- \\[Feature Request\\] Support for image file type [\\#343](https://github.com/sparckles/robyn/issues/343)\n- Not able to see the added logs  [\\#342](https://github.com/sparckles/robyn/issues/342)\n- \\[Feature Request\\] Hope robyn can support returning f-string format [\\#338](https://github.com/sparckles/robyn/issues/338)\n- \\[Feature Request\\] Refactor Robyn response to allow objects other than strings [\\#336](https://github.com/sparckles/robyn/issues/336)\n- \\[BUG\\] Custom headers not sent when const=False [\\#323](https://github.com/sparckles/robyn/issues/323)\n- \\[Feature Request\\] Add documentation for custom template support in v0.19.0 [\\#321](https://github.com/sparckles/robyn/issues/321)\n- \\[BUG\\] Always need to return a string in a route [\\#305](https://github.com/sparckles/robyn/issues/305)\n\n**Merged pull requests:**\n\n- fix: fix the static file serving [\\#347](https://github.com/sparckles/robyn/pull/347) ([sansyrox](https://github.com/sansyrox))\n- feat: return Response from routes [\\#346](https://github.com/sparckles/robyn/pull/346) ([AntoineRR](https://github.com/AntoineRR))\n\n## [v0.20.0](https://github.com/sparckles/robyn/tree/v0.20.0) (2022-12-20)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.19.2...v0.20.0)\n\n**Closed issues:**\n\n- \\[Feature Request\\] Add an automated benchmark script [\\#320](https://github.com/sparckles/robyn/issues/320)\n\n**Merged pull requests:**\n\n- feat: allow non string types in response [\\#337](https://github.com/sparckles/robyn/pull/337) ([sansyrox](https://github.com/sansyrox))\n- feat: add an auto benchmark script [\\#329](https://github.com/sparckles/robyn/pull/329) ([AntoineRR](https://github.com/AntoineRR))\n\n## [v0.19.2](https://github.com/sparckles/robyn/tree/v0.19.2) (2022-12-14)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.19.1...v0.19.2)\n\n**Closed issues:**\n\n- \\[BUG\\] The --dev flag not working on Ubuntu 20.04 [\\#332](https://github.com/sparckles/robyn/issues/332)\n- \\[Feature Request\\] Allow the ability of sending the headers from the same route [\\#325](https://github.com/sparckles/robyn/issues/325)\n\n**Merged pull requests:**\n\n- fix: allow response headers  and fix headers not working in const requests [\\#331](https://github.com/sparckles/robyn/pull/331) ([sansyrox](https://github.com/sansyrox))\n- fix: factorizing code [\\#322](https://github.com/sparckles/robyn/pull/322) ([AntoineRR](https://github.com/AntoineRR))\n\n## [v0.19.1](https://github.com/sparckles/robyn/tree/v0.19.1) (2022-12-03)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.19.0...v0.19.1)\n\n## [v0.19.0](https://github.com/sparckles/robyn/tree/v0.19.0) (2022-12-02)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.18.3...v0.19.0)\n\n**Closed issues:**\n\n- \\[Feature Request\\] Allow the ability of sending the headers from the same route [\\#326](https://github.com/sparckles/robyn/issues/326)\n- \\[Feature Request\\] Allow the ability of sending the headers from the same route [\\#324](https://github.com/sparckles/robyn/issues/324)\n- \\[BUG\\] Error in Examples section in Documentation [\\#314](https://github.com/sparckles/robyn/issues/314)\n- \\[BUG\\] Wrong link for the blog post on Robyn [\\#306](https://github.com/sparckles/robyn/issues/306)\n- Add documentation about deployment [\\#93](https://github.com/sparckles/robyn/issues/93)\n- Add support for templates! [\\#10](https://github.com/sparckles/robyn/issues/10)\n\n**Merged pull requests:**\n\n- docs: update hosting docs [\\#319](https://github.com/sparckles/robyn/pull/319) ([sansyrox](https://github.com/sansyrox))\n- Various improvements around the index method [\\#318](https://github.com/sparckles/robyn/pull/318) ([AntoineRR](https://github.com/AntoineRR))\n- Add  Railway deployment process.  [\\#316](https://github.com/sparckles/robyn/pull/316) ([carlosm27](https://github.com/carlosm27))\n- docs: fix middleware section in examples [\\#315](https://github.com/sparckles/robyn/pull/315) ([sansyrox](https://github.com/sansyrox))\n- docs: fix blog link in website [\\#309](https://github.com/sparckles/robyn/pull/309) ([sansyrox](https://github.com/sansyrox))\n- Router refactor [\\#307](https://github.com/sparckles/robyn/pull/307) ([AntoineRR](https://github.com/AntoineRR))\n\n## [v0.18.3](https://github.com/sparckles/robyn/tree/v0.18.3) (2022-11-10)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.18.2...v0.18.3)\n\n**Closed issues:**\n\n- \\[BUG\\] `--log-level` not working [\\#300](https://github.com/sparckles/robyn/issues/300)\n- \\[Feature Request\\] Refactor Code to include better types [\\#254](https://github.com/sparckles/robyn/issues/254)\n\n**Merged pull requests:**\n\n- fix: log level not working [\\#303](https://github.com/sparckles/robyn/pull/303) ([sansyrox](https://github.com/sansyrox))\n- add route type enum [\\#299](https://github.com/sparckles/robyn/pull/299) ([suhailmalik07](https://github.com/suhailmalik07))\n\n## [v0.18.2](https://github.com/sparckles/robyn/tree/v0.18.2) (2022-11-05)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.18.1...v0.18.2)\n\n**Closed issues:**\n\n- \\[Feature Request?\\] Update `matchit` crate to the most recent version [\\#291](https://github.com/sparckles/robyn/issues/291)\n- \\[Feature Request\\] Add `@wraps` in route dectorators [\\#285](https://github.com/sparckles/robyn/issues/285)\n- \\[Feature Request\\] fix clippy issues [\\#265](https://github.com/sparckles/robyn/issues/265)\n\n**Merged pull requests:**\n\n- style: add logging for url port and host [\\#304](https://github.com/sparckles/robyn/pull/304) ([sansyrox](https://github.com/sansyrox))\n- fix config of port and url [\\#302](https://github.com/sparckles/robyn/pull/302) ([kimhyun5u](https://github.com/kimhyun5u))\n- update rust packages to latest [\\#298](https://github.com/sparckles/robyn/pull/298) ([suhailmalik07](https://github.com/suhailmalik07))\n- fix: retain metadata of the route functions [\\#295](https://github.com/sparckles/robyn/pull/295) ([sansyrox](https://github.com/sansyrox))\n- `SocketHeld::new` refactor [\\#294](https://github.com/sparckles/robyn/pull/294) ([Jamyw7g](https://github.com/Jamyw7g))\n\n## [v0.18.1](https://github.com/sparckles/robyn/tree/v0.18.1) (2022-10-23)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.18.0...v0.18.1)\n\n**Merged pull requests:**\n\n- fix: replaced match with if let [\\#293](https://github.com/sparckles/robyn/pull/293) ([Markaeus](https://github.com/Markaeus))\n-  Hotfix detecting robyn.env [\\#292](https://github.com/sparckles/robyn/pull/292) ([Shending-Help](https://github.com/Shending-Help))\n- fix: enable hot reload on windows [\\#290](https://github.com/sparckles/robyn/pull/290) ([guilefoylegaurav](https://github.com/guilefoylegaurav))\n\n## [v0.18.0](https://github.com/sparckles/robyn/tree/v0.18.0) (2022-10-12)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.17.5...v0.18.0)\n\n**Closed issues:**\n\n- \\[BUG\\] The --dev mode spawns more servers without clearing previous ones. [\\#249](https://github.com/sparckles/robyn/issues/249)\n- \\[Feature\\] Add support for Env variables and a robyn.yaml config file [\\#97](https://github.com/sparckles/robyn/issues/97)\n\n**Merged pull requests:**\n\n- testing env support [\\#288](https://github.com/sparckles/robyn/pull/288) ([Shending-Help](https://github.com/Shending-Help))\n- Feature add support for env variables [\\#286](https://github.com/sparckles/robyn/pull/286) ([Shending-Help](https://github.com/Shending-Help))\n- fix: add proper kill process to conftest. \\#249 [\\#278](https://github.com/sparckles/robyn/pull/278) ([guilefoylegaurav](https://github.com/guilefoylegaurav))\n\n## [v0.17.5](https://github.com/sparckles/robyn/tree/v0.17.5) (2022-09-14)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.17.4...v0.17.5)\n\n**Closed issues:**\n\n- \\[BUG\\] README.md Discord link is invalid [\\#272](https://github.com/sparckles/robyn/issues/272)\n- \\[Feature Request\\] Add Digital Ocean to list of sponsors in Robyn Docs [\\#270](https://github.com/sparckles/robyn/issues/270)\n- \\[Feature Request\\] Add PyCon USA lightning talk in resources section [\\#204](https://github.com/sparckles/robyn/issues/204)\n- \\[Feature Request\\] Add community/ resources section in Docs or README  [\\#203](https://github.com/sparckles/robyn/issues/203)\n- \\[Feature Request\\] Update the new architecture on the docs website [\\#191](https://github.com/sparckles/robyn/issues/191)\n- Add examples section [\\#101](https://github.com/sparckles/robyn/issues/101)\n\n**Merged pull requests:**\n\n- Don't run sync functions in pool [\\#282](https://github.com/sparckles/robyn/pull/282) ([JackThomson2](https://github.com/JackThomson2))\n- Add documentation of Adding GraphQL support | version 1 [\\#275](https://github.com/sparckles/robyn/pull/275) ([sansyrox](https://github.com/sansyrox))\n- docs: improve documentation [\\#269](https://github.com/sparckles/robyn/pull/269) ([sansyrox](https://github.com/sansyrox))\n\n## [v0.17.4](https://github.com/sparckles/robyn/tree/v0.17.4) (2022-08-25)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.17.3...v0.17.4)\n\n**Closed issues:**\n\n- \\[BUG?\\] Startup failure OSError: \\[WinError 87\\] The parameter is incorrect [\\#252](https://github.com/sparckles/robyn/issues/252)\n- \\[Feature Request\\] Add mypy for pyi\\(stubs\\) synchronisation [\\#226](https://github.com/sparckles/robyn/issues/226)\n- not working on mac/windows [\\#140](https://github.com/sparckles/robyn/issues/140)\n\n**Merged pull requests:**\n\n- Father, forgive me, for I am adding inline types. [\\#266](https://github.com/sparckles/robyn/pull/266) ([sansyrox](https://github.com/sansyrox))\n\n## [v0.17.3](https://github.com/sparckles/robyn/tree/v0.17.3) (2022-08-17)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.17.2...v0.17.3)\n\n**Merged pull requests:**\n\n- fix: parse int status code to str [\\#264](https://github.com/sparckles/robyn/pull/264) ([hougesen](https://github.com/hougesen))\n\n## [v0.17.2](https://github.com/sparckles/robyn/tree/v0.17.2) (2022-08-11)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.17.1...v0.17.2)\n\n**Fixed bugs:**\n\n- Cannot run Robyn on Windows [\\#139](https://github.com/sparckles/robyn/issues/139)\n\n**Closed issues:**\n\n- \\[BUG\\] Move away from circle ci [\\#240](https://github.com/sparckles/robyn/issues/240)\n- Migrate the community to discord [\\#239](https://github.com/sparckles/robyn/issues/239)\n- \\[Feature Request\\] Release on test pypi before releasing on the main PyPi [\\#224](https://github.com/sparckles/robyn/issues/224)\n- For 0.8.x [\\#75](https://github.com/sparckles/robyn/issues/75)\n- Add a layer of caching in front of router [\\#59](https://github.com/sparckles/robyn/issues/59)\n\n**Merged pull requests:**\n\n- Windows fix [\\#261](https://github.com/sparckles/robyn/pull/261) ([sansyrox](https://github.com/sansyrox))\n- ci: enable fail fast for faster response time in the pipelines [\\#260](https://github.com/sparckles/robyn/pull/260) ([sansyrox](https://github.com/sansyrox))\n- ci: add github actions to publish every PR on test pypi [\\#259](https://github.com/sparckles/robyn/pull/259) ([sansyrox](https://github.com/sansyrox))\n- Fix typo in README [\\#246](https://github.com/sparckles/robyn/pull/246) ([bartbroere](https://github.com/bartbroere))\n- chore\\(ci\\): move pytest from CircleCi to Github Actions [\\#241](https://github.com/sparckles/robyn/pull/241) ([AntoineRR](https://github.com/AntoineRR))\n\n## [v0.17.1](https://github.com/sparckles/robyn/tree/v0.17.1) (2022-07-19)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.17.0...v0.17.1)\n\n**Closed issues:**\n\n- \\[Feature Request\\] add clippy in ci [\\#236](https://github.com/sparckles/robyn/issues/236)\n- \\[BUG\\] Headers not available [\\#231](https://github.com/sparckles/robyn/issues/231)\n- \\[Feature Request\\] Add an all contributor bot in the README of the repo [\\#225](https://github.com/sparckles/robyn/issues/225)\n\n**Merged pull requests:**\n\n- Add Rust CI [\\#237](https://github.com/sparckles/robyn/pull/237) ([AntoineRR](https://github.com/AntoineRR))\n- Contributors added in Readme [\\#235](https://github.com/sparckles/robyn/pull/235) ([orvil1026](https://github.com/orvil1026))\n- fix external project link in README [\\#234](https://github.com/sparckles/robyn/pull/234) ([touilleMan](https://github.com/touilleMan))\n- fix: fix request headers not being propagated [\\#232](https://github.com/sparckles/robyn/pull/232) ([sansyrox](https://github.com/sansyrox))\n- Upgrade GitHub Actions and add Python 3.10 [\\#230](https://github.com/sparckles/robyn/pull/230) ([cclauss](https://github.com/cclauss))\n- OrbUp: Upgrade the CircleCI Orbs [\\#229](https://github.com/sparckles/robyn/pull/229) ([cclauss](https://github.com/cclauss))\n- CHANGELOG.md: Fix typo [\\#228](https://github.com/sparckles/robyn/pull/228) ([cclauss](https://github.com/cclauss))\n\n## [v0.17.0](https://github.com/sparckles/robyn/tree/v0.17.0) (2022-07-06)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.16.6...v0.17.0)\n\n**Closed issues:**\n\n- A refactor [\\#176](https://github.com/sparckles/robyn/issues/176)\n- \\[Proposal\\] Const Requests [\\#48](https://github.com/sparckles/robyn/issues/48)\n\n**Merged pull requests:**\n\n- Add a const router [\\#210](https://github.com/sparckles/robyn/pull/210) ([sansyrox](https://github.com/sansyrox))\n\n## [v0.16.6](https://github.com/sparckles/robyn/tree/v0.16.6) (2022-07-02)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.16.5...v0.16.6)\n\n## [v0.16.5](https://github.com/sparckles/robyn/tree/v0.16.5) (2022-07-01)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.16.4...v0.16.5)\n\n**Closed issues:**\n\n- \\[Feature Request\\] Add sponsors in the repo and website [\\#212](https://github.com/sparckles/robyn/issues/212)\n- \\[Feature Request\\] Add commitizen as a dev dependency [\\#211](https://github.com/sparckles/robyn/issues/211)\n- Add better logging [\\#158](https://github.com/sparckles/robyn/issues/158)\n- Remove freeport dependency [\\#151](https://github.com/sparckles/robyn/issues/151)\n- Add websocket support [\\#104](https://github.com/sparckles/robyn/issues/104)\n- Maintenance issue [\\#56](https://github.com/sparckles/robyn/issues/56)\n- Improve Readme [\\#4](https://github.com/sparckles/robyn/issues/4)\n\n**Merged pull requests:**\n\n- fix: Fixes the crashing dev mode [\\#218](https://github.com/sparckles/robyn/pull/218) ([sansyrox](https://github.com/sansyrox))\n- feat: add commitizen as a dev dependency [\\#216](https://github.com/sparckles/robyn/pull/216) ([sansyrox](https://github.com/sansyrox))\n- Isort imports [\\#205](https://github.com/sparckles/robyn/pull/205) ([sansyrox](https://github.com/sansyrox))\n- Add bridged logger. Improves performance substantially. [\\#201](https://github.com/sparckles/robyn/pull/201) ([sansyrox](https://github.com/sansyrox))\n- Adds pre-commit hooks for black, flake8, isort [\\#198](https://github.com/sparckles/robyn/pull/198) ([chrismoradi](https://github.com/chrismoradi))\n- Resolves port open issue when app is killed \\#183 [\\#196](https://github.com/sparckles/robyn/pull/196) ([anandtripathi5](https://github.com/anandtripathi5))\n- Removing unwraps [\\#195](https://github.com/sparckles/robyn/pull/195) ([sansyrox](https://github.com/sansyrox))\n\n## [v0.16.4](https://github.com/sparckles/robyn/tree/v0.16.4) (2022-05-30)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.16.3...v0.16.4)\n\n**Closed issues:**\n\n- \\[Feature Request\\] Remove extra logs [\\#200](https://github.com/sparckles/robyn/issues/200)\n- \\[Feature Request\\] Add precommit hook for black, flake8 and isort [\\#194](https://github.com/sparckles/robyn/issues/194)\n- \\[BUG\\] Get rid of Hashmap Clones and Unwraps! [\\#186](https://github.com/sparckles/robyn/issues/186)\n\n## [v0.16.3](https://github.com/sparckles/robyn/tree/v0.16.3) (2022-05-18)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.16.2...v0.16.3)\n\n**Closed issues:**\n\n- \\[BUG\\] Port not being free on app kill [\\#183](https://github.com/sparckles/robyn/issues/183)\n- Build failure [\\#166](https://github.com/sparckles/robyn/issues/166)\n\n## [v0.16.2](https://github.com/sparckles/robyn/tree/v0.16.2) (2022-05-09)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.16.1...v0.16.2)\n\n## [v0.16.1](https://github.com/sparckles/robyn/tree/v0.16.1) (2022-05-09)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.16.0...v0.16.1)\n\n**Closed issues:**\n\n- Add Python stubs [\\#130](https://github.com/sparckles/robyn/issues/130)\n\n**Merged pull requests:**\n\n- Setup types for Robyn [\\#192](https://github.com/sparckles/robyn/pull/192) ([sansyrox](https://github.com/sansyrox))\n- Fix build pipeline [\\#190](https://github.com/sparckles/robyn/pull/190) ([sansyrox](https://github.com/sansyrox))\n- fix typo :pencil2: in api docs. [\\#189](https://github.com/sparckles/robyn/pull/189) ([sombralibre](https://github.com/sombralibre))\n- Remove hashmap clones [\\#187](https://github.com/sparckles/robyn/pull/187) ([sansyrox](https://github.com/sansyrox))\n- Code clean up | Modularise rust code [\\#185](https://github.com/sparckles/robyn/pull/185) ([sansyrox](https://github.com/sansyrox))\n- Add experimental io-uring [\\#184](https://github.com/sparckles/robyn/pull/184) ([sansyrox](https://github.com/sansyrox))\n- Implement Response headers [\\#179](https://github.com/sparckles/robyn/pull/179) ([sansyrox](https://github.com/sansyrox))\n- Code cleanup [\\#178](https://github.com/sparckles/robyn/pull/178) ([sansyrox](https://github.com/sansyrox))\n\n## [v0.16.0](https://github.com/sparckles/robyn/tree/v0.16.0) (2022-04-29)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.15.1...v0.16.0)\n\n**Closed issues:**\n\n- \\[Feature Request\\] Add list of sponsors on the project website  [\\#182](https://github.com/sparckles/robyn/issues/182)\n- Optional build feature for io\\_uring [\\#177](https://github.com/sparckles/robyn/issues/177)\n- Create Custom headers for the response. [\\#174](https://github.com/sparckles/robyn/issues/174)\n\n## [v0.15.1](https://github.com/sparckles/robyn/tree/v0.15.1) (2022-03-24)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.15.0...v0.15.1)\n\n**Closed issues:**\n\n- Add middleware support [\\#95](https://github.com/sparckles/robyn/issues/95)\n\n**Merged pull requests:**\n\n- Make websocket id accessible [\\#173](https://github.com/sparckles/robyn/pull/173) ([sansyrox](https://github.com/sansyrox))\n- Use Clippy tool optimized code [\\#171](https://github.com/sparckles/robyn/pull/171) ([mrxiaozhuox](https://github.com/mrxiaozhuox))\n- Modify headers [\\#170](https://github.com/sparckles/robyn/pull/170) ([sansyrox](https://github.com/sansyrox))\n- Update README.md [\\#168](https://github.com/sparckles/robyn/pull/168) ([Polokghosh53](https://github.com/Polokghosh53))\n\n## [v0.15.0](https://github.com/sparckles/robyn/tree/v0.15.0) (2022-03-17)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.14.0...v0.15.0)\n\n**Closed issues:**\n\n- \\[BUG\\] Unable to modify headers in middlewares [\\#167](https://github.com/sparckles/robyn/issues/167)\n- Add Pycon talk link to docs [\\#102](https://github.com/sparckles/robyn/issues/102)\n\n## [v0.14.0](https://github.com/sparckles/robyn/tree/v0.14.0) (2022-03-03)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.13.1...v0.14.0)\n\n**Fixed bugs:**\n\n- Build error [\\#161](https://github.com/sparckles/robyn/issues/161)\n\n**Merged pull requests:**\n\n- Implement Custom Response objects. [\\#165](https://github.com/sparckles/robyn/pull/165) ([sansyrox](https://github.com/sansyrox))\n- Remove deprecated endpoints [\\#162](https://github.com/sparckles/robyn/pull/162) ([sansyrox](https://github.com/sansyrox))\n- Fix: default url param in app.start [\\#160](https://github.com/sparckles/robyn/pull/160) ([sansyrox](https://github.com/sansyrox))\n- Add middlewares [\\#157](https://github.com/sparckles/robyn/pull/157) ([sansyrox](https://github.com/sansyrox))\n- Remove arc\\(ing\\) [\\#156](https://github.com/sparckles/robyn/pull/156) ([sansyrox](https://github.com/sansyrox))\n\n## [v0.13.1](https://github.com/sparckles/robyn/tree/v0.13.1) (2022-02-19)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.13.0...v0.13.1)\n\n## [v0.13.0](https://github.com/sparckles/robyn/tree/v0.13.0) (2022-02-15)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.12.1...v0.13.0)\n\n## [v0.12.1](https://github.com/sparckles/robyn/tree/v0.12.1) (2022-02-13)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.12.0...v0.12.1)\n\n**Closed issues:**\n\n- \\[BUG\\] Default URL cannot be assigned [\\#159](https://github.com/sparckles/robyn/issues/159)\n- Upcoming release\\(s\\) [\\#141](https://github.com/sparckles/robyn/issues/141)\n\n## [v0.12.0](https://github.com/sparckles/robyn/tree/v0.12.0) (2022-01-21)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.11.1...v0.12.0)\n\n**Closed issues:**\n\n- Consider adding startup events [\\#153](https://github.com/sparckles/robyn/issues/153)\n- Remove poetry dependency [\\#150](https://github.com/sparckles/robyn/issues/150)\n\n**Merged pull requests:**\n\n- Add Event handlers [\\#154](https://github.com/sparckles/robyn/pull/154) ([sansyrox](https://github.com/sansyrox))\n- Remove poetry [\\#152](https://github.com/sparckles/robyn/pull/152) ([sansyrox](https://github.com/sansyrox))\n- Use print instead of input after starting server [\\#149](https://github.com/sparckles/robyn/pull/149) ([klaa97](https://github.com/klaa97))\n- Fix dev server [\\#148](https://github.com/sparckles/robyn/pull/148) ([sansyrox](https://github.com/sansyrox))\n- URL queries [\\#146](https://github.com/sparckles/robyn/pull/146) ([patchgamestudio](https://github.com/patchgamestudio))\n- Add project wide flake8 settings [\\#143](https://github.com/sparckles/robyn/pull/143) ([sansyrox](https://github.com/sansyrox))\n\n## [v0.11.1](https://github.com/sparckles/robyn/tree/v0.11.1) (2022-01-11)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.11.0...v0.11.1)\n\n## [v0.11.0](https://github.com/sparckles/robyn/tree/v0.11.0) (2022-01-07)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.10.0...v0.11.0)\n\n**Fixed bugs:**\n\n- Hot Reloading goes in an infinite loop [\\#115](https://github.com/sparckles/robyn/issues/115)\n\n**Closed issues:**\n\n- Benchmarks to Björn, uvicorn etc.  [\\#142](https://github.com/sparckles/robyn/issues/142)\n- Add Python linter setup [\\#129](https://github.com/sparckles/robyn/issues/129)\n- Add fixtures in testing [\\#84](https://github.com/sparckles/robyn/issues/84)\n\n## [v0.10.0](https://github.com/sparckles/robyn/tree/v0.10.0) (2021-12-20)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.9.0...v0.10.0)\n\n**Closed issues:**\n\n- Add PyPI classifiers [\\#127](https://github.com/sparckles/robyn/issues/127)\n- Robyn version 0.9.0 doesn't work on Mac M1 Models  [\\#120](https://github.com/sparckles/robyn/issues/120)\n- Inconsistency in steps mentioned in Readme to run locally  [\\#119](https://github.com/sparckles/robyn/issues/119)\n- Async web socket support [\\#116](https://github.com/sparckles/robyn/issues/116)\n- Reveal Logo to be removed from Future Roadmap [\\#107](https://github.com/sparckles/robyn/issues/107)\n- Dead Link for Test Drive Button on Robyn Landing Page  [\\#106](https://github.com/sparckles/robyn/issues/106)\n- Add issue template, pr template and community guidelines [\\#105](https://github.com/sparckles/robyn/issues/105)\n- For v0.7.0 [\\#72](https://github.com/sparckles/robyn/issues/72)\n- Add better support for requests and response! [\\#13](https://github.com/sparckles/robyn/issues/13)\n\n**Merged pull requests:**\n\n- Add async support in WS [\\#134](https://github.com/sparckles/robyn/pull/134) ([sansyrox](https://github.com/sansyrox))\n- Add help messages and simplify 'dev' option [\\#128](https://github.com/sparckles/robyn/pull/128) ([Kludex](https://github.com/Kludex))\n- Apply Python highlight on api.md [\\#126](https://github.com/sparckles/robyn/pull/126) ([Kludex](https://github.com/Kludex))\n- Update comparison.md [\\#124](https://github.com/sparckles/robyn/pull/124) ([Kludex](https://github.com/Kludex))\n- Update comparison.md [\\#123](https://github.com/sparckles/robyn/pull/123) ([Kludex](https://github.com/Kludex))\n- Fix readme documentation [\\#122](https://github.com/sparckles/robyn/pull/122) ([sansyrox](https://github.com/sansyrox))\n- Release v0.9.0 Changelog [\\#121](https://github.com/sparckles/robyn/pull/121) ([sansyrox](https://github.com/sansyrox))\n- \\[FEAT\\] Open Source Contribution Templates [\\#118](https://github.com/sparckles/robyn/pull/118) ([shivaylamba](https://github.com/shivaylamba))\n- FIX : Wrong link for Test Drive [\\#117](https://github.com/sparckles/robyn/pull/117) ([shivaylamba](https://github.com/shivaylamba))\n\n## [v0.9.0](https://github.com/sparckles/robyn/tree/v0.9.0) (2021-12-01)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.8.1...v0.9.0)\n\n**Closed issues:**\n\n- Add more HTTP methods [\\#74](https://github.com/sparckles/robyn/issues/74)\n\n**Merged pull requests:**\n\n- Fix default url bug [\\#111](https://github.com/sparckles/robyn/pull/111) ([sansyrox](https://github.com/sansyrox))\n- Web socket integration attempt 2 [\\#109](https://github.com/sparckles/robyn/pull/109) ([sansyrox](https://github.com/sansyrox))\n\n## [v0.8.1](https://github.com/sparckles/robyn/tree/v0.8.1) (2021-11-17)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.8.0...v0.8.1)\n\n**Fixed bugs:**\n\n- The default start is running the server at '0.0.0.0' instead of '127.0.0.1' [\\#110](https://github.com/sparckles/robyn/issues/110)\n\n## [v0.8.0](https://github.com/sparckles/robyn/tree/v0.8.0) (2021-11-10)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.7.1...v0.8.0)\n\n**Closed issues:**\n\n- Share the TCP web socket across different cores [\\#91](https://github.com/sparckles/robyn/issues/91)\n- Improve the router [\\#52](https://github.com/sparckles/robyn/issues/52)\n- \\[Stretch Goal\\] Create a a way of writing async request [\\#32](https://github.com/sparckles/robyn/issues/32)\n- Improve the router [\\#29](https://github.com/sparckles/robyn/issues/29)\n\n**Merged pull requests:**\n\n- Fix the failing testing suite! [\\#100](https://github.com/sparckles/robyn/pull/100) ([sansyrox](https://github.com/sansyrox))\n- Requests object is now optional [\\#99](https://github.com/sparckles/robyn/pull/99) ([sansyrox](https://github.com/sansyrox))\n- Add socket sharing [\\#94](https://github.com/sparckles/robyn/pull/94) ([sansyrox](https://github.com/sansyrox))\n\n## [v0.7.1](https://github.com/sparckles/robyn/tree/v0.7.1) (2021-10-28)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.7.0...v0.7.1)\n\n**Closed issues:**\n\n- Remove the solution using dockerisation of tests [\\#98](https://github.com/sparckles/robyn/issues/98)\n- Functions not working without request param [\\#96](https://github.com/sparckles/robyn/issues/96)\n- Add actix router [\\#85](https://github.com/sparckles/robyn/issues/85)\n- Request apart from GET are not working in directory subroutes [\\#79](https://github.com/sparckles/robyn/issues/79)\n- Add the ability to share the server across the network [\\#69](https://github.com/sparckles/robyn/issues/69)\n- Add the ability to view headers in the HTTP Methods [\\#54](https://github.com/sparckles/robyn/issues/54)\n- Add tests! [\\#8](https://github.com/sparckles/robyn/issues/8)\n\n## [v0.7.0](https://github.com/sparckles/robyn/tree/v0.7.0) (2021-10-03)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.6.1...v0.7.0)\n\n**Closed issues:**\n\n- Robyn the replacement of Quart [\\#86](https://github.com/sparckles/robyn/issues/86)\n- Add Pytest support for the test endpoints [\\#81](https://github.com/sparckles/robyn/issues/81)\n\n**Merged pull requests:**\n\n- Finally completed router integration [\\#90](https://github.com/sparckles/robyn/pull/90) ([sansyrox](https://github.com/sansyrox))\n- Address clippy lints [\\#89](https://github.com/sparckles/robyn/pull/89) ([SanchithHegde](https://github.com/SanchithHegde))\n- Initial docs update [\\#83](https://github.com/sparckles/robyn/pull/83) ([sansyrox](https://github.com/sansyrox))\n- Add the basics of python testing [\\#82](https://github.com/sparckles/robyn/pull/82) ([sansyrox](https://github.com/sansyrox))\n- Add a new landing page [\\#80](https://github.com/sparckles/robyn/pull/80) ([sansyrox](https://github.com/sansyrox))\n\n## [v0.6.1](https://github.com/sparckles/robyn/tree/v0.6.1) (2021-08-30)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.6.0...v0.6.1)\n\n**Closed issues:**\n\n- Make a new release [\\#71](https://github.com/sparckles/robyn/issues/71)\n- Update to the pyo3 v0.14 [\\#63](https://github.com/sparckles/robyn/issues/63)\n- Add the support to serve static directories [\\#55](https://github.com/sparckles/robyn/issues/55)\n- Add support for mounting directory [\\#38](https://github.com/sparckles/robyn/issues/38)\n\n**Merged pull requests:**\n\n- Add the base of http requests [\\#78](https://github.com/sparckles/robyn/pull/78) ([sansyrox](https://github.com/sansyrox))\n- Add default port and a variable url [\\#77](https://github.com/sparckles/robyn/pull/77) ([sansyrox](https://github.com/sansyrox))\n- Make the request object accessible in every route [\\#76](https://github.com/sparckles/robyn/pull/76) ([sansyrox](https://github.com/sansyrox))\n- Add the basics for circle ci and testing framework [\\#67](https://github.com/sparckles/robyn/pull/67) ([sansyrox](https://github.com/sansyrox))\n- Update to pyo3 v0.14 [\\#65](https://github.com/sparckles/robyn/pull/65) ([sansyrox](https://github.com/sansyrox))\n- Add the static directory serving [\\#64](https://github.com/sparckles/robyn/pull/64) ([sansyrox](https://github.com/sansyrox))\n- Create a request object [\\#61](https://github.com/sparckles/robyn/pull/61) ([sansyrox](https://github.com/sansyrox))\n- Add the ability to add body in PUT, PATCH and DELETE [\\#60](https://github.com/sparckles/robyn/pull/60) ([sansyrox](https://github.com/sansyrox))\n- Implement a working dev server [\\#40](https://github.com/sparckles/robyn/pull/40) ([sansyrox](https://github.com/sansyrox))\n- Use Actix as base [\\#35](https://github.com/sparckles/robyn/pull/35) ([JackThomson2](https://github.com/JackThomson2))\n\n## [v0.6.0](https://github.com/sparckles/robyn/tree/v0.6.0) (2021-08-11)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.5.3...v0.6.0)\n\n**Closed issues:**\n\n- Add body support for PUT, POST and PATCH [\\#53](https://github.com/sparckles/robyn/issues/53)\n- Away with limited internet access till 1st August [\\#51](https://github.com/sparckles/robyn/issues/51)\n- Add doc stings [\\#42](https://github.com/sparckles/robyn/issues/42)\n- OSX builds are failing [\\#41](https://github.com/sparckles/robyn/issues/41)\n- Add a dev server implementation [\\#37](https://github.com/sparckles/robyn/issues/37)\n- Mini Roadmap | A list of issues that would require fixing [\\#19](https://github.com/sparckles/robyn/issues/19)\n- Add support for Object/JSON Return Type! [\\#9](https://github.com/sparckles/robyn/issues/9)\n\n## [v0.5.3](https://github.com/sparckles/robyn/tree/v0.5.3) (2021-07-12)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.5.2...v0.5.3)\n\n**Merged pull requests:**\n\n- Improve the HTML file serving [\\#46](https://github.com/sparckles/robyn/pull/46) ([sansyrox](https://github.com/sansyrox))\n- Add the basics to add serving of static files [\\#36](https://github.com/sparckles/robyn/pull/36) ([sansyrox](https://github.com/sansyrox))\n\n## [v0.5.2](https://github.com/sparckles/robyn/tree/v0.5.2) (2021-07-11)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.5.1...v0.5.2)\n\n## [v0.5.1](https://github.com/sparckles/robyn/tree/v0.5.1) (2021-07-10)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.5.0...v0.5.1)\n\n**Closed issues:**\n\n- Make html file serving more robust [\\#45](https://github.com/sparckles/robyn/issues/45)\n- Try to serve individual static files using vanilla rust [\\#43](https://github.com/sparckles/robyn/issues/43)\n- Error on import  [\\#16](https://github.com/sparckles/robyn/issues/16)\n- Add multiple process sharing [\\#2](https://github.com/sparckles/robyn/issues/2)\n\n## [v0.5.0](https://github.com/sparckles/robyn/tree/v0.5.0) (2021-07-01)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.4.1...v0.5.0)\n\n**Closed issues:**\n\n- QPS drops drastically after processing many requests [\\#31](https://github.com/sparckles/robyn/issues/31)\n- Improve the way you parse TCP streams [\\#30](https://github.com/sparckles/robyn/issues/30)\n- Re-introduce thread pool for the sync functions \\(maybe\\) [\\#22](https://github.com/sparckles/robyn/issues/22)\n- Add async listener object in rust stream! [\\#11](https://github.com/sparckles/robyn/issues/11)\n\n**Merged pull requests:**\n\n- Make the server http compliant [\\#33](https://github.com/sparckles/robyn/pull/33) ([sansyrox](https://github.com/sansyrox))\n\n## [v0.4.1](https://github.com/sparckles/robyn/tree/v0.4.1) (2021-06-26)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/0.4.0...v0.4.1)\n\n**Closed issues:**\n\n- Add PyPi Metadata [\\#5](https://github.com/sparckles/robyn/issues/5)\n\n**Merged pull requests:**\n\n- Build and publish wheels on GitHub Actions [\\#26](https://github.com/sparckles/robyn/pull/26) ([messense](https://github.com/messense))\n- Code cleanup using PyFunction type [\\#25](https://github.com/sparckles/robyn/pull/25) ([sansyrox](https://github.com/sansyrox))\n- Add non blocking sync functions [\\#23](https://github.com/sparckles/robyn/pull/23) ([sansyrox](https://github.com/sansyrox))\n- Add support for sync functions [\\#20](https://github.com/sparckles/robyn/pull/20) ([sansyrox](https://github.com/sansyrox))\n\n## [0.4.0](https://github.com/sparckles/robyn/tree/0.4.0) (2021-06-22)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.3.0...0.4.0)\n\n**Closed issues:**\n\n- Add support for Sync functions as well! [\\#7](https://github.com/sparckles/robyn/issues/7)\n\n## [v0.3.0](https://github.com/sparckles/robyn/tree/v0.3.0) (2021-06-21)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/v0.2.3...v0.3.0)\n\n**Closed issues:**\n\n- Architecture link in readme redirects to raw content [\\#18](https://github.com/sparckles/robyn/issues/18)\n- Link pointing to the wrong destination [\\#6](https://github.com/sparckles/robyn/issues/6)\n\n**Merged pull requests:**\n\n- Pure tokio [\\#17](https://github.com/sparckles/robyn/pull/17) ([JackThomson2](https://github.com/JackThomson2))\n- Remove Mutex lock on Threadpool and routes [\\#15](https://github.com/sparckles/robyn/pull/15) ([JackThomson2](https://github.com/JackThomson2))\n\n## [v0.2.3](https://github.com/sparckles/robyn/tree/v0.2.3) (2021-06-18)\n\n[Full Changelog](https://github.com/sparckles/robyn/compare/c14f52e6faa79917e89de4220590da7bf28f6a65...v0.2.3)\n\n**Closed issues:**\n\n- Improve async runtime [\\#3](https://github.com/sparckles/robyn/issues/3)\n\n\n\n\\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "### Robyn Open Source Community Guidelines\n\n- **Be friendly and patient**.\n- **Be welcoming**.\n- **Be respectful**.\n- **Be careful in the words that we choose**.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "## Contributing Guidelines\n\nFirst off, thank you for considering contributing to `Robyn`. This guide details all the general information that one should know before contributing to the project.\nPlease stick as close as possible to the guidelines. That way, we ensure that you have a smooth experience contributing to this project.\n\n### General Rules:\n\nThese are, in general, rules that you should be following while contributing to an Open-Source project :\n\n- Be Nice, Be Respectful (BNBR)\n- Check if the Issue you created, exists or not.\n- While creating a new issue, make sure you describe the issue clearly.\n- Make proper commit messages and document your PR well.\n- Always add comments in your Code and explain it at points if possible, add Doctest.\n- Always create a Pull Request from a Branch; Never from the Main.\n- Follow proper code conventions because writing clean code is important.\n- Issues would be assigned on a \"First Come, First Served\" basis.\n- Do mention (@sansyrox) the project maintainer if your PR isn't reviewed within a few days.\n\n## First time contributors:\n\nPushing files in your own repository is easy, but how to contribute to someone else's project? If you have the same question, then below are the steps that you can follow\nto make your first contribution in this repository.\n\n### Pull Request\n\n**1.** The very first step includes forking the project. Click on the `fork` button as shown below to fork the project.\n<br><br><img src=\"https://i.imgur.com/7wapvt2.png\" width=\"750\" /><br>\n\n**2.** Clone the forked repository. Open up the GitBash/Command Line and type\n\n```\ngit clone https://github.com/<YOUR_USER_NAME>/robyn.git\n```\n\n**3.** Navigate to the project directory.\n\n```\ncd robyn\n```\n\n**4.** Add a reference to the original repository.\n\n```\ngit remote add upstream https://github.com/sparckles/robyn.git\n```\n\n**5.** See latest changes to the repo using\n\n```\ngit remote -v\n```\n\n**6.** Create a new branch.\n\n```\ngit checkout -b <YOUR_BRANCH_NAME>\n```\n\n**7.** Always take a pull from the upstream repository to your main branch to keep it even with the main project. This will save you from frequent merge conflicts.\n\n```\ngit pull upstream main\n```\n\n**8.** You can make the required changes now. Make appropriate commits with proper commit messages.\n\n**9.** Add and then commit your changes.\n\n```\ngit add .\n```\n\n```\ngit commit -m \"<YOUR_COMMIT_MESSAGE>\"\n```\n\n**10.** Push your local branch to the remote repository.\n\n```\ngit push -u origin <YOUR_BRANCH_NAME>\n```\n\n**11.** Once you have pushed the changes to your repository, go to your forked repository. Click on the `Compare & pull request` button as shown below.\n<br><br><img src=\"https://hisham.hm/img/posts/github-comparepr.png\" width=\"750\" /><br>\n\n**12.** The image below is what the new page would look like. Give a proper title to your PR and describe the changes made by you in the description box.(Note - Sometimes there are PR templates which are to be filled as instructed.)\n<br><br><img src=\"https://github.blog/wp-content/uploads/2019/02/draft-pull-requests.png?fit=1354%2C780\" width=\"750\" /><br>\n\n**13.** Open a pull request by clicking the `Create pull request` button.\n\n`Voila, you have made your first contribution to this project`\n\n## Issue\n\n- Issues can be used to keep track of bugs, enhancements, or other requests. Creating an issue to let the project maintainers know about the changes you are planning to make before raising a PR is a good open-source practice.\n  <br>\n\nLet's walk through the steps to create an issue:\n\n**1.** On GitHub, navigate to the main page of the repository. [Here](https://github.com/sparckles/robyn.git) in this case.\n\n**2.** Under your repository name, click on the `Issues` button.\n<br><br><img src=\"https://www.stevejgordon.co.uk/wp-content/uploads/2018/01/GitHubIssueTab.png\" width=\"750\" /><br>\n\n**3.** Click on the `New issue` button.\n<br><br><img src=\"https://miro.medium.com/max/3696/1*8jiGiKhMdVQDycWSAbjB8A.png\" width=\"750\" /><br>\n\n**4.** Select one of the Issue Templates to get started.\n<br><br><img src=\"https://i.imgur.com/xz2KAwU.png\" width=\"750\" /><br>\n\n**5.** Fill in the appropriate `Title` and `Issue description` and click on `Submit new issue`.\n<br><br><img src=\"https://i.imgur.com/XwjtGG1.png\" width=\"750\" /><br>\n\n### Tutorials that may help you:\n\n- [Git & GitHub Tutorial](https://www.youtube.com/watch?v=RGOj5yH7evk)\n- [Resolve merge conflict](https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/resolving-a-merge-conflict-on-github)\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"robyn\"\nversion = \"0.82.0\"\nauthors = [\"Sanskar Jethi <sansyrox@gmail.com>\"]\nedition = \"2021\"\ndescription = \"Robyn is a Super Fast Async Python Web Framework with a Rust runtime.\"\nlicense = \"BSD License (BSD)\"\nhomepage = \"https://github.com/sparckles/robyn\"\nreadme = \"README.md\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n[lib]\nname = \"robyn\"\ncrate-type = [\"cdylib\", \"rlib\"]\n\n[dependencies]\npyo3 = { version = \"0.27.2\", features = [\"extension-module\", \"py-clone\"]}\npyo3-async-runtimes = { version = \"0.27\", features = [\"tokio-runtime\"] }\npyo3-async-runtimes-macros = { version = \"0.27\" }\npyo3-log = \"0.13.2\"\ntokio = { version = \"1.40\", features = [\"full\"] }\ndashmap = \"5.4.3\"\nanyhow = \"1.0.69\"\nactix = \"0.13.4\"\nactix-web-actors = \"4.3.0\"\nactix-web = \"4.4.2\"\nactix-http = \"3.3.1\"\nactix-files = \"0.6.2\"\nfutures = \"0.3.27\"\nfutures-util = \"0.3.27\"\nmatchit = \"0.7.3\"\nsocket2 = { version = \"0.5.1\", features = [\"all\"] }\nuuid = { version = \"1.3.0\", features = [\"serde\", \"v4\"] }\nlog = \"0.4.17\"\npythonize = \"0.27\"\nserde = \"1.0.187\"\nserde_json = \"1.0.109\"\nonce_cell = \"1.8.0\"\nactix-multipart = \"0.6.1\"\nparking_lot = \"0.12.3\"\ncrossbeam-channel = \"0.5\"\n\n[features]\nio-uring = [\"actix-web/experimental-io-uring\"]\n\n[profile.release]\ncodegen-units = 1\nlto = \"fat\"\npanic = \"abort\"\nstrip = true\nopt-level = 3\n\n[profile.release.build-override]\nopt-level = 3\n\n[package.metadata.maturin]\nname = \"robyn\"\n"
  },
  {
    "path": "LICENSE",
    "content": "BSD 2-Clause License\n\nCopyright (c) 2021, Sanskar Jethi\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n   list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice,\n   this list of conditions and the following disclaimer in the documentation\n   and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\"><img alt=\"Robyn Logo\" src=\"https://user-images.githubusercontent.com/29942790/140995889-5d91dcff-3aa7-4cfb-8a90-2cddf1337dca.png\" width=\"250\" /><p>\n\n# Robyn\n\n[![Twitter](https://badgen.net/badge/icon/twitter?icon=twitter&label)](https://twitter.com/Robyn_oss)\n[![Downloads](https://static.pepy.tech/personalized-badge/Robyn?period=total&units=international_system&left_color=grey&right_color=blue&left_text=Downloads)](https://pepy.tech/project/Robyn)\n[![GitHub tag](https://img.shields.io/github/tag/sparckles/Robyn?include_prereleases=&sort=semver&color=black)](https://github.com/sparckles/Robyn/releases/)\n[![License](https://img.shields.io/badge/License-BSD_2.0-black)](https://github.com/sparckles/Robyn/blob/main/LICENSE)\n![Python](https://img.shields.io/badge/Support-Version%20%E2%89%A5%203.10-brightgreen)\n[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/sparckles/Robyn)\n\n[![view - Documentation](https://img.shields.io/badge/view-Documentation-blue?style=for-the-badge)](https://robyn.tech/documentation)\n[![Discord](https://img.shields.io/discord/999782964143603713?label=discord&logo=discord&logoColor=white&style=for-the-badge&color=blue)](https://discord.gg/rkERZ5eNU8)\n[![Gurubase](https://img.shields.io/badge/Gurubase-Ask%20Robyn%20Guru-006BFF?style=for-the-badge)](https://gurubase.io/g/robyn)\n\nRobyn is a High-Performance, Community-Driven, and Innovator Friendly Web Framework with a Rust runtime. You can learn more by checking our [community resources](https://robyn.tech/documentation/en/community-resources#talks)!\n\n<img width=\"652\" alt=\"image\" src=\"https://github.com/sparckles/Robyn/assets/29942790/4a2bba61-24e7-4ee2-8884-19b40204bfcd\">\n\n\nSource: [TechEmpower Round 22](https://www.techempower.com/benchmarks/#section=data-r22&test=plaintext)\n\n## 📦 Installation\n\nYou can simply use Pip for installation.\n\n```bash\npip install robyn\n```\n\nOr, with [conda-forge](https://conda-forge.org/)\n\n```bash\nconda install -c conda-forge robyn\n```\n\nTo install with all optional features (Pydantic validation, Jinja2 templating):\n\n```bash\npip install \"robyn[all]\"\n```\n\n## 🤔 Usage\n\n### 🚀 Define your API\n\nTo define your API, you can add the following code in an `app.py` file.\n\n```python\nfrom robyn import Robyn\n\napp = Robyn(__file__)\n\n@app.get(\"/\")\nasync def h(request):\n    return \"Hello, world!\"\n\napp.start(port=8080)\n```\n\n### 🏃 Run your code\n\nSimply run the app.py file you created. You will then have access to a server on the `localhost:8080`, that you can request from an other program. Robyn provides several options to customize your web server.\n\n```\n$ python3 app.py\n```\n\nTo see the usage\n\n```\nusage: app.py [-h] [--processes PROCESSES] [--workers WORKERS] [--dev] [--log-level LOG_LEVEL]\n\nRobyn, a fast async web framework with a rust runtime.\n\noptions:\n  -h, --help            show this help message and exit\n  --processes PROCESSES\n                        Choose the number of processes. [Default: 1]\n  --workers WORKERS     Choose the number of workers. [Default: 1]\n  --dev                 Development mode. It restarts the server based on file changes.\n  --log-level LOG_LEVEL\n                        Set the log level name\n  --create              Create a new project template.\n  --docs                Open the Robyn documentation.\n  --open-browser        Open the browser on successful start.\n  --version             Show the Robyn version.\n  --compile-rust-path COMPILE_RUST_PATH\n                        Compile rust files in the given path.\n  --create-rust-file CREATE_RUST_FILE\n                        Create a rust file with the given name.\n  --disable-openapi     Disable the OpenAPI documentation.\n  --fast                Enable the fast mode.\n```\n\nLog level can be `DEBUG`, `INFO`, `WARNING`, or `ERROR`.\n\nWhen running the app using `--open-browser` a new browser window will open at the app location, e.g:\n\n```\n$ python3 app.py --open-browser\n```\n\n### 💻 Add more routes\n\nYou can add more routes to your API. Check out the routes in [this file](https://github.com/sparckles/Robyn/blob/main/integration_tests/base_routes.py) as examples.\n\n### 🐍 Python Version Support\n\nRobyn is compatible with the following Python versions:\n\n> Python >= 3.10\n\nIt is recommended to use the latest version of Python for the best performances.\n\nPlease make sure you have the correct version of Python installed before starting to use\nthis project. You can check your Python version by running the following command in your\nterminal:\n\n```bash\npython --version\n```\n\n## 💡 Features\n\n- Under active development!\n- A multithreaded Runtime\n- Extensible\n- A simple API\n- Sync and Async Function Support\n- Dynamic URL Routing\n- Multi Core Scaling\n- WebSockets\n- Middlewares (before and after request hooks)\n- Built in form data handling\n- Dependency Injection\n- Hot Reloading\n- Direct Rust Integration\n- Automatic OpenAPI generation\n- Jinja2 Templating\n- Static File Serving\n- File Responses and Downloads\n- Authentication Support\n- CORS Configuration\n- Streaming / SSE Responses\n- Startup and Shutdown Events\n- Exception Handling\n- SubRouters\n- Project Scaffolding via CLI\n- Experimental io-uring Support\n- **🤖 AI Agent Support** - Built-in agent routing and execution\n- **🔌 MCP (Model Context Protocol)** - Connect to AI applications as a server\n- Community First and truly FOSS!\n\n## 🗒️ How to contribute\n\n### 🏁 Get started\n\nPlease read the [code of conduct](https://github.com/sparckles/Robyn/blob/main/CODE_OF_CONDUCT.md) and go through [CONTRIBUTING.md](https://github.com/sparckles/Robyn/blob/main/CONTRIBUTING.md) before contributing to Robyn.\nFeel free to open an issue for any clarifications or suggestions.\n\nIf you're feeling curious. You can take a look at a more detailed architecture [here](https://robyn.tech/documentation/architecture).\n\nIf you still need help to get started, feel free to reach out on our [community discord](https://discord.gg/rkERZ5eNU8).\n\n### ⚙️ To Develop Locally\n\n#### Prerequisites\n\nBefore starting, ensure you have the following installed:\n- Python >= 3.10, <= 3.14\n- Rust (latest stable)\n- C compiler (gcc/clang)\n\n#### Setup\n\n- Clone the repository:\n\n  ```\n  git clone https://github.com/sparckles/Robyn.git\n  ```\n\n- Setup a virtual environment:\n  ```\n  python3 -m venv .venv\n  source .venv/bin/activate\n  ```\n\n- Install required packages\n\n  ```\n  pip install pre-commit poetry maturin\n  ```\n- Install development dependencies\n  ```\n  poetry install --with dev --with test\n  ```\n- Install pre-commit git hooks\n  ```\n  pre-commit install\n  ```\n- Build & install Robyn Rust package\n  ```\n  maturin develop\n  ```\n- Build & install Robyn Rust package (**experimental**)\n  ```\n  maturin develop --cargo-extra-args=\"--features=io-uring\"\n  ```\n- Run!\n  ```\n  poetry run test_server\n  ```\n- Run all tests\n  ```\n  pytest\n  ```\n- Run only the integration tests\n  ```\n  pytest integration_tests\n  ```\n- Run only the unit tests (you don't need to be running the test_server for these)\n  ```\n  pytest unit_tests\n  ```\n- Test (refer to `integration_tests/base_routes.py` for more endpoints)\n  ```\n  curl http://localhost:8080/sync/str\n  ```\n\n- **tip:** One liners for testing changes!\n  ```\n  maturin develop && poetry run test_server\n\n  maturin develop && pytest \n  ```\n\n- **tip:** For IO-uring support, you can use the following command:\n  ```\n  maturin develop --cargo-extra-args=\"--features=io-uring\"\n  ```\n\n- **tip:** To use your local Robyn version in other projects, you can install it using pip:\n  ```\n  pip install -e path/to/robyn/target/wheels/robyn-<version>-<python_version>-<platform>.whl\n  ```\ne.g.\n  ```\n  pip install -e /repos/Robyn/target/wheels/robyn-0.63.0-cp312-cp312-macosx_10_15_universal2.whl\n  ```\n\n#### Troubleshooting\nIf you face any issues, here are some common fixes:\n  - install `patchelf` with `pip install patchelf` if you face `patchelf` not found issue during `maturin develop` (esp. on Arch Linux)\n  - If you get Rust compilation errors, ensure you have a C compiler installed:\n    - Ubuntu/Debian: `sudo apt install build-essential`\n    - Fedora: `sudo dnf install gcc`\n    - macOS: Install Xcode Command Line Tools\n    - Windows: Install Visual Studio Build Tools\n\n\n## ✨ Special thanks\n\n### ✨ Contributors/Supporters\n\nThanks to all the contributors of the project. Robyn will not be what it is without all your support :heart:.\n\n<a href=\"https://github.com/sparckles/Robyn/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=sparckles/Robyn\" />\n</a>\n\nSpecial thanks to the [PyO3](https://pyo3.rs/v0.13.2/) community and [Andrew from PyO3-asyncio](https://github.com/awestlake87/pyo3-asyncio) for their amazing libraries and their support for my queries. 💖\n\n### ✨ Sponsors\n\nThese sponsors help us make the magic happen!\n\n[![DigitalOcean Referral Badge](https://web-platforms.sfo2.cdn.digitaloceanspaces.com/WWW/Badge%201.svg)](https://www.digitalocean.com/?refcode=3f2b9fd4968d&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge)\n[![Appwrite Logo](https://avatars.githubusercontent.com/u/25003669?s=105&v=1)](https://github.com/appwrite)\n\n\n## Star History\n\n[![Star History Chart](https://api.star-history.com/svg?repos=sparckles/Robyn&type=Date)](https://star-history.com/#sparckles/Robyn&Date)\n"
  },
  {
    "path": "benchmark.sh",
    "content": "#!/bin/sh\n\n# Benchmark script to get info about Robyn's performances\n# You can use this benchmark when developing on Robyn to test if your changes had a huge\n# impact on performances. You cannot compare benchmarks from different machine and even\n# several runs on the same machine can give very different results sometimes.\n# Be aware of this when using this script!\n\nHelp() {\n    echo \"Benchmark script to get info about Robyn's performances.\"\n    echo\n    echo \"USAGE:\"\n    echo \"    benchmark [-h|m|n|y]\"\n    echo\n    echo \"OPTIONS:\"\n    echo \"    -h              Print this help.\"\n    echo \"    -m              Run 'maturin develop' to compile the Rust part of Robyn.\"\n    echo \"    -n <number>     Set the number of requests that oha sends.\"\n    echo \"    -y              Skip prompt\"\n    exit 0\n}\n\nyes_flag=false\nrun_maturin=false\nnumber=100000\nwhile getopts hymn: opt; do\n    case $opt in\n        h)\n            Help\n            ;;\n        y)\n            yes_flag=true\n            ;;\n        m)\n            run_maturin=true\n            ;;\n        n)\n            number=$OPTARG\n            ;;\n        ?)\n            echo 'Error in command line parsing' >&2\n            Help\n            exit 1\n            ;;\n    esac\ndone\n\n# Prompt user to check if he installed the requirements for running the benchmark\nif [ \"$yes_flag\" = false ]; then\n    echo \"Make sure you are running this in your venv and you installed 'oha' using 'cargo install oha'\"\n    echo \"Do you want to proceed?\"\n    while true; do\n        read -p \"\" yn\n        case $yn in\n            [Yy]* ) break;;\n            [Nn]* ) exit;;\n            * ) echo \"Please answer yes or no.\";;\n        esac\n    done\nfi\n\n\n# Compile Rust\nif $run_maturin; then\n    maturin develop\nfi\n\n# Run the server in the background\npython3 ./integration_tests/base_routes.py &\nsleep 1\n\n# oha will display benchmark results\noha -n \"$number\" http://localhost:8080/sync\n\n# Kill subprocesses after exiting the script (python + robyn server)\n# (see https://stackoverflow.com/questions/360201/how-do-i-kill-background-processes-jobs-when-my-shell-script-exits)\ntrap \"trap - TERM && kill 0\" INT TERM EXIT\n"
  },
  {
    "path": "ci-local.sh",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[1;33m'\nCYAN='\\033[0;36m'\nNC='\\033[0m'\n\nFAILED=()\nPASSED=()\nSKIPPED=()\n\nrun_step() {\n    local name=\"$1\"\n    shift\n    echo -e \"\\n${CYAN}── $name ──${NC}\"\n    echo -e \"${YELLOW}$ $*${NC}\"\n    if \"$@\"; then\n        PASSED+=(\"$name\")\n        echo -e \"${GREEN}✓ $name${NC}\"\n    else\n        FAILED+=(\"$name\")\n        echo -e \"${RED}✗ $name${NC}\"\n    fi\n}\n\nskip_step() {\n    local name=\"$1\"\n    local reason=\"$2\"\n    SKIPPED+=(\"$name ($reason)\")\n    echo -e \"\\n${YELLOW}── $name [SKIPPED: $reason] ──${NC}\"\n}\n\nusage() {\n    echo \"Usage: $0 [rust|lint|python|all|fix]\"\n    echo \"\"\n    echo \"Mirrors the GitHub Actions CI workflows locally.\"\n    echo \"\"\n    echo \"  rust     Rust CI:    cargo check, test, fmt --check, clippy\"\n    echo \"  lint     Lint PR:    ruff check, isort --check-only\"\n    echo \"  python   Python CI:  nox test suite (current Python version)\"\n    echo \"  all      Everything  (default)\"\n    echo \"  fix      Auto-fix:   cargo fmt, ruff --fix, isort\"\n    exit 0\n}\n\n# ── rust-CI.yml ───────────────────────────────────────────────────────────────\nrun_rust() {\n    echo -e \"\\n${CYAN}═══ Rust CI (.github/workflows/rust-CI.yml) ═══${NC}\"\n    run_step \"cargo check\"   cargo check\n    run_step \"cargo test\"    cargo test\n    run_step \"cargo fmt\"     cargo fmt --check\n    run_step \"cargo clippy\"  cargo clippy\n}\n\n# ── lint-pr.yml ───────────────────────────────────────────────────────────────\nrun_lint() {\n    echo -e \"\\n${CYAN}═══ Lint PR (.github/workflows/lint-pr.yml) ═══${NC}\"\n\n    if command -v ruff &>/dev/null; then\n        run_step \"ruff check\" ruff check .\n    else\n        skip_step \"ruff check\" \"ruff not installed (pip install ruff)\"\n    fi\n\n    if command -v isort &>/dev/null; then\n        run_step \"isort check\" isort --check-only --diff .\n    else\n        skip_step \"isort check\" \"isort not installed (pip install isort)\"\n    fi\n}\n\n# ── python-CI.yml ─────────────────────────────────────────────────────────────\nrun_python() {\n    echo -e \"\\n${CYAN}═══ Python CI (.github/workflows/python-CI.yml) ═══${NC}\"\n    local pyver\n    pyver=$(python3 -c \"import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')\")\n\n    if command -v nox &>/dev/null; then\n        run_step \"nox (python $pyver)\" nox --non-interactive --error-on-missing-interpreter -p \"$pyver\"\n    else\n        skip_step \"nox tests\" \"nox not installed (pip install nox)\"\n    fi\n}\n\n# ── fix mode ──────────────────────────────────────────────────────────────────\nrun_fix() {\n    echo -e \"\\n${CYAN}═══ Auto-fix ═══${NC}\"\n    run_step \"cargo fmt\"  cargo fmt\n    command -v ruff  &>/dev/null && run_step \"ruff fix\"  ruff check --fix . || skip_step \"ruff fix\" \"not installed\"\n    command -v isort &>/dev/null && run_step \"isort fix\" isort .            || skip_step \"isort fix\" \"not installed\"\n}\n\n# ── main ──────────────────────────────────────────────────────────────────────\nMODE=\"${1:-all}\"\n\ncase \"$MODE\" in\n    rust)   run_rust ;;\n    lint)   run_lint ;;\n    python) run_python ;;\n    fix)    run_fix ;;\n    all)    run_rust; run_lint; run_python ;;\n    -h|--help|help) usage ;;\n    *) echo \"Unknown mode: $MODE\"; usage ;;\nesac\n\n# ── summary ───────────────────────────────────────────────────────────────────\necho -e \"\\n${CYAN}═══ Summary ═══${NC}\"\nfor s in \"${PASSED[@]+\"${PASSED[@]}\"}\"; do echo -e \"  ${GREEN}✓${NC} $s\"; done\nfor s in \"${SKIPPED[@]+\"${SKIPPED[@]}\"}\"; do echo -e \"  ${YELLOW}⊘${NC} $s\"; done\nfor s in \"${FAILED[@]+\"${FAILED[@]}\"}\"; do echo -e \"  ${RED}✗${NC} $s\"; done\n\nif [ ${#FAILED[@]} -gt 0 ]; then\n    echo -e \"\\n${RED}CI would fail: ${#FAILED[@]} check(s) failed.${NC}\"\n    exit 1\nelse\n    echo -e \"\\n${GREEN}All checks passed. Safe to push.${NC}\"\n    exit 0\nfi\n"
  },
  {
    "path": "docs_src/.eslintrc.json",
    "content": "{\n  \"extends\": [\"next\",\"next/core-web-vitals\"],\n  \"rules\": {\n    \"react/no-unescaped-entities\": \"off\"\n  }\n}\n"
  },
  {
    "path": "docs_src/.gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.js\n\n# testing\n/coverage\n\n# next.js\n/.next/\n/out/\n\n# production\n/build\n\n# misc\n.DS_Store\n*.pem\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n.pnpm-debug.log*\n\n# local env files\n.env*.local\n\n# vercel\n.vercel\n\n# generated files\n/public/rss/\n"
  },
  {
    "path": "docs_src/README.md",
    "content": "## Docs Base\n\nThis is the documentation website that will be used as a base for Robyn and Starfyre docs.\n\n## Setup\n\n1. Clone this repo\n2. Run `npm install`\n3. Run `npm run dev`\n4. Open `http://localhost:3000` in your browser\n\n"
  },
  {
    "path": "docs_src/jsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@/*\": [\"src/*\"]\n    }\n  }\n}\n"
  },
  {
    "path": "docs_src/mdx/recma.mjs",
    "content": "import { mdxAnnotations } from 'mdx-annotations'\nimport recmaNextjsStaticProps from 'recma-nextjs-static-props'\nimport { recmaImportImages } from 'recma-import-images'\n\nfunction recmaRemoveNamedExports() {\n  return (tree) => {\n    tree.body = tree.body.map((node) => {\n      if (node.type === 'ExportNamedDeclaration') {\n        return node.declaration\n      }\n      return node\n    })\n  }\n}\n\nexport const recmaPlugins = [\n  mdxAnnotations.recma,\n  recmaRemoveNamedExports,\n  recmaNextjsStaticProps,\n  recmaImportImages,\n]\n"
  },
  {
    "path": "docs_src/mdx/rehype.mjs",
    "content": "import { mdxAnnotations } from 'mdx-annotations'\nimport { visit } from 'unist-util-visit'\nimport rehypeMdxTitle from 'rehype-mdx-title'\nimport shiki from 'shiki'\nimport { toString } from 'mdast-util-to-string'\nimport * as acorn from 'acorn'\nimport { slugifyWithCounter } from '@sindresorhus/slugify'\nimport rehypeSlug from 'rehype-slug'\nimport { remarkRehypeWrap } from 'remark-rehype-wrap'\nimport rehypeAutolinkHeadings from 'rehype-autolink-headings'\n\nfunction rehypeParseCodeBlocks() {\n  return (tree) => {\n    visit(tree, 'element', (node, _nodeIndex, parentNode) => {\n      if (node.tagName === 'code' && node.properties.className) {\n        parentNode.properties.language = node.properties.className[0]?.replace(\n          /^language-/,\n          ''\n        )\n      }\n    })\n  }\n}\n\nlet highlighter\n\nfunction rehypeShiki() {\n  return async (tree) => {\n    highlighter =\n      highlighter ?? (await shiki.getHighlighter({ theme: 'css-variables' }))\n\n    visit(tree, 'element', (node) => {\n      if (node.tagName === 'pre' && node.children[0]?.tagName === 'code') {\n        let codeNode = node.children[0]\n        let textNode = codeNode.children[0]\n\n        node.properties.code = textNode.value\n\n        if (node.properties.language) {\n          let tokens = highlighter.codeToThemedTokens(\n            textNode.value,\n            node.properties.language\n          )\n\n          textNode.value = shiki.renderToHtml(tokens, {\n            elements: {\n              pre: ({ children }) => children,\n              code: ({ children }) => children,\n              line: ({ children }) => `<span>${children}</span>`,\n            },\n          })\n        }\n      }\n    })\n  }\n}\n\nfunction rehypeSlugify() {\n  return (tree) => {\n    let slugify = slugifyWithCounter()\n    visit(tree, 'element', (node) => {\n      if (node.tagName === 'h2' && !node.properties.id) {\n        node.properties.id = slugify(toString(node))\n      }\n    })\n  }\n}\n\nfunction rehypeAddMDXExports(getExports) {\n  return (tree) => {\n    let exports = Object.entries(getExports(tree))\n\n    for (let [name, value] of exports) {\n      for (let node of tree.children) {\n        if (\n          node.type === 'mdxjsEsm' &&\n          new RegExp(`export\\\\s+const\\\\s+${name}\\\\s*=`).test(node.value)\n        ) {\n          return\n        }\n      }\n\n      let exportStr = `export const ${name} = ${value}`\n\n      tree.children.push({\n        type: 'mdxjsEsm',\n        value: exportStr,\n        data: {\n          estree: acorn.parse(exportStr, {\n            sourceType: 'module',\n            ecmaVersion: 'latest',\n          }),\n        },\n      })\n    }\n  }\n}\n\nfunction getSections(node) {\n  let sections = []\n\n  for (let child of node.children ?? []) {\n    if (child.type === 'element' && child.tagName === 'h2') {\n      sections.push(`{\n        title: ${JSON.stringify(toString(child))},\n        id: ${JSON.stringify(child.properties.id)},\n        ...${child.properties.annotation}\n      }`)\n    } else if (child.children) {\n      sections.push(...getSections(child))\n    }\n  }\n\n  return sections\n}\n\nexport const rehypePlugins = [\n  rehypeSlug,\n  [rehypeAutolinkHeadings, { behavior: 'wrap', test: ['h2'] }],\n  [\n    remarkRehypeWrap,\n    {\n      node: { type: 'element', tagName: 'article' },\n      start: 'element[tagName=hr]',\n      transform: (article) => {\n        article.children.splice(0, 1)\n        let heading = article.children.find((n) => n.tagName === 'h2')\n        if (heading) {\n          article.properties = { ...heading.properties, title: toString(heading) }\n          heading.properties = {}\n        } else {\n          article.properties = {}\n        }\n        return article\n      },\n    },\n  ],\n\n  mdxAnnotations.rehype,\n  rehypeParseCodeBlocks,\n  rehypeShiki,\n  rehypeSlugify,\n  rehypeMdxTitle,\n  [\n    rehypeAddMDXExports,\n    (tree) => ({\n      sections: `[${getSections(tree).join()}]`,\n    }),\n  ],\n]\n"
  },
  {
    "path": "docs_src/mdx/remark.mjs",
    "content": "import { mdxAnnotations } from 'mdx-annotations'\nimport remarkGfm from 'remark-gfm'\nimport remarkUnwrapImages from 'remark-unwrap-images'\n\nexport const remarkPlugins = [\n  mdxAnnotations.remark,\n  remarkGfm,\n  remarkUnwrapImages,\n]\n"
  },
  {
    "path": "docs_src/next.config.mjs",
    "content": "import nextMDX from '@next/mdx'\nimport { remarkPlugins } from './mdx/remark.mjs'\nimport { rehypePlugins } from './mdx/rehype.mjs'\nimport { recmaPlugins } from './mdx/recma.mjs'\n\nconst withMDX = nextMDX({\n  options: {\n    remarkPlugins,\n    rehypePlugins,\n    recmaPlugins,\n    providerImportSource: '@mdx-js/react',\n  },\n})\n\n/** @type {import('next').NextConfig} */\nconst nextConfig = {\n  reactStrictMode: true,\n  pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'mdx'],\n  experimental: {\n    scrollRestoration: true,\n  },\n  i18n: {\n    locales: ['en', 'zh'],\n    defaultLocale: 'en',\n    localeDetection: false,\n  },\n  async redirects() {\n    return [\n      {\n        source: '/documentation',\n        destination: '/documentation/en',\n        permanent: false,\n      },\n    ]\n  },\n}\n\nexport default withMDX(nextConfig)\n"
  },
  {
    "path": "docs_src/package.json",
    "content": "{\n  \"name\": \"tailwindui-template\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev\",\n    \"build\": \"next build\",\n    \"start\": \"next start\",\n    \"lint\": \"next lint\"\n  },\n  \"browserslist\": \"defaults, not ie <= 11\",\n  \"dependencies\": {\n    \"@algolia/autocomplete-core\": \"^1.9.3\",\n    \"@algolia/autocomplete-preset-algolia\": \"^1.9.3\",\n    \"@headlessui/react\": \"^1.7.15\",\n    \"@heroicons/react\": \"^2.0.18\",\n    \"@mapbox/rehype-prism\": \"^0.8.0\",\n    \"@mdx-js/loader\": \"^2.1.5\",\n    \"@mdx-js/react\": \"^2.1.5\",\n    \"@next/mdx\": \"^13.0.2\",\n    \"@sindresorhus/slugify\": \"^2.2.1\",\n    \"@tailwindcss/typography\": \"^0.5.4\",\n    \"@vercel/analytics\": \"^1.0.2\",\n    \"algoliasearch\": \"^4.17.2\",\n    \"autoprefixer\": \"^10.4.12\",\n    \"axios\": \"^1.4.0\",\n    \"clsx\": \"^1.2.1\",\n    \"fast-glob\": \"^3.2.11\",\n    \"feed\": \"^4.2.2\",\n    \"focus-visible\": \"^5.2.0\",\n    \"framer-motion\": \"^10.12.16\",\n    \"highlight.js\": \"^11.8.0\",\n    \"mdx-annotations\": \"^0.1.3\",\n    \"meilisearch\": \"^0.33.0\",\n    \"next\": \"13.4.2\",\n    \"next-mdx-remote\": \"^6.0.0\",\n    \"next-router-mock\": \"^0.9.3\",\n    \"postcss-focus-visible\": \"^6.0.4\",\n    \"prism-themes\": \"^1.9.0\",\n    \"prismjs\": \"^1.29.0\",\n    \"react\": \"18.2.0\",\n    \"react-dom\": \"18.2.0\",\n    \"react-markdown\": \"^8.0.7\",\n    \"recma-import-images\": \"^0.0.3\",\n    \"recma-nextjs-static-props\": \"^1.0.0\",\n    \"rehype-autolink-headings\": \"^6.1.1\",\n    \"rehype-mdx-title\": \"^2.0.0\",\n    \"rehype-slug\": \"^5.1.0\",\n    \"remark-gfm\": \"^3.0.1\",\n    \"remark-rehype-wrap\": \"^0.0.2\",\n    \"remark-unwrap-images\": \"^3.0.1\",\n    \"shiki\": \"^0.14.2\",\n    \"tailwindcss\": \"^3.3.0\",\n    \"zustand\": \"^4.3.8\"\n  },\n  \"devDependencies\": {\n    \"eslint\": \"8.26.0\",\n    \"eslint-config-next\": \"13.0.2\",\n    \"prettier\": \"^2.8.7\",\n    \"prettier-plugin-tailwindcss\": \"^0.2.6\"\n  }\n}\n"
  },
  {
    "path": "docs_src/postcss.config.js",
    "content": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    'postcss-focus-visible': {\n      replaceWith: '[data-focus-visible-added]',\n    },\n    autoprefixer: {},\n  },\n}\n"
  },
  {
    "path": "docs_src/prettier.config.js",
    "content": "module.exports = {\n  singleQuote: true,\n  semi: false,\n  plugins: [require('prettier-plugin-tailwindcss')],\n}\n"
  },
  {
    "path": "docs_src/public/funding.json",
    "content": "{\n  \"version\": \"v1.0.0\",\n  \"entity\": {\n    \"type\": \"individual\",\n    \"role\": \"owner\",\n    \"name\": \"Sanskar Jethi\",\n    \"email\": \"sansyrox@gmail.com\",\n    \"phone\": \"\",\n    \"description\": \"Sanskar is a FOSS engineer who created Robyn and Starfyre. Sanskar has created software for over half his life and used it for almost all of his.\",\n    \"webpageUrl\": {\n      \"url\": \"https://robyn.tech/\"\n    }\n  },\n  \"projects\": [\n    {\n      \"guid\": \"robyn\",\n      \"name\": \"robyn\",\n      \"description\": \"Robyn is one of the fastest Python web frameworks, which comes with a built in web server and a Rust runtime.\",\n      \"webpageUrl\": {\n        \"url\": \"https://robyn.tech/\"\n      },\n      \"repositoryUrl\": {\n        \"url\": \"https://github.com/sparckles/Robyn\",\n        \"wellKnown\": \"https://github.com/sparckles/Robyn/blob/main/.well-known/funding-manifest-urls\"\n      },\n      \"licenses\": [\"BSD 2-Clause \\\"Simplified\\\" License\"],\n      \"tags\": [\"programming\", \"python\", \"rust\", \"web\", \"backend\", \"async\"]\n    }\n  ],\n  \"funding\": {\n    \"channels\": [\n      {\n        \"guid\": \"mybank\",\n        \"type\": \"bank\",\n        \"address\": \"\",\n        \"description\": \"Send me an email to get my bank details\"\n      }\n    ],\n    \"plans\": [\n      {\n        \"guid\": \"mybank\",\n        \"status\": \"active\",\n        \"name\": \"Support Maintainer Part Time\",\n        \"description\": \"Support the maintainer for his work on Robyn part time.\",\n        \"amount\": 500,\n        \"currency\": \"GBP\",\n        \"frequency\": \"monthly\",\n        \"channels\": [\"mybank\"]\n      },\n      {\n        \"guid\": \"mybank\",\n        \"status\": \"active\",\n        \"name\": \"Support Maintainer Full Time\",\n        \"description\": \"Support the maintainer for his work on Robyn full time.\",\n        \"amount\": 3000,\n        \"currency\": \"GBP\",\n        \"frequency\": \"monthly\",\n        \"channels\": [\"mybank\"]\n      },\n      {\n        \"guid\": \"150\",\n        \"status\": \"active\",\n        \"name\": \" Support Contributor Part Time\",\n        \"description\": \"Support for one contributor per month\",\n        \"amount\": 150,\n        \"currency\": \"GBP\",\n        \"frequency\": \"monthly\",\n        \"channels\": [\"mybank\"]\n      },\n      {\n        \"guid\": \"500\",\n        \"status\": \"active\",\n        \"name\": \" Support Contributor Full Time\",\n        \"description\": \"Support for one contributor per month\",\n        \"amount\": 500,\n        \"currency\": \"GBP\",\n        \"frequency\": \"monthly\",\n        \"channels\": [\"mybank\"]\n      }\n    ],\n    \"history\": []\n  }\n}\n"
  },
  {
    "path": "docs_src/public/llms.txt",
    "content": "# Robyn\n\n> Robyn is a high-performance, community-driven, and innovator-friendly async web framework for Python with a Rust runtime. It combines Python's ease of use with Rust's performance.\n\n## Quick Facts\n\n- Version: 0.79.0\n- Python: >= 3.10\n- License: BSD 2.0\n- Repository: https://github.com/sparckles/robyn\n- Documentation: https://robyn.tech/documentation\n- Discord: https://discord.gg/rkERZ5eNU8\n\n## Installation\n\n```bash\npip install robyn\n```\n\n## Basic Usage\n\n```python\nfrom robyn import Robyn\n\napp = Robyn(__file__)\n\n@app.get(\"/\")\nasync def index(request):\n    return \"Hello, World!\"\n\napp.start(port=8080)\n```\n\n## Key Features\n\n- **Rust Runtime**: Core server written in Rust using actix-web for high performance\n- **Async/Sync Support**: Both async and sync route handlers supported\n- **Multi-Process Scaling**: Built-in multiprocess execution via `--processes` and `--workers`\n- **WebSockets**: Native WebSocket support\n- **Middlewares**: Before/after request middlewares\n- **Dependency Injection**: Built-in DI system\n- **OpenAPI/Swagger**: Automatic OpenAPI documentation generation\n- **Hot Reloading**: Development mode with `--dev` flag\n- **AI Agents**: Built-in AI agent routing via `robyn.ai`\n- **MCP Support**: Model Context Protocol server capabilities via `app.mcp`\n- **Templating**: Jinja2 templating support (optional)\n- **CORS**: Built-in CORS helper via `ALLOW_CORS()`\n- **Authentication**: AuthenticationHandler base class for custom auth\n- **Static Files**: Directory serving via `app.serve_directory()`\n- **SSE**: Server-Sent Events support via `SSEResponse`\n- **Easy Access Parameters**: Typed path/query params with automatic coercion in handler signatures\n- **Direct Rust Integration**: Embed Rust code directly in routes\n\n## Project Structure\n\n```\nrobyn/\n├── src/                    # Rust source code\n│   ├── lib.rs              # PyO3 module entry point\n│   ├── server.rs           # Main HTTP server implementation\n│   ├── types/              # Request, Response, Headers, Cookie types\n│   ├── routers/            # HTTP, WebSocket, middleware routers\n│   ├── executors/          # Route execution handlers\n│   └── websockets/         # WebSocket implementation\n├── robyn/                  # Python package\n│   ├── __init__.py         # Main Robyn and SubRouter classes\n│   ├── router.py           # Python router implementation\n│   ├── authentication.py   # AuthenticationHandler\n│   ├── dependency_injection.py\n│   ├── openapi.py          # OpenAPI generation\n│   ├── mcp.py              # MCP protocol support\n│   ├── ai.py               # AI agent support\n│   ├── responses.py        # Response helpers (serve_file, html, SSE)\n│   ├── ws.py               # WebSocket class\n│   └── robyn.pyi           # Type stubs\n├── integration_tests/      # Integration test suite\n├── unit_tests/             # Unit test suite\n├── docs_src/               # Documentation (Next.js)\n├── granian/                # Bundled Granian server (fork)\n└── examples/               # Example applications\n```\n\n## Core Classes\n\n### Robyn / SubRouter\nMain application class and sub-router for modular routes.\n\n```python\nfrom robyn import Robyn, SubRouter\n\napp = Robyn(__file__)\napi = SubRouter(__file__, prefix=\"/api\")\n\n@api.get(\"/users\")\ndef get_users(request):\n    return {\"users\": []}\n\napp.include_router(api)\n```\n\n### Request Object\n```python\nrequest.method      # HTTP method\nrequest.url         # Url object (scheme, host, path)\nrequest.headers     # Headers dict-like\nrequest.query_params # QueryParams\nrequest.path_params  # Dict of URL params\nrequest.body        # Raw bytes\nrequest.json()      # Parse JSON body\nrequest.form_data   # Multipart form data\nrequest.ip_addr     # Client IP\nrequest.identity    # Identity (if authenticated)\n```\n\n### Response Object\n```python\nfrom robyn import Response\n\nResponse(\n    status_code=200,\n    headers={\"Content-Type\": \"application/json\"},\n    description=\"body content\"  # or body bytes\n)\n```\n\n### Decorators\n```python\n@app.get(\"/path\")\n@app.post(\"/path\")\n@app.put(\"/path\")\n@app.delete(\"/path\")\n@app.patch(\"/path\")\n@app.head(\"/path\")\n@app.options(\"/path\")\n\n@app.before_request(\"/path\")  # Middleware before\n@app.after_request(\"/path\")   # Middleware after\n\n@app.startup_handler         # Server startup\n@app.shutdown_handler        # Server shutdown\n```\n\n### WebSockets\n```python\nfrom robyn import WebSocketDisconnect\n\n@app.websocket(\"/ws\")\nasync def handler(websocket):\n    try:\n        while True:\n            msg = await websocket.receive_text()\n            await websocket.send_text(f\"Echo: {msg}\")\n    except WebSocketDisconnect:\n        pass\n\n@handler.on_connect\ndef on_connect(websocket):\n    return \"Connected\"\n\n@handler.on_close\ndef on_close(websocket):\n    return \"Closed\"\n```\n\n### Easy Access Parameters\nDeclare typed path and query parameters directly in handler signatures. Works for both HTTP and WebSocket handlers.\n\n```python\nfrom typing import List, Optional\n\n# HTTP: path params + query params with type coercion\n@app.get(\"/items/:id\")\nasync def get_item(id: int, q: str, page: int = 1):\n    return {\"id\": id, \"q\": q, \"page\": page}\n\n# Optional, List, and bool params\n@app.get(\"/search\")\ndef search(name: str, tags: List[str], active: bool = False, age: Optional[int] = None):\n    return {\"name\": name, \"tags\": tags, \"active\": active, \"age\": age}\n\n# WebSocket: typed query params on handler and callbacks\n@app.websocket(\"/ws\")\nasync def handler(websocket, room: str = \"default\", page: int = 1):\n    while True:\n        msg = await websocket.receive_text()\n        await websocket.send_text(f\"room={room} page={page} msg={msg}\")\n\n@handler.on_connect\ndef on_connect(websocket, room: str = \"default\"):\n    return f\"connected to {room}\"\n```\n\n### MCP (Model Context Protocol)\n```python\n@app.mcp.resource(\"time://current\")\ndef get_time():\n    return datetime.now().isoformat()\n\n@app.mcp.tool(name=\"calc\", description=\"Calculate\", input_schema={...})\ndef calculate(args):\n    return eval(args[\"expression\"])\n\n@app.mcp.prompt(name=\"explain\", description=\"Explain code\", arguments=[...])\ndef explain_prompt(args):\n    return f\"Please explain: {args['code']}\"\n```\n\n## CLI Commands\n\n```bash\npython app.py                     # Start server\npython app.py --dev               # Development mode (hot reload)\npython app.py --processes 4       # Multi-process\npython app.py --workers 2         # Workers per process\npython app.py --log-level DEBUG   # Log level\npython app.py --open-browser      # Open browser on start\npython app.py --create            # Create new project scaffold\npython app.py --docs              # Open documentation\n```\n\n## Development Setup\n\n```bash\n# Clone\ngit clone https://github.com/sparckles/robyn.git\ncd robyn\n\n# Virtual environment\npython3 -m venv .venv && source .venv/bin/activate\n\n# Install tools\npip install pre-commit poetry maturin\n\n# Install dependencies\npoetry install --with dev --with test\n\n# Build Rust extension\nmaturin develop\n\n# Run tests\npytest\n```\n\n## Key Dependencies\n\n- **PyO3**: Rust-Python bindings\n- **actix-web**: Rust HTTP server (via cookie crate)\n- **orjson**: Fast JSON serialization\n- **multiprocess**: Multi-process support\n- **uvloop**: Fast event loop (non-Windows)\n- **watchdog**: File watching for hot reload\n\n## Configuration\n\nEnvironment variables:\n- `ROBYN_HOST`: Server host (default: 127.0.0.1)\n- `ROBYN_PORT`: Server port (default: 8080)\n- `ROBYN_DEV_MODE`: Enable dev mode\n- `ROBYN_BROWSER_OPEN`: Open browser on start\n- `ROBYN_CLIENT_TIMEOUT`: Client timeout seconds\n- `ROBYN_KEEP_ALIVE_TIMEOUT`: Keep-alive timeout\n\n## Documentation Structure\n\nMain docs at `docs_src/src/pages/documentation/`:\n- `api_reference/getting_started.mdx` - Quick start guide\n- `api_reference/request_object.mdx` - Request handling\n- `api_reference/middlewares.mdx` - Middleware usage\n- `api_reference/websockets.mdx` - WebSocket guide\n- `api_reference/authentication.mdx` - Auth patterns\n- `api_reference/openapi.mdx` - OpenAPI docs\n- `api_reference/agents.mdx` - AI agent integration\n- `api_reference/mcps.mdx` - MCP server guide\n- `example_app/` - Full example application tutorial\n"
  },
  {
    "path": "docs_src/src/components/Button.jsx",
    "content": "import Link from 'next/link'\nimport clsx from 'clsx'\n\nconst variantStyles = {\n  primary:\n    'font-semibold text-zinc-100 bg-zinc-700 hover:bg-zinc-600 active:bg-zinc-700 active:text-zinc-100/70',\n  secondary:\n    'font-medium bg-zinc-800/50 text-zinc-300 hover:bg-zinc-800 hover:text-zinc-50 active:bg-zinc-800/50 active:text-zinc-50/70',\n}\n\nexport function Button({ variant = 'primary', className, href, ...props }) {\n  className = clsx(\n    'inline-flex items-center gap-2 justify-center rounded-md py-2 px-3 text-sm outline-offset-2 transition active:transition-none',\n    variantStyles[variant],\n    className\n  )\n\n  return href ? (\n    <Link href={href} className={className} {...props} />\n  ) : (\n    <button className={className} {...props} />\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/Card.jsx",
    "content": "import Link from 'next/link'\nimport clsx from 'clsx'\n\nfunction ChevronRightIcon(props) {\n  return (\n    <svg viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\" {...props}>\n      <path\n        d=\"M6.75 5.75 9.25 8l-2.5 2.25\"\n        strokeWidth=\"1.5\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n    </svg>\n  )\n}\n\nexport function Card({ as: Component = 'div', className, children }) {\n  return (\n    <Component\n      className={clsx(className, 'group relative flex flex-col items-start')}\n    >\n      {children}\n    </Component>\n  )\n}\n\nCard.Link = function CardLink({ children, ...props }) {\n  return (\n    <>\n      <div className=\"absolute -inset-x-4 -inset-y-6 z-0 scale-95  bg-zinc-800/50 opacity-0 transition group-hover:scale-100 group-hover:opacity-100 sm:-inset-x-6 sm:rounded-2xl\" />\n      <Link {...props}>\n        <span className=\"absolute -inset-x-4 -inset-y-6 z-20 sm:-inset-x-6 sm:rounded-2xl\" />\n        <span className=\"relative z-10\">{children}</span>\n      </Link>\n    </>\n  )\n}\n\nCard.Title = function CardTitle({ as: Component = 'h2', href, children }) {\n  return (\n    <Component className=\"text-base font-semibold tracking-tight  text-zinc-100\">\n      {href ? <Card.Link href={href}>{children}</Card.Link> : children}\n    </Component>\n  )\n}\n\nCard.Description = function CardDescription({ children }) {\n  return <p className=\"relative z-10 mt-2 text-sm text-zinc-400\">{children}</p>\n}\n\nCard.Cta = function CardCta({ children }) {\n  return (\n    <div\n      aria-hidden=\"true\"\n      className=\"relative z-10 mt-4 flex items-center text-sm font-medium text-yellow-500\"\n    >\n      {children}\n      <ChevronRightIcon className=\"ml-1 h-4 w-4 stroke-current\" />\n    </div>\n  )\n}\n\nCard.Eyebrow = function CardEyebrow({\n  as: Component = 'p',\n  decorate = false,\n  className,\n  children,\n  ...props\n}) {\n  return (\n    <Component\n      className={clsx(\n        className,\n        'relative z-10 order-first mb-3 flex items-center text-sm text-zinc-500',\n        decorate && 'pl-3.5'\n      )}\n      {...props}\n    >\n      {decorate && (\n        <span\n          className=\"absolute inset-y-0 left-0 flex items-center\"\n          aria-hidden=\"true\"\n        >\n          <span className=\"h-4 w-0.5 rounded-full bg-zinc-500\" />\n        </span>\n      )}\n      {children}\n    </Component>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/Container.jsx",
    "content": "import { forwardRef } from 'react'\nimport clsx from 'clsx'\n\nconst OuterContainer = forwardRef(function OuterContainer(\n  { className, children, ...props },\n  ref\n) {\n  return (\n    <div ref={ref} className={clsx('sm:px-8', className)} {...props}>\n      <div className=\"mx-auto max-w-7xl lg:px-8\">{children}</div>\n    </div>\n  )\n})\n\nconst InnerContainer = forwardRef(function InnerContainer(\n  { className, children, ...props },\n  ref\n) {\n  return (\n    <div\n      ref={ref}\n      className={clsx('relative px-4 sm:px-8 lg:px-12', className)}\n      {...props}\n    >\n      <div className=\"mx-auto max-w-3xl lg:max-w-5xl\">{children}</div>\n    </div>\n  )\n})\n\nexport const Container = forwardRef(function Container(\n  { children, ...props },\n  ref\n) {\n  return (\n    <OuterContainer ref={ref} {...props}>\n      <InnerContainer>{children}</InnerContainer>\n    </OuterContainer>\n  )\n})\n\nContainer.Outer = OuterContainer\nContainer.Inner = InnerContainer\n"
  },
  {
    "path": "docs_src/src/components/Footer.jsx",
    "content": "import Link from 'next/link'\n\nimport { Container } from '@/components/Container'\nimport { GitHubIcon } from './SocialIcons'\n\nfunction NavLink({ href, children, ...props }) {\n  return (\n    <Link {...props} href={href} className=\"transition hover:text-yellow-400\">\n      {children}\n    </Link>\n  )\n}\n\nexport function GithubButton() {\n  return (\n    <button\n      type=\"button\"\n      aria-label=\"Toggle dark mode\"\n      className=\"group rounded-full border-2 border-yellow-500 bg-zinc-800/90 px-3 py-2 shadow-lg shadow-zinc-800/5 ring-1 ring-white/10  backdrop-blur transition hover:ring-white/20\"\n    >\n      <GitHubIcon className=\"block h-6 w-6 fill-zinc-700 stroke-zinc-500 transition group-hover:stroke-zinc-400\" />\n    </button>\n  )\n}\n\nexport function Footer() {\n  return (\n    <>\n      <Container\n        className=\"bottom-2 right-2 z-40\"\n        style={{ position: 'fixed' }}\n      >\n        <div className=\"flex md:flex-1\">\n          <div className=\"pointer-events-auto \">\n            <Link target=\"_blank\" href=\"https://github.com/sparckles/robyn\">\n              <GithubButton />\n            </Link>\n          </div>\n        </div>\n      </Container>\n\n      <footer className=\"mt-32\">\n        <Container.Outer>\n          <div className=\"border-t  border-zinc-700/40 pb-16 pt-10\">\n            <Container.Inner>\n              <div className=\"flex flex-col items-center justify-between gap-6 sm:flex-row\">\n                <div className=\"flex flex-wrap justify-center gap-x-6 gap-y-1 text-sm font-medium text-zinc-200\">\n                  <NavLink href=\"/\">Home</NavLink>\n                  <NavLink href=\"/documentation\">Documentation</NavLink>\n                  <NavLink href=\"/releases\">Releases</NavLink>\n                  <NavLink href=\"/community\">Community</NavLink>\n                  <NavLink href=\"https://discord.gg/rkERZ5eNU8\" target=\"_blank\">\n                    Discord\n                  </NavLink>\n                </div>\n                <p className=\"text-sm text-zinc-500\">\n                  &copy; {new Date().getFullYear()} Sparckles OSS. All rights\n                  reserved.\n                </p>\n              </div>\n            </Container.Inner>\n          </div>\n        </Container.Outer>\n      </footer>\n    </>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/Header.jsx",
    "content": "import { Fragment, useEffect, useRef, useState } from 'react'\nimport Image from 'next/image'\nimport Link from 'next/link'\nimport { useRouter } from 'next/router'\nimport { Popover, Transition } from '@headlessui/react'\nimport clsx from 'clsx'\n\nimport { Container } from '@/components/Container'\nimport robynLogo from '@/images/robyn_logo.jpg'\nimport { MobileSearch, Search } from '@/components/documentation/Search'\n\nfunction CloseIcon(props) {\n  return (\n    <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" {...props}>\n      <path\n        d=\"m17.25 6.75-10.5 10.5M6.75 6.75l10.5 10.5\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeWidth=\"1.5\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n    </svg>\n  )\n}\n\nfunction ChevronDownIcon(props) {\n  return (\n    <svg viewBox=\"0 0 8 6\" aria-hidden=\"true\" {...props}>\n      <path\n        d=\"M1.75 1.75 4 4.25l2.25-2.5\"\n        fill=\"none\"\n        strokeWidth=\"1.5\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n    </svg>\n  )\n}\n\nfunction MobileNavItem({ href, children }) {\n  return (\n    <li>\n      <Popover.Button as={Link} href={href} className=\"block py-2\">\n        {children}\n      </Popover.Button>\n    </li>\n  )\n}\n\nfunction MobileNavigation(props) {\n  return (\n    <Popover {...props}>\n      <Popover.Button className=\"group flex items-center rounded-full bg-zinc-800/90 px-4 py-2 text-sm font-medium text-zinc-200 shadow-lg shadow-zinc-800/5 ring-1  ring-white/10 backdrop-blur hover:ring-white/20\">\n        Menu\n        <ChevronDownIcon className=\"ml-3 h-auto w-2 stroke-zinc-500 group-hover:stroke-zinc-400\" />\n      </Popover.Button>\n      <Transition.Root>\n        <Transition.Child\n          as={Fragment}\n          enter=\"duration-150 ease-out\"\n          enterFrom=\"opacity-0\"\n          enterTo=\"opacity-100\"\n          leave=\"duration-150 ease-in\"\n          leaveFrom=\"opacity-100\"\n          leaveTo=\"opacity-0\"\n        >\n          <Popover.Overlay className=\"fixed inset-0 z-50 bg-black/80 backdrop-blur-sm\" />\n        </Transition.Child>\n        <Transition.Child\n          as={Fragment}\n          enter=\"duration-150 ease-out\"\n          enterFrom=\"opacity-0 scale-95\"\n          enterTo=\"opacity-100 scale-100\"\n          leave=\"duration-150 ease-in\"\n          leaveFrom=\"opacity-100 scale-100\"\n          leaveTo=\"opacity-0 scale-95\"\n        >\n          <Popover.Panel\n            focus\n            className=\"fixed inset-x-4 top-8 z-50 origin-top rounded-3xl bg-zinc-900 p-8 ring-1 ring-zinc-800\"\n          >\n            <div className=\"flex flex-row-reverse items-center justify-between\">\n              <Popover.Button aria-label=\"Close menu\" className=\"-m-1 p-1\">\n                <CloseIcon className=\"h-6 w-6 text-zinc-400\" />\n              </Popover.Button>\n              <h2 className=\"text-sm font-medium text-zinc-400\">Navigation</h2>\n            </div>\n            <nav className=\"mt-6\">\n              <ul className=\"-my-2 divide-y divide-zinc-100/5 text-base text-zinc-300\">\n                <MobileNavItem href=\"/documentation\">\n                  Documentation\n                </MobileNavItem>\n                <MobileNavItem href=\"/releases\">Releases</MobileNavItem>\n                <MobileNavItem href=\"/community\">Community</MobileNavItem>\n                <MobileNavItem href=\"https://github.com/sparckles/robyn\">\n                  GitHub\n                </MobileNavItem>\n              </ul>\n            </nav>\n          </Popover.Panel>\n        </Transition.Child>\n      </Transition.Root>\n    </Popover>\n  )\n}\n\nfunction NavItem({ href, children, ...props }) {\n  let isActive = useRouter().pathname === href\n\n  return (\n    <li>\n      <Link\n        href={href}\n        className={clsx(\n          'relative block px-3 py-2 transition',\n          isActive ? 'text-yellow-500' : 'hover:text-yellow-500'\n        )}\n        {...props}\n      >\n        {children}\n        {isActive && (\n          <span className=\"absolute inset-x-1 -bottom-px h-px bg-gradient-to-r from-orange-800/0 via-yellow-800/40 to-yellow-500/0 \" />\n        )}\n      </Link>\n    </li>\n  )\n}\n\nfunction DesktopNavigation(props) {\n  return (\n    <nav {...props}>\n      <ul className=\"flex rounded-full bg-zinc-800/90 px-3 text-sm  font-medium text-zinc-200 shadow-lg  shadow-zinc-800/5 ring-1 ring-white/10 backdrop-blur\">\n        <NavItem href=\"/documentation\">Documentation</NavItem>\n        <NavItem href=\"/releases\">Releases</NavItem>\n        <NavItem href=\"/community\">Community</NavItem>\n        <NavItem href=\"https://discord.gg/rkERZ5eNU8\" target=\"_blank\">\n          Discord\n        </NavItem>\n      </ul>\n    </nav>\n  )\n}\n\nfunction GitHubStars() {\n  const [stars, setStars] = useState('6.1k')\n  const [loading, setLoading] = useState(false)\n\n  const fetchStars = async () => {\n    if (loading) return\n    \n    setLoading(true)\n    try {\n      const response = await fetch('https://api.github.com/repos/sparckles/robyn')\n      const data = await response.json()\n      \n      if (data.stargazers_count) {\n        const count = data.stargazers_count\n        const formatted = count >= 1000 \n          ? `${(count / 1000).toFixed(1)}k` \n          : count.toString()\n        setStars(formatted)\n      }\n    } catch (error) {\n      console.error('Failed to fetch GitHub stars:', error)\n    } finally {\n      setLoading(false)\n    }\n  }\n\n  useEffect(() => {\n    fetchStars()\n    \n    const interval = setInterval(fetchStars, 300000)\n    \n    return () => clearInterval(interval)\n  }, [])\n\n  return (\n    <a\n      href=\"https://github.com/sparckles/robyn\"\n      target=\"_blank\"\n      rel=\"noopener noreferrer\"\n      className=\"pointer-events-auto flex items-center gap-2 rounded-full bg-zinc-800/90 px-3 py-2 text-sm font-medium text-zinc-200 shadow-lg shadow-zinc-800/5 ring-1 ring-white/10 backdrop-blur hover:ring-white/20 transition-all\"\n      onClick={fetchStars}\n    >\n      <svg className=\"h-4 w-4\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n        <path d=\"M12 0C5.374 0 0 5.373 0 12 0 17.302 3.438 21.8 8.207 23.387c.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.509 11.509 0 0112 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576C20.566 21.797 24 17.3 24 12c0-6.627-5.373-12-12-12z\"/>\n      </svg>\n      <svg className=\"h-4 w-4\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\n        <path\n          fillRule=\"evenodd\"\n          d=\"M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z\"\n          clipRule=\"evenodd\"\n        />\n      </svg>\n      <span className={loading ? 'opacity-60' : ''}>{stars}</span>\n    </a>\n  )\n}\n\nfunction clamp(number, a, b) {\n  let min = Math.min(a, b)\n  let max = Math.max(a, b)\n  return Math.min(Math.max(number, min), max)\n}\n\nfunction Avatar({ large = false, className, ...props }) {\n  return (\n    <Link\n      href=\"/\"\n      aria-label=\"Home\"\n      className={clsx(className, 'pointer-events-auto')}\n      {...props}\n    >\n      <Image\n        src={robynLogo}\n        alt=\"\"\n        sizes={large ? '4rem' : '2.25rem'}\n        className={clsx(\n          'rounded-md bg-zinc-800 object-cover',\n          large ? 'h-16 w-16' : 'h-9 w-9'\n        )}\n        priority\n      />\n    </Link>\n  )\n}\n\nexport function Header() {\n  let isHomePage = useRouter().pathname === '/'\n\n  let headerRef = useRef()\n  let avatarRef = useRef()\n  let isInitial = useRef(true)\n\n  useEffect(() => {\n    let downDelay = avatarRef.current?.offsetTop ?? 0\n    let upDelay = 64\n\n    function setProperty(property, value) {\n      document.documentElement.style.setProperty(property, value)\n    }\n\n    function removeProperty(property) {\n      document.documentElement.style.removeProperty(property)\n    }\n\n    function updateHeaderStyles() {\n      let { top, height } = headerRef.current.getBoundingClientRect()\n      let scrollY = clamp(\n        window.scrollY,\n        0,\n        document.body.scrollHeight - window.innerHeight\n      )\n\n      if (isInitial.current) {\n        setProperty('--header-position', 'sticky')\n      }\n\n      setProperty('--content-offset', `${downDelay}px`)\n\n      if (isInitial.current || scrollY < downDelay) {\n        setProperty('--header-height', `${downDelay + height}px`)\n        setProperty('--header-mb', `${-downDelay}px`)\n      } else if (top + height < -upDelay) {\n        let offset = Math.max(height, scrollY - upDelay)\n        setProperty('--header-height', `${offset}px`)\n        setProperty('--header-mb', `${height - offset}px`)\n      } else if (top === 0) {\n        setProperty('--header-height', `${scrollY + height}px`)\n        setProperty('--header-mb', `${-scrollY}px`)\n      }\n\n      if (top === 0 && scrollY > 0 && scrollY >= downDelay) {\n        setProperty('--header-inner-position', 'fixed')\n        removeProperty('--header-top')\n        removeProperty('--avatar-top')\n      } else {\n        removeProperty('--header-inner-position')\n        setProperty('--header-top', '0px')\n        setProperty('--avatar-top', '0px')\n      }\n    }\n\n    function updateStyles() {\n      updateHeaderStyles()\n      isInitial.current = false\n    }\n\n    updateStyles()\n    window.addEventListener('scroll', updateStyles, { passive: true })\n    window.addEventListener('resize', updateStyles)\n\n    return () => {\n      window.removeEventListener('scroll', updateStyles)\n      window.removeEventListener('resize', updateStyles)\n    }\n  }, [isHomePage])\n\n  return (\n    <>\n      <header\n        className=\"pointer-events-none relative z-50 flex flex-col\"\n        style={{\n          height: 'var(--header-height)',\n          marginBottom: 'var(--header-mb)',\n        }}\n      >\n        <div\n          ref={headerRef}\n          className=\"top-0 z-10 h-16 pt-6\"\n          style={{ position: 'var(--header-position)' }}\n        >\n          <Container\n            className=\"top-[var(--header-top,theme(spacing.6))] w-full\"\n            style={{ position: 'var(--header-inner-position)' }}\n          >\n            <div className=\"relative flex gap-4\">\n              <div className=\"flex flex-1\">{<Avatar />}</div>\n              <div className=\"flex flex-1 justify-end md:justify-center\">\n                <MobileNavigation className=\"pointer-events-auto md:hidden\" />\n                <DesktopNavigation className=\"pointer-events-auto hidden md:block\" />\n              </div>\n              <div className=\"flex justify-end md:flex-1\">\n                <div className=\"flex items-center gap-3\">\n                  <GitHubStars />\n                  <div className=\"pointer-events-auto\">\n                    <Search />\n                    <MobileSearch />\n                  </div>\n                </div>\n              </div>\n            </div>\n          </Container>\n        </div>\n      </header>\n      {isHomePage && <div style={{ height: 'var(--content-offset)' }} />}\n    </>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/Prose.jsx",
    "content": "import clsx from 'clsx'\n\nexport function Prose({ children, className }) {\n  return <div className={clsx(className, 'prose-invert')}>{children}</div>\n}\n"
  },
  {
    "path": "docs_src/src/components/Section.jsx",
    "content": "import { useId } from 'react'\n\nexport function Section({ title, children }) {\n  let id = useId()\n\n  return (\n    <section\n      aria-labelledby={id}\n      className=\"md:border-l md:border-zinc-700/40 md:pl-6\"\n    >\n      <div className=\"grid max-w-3xl grid-cols-1 items-baseline gap-y-8 md:grid-cols-4\">\n        <h2 id={id} className=\"text-sm font-semibold text-zinc-100\">\n          {title}\n        </h2>\n        <div className=\"md:col-span-3\">{children}</div>\n      </div>\n    </section>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/SimpleLayout.jsx",
    "content": "import { Container } from '@/components/Container'\n\nexport function SimpleLayout({ title, intro, children }) {\n  return (\n    <Container className=\"mt-16 sm:mt-32\">\n      <header className=\"max-w-2xl\">\n        <h1 className=\"text-4xl font-bold tracking-tight text-zinc-100 sm:text-5xl\">\n          {title}\n        </h1>\n        <p className=\"mt-6 text-base text-zinc-400\">{intro}</p>\n      </header>\n      <div className=\"mt-16 sm:mt-20\">{children}</div>\n    </Container>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/SocialIcons.jsx",
    "content": "export function TwitterIcon(props) {\n  return (\n    <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\" {...props}>\n      <path d=\"M20.055 7.983c.011.174.011.347.011.523 0 5.338-3.92 11.494-11.09 11.494v-.003A10.755 10.755 0 0 1 3 18.186c.308.038.618.057.928.058a7.655 7.655 0 0 0 4.841-1.733c-1.668-.032-3.13-1.16-3.642-2.805a3.753 3.753 0 0 0 1.76-.07C5.07 13.256 3.76 11.6 3.76 9.676v-.05a3.77 3.77 0 0 0 1.77.505C3.816 8.945 3.288 6.583 4.322 4.737c1.98 2.524 4.9 4.058 8.034 4.22a4.137 4.137 0 0 1 1.128-3.86A3.807 3.807 0 0 1 19 5.274a7.657 7.657 0 0 0 2.475-.98c-.29.934-.9 1.729-1.713 2.233A7.54 7.54 0 0 0 22 5.89a8.084 8.084 0 0 1-1.945 2.093Z\" />\n    </svg>\n  )\n}\n\nexport function InstagramIcon(props) {\n  return (\n    <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" {...props}>\n      <path d=\"M12 3c-2.444 0-2.75.01-3.71.054-.959.044-1.613.196-2.185.418A4.412 4.412 0 0 0 4.51 4.511c-.5.5-.809 1.002-1.039 1.594-.222.572-.374 1.226-.418 2.184C3.01 9.25 3 9.556 3 12s.01 2.75.054 3.71c.044.959.196 1.613.418 2.185.23.592.538 1.094 1.039 1.595.5.5 1.002.808 1.594 1.038.572.222 1.226.374 2.184.418C9.25 20.99 9.556 21 12 21s2.75-.01 3.71-.054c.959-.044 1.613-.196 2.185-.419a4.412 4.412 0 0 0 1.595-1.038c.5-.5.808-1.002 1.038-1.594.222-.572.374-1.226.418-2.184.044-.96.054-1.267.054-3.711s-.01-2.75-.054-3.71c-.044-.959-.196-1.613-.419-2.185A4.412 4.412 0 0 0 19.49 4.51c-.5-.5-1.002-.809-1.594-1.039-.572-.222-1.226-.374-2.184-.418C14.75 3.01 14.444 3 12 3Zm0 1.622c2.403 0 2.688.009 3.637.052.877.04 1.354.187 1.67.31.421.163.72.358 1.036.673.315.315.51.615.673 1.035.123.317.27.794.31 1.671.043.95.052 1.234.052 3.637s-.009 2.688-.052 3.637c-.04.877-.187 1.354-.31 1.67-.163.421-.358.72-.673 1.036a2.79 2.79 0 0 1-1.035.673c-.317.123-.794.27-1.671.31-.95.043-1.234.052-3.637.052s-2.688-.009-3.637-.052c-.877-.04-1.354-.187-1.67-.31a2.789 2.789 0 0 1-1.036-.673 2.79 2.79 0 0 1-.673-1.035c-.123-.317-.27-.794-.31-1.671-.043-.95-.052-1.234-.052-3.637s.009-2.688.052-3.637c.04-.877.187-1.354.31-1.67.163-.421.358-.72.673-1.036.315-.315.615-.51 1.035-.673.317-.123.794-.27 1.671-.31.95-.043 1.234-.052 3.637-.052Z\" />\n      <path d=\"M12 15a3 3 0 1 1 0-6 3 3 0 0 1 0 6Zm0-7.622a4.622 4.622 0 1 0 0 9.244 4.622 4.622 0 0 0 0-9.244Zm5.884-.182a1.08 1.08 0 1 1-2.16 0 1.08 1.08 0 0 1 2.16 0Z\" />\n    </svg>\n  )\n}\n\nexport function GitHubIcon(props) {\n  return (\n    <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" {...props}>\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M12 2C6.475 2 2 6.588 2 12.253c0 4.537 2.862 8.369 6.838 9.727.5.09.687-.218.687-.487 0-.243-.013-1.05-.013-1.91C7 20.059 6.35 18.957 6.15 18.38c-.113-.295-.6-1.205-1.025-1.448-.35-.192-.85-.667-.013-.68.788-.012 1.35.744 1.538 1.051.9 1.551 2.338 1.116 2.912.846.088-.666.35-1.115.638-1.371-2.225-.256-4.55-1.14-4.55-5.062 0-1.115.387-2.038 1.025-2.756-.1-.256-.45-1.307.1-2.717 0 0 .837-.269 2.75 1.051.8-.23 1.65-.346 2.5-.346.85 0 1.7.115 2.5.346 1.912-1.333 2.75-1.05 2.75-1.05.55 1.409.2 2.46.1 2.716.637.718 1.025 1.628 1.025 2.756 0 3.934-2.337 4.806-4.562 5.062.362.32.675.936.675 1.897 0 1.371-.013 2.473-.013 2.82 0 .268.188.589.688.486a10.039 10.039 0 0 0 4.932-3.74A10.447 10.447 0 0 0 22 12.253C22 6.588 17.525 2 12 2Z\"\n      />\n    </svg>\n  )\n}\n\nexport function LinkedInIcon(props) {\n  return (\n    <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" {...props}>\n      <path d=\"M18.335 18.339H15.67v-4.177c0-.996-.02-2.278-1.39-2.278-1.389 0-1.601 1.084-1.601 2.205v4.25h-2.666V9.75h2.56v1.17h.035c.358-.674 1.228-1.387 2.528-1.387 2.7 0 3.2 1.778 3.2 4.091v4.715zM7.003 8.575a1.546 1.546 0 01-1.548-1.549 1.548 1.548 0 111.547 1.549zm1.336 9.764H5.666V9.75H8.34v8.589zM19.67 3H4.329C3.593 3 3 3.58 3 4.297v15.406C3 20.42 3.594 21 4.328 21h15.338C20.4 21 21 20.42 21 19.703V4.297C21 3.58 20.4 3 19.666 3h.003z\" />\n    </svg>\n  )\n}\n\nexport function DiscordIcon(props) {\n  return (\n    <svg\n      style={{ color: '#738ADB' }}\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"32\"\n      height=\"32\"\n      fill=\"currentColor\"\n      class=\"bi bi-discord\"\n      viewBox=\"0 0 16 16\"\n      {...props}\n    >\n      <path\n        d=\"M13.545 2.907a13.227 13.227 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.19 12.19 0 0 0-3.658 0 8.258 8.258 0 0 0-.412-.833.051.051 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.041.041 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032c.001.014.01.028.021.037a13.276 13.276 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019c.308-.42.582-.863.818-1.329a.05.05 0 0 0-.01-.059.051.051 0 0 0-.018-.011 8.875 8.875 0 0 1-1.248-.595.05.05 0 0 1-.02-.066.051.051 0 0 1 .015-.019c.084-.063.168-.129.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.052.052 0 0 1 .053.007c.08.066.164.132.248.195a.051.051 0 0 1-.004.085 8.254 8.254 0 0 1-1.249.594.05.05 0 0 0-.03.03.052.052 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.235 13.235 0 0 0 4.001-2.02.049.049 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.034.034 0 0 0-.02-.019Zm-8.198 7.307c-.789 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612Zm5.316 0c-.788 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612Z\"\n        fill=\"blue\"\n      ></path>\n    </svg>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/Testimonials.jsx",
    "content": "import { useEffect } from 'react'\n\nlet testimonials = [\n  {\n    body: \"Robyn has revolutionized the way I develop web solutions. Its seamless integration of Python's async capabilities with a Rust runtime not only ensures reliability and scalability but also provides quick project setup, a delightful user experience, and robust plugin support. With its exceptional speed and multithreaded efficiency, Robyn's real-time communication through WebSockets and dynamic URL routing has empowered me to create highly performant and interactive applications while maintaining full control over navigation and workflows. A game-changer for modern web development!\",\n    author: {\n      name: 'Kunal Kushwaha',\n      handle: 'kunalstwt',\n      imageUrl: '/testimonials/kunalstwt.jpg',\n      title: 'DevRel manager at Civo',\n    },\n  },\n\n  {\n    body: \"Having worked with a company building a Rust based open source search engine for over a year, I strongly believe in the notion that rewriting software with Rust can significantly improve software performance. Sanskar's idea to recreate Flask with Rust, was just incredible. Having used Robyn myself, it is refreshing to see such a performant Python framework and just the amazing developer ecosystem around it. Yes it still new and being developed, but I can say this with confidence that given the underlying Rust-based multithreaded run time will provide immense performance for running high throughout applications. I am glad to be one of the early sponsors and adopters for Robyn!\",\n    author: {\n      name: 'Shivay Lamba',\n      handle: 'howdevelop',\n      imageUrl: '/testimonials/howdevelop.jpg',\n      title: 'Developer Experience Engineer at MeiliSearch',\n    },\n  },\n  {\n    body: \"I'm impressed with Robyn. It's a fast asynchronous web framework for the Python ecosystem that's built on top of Rust. The syntax is similar to other popular web frameworks, so it's easy to learn and be productive with. I've been using it to build web applications and services, and I'm really happy with the results. I'm also impressed with the Robyn community. They are very supportive and the developers are very responsive to feedback\",\n    author: {\n      name: 'Carlos A. Marcano Vargas',\n      handle: 'carlos_marcv',\n      imageUrl: '/testimonials/carlos_marcv.jpg',\n      title: 'Technical Writer',\n    },\n  },\n  // More testimonials...\n  {\n    body: 'Great to see a Community Driven Open Source project, achieve new heights! Robyn is built by the community for the community',\n    author: {\n      name: 'Eddie Jaoude',\n      handle: 'eddiejaoude',\n      imageUrl: '/testimonials/eddiejaoude.jpg',\n      title: 'Creator of EddieHub',\n    },\n  },\n  // More testimonials...\n  {\n    body: 'I used to be a Batman fan, but having met Robyn I now think the sidekick has become the hero. Free, OSS, straight forward and powerful, what is not to love?',\n    author: {\n      name: 'GrahamTheDev',\n      handle: 'GrahamTheDev',\n      imageUrl: '/testimonials/GrahamTheDev.jpg',\n      title: 'The Accessibility First DevRel ',\n    },\n  },\n  {\n    body: 'Having used both, Flask and Django for writing web applications in Python in the past, Robyn looks like their combined successor in terms of ergonomics and features available. Its reliance on a Rust runtime for performance and security is the cherry on the cake!',\n    author: {\n      name: 'Daniel Bodky',\n      handle: 'd_bodky',\n      imageUrl: '/testimonials/d_bodky.jpg',\n      title: 'Consultant, Trainer, Speaker @NETWAYS',\n    },\n  },\n  // More testimonials...\n  {\n    body: 'Robyn has made a big difference in my projects. Its flexible structure allows my work to adapt smoothly to my needs, even when I face complex challenges. The community-driven and open-source nature of Robyn makes it a welcoming place for developers like me. Plus, its simple yet powerful API has greatly streamlined my development process, reducing my wor oad. I highly recommend it!',\n    author: {\n      name: 'Julia Furst Morgado',\n      handle: 'juliafmorgado',\n      imageUrl: '/testimonials/juliafmorgado.jpg',\n      title: 'Global technologist @Veeam',\n    },\n  },\n  // More testimonials...\n  {\n    body: 'Robyn opens a new chapter in the Python web frameworks scene: the Rust powered one, where performance and safety are not the sole protagonists.',\n    author: {\n      name: 'Giovanni Barillari',\n      handle: 'gi0baro',\n      imageUrl: '/testimonials/gi0baro.jpeg',\n      title: 'Author of Granian and Emmett',\n    },\n  },\n  // More testimonials...\n  {\n    body: \"I collaborate with Robyn's team and I must say, Sanskar does an excellent job maintaining the community. The project as a whole is immensely beneficial, both for collaboration and its practical uses. The tool is impressive, easy to use and the entire community is very welcoming to first-time contributors\",\n    author: {\n      name: 'Jyoti Bisht',\n      handle: 'joeyousss',\n      imageUrl: '/testimonials/joeyousss.jpg',\n      title: 'Open Source Developer',\n    },\n  },\n  // More testimonials...\n  {\n    body: \"Robyn is a breath of fresh air in web development. Merging Python's simplicity with Rust's speed, it offers a seamless experience for developers. Its features are precisely what today's web projects need. I'm particularly impressed with its community focus; it's evident that everyone's voice matters in shaping Robyn's journey. In a sea of web frameworks, Robyn stands out not just for its tech but also for its heart.\",\n    author: {\n      name: 'Francesco Ciulla',\n      handle: 'FrancescoCiull4',\n      imageUrl: '/testimonials/FrancescoCiull4.jpg',\n      title: 'DevRel at daily.dev',\n    },\n  },\n  // More testimonials...\n]\n\n//shuflle testimonials\n\nfunction classNames(...classes) {\n  return classes.filter(Boolean).join(' ')\n}\n\nconst chunk = (arr, size) =>\n  Array.from({ length: Math.ceil(arr.length / size) }, (v, i) =>\n    arr.slice(i * size, i * size + size)\n  )\n\nexport default function Testimonials() {\n  let chunkedTestimonials = [];\n  \n  // Separate testimonials into groups of 3, 4, and 3\n  const firstGroup = chunk(testimonials.slice(0, 3), 3);\n  const secondGroup = chunk(testimonials.slice(3, 7), 4);\n  const thirdGroup = chunk(testimonials.slice(7), 3);\n  \n  // Concatenate the groups in the desired order\n  chunkedTestimonials = firstGroup.concat(secondGroup, thirdGroup);\n  return (\n    <div className=\"relative isolate  pb-32 pt-24 sm:pt-32\">\n      <div\n        className=\"absolute inset-x-0 top-1/2 -z-10 -translate-y-1/2 transform-gpu overflow-hidden opacity-30 blur-3xl\"\n        aria-hidden=\"true\"\n      >\n        <div\n          className=\"ml-[max(50%,38rem)] aspect-[1313/771] w-[82.0625rem] flex-none origin-top-right rotate-[30deg] bg-yellow-400 xl:mr-[calc(50%-12rem)]\"\n          style={{\n            clipPath:\n              'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)',\n          }}\n        />\n      </div>\n      <div\n        className=\"absolute inset-x-0 top-0 -z-10 flex transform-gpu overflow-hidden pt-32 opacity-25 blur-3xl sm:pt-40 xl:justify-end\"\n        aria-hidden=\"true\"\n      >\n        <div\n          className=\"ml-[-22rem] aspect-[1313/771] w-[82.0625rem] flex-none origin-top-right rotate-[30deg]  xl:ml-0 xl:mr-[calc(50%-12rem)]\"\n          style={{\n            clipPath:\n              'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)',\n          }}\n        />\n      </div>\n      <div className=\"mx-auto max-w-7xl px-6 lg:px-8\">\n        <div className=\"mx-auto max-w-xl text-center\">\n          <h2 className=\"text-lg font-semibold leading-8 tracking-tight text-yellow-400\">\n            Testimonials\n          </h2>\n          <p className=\"mt-2 text-3xl font-bold tracking-tight text-white sm:text-4xl\">\n            Some amazing people have said nice things about us.\n          </p>\n        </div>\n        <div className=\"mx-auto mt-16 grid max-w-2xl grid-cols-1 grid-rows-1 gap-8 text-sm leading-6 text-gray-900 sm:mt-20 sm:grid-cols-1 xl:mx-0 xl:max-w-none xl:grid-flow-col xl:grid-cols-3\">\n          {chunkedTestimonials.map((columnGroup, columnGroupIdx) => (\n            <div key={columnGroupIdx} className=\"space-y-8  \">\n              {columnGroup.map((testimonial) => (\n                <figure\n                  key={testimonial.author.handle}\n                  className=\"rounded-2xl bg-white/5 p-6 shadow-lg ring-1 ring-gray-900/5 duration-200 hover:bg-white/10\"\n                >\n                  <blockquote className=\"text-white\">\n                    <p>{`\"${testimonial.body}\"`}</p>\n                  </blockquote>\n                  <figcaption className=\"mt-6 flex items-center gap-x-4\">\n                    <img\n                      className=\"h-10 w-10 rounded-full bg-gray-50\"\n                      src={testimonial.author.imageUrl}\n                      alt=\"\"\n                    />\n                    <div>\n                      <div className=\"font-semibold text-white\">\n                        {testimonial.author.name}\n                      </div>\n                      <div className=\"text-yellow-400\">{`@${testimonial.author.handle}`}</div>\n                      <div className=\"font-normal text-white\">\n                        {testimonial.author.title}\n                      </div>\n                    </div>\n                  </figcaption>\n                </figure>\n              ))}\n            </div>\n          ))}\n        </div>\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/ApiDocs.jsx",
    "content": "import { Button } from '@/components/documentation/Button'\nimport { Heading } from '@/components/documentation/Heading'\n\nconst guides = [\n  {\n    href: '/documentation/en/api_reference',\n    name: 'Installation',\n    description: 'Start using Robyn in your project.',\n  },\n  {\n    href: '/documentation/en/api_reference/getting_started',\n    name: 'Getting Started',\n    description: 'Start with creating basic routes in Robyn.',\n  },\n  {\n    href: '/documentation/en/api_reference/request_object',\n    name: 'The Request Object',\n    description: 'Learn about the Request Object in Robyn.',\n  },\n  {\n    href: '/documentation/en/api_reference/robyn_env',\n    name: 'The Robyn Env file',\n    description: 'Learn about the Robyn variables',\n  },\n  {\n    href: '/documentation/en/api_reference/middlewares',\n    name: 'Middlewares, Events and Websockets',\n    description: 'Learn about Middlewares, Events and Websockets in Robyn.',\n  },\n  {\n    href: '/documentation/en/api_reference/authentication',\n    name: 'Authentication',\n    description: 'Learn about Authentication in Robyn.',\n  },\n  {\n    href: '/documentation/en/api_reference/const_requests',\n    name: 'Const Requests and Multi Core Scaling',\n    description: 'Learn about Const Requests and Multi Core Scaling in Robyn.',\n  },\n  {\n    href: '/documentation/en/api_reference/cors',\n    name: 'CORS',\n    description: 'CORS',\n  },\n  {\n    href: '/documentation/en/api_reference/templating',\n    name: 'Templating',\n    description: 'Learn about Templating in Robyn.',\n  },\n  {\n    href: '/documentation/en/api_reference/redirection',\n    name: 'Redirection',\n    description: 'Learn how to redirect requests to different endpoints.',\n  },\n  {\n    href: '/documentation/en/api_reference/file-uploads',\n    name: 'File Uploads',\n    description:\n      'Learn how to upload and download files to your server using Robyn.',\n  },\n  {\n    href: '/documentation/en/api_reference/form_data',\n    name: 'Form Data and Multi Part Form Data',\n    description: 'Learn how to handle form data.',\n  },\n  {\n    href: '/documentation/en/api_reference/websockets',\n    name: 'Websockets',\n    description: 'Learn how to use Websockets in Robyn.',\n  },\n  {\n    href: '/documentation/en/api_reference/server_sent_events',\n    name: 'Server-Sent Events',\n    description: 'Learn how to implement Server-Sent Events for real-time communication.',\n  },\n  {\n    href: '/documentation/en/api_reference/exceptions',\n    name: 'Exceptions',\n    description: 'Learn how to handle exceptions in Robyn.',\n  },\n  {\n    href: '/documentation/en/api_reference/scaling',\n    name: 'Scaling the Application',\n    description: 'Learn how to scaled Robyn across multiple cores.',\n  },\n  {\n    href: '/documentation/en/api_reference/advanced_features',\n    name: 'Advanced Features',\n    description: 'Learn about advanced features in Robyn.',\n  },\n  {\n    href: '/documentation/en/api_reference/multiprocess_execution',\n    name: 'Multiprocess Execution',\n    description: 'Learn about the behaviour or variables during multithreading',\n  },\n  {\n    href: '/documentation/en/api_reference/using_rust_directly',\n    name: 'Direct Rust Usage',\n    description: 'Learn about directly using Rust in Robyn.',\n  },\n  {\n    href: '/documentation/en/api_reference/graphql-support',\n    name: 'GraphQL Support',\n    description: 'Learn about GraphQL Support in Robyn.',\n  },\n  {\n    href: '/documentation/en/api_reference/openapi',\n    name: 'OpenAPI Documentation',\n    description: 'Learn how to generate OpenAPI docs for your applications.',\n  },\n  {\n    href: '/documentation/en/api_reference/dependency_injection',\n    name: 'Dependency Injection',\n    description: 'Learn about Dependency Injection in Robyn.',\n  },\n]\n\nexport function ApiDocs() {\n  return (\n    <div className=\"my-16 xl:max-w-none\">\n      <Heading level={2} id=\"api_docs\">\n        <h3 className=\"text-white\">Api Docs</h3>\n      </Heading>\n      <div className=\"not-prose mt-4 grid grid-cols-1 gap-8 border-t border-white/5 pt-10 sm:grid-cols-2 xl:grid-cols-4\">\n        {guides.map((guide) => (\n          <div key={guide.href}>\n            <h3 className=\"text-sm font-semibold text-white\">{guide.name}</h3>\n            <p className=\"mt-1 text-sm text-zinc-400\">{guide.description}</p>\n            <p className=\"mt-4\">\n              <Button href={guide.href} variant=\"text\" arrow=\"right\">\n                Read more\n              </Button>\n            </p>\n          </div>\n        ))}\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/BottomNavbar.jsx",
    "content": "import { forwardRef } from 'react'\nimport Link from 'next/link'\nimport clsx from 'clsx'\nimport { motion, useScroll, useTransform } from 'framer-motion'\n\nimport { MobileNavigation } from '@/components/documentation/MobileNavigation'\nimport { useMobileNavigationStore } from '@/components/documentation/MobileNavigation'\nimport { MobileSearch, Search } from '@/components/documentation/Search'\n\nexport const BottomNavbar = forwardRef(function Header({ className }, ref) {\n  let { scrollY } = useScroll()\n  let bgOpacityLight = useTransform(scrollY, [0, 72], [0.5, 0.9])\n  let bgOpacityDark = useTransform(scrollY, [0, 72], [0.2, 0.8])\n\n  return (\n    <>\n      <motion.div\n        ref={ref}\n        className={clsx(\n          className,\n          'fixed inset-x-0 bottom-0 z-50 flex h-14 max-w-fit items-center justify-between gap-12 px-4 transition sm:px-6 lg:left-72 lg:z-30 lg:px-8 xl:left-80'\n        )}\n        style={{\n          '--bg-opacity-light': bgOpacityLight,\n          '--bg-opacity-dark': bgOpacityDark,\n        }}\n      >\n        <div className=\"flex items-center gap-5 lg:hidden\">\n          <MobileNavigation />\n        </div>\n      </motion.div>\n    </>\n  )\n})\n"
  },
  {
    "path": "docs_src/src/components/documentation/Button.jsx",
    "content": "import Link from 'next/link'\nimport clsx from 'clsx'\n\nfunction ArrowIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" fill=\"none\" aria-hidden=\"true\" {...props}>\n      <path\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"m11.5 6.5 3 3.5m0 0-3 3.5m3-3.5h-9\"\n      />\n    </svg>\n  )\n}\n\nconst variantStyles = {\n  primary:\n    'rounded-full  py-1 px-3 bg-orange-400/10 text-orange-400 ring-1 ring-inset ring-orange-400/20 hover:bg-orange-400/10 hover:text-orange-300 hover:ring-orange-300',\n  secondary:\n    'rounded-full  py-1 px-3 bg-zinc-800/40 text-zinc-400 ring-1 ring-inset ring-zinc-800 hover:bg-zinc-800 hover:text-zinc-300',\n  filled: 'rounded-full py-1 px-3 bg-orange-500 text-white hover:bg-orange-400',\n  outline:\n    'rounded-full py-1 px-3 ring-1 ring-inset text-zinc-400 ring-white/10 hover:bg-white/5 hover:text-white',\n  text: 'text-orange-400 hover:text-orange-500',\n}\n\nexport function Button({\n  variant = 'primary',\n  className,\n  children,\n  arrow,\n  ...props\n}) {\n  let Component = props.href ? Link : 'button'\n\n  className = clsx(\n    'inline-flex gap-0.5 justify-center overflow-hidden text-sm font-medium transition',\n    variantStyles[variant],\n    className\n  )\n\n  let arrowIcon = (\n    <ArrowIcon\n      className={clsx(\n        'mt-0.5 h-5 w-5',\n        variant === 'text' && 'relative top-px',\n        arrow === 'left' && '-ml-1 rotate-180',\n        arrow === 'right' && '-mr-1'\n      )}\n    />\n  )\n\n  return (\n    <Component className={className} {...props}>\n      {arrow === 'left' && arrowIcon}\n      {children}\n      {arrow === 'right' && arrowIcon}\n    </Component>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/Code.jsx",
    "content": "import {\n  Children,\n  createContext,\n  useContext,\n  useEffect,\n  useRef,\n  useState,\n} from 'react'\nimport { Tab } from '@headlessui/react'\nimport clsx from 'clsx'\nimport { create } from 'zustand'\n\nimport { Tag } from '@/components/documentation/Tag'\n\nconst languageNames = {\n  js: 'JavaScript',\n  ts: 'TypeScript',\n  javascript: 'JavaScript',\n  typescript: 'TypeScript',\n  php: 'PHP',\n  python: 'Python',\n  ruby: 'Ruby',\n  go: 'Go',\n}\n\nfunction getPanelTitle({ title, language }) {\n  return title ?? languageNames[language] ?? 'Code'\n}\n\nfunction ClipboardIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" {...props}>\n      <path\n        strokeWidth=\"0\"\n        d=\"M5.5 13.5v-5a2 2 0 0 1 2-2l.447-.894A2 2 0 0 1 9.737 4.5h.527a2 2 0 0 1 1.789 1.106l.447.894a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2Z\"\n      />\n      <path\n        fill=\"none\"\n        strokeLinejoin=\"round\"\n        d=\"M12.5 6.5a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2m5 0-.447-.894a2 2 0 0 0-1.79-1.106h-.527a2 2 0 0 0-1.789 1.106L7.5 6.5m5 0-1 1h-3l-1-1\"\n      />\n    </svg>\n  )\n}\n\nfunction CopyButton({ code }) {\n  let [copyCount, setCopyCount] = useState(0)\n  let copied = copyCount > 0\n\n  useEffect(() => {\n    if (copyCount > 0) {\n      let timeout = setTimeout(() => setCopyCount(0), 1000)\n      return () => {\n        clearTimeout(timeout)\n      }\n    }\n  }, [copyCount])\n\n  return (\n    <button\n      type=\"button\"\n      className={clsx(\n        'group/button text-2xs absolute right-4 top-3.5 overflow-hidden rounded-full py-1 pl-2 pr-3 font-medium opacity-0 backdrop-blur transition focus:opacity-100 group-hover:opacity-100',\n        copied\n          ? 'bg-orange-400/10 ring-1 ring-inset ring-orange-400/20'\n          : 'bg-white/2.5 hover:bg-white/5'\n      )}\n      onClick={() => {\n        window.navigator.clipboard.writeText(code).then(() => {\n          setCopyCount((count) => count + 1)\n        })\n      }}\n    >\n      <span\n        aria-hidden={copied}\n        className={clsx(\n          'pointer-events-none flex items-center gap-0.5 text-zinc-400 transition duration-300',\n          copied && '-translate-y-1.5 opacity-0'\n        )}\n      >\n        <ClipboardIcon className=\"h-5 w-5 fill-zinc-500/20 stroke-zinc-500 transition-colors group-hover/button:stroke-zinc-400\" />\n        Copy\n      </span>\n      <span\n        aria-hidden={!copied}\n        className={clsx(\n          'pointer-events-none absolute inset-0 flex items-center justify-center text-orange-400 transition duration-300',\n          !copied && 'translate-y-1.5 opacity-0'\n        )}\n      >\n        Copied!\n      </span>\n    </button>\n  )\n}\n\nfunction CodePanelHeader({ tag, label }) {\n  if (!tag && !label) {\n    return null\n  }\n\n  return (\n    <div className=\"border-b-white/7.5 bg-white/1 flex h-9 items-center gap-2 border-y border-b-white/5 border-t-transparent bg-zinc-900 px-4\">\n      {tag && (\n        <div className=\"dark flex\">\n          <Tag variant=\"small\">{tag}</Tag>\n        </div>\n      )}\n      {tag && label && (\n        <span className=\"h-0.5 w-0.5 rounded-full bg-zinc-500\" />\n      )}\n      {label && (\n        <span className=\"font-mono text-xs text-zinc-400\">{label}</span>\n      )}\n    </div>\n  )\n}\n\nfunction CodePanel({ tag, label, code, children }) {\n  let child = Children.only(children)\n\n  return (\n    <div className=\"bg-white/2.5 group\">\n      <CodePanelHeader\n        tag={child.props.tag ?? tag}\n        label={child.props.label ?? label}\n      />\n      <div className=\"relative\">\n        <pre className=\"overflow-x-auto p-4 text-xs text-white\">{children}</pre>\n        <CopyButton code={child.props.code ?? code} />\n      </div>\n    </div>\n  )\n}\n\nfunction CodeGroupHeader({ title, children, selectedIndex }) {\n  let hasTabs = Children.count(children) > 1\n\n  if (!title && !hasTabs) {\n    return null\n  }\n\n  return (\n    <div className=\"flex min-h-[calc(theme(spacing.12)+1px)] flex-wrap items-start gap-x-4 border-b  border-zinc-800 bg-transparent bg-zinc-800 px-4\">\n      {title && (\n        <h3 className=\"mr-auto pt-3 text-xs font-semibold text-white\">\n          {title}\n        </h3>\n      )}\n      {hasTabs && (\n        <Tab.List className=\"-mb-px flex gap-4 text-xs font-medium\">\n          {Children.map(children, (child, childIndex) => (\n            <Tab\n              className={clsx(\n                'border-b py-3 transition focus:[&:not(:focus-visible)]:outline-none',\n                childIndex === selectedIndex\n                  ? 'border-orange-500 text-orange-400'\n                  : 'border-transparent text-zinc-400 hover:text-zinc-300'\n              )}\n            >\n              {getPanelTitle(child.props)}\n            </Tab>\n          ))}\n        </Tab.List>\n      )}\n    </div>\n  )\n}\n\nfunction CodeGroupPanels({ children, ...props }) {\n  let hasTabs = Children.count(children) > 1\n\n  if (hasTabs) {\n    return (\n      <Tab.Panels>\n        {Children.map(children, (child) => (\n          <Tab.Panel>\n            <CodePanel {...props}>{child}</CodePanel>\n          </Tab.Panel>\n        ))}\n      </Tab.Panels>\n    )\n  }\n\n  return <CodePanel {...props}>{children}</CodePanel>\n}\n\nfunction usePreventLayoutShift() {\n  let positionRef = useRef()\n  let rafRef = useRef()\n\n  useEffect(() => {\n    return () => {\n      window.cancelAnimationFrame(rafRef.current)\n    }\n  }, [])\n\n  return {\n    positionRef,\n    preventLayoutShift(callback) {\n      let initialTop = positionRef.current.getBoundingClientRect().top\n\n      callback()\n\n      rafRef.current = window.requestAnimationFrame(() => {\n        let newTop = positionRef.current.getBoundingClientRect().top\n        window.scrollBy(0, newTop - initialTop)\n      })\n    },\n  }\n}\n\nconst usePreferredLanguageStore = create((set) => ({\n  preferredLanguages: [],\n  addPreferredLanguage: (language) =>\n    set((state) => ({\n      preferredLanguages: [\n        ...state.preferredLanguages.filter(\n          (preferredLanguage) => preferredLanguage !== language\n        ),\n        language,\n      ],\n    })),\n}))\n\nfunction useTabGroupProps(availableLanguages) {\n  let { preferredLanguages, addPreferredLanguage } = usePreferredLanguageStore()\n  let [selectedIndex, setSelectedIndex] = useState(0)\n  let activeLanguage = [...availableLanguages].sort(\n    (a, z) => preferredLanguages.indexOf(z) - preferredLanguages.indexOf(a)\n  )[0]\n  let languageIndex = availableLanguages.indexOf(activeLanguage)\n  let newSelectedIndex = languageIndex === -1 ? selectedIndex : languageIndex\n  if (newSelectedIndex !== selectedIndex) {\n    setSelectedIndex(newSelectedIndex)\n  }\n\n  let { positionRef, preventLayoutShift } = usePreventLayoutShift()\n\n  return {\n    as: 'div',\n    ref: positionRef,\n    selectedIndex,\n    onChange: (newSelectedIndex) => {\n      preventLayoutShift(() =>\n        addPreferredLanguage(availableLanguages[newSelectedIndex])\n      )\n    },\n  }\n}\n\nconst CodeGroupContext = createContext(false)\n\nexport function CodeGroup({ children, title, ...props }) {\n  let languages = Children.map(children, (child) => getPanelTitle(child.props))\n  let tabGroupProps = useTabGroupProps(languages)\n  let hasTabs = Children.count(children) > 1\n  let Container = hasTabs ? Tab.Group : 'div'\n  let containerProps = hasTabs ? tabGroupProps : {}\n  let headerProps = hasTabs\n    ? { selectedIndex: tabGroupProps.selectedIndex }\n    : {}\n\n  return (\n    <CodeGroupContext.Provider value={true}>\n      <Container\n        {...containerProps}\n        className=\"not-prose my-6 overflow-hidden rounded-2xl bg-zinc-900 shadow-md ring-1 ring-white/10\"\n      >\n        <CodeGroupHeader title={title} {...headerProps}>\n          {children}\n        </CodeGroupHeader>\n        <CodeGroupPanels {...props}>{children}</CodeGroupPanels>\n      </Container>\n    </CodeGroupContext.Provider>\n  )\n}\n\nexport function Code({ children, ...props }) {\n  let isGrouped = useContext(CodeGroupContext)\n\n  if (isGrouped) {\n    return (\n      <code\n        className=\"text-white\"\n        {...props}\n        dangerouslySetInnerHTML={{ __html: children }}\n      />\n    )\n  }\n\n  return (\n    <code className=\"text-white\" {...props}>\n      {children}\n    </code>\n  )\n}\n\nexport function Pre({ children, ...props }) {\n  let isGrouped = useContext(CodeGroupContext)\n\n  if (isGrouped) {\n    return children\n  }\n\n  return <CodeGroup {...props}>{children}</CodeGroup>\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/Guides.jsx",
    "content": "import { Button } from '@/components/documentation/Button'\nimport { Heading } from '@/components/documentation/Heading'\n\nconst guides = [\n  {\n    href: '/documentation/en/example_app',\n    name: 'Getting Started',\n    description: 'Learn how to authenticate your API requests.',\n  },\n  {\n    href: '/documentation/en/example_app/authentication',\n    name: 'Authentication and Authorization',\n    description: 'Understand how to use authentication and authorization.',\n  },\n  {\n    href: '/documentation/en/example_app/authentication-middlewares',\n    name: 'Middlewares',\n    description:\n      'Read about different kinds of Middlewares and how to use them.',\n  },\n  {\n    href: '/documentation/en/example_app/monitoring_and_logging',\n    name: 'Monitoring and Logging',\n    description: 'Learn how to have montoring and logging in Robyn.',\n  },\n  {\n    href: '/documentation/en/example_app/real_time_notifications',\n    name: 'Real Time Notifications',\n    description: 'Learn how to have real time notification in Robyn.',\n  },\n  {\n    href: '/documentation/en/example_app/deployment',\n    name: 'Deployments',\n    description:\n      'Learn how to deploy your app to production and manage your deployments.',\n  },\n  {\n    href: '/documentation/en/example_app/openapi',\n    name: 'OpenAPI Documentation',\n    description: 'Learn how OpenAPI docs are generate for your applications.',\n  },\n]\n\nexport function Guides() {\n  return (\n    <div className=\"my-16 xl:max-w-none\">\n      <Heading level={2} id=\"guides\">\n        <h3 className=\"text-white\">Example Application</h3>\n      </Heading>\n      <div className=\"not-prose mt-4 grid grid-cols-1 gap-8 border-t  border-white/5 pt-10 sm:grid-cols-2 xl:grid-cols-4\">\n        {guides.map((guide) => (\n          <div key={guide.href}>\n            <h3 className=\"text-sm font-semibold text-white\">{guide.name}</h3>\n            <p className=\"mt-1 text-sm text-zinc-400\">{guide.description}</p>\n            <p className=\"mt-4\">\n              <Button href={guide.href} variant=\"text\" arrow=\"right\">\n                Read more\n              </Button>\n            </p>\n          </div>\n        ))}\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/Heading.jsx",
    "content": "import { useEffect, useRef } from 'react'\nimport Link from 'next/link'\nimport { useInView } from 'framer-motion'\n\nimport { useSectionStore } from '@/components/documentation/SectionProvider'\nimport { Tag } from '@/components/documentation/Tag'\nimport { remToPx } from '@/lib/remToPx'\n\nfunction AnchorIcon(props) {\n  return (\n    <svg\n      viewBox=\"0 0 20 20\"\n      fill=\"none\"\n      strokeLinecap=\"round\"\n      aria-hidden=\"true\"\n      {...props}\n    >\n      <path d=\"m6.5 11.5-.964-.964a3.535 3.535 0 1 1 5-5l.964.964m2 2 .964.964a3.536 3.536 0 0 1-5 5L8.5 13.5m0-5 3 3\" />\n    </svg>\n  )\n}\n\nfunction Eyebrow({ tag, label }) {\n  if (!tag && !label) {\n    return null\n  }\n\n  return (\n    <div className=\"flex items-center gap-x-3\">\n      {tag && <Tag>{tag}</Tag>}\n      {tag && label && (\n        <span className=\"h-0.5 w-0.5 rounded-full bg-zinc-600\" />\n      )}\n      {label && (\n        <span className=\"font-mono text-xs text-zinc-400\">{label}</span>\n      )}\n    </div>\n  )\n}\n\nfunction Anchor({ id, inView, children }) {\n  return (\n    <Link\n      href={`#${id}`}\n      className=\"group text-inherit no-underline hover:text-inherit\"\n    >\n      {inView && (\n        <div className=\"absolute ml-[calc(-1*var(--width))] mt-1 hidden w-[var(--width)] opacity-0 transition [--width:calc(2.625rem+0.5px+50%-min(50%,calc(theme(maxWidth.lg)+theme(spacing.8))))] group-hover:opacity-100 group-focus:opacity-100 md:block lg:z-50 2xl:[--width:theme(spacing.10)]\">\n          <div className=\"group/anchor block h-5 w-5 rounded-lg bg-zinc-800 ring-1 ring-inset ring-zinc-700 transition hover:bg-zinc-700 hover:ring-zinc-600\">\n            <AnchorIcon className=\"h-5 w-5 stroke-zinc-400 transition group-hover/anchor:stroke-white\" />\n          </div>\n        </div>\n      )}\n      {children}\n    </Link>\n  )\n}\n\nexport function Heading({\n  level = 2,\n  children,\n  id,\n  tag,\n  label,\n  anchor = true,\n  ...props\n}) {\n  let Component = `h${level}`\n  let ref = useRef()\n  let registerHeading = useSectionStore((s) => s.registerHeading)\n\n  let inView = useInView(ref, {\n    margin: `${remToPx(-3.5)}px 0px 0px 0px`,\n    amount: 'all',\n  })\n\n  useEffect(() => {\n    if (level === 2) {\n      registerHeading({ id, ref, offsetRem: tag || label ? 8 : 6 })\n    }\n  })\n\n  return (\n    <>\n      <Eyebrow tag={tag} label={label} />\n      <Component\n        ref={ref}\n        id={anchor ? id : undefined}\n        className={tag || label ? 'mt-2 scroll-mt-32' : 'scroll-mt-24'}\n        {...props}\n      >\n        {anchor ? (\n          <Anchor id={id} inView={inView}>\n            {children}\n          </Anchor>\n        ) : (\n          children\n        )}\n      </Component>\n    </>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/HeroPattern.jsx",
    "content": "export function HeroPattern() {\n  return (\n    <div className=\"absolute inset-0 -z-10 mx-0 max-w-none overflow-hidden\">\n      <div\n        className=\"absolute inset-x-0 top-4 -z-10 flex transform-gpu justify-center overflow-hidden blur-3xl\"\n        aria-hidden=\"true\"\n      >\n        <div\n          className=\"aspect-[1108/632] w-[69.25rem] flex-none bg-gradient-to-r from-orange-800 to-yellow-500 opacity-40\"\n          style={{\n            clipPath:\n              'polygon(73.6% 51.7%, 91.7% 11.8%, 100% 46.4%, 97.4% 82.2%, 92.5% 84.9%, 75.7% 64%, 55.3% 47.5%, 46.5% 49.4%, 45% 62.9%, 50.3% 87.2%, 21.3% 64.1%, 0.1% 100%, 5.4% 51.1%, 21.4% 63.9%, 58.9% 0.2%, 73.6% 51.7%)',\n          }}\n        />\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/LanguageSelector.jsx",
    "content": "import { useRouter } from 'next/router'\nimport { Menu } from '@headlessui/react'\nimport { motion } from 'framer-motion'\n\nconst languages = [\n  { code: 'en', name: 'English' },\n  { code: 'zh', name: '中文' },\n]\n\nfunction LanguageSelector() {\n  const router = useRouter()\n  const { pathname, asPath, query } = router\n  const currentLanguage = asPath.includes('/zh') ? 'zh' : 'en'\n\n  const changeLanguage = (locale) => {\n    const currentPath = asPath.split('?')[0]\n    \n    if (currentPath === '/documentation' || currentPath === '/documentation/') {\n      router.push(`/documentation/${locale}`)\n      return\n    }\n\n    const newPath = currentPath.replace(\n      /\\/documentation\\/(en|zh)/,\n      `/documentation/${locale}`\n    )\n    \n    router.push(newPath)\n  }\n\n  return (\n    <Menu as=\"div\" className=\"relative\">\n      <Menu.Button className=\"flex items-center gap-2 rounded-lg bg-zinc-800/40 px-3 py-2 text-sm text-white hover:bg-zinc-800 transition-colors duration-200\">\n        {languages.find(l => l.code === currentLanguage)?.name}\n        <svg\n          width=\"8\"\n          height=\"5\"\n          className=\"ml-1 stroke-current\"\n          fill=\"none\"\n          viewBox=\"0 0 8 5\"\n        >\n          <path d=\"M1 1L4 4L7 1\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n        </svg>\n      </Menu.Button>\n      <Menu.Items as={motion.div}\n        initial={{ y: -10 }}\n        animate={{ y: 0 }}\n        exit={{ y: -10 }}\n        className=\"absolute left-0 mt-1 w-40 origin-top-left rounded-lg bg-[#27272A] ring-1 ring-zinc-700 shadow-xl z-50\"\n      >\n        {languages.map((language) => (\n          <Menu.Item key={language.code}>\n            {({ active }) => (\n              <button\n                onClick={() => changeLanguage(language.code)}\n                className={`${\n                  active ? 'bg-zinc-700/50' : ''\n                } ${\n                  currentLanguage === language.code ? 'bg-orange-500/10 text-orange-500' : 'text-zinc-100'\n                } group flex w-full items-center gap-2 rounded-md px-3 py-2.5 text-sm transition-colors duration-200`}\n              >\n                {language.name}\n              </button>\n            )}\n          </Menu.Item>\n        ))}\n      </Menu.Items>\n    </Menu>\n  )\n}\n\nexport default LanguageSelector "
  },
  {
    "path": "docs_src/src/components/documentation/Layout.jsx",
    "content": "import { motion } from 'framer-motion'\n\nimport { Footer } from '@/components/Footer'\nimport { BottomNavbar } from '@/components/documentation/BottomNavbar'\nimport { Navigation } from '@/components/documentation/Navigation'\nimport { Prose } from '@/components/documentation/Prose'\nimport { SectionProvider } from '@/components/documentation/SectionProvider'\n\nexport function Layout({ children, sections = [] }) {\n  return (\n    <SectionProvider sections={sections}>\n      <div className=\"lg:ml-72 xl:ml-80\">\n        <motion.header\n          layoutScroll\n          className=\"contents lg:pointer-events-none lg:fixed lg:inset-0 lg:z-40 lg:flex\"\n        >\n          <div className=\"contents lg:pointer-events-auto lg:block lg:w-72 lg:overflow-y-auto lg:border-white/10 lg:px-6 lg:pb-8 xl:w-80\">\n            <BottomNavbar />\n\n            <Navigation className=\"hidden lg:mt-32 lg:block\" />\n          </div>\n        </motion.header>\n        <div className=\"relative px-4 sm:px-6 lg:px-8\">\n          <main className=\"py-16\">\n            <Prose>{children}</Prose>\n          </main>\n          <Footer />\n        </div>\n      </div>\n    </SectionProvider>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/Libraries.jsx",
    "content": "import Image from 'next/image'\n\nimport { Button } from '@/components/documentation/Button'\nimport { Heading } from '@/components/documentation/Heading'\n\nconst libraries = [\n  {\n    href: '#',\n    name: 'PHP',\n    description:\n      'A popular general-purpose scripting language that is especially suited to web development.',\n  },\n  {\n    href: '#',\n    name: 'Ruby',\n    description:\n      'A dynamic, open source programming language with a focus on simplicity and productivity.',\n  },\n  {\n    href: '#',\n    name: 'Node.js',\n    description:\n      'Node.js® is an open-source, cross-platform JavaScript runtime environment.',\n  },\n  {\n    href: '#',\n    name: 'Python',\n    description:\n      'Python is a programming language that lets you work quickly and integrate systems more effectively.',\n  },\n  {\n    href: '#',\n    name: 'Go',\n    description:\n      'An open-source programming language supported by Google with built-in concurrency.',\n  },\n]\n\nexport function Libraries() {\n  return (\n    <div className=\"my-16 xl:max-w-none\">\n      <Heading level={2} id=\"official-libraries\">\n        Official libraries\n      </Heading>\n      <div className=\"not-prose mt-4 grid grid-cols-1 gap-x-6 gap-y-10 border-t border-white/5 pt-10 sm:grid-cols-2 xl:max-w-none xl:grid-cols-3\">\n        {libraries.map((library) => (\n          <div key={library.name} className=\"flex flex-row-reverse gap-6\">\n            <div className=\"flex-auto\">\n              <h3 className=\"text-sm font-semibold text-white\">\n                {library.name}\n              </h3>\n              <p className=\"mt-1 text-sm text-zinc-400\">\n                {library.description}\n              </p>\n              <p className=\"mt-4\">\n                <Button href={library.href} variant=\"text\" arrow=\"right\">\n                  Read more\n                </Button>\n              </p>\n            </div>\n            <Image\n              src={library.logo}\n              alt=\"\"\n              className=\"h-12 w-12\"\n              unoptimized\n            />\n          </div>\n        ))}\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/MobileNavigation.jsx",
    "content": "import { createContext, Fragment, useContext } from 'react'\nimport { Dialog, Transition } from '@headlessui/react'\nimport { motion } from 'framer-motion'\nimport { create } from 'zustand'\n\nimport { BottomNavbar } from '@/components/documentation/BottomNavbar'\nimport { Navigation } from '@/components/documentation/Navigation'\n\nfunction MenuIcon(props) {\n  return (\n    <button\n      type=\"button\"\n      aria-label=\"Toggle dark mode\"\n      className=\"group rounded-full bg-zinc-800/90 px-3 py-2 shadow-lg shadow-zinc-800/5  ring-1 ring-white/10 backdrop-blur transition hover:ring-white/20\"\n    >\n      <svg\n        viewBox=\"0 0 10 9\"\n        fill=\"none\"\n        strokeLinecap=\"round\"\n        aria-hidden=\"true\"\n        {...props}\n      >\n        <path d=\"M.5 1h9M.5 8h9M.5 4.5h9\" />\n      </svg>\n    </button>\n  )\n}\n\nfunction XIcon(props) {\n  return (\n    <svg\n      viewBox=\"0 0 10 9\"\n      fill=\"none\"\n      strokeLinecap=\"round\"\n      aria-hidden=\"true\"\n      {...props}\n    >\n      <path d=\"m1.5 1 7 7M8.5 1l-7 7\" />\n    </svg>\n  )\n}\n\nconst IsInsideMobileNavigationContext = createContext(false)\n\nexport function useIsInsideMobileNavigation() {\n  return useContext(IsInsideMobileNavigationContext)\n}\n\nexport const useMobileNavigationStore = create((set) => ({\n  isOpen: false,\n  open: () => set({ isOpen: true }),\n  close: () => set({ isOpen: false }),\n  toggle: () => set((state) => ({ isOpen: !state.isOpen })),\n}))\n\nexport function MobileNavigation() {\n  let isInsideMobileNavigation = useIsInsideMobileNavigation()\n  let { isOpen, toggle, close } = useMobileNavigationStore()\n  let ToggleIcon = isOpen ? XIcon : MenuIcon\n\n  return (\n    <IsInsideMobileNavigationContext.Provider value={true}>\n      <button\n        type=\"button\"\n        className=\"flex h-6 w-6 items-center justify-center rounded-md transition hover:bg-white/5\"\n        aria-label=\"Toggle navigation\"\n        onClick={toggle}\n      >\n        <ToggleIcon className=\"w-2.5 stroke-white\" />\n      </button>\n      {!isInsideMobileNavigation && (\n        <Transition.Root show={isOpen} as={Fragment}>\n          <Dialog onClose={close} className=\"fixed inset-0 z-50 lg:hidden\">\n            <Transition.Child\n              as={Fragment}\n              enter=\"duration-300 ease-out\"\n              enterFrom=\"opacity-0\"\n              enterTo=\"opacity-100\"\n              leave=\"duration-200 ease-in\"\n              leaveFrom=\"opacity-100\"\n              leaveTo=\"opacity-0\"\n            >\n              <div className=\"fixed inset-0 top-0 bg-black/40 backdrop-blur-sm\" />\n            </Transition.Child>\n\n            <Dialog.Panel>\n              <Transition.Child\n                as={Fragment}\n                enter=\"duration-300 ease-out\"\n                enterFrom=\"opacity-0\"\n                enterTo=\"opacity-100\"\n                leave=\"duration-200 ease-in\"\n                leaveFrom=\"opacity-100\"\n                leaveTo=\"opacity-0\"\n              >\n                <BottomNavbar />\n              </Transition.Child>\n\n              <Transition.Child\n                as={Fragment}\n                enter=\"duration-500 ease-in-out\"\n                enterFrom=\"-translate-x-full\"\n                enterTo=\"translate-x-0\"\n                leave=\"duration-500 ease-in-out\"\n                leaveFrom=\"translate-x-0\"\n                leaveTo=\"-translate-x-full\"\n              >\n                <motion.div\n                  layoutScroll\n                  className=\"ring-zinc-900/7.5 fixed bottom-0 left-0 top-0 w-full overflow-y-auto  bg-black px-4 pb-4 pt-6 shadow-lg shadow-zinc-900/10 ring-1 ring-black min-[416px]:max-w-sm sm:px-6 sm:pb-10\"\n                >\n                  <Navigation />\n                </motion.div>\n              </Transition.Child>\n            </Dialog.Panel>\n          </Dialog>\n        </Transition.Root>\n      )}\n    </IsInsideMobileNavigationContext.Provider>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/ModeToggle.jsx",
    "content": "function SunIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" fill=\"none\" aria-hidden=\"true\" {...props}>\n      <path d=\"M12.5 10a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0Z\" />\n      <path\n        strokeLinecap=\"round\"\n        d=\"M10 5.5v-1M13.182 6.818l.707-.707M14.5 10h1M13.182 13.182l.707.707M10 15.5v-1M6.11 13.889l.708-.707M4.5 10h1M6.11 6.111l.708.707\"\n      />\n    </svg>\n  )\n}\n\nfunction MoonIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" fill=\"none\" aria-hidden=\"true\" {...props}>\n      <path d=\"M15.224 11.724a5.5 5.5 0 0 1-6.949-6.949 5.5 5.5 0 1 0 6.949 6.949Z\" />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/Navigation.jsx",
    "content": "import { useRef } from 'react'\nimport Link from 'next/link'\nimport { useRouter } from 'next/router'\nimport clsx from 'clsx'\nimport { AnimatePresence, motion, useIsPresent } from 'framer-motion'\n\nimport { useIsInsideMobileNavigation } from '@/components/documentation/MobileNavigation'\nimport { useSectionStore } from '@/components/documentation/SectionProvider'\nimport { Tag } from '@/components/documentation/Tag'\nimport LanguageSelector from '@/components/documentation/LanguageSelector'\nimport { remToPx } from '@/lib/remToPx'\n\nfunction useInitialValue(value, condition = true) {\n  let initialValue = useRef(value).current\n  return condition ? initialValue : value\n}\n\nfunction TopLevelNavItem({ href, children }) {\n  return (\n    <li className=\"md:hidden\">\n      <Link\n        href={href}\n        className=\"block py-1 text-sm text-zinc-400 transition hover:text-white\"\n      >\n        {children}\n      </Link>\n    </li>\n  )\n}\n\nfunction NavLink({ href, tag, active, isAnchorLink = false, children }) {\n  return (\n    <Link\n      href={href}\n      aria-current={active ? 'page' : undefined}\n      className={clsx(\n        'flex justify-between gap-2 py-1 pr-3 text-sm transition',\n        isAnchorLink ? 'pl-7' : 'pl-4',\n        active ? 'text-white' : 'text-zinc-400 hover:text-white'\n      )}\n    >\n      <span className=\"truncate\">{children}</span>\n      {tag && (\n        <Tag variant=\"small\" color=\"zinc\">\n          {tag}\n        </Tag>\n      )}\n    </Link>\n  )\n}\n\nfunction VisibleSectionHighlight({ group, pathname }) {\n  let [sections, visibleSections] = useInitialValue(\n    [\n      useSectionStore((s) => s.sections),\n      useSectionStore((s) => s.visibleSections),\n    ],\n    useIsInsideMobileNavigation()\n  )\n\n  let isPresent = useIsPresent()\n  let firstVisibleSectionIndex = Math.max(\n    0,\n    [{ id: '_top' }, ...sections].findIndex(\n      (section) => section.id === visibleSections[0]\n    )\n  )\n  let itemHeight = remToPx(2)\n  let height = isPresent\n    ? Math.max(1, visibleSections.length) * itemHeight\n    : itemHeight\n  let top =\n    group.links.findIndex((link) => link.href === pathname) * itemHeight +\n    firstVisibleSectionIndex * itemHeight\n\n  return (\n    <motion.div\n      layout\n      initial={{ opacity: 0 }}\n      animate={{ opacity: 1, transition: { delay: 0.2 } }}\n      exit={{ opacity: 0 }}\n      className=\"bg-white/2.5 absolute inset-x-0 top-0 will-change-transform\"\n      style={{ borderRadius: 8, height, top }}\n    />\n  )\n}\n\nfunction ActivePageMarker({ group, pathname }) {\n  let itemHeight = remToPx(2)\n  let offset = remToPx(0.25)\n  let activePageIndex = group.links.findIndex((link) => link.href === pathname)\n  let top = offset + activePageIndex * itemHeight\n\n  return (\n    <motion.div\n      layout\n      className=\"absolute left-2 h-6 w-px bg-orange-500\"\n      initial={{ opacity: 0 }}\n      animate={{ opacity: 1, transition: { delay: 0.2 } }}\n      exit={{ opacity: 0 }}\n      style={{ top }}\n    />\n  )\n}\n\nfunction NavigationGroup({ group, className }) {\n  // If this is the mobile navigation then we always render the initial\n  // state, so that the state does not change during the close animation.\n  // The state will still update when we re-open (re-render) the navigation.\n  let isInsideMobileNavigation = useIsInsideMobileNavigation()\n  let [router, sections] = useInitialValue(\n    [useRouter(), useSectionStore((s) => s.sections)],\n    isInsideMobileNavigation\n  )\n\n  let isActiveGroup =\n    group.links.findIndex((link) => link.href === router.pathname) !== -1\n\n  return (\n    <li className={clsx('relative mt-6', className)}>\n      <motion.h2 layout=\"position\" className=\"text-xs font-semibold text-white\">\n        {group.title}\n      </motion.h2>\n      <div className=\"relative mt-3 pl-2\">\n        <AnimatePresence initial={!isInsideMobileNavigation}>\n          {isActiveGroup && (\n            <VisibleSectionHighlight group={group} pathname={router.pathname} />\n          )}\n        </AnimatePresence>\n        <motion.div\n          layout\n          className=\"absolute inset-y-0 left-2 w-px bg-white/5\"\n        />\n        <AnimatePresence initial={false}>\n          {isActiveGroup && (\n            <ActivePageMarker group={group} pathname={router.pathname} />\n          )}\n        </AnimatePresence>\n        <ul role=\"list\" className=\"border-l border-transparent\">\n          {group.links.map((link) => (\n            <motion.li key={link.href} layout=\"position\" className=\"relative\">\n              <NavLink href={link.href} active={link.href === router.pathname}>\n                {link.title}\n              </NavLink>\n              <AnimatePresence mode=\"popLayout\" initial={false}>\n                {link.href === router.pathname && sections.length > 0 && (\n                  <motion.ul\n                    role=\"list\"\n                    initial={{ opacity: 0 }}\n                    animate={{\n                      opacity: 1,\n                      transition: { delay: 0.1 },\n                    }}\n                    exit={{\n                      opacity: 0,\n                      transition: { duration: 0.15 },\n                    }}\n                  >\n                    {sections.map((section) => (\n                      <li key={section.id}>\n                        <NavLink\n                          href={`${link.href}#${section.id}`}\n                          tag={section.tag}\n                          isAnchorLink\n                        >\n                          {section.title}\n                        </NavLink>\n                      </li>\n                    ))}\n                  </motion.ul>\n                )}\n              </AnimatePresence>\n            </motion.li>\n          ))}\n        </ul>\n      </div>\n    </li>\n  )\n}\n\nexport const navigation = [\n  {\n    title: 'Documentation',\n    links: [{ title: 'Introduction', href: '/documentation' }],\n  },\n\n  {\n    title: 'Example Application',\n    links: [\n      { title: 'Getting Started', href: '/documentation/en/example_app' },\n      {\n        title: 'Modeling Routes',\n        href: '/documentation/en/example_app/modeling_routes',\n      },\n\n      {\n        title: 'Authentication and Authorization',\n        href: '/documentation/en/example_app/authentication',\n      },\n      {\n        title: 'Middlewares',\n        href: '/documentation/en/example_app/authentication-middlewares',\n      },\n      {\n        title: 'Real Time Notifications',\n        href: '/documentation/en/example_app/real_time_notifications',\n      },\n      {\n        title: 'Monitoring and Logging',\n        href: '/documentation/en/example_app/monitoring_and_logging',\n      },\n      { title: 'Deployment', href: '/documentation/en/example_app/deployment' },\n      {\n        title: 'OpenAPI Documentation',\n        href: '/documentation/en/example_app/openapi',\n      },\n      { title: 'Templates', href: '/documentation/en/example_app/templates' },\n      {\n        title: 'SubRouters',\n        href: '/documentation/en/example_app/subrouters',\n      },\n    ],\n  },\n  {\n    title: 'API Reference',\n    links: [\n      {\n        href: '/documentation/en/api_reference/',\n        title: 'Installation',\n      },\n      {\n        href: '/documentation/en/api_reference/getting_started',\n        title: 'Getting Started',\n      },\n      {\n        href: '/documentation/en/api_reference/request_object',\n        title: 'The Request Object',\n      },\n      {\n        href: '/documentation/en/api_reference/robyn_env',\n        title: 'The Robyn Env file',\n      },\n      {\n        href: '/documentation/en/api_reference/middlewares',\n        title: 'Middlewares, Events and Websockets',\n      },\n      {\n        href: '/documentation/en/api_reference/authentication',\n        title: 'Authentication',\n      },\n      {\n        href: '/documentation/en/api_reference/const_requests',\n        title: 'Const Requests and Multi Core Scaling',\n      },\n      {\n        href: '/documentation/en/api_reference/cors',\n        title: 'CORS',\n      },\n      {\n        href: '/documentation/en/api_reference/templating',\n        title: 'Templating',\n      },\n      {\n        title: 'Redirection',\n        href: '/documentation/en/api_reference/redirection',\n      },\n      {\n        href: '/documentation/en/api_reference/file-uploads',\n        title: 'File Uploads',\n      },\n      {\n        href: '/documentation/en/api_reference/form_data',\n        title: 'Form Data',\n      },\n      {\n        href: '/documentation/en/api_reference/websockets',\n        title: 'Websockets',\n      },\n      {\n        href: '/documentation/en/api_reference/server_sent_events',\n        title: 'Server-Sent Events',\n      },\n      {\n        href: '/documentation/en/api_reference/exceptions',\n        title: 'Exceptions',\n      },\n      {\n        href: '/documentation/en/api_reference/scaling',\n        title: 'Scaling the Application',\n      },\n      {\n        href: '/documentation/en/api_reference/timeout_configuration',\n        title: 'Timeout Configuration',\n      },\n      {\n        href: '/documentation/en/api_reference/advanced_features',\n        title: 'Advanced Features',\n      },\n      {\n        href: '/documentation/en/api_reference/advanced_routing',\n        title: 'Advanced Routing',\n      },\n      {\n        href: '/documentation/en/api_reference/architecture_deep_dive',\n        title: 'Architecture Deep Dive',\n      },\n      {\n        href: '/documentation/en/api_reference/multiprocess_execution',\n        title: 'Multiprocess Execution',\n      },\n      {\n        href: '/documentation/en/api_reference/using_rust_directly',\n        title: 'Direct Rust Usage',\n      },\n      {\n        href: '/documentation/en/api_reference/graphql-support',\n        title: 'GraphQL Support',\n      },\n      {\n        href: '/documentation/en/api_reference/openapi',\n        title: 'OpenAPI Documentation',\n      },\n      {\n        href: '/documentation/en/api_reference/pydantic',\n        title: 'Pydantic Integration',\n      },\n      {\n        href: '/documentation/en/api_reference/dependency_injection',\n        title: 'Dependency Injection',\n      },\n      {\n        href: '/documentation/en/api_reference/mcps',\n        title: 'MCPs',\n      },\n      {\n        href: '/documentation/en/api_reference/ai',\n        title: 'AI',\n      },\n      {\n        href: '/documentation/en/api_reference/agents',\n        title: 'AI Agents',\n      },\n    ],\n  },\n  {\n    title: 'Community Resources',\n    links: [\n      {\n        href: '/documentation/en/community-resources#talks',\n        title: 'Talks',\n      },\n      {\n        href: '/documentation/en/community-resources#blogs',\n        title: 'Blogs',\n      },\n    ],\n  },\n  {\n    title: 'Architecture',\n    links: [\n      {\n        href: '/documentation/en/architecture',\n        title: 'Architecture',\n      },\n    ],\n  },\n  {\n    title: 'Framework Comparison',\n    links: [\n      {\n        href: '/documentation/en/framework_performance_comparison',\n        title: 'Performance Comparison',\n      },\n    ],\n  },\n  {\n    title: 'Hosting',\n    links: [\n      {\n        href: '/documentation/en/hosting#railway',\n        title: 'Railway',\n      },\n      {\n        href: '/documentation/en/hosting#exposing-ports',\n        title: 'Exposing Ports',\n      },\n    ],\n  },\n  {\n    title: 'Plugins',\n    links: [\n      {\n        href: '/documentation/en/plugins',\n        title: 'Plugins',\n      },\n    ],\n  },\n  {\n    title: 'Future Roadmap',\n    links: [\n      {\n        href: '/documentation/en/api_reference/future-roadmap',\n        title: 'Upcoming Features',\n      },\n    ],\n  },\n]\n\n// Add translations for navigation titles\nconst navigationTitles = {\n  en: {\n    Documentation: 'Documentation',\n    'Example Application': 'Example Application',\n    'API Reference': 'API Reference',\n    'Community Resources': 'Community Resources',\n    Architecture: 'Architecture',\n    'Framework Comparison': 'Framework Comparison',\n    Hosting: 'Hosting',\n    Plugins: 'Plugins',\n    'Future Roadmap': 'Future Roadmap',\n  },\n  zh: {\n    'Documentation': '文档',\n    'Example Application': '应用示例',\n    'API Reference': 'API 参考',\n    'Community Resources': '社区资源',\n    'Architecture': '架构',\n    'Framework Comparison': '性能对比',\n    'Hosting': '托管',\n    'Plugins': '插件',\n    'Future Roadmap': '未来发展路线图'\n  }\n}\n\n// Add translations for navigation titles and link titles\nconst translations = {\n  en: {\n    titles: navigationTitles.en,\n    links: {\n      'Getting Started': 'Getting Started',\n      'Modeling Routes': 'Modeling Routes',\n      'Authentication and Authorization': 'Authentication and Authorization',\n      Middlewares: 'Middlewares',\n      'Real Time Notifications': 'Real Time Notifications',\n      'Monitoring and Logging': 'Monitoring and Logging',\n      Deployment: 'Deployment',\n      'OpenAPI Documentation': 'OpenAPI Documentation',\n      Templates: 'Templates',\n      SubRouters: 'SubRouters',\n      Installation: 'Installation',\n      'The Request Object': 'The Request Object',\n      'The Robyn Env file': 'The Robyn Env file',\n      'Middlewares, Events and Websockets':\n        'Middlewares, Events and Websockets',\n      Authentication: 'Authentication',\n      'Const Requests and Multi Core Scaling':\n        'Const Requests and Multi Core Scaling',\n      CORS: 'CORS',\n      Templating: 'Templating',\n      Redirection: 'Redirection',\n      'File Uploads': 'File Uploads',\n      'Form Data': 'Form Data',\n      Websockets: 'Websockets',\n      Exceptions: 'Exceptions',\n      'Scaling the Application': 'Scaling the Application',\n      'Timeout Configuration': 'Timeout Configuration',\n      'Advanced Features': 'Advanced Features',\n      'Advanced Routing': 'Advanced Routing',\n      'Architecture Deep Dive': 'Architecture Deep Dive',\n      'Multiprocess Execution': 'Multiprocess Execution',\n      'Direct Rust Usage': 'Direct Rust Usage',\n      'GraphQL Support': 'GraphQL Support',\n      'Dependency Injection': 'Dependency Injection',\n      'Pydantic Integration': 'Pydantic Integration',\n      'AI': 'AI',\n      'AI Agents': 'AI Agents',\n      Talks: 'Talks',\n      Blogs: 'Blogs',\n      Introduction: 'Introduction',\n      'Upcoming Features': 'Upcoming Features',\n      Railway: 'Railway',\n      'Exposing Ports': 'Exposing Ports',\n    },\n  },\n  zh: {\n    titles: navigationTitles.zh,\n    links: {\n      'Getting Started': '开始',\n      'Modeling Routes': '路由建模',\n      'Authentication and Authorization': '身份验证',\n      'Middlewares': '身份验证中间件',\n      'Real Time Notifications': '即时通讯',\n      'Monitoring and Logging': '监控和日志',\n      Deployment: '部署',\n      'OpenAPI Documentation': 'OpenAPI 文档',\n      Templates: '模板',\n      SubRouters: '子路由',\n      Installation: '安装',\n      'The Request Object': '请求对象',\n      'The Robyn Env file': 'Robyn 环境文件',\n      'Middlewares, Events and Websockets': '中间件、事件和 WebSocket',\n      Authentication: '身份验证',\n      'Const Requests and Multi Core Scaling': '常量请求和多核心扩展',\n      CORS: '跨域资源共享',\n      Templating: '模板系统',\n      Redirection: '重定向',\n      'File Uploads': '文件上传',\n      'Form Data': '表单数据',\n      'Websockets': 'WebSocket',\n      'Server-Sent Events': '服务器发送事件',\n      'Exceptions': '异常处理',\n      'Scaling the Application': '多核扩展',\n      'Timeout Configuration': '超时配置',\n      'Advanced Features': '高级特性',\n      'Advanced Routing': '高级路由',\n      'Architecture Deep Dive': '架构深度剖析',\n      'Multiprocess Execution': '多进程执行',\n      'Direct Rust Usage': '直接使用 Rust',\n      'GraphQL Support': 'GraphQL 支持',\n      'Dependency Injection': '依赖注入',\n      'Pydantic Integration': 'Pydantic 集成',\n      'AI': 'AI',\n      'AI Agents': 'AI 代理',\n      'Talks': '演讲',\n      'Blogs': '博客',\n      'Introduction': '引入',\n      'Upcoming Features': '即将推出的功能',\n      'Railway': 'Railway',\n      'Exposing Ports': '开放端口'\n    }\n  }\n}\n\nexport function Navigation(props) {\n  const router = useRouter()\n  const currentLanguage = router.asPath.includes('/zh') ? 'zh' : 'en'\n\n  const getLocalizedHref = (href) => {\n    if (href === '/documentation') {\n      return `/documentation/${currentLanguage}`\n    }\n    return href.replace(\n      '/documentation/en/',\n      `/documentation/${currentLanguage}/`\n    )\n  }\n\n  // Create localized navigation with translated titles and link titles\n  const localizedNavigation = navigation.map((group) => ({\n    ...group,\n    title: translations[currentLanguage].titles[group.title] || group.title,\n    links: group.links.map((link) => ({\n      ...link,\n      title: translations[currentLanguage].links[link.title] || link.title,\n      href: getLocalizedHref(link.href),\n    })),\n  }))\n\n  return (\n    <nav {...props}>\n      <div className=\"flex items-center justify-between px-4 py-2\">\n        <LanguageSelector />\n      </div>\n      <ul role=\"list\">\n        <TopLevelNavItem href=\"/\">API</TopLevelNavItem>\n        <TopLevelNavItem href=\"#\">\n          {currentLanguage === 'zh' ? '文档' : 'Documentation'}\n        </TopLevelNavItem>\n        <TopLevelNavItem href=\"#\">\n          {currentLanguage === 'zh' ? '支持' : 'Support'}\n        </TopLevelNavItem>\n        {localizedNavigation.map((group, groupIndex) => (\n          <NavigationGroup\n            key={group.title}\n            group={group}\n            className={groupIndex === 0 && 'md:mt-0'}\n          />\n        ))}\n      </ul>\n    </nav>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/Prose.jsx",
    "content": "import clsx from 'clsx'\n\nexport function Prose({ className, ...props }) {\n  return (\n    <article\n      className={clsx(\n        className,\n        'prose text-white dark:prose-invert prose-headings:font-extrabold'\n      )}\n      {...props}\n    />\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/Search.jsx",
    "content": "import { forwardRef, Fragment, useEffect, useId, useRef, useState } from 'react'\nimport { useRouter } from 'next/router'\nimport { createAutocomplete } from '@algolia/autocomplete-core'\nimport { getAlgoliaResults } from '@algolia/autocomplete-preset-algolia'\nimport { Dialog, Transition } from '@headlessui/react'\nimport algoliasearch from 'algoliasearch/lite'\nimport clsx from 'clsx'\n\nconst searchClient = algoliasearch(\n  process.env.NEXT_PUBLIC_ALGOLIA_APP_ID,\n  process.env.NEXT_PUBLIC_ALGOLIA_API_KEY\n)\n\nfunction useAutocomplete() {\n  let id = useId()\n  let router = useRouter()\n  let [autocompleteState, setAutocompleteState] = useState({})\n\n  let [autocomplete] = useState(() =>\n    createAutocomplete({\n      id,\n      placeholder: 'Find something...',\n      defaultActiveItemId: 0,\n      onStateChange({ state }) {\n        setAutocompleteState(state)\n      },\n      shouldPanelOpen({ state }) {\n        return state.query !== ''\n      },\n      navigator: {\n        navigate({ itemUrl }) {\n          autocomplete.setIsOpen(true)\n          router.push(itemUrl)\n        },\n      },\n      getSources() {\n        return [\n          {\n            sourceId: 'documentation',\n            getItemInputValue({ item }) {\n              return item.query\n            },\n            getItemUrl({ item }) {\n              let url = new URL(item.url)\n              const pathname = url.pathname\n              \n              // Get current language from router\n              const currentLanguage = router.asPath.includes('/zh') ? 'zh' : 'en'\n              \n              // Check if the URL already has a language prefix\n              if (pathname.match(/\\/documentation\\/(en|zh)\\//)) {\n                // If it does, ensure it matches the current language\n                const pathWithCorrectLang = pathname.replace(\n                  /\\/documentation\\/(en|zh)\\//,\n                  `/documentation/${currentLanguage}/`\n                )\n                return `${pathWithCorrectLang}${url.hash}`\n              } else if (pathname.includes('/documentation/')) {\n                // If it doesn't have a language prefix but has /documentation/, add the language prefix\n                const pathWithLang = pathname.replace(\n                  '/documentation/',\n                  `/documentation/${currentLanguage}/`\n                )\n                return `${pathWithLang}${url.hash}`\n              }\n              \n              // Return the original URL if it doesn't match any of the above patterns\n              return `${pathname}${url.hash}`\n            },\n            onSelect({ itemUrl }) {\n              router.push(itemUrl)\n            },\n            getItems({ query }) {\n              return getAlgoliaResults({\n                searchClient,\n                queries: [\n                  {\n                    query,\n                    indexName: process.env.NEXT_PUBLIC_ALGOLIA_INDEX_NAME,\n                    params: {\n                      hitsPerPage: 5,\n                      highlightPreTag:\n                        '<mark class=\"underline bg-transparent text-orange-500\">',\n                      highlightPostTag: '</mark>',\n                    },\n                  },\n                ],\n              })\n            },\n          },\n        ]\n      },\n    })\n  )\n\n  return { autocomplete, autocompleteState }\n}\n\nfunction resolveResult(result) {\n  let allLevels = Object.keys(result.hierarchy)\n  let hierarchy = Object.entries(result._highlightResult.hierarchy).filter(\n    ([, { value }]) => Boolean(value)\n  )\n  let levels = hierarchy.map(([level]) => level)\n\n  let level =\n    result.type === 'content'\n      ? levels.pop()\n      : levels\n          .filter(\n            (level) =>\n              allLevels.indexOf(level) <= allLevels.indexOf(result.type)\n          )\n          .pop()\n\n  return {\n    titleHtml: result._highlightResult.hierarchy[level].value,\n    hierarchyHtml: hierarchy\n      .slice(0, levels.indexOf(level))\n      .map(([, { value }]) => value),\n  }\n}\n\nfunction SearchIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" fill=\"none\" aria-hidden=\"true\" {...props}>\n      <path\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M12.01 12a4.25 4.25 0 1 0-6.02-6 4.25 4.25 0 0 0 6.02 6Zm0 0 3.24 3.25\"\n      />\n    </svg>\n  )\n}\n\nfunction NoResultsIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" fill=\"none\" aria-hidden=\"true\" {...props}>\n      <path\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M12.01 12a4.237 4.237 0 0 0 1.24-3c0-.62-.132-1.207-.37-1.738M12.01 12A4.237 4.237 0 0 1 9 13.25c-.635 0-1.237-.14-1.777-.388M12.01 12l3.24 3.25m-3.715-9.661a4.25 4.25 0 0 0-5.975 5.908M4.5 15.5l11-11\"\n      />\n    </svg>\n  )\n}\n\nfunction LoadingIcon(props) {\n  let id = useId()\n\n  return (\n    <svg viewBox=\"0 0 20 20\" fill=\"none\" aria-hidden=\"true\" {...props}>\n      <circle cx=\"10\" cy=\"10\" r=\"5.5\" strokeLinejoin=\"round\" />\n      <path\n        stroke={`url(#${id})`}\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M15.5 10a5.5 5.5 0 1 0-5.5 5.5\"\n      />\n      <defs>\n        <linearGradient\n          id={id}\n          x1=\"13\"\n          x2=\"9.5\"\n          y1=\"9\"\n          y2=\"15\"\n          gradientUnits=\"userSpaceOnUse\"\n        >\n          <stop stopColor=\"currentColor\" />\n          <stop offset=\"1\" stopColor=\"currentColor\" stopOpacity=\"0\" />\n        </linearGradient>\n      </defs>\n    </svg>\n  )\n}\n\nfunction SearchResult({ result, resultIndex, autocomplete, collection }) {\n  let id = useId()\n  let { titleHtml, hierarchyHtml } = resolveResult(result)\n\n  return (\n    <li\n      className={clsx(\n        'group block cursor-default px-4 py-3 aria-selected:bg-zinc-800/50',\n        resultIndex > 0 && 'border-t border-zinc-800'\n      )}\n      aria-labelledby={`${id}-hierarchy ${id}-title`}\n      {...autocomplete.getItemProps({\n        item: result,\n        source: collection.source,\n      })}\n    >\n      <div\n        id={`${id}-title`}\n        aria-hidden=\"true\"\n        className=\"text-sm font-medium text-white  group-aria-selected:text-orange-500\"\n        dangerouslySetInnerHTML={{ __html: titleHtml }}\n      />\n      {hierarchyHtml.length > 0 && (\n        <div\n          id={`${id}-hierarchy`}\n          aria-hidden=\"true\"\n          className=\"text-2xs mt-1 truncate whitespace-nowrap text-zinc-500\"\n        >\n          {hierarchyHtml.map((item, itemIndex, items) => (\n            <Fragment key={itemIndex}>\n              <span dangerouslySetInnerHTML={{ __html: item }} />\n              <span\n                className={\n                  itemIndex === items.length - 1\n                    ? 'sr-only'\n                    : 'mx-2 text-zinc-700'\n                }\n              >\n                /\n              </span>\n            </Fragment>\n          ))}\n        </div>\n      )}\n    </li>\n  )\n}\n\nfunction SearchResults({ autocomplete, query, collection }) {\n  if (collection.items.length === 0) {\n    return (\n      <div className=\"p-6 text-center\">\n        <NoResultsIcon className=\"mx-auto h-5 w-5 stroke-zinc-600\" />\n        <p className=\"mt-2 text-xs text-zinc-400\">\n          Nothing found for{' '}\n          <strong className=\"break-words font-semibold text-white\">\n            &lsquo;{query}&rsquo;\n          </strong>\n          . Please try again.\n        </p>\n      </div>\n    )\n  }\n\n  return (\n    <ul role=\"list\" {...autocomplete.getListProps()}>\n      {collection.items.map((result, resultIndex) => (\n        <SearchResult\n          key={result.objectID}\n          result={result}\n          resultIndex={resultIndex}\n          autocomplete={autocomplete}\n          collection={collection}\n        />\n      ))}\n    </ul>\n  )\n}\n\nconst SearchInput = forwardRef(function SearchInput(\n  { autocomplete, autocompleteState, onClose },\n  inputRef\n) {\n  let inputProps = autocomplete.getInputProps({})\n\n  return (\n    <div className=\"group relative flex h-12\">\n      <SearchIcon className=\"pointer-events-none absolute left-3 top-0 h-full w-5 stroke-zinc-500\" />\n      <input\n        ref={inputRef}\n        className={clsx(\n          'flex-auto appearance-none bg-transparent pl-10  text-white outline-none placeholder:text-zinc-500 focus:w-full focus:flex-none sm:text-sm [&::-webkit-search-cancel-button]:hidden [&::-webkit-search-decoration]:hidden [&::-webkit-search-results-button]:hidden [&::-webkit-search-results-decoration]:hidden',\n          autocompleteState.status === 'stalled' ? 'pr-11' : 'pr-4'\n        )}\n        {...inputProps}\n        onKeyDown={(event) => {\n          if (\n            event.key === 'Escape' &&\n            !autocompleteState.isOpen &&\n            autocompleteState.query === ''\n          ) {\n            // In Safari, closing the dialog with the escape key can sometimes cause the scroll position to jump to the\n            // bottom of the page. This is a workaround for that until we can figure out a proper fix in Headless UI.\n            document.activeElement?.blur()\n\n            onClose()\n          } else {\n            inputProps.onKeyDown(event)\n          }\n        }}\n      />\n      {autocompleteState.status === 'stalled' && (\n        <div className=\"absolute inset-y-0 right-3 flex items-center\">\n          <LoadingIcon className=\"h-5 w-5 animate-spin  stroke-zinc-800 text-orange-400\" />\n        </div>\n      )}\n    </div>\n  )\n})\n\nfunction AlgoliaLogo(props) {\n  return (\n    <svg viewBox=\"0 0 71 16\" role=\"img\" aria-label=\"Algolia\" {...props}>\n      <path\n        fillRule=\"evenodd\"\n        d=\"M34.98 8.81V.19a.189.189 0 0 0-.218-.186l-1.615.254a.19.19 0 0 0-.16.187l.006 8.741c0 .414 0 2.966 3.07 3.056a.19.19 0 0 0 .195-.19v-1.304a.187.187 0 0 0-.164-.187c-1.115-.128-1.115-1.522-1.115-1.75v-.002Z\"\n        clipRule=\"evenodd\"\n      />\n      <path d=\"M61.605 3.352H59.98a.189.189 0 0 0-.189.189v8.514c0 .104.085.189.189.189h1.625a.189.189 0 0 0 .188-.19V3.542a.189.189 0 0 0-.188-.189Z\" />\n      <path\n        fillRule=\"evenodd\"\n        d=\"M59.98 2.285h1.625a.189.189 0 0 0 .188-.189V.19a.189.189 0 0 0-.218-.187l-1.624.255a.189.189 0 0 0-.16.186v1.652c0 .104.085.189.189.189ZM57.172 8.81V.19a.189.189 0 0 0-.218-.186l-1.615.254a.19.19 0 0 0-.16.187l.006 8.741c0 .414 0 2.966 3.07 3.056a.19.19 0 0 0 .196-.19v-1.304a.187.187 0 0 0-.164-.187c-1.115-.128-1.115-1.522-1.115-1.75v-.002ZM52.946 4.568a3.628 3.628 0 0 0-1.304-.906 4.347 4.347 0 0 0-1.666-.315c-.601 0-1.157.101-1.662.315a3.822 3.822 0 0 0-1.304.906c-.367.39-.652.86-.856 1.408-.204.55-.296 1.196-.296 1.868 0 .671.103 1.18.306 1.734.204.554.484 1.027.846 1.42.361.39.795.691 1.3.91.504.218 1.283.33 1.676.335.392 0 1.177-.122 1.686-.335.51-.214.943-.52 1.305-.91.361-.393.641-.866.84-1.42.199-.555.295-1.063.295-1.734 0-.672-.107-1.318-.32-1.868a4.203 4.203 0 0 0-.846-1.408Zm-1.421 5.239c-.367.504-.882.758-1.539.758-.657 0-1.172-.25-1.539-.758-.367-.504-.55-1.088-.55-1.958 0-.86.178-1.573.545-2.076.367-.504.882-.752 1.538-.752.658 0 1.172.248 1.539.752.367.498.556 1.215.556 2.076 0 .87-.184 1.449-.55 1.958ZM29.35 3.352H27.77c-1.547 0-2.909.815-3.703 2.051a4.643 4.643 0 0 0-.736 2.519 4.611 4.611 0 0 0 1.949 3.783 2.574 2.574 0 0 0 1.542.428l.034-.002.084-.006.032-.004.088-.011.02-.003c1.052-.163 1.97-.986 2.268-2.01v1.85c0 .105.085.19.19.19h1.612a.189.189 0 0 0 .19-.19V3.541a.189.189 0 0 0-.19-.189H29.35Zm0 6.62c-.39.326-.896.448-1.435.484l-.016.002a1.68 1.68 0 0 1-.107.003c-1.352 0-2.468-1.149-2.468-2.54 0-.328.063-.64.173-.927.36-.932 1.241-1.591 2.274-1.591h1.578v4.57ZM69.009 3.352H67.43c-1.547 0-2.908.815-3.703 2.051a4.643 4.643 0 0 0-.736 2.519 4.611 4.611 0 0 0 1.949 3.783 2.575 2.575 0 0 0 1.542.428l.034-.002.084-.006.033-.004.087-.011.02-.003c1.053-.163 1.97-.986 2.269-2.01v1.85c0 .105.084.19.188.19h1.614a.189.189 0 0 0 .188-.19V3.541a.189.189 0 0 0-.188-.189h-1.802Zm0 6.62c-.39.326-.895.448-1.435.484l-.016.002a1.675 1.675 0 0 1-.107.003c-1.352 0-2.468-1.149-2.468-2.54 0-.328.063-.64.174-.927.359-.932 1.24-1.591 2.273-1.591h1.579v4.57ZM42.775 3.352h-1.578c-1.547 0-2.909.815-3.704 2.051a4.63 4.63 0 0 0-.735 2.519 4.6 4.6 0 0 0 1.65 3.555c.094.083.194.16.298.228a2.575 2.575 0 0 0 2.966-.08c.52-.37.924-.913 1.103-1.527v1.608h-.004v.354c0 .7-.182 1.225-.554 1.58-.372.354-.994.532-1.864.532-.356 0-.921-.02-1.491-.078a.19.19 0 0 0-.2.136l-.41 1.379a.19.19 0 0 0 .155.24c.688.1 1.36.15 1.748.15 1.565 0 2.725-.343 3.484-1.03.688-.621 1.061-1.564 1.127-2.832V3.54a.189.189 0 0 0-.19-.189h-1.801Zm0 2.051s.021 4.452 0 4.587c-.386.312-.867.435-1.391.47l-.016.001a1.751 1.751 0 0 1-.233 0c-1.293-.067-2.385-1.192-2.385-2.54 0-.327.063-.64.174-.927.359-.931 1.24-1.591 2.273-1.591h1.578Z\"\n        clipRule=\"evenodd\"\n      />\n      <path d=\"M8.725.001C4.356.001.795 3.523.732 7.877c-.064 4.422 3.524 8.085 7.946 8.111a7.94 7.94 0 0 0 3.849-.96.187.187 0 0 0 .034-.305l-.748-.663a.528.528 0 0 0-.555-.094 6.461 6.461 0 0 1-2.614.513c-3.574-.043-6.46-3.016-6.404-6.59a6.493 6.493 0 0 1 6.485-6.38h6.485v11.527l-3.68-3.269a.271.271 0 0 0-.397.042 3.014 3.014 0 0 1-5.416-1.583 3.02 3.02 0 0 1 3.008-3.248 3.02 3.02 0 0 1 3.005 2.75.537.537 0 0 0 .176.356l.958.85a.187.187 0 0 0 .308-.106c.07-.37.094-.755.067-1.15a4.536 4.536 0 0 0-4.23-4.2A4.53 4.53 0 0 0 4.203 7.87c-.067 2.467 1.954 4.593 4.421 4.648a4.498 4.498 0 0 0 2.756-.863l4.808 4.262a.32.32 0 0 0 .531-.239V.304a.304.304 0 0 0-.303-.303h-7.69Z\" />\n    </svg>\n  )\n}\n\nfunction SearchButton(props) {\n  let [modifierKey, setModifierKey] = useState()\n\n  useEffect(() => {\n    setModifierKey(\n      /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform) ? '⌘' : 'Ctrl '\n    )\n  }, [])\n\n  return (\n    <>\n      <button\n        type=\"button\"\n        className=\"hidden h-8 w-full items-center gap-2 rounded-full bg-white bg-white/5 pl-2 pr-3 text-sm  text-zinc-400  ring-inset ring-white/10  transition hover:ring-white/20 lg:flex focus:[&:not(:focus-visible)]:outline-none\"\n        {...props}\n      >\n        <SearchIcon className=\"h-5 w-5 stroke-current\" />\n        Find something...\n        <kbd className=\"text-2xs ml-auto text-zinc-500\">\n          <kbd className=\"font-sans\">{modifierKey}</kbd>\n          <kbd className=\"font-sans\">K</kbd>\n        </kbd>\n      </button>\n      <button\n        type=\"button\"\n        className=\"flex h-6 w-6 items-center justify-center rounded-md transition hover:bg-white/5 lg:hidden focus:[&:not(:focus-visible)]:outline-none\"\n        aria-label=\"Find something...\"\n        {...props}\n      >\n        <SearchIcon className=\"h-5 w-5 stroke-white\" />\n      </button>\n    </>\n  )\n}\n\nfunction SearchDialog({ open, setOpen, className }) {\n  let router = useRouter()\n  let formRef = useRef()\n  let panelRef = useRef()\n  let inputRef = useRef()\n  let { autocomplete, autocompleteState } = useAutocomplete()\n\n  useEffect(() => {\n    if (!open) {\n      return\n    }\n\n    function onRouteChange() {\n      setOpen(false)\n    }\n\n    router.events.on('routeChangeStart', onRouteChange)\n    router.events.on('hashChangeStart', onRouteChange)\n\n    return () => {\n      router.events.off('routeChangeStart', onRouteChange)\n      router.events.off('hashChangeStart', onRouteChange)\n    }\n  }, [open, setOpen, router])\n\n  useEffect(() => {\n    if (open) {\n      return\n    }\n\n    function onKeyDown(event) {\n      if (event.key === 'k' && (event.metaKey || event.ctrlKey)) {\n        event.preventDefault()\n        setOpen(true)\n      }\n    }\n\n    window.addEventListener('keydown', onKeyDown)\n\n    return () => {\n      window.removeEventListener('keydown', onKeyDown)\n    }\n  }, [open, setOpen])\n\n  return (\n    <Transition.Root\n      show={open}\n      as={Fragment}\n      afterLeave={() => autocomplete.setQuery('')}\n    >\n      <Dialog\n        onClose={setOpen}\n        className={clsx('fixed inset-0 z-50', className)}\n      >\n        <Transition.Child\n          as={Fragment}\n          enter=\"ease-out duration-300\"\n          enterFrom=\"opacity-0\"\n          enterTo=\"opacity-100\"\n          leave=\"ease-in duration-200\"\n          leaveFrom=\"opacity-100\"\n          leaveTo=\"opacity-0\"\n        >\n          <div className=\"fixed inset-0  bg-black/40 backdrop-blur-sm\" />\n        </Transition.Child>\n\n        <div className=\"fixed inset-0 overflow-y-auto px-4 py-4 sm:px-6 sm:py-20 md:py-32 lg:px-8 lg:py-[15vh]\">\n          <Transition.Child\n            as={Fragment}\n            enter=\"ease-out duration-300\"\n            enterFrom=\"opacity-0 scale-95\"\n            enterTo=\"opacity-100 scale-100\"\n            leave=\"ease-in duration-200\"\n            leaveFrom=\"opacity-100 scale-100\"\n            leaveTo=\"opacity-0 scale-95\"\n          >\n            <Dialog.Panel className=\"ring-zinc-900/7.5 mx-auto overflow-hidden rounded-lg bg-zinc-900 shadow-xl ring-zinc-800 sm:max-w-xl\">\n              <div {...autocomplete.getRootProps({})}>\n                <form\n                  ref={formRef}\n                  {...autocomplete.getFormProps({\n                    inputElement: inputRef.current,\n                  })}\n                >\n                  <SearchInput\n                    ref={inputRef}\n                    autocomplete={autocomplete}\n                    autocompleteState={autocompleteState}\n                    onClose={() => setOpen(false)}\n                  />\n                  <div\n                    ref={panelRef}\n                    className=\"bg-white/2.5 border-t border-zinc-100/5 empty:hidden\"\n                    {...autocomplete.getPanelProps({})}\n                  >\n                    {autocompleteState.isOpen && (\n                      <>\n                        <SearchResults\n                          autocomplete={autocomplete}\n                          query={autocompleteState.query}\n                          collection={autocompleteState.collections[0]}\n                        />\n                        <p className=\"flex items-center justify-end gap-2 border-t border-zinc-800 px-4 py-2 text-xs text-zinc-500\">\n                          Search by{' '}\n                          <AlgoliaLogo className=\"h-4 fill-zinc-400\" />\n                        </p>\n                      </>\n                    )}\n                  </div>\n                </form>\n              </div>\n            </Dialog.Panel>\n          </Transition.Child>\n        </div>\n      </Dialog>\n    </Transition.Root>\n  )\n}\n\nfunction useSearchProps() {\n  let buttonRef = useRef()\n  let [open, setOpen] = useState(false)\n\n  return {\n    buttonProps: {\n      ref: buttonRef,\n      onClick() {\n        setOpen(true)\n      },\n    },\n    dialogProps: {\n      open,\n      setOpen(open) {\n        let { width, height } = buttonRef.current.getBoundingClientRect()\n        if (!open || (width !== 0 && height !== 0)) {\n          setOpen(open)\n        }\n      },\n    },\n  }\n}\n\nexport function Search() {\n  let [modifierKey, setModifierKey] = useState()\n  let { buttonProps, dialogProps } = useSearchProps()\n\n  useEffect(() => {\n    setModifierKey(\n      /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform) ? '⌘' : 'Ctrl '\n    )\n  }, [])\n\n  return (\n    <div className=\"hidden lg:block lg:max-w-md lg:flex-auto\">\n      <button\n        type=\"button\"\n        className=\"hidden h-8 w-full items-center gap-2 rounded-full  bg-white/5 pl-2 pr-3  text-sm text-zinc-400 ring-1  ring-inset ring-white/10  transition hover:ring-zinc-900/20 lg:flex focus:[&:not(:focus-visible)]:outline-none\"\n        {...buttonProps}\n      >\n        <SearchIcon className=\"h-5 w-5 stroke-current\" />\n        <kbd className=\"text-2xs ml-auto text-zinc-500\">\n          <kbd className=\"font-sans\">{modifierKey}</kbd>\n          <kbd className=\"font-sans\">K</kbd>\n        </kbd>\n      </button>\n      <SearchDialog className=\"hidden lg:block\" {...dialogProps} />\n    </div>\n  )\n}\n\nexport function MobileSearch() {\n  let { buttonProps, dialogProps } = useSearchProps()\n\n  return (\n    <div className=\"contents lg:hidden\">\n      <button\n        type=\"button\"\n        className=\"flex h-6 w-6 items-center justify-center rounded-md transition hover:bg-white/5 lg:hidden focus:[&:not(:focus-visible)]:outline-none\"\n        aria-label=\"Find something...\"\n        {...buttonProps}\n      >\n        <SearchIcon className=\"mt-4 h-5 w-5 stroke-white\" />\n      </button>\n      <SearchDialog className=\"lg:hidden\" {...dialogProps} />\n    </div>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/SectionProvider.jsx",
    "content": "import {\n  createContext,\n  useContext,\n  useEffect,\n  useLayoutEffect,\n  useState,\n} from 'react'\nimport { createStore, useStore } from 'zustand'\n\nimport { remToPx } from '@/lib/remToPx'\n\nfunction createSectionStore(sections) {\n  return createStore((set) => ({\n    sections,\n    visibleSections: [],\n    setVisibleSections: (visibleSections) =>\n      set((state) =>\n        state.visibleSections.join() === visibleSections.join()\n          ? {}\n          : { visibleSections }\n      ),\n    registerHeading: ({ id, ref, offsetRem }) =>\n      set((state) => {\n        return {\n          sections: state.sections.map((section) => {\n            if (section.id === id) {\n              return {\n                ...section,\n                headingRef: ref,\n                offsetRem,\n              }\n            }\n            return section\n          }),\n        }\n      }),\n  }))\n}\n\nfunction useVisibleSections(sectionStore) {\n  let setVisibleSections = useStore(sectionStore, (s) => s.setVisibleSections)\n  let sections = useStore(sectionStore, (s) => s.sections)\n\n  useEffect(() => {\n    function checkVisibleSections() {\n      let { innerHeight, scrollY } = window\n      let newVisibleSections = []\n\n      for (\n        let sectionIndex = 0;\n        sectionIndex < sections.length;\n        sectionIndex++\n      ) {\n        let { id, headingRef, offsetRem } = sections[sectionIndex]\n        let offset = remToPx(offsetRem)\n        let top = headingRef.current.getBoundingClientRect().top + scrollY\n\n        if (sectionIndex === 0 && top - offset > scrollY) {\n          newVisibleSections.push('_top')\n        }\n\n        let nextSection = sections[sectionIndex + 1]\n        let bottom =\n          (nextSection?.headingRef.current.getBoundingClientRect().top ??\n            Infinity) +\n          scrollY -\n          remToPx(nextSection?.offsetRem ?? 0)\n\n        if (\n          (top > scrollY && top < scrollY + innerHeight) ||\n          (bottom > scrollY && bottom < scrollY + innerHeight) ||\n          (top <= scrollY && bottom >= scrollY + innerHeight)\n        ) {\n          newVisibleSections.push(id)\n        }\n      }\n\n      setVisibleSections(newVisibleSections)\n    }\n\n    let raf = window.requestAnimationFrame(() => checkVisibleSections())\n    window.addEventListener('scroll', checkVisibleSections, { passive: true })\n    window.addEventListener('resize', checkVisibleSections)\n\n    return () => {\n      window.cancelAnimationFrame(raf)\n      window.removeEventListener('scroll', checkVisibleSections)\n      window.removeEventListener('resize', checkVisibleSections)\n    }\n  }, [setVisibleSections, sections])\n}\n\nconst SectionStoreContext = createContext()\n\nconst useIsomorphicLayoutEffect =\n  typeof window === 'undefined' ? useEffect : useLayoutEffect\n\nexport function SectionProvider({ sections, children }) {\n  let [sectionStore] = useState(() => createSectionStore(sections))\n\n  useVisibleSections(sectionStore)\n\n  useIsomorphicLayoutEffect(() => {\n    sectionStore.setState({ sections })\n  }, [sectionStore, sections])\n\n  return (\n    <SectionStoreContext.Provider value={sectionStore}>\n      {children}\n    </SectionStoreContext.Provider>\n  )\n}\n\nexport function useSectionStore(selector) {\n  let store = useContext(SectionStoreContext)\n  return useStore(store, selector)\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/Tag.jsx",
    "content": "import clsx from 'clsx'\n\nconst variantStyles = {\n  medium: 'rounded-lg px-1.5 ring-1 ring-inset',\n}\n\nconst colorStyles = {\n  orange: {\n    small: 'text-orange-400',\n    medium: 'ring-orange-400/30 bg-orange-400/10 text-orange-400',\n  },\n  sky: {\n    small: 'text-sky-500',\n    medium: 'ring-sky-400/30 bg-sky-400/10 text-sky-400',\n  },\n  amber: {\n    small: 'text-amber-500',\n    medium: 'ring-amber-400/30 bg-amber-400/10 text-amber-400',\n  },\n  rose: {\n    small: 'text-rose-500',\n    medium: 'ring-rose-500/20 bg-rose-400/10 text-rose-400',\n  },\n  zinc: {\n    small: 'text-zinc-500',\n    medium: 'ring-zinc-500/20 bg-zinc-400/10 text-zinc-400',\n  },\n}\n\nconst valueColorMap = {\n  get: 'orange',\n  post: 'sky',\n  put: 'amber',\n  delete: 'rose',\n}\n\nexport function Tag({\n  children,\n  variant = 'medium',\n  color = valueColorMap[children.toLowerCase()] ?? 'orange',\n}) {\n  return (\n    <span\n      className={clsx(\n        'font-mono text-[0.625rem] font-semibold leading-6',\n        variantStyles[variant],\n        colorStyles[color][variant]\n      )}\n    >\n      {children}\n    </span>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/icons/BellIcon.jsx",
    "content": "export function BellIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" {...props}>\n      <path\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M4.438 8.063a5.563 5.563 0 0 1 11.125 0v2.626c0 1.182.34 2.34.982 3.332L17.5 15.5h-15l.955-1.479c.641-.993.982-2.15.982-3.332V8.062Z\"\n      />\n      <path\n        fill=\"none\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M7.5 15.5v0a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v0\"\n      />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/icons/BoltIcon.jsx",
    "content": "export function BoltIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" {...props}>\n      <path\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M4.5 11.5 10 2v5.5a1 1 0 0 0 1 1h4.5L10 18v-5.5a1 1 0 0 0-1-1H4.5Z\"\n      />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/icons/BookIcon.jsx",
    "content": "export function BookIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" {...props}>\n      <path\n        fill=\"none\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"m10 5.5-7.5-3v12l7.5 3m0-12 7.5-3v12l-7.5 3m0-12v12\"\n      />\n      <path\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"m17.5 2.5-7.5 3v12l7.5-3v-12Z\"\n      />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/icons/CalendarIcon.jsx",
    "content": "export function CalendarIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" {...props}>\n      <path\n        fill=\"none\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M2.5 6.5a2 2 0 0 1 2-2h11a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-11a2 2 0 0 1-2-2v-9Z\"\n      />\n      <path\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M2.5 6.5a2 2 0 0 1 2-2h11a2 2 0 0 1 2 2v2h-15v-2Z\"\n      />\n      <path\n        fill=\"none\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M5.5 5.5v-3M14.5 5.5v-3\"\n      />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/icons/CartIcon.jsx",
    "content": "export function CartIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" {...props}>\n      <path\n        strokeWidth=\"0\"\n        d=\"M5.98 11.288 3.5 5.5h14l-2.48 5.788A2 2 0 0 1 13.18 12.5H7.82a2 2 0 0 1-1.838-1.212Z\"\n      />\n      <path\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"m3.5 5.5 2.48 5.788A2 2 0 0 0 7.82 12.5h5.362a2 2 0 0 0 1.839-1.212L17.5 5.5h-14Zm0 0-1-2M6.5 14.5a1 1 0 1 1 0 2 1 1 0 0 1 0-2ZM14.5 14.5a1 1 0 1 1 0 2 1 1 0 0 1 0-2Z\"\n      />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/icons/ChatBubbleIcon.jsx",
    "content": "export function ChatBubbleIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" {...props}>\n      <path\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M10 16.5c4.142 0 7.5-3.134 7.5-7s-3.358-7-7.5-7c-4.142 0-7.5 3.134-7.5 7 0 1.941.846 3.698 2.214 4.966L3.5 17.5c2.231 0 3.633-.553 4.513-1.248A8.014 8.014 0 0 0 10 16.5Z\"\n      />\n      <path\n        fill=\"none\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M7.5 8.5h5M8.5 11.5h3\"\n      />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/icons/CheckIcon.jsx",
    "content": "export function CheckIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" {...props}>\n      <path\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M10 1.5a8.5 8.5 0 1 1 0 17 8.5 8.5 0 0 1 0-17Z\"\n      />\n      <path\n        fill=\"none\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"m7.5 10.5 2 2c1-3.5 3-5 3-5\"\n      />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/icons/ChevronRightLeftIcon.jsx",
    "content": "export function ChevronRightLeftIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" {...props}>\n      <path\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M1.5 10A6.5 6.5 0 0 1 8 3.5h4a6.5 6.5 0 1 1 0 13H8A6.5 6.5 0 0 1 1.5 10Z\"\n      />\n      <path\n        fill=\"none\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"m7.5 7.5-3 2.5 3 2.5M12.5 7.5l3 2.5-3 2.5\"\n      />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/icons/ClipboardIcon.jsx",
    "content": "export function ClipboardIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" {...props}>\n      <path\n        fill=\"none\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M3.5 6v10a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-1l-.447.894A2 2 0 0 1 11.263 6H8.737a2 2 0 0 1-1.789-1.106L6.5 4h-1a2 2 0 0 0-2 2Z\"\n      />\n      <path\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"m13.5 4-.447.894A2 2 0 0 1 11.263 6H8.737a2 2 0 0 1-1.789-1.106L6.5 4l.724-1.447A1 1 0 0 1 8.118 2h3.764a1 1 0 0 1 .894.553L13.5 4Z\"\n      />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/icons/CogIcon.jsx",
    "content": "export function CogIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" {...props}>\n      <path\n        strokeWidth=\"0\"\n        fillRule=\"evenodd\"\n        d=\"M11.063 1.5H8.937l-.14 1.128c-.086.682-.61 1.22-1.246 1.484-.634.264-1.37.247-1.912-.175l-.898-.699-1.503 1.503.699.898c.422.543.44 1.278.175 1.912-.264.635-.802 1.16-1.484 1.245L1.5 8.938v2.124l1.128.142c.682.085 1.22.61 1.484 1.244.264.635.247 1.37-.175 1.913l-.699.898 1.503 1.503.898-.699c.543-.422 1.278-.44 1.912-.175.635.264 1.16.801 1.245 1.484l.142 1.128h2.124l.142-1.128c.085-.683.61-1.22 1.244-1.484.635-.264 1.37-.247 1.913.175l.898.699 1.503-1.503-.699-.898c-.422-.543-.44-1.278-.175-1.913.264-.634.801-1.16 1.484-1.245l1.128-.14V8.937l-1.128-.14c-.683-.086-1.22-.611-1.484-1.246-.264-.634-.247-1.37.175-1.912l.699-.898-1.503-1.503-.898.699c-.543.422-1.278.44-1.913.175-.634-.264-1.16-.802-1.244-1.484L11.062 1.5ZM10 12.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Z\"\n        clipRule=\"evenodd\"\n      />\n      <path\n        fill=\"none\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M8.938 1.5h2.124l.142 1.128c.085.682.61 1.22 1.244 1.484v0c.635.264 1.37.247 1.913-.175l.898-.699 1.503 1.503-.699.898c-.422.543-.44 1.278-.175 1.912v0c.264.635.801 1.16 1.484 1.245l1.128.142v2.124l-1.128.142c-.683.085-1.22.61-1.484 1.244v0c-.264.635-.247 1.37.175 1.913l.699.898-1.503 1.503-.898-.699c-.543-.422-1.278-.44-1.913-.175v0c-.634.264-1.16.801-1.245 1.484l-.14 1.128H8.937l-.14-1.128c-.086-.683-.611-1.22-1.246-1.484v0c-.634-.264-1.37-.247-1.912.175l-.898.699-1.503-1.503.699-.898c.422-.543.44-1.278.175-1.913v0c-.264-.634-.802-1.16-1.484-1.245l-1.128-.14V8.937l1.128-.14c.682-.086 1.22-.61 1.484-1.246v0c.264-.634.247-1.37-.175-1.912l-.699-.898 1.503-1.503.898.699c.543.422 1.278.44 1.912.175v0c.635-.264 1.16-.802 1.245-1.484L8.938 1.5Z\"\n      />\n      <circle cx=\"10\" cy=\"10\" r=\"2.5\" fill=\"none\" />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/icons/CopyIcon.jsx",
    "content": "export function CopyIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" {...props}>\n      <path\n        fill=\"none\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M14.5 5.5v-1a2 2 0 0 0-2-2h-8a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h1\"\n      />\n      <path\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M5.5 7.5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-8a2 2 0 0 1-2-2v-8Z\"\n      />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/icons/DocumentIcon.jsx",
    "content": "export function DocumentIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" {...props}>\n      <path\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M3.5 4.5v11a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2v-8h-5v-5h-6a2 2 0 0 0-2 2Z\"\n      />\n      <path\n        fill=\"none\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"m11.5 2.5 5 5\"\n      />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/icons/EnvelopeIcon.jsx",
    "content": "export function EnvelopeIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" {...props}>\n      <path\n        fill=\"none\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M2.5 5.5a2 2 0 0 1 2-2h11a2 2 0 0 1 2 2v8a3 3 0 0 1-3 3h-9a3 3 0 0 1-3-3v-8Z\"\n      />\n      <path\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M10 10 4.526 5.256c-.7-.607-.271-1.756.655-1.756h9.638c.926 0 1.355 1.15.655 1.756L10 10Z\"\n      />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/icons/FaceSmileIcon.jsx",
    "content": "export function FaceSmileIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" {...props}>\n      <path\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M10 1.5a8.5 8.5 0 1 1 0 17 8.5 8.5 0 0 1 0-17Z\"\n      />\n      <path\n        fill=\"none\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M7.5 6.5v2M12.5 6.5v2M5.5 11.5s1 3 4.5 3 4.5-3 4.5-3\"\n      />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/icons/FolderIcon.jsx",
    "content": "export function FolderIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" {...props}>\n      <path\n        fill=\"none\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M17.5 15.5v-8a2 2 0 0 0-2-2h-2.93a2 2 0 0 1-1.664-.89l-.812-1.22A2 2 0 0 0 8.43 2.5H4.5a2 2 0 0 0-2 2v11a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2Z\"\n      />\n      <path\n        strokeWidth=\"0\"\n        d=\"M8.43 2.5H4.5a2 2 0 0 0-2 2v1h9l-1.406-2.11A2 2 0 0 0 8.43 2.5Z\"\n      />\n      <path\n        fill=\"none\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"m11.5 5.5-1.406-2.11A2 2 0 0 0 8.43 2.5H4.5a2 2 0 0 0-2 2v1h9Zm0 0h2\"\n      />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/icons/LinkIcon.jsx",
    "content": "export function LinkIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" {...props}>\n      <path\n        fill=\"none\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"m5.056 11.5-1.221-1.222a4.556 4.556 0 0 1 6.443-6.443L11.5 5.056M7.5 7.5l5 5m2.444-4 1.222 1.222a4.556 4.556 0 0 1-6.444 6.444L8.5 14.944\"\n      />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/icons/ListIcon.jsx",
    "content": "export function ListIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" {...props}>\n      <path\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M2.5 4.5a2 2 0 0 1 2-2h11a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2h-11a2 2 0 0 1-2-2v-11Z\"\n      />\n      <path\n        fill=\"none\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M6.5 6.5h7M6.5 13.5h7M6.5 10h7\"\n      />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/icons/MagnifyingGlassIcon.jsx",
    "content": "export function MagnifyingGlassIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" {...props}>\n      <path strokeWidth=\"0\" d=\"M2.5 8.5a6 6 0 1 1 12 0 6 6 0 0 1-12 0Z\" />\n      <path\n        fill=\"none\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"m13 13 4.5 4.5m-9-3a6 6 0 1 1 0-12 6 6 0 0 1 0 12Z\"\n      />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/icons/MapPinIcon.jsx",
    "content": "export function MapPinIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" {...props}>\n      <path\n        strokeWidth=\"0\"\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M10 2.5A5.5 5.5 0 0 0 4.5 8c0 3.038 5.5 9.5 5.5 9.5s5.5-6.462 5.5-9.5A5.5 5.5 0 0 0 10 2.5Zm0 7a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z\"\n      />\n      <path\n        fill=\"none\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M4.5 8a5.5 5.5 0 1 1 11 0c0 3.038-5.5 9.5-5.5 9.5S4.5 11.038 4.5 8Z\"\n      />\n      <circle cx=\"10\" cy=\"8\" r=\"1.5\" fill=\"none\" />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/icons/PackageIcon.jsx",
    "content": "export function PackageIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" {...props}>\n      <path\n        strokeWidth=\"0\"\n        d=\"m10 9.5-7.5-4v9l7.5 4v-9ZM10 9.5l7.5-4v9l-7.5 4v-9Z\"\n      />\n      <path\n        fill=\"none\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"m2.5 5.5 7.5 4m-7.5-4v9l7.5 4m-7.5-13 7.5-4 7.5 4m-7.5 4v9m0-9 7.5-4m-7.5 13 7.5-4v-9m-11 6 .028-3.852L13.5 3.5\"\n      />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/icons/PaperAirplaneIcon.jsx",
    "content": "export function PaperAirplaneIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" {...props}>\n      <path\n        fill=\"none\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M17 3L1 9L8 12M17 3L11 19L8 12M17 3L8 12\"\n      />\n      <path\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M11 19L8 12L17 3L11 19Z\"\n      />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/icons/PaperClipIcon.jsx",
    "content": "export function PaperClipIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" {...props}>\n      <path\n        fill=\"none\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"m15.56 7.375-3.678-3.447c-2.032-1.904-5.326-1.904-7.358 0s-2.032 4.99 0 6.895l6.017 5.639c1.477 1.384 3.873 1.384 5.35 0 1.478-1.385 1.478-3.63 0-5.015L10.21 6.122a1.983 1.983 0 0 0-2.676 0 1.695 1.695 0 0 0 0 2.507l4.013 3.76\"\n      />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/icons/ShapesIcon.jsx",
    "content": "export function ShapesIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" {...props}>\n      <path\n        fill=\"none\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M2.5 7.5v-4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-4a1 1 0 0 1-1-1ZM11.5 16.5v-4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-4a1 1 0 0 1-1-1Z\"\n      />\n      <path\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"m2.5 17.5 3-6 3 6h-6ZM14.5 2.5a3 3 0 1 1 0 6 3 3 0 0 1 0-6Z\"\n      />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/icons/ShirtIcon.jsx",
    "content": "export function ShirtIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" {...props}>\n      <path\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M12.5 1.5s0 2-2.5 2-2.5-2-2.5-2h-2L2.207 4.793a1 1 0 0 0 0 1.414L4.5 8.5v10h11v-10l2.293-2.293a1 1 0 0 0 0-1.414L14.5 1.5h-2Z\"\n      />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/icons/SquaresPlusIcon.jsx",
    "content": "export function SquaresPlusIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" {...props}>\n      <path\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M8.5 4.5v2a2 2 0 0 1-2 2h-2a2 2 0 0 1-2-2v-2a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2ZM8.5 13.5v2a2 2 0 0 1-2 2h-2a2 2 0 0 1-2-2v-2a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2ZM17.5 4.5v2a2 2 0 0 1-2 2h-2a2 2 0 0 1-2-2v-2a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2Z\"\n      />\n      <path\n        fill=\"none\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M14.5 11.5v6M17.5 14.5h-6\"\n      />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/icons/TagIcon.jsx",
    "content": "export function TagIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" {...props}>\n      <path\n        strokeWidth=\"0\"\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M3 8.69499V3H8.69499C9.18447 3 9.65389 3.19444 10 3.54055L16.4594 10C17.1802 10.7207 17.1802 11.8893 16.4594 12.61L12.61 16.4594C11.8893 17.1802 10.7207 17.1802 10 16.4594L3.54055 10C3.19444 9.65389 3 9.18447 3 8.69499ZM7 8.5C7.82843 8.5 8.5 7.82843 8.5 7C8.5 6.17157 7.82843 5.5 7 5.5C6.17157 5.5 5.5 6.17157 5.5 7C5.5 7.82843 6.17157 8.5 7 8.5Z\"\n      />\n      <path\n        fill=\"none\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M3 3V8.69499C3 9.18447 3.19444 9.65389 3.54055 10L10 16.4594C10.7207 17.1802 11.8893 17.1802 12.61 16.4594L16.4594 12.61C17.1802 11.8893 17.1802 10.7207 16.4594 10L10 3.54055C9.65389 3.19444 9.18447 3 8.69499 3H3Z\"\n      />\n      <circle cx=\"7\" cy=\"7\" r=\"1.5\" fill=\"none\" />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/icons/UserIcon.jsx",
    "content": "export function UserIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" {...props}>\n      <path\n        strokeWidth=\"0\"\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M10 .5a9.5 9.5 0 0 1 5.598 17.177C14.466 15.177 12.383 13.5 10 13.5s-4.466 1.677-5.598 4.177A9.5 9.5 0 0 1 10 .5ZM12.5 8a2.5 2.5 0 1 0-5 0 2.5 2.5 0 0 0 5 0Z\"\n      />\n      <path\n        fill=\"none\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M10 .5a9.5 9.5 0 0 1 5.598 17.177A9.458 9.458 0 0 1 10 19.5a9.458 9.458 0 0 1-5.598-1.823A9.5 9.5 0 0 1 10 .5Z\"\n      />\n      <path\n        fill=\"none\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M4.402 17.677C5.534 15.177 7.617 13.5 10 13.5s4.466 1.677 5.598 4.177M10 5.5a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5Z\"\n      />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/icons/UsersIcon.jsx",
    "content": "export function UsersIcon(props) {\n  return (\n    <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" {...props}>\n      <path\n        fill=\"none\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M10.046 16H1.955a.458.458 0 0 1-.455-.459C1.5 13.056 3.515 11 6 11h.5\"\n      />\n      <path\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M7.5 15.454C7.5 12.442 9.988 10 13 10s5.5 2.442 5.5 5.454a.545.545 0 0 1-.546.546H8.045a.545.545 0 0 1-.545-.546Z\"\n      />\n      <path\n        fill=\"none\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M6.5 4a2 2 0 1 1 0 4 2 2 0 0 1 0-4Z\"\n      />\n      <path\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M13 2a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5Z\"\n      />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/documentation/mdx.jsx",
    "content": "import Link from 'next/link'\nimport clsx from 'clsx'\n\nimport { Heading } from '@/components/documentation/Heading'\n\nexport const a = Link\nexport { Button } from '@/components/documentation/Button'\nexport {\n  CodeGroup,\n  Code as code,\n  Pre as pre,\n} from '@/components/documentation/Code'\n\nexport const h2 = function H2(props) {\n  return <Heading level={2} {...props} />\n}\n\nexport const h3 = function H3(props) {\n  return (\n    <h3 className=\"mb-4 mt-10 text-xl font-semibold text-white\" {...props} />\n  )\n}\n\nfunction InfoIcon(props) {\n  return (\n    <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\" {...props}>\n      <circle cx=\"8\" cy=\"8\" r=\"8\" strokeWidth=\"0\" />\n      <path\n        fill=\"none\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        strokeWidth=\"1.5\"\n        d=\"M6.75 7.75h1.5v3.5\"\n      />\n      <circle cx=\"8\" cy=\"4\" r=\".5\" fill=\"none\" />\n    </svg>\n  )\n}\n\nexport function Note({ children }) {\n  return (\n    <div className=\"my-6 flex gap-2.5 rounded-2xl border  border-orange-500/30 bg-orange-500/5 p-4 leading-6 text-orange-200 [--tw-prose-links-hover:theme(colors.orange.300)] [--tw-prose-links:theme(colors.white)]\">\n      <InfoIcon className=\"mt-1 h-4 w-4 flex-none fill-orange-200/20 stroke-orange-200\" />\n      <div className=\"[&>:first-child]:mt-0 [&>:last-child]:mb-0\">\n        {children}\n      </div>\n    </div>\n  )\n}\n\nexport function Row({ children }) {\n  return (\n    <div className=\"grid grid-cols-1 items-start gap-x-16 gap-y-10 xl:max-w-none xl:grid-cols-2\">\n      {children}\n    </div>\n  )\n}\n\nexport function Col({ children, sticky = false }) {\n  return (\n    <div\n      className={clsx(\n        '[&>:first-child]:mt-0 [&>:last-child]:mb-0',\n        sticky && 'xl:sticky xl:top-24'\n      )}\n    >\n      {children}\n    </div>\n  )\n}\n\nexport function Properties({ children }) {\n  return (\n    <div className=\"my-6\">\n      <ul\n        role=\"list\"\n        className=\"m-0 max-w-[calc(theme(maxWidth.lg)-theme(spacing.8))] list-none divide-y divide-white/5 p-0\"\n      >\n        {children}\n      </ul>\n    </div>\n  )\n}\n\nexport function Property({ name, type, children }) {\n  return (\n    <li className=\"m-0 px-0 py-4 first:pt-0 last:pb-0\">\n      <dl className=\"m-0 flex flex-wrap items-center gap-x-3 gap-y-2\">\n        <dt className=\"sr-only\">Name</dt>\n        <dd>\n          <code>{name}</code>\n        </dd>\n        <dt className=\"sr-only\">Type</dt>\n        <dd className=\"font-mono text-xs text-zinc-500\">{type}</dd>\n        <dt className=\"sr-only\">Description</dt>\n        <dd className=\"w-full flex-none [&>:first-child]:mt-0 [&>:last-child]:mb-0\">\n          {children}\n        </dd>\n      </dl>\n    </li>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/releases/Button.jsx",
    "content": "import Link from 'next/link'\nimport clsx from 'clsx'\n\nfunction ButtonInner({ arrow = false, children }) {\n  return (\n    <>\n      <span className=\"group-hover:opacity-15 absolute inset-0 rounded-md bg-gradient-to-b from-white/80 to-white opacity-10 transition-opacity\" />\n      <span className=\"opacity-7.5 absolute inset-0 rounded-md shadow-[inset_0_1px_1px_white] transition-opacity group-hover:opacity-10\" />\n      {children} {arrow ? <span aria-hidden=\"true\">&rarr;</span> : null}\n    </>\n  )\n}\n\nexport function Button({ href, className, arrow, children, ...props }) {\n  className = clsx(\n    className,\n    'group relative isolate flex-none rounded-md py-1.5 text-[0.8125rem]/6 font-semibold text-white',\n    arrow ? 'pl-2.5 pr-[calc(9/16*1rem)]' : 'px-2.5'\n  )\n\n  return href ? (\n    <Link href={href} className={className} {...props}>\n      <ButtonInner arrow={arrow}>{children}</ButtonInner>\n    </Link>\n  ) : (\n    <button className={className} {...props}>\n      <ButtonInner arrow={arrow}>{children}</ButtonInner>\n    </button>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/releases/FeedProvider.jsx",
    "content": "import { createContext, useContext } from 'react'\n\nlet FeedContext = createContext({ isFeed: false })\n\nexport function FeedProvider({ children }) {\n  return (\n    <FeedContext.Provider value={{ isFeed: true }}>\n      {children}\n    </FeedContext.Provider>\n  )\n}\n\nexport function useFeed() {\n  return useContext(FeedContext)\n}\n"
  },
  {
    "path": "docs_src/src/components/releases/FormattedDate.jsx",
    "content": "const dateFormatter = new Intl.DateTimeFormat('en-US', {\n  year: 'numeric',\n  month: 'short',\n  day: 'numeric',\n  timeZone: 'UTC',\n})\n\nexport function FormattedDate({ date, ...props }) {\n  date = typeof date === 'string' ? new Date(date) : date\n\n  return (\n    <time dateTime={date.toISOString()} {...props}>\n      {dateFormatter.format(date)}\n    </time>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/releases/IconLink.jsx",
    "content": "import Link from 'next/link'\nimport clsx from 'clsx'\n\nexport function IconLink({\n  children,\n  className,\n  compact = false,\n  large = false,\n  icon: Icon,\n  ...props\n}) {\n  return (\n    <Link\n      {...props}\n      className={clsx(\n        className,\n        'group relative isolate flex items-center rounded-lg px-2 py-0.5 text-[0.8125rem]/6 font-medium text-white/30 transition-colors hover:text-sky-300',\n        compact ? 'gap-x-2' : 'gap-x-3'\n      )}\n    >\n      <span className=\"absolute inset-0 -z-10 scale-75 rounded-lg bg-white/5 opacity-0 transition group-hover:scale-100 group-hover:opacity-100\" />\n      <Icon className={clsx('flex-none', large ? 'h-6 w-6' : 'h-4 w-4')} />\n      <span className=\"self-baseline text-white\">{children}</span>\n    </Link>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/releases/Intro.jsx",
    "content": "import { IconLink } from '@/components/releases/IconLink'\nimport { SignUpForm } from '@/components/releases/SignUpForm'\nimport {\n  InstagramIcon,\n  LinkedInIcon,\n  TwitterIcon,\n} from '@/components/SocialIcons'\n\nfunction BookIcon(props) {\n  return (\n    <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\" fill=\"currentColor\" {...props}>\n      <path d=\"M7 3.41a1 1 0 0 0-.668-.943L2.275 1.039a.987.987 0 0 0-.877.166c-.25.192-.398.493-.398.812V12.2c0 .454.296.853.725.977l3.948 1.365A1 1 0 0 0 7 13.596V3.41ZM9 13.596a1 1 0 0 0 1.327.946l3.948-1.365c.429-.124.725-.523.725-.977V2.017c0-.32-.147-.62-.398-.812a.987.987 0 0 0-.877-.166L9.668 2.467A1 1 0 0 0 9 3.41v10.186Z\" />\n    </svg>\n  )\n}\n\nfunction GitHubIcon(props) {\n  return (\n    <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\" fill=\"currentColor\" {...props}>\n      <path d=\"M8 .198a8 8 0 0 0-8 8 7.999 7.999 0 0 0 5.47 7.59c.4.076.547-.172.547-.384 0-.19-.007-.694-.01-1.36-2.226.482-2.695-1.074-2.695-1.074-.364-.923-.89-1.17-.89-1.17-.725-.496.056-.486.056-.486.803.056 1.225.824 1.225.824.714 1.224 1.873.87 2.33.666.072-.518.278-.87.507-1.07-1.777-.2-3.644-.888-3.644-3.954 0-.873.31-1.586.823-2.146-.09-.202-.36-1.016.07-2.118 0 0 .67-.214 2.2.82a7.67 7.67 0 0 1 2-.27 7.67 7.67 0 0 1 2 .27c1.52-1.034 2.19-.82 2.19-.82.43 1.102.16 1.916.08 2.118.51.56.82 1.273.82 2.146 0 3.074-1.87 3.75-3.65 3.947.28.24.54.73.54 1.48 0 1.07-.01 1.93-.01 2.19 0 .21.14.46.55.38A7.972 7.972 0 0 0 16 8.199a8 8 0 0 0-8-8Z\" />\n    </svg>\n  )\n}\n\nfunction FeedIcon(props) {\n  return (\n    <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\" fill=\"currentColor\" {...props}>\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M2.5 3a.5.5 0 0 1 .5-.5h.5c5.523 0 10 4.477 10 10v.5a.5.5 0 0 1-.5.5h-.5a.5.5 0 0 1-.5-.5v-.5A8.5 8.5 0 0 0 3.5 4H3a.5.5 0 0 1-.5-.5V3Zm0 4.5A.5.5 0 0 1 3 7h.5A5.5 5.5 0 0 1 9 12.5v.5a.5.5 0 0 1-.5.5H8a.5.5 0 0 1-.5-.5v-.5a4 4 0 0 0-4-4H3a.5.5 0 0 1-.5-.5v-.5Zm0 5a1 1 0 1 1 2 0 1 1 0 0 1-2 0Z\"\n      />\n    </svg>\n  )\n}\n\nexport function Intro() {\n  return (\n    <>\n      <h1 className=\"mt-14 font-display text-4xl/tight font-light text-white\">\n        All the latest Robyn releases,\n        <span className=\"text-yellow-300\"> right here.</span>\n      </h1>\n      <div className=\"mt-8 flex flex-wrap justify-start gap-x-1 gap-y-3 sm:gap-x-2\">\n        <IconLink\n          href=\"https://twitter.com/robyn_oss\"\n          icon={TwitterIcon}\n          className=\"flex-none\"\n        >\n          Twitter\n        </IconLink>\n        <IconLink\n          href=\"https://github.com/sparckles/robyn/releases\"\n          icon={GitHubIcon}\n          className=\"flex-none\"\n        >\n          Release Page\n        </IconLink>\n      </div>\n    </>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/releases/Layout.jsx",
    "content": "import { useId } from 'react'\n\nimport { Intro } from '@/components/releases/Intro'\n\nfunction Timeline() {\n  let id = useId()\n\n  return (\n    <div className=\"pointer-events-none absolute inset-0 z-50 overflow-hidden \">\n      <svg\n        className=\"absolute left-[max(0px,calc(50%-18.125rem))] top-0 h-full w-1.5 \"\n        aria-hidden=\"true\"\n      >\n        <defs>\n          <pattern id={id} width=\"6\" height=\"8\" patternUnits=\"userSpaceOnUse\">\n            <path\n              d=\"M0 0H6M0 8H6\"\n              className=\"stroke-white/10 xl:stroke-white/10\"\n              fill=\"none\"\n            />\n          </pattern>\n        </defs>\n        <rect width=\"100%\" height=\"100%\" fill={`url(#${id})`} />\n      </svg>\n    </div>\n  )\n}\n\nfunction FixedSidebar({ main }) {\n  return (\n    <div className=\"relative mt-10 flex-none overflow-hidden px-6\">\n      <div className=\"relative flex w-full \">\n        <div className=\"mx-auto max-w-lg\">\n          <div className=\"pb-16 pt-20 sm:pb-20 sm:pt-32 \">\n            <div className=\"relative\">{main}</div>\n          </div>\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport function Layout({ children }) {\n  return (\n    <>\n      <FixedSidebar main={<Intro />} />\n      <div\n        className=\"absolute inset-x-0 top-4 -z-10 flex transform-gpu justify-center overflow-hidden blur-3xl\"\n        aria-hidden=\"true\"\n      >\n        <div\n          className=\"aspect-[1108/632] w-[69.25rem] flex-none bg-gradient-to-r from-orange-800 to-yellow-500 opacity-40\"\n          style={{\n            clipPath:\n              'polygon(73.6% 51.7%, 91.7% 11.8%, 100% 46.4%, 97.4% 82.2%, 92.5% 84.9%, 75.7% 64%, 55.3% 47.5%, 46.5% 49.4%, 45% 62.9%, 50.3% 87.2%, 21.3% 64.1%, 0.1% 100%, 5.4% 51.1%, 21.4% 63.9%, 58.9% 0.2%, 73.6% 51.7%)',\n          }}\n        />\n      </div>\n\n      <div className=\"relative flex-auto justify-center\">\n        <Timeline />\n        <main className=\"space-y-20 py-20 sm:space-y-32 sm:py-32\">\n          {children}\n        </main>\n      </div>\n    </>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/releases/SignUpForm.jsx",
    "content": "import { useId } from 'react'\n\nimport { Button } from '@/components/Button'\n\nexport function SignUpForm() {\n  let id = useId()\n\n  return (\n    <form className=\"relative isolate mt-8 flex items-center pr-1\">\n      <label htmlFor={id} className=\"sr-only\">\n        Email address\n      </label>\n      <input\n        required\n        type=\"email\"\n        autoComplete=\"email\"\n        name=\"email\"\n        id={id}\n        placeholder=\"Email address\"\n        className=\"peer w-0 flex-auto bg-transparent px-4 py-2.5 text-base text-white placeholder:text-gray-500 focus:outline-none sm:text-[0.8125rem]/6\"\n      />\n      <Button type=\"submit\" arrow>\n        Get updates\n      </Button>\n      <div className=\"peer-focus:ring-sky-300/15 absolute inset-0 -z-10 rounded-lg transition peer-focus:ring-4\" />\n      <div className=\"bg-white/2.5 ring-white/15 absolute inset-0 -z-10 rounded-lg ring-1 transition peer-focus:ring-sky-300\" />\n    </form>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/components/releases/mdx.jsx",
    "content": "import { useEffect, useRef, useState } from 'react'\nimport Image from 'next/image'\nimport Link from 'next/link'\nimport clsx from 'clsx'\n\nimport { useFeed } from '@/components/releases/FeedProvider'\nimport { FormattedDate } from '@/components/releases/FormattedDate'\n\nexport const a = Link\n\nexport const wrapper = function Wrapper({ children }) {\n  return children\n}\n\nexport function H2(props) {\n  let { isFeed } = useFeed()\n\n  if (isFeed) {\n    return null\n  }\n\n  return (\n    <h2 className=\"text-2xs/4 typography  font-bold text-white/50\" {...props} />\n  )\n}\n\nexport const p = function Paragraph({ children }) {\n  return <p className=\"text-2xs/4 typography text-white\">{children}</p>\n}\n\nexport const img = function Img(props) {\n  return (\n    <div className=\"relative mt-8 overflow-hidden rounded-xl bg-gray-900 [&+*]:mt-8\">\n      <Image\n        alt=\"\"\n        sizes=\"(min-width: 1280px) 36rem, (min-width: 1024px) 45vw, (min-width: 640px) 32rem, 95vw\"\n        {...props}\n      />\n      <div className=\"pointer-events-none absolute inset-0 rounded-xl ring-1 ring-inset  ring-white/10\" />\n    </div>\n  )\n}\n\nfunction ContentWrapper({ className, children }) {\n  return (\n    <div className=\"mx-auto max-w-7xl px-6 lg:flex lg:px-8\">\n      <div className=\"ml-10 lg:flex lg:w-full lg:justify-center \">\n        <div\n          className={clsx(\n            'mx-auto max-w-lg break-words text-gray-300 lg:mx-0 lg:w-0 lg:max-w-xl lg:flex-auto ',\n            className\n          )}\n        >\n          {children}\n        </div>\n      </div>\n    </div>\n  )\n}\n\nfunction ArticleHeader({ id, date }) {\n  return (\n    <header className=\"relative mb-10 \">\n      <div className=\"pointer-events-none absolute left-[max(-0.5rem,calc(50%-18.625rem))] top-0 z-50 flex h-4 items-center justify-end gap-x-2 \">\n        <div className=\"h-[0.0625rem] w-3.5 bg-gray-400 \" />\n      </div>\n      <ContentWrapper>\n        <div className=\"flex flex-wrap\">\n          <Link href={`#${id}`} className=\"inline-flex\">\n            <FormattedDate\n              date={date}\n              className=\"text-2xs/4 font-medium text-white/50\"\n            />\n          </Link>\n        </div>\n      </ContentWrapper>\n    </header>\n  )\n}\n\nexport function Article({ id, title, date, children }) {\n  let { isFeed } = useFeed()\n  let heightRef = useRef(null)\n  let [heightAdjustment, setHeightAdjustment] = useState(0)\n\n  useEffect(() => {\n    let observer = new window.ResizeObserver(() => {\n      let { height } = heightRef.current.getBoundingClientRect()\n      let nextMultipleOf8 = 8 * Math.ceil(height / 8)\n      setHeightAdjustment(nextMultipleOf8 - height)\n    })\n\n    observer.observe(heightRef.current)\n\n    return () => {\n      observer.disconnect()\n    }\n  }, [])\n\n  if (isFeed) {\n    return (\n      <article class=\"text-white\">\n        <script\n          type=\"text/metadata\"\n          dangerouslySetInnerHTML={{\n            __html: JSON.stringify({ id, title, date }),\n          }}\n        />\n        {children}\n      </article>\n    )\n  }\n\n  return (\n    <article\n      id={id}\n      className=\"scroll-mt-16\"\n      style={{ paddingBottom: `${heightAdjustment}px` }}\n    >\n      <div ref={heightRef}>\n        <ArticleHeader id={id} date={date} />\n        <ContentWrapper className=\"typography\">{children}</ContentWrapper>\n      </div>\n    </article>\n  )\n}\n\nexport const article = Article\nexport const h2 = H2\n\nexport const ul = function UnorderedList({ children }) {\n  return <ul className=\"list-inside list-disc\">{children}</ul>\n}\n\nexport const code = function Code({ highlightedCode, ...props }) {\n  if (highlightedCode) {\n    return (\n      <code {...props} dangerouslySetInnerHTML={{ __html: highlightedCode }} />\n    )\n  }\n\n  return <code {...props} />\n}\n\nexport const pre = function Pre({ children }) {\n  return <pre className=\"overflow-x-scroll\">{children}</pre>\n}\n"
  },
  {
    "path": "docs_src/src/lib/formatDate.js",
    "content": "export function formatDate(dateString) {\n  return new Date(`${dateString}T00:00:00Z`).toLocaleDateString('en-US', {\n    day: 'numeric',\n    month: 'long',\n    year: 'numeric',\n    timeZone: 'UTC',\n  })\n}\n"
  },
  {
    "path": "docs_src/src/lib/getAllArticles.js",
    "content": "import glob from 'fast-glob'\nimport * as path from 'path'\n\nasync function importArticle(articleFilename) {\n  let { meta, default: component } = await import(\n    `../pages/articles/${articleFilename}`\n  )\n  return {\n    slug: articleFilename.replace(/(\\/index)?\\.mdx$/, ''),\n    ...meta,\n    component,\n  }\n}\n\nexport async function getAllArticles() {\n  let articleFilenames = await glob(['*.mdx', '*/index.mdx'], {\n    cwd: path.join(process.cwd(), 'src/pages/articles'),\n  })\n\n  let articles = await Promise.all(articleFilenames.map(importArticle))\n\n  return articles.sort((a, z) => new Date(z.date) - new Date(a.date))\n}\n"
  },
  {
    "path": "docs_src/src/lib/remToPx.js",
    "content": "export function remToPx(remValue) {\n  let rootFontSize =\n    typeof window === 'undefined'\n      ? 16\n      : parseFloat(window.getComputedStyle(document.documentElement).fontSize)\n\n  return parseFloat(remValue) * rootFontSize\n}\n"
  },
  {
    "path": "docs_src/src/pages/_app.jsx",
    "content": "import { useEffect, useRef } from 'react'\n\nimport { Footer, GithubButton } from '@/components/Footer'\nimport { Header } from '@/components/Header'\n\nimport '@/styles/tailwind.css'\nimport '@/styles/documentation.css'\nimport 'focus-visible'\nimport { Router, useRouter } from 'next/router'\n\nimport { MDXProvider } from '@mdx-js/react'\n\nimport { Layout } from '@/components/documentation/Layout'\nimport { Layout as ReleaseLayout } from '@/components/releases/Layout'\nimport * as mdxComponents from '@/components/documentation/mdx'\nimport { useMobileNavigationStore } from '@/components/documentation/MobileNavigation'\n\nimport { Analytics } from '@vercel/analytics/react'\n\nfunction usePrevious(value) {\n  let ref = useRef()\n\n  useEffect(() => {\n    ref.current = value\n  }, [value])\n\n  return ref.current\n}\n\nfunction onRouteChange() {\n  useMobileNavigationStore.getState().close()\n}\n\nRouter.events.on('routeChangeStart', onRouteChange)\nRouter.events.on('hashChangeStart', onRouteChange)\nexport default function App({ Component, pageProps, router }) {\n  let previousPathname = usePrevious(router.pathname)\n  let router_ = useRouter()\n\n  if (router_.pathname.includes('documentation')) {\n    return (\n      <>\n        <Header />\n        <MDXProvider components={mdxComponents}>\n          <Layout {...pageProps}>\n            <Component {...pageProps} />\n          </Layout>\n        </MDXProvider>\n        <Analytics />\n      </>\n    )\n  } else if (router_.pathname.includes('release')) {\n    return (\n      <>\n        <Header />\n        <ReleaseLayout>\n          <Component {...pageProps} />\n        </ReleaseLayout>\n        <Footer />\n        <Analytics />\n      </>\n    )\n  }\n\n  return (\n    <>\n      <div className=\"fixed inset-0 flex justify-center sm:px-8\">\n        <div className=\"flex w-full\">\n          <div className=\"w-full bg-black\" />\n        </div>\n      </div>\n      <div className=\"relative\">\n        <Header />\n        <main>\n          <Component previousPathname={previousPathname} {...pageProps} />\n        </main>\n        <Footer />\n      </div>\n      <Analytics />\n    </>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/pages/_document.jsx",
    "content": "import { Head, Html, Main, NextScript } from 'next/document'\nimport { Analytics } from '@vercel/analytics/react';\n\nconst modeScript = `\n  let darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)')\n\n  updateMode()\n  darkModeMediaQuery.addEventListener('change', updateModeWithoutTransitions)\n  window.addEventListener('storage', updateModeWithoutTransitions)\n\n  function updateMode() {\n    let isSystemDarkMode = darkModeMediaQuery.matches\n    let isDarkMode = window.localStorage.isDarkMode === 'true' || (!('isDarkMode' in window.localStorage) && isSystemDarkMode)\n\n    if (isDarkMode) {\n      document.documentElement.classList.add('dark')\n    } else {\n      document.documentElement.classList.remove('dark')\n    }\n\n    if (isDarkMode === isSystemDarkMode) {\n      delete window.localStorage.isDarkMode\n    }\n  }\n\n  function disableTransitionsTemporarily() {\n    document.documentElement.classList.add('[&_*]:!transition-none')\n    window.setTimeout(() => {\n      document.documentElement.classList.remove('[&_*]:!transition-none')\n    }, 0)\n  }\n\n  function updateModeWithoutTransitions() {\n    disableTransitionsTemporarily()\n    updateMode()\n  }\n`\n\nexport default function Document() {\n  return (\n    <Html className=\"h-full antialiased\" lang=\"en\">\n      <Head>\n        <script dangerouslySetInnerHTML={{ __html: modeScript }} />\n        <link\n          rel=\"alternate\"\n          type=\"application/rss+xml\"\n          href={`${process.env.NEXT_PUBLIC_SITE_URL}/rss/feed.xml`}\n        />\n        <link\n          rel=\"alternate\"\n          type=\"application/feed+json\"\n          href={`${process.env.NEXT_PUBLIC_SITE_URL}/rss/feed.json`}\n        />\n        <link\n          rel=\"icon\"\n          type=\"image/png\"\n          href=\"https://user-images.githubusercontent.com/29942790/140995889-5d91dcff-3aa7-4cfb-8a90-2cddf1337dca.png\"\n        />\n      </Head>\n      <body className=\"flex h-full flex-col bg-black\">\n        <Main />\n        <NextScript />\n        <Analytics />\n      </body>\n    </Html>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/pages/community.jsx",
    "content": "import Head from 'next/head'\nimport Image from 'next/image'\nimport sparcklesLogo from '@/images/sparckles-logo.png'\nimport { useEffect, useState } from 'react'\n\nfunction Contributors() {\n  const [contributors, setContributors] = useState([])\n  const [error, setError] = useState(null)\n\n  useEffect(() => {\n    const fetchContributors = async () => {\n      try {\n        const response = await fetch(\n          `https://api.github.com/repos/sparckles/robyn/contributors`\n        )\n        if (!response.ok) {\n          throw new Error(`HTTP error! status: ${response.status}`)\n        }\n        const data = await response.json()\n        setContributors(data)\n      } catch (error) {\n        setError(error.toString())\n      }\n    }\n\n    fetchContributors()\n  }, [])\n\n  if (error) {\n    return <div className=\"text-red-500\">{error}</div>\n  }\n\n  return (\n    <div className=\"flex flex-wrap bg-transparent\">\n      {contributors.map((contributor) => (\n        <div key={contributor.id} className=\"m-2 flex flex-col items-center\">\n          <a\n            href={contributor.html_url}\n            target=\"_blank\"\n            rel=\"noreferrer\"\n            className=\"mt-2 text-white hover:text-white hover:underline\"\n          >\n            <img\n              src={contributor.avatar_url}\n              alt={contributor.login}\n              className=\"h-12 w-12 rounded-full\"\n            />\n          </a>\n        </div>\n      ))}\n    </div>\n  )\n}\nexport default function Community() {\n  return (\n    <>\n      <Head>\n        <meta\n          name=\"description\"\n          content=\"The Robyn Community is a group of people who are passionate about the Robyn project.\"\n        />\n      </Head>\n      <main className=\"relative isolate\">\n        {/* Background */}\n        <div\n          className=\"absolute inset-x-0 top-4 -z-10 flex transform-gpu justify-center overflow-hidden blur-3xl\"\n          aria-hidden=\"true\"\n        >\n          <div\n            className=\"aspect-[1108/632] w-[69.25rem] flex-none bg-gradient-to-r from-orange-800 to-yellow-500 opacity-40\"\n            style={{\n              clipPath:\n                'polygon(73.6% 51.7%, 91.7% 11.8%, 100% 46.4%, 97.4% 82.2%, 92.5% 84.9%, 75.7% 64%, 55.3% 47.5%, 46.5% 49.4%, 45% 62.9%, 50.3% 87.2%, 21.3% 64.1%, 0.1% 100%, 5.4% 51.1%, 21.4% 63.9%, 58.9% 0.2%, 73.6% 51.7%)',\n            }}\n          />\n        </div>\n\n        {/* Header section */}\n        <div className=\"px-6 pt-14 lg:px-8\">\n          <div className=\"mx-auto max-w-2xl pt-24 text-center sm:pt-40\">\n            <h2 className=\"text-4xl font-bold tracking-tight text-white sm:text-6xl\">\n              Robyn Community\n            </h2>\n            <p className=\"mt-6 text-lg leading-8 text-gray-300\">\n              Robyn is a community project and is housed under the sparckles\n              organisation.\n            </p>\n          </div>\n        </div>\n\n        {/* Content section */}\n\n        <div className=\"relative isolate -z-10 mt-4 sm:mt-12\">\n          <div className=\"mx-auto max-w-7xl sm:px-6 lg:px-8\">\n            <div className=\"mx-auto flex max-w-2xl flex-col gap-16  px-6 py-16 sm:rounded-3xl sm:p-8 lg:mx-0 lg:max-w-none lg:flex-row lg:items-center lg:py-20 xl:gap-x-20 xl:px-20\">\n              <div className=\"w-full flex-auto\">\n                <h2 className=\"text-3xl font-bold tracking-tight text-white sm:text-4xl\">\n                  Our Amazing Contributors\n                </h2>\n                <Contributors />\n              </div>\n            </div>\n          </div>\n        </div>\n\n        {/* Values section */}\n        <div className=\"mx-auto mt-32 max-w-7xl px-6 sm:mt-40 lg:px-8\">\n          <div className=\"mx-auto max-w-2xl lg:mx-0\">\n            <h2 className=\"text-3xl font-bold tracking-tight text-white sm:text-4xl\">\n              Join us on our journey\n            </h2>\n            <p className=\"mt-6 text-lg leading-8 text-gray-300\">\n              Join us on our journey to spark new ideas, ignite the spirit of\n              open-source development, and break down the walls that limit\n              innovation and progress. Together, we can create a brighter future\n              for the Python ecosystem and the global software community.\n            </p>\n\n            <div className=\"mt-10 flex\">\n              <a\n                href=\"https://discord.com/invite/rkERZ5eNU8\"\n                target=\"_blank\"\n                rel=\"noreferrer\"\n                className=\"text-sm font-semibold leading-6 text-indigo-400\"\n              >\n                Join our community! <span aria-hidden=\"true\">&rarr;</span>\n              </a>\n            </div>\n          </div>\n        </div>\n\n        {/* Team section */}\n        <div className=\"mx-auto mt-32 max-w-7xl px-6 sm:mt-40 lg:px-8\">\n          <div className=\"mx-auto max-w-2xl lg:mx-0\">\n            <h2 className=\"text-3xl font-bold tracking-tight text-white sm:text-4xl\">\n              Our team\n            </h2>\n            <p className=\"mt-6 text-lg leading-8 text-gray-300\">\n              Robyn is housed under an open source organisation called\n              Sparckles. We are a group of developers passionate about the open\n              source community. Come join us and help us empower the Python web\n              ecosystem.\n            </p>\n          </div>\n        </div>\n\n        {/* CTA section */}\n        <div className=\"relative isolate -z-10 mt-32 sm:mt-40\">\n          <div className=\"mx-auto max-w-7xl sm:px-6 lg:px-8\">\n            <div className=\"mx-10 flex max-w-2xl flex-col gap-16 bg-white/5 px-6 py-16 ring-1 ring-white/10 sm:rounded-3xl sm:p-8 md:mx-auto lg:mx-0 lg:max-w-none lg:flex-row lg:items-center lg:py-20 xl:gap-x-20 xl:px-20\">\n              <Image\n                className=\"h-96 w-full flex-none rounded-2xl object-cover shadow-xl  lg:aspect-square lg:h-auto lg:max-w-sm\"\n                src={sparcklesLogo}\n                alt=\"sparckles logo\"\n              />\n              <div className=\"w-full flex-auto\">\n                <h2 className=\"text-3xl font-bold tracking-tight text-white sm:text-4xl\">\n                  Sparckles\n                </h2>\n                <p className=\"mt-6 text-lg leading-8 text-gray-300\">\n                  Sparckles is an innovative open-source organization dedicated\n                  to enhancing the Python ecosystem by developing cutting-edge\n                  tools, fostering a vibrant community, and providing robust\n                  infrastructure solutions.\n                </p>\n                <div className=\"mt-10 flex\">\n                  <a\n                    href=\"https://github.com/sparckles\"\n                    target=\"_blank\"\n                    rel=\"noreferrer\"\n                    className=\"text-sm font-semibold leading-6 text-indigo-400\"\n                  >\n                    Visit our homepage <span aria-hidden=\"true\">&rarr;</span>\n                  </a>\n                </div>\n              </div>\n            </div>\n          </div>\n          <div\n            className=\"absolute inset-x-0 -top-16 -z-10 flex transform-gpu justify-center overflow-hidden blur-3xl\"\n            aria-hidden=\"true\"\n          >\n            <div\n              className=\"aspect-[1318/752] w-[82.375rem] flex-none bg-gradient-to-r from-orange-800 to-yellow-500 opacity-20\"\n              style={{\n                clipPath:\n                  'polygon(73.6% 51.7%, 91.7% 11.8%, 100% 46.4%, 97.4% 82.2%, 92.5% 84.9%, 75.7% 64%, 55.3% 47.5%, 46.5% 49.4%, 45% 62.9%, 50.3% 87.2%, 21.3% 64.1%, 0.1% 100%, 5.4% 51.1%, 21.4% 63.9%, 58.9% 0.2%, 73.6% 51.7%)',\n              }}\n            />\n          </div>\n        </div>\n\n        {/* sponsors */}\n        <div className=\"py-24 sm:py-32\">\n          <div className=\"mx-auto max-w-7xl px-6 lg:px-8\">\n            <div className=\"mx-auto max-w-2xl lg:max-w-none\">\n              <div className=\"text-center\">\n                <h2 className=\"text-3xl font-bold tracking-tight text-white sm:text-4xl\">\n                  Sponsors\n                </h2>\n                <p className=\"mt-4 text-lg leading-8 text-gray-300\">\n                  Thanks to our sponsors for supporting us!\n                </p>\n              </div>\n              <dl className=\"mx-10 mt-16 grid max-w-screen-xl grid-cols-1 gap-0.5 overflow-hidden rounded-2xl text-center sm:mx-10 sm:grid-cols-2 lg:grid-cols-3\">\n                <div className=\"flex flex-col justify-center justify-items-center bg-white/5 p-8\">\n                  <a\n                    href=\"https://www.digitalocean.com/?refcode=3f2b9fd4968d&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge\"\n                    target=\"_blank\"\n                    rel=\"noopener noreferrer\"\n                  >\n                    <dd className=\" text-3xl font-semibold tracking-tight text-white\">\n                      <img\n                        src=\"https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_vertical_blue.svg\"\n                        alt=\"Digital Ocean\"\n                        className=\"aspect-square  \"\n                      />\n                    </dd>\n                  </a>\n                </div>\n                <div className=\"flex flex-col justify-center   bg-white/5 p-8\">\n                  <dt className=\"text-sm font-semibold leading-6 text-gray-300\"></dt>\n                  <a\n                    href=\"https://github.com/appwrite\"\n                    target=\"_blank\"\n                    rel=\"noopener noreferrer\"\n                  >\n                    <dd className=\" text-3xl font-semibold tracking-tight text-white\">\n                      <img\n                        src=\"https://pbs.twimg.com/profile_images/1569586501335359494/4rq0Hb99_400x400.jpg\"\n                        alt=\"AppWrite\"\n                        className=\"rounded-full\"\n                      />\n                    </dd>\n                  </a>\n                </div>\n                <div className=\"flex flex-col justify-center justify-items-center  bg-white/5 p-8\">\n                  <dt className=\"text-sm font-semibold leading-6 text-gray-300\">\n                    {/* Community Contributors */}\n                  </dt>\n                  <a\n                    href=\"https://github.com/shivaylamba\"\n                    target=\"_blank\"\n                    rel=\"noopener noreferrer\"\n                  >\n                    <dd className=\" text-3xl font-semibold tracking-tight text-white\">\n                      <img\n                        src=\"https://avatars.githubusercontent.com/u/19529592?v=4\"\n                        alt=\"Shivay Lamba\"\n                        className=\"rounded-full \"\n                      />\n                    </dd>\n                  </a>\n                </div>\n              </dl>\n            </div>\n          </div>\n        </div>\n      </main>\n    </>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/api_reference/advanced_features.mdx",
    "content": "export const description =\n  'On this page, we’ll dive into the different conversation endpoints you can use to manage conversations programmatically.'\n\n## Keep a track of client's IP address\n\nNow that the portal was up and ready, Batman realised that the Joker was using the Gotham Police Dashboard too. So, he wanted to keep a track of the IP address of the client who was accessing his application. He used the following code to do so:\n\n\n<Row>\n<Col>\nBatman scaled his application across multiple cores for better performance. He used the following command:\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    from robyn import Robyn, Request\n\n    app = Robyn(__file__)\n\n    @app.get(\"/\")\n    async def h(request: Request):\n        return f\"hello to you, {request.ip_addr}\"\n\n    ```\n\n    </CodeGroup>\n  </Col>\n</Row>\n---\n\n\n## What's next?\n\n\nBatman wondered about how to help users explore the endpoints in his application.\n\nRobyn showed him the OpenAPI Documentation!\n\n[OpenAPI Documentation](/documentation/en/api_reference/openapi)\n\n\n\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/api_reference/advanced_routing.mdx",
    "content": "export const description =\n  'Master Robyn\\'s advanced routing features including parameter injection, route optimization, and complex URL patterns.'\n\n# Advanced Routing and Parameter Injection\n\nRobyn's routing system goes far beyond simple URL matching. It includes sophisticated parameter injection, route optimization, and flexible pattern matching that makes building complex APIs effortless.\n\n## Understanding Parameter Injection\n\nRobyn automatically analyzes your function signatures and injects the appropriate request components. This eliminates boilerplate code and makes handlers cleaner.\n\n### The Injection Engine\n\nThe parameter injection system works in two phases:\n\n1. **Function Introspection**: Robyn analyzes your function signature at registration time\n2. **Runtime Injection**: For each request, Robyn provides the exact parameters your function needs\n\n<Row>\n  <Col>\n    **Type-Based Injection**: Uses type annotations to determine what to inject\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Type-Based Parameter Injection\">\n    ```python\n    from robyn import Request, QueryParams, Headers\n    from robyn.types import PathParams, RequestBody, RequestMethod\n    \n    @app.post(\"/users/:user_id/posts/:post_id\")\n    async def update_post(\n        # Robyn automatically injects these based on type annotations\n        request: Request,           # Complete request object\n        path_params: PathParams,    # {\"user_id\": \"123\", \"post_id\": \"456\"}\n        query_params: QueryParams,  # ?draft=true&tags=python,web\n        headers: Headers,           # All request headers\n        body: RequestBody,         # Raw request body\n        method: RequestMethod      # \"POST\"\n    ):\n        user_id = path_params[\"user_id\"]\n        post_id = path_params[\"post_id\"]\n        is_draft = query_params.get(\"draft\") == \"true\"\n        content_type = headers.get(\"content-type\")\n        \n        return {\n            \"user_id\": user_id,\n            \"post_id\": post_id,\n            \"is_draft\": is_draft,\n            \"body_size\": len(body),\n            \"method\": method\n        }\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n<Row>\n  <Col>\n    **Name-Based Injection**: Uses parameter names to inject components when type annotations aren't available\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Name-Based Parameter Injection\">\n    ```python\n    # Reserved parameter names that Robyn recognizes\n    @app.get(\"/search/:category\")\n    def search_handler(\n        query_params,      # Injected by name\n        path_params,       # Injected by name\n        headers,           # Injected by name\n        request           # Injected by name (full request object)\n    ):\n        category = path_params[\"category\"]\n        search_term = query_params.get(\"q\", \"\")\n        user_agent = headers.get(\"user-agent\", \"\")\n        \n        return {\n            \"category\": category,\n            \"search\": search_term,\n            \"user_agent\": user_agent\n        }\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n### Complete List of Injectable Types\n\n| Type Annotation | Reserved Name | Description |\n|-----------------|---------------|-------------|\n| `Request` | `request`, `req`, `r` | Complete request object |\n| `QueryParams` | `query_params` | URL query parameters |\n| `Headers` | `headers` | Request headers |\n| `PathParams` | `path_params` | URL path parameters |\n| `RequestBody` | `body` | Raw request body |\n| `RequestMethod` | `method` | HTTP method (GET, POST, etc.) |\n| `RequestURL` | `url` | Request URL information |\n| `FormData` | `form_data` | Form-encoded data |\n| `RequestFiles` | `files` | Uploaded files |\n| `RequestIP` | `ip_addr` | Client IP address |\n| `RequestIdentity` | `identity` | Authentication identity |\n\n## Advanced URL Patterns\n\n### Dynamic Route Parameters\n\n<Row>\n  <Col>\n    Robyn supports multiple types of path parameters with flexible matching patterns.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Dynamic Route Patterns\">\n    ```python\n    # Simple parameter\n    @app.get(\"/users/:id\")\n    def get_user(path_params):\n        return {\"user_id\": path_params[\"id\"]}\n    \n    # Multiple parameters\n    @app.get(\"/users/:user_id/posts/:post_id\")\n    def get_user_post(path_params):\n        return {\n            \"user_id\": path_params[\"user_id\"],\n            \"post_id\": path_params[\"post_id\"]\n        }\n    \n    # Optional parameters with defaults\n    @app.get(\"/posts/:id/:slug?\")\n    def get_post(path_params):\n        post_id = path_params[\"id\"]\n        slug = path_params.get(\"slug\", f\"post-{post_id}\")\n        return {\"id\": post_id, \"slug\": slug}\n    \n    # Wildcard matching\n    @app.get(\"/files/*filepath\")\n    def serve_file(path_params):\n        filepath = path_params[\"filepath\"]\n        return {\"serving\": filepath}\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n### Route Constraints and Validation\n\n<Row>\n  <Col>\n    While Robyn doesn't have built-in parameter validation, you can implement it in your handlers for type safety.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Parameter Validation\">\n    ```python\n    import re\n    from robyn import HTTPException\n    \n    @app.get(\"/users/:user_id\")\n    def get_user(path_params):\n        user_id = path_params[\"user_id\"]\n        \n        # Validate that user_id is numeric\n        if not user_id.isdigit():\n            raise HTTPException(400, \"user_id must be numeric\")\n        \n        user_id = int(user_id)\n        if user_id <= 0:\n            raise HTTPException(400, \"user_id must be positive\")\n        \n        return {\"user_id\": user_id}\n    \n    @app.get(\"/posts/:slug\")\n    def get_post_by_slug(path_params):\n        slug = path_params[\"slug\"]\n        \n        # Validate slug format\n        if not re.match(r'^[a-z0-9-]+$', slug):\n            raise HTTPException(400, \"Invalid slug format\")\n        \n        return {\"slug\": slug}\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n## Route Optimization\n\n### Const Routes for Static Responses\n\n<Row>\n  <Col>\n    Use `const=True` for responses that never change. These are cached in Rust memory and the handler function is never re-executed after startup.\n\n    When no middleware is registered, const routes take a fast path served entirely from the Rust layer without entering Python at all. When middleware is registered (including global before-request and after-request handlers), const routes still serve the cached response but middleware executes normally for every request. This means const routes are always safe to use alongside middleware.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Const Route Optimization\">\n    ```python\n    # Perfect for health checks, static configuration\n    @app.get(\"/health\", const=True)\n    def health_check():\n        return {\"status\": \"healthy\", \"version\": \"1.0\"}\n    \n    # API metadata that rarely changes\n    @app.get(\"/api/info\", const=True)  \n    def api_info():\n        return {\n            \"name\": \"My API\",\n            \"version\": \"2.1.0\",\n            \"documentation\": \"/docs\"\n        }\n    \n    # Static configuration endpoints\n    @app.get(\"/config/public\", const=True)\n    def public_config():\n        return {\n            \"max_upload_size\": \"10MB\",\n            \"allowed_origins\": [\"https://myapp.com\"]\n        }\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n### Route Priority and Ordering\n\n<Row>\n  <Col>\n    Routes are matched in the order they're registered. More specific routes should be registered before general ones.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Route Ordering Best Practices\">\n    ```python\n    # GOOD: Specific routes first\n    @app.get(\"/users/profile\")\n    def get_current_user_profile():\n        return {\"profile\": \"current_user\"}\n    \n    @app.get(\"/users/settings\")\n    def get_user_settings():\n        return {\"settings\": \"user_settings\"}\n    \n    @app.get(\"/users/:id\")\n    def get_user(path_params):\n        return {\"user_id\": path_params[\"id\"]}\n    \n    # BAD: This would never be reached\n    # @app.get(\"/users/:id\")  # Registered first\n    # @app.get(\"/users/profile\")  # Never matched!\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n## Advanced Query Parameter Handling\n\n### Query Parameter Parsing\n\n<Row>\n  <Col>\n    Robyn provides rich query parameter handling with automatic type conversion helpers.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Advanced Query Parameters\">\n    ```python\n    @app.get(\"/search\")\n    def search(query_params):\n        # Basic parameter access\n        q = query_params.get(\"q\", \"\")\n        \n        # Parameters with defaults\n        page = int(query_params.get(\"page\", \"1\"))\n        limit = int(query_params.get(\"limit\", \"10\"))\n        \n        # Boolean parameters\n        include_deleted = query_params.get(\"include_deleted\", \"false\").lower() == \"true\"\n        \n        # Array parameters (?tags=python&tags=web&tags=api)\n        tags = query_params.get_list(\"tags\") or []\n        \n        # Convert to dict for easier processing\n        all_params = query_params.to_dict()\n        \n        return {\n            \"query\": q,\n            \"page\": page,\n            \"limit\": limit,\n            \"include_deleted\": include_deleted,\n            \"tags\": tags,\n            \"all_params\": all_params\n        }\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n### Complex Query String Patterns\n\n<Row>\n  <Col>\n    Handle complex query patterns like filtering, sorting, and nested parameters.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Complex Query Patterns\">\n    ```python\n    @app.get(\"/api/products\")\n    def get_products(query_params):\n        # Filtering: ?filter[category]=electronics&filter[price_min]=100\n        filters = {}\n        for key, value in query_params.to_dict().items():\n            if key.startswith(\"filter[\") and key.endswith(\"]\"):\n                filter_key = key[7:-1]  # Remove \"filter[\" and \"]\"\n                filters[filter_key] = value\n        \n        # Sorting: ?sort=price&order=desc\n        sort_field = query_params.get(\"sort\", \"created_at\")\n        sort_order = query_params.get(\"order\", \"asc\")\n        \n        # Pagination: ?page=2&per_page=20\n        page = int(query_params.get(\"page\", \"1\"))\n        per_page = min(int(query_params.get(\"per_page\", \"10\")), 100)  # Cap at 100\n        \n        # Field selection: ?fields=id,name,price\n        fields = query_params.get(\"fields\", \"\").split(\",\") if query_params.get(\"fields\") else None\n        \n        return {\n            \"filters\": filters,\n            \"sort\": {\"field\": sort_field, \"order\": sort_order},\n            \"pagination\": {\"page\": page, \"per_page\": per_page},\n            \"fields\": fields\n        }\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n## SubRouters and Modular Routing\n\n### Creating SubRouters\n\n<Row>\n  <Col>\n    SubRouters help organize large applications by grouping related routes with common prefixes.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"SubRouter Organization\">\n    ```python\n    from robyn import Robyn, SubRouter\n    \n    app = Robyn(__file__)\n    \n    # API v1 routes\n    api_v1 = SubRouter(__file__, prefix=\"/api/v1\")\n    \n    @api_v1.get(\"/users\")\n    def list_users():\n        return {\"users\": []}\n    \n    @api_v1.get(\"/users/:id\")\n    def get_user(path_params):\n        return {\"user_id\": path_params[\"id\"]}\n    \n    @api_v1.post(\"/users\")\n    def create_user(body):\n        return {\"created\": True, \"data\": body}\n    \n    # Admin routes\n    admin = SubRouter(__file__, prefix=\"/admin\")\n    \n    @admin.get(\"/dashboard\")\n    def admin_dashboard():\n        return {\"dashboard\": \"admin\"}\n    \n    @admin.get(\"/users\")\n    def admin_users():\n        return {\"admin_users\": []}\n    \n    # Register subrouters\n    app.include_router(api_v1)\n    app.include_router(admin)\n    \n    # Routes are now available at:\n    # /api/v1/users, /api/v1/users/:id, /admin/dashboard, etc.\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n### SubRouter Middleware\n\n<Row>\n  <Col>\n    Configure authentication handlers on SubRouters and apply authentication to routes using `auth_required=True`.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"SubRouter Middleware\">\n    ```python\n    from robyn import SubRouter\n    from robyn.authentication import AuthenticationHandler\n    \n    # Admin routes with authentication\n    admin = SubRouter(__file__, prefix=\"/admin\")\n    \n    class AdminAuth(AuthenticationHandler):\n        def authenticate(self, request):\n            auth_header = request.headers.get(\"authorization\", \"\")\n            if not auth_header.startswith(\"Bearer \"):\n                return None\n            \n            token = auth_header[7:]  # Remove \"Bearer \"\n            return self.validate_admin_token(token)\n        \n        def validate_admin_token(self, token):\n            # Your token validation logic\n            if token == \"admin-secret-token\":\n                return {\"user\": \"admin\"}  # Return identity object\n            return None\n    \n    # Configure the authentication handler for this SubRouter\n    admin.configure_authentication(AdminAuth())\n    \n    # Routes must explicitly require authentication with auth_required=True\n    @admin.get(\"/users\", auth_required=True)\n    def admin_users():\n        return {\"admin_users\": [\"user1\", \"user2\"]}\n    \n    @admin.delete(\"/users/:id\", auth_required=True)\n    def delete_user(path_params):\n        return {\"deleted\": path_params[\"id\"]}\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n## Route Testing and Debugging\n\n### Route Inspection\n\n<Row>\n  <Col>\n    Debug your routes by inspecting the registered route table.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Route Debugging\">\n    ```python\n    from robyn import Robyn\n    \n    app = Robyn(__file__)\n    \n    @app.get(\"/users/:id\")\n    def get_user(path_params):\n        return {\"user\": path_params[\"id\"]}\n    \n    @app.post(\"/users\")\n    def create_user(body):\n        return {\"created\": True}\n    \n    # Debug: Print all registered routes\n    if __name__ == \"__main__\":\n        print(\"Registered routes:\")\n        for route in app.get_routes():\n            print(f\"{route.method} {route.path}\")\n        \n        app.start(port=8080)\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n### Route Performance Monitoring\n\n<Row>\n  <Col>\n    Add timing middleware to monitor route performance.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Performance Monitoring\">\n    ```python\n    import time\n    from robyn import Robyn\n    \n    app = Robyn(__file__)\n    \n    @app.before_request\n    def timing_middleware(request):\n        request.start_time = time.time()\n        return request\n    \n    @app.after_request\n    def timing_after_middleware(request, response):\n        duration = time.time() - request.start_time\n        print(f\"{request.method} {request.url.path} - {duration:.3f}s\")\n        response.headers[\"X-Response-Time\"] = f\"{duration:.3f}s\"\n        return response\n    \n    @app.get(\"/slow\")\n    def slow_endpoint():\n        time.sleep(0.1)  # Simulate work\n        return {\"message\": \"slow response\"}\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n## Best Practices\n\n### 1. Parameter Injection Patterns\n\n- **Use type annotations** for better IDE support and self-documenting code\n- **Inject only what you need** to keep handlers focused\n- **Validate parameters early** to provide clear error messages\n\n### 2. Route Organization\n\n- **Group related routes** using SubRouters\n- **Order routes from specific to general** to avoid matching issues\n- **Use consistent naming conventions** for path parameters\n\n### 3. Performance Optimization\n\n- **Use const routes** for static responses\n- **Minimize parameter injection** in high-traffic endpoints\n- **Cache expensive computations** rather than repeating them\n\n### 4. Error Handling\n\n- **Validate path parameters** early in handlers\n- **Provide meaningful error messages** for invalid input\n- **Use consistent error response formats** across your API\n\n## What's Next?\n\nNow that you've mastered advanced routing, explore other Robyn features:\n\n- [Middleware Development Guide](/documentation/en/api_reference/middlewares_advanced)\n- [Performance Optimization](/documentation/en/api_reference/performance_optimization)\n- [WebSocket Advanced Features](/documentation/en/api_reference/websockets_advanced)"
  },
  {
    "path": "docs_src/src/pages/documentation/en/api_reference/agents.mdx",
    "content": "export const description =\n  'Robyn AI Agents provide intelligent functionality for building AI-powered applications using MCP (Model Context Protocol) implementation with file system access, task management, and context-aware capabilities.'\n\n# Agents\n\nThis guide demonstrates how to build AI-powered agents using Robyn's MCP (Model Context Protocol) implementation.\n\n## Overview\n\nThe agent system connects AI assistants like Claude Desktop to your development environment, providing seamless access to:\n\n- File system operations (read, search, organize)\n- Task and note management\n- System monitoring and git integration\n- Web content fetching and analysis\n- Context-aware code analysis\n\n## Quick Start\n\n1. Run the MCP server:\n   ```bash\n   python examples/agents.py\n   ```\n\n2. Connect your AI assistant to `http://localhost:8080/mcp`\n\n3. Start using natural language commands:\n   - \"What files are in my projects directory?\"\n   - \"Show me my recent git commits\"\n   - \"Create a note about today's standup meeting\"\n   - \"What processes are using the most CPU?\"\n   - \"Add a task to review the quarterly report\"\n\n## Configuration\n\nThe assistant creates the following structure:\n\n```\n~/Documents/\n├── notes/           # Markdown notes\n└── tasks.json      # Task list\n\n~/projects/          # Development projects\n├── project1/\n└── project2/\n```\n\n## Security\n\n- File access restricted to home directory\n- Safe mathematical expression evaluation\n- Path validation for all file operations\n- Read-only git operations\n\n## Available Resources\n\n### File System\n- `fs://{path}` - Read files in home directory\n- `fs://dir/{path}` - List directory contents\n\n### Git Integration\n- `git://repo/{repo_name}` - Repository status and commits\n\n### System Monitoring\n- `system://processes` - Running processes\n- `system://stats` - System statistics\n\n## Available Tools\n\n- `create_note(title, content, tags)` - Create markdown notes\n- `add_task(task, priority, due_date)` - Add tasks\n- `complete_task(task_id)` - Mark tasks complete\n- `search_files(query, directory)` - Search file contents\n- `fetch_url_content(url, max_length)` - Download web content\n\n## Available Prompts\n\n- `analyze_file_structure(directory)` - Generate project analysis\n- `code_review_request(file_path, focus_area)` - Create code reviews\n- `task_prioritization(context)` - Organize and prioritize work\n\n## Dependencies\n\nOptional enhanced functionality:\n\n```bash\npip install psutil  # Enhanced system monitoring\n```\n\n## Implementation Examples\n\n### Development Workflow\n\"Analyze my projects directory and help prioritize work based on recent activity\"\n\n### Project Analysis\n\"Review my web-app project structure and suggest improvements\"\n\n### Meeting Notes\n\"Create a note about today's architecture review with key decisions\"\n\n### Code Search\n\"Find all files mentioning 'authentication' and summarize approaches\"\n\n### Task Management\n\"Add high-priority task to refactor user service, due Friday\"\n\n## Integration Benefits\n\nConnecting AI assistants to your development environment enables:\n- Native file system browsing\n- Context-aware project conversations\n- Personalized code suggestions\n- Real-time task management\n- Workspace-specific code reviews\n\n## Advanced Features\n\nThe MCP implementation includes:\n- URI templates with parameter extraction\n- Auto-generated schemas from type hints\n- Async/sync operation handlers\n- MCP-compliant error handling\n- Type-safe parameter passing\n\nExtend easily with custom resources, tools, and prompts for your specific workflow."
  },
  {
    "path": "docs_src/src/pages/documentation/en/api_reference/ai.mdx",
    "content": "export const description =\n  'Robyn AI provides agent and memory functionality for building intelligent applications with conversation history, context awareness, and pluggable AI runners.'\n\n# AI Agent and Memory\n\nRobyn includes built-in AI capabilities that allow you to create intelligent applications with conversation memory, context awareness, and pluggable agent runners. The AI module provides abstractions for memory storage and agent execution that can be easily integrated into your Robyn applications.\n\n## Installation\n\nThe AI features are included with the base Robyn installation:\n\n```bash\npip install robyn\n```\n\n\n## Quick Start\n\nHere's a simple example of using Robyn's AI features:\n\n```python\nfrom robyn import Robyn\nfrom robyn.ai import agent, memory\n\napp = Robyn(__file__)\n\n# Create memory instance\nmem = memory(provider=\"inmemory\", user_id=\"user123\")\n\n# Create agent with memory\nchat_agent = agent(runner=\"simple\", memory=mem)\n\n@app.get(\"/chat\")\nasync def chat_endpoint(request):\n    query = request.query_params.get(\"q\", [\"\"])[0]\n    if not query:\n        return {\"error\": \"Query required\"}\n    \n    # Run agent with conversation history\n    result = await chat_agent.run(query, history=True)\n    return result\n```\n\n## Memory System\n\nThe memory system provides persistent storage for conversation history and context. It supports multiple providers and offers a consistent interface for storing and retrieving conversation data.\n\n### Memory Providers\n\n#### InMemory Provider\n\nThe simplest provider that stores data in memory. Data is lost when the application restarts.\n\n```python\nfrom robyn.ai import memory\n\n# Create in-memory storage\nmem = memory(provider=\"inmemory\", user_id=\"user123\")\n\n# Add messages\nawait mem.add(\"Hello, how are you?\")\nawait mem.add(\"I'm doing great, thanks!\")\n\n# Retrieve all messages\nmessages = await mem.get()\n\n# Clear memory\nawait mem.clear()\n```\n\n\n### Memory API\n\nThe Memory class provides these key methods:\n\n- `add(message, metadata=None)` - Store a message with optional metadata\n- `get(query=None)` - Retrieve messages, optionally filtered by query\n- `clear()` - Clear all stored messages for the user\n\n## Agent System\n\nAgents provide the execution layer for AI functionality. They can use different runners and integrate with memory for context-aware responses.\n\n### Agent Runners\n\n#### Simple Runner\n\nA runner with OpenAI integration that provides intelligent responses:\n\n```python\nfrom robyn.ai import agent\n\n# Create simple agent with OpenAI\nfrom robyn.ai import configure\n\nconfig = configure(openai_api_key=\"your-openai-key\")\nsimple_agent = agent(runner=\"simple\", config=config)\n\n# Use the agent\nresult = await simple_agent.run(\"What's the weather like?\")\n# Returns structured response with AI-generated content\n```\n\n\n### Agent API\n\nThe Agent class provides:\n\n- `run(query, history=False, **kwargs)` - Execute the agent with optional history context\n- Automatic memory integration when provided\n- Support for custom runners and configuration\n\n## Complete Example\n\nHere's a comprehensive example showing all features:\n\n```python\nfrom robyn import Robyn\nfrom robyn.ai import agent, memory\n\napp = Robyn(__file__)\n\n# Create memory with InMemory provider\nmem = memory(\n    provider=\"inmemory\",\n    user_id=\"guest\"\n)\n\n# Create agent with memory\nchat_agent = agent(runner=\"simple\", memory=mem)\n\n@app.get(\"/\")\nasync def home():\n    return {\"message\": \"Robyn AI Chat API\"}\n\n@app.post(\"/chat\")  \nasync def chat(request):\n    \"\"\"Chat with AI agent\"\"\"\n    data = request.json()\n    query = data.get(\"query\", \"\")\n    include_history = data.get(\"history\", True)\n    \n    if not query:\n        return {\"error\": \"Query is required\"}\n    \n    try:\n        result = await chat_agent.run(query, history=include_history)\n        return {\n            \"query\": query,\n            \"response\": result.get(\"response\"),\n            \"history_included\": include_history\n        }\n    except Exception as e:\n        return {\"error\": str(e)}\n\n@app.get(\"/memory\")\nasync def get_memory():\n    \"\"\"Retrieve conversation history\"\"\"\n    try:\n        memories = await mem.get()\n        return {\"memories\": memories, \"count\": len(memories)}\n    except Exception as e:\n        return {\"error\": str(e)}\n\n@app.delete(\"/memory\")\nasync def clear_memory():\n    \"\"\"Clear conversation history\"\"\"\n    try:\n        await mem.clear()\n        return {\"message\": \"Memory cleared\"}\n    except Exception as e:\n        return {\"error\": str(e)}\n\n@app.post(\"/memory\")\nasync def add_memory(request):\n    \"\"\"Add message to memory\"\"\"\n    data = request.json()\n    message = data.get(\"message\", \"\")\n    metadata = data.get(\"metadata\", {})\n    \n    if not message:\n        return {\"error\": \"Message is required\"}\n    \n    try:\n        await mem.add(message, metadata)\n        return {\"message\": \"Added to memory\"}\n    except Exception as e:\n        return {\"error\": str(e)}\n\nif __name__ == \"__main__\":\n    app.start(host=\"127.0.0.1\", port=8080)\n```\n\n## Advanced Usage\n\n### Custom Memory Providers\n\nYou can create custom memory providers by extending the `MemoryProvider` abstract base class:\n\n```python\nfrom robyn.ai import MemoryProvider\nfrom typing import Dict, List, Any, Optional\n\nclass CustomMemoryProvider(MemoryProvider):\n    async def store(self, user_id: str, data: Dict[str, Any]) -> None:\n        # Implement custom storage logic\n        pass\n    \n    async def retrieve(self, user_id: str, query: Optional[str] = None) -> List[Dict[str, Any]]:\n        # Implement custom retrieval logic\n        return []\n    \n    async def clear(self, user_id: str) -> None:\n        # Implement custom clearing logic\n        pass\n\n# Use custom provider\nfrom robyn.ai import Memory\ncustom_mem = Memory(provider=CustomMemoryProvider(), user_id=\"user123\")\n```\n\n### Custom Agent Runners\n\nSimilarly, you can create custom agent runners:\n\n```python\nfrom robyn.ai import AgentRunner\nfrom typing import Dict, Any\n\nclass CustomAgentRunner(AgentRunner):\n    async def run(self, query: str, **kwargs) -> Dict[str, Any]:\n        # Implement custom agent logic\n        return {\n            \"response\": f\"Custom response to: {query}\",\n            \"processed\": True\n        }\n\n# Use custom runner\nfrom robyn.ai import Agent\ncustom_agent = Agent(runner=CustomAgentRunner())\n```\n\n## Best Practices\n\n1. **User Isolation**: Always use unique user IDs to isolate memory between different users\n2. **Error Handling**: Wrap AI operations in try-catch blocks as external services may fail\n3. **Memory Management**: Regularly clear or archive old memories to prevent unbounded growth\n4. **Configuration**: Store sensitive configuration (API keys, etc.) in environment variables\n5. **Testing**: Use the simple runner for development and testing before deploying complex agents\n\n## Troubleshooting\n\n### Common Issues\n\n**ImportError for openai**: Install the required package:\n```bash\npip install openai\n```\n\n**Memory not persisting**: Note that the in-memory provider loses data when the application restarts. Consider implementing a custom persistent provider for production use.\n\n**Agent timeouts**: Complex operations may take time. Consider implementing timeout handling in your endpoints.\n\n**Memory growing too large**: Implement periodic cleanup or use providers with built-in retention policies."
  },
  {
    "path": "docs_src/src/pages/documentation/en/api_reference/architecture_deep_dive.mdx",
    "content": "export const description =\n  'Deep dive into Robyn\\'s unique hybrid Python-Rust architecture and how it delivers exceptional performance while maintaining Python\\'s ease of use.'\n\n# Architecture Deep Dive\n\nRobyn's architecture is unique in the Python web framework landscape. It combines Python's expressiveness with Rust's performance through a carefully designed hybrid system. This deep dive explains how Robyn works under the hood and why it's so fast.\n\n## The Hybrid Python-Rust Design\n\n### Two-Layer Architecture\n\nRobyn operates on two distinct but interconnected layers:\n\n1. **Python Layer**: Provides the developer-facing API, routing configuration, and business logic\n2. **Rust Layer**: Handles HTTP parsing, request routing, response generation, and I/O operations\n\n<Row>\n  <Col>\n    The Python layer is where you write your application code. It handles:\n    - Route definitions and decorators\n    - Request parameter injection\n    - Middleware configuration\n    - Business logic execution\n    - Response formatting\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Python Layer Example\">\n    ```python\n    from robyn import Robyn, Request\n    \n    app = Robyn(__file__)\n    \n    @app.get(\"/users/:id\")\n    async def get_user(request: Request, user_id: str):\n        # Business logic runs in Python\n        user = await fetch_user_from_db(user_id)\n        return {\"user\": user.to_dict()}\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n<Row>\n  <Col>\n    The Rust layer handles all performance-critical operations:\n    - HTTP request parsing\n    - URL routing and matching\n    - WebSocket connections\n    - Static file serving\n    - Response serialization\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Rust Layer (Internal)\">\n    ```rust\n    // This happens internally in Robyn's Rust core\n    impl HttpRouter {\n        pub fn route_request(&self, method: &str, path: &str) -> Option<RouteInfo> {\n            // High-performance routing using matchit crate\n            self.router.at(path).ok().map(|matched| {\n                RouteInfo {\n                    handler: matched.value.clone(),\n                    params: matched.params.clone(),\n                }\n            })\n        }\n    }\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n### PyO3 Bridge: Connecting Python and Rust\n\nThe magic happens through PyO3, which enables seamless communication between Python and Rust:\n\n<Row>\n  <Col>\n    **Function Registration**: When you define a route handler in Python, Robyn registers it with the Rust runtime through PyO3 bindings.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Function Registration Flow\">\n    ```python\n    # Python side - route registration\n    @app.get(\"/api/data\")\n    def handler(request):\n        return {\"data\": \"example\"}\n    \n    # Internally, this creates a FunctionInfo object\n    # that's passed to the Rust runtime\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n<Row>\n  <Col>\n    **Request Execution Flow**: When a request arrives, the Rust layer routes it and then calls back into Python to execute your handler.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Request Execution\">\n    ```\n    1. HTTP Request arrives → Rust HTTP parser\n    2. Route matching → Rust router (matchit crate)\n    3. Parameter extraction → Rust\n    4. Handler execution → Python (via PyO3)\n    5. Response processing → Rust\n    6. HTTP Response → Client\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n## Performance Optimizations\n\n### Fast Path for Static Responses\n\nRobyn includes a \"const routes\" optimization for static responses:\n\n<Row>\n  <Col>\n    When you mark a route as `const`, Robyn caches the response in Rust memory and never re-executes the Python handler. If no middleware is registered, const routes are served entirely from the Rust layer without entering Python at all. When middleware is present, the cached response is still used but before-request and after-request middleware execute normally.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Const Routes\">\n    ```python\n    # This response is cached in Rust memory\n    @app.get(\"/health\", const=True)\n    def health_check():\n        return {\"status\": \"healthy\"}\n    \n    # Without middleware: served directly from Rust, bypassing Python entirely\n    # With middleware: cached response is used, but middleware still runs\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n### Zero-Copy Request Handling\n\nThe Rust layer uses zero-copy techniques wherever possible:\n\n- Request bodies are parsed once and shared between layers\n- String data is referenced rather than copied\n- Response buffers are reused across requests\n\n### Async Runtime Integration\n\nRobyn integrates with Python's asyncio while maintaining Rust's async runtime:\n\n<Row>\n  <Col>\n    **Sync Handlers**: Executed in a thread pool to avoid blocking the async runtime\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Sync Handler Execution\">\n    ```python\n    @app.get(\"/sync\")\n    def sync_handler(request):\n        # Runs in thread pool\n        time.sleep(1)  # Won't block other requests\n        return \"Done\"\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n<Row>\n  <Col>\n    **Async Handlers**: Executed directly in the async runtime for maximum performance\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Async Handler Execution\">\n    ```python\n    @app.get(\"/async\")\n    async def async_handler(request):\n        # Runs in main async runtime\n        await asyncio.sleep(1)\n        return \"Done\"\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n## Advanced Parameter Injection\n\nOne of Robyn's most sophisticated features is its parameter injection system:\n\n### Type-Based Injection\n\n<Row>\n  <Col>\n    Robyn analyzes your function signatures and automatically injects the appropriate request components based on type annotations.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Type-Based Injection\">\n    ```python\n    from robyn import Request, QueryParams, Headers\n    from robyn.types import PathParams, RequestBody\n    \n    @app.post(\"/complex/:id\")\n    async def complex_handler(\n        request: Request,           # Full request object\n        query_params: QueryParams,  # ?param=value\n        headers: Headers,           # Request headers\n        path_params: PathParams,    # :id from URL\n        body: RequestBody          # Request body\n    ):\n        return {\n            \"id\": path_params[\"id\"],\n            \"query\": query_params.to_dict(),\n            \"user_agent\": headers.get(\"user-agent\"),\n            \"body_length\": len(body)\n        }\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n### Name-Based Injection\n\n<Row>\n  <Col>\n    You can also use reserved parameter names without type annotations.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Name-Based Injection\">\n    ```python\n    @app.get(\"/simple/:id\")\n    def simple_handler(query_params, path_params, headers):\n        # Parameters injected based on names\n        return {\n            \"id\": path_params[\"id\"],\n            \"search\": query_params.get(\"q\", \"\"),\n            \"auth\": headers.get(\"authorization\")\n        }\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n## Memory Management\n\n### Python Object Lifecycle\n\nRobyn carefully manages Python objects across the Python-Rust boundary:\n\n1. **Request Objects**: Created once per request and reused\n2. **Response Objects**: Efficiently serialized and passed to Rust\n3. **Handler References**: Stored in Rust and called via PyO3\n\n### Rust Memory Safety\n\nThe Rust layer benefits from Rust's ownership system:\n\n- No memory leaks from HTTP parsing\n- Safe concurrent access to shared data\n- Automatic cleanup of connection resources\n\n## Scaling Architecture\n\n### Multi-Process Mode\n\n<Row>\n  <Col>\n    Robyn can spawn multiple processes to utilize all CPU cores.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Multi-Process Scaling\">\n    ```bash\n    # Spawn 4 worker processes\n    python app.py --processes 4 --workers 2\n    \n    # Each process runs independently with shared-nothing architecture\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n### Multi-Worker Mode\n\n<Row>\n  <Col>\n    Within each process, multiple worker threads handle requests concurrently.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Worker Thread Model\">\n    ```python\n    # Workers share the same Python interpreter\n    # but handle requests concurrently\n    app.start(port=8080, workers=4)\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n## WebSocket Architecture\n\n### Persistent Connections\n\nRobyn's WebSocket implementation maintains persistent connections in the Rust layer while allowing Python handlers to process messages:\n\n<Row>\n  <Col>\n    **Connection Management**: Handled entirely in Rust for efficiency\n    \n    **Message Processing**: Python handlers process individual messages\n    \n    **Broadcasting**: Rust-based message distribution for high throughput\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"WebSocket Flow\">\n    ```python\n    from robyn import WebSocketDisconnect\n\n    @app.websocket(\"/chat\")\n    async def websocket_handler(websocket):\n        try:\n            while True:\n                message = await websocket.receive_text()\n                response = process_chat_message(message)\n                await websocket.send_text(response)\n        except WebSocketDisconnect:\n            pass\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n## Why This Architecture Works\n\n1. **Best of Both Worlds**: Python's productivity with Rust's performance\n2. **Gradual Optimization**: Hot paths can be moved to Rust incrementally  \n3. **Memory Efficiency**: Minimal copying between layers\n4. **Async Integration**: Seamless integration with Python's async/await\n5. **Safety**: Rust's memory safety prevents common server vulnerabilities\n\nThis architecture allows Robyn to achieve performance comparable to pure Rust web frameworks while maintaining the ease of development that Python developers expect.\n\n## What's Next?\n\nNow that you understand Robyn's architecture, explore how to leverage its advanced features:\n\n- [Advanced Routing and Parameter Injection](/documentation/en/api_reference/advanced_routing)\n- [Performance Optimization Guide](/documentation/en/api_reference/performance_optimization)\n- [WebSocket Deep Dive](/documentation/en/api_reference/websockets_advanced)"
  },
  {
    "path": "docs_src/src/pages/documentation/en/api_reference/authentication.mdx",
    "content": "\nexport const description =\n  'On this page, we’ll dive into the different conversation endpoints you can use to manage conversations programmatically.'\n\nAfter Creating a basic version of the app, Batman wanted to restrict the access to the Gotham Police Department. So, he enquired about the Authentication functionalities in Robyn.\n\n\n## Authentication\n\n\nAs Batman found out, Robyn provides an easy way to add an authentication middleware to your application. You can then specify `auth_required=True` in your routes to make them accessible only to authenticated users.\n\n\n\n<Row>\n<Col>\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    @app.get(\"/auth\", auth_required=True)\n    async def auth(request: Request):\n        # This route method will only be executed if the user is authenticated\n        # Otherwise, a 401 response will be returned\n        return \"Hello, world\"\n\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n<Row>\n\n  <Col>\n  To add an authentication middleware, you can use the `configure_authentication` method. This method requires an `AuthenticationHandler` object as an argument. This object specifies how to authenticate a user, and uses a `TokenGetter` object to retrieve the token from the request. Robyn does currently provide a `BearerGetter` class that gets the token from the `Authorization` header, using the `Bearer` scheme. Here is an example of a basic authentication handler:\n\n\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n      ```python\n      class BasicAuthHandler(AuthenticationHandler):\n        def authenticate(self, request: Request) -> Optional[Identity]:\n            token = self.token_getter.get_token(request)\n            if token == \"valid\":\n                return Identity(claims={})\n            return None\n\n      app.configure_authentication(BasicAuthHandler(token_getter=BearerGetter()))\n\n      ```\n    </CodeGroup>\n  </Col>\n\nThe authenticate method should return an `Identity` object if the user is authenticated, or `None` otherwise. The Identity object can contain any data you want, and will be accessible in the route methods using the `request.identity` attribute.\n\n<b>\n  Note: that this authentication system is basically only using a `before request` middleware under the hood. This means you can overlook it and create your own authentication system using middlewares if you want to. However, Robyn still provides this easy to implement solution that should suit most use cases.\n</b>\n\n\n</Row>\n\n--- \n\n## What's next?\n\nNow, that Batman has learned about authentication, he wanted to know about certain optimization techniques that he could use to make his application faster. He found out about the following features\n\n- [Const Requests and Multi Core Scaling](/documentation/en/api_reference/const_requests)\n\n\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/api_reference/const_requests.mdx",
    "content": "\nexport const description =\n  'On this page, we’ll dive into the different conversation endpoints you can use to manage conversations programmatically.'\n\nAfter authentication, Batman was worried about the website traffic during the rush hours. He was worried about the server crashing when Joker would try to break all the criminals from the Arkham asylum one more time. So, Robyn told him about the `Const Requests` feature and the multi-core scaling potential.\n\n\n## Const Requests\n\n\nRobyn told Batman that you can pre-compute the response for each route. This will compute the response even before execution. This will improve the response time bypassing the need to access the router.\n\n\n<Row>\n<Col>\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n      ```python\n      @app.get(\"/\", const=True)\n      async def h():\n          return \"Hello, world\"\n\n      ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n## Muli-core scaling\n\nRobyn told Batman that he can use the `--workers` flag to scale the application to multiple cores. This will create multiple instances of the application and will distribute the load among them. This will improve the performance of the application.\n\n\n<Row>\n\n  <Col>\n\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Request\">\n\n      ```python\n      python3 app.py --workers=N --process=M\n      ```\n    </CodeGroup>\n  </Col>\n\nThe authenticate method should return an `Identity` object if the user is authenticated, or `None` otherwise. The Identity object can contain any data you want, and will be accessible in the route methods using the `request.identity` attribute.\n\n<b>\n  Note: that this authentication system is basically only using a `before request` middleware under the hood. This means you can overlook it and create your own authentication system using middlewares if you want to. However, Robyn still provides this easy to implement solution that should suit most use cases.\n</b>\n\n\n</Row>\n\n--- \n\n## What's next?\n\nAfter making the application faster, Batman was happy and wanted to make a request from his Frontend Dashboard.\n\nBut he was faced with CORS issues! He asked Robyn about how to solve this issue. Robyn told him about the following features\n\n- [CORS](/documentation/en/api_reference/cors)\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/api_reference/cors.mdx",
    "content": "export const description =\n  'On this page, we’ll dive into the different conversation endpoints you can use to manage conversations programmatically.'\n\n\n## CORS\n\nBatman was annoyed on getting a CORS error whenever he tried to access the API.\n\n\n## Scaling the Application\n\n<Row>\n<Col>\nYou can allow CORS for your application by adding the following code:\n</Col>\n  <Col>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n      from robyn import Robyn, ALLOW_CORS\n\n      app = Robyn(__file__)\n      ALLOW_CORS(app, origins = [\"http://localhost:<PORT>/\"])\n    ```\n\n    </CodeGroup>\n  </Col>\n</Row>\n\n---\n\n\n## What's next?\n\nAfter fixing the CORS issues. Batman was satisfied but he wanted to learn about ways to have small frontend pages in the server itself.\n\nRobyn told him about templates and how he can use them to render HTML pages.\n\n- [Templating](/documentation/en/api_reference/templating)\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/api_reference/dependency_injection.mdx",
    "content": "export const description =\n  'On this page, we will learn about dependency injection in Robyn.'\n\n\n## Dependency Injection\n\nBatman wanted to learn about dependency injection in Robyn. Robyn introduced him to the concept of dependency injection and how it can be used in Robyn.\n\n\n\nRobyn has two types of dependency injection:\nOne is for the application level and the other is for the router level.\n\n### Application Level Dependency Injection\n\n<Row>\n<Col>\nApplication level dependency injection is used to inject dependencies into the application. These dependencies are available to all the requests.\n</Col>\n  <Col>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n      from robyn import Robyn, ALLOW_CORS\n\n      app = Robyn(__file__)\n      GLOBAL_DEPENDENCY = \"GLOBAL DEPENDENCY\"\n\n      app.inject_global(GLOBAL_DEPENDENCY=GLOBAL_DEPENDENCY)\n\n      @app.get(\"/sync/global_di\")\n      def sync_global_di(request, global_dependencies):\n        return global_dependencies[\"GLOBAL_DEPENDENCY\"]\n\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n\n### Router Level Dependency Injection\n\n<Row>\n<Col>\nRouter level dependency injection is used to inject dependencies into the router. These dependencies are available to all the requests of that router.\n</Col>\n  <Col>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n      from robyn import Robyn, ALLOW_CORS, Request\n\n      app = Robyn(__file__)\n      ROUTER_DEPENDENCY = \"ROUTER DEPENDENCY\"\n\n      app.inject(ROUTER_DEPENDENCY=ROUTER_DEPENDENCY)\n\n      @app.get(\"/sync/global_di\")\n      def sync_global_di(r: Request, router_dependencies):\n        return router_dependencies[\"ROUTER_DEPENDENCY\"]\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n<Row>\n<Col>\nNote: `router_dependencies`, `global_dependencies` are reserved parameters and **must** be named as such. The order of the parameters does not matter among them. However, the `router_dependencies` and `global_dependencies` must only come after the `request` parameter. \n</Col>\n</Row>\n\n### WebSocket Dependency Injection\n\n<Row>\n<Col>\nWebSockets support the same dependency injection system as HTTP routes. The `global_dependencies` and `router_dependencies` parameters work in the main handler, `on_connect`, and `on_close` callbacks.\n</Col>\n  <Col>\n    <CodeGroup title=\"WebSocket DI\" tag=\"WebSocket\" label=\"/chat\">\n    ```python {{ title: 'WebSocket DI' }}\n      from robyn import Robyn\n      import logging\n\n      app = Robyn(__file__)\n\n      app.inject_global(logger=logging.getLogger(__name__))\n      app.inject(cache=RedisCache())\n\n      @app.websocket(\"/chat\")\n      async def chat(websocket, global_dependencies=None, router_dependencies=None):\n          logger = global_dependencies.get(\"logger\")\n          cache = router_dependencies.get(\"cache\")\n          logger.info(f\"New connection: {websocket.id}\")\n\n          while True:\n              message = await websocket.receive_text()\n              cache.set(f\"ws_{websocket.id}\", message)\n              await websocket.broadcast(f\"User {websocket.id}: {message}\")\n\n      @chat.on_connect\n      async def on_connect(websocket, global_dependencies=None):\n          logger = global_dependencies.get(\"logger\")\n          logger.info(f\"Client connected: {websocket.id}\")\n          return \"Connected\"\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n---\n\n\n## What's next?\n\nBatman, being the familiar with the dark side wanted to know about Exceptions!\n\nRobyn introduced him to the concept of exceptions and how he can use them to handle errors in his application.\n\n- [Exceptions](/documentation/en/api_reference/exceptions)\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/api_reference/exceptions.mdx",
    "content": "## Custom Exception Handler\n\nBatman learned how to create custom error handlers for different exception types in his application. He wrote the following code to handle exceptions and return a custom error response:\n\n<Row>\n<Col>\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    @app.exception\n    def handle_exception(error: Exception):\n        return Response(status_code=500, description=f\"error msg: {error}\", headers={})\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n---\n\n## What's next?\n\nNow, Batman wanted to scale his application across multiple cores. Robyn led him to Scaling.\n\n- [Scaling](/documentation/en/api_reference/scaling)\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/api_reference/file-uploads.mdx",
    "content": "export const description =\n  'On this page, we’ll dive into the different conversation endpoints you can use to manage conversations programmatically.'\n\n## File Uploads\n\nBatman learned how to handle file uploads using Robyn. He created an endpoint to handle file uploads using the following code:\n\n\n## Sending a File without MultiPart Form Data\n\n<Row>\n<Col>\nBatman scaled his application across multiple cores for better performance. He used the following command:\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    @app.post(\"/upload\")\n    async def upload():\n      body = request.body\n      file = bytearray(body)\n\n      # write whatever filename\n      with open('test.txt', 'wb') as f:\n          f.write(file)\n\n      return {'message': 'success'}\n    ```\n\n    </CodeGroup>\n  </Col>\n</Row>\n\n\n## Sending a File with MultiPart Form Data\n\n<Row>\n<Col>\nBatman scaled his application across multiple cores for better performance. He used the following command:\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n\n    @app.post(\"/sync/multipart-file\")\n    def sync_multipart_file(request: Request):\n        files = request.files\n        file_names = files.keys()\n        return {\"file_names\": list(file_names)}\n    ```\n\n    </CodeGroup>\n  </Col>\n</Row>\n\n---\n\n## File Downloads\n\nBatman now wanted to allow users to download files from his application. He created an endpoint to handle file downloads using the following code:\n\n\n### Serving Simple HTML Files\n\n<Row>\n<Col>\nBatman scaled his application across multiple cores for better performance. He used the following command:\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    from robyn import Robyn, Request, serve_html\n\n    app = Robyn(__file__)\n\n\n    @app.get(\"/\")\n    async def h(request: Request):\n        return serve_html(\"./index.html\")\n\n    app.start(port=8080)\n\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n\n### Serving simple HTML strings\n\n<Row>\n<Col>\nSpeaking of HTML files, Batman wanted to serve simple HTML strings. He was suggested to use the following code:\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    from robyn import Robyn, Request, html\n\n    app = Robyn(__file__)\n\n\n    @app.get(\"/\")\n    async def h(request: Request):\n        html_string = \"<h1>Hello World</h1>\"\n        return html(html_string)\n\n    app.start(port=8080)\n\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n\n### Serving Other Files\n\n\n<Row>\n<Col>\nNow, that Batman was able to serve HTML files, he wanted to serve other files like CSS, JS, and images. He was suggested to use the following code:\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    from robyn import Robyn, serve_file, Request\n\n    app = Robyn(__file__)\n\n\n    @app.get(\"/\")\n    async def h(request: Request):\n        return serve_file(\"./index.html\", file_name=\"index.html\") # file_name is optional\n\n    app.start(port=8080)\n\n    ```\n\n    </CodeGroup>\n  </Col>\n</Row>\n\n\n### Serving Directories\n\n<Row>\n<Col>\nAfter serving other files, Batman wanted to serve directories, e.g. to serve a React build directory or just a simple HTML/CSS/JS directory. He was suggested to use the following code:\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    from robyn import Robyn, serve_file, Request\n\n    app = Robyn(__file__)\n\n\n    app.serve_directory(\n        route=\"/test_dir\",\n        directory_path=os.path.join(current_file_path, \"build\"),\n        index_file=\"index.html\",\n    )\n\n    app.start(port=8080)\n\n    ```\n\n    </CodeGroup>\n  </Col>\n</Row>\n\n\n## What's next?\n\nNow, Batman was ready to learn about the advanced features of Robyn. He wanted to find a way to handle form data\n\n- [Form Data](/documentation/en/api_reference/form_data)\n\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/api_reference/form_data.mdx",
    "content": "export const description =\n  'On this page, we’ll dive into using the form data.'\n\n## Form Data and Multi Part Form Data\n\nBatman learned how to handle file uploads using Robyn. Now, he wanted to handle the form data.\n\n\n## Handling Multi Part Form Data\n\n<Row>\n<Col>\nBatman uploaded some multipart form data and wanted to handle it using the following code:\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    @app.post(\"/upload\")\n    async def upload(request: Request):\n      form_data = request.form_data\n\n      return form_data\n    ```\n\n    </CodeGroup>\n  </Col>\n</Row>\n\n\n## What's next?\n\nNow, Batman was ready to learn about the advanced features of Robyn. He wanted to find a way to get realtime updates in his dashboard.\n\n- [WebSockets](/documentation/en/api_reference/websockets)\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/api_reference/future-roadmap.mdx",
    "content": "export const description =\n  'On this page, we`ll take a look at the future roadmap for Robyn.'\n\n\n- Add performance optimizations\n- Pydantic Integration\n- Implement Auto Const Requests\n- Add ORM support, especially Prisma integration    \n- Improve Plugin Ecosystem\n- Better Documentation\n- Improve the Websockets\n- Template Support\n- Graphql integration with Strawberry\n- Invest more time in the community around Robyn.\n\n## Next Steps\n\n- [Advanced Features](/documentation/en/api_reference/advanced_features)\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/api_reference/getting_started.mdx",
    "content": "export const description =\n  'On this page, we’ll dive into the different fundamentals of building web applications with Robyn, including examples and best practices.'\n\n\n## Building Your First Robyn Application\n\nRobyn is the fastest Python web framework, combining Python's simplicity with Rust's performance. Whether you're building APIs, web services, or full-stack applications, Robyn makes it easy to get started and scale.\n\n### Quick Start\n\nInstall Robyn and create your first application in minutes:\n\n```bash\npip install robyn\n```\n\n## Understanding Handler Types\n\nRobyn supports both synchronous and asynchronous request handlers, allowing you to choose the best approach for your use case:\n\n- **Synchronous handlers**: Perfect for CPU-bound operations, simple logic, or when you don't need to wait for external resources\n- **Asynchronous handlers**: Ideal for I/O-bound operations like database calls, HTTP requests, file operations, or any task that involves waiting\n\n\n<Row>\n<Col>\n**Synchronous handlers** are perfect for simple operations, calculations, or when you don't need to wait for external resources:\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    from robyn import Robyn, Request\n\n    app = Robyn(__file__)\n\n    @app.get(\"/\")\n    def h(request: Request):\n        return \"Hello, world\"\n\n    app.start(port=8080, host=\"0.0.0.0\") # host is optional, defaults to 127.0.0.1\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n<Row>\n\n  <Col>\n    **Asynchronous handlers** are ideal for database operations, HTTP requests, file I/O, or any operation that involves waiting:\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n      ```python\n      from robyn import Request\n\n      @app.get(\"/\")\n      async def h(request: Request) -> str:\n          return \"Hello, world\"\n\n      ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n### Complete Example: User Management API\n\n<Row>\n  <Col>\n    Here's a comprehensive example that demonstrates both sync and async handlers, proper error handling, and real-world patterns:\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Complete API Example\" tag=\"API\" label=\"/users\">\n    ```python\n    from robyn import Robyn, Request\n    import asyncio\n    import json\n    import time\n    from typing import Dict, Any\n    \n    app = Robyn(__file__)\n    \n    # In-memory storage for demo (use a real database in production)\n    users: Dict[str, Dict[str, Any]] = {\n        \"1\": {\"id\": \"1\", \"name\": \"Alice\", \"email\": \"alice@example.com\", \"created_at\": \"2024-01-01\"},\n        \"2\": {\"id\": \"2\", \"name\": \"Bob\", \"email\": \"bob@example.com\", \"created_at\": \"2024-01-02\"}\n    }\n    \n    # Const route for health checks (cached in Rust for max performance)\n    @app.get(\"/health\", const=True)\n    def health_check():\n        return {\"status\": \"healthy\", \"version\": \"1.0.0\"}\n    \n    # Async handler for database-like operations\n    @app.get(\"/users/:id\")\n    async def get_user(path_params):\n        user_id = path_params[\"id\"]\n        \n        # Simulate async database lookup\n        await asyncio.sleep(0.01)  # Simulate DB query time\n        \n        if user_id in users:\n            return {\"success\": True, \"user\": users[user_id]}\n        else:\n            return {\"success\": False, \"error\": \"User not found\"}, 404\n    \n    # Get all users with pagination\n    @app.get(\"/users\")\n    async def list_users(query_params):\n        page = int(query_params.get(\"page\", \"1\"))\n        limit = int(query_params.get(\"limit\", \"10\"))\n        \n        # Simulate async operation\n        await asyncio.sleep(0.01)\n        \n        user_list = list(users.values())\n        start = (page - 1) * limit\n        end = start + limit\n        \n        return {\n            \"users\": user_list[start:end],\n            \"total\": len(user_list),\n            \"page\": page,\n            \"limit\": limit\n        }\n    \n    # Create new user\n    @app.post(\"/users\")\n    async def create_user(body):\n        try:\n            data = json.loads(body)\n            \n            # Validate required fields\n            if not data.get(\"name\") or not data.get(\"email\"):\n                return {\"success\": False, \"error\": \"Name and email are required\"}, 400\n            \n            # Generate new ID\n            new_id = str(len(users) + 1)\n            new_user = {\n                \"id\": new_id,\n                \"name\": data[\"name\"],\n                \"email\": data[\"email\"],\n                \"created_at\": time.strftime(\"%Y-%m-%d\")\n            }\n            \n            # Simulate async database save\n            await asyncio.sleep(0.02)\n            users[new_id] = new_user\n            \n            return {\"success\": True, \"user\": new_user}, 201\n            \n        except json.JSONDecodeError:\n            return {\"success\": False, \"error\": \"Invalid JSON\"}, 400\n    \n    # Sync handler for CPU-intensive operations\n    @app.post(\"/calculate\")\n    def calculate_fibonacci(body):\n        try:\n            data = json.loads(body)\n            n = data.get(\"number\", 10)\n            \n            if n < 0 or n > 35:  # Prevent excessive computation\n                return {\"error\": \"Number must be between 0 and 35\"}, 400\n            \n            # CPU-intensive calculation (runs in thread pool)\n            def fib(x):\n                if x <= 1:\n                    return x\n                return fib(x-1) + fib(x-2)\n            \n            start_time = time.time()\n            result = fib(n)\n            calc_time = time.time() - start_time\n            \n            return {\n                \"input\": n,\n                \"result\": result,\n                \"calculation_time\": f\"{calc_time:.4f}s\"\n            }\n            \n        except json.JSONDecodeError:\n            return {\"error\": \"Invalid JSON\"}, 400\n    \n    if __name__ == \"__main__\":\n        app.start(port=8080)\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n### Testing Your API\n\n<Row>\n  <Col>\n    Once your server is running, you can test these endpoints using curl or any HTTP client:\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"API Testing\" tag=\"CURL\" label=\"Testing\">\n    ```bash\n    # Test health endpoint\n    curl http://localhost:8080/health\n    \n    # Get a user\n    curl http://localhost:8080/users/1\n    \n    # List users with pagination\n    curl \"http://localhost:8080/users?page=1&limit=5\"\n    \n    # Create a new user\n    curl -X POST http://localhost:8080/users \\\n      -H \"Content-Type: application/json\" \\\n      -d '{\"name\": \"Charlie\", \"email\": \"charlie@example.com\"}'\n    \n    # Calculate fibonacci\n    curl -X POST http://localhost:8080/calculate \\\n      -H \"Content-Type: application/json\" \\\n      -d '{\"number\": 20}'\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n--- \n\n## Running Your Application\n\nRobyn applications can be run in several ways, each optimized for different scenarios. Here are the most common approaches:\n\n\n<Row>\n  <Col>\n    **Direct execution** - Run your application file directly with various optimization flags:\n    \n    - `--dev`: Development mode with auto-reload\n    - `--fast`: Optimized settings for production\n    - `--processes N`: Scale across multiple CPU cores\n    - `--workers N`: Multiple workers per process\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n      ```bash\n      usage: app.py [-h] [--processes PROCESSES] [--workers WORKERS] [--log-level LOG_LEVEL] [--create] [--docs] [--open-browser] [--version]\n\n      Robyn, a fast async web framework with a rust runtime.\n\n      options:\n        -h, --help            show this help message and exit\n        --processes PROCESSES\n                              Choose the number of processes. [Default: 1]\n        --workers WORKERS     Choose the number of workers. [Default: 1]\n        --dev                 Development mode. It restarts the server based on file changes.\n        --log-level LOG_LEVEL\n                              Set the log level name\n        --create              Create a new project template.\n        --docs                Open the Robyn documentation.\n        --open-browser        Open the browser on successful start.\n        --version             Show the Robyn version.\n        --compile-rust-path COMPILE_RUST_PATH\n                              Compile rust files in the given path.\n        --create-rust-file CREATE_RUST_FILE\n                              Create a rust file with the given name.\n        --disable-openapi     Disable the OpenAPI documentation.\n        --fast                Fast mode. It sets the optimal values for processes, workers and log level. However, you can override them.\n      ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n### Common Run Configurations\n\n<Row>\n  <Col>\n    **Development Mode**: Best for local development with automatic reloading when files change.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Development\" tag=\"DEV\">\n    ```bash\n    # Basic development mode\n    python app.py --dev\n    \n    # Development with custom port\n    python app.py --dev --port 3000\n    \n    # Development with debug logging\n    python app.py --dev --log-level DEBUG\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n<Row>\n  <Col>\n    **Production Mode**: Optimized for performance with multiple processes and workers.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Production\" tag=\"PROD\">\n    ```bash\n    # Fast mode (automatic optimization)\n    python app.py --fast\n    \n    # Custom scaling configuration\n    python app.py --processes 4 --workers 2\n    \n    # Production with specific log level\n    python app.py --fast --log-level INFO\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n<Row />\n\n<Row>\n  <Col>\n    **Module execution** - Use Robyn's CLI module for additional features and consistent behavior across environments:\n  </Col>\n\n  <Col sticky>\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n      ```bash\n\n      usage: python -m robyn app.py [-h] [--processes PROCESSES] [--workers WORKERS] [--dev] [--log-level LOG_LEVEL] [--create] [--docs] [--open-browser] [--version]\n\n\n      Robyn, a fast async web framework with a rust runtime.\n\n      options:\n        -h, --help            show this help message and exit\n        --processes PROCESSES\n                              Choose the number of processes. [Default: 1]\n        --workers WORKERS     Choose the number of workers. [Default: 1]\n        --dev                 Development mode. It restarts the server based on file changes.\n        --log-level LOG_LEVEL\n                              Set the log level name\n        --create              Create a new project template.\n        --docs                Open the Robyn documentation.\n        --open-browser        Open the browser on successful start.\n        --version             Show the Robyn version.\n        --compile-rust-path COMPILE_RUST_PATH\n                              Compile rust files in the given path.\n        --create-rust-file CREATE_RUST_FILE\n                              Create a rust file with the given name.\n        --disable-openapi     Disable the OpenAPI documentation.\n        --fast                Fast mode. It sets the optimal values for processes, workers and log level. However, you can override them.\n      ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n\n\n\n---\n\n## Handling Different HTTP Methods\n\nRobyn supports all standard HTTP methods. Here's how to create a complete RESTful API with proper request handling:\n\n<Row>\n<Col>\n**Complete REST API Example**: Here's a practical example showing all HTTP methods for a blog post API:\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"REST API\" tag=\"CRUD\" label=\"/posts\">\n\n    ```python\n    from robyn import Robyn, Request\n    \n    app = Robyn(__file__)\n    \n    # In-memory storage for demo\n    posts = {\n        \"1\": {\"id\": \"1\", \"title\": \"First Post\", \"content\": \"Hello World\"},\n        \"2\": {\"id\": \"2\", \"title\": \"Second Post\", \"content\": \"Learning Robyn\"}\n    }\n    \n    # GET - Retrieve all posts\n    @app.get(\"/posts\")\n    def get_posts(query_params):\n        limit = int(query_params.get(\"limit\", \"10\"))\n        posts_list = list(posts.values())[:limit]\n        return {\"posts\": posts_list, \"total\": len(posts)}\n    \n    # GET - Retrieve specific post\n    @app.get(\"/posts/:id\")\n    def get_post(path_params):\n        post_id = path_params[\"id\"]\n        if post_id in posts:\n            return {\"post\": posts[post_id]}\n        return {\"error\": \"Post not found\"}, 404\n    \n    # POST - Create new post\n    @app.post(\"/posts\")\n    def create_post(request: Request):\n        data = request.json()\n        post_id = str(len(posts) + 1)\n        new_post = {\n            \"id\": post_id,\n            \"title\": data.get(\"title\", \"\"),\n            \"content\": data.get(\"content\", \"\")\n        }\n        posts[post_id] = new_post\n        return {\"message\": \"Post created\", \"post\": new_post}, 201\n    \n    # PUT - Update entire post\n    @app.put(\"/posts/:id\")\n    def update_post(request: Request, path_params):\n        post_id = path_params[\"id\"]\n        if post_id not in posts:\n            return {\"error\": \"Post not found\"}, 404\n        \n        data = request.json()\n        posts[post_id] = {\n            \"id\": post_id,\n            \"title\": data.get(\"title\", \"\"),\n            \"content\": data.get(\"content\", \"\")\n        }\n        return {\"message\": \"Post updated\", \"post\": posts[post_id]}\n    \n    # PATCH - Partial update\n    @app.patch(\"/posts/:id\")\n    def patch_post(request: Request, path_params):\n        post_id = path_params[\"id\"]\n        if post_id not in posts:\n            return {\"error\": \"Post not found\"}, 404\n        \n        data = request.json()\n        post = posts[post_id]\n        \n        # Update only provided fields\n        if \"title\" in data:\n            post[\"title\"] = data[\"title\"]\n        if \"content\" in data:\n            post[\"content\"] = data[\"content\"]\n        \n        return {\"message\": \"Post updated\", \"post\": post}\n    \n    # DELETE - Remove post\n    @app.delete(\"/posts/:id\")\n    def delete_post(path_params):\n        post_id = path_params[\"id\"]\n        if post_id not in posts:\n            return {\"error\": \"Post not found\"}, 404\n        \n        deleted_post = posts.pop(post_id)\n        return {\"message\": \"Post deleted\", \"post\": deleted_post}\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n---\n\n\n## Working with JSON and Response Formats\n\nRobyn automatically handles JSON serialization, but also provides flexible response formatting options for different use cases.\n\n<Row>\n<Col>\n  **Automatic JSON Handling**: Robyn automatically converts Python dictionaries and lists to JSON responses with the correct Content-Type headers.\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"JSON Responses\" tag=\"JSON\" label=\"/api\">\n\n    ```python\n    from robyn import Robyn, Request\n    from datetime import datetime\n    \n    app = Robyn(__file__)\n\n    # Simple JSON response - automatic serialization\n    @app.get(\"/api/status\")\n    def get_status():\n        return {\n            \"status\": \"active\",\n            \"timestamp\": datetime.now().isoformat(),\n            \"version\": \"1.0.0\"\n        }\n\n    # Complex nested JSON\n    @app.get(\"/api/user/:id\")\n    def get_user_profile(path_params):\n        user_id = path_params[\"id\"]\n        return {\n            \"user\": {\n                \"id\": user_id,\n                \"profile\": {\n                    \"name\": \"John Doe\",\n                    \"email\": \"john@example.com\",\n                    \"preferences\": {\n                        \"theme\": \"dark\",\n                        \"notifications\": True\n                    }\n                },\n                \"activity\": [\n                    {\"action\": \"login\", \"timestamp\": \"2024-01-15T10:30:00Z\"},\n                    {\"action\": \"view_post\", \"timestamp\": \"2024-01-15T10:35:00Z\"}\n                ]\n            }\n        }\n\n    # List/array responses\n    @app.get(\"/api/posts\")\n    def get_posts():\n        return [\n            {\"id\": 1, \"title\": \"First Post\", \"published\": True},\n            {\"id\": 2, \"title\": \"Draft Post\", \"published\": False},\n            {\"id\": 3, \"title\": \"Latest Post\", \"published\": True}\n        ]\n\n    # Custom status codes with JSON\n    @app.post(\"/api/posts\")\n    def create_post(request: Request):\n        try:\n            data = request.json()\n            # Validate required fields\n            if not data.get(\"title\"):\n                return {\"error\": \"Title is required\"}, 400\n            \n            # Success response\n            return {\n                \"message\": \"Post created successfully\",\n                \"post\": {\n                    \"id\": 123,\n                    \"title\": data[\"title\"],\n                    \"created_at\": datetime.now().isoformat()\n                }\n            }, 201\n        except ValueError:\n            return {\"error\": \"Invalid JSON format\"}, 400\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n\n## Parameter Injection and Route Handling\n\nRobyn provides powerful parameter injection that automatically extracts and injects request components into your handler functions. This eliminates boilerplate code and makes handlers cleaner and more focused.\n\n<Row>\n  <Col>\n\n  **Path Parameters**: Extract dynamic segments from URLs using colon syntax (`:param`)\n  \n  **Type-Safe Injection**: Use type annotations for automatic parameter injection with IDE support\n  \n\n  </Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Path Parameters\" tag=\"GET\" label=\"/users/:id\">\n\n    ```python {{ title: 'Type-based injection' }}\n    from robyn import Request\n    from robyn.types import PathParams\n\n    @app.get(\"/users/:id/posts/:post_id\")\n    async def get_user_post(request: Request, path_params: PathParams):\n        user_id = path_params[\"id\"]\n        post_id = path_params[\"post_id\"]\n        \n        # Validate parameters\n        if not user_id.isdigit():\n            return {\"error\": \"Invalid user ID\"}, 400\n        \n        return {\n            \"user_id\": int(user_id),\n            \"post_id\": post_id,\n            \"url\": request.url.path\n        }\n    ```\n\n    ```python {{ title: 'Name-based injection' }}\n    @app.get(\"/users/:id/posts/:post_id\")\n    async def get_user_post(request, path_params):\n        user_id = path_params[\"id\"]\n        post_id = path_params[\"post_id\"]\n        \n        return {\n            \"user_id\": user_id,\n            \"post_id\": post_id,\n            \"method\": request.method\n        }\n    ```\n\n    </CodeGroup>\n\n\n  </Col>\n</Row>\n\n\n<Row>\n  <Col>\n\n  **Query Parameters**: Access URL query strings with automatic parsing and type conversion helpers\n  \n\n  </Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Query Parameters\" tag=\"GET\" label=\"/search?q=python&page=1\">\n\n    ```python {{ title: 'Advanced query handling' }}\n    from robyn import Request\n    from robyn.robyn import QueryParams\n\n    @app.get(\"/search\")\n    async def search_products(request: Request, query_params: QueryParams):\n        # Get search parameters with defaults\n        query = query_params.get(\"q\", \"\")\n        page = int(query_params.get(\"page\", \"1\"))\n        limit = min(int(query_params.get(\"limit\", \"10\")), 100)  # Cap at 100\n        \n        # Boolean parameter\n        include_sold = query_params.get(\"include_sold\", \"false\").lower() == \"true\"\n        \n        # Array parameters (?tags=python&tags=web)\n        tags = query_params.get_list(\"tags\") or []\n        \n        # Build response\n        return {\n            \"search_query\": query,\n            \"pagination\": {\"page\": page, \"limit\": limit},\n            \"filters\": {\"include_sold\": include_sold, \"tags\": tags},\n            \"total_params\": len(query_params.to_dict())\n        }\n    ```\n\n    ```python {{ title: 'Simple query access' }}\n    @app.get(\"/search\")\n    async def search_simple(query_params):\n        # Basic parameter access\n        query = query_params.get(\"q\", \"\")\n        page = query_params.get(\"page\", \"1\")\n        \n        # Convert to dictionary for processing\n        all_params = query_params.to_dict()\n        \n        return {\n            \"query\": query,\n            \"page\": page,\n            \"all_params\": all_params\n        }\n    ```\n\n    </CodeGroup>\n  </Col>\n</Row>\n\n<Row>\n  <Col>\n\n  Any request param can be used in the handler function either using type annotations or using the reserved names.\n  \n  <b>\n Do note that the type annotations will take precedence over the reserved names.\n </b>\n  \n  Robyn showed Batman example syntaxes of accessing the request params:\n\n  </Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/split_request_params\">\n\n    ```python\n    from robyn.robyn import QueryParams, Headers\n    from robyn.types import PathParams, RequestMethod, RequestBody, RequestURL\n    \n    @app.get(\"/untyped/query_params\")\n    def untyped_basic(query_params):\n        return query_params.to_dict()\n    \n    \n    @app.get(\"/typed/query_params\")\n    def typed_basic(query_data: QueryParams):\n        return query_data.to_dict()\n    \n    \n    @app.get(\"/untyped/path_params/:id\")\n    def untyped_path_params(query_params: PathParams):\n        return query_params  # contains the path params since the type annotations takes precedence over the reserved names\n    \n    \n    @app.post(\"/typed_untyped/combined\")\n    def typed_untyped_combined(\n            query_params,\n            method_data: RequestMethod,\n            body_data: RequestBody,\n            url: RequestURL,\n            headers_item: Headers,\n    ):\n        return {\n            \"body\": body_data,\n            \"query_params\": query_params.to_dict(),\n            \"method\": method_data,\n            \"url\": url.path,\n            \"headers\": headers_item.get(\"server\"),\n        }\n    ```\n\n    </CodeGroup>\n  </Col>\n</Row>\n\nType Aliases: `Request`, `QueryParams`, `Headers`, `PathParams`, `RequestBody`, `RequestMethod`, `RequestURL`, `FormData`, `RequestFiles`, `RequestIP`, `RequestIdentity`\n\nReserved Names: `r`, `req`, `request`, `query_params`, `headers`, `path_params`, `body`, `method`, `url`, `ip_addr`, `identity`, `form_data`, `files`\n\n---\n\nAs Batman continued to develop his web application with Robyn, he explored more features and implemented them using code samples.\n\n## Customizing Response Formats and Headers\n\n\nAfter understanding the dynamic nature of Robyn, Batman, now wanted the ability to customize response formats and headers. Robyn showed him how to do this using dictionaries and Robyn's Response object.\n\n### Using Dictionaries\n\n<Row>\n<Col>\nBatman learned to customize response formats by returning dictionaries or using Robyn's Response object. He could also set status codes and headers for each response. For example, Batman created a response with a dictionary like this:\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    from robyn import Request\n\n    @app.post(\"/dictionary\")\n    async def dictionary(request: Request):\n        return {\n            \"status_code\": 200,\n            \"description\": \"This is a regular response\",\n            \"type\": \"text\",\n            \"headers\": {\"Header\": \"header_value\"},\n        }\n\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n\n### Using the Response object\n<Row>\n<Col>\nTo use the Response object, he wrote:\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    from robyn.robyn import Response, Request\n\n    @app.get(\"/response\")\n    async def response(request: Request):\n        return Response(status_code=200, headers=Headers({}), description=\"OK\")\n\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n### Returning a Binary Output\n\n<Row>\n<Col>\nBatman then wanted to return a binary output from his application. He could do this by setting the type of the response to \"binary\" and returning a bytes object. For example, he wrote:\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    from robyn import Request, Response\n\n    @app.get(\"/binary_output_response_sync\")\n    def binary_output_response_sync(request: Request):\n        return Response(\n            status_code=200,\n            headers={\"Content-Type\": \"application/octet-stream\"},\n            description=\"OK\",\n        )\n\n\n    @app.get(\"/binary_output_async\")\n    async def binary_output_async(request: Request):\n        return b\"OK\"\n\n\n    @app.get(\"/binary_output_response_async\")\n    async def binary_output_response_async(request: Request):\n        return Response(\n            status_code=200,\n            headers={\"Content-Type\": \"application/octet-stream\"},\n            description=\"OK\",\n        )\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n\n\n---\n\n## Response Headers\n\nBatman, being the world's greatest detective, spotted the `headers` field in the `Response` object. He, naturally wanted to know more about it. Robyn explained that he could use the `headers` field to set response headers. For example, he could set the `Content-Type` header to `application/json` by writing:\n\n### Local Response Headers\n\n<Row>\n<Col>\nEither, by using the `headers` field in the `Response` object:\n</Col>\n<Col sticky>\n\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    from robyn import Request\n\n    @app.get(\"/\")\n    def binary_output_response_sync(request: Request):\n        return Response(\n            status_code=200,\n            headers={\"Content-Type\": \"application/octet-stream\"},\n            description=\"OK\",\n        )\n\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n### Global Response Headers\n\n<Row>\n\n<Col>Or setting the Headers globally *per* router.</Col>\n\n  <Col>\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    app.add_response_header(\"content-type\", \"application/json\")\n    ```\n    </CodeGroup>\n\n  </Col>\n\n<Col>\n`add_response_header` appends the header to the list of headers, while `set_response_header` replaces the header if it exists.\n</Col>\n<Col>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    app.set_response_header(\"content-type\", \"application/json\")\n    ```\n    </CodeGroup>\n\n  </Col>\n  \n<Col>\nTo prevent the headers from getting applied to certain endpoints, you can use the `exclude_response_headers_for` function.\n</Col>\n<Col>\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    app.exclude_response_headers_for([\"/login\", \"/signup\"])\n    ```\n    </CodeGroup>\n\n</Col>\n</Row>\n\n### Cookies\n\n<Row>\n<Col>\nRobyn provides a complete cookie API following RFC 6265. Set cookies using the `set_cookie` method on the Response object.\n</Col>\n<Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    from robyn import Request, Response, Headers\n\n    @app.get(\"/\")\n    def set_session(request: Request):\n        response = Response(200, Headers({}), \"Welcome!\")\n        response.set_cookie(key=\"session\", value=\"abc123\")\n        return response\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n#### Cookie Attributes\n\n<Row>\n<Col>\nYou can set additional cookie attributes for security and control:\n\n- **path**: Cookie path (default: \"/\")\n- **domain**: Cookie domain\n- **max_age**: Cookie lifetime in seconds\n- **secure**: Only send over HTTPS\n- **http_only**: Not accessible via JavaScript\n- **same_site**: CSRF protection (\"Strict\", \"Lax\", or \"None\" - case insensitive)\n</Col>\n<Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/secure-cookie\">\n\n    ```python\n    from robyn import Request, Response, Headers\n\n    @app.get(\"/login\")\n    def login(request: Request):\n        response = Response(200, Headers({}), \"Logged in\")\n        response.set_cookie(\n            key=\"auth_token\",\n            value=\"secret123\",\n            path=\"/\",\n            max_age=3600,        # 1 hour\n            secure=True,         # HTTPS only\n            http_only=True,      # No JavaScript access\n            same_site=\"Strict\",  # CSRF protection\n        )\n        return response\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n#### Deleting Cookies\n\n<Row>\n<Col>\nTo delete a cookie from the browser, use the `delete` method on the cookies collection. This sets `max_age=0` which tells the browser to remove the cookie.\n</Col>\n<Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/logout\">\n\n    ```python\n    from robyn import Request, Response, Headers\n\n    @app.get(\"/logout\")\n    def logout(request: Request):\n        response = Response(200, Headers({}), \"Logged out\")\n        response.cookies.delete(\"auth_token\")\n        return response\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n#### Accessing Cookies\n\n<Row>\n<Col>\nYou can iterate over cookies or access them by name:\n</Col>\n<Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/cookies\">\n\n    ```python\n    from robyn import Request, Response, Headers\n\n    @app.get(\"/debug\")\n    def debug_cookies(request: Request):\n        response = Response(200, Headers({}), \"Cookies set\")\n        response.set_cookie(\"a\", \"1\")\n        response.set_cookie(\"b\", \"2\")\n        \n        # Get all cookie names\n        names = response.cookies.keys()\n        \n        # Iterate over cookies\n        for name in response.cookies:\n            print(f\"Cookie: {name}\")\n        \n        # Check if cookie exists\n        if \"a\" in response.cookies:\n            print(\"Cookie 'a' exists\")\n            \n        return response\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n\n## Request Headers\n\nBatman, now wanted to know how to read request headers. Robyn explained that he could use the `request.headers` field to read request headers. For example, he could read the `Content-Type` header by writing:\n\n### Local Request Headers\n\n<Row>\n<Col>\nEither, by using the `headers` field in the `Request` object:\n</Col>\n<Col sticky>\n\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    from robyn import Request\n\n    @app.get(\"/\")\n    def binary_output_response_sync(request: Request):\n      headers = request.headers\n\n      print(\"These are the request headers: \", headers)\n      existing_header = headers.get(\"exisiting_header\")\n      existing_header = headers.get(\"exisiting_header\", \"default_value\")\n      exisiting_header = headers[\"exisiting_header\"] # This syntax is also valid\n\n      headers.set(\"modified\", \"modified_value\")\n      headers[\"new_header\"] = \"new_value\" # This syntax is also valid\n\n      print(\"These are the modified request headers: \", headers)\n      \n      return \"\"\n\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n<Row>\n<Col>\nOr by using the global Request Headers:\n</Col>\n<Col sticky>\n\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    app.add_request_header(\"server\", \"robyn\")\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n<Row>\n<Col>\n`add_request_header` appends the header to the list of headers, while `set_request_header` replaces the header if it exists.\n</Col>\n<Col sticky>\n\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    app.set_request_header(\"server\", \"robyn\")\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n\n---\n\n## Status Codes\n\n<Row>\n<Col>\nAfter learning about response formats and headers, Batman learned to set status codes for his responses. \n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    from robyn import status_codes, Request\n\n\n    @app.get(\"/response\")\n    async def response(request: Request):\n        return Response(status_code=status_codes.HTTP_200_OK, headers=Headers({}), description=\"OK\")\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n\n---\n\n## What's next?\n\nGreat, now Robyn, what is the `Request` Object that you keep talking about?, Batman said. \"Next section\", said Robyn.\n\n- [The Request Object](/documentation/en/api_reference/request_object)\n\nBatman was also interested to know about the architecture of Robyn. \"Next section\", said Robyn.\n\n- [Architecture](/documentation/en/architecture)\n\n\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/api_reference/graphql-support.mdx",
    "content": "export const description =\n  'On this page, we`ll understand how GraphQL support is provided in the existing Robyn codebase to ensure faster data fetching in modern webapps .'\n\n## GraphQL Support [(With Strawberry 🍓)](https://strawberry.rocks/)\n\nThis is in a very early stage right now. We will have a much more stable version when we have a stable API for Views and View Controllers.\n\n## Step 1: Creating a virtualenv\n\nTo ensure that there are isolated dependencies, we will use virtual environments.\n\n  <CodeGroup title=\"Virtual Environment\">\n\n```bash {{ title: 'pip' }}\npython3 -m venv venv\n```\n\n  </CodeGroup>\n\n## Step 2: Activate the virtualenv and install Robyn\n\n  <CodeGroup title=\"Activating the virtualenv\">\n\n```bash {{ title: 'pip' }}\nsource venv/bin/activate\n```\n\n  </CodeGroup>\n\n  <CodeGroup title=\"Installing Robyn and Strawberry\">\n\n```bash {{ title: 'pip' }}\npip install robyn strawberry-graphql\n```\n\n  </CodeGroup>\n\n## Step 3: Coding the App\n\n  <CodeGroup title=\"Code\">\n\n```python {{ title: 'python' }}\nfrom typing import List, Optional\nfrom robyn import Robyn, jsonify\nimport json\n\nimport dataclasses\nimport strawberry\nimport strawberry.utils.graphiql\n\n\n@strawberry.type\nclass User:\n  name: str\n\n\n@strawberry.type\nclass Query:\n  @strawberry.field\n  def user(self) -> Optional[User]:\n      return User(name=\"Hello\")\n\n\nschema = strawberry.Schema(Query)\n\napp = Robyn(__file__)\n\n\n@app.get(\"/\", const=True)\nasync def get():\n  return strawberry.utils.graphiql.get_graphiql_html()\n\n\n@app.post(\"/\")\nasync def post(request):\n  body = request.json()\n  query = body[\"query\"]\n  variables = body.get(\"variables\", None)\n  context_value = {\"request\": request}\n  root_value = body.get(\"root_value\", None)\n  operation_name = body.get(\"operation_name\", None)\n\n  data = await schema.execute(\n      query,\n      variables,\n      context_value,\n      root_value,\n      operation_name,\n  )\n\n  return jsonify(\n      {\n          \"data\": (data.data),\n          **({\"errors\": data.errors} if data.errors else {}),\n          **({\"extensions\": data.extensions} if data.extensions else {}),\n      }\n  )\n\n\nif __name__ == \"__main__\":\n  app.start(port=8080, host=\"0.0.0.0\")\n```\n\n  </CodeGroup>\n\nLet us try to decipher the usage line by line.\n\n<Row>\n<Col>\nThese statements just import the dependencies.\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Section 1\">\n\n    ```python {{ title: 'python' }}\n    from typing import List, Optional\n\n    from robyn import Robyn, jsonify\n    import json\n\n    import dataclasses\n    import strawberry\n    import strawberry.utils.graphiql\n\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n\n<Row>\n<Col>\nHere, we are creating a base `User` type with a `name` property.\n\nWe are then creating a GraphQl `Query` that returns the `User`.\n\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Section 2\">\n\n    ```python {{ title: 'python' }}\n    @strawberry.type\n    class User:\n        name: str\n\n\n    @strawberry.type\n    class Query:\n        @strawberry.field\n        def user(self) -> Optional[User]:\n            return User(name=\"Hello\")\n\n\n    schema = strawberry.Schema(Query)\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n\n<Row>\n<Col>\nNow, we are initializing a Robyn app. For us, to serve a GraphQl app, we need to have a `get` route to return the `GraphiQL(ide)` and then a post route to process the `GraphQl` request.\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Section 3\">\n\n    ```python {{ title: 'python' }}\n    app = Robyn(__file__)\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n\n<Row>\n<Col>\nWe are populating the html page with the GraphiQL IDE using `strawberry`. We are using `const=True` to precompute this population. Essentially, making it very fast and bypassing the execution overhead in this get request.\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Section 4\">\n\n    ```python {{ title: 'python' }}\n      @app.get(\"/\", const=True)\n      async def get():\n      return strawberry.utils.graphiql.get_graphiql_html()\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n\n<Row>\n<Col>\nFinally, we are getting params(body, query, variables, context_value, root_value, operation_name) from the `request` object.\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Section 5\">\n\n    ```python {{ title: 'python' }}\n    @app.post(\"/\")\n    async def post(request):\n    body = request.json()\n    query = body[\"query\"]\n    variables = body.get(\"variables\", None)\n    context_value = {\"request\": request}\n    root_value = body.get(\"root_value\", None)\n    operation_name = body.get(\"operation_name\", None)\n\n    data = await schema.execute(\n        query,\n        variables,\n        context_value,\n        root_value,\n        operation_name,\n    )\n\n    return jsonify(\n        {\n            \"data\": (data.data),\n            **({\"errors\": data.errors} if data.errors else {}),\n            **({\"extensions\": data.extensions} if data.extensions else {}),\n        }\n    )\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\nThe above is the example for just one route. You can do the same for as many as you like. :)\n\n\n## What's next?\n\nThat's all folks. :D Keep an eye out for more updates on this page. We will be adding more examples and documentation as we go along.\n\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/api_reference/index.mdx",
    "content": "export const description =\n  'On this page, we’ll dive into the different conversation endpoints you can use to manage conversations programmatically.'\n\nOnce upon a time in the city of Gotham, there was a powerful superhero named Robyn. Robyn had a unique set of abilities that allowed it to fetch information from the far corners of the internet. It could send requests and receive responses at lightning speed, and its prowess was admired by developers everywhere.\n\nOne day, Batman approached Robyn for help with building a web application. Batman had heard about Robyn's powerful features and wanted to harness them to create a remarkable application. Batman was looking for an ally and in Robyn, he found the best one!\n\n\n## Installing Robyn\n\n\nRobyn is a Python library that you can install using `pip` or `conda`\n\n  <CodeGroup title=\"installation\">\n\n  ```bash {{ title: 'pip' }}\n  pip install robyn\n  ```\n\n  ```bash {{ title: 'conda' }}\n  conda install robyn -c conda-forge\n  ```\n  </CodeGroup>\n\nWhile there are other more extensions of Robyn like\n\n  <CodeGroup title=\"installation\">\n\n  ```bash {{ title: 'pip' }}\n  pip install \"robyn[templating]\"\n  ```\n\n  ```bash {{ title: 'conda' }}\n  conda install \"robyn[templating]\" -c conda-forge\n  ```\n  </CodeGroup>\n\n\n\nIt is recommended to install the base package first and then install the extensions as needed.\n\n## What's next?\n\nNow, we can start using Robyn to build our application.\n\n\n- [Getting Started](/documentation/en/api_reference/getting_started)\n\n\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/api_reference/mcps.mdx",
    "content": "# Model Context Protocol (MCP)\n\n> **⚠️ Experimental**: MCP support is experimental and may change.\n\nRobyn supports MCP, allowing AI applications like Claude Desktop to connect to your application's resources and tools.\n\n## Quick Start\n\n```python\nfrom robyn import Robyn\n\napp = Robyn(__file__)\n\n@app.mcp.resource(\"echo://{message}\")\ndef echo_resource(message: str) -> str:\n    return f\"Resource echo: {message}\"\n\n@app.mcp.tool()\ndef echo_tool(message: str) -> str:\n    return f\"Tool echo: {message}\"\n\n@app.mcp.prompt()\ndef echo_prompt(message: str) -> str:\n    return f\"Please process: {message}\"\n\napp.start()\n```\n\n## Features\n\n- Auto-generated JSON schemas from function signatures\n- URI templates with parameter extraction\n- JSON-RPC 2.0 protocol compliance\n- Type-aware parameter handling\n\n## Decorator Reference\n\n### `@app.mcp.resource(uri, name=None, description=None, mime_type=None)`\n\nRegister a resource that can be read by clients.\n\n```python\n@app.mcp.resource(\"user://{user_id}/profile\")\ndef user_profile(user_id: str) -> str:\n    return f\"Profile for user {user_id}\"\n```\n\n### `@app.mcp.tool(name=None, description=None, input_schema=None)`\n\nRegister a tool that can be executed by AI models.\n\n```python\n@app.mcp.tool()\ndef greet(name: str, formal: bool = False) -> str:\n    if formal:\n        return f\"Good day, {name}.\"\n    return f\"Hi {name}!\"\n```\n\n### `@app.mcp.prompt(name=None, description=None, arguments=None)`\n\nRegister a prompt template for AI workflows.\n\n```python\n@app.mcp.prompt()\ndef code_review(code: str, language: str = \"python\") -> str:\n    return f\"Please review this {language} code: {code}\"\n```\n\n## Type Support\n\nSupported types: `str`, `int`, `float`, `bool`, `List`, `Dict`\n\n## URI Templates\n\nExtract parameters from URIs:\n\n```python\n@app.mcp.resource(\"user://{user_id}/posts/{post_id}\")\ndef get_user_post(user_id: str, post_id: str) -> str:\n    return f\"Post {post_id} from user {user_id}\"\n```\n\n## Client Usage\n\nTest with curl:\n\n```bash\n# List resources\ncurl -X POST http://localhost:8080/mcp \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"jsonrpc\": \"2.0\", \"id\": 1, \"method\": \"resources/list\", \"params\": {}}'\n```\n\n## Integration with Claude Desktop\n\nTo connect your Robyn MCP server to Claude Desktop:\n\n1. Start your Robyn app with MCP endpoints\n2. Configure Claude Desktop to connect to `http://localhost:8080/mcp`\n3. Use the registered resources, tools, and prompts in your conversations\n\n## Error Handling\n\n```python\n@app.mcp.tool()\ndef divide(a: float, b: float) -> str:\n    if b == 0:\n        raise ValueError(\"Division by zero\")\n    return str(a / b)\n```\n\n## Testing\n\n```bash\n# Unit tests\npython examples/mcp.py test-unit\n\n# Live tests\npython examples/mcp.py test-live\n\n# All tests\npython examples/mcp.py test-all\n```\n\n## Configuration\n\nMCP runs at `/mcp` endpoint using JSON-RPC 2.0 over HTTP. No additional setup required.\n\n## Claude Desktop Integration\n\n1. Start your Robyn server\n2. Configure Claude Desktop to connect to `http://localhost:8080/mcp`\n3. Use resources, tools, and prompts in conversations\n\n\n\nSee `examples/mcp.py` for a complete example.\n\nFor more information: https://modelcontextprotocol.io/"
  },
  {
    "path": "docs_src/src/pages/documentation/en/api_reference/middlewares.mdx",
    "content": "export const description =\n  'On this page, we’ll dive into the different conversation endpoints you can use to manage conversations programmatically.'\n\n## Working with Middlewares and Events\n\nAs Batman's application grew more complex, Robyn taught him about middlewares, startup and shutdown events, and even working with WebSockets. Batman learned how to create functions that could execute before or after a request, manage the application's life cycle, and handle real-time communication with clients using WebSockets.\n\n\n## Handling Events\n\nBatman discovered that he could add startup and shutdown events to manage his application's life cycle. He added the following code to define these events:\n\n<Row>\n<Col>\nBatman was excited to learn that he could add events as functions as well as decorators.\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    async def startup_handler():\n      print(\"Starting up\")\n\n    app.startup_handler(startup_handler)\n\n    @app.shutdown_handler\n    def shutdown_handler():\n        print(\"Shutting down\")\n\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n<Row>\n\n  <Col>\n    For an asynchronous request, Batman used:\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n      ```python\n      from robyn import Request \n\n      @app.get(\"/\")\n      async def h(request: Request) -> str:\n          return \"Hello, world\"\n\n      ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n\n\n\n## Handling Middlewares {{ tag: 'POST', label: '/http_requests' }}\n\n<Row>\n  <Col>\n\n  Batman learned to use both sync and async functions for middlewares. He wrote the following code to add a middleware that would execute before and after each request.\n  A before request middleware is a function that executes before each request. It can modify the request object or perform any other operation before the request is processed.\n  An after request middleware is a function that executes after each request. It can modify the response object or perform any other operation after the request is processed.\n\n  Every before request middleware should accept a request object and return a request object. Every after-request middleware should accept a response object and return a response object on happy case scenario. After-request middlewares can also optionally accept the request object as the first parameter to access request data.\n\n  The execution of the before request middleware is stopped if any of the before request middleware returns a response object. The response object is returned to the client without executing the after request middleware or the main entry point code.\n\n\n  \n\n  </Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"POST\" label=\"/http_requests\">\n\n    ```python\n    from robyn import Request, Response\n\n    @app.before_request(\"/\")\n    async def hello_before_request(request: Request):\n        request.headers.set(\"before\", \"sync_before_request\")\n        return request\n\n    @app.after_request(\"/\")\n    def hello_after_request(response: Response):\n        response.headers.set(\"after\", \"sync_after_request\")\n        return response\n    ```\n\n    ```python {{ title: 'after_request with request access' }}\n    from robyn import Request, Response\n\n    @app.after_request(\"/\")\n    def hello_after_request(request: Request, response: Response):\n        # Access request data in after_request\n        response.headers.set(\"request_path\", request.url.path)\n        response.headers.set(\"after\", \"sync_after_request\")\n        return response\n    ```\n\n\n    </CodeGroup>\n\n\n  </Col>\n</Row>\n\n---\n\n\n## What's next?\n\nRobyn - Great, you're now familiar with the certain advanced concepts of Robyn.\n\nBatman - \"Authentication! I want to learn about authentication. I want to make sure that only the right people can access my application.\"\n\nRobyn - Yes, Authentication!\n\n\n- [Authentication](/documentation/en/api_reference/authentication)\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/api_reference/multiprocess_execution.mdx",
    "content": "export const description =\n  'On this page, we’ll dive into the different conversation endpoints you can use to manage conversations programmatically.'\n\n## Multiprocess Execution\n\nBatman wondered about the behaviour of variables in a Robyn multiprocessing environment.\n\nRobyn reassured that it can indeed support them! i.e, handlers can be dispatched to multiple threads.\n\nAny variable used in a multiprocessing environment is shared across multiple processes.\n\nWhilst using multithreading in Robyn, the variables are not protected from multiple threads access by default.\n\n<Row>\n<Col>\n\nIf one needs a variable to be protected within a process, while accessing it from different threads, one can use <a href=\"https://docs.python.org/3/library/multiprocessing.html#multiprocessing.Value\">`multiprocessing.Value`</a> for achieving the required protection.\n\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n        import threading\n        import time\n        from multiprocessing import Value\n\n        from robyn import Robyn, Request\n\n        app = Robyn(__file__)\n\n        count: Value = Value(\"i\", 0)\n\n        def counter():\n            while True:\n                count.value += 1\n                time.sleep(0.2)\n                print(count.value, \"added 1\")\n\n        @app.get(\"/\")\n        def index(request: Request):\n            return f\"{count.value}\"\n\n        threading.Thread(target=counter, daemon=True).start()\n\n        app.start()\n    ```\n\n    </CodeGroup>\n  </Col>\n</Row>\n---\n\n\n## What's next?\n\n\nBatman wondered if it was possible to use Rust directly from Robyn's codebase.\n\nRobyn showed him the path.\n\n[Using Rust Directly](/documentation/en/api_reference/using_rust_directly)\n\n\n\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/api_reference/openapi.mdx",
    "content": "export const description =\n  'Welcome to the Robyn API documentation. You will find comprehensive guides and documentation to help you start working with Robyn as quickly as possible, as well as support if you get stuck.'\n\n\n## OpenAPI Docs a.k.a Swagger\n\nAfter deploying the application, Batman got multiple queries from the users on how to use the endpoints. Robyn showed him how to generate OpenAPI specifications for his application.\n\nOut of the box, the following endpoints are setup for you:\n\n- `/docs` The Swagger UI\n- `/openapi.json` The JSON Specification\n\nTo use a custom openapi configuration, you can:\n\n    - Place the `openapi.json` config file in the root directory.\n    - Or, pass the file path to the `openapi_file_path` parameter in the `Robyn()` constructor. (the parameter gets priority over the file).\n\nHowever, if you don't want to generate the OpenAPI docs, you can disable it by passing `--disable-openapi` flag while starting the application.\n\n```bash\npython app.py --disable-openapi\n```\n\n## How to use?\n\n- Query Params: The typing for query params can be added as `def get(r: Request, query_params: GetRequestParams)` where `GetRequestParams` is a subclass of `QueryParams`\n- Path Params are defaulted to string type (ref: https://en.wikipedia.org/wiki/Query_string)\n\n<CodeGroup title=\"Basic App\">\n\n```python\nfrom robyn.robyn import QueryParams\n\nfrom robyn import Robyn, Request\n\napp = Robyn(\n    file_object=__file__,\n    openapi=OpenAPI(\n        info=OpenAPIInfo(\n            title=\"Sample App\",\n            description=\"This is a sample server application.\",\n            termsOfService=\"https://example.com/terms/\",\n            version=\"1.0.0\",\n            contact=Contact(\n                name=\"API Support\",\n                url=\"https://www.example.com/support\",\n                email=\"support@example.com\",\n            ),\n            license=License(\n                name=\"BSD2.0\",\n                url=\"https://opensource.org/license/bsd-2-clause\",\n            ),\n            externalDocs=ExternalDocumentation(description=\"Find more info here\", url=\"https://example.com/\"),\n            components=Components(),\n        ),\n    ),\n)\n\n\n@app.get(\"/\")\nasync def welcome():\n    \"\"\"welcome endpoint\"\"\"\n    return \"hi\"\n\n\nclass GetRequestParams(QueryParams):\n    appointment_id: str\n    year: int\n\n\n@app.get(\"/api/v1/name\", openapi_name=\"Name Route\", openapi_tags=[\"Name\"])\nasync def get(r: Request, query_params: GetRequestParams):\n    \"\"\"Get Name by ID\"\"\"\n    return r.query_params\n\n\n@app.delete(\"/users/:name\", openapi_tags=[\"Name\"])\nasync def delete(r: Request):\n    \"\"\"Delete Name by ID\"\"\"\n    return r.path_params\n\n\nif __name__ == \"__main__\":\n    app.start()\n```\n\n</CodeGroup>\n\n## How does it work with subrouters?\n\n<CodeGroup title=\"Subrouters\">\n\n```python\nfrom robyn.robyn import QueryParams\n\nfrom robyn import Request, SubRouter\n\nsubrouter: SubRouter = SubRouter(__name__, prefix=\"/sub\")\n\n\n@subrouter.get(\"/\")\nasync def subrouter_welcome():\n    \"\"\"welcome subrouter\"\"\"\n    return \"hiiiiii subrouter\"\n\n\nclass SubRouterGetRequestParams(QueryParams):\n    _id: int\n    value: str\n\n\n@subrouter.get(\"/name\")\nasync def subrouter_get(r: Request, query_params: SubRouterGetRequestParams):\n    \"\"\"Get Name by ID\"\"\"\n    return r.query_params\n\n\n@subrouter.delete(\"/:name\")\nasync def subrouter_delete(r: Request):\n    \"\"\"Delete Name by ID\"\"\"\n    return r.path_params\n\n\napp.include_router(subrouter)\n```\n\n</CodeGroup>\n\n## Other Specification Params\n\nWe support all the params mentioned in the latest OpenAPI specifications (https://swagger.io/specification/). See an example using request & response bodies below:\n\n<CodeGroup title=\"Request & Response Body\">\n\n```python\nfrom robyn.types import JSONResponse, Body\n\nclass Initial(Body):\n    is_present: bool\n    letter: Optional[str]\n\n\nclass FullName(Body):\n    first: str\n    second: str\n    initial: Initial\n\n\nclass CreateItemBody(Body):\n    name: FullName\n    description: str\n    price: float\n    tax: float\n\n\nclass CreateResponse(JSONResponse):\n    success: bool\n    items_changed: int\n\n\n@app.post(\"/\")\ndef create_item(request: Request, body: CreateItemBody) -> CreateResponse:\n    return CreateResponse(success=True, items_changed=2)\n```\n\n</CodeGroup>\n\nWith the reference documentation deployed and running smoothly, Batman had a powerful new tool at his disposal. The Robyn framework had provided him with the flexibility, scalability, and performance needed to create an effective crime-fighting application, giving him a technological edge in his ongoing battle to protect Gotham City.\n\n## Using Pydantic Models\n\nIf you have Pydantic installed (`pip install \"robyn[pydantic]\"` or `pip install \"robyn[all]\"`), you can use Pydantic `BaseModel` classes directly as handler parameter annotations. Robyn will automatically validate the request body **and** generate a rich OpenAPI schema — including property types, required fields, defaults, and `$ref` for nested models.\n\n<CodeGroup title=\"Pydantic + OpenAPI\">\n\n```python\nfrom pydantic import BaseModel\n\nclass UserCreate(BaseModel):\n    name: str\n    email: str\n    age: int\n    active: bool = True\n\n@app.post(\"/users\", openapi_tags=[\"Users\"])\ndef create_user(user: UserCreate) -> dict:\n    \"\"\"Create a new user\"\"\"\n    return {\"name\": user.name}\n```\n\n</CodeGroup>\n\nFor the full guide on Pydantic validation, nested models, error responses, and OpenAPI integration, see the dedicated [Pydantic Integration](/documentation/en/api_reference/pydantic) page.\n\n\n## What's next?\n\n\nBatman wondered about whether Robyn handlers can be dispatched to multiple processes.\n\nRobyn showed him the way!\n\n[Multiprocess Execution](/documentation/en/api_reference/multiprocess_execution)\n\n\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/api_reference/pydantic.mdx",
    "content": "export const description =\n  'Learn how to use Pydantic models with Robyn for automatic request body validation and OpenAPI schema generation.'\n\n\n## Pydantic Integration\n\nRobyn supports [Pydantic](https://docs.pydantic.dev/) v2 as an optional dependency for automatic request body validation and rich OpenAPI schema generation. Validation is **opt-in per handler** — it only activates when you annotate a parameter with a Pydantic `BaseModel`. Handlers without Pydantic annotations are completely unaffected: no parsing, no validation, no overhead. When Pydantic is not installed at all, Robyn never imports it.\n\n\n## Installation\n\nInstall Robyn with Pydantic support using the optional extra:\n\n<CodeGroup title=\"Installation\">\n\n```bash {{ title: 'Pydantic only' }}\npip install \"robyn[pydantic]\"\n```\n\n```bash {{ title: 'All extras' }}\npip install \"robyn[all]\"\n```\n\n```bash {{ title: 'conda' }}\nconda install robyn pydantic -c conda-forge\n```\n\n</CodeGroup>\n\n`robyn[all]` includes Pydantic, Jinja2 templating, and any future optional features.\n\n\n## Basic Usage\n\nDefine a Pydantic `BaseModel` and use it as a type annotation on your handler parameter. Robyn will automatically parse the incoming JSON body, validate it against the model, and inject the validated instance into your handler.\n\n<CodeGroup title=\"Basic Pydantic Validation\">\n\n```python {{ title: 'Synchronous' }}\nfrom pydantic import BaseModel\nfrom robyn import Robyn\n\napp = Robyn(__file__)\n\n\nclass UserCreate(BaseModel):\n    name: str\n    email: str\n    age: int\n    active: bool = True\n\n\n@app.post(\"/users\")\ndef create_user(user: UserCreate):\n    \"\"\"Create a new user\"\"\"\n    return {\n        \"name\": user.name,\n        \"email\": user.email,\n        \"age\": user.age,\n        \"active\": user.active,\n    }\n\n\nif __name__ == \"__main__\":\n    app.start()\n```\n\n```python {{ title: 'Asynchronous' }}\nfrom pydantic import BaseModel\nfrom robyn import Robyn\n\napp = Robyn(__file__)\n\n\nclass UserCreate(BaseModel):\n    name: str\n    email: str\n    age: int\n    active: bool = True\n\n\n@app.post(\"/users\")\nasync def create_user(user: UserCreate):\n    \"\"\"Create a new user\"\"\"\n    return {\n        \"name\": user.name,\n        \"email\": user.email,\n        \"age\": user.age,\n        \"active\": user.active,\n    }\n\n\nif __name__ == \"__main__\":\n    app.start()\n```\n\n</CodeGroup>\n\n\n## Validation Errors\n\nWhen the request body fails validation, Robyn automatically returns a **422 Unprocessable Entity** response with structured error details. You do not need to write any error handling code.\n\nFor example, sending `{\"name\": \"Alice\", \"email\": \"alice@example.com\", \"age\": \"not_a_number\"}` would produce:\n\n```json\n{\n  \"error\": \"Validation Error\",\n  \"detail\": [\n    {\n      \"type\": \"int_parsing\",\n      \"loc\": [\"age\"],\n      \"msg\": \"Input should be a valid integer, unable to parse string as an integer\",\n      \"input\": \"not_a_number\"\n    }\n  ]\n}\n```\n\nMissing required fields are also caught:\n\n```json\n{\n  \"error\": \"Validation Error\",\n  \"detail\": [\n    {\n      \"type\": \"missing\",\n      \"loc\": [\"email\"],\n      \"msg\": \"Field required\",\n      \"input\": {\"name\": \"Alice\", \"age\": 30}\n    }\n  ]\n}\n```\n\n\n## Nested Models\n\nPydantic models can reference other models. Robyn handles nested validation automatically.\n\n<CodeGroup title=\"Nested Models\">\n\n```python\nfrom pydantic import BaseModel\nfrom robyn import Robyn\n\napp = Robyn(__file__)\n\n\nclass Address(BaseModel):\n    street: str\n    city: str\n    zip_code: str\n\n\nclass UserWithAddress(BaseModel):\n    name: str\n    email: str\n    address: Address\n\n\n@app.post(\"/users\")\ndef create_user(data: UserWithAddress):\n    \"\"\"Create a user with an address\"\"\"\n    return {\"name\": data.name, \"city\": data.address.city}\n```\n\n</CodeGroup>\n\nIf the nested `address` object is missing or malformed, Robyn returns a 422 with the full error path (e.g. `[\"address\", \"city\"]`).\n\n\n## Using with the Request Object\n\nYou can combine Pydantic parameters with the standard `Request` object in the same handler. This gives you access to headers, query params, and other request metadata alongside the validated body.\n\n<CodeGroup title=\"Pydantic + Request\">\n\n```python\nfrom pydantic import BaseModel\nfrom robyn import Robyn, Request\n\napp = Robyn(__file__)\n\n\nclass UserCreate(BaseModel):\n    name: str\n    email: str\n    age: int\n    active: bool = True\n\n\n@app.post(\"/users\")\ndef create_user(request: Request, user: UserCreate):\n    \"\"\"Create a user — access both raw request and validated model\"\"\"\n    return {\n        \"method\": request.method,\n        \"name\": user.name,\n        \"email\": user.email,\n    }\n```\n\n</CodeGroup>\n\n\n## Returning Pydantic Models Directly\n\nYou can return a Pydantic model instance (or a list of them) directly from a handler. Robyn will automatically serialize it to JSON with the correct `Content-Type` header — no need to call `.model_dump()` manually.\n\n<CodeGroup title=\"Returning Models\">\n\n```python {{ title: 'Single model' }}\n@app.post(\"/users\")\ndef create_user(user: UserCreate) -> UserCreate:\n    \"\"\"Validate and echo back the user\"\"\"\n    return user\n```\n\n```python {{ title: 'List of models' }}\n@app.post(\"/users/batch\")\ndef create_users(user: UserCreate) -> list[UserCreate]:\n    \"\"\"Return multiple model instances\"\"\"\n    return [user, user]\n```\n\n</CodeGroup>\n\nBoth forms produce an `application/json` response. The single-model path uses Pydantic's Rust-based `model_dump_json()` for maximum throughput.\n\n\n## How Validation Is Triggered\n\nPydantic validation is **annotation-driven, not method-driven**. The router inspects each handler's signature at registration time; any parameter annotated with a `BaseModel` subclass triggers automatic validation of `request.body` when that route is called. This works with every HTTP method — `POST`, `PUT`, `PATCH`, `DELETE`, or any other method that carries a body.\n\n<CodeGroup title=\"Any HTTP Method\">\n\n```python {{ title: 'PUT' }}\n@app.put(\"/users/:id\")\ndef update_user(user: UserCreate):\n    return {\"updated\": True, \"name\": user.name}\n```\n\n```python {{ title: 'PATCH' }}\n@app.patch(\"/users/:id\")\ndef patch_user(user: UserCreate):\n    return {\"patched\": True, \"name\": user.name}\n```\n\n</CodeGroup>\n\n\n## OpenAPI Integration\n\nWhen you use Pydantic models, Robyn automatically generates rich JSON Schema in your OpenAPI specification at `/openapi.json`. This includes:\n\n- **Property types** — `string`, `integer`, `boolean`, etc.\n- **Required fields** — fields without defaults are listed in `required`\n- **Default values** — shown in the schema\n- **Nested models** — referenced via `$ref` and placed in `components/schemas`\n\n<CodeGroup title=\"OpenAPI with Pydantic\">\n\n```python\nfrom pydantic import BaseModel\nfrom robyn import Robyn, Request\n\napp = Robyn(__file__)\n\n\nclass Address(BaseModel):\n    street: str\n    city: str\n    zip_code: str\n\n\nclass UserWithAddress(BaseModel):\n    name: str\n    email: str\n    address: Address\n\n\n@app.post(\"/users\", openapi_tags=[\"Users\"])\ndef create_user(request: Request, data: UserWithAddress) -> dict:\n    \"\"\"Create a user with a nested address\"\"\"\n    return {\"name\": data.name, \"city\": data.address.city}\n```\n\n</CodeGroup>\n\nThe generated `/openapi.json` will contain:\n\n```json\n{\n  \"paths\": {\n    \"/users\": {\n      \"post\": {\n        \"requestBody\": {\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"type\": \"object\",\n                \"properties\": {\n                  \"name\": {\"type\": \"string\", \"title\": \"Name\"},\n                  \"email\": {\"type\": \"string\", \"title\": \"Email\"},\n                  \"address\": {\"$ref\": \"#/components/schemas/Address\"}\n                },\n                \"required\": [\"name\", \"email\", \"address\"],\n                \"title\": \"UserWithAddress\"\n              }\n            }\n          }\n        }\n      }\n    }\n  },\n  \"components\": {\n    \"schemas\": {\n      \"Address\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"street\": {\"type\": \"string\", \"title\": \"Street\"},\n          \"city\": {\"type\": \"string\", \"title\": \"City\"},\n          \"zip_code\": {\"type\": \"string\", \"title\": \"Zip Code\"}\n        },\n        \"required\": [\"street\", \"city\", \"zip_code\"],\n        \"title\": \"Address\"\n      }\n    }\n  }\n}\n```\n\n\n## Pydantic vs Body\n\nRobyn supports two approaches for typed request bodies. Choose the one that fits your needs:\n\n| Feature | `Body` subclass | Pydantic `BaseModel` |\n|---|---|---|\n| Installation | Built-in | `pip install \"robyn[pydantic]\"` |\n| Validation | No automatic validation | Full validation with detailed errors |\n| Error responses | Manual | Automatic 422 with structured errors |\n| Return serialization | Manual `dict()` | Auto-serialize model to JSON |\n| OpenAPI schema | Basic type inference | Full JSON Schema (types, required, defaults, `$ref`) |\n| Nested models | Supported (basic) | Supported (with `$ref` in OpenAPI) |\n| Performance overhead | None | Only when Pydantic is installed and used |\n\nBoth approaches work with OpenAPI documentation. If you need validation, use Pydantic. If you just need OpenAPI schema hints without validation, `Body` is sufficient.\n\n\n## Important Notes\n\n- **Opt-in per handler** — Validation only runs on handlers where a parameter is annotated with a Pydantic `BaseModel`. All other handlers (using `Body`, `Request`, path params, etc.) behave exactly as before with zero additional overhead.\n- **One Pydantic body per handler** — Each handler can have at most one parameter annotated with a Pydantic model. The entire request body is parsed into that single model. If you need multiple model inputs, compose them into a single parent model with nested fields.\n- **Request validation only** — Robyn validates *incoming* request bodies against Pydantic models but does not validate *outgoing* responses. When you return a model instance, it is serialized as-is without re-validation. This is a deliberate design choice for performance — if you constructed the model, it's already valid.\n\n\n## What's next?\n\nBatman wondered about whether Robyn handlers can be dispatched to multiple processes.\n\nRobyn showed him the way!\n\n[Multiprocess Execution](/documentation/en/api_reference/multiprocess_execution)\n\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/api_reference/redirection.mdx",
    "content": "## Redirection\n\nBatman wanted to redirect some endpoints to others. Robyn helped him do so by the following:\n\n<Row>\n  <Col>\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Request\">\n\n      ```python\n      from robyn import Robyn, Response\n      app = Robyn(__file__)\n\n      @app.get(\"/\")\n      async def index():\n        return Response(\n          status_code=307,\n          description=\"\",\n          headers={\"Location\": \"landing\"},\n        )\n\n      @app.get(\"/landing\")\n      def landing():\n        return \"hii!\"\n      ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n---\n\n## What's next?\n\nNow, Batman wanted to have the ability to upload files to the server if any new villain appeared. Robyn introduced him to the file upload and some of the form data features. \n\n- [File Uploads](/documentation/en/api_reference/file-uploads)\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/api_reference/request_object.mdx",
    "content": "export const description =\n  'On this page, we’ll dive into the different conversation endpoints you can use to manage conversations programmatically.'\n\n\n## Request Object\n\nThe request object is a dataclass that contains all the information about the request. It is available in the route handler as the first argument.\n\n\n\n<Row>\n<Col>\nThe request object is created in Rust side but is exposed to Python as a dataclass.\n\n<ul>\n<li>\nAttributes:\n</li>\n<li>\nquery_params (QueryParams): The query parameters of the request. `e.g. /user?id=123 -> {\"id\": [ \"123\" ]}`\n</li>\n<li>\nheaders (dict[str, str]): The headers of the request. `e.g. {\"Content-Type\": \"application/json\"}`\n</li>\n<li>\nparams (dict[str, str]): The parameters of the request. `e.g. /user/:id -> {\"id\": \"123\"}`\n</li>\n<li>\nbody (Union[str, bytes]): The raw body of the request. For JSON payloads, use the `json()` method to parse the body into a dict with proper type preservation.\n</li>\n<li>\nmethod (str): The method of the request. `e.g. GET, POST, PUT, DELETE`\n</li>\n<li>\nip_addr (Optional[str]): The IP Address of the client\n</li>\n<li>\nidentity (Optional[Identity]): The identity of the client\n</li>\n\n\n</ul>\n\n\n\n\n\n</Col>\n  <Col>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    @dataclass\n    class Request:\n      \"\"\"\n      query_params: QueryParams\n      headers: Headers\n      path_params: dict[str, str]\n      body: Union[str, bytes]\n      method: str\n      url: Url\n      form_data: dict[str, str]\n      files: dict[str, bytes]\n      ip_addr: Optional[str]\n      identity: Optional[Identity]\n      \"\"\"\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n## Parsing JSON Body\n\nThe `request.json()` method parses the request body as JSON and returns a Python `dict` with full type preservation:\n\n- JSON `null` becomes Python `None`\n- JSON numbers become Python `int` or `float`\n- JSON booleans become Python `bool`\n- JSON strings become Python `str`\n- JSON arrays become Python `list`\n- JSON objects become Python `dict`\n\nNested structures are handled recursively up to a maximum depth of 128 levels.\n\n<CodeGroup title=\"Parsing JSON\" tag=\"POST\" label=\"/example\">\n\n```python\n@app.post(\"/example\")\nasync def handler(request: Request):\n    data = request.json()  # Returns a dict with preserved types\n    # e.g. {\"count\": 42, \"active\": true, \"tags\": [\"a\", \"b\"]}\n    # ->   {\"count\": 42, \"active\": True, \"tags\": [\"a\", \"b\"]}\n    return {\"received\": data}\n```\n</CodeGroup>\n\nIf the body is not valid JSON or is not a JSON object, a `ValueError` will be raised.\n\n## Extra Path Parameters\n\nRobyn supports capturing extra path parameters using the `*extra` syntax in route definitions. This allows you to capture any additional segments in the URL path that come after the defined route.\n\nFor example, if you define a route like this:\n\n<CodeGroup>\n```python\n@app.get(\"/sync/extra/*extra\")\ndef sync_param_extra(request: Request):\n    extra = request.path_params[\"extra\"]\n    return extra\n```\n</CodeGroup>\n\nAny additional path segments after `/sync/extra/` will be captured in the `extra` parameter. For instance:\n\n<ul>\n<li>\nA request to `/sync/extra/foo/bar` would result in `extra = \"foo/bar\"`\n</li>\n<li>\n     A request to `/sync/extra/123/456/789` would result in `extra = \"123/456/789\"`\n</li>\n</ul>\n\nYou can access the extra path parameters through `request.path_params[\"extra\"]` in your route handler.\n\nThis feature is particularly useful when you need to handle dynamic, nested routes or when you want to capture an unknown number of path segments.\n\n---\n\n## Easy Access Parameters\n\nInstead of manually extracting and converting query parameters and path parameters from the request object, you can declare them directly in your function signature with type annotations. Robyn will automatically resolve and coerce them for you.\n\nAny handler parameter that doesn't match a known request component (`Request`, `QueryParams`, `Headers`, etc.) is treated as an individual path or query parameter.\n\n<Row>\n  <Col>\n    **Basic usage** — path params and query params with type coercion and defaults.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Easy Access Params\" tag=\"GET\" label=\"/items/:id?q=hello&page=5\">\n\n    ```python {{ title: 'Typed Params' }}\n    @app.get(\"/items/:id\")\n    async def get_item(id: int, q: str, page: int = 1):\n        # id is coerced from the path param string to int\n        # q is taken from ?q=...\n        # page defaults to 1 if not provided\n        return {\"id\": id, \"q\": q, \"page\": page}\n    ```\n\n    ```python {{ title: 'Mixed with Request' }}\n    @app.get(\"/items/:id\")\n    async def get_item(request: Request, id: int, q: str = \"\"):\n        # request is still injected as usual\n        # id and q are resolved as individual params\n        return {\"id\": id, \"q\": q, \"method\": request.method}\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n<Row>\n  <Col>\n    **Optional, List, Bool, and Float params** — Robyn handles common Python types automatically.\n\n    - `Optional[T]` — resolves to `None` when not provided\n    - `List[T]` — collects repeated query params (e.g. `?tag=a&tag=b`)\n    - `bool` — accepts `true/false`, `1/0`, `yes/no`, `on/off`\n    - `float` — standard float coercion\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Advanced Types\" tag=\"GET\" label=\"/search\">\n\n    ```python {{ title: 'Optional' }}\n    @app.get(\"/search\")\n    def search(name: str, age: Optional[int] = None):\n        return {\"name\": name, \"age\": age}\n    # GET /search?name=bob        -> {\"name\": \"bob\", \"age\": null}\n    # GET /search?name=bob&age=30 -> {\"name\": \"bob\", \"age\": 30}\n    ```\n\n    ```python {{ title: 'List' }}\n    from typing import List\n\n    @app.get(\"/filter\")\n    def filter_items(tag: List[str]):\n        return {\"tags\": tag}\n    # GET /filter?tag=python&tag=rust -> {\"tags\": [\"python\", \"rust\"]}\n    ```\n\n    ```python {{ title: 'Bool & Float' }}\n    @app.get(\"/settings\")\n    def settings(active: bool = False, price: float = 0.0):\n        return {\"active\": active, \"price\": price}\n    # GET /settings?active=true&price=19.99\n    # -> {\"active\": true, \"price\": 19.99}\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n<Row>\n  <Col>\n    **Error handling** — if a required parameter is missing or a value cannot be coerced to the declared type, Robyn returns a `400 Bad Request` response automatically.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Validation Errors\" tag=\"GET\" label=\"/items/:id\">\n\n    ```python {{ title: 'Automatic 400' }}\n    @app.get(\"/items/:id\")\n    def get_item(id: int, q: str):\n        return {\"id\": id, \"q\": q}\n\n    # GET /items/42         -> 400 (missing required 'q')\n    # GET /items/abc?q=test -> 400 (cannot coerce 'abc' to int)\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n---\n\n\n## What's next?\n\nNow, Batman wanted to understand the configuration of the Robyn server. He was then introduced to the concept of Robyn env files.\n\n- [Robyn Env](/documentation/en/api_reference/robyn_env)\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/api_reference/robyn_env.mdx",
    "content": "export const description = 'In this section, we will learn how to configure the server through a configuration file.'\n\n\n## Configuring the server through an environment file\n\nBatman wanted to configure the server through an environment file. Changing code continuously induced the risk of error.\n\n## Environment Variables\n\n - `ROBYN_PORT`: Specifies the port on which the Robyn server will listen.\n    - Default: `8080`\n    - Example: `ROBYN_PORT=3000`\n - `ROBYN_HOST`: Specifies the host address for the Robyn server.\n    - Default: `127.0.0.1`\n    - Example: `ROBYN_HOST=0.0.0.0`\n - `ROBYN_BROWSER_OPEN`: Open the browser on successful start.\n    - Default: `False`\n    - Example: `ROBYN_BROWSER_OPEN=True`\n - `ROBYN_DEV_MODE`: Configures the dev mode\n    - Default: `False`\n    - Example: `ROBYN_DEV_MODE=True`\n - `ROBYN_MAX_PAYLOAD_SIZE`: Sets the maximum payload size for HTTP requests and WebSocket messages in bytes.\n    - Default: `1000000` bytes\n    - Example: `ROBYN_MAX_PAYLOAD_SIZE=1000000`\n\nYou can have a `robyn.env` file to load them automatically in your environment.\n\nThese environment variables are typically set in a `robyn.env` file located at the root of the project. The server parses this file at startup to configure itself accordingly.\n\nFor more details on the structure and usage of the `robyn.env` file, refer to the documentation snippet:\n\n```bash {{title: 'Sample project directory'}}\n--project/\n  --robyn.env\n  --index.py\n  ...\n```\n\nSample `robyn.env` file:\n\n```bash {{ title: 'Sample Robyn.env' }}\nROBYN_PORT=8080\nROBYN_HOST=127.0.0.1\nRANDOM_ENV=123\nROBYN_BROWSER_OPEN=True\nROBYN_DEV_MODE=True\nROBYN_MAX_PAYLOAD_SIZE=1000000\n```\n\nWith the web application deployed and running smoothly, Batman had a powerful new tool at his disposal. The Robyn framework had provided him with the flexibility, scalability, and performance needed to create an effective crime-fighting application, giving him a technological edge in his ongoing battle to protect Gotham City.\n\n\n## What's next?\n\nBatman - Thanks, Robyn. Now tell me more.\nRobyn - Let us learn about the Middlewares and events now!\n\n- [Middlewares](/documentation/en/api_reference/middlewares)\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/api_reference/scaling.mdx",
    "content": "export const description =\n  'Comprehensive guide to scaling Robyn applications from development to production, including multi-process configuration, load balancing, and deployment strategies.'\n\n# Scaling and Production Deployment\n\nRobyn is designed to scale efficiently from development to production. This guide covers scaling strategies, production deployment patterns, and performance optimization techniques.\n\n## Understanding Robyn's Scaling Model\n\n### Multi-Process Architecture\n\nRobyn uses a **shared-nothing multi-process model** optimized for modern multi-core systems. Each process:\n- Runs independently with its own Python interpreter and memory space\n- Has its own Global Interpreter Lock (GIL), eliminating GIL contention\n- Can handle multiple concurrent requests via worker threads\n- Scales linearly across CPU cores for true parallelism\n- Provides fault isolation - one process crash doesn't affect others\n\n### Worker Threads\n\nWithin each process, multiple worker threads provide concurrency:\n- Share the same Python interpreter and memory space\n- Are subject to the GIL for CPU-bound tasks (use more processes for CPU work)\n- Excel at I/O-bound operations where threads can release the GIL\n- Handle database calls, HTTP requests, file operations efficiently\n- Provide request-level concurrency within the same process\n\n## Scaling Configuration\n\n### Basic Scaling\n\n<Row>\n  <Col>\n    **Single Process Development**: Start simple during development with auto-reload enabled.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Development Scaling\">\n    ```bash\n    # Development mode (single process, single worker)\n    python app.py --dev\n    \n    # Development with custom port\n    python app.py --dev --port 3000\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n<Row>\n  <Col>\n    **Multi-Core Production**: Scale across all available CPU cores for maximum performance.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Production Scaling\">\n    ```bash\n    # Automatic optimization (recommended)\n    python app.py --fast\n    \n    # Manual configuration for 8-core system\n    python app.py --processes 8 --workers 2\n    \n    # I/O heavy workloads\n    python app.py --processes 4 --workers 4\n    \n    # CPU heavy workloads  \n    python app.py --processes 8 --workers 1\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n### Advanced Configuration\n\n<Row>\n  <Col>\n    **Hardware-Specific Tuning**: Optimize based on your specific hardware and application characteristics.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Advanced Tuning\">\n    ```bash\n    # High-traffic web API (4-core system)\n    python app.py --processes 4 --workers 3 --log-level INFO\n    \n    # Data processing service (8-core system)\n    python app.py --processes 8 --workers 1 --log-level WARNING\n    \n    # Mixed workload (balanced approach)\n    python app.py --processes 6 --workers 2 --log-level INFO\n    \n    # Maximum concurrency (16-core system)\n    python app.py --processes 8 --workers 4\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n### Configuration Guidelines\n\n<Row>\n  <Col>\n    **Choosing the Right Configuration**: Use these guidelines to optimize for your specific use case.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Configuration Guidelines\">\n    ```python\n    # For CPU-intensive applications\n    # Rule: processes = CPU cores, workers = 1\n    # Example: 8-core machine\n    python app.py --processes 8 --workers 1\n    \n    # For I/O-intensive applications  \n    # Rule: processes = CPU cores / 2, workers = 2-4\n    # Example: 8-core machine\n    python app.py --processes 4 --workers 3\n    \n    # For balanced applications\n    # Rule: processes = CPU cores / 2, workers = 2\n    # Example: 8-core machine\n    python app.py --processes 4 --workers 2\n    \n    # Memory considerations\n    # Each process uses ~50-100MB base memory\n    # Monitor with: ps aux | grep python\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n## Production Deployment Strategies\n\n### Container Deployment\n\n<Row>\n  <Col>\n    **Docker Containerization**: Deploy Robyn applications using Docker for consistent environments and easy scaling.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Docker Deployment\">\n    ```dockerfile\n    # Dockerfile\n    FROM python:3.11-slim\n    \n    # Install system dependencies\n    RUN apt-get update && apt-get install -y \\\n        build-essential \\\n        && rm -rf /var/lib/apt/lists/*\n    \n    # Set working directory\n    WORKDIR /app\n    \n    # Copy requirements and install dependencies\n    COPY requirements.txt .\n    RUN pip install --no-cache-dir -r requirements.txt\n    \n    # Copy application code\n    COPY . .\n    \n    # Expose port\n    EXPOSE 8080\n    \n    # Health check\n    HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\\n        CMD curl -f http://localhost:8080/health || exit 1\n    \n    # Run application with optimized settings\n    CMD [\"python\", \"app.py\", \"--fast\", \"--host\", \"0.0.0.0\", \"--port\", \"8080\"]\n    ```\n    \n    ```yaml\n    # docker-compose.yml\n    version: '3.8'\n    services:\n      robyn-app:\n        build: .\n        ports:\n          - \"8080:8080\"\n        environment:\n          - ROBYN_HOST=0.0.0.0\n          - ROBYN_PORT=8080\n          - ROBYN_LOG_LEVEL=INFO\n        restart: unless-stopped\n        deploy:\n          resources:\n            limits:\n              cpus: '2.0'\n              memory: 1G\n            reservations:\n              cpus: '1.0'\n              memory: 512M\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n### Load Balancer Configuration\n\n<Row>\n  <Col>\n    **Nginx Load Balancing**: Use Nginx as a reverse proxy and load balancer for multiple Robyn instances.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Nginx Configuration\">\n    ```nginx\n    # /etc/nginx/sites-available/robyn-app\n    upstream robyn_backend {\n        least_conn;\n        server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;\n        server 127.0.0.1:8081 max_fails=3 fail_timeout=30s;\n        server 127.0.0.1:8082 max_fails=3 fail_timeout=30s;\n        server 127.0.0.1:8083 max_fails=3 fail_timeout=30s;\n    }\n    \n    server {\n        listen 80;\n        server_name yourdomain.com;\n        \n        # Security headers\n        add_header X-Frame-Options DENY;\n        add_header X-Content-Type-Options nosniff;\n        add_header X-XSS-Protection \"1; mode=block\";\n        \n        # Gzip compression\n        gzip on;\n        gzip_types text/plain application/json application/javascript text/css;\n        \n        # Static files (if any)\n        location /static/ {\n            alias /var/www/static/;\n            expires 1y;\n            add_header Cache-Control \"public, immutable\";\n        }\n        \n        # API routes\n        location / {\n            proxy_pass http://robyn_backend;\n            proxy_set_header Host $host;\n            proxy_set_header X-Real-IP $remote_addr;\n            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n            proxy_set_header X-Forwarded-Proto $scheme;\n            \n            # Timeouts\n            proxy_connect_timeout 5s;\n            proxy_send_timeout 60s;\n            proxy_read_timeout 60s;\n            \n            # Health checks\n            proxy_next_upstream error timeout http_500 http_502 http_503;\n        }\n        \n        # Health check endpoint\n        location /health {\n            access_log off;\n            proxy_pass http://robyn_backend;\n        }\n    }\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n### Kubernetes Deployment\n\n<Row>\n  <Col>\n    **Kubernetes Orchestration**: Deploy and scale Robyn applications in Kubernetes clusters.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Kubernetes Manifests\">\n    ```yaml\n    # deployment.yaml\n    apiVersion: apps/v1\n    kind: Deployment\n    metadata:\n      name: robyn-app\n      labels:\n        app: robyn-app\n    spec:\n      replicas: 3\n      selector:\n        matchLabels:\n          app: robyn-app\n      template:\n        metadata:\n          labels:\n            app: robyn-app\n        spec:\n          containers:\n          - name: robyn-app\n            image: your-registry/robyn-app:latest\n            ports:\n            - containerPort: 8080\n            env:\n            - name: ROBYN_HOST\n              value: \"0.0.0.0\"\n            - name: ROBYN_PORT\n              value: \"8080\"\n            - name: ROBYN_LOG_LEVEL\n              value: \"INFO\"\n            resources:\n              requests:\n                memory: \"256Mi\"\n                cpu: \"250m\"\n              limits:\n                memory: \"512Mi\"\n                cpu: \"500m\"\n            livenessProbe:\n              httpGet:\n                path: /health\n                port: 8080\n              initialDelaySeconds: 30\n              periodSeconds: 10\n            readinessProbe:\n              httpGet:\n                path: /health\n                port: 8080\n              initialDelaySeconds: 5\n              periodSeconds: 5\n    \n    ---\n    apiVersion: v1\n    kind: Service\n    metadata:\n      name: robyn-service\n    spec:\n      selector:\n        app: robyn-app\n      ports:\n      - protocol: TCP\n        port: 80\n        targetPort: 8080\n      type: LoadBalancer\n    \n    ---\n    apiVersion: autoscaling/v2\n    kind: HorizontalPodAutoscaler\n    metadata:\n      name: robyn-hpa\n    spec:\n      scaleTargetRef:\n        apiVersion: apps/v1\n        kind: Deployment\n        name: robyn-app\n      minReplicas: 3\n      maxReplicas: 10\n      metrics:\n      - type: Resource\n        resource:\n          name: cpu\n          target:\n            type: Utilization\n            averageUtilization: 70\n      - type: Resource\n        resource:\n          name: memory\n          target:\n            type: Utilization\n            averageUtilization: 80\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n## Monitoring and Observability\n\n### Application Metrics\n\n<Row>\n  <Col>\n    **Built-in Monitoring**: Add comprehensive monitoring and metrics collection to your Robyn application for production observability.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Advanced Monitoring Setup\">\n    ```python\n    from robyn import Robyn\n    import time\n    import psutil\n    import logging\n    import threading\n    from collections import defaultdict, deque\n    \n    app = Robyn(__file__)\n    \n    # Configure structured logging\n    logging.basicConfig(\n        level=logging.INFO,\n        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',\n        handlers=[\n            logging.StreamHandler(),\n            logging.FileHandler('app.log')\n        ]\n    )\n    logger = logging.getLogger(__name__)\n    \n    # Advanced metrics collection\n    class MetricsCollector:\n        def __init__(self):\n            self.request_count = 0\n            self.total_response_time = 0\n            self.status_codes = defaultdict(int)\n            self.endpoint_stats = defaultdict(lambda: {\"count\": 0, \"total_time\": 0})\n            self.response_times = deque(maxlen=1000)  # Last 1000 requests\n            self.lock = threading.Lock()\n        \n        def record_request(self, method, path, status_code, duration):\n            with self.lock:\n                self.request_count += 1\n                self.total_response_time += duration\n                self.status_codes[status_code] += 1\n                endpoint_key = f\"{method} {path}\"\n                self.endpoint_stats[endpoint_key][\"count\"] += 1\n                self.endpoint_stats[endpoint_key][\"total_time\"] += duration\n                self.response_times.append(duration)\n        \n        def get_metrics(self):\n            with self.lock:\n                avg_response_time = self.total_response_time / max(self.request_count, 1)\n                p95_response_time = sorted(self.response_times)[int(len(self.response_times) * 0.95)] if self.response_times else 0\n                \n                return {\n                    \"requests_total\": self.request_count,\n                    \"avg_response_time\": avg_response_time,\n                    \"p95_response_time\": p95_response_time,\n                    \"status_codes\": dict(self.status_codes),\n                    \"top_endpoints\": dict(sorted(\n                        self.endpoint_stats.items(),\n                        key=lambda x: x[1][\"count\"],\n                        reverse=True\n                    )[:10])\n                }\n    \n    metrics = MetricsCollector()\n    start_time = time.time()\n    \n    @app.before_request\n    def monitor_request_start(request):\n        request.start_time = time.time()\n        return request\n    \n    @app.after_request\n    def monitor_request_end(request, response):\n        duration = time.time() - request.start_time\n        status_code = getattr(response, 'status_code', 200)\n        \n        # Record metrics\n        metrics.record_request(request.method, request.url.path, status_code, duration)\n        \n        # Log slow requests\n        if duration > 1.0:\n            logger.warning(\n                f\"Slow request: {request.method} {request.url.path} - \"\n                f\"{duration:.3f}s (status: {status_code})\"\n            )\n        \n        # Add response headers\n        response.headers[\"X-Response-Time\"] = f\"{duration:.3f}s\"\n        response.headers[\"X-Request-ID\"] = str(time.time_ns())\n        return response\n    \n    # Health endpoint with detailed status\n    @app.get(\"/health\", const=True)\n    def health_check():\n        return {\n            \"status\": \"healthy\",\n            \"version\": \"1.0.0\",\n            \"uptime_seconds\": time.time() - start_time\n        }\n    \n    # Comprehensive metrics endpoint\n    @app.get(\"/metrics\")\n    def get_metrics():\n        cpu_percent = psutil.cpu_percent(interval=1)\n        memory = psutil.virtual_memory()\n        disk = psutil.disk_usage('/')\n        \n        app_metrics = metrics.get_metrics()\n        \n        return {\n            \"system\": {\n                \"cpu_usage_percent\": cpu_percent,\n                \"memory_usage_percent\": memory.percent,\n                \"memory_available_mb\": memory.available / 1024 / 1024,\n                \"disk_usage_percent\": disk.percent,\n                \"load_average\": psutil.getloadavg()\n            },\n            \"application\": app_metrics,\n            \"timestamp\": time.time()\n        }\n    \n    # Readiness endpoint for k8s\n    @app.get(\"/ready\")\n    def readiness_check():\n        # Add your readiness checks here (DB connection, etc.)\n        return {\"ready\": True}\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n## Performance Optimization\n\n### Scaling Best Practices\n\n1. **Start Simple**: Begin with `--fast` mode and measure performance\n2. **Profile Your Application**: Identify CPU vs I/O bound operations\n3. **Test Different Configurations**: Benchmark various process/worker combinations\n4. **Monitor Resource Usage**: Watch CPU, memory, and I/O utilization\n5. **Scale Horizontally**: Use multiple instances behind a load balancer\n\n### Performance Testing\n\n<Row>\n  <Col>\n    **Load Testing**: Use tools like wrk, Apache Bench, or Locust to test different configurations and find optimal settings.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Performance Testing\">\n    ```bash\n    # Install wrk for load testing\n    # macOS: brew install wrk\n    # Ubuntu: sudo apt install wrk\n    \n    # Test baseline performance\n    wrk -t4 -c100 -d30s http://localhost:8080/health\n    \n    # Test with different configurations\n    # Configuration 1: Single process\n    python app.py --processes 1 --workers 1 &\n    wrk -t4 -c100 -d30s http://localhost:8080/api/endpoint\n    \n    # Configuration 2: Multi-process\n    python app.py --processes 4 --workers 2 &\n    wrk -t4 -c100 -d30s http://localhost:8080/api/endpoint\n    \n    # Configuration 3: Fast mode\n    python app.py --fast &\n    wrk -t4 -c100 -d30s http://localhost:8080/api/endpoint\n    \n    # Compare results and choose the best configuration\n    ```\n    \n    ```python\n    # Python script for automated testing\n    import subprocess\n    import time\n    import requests\n    \n    def test_configuration(processes, workers, duration=30):\n        # Start server\n        cmd = f\"python app.py --processes {processes} --workers {workers}\"\n        server = subprocess.Popen(cmd.split())\n        time.sleep(2)  # Wait for startup\n        \n        try:\n            # Run load test\n            wrk_cmd = f\"wrk -t4 -c100 -d{duration}s http://localhost:8080/health\"\n            result = subprocess.run(wrk_cmd.split(), capture_output=True, text=True)\n            return result.stdout\n        finally:\n            server.terminate()\n            time.sleep(1)\n    \n    # Test different configurations\n    configs = [(1, 1), (2, 2), (4, 1), (4, 2), (8, 1)]\n    for processes, workers in configs:\n        print(f\"\\nTesting: {processes} processes, {workers} workers\")\n        result = test_configuration(processes, workers)\n        print(result)\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n### Environment Variables\n\n<Row>\n  <Col>\n    **Configuration via Environment**: Use environment variables for deployment flexibility.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Environment Configuration\">\n    ```bash\n    # Production environment variables\n    export ROBYN_HOST=0.0.0.0\n    export ROBYN_PORT=8080\n    export ROBYN_LOG_LEVEL=INFO\n    export ROBYN_PROCESSES=4\n    export ROBYN_WORKERS=2\n    \n    # Database configuration\n    export DATABASE_URL=postgresql://user:pass@localhost/db\n    export REDIS_URL=redis://localhost:6379/0\n    \n    # Application settings\n    export SECRET_KEY=your-secret-key\n    export DEBUG=false\n    export ENVIRONMENT=production\n    \n    # Start application with environment config\n    python app.py\n    ```\n    \n    ```python\n    # app.py - Using environment variables\n    import os\n    from robyn import Robyn\n    \n    app = Robyn(__file__)\n    \n    # Configuration from environment\n    HOST = os.getenv(\"ROBYN_HOST\", \"127.0.0.1\")\n    PORT = int(os.getenv(\"ROBYN_PORT\", \"8080\"))\n    PROCESSES = int(os.getenv(\"ROBYN_PROCESSES\", \"1\"))\n    WORKERS = int(os.getenv(\"ROBYN_WORKERS\", \"1\"))\n    LOG_LEVEL = os.getenv(\"ROBYN_LOG_LEVEL\", \"INFO\")\n    \n    if __name__ == \"__main__\":\n        app.start(\n            host=HOST,\n            port=PORT,\n            processes=PROCESSES,\n            workers=WORKERS,\n            log_level=LOG_LEVEL\n        )\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n---\n\n## What's next?\n\nNow, Batman wanted to extend Robyn. Robyn told him about the advanced features.\n\n- [Advanced Features](/documentation/en/api_reference/advanced_features)\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/api_reference/server_sent_events.mdx",
    "content": "export const description =\n  'Learn how to implement Server-Sent Events (SSE) in Robyn for real-time server-to-client communication.'\n\n# Server-Sent Events (SSE)\n\nAfter learning about [form data handling](/documentation/en/api_reference/form_data), Batman realized he needed a way to push real-time updates to his crime monitoring dashboard. Criminals don't wait for Batman to refresh his browser!\n\nHe discovered Server-Sent Events (SSE) - a perfect solution for one-way communication from server to client over HTTP. SSE allows Batman to stream live data to his dashboard without the complexity of full bidirectional communication.\n\n\"This is exactly what I need for my crime alerts!\" Batman exclaimed. \"I can push updates to the dashboard instantly when new crimes are detected.\"\n\nServer-Sent Events are ideal for:\n- Real-time notifications\n- Live data feeds\n- Progress updates\n- Chat applications (server-to-client only)\n- Dashboard updates\n- Log streaming\n\n## How does it work?\n\n<Row>\n<Col>\n\nBatman can create Server-Sent Events streams by using the `SSEResponse` and `SSEMessage` classes. He can use both regular generators and async generators depending on his needs:\n\n- **Regular generators**: Perfect for simple data streams or when working with synchronous operations\n- **Async generators**: Ideal when Batman needs to perform async operations like database queries or API calls within the stream\n\n</Col>\n\n<Col sticky>\n\n<CodeGroup title=\"SSE Response\" tag=\"GET\" label=\"/events\">\n\n```python {{ title: 'Basic SSE Stream' }}\nfrom robyn import Robyn, SSEResponse, SSEMessage\nimport time\n\napp = Robyn(__file__)\n\n@app.get(\"/events\")\ndef stream_events(request):\n    def event_generator():\n        for i in range(10):\n            yield SSEMessage(f\"Event {i}\", id=str(i))\n            time.sleep(1)\n    \n    return SSEResponse(event_generator())\n```\n```python {{ title: 'JSON Data Stream' }}\nfrom robyn import Robyn, SSEResponse, SSEMessage\nimport json\nimport time\n\napp = Robyn(__file__)\n\n@app.get(\"/events/json\")\ndef stream_json_events(request):\n    def json_event_generator():\n        for i in range(5):\n            data = {\n                \"id\": i,\n                \"message\": f\"Update {i}\",\n                \"timestamp\": time.time()\n            }\n            yield SSEMessage(\n                json.dumps(data), \n                event=\"update\", \n                id=str(i)\n            )\n            time.sleep(2)\n    return SSEResponse(json_event_generator())\n```\n\n```python {{ title: 'Async Generator Stream' }}\nfrom robyn import Robyn, SSEResponse, SSEMessage\nimport asyncio\nimport time\n\napp = Robyn(__file__)\n\n@app.get(\"/events/async\")\nasync def stream_async_events(request):\n    async def async_event_generator():\n        for i in range(8):\n            # Simulate async work like database calls\n            await asyncio.sleep(0.5)\n            yield SSEMessage(\n                f\"Async message {i} - {time.strftime('%H:%M:%S')}\", \n                event=\"async\", \n                id=str(i)\n            )\n    \n    return SSEResponse(async_event_generator())\n```\n\n</CodeGroup>\n</Col>\n</Row>\n\n---\n\n## Async Generators\n\n<Row>\n<Col>\n\nWhen Batman needs to perform async operations during his SSE streams - like fetching data from databases or making API calls - he uses async generators with `async def` and `await`. This allows him to handle multiple streams concurrently without blocking other operations.\n\nThe key difference is using `async def` for the generator function and `await` for async operations inside the generator:\n\n</Col>\n\n<Col sticky>\n\n<CodeGroup title=\"Advanced Async SSE\" tag=\"GET\" label=\"/events/database\">\n\n```python {{ title: 'Database Stream' }}\nfrom robyn import Robyn, SSEResponse, SSEMessage\nimport asyncio\nimport json\nimport time\n\napp = Robyn(__file__)\n\n@app.get(\"/events/database\")\nasync def stream_database_events(request):\n    async def database_event_generator():\n        for i in range(10):\n            # Simulate async database query\n            await asyncio.sleep(0.3)\n            \n            # Simulate fetching data from database\n            data = {\n                \"crime_id\": i,\n                \"location\": f\"Gotham District {i}\",\n                \"severity\": \"high\" if i % 2 == 0 else \"low\",\n                \"timestamp\": time.time()\n            }\n            \n            yield SSEMessage(\n                json.dumps(data),\n                event=\"crime_alert\",\n                id=str(i)\n            )\n    \n    return SSEResponse(database_event_generator())\n```\n\n```python {{ title: 'API Integration Stream' }}\nfrom robyn import Robyn, SSEResponse, SSEMessage\nimport asyncio\nimport aiohttp\nimport json\n\napp = Robyn(__file__)\n\n@app.get(\"/events/api\")\nasync def stream_api_events(request):\n    async def api_event_generator():\n        async with aiohttp.ClientSession() as session:\n            for i in range(5):\n                try:\n                    # Make async API call\n                    async with session.get(f\"https://api.example.com/data/{i}\") as response:\n                        data = await response.json()\n                        \n                        yield SSEMessage(\n                            json.dumps(data),\n                            event=\"api_update\",\n                            id=str(i)\n                        )\n                except Exception as e:\n                    yield SSEMessage(\n                        f\"Error fetching data: {str(e)}\",\n                        event=\"error\",\n                        id=f\"error_{i}\"\n                    )\n                \n                await asyncio.sleep(1)\n    \n    return SSEResponse(api_event_generator())\n```\n\n</CodeGroup>\n</Col>\n</Row>\n\n---\n\n## What's next?\n\nBatman has mastered Server-Sent Events and can now stream real-time updates to his crime dashboard. While SSE is perfect for one-way communication from server to client, Batman realizes he needs bidirectional communication for more interactive features like real-time chat with his allies.\n\nNext, he wants to explore how to handle bidirectional communication with [WebSockets](/documentation/en/api_reference/websockets) for more interactive features.\n\nIf Batman needs to handle unexpected situations, he'll learn about [Exception handling](/documentation/en/api_reference/exceptions) to make his applications more robust.\n\nFor scaling his crime monitoring system across multiple processes, Batman will explore [Scaling the Application](/documentation/en/api_reference/scaling).\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/api_reference/templating.mdx",
    "content": "export const description =\n  'On this page, we’ll dive into the different conversation endpoints you can use to manage conversations programmatically.'\n\n\n\n## Templating.\n\nBatman wanted to quickly render html pages on the website. He wanted to use a templating engine to render the html pages. Robyn told him that he can use the Jinja2 templating engine to render the html pages. He can use the `JinjaTemplate` class to render the html pages.\n\n\n<Row>\n<Col>\nBatman was excited to learn that he could add events as functions as well as decorators.\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    from robyn.templating import JinjaTemplate\n\n    current_file_path = pathlib.Path(__file__).parent.resolve()\n    JINJA_TEMPLATE = JinjaTemplate(os.path.join(current_file_path, \"templates\"))\n\n    @app.get(\"/template_render\")\n    def template_render():\n        context = {\"framework\": \"Robyn\", \"templating_engine\": \"Jinja2\"}\n\n        template = JINJA_TEMPLATE.render_template(template_name=\"test.html\", **context)\n        return template\n\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n<Row>\n\n  <Col>\n  test.html file\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n      ```html\n        <!DOCTYPE html>\n        <html lang=\"en\">\n        <head>\n          <meta charset=\"utf-8\">\n          <title>Results</title>\n        </head>\n\n        <body>\n         Hello {{ framework }}! You're using {{ templating_engine }}.\n        </body>\n      ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n\n## Supporting Custom Templating Engines\n\nBatman was also super excited to know that Robyn allows the support of custom templating engines.\n\n<Row>\n<Col>\nTo do that, you need to import the `TemplateInterface` from `robyn.templating`\n</Col>\n  <Col sticky>\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n      ```python\n      from robyn.templating import TemplateInterface\n      ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n\n<Row>\n<Col>\nThen You need to have a `render_template` method inside your implementation. So, an example would look like the following:\n</Col>\n  <Col sticky>\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n      ```python\n      class JinjaTemplate(TemplateInterface):\n        def __init__(self, directory, encoding=\"utf-8\", followlinks=False):\n            self.env = Environment(\n                loader=FileSystemLoader(\n                    searchpath=directory, encoding=encoding, followlinks=followlinks\n                )\n            )\n\n        def render_template(self, template_name: str, **kwargs):\n            return self.env.get_template(template_name).render(**kwargs)\n      ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n\n\n## What's next?\n\nNow, Batman wanted to have the ability to redirect the endpoints.\n\n- [Redirection](/documentation/en/api_reference/redirection)\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/api_reference/timeout_configuration.mdx",
    "content": "export const description =\n  'Configure timeout settings to handle high-concurrency scenarios and prevent resource exhaustion.'\n\n# Timeout Configuration\n\nRobyn supports comprehensive timeout configuration to handle high-concurrency scenarios and prevent resource exhaustion like the \"Too many open files\" error. {{ className: 'lead' }}\n\n## Configuration Options\n\n### Method Parameters\n\nConfigure timeouts directly in the `app.start()` method:\n\n```python\nfrom robyn import Robyn\n\napp = Robyn(__file__)\n\n@app.get(\"/\")\nasync def hello(request):\n    return \"Hello, world!\"\n\n# Configure timeout settings\napp.start(\n    host=\"0.0.0.0\", \n    port=8080,\n    client_timeout=30,          # Client connection timeout (seconds)\n    keep_alive_timeout=20       # Keep-alive timeout (seconds)\n)\n```\n\n### Environment Variables\n\nOverride configuration using environment variables:\n\n```bash\n# Set timeout configurations\nexport ROBYN_CLIENT_TIMEOUT=45\nexport ROBYN_KEEP_ALIVE_TIMEOUT=30\n\n# Start your application\npython app.py\n```\n\n## Configuration Parameters\n\n| Parameter | Default | Description | Environment Variable |\n|-----------|---------|-------------|---------------------|\n| `client_timeout` | 30 | Maximum time (seconds) for client request processing | `ROBYN_CLIENT_TIMEOUT` |\n| `keep_alive_timeout` | 20 | Time (seconds) to keep idle connections alive | `ROBYN_KEEP_ALIVE_TIMEOUT` |\n\n## Usage Examples\n\n### Basic Configuration\n\n```python\n# Minimal timeout configuration\napp.start(client_timeout=30)\n```\n\n### High-Traffic Production Setup\n\n```python\n# Optimized for high-traffic scenarios\napp.start(\n    host=\"0.0.0.0\",\n    port=8080,\n    client_timeout=60,      # Allow longer processing time\n    keep_alive_timeout=15   # Shorter keep-alive for faster turnover\n)\n```\n\n### Development Setup\n\n```python\n# Development-friendly settings\napp.start(\n    client_timeout=300,     # Long timeout for debugging\n    keep_alive_timeout=60   # Longer keep-alive for testing\n)\n```\n\n### Load Testing Configuration\n\n```python\n# Optimized for load testing with tools like wrk\napp.start(\n    client_timeout=10,      # Quick timeouts\n    keep_alive_timeout=5    # Fast connection turnover\n)\n```\n\n## Environment Variable Priority\n\nEnvironment variables take precedence over method parameters:\n\n```python\n# This will use ROBYN_CLIENT_TIMEOUT=60 if set, otherwise 30\napp.start(client_timeout=30)\n```\n\n## Troubleshooting\n\n### \"Too Many Open Files\" Error\n\nIf you encounter file descriptor exhaustion:\n\n1. **Increase system limits:**\n   ```bash\n   ulimit -n 65536\n   ```\n\n2. **Optimize timeout settings:**\n   ```python\n   app.start(\n       client_timeout=15,      # Shorter timeouts\n       keep_alive_timeout=5    # Faster connection cleanup\n   )\n   ```\n\n3. **Use environment variables for deployment:**\n   ```bash\n   export ROBYN_CLIENT_TIMEOUT=15\n   export ROBYN_KEEP_ALIVE_TIMEOUT=5\n   ```\n\n### Performance Tuning\n\n**For high-throughput APIs:**\n- Lower `keep_alive_timeout` (5-15s)\n- Moderate `client_timeout` (15-30s)\n\n**For long-running operations:**\n- Higher `client_timeout` (60-300s)\n- Standard `keep_alive_timeout` (20-30s)\n\n\n## Best Practices\n\n1. **Always set explicit timeouts** in production\n2. **Use environment variables** for deployment-specific configuration\n3. **Test timeout settings** with realistic load patterns\n4. **Start with conservative values** and tune based on metrics\n\n## Migration Guide\n\n### From Previous Versions\n\nIf upgrading from earlier Robyn versions, the default behavior changes:\n\n**Before (infinite timeout):**\n```python\n# Previously: no timeout (could cause resource exhaustion)\napp.start(host=\"0.0.0.0\", port=8080)\n```\n\n**After (sensible defaults):**\n```python\n# Now: automatic 30s client timeout, 20s keep-alive\napp.start(host=\"0.0.0.0\", port=8080)\n# Equivalent to:\napp.start(\n    host=\"0.0.0.0\", \n    port=8080,\n    client_timeout=30,\n    keep_alive_timeout=20\n)\n```"
  },
  {
    "path": "docs_src/src/pages/documentation/en/api_reference/using_rust_directly.mdx",
    "content": "\n## Using Rust to extend Robyn\n\n\nThere may be occasions where Batman may be working with a high computation task, or a task that requires a lot of memory. In such cases, he may want to use Rust to implement that task. Robyn introduces a special way to do this. Not only you can use Rust to extend Python code, you can do it while maintaining the hot reloading nature of your codebase. Making it *feel* like an interpreted version in many situations.\n\n\n\n<Row>\n<Col>\nThe first thing you need to is to create a Rust file. Let's call it `hello_world.rs`. You can do it using the cli:\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    python -m robyn --create-rust-file hello_world\n    ```\n\n    </CodeGroup>\n  </Col>\n</Row>\n<Row>\n\n  <Col>\n  Then you can open the file and write your Rust code. For example, let's write a function that returns a string.\n\n\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n      ```rust\n      // hello_world.rs\n\n      // rustimport:pyo3\n\n      use pyo3::prelude::*;\n\n      #[pyfunction]\n      fn square(n: i32) -> i32 {\n          n * n\n          // this is another comment\n      }\n\n      ```\n\n    </CodeGroup>\n  </Col>\n</Row>\n\n<Row>\n\n  <Col>\n  Every Rust file that you create using the cli will have a special comment at the top of the file. This comment is used by Robyn to know which dependencies to import. In this case, we are importing the `pyo3` crate. You can import as many crates as you want. You can also import crates from crates.io. For example, if you want to use the `rusqlite` crate, you can do it like this:\n\n\n\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n      ```rust\n      // rustimport:pyo3\n\n    //:\n    //: [dependencies]\n    //: rusqlite = \"0.19.0\"\n\n      use pyo3::prelude::*;\n\n      #[pyfunction]\n      fn square(n: i32) -> i32 {\n          n * n * n\n          // this is another comment\n      }\n\n      ```\n\n    </CodeGroup>\n  </Col>\n</Row>\n\n\n<Row>\n  Then you can import the function in your Python code and use it.\n\n  <Col sticky>\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n      ```python\n      from hello_world import square\n\n      print(square(5))\n      ```\n    </CodeGroup>\n  </Col>\n\n  To run the code, you need to use the `--compile-rust-path` flag. This will compile the Rust code and run it. You can also use the `--dev` flag to watch for changes in the Rust code and recompile it on the fly.\n\n  <Col sticky>\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n      ```python\n      python -m robyn --compile-rust-path \".\" --dev\n      ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\nAn example of a Robyn app with a Rust file that using the `rusqlite` crate to connect to a database and return the number of rows in a table: https://github.com/sansyrox/rusty-sql\n\n\n## What's next?\n\n\nBatman was curious to know what else he could do with Robyn. \n\nRobyn told him to keep an eye on the GraphQl support.\n\n[GraphQl Support](/documentation/en/api_reference/graphql_support)\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/api_reference/websockets.mdx",
    "content": "export const description =\n  'Learn how to use Robyn\\'s WebSocket API for real-time, bidirectional communication — including message streaming, connect/close callbacks, broadcasting, and common patterns like live updates, presence tracking, and low-latency messaging.'\n\n\n## WebSockets {{ tag: 'WebSockets', label: 'WebSockets' }}\n\n<Row>\n<Col>\nAfter mastering [Server-Sent Events](/documentation/en/api_reference/server_sent_events) for one-way communication, Batman realized he needed something more powerful. When Commissioner Gordon wanted to chat with him in real-time during crisis situations, Batman needed bidirectional communication.\n\n\"SSE is great for pushing updates to my dashboard,\" Batman thought, \"but I need two-way communication for coordinating with my allies!\"\n\nTo handle real-time bidirectional communication, Batman learned how to work with WebSockets using Robyn's modern decorator-based API. Under the hood, messages flow through Rust channels for maximum performance — no Python GIL overhead during message dispatch.\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"WebSocket\" label=\"/web_socket\">\n\n    ```python {{ title: 'Basic Echo' }}\n    from robyn import Robyn\n\n    app = Robyn(__file__)\n\n    @app.websocket(\"/web_socket\")\n    async def handler(websocket):\n        while True:\n            msg = await websocket.receive_text()\n            await websocket.send_text(f\"Echo: {msg}\")\n\n    app.start()\n    ```\n\n    ```python {{ title: 'With Callbacks' }}\n    from robyn import Robyn, WebSocketDisconnect\n\n    app = Robyn(__file__)\n\n    @app.websocket(\"/web_socket\")\n    async def handler(websocket):\n        try:\n            while True:\n                msg = await websocket.receive_text()\n                await websocket.send_text(f\"Echo: {msg}\")\n        except WebSocketDisconnect:\n            print(f\"Client {websocket.id} disconnected\")\n\n    @handler.on_connect\n    def on_connect(websocket):\n        return \"Connected to ws\"\n\n    @handler.on_close\n    def on_close(websocket):\n        return \"Goodbye world, from ws\"\n\n    app.start()\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n---\n\n## Receiving Messages {{ tag: 'receive_text', label: 'receive_text' }}\n\n<Row>\n  <Col>\n    The `receive_text()` method blocks until the next message arrives from the client. It is backed by a Rust `tokio::mpsc` channel, so the Python handler genuinely suspends without holding the GIL.\n\n    When the client disconnects, `receive_text()` raises `WebSocketDisconnect`. You can either catch it explicitly or let the internal wrapper handle it silently.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Receiving Messages\" tag=\"WebSocket\" label=\"/web_socket\">\n\n      ```python {{ title: 'Text Messages' }}\n      @app.websocket(\"/ws\")\n      async def handler(websocket):\n          try:\n              while True:\n                  msg = await websocket.receive_text()\n                  await websocket.send_text(f\"Got: {msg}\")\n          except WebSocketDisconnect:\n              print(f\"Client {websocket.id} disconnected\")\n      ```\n\n      ```python {{ title: 'JSON Messages' }}\n      @app.websocket(\"/api\")\n      async def handler(websocket):\n          while True:\n              data = await websocket.receive_json()\n              result = process(data)\n              await websocket.send_json({\"status\": \"ok\", \"result\": result})\n      ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n---\n\n## Sending Messages {{ tag: 'send_text', label: 'send_text' }}\n\n<Row>\n  <Col>\n    To send a message to the current client, use `send_text()` or `send_json()`. All send methods are async.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Sending Messages\" tag=\"WebSocket\" label=\"/web_socket\">\n\n      ```python {{ title: 'Send Text' }}\n      @app.websocket(\"/ws\")\n      async def handler(websocket):\n          while True:\n              msg = await websocket.receive_text()\n              await websocket.send_text(f\"Echo: {msg}\")\n      ```\n\n      ```python {{ title: 'Send JSON' }}\n      @app.websocket(\"/ws\")\n      async def handler(websocket):\n          while True:\n              data = await websocket.receive_json()\n              await websocket.send_json({\"echo\": data})\n      ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n---\n\n## Broadcasting {{ tag: 'broadcast', label: 'broadcast' }}\n\n<Row>\n  <Col>\n    To send a message to all connected clients on the same WebSocket endpoint, use the `broadcast()` method.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Broadcasting\" tag=\"WebSocket\" label=\"/chat\">\n\n      ```python {{ title: 'Broadcast' }}\n      @app.websocket(\"/chat\")\n      async def handler(websocket):\n          while True:\n              msg = await websocket.receive_text()\n              # Send to all connected clients\n              await websocket.broadcast(f\"User {websocket.id}: {msg}\")\n              # Also send a confirmation to this client only\n              await websocket.send_text(\"Your message was sent\")\n      ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n---\n\n## Query Parameters {{ tag: 'query_params', label: 'query_params' }}\n\n<Row>\n  <Col>\n    You can access query parameters from the WebSocket connection URL via `websocket.query_params`.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Query Params\" tag=\"WebSocket\" label=\"/ws?name=gordon&role=commissioner\">\n\n      ```python {{ title: 'Query Params' }}\n      @app.websocket(\"/ws\")\n      async def handler(websocket):\n          name = websocket.query_params.get(\"name\")\n          role = websocket.query_params.get(\"role\")\n\n          if name == \"gordon\" and role == \"commissioner\":\n              await websocket.broadcast(\"Gordon authorized!\")\n\n          while True:\n              msg = await websocket.receive_text()\n              await websocket.send_text(f\"Hello {name}: {msg}\")\n      ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n---\n\n## Easy Access Query Parameters {{ tag: 'easy_access', label: 'easy_access' }}\n\n<Row>\n  <Col>\n    Instead of manually calling `websocket.query_params.get(...)`, you can declare typed query parameters directly in your handler, `on_connect`, and `on_close` signatures. Robyn will automatically resolve and coerce them — just like HTTP easy access parameters.\n\n    Parameters with defaults are optional. Parameters without defaults are required — if missing, the connection is rejected with an error message.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Easy Access Query Params\" tag=\"WebSocket\" label=\"/ws?room=chat&page=5\">\n\n      ```python {{ title: 'Handler' }}\n      @app.websocket(\"/ws\")\n      async def handler(websocket, room: str = \"default\", page: int = 1):\n          try:\n              while True:\n                  msg = await websocket.receive_text()\n                  await websocket.send_text(\n                      f\"room={room} page={page} msg={msg}\"\n                  )\n          except WebSocketDisconnect:\n              pass\n      ```\n\n      ```python {{ title: 'Callbacks' }}\n      @handler.on_connect\n      def on_connect(websocket, room: str = \"default\"):\n          return f\"connected to {room}\"\n\n      @handler.on_close\n      def on_close(websocket, room: str = \"default\"):\n          return f\"left {room}\"\n      ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n---\n\n## Closing Connections {{ tag: 'close', label: 'close' }}\n\n<Row>\n  <Col>\n    To programmatically close a WebSocket connection from the server side, use `websocket.close()`. This will:\n1. Close the WebSocket connection.\n2. Remove the client from the WebSocket registry.\n3. Cause any pending `receive_text()` to raise `WebSocketDisconnect`.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Close Connection\" tag=\"WebSocket\" label=\"/ws\">\n\n      ```python {{ title: 'Server Close' }}\n      @app.websocket(\"/ws\")\n      async def handler(websocket):\n          while True:\n              msg = await websocket.receive_text()\n              if msg == \"quit\":\n                  await websocket.close()\n                  break\n              await websocket.send_text(f\"Got: {msg}\")\n      ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n---\n\n## Connect and Close Callbacks {{ tag: 'Callbacks', label: 'Callbacks' }}\n\n<Row>\n  <Col>\n    You can attach optional `on_connect` and `on_close` callbacks to your WebSocket handler. These are decorators on the handler function itself.\n\n    - `on_connect` is called when a new client connects. Its return value is sent to the client as the first message.\n    - `on_close` is called when the connection closes. Its return value is sent to the client as the final message.\n\n    Both callbacks receive a `websocket` object with access to `id` and `query_params`. Both are optional.\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Callbacks\" tag=\"WebSocket\" label=\"/chat\">\n\n      ```python {{ title: 'Callbacks' }}\n      @app.websocket(\"/chat\")\n      async def chat(websocket):\n          while True:\n              msg = await websocket.receive_text()\n              await websocket.broadcast(msg)\n\n      @chat.on_connect\n      def on_connect(websocket):\n          return f\"Welcome, {websocket.id}!\"\n\n      @chat.on_close\n      def on_close(websocket):\n          return \"Goodbye!\"\n      ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n---\n\n## WebSocket API Reference {{ tag: 'API', label: 'API' }}\n\n<Row>\n  <Col>\n    The `websocket` object passed to handlers exposes the following methods and properties:\n\n    | Method / Property | Description |\n    |---|---|\n    | `await websocket.receive_text()` | Block until next message; raises `WebSocketDisconnect` on close |\n    | `await websocket.receive_bytes()` | Block until next binary message; raises `WebSocketDisconnect` on close |\n    | `await websocket.receive_json()` | Same as `receive_text()` but JSON-decoded |\n    | `await websocket.send_text(data)` | Send string to this client |\n    | `await websocket.send_bytes(data)` | Send binary data to this client |\n    | `await websocket.send_json(data)` | Send JSON to this client |\n    | `await websocket.broadcast(data)` | Send to all clients on this endpoint |\n    | `await websocket.close()` | Close the connection server-side |\n    | `websocket.id` | Connection UUID string |\n    | `websocket.query_params` | Query parameters from the connection URL |\n  </Col>\n</Row>\n\n---\n\n## What's next?\n\nAs the codebase grew, Batman wanted to onboard the justice league to help him manage the application. \n\nRobyn told him about the different ways he could scale his application, and how to use views and subrouters to make his code more readable. \n\n\n- [Views and SubRouters](/documentation/en/api_reference/views)\n\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/api_reference/zh/getting_started.mdx",
    "content": "export const description =\n  '开始使用Robyn构建您的第一个应用程序。'\n\n# 入门指南\n\n## 创建您的第一个Robyn应用程序\n\n让我们从创建一个简单的Robyn应用程序开始。首先，使用pip安装Robyn：\n\n```bash\npip install robyn\n```\n\n然后，创建一个新的Python文件 `app.py`：\n\n```python\nfrom robyn import Robyn\n\napp = Robyn(__file__)\n\n@app.get(\"/\")\nasync def hello_world():\n    return \"Hello, World!\"\n\nif __name__ == \"__main__\":\n    app.start()\n```\n\n现在运行您的应用程序：\n\n```bash\npython app.py\n```\n\n访问 `http://localhost:8080`，您应该能看到 \"Hello, World!\" 消息。\n\n## 命令行选项\n\nRobyn提供了多个命令行选项来配置您的应用程序：\n\n```bash\npython app.py --help\n```\n\n输出将显示所有可用选项：\n\n```text\noptions:\n  -h, --help            显示帮助信息\n  --processes PROCESSES\n                        选择进程数量。[默认值: 1]\n  --workers WORKERS     选择工作线程数量。[默认值: 1]\n  --dev                 开发模式。根据文件更改自动重启服务器。\n  --log-level LOG_LEVEL\n                        设置日志级别\n  --create              创建新项目模板。\n  --docs                打开Robyn文档。\n  --open-browser        成功启动时打开浏览器。\n  --version            显示Robyn版本。\n  --compile-rust-path COMPILE_RUST_PATH\n                        编译指定路径中的rust文件。\n  --create-rust-file CREATE_RUST_FILE\n                        创建指定名称的rust文件。\n  --disable-openapi     禁用OpenAPI文档。\n  --fast               快速模式。设置最优的进程、工作线程和日志级别。但您可以覆盖这些设置。\n```\n\n## 路由装饰器\n\nRobyn支持所有标准的HTTP方法：\n\n```python\n@app.get(\"/users\")\nasync def get_users():\n    return {\"users\": [\"user1\", \"user2\"]}\n\n@app.post(\"/users\")\nasync def create_user(request):\n    return {\"message\": \"用户已创建\"}\n\n@app.put(\"/users/:id\")\nasync def update_user(request):\n    return {\"message\": \"用户已更新\"}\n\n@app.delete(\"/users/:id\")\nasync def delete_user(request):\n    return {\"message\": \"用户已删除\"}\n```\n\n## 请求参数\n\n您可以通过多种方式访问请求数据：\n\n```python\n@app.post(\"/api/data\")\nasync def handle_data(request):\n    # 访问JSON数据\n    json_data = request.json()\n    \n    # 访问查询参数\n    query_params = request.query_params\n    \n    # 访问路径参数\n    path_params = request.path_params\n    \n    # 访问请求头\n    headers = request.headers\n    \n    return {\n        \"json\": json_data,\n        \"query\": query_params,\n        \"path\": path_params,\n        \"headers\": headers\n    }\n```\n\n## 响应类型\n\nRobyn支持多种响应类型：\n\n```python\nfrom robyn import Response, jsonify\n\n@app.get(\"/text\")\nasync def text_response():\n    return \"纯文本响应\"\n\n@app.get(\"/json\")\nasync def json_response():\n    return jsonify({\"message\": \"JSON响应\"})\n\n@app.get(\"/custom\")\nasync def custom_response():\n    return Response(\n        status_code=201,\n        description=\"自定义响应\",\n        headers={\"Content-Type\": \"text/plain\"}\n    )\n``` "
  },
  {
    "path": "docs_src/src/pages/documentation/en/architecture.mdx",
    "content": "export const description = 'Robyn is a Python web server that employs the tokio runtime and leverages a blend of Python and Rust, enabling efficient request handling through a worker event cycle, multi-core scaling, and introducing \"Const Requests\" for optimized, cached responses in a multi-threaded environment.'\n\n\n## Robyn Architecture Overview\n\nRobyn's unique architecture combines Python's ease of development with Rust's performance. This hybrid design allows developers to write familiar Python code while benefiting from Rust's speed and memory safety.\n\n## The Python-Rust Hybrid Design\n\n### Two-Layer Architecture\n\nRobyn operates on two interconnected but distinct layers:\n\n**Python Layer (Developer Interface)**:\n- Route definitions and decorators (`@app.get`, `@app.post`, etc.)\n- Request parameter injection and validation\n- Business logic execution\n- Middleware configuration\n- Response formatting\n\n**Rust Layer (Performance Engine)**:\n- HTTP request parsing and validation\n- URL routing and pattern matching\n- WebSocket connection management  \n- Static file serving\n- Response serialization\n- Memory management\n\n### Communication Bridge\n\nThe layers communicate through **PyO3**, a Rust crate that enables seamless Python-Rust interoperability:\n\n1. **Function Registration**: Python route handlers are registered with the Rust runtime at startup\n2. **Request Flow**: Rust handles incoming HTTP requests and calls Python handlers via PyO3\n3. **Response Processing**: Python responses are converted back to Rust for efficient HTTP serialization\n\n### Why This Design Works\n\n- **Best of Both Worlds**: Python's productivity with Rust's performance\n- **Zero-Copy Operations**: Minimal data copying between layers\n- **Memory Safety**: Rust prevents common server vulnerabilities\n- **Async Integration**: Seamless integration with Python's asyncio\n\n## Server Process Model\n\nRobyn is built on a multi-process, multi-threaded model that maximizes hardware utilization:\n\n## Master Process\n\nThe master process in Robyn is responsible for initializing the server, managing worker processes, and handling signals. It creates a socket and passes it to the worker processes, allowing them to accept connections. The master process is implemented in Python, providing a familiar interface for developers while leveraging Rust's performance for core operations.\n\n\n```python\n216:257:robyn/__init__.py\n    def start(self, host: str = \"127.0.0.1\", port: int = 8080, _check_port: bool = True):\n        \"\"\"\n        Starts the server\n\n        :param host str: represents the host at which the server is listening\n        :param port int: represents the port number at which the server is listening\n        :param _check_port bool: represents if the port should be checked if it is already in use\n        \"\"\"\n\n        host = os.getenv(\"ROBYN_HOST\", host)\n        port = int(os.getenv(\"ROBYN_PORT\", port))\n        open_browser = bool(os.getenv(\"ROBYN_BROWSER_OPEN\", self.config.open_browser))\n\n        if _check_port:\n            while self.is_port_in_use(port):\n                logger.error(\"Port %s is already in use. Please use a different port.\", port)\n                try:\n                    port = int(input(\"Enter a different port: \"))\n                except Exception:\n                    logger.error(\"Invalid port number. Please enter a valid port number.\")\n                    continue\n\n        logger.info(\"Robyn version: %s\", __version__)\n        logger.info(\"Starting server at http://%s:%s\", host, port)\n\n        mp.allow_connection_pickling()\n\n        run_processes(\n            host,\n            port,\n            self.directories,\n            self.request_headers,\n            self.router.get_routes(),\n            self.middleware_router.get_global_middlewares(),\n            self.middleware_router.get_route_middlewares(),\n            self.web_socket_router.get_routes(),\n            self.event_handlers,\n            self.config.workers,\n            self.config.processes,\n            self.response_headers,\n            open_browser,\n        )\n```\n\n\n## Worker Processes\n\nRobyn uses multiple worker processes to handle incoming requests. Each worker process is capable of managing multiple threads, allowing for efficient concurrent processing. The number of worker processes can be configured using the `--processes` flag, with a default of 1.\n\n\n```python\n66:116:robyn/processpool.py\ndef init_processpool(\n    directories: List[Directory],\n    request_headers: Headers,\n    routes: List[Route],\n    global_middlewares: List[GlobalMiddleware],\n    route_middlewares: List[RouteMiddleware],\n    web_sockets: Dict[str, WebSocket],\n    event_handlers: Dict[Events, FunctionInfo],\n    socket: SocketHeld,\n    workers: int,\n    processes: int,\n    response_headers: Headers,\n) -> List[Process]:\n    process_pool = []\n    if sys.platform.startswith(\"win32\") or processes == 1:\n        spawn_process(\n            directories,\n            request_headers,\n            routes,\n            global_middlewares,\n            route_middlewares,\n            web_sockets,\n            event_handlers,\n            socket,\n            workers,\n            response_headers,\n        )\n\n        return process_pool\n\n    for _ in range(processes):\n        copied_socket = socket.try_clone()\n        process = Process(\n            target=spawn_process,\n            args=(\n                directories,\n                request_headers,\n                routes,\n                global_middlewares,\n                route_middlewares,\n                web_sockets,\n                event_handlers,\n                copied_socket,\n                workers,\n                response_headers,\n            ),\n        )\n        process.start()\n        process_pool.append(process)\n\n    return process_pool\n```\n\n\n## Worker Threads\n\nWithin each worker process, Robyn utilizes multiple threads to handle requests concurrently. The number of worker threads can be configured using the `--workers` flag. By default, Robyn uses a single worker thread per process.\n\n## Request Processing Flow\n\nUnderstanding how requests flow through Robyn's hybrid architecture:\n\n### 1. Request Arrival\n```\nHTTP Request → Rust HTTP Parser → Fast Validation\n```\n\n### 2. Routing and Matching\n```\nValidated Request → Rust Router (matchit crate) → Route Resolution\n```\n\n### 3. Parameter Extraction\n```\nMatched Route → Rust Parameter Parser → Path/Query/Header Extraction\n```\n\n### 4. Python Handler Execution\n```\nExtracted Parameters → PyO3 Bridge → Python Handler → Response\n```\n\n### 5. Response Processing\n```\nPython Response → Rust Serializer → HTTP Response → Client\n```\n\n## Rust Integration Deep Dive\n\nRobyn's Rust integration is powered by the **Tokio** async runtime and several high-performance crates:\n\n### Core Components\n\n- **Tokio Runtime**: Handles async I/O and task scheduling\n- **Actix Web**: Provides HTTP server functionality\n- **PyO3**: Enables Python-Rust communication\n- **matchit**: Ultra-fast URL routing with radix tree implementation\n- **Serde**: JSON serialization/deserialization\n\n### Performance Benefits\n\n1. **Zero-allocation routing** using compiled radix trees\n2. **Memory-efficient HTTP parsing** with minimal allocations\n3. **Async task scheduling** without GIL interference\n4. **Direct memory access** for static file serving\n\n\n```python\n76:107:src/server.rs\n    pub fn start(\n        &mut self,\n        py: Python,\n        socket: &PyCell<SocketHeld>,\n        workers: usize,\n    ) -> PyResult<()> {\n        pyo3_log::init();\n\n        if STARTED\n            .compare_exchange(false, true, SeqCst, Relaxed)\n            .is_err()\n        {\n            debug!(\"Robyn is already running...\");\n            return Ok(());\n        }\n\n        let raw_socket = socket.try_borrow_mut()?.get_socket();\n\n        let router = self.router.clone();\n        let const_router = self.const_router.clone();\n        let middleware_router = self.middleware_router.clone();\n        let web_socket_router = self.websocket_router.clone();\n        let global_request_headers = self.global_request_headers.clone();\n        let global_response_headers = self.global_response_headers.clone();\n        let directories = self.directories.clone();\n\n        let asyncio = py.import(\"asyncio\")?;\n        let event_loop = asyncio.call_method0(\"new_event_loop\")?;\n        asyncio.call_method1(\"set_event_loop\", (event_loop,))?;\n\n        let startup_handler = self.startup_handler.clone();\n        let shutdown_handler = self.shutdown_handler.clone();\n```\n\n\n## Const Requests Optimization\n\nRobyn's \"Const Requests\" feature provides significant performance improvements for static endpoints:\n\n### How Const Requests Work\n\n1. **Route Registration**: Routes marked with `const=True` are identified at startup\n2. **Response Caching**: The first response is cached in Rust memory\n3. **Direct Serving**: Subsequent requests bypass Python entirely\n4. **Zero Overhead**: Responses are served directly from Rust with minimal CPU usage\n\n### Performance Impact\n\n- **10x faster response times** compared to regular Python handlers\n- **Minimal memory usage** with efficient caching\n- **No Python GIL contention** for cached responses\n- **Ideal for**: Health checks, API metadata, configuration endpoints\n\n### Example Usage\n\n```python\nfrom robyn import Robyn\n\napp = Robyn(__file__)\n\n# Regular route - executes Python on every request\n@app.get(\"/dynamic\")\ndef dynamic_endpoint():\n    return {\"timestamp\": time.time()}  # Changes every request\n\n# Const route - cached in Rust after first request\n@app.get(\"/health\", const=True)\ndef health_check():\n    return {\"status\": \"healthy\", \"version\": \"1.0.0\"}  # Static response\n\n# Perfect for API metadata\n@app.get(\"/api/info\", const=True)\ndef api_info():\n    return {\n        \"name\": \"My API\",\n        \"version\": \"2.1.0\",\n        \"endpoints\": [\"/users\", \"/posts\", \"/health\"]\n    }\n```\n\n### When to Use Const Routes\n\n- **Health/status endpoints** that return consistent data\n- **API documentation** or metadata endpoints\n- **Configuration endpoints** with static values\n- **Version information** endpoints\n\n## Scaling Configuration Guide\n\n### Understanding Processes vs Workers\n\n**Processes**:\n- Independent Python interpreters\n- Share no memory (shared-nothing architecture)\n- Each process has its own GIL\n- Best for CPU-bound applications\n- Recommended: 1 process per CPU core\n\n**Workers** (within each process):\n- Threads sharing the same Python interpreter\n- Affected by Python's GIL\n- Better for I/O-bound operations\n- Recommended: 2-4 workers per process\n\n### Configuration Strategies\n\n#### CPU-Intensive Applications\n```bash\n# Favor more processes, fewer workers\npython app.py --processes=8 --workers=1\n\n# Example: Image processing, data analysis, calculations\n```\n\n#### I/O-Intensive Applications  \n```bash\n# Favor fewer processes, more workers\npython app.py --processes=2 --workers=8\n\n# Example: Database queries, API calls, file operations\n```\n\n#### Balanced Applications\n```bash\n# General-purpose configuration\npython app.py --processes=4 --workers=2\n\n# Most web applications fit this pattern\n```\n\n### Hardware-Based Recommendations\n\nFor a system with **N CPU cores**:\n\n| Application Type | Processes | Workers | Total Concurrency |\n|-----------------|-----------|---------|-------------------|\n| CPU-bound | N | 1 | N |\n| I/O-bound | N/2 | 4 | 2N |\n| Balanced | N/2 | 2 | N |\n| High-traffic | N | 2 | 2N |\n\n### Performance Testing\n\nAlways benchmark your specific application:\n\n```bash\n# Test different configurations\npython app.py --processes=1 --workers=1   # Baseline\npython app.py --processes=2 --workers=2   # Moderate scaling\npython app.py --processes=4 --workers=1   # CPU-focused\npython app.py --processes=2 --workers=4   # I/O-focused\npython app.py --fast                      # Auto-optimized\n```\n\n## Scaling Considerations\n\n- Robyn's multi-process model allows it to scale across multiple CPU cores effectively.\n- The combination of Python and Rust allows for both ease of development and high performance.\n- Const Requests feature can significantly improve performance for routes with constant output.\n- When scaling, consider both the number of processes and workers to find the optimal configuration for your hardware and application needs.\n\n## Development Mode\n\nRobyn provides a development mode that can be activated using the `--dev` flag. This mode is designed for ease of development and includes features like hot reloading. Note that in development mode, multi-process and multi-worker configurations are disabled to ensure consistent behavior during development.\n\n\n```python\n92:101:robyn/argument_parser.py\n        if self.dev and (self.processes != 1 or self.workers != 1):\n            raise Exception(\"--processes and --workers shouldn't be used with --dev\")\n\n        if self.dev and args.log_level is None:\n            self.log_level = \"DEBUG\"\n\n        elif args.log_level is None:\n            self.log_level = \"INFO\"\n        else:\n            self.log_level = args.log_level\n```\n\n\nBy understanding these design principles and adjusting the configuration accordingly, developers can leverage Robyn's unique architecture to build high-performance web applications that efficiently utilize system resources.\n\n\n## Design Diagram\n\n<img src=\"https://robyn.tech/architecture/architecture.png\" />\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/community-resources.mdx",
    "content": "export const description =\n  'On this page, we`ll take a look at some community resources contributed by our amazing members to help developers get started with Robyn.'\n\n\n## Talks\n- [EuroPython 2022](https://www.youtube.com/watch?v=AutugvJNVkY&)\n- [GeoPython 2022](https://www.youtube.com/watch?v=YCpbCQwbkd4)\n- [PyCon US 2022](https://youtu.be/1IiL31tUEVk?t=2101)\n- [PyCon Sweden 2021](https://www.youtube.com/watch?v=DK9teAs72Do)\n\n## Blogs\n- [Hello, Robyn!](https://www.sanskar.me/posts/hello-robyn)\n\n\n## Next Steps\n\nBatman was now interes\n\n- [Hosting](/documentation/hosting)\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/example_app/authentication-middlewares.mdx",
    "content": "export const description =\n  'Welcome to the Robyn API documentation. You will find comprehensive guides and documentation to help you start working with Robyn as quickly as possible, as well as support if you get stuck.'\n\n## Authentication and Authorization Middleware\nBatman added middleware to the Robyn application to verify the JWT tokens and to restrict access to certain endpoints based on the user's role.\n\n\n```python {{ title: 'Setting up Authentication Middlewares' }}\nfrom robyn.authentication import AuthenticationHandler, BearerGetter, Identity\n\n\nclass BasicAuthHandler(AuthenticationHandler):\n    def authenticate(self, request: Request):\n        token = self.token_getter.get_token(request)\n\n        try:\n            payload = crud.decode_access_token(token)\n            username = payload[\"sub\"]\n        except Exception:\n            return\n\n        with SessionLocal() as db:\n            user = crud.get_user_by_username(db, username=username)\n\n        return Identity(claims={\"user\": f\"{ user }\"})\n\n\napp.configure_authentication(BasicAuthHandler(token_getter=BearerGetter()))\n\n\n@app.get(\"/users/me\", auth_required=True)\nasync def get_current_user(request):\n    user = request.identity.claims[\"user\"]\n    return user\n\n\n```\n\nWith the web application in place, the Gotham City Police Department could now efficiently manage crime data and track criminal activities in real-time. Batman had successfully used the Robyn web framework to build a real-world application to help fight crime in Gotham City.\n\n\n\n\n<div className=\"not-prose\">\n  <Button\n    href=\"/documentation/en/example_app/real_time_notifications\"\n    variant=\"text\"\n    arrow=\"right\"\n    children=\"Checkout the real time notifications\"\n  />\n</div>\n\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/example_app/authentication.mdx",
    "content": "export const description =\n  'Welcome to the Robyn API documentation. You will find comprehensive guides and documentation to help you start working with Robyn as quickly as possible, as well as support if you get stuck.'\n\n## Authentication and Authorization\nTo restrict access to the crime data, Batman added authentication and authorization to the application. He decided to use JWT (JSON Web Token) for authentication. He created a new table for users and added an endpoint for user registration.\n\n\n## User Model\n\nBatman added a new User model to represent the users who can access the application.\n\n\n```python {{ title: 'Example request with basic auth' }}\n# models.py\nfrom sqlalchemy import Column, Integer, String, Boolean\n\nclass User(Base):\n    __tablename__ = \"users\"\n\n    id = Column(Integer, primary_key=True, index=True)\n    username = Column(String, unique=True, index=True)\n    hashed_password = Column(String)\n\n    \n    def __repr__(self):\n        return \"<User(id={id}, username={username}, hashed_password={hashed_password})>\".format(\n            id=self.id,\n            username=self.username,\n            hashed_password=self.hashed_password,\n        )\n\n\n```\n\nThen in crud.py, he added a new method to create a user.\n\n```python {{ title: 'Example request with basic auth' }}\n# crud.py\n# also need to do pip install passlib[bcrypt]\n\nfrom sqlalchemy.orm import Session\nfrom .models import User\n\nfrom passlib.context import CryptContext\n\npwd_context = CryptContext(schemes=[\"bcrypt\"], deprecated=\"auto\")\n\ndef get_password_hash(password: str) -> str:\n    return pwd_context.hash(password)\n\ndef get_user(db: Session, user_id: int):\n    return db.query(User).filter(User.id == user_id).first()\n\ndef create_user(db: Session, user: User):\n    hashed_password = get_password_hash(user.password)\n    db_user = User(username=user.username, hashed_password=hashed_password)\n    db.add(db_user)\n    db.commit()\n    db.refresh(db_user)\n    return db_user\n\n```\n\n\n## Authentication Utilities\n\nBatman created utility functions to handle authentication, including hashing passwords and verifying passwords.\n\n```python {{ title: 'Example request with bearer token' }}\n# crud.py\n# also need to do pip install passlib[bcrypt]\n# pip install \"python-jose[cryptography]\"\nfrom passlib.context import CryptContext\nfrom jose import JWTError, jwt\n\npwd_context = CryptContext(schemes=[\"bcrypt\"], deprecated=\"auto\")\n\ndef verify_password(plain_password, hashed_password):\n    return pwd_context.verify(plain_password, hashed_password)\n\ndef get_password_hash(password):\n    return pwd_context.hash(password)\n\nALGORITHM = \"HS256\"\nSECRET_KEY = \"your_secret_key\"\n\ndef create_access_token(data: dict, expires_delta: timedelta = None):\n    to_encode = data.copy()\n    if expires_delta:\n        expire = datetime.utcnow() + expires_delta\n    else:\n        expire = datetime.utcnow() + timedelta(minutes=15)\n    to_encode.update({\"exp\": expire})\n    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)\n    return encoded_jwt\n\ndef decode_access_token(token: str):\n    return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])\n\n\ndef authenticate_user(db: Session, username: str, password: str):\n    user = get_user_by_username(db, username)\n    if user is None:\n        return False\n    if not verify_password(password, user.hashed_password):\n        return False\n\n    created_token = create_access_token(data={\"sub\": user.username})\n    return created_token\n\n\n```\n\n\n## User Registration Endpoint\n\nBatman added a new endpoint to allow users to register.\n\n```python {{ title: 'Setting up Routes' }}\nfrom . import crud\n\n@app.post(\"/users/register\")\nasync def register_user(request):\n    user = request.json()\n    with SessionLocal() as db:\n        created_user = crud.create_user(db, user)\n    return created_user\n\n@app.post(\"/users/login\")\nasync def login_user(request):\n    user = request.json()\n    with SessionLocal() as db:\n        token = crud.authenticate_user(db, **user)\n\n    if token is None:\n        raise HTTPException(status_code=401, detail=\"Invalid credentials\")\n\n\n    return {\"access_token\": token}\n\n```\n\n<div className=\"not-prose\">\n  <Button\n    href=\"/documentation/en/example_app/authentication-middlewares\"\n    variant=\"text\"\n    arrow=\"right\"\n    children=\"Next: Authentication Middlewares\"\n  />\n</div>\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/example_app/deployment.mdx",
    "content": "export const description =\n  'Welcome to the Robyn API documentation. You will find comprehensive guides and documentation to help you start working with Robyn as quickly as possible, as well as support if you get stuck.'\n\n\n## Deploying the Application\n\nAfter thoroughly testing the web application and ensuring that all features were working as expected, Batman decided it was time to deploy it to a production server. He chose to use a robust and scalable platform, ensuring that his application would be available and performant at all times.\n\n\n```python {{ title: 'Deploying the Application' }}\npython app.py --processes=n --workers=m\n```\n\nWith the web application deployed and running smoothly, Batman had a powerful new tool at his disposal. The Robyn framework had provided him with the flexibility, scalability, and performance needed to create an effective crime-fighting application, giving him a technological edge in his ongoing battle to protect Gotham City.\n\n\n<div className=\"not-prose\">\n  <Button\n    href=\"/documentation/en/example_app/openapi\"\n    variant=\"text\"\n    arrow=\"right\"\n    children=\"OpenAPI Docs\"\n  />\n</div>\n\n\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/example_app/index.mdx",
    "content": "export const description =\n  'Welcome to the Robyn API documentation. You will find comprehensive guides and documentation to help you start working with Robyn as quickly as possible, as well as support if you get stuck.'\n\n# Real Life Web Apps with Robyn\n\nBatman was tasked with building a web application to manage the crime data in Gotham City. The application would allow the Gotham police department to store and retrieve data on criminal activities, suspects, and their locations. He decided to use the Robyn web framework to build this application efficiently and quickly.\n\nYou can find the source code for this application [here](https://github.com/sparckles/example_robyn_app).\n\n## Installing Robyn\n\nThe first step was to install Robyn. Batman created a virtual environment and installed Robyn using pip.\n\n```bash\n$ python3 -m venv venv\n$ source venv/bin/activate\n$ pip install robyn\n```\n\n## Creating a Robyn Application\n\nBatman wanted to create a Robyn app and was about to create an `src/app.py` before he was told that Robyn comes with a CLI tool to create a new application. He ran the following command to create a new Robyn application.\n\n```bash\n$ python -m robyn --create\n```\n\nThis, would result in the following output.\n\n```bash\n$ python3 -m robyn --create\n? Directory Path: .\n? Need Docker? (Y/N) Y\n? Please select project type (Mongo/Postgres/Sqlalchemy/Prisma):\n❯ No DB\n  Sqlite\n  Postgres\n  MongoDB\n  SqlAlchemy\n  Prisma\n```\n\nand the following directory structure.\n\n\nBatman was asked a set of questions to configure the application. He chose to use the default values for most of the questions.\n\nAnd he was done! The Robyn CLI created a new application with the following structure.\n\n```bash\n\n├── src\n│   ├── app.py\n├── Dockerfile\n\n```\n\n\n<div className=\"not-prose\">\n  <Button\n    href=\"/documentation/en/example_app/modeling_routes\"\n    variant=\"text\"\n    arrow=\"right\"\n    children=\"Modeling Routes\"\n  />\n</div>\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/example_app/modeling_routes.mdx",
    "content": "export const description =\n  'Welcome to the Robyn API documentation. You will find comprehensive guides and documentation to help you start working with Robyn as quickly as possible, as well as support if you get stuck.'\n\n\n## Crime Data Model and Database Connection\n\nBatman designed a data model to represent crime data, including information about the crime, suspect, and location. He decided to use a SQLite database to store the data and used an ORM (Object Relational Mapping) library to interact with the database.\n\n\n```python\n# models.py\nfrom sqlalchemy import create_engine, Column, Integer, String, DateTime, Float\nfrom sqlalchemy.orm import declarative_base, sessionmaker\n\nDATABASE_URL = \"sqlite:///./gotham_crime_data.db\"\n\nengine = create_engine(DATABASE_URL)\nSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)\n\n\nBase = declarative_base()\n\nclass Crime(Base):\n    __tablename__ = \"crimes\"\n\n    id = Column(Integer, primary_key=True, index=True)\n    type = Column(String, index=True)\n    description = Column(String)\n    location = Column(String)\n    suspect_name = Column(String)\n    date_time = Column(DateTime)\n    latitude = Column(Float)\n    longitude = Column(Float)\n\n```\n\n\n## Setting up the Robyn Application\n\nBatman set up a Robyn application and configured it to use the database session to access the SQLite database.\n\n\nBased on the Database model, Batman created a few helper methods to interact with the database. These methods would be used by the endpoints to perform CRUD operations on the database.\n\n```python {{ title: 'crud.py' }}\n# crud.py\nfrom sqlalchemy.orm import Session\nfrom .models import  Crime\n\n\ndef get_crime(db: Session, crime_id: int):\n    return db.query(Crime).filter(Crime.id == crime_id).first()\n\ndef get_crimes(db: Session, skip: int = 0, limit: int = 100):\n    return db.query(Crime).offset(skip).limit(limit).all()\n\ndef create_crime(db: Session, crime):\n    db_crime = Crime(**crime)\n    db.add(db_crime)\n    db.commit()\n    db.refresh(db_crime)\n    return db_crime\n\ndef update_crime(db: Session, crime_id: int, crime):\n    db_crime = get_crime(db, crime_id)\n    if db_crime is None:\n        return None\n    for key, value in crime.items():\n        setattr(db_crime, key, value)\n    db.commit()\n    db.refresh(db_crime)\n    return db_crime\n\ndef delete_crime(db: Session, crime_id: int):\n    db_crime = get_crime(db, crime_id)\n    if db_crime is None:\n        return False\n    db.delete(db_crime)\n    db.commit()\n    return True\n\n```\n\n\n## Crime Data Endpoints\n\nBatman created various endpoints to manage crime data. These endpoints allowed the Gotham City Police Department to add, update, and retrieve crime data.\n\n```python {{ title: 'Setting up Routes' }}\n# __main__.py\nfrom robyn import Robyn\nfrom robyn.robyn import Request, Response\nfrom sqlalchemy.orm import Session\n\napp = Robyn(__file__)\n\n@app.post(\"/crimes\")\nasync def add_crime(request):\n    with SessionLocal() as db:\n        crime = request.json()\n        insertion = crud.create_crime(db, crime)\n\n    if insertion is None:\n        raise Exception(\"Crime not added\")\n\n    return {\n        \"description\": \"Crime added successfully\",\n        \"status_code\": 200,\n    }\n\n@app.get(\"/crimes\")\nasync def get_crimes(request):\n    with SessionLocal() as db:\n        skip = request.query_params.get(\"skip\", \"0\")\n        limit = request.query_params.get(\"limit\", \"100\")\n        crimes = crud.get_crimes(db, skip=skip, limit=limit)\n\n    return crimes\n\n@app.get(\"/crimes/:crime_id\", auth_required=True)\nasync def get_crime(request):\n    crime_id = int(request.path_params.get(\"crime_id\"))\n    with SessionLocal() as db:\n        crime = crud.get_crime(db, crime_id=crime_id)\n\n    if crime is None:\n        raise Exception(\"Crime not found\")\n\n    return crime\n\n@app.put(\"/crimes/:crime_id\")\nasync def update_crime(request):\n    crime = request.json()\n    crime_id = int(request.path_params.get(\"crime_id\"))\n    with SessionLocal() as db:\n        updated_crime = crud.update_crime(db, crime_id=crime_id, crime=crime)\n    if updated_crime is None:\n        raise Exception(\"Crime not found\")\n    return updated_crime\n\n@app.delete(\"/crimes/{crime_id}\")\nasync def delete_crime(request):\n    crime_id = int(request.path_params.get(\"crime_id\"))\n    with SessionLocal() as db:\n        success = crud.delete_crime(db, crime_id=crime_id)\n    if not success:\n        raise Exception(\"Crime not found\")\n    return {\"message\": \"Crime deleted successfully\"}\n\n\n```\n\n\n<div className=\"not-prose\">\n  <Button\n    href=\"/documentation/en/example_app/authentication\"\n    variant=\"text\"\n    arrow=\"right\"\n    children=\"Next: Authentication\"\n  />\n</div>\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/example_app/monitoring_and_logging.mdx",
    "content": "\n## Monitoring and Logging\nTo keep an eye on the performance of his application and troubleshoot issues, Batman decided to implement monitoring and logging. He used a third-party library to integrate logging middleware, enabling him to track requests, errors, and performance metrics.\n\n```python {{ title: 'Monitoring and Logging' }}\nfrom robyn import Logger\n\nlogger = Logger(app)\n\n@app.before_request()\nasync def log_request(request: Request):\n    logger.info(f\"Received request: %s\", request)\n\n@app.after_request()\nasync def log_response(response: Response):\n    logger.info(f\"Sending response: %s\", response)\n```\n\nWith monitoring and logging in place, Batman could now easily detect issues and analyze the performance of his web application, ensuring that it was always running optimally and ready to assist him in his fight against crime.\n\n\n<div className=\"not-prose\">\n  <Button\n    href=\"/documentation/en/example_app/deployment\"\n    variant=\"text\"\n    arrow=\"right\"\n    children=\"Deploying the application\"\n  />\n</div>\n\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/example_app/openapi.mdx",
    "content": "export const description =\n  'Welcome to the Robyn API documentation. You will find comprehensive guides and documentation to help you start working with Robyn as quickly as possible, as well as support if you get stuck.'\n\n\n## OpenAPI Docs a.k.a Swagger\n\nAfter deploying the application, Batman got multiple queries from the users on how to use the endpoints. Robyn showed him how to generate OpenAPI specifications for his application.\n\nOut of the box, the following endpoints are setup for you:\n\n- `/docs` The Swagger UI\n- `/openapi.json` The JSON Specification\n\nHowever, if you don't want to generate the OpenAPI docs, you can disable it by passing `--disable-openapi` flag while starting the application.\n\nTo use a custom openapi configuration, you can:\n\n    - Place the `openapi.json` config file in the root directory.\n    - Or, pass the file path to the `openapi_file_path` parameter in the `Robyn()` constructor. (the parameter gets priority over the file).\n\n```bash\npython app.py --disable-openapi\n```\n\n## How to use?\n\n- Query Params: The typing for query params can be added as `def get(r: Request, query_params: GetRequestParams)` where `GetRequestParams` is a subclass of `QueryParams`\n- Path Params are defaulted to string type (ref: https://en.wikipedia.org/wiki/Query_string)\n\n<CodeGroup title=\"Basic App\">\n\n```python\nfrom robyn.robyn import QueryParams\n\nfrom robyn import Robyn, Request\n\napp = Robyn(\n    file_object=__file__,\n    openapi=OpenAPI(\n        info=OpenAPIInfo(\n            title=\"Sample App\",\n            description=\"This is a sample server application.\",\n            termsOfService=\"https://example.com/terms/\",\n            version=\"1.0.0\",\n            contact=Contact(\n                name=\"API Support\",\n                url=\"https://www.example.com/support\",\n                email=\"support@example.com\",\n            ),\n            license=License(\n                name=\"BSD2.0\",\n                url=\"https://opensource.org/license/bsd-2-clause\",\n            ),\n            externalDocs=ExternalDocumentation(description=\"Find more info here\", url=\"https://example.com/\"),\n            components=Components(),\n        ),\n    ),\n)\n\n\n@app.get(\"/\")\nasync def welcome():\n    \"\"\"welcome endpoint\"\"\"\n    return \"hi\"\n\n\nclass GetRequestParams(QueryParams):\n    appointment_id: str\n    year: int\n\n\n@app.get(\"/api/v1/name\", openapi_name=\"Name Route\", openapi_tags=[\"Name\"])\nasync def get(r: Request, query_params: GetRequestParams):\n    \"\"\"Get Name by ID\"\"\"\n    return r.query_params\n\n\n@app.delete(\"/users/:name\", openapi_tags=[\"Name\"])\nasync def delete(r: Request):\n    \"\"\"Delete Name by ID\"\"\"\n    return r.path_params\n\n\nif __name__ == \"__main__\":\n    app.start()\n```\n\n</CodeGroup>\n\n## How does it work with subrouters?\n\n<CodeGroup title=\"Subrouters\">\n\n```python\nfrom robyn.robyn import QueryParams\n\nfrom robyn import Request, SubRouter\n\nsubrouter: SubRouter = SubRouter(__name__, prefix=\"/sub\")\n\n\n@subrouter.get(\"/\")\nasync def subrouter_welcome():\n    \"\"\"welcome subrouter\"\"\"\n    return \"hiiiiii subrouter\"\n\n\nclass SubRouterGetRequestParams(QueryParams):\n    _id: int\n    value: str\n\n\n@subrouter.get(\"/name\")\nasync def subrouter_get(r: Request, query_params: SubRouterGetRequestParams):\n    \"\"\"Get Name by ID\"\"\"\n    return r.query_params\n\n\n@subrouter.delete(\"/:name\")\nasync def subrouter_delete(r: Request):\n    \"\"\"Delete Name by ID\"\"\"\n    return r.path_params\n\n\napp.include_router(subrouter)\n```\n\n</CodeGroup>\n\n## Other Specification Params\n\nWe support all the params mentioned in the latest OpenAPI specifications (https://swagger.io/specification/). See an example using request & response bodies below:\n\n<CodeGroup title=\"Request & Response Body\">\n\n```python\nfrom robyn.types import JSONResponse, Body\n\nclass Initial(Body):\n    is_present: bool\n    letter: Optional[str]\n\n\nclass FullName(Body):\n    first: str\n    second: str\n    initial: Initial\n\n\nclass CreateItemBody(Body):\n    name: FullName\n    description: str\n    price: float\n    tax: float\n\n\nclass CreateResponse(JSONResponse):\n    success: bool\n    items_changed: int\n\n\n@app.post(\"/\")\ndef create_item(request: Request, body: CreateItemBody) -> CreateResponse:\n    return CreateResponse(success=True, items_changed=2)\n```\n\n</CodeGroup>\n\nWith the reference documentation deployed and running smoothly, Batman had a powerful new tool at his disposal. The Robyn framework had provided him with the flexibility, scalability, and performance needed to create an effective crime-fighting application, giving him a technological edge in his ongoing battle to protect Gotham City.\n\n\n<div className=\"not-prose\">\n  <Button\n    href=\"/documentation/en/example_app/templates\"\n    variant=\"text\"\n    arrow=\"right\"\n    children=\"Templates\"\n  />\n</div>\n\n\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/example_app/real_time_notifications.mdx",
    "content": "export const description =\n  'Welcome to the Robyn API documentation. You will find comprehensive guides and documentation to help you start working with Robyn as quickly as possible, as well as support if you get stuck.'\n\n## Real time notifications\nBatman decided to implement real-time notifications for police officers using WebSockets. This would allow officers to receive instant updates on criminal activities, as well as alerts when a new crime is reported.\n\n\n\n```python {{ title: 'Setting up Real-time Notifications' }}\nfrom robyn import WebSocketDisconnect\n\n@app.websocket(\"/notifications\")\nasync def notify_handler(websocket):\n    try:\n        while True:\n            message = await websocket.receive_text()\n            await websocket.send_text(f\"Received: {message}\")\n    except WebSocketDisconnect:\n        pass\n\n@notify_handler.on_connect\ndef notify_connect(websocket):\n    return \"Connected to notifications\"\n\n@notify_handler.on_close\ndef notify_close(websocket):\n    return \"Disconnected from notifications\"\n\n```\n\n## Advanced Search and Filtering \n\nTo make it easier for the police officers to search for specific crimes or criminals, Batman added advanced search and filtering options to the application. He implemented a new endpoint that allows users to search based on various criteria like crime type, date, location, and status.\n\n\n```python {{ title: 'Advanced Search and Filtering' }}\n@app.get(\"/crimes/search\")\nasync def search_crimes(request):\n    crime_type = request.query_params.get(\"crime_type\")\n    date = request.query_params.get(\"date\")\n    location = request.query_params.get(\"location\")\n    status = request.query_params.get(\"status\")\n\n    crimes = crud.search_crimes(db, crime_type=crime_type, date=date, location=location, status=status)\n    return crimes\n\n```\n\n\nWith the new features in place, the Gotham City Police Department was able to use the web application more effectively to track criminal activities and deploy resources efficiently. Batman's work on the Robyn web framework had a significant impact on Gotham City's crime-fighting efforts, making it a safer place for its citizens.\n\nAlthough Batman had achieved great success with the current implementation, he knew that there would always be room for improvement and new features to add. But for now, he could take a moment to appreciate his work and focus on his primary duty - protecting Gotham City as the Dark Knight.\n\n\n\n\n\n<div className=\"not-prose\">\n  <Button\n    href=\"/documentation/en/example_app/monitoring_and_logging\"\n    variant=\"text\"\n    arrow=\"right\"\n    children=\"Checkout the monitoring and logging\"\n  />\n</div>\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/example_app/subrouters.mdx",
    "content": "export const description =\n  'Welcome to the Robyn API documentation. You will find comprehensive guides and documentation to help you start working with Robyn as quickly as possible, as well as support if you get stuck.'\n\n## Code Organization with SubRouters\n\nAs the application grew, Batman needed a way to organize his routes better. He decided to use Robyn's SubRouter feature to group related routes together.\n\n```python\nfrom robyn import SubRouter\n\n# Create a subrouter for crime-related routes\ncrime_router = SubRouter(__file__, prefix=\"/crimes\")\n\n@crime_router.get(\"/list\")\ndef list_crimes():\n    return {\"crimes\": get_all_crimes()}\n\n@crime_router.post(\"/report\")\ndef report_crime(request):\n    crime_data = request.json()\n    return {\"id\": create_crime_report(crime_data)}\n\n# Create a subrouter for suspect-related routes\nsuspect_router = SubRouter(__file__, prefix=\"/suspects\")\n\n@suspect_router.get(\"/list\")\ndef list_suspects():\n    return {\"suspects\": get_all_suspects()}\n\n@suspect_router.get(\"/:id\")\ndef get_suspect(request, path_params):\n    suspect_id = path_params.id\n    return {\"suspect\": get_suspect_by_id(suspect_id)}\n\n# Include the subrouters in the main app\napp.include_router(crime_router)\napp.include_router(suspect_router)\n```\n\nSubRouters help organize related routes under a common prefix, making the code more maintainable and easier to understand. In this example:\n\n- All crime-related routes are under `/crimes`\n- All suspect-related routes are under `/suspects`\n\nThis organization makes it clear which routes handle what functionality and keeps related code together.\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/example_app/templates.mdx",
    "content": "export const description =\n  'Welcome to the Robyn API documentation. You will find comprehensive guides and documentation to help you start working with Robyn as quickly as possible, as well as support if you get stuck.'\n\n\n## Templates\n\nAfter implementing the backend, Batman decided to add a frontend to his application. He wanted to create a simple web page that would allow him to view the data he had collected. He also wanted to be able to add new data to the database and edit existing data.\n\nThis is when he was introduced to templates!\n\nTemplates are a powerful feature of the Robyn framework that allow you to create dynamic web pages using HTML and Python. They are a great way to add a frontend to your application without having to learn a new language or framework.\n\nRobyn supports Jinja2 templates by default but provides an easy way to add other templating engines as well.\n\n### Creating a Template\n\nTo create a template, you need to create a file with the `.html` extension in the a directory, usually it is convention to use the `templates` directory. For example, if you wanted to create a template called `index.html`, you would create a file called `index.html` in the `templates` directory.\n\nSo the folder structure would look like this:\n\n```bash\n├── app.py\n├── templates\n│   └── index.html\n├── Dockerfile\n└── requirements.txt\n\n```\n\n### Rendering a Template\n\nOnce you have created a template, you can render it by using the `render_template` function. This function takes the name of the template as its first argument and a dictionary of variables as its second argument.\n\nFor example, if you wanted to render the `index.html` template, you would use the following code:\n\n```python {{ title: 'Rendering a Template' }}\n\nimport os\nimport pathlib\nfrom robyn.templating import JinjaTemplate\n\n\ncurrent_file_path = pathlib.Path(__file__).parent.resolve()\njinja_template = JinjaTemplate(os.path.join(current_file_path, \"templates\"))\n\n@app.get(\"/frontend\")\nasync def get_frontend(request):\n    context = {\"framework\": \"Robyn\", \"templating_engine\": \"Jinja2\"}\n    return jinja_template.render_template(\"index.html\", **context)\n\napp.include_router(frontend)\n```\n\n\nNow Batman very happy that the application had come to completion. However, he was not satisfied with the current state of the application. He felt the code was all crammed in a single file and asked Robyn if there was a way to split the codebase in other parts.\n\nThis is Robyn introduced him to the concept of routers and views.\n\n<div className=\"not-prose\">\n  <Button\n    href=\"/documentation/en/example_app/subrouters\"\n    variant=\"text\"\n    arrow=\"right\"\n    children=\"SubRouters and Views\"\n  />\n</div>\n\n\n\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/example_app/zh/index.mdx",
    "content": "export const description =\n  '欢迎来到Robyn API文档。您将找到全面的指南和文档，帮助您尽快开始使用Robyn，并在遇到困难时获得支持。'\n\n# 使用Robyn构建实际应用\n\n蝙蝠侠被委托开发一个Web应用程序来管理哥谭市的犯罪数据。该应用程序将允许哥谭警察局存储和检索有关犯罪活动、嫌疑人及其位置的数据。他决定使用Robyn Web框架来高效快速地构建这个应用程序。\n\n您可以在[这里](https://github.com/sparckles/example_robyn_app)找到此应用程序的源代码。\n\n## 安装Robyn\n\n第一步是安装Robyn。蝙蝠侠创建了一个虚拟环境并使用pip安装了Robyn。\n\n```bash\n$ python3 -m venv venv\n$ source venv/bin/activate\n$ pip install robyn\n```\n\n## 创建Robyn应用程序\n\n蝙蝠侠正准备创建一个`src/app.py`文件，这时他得知Robyn提供了一个CLI工具来创建新应用程序。他运行以下命令来创建一个新的Robyn应用程序：\n\n```bash\n$ python -m robyn --create\n```\n\n这个示例应用程序将展示如何：\n\n- 设计和实现RESTful API\n- 处理身份验证和授权\n- 使用中间件进行请求处理\n- 实现实时通知\n- 设置监控和日志记录\n- 部署应用程序\n- 生成API文档\n- 组织和构建可维护的代码 "
  },
  {
    "path": "docs_src/src/pages/documentation/en/example_app/zh/subrouters.mdx",
    "content": "export const description =\n  '欢迎来到Robyn API文档。您将找到全面的指南和文档，帮助您尽快开始使用Robyn，并在遇到困难时获得支持。'\n\n## 使用SubRouters组织代码\n\n随着应用程序的增长，蝙蝠侠需要一种更好的方式来组织他的路由。他决定使用Robyn的SubRouter功能来对相关路由进行分组。\n\n```python\nfrom robyn import SubRouter\n\n# 创建处理犯罪相关路由的子路由器\ncrime_router = SubRouter(__file__, prefix=\"/crimes\")\n\n@crime_router.get(\"/list\")\ndef list_crimes():\n    return {\"crimes\": get_all_crimes()}\n\n@crime_router.post(\"/report\")\ndef report_crime(request):\n    crime_data = request.json()\n    return {\"id\": create_crime_report(crime_data)}\n\n# 创建处理嫌疑人相关路由的子路由器\nsuspect_router = SubRouter(__file__, prefix=\"/suspects\")\n\n@suspect_router.get(\"/list\")\ndef list_suspects():\n    return {\"suspects\": get_all_suspects()}\n\n@suspect_router.get(\"/:id\")\ndef get_suspect(request, path_params):\n    suspect_id = path_params.id\n    return {\"suspect\": get_suspect_by_id(suspect_id)}\n\n# 将子路由器包含在主应用程序中\napp.include_router(crime_router)\napp.include_router(suspect_router)\n```\n\nSubRouters帮助在公共前缀下组织相关路由，使代码更易于维护和理解。在这个例子中：\n\n- 所有与犯罪相关的路由都在 `/crimes` 下\n- 所有与嫌疑人相关的路由都在 `/suspects` 下\n\n这种组织方式清晰地表明了哪些路由处理什么功能，并将相关代码保持在一起。 "
  },
  {
    "path": "docs_src/src/pages/documentation/en/framework_performance_comparison.mdx",
    "content": "export const description =\n  'On this page, we`ll understand the performance comparison across different frameworks.'\n\n\n# Performance comparison across different frameworks\n\n## Read this before you scroll down\n\nBefore delving into the details, it is imperative to note that this comparison doesn’t aim to discredit any developers or the frameworks listed below. Mentioning the names of the frameworks is solely for elucidating a clear comparison. My profound inclination towards the Python web ecosystem has been significantly influenced by all these frameworks, and my intention is not to cause offense to anyone by listing them here.\n\nMoreover, these tests were conducted on my development machine, and thus, the figures portrayed below are not absolute. The numbers only serve to indicate the relative performance of these frameworks under the specific testing conditions.\n\n\nThe [oha](https://github.com/hatoo/oha) tool was utilized to test 10,000 requests on the following frameworks, yielding the subsequent results:\n\n1. Flask(Gunicorn)\n\n```\nTotal:        5.5254 secs\nSlowest:      0.0784 secs\nFastest:      0.0028 secs\nAverage:      0.0275 secs\nRequests/sec: 1809.8082\n```\n\n1. FastAPI(Uvicorn)\n\n```\nTotal:        4.1314 secs\nSlowest:      0.0733 secs\nFastest:      0.0027 secs\nAverage:      0.0206 secs\nRequests/sec: 2420.4851\n```\n\n1. Django(Gunicorn)\n\n```\nTotal:        13.5070 secs\nSlowest:      0.3635 secs\nFastest:      0.0249 secs\nAverage:      0.0674 secs\nRequests/sec: 740.3558\n```\n\n1. Robyn(Doesn't need a *SGI)\n\n```\nTotal:\t1.8324 secs\nSlowest:\t0.0269 secs\nFastest:\t0.0024 secs\nAverage:\t0.0091 secs\nRequests/sec:\t5457.2339\n```\n\n1. Robyn (5 workers)\n\n```\nTotal:\t1.5592 secs\nSlowest:\t0.0211 secs\nFastest:\t0.0017 secs\nAverage:\t0.0078 secs\nRequests/sec:\t6413.6480\n```\n\nRobyn is able to serve the 10k requests in 1.8 seconds followed by Flask and FastAPI, which take around 5 seconds(using 5 workers on a dual-core machine). Finally, Django takes around 13.5070 seconds.\n\n## Verbose Logs\n\nFlask(Gunicorn)\n\n```\nSummary:\n  Success rate: 1.0000\n  Total:        5.5254 secs\n  Slowest:      0.0784 secs\n  Fastest:      0.0028 secs\n  Average:      0.0275 secs\n  Requests/sec: 1809.8082\n\n  Total data:   126.95 KiB\n  Size/request: 13 B\n  Size/sec:     22.98 KiB\n\nResponse time histogram:\n  0.007 [55]   |\n  0.014 [641]  |■■■■■\n  0.021 [2413] |■■■■■■■■■■■■■■■■■■■■\n  0.027 [3771] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n  0.034 [1999] |■■■■■■■■■■■■■■■■\n  0.041 [737]  |■■■■■■\n  0.048 [236]  |■■\n  0.055 [75]   |\n  0.062 [46]   |\n  0.069 [24]   |\n  0.076 [3]    |\n\nLatency distribution:\n  10% in 0.0178 secs\n  25% in 0.0223 secs\n  50% in 0.0266 secs\n  75% in 0.0317 secs\n  90% in 0.0378 secs\n  95% in 0.0419 secs\n  99% in 0.0551 secs\n\nDetails (average, fastest, slowest):\n  DNS+dialup:   0.0071 secs, 0.0001 secs, 0.0443 secs\n  DNS-lookup:   0.0000 secs, 0.0000 secs, 0.0010 secs\n\nStatus code distribution:\n  [200] 10000 responses\n```\n\nFastAPI(Uvicorn)\n\n```\nSummary:\n  Success rate: 1.0000\n  Total:        4.1314 secs\n  Slowest:      0.0733 secs\n  Fastest:      0.0027 secs\n  Average:      0.0206 secs\n  Requests/sec: 2420.4851\n\n  Total data:   166.02 KiB\n  Size/request: 17 B\n  Size/sec:     40.18 KiB\n\nResponse time histogram:\n  0.005 [175]  |■\n  0.011 [1541] |■■■■■■■■■■■■■■■■\n  0.016 [2942] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n  0.021 [2770] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n  0.027 [1479] |■■■■■■■■■■■■■■■■\n  0.032 [608]  |■■■■■■\n  0.038 [217]  |■■\n  0.043 [103]  |■\n  0.048 [53]   |\n  0.054 [54]   |\n  0.059 [58]   |\n\nLatency distribution:\n  10% in 0.0120 secs\n  25% in 0.0151 secs\n  50% in 0.0194 secs\n  75% in 0.0243 secs\n  90% in 0.0300 secs\n  95% in 0.0348 secs\n  99% in 0.0522 secs\n\nDetails (average, fastest, slowest):\n  DNS+dialup:   0.0088 secs, 0.0073 secs, 0.0103 secs\n  DNS-lookup:   0.0001 secs, 0.0000 secs, 0.0008 secs\n\nStatus code distribution:\n  [200] 10000 responses\n```\n\nRobyn\n\n```\nSummary:\n  Success rate:\t1.0000\n  Total:\t1.8324 secs\n  Slowest:\t0.0269 secs\n  Fastest:\t0.0024 secs\n  Average:\t0.0091 secs\n  Requests/sec:\t5457.2339\n\n  Total data:\t117.19 KiB\n  Size/request:\t12 B\n  Size/sec:\t63.95 KiB\n\nResponse time histogram:\n  0.002 [183]  |■\n  0.004 [1669] |■■■■■■■■■■■■■■\n  0.007 [3724] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n  0.009 [2631] |■■■■■■■■■■■■■■■■■■■■■■\n  0.011 [1060] |■■■■■■■■■\n  0.013 [496]  |■■■■\n  0.016 [188]  |■\n  0.018 [34]   |\n  0.020 [12]   |\n  0.022 [2]    |\n  0.025 [1]    |\n\nLatency distribution:\n  10% in 0.0061 secs\n  25% in 0.0073 secs\n  50% in 0.0087 secs\n  75% in 0.0105 secs\n  90% in 0.0129 secs\n  95% in 0.0143 secs\n  99% in 0.0171 secs\n\nDetails (average, fastest, slowest):\n  DNS+dialup:\t0.0049 secs, 0.0035 secs, 0.0065 secs\n  DNS-lookup:\t0.0001 secs, 0.0000 secs, 0.0010 secs\n\nStatus code distribution:\n  [200] 10000 responses\n```\n\nDjango(Gunicorn)\n\n```\nSummary:\n  Success rate: 1.0000\n  Total:        13.5070 secs\n  Slowest:      0.3635 secs\n  Fastest:      0.0249 secs\n  Average:      0.0674 secs\n  Requests/sec: 740.3558\n\n  Total data:   102.01 MiB\n  Size/request: 10.45 KiB\n  Size/sec:     7.55 MiB\n\nResponse time histogram:\n  0.016 [283]  |■\n  0.032 [2616] |■■■■■■■■■■■■■■■■■■\n  0.048 [4587] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n  0.064 [1829] |■■■■■■■■■■■■\n  0.081 [362]  |■■\n  0.097 [98]   |\n  0.113 [105]  |\n  0.129 [20]   |\n  0.145 [7]    |\n  0.161 [28]   |\n  0.177 [65]   |\n\nLatency distribution:\n  10% in 0.0493 secs\n  25% in 0.0559 secs\n  50% in 0.0638 secs\n  75% in 0.0733 secs\n  90% in 0.0840 secs\n  95% in 0.0948 secs\n  99% in 0.1543 secs\n\nDetails (average, fastest, slowest):\n  DNS+dialup:   0.0097 secs, 0.0001 secs, 0.0444 secs\n  DNS-lookup:   0.0000 secs, 0.0000 secs, 0.0007 secs\n\nStatus code distribution:\n  [200] 10000 responses\n```\n\nRobyn(with 5 workers)\n\n```\nSummary:\n  Success rate:\t1.0000\n  Total:\t1.5592 secs\n  Slowest:\t0.0211 secs\n  Fastest:\t0.0017 secs\n  Average:\t0.0078 secs\n  Requests/sec:\t6413.6480\n\n  Total data:\t126.95 KiB\n  Size/request:\t13 B\n  Size/sec:\t81.42 KiB\n\nResponse time histogram:\n  0.002 [30]   |\n  0.004 [599]  |■■■■■\n  0.005 [3336] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n  0.007 [3309] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n  0.009 [1614] |■■■■■■■■■■■■■■■\n  0.011 [749]  |■■■■■■■\n  0.012 [253]  |■■\n  0.014 [94]   |\n  0.016 [14]   |\n  0.018 [1]    |\n  0.019 [1]    |\n\nLatency distribution:\n  10% in 0.0055 secs\n  25% in 0.0063 secs\n  50% in 0.0074 secs\n  75% in 0.0089 secs\n  90% in 0.0107 secs\n  95% in 0.0117 secs\n  99% in 0.0142 secs\n\nDetails (average, fastest, slowest):\n  DNS+dialup:\t0.0022 secs, 0.0013 secs, 0.0028 secs\n  DNS-lookup:\t0.0000 secs, 0.0000 secs, 0.0001 secs\n\nStatus code distribution:\n  [200] 10000 responses\n```\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/hosting.mdx",
    "content": "export const description =\n  'On this page, we`ll understand how Robyn can be deployed on various cloud providers .'\n\nThe process of hosting a Robyn app on various cloud providers.\n\n\n## Railway\n\nWe will be deploying the app on `Railway`.\n\nA GitHub account is needed as a mandatory prerequisite.\n\nWe will deploy a sample \"Hello World,\" demonstrating a simple GET route and serving an HTML file.\n\nDirectory structure:\n\n  <CodeGroup>\n\n```bash \napp folder/\n  main.py\n  requirements.txt\n  index.html\n```\n\n  </CodeGroup>\n\n  Note - Railway looks for a main.py as an entry point instead of app.py. The build process will fail if there is no main.py file.\n\n\n\n  <CodeGroup title=\"main.py\">\n\n```python {{ title: 'python' }}\nfrom robyn import Robyn, serve_html\n\n\napp = Robyn(__file__)\n\n\n@app.get(\"/hello\")\nasync def h(request):\n    print(request)\n    return \"Hello, world!\"\n\n\n@app.get(\"/\")\nasync def get_page(request):\n    return serve_html(\"./index.html\")\n\n\nif __name__ == \"__main__\":\n    app.start(url=\"0.0.0.0\", port=PORT)\n```\n\n  </CodeGroup>\n\n\n\n  <CodeGroup title=\"index.html\">\n\n    ```python {{ title: 'python' }}\n    <h1> Hello World, this is Robyn framework! <h1>\n    ```\n\n  </CodeGroup>\n\n## Exposing Ports\n\nThe Railway documentation says the following about the listening to ports:\n\n> The easiest way to get up and running is to have your application listen on 0.0.0.0:$PORT, where PORT is a Railway-provided environment variable.\n\nSo, passing the host as `0.0.0.0` to `app.start()` as an argument is necessary.\n\nWe need to create a Railway account to deploy this app on Railway. We can do so by going on the `Railway HomePage`.\n\nPress the \"Login\" button and select \"login with a GitHub account.\"\n\n<img src=\"https://user-images.githubusercontent.com/70811425/202867604-10a09f87-ecb9-4a42-ae90-1359223049bc.png\" />\n\nThen, we press the \"New Project\" button and select \"Deploy from GitHub repo\".\n\n<img src=\"https://user-images.githubusercontent.com/70811425/202870632-4d3f46dc-1aa9-4603-9b0f-344ed87ec9d0.png\" />\n\nThen we select the repo we want to deploy. And click \"Deploy Now\".\n\n<img src=\"https://user-images.githubusercontent.com/70811425/202870837-16884fef-8900-4ab3-9794-0fb53c3ffd2e.png\" />\n<img src=\"https://user-images.githubusercontent.com/70811425/202871003-f79a1cef-9a5f-4166-be4f-527c60ec6c79.png\" />\n\nNow, we click on our project's card.\n\nSelect \"Variables\" and press the \"New Variable\" button to set the environments variables.\n\n<img src=\"https://user-images.githubusercontent.com/70811425/202870681-5c069475-a5d1-4069-8582-c5b549d27aad.png\" />\n\nThen, we go to the \"Settings\" tab and click on \"Generate Domain.\"\n\nWe can generate a temporary domain under the \"Domains\" tab.\n\n<img src=\"https://user-images.githubusercontent.com/70811425/202870735-6b955752-c5a6-48d5-acbc-1a4ea6fd7574.png\" />\n\nWe can go to our domain `<domain>/hello` and confirm that the message \"Hello World\" is displayed.\n\n\n## Next Steps\n\n- [Future Roadmap](/documentation/en/api_reference/future-roadmap)\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/index.mdx",
    "content": "import { Guides } from '@/components/documentation/Guides'\nimport { ApiDocs } from '@/components/documentation/ApiDocs'\nimport { HeroPattern } from '@/components/documentation/HeroPattern'\n\nexport const description =\n  'Welcome to the Robyn API documentation. You will find comprehensive guides and documentation to help you start working with Robyn as quickly as possible, as well as support if you get stuck.'\n\nexport const sections = [\n  { title: 'Example Application', id: 'guides' },\n  { title: 'Api Reference', id: 'api_docs' },\n]\n\n<HeroPattern />\n\n<div className=\"text-white\">\n# API Documentation\nWelcome to the Robyn API documentation. You'll find comprehensive guides and documentation to help you start working with Robyn as quickly as possible, as well as support if you get stuck. \n\nWe have divided the documentation into two parts: the [Example Application](#guides) and the [API Docs](#api_docs).\n\n<div className=\"not-prose mb-16 mt-6 flex gap-3\">\n  <Button href=\"/documentation/en/example_app\" arrow=\"right\" children=\"Real World App\" />\n  <Button href=\"/documentation/en/api_reference\" variant=\"outline\" children=\"Api Docs\" />\n</div>\n\n## Getting started {{ anchor: false }}\n\nThe Example Application is a simple web application that demonstrates how to use the Robyn API. It is a great place to start if you are new to Robyn.\n\nThe API Reference contains detailed information about the Robyn API. It is a great place to start if you are already familiar with Robyn and want to learn more about the API.\n\n\n</div>\n<Guides />\n\n<ApiDocs />\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/en/plugins.mdx",
    "content": "## Plugins\n\nRobyn is a versatile and extensible web framework that allows anyone to make plugins over the top of Robyn.\nPlugins in Robyn allow you to enhance and customize the framework's functionality to suit your specific needs. Here are some noteworthy plugins that can supercharge your Robyn-based projects:\n\n### Rate Limit Plugin\n\n- Description: This plugin enables you to implement rate limiting for your Robyn application's routes. It helps prevent abuse, and brute-force attacks and ensures fair usage of your resources.\n- GitHub repository: [robyn-rate-limits](https://github.com/IdoKendo/robyn_rate_limits)\n- Installation:\n  `python -m pip install robyn-rate-limits`\n- Usage:\n\n```py\nfrom robyn import Robyn, Request\nfrom robyn_rate_limits import InMemoryStore\nfrom robyn_rate_limits import RateLimiter\n\napp = Robyn(__file__)\nlimiter = RateLimiter(store=InMemoryStore, calls_limit=3, limit_ttl=100)\n\n@app.before_request()\ndef middleware(request: Request):\n    return limiter.handle_request(app, request)\n\n@app.get(\"/\")\ndef h():\n    return \"Hello, World!\"\n\napp.start(port=8080)\n```\n\nIn this example, robyn-rate-limits is used to enforce a rate limit of 3 requests per 100-seconds window for specific routes. If a client exceeds this limit, they will receive a \"Too many requests\" message.\n\nThe plugin integrates seamlessly with the Robyn web framework, enhancing the security and stability of your application by preventing excessive requests from a single client.\n\n## What's next?\n\nAfter exploring the plugins, Batman wanted to explore the community.So, Robyn pointed him to\n\n- [Future Roadmap](/documentation/en/api_reference/future-roadmap)\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/api_reference/advanced_features.mdx",
    "content": "export const description =\n  '在此页面中，我们将深入探讨如何通过不同的接口实现符合预期的交互。'\n\n## 获取客户端 IP 地址\n\n意识到小丑可能也在使用哥谭警察控制面板后，蝙蝠侠决定在他的应用程序中实现访问者 IP 地址追踪功能。\n\n<Row>\n<Col>\n为了提升性能，他将应用程序扩展到了多个内核，并使用了以下命令：\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    from robyn import Robyn, Request\n\n    app = Robyn(__file__)\n\n    @app.get(\"/\")\n    async def h(request: Request):\n        return f\"hello to you, {request.ip_addr}\"\n\n    ```\n\n    </CodeGroup>\n\n  </Col>\n</Row>\n---\n\n## 下一步\n\n在成功实现 IP 跟踪功能后，蝙蝠侠开始思考如何帮助用户更好地理解和使用他的 API 接口。\n\n为此，Robyn 向他介绍了 OpenAPI 文档功能。\n\n[OpenAPI 文档](/documentation/zh/api_reference/openapi)\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/api_reference/authentication.mdx",
    "content": "export const description =\n  '在此页面中，我们将深入探讨如何通过不同的接口实现符合预期的交互。'\n\n创建应用程序的基本版本后，蝙蝠侠希望限制哥谭警察局的访问权限。因此，他询问了 Robyn 提供的身份验证功能。\n\n## 身份验证\n\n正如蝙蝠侠发现的那样，Robyn 提供了一种简便的方法，允许将身份验证中间件添加到应用程序中。通过在路由中指定 `auth_required=True`，可以确保该路由仅对已验证的用户开放。\n\n<Row>\n<Col>\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    @app.get(\"/auth\", auth_required=True)\n    async def auth(request: Request):\n        # This route method will only be executed if the user is authenticated\n        # Otherwise, a 401 response will be returned\n        return \"Hello, world\"\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n<Row>\n\n  <Col>\n  要添加身份验证中间件，您可以使用 `configure_authentication` 方法。此方法需要一个 `AuthenticationHandler` 对象作为参数。该对象定义了如何进行用户身份验证，并使用 `TokenGetter` 对象从请求中提取令牌。Robyn 目前提供了一个 `BearerGetter` 类，它使用 `Bearer` 认证方案从 `Authorization` 请求头中获取令牌。以下是一个基本身份验证处理程序的示例：\n\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n      ```python\n      class BasicAuthHandler(AuthenticationHandler):\n        def authenticate(self, request: Request) -> Optional[Identity]:\n            token = self.token_getter.get_token(request)\n            if token == \"valid\":\n                return Identity(claims={})\n            return None\n\n      app.configure_authentication(BasicAuthHandler(token_getter=BearerGetter()))\n\n      ```\n    </CodeGroup>\n\n  </Col>\n\n`authenticate` 方法应在用户通过身份验证时返回 `Identity` 对象，否则返回 `None`。`Identity` 对象可以包含任意数据，并可以通过 `request.identity` 属性在路由方法中访问。\n\n<b>\n  注意：该身份验证系统在底层主要通过 `before request`\n  中间件实现。您可以忽略此机制，使用自定义中间件实现自己的身份验证系统。然而，Robyn\n  提供的这一简单易用的解决方案已能满足大多数应用场景的需求。\n</b>\n\n</Row>\n\n---\n\n## 下一步\n\n蝙蝠侠已经掌握了身份验证的基本知识，接下来他希望了解一些优化技术，以提升应用程序的性能。他发现了以下功能：\n\n- [常量请求与多核扩展](/documentation/zh/api_reference/const_requests)\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/api_reference/const_requests.mdx",
    "content": "export const description =\n  '在此页面中，我们将深入探讨如何通过不同的接口实现符合预期的交互。'\n\n实现身份验证后，蝙蝠侠担心在高峰时段，尤其是小丑企图释放阿卡姆疯人院所有罪犯时，可能会引发大量请求，导致服务器崩溃。为此，Robyn 向他介绍了常量请求和多核扩展功能，以增强应用程序的性能。\n\n## 常量请求\n\nRobyn 告诉蝙蝠侠，可以为每个路由预处理响应，这样即使在路由处理之前，响应就已准备好。这样做可以减少访问路由器的次数，从而提高响应速度。\n\n<Row>\n<Col>\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n      ```python\n      @app.get(\"/\", const=True)\n      async def h():\n          return \"Hello, world\"\n      ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n## 多核扩展\n\nRobyn 告诉蝙蝠侠，可以使用 `--workers` 参数将应用程序扩展到多个核心。这样，系统会创建多个应用实例并分配负载，从而提升性能。\n\n<Row>\n\n  <Col>\n\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Request\">\n\n      ```python\n      python3 app.py --workers=N --process=M\n      ```\n    </CodeGroup>\n\n  </Col>\n\n`authenticate` 方法应在用户通过身份验证时返回 `Identity` 对象，否则返回 `None`。`Identity` 对象可以包含任意数据，并可以通过 `request.identity` 属性在路由方法中访问。\n\n<b>\n  注意：该身份验证系统在底层主要通过 `before request`\n  中间件实现。您可以忽略此机制，使用自定义中间件实现自己的身份验证系统。然而，Robyn\n  提供的这一简单易用的解决方案已能满足大多数应用场景的需求。\n</b>\n\n</Row>\n\n---\n\n## 下一步\n\n在提升了应用程序的性能后，蝙蝠侠非常高兴，并希望应用也可以接收从前端项目中发出请求。\n\n然而，这是他却遇到了 CORS（跨源资源共享）问题！于是，他向 Robyn 咨询如何解决这个问题。Robyn 向他介绍了相关的功能：\n\n- [跨域资源共享](/documentation/zh/api_reference/cors)\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/api_reference/cors.mdx",
    "content": "export const description =\n  '在此页面中，我们将深入探讨如何通过不同的接口实现符合预期的交互。'\n\n## CORS\n\n每次蝙蝠侠尝试访问 API 时，都会遇到 CORS 错误，这让他苦不堪言。\n\n## 扩展应用程序\n\n<Row>\n<Col>\nYou can allow CORS for your application by adding the following code:\n</Col>\n  <Col>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n      from robyn import Robyn, ALLOW_CORS\n\n      app = Robyn(__file__)\n      ALLOW_CORS(app, origins = [\"http://localhost:<PORT>/\"])\n    ```\n\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n---\n\n## 下一步\n\n修复了 CORS 问题后，蝙蝠侠感到非常满意，现在他希望了解如何在服务器中集成小型前端页面。\n\n于是，Robyn 向他介绍了模板系统，以及如何使用模板来渲染 HTML 页面。\n\n- [模板系统](/documentation/zh/api_reference/templating)\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/api_reference/dependency_injection.mdx",
    "content": "export const description = '在此页面中，我们将了解 Robyn 中的依赖注入。'\n\n## 依赖注入\n\n蝙蝠侠想要了解 Robyn 中的依赖注入机制。Robyn 向他介绍了依赖注入的概念以及如何在应用程序中使用它。\n\nRobyn 提供了两种依赖注入方式：\n全局依赖注入和局部依赖注入。\n\n### 应用程序级依赖注入\n\n<Row>\n<Col>\n全局依赖注入用于将依赖项注入到整个应用程序中，注入的依赖项对所有请求都可用。\n</Col>\n  <Col>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n      from robyn import Robyn, ALLOW_CORS\n\n      app = Robyn(__file__)\n      GLOBAL_DEPENDENCY = \"GLOBAL DEPENDENCY\"\n\n      app.inject_global(GLOBAL_DEPENDENCY=GLOBAL_DEPENDENCY)\n\n      @app.get(\"/sync/global_di\")\n      def sync_global_di(request, global_dependencies):\n        return global_dependencies[\"GLOBAL_DEPENDENCY\"]\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n### 路由器级依赖注入\n\n<Row>\n<Col>\n局部依赖注入用于将依赖项注入到特定路由器中，注入的依赖项仅对该路由器的请求有效。\n</Col>\n  <Col>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n      from robyn import Robyn, ALLOW_CORS, Request\n\n      app = Robyn(__file__)\n      ROUTER_DEPENDENCY = \"ROUTER DEPENDENCY\"\n\n      app.inject(ROUTER_DEPENDENCY=ROUTER_DEPENDENCY)\n\n      @app.get(\"/sync/global_di\")\n      def sync_global_di(r: Request, router_dependencies):\n        return router_dependencies[\"ROUTER_DEPENDENCY\"]\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n<Row>\n  <Col>\n    注意：`router_dependencies` 和 `global_dependencies`\n    是保留参数名，必须按照这些名称使用。参数的顺序不重要，但这两个参数必须位于\n    `request` 参数之后。\n  </Col>\n</Row>\n\n### WebSocket 依赖注入\n\n<Row>\n<Col>\nWebSocket 支持与 HTTP 路由相同的依赖注入系统。`global_dependencies` 和 `router_dependencies` 参数可以在主处理程序、`on_connect` 和 `on_close` 回调中使用。\n</Col>\n  <Col>\n    <CodeGroup title=\"WebSocket DI\" tag=\"WebSocket\" label=\"/chat\">\n    ```python {{ title: 'WebSocket 依赖注入' }}\n      from robyn import Robyn\n      import logging\n\n      app = Robyn(__file__)\n\n      app.inject_global(logger=logging.getLogger(__name__))\n      app.inject(cache=RedisCache())\n\n      @app.websocket(\"/chat\")\n      async def chat(websocket, global_dependencies=None, router_dependencies=None):\n          logger = global_dependencies.get(\"logger\")\n          cache = router_dependencies.get(\"cache\")\n          logger.info(f\"新连接: {websocket.id}\")\n\n          while True:\n              message = await websocket.receive_text()\n              cache.set(f\"ws_{websocket.id}\", message)\n              await websocket.broadcast(f\"用户 {websocket.id}: {message}\")\n\n      @chat.on_connect\n      async def on_connect(websocket, global_dependencies=None):\n          logger = global_dependencies.get(\"logger\")\n          logger.info(f\"客户端已连接: {websocket.id}\")\n          return \"已连接\"\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n---\n\n## 下一步\n\n由于蝙蝠侠对黑暗面非常熟悉，他对异常处理产生了兴趣。\n\nRobyn 随即向他介绍了异常处理的概念，以及如何通过异常处理来应对应用程序中的各种错误情况。\n\n- [异常处理](/documentation/zh/api_reference/exceptions)\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/api_reference/exceptions.mdx",
    "content": "## 自定义异常处理\n\n蝙蝠侠学习了如何在应用程序中为不同类型的异常创建自定义处理程序。他实现了以下代码来捕获异常并返回自定义的错误响应：\n\n<Row>\n<Col>\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    @app.exception\n    def handle_exception(error: Exception):\n        return Response(status_code=500, description=f\"error msg: {error}\", headers={})\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n---\n\n## 下一步\n\n现在，蝙蝠侠希望进一步提升 Web 应用的性能。Robyn 随即为他介绍了多核扩展的概念。\n\n- [多核扩展](/documentation/zh/api_reference/scaling)\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/api_reference/file-uploads.mdx",
    "content": "export const description =\n  '在此页面中，我们将深入探讨如何通过不同的接口实现符合预期的交互。'\n\n## 文件上传\n\n蝙蝠侠学会了如何使用 Robyn 实现文件上传功能，并创建了一个接口来处理文件上传。具体代码如下：\n\n## 上传不带 MultiPart 表单数据的文件\n\n<Row>\n<Col>\n为了提升性能，蝙蝠侠将应用程序扩展到了多个 CPU 核心。他使用以下代码处理文件上传：\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    @app.post(\"/upload\")\n    async def upload():\n      body = request.body\n      file = bytearray(body)\n\n      # write whatever filename\n      with open('test.txt', 'wb') as f:\n          f.write(file)\n\n      return {'message': 'success'}\n    ```\n\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n## 上传带 MultiPart 表单数据的文件\n\n<Row>\n<Col>\n为了进一步提升性能，蝙蝠侠继续扩展应用至多个内核，并使用以下代码处理带 MultiPart 表单数据的文件上传：\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n\n    @app.post(\"/sync/multipart-file\")\n    def sync_multipart_file(request: Request):\n        files = request.files\n        file_names = files.keys()\n        return {\"file_names\": list(file_names)}\n    ```\n\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n---\n\n## 文件下载\n\n蝙蝠侠希望让用户能够从 Web 应用中下载文件。他创建了一个接口来处理文件下载，具体代码如下：\n\n### 返回简单的 HTML 文件\n\n<Row>\n<Col>\n为提升应用性能，蝙蝠侠将应用扩展到多个核心，并使用以下命令提供简单的 HTML 文件：\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    from robyn import Robyn, Request, serve_html\n\n    app = Robyn(__file__)\n\n\n    @app.get(\"/\")\n    async def h(request: Request):\n        return serve_html(\"./index.html\")\n\n    app.start(port=8080)\n\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n### 返回简单的 HTML 字符串\n\n<Row>\n<Col>\n除了 HTML 文件，蝙蝠侠还希望提供简单的 HTML 字符串。为此，他使用了以下代码：\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    from robyn import Robyn, Request, html\n\n    app = Robyn(__file__)\n\n\n    @app.get(\"/\")\n    async def h(request: Request):\n        html_string = \"<h1>Hello World</h1>\"\n        return html(html_string)\n\n    app.start(port=8080)\n\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n### 返回其他类型的文件\n\n<Row>\n<Col>\n在成功提供 HTML 文件后，蝙蝠侠还希望能够提供其他类型的文件，如 CSS、JS 和图片。为此，他使用了以下代码：\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    from robyn import Robyn, serve_file, Request\n\n    app = Robyn(__file__)\n\n\n    @app.get(\"/\")\n    async def h(request: Request):\n        return serve_file(\"./index.html\", file_name=\"index.html\") # file_name is optional\n\n    app.start(port=8080)\n\n    ```\n\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n### 返回文件目录\n\n<Row>\n<Col>\n在能够提供其他类型的文件后，蝙蝠侠希望能够提供文件目录，例如用于提供 React 构建目录或简单的 HTML/CSS/JS 目录。为此，他使用了以下代码：\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    from robyn import Robyn, serve_file, Request\n\n    app = Robyn(__file__)\n\n\n    app.serve_directory(\n        route=\"/test_dir\",\n        directory_path=os.path.join(current_file_path, \"build\"),\n        index_file=\"index.html\",\n    )\n\n    app.start(port=8080)\n\n    ```\n\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n## 下一步\n\n现在，蝙蝠侠准备好深入了解 Robyn 的高级功能。现在，他非常好奇如何处理表单数据。\n\n- [表单处理](/documentation/zh/api_reference/form_data)\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/api_reference/form_data.mdx",
    "content": "export const description = '在此页面中，我们将深入了解如何处理表单数据。'\n\n## 表单数据与 Multi-Part 表单数据\n\n蝙蝠侠学会了如何使用 Robyn 处理文件上传。接下来，他希望能处理表单数据。\n\n## 处理 Multi-Part 表单数据\n\n<Row>\n<Col>\n蝙蝠侠上传了一些 Multi-Part 表单数据，并希望使用以下代码来处理这些数据：\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    @app.post(\"/upload\")\n    async def upload(request: Request):\n      form_data = request.form_data\n\n      return form_data\n    ```\n\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n## 下一步\n\n现在，蝙蝠侠准备深入了解 Robyn 的高级功能。他希望找到一种方法，在控制面板中实现实时更新。\n\n- [WebSockets](/documentation/zh/api_reference/websockets)\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/api_reference/future-roadmap.mdx",
    "content": "export const description = '在此页面中，我们将查看 Robyn 的未来发展路线图。'\n\n- 添加性能优化\n- 集成 Pydantic\n- 实现自动常量请求\n- 添加 ORM 支持，特别是 Prisma 集成\n- 改善插件生态系统\n- 改善文档\n- 改善 WebSocket 支持\n- 添加模板支持\n- 集成 GraphQL 与 Strawberry\n- 更多地投入到 Robyn 社区建设中\n\n## 下一步\n\n- [高级特性](/documentation/zh/api_reference/advanced-features)\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/api_reference/getting_started.mdx",
    "content": "export const description =\n  '在此页面中，我们将深入探讨如何通过不同的接口实现符合预期的交互。'\n\n## 同步与异步请求\n\n首先，Robyn 向蝙蝠侠介绍了它处理同步和异步请求的能力。蝙蝠侠很高兴了解这些功能，并开始在应用中实现它们。\n\n<Row>\n<Col>\n对于一个简单的同步请求，蝙蝠侠使用以下代码：\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    from robyn import Robyn, Request\n\n    app = Robyn(__file__)\n\n    @app.get(\"/\")\n    def h(request: Request):\n        return \"Hello, world\"\n\n    app.start(port=8080, host=\"0.0.0.0\") # host 是可选的，默认为 127.0.0.1\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n<Row>\n\n  <Col>\n    对于异步请求，蝙蝠侠使用了以下代码：\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n      ```python\n      from robyn import Request\n\n      @app.get(\"/\")\n      async def h(request: Request) -> str:\n          return \"Hello, world\"\n\n      ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n---\n\n## 运行 Robyn\n\n蝙蝠侠对如何运行该应用程序感到好奇。Robyn 解释说，他可以通过一个简单的命令 `python3 app.py` 来运行应用程序 `app.py`。\n\n<Row>\n  <Col>\n    Robyn 公开如下命令。这些命令可用于运行应用程序或生成新项目。\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n      ```bash\n      usage: app.py [-h] [--processes PROCESSES] [--workers WORKERS] [--log-level LOG_LEVEL] [--create] [--docs] [--open-browser] [--version]\n\n      Robyn，一个快速的异步 web 框架，基于 Rust 运行。\n\n      选项:\n        -h, --help            显示帮助信息并退出\n        --processes PROCESSES\n                              选择进程的数量。[默认值: 1]\n        --workers WORKERS     选择工作线程的数量。[默认值: 1]\n        --dev                 开发者模式。它会根据文件更改重新启动服务器。\n        --log-level LOG_LEVEL\n                              设置日志级别\n        --create              创建一个新的项目模板。\n        --docs                打开 Robyn 文档。\n        --open-browser        启动成功后打开浏览器。\n        --version             显示 Robyn 的版本。\n        --compile-rust-path COMPILE_RUST_PATH\n                              编译指定路径下的 Rust 文件。\n        --create-rust-file CREATE_RUST_FILE\n                              创建一个指定名称的 Rust 文件。\n        --disable-openapi     禁用 OpenAPI 文档。\n        --fast                快速模式。它设置进程、工作线程和日志级别的最佳值。不过，您可以覆盖这些设置。\n      ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n<Row />\n\n<Row>\n  <Col>\n    另外，您还可以使用 Robyn 的 CLI 来运行应用程序，即 `python -m robyn app.py`。\n  </Col>\n\n  <Col sticky>\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n      ```bash\n\n      usage: python -m robyn app.py [-h] [--processes PROCESSES] [--workers WORKERS] [--dev] [--log-level LOG_LEVEL] [--create] [--docs] [--open-browser] [--version]\n\n\n      Robyn，一个快速的异步 web 框架，基于 Rust 运行。\n\n      选项:\n        -h, --help            显示帮助信息并退出\n        --processes PROCESSES\n                              选择进程的数量。[默认值: 1]\n        --workers WORKERS     选择工作线程的数量。[默认值: 1]\n        --dev                 开发者模式。它会根据文件更改重新启动服务器。\n        --log-level LOG_LEVEL\n                              设置日志级别\n        --create              创建一个新的项目模板。\n        --docs                打开 Robyn 文档。\n        --open-browser        启动成功后打开浏览器。\n        --version             显示 Robyn 的版本。\n        --compile-rust-path COMPILE_RUST_PATH\n                              编译指定路径下的 Rust 文件。\n        --create-rust-file CREATE_RUST_FILE\n                              创建一个指定名称的 Rust 文件。\n        --disable-openapi     禁用 OpenAPI 文档。\n        --fast                快速模式。它设置进程、工作线程和日志级别的最佳值。不过，您可以覆盖这些设置。\n      ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n---\n\n## 处理 HTTP 请求\n\n然后，Robyn 教蝙蝠侠如何处理各种 HTTP 请求，如 GET、POST、PUT、PATCH 和 DELETE。在 Robyn 的指导下，蝙蝠侠可以为每种请求类型创建接口，从而使应用程序更加灵活和高效。\n\n<Row>\n<Col>\n例如，蝙蝠侠学会了创建如下 POST 请求：\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    from robyn import Request\n\n    @app.post(\"/\")\n    async def h(request: Request):\n        return \"Hello World\"\n\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n---\n\n## 响应 JSON 数据\n\n蝙蝠侠对从应用程序返回 JSON 响应的能力感到好奇。Robyn 向他展示了如何使用 `jsonify` 函数来实现这一点。\n\n<Row>\n<Col>\n  现在，蝙蝠侠可以从应用程序返回 JSON 响应，从而方便前端解析数据。\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    from robyn import jsonify, Request\n\n\n    @app.post(\"/jsonify\")\n    async def json(request: Request):\n        return {\"hello\": \"world\"}\n\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n## 获取路径参数与查询参数\n\n蝙蝠侠想了解如何从传入的请求中访问路径参数和查询参数，这样他就能创建动态路由，并从请求中提取特定信息。\n\n<Row>\n  <Col>\n\nRobyn 向蝙蝠侠展示了如何从请求中访问路径参数和查询参数。以下是相关的示例：\n\n例如，蝙蝠侠可以创建带路径参数的路由，并通过以下代码访问该参数：\n\n  </Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"POST\" label=\"/http_requests\">\n\n    ```python\n    from robyn import jsonify\n    from robyn.types import PathParams\n\n    @app.post(\"/jsonify/:id\")\n    async def json(req_obj: Request, path_parameters: PathParams):\n        print(req_obj.path_params[\"id\"])\n        print(path_parameters[\"id\"])\n        assert req_obj.path_params[\"id\"] == path_parameters[\"id\"]\n        return {\"hello\": \"world\"}\n\n    ```\n\n\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n<Row>\n  <Col>\n\n要访问查询参数，蝙蝠侠可以使用以下代码：\n\n  </Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"POST\" label=\"/http_requests\">\n\n    ```python\n    from robyn import Request\n    from robyn.robyn import QueryParams\n\n    @app.get(\"/query\")\n    async def query_get(req_obj: Request, query_params: QueryParams):\n        query_data = query_params.to_dict()\n        assert query_data == req_obj.query_params.to_dict()\n        return jsonify(query_data)\n    ```\n\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n<Row>\n  <Col>\n\n任何请求参数都可以在处理程序函数中通过类型注释或使用保留名称进行访问。\n\n{' '}\n\n<b>请注意，类型注释会优先于保留名称。</b>\nRobyn 向蝙蝠侠演示了访问请求参数的不同语法示例：\n\n  </Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/split_request_params\">\n\n    ```python\n    from robyn.robyn import QueryParams, Headers\n    from robyn.types import PathParams, RequestMethod, RequestBody, RequestURL\n\n    @app.get(\"/untyped/query_params\")\n    def untyped_basic(query_params):\n        return query_params.to_dict()\n\n\n    @app.get(\"/typed/query_params\")\n    def typed_basic(query_data: QueryParams):\n        return query_data.to_dict()\n\n\n    @app.get(\"/untyped/path_params/:id\")\n    def untyped_path_params(query_params: PathParams):\n        return query_params  # 由于类型注释优先，PathParams 包含路径参数\n\n\n    @app.post(\"/typed_untyped/combined\")\n    def typed_untyped_combined(\n            query_params,\n            method_data: RequestMethod,\n            body_data: RequestBody,\n            url: RequestURL,\n            headers_item: Headers,\n    ):\n        return {\n            \"body\": body_data,\n            \"query_params\": query_params.to_dict(),\n            \"method\": method_data,\n            \"url\": url.path,\n            \"headers\": headers_item.get(\"server\"),\n        }\n    ```\n\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n类型别名: `Request`、`QueryParams`、`Headers`、`PathParams`、`RequestBody`、`RequestURL`、`FormData`、`RequestFiles`、`RequestIP`、`RequestIdentity`\n\n保留名称: `r`、`req`、`request`、`query_params`、`headers`、`path_params`、`body、method`、`url`、`ip_addr`、`identity`、`form_data`、`files`\n\n---\n\n随着蝙蝠侠继续使用 Robyn 开发 Web 应用，他探索了更多功能，并通过代码示例实现它们。\n\n## 自定义响应格式与响应头\n\n在了解了 Robyn 的动态特性后，蝙蝠侠想要自定义响应格式和响应头。Robyn 向他展示了如何使用字典和 `Response` 对象来实现这一功能。\n\n### 使用字典\n\n<Row>\n<Col>\n蝙蝠侠学会了通过返回字典或使用 Robyn 的 `Response` 对象来定制响应格式，并为每个响应设置状态码和响应头。下面是一个使用字典创建响应的示例：\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    from robyn import Request\n\n    @app.post(\"/dictionary\")\n    async def dictionary(request: Request):\n        return {\n            \"status_code\": 200,\n            \"description\": \"This is a regular response\",\n            \"type\": \"text\",\n            \"headers\": {\"Header\": \"header_value\"},\n        }\n\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n### 使用 Response 对象\n\n<Row>\n<Col>\n蝙蝠侠还学会了如何使用 `Response` 对象，示例如下：\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    from robyn.robyn import Response, Request\n\n    @app.get(\"/response\")\n    async def response(request: Request):\n        return Response(status_code=200, headers=Headers({}), description=\"OK\")\n\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n### 返回二进制输出\n\n<Row>\n<Col>\n蝙蝠侠还希望从应用程序返回二进制数据。他可以通过将响应类型设置为 binary 并返回一个字节对象来实现：\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    from robyn import Request, Response\n\n    @app.get(\"/binary_output_response_sync\")\n    def binary_output_response_sync(request: Request):\n        return Response(\n            status_code=200,\n            headers={\"Content-Type\": \"application/octet-stream\"},\n            description=\"OK\",\n        )\n\n\n    @app.get(\"/binary_output_async\")\n    async def binary_output_async(request: Request):\n        return b\"OK\"\n\n\n    @app.get(\"/binary_output_response_async\")\n    async def binary_output_response_async(request: Request):\n        return Response(\n            status_code=200,\n            headers={\"Content-Type\": \"application/octet-stream\"},\n            description=\"OK\",\n        )\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n---\n\n## 响应头\n\n作为世界上最伟大的侦探，蝙蝠侠在 `Response` 对象中发现了 `headers` 字段，他希望进一步了解如何使用它来设置响应头。例如，蝙蝠侠可以通过以下方式设置 `Content-Type` 响应头为 `application/json`：\n\n### 局部响应头\n\n<Row>\n<Col>\n通过使用 `Response` 对象中的 `headers` 字段：\n</Col>\n<Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    from robyn import Request\n\n    @app.get(\"/\")\n    def binary_output_response_sync(request: Request):\n        return Response(\n            status_code=200,\n            headers={\"Content-Type\": \"application/octet-stream\"},\n            description=\"OK\",\n        )\n\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n### 全局响应头\n\n<Row>\n\n<Col>蝙蝠侠还可以为所有路由器设置全局响应头：</Col>\n\n  <Col>\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    app.add_response_header(\"content-type\", \"application/json\")\n    ```\n    </CodeGroup>\n\n  </Col>\n\n<Col>\n`add_response_header` 会将响应头附加到响应头列表中，而 `set_response_header` 会替换现有响应头（如果有的话）。\n</Col>\n<Col>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    app.set_response_header(\"content-type\", \"application/json\")\n    ```\n    </CodeGroup>\n\n  </Col>\n  \n<Col>\n如果希望某些接口不使用响应头，可以使用 `exclude_response_headers_for` 函数。\n</Col>\n<Col>\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    app.exclude_response_headers_for([\"/login\", \"/signup\"])\n    ```\n    </CodeGroup>\n\n</Col>\n</Row>\n\n### Cookies\n\n<Row>\n<Col>\nRobyn 提供了符合 RFC 6265 标准的完整 Cookie API。使用 Response 对象上的 `set_cookie` 方法来设置 Cookie。\n</Col>\n<Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    from robyn import Request, Response, Headers\n\n    @app.get(\"/\")\n    def set_session(request: Request):\n        response = Response(200, Headers({}), \"Welcome!\")\n        response.set_cookie(key=\"session\", value=\"abc123\")\n        return response\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n#### Cookie 属性\n\n<Row>\n<Col>\n您可以设置额外的 Cookie 属性以增强安全性和控制：\n\n- **path**: Cookie 路径（默认：\"/\"）\n- **domain**: Cookie 域名\n- **max_age**: Cookie 有效期（秒）\n- **secure**: 仅通过 HTTPS 发送\n- **http_only**: JavaScript 无法访问\n- **same_site**: CSRF 保护（\"Strict\"、\"Lax\" 或 \"None\" - 不区分大小写）\n</Col>\n<Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/secure-cookie\">\n\n    ```python\n    from robyn import Request, Response, Headers\n\n    @app.get(\"/login\")\n    def login(request: Request):\n        response = Response(200, Headers({}), \"Logged in\")\n        response.set_cookie(\n            key=\"auth_token\",\n            value=\"secret123\",\n            path=\"/\",\n            max_age=3600,        # 1 小时\n            secure=True,         # 仅 HTTPS\n            http_only=True,      # 禁止 JavaScript 访问\n            same_site=\"Strict\",  # CSRF 保护\n        )\n        return response\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n#### 删除 Cookie\n\n<Row>\n<Col>\n要从浏览器删除 Cookie，请使用 cookies 集合上的 `delete` 方法。这会设置 `max_age=0`，告诉浏览器删除该 Cookie。\n</Col>\n<Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/logout\">\n\n    ```python\n    from robyn import Request, Response, Headers\n\n    @app.get(\"/logout\")\n    def logout(request: Request):\n        response = Response(200, Headers({}), \"Logged out\")\n        response.cookies.delete(\"auth_token\")\n        return response\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n#### 访问 Cookie\n\n<Row>\n<Col>\n您可以遍历 Cookie 或按名称访问它们：\n</Col>\n<Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/cookies\">\n\n    ```python\n    from robyn import Request, Response, Headers\n\n    @app.get(\"/debug\")\n    def debug_cookies(request: Request):\n        response = Response(200, Headers({}), \"Cookies set\")\n        response.set_cookie(\"a\", \"1\")\n        response.set_cookie(\"b\", \"2\")\n        \n        # 获取所有 Cookie 名称\n        names = response.cookies.keys()\n        \n        # 遍历 Cookie\n        for name in response.cookies:\n            print(f\"Cookie: {name}\")\n        \n        # 检查 Cookie 是否存在\n        if \"a\" in response.cookies:\n            print(\"Cookie 'a' exists\")\n            \n        return response\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n## 请求头\n\n蝙蝠侠现在想了解如何读取请求头。Robyn 向他解释，他可以使用 `request.headers` 字段来读取响应头。例如，蝙蝠侠可以通过以下方式读取 `Content-Type` 请求头：\n\n### 局部请求头\n\n<Row>\n<Col>\n通过使用 `Request` 对象中的 `headers` 字段：\n</Col>\n<Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    from robyn import Request\n\n    @app.get(\"/\")\n    def binary_output_response_sync(request: Request):\n      headers = request.headers\n\n      print(\"These are the request headers: \", headers)\n      existing_header = headers.get(\"exisiting_header\")\n      existing_header = headers.get(\"exisiting_header\", \"default_value\")\n      exisiting_header = headers[\"exisiting_header\"] # This syntax is also valid\n\n      headers.set(\"modified\", \"modified_value\")\n      headers[\"new_header\"] = \"new_value\" # This syntax is also valid\n\n      print(\"These are the modified request headers: \", headers)\n\n      return \"\"\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n<Row>\n<Col>\n或者使用全局请求头：\n</Col>\n<Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    app.add_request_header(\"server\", \"robyn\")\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n<Row>\n<Col>\n`add_request_header` 会将请求头附加到请求头列表中，而 `set_request_header` 会替换现有请求头（如果有的话）。\n</Col>\n<Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    app.set_request_header(\"server\", \"robyn\")\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n---\n\n## 响应状态码\n\n<Row>\n<Col>\n了解了响应格式和响应头之后，蝙蝠侠学会了为他的响应设置状态码：\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    from robyn import status_codes, Request\n\n\n    @app.get(\"/response\")\n    async def response(request: Request):\n        return Response(status_code=status_codes.HTTP_200_OK, headers=Headers({}), description=\"OK\")\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n---\n\n## 下一步\n\n接下来，蝙蝠侠想知道 Robyn 提到的 **`Request` 请求对象**是什么。Robyn 说：“下一部分。”\n\n- [请求对象](/documentation/zh/api_reference/request_object)\n\n蝙蝠侠也对 Robyn 的架构感兴趣。Robyn 继续说道：“下一部分。”\n\n- [架构](/documentation/zh/architecture)\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/api_reference/graphql-support.mdx",
    "content": "export const description =\n  '在此页面中，我们将了解如何在现有的 Robyn 代码库中提供 GraphQL 支持，以更快速地获取数据。'\n\n## GraphQL 支持 [(With Strawberry 🍓)](https://strawberry.rocks/)\n\n目前，GraphQL 支持处于初步摸索阶段。当我们能为视图和视图控制器提供稳定的 API 时，将推出更加稳定的版本。\n\n## 第 1 步：创建虚拟环境\n\n为了确保依赖关系互相独立，我们将使用虚拟环境。\n\n  <CodeGroup title=\"Virtual Environment\">\n\n```bash {{ title: 'pip' }}\npython3 -m venv venv\n```\n\n  </CodeGroup>\n\n## 第 2 步：激活虚拟环境并安装 Robyn\n\n  <CodeGroup title=\"Activating the virtualenv\">\n\n```bash {{ title: 'pip' }}\nsource venv/bin/activate\n```\n\n  </CodeGroup>\n\n  <CodeGroup title=\"Installing Robyn and Strawberry\">\n\n```bash {{ title: 'pip' }}\npip install robyn strawberry-graphql\n```\n\n  </CodeGroup>\n\n## 第 3 步：编写 Robyn 应用\n\n  <CodeGroup title=\"Code\">\n\n```python {{ title: 'python' }}\nfrom typing import List, Optional\nfrom robyn import Robyn, jsonify\nimport json\n\nimport dataclasses\nimport strawberry\nimport strawberry.utils.graphiql\n\n\n@strawberry.type\nclass User:\n  name: str\n\n\n@strawberry.type\nclass Query:\n  @strawberry.field\n  def user(self) -> Optional[User]:\n      return User(name=\"Hello\")\n\n\nschema = strawberry.Schema(Query)\n\napp = Robyn(__file__)\n\n\n@app.get(\"/\", const=True)\nasync def get():\n  return strawberry.utils.graphiql.get_graphiql_html()\n\n\n@app.post(\"/\")\nasync def post(request):\n  body = request.json()\n  query = body[\"query\"]\n  variables = body.get(\"variables\", None)\n  context_value = {\"request\": request}\n  root_value = body.get(\"root_value\", None)\n  operation_name = body.get(\"operation_name\", None)\n\n  data = await schema.execute(\n      query,\n      variables,\n      context_value,\n      root_value,\n      operation_name,\n  )\n\n  return jsonify(\n      {\n          \"data\": (data.data),\n          **({\"errors\": data.errors} if data.errors else {}),\n          **({\"extensions\": data.extensions} if data.extensions else {}),\n      }\n  )\n\n\nif __name__ == \"__main__\":\n  app.start(port=8080, host=\"0.0.0.0\")\n```\n\n  </CodeGroup>\n\n接下来，让我们逐行解读以上代码。\n\n<Row>\n<Col>\n这些语句导入了所需的依赖项。\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Section 1\">\n\n    ```python {{ title: 'python' }}\n    from typing import List, Optional\n\n    from robyn import Robyn, jsonify\n    import json\n\n    import dataclasses\n    import strawberry\n    import strawberry.utils.graphiql\n\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n<Row>\n<Col>\n在这里，我们实现了一个基本的 `User` 类，并为其定义了 `name` 属性。\n\n然后，我们创建了一个 GraphQl 查询类（`Query` 类型），该查询返回一个 `User` 实例。\n\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Section 2\">\n\n    ```python {{ title: 'python' }}\n    @strawberry.type\n    class User:\n        name: str\n\n\n    @strawberry.type\n    class Query:\n        @strawberry.field\n        def user(self) -> Optional[User]:\n            return User(name=\"Hello\")\n\n\n    schema = strawberry.Schema(Query)\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n<Row>\n<Col>\n接下来，我们初始化了一个 Robyn 应用。为了提供 GraphQl 应用，我们需要定义两个路由：一个 GET 路由用于返回 `GraphiQL(ide)`，另一个 POST 路由用于处理 `GraphQl` 请求。\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Section 3\">\n\n    ```python {{ title: 'python' }}\n    app = Robyn(__file__)\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n<Row>\n<Col>\n我们通过 GraphiQL IDE 使用 `strawberry` 填充 HTML 页面，并设置 `const=True` 以预计算页面内容，从而加快返回速度，避免了 GET 请求的额外执行开销。\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Section 4\">\n\n    ```python {{ title: 'python' }}\n      @app.get(\"/\", const=True)\n      async def get():\n      return strawberry.utils.graphiql.get_graphiql_html()\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n<Row>\n<Col>\n最后，我们从 `request` 对象中获取参数（如 body、query、variables、context_value、root_value 和 operation_name）。\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Section 5\">\n\n    ```python {{ title: 'python' }}\n    @app.post(\"/\")\n    async def post(request):\n    body = request.json()\n    query = body[\"query\"]\n    variables = body.get(\"variables\", None)\n    context_value = {\"request\": request}\n    root_value = body.get(\"root_value\", None)\n    operation_name = body.get(\"operation_name\", None)\n\n    data = await schema.execute(\n        query,\n        variables,\n        context_value,\n        root_value,\n        operation_name,\n    )\n\n    return jsonify(\n        {\n            \"data\": (data.data),\n            **({\"errors\": data.errors} if data.errors else {}),\n            **({\"extensions\": data.extensions} if data.extensions else {}),\n        }\n    )\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n上述代码展示了如何为 Robyn 应用添加 GraphQL 支持。您可以为任意数量的路由执行类似操作。\n\n## 下一步\n\n这就是目前的所有内容。:D 请留意我们页面上的更新，我们将继续添加更多示例和文档。\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/api_reference/index.mdx",
    "content": "export const description =\n  '在此页面中，我们将深入探讨如何通过不同的接口实现符合预期的交互。'\n\n很久以前，在哥谭市，有一位名叫 Robyn 的强大超级英雄。Robyn 拥有一种独特的能力，能够快速从互联网的各个角落获取信息。凭借其高效的请求发送和响应接收能力，Robyn 的技术实力在开发者社区中备受推崇。\n\n有一天，蝙蝠侠向 Robyn 寻求帮助，计划构建一个 Web 应用程序。听闻 Robyn 的强大功能，蝙蝠侠希望借助其技术力量来打造一款卓越的应用。在寻找合作伙伴的过程中，蝙蝠侠最终选择了 Robyn，认为他是最合适的盟友。\n\n## 安装 Robyn\n\nRobyn 是一个 Python 库，您可以通过 `pip` 或 `conda` 来安装它：\n\n  <CodeGroup title=\"installation\">\n\n```bash {{ title: 'pip' }}\npip install robyn\n```\n\n```bash {{ title: 'conda' }}\nconda install robyn -c conda-forge\n```\n\n  </CodeGroup>\n\n此外，Robyn 还提供了一些扩展功能，您可以根据需要进行安装：\n\n  <CodeGroup title=\"installation\">\n\n```bash {{ title: 'pip' }}\npip install \"robyn[templating]\"\n```\n\n```bash {{ title: 'conda' }}\nconda install \"robyn[templating]\" -c conda-forge\n```\n\n  </CodeGroup>\n\n建议先安装基础包，然后根据实际需求安装扩展模块。\n\n## 下一步\n\n恭喜您成功安装了 Robyn！接下来，您可以开始使用它来构建您的 Web 应用程序。\n\n- [开始](/documentation/zh/api_reference/getting_started)\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/api_reference/middlewares.mdx",
    "content": "export const description =\n  '在此页面中，我们将深入探讨如何通过不同的接口实现符合预期的交互。'\n\n## 使用中间件和事件\n\n随着蝙蝠侠的应用程序逐渐变得更加复杂，Robyn 向他介绍了中间件、启动和关闭事件，甚至 WebSocket 的使用。蝙蝠侠学会了如何创建中间件（在请求之前或之后执行的函数），如何管理应用程序的生命周期，并通过 WebSocket 实现与客户端的实时通信。\n\n## 处理事件\n\n蝙蝠侠发现，通过添加启动和关闭事件，他能够有效地管理应用程序的生命周期。为此，他编写了以下代码来定义这些事件：\n\n<Row>\n<Col>\n此外，蝙蝠侠也了解到，事件不仅可以通过函数来定义，还可以使用装饰器的方式来实现。\n\n对于同步请求，蝙蝠侠使用如下代码：\n\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    async def startup_handler():\n      print(\"Starting up\")\n\n    app.startup_handler(startup_handler)\n\n    @app.shutdown_handler\n    def shutdown_handler():\n        print(\"Shutting down\")\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n<Row>\n\n  <Col>\n    对于异步请求，蝙蝠侠则使用如下代码：\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n      ```python\n      from robyn import Request\n\n      @app.get(\"/\")\n      async def h(request: Request) -> str:\n          return \"Hello, world\"\n\n      ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n## 处理中间件 {{ tag: 'POST', label: '/http_requests' }}\n\n<Row>\n  <Col>\n\n蝙蝠侠学会了如何在中间件中同时使用同步和异步函数。他编写了以下代码，为每个请求添加了在请求之前和之后执行的中间件。\n中间件可以分为两类：请求前中间件和请求后中间件。\n该函数在每个请求处理之前执行，可以修改请求对象或执行其他操作。\n请求后中间件：该函数在每个请求处理之后执行，可以修改响应对象或执行其他操作。\n\n每个请求前中间件都应接收并返回一个请求对象；每个请求后中间件都应接收并返回一个响应对象。请求后中间件也可以选择性地接收请求对象作为第一个参数，以便访问请求数据。\n\n如果某个请求前中间件返回了响应对象，执行将被中止，响应对象直接返回给客户端，后续的请求后中间件和主处理函数将不再执行。\n\n  </Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"POST\" label=\"/http_requests\">\n\n    ```python\n    from robyn import Request, Response\n\n    @app.before_request(\"/\")\n    async def hello_before_request(request: Request):\n        request.headers.set(\"before\", \"sync_before_request\")\n        return request\n\n    @app.after_request(\"/\")\n    def hello_after_request(response: Response):\n        response.headers.set(\"after\", \"sync_after_request\")\n        return response\n    ```\n\n    ```python {{ title: '在 after_request 中访问请求对象' }}\n    from robyn import Request, Response\n\n    @app.after_request(\"/\")\n    def hello_after_request(request: Request, response: Response):\n        # 在 after_request 中访问请求数据\n        response.headers.set(\"request_path\", request.url.path)\n        response.headers.set(\"after\", \"sync_after_request\")\n        return response\n    ```\n\n\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n---\n\n## 下一步\n\nRobyn - 太好了，中间件是 Robyn 框架的重要组件之一，您现在已经掌握了 Robyn 的一些高级概念。\n\n蝙蝠侠 - 身份验证！我想了解身份验证，确保只有经过身份验证的的人才能访问我的应用程序。\n\nRobyn - 好的，接下来我将为您介绍身份验证！\n\n- [身份验证](/documentation/zh/api_reference/authentication)\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/api_reference/multiprocess_execution.mdx",
    "content": "export const description =\n  '在此页面中，我们将深入探讨如何通过不同的接口实现符合预期的交互。'\n\n## 多进程\n\n蝙蝠侠对 Robyn 多进程环境中变量的行为产生了好奇。\n\nRobyn 向他保证，确实支持在多进程环境中共享变量。换句话说，处理程序可以分派到多个线程中。\n\n在多进程环境中，任何使用的变量都会在多个进程之间共享。\n\n然而，默认情况下，在 Robyn 中使用多线程时，变量并不受到多线程访问的保护。\n\n<Row>\n<Col>\n\n如果需要在进程内保护某个变量，并希望多个线程能够访问该变量，可以使用 <a href=\"https://docs.python.org/3/library/multiprocessing.html#multiprocessing.Value\">`multiprocessing.Value`</a> 来实现所需的保护。\n\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n        import threading\n        import time\n        from multiprocessing import Value\n\n        from robyn import Robyn, Request\n\n        app = Robyn(__file__)\n\n        count: Value = Value(\"i\", 0)\n\n        def counter():\n            while True:\n                count.value += 1\n                time.sleep(0.2)\n                print(count.value, \"added 1\")\n\n        @app.get(\"/\")\n        def index(request: Request):\n            return f\"{count.value}\"\n\n        threading.Thread(target=counter, daemon=True).start()\n\n        app.start()\n    ```\n\n    </CodeGroup>\n\n  </Col>\n</Row>\n---\n\n## 下一步\n\n蝙蝠侠还想了解是否可以直接在 Robyn 的代码库中使用 Rust。\n\nRobyn 随即向他介绍了如何在 Robyn 中使用 Rust。\n\n[直接使用 Rust](/documentation/zh/api_reference/using_rust_directly)\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/api_reference/openapi.mdx",
    "content": "export const description =\n  '欢迎阅读 Robyn API 文档。本文档旨在提供全面的指南和参考，帮助您快速上手 Robyn，并在使用过程中解决可能遇到的问题。'\n\n## OpenAPI 文档（Swagger）\n\n部署应用程序后，蝙蝠侠收到了大量关于如何使用接口的问题。为了解决这一问题，Robyn 向他介绍了 OpenAPI 规范，该规范能够自动生成接口文档。\n\n开箱即用，Robyn 为应用程序提供了以下默认接口：\n\n- `/docs` Swagger UI 可视化界面\n- `/openapi.json` JSON 格式的 OpenAPI 文档\n\n若需要使用自定义的 OpenAPI 配置，您可以：\n\n    - 将 `openapi.json` 配置文件放在应用程序的根目录中。\n    - 或者，将文件路径作为参数传递给 `Robyn()` 构造函数中的 `openapi_file_path` 参数（参数的优先级高于文件）。\n\n如果您不希望生成 OpenAPI 文档，可以在启动应用程序时传递 `--disable-openapi` 参数来禁用该功能。\n\n```bash\npython app.py --disable-openapi\n```\n\n## 如何使用？\n\n- 查询参数：通过继承 `QueryParams` 类来定义参数类型。\n- 路径参数默认为字符串类型（参考：[https://en.wikipedia.org/wiki/Query_string](https://en.wikipedia.org/wiki/Query_string)）\n\n<CodeGroup title=\"Basic App\">\n\n```python\nfrom robyn.robyn import QueryParams\n\nfrom robyn import Robyn, Request\n\napp = Robyn(\n    file_object=__file__,\n    openapi=OpenAPI(\n        info=OpenAPIInfo(\n            title=\"Sample App\",\n            description=\"This is a sample server application.\",\n            termsOfService=\"https://example.com/terms/\",\n            version=\"1.0.0\",\n            contact=Contact(\n                name=\"API Support\",\n                url=\"https://www.example.com/support\",\n                email=\"support@example.com\",\n            ),\n            license=License(\n                name=\"BSD2.0\",\n                url=\"https://opensource.org/license/bsd-2-clause\",\n            ),\n            externalDocs=ExternalDocumentation(description=\"Find more info here\", url=\"https://example.com/\"),\n            components=Components(),\n        ),\n    ),\n)\n\n\n@app.get(\"/\")\nasync def welcome():\n    \"\"\"welcome endpoint\"\"\"\n    return \"hi\"\n\n\nclass GetRequestParams(QueryParams):\n    appointment_id: str\n    year: int\n\n\n@app.get(\"/api/v1/name\", openapi_name=\"Name Route\", openapi_tags=[\"Name\"])\nasync def get(r: Request, query_params: GetRequestParams):\n    \"\"\"Get Name by ID\"\"\"\n    return r.query_params\n\n\n@app.delete(\"/users/:name\", openapi_tags=[\"Name\"])\nasync def delete(r: Request):\n    \"\"\"Delete Name by ID\"\"\"\n    return r.path_params\n\n\nif __name__ == \"__main__\":\n    app.start()\n```\n\n</CodeGroup>\n\n## 如何在子路由下使用？\n\n<CodeGroup title=\"Subrouters\">\n\n```python\nfrom robyn.robyn import QueryParams\n\nfrom robyn import Request, SubRouter\n\nsubrouter: SubRouter = SubRouter(__name__, prefix=\"/sub\")\n\n\n@subrouter.get(\"/\")\nasync def subrouter_welcome():\n    \"\"\"欢迎来到子路由\"\"\"\n    return \"hiiiiii subrouter\"\n\n\nclass SubRouterGetRequestParams(QueryParams):\n    _id: int\n    value: str\n\n\n@subrouter.get(\"/name\")\nasync def subrouter_get(r: Request, query_params: SubRouterGetRequestParams):\n    \"\"\"根据 ID 获取名称\"\"\"\n    return r.query_params\n\n\n@subrouter.delete(\"/:name\")\nasync def subrouter_delete(r: Request):\n    \"\"\"根据名称删除用户\"\"\"\n    return r.path_params\n\n\napp.include_router(subrouter)\n```\n\n</CodeGroup>\n\n## 其他规范参数\n\nWe support all the params mentioned in the latest OpenAPI specifications (https://swagger.io/specification/). See an example using request & response bodies below:\n\n<CodeGroup title=\"Request & Response Body\">\n\n```python\nfrom robyn.types import JSONResponse, Body\n\nclass Initial(Body):\n    is_present: bool\n    letter: Optional[str]\n\n\nclass FullName(Body):\n    first: str\n    second: str\n    initial: Initial\n\n\nclass CreateItemBody(Body):\n    name: FullName\n    description: str\n    price: float\n    tax: float\n\n\nclass CreateResponse(JSONResponse):\n    success: bool\n    items_changed: int\n\n\n@app.post(\"/\")\ndef create_item(request: Request, body: CreateItemBody) -> CreateResponse:\n    return CreateResponse(success=True, items_changed=2)\n```\n\n</CodeGroup>\n\n随着参考文档的成功部署，蝙蝠侠拥有了一个强大的新工具。Robyn 框架为他提供了创建高效打击犯罪应用所需的灵活性、可扩展性和性能，使他在保护哥谭市的持续战斗中获得了技术优势。\n\n## 使用 Pydantic 模型\n\n如果您已安装 Pydantic（`pip install \"robyn[pydantic]\"` 或 `pip install \"robyn[all]\"`），可以直接使用 Pydantic `BaseModel` 类作为处理函数参数的类型注解。Robyn 将自动验证请求体，**并且**生成丰富的 OpenAPI Schema — 包括属性类型、必填字段、默认值，以及嵌套模型的 `$ref` 引用。\n\n<CodeGroup title=\"Pydantic + OpenAPI\">\n\n```python\nfrom pydantic import BaseModel\n\nclass UserCreate(BaseModel):\n    name: str\n    email: str\n    age: int\n    active: bool = True\n\n@app.post(\"/users\", openapi_tags=[\"Users\"])\ndef create_user(user: UserCreate) -> dict:\n    \"\"\"创建新用户\"\"\"\n    return {\"name\": user.name}\n```\n\n</CodeGroup>\n\n有关 Pydantic 验证、嵌套模型、错误响应和 OpenAPI 集成的完整指南，请参阅 [Pydantic 集成](/documentation/zh/api_reference/pydantic) 页面。\n\n\n## 下一步\n\n完成接口文档配置后，蝙蝠侠开始思考如何提升应用程序的并发处理能力。\n\nRobyn 随即向他介绍了多进程执行的相关内容。\n\n[多进程](/documentation/zh/api_reference/multiprocess_execution)\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/api_reference/pydantic.mdx",
    "content": "export const description =\n  '了解如何在 Robyn 中使用 Pydantic 模型实现自动请求体验证和 OpenAPI 文档生成。'\n\n\n## Pydantic 集成\n\nRobyn 支持 [Pydantic](https://docs.pydantic.dev/) v2 作为可选依赖，用于自动请求体验证和丰富的 OpenAPI 文档生成。验证是**按处理函数选择启用**的 — 只有当您使用 Pydantic `BaseModel` 注解参数时才会激活。未使用 Pydantic 注解的处理函数完全不受影响：不进行解析、不进行验证、无额外开销。当未安装 Pydantic 时，Robyn 不会导入它。\n\n\n## 安装\n\n使用可选扩展安装带 Pydantic 支持的 Robyn：\n\n<CodeGroup title=\"安装\">\n\n```bash {{ title: '仅 Pydantic' }}\npip install \"robyn[pydantic]\"\n```\n\n```bash {{ title: '所有扩展' }}\npip install \"robyn[all]\"\n```\n\n```bash {{ title: 'conda' }}\nconda install robyn pydantic -c conda-forge\n```\n\n</CodeGroup>\n\n`robyn[all]` 包含 Pydantic、Jinja2 模板引擎以及未来的所有可选功能。\n\n\n## 基本用法\n\n定义一个 Pydantic `BaseModel`，并将其用作处理函数参数的类型注解。Robyn 将自动解析传入的 JSON 请求体，根据模型进行验证，并将验证后的实例注入到处理函数中。\n\n<CodeGroup title=\"基本 Pydantic 验证\">\n\n```python {{ title: '同步' }}\nfrom pydantic import BaseModel\nfrom robyn import Robyn\n\napp = Robyn(__file__)\n\n\nclass UserCreate(BaseModel):\n    name: str\n    email: str\n    age: int\n    active: bool = True\n\n\n@app.post(\"/users\")\ndef create_user(user: UserCreate):\n    \"\"\"创建新用户\"\"\"\n    return {\n        \"name\": user.name,\n        \"email\": user.email,\n        \"age\": user.age,\n        \"active\": user.active,\n    }\n\n\nif __name__ == \"__main__\":\n    app.start()\n```\n\n```python {{ title: '异步' }}\nfrom pydantic import BaseModel\nfrom robyn import Robyn\n\napp = Robyn(__file__)\n\n\nclass UserCreate(BaseModel):\n    name: str\n    email: str\n    age: int\n    active: bool = True\n\n\n@app.post(\"/users\")\nasync def create_user(user: UserCreate):\n    \"\"\"创建新用户\"\"\"\n    return {\n        \"name\": user.name,\n        \"email\": user.email,\n        \"age\": user.age,\n        \"active\": user.active,\n    }\n\n\nif __name__ == \"__main__\":\n    app.start()\n```\n\n</CodeGroup>\n\n\n## 验证错误\n\n当请求体验证失败时，Robyn 会自动返回 **422 Unprocessable Entity** 响应，并附带结构化的错误详情。您无需编写任何错误处理代码。\n\n例如，发送 `{\"name\": \"Alice\", \"email\": \"alice@example.com\", \"age\": \"not_a_number\"}` 将产生：\n\n```json\n{\n  \"error\": \"Validation Error\",\n  \"detail\": [\n    {\n      \"type\": \"int_parsing\",\n      \"loc\": [\"age\"],\n      \"msg\": \"Input should be a valid integer, unable to parse string as an integer\",\n      \"input\": \"not_a_number\"\n    }\n  ]\n}\n```\n\n缺少必填字段也会被捕获：\n\n```json\n{\n  \"error\": \"Validation Error\",\n  \"detail\": [\n    {\n      \"type\": \"missing\",\n      \"loc\": [\"email\"],\n      \"msg\": \"Field required\",\n      \"input\": {\"name\": \"Alice\", \"age\": 30}\n    }\n  ]\n}\n```\n\n\n## 嵌套模型\n\nPydantic 模型可以引用其他模型。Robyn 会自动处理嵌套验证。\n\n<CodeGroup title=\"嵌套模型\">\n\n```python\nfrom pydantic import BaseModel\nfrom robyn import Robyn\n\napp = Robyn(__file__)\n\n\nclass Address(BaseModel):\n    street: str\n    city: str\n    zip_code: str\n\n\nclass UserWithAddress(BaseModel):\n    name: str\n    email: str\n    address: Address\n\n\n@app.post(\"/users\")\ndef create_user(data: UserWithAddress):\n    \"\"\"创建带有地址的用户\"\"\"\n    return {\"name\": data.name, \"city\": data.address.city}\n```\n\n</CodeGroup>\n\n如果嵌套的 `address` 对象缺失或格式错误，Robyn 将返回 422 响应，并包含完整的错误路径（例如 `[\"address\", \"city\"]`）。\n\n\n## 与 Request 对象配合使用\n\n您可以在同一个处理函数中同时使用 Pydantic 参数和标准的 `Request` 对象。这样您可以在获取验证后的请求体的同时，访问请求头、查询参数和其他请求元数据。\n\n<CodeGroup title=\"Pydantic + Request\">\n\n```python\nfrom pydantic import BaseModel\nfrom robyn import Robyn, Request\n\napp = Robyn(__file__)\n\n\nclass UserCreate(BaseModel):\n    name: str\n    email: str\n    age: int\n    active: bool = True\n\n\n@app.post(\"/users\")\ndef create_user(request: Request, user: UserCreate):\n    \"\"\"创建用户 — 同时访问原始请求和验证后的模型\"\"\"\n    return {\n        \"method\": request.method,\n        \"name\": user.name,\n        \"email\": user.email,\n    }\n```\n\n</CodeGroup>\n\n\n## 直接返回 Pydantic 模型\n\n您可以直接从处理函数返回 Pydantic 模型实例（或模型列表）。Robyn 会自动将其序列化为 JSON 并设置正确的 `Content-Type` 头 — 无需手动调用 `.model_dump()`。\n\n<CodeGroup title=\"返回模型\">\n\n```python {{ title: '单个模型' }}\n@app.post(\"/users\")\ndef create_user(user: UserCreate) -> UserCreate:\n    \"\"\"验证并直接返回用户\"\"\"\n    return user\n```\n\n```python {{ title: '模型列表' }}\n@app.post(\"/users/batch\")\ndef create_users(user: UserCreate) -> list[UserCreate]:\n    \"\"\"返回多个模型实例\"\"\"\n    return [user, user]\n```\n\n</CodeGroup>\n\n两种形式都会生成 `application/json` 响应。单个模型路径使用 Pydantic 基于 Rust 的 `model_dump_json()` 以获得最大吞吐量。\n\n\n## 验证触发机制\n\nPydantic 验证是**基于注解驱动的，而非基于 HTTP 方法**。路由器在注册时检查每个处理函数的签名；任何使用 `BaseModel` 子类注解的参数都会在路由被调用时自动触发 `request.body` 的验证。这适用于所有 HTTP 方法 — `POST`、`PUT`、`PATCH`、`DELETE` 或任何携带请求体的方法。\n\n<CodeGroup title=\"任意 HTTP 方法\">\n\n```python {{ title: 'PUT' }}\n@app.put(\"/users/:id\")\ndef update_user(user: UserCreate):\n    return {\"updated\": True, \"name\": user.name}\n```\n\n```python {{ title: 'PATCH' }}\n@app.patch(\"/users/:id\")\ndef patch_user(user: UserCreate):\n    return {\"patched\": True, \"name\": user.name}\n```\n\n</CodeGroup>\n\n\n## OpenAPI 集成\n\n当您使用 Pydantic 模型时，Robyn 会自动在 `/openapi.json` 的 OpenAPI 规范中生成丰富的 JSON Schema。包括：\n\n- **属性类型** — `string`、`integer`、`boolean` 等\n- **必填字段** — 没有默认值的字段会列在 `required` 中\n- **默认值** — 在 Schema 中展示\n- **嵌套模型** — 通过 `$ref` 引用，并放置在 `components/schemas` 中\n\n<CodeGroup title=\"OpenAPI 与 Pydantic\">\n\n```python\nfrom pydantic import BaseModel\nfrom robyn import Robyn, Request\n\napp = Robyn(__file__)\n\n\nclass Address(BaseModel):\n    street: str\n    city: str\n    zip_code: str\n\n\nclass UserWithAddress(BaseModel):\n    name: str\n    email: str\n    address: Address\n\n\n@app.post(\"/users\", openapi_tags=[\"Users\"])\ndef create_user(request: Request, data: UserWithAddress) -> dict:\n    \"\"\"创建带有嵌套地址的用户\"\"\"\n    return {\"name\": data.name, \"city\": data.address.city}\n```\n\n</CodeGroup>\n\n生成的 `/openapi.json` 将包含：\n\n```json\n{\n  \"paths\": {\n    \"/users\": {\n      \"post\": {\n        \"requestBody\": {\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"type\": \"object\",\n                \"properties\": {\n                  \"name\": {\"type\": \"string\", \"title\": \"Name\"},\n                  \"email\": {\"type\": \"string\", \"title\": \"Email\"},\n                  \"address\": {\"$ref\": \"#/components/schemas/Address\"}\n                },\n                \"required\": [\"name\", \"email\", \"address\"],\n                \"title\": \"UserWithAddress\"\n              }\n            }\n          }\n        }\n      }\n    }\n  },\n  \"components\": {\n    \"schemas\": {\n      \"Address\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"street\": {\"type\": \"string\", \"title\": \"Street\"},\n          \"city\": {\"type\": \"string\", \"title\": \"City\"},\n          \"zip_code\": {\"type\": \"string\", \"title\": \"Zip Code\"}\n        },\n        \"required\": [\"street\", \"city\", \"zip_code\"],\n        \"title\": \"Address\"\n      }\n    }\n  }\n}\n```\n\n\n## Pydantic 与 Body 对比\n\nRobyn 支持两种类型化请求体的方式。根据您的需求选择合适的方式：\n\n| 特性 | `Body` 子类 | Pydantic `BaseModel` |\n|---|---|---|\n| 安装 | 内置 | `pip install \"robyn[pydantic]\"` |\n| 验证 | 无自动验证 | 完整验证，附带详细错误信息 |\n| 错误响应 | 手动处理 | 自动返回 422 结构化错误 |\n| 返回序列化 | 手动 `dict()` | 自动序列化模型为 JSON |\n| OpenAPI Schema | 基本类型推断 | 完整 JSON Schema（类型、必填、默认值、`$ref`） |\n| 嵌套模型 | 支持（基本） | 支持（OpenAPI 中使用 `$ref`） |\n| 性能开销 | 无 | 仅在安装并使用 Pydantic 时 |\n\n两种方式都支持 OpenAPI 文档。如果您需要验证功能，请使用 Pydantic。如果只需要 OpenAPI Schema 提示而不需要验证，`Body` 即可满足需求。\n\n\n## 重要说明\n\n- **按处理函数选择启用** — 验证仅在使用 Pydantic `BaseModel` 注解参数的处理函数上运行。所有其他处理函数（使用 `Body`、`Request`、路径参数等）行为完全不变，无任何额外开销。\n- **每个处理函数只能有一个 Pydantic 请求体参数** — 每个处理函数最多只能有一个使用 Pydantic 模型注解的参数。整个请求体会被解析到这个模型中。如果需要多个模型输入，请将它们组合成一个包含嵌套字段的父模型。\n- **仅验证请求** — Robyn 根据 Pydantic 模型验证*传入*的请求体，但不验证*传出*的响应。当您返回模型实例时，它会直接序列化而不进行重新验证。这是出于性能考虑的设计决策 — 如果您构造了模型，它已经是有效的。\n\n\n## 下一步\n\n蝙蝠侠想知道 Robyn 的处理函数是否可以分发到多个进程中执行。\n\nRobyn 向他展示了方法！\n\n[多进程执行](/documentation/zh/api_reference/multiprocess_execution)\n\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/api_reference/redirection.mdx",
    "content": "## 重定向\n\n蝙蝠侠希望能够将一些路由重定向到其他路由。Robyn 帮助他实现了这一功能，具体操作如下：\n\n<Row>\n  <Col>\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Request\">\n\n      ```python\n      from robyn import Robyn, Response\n      app = Robyn(__file__)\n\n      @app.get(\"/\")\n      async def index():\n        return Response(\n          status_code=307,\n          description=\"\",\n          headers={\"Location\": \"landing\"},\n        )\n\n      @app.get(\"/landing\")\n      def landing():\n        return \"hii!\"\n      ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n---\n\n## 下一步\n\n实现路由重定向功能后，蝙蝠侠希望能够在出现新的反派时，将文件上传到服务器。Robyn 向他介绍了文件上传和表单处理的功能。\n\n- [文件上传](/documentation/zh/api_reference/file-uploads)\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/api_reference/request_object.mdx",
    "content": "export const description =\n  '在此页面中，我们将深入探讨如何通过不同的接口实现符合预期的交互。'\n\n## 请求对象\n\n请求对象是一个数据类，包含了与该次请求相关的所有信息。它会在路由处理程序中作为第一个参数传入。\n\n<Row>\n<Col>\n该请求对象在 Rust 端创建，但会作为数据类暴露给 Python。\n\n<ul>\n<li>\nAttributes:\n</li>\n<li>\nquery_params (QueryParams)：请求的查询参数。`例如：/user?id=123 -> {\"id\": [\"123\"]}`\n</li>\n<li>\nheaders (dict[str, str])：请求的标头。`例如：{\"Content-Type\": \"application/json\"}`\n</li>\n<li>\nparams (dict[str, str])：请求的路径参数。`例如：/user/:id -> {\"id\": \"123\"}`\n</li>\n<li>\nbody (Union[str, bytes])：请求的原始正文。对于 JSON 请求体，请使用 `json()` 方法将正文解析为具有正确类型保留的字典。\n</li>\n<li>\nmethod (str)：请求的方法。`例如：GET、POST、PUT、DELETE`\n</li>\n<li>\nip_addr (Optional[str])：客户端的 IP 地址\n</li>\n<li>\nidentity (Optional[Identity])：客户端的身份\n</li>\n\n</ul>\n\n</Col>\n  <Col>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    @dataclass\n    class Request:\n      \"\"\"\n      query_params: QueryParams\n      headers: Headers\n      path_params: dict[str, str]\n      body: Union[str, bytes]\n      method: str\n      url: Url\n      form_data: dict[str, str]\n      files: dict[str, bytes]\n      ip_addr: Optional[str]\n      identity: Optional[Identity]\n      \"\"\"\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n## 解析 JSON 请求体\n\n`request.json()` 方法将请求体解析为 JSON，并返回一个保留完整类型的 Python `dict` 或 `list`（JSON 对象返回 `dict`，JSON 数组返回 `list`）：\n\n- JSON `null` 转换为 Python `None`\n- JSON 数字转换为 Python `int` 或 `float`\n- JSON 布尔值转换为 Python `bool`\n- JSON 字符串转换为 Python `str`\n- JSON 数组转换为 Python `list`\n- JSON 对象转换为 Python `dict`\n\n嵌套结构将被递归处理，最大深度为 128 层。\n\n<CodeGroup title=\"解析 JSON\" tag=\"POST\" label=\"/example\">\n\n```python\n@app.post(\"/example\")\nasync def handler(request: Request):\n    data = request.json()  # 返回一个保留类型的字典或列表\n    # 例如 {\"count\": 42, \"active\": true, \"tags\": [\"a\", \"b\"]}\n    # ->   {\"count\": 42, \"active\": True, \"tags\": [\"a\", \"b\"]}\n    return {\"received\": data}\n```\n</CodeGroup>\n\n如果请求体不是有效的 JSON，将会抛出 `ValueError`。\n\n## Extra Path Parameters\n\nRobyn 支持通过 `*extra` 语法捕获额外的路径参数，这样可以捕获在定义的路由之后的所有额外的路径段。\n\n例如，如果有这样一个路由：\n\n<CodeGroup>\n    ```python\n    @app.get(\"/sync/extra/*extra\") def sync_param_extra(request: Request):\n        extra = request.path_params[\"extra\"]\n        return extra\n    ```\n</CodeGroup>\n\n在 `/sync/extra/` 后的任何路径段都会被捕获到 `extra` 参数中。例如：\n\n<ul>\n  <li>请求 `/sync/extra/foo/bar` 将使得 `extra = \"foo/bar`\"</li>\n  <li>请求 `/sync/extra/123/456/789` 将使得 `extra = \"123/456/789`\"</li>\n</ul>\n\n你可以通过 `request.path_params[\"extra\"]` 在路由处理程序中访问这些额外的路径参数。\n\n在处理动态嵌套路由，或者捕获未知数量的路径段时，这个功能将有奇效。\n\n---\n\n## 便捷参数访问\n\n你可以在函数签名中直接声明带类型注解的查询参数和路径参数，无需手动从请求对象中提取和转换。Robyn 会自动解析并进行类型转换。\n\n任何不匹配已知请求组件（`Request`、`QueryParams`、`Headers` 等）的处理函数参数，都会被视为独立的路径参数或查询参数。\n\n<Row>\n  <Col>\n    **基本用法** — 带类型转换和默认值的路径参数与查询参数。\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"便捷参数\" tag=\"GET\" label=\"/items/:id?q=hello&page=5\">\n\n    ```python {{ title: '类型化参数' }}\n    @app.get(\"/items/:id\")\n    async def get_item(id: int, q: str, page: int = 1):\n        # id 从路径参数字符串自动转换为 int\n        # q 从 ?q=... 获取\n        # page 未提供时默认为 1\n        return {\"id\": id, \"q\": q, \"page\": page}\n    ```\n\n    ```python {{ title: '与 Request 混合使用' }}\n    @app.get(\"/items/:id\")\n    async def get_item(request: Request, id: int, q: str = \"\"):\n        # request 仍然按原来的方式注入\n        # id 和 q 作为独立参数解析\n        return {\"id\": id, \"q\": q, \"method\": request.method}\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n<Row>\n  <Col>\n    **Optional、List、Bool 和 Float 参数** — Robyn 自动处理常见的 Python 类型。\n\n    - `Optional[T]` — 未提供时解析为 `None`\n    - `List[T]` — 收集重复的查询参数（例如 `?tag=a&tag=b`）\n    - `bool` — 接受 `true/false`、`1/0`、`yes/no`、`on/off`\n    - `float` — 标准浮点数转换\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"高级类型\" tag=\"GET\" label=\"/search\">\n\n    ```python {{ title: 'Optional' }}\n    @app.get(\"/search\")\n    def search(name: str, age: Optional[int] = None):\n        return {\"name\": name, \"age\": age}\n    # GET /search?name=bob        -> {\"name\": \"bob\", \"age\": null}\n    # GET /search?name=bob&age=30 -> {\"name\": \"bob\", \"age\": 30}\n    ```\n\n    ```python {{ title: 'List' }}\n    from typing import List\n\n    @app.get(\"/filter\")\n    def filter_items(tag: List[str]):\n        return {\"tags\": tag}\n    # GET /filter?tag=python&tag=rust -> {\"tags\": [\"python\", \"rust\"]}\n    ```\n\n    ```python {{ title: 'Bool 和 Float' }}\n    @app.get(\"/settings\")\n    def settings(active: bool = False, price: float = 0.0):\n        return {\"active\": active, \"price\": price}\n    # GET /settings?active=true&price=19.99\n    # -> {\"active\": true, \"price\": 19.99}\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n<Row>\n  <Col>\n    **错误处理** — 如果缺少必需的参数或值无法转换为声明的类型，Robyn 会自动返回 `400 Bad Request` 响应。\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"验证错误\" tag=\"GET\" label=\"/items/:id\">\n\n    ```python {{ title: '自动返回 400' }}\n    @app.get(\"/items/:id\")\n    def get_item(id: int, q: str):\n        return {\"id\": id, \"q\": q}\n\n    # GET /items/42         -> 400（缺少必需的 'q'）\n    # GET /items/abc?q=test -> 400（无法将 'abc' 转换为 int）\n    ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n---\n\n## 下一步\n\n接下来，蝙蝠侠希望了解 Robyn 服务器的配置。于是他开始了解 Robyn 环境配置文件的概念。\n\n- [Robyn Env](/documentation/zh/api_reference/robyn_env)\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/api_reference/robyn_env.mdx",
    "content": "export const description = '在本节中，我们将学习如何通过配置文件配置服务器。'\n\n## 通过环境文件配置服务器\n\n因为频繁修改代码容易引起报错，蝙蝠侠想要通过使用环境文件来配置服务器。\n\n## 环境变量\n\n- `ROBYN_PORT`：设置 Robyn 服务器监听的端口。\n  - 默认值：`8080`\n  - 示例：`ROBYN_PORT=3000`\n- `ROBYN_HOST`：设置 Robyn 服务器的主机地址。\n  - 默认值：`127.0.0.1`\n  - 示例：`ROBYN_HOST=0.0.0.0`\n- `ROBYN_BROWSER_OPEN`：启动成功后是否自动打开浏览器。\n  - 默认值：`False`\n  - 示例：`ROBYN_BROWSER_OPEN=True`\n- `ROBYN_DEV_MODE`：是否开启开发者模式。\n  - 默认值：`False`\n  - 示例：`ROBYN_DEV_MODE=True`\n- `ROBYN_MAX_PAYLOAD_SIZE`：设置 HTTP 请求和 WebSocket 消息的最大负载大小（以字节为单位）。\n  - 默认值：1000000 bytes\n  - 示例：`ROBYN_MAX_PAYLOAD_SIZE=1000000`\n\n您可以使用 `robyn.env` 文件自动加载这些环境变量。\n\n这些环境变量通常存储在项目根目录下的 `robyn.env` 文件中。服务器在启动时会自动读取该文件，并据此进行配置。\n\n更多关于项目结构和 `robyn.env` 的使用方法，请参考以下文档：\n\n```bash {{title: '项目结构示例'}}\n--project/\n  --robyn.env\n  --index.py\n  ...\n```\n\n`robyn.env` 文件示例如下：\n\n```bash {{ title: '简易 Robyn.env' }}\nROBYN_PORT=8080\nROBYN_HOST=127.0.0.1\nRANDOM_ENV=123\nROBYN_BROWSER_OPEN=True\nROBYN_DEV_MODE=True\nROBYN_MAX_PAYLOAD_SIZE=1000000\n```\n\n随着 Web 应用程序的顺利部署和运行，蝙蝠侠拥有了一个强大的新工具。Robyn 框架为他提供了创建高效打击犯罪应用所需的灵活性、可扩展性和高性能，使他在保护哥谭市的战斗中获得了技术上的优势。\n\n## 下一步\n\n蝙蝠侠：罗宾。请告诉我更多。  \nRobyn：接下来我们来了解中间件和 `Events` 事件吧！\n\n- [中间件](/documentation/zh/api_reference/middlewares)\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/api_reference/scaling.mdx",
    "content": "## 多核扩展\n\n蝙蝠侠学习了如何利用多核资源来提升应用程序的性能。他使用了以下命令来启动多进程和多线程：\n\n<Row>\n  <Col>\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Request\">\n\n      ```python\n      python3 app.py --workers=N --process=M\n      ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n---\n\n## 下一步\n\n掌握了多核扩展技术后，蝙蝠侠希望进一步探索 Robyn 框架的高级特性。Robyn 随即向他介绍了一些更为复杂和强大的功能。\n\n- [高级特性](/documentation/zh/api_reference/advanced_features)\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/api_reference/server_sent_events.mdx",
    "content": "export const description =\n  '学习如何在 Robyn 中实现服务器发送事件 (SSE)，用于实时的服务器到客户端通信。'\n\n## 服务器发送事件 (SSE)\n\n在学习了[表单数据处理](/documentation/zh/api_reference/form_data)之后，蝙蝠侠意识到他需要一种方法来向他的犯罪监控仪表板推送实时更新。犯罪分子不会等待蝙蝠侠刷新浏览器！\n\n他发现了服务器发送事件 (SSE) - 一个完美的解决方案，用于通过 HTTP 进行服务器到客户端的单向通信。SSE 允许蝙蝠侠将实时数据流式传输到他的仪表板，而无需完整双向通信的复杂性。\n\n\"这正是我的犯罪警报系统所需要的！\"蝙蝠侠惊呼道。\"我可以在检测到新犯罪时立即向仪表板推送更新。\"\n\n服务器发送事件非常适合：\n- 实时通知\n- 实时数据源\n- 进度更新\n- 聊天应用程序（仅服务器到客户端）\n- 仪表板更新\n- 日志流式传输\n\n## 如何工作？\n\n<Row>\n<Col>\n\n蝙蝠侠可以使用 `SSEResponse` 和 `SSEMessage` 类创建服务器发送事件流。他可以根据需要使用常规生成器和异步生成器：\n\n- **常规生成器**：非常适合简单的数据流或使用同步操作时\n- **异步生成器**：当蝙蝠侠需要在流中执行异步操作（如数据库查询或 API 调用）时的理想选择\n\n</Col>\n<Col sticky>\n\n<CodeGroup title=\"SSE 响应\" tag=\"GET\" label=\"/events\">\n\n```python {{ title: '基本 SSE 流' }}\nfrom robyn import Robyn, SSEResponse, SSEMessage\nimport time\n\napp = Robyn(__file__)\n\n@app.get(\"/events\")\ndef stream_events(request):\n    def event_generator():\n        for i in range(10):\n            yield SSEMessage(f\"事件 {i}\", id=str(i))\n            time.sleep(1)\n    \n    return SSEResponse(event_generator())\n```\n\n```python {{ title: 'JSON 数据流' }}\nfrom robyn import Robyn, SSEResponse, SSEMessage\nimport json\nimport time\n\napp = Robyn(__file__)\n\n@app.get(\"/events/json\")\ndef stream_json_events(request):\n    def json_event_generator():\n        for i in range(5):\n            data = {\n                \"id\": i,\n                \"message\": f\"更新 {i}\",\n                \"timestamp\": time.time()\n            }\n            yield SSEMessage(\n                json.dumps(data), \n                event=\"update\", \n                id=str(i)\n            )\n            time.sleep(2)\n    \n    return SSEResponse(json_event_generator())\n```\n\n```python {{ title: '异步生成器流' }}\nfrom robyn import Robyn, SSEResponse, SSEMessage\nimport asyncio\nimport time\n\napp = Robyn(__file__)\n\n@app.get(\"/events/async\")\nasync def stream_async_events(request):\n    async def async_event_generator():\n        for i in range(8):\n            # 模拟异步工作，如数据库调用\n            await asyncio.sleep(0.5)\n            yield SSEMessage(\n                f\"异步消息 {i} - {time.strftime('%H:%M:%S')}\", \n                event=\"async\", \n                id=str(i)\n            )\n    \n    return SSEResponse(async_event_generator())\n```\n\n</CodeGroup>\n</Col>\n</Row>\n\n---\n\n## 异步生成器\n\n<Row>\n<Col>\n\n当蝙蝠侠需要在他的 SSE 流中执行异步操作时（如从数据库获取数据或进行 API 调用），他使用带有 `async def` 和 `await` 的异步生成器。这使他能够并发处理多个流，而不会阻塞其他操作。\n\n关键区别是为生成器函数使用 `async def`，并在生成器内部为异步操作使用 `await`：\n\n</Col>\n\n<Col sticky>\n\n<CodeGroup title=\"高级异步 SSE\" tag=\"GET\" label=\"/events/database\">\n\n```python {{ title: '数据库流' }}\nfrom robyn import Robyn, SSEResponse, SSEMessage\nimport asyncio\nimport json\nimport time\n\napp = Robyn(__file__)\n\n@app.get(\"/events/database\")\nasync def stream_database_events(request):\n    async def database_event_generator():\n        for i in range(10):\n            # 模拟异步数据库查询\n            await asyncio.sleep(0.3)\n            \n            # 模拟从数据库获取数据\n            data = {\n                \"crime_id\": i,\n                \"location\": f\"哥谭市第{i}区\",\n                \"severity\": \"高\" if i % 2 == 0 else \"低\",\n                \"timestamp\": time.time()\n            }\n            \n            yield SSEMessage(\n                json.dumps(data),\n                event=\"crime_alert\",\n                id=str(i)\n            )\n    \n    return SSEResponse(database_event_generator())\n```\n\n```python {{ title: 'API 集成流' }}\nfrom robyn import Robyn, SSEResponse, SSEMessage\nimport asyncio\nimport aiohttp\nimport json\n\napp = Robyn(__file__)\n\n@app.get(\"/events/api\")\nasync def stream_api_events(request):\n    async def api_event_generator():\n        async with aiohttp.ClientSession() as session:\n            for i in range(5):\n                try:\n                    # 进行异步 API 调用\n                    async with session.get(f\"https://api.example.com/data/{i}\") as response:\n                        data = await response.json()\n                        \n                        yield SSEMessage(\n                            json.dumps(data),\n                            event=\"api_update\",\n                            id=str(i)\n                        )\n                except Exception as e:\n                    yield SSEMessage(\n                        f\"获取数据错误: {str(e)}\",\n                        event=\"error\",\n                        id=f\"error_{i}\"\n                    )\n                \n                await asyncio.sleep(1)\n    \n    return SSEResponse(api_event_generator())\n```\n\n</CodeGroup>\n</Col>\n</Row>\n\n---\n\n## 接下来做什么？\n\n蝙蝠侠已经掌握了服务器发送事件，现在可以向他的犯罪仪表板流式传输实时更新。虽然 SSE 非常适合服务器到客户端的单向通信，但蝙蝠侠意识到他需要双向通信来实现更多交互功能，比如与他的盟友进行实时聊天。\n\n接下来，他想探索如何使用 [WebSocket](/documentation/zh/api_reference/websockets) 处理双向通信，以获得更多交互功能。\n\n如果蝙蝠侠需要处理意外情况，他将学习[异常处理](/documentation/zh/api_reference/exceptions)以使他的应用程序更加健壮。\n\n为了将他的犯罪监控系统扩展到多个进程，蝙蝠侠将探索[多核扩展](/documentation/zh/api_reference/scaling)。\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/api_reference/templating.mdx",
    "content": "export const description =\n  '在此页面中，我们将深入探讨如何通过不同的接口实现符合预期的交互。'\n\n## 模板\n\n蝙蝠侠希望能在网站上快速渲染 HTML 页面，并打算使用模板引擎来实现这一目标。Robyn 告诉他可以使用 Jinja2 模板引擎，并通过 JinjaTemplate 类来渲染 HTML 页面。\n\n<Row>\n<Col>\n了解一番后，蝙蝠侠很高兴地发现，他还可以将事件作为函数和装饰器进行添加。\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    from robyn.templating import JinjaTemplate\n\n    current_file_path = pathlib.Path(__file__).parent.resolve()\n    JINJA_TEMPLATE = JinjaTemplate(os.path.join(current_file_path, \"templates\"))\n\n    @app.get(\"/template_render\")\n    def template_render():\n        context = {\"framework\": \"Robyn\", \"templating_engine\": \"Jinja2\"}\n\n        template = JINJA_TEMPLATE.render_template(template_name=\"test.html\", **context)\n        return template\n\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n<Row>\n\n  <Col>\n  `test.html` 文件\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n      ```html\n        <!DOCTYPE html>\n        <html lang=\"en\">\n        <head>\n          <meta charset=\"utf-8\">\n          <title>Results</title>\n        </head>\n\n        <body>\n         Hello {{ framework }}! You're using {{ templating_engine }}.\n        </body>\n      ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n## 自定义模板引擎\n\n蝙蝠侠还发现，Robyn 允许支持自定义模板引擎。\n\n<Row>\n<Col>\n为此，您需要从 `robyn.templating` 导入 `TemplateInterface`：\n</Col>\n  <Col sticky>\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n      ```python\n      from robyn.templating import TemplateInterface\n      ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n<Row>\n<Col>\n然后，在您的项目中定义 `render_template` 方法。以下是一个自定义模板引擎的实现示例：\n</Col>\n  <Col sticky>\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n      ```python\n      class JinjaTemplate(TemplateInterface):\n        def __init__(self, directory, encoding=\"utf-8\", followlinks=False):\n            self.env = Environment(\n                loader=FileSystemLoader(\n                    searchpath=directory, encoding=encoding, followlinks=followlinks\n                )\n            )\n\n        def render_template(self, template_name: str, **kwargs):\n            return self.env.get_template(template_name).render(**kwargs)\n      ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n## 下一步\n\n在掌握了模板引擎的使用后，蝙蝠侠还希望能够在应用中实现路由重定向功能。\n\n- [重定向](/documentation/zh/api_reference/redirection)\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/api_reference/timeout_configuration.mdx",
    "content": "export const description =\n  '配置超时设置以处理高并发场景并防止资源耗尽。'\n\n# 超时配置\n\nRobyn 支持全面的超时配置，以处理高并发场景并防止资源耗尽，如\"打开文件过多\"错误。 {{ className: 'lead' }}\n\n## 配置选项\n\n### 方法参数\n\n直接在 `app.start()` 方法中配置超时：\n\n```python\nfrom robyn import Robyn\n\napp = Robyn(__file__)\n\n@app.get(\"/\")\nasync def hello(request):\n    return \"Hello, world!\"\n\n# 配置超时设置\napp.start(\n    host=\"0.0.0.0\", \n    port=8080,\n    client_timeout=30,          # 客户端连接超时（秒）\n    keep_alive_timeout=20,      # 保持连接超时（秒）\n)\n```\n\n### 环境变量\n\n使用环境变量覆盖配置：\n\n```bash\n# 设置超时配置\nexport ROBYN_CLIENT_TIMEOUT=45\nexport ROBYN_KEEP_ALIVE_TIMEOUT=30\n\n# 启动应用程序\npython app.py\n```\n\n## 配置参数\n\n| 参数 | 默认值 | 描述 | 环境变量 |\n|------|-------|------|---------|\n| `client_timeout` | 30 | 客户端请求处理的最大时间（秒） | `ROBYN_CLIENT_TIMEOUT` |\n| `keep_alive_timeout` | 20 | 保持空闲连接的时间（秒） | `ROBYN_KEEP_ALIVE_TIMEOUT` |\n\n## 使用示例\n\n### 基本配置\n\n```python\n# 最小超时配置\napp.start(client_timeout=30)\n```\n\n### 高流量生产设置\n\n```python\n# 针对高流量场景优化\napp.start(\n    host=\"0.0.0.0\",\n    port=8080,\n    client_timeout=60,      # 允许更长的处理时间\n    keep_alive_timeout=15,  # 更短的保持连接以加快周转\n)\n```\n\n### 开发设置\n\n```python\n# 开发友好设置\napp.start(\n    client_timeout=300,     # 调试的长超时\n    keep_alive_timeout=60,  # 测试的长保持连接\n)\n```\n\n### 负载测试配置\n\n```python\n# 针对 wrk 等工具的负载测试优化\napp.start(\n    client_timeout=10,      # 快速超时\n    keep_alive_timeout=5,   # 快速连接周转\n)\n```\n\n## 环境变量优先级\n\n环境变量优先于方法参数：\n\n```python\n# 如果设置了 ROBYN_CLIENT_TIMEOUT=60，将使用 60，否则使用 30\napp.start(client_timeout=30)\n```\n\n## 故障排除\n\n### \"打开文件过多\"错误\n\n如果遇到文件描述符耗尽：\n\n1. **增加系统限制：**\n   ```bash\n   ulimit -n 65536\n   ```\n\n2. **优化超时设置：**\n   ```python\n   app.start(\n       client_timeout=15,      # 更短的超时\n       keep_alive_timeout=5,   # 更快的连接清理\n   )\n   ```\n\n3. **在部署中使用环境变量：**\n   ```bash\n   export ROBYN_CLIENT_TIMEOUT=15\n   export ROBYN_KEEP_ALIVE_TIMEOUT=5\n      ```\n\n### 性能调优\n\n**对于高吞吐量 API：**\n- 较低的 `keep_alive_timeout` (5-15秒)\n- 中等的 `client_timeout` (15-30秒)\n\n**对于长时间运行的操作：**\n- 较高的 `client_timeout` (60-300秒)\n- 标准的 `keep_alive_timeout` (20-30秒)\n\n## 与其他框架的比较\n\n| 框架 | 默认客户端超时 | 默认保持连接 | 配置方式 |\n|-----|--------------|-------------|----------|\n| **Robyn** | 30秒 | 20秒 | 方法参数 + 环境变量 |\n| Uvicorn | 无 | 20秒 | 构造函数参数 |\n| Express.js | 120秒 | 5秒 | 方法链式调用 |\n| Django/Gunicorn | 30秒 | 不适用 | 配置文件 |\n| Flask/Gunicorn | 30秒 | 2秒 | 命令行选项 |\n\n## 最佳实践\n\n1. **在生产环境中始终设置明确的超时**\n2. **使用环境变量进行特定部署的配置**\n3. **在负载下监控文件描述符使用**\n4. **用现实负载模式测试超时设置**\n5. **从保守值开始，根据指标进行调优**\n\n## 迁移指南\n\n### 从早期版本\n\n如果从早期 Robyn 版本升级，默认行为会改变：\n\n**之前（无限超时）：**\n```python\n# 以前：无超时（可能导致资源耗尽）\napp.start(host=\"0.0.0.0\", port=8080)\n```\n\n**之后（合理默认值）：**\n```python\n# 现在：自动 30秒 客户端超时，20秒 保持连接\napp.start(host=\"0.0.0.0\", port=8080)\n# 等价于：\napp.start(\n    host=\"0.0.0.0\", \n    port=8080,\n    client_timeout=30,\n    keep_alive_timeout=20,\n)\n```"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/api_reference/using_rust_directly.mdx",
    "content": "## 使用 Rust 扩展 Robyn\n\n有时，蝙蝠侠可能需要处理 CPU 密集型任务或需要大量内存的操作。在这种情况下，他可能希望使用 Rust 来实现这些任务。Robyn 提供了一种特殊的方式，允许通过 Rust 扩展 Python 代码，并且能够在保持代码热重载特性的同时进行扩展，使代码在许多情况下仍然表现得像解释型语言。\n\n<Row>\n<Col>\n首先，您需要创建一个 Rust 文件，我们将其命名为 `hello_world.rs`。可以通过以下 CLI 命令创建该文件：\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n    python -m robyn --create-rust-file hello_world\n    ```\n\n    </CodeGroup>\n\n  </Col>\n</Row>\n<Row>\n\n  <Col>\n  然后，您可以打开该文件并开始编写 Rust 代码。例如，假设我们实现一个返回平方值的函数：\n\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n      ```rust\n      // hello_world.rs\n\n      // rustimport:pyo3\n\n      use pyo3::prelude::*;\n\n      #[pyfunction]\n      fn square(n: i32) -> i32 {\n          n * n\n          // 这是一个注释\n      }\n\n      ```\n\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n<Row>\n\n  <Col>\n  每个通过 CLI 创建的 Rust 文件都会在文件顶部添加一个特殊注释，Robyn 会根据该注释确定需要导入的依赖项。在此例中，我们导入了 `pyo3` 包。您可以根据需要导入多个包，还可以从 `crates.io` 导入`crates`。例如，如果您希望使用 `rusqlite` 包，可以按如下方式导入：\n\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n      ```rust\n      // rustimport:pyo3\n\n    //:\n    //: [dependencies]\n    //: rusqlite = \"0.19.0\"\n\n      use pyo3::prelude::*;\n\n      #[pyfunction]\n      fn square(n: i32) -> i32 {\n          n * n * n\n          // 这是另一个注释\n      }\n\n      ```\n\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n<Row>\n  接下来，您可以在 Python 代码中导入并使用该 Rust 函数：\n\n  <Col sticky>\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n      ```python\n      from hello_world import square\n\n      print(square(5))\n      ```\n    </CodeGroup>\n\n  </Col>\n\n要运行此代码，您需要使用 `--compile-rust-path` 标志来编译 Rust 代码并执行。同时，您还可以使用 `--dev` 标志监控 Rust 代码的变化，并在变化时即时重新编译：\n\n  <Col sticky>\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n      ```python\n      python -m robyn --compile-rust-path \".\" --dev\n      ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n以下是一个示例，展示了如何在 Robyn 应用中使用 Rust 文件，借助 `rusqlite` 包连接数据库并返回表中的行数：[rusty-sql 示例项目](https://github.com/sansyrox/rusty-sql)\n\n## 下一步\n\n蝙蝠侠很好奇 Robyn 还能做什么。\n\nRobyn 向他透露，这个框架还可以支持 GraphQL。\n\n[GraphQL 支持](/documentation/zh/api_reference/graphql-support)\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/api_reference/views.mdx",
    "content": "export const description =\n  '在此页面中，我们将深入探讨如何通过不同的接口实现符合预期的交互。'\n\n随着代码库的不断扩展，蝙蝠侠希望找到一种更好的方式来组织代码。Robyn 告诉他，他有能力以更清晰的方式组织代码，并向他介绍了视图和子路由器的概念。\n\n## 视图 (Views)\n\n<Row>\n<Col>\n为了更好地组织代码，无论是按照职责分组还是进行代码拆分，蝙蝠侠都可以选择使用视图。\n\n简单来说，视图就是一个包含多个处理函数（闭包）的函数。例如：\n\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n    ```python\n      from robyn import Request\n\n      def sample_view():\n        def get():\n            return \"Hello, world!\"\n\n        def post(request: Request):\n            body = request.body\n            return Response({\"status_code\": 200, \"description\": body, \"headers\": {}})\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n上面的视图包含了处理 GET 和 POST 请求的两个闭包。\n\n您可以通过以下两种方式来提供视图：\n\n<Row>\n\n  <Col>\n  使用 `@app.view` 装饰器：\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n      ```python\n      from robyn import Request\n\n      @app.view(\"/sync/view/decorator\")\n      def sync_decorator_view():\n       def get():\n           return \"Hello, world!\"\n\n       def post(request: Request):\n           body = request.body\n           return body\n\n      ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n<Row>\n  <Col>\n  使用 `app.add_view` 方：\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n      ```python\n        from .views import sample_view\n\n        ...\n        ...\n\n        app.add_view(\"/\", sample_view)\n      ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n---\n\n## 子路由器 (Subrouters)\n\n<Row>\n\n  <Col>\n\n蝙蝠侠可以在 Robyn 项目中创建子路由器，用于将多个相关的路由分组在一起，方便管理和使用。\n\n子路由器不仅可以用来管理常规的 HTTP 路由，还可以用来管理 Web 套接字（web sockets）路由。换句话说，子路由器的功能与主路由器类似，它可以被视作是主路由器的一个简化版。\n\n有一点需要注意的是，子路由器必须添加到主路由器中。\n\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"Request\" tag=\"GET\" label=\"/hello_world\">\n\n      ```python\n      from robyn import Robyn, SubRouter\n\n      app = Robyn(__file__)\n\n      sub_router = SubRouter(\"/sub_router\")\n\n      @sub_router.get(\"/hello\")\n      def hello():\n          return \"Hello, world\"\n\n      web_socket = SubRouter(\"/web_socket\")\n\n      @web_socket.message()\n      async def hello():\n          return \"Hello, world\"\n\n      app.include_router(sub_router)\n      ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n## 下一步\n\n现在，蝙蝠侠已经了解了如何组织代码，接下来他希望学习如何向代码中添加依赖项。Robyn 告诉他，依赖项可以在全局级别、路由器级别和视图级别进行添加。\n\n- [依赖注入](/documentation/zh/api_reference/dependency_injection)\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/api_reference/websockets.mdx",
    "content": "export const description =\n  '在此页面中，我们将深入探讨如何通过不同的接口实现符合预期的交互。'\n\n## WebSockets {{ tag: 'WebSockets', label: 'WebSockets' }}\n\n<Row>\n<Col>\n在掌握了[服务器发送事件](/documentation/zh/api_reference/server_sent_events)的单向通信之后，蝙蝠侠意识到他需要更强大的功能。当戈登局长想要在危机情况下与他实时聊天时，蝙蝠侠需要双向通信。\n\n\"SSE 很适合向我的仪表板推送更新，\"蝙蝠侠想道，\"但我需要双向通信来与我的盟友协调！\"\n\n为了实现双向实时通信，蝙蝠侠学习了如何使用 Robyn 的现代装饰器 API 处理 WebSocket。底层消息通过 Rust 通道传递，实现最大性能——消息分发过程中无需 Python GIL。\n</Col>\n  <Col sticky>\n\n    <CodeGroup title=\"Request\" tag=\"WebSocket\" label=\"/web_socket\">\n\n    ```python {{ title: '基础回声' }}\n    from robyn import Robyn\n\n    app = Robyn(__file__)\n\n    @app.websocket(\"/web_socket\")\n    async def handler(websocket):\n        while True:\n            msg = await websocket.receive_text()\n            await websocket.send_text(f\"Echo: {msg}\")\n\n    app.start()\n    ```\n\n    ```python {{ title: '带回调' }}\n    from robyn import Robyn\n\n    app = Robyn(__file__)\n\n    @app.websocket(\"/web_socket\")\n    async def handler(websocket):\n        while True:\n            msg = await websocket.receive_text()\n            await websocket.send_text(f\"Echo: {msg}\")\n\n    @handler.on_connect\n    def on_connect(websocket):\n        return \"已连接到 ws\"\n\n    @handler.on_close\n    def on_close(websocket):\n        return \"再见，来自 ws\"\n\n    app.start()\n    ```\n    </CodeGroup>\n\n  </Col>\n</Row>\n\n---\n\n## 接收消息 {{ tag: 'receive_text', label: 'receive_text' }}\n\n<Row>\n  <Col>\n    `receive_text()` 方法会阻塞直到下一条消息到达。它由 Rust 的 `tokio::mpsc` 通道支持，因此 Python 处理程序在等待时不会持有 GIL。\n\n    当客户端断开连接时，`receive_text()` 会抛出 `WebSocketDisconnect` 异常。您可以显式捕获它，也可以让内部包装器静默处理。\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"接收消息\" tag=\"WebSocket\" label=\"/web_socket\">\n\n      ```python {{ title: '文本消息' }}\n      @app.websocket(\"/ws\")\n      async def handler(websocket):\n          try:\n              while True:\n                  msg = await websocket.receive_text()\n                  await websocket.send_text(f\"收到: {msg}\")\n          except WebSocketDisconnect:\n              print(f\"客户端 {websocket.id} 已断开\")\n      ```\n\n      ```python {{ title: 'JSON 消息' }}\n      @app.websocket(\"/api\")\n      async def handler(websocket):\n          while True:\n              data = await websocket.receive_json()\n              result = process(data)\n              await websocket.send_json({\"status\": \"ok\", \"result\": result})\n      ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n---\n\n## 发送消息 {{ tag: 'send_text', label: 'send_text' }}\n\n<Row>\n  <Col>\n    使用 `send_text()` 或 `send_json()` 向当前客户端发送消息。所有发送方法都是异步的。\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"发送消息\" tag=\"WebSocket\" label=\"/web_socket\">\n\n      ```python {{ title: '发送文本' }}\n      @app.websocket(\"/ws\")\n      async def handler(websocket):\n          while True:\n              msg = await websocket.receive_text()\n              await websocket.send_text(f\"Echo: {msg}\")\n      ```\n\n      ```python {{ title: '发送 JSON' }}\n      @app.websocket(\"/ws\")\n      async def handler(websocket):\n          while True:\n              data = await websocket.receive_json()\n              await websocket.send_json({\"echo\": data})\n      ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n---\n\n## 广播 {{ tag: 'broadcast', label: 'broadcast' }}\n\n<Row>\n  <Col>\n    使用 `broadcast()` 方法向同一 WebSocket 端点上的所有已连接客户端发送消息。\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"广播\" tag=\"WebSocket\" label=\"/chat\">\n\n      ```python {{ title: '广播' }}\n      @app.websocket(\"/chat\")\n      async def handler(websocket):\n          while True:\n              msg = await websocket.receive_text()\n              # 向所有已连接的客户端发送\n              await websocket.broadcast(f\"用户 {websocket.id}: {msg}\")\n              # 仅向当前客户端发送确认\n              await websocket.send_text(\"您的消息已发送\")\n      ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n---\n\n## 查询参数 {{ tag: 'query_params', label: 'query_params' }}\n\n<Row>\n  <Col>\n    通过 `websocket.query_params` 访问 WebSocket 连接 URL 中的查询参数。\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"查询参数\" tag=\"WebSocket\" label=\"/ws?name=gordon&role=commissioner\">\n\n      ```python {{ title: '查询参数' }}\n      @app.websocket(\"/ws\")\n      async def handler(websocket):\n          name = websocket.query_params.get(\"name\")\n          role = websocket.query_params.get(\"role\")\n\n          if name == \"gordon\" and role == \"commissioner\":\n              await websocket.broadcast(\"戈登已授权！\")\n\n          while True:\n              msg = await websocket.receive_text()\n              await websocket.send_text(f\"你好 {name}: {msg}\")\n      ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n---\n\n## 便捷查询参数访问 {{ tag: 'easy_access', label: 'easy_access' }}\n\n<Row>\n  <Col>\n    除了手动调用 `websocket.query_params.get(...)` 之外，你还可以在处理函数、`on_connect` 和 `on_close` 的签名中直接声明带类型注解的查询参数。Robyn 会自动解析并进行类型转换——与 HTTP 便捷参数访问的用法一致。\n\n    带默认值的参数是可选的。没有默认值的参数是必需的——如果缺少，连接将被拒绝并返回错误消息。\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"便捷查询参数\" tag=\"WebSocket\" label=\"/ws?room=chat&page=5\">\n\n      ```python {{ title: '处理函数' }}\n      @app.websocket(\"/ws\")\n      async def handler(websocket, room: str = \"default\", page: int = 1):\n          try:\n              while True:\n                  msg = await websocket.receive_text()\n                  await websocket.send_text(\n                      f\"room={room} page={page} msg={msg}\"\n                  )\n          except WebSocketDisconnect:\n              pass\n      ```\n\n      ```python {{ title: '回调' }}\n      @handler.on_connect\n      def on_connect(websocket, room: str = \"default\"):\n          return f\"已连接到 {room}\"\n\n      @handler.on_close\n      def on_close(websocket, room: str = \"default\"):\n          return f\"已离开 {room}\"\n      ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n---\n\n## 关闭连接 {{ tag: 'close', label: 'close' }}\n\n<Row>\n  <Col>\n    使用 `websocket.close()` 从服务端关闭 WebSocket 连接。该方法将：\n1. 关闭 WebSocket 连接。\n2. 从 WebSocket 注册表中移除客户端。\n3. 使任何挂起的 `receive_text()` 抛出 `WebSocketDisconnect` 异常。\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"关闭连接\" tag=\"WebSocket\" label=\"/ws\">\n\n      ```python {{ title: '服务端关闭' }}\n      @app.websocket(\"/ws\")\n      async def handler(websocket):\n          while True:\n              msg = await websocket.receive_text()\n              if msg == \"quit\":\n                  await websocket.close()\n                  break\n              await websocket.send_text(f\"收到: {msg}\")\n      ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n---\n\n## 连接和关闭回调 {{ tag: '回调', label: '回调' }}\n\n<Row>\n  <Col>\n    您可以为 WebSocket 处理程序附加可选的 `on_connect` 和 `on_close` 回调。它们是处理函数本身的装饰器。\n\n    - `on_connect` 在新客户端连接时调用。其返回值作为第一条消息发送给客户端。\n    - `on_close` 在连接关闭时调用。其返回值作为最后一条消息发送给客户端。\n\n    两个回调都接收一个 `websocket` 对象，可以访问 `id` 和 `query_params`。两者都是可选的。\n  </Col>\n  <Col sticky>\n    <CodeGroup title=\"回调\" tag=\"WebSocket\" label=\"/chat\">\n\n      ```python {{ title: '回调' }}\n      @app.websocket(\"/chat\")\n      async def chat(websocket):\n          while True:\n              msg = await websocket.receive_text()\n              await websocket.broadcast(msg)\n\n      @chat.on_connect\n      def on_connect(websocket):\n          return f\"欢迎，{websocket.id}！\"\n\n      @chat.on_close\n      def on_close(websocket):\n          return \"再见！\"\n      ```\n    </CodeGroup>\n  </Col>\n</Row>\n\n---\n\n## WebSocket API 参考 {{ tag: 'API', label: 'API' }}\n\n<Row>\n  <Col>\n    传递给处理程序的 `websocket` 对象提供以下方法和属性：\n\n    | 方法 / 属性 | 描述 |\n    |---|---|\n    | `await websocket.receive_text()` | 阻塞直到下一条消息；连接关闭时抛出 `WebSocketDisconnect` |\n    | `await websocket.receive_bytes()` | 阻塞直到下一条二进制消息；连接关闭时抛出 `WebSocketDisconnect` |\n    | `await websocket.receive_json()` | 与 `receive_text()` 相同，但返回 JSON 解码后的数据 |\n    | `await websocket.send_text(data)` | 向当前客户端发送文本 |\n    | `await websocket.send_bytes(data)` | 向当前客户端发送二进制数据 |\n    | `await websocket.send_json(data)` | 向当前客户端发送 JSON |\n    | `await websocket.broadcast(data)` | 向此端点的所有客户端广播 |\n    | `await websocket.close()` | 从服务端关闭连接 |\n    | `websocket.id` | 连接 UUID 字符串 |\n    | `websocket.query_params` | 连接 URL 中的查询参数 |\n  </Col>\n</Row>\n\n---\n\n## 下一步\n\n随着代码库的扩展，蝙蝠侠希望正义联盟的成员能够参与管理应用程序。\n\nRobyn 向他介绍了应用扩展的最佳实践，并展示了如何通过视图和子路由器来提升代码的可读性和可维护性。\n\n- [视图和子路由器](/documentation/zh/api_reference/views)\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/architecture.mdx",
    "content": "export const description = 'Robyn 是一个基于 Tokio 运行时的 Python Web 服务器，结合了 Python 和 Rust 的优势，利用工作者事件循环、高效的请求处理、多核扩展，以及引入了“常量请求”功能，在多线程环境中优化缓存响应。'\n\n\n## Robyn 设计文档\n\n## 服务器模型\n\nRobyn 基于多进程、多线程模型构建，结合了 Python 和 Rust 的优势。它使用一个主进程来管理多个工作进程，每个工作进程可以处理多个线程。这种设计使 Robyn 能够高效利用系统资源并处理大量并发请求。\n\n## 主进程\n\nRobyn 的主进程负责初始化服务器、管理工作进程并处理信号。它创建一个套接字，并将其传递给工作进程，允许它们接受连接。主进程使用 Python 实现，为开发者提供熟悉的接口，同时利用 Rust 在核心操作中的高性能。\n\n\n```python\n216:257:robyn/__init__.py\n    def start(self, host: str = \"127.0.0.1\", port: int = 8080, _check_port: bool = True):\n        \"\"\"\n        启动服务器\n\n        :param host str: 服务器监听的主机地址\n        :param port int: 服务器监听的端口号\n        :param _check_port bool: 是否检查端口是否已被占用\n        \"\"\"\n\n        host = os.getenv(\"ROBYN_HOST\", host)\n        port = int(os.getenv(\"ROBYN_PORT\", port))\n        open_browser = bool(os.getenv(\"ROBYN_BROWSER_OPEN\", self.config.open_browser))\n\n        if _check_port:\n            while self.is_port_in_use(port):\n                logger.error(\"端口 %s 已被占用，请使用其他端口。\", port)\n                try:\n                    port = int(input(\"Enter a different port: \"))\n                except Exception:\n                    logger.error(\"无效的端口号，请输入有效的端口号。\")\n                    continue\n\n        logger.info(\"Robyn 版本：%s\", __version__)\n        logger.info(\"正在启动服务器，地址：http://%s:%s\", host, port)\n\n        mp.allow_connection_pickling()\n\n        run_processes(\n            host,\n            port,\n            self.directories,\n            self.request_headers,\n            self.router.get_routes(),\n            self.middleware_router.get_global_middlewares(),\n            self.middleware_router.get_route_middlewares(),\n            self.web_socket_router.get_routes(),\n            self.event_handlers,\n            self.config.workers,\n            self.config.processes,\n            self.response_headers,\n            open_browser,\n        )\n```\n\n\n## 工作进程\n\nRobyn 使用多个工作进程来处理传入的请求。每个工作进程能够管理多个线程，从而实现高效的并发处理。工作进程的数量可以通过 `--processes` 参数配置，默认值为 1。\n\n\n```python\n66:116:robyn/processpool.py\ndef init_processpool(\n    directories: List[Directory],\n    request_headers: Headers,\n    routes: List[Route],\n    global_middlewares: List[GlobalMiddleware],\n    route_middlewares: List[RouteMiddleware],\n    web_sockets: Dict[str, WebSocket],\n    event_handlers: Dict[Events, FunctionInfo],\n    socket: SocketHeld,\n    workers: int,\n    processes: int,\n    response_headers: Headers,\n) -> List[Process]:\n    process_pool = []\n    if sys.platform.startswith(\"win32\") or processes == 1:\n        spawn_process(\n            directories,\n            request_headers,\n            routes,\n            global_middlewares,\n            route_middlewares,\n            web_sockets,\n            event_handlers,\n            socket,\n            workers,\n            response_headers,\n        )\n\n        return process_pool\n\n    for _ in range(processes):\n        copied_socket = socket.try_clone()\n        process = Process(\n            target=spawn_process,\n            args=(\n                directories,\n                request_headers,\n                routes,\n                global_middlewares,\n                route_middlewares,\n                web_sockets,\n                event_handlers,\n                copied_socket,\n                workers,\n                response_headers,\n            ),\n        )\n        process.start()\n        process_pool.append(process)\n\n    return process_pool\n```\n\n\n## 工作线程\n\n在每个工作进程中，Robyn 使用多个线程并发处理请求。工作线程的数量可以通过 `--workers` 参数配置。默认情况下，Robyn 每个进程使用一个工作线程。\n\n## Rust 集成\n\nRobyn 的一个独特特性是与 Rust 的集成。核心的服务器功能，包括请求处理和路由，都是用 Rust 实现的。这使得 Robyn 在提供 Python 友好的 API 的同时，能够实现高性能。\n\n\n```python\n76:107:src/server.rs\n    pub fn start(\n        &mut self,\n        py: Python,\n        socket: &PyCell<SocketHeld>,\n        workers: usize,\n    ) -> PyResult<()> {\n        pyo3_log::init();\n\n        if STARTED\n            .compare_exchange(false, true, SeqCst, Relaxed)\n            .is_err()\n        {\n            debug!(\"Robyn 已经启动...\");\n            return Ok(());\n        }\n\n        let raw_socket = socket.try_borrow_mut()?.get_socket();\n\n        let router = self.router.clone();\n        let const_router = self.const_router.clone();\n        let middleware_router = self.middleware_router.clone();\n        let web_socket_router = self.websocket_router.clone();\n        let global_request_headers = self.global_request_headers.clone();\n        let global_response_headers = self.global_response_headers.clone();\n        let directories = self.directories.clone();\n\n        let asyncio = py.import(\"asyncio\")?;\n        let event_loop = asyncio.call_method0(\"new_event_loop\")?;\n        asyncio.call_method1(\"set_event_loop\", (event_loop,))?;\n\n        let startup_handler = self.startup_handler.clone();\n        let shutdown_handler = self.shutdown_handler.clone();\n```\n\n\n## 常量请求（Const Requests）\n\nRobyn 引入了一个优化功能，称为“常量请求”。这个功能允许某些路由在 Rust 层进行缓存，从而减少频繁访问的端点的开销，这些端点返回常量数据。\n\n## 选择工作进程和进程配置\n\n通过调整工作进程和进程的数量，Robyn 的性能可以根据系统资源和应用程序的性质进行优化。\n\n1. 进程数量：\n   设置进程数量为 CPU 核心数（N）。这使得 Robyn 能够充分利用多核系统。\n\n2. 工作线程数量： \n   使用公式 `2 * N + 1`，其中 N 是 CPU 核心数。这个配置有助于高效处理 I/O 密集型和 CPU 密集型任务。\n\n例如，在一个 4 核的机器上：\n```\npython app.py --processes=4 --workers=9\n```\n\n请记住，最佳配置可能会因应用程序和服务器资源的不同而有所变化。建议进行负载测试，尝试不同的配置，以找到最适合您用例的平衡。\n\n## 扩展考虑\n\n- Robyn 的多进程模型使其能够有效地跨多个 CPU 核心扩展。\n- Python 和 Rust 的结合既便于开发，又能提供高性能。\n- 常量请求功能可以显著提高返回常量输出的路由的性能。\n- 扩展时，考虑进程和工作线程的数量，以找到适合硬件和应用需求的最佳配置。\n\n## 开发模式\n\nRobyn 提供了一个开发模式，可以通过 `--dev` 参数启用。此模式旨在简化开发过程，包含热重载等功能。请注意，在开发者模式下，多进程和多工作线程配置被禁用，以确保开发过程中的一致性。\n\n\n```python\n92:101:robyn/argument_parser.py\n        if self.dev and (self.processes != 1 or self.workers != 1):\n            raise Exception(\"--processes and --workers shouldn't be used with --dev\")\n\n        if self.dev and args.log_level is None:\n            self.log_level = \"DEBUG\"\n\n        elif args.log_level is None:\n            self.log_level = \"INFO\"\n        else:\n            self.log_level = args.log_level\n```\n\n\n通过理解这些设计原则并根据需要调整配置，开发者可以利用 Robyn 的独特架构构建高性能的 Web 应用程序，充分利用系统资源。\n\n\n## 设计图\n\n<img src=\"https://robyn.tech/architecture/architecture.png\" />\n\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/community-resources.mdx",
    "content": "export const description =\n  '在此页面中，我们将看到一些由我们杰出的成员贡献的社区资源，以帮助开发者快速入门使用 Robyn。'\n\n## 演讲\n\n- [EuroPython 2022](https://www.youtube.com/watch?v=AutugvJNVkY&)\n- [GeoPython 2022](https://www.youtube.com/watch?v=YCpbCQwbkd4)\n- [PyCon US 2022](https://youtu.be/1IiL31tUEVk?t=2101)\n- [PyCon Sweden 2021](https://www.youtube.com/watch?v=DK9teAs72Do)\n\n## 博客\n\n- [Hello, Robyn!](https://www.sanskar.me/posts/hello-robyn)\n\n## 下一步\n\n现在，蝙蝠侠对它充满兴趣\n\n- [托管](/documentation/zh/hosting)\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/example_app/authentication-middlewares.mdx",
    "content": "export const description =\n  '欢迎阅读 Robyn API 文档。本文档旨在提供全面的指南和参考，帮助您快速上手 Robyn，并在使用过程中解决可能遇到的问题。'\n\n## 鉴权与授权中间件\n\n为防止无关人员访问犯罪数据，蝙蝠侠为应用程序添加了基于 JWT（JSON Web Token）的身份验证和授权功能，并创建了用户表和用户注册接口。\n\n```python {{ title: '设置鉴权中间件' }}\nfrom robyn.authentication import AuthenticationHandler, BearerGetter, Identity\n\n\nclass BasicAuthHandler(AuthenticationHandler):\n    def authenticate(self, request: Request):\n        token = self.token_getter.get_token(request)\n\n        try:\n            payload = crud.decode_access_token(token)\n            username = payload[\"sub\"]\n        except Exception:\n            return\n\n        with SessionLocal() as db:\n            user = crud.get_user_by_username(db, username=username)\n\n        return Identity(claims={\"user\": f\"{ user }\"})\n\n\napp.configure_authentication(BasicAuthHandler(token_getter=BearerGetter()))\n\n\n@app.get(\"/users/me\", auth_required=True)\nasync def get_current_user(request):\n    user = request.identity.claims[\"user\"]\n    return user\n\n\n```\n\n得益于 Robyn 框架，哥谭市警察局现在能高效管理犯罪数据并实时追踪犯罪动态。蝙蝠侠利用该框架成功实现能够实际运用的程序，助力哥谭市打击犯罪。\n\n<div className=\"not-prose\">\n  <Button\n    href=\"/documentation/zh/example_app/real_time_notifications\"\n    variant=\"text\"\n    arrow=\"right\"\n    children=\"即时通讯\"\n  />\n</div>\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/example_app/authentication.mdx",
    "content": "export const description =\n  '欢迎阅读 Robyn API 文档。本文档旨在提供全面的指南和参考，帮助您快速上手 Robyn，并在使用过程中解决可能遇到的问题。'\n\n## 身份验证\n\n为防止无关人员访问犯罪数据，蝙蝠侠为应用程序添加了基于 JWT（JSON Web Token）的身份验证和授权功能，并创建了用户表和用户注册接口。\n\n## 用户模型\n\n蝙蝠侠设计了一个用户模型（User Model），用于表示可以访问该应用程序的用户。\n\n```python {{ title: '基本身份验证请求示例' }}\n# models.py\nfrom sqlalchemy import Column, Integer, String, Boolean\n\nclass User(Base):\n    __tablename__ = \"users\"\n\n    id = Column(Integer, primary_key=True, index=True)\n    username = Column(String, unique=True, index=True)\n    hashed_password = Column(String)\n\n\n    def __repr__(self):\n        return \"<User(id={id}, username={username}, hashed_password={hashed_password})>\".format(\n            id=self.id,\n            username=self.username,\n            hashed_password=self.hashed_password,\n        )\n\n\n```\n\n接着，他在 `crud.py` 文件中新增了一个方法，用于创建用户。\n\n```python {{ title: '基本身份验证请求示例' }}\n# crud.py\n# also need to do pip install passlib[bcrypt]\n\nfrom sqlalchemy.orm import Session\nfrom .models import User\n\nfrom passlib.context import CryptContext\n\npwd_context = CryptContext(schemes=[\"bcrypt\"], deprecated=\"auto\")\n\ndef get_password_hash(password: str) -> str:\n    return pwd_context.hash(password)\n\ndef get_user(db: Session, user_id: int):\n    return db.query(User).filter(User.id == user_id).first()\n\ndef create_user(db: Session, user: User):\n    hashed_password = get_password_hash(user.password)\n    db_user = User(username=user.username, hashed_password=hashed_password)\n    db.add(db_user)\n    db.commit()\n    db.refresh(db_user)\n    return db_user\n\n```\n\n## 鉴权工具\n\n同时，他还实现了一些工具函数，用于处理身份验证操作，包括密码加密与校验。\n\n```python {{ title: '基本身份验证请求示例（bearer token）' }}\n# crud.py\n# also need to do pip install passlib[bcrypt]\n# pip install \"python-jose[cryptography]\"\nfrom passlib.context import CryptContext\nfrom jose import JWTError, jwt\n\npwd_context = CryptContext(schemes=[\"bcrypt\"], deprecated=\"auto\")\n\ndef verify_password(plain_password, hashed_password):\n    return pwd_context.verify(plain_password, hashed_password)\n\ndef get_password_hash(password):\n    return pwd_context.hash(password)\n\nALGORITHM = \"HS256\"\nSECRET_KEY = \"your_secret_key\"\n\ndef create_access_token(data: dict, expires_delta: timedelta = None):\n    to_encode = data.copy()\n    if expires_delta:\n        expire = datetime.utcnow() + expires_delta\n    else:\n        expire = datetime.utcnow() + timedelta(minutes=15)\n    to_encode.update({\"exp\": expire})\n    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)\n    return encoded_jwt\n\ndef decode_access_token(token: str):\n    return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])\n\n\ndef authenticate_user(db: Session, username: str, password: str):\n    user = get_user_by_username(db, username)\n    if user is None:\n        return False\n    if not verify_password(password, user.hashed_password):\n        return False\n\n    created_token = create_access_token(data={\"sub\": user.username})\n    return created_token\n\n\n```\n\n## 用户注册接口\n\n最后，蝙蝠侠新增了用户注册与登录接口，完成用户注册和登录验证功能。\n\n```python {{ title: '设置路由' }}\nfrom . import crud\n\n@app.post(\"/users/register\")\nasync def register_user(request):\n    user = request.json()\n    with SessionLocal() as db:\n        created_user = crud.create_user(db, user)\n    return created_user\n\n@app.post(\"/users/login\")\nasync def login_user(request):\n    user = request.json()\n    with SessionLocal() as db:\n        token = crud.authenticate_user(db, **user)\n\n    if token is None:\n        raise HTTPException(status_code=401, detail=\"Invalid credentials\")\n\n\n    return {\"access_token\": token}\n\n```\n\n<div className=\"not-prose\">\n  <Button\n    href=\"/documentation/zh/example_app/authentication-middlewares\"\n    variant=\"text\"\n    arrow=\"right\"\n    children=\"下一步: 身份验证中间件\"\n  />\n</div>\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/example_app/deployment.mdx",
    "content": "export const description =\n  '欢迎阅读 Robyn API 文档。本文档旨在提供全面的指南和参考，帮助您快速上手 Robyn，并在使用过程中解决可能遇到的问题。'\n\n## 部署\n\n在对 Web 应用程序进行全面测试并确认所有功能正常后，蝙蝠侠决定将其部署到生产服务器。他选择了一个强大且可扩展的平台，确保应用程序始终保持高可用性和卓越性能。\n\n```python {{ title: '部署' }}\npython app.py --processes=n --workers=m\n```\n\n随着 Web 应用程序的成功部署，蝙蝠侠获得了一个强大的新工具。Robyn 框架为他提供了创建高效打击犯罪应用所需的灵活性、可扩展性和性能，使他在保护哥谭市的持续战斗中获得了显著的技术优势。\n\n<div className=\"not-prose\">\n  <Button\n    href=\"/documentation/zh/example_app/openapi\"\n    variant=\"text\"\n    arrow=\"right\"\n    children=\"OpenAPI 文档\"\n  />\n</div>\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/example_app/index.mdx",
    "content": "export const description =\n  '欢迎阅读 Robyn API 文档。本文档旨在提供全面的指南和参考，帮助您快速上手 Robyn，并在使用过程中解决可能遇到的问题。'\n\n# 基于 Robyn 创建真正的 Web 应用\n\n蝙蝠侠的任务是开发一个 Web 应用，用于管理哥谭市的犯罪数据。该应用允许哥谭市警察局存储和检索有关犯罪活动、嫌疑人以及其位置的信息。为提高开发效率，蝙蝠侠决定使用 Robyn Web 框架来快速构建该应用。\n\n您可以通过[此链接](https://github.com/sparckles/example_robyn_app)获取该应用的源代码。\n\n## 安装 Robyn\n\n首先，需要安装 Robyn。蝙蝠侠创建了一个虚拟环境，并通过 pip 安装了 Robyn。\n\n```bash\n$ python3 -m venv venv\n$ source venv/bin/activate\n$ pip install robyn\n```\n\n## 创建 Robyn 应用\n\n蝙蝠侠决定使用 Robyn 框架创建应用，在他准备创建 `src/app.py` 文件时，有人提醒他，Robyn 自带了一个命令行工具 (CLI)，可以帮助快速创建应用。于是他运行了以下命令来创建一个新的 Robyn 应用。\n\n```bash\n$ python -m robyn --create\n```\n\n运行命令后，终端显示如下提示：\n\n```bash\n$ python3 -m robyn --create\n? Directory Path: .\n? Need Docker? (Y/N) Y\n? Please select project type (Mongo/Postgres/Sqlalchemy/Prisma):\n❯ No DB\n  Sqlite\n  Postgres\n  MongoDB\n  SqlAlchemy\n  Prisma\n```\n\n接下来，他配置了应用的选项，大多数问题都选择了默认设置。\n\n完成后，Robyn CLI 成功创建了一个应用，应用的目录结构如下：\n\n```bash\n\n├── src\n│   ├── app.py\n├── Dockerfile\n\n```\n\n<div className=\"not-prose\">\n  <Button\n    href=\"/documentation/zh/example_app/modeling_routes\"\n    variant=\"text\"\n    arrow=\"right\"\n    children=\"路由建模\"\n  />\n</div>\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/example_app/modeling_routes.mdx",
    "content": "export const description =\n  '欢迎阅读 Robyn API 文档。本文档旨在提供全面的指南和参考，帮助您快速上手 Robyn，并在使用过程中解决可能遇到的问题。'\n\n## 数据模型和数据库连接\n\n蝙蝠侠设计了一个数据模型，用于表示犯罪数据，包括罪行类型、嫌疑人及其位置等信息。他选择使用 SQLite 数据库来存储数据，并通过 ORM（对象关系映射）库与数据库进行交互。\n\n```python\n# models.py\nfrom sqlalchemy import create_engine, Column, Integer, String, DateTime, Float\nfrom sqlalchemy.orm import declarative_base, sessionmaker\n\nDATABASE_URL = \"sqlite:///./gotham_crime_data.db\"\n\nengine = create_engine(DATABASE_URL)\nSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)\n\n\nBase = declarative_base()\n\nclass Crime(Base):\n    __tablename__ = \"crimes\"\n\n    id = Column(Integer, primary_key=True, index=True)\n    type = Column(String, index=True)\n    description = Column(String)\n    location = Column(String)\n    suspect_name = Column(String)\n    date_time = Column(DateTime)\n    latitude = Column(Float)\n    longitude = Column(Float)\n\n```\n\n## 配置 Robyn 应用\n\n蝙蝠侠成功创建了一个 Robyn 应用，并将其配置为使用数据库会话来访问 SQLite 数据库。\n\n根据数据库模型，蝙蝠侠实现了一些辅助方法来与数据库进行交互。这些方法将用于在应用的 API 接口中执行 CRUD（增删改查）操作。\n\n```python {{ title: 'crud.py' }}\n# crud.py\nfrom sqlalchemy.orm import Session\nfrom .models import  Crime\n\n\ndef get_crime(db: Session, crime_id: int):\n    return db.query(Crime).filter(Crime.id == crime_id).first()\n\ndef get_crimes(db: Session, skip: int = 0, limit: int = 100):\n    return db.query(Crime).offset(skip).limit(limit).all()\n\ndef create_crime(db: Session, crime):\n    db_crime = Crime(**crime)\n    db.add(db_crime)\n    db.commit()\n    db.refresh(db_crime)\n    return db_crime\n\ndef update_crime(db: Session, crime_id: int, crime):\n    db_crime = get_crime(db, crime_id)\n    if db_crime is None:\n        return None\n    for key, value in crime.items():\n        setattr(db_crime, key, value)\n    db.commit()\n    db.refresh(db_crime)\n    return db_crime\n\ndef delete_crime(db: Session, crime_id: int):\n    db_crime = get_crime(db, crime_id)\n    if db_crime is None:\n        return False\n    db.delete(db_crime)\n    db.commit()\n    return True\n\n```\n\n## 犯罪数据 API 接口\n\n蝙蝠侠为管理犯罪数据创建了多个 API 接口。这些接口允许哥谭市警察局添加、更新和查询犯罪记录。\n\n```python {{ title: 'Setting up Routes' }}\n# __main__.py\nfrom robyn import Robyn\nfrom robyn.robyn import Request, Response\nfrom sqlalchemy.orm import Session\n\napp = Robyn(__file__)\n\n@app.post(\"/crimes\")\nasync def add_crime(request):\n    with SessionLocal() as db:\n        crime = request.json()\n        insertion = crud.create_crime(db, crime)\n\n    if insertion is None:\n        raise Exception(\"Crime not added\")\n\n    return {\n        \"description\": \"Crime added successfully\",\n        \"status_code\": 200,\n    }\n\n@app.get(\"/crimes\")\nasync def get_crimes(request):\n    with SessionLocal() as db:\n        skip = request.query_params.get(\"skip\", \"0\")\n        limit = request.query_params.get(\"limit\", \"100\")\n        crimes = crud.get_crimes(db, skip=skip, limit=limit)\n\n    return crimes\n\n@app.get(\"/crimes/:crime_id\", auth_required=True)\nasync def get_crime(request):\n    crime_id = int(request.path_params.get(\"crime_id\"))\n    with SessionLocal() as db:\n        crime = crud.get_crime(db, crime_id=crime_id)\n\n    if crime is None:\n        raise Exception(\"Crime not found\")\n\n    return crime\n\n@app.put(\"/crimes/:crime_id\")\nasync def update_crime(request):\n    crime = request.json()\n    crime_id = int(request.path_params.get(\"crime_id\"))\n    with SessionLocal() as db:\n        updated_crime = crud.update_crime(db, crime_id=crime_id, crime=crime)\n    if updated_crime is None:\n        raise Exception(\"Crime not found\")\n    return updated_crime\n\n@app.delete(\"/crimes/{crime_id}\")\nasync def delete_crime(request):\n    crime_id = int(request.path_params.get(\"crime_id\"))\n    with SessionLocal() as db:\n        success = crud.delete_crime(db, crime_id=crime_id)\n    if not success:\n        raise Exception(\"Crime not found\")\n    return {\"message\": \"Crime deleted successfully\"}\n\n\n```\n\n<div className=\"not-prose\">\n  <Button\n    href=\"/documentation/zh/example_app/authentication\"\n    variant=\"text\"\n    arrow=\"right\"\n    children=\"下一步: 身份验证\"\n  />\n</div>\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/example_app/monitoring_and_logging.mdx",
    "content": "## 监控和日志记录\n\n为了更好地监控应用程序的性能并及时解决潜在问题，蝙蝠侠决定实施全面的监控和日志记录机制。他通过集成第三方库来使用日志记录中间件，从而能够跟踪请求、错误和性能指标。\n\n```python {{ title: '监控和日志记录' }}\nfrom robyn import Logger\n\nlogger = Logger(app)\n\n@app.before_request()\nasync def log_request(request: Request):\n    logger.info(f\"Received request: %s\", request)\n\n@app.after_request()\nasync def log_response(response: Response):\n    logger.info(f\"Sending response: %s\", response)\n```\n\n借助监控和日志记录功能，蝙蝠侠现在能够轻松检测应用程序中的问题，分析其性能表现，确保系统始终保持最佳状态，随时准备协助他打击犯罪。\n\n<div className=\"not-prose\">\n  <Button\n    href=\"/documentation/zh/example_app/deployment\"\n    variant=\"text\"\n    arrow=\"right\"\n    children=\"部署\"\n  />\n</div>\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/example_app/openapi.mdx",
    "content": "export const description =\n  '欢迎阅读 Robyn API 文档。本文档旨在提供全面的指南和参考，帮助您快速上手 Robyn，并在使用过程中解决可能遇到的问题。'\n\n## OpenAPI 文档 (Swagger)\n\n应用程序部署后，蝙蝠侠收到许多用户询问如何使用接口。为了回应这些需求，Robyn 向他展示了如何生成 OpenAPI 规范。\n\n开箱即用，Robyn 为应用程序提供了以下接口：\n\n- `/docs` Swagger UI 可视化界面\n- `/openapi.json` JSON 格式的 OpenAPI 文档\n\n如果您不希望生成 OpenAPI 文档，可以在启动应用程序时传递 `--disable-openapi` 参数来禁用该功能。\n\n若需要使用自定义的 OpenAPI 配置，您可以：\n\n    - 将 `openapi.json` 配置文件放在应用程序的根目录中。\n    - 或者，将文件路径作为参数传递给 `Robyn()` 构造函数中的 `openapi_file_path` 参数（参数的优先级高于文件）。\n\n```bash\npython app.py --disable-openapi\n```\n\n## 如何使用?\n\n- 要使用查询参数类型，您可以通过定义 `def get(r: Request, query_params: GetRequestParams)` 的方式，其中 `GetRequestParams` 是 `QueryParams` 的子类。\n- 路径参数默认为字符串类型（参考：[https://en.wikipedia.org/wiki/Query_string](https://en.wikipedia.org/wiki/Query_string)）\n\n<CodeGroup title=\"基础应用\">\n\n```python\nfrom robyn.robyn import QueryParams\n\nfrom robyn import Robyn, Request\n\napp = Robyn(\n    file_object=__file__,\n    openapi=OpenAPI(\n        info=OpenAPIInfo(\n            title=\"Sample App\",\n            description=\"This is a sample server application.\",\n            termsOfService=\"https://example.com/terms/\",\n            version=\"1.0.0\",\n            contact=Contact(\n                name=\"API Support\",\n                url=\"https://www.example.com/support\",\n                email=\"support@example.com\",\n            ),\n            license=License(\n                name=\"BSD2.0\",\n                url=\"https://opensource.org/license/bsd-2-clause\",\n            ),\n            externalDocs=ExternalDocumentation(description=\"Find more info here\", url=\"https://example.com/\"),\n            components=Components(),\n        ),\n    ),\n)\n\n\n@app.get(\"/\")\nasync def welcome():\n    \"\"\"欢迎\"\"\"\n    return \"hi\"\n\n\nclass GetRequestParams(QueryParams):\n    appointment_id: str\n    year: int\n\n\n@app.get(\"/api/v1/name\", openapi_name=\"Name Route\", openapi_tags=[\"Name\"])\nasync def get(r: Request, query_params: GetRequestParams):\n    \"\"\"根据 ID 获取名称\"\"\"\n    return r.query_params\n\n\n@app.delete(\"/users/:name\", openapi_tags=[\"Name\"])\nasync def delete(r: Request):\n    \"\"\"根据名称删除用户\"\"\"\n    return r.path_params\n\n\nif __name__ == \"__main__\":\n    app.start()\n```\n\n</CodeGroup>\n\n## 如何在子路由下使用?\n\n<CodeGroup title=\"子路由\">\n\n```python\nfrom robyn.robyn import QueryParams\n\nfrom robyn import Request, SubRouter\n\nsubrouter: SubRouter = SubRouter(__name__, prefix=\"/sub\")\n\n\n@subrouter.get(\"/\")\nasync def subrouter_welcome():\n    \"\"\"欢迎来到子路由\"\"\"\n    return \"hiiiiii subrouter\"\n\n\nclass SubRouterGetRequestParams(QueryParams):\n    _id: int\n    value: str\n\n\n@subrouter.get(\"/name\")\nasync def subrouter_get(r: Request, query_params: SubRouterGetRequestParams):\n    \"\"\"根据 ID 获取名称\"\"\"\n    return r.query_params\n\n\n@subrouter.delete(\"/:name\")\nasync def subrouter_delete(r: Request):\n    \"\"\"根据名称删除用户\"\"\"\n    return r.path_params\n\n\napp.include_router(subrouter)\n```\n\n</CodeGroup>\n\n## 其他规范参数\n\nRobyn 支持最新的 OpenAPI 规范（[https://swagger.io/specification/](https://swagger.io/specification/)）中提到的所有参数。下是请求和响应体的示例：\n\n<CodeGroup title=\"请求 & 响应体\">\n\n```python\nfrom robyn.types import JSONResponse, Body\n\nclass Initial(Body):\n    is_present: bool\n    letter: Optional[str]\n\n\nclass FullName(Body):\n    first: str\n    second: str\n    initial: Initial\n\n\nclass CreateItemBody(Body):\n    name: FullName\n    description: str\n    price: float\n    tax: float\n\n\nclass CreateResponse(JSONResponse):\n    success: bool\n    items_changed: int\n\n\n@app.post(\"/\")\ndef create_item(request: Request, body: CreateItemBody) -> CreateResponse:\n    return CreateResponse(success=True, items_changed=2)\n```\n\n</CodeGroup>\n\n随着参考文档的成功部署，蝙蝠侠拥有了一个强大的新工具。Robyn 框架为他提供了创建高效打击犯罪应用所需的灵活性、可扩展性和性能，使他在保护哥谭市的持续战斗中获得了技术优势。\n\n<div className=\"not-prose\">\n  <Button\n    href=\"/documentation/zh/example_app/templates\"\n    variant=\"text\"\n    arrow=\"right\"\n    children=\"模板\"\n  />\n</div>\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/example_app/real_time_notifications.mdx",
    "content": "export const description =\n  '欢迎阅读 Robyn API 文档。本文档旨在提供全面的指南和参考，帮助您快速上手 Robyn，并在使用过程中解决可能遇到的问题。'\n\n## 即时通讯\n\n蝙蝠侠决定使用 WebSockets 为哥谭市警察局的警官们实现实时通知功能，从而使他们能够即时接收关于犯罪活动的更新，并在发生新的犯罪报告时触发警报。\n\n```python {{ title: '设置实时通知' }}\nfrom robyn import WebSocketDisconnect\n\n@app.websocket(\"/notifications\")\nasync def notify_handler(websocket):\n    try:\n        while True:\n            message = await websocket.receive_text()\n            await websocket.send_text(f\"Received: {message}\")\n    except WebSocketDisconnect:\n        pass\n\n@notify_handler.on_connect\ndef notify_connect(websocket):\n    return \"Connected to notifications\"\n\n@notify_handler.on_close\ndef notify_close(websocket):\n    return \"Disconnected from notifications\"\n\n```\n\n## 高级搜索和过滤\n\n为了提高警察在搜寻特定犯罪或嫌疑人时的效率，蝙蝠侠为应用程序增加了高级搜索和过滤选项。他设计了一个新的接口，允许用户根据多个标准（如犯罪类型、日期、地点和案件状态）进行精确搜索。\n\n```python {{ title: '高级搜索和过滤' }}\n@app.get(\"/crimes/search\")\nasync def search_crimes(request):\n    crime_type = request.query_params.get(\"crime_type\")\n    date = request.query_params.get(\"date\")\n    location = request.query_params.get(\"location\")\n    status = request.query_params.get(\"status\")\n\n    crimes = crud.search_crimes(db, crime_type=crime_type, date=date, location=location, status=status)\n    return crimes\n\n```\n\n通过这些新功能，哥谭市警察局能够更高效地利用 Web 应用程序来跟踪犯罪活动，并合理调配资源。蝙蝠侠在 Robyn Web 框架上的努力显著提升了打击犯罪的能力，使哥谭市变得更加安全。\n\n尽管蝙蝠侠在当前任务中取得了显著成效，他仍意识到系统有改进空间。因此，他将继续总结经验，不断完善系统，同时专注于履行黑暗骑士的核心使命——保护哥谭市的安全。\n\n<div className=\"not-prose\">\n  <Button\n    href=\"/documentation/zh/example_app/monitoring_and_logging\"\n    variant=\"text\"\n    arrow=\"right\"\n    children=\"监控和日志\"\n  />\n</div>\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/example_app/subrouters_and_views.mdx",
    "content": "export const description =\n  '欢迎阅读 Robyn API 文档。本文档旨在提供全面的指南和参考，帮助您快速上手 Robyn，并在使用过程中解决可能遇到的问题。'\n\n## 子路由器和视图\n\n在实现了基本的应用程序功能后，蝙蝠侠希望将代码库拆分为多个文件，以提高可维护性和扩展性。\n\n此时，Robyn 向他介绍了子路由器和视图的概念。\n\n### 路由器 (Routers)\n\n路由器用于将路由（URL 路径与请求处理函数的映射）组织到不同模块中，从而使应用更加模块化，便于管理和扩展。\n\n例如，如果要为前端功能创建一个路由器，可以创建一个名为 `frontend.py` 的文件，该文件将包含所有与前端相关的路由。\n\n`frontend.py` 文件中的代码示例如下：\n\n```bash\n├── app.py\n├── frontend.py\n├── Dockerfile\n└── requirements.txt\n```\n\n`frontend.py` 文件中的代码示例如下：\n\n```python {{ title: '创建路由器' }}\n# frontend.py\n\nfrom robyn.templating import JinjaTemplate\nfrom robyn import SubRouter\nimport os\nimport pathlib\n\n\ncurrent_file_path = pathlib.Path(__file__).parent.resolve()\njinja_template = JinjaTemplate(os.path.join(current_file_path, \"templates\"))\n\n\nfrontend = SubRouter(__name__, prefix=\"/frontend\")\n\n@frontend.get(\"/\")\nasync def get_frontend(request):\n    context = {\"framework\": \"Robyn\", \"templating_engine\": \"Jinja2\"}\n    return jinja_template.render_template(\"index.html\", **context)\n```\n\n在 `app.py` 文件中，您可以通过以下方式引入并注册前端路由器：\n\n```python {{ title: '嵌套路由' }}\n# app.py\n\nfrom .frontend import frontend\n\n\napp.include_router(frontend)\n```\n\n### 视图 (Views)\n\n视图用于组织和处理路由的具体功能，通常涉及同步或异步的请求处理。它侧重于定义路由的行为和处理请求与响应。\n\n例如，如果要为前端创建视图，可以创建一个名为 `frontend.py` 的文件。此文件将包含所有与前端相关的路由。\n\n代码示例如下：\n\n<CodeGroup>\n    ```python {{ title: '创建装饰器下的视图' }}\n    from robyn import SyncView\n\n    @app.view(\"/sync/view/decorator\")\n    def sync_decorator_view():\n        def get():\n            return \"Hello, world!\"\n\n        def post(request: Request):\n            body = request.body\n            return body\n    ```\n\n\n    ```python {{ title: '创建视图' }}\n    def sync_decorator_view():\n        def get():\n            return \"Hello, world!\"\n\n        def post(request: Request):\n            body = request.body\n            return body\n\n    app.add_view(\"/sync/view/decorator\", sync_decorator_view)\n    ```\n\n</CodeGroup>\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/example_app/templates.mdx",
    "content": "export const description =\n  '欢迎阅读 Robyn API 文档。本文档旨在提供全面的指南和参考，帮助您快速上手 Robyn，并在使用过程中解决可能遇到的问题。'\n\n## 模板\n\n在完成后端开发后，蝙蝠侠决定为他的应用程序添加前端。他希望创建一个简单的网页来展示他收集的数据，同时也希望能够将新数据添加到数据库，并编辑现有的数据。\n\n这时，他接触到了模板！\n\n模板是 Robyn 框架中的一项强大功能，允许开发者使用 HTML 和 Python 创建动态网页。通过模板，您可以方便地为应用程序添加前端，而无需学习新的语言或框架。\n\nRobyn 默认支持 Jinja2 模板引擎，并且提供了简单的方式来集成其他模板引擎。\n\n### 创建模板\n\n要创建模板，您需要在项目目录中创建一个扩展名为 `.html` 的文件。通常情况下，这些模板文件会存放在名为 `templates` 的目录下。例如，如果您想创建一个名为 `index.html` 的模板，您可以在 `templates` 目录中创建该文件。\n\n因此，您的文件夹结构可能如下所示：\n\n```bash\n├── app.py\n├── templates\n│   └── index.html\n├── Dockerfile\n└── requirements.txt\n\n```\n\n### 模板渲染\n\n创建好模板后，您可以使用 `render_template` 函数来渲染它。该函数的第一个参数是模板的名称，第二个参数是一个字典，其中包含要传递给模板的变量。\n\n例如，要渲染 `index.html` 模板，可以使用如下代码：\n\n```python {{ title: '模板渲染' }}\n\nimport os\nimport pathlib\nfrom robyn.templating import JinjaTemplate\n\n\ncurrent_file_path = pathlib.Path(__file__).parent.resolve()\njinja_template = JinjaTemplate(os.path.join(current_file_path, \"templates\"))\n\n@app.get(\"/frontend\")\nasync def get_frontend(request):\n    context = {\"framework\": \"Robyn\", \"templating_engine\": \"Jinja2\"}\n    return jinja_template.render_template(\"index.html\", **context)\n\napp.include_router(frontend)\n```\n\n现在，蝙蝠侠的应用程序已经成功完成，前端页面也正常显示。然而，蝙蝠侠对当前的代码结构并不满意。他认为所有的代码都集中在一个文件中，难以维护。于是，他询问 Robyn 是否有办法将代码拆分成多个模块。\n\n这时，Robyn 向蝙蝠侠介绍了路由器和视图的概念。\n\n<div className=\"not-prose\">\n  <Button\n    href=\"/documentation/zh/example_app/subrouters_and_views\"\n    variant=\"text\"\n    arrow=\"right\"\n    children=\"子路由器和视图\"\n  />\n</div>\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/framework_performance_comparison.mdx",
    "content": "export const description = '在此页面中，我们将了解不同框架之间的性能对比。'\n\n# 不同框架之间的性能对比\n\n## 请先阅读此内容\n\n在深入之前，需要强调的是，本次性能对比的目的并非贬低任何开发者或下列所提及的框架。列出框架名称只是为了使对比更清晰。我对 Python-Web 生态系统的偏好很大程度上受到了这些框架的影响，在这里列出它们并不是为了冒犯任何人。\n\n此外，这些测试是在我个人开发设备上进行的，因此，以下展示的数据并非绝对值。这些数据仅用于反映在特定测试条件下这些框架的相对性能。\n\n使用 [oha](https://github.com/hatoo/oha) 工具对以下框架进行 10,000 个请求的测试，并得出了以下结果：\n\n1. Flask(Gunicorn)\n\n```bash\nTotal:        5.5254 secs\nSlowest:      0.0784 secs\nFastest:      0.0028 secs\nAverage:      0.0275 secs\nRequests/sec: 1809.8082\n```\n\n1. FastAPI(Uvicorn)\n\n```bash\nTotal:        4.1314 secs\nSlowest:      0.0733 secs\nFastest:      0.0027 secs\nAverage:      0.0206 secs\nRequests/sec: 2420.4851\n```\n\n1. Django(Gunicorn)\n\n```bash\nTotal:        13.5070 secs\nSlowest:      0.3635 secs\nFastest:      0.0249 secs\nAverage:      0.0674 secs\nRequests/sec: 740.3558\n```\n\n1. Robyn(Doesn't need a \\*SGI)\n\n```bash\nTotal:\t1.8324 secs\nSlowest:\t0.0269 secs\nFastest:\t0.0024 secs\nAverage:\t0.0091 secs\nRequests/sec:\t5457.2339\n```\n\n1. Robyn (5 workers)\n\n```bash\nTotal:\t1.5592 secs\nSlowest:\t0.0211 secs\nFastest:\t0.0017 secs\nAverage:\t0.0078 secs\nRequests/sec:\t6413.6480\n```\n\nRobyn 需要 1.8 秒处理 10,000 个请求，其次是 Flask 和 FastAPI，它们大约需要 5 秒（在配备双核处理器的机器上使用 5 个工作进程）。相比之下，Django 大约需要 13.5070 秒。\n\n## 详细日志\n\nFlask(Gunicorn)\n\n```bash\nSummary:\n  Success rate: 1.0000\n  Total:        5.5254 secs\n  Slowest:      0.0784 secs\n  Fastest:      0.0028 secs\n  Average:      0.0275 secs\n  Requests/sec: 1809.8082\n\n  Total data:   126.95 KiB\n  Size/request: 13 B\n  Size/sec:     22.98 KiB\n\nResponse time histogram:\n  0.007 [55]   |\n  0.014 [641]  |■■■■■\n  0.021 [2413] |■■■■■■■■■■■■■■■■■■■■\n  0.027 [3771] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n  0.034 [1999] |■■■■■■■■■■■■■■■■\n  0.041 [737]  |■■■■■■\n  0.048 [236]  |■■\n  0.055 [75]   |\n  0.062 [46]   |\n  0.069 [24]   |\n  0.076 [3]    |\n\nLatency distribution:\n  10% in 0.0178 secs\n  25% in 0.0223 secs\n  50% in 0.0266 secs\n  75% in 0.0317 secs\n  90% in 0.0378 secs\n  95% in 0.0419 secs\n  99% in 0.0551 secs\n\nDetails (average, fastest, slowest):\n  DNS+dialup:   0.0071 secs, 0.0001 secs, 0.0443 secs\n  DNS-lookup:   0.0000 secs, 0.0000 secs, 0.0010 secs\n\nStatus code distribution:\n  [200] 10000 responses\n```\n\nFastAPI(Uvicorn)\n\n```bash\nSummary:\n  Success rate: 1.0000\n  Total:        4.1314 secs\n  Slowest:      0.0733 secs\n  Fastest:      0.0027 secs\n  Average:      0.0206 secs\n  Requests/sec: 2420.4851\n\n  Total data:   166.02 KiB\n  Size/request: 17 B\n  Size/sec:     40.18 KiB\n\nResponse time histogram:\n  0.005 [175]  |■\n  0.011 [1541] |■■■■■■■■■■■■■■■■\n  0.016 [2942] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n  0.021 [2770] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n  0.027 [1479] |■■■■■■■■■■■■■■■■\n  0.032 [608]  |■■■■■■\n  0.038 [217]  |■■\n  0.043 [103]  |■\n  0.048 [53]   |\n  0.054 [54]   |\n  0.059 [58]   |\n\nLatency distribution:\n  10% in 0.0120 secs\n  25% in 0.0151 secs\n  50% in 0.0194 secs\n  75% in 0.0243 secs\n  90% in 0.0300 secs\n  95% in 0.0348 secs\n  99% in 0.0522 secs\n\nDetails (average, fastest, slowest):\n  DNS+dialup:   0.0088 secs, 0.0073 secs, 0.0103 secs\n  DNS-lookup:   0.0001 secs, 0.0000 secs, 0.0008 secs\n\nStatus code distribution:\n  [200] 10000 responses\n```\n\nRobyn\n\n```bash\nSummary:\n  Success rate:\t1.0000\n  Total:\t1.8324 secs\n  Slowest:\t0.0269 secs\n  Fastest:\t0.0024 secs\n  Average:\t0.0091 secs\n  Requests/sec:\t5457.2339\n\n  Total data:\t117.19 KiB\n  Size/request:\t12 B\n  Size/sec:\t63.95 KiB\n\nResponse time histogram:\n  0.002 [183]  |■\n  0.004 [1669] |■■■■■■■■■■■■■■\n  0.007 [3724] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n  0.009 [2631] |■■■■■■■■■■■■■■■■■■■■■■\n  0.011 [1060] |■■■■■■■■■\n  0.013 [496]  |■■■■\n  0.016 [188]  |■\n  0.018 [34]   |\n  0.020 [12]   |\n  0.022 [2]    |\n  0.025 [1]    |\n\nLatency distribution:\n  10% in 0.0061 secs\n  25% in 0.0073 secs\n  50% in 0.0087 secs\n  75% in 0.0105 secs\n  90% in 0.0129 secs\n  95% in 0.0143 secs\n  99% in 0.0171 secs\n\nDetails (average, fastest, slowest):\n  DNS+dialup:\t0.0049 secs, 0.0035 secs, 0.0065 secs\n  DNS-lookup:\t0.0001 secs, 0.0000 secs, 0.0010 secs\n\nStatus code distribution:\n  [200] 10000 responses\n```\n\nDjango(Gunicorn)\n\n```bash\nSummary:\n  Success rate: 1.0000\n  Total:        13.5070 secs\n  Slowest:      0.3635 secs\n  Fastest:      0.0249 secs\n  Average:      0.0674 secs\n  Requests/sec: 740.3558\n\n  Total data:   102.01 MiB\n  Size/request: 10.45 KiB\n  Size/sec:     7.55 MiB\n\nResponse time histogram:\n  0.016 [283]  |■\n  0.032 [2616] |■■■■■■■■■■■■■■■■■■\n  0.048 [4587] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n  0.064 [1829] |■■■■■■■■■■■■\n  0.081 [362]  |■■\n  0.097 [98]   |\n  0.113 [105]  |\n  0.129 [20]   |\n  0.145 [7]    |\n  0.161 [28]   |\n  0.177 [65]   |\n\nLatency distribution:\n  10% in 0.0493 secs\n  25% in 0.0559 secs\n  50% in 0.0638 secs\n  75% in 0.0733 secs\n  90% in 0.0840 secs\n  95% in 0.0948 secs\n  99% in 0.1543 secs\n\nDetails (average, fastest, slowest):\n  DNS+dialup:   0.0097 secs, 0.0001 secs, 0.0444 secs\n  DNS-lookup:   0.0000 secs, 0.0000 secs, 0.0007 secs\n\nStatus code distribution:\n  [200] 10000 responses\n```\n\nRobyn(with 5 workers)\n\n```bash\nSummary:\n  Success rate:\t1.0000\n  Total:\t1.5592 secs\n  Slowest:\t0.0211 secs\n  Fastest:\t0.0017 secs\n  Average:\t0.0078 secs\n  Requests/sec:\t6413.6480\n\n  Total data:\t126.95 KiB\n  Size/request:\t13 B\n  Size/sec:\t81.42 KiB\n\nResponse time histogram:\n  0.002 [30]   |\n  0.004 [599]  |■■■■■\n  0.005 [3336] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n  0.007 [3309] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■\n  0.009 [1614] |■■■■■■■■■■■■■■■\n  0.011 [749]  |■■■■■■■\n  0.012 [253]  |■■\n  0.014 [94]   |\n  0.016 [14]   |\n  0.018 [1]    |\n  0.019 [1]    |\n\nLatency distribution:\n  10% in 0.0055 secs\n  25% in 0.0063 secs\n  50% in 0.0074 secs\n  75% in 0.0089 secs\n  90% in 0.0107 secs\n  95% in 0.0117 secs\n  99% in 0.0142 secs\n\nDetails (average, fastest, slowest):\n  DNS+dialup:\t0.0022 secs, 0.0013 secs, 0.0028 secs\n  DNS-lookup:\t0.0000 secs, 0.0000 secs, 0.0001 secs\n\nStatus code distribution:\n  [200] 10000 responses\n```\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/hosting.mdx",
    "content": "export const description =\n  '在此页面中，我们将了解如何在不同的云提供商上部署 Robyn。'\n\n在各种云提供商上托管 Robyn 应用程序的服务。\n\n## Railway\n\n这里，我们将在 `Railway` 上部署应用程序。\n\n但是，在部署之前，我们需要一个 GitHub 账户作为必备条件。\n\n我们将部署一个示例的 \"Hello World\"，演示一个简单的 GET 路由并提供一个 HTML 文件。\n\nDirectory structure:\n\n  <CodeGroup>\n\n```bash\napp folder/\n  main.py\n  requirements.txt\n  index.html\n```\n\n  </CodeGroup>\n\n注意：Railway 会寻找 `main.py` 作为入口点，而不是 `app.py`。如果没有 `main.py` 文件，构建过程将失败。\n\n  <CodeGroup title=\"main.py\">\n\n```python {{ title: 'python' }}\nfrom robyn import Robyn, serve_html\n\n\napp = Robyn(__file__)\n\n\n@app.get(\"/hello\")\nasync def h(request):\n    print(request)\n    return \"Hello, world!\"\n\n\n@app.get(\"/\")\nasync def get_page(request):\n    return serve_html(\"./index.html\")\n\n\nif __name__ == \"__main__\":\n    app.start(url=\"0.0.0.0\", port=PORT)\n```\n\n  </CodeGroup>\n\n  <CodeGroup title=\"index.html\">\n\n    ```python {{ title: 'python' }}\n    <h1> Hello World, this is Robyn framework! <h1>\n    ```\n\n  </CodeGroup>\n\n## 开放端口\n\nRailway 的文档中讲解了如何监听端口：\n\n> 启动应用程序的最简单方法是让应用程序监听 `0.0.0.0:$PORT`，其中 `PORT` 是 Railway 提供的环境变量。\n\n因此，必须将主机设置为 `0.0.0.0`，并将其作为参数传递给 `app.start()`。\n\n我们需要创建一个 Railway 账户来在 Railway 上部署该应用。可以通过访问 `Railway 首页` 来完成。\n\n点击 \"登录\" 按钮并选择 \"使用 GitHub 账户登录\"。\n\n<img src=\"https://user-images.githubusercontent.com/70811425/202867604-10a09f87-ecb9-4a42-ae90-1359223049bc.png\" />\n\n接下来，点击 \"新项目\" 按钮，并选择 \"从 GitHub 仓库部署\"。\n\n<img src=\"https://user-images.githubusercontent.com/70811425/202870632-4d3f46dc-1aa9-4603-9b0f-344ed87ec9d0.png\" />\n\n然后选择我们想要部署的仓库，并点击 \"立即部署\"。\n\n<img src=\"https://user-images.githubusercontent.com/70811425/202870837-16884fef-8900-4ab3-9794-0fb53c3ffd2e.png\" />\n<img src=\"https://user-images.githubusercontent.com/70811425/202871003-f79a1cef-9a5f-4166-be4f-527c60ec6c79.png\" />\n\n现在，点击我们项目。\n\n选择 \"变量\" 并点击 \"新变量\" 按钮设置环境变量。\n\n<img src=\"https://user-images.githubusercontent.com/70811425/202870681-5c069475-a5d1-4069-8582-c5b549d27aad.png\" />\n\n接着，进入 \"设置\" 标签页，点击 \"生成域名\"。\n\n我们可以在 \"域名\" 标签页下生成一个临时域名。\n\n<img src=\"https://user-images.githubusercontent.com/70811425/202870735-6b955752-c5a6-48d5-acbc-1a4ea6fd7574.png\" />\n\n我们可以访问 `<domain>/hello` 来确认 \"Hello World\" 消息是否正确显示。\n\n## 下一步\n\n- [未来发展路线图](/documentation/zh/api_reference/future-roadmap)\n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/index.mdx",
    "content": "import { Guides } from '@/components/documentation/Guides'\nimport { ApiDocs } from '@/components/documentation/ApiDocs'\nimport { HeroPattern } from '@/components/documentation/HeroPattern'\n\nexport const description =\n  '欢迎来到 Robyn API 文档。您将找到全面的指南和文档，帮助您尽快开始使用 Robyn，并在遇到困难时获得支持。'\n\nexport const sections = [\n  { title: '应用示例', id: 'guides' },\n  { title: 'API 参考', id: 'api_docs' },\n]\n\n<HeroPattern />\n\n<div className=\"text-white\">\n# API文档\n欢迎来到 Robyn API 文档。您将找到全面的指南和文档，帮助您尽快开始使用 Robyn，并在遇到困难时获得支持。\n\n<div className=\"not-prose mb-16 mt-6 flex gap-3\">\n  <Button href=\"/documentation/zh/example_app\" arrow=\"right\" children=\"应用示例\" />\n  <Button href=\"/documentation/zh/api_reference\" variant=\"outline\" children=\"API 文档\" />\n</div>\n\n## 入门指南 {{ anchor: false }}\n\n示例应用是一个简单的 Web 应用程序，展示了如何使用 Robyn API。如果您是 Robyn 的新手，这是一个很好的起点。\n\nAPI 参考包含了有关 Robyn API 的详细信息。如果您已经熟悉 Robyn 并想了解更多关于 API 的信息，这是一个很好的起点。\n\n</div>\n<Guides />\n\n<ApiDocs /> \n"
  },
  {
    "path": "docs_src/src/pages/documentation/zh/plugins.mdx",
    "content": "## 插件\n\nRobyn 是一个灵活且可扩展的 Web 框架，允许用户在其基础上拓展插件。\nRobyn 的插件可以增强和自定义框架的功能，以满足特定需求。以下是一些值得关注的插件，它们可以大大优化 Robyn 的项目：\n\n### 限流插件\n\n- 描述：此插件使您能够为 Robyn 应用程序的路由实现限流功能。它有助于防止滥用、暴力攻击，并确保资源的公平使用。\n- GitHub 仓库: [robyn-rate-limits](https://github.com/IdoKendo/robyn_rate_limits)\n- 安装:\n  `python -m pip install robyn-rate-limits`\n- 使用示例:\n\n```py\nfrom robyn import Robyn, Request\nfrom robyn_rate_limits import InMemoryStore\nfrom robyn_rate_limits import RateLimiter\n\napp = Robyn(__file__)\nlimiter = RateLimiter(store=InMemoryStore, calls_limit=3, limit_ttl=100)\n\n@app.before_request()\ndef middleware(request: Request):\n    return limiter.handle_request(app, request)\n\n@app.get(\"/\")\ndef h():\n    return \"Hello, World!\"\n\napp.start(port=8080)\n```\n\n在这个示例中，robyn-rate-limits 用于为特定路由设置每 100 秒最多允许 3 次请求的限流规则。如果客户端超过此限制，他们将收到 \"请求过多\" 的提示消息。\n\n该插件与 Robyn Web 框架无缝集成，通过防止单个客户端发起过多请求，增强了应用程序的安全性和稳定性。\n\n## 下一步\n\n在探索完插件后，蝙蝠侠希望了解社区的更多内容。所以，Robyn 带他去了一个新地方：\n\n- [未来发展路线图](/documentation/zh/api_reference/future-roadmap)\n"
  },
  {
    "path": "docs_src/src/pages/index.jsx",
    "content": "import Head from 'next/head'\nimport Link from 'next/link'\n\nimport { Container } from '@/components/Container'\nimport hljs from 'highlight.js/lib/core'\nimport python from 'highlight.js/lib/languages/python'\nimport 'highlight.js/styles/tomorrow-night-blue.css'\nimport Testimonials from '@/components/Testimonials'\n\nhljs.registerLanguage('python', python)\n\nimport { useEffect, useRef } from 'react'\n\nfunction CodeSnippet() {\n  const ref = useRef(null)\n  useEffect(() => {\n    hljs.highlightBlock(ref.current)\n  }, [])\n  const pythonCode = `from robyn import Robyn\n\napp = Robyn(__file__)\n\n@app.get(\"/\")\nasync def h(request):\n    return \"Hello, world!\"\n\napp.start(port=8080)`\n  return (\n    <div className=\"mt-6 rounded-b-xl rounded-t-xl bg-gray-900\">\n      <div className=\"flex rounded-t-xl bg-gray-800/40 ring-1 ring-white/5\">\n        <div className=\"-mb-px flex rounded-t-xl text-sm font-medium leading-6 text-gray-400\">\n          <div className=\"rounded-t-xl-b border-r border-b-white/20 border-r-white/10 bg-white/5 px-4 py-2 text-white\">\n            app.py\n          </div>\n        </div>\n      </div>\n      <div className=\"px-6 pb-14 pt-6\">\n        <pre className=\" rounded  !bg-gray-900 p-4\">\n          <code ref={ref} className=\"language-python !bg-gray-900\">\n            {pythonCode}\n          </code>\n        </pre>\n      </div>\n    </div>\n  )\n}\n\nfunction Example() {\n  return (\n    <div className=\"relative isolate h-full w-full rounded-t-xl  bg-gradient-to-b from-indigo-100/20\">\n      <CodeSnippet />\n    </div>\n  )\n}\n\nexport default function Home({ articles }) {\n  return (\n    <>\n      <Head>\n        <title>\n          Robyn - A Fast, Innovator Friendly, and Community Driven Python Web\n          Framework.\n        </title>\n        <meta\n          name=\"description\"\n          content=\"Robyn - A Fast, Innovator Friendly, and Community Driven Python Web Framework.\"\n        />\n        <meta\n          property=\"og:title\"\n          content=\"Robyn - A Fast, Innovator Friendly, and Community Driven Python Web Framework.\"\n        />\n        <meta property=\"og:image\" content=\"https://robyn.tech/robynog.png\" />\n        <meta\n          property=\"og:description\"\n          content=\"Robyn is a fast, innovator-friendly, and community-driven Python web framework.\"\n        />\n        <meta property=\"og:url\" content=\"https://robyn.tech\" />\n\n        {/*Twitter specific meta tags*/}\n        <meta name=\"twitter:card\" content=\"summary_large_image\" />\n        <meta name=\"twitter:site\" content=\"@yourTwitterHandle\" />\n        <meta\n          name=\"twitter:title\"\n          content=\"Robyn - A Fast, Innovator Friendly, and Community Driven Python Web Framework.\"\n        />\n        <meta\n          name=\"twitter:description\"\n          content=\"Robyn is a fast, innovator-friendly, and community-driven Python web framework.\"\n        />\n        <meta name=\"twitter:image\" content=\"https://robyn.tech/robynog.png\" />\n\n        {/*LinkedIn specific meta tags*/}\n        <meta property=\"og:type\" content=\"website\" />\n        <meta property=\"og:site_name\" content=\"Robyn Framework\" />\n      </Head>\n\n      <Container className=\"mt-10 md:mt-14\">\n        <div className=\"relative isolate pt-4\">\n          <div\n            className=\"absolute inset-x-0 -top-40 -z-10 transform-gpu overflow-hidden blur-3xl sm:-top-80\"\n            aria-hidden=\"true\"\n          >\n            <div\n              className=\"relative left-[calc(50%-11rem)] aspect-[1155/678] w-[36.125rem] -translate-x-1/2 rotate-[30deg] bg-gradient-to-tr from-orange-800 to-yellow-500 opacity-40 sm:left-[calc(50%-30rem)] sm:w-[72.1875rem]\"\n              style={{\n                clipPath:\n                  'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)',\n              }}\n            />\n          </div>\n          <div className=\"py-24 sm:py-32 lg:pb-40\">\n            <div className=\"mx-auto max-w-7xl px-6 lg:px-8\">\n              <div className=\"mx-auto max-w-2xl text-center sm:max-w-4xl\">\n                <h1 className=\"text-4xl font-bold tracking-tight text-white sm:text-6xl\">\n                  Meet<span className=\"text-yellow-400\"> Robyn</span>\n                </h1>\n                <h2 className=\"text-2xl font-bold tracking-tight text-white sm:text-4xl\">\n                  A Fast, Innovator Friendly, and Community Driven Python Web\n                  Framework\n                </h2>\n                <p className=\"mt-6 text-lg leading-8 text-gray-300\">\n                  Robyn merges Python's async capabilities with a Rust runtime\n                  for reliable, scalable web solutions. Experience quick project\n                  scaffolding, enjoyable usage, and robust plugin support.\n                </p>\n                <div className=\"mt-10 flex items-center justify-center gap-x-6\">\n                  <a\n                    href=\"/documentation/en\"\n                    className=\"rounded-md bg-yellow-500 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-yellow-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-400\"\n                  >\n                    Get started\n                  </a>\n                  <a\n                    href=\"https://github.com/sparckles/robyn\"\n                    className=\"text-sm font-semibold leading-6 text-white\"\n                  >\n                    Learn more\n                  </a>\n                </div>\n              </div>\n              <Example />\n            </div>\n          </div>\n          <div\n            className=\"absolute inset-x-0 top-[calc(100%-13rem)] -z-10 transform-gpu overflow-hidden blur-3xl sm:top-[calc(100%-30rem)]\"\n            aria-hidden=\"true\"\n          >\n            <div\n              className=\"relative left-[calc(50%+3rem)] aspect-[1155/678] w-[36.125rem] -translate-x-1/2 bg-gradient-to-tr from-orange-800 to-yellow-500 opacity-60 sm:left-[calc(50%+36rem)] sm:w-[72.1875rem]\"\n              style={{\n                clipPath:\n                  'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)',\n              }}\n            />\n          </div>\n        </div>\n      </Container>\n      <Testimonials />\n      <Container className=\"mt-9\">\n        <div className=\"mx-auto max-w-7xl px-6 lg:px-8\">\n          <div className=\"mx-auto max-w-xl text-center\">\n            <h2 className=\"text-base font-semibold leading-7 text-yellow-400\">\n              Our Philosophy\n            </h2>\n            <p className=\"mt-2 text-3xl font-bold tracking-tight text-white sm:text-4xl\">\n              Fast. Innovator Friendly. Robust. Community Driven.\n            </p>\n          </div>\n          <dl className=\"mx-auto mt-16 grid max-w-2xl grid-cols-1 gap-8 text-base leading-7 text-gray-300 sm:grid-cols-2 lg:mx-0 lg:max-w-none lg:gap-x-16\">\n            <div className=\"relative pl-9\">\n              <dt className=\"inline font-semibold text-white\">\n                <svg\n                  className=\"absolute left-1 top-1 h-5 w-5 text-yellow-500\"\n                  viewBox=\"0 0 20 20\"\n                  fill=\"currentColor\"\n                  aria-hidden=\"true\"\n                >\n                  <path\n                    fillRule=\"evenodd\"\n                    d=\"M5.5 17a4.5 4.5 0 01-1.44-8.765 4.5 4.5 0 018.302-3.046 3.5 3.5 0 014.504 4.272A4 4 0 0115 17H5.5zm3.75-2.75a.75.75 0 001.5 0V9.66l1.95 2.1a.75.75 0 101.1-1.02l-3.25-3.5a.75.75 0 00-1.1 0l-3.25 3.5a.75.75 0 101.1 1.02l1.95-2.1v4.59z\"\n                    clipRule=\"evenodd\"\n                  />\n                </svg>\n                Fast.{' '}\n              </dt>\n              <dd className=\"inline\">\n                Robyn, written in Rust, embodies speed and reliability. Our\n                multithreaded runtime creates a highly efficient and secure\n                environment optimized for peak performance.\n              </dd>\n            </div>\n            <div className=\"relative pl-9\">\n              <dt className=\"inline font-semibold text-white\">\n                <svg\n                  className=\"absolute left-1 top-1 h-5 w-5 text-yellow-500\"\n                  viewBox=\"0 0 20 20\"\n                  fill=\"currentColor\"\n                  aria-hidden=\"true\"\n                >\n                  <path\n                    fillRule=\"evenodd\"\n                    d=\"M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0V5.36l-.31-.31A7 7 0 003.239 8.188a.75.75 0 101.448.389A5.5 5.5 0 0113.89 6.11l.311.31h-2.432a.75.75 0 000 1.5h4.243a.75.75 0 00.53-.219z\"\n                    clipRule=\"evenodd\"\n                  />\n                </svg>\n                Simple API.\n              </dt>\n              <dd className=\"inline\">\n                With Robyn, complexity takes a backseat. Our API is simple yet\n                potent, built to streamline your development process and\n                minimize your workload.\n              </dd>\n            </div>\n            <div className=\"relative pl-9\">\n              <dt className=\"inline font-semibold text-white\">\n                <svg\n                  className=\"absolute left-1 top-1 h-5 w-5 text-yellow-500\"\n                  viewBox=\"0 0 20 20\"\n                  fill=\"currentColor\"\n                  aria-hidden=\"true\"\n                >\n                  <path\n                    fillRule=\"evenodd\"\n                    d=\"M10 2.5c-1.31 0-2.526.386-3.546 1.051a.75.75 0 01-.82-1.256A8 8 0 0118 9a22.47 22.47 0 01-1.228 7.351.75.75 0 11-1.417-.49A20.97 20.97 0 0016.5 9 6.5 6.5 0 0010 2.5zM4.333 4.416a.75.75 0 01.218 1.038A6.466 6.466 0 003.5 9a7.966 7.966 0 01-1.293 4.362.75.75 0 01-1.257-.819A6.466 6.466 0 002 9c0-1.61.476-3.11 1.295-4.365a.75.75 0 011.038-.219zM10 6.12a3 3 0 00-3.001 3.041 11.455 11.455 0 01-2.697 7.24.75.75 0 01-1.148-.965A9.957 9.957 0 005.5 9c0-.028.002-.055.004-.082a4.5 4.5 0 018.996.084V9.15l-.005.297a.75.75 0 11-1.5-.034c.003-.11.004-.219.005-.328a3 3 0 00-3-2.965zm0 2.13a.75.75 0 01.75.75c0 3.51-1.187 6.745-3.181 9.323a.75.75 0 11-1.186-.918A13.687 13.687 0 009.25 9a.75.75 0 01.75-.75zm3.529 3.698a.75.75 0 01.584.885 18.883 18.883 0 01-2.257 5.84.75.75 0 11-1.29-.764 17.386 17.386 0 002.078-5.377.75.75 0 01.885-.584z\"\n                    clipRule=\"evenodd\"\n                  />\n                </svg>\n                Hacker Friendly.\n              </dt>\n              <dd className=\"inline\">\n                While many focus on large-scale challenges that many of us might\n                never encounter, Robyn prioritizes the broader issues most\n                developers face. We foster innovation for all, ensuring both\n                common and complex challenges are met with expertise. At Robyn,\n                every developer feels at home.\n              </dd>\n            </div>\n\n            <div className=\"relative pl-9\">\n              <dt className=\"inline font-semibold text-white\">\n                <svg\n                  className=\"absolute left-1 top-1 h-5 w-5 text-yellow-500\"\n                  viewBox=\"0 0 20 20\"\n                  fill=\"currentColor\"\n                  aria-hidden=\"true\"\n                >\n                  <path d=\"M4.632 3.533A2 2 0 016.577 2h6.846a2 2 0 011.945 1.533l1.976 8.234A3.489 3.489 0 0016 11.5H4c-.476 0-.93.095-1.344.267l1.976-8.234z\" />\n                  <path\n                    fillRule=\"evenodd\"\n                    d=\"M4 13a2 2 0 100 4h12a2 2 0 100-4H4zm11.24 2a.75.75 0 01.75-.75H16a.75.75 0 01.75.75v.01a.75.75 0 01-.75.75h-.01a.75.75 0 01-.75-.75V15zm-2.25-.75a.75.75 0 00-.75.75v.01c0 .414.336.75.75.75H13a.75.75 0 00.75-.75V15a.75.75 0 00-.75-.75h-.01z\"\n                    clipRule=\"evenodd\"\n                  />\n                </svg>\n                Community First and Truly FOSS.\n              </dt>\n              <dd className=\"inline\">\n                Rooted in a community-first ethos, Robyn is built with\n                collective effort and is a true representation of free and\n                open-source software.\n              </dd>\n            </div>\n          </dl>\n        </div>\n      </Container>\n      <Container>\n        <div className=\"py-24 sm:py-32\">\n          <div className=\"mx-auto max-w-7xl px-6 lg:px-8\">\n            <div className=\"mx-auto max-w-2xl lg:max-w-none\">\n              <div className=\"text-center\">\n                <h2 className=\"text-3xl font-bold tracking-tight text-white sm:text-4xl\">\n                  Some of our stats\n                </h2>\n                <p className=\"mt-4 text-lg leading-8 text-gray-300\">\n                  Robyn is going strong!\n                </p>\n              </div>\n              <dl className=\"mt-16 grid grid-cols-1  gap-0.5 overflow-hidden rounded-2xl text-center sm:grid-cols-2 lg:grid-cols-4\">\n                <div className=\"flex flex-col justify-center bg-white/5 p-8\">\n                  <dt className=\"text-sm font-semibold leading-6 text-gray-300\">\n                    Robyn installs\n                  </dt>\n                  {/* <a href=\"https://www.digitalocean.com/?refcode=3f2b9fd4968d&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge\" target=\"_blank\" rel=\"noopener noreferrer\">\n                  <dd className=\"order-first text-3xl font-semibold tracking-tight text-white\">\n                    <img src=\"https://web-platforms.sfo2.cdn.digitaloceanspaces.com/WWW/Badge%201.svg\" alt=\"Digital Ocean\" />\n                  </dd>\n                  </a> */}\n\n                  <dd className=\"order-first text-3xl font-semibold tracking-tight text-white\">\n                    3 million\n                  </dd>\n                </div>\n                <div className=\"flex flex-col justify-center  bg-white/5 p-8\">\n                  <dt className=\"text-sm font-semibold leading-6 text-gray-300\">\n                    Stars on Github\n                  </dt>\n                  {/* <a href=\"https://github.com/appwrite\" target=\"_blank\" rel=\"noopener noreferrer\">\n                  <dd className=\"order-first text-3xl font-semibold tracking-tight text-white\">\n                    <img src=\"https://avatars.githubusercontent.com/u/25003669?s=200&v=4\" alt=\"AppWrite\" />\n                  </dd>\n                  </a> */}\n\n                  <dd className=\"order-first text-3xl font-semibold tracking-tight text-white\">\n                    6k+\n                  </dd>\n                </div>\n                <div className=\"flex flex-col  bg-white/5 p-8\">\n                  <dt className=\"text-sm font-semibold leading-6 text-gray-300\">\n                    Community Contributors\n                  </dt>\n                  {/* <a href=\"https://github.com/shivaylamba\" target=\"_blank\" rel=\"noopener noreferrer\">\n                  <dd className=\"order-first text-3xl font-semibold tracking-tight text-white\">\n                    <img src=\"https://avatars.githubusercontent.com/u/19529592?v=4\" alt=\"Shivay Lamba\" className='rounded-full' />\n                  </dd>\n                  </a> */}\n                  <dd className=\"order-first text-3xl font-semibold tracking-tight text-white\">\n                    70+\n                  </dd>\n                </div>\n                <div className=\"flex flex-col bg-white/5 p-8\">\n                  <dt className=\"text-sm font-semibold leading-6 text-gray-300\">\n                    Community Members\n                  </dt>\n                  <dd className=\"order-first text-3xl font-semibold tracking-tight text-white\">\n                    500+\n                  </dd>\n                </div>\n              </dl>\n            </div>\n          </div>\n        </div>\n      </Container>\n      <Container>\n        <div className=\"relative isolate overflow-hidden \">\n          <div className=\"px-6 py-24 sm:px-6 sm:py-32 lg:px-8\">\n            <div className=\"mx-auto max-w-2xl text-center\">\n              <h2 className=\"text-3xl font-bold tracking-tight text-white sm:text-4xl\">\n                Ready to Dive In?\n                <br />\n                Start using Robyn today.\n              </h2>\n              <p className=\"mx-auto mt-6 max-w-xl text-lg leading-8 text-gray-300\">\n                Go through a small tutorial or read the docs to get started.\n              </p>\n              <div className=\"mt-10 flex items-center justify-center gap-x-6\">\n                <a\n                  href=\"/documentation/en/example_app\"\n                  className=\"rounded-md bg-white px-3.5 py-2.5 text-sm font-semibold text-gray-900 shadow-sm hover:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white\"\n                >\n                  Get started\n                </a>\n                <a\n                  href=\"/documentation/en/api_reference\"\n                  className=\"text-sm font-semibold leading-6 text-white\"\n                >\n                  Read the docs <span aria-hidden=\"true\">→</span>\n                </a>\n              </div>\n            </div>\n          </div>\n          <svg\n            viewBox=\"0 0 1024 1024\"\n            className=\"absolute left-1/2 top-1/2 -z-10 h-[64rem] w-[64rem] -translate-x-1/2 [mask-image:radial-gradient(closest-side,white,transparent)]\"\n            aria-hidden=\"true\"\n          >\n            <circle\n              cx={512}\n              cy={512}\n              r={512}\n              fill=\"url(#8d958450-c69f-4251-94bc-4e091a323369)\"\n              fillOpacity=\"0.7\"\n            />\n            <defs>\n              <radialGradient id=\"8d958450-c69f-4251-94bc-4e091a323369\">\n                <stop stopColor=\"#ca8a04\" />\n                <stop offset={1} stopColor=\"#9a3142\" />\n              </radialGradient>\n            </defs>\n          </svg>\n        </div>\n      </Container>\n    </>\n  )\n}\n"
  },
  {
    "path": "docs_src/src/pages/releases/index.jsx",
    "content": "import axios from 'axios'\nimport { MDXProvider } from '@mdx-js/react'\nimport { serialize } from 'next-mdx-remote/serialize'\nimport { MDXRemote } from 'next-mdx-remote'\n\nimport {\n  a as A,\n  H2,\n  Article,\n} from '@/components/releases/mdx'\n\nimport * as releaseMdxComponents from '@/components/releases/mdx'\n\nconst ChangelogPage = ({ releases }) => {\n  return (\n    <>\n      {releases.map((release) => (\n        <MDXProvider key={release.id} components={releaseMdxComponents}>\n          <Article\n            key={release.id}\n            date={new Date(release.publishedAt)}\n            id={release.name}\n          >\n            <A\n              href={`https://github.com/sparckles/robyn/releases/tag/${release.tag}`}\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n              className=\"hover:text-yellow-500 transition-colors\"\n            >\n            <H2 key={release.id} className=\"text-lg font-extrabold text-white\">\n              {release.name}\n              </H2>\n            </A>\n            <MDXRemote\n              key={release.id}\n              {...release.body}\n              components={releaseMdxComponents}\n            />\n          </Article>\n        </MDXProvider>\n      ))}\n    </>\n  )\n}\n\nexport async function getStaticProps() {\n  const response = await axios.get(\n    'https://api.github.com/repos/sparckles/robyn/releases'\n  )\n\n  const releases = await Promise.all(\n    response.data.map(async (release) => ({\n      id: release.id,\n      name: release.name,\n      body: await serialize(release.body),\n      publishedAt: release.published_at,\n      tag: release.tag_name,\n    }))\n  )\n\n  return {\n    props: {\n      releases,\n    },\n  }\n}\n\nexport default ChangelogPage\n"
  },
  {
    "path": "docs_src/src/styles/documentation.css",
    "content": ":root {\n  --shiki-color-text: theme('colors.white');\n  --shiki-token-constant: theme('colors.orange.300');\n  --shiki-token-string: theme('colors.orange.300');\n  --shiki-token-comment: theme('colors.zinc.500');\n  --shiki-token-keyword: theme('colors.sky.300');\n  --shiki-token-parameter: theme('colors.pink.300');\n  --shiki-token-function: theme('colors.violet.300');\n  --shiki-token-string-expression: theme('colors.orange.300');\n  --shiki-token-punctuation: theme('colors.zinc.200');\n}\n\n@tailwind base;\n@tailwind components;\n@tailwind utilities;\n"
  },
  {
    "path": "docs_src/src/styles/prism.css",
    "content": "pre[class*='language-'] {\n  color: theme('colors.zinc.100');\n}\n\n.token.tag,\n.token.class-name,\n.token.selector,\n.token.selector .class,\n.token.selector.class,\n.token.function {\n  color: theme('colors.pink.400');\n}\n\n.token.attr-name,\n.token.keyword,\n.token.rule,\n.token.pseudo-class,\n.token.important {\n  color: theme('colors.zinc.300');\n}\n\n.token.module {\n  color: theme('colors.pink.400');\n}\n\n.token.attr-value,\n.token.class,\n.token.string,\n.token.property {\n  color: theme('colors.yellow.300');\n}\n\n.token.punctuation,\n.token.attr-equals {\n  color: theme('colors.zinc.500');\n}\n\n.token.unit,\n.language-css .token.function {\n  color: theme('colors.sky.200');\n}\n\n.token.comment,\n.token.operator,\n.token.combinator {\n  color: theme('colors.zinc.400');\n}\n"
  },
  {
    "path": "docs_src/src/styles/releases/base.css",
    "content": "@font-face {\n  font-family: 'Inter';\n  font-weight: 100 900;\n  font-display: block;\n  font-style: normal;\n  font-named-instance: 'Regular';\n  src: url('/fonts/Inter-roman.var.woff2') format('woff2');\n}\n@font-face {\n  font-family: 'Inter';\n  font-weight: 100 900;\n  font-display: block;\n  font-style: italic;\n  font-named-instance: 'Italic';\n  src: url('/fonts/Inter-italic.var.woff2') format('woff2');\n}\n@font-face {\n  font-family: 'Mona Sans';\n  font-weight: 200 900;\n  font-display: block;\n  font-style: normal;\n  font-stretch: 75% 125%;\n  src: url('/fonts/Mona-Sans.var.woff2') format('woff2');\n}\n\n@tailwind base;\n\n#__next {\n  flex: auto;\n  display: flex;\n  flex-direction: column;\n}\n"
  },
  {
    "path": "docs_src/src/styles/releases/components.css",
    "content": "@import './typography.css';\n@tailwind components;\n"
  },
  {
    "path": "docs_src/src/styles/releases/tailwind.css",
    "content": "@import './base.css';\n@import './components.css';\n@import './utilities.css';\n"
  },
  {
    "path": "docs_src/src/styles/releases/typography.css",
    "content": ".typography {\n  --typography-body: theme(colors.gray.600);\n  --typography-headings: theme(colors.white);\n  --typography-quotes: theme(colors.gray.500);\n  --typography-quotes-border: theme(colors.gray.200);\n  --typography-links: theme(colors.sky.500);\n  --typography-link-hover: theme(colors.sky.600);\n  --typography-link-underline: theme(colors.sky.400 / 0.4);\n  --typography-link-hover-underline: theme(colors.sky.600 / 0.4);\n  --typography-pre: theme(colors.gray.300);\n  --typography-pre-shadow: theme(boxShadow.md);\n  --typography-bold: theme(colors.gray.900);\n  --typography-kbd: theme(colors.gray.600);\n  --typography-kbd-border: theme(colors.gray.200);\n  --typography-kbd-bg: theme(colors.gray.50);\n  --typography-code: theme(colors.gray.900);\n  --typography-hr: theme(colors.gray.900 / 0.05);\n  --typography-th-borders: theme(colors.gray.900 / 0.2);\n  --typography-td-borders: theme(colors.gray.900 / 0.05);\n\n  --shiki-color-text: theme(colors.white);\n  --shiki-token-constant: theme(colors.orange.300);\n  --shiki-token-string: theme(colors.orange.300);\n  --shiki-token-comment: theme(colors.gray.500);\n  --shiki-token-keyword: theme(colors.sky.300);\n  --shiki-token-parameter: theme(colors.pink.300);\n  --shiki-token-function: theme(colors.violet.300);\n  --shiki-token-string-expression: theme(colors.orange.300);\n  --shiki-token-punctuation: theme(colors.gray.200);\n\n  .dark & {\n    --typography-body: theme(colors.gray.300);\n    --typography-headings: theme(colors.white);\n    --typography-quotes: theme(colors.gray.400);\n    --typography-quotes-border: theme(colors.gray.800);\n    --typography-links: theme(colors.sky.400);\n    --typography-link-hover: theme(colors.white);\n    --typography-link-underline: theme(colors.sky.400 / 0.4);\n    --typography-link-hover-underline: theme(colors.white / 0.4);\n    --typography-pre: theme(colors.gray.400);\n    --typography-pre-shadow: inset 0 0 0 1px theme(colors.white / 0.1);\n    --typography-bold: theme(colors.white);\n    --typography-kbd: theme(colors.white);\n    --typography-kbd-border: theme(colors.gray.800);\n    --typography-kbd-bg: theme(colors.gray.900);\n    --typography-code: theme(colors.white);\n    --typography-hr: theme(colors.white / 0.1);\n    --typography-th-borders: theme(colors.white / 0.1);\n    --typography-td-borders: theme(colors.white / 0.05);\n  }\n\n  color: var(--typography-body);\n  font-size: theme(fontSize.sm);\n  line-height: theme(lineHeight.6);\n\n  h2 {\n    font-family: theme(fontFamily.display);\n    color: var(--typography-headings);\n    font-weight: theme(fontWeight.semibold);\n    font-size: theme(fontSize.xl);\n    line-height: theme(lineHeight.8);\n  }\n\n  /* Headings */\n  h3 {\n    color: var(--typography-headings);\n    font-family: theme(fontFamily.display);\n    font-weight: theme(fontWeight.semibold);\n    font-size: theme(fontSize.base);\n    line-height: theme(lineHeight.6);\n    display: flex;\n    align-items: center;\n    column-gap: theme(gap.3);\n  }\n\n  h3 > svg {\n    flex: none;\n    width: theme(width.4);\n    height: theme(height.4);\n  }\n\n  h4 {\n    color: var(--typography-headings);\n    font-family: theme(fontFamily.display);\n    font-weight: theme(fontWeight.semibold);\n    font-size: theme(fontSize.sm);\n    line-height: theme(lineHeight.6);\n  }\n\n  /* Quotes */\n  blockquote {\n    border-left: 3px solid var(--typography-quotes-border);\n    padding-left: theme(padding.6);\n    color: var(--typography-quotes);\n  }\n\n  /* Links */\n  a:not(h2 a) {\n    font-weight: theme(fontWeight.semibold);\n    color: var(--typography-links);\n    text-decoration: underline;\n    text-decoration-color: var(--typography-link-underline);\n    text-underline-offset: theme(textUnderlineOffset.2);\n    transition-property: color, text-decoration-color;\n    transition-duration: theme(transitionDuration.DEFAULT);\n    transition-timing-function: theme(transitionTimingFunction.DEFAULT);\n\n    &:hover {\n      color: var(--typography-link-hover);\n      text-decoration-color: var(--typography-link-hover-underline);\n    }\n  }\n\n  /* Inline text */\n  strong {\n    font-weight: theme(fontWeight.semibold);\n  }\n\n  strong:not(a strong) {\n    color: var(--typography-bold);\n  }\n\n  kbd {\n    display: inline-block;\n    border-radius: theme(borderRadius.DEFAULT);\n    background-color: var(--typography-kbd-bg);\n    padding: 0 theme(padding[1.5]);\n    font-family: theme(fontFamily.mono);\n    font-size: theme(fontSize.xs);\n    font-weight: 400;\n    line-height: theme(lineHeight.5);\n    color: var(--typography-kbd);\n    box-shadow: inset 0 0 0 1px var(--typography-kbd-border);\n  }\n\n  code {\n    font-family: theme(fontFamily.mono);\n  }\n\n  code:not(a code, pre code) {\n    color: var(--typography-code);\n  }\n\n  code:not(pre code) {\n    font-size: calc(12 / 14 * 1em);\n    line-height: theme(lineHeight.none);\n    font-weight: theme(fontWeight.bold);\n\n    &::before {\n      content: '`';\n    }\n\n    &::after {\n      content: '`';\n    }\n  }\n\n  /* Code blocks */\n  pre {\n    display: flex;\n    background-color: theme(colors.gray.900);\n    border-radius: theme(borderRadius.lg);\n    overflow-x: auto;\n    box-shadow: var(--typography-pre-shadow);\n  }\n\n  pre code {\n    flex: none;\n    padding: theme(padding.6);\n    font-size: 0.8125rem;\n    line-height: theme(lineHeight.6);\n    color: var(--typography-pre);\n  }\n\n  /* <hr> */\n  hr {\n    border-color: var(--typography-hr);\n  }\n\n  /* Lists */\n  ul,\n  ol {\n    padding-left: 1.375rem;\n  }\n\n  ul {\n    list-style-type: disc;\n  }\n\n  ol {\n    list-style-type: decimal;\n  }\n\n  li {\n    padding-left: 0.625rem;\n  }\n\n  li::marker {\n    color: theme(colors.gray.400);\n  }\n\n  ol > li::marker {\n    font-size: theme(fontSize.xs);\n    font-weight: theme(fontWeight.semibold);\n  }\n\n  /* Tables */\n  table {\n    width: theme(width.full);\n    text-align: left;\n  }\n\n  thead {\n    border-bottom: 1px solid var(--typography-th-borders);\n  }\n\n  thead th {\n    font-weight: theme(fontWeight.semibold);\n    padding-top: 0;\n    padding-bottom: calc(theme(padding.2) - 1px);\n    color: var(--typography-headings);\n  }\n\n  tbody tr {\n    border-bottom: 1px solid var(--typography-td-borders);\n  }\n\n  tbody td {\n    padding-top: calc(theme(padding.2) - 1px);\n    padding-bottom: theme(padding.2);\n  }\n\n  :is(th, td):first-child {\n    padding-left: 0;\n    padding-right: theme(padding.2);\n  }\n\n  :is(th, td):last-child {\n    padding-left: theme(padding.2);\n    padding-right: 0;\n  }\n\n  :is(th, td):not(:first-child, :last-child) {\n    padding-left: theme(padding.2);\n    padding-right: theme(padding.2);\n  }\n\n  /* Spacing */\n  > * {\n    margin-top: theme(margin.6);\n  }\n\n  :is(h2, h3, h4, blockquote, pre, table) {\n    margin-top: theme(margin.8);\n  }\n\n  hr {\n    margin-top: calc(theme(margin.16) - 1px);\n  }\n\n  li {\n    margin-top: theme(margin.4);\n  }\n\n  li > :is(p, ol, ul) {\n    margin-top: theme(margin.4);\n  }\n\n  :is(h2, h3, h4) + * {\n    margin-top: theme(margin.4);\n  }\n\n  :is(blockquote, pre, table) + * {\n    margin-top: theme(margin.8);\n  }\n\n  hr + * {\n    margin-top: theme(margin.16);\n  }\n\n  > :first-child,\n  li > :first-child {\n    margin-top: 0;\n  }\n}\n"
  },
  {
    "path": "docs_src/src/styles/releases/utilities.css",
    "content": "@tailwind utilities;\n"
  },
  {
    "path": "docs_src/src/styles/tailwind.css",
    "content": "@import 'tailwindcss/base';\n@import 'tailwindcss/components';\n@import './prism.css';\n@import 'tailwindcss/utilities';\n\n@import './releases/base.css';\n@import './releases/components.css';\n@import './releases/utilities.css';\n"
  },
  {
    "path": "docs_src/tailwind.config.js",
    "content": "const defaultTheme = require('tailwindcss/defaultTheme')\n\n/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n  content: ['./src/**/*.{js,jsx}'],\n  darkMode: 'class',\n  plugins: [require('@tailwindcss/typography')],\n  theme: {\n    extend: {\n      fontFamily: {\n        sans: ['Inter', ...defaultTheme.fontFamily.sans],\n        display: ['Mona Sans', ...defaultTheme.fontFamily.sans],\n      },\n    },\n    fontSize: {\n      xs: ['0.8125rem', { lineHeight: '1.5rem' }],\n      sm: ['0.875rem', { lineHeight: '1.5rem' }],\n      base: ['1rem', { lineHeight: '1.75rem' }],\n      lg: ['1.125rem', { lineHeight: '1.75rem' }],\n      xl: ['1.25rem', { lineHeight: '2rem' }],\n      '2xl': ['1.5rem', { lineHeight: '2rem' }],\n      '3xl': ['1.875rem', { lineHeight: '2.25rem' }],\n      '4xl': ['2rem', { lineHeight: '2.5rem' }],\n      '5xl': ['3rem', { lineHeight: '3.5rem' }],\n      '6xl': ['3.75rem', { lineHeight: '1' }],\n      '7xl': ['4.5rem', { lineHeight: '1' }],\n      '8xl': ['6rem', { lineHeight: '1' }],\n      '9xl': ['8rem', { lineHeight: '1' }],\n    },\n    typography: (theme) => ({\n      invert: {\n        css: {\n          '--tw-prose-body': 'var(--tw-prose-invert-body)',\n          '--tw-prose-headings': 'var(--tw-prose-invert-headings)',\n          '--tw-prose-links': 'var(--tw-prose-invert-links)',\n          '--tw-prose-links-hover': 'var(--tw-prose-invert-links-hover)',\n          '--tw-prose-underline': 'var(--tw-prose-invert-underline)',\n          '--tw-prose-underline-hover':\n            'var(--tw-prose-invert-underline-hover)',\n          '--tw-prose-bold': 'var(--tw-prose-invert-bold)',\n          '--tw-prose-counters': 'var(--tw-prose-invert-counters)',\n          '--tw-prose-bullets': 'var(--tw-prose-invert-bullets)',\n          '--tw-prose-hr': 'var(--tw-prose-invert-hr)',\n          '--tw-prose-quote-borders': 'var(--tw-prose-invert-quote-borders)',\n          '--tw-prose-captions': 'var(--tw-prose-invert-captions)',\n          '--tw-prose-code': 'var(--tw-prose-invert-code)',\n          '--tw-prose-code-bg': 'var(--tw-prose-invert-code-bg)',\n          '--tw-prose-pre-code': 'var(--tw-prose-invert-pre-code)',\n          '--tw-prose-pre-bg': 'var(--tw-prose-invert-pre-bg)',\n          '--tw-prose-pre-border': 'var(--tw-prose-invert-pre-border)',\n          '--tw-prose-th-borders': 'var(--tw-prose-invert-th-borders)',\n          '--tw-prose-td-borders': 'var(--tw-prose-invert-td-borders)',\n        },\n      },\n      DEFAULT: {\n        css: {\n          '--tw-prose-body': theme('colors.zinc.600'),\n          '--tw-prose-headings': theme('colors.zinc.900'),\n          '--tw-prose-links': theme('colors.yellow.500'),\n          '--tw-prose-links-hover': theme('colors.yellow.600'),\n          '--tw-prose-underline': theme('colors.yellow.500 / 0.2'),\n          '--tw-prose-underline-hover': theme('colors.yellow.500'),\n          '--tw-prose-bold': theme('colors.zinc.900'),\n          '--tw-prose-counters': theme('colors.zinc.900'),\n          '--tw-prose-bullets': theme('colors.zinc.900'),\n          '--tw-prose-hr': theme('colors.zinc.100'),\n          '--tw-prose-quote-borders': theme('colors.zinc.200'),\n          '--tw-prose-captions': theme('colors.zinc.400'),\n          '--tw-prose-code': theme('colors.zinc.700'),\n          '--tw-prose-code-bg': theme('colors.zinc.300 / 0.2'),\n          '--tw-prose-pre-code': theme('colors.zinc.100'),\n          '--tw-prose-pre-bg': theme('colors.zinc.900'),\n          '--tw-prose-pre-border': 'transparent',\n          '--tw-prose-th-borders': theme('colors.zinc.200'),\n          '--tw-prose-td-borders': theme('colors.zinc.100'),\n\n          '--tw-prose-invert-body': theme('colors.zinc.400'),\n          '--tw-prose-invert-headings': theme('colors.zinc.200'),\n          '--tw-prose-invert-links': theme('colors.yellow.400'),\n          '--tw-prose-invert-links-hover': theme('colors.yellow.400'),\n          '--tw-prose-invert-underline': theme('colors.yellow.400 / 0.3'),\n          '--tw-prose-invert-underline-hover': theme('colors.yellow.400'),\n          '--tw-prose-invert-bold': theme('colors.zinc.200'),\n          '--tw-prose-invert-counters': theme('colors.zinc.200'),\n          '--tw-prose-invert-bullets': theme('colors.zinc.200'),\n          '--tw-prose-invert-hr': theme('colors.zinc.700 / 0.4'),\n          '--tw-prose-invert-quote-borders': theme('colors.zinc.500'),\n          '--tw-prose-invert-captions': theme('colors.zinc.500'),\n          '--tw-prose-invert-code': theme('colors.zinc.300'),\n          '--tw-prose-invert-code-bg': theme('colors.zinc.200 / 0.05'),\n          '--tw-prose-invert-pre-code': theme('colors.zinc.100'),\n          '--tw-prose-invert-pre-bg': 'rgb(0 0 0 / 0.4)',\n          '--tw-prose-invert-pre-border': theme('colors.zinc.200 / 0.1'),\n          '--tw-prose-invert-th-borders': theme('colors.zinc.700'),\n          '--tw-prose-invert-td-borders': theme('colors.zinc.800'),\n\n          // Base\n          color: 'var(--tw-prose-body)',\n          lineHeight: theme('lineHeight.7'),\n          '> *': {\n            marginTop: theme('spacing.10'),\n            marginBottom: theme('spacing.10'),\n          },\n          p: {\n            marginTop: theme('spacing.7'),\n            marginBottom: theme('spacing.7'),\n          },\n\n          // Headings\n          'h2, h3': {\n            color: 'var(--tw-prose-headings)',\n            fontWeight: theme('fontWeight.semibold'),\n          },\n          h2: {\n            fontSize: theme('fontSize.xl')[0],\n            lineHeight: theme('lineHeight.7'),\n            marginTop: theme('spacing.20'),\n            marginBottom: theme('spacing.4'),\n          },\n          h3: {\n            fontSize: theme('fontSize.base')[0],\n            lineHeight: theme('lineHeight.7'),\n            marginTop: theme('spacing.16'),\n            marginBottom: theme('spacing.4'),\n          },\n          ':is(h2, h3) + *': {\n            marginTop: 0,\n          },\n\n          // Images\n          img: {\n            borderRadius: theme('borderRadius.3xl'),\n          },\n\n          // Inline elements\n          a: {\n            color: 'var(--tw-prose-links)',\n            fontWeight: theme('fontWeight.semibold'),\n            textDecoration: 'underline',\n            textDecorationColor: 'var(--tw-prose-underline)',\n            transitionProperty: 'color, text-decoration-color',\n            transitionDuration: theme('transitionDuration.150'),\n            transitionTimingFunction: theme('transitionTimingFunction.in-out'),\n          },\n          'a:hover': {\n            color: 'var(--tw-prose-links-hover)',\n            textDecorationColor: 'var(--tw-prose-underline-hover)',\n          },\n          strong: {\n            color: 'var(--tw-prose-bold)',\n            fontWeight: theme('fontWeight.semibold'),\n          },\n          code: {\n            display: 'inline-block',\n            color: 'var(--tw-prose-code)',\n            fontSize: theme('fontSize.sm')[0],\n            fontWeight: theme('fontWeight.semibold'),\n            backgroundColor: 'var(--tw-prose-code-bg)',\n            borderRadius: theme('borderRadius.lg'),\n            paddingLeft: theme('spacing.1'),\n            paddingRight: theme('spacing.1'),\n          },\n          'a code': {\n            color: 'inherit',\n          },\n          ':is(h2, h3) code': {\n            fontWeight: theme('fontWeight.bold'),\n          },\n\n          // Quotes\n          blockquote: {\n            paddingLeft: theme('spacing.6'),\n            borderLeftWidth: theme('borderWidth.2'),\n            borderLeftColor: 'var(--tw-prose-quote-borders)',\n            fontStyle: 'italic',\n          },\n\n          // Figures\n          figcaption: {\n            color: 'var(--tw-prose-captions)',\n            fontSize: theme('fontSize.sm')[0],\n            lineHeight: theme('lineHeight.6'),\n            marginTop: theme('spacing.3'),\n          },\n          'figcaption > p': {\n            margin: 0,\n          },\n\n          // Lists\n          ul: {\n            listStyleType: 'disc',\n          },\n          ol: {\n            listStyleType: 'decimal',\n          },\n          'ul, ol': {\n            paddingLeft: theme('spacing.6'),\n          },\n          li: {\n            marginTop: theme('spacing.6'),\n            marginBottom: theme('spacing.6'),\n            paddingLeft: theme('spacing[3.5]'),\n          },\n          'li::marker': {\n            fontSize: theme('fontSize.sm')[0],\n            fontWeight: theme('fontWeight.semibold'),\n          },\n          'ol > li::marker': {\n            color: 'var(--tw-prose-counters)',\n          },\n          'ul > li::marker': {\n            color: 'var(--tw-prose-bullets)',\n          },\n          'li :is(ol, ul)': {\n            marginTop: theme('spacing.4'),\n            marginBottom: theme('spacing.4'),\n          },\n          'li :is(li, p)': {\n            marginTop: theme('spacing.3'),\n            marginBottom: theme('spacing.3'),\n          },\n\n          // Code blocks\n          pre: {\n            color: 'var(--tw-prose-pre-code)',\n            fontSize: theme('fontSize.sm')[0],\n            fontWeight: theme('fontWeight.medium'),\n            backgroundColor: 'var(--tw-prose-pre-bg)',\n            borderRadius: theme('borderRadius.3xl'),\n            padding: theme('spacing.8'),\n            overflowX: 'auto',\n            border: '1px solid',\n            borderColor: 'var(--tw-prose-pre-border)',\n          },\n          'pre code': {\n            display: 'inline',\n            color: 'inherit',\n            fontSize: 'inherit',\n            fontWeight: 'inherit',\n            backgroundColor: 'transparent',\n            borderRadius: 0,\n            padding: 0,\n          },\n\n          // Horizontal rules\n          hr: {\n            marginTop: theme('spacing.20'),\n            marginBottom: theme('spacing.20'),\n            borderTopWidth: '1px',\n            borderColor: 'var(--tw-prose-hr)',\n            '@screen lg': {\n              marginLeft: `calc(${theme('spacing.12')} * -1)`,\n              marginRight: `calc(${theme('spacing.12')} * -1)`,\n            },\n          },\n\n          // Tables\n          table: {\n            width: '100%',\n            tableLayout: 'auto',\n            textAlign: 'left',\n            fontSize: theme('fontSize.sm')[0],\n          },\n          thead: {\n            borderBottomWidth: '1px',\n            borderBottomColor: 'var(--tw-prose-th-borders)',\n          },\n          'thead th': {\n            color: 'var(--tw-prose-headings)',\n            fontWeight: theme('fontWeight.semibold'),\n            verticalAlign: 'bottom',\n            paddingBottom: theme('spacing.2'),\n          },\n          'thead th:not(:first-child)': {\n            paddingLeft: theme('spacing.2'),\n          },\n          'thead th:not(:last-child)': {\n            paddingRight: theme('spacing.2'),\n          },\n          'tbody tr': {\n            borderBottomWidth: '1px',\n            borderBottomColor: 'var(--tw-prose-td-borders)',\n          },\n          'tbody tr:last-child': {\n            borderBottomWidth: 0,\n          },\n          'tbody td': {\n            verticalAlign: 'baseline',\n          },\n          tfoot: {\n            borderTopWidth: '1px',\n            borderTopColor: 'var(--tw-prose-th-borders)',\n          },\n          'tfoot td': {\n            verticalAlign: 'top',\n          },\n          ':is(tbody, tfoot) td': {\n            paddingTop: theme('spacing.2'),\n            paddingBottom: theme('spacing.2'),\n          },\n          ':is(tbody, tfoot) td:not(:first-child)': {\n            paddingLeft: theme('spacing.2'),\n          },\n          ':is(tbody, tfoot) td:not(:last-child)': {\n            paddingRight: theme('spacing.2'),\n          },\n        },\n      },\n    }),\n  },\n}\n"
  },
  {
    "path": "examples/agents.py",
    "content": "#!/usr/bin/env python3\n\"\"\"Simple Robyn AI Agents Example\"\"\"\n\nimport os\n\nfrom robyn import Robyn\nfrom robyn.ai import agent, configure, memory\n\napp = Robyn(__file__)\nai_agent = None\n\n\n@app.startup_handler\nasync def startup():\n    global ai_agent\n\n    if not os.getenv(\"OPENAI_API_KEY\"):\n        print(\"Set OPENAI_API_KEY to use AI features\")\n        return\n\n    user_memory = memory(provider=\"inmemory\", user_id=\"user\")\n    config = configure(openai_api_key=os.getenv(\"OPENAI_API_KEY\"))\n    ai_agent = agent(memory=user_memory, config=config)\n\n\n@app.post(\"/chat\")\nasync def chat(request):\n    if not ai_agent:\n        return {\"error\": \"AI not available\"}\n\n    query = request.json().get(\"query\")\n    if not query:\n        return {\"error\": \"Query required\"}\n\n    result = await ai_agent.run(query)\n    return {\"response\": result.get(\"response\")}\n\n\n@app.get(\"/\")\ndef home():\n    return {\"message\": \"Robyn AI Agent\", \"endpoint\": \"/chat\"}\n\n\nif __name__ == \"__main__\":\n    app.start(port=8080)\n"
  },
  {
    "path": "examples/mcp.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\nRobyn MCP Server Example\n\nSimple MCP server demonstrating basic resources and tools.\nRun with: python examples/mcp.py\n\"\"\"\n\nimport json\nimport platform\nfrom datetime import datetime\n\nfrom robyn import Robyn\n\napp = Robyn(__file__)\n\n\n# MCP Resources\n@app.mcp.resource(\"time://current\")\ndef current_time() -> str:\n    return f\"Current time: {datetime.now().isoformat()}\"\n\n\n@app.mcp.resource(\"system://info\")\ndef system_info() -> str:\n    return json.dumps({\"platform\": platform.system(), \"python_version\": platform.python_version(), \"timestamp\": datetime.now().isoformat()})\n\n\n# MCP Tools\n@app.mcp.tool(\n    name=\"calculate\",\n    description=\"Perform safe mathematical calculations\",\n    input_schema={\n        \"type\": \"object\",\n        \"properties\": {\"expression\": {\"type\": \"string\", \"description\": \"Math expression like '2 + 2'\"}},\n        \"required\": [\"expression\"],\n    },\n)\ndef calculate_tool(args):\n    expression = args.get(\"expression\", \"\")\n    allowed_chars = set(\"0123456789+-*/.() \")\n    if not all(c in allowed_chars for c in expression):\n        return \"Error: Invalid characters in expression\"\n\n    try:\n        result = eval(expression, {\"__builtins__\": {}}, {})\n        return f\"{expression} = {result}\"\n    except Exception as e:\n        return f\"Error: {str(e)}\"\n\n\n@app.mcp.tool(\n    name=\"echo\",\n    description=\"Echo back text\",\n    input_schema={\"type\": \"object\", \"properties\": {\"text\": {\"type\": \"string\", \"description\": \"Text to echo\"}}, \"required\": [\"text\"]},\n)\ndef echo_tool(args):\n    return args.get(\"text\", \"\")\n\n\n# MCP Prompts\n@app.mcp.prompt(\n    name=\"explain_code\",\n    description=\"Generate code explanation prompt\",\n    arguments=[\n        {\"name\": \"code\", \"description\": \"Code to explain\", \"required\": True},\n        {\"name\": \"language\", \"description\": \"Programming language\", \"required\": False},\n    ],\n)\ndef explain_code_prompt(args):\n    code = args.get(\"code\", \"\")\n    language = args.get(\"language\", \"unknown\")\n\n    return f\"\"\"Please explain this {language} code:\n\n```{language}\n{code}\n```\n\nInclude:\n1. What it does\n2. How it works\n3. Key concepts used\n\"\"\"\n\n\n@app.get(\"/\")\ndef home():\n    return {\n        \"name\": \"Robyn MCP Server Example\",\n        \"mcp_endpoint\": \"/mcp\",\n        \"resources\": [\"time://current\", \"system://info\"],\n        \"tools\": [\"calculate\", \"echo\"],\n        \"prompts\": [\"explain_code\"],\n    }\n\n\nif __name__ == \"__main__\":\n    print(\"Robyn MCP Server Example\")\n    print(\"Server: http://localhost:8080\")\n    print(\"MCP Endpoint: http://localhost:8080/mcp\")\n    app.start(port=8080)\n"
  },
  {
    "path": "examples/sse_example.py",
    "content": "\"\"\"\nServer-Sent Events (SSE) Example for Robyn\n\nThis example demonstrates how to implement Server-Sent Events using Robyn,\nsimilar to FastAPI's implementation. SSE allows real-time server-to-client\ncommunication over a single HTTP connection.\n\"\"\"\n\nimport asyncio\nimport time\n\nfrom robyn import Robyn, SSEMessage, SSEResponse, html\n\napp = Robyn(__file__)\n\n\n@app.get(\"/\")\ndef index(request):\n    \"\"\"Serve a simple HTML page to test SSE\"\"\"\n    return html(\"\"\"\n    <!DOCTYPE html>\n    <html>\n    <head>\n        <title>Robyn SSE Example</title>\n        <style>\n            body { font-family: Arial, sans-serif; margin: 40px; }\n            #events { border: 1px solid #ccc; padding: 20px; height: 300px; overflow-y: auto; }\n            .event { margin: 5px 0; padding: 5px; background: #f0f0f0; }\n        </style>\n    </head>\n    <body>\n        <h1>Robyn Server-Sent Events Demo</h1>\n        <div id=\"events\"></div>\n        <script>\n            const eventSource = new EventSource('/events');\n            const eventsDiv = document.getElementById('events');\n            \n            eventSource.onmessage = function(event) {\n                const eventDiv = document.createElement('div');\n                eventDiv.className = 'event';\n                eventDiv.textContent = event.data;\n                eventsDiv.appendChild(eventDiv);\n                eventsDiv.scrollTop = eventsDiv.scrollHeight;\n            };\n            \n            eventSource.onerror = function(event) {\n                console.error('SSE error:', event);\n            };\n        </script>\n    </body>\n    </html>\n    \"\"\")\n\n\n@app.get(\"/events\")\ndef stream_events(request):\n    \"\"\"Basic SSE endpoint that sends a message every second\"\"\"\n\n    def event_generator():\n        \"\"\"Generator function that yields SSE-formatted messages\"\"\"\n        for i in range(10):\n            yield SSEMessage(f\"Message {i} - {time.strftime('%H:%M:%S')}\", id=str(i))\n            time.sleep(1)\n\n        # Send a final message\n        yield SSEMessage(\"Stream ended\", event=\"end\")\n\n    return SSEResponse(event_generator())\n\n\n@app.get(\"/events/json\")\ndef stream_json_events(request):\n    \"\"\"SSE endpoint that sends JSON data\"\"\"\n    import json\n\n    def json_event_generator():\n        \"\"\"Generator that yields JSON data as SSE\"\"\"\n        for i in range(5):\n            data = {\"id\": i, \"message\": f\"JSON message {i}\", \"timestamp\": time.time(), \"type\": \"notification\"}\n            yield SSEMessage(json.dumps(data), event=\"notification\", id=str(i))\n            time.sleep(2)\n\n    return SSEResponse(json_event_generator())\n\n\n@app.get(\"/events/named\")\ndef stream_named_events(request):\n    \"\"\"SSE endpoint with named events\"\"\"\n\n    def named_event_generator():\n        \"\"\"Generator that yields named SSE events\"\"\"\n        events = [\n            (\"user_joined\", \"Alice joined the chat\"),\n            (\"message\", \"Hello everyone!\"),\n            (\"user_left\", \"Bob left the chat\"),\n            (\"message\", \"How is everyone doing?\"),\n            (\"user_joined\", \"Charlie joined the chat\"),\n        ]\n\n        for i, (event_type, message) in enumerate(events):\n            yield SSEMessage(message, event=event_type, id=str(i))\n            time.sleep(1.5)\n\n    return SSEResponse(named_event_generator())\n\n\n@app.get(\"/events/async\")\nasync def stream_async_events(request):\n    \"\"\"Async SSE endpoint demonstrating async generators\"\"\"\n\n    async def async_event_generator():\n        \"\"\"Async generator for SSE events\"\"\"\n        for i in range(8):\n            # Simulate async work\n            await asyncio.sleep(0.5)\n            yield SSEMessage(f\"Async message {i} - {time.strftime('%H:%M:%S')}\", event=\"async\", id=str(i))\n\n    return SSEResponse(async_event_generator())\n\n\n@app.get(\"/events/heartbeat\")\ndef stream_heartbeat(request):\n    \"\"\"SSE endpoint that sends heartbeat messages\"\"\"\n\n    def heartbeat_generator():\n        \"\"\"Generator that sends heartbeat pings\"\"\"\n        counter = 0\n        while counter < 20:  # Send 20 heartbeats\n            yield SSEMessage(f\"heartbeat {counter}\", event=\"heartbeat\", id=str(counter))\n            counter += 1\n            time.sleep(0.5)\n\n        yield SSEMessage(\"heartbeat ended\", event=\"end\")\n\n    return SSEResponse(heartbeat_generator())\n\n\n@app.get(\"/help\")\ndef help_page(request):\n    \"\"\"Help page explaining available endpoints\"\"\"\n    return html(\"\"\"\n    <!DOCTYPE html>\n    <html>\n    <head>\n        <title>Robyn SSE Help</title>\n        <style>\n            body { font-family: Arial, sans-serif; margin: 40px; }\n            .endpoint { margin: 20px 0; padding: 15px; border: 1px solid #ddd; }\n            code { background: #f4f4f4; padding: 2px 4px; }\n        </style>\n    </head>\n    <body>\n        <h1>Robyn SSE Example Endpoints</h1>\n        \n        <div class=\"endpoint\">\n            <h3><code>GET /</code></h3>\n            <p>Main demo page with a live SSE client</p>\n        </div>\n        \n        <div class=\"endpoint\">\n            <h3><code>GET /events</code></h3>\n            <p>Basic SSE stream with 10 messages sent every second</p>\n        </div>\n        \n        <div class=\"endpoint\">\n            <h3><code>GET /events/json</code></h3>\n            <p>SSE stream sending JSON data every 2 seconds</p>\n        </div>\n        \n        <div class=\"endpoint\">\n            <h3><code>GET /events/named</code></h3>\n            <p>SSE stream with named events (user_joined, message, user_left)</p>\n        </div>\n        \n        <div class=\"endpoint\">\n            <h3><code>GET /events/async</code></h3>\n            <p>Async SSE stream demonstrating async generators</p>\n        </div>\n        \n        <div class=\"endpoint\">\n            <h3><code>GET /events/heartbeat</code></h3>\n            <p>Fast heartbeat stream sending messages every 0.5 seconds</p>\n        </div>\n        \n        <p><strong>Usage:</strong> Use curl, browser EventSource API, or any SSE client to connect to these endpoints.</p>\n        \n        <h3>Example with curl:</h3>\n        <pre><code>curl http://localhost:8080/events</code></pre>\n        \n        <h3>Example with JavaScript:</h3>\n        <pre><code>const eventSource = new EventSource('/events');\neventSource.onmessage = function(event) {\n    console.log('Received:', event.data);\n};</code></pre>\n    </body>\n    </html>\n    \"\"\")\n\n\nif __name__ == \"__main__\":\n    print(\"Starting Robyn SSE Example Server...\")\n    print(\"Visit http://localhost:8080/ for the main demo\")\n    print(\"Visit http://localhost:8080/help for endpoint documentation\")\n    app.start(host=\"0.0.0.0\", port=8080)\n"
  },
  {
    "path": "integration_tests/__init__.py",
    "content": ""
  },
  {
    "path": "integration_tests/base_routes.py",
    "content": "import asyncio\nimport json\nimport os\nimport pathlib\nimport time\nfrom collections import defaultdict\nfrom typing import List, Optional, TypedDict\n\nfrom integration_tests.subroutes import di_subrouter, static_router, sub_router\nfrom robyn import Headers, Request, Response, Robyn, SSEMessage, SSEResponse, WebSocketDisconnect, jsonify, serve_file, serve_html\nfrom robyn.authentication import AuthenticationHandler, BearerGetter, Identity\nfrom robyn.robyn import QueryParams, Url\nfrom robyn.templating import JinjaTemplate\nfrom robyn.types import Body, JsonBody, JSONResponse, Method, PathParams\n\ntry:\n    from pydantic import BaseModel as PydanticBaseModel\n\n    _HAS_PYDANTIC = True\nexcept ImportError:\n    _HAS_PYDANTIC = False\n\napp = Robyn(__file__)\n\ncurrent_file_path = pathlib.Path(__file__).parent.resolve()\njinja_template = JinjaTemplate(os.path.join(current_file_path, \"templates\"))\n\n# ===== Websockets =====\n\n# Make it easier for multiple test runs\nwebsocket_state = defaultdict(int)\n\n\n# --- Regular WebSocket endpoint (new-style with Rust channels) ---\n@app.websocket(\"/web_socket\")\nasync def websocket_endpoint(websocket):\n    try:\n        while True:\n            _ = await websocket.receive_text()\n            websocket_id = websocket.id\n            global websocket_state\n            state = websocket_state[websocket_id]\n\n            if state == 0:\n                await websocket.broadcast(\"This is a broadcast message\")\n                await websocket.send_text(\"This is a message to self\")\n                await websocket.send_text(\"Whaaat??\")\n            elif state == 1:\n                await websocket.send_text(\"Whooo??\")\n            elif state == 2:\n                await websocket.broadcast(websocket.query_params.get(\"one\", \"\"))\n                await websocket.send_text(websocket.query_params.get(\"two\", \"\"))\n                await websocket.send_text(\"*chika* *chika* Slim Shady.\")\n            elif state == 3:\n                await websocket.send_text(\"Connection closed\")\n                await websocket.close()\n                break\n\n            websocket_state[websocket_id] = (state + 1) % 4\n    except WebSocketDisconnect:\n        pass\n\n\n@websocket_endpoint.on_connect\ndef websocket_on_connect(websocket):\n    return \"Hello world, from ws\"\n\n\n@websocket_endpoint.on_close\ndef websocket_on_close(websocket):\n    return \"GoodBye world, from ws\"\n\n\n# --- JSON WebSocket endpoint ---\n@app.websocket(\"/web_socket_json\")\nasync def json_websocket_endpoint(websocket):\n    try:\n        while True:\n            msg = await websocket.receive_text()\n            websocket_id = websocket.id\n            response = {\"ws_id\": websocket_id, \"resp\": \"\", \"msg\": msg}\n            global websocket_state\n            state = websocket_state[websocket_id]\n\n            if state == 0:\n                response[\"resp\"] = \"Whaaat??\"\n            elif state == 1:\n                response[\"resp\"] = \"Whooo??\"\n            elif state == 2:\n                response[\"resp\"] = \"*chika* *chika* Slim Shady.\"\n\n            websocket_state[websocket_id] = (state + 1) % 3\n            await websocket.send_json(response)\n    except WebSocketDisconnect:\n        pass\n\n\n@json_websocket_endpoint.on_connect\ndef json_websocket_on_connect(websocket):\n    return \"Hello world, from ws\"\n\n\n@json_websocket_endpoint.on_close\ndef json_websocket_on_close(websocket):\n    return \"GoodBye world, from ws\"\n\n\n# --- WebSocket with dependency injection ---\n@app.websocket(\"/web_socket_di\")\nasync def di_websocket_endpoint(websocket, global_dependencies=None, router_dependencies=None):\n    try:\n        while True:\n            _ = await websocket.receive_text()\n            global_dep = global_dependencies.get(\"GLOBAL_DEPENDENCY\", \"MISSING GLOBAL\") if global_dependencies else \"MISSING GLOBAL\"\n            router_dep = router_dependencies.get(\"ROUTER_DEPENDENCY\", \"MISSING ROUTER\") if router_dependencies else \"MISSING ROUTER\"\n            await websocket.send_text(f\"handler: {global_dep} {router_dep}\")\n    except WebSocketDisconnect:\n        pass\n\n\n@di_websocket_endpoint.on_connect\nasync def di_websocket_on_connect(websocket, global_dependencies=None, router_dependencies=None):\n    global_dep = global_dependencies.get(\"GLOBAL_DEPENDENCY\") if global_dependencies else \"MISSING GLOBAL\"\n    router_dep = router_dependencies.get(\"ROUTER_DEPENDENCY\") if router_dependencies else \"MISSING ROUTER\"\n    return f\"connect: {global_dep} {router_dep}\"\n\n\n@di_websocket_endpoint.on_close\nasync def di_websocket_on_close(websocket, global_dependencies=None):\n    global_dep = global_dependencies.get(\"GLOBAL_DEPENDENCY\") if global_dependencies else \"MISSING GLOBAL\"\n    return f\"close: {global_dep}\"\n\n\n# --- WebSocket echo endpoint for large payload testing (#1269) ---\n@app.websocket(\"/web_socket_echo\")\nasync def echo_websocket_endpoint(websocket):\n    try:\n        while True:\n            msg = await websocket.receive_text()\n            await websocket.send_text(msg)\n    except WebSocketDisconnect:\n        pass\n\n\n@echo_websocket_endpoint.on_connect\ndef echo_websocket_on_connect(websocket):\n    return \"\"\n\n\n@echo_websocket_endpoint.on_close\ndef echo_websocket_on_close(websocket):\n    return \"\"\n\n\n# --- WebSocket with empty returns ---\n@app.websocket(\"/web_socket_empty_returns\")\nasync def empty_websocket_endpoint(websocket):\n    try:\n        while True:\n            await websocket.receive_text()\n            # No response sent\n    except WebSocketDisconnect:\n        pass\n\n\n@empty_websocket_endpoint.on_connect\nasync def empty_websocket_on_connect(websocket):\n    \"\"\"Test async handler with no return\"\"\"\n    pass\n\n\n@empty_websocket_endpoint.on_close\nasync def empty_websocket_on_close(websocket):\n    \"\"\"Test async handler with explicit None return\"\"\"\n    return None\n\n\n# ===== Lifecycle handlers =====\n\n\nasync def startup_handler():\n    print(\"Starting up\")\n\n\n@app.shutdown_handler\ndef shutdown_handler():\n    print(\"Shutting down\")\n\n\n# ===== Middlewares =====\n\n# --- Global ---\n\n\n@app.before_request()\ndef global_before_request(request: Request):\n    request.headers.set(\"global_before\", \"global_before_request\")\n    return request\n\n\n@app.after_request()\ndef global_after_request(response: Response):\n    response.headers.set(\"global_after\", \"global_after_request\")\n    return response\n\n\n@app.get(\"/sync/global/middlewares\")\ndef sync_global_middlewares(request: Request):\n    print(request.headers)\n    print(request.headers.get(\"txt\"))\n    print(request.headers[\"txt\"])\n    assert \"global_before\" in request.headers\n    assert request.headers.get(\"global_before\") == \"global_before_request\"\n    return \"sync global middlewares\"\n\n\n# --- Route specific ---\n\n\n@app.before_request(\"/sync/middlewares\")\ndef sync_before_request(request: Request):\n    request.headers.set(\"before\", \"sync_before_request\")\n    return request\n\n\n@app.after_request(\"/sync/middlewares\")\ndef sync_after_request(response: Response):\n    response.headers.set(\"after\", \"sync_after_request\")\n    response.description = response.description + \" after\"\n    return response\n\n\n@app.get(\"/sync/middlewares\")\ndef sync_middlewares(request: Request):\n    assert \"before\" in request.headers\n    assert request.headers.get(\"before\") == \"sync_before_request\"\n    assert request.ip_addr == \"127.0.0.1\"\n    return \"sync middlewares\"\n\n\n@app.before_request(\"/async/middlewares\")\nasync def async_before_request(request: Request):\n    request.headers.set(\"before\", \"async_before_request\")\n    return request\n\n\n@app.after_request(\"/async/middlewares\")\nasync def async_after_request(response: Response):\n    response.headers.set(\"after\", \"async_after_request\")\n    response.description = response.description + \" after\"\n    return response\n\n\n@app.get(\"/async/middlewares\")\nasync def async_middlewares(request: Request):\n    assert \"before\" in request.headers\n    assert request.headers.get(\"before\") == \"async_before_request\"\n    assert request.ip_addr == \"127.0.0.1\"\n    return \"async middlewares\"\n\n\n@app.before_request(\"/sync/middlewares/401\")\ndef sync_before_request_401():\n    return Response(401, Headers({}), \"sync before request 401\")\n\n\n@app.get(\"/sync/middlewares/401\")\ndef sync_middlewares_401():\n    pass\n\n\n# ===== Routes =====\n\n# --- GET ---\n\n# Hello world\n\napp.inject(RouterDependency=\"Router Dependency\")\n\n\n@app.get(\"/\", openapi_name=\"Index\")\nasync def hello_world(r):\n    \"\"\"\n    Get hello world\n    \"\"\"\n    return \"Hello, world!\"\n\n\n@app.get(\"/trailing\")\ndef trailing_slash(request):\n    return \"Trailing slash test successful!\"\n\n\n@app.get(\"/sync/str\")\ndef sync_str_get():\n    return \"sync str get\"\n\n\n@app.get(\"/async/str\")\nasync def async_str_get():\n    return \"async str get\"\n\n\n@app.get(\"/sync/str/const\", const=True)\ndef sync_str_const_get():\n    return \"sync str const get\"\n\n\n@app.get(\"/async/str/const\", const=True)\nasync def async_str_const_get():\n    return \"async str const get\"\n\n\n# dict\n\n\n@app.get(\"/sync/dict\")\ndef sync_dict_get():\n    return Response(\n        status_code=200,\n        description=\"sync dict get\",\n        headers={\"sync\": \"dict\"},\n    )\n\n\n@app.get(\"/async/dict\")\nasync def async_dict_get():\n    return Response(\n        status_code=200,\n        description=\"async dict get\",\n        headers={\"async\": \"dict\"},\n    )\n\n\n@app.get(\"/sync/dict/const\", const=True)\ndef sync_dict_const_get():\n    return Response(\n        status_code=200,\n        description=\"sync dict const get\",\n        headers={\"sync_const\": \"dict\"},\n    )\n\n\n@app.get(\"/async/dict/const\", const=True)\nasync def async_dict_const_get():\n    return Response(\n        status_code=200,\n        description=\"async dict const get\",\n        headers={\"async_const\": \"dict\"},\n    )\n\n\n# Response\n\n\n@app.get(\"/sync/response\")\ndef sync_response_get():\n    return Response(200, Headers({\"sync\": \"response\"}), \"sync response get\")\n\n\n@app.get(\"/async/response\")\nasync def async_response_get():\n    return Response(200, Headers({\"async\": \"response\"}), \"async response get\")\n\n\n@app.get(\"/sync/response/const\", const=True)\ndef sync_response_const_get():\n    return Response(200, Headers({\"sync_const\": \"response\"}), \"sync response const get\")\n\n\n@app.get(\"/async/response/const\", const=True)\nasync def async_response_const_get():\n    return Response(200, Headers({\"async_const\": \"response\"}), \"async response const get\")\n\n\n# Binary\n\n\n@app.get(\"/sync/octet\")\ndef sync_octet_get():\n    return b\"sync octet\"\n\n\n@app.get(\"/async/octet\")\nasync def async_octet_get():\n    return b\"async octet\"\n\n\n@app.get(\"/sync/octet/response\")\ndef sync_octet_response_get():\n    return Response(\n        status_code=200,\n        headers=Headers({\"Content-Type\": \"application/octet-stream\"}),\n        description=\"sync octet response\",\n    )\n\n\n@app.get(\"/async/octet/response\")\nasync def async_octet_response_get():\n    return Response(\n        status_code=200,\n        headers=Headers({\"Content-Type\": \"application/octet-stream\"}),\n        description=\"async octet response\",\n    )\n\n\n# JSON\n\n\n@app.get(\"/sync/json\")\ndef sync_json_get():\n    return jsonify({\"sync json get\": \"json\"})\n\n\n@app.get(\"/async/json\")\nasync def async_json_get():\n    return jsonify({\"async json get\": \"json\"})\n\n\n# JSON List (auto-serialized without explicit jsonify)\n\n\n@app.get(\"/sync/json/list\")\ndef sync_json_list_get():\n    return [\n        {\"id\": 1, \"title\": \"First Post\", \"published\": True},\n        {\"id\": 2, \"title\": \"Draft Post\", \"published\": False},\n        {\"id\": 3, \"title\": \"Latest Post\", \"published\": True},\n    ]\n\n\n@app.get(\"/async/json/list\")\nasync def async_json_list_get():\n    return [\n        {\"id\": 1, \"title\": \"First Post\", \"published\": True},\n        {\"id\": 2, \"title\": \"Draft Post\", \"published\": False},\n        {\"id\": 3, \"title\": \"Latest Post\", \"published\": True},\n    ]\n\n\n@app.get(\"/sync/json/list/empty\")\ndef sync_json_list_empty_get():\n    return []\n\n\n@app.get(\"/async/json/list/empty\")\nasync def async_json_list_empty_get():\n    return []\n\n\n@app.get(\"/sync/json/list/primitives\")\ndef sync_json_list_primitives_get():\n    return [1, 2, 3, \"four\", True, None]\n\n\n@app.get(\"/async/json/list/primitives\")\nasync def async_json_list_primitives_get():\n    return [1, 2, 3, \"four\", True, None]\n\n\n# JSON Dict (auto-serialized without explicit jsonify)\n\n\n@app.get(\"/sync/json/dict\")\ndef sync_json_dict_get():\n    return {\"message\": \"sync dict\", \"count\": 42, \"active\": True}\n\n\n@app.get(\"/async/json/dict\")\nasync def async_json_dict_get():\n    return {\"message\": \"async dict\", \"count\": 42, \"active\": True}\n\n\n@app.get(\"/sync/json/const\", const=True)\ndef sync_json_const_get():\n    return jsonify({\"sync json const get\": \"json\"})\n\n\n@app.get(\"/async/json/const\", const=True)\nasync def async_json_const_get():\n    return jsonify({\"async json const get\": \"json\"})\n\n\n# Param\n\n\n@app.get(\"/sync/param/:id\")\ndef sync_param(request: Request):\n    id = request.path_params[\"id\"]\n    return id\n\n\n@app.get(\"/async/param/:id\")\nasync def async_param(request: Request):\n    id = request.path_params[\"id\"]\n    return id\n\n\n@app.get(\"/sync/extra/*extra\")\ndef sync_param_extra(request: Request):\n    extra = request.path_params[\"extra\"]\n    return extra\n\n\n@app.get(\"/async/extra/*extra\")\nasync def async_param_extra(request: Request):\n    extra = request.path_params[\"extra\"]\n    return extra\n\n\n# Request Info\n\n\n@app.get(\"/sync/http/param\")\ndef sync_http_param(request: Request):\n    return jsonify(\n        {\n            \"url\": {\n                \"scheme\": request.url.scheme,\n                \"host\": request.url.host,\n                \"path\": request.url.path,\n            },\n            \"method\": request.method,\n        }\n    )\n\n\n@app.get(\"/async/http/param\")\nasync def async_http_param(request: Request):\n    return jsonify(\n        {\n            \"url\": {\n                \"scheme\": request.url.scheme,\n                \"host\": request.url.host,\n                \"path\": request.url.path,\n            },\n            \"method\": request.method,\n        }\n    )\n\n\n# HTML serving\n\n\n@app.get(\"/sync/serve/html\")\ndef sync_serve_html():\n    html_file = os.path.join(current_file_path, \"index.html\")\n    return serve_html(html_file)\n\n\n@app.get(\"/async/serve/html\")\nasync def async_serve_html():\n    html_file = os.path.join(current_file_path, \"index.html\")\n    return serve_html(html_file)\n\n\n# Template\n\n\n@app.get(\"/sync/template\")\ndef sync_template_render():\n    context = {\"framework\": \"Robyn\", \"templating_engine\": \"Jinja2\"}\n    template = jinja_template.render_template(template_name=\"test.html\", **context)\n    return template\n\n\n@app.get(\"/async/template\")\nasync def async_template_render():\n    context = {\"framework\": \"Robyn\", \"templating_engine\": \"Jinja2\"}\n    template = jinja_template.render_template(template_name=\"test.html\", **context)\n    return template\n\n\n# File download\n\n\n@app.get(\"/sync/file/download\")\ndef sync_file_download():\n    file_path = os.path.join(current_file_path, \"downloads\", \"test.txt\")\n    return serve_file(file_path)\n\n\n@app.get(\"/async/file/download\")\nasync def file_download_async():\n    file_path = os.path.join(current_file_path, \"downloads\", \"test.txt\")\n    return serve_file(file_path)\n\n\n# Multipart file\n\n\n@app.post(\"/sync/multipart-file\")\ndef sync_multipart_file(request: Request):\n    files = request.files\n    file_names = files.keys()\n    return {\"file_names\": list(file_names)}\n\n\n# Queries\n\n\n@app.get(\"/sync/queries\")\ndef sync_queries(request: Request):\n    query_data = request.query_params.to_dict()\n    return jsonify(query_data)\n\n\n@app.get(\"/async/queries\")\nasync def async_query(request: Request):\n    query_data = request.query_params.to_dict()\n    return jsonify(query_data)\n\n\n# Status code\n\n\n@app.get(\"/404\")\ndef return_404():\n    return Response(status_code=404, description=\"not found\", headers={\"Content-Type\": \"text\"})\n\n\n@app.get(\"/202\")\ndef return_202():\n    return Response(status_code=202, description=\"hello\", headers={\"Content-Type\": \"text\"})\n\n\n@app.get(\"/307\")\nasync def redirect():\n    return Response(\n        status_code=307,\n        description=\"\",\n        headers={\"Location\": \"redirect_route\"},\n    )\n\n\n@app.get(\"/redirect_route\")\nasync def redirect_route():\n    return \"This is the redirected route\"\n\n\n@app.get(\"/sync/raise\")\ndef sync_raise():\n    raise Exception()\n\n\n@app.get(\"/async/raise\")\nasync def async_raise():\n    raise Exception()\n\n\n# cookie\n@app.get(\"/cookie\")\ndef cookie():\n    response = Response(status_code=200, headers=Headers({}), description=\"test cookies\")\n    response.set_cookie(key=\"fakesession\", value=\"fake-cookie-session-value\")\n\n    return response\n\n\n@app.get(\"/cookie/multiple\")\ndef multiple_cookies():\n    response = Response(status_code=200, headers=Headers({}), description=\"test multiple cookies\")\n    response.set_cookie(key=\"session\", value=\"abc123\")\n    response.set_cookie(key=\"theme\", value=\"dark\")\n    return response\n\n\n@app.get(\"/cookie/attributes\")\ndef cookie_with_attributes():\n    response = Response(status_code=200, headers=Headers({}), description=\"test cookie attributes\")\n    response.set_cookie(\n        key=\"secure_session\",\n        value=\"secret123\",\n        path=\"/\",\n        http_only=True,\n        secure=True,\n        same_site=\"Strict\",\n        max_age=3600,\n    )\n    return response\n\n\n@app.get(\"/cookie/overwrite\")\ndef cookie_overwrite():\n    response = Response(status_code=200, headers=Headers({}), description=\"test cookie overwrite\")\n    response.set_cookie(key=\"session\", value=\"first-value\")\n    response.set_cookie(key=\"session\", value=\"final-value\")  # Should overwrite\n    return response\n\n\n# --- POST ---\n\n# dict\n\n\n@app.post(\"/sync/dict\")\ndef sync_dict_post():\n    return Response(\n        status_code=200,\n        description=\"sync dict post\",\n        headers={\"sync\": \"dict\"},\n    )\n\n\n@app.post(\"/async/dict\")\nasync def async_dict_post():\n    return Response(\n        status_code=200,\n        description=\"async dict post\",\n        headers={\"async\": \"dict\"},\n    )\n\n\n# Body\n\n\n@app.post(\"/sync/body\")\ndef sync_body_post(request: Request):\n    return request.body\n\n\n@app.post(\"/async/body\")\nasync def async_body_post(request: Request):\n    return request.body\n\n\n@app.post(\"/sync/form_data\")\ndef sync_form_data(request: Request):\n    return request.headers[\"Content-Type\"]\n\n\n# JSON Request\n\n\n@app.post(\"/sync/request_json\")\ndef sync_json_post(request: Request):\n    try:\n        return type(request.json())\n    except ValueError:\n        return None\n\n\n@app.post(\"/async/request_json\")\nasync def async_json_post(request: Request):\n    try:\n        return type(request.json())\n    except ValueError:\n        return None\n\n\n@app.post(\"/sync/request_json/key\")\nasync def request_json(request: Request):\n    json = request.json()\n    return json[\"key\"]\n\n\n# JSON type preservation test\n@app.post(\"/sync/request_json/types\")\ndef sync_json_types(request: Request):\n    \"\"\"Returns the JSON data with Python type names for verification\"\"\"\n    data = request.json()\n    result = {}\n    for key, value in data.items():\n        result[key] = {\n            \"value\": value,\n            \"type\": type(value).__name__,\n        }\n    return result\n\n\n@app.post(\"/async/request_json/types\")\nasync def async_json_types(request: Request):\n    \"\"\"Returns the JSON data with Python type names for verification\"\"\"\n    data = request.json()\n    result = {}\n    for key, value in data.items():\n        result[key] = {\n            \"value\": value,\n            \"type\": type(value).__name__,\n        }\n    return result\n\n\n# JSON top-level array test (Issue #1145)\n@app.post(\"/sync/request_json/array\")\ndef sync_json_array(request: Request):\n    \"\"\"Returns the parsed JSON when the body is a top-level array\"\"\"\n    data = request.json()\n    return {\"parsed\": data, \"type\": type(data).__name__}\n\n\n@app.post(\"/async/request_json/array\")\nasync def async_json_array(request: Request):\n    \"\"\"Returns the parsed JSON when the body is a top-level array\"\"\"\n    data = request.json()\n    return {\"parsed\": data, \"type\": type(data).__name__}\n\n\n# --- PUT ---\n\n# dict\n\n\n@app.put(\"/sync/dict\")\ndef sync_dict_put():\n    return Response(\n        status_code=200,\n        description=\"sync dict put\",\n        headers={\"sync\": \"dict\"},\n    )\n\n\n@app.put(\"/async/dict\")\nasync def async_dict_put():\n    return Response(\n        status_code=200,\n        description=\"async dict put\",\n        headers={\"async\": \"dict\"},\n    )\n\n\n# Body\n\n\n@app.put(\"/sync/body\")\ndef sync_body_put(request: Request):\n    return request.body\n\n\n@app.put(\"/async/body\")\nasync def async_body_put(request: Request):\n    return request.body\n\n\n# --- DELETE ---\n\n# dict\n\n\n@app.delete(\"/sync/dict\")\ndef sync_dict_delete():\n    return Response(\n        status_code=200,\n        description=\"sync dict delete\",\n        headers={\"sync\": \"dict\"},\n    )\n\n\n@app.delete(\"/async/dict\")\nasync def async_dict_delete():\n    return Response(\n        status_code=200,\n        description=\"async dict delete\",\n        headers={\"async\": \"dict\"},\n    )\n\n\n# Body\n\n\n@app.delete(\"/sync/body\")\ndef sync_body_delete(request: Request):\n    print(request.body)\n    return request.body\n\n\n@app.delete(\"/async/body\")\nasync def async_body_delete(request: Request):\n    return request.body\n\n\n# --- PATCH ---\n\n# dict\n\n\n@app.patch(\"/sync/dict\")\ndef sync_dict_patch():\n    return Response(\n        status_code=200,\n        description=\"sync dict patch\",\n        headers={\"sync\": \"dict\"},\n    )\n\n\n@app.patch(\"/async/dict\")\nasync def async_dict_patch():\n    return Response(\n        status_code=200,\n        description=\"async dict patch\",\n        # need to fix this\n        headers={\"async\": \"dict\"},\n    )\n\n\n# Body\n\n\n@app.patch(\"/sync/body\")\ndef sync_body_patch(request: Request):\n    return request.body\n\n\n@app.patch(\"/async/body\")\nasync def async_body_patch(request: Request):\n    return request.body\n\n\n# ==== Exception Handling ====\n\n\n@app.exception\ndef handle_exception(error):\n    return Response(status_code=500, description=f\"error msg: {error}\", headers={})\n\n\n@app.get(\"/sync/exception/get\")\ndef sync_exception_get():\n    raise ValueError(\"value error\")\n\n\n@app.get(\"/async/exception/get\")\nasync def async_exception_get():\n    raise ValueError(\"value error\")\n\n\n@app.put(\"/sync/exception/put\")\ndef sync_exception_put(request: Request):\n    raise ValueError(\"value error\")\n\n\n@app.put(\"/async/exception/put\")\nasync def async_exception_put(request: Request):\n    raise ValueError(\"value error\")\n\n\n@app.post(\"/sync/exception/post\")\ndef sync_exception_post(request: Request):\n    raise ValueError(\"value error\")\n\n\n@app.post(\"/async/exception/post\")\nasync def async_exception_post(request: Request):\n    raise ValueError(\"value error\")\n\n\n# ===== Authentication =====\n\n\n@app.get(\"/sync/auth\", auth_required=True)\ndef sync_auth(request: Request):\n    assert request.identity is not None\n    assert request.identity.claims == {\"key\": \"value\"}\n    return \"authenticated\"\n\n\n@app.get(\"/async/auth\", auth_required=True)\nasync def async_auth(request: Request):\n    assert request.identity is not None\n    assert request.identity.claims == {\"key\": \"value\"}\n    return \"authenticated\"\n\n\n# ===== Main =====\n\n\ndef sync_without_decorator():\n    return \"Success!\"\n\n\nasync def async_without_decorator():\n    return \"Success!\"\n\n\napp.add_route(\"GET\", \"/sync/get/no_dec\", sync_without_decorator)\napp.add_route(\"PUT\", \"/sync/put/no_dec\", sync_without_decorator)\napp.add_route(\"POST\", \"/sync/post/no_dec\", sync_without_decorator)\napp.add_route(\"GET\", \"/async/get/no_dec\", async_without_decorator)\napp.add_route(\"PUT\", \"/async/put/no_dec\", async_without_decorator)\napp.add_route(\"POST\", \"/async/post/no_dec\", async_without_decorator)\n\n# ===== Dependency Injection =====\n\nGLOBAL_DEPENDENCY = \"GLOBAL DEPENDENCY\"\nROUTER_DEPENDENCY = \"ROUTER DEPENDENCY\"\n\napp.inject_global(GLOBAL_DEPENDENCY=GLOBAL_DEPENDENCY)\napp.inject(ROUTER_DEPENDENCY=ROUTER_DEPENDENCY)\n\n\n@app.get(\"/sync/global_di\")\ndef sync_global_di(request, router_dependencies, global_dependencies):\n    return global_dependencies[\"GLOBAL_DEPENDENCY\"]\n\n\n@app.get(\"/sync/router_di\")\ndef sync_router_di(request, router_dependencies):\n    return router_dependencies[\"ROUTER_DEPENDENCY\"]\n\n\n# ===== Split request body =====\n\n\n@app.get(\"/sync/split_request_untyped/query_params\")\ndef sync_split_request_untyped_basic(query_params):\n    return query_params.to_dict()\n\n\n@app.get(\"/async/split_request_untyped/query_params\")\nasync def async_split_request_untyped_basic(query_params):\n    return query_params.to_dict()\n\n\n@app.get(\"/sync/split_request_untyped/headers\")\ndef sync_split_request_untyped_headers(headers):\n    return headers.get(\"server\")\n\n\n@app.get(\"/async/split_request_untyped/headers\")\nasync def async_split_request_untyped_headers(headers):\n    return headers.get(\"server\")\n\n\n@app.get(\"/sync/split_request_untyped/path_params/:id\")\ndef sync_split_request_untyped_path_params(path_params):\n    return path_params\n\n\n@app.get(\"/async/split_request_untyped/path_params/:id\")\nasync def async_split_request_untyped_path_params(path_params):\n    return path_params\n\n\n@app.get(\"/sync/split_request_untyped/method\")\ndef sync_split_request_untyped_method(method):\n    return method\n\n\n@app.get(\"/async/split_request_untyped/method\")\nasync def async_split_request_untyped_method(method):\n    return method\n\n\n@app.post(\"/sync/split_request_untyped/body\")\ndef sync_split_request_untyped_body(body):\n    return body\n\n\n@app.post(\"/async/split_request_untyped/body\")\nasync def async_split_request_untyped_body(body):\n    return body\n\n\n@app.post(\"/sync/split_request_untyped/combined\")\ndef sync_split_request_untyped_combined(body, query_params, method, url, headers):\n    return {\n        \"body\": body,\n        \"query_params\": query_params.to_dict(),\n        \"method\": method,\n        \"url\": url.path,\n        \"headers\": headers.get(\"server\"),\n    }\n\n\n@app.post(\"/async/split_request_untyped/combined\")\nasync def async_split_request_untyped_combined(body, query_params, method, url, headers):\n    return {\n        \"body\": body,\n        \"query_params\": query_params.to_dict(),\n        \"method\": method,\n        \"url\": url.path,\n        \"headers\": headers.get(\"server\"),\n    }\n\n\n@app.get(\"/sync/split_request_typed/query_params\")\ndef sync_split_request_basic(query_data: QueryParams):\n    return query_data.to_dict()\n\n\n@app.get(\"/async/split_request_typed/query_params\")\nasync def async_split_request_basic(query_data: QueryParams):\n    return query_data.to_dict()\n\n\n@app.get(\"/sync/split_request_typed/headers\")\ndef sync_split_request_headers(request_headers: Headers):\n    return request_headers.get(\"server\")\n\n\n@app.get(\"/async/split_request_typed/headers\")\nasync def async_split_request_headers(request_headers: Headers):\n    return request_headers.get(\"server\")\n\n\n@app.get(\"/sync/split_request_typed/path_params/:id\")\ndef sync_split_request_path_params(path_data: PathParams):\n    return path_data\n\n\n@app.get(\"/async/split_request_typed/path_params/:id\")\nasync def async_split_request_path_params(path_data: PathParams):\n    return path_data\n\n\n@app.get(\"/sync/split_request_typed/method\")\ndef sync_split_request_method(request_method: Method):\n    return request_method\n\n\n@app.get(\"/async/split_request_typed/method\")\nasync def async_split_request_method(request_method: Method):\n    return request_method\n\n\n@app.post(\"/sync/split_request_typed/body\")\ndef sync_split_request_body(request_body: Body):\n    return request_body\n\n\n@app.post(\"/async/split_request_typed/body\")\nasync def async_split_request_body(request_body: Body):\n    return request_body\n\n\n@app.post(\"/sync/split_request_typed/combined\")\ndef sync_split_request_combined(\n    request_body: Body,\n    query_data: QueryParams,\n    request_method: Method,\n    request_url: Url,\n    request_headers: Headers,\n):\n    return {\n        \"body\": request_body,\n        \"query_params\": query_data.to_dict(),\n        \"method\": request_method,\n        \"url\": request_url.path,\n        \"headers\": request_headers.get(\"server\"),\n    }\n\n\n@app.post(\"/async/split_request_typed/combined\")\nasync def async_split_request_combined(\n    request_body: Body,\n    query_data: QueryParams,\n    request_method: Method,\n    request_url: Url,\n    request_headers: Headers,\n):\n    return {\n        \"body\": request_body,\n        \"query_params\": query_data.to_dict(),\n        \"method\": request_method,\n        \"url\": request_url.path,\n        \"headers\": request_headers.get(\"server\"),\n    }\n\n\n@app.post(\"/sync/split_request_typed_untyped/combined\")\ndef sync_split_request_typed_untyped_combined(\n    query_params,\n    request_method: Method,\n    request_body: Body,\n    url: Url,\n    headers: Headers,\n):\n    return {\n        \"body\": request_body,\n        \"query_params\": query_params.to_dict(),\n        \"method\": request_method,\n        \"url\": url.path,\n        \"headers\": headers.get(\"server\"),\n    }\n\n\n@app.post(\"/async/split_request_typed_untyped/combined\")\nasync def async_split_request_typed_untyped_combined(\n    query_params,\n    request_method: Method,\n    request_body: Body,\n    url: Url,\n    headers: Headers,\n):\n    return {\n        \"body\": request_body,\n        \"query_params\": query_params.to_dict(),\n        \"method\": request_method,\n        \"url\": url.path,\n        \"headers\": headers.get(\"server\"),\n    }\n\n\n@app.post(\"/sync/split_request_typed_untyped/combined/failure\")\ndef sync_split_request_typed_untyped_combined_failure(query_params, request_method: Method, request_body: Body, url: Url, headers: Headers, vishnu):\n    return {\n        \"body\": request_body,\n        \"query_params\": query_params.to_dict(),\n        \"method\": request_method,\n        \"url\": url.path,\n        \"headers\": headers.get(\"server\"),\n        \"vishnu\": vishnu,\n    }\n\n\n@app.post(\"/async/split_request_typed_untyped/combined/failure\")\nasync def async_split_request_typed_untyped_combined_failure(query_params, request_method: Method, request_body: Body, url: Url, headers: Headers, vishnu):\n    return {\n        \"body\": request_body,\n        \"query_params\": query_params.to_dict(),\n        \"method\": request_method,\n        \"url\": url.path,\n        \"headers\": headers.get(\"server\"),\n        \"vishnu\": vishnu,\n    }\n\n\n@app.get(\"/openapi_test\", openapi_tags=[\"test tag\"])\ndef sample_openapi_endpoint():\n    \"\"\"Get openapi\"\"\"\n    return 200\n\n\nclass Initial(Body):\n    is_present: bool\n    letter: Optional[str]\n\n\nclass FullName(Body):\n    first: str\n    second: str\n    initial: Initial\n\n\nclass CreateItemBody(Body):\n    name: FullName\n    description: str\n    price: float\n    tax: float\n\n\nclass CreateItemResponse(JSONResponse):\n    success: bool\n    items_changed: int\n\n\nclass CreateItemQueryParamsParams(QueryParams):\n    required: bool\n\n\n@app.post(\"/openapi_request_body\")\ndef create_item(request, body: CreateItemBody, query: CreateItemQueryParamsParams) -> CreateItemResponse:\n    return CreateItemResponse(success=True, items_changed=2)\n\n\n# ===== JsonBody Routes =====\n\n\nclass TemperatureInput(JsonBody):\n    fahrenheit: float\n\n\n@app.post(\"/sync/json_body/bare\")\ndef sync_json_body_bare(data: JsonBody):\n    \"\"\"Bare JsonBody - receives parsed JSON dict\"\"\"\n    return data\n\n\n@app.post(\"/async/json_body/bare\")\nasync def async_json_body_bare(data: JsonBody):\n    \"\"\"Bare JsonBody - receives parsed JSON dict\"\"\"\n    return data\n\n\n@app.post(\"/sync/json_body/typed\")\ndef sync_json_body_typed(data: TemperatureInput):\n    \"\"\"Typed JsonBody - receives parsed JSON dict, docs show schema\"\"\"\n    fahrenheit = data.get(\"fahrenheit\", 0)\n    celsius = (float(fahrenheit) - 32) * 5 / 9\n    return {\"celsius\": celsius}\n\n\n@app.post(\"/async/json_body/typed\")\nasync def async_json_body_typed(data: TemperatureInput):\n    \"\"\"Typed JsonBody - receives parsed JSON dict, docs show schema\"\"\"\n    fahrenheit = data.get(\"fahrenheit\", 0)\n    celsius = (float(fahrenheit) - 32) * 5 / 9\n    return {\"celsius\": celsius}\n\n\n@app.post(\"/openapi_json_body\")\ndef openapi_json_body_endpoint(request: Request, data: TemperatureInput) -> dict:\n    \"\"\"Convert fahrenheit to celsius using JsonBody\"\"\"\n    return {\"celsius\": (float(data.get(\"fahrenheit\", 0)) - 32) * 5 / 9}\n\n\n# ===== Server-Sent Events (SSE) Routes =====\n\n\n@app.get(\"/sse/basic\")\ndef sse_basic(request):\n    \"\"\"Basic SSE endpoint that sends 3 messages\"\"\"\n\n    def event_generator():\n        for i in range(3):\n            yield f\"data: Test message {i}\\n\\n\"\n\n    return SSEResponse(event_generator())\n\n\n@app.get(\"/sse/formatted\")\ndef sse_formatted(request):\n    \"\"\"SSE endpoint using SSEMessage formatter\"\"\"\n\n    def event_generator():\n        for i in range(3):\n            yield SSEMessage(f\"Formatted message {i}\", event=\"test\", id=str(i))\n\n    return SSEResponse(event_generator())\n\n\n@app.get(\"/sse/json\")\ndef sse_json(request):\n    \"\"\"SSE endpoint that sends JSON data\"\"\"\n\n    def event_generator():\n        for i in range(3):\n            data = {\"id\": i, \"message\": f\"JSON message {i}\", \"type\": \"test\"}\n            yield f\"data: {json.dumps(data)}\\n\\n\"\n\n    return SSEResponse(event_generator())\n\n\n@app.get(\"/sse/named_events\")\ndef sse_named_events(request):\n    \"\"\"SSE endpoint with different event types\"\"\"\n\n    def event_generator():\n        events = [(\"start\", \"Test started\"), (\"progress\", \"Test in progress\"), (\"end\", \"Test completed\")]\n        for event_type, message in events:\n            yield SSEMessage(message, event=event_type)\n\n    return SSEResponse(event_generator())\n\n\n@app.get(\"/sse/async\")\nasync def sse_async(request):\n    \"\"\"Async SSE endpoint with true async generator support\"\"\"\n\n    async def async_event_generator():\n        \"\"\"True async generator for SSE events\"\"\"\n        for i in range(3):\n            await asyncio.sleep(0.1)  # Simulate async work\n            yield SSEMessage(f\"Async message {i}\", event=\"async\", id=str(i))\n\n    return SSEResponse(async_event_generator())\n\n\n@app.get(\"/sse/streaming_sync\")\ndef sse_streaming_sync(request):\n    \"\"\"SSE endpoint to test real-time sync streaming\"\"\"\n\n    def streaming_generator():\n        for i in range(3):\n            yield SSEMessage(f\"Streaming sync message {i} at {time.strftime('%H:%M:%S')}\", id=str(i))\n            time.sleep(0.5)  # 500ms delay to test streaming\n        yield SSEMessage(\"Streaming test completed\", event=\"end\")\n\n    return SSEResponse(streaming_generator())\n\n\n@app.get(\"/sse/streaming_async\")\nasync def sse_streaming_async(request):\n    \"\"\"SSE endpoint to test real-time async streaming\"\"\"\n\n    async def streaming_async_generator():\n        for i in range(3):\n            await asyncio.sleep(0.3)  # 300ms delay\n            yield SSEMessage(f\"Streaming async message {i} at {time.strftime('%H:%M:%S')}\", event=\"async\", id=str(i))\n        yield SSEMessage(\"Async streaming test completed\", event=\"end\")\n\n    return SSEResponse(streaming_async_generator())\n\n\n@app.get(\"/sse/high_frequency\")\ndef sse_high_frequency(request):\n    \"\"\"SSE endpoint to test high frequency streaming\"\"\"\n\n    def high_freq_generator():\n        for i in range(20):\n            yield SSEMessage(f\"Fast message {i}\", id=str(i))\n            time.sleep(0.05)  # 50ms = 20 messages per second\n        yield SSEMessage(\"High frequency test completed\", event=\"end\")\n\n    return SSEResponse(high_freq_generator())\n\n\n@app.get(\"/sse/single\")\ndef sse_single(request):\n    \"\"\"SSE endpoint that sends a single message and closes\"\"\"\n\n    def event_generator():\n        yield \"data: Single message\\n\\n\"\n\n    return SSEResponse(event_generator())\n\n\n@app.get(\"/sse/empty\")\ndef sse_empty(request):\n    \"\"\"SSE endpoint that sends no messages\"\"\"\n\n    def event_generator():\n        return\n        yield  # This will never be reached\n\n    return SSEResponse(event_generator())\n\n\n@app.get(\"/sse/with_headers\")\ndef sse_with_headers(request):\n    \"\"\"SSE endpoint with custom headers\"\"\"\n    headers = Headers({\"X-Custom-Header\": \"custom-value\"})\n\n    def event_generator():\n        yield \"data: Message with custom headers\\n\\n\"\n\n    return SSEResponse(event_generator(), headers=headers)\n\n\n@app.get(\"/sse/status_code\")\ndef sse_status_code(request):\n    \"\"\"SSE endpoint with custom status code\"\"\"\n\n    def event_generator():\n        yield \"data: Message with custom status\\n\\n\"\n\n    return SSEResponse(event_generator(), status_code=201)\n\n\n# ===== Easy Access Query/Path Parameters =====\n\n\n@app.get(\"/easy/sync/:id\")\ndef easy_access_sync(id: int, q: str, page: int = 1):\n    return {\"id\": id, \"q\": q, \"page\": page}\n\n\n@app.get(\"/easy/async/:id\")\nasync def easy_access_async(id: int, q: str, page: int = 1):\n    return {\"id\": id, \"q\": q, \"page\": page}\n\n\n@app.get(\"/easy/sync/optional\")\ndef easy_access_optional_sync(name: str, age: Optional[int] = None):\n    return {\"name\": name, \"age\": age}\n\n\n@app.get(\"/easy/async/optional\")\nasync def easy_access_optional_async(name: str, age: Optional[int] = None):\n    return {\"name\": name, \"age\": age}\n\n\n@app.get(\"/easy/sync/list\")\ndef easy_access_list_sync(tag: List[str]):\n    return {\"tags\": tag}\n\n\n@app.get(\"/easy/async/list\")\nasync def easy_access_list_async(tag: List[str]):\n    return {\"tags\": tag}\n\n\n@app.get(\"/easy/sync/bool\")\ndef easy_access_bool_sync(active: bool = False):\n    return {\"active\": active}\n\n\n@app.get(\"/easy/async/bool\")\nasync def easy_access_bool_async(active: bool = False):\n    return {\"active\": active}\n\n\n@app.get(\"/easy/sync/mixed/:id\")\ndef easy_access_mixed_sync(request: Request, id: int, q: str = \"\"):\n    return {\"id\": id, \"q\": q, \"method\": request.method}\n\n\n@app.get(\"/easy/async/mixed/:id\")\nasync def easy_access_mixed_async(request: Request, id: int, q: str = \"\"):\n    return {\"id\": id, \"q\": q, \"method\": request.method}\n\n\n@app.get(\"/easy/sync/float\")\ndef easy_access_float_sync(price: float):\n    return {\"price\": price}\n\n\n@app.get(\"/easy/async/float\")\nasync def easy_access_float_async(price: float):\n    return {\"price\": price}\n\n\n# --- WebSocket with easy access query params ---\n@app.websocket(\"/web_socket_easy_access\")\nasync def easy_access_ws_handler(websocket, room: str = \"default\", page: int = 1):\n    try:\n        while True:\n            msg = await websocket.receive_text()\n            await websocket.send_text(f\"room={room} page={page} msg={msg}\")\n    except WebSocketDisconnect:\n        pass\n\n\n@easy_access_ws_handler.on_connect\ndef easy_access_ws_on_connect(websocket, room: str = \"default\"):\n    return f\"connected to {room}\"\n\n\n@easy_access_ws_handler.on_close\ndef easy_access_ws_on_close(websocket, room: str = \"default\"):\n    return f\"left {room}\"\n\n\n# ===== TypedDict Body Routes =====\n\n\nclass TypedDictRequestBody(TypedDict):\n    name: str\n    value: int\n\n\nclass TypedDictResponseBody(TypedDict):\n    result: str\n    count: int\n\n\n@app.post(\"/sync/typeddict/body\", openapi_tags=[\"typeddict\"])\ndef sync_typeddict_body(data: TypedDictRequestBody) -> TypedDictResponseBody:\n    \"\"\"Accept a TypedDict request body and return a TypedDict response\"\"\"\n    return {\"result\": data[\"name\"], \"count\": data[\"value\"]}\n\n\n@app.post(\"/async/typeddict/body\", openapi_tags=[\"typeddict\"])\nasync def async_typeddict_body(data: TypedDictRequestBody) -> TypedDictResponseBody:\n    \"\"\"Accept a TypedDict request body and return a TypedDict response\"\"\"\n    return {\"result\": data[\"name\"], \"count\": data[\"value\"]}\n\n\n@app.post(\"/sync/typeddict/with_request\", openapi_tags=[\"typeddict\"])\ndef sync_typeddict_with_request(request: Request, data: TypedDictRequestBody):\n    \"\"\"TypedDict body alongside Request object\"\"\"\n    return {\"method\": request.method, \"name\": data[\"name\"]}\n\n\n@app.post(\"/async/typeddict/with_request\", openapi_tags=[\"typeddict\"])\nasync def async_typeddict_with_request(request: Request, data: TypedDictRequestBody):\n    \"\"\"TypedDict body alongside Request object\"\"\"\n    return {\"method\": request.method, \"name\": data[\"name\"]}\n\n\n# ===== Pydantic Integration Routes =====\n\nif _HAS_PYDANTIC:\n\n    class UserCreate(PydanticBaseModel):\n        name: str\n        email: str\n        age: int\n        active: bool = True\n\n    class Address(PydanticBaseModel):\n        street: str\n        city: str\n        zip_code: str\n\n    class UserWithAddress(PydanticBaseModel):\n        name: str\n        email: str\n        address: Address\n\n    @app.post(\"/sync/pydantic/user\", openapi_tags=[\"pydantic\"])\n    def sync_pydantic_user(user: UserCreate) -> dict:\n        \"\"\"Create a user with Pydantic validation\"\"\"\n        return {\"name\": user.name, \"email\": user.email, \"age\": user.age, \"active\": user.active}\n\n    @app.post(\"/async/pydantic/user\", openapi_tags=[\"pydantic\"])\n    async def async_pydantic_user(user: UserCreate):\n        return {\"name\": user.name, \"email\": user.email, \"age\": user.age, \"active\": user.active}\n\n    @app.post(\"/sync/pydantic/user_with_request\")\n    def sync_pydantic_user_with_request(request: Request, user: UserCreate):\n        return {\"method\": request.method, \"name\": user.name, \"email\": user.email}\n\n    @app.post(\"/async/pydantic/user_with_request\")\n    async def async_pydantic_user_with_request(request: Request, user: UserCreate):\n        return {\"method\": request.method, \"name\": user.name, \"email\": user.email}\n\n    @app.post(\"/sync/pydantic/nested\", openapi_tags=[\"pydantic\"])\n    def sync_pydantic_nested(data: UserWithAddress) -> dict:\n        \"\"\"Create a user with nested address\"\"\"\n        return {\"name\": data.name, \"city\": data.address.city}\n\n    @app.post(\"/async/pydantic/nested\", openapi_tags=[\"pydantic\"])\n    async def async_pydantic_nested(data: UserWithAddress):\n        return {\"name\": data.name, \"city\": data.address.city}\n\n    @app.put(\"/sync/pydantic/user\")\n    def sync_pydantic_user_put(user: UserCreate):\n        return {\"updated\": True, \"name\": user.name}\n\n    @app.put(\"/async/pydantic/user\")\n    async def async_pydantic_user_put(user: UserCreate):\n        return {\"updated\": True, \"name\": user.name}\n\n    @app.patch(\"/sync/pydantic/user\")\n    def sync_pydantic_user_patch(user: UserCreate):\n        return {\"patched\": True, \"name\": user.name}\n\n    @app.patch(\"/async/pydantic/user\")\n    async def async_pydantic_user_patch(user: UserCreate):\n        return {\"patched\": True, \"name\": user.name}\n\n    @app.delete(\"/sync/pydantic/user\")\n    def sync_pydantic_user_delete(user: UserCreate):\n        return {\"deleted\": True, \"name\": user.name}\n\n    @app.delete(\"/async/pydantic/user\")\n    async def async_pydantic_user_delete(user: UserCreate):\n        return {\"deleted\": True, \"name\": user.name}\n\n    @app.post(\"/sync/pydantic/return_model\", openapi_tags=[\"pydantic\"])\n    def sync_pydantic_return_model(user: UserCreate) -> UserCreate:\n        \"\"\"Return the validated Pydantic model directly\"\"\"\n        return user\n\n    @app.post(\"/async/pydantic/return_model\", openapi_tags=[\"pydantic\"])\n    async def async_pydantic_return_model(user: UserCreate) -> UserCreate:\n        return user\n\n    @app.post(\"/sync/pydantic/return_list\", openapi_tags=[\"pydantic\"])\n    def sync_pydantic_return_list(user: UserCreate) -> list[UserCreate]:\n        \"\"\"Return a list of Pydantic models\"\"\"\n        return [user, user]\n\n    @app.post(\"/async/pydantic/return_list\", openapi_tags=[\"pydantic\"])\n    async def async_pydantic_return_list(user: UserCreate) -> list[UserCreate]:\n        return [user, user]\n\n\ndef main():\n    app.set_response_header(\"server\", \"robyn\")\n    app.serve_directory(\n        route=\"/test_dir\",\n        directory_path=os.path.join(current_file_path, \"build\"),\n        index_file=\"index.html\",\n    )\n    # Serving static files at /static from ./integration_tests.\n    app.serve_directory(\n        route=\"/static\",\n        directory_path=str(current_file_path),\n    )\n    app.startup_handler(startup_handler)\n    app.include_router(sub_router)\n    app.include_router(di_subrouter)\n    app.include_router(static_router)\n\n    class BasicAuthHandler(AuthenticationHandler):\n        def authenticate(self, request: Request) -> Optional[Identity]:\n            token = self.token_getter.get_token(request)\n            if token is not None:\n                # Useless but we call the set_token method for testing purposes\n                self.token_getter.set_token(request, token)\n            if token == \"valid\":\n                return Identity(claims={\"key\": \"value\"})\n            return None\n\n    app.configure_authentication(BasicAuthHandler(token_getter=BearerGetter()))\n\n    # Read port from environment variable if set, otherwise default to 8080\n    port = int(os.getenv(\"ROBYN_PORT\", \"8080\"))\n    app.start(port=port, _check_port=False)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "integration_tests/build/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Document</title>\n  </head>\n  <body>\n    <p>Hello world</p>\n  </body>\n</html>\n"
  },
  {
    "path": "integration_tests/conftest.py",
    "content": "import os\nimport pathlib\nimport platform\nimport signal\nimport socket\nimport subprocess\nimport time\nfrom typing import List\n\nimport pytest\n\nfrom integration_tests.helpers.network_helpers import get_network_host\n\n\ndef spawn_process(command: List[str]) -> subprocess.Popen:\n    if platform.system() == \"Windows\":\n        command[0] = \"python\"\n        process = subprocess.Popen(command, shell=True, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)\n        return process\n    process = subprocess.Popen(command, preexec_fn=os.setsid)\n    return process\n\n\ndef kill_process(process: subprocess.Popen) -> None:\n    if platform.system() == \"Windows\":\n        process.send_signal(signal.CTRL_BREAK_EVENT)\n        process.kill()\n        return\n\n    try:\n        os.killpg(os.getpgid(process.pid), signal.SIGKILL)\n    except ProcessLookupError:\n        pass\n\n\ndef start_server(domain: str, port: int, is_dev: bool = False) -> subprocess.Popen:\n    \"\"\"\n    Call this method to wait for the server to start\n    \"\"\"\n    # Start the server\n    current_file_path = pathlib.Path(__file__).parent.resolve()\n    base_routes = os.path.join(current_file_path, \"./base_routes.py\")\n    command = [\"python3\", base_routes]\n    if is_dev:\n        command.append(\"--dev\")\n\n    # Ensure environment variables are properly set for the subprocess\n    env = os.environ.copy()\n    env[\"ROBYN_HOST\"] = domain\n    env[\"ROBYN_PORT\"] = str(port)\n\n    process = spawn_process(command)\n\n    # Wait for the server to be reachable\n    timeout = 15  # The maximum time we will wait for an answer\n    start_time = time.time()\n    while True:\n        current_time = time.time()\n        if current_time - start_time > timeout:\n            # Robyn didn't start correctly before timeout, kill the process and exit with an exception\n            kill_process(process)\n            raise ConnectionError(f\"Could not reach Robyn server on {domain}:{port} after {timeout} seconds\")\n        try:\n            sock = socket.create_connection((domain, port), timeout=2)\n            sock.close()\n            break  # We were able to reach the server, exit the loop\n        except Exception:\n            time.sleep(0.5)  # Longer delay before retrying\n\n    # Give the server a moment to fully initialize after accepting connections\n    time.sleep(1)\n    return process\n\n\n@pytest.fixture(scope=\"session\")\ndef session():\n    domain = \"127.0.0.1\"\n    port = 8080\n    os.environ[\"ROBYN_HOST\"] = domain\n    process = start_server(domain, port)\n    yield\n    kill_process(process)\n\n\n@pytest.fixture(scope=\"session\")\ndef default_session():\n    domain = \"127.0.0.1\"\n    port = 8080\n    process = start_server(domain, port)\n    yield\n    kill_process(process)\n\n\n@pytest.fixture(scope=\"session\")\ndef global_session():\n    domain = get_network_host()\n    port = 8080\n    os.environ[\"ROBYN_HOST\"] = domain\n    process = start_server(domain, port)\n    yield\n    kill_process(process)\n\n\n@pytest.fixture(scope=\"session\")\ndef dev_session():\n    domain = \"127.0.0.1\"\n    port = 8081\n    os.environ[\"ROBYN_HOST\"] = domain\n    os.environ[\"ROBYN_PORT\"] = str(port)\n    # This doesn't test is_dev=True!!!!\n    process = start_server(domain, port)\n    yield\n    kill_process(process)\n\n\n@pytest.fixture(scope=\"session\")\ndef test_session():\n    domain = \"127.0.0.1\"\n    port = 8080\n    os.environ[\"ROBYN_HOST\"] = domain\n    os.environ[\"ROBYN_PORT\"] = str(port)\n    process = start_server(domain, port, is_dev=True)\n    yield\n    kill_process(process)\n\n\n# create robyn.env before test and delete it after test\n@pytest.fixture\ndef env_file():\n    CONTENT = \"\"\"ROBYN_PORT=8081\n    ROBYN_URL=127.0.0.1\"\"\"\n    path = pathlib.Path(__file__).parent\n    env_path = path / \"robyn.env\"\n    env_path.write_text(CONTENT)\n    yield\n    env_path.unlink()\n    del os.environ[\"ROBYN_PORT\"]\n    del os.environ[\"ROBYN_HOST\"]\n"
  },
  {
    "path": "integration_tests/downloads/test.txt",
    "content": "This is a test file for the downloading purpose"
  },
  {
    "path": "integration_tests/helpers/__init__.py",
    "content": ""
  },
  {
    "path": "integration_tests/helpers/http_methods_helpers.py",
    "content": "from typing import Optional\n\nimport requests\n\nBASE_URL = \"http://127.0.0.1:8080\"\n\n\ndef check_response(response: requests.Response, expected_status_code: int):\n    \"\"\"\n    Raises if the response status code is not the expected one or if one of the global\n    headers is not present in the response.\n    \"\"\"\n    assert response.status_code == expected_status_code\n    assert response.headers.get(\"global_after\") == \"global_after_request\"\n    assert \"server\" in response.headers\n    assert response.headers.get(\"server\") == \"robyn\"\n\n\ndef get(\n    endpoint: str,\n    expected_status_code: int = 200,\n    headers: dict = {},\n    should_check_response: bool = True,\n) -> requests.Response:\n    \"\"\"\n    Makes a GET request to the given endpoint and checks the response.\n\n    endpoint str: The endpoint to make the request to.\n    expected_status_code int: The expected status code of the response.\n    headers dict: The headers to send with the request.\n    should_check_response bool: A boolean to indicate if the status code and headers should be checked.\n    \"\"\"\n    endpoint = endpoint.lstrip(\"/\")\n    response = requests.get(f\"{BASE_URL}/{endpoint}\", headers=headers)\n    if should_check_response:\n        check_response(response, expected_status_code)\n    return response\n\n\ndef post(\n    endpoint: str,\n    data: Optional[dict] = None,\n    expected_status_code: int = 200,\n    headers: dict = {},\n    should_check_response: bool = True,\n) -> requests.Response:\n    \"\"\"\n    Makes a POST request to the given endpoint and checks the response.\n\n    endpoint str: The endpoint to make the request to.\n    data Optional[dict]: The data to send with the request.\n    expected_status_code int: The expected status code of the response.\n    headers dict: The headers to send with the request.\n    should_check_response bool: A boolean to indicate if the status code and headers should be checked.\n    \"\"\"\n\n    endpoint = endpoint.lstrip(\"/\")\n    response = requests.post(f\"{BASE_URL}/{endpoint}\", data=data, headers=headers)\n    if should_check_response:\n        check_response(response, expected_status_code)\n    return response\n\n\ndef json_post(\n    endpoint: str,\n    json_data=None,\n    expected_status_code: int = 200,\n    headers: dict = {},\n    should_check_response: bool = True,\n) -> requests.Response:\n    \"\"\"\n    Makes a POST request with JSON body to the given endpoint and checks the response.\n\n    endpoint str: The endpoint to make the request to.\n    json_data: The JSON-serializable data to send with the request (dict, list, etc.).\n    expected_status_code int: The expected status code of the response.\n    headers dict: The headers to send with the request.\n    should_check_response bool: A boolean to indicate if the status code and headers should be checked.\n    \"\"\"\n\n    endpoint = endpoint.strip(\"/\")\n    response = requests.post(f\"{BASE_URL}/{endpoint}\", json=json_data, headers=headers)\n    if should_check_response:\n        check_response(response, expected_status_code)\n    return response\n\n\ndef multipart_post(\n    endpoint: str,\n    files: Optional[dict] = None,\n    expected_status_code: int = 200,\n    should_check_response: bool = True,\n) -> requests.Response:\n    \"\"\"\n    Makes a POST request to the given endpoint and checks the response.\n\n    endpoint str: The endpoint to make the request to.\n    files Optional[dict]: The files to send with the request.\n    expected_status_code int: The expected status code of the response.\n    should_check_response bool: A boolean to indicate if the status code and headers should be checked.\n    \"\"\"\n\n    endpoint = endpoint.lstrip(\"/\")\n    response = requests.post(f\"{BASE_URL}/{endpoint}\", files=files)\n    if should_check_response:\n        check_response(response, expected_status_code)\n    return response\n\n\ndef put(\n    endpoint: str,\n    data: Optional[dict] = None,\n    expected_status_code: int = 200,\n    headers: dict = {},\n    should_check_response: bool = True,\n) -> requests.Response:\n    \"\"\"\n    Makes a PUT request to the given endpoint and checks the response.\n\n    endpoint str: The endpoint to make the request to.\n    expected_status_code int: The expected status code of the response.\n    headers dict: The headers to send with the request.\n    should_check_response bool: A boolean to indicate if the status code and headers should be checked.\n    \"\"\"\n\n    endpoint = endpoint.lstrip(\"/\")\n    response = requests.put(f\"{BASE_URL}/{endpoint}\", data=data, headers=headers)\n    if should_check_response:\n        check_response(response, expected_status_code)\n    return response\n\n\ndef patch(\n    endpoint: str,\n    data: Optional[dict] = None,\n    expected_status_code: int = 200,\n    headers: dict = {},\n    should_check_response: bool = True,\n) -> requests.Response:\n    \"\"\"\n    Makes a PATCH request to the given endpoint and checks the response.\n\n    endpoint str: The endpoint to make the request to.\n    expected_status_code int: The expected status code of the response.\n    headers dict: The headers to send with the request.\n    should_check_response bool: A boolean to indicate if the status code and headers should be checked.\n    \"\"\"\n\n    endpoint = endpoint.lstrip(\"/\")\n    response = requests.patch(f\"{BASE_URL}/{endpoint}\", data=data, headers=headers)\n    if should_check_response:\n        check_response(response, expected_status_code)\n    return response\n\n\ndef delete(\n    endpoint: str,\n    data: Optional[dict] = None,\n    expected_status_code: int = 200,\n    headers: dict = {},\n    should_check_response: bool = True,\n) -> requests.Response:\n    \"\"\"\n    Makes a DELETE request to the given endpoint and checks the response.\n\n    endpoint str: The endpoint to make the request to.\n    expected_status_code int: The expected status code of the response.\n    headers dict: The headers to send with the request.\n    should_check_response bool: A boolean to indicate if the status code and headers should be checked.\n    \"\"\"\n\n    endpoint = endpoint.lstrip(\"/\")\n    response = requests.delete(f\"{BASE_URL}/{endpoint}\", data=data, headers=headers)\n    if should_check_response:\n        check_response(response, expected_status_code)\n    return response\n\n\ndef head(\n    endpoint: str,\n    data: Optional[dict] = None,\n    expected_status_code: int = 200,\n    headers: dict = {},\n    should_check_response: bool = True,\n) -> requests.Response:\n    \"\"\"\n    Makes a HEAD request to the given endpoint and checks the response.\n\n    endpoint str: The endpoint to make the request to.\n    expected_status_code int: The expected status code of the response.\n    headers dict: The headers to send with the request.\n    should_check_response bool: A boolean to indicate if the status code and headers should be checked.\n    \"\"\"\n\n    endpoint = endpoint.lstrip(\"/\")\n    response = requests.head(f\"{BASE_URL}/{endpoint}\", data=data, headers=headers)\n    if should_check_response:\n        check_response(response, expected_status_code)\n    return response\n\n\n# TODO - at some point this should be the defacto\n# and every other method should be replaced with this\ndef generic_http_helper(\n    method: str,\n    endpoint: str,\n    data: Optional[dict] = None,\n    expected_status_code: int = 200,\n    headers: dict = {},\n    should_check_response: bool = True,\n) -> requests.Response:\n    \"\"\"\n    Makes a request to the given endpoint and checks the response.\n\n    endpoint str: The endpoint to make the request to.\n    expected_status_code int: The expected status code of the response.\n    headers dict: The headers to send with the request.\n    should_check_response bool: A boolean to indicate if the status code and headers should be checked.\n    \"\"\"\n\n    endpoint = endpoint.lstrip(\"/\")\n    if method not in [\"get\", \"post\", \"put\", \"patch\", \"delete\", \"options\", \"trace\"]:\n        raise ValueError(f\"{method} method must be one of get, post, put, patch, delete\")\n    if method == \"get\":\n        response = requests.get(f\"{BASE_URL}/{endpoint}\", headers=headers)\n    else:\n        response = requests.request(method, f\"{BASE_URL}/{endpoint}\", data=data, headers=headers)\n    if should_check_response:\n        check_response(response, expected_status_code)\n    return response\n"
  },
  {
    "path": "integration_tests/helpers/network_helpers.py",
    "content": "import platform\nimport socket\n\n\ndef get_network_host():\n    # windows doesn't support 0.0.0.0\n    if platform.system() == \"Windows\":\n        hostname = socket.gethostname()\n        ip_address = socket.gethostbyname(hostname)\n        return ip_address\n    else:\n        return \"0.0.0.0\"\n"
  },
  {
    "path": "integration_tests/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Document</title>\n  </head>\n  <body>\n    Hello world. How are you?\n  </body>\n</html>\n"
  },
  {
    "path": "integration_tests/index.py",
    "content": "from robyn import Robyn\n\napp = Robyn(__file__)\n\n\n@app.get(\"/\")\nasync def h():\n    return \"Hello, world!\"\n\n\napp.start()\n"
  },
  {
    "path": "integration_tests/openapi_config.json",
    "content": "{\n  \"openapi\": \"3.1.0\",\n  \"info\": {\n    \"title\": \"Robyn Test API\",\n    \"version\": \"1.0.0\",\n    \"description\": null,\n    \"termsOfService\": null,\n    \"contact\": {\n      \"name\": null,\n      \"url\": null,\n      \"email\": null\n    },\n    \"license\": {\n      \"name\": null,\n      \"url\": null\n    },\n    \"servers\": [],\n    \"externalDocs\": {\n      \"description\": null,\n      \"url\": null\n    },\n    \"components\": {\n      \"schemas\": {},\n      \"responses\": {},\n      \"parameters\": {},\n      \"examples\": {},\n      \"requestBodies\": {},\n      \"securitySchemes\": {},\n      \"links\": {},\n      \"callbacks\": {},\n      \"pathItems\": {}\n    }\n  },\n  \"paths\": {\n    \"/\": {\n      \"get\": {\n        \"summary\": \"\",\n        \"description\": \"No description provided\",\n        \"parameters\": [],\n        \"tags\": [\n          \"get\"\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"text/plain\": {\n                \"schema\": {}\n              }\n            }\n          }\n        }\n      }\n    }\n  },\n  \"components\": {\n    \"schemas\": {},\n    \"responses\": {},\n    \"parameters\": {},\n    \"examples\": {},\n    \"requestBodies\": {},\n    \"securitySchemes\": {},\n    \"links\": {},\n    \"callbacks\": {},\n    \"pathItems\": {}\n  },\n  \"servers\": [],\n  \"externalDocs\": null\n}"
  },
  {
    "path": "integration_tests/random_number.rs",
    "content": "// rustimport:pyo3\n\nuse pyo3::prelude::*;\n\n#[pyfunction]\nfn say_hello() {\n    println!(\"Hello from random_number, implemented in Rust!\")\n}\n\n// Uncomment the below to implement custom pyo3 binding code. Otherwise, \n// rustimport will generate it for you for all functions annotated with\n// #[pyfunction] and all structs annotated with #[pyclass].\n//\n//#[pymodule]\n//fn random_number(_py: Python, m: &PyModule) -> PyResult<()> {\n//    m.add_function(wrap_pyfunction!(say_hello, m)?)?;\n//    Ok(())\n//}\n"
  },
  {
    "path": "integration_tests/subroutes/__init__.py",
    "content": "from robyn import SubRouter, WebSocketDisconnect, jsonify\n\nfrom .di_subrouter import di_subrouter\nfrom .file_api import static_router\n\nsub_router = SubRouter(__name__, prefix=\"/sub_router\")\n\n__all__ = [\"sub_router\", \"di_subrouter\", \"static_router\"]\n\n\n@sub_router.websocket(\"/ws\")\nasync def ws_handler(websocket):\n    try:\n        while True:\n            await websocket.receive_text()\n            await websocket.send_text(\"Message\")\n    except WebSocketDisconnect:\n        pass\n\n\n@ws_handler.on_connect\nasync def connect(websocket):\n    return \"Hello world, from ws\"\n\n\n@ws_handler.on_close\nasync def close(websocket):\n    return jsonify({\"message\": \"closed\"})\n\n\n@sub_router.get(\"/foo\")\ndef get_foo():\n    return {\"message\": \"foo\"}\n\n\n@sub_router.post(\"/foo\")\ndef post_foo():\n    return {\"message\": \"foo\"}\n\n\n@sub_router.put(\"/foo\")\ndef put_foo():\n    return {\"message\": \"foo\"}\n\n\n@sub_router.delete(\"/foo\")\ndef delete_foo():\n    return {\"message\": \"foo\"}\n\n\n@sub_router.patch(\"/foo\")\ndef patch_foo():\n    return {\"message\": \"foo\"}\n\n\n@sub_router.options(\"/foo\")\ndef option_foo():\n    return {\"message\": \"foo\"}\n\n\n@sub_router.trace(\"/foo\")\ndef trace_foo():\n    return {\"message\": \"foo\"}\n\n\n@sub_router.head(\"/foo\")\ndef head_foo():\n    return {\"message\": \"foo\"}\n\n\n@sub_router.post(\"/openapi_test\", openapi_tags=[\"test subrouter tag\"])\ndef sample_subrouter_openapi_endpoint():\n    \"\"\"Get subrouter openapi\"\"\"\n    return 200\n"
  },
  {
    "path": "integration_tests/subroutes/di_subrouter.py",
    "content": "from robyn import Request, SubRouter\n\ndi_subrouter = SubRouter(__file__, \"/di_subrouter\")\nGLOBAL_DEPENDENCY = \"GLOBAL DEPENDENCY OVERRIDE\"\nROUTER_DEPENDENCY = \"ROUTER DEPENDENCY\"\ndi_subrouter.inject_global(GLOBAL_DEPENDENCY=GLOBAL_DEPENDENCY)\ndi_subrouter.inject(ROUTER_DEPENDENCY=ROUTER_DEPENDENCY)\n\n\n@di_subrouter.get(\"/subrouter_router_di\")\ndef sync_subrouter_route_dependency(r: Request, router_dependencies, global_dependencies):\n    return router_dependencies[\"ROUTER_DEPENDENCY\"]\n\n\n@di_subrouter.get(\"/subrouter_global_di\")\ndef sync_subrouter_global_dependency(global_dependencies):\n    return global_dependencies[\"GLOBAL_DEPENDENCY\"]\n"
  },
  {
    "path": "integration_tests/subroutes/file_api.py",
    "content": "\"\"\"\nTest routes for issue #1251: static files + API routes at same base path.\n\nNotes:\n1. No need to test every method, just one is enough to ensure no conflict.\n2. The static files are served from ./integration_tests to avoid conflict with the /test_dir route in main app.\n3. The static file serving route is defined in a separate SubRouter to isolate it from the main app routes.\n4. Serving api & files from the same /static path to test the fix.\n\"\"\"\n\nfrom robyn import Request, SubRouter\n\nstatic_router = SubRouter(__name__, \"/static\")\n\n\n@static_router.get(\"/build\")\n@static_router.post(\"/build\")\nasync def build_handler(request: Request):\n    \"\"\"\n    Test route ensuring no conflict with static file serving at /static.\n\n    Although static files are served at /static, this API route should be reached\n    because /build is not a file, so the request falls through to the API handler.\n    \"\"\"\n    return f\"{request.method}:{request.url.path} works\"\n"
  },
  {
    "path": "integration_tests/templates/test.html",
    "content": "{# templates/test.html #}\n\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n  <title>Results</title>\n</head>\n\n<body>\n  <h1>{{framework}} 🤝 {{templating_engine}}</h1>\n</body>\n</html>\n"
  },
  {
    "path": "integration_tests/test_add_route_without_decorator.py",
    "content": "from collections.abc import Callable\n\nimport pytest\n\nfrom integration_tests.helpers.http_methods_helpers import get, post, put\n\n\n@pytest.mark.benchmark\n@pytest.mark.usefixtures(\"session\")\n@pytest.mark.parametrize(\n    \"route,method\",\n    [\n        (\"/sync/get/no_dec\", get),\n        (\"/async/get/no_dec\", get),\n        (\"/sync/put/no_dec\", put),\n        (\"/async/put/no_dec\", put),\n        (\"/sync/post/no_dec\", post),\n        (\"/async/post/no_dec\", post),\n    ],\n)\ndef test_exception_handling(route: str, method: Callable):\n    r = method(route, expected_status_code=200)\n    assert r.text == \"Success!\"\n"
  },
  {
    "path": "integration_tests/test_app.py",
    "content": "import pytest\n\nfrom robyn import ALLOW_CORS, Robyn\nfrom robyn.events import Events\nfrom robyn.robyn import Headers\n\n\n@pytest.mark.benchmark\ndef test_add_request_header():\n    app = Robyn(__file__)\n    app.set_request_header(\"server\", \"robyn\")\n    assert app.request_headers.get_headers() == Headers({\"server\": \"robyn\"}).get_headers()\n\n\n@pytest.mark.benchmark\ndef test_add_response_header():\n    app = Robyn(__file__)\n    app.add_response_header(\"content-type\", \"application/json\")\n    assert app.response_headers.get_headers() == Headers({\"content-type\": \"application/json\"}).get_headers()\n\n\n@pytest.mark.benchmark\ndef test_lifecycle_handlers():\n    def mock_startup_handler():\n        pass\n\n    async def mock_shutdown_handler():\n        pass\n\n    app = Robyn(__file__)\n\n    app.startup_handler(mock_startup_handler)\n    assert Events.STARTUP in app.event_handlers\n    startup = app.event_handlers[Events.STARTUP]\n    assert startup.handler == mock_startup_handler\n    assert startup.is_async is False\n    assert startup.number_of_params == 0\n\n    app.shutdown_handler(mock_shutdown_handler)\n    assert Events.SHUTDOWN in app.event_handlers\n    shutdown = app.event_handlers[Events.SHUTDOWN]\n    assert shutdown.handler == mock_shutdown_handler\n    assert shutdown.is_async is True\n    assert shutdown.number_of_params == 0\n\n\n@pytest.mark.benchmark\ndef test_allow_cors():\n    app = Robyn(__file__)\n    ALLOW_CORS(app, [\"*\"])\n\n    headers = Headers({})\n    headers.set(\"Access-Control-Allow-Origin\", \"*\")\n    headers.set(\n        \"Access-Control-Allow-Methods\",\n        \"GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS\",\n    )\n    headers.set(\"Access-Control-Allow-Headers\", \"Content-Type, Authorization\")\n    headers.set(\"Access-Control-Allow-Credentials\", \"true\")\n    assert app.response_headers.get_headers() == headers.get_headers()\n"
  },
  {
    "path": "integration_tests/test_authentication.py",
    "content": "from urllib.parse import urlparse\n\nimport pytest\n\nfrom integration_tests.helpers.http_methods_helpers import get\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_valid_authentication(session, function_type: str):\n    r = get(f\"/{function_type}/auth\", headers={\"Authorization\": \"Bearer valid\"})\n    assert r.text == \"authenticated\"\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_valid_authentication_trailing_slash(session, function_type: str):\n    r = get(f\"/{function_type}/auth/\", headers={\"Authorization\": \"Bearer valid\"})\n    # Checks whether request is being sent to exact /trailing/ route.\n    assert urlparse(r.url).path == f\"/{function_type}/auth/\"\n    assert r.text == \"authenticated\"\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_invalid_authentication_token(session, function_type: str):\n    r = get(\n        f\"/{function_type}/auth\",\n        headers={\"Authorization\": \"Bearer invalid\"},\n        should_check_response=False,\n    )\n    assert r.status_code == 401\n    assert r.headers.get(\"WWW-Authenticate\") == \"BearerGetter\"\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_invalid_authentication_token_trailing_slash(session, function_type: str):\n    r = get(\n        f\"/{function_type}/auth/\",\n        headers={\"Authorization\": \"Bearer invalid\"},\n        should_check_response=False,\n    )\n    assert urlparse(r.url).path == f\"/{function_type}/auth/\"\n    assert r.status_code == 401\n    assert r.headers.get(\"WWW-Authenticate\") == \"BearerGetter\"\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_invalid_authentication_header(session, function_type: str):\n    r = get(\n        f\"/{function_type}/auth\",\n        headers={\"Authorization\": \"Bear valid\"},\n        should_check_response=False,\n    )\n    assert r.status_code == 401\n    assert r.headers.get(\"WWW-Authenticate\") == \"BearerGetter\"\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_invalid_authentication_header_trailing_slash(session, function_type: str):\n    r = get(\n        f\"/{function_type}/auth/\",\n        headers={\"Authorization\": \"Bear valid\"},\n        should_check_response=False,\n    )\n    assert r.status_code == 401\n    assert r.headers.get(\"WWW-Authenticate\") == \"BearerGetter\"\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_invalid_authentication_no_token(session, function_type: str):\n    r = get(f\"/{function_type}/auth\", should_check_response=False)\n    assert r.status_code == 401\n    assert r.headers.get(\"WWW-Authenticate\") == \"BearerGetter\"\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_invalid_authentication_no_token_trailing_slash(session, function_type: str):\n    r = get(f\"/{function_type}/auth/\", should_check_response=False)\n    assert r.status_code == 401\n    assert r.headers.get(\"WWW-Authenticate\") == \"BearerGetter\"\n"
  },
  {
    "path": "integration_tests/test_base_url.py",
    "content": "import os\n\nimport pytest\nimport requests\n\nfrom integration_tests.helpers.network_helpers import get_network_host\n\n\n@pytest.mark.benchmark\ndef test_default_url_index_request(default_session):\n    BASE_URL = \"http://127.0.0.1:8080\"\n    res = requests.get(f\"{BASE_URL}\")\n    assert res.status_code == 200\n\n\n@pytest.mark.benchmark\ndef test_local_index_request(session):\n    BASE_URL = \"http://127.0.0.1:8080\"\n    res = requests.get(f\"{BASE_URL}\")\n    assert os.getenv(\"ROBYN_HOST\") == \"127.0.0.1\"\n    assert res.status_code == 200\n\n\n@pytest.mark.benchmark\ndef test_global_index_request(global_session):\n    host = get_network_host()\n    BASE_URL = f\"http://{host}:8080\"\n    res = requests.get(f\"{BASE_URL}\")\n    assert os.getenv(\"ROBYN_HOST\") == f\"{host}\"\n    assert res.status_code == 200\n"
  },
  {
    "path": "integration_tests/test_basic_routes.py",
    "content": "# Test the routes with:\n# - the GET method\n# - most common return types\n# - sync and async\n\nfrom typing import Optional\n\nimport pytest\n\nfrom integration_tests.helpers.http_methods_helpers import get\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\n    \"route,expected_text,expected_header_key,expected_header_value\",\n    [\n        (\"/sync/str\", \"sync str get\", None, None),\n        (\"/sync/dict\", \"sync dict get\", \"sync\", \"dict\"),\n        (\"/sync/response\", \"sync response get\", \"sync\", \"response\"),\n        (\"/sync/str/const\", \"sync str const get\", None, None),\n        (\"/sync/dict/const\", \"sync dict const get\", \"sync_const\", \"dict\"),\n        (\"/sync/response/const\", \"sync response const get\", \"sync_const\", \"response\"),\n        (\"/async/str\", \"async str get\", None, None),\n        (\"/async/dict\", \"async dict get\", \"async\", \"dict\"),\n        (\"/async/response\", \"async response get\", \"async\", \"response\"),\n        (\"/async/str/const\", \"async str const get\", None, None),\n        (\"/async/dict/const\", \"async dict const get\", \"async_const\", \"dict\"),\n        (\n            \"/async/response/const\",\n            \"async response const get\",\n            \"async_const\",\n            \"response\",\n        ),\n    ],\n)\ndef test_basic_get(\n    route: str,\n    expected_text: str,\n    expected_header_key: Optional[str],\n    expected_header_value: Optional[str],\n    session,\n):\n    res = get(route)\n    assert res.text == expected_text\n    if expected_header_key is not None:\n        assert expected_header_key in res.headers\n        assert res.headers[expected_header_key] == expected_header_value\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\n    \"route, expected_json\",\n    [\n        (\"/sync/json\", {\"sync json get\": \"json\"}),\n        (\"/async/json\", {\"async json get\": \"json\"}),\n        (\"/sync/json/const\", {\"sync json const get\": \"json\"}),\n        (\"/async/json/const\", {\"async json const get\": \"json\"}),\n    ],\n)\ndef test_json_get(route: str, expected_json: dict, session):\n    res = get(route)\n    for key in expected_json.keys():\n        assert key in res.json()\n        assert res.json()[key] == expected_json[key]\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\n    \"route, expected_json\",\n    [\n        (\n            \"/sync/http/param\",\n            {\n                \"method\": \"GET\",\n                \"url\": {\n                    \"host\": \"127.0.0.1:8080\",\n                    \"path\": \"/sync/http/param\",\n                    \"scheme\": \"http\",\n                },\n            },\n        ),\n        (\n            \"/async/http/param\",\n            {\n                \"method\": \"GET\",\n                \"url\": {\n                    \"host\": \"127.0.0.1:8080\",\n                    \"path\": \"/async/http/param\",\n                    \"scheme\": \"http\",\n                },\n            },\n        ),\n    ],\n)\ndef test_http_request_info_get(route: str, expected_json: dict, session):\n    res = get(route)\n    for key in expected_json.keys():\n        assert key in res.json()\n        assert res.json()[key] == expected_json[key]\n"
  },
  {
    "path": "integration_tests/test_binary_output.py",
    "content": "import pytest\n\nfrom integration_tests.helpers.http_methods_helpers import get\n\nBASE_URL = \"http://127.0.0.1:8080\"\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\n    \"route, text\",\n    [\n        (\"/sync/octet\", \"sync octet\"),\n        (\"/async/octet\", \"async octet\"),\n        (\"/sync/octet/response\", \"sync octet response\"),\n        (\"/async/octet/response\", \"async octet response\"),\n    ],\n)\ndef test_binary_output(route: str, text: str, session):\n    r = get(route)\n    assert r.headers.get(\"Content-Type\") == \"application/octet-stream\"\n    assert r.text == text\n"
  },
  {
    "path": "integration_tests/test_delete_requests.py",
    "content": "import pytest\n\nfrom integration_tests.helpers.http_methods_helpers import delete\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_delete(function_type: str, session):\n    res = delete(f\"/{function_type}/dict\")\n    assert res.text == f\"{function_type} dict delete\"\n    assert function_type in res.headers\n    assert res.headers[function_type] == \"dict\"\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_delete_with_param(function_type: str, session):\n    res = delete(f\"/{function_type}/body\", data={\"hello\": \"world\"})\n    assert res.text == \"hello=world\"\n"
  },
  {
    "path": "integration_tests/test_dependency_injection.py",
    "content": "import pytest\n\nfrom integration_tests.helpers.http_methods_helpers import get\n\n\n@pytest.mark.benchmark\ndef test_global_dependency_injection():\n    r = get(\"/sync/global_di\")\n    assert r.status_code == 200\n    assert r.text == \"GLOBAL DEPENDENCY\"\n\n\n@pytest.mark.benchmark\ndef test_router_dependency_injection():\n    r = get(\"/sync/router_di\")\n    assert r.status_code == 200\n    assert r.text == \"ROUTER DEPENDENCY\"\n\n\n@pytest.mark.benchmark\ndef test_subrouter_global_dependency_injection():\n    r = get(\"/di_subrouter/subrouter_global_di\")\n    assert r.status_code == 200\n    assert r.text == \"GLOBAL DEPENDENCY\"\n\n\n@pytest.mark.benchmark\ndef test_subrouter_router_dependency_injection():\n    r = get(\"/di_subrouter/subrouter_router_di\")\n    assert r.status_code == 200\n    assert r.text == \"ROUTER DEPENDENCY\"\n"
  },
  {
    "path": "integration_tests/test_easy_access_params.py",
    "content": "import os\n\nimport pytest\nfrom websocket import create_connection\n\nfrom integration_tests.helpers.http_methods_helpers import get\n\nWS_BASE_URL = f\"ws://127.0.0.1:{os.environ.get('ROBYN_PORT', '8080')}\"\n\n\n# ===== HTTP: Path param + query param with type coercion =====\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_easy_access_path_and_query_params(session, function_type):\n    r = get(f\"/easy/{function_type}/42?q=hello&page=5\")\n    assert r.json() == {\"id\": 42, \"q\": \"hello\", \"page\": 5}\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_easy_access_default_value(session, function_type):\n    r = get(f\"/easy/{function_type}/42?q=hello\")\n    assert r.json() == {\"id\": 42, \"q\": \"hello\", \"page\": 1}\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_easy_access_missing_required_param(session, function_type):\n    \"\"\"Missing required 'q' param should return 400.\"\"\"\n    r = get(f\"/easy/{function_type}/42\", should_check_response=False)\n    assert r.status_code == 400\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_easy_access_bad_type_coercion(session, function_type):\n    \"\"\"Path param :id declared as int but given 'abc' should return 400.\"\"\"\n    r = get(f\"/easy/{function_type}/abc?q=hello\", should_check_response=False)\n    assert r.status_code == 400\n\n\n# ===== HTTP: Optional params =====\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_easy_access_optional_present(session, function_type):\n    r = get(f\"/easy/{function_type}/optional?name=bob&age=30\")\n    assert r.json() == {\"name\": \"bob\", \"age\": 30}\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_easy_access_optional_missing(session, function_type):\n    r = get(f\"/easy/{function_type}/optional?name=bob\")\n    assert r.json() == {\"name\": \"bob\", \"age\": None}\n\n\n# ===== HTTP: List params =====\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_easy_access_list_params(session, function_type):\n    r = get(f\"/easy/{function_type}/list?tag=python&tag=rust&tag=web\")\n    assert r.json() == {\"tags\": [\"python\", \"rust\", \"web\"]}\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_easy_access_list_single_value(session, function_type):\n    r = get(f\"/easy/{function_type}/list?tag=python\")\n    assert r.json() == {\"tags\": [\"python\"]}\n\n\n# ===== HTTP: Bool params =====\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_easy_access_bool_true(session, function_type):\n    r = get(f\"/easy/{function_type}/bool?active=true\")\n    assert r.json() == {\"active\": True}\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_easy_access_bool_false(session, function_type):\n    r = get(f\"/easy/{function_type}/bool?active=false\")\n    assert r.json() == {\"active\": False}\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_easy_access_bool_default(session, function_type):\n    r = get(f\"/easy/{function_type}/bool\")\n    assert r.json() == {\"active\": False}\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_easy_access_bool_numeric(session, function_type):\n    r = get(f\"/easy/{function_type}/bool?active=1\")\n    assert r.json() == {\"active\": True}\n\n\n# ===== HTTP: Mixed (Request object + individual params) =====\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_easy_access_mixed_with_request(session, function_type):\n    r = get(f\"/easy/{function_type}/mixed/99?q=search\")\n    assert r.json() == {\"id\": 99, \"q\": \"search\", \"method\": \"GET\"}\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_easy_access_mixed_with_default(session, function_type):\n    r = get(f\"/easy/{function_type}/mixed/99\")\n    assert r.json() == {\"id\": 99, \"q\": \"\", \"method\": \"GET\"}\n\n\n# ===== HTTP: Float params =====\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_easy_access_float(session, function_type):\n    r = get(f\"/easy/{function_type}/float?price=19.99\")\n    assert r.json() == {\"price\": 19.99}\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_easy_access_float_bad_value(session, function_type):\n    r = get(f\"/easy/{function_type}/float?price=notanumber\", should_check_response=False)\n    assert r.status_code == 400\n\n\n# ===== WebSocket: Easy access query params =====\n\n\ndef test_easy_access_ws_with_params(session):\n    ws = create_connection(f\"{WS_BASE_URL}/web_socket_easy_access?room=chat&page=5\")\n    connect_msg = ws.recv()\n    assert connect_msg == \"connected to chat\"\n\n    ws.send(\"hello\")\n    response = ws.recv()\n    assert response == \"room=chat page=5 msg=hello\"\n\n    ws.close()\n\n\ndef test_easy_access_ws_with_defaults(session):\n    ws = create_connection(f\"{WS_BASE_URL}/web_socket_easy_access\")\n    connect_msg = ws.recv()\n    assert connect_msg == \"connected to default\"\n\n    ws.send(\"hello\")\n    response = ws.recv()\n    assert response == \"room=default page=1 msg=hello\"\n\n    ws.close()\n"
  },
  {
    "path": "integration_tests/test_exception_handling.py",
    "content": "from collections.abc import Callable\n\nimport pytest\n\nfrom integration_tests.helpers.http_methods_helpers import get, post, put\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\n    \"route,method\",\n    [\n        (\"/sync/exception/get\", get),\n        (\"/async/exception/get\", get),\n        (\"/sync/exception/put\", put),\n        (\"/async/exception/put\", put),\n        (\"/sync/exception/post\", post),\n        (\"/async/exception/post\", post),\n    ],\n)\ndef test_exception_handling(\n    route: str,\n    method: Callable,\n    session,\n):\n    r = method(route, expected_status_code=500)\n    assert r.text == \"error msg: value error\"\n"
  },
  {
    "path": "integration_tests/test_file_download.py",
    "content": "import pytest\n\nfrom integration_tests.helpers.http_methods_helpers import get\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_file_download(function_type: str, session):\n    r = get(f\"/{function_type}/file/download\")\n    assert r.headers.get(\"Content-Disposition\") == \"attachment; filename=test.txt\"\n\n    assert r.text == \"This is a test file for the downloading purpose\"\n"
  },
  {
    "path": "integration_tests/test_get_requests.py",
    "content": "import pytest\nimport requests\nfrom requests import Response\n\nfrom integration_tests.helpers.http_methods_helpers import get\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_param(function_type: str, session):\n    r = get(f\"/{function_type}/param/1\")\n    assert r.text == \"1\"\n    r = get(f\"/{function_type}/param/12345\")\n    assert r.text == \"12345\"\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_param_suffix(function_type: str, session):\n    r = get(f\"/{function_type}/extra/foo/1/baz\")\n    assert r.text == \"foo/1/baz\"\n    r = get(f\"/{function_type}/extra/foo/bar/baz\")\n    assert r.text == \"foo/bar/baz\"\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_serve_html(function_type: str, session):\n    def check_response(r: Response):\n        assert r.text.startswith(\"<!DOCTYPE html>\")\n        assert \"Hello world. How are you?\" in r.text\n\n    check_response(get(f\"/{function_type}/serve/html\"))\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_template(function_type: str, session):\n    def check_response(r: Response):\n        assert r.text.startswith(\"\\n\\n<!DOCTYPE html>\")\n        assert \"Jinja2\" in r.text\n        assert \"Robyn\" in r.text\n\n    check_response(get(f\"/{function_type}/template\"))\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_queries(function_type: str, session):\n    r = get(f\"/{function_type}/queries?hello=robyn\")\n    assert r.json() == {\"hello\": [\"robyn\"]}\n\n    r = get(f\"/{function_type}/queries\")\n    assert r.json() == {}\n\n\n@pytest.mark.benchmark\ndef test_trailing_slash(session):\n    r = requests.get(\"http://localhost:8080/trailing\")  # `integration_tests#get` strips the trailing slash, tests always pass!`\n    assert r.text == \"Trailing slash test successful!\"\n\n    r = requests.get(\"http://localhost:8080/trailing/\")\n    assert r.text == \"Trailing slash test successful!\"\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"key, value\", [(\"fakesession\", \"fake-cookie-session-value\")])\ndef test_cookies(session, key, value):\n    response = get(\"/cookie\", 200)\n\n    # Cookies should be sent via Set-Cookie header, accessible via response.cookies\n    assert response.cookies[key] == value\n\n\n@pytest.mark.benchmark\ndef test_multiple_cookies(session):\n    response = get(\"/cookie/multiple\", 200)\n\n    assert response.cookies[\"session\"] == \"abc123\"\n    assert response.cookies[\"theme\"] == \"dark\"\n\n\n@pytest.mark.benchmark\ndef test_cookie_with_attributes(session):\n    response = get(\"/cookie/attributes\", 200)\n\n    # Check the cookie value\n    assert response.cookies[\"secure_session\"] == \"secret123\"\n\n    # Check the Set-Cookie header for attributes\n    set_cookie_header = response.headers.get(\"Set-Cookie\", \"\")\n    assert \"secure_session=secret123\" in set_cookie_header\n    assert \"Path=/\" in set_cookie_header\n    assert \"HttpOnly\" in set_cookie_header\n    assert \"Secure\" in set_cookie_header\n    assert \"SameSite=Strict\" in set_cookie_header\n    assert \"Max-Age=3600\" in set_cookie_header\n\n\n@pytest.mark.benchmark\ndef test_cookie_overwrite(session):\n    response = get(\"/cookie/overwrite\", 200)\n\n    # Same-name cookies should be overwritten, final value should be used\n    assert response.cookies[\"session\"] == \"final-value\"\n"
  },
  {
    "path": "integration_tests/test_json_types.py",
    "content": "import pytest\n\nfrom integration_tests.helpers.http_methods_helpers import get, json_post\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_json_integer_type_preserved(function_type: str, session):\n    \"\"\"Test that integer values in JSON are preserved as integers, not strings\"\"\"\n    json_data = {\"lid\": 570, \"count\": 42}\n    res = json_post(f\"/{function_type}/request_json/types\", json_data=json_data)\n    result = res.json()\n\n    assert result[\"lid\"][\"value\"] == 570\n    assert result[\"lid\"][\"type\"] == \"int\"\n    assert result[\"count\"][\"value\"] == 42\n    assert result[\"count\"][\"type\"] == \"int\"\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_json_null_type_preserved(function_type: str, session):\n    \"\"\"Test that null values in JSON are preserved as None, not string 'null'\"\"\"\n    json_data = {\"start\": None, \"end\": None}\n    res = json_post(f\"/{function_type}/request_json/types\", json_data=json_data)\n    result = res.json()\n\n    assert result[\"start\"][\"value\"] is None\n    assert result[\"start\"][\"type\"] == \"NoneType\"\n    assert result[\"end\"][\"value\"] is None\n    assert result[\"end\"][\"type\"] == \"NoneType\"\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_json_boolean_type_preserved(function_type: str, session):\n    \"\"\"Test that boolean values in JSON are preserved as booleans, not strings\"\"\"\n    json_data = {\"active\": True, \"deleted\": False}\n    res = json_post(f\"/{function_type}/request_json/types\", json_data=json_data)\n    result = res.json()\n\n    assert result[\"active\"][\"value\"] is True\n    assert result[\"active\"][\"type\"] == \"bool\"\n    assert result[\"deleted\"][\"value\"] is False\n    assert result[\"deleted\"][\"type\"] == \"bool\"\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_json_float_type_preserved(function_type: str, session):\n    \"\"\"Test that float values in JSON are preserved as floats\"\"\"\n    json_data = {\"price\": 19.99, \"rate\": 0.15}\n    res = json_post(f\"/{function_type}/request_json/types\", json_data=json_data)\n    result = res.json()\n\n    assert result[\"price\"][\"value\"] == 19.99\n    assert result[\"price\"][\"type\"] == \"float\"\n    assert result[\"rate\"][\"value\"] == 0.15\n    assert result[\"rate\"][\"type\"] == \"float\"\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_json_string_type_preserved(function_type: str, session):\n    \"\"\"Test that string values in JSON remain strings\"\"\"\n    json_data = {\"field_name\": \"mobile\", \"field_value\": \"111000111\"}\n    res = json_post(f\"/{function_type}/request_json/types\", json_data=json_data)\n    result = res.json()\n\n    assert result[\"field_name\"][\"value\"] == \"mobile\"\n    assert result[\"field_name\"][\"type\"] == \"str\"\n    assert result[\"field_value\"][\"value\"] == \"111000111\"\n    assert result[\"field_value\"][\"type\"] == \"str\"\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_json_array_type_preserved(function_type: str, session):\n    \"\"\"Test that array values in JSON are preserved as lists\"\"\"\n    json_data = {\"items\": [1, 2, 3], \"tags\": [\"a\", \"b\"]}\n    res = json_post(f\"/{function_type}/request_json/types\", json_data=json_data)\n    result = res.json()\n\n    assert result[\"items\"][\"value\"] == [1, 2, 3]\n    assert result[\"items\"][\"type\"] == \"list\"\n    assert result[\"tags\"][\"value\"] == [\"a\", \"b\"]\n    assert result[\"tags\"][\"type\"] == \"list\"\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_json_nested_object_type_preserved(function_type: str, session):\n    \"\"\"Test that nested object values in JSON are preserved as dicts\"\"\"\n    json_data = {\"user\": {\"name\": \"John\", \"age\": 30}}\n    res = json_post(f\"/{function_type}/request_json/types\", json_data=json_data)\n    result = res.json()\n\n    assert result[\"user\"][\"value\"] == {\"name\": \"John\", \"age\": 30}\n    assert result[\"user\"][\"type\"] == \"dict\"\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_json_mixed_types_preserved(function_type: str, session):\n    \"\"\"Test the exact scenario from the bug report - mixed types in one request\"\"\"\n    json_data = {\n        \"lid\": 570,\n        \"start\": None,\n        \"field_name\": \"mobile\",\n        \"field_value\": \"111000111\",\n    }\n    res = json_post(f\"/{function_type}/request_json/types\", json_data=json_data)\n    result = res.json()\n\n    # Integer should remain integer (not become \"570\")\n    assert result[\"lid\"][\"value\"] == 570\n    assert result[\"lid\"][\"type\"] == \"int\"\n\n    # None should remain None (not become \"null\")\n    assert result[\"start\"][\"value\"] is None\n    assert result[\"start\"][\"type\"] == \"NoneType\"\n\n    # Strings should remain strings\n    assert result[\"field_name\"][\"value\"] == \"mobile\"\n    assert result[\"field_name\"][\"type\"] == \"str\"\n    assert result[\"field_value\"][\"value\"] == \"111000111\"\n    assert result[\"field_value\"][\"type\"] == \"str\"\n\n\n# ===== Top-level JSON Array Parsing Tests (Issue #1145) =====\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_json_top_level_array_of_strings(function_type: str, session):\n    \"\"\"Test that request.json() handles a top-level array of strings (exact scenario from #1145)\"\"\"\n    json_data = [\"google_docs\", \"notion\"]\n    res = json_post(f\"/{function_type}/request_json/array\", json_data=json_data)\n    result = res.json()\n\n    assert result[\"type\"] == \"list\"\n    assert result[\"parsed\"] == [\"google_docs\", \"notion\"]\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_json_top_level_array_of_objects(function_type: str, session):\n    \"\"\"Test that request.json() handles a top-level array of objects\"\"\"\n    json_data = [{\"id\": 1, \"name\": \"Alice\"}, {\"id\": 2, \"name\": \"Bob\"}]\n    res = json_post(f\"/{function_type}/request_json/array\", json_data=json_data)\n    result = res.json()\n\n    assert result[\"type\"] == \"list\"\n    assert result[\"parsed\"] == [{\"id\": 1, \"name\": \"Alice\"}, {\"id\": 2, \"name\": \"Bob\"}]\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_json_top_level_empty_array(function_type: str, session):\n    \"\"\"Test that request.json() handles an empty top-level array\"\"\"\n    json_data = []\n    res = json_post(f\"/{function_type}/request_json/array\", json_data=json_data)\n    result = res.json()\n\n    assert result[\"type\"] == \"list\"\n    assert result[\"parsed\"] == []\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_json_top_level_array_of_mixed_types(function_type: str, session):\n    \"\"\"Test that request.json() handles a top-level array with mixed types\"\"\"\n    json_data = [1, \"two\", True, None, {\"key\": \"value\"}]\n    res = json_post(f\"/{function_type}/request_json/array\", json_data=json_data)\n    result = res.json()\n\n    assert result[\"type\"] == \"list\"\n    assert result[\"parsed\"] == [1, \"two\", True, None, {\"key\": \"value\"}]\n\n\n# ===== JSON List Serialization Tests (Issue #1300) =====\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_json_list_response_serialization(function_type: str, session):\n    \"\"\"Test that returning a list from a handler is properly serialized as JSON\"\"\"\n    res = get(f\"/{function_type}/json/list\")\n\n    # Check content type is application/json\n    assert res.headers[\"content-type\"] == \"application/json\"\n\n    # Check that response is valid JSON (not Python str representation)\n    result = res.json()\n    assert isinstance(result, list)\n    assert len(result) == 3\n\n    # Verify the data structure and types are correct\n    assert result[0] == {\"id\": 1, \"title\": \"First Post\", \"published\": True}\n    assert result[1] == {\"id\": 2, \"title\": \"Draft Post\", \"published\": False}\n    assert result[2] == {\"id\": 3, \"title\": \"Latest Post\", \"published\": True}\n\n    # Verify booleans are proper JSON booleans (true/false), not Python (True/False)\n    # This is implicitly tested by res.json() succeeding, but let's verify the raw response too\n    assert \"true\" in res.text.lower()\n    assert \"false\" in res.text.lower()\n    assert \"True\" not in res.text  # Python boolean should not appear\n    assert \"False\" not in res.text\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_json_empty_list_response_serialization(function_type: str, session):\n    \"\"\"Test that returning an empty list is properly serialized as JSON\"\"\"\n    res = get(f\"/{function_type}/json/list/empty\")\n\n    assert res.headers[\"content-type\"] == \"application/json\"\n    result = res.json()\n    assert result == []\n    assert res.text == \"[]\"\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_json_list_primitives_response_serialization(function_type: str, session):\n    \"\"\"Test that a list of primitives is properly serialized as JSON\"\"\"\n    res = get(f\"/{function_type}/json/list/primitives\")\n\n    assert res.headers[\"content-type\"] == \"application/json\"\n    result = res.json()\n    assert result == [1, 2, 3, \"four\", True, None]\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_json_dict_response_auto_serialization(function_type: str, session):\n    \"\"\"Test that returning a dict from a handler is properly auto-serialized as JSON\"\"\"\n    res = get(f\"/{function_type}/json/dict\")\n\n    assert res.headers[\"content-type\"] == \"application/json\"\n    result = res.json()\n    assert result[\"message\"] == f\"{function_type} dict\"\n    assert result[\"count\"] == 42\n    assert result[\"active\"] is True\n"
  },
  {
    "path": "integration_tests/test_middlewares.py",
    "content": "import pytest\n\nfrom integration_tests.helpers.http_methods_helpers import get\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_middlewares(function_type: str, session):\n    r = get(f\"/{function_type}/middlewares\")\n    headers = r.headers\n    # We do not want the request headers to be in the response\n    assert not headers.get(\"before\")\n    assert headers.get(\"after\")\n    assert r.headers.get(\"after\") == f\"{function_type}_after_request\"\n    assert r.text == f\"{function_type} middlewares after\"\n\n\n@pytest.mark.benchmark\ndef test_global_middleware(session):\n    r = get(\"/sync/global/middlewares\")\n    headers = r.headers\n    assert headers.get(\"global_after\")\n    assert r.headers.get(\"global_after\") == \"global_after_request\"\n    assert r.text == \"sync global middlewares\"\n\n\n@pytest.mark.benchmark\ndef test_response_in_before_middleware(session):\n    r = get(\"/sync/middlewares/401\", should_check_response=False)\n    assert r.status_code == 401\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\n    \"route\",\n    [\n        \"/sync/str/const\",\n        \"/async/str/const\",\n        \"/sync/dict/const\",\n        \"/async/dict/const\",\n        \"/sync/response/const\",\n        \"/async/response/const\",\n    ],\n)\ndef test_global_middleware_applied_to_const_routes(route: str, session):\n    r = get(route)\n    assert r.headers.get(\"global_after\") == \"global_after_request\", f\"Global after-request middleware was not applied to const route {route}\"\n"
  },
  {
    "path": "integration_tests/test_multipart_data.py",
    "content": "import pytest\n\nfrom integration_tests.helpers.http_methods_helpers import multipart_post\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\"])\ndef test_form_data(function_type: str, session):\n    res = multipart_post(f\"/{function_type}/form_data\", files={\"hello\": \"world\"})\n    assert \"multipart\" in res.text\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\"])\ndef test_multipart_file(function_type: str, session):\n    res = multipart_post(f\"/{function_type}/multipart-file\", files={\"hello\": \"world\"})\n    assert \"hello\" in res.text\n"
  },
  {
    "path": "integration_tests/test_openapi.py",
    "content": "import pytest\n\nfrom integration_tests.helpers.http_methods_helpers import get, json_post\nfrom robyn import Robyn\n\n\n@pytest.mark.benchmark\ndef test_custom_openapi_spec():\n    app = Robyn(__file__, openapi_file_path=\"openapi_config.json\")\n\n    openapi_spec = app.openapi.openapi_spec\n\n    assert isinstance(openapi_spec, dict)\n\n    assert \"openapi\" in openapi_spec\n    assert \"info\" in openapi_spec\n    assert \"paths\" in openapi_spec\n    assert \"components\" in openapi_spec\n    assert \"servers\" in openapi_spec\n    assert \"externalDocs\" in openapi_spec\n\n    assert openapi_spec[\"info\"][\"title\"] == \"Robyn Test API\"\n    assert openapi_spec[\"info\"][\"version\"] == \"1.0.0\"\n\n\n@pytest.mark.benchmark\ndef test_docs_handler():\n    # should_check_response = False because check_response raises a\n    # failure if the global headers are not present in the response\n    # provided we are excluding headers for /docs and /openapi.json\n    html_response = get(\"/docs\", should_check_response=False)\n    assert html_response.status_code == 200\n\n\n@pytest.mark.benchmark\ndef test_json_handler():\n    openapi_response = get(\"/openapi.json\", should_check_response=False)\n\n    assert openapi_response.status_code == 200\n\n    openapi_spec = openapi_response.json()\n\n    assert isinstance(openapi_spec, dict)\n    assert \"openapi\" in openapi_spec\n    assert \"info\" in openapi_spec\n    assert \"paths\" in openapi_spec\n    assert \"components\" in openapi_spec\n    assert \"servers\" in openapi_spec\n    assert \"externalDocs\" in openapi_spec\n\n\n@pytest.mark.benchmark\ndef test_add_openapi_path():\n    openapi_response = get(\"/openapi.json\", should_check_response=False)\n\n    assert openapi_response.status_code == 200\n\n    openapi_spec = openapi_response.json()\n\n    assert isinstance(openapi_spec, dict)\n\n    route_type = \"get\"\n    endpoint = \"/openapi_test\"\n    openapi_description = \"Get openapi\"\n    openapi_tags = [\"test tag\"]\n\n    assert endpoint in openapi_spec[\"paths\"]\n    assert route_type in openapi_spec[\"paths\"][endpoint]\n    assert openapi_description == openapi_spec[\"paths\"][endpoint][route_type][\"description\"]\n    assert openapi_tags == openapi_spec[\"paths\"][endpoint][route_type][\"tags\"]\n\n\n@pytest.mark.benchmark\ndef test_add_subrouter_paths():\n    openapi_response = get(\"/openapi.json\", should_check_response=False)\n\n    assert openapi_response.status_code == 200\n\n    openapi_spec = openapi_response.json()\n\n    assert isinstance(openapi_spec, dict)\n\n    route_type = \"post\"\n    endpoint = \"/sub_router/openapi_test\"\n    openapi_description = \"Get subrouter openapi\"\n    openapi_tags = [\"test subrouter tag\"]\n\n    assert endpoint in openapi_spec[\"paths\"]\n    assert route_type in openapi_spec[\"paths\"][endpoint]\n    assert openapi_description == openapi_spec[\"paths\"][endpoint][route_type][\"description\"]\n    assert openapi_tags == openapi_spec[\"paths\"][endpoint][route_type][\"tags\"]\n\n\n@pytest.mark.benchmark\ndef test_openapi_request_body():\n    openapi_response = get(\"/openapi.json\", should_check_response=False)\n\n    assert openapi_response.status_code == 200\n\n    openapi_spec = openapi_response.json()\n\n    assert isinstance(openapi_spec, dict)\n\n    route_type = \"post\"\n    endpoint = \"/openapi_request_body\"\n\n    assert endpoint in openapi_spec[\"paths\"]\n    assert route_type in openapi_spec[\"paths\"][endpoint]\n    assert \"requestBody\" in openapi_spec[\"paths\"][endpoint][route_type]\n    assert \"content\" in openapi_spec[\"paths\"][endpoint][route_type][\"requestBody\"]\n    assert \"application/json\" in openapi_spec[\"paths\"][endpoint][route_type][\"requestBody\"][\"content\"]\n    assert \"schema\" in openapi_spec[\"paths\"][endpoint][route_type][\"requestBody\"][\"content\"][\"application/json\"]\n    assert \"properties\" in openapi_spec[\"paths\"][endpoint][route_type][\"requestBody\"][\"content\"][\"application/json\"][\"schema\"]\n\n    assert \"name\" in openapi_spec[\"paths\"][endpoint][route_type][\"requestBody\"][\"content\"][\"application/json\"][\"schema\"][\"properties\"]\n    assert \"description\" in openapi_spec[\"paths\"][endpoint][route_type][\"requestBody\"][\"content\"][\"application/json\"][\"schema\"][\"properties\"]\n    assert \"price\" in openapi_spec[\"paths\"][endpoint][route_type][\"requestBody\"][\"content\"][\"application/json\"][\"schema\"][\"properties\"]\n    assert \"tax\" in openapi_spec[\"paths\"][endpoint][route_type][\"requestBody\"][\"content\"][\"application/json\"][\"schema\"][\"properties\"]\n\n    assert \"string\" == openapi_spec[\"paths\"][endpoint][route_type][\"requestBody\"][\"content\"][\"application/json\"][\"schema\"][\"properties\"][\"description\"][\"type\"]\n    assert \"number\" == openapi_spec[\"paths\"][endpoint][route_type][\"requestBody\"][\"content\"][\"application/json\"][\"schema\"][\"properties\"][\"price\"][\"type\"]\n    assert \"number\" == openapi_spec[\"paths\"][endpoint][route_type][\"requestBody\"][\"content\"][\"application/json\"][\"schema\"][\"properties\"][\"tax\"][\"type\"]\n\n    assert \"object\" == openapi_spec[\"paths\"][endpoint][route_type][\"requestBody\"][\"content\"][\"application/json\"][\"schema\"][\"properties\"][\"name\"][\"type\"]\n\n    assert \"first\" in openapi_spec[\"paths\"][endpoint][route_type][\"requestBody\"][\"content\"][\"application/json\"][\"schema\"][\"properties\"][\"name\"][\"properties\"]\n    assert \"second\" in openapi_spec[\"paths\"][endpoint][route_type][\"requestBody\"][\"content\"][\"application/json\"][\"schema\"][\"properties\"][\"name\"][\"properties\"]\n    assert \"initial\" in openapi_spec[\"paths\"][endpoint][route_type][\"requestBody\"][\"content\"][\"application/json\"][\"schema\"][\"properties\"][\"name\"][\"properties\"]\n\n    assert (\n        \"object\"\n        in openapi_spec[\"paths\"][endpoint][route_type][\"requestBody\"][\"content\"][\"application/json\"][\"schema\"][\"properties\"][\"name\"][\"properties\"][\"initial\"][\n            \"type\"\n        ]\n    )\n\n    assert (\n        \"is_present\"\n        in openapi_spec[\"paths\"][endpoint][route_type][\"requestBody\"][\"content\"][\"application/json\"][\"schema\"][\"properties\"][\"name\"][\"properties\"][\"initial\"][\n            \"properties\"\n        ]\n    )\n    assert (\n        \"letter\"\n        in openapi_spec[\"paths\"][endpoint][route_type][\"requestBody\"][\"content\"][\"application/json\"][\"schema\"][\"properties\"][\"name\"][\"properties\"][\"initial\"][\n            \"properties\"\n        ]\n    )\n\n    assert {\"type\": \"string\"} in openapi_spec[\"paths\"][endpoint][route_type][\"requestBody\"][\"content\"][\"application/json\"][\"schema\"][\"properties\"][\"name\"][\n        \"properties\"\n    ][\"initial\"][\"properties\"][\"letter\"][\"anyOf\"]\n    assert {\"type\": \"null\"} in openapi_spec[\"paths\"][endpoint][route_type][\"requestBody\"][\"content\"][\"application/json\"][\"schema\"][\"properties\"][\"name\"][\n        \"properties\"\n    ][\"initial\"][\"properties\"][\"letter\"][\"anyOf\"]\n\n\n@pytest.mark.benchmark\ndef test_openapi_response_body():\n    openapi_response = get(\"/openapi.json\", should_check_response=False)\n\n    assert openapi_response.status_code == 200\n\n    openapi_spec = openapi_response.json()\n\n    assert isinstance(openapi_spec, dict)\n\n    route_type = \"post\"\n    endpoint = \"/openapi_request_body\"\n\n    assert endpoint in openapi_spec[\"paths\"]\n    assert route_type in openapi_spec[\"paths\"][endpoint]\n    assert \"responses\" in openapi_spec[\"paths\"][endpoint][route_type]\n    assert \"200\" in openapi_spec[\"paths\"][endpoint][route_type][\"responses\"]\n\n    assert openapi_spec[\"paths\"][endpoint][route_type][\"responses\"][\"200\"][\"description\"] == \"Successful Response\"\n\n    assert \"content\" in openapi_spec[\"paths\"][endpoint][route_type][\"responses\"][\"200\"]\n\n    assert \"application/json\" in openapi_spec[\"paths\"][endpoint][route_type][\"responses\"][\"200\"][\"content\"]\n    assert \"schema\" in openapi_spec[\"paths\"][endpoint][route_type][\"responses\"][\"200\"][\"content\"][\"application/json\"]\n    assert \"properties\" in openapi_spec[\"paths\"][endpoint][route_type][\"responses\"][\"200\"][\"content\"][\"application/json\"][\"schema\"]\n\n    assert \"success\" in openapi_spec[\"paths\"][endpoint][route_type][\"responses\"][\"200\"][\"content\"][\"application/json\"][\"schema\"][\"properties\"]\n    assert \"items_changed\" in openapi_spec[\"paths\"][endpoint][route_type][\"responses\"][\"200\"][\"content\"][\"application/json\"][\"schema\"][\"properties\"]\n\n    assert (\n        \"boolean\" == openapi_spec[\"paths\"][endpoint][route_type][\"responses\"][\"200\"][\"content\"][\"application/json\"][\"schema\"][\"properties\"][\"success\"][\"type\"]\n    )\n\n    assert (\n        \"integer\"\n        == openapi_spec[\"paths\"][endpoint][route_type][\"responses\"][\"200\"][\"content\"][\"application/json\"][\"schema\"][\"properties\"][\"items_changed\"][\"type\"]\n    )\n\n\n@pytest.mark.benchmark\ndef test_openapi_query_params():\n    openapi_response = get(\"/openapi.json\", should_check_response=False)\n\n    assert openapi_response.status_code == 200\n\n    openapi_spec = openapi_response.json()\n\n    assert isinstance(openapi_spec, dict)\n\n    route_type = \"post\"\n    endpoint = \"/openapi_request_body\"\n\n    assert endpoint in openapi_spec[\"paths\"]\n    assert route_type in openapi_spec[\"paths\"][endpoint]\n    assert \"parameters\" in openapi_spec[\"paths\"][endpoint][route_type]\n\n    assert \"required\" == openapi_spec[\"paths\"][endpoint][route_type][\"parameters\"][0][\"name\"]\n    assert \"query\" == openapi_spec[\"paths\"][endpoint][route_type][\"parameters\"][0][\"in\"]\n    assert {\"type\": \"boolean\"} == openapi_spec[\"paths\"][endpoint][route_type][\"parameters\"][0][\"schema\"]\n\n\n@pytest.mark.benchmark\ndef test_openapi_json_body_typed():\n    \"\"\"Test that a typed JsonBody subclass generates a proper requestBody schema in OpenAPI docs.\"\"\"\n    openapi_response = get(\"/openapi.json\", should_check_response=False)\n\n    assert openapi_response.status_code == 200\n\n    openapi_spec = openapi_response.json()\n\n    assert isinstance(openapi_spec, dict)\n\n    route_type = \"post\"\n    endpoint = \"/openapi_json_body\"\n\n    assert endpoint in openapi_spec[\"paths\"]\n    assert route_type in openapi_spec[\"paths\"][endpoint]\n    assert \"requestBody\" in openapi_spec[\"paths\"][endpoint][route_type]\n    assert \"content\" in openapi_spec[\"paths\"][endpoint][route_type][\"requestBody\"]\n    assert \"application/json\" in openapi_spec[\"paths\"][endpoint][route_type][\"requestBody\"][\"content\"]\n    assert \"schema\" in openapi_spec[\"paths\"][endpoint][route_type][\"requestBody\"][\"content\"][\"application/json\"]\n    assert \"properties\" in openapi_spec[\"paths\"][endpoint][route_type][\"requestBody\"][\"content\"][\"application/json\"][\"schema\"]\n\n    properties = openapi_spec[\"paths\"][endpoint][route_type][\"requestBody\"][\"content\"][\"application/json\"][\"schema\"][\"properties\"]\n    assert \"fahrenheit\" in properties\n    assert \"number\" == properties[\"fahrenheit\"][\"type\"]\n\n\n@pytest.mark.benchmark\ndef test_openapi_json_body_bare():\n    \"\"\"Test that a bare JsonBody generates a requestBody with empty properties in OpenAPI docs.\"\"\"\n    openapi_response = get(\"/openapi.json\", should_check_response=False)\n\n    assert openapi_response.status_code == 200\n\n    openapi_spec = openapi_response.json()\n\n    assert isinstance(openapi_spec, dict)\n\n    route_type = \"post\"\n    # bare JsonBody routes should still have requestBody in the spec\n    endpoint = \"/sync/json_body/bare\"\n\n    assert endpoint in openapi_spec[\"paths\"]\n    assert route_type in openapi_spec[\"paths\"][endpoint]\n    assert \"requestBody\" in openapi_spec[\"paths\"][endpoint][route_type]\n    assert \"content\" in openapi_spec[\"paths\"][endpoint][route_type][\"requestBody\"]\n    assert \"application/json\" in openapi_spec[\"paths\"][endpoint][route_type][\"requestBody\"][\"content\"]\n\n\ntry:\n    import pydantic  # noqa: F401\n\n    _HAS_PYDANTIC = True\nexcept ImportError:\n    _HAS_PYDANTIC = False\n\n\n@pytest.mark.benchmark\n@pytest.mark.skipif(not _HAS_PYDANTIC, reason=\"pydantic not installed\")\ndef test_openapi_pydantic_request_body():\n    \"\"\"Pydantic model on a regular route should produce a full JSON Schema in\n    requestBody — no dedicated OpenAPI route needed.\"\"\"\n    openapi_response = get(\"/openapi.json\", should_check_response=False)\n    assert openapi_response.status_code == 200\n    openapi_spec = openapi_response.json()\n\n    endpoint = \"/sync/pydantic/user\"\n    route = openapi_spec[\"paths\"][endpoint][\"post\"]\n\n    assert route[\"tags\"] == [\"pydantic\"]\n    assert route[\"description\"] == \"Create a user with Pydantic validation\"\n\n    assert \"requestBody\" in route\n    schema = route[\"requestBody\"][\"content\"][\"application/json\"][\"schema\"]\n\n    assert schema[\"type\"] == \"object\"\n    assert schema[\"title\"] == \"UserCreate\"\n\n    props = schema[\"properties\"]\n    assert props[\"name\"][\"type\"] == \"string\"\n    assert props[\"name\"][\"title\"] == \"Name\"\n    assert props[\"email\"][\"type\"] == \"string\"\n    assert props[\"email\"][\"title\"] == \"Email\"\n    assert props[\"age\"][\"type\"] == \"integer\"\n    assert props[\"age\"][\"title\"] == \"Age\"\n    assert props[\"active\"][\"type\"] == \"boolean\"\n    assert props[\"active\"][\"title\"] == \"Active\"\n    assert props[\"active\"][\"default\"] is True\n\n    assert set(schema[\"required\"]) == {\"name\", \"email\", \"age\"}\n    assert \"active\" not in schema[\"required\"]\n\n    assert \"responses\" in route\n    assert \"200\" in route[\"responses\"]\n    assert \"application/json\" in route[\"responses\"][\"200\"][\"content\"]\n\n\n@pytest.mark.benchmark\n@pytest.mark.skipif(not _HAS_PYDANTIC, reason=\"pydantic not installed\")\ndef test_openapi_pydantic_nested_model():\n    \"\"\"Nested Pydantic models on a regular route should use $ref and populate\n    components/schemas — no dedicated OpenAPI route needed.\"\"\"\n    openapi_response = get(\"/openapi.json\", should_check_response=False)\n    assert openapi_response.status_code == 200\n    openapi_spec = openapi_response.json()\n\n    endpoint = \"/sync/pydantic/nested\"\n    route = openapi_spec[\"paths\"][endpoint][\"post\"]\n\n    assert route[\"tags\"] == [\"pydantic\"]\n    assert route[\"description\"] == \"Create a user with nested address\"\n\n    schema = route[\"requestBody\"][\"content\"][\"application/json\"][\"schema\"]\n    assert schema[\"type\"] == \"object\"\n    assert schema[\"title\"] == \"UserWithAddress\"\n\n    assert schema[\"properties\"][\"name\"][\"type\"] == \"string\"\n    assert schema[\"properties\"][\"email\"][\"type\"] == \"string\"\n    assert schema[\"properties\"][\"address\"][\"$ref\"] == \"#/components/schemas/Address\"\n\n    assert set(schema[\"required\"]) == {\"name\", \"email\", \"address\"}\n\n    assert \"Address\" in openapi_spec[\"components\"][\"schemas\"]\n    address_schema = openapi_spec[\"components\"][\"schemas\"][\"Address\"]\n    assert address_schema[\"type\"] == \"object\"\n    assert address_schema[\"title\"] == \"Address\"\n\n    assert address_schema[\"properties\"][\"street\"][\"type\"] == \"string\"\n    assert address_schema[\"properties\"][\"street\"][\"title\"] == \"Street\"\n    assert address_schema[\"properties\"][\"city\"][\"type\"] == \"string\"\n    assert address_schema[\"properties\"][\"city\"][\"title\"] == \"City\"\n    assert address_schema[\"properties\"][\"zip_code\"][\"type\"] == \"string\"\n    assert address_schema[\"properties\"][\"zip_code\"][\"title\"] == \"Zip Code\"\n\n    assert set(address_schema[\"required\"]) == {\"street\", \"city\", \"zip_code\"}\n\n    assert \"responses\" in route\n    assert \"200\" in route[\"responses\"]\n    assert \"application/json\" in route[\"responses\"][\"200\"][\"content\"]\n\n\n@pytest.mark.benchmark\n@pytest.mark.skipif(not _HAS_PYDANTIC, reason=\"pydantic not installed\")\ndef test_openapi_pydantic_return_type():\n    \"\"\"When a route has a Pydantic model as return type annotation, the response\n    schema should reflect the full Pydantic model schema, not just 'object'.\"\"\"\n    openapi_response = get(\"/openapi.json\", should_check_response=False)\n    assert openapi_response.status_code == 200\n    openapi_spec = openapi_response.json()\n\n    endpoint = \"/sync/pydantic/return_model\"\n    route = openapi_spec[\"paths\"][endpoint][\"post\"]\n\n    assert route[\"tags\"] == [\"pydantic\"]\n    assert route[\"description\"] == \"Return the validated Pydantic model directly\"\n\n    response_schema = route[\"responses\"][\"200\"][\"content\"][\"application/json\"][\"schema\"]\n    assert response_schema[\"type\"] == \"object\"\n    assert response_schema[\"title\"] == \"UserCreate\"\n    assert \"properties\" in response_schema\n    assert response_schema[\"properties\"][\"name\"][\"type\"] == \"string\"\n    assert response_schema[\"properties\"][\"age\"][\"type\"] == \"integer\"\n    assert set(response_schema[\"required\"]) == {\"name\", \"email\", \"age\"}\n\n\n@pytest.mark.benchmark\n@pytest.mark.skipif(not _HAS_PYDANTIC, reason=\"pydantic not installed\")\ndef test_openapi_pydantic_return_list_type():\n    \"\"\"When a route returns list[PydanticModel], the response schema should be\n    an array with items containing the full Pydantic model schema.\"\"\"\n    openapi_response = get(\"/openapi.json\", should_check_response=False)\n    assert openapi_response.status_code == 200\n    openapi_spec = openapi_response.json()\n\n    endpoint = \"/sync/pydantic/return_list\"\n    route = openapi_spec[\"paths\"][endpoint][\"post\"]\n\n    assert route[\"tags\"] == [\"pydantic\"]\n    assert route[\"description\"] == \"Return a list of Pydantic models\"\n\n    response_schema = route[\"responses\"][\"200\"][\"content\"][\"application/json\"][\"schema\"]\n    assert response_schema[\"type\"] == \"array\"\n    assert \"items\" in response_schema\n\n    items = response_schema[\"items\"]\n    assert items[\"type\"] == \"object\"\n    assert items[\"title\"] == \"UserCreate\"\n    assert items[\"properties\"][\"name\"][\"type\"] == \"string\"\n    assert items[\"properties\"][\"age\"][\"type\"] == \"integer\"\n\n\n# ===== TypedDict request body tests =====\n\n\n@pytest.mark.benchmark\ndef test_openapi_typeddict_request_body():\n    \"\"\"TypedDict subclass used as a parameter annotation should produce a\n    requestBody schema in OpenAPI docs (issue #1254).\"\"\"\n    openapi_response = get(\"/openapi.json\", should_check_response=False)\n    assert openapi_response.status_code == 200\n    openapi_spec = openapi_response.json()\n\n    endpoint = \"/sync/typeddict/body\"\n    route = openapi_spec[\"paths\"][endpoint][\"post\"]\n\n    assert route[\"tags\"] == [\"typeddict\"]\n    assert \"requestBody\" in route\n    schema = route[\"requestBody\"][\"content\"][\"application/json\"][\"schema\"]\n    assert \"properties\" in schema\n    assert \"name\" in schema[\"properties\"]\n    assert \"value\" in schema[\"properties\"]\n    assert schema[\"properties\"][\"name\"][\"type\"] == \"string\"\n    assert schema[\"properties\"][\"value\"][\"type\"] == \"integer\"\n\n    assert \"responses\" in route\n    assert \"200\" in route[\"responses\"]\n    response_schema = route[\"responses\"][\"200\"][\"content\"][\"application/json\"][\"schema\"]\n    assert \"properties\" in response_schema\n    assert \"result\" in response_schema[\"properties\"]\n    assert \"count\" in response_schema[\"properties\"]\n\n\n@pytest.mark.benchmark\ndef test_openapi_typeddict_with_request():\n    \"\"\"TypedDict body combined with a Request param should still produce\n    a requestBody schema (issue #1254).\"\"\"\n    openapi_response = get(\"/openapi.json\", should_check_response=False)\n    assert openapi_response.status_code == 200\n    openapi_spec = openapi_response.json()\n\n    endpoint = \"/sync/typeddict/with_request\"\n    route = openapi_spec[\"paths\"][endpoint][\"post\"]\n\n    assert \"requestBody\" in route\n    schema = route[\"requestBody\"][\"content\"][\"application/json\"][\"schema\"]\n    assert \"properties\" in schema\n    assert \"name\" in schema[\"properties\"]\n    assert \"value\" in schema[\"properties\"]\n\n\n@pytest.mark.benchmark\ndef test_typeddict_body_injection_sync():\n    \"\"\"TypedDict-annotated parameter should receive the parsed JSON dict\n    at runtime, not the raw string (issue #1254).\"\"\"\n    response = json_post(\n        \"/sync/typeddict/body\",\n        json_data={\"name\": \"alice\", \"value\": 42},\n    )\n    assert response.status_code == 200\n    data = response.json()\n    assert data[\"result\"] == \"alice\"\n    assert data[\"count\"] == 42\n\n\n@pytest.mark.benchmark\ndef test_typeddict_body_injection_async():\n    \"\"\"Async handler with TypedDict body should also receive parsed JSON.\"\"\"\n    response = json_post(\n        \"/async/typeddict/body\",\n        json_data={\"name\": \"bob\", \"value\": 7},\n    )\n    assert response.status_code == 200\n    data = response.json()\n    assert data[\"result\"] == \"bob\"\n    assert data[\"count\"] == 7\n\n\n@pytest.mark.benchmark\ndef test_typeddict_body_with_request_injection():\n    \"\"\"TypedDict body and Request object should coexist in the same handler.\"\"\"\n    response = json_post(\n        \"/sync/typeddict/with_request\",\n        json_data={\"name\": \"charlie\", \"value\": 99},\n    )\n    assert response.status_code == 200\n    data = response.json()\n    assert data[\"method\"] == \"POST\"\n    assert data[\"name\"] == \"charlie\"\n\n\n@pytest.mark.benchmark\ndef test_typeddict_body_invalid_json():\n    \"\"\"Sending invalid JSON to a TypedDict-annotated handler should return 400.\"\"\"\n    import requests\n\n    response = requests.post(\n        \"http://127.0.0.1:8080/sync/typeddict/body\",\n        data=\"not valid json\",\n        headers={\"Content-Type\": \"application/json\"},\n    )\n    assert response.status_code == 400\n"
  },
  {
    "path": "integration_tests/test_patch_requests.py",
    "content": "import pytest\n\nfrom integration_tests.helpers.http_methods_helpers import patch\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_patch(function_type: str, session):\n    res = patch(f\"/{function_type}/dict\")\n    assert res.text == f\"{function_type} dict patch\"\n    assert function_type in res.headers\n    assert res.headers[function_type] == \"dict\"\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_patch_with_param(function_type: str, session):\n    res = patch(f\"/{function_type}/body\", data={\"hello\": \"world\"})\n    assert res.text == \"hello=world\"\n"
  },
  {
    "path": "integration_tests/test_post_requests.py",
    "content": "import pytest\n\nfrom integration_tests.helpers.http_methods_helpers import post\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_post(function_type: str, session):\n    res = post(f\"/{function_type}/dict\")\n    assert res.text == f\"{function_type} dict post\"\n    assert function_type in res.headers\n    assert res.headers[function_type] == \"dict\"\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_post_with_param(function_type: str, session):\n    res = post(f\"/{function_type}/body\", data={\"hello\": \"world\"})\n    assert res.text == \"hello=world\"\n"
  },
  {
    "path": "integration_tests/test_put_requests.py",
    "content": "import pytest\n\nfrom integration_tests.helpers.http_methods_helpers import put\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_put(function_type: str, session):\n    res = put(f\"/{function_type}/dict\")\n    assert res.text == f\"{function_type} dict put\"\n    assert function_type in res.headers\n    assert res.headers[function_type] == \"dict\"\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_put_with_param(function_type: str, session):\n    res = put(f\"/{function_type}/body\", data={\"hello\": \"world\"})\n    assert res.text == \"hello=world\"\n"
  },
  {
    "path": "integration_tests/test_pydantic.py",
    "content": "import pytest\nimport requests\n\nfrom integration_tests.helpers.http_methods_helpers import json_post\n\nBASE_URL = \"http://127.0.0.1:8080\"\n\ntry:\n    import pydantic\n\n    _HAS_PYDANTIC = True\nexcept ImportError:\n    _HAS_PYDANTIC = False\n\npytestmark = pytest.mark.skipif(not _HAS_PYDANTIC, reason=\"pydantic not installed\")\n\n\ndef _raw_post(endpoint: str, data: str, content_type: str = \"application/json\") -> requests.Response:\n    url = f\"{BASE_URL}/{endpoint.lstrip('/')}\"\n    return requests.post(url, data=data, headers={\"Content-Type\": content_type})\n\n\n# ===== Valid Pydantic Body =====\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_pydantic_valid_user_all_fields(function_type: str, session):\n    \"\"\"All fields provided explicitly — every field value must round-trip correctly.\"\"\"\n    json_data = {\"name\": \"Alice\", \"email\": \"alice@example.com\", \"age\": 30, \"active\": False}\n    res = json_post(f\"/{function_type}/pydantic/user\", json_data=json_data)\n    result = res.json()\n\n    assert result[\"name\"] == \"Alice\"\n    assert result[\"email\"] == \"alice@example.com\"\n    assert result[\"age\"] == 30\n    assert result[\"active\"] is False\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_pydantic_default_field_applied(function_type: str, session):\n    \"\"\"Omitting 'active' should use the model default (True) and the handler must see it.\"\"\"\n    json_data = {\"name\": \"Bob\", \"email\": \"bob@example.com\", \"age\": 25}\n    res = json_post(f\"/{function_type}/pydantic/user\", json_data=json_data)\n    result = res.json()\n\n    assert result[\"name\"] == \"Bob\"\n    assert result[\"email\"] == \"bob@example.com\"\n    assert result[\"age\"] == 25\n    assert result[\"active\"] is True\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_pydantic_string_to_int_coercion(function_type: str, session):\n    \"\"\"Pydantic v2 in lax mode (default) coerces '30' string to int 30.\"\"\"\n    json_data = {\"name\": \"Coerce\", \"email\": \"c@test.com\", \"age\": \"30\"}\n    res = json_post(f\"/{function_type}/pydantic/user\", json_data=json_data)\n    result = res.json()\n\n    assert result[\"name\"] == \"Coerce\"\n    assert result[\"age\"] == 30\n    assert isinstance(result[\"age\"], int)\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_pydantic_extra_fields_ignored(function_type: str, session):\n    \"\"\"Extra fields not in the model should be silently ignored (pydantic v2 default).\"\"\"\n    json_data = {\"name\": \"Eve\", \"email\": \"eve@example.com\", \"age\": 28, \"extra_field\": \"should_be_ignored\", \"another\": 99}\n    res = json_post(f\"/{function_type}/pydantic/user\", json_data=json_data)\n    result = res.json()\n\n    assert result[\"name\"] == \"Eve\"\n    assert result[\"age\"] == 28\n    assert \"extra_field\" not in result\n    assert \"another\" not in result\n\n\n# ===== Invalid Pydantic Body — error structure verification =====\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_pydantic_missing_single_required_field(function_type: str, session):\n    \"\"\"Missing 'age' should produce exactly one error with correct loc, type, and msg.\"\"\"\n    json_data = {\"name\": \"Charlie\", \"email\": \"charlie@example.com\"}\n    res = json_post(\n        f\"/{function_type}/pydantic/user\",\n        json_data=json_data,\n        expected_status_code=422,\n        should_check_response=False,\n    )\n    assert res.status_code == 422\n    result = res.json()\n    assert result[\"error\"] == \"Validation Error\"\n\n    errors = result[\"detail\"]\n    assert isinstance(errors, list)\n    assert len(errors) == 1\n\n    err = errors[0]\n    assert err[\"loc\"] == [\"age\"]\n    assert err[\"type\"] == \"missing\"\n    assert \"required\" in err[\"msg\"].lower()\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_pydantic_missing_all_required_fields(function_type: str, session):\n    \"\"\"Sending {} should produce errors for all 3 required fields (name, email, age).\"\"\"\n    res = json_post(\n        f\"/{function_type}/pydantic/user\",\n        json_data={},\n        expected_status_code=422,\n        should_check_response=False,\n    )\n    assert res.status_code == 422\n    result = res.json()\n    assert result[\"error\"] == \"Validation Error\"\n\n    errors = result[\"detail\"]\n    assert isinstance(errors, list)\n    error_fields = {tuple(e[\"loc\"]) for e in errors}\n    assert (\"name\",) in error_fields\n    assert (\"email\",) in error_fields\n    assert (\"age\",) in error_fields\n\n    for err in errors:\n        assert err[\"type\"] == \"missing\"\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_pydantic_wrong_type_error_detail(function_type: str, session):\n    \"\"\"Wrong type should produce error with correct loc and a meaningful msg.\"\"\"\n    json_data = {\"name\": \"Diana\", \"email\": \"diana@example.com\", \"age\": \"not_a_number\"}\n    res = json_post(\n        f\"/{function_type}/pydantic/user\",\n        json_data=json_data,\n        expected_status_code=422,\n        should_check_response=False,\n    )\n    assert res.status_code == 422\n    result = res.json()\n    assert result[\"error\"] == \"Validation Error\"\n\n    errors = result[\"detail\"]\n    age_errors = [e for e in errors if e[\"loc\"] == [\"age\"]]\n    assert len(age_errors) == 1\n    assert \"int\" in age_errors[0][\"type\"]\n    assert \"input\" in age_errors[0]\n    assert age_errors[0][\"input\"] == \"not_a_number\"\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_pydantic_multiple_type_errors(function_type: str, session):\n    \"\"\"Multiple fields with wrong types should each produce their own error.\"\"\"\n    json_data = {\"name\": 12345, \"email\": True, \"age\": \"bad\"}\n    res = json_post(\n        f\"/{function_type}/pydantic/user\",\n        json_data=json_data,\n        expected_status_code=422,\n        should_check_response=False,\n    )\n    assert res.status_code == 422\n    result = res.json()\n    error_locs = {tuple(e[\"loc\"]) for e in result[\"detail\"]}\n    assert (\"name\",) in error_locs\n    assert (\"email\",) in error_locs\n    assert (\"age\",) in error_locs\n\n\n# ===== Malformed / edge-case bodies =====\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_pydantic_invalid_json_syntax(function_type: str, session):\n    \"\"\"Completely invalid JSON should return 422 with 'Invalid request body' error.\"\"\"\n    res = _raw_post(f\"/{function_type}/pydantic/user\", data=\"not json at all {{{\")\n    assert res.status_code == 422\n    result = res.json()\n    assert \"error\" in result\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_pydantic_empty_body(function_type: str, session):\n    \"\"\"Empty body should return 422.\"\"\"\n    res = _raw_post(f\"/{function_type}/pydantic/user\", data=\"\")\n    assert res.status_code == 422\n    result = res.json()\n    assert \"error\" in result\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_pydantic_json_array_body(function_type: str, session):\n    \"\"\"A JSON array instead of an object should return 422.\"\"\"\n    res = _raw_post(f\"/{function_type}/pydantic/user\", data='[{\"name\": \"X\"}]')\n    assert res.status_code == 422\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_pydantic_null_body(function_type: str, session):\n    \"\"\"JSON null body should return 422.\"\"\"\n    res = _raw_post(f\"/{function_type}/pydantic/user\", data=\"null\")\n    assert res.status_code == 422\n\n\n# ===== Pydantic + Request object =====\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_pydantic_with_request_object(function_type: str, session):\n    \"\"\"Handler receiving both Request and Pydantic model must see both correctly.\"\"\"\n    json_data = {\"name\": \"Frank\", \"email\": \"frank@example.com\", \"age\": 35}\n    res = json_post(f\"/{function_type}/pydantic/user_with_request\", json_data=json_data)\n    result = res.json()\n\n    assert result[\"method\"] == \"POST\"\n    assert result[\"name\"] == \"Frank\"\n    assert result[\"email\"] == \"frank@example.com\"\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_pydantic_with_request_validation_still_works(function_type: str, session):\n    \"\"\"Validation must still trigger 422 even when Request is in the signature.\"\"\"\n    json_data = {\"name\": \"Frank\"}  # missing email and age\n    res = json_post(\n        f\"/{function_type}/pydantic/user_with_request\",\n        json_data=json_data,\n        expected_status_code=422,\n        should_check_response=False,\n    )\n    assert res.status_code == 422\n    result = res.json()\n    assert result[\"error\"] == \"Validation Error\"\n    error_fields = {tuple(e[\"loc\"]) for e in result[\"detail\"]}\n    assert (\"email\",) in error_fields\n    assert (\"age\",) in error_fields\n\n\n# ===== Nested Pydantic Models =====\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_pydantic_nested_model_valid(function_type: str, session):\n    \"\"\"Valid nested model should be parsed and accessible through the parent.\"\"\"\n    json_data = {\n        \"name\": \"Grace\",\n        \"email\": \"grace@example.com\",\n        \"address\": {\"street\": \"123 Main St\", \"city\": \"Springfield\", \"zip_code\": \"62701\"},\n    }\n    res = json_post(f\"/{function_type}/pydantic/nested\", json_data=json_data)\n    result = res.json()\n\n    assert result[\"name\"] == \"Grace\"\n    assert result[\"city\"] == \"Springfield\"\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_pydantic_nested_model_missing_nested_fields(function_type: str, session):\n    \"\"\"Missing fields in nested model should produce errors with correct nested loc paths.\"\"\"\n    json_data = {\n        \"name\": \"Grace\",\n        \"email\": \"grace@example.com\",\n        \"address\": {\"street\": \"123 Main St\"},  # missing city and zip_code\n    }\n    res = json_post(\n        f\"/{function_type}/pydantic/nested\",\n        json_data=json_data,\n        expected_status_code=422,\n        should_check_response=False,\n    )\n    assert res.status_code == 422\n    result = res.json()\n    assert result[\"error\"] == \"Validation Error\"\n\n    errors = result[\"detail\"]\n    error_locs = {tuple(e[\"loc\"]) for e in errors}\n    assert (\"address\", \"city\") in error_locs\n    assert (\"address\", \"zip_code\") in error_locs\n\n    for err in errors:\n        assert err[\"type\"] == \"missing\"\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_pydantic_nested_model_missing_entirely(function_type: str, session):\n    \"\"\"Missing the entire nested object should produce an error at the parent field.\"\"\"\n    json_data = {\"name\": \"Grace\", \"email\": \"grace@example.com\"}\n    res = json_post(\n        f\"/{function_type}/pydantic/nested\",\n        json_data=json_data,\n        expected_status_code=422,\n        should_check_response=False,\n    )\n    assert res.status_code == 422\n    result = res.json()\n\n    errors = result[\"detail\"]\n    address_errors = [e for e in errors if e[\"loc\"] == [\"address\"]]\n    assert len(address_errors) == 1\n    assert address_errors[0][\"type\"] == \"missing\"\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_pydantic_nested_model_wrong_type(function_type: str, session):\n    \"\"\"Passing a non-object for the nested model should return 422.\"\"\"\n    json_data = {\n        \"name\": \"Grace\",\n        \"email\": \"grace@example.com\",\n        \"address\": \"not an object\",\n    }\n    res = json_post(\n        f\"/{function_type}/pydantic/nested\",\n        json_data=json_data,\n        expected_status_code=422,\n        should_check_response=False,\n    )\n    assert res.status_code == 422\n    result = res.json()\n    errors = result[\"detail\"]\n    address_errors = [e for e in errors if \"address\" in e[\"loc\"]]\n    assert len(address_errors) >= 1\n\n\n# ===== Pydantic with PUT =====\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_pydantic_put_valid(function_type: str, session):\n    \"\"\"Pydantic validation must work with PUT method.\"\"\"\n    json_data = {\"name\": \"Hank\", \"email\": \"hank@example.com\", \"age\": 40}\n    res = requests.put(f\"{BASE_URL}/{function_type}/pydantic/user\", json=json_data)\n    result = res.json()\n\n    assert result[\"updated\"] is True\n    assert result[\"name\"] == \"Hank\"\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_pydantic_put_invalid(function_type: str, session):\n    \"\"\"PUT with invalid body must also return 422 with proper error structure.\"\"\"\n    json_data = {\"name\": \"Hank\"}  # missing email and age\n    res = requests.put(f\"{BASE_URL}/{function_type}/pydantic/user\", json=json_data)\n    assert res.status_code == 422\n    result = res.json()\n    assert result[\"error\"] == \"Validation Error\"\n    error_fields = {tuple(e[\"loc\"]) for e in result[\"detail\"]}\n    assert (\"email\",) in error_fields\n    assert (\"age\",) in error_fields\n\n\n# ===== Pydantic with PATCH =====\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_pydantic_patch_valid(function_type: str, session):\n    \"\"\"Pydantic validation must work with PATCH method.\"\"\"\n    json_data = {\"name\": \"Iris\", \"email\": \"iris@example.com\", \"age\": 29}\n    res = requests.patch(f\"{BASE_URL}/{function_type}/pydantic/user\", json=json_data)\n    result = res.json()\n\n    assert result[\"patched\"] is True\n    assert result[\"name\"] == \"Iris\"\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_pydantic_patch_invalid(function_type: str, session):\n    \"\"\"PATCH with invalid body must also return 422.\"\"\"\n    json_data = {\"name\": \"Iris\"}  # missing email and age\n    res = requests.patch(f\"{BASE_URL}/{function_type}/pydantic/user\", json=json_data)\n    assert res.status_code == 422\n    result = res.json()\n    assert result[\"error\"] == \"Validation Error\"\n    error_fields = {tuple(e[\"loc\"]) for e in result[\"detail\"]}\n    assert (\"email\",) in error_fields\n    assert (\"age\",) in error_fields\n\n\n# ===== Pydantic with DELETE =====\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_pydantic_delete_valid(function_type: str, session):\n    \"\"\"Pydantic validation must work with DELETE method.\"\"\"\n    json_data = {\"name\": \"Zara\", \"email\": \"zara@example.com\", \"age\": 33}\n    res = requests.delete(f\"{BASE_URL}/{function_type}/pydantic/user\", json=json_data)\n    result = res.json()\n\n    assert result[\"deleted\"] is True\n    assert result[\"name\"] == \"Zara\"\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_pydantic_delete_invalid(function_type: str, session):\n    \"\"\"DELETE with invalid body must also return 422 with proper error structure.\"\"\"\n    json_data = {\"name\": \"Zara\"}  # missing email and age\n    res = requests.delete(f\"{BASE_URL}/{function_type}/pydantic/user\", json=json_data)\n    assert res.status_code == 422\n    result = res.json()\n    assert result[\"error\"] == \"Validation Error\"\n    error_fields = {tuple(e[\"loc\"]) for e in result[\"detail\"]}\n    assert (\"email\",) in error_fields\n    assert (\"age\",) in error_fields\n\n\n# ===== Returning Pydantic models directly =====\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_pydantic_return_model_directly(function_type: str, session):\n    \"\"\"Returning a Pydantic model from a handler should auto-serialize to JSON.\"\"\"\n    json_data = {\"name\": \"Jack\", \"email\": \"jack@example.com\", \"age\": 32}\n    res = requests.post(f\"{BASE_URL}/{function_type}/pydantic/return_model\", json=json_data)\n\n    assert res.status_code == 200\n    assert \"application/json\" in res.headers.get(\"content-type\", \"\")\n\n    result = res.json()\n    assert result[\"name\"] == \"Jack\"\n    assert result[\"email\"] == \"jack@example.com\"\n    assert result[\"age\"] == 32\n    assert result[\"active\"] is True  # default field\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_pydantic_return_model_preserves_all_fields(function_type: str, session):\n    \"\"\"Returned model should include every field, including those with defaults.\"\"\"\n    json_data = {\"name\": \"Kate\", \"email\": \"kate@example.com\", \"age\": 27, \"active\": False}\n    res = requests.post(f\"{BASE_URL}/{function_type}/pydantic/return_model\", json=json_data)\n    result = res.json()\n\n    assert result[\"name\"] == \"Kate\"\n    assert result[\"email\"] == \"kate@example.com\"\n    assert result[\"age\"] == 27\n    assert result[\"active\"] is False\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_pydantic_return_model_validation_still_works(function_type: str, session):\n    \"\"\"Validation should still trigger 422 even when the route returns a model.\"\"\"\n    json_data = {\"name\": \"Kate\"}  # missing email and age\n    res = requests.post(f\"{BASE_URL}/{function_type}/pydantic/return_model\", json=json_data)\n    assert res.status_code == 422\n    result = res.json()\n    assert result[\"error\"] == \"Validation Error\"\n\n\n# ===== Returning lists of Pydantic models =====\n\n\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_pydantic_return_list_of_models(function_type: str, session):\n    \"\"\"Returning a list of Pydantic models should auto-serialize to a JSON array.\"\"\"\n    json_data = {\"name\": \"Leo\", \"email\": \"leo@example.com\", \"age\": 45}\n    res = requests.post(f\"{BASE_URL}/{function_type}/pydantic/return_list\", json=json_data)\n\n    assert res.status_code == 200\n    assert \"application/json\" in res.headers.get(\"content-type\", \"\")\n\n    result = res.json()\n    assert isinstance(result, list)\n    assert len(result) == 2\n    assert result[0][\"name\"] == \"Leo\"\n    assert result[1][\"name\"] == \"Leo\"\n    assert result[0][\"active\"] is True\n"
  },
  {
    "path": "integration_tests/test_request_json.py",
    "content": "import pytest\n\nfrom integration_tests.helpers.http_methods_helpers import post\n\n\n@pytest.mark.parametrize(\n    \"route, body, expected_result\",\n    [\n        (\"/sync/request_json\", '{\"hello\": \"world\"}', \"<class 'dict'>\"),\n        (\"/sync/request_json/key\", '{\"key\": \"world\"}', \"world\"),\n        (\"/sync/request_json\", '{\"hello\": \"world\"', \"None\"),\n        (\"/async/request_json\", '{\"hello\": \"world\"}', \"<class 'dict'>\"),\n        (\"/async/request_json\", '{\"hello\": \"world\"', \"None\"),\n    ],\n)\ndef test_request(route, body, expected_result):\n    res = post(route, body)\n    assert res.text == expected_result\n"
  },
  {
    "path": "integration_tests/test_split_request_params.py",
    "content": "import pytest\n\nfrom integration_tests.helpers.http_methods_helpers import get, json_post, post\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\n@pytest.mark.parametrize(\"type_route\", [\"split_request_untyped\", \"split_request_typed\"])\ndef test_split_request_params_get_query_params(session, type_route, function_type):\n    r = get(f\"/{function_type}/{type_route}/query_params?hello=robyn\")\n    assert r.json() == {\"hello\": [\"robyn\"]}\n    r = get(f\"/{function_type}/{type_route}/query_params?hello=robyn&a=1&b=2\")\n    assert r.json() == {\"hello\": [\"robyn\"], \"a\": [\"1\"], \"b\": [\"2\"]}\n    r = get(f\"/{function_type}/{type_route}/query_params\")\n    assert r.json() == {}\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\n@pytest.mark.parametrize(\"type_route\", [\"split_request_untyped\", \"split_request_typed\"])\ndef test_split_request_params_get_headers(session, type_route, function_type):\n    r = get(f\"/{function_type}/{type_route}/headers\")\n    assert r.text == \"robyn\"\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\n@pytest.mark.parametrize(\"type_route\", [\"split_request_untyped\", \"split_request_typed\"])\ndef test_split_request_params_get_path_params(session, type_route, function_type):\n    r = get(f\"/{function_type}/{type_route}/path_params/123\")\n    assert r.json() == {\"id\": \"123\"}\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\n@pytest.mark.parametrize(\"type_route\", [\"split_request_untyped\", \"split_request_typed\"])\ndef test_split_request_params_get_method(session, type_route, function_type):\n    r = get(f\"/{function_type}/{type_route}/method\")\n    assert r.text == \"GET\"\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\n@pytest.mark.parametrize(\"type_route\", [\"split_request_untyped\", \"split_request_typed\"])\ndef test_split_request_params_get_body(session, type_route, function_type):\n    res = post(f\"/{function_type}/{type_route}/body\", data={\"hello\": \"world\"})\n    assert res.text == \"hello=world\"\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\n@pytest.mark.parametrize(\"type_route\", [\"split_request_untyped\", \"split_request_typed\"])\ndef test_split_request_params_get_combined(session, type_route, function_type):\n    res = post(\n        f\"/{function_type}/{type_route}/combined?hello=robyn&a=1&b=2\",\n        data={\"hello\": \"world\"},\n    )\n    out = res.json()\n    assert out[\"query_params\"] == {\"hello\": [\"robyn\"], \"a\": [\"1\"], \"b\": [\"2\"]}\n    assert out[\"body\"] == \"hello=world\"\n    assert out[\"method\"] == \"POST\"\n    assert out[\"url\"] == f\"/{function_type}/{type_route}/combined\"\n    assert out[\"headers\"] == \"robyn\"\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_split_request_params_typed_untyped_post_combined(session, function_type):\n    res = post(\n        f\"/{function_type}/split_request_typed_untyped/combined?hello=robyn&a=1&b=2\",\n        data={\"hello\": \"world\"},\n    )\n    out = res.json()\n    assert out[\"query_params\"] == {\"hello\": [\"robyn\"], \"a\": [\"1\"], \"b\": [\"2\"]}\n    assert out[\"body\"] == \"hello=world\"\n    assert out[\"method\"] == \"POST\"\n    assert out[\"url\"] == f\"/{function_type}/split_request_typed_untyped/combined\"\n    assert out[\"headers\"] == \"robyn\"\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_split_request_params_get_combined_failure(session, function_type):\n    # 'vishnu' is an unknown param with no default — now returns 400 (was 500 before easy-access params)\n    # because unresolved params are treated as missing required query params\n    res = post(f\"/{function_type}/split_request_typed_untyped/combined/failure?hello=robyn&a=1&b=2\", data={\"hello\": \"world\"}, should_check_response=False)\n    assert 400 == res.status_code\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_json_body_bare(session, function_type):\n    \"\"\"Test that bare JsonBody passes the parsed JSON dict to the handler.\"\"\"\n    res = json_post(f\"/{function_type}/json_body/bare\", json_data={\"hello\": \"world\", \"count\": 42})\n    assert res.json() == {\"hello\": \"world\", \"count\": 42}\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_json_body_typed(session, function_type):\n    \"\"\"Test that typed JsonBody subclass passes the parsed JSON dict to the handler.\"\"\"\n    res = json_post(f\"/{function_type}/json_body/typed\", json_data={\"fahrenheit\": 212})\n    result = res.json()\n    assert result[\"celsius\"] == pytest.approx(100.0)\n"
  },
  {
    "path": "integration_tests/test_sse.py",
    "content": "import json\n\nimport pytest\nimport requests\n\nfrom integration_tests.helpers.http_methods_helpers import BASE_URL\nfrom robyn.responses import SSEMessage, SSEResponse, StreamingResponse\n\n\n@pytest.mark.benchmark\ndef test_sse_basic_headers(session):\n    \"\"\"Test that SSE endpoints return correct headers\"\"\"\n    response = requests.get(f\"{BASE_URL}/sse/basic\", stream=True)\n\n    assert response.status_code == 200\n    assert response.headers.get(\"Content-Type\") == \"text/event-stream\"\n    # Accept either clean optimized headers or legacy compatibility\n    cache_control = response.headers.get(\"Cache-Control\")\n    assert cache_control in [\"no-cache, no-store, must-revalidate\", \"no-cache, no-cache, no-store, must-revalidate\"]\n\n\n@pytest.mark.benchmark\ndef test_sse_basic_stream(session):\n    \"\"\"Test basic SSE streaming with simple data messages\"\"\"\n    response = requests.get(f\"{BASE_URL}/sse/basic\", stream=True, timeout=5)\n    response.raise_for_status()\n\n    content = \"\"\n    for chunk in response.iter_content(chunk_size=1024, decode_unicode=True):\n        if chunk:\n            content += chunk\n        if content.count(\"\\n\\n\") >= 3:  # Got 3 messages\n            break\n\n    # Parse events\n    events = []\n    for line in content.split(\"\\n\"):\n        if line.startswith(\"data: \"):\n            events.append(line[6:])  # Remove 'data: ' prefix\n\n    assert len(events) >= 3\n    for i in range(3):\n        assert f\"Test message {i}\" in events\n\n\n@pytest.mark.benchmark\ndef test_sse_formatted_messages(session):\n    \"\"\"Test SSE messages formatted with SSEMessage helper\"\"\"\n    response = requests.get(f\"{BASE_URL}/sse/formatted\", stream=True, timeout=5)\n    response.raise_for_status()\n\n    content = \"\"\n    for chunk in response.iter_content(chunk_size=1024, decode_unicode=True):\n        if chunk:\n            content += chunk\n        if content.count(\"\\n\\n\") >= 3:\n            break\n\n    # Should contain event and id fields\n    assert \"event: test\" in content\n    assert \"id: 0\" in content\n    assert \"id: 1\" in content\n    assert \"id: 2\" in content\n\n    # Parse data lines\n    data_lines = []\n    for line in content.split(\"\\n\"):\n        if line.startswith(\"data: \"):\n            data_lines.append(line[6:])\n\n    assert len(data_lines) >= 3\n    for i in range(3):\n        assert f\"Formatted message {i}\" in data_lines\n\n\n@pytest.mark.benchmark\ndef test_sse_json_data(session):\n    \"\"\"Test SSE streaming with JSON data\"\"\"\n    response = requests.get(f\"{BASE_URL}/sse/json\", stream=True, timeout=5)\n    response.raise_for_status()\n\n    content = \"\"\n    for chunk in response.iter_content(chunk_size=1024, decode_unicode=True):\n        if chunk:\n            content += chunk\n        if content.count(\"\\n\\n\") >= 3:\n            break\n\n    # Parse data lines\n    data_lines = []\n    for line in content.split(\"\\n\"):\n        if line.startswith(\"data: \"):\n            data_lines.append(line[6:])\n\n    assert len(data_lines) >= 3\n    for i in range(3):\n        json_data = json.loads(data_lines[i])\n        assert json_data[\"id\"] == i\n        assert json_data[\"message\"] == f\"JSON message {i}\"\n        assert json_data[\"type\"] == \"test\"\n\n\n@pytest.mark.benchmark\ndef test_sse_named_events(session):\n    \"\"\"Test SSE with different event types\"\"\"\n    response = requests.get(f\"{BASE_URL}/sse/named_events\", stream=True, timeout=5)\n    response.raise_for_status()\n\n    content = \"\"\n    for chunk in response.iter_content(chunk_size=1024, decode_unicode=True):\n        if chunk:\n            content += chunk\n        if content.count(\"\\n\\n\") >= 3:\n            break\n\n    # Should contain different event types\n    assert \"event: start\" in content\n    assert \"event: progress\" in content\n    assert \"event: end\" in content\n\n    # Should contain corresponding data\n    assert \"data: Test started\" in content\n    assert \"data: Test in progress\" in content\n    assert \"data: Test completed\" in content\n\n\n@pytest.mark.benchmark\ndef test_sse_async_endpoint(session):\n    \"\"\"Test async SSE endpoint\"\"\"\n    response = requests.get(f\"{BASE_URL}/sse/async\", stream=True, timeout=5)\n    response.raise_for_status()\n\n    content = \"\"\n    for chunk in response.iter_content(chunk_size=1024, decode_unicode=True):\n        if chunk:\n            content += chunk\n        if content.count(\"\\n\\n\") >= 3:\n            break\n\n    # Parse data lines\n    data_lines = []\n    for line in content.split(\"\\n\"):\n        if line.startswith(\"data: \"):\n            data_lines.append(line[6:])\n\n    assert len(data_lines) >= 3\n    for i in range(3):\n        assert f\"Async message {i}\" in data_lines\n\n\n@pytest.mark.benchmark\ndef test_sse_single_message(session):\n    \"\"\"Test SSE endpoint that sends only one message\"\"\"\n    response = requests.get(f\"{BASE_URL}/sse/single\", stream=True, timeout=3)\n    response.raise_for_status()\n\n    content = \"\"\n    for chunk in response.iter_content(chunk_size=1024, decode_unicode=True):\n        if chunk:\n            content += chunk\n        if \"\\n\\n\" in content:  # Got at least one message\n            break\n\n    # Parse data lines\n    data_lines = []\n    for line in content.split(\"\\n\"):\n        if line.startswith(\"data: \"):\n            data_lines.append(line[6:])\n\n    assert len(data_lines) == 1\n    assert data_lines[0] == \"Single message\"\n\n\n@pytest.mark.benchmark\ndef test_sse_empty_stream(session):\n    \"\"\"Test SSE endpoint that sends no messages\"\"\"\n    response = requests.get(f\"{BASE_URL}/sse/empty\", stream=True, timeout=2)\n    response.raise_for_status()\n\n    content = \"\"\n    for chunk in response.iter_content(chunk_size=1024, decode_unicode=True):\n        if chunk:\n            content += chunk\n        # Don't wait forever for empty stream\n        break\n\n    # Parse data lines\n    data_lines = []\n    for line in content.split(\"\\n\"):\n        if line.startswith(\"data: \"):\n            data_lines.append(line[6:])\n\n    assert len(data_lines) == 0\n\n\n@pytest.mark.benchmark\ndef test_sse_custom_headers(session):\n    \"\"\"Test SSE endpoint with custom headers; SSE responses should not include default CORS headers for cross-origin EventSource support\"\"\"\n    response = requests.get(f\"{BASE_URL}/sse/with_headers\", stream=True)\n\n    assert response.status_code == 200\n    assert response.headers.get(\"X-Custom-Header\") == \"custom-value\"\n    assert response.headers.get(\"Content-Type\") == \"text/event-stream\"\n\n    # SSE responses should not include default CORS headers\n    assert response.headers.get(\"Access-Control-Allow-Origin\") is None\n    assert response.headers.get(\"Access-Control-Allow-Headers\") is None\n\n\n@pytest.mark.benchmark\ndef test_sse_custom_status_code(session):\n    \"\"\"Test SSE endpoint with custom status code\"\"\"\n    response = requests.get(f\"{BASE_URL}/sse/status_code\", stream=True)\n\n    assert response.status_code == 201\n    assert response.headers.get(\"Content-Type\") == \"text/event-stream\"\n\n\n@pytest.mark.benchmark\ndef test_sse_middleware_compatibility(session):\n    \"\"\"Test that SSE endpoints work with global middleware\"\"\"\n    response = requests.get(f\"{BASE_URL}/sse/basic\", stream=True)\n\n    # Should have global response headers from middleware\n    assert response.headers.get(\"server\") == \"robyn\"\n\n\ndef test_sse_message_formatter():\n    \"\"\"Test the SSEMessage formatter utility function\"\"\"\n\n    # Test basic message\n    result = SSEMessage(\"Hello world\")\n    assert \"data: Hello world\\n\\n\" in result\n\n    # Test with event type\n    result = SSEMessage(\"Hello\", event=\"greeting\")\n    assert \"event: greeting\\n\" in result\n    assert \"data: Hello\\n\\n\" in result\n\n    # Test with ID\n    result = SSEMessage(\"Hello\", id=\"123\")\n    assert \"id: 123\\n\" in result\n    assert \"data: Hello\\n\\n\" in result\n\n    # Test with retry\n    result = SSEMessage(\"Hello\", retry=5000)\n    assert \"retry: 5000\\n\" in result\n    assert \"data: Hello\\n\\n\" in result\n\n    # Test with all parameters\n    result = SSEMessage(\"Hello\", event=\"test\", id=\"456\", retry=3000)\n    assert \"event: test\\n\" in result\n    assert \"id: 456\\n\" in result\n    assert \"retry: 3000\\n\" in result\n    assert \"data: Hello\\n\\n\" in result\n\n    # Test multiline data\n    result = SSEMessage(\"Line 1\\nLine 2\")\n    assert \"data: Line 1\\ndata: Line 2\\n\\n\" in result\n\n\ndef test_sse_message_edge_cases():\n    \"\"\"Test SSEMessage with edge cases\"\"\"\n\n    # Test empty message\n    result = SSEMessage(\"\")\n    assert result == \"data: \\n\\n\"\n\n    # Test None message (should handle gracefully)\n    try:\n        result = SSEMessage(None)\n        assert \"data:\" in result\n    except TypeError:\n        # This is acceptable behavior\n        pass\n\n    # Test message with special characters\n    special_message = \"Hello\\nWorld\\r\\nWith\\tTabs\"\n    result = SSEMessage(special_message)\n    assert \"data: Hello\\ndata: World\\ndata: With\\tTabs\\n\\n\" in result\n\n\ndef test_sse_response_classes():\n    \"\"\"Test that SSE response classes can be imported correctly\"\"\"\n    assert SSEResponse is not None\n    assert SSEMessage is not None\n    assert StreamingResponse is not None\n\n    # Test basic functionality\n    def simple_generator():\n        yield \"data: test\\n\\n\"\n\n    response = SSEResponse(simple_generator())\n    assert response.media_type == \"text/event-stream\"\n    assert response.status_code == 200\n\n\ndef test_sse_error_handling():\n    \"\"\"Test error handling in SSE streams\"\"\"\n    # Test with a non-existent endpoint\n    response = requests.get(f\"{BASE_URL}/sse/nonexistent\", stream=True)\n    assert response.status_code == 404\n\n\ndef test_sse_http_methods():\n    \"\"\"Test that SSE endpoints only work with GET\"\"\"\n    # GET should work\n    response = requests.get(f\"{BASE_URL}/sse/basic\", stream=True)\n    assert response.status_code == 200\n\n    # POST should not work (404 or 405)\n    response = requests.post(f\"{BASE_URL}/sse/basic\")\n    assert response.status_code in [404, 405]\n\n\n@pytest.mark.benchmark\ndef test_sse_streaming_sync_real_time(session):\n    \"\"\"Test that sync SSE streaming happens in real-time with timing delays\"\"\"\n    import time\n\n    start_time = time.time()\n    response = requests.get(f\"{BASE_URL}/sse/streaming_sync\", stream=True, timeout=10)\n    response.raise_for_status()\n\n    messages_received = 0\n    message_times = []\n\n    content = \"\"\n    for chunk in response.iter_content(chunk_size=1, decode_unicode=True):\n        if chunk:\n            content += chunk\n            # Check for complete messages\n            while \"\\n\\n\" in content:\n                message_end = content.find(\"\\n\\n\") + 2\n                message = content[:message_end]\n                content = content[message_end:]\n\n                if \"data:\" in message:\n                    messages_received += 1\n                    message_times.append(time.time() - start_time)\n\n                if messages_received >= 3:  # Got all 3 data messages\n                    break\n\n        if messages_received >= 3:\n            break\n\n    # Verify we got 3 messages\n    assert messages_received == 3\n\n    # Verify timing: each message should arrive ~0.5s after the previous\n    # Allow some tolerance for processing time (±200ms)\n    for i in range(1, len(message_times)):\n        time_diff = message_times[i] - message_times[i - 1]\n        assert 0.3 <= time_diff <= 0.8, f\"Message {i} arrived {time_diff:.2f}s after previous (expected ~0.5s)\"\n\n\n@pytest.mark.benchmark\ndef test_sse_streaming_async_real_time(session):\n    \"\"\"Test that async SSE streaming happens in real-time with timing delays\"\"\"\n    import time\n\n    start_time = time.time()\n    response = requests.get(f\"{BASE_URL}/sse/streaming_async\", stream=True, timeout=10)\n    response.raise_for_status()\n\n    messages_received = 0\n    message_times = []\n\n    content = \"\"\n    for chunk in response.iter_content(chunk_size=1, decode_unicode=True):\n        if chunk:\n            content += chunk\n            # Check for complete messages\n            while \"\\n\\n\" in content:\n                message_end = content.find(\"\\n\\n\") + 2\n                message = content[:message_end]\n                content = content[message_end:]\n\n                if \"data:\" in message and \"event: async\" in message:\n                    messages_received += 1\n                    message_times.append(time.time() - start_time)\n\n                if messages_received >= 3:  # Got all 3 data messages\n                    break\n\n        if messages_received >= 3:\n            break\n\n    # Verify we got 3 messages\n    assert messages_received == 3\n\n    # Verify timing: each message should arrive ~0.3s after the previous\n    # Allow some tolerance for processing time (±150ms)\n    for i in range(1, len(message_times)):\n        time_diff = message_times[i] - message_times[i - 1]\n        assert 0.15 <= time_diff <= 0.5, f\"Async message {i} arrived {time_diff:.2f}s after previous (expected ~0.3s)\"\n\n\n@pytest.mark.benchmark\ndef test_sse_optimization_headers(session):\n    \"\"\"Test that optimized SSE headers are present\"\"\"\n    response = requests.get(f\"{BASE_URL}/sse/streaming_sync\", stream=True)\n\n    assert response.status_code == 200\n\n    # Check for optimization headers\n    assert response.headers.get(\"Content-Type\") == \"text/event-stream\"\n    # Accept either clean optimized headers or legacy compatibility\n    cache_control = response.headers.get(\"Cache-Control\")\n    assert cache_control in [\"no-cache, no-store, must-revalidate\", \"no-cache, no-cache, no-store, must-revalidate\"]\n    assert response.headers.get(\"Pragma\") == \"no-cache\"\n    assert response.headers.get(\"Expires\") == \"0\"\n    assert response.headers.get(\"X-Accel-Buffering\") == \"no\"  # Nginx buffering disabled\n    # Connection header might be managed by underlying HTTP infrastructure\n    connection = response.headers.get(\"Connection\")\n    assert connection is None or connection == \"keep-alive\"\n\n\ndef test_sse_message_optimization():\n    \"\"\"Test that SSEMessage formatting is optimized\"\"\"\n    import time\n\n    # Test single-line fast path\n    start_time = time.perf_counter()\n    for _ in range(1000):\n        result = SSEMessage(\"Simple message\", id=\"123\")\n    single_line_time = time.perf_counter() - start_time\n\n    # Test multi-line path\n    start_time = time.perf_counter()\n    for _ in range(1000):\n        result = SSEMessage(\"Line 1\\nLine 2\\nLine 3\", id=\"123\")\n    multi_line_time = time.perf_counter() - start_time\n\n    # Single-line should be faster (this is more of a performance regression test)\n    assert single_line_time < multi_line_time * 2, \"Single-line SSEMessage optimization may have regressed\"\n\n    # Verify correctness\n    result = SSEMessage(\"Test\", event=\"test\", id=\"1\", retry=1000)\n    expected_parts = [\"event: test\\n\", \"id: 1\\n\", \"retry: 1000\\n\", \"data: Test\\n\", \"\\n\"]\n    for part in expected_parts:\n        assert part in result\n"
  },
  {
    "path": "integration_tests/test_static_files_with_api_routes.py",
    "content": "\"\"\"\nTest for issue #1251: Verify that API routes work correctly\nwhen static files are served from the same base path.\n\nThis test ensures that non-GET/HEAD HTTP methods properly fall through\nto API handlers when a static file service is mounted at the same route.\n\"\"\"\n\nimport pytest\n\nfrom integration_tests.helpers.http_methods_helpers import get, post\n\n# Notes:\n# 1. The /static route serves the integration_tests having files & directories.\n\n\n@pytest.mark.benchmark\ndef test_post_api_route_with_root_static_files(session):\n    \"\"\"Test that POST requests reach API handlers (issue #1251).\n    This ensures that non-GET/HEAD methods are not blocked by static file serving.\n    \"\"\"\n    response = post(\"/static/build\")\n    assert response.status_code == 200\n    assert response.text == f\"{response.request.method}:{response.request.path_url} works\"\n\n\n@pytest.mark.benchmark\ndef test_static_file_still_served_correctly(session):\n    \"\"\"Verify that actual static files are still served correctly.\"\"\"\n    response = get(\"/static/build/index.html\", should_check_response=False)\n    assert response.status_code == 200\n    # Should serve the index.html file\n    assert \"html\" in response.text.lower()\n"
  },
  {
    "path": "integration_tests/test_status_code.py",
    "content": "import pytest\nimport requests\n\nfrom integration_tests.helpers.http_methods_helpers import BASE_URL, get\n\n\n@pytest.mark.benchmark\ndef test_404_status_code(session):\n    get(\"/404\", expected_status_code=404)\n\n\n@pytest.mark.benchmark\ndef test_404_not_found(session):\n    r = get(\"/real/404\", expected_status_code=404)\n    assert r.text == \"Not found\"\n\n\n@pytest.mark.benchmark\ndef test_202_status_code(session):\n    get(\"/202\", expected_status_code=202)\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_sync_500_internal_server_error(function_type: str, session):\n    get(f\"/{function_type}/raise\", expected_status_code=500)\n\n\n# ===== Content-Type on error responses =====\n\n\n@pytest.mark.benchmark\ndef test_404_not_found_content_type(session):\n    \"\"\"A request to a non-existent route should return Content-Type: text/plain\"\"\"\n    r = get(\"/real/404\", expected_status_code=404)\n    assert r.text == \"Not found\"\n    content_type = r.headers.get(\"Content-Type\", \"\")\n    assert \"text/plain\" in content_type\n\n\n@pytest.mark.benchmark\n@pytest.mark.parametrize(\"function_type\", [\"sync\", \"async\"])\ndef test_500_error_content_type(function_type: str, session):\n    \"\"\"An unhandled exception should return Content-Type: text/plain\"\"\"\n    r = get(f\"/{function_type}/raise\", expected_status_code=500)\n    content_type = r.headers.get(\"Content-Type\", \"\")\n    assert \"text/plain\" in content_type\n\n\n@pytest.mark.benchmark\ndef test_405_method_not_allowed_content_type(session):\n    \"\"\"An unsupported HTTP method should return 405 with Content-Type: text/plain\"\"\"\n    response = requests.request(\"NONSTANDARD\", f\"{BASE_URL}/\")\n    assert response.status_code == 405\n    content_type = response.headers.get(\"Content-Type\", \"\")\n    assert \"text/plain\" in content_type\n"
  },
  {
    "path": "integration_tests/test_subrouter.py",
    "content": "import pytest\nfrom websocket import create_connection\n\nfrom integration_tests.helpers.http_methods_helpers import generic_http_helper, head\n\n\n@pytest.mark.parametrize(\n    \"http_method_type\",\n    [\"get\", \"post\", \"put\", \"delete\", \"patch\", \"options\", \"trace\"],\n)\n@pytest.mark.benchmark\ndef test_sub_router(http_method_type, session):\n    response = generic_http_helper(http_method_type, \"sub_router/foo\")\n    assert response.json() == {\"message\": \"foo\"}\n\n\n@pytest.mark.benchmark\ndef test_sub_router_head(session):\n    response = head(\"sub_router/foo\")\n    assert response.text == \"\"  # response body is expected to be empty\n\n\n@pytest.mark.benchmark\ndef test_sub_router_web_socket(session):\n    BASE_URL = \"ws://127.0.0.1:8080\"\n    ws = create_connection(f\"{BASE_URL}/sub_router/ws\")\n    assert ws.recv() == \"Hello world, from ws\"\n    ws.send(\"My name is?\")\n    assert ws.recv() == \"Message\"\n"
  },
  {
    "path": "integration_tests/test_web_sockets.py",
    "content": "import json\n\nimport pytest\nfrom websocket import create_connection\n\nBASE_URL = \"ws://127.0.0.1:8080\"\n\n\n@pytest.mark.benchmark\ndef test_web_socket_raw_benchmark(session):\n    ws = create_connection(f\"{BASE_URL}/web_socket?one=hi&two=hello\")\n    assert ws.recv() == \"Hello world, from ws\"\n\n    ws.send(\"My name is?\")\n    # Messages may arrive in any order due to WebSocket broadcast behavior\n    received = sorted([ws.recv() for _ in range(3)])\n    expected = sorted([\"This is a broadcast message\", \"This is a message to self\", \"Whaaat??\"])\n    assert received == expected\n\n    ws.send(\"My name is?\")\n    assert ws.recv() == \"Whooo??\"\n\n    ws.send(\"My name is?\")\n    received = sorted([ws.recv() for _ in range(3)])\n    expected = sorted([\"hi\", \"hello\", \"*chika* *chika* Slim Shady.\"])\n    assert received == expected\n\n    # this will close the connection\n    ws.send(\"test\")\n    assert ws.recv() == \"Connection closed\"\n\n\ndef test_web_socket_json(session):\n    \"\"\"\n    Not using this as the benchmark test since this involves JSON marshalling/unmarshalling\n    which pollutes the benchmark measurement.\n    \"\"\"\n    ws = create_connection(f\"{BASE_URL}/web_socket_json\")\n    assert ws.recv() == \"Hello world, from ws\"\n\n    msg = \"My name is?\"\n\n    ws.send(msg)\n    resp = json.loads(ws.recv())\n    assert resp[\"resp\"] == \"Whaaat??\"\n    assert resp[\"msg\"] == msg\n\n    ws.send(msg)\n    resp = json.loads(ws.recv())\n    assert resp[\"resp\"] == \"Whooo??\"\n    assert resp[\"msg\"] == msg\n\n    ws.send(msg)\n    resp = json.loads(ws.recv())\n    assert resp[\"resp\"] == \"*chika* *chika* Slim Shady.\"\n    assert resp[\"msg\"] == msg\n\n\ndef test_websocket_di(session):\n    \"\"\"Test dependency injection in WebSocket connect and handler phases.\"\"\"\n\n    ws = create_connection(f\"{BASE_URL}/web_socket_di\")\n\n    # 1. on_connect should receive both global and router dependencies\n    assert ws.recv() == \"connect: GLOBAL DEPENDENCY ROUTER DEPENDENCY\"\n\n    # 2. Main handler should also receive both dependencies when processing messages\n    ws.send(\"test\")\n    assert ws.recv() == \"handler: GLOBAL DEPENDENCY ROUTER DEPENDENCY\"\n\n    # Send another message to confirm DI is stable across multiple messages\n    ws.send(\"test again\")\n    assert ws.recv() == \"handler: GLOBAL DEPENDENCY ROUTER DEPENDENCY\"\n\n    ws.close()\n\n\ndef test_websocket_large_payload(session):\n    \"\"\"Test that WebSocket can handle messages larger than the default 64KB frame size (#1269)\"\"\"\n    ws = create_connection(f\"{BASE_URL}/web_socket_echo\")\n    # Consume the empty connect message\n    ws.recv()\n\n    large_message = \"A\" * (128 * 1024)  # 128KB, well above the old 64KB default\n    ws.send(large_message)\n    response = ws.recv()\n    assert response == large_message\n    assert len(response) == 128 * 1024\n\n    ws.close()\n\n\ndef test_websocket_empty_returns(session):\n    \"\"\"Test that WebSocket handlers can return nothing without causing errors\"\"\"\n    ws = create_connection(f\"{BASE_URL}/web_socket_empty_returns\")\n\n    # Connect handler returns None - no message should be received on connection\n    # We need to send a message to verify the connection is still active\n    ws.send(\"test message\")\n\n    # Message handler returns None - no response should be sent\n    # The socket should still be open, not crashed\n    # We can verify this by closing the connection gracefully\n    ws.close()\n    # If we got here without exceptions, the test passed\n"
  },
  {
    "path": "llms.txt",
    "content": "# Robyn\n\n> Robyn is a high-performance, community-driven, and innovator-friendly async web framework for Python with a Rust runtime. It combines Python's ease of use with Rust's performance.\n\n## Quick Facts\n\n- Version: 0.79.0\n- Python: >= 3.10\n- License: BSD 2.0\n- Repository: https://github.com/sparckles/robyn\n- Documentation: https://robyn.tech/documentation\n- Discord: https://discord.gg/rkERZ5eNU8\n\n## Installation\n\n```bash\npip install robyn\n```\n\n## Basic Usage\n\n```python\nfrom robyn import Robyn\n\napp = Robyn(__file__)\n\n@app.get(\"/\")\nasync def index(request):\n    return \"Hello, World!\"\n\napp.start(port=8080)\n```\n\n## Key Features\n\n- **Rust Runtime**: Core server written in Rust using actix-web for high performance\n- **Async/Sync Support**: Both async and sync route handlers supported\n- **Multi-Process Scaling**: Built-in multiprocess execution via `--processes` and `--workers`\n- **WebSockets**: Native WebSocket support\n- **Middlewares**: Before/after request middlewares\n- **Dependency Injection**: Built-in DI system\n- **OpenAPI/Swagger**: Automatic OpenAPI documentation generation\n- **Hot Reloading**: Development mode with `--dev` flag\n- **AI Agents**: Built-in AI agent routing via `robyn.ai`\n- **MCP Support**: Model Context Protocol server capabilities via `app.mcp`\n- **Templating**: Jinja2 templating support (optional)\n- **CORS**: Built-in CORS helper via `ALLOW_CORS()`\n- **Authentication**: AuthenticationHandler base class for custom auth\n- **Static Files**: Directory serving via `app.serve_directory()`\n- **SSE**: Server-Sent Events support via `SSEResponse`\n- **Easy Access Parameters**: Typed path/query params with automatic coercion in handler signatures\n- **Direct Rust Integration**: Embed Rust code directly in routes\n\n## Project Structure\n\n```\nrobyn/\n├── src/                    # Rust source code\n│   ├── lib.rs              # PyO3 module entry point\n│   ├── server.rs           # Main HTTP server implementation\n│   ├── types/              # Request, Response, Headers, Cookie types\n│   ├── routers/            # HTTP, WebSocket, middleware routers\n│   ├── executors/          # Route execution handlers\n│   └── websockets/         # WebSocket implementation\n├── robyn/                  # Python package\n│   ├── __init__.py         # Main Robyn and SubRouter classes\n│   ├── router.py           # Python router implementation\n│   ├── authentication.py   # AuthenticationHandler\n│   ├── dependency_injection.py\n│   ├── openapi.py          # OpenAPI generation\n│   ├── mcp.py              # MCP protocol support\n│   ├── ai.py               # AI agent support\n│   ├── responses.py        # Response helpers (serve_file, html, SSE)\n│   ├── ws.py               # WebSocket class\n│   └── robyn.pyi           # Type stubs\n├── integration_tests/      # Integration test suite\n├── unit_tests/             # Unit test suite\n├── docs_src/               # Documentation (Next.js)\n├── granian/                # Bundled Granian server (fork)\n└── examples/               # Example applications\n```\n\n## Core Classes\n\n### Robyn / SubRouter\nMain application class and sub-router for modular routes.\n\n```python\nfrom robyn import Robyn, SubRouter\n\napp = Robyn(__file__)\napi = SubRouter(__file__, prefix=\"/api\")\n\n@api.get(\"/users\")\ndef get_users(request):\n    return {\"users\": []}\n\napp.include_router(api)\n```\n\n### Request Object\n```python\nrequest.method      # HTTP method\nrequest.url         # Url object (scheme, host, path)\nrequest.headers     # Headers dict-like\nrequest.query_params # QueryParams\nrequest.path_params  # Dict of URL params\nrequest.body        # Raw bytes\nrequest.json()      # Parse JSON body\nrequest.form_data   # Multipart form data\nrequest.ip_addr     # Client IP\nrequest.identity    # Identity (if authenticated)\n```\n\n### Response Object\n```python\nfrom robyn import Response\n\nResponse(\n    status_code=200,\n    headers={\"Content-Type\": \"application/json\"},\n    description=\"body content\"  # or body bytes\n)\n```\n\n### Decorators\n```python\n@app.get(\"/path\")\n@app.post(\"/path\")\n@app.put(\"/path\")\n@app.delete(\"/path\")\n@app.patch(\"/path\")\n@app.head(\"/path\")\n@app.options(\"/path\")\n\n@app.before_request(\"/path\")  # Middleware before\n@app.after_request(\"/path\")   # Middleware after\n\n@app.startup_handler         # Server startup\n@app.shutdown_handler        # Server shutdown\n```\n\n### WebSockets\n```python\nfrom robyn import WebSocketDisconnect\n\n@app.websocket(\"/ws\")\nasync def handler(websocket):\n    try:\n        while True:\n            msg = await websocket.receive_text()\n            await websocket.send_text(f\"Echo: {msg}\")\n    except WebSocketDisconnect:\n        pass\n\n@handler.on_connect\ndef on_connect(websocket):\n    return \"Connected\"\n\n@handler.on_close\ndef on_close(websocket):\n    return \"Closed\"\n```\n\n### Easy Access Parameters\nDeclare typed path and query parameters directly in handler signatures. Works for both HTTP and WebSocket handlers.\n\n```python\nfrom typing import List, Optional\n\n# HTTP: path params + query params with type coercion\n@app.get(\"/items/:id\")\nasync def get_item(id: int, q: str, page: int = 1):\n    return {\"id\": id, \"q\": q, \"page\": page}\n\n# Optional, List, and bool params\n@app.get(\"/search\")\ndef search(name: str, tags: List[str], active: bool = False, age: Optional[int] = None):\n    return {\"name\": name, \"tags\": tags, \"active\": active, \"age\": age}\n\n# WebSocket: typed query params on handler and callbacks\n@app.websocket(\"/ws\")\nasync def handler(websocket, room: str = \"default\", page: int = 1):\n    while True:\n        msg = await websocket.receive_text()\n        await websocket.send_text(f\"room={room} page={page} msg={msg}\")\n\n@handler.on_connect\ndef on_connect(websocket, room: str = \"default\"):\n    return f\"connected to {room}\"\n```\n\n### MCP (Model Context Protocol)\n```python\n@app.mcp.resource(\"time://current\")\ndef get_time():\n    return datetime.now().isoformat()\n\n@app.mcp.tool(name=\"calc\", description=\"Calculate\", input_schema={...})\ndef calculate(args):\n    return eval(args[\"expression\"])\n\n@app.mcp.prompt(name=\"explain\", description=\"Explain code\", arguments=[...])\ndef explain_prompt(args):\n    return f\"Please explain: {args['code']}\"\n```\n\n## CLI Commands\n\n```bash\npython app.py                     # Start server\npython app.py --dev               # Development mode (hot reload)\npython app.py --processes 4       # Multi-process\npython app.py --workers 2         # Workers per process\npython app.py --log-level DEBUG   # Log level\npython app.py --open-browser      # Open browser on start\npython app.py --create            # Create new project scaffold\npython app.py --docs              # Open documentation\n```\n\n## Development Setup\n\n```bash\n# Clone\ngit clone https://github.com/sparckles/robyn.git\ncd robyn\n\n# Virtual environment\npython3 -m venv .venv && source .venv/bin/activate\n\n# Install tools\npip install pre-commit poetry maturin\n\n# Install dependencies\npoetry install --with dev --with test\n\n# Build Rust extension\nmaturin develop\n\n# Run tests\npytest\n```\n\n## Key Dependencies\n\n- **PyO3**: Rust-Python bindings\n- **actix-web**: Rust HTTP server (via cookie crate)\n- **orjson**: Fast JSON serialization\n- **multiprocess**: Multi-process support\n- **uvloop**: Fast event loop (non-Windows)\n- **watchdog**: File watching for hot reload\n\n## Configuration\n\nEnvironment variables:\n- `ROBYN_HOST`: Server host (default: 127.0.0.1)\n- `ROBYN_PORT`: Server port (default: 8080)\n- `ROBYN_DEV_MODE`: Enable dev mode\n- `ROBYN_BROWSER_OPEN`: Open browser on start\n- `ROBYN_CLIENT_TIMEOUT`: Client timeout seconds\n- `ROBYN_KEEP_ALIVE_TIMEOUT`: Keep-alive timeout\n\n## Documentation Structure\n\nMain docs at `docs_src/src/pages/documentation/`:\n- `api_reference/getting_started.mdx` - Quick start guide\n- `api_reference/request_object.mdx` - Request handling\n- `api_reference/middlewares.mdx` - Middleware usage\n- `api_reference/websockets.mdx` - WebSocket guide\n- `api_reference/authentication.mdx` - Auth patterns\n- `api_reference/openapi.mdx` - OpenAPI docs\n- `api_reference/agents.mdx` - AI agent integration\n- `api_reference/mcps.mdx` - MCP server guide\n- `example_app/` - Full example application tutorial\n"
  },
  {
    "path": "noxfile.py",
    "content": "import sys\n\nimport nox\n\n\n@nox.session(python=[\"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\"])\ndef tests(session):\n    session.run(\"pip\", \"install\", \"poetry==1.3.0\")\n    session.run(\"pip\", \"install\", \"maturin\")\n    session.run(\n        \"poetry\",\n        \"export\",\n        \"--with\",\n        \"test\",\n        \"--with\",\n        \"dev\",\n        \"--without-hashes\",\n        \"--output\",\n        \"requirements.txt\",\n    )\n    session.run(\"pip\", \"install\", \"-r\", \"requirements.txt\")\n    session.run(\"pip\", \"install\", \"-e\", \".\")\n\n    args = [\n        \"maturin\",\n        \"build\",\n        \"-i\",\n        \"python\",\n        \"--out\",\n        \"dist\",\n    ]\n\n    if sys.platform == \"darwin\":\n        session.run(\"rustup\", \"target\", \"add\", \"x86_64-apple-darwin\")\n        session.run(\"rustup\", \"target\", \"add\", \"aarch64-apple-darwin\")\n        args.append(\"--target\")\n        args.append(\"universal2-apple-darwin\")\n\n    session.run(*args)\n    session.run(\"pip\", \"install\", \"--no-index\", \"--find-links=dist/\", \"robyn\")\n    session.run(\"pytest\")\n\n\n@nox.session(python=[\"3.11\"])\ndef lint(session):\n    session.run(\"pip\", \"install\", \"black\", \"ruff\")\n    session.run(\"black\", \"robyn/\", \"integration_tests/\")\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\"maturin>=1.0,<2.0\"]\nbuild-backend = \"maturin\"\n\n[project]\nname = \"robyn\"\nversion = \"0.82.0\"\ndescription = \"A Super Fast Async Python Web Framework with a Rust runtime.\"\nreadme = \"README.md\"\nauthors = [{ name = \"Sanskar Jethi\", email = \"sansyrox@gmail.com\" }]\nlicense = { file = \"LICENSE\" }\nclassifiers = [\n  \"Development Status :: 3 - Alpha\",\n  \"Environment :: Web Environment\",\n  \"Intended Audience :: Developers\",\n  \"License :: OSI Approved :: BSD License\",\n  \"Operating System :: OS Independent\",\n  \"Topic :: Internet :: WWW/HTTP\",\n  \"Programming Language :: Python :: 3\",\n  \"Programming Language :: Python :: 3.10\",\n  \"Programming Language :: Python :: 3.11\",\n  \"Programming Language :: Python :: 3.12\",\n  \"Programming Language :: Python :: 3.13\",\n  \"Programming Language :: Python :: 3.14\",\n  \"Programming Language :: Python :: Implementation :: CPython\",\n]\ndependencies = [\n  \"inquirerpy == 0.3.4\",\n  \"multiprocess >= 0.70.18, < 0.71.0\",\n  \"orjson >= 3.11.5, < 4.0.0\",\n  \"rustimport == 1.3.4\",\n  # conditional\n  \"uvloop~=0.22.1; sys_platform != 'win32' and platform_python_implementation == 'CPython' and platform_machine != 'armv7l'\",\n  \"watchdog >= 6.0.0, < 7.0.0\",\n]\n\n[project.optional-dependencies]\n\"templating\" = [\"jinja2 >= 3.1.6, < 4.0.0\"]\n\"pydantic\" = [\"pydantic >= 2.0.0, < 3.0.0\"]\n\"all\" = [\"jinja2 >= 3.1.6, < 4.0.0\", \"pydantic >= 2.0.0, < 3.0.0\"]\n\n[project.urls]\nDocumentation = \"https://robyn.tech/\"\nRepository = \"https://github.com/sparckles/robyn\"\nIssues = \"https://github.com/sparckles/robyn/issues\"\nChangelog = \"https://github.com/sparckles/robyn/blob/main/CHANGELOG.md\"\n\n[project.scripts]\nrobyn = \"robyn.cli:run\"\ntest_server = \"integration_tests.base_routes:main\"\n\n[dependency-groups]\ndev = [\n  \"black==23.1\",\n  \"commitizen==2.40\",\n  \"isort==5.11.5\",\n  \"maturin==1.7.4\",\n  \"pre-commit>=4.5.1,<5.0.0\",\n  \"ruff>=0.9.0\",\n]\ntest = [\n  \"nox==2023.4.22\",\n  \"pytest>=9.0.2\",\n  \"pytest-codspeed>=4.2.0\",\n  \"requests==2.28.2\",\n  \"websocket-client==1.5.0\",\n]\n\n\n[tool.poetry]\nname = \"robyn\"\nversion = \"0.82.0\"\ndescription = \"A Super Fast Async Python Web Framework with a Rust runtime.\"\nauthors = [\"Sanskar Jethi <sansyrox@gmail.com>\"]\n\n\n[tool.poetry.dependencies]\npython = \"^3.10\"\ninquirerpy = \"0.3.4\"\nmaturin = \"1.7.4\"\nwatchdog = \"^6.0.0\"\nmultiprocess = \"^0.70.18\"\nuvloop = { version = \"0.22.1\", markers = \"sys_platform != 'win32' and (sys_platform != 'cygwin' and platform_python_implementation != 'PyPy')\" }\njinja2 = { version = \"^3.1.6\", optional = true }\npydantic = { version = \"^2.0.0\", optional = true }\nrustimport = \"^1.3.4\"\norjson = \"^3.11.5\"\n\n[tool.poetry.extras]\ntemplating = [\"jinja2\"]\npydantic = [\"pydantic\"]\nall = [\"jinja2\", \"pydantic\"]\n\n[tool.poetry.group.dev]\noptional = true\n\n[tool.poetry.group.dev.dependencies]\nruff = \">=0.9.0\"\nblack = \"23.1\"\nisort = \"5.11.5\"\npre-commit = \"^4.5.1\"\ncommitizen = \"2.40\"\n\n[tool.poetry.group.test]\noptional = true\n\n[tool.poetry.group.test.dependencies]\npytest = \"^9.0.2\"\npytest-codspeed = \"^4.2.0\"\nrequests = \"2.28.2\"\nnox = \"2023.4.22\"\nwebsocket-client = \"1.5.0\"\n\n[tool.poetry.scripts]\ntest_server = { callable = \"integration_tests.base_routes:main\" }\n\n[tool.ruff]\nline-length = 160\nexclude = [\"src/*\", \".git\", \"docs\"]\n\n[tool.ruff.lint.mccabe]\nmax-complexity = 10\n\n[tool.isort]\nprofile = \"black\"\nline_length = 160\n\n[tool.black]\nline-length = 160\ntarget-version = ['py39']\ninclude = '\\.pyi?$'\nextend-exclude = '''\n/(\n  # directories\n  \\.eggs\n  | \\.git\n  | \\.hg\n  | \\.mypy_cache\n  | \\.tox\n  | \\.venv\n  | build\n  | dist\n)/\n'''\n\n[tool.pytest.ini_options]\nmarkers = [\n  \"benchmark: marks tests as benchmarks for performance measurement (deselect with '-m \\\"not benchmark\\\"')\",\n]\n\n[tool.maturin]\nmodule-name = \"robyn\"\n"
  },
  {
    "path": "robyn/__init__.py",
    "content": "import inspect\nimport logging\nimport os\nimport socket\nfrom abc import ABC\nfrom pathlib import Path\nfrom typing import Callable, List, Optional, Union\n\nimport multiprocess as mp  # type: ignore\n\nfrom robyn import status_codes\nfrom robyn.argument_parser import Config\nfrom robyn.authentication import AuthenticationHandler\nfrom robyn.dependency_injection import DependencyMap\nfrom robyn.env_populator import load_vars\nfrom robyn.events import Events\nfrom robyn.jsonify import jsonify\nfrom robyn.logger import Colors, logger\nfrom robyn.mcp import MCPApp\nfrom robyn.openapi import OpenAPI\nfrom robyn.processpool import run_processes\nfrom robyn.reloader import compile_rust_files\nfrom robyn.responses import SSEMessage, SSEResponse, StreamingResponse, html, serve_file, serve_html\nfrom robyn.robyn import FunctionInfo, Headers, HttpMethod, Request, Response, WebSocketConnector, get_version\nfrom robyn.router import MiddlewareRouter, MiddlewareType, Router, WebSocketRouter\nfrom robyn.types import Directory, JsonBody\nfrom robyn.ws import WebSocketAdapter, WebSocketDisconnect, create_websocket_decorator\n\n__version__ = get_version()\n\n\ndef _normalize_endpoint(endpoint: Optional[str], treat_empty_as_root: bool = False) -> Optional[str]:\n    \"\"\"\n    Normalize an endpoint to ensure consistent routing.\n\n    Rules:\n    - Root \"/\" remains unchanged\n    - All other endpoints get leading slash added if missing\n    - Trailing slashes are removed from all endpoints except root\n    - Empty or blank strings are handled based on treat_empty_as_root flag\n    - treat_empty_as_root is used for prefixes where empty/blank strings are valid\n\n    Args:\n        endpoint: The endpoint path to normalize.\n        treat_empty_as_root (used for prefixes):\n            If True, empty/blank strings are converted to \"/\" (root).\n            If False, empty/blank strings return None (invalid endpoint).\n\n    Returns:\n        Normalized endpoint path or None if invalid.\n    \"\"\"\n    if endpoint is None or (not endpoint and not treat_empty_as_root):\n        return None\n\n    # Remove trailing slashes\n    endpoint = endpoint.strip().rstrip(\"/\")\n\n    # Handle empty result\n    if not endpoint:\n        return \"/\"\n\n    # Add leading slash if missing\n    if not endpoint.startswith(\"/\"):\n        endpoint = \"/\" + endpoint\n\n    return endpoint\n\n\nconfig = Config()\n\nif (compile_path := config.compile_rust_path) is not None:\n    compile_rust_files(compile_path)\n    print(\"Compiled rust files\")\n\n\nclass BaseRobyn(ABC):\n    \"\"\"This is the python wrapper for the Robyn binaries.\"\"\"\n\n    def __init__(\n        self,\n        file_object: str,\n        config: Config = Config(),\n        openapi_file_path: Optional[str] = None,\n        openapi: Optional[OpenAPI] = None,\n        dependencies: DependencyMap = DependencyMap(),\n    ) -> None:\n        directory_path = os.path.dirname(os.path.abspath(file_object))\n        self.file_path = file_object\n        self.directory_path = directory_path\n        self.config = config\n        self.dependencies = dependencies\n        self.openapi = openapi\n\n        self.init_openapi(openapi_file_path)\n\n        if not bool(os.environ.get(\"ROBYN_CLI\", False)):\n            # the env variables are already set when are running through the cli\n            load_vars(project_root=directory_path)\n\n        self._handle_dev_mode()\n\n        logging.basicConfig(level=self.config.log_level)\n\n        if self.config.log_level.lower() != \"warn\":\n            logger.info(\n                \"SERVER IS RUNNING IN VERBOSE/DEBUG MODE. Set --log-level to WARN to run in production mode.\",\n                color=Colors.BLUE,\n            )\n\n        self.router = Router()\n        self.middleware_router = MiddlewareRouter()\n        self.web_socket_router = WebSocketRouter()\n        self.request_headers: Headers = Headers({})\n        self.response_headers: Headers = Headers({})\n        self.excluded_response_headers_paths: Optional[List[str]] = None\n        self.directories: List[Directory] = []\n        self.event_handlers: dict = {}\n        self.exception_handler: Optional[Callable] = None\n        self.authentication_handler: Optional[AuthenticationHandler] = None\n        self.included_routers: List[Router] = []\n        self._mcp_app: Optional[MCPApp] = None\n\n    def init_openapi(self, openapi_file_path: Optional[str]) -> None:\n        if self.config.disable_openapi:\n            return\n\n        if self.openapi is None:\n            self.openapi = OpenAPI()\n\n        if openapi_file_path:\n            self.openapi.override_openapi(Path(self.directory_path).joinpath(openapi_file_path))\n        elif Path(self.directory_path).joinpath(\"openapi.json\").exists():\n            self.openapi.override_openapi(Path(self.directory_path).joinpath(\"openapi.json\"))\n        else:\n            logger.debug(\"No OpenAPI spec file found; using auto-generated documentation only.\", color=Colors.YELLOW)\n\n    def _handle_dev_mode(self):\n        cli_dev_mode = self.config.dev  # --dev\n        env_dev_mode = os.getenv(\"ROBYN_DEV_MODE\", \"False\").lower() == \"true\"  # ROBYN_DEV_MODE=True\n        is_robyn = os.getenv(\"ROBYN_CLI\", False)\n\n        if cli_dev_mode and not is_robyn:\n            raise SystemExit(\"Dev mode is not supported in the python wrapper. Please use the Robyn CLI. e.g. python3 -m robyn app.py --dev\")\n\n        if env_dev_mode and not is_robyn:\n            logger.error(\"Ignoring ROBYN_DEV_MODE environment variable. Dev mode is not supported in the python wrapper.\")\n            raise SystemExit(\"Dev mode is not supported in the python wrapper. Please use the Robyn CLI. e.g. python3 -m robyn app.py\")\n\n    def add_route(\n        self,\n        route_type: Union[HttpMethod, str],\n        endpoint: str,\n        handler: Callable,\n        is_const: bool = False,\n        auth_required: bool = False,\n        openapi_name: str = \"\",\n        openapi_tags: Union[List[str], None] = None,\n    ):\n        \"\"\"\n        Connect a URI to a handler\n\n        :param route_type str: route type between GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS/TRACE\n        :param endpoint str: endpoint for the route added\n        :param handler function: represents the sync or async function passed as a handler for the route\n        :param is_const bool: represents if the handler is a const function or not\n        :param auth_required bool: represents if the route needs authentication or not\n        \"\"\"\n\n        \"\"\" We will add the status code here only\n        \"\"\"\n        injected_dependencies = self.dependencies.get_dependency_map(self)\n\n        list_openapi_tags: List[str] = openapi_tags if openapi_tags else []\n\n        if isinstance(route_type, str):\n            http_methods = {\n                \"GET\": HttpMethod.GET,\n                \"POST\": HttpMethod.POST,\n                \"PUT\": HttpMethod.PUT,\n                \"DELETE\": HttpMethod.DELETE,\n                \"PATCH\": HttpMethod.PATCH,\n                \"HEAD\": HttpMethod.HEAD,\n                \"OPTIONS\": HttpMethod.OPTIONS,\n            }\n            route_type = http_methods[route_type]\n\n        # Normalize endpoint before adding\n        normalized_endpoint = _normalize_endpoint(endpoint)\n\n        if normalized_endpoint is None:\n            raise ValueError(\"Endpoint cannot be blank, do specify '/' for root endpoint\")\n\n        if auth_required:\n            self.middleware_router.add_auth_middleware(normalized_endpoint, route_type)(handler)\n\n        # Check if this exact route (method + normalized_endpoint) already exists\n        route_key = f\"{route_type}:{normalized_endpoint}\"\n        if not hasattr(self, \"_added_routes\"):\n            self._added_routes = set()\n\n        if route_key in self._added_routes:\n            # Route already exists, raise an error\n            raise ValueError(f\"Route {route_type} {normalized_endpoint} already exists\")\n\n        # Add to our tracking set\n        self._added_routes.add(route_key)\n\n        add_route_response = self.router.add_route(\n            route_type=route_type,\n            endpoint=normalized_endpoint,\n            handler=handler,\n            is_const=is_const,\n            auth_required=auth_required,\n            openapi_name=openapi_name,\n            openapi_tags=list_openapi_tags,\n            exception_handler=self.exception_handler,\n            injected_dependencies=injected_dependencies,\n        )\n\n        logger.info(\"Added route %s %s\", route_type, normalized_endpoint)\n\n        return add_route_response\n\n    def inject(self, **kwargs):\n        \"\"\"\n        Injects the dependencies for the route\n\n        :param kwargs dict: the dependencies to be injected\n        \"\"\"\n        self.dependencies.add_router_dependency(self, **kwargs)\n\n    def inject_global(self, **kwargs):\n        \"\"\"\n        Injects the dependencies for the global routes\n        Ideally, this function should be a global function\n\n        :param kwargs dict: the dependencies to be injected\n        \"\"\"\n        self.dependencies.add_global_dependency(**kwargs)\n\n    def before_request(self, endpoint: Optional[str] = None) -> Callable[..., None]:\n        \"\"\"\n        You can use the @app.before_request decorator to call a method before routing to the specified endpoint\n\n        :param endpoint str|None: endpoint to server the route. If None, the middleware will be applied to all the routes.\n        \"\"\"\n        return self.middleware_router.add_middleware(MiddlewareType.BEFORE_REQUEST, _normalize_endpoint(endpoint))\n\n    def after_request(self, endpoint: Optional[str] = None) -> Callable[..., None]:\n        \"\"\"\n        You can use the @app.after_request decorator to call a method after routing to the specified endpoint\n\n        :param endpoint str|None: endpoint to server the route. If None, the middleware will be applied to all the routes.\n        \"\"\"\n        return self.middleware_router.add_middleware(MiddlewareType.AFTER_REQUEST, _normalize_endpoint(endpoint))\n\n    def serve_directory(\n        self,\n        route: str,\n        directory_path: str,\n        index_file: Optional[str] = None,\n        show_files_listing: bool = False,\n    ):\n        \"\"\"\n        Serves a directory at the given route\n\n        :param route str: the route at which the directory is to be served\n        :param directory_path str: the path of the directory to be served\n        :param index_file str|None: the index file to be served\n        :param show_files_listing bool: if the files listing should be shown or not\n        \"\"\"\n        self.directories.append(Directory(route, directory_path, show_files_listing, index_file))\n\n    def add_request_header(self, key: str, value: str) -> None:\n        self.request_headers.append(key, value)\n\n    def add_response_header(self, key: str, value: str) -> None:\n        self.response_headers.append(key, value)\n\n    def set_request_header(self, key: str, value: str) -> None:\n        self.request_headers.set(key, value)\n\n    def set_response_header(self, key: str, value: str) -> None:\n        self.response_headers.set(key, value)\n\n    def exclude_response_headers_for(self, excluded_response_headers_paths: Optional[List[str]]):\n        \"\"\"\n        To exclude response headers from certain routes\n        @param exclude_paths: the paths to exclude response headers from\n        \"\"\"\n        self.excluded_response_headers_paths = excluded_response_headers_paths\n\n    def add_web_socket(self, endpoint: str, handlers) -> None:\n        self.web_socket_router.add_route(endpoint, handlers)\n\n    def websocket(self, endpoint: str):\n        \"\"\"\n        Modern WebSocket decorator backed by Rust channels.\n\n        Usage:\n            @app.websocket(\"/ws\")\n            async def handler(websocket):\n                while True:\n                    msg = await websocket.receive_text()\n                    await websocket.send_text(f\"Echo: {msg}\")\n\n            @handler.on_connect\n            def on_connect(websocket):\n                return \"Welcome!\"\n\n            @handler.on_close\n            def on_close(websocket):\n                return \"Goodbye\"\n        \"\"\"\n        return create_websocket_decorator(self)(endpoint)\n\n    def _add_event_handler(self, event_type: Events, handler: Callable) -> None:\n        logger.info(\"Added event %s handler\", event_type)\n        if event_type not in {Events.STARTUP, Events.SHUTDOWN}:\n            return\n\n        is_async = inspect.iscoroutinefunction(handler)\n        self.event_handlers[event_type] = FunctionInfo(handler, is_async, 0, {}, {})\n\n    def startup_handler(self, handler: Callable) -> None:\n        self._add_event_handler(Events.STARTUP, handler)\n\n    def shutdown_handler(self, handler: Callable) -> None:\n        self._add_event_handler(Events.SHUTDOWN, handler)\n\n    def is_port_in_use(self, port: int) -> bool:\n        try:\n            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:\n                return s.connect_ex((\"localhost\", port)) == 0\n        except Exception:\n            raise Exception(f\"Invalid port number: {port}\")\n\n    def _add_openapi_routes(self, auth_required: bool = False):\n        if self.config.disable_openapi:\n            return\n\n        if self.openapi is None:\n            logger.error(\"No openAPI\")\n            return\n\n        self.router.prepare_routes_openapi(self.openapi, self.included_routers)\n\n        self.add_route(\n            route_type=HttpMethod.GET,\n            endpoint=\"/openapi.json\",\n            handler=self.openapi.get_openapi_config,\n            is_const=True,\n            auth_required=auth_required,\n        )\n        self.add_route(\n            route_type=HttpMethod.GET,\n            endpoint=\"/docs\",\n            handler=self.openapi.get_openapi_docs_page,\n            is_const=True,\n            auth_required=auth_required,\n        )\n        self.exclude_response_headers_for([\"/docs\", \"/openapi.json\"])\n\n    def exception(self, exception_handler: Callable):\n        self.exception_handler = exception_handler\n\n    def get(\n        self,\n        endpoint: str,\n        const: bool = False,\n        auth_required: bool = False,\n        openapi_name: str = \"\",\n        openapi_tags: List[str] = [\"get\"],\n    ):\n        \"\"\"\n        The @app.get decorator to add a route with the GET method\n\n        :param endpoint str: endpoint for the route added\n        :param const bool: represents if the handler is a const function or not\n        :param auth_required bool: represents if the route needs authentication or not\n        :param openapi_name: str -- the name of the endpoint in the openapi spec\n        :param openapi_tags: List[str] -- for grouping of endpoints in the openapi spec\n        \"\"\"\n\n        def inner(handler):\n            return self.add_route(HttpMethod.GET, endpoint, handler, const, auth_required, openapi_name, openapi_tags)\n\n        return inner\n\n    def post(\n        self,\n        endpoint: str,\n        auth_required: bool = False,\n        openapi_name: str = \"\",\n        openapi_tags: List[str] = [\"post\"],\n    ):\n        \"\"\"\n        The @app.post decorator to add a route with POST method\n\n        :param endpoint str: endpoint for the route added\n        :param auth_required bool: represents if the route needs authentication or not\n        :param openapi_name: str -- the name of the endpoint in the openapi spec\n        :param openapi_tags: List[str] -- for grouping of endpoints in the openapi spec\n        \"\"\"\n\n        def inner(handler):\n            return self.add_route(HttpMethod.POST, endpoint, handler, auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags)\n\n        return inner\n\n    def put(\n        self,\n        endpoint: str,\n        auth_required: bool = False,\n        openapi_name: str = \"\",\n        openapi_tags: List[str] = [\"put\"],\n    ):\n        \"\"\"\n        The @app.put decorator to add a get route with PUT method\n\n        :param endpoint str: endpoint for the route added\n        :param auth_required bool: represents if the route needs authentication or not\n        :param openapi_name: str -- the name of the endpoint in the openapi spec\n        :param openapi_tags: List[str] -- for grouping of endpoints in the openapi spec\n        \"\"\"\n\n        def inner(handler):\n            return self.add_route(HttpMethod.PUT, endpoint, handler, auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags)\n\n        return inner\n\n    def delete(\n        self,\n        endpoint: str,\n        auth_required: bool = False,\n        openapi_name: str = \"\",\n        openapi_tags: List[str] = [\"delete\"],\n    ):\n        \"\"\"\n        The @app.delete decorator to add a route with DELETE method\n\n        :param endpoint str: endpoint for the route added\n        :param auth_required bool: represents if the route needs authentication or not\n        :param openapi_name: str -- the name of the endpoint in the openapi spec\n        :param openapi_tags: List[str] -- for grouping of endpoints in the openapi spec\n        \"\"\"\n\n        def inner(handler):\n            return self.add_route(HttpMethod.DELETE, endpoint, handler, auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags)\n\n        return inner\n\n    def patch(\n        self,\n        endpoint: str,\n        auth_required: bool = False,\n        openapi_name: str = \"\",\n        openapi_tags: List[str] = [\"patch\"],\n    ):\n        \"\"\"\n        The @app.patch decorator to add a route with PATCH method\n\n        :param endpoint str: endpoint for the route added\n        :param auth_required bool: represents if the route needs authentication or not\n        :param openapi_name: str -- the name of the endpoint in the openapi spec\n        :param openapi_tags: List[str] -- for grouping of endpoints in the openapi spec\n        \"\"\"\n\n        def inner(handler):\n            return self.add_route(HttpMethod.PATCH, endpoint, handler, auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags)\n\n        return inner\n\n    def head(\n        self,\n        endpoint: str,\n        auth_required: bool = False,\n        openapi_name: str = \"\",\n        openapi_tags: List[str] = [\"head\"],\n    ):\n        \"\"\"\n        The @app.head decorator to add a route with HEAD method\n\n        :param endpoint str: endpoint for the route added\n        :param auth_required bool: represents if the route needs authentication or not\n        :param openapi_name: str -- the name of the endpoint in the openapi spec\n        :param openapi_tags: List[str] -- for grouping of endpoints in the openapi spec\n        \"\"\"\n\n        def inner(handler):\n            return self.add_route(HttpMethod.HEAD, endpoint, handler, auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags)\n\n        return inner\n\n    def options(\n        self,\n        endpoint: str,\n        auth_required: bool = False,\n        openapi_name: str = \"\",\n        openapi_tags: List[str] = [\"options\"],\n    ):\n        \"\"\"\n        The @app.options decorator to add a route with OPTIONS method\n\n        :param endpoint str: endpoint for the route added\n        :param auth_required bool: represents if the route needs authentication or not\n        :param openapi_name: str -- the name of the endpoint in the openapi spec\n        :param openapi_tags: List[str] -- for grouping of endpoints in the openapi spec\n        \"\"\"\n\n        def inner(handler):\n            return self.add_route(HttpMethod.OPTIONS, endpoint, handler, auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags)\n\n        return inner\n\n    def connect(\n        self,\n        endpoint: str,\n        auth_required: bool = False,\n        openapi_name: str = \"\",\n        openapi_tags: List[str] = [\"connect\"],\n    ):\n        \"\"\"\n        The @app.connect decorator to add a route with CONNECT method\n\n        :param endpoint str: endpoint for the route added\n        :param auth_required bool: represents if the route needs authentication or not\n        :param openapi_name: str -- the name of the endpoint in the openapi spec\n        :param openapi_tags: List[str] -- for grouping of endpoints in the openapi spec\n        \"\"\"\n\n        def inner(handler):\n            return self.add_route(HttpMethod.CONNECT, endpoint, handler, auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags)\n\n        return inner\n\n    def trace(\n        self,\n        endpoint: str,\n        auth_required: bool = False,\n        openapi_name: str = \"\",\n        openapi_tags: List[str] = [\"trace\"],\n    ):\n        \"\"\"\n        The @app.trace decorator to add a route with TRACE method\n\n        :param endpoint str: endpoint for the route added\n        :param auth_required bool: represents if the route needs authentication or not\n        :param openapi_name: str -- the name of the endpoint in the openapi spec\n        :param openapi_tags: List[str] -- for grouping of endpoints in the openapi spec\n        \"\"\"\n\n        def inner(handler):\n            return self.add_route(HttpMethod.TRACE, endpoint, handler, auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags)\n\n        return inner\n\n    def include_router(self, router: \"SubRouter\"):\n        \"\"\"\n        The method to include the routes from another router.\n        Merge another SubRouter's routes, middlewares, websocket routes, and dependencies into this router.\n        Note: This operation mutates the current router's internal collections (route list, middleware lists,\n        websocket routes, and dependencies) and does not deep-copy the included router. Callers should ensure\n        there are no path or name conflicts before including a router.\n\n        :param router SubRouter: the router object to include the routes from\n        \"\"\"\n        self.included_routers.append(router)\n\n        self.router.routes.extend(router.router.routes)\n        self.middleware_router.global_middlewares.extend(router.middleware_router.global_middlewares)\n        self.middleware_router.route_middlewares.extend(router.middleware_router.route_middlewares)\n\n        if not self.config.disable_openapi and self.openapi is not None:\n            self.openapi.add_subrouter_paths(self.openapi)\n\n        # extend the websocket routes\n        prefix = _normalize_endpoint(router.prefix, treat_empty_as_root=True)\n        if prefix == \"/\":\n            prefix = \"\"\n        for route, handlers in router.web_socket_router.routes.items():\n            normalized_route = _normalize_endpoint(route)\n            new_endpoint = f\"{prefix}{normalized_route}\"\n            self.web_socket_router.routes[new_endpoint] = handlers\n\n        self.dependencies.merge_dependencies(router)\n\n    def configure_authentication(self, authentication_handler: AuthenticationHandler):\n        \"\"\"\n        Configures the authentication handler for the application.\n\n        :param authentication_handler: the instance of a class inheriting the AuthenticationHandler base class\n        \"\"\"\n        self.authentication_handler = authentication_handler\n        self.middleware_router.set_authentication_handler(authentication_handler)\n\n    @property\n    def mcp(self):\n        \"\"\"\n        Get the MCP (Model Context Protocol) interface for this app.\n\n        Enables registering MCP resources, tools, and prompts that can be accessed\n        by MCP clients like Claude Desktop or other AI applications.\n\n        Returns:\n            MCPApp: MCP interface for registering handlers\n\n        Example:\n            @app.mcp.resource(\"file://documents\", \"Documents\", \"Access to document files\")\n            def get_documents(params):\n                return \"Document content here\"\n\n            @app.mcp.tool(\"calculate\", \"Perform calculations\", {\n                \"type\": \"object\",\n                \"properties\": {\n                    \"expression\": {\"type\": \"string\", \"description\": \"Math expression to evaluate\"}\n                },\n                \"required\": [\"expression\"]\n            })\n            def calculate_tool(args):\n                return eval(args[\"expression\"])\n        \"\"\"\n        if self._mcp_app is None:\n            self._mcp_app = MCPApp(self)\n        return self._mcp_app\n\n\nclass Robyn(BaseRobyn):\n    def start(self, host: str = \"127.0.0.1\", port: int = 8080, _check_port: bool = True, client_timeout: int = 30, keep_alive_timeout: int = 20):\n        \"\"\"\n        Starts the server\n\n        :param host str: represents the host at which the server is listening\n        :param port int: represents the port number at which the server is listening\n        :param _check_port bool: represents if the port should be checked if it is already in use\n        :param client_timeout int: timeout for client connections in seconds (default: 30)\n        :param keep_alive_timeout int: timeout for keep-alive connections in seconds (default: 20)\n        \"\"\"\n\n        host = os.getenv(\"ROBYN_HOST\", host)\n        port = int(os.getenv(\"ROBYN_PORT\", port))\n        client_timeout = int(os.getenv(\"ROBYN_CLIENT_TIMEOUT\", client_timeout))\n        keep_alive_timeout = int(os.getenv(\"ROBYN_KEEP_ALIVE_TIMEOUT\", keep_alive_timeout))\n        open_browser = bool(os.getenv(\"ROBYN_BROWSER_OPEN\", self.config.open_browser))\n\n        if _check_port:\n            while self.is_port_in_use(port):\n                logger.error(\"Port %s is already in use. Please use a different port.\", port)\n                try:\n                    port = int(input(\"Enter a different port: \"))\n                except Exception:\n                    logger.error(\"Invalid port number. Please enter a valid port number.\")\n                    continue\n\n        if not self.config.disable_openapi:\n            self._add_openapi_routes()\n            logger.info(\"Docs hosted at http://%s:%s/docs\", host, port)\n\n        logger.info(\"Robyn version: %s\", __version__)\n        logger.info(\"Starting server at http://%s:%s\", host, port)\n\n        mp.allow_connection_pickling()\n\n        run_processes(\n            host,\n            port,\n            self.directories,\n            self.request_headers,\n            self.router.get_routes(),\n            self.middleware_router.get_global_middlewares(),\n            self.middleware_router.get_route_middlewares(),\n            self.web_socket_router.get_routes(),\n            self.event_handlers,\n            self.config.workers,\n            self.config.processes,\n            self.response_headers,\n            self.excluded_response_headers_paths,\n            open_browser,\n            client_timeout,\n            keep_alive_timeout,\n        )\n\n\nclass SubRouter(BaseRobyn):\n    def __init__(self, file_object: str, prefix: str = \"\", config: Config = Config(), openapi: OpenAPI = OpenAPI()) -> None:\n        super().__init__(file_object=file_object, config=config, openapi=openapi)\n        self.prefix = prefix\n\n    def __add_prefix(self, endpoint: str):\n        # Normalize prefix, treating empty as empty (not root)\n        normalized_prefix = _normalize_endpoint(self.prefix, treat_empty_as_root=True)\n\n        # Handle empty endpoint - should just be the prefix\n        if endpoint in (\"\", \"/\"):\n            return normalized_prefix if normalized_prefix else \"/\"\n\n        # Convert root prefix to empty to avoid double slashes when making endpoint\n        if normalized_prefix == \"/\":\n            normalized_prefix = \"\"  # Empty prefix for root\n\n        # Normalize and validate endpoint\n        normalized_endpoint = _normalize_endpoint(endpoint)\n        if normalized_endpoint is None:\n            raise ValueError(\"Endpoint cannot be blank, do specify '/' for root endpoint\")\n\n        return f\"{normalized_prefix}{normalized_endpoint}\"\n\n    def get(self, endpoint: str, const: bool = False, auth_required: bool = False, openapi_name: str = \"\", openapi_tags: List[str] = [\"get\"]):\n        return super().get(endpoint=self.__add_prefix(endpoint), const=const, auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags)\n\n    def post(self, endpoint: str, auth_required: bool = False, openapi_name: str = \"\", openapi_tags: List[str] = [\"post\"]):\n        return super().post(endpoint=self.__add_prefix(endpoint), auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags)\n\n    def put(self, endpoint: str, auth_required: bool = False, openapi_name: str = \"\", openapi_tags: List[str] = [\"put\"]):\n        return super().put(endpoint=self.__add_prefix(endpoint), auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags)\n\n    def delete(self, endpoint: str, auth_required: bool = False, openapi_name: str = \"\", openapi_tags: List[str] = [\"delete\"]):\n        return super().delete(endpoint=self.__add_prefix(endpoint), auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags)\n\n    def patch(self, endpoint: str, auth_required: bool = False, openapi_name: str = \"\", openapi_tags: List[str] = [\"patch\"]):\n        return super().patch(endpoint=self.__add_prefix(endpoint), auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags)\n\n    def head(self, endpoint: str, auth_required: bool = False, openapi_name: str = \"\", openapi_tags: List[str] = [\"head\"]):\n        return super().head(endpoint=self.__add_prefix(endpoint), auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags)\n\n    def trace(self, endpoint: str, auth_required: bool = False, openapi_name: str = \"\", openapi_tags: List[str] = [\"trace\"]):\n        return super().trace(endpoint=self.__add_prefix(endpoint), auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags)\n\n    def options(self, endpoint: str, auth_required: bool = False, openapi_name: str = \"\", openapi_tags: List[str] = [\"options\"]):\n        return super().options(endpoint=self.__add_prefix(endpoint), auth_required=auth_required, openapi_name=openapi_name, openapi_tags=openapi_tags)\n\n    def websocket(self, endpoint: str):\n        \"\"\"\n        Modern WebSocket decorator for SubRouter with prefix support.\n        \"\"\"\n        return create_websocket_decorator(self)(endpoint)\n\n\ndef ALLOW_CORS(app: Robyn, origins: Union[List[str], str], headers: Union[List[str], str] = None):\n    \"\"\"\n    Configure CORS headers for the application.\n\n    Args:\n        app: Robyn application instance\n        origins: List of allowed origins or \"*\" for all origins\n        headers: List of allowed headers or \"*\" for all headers\n    \"\"\"\n    # Handle string input for origins\n    if isinstance(origins, str):\n        origins = [origins]\n\n    default_headers = [\"Content-Type\", \"Authorization\"]\n    if isinstance(headers, list):\n        headers = list(set(default_headers + headers))\n        headers = \", \".join(headers)\n\n    @app.before_request()\n    def cors_middleware(request):\n        origin = request.headers.get(\"Origin\")\n\n        # If specific origins are set, validate the request origin\n        if origin and \"*\" not in origins and origin not in origins:\n            return Response(status_code=403, description=\"\", headers={})\n\n        # Handle preflight requests\n        if request.method == \"OPTIONS\":\n            return Response(\n                status_code=204,\n                headers={\n                    \"Access-Control-Allow-Origin\": origin if origin else (origins[0] if origins else \"*\"),\n                    \"Access-Control-Allow-Methods\": \"GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS\",\n                    \"Access-Control-Allow-Headers\": str(headers) if headers else \"Content-Type, Authorization\",\n                    \"Access-Control-Allow-Credentials\": \"true\",\n                    \"Access-Control-Max-Age\": \"3600\",\n                },\n                description=\"\",\n            )\n\n        return request\n\n    # Set default CORS headers for all responses\n    if len(origins) == 1:\n        app.set_response_header(\"Access-Control-Allow-Origin\", origins[0])\n    else:\n        # For multiple origins, we'll handle it dynamically in the response\n        app.set_response_header(\"Access-Control-Allow-Origin\", \"*\")\n\n    app.set_response_header(\"Access-Control-Allow-Methods\", \"GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS\")\n    app.set_response_header(\"Access-Control-Allow-Headers\", str(headers) if headers else \"Content-Type, Authorization\")\n    app.set_response_header(\"Access-Control-Allow-Credentials\", \"true\")\n\n\n__all__ = [\n    \"Robyn\",\n    \"Request\",\n    \"Response\",\n    \"status_codes\",\n    \"jsonify\",\n    \"serve_file\",\n    \"serve_html\",\n    \"html\",\n    \"StreamingResponse\",\n    \"SSEResponse\",\n    \"SSEMessage\",\n    \"ALLOW_CORS\",\n    \"SubRouter\",\n    \"AuthenticationHandler\",\n    \"Headers\",\n    \"WebSocketConnector\",\n    \"WebSocketAdapter\",\n    \"WebSocketDisconnect\",\n    \"JsonBody\",\n    \"MCPApp\",\n]\n"
  },
  {
    "path": "robyn/__main__.py",
    "content": "from robyn.cli import run\n\nif __name__ == \"__main__\":\n    run()\n"
  },
  {
    "path": "robyn/_param_utils.py",
    "content": "\"\"\"\nShared utilities for resolving individual query/path parameters\nfrom handler function signatures, with type coercion.\n\nUsed by both robyn/router.py (HTTP handlers) and robyn/ws.py (WebSocket handlers).\n\"\"\"\n\nimport inspect\nimport logging\nfrom typing import Any, Dict, Optional, Set, Tuple, Union\n\n_logger = logging.getLogger(__name__)\n\n_MISSING = object()\n\n# Values that map to True/False from query string bool params\n_BOOL_TRUE_STRINGS = frozenset({\"true\", \"1\", \"yes\", \"on\"})\n_BOOL_FALSE_STRINGS = frozenset({\"false\", \"0\", \"no\", \"off\", \"\"})\n\n\nclass QueryParamValidationError(Exception):\n    \"\"\"Raised when a query or path parameter cannot be coerced to the expected type,\n    or when a required parameter is missing.\"\"\"\n\n    def __init__(self, param_name: str, value: Optional[str], expected_type: type, message: Optional[str] = None):\n        self.param_name = param_name\n        self.value = value\n        self.expected_type = expected_type\n        if message:\n            self.detail = message\n        elif value is None:\n            self.detail = f\"Missing required parameter: '{param_name}'\"\n        else:\n            self.detail = f\"Invalid value '{value}' for parameter '{param_name}': expected {expected_type.__name__}\"\n        super().__init__(self.detail)\n\n\ndef unwrap_optional(annotation) -> Tuple[Any, bool]:\n    \"\"\"\n    If annotation is Optional[T] (i.e. Union[T, None]), return (T, True).\n    Otherwise return (annotation, False).\n    \"\"\"\n    origin = getattr(annotation, \"__origin__\", None)\n    if origin is Union:\n        args = annotation.__args__\n        non_none_args = [a for a in args if a is not type(None)]\n        if len(non_none_args) == 1 and type(None) in args:\n            return non_none_args[0], True\n    return annotation, False\n\n\ndef is_list_type(annotation) -> bool:\n    \"\"\"Check if annotation is List[T] or list[T].\"\"\"\n    origin = getattr(annotation, \"__origin__\", None)\n    return origin is list\n\n\ndef get_list_element_type(annotation) -> type:\n    \"\"\"Get the element type from List[T]. Defaults to str if not specified.\"\"\"\n    args = getattr(annotation, \"__args__\", None)\n    if args and len(args) > 0:\n        return args[0]\n    return str\n\n\ndef coerce_value(value: str, target_type: type, param_name: str):\n    \"\"\"\n    Convert a string value to the target type.\n    Raises QueryParamValidationError on failure.\n    \"\"\"\n    if target_type is str or target_type is inspect.Parameter.empty:\n        return value\n\n    try:\n        if target_type is int:\n            return int(value)\n        if target_type is float:\n            return float(value)\n        if target_type is bool:\n            lower = value.lower()\n            if lower in _BOOL_TRUE_STRINGS:\n                return True\n            if lower in _BOOL_FALSE_STRINGS:\n                return False\n            raise ValueError(f\"Cannot interpret '{value}' as bool\")\n        # Fallback: try calling the type constructor (covers Enum, UUID, etc.)\n        return target_type(value)\n    except (ValueError, TypeError) as e:\n        raise QueryParamValidationError(param_name, value, target_type) from e\n\n\ndef resolve_individual_params(\n    unresolved_params: Dict[str, inspect.Parameter],\n    query_params,\n    path_params: Optional[Dict[str, str]],\n    route_param_names: Set[str],\n) -> Dict[str, Any]:\n    \"\"\"\n    Resolve handler parameters as individual path or query parameters.\n\n    For each unresolved parameter:\n      1. If its name matches a route param name (from the endpoint pattern), look it up in path_params.\n      2. Otherwise, look it up in query_params.\n      3. Apply type coercion based on the parameter's annotation.\n      4. Fall back to the parameter's default value, or None for Optional types.\n      5. Raise QueryParamValidationError if a required parameter is missing.\n\n    Args:\n        unresolved_params: dict of param_name -> inspect.Parameter for params not yet resolved.\n        query_params: QueryParams object with .get() and .get_all() methods.\n        path_params: dict of path parameter values (may be None for WebSocket).\n        route_param_names: set of parameter names declared in the route pattern (e.g. from /:id).\n\n    Returns:\n        dict mapping param names to their resolved values.\n    \"\"\"\n    resolved = {}\n\n    for param_name, param in unresolved_params.items():\n        annotation = param.annotation\n        if annotation is inspect.Parameter.empty:\n            annotation = str\n\n        inner_type, is_optional = unwrap_optional(annotation)\n        is_list = is_list_type(inner_type)\n        elem_type = get_list_element_type(inner_type) if is_list else inner_type\n\n        raw_value = _MISSING\n\n        # 1. Check path params first\n        if path_params is not None and param_name in route_param_names:\n            pv = path_params.get(param_name)\n            if pv is not None:\n                raw_value = pv\n\n        # 2. Check query params\n        if raw_value is _MISSING and query_params is not None:\n            if is_list:\n                all_values = query_params.get_all(param_name)\n                if all_values is not None:\n                    resolved[param_name] = [coerce_value(v, elem_type, param_name) for v in all_values]\n                    continue\n            else:\n                qp_value = query_params.get(param_name, None)\n                if qp_value is not None:\n                    raw_value = qp_value\n\n        # 3. Got a value — coerce it\n        if raw_value is not _MISSING:\n            resolved[param_name] = coerce_value(raw_value, inner_type, param_name)\n            continue\n\n        # 4. Use default value if available\n        if param.default is not inspect.Parameter.empty:\n            resolved[param_name] = param.default\n            continue\n\n        # 5. Optional with no default -> None\n        if is_optional:\n            resolved[param_name] = None\n            continue\n\n        # 6. Truly missing required parameter\n        raise QueryParamValidationError(param_name, None, elem_type)\n\n    return resolved\n\n\ndef parse_route_param_names(endpoint: str) -> Set[str]:\n    \"\"\"\n    Extract parameter names from a route endpoint pattern.\n    e.g. \"/users/:id/posts/:post_id\" -> {\"id\", \"post_id\"}\n\n    Walks the string character by character looking for ':' followed by\n    word characters (alphanumeric + underscore).\n    \"\"\"\n    names = set()\n    i = 0\n    length = len(endpoint)\n    while i < length:\n        if endpoint[i] == \":\":\n            # Start of a param name — collect word characters\n            i += 1\n            start = i\n            while i < length and (endpoint[i].isalnum() or endpoint[i] == \"_\"):\n                i += 1\n            if i > start:\n                names.add(endpoint[start:i])\n        else:\n            i += 1\n    return names\n"
  },
  {
    "path": "robyn/ai.py",
    "content": "\"\"\"\nThis is an experimental AI integration module for Robyn framework.\n\nA poc for the blog at https://sanskar.wtf/posts/the-future-of-robyn\n\nProvides agent and memory functionality for building AI-powered applications for the demonstration of the vision mentioned in\n\"\"\"\n\nimport logging\nimport os\nfrom abc import ABC, abstractmethod\nfrom typing import Any, Dict, List, Optional, Union\n\nlogger = logging.getLogger(__name__)\n\n\nclass AIConfig:\n    \"\"\"Configuration class for AI providers and settings\"\"\"\n\n    def __init__(self, **kwargs):\n        self.config = kwargs\n        self._load_from_env()\n\n    def _load_from_env(self):\n        \"\"\"Load configuration from environment variables\"\"\"\n        env_vars = {\n            \"OPENAI_API_KEY\": \"openai_api_key\",\n            \"ANTHROPIC_API_KEY\": \"anthropic_api_key\",\n            \"GOOGLE_API_KEY\": \"google_api_key\",\n            \"AI_MODEL\": \"model\",\n            \"AI_TEMPERATURE\": \"temperature\",\n            \"AI_MAX_TOKENS\": \"max_tokens\",\n        }\n\n        for env_var, config_key in env_vars.items():\n            if env_var in os.environ and config_key not in self.config:\n                value = os.environ[env_var]\n                # Convert numeric values\n                if config_key in [\"temperature\", \"max_tokens\"]:\n                    try:\n                        value = float(value) if config_key == \"temperature\" else int(value)\n                    except ValueError:\n                        pass\n                self.config[config_key] = value\n\n    def get(self, key: str, default: Any = None) -> Any:\n        \"\"\"Get configuration value\"\"\"\n        return self.config.get(key, default)\n\n    def set(self, key: str, value: Any) -> None:\n        \"\"\"Set configuration value\"\"\"\n        self.config[key] = value\n\n    def update(self, **kwargs) -> None:\n        \"\"\"Update configuration with new values\"\"\"\n        self.config.update(kwargs)\n\n    def to_dict(self) -> Dict[str, Any]:\n        \"\"\"Get configuration as dictionary\"\"\"\n        return self.config.copy()\n\n\nclass MemoryProvider(ABC):\n    \"\"\"Abstract base class for memory providers\"\"\"\n\n    @abstractmethod\n    async def store(self, user_id: str, data: Dict[str, Any]) -> None:\n        \"\"\"Store data in memory\"\"\"\n        pass\n\n    @abstractmethod\n    async def retrieve(self, user_id: str, query: Optional[str] = None) -> List[Dict[str, Any]]:\n        \"\"\"Retrieve data from memory\"\"\"\n        pass\n\n    @abstractmethod\n    async def clear(self, user_id: str) -> None:\n        \"\"\"Clear memory for a user\"\"\"\n        pass\n\n\nclass InMemoryProvider(MemoryProvider):\n    \"\"\"Simple in-memory storage provider\"\"\"\n\n    def __init__(self):\n        self._storage: Dict[str, List[Dict[str, Any]]] = {}\n\n    async def store(self, user_id: str, data: Dict[str, Any]) -> None:\n        if user_id not in self._storage:\n            self._storage[user_id] = []\n        self._storage[user_id].append(data)\n\n    async def retrieve(self, user_id: str, query: Optional[str] = None) -> List[Dict[str, Any]]:\n        return self._storage.get(user_id, [])\n\n    async def clear(self, user_id: str) -> None:\n        if user_id in self._storage:\n            del self._storage[user_id]\n\n\nclass Memory:\n    \"\"\"Memory interface for storing and retrieving conversation history and context\"\"\"\n\n    def __init__(self, provider: Union[str, MemoryProvider], user_id: str, **kwargs):\n        self.user_id = user_id\n\n        if isinstance(provider, str):\n            if provider == \"inmemory\":\n                self.provider = InMemoryProvider()\n            else:\n                raise ValueError(f\"Unknown memory provider: {provider}\")\n        else:\n            self.provider = provider\n\n    async def add(self, message: str, metadata: Optional[Dict[str, Any]] = None) -> None:\n        \"\"\"Add a message to memory\"\"\"\n        data = {\"message\": message, \"metadata\": metadata or {}}\n        await self.provider.store(self.user_id, data)\n\n    async def get(self, query: Optional[str] = None) -> List[Dict[str, Any]]:\n        \"\"\"Get messages from memory\"\"\"\n        return await self.provider.retrieve(self.user_id, query)\n\n    async def clear(self) -> None:\n        \"\"\"Clear all memory for this user\"\"\"\n        await self.provider.clear(self.user_id)\n\n\nclass AgentRunner(ABC):\n    \"\"\"Abstract base class for agent runners\"\"\"\n\n    @abstractmethod\n    async def run(self, query: str, **kwargs) -> Dict[str, Any]:\n        \"\"\"Execute the agent with the given query\"\"\"\n        pass\n\n\nclass SimpleRunner(AgentRunner):\n    \"\"\"Simple runner with OpenAI integration and fallback responses\"\"\"\n\n    def __init__(self, **config):\n        self.config = AIConfig(**config)\n        self._client = None\n\n    def _get_client(self):\n        \"\"\"Get OpenAI client if API key is available\"\"\"\n        if self._client is None and self.config.get(\"openai_api_key\"):\n            try:\n                import openai\n\n                self._client = openai.OpenAI(api_key=self.config.get(\"openai_api_key\"))\n            except ImportError:\n                raise ImportError(\"openai package not installed. Install with: pip install openai\")\n        return self._client\n\n    async def run(self, query: str, **kwargs) -> Dict[str, Any]:\n        \"\"\"Execute with OpenAI (requires API key)\"\"\"\n        client = self._get_client()\n\n        if not client:\n            raise ValueError(\"OpenAI API key is required. Set 'openai_api_key' in configuration.\")\n\n        try:\n            # Use OpenAI\n            messages = [{\"role\": \"user\", \"content\": query}]\n\n            # Add conversation history\n            context = kwargs.get(\"context\", {})\n            history = context.get(\"history\", [])\n\n            if history:\n                for item in history[-10:]:\n                    msg = item.get(\"message\", str(item))\n                    if msg.startswith(\"Query: \"):\n                        messages.insert(-1, {\"role\": \"user\", \"content\": msg[7:]})\n                    elif msg.startswith(\"Response: \"):\n                        messages.insert(-1, {\"role\": \"assistant\", \"content\": msg[10:]})\n\n            response = client.chat.completions.create(\n                model=self.config.get(\"model\", \"gpt-4o\"),\n                messages=messages,\n                temperature=self.config.get(\"temperature\", 0.7),\n                max_tokens=self.config.get(\"max_tokens\", 1000),\n            )\n\n            content = response.choices[0].message.content\n            metadata = {\n                \"runner_type\": \"simple_openai\",\n                \"model\": self.config.get(\"model\", \"gpt-4o\"),\n                \"usage\": {\n                    \"prompt_tokens\": response.usage.prompt_tokens,\n                    \"completion_tokens\": response.usage.completion_tokens,\n                    \"total_tokens\": response.usage.total_tokens,\n                },\n            }\n\n            if self.config.get(\"debug\", False):\n                metadata[\"debug_info\"] = {\"config\": self.config.to_dict()}\n\n            return {\"response\": content, \"query\": query, \"metadata\": metadata}\n\n        except Exception as e:\n            logger.error(f\"Error in SimpleRunner: {e}\")\n            raise e\n\n\nclass Agent:\n    \"\"\"AI Agent interface for handling queries with memory and execution\"\"\"\n\n    def __init__(self, runner: Union[str, AgentRunner], memory: Optional[Memory] = None, **kwargs):\n        self.memory = memory\n\n        if isinstance(runner, str):\n            if runner == \"simple\":\n                self.runner = SimpleRunner(**kwargs)\n            else:\n                raise ValueError(f\"Unknown runner type: {runner}\")\n        else:\n            self.runner = runner\n\n    async def run(self, query: str, history: bool = False, **kwargs) -> Dict[str, Any]:\n        \"\"\"Run the agent with the given query\"\"\"\n        context = {}\n\n        if self.memory and history:\n            context[\"history\"] = await self.memory.get()\n\n        # Add context to kwargs\n        if context:\n            kwargs[\"context\"] = context\n\n        # Execute the agent\n        result = await self.runner.run(query, **kwargs)\n\n        # Store the interaction in memory if available\n        if self.memory:\n            await self.memory.add(f\"Query: {query}\")\n            if \"response\" in result:\n                await self.memory.add(f\"Response: {result['response']}\")\n\n        return result\n\n\ndef memory(provider: str = \"inmemory\", user_id: str = \"default\", **kwargs) -> Memory:\n    \"\"\"\n    Create a memory instance with the specified provider\n\n    Args:\n        provider: Memory provider type (\"inmemory\")\n        user_id: User identifier for memory isolation\n        **kwargs: Additional configuration for the provider\n\n    Returns:\n        Memory instance\n    \"\"\"\n    return Memory(provider=provider, user_id=user_id, **kwargs)\n\n\ndef configure(**kwargs) -> AIConfig:\n    \"\"\"\n    Create and configure AI settings\n\n    Args:\n        **kwargs: Configuration options including API keys, model settings, etc.\n\n    Returns:\n        AIConfig instance\n\n    Example:\n        config = configure(\n            openai_api_key=\"your-key\",\n            model=\"gpt-4\",\n            temperature=0.7\n        )\n    \"\"\"\n    return AIConfig(**kwargs)\n\n\ndef agent(runner: str = \"simple\", memory: Optional[Memory] = None, config: Optional[AIConfig] = None, **kwargs) -> Agent:\n    \"\"\"\n    Create an agent instance with the specified runner\n\n    Args:\n        runner: Agent runner type (\"simple\")\n        memory: Optional memory instance for context\n        config: Optional AIConfig instance for configuration\n        **kwargs: Additional configuration for the runner\n\n    Returns:\n        Agent instance\n\n    Example:\n        # Simple usage\n        chat = agent()\n\n        # With configuration\n        config = configure(openai_api_key=\"your-key\")\n        chat = agent(runner=\"simple\", config=config)\n\n        # With memory\n        mem = memory(provider=\"inmemory\", user_id=\"user123\")\n        chat = agent(runner=\"simple\", memory=mem)\n    \"\"\"\n    # Merge config if provided\n    if config:\n        kwargs.update(config.to_dict())\n\n    return Agent(runner=runner, memory=memory, **kwargs)\n"
  },
  {
    "path": "robyn/argument_parser.py",
    "content": "import argparse\nimport os\n\n\nclass Config:\n    def __init__(self) -> None:\n        parser = argparse.ArgumentParser(description=\"Robyn, a fast async web framework with a rust runtime.\")\n        self.parser = parser\n        parser.add_argument(\n            \"--processes\",\n            type=int,\n            default=None,\n            required=False,\n            help=\"Choose the number of processes. [Default: 1]\",\n        )\n        parser.add_argument(\n            \"--workers\",\n            type=int,\n            default=None,\n            required=False,\n            help=\"Choose the number of workers. [Default: 1]\",\n        )\n        parser.add_argument(\n            \"--dev\",\n            dest=\"dev\",\n            action=\"store_true\",\n            default=None,\n            help=\"Development mode. It restarts the server based on file changes.\",\n        )\n        parser.add_argument(\n            \"--log-level\",\n            dest=\"log_level\",\n            default=None,\n            help=\"Set the log level name\",\n        )\n        parser.add_argument(\n            \"--create\",\n            action=\"store_true\",\n            default=False,\n            help=\"Create a new project template.\",\n        )\n        parser.add_argument(\n            \"--docs\",\n            action=\"store_true\",\n            default=False,\n            help=\"Open the Robyn documentation.\",\n        )\n        parser.add_argument(\n            \"--open-browser\",\n            action=\"store_true\",\n            default=False,\n            help=\"Open the browser on successful start.\",\n        )\n        parser.add_argument(\n            \"--version\",\n            action=\"store_true\",\n            default=False,\n            help=\"Show the Robyn version.\",\n        )\n        parser.add_argument(\n            \"--compile-rust-path\",\n            dest=\"compile_rust_path\",\n            default=None,\n            help=\"Compile rust files in the given path.\",\n        )\n\n        parser.add_argument(\n            \"--create-rust-file\",\n            dest=\"create_rust_file\",\n            default=None,\n            help=\"Create a rust file with the given name.\",\n        )\n        parser.add_argument(\n            \"--disable-openapi\",\n            dest=\"disable_openapi\",\n            action=\"store_true\",\n            default=False,\n            help=\"Disable the OpenAPI documentation.\",\n        )\n        parser.add_argument(\n            \"--fast\",\n            dest=\"fast\",\n            action=\"store_true\",\n            default=False,\n            help=\"Fast mode. It sets the optimal values for processes, workers and log level. However, you can override them.\",\n        )\n\n        args, unknown_args = parser.parse_known_args()\n        self.fast = args.fast\n        self.dev = args.dev\n        self.processes = args.processes\n        self.workers = args.workers\n        self.create = args.create\n        self.docs = args.docs\n        self.open_browser = args.open_browser\n        self.version = args.version\n        self.compile_rust_path = args.compile_rust_path\n        self.create_rust_file = args.create_rust_file\n        self.file_path = None\n        self.disable_openapi = args.disable_openapi\n        self.log_level = args.log_level\n\n        if self.fast:\n            # doing this here before every other check\n            # so that processes, workers and log_level can be overridden\n            cpu_count: int = os.cpu_count() or 1\n            self.processes = self.processes or ((cpu_count * 2) + 1) or 1\n            self.workers = self.workers or 2\n            self.log_level = self.log_level or \"WARNING\"\n\n        self.processes = self.processes or 1\n        self.workers = self.workers or 1\n\n        # find something that ends with .py in unknown_args\n        for arg in unknown_args:\n            if arg.endswith(\".py\"):\n                self.file_path = arg\n                break\n\n        if self.fast and self.dev:\n            raise ValueError(\"--fast and --dev shouldn't be used together\")\n\n        if self.dev and (self.processes != 1 or self.workers != 1):\n            raise ValueError(\"--processes and --workers shouldn't be used with --dev\")\n\n        if self.dev and self.log_level is None:\n            self.log_level = \"DEBUG\"\n        elif self.log_level is None:\n            self.log_level = \"INFO\"\n"
  },
  {
    "path": "robyn/authentication.py",
    "content": "from abc import ABC, abstractmethod\nfrom typing import Optional\n\nfrom robyn.robyn import Headers, Identity, Request, Response\nfrom robyn.status_codes import HTTP_401_UNAUTHORIZED\n\n\nclass AuthenticationNotConfiguredError(Exception):\n    \"\"\"\n    This exception is raised when the authentication is not configured.\n    \"\"\"\n\n    def __str__(self):\n        return \"Authentication is not configured. Use app.configure_authentication() to configure it.\"\n\n\nclass TokenGetter(ABC):\n    @property\n    def scheme(self) -> str:\n        \"\"\"\n        Gets the scheme of the token.\n        :return: The scheme of the token.\n        \"\"\"\n        return self.__class__.__name__\n\n    @classmethod\n    @abstractmethod\n    def get_token(cls, request: Request) -> Optional[str]:\n        \"\"\"\n        Gets the token from the request.\n        This method should not decode the token. Decoding is the role of the authentication handler.\n        :param request: The request object.\n        :return: The encoded token.\n        \"\"\"\n        raise NotImplementedError()\n\n    @classmethod\n    @abstractmethod\n    def set_token(cls, request: Request, token: str):\n        \"\"\"\n        Sets the token in the request.\n        This method should not encode the token. Encoding is the role of the authentication handler.\n        :param request: The request object.\n        :param token: The encoded token.\n        \"\"\"\n        raise NotImplementedError()\n\n\nclass AuthenticationHandler(ABC):\n    def __init__(self, token_getter: TokenGetter):\n        \"\"\"\n        Creates a new instance of the AuthenticationHandler class.\n        This class is an abstract class used to authenticate a user.\n        :param token_getter: The token getter used to get the token from the request.\n        \"\"\"\n        self.token_getter = token_getter\n\n    @property\n    def unauthorized_response(self) -> Response:\n        return Response(\n            headers=Headers({\"WWW-Authenticate\": self.token_getter.scheme}),\n            description=\"Unauthorized\",\n            status_code=HTTP_401_UNAUTHORIZED,\n        )\n\n    @abstractmethod\n    def authenticate(self, request: Request) -> Optional[Identity]:\n        \"\"\"\n        Authenticates the user.\n        :param request: The request object.\n        :return: The identity of the user.\n        \"\"\"\n        raise NotImplementedError()\n\n\nclass BearerGetter(TokenGetter):\n    \"\"\"\n    This class is used to get the token from the Authorization header.\n    The scheme of the header must be Bearer.\n    \"\"\"\n\n    @classmethod\n    def get_token(cls, request: Request) -> Optional[str]:\n        if request.headers.contains(\"authorization\"):\n            authorization_header = request.headers.get(\"authorization\")\n        else:\n            authorization_header = None\n\n        if not authorization_header or not authorization_header.startswith(\"Bearer \"):\n            return None\n\n        return authorization_header[7:]  # Remove the \"Bearer \" prefix\n\n    @classmethod\n    def set_token(cls, request: Request, token: str):\n        request.headers[\"Authorization\"] = f\"Bearer {token}\"\n"
  },
  {
    "path": "robyn/cli.py",
    "content": "import os\nimport shutil\nimport subprocess\nimport sys\nimport webbrowser\nfrom pathlib import Path\nfrom typing import Optional\n\nfrom InquirerPy.base.control import Choice\nfrom InquirerPy.resolver import prompt\n\nfrom robyn.env_populator import load_vars\nfrom robyn.robyn import get_version\n\nfrom .argument_parser import Config\nfrom .reloader import create_rust_file, setup_reloader\n\nSCAFFOLD_DIR = Path(__file__).parent / \"scaffold\"\nCURRENT_WORKING_DIR = Path.cwd()\n\n\ndef create_robyn_app():\n    questions = [\n        {\n            \"type\": \"input\",\n            \"message\": \"Directory Path:\",\n            \"name\": \"directory\",\n        },\n        {\n            \"type\": \"list\",\n            \"message\": \"Need Docker? (Y/N)\",\n            \"choices\": [\n                Choice(\"Y\", name=\"Y\"),\n                Choice(\"N\", name=\"N\"),\n            ],\n            \"default\": Choice(\"N\", name=\"N\"),\n            \"name\": \"docker\",\n        },\n        {\n            \"type\": \"list\",\n            \"message\": \"Please select project type (Mongo/Postgres/Sqlalchemy/Prisma): \",\n            \"choices\": [\n                Choice(\"no-db\", name=\"No DB\"),\n                Choice(\"sqlite\", name=\"Sqlite\"),\n                Choice(\"postgres\", name=\"Postgres\"),\n                Choice(\"mongo\", name=\"MongoDB\"),\n                Choice(\"sqlalchemy\", name=\"SqlAlchemy\"),\n                Choice(\"prisma\", name=\"Prisma\"),\n                Choice(\"sqlmodel\", name=\"SQLModel\"),\n            ],\n            \"default\": Choice(\"no-db\", name=\"No DB\"),\n            \"name\": \"project_type\",\n        },\n    ]\n    result = prompt(questions=questions)\n    project_dir_path = Path(str(result[\"directory\"])).resolve()\n    docker = result[\"docker\"]\n    project_type = str(result[\"project_type\"])\n\n    final_project_dir_path = (CURRENT_WORKING_DIR / project_dir_path).resolve()\n\n    print(f\"Creating a new Robyn project '{final_project_dir_path}'...\")\n\n    # Create a new directory for the project\n    os.makedirs(final_project_dir_path, exist_ok=True)\n\n    selected_project_template = (SCAFFOLD_DIR / Path(project_type)).resolve()\n    shutil.copytree(str(selected_project_template), str(final_project_dir_path), dirs_exist_ok=True)\n\n    # If docker is not needed, delete the docker file\n    if docker == \"N\":\n        os.remove(f\"{final_project_dir_path}/Dockerfile\")\n\n    print(f\"New Robyn project created in '{final_project_dir_path}' \")\n\n\ndef docs():\n    print(\"Opening Robyn documentation... | Offline docs coming soon!\")\n    webbrowser.open(\"https://robyn.tech\")\n\n\ndef start_dev_server(config: Config, file_path: Optional[str] = None):\n    if file_path is None:\n        return\n\n    absolute_file_path = (Path.cwd() / file_path).resolve()\n    directory_path = absolute_file_path.parent\n\n    if config.dev and not os.environ.get(\"IS_RELOADER_RUNNING\", False):\n        setup_reloader(str(directory_path), str(absolute_file_path))\n        return\n\n\ndef start_app_normally(config: Config):\n    command = [sys.executable]\n\n    for arg in sys.argv[1:]:\n        command.append(arg)\n\n    # Run the subprocess\n    subprocess.run(command, start_new_session=False)\n\n\ndef run():\n    config = Config()\n\n    if not config.file_path:\n        config.file_path = f\"{os.getcwd()}/{__name__}\"\n\n    load_vars(project_root=os.path.dirname(os.path.abspath(config.file_path)))\n    os.environ[\"ROBYN_CLI\"] = \"True\"\n\n    if config.dev is None:\n        config.dev = os.getenv(\"ROBYN_DEV_MODE\", False) == \"True\"\n\n    if config.create:\n        create_robyn_app()\n\n    elif file_name := config.create_rust_file:\n        create_rust_file(file_name)\n\n    elif config.version:\n        print(get_version())\n\n    elif config.docs:\n        docs()\n\n    elif config.dev:\n        print(\"Starting dev server...\")\n        start_dev_server(config, config.file_path)\n    else:\n        try:\n            start_app_normally(config)\n        except KeyboardInterrupt:\n            # for the crash happening upon pressing Ctrl + C\n            pass\n"
  },
  {
    "path": "robyn/dependency_injection.py",
    "content": "\"\"\"This is Robyn's dependency injection file.\"\"\"\n\nfrom typing import Any\n\n\nclass DependencyMap:\n    def __init__(self) -> None:\n        self.global_dependency_map: dict[str, Any] = {}\n        # {'router': {'dependency_name': dependency_class}\n        self.router_dependency_map: dict[str, dict[str, Any]] = {}\n\n    def add_router_dependency(self, router, **kwargs):\n        \"\"\"Adds a dependency to a route.\n\n        Args:\n            router App Object: The route to add the dependency to.\n            kwargs (dict): The dependencies to add to the route.\n        \"\"\"\n        if router not in self.router_dependency_map:\n            self.router_dependency_map[router] = {}\n\n        self.router_dependency_map[router].update(**kwargs)\n\n    def add_global_dependency(self, **kwargs):\n        \"\"\"Adds a dependency to all routes.\n\n        Args:\n            kwargs (dict): The dependencies to add to all routes.\n        \"\"\"\n        for name, element in kwargs.items():\n            self.global_dependency_map.update({name: element})\n\n    def get_router_dependencies(self, router):\n        \"\"\"Gets the dependencies for a specific route.\n\n        Args:\n            router\n\n        Returns:\n            dict: The dependencies for the specified route.\n        \"\"\"\n        return self.router_dependency_map.get(router, {})\n\n    def get_global_dependencies(self):\n        \"\"\"Gets the dependencies for a route.\n\n        Args:\n            route (str): The route to get the dependencies for.\n        \"\"\"\n        return self.global_dependency_map\n\n    def merge_dependencies(self, target_router):\n        \"\"\"\n        Merge dependencies from this DependencyMap into another router's DependencyMap.\n\n        Args:\n            target_router: The router with which to merge dependencies.\n\n        This method iterates through the dependencies of this DependencyMap and adds any dependencies\n        that are not already present in the target router's DependencyMap.\n        \"\"\"\n        for dep_key in self.get_global_dependencies():\n            if dep_key in target_router.dependencies.get_global_dependencies():\n                continue\n            target_router.dependencies.get_global_dependencies()[dep_key] = self.get_global_dependencies()[dep_key]\n\n    def get_dependency_map(self, router) -> dict:\n        return {\n            \"global_dependencies\": self.get_global_dependencies(),\n            \"router_dependencies\": self.get_router_dependencies(router),\n        }\n"
  },
  {
    "path": "robyn/env_populator.py",
    "content": "import logging\nimport os\nfrom pathlib import Path\n\nlogger = logging.getLogger(__name__)\n\n\n# parse the configuration file returning a list of tuples (key, value) containing the environment variables\ndef parser(config_path=None, project_root=\"\"):\n    \"\"\"Find robyn.env file in root of the project and parse it\"\"\"\n    if config_path is None:\n        config_path = Path(project_root) / \"robyn.env\"\n\n    if config_path.exists():\n        with open(config_path, \"r\") as f:\n            for line in f:\n                if line.startswith(\"#\"):\n                    continue\n                yield line.strip().split(\"=\")\n\n\n# check for the environment variables set in cli and if not set them\ndef load_vars(variables=None, project_root=\"\"):\n    \"\"\"Main function\"\"\"\n\n    if variables is None:\n        variables = parser(project_root=project_root)\n\n    for var in variables:\n        if var[0] in os.environ:\n            logger.info(\" Variable %s already set\", var[0])\n            continue\n        else:\n            os.environ[var[0]] = var[1]\n            logger.info(\" Variable %s set to %s\", var[0], var[1])\n"
  },
  {
    "path": "robyn/events.py",
    "content": "from enum import Enum\n\n\nclass Events(Enum):\n    STARTUP = \"startup\"\n    SHUTDOWN = \"shutdown\"\n"
  },
  {
    "path": "robyn/exceptions.py",
    "content": "import http\n\n\nclass HTTPException(Exception):\n    def __init__(self, status_code: int, detail: str | None = None) -> None:\n        if detail is None:\n            detail = http.HTTPStatus(status_code).phrase\n        self.status_code = status_code\n        self.detail = detail\n\n    def __str__(self) -> str:\n        return f\"{self.status_code}: {self.detail}\"\n\n    def __repr__(self) -> str:\n        class_name = self.__class__.__name__\n        return f\"{class_name}(status_code={self.status_code}, detail={self.detail})\"\n\n\nclass WebSocketException(Exception):\n    def __init__(self, code: int, reason: str | None = None) -> None:\n        self.code = code\n        self.reason = reason or \"\"\n\n    def __str__(self) -> str:\n        return f\"{self.code}: {self.reason}\"\n\n    def __repr__(self) -> str:\n        class_name = self.__class__.__name__\n        return f\"{class_name}(code={self.code}, reason={self.reason})\"\n\n\n__all__ = [\"HTTPException\", \"WebSocketException\"]\n"
  },
  {
    "path": "robyn/jsonify.py",
    "content": "from typing import Any, Dict, List, Union\n\nimport orjson\n\n\ndef jsonify(data: Union[Dict[str, Any], List[Any]]) -> str:\n    \"\"\"\n    This function serializes input data to a json string\n\n    Attributes:\n        data: dict or list to serialize as JSON response\n    \"\"\"\n    output_binary = orjson.dumps(data)\n    output_str = output_binary.decode(\"utf-8\")\n    return output_str\n"
  },
  {
    "path": "robyn/logger.py",
    "content": "import logging\nfrom enum import Enum\nfrom typing import Optional\n\n\nclass Colors(Enum):\n    BLUE = \"\\033[94m\"\n    CYAN = \"\\033[96m\"\n    GREEN = \"\\033[92m\"\n    YELLOW = \"\\033[93m\"\n    RED = \"\\033[91m\"\n\n\nclass Logger:\n    HEADER = \"\\033[95m\"\n    ENDC = \"\\033[0m\"\n    BOLD = \"\\033[1m\"\n    UNDERLINE = \"\\033[4m\"\n\n    def __init__(self):\n        self.logger = logging.getLogger(__name__)\n\n    def _format_msg(\n        self,\n        msg: str,\n        color: Optional[Colors],\n        bold: bool,\n        underline: bool,\n    ):\n        result = msg\n        if color is not None:\n            result = f\"{color.value}{result}{Logger.ENDC}\"\n        if bold:\n            result = f\"{Logger.BOLD}{result}\"\n        if underline:\n            result = f\"{Logger.UNDERLINE}{result}\"\n        return result\n\n    def error(\n        self,\n        msg: str,\n        *args,\n        color: Optional[Colors] = Colors.RED,\n        bold: bool = False,\n        underline: bool = False,\n    ):\n        self.logger.error(self._format_msg(msg, color, bold, underline), *args)\n\n    def warn(\n        self,\n        msg: str,\n        *args,\n        color: Optional[Colors] = Colors.YELLOW,\n        bold: bool = False,\n        underline: bool = False,\n    ):\n        self.logger.warning(self._format_msg(msg, color, bold, underline), *args)\n\n    def info(\n        self,\n        msg: str,\n        *args,\n        color: Optional[Colors] = Colors.GREEN,\n        bold: bool = False,\n        underline: bool = False,\n    ):\n        self.logger.info(self._format_msg(msg, color, bold, underline), *args)\n\n    def debug(\n        self,\n        msg: str,\n        *args,\n        color: Colors = Colors.BLUE,\n        bold: bool = False,\n        underline: bool = False,\n    ):\n        self.logger.debug(self._format_msg(msg, color, bold, underline), *args)\n\n\nlogger = Logger()\n"
  },
  {
    "path": "robyn/mcp.py",
    "content": "\"\"\"\nModel Context Protocol (MCP) implementation for Robyn.\n\nThis module provides MCP server functionality following the JSON-RPC 2.0 specification.\nIt allows Robyn applications to serve as MCP servers, exposing resources, tools, and prompts\nto MCP clients like Claude Desktop or other AI applications.\n\"\"\"\n\nimport inspect\nimport json\nimport logging\nimport re\nfrom dataclasses import asdict, dataclass\nfrom typing import Any, Callable, Dict, List, Optional, Tuple, Union\n\nlogger = logging.getLogger(__name__)\n\n\ndef _extract_uri_params(uri: str) -> List[str]:\n    \"\"\"Extract parameter names from URI template like 'echo://{message}'\"\"\"\n    return re.findall(r\"\\{(\\w+)\\}\", uri)\n\n\ndef _generate_schema_from_function(func: Callable) -> Dict[str, Any]:\n    \"\"\"Generate JSON schema from function signature\"\"\"\n    sig = inspect.signature(func)\n    properties = {}\n    required = []\n\n    for param_name, param in sig.parameters.items():\n        # Skip 'self' parameter\n        if param_name == \"self\":\n            continue\n\n        param_schema = {\"type\": \"string\"}  # Default to string\n\n        # Try to infer type from annotation\n        if param.annotation != inspect.Parameter.empty:\n            if param.annotation is str:\n                param_schema[\"type\"] = \"string\"\n            elif param.annotation is int:\n                param_schema[\"type\"] = \"integer\"\n            elif param.annotation is float:\n                param_schema[\"type\"] = \"number\"\n            elif param.annotation is bool:\n                param_schema[\"type\"] = \"boolean\"\n            elif hasattr(param.annotation, \"__origin__\"):\n                # Handle generic types like List, Dict, etc.\n                param_schema[\"type\"] = \"object\"\n\n        properties[param_name] = param_schema\n\n        # Add to required if no default value\n        if param.default == inspect.Parameter.empty:\n            required.append(param_name)\n\n    return {\"type\": \"object\", \"properties\": properties, \"required\": required}\n\n\ndef _generate_prompt_args_from_function(func: Callable) -> List[Dict[str, Any]]:\n    \"\"\"Generate prompt arguments from function signature\"\"\"\n    sig = inspect.signature(func)\n    arguments = []\n\n    for param_name, param in sig.parameters.items():\n        if param_name == \"self\":\n            continue\n\n        arg_def = {\"name\": param_name, \"description\": f\"Parameter {param_name}\", \"required\": param.default == inspect.Parameter.empty}\n        arguments.append(arg_def)\n\n    return arguments\n\n\n@dataclass\nclass MCPResource:\n    \"\"\"Represents an MCP resource\"\"\"\n\n    uri: str\n    name: str\n    description: Optional[str] = None\n    mimeType: Optional[str] = None\n\n\n@dataclass\nclass MCPTool:\n    \"\"\"Represents an MCP tool\"\"\"\n\n    name: str\n    description: str\n    inputSchema: Dict[str, Any]\n\n\n@dataclass\nclass MCPPrompt:\n    \"\"\"Represents an MCP prompt template\"\"\"\n\n    name: str\n    description: str\n    arguments: Optional[List[Dict[str, Any]]] = None\n\n\n@dataclass\nclass MCPMessage:\n    \"\"\"JSON-RPC 2.0 message structure\"\"\"\n\n    jsonrpc: str = \"2.0\"\n    id: Optional[Union[str, int]] = None\n    method: Optional[str] = None\n    params: Optional[Dict[str, Any]] = None\n    result: Optional[Any] = None\n    error: Optional[Dict[str, Any]] = None\n\n\nclass MCPError(Exception):\n    \"\"\"MCP-specific error\"\"\"\n\n    def __init__(self, code: int, message: str, data: Optional[Any] = None):\n        self.code = code\n        self.message = message\n        self.data = data\n        super().__init__(message)\n\n\nclass MCPHandler:\n    \"\"\"Handles MCP protocol requests and responses\"\"\"\n\n    def __init__(self):\n        self.resources: Dict[str, Callable] = {}\n        self.tools: Dict[str, Callable] = {}\n        self.prompts: Dict[str, Callable] = {}\n        self.resource_metadata: Dict[str, MCPResource] = {}\n        self.tool_metadata: Dict[str, MCPTool] = {}\n        self.prompt_metadata: Dict[str, MCPPrompt] = {}\n        self.server_info = {\"name\": \"robyn-mcp-server\", \"version\": \"1.0.0\"}\n\n    def register_resource(self, uri: str, name: str, handler: Callable, description: Optional[str] = None, mime_type: Optional[str] = None):\n        \"\"\"Register a resource handler\"\"\"\n        self.resources[uri] = handler\n        self.resource_metadata[uri] = MCPResource(uri=uri, name=name, description=description, mimeType=mime_type)\n\n    def register_tool(self, name: str, handler: Callable, description: str, input_schema: Dict[str, Any]):\n        \"\"\"Register a tool handler\"\"\"\n        self.tools[name] = handler\n        self.tool_metadata[name] = MCPTool(name=name, description=description, inputSchema=input_schema)\n\n    def register_prompt(self, name: str, handler: Callable, description: str, arguments: Optional[List[Dict[str, Any]]] = None):\n        \"\"\"Register a prompt handler\"\"\"\n        self.prompts[name] = handler\n        self.prompt_metadata[name] = MCPPrompt(name=name, description=description, arguments=arguments)\n\n    async def handle_request(self, request_data: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Handle an MCP JSON-RPC request\"\"\"\n        try:\n            # Parse the request\n            method = request_data.get(\"method\")\n            params = request_data.get(\"params\", {})\n            request_id = request_data.get(\"id\")\n\n            # Handle different MCP methods\n            if method == \"initialize\":\n                result = await self._handle_initialize(params)\n            elif method == \"resources/list\":\n                result = await self._handle_list_resources(params)\n            elif method == \"resources/read\":\n                result = await self._handle_read_resource(params)\n            elif method == \"tools/list\":\n                result = await self._handle_list_tools(params)\n            elif method == \"tools/call\":\n                result = await self._handle_call_tool(params)\n            elif method == \"prompts/list\":\n                result = await self._handle_list_prompts(params)\n            elif method == \"prompts/get\":\n                result = await self._handle_get_prompt(params)\n            else:\n                raise MCPError(-32601, f\"Method not found: {method}\")\n\n            # Return successful response\n            return {\"jsonrpc\": \"2.0\", \"id\": request_id, \"result\": result}\n\n        except MCPError as e:\n            return {\"jsonrpc\": \"2.0\", \"id\": request_data.get(\"id\"), \"error\": {\"code\": e.code, \"message\": e.message, \"data\": e.data}}\n        except Exception as e:\n            logger.exception(\"Error handling MCP request\")\n            return {\"jsonrpc\": \"2.0\", \"id\": request_data.get(\"id\"), \"error\": {\"code\": -32603, \"message\": \"Internal error\", \"data\": str(e)}}\n\n    async def _handle_initialize(self, params: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Handle MCP initialize request\"\"\"\n        return {\n            \"protocolVersion\": \"2024-11-05\",\n            \"capabilities\": {\"resources\": {\"subscribe\": False, \"listChanged\": False}, \"tools\": {}, \"prompts\": {}},\n            \"serverInfo\": self.server_info,\n        }\n\n    async def _handle_list_resources(self, params: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Handle resources/list request\"\"\"\n        resources = []\n        for uri, metadata in self.resource_metadata.items():\n            resources.append(asdict(metadata))\n        return {\"resources\": resources}\n\n    def _match_uri_template(self, requested_uri: str) -> Optional[Tuple[str, Dict[str, str]]]:\n        \"\"\"Match requested URI against registered URI templates\"\"\"\n        for template_uri in self.resources.keys():\n            # Extract parameter names from template\n            param_names = _extract_uri_params(template_uri)\n\n            if not param_names:\n                # Exact match for non-templated URIs\n                if requested_uri == template_uri:\n                    return template_uri, {}\n                continue\n\n            # Create regex pattern from template\n            pattern = template_uri\n            for param_name in param_names:\n                # Use (.+) to match any characters including slashes for paths\n                # Use ([^/]+) for single segments\n                if param_name in [\"path\", \"file_path\", \"directory\"]:\n                    pattern = pattern.replace(f\"{{{param_name}}}\", r\"(.+)\")\n                else:\n                    pattern = pattern.replace(f\"{{{param_name}}}\", r\"([^/]+)\")\n\n            match = re.match(f\"^{pattern}$\", requested_uri)\n            if match:\n                # Extract parameter values\n                param_values = {}\n                for i, param_name in enumerate(param_names):\n                    param_values[param_name] = match.group(i + 1)\n                return template_uri, param_values\n\n        return None\n\n    async def _handle_read_resource(self, params: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Handle resources/read request\"\"\"\n        uri = params.get(\"uri\")\n        if not uri:\n            raise MCPError(-32602, \"URI is required\")\n\n        # Try to match URI template\n        match_result = self._match_uri_template(uri)\n        if not match_result:\n            raise MCPError(-32602, f\"Resource not found: {uri}\")\n\n        template_uri, uri_params = match_result\n        handler = self.resources[template_uri]\n\n        # Call the handler with appropriate parameters based on its signature\n        try:\n            sig = inspect.signature(handler)\n            handler_params = list(sig.parameters.keys())\n\n            if inspect.iscoroutinefunction(handler):\n                if uri_params:\n                    # Use URI parameters for templated resources\n                    content = await handler(**uri_params)\n                elif handler_params:\n                    # Handler expects parameters, pass the full params dict\n                    content = await handler(params)\n                else:\n                    # Handler expects no parameters\n                    content = await handler()\n            else:\n                if uri_params:\n                    # Use URI parameters for templated resources\n                    content = handler(**uri_params)\n                elif handler_params:\n                    # Handler expects parameters, pass the full params dict\n                    content = handler(params)\n                else:\n                    # Handler expects no parameters\n                    content = handler()\n        except TypeError as e:\n            # Handle parameter mismatch errors\n            raise MCPError(-32603, f\"Handler parameter error: {str(e)}\")\n\n        # Determine content type\n        metadata = self.resource_metadata[template_uri]\n        mime_type = metadata.mimeType or \"text/plain\"\n\n        return {\"contents\": [{\"uri\": uri, \"mimeType\": mime_type, \"text\": str(content)}]}\n\n    async def _handle_list_tools(self, params: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Handle tools/list request\"\"\"\n        tools = []\n        for name, metadata in self.tool_metadata.items():\n            tools.append(asdict(metadata))\n        return {\"tools\": tools}\n\n    async def _handle_call_tool(self, params: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Handle tools/call request\"\"\"\n        name = params.get(\"name\")\n        arguments = params.get(\"arguments\", {})\n\n        if not name or name not in self.tools:\n            raise MCPError(-32602, f\"Tool not found: {name}\")\n\n        handler = self.tools[name]\n\n        # Call the tool handler\n        if inspect.iscoroutinefunction(handler):\n            result = await handler(**arguments)\n        else:\n            result = handler(**arguments)\n\n        return {\"content\": [{\"type\": \"text\", \"text\": str(result)}]}\n\n    async def _handle_list_prompts(self, params: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Handle prompts/list request\"\"\"\n        prompts = []\n        for name, metadata in self.prompt_metadata.items():\n            prompts.append(asdict(metadata))\n        return {\"prompts\": prompts}\n\n    async def _handle_get_prompt(self, params: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Handle prompts/get request\"\"\"\n        name = params.get(\"name\")\n        arguments = params.get(\"arguments\", {})\n\n        if not name or name not in self.prompts:\n            raise MCPError(-32602, f\"Prompt not found: {name}\")\n\n        handler = self.prompts[name]\n\n        # Call the prompt handler\n        if inspect.iscoroutinefunction(handler):\n            result = await handler(**arguments)\n        else:\n            result = handler(**arguments)\n\n        # Ensure result is in the expected format\n        if isinstance(result, str):\n            messages = [{\"role\": \"user\", \"content\": {\"type\": \"text\", \"text\": result}}]\n        elif isinstance(result, list):\n            messages = result\n        else:\n            messages = [{\"role\": \"user\", \"content\": {\"type\": \"text\", \"text\": str(result)}}]\n\n        return {\"description\": self.prompt_metadata[name].description, \"messages\": messages}\n\n\nclass MCPApp:\n    \"\"\"MCP application wrapper for Robyn\"\"\"\n\n    def __init__(self, robyn_app):\n        self.app = robyn_app\n        self.handler = MCPHandler()\n        self._setup_routes()\n\n    def _setup_routes(self):\n        \"\"\"Setup MCP routes on the Robyn app\"\"\"\n\n        @self.app.post(\"/mcp\")\n        async def handle_mcp_request(request):\n            \"\"\"Handle MCP JSON-RPC requests\"\"\"\n            try:\n                # Parse JSON request - try multiple approaches\n                try:\n                    request_data = request.json()\n                except (ValueError, TypeError, AttributeError):\n                    # Fallback to parsing body as string\n                    body = request.body\n                    if isinstance(body, str):\n                        request_data = json.loads(body)\n                    else:\n                        request_data = json.loads(body.decode(\"utf-8\"))\n\n                # Handle case where request.json() returns a string instead of dict\n                if isinstance(request_data, str):\n                    request_data = json.loads(request_data)\n\n                # Handle the request\n                response = await self.handler.handle_request(request_data)\n\n                return response\n\n            except json.JSONDecodeError:\n                return {\"jsonrpc\": \"2.0\", \"id\": None, \"error\": {\"code\": -32700, \"message\": \"Parse error\"}}\n            except Exception as e:\n                logger.exception(\"Error in MCP request handler\")\n                return {\"jsonrpc\": \"2.0\", \"id\": None, \"error\": {\"code\": -32603, \"message\": \"Internal error\", \"data\": str(e)}}\n\n    def resource(self, uri: str = None, name: str = None, description: Optional[str] = None, mime_type: Optional[str] = None):\n        \"\"\"\n        Decorator to register an MCP resource\n\n        Args:\n            uri: Resource URI template (e.g., \"echo://{message}\")\n            name: Human-readable name (auto-generated if not provided)\n            description: Resource description (auto-generated if not provided)\n            mime_type: MIME type (defaults to \"text/plain\")\n\n        Example:\n            @app.mcp.resource(\"echo://{message}\")\n            def echo_resource(message: str) -> str:\n                return f\"Resource echo: {message}\"\n        \"\"\"\n\n        def decorator(func: Callable):\n            # Auto-generate metadata if not provided\n            actual_uri = uri or f\"function://{func.__name__}\"\n            actual_name = name or func.__name__.replace(\"_\", \" \").title()\n            actual_description = description or func.__doc__ or f\"Resource: {actual_name}\"\n            actual_mime_type = mime_type or \"text/plain\"\n\n            self.handler.register_resource(actual_uri, actual_name, func, actual_description, actual_mime_type)\n            return func\n\n        return decorator\n\n    def tool(self, name: str = None, description: str = None, input_schema: Dict[str, Any] = None):\n        \"\"\"\n        Decorator to register an MCP tool\n\n        Args:\n            name: Tool name (defaults to function name)\n            description: Tool description (auto-generated if not provided)\n            input_schema: JSON schema for inputs (auto-generated if not provided)\n\n        Example:\n            @app.mcp.tool()\n            def echo_tool(message: str) -> str:\n                return f\"Tool echo: {message}\"\n        \"\"\"\n\n        def decorator(func: Callable):\n            # Auto-generate metadata if not provided\n            actual_name = name or func.__name__\n            actual_description = description or func.__doc__ or f\"Tool: {func.__name__}\"\n            actual_schema = input_schema or _generate_schema_from_function(func)\n\n            self.handler.register_tool(actual_name, func, actual_description, actual_schema)\n            return func\n\n        return decorator\n\n    def prompt(self, name: str = None, description: str = None, arguments: Optional[List[Dict[str, Any]]] = None):\n        \"\"\"\n        Decorator to register an MCP prompt\n\n        Args:\n            name: Prompt name (defaults to function name)\n            description: Prompt description (auto-generated if not provided)\n            arguments: Prompt arguments (auto-generated if not provided)\n\n        Example:\n            @app.mcp.prompt()\n            def echo_prompt(message: str) -> str:\n                return f\"Please process this message: {message}\"\n        \"\"\"\n\n        def decorator(func: Callable):\n            # Auto-generate metadata if not provided\n            actual_name = name or func.__name__\n            actual_description = description or func.__doc__ or f\"Prompt: {func.__name__}\"\n            actual_arguments = arguments or _generate_prompt_args_from_function(func)\n\n            self.handler.register_prompt(actual_name, func, actual_description, actual_arguments)\n            return func\n\n        return decorator\n"
  },
  {
    "path": "robyn/openapi.py",
    "content": "import inspect\nimport json\nimport logging\nimport re\nimport typing\nfrom dataclasses import asdict, dataclass, field\nfrom importlib import resources\nfrom inspect import Signature\nfrom pathlib import Path\nfrom typing import Any, Callable, Dict, List, Optional, Tuple, TypedDict, is_typeddict\n\nfrom robyn.responses import html\nfrom robyn.robyn import QueryParams, Response\nfrom robyn.pydantic_support import get_pydantic_openapi_schema, is_pydantic_model\nfrom robyn.types import Body, JsonBody\n\n_logger = logging.getLogger(__name__)\n\n\nclass str_typed_dict(TypedDict):\n    key: str\n    value: str\n\n\n@dataclass\nclass Contact:\n    \"\"\"\n    The contact information for the exposed API.\n    (https://swagger.io/specification/#contact-object)\n\n    @param name: Optional[str] The identifying name of the contact person/organization.\n    @param url: Optional[str] The URL pointing to the contact information. This MUST be in the form of a URL.\n    @param email: Optional[str] The email address of the contact person/organization. This MUST be in the form of an email address.\n    \"\"\"\n\n    name: Optional[str] = None\n    url: Optional[str] = None\n    email: Optional[str] = None\n\n\n@dataclass\nclass License:\n    \"\"\"\n    The license information for the exposed API.\n    (https://swagger.io/specification/#license-object)\n\n    @param name: Optional[str] The license name used for the API.\n    @param url: Optional[str] A URL to the license used for the API. This MUST be in the form of a URL.\n    \"\"\"\n\n    name: Optional[str] = None\n    url: Optional[str] = None\n\n\n@dataclass\nclass Server:\n    \"\"\"\n    An array of Server Objects, which provide connectivity information to a target server. If the servers property is\n    not provided, or is an empty array, the default value would be a Server Object with a url value of /.\n    (https://swagger.io/specification/#server-object)\n\n    @param url: str A URL to the target host. This URL supports Server Variables and MAY be relative,\n    to indicate that the host location is relative to the location where the OpenAPI document is being served.\n    Variable substitutions will be made when a variable is named in {brackets}.\n    @param description: Optional[str] An optional string describing the host designated by the URL.\n    \"\"\"\n\n    url: str\n    description: Optional[str] = None\n\n\n@dataclass\nclass ExternalDocumentation:\n    \"\"\"\n    Additional external documentation for this operation.\n    (https://swagger.io/specification/#external-documentation-object)\n\n    @param description: Optional[str] A description of the target documentation.\n    @param url: Optional[str] The URL for the target documentation.\n    \"\"\"\n\n    description: Optional[str] = None\n    url: Optional[str] = None\n\n\n@dataclass\nclass Components:\n    \"\"\"\n    Additional external documentation for this operation.\n    (https://swagger.io/specification/#components-object)\n\n    @param schemas: Optional[Dict[str, Dict]] An object to hold reusable Schema Objects.\n    @param responses: Optional[Dict[str, Dict]] An object to hold reusable Response Objects.\n    @param parameters: Optional[Dict[str, Dict]] An object to hold reusable Parameter Objects.\n    @param examples: Optional[Dict[str, Dict]] An object to hold reusable Example Objects.\n    @param requestBodies: Optional[Dict[str, Dict]] An object to hold reusable Request Body Objects.\n    @param securitySchemes: Optional[Dict[str, Dict]] An object to hold reusable Security Scheme Objects.\n    @param links: Optional[Dict[str, Dict]] An object to hold reusable Link Objects.\n    @param callbacks: Optional[Dict[str, Dict]] An object to hold reusable Callback Objects.\n    @param pathItems: Optional[Dict[str, Dict]] An object to hold reusable Callback Objects.\n    \"\"\"\n\n    schemas: Optional[Dict[str, Dict]] = field(default_factory=dict)\n    responses: Optional[Dict[str, Dict]] = field(default_factory=dict)\n    parameters: Optional[Dict[str, Dict]] = field(default_factory=dict)\n    examples: Optional[Dict[str, Dict]] = field(default_factory=dict)\n    requestBodies: Optional[Dict[str, Dict]] = field(default_factory=dict)\n    securitySchemes: Optional[Dict[str, Dict]] = field(default_factory=dict)\n    links: Optional[Dict[str, Dict]] = field(default_factory=dict)\n    callbacks: Optional[Dict[str, Dict]] = field(default_factory=dict)\n    pathItems: Optional[Dict[str, Dict]] = field(default_factory=dict)\n\n\n@dataclass\nclass OpenAPIInfo:\n    \"\"\"\n    Provides metadata about the API. The metadata MAY be used by tooling as required.\n    (https://swagger.io/specification/#info-object)\n\n    @param title: str The title of the API.\n    @param version: str The version of the API.\n    @param description: Optional[str] A description of the API.\n    @param termsOfService: Optional[str] A URL to the Terms of Service for the API.\n    @param contact: Contact The contact information for the exposed API.\n    @param license: License The license information for the exposed API.\n    @param servers: list[Server] An list of Server objects representing the servers.\n    @param externalDocs: Optional[ExternalDocumentation] Additional external documentation.\n    @param components: Components An element to hold various schemas for the document.\n    \"\"\"\n\n    title: str = \"Robyn API\"\n    version: str = \"1.0.0\"\n    description: Optional[str] = None\n    termsOfService: Optional[str] = None\n    contact: Contact = field(default_factory=Contact)\n    license: License = field(default_factory=License)\n    servers: List[Server] = field(default_factory=list)\n    externalDocs: Optional[ExternalDocumentation] = field(default_factory=ExternalDocumentation)\n    components: Components = field(default_factory=Components)\n\n\n@dataclass\nclass OpenAPI:\n    \"\"\"\n    This is the root object of the OpenAPI document.\n\n    @param info: OpenAPIInfo Provides metadata about the API.\n    @param openapi_spec: dict The content of openapi.json as a dict\n    \"\"\"\n\n    info: OpenAPIInfo = field(default_factory=OpenAPIInfo)\n    openapi_spec: dict = field(init=False)\n    openapi_file_override: bool = False  # denotes whether there is an override or not.\n\n    def __post_init__(self):\n        \"\"\"\n        Initializes the openapi_spec dict\n        \"\"\"\n        if self.openapi_file_override:\n            return\n\n        self.openapi_spec = {\n            \"openapi\": \"3.1.0\",\n            \"info\": asdict(self.info),\n            \"paths\": {},\n            \"components\": asdict(self.info.components),\n            \"servers\": [asdict(server) for server in self.info.servers],\n            \"externalDocs\": asdict(self.info.externalDocs) if self.info.externalDocs.url else None,\n        }\n\n    def add_openapi_path_obj(self, route_type: str, endpoint: str, openapi_name: str, openapi_tags: List[str], handler: Callable):\n        \"\"\"\n        Adds the given path to openapi spec\n\n        @param route_type: str the http method as string (get, post ...)\n        @param endpoint: str the endpoint to be added\n        @param openapi_name: str the name of the endpoint\n        @param openapi_tags: List[str] for grouping of endpoints\n        @param handler: Callable the handler function for the endpoint\n        \"\"\"\n\n        if self.openapi_file_override:\n            return\n\n        query_params = None\n        request_body = None\n        return_annotation = None\n\n        signature = inspect.signature(handler)\n        openapi_description = inspect.getdoc(handler) or \"\"\n\n        if signature:\n            parameters = signature.parameters\n\n            if \"query_params\" in parameters:\n                query_params = parameters[\"query_params\"].default\n\n                if query_params is Signature.empty:\n                    query_params = None\n\n            if \"body\" in parameters:\n                request_body = parameters[\"body\"].default\n\n                if request_body is Signature.empty:\n                    request_body = None\n\n            # priority to typing\n            for parameter in parameters:\n                param_annotation = parameters[parameter].annotation\n\n                if inspect.isclass(param_annotation):\n                    if issubclass(param_annotation, JsonBody):\n                        request_body = param_annotation\n                    elif issubclass(param_annotation, Body):\n                        request_body = param_annotation\n                    elif issubclass(param_annotation, QueryParams):\n                        query_params = param_annotation\n                    elif is_pydantic_model(param_annotation):\n                        request_body = param_annotation\n                    elif is_typeddict(param_annotation):\n                        request_body = param_annotation\n\n            if signature.return_annotation is not Signature.empty:\n                return_annotation = signature.return_annotation\n\n        modified_endpoint, path_obj = self.get_path_obj(\n            endpoint, openapi_name, openapi_description, openapi_tags, query_params, request_body, return_annotation\n        )\n\n        if modified_endpoint not in self.openapi_spec[\"paths\"]:\n            self.openapi_spec[\"paths\"][modified_endpoint] = {}\n        self.openapi_spec[\"paths\"][modified_endpoint][route_type] = path_obj\n\n    def _merge_component_schemas(self, incoming: dict):\n        \"\"\"Merge incoming component schemas into the spec with collision detection.\n\n        If an incoming schema name already exists and the schemas differ,\n        a warning is logged.  The incoming schema always wins so that the\n        most-recently-registered route's models are used (matching the\n        behaviour of path registration).\n        \"\"\"\n        existing = self.openapi_spec[\"components\"][\"schemas\"]\n        for name, schema in incoming.items():\n            if name in existing and existing[name] != schema:\n                _logger.warning(\n                    \"OpenAPI component schema '%s' is defined by multiple models with different shapes — the later definition will be used\",\n                    name,\n                )\n            existing[name] = schema\n\n    def add_subrouter_paths(self, subrouter_openapi: \"OpenAPI\"):\n        \"\"\"\n        Adds the subrouter paths and component schemas to main router's openapi specs.\n\n        @param subrouter_openapi: OpenAPI the OpenAPI object of the current subrouter\n        \"\"\"\n\n        if self.openapi_file_override:\n            return\n\n        for path, path_obj in subrouter_openapi.openapi_spec[\"paths\"].items():\n            self.openapi_spec[\"paths\"][path] = path_obj\n\n        subrouter_schemas = subrouter_openapi.openapi_spec.get(\"components\", {}).get(\"schemas\", {})\n        if subrouter_schemas:\n            self._merge_component_schemas(subrouter_schemas)\n\n    def get_path_obj(\n        self,\n        endpoint: str,\n        name: str,\n        description: str,\n        tags: List[str],\n        query_params: Optional[str_typed_dict],\n        request_body: Optional[str_typed_dict],\n        return_annotation: Optional[str_typed_dict],\n    ) -> Tuple[str, dict]:\n        \"\"\"\n        Get the \"path\" openapi object according to spec\n\n        @param endpoint: str the endpoint to be added\n        @param name: str the name of the endpoint\n        @param description: Optional[str] short description of the endpoint (to be fetched from the endpoint definition by default)\n        @param tags: List[str] for grouping of endpoints\n        @param query_params: Optional[TypedDict] query params for the function\n        @param request_body: Optional[TypedDict] request body for the function\n        @param return_annotation: Optional[TypedDict] return type of the endpoint handler\n\n        @return: (str, dict) a tuple containing the endpoint with path params wrapped in braces and the \"path\" openapi object\n        according to spec\n        \"\"\"\n\n        if not description:\n            description = \"No description provided\"\n\n        openapi_path_object: dict = {\n            \"summary\": name,\n            \"description\": description,\n            \"parameters\": [],\n            \"tags\": tags,\n        }\n\n        # robyn has paths like /:url/:etc whereas openapi requires path like /{url}/{path}\n        # this function is used for converting path params to the required form\n        # initialized with endpoint for handling endpoints without path params\n        endpoint_with_path_params_wrapped_in_braces = endpoint\n\n        path_param_names = re.findall(r\":(\\w+)\", endpoint)\n\n        if path_param_names:\n            # Convert param syntax to OpenAPI's {param} syntax\n            # \\w+ matches word characters (letters, digits, underscores) and does not match '/',\n            # so each :param is captured individually without swallowing intervening path segments.\n            endpoint_with_path_params_wrapped_in_braces = re.sub(r\":(\\w+)\", r\"{\\1}\", endpoint)\n\n            for name in path_param_names:\n                openapi_path_object[\"parameters\"].append(\n                    {\n                        \"name\": name,\n                        \"in\": \"path\",\n                        \"required\": True,\n                        \"schema\": {\"type\": \"string\"},\n                    }\n                )\n\n        if query_params:\n            query_param_annotations = query_params.__annotations__ if query_params is str_typed_dict else typing.get_type_hints(query_params)\n\n            for query_param in query_param_annotations:\n                query_param_type = self.get_openapi_type(query_param_annotations[query_param])\n\n                openapi_path_object[\"parameters\"].append(\n                    {\n                        \"name\": query_param,\n                        \"in\": \"query\",\n                        \"required\": False,\n                        \"schema\": {\"type\": query_param_type},\n                    }\n                )\n\n        if request_body:\n            if is_pydantic_model(request_body):\n                schema, component_schemas = get_pydantic_openapi_schema(request_body)\n                if component_schemas:\n                    self._merge_component_schemas(component_schemas)\n                request_body_object = {\"content\": {\"application/json\": {\"schema\": schema}}}\n            else:\n                properties = {}\n                request_body_annotations = request_body.__annotations__ if request_body is TypedDict else typing.get_type_hints(request_body)\n                for body_item in request_body_annotations:\n                    properties[body_item] = self.get_schema_object(body_item, request_body_annotations[body_item])\n                request_body_object = {\n                    \"content\": {\n                        \"application/json\": {\n                            \"schema\": {\n                                \"type\": \"object\",\n                                \"properties\": properties,\n                            }\n                        }\n                    }\n                }\n\n            openapi_path_object[\"requestBody\"] = request_body_object\n\n        response_schema: dict = {}\n        response_type = \"text/plain\"\n\n        if return_annotation and return_annotation is not Response:\n            response_type = \"application/json\"\n            response_schema = self.get_schema_object(\"response object\", return_annotation)\n\n        openapi_path_object[\"responses\"] = {\"200\": {\"description\": \"Successful Response\", \"content\": {response_type: {\"schema\": response_schema}}}}\n\n        return endpoint_with_path_params_wrapped_in_braces, openapi_path_object\n\n    def get_openapi_type(self, typed_dict: str_typed_dict) -> str:\n        \"\"\"\n        Get actual type from the TypedDict annotations\n\n        @param typed_dict: TypedDict The TypedDict to be converted\n        @return: str the type inferred\n        \"\"\"\n        type_mapping = {\n            int: \"integer\",\n            str: \"string\",\n            bool: \"boolean\",\n            float: \"number\",\n            dict: \"object\",\n            list: \"array\",\n        }\n\n        for type_name in type_mapping:\n            if typed_dict is type_name:\n                return type_mapping[type_name]\n\n        # default to \"string\" if type is not found\n        return \"string\"\n\n    def get_schema_object(self, parameter: str, param_type: Any) -> dict:\n        \"\"\"\n        Get the schema object for request/response body\n\n        @param parameter: name of the parameter\n        @param param_type: Any the type to be inferred\n        @return: dict the properties object\n        \"\"\"\n\n        properties: dict = {\n            \"title\": parameter.capitalize(),\n        }\n\n        type_mapping: dict = {\n            int: \"integer\",\n            str: \"string\",\n            bool: \"boolean\",\n            float: \"number\",\n            dict: \"object\",\n            list: \"array\",\n        }\n\n        for type_name in type_mapping:\n            if param_type is type_name:\n                properties[\"type\"] = type_mapping[type_name]\n                return properties\n\n        # Check if it's a generic type (like List[Object])\n        if hasattr(param_type, \"__origin__\"):\n            if param_type.__origin__ is list or param_type.__origin__ is List:\n                properties[\"type\"] = \"array\"\n                # Handle the element type in the list\n                if hasattr(param_type, \"__args__\") and param_type.__args__:\n                    item_type = param_type.__args__[0]\n                    properties[\"items\"] = self.get_schema_object(f\"{parameter}_item\", item_type)\n                return properties\n\n        # check for Pydantic models\n        if is_pydantic_model(param_type):\n            schema, component_schemas = get_pydantic_openapi_schema(param_type)\n            if component_schemas:\n                self._merge_component_schemas(component_schemas)\n            return schema\n\n        # check for Optional type\n        if param_type.__module__ == \"typing\":\n            properties[\"anyOf\"] = [{\"type\": self.get_openapi_type(param_type.__args__[0])}, {\"type\": \"null\"}]\n            return properties\n        # check for custom classes and TypedDicts\n        elif inspect.isclass(param_type):\n            properties[\"type\"] = \"object\"\n\n            properties[\"properties\"] = {}\n\n            for e in param_type.__annotations__:\n                properties[\"properties\"][e] = self.get_schema_object(e, param_type.__annotations__[e])\n\n        properties[\"type\"] = \"object\"\n\n        return properties\n\n    def override_openapi(self, openapi_json_spec_path: Path):\n        \"\"\"\n        Set a pre-configured OpenAPI spec\n        @param openapi_json_spec_path: str the path to the json file\n        \"\"\"\n        with open(openapi_json_spec_path) as json_file:\n            json_file_content = json.load(json_file)\n            self.openapi_spec = dict(json_file_content)\n            self.openapi_file_override = True\n\n    def get_openapi_docs_page(self) -> Response:\n        \"\"\"\n        Handler to the swagger html page to be deployed to the endpoint `/docs`\n        @return: FileResponse the swagger html page\n        \"\"\"\n        with resources.open_text(\"robyn\", \"swagger.html\") as path:\n            html_file = path.read()\n        return html(html_file)\n\n    def get_openapi_config(self) -> dict:\n        \"\"\"\n        Returns the openapi spec as a dict\n        @return: dict the openapi spec\n        \"\"\"\n        return self.openapi_spec\n"
  },
  {
    "path": "robyn/processpool.py",
    "content": "import asyncio\nimport signal\nimport sys\nimport webbrowser\nfrom typing import Dict, List, Optional\n\nfrom multiprocess import Process  # type: ignore\n\nfrom robyn.events import Events\nfrom robyn.logger import logger\nfrom robyn.robyn import FunctionInfo, Headers, Server, SocketHeld\nfrom robyn.router import GlobalMiddleware, Route, RouteMiddleware\nfrom robyn.types import Directory\n\n\ndef run_processes(\n    url: str,\n    port: int,\n    directories: List[Directory],\n    request_headers: Headers,\n    routes: List[Route],\n    global_middlewares: List[GlobalMiddleware],\n    route_middlewares: List[RouteMiddleware],\n    web_sockets: Dict[str, dict],\n    event_handlers: Dict[Events, FunctionInfo],\n    workers: int,\n    processes: int,\n    response_headers: Headers,\n    excluded_response_headers_paths: Optional[List[str]],\n    open_browser: bool,\n    client_timeout: int = 30,\n    keep_alive_timeout: int = 20,\n) -> List[Process]:\n    socket = SocketHeld(url, port)\n\n    process_pool = init_processpool(\n        directories,\n        request_headers,\n        routes,\n        global_middlewares,\n        route_middlewares,\n        web_sockets,\n        event_handlers,\n        socket,\n        workers,\n        processes,\n        response_headers,\n        excluded_response_headers_paths,\n        client_timeout,\n        keep_alive_timeout,\n    )\n\n    def terminating_signal_handler(_sig, _frame):\n        logger.info(\"Terminating server!!\", bold=True)\n        for process in process_pool:\n            process.kill()\n\n    signal.signal(signal.SIGINT, terminating_signal_handler)\n    signal.signal(signal.SIGTERM, terminating_signal_handler)\n\n    if open_browser:\n        logger.info(\"Opening browser...\")\n        webbrowser.open_new_tab(f\"http://{url}:{port}/\")\n\n    logger.info(\"Press Ctrl + C to stop \\n\")\n    for process in process_pool:\n        process.join()\n\n    return process_pool\n\n\ndef init_processpool(\n    directories: List[Directory],\n    request_headers: Headers,\n    routes: List[Route],\n    global_middlewares: List[GlobalMiddleware],\n    route_middlewares: List[RouteMiddleware],\n    web_sockets: Dict[str, dict],\n    event_handlers: Dict[Events, FunctionInfo],\n    socket: SocketHeld,\n    workers: int,\n    processes: int,\n    response_headers: Headers,\n    excluded_response_headers_paths: Optional[List[str]],\n    client_timeout: int = 30,\n    keep_alive_timeout: int = 20,\n) -> List[Process]:\n    process_pool: List = []\n    if sys.platform.startswith(\"win32\") or processes == 1:\n        spawn_process(\n            directories,\n            request_headers,\n            routes,\n            global_middlewares,\n            route_middlewares,\n            web_sockets,\n            event_handlers,\n            socket,\n            workers,\n            response_headers,\n            excluded_response_headers_paths,\n            client_timeout,\n            keep_alive_timeout,\n        )\n\n        return process_pool\n\n    for _ in range(processes):\n        copied_socket = socket.try_clone()\n        process = Process(\n            target=spawn_process,\n            args=(\n                directories,\n                request_headers,\n                routes,\n                global_middlewares,\n                route_middlewares,\n                web_sockets,\n                event_handlers,\n                copied_socket,\n                workers,\n                response_headers,\n                excluded_response_headers_paths,\n                client_timeout,\n                keep_alive_timeout,\n            ),\n        )\n        process.start()\n        process_pool.append(process)\n\n    return process_pool\n\n\ndef initialize_event_loop():\n    if sys.platform.startswith(\"win32\") or sys.platform.startswith(\"linux-cross\"):\n        loop = asyncio.new_event_loop()\n        asyncio.set_event_loop(loop)\n        return loop\n    else:\n        # uv loop doesn't support windows or arm machines at the moment\n        # but uv loop is much faster than native asyncio\n        import uvloop\n\n        uvloop.install()\n        loop = uvloop.new_event_loop()\n        asyncio.set_event_loop(loop)\n        return loop\n\n\ndef spawn_process(\n    directories: List[Directory],\n    request_headers: Headers,\n    routes: List[Route],\n    global_middlewares: List[GlobalMiddleware],\n    route_middlewares: List[RouteMiddleware],\n    web_sockets: Dict[str, dict],\n    event_handlers: Dict[Events, FunctionInfo],\n    socket: SocketHeld,\n    workers: int,\n    response_headers: Headers,\n    excluded_response_headers_paths: Optional[List[str]],\n    client_timeout: int = 30,\n    keep_alive_timeout: int = 20,\n):\n    \"\"\"\n    This function is called by the main process handler to create a server runtime.\n    This functions allows one runtime per process.\n\n    :param directories List: the list of all the directories and related data\n    :param headers tuple: All the global headers in a tuple\n    :param routes Tuple[Route]: The routes tuple, containing the description about every route.\n    :param middlewares Tuple[Route]: The middleware routes tuple, containing the description about every route.\n    :param web_sockets list: This is a list of all the web socket routes\n    :param event_handlers Dict: This is an event dict that contains the event handlers\n    :param socket SocketHeld: This is the main tcp socket, which is being shared across multiple processes.\n    :param process_name string: This is the name given to the process to identify the process\n    :param workers int: This is the name given to the process to identify the process\n    \"\"\"\n\n    loop = initialize_event_loop()\n\n    server = Server()\n\n    # TODO: if we remove the dot access\n    # the startup time will improve in the server\n    for directory in directories:\n        server.add_directory(*directory.as_list())\n\n    server.apply_request_headers(request_headers)\n\n    server.apply_response_headers(response_headers)\n\n    server.set_response_headers_exclude_paths(excluded_response_headers_paths)\n\n    for route in routes:\n        route_type, endpoint, function, is_const, auth_required, openapi_name, openapi_tags = route\n        server.add_route(route_type, endpoint, function, is_const)\n\n    for middleware_type, middleware_function in global_middlewares:\n        server.add_global_middleware(middleware_type, middleware_function)\n\n    for middleware_type, endpoint, function, route_type in route_middlewares:\n        server.add_middleware_route(middleware_type, endpoint, function, route_type)\n\n    if Events.STARTUP in event_handlers:\n        server.add_startup_handler(event_handlers[Events.STARTUP])\n\n    if Events.SHUTDOWN in event_handlers:\n        server.add_shutdown_handler(event_handlers[Events.SHUTDOWN])\n\n    for endpoint in web_sockets:\n        web_socket = web_sockets[endpoint]\n        server.add_web_socket_route(\n            endpoint,\n            web_socket[\"connect\"],\n            web_socket[\"close\"],\n            web_socket[\"message\"],\n        )\n\n    try:\n        server.start(socket, workers)\n        loop = asyncio.get_event_loop()\n        loop.run_forever()\n    except KeyboardInterrupt:\n        loop.close()\n"
  },
  {
    "path": "robyn/py.typed",
    "content": ""
  },
  {
    "path": "robyn/pydantic_support.py",
    "content": "\"\"\"\nOptional Pydantic integration for Robyn.\n\nAll pydantic imports are lazy: pydantic is only loaded on the first call\nto _ensure_pydantic(), so there is zero import-time overhead when pydantic\nis not installed or not used.  The module itself is imported at the top\nlevel by router.py and openapi.py, but this is safe because it contains\nno pydantic imports at module scope.\n\"\"\"\n\nimport inspect\nfrom typing import Any, Optional, Tuple\n\nimport orjson\n\n__all__ = [\n    \"is_pydantic_available\",\n    \"is_pydantic_model\",\n    \"detect_pydantic_params\",\n    \"validate_pydantic_body\",\n    \"get_pydantic_openapi_schema\",\n    \"serialize_pydantic_response\",\n    \"check_pydantic_installed_for_handler\",\n    \"PydanticBodyValidationError\",\n    \"PydanticNotInstalledError\",\n    \"MultiplePydanticBodyError\",\n]\n\n_BaseModel = None\n_ValidationError = None\n_pydantic_checked = False\n\n\ndef _ensure_pydantic():\n    \"\"\"Lazy-load pydantic classes. Called at most once.\"\"\"\n    global _BaseModel, _ValidationError, _pydantic_checked\n    if _pydantic_checked:\n        return\n    _pydantic_checked = True\n    try:\n        from pydantic import BaseModel, ValidationError\n\n        _BaseModel = BaseModel\n        _ValidationError = ValidationError\n    except ImportError:\n        _BaseModel = None\n        _ValidationError = None\n\n\ndef is_pydantic_available() -> bool:\n    _ensure_pydantic()\n    return _BaseModel is not None\n\n\ndef is_pydantic_model(annotation) -> bool:\n    \"\"\"Check if an annotation is a pydantic BaseModel subclass.\n    Returns False if pydantic is not installed or annotation is not a class.\"\"\"\n    _ensure_pydantic()\n    if _BaseModel is None:\n        return False\n    return inspect.isclass(annotation) and issubclass(annotation, _BaseModel)\n\n\ndef detect_pydantic_params(handler_params) -> dict:\n    \"\"\"Scan pre-computed handler parameters for Pydantic BaseModel annotations.\n\n    Accepts the ``parameters`` OrderedDict from ``inspect.signature(handler)``.\n    Returns a dict mapping param_name -> model_class for params annotated with\n    a Pydantic BaseModel subclass.  Returns empty dict if pydantic is not\n    installed or no params use Pydantic models.\n    \"\"\"\n    _ensure_pydantic()\n    if _BaseModel is None:\n        return {}\n\n    result = {}\n    for name, param in handler_params.items():\n        ann = param.annotation\n        if ann is inspect.Parameter.empty:\n            continue\n        if inspect.isclass(ann) and issubclass(ann, _BaseModel):\n            result[name] = ann\n    return result\n\n\ndef _sanitize_errors(errors: list) -> list:\n    \"\"\"Make pydantic error dicts JSON-serializable.\n\n    Only copies dicts that actually contain non-serializable values.\n    Pydantic v2 error dicts can contain bytes, tuples, and other\n    non-JSON-serializable values in 'input', 'loc', and 'ctx' fields.\n    \"\"\"\n    sanitized = []\n    for err in errors:\n        needs_copy = False\n        for key, val in err.items():\n            if (key == \"input\" and isinstance(val, bytes)) or key == \"loc\" or (key == \"ctx\" and isinstance(val, dict)):\n                needs_copy = True\n                break\n\n        if not needs_copy:\n            sanitized.append(err)\n            continue\n\n        clean = dict(err)\n        if \"input\" in clean and isinstance(clean[\"input\"], bytes):\n            clean[\"input\"] = clean[\"input\"].decode(\"utf-8\", errors=\"replace\")\n        if \"loc\" in clean:\n            clean[\"loc\"] = list(clean[\"loc\"])\n        if \"ctx\" in clean and isinstance(clean[\"ctx\"], dict):\n            clean[\"ctx\"] = {k: str(v) for k, v in clean[\"ctx\"].items()}\n        sanitized.append(clean)\n    return sanitized\n\n\ndef validate_pydantic_body(model_class, body: Any) -> Tuple[Any, Optional[dict]]:\n    \"\"\"Validate request body against a Pydantic model.\n\n    Uses model_validate_json for maximum performance — single-pass\n    parse+validate without an intermediate dict.  model_validate_json\n    accepts str, bytes, and bytearray natively.\n\n    This function is only called from the request hot path *after*\n    _ensure_pydantic() has already been called at registration time,\n    so we skip the redundant check here.\n\n    Returns:\n        (validated_model_instance, None) on success\n        (None, error_detail_dict) on failure\n    \"\"\"\n    try:\n        return model_class.model_validate_json(body), None\n    except _ValidationError as e:\n        return None, {\n            \"detail\": _sanitize_errors(e.errors()),\n            \"error\": \"Validation Error\",\n        }\n\n\ndef get_pydantic_openapi_schema(model_class) -> Tuple[dict, dict]:\n    \"\"\"Get OpenAPI-compatible JSON Schema from a Pydantic model.\n\n    Uses ref_template so nested model references point to\n    #/components/schemas/{ModelName} in the OpenAPI spec.\n\n    Returns:\n        (schema, component_schemas) where:\n        - schema: the model's JSON Schema (without $defs)\n        - component_schemas: dict of model_name -> schema for components/schemas\n    \"\"\"\n    _ensure_pydantic()\n    if _BaseModel is None or not (inspect.isclass(model_class) and issubclass(model_class, _BaseModel)):\n        return {}, {}\n\n    full_schema = model_class.model_json_schema(ref_template=\"#/components/schemas/{model}\")\n    component_schemas = full_schema.pop(\"$defs\", {})\n    return full_schema, component_schemas\n\n\ndef serialize_pydantic_response(res) -> Optional[str]:\n    \"\"\"Serialize a Pydantic model (or list of models) to a JSON string.\n\n    Returns None when *res* is not a Pydantic type so the caller can fall\n    through to other serialisation paths.\n\n    This function is only called from the response hot path *after*\n    _ensure_pydantic() has already been called at registration time,\n    so we skip the redundant check here.\n    \"\"\"\n    if _BaseModel is None:\n        return None\n    if isinstance(res, _BaseModel):\n        return res.model_dump_json()\n    if isinstance(res, list) and res and isinstance(res[0], _BaseModel):\n        return orjson.dumps([item.model_dump(mode=\"python\") for item in res]).decode(\"utf-8\")\n    return None\n\n\nclass PydanticBodyValidationError(Exception):\n    \"\"\"Raised at request time when Pydantic body validation fails.\n    Carries the serializable error dict for the 422 response.\"\"\"\n\n    def __init__(self, error_detail: dict):\n        self.error_detail = error_detail\n        super().__init__(error_detail.get(\"error\", \"Validation Error\"))\n\n\nclass PydanticNotInstalledError(ImportError):\n    \"\"\"Raised at route registration when a handler uses a Pydantic model\n    but pydantic is not installed.\"\"\"\n\n    def __init__(self, handler_name: str, param_name: str, model_name: str):\n        super().__init__(\n            f\"Handler '{handler_name}' has parameter '{param_name}' annotated with \"\n            f\"Pydantic model '{model_name}', but pydantic is not installed. \"\n            f'Install it with: pip install \"robyn[pydantic]\" or pip install \"robyn[all]\"'\n        )\n\n\nclass MultiplePydanticBodyError(TypeError):\n    \"\"\"Raised at route registration when a handler declares more than one\n    Pydantic body parameter.\"\"\"\n\n    def __init__(self, handler_name: str, param_names: list):\n        super().__init__(\n            f\"Handler '{handler_name}' has multiple Pydantic body parameters \"\n            f\"{param_names}. Only one Pydantic body parameter per handler is \"\n            f\"supported — the entire request body is parsed into that single model.\"\n        )\n\n\ndef check_pydantic_installed_for_handler(handler, pydantic_params: dict):\n    \"\"\"Validate Pydantic usage at startup.\n\n    Raises PydanticNotInstalledError if pydantic isn't available.\n    Raises MultiplePydanticBodyError if more than one body param is declared.\n    \"\"\"\n    if not pydantic_params:\n        return\n    if not is_pydantic_available():\n        first_param = next(iter(pydantic_params))\n        model = pydantic_params[first_param]\n        raise PydanticNotInstalledError(handler.__name__, first_param, model.__name__)\n    if len(pydantic_params) > 1:\n        raise MultiplePydanticBodyError(handler.__name__, list(pydantic_params.keys()))\n"
  },
  {
    "path": "robyn/reloader.py",
    "content": "import glob\nimport os\nimport signal\nimport subprocess\nimport sys\nimport time\nfrom typing import List, Union\n\nfrom watchdog.events import FileSystemEventHandler\nfrom watchdog.observers import Observer\n\nfrom robyn.logger import Colors, logger\n\n\ndef compile_rust_files(directory_path: str) -> List[str]:\n    rust_files = glob.glob(os.path.join(directory_path, \"**/*.rs\"), recursive=True)\n    rust_binaries: list[str] = []\n\n    for rust_file in rust_files:\n        print(f\"Compiling rust file: {rust_file}\")\n\n        result = subprocess.run(\n            [sys.executable, \"-m\", \"rustimport\", \"build\", rust_file],\n            stdout=subprocess.PIPE,\n            stderr=subprocess.PIPE,\n            start_new_session=False,\n        )\n        if result.returncode != 0:\n            print(f\"Error compiling rust file: {rust_file} \\n {result.stderr.decode('utf-8')} \\n {result.stdout.decode('utf-8')}\")\n        else:\n            print(f\"Compiled rust file: {rust_file}\")\n            rust_file_base = rust_file.removesuffix(\".rs\")\n\n            # Define the search pattern for the binary file\n            if sys.platform == \"win32\":\n                binary_extension = \".dll\"\n            elif sys.platform == \"darwin\":\n                binary_extension = \".so\"\n            elif sys.platform == \"linux\":\n                binary_extension = \".so\"\n            else:\n                raise ValueError(f\"Unsupported platform: {sys.platform}\")\n\n            search_pattern = f\"{rust_file_base}.*{binary_extension}\"\n            # Use glob to find matching binary files\n            matching_binaries = glob.glob(search_pattern)\n            rust_binaries.extend(matching_binaries)\n\n    return rust_binaries\n\n\ndef create_rust_file(file_name: str) -> None:\n    if file_name.endswith(\".rs\"):\n        file_name = file_name.removesuffix(\".rs\")\n\n    rust_file = f\"{file_name}.rs\"\n\n    result = subprocess.run(\n        [sys.executable, \"-m\", \"rustimport\", \"new\", rust_file],\n        stdout=subprocess.PIPE,\n        stderr=subprocess.PIPE,\n        start_new_session=False,\n    )\n\n    if result.returncode != 0:\n        print(\n            \"Error creating rust file : %s %s\",\n            result.stderr.decode(\"utf-8\"),\n            result.stdout.decode(\"utf-8\"),\n        )\n    else:\n        print(\"Created rust file : %s\", rust_file)\n\n\ndef clean_rust_binaries(rust_binaries: List[str]) -> None:\n    for file in rust_binaries:\n        print(\"Cleaning rust file : %s\", file)\n        os.remove(file)\n\n\ndef setup_reloader(directory_path: str, file_path: str) -> None:\n    event_handler = EventHandler(file_path, directory_path)\n\n    # sets the IS_RELOADER_RUNNING environment variable to True\n    event_handler.reload()\n\n    logger.info(\n        \"Dev server initialized with the directory_path : %s\",\n        directory_path,\n        color=Colors.BLUE,\n    )\n\n    def terminating_signal_handler(_sig, _frame):\n        event_handler.stop_server()\n        logger.info(\"Terminating reloader\", bold=True)\n        observer.stop()\n        observer.join()\n\n    signal.signal(signal.SIGINT, terminating_signal_handler)\n    signal.signal(signal.SIGTERM, terminating_signal_handler)\n\n    observer = Observer()\n    observer.schedule(event_handler, path=directory_path, recursive=True)\n    observer.start()\n\n    try:\n        while observer.is_alive():\n            observer.join(1)\n    finally:\n        observer.stop()\n        observer.join()\n        event_handler.process.wait()\n\n\nclass EventHandler(FileSystemEventHandler):\n    def __init__(self, file_path: str, directory_path: str) -> None:\n        self.file_path = file_path\n        self.directory_path = directory_path\n        self.process: Union[subprocess.Popen[bytes], None] = None  # Keep track of the subprocess\n        self.built_rust_binaries: List = []  # Keep track of the built rust binaries\n\n        self.last_reload = time.time()  # Keep track of the last reload. EventHandler is initialized with the process.\n\n    def stop_server(self) -> None:\n        if self.process:\n            os.kill(self.process.pid, signal.SIGTERM)  # Stop the subprocess using os.kill()\n\n    def reload(self) -> None:\n        self.stop_server()\n        print(\"Reloading the server\")\n\n        new_env = os.environ.copy()\n        new_env[\"IS_RELOADER_RUNNING\"] = \"True\"  # This is used to check if a reloader is already running\n        # IS_RELOADER_RUNNING is specifically used for IPC between the reloader and the server\n\n        arguments = [arg for arg in sys.argv[1:] if not arg.startswith(\"--dev\")]\n\n        clean_rust_binaries(self.built_rust_binaries)\n        self.built_rust_binaries = compile_rust_files(self.directory_path)\n\n        prev_process = self.process\n        if prev_process:\n            prev_process.kill()\n\n        self.process = subprocess.Popen(\n            [sys.executable, *arguments],\n            env=new_env,\n        )\n\n        self.last_reload = time.time()\n\n    def on_modified(self, event) -> None:\n        \"\"\"\n        This function is a callback that will start a new server on every even change\n\n        :param event FSEvent: a data structure with info about the events\n        \"\"\"\n\n        # Avoid reloading multiple times when watchdog detects multiple events\n        if time.time() - self.last_reload < 0.5:\n            return\n\n        time.sleep(0.2)  # Wait for the file to be fully written\n        self.reload()\n"
  },
  {
    "path": "robyn/responses.py",
    "content": "import asyncio\nimport mimetypes\nimport os\nfrom typing import AsyncGenerator, Generator, Optional, Union\n\nfrom robyn.robyn import Headers, Response\n\n\nclass FileResponse:\n    def __init__(\n        self,\n        file_path: str,\n        status_code: Optional[int] = None,\n        headers: Optional[Headers] = None,\n    ):\n        self.file_path = file_path\n        self.description = \"\"\n        self.status_code = status_code or 200\n        self.headers = headers or Headers({\"Content-Disposition\": \"attachment\"})\n\n\ndef html(html: str) -> Response:\n    \"\"\"\n    This function will help in serving a simple html string\n\n    :param html str: html to serve as a response\n    \"\"\"\n    return Response(\n        description=html,\n        status_code=200,\n        headers=Headers({\"Content-Type\": \"text/html\"}),\n    )\n\n\ndef serve_html(file_path: str) -> FileResponse:\n    \"\"\"\n    This function will help in serving a single html file\n\n    :param file_path str: file path to serve as a response\n    \"\"\"\n\n    return FileResponse(file_path, headers=Headers({\"Content-Type\": \"text/html\"}))\n\n\ndef serve_file(file_path: str, file_name: Optional[str] = None) -> FileResponse:\n    \"\"\"\n    This function will help in serving a file\n\n    :param file_path str: file path to serve as a response\n    :param file_name [str | None]: file name to serve as a response, defaults to None\n    \"\"\"\n    file_name = file_name or os.path.basename(file_path)\n\n    mime_type = mimetypes.guess_type(file_name)[0]\n\n    headers = Headers({\"Content-Type\": mime_type})\n    headers.append(\"Content-Disposition\", f\"attachment; filename={file_name}\")\n\n    return FileResponse(\n        file_path,\n        headers=headers,\n    )\n\n\nclass AsyncGeneratorWrapper:\n    \"\"\"Optimized true-streaming wrapper for async generators\"\"\"\n\n    def __init__(self, async_gen: AsyncGenerator[str, None]):\n        self.async_gen = async_gen\n        self._loop = None\n        self._iterator = None\n        self._exhausted = False\n\n    def __iter__(self):\n        return self\n\n    def __next__(self):\n        if self._exhausted:\n            raise StopIteration\n\n        # Initialize the loop and iterator only once\n        if self._iterator is None:\n            self._init_async_iterator()\n\n        try:\n            # Get the next value from the async generator\n            # This is the key optimization - we don't buffer, we get one value at a time\n            return self._get_next_value()\n        except StopIteration:\n            self._exhausted = True\n            raise\n\n    def _init_async_iterator(self):\n        \"\"\"Initialize the async iterator with proper loop handling\"\"\"\n        try:\n            # Try to get the running event loop\n            self._loop = asyncio.get_running_loop()\n        except RuntimeError:\n            # No running loop, create a new one\n            self._loop = asyncio.new_event_loop()\n            asyncio.set_event_loop(self._loop)\n\n        # Create the async iterator\n        self._iterator = self.async_gen.__aiter__()\n\n    def _get_next_value(self):\n        \"\"\"Get the next value from async generator without buffering\"\"\"\n        try:\n            # Create a coroutine to get the next value\n            async def get_next():\n                return await self._iterator.__anext__()\n\n            # Run the coroutine to get the next value\n            return self._loop.run_until_complete(get_next())\n        except StopAsyncIteration:\n            # Convert StopAsyncIteration to StopIteration for sync generator protocol\n            raise StopIteration\n        except Exception as e:\n            # Log error and stop iteration\n            print(f\"Error in async generator: {e}\")\n            raise StopIteration\n\n\nclass StreamingResponse:\n    def __init__(\n        self,\n        content: Union[Generator[str, None, None], AsyncGenerator[str, None]],\n        status_code: Optional[int] = None,\n        headers: Optional[Headers] = None,\n        media_type: str = \"text/event-stream\",\n    ):\n        # Convert async generator to sync generator if needed\n        # The Rust implementation detects async generators but falls back to Python wrapper\n        if hasattr(content, \"__anext__\"):\n            # This is an async generator - wrap it with optimized wrapper\n            self.content = AsyncGeneratorWrapper(content)\n        else:\n            # This is a sync generator - use as is\n            self.content = content\n\n        self.status_code = status_code or 200\n        self.headers = headers or Headers({})\n        self.media_type = media_type\n\n        # Set default SSE headers\n        if media_type == \"text/event-stream\":\n            self.headers.set(\"Content-Type\", \"text/event-stream\")\n            # Cache-Control and Connection headers are set by Rust layer with optimized headers\n\n\ndef SSEResponse(\n    content: Union[Generator[str, None, None], AsyncGenerator[str, None]],\n    status_code: Optional[int] = None,\n    headers: Optional[Headers] = None,\n) -> StreamingResponse:\n    \"\"\"\n    Create a Server-Sent Events (SSE) streaming response.\n\n    :param content: Generator or AsyncGenerator yielding SSE-formatted strings\n    :param status_code: HTTP status code (default: 200)\n    :param headers: Additional headers\n    :return: StreamingResponse configured for SSE\n    \"\"\"\n    return StreamingResponse(content=content, status_code=status_code, headers=headers, media_type=\"text/event-stream\")\n\n\ndef SSEMessage(data: str, event: Optional[str] = None, id: Optional[str] = None, retry: Optional[int] = None) -> str:\n    \"\"\"\n    Optimized SSE message formatting with minimal allocations.\n\n    :param data: The message data\n    :param event: Optional event type\n    :param id: Optional event ID\n    :param retry: Optional retry time in milliseconds\n    :return: SSE-formatted string\n    \"\"\"\n    # Pre-calculate size to avoid multiple string concatenations\n    parts = []\n\n    # Add optional fields first\n    if event:\n        parts.append(f\"event: {event}\\n\")\n    if id:\n        parts.append(f\"id: {id}\\n\")\n    if retry:\n        parts.append(f\"retry: {retry}\\n\")\n\n    # Handle data with optimized multi-line processing\n    if data:\n        data_str = str(data)\n        # Fast path for single-line data (most common case)\n        if \"\\n\" not in data_str and \"\\r\" not in data_str:\n            parts.append(f\"data: {data_str}\\n\")\n        else:\n            # Multi-line data handling\n            normalized_data = data_str.replace(\"\\r\\n\", \"\\n\").replace(\"\\r\", \"\\n\")\n            for line in normalized_data.split(\"\\n\"):\n                parts.append(f\"data: {line}\\n\")\n    else:\n        parts.append(\"data: \\n\")\n\n    # Add the required double newline terminator\n    parts.append(\"\\n\")\n\n    # Single join operation for optimal performance\n    return \"\".join(parts)\n"
  },
  {
    "path": "robyn/robyn.pyi",
    "content": "from __future__ import annotations\n\nfrom dataclasses import dataclass\nfrom enum import Enum\nfrom typing import Callable, Optional, Union\n\ndef get_version() -> str:\n    pass\n\nclass SocketHeld:\n    def __init__(self, url: str, port: int):\n        pass\n    def try_clone(self) -> SocketHeld:\n        pass\n\nclass MiddlewareType(Enum):\n    \"\"\"\n    The middleware types supported by Robyn.\n\n    Attributes:\n        BEFORE_REQUEST: str\n        AFTER_REQUEST: str\n    \"\"\"\n\n    BEFORE_REQUEST: str\n    AFTER_REQUEST: str\n\nclass HttpMethod(Enum):\n    \"\"\"\n    The HTTP methods supported by Robyn.\n\n    Attributes:\n        GET: str\n        POST: str\n        PUT: str\n        DELETE: str\n        PATCH: str\n        OPTIONS: str\n        HEAD: str\n        TRACE: str\n        CONNECT: str\n    \"\"\"\n\n    GET: str\n    POST: str\n    PUT: str\n    DELETE: str\n    PATCH: str\n    OPTIONS: str\n    HEAD: str\n    TRACE: str\n    CONNECT: str\n\n@dataclass\nclass FunctionInfo:\n    \"\"\"\n    The function info object passed to the route handler.\n\n    Attributes:\n        handler (Callable): The function to be called\n        is_async (bool): Whether the function is async or not\n        number_of_params (int): The number of parameters the function has\n        args (dict): The arguments of the function\n        kwargs (dict): The keyword arguments of the function\n    \"\"\"\n\n    handler: Callable\n    is_async: bool\n    number_of_params: int\n    args: dict\n    kwargs: dict\n\n@dataclass\nclass Url:\n    \"\"\"\n    The url object passed to the route handler.\n\n    Attributes:\n        scheme (str): The scheme of the url. e.g. http, https\n        host (str): The host of the url. e.g. localhost,\n        path (str): The path of the url. e.g. /user\n    \"\"\"\n\n    scheme: str\n    host: str\n    path: str\n\n@dataclass\nclass Identity:\n    claims: dict[str, str]\n\nclass QueryParams:\n    \"\"\"\n    The query params object passed to the route handler.\n\n    Attributes:\n        queries (dict[str, list[str]]): The query parameters of the request. e.g. /user?id=123 -> {\"id\": \"123\"}\n    \"\"\"\n\n    def set(self, key: str, value: str) -> None:\n        \"\"\"\n        Sets the value of the query parameter with the given key.\n        If the key already exists, the value will be appended to the list of values.\n\n        Args:\n            key (str): The key of the query parameter\n            value (str): The value of the query parameter\n        \"\"\"\n        pass\n\n    def get(self, key: str, default: Optional[str] = None) -> Optional[str]:\n        \"\"\"\n        Gets the last value of the query parameter with the given key.\n\n        Args:\n            key (str): The key of the query parameter\n            default (Optional[str]): The default value if the key does not exist\n        \"\"\"\n        pass\n\n    def empty(self) -> bool:\n        \"\"\"\n        Returns:\n            True if the query params are empty, False otherwise\n        \"\"\"\n        pass\n\n    def contains(self, key: str) -> bool:\n        \"\"\"\n        Returns:\n            True if the query params contain the key, False otherwise\n\n        Args:\n            key (str): The key of the query parameter\n        \"\"\"\n        pass\n\n    def get_first(self, key: str) -> Optional[str]:\n        \"\"\"\n        Gets the first value of the query parameter with the given key.\n\n        Args:\n            key (str): The key of the query parameter\n\n        \"\"\"\n        pass\n\n    def get_all(self, key: str) -> Optional[list[str]]:\n        \"\"\"\n        Gets all the values of the query parameter with the given key.\n\n        Args:\n            key (str): The key of the query parameter\n        \"\"\"\n        pass\n\n    def extend(self, other: QueryParams) -> None:\n        \"\"\"\n        Extends the query params with the other query params.\n\n        Args:\n            other (QueryParams): The other QueryParams object\n        \"\"\"\n        pass\n\n    def to_dict(self) -> dict[str, list[str]]:\n        \"\"\"\n        Returns:\n            The query params as a dictionary\n        \"\"\"\n        pass\n\n    def __contains__(self, key: str) -> bool:\n        pass\n\n    def __repr__(self) -> str:\n        pass\n\n@dataclass\nclass Cookie:\n    \"\"\"\n    A cookie with optional attributes following RFC 6265.\n\n    Attributes:\n        value (str): The cookie value\n        path (Optional[str]): Cookie path (e.g. \"/\")\n        domain (Optional[str]): Cookie domain\n        max_age (Optional[int]): Max age in seconds\n        expires (Optional[str]): Expiry date (deprecated, use max_age instead)\n        secure (bool): Only send over HTTPS\n        http_only (bool): Not accessible via JavaScript\n        same_site (Optional[str]): \"Strict\", \"Lax\", or \"None\" (case-insensitive)\n    \"\"\"\n\n    value: str\n    path: Optional[str] = None\n    domain: Optional[str] = None\n    max_age: Optional[int] = None\n    expires: Optional[str] = None\n    secure: bool = False\n    http_only: bool = False\n    same_site: Optional[str] = None\n\n    @staticmethod\n    def deleted() -> \"Cookie\":\n        \"\"\"\n        Create a cookie configured for deletion (expires immediately with max_age=0).\n\n        Returns:\n            Cookie: A cookie that will be deleted by the browser\n        \"\"\"\n        pass\n\nclass Cookies:\n    \"\"\"A collection of cookies keyed by name.\"\"\"\n\n    def __init__(self) -> None:\n        pass\n\n    def set(self, name: str, cookie: Cookie) -> None:\n        \"\"\"\n        Sets a cookie with the given name.\n\n        Args:\n            name (str): The name of the cookie\n            cookie (Cookie): The cookie object\n        \"\"\"\n        pass\n\n    def get(self, name: str) -> Optional[Cookie]:\n        \"\"\"\n        Gets the cookie with the given name.\n\n        Args:\n            name (str): The name of the cookie\n        \"\"\"\n        pass\n\n    def remove(self, name: str) -> None:\n        \"\"\"\n        Removes the cookie from the collection (does not delete it from the browser).\n\n        Args:\n            name (str): The name of the cookie\n        \"\"\"\n        pass\n\n    def delete(self, name: str) -> None:\n        \"\"\"\n        Mark a cookie for deletion by setting it to expire immediately.\n        This sets max_age=0 which tells the browser to delete the cookie.\n\n        Args:\n            name (str): The name of the cookie to delete\n        \"\"\"\n        pass\n\n    def is_empty(self) -> bool:\n        \"\"\"\n        Returns:\n            True if there are no cookies, False otherwise\n        \"\"\"\n        pass\n\n    def keys(self) -> list[str]:\n        \"\"\"\n        Returns:\n            A list of all cookie names\n        \"\"\"\n        pass\n\n    def __setitem__(self, name: str, cookie: Cookie) -> None:\n        pass\n\n    def __getitem__(self, name: str) -> Optional[Cookie]:\n        pass\n\n    def __contains__(self, name: str) -> bool:\n        pass\n\n    def __len__(self) -> int:\n        pass\n\n    def __iter__(self) -> \"CookiesIter\":\n        pass\n\n    def __repr__(self) -> str:\n        pass\n\nclass CookiesIter:\n    \"\"\"Iterator for Cookies collection.\"\"\"\n\n    def __iter__(self) -> \"CookiesIter\":\n        pass\n\n    def __next__(self) -> str:\n        pass\n\nclass Headers:\n    def __init__(self, default_headers: Optional[dict]) -> None:\n        pass\n\n    def __getitem__(self, key: str) -> Optional[str]:\n        pass\n\n    def __setitem__(self, key: str, value: str) -> None:\n        pass\n\n    def set(self, key: str, value: str) -> None:\n        \"\"\"\n        Sets the value of the header with the given key.\n        If the key already exists, the value will be appended to the list of values.\n\n        Args:\n            key (str): The key of the header\n            value (str): The value of the header\n        \"\"\"\n        pass\n\n    def get(self, key: str) -> Optional[str]:\n        \"\"\"\n        Gets the last value of the header with the given key.\n\n        Args:\n            key (str): The key of the header\n        \"\"\"\n        pass\n\n    def populate_from_dict(self, headers: dict[str, str]) -> None:\n        \"\"\"\n        Populates the headers from a dictionary.\n\n        Args:\n            headers (dict[str, str]): The dictionary of headers\n        \"\"\"\n        pass\n\n    def contains(self, key: str) -> bool:\n        \"\"\"\n        Returns:\n            True if the headers contain the key, False otherwise\n\n        Args:\n            key (str): The key of the header\n        \"\"\"\n        pass\n\n    def append(self, key: str, value: str) -> None:\n        \"\"\"\n        Appends the value to the header with the given key.\n\n        Args:\n            key (str): The key of the header\n            value (str): The value of the header\n        \"\"\"\n        pass\n\n    def is_empty(self) -> bool:\n        \"\"\"\n        Returns:\n            True if the headers are empty, False otherwise\n        \"\"\"\n        pass\n\n@dataclass\nclass Request:\n    \"\"\"\n    The request object passed to the route handler.\n\n    Attributes:\n        query_params (QueryParams): The query parameters of the request. e.g. /user?id=123 -> {\"id\": \"123\"}\n        headers Headers: The headers of the request. e.g. Headers({\"Content-Type\": \"application/json\"})\n        path_params (dict[str, str]): The parameters of the request. e.g. /user/:id -> {\"id\": \"123\"}\n        body (Union[str, bytes]): The body of the request. If the request is a JSON, it will be a dict.\n        method (str): The method of the request. e.g. GET, POST, PUT etc.\n        url (Url): The url of the request. e.g. https://localhost/user\n        form_data (dict[str, str]): The form data of the request. e.g. {\"name\": \"John\"}\n        files (dict[str, bytes]): The files of the request. e.g. {\"file\": b\"file\"}\n        ip_addr (Optional[str]): The IP Address of the client\n        identity (Optional[Identity]): The identity of the client\n    \"\"\"\n\n    query_params: QueryParams\n    headers: Headers\n    path_params: dict[str, str]\n    body: Union[str, bytes]\n    method: str\n    url: Url\n    form_data: dict[str, str]\n    files: dict[str, bytes]\n    ip_addr: Optional[str]\n    identity: Optional[Identity]\n\n    def json(self) -> Union[dict, list]:\n        \"\"\"\n        Parse the request body as JSON and return a Python dict or list with preserved types.\n\n        JSON types are mapped to Python types as follows:\n        - null -> None\n        - bool -> bool\n        - number -> int or float\n        - string -> str\n        - array -> list\n        - object -> dict\n\n        Nested structures are handled recursively up to a maximum depth of 128.\n\n        Raises:\n            ValueError: If the body is not valid JSON.\n        \"\"\"\n        pass\n\n@dataclass\nclass Response:\n    \"\"\"\n    The response object passed to the route handler.\n\n    Attributes:\n        status_code (int): The status code of the response. e.g. 200, 404, 500 etc.\n        response_type (Optional[str]): The response type of the response. e.g. text, json, html, file etc.\n        headers (Union[Headers, dict]): The headers of the response or Headers directly. e.g. {\"Content-Type\": \"application/json\"}\n        description (Union[str, bytes]): The body of the response. If the response is a JSON, it will be a dict.\n        file_path (Optional[str]): The file path of the response. e.g. /home/user/file.txt\n        cookies (Cookies): The cookies to set in the response.\n    \"\"\"\n\n    status_code: int\n    headers: Union[Headers, dict]\n    description: Union[str, bytes]\n    response_type: Optional[str] = None\n    file_path: Optional[str] = None\n    cookies: Cookies = None  # Initialized automatically\n\n    def set_cookie(\n        self,\n        key: str,\n        value: str,\n        path: Optional[str] = None,\n        domain: Optional[str] = None,\n        max_age: Optional[int] = None,\n        expires: Optional[str] = None,\n        secure: bool = False,\n        http_only: bool = False,\n        same_site: Optional[str] = None,\n    ) -> None:\n        \"\"\"\n        Sets a cookie in the response. If a cookie with the same key exists,\n        it will be overwritten.\n\n        Args:\n            key (str): The name of the cookie\n            value (str): The value of the cookie\n            path (Optional[str]): Cookie path (e.g. \"/\")\n            domain (Optional[str]): Cookie domain\n            max_age (Optional[int]): Max age in seconds\n            expires (Optional[str]): Expiry date (RFC 7231 format)\n            secure (bool): Only send over HTTPS\n            http_only (bool): Not accessible via JavaScript\n            same_site (Optional[str]): \"Strict\", \"Lax\", or \"None\"\n        \"\"\"\n        pass\n\nclass Server:\n    \"\"\"\n    The Server object used to create a Robyn server.\n\n    This object is used to create a Robyn server and add routes, middlewares, etc.\n    \"\"\"\n    def __init__(self) -> None:\n        pass\n    def add_directory(\n        self,\n        route: str,\n        directory_path: str,\n        show_files_listing: bool,\n        index_file: Optional[str],\n    ) -> None:\n        pass\n    def apply_request_headers(self, headers: Headers) -> None:\n        pass\n    def apply_response_headers(self, headers: Headers) -> None:\n        pass\n    def set_response_headers_exclude_paths(self, excluded_response_headers_paths: Optional[list[str]] = None):\n        pass\n\n    def add_route(\n        self,\n        route_type: HttpMethod,\n        route: str,\n        function: FunctionInfo,\n        is_const: bool,\n    ) -> None:\n        pass\n    def add_global_middleware(self, middleware_type: MiddlewareType, function: FunctionInfo) -> None:\n        pass\n    def add_middleware_route(\n        self,\n        middleware_type: MiddlewareType,\n        route: str,\n        function: FunctionInfo,\n        route_type: HttpMethod,\n    ) -> None:\n        pass\n    def add_startup_handler(self, function: FunctionInfo) -> None:\n        pass\n    def add_shutdown_handler(self, function: FunctionInfo) -> None:\n        pass\n    def add_web_socket_route(\n        self,\n        route: str,\n        connect_route: FunctionInfo,\n        close_route: FunctionInfo,\n        message_route: FunctionInfo,\n        use_channel: bool,\n    ) -> None:\n        pass\n    def start(self, socket: SocketHeld, workers: int, client_timeout: int, keep_alive_timeout: int) -> None:\n        pass\n\nclass WebSocketConnector:\n    \"\"\"\n    The WebSocketConnector object passed to the route handler.\n\n    Attributes:\n        id (str): The id of the client\n        query_params (QueryParams): The query parameters object\n\n        async_broadcast (Callable): The function to broadcast a message to all clients\n        async_send_to (Callable): The function to send a message to the client\n        sync_broadcast (Callable): The function to broadcast a message to all clients\n        sync_send_to (Callable): The function to send a message to the client\n    \"\"\"\n\n    id: str\n    query_params: QueryParams\n\n    async def async_broadcast(self, message: str) -> None:\n        \"\"\"\n        Broadcasts a message to all clients.\n\n        Args:\n            message (str): The message to broadcast\n        \"\"\"\n        pass\n    async def async_send_to(self, sender_id: str, message: str) -> None:\n        \"\"\"\n        Sends a message to a specific client.\n\n        Args:\n            sender_id (str): The id of the sender\n            message (str): The message to send\n        \"\"\"\n        pass\n    def sync_broadcast(self, message: str) -> None:\n        \"\"\"\n        Broadcasts a message to all clients.\n\n        Args:\n            message (str): The message to broadcast\n        \"\"\"\n        pass\n    def sync_send_to(self, sender_id: str, message: str) -> None:\n        \"\"\"\n        Sends a message to a specific client.\n\n        Args:\n            sender_id (str): The id of the sender\n            message (str): The message to send\n        \"\"\"\n        pass\n    def close(self) -> None:\n        \"\"\"\n        Closes the connection.\n        \"\"\"\n        pass\n"
  },
  {
    "path": "robyn/router.py",
    "content": "import inspect\nimport logging\nfrom abc import ABC, abstractmethod\nfrom functools import wraps\nfrom types import CoroutineType\nfrom typing import Callable, Dict, List, NamedTuple, Optional, Union, is_typeddict\n\nfrom robyn import status_codes\nfrom robyn._param_utils import QueryParamValidationError, parse_route_param_names, resolve_individual_params\nfrom robyn.authentication import AuthenticationHandler, AuthenticationNotConfiguredError\nfrom robyn.dependency_injection import DependencyMap\nfrom robyn.jsonify import jsonify\nfrom robyn.openapi import OpenAPI\nfrom robyn.pydantic_support import (\n    PydanticBodyValidationError,\n    check_pydantic_installed_for_handler,\n    detect_pydantic_params,\n    serialize_pydantic_response,\n    validate_pydantic_body,\n)\nfrom robyn.responses import FileResponse, StreamingResponse\nfrom robyn.robyn import FunctionInfo, Headers, HttpMethod, Identity, MiddlewareType, QueryParams, Request, Response, Url\nfrom robyn.types import Body, Files, FormData, IPAddress, JsonBody, Method, PathParams\n\n_logger = logging.getLogger(__name__)\n\n\ndef lower_http_method(method: HttpMethod):\n    return (str(method))[11:].lower()\n\n\nclass Route(NamedTuple):\n    route_type: HttpMethod\n    route: str\n    function: FunctionInfo\n    is_const: bool\n    auth_required: bool\n    openapi_name: str\n    openapi_tags: List[str]\n\n\nclass RouteMiddleware(NamedTuple):\n    middleware_type: MiddlewareType\n    route: str\n    function: FunctionInfo\n    route_type: HttpMethod\n\n\nclass GlobalMiddleware(NamedTuple):\n    middleware_type: MiddlewareType\n    function: FunctionInfo\n\n\nclass BaseRouter(ABC):\n    @abstractmethod\n    def add_route(*args) -> Union[Callable, CoroutineType, Dict]: ...\n\n\nclass Router(BaseRouter):\n    def __init__(self) -> None:\n        super().__init__()\n        self.routes: List[Route] = []\n\n    def _format_tuple_response(self, res: tuple) -> Response:\n        if len(res) != 3:\n            raise ValueError(\"Tuple should have 3 elements\")\n\n        description, headers, status_code = res\n        description = self._format_response(description).description\n        new_headers: Headers = Headers(headers)\n        if new_headers.contains(\"Content-Type\"):\n            headers.set(\"Content-Type\", new_headers.get(\"Content-Type\"))\n\n        return Response(\n            status_code=status_code,\n            headers=headers,\n            description=description,\n        )\n\n    def _format_response(\n        self,\n        res: Union[Dict, List, Response, StreamingResponse, bytes, tuple, str],\n    ) -> Union[Response, StreamingResponse]:\n        if isinstance(res, Response):\n            return res\n\n        if isinstance(res, StreamingResponse):\n            return res\n\n        pydantic_json = serialize_pydantic_response(res)\n        if pydantic_json is not None:\n            return Response(\n                status_code=status_codes.HTTP_200_OK,\n                headers=Headers({\"Content-Type\": \"application/json\"}),\n                description=pydantic_json,\n            )\n\n        if isinstance(res, (dict, list)):\n            return Response(\n                status_code=status_codes.HTTP_200_OK,\n                headers=Headers({\"Content-Type\": \"application/json\"}),\n                description=jsonify(res),\n            )\n\n        if isinstance(res, FileResponse):\n            response: Response = Response(\n                status_code=res.status_code,\n                headers=res.headers,\n                description=res.file_path,\n            )\n            response.file_path = res.file_path\n            return response\n\n        if isinstance(res, bytes):\n            return Response(\n                status_code=status_codes.HTTP_200_OK,\n                headers=Headers({\"Content-Type\": \"application/octet-stream\"}),\n                description=res,\n            )\n\n        if isinstance(res, tuple):\n            return self._format_tuple_response(tuple(res))\n\n        return Response(\n            status_code=status_codes.HTTP_200_OK,\n            headers=Headers({\"Content-Type\": \"text/plain\"}),\n            description=str(res).encode(\"utf-8\"),\n        )\n\n    def add_route(  # type: ignore\n        self,\n        route_type: HttpMethod,\n        endpoint: str,\n        handler: Callable,\n        is_const: bool,\n        auth_required: bool,\n        openapi_name: str,\n        openapi_tags: List[str],\n        exception_handler: Optional[Callable],\n        injected_dependencies: dict,\n    ) -> Union[Callable, CoroutineType]:\n        # Pre-compute handler signature ONCE at registration time.\n        # This avoids calling inspect.signature() on every request.\n        route_param_names = parse_route_param_names(endpoint)\n        handler_params = inspect.signature(handler).parameters\n        handler_param_names = set(handler_params.keys())\n\n        unused_route_params = route_param_names - handler_param_names\n        if unused_route_params:\n            _logger.warning(\n                \"Route '%s' declares path params %s but handler '%s' doesn't use them\",\n                endpoint,\n                unused_route_params,\n                handler.__name__,\n            )\n\n        # Detect Pydantic model params once at registration (zero cost if not used)\n        pydantic_params = detect_pydantic_params(handler_params)\n        check_pydantic_installed_for_handler(handler, pydantic_params)\n\n        if pydantic_params and route_type in (HttpMethod.GET, HttpMethod.HEAD):\n            _logger.warning(\n                \"Handler '%s' on %s '%s' uses Pydantic body parameter(s) %s, but %s requests typically do not carry a request body\",\n                handler.__name__,\n                route_type.name,\n                endpoint,\n                list(pydantic_params.keys()),\n                route_type.name,\n            )\n\n        def wrapped_handler(*args, **kwargs):\n            request = next(filter(lambda it: isinstance(it, Request), args), None)\n\n            if not request or (len(handler_params) == 1 and next(iter(handler_params)) is Request):\n                return handler(*args, **kwargs)\n\n            type_mapping = {\n                \"request\": Request,\n                \"query_params\": QueryParams,\n                \"headers\": Headers,\n                \"path_params\": PathParams,\n                \"body\": Body,\n                \"method\": Method,\n                \"url\": Url,\n                \"form_data\": FormData,\n                \"files\": Files,\n                \"ip_addr\": IPAddress,\n                \"identity\": Identity,\n            }\n\n            # Phase 1: Type-annotated request components\n            type_filtered_params = {}\n\n            for handler_param in iter(handler_params):\n                for type_name in type_mapping:\n                    handler_param_type = handler_params[handler_param].annotation\n                    handler_param_name = handler_params[handler_param].name\n                    if handler_param_type is Request:\n                        type_filtered_params[handler_param_name] = request\n                    elif handler_param_type is type_mapping[type_name]:\n                        type_filtered_params[handler_param_name] = getattr(request, type_name)\n                    elif inspect.isclass(handler_param_type):\n                        if issubclass(handler_param_type, JsonBody):\n                            try:\n                                type_filtered_params[handler_param_name] = request.json()\n                            except ValueError as e:\n                                return Response(\n                                    status_code=status_codes.HTTP_400_BAD_REQUEST,\n                                    headers=Headers({\"Content-Type\": \"application/json\"}),\n                                    description=jsonify({\"error\": f\"Invalid JSON body: {e}\"}),\n                                )\n                        elif issubclass(handler_param_type, Body):\n                            type_filtered_params[handler_param_name] = getattr(request, \"body\")\n                        elif issubclass(handler_param_type, QueryParams):\n                            type_filtered_params[handler_param_name] = getattr(request, \"query_params\")\n                        elif is_typeddict(handler_param_type):\n                            try:\n                                type_filtered_params[handler_param_name] = request.json()\n                            except ValueError as e:\n                                return Response(\n                                    status_code=status_codes.HTTP_400_BAD_REQUEST,\n                                    headers=Headers({\"Content-Type\": \"application/json\"}),\n                                    description=jsonify({\"error\": f\"Invalid JSON body: {e}\"}),\n                                )\n\n            # Phase 1.5: Pydantic model body params (only runs if handler uses pydantic)\n            if pydantic_params:\n                for param_name, model_class in pydantic_params.items():\n                    if param_name in type_filtered_params:\n                        continue\n                    validated, error = validate_pydantic_body(model_class, request.body)\n                    if error is not None:\n                        raise PydanticBodyValidationError(error)\n                    type_filtered_params[param_name] = validated\n\n            # Phase 2: Reserved-name request components\n            request_components = {\n                \"r\": request,\n                \"req\": request,\n                \"request\": request,\n                \"query_params\": request.query_params,\n                \"headers\": request.headers,\n                \"path_params\": request.path_params,\n                \"body\": request.body,\n                \"method\": request.method,\n                \"url\": request.url,\n                \"ip_addr\": request.ip_addr,\n                \"identity\": request.identity,\n                \"form_data\": request.form_data,\n                \"files\": request.files,\n                \"router_dependencies\": injected_dependencies[\"router_dependencies\"],\n                \"global_dependencies\": injected_dependencies[\"global_dependencies\"],\n                **kwargs,\n            }\n\n            name_filtered_params = {k: v for k, v in request_components.items() if k in handler_params and k not in type_filtered_params}\n\n            filtered_params = dict(**type_filtered_params, **name_filtered_params)\n\n            # Phase 3: Individual path/query param resolution\n            unresolved_names = set(handler_params) - set(filtered_params)\n            if unresolved_names:\n                unresolved = {name: handler_params[name] for name in unresolved_names}\n                individual_params = resolve_individual_params(\n                    unresolved,\n                    request.query_params,\n                    request.path_params,\n                    route_param_names,\n                )\n                filtered_params.update(individual_params)\n\n            return handler(**filtered_params)\n\n        @wraps(handler)\n        async def async_inner_handler(*args, **kwargs):\n            try:\n                response = self._format_response(\n                    await wrapped_handler(*args, **kwargs),\n                )\n            except QueryParamValidationError as err:\n                response = Response(\n                    status_code=status_codes.HTTP_400_BAD_REQUEST,\n                    headers=Headers({\"Content-Type\": \"text/plain\"}),\n                    description=str(err),\n                )\n            except PydanticBodyValidationError as err:\n                response = Response(\n                    status_code=status_codes.HTTP_422_UNPROCESSABLE_ENTITY,\n                    headers=Headers({\"Content-Type\": \"application/json\"}),\n                    description=jsonify(err.error_detail),\n                )\n            except Exception as err:\n                if exception_handler is None:\n                    raise\n                response = self._format_response(\n                    exception_handler(err),\n                )\n            return response\n\n        @wraps(handler)\n        def inner_handler(*args, **kwargs):\n            try:\n                response = self._format_response(\n                    wrapped_handler(*args, **kwargs),\n                )\n            except QueryParamValidationError as err:\n                response = Response(\n                    status_code=status_codes.HTTP_400_BAD_REQUEST,\n                    headers=Headers({\"Content-Type\": \"text/plain\"}),\n                    description=str(err),\n                )\n            except PydanticBodyValidationError as err:\n                response = Response(\n                    status_code=status_codes.HTTP_422_UNPROCESSABLE_ENTITY,\n                    headers=Headers({\"Content-Type\": \"application/json\"}),\n                    description=jsonify(err.error_detail),\n                )\n            except Exception as err:\n                if exception_handler is None:\n                    raise\n                response = self._format_response(\n                    exception_handler(err),\n                )\n            return response\n\n        params = dict(handler_params)\n\n        new_injected_dependencies = {}\n        for dependency in injected_dependencies:\n            if dependency in params:\n                new_injected_dependencies[dependency] = injected_dependencies[dependency]\n            else:\n                _logger.debug(f\"Dependency {dependency} is not used in the handler {handler.__name__}\")\n\n        if inspect.iscoroutinefunction(handler):\n            function = FunctionInfo(\n                async_inner_handler,\n                True,\n                len(params),\n                params,\n                new_injected_dependencies,\n            )\n            self.routes.append(Route(route_type, endpoint, function, is_const, auth_required, openapi_name, openapi_tags))\n            return async_inner_handler\n        else:\n            function = FunctionInfo(\n                inner_handler,\n                False,\n                len(params),\n                params,\n                new_injected_dependencies,\n            )\n            self.routes.append(Route(route_type, endpoint, function, is_const, auth_required, openapi_name, openapi_tags))\n            return inner_handler\n\n    def prepare_routes_openapi(self, openapi: OpenAPI, included_routers: List) -> None:\n        for route in self.routes:\n            openapi.add_openapi_path_obj(lower_http_method(route.route_type), route.route, route.openapi_name, route.openapi_tags, route.function.handler)\n\n        # TODO! after include_routes does not immediately merge all the routes\n        # for router in included_routers:\n        #    for route in router:\n        #        openapi.add_openapi_path_obj(lower_http_method(route.route_type), route.route, route.openapi_name, route.openapi_tags, route.function.handler)\n\n    def get_routes(self) -> List[Route]:\n        return self.routes\n\n\nclass MiddlewareRouter(BaseRouter):\n    def __init__(self, dependencies: DependencyMap = DependencyMap()) -> None:\n        super().__init__()\n        self.global_middlewares: List[GlobalMiddleware] = []\n        self.route_middlewares: List[RouteMiddleware] = []\n        self.authentication_handler: Optional[AuthenticationHandler] = None\n        self.dependencies = dependencies\n\n    def set_authentication_handler(self, authentication_handler: AuthenticationHandler):\n        self.authentication_handler = authentication_handler\n\n    def add_route(  # type: ignore\n        self,\n        middleware_type: MiddlewareType,\n        endpoint: str,\n        route_type: HttpMethod,\n        handler: Callable,\n        injected_dependencies: dict,\n    ) -> Callable:\n        params = dict(inspect.signature(handler).parameters)\n\n        new_injected_dependencies = {}\n        for dependency in injected_dependencies:\n            if dependency in params:\n                new_injected_dependencies[dependency] = injected_dependencies[dependency]\n            else:\n                _logger.debug(f\"Dependency {dependency} is not used in the middleware handler {handler.__name__}\")\n\n        function = FunctionInfo(\n            handler,\n            inspect.iscoroutinefunction(handler),\n            len(params),\n            params,\n            new_injected_dependencies,\n        )\n        self.route_middlewares.append(RouteMiddleware(middleware_type, endpoint, function, route_type))\n        return handler\n\n    def add_auth_middleware(self, endpoint: str, route_type: HttpMethod):\n        \"\"\"\n        This method adds an authentication middleware to the specified endpoint.\n        \"\"\"\n\n        injected_dependencies: dict = {}\n\n        def decorator(handler):\n            @wraps(handler)\n            def inner_handler(request: Request, *args):\n                if not self.authentication_handler:\n                    raise AuthenticationNotConfiguredError()\n                identity = self.authentication_handler.authenticate(request)\n                if identity is None:\n                    return self.authentication_handler.unauthorized_response\n                request.identity = identity\n\n                return request\n\n            self.add_route(\n                MiddlewareType.BEFORE_REQUEST,\n                endpoint,\n                route_type,\n                inner_handler,\n                injected_dependencies,\n            )\n            return inner_handler\n\n        return decorator\n\n    # These inner functions are basically a wrapper around the closure(decorator) being returned.\n    # They take a handler, convert it into a closure and return the arguments.\n    # Arguments are returned as they could be modified by the middlewares.\n    def add_middleware(self, middleware_type: MiddlewareType, endpoint: Optional[str]) -> Callable[..., None]:\n        \"\"\"\n        This method adds a middleware to the router.\n\n        Rules:\n            If endpoint is None, the middleware is added as a global middleware.\n            If endpoint is provided, the middleware is added to that specific endpoint.\n            Only None is supported for global middleware, empty string is not supported.\n            empty string or \"/\" is considered as root endpoint.\n\n        Args:\n            middleware_type: The type of middleware to add (before_request, after_request).\n            endpoint: The endpoint to add the middleware to. If None, the middleware is added as a global middleware.\n\n        Returns:\n            A decorator that takes a handler and adds it as a middleware.\n        \"\"\"\n        # no dependency injection here\n        injected_dependencies: dict = {}\n\n        def inner(handler):\n            @wraps(handler)\n            async def async_inner_handler(*args, **kwargs):\n                return await handler(*args, **kwargs)\n\n            @wraps(handler)\n            def inner_handler(*args, **kwargs):\n                return handler(*args, **kwargs)\n\n            if endpoint is not None:\n                if inspect.iscoroutinefunction(handler):\n                    self.add_route(\n                        middleware_type,\n                        endpoint,\n                        HttpMethod.GET,\n                        async_inner_handler,\n                        injected_dependencies,\n                    )\n                else:\n                    self.add_route(middleware_type, endpoint, HttpMethod.GET, inner_handler, injected_dependencies)\n            else:\n                params = dict(inspect.signature(handler).parameters)\n\n                if inspect.iscoroutinefunction(handler):\n                    self.global_middlewares.append(\n                        GlobalMiddleware(\n                            middleware_type,\n                            FunctionInfo(\n                                async_inner_handler,\n                                True,\n                                len(params),\n                                params,\n                                injected_dependencies,\n                            ),\n                        )\n                    )\n                else:\n                    self.global_middlewares.append(\n                        GlobalMiddleware(\n                            middleware_type,\n                            FunctionInfo(\n                                inner_handler,\n                                False,\n                                len(params),\n                                params,\n                                injected_dependencies,\n                            ),\n                        )\n                    )\n\n        return inner\n\n    def get_route_middlewares(self) -> List[RouteMiddleware]:\n        return self.route_middlewares\n\n    def get_global_middlewares(self) -> List[GlobalMiddleware]:\n        return self.global_middlewares\n\n\nclass WebSocketRouter(BaseRouter):\n    def __init__(self) -> None:\n        super().__init__()\n        self.routes: Dict[str, dict] = {}\n\n    def add_route(self, endpoint: str, handlers: dict) -> None:  # type: ignore\n        self.routes[endpoint] = handlers\n\n    def get_routes(self) -> Dict[str, dict]:\n        return self.routes\n"
  },
  {
    "path": "robyn/scaffold/mongo/Dockerfile",
    "content": "FROM python:3.11-bookworm\n\nWORKDIR /workspace\n\nCOPY . .\n\nRUN pip install --no-cache-dir --upgrade -r requirements.txt\n\n\nEXPOSE 8080\n\nCMD [\"python3\", \"app.py\", \"--log-level=DEBUG\"]\n"
  },
  {
    "path": "robyn/scaffold/mongo/app.py",
    "content": "from pymongo import MongoClient\n\nfrom robyn import Robyn\n\napp = Robyn(__file__)\ndb = MongoClient(\"URL HERE\")\n\nusers = db.users  # define a collection\n\n\n@app.get(\"/\")\ndef index():\n    return \"Hello World!\"\n\n\n# create a route\n@app.get(\"/users\")\nasync def get_users():\n    all_users = await users.find().to_list(length=None)\n    return {\"users\": all_users}\n\n\n# create a route to add a new user\n@app.post(\"/users\")\nasync def add_user(request):\n    user_data = await request.json()\n    result = await users.insert_one(user_data)\n    return {\"success\": True, \"inserted_id\": str(result.inserted_id)}\n\n\n# create a route to fetch a single user by ID\n@app.get(\"/users/{user_id}\")\nasync def get_user(request):\n    user_id = request.path_params[\"user_id\"]\n    user = await users.find_one({\"_id\": user_id})\n    if user:\n        return user\n    else:\n        return {\"error\": \"User not found\"}, 404\n\n\nif __name__ == \"__main__\":\n    app.start(host=\"0.0.0.0\", port=8080)\n"
  },
  {
    "path": "robyn/scaffold/mongo/requirements.txt",
    "content": "robyn\npymongo\n"
  },
  {
    "path": "robyn/scaffold/no-db/Dockerfile",
    "content": "FROM python:3.11-bookworm\n\nWORKDIR /workspace\n\nCOPY . .\n\nRUN pip install --no-cache-dir --upgrade -r requirements.txt\n\n\nEXPOSE 8080\n\nCMD [\"python3\", \"app.py\", \"--log-level=DEBUG\"]\n"
  },
  {
    "path": "robyn/scaffold/no-db/app.py",
    "content": "from robyn import Robyn\n\napp = Robyn(__file__)\n\n\n@app.get(\"/\")\ndef index():\n    return \"Hello World!\"\n\n\nif __name__ == \"__main__\":\n    app.start(host=\"0.0.0.0\", port=8080)\n"
  },
  {
    "path": "robyn/scaffold/no-db/requirements.txt",
    "content": "robyn\n"
  },
  {
    "path": "robyn/scaffold/postgres/Dockerfile",
    "content": "# ---- Build the PostgreSQL Base ----\nFROM postgres:latest AS postgres-base\n\nENV POSTGRES_USER=postgres\nENV POSTGRES_PASSWORD=password\nENV POSTGRES_DB=postgresDB\n\n# ---- Build the Python App ----\nFROM python:3.11-bookworm\n\n# Install supervisor\nRUN apt-get update && apt-get install -y supervisor\n\nWORKDIR /workspace\n\nCOPY . .\n\nRUN pip install --no-cache-dir --upgrade -r requirements.txt\n\n\n# Copy PostgreSQL binaries from the first stage\nCOPY --from=postgres-base /usr/local/bin /usr/local/bin\nCOPY --from=postgres-base /usr/lib/postgresql /usr/lib/postgresql\nCOPY --from=postgres-base /usr/share/postgresql /usr/share/postgresql\nCOPY --from=postgres-base /var/lib/postgresql /var/lib/postgresql\n\n# Add supervisord config\nCOPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf\n\nEXPOSE 8080 5455\n\nCMD [\"/usr/bin/supervisord\"]\n"
  },
  {
    "path": "robyn/scaffold/postgres/app.py",
    "content": "import psycopg2\n\nfrom robyn import Robyn\n\nDB_NAME = \"postgresDB\"\nDB_HOST = \"localhost\"\nDB_USER = \"postgres\"\nDB_PASS = \"password\"\nDB_PORT = \"5455\"\n\nconn = psycopg2.connect(database=DB_NAME, host=DB_HOST, user=DB_USER, password=DB_PASS, port=DB_PORT)\n\napp = Robyn(__file__)\n\n\n# create a route to fetch all users\n@app.get(\"/users\")\ndef get_users():\n    cursor = conn.cursor()\n    cursor.execute(\"SELECT * FROM users\")\n    all_users = cursor.fetchall()\n    return {\"users\": all_users}\n\n\n@app.get(\"/\")\ndef index():\n    return \"Hello World!\"\n\n\nif __name__ == \"__main__\":\n    app.start(host=\"0.0.0.0\", port=8080)\n"
  },
  {
    "path": "robyn/scaffold/postgres/requirements.txt",
    "content": "robyn\npsycopg2; platform_system==\"Windows\"\npsycopg2-binary; platform_system!=\"Windows\"\n"
  },
  {
    "path": "robyn/scaffold/postgres/supervisord.conf",
    "content": "[supervisord]\nnodaemon=true\n\n[program:python-app]\ncommand=python3 /workspace/app.py --log-level=DEBUG\nautostart=true\nautorestart=true\nredirect_stderr=true\n\n[program:postgres]\ncommand=postgres -c 'config_file=/etc/postgresql/postgresql.conf'\nautostart=true\nautorestart=true\nredirect_stderr=true\n"
  },
  {
    "path": "robyn/scaffold/prisma/Dockerfile",
    "content": "FROM python:3.11-bookworm\n\nWORKDIR /workspace\n\nCOPY . .\n\nRUN pip install --no-cache-dir --upgrade -r requirements.txt\n\n\nRUN python3 -m prisma generate\nRUN python3 -m prisma migrate dev --name init\n\nEXPOSE 8080\n\nCMD [\"python3\", \"app.py\", \"--log-level=DEBUG\"]\n"
  },
  {
    "path": "robyn/scaffold/prisma/app.py",
    "content": "from prisma import Prisma\nfrom prisma.models import User\n\nfrom robyn import Robyn\n\napp = Robyn(__file__)\nprisma = Prisma(auto_register=True)\n\n\n@app.startup_handler\nasync def startup_handler() -> None:\n    await prisma.connect()\n\n\n@app.shutdown_handler\nasync def shutdown_handler() -> None:\n    if prisma.is_connected():\n        await prisma.disconnect()\n\n\n@app.get(\"/\")\nasync def h():\n    user = await User.prisma().create(\n        data={\n            \"name\": \"Robert\",\n        },\n    )\n    return user.json(indent=2)\n\n\nif __name__ == \"__main__\":\n    app.start(host=\"0.0.0.0\", port=8080)\n"
  },
  {
    "path": "robyn/scaffold/prisma/requirements.txt",
    "content": "robyn\nprisma\n"
  },
  {
    "path": "robyn/scaffold/prisma/schema.prisma",
    "content": "datasource db {\n  provider = \"sqlite\"\n  url      = \"file:dev.db\"\n}\n\ngenerator py {\n  provider = \"prisma-client-py\"\n}\n\nmodel User {\n  id   String @id @default(cuid())\n  name String\n}"
  },
  {
    "path": "robyn/scaffold/sqlalchemy/Dockerfile",
    "content": "FROM python:3.11-bookworm\n\nWORKDIR /workspace\n\nCOPY . .\n\nRUN pip install --no-cache-dir --upgrade -r requirements.txt\n\n\nEXPOSE 8080\n\nCMD [\"python3\", \"app.py\", \"--log-level=DEBUG\"]\n"
  },
  {
    "path": "robyn/scaffold/sqlalchemy/__init__.py",
    "content": ""
  },
  {
    "path": "robyn/scaffold/sqlalchemy/app.py",
    "content": "from robyn import Robyn\n\napp = Robyn(__file__)\n\n\n@app.get(\"/\")\ndef index():\n    return \"Hello World!\"\n\n\nif __name__ == \"__main__\":\n    # create a configured \"Session\" class\n    app.start(host=\"0.0.0.0\", port=8080)\n"
  },
  {
    "path": "robyn/scaffold/sqlalchemy/models.py",
    "content": "from sqlalchemy import Boolean, Column, Integer, String, create_engine\nfrom sqlalchemy.orm import declarative_base, sessionmaker\n\nBase = declarative_base()\n\nengine = create_engine(\"sqlite+pysqlite:///:memory:\", echo=True)\nSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)\n\n\nclass User(Base):\n    __tablename__ = \"users\"\n\n    id = Column(Integer, primary_key=True, index=True)\n    username = Column(String, unique=True, index=True)\n    hashed_password = Column(String)\n    is_active = Column(Boolean, default=True)\n    is_superuser = Column(Boolean, default=False)\n\n\nif __name__ == \"__main__\":\n    Base.metadata.create_all(bind=engine)\n"
  },
  {
    "path": "robyn/scaffold/sqlalchemy/requirements.txt",
    "content": "robyn\nSQLAlchemy\n"
  },
  {
    "path": "robyn/scaffold/sqlite/Dockerfile",
    "content": "FROM python:3.11-bookworm\n\nWORKDIR /workspace\n\nCOPY . .\n\nRUN pip install --no-cache-dir --upgrade -r requirements.txt\n\n\nEXPOSE 8080\n\nCMD [\"python3\", \"app.py\", \"--log-level=DEBUG\"]\n"
  },
  {
    "path": "robyn/scaffold/sqlite/app.py",
    "content": "import sqlite3\n\nfrom robyn import Robyn\n\napp = Robyn(__file__)\n\n\n@app.get(\"/\")\ndef index():\n    # your db name\n    conn = sqlite3.connect(\"example.db\")\n    cur = conn.cursor()\n    cur.execute(\"DROP TABLE IF EXISTS test\")\n    cur.execute(\"CREATE TABLE test(column_1, column_2)\")\n    res = cur.execute(\"SELECT name FROM sqlite_master\")\n    th = res.fetchone()\n    table_name = th[0]\n    return f\"Hello World! {table_name}\"\n\n\nif __name__ == \"__main__\":\n    app.start(host=\"0.0.0.0\", port=8080)\n"
  },
  {
    "path": "robyn/scaffold/sqlite/requirements.txt",
    "content": "robyn\n"
  },
  {
    "path": "robyn/scaffold/sqlmodel/Dockerfile",
    "content": "FROM python:3.11-bookworm\n\nWORKDIR /workspace\n\nCOPY . .\n\nRUN pip install --no-cache-dir --upgrade -r requirements.txt\n\nEXPOSE 8080\n\nCMD [\"python3\", \"app.py\", \"--log-level=DEBUG\"]"
  },
  {
    "path": "robyn/scaffold/sqlmodel/app.py",
    "content": "from models import Hero\nfrom sqlmodel import Session, SQLModel, create_engine, select\n\nfrom robyn import Robyn\n\napp = Robyn(__file__)\n\nengine = create_engine(\"sqlite:///database.db\", echo=True)\n\n\n@app.get(\"/\")\ndef index():\n    return \"Hello World\"\n\n\n@app.get(\"/create\")\ndef create():\n    SQLModel.metadata.create_all(bind=engine)\n    return \"created tables\"\n\n\n@app.get(\"/insert\")\ndef insert():\n    hero_1 = Hero(name=\"Deadpond\", secret_name=\"Dive Wilson\")\n    hero_2 = Hero(name=\"Spider-Boy\", secret_name=\"Pedro Parqueador\")\n    hero_3 = Hero(name=\"Rusty-Man\", secret_name=\"Tommy Sharp\", age=48)\n\n    with Session(engine) as session:\n        session.add(hero_1)\n        session.add(hero_2)\n        session.add(hero_3)\n        session.commit()\n        return \"inserted\"\n\n\n@app.get(\"/select\")\ndef get_data():\n    with Session(engine) as session:\n        statement = select(Hero).where(Hero.name == \"Spider-Boy\")\n        hero = session.exec(statement).first()\n        return hero\n\n\nif __name__ == \"__main__\":\n    # create a configured \"Session\" class\n    app.start(host=\"0.0.0.0\", port=8080)\n"
  },
  {
    "path": "robyn/scaffold/sqlmodel/models.py",
    "content": "from typing import Optional\n\nfrom sqlmodel import Field, SQLModel\n\n\nclass Hero(SQLModel, table=True):\n    id: Optional[int] = Field(default=None, primary_key=True)\n    name: str\n    secret_name: str\n    age: Optional[int] = None\n"
  },
  {
    "path": "robyn/scaffold/sqlmodel/requirements.txt",
    "content": "robyn\nsqlmodel"
  },
  {
    "path": "robyn/status_codes.py",
    "content": "\"\"\"\nHTTP codes\nSee HTTP Status Code Registry:\nhttps://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml\n\nAnd RFC 2324 - https://tools.ietf.org/html/rfc2324\n\"\"\"\n\n__all__ = (\n    \"HTTP_100_CONTINUE\",\n    \"HTTP_101_SWITCHING_PROTOCOLS\",\n    \"HTTP_102_PROCESSING\",\n    \"HTTP_103_EARLY_HINTS\",\n    \"HTTP_200_OK\",\n    \"HTTP_201_CREATED\",\n    \"HTTP_202_ACCEPTED\",\n    \"HTTP_203_NON_AUTHORITATIVE_INFORMATION\",\n    \"HTTP_204_NO_CONTENT\",\n    \"HTTP_205_RESET_CONTENT\",\n    \"HTTP_206_PARTIAL_CONTENT\",\n    \"HTTP_207_MULTI_STATUS\",\n    \"HTTP_208_ALREADY_REPORTED\",\n    \"HTTP_226_IM_USED\",\n    \"HTTP_300_MULTIPLE_CHOICES\",\n    \"HTTP_301_MOVED_PERMANENTLY\",\n    \"HTTP_302_FOUND\",\n    \"HTTP_303_SEE_OTHER\",\n    \"HTTP_304_NOT_MODIFIED\",\n    \"HTTP_305_USE_PROXY\",\n    \"HTTP_306_RESERVED\",\n    \"HTTP_307_TEMPORARY_REDIRECT\",\n    \"HTTP_308_PERMANENT_REDIRECT\",\n    \"HTTP_400_BAD_REQUEST\",\n    \"HTTP_401_UNAUTHORIZED\",\n    \"HTTP_402_PAYMENT_REQUIRED\",\n    \"HTTP_403_FORBIDDEN\",\n    \"HTTP_404_NOT_FOUND\",\n    \"HTTP_405_METHOD_NOT_ALLOWED\",\n    \"HTTP_406_NOT_ACCEPTABLE\",\n    \"HTTP_407_PROXY_AUTHENTICATION_REQUIRED\",\n    \"HTTP_408_REQUEST_TIMEOUT\",\n    \"HTTP_409_CONFLICT\",\n    \"HTTP_410_GONE\",\n    \"HTTP_411_LENGTH_REQUIRED\",\n    \"HTTP_412_PRECONDITION_FAILED\",\n    \"HTTP_413_REQUEST_ENTITY_TOO_LARGE\",\n    \"HTTP_414_REQUEST_URI_TOO_LONG\",\n    \"HTTP_415_UNSUPPORTED_MEDIA_TYPE\",\n    \"HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE\",\n    \"HTTP_417_EXPECTATION_FAILED\",\n    \"HTTP_418_IM_A_TEAPOT\",\n    \"HTTP_421_MISDIRECTED_REQUEST\",\n    \"HTTP_422_UNPROCESSABLE_ENTITY\",\n    \"HTTP_423_LOCKED\",\n    \"HTTP_424_FAILED_DEPENDENCY\",\n    \"HTTP_425_TOO_EARLY\",\n    \"HTTP_426_UPGRADE_REQUIRED\",\n    \"HTTP_428_PRECONDITION_REQUIRED\",\n    \"HTTP_429_TOO_MANY_REQUESTS\",\n    \"HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE\",\n    \"HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS\",\n    \"HTTP_500_INTERNAL_SERVER_ERROR\",\n    \"HTTP_501_NOT_IMPLEMENTED\",\n    \"HTTP_502_BAD_GATEWAY\",\n    \"HTTP_503_SERVICE_UNAVAILABLE\",\n    \"HTTP_504_GATEWAY_TIMEOUT\",\n    \"HTTP_505_HTTP_VERSION_NOT_SUPPORTED\",\n    \"HTTP_506_VARIANT_ALSO_NEGOTIATES\",\n    \"HTTP_507_INSUFFICIENT_STORAGE\",\n    \"HTTP_508_LOOP_DETECTED\",\n    \"HTTP_510_NOT_EXTENDED\",\n    \"HTTP_511_NETWORK_AUTHENTICATION_REQUIRED\",\n)\n\nHTTP_100_CONTINUE = 100\nHTTP_101_SWITCHING_PROTOCOLS = 101\nHTTP_102_PROCESSING = 102\nHTTP_103_EARLY_HINTS = 103\nHTTP_200_OK = 200\nHTTP_201_CREATED = 201\nHTTP_202_ACCEPTED = 202\nHTTP_203_NON_AUTHORITATIVE_INFORMATION = 203\nHTTP_204_NO_CONTENT = 204\nHTTP_205_RESET_CONTENT = 205\nHTTP_206_PARTIAL_CONTENT = 206\nHTTP_207_MULTI_STATUS = 207\nHTTP_208_ALREADY_REPORTED = 208\nHTTP_226_IM_USED = 226\nHTTP_300_MULTIPLE_CHOICES = 300\nHTTP_301_MOVED_PERMANENTLY = 301\nHTTP_302_FOUND = 302\nHTTP_303_SEE_OTHER = 303\nHTTP_304_NOT_MODIFIED = 304\nHTTP_305_USE_PROXY = 305\nHTTP_306_RESERVED = 306\nHTTP_307_TEMPORARY_REDIRECT = 307\nHTTP_308_PERMANENT_REDIRECT = 308\nHTTP_400_BAD_REQUEST = 400\nHTTP_401_UNAUTHORIZED = 401\nHTTP_402_PAYMENT_REQUIRED = 402\nHTTP_403_FORBIDDEN = 403\nHTTP_404_NOT_FOUND = 404\nHTTP_405_METHOD_NOT_ALLOWED = 405\nHTTP_406_NOT_ACCEPTABLE = 406\nHTTP_407_PROXY_AUTHENTICATION_REQUIRED = 407\nHTTP_408_REQUEST_TIMEOUT = 408\nHTTP_409_CONFLICT = 409\nHTTP_410_GONE = 410\nHTTP_411_LENGTH_REQUIRED = 411\nHTTP_412_PRECONDITION_FAILED = 412\nHTTP_413_REQUEST_ENTITY_TOO_LARGE = 413\nHTTP_414_REQUEST_URI_TOO_LONG = 414\nHTTP_415_UNSUPPORTED_MEDIA_TYPE = 415\nHTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE = 416\nHTTP_417_EXPECTATION_FAILED = 417\nHTTP_418_IM_A_TEAPOT = 418\nHTTP_421_MISDIRECTED_REQUEST = 421\nHTTP_422_UNPROCESSABLE_ENTITY = 422\nHTTP_423_LOCKED = 423\nHTTP_424_FAILED_DEPENDENCY = 424\nHTTP_425_TOO_EARLY = 425\nHTTP_426_UPGRADE_REQUIRED = 426\nHTTP_428_PRECONDITION_REQUIRED = 428\nHTTP_429_TOO_MANY_REQUESTS = 429\nHTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 431\nHTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS = 451\nHTTP_500_INTERNAL_SERVER_ERROR = 500\nHTTP_501_NOT_IMPLEMENTED = 501\nHTTP_502_BAD_GATEWAY = 502\nHTTP_503_SERVICE_UNAVAILABLE = 503\nHTTP_504_GATEWAY_TIMEOUT = 504\nHTTP_505_HTTP_VERSION_NOT_SUPPORTED = 505\nHTTP_506_VARIANT_ALSO_NEGOTIATES = 506\nHTTP_507_INSUFFICIENT_STORAGE = 507\nHTTP_508_LOOP_DETECTED = 508\nHTTP_510_NOT_EXTENDED = 510\nHTTP_511_NETWORK_AUTHENTICATION_REQUIRED = 511\n"
  },
  {
    "path": "robyn/swagger.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>Robyn OpenAPI Docs</title>\n    <link\n      rel=\"stylesheet\"\n      type=\"text/css\"\n      href=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui.css\"\n    />\n    <link\n      rel=\"icon\"\n      type=\"image/png\"\n      href=\"https://user-images.githubusercontent.com/29942790/140995889-5d91dcff-3aa7-4cfb-8a90-2cddf1337dca.png\"\n    />\n    <script src=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js\"></script>\n    <script src=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui-standalone-preset.js\"></script>\n  </head>\n  <body>\n    <div id=\"swagger-ui\"></div>\n    <script>\n      window.onload = function () {\n        SwaggerUIBundle({\n          url: \"/openapi.json\",\n          dom_id: \"#swagger-ui\",\n          deepLinking: true,\n          presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],\n          layout: \"StandaloneLayout\",\n        });\n      };\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "robyn/templating.py",
    "content": "from abc import ABC, abstractmethod\n\nfrom jinja2 import Environment, FileSystemLoader\n\nfrom robyn import status_codes\n\nfrom .robyn import Headers, Response\n\n\nclass TemplateInterface(ABC):\n    def __init__(self): ...\n\n    @abstractmethod\n    def render_template(self, *args, **kwargs) -> Response: ...\n\n\nclass JinjaTemplate(TemplateInterface):\n    def __init__(self, directory, encoding=\"utf-8\", followlinks=False):\n        self.env = Environment(loader=FileSystemLoader(searchpath=directory, encoding=encoding, followlinks=followlinks))\n\n    def render_template(self, template_name, **kwargs) -> Response:\n        rendered_template = self.env.get_template(template_name).render(**kwargs)\n        return Response(\n            status_code=status_codes.HTTP_200_OK,\n            description=rendered_template,\n            headers=Headers({\"Content-Type\": \"text/html; charset=utf-8\"}),\n        )\n\n\n__all__ = [\"TemplateInterface\", \"JinjaTemplate\"]\n"
  },
  {
    "path": "robyn/types.py",
    "content": "from dataclasses import dataclass\nfrom typing import Dict, NewType, Optional, TypedDict\n\nfrom robyn._param_utils import QueryParamValidationError\n\n\n@dataclass\nclass Directory:\n    route: str\n    directory_path: str\n    show_files_listing: bool\n    index_file: Optional[str]\n\n    def as_list(self):\n        return [\n            self.route,\n            self.directory_path,\n            self.show_files_listing,\n            self.index_file,\n        ]\n\n\nPathParams = NewType(\"PathParams\", Dict[str, str])\nMethod = NewType(\"Method\", str)\nFormData = NewType(\"FormData\", Dict[str, str])\nFiles = NewType(\"Files\", Dict[str, bytes])\nIPAddress = NewType(\"IPAddress\", Optional[str])\n\n\nclass JSONResponse(TypedDict):\n    \"\"\"\n    A type alias for openapi response bodies. This class should be inherited by the response class type definition.\n    \"\"\"\n\n    pass\n\n\nclass Body:\n    \"\"\"\n    A type alias for openapi request bodies. This class should be inherited by the request body class annotation.\n    \"\"\"\n\n    pass\n\n\nclass JsonBody:\n    \"\"\"\n    A type alias for JSON request bodies. When used as a parameter type annotation,\n    the handler receives the parsed JSON (dict) from request.json() and the OpenAPI\n    docs will show a generic JSON request body input.\n\n    Can be subclassed with annotations to provide a typed schema in the OpenAPI docs:\n\n        class MyBody(JsonBody):\n            name: str\n            age: int\n\n        @app.post(\"/users\")\n        def create_user(request: Request, data: MyBody):\n            # data is the parsed JSON dict\n            ...\n\n    .. note::\n\n        The JSON body is parsed via ``request.json()`` during parameter\n        resolution, *before* the handler is invoked.  If the request body is\n        not valid JSON, a 400 Bad Request response is returned automatically\n        with a JSON error message (e.g., ``{\"error\": \"Invalid JSON body: ...\"}``)\n        and the handler is never called.  Because parsing happens before\n        handler invocation, the error **cannot** be caught with a try/except\n        inside the handler.\n\n        If you need custom error handling for malformed JSON, accept the raw\n        body instead (e.g., ``body: Body``) and call ``request.json()``\n        yourself inside a try/except block.\n    \"\"\"\n\n    pass\n\n\n__all__ = [\"JSONResponse\", \"Body\", \"JsonBody\", \"QueryParamValidationError\", \"Directory\", \"PathParams\", \"Method\", \"FormData\", \"Files\", \"IPAddress\"]\n"
  },
  {
    "path": "robyn/ws.py",
    "content": "from __future__ import annotations\n\nimport asyncio\nimport inspect\nimport logging\n\nimport orjson\n\nfrom robyn._param_utils import QueryParamValidationError, resolve_individual_params\nfrom robyn.robyn import FunctionInfo, QueryParams, WebSocketConnector\n\n_logger = logging.getLogger(__name__)\n\n\nclass WebSocketDisconnect(Exception):\n    \"\"\"Exception raised when a WebSocket connection is disconnected.\"\"\"\n\n    def __init__(self, code: int = 1000, reason: str = \"\"):\n        self.code = code\n        self.reason = reason\n        super().__init__(f\"WebSocket disconnected with code {code}: {reason}\")\n\n\nclass WebSocketAdapter:\n    \"\"\"\n    Modern WebSocket interface backed by Rust channels.\n\n    Wraps a WebSocketConnector and a Rust WebSocketChannel to provide\n    a clean async API for WebSocket handlers.\n    \"\"\"\n\n    def __init__(self, websocket_connector: WebSocketConnector, channel=None):\n        self._connector = websocket_connector\n        self._channel = channel\n\n    async def receive_text(self) -> str:\n        \"\"\"Receive the next text message. Blocks until a message arrives.\n        Raises WebSocketDisconnect when the connection is closed.\"\"\"\n        if self._channel is None:\n            raise WebSocketDisconnect(reason=\"No message channel available\")\n        result = await self._channel.receive()\n        if result is None:\n            raise WebSocketDisconnect()\n        return result\n\n    async def receive_bytes(self) -> bytes:\n        \"\"\"Receive binary data (decoded from text).\"\"\"\n        text = await self.receive_text()\n        return text.encode(\"utf-8\")\n\n    async def receive_json(self):\n        \"\"\"Receive and decode JSON data.\n        Raises WebSocketDisconnect when the connection is closed.\"\"\"\n        text = await self.receive_text()\n        return orjson.loads(text)\n\n    async def send_text(self, data: str):\n        \"\"\"Send text data to this WebSocket client.\"\"\"\n        await self._connector.async_send_to(self._connector.id, data)\n\n    async def send_bytes(self, data: bytes):\n        \"\"\"Send binary data (as text) to this WebSocket client.\"\"\"\n        await self._connector.async_send_to(self._connector.id, data.decode(\"utf-8\"))\n\n    async def send_json(self, data):\n        \"\"\"Send JSON data to this WebSocket client.\"\"\"\n        await self.send_text(orjson.dumps(data).decode())\n\n    async def broadcast(self, data: str):\n        \"\"\"Broadcast text data to all connected WebSocket clients on this endpoint.\"\"\"\n        await self._connector.async_broadcast(data)\n\n    async def close(self):\n        \"\"\"Close the WebSocket connection.\"\"\"\n        self._connector.close()\n\n    @property\n    def id(self) -> str:\n        \"\"\"WebSocket connection ID.\"\"\"\n        return self._connector.id\n\n    @property\n    def query_params(self):\n        \"\"\"Access query parameters from the connection URL.\"\"\"\n        return self._connector.query_params\n\n\n# Global storage for connection state (per-connection queues and tasks)\n_connection_tasks: dict[str, asyncio.Task] = {}\n\n\ndef create_websocket_decorator(app_instance):\n    \"\"\"\n    Factory function to create a websocket decorator for an app instance.\n    Returns a decorator that registers a modern WebSocket endpoint\n    backed by Rust channels.\n    \"\"\"\n\n    def websocket(endpoint: str):\n        \"\"\"\n        Modern WebSocket decorator.\n\n        Usage:\n            @app.websocket(\"/ws\")\n            async def handler(websocket):\n                while True:\n                    msg = await websocket.receive_text()\n                    await websocket.send_text(f\"Echo: {msg}\")\n\n            @handler.on_connect\n            def on_connect(websocket):\n                return \"Welcome!\"\n\n            @handler.on_close\n            def on_close(websocket):\n                return \"Goodbye\"\n        \"\"\"\n\n        def decorator(handler):\n            _on_connect_fn = None\n            _on_close_fn = None\n\n            def _resolve_ws_params(func_params, adapter):\n                \"\"\"\n                Resolve all handler params beyond the first positional (websocket).\n\n                Handles:\n                  - global_dependencies / router_dependencies (DI)\n                  - query_params (whole QueryParams object, by name or type annotation)\n                  - individual query params (everything else, with type coercion)\n                \"\"\"\n                injected = app_instance.dependencies.get_dependency_map(app_instance)\n                resolved = {}\n                unresolved = {}\n\n                for idx, (param_name, param) in enumerate(func_params.items()):\n                    # Skip the websocket adapter (first positional arg, passed separately)\n                    if idx == 0 or param.annotation is WebSocketAdapter:\n                        continue\n\n                    # DI: global_dependencies\n                    if param_name == \"global_dependencies\":\n                        resolved[param_name] = injected.get(\"global_dependencies\", {})\n                        continue\n\n                    # DI: router_dependencies\n                    if param_name == \"router_dependencies\":\n                        resolved[param_name] = injected.get(\"router_dependencies\", {})\n                        continue\n\n                    # Whole QueryParams object (by type annotation or reserved name)\n                    if param.annotation is QueryParams or param_name == \"query_params\":\n                        resolved[param_name] = adapter.query_params\n                        continue\n\n                    # Everything else: individual query param\n                    unresolved[param_name] = param\n\n                if unresolved:\n                    individual = resolve_individual_params(\n                        unresolved,\n                        adapter.query_params,\n                        path_params=None,  # WebSocket has no path params yet\n                        route_param_names=set(),\n                    )\n                    resolved.update(individual)\n\n                return resolved\n\n            # --- Connect handler (called by Rust on connection open) ---\n            async def connect_handler(ws):\n                \"\"\"Internal connect handler called by Rust.\n                Creates the adapter, starts the user's handler task,\n                and calls the user's on_connect callback.\"\"\"\n                conn_id = ws.id\n                channel = ws.message_channel\n\n                # Create the adapter with the Rust channel\n                adapter = WebSocketAdapter(ws, channel)\n\n                # Build resolved kwargs for the main handler\n                try:\n                    handler_params = inspect.signature(handler).parameters\n                    handler_kwargs = _resolve_ws_params(handler_params, adapter)\n                except QueryParamValidationError as e:\n                    _logger.warning(\"WebSocket connection rejected for %s: %s\", endpoint, e.detail)\n                    return f\"Error: {e.detail}\"\n\n                # Start the user's handler as a long-running asyncio task\n                async def _run_handler():\n                    try:\n                        await handler(adapter, **handler_kwargs)\n                    except WebSocketDisconnect:\n                        pass\n                    except ConnectionError:\n                        _logger.debug(\"Connection lost in WebSocket handler for %s\", endpoint, exc_info=True)\n                    except Exception:\n                        _logger.exception(\"Error in WebSocket handler for %s\", endpoint)\n                    finally:\n                        _connection_tasks.pop(conn_id, None)\n\n                task = asyncio.create_task(_run_handler())\n                _connection_tasks[conn_id] = task\n\n                # Call user's on_connect if defined\n                if _on_connect_fn is not None:\n                    connect_adapter = WebSocketAdapter(ws, channel)\n                    try:\n                        connect_params = inspect.signature(_on_connect_fn).parameters\n                        connect_kwargs = _resolve_ws_params(connect_params, connect_adapter)\n                    except QueryParamValidationError as e:\n                        _logger.warning(\"WebSocket on_connect rejected for %s: %s\", endpoint, e.detail)\n                        task = _connection_tasks.pop(conn_id, None)\n                        if task is not None and not task.done():\n                            task.cancel()\n                        return f\"Error: {e.detail}\"\n                    if asyncio.iscoroutinefunction(_on_connect_fn):\n                        result = await _on_connect_fn(connect_adapter, **connect_kwargs)\n                    else:\n                        result = _on_connect_fn(connect_adapter, **connect_kwargs)\n                    return result\n\n                return None\n\n            # --- Message handler (dummy for new-style; Rust pushes to channel instead) ---\n            async def message_handler(ws, msg):\n                \"\"\"Dummy message handler. In channel mode, Rust pushes messages\n                directly to the channel and never calls this.\"\"\"\n                return None\n\n            # --- Close handler (called by Rust on connection close) ---\n            async def close_handler(ws):\n                \"\"\"Internal close handler called by Rust.\n                Waits for the handler task to finish and calls on_close.\"\"\"\n                conn_id = ws.id\n\n                # Wait for the handler task to finish (it should exit because\n                # the channel was closed by Rust, triggering WebSocketDisconnect)\n                task = _connection_tasks.pop(conn_id, None)\n                if task is not None:\n                    try:\n                        await asyncio.wait_for(task, timeout=5.0)\n                    except (asyncio.TimeoutError, asyncio.CancelledError):\n                        if not task.done():\n                            task.cancel()\n                    except Exception:\n                        _logger.debug(\"Unexpected error while awaiting handler task for %s\", conn_id, exc_info=True)\n                        if not task.done():\n                            task.cancel()\n\n                # Call user's on_close if defined\n                if _on_close_fn is not None:\n                    close_adapter = WebSocketAdapter(ws, None)\n                    try:\n                        close_params = inspect.signature(_on_close_fn).parameters\n                        close_kwargs = _resolve_ws_params(close_params, close_adapter)\n                    except QueryParamValidationError as e:\n                        _logger.warning(\"WebSocket on_close param error for %s: %s\", endpoint, e.detail)\n                        return None\n                    if asyncio.iscoroutinefunction(_on_close_fn):\n                        result = await _on_close_fn(close_adapter, **close_kwargs)\n                    else:\n                        result = _on_close_fn(close_adapter, **close_kwargs)\n                    return result\n\n                return None\n\n            # --- Build FunctionInfo objects for Rust ---\n            handlers = {}\n\n            # Connect handler FunctionInfo\n            connect_params = dict(inspect.signature(connect_handler).parameters)\n            handlers[\"connect\"] = FunctionInfo(\n                connect_handler,\n                True,  # is_async\n                len(connect_params),\n                connect_params,\n                {},  # no kwargs needed - DI handled in Python\n            )\n\n            # Message handler FunctionInfo (dummy, won't be called in channel mode)\n            message_params = dict(inspect.signature(message_handler).parameters)\n            handlers[\"message\"] = FunctionInfo(\n                message_handler,\n                True,\n                len(message_params),\n                message_params,\n                {},\n            )\n\n            # Close handler FunctionInfo\n            close_params = dict(inspect.signature(close_handler).parameters)\n            handlers[\"close\"] = FunctionInfo(\n                close_handler,\n                True,\n                len(close_params),\n                close_params,\n                {},\n            )\n\n            # --- Decorator methods for on_connect / on_close ---\n            def add_on_connect(connect_fn):\n                nonlocal _on_connect_fn\n                _on_connect_fn = connect_fn\n                return connect_fn\n\n            def add_on_close(close_fn):\n                nonlocal _on_close_fn\n                _on_close_fn = close_fn\n                return close_fn\n\n            handler.on_connect = add_on_connect\n            handler.on_close = add_on_close\n\n            # Register with the app\n            app_instance.add_web_socket(endpoint, handlers)\n\n            return handler\n\n        return decorator\n\n    return websocket\n"
  },
  {
    "path": "scripts/format.sh",
    "content": "#!/bin/bash\n\nset -x\n\nruff format robyn integration_tests docs_src\n"
  },
  {
    "path": "scripts/release.sh",
    "content": "#!/bin/bash\n\nset -x\n\nmaturin develop\npoetry lock \n"
  },
  {
    "path": "setup.py",
    "content": "import sys\n\nsys.stderr.write(\n    \"\"\"\n===============================\nUnsupported installation method\n===============================\nrobyn doesn't support installation with `python setup.py install`.\nPlease use `python -m pip install .` instead.\n\"\"\"\n)\nsys.exit(1)\n"
  },
  {
    "path": "src/asyncio.rs",
    "content": "use pyo3::{prelude::*, sync::PyOnceLock};\nuse std::convert::Into;\n\nstatic CONTEXTVARS: PyOnceLock<Py<PyAny>> = PyOnceLock::new();\nstatic CONTEXT: PyOnceLock<Py<PyAny>> = PyOnceLock::new();\n\nfn contextvars(py: Python<'_>) -> PyResult<&Bound<'_, PyAny>> {\n    Ok(CONTEXTVARS\n        .get_or_try_init(py, || py.import(\"contextvars\").map(Into::into))?\n        .bind(py))\n}\n\n#[allow(dead_code)]\npub(crate) fn empty_context(py: Python<'_>) -> PyResult<&Bound<'_, PyAny>> {\n    Ok(CONTEXT\n        .get_or_try_init(py, || {\n            contextvars(py)?\n                .getattr(\"Context\")?\n                .call0()\n                .map(std::convert::Into::into)\n        })?\n        .bind(py))\n}\n\n#[inline(always)]\npub(crate) fn copy_context(py: Python) -> PyResult<Py<PyAny>> {\n    unsafe {\n        let ptr = pyo3::ffi::PyContext_CopyCurrent();\n        Ok(Bound::from_owned_ptr_or_err(py, ptr)?.unbind())\n    }\n}\n"
  },
  {
    "path": "src/blocking.rs",
    "content": "use crossbeam_channel as channel;\nuse pyo3::prelude::*;\nuse std::{\n    sync::{atomic, Arc},\n    thread, time,\n};\n\npub(crate) struct BlockingTask {\n    inner: Box<dyn FnOnce(Python) + Send + 'static>,\n}\n\nimpl BlockingTask {\n    #[inline]\n    pub fn new<T>(inner: T) -> BlockingTask\n    where\n        T: FnOnce(Python) + Send + 'static,\n    {\n        Self {\n            inner: Box::new(inner),\n        }\n    }\n\n    #[inline(always)]\n    pub fn run(self, py: Python) {\n        (self.inner)(py);\n    }\n}\n\npub(crate) enum BlockingRunner {\n    Empty,\n    Mono(BlockingRunnerMono),\n    Pool(BlockingRunnerPool),\n}\n\nimpl BlockingRunner {\n    pub fn new(max_threads: usize, idle_timeout: u64) -> Self {\n        match max_threads {\n            0 => Self::Empty,\n            1 => Self::Mono(BlockingRunnerMono::new()),\n            _ => Self::Pool(BlockingRunnerPool::new(max_threads, idle_timeout)),\n        }\n    }\n\n    #[inline]\n    pub fn run<T>(&self, task: T) -> Result<(), channel::SendError<BlockingTask>>\n    where\n        T: FnOnce(Python) + Send + 'static,\n    {\n        match self {\n            Self::Mono(runner) => runner.run(task),\n            Self::Pool(runner) => runner.run(task),\n            Self::Empty => Ok(()),\n        }\n    }\n}\n\npub(crate) struct BlockingRunnerMono {\n    queue: channel::Sender<BlockingTask>,\n}\n\nimpl BlockingRunnerMono {\n    pub fn new() -> Self {\n        let (qtx, qrx) = channel::unbounded();\n        let ret = Self { queue: qtx };\n        thread::spawn(move || blocking_worker(qrx));\n\n        ret\n    }\n\n    #[inline]\n    pub fn run<T>(&self, task: T) -> Result<(), channel::SendError<BlockingTask>>\n    where\n        T: FnOnce(Python) + Send + 'static,\n    {\n        self.queue.send(BlockingTask::new(task))\n    }\n}\n\npub(crate) struct BlockingRunnerPool {\n    birth: time::Instant,\n    queue: channel::Sender<BlockingTask>,\n    tq: channel::Receiver<BlockingTask>,\n    threads: Arc<atomic::AtomicUsize>,\n    tmax: usize,\n    idle_timeout: time::Duration,\n    spawning: atomic::AtomicBool,\n    spawn_tick: atomic::AtomicU64,\n}\n\nimpl BlockingRunnerPool {\n    pub fn new(max_threads: usize, idle_timeout: u64) -> Self {\n        let (qtx, qrx) = channel::unbounded();\n        let ret = Self {\n            queue: qtx,\n            tq: qrx.clone(),\n            threads: Arc::new(1.into()),\n            tmax: max_threads,\n            birth: time::Instant::now(),\n            spawning: false.into(),\n            spawn_tick: 0.into(),\n            idle_timeout: time::Duration::from_secs(idle_timeout),\n        };\n\n        // always spawn the first thread\n        thread::spawn(move || blocking_worker(qrx));\n\n        ret\n    }\n\n    #[inline(always)]\n    fn spawn_thread(&self) {\n        let tick = self.birth.elapsed().as_micros() as u64;\n        if tick - self.spawn_tick.load(atomic::Ordering::Relaxed) < 350 {\n            return;\n        }\n        if self\n            .spawning\n            .compare_exchange(\n                false,\n                true,\n                atomic::Ordering::Relaxed,\n                atomic::Ordering::Relaxed,\n            )\n            .is_err()\n        {\n            return;\n        }\n\n        let queue = self.tq.clone();\n        let tcount = self.threads.clone();\n        let timeout = self.idle_timeout;\n        thread::spawn(move || {\n            tcount.fetch_add(1, atomic::Ordering::Release);\n            blocking_worker_idle(queue, timeout);\n            tcount.fetch_sub(1, atomic::Ordering::Release);\n        });\n\n        self.spawn_tick.store(\n            self.birth.elapsed().as_micros() as u64,\n            atomic::Ordering::Relaxed,\n        );\n        self.spawning.store(false, atomic::Ordering::Relaxed);\n    }\n\n    #[inline]\n    pub fn run<T>(&self, task: T) -> Result<(), channel::SendError<BlockingTask>>\n    where\n        T: FnOnce(Python) + Send + 'static,\n    {\n        self.queue.send(BlockingTask::new(task))?;\n        if self.queue.len() > 1 && self.threads.load(atomic::Ordering::Acquire) < self.tmax {\n            self.spawn_thread();\n        }\n        Ok(())\n    }\n}\n\nfn blocking_worker(queue: channel::Receiver<BlockingTask>) {\n    Python::attach(|py| {\n        while let Ok(task) = py.detach(|| queue.recv()) {\n            task.run(py);\n        }\n    });\n}\n\nfn blocking_worker_idle(queue: channel::Receiver<BlockingTask>, timeout: time::Duration) {\n    Python::attach(|py| {\n        while let Ok(task) = py.detach(|| queue.recv_timeout(timeout)) {\n            task.run(py);\n        }\n    });\n}\n"
  },
  {
    "path": "src/callbacks.rs",
    "content": "use pyo3::{exceptions::PyStopIteration, prelude::*, IntoPyObjectExt};\nuse std::sync::{atomic, Arc, OnceLock, RwLock};\nuse tokio::sync::Notify;\n\nuse crate::conversion::FutureResultToPy;\n\n#[pyclass(frozen, freelist = 128, module = \"robyn._robyn\")]\npub(crate) struct PyEmptyAwaitable;\n\n#[pymethods]\nimpl PyEmptyAwaitable {\n    fn __await__(pyself: PyRef<'_, Self>) -> PyRef<'_, Self> {\n        pyself\n    }\n\n    fn __iter__(pyself: PyRef<'_, Self>) -> PyRef<'_, Self> {\n        pyself\n    }\n\n    fn __next__(&self) -> Option<()> {\n        None\n    }\n}\n\n#[pyclass(frozen, module = \"robyn._robyn\")]\npub(crate) struct PyDoneAwaitable {\n    result: PyResult<Py<PyAny>>,\n}\n\nimpl PyDoneAwaitable {\n    pub(crate) fn new(result: PyResult<Py<PyAny>>) -> Self {\n        Self { result }\n    }\n}\n\n#[pymethods]\nimpl PyDoneAwaitable {\n    fn __await__(pyself: PyRef<'_, Self>) -> PyRef<'_, Self> {\n        pyself\n    }\n\n    fn __iter__(pyself: PyRef<'_, Self>) -> PyRef<'_, Self> {\n        pyself\n    }\n\n    fn __next__(&self, py: Python) -> PyResult<Py<PyAny>> {\n        self.result\n            .as_ref()\n            .map_err(|v| v.clone_ref(py))\n            .map(|v| Err(PyStopIteration::new_err(v.clone_ref(py))))?\n    }\n}\n\n#[pyclass(frozen, module = \"robyn._robyn\")]\npub(crate) struct PyErrAwaitable {\n    err: PyErr,\n}\n\nimpl PyErrAwaitable {\n    pub(crate) fn new(err: PyErr) -> Self {\n        Self { err }\n    }\n}\n\n#[pymethods]\nimpl PyErrAwaitable {\n    fn __await__(pyself: PyRef<'_, Self>) -> PyRef<'_, Self> {\n        pyself\n    }\n\n    fn __iter__(pyself: PyRef<'_, Self>) -> PyRef<'_, Self> {\n        pyself\n    }\n\n    fn __next__(&self, py: Python) -> PyResult<()> {\n        Err(self.err.clone_ref(py))\n    }\n}\n\n#[pyclass(frozen, module = \"robyn._robyn\")]\npub(crate) struct PyIterAwaitable {\n    result: OnceLock<PyResult<Py<PyAny>>>,\n}\n\nimpl PyIterAwaitable {\n    pub(crate) fn new() -> Self {\n        Self {\n            result: OnceLock::new(),\n        }\n    }\n\n    #[inline]\n    pub(crate) fn set_result(pyself: Py<Self>, py: Python, result: FutureResultToPy) {\n        _ = pyself\n            .get()\n            .result\n            .set(result.into_pyobject(py).map(Bound::unbind));\n        pyself.drop_ref(py);\n    }\n}\n\n#[pymethods]\nimpl PyIterAwaitable {\n    fn __await__(pyself: PyRef<'_, Self>) -> PyRef<'_, Self> {\n        pyself\n    }\n\n    fn __iter__(pyself: PyRef<'_, Self>) -> PyRef<'_, Self> {\n        pyself\n    }\n\n    fn __next__(&self, py: Python) -> PyResult<Option<Py<PyAny>>> {\n        if let Some(res) = self.result.get() {\n            return res\n                .as_ref()\n                .map_err(|err| err.clone_ref(py))\n                .map(|v| Err(PyStopIteration::new_err(v.clone_ref(py))))?;\n        }\n\n        Ok(Some(py.None()))\n    }\n}\n\n#[repr(u8)]\nenum PyFutureAwaitableState {\n    Pending = 0,\n    Completed = 1,\n    Cancelled = 2,\n}\n\n#[pyclass(frozen, module = \"robyn._robyn\")]\npub(crate) struct PyFutureAwaitable {\n    state: atomic::AtomicU8,\n    result: OnceLock<PyResult<Py<PyAny>>>,\n    event_loop: Py<PyAny>,\n    cancel_tx: Arc<Notify>,\n    cancel_msg: OnceLock<Py<PyAny>>,\n    py_block: atomic::AtomicBool,\n    ack: RwLock<Option<(Py<PyAny>, Py<pyo3::types::PyDict>)>>,\n}\n\nimpl PyFutureAwaitable {\n    pub(crate) fn new(event_loop: Py<PyAny>) -> Self {\n        Self {\n            state: atomic::AtomicU8::new(PyFutureAwaitableState::Pending as u8),\n            result: OnceLock::new(),\n            event_loop,\n            cancel_tx: Arc::new(Notify::new()),\n            cancel_msg: OnceLock::new(),\n            py_block: true.into(),\n            ack: RwLock::new(None),\n        }\n    }\n\n    pub fn to_spawn(self, py: Python) -> PyResult<(Py<PyFutureAwaitable>, Arc<Notify>)> {\n        let cancel_tx = self.cancel_tx.clone();\n        Ok((Py::new(py, self)?, cancel_tx))\n    }\n\n    pub(crate) fn set_result(pyself: Py<Self>, py: Python, result: FutureResultToPy) {\n        let rself = pyself.get();\n\n        _ = rself\n            .result\n            .set(result.into_pyobject(py).map(Bound::unbind));\n        if rself\n            .state\n            .compare_exchange(\n                PyFutureAwaitableState::Pending as u8,\n                PyFutureAwaitableState::Completed as u8,\n                atomic::Ordering::Release,\n                atomic::Ordering::Relaxed,\n            )\n            .is_err()\n        {\n            pyself.drop_ref(py);\n            return;\n        }\n\n        {\n            let ack = rself.ack.read().unwrap();\n            if let Some((cb, ctx)) = &*ack {\n                _ = rself.event_loop.clone_ref(py).call_method(\n                    py,\n                    pyo3::intern!(py, \"call_soon_threadsafe\"),\n                    (cb, pyself.clone_ref(py)),\n                    Some(ctx.bind(py)),\n                );\n            }\n        }\n        pyself.drop_ref(py);\n    }\n}\n\n#[pymethods]\nimpl PyFutureAwaitable {\n    fn __await__(pyself: PyRef<'_, Self>) -> PyRef<'_, Self> {\n        pyself\n    }\n    fn __iter__(pyself: PyRef<'_, Self>) -> PyRef<'_, Self> {\n        pyself\n    }\n\n    fn __next__(pyself: PyRef<'_, Self>) -> PyResult<Option<PyRef<'_, Self>>> {\n        if pyself.state.load(atomic::Ordering::Acquire) == PyFutureAwaitableState::Completed as u8 {\n            let py = pyself.py();\n            return pyself\n                .result\n                .get()\n                .unwrap()\n                .as_ref()\n                .map_err(|err| err.clone_ref(py))\n                .map(|v| Err(PyStopIteration::new_err(v.clone_ref(py))))?;\n        }\n\n        Ok(Some(pyself))\n    }\n\n    #[getter(_asyncio_future_blocking)]\n    fn get_block(&self) -> bool {\n        self.py_block.load(atomic::Ordering::Relaxed)\n    }\n\n    #[setter(_asyncio_future_blocking)]\n    fn set_block(&self, val: bool) {\n        self.py_block.store(val, atomic::Ordering::Relaxed);\n    }\n\n    fn get_loop(&self, py: Python) -> Py<PyAny> {\n        self.event_loop.clone_ref(py)\n    }\n\n    /// Single-callback optimization: only the most recent callback is stored.\n    /// This is intentional — this type is internal to the robyn runtime and in\n    /// practice asyncio registers at most one done-callback per future.\n    #[pyo3(signature = (cb, context=None))]\n    fn add_done_callback(\n        pyself: PyRef<'_, Self>,\n        cb: Py<PyAny>,\n        context: Option<Py<PyAny>>,\n    ) -> PyResult<()> {\n        let py = pyself.py();\n        let kwctx = pyo3::types::PyDict::new(py);\n        kwctx.set_item(pyo3::intern!(py, \"context\"), context)?;\n\n        let state = pyself.state.load(atomic::Ordering::Acquire);\n        if state == PyFutureAwaitableState::Pending as u8 {\n            let mut ack = pyself.ack.write().unwrap();\n            *ack = Some((cb, kwctx.unbind()));\n        } else {\n            let event_loop = pyself.event_loop.clone_ref(py);\n            event_loop.call_method(\n                py,\n                pyo3::intern!(py, \"call_soon\"),\n                (cb, pyself),\n                Some(&kwctx),\n            )?;\n        }\n\n        Ok(())\n    }\n\n    /// Clears the single stored callback (see `add_done_callback`).\n    /// The `cb` argument is accepted for asyncio protocol compatibility but\n    /// is not used for matching — the sole stored callback is always removed.\n    #[allow(unused)]\n    fn remove_done_callback(&self, cb: Py<PyAny>) -> i32 {\n        let mut ack = self.ack.write().unwrap();\n        if ack.is_some() {\n            *ack = None;\n            1\n        } else {\n            0\n        }\n    }\n\n    #[allow(unused)]\n    #[pyo3(signature = (msg=None))]\n    fn cancel(pyself: PyRef<'_, Self>, msg: Option<Py<PyAny>>) -> bool {\n        if pyself\n            .state\n            .compare_exchange(\n                PyFutureAwaitableState::Pending as u8,\n                PyFutureAwaitableState::Cancelled as u8,\n                atomic::Ordering::Release,\n                atomic::Ordering::Relaxed,\n            )\n            .is_err()\n        {\n            return false;\n        }\n\n        if let Some(cancel_msg) = msg {\n            _ = pyself.cancel_msg.set(cancel_msg);\n        }\n        pyself.cancel_tx.notify_one();\n\n        let ack = pyself.ack.read().unwrap();\n        if let Some((cb, ctx)) = &*ack {\n            let py = pyself.py();\n            let event_loop = pyself.event_loop.clone_ref(py);\n            let cb = cb.clone_ref(py);\n            let ctx = ctx.clone_ref(py);\n            drop(ack);\n\n            let _ = event_loop.call_method(\n                py,\n                pyo3::intern!(py, \"call_soon\"),\n                (cb, pyself),\n                Some(ctx.bind(py)),\n            );\n        }\n\n        true\n    }\n\n    fn done(&self) -> bool {\n        self.state.load(atomic::Ordering::Acquire) != PyFutureAwaitableState::Pending as u8\n    }\n\n    fn result(&self, py: Python) -> PyResult<Py<PyAny>> {\n        let state = self.state.load(atomic::Ordering::Acquire);\n\n        if state == PyFutureAwaitableState::Completed as u8 {\n            return self\n                .result\n                .get()\n                .unwrap()\n                .as_ref()\n                .map(|v| v.clone_ref(py))\n                .map_err(|err| err.clone_ref(py));\n        }\n        if state == PyFutureAwaitableState::Cancelled as u8 {\n            let msg = self\n                .cancel_msg\n                .get()\n                .unwrap_or(&\"Future cancelled.\".into_py_any(py).unwrap())\n                .clone_ref(py);\n            return Err(pyo3::exceptions::asyncio::CancelledError::new_err(msg));\n        }\n        Err(pyo3::exceptions::asyncio::InvalidStateError::new_err(\n            \"Result is not ready.\",\n        ))\n    }\n\n    fn exception(&self, py: Python) -> PyResult<Py<PyAny>> {\n        let state = self.state.load(atomic::Ordering::Acquire);\n\n        if state == PyFutureAwaitableState::Completed as u8 {\n            return self\n                .result\n                .get()\n                .unwrap()\n                .as_ref()\n                .map(|_| py.None())\n                .map_err(|err| err.clone_ref(py));\n        }\n        if state == PyFutureAwaitableState::Cancelled as u8 {\n            let msg = self\n                .cancel_msg\n                .get()\n                .unwrap_or(&\"Future cancelled.\".into_py_any(py).unwrap())\n                .clone_ref(py);\n            return Err(pyo3::exceptions::asyncio::CancelledError::new_err(msg));\n        }\n        Err(pyo3::exceptions::asyncio::InvalidStateError::new_err(\n            \"Exception is not set.\",\n        ))\n    }\n}\n\n#[pyclass(frozen)]\npub(crate) struct PyFutureDoneCallback {\n    pub cancel_tx: Arc<Notify>,\n}\n\n#[pymethods]\nimpl PyFutureDoneCallback {\n    pub fn __call__(&self, fut: Bound<PyAny>) -> PyResult<()> {\n        let py = fut.py();\n\n        if {\n            fut.getattr(pyo3::intern!(py, \"cancelled\"))?\n                .call0()?\n                .is_truthy()\n        }\n        .unwrap_or(false)\n        {\n            self.cancel_tx.notify_one();\n        }\n\n        Ok(())\n    }\n}\n\n#[pyclass(frozen)]\npub(crate) struct PyFutureResultSetter;\n\n#[pymethods]\nimpl PyFutureResultSetter {\n    pub fn __call__(&self, target: Bound<PyAny>, value: Bound<PyAny>) {\n        let _ = target.call1((value,));\n    }\n}\n"
  },
  {
    "path": "src/conversion.rs",
    "content": "use pyo3::prelude::*;\n\n// Adapted for robyn - returns Py<PyAny> directly\npub(crate) enum FutureResultToPy {\n    None,\n    Err(PyResult<()>),\n    Value(Py<PyAny>),\n}\n\nimpl<'p> IntoPyObject<'p> for FutureResultToPy {\n    type Target = PyAny;\n    type Output = Bound<'p, Self::Target>;\n    type Error = PyErr;\n\n    fn into_pyobject(self, py: Python<'p>) -> Result<Self::Output, Self::Error> {\n        match self {\n            Self::None => Ok(py.None().into_bound(py)),\n            Self::Err(res) => Err(res.err().unwrap()),\n            Self::Value(val) => {\n                let bound = val.bind(py);\n                Ok(bound.clone())\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/executors/mod.rs",
    "content": "#[deny(clippy::if_same_then_else)]\npub mod web_socket_executors;\n\nuse std::sync::Arc;\n\nuse anyhow::Result;\nuse pyo3::prelude::*;\nuse pyo3::{BoundObject, IntoPyObject};\nuse pyo3_async_runtimes::TaskLocals;\n\nuse crate::types::{\n    function_info::FunctionInfo,\n    request::Request,\n    response::{Response, ResponseType, StreamingResponse},\n    MiddlewareReturn,\n};\n\n#[inline]\nfn get_function_output<'a, T>(\n    function: &'a FunctionInfo,\n    py: Python<'a>,\n    function_args: &T,\n) -> Result<pyo3::Bound<'a, pyo3::PyAny>, PyErr>\nwhere\n    T: Clone + for<'py> IntoPyObject<'py>,\n    for<'py> <T as IntoPyObject<'py>>::Error: std::fmt::Debug,\n{\n    let handler = function.handler.bind(py).downcast()?;\n\n    // 0-param handlers: skip Request→Python conversion entirely\n    if function.number_of_params == 0 {\n        return handler.call0();\n    }\n\n    let kwargs = function.kwargs.bind(py);\n    let function_args: Py<PyAny> = function_args\n        .clone()\n        .into_pyobject(py)\n        .map_err(|e| {\n            PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!(\n                \"Failed to convert args: {:?}\",\n                e\n            ))\n        })?\n        .into_any()\n        .unbind();\n\n    match function.number_of_params {\n        1 => {\n            if pyo3::types::PyDictMethods::get_item(kwargs, \"global_dependencies\")\n                .is_ok_and(|it| !it.is_none())\n                || pyo3::types::PyDictMethods::get_item(kwargs, \"router_dependencies\")\n                    .is_ok_and(|it| !it.is_none())\n            {\n                handler.call((), Some(kwargs))\n            } else {\n                handler.call1((function_args,))\n            }\n        }\n        _ => handler.call((function_args,), Some(kwargs)),\n    }\n}\n\n// Execute the middleware function\n// type T can be either Request (before middleware) or Response (after middleware)\n// Return type can either be a Request or a Response, we wrap it inside an enum for easier handling\n#[inline]\npub async fn execute_middleware_function<T>(\n    input: &T,\n    function: &FunctionInfo,\n) -> Result<MiddlewareReturn>\nwhere\n    T: Clone + for<'a, 'py> FromPyObject<'a, 'py> + for<'py> IntoPyObject<'py>,\n    for<'py> <T as IntoPyObject<'py>>::Error: std::fmt::Debug,\n{\n    if function.is_async {\n        let output: Py<PyAny> = Python::with_gil(|py| {\n            pyo3_async_runtimes::tokio::into_future(get_function_output(function, py, input)?)\n        })?\n        .await?;\n\n        Python::with_gil(|py| -> Result<MiddlewareReturn> {\n            // Try response extraction first, then request\n            match output.extract::<Response>(py) {\n                Ok(response) => Ok(MiddlewareReturn::Response(response)),\n                Err(_) => match output.extract::<Request>(py) {\n                    Ok(request) => Ok(MiddlewareReturn::Request(request)),\n                    Err(e) => Err(e.into()),\n                },\n            }\n        })\n    } else {\n        Python::with_gil(|py| -> Result<MiddlewareReturn> {\n            let output = get_function_output(function, py, input)?;\n\n            match output.extract::<Response>() {\n                Ok(response) => Ok(MiddlewareReturn::Response(response)),\n                Err(_) => match output.extract::<Request>() {\n                    Ok(request) => Ok(MiddlewareReturn::Request(request)),\n                    Err(e) => Err(e.into()),\n                },\n            }\n        })\n    }\n}\n\n// Execute the after_request middleware function with both request and response\n// This allows after_request callbacks to access the request object\n#[inline]\nfn get_function_output_with_two_args<'a, T, U>(\n    function: &'a FunctionInfo,\n    py: Python<'a>,\n    first_arg: &T,\n    second_arg: &U,\n) -> Result<pyo3::Bound<'a, pyo3::PyAny>, PyErr>\nwhere\n    T: Clone + for<'py> IntoPyObject<'py>,\n    U: Clone + for<'py> IntoPyObject<'py>,\n    for<'py> <T as IntoPyObject<'py>>::Error: std::fmt::Debug,\n    for<'py> <U as IntoPyObject<'py>>::Error: std::fmt::Debug,\n{\n    let handler = function.handler.bind(py).downcast()?;\n\n    if function.number_of_params == 0 {\n        return handler.call0();\n    }\n\n    let kwargs = function.kwargs.bind(py);\n    let first_arg: Py<PyAny> = first_arg\n        .clone()\n        .into_pyobject(py)\n        .map_err(|e| {\n            PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!(\n                \"Failed to convert first arg: {:?}\",\n                e\n            ))\n        })?\n        .into_any()\n        .unbind();\n    let second_arg: Py<PyAny> = second_arg\n        .clone()\n        .into_pyobject(py)\n        .map_err(|e| {\n            PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!(\n                \"Failed to convert second arg: {:?}\",\n                e\n            ))\n        })?\n        .into_any()\n        .unbind();\n\n    match function.number_of_params {\n        1 => {\n            if pyo3::types::PyDictMethods::get_item(kwargs, \"global_dependencies\")\n                .is_ok_and(|it| !it.is_none())\n                || pyo3::types::PyDictMethods::get_item(kwargs, \"router_dependencies\")\n                    .is_ok_and(|it| !it.is_none())\n            {\n                handler.call((), Some(kwargs))\n            } else {\n                handler.call1((second_arg,))\n            }\n        }\n        2 => {\n            if pyo3::types::PyDictMethods::get_item(kwargs, \"global_dependencies\")\n                .is_ok_and(|it| !it.is_none())\n                || pyo3::types::PyDictMethods::get_item(kwargs, \"router_dependencies\")\n                    .is_ok_and(|it| !it.is_none())\n            {\n                handler.call((first_arg, second_arg), Some(kwargs))\n            } else {\n                handler.call1((first_arg, second_arg))\n            }\n        }\n        _ => handler.call((first_arg, second_arg), Some(kwargs)),\n    }\n}\n\n// Execute the after_request middleware function with both request and response\n#[inline]\npub async fn execute_after_middleware_function(\n    request: &Request,\n    response: &Response,\n    function: &FunctionInfo,\n) -> Result<MiddlewareReturn> {\n    if function.is_async {\n        let output: Py<PyAny> = Python::with_gil(|py| {\n            pyo3_async_runtimes::tokio::into_future(get_function_output_with_two_args(\n                function, py, request, response,\n            )?)\n        })?\n        .await?;\n\n        Python::with_gil(|py| -> Result<MiddlewareReturn> {\n            // Try response extraction first, then request\n            match output.extract::<Response>(py) {\n                Ok(response) => Ok(MiddlewareReturn::Response(response)),\n                Err(_) => match output.extract::<Request>(py) {\n                    Ok(request) => Ok(MiddlewareReturn::Request(request)),\n                    Err(e) => Err(e.into()),\n                },\n            }\n        })\n    } else {\n        Python::with_gil(|py| -> Result<MiddlewareReturn> {\n            let output = get_function_output_with_two_args(function, py, request, response)?;\n\n            match output.extract::<Response>() {\n                Ok(response) => Ok(MiddlewareReturn::Response(response)),\n                Err(_) => match output.extract::<Request>() {\n                    Ok(request) => Ok(MiddlewareReturn::Request(request)),\n                    Err(e) => Err(e.into()),\n                },\n            }\n        })\n    }\n}\n\n#[inline]\npub async fn execute_http_function(\n    request: &Request,\n    function: &FunctionInfo,\n) -> PyResult<ResponseType> {\n    if function.is_async {\n        let output = Python::with_gil(|py| {\n            let function_output = get_function_output(function, py, request)?;\n            pyo3_async_runtimes::tokio::into_future(function_output)\n        })?\n        .await?;\n\n        Python::with_gil(|py| extract_response_type(output, py))\n    } else {\n        Python::with_gil(|py| {\n            let output = get_function_output(function, py, request)?;\n            extract_response_type_bound(output)\n        })\n    }\n}\n\n#[inline]\nfn extract_response_type(output: Py<PyAny>, py: Python) -> PyResult<ResponseType> {\n    // Try Response first (most common case), then StreamingResponse\n    match output.extract::<Response>(py) {\n        Ok(response) => Ok(ResponseType::Standard(response)),\n        Err(_) => match output.extract::<StreamingResponse>(py) {\n            Ok(streaming_response) => Ok(ResponseType::Streaming(streaming_response)),\n            Err(_) => Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(\n                \"Function must return a Response or StreamingResponse\",\n            )),\n        },\n    }\n}\n\n#[inline]\nfn extract_response_type_bound(output: pyo3::Bound<'_, pyo3::PyAny>) -> PyResult<ResponseType> {\n    match output.extract::<Response>() {\n        Ok(response) => Ok(ResponseType::Standard(response)),\n        Err(_) => match output.extract::<StreamingResponse>() {\n            Ok(streaming_response) => Ok(ResponseType::Streaming(streaming_response)),\n            Err(_) => Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(\n                \"Function must return a Response or StreamingResponse\",\n            )),\n        },\n    }\n}\n\npub async fn execute_startup_handler(\n    event_handler: Option<Arc<FunctionInfo>>,\n    task_locals: &TaskLocals,\n) -> Result<()> {\n    if let Some(function) = event_handler {\n        if function.is_async {\n            Python::with_gil(|py| {\n                pyo3_async_runtimes::into_future_with_locals(\n                    task_locals,\n                    function.handler.bind(py).call0()?,\n                )\n            })?\n            .await?;\n        } else {\n            Python::with_gil(|py| function.handler.call0(py))?;\n        }\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "src/executors/web_socket_executors.rs",
    "content": "use actix::{ActorFutureExt, AsyncContext, WrapFuture};\nuse actix_web_actors::ws::WebsocketContext;\nuse pyo3::prelude::*;\nuse pyo3_async_runtimes::TaskLocals;\n\nuse crate::types::function_info::FunctionInfo;\nuse crate::websockets::WebSocketConnector;\n\npub fn execute_ws_function(\n    function: &FunctionInfo,\n    task_locals: &TaskLocals,\n    ctx: &mut WebsocketContext<WebSocketConnector>,\n    ws: &WebSocketConnector,\n) {\n    if function.is_async {\n        let fut = Python::with_gil(|py| {\n            let handler = function.handler.bind(py).downcast().unwrap();\n            pyo3_async_runtimes::into_future_with_locals(\n                task_locals,\n                handler.call1((ws.clone(),)).unwrap(),\n            )\n            .unwrap()\n        });\n        let f = async {\n            let output = fut.await.unwrap();\n            Python::with_gil(|py| output.extract::<Option<String>>(py).unwrap())\n        }\n        .into_actor(ws)\n        .map(|res, _, ctx| {\n            if let Some(msg) = res {\n                ctx.text(msg);\n            }\n        });\n        ctx.spawn(f);\n    } else {\n        Python::with_gil(|py| {\n            let handler = function.handler.bind(py).downcast().unwrap();\n            if let Some(op) = handler\n                .call1((ws.clone(),))\n                .unwrap()\n                .extract::<Option<String>>()\n                .unwrap()\n            {\n                ctx.text(op);\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "src/io_helpers/mod.rs",
    "content": "use std::fs::File;\nuse std::io::Read;\n\nuse actix_web::HttpResponseBuilder;\nuse anyhow::Result;\n\nuse crate::types::headers::Headers;\n\n// this should be something else\n// probably inside the submodule of the http router\n#[inline]\npub fn apply_hashmap_headers(response: &mut HttpResponseBuilder, headers: &Headers) {\n    for iter in headers.headers.iter() {\n        let (key, values) = iter.pair();\n        for value in values {\n            response.append_header((key.clone(), value.clone()));\n        }\n    }\n}\n\n/// A function to read lossy files and serve it as a html response\n///\n/// # Arguments\n///\n/// * `file_path` - The file path that we want the function to read\n///\n// ideally this should be async\npub fn read_file(file_path: &str) -> Result<Vec<u8>> {\n    let mut file = File::open(file_path)?;\n    let mut buf = vec![];\n    file.read_to_end(&mut buf)?;\n    Ok(buf)\n}\n"
  },
  {
    "path": "src/lib.rs",
    "content": "mod asyncio;\nmod blocking;\nmod callbacks;\nmod conversion;\nmod executors;\nmod io_helpers;\nmod routers;\nmod runtime;\nmod server;\nmod shared_socket;\nmod types;\nmod websockets;\n\nuse server::Server;\nuse shared_socket::SocketHeld;\n\n// pyO3 module\nuse pyo3::prelude::*;\nuse types::{\n    cookie::{Cookie, Cookies, CookiesIter},\n    function_info::{FunctionInfo, MiddlewareType},\n    headers::Headers,\n    identity::Identity,\n    multimap::QueryParams,\n    request::PyRequest,\n    response::{PyResponse, PyStreamingResponse},\n    HttpMethod, Url,\n};\n\nuse websockets::{registry::WebSocketRegistry, WebSocketChannel, WebSocketConnector};\n\n#[pyfunction]\nfn get_version() -> String {\n    env!(\"CARGO_PKG_VERSION\").into()\n}\n\n#[pymodule]\npub fn robyn(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {\n    // the pymodule class/function to make the rustPyFunctions available\n    m.add_function(wrap_pyfunction!(get_version, m)?)?;\n\n    m.add_class::<Server>()?;\n    m.add_class::<Headers>()?;\n    m.add_class::<Cookie>()?;\n    m.add_class::<Cookies>()?;\n    m.add_class::<CookiesIter>()?;\n    m.add_class::<WebSocketRegistry>()?;\n    m.add_class::<WebSocketConnector>()?;\n    m.add_class::<WebSocketChannel>()?;\n    m.add_class::<SocketHeld>()?;\n    m.add_class::<FunctionInfo>()?;\n    m.add_class::<Identity>()?;\n    m.add_class::<PyRequest>()?;\n    m.add_class::<PyResponse>()?;\n    m.add_class::<PyStreamingResponse>()?;\n    m.add_class::<Url>()?;\n    m.add_class::<QueryParams>()?;\n    m.add_class::<MiddlewareType>()?;\n    m.add_class::<HttpMethod>()?;\n\n    // Register awaitable types\n    m.add_class::<callbacks::PyEmptyAwaitable>()?;\n    m.add_class::<callbacks::PyDoneAwaitable>()?;\n    m.add_class::<callbacks::PyErrAwaitable>()?;\n    m.add_class::<callbacks::PyIterAwaitable>()?;\n    m.add_class::<callbacks::PyFutureAwaitable>()?;\n\n    Ok(())\n}\n"
  },
  {
    "path": "src/routers/const_router.rs",
    "content": "use actix_http::StatusCode;\nuse actix_web::{web::Bytes, HttpResponse, HttpResponseBuilder};\nuse parking_lot::RwLock;\nuse std::collections::HashMap;\nuse std::sync::Arc;\n\nuse crate::executors::execute_http_function;\nuse crate::types::cookie::Cookies;\nuse crate::types::function_info::FunctionInfo;\nuse crate::types::headers::Headers;\nuse crate::types::request::Request;\nuse crate::types::response::Response;\nuse crate::types::HttpMethod;\nuse anyhow::Context;\nuse matchit::Router as MatchItRouter;\nuse pyo3::{Bound, PyErr, Python};\n\nuse anyhow::{Error, Result};\n\nuse crate::routers::Router;\n\n/// Pre-built response for const routes — zero per-request allocation.\n/// Headers (including global response headers) are baked in at startup.\n#[derive(Clone)]\npub struct CachedResponse {\n    pub status: StatusCode,\n    pub headers: Arc<Vec<(String, String)>>,\n    pub body: Bytes,\n    route_header_count: usize,\n}\n\nimpl CachedResponse {\n    fn from_response(response: &Response) -> Self {\n        let mut headers = Vec::new();\n        for entry in response.headers.headers.iter() {\n            let (key, values) = entry.pair();\n            for value in values {\n                headers.push((key.clone(), value.clone()));\n            }\n        }\n        for (name, cookie) in &response.cookies.cookies {\n            if let Ok(header_value) = cookie.to_header_value(name) {\n                headers.push((\"set-cookie\".to_string(), header_value));\n            }\n        }\n        let route_header_count = headers.len();\n        Self {\n            status: StatusCode::from_u16(response.status_code)\n                .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),\n            headers: Arc::new(headers),\n            body: Bytes::from(response.description.clone()),\n            route_header_count,\n        }\n    }\n\n    #[inline(always)]\n    pub fn to_http_response(&self) -> HttpResponse {\n        let mut builder = HttpResponseBuilder::new(self.status);\n        for (k, v) in self.headers.as_ref() {\n            builder.append_header((k.as_str(), v.as_str()));\n        }\n        builder.body(self.body.clone())\n    }\n\n    #[inline(always)]\n    pub fn to_http_response_without_global_headers(&self) -> HttpResponse {\n        let mut builder = HttpResponseBuilder::new(self.status);\n        for (k, v) in self.headers.as_ref().iter().take(self.route_header_count) {\n            builder.append_header((k.as_str(), v.as_str()));\n        }\n        builder.body(self.body.clone())\n    }\n}\n\ntype RouteMap = RwLock<MatchItRouter<CachedResponse>>;\n\n/// Fast const-route lookup table: exact path → CachedResponse.\n/// Uses a simple HashMap (no regex, no path params, no RwLock per lookup).\ntype FastMap = RwLock<HashMap<String, CachedResponse>>;\n\npub struct ConstRouter {\n    routes: HashMap<HttpMethod, Arc<RouteMap>>,\n    fast_routes: HashMap<HttpMethod, Arc<FastMap>>,\n}\n\nimpl Router<Response, HttpMethod> for ConstRouter {\n    fn add_route<'py>(\n        &self,\n        _py: Python,\n        route_type: &HttpMethod,\n        route: &str,\n        function: FunctionInfo,\n        event_loop: Option<Bound<'py, pyo3::PyAny>>,\n    ) -> Result<(), Error> {\n        let table = Arc::clone(self.routes.get(route_type).context(\"No relevant map\")?);\n        let fast_table = Arc::clone(\n            self.fast_routes\n                .get(route_type)\n                .context(\"No relevant fast map\")?,\n        );\n\n        let route = route.to_string();\n        let event_loop =\n            event_loop.context(\"Event loop must be provided to add a route to the const router\")?;\n\n        pyo3_async_runtimes::tokio::run_until_complete(event_loop, async move {\n            let output = execute_http_function(&Request::default(), &function)\n                .await\n                .unwrap();\n            match output {\n                crate::types::response::ResponseType::Standard(response) => {\n                    let cached = CachedResponse::from_response(&response);\n                    table.write().insert(route.clone(), cached.clone()).unwrap();\n                    fast_table.write().insert(route, cached);\n                }\n                crate::types::response::ResponseType::Streaming(_) => {\n                    return Err(PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(\n                        \"Streaming responses are not supported for const routes\",\n                    )\n                    .into());\n                }\n            }\n            Ok(())\n        })?;\n\n        Ok(())\n    }\n\n    fn get_route(&self, route_method: &HttpMethod, route: &str) -> Option<Response> {\n        let cached = self.get_cached_route(route_method, route)?;\n        let mut resp = Response {\n            status_code: cached.status.as_u16(),\n            response_type: \"text\".to_string(),\n            headers: Headers::new(None),\n            description: cached.body.to_vec(),\n            file_path: None,\n            cookies: Cookies::new(),\n        };\n        for (k, v) in cached.headers.as_ref() {\n            resp.headers.set(k.clone(), v.clone());\n        }\n        Some(resp)\n    }\n}\n\nimpl ConstRouter {\n    pub fn new() -> Self {\n        let mut routes = HashMap::new();\n        let mut fast_routes = HashMap::new();\n        for method in [\n            HttpMethod::GET,\n            HttpMethod::POST,\n            HttpMethod::PUT,\n            HttpMethod::DELETE,\n            HttpMethod::PATCH,\n            HttpMethod::HEAD,\n            HttpMethod::OPTIONS,\n            HttpMethod::CONNECT,\n            HttpMethod::TRACE,\n        ] {\n            routes.insert(method.clone(), Arc::new(RwLock::new(MatchItRouter::new())));\n            fast_routes.insert(method, Arc::new(RwLock::new(HashMap::new())));\n        }\n        Self {\n            routes,\n            fast_routes,\n        }\n    }\n\n    /// Bake global response headers into all cached responses.\n    /// Called once at server start, after global headers are set.\n    pub fn bake_global_headers(&self, global_headers: &Headers) {\n        let mut extra_headers: Vec<(String, String)> = Vec::new();\n        for entry in global_headers.headers.iter() {\n            let (key, values) = entry.pair();\n            for value in values {\n                extra_headers.push((key.clone(), value.clone()));\n            }\n        }\n        if extra_headers.is_empty() {\n            return;\n        }\n        for (method, fast_table) in &self.fast_routes {\n            let mut map = fast_table.write();\n            for cached in map.values_mut() {\n                let mut combined = cached.headers.as_ref().clone();\n                combined.extend(extra_headers.iter().cloned());\n                cached.headers = Arc::new(combined);\n            }\n\n            if let Some(route_table) = self.routes.get(method) {\n                let mut new_router = MatchItRouter::new();\n                for (route, cached) in map.iter() {\n                    let _ = new_router.insert(route.clone(), cached.clone());\n                }\n                *route_table.write() = new_router;\n            }\n        }\n    }\n\n    /// Fast lookup: tries exact HashMap first, falls back to matchit for parameterized/wildcard routes.\n    #[inline(always)]\n    pub fn get_cached_route(\n        &self,\n        route_method: &HttpMethod,\n        route: &str,\n    ) -> Option<CachedResponse> {\n        let fast_table = self.fast_routes.get(route_method)?;\n        {\n            let map = fast_table.read();\n            if let Some(cached) = map.get(route) {\n                return Some(cached.clone());\n            }\n        }\n\n        let route_table = self.routes.get(route_method)?;\n        let router = route_table.read();\n        router.at(route).ok().map(|matched| matched.value.clone())\n    }\n}\n"
  },
  {
    "path": "src/routers/http_router.rs",
    "content": "use parking_lot::RwLock;\nuse pyo3::{Bound, Python};\nuse std::collections::HashMap;\n\nuse matchit::Router as MatchItRouter;\n\nuse anyhow::{Context, Result};\n\nuse crate::routers::Router;\nuse crate::types::function_info::FunctionInfo;\nuse crate::types::HttpMethod;\n\ntype RouteMap = RwLock<MatchItRouter<FunctionInfo>>;\n\n/// Contains the thread safe hashmaps of different routes\npub struct HttpRouter {\n    routes: HashMap<HttpMethod, RouteMap>,\n}\n\nimpl Router<(FunctionInfo, HashMap<String, String>), HttpMethod> for HttpRouter {\n    fn add_route<'py>(\n        &self,\n        _py: Python,\n        route_type: &HttpMethod,\n        route: &str,\n        function: FunctionInfo,\n        _event_loop: Option<Bound<'py, pyo3::PyAny>>,\n    ) -> Result<()> {\n        let table = self.routes.get(route_type).context(\"No relevant map\")?;\n\n        // try removing unwrap here\n        table.write().insert(route.to_string(), function)?;\n\n        Ok(())\n    }\n\n    fn get_route(\n        &self,\n        route_method: &HttpMethod,\n        route: &str,\n    ) -> Option<(FunctionInfo, HashMap<String, String>)> {\n        let table = self.routes.get(route_method)?;\n\n        let table_lock = table.read();\n\n        // Trying route matching just once.\n        if let Ok(res) = table_lock.at(route) {\n            let mut route_params = HashMap::new();\n            for (key, value) in res.params.iter() {\n                route_params.insert(key.to_string(), value.to_string());\n            }\n\n            let function_info = Python::with_gil(|_| res.value.to_owned());\n            return Some((function_info, route_params));\n        }\n\n        None\n    }\n}\n\nimpl HttpRouter {\n    pub fn new() -> Self {\n        let mut routes = HashMap::new();\n        routes.insert(HttpMethod::GET, RwLock::new(MatchItRouter::new()));\n        routes.insert(HttpMethod::POST, RwLock::new(MatchItRouter::new()));\n        routes.insert(HttpMethod::PUT, RwLock::new(MatchItRouter::new()));\n        routes.insert(HttpMethod::DELETE, RwLock::new(MatchItRouter::new()));\n        routes.insert(HttpMethod::PATCH, RwLock::new(MatchItRouter::new()));\n        routes.insert(HttpMethod::HEAD, RwLock::new(MatchItRouter::new()));\n        routes.insert(HttpMethod::OPTIONS, RwLock::new(MatchItRouter::new()));\n        routes.insert(HttpMethod::CONNECT, RwLock::new(MatchItRouter::new()));\n        routes.insert(HttpMethod::TRACE, RwLock::new(MatchItRouter::new()));\n        Self { routes }\n    }\n}\n"
  },
  {
    "path": "src/routers/middleware_router.rs",
    "content": "use std::collections::HashMap;\nuse std::sync::atomic::{AtomicBool, Ordering};\nuse std::sync::RwLock;\n\nuse anyhow::{Context, Error, Result};\nuse matchit::Router as MatchItRouter;\nuse pyo3::{Bound, Python};\n\nuse crate::routers::Router;\nuse crate::types::function_info::{FunctionInfo, MiddlewareType};\n\ntype RouteMap = RwLock<MatchItRouter<FunctionInfo>>;\n\npub struct MiddlewareRouter {\n    globals: HashMap<MiddlewareType, RwLock<Vec<FunctionInfo>>>,\n    routes: HashMap<MiddlewareType, RouteMap>,\n    has_middleware: AtomicBool,\n}\n\nimpl Router<(FunctionInfo, HashMap<String, String>), MiddlewareType> for MiddlewareRouter {\n    fn add_route<'py>(\n        &self,\n        _py: Python,\n        route_type: &MiddlewareType,\n        route: &str,\n        function: FunctionInfo,\n        _event_loop: Option<Bound<'py, pyo3::PyAny>>,\n    ) -> Result<(), Error> {\n        let table = self.routes.get(route_type).context(\"No relevant map\")?;\n\n        table.write().unwrap().insert(route.to_string(), function)?;\n        self.has_middleware.store(true, Ordering::Release);\n\n        Ok(())\n    }\n\n    fn get_route(\n        &self,\n        route_method: &MiddlewareType,\n        route: &str,\n    ) -> Option<(FunctionInfo, HashMap<String, String>)> {\n        let table = self.routes.get(route_method)?;\n\n        let table_lock = table.read().ok()?;\n        let res = table_lock.at(route).ok()?;\n        let mut route_params = HashMap::new();\n        for (key, value) in res.params.iter() {\n            route_params.insert(key.to_string(), value.to_string());\n        }\n\n        let function_info = Python::with_gil(|_| res.value.to_owned());\n\n        Some((function_info, route_params))\n    }\n}\n\nimpl MiddlewareRouter {\n    pub fn new() -> Self {\n        let mut globals = HashMap::new();\n        globals.insert(MiddlewareType::BeforeRequest, RwLock::new(vec![]));\n        globals.insert(MiddlewareType::AfterRequest, RwLock::new(vec![]));\n        let mut routes = HashMap::new();\n        routes.insert(\n            MiddlewareType::BeforeRequest,\n            RwLock::new(MatchItRouter::new()),\n        );\n        routes.insert(\n            MiddlewareType::AfterRequest,\n            RwLock::new(MatchItRouter::new()),\n        );\n        Self {\n            globals,\n            routes,\n            has_middleware: AtomicBool::new(false),\n        }\n    }\n\n    pub fn add_global_middleware(\n        &self,\n        middleware_type: &MiddlewareType,\n        function: FunctionInfo,\n    ) -> Result<()> {\n        self.globals\n            .get(middleware_type)\n            .context(\"No relevant map\")?\n            .write()\n            .unwrap()\n            .push(function);\n        self.has_middleware.store(true, Ordering::Release);\n        Ok(())\n    }\n\n    pub fn get_global_middlewares(&self, middleware_type: &MiddlewareType) -> Vec<FunctionInfo> {\n        self.globals\n            .get(middleware_type)\n            .unwrap()\n            .read()\n            .unwrap()\n            .to_vec()\n    }\n\n    pub fn has_any_middleware(&self) -> bool {\n        self.has_middleware.load(Ordering::Acquire)\n    }\n}\n"
  },
  {
    "path": "src/routers/mod.rs",
    "content": "use anyhow::Result;\nuse pyo3::{Bound, Python};\n\nuse crate::types::function_info::FunctionInfo;\n\npub mod const_router;\npub mod http_router;\npub mod middleware_router;\npub mod web_socket_router;\n\npub trait Router<T, U> {\n    /// Checks if the functions is an async function\n    /// Inserts them in the router according to their nature(CoRoutine/SyncFunction)\n    fn add_route<'py>(\n        &self,\n        py: Python,\n        route_type: &U,\n        route: &str,\n        function: FunctionInfo,\n        event_loop: Option<Bound<'py, pyo3::PyAny>>,\n    ) -> Result<()>;\n\n    /// Retrieve the correct function from the previously inserted routes\n    fn get_route(&self, route_type: &U, route: &str) -> Option<T>;\n}\n"
  },
  {
    "path": "src/routers/web_socket_router.rs",
    "content": "use parking_lot::RwLock;\nuse std::collections::HashMap;\n\nuse log::debug;\n\nuse crate::types::function_info::FunctionInfo;\n\n/// Contains the thread safe hashmaps of different routes\ntype WebSocketRoutes = RwLock<HashMap<String, HashMap<String, FunctionInfo>>>;\n\npub struct WebSocketRouter {\n    web_socket_routes: WebSocketRoutes,\n}\n\nimpl WebSocketRouter {\n    pub fn new() -> Self {\n        Self {\n            web_socket_routes: RwLock::new(HashMap::new()),\n        }\n    }\n\n    #[inline]\n    pub fn get_web_socket_map(&self) -> &WebSocketRoutes {\n        &self.web_socket_routes\n    }\n\n    pub fn add_websocket_route(\n        &self,\n        route: &str,\n        connect_route: FunctionInfo,\n        close_route: FunctionInfo,\n        message_route: FunctionInfo,\n    ) {\n        let table = self.get_web_socket_map();\n\n        let insert_in_router = |function: FunctionInfo, socket_type: &str| {\n            debug!(\"socket type is {:?} {:?}\", table, route);\n\n            table\n                .write()\n                .entry(route.to_string())\n                .or_default()\n                .insert(socket_type.to_string(), function)\n        };\n\n        insert_in_router(connect_route, \"connect\");\n        insert_in_router(close_route, \"close\");\n        insert_in_router(message_route, \"message\");\n    }\n}\n"
  },
  {
    "path": "src/runtime.rs",
    "content": "use futures::FutureExt;\nuse pyo3::{prelude::*, IntoPyObjectExt};\nuse std::{future::Future, sync::Arc, sync::OnceLock};\nuse tokio::{runtime::Builder as RuntimeBuilder, task::JoinHandle};\n\n#[cfg(unix)]\nuse super::callbacks::PyFutureAwaitable;\n#[cfg(windows)]\nuse super::callbacks::{PyFutureDoneCallback, PyFutureResultSetter};\n\nuse super::blocking::BlockingRunner;\nuse super::callbacks::{PyDoneAwaitable, PyEmptyAwaitable, PyErrAwaitable, PyIterAwaitable};\nuse super::conversion::FutureResultToPy;\n\npub trait JoinError {\n    #[allow(dead_code)]\n    fn is_panic(&self) -> bool;\n}\n\npub trait Runtime: Send + 'static {\n    type JoinError: JoinError + Send;\n    type JoinHandle: Future<Output = Result<(), Self::JoinError>> + Send;\n\n    fn spawn<F>(&self, fut: F) -> Self::JoinHandle\n    where\n        F: Future<Output = ()> + Send + 'static;\n\n    fn spawn_blocking<F>(&self, task: F)\n    where\n        F: FnOnce(Python) + Send + 'static;\n}\n\npub trait ContextExt: Runtime {\n    fn py_event_loop(&self, py: Python) -> Py<PyAny>;\n}\n\npub(crate) struct RuntimeWrapper {\n    pub inner: tokio::runtime::Runtime,\n    br: Arc<BlockingRunner>,\n    pr: Arc<Py<PyAny>>,\n}\n\nimpl RuntimeWrapper {\n    pub fn new(\n        blocking_threads: usize,\n        py_threads: usize,\n        py_threads_idle_timeout: u64,\n        py_loop: Arc<Py<PyAny>>,\n    ) -> Self {\n        Self {\n            inner: default_runtime(blocking_threads),\n            br: BlockingRunner::new(py_threads, py_threads_idle_timeout).into(),\n            pr: py_loop,\n        }\n    }\n\n    pub fn with_runtime(\n        rt: tokio::runtime::Runtime,\n        py_threads: usize,\n        py_threads_idle_timeout: u64,\n        py_loop: Arc<Py<PyAny>>,\n    ) -> Self {\n        Self {\n            inner: rt,\n            br: BlockingRunner::new(py_threads, py_threads_idle_timeout).into(),\n            pr: py_loop,\n        }\n    }\n\n    pub fn handler(&self) -> RuntimeRef {\n        RuntimeRef::new(\n            self.inner.handle().clone(),\n            self.br.clone(),\n            self.pr.clone(),\n        )\n    }\n}\n\n#[derive(Clone)]\npub struct RuntimeRef {\n    pub inner: tokio::runtime::Handle,\n    innerb: Arc<BlockingRunner>,\n    innerp: Arc<Py<PyAny>>,\n}\n\nimpl RuntimeRef {\n    pub fn new(\n        rt: tokio::runtime::Handle,\n        br: Arc<BlockingRunner>,\n        pyloop: Arc<Py<PyAny>>,\n    ) -> Self {\n        Self {\n            inner: rt,\n            innerb: br,\n            innerp: pyloop,\n        }\n    }\n}\n\nimpl JoinError for tokio::task::JoinError {\n    fn is_panic(&self) -> bool {\n        tokio::task::JoinError::is_panic(self)\n    }\n}\n\nimpl Runtime for RuntimeRef {\n    type JoinError = tokio::task::JoinError;\n    type JoinHandle = JoinHandle<()>;\n\n    fn spawn<F>(&self, fut: F) -> Self::JoinHandle\n    where\n        F: Future<Output = ()> + Send + 'static,\n    {\n        self.inner.spawn(fut)\n    }\n\n    #[inline]\n    fn spawn_blocking<F>(&self, task: F)\n    where\n        F: FnOnce(Python) + Send + 'static,\n    {\n        _ = self.innerb.run(task);\n    }\n}\n\nimpl ContextExt for RuntimeRef {\n    fn py_event_loop(&self, py: Python) -> Py<PyAny> {\n        self.innerp.clone_ref(py)\n    }\n}\n\nfn default_runtime(blocking_threads: usize) -> tokio::runtime::Runtime {\n    RuntimeBuilder::new_current_thread()\n        .max_blocking_threads(blocking_threads)\n        .enable_all()\n        .build()\n        .unwrap()\n}\n\n#[inline(always)]\npub(crate) fn empty_future_into_py(py: Python) -> PyResult<Bound<PyAny>> {\n    PyEmptyAwaitable.into_bound_py_any(py)\n}\n\n#[inline(always)]\npub(crate) fn done_future_into_py(\n    py: Python,\n    result: PyResult<Py<PyAny>>,\n) -> PyResult<Bound<PyAny>> {\n    PyDoneAwaitable::new(result).into_bound_py_any(py)\n}\n\n#[inline(always)]\npub(crate) fn err_future_into_py(py: Python, err: PyErr) -> PyResult<Bound<PyAny>> {\n    PyErrAwaitable::new(err).into_bound_py_any(py)\n}\n\n// NOTE: ~55% faster than pyo3_asyncio.future_into_py\n#[allow(dead_code, unused_must_use)]\npub(crate) fn future_into_py_iter<R, F>(rt: R, py: Python, fut: F) -> PyResult<Bound<PyAny>>\nwhere\n    R: Runtime + ContextExt + Clone,\n    F: Future<Output = FutureResultToPy> + Send + 'static,\n{\n    let aw = Py::new(py, PyIterAwaitable::new())?;\n    let py_fut = aw.clone_ref(py);\n    let rth = rt.clone();\n\n    rt.spawn(async move {\n        let result = fut.await;\n        rth.spawn_blocking(move |py| PyIterAwaitable::set_result(aw, py, result));\n    });\n\n    Ok(py_fut.into_any().into_bound(py))\n}\n\n// NOTE: ~38% faster than pyo3_asyncio.future_into_py\n#[allow(unused_must_use)]\n#[cfg(unix)]\npub(crate) fn future_into_py_futlike<R, F>(rt: R, py: Python, fut: F) -> PyResult<Bound<PyAny>>\nwhere\n    R: Runtime + ContextExt + Clone,\n    F: Future<Output = FutureResultToPy> + Send + 'static,\n{\n    let event_loop = rt.py_event_loop(py);\n    let (aw, cancel_tx) = PyFutureAwaitable::new(event_loop).to_spawn(py)?;\n    let py_fut = aw.clone_ref(py);\n    let rth = rt.clone();\n\n    rt.spawn(async move {\n        tokio::select! {\n            biased;\n            result = fut => rth.spawn_blocking(move |py| PyFutureAwaitable::set_result(aw, py, result)),\n            () = cancel_tx.notified() => rth.spawn_blocking(move |py| aw.drop_ref(py)),\n        }\n    });\n\n    Ok(py_fut.into_any().into_bound(py))\n}\n\n#[allow(unused_must_use)]\n#[cfg(windows)]\npub(crate) fn future_into_py_futlike<R, F>(rt: R, py: Python, fut: F) -> PyResult<Bound<PyAny>>\nwhere\n    R: Runtime + ContextExt + Clone,\n    F: Future<Output = FutureResultToPy> + Send + 'static,\n{\n    let event_loop = rt.py_event_loop(py);\n    let event_loop_ref = event_loop.clone_ref(py);\n    let cancel_tx = Arc::new(tokio::sync::Notify::new());\n    let rth = rt.clone();\n\n    let py_fut = event_loop.call_method0(py, pyo3::intern!(py, \"create_future\"))?;\n    py_fut.call_method1(\n        py,\n        pyo3::intern!(py, \"add_done_callback\"),\n        (PyFutureDoneCallback {\n            cancel_tx: cancel_tx.clone(),\n        },),\n    )?;\n    let fut_ref = py_fut.clone_ref(py);\n\n    rt.spawn(async move {\n        tokio::select! {\n            biased;\n            result = fut => {\n                rth.spawn_blocking(move |py| {\n                    let pyres = result.into_pyobject(py).map(Bound::unbind);\n                    let resolved: PyResult<()> = match pyres {\n                        Ok(val) => {\n                            let cb = fut_ref.getattr(py, pyo3::intern!(py, \"set_result\"));\n                            match cb {\n                                Ok(cb) => {\n                                    let _ = event_loop_ref.call_method1(\n                                        py,\n                                        pyo3::intern!(py, \"call_soon_threadsafe\"),\n                                        (PyFutureResultSetter, cb, val),\n                                    );\n                                    Ok(())\n                                }\n                                Err(e) => Err(e),\n                            }\n                        }\n                        Err(err) => {\n                            (|| -> PyResult<()> {\n                                let cb = fut_ref.getattr(py, pyo3::intern!(py, \"set_exception\"))?;\n                                let val = err.into_py_any(py)?;\n                                let _ = event_loop_ref.call_method1(\n                                    py,\n                                    pyo3::intern!(py, \"call_soon_threadsafe\"),\n                                    (PyFutureResultSetter, cb, val),\n                                );\n                                Ok(())\n                            })()\n                        }\n                    };\n                    if let Err(err) = resolved {\n                        log::error!(\"Failed to resolve Python future: {}\", err);\n                    }\n                    fut_ref.drop_ref(py);\n                    event_loop_ref.drop_ref(py);\n                });\n            },\n            () = cancel_tx.notified() => {\n                rth.spawn_blocking(move |py| {\n                    fut_ref.drop_ref(py);\n                    event_loop_ref.drop_ref(py);\n                });\n            }\n        }\n    });\n\n    Ok(py_fut.into_bound(py))\n}\n\nstatic SHARED_BLOCKING_RUNNER: OnceLock<Arc<BlockingRunner>> = OnceLock::new();\n\nfn shared_blocking_runner() -> Arc<BlockingRunner> {\n    SHARED_BLOCKING_RUNNER\n        .get_or_init(|| Arc::new(BlockingRunner::new(1, 30)))\n        .clone()\n}\n\npub fn future_into_py<F>(py: Python, fut: F) -> PyResult<Bound<PyAny>>\nwhere\n    F: Future<Output = Result<(), anyhow::Error>> + Send + 'static,\n{\n    match tokio::runtime::Handle::try_current() {\n        Ok(rt_handle) => {\n            let asyncio = py.import(\"asyncio\")?;\n            let event_loop = asyncio\n                .call_method0(\"get_running_loop\")\n                .or_else(|_| asyncio.call_method0(\"new_event_loop\"))?;\n            let event_loop: Py<PyAny> = event_loop.unbind();\n\n            let blocking_runner = shared_blocking_runner();\n\n            let rt_ref = RuntimeRef::new(rt_handle, blocking_runner, Arc::new(event_loop));\n\n            let wrapped_fut = async move {\n                match fut.await {\n                    Ok(()) => FutureResultToPy::None,\n                    Err(e) => FutureResultToPy::Err(Err(PyErr::new::<\n                        pyo3::exceptions::PyRuntimeError,\n                        _,\n                    >(format!(\n                        \"Future error: {}\",\n                        e\n                    )))),\n                }\n            };\n\n            future_into_py_futlike(rt_ref, py, wrapped_fut)\n        }\n        Err(_) => {\n            let py_fut = fut.map(|result| {\n                result.map_err(|e| {\n                    PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!(\n                        \"Future error: {}\",\n                        e\n                    ))\n                })\n            });\n            pyo3_async_runtimes::tokio::future_into_py(py, py_fut)\n        }\n    }\n}\n"
  },
  {
    "path": "src/server.rs",
    "content": "use crate::executors::{\n    execute_after_middleware_function, execute_http_function, execute_middleware_function,\n    execute_startup_handler,\n};\n\nuse crate::routers::const_router::ConstRouter;\nuse crate::routers::Router;\n\nuse crate::routers::http_router::HttpRouter;\nuse crate::routers::{middleware_router::MiddlewareRouter, web_socket_router::WebSocketRouter};\nuse crate::shared_socket::SocketHeld;\nuse crate::types::cookie::Cookies;\nuse crate::types::function_info::{FunctionInfo, MiddlewareType};\nuse crate::types::headers::Headers;\nuse crate::types::request::Request;\nuse crate::types::response::{Response, ResponseType};\nuse crate::types::HttpMethod;\nuse crate::types::MiddlewareReturn;\nuse crate::websockets::start_web_socket;\n\nuse std::sync::atomic::AtomicBool;\nuse std::sync::atomic::Ordering::{Relaxed, SeqCst};\nuse std::sync::{Arc, RwLock};\n\nuse std::process::exit;\nuse std::{env, thread};\n\nuse actix_files::Files;\nuse actix_http::KeepAlive;\nuse actix_web::*;\n\nuse log::error;\nuse once_cell::sync::OnceCell;\nuse pyo3::exceptions::PyValueError;\nuse pyo3::prelude::*;\nuse pyo3::pycell::PyRef;\nuse pyo3_async_runtimes::TaskLocals;\n\nconst MAX_PAYLOAD_SIZE: &str = \"ROBYN_MAX_PAYLOAD_SIZE\";\nconst DEFAULT_MAX_PAYLOAD_SIZE: usize = 1_000_000; // 1Mb\n\nstatic STARTED: AtomicBool = AtomicBool::new(false);\n\n#[derive(Clone)]\nstruct Directory {\n    route: String,\n    directory_path: String,\n    show_files_listing: bool,\n    index_file: Option<String>,\n}\n\n#[pyclass]\npub struct Server {\n    router: Arc<HttpRouter>,\n    const_router: Arc<ConstRouter>,\n    websocket_router: Arc<WebSocketRouter>,\n    middleware_router: Arc<MiddlewareRouter>,\n    global_request_headers: Arc<Headers>,\n    global_response_headers: Arc<Headers>,\n    directories: Arc<RwLock<Vec<Directory>>>,\n    startup_handler: Option<Arc<FunctionInfo>>,\n    shutdown_handler: Option<Arc<FunctionInfo>>,\n    excluded_response_headers_paths: Option<Vec<String>>,\n}\n\n#[pymethods]\nimpl Server {\n    #[new]\n    pub fn new() -> Self {\n        Self {\n            router: Arc::new(HttpRouter::new()),\n            const_router: Arc::new(ConstRouter::new()),\n            websocket_router: Arc::new(WebSocketRouter::new()),\n            middleware_router: Arc::new(MiddlewareRouter::new()),\n            global_request_headers: Arc::new(Headers::new(None)),\n            global_response_headers: Arc::new(Headers::new(None)),\n            directories: Arc::new(RwLock::new(Vec::new())),\n            startup_handler: None,\n            shutdown_handler: None,\n            excluded_response_headers_paths: None,\n        }\n    }\n\n    pub fn start(\n        &mut self,\n        _py: Python,\n        socket: PyRef<SocketHeld>,\n        workers: usize,\n    ) -> PyResult<()> {\n        pyo3_log::init();\n\n        static TASK_LOCALS: OnceCell<TaskLocals> = OnceCell::new();\n\n        if STARTED\n            .compare_exchange(false, true, SeqCst, Relaxed)\n            .is_err()\n        {\n            return Ok(());\n        }\n\n        let raw_socket = socket.get_socket();\n\n        let router = Arc::clone(&self.router);\n        let const_router = Arc::clone(&self.const_router);\n        let middleware_router = Arc::clone(&self.middleware_router);\n        let web_socket_router = Arc::clone(&self.websocket_router);\n        let global_request_headers = Arc::clone(&self.global_request_headers);\n        let global_response_headers = Arc::clone(&self.global_response_headers);\n        let directories = Arc::clone(&self.directories);\n\n        let asyncio = _py.import(\"asyncio\")?;\n        let event_loop = asyncio.call_method0(\"new_event_loop\")?;\n        asyncio.call_method1(\"set_event_loop\", (event_loop.clone(),))?;\n\n        let startup_handler = self.startup_handler.clone();\n        let shutdown_handler = self.shutdown_handler.clone();\n\n        let excluded_response_headers_paths = self.excluded_response_headers_paths.clone();\n\n        let _ = TASK_LOCALS.get_or_try_init(|| {\n            Python::with_gil(|py| {\n                pyo3_async_runtimes::TaskLocals::new(event_loop.clone().into()).copy_context(py)\n            })\n        });\n\n        let max_payload_size = env::var(MAX_PAYLOAD_SIZE)\n            .unwrap_or(DEFAULT_MAX_PAYLOAD_SIZE.to_string())\n            .trim()\n            .parse::<usize>()\n            .map_err(|e| {\n                PyValueError::new_err(format!(\n                    \"Failed to parse environment variable {MAX_PAYLOAD_SIZE} - {e}\"\n                ))\n            })?;\n\n        thread::spawn(move || {\n            actix_web::rt::System::new().block_on(async move {\n                let task_locals = Python::with_gil(|py| TASK_LOCALS.get().unwrap().clone_ref(py));\n                execute_startup_handler(startup_handler, &task_locals)\n                    .await\n                    .unwrap();\n\n                if !middleware_router.has_any_middleware() {\n                    const_router.bake_global_headers(&global_response_headers);\n                }\n\n                HttpServer::new(move || {\n                    let mut app = App::new();\n\n                    let directories = directories.read().unwrap();\n\n                    // this loop matches three types of directory serving\n                    // 1. Serves a build folder. e.g. the build folder generated from yarn build\n                    // 2. Shows file listing\n                    // 3. Just serves the file without any redirection to sub links\n                    for directory in directories.iter() {\n                        let mut files = Files::new(&directory.route, &directory.directory_path)\n                            .method_guard(guard::fn_guard(|_| true))\n                            .redirect_to_slash_directory();\n                        if let Some(index_file) = &directory.index_file {\n                            files = files.index_file(index_file);\n                        } else if directory.show_files_listing {\n                            files = files.show_files_listing();\n                        } else {\n                            // To serve regular files only, nothing else.\n                            let directory_path = directory.directory_path.clone();\n                            let directory_route = directory.route.clone();\n                            // This guard allows request if it corresponds to a regular file.\n                            files = files.guard(guard::fn_guard(move |ctx| {\n                                let route = ctx.head().uri.path();\n                                // Resolve the path by combining directory path and requested path\n                                let full_path = std::path::Path::new(&directory_path).join(\n                                    route\n                                        .trim_start_matches(&directory_route)\n                                        .trim_start_matches(\"/\"),\n                                );\n                                // Check if the path exists and is a regular file (not dir/symlink)\n                                if let Ok(metadata) = std::fs::metadata(&full_path) {\n                                    metadata.is_file()\n                                } else {\n                                    false\n                                }\n                            }));\n                        }\n                        app = app.service(files);\n                    }\n\n                    app = app\n                        .app_data(web::Data::new(Arc::clone(&router)))\n                        .app_data(web::Data::new(Arc::clone(&const_router)))\n                        .app_data(web::Data::new(Arc::clone(&middleware_router)))\n                        .app_data(web::Data::new(Arc::clone(&global_request_headers)))\n                        .app_data(web::Data::new(Arc::clone(&global_response_headers)))\n                        .app_data(web::Data::new(excluded_response_headers_paths.clone()));\n\n                    let web_socket_map = web_socket_router.get_web_socket_map();\n                    for (elem, value) in (web_socket_map.read()).iter() {\n                        let endpoint = elem.clone();\n                        let path_params = value.clone();\n                        let endpoint_for_closure = endpoint.clone();\n                        app = app.route(\n                            &endpoint,\n                            web::get().to(move |stream: web::Payload, req: HttpRequest| {\n                                let endpoint_copy = endpoint_for_closure.clone();\n                                let task_locals =\n                                    Python::with_gil(|py| TASK_LOCALS.get().unwrap().clone_ref(py));\n                                start_web_socket(\n                                    req,\n                                    stream,\n                                    path_params.clone(),\n                                    task_locals,\n                                    endpoint_copy.to_string(),\n                                    max_payload_size,\n                                )\n                            }),\n                        );\n                    }\n\n                    app.app_data(web::PayloadConfig::new(max_payload_size))\n                        .default_service(web::route().to(\n                            move |router: web::Data<Arc<HttpRouter>>,\n                                  const_router: web::Data<Arc<ConstRouter>>,\n                                  middleware_router: web::Data<Arc<MiddlewareRouter>>,\n                                  payload: web::Payload,\n                                  global_request_headers: web::Data<Arc<Headers>>,\n                                  global_response_headers: web::Data<Arc<Headers>>,\n                                  response_headers_exclude_paths: web::Data<\n                                Option<Vec<String>>,\n                            >,\n                                  req: HttpRequest| async move {\n                                // Fast path: const routes bypass request parsing, Python, and middleware\n                                // Only safe when no middlewares are registered (checked dynamically via AtomicBool)\n                                if !middleware_router.has_any_middleware() {\n                                    if let Ok(http_method) =\n                                        HttpMethod::from_actix_method(req.method())\n                                    {\n                                        if let Some(cached) = const_router\n                                            .get_cached_route(&http_method, req.uri().path())\n                                        {\n                                            if let Some(ref excluded) =\n                                                *response_headers_exclude_paths.get_ref()\n                                            {\n                                                if excluded.contains(&req.uri().path().to_owned()) {\n                                                    return cached\n                                                        .to_http_response_without_global_headers();\n                                                }\n                                            }\n                                            return cached.to_http_response();\n                                        }\n                                    }\n                                }\n\n                                // Normal path: dynamic routes (and const routes when middlewares exist) require Python\n                                let req_ref = req.clone();\n                                let task_locals =\n                                    Python::with_gil(|py| TASK_LOCALS.get().unwrap().clone_ref(py));\n                                let response = pyo3_async_runtimes::tokio::scope_local(\n                                    task_locals,\n                                    async move {\n                                        index(\n                                            router,\n                                            const_router,\n                                            payload,\n                                            middleware_router,\n                                            global_request_headers,\n                                            global_response_headers,\n                                            response_headers_exclude_paths,\n                                            req,\n                                        )\n                                        .await\n                                    },\n                                )\n                                .await;\n                                response.respond_to(&req_ref)\n                            },\n                        ))\n                })\n                .keep_alive(KeepAlive::Os)\n                .workers(workers)\n                .client_request_timeout(std::time::Duration::from_secs(0))\n                .listen(raw_socket.into())\n                .unwrap()\n                .run()\n                .await\n                .unwrap();\n            });\n        });\n\n        let event_loop = event_loop.call_method0(\"run_forever\");\n        if event_loop.is_err() {\n            if let Some(function) = shutdown_handler {\n                if function.is_async {\n                    let task_locals =\n                        Python::with_gil(|py| TASK_LOCALS.get().unwrap().clone_ref(py));\n\n                    pyo3_async_runtimes::tokio::run_until_complete(\n                        task_locals.event_loop(_py),\n                        pyo3_async_runtimes::into_future_with_locals(\n                            &task_locals.clone_ref(_py),\n                            function.handler.bind(_py).call0()?,\n                        )\n                        .unwrap(),\n                    )\n                    .unwrap();\n                } else {\n                    Python::with_gil(|py| function.handler.call0(py))?;\n                }\n            }\n\n            exit(0);\n        }\n        Ok(())\n    }\n\n    pub fn add_directory(\n        &mut self,\n        route: String,\n        directory_path: String,\n        show_files_listing: bool,\n        index_file: Option<String>,\n    ) {\n        self.directories.write().unwrap().push(Directory {\n            route,\n            directory_path,\n            show_files_listing,\n            index_file,\n        });\n    }\n\n    /// Removes a new request header to our concurrent hashmap\n    /// this can be called after the server has started.\n    pub fn remove_header(&self, key: &str) {\n        self.global_request_headers.headers.remove(key);\n    }\n\n    /// Removes a new response header to our concurrent hashmap\n    /// this can be called after the server has started.\n    pub fn remove_response_header(&self, key: &str) {\n        self.global_response_headers.headers.remove(key);\n    }\n\n    pub fn apply_request_headers(&mut self, headers: &Headers) {\n        self.global_request_headers = Arc::new(headers.clone());\n    }\n\n    pub fn apply_response_headers(&mut self, headers: &Headers) {\n        self.global_response_headers = Arc::new(headers.clone());\n    }\n\n    pub fn set_response_headers_exclude_paths(\n        &mut self,\n        excluded_response_headers_paths: Option<Vec<String>>,\n    ) {\n        self.excluded_response_headers_paths = excluded_response_headers_paths;\n    }\n\n    /// Add a new route to the routing tables\n    /// can be called after the server has been started\n    pub fn add_route(\n        &self,\n        py: Python,\n        route_type: &HttpMethod,\n        route: &str,\n        function: FunctionInfo,\n        is_const: bool,\n    ) {\n        self._add_route(py, route_type, route, &function, is_const);\n    }\n\n    fn _add_route(\n        &self,\n        py: Python,\n        route_type: &HttpMethod,\n        route: &str,\n        function: &FunctionInfo,\n        is_const: bool,\n    ) {\n        let asyncio = py.import(\"asyncio\").unwrap();\n        let event_loop = asyncio.call_method0(\"get_event_loop\").unwrap();\n\n        if is_const {\n            match self.const_router.add_route(\n                py,\n                route_type,\n                route,\n                function.clone(),\n                Some(event_loop),\n            ) {\n                Ok(_) => (),\n                Err(e) => {\n                    log::debug!(\"Error adding const route {}\", e);\n                }\n            }\n        } else {\n            match self\n                .router\n                .add_route(py, route_type, route, function.clone(), None)\n            {\n                Ok(_) => (),\n                Err(e) => {\n                    log::debug!(\"Error adding route {}\", e);\n                }\n            }\n        }\n    }\n\n    /// Add a new global middleware\n    /// can be called after the server has been started\n    pub fn add_global_middleware(&self, middleware_type: &MiddlewareType, function: FunctionInfo) {\n        self.middleware_router\n            .add_global_middleware(middleware_type, function)\n            .unwrap();\n    }\n\n    /// Add a new route to the routing tables\n    /// can be called after the server has been started\n    pub fn add_middleware_route(\n        &self,\n        middleware_type: &MiddlewareType,\n        route: &str,\n        function: FunctionInfo,\n        http_method: HttpMethod,\n    ) {\n        let mut endpoint_prefixed_with_method = http_method.to_string();\n\n        if !route.starts_with('/') {\n            endpoint_prefixed_with_method.push('/');\n        }\n\n        endpoint_prefixed_with_method.push_str(route);\n\n        Python::with_gil(|py| {\n            self.middleware_router\n                .add_route(\n                    py,\n                    middleware_type,\n                    &endpoint_prefixed_with_method,\n                    function,\n                    None,\n                )\n                .unwrap()\n        });\n    }\n\n    /// Add a new web socket route to the routing tables\n    /// can be called after the server has been started\n    pub fn add_web_socket_route(\n        &mut self,\n        route: &str,\n        connect_route: FunctionInfo,\n        close_route: FunctionInfo,\n        message_route: FunctionInfo,\n    ) {\n        self.websocket_router\n            .add_websocket_route(route, connect_route, close_route, message_route);\n    }\n\n    /// Add a new startup handler\n    pub fn add_startup_handler(&mut self, function: FunctionInfo) {\n        self.startup_handler = Some(Arc::new(function));\n    }\n\n    /// Add a new shutdown handler\n    pub fn add_shutdown_handler(&mut self, function: FunctionInfo) {\n        self.shutdown_handler = Some(Arc::new(function));\n    }\n}\n\nimpl Default for Server {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nasync fn index(\n    router: web::Data<Arc<HttpRouter>>,\n    const_router: web::Data<Arc<ConstRouter>>,\n    payload: web::Payload,\n    middleware_router: web::Data<Arc<MiddlewareRouter>>,\n    global_request_headers: web::Data<Arc<Headers>>,\n    global_response_headers: web::Data<Arc<Headers>>,\n    excluded_response_headers_paths: web::Data<Option<Vec<String>>>,\n    req: HttpRequest,\n) -> ResponseType {\n    if !HttpMethod::is_supported(req.method()) {\n        return ResponseType::Standard(Response::method_not_allowed(None));\n    }\n\n    let http_method = match HttpMethod::from_actix_method(req.method()) {\n        Ok(method) => method,\n        Err(_) => return ResponseType::Standard(Response::method_not_allowed(None)),\n    };\n\n    let mut request: Request =\n        match Request::from_actix_request(&req, payload, &global_request_headers).await {\n            Ok(r) => r,\n            Err(e) => {\n                error!(\"Failed to parse request for `{}`: {}\", req.path(), e);\n                return ResponseType::Standard(Response::internal_server_error(None));\n            }\n        };\n\n    let route = format!(\"{}{}\", req.method(), request.url.path);\n\n    // Before middleware\n    let before_middlewares =\n        middleware_router.get_global_middlewares(&MiddlewareType::BeforeRequest);\n    let route_before = middleware_router.get_route(&MiddlewareType::BeforeRequest, &route);\n\n    let mut early_response: Option<Response> = None;\n    if !before_middlewares.is_empty() || route_before.is_some() {\n        let mut all_before = before_middlewares;\n        if let Some((function, route_params)) = route_before {\n            all_before.push(function);\n            request.path_params = route_params;\n        }\n        for before_middleware in all_before {\n            request = match execute_middleware_function(&request, &before_middleware).await {\n                Ok(MiddlewareReturn::Request(r)) => r,\n                Ok(MiddlewareReturn::Response(r)) => {\n                    early_response = Some(r);\n                    break;\n                }\n                Err(e) => {\n                    let msg = match e.downcast_ref::<PyErr>() {\n                        Some(py_err) => get_traceback(py_err),\n                        None => format!(\"{e:?}\"),\n                    };\n                    error!(\n                        \"Error executing before middleware for `{}`: {}\",\n                        request.url.path, msg\n                    );\n                    return ResponseType::Standard(Response::internal_server_error(None));\n                }\n            };\n        }\n    }\n\n    let mut response = if let Some(r) = early_response {\n        ResponseType::Standard(r)\n    } else if let Some(cached) = const_router.get_cached_route(&http_method, &request.url.path) {\n        let mut resp = Response {\n            status_code: cached.status.as_u16(),\n            response_type: \"text\".to_string(),\n            headers: Headers::new(None),\n            description: cached.body.to_vec(),\n            file_path: None,\n            cookies: Cookies::new(),\n        };\n        for (k, v) in cached.headers.as_ref() {\n            resp.headers.set(k.clone(), v.clone());\n        }\n        ResponseType::Standard(resp)\n    } else if let Some((function, route_params)) = router.get_route(&http_method, &request.url.path)\n    {\n        request.path_params = route_params;\n        match execute_http_function(&request, &function).await {\n            Ok(r) => r,\n            Err(e) => {\n                error!(\n                    \"Error executing route function for `{}`: {}\",\n                    request.url.path,\n                    get_traceback(&e)\n                );\n                ResponseType::Standard(Response::internal_server_error(None))\n            }\n        }\n    } else {\n        ResponseType::Standard(Response::not_found(None))\n    };\n\n    let is_excluded = excluded_response_headers_paths\n        .get_ref()\n        .as_ref()\n        .is_some_and(|paths| paths.contains(&request.url.path));\n\n    if !is_excluded {\n        response.headers_mut().extend(&global_response_headers);\n    }\n\n    // After middleware\n    let after_middlewares = middleware_router.get_global_middlewares(&MiddlewareType::AfterRequest);\n    let route_after = middleware_router.get_route(&MiddlewareType::AfterRequest, &route);\n\n    if !after_middlewares.is_empty() || route_after.is_some() {\n        let mut all_after = after_middlewares;\n        if let Some((function, _)) = route_after {\n            all_after.push(function);\n        }\n        for after_middleware in all_after {\n            if let ResponseType::Standard(std_response) = response {\n                response = match execute_after_middleware_function(\n                    &request,\n                    &std_response,\n                    &after_middleware,\n                )\n                .await\n                {\n                    Ok(MiddlewareReturn::Request(_)) => {\n                        error!(\"After middleware returned a request\");\n                        return ResponseType::Standard(Response::internal_server_error(None));\n                    }\n                    Ok(MiddlewareReturn::Response(r)) => ResponseType::Standard(r),\n                    Err(e) => {\n                        let msg = match e.downcast_ref::<PyErr>() {\n                            Some(py_err) => get_traceback(py_err),\n                            None => format!(\"{e:?}\"),\n                        };\n                        error!(\n                            \"Error executing after middleware for `{}`: {}\",\n                            request.url.path, msg\n                        );\n                        return ResponseType::Standard(Response::internal_server_error(Some(\n                            &std_response.headers,\n                        )));\n                    }\n                };\n            }\n        }\n    }\n\n    response\n}\n\nfn get_traceback(error: &PyErr) -> String {\n    Python::with_gil(|py| -> String {\n        if let Some(traceback) = error.traceback(py) {\n            let msg = match traceback.format() {\n                Ok(msg) => format!(\"\\n{msg} {error}\"),\n                Err(e) => e.to_string(),\n            };\n            return msg;\n        };\n\n        error.value(py).to_string()\n    })\n}\n"
  },
  {
    "path": "src/shared_socket.rs",
    "content": "use pyo3::prelude::*;\n\nuse socket2::{Domain, Protocol, Socket, Type};\nuse std::net::{IpAddr, SocketAddr};\n\n#[pyclass]\n#[derive(Debug)]\npub struct SocketHeld {\n    pub socket: Socket,\n}\n\n#[pymethods]\nimpl SocketHeld {\n    #[new]\n    pub fn new(ip: String, port: u16) -> PyResult<SocketHeld> {\n        let ip: IpAddr = ip.parse()?;\n        let socket = if ip.is_ipv4() {\n            Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP))?\n        } else {\n            Socket::new(Domain::IPV6, Type::STREAM, Some(Protocol::TCP))?\n        };\n        let address = SocketAddr::new(ip, port);\n        #[cfg(not(target_os = \"windows\"))]\n        socket.set_reuse_port(true)?;\n        socket.set_reuse_address(true)?;\n        socket.set_nodelay(true)?;\n        socket.bind(&address.into())?;\n        socket.listen(16384)?;\n        socket.set_nonblocking(true)?;\n\n        Ok(SocketHeld { socket })\n    }\n\n    pub fn try_clone(&self) -> PyResult<SocketHeld> {\n        let copied = self.socket.try_clone()?;\n        Ok(SocketHeld { socket: copied })\n    }\n}\n\nimpl SocketHeld {\n    pub fn get_socket(&self) -> Socket {\n        self.socket.try_clone().unwrap()\n    }\n}\n"
  },
  {
    "path": "src/types/cookie.rs",
    "content": "use actix_web::cookie::{Cookie as ActixCookie, SameSite};\nuse log::debug;\nuse pyo3::prelude::*;\nuse std::collections::HashMap;\nuse std::fmt;\n\n/// Error type for cookie validation failures\n#[derive(Debug, Clone)]\npub enum CookieError {\n    InvalidSameSite(String),\n}\n\nimpl fmt::Display for CookieError {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            CookieError::InvalidSameSite(msg) => write!(f, \"Invalid SameSite value: {}\", msg),\n        }\n    }\n}\n\nimpl std::error::Error for CookieError {}\n\n/// Parse SameSite value (case-insensitive)\nfn parse_same_site(value: &str) -> Result<SameSite, CookieError> {\n    match value.to_lowercase().as_str() {\n        \"strict\" => Ok(SameSite::Strict),\n        \"lax\" => Ok(SameSite::Lax),\n        \"none\" => Ok(SameSite::None),\n        _ => Err(CookieError::InvalidSameSite(format!(\n            \"must be 'Strict', 'Lax', or 'None', got '{}'\",\n            value\n        ))),\n    }\n}\n\n/// A cookie with optional attributes following RFC 6265\n#[pyclass(name = \"Cookie\")]\n#[derive(Debug, Clone)]\npub struct Cookie {\n    #[pyo3(get, set)]\n    pub value: String,\n    #[pyo3(get, set)]\n    pub path: Option<String>,\n    #[pyo3(get, set)]\n    pub domain: Option<String>,\n    #[pyo3(get, set)]\n    pub max_age: Option<i64>,\n    #[pyo3(get, set)]\n    pub expires: Option<String>,\n    #[pyo3(get, set)]\n    pub secure: bool,\n    #[pyo3(get, set)]\n    pub http_only: bool,\n    #[pyo3(get, set)]\n    pub same_site: Option<String>,\n}\n\n#[pymethods]\nimpl Cookie {\n    #[new]\n    #[pyo3(signature = (value, path=None, domain=None, max_age=None, expires=None, secure=false, http_only=false, same_site=None))]\n    pub fn new(\n        value: String,\n        path: Option<String>,\n        domain: Option<String>,\n        max_age: Option<i64>,\n        expires: Option<String>,\n        secure: bool,\n        http_only: bool,\n        same_site: Option<String>,\n    ) -> Self {\n        Self {\n            value,\n            path,\n            domain,\n            max_age,\n            expires,\n            secure,\n            http_only,\n            same_site,\n        }\n    }\n\n    /// Create a cookie configured for deletion (expires immediately with max_age=0)\n    #[staticmethod]\n    pub fn deleted() -> Self {\n        Self {\n            value: String::new(),\n            path: Some(\"/\".to_string()),\n            domain: None,\n            max_age: Some(0),\n            expires: None,\n            secure: false,\n            http_only: false,\n            same_site: None,\n        }\n    }\n\n    fn __repr__(&self) -> String {\n        format!(\n            \"Cookie(value={:?}, path={:?}, domain={:?}, max_age={:?}, expires={:?}, secure={}, http_only={}, same_site={:?})\",\n            self.value, self.path, self.domain, self.max_age, self.expires, self.secure, self.http_only, self.same_site\n        )\n    }\n}\n\nimpl Cookie {\n    /// Serialize cookie to Set-Cookie header value format.\n    ///\n    /// Uses actix-web's cookie crate for RFC 6265 compliant serialization\n    /// which handles proper validation and encoding of cookie values.\n    ///\n    /// Returns an error if SameSite has an invalid value.\n    pub fn to_header_value(&self, name: &str) -> Result<String, CookieError> {\n        let mut builder = ActixCookie::build(name, &self.value);\n\n        if let Some(ref path) = self.path {\n            builder = builder.path(path.clone());\n        }\n        if let Some(ref domain) = self.domain {\n            builder = builder.domain(domain.clone());\n        }\n        if let Some(max_age) = self.max_age {\n            builder = builder.max_age(actix_web::cookie::time::Duration::seconds(max_age));\n        }\n        // Note: expires is skipped as max_age is the modern/preferred approach\n        // The expires field is kept for API compatibility but not used in serialization\n        if self.expires.is_some() {\n            debug!(\"Cookie 'expires' attribute is deprecated; use 'max_age' instead\");\n        }\n        if self.secure {\n            builder = builder.secure(true);\n        }\n        if self.http_only {\n            builder = builder.http_only(true);\n        }\n        if let Some(ref same_site) = self.same_site {\n            builder = builder.same_site(parse_same_site(same_site)?);\n        }\n\n        Ok(builder.finish().to_string())\n    }\n}\n\n/// A collection of cookies keyed by name\n#[pyclass(name = \"Cookies\")]\n#[derive(Debug, Clone, Default)]\npub struct Cookies {\n    pub cookies: HashMap<String, Cookie>,\n}\n\n#[pymethods]\nimpl Cookies {\n    #[new]\n    pub fn new() -> Self {\n        Self {\n            cookies: HashMap::new(),\n        }\n    }\n\n    /// Set a cookie with the given name\n    pub fn set(&mut self, name: String, cookie: Cookie) {\n        self.cookies.insert(name, cookie);\n    }\n\n    /// Get a cookie by name\n    pub fn get(&self, name: String) -> Option<Cookie> {\n        self.cookies.get(&name).cloned()\n    }\n\n    /// Remove a cookie from the collection (does not delete it from the browser)\n    pub fn remove(&mut self, name: &str) {\n        self.cookies.remove(name);\n    }\n\n    /// Mark a cookie for deletion by setting it to expire immediately.\n    /// This sets max_age=0 which tells the browser to delete the cookie.\n    pub fn delete(&mut self, name: String) {\n        self.cookies.insert(name, Cookie::deleted());\n    }\n\n    /// Check if the collection is empty\n    pub fn is_empty(&self) -> bool {\n        self.cookies.is_empty()\n    }\n\n    /// Get the number of cookies\n    pub fn len(&self) -> usize {\n        self.cookies.len()\n    }\n\n    /// Get all cookie names\n    pub fn keys(&self) -> Vec<String> {\n        self.cookies.keys().cloned().collect()\n    }\n\n    pub fn __setitem__(&mut self, name: String, cookie: Cookie) {\n        self.set(name, cookie);\n    }\n\n    pub fn __getitem__(&self, name: String) -> Option<Cookie> {\n        self.get(name)\n    }\n\n    pub fn __contains__(&self, name: String) -> bool {\n        self.cookies.contains_key(&name)\n    }\n\n    pub fn __len__(&self) -> usize {\n        self.len()\n    }\n\n    pub fn __iter__(slf: PyRef<'_, Self>) -> CookiesIter {\n        CookiesIter {\n            keys: slf.cookies.keys().cloned().collect(),\n            index: 0,\n        }\n    }\n\n    fn __repr__(&self) -> String {\n        format!(\"{:?}\", self.cookies)\n    }\n}\n\n/// Iterator for Cookies collection\n#[pyclass]\npub struct CookiesIter {\n    keys: Vec<String>,\n    index: usize,\n}\n\n#[pymethods]\nimpl CookiesIter {\n    fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {\n        slf\n    }\n\n    fn __next__(mut slf: PyRefMut<'_, Self>) -> Option<String> {\n        if slf.index < slf.keys.len() {\n            let key = slf.keys[slf.index].clone();\n            slf.index += 1;\n            Some(key)\n        } else {\n            None\n        }\n    }\n}\n"
  },
  {
    "path": "src/types/function_info.rs",
    "content": "use std::{\n    collections::hash_map::DefaultHasher,\n    hash::{Hash, Hasher},\n};\n\nuse pyo3::{prelude::*, types::PyDict};\n\n#[pyclass]\n#[derive(Debug, PartialEq, Eq, Hash)]\npub enum MiddlewareType {\n    #[pyo3(name = \"BEFORE_REQUEST\")]\n    BeforeRequest = 0,\n    #[pyo3(name = \"AFTER_REQUEST\")]\n    AfterRequest = 1,\n}\n\n#[pymethods]\nimpl MiddlewareType {\n    // This is needed because pyo3 doesn't support hashing enums from Python\n    pub fn __hash__(&self) -> u64 {\n        let mut hasher = DefaultHasher::new();\n        self.hash(&mut hasher);\n        hasher.finish()\n    }\n}\n\n#[pyclass]\n#[derive(Debug)]\npub struct FunctionInfo {\n    #[pyo3(get, set)]\n    pub handler: Py<PyAny>,\n    #[pyo3(get, set)]\n    pub is_async: bool,\n    #[pyo3(get, set)]\n    pub number_of_params: u8,\n    #[pyo3(get, set)]\n    pub args: Py<PyDict>,\n    #[pyo3(get, set)]\n    pub kwargs: Py<PyDict>,\n}\n\n#[pymethods]\nimpl FunctionInfo {\n    #[new]\n    pub fn new(\n        handler: Py<PyAny>,\n        is_async: bool,\n        number_of_params: u8,\n        args: Py<PyDict>,\n        kwargs: Py<PyDict>,\n    ) -> Self {\n        Self {\n            handler,\n            is_async,\n            number_of_params,\n            args,\n            kwargs,\n        }\n    }\n}\n\nimpl Clone for FunctionInfo {\n    fn clone(&self) -> Self {\n        Python::with_gil(|py| Self {\n            handler: self.handler.clone_ref(py),\n            is_async: self.is_async,\n            number_of_params: self.number_of_params,\n            args: self.args.clone_ref(py),\n            kwargs: self.kwargs.clone_ref(py),\n        })\n    }\n}\n"
  },
  {
    "path": "src/types/headers.rs",
    "content": "use actix_http::header::HeaderMap;\nuse dashmap::DashMap;\nuse pyo3::prelude::*;\nuse pyo3::types::{PyDict, PyList};\nuse pyo3::IntoPyObject;\n\n// Custom Multimap class\n#[pyclass(name = \"Headers\")]\n#[derive(Clone, Debug, Default)]\npub struct Headers {\n    pub headers: DashMap<String, Vec<String>>,\n}\n\n#[pymethods]\nimpl Headers {\n    #[new]\n    pub fn new(default_headers: Option<&Bound<'_, PyDict>>) -> Self {\n        match default_headers {\n            Some(default_headers) => {\n                let headers = DashMap::new();\n                for (key, value) in default_headers {\n                    let key = key.to_string().to_lowercase();\n\n                    let new_value = value.downcast::<PyList>();\n\n                    if let Ok(new_value) = new_value {\n                        let value: Vec<String> = new_value.iter().map(|x| x.to_string()).collect();\n                        headers.entry(key).or_insert_with(Vec::new).extend(value);\n                    } else {\n                        let value = value.to_string();\n                        headers.entry(key).or_insert_with(Vec::new).push(value);\n                    }\n                }\n                Headers { headers }\n            }\n            None => Headers {\n                headers: DashMap::new(),\n            },\n        }\n    }\n\n    pub fn set(&mut self, key: String, value: String) {\n        self.headers.insert(key.to_lowercase(), vec![value]);\n    }\n\n    pub fn append(&mut self, key: String, value: String) {\n        self.headers\n            .entry(key.to_lowercase())\n            .or_default()\n            .push(value);\n    }\n\n    pub fn get_all(&self, py: Python, key: String) -> Py<PyList> {\n        match self.headers.get(&key.to_lowercase()) {\n            Some(values) => {\n                let py_values = PyList::new(\n                    py,\n                    values\n                        .iter()\n                        .map(|value| value.into_pyobject(py).unwrap().into_any()),\n                );\n                py_values.expect(\"get-all failed\").into()\n            }\n            None => PyList::empty(py).into(),\n        }\n    }\n\n    pub fn get(&self, key: String) -> Option<String> {\n        // return the last value\n        match self.headers.get(&key.to_lowercase()) {\n            Some(iter) => {\n                let (_, values) = iter.pair();\n                let last_value = values.last().unwrap();\n                Some(last_value.to_string())\n            }\n            None => None,\n        }\n    }\n\n    pub fn get_headers(&self, py: Python) -> Py<PyDict> {\n        // return as a dict of lists\n        let dict = PyDict::new(py);\n        for iter in self.headers.iter() {\n            let (key, values) = iter.pair();\n            let py_values: Bound<'_, PyList> = PyList::new(\n                py,\n                values\n                    .iter()\n                    .map(|value| value.into_pyobject(py).unwrap().into_any()),\n            )\n            .expect(\"get-all failed\");\n            dict.set_item(key, py_values).unwrap();\n        }\n        dict.into()\n    }\n\n    pub fn contains(&self, key: String) -> bool {\n        self.headers.contains_key(&key.to_lowercase())\n    }\n\n    pub fn populate_from_dict(&mut self, headers: &Bound<PyDict>) {\n        for (key, value) in headers.iter() {\n            let key = key.to_string().to_lowercase();\n            let new_value = value.downcast::<PyList>();\n\n            if let Ok(new_value) = new_value {\n                let value: Vec<String> = new_value.iter().map(|x| x.to_string()).collect();\n                self.headers.entry(key).or_default().extend(value);\n            } else {\n                let value = value.to_string();\n                self.headers.entry(key).or_default().push(value);\n            }\n        }\n    }\n\n    pub fn is_empty(&self) -> bool {\n        self.headers.is_empty()\n    }\n\n    fn __eq__(&self, other: &Headers) -> bool {\n        if self.headers.is_empty() && other.headers.is_empty() {\n            return true;\n        }\n\n        if self.headers.len() != other.headers.len() {\n            return false;\n        }\n\n        for iter in &self.headers {\n            let (key, values) = iter.pair();\n            match other.headers.get(key) {\n                Some(other_values) => {\n                    if values.len() != other_values.len()\n                        || !values.iter().all(|v| other_values.contains(v))\n                    {\n                        return false;\n                    }\n                }\n                None => return false,\n            }\n        }\n\n        true\n    }\n\n    pub fn __contains__(&self, key: String) -> bool {\n        self.contains(key)\n    }\n\n    pub fn __repr__(&self) -> String {\n        format!(\"{:?}\", self.headers)\n    }\n\n    pub fn __setitem__(&mut self, key: String, value: String) {\n        self.set(key, value);\n    }\n\n    pub fn __getitem__(&self, key: String) -> Option<String> {\n        self.get(key)\n    }\n}\n\nimpl Headers {\n    pub fn remove(&mut self, key: &str) {\n        self.headers.remove(&key.to_lowercase());\n    }\n\n    pub fn clear(&mut self) {\n        self.headers.clear();\n    }\n\n    pub fn extend(&mut self, headers: &Headers) {\n        for iter in headers.headers.iter() {\n            let (key, values) = iter.pair();\n            let mut entry = self.headers.entry(key.clone()).or_default();\n            entry.extend(values.iter().cloned());\n        }\n    }\n\n    pub fn from_actix_headers(req_headers: &HeaderMap) -> Self {\n        let headers = Headers::default();\n\n        for (key, value) in req_headers {\n            let key = key.to_string().to_lowercase();\n            let value = match value.to_str() {\n                Ok(value) => value.to_string(),\n                Err(_) => continue,\n            };\n            headers.headers.entry(key).or_default().push(value);\n        }\n\n        headers\n    }\n}\n"
  },
  {
    "path": "src/types/identity.rs",
    "content": "use std::collections::HashMap;\n\nuse pyo3::{pyclass, pymethods};\n\n#[pyclass]\n#[derive(Debug, Clone)]\npub struct Identity {\n    #[pyo3(get, set)]\n    claims: HashMap<String, String>,\n}\n\n#[pymethods]\nimpl Identity {\n    #[new]\n    pub fn new(claims: HashMap<String, String>) -> Self {\n        Self { claims }\n    }\n}\n"
  },
  {
    "path": "src/types/mod.rs",
    "content": "use log::debug;\nuse pyo3::{\n    exceptions::PyValueError,\n    prelude::*,\n    types::{PyBytes, PyString},\n};\n\npub mod cookie;\npub mod function_info;\npub mod headers;\npub mod identity;\npub mod multimap;\npub mod request;\npub mod response;\n\n#[allow(clippy::large_enum_variant)]\npub enum MiddlewareReturn {\n    Request(request::Request),\n    Response(response::Response),\n}\n\n#[pyclass]\n#[allow(clippy::upper_case_acronyms)]\n#[derive(Debug, Clone, PartialEq, Eq, Hash)]\npub enum HttpMethod {\n    GET,\n    POST,\n    PUT,\n    DELETE,\n    PATCH,\n    HEAD,\n    OPTIONS,\n    CONNECT,\n    TRACE,\n}\n\nimpl HttpMethod {\n    pub fn is_supported(method: &actix_web::http::Method) -> bool {\n        matches!(\n            *method,\n            actix_web::http::Method::GET\n                | actix_web::http::Method::POST\n                | actix_web::http::Method::PUT\n                | actix_web::http::Method::DELETE\n                | actix_web::http::Method::PATCH\n                | actix_web::http::Method::HEAD\n                | actix_web::http::Method::OPTIONS\n                | actix_web::http::Method::CONNECT\n                | actix_web::http::Method::TRACE\n        )\n    }\n\n    pub fn from_actix_method(method: &actix_web::http::Method) -> Result<Self, &'static str> {\n        match *method {\n            actix_web::http::Method::GET => Ok(Self::GET),\n            actix_web::http::Method::POST => Ok(Self::POST),\n            actix_web::http::Method::PUT => Ok(Self::PUT),\n            actix_web::http::Method::DELETE => Ok(Self::DELETE),\n            actix_web::http::Method::PATCH => Ok(Self::PATCH),\n            actix_web::http::Method::HEAD => Ok(Self::HEAD),\n            actix_web::http::Method::OPTIONS => Ok(Self::OPTIONS),\n            actix_web::http::Method::CONNECT => Ok(Self::CONNECT),\n            actix_web::http::Method::TRACE => Ok(Self::TRACE),\n            _ => Err(\"Method Not Allowed\"),\n        }\n    }\n}\n\n// https://github.com/sparckles/Robyn/pull/987#discussion_r2191722539\nimpl std::fmt::Display for HttpMethod {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        match self {\n            HttpMethod::GET => write!(f, \"GET\"),\n            HttpMethod::POST => write!(f, \"POST\"),\n            HttpMethod::PUT => write!(f, \"PUT\"),\n            HttpMethod::DELETE => write!(f, \"DELETE\"),\n            HttpMethod::PATCH => write!(f, \"PATCH\"),\n            HttpMethod::HEAD => write!(f, \"HEAD\"),\n            HttpMethod::OPTIONS => write!(f, \"OPTIONS\"),\n            HttpMethod::CONNECT => write!(f, \"CONNECT\"),\n            HttpMethod::TRACE => write!(f, \"TRACE\"),\n        }\n    }\n}\n\n#[pyclass]\n#[derive(Default, Debug, Clone)]\npub struct Url {\n    #[pyo3(get)]\n    pub scheme: String,\n    #[pyo3(get)]\n    pub host: String,\n    #[pyo3(get)]\n    pub path: String,\n}\n\n#[pymethods]\nimpl Url {\n    #[new]\n    pub fn new(scheme: &str, host: &str, path: &str) -> Self {\n        Self {\n            scheme: scheme.to_string(),\n            host: host.to_string(),\n            path: path.to_string(),\n        }\n    }\n}\n\npub fn get_body_from_pyobject(body: &Bound<'_, PyAny>) -> PyResult<Vec<u8>> {\n    if let Ok(s) = body.downcast::<PyString>() {\n        Ok(s.to_string().into_bytes())\n    } else if let Ok(b) = body.downcast::<PyBytes>() {\n        Ok(b.as_bytes().to_vec())\n    } else {\n        debug!(\"Could not convert specified body to bytes\");\n        Ok(vec![])\n    }\n}\n\npub fn get_description_from_pyobject(description: &Bound<'_, PyAny>) -> PyResult<Vec<u8>> {\n    if let Ok(s) = description.downcast::<PyString>() {\n        Ok(s.to_string().into_bytes())\n    } else if let Ok(b) = description.downcast::<PyBytes>() {\n        Ok(b.as_bytes().to_vec())\n    } else {\n        debug!(\"Could not convert specified response description to bytes\");\n        Ok(vec![])\n    }\n}\n\npub fn check_body_type(py: Python, body: &Py<PyAny>) -> PyResult<()> {\n    if body.downcast_bound::<PyString>(py).is_err() && body.downcast_bound::<PyBytes>(py).is_err() {\n        return Err(PyValueError::new_err(\n            \"Could not convert specified body to bytes\",\n        ));\n    };\n    Ok(())\n}\n\npub fn check_description_type(py: Python, body: &Py<PyAny>) -> PyResult<()> {\n    if body.downcast_bound::<PyString>(py).is_err() && body.downcast_bound::<PyBytes>(py).is_err() {\n        return Err(PyValueError::new_err(\n            \"Could not convert specified response description to bytes\",\n        ));\n    };\n    Ok(())\n}\n"
  },
  {
    "path": "src/types/multimap.rs",
    "content": "use log::debug;\nuse pyo3::prelude::*;\nuse pyo3::types::{PyDict, PyList};\nuse std::collections::HashMap;\n\n// Custom Multimap class\n#[pyclass(subclass)]\n#[derive(Clone, Debug, Default)]\npub struct QueryParams {\n    pub queries: HashMap<String, Vec<String>>,\n}\n\n#[pymethods]\nimpl QueryParams {\n    #[new]\n    pub fn new() -> Self {\n        QueryParams {\n            queries: HashMap::new(),\n        }\n    }\n\n    pub fn set(&mut self, key: String, value: String) {\n        debug!(\"Setting key: {} to value: {}\", key, value);\n        self.queries.entry(key).or_default().push(value);\n        debug!(\"Multimap: {:?}\", self.queries);\n    }\n\n    pub fn get(&self, key: String, default: Option<String>) -> Option<String> {\n        match self.queries.get(&key) {\n            Some(values) => values.last().cloned(),\n            None => default,\n        }\n    }\n\n    pub fn get_first(&self, key: String) -> Option<String> {\n        match self.queries.get(&key) {\n            Some(values) => values.first().cloned(),\n            None => None,\n        }\n    }\n\n    pub fn empty(&self) -> bool {\n        self.queries.is_empty()\n    }\n\n    pub fn contains(&self, key: String) -> bool {\n        self.queries.contains_key(&key)\n    }\n\n    pub fn get_all(&self, key: String) -> Option<Vec<String>> {\n        self.queries.get(&key).cloned()\n    }\n\n    pub fn extend(&mut self, other: &mut Self) {\n        for (key, values) in other.queries.iter_mut() {\n            for value in values.iter() {\n                self.set(key.clone(), value.clone());\n            }\n        }\n    }\n\n    pub fn to_dict(&self, py: Python) -> PyResult<Py<PyDict>> {\n        let dict = PyDict::new(py);\n        for (key, values) in self.queries.iter() {\n            let values = PyList::new(py, values.iter());\n            dict.set_item(key, values?)?;\n        }\n        Ok(dict.into())\n    }\n\n    pub fn __contains__(&self, key: String) -> bool {\n        self.queries.contains_key(&key)\n    }\n\n    pub fn __repr__(&self) -> String {\n        format!(\"{:?}\", self.queries)\n    }\n}\n\nimpl QueryParams {\n    pub fn from_hashmap(map: HashMap<String, Vec<String>>) -> Self {\n        let mut multimap = QueryParams::new();\n        for (key, values) in map {\n            for value in values {\n                multimap.set(key.clone(), value);\n            }\n        }\n        multimap\n    }\n\n    pub fn from_py_dict(py: Python, dict: Py<PyDict>) -> Self {\n        let mut multimap = QueryParams::new();\n        for (key, value) in dict.bind(py).iter() {\n            let key = key.extract::<String>().unwrap();\n            let value = value.extract::<String>().unwrap();\n            multimap.set(key, value);\n        }\n        multimap\n    }\n\n    pub fn contains_key(&self, key: &str) -> bool {\n        self.queries.contains_key(key)\n    }\n\n    pub fn insert(&mut self, key: String, value: Vec<String>) {\n        self.queries.insert(key, value);\n    }\n\n    pub fn get_mut(&mut self, key: &str) -> Option<&Vec<String>> {\n        self.queries.get(key)\n    }\n}\n"
  },
  {
    "path": "src/types/request.rs",
    "content": "use actix_multipart::Multipart;\nuse actix_web::{\n    web::{self, BytesMut},\n    Error, HttpRequest,\n};\nuse futures_util::StreamExt as _;\nuse pyo3::types::{PyBytes, PyDict, PyList, PyString};\nuse pyo3::{exceptions::PyValueError, prelude::*, IntoPyObject};\nuse serde_json::Value;\nuse std::collections::HashMap;\n\nuse crate::types::{check_body_type, get_body_from_pyobject, Url};\n\nuse super::{headers::Headers, identity::Identity, multimap::QueryParams};\n\n#[derive(Default, Debug, Clone, FromPyObject)]\npub struct Request {\n    pub query_params: QueryParams,\n    pub headers: Headers,\n    pub method: String,\n    pub path_params: HashMap<String, String>,\n    // https://pyo3.rs/v0.19.2/function.html?highlight=from_py_#per-argument-options\n    #[pyo3(from_py_with = get_body_from_pyobject)]\n    pub body: Vec<u8>,\n    pub url: Url,\n    pub ip_addr: Option<String>,\n    pub identity: Option<Identity>,\n    pub form_data: Option<HashMap<String, String>>,\n    pub files: Option<HashMap<String, Vec<u8>>>,\n}\n\nimpl<'py> IntoPyObject<'py> for Request {\n    type Target = PyAny;\n    type Output = Bound<'py, Self::Target>;\n    type Error = PyErr;\n    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {\n        let headers: Py<Headers> = self.headers.into_pyobject(py)?.extract()?;\n        let path_params = self.path_params.into_pyobject(py)?.extract()?;\n\n        let body = if self.body.is_empty() {\n            PyString::new(py, \"\").into_any()\n        } else {\n            match std::str::from_utf8(&self.body) {\n                Ok(s) => PyString::new(py, s).into_any(),\n                Err(_) => PyBytes::new(py, &self.body).into_any(),\n            }\n        };\n\n        let form_data: Py<PyDict> = match self.form_data {\n            Some(data) if !data.is_empty() => {\n                let dict = PyDict::new(py);\n                // Use with_capacity equivalent by setting items directly\n                for (key, value) in data {\n                    dict.set_item(key, value)?;\n                }\n                dict.into()\n            }\n            _ => PyDict::new(py).into(),\n        };\n\n        let files: Py<PyDict> = match self.files {\n            Some(data) if !data.is_empty() => {\n                let dict = PyDict::new(py);\n                for (key, value) in data {\n                    let bytes = PyBytes::new(py, &value);\n                    dict.set_item(key, bytes)?;\n                }\n                dict.into()\n            }\n            _ => PyDict::new(py).into(),\n        };\n\n        let request = PyRequest {\n            query_params: self.query_params,\n            path_params,\n            headers,\n            body: body.into(),\n            method: self.method,\n            url: self.url,\n            ip_addr: self.ip_addr,\n            identity: self.identity,\n            form_data,\n            files,\n        };\n        Ok(Py::new(py, request)?.into_bound(py).into_any())\n    }\n}\n\nasync fn handle_multipart(\n    mut payload: Multipart,\n    files: &mut HashMap<String, Vec<u8>>,\n    form_data: &mut HashMap<String, String>,\n    body: &mut Vec<u8>,\n) -> Result<(), Error> {\n    // Iterate over multipart stream\n\n    while let Some(item) = payload.next().await {\n        let mut field = item?;\n\n        let mut data = Vec::new();\n        while let Some(chunk) = field.next().await {\n            let data_chunk = chunk?;\n            data.extend_from_slice(&data_chunk);\n        }\n\n        let content_disposition = field.content_disposition();\n        let field_name = content_disposition.get_name().unwrap_or_default();\n        let file_name = content_disposition.get_filename().map(|s| s.to_string());\n\n        body.extend_from_slice(&data);\n\n        if let Some(name) = file_name {\n            files.insert(name, data);\n        } else if let Ok(text) = String::from_utf8(data) {\n            form_data.insert(field_name.to_string(), text);\n        }\n    }\n\n    Ok(())\n}\n\nimpl Request {\n    pub async fn from_actix_request(\n        req: &HttpRequest,\n        mut payload: web::Payload,\n        global_headers: &Headers,\n    ) -> Result<Self, Error> {\n        let mut query_params: QueryParams = QueryParams::new();\n        let mut form_data: HashMap<String, String> = HashMap::new();\n        let mut files = HashMap::new();\n\n        if !req.query_string().is_empty() {\n            let split = req.query_string().split('&');\n            for s in split {\n                let path_params = s.split_once('=').unwrap_or((s, \"\"));\n                let key = path_params.0.to_string();\n                let value = path_params.1.to_string();\n\n                query_params.set(key, value);\n            }\n        }\n\n        let mut headers = Headers::from_actix_headers(req.headers());\n        headers.extend(global_headers);\n\n        let body: Vec<u8> = if headers.contains(String::from(\"content-type\"))\n            && headers\n                .get(String::from(\"content-type\"))\n                .is_some_and(|val| val.contains(\"multipart/form-data\"))\n        {\n            let multipart = Multipart::new(req.headers(), payload);\n            let mut body_local: Vec<u8> = Vec::new();\n\n            handle_multipart(multipart, &mut files, &mut form_data, &mut body_local).await?;\n\n            body_local\n        } else {\n            let mut body_local = BytesMut::new();\n            while let Some(chunk) = payload.next().await {\n                let chunk = chunk.expect(\"Failed to read chunk from payload\");\n                body_local.extend_from_slice(&chunk);\n            }\n            body_local.freeze().to_vec()\n        };\n\n        let route_path = {\n            let mut path = req.path();\n            if path.ends_with(\"/\") && path.len() > 1 {\n                path = &path[..path.len() - 1]\n            }\n            path\n        };\n\n        let url = Url::new(\n            req.connection_info().scheme(),\n            req.connection_info().host(),\n            route_path,\n        );\n        let ip_addr = req.peer_addr().map(|val| val.ip().to_string());\n\n        Ok(Self {\n            query_params,\n            headers,\n            method: req.method().as_str().to_owned(),\n            path_params: HashMap::new(),\n            body,\n            url,\n            ip_addr,\n            identity: None,\n            form_data: Some(form_data),\n            files: Some(files),\n        })\n    }\n}\n\n#[pyclass(name = \"Request\")]\n#[derive(Clone)]\npub struct PyRequest {\n    #[pyo3(get, set)]\n    pub query_params: QueryParams,\n    #[pyo3(get, set)]\n    pub headers: Py<Headers>,\n    #[pyo3(get, set)]\n    pub path_params: Py<PyDict>,\n    #[pyo3(get, set)]\n    pub identity: Option<Identity>,\n    #[pyo3(get)]\n    pub body: Py<PyAny>,\n    #[pyo3(get)]\n    pub method: String,\n    #[pyo3(get)]\n    pub url: Url,\n    #[pyo3(get)]\n    pub ip_addr: Option<String>,\n    #[pyo3(get, set)]\n    pub form_data: Py<PyDict>,\n    #[pyo3(get, set)]\n    pub files: Py<PyDict>,\n}\n\n#[pymethods]\nimpl PyRequest {\n    #[new]\n    #[allow(clippy::too_many_arguments)]\n    pub fn new(\n        query_params: QueryParams,\n        headers: Py<Headers>,\n        path_params: Py<PyDict>,\n        body: Py<PyAny>,\n        method: String,\n        url: Url,\n        form_data: Py<PyDict>,\n        files: Py<PyDict>,\n        identity: Option<Identity>,\n        ip_addr: Option<String>,\n    ) -> Self {\n        Self {\n            query_params,\n            headers,\n            path_params,\n            identity,\n            body,\n            method,\n            url,\n            form_data,\n            files,\n            ip_addr,\n        }\n    }\n\n    #[setter]\n    pub fn set_body(&mut self, py: Python, body: Py<PyAny>) -> PyResult<()> {\n        check_body_type(py, &body)?;\n        self.body = body;\n        Ok(())\n    }\n\n    pub fn json(&self, py: Python) -> PyResult<Py<PyAny>> {\n        match self.body.downcast_bound::<PyString>(py) {\n            Ok(python_string) => {\n                let parsed: Value = serde_json::from_str(python_string.extract()?)\n                    .map_err(|e| PyValueError::new_err(format!(\"Invalid JSON: {}\", e)))?;\n                json_value_to_py(py, &parsed)\n            }\n            Err(e) => Err(e.into()),\n        }\n    }\n}\n\n/// Maximum allowed recursion depth for JSON parsing to prevent stack overflow attacks.\nconst MAX_JSON_DEPTH: usize = 128;\n\n/// Converts a serde_json::Value to a Python object with proper type preservation.\n/// This is a convenience wrapper that starts recursion with MAX_JSON_DEPTH.\nfn json_value_to_py(py: Python, value: &Value) -> PyResult<Py<PyAny>> {\n    json_value_to_py_with_depth(py, value, MAX_JSON_DEPTH)\n}\n\n/// Converts a serde_json::Value to a Python object with recursion depth limiting.\nfn json_value_to_py_with_depth(py: Python, value: &Value, depth: usize) -> PyResult<Py<PyAny>> {\n    if depth == 0 {\n        return Err(PyValueError::new_err(\n            \"JSON nesting depth exceeds maximum allowed limit\",\n        ));\n    }\n\n    match value {\n        Value::Null => Ok(py.None()),\n        Value::Bool(b) => Ok(b.into_pyobject(py)?.to_owned().into_any().unbind()),\n        Value::Number(n) => {\n            if let Some(i) = n.as_i64() {\n                Ok(i.into_pyobject(py)?.into_any().unbind())\n            } else if let Some(u) = n.as_u64() {\n                Ok(u.into_pyobject(py)?.into_any().unbind())\n            } else if let Some(f) = n.as_f64() {\n                Ok(f.into_pyobject(py)?.into_any().unbind())\n            } else {\n                Err(PyValueError::new_err(\"Invalid number in JSON\"))\n            }\n        }\n        Value::String(s) => Ok(s.as_str().into_pyobject(py)?.into_any().unbind()),\n        Value::Array(arr) => {\n            let list = PyList::empty(py);\n            for item in arr {\n                list.append(json_value_to_py_with_depth(py, item, depth - 1)?)?;\n            }\n            Ok(list.into_pyobject(py)?.into_any().unbind())\n        }\n        Value::Object(map) => {\n            let dict = PyDict::new(py);\n            for (k, v) in map {\n                dict.set_item(k, json_value_to_py_with_depth(py, v, depth - 1)?)?;\n            }\n            Ok(dict.into_pyobject(py)?.into_any().unbind())\n        }\n    }\n}\n"
  },
  {
    "path": "src/types/response.rs",
    "content": "use actix_http::{body::BoxBody, StatusCode};\nuse actix_web::{web::Bytes, HttpRequest, HttpResponse, HttpResponseBuilder, Responder};\nuse futures::Stream;\nuse pyo3::{\n    exceptions::PyIOError,\n    prelude::*,\n    types::{PyBytes, PyDict},\n    Bound, IntoPyObject,\n};\nuse std::pin::Pin;\nuse tokio;\n\nuse crate::io_helpers::{apply_hashmap_headers, read_file};\nuse crate::types::{check_body_type, check_description_type, get_description_from_pyobject};\n\nuse super::cookie::{Cookie, Cookies};\nuse super::headers::Headers;\n\n#[derive(Debug, Clone)]\npub struct Response {\n    pub status_code: u16,\n    pub response_type: String,\n    pub headers: Headers,\n    pub description: Vec<u8>,\n    pub file_path: Option<String>,\n    pub cookies: Cookies,\n}\n\n#[derive(Debug, Clone)]\npub struct StreamingResponse {\n    pub status_code: u16,\n    pub headers: Headers,\n    pub content_generator: Py<PyAny>,\n}\n\n#[derive(Debug)]\npub enum ResponseType {\n    Standard(Response),\n    Streaming(StreamingResponse),\n}\n\nimpl ResponseType {\n    pub fn headers_mut(&mut self) -> &mut Headers {\n        match self {\n            ResponseType::Standard(response) => &mut response.headers,\n            ResponseType::Streaming(streaming_response) => &mut streaming_response.headers,\n        }\n    }\n}\n\nimpl Responder for ResponseType {\n    type Body = BoxBody;\n\n    fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {\n        match self {\n            ResponseType::Standard(response) => response.respond_to(req),\n            ResponseType::Streaming(streaming_response) => streaming_response.respond_to(req),\n        }\n    }\n}\n\nimpl Responder for Response {\n    type Body = BoxBody;\n\n    fn respond_to(self, _req: &HttpRequest) -> HttpResponse<Self::Body> {\n        let mut response_builder = HttpResponseBuilder::new(\n            StatusCode::from_u16(self.status_code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),\n        );\n        apply_hashmap_headers(&mut response_builder, &self.headers);\n\n        // Apply cookies as Set-Cookie headers\n        for (name, cookie) in &self.cookies.cookies {\n            match cookie.to_header_value(name) {\n                Ok(header_value) => {\n                    response_builder.append_header((\"Set-Cookie\", header_value));\n                }\n                Err(e) => {\n                    log::debug!(\"Skipping invalid cookie '{}': {}\", name, e);\n                }\n            }\n        }\n\n        response_builder.body(self.description)\n    }\n}\n\nimpl StreamingResponse {\n    pub fn new(status_code: u16, headers: Headers, content_generator: Py<PyAny>) -> Self {\n        Self {\n            status_code,\n            headers,\n            content_generator,\n        }\n    }\n}\n\nimpl Responder for StreamingResponse {\n    type Body = BoxBody;\n\n    fn respond_to(self, _req: &HttpRequest) -> HttpResponse<Self::Body> {\n        let mut response_builder = HttpResponseBuilder::new(\n            StatusCode::from_u16(self.status_code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),\n        );\n\n        apply_hashmap_headers(&mut response_builder, &self.headers);\n\n        // Optimized headers for SSE streaming\n        response_builder\n            .append_header((\"Connection\", \"keep-alive\"))\n            .append_header((\"X-Accel-Buffering\", \"no\")) // Disable nginx buffering\n            .append_header((\"Cache-Control\", \"no-cache, no-store, must-revalidate\"))\n            .append_header((\"Pragma\", \"no-cache\"))\n            .append_header((\"Expires\", \"0\"));\n\n        // Create the optimized stream from the Python generator\n        let stream = create_python_stream(self.content_generator);\n\n        // Build streaming response with optimized settings\n        response_builder.streaming(stream)\n    }\n}\n\nfn create_python_stream(\n    generator: Py<PyAny>,\n) -> Pin<Box<dyn Stream<Item = Result<Bytes, actix_web::Error>> + Send>> {\n    Box::pin(futures::stream::unfold(generator, |generator| async move {\n        match tokio::task::spawn_blocking(move || {\n            Python::with_gil(|py| {\n                let gen = generator.bind(py);\n\n                match gen.call_method0(\"__next__\") {\n                    Ok(value) => value.extract::<String>().ok().map(|s| (s, generator)),\n                    Err(e) => {\n                        if !e.is_instance_of::<pyo3::exceptions::PyStopIteration>(py) {\n                            log::error!(\"Generator error: {}\", e);\n                        }\n                        None\n                    }\n                }\n            })\n        })\n        .await\n        {\n            Ok(Some((string_value, generator))) => Some((Ok(Bytes::from(string_value)), generator)),\n            _ => None,\n        }\n    }))\n}\n\nimpl Response {\n    fn default_text_headers() -> Headers {\n        let mut headers = Headers::new(None);\n        headers.set(\"Content-Type\".to_string(), \"text/plain\".to_string());\n        headers\n    }\n\n    pub fn not_found(headers: Option<&Headers>) -> Self {\n        const NOT_FOUND_BYTES: &[u8] = b\"Not found\";\n\n        Self {\n            status_code: 404,\n            response_type: \"text\".to_string(),\n            headers: headers.cloned().unwrap_or_else(Self::default_text_headers),\n            description: NOT_FOUND_BYTES.to_vec(),\n            file_path: None,\n            cookies: Cookies::new(),\n        }\n    }\n\n    pub fn internal_server_error(headers: Option<&Headers>) -> Self {\n        const SERVER_ERROR_BYTES: &[u8] = b\"Internal server error\";\n\n        Self {\n            status_code: 500,\n            response_type: \"text\".to_string(),\n            headers: headers.cloned().unwrap_or_else(Self::default_text_headers),\n            description: SERVER_ERROR_BYTES.to_vec(),\n            file_path: None,\n            cookies: Cookies::new(),\n        }\n    }\n\n    pub fn method_not_allowed(headers: Option<&Headers>) -> Self {\n        const METHOD_NOT_ALLOWED_BYTES: &[u8] = b\"Method not allowed\";\n\n        Self {\n            status_code: 405,\n            response_type: \"text\".to_string(),\n            headers: headers.cloned().unwrap_or_else(Self::default_text_headers),\n            description: METHOD_NOT_ALLOWED_BYTES.to_vec(),\n            file_path: None,\n            cookies: Cookies::new(),\n        }\n    }\n}\n\nimpl<'py> IntoPyObject<'py> for Response {\n    type Target = PyAny;\n    type Output = Bound<'py, Self::Target>;\n    type Error = PyErr;\n    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {\n        let headers = self.headers.into_pyobject(py)?.extract()?;\n\n        let description = if self.description.is_empty() {\n            \"\".into_pyobject(py)?.into_any()\n        } else {\n            match String::from_utf8(self.description.clone()) {\n                Ok(description) => description.into_pyobject(py)?.into_any(),\n                Err(_) => PyBytes::new(py, &self.description).into_any(),\n            }\n        };\n\n        let cookies: Py<Cookies> = Py::new(py, self.cookies)?;\n\n        let response = PyResponse {\n            status_code: self.status_code,\n            response_type: self.response_type,\n            headers,\n            description: description.into(),\n            file_path: self.file_path,\n            cookies,\n        };\n        Ok(Py::new(py, response)?.into_bound(py).into_any())\n    }\n}\n\n#[pyclass(name = \"Response\")]\n#[derive(Debug, Clone)]\npub struct PyResponse {\n    #[pyo3(get)]\n    pub status_code: u16,\n    #[pyo3(get)]\n    pub response_type: String,\n    #[pyo3(get, set)]\n    pub headers: Py<Headers>,\n    #[pyo3(get)]\n    pub description: Py<PyAny>,\n    #[pyo3(get)]\n    pub file_path: Option<String>,\n    #[pyo3(get)]\n    pub cookies: Py<Cookies>,\n}\n\n#[pyclass(name = \"StreamingResponse\")]\n#[derive(Debug, Clone)]\npub struct PyStreamingResponse {\n    #[pyo3(get)]\n    pub status_code: u16,\n    #[pyo3(get, set)]\n    pub headers: Py<Headers>,\n    #[pyo3(get)]\n    pub content: Py<PyAny>,\n    #[pyo3(get)]\n    pub media_type: String,\n}\n\n#[pymethods]\nimpl PyStreamingResponse {\n    #[new]\n    pub fn new(\n        py: Python,\n        content: Py<PyAny>,\n        status_code: Option<u16>,\n        headers: Option<Bound<'_, PyAny>>,\n        media_type: Option<String>,\n    ) -> PyResult<Self> {\n        let status_code = status_code.unwrap_or(200);\n        let media_type = media_type.unwrap_or_else(|| \"text/event-stream\".to_string());\n\n        let headers_output: Py<Headers> = if let Some(headers) = headers {\n            if let Ok(headers_dict) = headers.downcast::<PyDict>() {\n                let headers = Headers::new(Some(headers_dict));\n                Py::new(py, headers)?\n            } else if let Ok(headers) = headers.extract::<Py<Headers>>() {\n                headers\n            } else {\n                return Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(\n                    \"headers must be a Headers instance or a dict\",\n                ));\n            }\n        } else {\n            let mut headers = Headers::new(None);\n            if media_type == \"text/event-stream\" {\n                headers.set(\"Content-Type\".to_string(), \"text/event-stream\".to_string());\n                headers.set(\"Cache-Control\".to_string(), \"no-cache\".to_string());\n                headers.set(\"Connection\".to_string(), \"keep-alive\".to_string());\n            } else {\n                // For non-SSE streaming responses, still set appropriate headers\n                headers.set(\"Content-Type\".to_string(), media_type.clone());\n            }\n            Py::new(py, headers)?\n        };\n\n        Ok(Self {\n            status_code,\n            headers: headers_output,\n            content,\n            media_type,\n        })\n    }\n}\n\n#[pymethods]\nimpl PyResponse {\n    // To do: Add check for content-type in header and change response_type accordingly\n    #[new]\n    pub fn new(\n        py: Python,\n        status_code: u16,\n        headers: Bound<'_, PyAny>,\n        description: Py<PyAny>,\n    ) -> PyResult<Self> {\n        check_body_type(py, &description)?;\n\n        let headers_output: Py<Headers> = if let Ok(headers_dict) = headers.downcast::<PyDict>() {\n            // Here you'd have logic to create a Headers instance from a PyDict\n            // For simplicity, let's assume you have a method `from_dict` on Headers for this\n            let headers = Headers::new(Some(headers_dict)); // Hypothetical method\n            Py::new(py, headers)?\n        } else if let Ok(headers) = headers.extract::<Py<Headers>>() {\n            // If it's already a Py<Headers>, use it directly\n            headers\n        } else {\n            return Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(\n                \"headers must be a Headers instance or a dict\",\n            ));\n        };\n\n        let cookies: Py<Cookies> = Py::new(py, Cookies::new())?;\n\n        Ok(Self {\n            status_code,\n            // we should be handling based on headers but works for now\n            response_type: \"text\".to_string(),\n            headers: headers_output,\n            description,\n            file_path: None,\n            cookies,\n        })\n    }\n\n    #[setter]\n    pub fn set_description(&mut self, py: Python, description: Py<PyAny>) -> PyResult<()> {\n        check_description_type(py, &description)?;\n        self.description = description;\n        Ok(())\n    }\n\n    #[setter]\n    pub fn set_file_path(&mut self, py: Python, file_path: &str) -> PyResult<()> {\n        self.response_type = \"static_file\".to_string();\n        self.file_path = Some(file_path.to_string());\n\n        match read_file(file_path) {\n            Ok(content) => {\n                self.description = PyBytes::new(py, &content).into();\n                Ok(())\n            }\n            Err(e) => Err(PyIOError::new_err(format!(\"Failed to read file: {}\", e))),\n        }\n    }\n\n    #[pyo3(signature = (key, value, path=None, domain=None, max_age=None, expires=None, secure=false, http_only=false, same_site=None))]\n    pub fn set_cookie(\n        &mut self,\n        py: Python,\n        key: &str,\n        value: &str,\n        path: Option<String>,\n        domain: Option<String>,\n        max_age: Option<i64>,\n        expires: Option<String>,\n        secure: bool,\n        http_only: bool,\n        same_site: Option<String>,\n    ) -> PyResult<()> {\n        let cookie = Cookie::new(\n            value.to_string(),\n            path,\n            domain,\n            max_age,\n            expires,\n            secure,\n            http_only,\n            same_site,\n        );\n\n        self.cookies\n            .try_borrow_mut(py)\n            .expect(\"value already borrowed\")\n            .set(key.to_string(), cookie);\n        Ok(())\n    }\n}\n\nimpl FromPyObject<'_, '_> for Response {\n    type Error = PyErr;\n\n    #[inline]\n    fn extract(obj: pyo3::Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {\n        let type_name = obj.get_type().name()?;\n        if type_name != \"Response\" {\n            return Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(format!(\n                \"Expected Response, got {}\",\n                type_name\n            )));\n        }\n\n        let status_code: u16 = obj.getattr(\"status_code\")?.extract()?;\n        let response_type: String = obj.getattr(\"response_type\")?.extract()?;\n        let headers: Headers = obj.getattr(\"headers\")?.extract()?;\n        let description: Vec<u8> = get_description_from_pyobject(&obj.getattr(\"description\")?)?;\n        let file_path: Option<String> = obj.getattr(\"file_path\")?.extract()?;\n        let cookies: Cookies = obj.getattr(\"cookies\")?.extract()?;\n\n        Ok(Response {\n            status_code,\n            response_type,\n            headers,\n            description,\n            file_path,\n            cookies,\n        })\n    }\n}\n\nimpl FromPyObject<'_, '_> for StreamingResponse {\n    type Error = PyErr;\n\n    #[inline]\n    fn extract(obj: pyo3::Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {\n        if !obj.hasattr(\"content\").unwrap_or(false) || !obj.hasattr(\"media_type\").unwrap_or(false) {\n            return Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(\n                \"Object is missing required StreamingResponse attributes\",\n            ));\n        }\n\n        let status_code: u16 = obj.getattr(\"status_code\")?.extract()?;\n        let mut headers: Headers = obj.getattr(\"headers\")?.extract()?;\n\n        let media_type: String = obj\n            .getattr(\"media_type\")\n            .and_then(|a| a.extract())\n            .unwrap_or_else(|_| \"text/event-stream\".to_string());\n\n        if media_type == \"text/event-stream\" {\n            headers.set(\"Content-Type\".to_string(), \"text/event-stream\".to_string());\n            if headers.get(\"Cache-Control\".to_string()).is_none() {\n                headers.set(\"Cache-Control\".to_string(), \"no-cache\".to_string());\n            }\n            if headers.get(\"Connection\".to_string()).is_none() {\n                headers.set(\"Connection\".to_string(), \"keep-alive\".to_string());\n            }\n        }\n\n        let content: pyo3::Py<PyAny> = obj.getattr(\"content\")?.unbind();\n\n        Ok(StreamingResponse::new(status_code, headers, content))\n    }\n}\n"
  },
  {
    "path": "src/websockets/mod.rs",
    "content": "pub mod registry;\n\nuse crate::executors::web_socket_executors::execute_ws_function;\nuse crate::types::function_info::FunctionInfo;\nuse crate::types::multimap::QueryParams;\nuse registry::{Close, SendMessageToAll, SendText};\n\nuse actix::prelude::*;\nuse actix::{Actor, AsyncContext, StreamHandler};\nuse actix_web::{web, Error, HttpRequest, HttpResponse};\nuse actix_web_actors::ws;\nuse log::debug;\nuse once_cell::sync::OnceCell;\nuse parking_lot::RwLock;\nuse pyo3::prelude::*;\nuse pyo3::IntoPyObject;\nuse pyo3_async_runtimes::TaskLocals;\nuse std::sync::Arc;\nuse tokio::sync::mpsc;\nuse uuid::Uuid;\n\nuse crate::runtime;\n\nuse registry::{Register, WebSocketRegistry};\nuse std::collections::HashMap;\n\n/// A Rust-backed channel receiver exposed to Python.\n/// Python handlers call `await channel.receive()` to get the next message.\n/// Returns the message string, or None when the connection is closed.\n#[pyclass]\npub struct WebSocketChannel {\n    receiver: Arc<tokio::sync::Mutex<mpsc::UnboundedReceiver<Option<String>>>>,\n}\n\n#[pymethods]\nimpl WebSocketChannel {\n    /// Await the next message from the WebSocket.\n    /// Returns the message string, or None if the connection was closed.\n    fn receive<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {\n        let receiver = self.receiver.clone();\n        pyo3_async_runtimes::tokio::future_into_py(py, async move {\n            let mut rx = receiver.lock().await;\n            match rx.recv().await {\n                Some(Some(msg)) => Ok(Some(msg)),\n                Some(None) | None => Ok(None),\n            }\n        })\n    }\n}\n\n/// Define HTTP actor\n#[pyclass]\npub struct WebSocketConnector {\n    pub id: Uuid,\n    pub router: HashMap<String, FunctionInfo>,\n    pub task_locals: TaskLocals,\n    pub registry_addr: Addr<WebSocketRegistry>,\n    pub query_params: QueryParams,\n    /// Sender side of the message channel (stays in the Actix actor).\n    pub message_sender: Option<mpsc::UnboundedSender<Option<String>>>,\n    /// Receiver side exposed to Python via WebSocketChannel.\n    pub message_channel: Option<Py<WebSocketChannel>>,\n}\n\n// By default mailbox capacity is 16 messages.\nimpl Actor for WebSocketConnector {\n    type Context = ws::WebsocketContext<Self>;\n\n    fn started(&mut self, ctx: &mut Self::Context) {\n        let addr = ctx.address();\n        self.registry_addr.do_send(Register {\n            id: self.id,\n            addr: addr.clone(),\n        });\n\n        let (tx, rx) = mpsc::unbounded_channel::<Option<String>>();\n        self.message_sender = Some(tx);\n        self.message_channel = Python::with_gil(|py| {\n            Some(\n                Py::new(\n                    py,\n                    WebSocketChannel {\n                        receiver: Arc::new(tokio::sync::Mutex::new(rx)),\n                    },\n                )\n                .unwrap(),\n            )\n        });\n\n        let function = self.router.get(\"connect\").unwrap();\n        execute_ws_function(function, &self.task_locals, ctx, self);\n\n        debug!(\"Actor is alive\");\n    }\n\n    fn stopped(&mut self, ctx: &mut Self::Context) {\n        // Drop the sender to close the channel.\n        // This causes any pending `channel.receive()` in Python to return None,\n        // which the WebSocketAdapter converts to WebSocketDisconnect.\n        self.message_sender.take();\n\n        let function = self.router.get(\"close\").unwrap();\n        execute_ws_function(function, &self.task_locals, ctx, self);\n        debug!(\"Actor is dead\");\n    }\n}\n\nimpl Clone for WebSocketConnector {\n    fn clone(&self) -> Self {\n        let task_locals_clone = Python::with_gil(|py| self.task_locals.clone_ref(py));\n\n        Self {\n            id: self.id,\n            router: self.router.clone(),\n            task_locals: task_locals_clone,\n            registry_addr: self.registry_addr.clone(),\n            query_params: self.query_params.clone(),\n            message_sender: self.message_sender.clone(),\n            message_channel: Python::with_gil(|py| {\n                self.message_channel.as_ref().map(|c| c.clone_ref(py))\n            }),\n        }\n    }\n}\n\nimpl Handler<SendText> for WebSocketConnector {\n    type Result = ();\n\n    fn handle(&mut self, msg: SendText, ctx: &mut Self::Context) {\n        if self.id == msg.recipient_id {\n            ctx.text(msg.message.clone());\n            if msg.message == \"Connection closed\" {\n                // Close the WebSocket connection\n                ctx.stop();\n            }\n        }\n    }\n}\n\n/// Handler for ws::Message message\nimpl StreamHandler<Result<ws::Message, ws::ProtocolError>> for WebSocketConnector {\n    fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {\n        match msg {\n            Ok(ws::Message::Ping(msg)) => {\n                debug!(\"Ping message {:?}\", msg);\n                ctx.pong(&msg)\n            }\n            Ok(ws::Message::Pong(msg)) => {\n                debug!(\"Pong message {:?}\", msg);\n            }\n            Ok(ws::Message::Text(text)) => {\n                debug!(\"Text message received {:?}\", text);\n                if let Some(ref sender) = self.message_sender {\n                    let _ = sender.send(Some(text.to_string()));\n                }\n            }\n            Ok(ws::Message::Binary(bin)) => ctx.binary(bin),\n            Ok(ws::Message::Close(_close_reason)) => {\n                debug!(\"Socket was closed\");\n                // Drop sender to signal channel closure so receive() returns None.\n                // The close handler is called once from stopped().\n                self.message_sender.take();\n                ctx.stop();\n            }\n            _ => (),\n        }\n    }\n}\n\n#[pymethods]\nimpl WebSocketConnector {\n    pub fn sync_send_to(&self, recipient_id: String, message: String) {\n        let recipient_id = Uuid::parse_str(&recipient_id).unwrap();\n\n        match self.registry_addr.try_send(SendText {\n            message,\n            sender_id: self.id,\n            recipient_id,\n        }) {\n            Ok(_) => println!(\"Message sent successfully\"),\n            Err(e) => println!(\"Failed to send message: {}\", e),\n        }\n    }\n\n    pub fn async_send_to(\n        &self,\n        py: Python,\n        recipient_id: String,\n        message: String,\n    ) -> PyResult<Py<PyAny>> {\n        let registry = self.registry_addr.clone();\n        let recipient_id = Uuid::parse_str(&recipient_id).unwrap();\n        let sender_id = self.id;\n\n        let awaitable = runtime::future_into_py(py, async move {\n            match registry.try_send(SendText {\n                message,\n                sender_id,\n                recipient_id,\n            }) {\n                Ok(_) => println!(\"Message sent successfully\"),\n                Err(e) => println!(\"Failed to send message: {}\", e),\n            }\n            Ok(())\n        })?;\n\n        Ok(awaitable.into_pyobject(py)?.into_any().into())\n    }\n\n    pub fn sync_broadcast(&self, message: String) {\n        let registry = self.registry_addr.clone();\n        match registry.try_send(SendMessageToAll {\n            message,\n            sender_id: self.id,\n        }) {\n            Ok(_) => println!(\"Message sent successfully\"),\n            Err(e) => println!(\"Failed to send message: {}\", e),\n        }\n    }\n\n    pub fn async_broadcast(&self, py: Python, message: String) -> PyResult<Py<PyAny>> {\n        let registry = self.registry_addr.clone();\n        let sender_id = self.id;\n\n        let awaitable = runtime::future_into_py(py, async move {\n            match registry.try_send(SendMessageToAll { message, sender_id }) {\n                Ok(_) => println!(\"Message sent successfully\"),\n                Err(e) => println!(\"Failed to send message: {}\", e),\n            }\n            Ok(())\n        })?;\n\n        Ok(awaitable.into_pyobject(py)?.into_any().into())\n    }\n\n    pub fn close(&self) {\n        self.registry_addr.do_send(Close { id: self.id });\n    }\n\n    #[getter]\n    pub fn get_id(&self) -> String {\n        self.id.to_string()\n    }\n\n    #[getter]\n    pub fn get_query_params(&self) -> QueryParams {\n        self.query_params.clone()\n    }\n\n    /// Get the message channel for WebSocket handlers.\n    #[getter]\n    pub fn get_message_channel(&self, py: Python) -> Option<Py<WebSocketChannel>> {\n        self.message_channel.as_ref().map(|c| c.clone_ref(py))\n    }\n}\n\nstatic REGISTRY_ADDRESSES: OnceCell<RwLock<HashMap<String, Addr<WebSocketRegistry>>>> =\n    OnceCell::new();\n\nfn get_or_init_registry_for_endpoint(endpoint: String) -> Addr<WebSocketRegistry> {\n    let map_lock = REGISTRY_ADDRESSES.get_or_init(|| RwLock::new(HashMap::new()));\n\n    {\n        let map = map_lock.read();\n        if let Some(registry_addr) = map.get(&endpoint) {\n            return registry_addr.clone();\n        }\n    }\n\n    let new_registry = WebSocketRegistry::new().start();\n\n    {\n        let mut map = map_lock.write();\n        map.insert(endpoint, new_registry.clone());\n    }\n\n    new_registry\n}\n\npub async fn start_web_socket(\n    req: HttpRequest,\n    stream: web::Payload,\n    router: HashMap<String, FunctionInfo>,\n    task_locals: TaskLocals,\n    endpoint: String,\n    max_frame_size: usize,\n) -> Result<HttpResponse, Error> {\n    let registry_addr = get_or_init_registry_for_endpoint(endpoint);\n\n    let mut query_params = QueryParams::new();\n\n    if !req.query_string().is_empty() {\n        let split = req.query_string().split('&');\n        for s in split {\n            let path_params = s.split_once('=').unwrap_or((s, \"\"));\n            let key = path_params.0.to_string();\n            let value = path_params.1.to_string();\n\n            query_params.set(key, value);\n        }\n    }\n\n    ws::WsResponseBuilder::new(\n        WebSocketConnector {\n            router,\n            task_locals,\n            id: Uuid::new_v4(),\n            registry_addr,\n            query_params,\n            message_sender: None,\n            message_channel: None,\n        },\n        &req,\n        stream,\n    )\n    .frame_size(max_frame_size)\n    .start()\n}\n"
  },
  {
    "path": "src/websockets/registry.rs",
    "content": "use actix::prelude::*;\nuse actix::Actor;\nuse pyo3::prelude::*;\nuse uuid::Uuid;\n\nuse std::collections::HashMap;\n\nuse crate::websockets::WebSocketConnector;\n\n#[derive(Default)]\n#[pyclass]\npub struct WebSocketRegistry {\n    // A map of client IDs to their Actor addresses.\n    clients: HashMap<Uuid, Addr<WebSocketConnector>>,\n}\n\nimpl actix::Supervised for WebSocketRegistry {}\n\nimpl SystemService for WebSocketRegistry {}\n\nimpl Actor for WebSocketRegistry {\n    type Context = Context<Self>;\n}\n\npub struct Register {\n    pub id: Uuid,\n    pub addr: Addr<WebSocketConnector>,\n}\n\nimpl Message for Register {\n    type Result = ();\n}\n\nimpl Handler<Register> for WebSocketRegistry {\n    type Result = ();\n\n    fn handle(&mut self, msg: Register, _ctx: &mut Self::Context) {\n        self.clients.insert(msg.id, msg.addr);\n    }\n}\n\n// New message for sending text to a specific client\npub struct SendText {\n    pub recipient_id: Uuid,\n    pub message: String,\n    pub sender_id: Uuid,\n}\n\nimpl Message for SendText {\n    type Result = ();\n}\n\nimpl WebSocketRegistry {\n    pub fn new() -> Self {\n        Self {\n            clients: HashMap::new(),\n        }\n    }\n\n    pub fn start() -> Addr<Self> {\n        Self::from_registry()\n    }\n}\n\nimpl Handler<SendText> for WebSocketRegistry {\n    type Result = ();\n\n    fn handle(&mut self, msg: SendText, _ctx: &mut Self::Context) {\n        let recipient_id = msg.recipient_id;\n\n        if let Some(client_addr) = self.clients.get(&recipient_id) {\n            client_addr.do_send(msg);\n        } else {\n            log::warn!(\"No client found for id: {}\", recipient_id);\n        }\n    }\n}\n\npub struct SendMessageToAll {\n    pub message: String,\n    pub sender_id: Uuid,\n}\n\nimpl Message for SendMessageToAll {\n    type Result = ();\n}\n\nimpl Handler<SendMessageToAll> for WebSocketRegistry {\n    type Result = ();\n\n    fn handle(&mut self, msg: SendMessageToAll, _ctx: &mut Self::Context) {\n        for (id, client) in &self.clients {\n            client.do_send(SendText {\n                recipient_id: *id,\n                message: msg.message.clone(),\n                sender_id: msg.sender_id,\n            });\n        }\n    }\n}\n\npub struct Close {\n    pub id: Uuid,\n}\n\nimpl Message for Close {\n    type Result = ();\n}\n\nimpl Handler<Close> for WebSocketRegistry {\n    type Result = ();\n\n    fn handle(&mut self, msg: Close, _ctx: &mut Self::Context) {\n        if let Some(client) = self.clients.remove(&msg.id) {\n            // Send a close message to the client before removing it\n            client.do_send(SendText {\n                recipient_id: msg.id,\n                message: \"Connection closed\".to_string(),\n                sender_id: msg.id,\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "unit_tests/test_cli.py",
    "content": "from unittest.mock import MagicMock, patch\n\nfrom robyn.cli import create_robyn_app, docs, run, start_app_normally, start_dev_server\n\n\n# Unit tests\ndef test_create_robyn_app():\n    with patch(\"robyn.cli.prompt\") as mock_prompt:\n        mock_prompt.return_value = {\n            \"directory\": \"test_dir\",\n            \"docker\": \"N\",\n            \"project_type\": \"no-db\",\n        }\n        with patch(\"robyn.cli.os.makedirs\") as mock_makedirs:\n            with patch(\"robyn.cli.shutil.copytree\") as mock_copytree, patch(\"robyn.os.remove\") as _mock_remove:\n                create_robyn_app()\n                mock_makedirs.assert_called_once()\n                mock_copytree.assert_called_once()\n\n\ndef test_docs():\n    with patch(\"robyn.cli.webbrowser.open\") as mock_open:\n        docs()\n        mock_open.assert_called_once_with(\"https://robyn.tech\")\n\n\ndef test_start_dev_server():\n    config = MagicMock()\n    config.dev = True\n    with patch(\"robyn.cli.setup_reloader\") as mock_setup_reloader:\n        start_dev_server(config, \"test_file.py\")\n        mock_setup_reloader.assert_called_once()\n\n\ndef test_start_app_normally():\n    config = MagicMock()\n    config.dev = False\n    config.parser.parse_known_args.return_value = (MagicMock(), [])\n    with patch(\"robyn.cli.subprocess.run\") as mock_run:\n        start_app_normally(config)\n        mock_run.assert_called_once()\n\n\n# Integration tests\ndef test_run_create():\n    with patch(\"robyn.cli.Config\") as mock_config:\n        mock_config.return_value.create = True\n        with patch(\"robyn.cli.create_robyn_app\") as mock_create:\n            run()\n            mock_create.assert_called_once()\n"
  },
  {
    "path": "unit_tests/test_env_populator.py",
    "content": "import os\nimport pathlib\n\nimport pytest\n\nfrom robyn.env_populator import load_vars, parser\n\n\n@pytest.fixture\ndef env_file():\n    CONTENT = \"\"\"ROBYN_PORT=8080\nROBYN_HOST=127.0.0.1\"\"\"\n    path = pathlib.Path(__file__).parent\n    env_path = path / \"robyn.env\"\n    env_path.write_text(CONTENT)\n    yield\n    env_path.unlink()\n    # Clean up environment variables if they exist\n    if \"ROBYN_PORT\" in os.environ:\n        del os.environ[\"ROBYN_PORT\"]\n    if \"ROBYN_HOST\" in os.environ:\n        del os.environ[\"ROBYN_HOST\"]\n\n\n# this tests if a connection can be made to the server with the correct port imported from the env file\n@pytest.mark.benchmark\ndef test_env_population(env_file):\n    # Clean up environment variables before test to ensure fresh state\n    if \"ROBYN_PORT\" in os.environ:\n        del os.environ[\"ROBYN_PORT\"]\n    if \"ROBYN_HOST\" in os.environ:\n        del os.environ[\"ROBYN_HOST\"]\n\n    path = pathlib.Path(__file__).parent\n    env_path = path / \"robyn.env\"\n    load_vars(variables=parser(config_path=env_path))\n    PORT = os.environ[\"ROBYN_PORT\"]\n    HOST = os.environ[\"ROBYN_HOST\"]\n    assert PORT == \"8080\"\n    assert HOST == \"127.0.0.1\"\n"
  },
  {
    "path": "unit_tests/test_openapi_issue_1270.py",
    "content": "from robyn.openapi import OpenAPI, OpenAPIInfo\n\n\ndef test_openapi_nested_path_parsing():\n    \"\"\"\n    Test for Issue #1270: Ensures nested path parameters\n    like /users/:id/posts/:post_id are parsed correctly.\n    \"\"\"\n    # Initialize OpenAPI with default info\n    openapi = OpenAPI(info=OpenAPIInfo())\n\n    # A dummy handler for the test\n    def mock_handler():\n        pass\n\n    # 1. Test Standard Nested Route\n    openapi.add_openapi_path_obj(\n        route_type=\"get\", endpoint=\"/users/:id/posts/:post_id\", openapi_name=\"get_user_posts\", openapi_tags=[\"testing\"], handler=mock_handler\n    )\n\n    # 2. Test Parameters with Underscores\n    openapi.add_openapi_path_obj(\n        route_type=\"get\", endpoint=\"/orgs/:org_id/members/:user_id\", openapi_name=\"get_org_member\", openapi_tags=[\"testing\"], handler=mock_handler\n    )\n\n    # 3. Test Triple Nested Parameters\n    openapi.add_openapi_path_obj(route_type=\"get\", endpoint=\"/a/:p1/b/:p2/c/:p3\", openapi_name=\"triple_nested\", openapi_tags=[\"testing\"], handler=mock_handler)\n\n    # 4. Test Route Without Parameters (Static)\n    openapi.add_openapi_path_obj(route_type=\"get\", endpoint=\"/health\", openapi_name=\"health_check\", openapi_tags=[\"testing\"], handler=mock_handler)\n\n    generated_spec = openapi.get_openapi_config()\n    paths = generated_spec[\"paths\"]\n\n    # Assertions for Case 1: Standard Nested\n    expected_path_1 = \"/users/{id}/posts/{post_id}\"\n    assert expected_path_1 in paths\n    params_1 = [p[\"name\"] for p in paths[expected_path_1][\"get\"][\"parameters\"]]\n    assert \"id\" in params_1\n    assert \"post_id\" in params_1\n    assert \"id/posts\" not in params_1\n\n    # Assertions for Case 2: Underscores\n    expected_path_2 = \"/orgs/{org_id}/members/{user_id}\"\n    assert expected_path_2 in paths\n    params_2 = [p[\"name\"] for p in paths[expected_path_2][\"get\"][\"parameters\"]]\n    assert \"org_id\" in params_2\n    assert \"user_id\" in params_2\n\n    # Assertions for Case 3: Triple Nested\n    expected_path_3 = \"/a/{p1}/b/{p2}/c/{p3}\"\n    assert expected_path_3 in paths\n    params_3 = [p[\"name\"] for p in paths[expected_path_3][\"get\"][\"parameters\"]]\n    assert len(params_3) == 3\n\n    # Assertions for Case 4: Static Route\n    assert \"/health\" in paths\n    assert len(paths[\"/health\"][\"get\"][\"parameters\"]) == 0\n"
  },
  {
    "path": "unit_tests/test_request_object.py",
    "content": "from robyn.robyn import Headers, QueryParams, Request, Url\n\n\ndef test_request_object():\n    url = Url(\n        scheme=\"https\",\n        host=\"localhost\",\n        path=\"/user\",\n    )\n    request = Request(\n        query_params=QueryParams(),\n        headers=Headers({\"Content-Type\": \"application/json\"}),\n        path_params={},\n        body=\"\",\n        method=\"GET\",\n        url=url,\n        ip_addr=None,\n        identity=None,\n        form_data={},\n        files={},\n    )\n\n    assert request.url.scheme == \"https\"\n    assert request.url.host == \"localhost\"\n    print(request.headers.get(\"Content-Type\"))\n    assert request.headers.get(\"Content-Type\") == \"application/json\"\n    assert request.method == \"GET\"\n"
  },
  {
    "path": "unit_tests/test_unsupported_types.py",
    "content": "import pytest\n\nfrom robyn.robyn import Headers, Response\n\n\nclass A:\n    pass\n\n\nbad_bodies = [\n    None,\n    1,\n    True,\n    A,\n    {\"body\": \"OK\"},\n    [\"OK\", b\"OK\"],\n    Response(\n        status_code=200,\n        headers=Headers({}),\n        description=b\"OK\",\n    ),\n]\n\ngood_bodies = [\"OK\", b\"OK\"]\n\n\n@pytest.mark.parametrize(\"description\", bad_bodies)\ndef test_bad_body_types(description):\n    with pytest.raises(ValueError):\n        _ = Response(\n            status_code=200,\n            headers=Headers({}),\n            description=description,\n        )\n\n\n@pytest.mark.parametrize(\"description\", good_bodies)\ndef test_good_body_types(description):\n    _ = Response(\n        status_code=200,\n        headers=Headers({}),\n        description=description,\n    )\n"
  }
]