[
  {
    "path": ".appveyor.yml",
    "content": "version: \"{branch}.{build}\"\n\nenvironment:\n  matrix:\n    - TOXENV: py36-no-ext\n      PYTHON: \"C:\\\\Python36-x64\"\n      PYTHON_VERSION: \"3.6.x\"\n      PYTHON_ARCH: \"64\"\n\n    - TOXENV: py37-no-ext\n      PYTHON: \"C:\\\\Python37-x64\"\n      PYTHON_VERSION: \"3.7.x\"\n      PYTHON_ARCH: \"64\"\n\n    - TOXENV: py38-no-ext\n      PYTHON: \"C:\\\\Python38-x64\"\n      PYTHON_VERSION: \"3.8.x\"\n      PYTHON_ARCH: \"64\"\n\n    # - TOXENV: py39-no-ext\n    #   PYTHON: \"C:\\\\Python39-x64\\\\python\"\n    #   PYTHONPATH: \"C:\\\\Python39-x64\"\n    #   PYTHON_VERSION: \"3.9.x\"\n    #   PYTHON_ARCH: \"64\"\n\ninit: SET \"PATH=%PYTHON%;%PYTHON%\\\\Scripts;%PATH%\"\n\ninstall:\n  - pip install tox\n\nbuild: off\n\ntest_script: tox\n\nnotifications:\n  - provider: Email\n    on_build_success: false\n    on_build_status_changed: false\n"
  },
  {
    "path": ".coveragerc",
    "content": "[run]\nbranch = True\nsource = sanic\nomit =\n    site-packages\n    sanic/__main__.py\n    sanic/server/legacy.py\n    sanic/compat.py\n    sanic/simple.py\n    sanic/utils.py\n    sanic/cli\n    sanic/pages\n\n[html]\ndirectory = coverage\n\n[report]\nexclude_lines =\n    no cov\n    no qa\n    noqa\n    NOQA\n    pragma: no cover\n    TYPE_CHECKING\nskip_empty = True\n"
  },
  {
    "path": ".gitattributes",
    "content": "* text=auto\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "*  @sanic-org/sanic-release-managers\n/sanic/ @sanic-org/framework\n/tests/ @sanic-org/framework\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\npatreon: # Replace with a single Patreon username\nopen_collective: sanic-org # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.yml",
    "content": "name: 🐞 Bug report\ndescription: Create a report to help us improve\nlabels: [\"bug\", \"triage\"]\nbody:\n  - type: checkboxes\n    id: existing\n    attributes:\n      label: Is there an existing issue for this?\n      description: Please search to see if an issue already exists for the bug you encountered.\n      options:\n      - label: I have searched the existing issues\n        required: true\n  - type: textarea\n    id: description\n    attributes:\n      label: Describe the bug\n      description: A clear and concise description of what the bug is, make sure to paste any exceptions and tracebacks using markdown code-block syntax to make it easier to read.\n    validations:\n      required: true\n  - type: textarea\n    id: code\n    attributes:\n      label: Code snippet\n      description: |\n          Relevant source code, make sure to remove what is not necessary. Please try and format your code so that it is easier to read. For example:\n\n              ```python\n              from sanic import Sanic\n\n              app = Sanic(\"Example\")\n              ```\n    validations:\n      required: false\n  - type: textarea\n    id: expected\n    attributes:\n      label: Expected Behavior\n      description: A concise description of what you expected to happen.\n    validations:\n      required: false\n  - type: dropdown\n    id: running\n    attributes:\n      label: How do you run Sanic?\n      options:\n        - Sanic CLI\n        - As a module\n        - As a script (`app.run` or `Sanic.serve`)\n        - ASGI\n    validations:\n      required: true\n  - type: dropdown\n    id: os\n    attributes:\n      label: Operating System\n      description: What OS?\n      options:\n        - Linux\n        - MacOS\n        - Windows\n        - Other (tell us in the description)\n    validations:\n      required: true\n  - type: input\n    id: version\n    attributes:\n      label: Sanic Version\n      description: Check startup logs or try `sanic --version`\n    validations:\n      required: true\n  - type: textarea\n    id: additional\n    attributes:\n      label: Additional context\n      description: Add any other context about the problem here.\n    validations:\n      required: false\n\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: Questions and Help\n    url: https://community.sanicframework.org/c/questions-and-help\n    about: Do you need help with Sanic? Ask your questions here.\n  - name: Discussion and Support\n    url: https://discord.gg/FARQzAEMAA\n    about: For live discussion and support, checkout the Sanic Discord server.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature-request.yml",
    "content": "name: 🌟 Feature request\ndescription: Suggest an enhancement for Sanic\nlabels: [\"feature request\"]\nbody:\n  - type: checkboxes\n    id: existing\n    attributes:\n      label: Is there an existing issue for this?\n      description: Please search to see if an issue already exists for the enhancement you are proposing.\n      options:\n      - label: I have searched the existing issues\n        required: true\n  - type: textarea\n    id: description\n    attributes:\n      label: Is your feature request related to a problem? Please describe.\n      description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n    validations:\n      required: false\n  - type: textarea\n    id: code\n    attributes:\n      label: Describe the solution you'd like\n      description: A clear and concise description of what you want to happen.\n    validations:\n      required: true\n  - type: textarea\n    id: additional\n    attributes:\n      label: Additional context\n      description: Add any other context about the problem here.\n    validations:\n      required: false\n\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/rfc.yml",
    "content": "name: 💡 Request for Comments\ndescription: Open an RFC for discussion\nlabels: [\"RFC\"]\nbody:\n  - type: input\n    id: compare\n    attributes:\n      label: Link to code\n      description: If available, share a [comparison](https://github.com/sanic-org/sanic/compare) from a POC branch to main\n      placeholder: https://github.com/sanic-org/sanic/compare/main...some-new-branch\n    validations:\n      required: false\n  - type: textarea\n    id: proposal\n    attributes:\n      label: Proposal\n      description: A thorough discussion of the proposal discussing the problem it solves, potential code, use cases, and impacts\n    validations:\n      required: true\n  - type: textarea\n    id: additional\n    attributes:\n      label: Additional context\n      description: Add any other context that is relevant\n    validations:\n      required: false\n  - type: checkboxes\n    id: breaking\n    attributes:\n      label: Is this a breaking change?\n      options:\n        - label: \"Yes\"\n          required: false\n"
  },
  {
    "path": ".github/stale.yml",
    "content": "# Number of days of inactivity before an issue becomes stale\ndaysUntilStale: 90\n# Number of days of inactivity before a stale issue is closed\ndaysUntilClose: 30\n# Issues with these labels will never be considered stale\nexemptLabels:\n  - bug\n  - urgent\n  - necessary\n  - help wanted\n  - RFC\n# Label to use when marking an issue as stale\nstaleLabel: stale\n# Comment to post when marking an issue as stale. Set to `false` to disable\nmarkComment: >\n  This issue has been automatically marked as stale because it has not had\n  recent activity. It will be closed if no further activity occurs. If this \n  is incorrect, please respond with an update. Thank you for your contributions.\n# Comment to post when closing a stale issue. Set to `false` to disable\ncloseComment: false\n"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "content": "name: \"CodeQL\"\n\non:\n  push:\n    branches:\n      - main\n      - current-release\n      - \"*LTS\"\n  pull_request:\n    branches:\n      - main\n      - current-release\n      - \"*LTS\"\n    types: [opened, synchronize, reopened, ready_for_review]\n  schedule:\n    - cron: '25 16 * * 0'\n\njobs:\n  analyze:\n    if: github.event.pull_request.draft == false\n    name: Analyze\n    runs-on: ubuntu-latest\n\n    strategy:\n      fail-fast: false\n      matrix:\n        language: [ 'python' ]\n\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v2\n\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@v1\n      with:\n        languages: ${{ matrix.language }}\n\n    - name: Autobuild\n      uses: github/codeql-action/autobuild@v1\n\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@v1\n"
  },
  {
    "path": ".github/workflows/coverage-upload.yml",
    "content": "name: Upload coverage to Codecov\non:\n  workflow_run:\n    workflows: [\"Coverage check\"]\n    types:\n      - completed\n\njobs:\n  upload:\n    name: Upload coverage\n    runs-on: ubuntu-latest\n    if: github.event.workflow_run.conclusion == 'success'\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          ref: ${{ github.event.workflow_run.head_sha }}\n\n      - name: Download coverage artifact\n        uses: dawidd6/action-download-artifact@v6\n        with:\n          name: coverage-report\n          run_id: ${{ github.event.workflow_run.id }}\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Get PR number\n        id: pr\n        run: |\n          if [ \"${{ github.event.workflow_run.event }}\" == \"pull_request\" ]; then\n            PR_NUMBERS=$(gh pr list --search \"${{ github.event.workflow_run.head_sha }}\" --state open --json number --jq '.[].number')\n            PR_COUNT=$(printf \"%s\\n\" \"$PR_NUMBERS\" | sed '/^$/d' | wc -l | tr -d ' ')\n            if [ \"$PR_COUNT\" -eq 0 ]; then\n              echo \"Error: No open pull request found for head SHA '${{ github.event.workflow_run.head_sha }}'.\" >&2\n              exit 1\n            elif [ \"$PR_COUNT\" -gt 1 ]; then\n              echo \"Error: Multiple open pull requests ($PR_COUNT) found for head SHA '${{ github.event.workflow_run.head_sha }}':\" >&2\n              printf \"%s\\n\" \"$PR_NUMBERS\" >&2\n              exit 1\n            fi\n            PR_NUMBER=\"$PR_NUMBERS\"\n            echo \"number=${PR_NUMBER}\" >> \"$GITHUB_OUTPUT\"\n          fi\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Upload to Codecov\n        uses: codecov/codecov-action@v4\n        with:\n          token: ${{ secrets.CODECOV_TOKEN }}\n          files: ./coverage.xml\n          fail_ci_if_error: true\n          override_commit: ${{ github.event.workflow_run.head_sha }}\n          override_branch: ${{ github.event.workflow_run.head_branch }}\n          override_pr: ${{ steps.pr.outputs.number }}\n"
  },
  {
    "path": ".github/workflows/coverage.yml",
    "content": "name: Coverage check\non:\n  push:\n    branches:\n      - main\n      - current-release\n      - \"*LTS\"\n    tags:\n      - \"!*\" # Do not execute on tags\n  pull_request:\n    branches:\n      - main\n      - current-release\n      - \"*LTS\"\n\njobs:\n  coverage:\n    name: Check coverage\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n\n    steps:\n      - name: Run coverage\n        uses: sanic-org/simple-tox-action@v1\n        with:\n          python-version: \"3.11\"\n          tox-env: coverage\n          ignore-errors: true\n      - name: Upload coverage artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: coverage-report\n          path: ./coverage.xml\n          retention-days: 1\n          if-no-files-found: error\n"
  },
  {
    "path": ".github/workflows/publish-release.yml",
    "content": "name: Publish release\n\non:\n  release:\n    types: [created]\n\nenv:\n  IS_TEST: false\n  DOCKER_ORG_NAME: sanicframework\n  DOCKER_IMAGE_NAME: sanic\n  DOCKER_BASE_IMAGE_NAME: sanic-build\n  DOCKER_IMAGE_DOCKERFILE: ./docker/Dockerfile\n  DOCKER_BASE_IMAGE_DOCKERFILE: ./docker/Dockerfile-base\n\njobs:\n  generate_info:\n    name: Generate info\n    runs-on: ubuntu-latest\n    outputs:\n      docker-tags: ${{ steps.generate_docker_info.outputs.tags }}\n      pypi-version: ${{ steps.parse_version_tag.outputs.pypi-version }}\n      is-test: ${{ env.IS_TEST }}\n    steps:\n      - name: Parse version tag\n        id: parse_version_tag\n        env:\n          TAG_NAME: ${{ github.event.release.tag_name }}\n        run: |\n          tag_name=\"${{ env.TAG_NAME }}\"\n\n          if [[ ! \"${tag_name}\" =~ ^v([0-9]{2})\\.([0-9]{1,2})\\.([0-9]+)$ ]]; then\n            echo \"::error::Tag name must be in the format vYY.MM.MICRO\"\n            exit 1\n          fi\n\n          year_output=\"year=${BASH_REMATCH[1]}\"\n          month_output=\"month=${BASH_REMATCH[2]}\"\n          pypi_output=\"pypi-version=${tag_name#v}\"\n\n          echo \"${year_output}\"\n          echo \"${month_output}\"\n          echo \"${pypi_output}\"\n\n          echo \"${year_output}\" >> $GITHUB_OUTPUT\n          echo \"${month_output}\" >> $GITHUB_OUTPUT\n          echo \"${pypi_output}\" >> $GITHUB_OUTPUT\n\n      - name: Get latest release\n        id: get_latest_release\n        run: |\n          latest_tag=$(\n            curl -L \\\n              -H \"Accept: application/vnd.github+json\" \\\n              -H \"Authorization: Bearer ${{ github.token }}\" \\\n              -H \"X-GitHub-Api-Version: 2022-11-28\" \\\n              https://api.github.com/repos/${{ github.repository }}/releases/latest \\\n              | jq -r '.tag_name'\n          )\n          echo \"latest_tag=$latest_tag\" >> $GITHUB_OUTPUT\n\n      - name: Generate Docker info\n        id: generate_docker_info\n        run: |\n          tag_year=\"${{ steps.parse_version_tag.outputs.year }}\"\n          tag_month=\"${{ steps.parse_version_tag.outputs.month }}\"\n          latest_tag=\"${{ steps.get_latest_release.outputs.latest_tag }}\"\n          tag=\"${{ github.event.release.tag_name }}\"\n\n          tags=\"${tag_year}.${tag_month}\"\n\n          if [[ \"${tag_month}\" == \"12\" ]]; then\n            tags+=\",lts\"\n            echo \"::notice::Tag ${tag} is LTS version\"\n          else\n            echo \"::notice::Tag ${tag} is not LTS version\"\n          fi\n\n          if [[ \"${latest_tag}\" == \"${{ github.event.release.tag_name }}\" ]]; then\n            tags+=\",latest\"\n            echo \"::notice::Tag ${tag} is marked as latest\"\n          else\n            echo \"::notice::Tag ${tag} is not marked as latest\"\n          fi\n\n          tags_output=\"tags=${tags}\"\n\n          echo \"${tags_output}\"\n          echo \"${tags_output}\" >> $GITHUB_OUTPUT\n\n  publish_package:\n    name: Build and publish package\n    runs-on: ubuntu-latest\n    needs: generate_info\n    environment: release\n    permissions:\n      id-token: write\n    steps:\n    - name: Checkout repo\n      uses: actions/checkout@v3\n\n    - name: Setup Python\n      uses: actions/setup-python@v4\n      with:\n        python-version: \"3.11\"\n\n    - name: Install dependencies\n      run: pip install build twine\n\n    - name: Update package version\n      run: |\n        echo \"__version__ = \\\"${{ needs.generate_info.outputs.pypi-version }}\\\"\" > sanic/__version__.py\n\n    - name: Build a binary wheel and a source tarball\n      run: python -m build --sdist --wheel --outdir dist/ .\n\n    - name: Publish package distribution\n      uses: pypa/gh-action-pypi-publish@release/v1\n      with:\n        repository-url: ${{ env.IS_TEST == 'true' && 'https://test.pypi.org/legacy/' || 'https://upload.pypi.org/legacy/' }}\n\n  publish_docker:\n    name: Publish Docker / Python ${{ matrix.python-version }}\n    needs: [generate_info, publish_package]\n    runs-on: ubuntu-latest\n    if: ${{ needs.generate_info.outputs.is-test == 'false' }}\n    strategy:\n      fail-fast: true\n      matrix:\n        python-version: [\"3.10\", \"3.11\"]\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v3\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v2\n\n      - name: Login to Docker Hub\n        uses: docker/login-action@v2\n        with:\n          username: ${{ secrets.DOCKER_ACCESS_USER }}\n          password: ${{ secrets.DOCKER_ACCESS_TOKEN }}\n\n      - name: Build and push base image\n        uses: docker/build-push-action@v4\n        with:\n          push: ${{ env.IS_TEST == 'false' }}\n          file: ${{ env.DOCKER_BASE_IMAGE_DOCKERFILE }}\n          tags: ${{ env.DOCKER_ORG_NAME }}/${{ env.DOCKER_BASE_IMAGE_NAME }}:${{ matrix.python-version }}\n          build-args: |\n            PYTHON_VERSION=${{ matrix.python-version }}\n\n      - name: Parse tags for this Python version\n        id: parse_tags\n        run: |\n          IFS=',' read -ra tags <<< \"${{ needs.generate_info.outputs.docker-tags }}\"\n          tag_args=\"\"\n\n          for tag in \"${tags[@]}\"; do\n              tag_args+=\",${{ env.DOCKER_ORG_NAME }}/${{ env.DOCKER_IMAGE_NAME }}:${tag}-py${{ matrix.python-version }}\"\n          done\n\n          tag_args_output=\"tag_args=${tag_args:1}\"\n\n          echo \"${tag_args_output}\"\n          echo \"${tag_args_output}\" >> $GITHUB_OUTPUT\n\n      - name: Build and push Sanic image\n        uses: docker/build-push-action@v4\n        with:\n          push: ${{ env.IS_TEST == 'false' }}\n          file: ${{ env.DOCKER_IMAGE_DOCKERFILE }}\n          tags: ${{ steps.parse_tags.outputs.tag_args }}\n          build-args: |\n            BASE_IMAGE_ORG=${{ env.DOCKER_ORG_NAME }}\n            BASE_IMAGE_NAME=${{ env.DOCKER_BASE_IMAGE_NAME }}\n            BASE_IMAGE_TAG=${{ matrix.python-version }}\n            SANIC_PYPI_VERSION=${{ needs.generate_info.outputs.pypi-version }}\n"
  },
  {
    "path": ".github/workflows/tests.yml",
    "content": "name: Tests\n\non:\n  push:\n    branches:\n      - main\n      - current-release\n      - \"*LTS\"\n    tags:\n      - \"!*\"\n  pull_request:\n    branches:\n      - main\n      - current-release\n      - \"*LTS\"\n    types: [opened, synchronize, reopened, ready_for_review]\n\njobs:\n  run_tests:\n    name: \"${{ matrix.config.platform == 'windows-latest' && 'Windows' || 'Linux' }} / Python ${{ matrix.config.python-version }} / tox -e ${{ matrix.config.tox-env }}\"\n    if: github.event.pull_request.draft == false\n    runs-on: ${{ matrix.config.platform || 'ubuntu-latest' }}\n    strategy:\n      fail-fast: true\n      matrix:\n        config:\n          - { python-version: \"3.10\", tox-env: security }\n          - { python-version: \"3.11\", tox-env: security }\n          - { python-version: \"3.12\", tox-env: security }\n          - { python-version: \"3.13\", tox-env: security }\n          - { python-version: \"3.14\", tox-env: security }\n          - { python-version: \"3.14\", tox-env: lint }\n          # - { python-version: \"3.10\", tox-env: docs }\n          - { python-version: \"3.10\", tox-env: type-checking }\n          - { python-version: \"3.11\", tox-env: type-checking }\n          - { python-version: \"3.12\", tox-env: type-checking }\n          - { python-version: \"3.13\", tox-env: type-checking }\n          - { python-version: \"3.14\", tox-env: type-checking }\n          - { python-version: \"3.10\", tox-env: py310,         max-attempts: 3 }\n          - { python-version: \"3.10\", tox-env: py310-no-ext,  max-attempts: 3 }\n          - { python-version: \"3.11\", tox-env: py311,         max-attempts: 3 }\n          - { python-version: \"3.11\", tox-env: py311-no-ext,  max-attempts: 3 }\n          - { python-version: \"3.12\", tox-env: py312,         max-attempts: 3 }\n          - { python-version: \"3.12\", tox-env: py312-no-ext,  max-attempts: 3 }\n          - { python-version: \"3.13\", tox-env: py313,         max-attempts: 3 }\n          - { python-version: \"3.13\", tox-env: py313-no-ext,  max-attempts: 3 }\n          - { python-version: \"3.14\", tox-env: py314,         max-attempts: 3 }\n          - { python-version: \"3.14\", tox-env: py314-no-ext,  max-attempts: 3 }\n          - { python-version: \"3.10\", tox-env: py310-no-ext,  platform: windows-latest, ignore-errors: true }\n          - { python-version: \"3.11\", tox-env: py311-no-ext,  platform: windows-latest, ignore-errors: true }\n    steps:\n      - name: Run tests\n        uses: sanic-org/simple-tox-action@v1\n        with:\n          python-version: ${{ matrix.config.python-version }}\n          tox-env: ${{ matrix.config.tox-env }}\n          max-attempts: ${{ matrix.config.max-attempts || 1 }}\n          ignore-errors: ${{ matrix.config.ignore-errors || false }}\n"
  },
  {
    "path": ".gitignore",
    "content": "*~\n*.egg-info\n*.egg\n*.eggs\n*.pyc\n.coverage\n.coverage.*\ncoverage\ncoverage.xml\n.tox\nsettings.py\n.idea/*\n.cache/*\n.mypy_cache/\n.python-version\ndocs/_build/\ndocs/_api/\nbuild/*\n.DS_Store\ndist/*\npip-wheel-metadata/\n.pytest_cache/*\n.venv/*\nvenv/*\n.vscode/*\nguide/node_modules/\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": ".. note::\n\n  See https://sanic.dev/en/release-notes/changelog.html\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\nSee https://sanic.dev/en/organization/code-of-conduct.html\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nSee https://sanic.dev/en/organization/contributing.html\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2016-present Sanic Community\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "# Non Code related contents\ninclude LICENSE\ninclude README.rst\ninclude pyproject.toml\n\n# Setup\ninclude setup.py\ninclude Makefile\n\n# Tests\ninclude .coveragerc\ngraft tests\n\nglobal-exclude __pycache__\nglobal-exclude *.py[co]"
  },
  {
    "path": "Makefile",
    "content": "RUFF_FORMATTED_FOLDERS = sanic examples scripts tests guide docs\n.DEFAULT: help\n\n.PHONY: help\nhelp:\n\t@echo \"Please use \\`make <target>' where <target> is one of\"\n\t@echo \"install\"\n\t@echo \"\t\tInstall Sanic\"\n\t@echo \"docker-test\"\n\t@echo \"\t\tRun Sanic Unit Tests using Docker\"\n\t@echo \"fix\"\n\t@echo \"\t\tAnalyze and fix linting issues using ruff\"\n\t@echo \"format\"\n\t@echo \"\t\tAnalyze and format using ruff\"\n\t@echo \"pretty\"\n\t@echo \"\t\tAnalyze and fix linting and format using ruff\"\n\t@echo \"\"\n\t@echo \"docs\"\n\t@echo \"\t\tGenerate Sanic documentation\"\n\t@echo \"\"\n\t@echo \"clean-docs\"\n\t@echo \"\t\tClean Sanic documentation\"\n\t@echo \"\"\n\t@echo \"docs-test\"\n\t@echo \"\t\tTest Sanic Documentation for errors\"\n\t@echo \"\"\n\t@echo \"changelog\"\n\t@echo \"\t\tGenerate changelog for Sanic to prepare for new release\"\n\t@echo \"\"\n\t@echo \"release\"\n\t@echo \"\t\tPrepare Sanic for a new changes by version bump and changelog\"\n\t@echo \"\"\n\n.PHONY: clean\nclean:\n\tfind . ! -path \"./.eggs/*\" -name \"*.pyc\" -exec rm {} \\;\n\tfind . ! -path \"./.eggs/*\" -name \"*.pyo\" -exec rm {} \\;\n\tfind . ! -path \"./.eggs/*\" -name \".coverage\" -exec rm {} \\;\n\trm -rf build/* > /dev/null 2>&1\n\trm -rf dist/* > /dev/null 2>&1\n\n.PHONY: view-coverage\nview-coverage:\n\tsanic ./coverage --simple\n\n.PHONY: install\ninstall:\n\tpython -m pip install .\n\n.PHONY: docker-test\ndocker-test: clean\n\tdocker build -t sanic/test-image -f docker/Dockerfile .\n\tdocker run -t sanic/test-image tox\n\n.PHONY: fix\nfix:\n\truff check ${RUFF_FORMATTED_FOLDERS} --fix\n\n.PHONY: format\nformat:\n\truff format ${RUFF_FORMATTED_FOLDERS}\n\n.PHONY: pretty\npretty: format fix\n\n.PHONY: docs-clean\ndocs-clean:\n\tcd docs && make clean\n\n.PHONY: docs\ndocs: docs-clean\n\tcd docs && make html\n\n.PHONY: docs-test\ndocs-test: docs-clean\n\tcd docs && make dummy\n\n.PHONY: docs-serve\ndocs-serve:\n\tsphinx-autobuild docs docs/_build/html --port 9999 --watch ./\n\n.PHONY: changelog\nchangelog:\n\tpython scripts/changelog.py\n\n.PHONY: guide-serve\nguide-serve:\n\tcd guide && sanic server:app -r -R ./content -R ./style\n\n.PHONY: release\nrelease:\nifdef version\n\tpython scripts/release.py --release-version ${version} --generate-changelog\nelse\n\tpython scripts/release.py --generate-changelog\nendif\n\n"
  },
  {
    "path": "README.rst",
    "content": ".. image:: https://raw.githubusercontent.com/sanic-org/sanic-assets/master/png/sanic-framework-logo-400x97.png\n    :alt: Sanic | Build fast. Run fast.\n\nSanic | Build fast. Run fast.\n=============================\n\n.. start-badges\n\n.. list-table::\n    :widths: 15 85\n    :stub-columns: 1\n\n    * - Build\n      - | |Tests|\n    * - Docs\n      - | |UserGuide| |Documentation|\n    * - Package\n      - | |PyPI| |PyPI version| |Wheel| |Supported implementations| |Code style ruff|\n    * - Support\n      - | |Forums| |Discord| |Awesome|\n    * - Stats\n      - | |Monthly Downloads| |Weekly Downloads| |Conda downloads|\n\n.. |UserGuide| image:: https://img.shields.io/badge/user%20guide-sanic-ff0068\n   :target: https://sanic.dev/\n.. |Forums| image:: https://img.shields.io/badge/forums-community-ff0068.svg\n   :target: https://community.sanicframework.org/\n.. |Discord| image:: https://img.shields.io/discord/812221182594121728?logo=discord&label=Discord&color=5865F2\n   :target: https://discord.gg/FARQzAEMAA\n.. |Tests| image:: https://github.com/sanic-org/sanic/actions/workflows/tests.yml/badge.svg?branch=main\n   :target: https://github.com/sanic-org/sanic/actions/workflows/tests.yml\n.. |Documentation| image:: https://readthedocs.org/projects/sanic/badge/?version=latest\n   :target: http://sanic.readthedocs.io/en/latest/?badge=latest\n.. |PyPI| image:: https://img.shields.io/pypi/v/sanic.svg\n   :target: https://pypi.python.org/pypi/sanic/\n.. |PyPI version| image:: https://img.shields.io/pypi/pyversions/sanic.svg\n   :target: https://pypi.python.org/pypi/sanic/\n.. |Code style ruff| image:: https://img.shields.io/badge/code%20style-ruff-000000.svg\n    :target: https://docs.astral.sh/ruff/\n.. |Wheel| image:: https://img.shields.io/pypi/wheel/sanic.svg\n    :alt: PyPI Wheel\n    :target: https://pypi.python.org/pypi/sanic\n.. |Supported implementations| image:: https://img.shields.io/pypi/implementation/sanic.svg\n    :alt: Supported implementations\n    :target: https://pypi.python.org/pypi/sanic\n.. |Awesome| image:: https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg\n    :alt: Awesome Sanic List\n    :target: https://github.com/mekicha/awesome-sanic\n.. |Monthly Downloads| image:: https://img.shields.io/pypi/dm/sanic.svg\n    :alt: Downloads\n    :target: https://pepy.tech/project/sanic\n.. |Weekly Downloads| image:: https://img.shields.io/pypi/dw/sanic.svg\n    :alt: Downloads\n    :target: https://pepy.tech/project/sanic\n.. |Conda downloads| image:: https://img.shields.io/conda/dn/conda-forge/sanic.svg\n    :alt: Downloads\n    :target: https://anaconda.org/conda-forge/sanic\n\n.. end-badges\n\nSanic is a **Python 3.10+** web server and web framework that's written to go fast. It allows the usage of the ``async/await`` syntax added in Python 3.5, which makes your code non-blocking and speedy.\n\nSanic is also ASGI compliant, so you can deploy it with an `alternative ASGI webserver <https://sanicframework.org/en/guide/deployment/running.html#asgi>`_.\n\n`Source code on GitHub <https://github.com/sanic-org/sanic/>`_ | `Help and discussion board <https://community.sanicframework.org/>`_ | `User Guide <https://sanicframework.org>`_ | `Chat on Discord <https://discord.gg/FARQzAEMAA>`_\n\nThe project is maintained by the community, for the community. **Contributions are welcome!**\n\nThe goal of the project is to provide a simple way to get up and running a highly performant HTTP server that is easy to build, to expand, and ultimately to scale.\n\nSponsor\n-------\n\nCheck out `open collective <https://opencollective.com/sanic-org>`_ to learn more about helping to fund Sanic.\n\n\nInstallation\n------------\n\n``pip install sanic``\n\n    Sanic makes use of ``uvloop`` and ``ujson`` to help with performance. If you do not want to use those packages, simply add an environmental variable ``SANIC_NO_UVLOOP=true`` or ``SANIC_NO_UJSON=true`` at install time.\n\n    .. code:: shell\n\n       $ export SANIC_NO_UVLOOP=true\n       $ export SANIC_NO_UJSON=true\n       $ pip install --no-binary :all: sanic\n\n\n.. note::\n\n  If you are running on a clean install of Fedora 28 or above, please make sure you have the ``redhat-rpm-config`` package installed in case if you want to\n  use ``sanic`` with ``ujson`` dependency.\n\n\nHello World Example\n-------------------\n\n.. code:: python\n\n    from sanic import Sanic\n    from sanic.response import json\n\n    app = Sanic(\"my-hello-world-app\")\n\n    @app.route('/')\n    async def test(request):\n        return json({'hello': 'world'})\n\nSanic can now be easily run from CLI using ``sanic hello.app``.\n\n.. code::\n\n    [2018-12-30 11:37:41 +0200] [13564] [INFO] Goin' Fast @ http://127.0.0.1:8000\n    [2018-12-30 11:37:41 +0200] [13564] [INFO] Starting worker [13564]\n\nAnd, we can verify it is working: ``curl localhost:8000 -i``\n\n.. code::\n\n    HTTP/1.1 200 OK\n    Connection: keep-alive\n    Keep-Alive: 5\n    Content-Length: 17\n    Content-Type: application/json\n\n    {\"hello\":\"world\"}\n\n**Now, let's go build something fast!**\n\nMinimum Python version is 3.10.\n\nDocumentation\n-------------\n\nUser Guide, Changelog, and API Documentation can be found at `sanic.dev <https://sanic.dev>`__.\n\n\nQuestions and Discussion\n------------------------\n\n`Ask a question or join the conversation <https://community.sanicframework.org/>`__.\n\nContribution\n------------\n\nWe are always happy to have new contributions. We have `marked issues good for anyone looking to get started <https://github.com/sanic-org/sanic/issues?q=is%3Aopen+is%3Aissue+label%3Abeginner>`_, and welcome `questions on the forums <https://community.sanicframework.org/>`_. Please take a look at our `Contribution guidelines <https://github.com/sanic-org/sanic/blob/master/CONTRIBUTING.md>`_.\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\nSee https://sanic.dev/en/organization/policies.html\n"
  },
  {
    "path": "bandit.baseline",
    "content": "{\n  \"errors\": [],\n  \"generated_at\": \"2025-09-14T20:47:44Z\",\n  \"metrics\": {\n    \"_totals\": {\n      \"CONFIDENCE.HIGH\": 10,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 10,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 20091,\n      \"nosec\": 2,\n      \"skipped_tests\": 6\n    },\n    \"sanic/__init__.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 76,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/__main__.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 10,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/__version__.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 1,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/app.py\": {\n      \"CONFIDENCE.HIGH\": 1,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 1,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 2122,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/application/__init__.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 0,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/application/constants.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 24,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/application/ext.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 34,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/application/logo.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 53,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/application/motd.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 155,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/application/spinner.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 74,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/application/state.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 89,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/asgi.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 233,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/base/__init__.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 0,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/base/meta.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 6,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/base/root.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 55,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/blueprint_group.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 2,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/blueprints.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 790,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/cli/__init__.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 0,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/cli/app.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 239,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/cli/arguments.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 283,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/cli/base.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 27,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/cli/console.py\": {\n      \"CONFIDENCE.HIGH\": 3,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 3,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 242,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/cli/executor.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 76,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/cli/inspector.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 97,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/cli/inspector_client.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 106,\n      \"nosec\": 0,\n      \"skipped_tests\": 1\n    },\n    \"sanic/compat.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 126,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/config.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 350,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/constants.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 29,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/cookies/__init__.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 2,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/cookies/request.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 122,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/cookies/response.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 521,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/errorpages.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 321,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/exceptions.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 538,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/handlers/__init__.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 8,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/handlers/content_range.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 63,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/handlers/directory.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 89,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/handlers/error.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 166,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/headers.py\": {\n      \"CONFIDENCE.HIGH\": 1,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 1,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 421,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/helpers.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 142,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/http/__init__.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 4,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/http/constants.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 22,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/http/http1.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 470,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/http/http3.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 348,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/http/stream.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 19,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/http/tls/__init__.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 3,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/http/tls/context.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 169,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/http/tls/creators.py\": {\n      \"CONFIDENCE.HIGH\": 1,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 1,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 245,\n      \"nosec\": 0,\n      \"skipped_tests\": 3\n    },\n    \"sanic/log.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 22,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/logging/__init__.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 0,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/logging/color.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 48,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/logging/default.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 57,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/logging/deprecation.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 23,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/logging/filter.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 9,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/logging/formatter.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 315,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/logging/loggers.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 29,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/logging/setup.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 53,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/middleware.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 83,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/mixins/__init__.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 0,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/mixins/base.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 30,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/mixins/commands.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 24,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/mixins/exceptions.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 81,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/mixins/listeners.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 346,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/mixins/middleware.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 198,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/mixins/routes.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 725,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/mixins/signals.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 113,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/mixins/startup.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 1236,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/mixins/static.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 322,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/models/__init__.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 0,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/models/asgi.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 75,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/models/ctx_types.py\": {\n      \"CONFIDENCE.HIGH\": 1,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 1,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 54,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/models/futures.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 61,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/models/handler_types.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 25,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/models/http_types.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 28,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/models/protocol_types.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 20,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/models/server_types.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 60,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/pages/__init__.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 0,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/pages/base.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 67,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/pages/css.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 26,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/pages/directory_page.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 49,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/pages/error.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 89,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/request/__init__.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 9,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/request/form.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 85,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/request/parameters.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 24,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/request/types.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 941,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/response/__init__.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 34,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/response/convenience.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 335,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/response/types.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 449,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/router.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 222,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/server/__init__.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 13,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/server/async_server.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 89,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/server/events.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 30,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/server/goodbye.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 21,\n      \"nosec\": 0,\n      \"skipped_tests\": 1\n    },\n    \"sanic/server/loop.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 54,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/server/protocols/__init__.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 0,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/server/protocols/base_protocol.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 186,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/server/protocols/http_protocol.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 305,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/server/protocols/websocket_protocol.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 197,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/server/runners.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 306,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/server/socket.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 115,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/server/websockets/__init__.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 0,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/server/websockets/connection.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 66,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/server/websockets/frame.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 208,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/server/websockets/impl.py\": {\n      \"CONFIDENCE.HIGH\": 1,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 1,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 720,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/signals.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 349,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/simple.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 13,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/touchup/__init__.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 6,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/touchup/meta.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 17,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/touchup/schemes/__init__.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 4,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/touchup/schemes/altsvc.py\": {\n      \"CONFIDENCE.HIGH\": 1,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 1,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 42,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/touchup/schemes/base.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 28,\n      \"nosec\": 1,\n      \"skipped_tests\": 0\n    },\n    \"sanic/touchup/schemes/ode.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 73,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/touchup/service.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 22,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/types/__init__.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 2,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/types/hashable_dict.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 3,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/types/shared_ctx.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 49,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/utils.py\": {\n      \"CONFIDENCE.HIGH\": 1,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 1,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 110,\n      \"nosec\": 1,\n      \"skipped_tests\": 0\n    },\n    \"sanic/views.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 198,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/worker/__init__.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 0,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/worker/constants.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 18,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/worker/inspector.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 132,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/worker/loader.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 137,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/worker/manager.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 473,\n      \"nosec\": 0,\n      \"skipped_tests\": 1\n    },\n    \"sanic/worker/multiplexer.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 143,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/worker/process.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 247,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/worker/reloader.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 103,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/worker/restarter.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 81,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/worker/serve.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 127,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    },\n    \"sanic/worker/state.py\": {\n      \"CONFIDENCE.HIGH\": 0,\n      \"CONFIDENCE.LOW\": 0,\n      \"CONFIDENCE.MEDIUM\": 0,\n      \"CONFIDENCE.UNDEFINED\": 0,\n      \"SEVERITY.HIGH\": 0,\n      \"SEVERITY.LOW\": 0,\n      \"SEVERITY.MEDIUM\": 0,\n      \"SEVERITY.UNDEFINED\": 0,\n      \"loc\": 65,\n      \"nosec\": 0,\n      \"skipped_tests\": 0\n    }\n  },\n  \"results\": [\n    {\n      \"code\": \"931         if blueprint.name in self.blueprints:\\n932             assert self.blueprints[blueprint.name] is blueprint, (\\n933                 'A blueprint with the name \\\"%s\\\" is already registered.  '\\n934                 \\\"Blueprint names must be unique.\\\" % (blueprint.name,)\\n935             )\\n936         else:\\n\",\n      \"col_offset\": 12,\n      \"end_col_offset\": 13,\n      \"filename\": \"sanic/app.py\",\n      \"issue_confidence\": \"HIGH\",\n      \"issue_cwe\": {\n        \"id\": 703,\n        \"link\": \"https://cwe.mitre.org/data/definitions/703.html\"\n      },\n      \"issue_severity\": \"LOW\",\n      \"issue_text\": \"Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.\",\n      \"line_number\": 932,\n      \"line_range\": [\n        932,\n        933,\n        934,\n        935\n      ],\n      \"more_info\": \"https://bandit.readthedocs.io/en/1.8.6/plugins/b101_assert_used.html\",\n      \"test_id\": \"B101\",\n      \"test_name\": \"assert_used\"\n    },\n    {\n      \"code\": \"77 ):\\n78     assert repl_app, \\\"No Sanic app has been registered.\\\"\\n79     headers = headers or {}\\n\",\n      \"col_offset\": 4,\n      \"end_col_offset\": 56,\n      \"filename\": \"sanic/cli/console.py\",\n      \"issue_confidence\": \"HIGH\",\n      \"issue_cwe\": {\n        \"id\": 703,\n        \"link\": \"https://cwe.mitre.org/data/definitions/703.html\"\n      },\n      \"issue_severity\": \"LOW\",\n      \"issue_text\": \"Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.\",\n      \"line_number\": 78,\n      \"line_range\": [\n        78\n      ],\n      \"more_info\": \"https://bandit.readthedocs.io/en/1.8.6/plugins/b101_assert_used.html\",\n      \"test_id\": \"B101\",\n      \"test_name\": \"assert_used\"\n    },\n    {\n      \"code\": \"96 async def respond(request) -> HTTPResponse:\\n97     assert repl_app, \\\"No Sanic app has been registered.\\\"\\n98     await repl_app.handle_request(request)\\n\",\n      \"col_offset\": 4,\n      \"end_col_offset\": 56,\n      \"filename\": \"sanic/cli/console.py\",\n      \"issue_confidence\": \"HIGH\",\n      \"issue_cwe\": {\n        \"id\": 703,\n        \"link\": \"https://cwe.mitre.org/data/definitions/703.html\"\n      },\n      \"issue_severity\": \"LOW\",\n      \"issue_text\": \"Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.\",\n      \"line_number\": 97,\n      \"line_range\": [\n        97\n      ],\n      \"more_info\": \"https://bandit.readthedocs.io/en/1.8.6/plugins/b101_assert_used.html\",\n      \"test_id\": \"B101\",\n      \"test_name\": \"assert_used\"\n    },\n    {\n      \"code\": \"98     await repl_app.handle_request(request)\\n99     assert repl_response\\n100     return repl_response\\n\",\n      \"col_offset\": 4,\n      \"end_col_offset\": 24,\n      \"filename\": \"sanic/cli/console.py\",\n      \"issue_confidence\": \"HIGH\",\n      \"issue_cwe\": {\n        \"id\": 703,\n        \"link\": \"https://cwe.mitre.org/data/definitions/703.html\"\n      },\n      \"issue_severity\": \"LOW\",\n      \"issue_text\": \"Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.\",\n      \"line_number\": 99,\n      \"line_range\": [\n        99\n      ],\n      \"more_info\": \"https://bandit.readthedocs.io/en/1.8.6/plugins/b101_assert_used.html\",\n      \"test_id\": \"B101\",\n      \"test_name\": \"assert_used\"\n    },\n    {\n      \"code\": \"411     if not addr and proxies_count:\\n412         assert proxies_count > 0\\n413         try:\\n\",\n      \"col_offset\": 8,\n      \"end_col_offset\": 32,\n      \"filename\": \"sanic/headers.py\",\n      \"issue_confidence\": \"HIGH\",\n      \"issue_cwe\": {\n        \"id\": 703,\n        \"link\": \"https://cwe.mitre.org/data/definitions/703.html\"\n      },\n      \"issue_severity\": \"LOW\",\n      \"issue_text\": \"Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.\",\n      \"line_number\": 412,\n      \"line_range\": [\n        412\n      ],\n      \"more_info\": \"https://bandit.readthedocs.io/en/1.8.6/plugins/b101_assert_used.html\",\n      \"test_id\": \"B101\",\n      \"test_name\": \"assert_used\"\n    },\n    {\n      \"code\": \"3 import ssl\\n4 import subprocess\\n5 import sys\\n\",\n      \"col_offset\": 0,\n      \"end_col_offset\": 17,\n      \"filename\": \"sanic/http/tls/creators.py\",\n      \"issue_confidence\": \"HIGH\",\n      \"issue_cwe\": {\n        \"id\": 78,\n        \"link\": \"https://cwe.mitre.org/data/definitions/78.html\"\n      },\n      \"issue_severity\": \"LOW\",\n      \"issue_text\": \"Consider possible security implications associated with the subprocess module.\",\n      \"line_number\": 4,\n      \"line_range\": [\n        4\n      ],\n      \"more_info\": \"https://bandit.readthedocs.io/en/1.8.6/blacklists/blacklist_imports.html#b404-import-subprocess\",\n      \"test_id\": \"B404\",\n      \"test_name\": \"blacklist\"\n    },\n    {\n      \"code\": \"45 \\n46         assert isinstance(desc, str) and isinstance(\\n47             name, str\\n48         )  # Just to make mypy happy\\n49 \\n\",\n      \"col_offset\": 8,\n      \"end_col_offset\": 9,\n      \"filename\": \"sanic/models/ctx_types.py\",\n      \"issue_confidence\": \"HIGH\",\n      \"issue_cwe\": {\n        \"id\": 703,\n        \"link\": \"https://cwe.mitre.org/data/definitions/703.html\"\n      },\n      \"issue_severity\": \"LOW\",\n      \"issue_text\": \"Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.\",\n      \"line_number\": 46,\n      \"line_range\": [\n        46,\n        47,\n        48\n      ],\n      \"more_info\": \"https://bandit.readthedocs.io/en/1.8.6/plugins/b101_assert_used.html\",\n      \"test_id\": \"B101\",\n      \"test_name\": \"assert_used\"\n    },\n    {\n      \"code\": \"766             while data is None or data in self.pings:\\n767                 data = struct.pack(\\\"!I\\\", random.getrandbits(32))\\n768 \\n\",\n      \"col_offset\": 41,\n      \"end_col_offset\": 63,\n      \"filename\": \"sanic/server/websockets/impl.py\",\n      \"issue_confidence\": \"HIGH\",\n      \"issue_cwe\": {\n        \"id\": 330,\n        \"link\": \"https://cwe.mitre.org/data/definitions/330.html\"\n      },\n      \"issue_severity\": \"LOW\",\n      \"issue_text\": \"Standard pseudo-random generators are not suitable for security/cryptographic purposes.\",\n      \"line_number\": 767,\n      \"line_range\": [\n        767\n      ],\n      \"more_info\": \"https://bandit.readthedocs.io/en/1.8.6/blacklists/blacklist_calls.html#b311-random\",\n      \"test_id\": \"B311\",\n      \"test_name\": \"blacklist\"\n    },\n    {\n      \"code\": \"33                 return None\\n34             assert isinstance(node.value, Constant)\\n35             node.value.value = self.value()\\n\",\n      \"col_offset\": 12,\n      \"end_col_offset\": 51,\n      \"filename\": \"sanic/touchup/schemes/altsvc.py\",\n      \"issue_confidence\": \"HIGH\",\n      \"issue_cwe\": {\n        \"id\": 703,\n        \"link\": \"https://cwe.mitre.org/data/definitions/703.html\"\n      },\n      \"issue_severity\": \"LOW\",\n      \"issue_text\": \"Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.\",\n      \"line_number\": 34,\n      \"line_range\": [\n        34\n      ],\n      \"more_info\": \"https://bandit.readthedocs.io/en/1.8.6/plugins/b101_assert_used.html\",\n      \"test_id\": \"B101\",\n      \"test_name\": \"assert_used\"\n    },\n    {\n      \"code\": \"106             )\\n107             assert _mod_spec is not None  # type assertion for mypy\\n108             module = module_from_spec(_mod_spec)\\n\",\n      \"col_offset\": 12,\n      \"end_col_offset\": 40,\n      \"filename\": \"sanic/utils.py\",\n      \"issue_confidence\": \"HIGH\",\n      \"issue_cwe\": {\n        \"id\": 703,\n        \"link\": \"https://cwe.mitre.org/data/definitions/703.html\"\n      },\n      \"issue_severity\": \"LOW\",\n      \"issue_text\": \"Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.\",\n      \"line_number\": 107,\n      \"line_range\": [\n        107\n      ],\n      \"more_info\": \"https://bandit.readthedocs.io/en/1.8.6/plugins/b101_assert_used.html\",\n      \"test_id\": \"B101\",\n      \"test_name\": \"assert_used\"\n    }\n  ]\n}"
  },
  {
    "path": "changelogs/.gitignore",
    "content": "# Except this file\n!.gitignore"
  },
  {
    "path": "changelogs/1892.removal.rst",
    "content": "Remove [version] section.\n"
  },
  {
    "path": "changelogs/1904.feature.rst",
    "content": "Adds WEBSOCKET_PING_TIMEOUT and WEBSOCKET_PING_INTERVAL configuration values\n\nAllows setting the ping_interval and ping_timeout arguments when initializing `WebSocketCommonProtocol`."
  },
  {
    "path": "changelogs/1970.misc.rst",
    "content": "Adds py.typed file to expose type information to other packages.\n"
  },
  {
    "path": "codecov.yml",
    "content": "coverage:\n  status:\n    patch:\n      default:\n        target: auto\n        threshold: 0.75\n        informational: true\n    project:\n      default:\n        target: auto\n        threshold: 0.5\n  precision: 3\ncodecov:\n  require_ci_to_pass: false\nignore:\n  - \"sanic/__main__.py\"\n  - \"sanic/compat.py\"\n  - \"sanic/simple.py\"\n  - \"sanic/utils.py\"\n  - \"sanic/cli/\"\n  - \"sanic/pages/\"\n  - \".github/\"\n  - \"changelogs/\"\n  - \"docker/\"\n  - \"docs/\"\n  - \"examples/\"\n  - \"scripts/\"\n  - \"tests/\"\n"
  },
  {
    "path": "crowdin.yml",
    "content": "files:\n  - source: /guide/content/en/**/*.md\n    translation: /guide/content/%two_letters_code%/**/%original_file_name%\n"
  },
  {
    "path": "docker/Dockerfile",
    "content": "ARG BASE_IMAGE_ORG\nARG BASE_IMAGE_NAME\nARG BASE_IMAGE_TAG\n\nFROM ${BASE_IMAGE_ORG}/${BASE_IMAGE_NAME}:${BASE_IMAGE_TAG}\n\nRUN apk update\nRUN update-ca-certificates\n\nARG SANIC_PYPI_VERSION\n\nRUN pip install -U pip && pip install sanic==${SANIC_PYPI_VERSION}\nRUN apk del build-base\n"
  },
  {
    "path": "docker/Dockerfile-base",
    "content": "ARG PYTHON_VERSION\n\nFROM python:${PYTHON_VERSION}-alpine\nRUN apk update\nRUN apk add --no-cache --update build-base \\\n        ca-certificates \\\n        openssl\nRUN update-ca-certificates\nRUN rm -rf /var/cache/apk/*\n"
  },
  {
    "path": "docs/Makefile",
    "content": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nPAPER         =\nBUILDDIR      = _build\n\n# Internal variables.\nPAPEROPT_a4     = -D latex_paper_size=a4\nPAPEROPT_letter = -D latex_paper_size=letter\nALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n# the i18n builder cannot share the environment and doctrees with the others\nI18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n\n.PHONY: help\nhelp:\n\t@echo \"Please use \\`make <target>' where <target> is one of\"\n\t@echo \"  html       to make standalone HTML files\"\n\t@echo \"  dirhtml    to make HTML files named index.html in directories\"\n\t@echo \"  singlehtml to make a single large HTML file\"\n\t@echo \"  pickle     to make pickle files\"\n\t@echo \"  json       to make JSON files\"\n\t@echo \"  htmlhelp   to make HTML files and a HTML help project\"\n\t@echo \"  qthelp     to make HTML files and a qthelp project\"\n\t@echo \"  applehelp  to make an Apple Help Book\"\n\t@echo \"  devhelp    to make HTML files and a Devhelp project\"\n\t@echo \"  epub       to make an epub\"\n\t@echo \"  epub3      to make an epub3\"\n\t@echo \"  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\"\n\t@echo \"  latexpdf   to make LaTeX files and run them through pdflatex\"\n\t@echo \"  latexpdfja to make LaTeX files and run them through platex/dvipdfmx\"\n\t@echo \"  text       to make text files\"\n\t@echo \"  man        to make manual pages\"\n\t@echo \"  texinfo    to make Texinfo files\"\n\t@echo \"  info       to make Texinfo files and run them through makeinfo\"\n\t@echo \"  gettext    to make PO message catalogs\"\n\t@echo \"  changes    to make an overview of all changed/added/deprecated items\"\n\t@echo \"  xml        to make Docutils-native XML files\"\n\t@echo \"  pseudoxml  to make pseudoxml-XML files for display purposes\"\n\t@echo \"  linkcheck  to check all external links for integrity\"\n\t@echo \"  doctest    to run all doctests embedded in the documentation (if enabled)\"\n\t@echo \"  coverage   to run coverage check of the documentation (if enabled)\"\n\t@echo \"  dummy      to check syntax errors of document sources\"\n\n.PHONY: clean\nclean:\n\trm -rf $(BUILDDIR)/*\n\n.PHONY: html\nhtml:\n\t$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/html.\"\n\n.PHONY: dirhtml\ndirhtml:\n\t$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/dirhtml.\"\n\n.PHONY: singlehtml\nsinglehtml:\n\t$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml\n\t@echo\n\t@echo \"Build finished. The HTML page is in $(BUILDDIR)/singlehtml.\"\n\n.PHONY: pickle\npickle:\n\t$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle\n\t@echo\n\t@echo \"Build finished; now you can process the pickle files.\"\n\n.PHONY: json\njson:\n\t$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json\n\t@echo\n\t@echo \"Build finished; now you can process the JSON files.\"\n\n.PHONY: htmlhelp\nhtmlhelp:\n\t$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp\n\t@echo\n\t@echo \"Build finished; now you can run HTML Help Workshop with the\" \\\n\t      \".hhp project file in $(BUILDDIR)/htmlhelp.\"\n\n.PHONY: qthelp\nqthelp:\n\t$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp\n\t@echo\n\t@echo \"Build finished; now you can run \"qcollectiongenerator\" with the\" \\\n\t      \".qhcp project file in $(BUILDDIR)/qthelp, like this:\"\n\t@echo \"# qcollectiongenerator $(BUILDDIR)/qthelp/aiographite.qhcp\"\n\t@echo \"To view the help file:\"\n\t@echo \"# assistant -collectionFile $(BUILDDIR)/qthelp/aiographite.qhc\"\n\n.PHONY: applehelp\napplehelp:\n\t$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp\n\t@echo\n\t@echo \"Build finished. The help book is in $(BUILDDIR)/applehelp.\"\n\t@echo \"N.B. You won't be able to view it unless you put it in\" \\\n\t      \"~/Library/Documentation/Help or install it in your application\" \\\n\t      \"bundle.\"\n\n.PHONY: devhelp\ndevhelp:\n\t$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp\n\t@echo\n\t@echo \"Build finished.\"\n\t@echo \"To view the help file:\"\n\t@echo \"# mkdir -p $$HOME/.local/share/devhelp/aiographite\"\n\t@echo \"# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/aiographite\"\n\t@echo \"# devhelp\"\n\n.PHONY: epub\nepub:\n\t$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub\n\t@echo\n\t@echo \"Build finished. The epub file is in $(BUILDDIR)/epub.\"\n\n.PHONY: epub3\nepub3:\n\t$(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3\n\t@echo\n\t@echo \"Build finished. The epub3 file is in $(BUILDDIR)/epub3.\"\n\n.PHONY: latex\nlatex:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo\n\t@echo \"Build finished; the LaTeX files are in $(BUILDDIR)/latex.\"\n\t@echo \"Run \\`make' in that directory to run these through (pdf)latex\" \\\n\t      \"(use \\`make latexpdf' here to do that automatically).\"\n\n.PHONY: latexpdf\nlatexpdf:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through pdflatex...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\n.PHONY: latexpdfja\nlatexpdfja:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through platex and dvipdfmx...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\n.PHONY: text\ntext:\n\t$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text\n\t@echo\n\t@echo \"Build finished. The text files are in $(BUILDDIR)/text.\"\n\n.PHONY: man\nman:\n\t$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man\n\t@echo\n\t@echo \"Build finished. The manual pages are in $(BUILDDIR)/man.\"\n\n.PHONY: texinfo\ntexinfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo\n\t@echo \"Build finished. The Texinfo files are in $(BUILDDIR)/texinfo.\"\n\t@echo \"Run \\`make' in that directory to run these through makeinfo\" \\\n\t      \"(use \\`make info' here to do that automatically).\"\n\n.PHONY: info\ninfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo \"Running Texinfo files through makeinfo...\"\n\tmake -C $(BUILDDIR)/texinfo info\n\t@echo \"makeinfo finished; the Info files are in $(BUILDDIR)/texinfo.\"\n\n.PHONY: gettext\ngettext:\n\t$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale\n\t@echo\n\t@echo \"Build finished. The message catalogs are in $(BUILDDIR)/locale.\"\n\n.PHONY: changes\nchanges:\n\t$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes\n\t@echo\n\t@echo \"The overview file is in $(BUILDDIR)/changes.\"\n\n.PHONY: linkcheck\nlinkcheck:\n\t$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck\n\t@echo\n\t@echo \"Link check complete; look for any errors in the above output \" \\\n\t      \"or in $(BUILDDIR)/linkcheck/output.txt.\"\n\n.PHONY: doctest\ndoctest:\n\t$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest\n\t@echo \"Testing of doctests in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/doctest/output.txt.\"\n\n.PHONY: coverage\ncoverage:\n\t$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage\n\t@echo \"Testing of coverage in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/coverage/python.txt.\"\n\n.PHONY: xml\nxml:\n\t$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml\n\t@echo\n\t@echo \"Build finished. The XML files are in $(BUILDDIR)/xml.\"\n\n.PHONY: pseudoxml\npseudoxml:\n\t$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml\n\t@echo\n\t@echo \"Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml.\"\n\n.PHONY: dummy\ndummy:\n\t$(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy\n\t@echo\n\t@echo \"Build finished. Dummy builder generates no files.\"\n"
  },
  {
    "path": "docs/_static/.gitkeep",
    "content": ""
  },
  {
    "path": "docs/_static/custom.css",
    "content": ".wy-side-nav-search,\n.wy-nav-top {\n  background: #444444;\n}\n\n#changelog section {\n  padding-left: 3rem;\n}\n\n#changelog section h2,\n#changelog section h3 {\n  margin-left: -3rem;\n}\n"
  },
  {
    "path": "docs/_templates/banner.html",
    "content": "<div style=\"background-color: orange; text-align: center; padding: 10px;\">\n    <p>This is a banner!</p>\n</div>\n"
  },
  {
    "path": "docs/conf.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n#\n# Sanic documentation build configuration file, created by\n# sphinx-quickstart on Sun Dec 25 18:07:21 2016.\n#\n# This file is execfile()d with the current directory set to its\n# containing dir.\n\nimport os\nimport sys\n\n\n# Add support for auto-doc\n\n\n# Ensure that sanic is present in the path, to allow sphinx-apidoc to\n# autogenerate documentation from docstrings\nroot_directory = os.path.dirname(os.getcwd())\nsys.path.insert(0, root_directory)\n\nimport sanic  #  noqa\n\n\n# -- General configuration ------------------------------------------------\n\nextensions = [\n    \"sphinx.ext.autodoc\",\n    \"m2r2\",\n    \"enum_tools.autoenum\",\n]\n\ntemplates_path = [\"_templates\"]\n\n# Enable support for both Restructured Text and Markdown\nsource_suffix = [\".rst\", \".md\"]\n\n# The master toctree document.\nmaster_doc = \"index\"\n\n# General information about the project.\nproject = \"Sanic\"\ncopyright = \"2021, Sanic Community Organization\"\nauthor = \"Sanic Community Organization\"\n\nhtml_logo = \"./_static/sanic-framework-logo-white-400x97.png\"\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n#\n# The short X.Y version.\nversion = sanic.__version__\n# The full version, including alpha/beta/rc tags.\nrelease = sanic.__version__\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n#\n# This is also used if you do content translation via gettext catalogs.\n# Usually you set \"language\" from the command line for these cases.\nlanguage = \"en\"\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\n# This patterns also effect to html_static_path and html_extra_path\n#\n# modules.rst is generated by sphinx-apidoc but is unused. This suppresses\n# a warning about it.\nexclude_patterns = [\"_build\", \"Thumbs.db\", \".DS_Store\", \"modules.rst\"]\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = \"sphinx\"\n\n# If true, `todo` and `todoList` produce output, else they produce nothing.\ntodo_include_todos = False\n\n# -- Options for HTML output ----------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\nhtml_theme = \"sphinx_rtd_theme\"\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = [\"_static\"]\nhtml_css_files = [\"custom.css\"]\n# -- Options for HTMLHelp output ------------------------------------------\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = \"Sanicdoc\"\n\n# -- Options for LaTeX output ---------------------------------------------\n\nlatex_elements = {\n    # The paper size ('letterpaper' or 'a4paper').\n    #\n    # 'papersize': 'letterpaper',\n    # The font size ('10pt', '11pt' or '12pt').\n    #\n    # 'pointsize': '10pt',\n    # Additional stuff for the LaTeX preamble.\n    #\n    # 'preamble': '',\n    # Latex figure (float) alignment\n    #\n    # 'figure_align': 'htbp',\n}\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title,\n#  author, documentclass [howto, manual, or own class]).\nlatex_documents = [\n    (\n        master_doc,\n        \"Sanic.tex\",\n        \"Sanic Documentation\",\n        \"Sanic contributors\",\n        \"manual\",\n    ),\n]\n\n# -- Options for manual page output ---------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [(master_doc, \"sanic\", \"Sanic Documentation\", [author], 1)]\n\n# -- Options for Texinfo output -------------------------------------------\n\n# Grouping the document tree into Texinfo files. List of tuples\n# (source start file, target name, title, author,\n#  dir menu entry, description, category)\ntexinfo_documents = [\n    (\n        master_doc,\n        \"Sanic\",\n        \"Sanic Documentation\",\n        author,\n        \"Sanic\",\n        \"One line description of project.\",\n        \"Miscellaneous\",\n    ),\n]\n\n# -- Options for Epub output ----------------------------------------------\n\n# Bibliographic Dublin Core info.\nepub_title = project\nepub_author = author\nepub_publisher = author\nepub_copyright = copyright\n\n# A list of files that should not be packed into the epub file.\nepub_exclude_files = [\"search.html\"]\n\n# -- Custom Settings -------------------------------------------------------\n\nsuppress_warnings = [\"image.nonlocal_uri\"]\n\n\nautodoc_typehints = \"description\"\nautodoc_default_options = {\n    \"member-order\": \"groupwise\",\n}\n\nhtml_theme_options = {\n    \"style_external_links\": False,\n}\n\nrst_prolog = \"\"\".. warning::\n    These documents are **OUTDATED** as of 2023-12-31.\n\n    Please refer to the latest version of the documentation at `sanic.dev <https://sanic.dev>`__.\n\"\"\"\n"
  },
  {
    "path": "docs/index.html",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n<meta name=\"generator\" content=\"Docutils 0.15.2: http://docutils.sourceforge.net/\" />\n<title>index.rst</title>\n<style type=\"text/css\">\n\n/*\n:Author: David Goodger (goodger@python.org)\n:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $\n:Copyright: This stylesheet has been placed in the public domain.\n\nDefault cascading style sheet for the HTML output of Docutils.\n\nSee http://docutils.sf.net/docs/howto/html-stylesheets.html for how to\ncustomize this style sheet.\n*/\n\n/* used to remove borders from tables and images */\n.borderless, table.borderless td, table.borderless th {\n  border: 0 }\n\ntable.borderless td, table.borderless th {\n  /* Override padding for \"table.docutils td\" with \"! important\".\n     The right padding separates the table cells. */\n  padding: 0 0.5em 0 0 ! important }\n\n.first {\n  /* Override more specific margin styles with \"! important\". */\n  margin-top: 0 ! important }\n\n.last, .with-subtitle {\n  margin-bottom: 0 ! important }\n\n.hidden {\n  display: none }\n\n.subscript {\n  vertical-align: sub;\n  font-size: smaller }\n\n.superscript {\n  vertical-align: super;\n  font-size: smaller }\n\na.toc-backref {\n  text-decoration: none ;\n  color: black }\n\nblockquote.epigraph {\n  margin: 2em 5em ; }\n\ndl.docutils dd {\n  margin-bottom: 0.5em }\n\nobject[type=\"image/svg+xml\"], object[type=\"application/x-shockwave-flash\"] {\n  overflow: hidden;\n}\n\n/* Uncomment (and remove this text!) to get bold-faced definition list terms\ndl.docutils dt {\n  font-weight: bold }\n*/\n\ndiv.abstract {\n  margin: 2em 5em }\n\ndiv.abstract p.topic-title {\n  font-weight: bold ;\n  text-align: center }\n\ndiv.admonition, div.attention, div.caution, div.danger, div.error,\ndiv.hint, div.important, div.note, div.tip, div.warning {\n  margin: 2em ;\n  border: medium outset ;\n  padding: 1em }\n\ndiv.admonition p.admonition-title, div.hint p.admonition-title,\ndiv.important p.admonition-title, div.note p.admonition-title,\ndiv.tip p.admonition-title {\n  font-weight: bold ;\n  font-family: sans-serif }\n\ndiv.attention p.admonition-title, div.caution p.admonition-title,\ndiv.danger p.admonition-title, div.error p.admonition-title,\ndiv.warning p.admonition-title, .code .error {\n  color: red ;\n  font-weight: bold ;\n  font-family: sans-serif }\n\n/* Uncomment (and remove this text!) to get reduced vertical space in\n   compound paragraphs.\ndiv.compound .compound-first, div.compound .compound-middle {\n  margin-bottom: 0.5em }\n\ndiv.compound .compound-last, div.compound .compound-middle {\n  margin-top: 0.5em }\n*/\n\ndiv.dedication {\n  margin: 2em 5em ;\n  text-align: center ;\n  font-style: italic }\n\ndiv.dedication p.topic-title {\n  font-weight: bold ;\n  font-style: normal }\n\ndiv.figure {\n  margin-left: 2em ;\n  margin-right: 2em }\n\ndiv.footer, div.header {\n  clear: both;\n  font-size: smaller }\n\ndiv.line-block {\n  display: block ;\n  margin-top: 1em ;\n  margin-bottom: 1em }\n\ndiv.line-block div.line-block {\n  margin-top: 0 ;\n  margin-bottom: 0 ;\n  margin-left: 1.5em }\n\ndiv.sidebar {\n  margin: 0 0 0.5em 1em ;\n  border: medium outset ;\n  padding: 1em ;\n  background-color: #ffffee ;\n  width: 40% ;\n  float: right ;\n  clear: right }\n\ndiv.sidebar p.rubric {\n  font-family: sans-serif ;\n  font-size: medium }\n\ndiv.system-messages {\n  margin: 5em }\n\ndiv.system-messages h1 {\n  color: red }\n\ndiv.system-message {\n  border: medium outset ;\n  padding: 1em }\n\ndiv.system-message p.system-message-title {\n  color: red ;\n  font-weight: bold }\n\ndiv.topic {\n  margin: 2em }\n\nh1.section-subtitle, h2.section-subtitle, h3.section-subtitle,\nh4.section-subtitle, h5.section-subtitle, h6.section-subtitle {\n  margin-top: 0.4em }\n\nh1.title {\n  text-align: center }\n\nh2.subtitle {\n  text-align: center }\n\nhr.docutils {\n  width: 75% }\n\nimg.align-left, .figure.align-left, object.align-left, table.align-left {\n  clear: left ;\n  float: left ;\n  margin-right: 1em }\n\nimg.align-right, .figure.align-right, object.align-right, table.align-right {\n  clear: right ;\n  float: right ;\n  margin-left: 1em }\n\nimg.align-center, .figure.align-center, object.align-center {\n  display: block;\n  margin-left: auto;\n  margin-right: auto;\n}\n\ntable.align-center {\n  margin-left: auto;\n  margin-right: auto;\n}\n\n.align-left {\n  text-align: left }\n\n.align-center {\n  clear: both ;\n  text-align: center }\n\n.align-right {\n  text-align: right }\n\n/* reset inner alignment in figures */\ndiv.align-right {\n  text-align: inherit }\n\n/* div.align-center * { */\n/*   text-align: left } */\n\n.align-top    {\n  vertical-align: top }\n\n.align-middle {\n  vertical-align: middle }\n\n.align-bottom {\n  vertical-align: bottom }\n\nol.simple, ul.simple {\n  margin-bottom: 1em }\n\nol.arabic {\n  list-style: decimal }\n\nol.loweralpha {\n  list-style: lower-alpha }\n\nol.upperalpha {\n  list-style: upper-alpha }\n\nol.lowerroman {\n  list-style: lower-roman }\n\nol.upperroman {\n  list-style: upper-roman }\n\np.attribution {\n  text-align: right ;\n  margin-left: 50% }\n\np.caption {\n  font-style: italic }\n\np.credits {\n  font-style: italic ;\n  font-size: smaller }\n\np.label {\n  white-space: nowrap }\n\np.rubric {\n  font-weight: bold ;\n  font-size: larger ;\n  color: maroon ;\n  text-align: center }\n\np.sidebar-title {\n  font-family: sans-serif ;\n  font-weight: bold ;\n  font-size: larger }\n\np.sidebar-subtitle {\n  font-family: sans-serif ;\n  font-weight: bold }\n\np.topic-title {\n  font-weight: bold }\n\npre.address {\n  margin-bottom: 0 ;\n  margin-top: 0 ;\n  font: inherit }\n\npre.literal-block, pre.doctest-block, pre.math, pre.code {\n  margin-left: 2em ;\n  margin-right: 2em }\n\npre.code .ln { color: grey; } /* line numbers */\npre.code, code { background-color: #eeeeee }\npre.code .comment, code .comment { color: #5C6576 }\npre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }\npre.code .literal.string, code .literal.string { color: #0C5404 }\npre.code .name.builtin, code .name.builtin { color: #352B84 }\npre.code .deleted, code .deleted { background-color: #DEB0A1}\npre.code .inserted, code .inserted { background-color: #A3D289}\n\nspan.classifier {\n  font-family: sans-serif ;\n  font-style: oblique }\n\nspan.classifier-delimiter {\n  font-family: sans-serif ;\n  font-weight: bold }\n\nspan.interpreted {\n  font-family: sans-serif }\n\nspan.option {\n  white-space: nowrap }\n\nspan.pre {\n  white-space: pre }\n\nspan.problematic {\n  color: red }\n\nspan.section-subtitle {\n  /* font-size relative to parent (h1..h6 element) */\n  font-size: 80% }\n\ntable.citation {\n  border-left: solid 1px gray;\n  margin-left: 1px }\n\ntable.docinfo {\n  margin: 2em 4em }\n\ntable.docutils {\n  margin-top: 0.5em ;\n  margin-bottom: 0.5em }\n\ntable.footnote {\n  border-left: solid 1px black;\n  margin-left: 1px }\n\ntable.docutils td, table.docutils th,\ntable.docinfo td, table.docinfo th {\n  padding-left: 0.5em ;\n  padding-right: 0.5em ;\n  vertical-align: top }\n\ntable.docutils th.field-name, table.docinfo th.docinfo-name {\n  font-weight: bold ;\n  text-align: left ;\n  white-space: nowrap ;\n  padding-left: 0 }\n\n/* \"booktabs\" style (no vertical lines) */\ntable.docutils.booktabs {\n  border: 0px;\n  border-top: 2px solid;\n  border-bottom: 2px solid;\n  border-collapse: collapse;\n}\ntable.docutils.booktabs * {\n  border: 0px;\n}\ntable.docutils.booktabs th {\n  border-bottom: thin solid;\n  text-align: left;\n}\n\nh1 tt.docutils, h2 tt.docutils, h3 tt.docutils,\nh4 tt.docutils, h5 tt.docutils, h6 tt.docutils {\n  font-size: 100% }\n\nul.auto-toc {\n  list-style-type: none }\n\n</style>\n</head>\n<body>\n<div class=\"document\">\n\n\n<div class=\"section\" id=\"sanic\">\n<h1>Sanic</h1>\n<p>Sanic is a Python 3.6+ web server and web framework that's written to go fast. It allows the usage of the async/await syntax added in Python 3.5, which makes your code non-blocking and speedy.</p>\n<p>The goal of the project is to provide a simple way to get up and running a highly performant HTTP server that is easy to build, to expand, and ultimately to scale.</p>\n<p>Sanic is developed <a class=\"reference external\" href=\"https://github.com/channelcat/sanic/\">on GitHub</a>. Contributions are welcome!</p>\n<div class=\"section\" id=\"sanic-aspires-to-be-simple\">\n<h2>Sanic aspires to be simple</h2>\n<pre class=\"code python literal-block\">\n<span class=\"keyword namespace\">from</span> <span class=\"name namespace\">sanic</span> <span class=\"keyword namespace\">import</span> <span class=\"name\">Sanic</span>\n<span class=\"keyword namespace\">from</span> <span class=\"name namespace\">sanic.response</span> <span class=\"keyword namespace\">import</span> <span class=\"name\">json</span>\n\n<span class=\"name\">app</span> <span class=\"operator\">=</span> <span class=\"name\">Sanic</span><span class=\"punctuation\">()</span>\n\n<span class=\"name decorator\">&#64;app.route</span><span class=\"punctuation\">(</span><span class=\"literal string double\">&quot;/&quot;</span><span class=\"punctuation\">)</span>\n<span class=\"name\">async</span> <span class=\"keyword\">def</span> <span class=\"name function\">test</span><span class=\"punctuation\">(</span><span class=\"name\">request</span><span class=\"punctuation\">):</span>\n    <span class=\"keyword\">return</span> <span class=\"name\">json</span><span class=\"punctuation\">({</span><span class=\"literal string double\">&quot;hello&quot;</span><span class=\"punctuation\">:</span> <span class=\"literal string double\">&quot;world&quot;</span><span class=\"punctuation\">})</span>\n\n<span class=\"keyword\">if</span> <span class=\"name variable magic\">__name__</span> <span class=\"operator\">==</span> <span class=\"literal string double\">&quot;__main__&quot;</span><span class=\"punctuation\">:</span>\n    <span class=\"name\">app</span><span class=\"operator\">.</span><span class=\"name\">run</span><span class=\"punctuation\">(</span><span class=\"name\">host</span><span class=\"operator\">=</span><span class=\"literal string double\">&quot;0.0.0.0&quot;</span><span class=\"punctuation\">,</span> <span class=\"name\">port</span><span class=\"operator\">=</span><span class=\"literal number integer\">8000</span><span class=\"punctuation\">)</span>\n</pre>\n<div class=\"admonition note\">\n<p class=\"first admonition-title\">Note</p>\n<p class=\"last\">Sanic does not support Python 3.5 from version 19.6 and forward. However, version 18.12LTS is supported thru\nDecember 2020. Official Python support for version 3.5 is set to expire in September 2020.</p>\n</div>\n</div>\n</div>\n<div class=\"section\" id=\"guides\">\n<h1>Guides</h1>\n<div class=\"system-message\">\n<p class=\"system-message-title\">System Message: ERROR/3 (<tt class=\"docutils\">E:/OneDrive/GitHub/sanic/docs/index.rst</tt>, line 6)</p>\n<p>Unknown directive type &quot;toctree&quot;.</p>\n<pre class=\"literal-block\">\n.. toctree::\n   :maxdepth: 2\n\n   sanic/getting_started\n   sanic/config\n   sanic/logging\n   sanic/request_data\n   sanic/response\n   sanic/cookies\n   sanic/routing\n   sanic/blueprints\n   sanic/static_files\n   sanic/versioning\n   sanic/exceptions\n   sanic/middleware\n   sanic/websocket\n   sanic/decorators\n   sanic/streaming\n   sanic/class_based_views\n   sanic/custom_protocol\n   sanic/sockets\n   sanic/ssl\n   sanic/debug_mode\n   sanic/testing\n   sanic/deploying\n   sanic/extensions\n   sanic/examples\n   sanic/changelog\n   sanic/contributing\n   sanic/api_reference\n   sanic/asyncio_python37\n\n\n</pre>\n</div>\n</div>\n<div class=\"section\" id=\"module-documentation\">\n<h1>Module Documentation</h1>\n<div class=\"system-message\">\n<p class=\"system-message-title\">System Message: ERROR/3 (<tt class=\"docutils\">E:/OneDrive/GitHub/sanic/docs/index.rst</tt>, line 42)</p>\n<p>Unknown directive type &quot;toctree&quot;.</p>\n<pre class=\"literal-block\">\n.. toctree::\n\n</pre>\n</div>\n<ul>\n<li><p class=\"first\"><a href=\"#id1\"><span class=\"problematic\" id=\"id2\">:ref:`genindex`</span></a></p>\n<div class=\"system-message\" id=\"id1\">\n<p class=\"system-message-title\">System Message: ERROR/3 (<tt class=\"docutils\">E:/OneDrive/GitHub/sanic/docs/index.rst</tt>, line 44); <em><a href=\"#id2\">backlink</a></em></p>\n<p>Unknown interpreted text role &quot;ref&quot;.</p>\n</div>\n</li>\n<li><p class=\"first\"><a href=\"#id3\"><span class=\"problematic\" id=\"id4\">:ref:`modindex`</span></a></p>\n<div class=\"system-message\" id=\"id3\">\n<p class=\"system-message-title\">System Message: ERROR/3 (<tt class=\"docutils\">E:/OneDrive/GitHub/sanic/docs/index.rst</tt>, line 45); <em><a href=\"#id4\">backlink</a></em></p>\n<p>Unknown interpreted text role &quot;ref&quot;.</p>\n</div>\n</li>\n<li><p class=\"first\"><a href=\"#id5\"><span class=\"problematic\" id=\"id6\">:ref:`search`</span></a></p>\n<div class=\"system-message\" id=\"id5\">\n<p class=\"system-message-title\">System Message: ERROR/3 (<tt class=\"docutils\">E:/OneDrive/GitHub/sanic/docs/index.rst</tt>, line 46); <em><a href=\"#id6\">backlink</a></em></p>\n<p>Unknown interpreted text role &quot;ref&quot;.</p>\n</div>\n</li>\n</ul>\n</div>\n</div>\n</body>\n</html>\n"
  },
  {
    "path": "docs/index.rst",
    "content": ".. include:: ../README.rst\n\nUser Guide\n==========\n\nTo learn about using Sanic, checkout the `User Guide <https://sanicframework.org/guide/>`__.\n\nAPI\n===\n\n.. toctree::\n   :maxdepth: 3\n\n   👥 User Guide <https://sanicframework.org/guide/>\n   sanic/api_reference\n   💻 Source code <https://github.com/sanic-org/sanic/>\n   sanic/changelog\n   sanic/contributing\n   ❓ Support <https://community.sanicframework.org/>\n   💬 Chat <https://discord.gg/FARQzAEMAA>\n\n\nModule Documentation\n====================\n\n.. toctree::\n\n* :ref:`genindex`\n* :ref:`modindex`\n"
  },
  {
    "path": "docs/make.bat",
    "content": "@ECHO OFF\n\nREM Command file for Sphinx documentation\n\nif \"%SPHINXBUILD%\" == \"\" (\n\tset SPHINXBUILD=sphinx-build\n)\nset BUILDDIR=_build\nset ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .\nset I18NSPHINXOPTS=%SPHINXOPTS% .\nif NOT \"%PAPER%\" == \"\" (\n\tset ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%\n\tset I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%\n)\n\nif \"%1\" == \"\" goto help\n\nif \"%1\" == \"help\" (\n\t:help\n\techo.Please use `make ^<target^>` where ^<target^> is one of\n\techo.  html       to make standalone HTML files\n\techo.  dirhtml    to make HTML files named index.html in directories\n\techo.  singlehtml to make a single large HTML file\n\techo.  pickle     to make pickle files\n\techo.  json       to make JSON files\n\techo.  htmlhelp   to make HTML files and a HTML help project\n\techo.  qthelp     to make HTML files and a qthelp project\n\techo.  devhelp    to make HTML files and a Devhelp project\n\techo.  epub       to make an epub\n\techo.  epub3      to make an epub3\n\techo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\n\techo.  text       to make text files\n\techo.  man        to make manual pages\n\techo.  texinfo    to make Texinfo files\n\techo.  gettext    to make PO message catalogs\n\techo.  changes    to make an overview over all changed/added/deprecated items\n\techo.  xml        to make Docutils-native XML files\n\techo.  pseudoxml  to make pseudoxml-XML files for display purposes\n\techo.  linkcheck  to check all external links for integrity\n\techo.  doctest    to run all doctests embedded in the documentation if enabled\n\techo.  coverage   to run coverage check of the documentation if enabled\n\techo.  dummy      to check syntax errors of document sources\n\tgoto end\n)\n\nif \"%1\" == \"clean\" (\n\tfor /d %%i in (%BUILDDIR%\\*) do rmdir /q /s %%i\n\tdel /q /s %BUILDDIR%\\*\n\tgoto end\n)\n\n\nREM Check if sphinx-build is available and fallback to Python version if any\n%SPHINXBUILD% 1>NUL 2>NUL\nif errorlevel 9009 goto sphinx_python\ngoto sphinx_ok\n\n:sphinx_python\n\nset SPHINXBUILD=python -m sphinx.__init__\n%SPHINXBUILD% 2> nul\nif errorlevel 9009 (\n\techo.\n\techo.The 'sphinx-build' command was not found. Make sure you have Sphinx\n\techo.installed, then set the SPHINXBUILD environment variable to point\n\techo.to the full path of the 'sphinx-build' executable. Alternatively you\n\techo.may add the Sphinx directory to PATH.\n\techo.\n\techo.If you don't have Sphinx installed, grab it from\n\techo.http://sphinx-doc.org/\n\texit /b 1\n)\n\n:sphinx_ok\n\n\nif \"%1\" == \"html\" (\n\t%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/html.\n\tgoto end\n)\n\nif \"%1\" == \"dirhtml\" (\n\t%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.\n\tgoto end\n)\n\nif \"%1\" == \"singlehtml\" (\n\t%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.\n\tgoto end\n)\n\nif \"%1\" == \"pickle\" (\n\t%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can process the pickle files.\n\tgoto end\n)\n\nif \"%1\" == \"json\" (\n\t%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can process the JSON files.\n\tgoto end\n)\n\nif \"%1\" == \"htmlhelp\" (\n\t%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can run HTML Help Workshop with the ^\n.hhp project file in %BUILDDIR%/htmlhelp.\n\tgoto end\n)\n\nif \"%1\" == \"qthelp\" (\n\t%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can run \"qcollectiongenerator\" with the ^\n.qhcp project file in %BUILDDIR%/qthelp, like this:\n\techo.^> qcollectiongenerator %BUILDDIR%\\qthelp\\aiographite.qhcp\n\techo.To view the help file:\n\techo.^> assistant -collectionFile %BUILDDIR%\\qthelp\\aiographite.ghc\n\tgoto end\n)\n\nif \"%1\" == \"devhelp\" (\n\t%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished.\n\tgoto end\n)\n\nif \"%1\" == \"epub\" (\n\t%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The epub file is in %BUILDDIR%/epub.\n\tgoto end\n)\n\nif \"%1\" == \"epub3\" (\n\t%SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The epub3 file is in %BUILDDIR%/epub3.\n\tgoto end\n)\n\nif \"%1\" == \"latex\" (\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; the LaTeX files are in %BUILDDIR%/latex.\n\tgoto end\n)\n\nif \"%1\" == \"latexpdf\" (\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\n\tcd %BUILDDIR%/latex\n\tmake all-pdf\n\tcd %~dp0\n\techo.\n\techo.Build finished; the PDF files are in %BUILDDIR%/latex.\n\tgoto end\n)\n\nif \"%1\" == \"latexpdfja\" (\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\n\tcd %BUILDDIR%/latex\n\tmake all-pdf-ja\n\tcd %~dp0\n\techo.\n\techo.Build finished; the PDF files are in %BUILDDIR%/latex.\n\tgoto end\n)\n\nif \"%1\" == \"text\" (\n\t%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The text files are in %BUILDDIR%/text.\n\tgoto end\n)\n\nif \"%1\" == \"man\" (\n\t%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The manual pages are in %BUILDDIR%/man.\n\tgoto end\n)\n\nif \"%1\" == \"texinfo\" (\n\t%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.\n\tgoto end\n)\n\nif \"%1\" == \"gettext\" (\n\t%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The message catalogs are in %BUILDDIR%/locale.\n\tgoto end\n)\n\nif \"%1\" == \"changes\" (\n\t%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.The overview file is in %BUILDDIR%/changes.\n\tgoto end\n)\n\nif \"%1\" == \"linkcheck\" (\n\t%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Link check complete; look for any errors in the above output ^\nor in %BUILDDIR%/linkcheck/output.txt.\n\tgoto end\n)\n\nif \"%1\" == \"doctest\" (\n\t%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Testing of doctests in the sources finished, look at the ^\nresults in %BUILDDIR%/doctest/output.txt.\n\tgoto end\n)\n\nif \"%1\" == \"coverage\" (\n\t%SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Testing of coverage in the sources finished, look at the ^\nresults in %BUILDDIR%/coverage/python.txt.\n\tgoto end\n)\n\nif \"%1\" == \"xml\" (\n\t%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The XML files are in %BUILDDIR%/xml.\n\tgoto end\n)\n\nif \"%1\" == \"pseudoxml\" (\n\t%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.\n\tgoto end\n)\n\nif \"%1\" == \"dummy\" (\n\t%SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. Dummy builder generates no files.\n\tgoto end\n)\n\n:end\n"
  },
  {
    "path": "docs/sanic/api/app.rst",
    "content": "Application\n===========\n\nsanic.app\n---------\n\n.. automodule:: sanic.app\n    :members:\n    :show-inheritance:\n    :inherited-members:\n\nsanic.config\n------------\n\n.. automodule:: sanic.config\n    :members:\n    :show-inheritance:\n\nsanic.application.constants\n---------------------------\n\n.. automodule:: sanic.application.constants\n    :exclude-members: StrEnum\n    :members:\n    :show-inheritance:\n    :inherited-members:\n\nsanic.application.state\n-----------------------\n\n.. automodule:: sanic.application.state\n    :members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/sanic/api/blueprints.rst",
    "content": "Blueprints\n==========\n\nsanic.blueprints\n----------------\n\n.. automodule:: sanic.blueprints\n    :members:\n    :show-inheritance:\n    :inherited-members:\n\nsanic.blueprint_group\n---------------------\n\n.. automodule:: sanic.blueprint_group\n    :members:\n    :special-members:\n"
  },
  {
    "path": "docs/sanic/api/core.rst",
    "content": "Core\n====\n\nsanic.cookies\n-------------\n\n.. automodule:: sanic.cookies\n    :members:\n    :show-inheritance:\n\n\nsanic.handlers\n--------------\n\n.. automodule:: sanic.handlers\n    :members:\n    :show-inheritance:\n\n\nsanic.headers\n--------------\n\n.. automodule:: sanic.headers\n    :members:\n    :show-inheritance:\n\n\nsanic.request\n-------------\n\n.. automodule:: sanic.request\n    :members:\n    :show-inheritance:\n\nsanic.response\n--------------\n\n.. automodule:: sanic.response\n    :members:\n    :show-inheritance:\n\n\nsanic.views\n-----------\n\n.. automodule:: sanic.views\n    :members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/sanic/api/exceptions.rst",
    "content": "Exceptions\n==========\n\nsanic.errorpages\n----------------\n\n.. automodule:: sanic.errorpages\n    :members:\n    :show-inheritance:\n\nsanic.exceptions\n----------------\n\n.. automodule:: sanic.exceptions\n    :members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/sanic/api/router.rst",
    "content": "Routing\n=======\n\nsanic_routing models\n--------------------\n\n.. autoclass:: sanic_routing.route::Route\n    :members:\n\n.. autoclass:: sanic_routing.group::RouteGroup\n    :members:\n\nsanic.router\n------------\n\n.. automodule:: sanic.router\n    :members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/sanic/api/server.rst",
    "content": "Sanic Server\n============\n\nsanic.http\n----------\n\n.. automodule:: sanic.http\n    :members:\n    :show-inheritance:\n\n\nsanic.server\n------------\n\n.. automodule:: sanic.server\n    :members:\n    :show-inheritance:\n\n"
  },
  {
    "path": "docs/sanic/api/utility.rst",
    "content": "Utility\n=======\n\nsanic.compat\n------------\n\n.. automodule:: sanic.compat\n    :members:\n    :show-inheritance:\n\nsanic.log\n---------\n\n.. automodule:: sanic.log\n    :members:\n    :show-inheritance:\n"
  },
  {
    "path": "docs/sanic/api_reference.rst",
    "content": "📑 API Reference\n================\n\n.. toctree::\n   :maxdepth: 2\n\n   api/app\n   api/blueprints\n   api/core\n   api/exceptions\n   api/router\n   api/server\n   api/utility\n"
  },
  {
    "path": "docs/sanic/changelog.rst",
    "content": "📜 Changelog\n============\n\n| 🔶 Current release\n| 🔷 In support release\n|\n\n.. mdinclude:: ./releases/23/23.6.md\n.. mdinclude:: ./releases/23/23.3.md\n.. mdinclude:: ./releases/22/22.12.md\n.. mdinclude:: ./releases/22/22.9.md\n.. mdinclude:: ./releases/22/22.6.md\n.. mdinclude:: ./releases/22/22.3.md\n.. mdinclude:: ./releases/21/21.12.md\n.. mdinclude:: ./releases/21/21.9.md\n.. include:: ../../CHANGELOG.rst\n"
  },
  {
    "path": "docs/sanic/contributing.rst",
    "content": "♥️ Contributing\n===============\n\n.. include:: ../../CONTRIBUTING.rst\n"
  },
  {
    "path": "docs/sanic/releases/21/21.12.md",
    "content": "## Version 21.12.1 🔷\n\n_Current LTS version_\n\n- [#2349](https://github.com/sanic-org/sanic/pull/2349) Only display MOTD on startup\n- [#2354](https://github.com/sanic-org/sanic/pull/2354) Ignore name argument in Python 3.7\n- [#2355](https://github.com/sanic-org/sanic/pull/2355) Add config.update support for all config values\n\n## Version 21.12.0 🔹\n\n### Features\n- [#2260](https://github.com/sanic-org/sanic/pull/2260) Allow early Blueprint registrations to still apply later added objects\n- [#2262](https://github.com/sanic-org/sanic/pull/2262) Noisy exceptions - force logging of all exceptions\n- [#2264](https://github.com/sanic-org/sanic/pull/2264) Optional `uvloop` by configuration\n- [#2270](https://github.com/sanic-org/sanic/pull/2270) Vhost support using multiple TLS certificates\n- [#2277](https://github.com/sanic-org/sanic/pull/2277) Change signal routing for increased consistency\n    - *BREAKING CHANGE*: If you were manually routing signals there is a breaking change. The signal router's `get` is no longer 100% determinative. There is now an additional step to loop thru the returned signals for proper matching on the requirements. If signals are being dispatched using `app.dispatch` or `bp.dispatch`, there is no change.\n- [#2290](https://github.com/sanic-org/sanic/pull/2290) Add contextual exceptions\n- [#2291](https://github.com/sanic-org/sanic/pull/2291) Increase join concat performance \n- [#2295](https://github.com/sanic-org/sanic/pull/2295), [#2316](https://github.com/sanic-org/sanic/pull/2316), [#2331](https://github.com/sanic-org/sanic/pull/2331) Restructure of CLI and application state with new displays and more command parity with `app.run`\n- [#2302](https://github.com/sanic-org/sanic/pull/2302) Add route context at definition time\n- [#2304](https://github.com/sanic-org/sanic/pull/2304) Named tasks and new API for managing background tasks\n- [#2307](https://github.com/sanic-org/sanic/pull/2307) On app auto-reload, provide insight of changed files\n- [#2308](https://github.com/sanic-org/sanic/pull/2308) Auto extend application with [Sanic Extensions](https://sanicframework.org/en/plugins/sanic-ext/getting-started.html) if it is installed, and provide first class support for accessing the extensions\n- [#2309](https://github.com/sanic-org/sanic/pull/2309) Builtin signals changed to `Enum`\n- [#2313](https://github.com/sanic-org/sanic/pull/2313) Support additional config implementation use case\n- [#2321](https://github.com/sanic-org/sanic/pull/2321) Refactor environment variable hydration logic\n- [#2327](https://github.com/sanic-org/sanic/pull/2327) Prevent sending multiple or mixed responses on a single request\n- [#2330](https://github.com/sanic-org/sanic/pull/2330) Custom type casting on environment variables\n- [#2332](https://github.com/sanic-org/sanic/pull/2332) Make all deprecation notices consistent\n- [#2335](https://github.com/sanic-org/sanic/pull/2335) Allow underscore to start instance names\n\n### Bugfixes\n- [#2273](https://github.com/sanic-org/sanic/pull/2273) Replace assignation by typing for `websocket_handshake`\n- [#2285](https://github.com/sanic-org/sanic/pull/2285) Fix IPv6 display in startup logs\n- [#2299](https://github.com/sanic-org/sanic/pull/2299) Dispatch `http.lifecyle.response` from exception handler\n\n### Deprecations and Removals\n- [#2306](https://github.com/sanic-org/sanic/pull/2306) Removal of deprecated items\n    - `Sanic` and `Blueprint` may no longer have arbitrary properties attached to them\n    - `Sanic` and `Blueprint` forced to have compliant names\n        - alphanumeric + `_` + `-`\n        - must start with letter or `_`\n    - `load_env` keyword argument of `Sanic`\n    - `sanic.exceptions.abort`\n    - `sanic.views.CompositionView`\n    - `sanic.response.StreamingHTTPResponse`\n        - *NOTE:* the `stream()` response method (where you pass a callable streaming function) has been deprecated and will be removed in v22.6. You should upgrade all streaming responses to the new style: https://sanicframework.org/en/guide/advanced/streaming.html#response-streaming\n- [#2320](https://github.com/sanic-org/sanic/pull/2320) Remove app instance from Config for error handler setting\n\n### Developer infrastructure\n- [#2251](https://github.com/sanic-org/sanic/pull/2251) Change dev install command\n- [#2286](https://github.com/sanic-org/sanic/pull/2286) Change codeclimate complexity threshold from 5 to 10\n- [#2287](https://github.com/sanic-org/sanic/pull/2287) Update host test function names so they are not overwritten\n- [#2292](https://github.com/sanic-org/sanic/pull/2292) Fail CI on error\n- [#2311](https://github.com/sanic-org/sanic/pull/2311), [#2324](https://github.com/sanic-org/sanic/pull/2324) Do not run tests for draft PRs\n- [#2336](https://github.com/sanic-org/sanic/pull/2336) Remove paths from coverage checks\n- [#2338](https://github.com/sanic-org/sanic/pull/2338) Cleanup ports on tests\n\n### Improved Documentation\n- [#2269](https://github.com/sanic-org/sanic/pull/2269), [#2329](https://github.com/sanic-org/sanic/pull/2329), [#2333](https://github.com/sanic-org/sanic/pull/2333) Cleanup typos and fix language\n\n### Miscellaneous\n- [#2257](https://github.com/sanic-org/sanic/pull/2257), [#2294](https://github.com/sanic-org/sanic/pull/2294), [#2341](https://github.com/sanic-org/sanic/pull/2341) Add Python 3.10 support\n- [#2279](https://github.com/sanic-org/sanic/pull/2279), [#2317](https://github.com/sanic-org/sanic/pull/2317), [#2322](https://github.com/sanic-org/sanic/pull/2322) Add/correct missing type annotations\n- [#2305](https://github.com/sanic-org/sanic/pull/2305) Fix examples to use modern implementations\n"
  },
  {
    "path": "docs/sanic/releases/21/21.9.md",
    "content": "## Version 21.9.3\n*Rerelease of v21.9.2 with some cleanup*\n\n## Version 21.9.2\n- [#2268](https://github.com/sanic-org/sanic/pull/2268) Make HTTP connections start in IDLE stage, avoiding delays and error messages\n- [#2310](https://github.com/sanic-org/sanic/pull/2310) More consistent config setting with post-FALLBACK_ERROR_FORMAT apply\n\n## Version 21.9.1\n- [#2259](https://github.com/sanic-org/sanic/pull/2259) Allow non-conforming ErrorHandlers\n\n## Version 21.9.0\n\n### Features\n- [#2158](https://github.com/sanic-org/sanic/pull/2158), [#2248](https://github.com/sanic-org/sanic/pull/2248) Complete overhaul of I/O to websockets\n- [#2160](https://github.com/sanic-org/sanic/pull/2160) Add new 17 signals into server and request lifecycles\n- [#2162](https://github.com/sanic-org/sanic/pull/2162) Smarter `auto` fallback formatting upon exception\n- [#2184](https://github.com/sanic-org/sanic/pull/2184) Introduce implementation for copying a Blueprint\n- [#2200](https://github.com/sanic-org/sanic/pull/2200) Accept header parsing\n- [#2207](https://github.com/sanic-org/sanic/pull/2207) Log remote address if available\n- [#2209](https://github.com/sanic-org/sanic/pull/2209) Add convenience methods to BP groups\n- [#2216](https://github.com/sanic-org/sanic/pull/2216) Add default messages to SanicExceptions\n- [#2225](https://github.com/sanic-org/sanic/pull/2225) Type annotation convenience for annotated handlers with path parameters\n- [#2236](https://github.com/sanic-org/sanic/pull/2236) Allow Falsey (but not-None) responses from route handlers\n- [#2238](https://github.com/sanic-org/sanic/pull/2238) Add `exception` decorator to Blueprint Groups\n- [#2244](https://github.com/sanic-org/sanic/pull/2244) Explicit static directive for serving file or dir (ex: `static(..., resource_type=\"file\")`)\n- [#2245](https://github.com/sanic-org/sanic/pull/2245) Close HTTP loop when connection task cancelled\n\n### Bugfixes\n- [#2188](https://github.com/sanic-org/sanic/pull/2188) Fix the handling of the end of a chunked request\n- [#2195](https://github.com/sanic-org/sanic/pull/2195) Resolve unexpected error handling on static requests\n- [#2208](https://github.com/sanic-org/sanic/pull/2208) Make blueprint-based exceptions attach and trigger in a more intuitive manner\n- [#2211](https://github.com/sanic-org/sanic/pull/2211) Fixed for handling exceptions of asgi app call\n- [#2213](https://github.com/sanic-org/sanic/pull/2213) Fix bug where ws exceptions not being logged\n- [#2231](https://github.com/sanic-org/sanic/pull/2231) Cleaner closing of tasks by using `abort()` in strategic places to avoid dangling sockets\n- [#2247](https://github.com/sanic-org/sanic/pull/2247) Fix logging of auto-reload status in debug mode\n- [#2246](https://github.com/sanic-org/sanic/pull/2246) Account for BP with exception handler but no routes\n\n### Developer infrastructure  \n- [#2194](https://github.com/sanic-org/sanic/pull/2194) HTTP unit tests with raw client\n- [#2199](https://github.com/sanic-org/sanic/pull/2199) Switch to codeclimate\n- [#2214](https://github.com/sanic-org/sanic/pull/2214) Try Reopening Windows Tests\n- [#2229](https://github.com/sanic-org/sanic/pull/2229) Refactor `HttpProtocol` into a base class\n- [#2230](https://github.com/sanic-org/sanic/pull/2230) Refactor `server.py` into multi-file module\n\n### Miscellaneous\n- [#2173](https://github.com/sanic-org/sanic/pull/2173) Remove Duplicated Dependencies and PEP 517 Support \n- [#2193](https://github.com/sanic-org/sanic/pull/2193), [#2196](https://github.com/sanic-org/sanic/pull/2196), [#2217](https://github.com/sanic-org/sanic/pull/2217) Type annotation changes\n\n\n\n"
  },
  {
    "path": "docs/sanic/releases/22/22.12.md",
    "content": "## Version 22.12.0 🔷\n\n_Current version_\n\n### Features\n\n- [#2569](https://github.com/sanic-org/sanic/pull/2569) Add `JSONResponse` class with some convenient methods when updating a response object\n- [#2598](https://github.com/sanic-org/sanic/pull/2598) Change `uvloop` requirement to `>=0.15.0`\n- [#2609](https://github.com/sanic-org/sanic/pull/2609) Add compatibility with `websockets` v11.0\n- [#2610](https://github.com/sanic-org/sanic/pull/2610) Kill server early on worker error\n    - Raise deadlock timeout to 30s\n- [#2617](https://github.com/sanic-org/sanic/pull/2617) Scale number of running server workers\n- [#2621](https://github.com/sanic-org/sanic/pull/2621) [#2634](https://github.com/sanic-org/sanic/pull/2634) Send `SIGKILL` on subsequent `ctrl+c` to force worker exit\n- [#2622](https://github.com/sanic-org/sanic/pull/2622) Add API to restart all workers from the multiplexer\n- [#2624](https://github.com/sanic-org/sanic/pull/2624) Default to `spawn` for all subprocesses unless specifically set:\n    ```python\n    from sanic import Sanic\n    \n    Sanic.start_method = \"fork\"\n    ```\n- [#2625](https://github.com/sanic-org/sanic/pull/2625) Filename normalisation of form-data/multipart file uploads\n- [#2626](https://github.com/sanic-org/sanic/pull/2626) Move to HTTP Inspector:\n    - Remote access to inspect running Sanic instances\n    - TLS support for encrypted calls to Inspector\n    - Authentication to Inspector with API key\n    - Ability to extend Inspector with custom commands\n- [#2632](https://github.com/sanic-org/sanic/pull/2632) Control order of restart operations\n- [#2633](https://github.com/sanic-org/sanic/pull/2633) Move reload interval to class variable\n- [#2636](https://github.com/sanic-org/sanic/pull/2636) Add `priority` to `register_middleware` method\n- [#2639](https://github.com/sanic-org/sanic/pull/2639) Add `unquote` to `add_route` method\n- [#2640](https://github.com/sanic-org/sanic/pull/2640) ASGI websockets to receive `text` or `bytes`\n\n\n### Bugfixes\n\n- [#2607](https://github.com/sanic-org/sanic/pull/2607) Force socket shutdown before close to allow rebinding\n- [#2590](https://github.com/sanic-org/sanic/pull/2590) Use actual `StrEnum` in Python 3.11+\n- [#2615](https://github.com/sanic-org/sanic/pull/2615) Ensure middleware executes only once per request timeout\n- [#2627](https://github.com/sanic-org/sanic/pull/2627) Crash ASGI application on lifespan failure\n- [#2635](https://github.com/sanic-org/sanic/pull/2635) Resolve error with low-level server creation on Windows\n\n\n### Deprecations and Removals\n\n- [#2608](https://github.com/sanic-org/sanic/pull/2608) [#2630](https://github.com/sanic-org/sanic/pull/2630) Signal conditions and triggers saved on `signal.extra` \n- [#2626](https://github.com/sanic-org/sanic/pull/2626) Move to HTTP Inspector\n    - 🚨 *BREAKING CHANGE*: Moves the Inspector to a Sanic app from a simple TCP socket with a custom protocol\n    - *DEPRECATE*: The `--inspect*` commands have been deprecated in favor of `inspect ...` commands\n- [#2628](https://github.com/sanic-org/sanic/pull/2628) Replace deprecated `distutils.strtobool`\n\n\n### Developer infrastructure\n\n- [#2612](https://github.com/sanic-org/sanic/pull/2612) Add CI testing for Python 3.11\n\n"
  },
  {
    "path": "docs/sanic/releases/22/22.3.md",
    "content": "## Version 22.3.0\n\n### Features\n- [#2347](https://github.com/sanic-org/sanic/pull/2347) API for multi-application server\n    - 🚨 *BREAKING CHANGE*: The old `sanic.worker.GunicornWorker` has been **removed**. To run Sanic with `gunicorn`, you should use it thru `uvicorn` [as described in their docs](https://www.uvicorn.org/#running-with-gunicorn).\n    - 🧁 *SIDE EFFECT*: Named background tasks are now supported, even in Python 3.7\n- [#2357](https://github.com/sanic-org/sanic/pull/2357) Parse `Authorization` header as `Request.credentials`\n- [#2361](https://github.com/sanic-org/sanic/pull/2361) Add config option to skip `Touchup` step in application startup\n- [#2372](https://github.com/sanic-org/sanic/pull/2372) Updates to CLI help messaging\n- [#2382](https://github.com/sanic-org/sanic/pull/2382) Downgrade warnings to backwater debug messages \n- [#2396](https://github.com/sanic-org/sanic/pull/2396) Allow for `multidict` v0.6\n- [#2401](https://github.com/sanic-org/sanic/pull/2401) Upgrade CLI catching for alternative application run types\n- [#2402](https://github.com/sanic-org/sanic/pull/2402) Conditionally inject CLI arguments into factory\n- [#2413](https://github.com/sanic-org/sanic/pull/2413) Add new start and stop event listeners to reloader process\n- [#2414](https://github.com/sanic-org/sanic/pull/2414) Remove loop as required listener arg\n- [#2415](https://github.com/sanic-org/sanic/pull/2415) Better exception for bad URL parsing\n- [sanic-routing#47](https://github.com/sanic-org/sanic-routing/pull/47) Add a new extention parameter type: `<file:ext>`, `<file:ext=jpg>`, `<file:ext=jpg|png|gif|svg>`, `<file=int:ext>`, `<file=int:ext=jpg|png|gif|svg>`, `<file=float:ext=tar.gz>`\n    - 👶 *BETA FEATURE*: This feature will not work with `path` type matching, and is being released as a beta feature only.\n- [sanic-routing#57](https://github.com/sanic-org/sanic-routing/pull/57) Change `register_pattern` to accept a `str` or `Pattern`\n- [sanic-routing#58](https://github.com/sanic-org/sanic-routing/pull/58) Default matching on non-empty strings only, and new `strorempty` pattern type\n    - 🚨 *BREAKING CHANGE*: Previously a route with a dynamic string parameter (`/<foo>` or `/<foo:str>`) would match on any string, including empty strings. It will now **only** match a non-empty string. To retain the old behavior, you should use the new parameter type: `/<foo:strorempty>`.\n\n### Bugfixes\n- [#2373](https://github.com/sanic-org/sanic/pull/2373) Remove `error_logger` on websockets\n- [#2381](https://github.com/sanic-org/sanic/pull/2381) Fix newly assigned `None` in task registry\n- [sanic-routing#52](https://github.com/sanic-org/sanic-routing/pull/52) Add type casting to regex route matching\n- [sanic-routing#60](https://github.com/sanic-org/sanic-routing/pull/60) Add requirements check on regex routes (this resolves, for example, multiple static directories with differing `host` values)\n\n### Deprecations and Removals\n- [#2362](https://github.com/sanic-org/sanic/pull/2362) 22.3 Deprecations and changes\n    1. `debug=True` and `--debug` do _NOT_ automatically run `auto_reload`\n    2. Default error render is with plain text (browsers still get HTML by default because `auto` looks at headers)\n    3. `config` is required for `ErrorHandler.finalize`\n    4. `ErrorHandler.lookup` requires two positional args\n    5. Unused websocket protocol args removed\n- [#2344](https://github.com/sanic-org/sanic/pull/2344) Deprecate loading of lowercase environment variables\n\n### Developer infrastructure\n- [#2363](https://github.com/sanic-org/sanic/pull/2363) Revert code coverage back to Codecov\n- [#2405](https://github.com/sanic-org/sanic/pull/2405) Upgrade tests for `sanic-routing` changes\n- [sanic-testing#35](https://github.com/sanic-org/sanic-testing/pull/35) Allow for httpx v0.22\n\n### Improved Documentation\n- [#2350](https://github.com/sanic-org/sanic/pull/2350) Fix link in README for ASGI\n- [#2398](https://github.com/sanic-org/sanic/pull/2398) Document middleware on_request and on_response\n- [#2409](https://github.com/sanic-org/sanic/pull/2409) Add missing documentation for `Request.respond`\n\n### Miscellaneous\n- [#2376](https://github.com/sanic-org/sanic/pull/2376) Fix typing for `ListenerMixin.listener`\n- [#2383](https://github.com/sanic-org/sanic/pull/2383) Clear deprecation warning in `asyncio.wait`\n- [#2387](https://github.com/sanic-org/sanic/pull/2387) Cleanup `__slots__` implementations\n- [#2390](https://github.com/sanic-org/sanic/pull/2390) Clear deprecation warning in `asyncio.get_event_loop`\n"
  },
  {
    "path": "docs/sanic/releases/22/22.6.md",
    "content": "## Version 22.6.2\n\n### Bugfixes\n\n- [#2522](https://github.com/sanic-org/sanic/pull/2522) Always show server location in ASGI\n\n## Version 22.6.1\n\n### Bugfixes\n\n- [#2477](https://github.com/sanic-org/sanic/pull/2477) Sanic static directory fails when folder name ends with \"..\"\n\n\n## Version 22.6.0\n\n### Features\n- [#2378](https://github.com/sanic-org/sanic/pull/2378) Introduce HTTP/3 and autogeneration of TLS certificates in `DEBUG` mode\n    - 👶 *EARLY RELEASE FEATURE*: Serving Sanic over HTTP/3 is an early release feature. It does not yet fully cover the HTTP/3 spec, but instead aims for feature parity with Sanic's existing HTTP/1.1 server. Websockets, WebTransport, push responses are examples of some features not yet implemented.\n    - 📦 *EXTRA REQUIREMENT*: Not all HTTP clients are capable of interfacing with HTTP/3 servers. You may need to install a [HTTP/3 capable client](https://curl.se/docs/http3.html).\n    - 📦 *EXTRA REQUIREMENT*: In order to use TLS autogeneration, you must install either [mkcert](https://github.com/FiloSottile/mkcert) or [trustme](https://github.com/python-trio/trustme).\n- [#2416](https://github.com/sanic-org/sanic/pull/2416) Add message to `task.cancel`\n- [#2420](https://github.com/sanic-org/sanic/pull/2420) Add exception aliases for more consistent naming with standard HTTP response types (`BadRequest`, `MethodNotAllowed`, `RangeNotSatisfiable`)\n- [#2432](https://github.com/sanic-org/sanic/pull/2432) Expose ASGI `scope` as a property on the `Request` object\n- [#2438](https://github.com/sanic-org/sanic/pull/2438) Easier access to websocket class for annotation: `from sanic import Websocket`\n- [#2439](https://github.com/sanic-org/sanic/pull/2439) New API for reading form values with options: `Request.get_form` \n- [#2445](https://github.com/sanic-org/sanic/pull/2445) Add custom `loads` function\n- [#2447](https://github.com/sanic-org/sanic/pull/2447), [#2486](https://github.com/sanic-org/sanic/pull/2486) Improved API to support setting cache control headers\n- [#2453](https://github.com/sanic-org/sanic/pull/2453) Move verbosity filtering to logger\n- [#2475](https://github.com/sanic-org/sanic/pull/2475) Expose getter for current request using `Request.get_current()`\n\n### Bugfixes\n- [#2448](https://github.com/sanic-org/sanic/pull/2448) Fix to allow running with `pythonw.exe` or places where there is no `sys.stdout`\n- [#2451](https://github.com/sanic-org/sanic/pull/2451) Trigger `http.lifecycle.request` signal in ASGI mode\n- [#2455](https://github.com/sanic-org/sanic/pull/2455) Resolve typing of stacked route definitions\n- [#2463](https://github.com/sanic-org/sanic/pull/2463) Properly catch websocket CancelledError in websocket handler in Python 3.7\n\n### Deprecations and Removals\n- [#2487](https://github.com/sanic-org/sanic/pull/2487) v22.6 deprecations and changes\n    1. Optional application registry\n    1. Execution of custom handlers after some part of response was sent\n    1. Configuring fallback handlers on the `ErrorHandler`\n    1. Custom `LOGO` setting\n    1. `sanic.response.stream`\n    1. `AsyncioServer.init`\n\n### Developer infrastructure\n- [#2449](https://github.com/sanic-org/sanic/pull/2449) Clean up `black` and `isort` config\n- [#2479](https://github.com/sanic-org/sanic/pull/2479) Fix some flappy tests\n\n### Improved Documentation\n- [#2461](https://github.com/sanic-org/sanic/pull/2461) Update example to match current application naming standards\n- [#2466](https://github.com/sanic-org/sanic/pull/2466) Better type annotation for `Extend`\n- [#2485](https://github.com/sanic-org/sanic/pull/2485) Improved help messages in CLI\n\n"
  },
  {
    "path": "docs/sanic/releases/22/22.9.md",
    "content": "## Version 22.9.1\n\n### Features\n\n- [#2585](https://github.com/sanic-org/sanic/pull/2585) Improved error message when no applications have been registered\n\n\n### Bugfixes\n\n- [#2578](https://github.com/sanic-org/sanic/pull/2578) Add certificate loader for in process certificate creation\n- [#2591](https://github.com/sanic-org/sanic/pull/2591) Do not use sentinel identity for `spawn` compatibility\n- [#2592](https://github.com/sanic-org/sanic/pull/2592) Fix properties in nested blueprint groups\n- [#2595](https://github.com/sanic-org/sanic/pull/2595) Introduce sleep interval on new worker reloader\n\n\n### Deprecations and Removals\n\n\n### Developer infrastructure\n\n- [#2588](https://github.com/sanic-org/sanic/pull/2588) Markdown templates on issue forms\n\n\n### Improved Documentation\n\n- [#2556](https://github.com/sanic-org/sanic/pull/2556) v22.9 documentation\n- [#2582](https://github.com/sanic-org/sanic/pull/2582) Cleanup documentation on Windows support\n\n\n## Version 22.9.0\n\n### Features\n\n- [#2445](https://github.com/sanic-org/sanic/pull/2445) Add custom loads function \n- [#2490](https://github.com/sanic-org/sanic/pull/2490) Make `WebsocketImplProtocol` async iterable\n- [#2499](https://github.com/sanic-org/sanic/pull/2499) Sanic Server WorkerManager refactor\n- [#2506](https://github.com/sanic-org/sanic/pull/2506) Use `pathlib` for path resolution (for static file serving)\n- [#2508](https://github.com/sanic-org/sanic/pull/2508) Use `path.parts` instead of `match` (for static file serving)\n- [#2513](https://github.com/sanic-org/sanic/pull/2513) Better request cancel handling\n- [#2516](https://github.com/sanic-org/sanic/pull/2516) Add request properties for HTTP method info:\n    - `request.is_safe`\n    - `request.is_idempotent`\n    - `request.is_cacheable`\n    - *See* [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) *for more information about when these apply*\n- [#2522](https://github.com/sanic-org/sanic/pull/2522) Always show server location in ASGI\n- [#2526](https://github.com/sanic-org/sanic/pull/2526) Cache control support for static files for returning 304 when appropriate\n- [#2533](https://github.com/sanic-org/sanic/pull/2533) Refactor `_static_request_handler`\n- [#2540](https://github.com/sanic-org/sanic/pull/2540) Add signals before and after handler execution\n    - `http.handler.before`\n    - `http.handler.after`\n- [#2542](https://github.com/sanic-org/sanic/pull/2542) Add *[redacted]* to CLI :)\n- [#2546](https://github.com/sanic-org/sanic/pull/2546) Add deprecation warning filter\n- [#2550](https://github.com/sanic-org/sanic/pull/2550) Middleware priority and performance enhancements\n\n### Bugfixes\n\n- [#2495](https://github.com/sanic-org/sanic/pull/2495) Prevent directory traversion with static files\n- [#2515](https://github.com/sanic-org/sanic/pull/2515) Do not apply double slash to paths in certain static dirs in Blueprints\n\n### Deprecations and Removals\n\n- [#2525](https://github.com/sanic-org/sanic/pull/2525) Warn on duplicate route names, will be prevented outright in v23.3\n- [#2537](https://github.com/sanic-org/sanic/pull/2537) Raise warning and deprecation notice on duplicate exceptions, will be prevented outright in v23.3\n\n### Developer infrastructure\n\n- [#2504](https://github.com/sanic-org/sanic/pull/2504) Cleanup test suite\n- [#2505](https://github.com/sanic-org/sanic/pull/2505) Replace Unsupported Python Version Number from the Contributing Doc\n- [#2530](https://github.com/sanic-org/sanic/pull/2530) Do not include tests folder in installed package resolver\n\n### Improved Documentation\n\n- [#2502](https://github.com/sanic-org/sanic/pull/2502) Fix a few typos\n- [#2517](https://github.com/sanic-org/sanic/pull/2517) [#2536](https://github.com/sanic-org/sanic/pull/2536) Add some type hints\n"
  },
  {
    "path": "docs/sanic/releases/23/23.3.md",
    "content": "## Version 23.3.0\n\n### Features\n- [#2545](https://github.com/sanic-org/sanic/pull/2545) Standardize init of exceptions for more consistent control of HTTP responses using exceptions\n- [#2606](https://github.com/sanic-org/sanic/pull/2606) Decode headers as UTF-8 also in ASGI\n- [#2646](https://github.com/sanic-org/sanic/pull/2646) Separate ASGI request and lifespan callables\n- [#2659](https://github.com/sanic-org/sanic/pull/2659) Use ``FALLBACK_ERROR_FORMAT`` for handlers that return ``empty()``\n- [#2662](https://github.com/sanic-org/sanic/pull/2662) Add basic file browser (HTML page) and auto-index serving\n- [#2667](https://github.com/sanic-org/sanic/pull/2667) Nicer traceback formatting (HTML page)\n- [#2668](https://github.com/sanic-org/sanic/pull/2668) Smarter error page rendering format selection; more reliant upon header and \"common sense\" defaults\n- [#2680](https://github.com/sanic-org/sanic/pull/2680) Check the status of socket before shutting down with ``SHUT_RDWR``\n- [#2687](https://github.com/sanic-org/sanic/pull/2687) Refresh ``Request.accept`` functionality to be more performant and spec-compliant\n- [#2696](https://github.com/sanic-org/sanic/pull/2696) Add header accessors as properties\n    ```\n    Example-Field: Foo, Bar\n    Example-Field: Baz\n    ```\n    ```python\n    request.headers.example_field == \"Foo, Bar,Baz\"\n    ```\n- [#2700](https://github.com/sanic-org/sanic/pull/2700) Simpler CLI targets\n\n    ```sh\n    $ sanic path.to.module:app          # global app instance\n    $ sanic path.to.module:create_app   # factory pattern\n    $ sanic ./path/to/directory/        # simple serve\n    ```\n- [#2701](https://github.com/sanic-org/sanic/pull/2701) API to define a number of workers in managed processes\n- [#2704](https://github.com/sanic-org/sanic/pull/2704) Add convenience for dynamic changes to routing\n- [#2706](https://github.com/sanic-org/sanic/pull/2706) Add convenience methods for cookie creation and deletion\n    \n    ```python\n    response = text(\"...\")\n    response.add_cookie(\"test\", \"It worked!\", domain=\".yummy-yummy-cookie.com\")\n    ```\n- [#2707](https://github.com/sanic-org/sanic/pull/2707) Simplified ``parse_content_header`` escaping to be RFC-compliant and remove outdated FF hack\n- [#2710](https://github.com/sanic-org/sanic/pull/2710) Stricter charset handling and escaping of request URLs\n- [#2711](https://github.com/sanic-org/sanic/pull/2711) Consume body on ``DELETE`` by default\n- [#2719](https://github.com/sanic-org/sanic/pull/2719) Allow ``password`` to be passed to TLS context\n- [#2720](https://github.com/sanic-org/sanic/pull/2720) Skip middleware on ``RequestCancelled``\n- [#2721](https://github.com/sanic-org/sanic/pull/2721) Change access logging format to ``%s``\n- [#2722](https://github.com/sanic-org/sanic/pull/2722) Add ``CertLoader`` as application option for directly controlling ``SSLContext`` objects\n- [#2725](https://github.com/sanic-org/sanic/pull/2725) Worker sync state tolerance on race condition\n\n### Bugfixes\n- [#2651](https://github.com/sanic-org/sanic/pull/2651) ASGI websocket to pass thru bytes as is\n- [#2697](https://github.com/sanic-org/sanic/pull/2697) Fix comparison between datetime aware and naive in ``file`` when using ``If-Modified-Since``\n\n### Deprecations and Removals\n- [#2666](https://github.com/sanic-org/sanic/pull/2666) Remove deprecated ``__blueprintname__`` property\n\n### Improved Documentation\n- [#2712](https://github.com/sanic-org/sanic/pull/2712) Improved example using ``'https'`` to create the redirect\n"
  },
  {
    "path": "docs/sanic/releases/23/23.6.md",
    "content": "## Version 23.6.0  🔶\n\n### Features\n- [#2670](https://github.com/sanic-org/sanic/pull/2670) Increase `KEEP_ALIVE_TIMEOUT` default to 120 seconds\n- [#2716](https://github.com/sanic-org/sanic/pull/2716) Adding allow route overwrite option in blueprint\n- [#2724](https://github.com/sanic-org/sanic/pull/2724) and [#2792](https://github.com/sanic-org/sanic/pull/2792) Add a new exception signal for ALL exceptions raised anywhere in application\n- [#2727](https://github.com/sanic-org/sanic/pull/2727) Add name prefixing to BP groups\n- [#2754](https://github.com/sanic-org/sanic/pull/2754) Update request type on middleware types\n- [#2770](https://github.com/sanic-org/sanic/pull/2770) Better exception message on startup time application induced import error\n- [#2776](https://github.com/sanic-org/sanic/pull/2776) Set multiprocessing start method early\n- [#2785](https://github.com/sanic-org/sanic/pull/2785) Add custom typing to config and ctx objects\n- [#2790](https://github.com/sanic-org/sanic/pull/2790) Add `request.client_ip`\n\n### Bugfixes\n- [#2728](https://github.com/sanic-org/sanic/pull/2728) Fix traversals for intended results\n- [#2729](https://github.com/sanic-org/sanic/pull/2729) Handle case when headers argument of ResponseStream constructor is None\n- [#2737](https://github.com/sanic-org/sanic/pull/2737) Fix type annotation for `JSONREsponse` default content type\n- [#2740](https://github.com/sanic-org/sanic/pull/2740) Use Sanic's serializer for JSON responses in the Inspector\n- [#2760](https://github.com/sanic-org/sanic/pull/2760) Support for `Request.get_current` in ASGI mode\n- [#2773](https://github.com/sanic-org/sanic/pull/2773) Alow Blueprint routes to explicitly define error_format\n- [#2774](https://github.com/sanic-org/sanic/pull/2774) Resolve headers on different renderers\n- [#2782](https://github.com/sanic-org/sanic/pull/2782) Resolve pypy compatibility issues\n\n### Deprecations and Removals\n- [#2777](https://github.com/sanic-org/sanic/pull/2777) Remove Python 3.7 support\n\n### Developer infrastructure\n- [#2766](https://github.com/sanic-org/sanic/pull/2766) Unpin setuptools version\n- [#2779](https://github.com/sanic-org/sanic/pull/2779) Run keep alive tests in loop to get available port\n\n### Improved Documentation\n- [#2741](https://github.com/sanic-org/sanic/pull/2741) Better documentation examples about running Sanic\nFrom that list, the items to highlight in the release notes:\n"
  },
  {
    "path": "examples/Dockerfile",
    "content": "FROM sanicframework/sanic:LTS\n\nRUN mkdir /srv\nCOPY . /srv\n\nWORKDIR /srv\n\nCMD [\"sanic\", \"simple_server.app\"]\n"
  },
  {
    "path": "examples/add_task_sanic.py",
    "content": "# -*- coding: utf-8 -*-\n\nimport asyncio\n\nfrom sanic import Sanic\n\n\napp = Sanic(\"Example\")\n\n\nasync def notify_server_started_after_five_seconds():\n    await asyncio.sleep(5)\n    print(\"Server successfully started!\")\n\n\napp.add_task(notify_server_started_after_five_seconds())\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", port=8000)\n"
  },
  {
    "path": "examples/amending_request_object.py",
    "content": "from random import randint\n\nfrom sanic import Sanic\nfrom sanic.response import text\n\n\napp = Sanic(\"Example\")\n\n\n@app.middleware(\"request\")\ndef append_request(request):\n    request.ctx.num = randint(0, 100)\n\n\n@app.get(\"/pop\")\ndef pop_handler(request):\n    return text(request.ctx.num)\n\n\n@app.get(\"/key_exist\")\ndef key_exist_handler(request):\n    # Check the key is exist or not\n    if hasattr(request.ctx, \"num\"):\n        return text(\"num exist in request\")\n\n    return text(\"num does not exist in request\")\n\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", port=8000, debug=True)\n"
  },
  {
    "path": "examples/authorized_sanic.py",
    "content": "# -*- coding: utf-8 -*-\n\nfrom functools import wraps\n\nfrom sanic import Sanic\nfrom sanic.response import json\n\n\napp = Sanic(\"Example\")\n\n\ndef check_request_for_authorization_status(request):\n    # Note: Define your check, for instance cookie, session.\n    flag = True\n    return flag\n\n\ndef authorized(f):\n    @wraps(f)\n    async def decorated_function(request, *args, **kwargs):\n        # run some method that checks the request\n        # for the client's authorization status\n        is_authorized = check_request_for_authorization_status(request)\n\n        if is_authorized:\n            # the user is authorized.\n            # run the handler method and return the response\n            response = await f(request, *args, **kwargs)\n            return response\n        else:\n            # the user is not authorized.\n            return json({\"status\": \"not_authorized\"}, 403)\n\n    return decorated_function\n\n\n@app.route(\"/\")\n@authorized\nasync def test(request):\n    return json({\"status\": \"authorized\"})\n\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", port=8000)\n"
  },
  {
    "path": "examples/blueprint_middlware_execution_order.py",
    "content": "from sanic import Blueprint, Sanic\nfrom sanic.response import text\n\n\n\"\"\"\nDemonstrates that blueprint request middleware are executed in the order they\nare added. And blueprint response middleware are executed in _reverse_ order.\nOn a valid request, it should print \"1 2 3 6 5 4\" to terminal\n\"\"\"\n\napp = Sanic(\"Example\")\n\nbp = Blueprint(\"bp_example\")\n\n\n@bp.on_request\ndef request_middleware_1(request):\n    print(\"1\")\n\n\n@bp.on_request\ndef request_middleware_2(request):\n    print(\"2\")\n\n\n@bp.on_request\ndef request_middleware_3(request):\n    print(\"3\")\n\n\n@bp.on_response\ndef resp_middleware_4(request, response):\n    print(\"4\")\n\n\n@bp.on_response\ndef resp_middleware_5(request, response):\n    print(\"5\")\n\n\n@bp.on_response\ndef resp_middleware_6(request, response):\n    print(\"6\")\n\n\n@bp.route(\"/\")\ndef pop_handler(request):\n    return text(\"hello world\")\n\n\napp.blueprint(bp, url_prefix=\"/bp\")\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", port=8000, debug=True, auto_reload=False)\n"
  },
  {
    "path": "examples/blueprints.py",
    "content": "from sanic import Blueprint, Sanic\nfrom sanic.response import file, json\n\n\napp = Sanic(\"Example\")\nblueprint = Blueprint(\"bp_example\", url_prefix=\"/my_blueprint\")\nblueprint2 = Blueprint(\"bp_example2\", url_prefix=\"/my_blueprint2\")\nblueprint3 = Blueprint(\"bp_example3\", url_prefix=\"/my_blueprint3\")\n\n\n@blueprint.route(\"/foo\")\nasync def foo(request):\n    return json({\"msg\": \"hi from blueprint\"})\n\n\n@blueprint2.route(\"/foo\")\nasync def foo2(request):\n    return json({\"msg\": \"hi from blueprint2\"})\n\n\n@blueprint3.route(\"/foo\")\nasync def index(request):\n    return await file(\"websocket.html\")\n\n\n@app.websocket(\"/feed\")\nasync def foo3(request, ws):\n    while True:\n        data = \"hello!\"\n        print(\"Sending: \" + data)\n        await ws.send(data)\n        data = await ws.recv()\n        print(\"Received: \" + data)\n\n\napp.blueprint(blueprint)\napp.blueprint(blueprint2)\napp.blueprint(blueprint3)\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", port=9999, debug=True)\n"
  },
  {
    "path": "examples/delayed_response.py",
    "content": "from asyncio import sleep\n\nfrom sanic import Sanic, response\n\n\napp = Sanic(\"DelayedResponseApp\", strict_slashes=True)\napp.config.AUTO_EXTEND = False\n\n\n@app.get(\"/\")\nasync def handler(request):\n    return response.redirect(\"/sleep/3\")\n\n\n@app.get(\"/sleep/<t:float>\")\nasync def handler2(request, t=0.3):\n    await sleep(t)\n    return response.text(f\"Slept {t:.1f} seconds.\\n\")\n\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", port=8000)\n"
  },
  {
    "path": "examples/docker-compose.yml",
    "content": "version: '2'\nservices:\n  sanic:\n    build: .\n    ports:\n      - \"8000:8000\""
  },
  {
    "path": "examples/exception_monitoring.py",
    "content": "\"\"\"\nExample intercepting uncaught exceptions using Sanic's error handler framework.\nThis may be useful for developers wishing to use Sentry, Airbrake, etc.\nor a custom system to log and monitor unexpected errors in production.\nFirst we create our own class inheriting from Handler in sanic.exceptions,\nand pass in an instance of it when we create our Sanic instance. Inside this\nclass' default handler, we can do anything including sending exceptions to\nan external service.\n\"\"\"\n\nfrom sanic import Sanic\nfrom sanic.exceptions import SanicException\nfrom sanic.handlers import ErrorHandler\n\n\n\"\"\"\nImports and code relevant for our CustomHandler class\n(Ordinarily this would be in a separate file)\n\"\"\"\n\n\nclass CustomHandler(ErrorHandler):\n    def default(self, request, exception):\n        # Here, we have access to the exception object\n        # and can do anything with it (log, send to external service, etc)\n\n        # Some exceptions are trivial and built into Sanic (404s, etc)\n        if not isinstance(exception, SanicException):\n            print(exception)\n\n        # Then, we must finish handling the exception by returning\n        # our response to the client\n        # For this we can just call the super class' default handler\n        return super().default(request, exception)\n\n\n\"\"\"\nThis is an ordinary Sanic server, with the exception that we set the\nserver's error_handler to an instance of our CustomHandler\n\"\"\"\n\n\nhandler = CustomHandler()\napp = Sanic(\"Example\", error_handler=handler)\n\n\n@app.route(\"/\")\nasync def test(request):\n    # Here, something occurs which causes an unexpected exception\n    # This exception will flow to our custom handler.\n    raise SanicException(\"You Broke It!\")\n\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", port=8000, debug=True)\n"
  },
  {
    "path": "examples/hello_world.py",
    "content": "from sanic import Sanic, response\n\n\napp = Sanic(\"Example\")\n\n\n@app.route(\"/\")\nasync def test(request):\n    return response.json({\"test\": True})\n\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", port=8000)\n"
  },
  {
    "path": "examples/http_redirect.py",
    "content": "from sanic import Sanic, response, text\nfrom sanic.handlers import ErrorHandler\nfrom sanic.server.async_server import AsyncioServer\n\n\nHTTP_PORT = 9999\nHTTPS_PORT = 8888\n\nhttp = Sanic(\"http\")\nhttp.config.SERVER_NAME = f\"localhost:{HTTP_PORT}\"\nhttps = Sanic(\"https\")\nhttps.config.SERVER_NAME = f\"localhost:{HTTPS_PORT}\"\n\n\n@https.get(\"/foo\")\ndef foo(request):\n    return text(\"foo\")\n\n\n@https.get(\"/bar\")\ndef bar(request):\n    return text(\"bar\")\n\n\n@http.get(\"/<path:path>\")\ndef proxy(request, path):\n    url = request.app.url_for(\n        \"proxy\",\n        path=path,\n        _server=https.config.SERVER_NAME,\n        _external=True,\n        _scheme=\"https\",\n    )\n    return response.redirect(url)\n\n\n@https.main_process_start\nasync def start(app, _):\n    http_server = await http.create_server(\n        port=HTTP_PORT, return_asyncio_server=True\n    )\n    app.add_task(runner(http, http_server))\n    app.ctx.http_server = http_server\n    app.ctx.http = http\n\n\n@https.main_process_stop\nasync def stop(app, _):\n    await app.ctx.http_server.before_stop()\n    await app.ctx.http_server.close()\n    for connection in app.ctx.http_server.connections:\n        connection.close_if_idle()\n    await app.ctx.http_server.after_stop()\n    app.ctx.http = False\n\n\nasync def runner(app: Sanic, app_server: AsyncioServer):\n    app.is_running = True\n    try:\n        app.signalize()\n        app.finalize()\n        ErrorHandler.finalize(app.error_handler)\n        app_server.init = True\n\n        await app_server.before_start()\n        await app_server.after_start()\n        await app_server.serve_forever()\n    finally:\n        app.is_running = False\n        app.is_stopping = True\n\n\nif __name__ == \"__main__\":\n    https.run(port=HTTPS_PORT, debug=True)\n"
  },
  {
    "path": "examples/limit_concurrency.py",
    "content": "import asyncio\n\nimport httpx\n\nfrom sanic import Sanic\nfrom sanic.response import json\n\n\napp = Sanic(\"Example\")\n\nsem = None\n\n\n@app.before_server_start\ndef init(sanic, _):\n    global sem\n    concurrency_per_worker = 4\n    sem = asyncio.Semaphore(concurrency_per_worker)\n\n\nasync def bounded_fetch(session, url):\n    \"\"\"\n    Use session object to perform 'get' request on url\n    \"\"\"\n    async with sem:\n        response = await session.get(url)\n        return response.json()\n\n\n@app.route(\"/\")\nasync def test(request):\n    \"\"\"\n    Download and serve example JSON\n    \"\"\"\n    url = \"https://api.github.com/repos/sanic-org/sanic\"\n\n    async with httpx.AsyncClient() as session:\n        response = await bounded_fetch(session, url)\n        return json(response)\n\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", port=8000, workers=2)\n"
  },
  {
    "path": "examples/log_request_id.py",
    "content": "import logging\n\nfrom contextvars import ContextVar\n\nfrom sanic import Sanic, response\n\n\nlog = logging.getLogger(__name__)\n\n\nclass RequestIdFilter(logging.Filter):\n    def filter(self, record):\n        try:\n            record.request_id = app.ctx.request_id.get(None) or \"n/a\"\n        except AttributeError:\n            record.request_id = \"n/a\"\n        return True\n\n\nLOG_SETTINGS = {\n    \"version\": 1,\n    \"disable_existing_loggers\": False,\n    \"handlers\": {\n        \"console\": {\n            \"class\": \"logging.StreamHandler\",\n            \"level\": \"DEBUG\",\n            \"formatter\": \"default\",\n            \"filters\": [\"requestid\"],\n        },\n    },\n    \"filters\": {\n        \"requestid\": {\n            \"()\": RequestIdFilter,\n        },\n    },\n    \"formatters\": {\n        \"default\": {\n            \"format\": (\n                \"%(asctime)s %(levelname)s %(name)s:%(lineno)d\"\n                \" %(request_id)s | %(message)s\"\n            ),\n        },\n    },\n    \"loggers\": {\n        \"\": {\"level\": \"DEBUG\", \"handlers\": [\"console\"], \"propagate\": True},\n    },\n}\n\n\napp = Sanic(\"Example\", log_config=LOG_SETTINGS)\n\n\n@app.on_request\nasync def set_request_id(request):\n    request.app.ctx.request_id.set(request.id)\n    log.info(f\"Setting {request.id=}\")\n\n\n@app.on_response\nasync def set_request_header(request, response):\n    response.headers[\"X-Request-ID\"] = request.id\n\n\n@app.route(\"/\")\nasync def test(request):\n    log.debug(\"X-Request-ID: %s\", request.id)\n    log.info(\"Hello from test!\")\n    return response.json({\"test\": True})\n\n\n@app.before_server_start\ndef setup(app, loop):\n    app.ctx.request_id = ContextVar(\"request_id\")\n\n\nif __name__ == \"__main__\":\n    app.run(port=9999, debug=True)\n"
  },
  {
    "path": "examples/logdna_example.py",
    "content": "import logging\nimport socket\n\nfrom os import getenv\nfrom platform import node\nfrom uuid import getnode as get_mac\n\nfrom logdna import LogDNAHandler\n\nfrom sanic import Sanic\nfrom sanic.request import Request\nfrom sanic.response import json\n\n\nlog = logging.getLogger(\"logdna\")\nlog.setLevel(logging.INFO)\n\n\ndef get_my_ip_address(remote_server=\"google.com\"):\n    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:\n        s.connect((remote_server, 80))\n        return s.getsockname()[0]\n\n\ndef get_mac_address():\n    h = iter(hex(get_mac())[2:].zfill(12))\n    return \":\".join(i + next(h) for i in h)\n\n\nlogdna_options = {\n    \"app\": __name__,\n    \"index_meta\": True,\n    \"hostname\": node(),\n    \"ip\": get_my_ip_address(),\n    \"mac\": get_mac_address(),\n}\n\nlogdna_handler = LogDNAHandler(\n    getenv(\"LOGDNA_API_KEY\"), options=logdna_options\n)\n\nlogdna = logging.getLogger(__name__)\nlogdna.setLevel(logging.INFO)\nlogdna.addHandler(logdna_handler)\n\napp = Sanic(\"Example\")\n\n\n@app.middleware\ndef log_request(request: Request):\n    logdna.info(\"I was Here with a new Request to URL: {}\".format(request.url))\n\n\n@app.route(\"/\")\ndef default(request):\n    return json({\"response\": \"I was here\"})\n\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", port=getenv(\"PORT\", 8080))\n"
  },
  {
    "path": "examples/modify_header_example.py",
    "content": "\"\"\"\nModify header or status in response\n\"\"\"\n\nfrom sanic import Sanic, response\n\n\napp = Sanic(\"Example\")\n\n\n@app.route(\"/\")\ndef handle_request(request):\n    return response.json(\n        {\"message\": \"Hello world!\"},\n        headers={\"X-Served-By\": \"sanic\"},\n        status=200,\n    )\n\n\n@app.route(\"/unauthorized\")\ndef handle_unauthorized_request(request):\n    return response.json(\n        {\"message\": \"You are not authorized\"},\n        headers={\"X-Served-By\": \"sanic\"},\n        status=404,\n    )\n\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", port=8000, debug=True)\n"
  },
  {
    "path": "examples/override_logging.py",
    "content": "import logging\n\nfrom sanic import Sanic, text\n\n\nlogging_format = \"[%(asctime)s] %(process)d-%(levelname)s \"\nlogging_format += \"%(module)s::%(funcName)s():l%(lineno)d: \"\nlogging_format += \"%(message)s\"\n\nlogging.basicConfig(format=logging_format, level=logging.DEBUG)\nlog = logging.getLogger()\n\n# Set logger to override default basicConfig\napp = Sanic(\"app\")\n\n\n@app.route(\"/\")\ndef test(request):\n    log.info(\"received request; responding with 'hey'\")\n    return text(\"hey\")\n\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", port=8000)\n"
  },
  {
    "path": "examples/pytest_xdist.py",
    "content": "\"\"\"pytest-xdist example for sanic server\n\nInstall testing tools:\n\n    $ pip install pytest pytest-xdist\n\nRun with xdist params:\n\n    $ pytest examples/pytest_xdist.py -n 8  # 8 workers\n\"\"\"\n\nimport re\n\nimport pytest\n\nfrom sanic_testing import SanicTestClient\nfrom sanic_testing.testing import PORT as PORT_BASE\n\nfrom sanic import Sanic\nfrom sanic.response import text\n\n\n@pytest.fixture(scope=\"session\")\ndef test_port(worker_id):\n    m = re.search(r\"[0-9]+\", worker_id)\n    if m:\n        num_id = m.group(0)\n    else:\n        num_id = 0\n    port = PORT_BASE + int(num_id)\n    return port\n\n\n@pytest.fixture(scope=\"session\")\ndef app():\n    app = Sanic(\"Example\")\n\n    @app.route(\"/\")\n    async def index(request):\n        return text(\"OK\")\n\n    return app\n\n\n@pytest.fixture(scope=\"session\")\ndef client(app, test_port):\n    return SanicTestClient(app, test_port)\n\n\n@pytest.mark.parametrize(\"run_id\", range(100))\ndef test_index(client, run_id):\n    request, response = client._sanic_endpoint_test(\"get\", \"/\")\n    assert response.status == 200\n    assert response.text == \"OK\"\n"
  },
  {
    "path": "examples/raygun_example.py",
    "content": "from os import getenv\n\nfrom raygun4py.raygunprovider import RaygunSender\n\nfrom sanic import Sanic\nfrom sanic.exceptions import SanicException\nfrom sanic.handlers import ErrorHandler\n\n\nclass RaygunExceptionReporter(ErrorHandler):\n    def __init__(self, raygun_api_key=None):\n        super().__init__()\n        if raygun_api_key is None:\n            raygun_api_key = getenv(\"RAYGUN_API_KEY\")\n\n        self.sender = RaygunSender(raygun_api_key)\n\n    def default(self, request, exception):\n        self.sender.send_exception(exception=exception)\n        return super().default(request, exception)\n\n\nraygun_error_reporter = RaygunExceptionReporter()\napp = Sanic(\"Example\", error_handler=raygun_error_reporter)\n\n\n@app.route(\"/raise\")\nasync def test(request):\n    raise SanicException(\"You Broke It!\")\n\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", port=getenv(\"PORT\", 8080))\n"
  },
  {
    "path": "examples/redirect_example.py",
    "content": "from sanic import Sanic, response\n\n\napp = Sanic(\"Example\")\n\n\n@app.route(\"/\")\ndef handle_request(request):\n    return response.redirect(\"/redirect\")\n\n\n@app.route(\"/redirect\")\nasync def test(request):\n    return response.json({\"Redirected\": True})\n\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", port=8000)\n"
  },
  {
    "path": "examples/request_stream/client.py",
    "content": "import requests\n\n\n# Warning: This is a heavy process.\n\ndata = \"\"\nfor i in range(1, 250000):\n    data += str(i)\n\nr = requests.post(\"http://0.0.0.0:8000/stream\", data=data)\nprint(r.text)\n"
  },
  {
    "path": "examples/request_stream/server.py",
    "content": "from sanic import Sanic\nfrom sanic.blueprints import Blueprint\nfrom sanic.response import stream, text\nfrom sanic.views import HTTPMethodView\nfrom sanic.views import stream as stream_decorator\n\n\nbp = Blueprint(\"bp_example\")\napp = Sanic(\"Example\")\n\n\nclass SimpleView(HTTPMethodView):\n    @stream_decorator\n    async def post(self, request):\n        result = \"\"\n        while True:\n            body = await request.stream.get()\n            if body is None:\n                break\n            result += body.decode(\"utf-8\")\n        return text(result)\n\n\n@app.post(\"/stream\", stream=True)\nasync def handler(request):\n    async def streaming(response):\n        while True:\n            body = await request.stream.get()\n            if body is None:\n                break\n            body = body.decode(\"utf-8\").replace(\"1\", \"A\")\n            await response.write(body)\n\n    return stream(streaming)\n\n\n@bp.put(\"/bp_stream\", stream=True)\nasync def bp_handler(request):\n    result = \"\"\n    while True:\n        body = await request.stream.get()\n        if body is None:\n            break\n        result += body.decode(\"utf-8\").replace(\"1\", \"A\")\n    return text(result)\n\n\nasync def post_handler(request):\n    result = \"\"\n    while True:\n        body = await request.stream.get()\n        if body is None:\n            break\n        result += body.decode(\"utf-8\")\n    return text(result)\n\n\napp.blueprint(bp)\napp.add_route(SimpleView.as_view(), \"/method_view\")\n\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", port=8000)\n"
  },
  {
    "path": "examples/request_timeout.py",
    "content": "import asyncio\n\nfrom sanic import Sanic, response\nfrom sanic.config import Config\nfrom sanic.exceptions import RequestTimeout\n\n\nConfig.REQUEST_TIMEOUT = 1\napp = Sanic(\"Example\")\n\n\n@app.route(\"/\")\nasync def test(request):\n    await asyncio.sleep(3)\n    return response.text(\"Hello, world!\")\n\n\n@app.exception(RequestTimeout)\ndef timeout(request, exception):\n    return response.text(\"RequestTimeout from error_handler.\", 408)\n\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", port=8000)\n"
  },
  {
    "path": "examples/rollbar_example.py",
    "content": "from os import getenv\n\nimport rollbar\n\nfrom sanic import Sanic\nfrom sanic.exceptions import SanicException\nfrom sanic.handlers import ErrorHandler\n\n\nrollbar.init(getenv(\"ROLLBAR_API_KEY\"))\n\n\nclass RollbarExceptionHandler(ErrorHandler):\n    def default(self, request, exception):\n        rollbar.report_message(str(exception))\n        return super().default(request, exception)\n\n\napp = Sanic(\"Example\", error_handler=RollbarExceptionHandler())\n\n\n@app.route(\"/raise\")\ndef create_error(request):\n    raise SanicException(\"I was here and I don't like where I am\")\n\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", port=getenv(\"PORT\", 8080))\n"
  },
  {
    "path": "examples/run_asgi.py",
    "content": "\"\"\"\n1. Create a simple Sanic app\n0. Run with an ASGI server:\n    $ uvicorn run_asgi:app\n    or\n    $ hypercorn run_asgi:app\n\"\"\"\n\nfrom pathlib import Path\n\nfrom sanic import Sanic, response\n\n\napp = Sanic(\"Example\")\n\n\n@app.route(\"/text\")\ndef handler_text(request):\n    return response.text(\"Hello\")\n\n\n@app.route(\"/json\")\ndef handler_json(request):\n    return response.json({\"foo\": \"bar\"})\n\n\n@app.websocket(\"/ws\")\nasync def handler_ws(request, ws):\n    name = \"<someone>\"\n    while True:\n        data = f\"Hello {name}\"\n        await ws.send(data)\n        name = await ws.recv()\n\n        if not name:\n            break\n\n\n@app.route(\"/file\")\nasync def handler_file(request):\n    return await response.file(Path(\"../\") / \"setup.py\")\n\n\n@app.route(\"/file_stream\")\nasync def handler_file_stream(request):\n    return await response.file_stream(\n        Path(\"../\") / \"setup.py\", chunk_size=1024\n    )\n\n\n@app.post(\"/stream\", stream=True)\nasync def handler_stream(request):\n    while True:\n        body = await request.stream.read()\n        if body is None:\n            break\n        body = body.decode(\"utf-8\").replace(\"1\", \"A\")\n        await response.write(body)\n    return response.stream(body)\n\n\n@app.before_server_start\nasync def listener_before_server_start(*args, **kwargs):\n    print(\"before_server_start\")\n\n\n@app.after_server_start\nasync def listener_after_server_start(*args, **kwargs):\n    print(\"after_server_start\")\n\n\n@app.before_server_stop\nasync def listener_before_server_stop(*args, **kwargs):\n    print(\"before_server_stop\")\n\n\n@app.after_server_stop\nasync def listener_after_server_stop(*args, **kwargs):\n    print(\"after_server_stop\")\n\n\n@app.on_request\nasync def print_on_request(request):\n    print(\"print_on_request\")\n\n\n@app.on_response\nasync def print_on_response(request, response):\n    print(\"print_on_response\")\n"
  },
  {
    "path": "examples/run_async.py",
    "content": "import asyncio\n\nimport uvloop\n\nfrom sanic import Sanic, response\n\n\napp = Sanic(\"Example\")\n\n\n@app.route(\"/\")\nasync def test(request):\n    return response.json({\"answer\": \"42\"})\n\n\nasync def main():\n    server = await app.create_server(\n        port=8000, host=\"0.0.0.0\", return_asyncio_server=True\n    )\n\n    if server is None:\n        return\n\n    await server.startup()\n    await server.serve_forever()\n\n\nif __name__ == \"__main__\":\n    asyncio.set_event_loop(uvloop.new_event_loop())\n    asyncio.run(main())\n"
  },
  {
    "path": "examples/run_async_advanced.py",
    "content": "import asyncio\n\nfrom signal import SIGINT, signal\n\nimport uvloop\n\nfrom sanic import Sanic, response\nfrom sanic.server import AsyncioServer\n\n\napp = Sanic(\"Example\")\n\n\n@app.before_server_start\nasync def before_server_start(app, loop):\n    print(\"Async Server starting\")\n\n\n@app.after_server_start\nasync def after_server_start(app, loop):\n    print(\"Async Server started\")\n\n\n@app.before_server_stop\nasync def before_server_stop(app, loop):\n    print(\"Async Server stopping\")\n\n\n@app.after_server_stop\nasync def after_server_stop(app, loop):\n    print(\"Async Server stopped\")\n\n\n@app.route(\"/\")\nasync def test(request):\n    return response.json({\"answer\": \"42\"})\n\n\nif __name__ == \"__main__\":\n    asyncio.set_event_loop(uvloop.new_event_loop())\n    serv_coro = app.create_server(\n        host=\"0.0.0.0\", port=8000, return_asyncio_server=True\n    )\n    loop = asyncio.get_event_loop()\n    serv_task = asyncio.ensure_future(serv_coro, loop=loop)\n    signal(SIGINT, lambda s, f: loop.stop())\n    server: AsyncioServer = loop.run_until_complete(serv_task)\n    loop.run_until_complete(server.startup())\n\n    # When using app.run(), this actually triggers before the serv_coro.\n    # But, in this example, we are using the convenience method, even if it is\n    # out of order.\n    loop.run_until_complete(server.before_start())\n    loop.run_until_complete(server.after_start())\n    try:\n        loop.run_forever()\n    except KeyboardInterrupt:\n        loop.stop()\n    finally:\n        loop.run_until_complete(server.before_stop())\n\n        # Wait for server to close\n        close_task = server.close()\n        loop.run_until_complete(close_task)\n\n        # Complete all tasks on the loop\n        for connection in server.connections:\n            connection.close_if_idle()\n        loop.run_until_complete(server.after_stop())\n"
  },
  {
    "path": "examples/sentry_example.py",
    "content": "from os import getenv\n\nfrom sentry_sdk import init as sentry_init\nfrom sentry_sdk.integrations.sanic import SanicIntegration\n\nfrom sanic import Sanic\nfrom sanic.response import json\n\n\nsentry_init(\n    dsn=getenv(\"SENTRY_DSN\"),\n    integrations=[SanicIntegration()],\n)\n\napp = Sanic(\"Example\")\n\n\n# noinspection PyUnusedLocal\n@app.route(\"/working\")\nasync def working_path(request):\n    return json({\"response\": \"Working API Response\"})\n\n\n# noinspection PyUnusedLocal\n@app.route(\"/raise-error\")\nasync def raise_error(request):\n    raise Exception(\"Testing Sentry Integration\")\n\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", port=getenv(\"PORT\", 8080))\n"
  },
  {
    "path": "examples/simple_async_view.py",
    "content": "from sanic import Sanic\nfrom sanic.response import text\nfrom sanic.views import HTTPMethodView\n\n\napp = Sanic(\"some_name\")\n\n\nclass SimpleView(HTTPMethodView):\n    def get(self, request):\n        return text(\"I am get method\")\n\n    def post(self, request):\n        return text(\"I am post method\")\n\n    def put(self, request):\n        return text(\"I am put method\")\n\n    def patch(self, request):\n        return text(\"I am patch method\")\n\n    def delete(self, request):\n        return text(\"I am delete method\")\n\n\nclass SimpleAsyncView(HTTPMethodView):\n    async def get(self, request):\n        return text(\"I am async get method\")\n\n    async def post(self, request):\n        return text(\"I am async post method\")\n\n    async def put(self, request):\n        return text(\"I am async put method\")\n\n\napp.add_route(SimpleView.as_view(), \"/\")\napp.add_route(SimpleAsyncView.as_view(), \"/async\")\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", port=8000, debug=True)\n"
  },
  {
    "path": "examples/static/robots.txt",
    "content": "User-agent: *\nDisallow: /\n"
  },
  {
    "path": "examples/static_assets.py",
    "content": "from sanic import Sanic\n\n\napp = Sanic(\"Example\")\n\napp.static(\"/\", \"./static\")\n"
  },
  {
    "path": "examples/teapot.py",
    "content": "from sanic import Sanic\nfrom sanic import response as res\n\n\napp = Sanic(\"Example\")\n\n\n@app.route(\"/\")\nasync def test(req):\n    return res.text(\"I'm a teapot\", status=418)\n\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", port=8000)\n"
  },
  {
    "path": "examples/try_everything.py",
    "content": "import os\n\nfrom sanic import Sanic, response\nfrom sanic.exceptions import ServerError\nfrom sanic.log import logger as log\n\n\napp = Sanic(\"Example\")\n\n\n@app.route(\"/\")\nasync def test_async(request):\n    return response.json({\"test\": True})\n\n\n@app.route(\"/sync\", methods=[\"GET\", \"POST\"])\ndef test_sync(request):\n    return response.json({\"test\": True})\n\n\n@app.route(\"/dynamic/<name>/<i:int>\")\ndef test_params(request, name, i):\n    return response.text(\"yeehaww {} {}\".format(name, i))\n\n\n@app.route(\"/exception\")\ndef exception(request):\n    raise ServerError(\"It's dead jim\")\n\n\n@app.route(\"/await\")\nasync def test_await(request):\n    import asyncio\n\n    await asyncio.sleep(5)\n    return response.text(\"I'm feeling sleepy\")\n\n\n@app.route(\"/file\")\nasync def test_file(request):\n    return await response.file(os.path.abspath(\"setup.py\"))\n\n\n@app.route(\"/file_stream\")\nasync def test_file_stream(request):\n    return await response.file_stream(\n        os.path.abspath(\"setup.py\"), chunk_size=1024\n    )\n\n\n# ----------------------------------------------- #\n# Exceptions\n# ----------------------------------------------- #\n\n\n@app.exception(ServerError)\nasync def test(request, exception):\n    return response.json(\n        {\"exception\": str(exception), \"status\": exception.status_code},\n        status=exception.status_code,\n    )\n\n\n# ----------------------------------------------- #\n# Read from request\n# ----------------------------------------------- #\n\n\n@app.route(\"/json\")\ndef post_json(request):\n    return response.json({\"received\": True, \"message\": request.json})\n\n\n@app.route(\"/form\")\ndef post_form_json(request):\n    return response.json(\n        {\n            \"received\": True,\n            \"form_data\": request.form,\n            \"test\": request.form.get(\"test\"),\n        }\n    )\n\n\n@app.route(\"/query_string\")\ndef query_string(request):\n    return response.json(\n        {\n            \"parsed\": True,\n            \"args\": request.args,\n            \"url\": request.url,\n            \"query_string\": request.query_string,\n        }\n    )\n\n\n# ----------------------------------------------- #\n# Run Server\n# ----------------------------------------------- #\n\n\n@app.before_server_start\ndef before_start(app, loop):\n    log.info(\"SERVER STARTING\")\n\n\n@app.after_server_start\ndef after_start(app, loop):\n    log.info(\"OH OH OH OH OHHHHHHHH\")\n\n\n@app.before_server_stop\ndef before_stop(app, loop):\n    log.info(\"SERVER STOPPING\")\n\n\n@app.after_server_stop\ndef after_stop(app, loop):\n    log.info(\"TRIED EVERYTHING\")\n\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", port=8000, debug=True)\n"
  },
  {
    "path": "examples/unix_socket.py",
    "content": "from sanic import Sanic, response\n\n\napp = Sanic(\"Example\")\n\n\n@app.route(\"/test\")\nasync def test(request):\n    return response.text(\"OK\")\n\n\nif __name__ == \"__main__\":\n    app.run(unix=\"./uds_socket\")\n"
  },
  {
    "path": "examples/url_for_example.py",
    "content": "from sanic import Sanic, response\n\n\napp = Sanic(\"Example\")\n\n\n@app.route(\"/\")\nasync def index(request):\n    # generate a URL for the endpoint `post_handler`\n    url = app.url_for(\"post_handler\", post_id=5)\n    # the URL is `/posts/5`, redirect to it\n    return response.redirect(url)\n\n\n@app.route(\"/posts/<post_id>\")\nasync def post_handler(request, post_id):\n    return response.text(\"Post - {}\".format(post_id))\n\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", port=8000, debug=True)\n"
  },
  {
    "path": "examples/versioned_blueprint_group.py",
    "content": "from sanic import Sanic\nfrom sanic.blueprints import Blueprint\nfrom sanic.response import json\n\n\napp = Sanic(name=\"blue-print-group-version-example\")\n\nbp1 = Blueprint(name=\"ultron\", url_prefix=\"/ultron\")\nbp2 = Blueprint(name=\"vision\", url_prefix=\"/vision\", strict_slashes=None)\n\nbpg = Blueprint.group(\n    bp1, bp2, url_prefix=\"/sentient/robot\", version=1, strict_slashes=True\n)\n\n\n@bp1.get(\"/name\")\nasync def bp1_name(request):\n    \"\"\"This will expose an Endpoint GET /v1/sentient/robot/ultron/name\"\"\"\n    return json({\"name\": \"Ultron\"})\n\n\n@bp2.get(\"/name\")\nasync def bp2_name(request):\n    \"\"\"This will expose an Endpoint GET /v1/sentient/robot/vision/name\"\"\"\n    return json({\"name\": \"vision\"})\n\n\n@bp2.get(\"/name\", version=2)\nasync def bp2_revised_name(request):\n    \"\"\"This will expose an Endpoint GET /v2/sentient/robot/vision/name\"\"\"\n    return json({\"name\": \"new vision\"})\n\n\napp.blueprint(bpg)\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", port=8000)\n"
  },
  {
    "path": "examples/vhosts.py",
    "content": "from sanic import Sanic, response\nfrom sanic.blueprints import Blueprint\n\n\n# Usage\n# curl -H \"Host: example.com\" localhost:8000\n# curl -H \"Host: sub.example.com\" localhost:8000\n# curl -H \"Host: bp.example.com\" localhost:8000/question\n# curl -H \"Host: bp.example.com\" localhost:8000/answer\n\napp = Sanic(\"Example\")\nbp = Blueprint(\"bp\", host=\"bp.example.com\")\n\n\n@app.route(\n    \"/\", host=[\"example.com\", \"somethingelse.com\", \"therestofyourdomains.com\"]\n)\nasync def hello_0(request):\n    return response.text(\"Some defaults\")\n\n\n@app.route(\"/\", host=\"sub.example.com\")\nasync def hello_1(request):\n    return response.text(\"42\")\n\n\n@bp.route(\"/question\")\nasync def hello_2(request):\n    return response.text(\"What is the meaning of life?\")\n\n\n@bp.route(\"/answer\")\nasync def hello_3(request):\n    return response.text(\"42\")\n\n\n@app.get(\"/name\")\ndef name(request):\n    return response.text(request.app.url_for(\"name\", _external=True))\n\n\napp.blueprint(bp)\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", port=8000)\n"
  },
  {
    "path": "examples/websocket.html",
    "content": "<!DOCTYPE html>\n<html>\n    <head>\n        <title>WebSocket demo</title>\n    </head>\n    <body>\n        <script>\n            var ws = new WebSocket('ws://' + document.domain + ':' + location.port + '/feed'),\n                messages = document.createElement('ul');\n            ws.onmessage = function (event) {\n                var messages = document.getElementsByTagName('ul')[0],\n                    message = document.createElement('li'),\n                    content = document.createTextNode('Received: ' + event.data);\n                message.appendChild(content);\n                messages.appendChild(message);\n            };\n            document.body.appendChild(messages);\n            window.setInterval(function() {\n                data = 'bye!'\n                ws.send(data);\n                var messages = document.getElementsByTagName('ul')[0],\n                    message = document.createElement('li'),\n                    content = document.createTextNode('Sent: ' + data);\n                message.appendChild(content);\n                messages.appendChild(message);\n            }, 1000);\n        </script>\n    </body>\n</html>\n"
  },
  {
    "path": "examples/websocket.py",
    "content": "from sanic import Sanic\nfrom sanic.response import redirect\n\n\napp = Sanic(\"Example\")\n\n\napp.static(\"index.html\", \"websocket.html\")\n\n\n@app.route(\"/\")\ndef index(request):\n    return redirect(\"index.html\")\n\n\n@app.websocket(\"/feed\")\nasync def feed(request, ws):\n    while True:\n        data = \"hello!\"\n        print(\"Sending: \" + data)\n        await ws.send(data)\n        data = await ws.recv()\n        print(\"Received: \" + data)\n\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", port=8000, debug=True)\n"
  },
  {
    "path": "guide/Procfile",
    "content": "web: sanic --port=${PORT} --host=0.0.0.0 --workers=1 server:app"
  },
  {
    "path": "guide/config/en/general.yaml",
    "content": "current_version: \"25.12\"\n"
  },
  {
    "path": "guide/config/en/navbar.yaml",
    "content": "root:\n    - label: Home\n      path: index.html\n    - label: Community\n      items:\n          - label: Forums\n            href: https://community.sanicframework.org\n          - label: Discord\n            href: https://discord.gg/FARQzAEMAA\n          - label: Twitter\n            href: https://twitter.com/sanicframework\n    - label: Help\n      path: ./help.html\n    - label: GitHub\n      href: https://github.com/sanic-org/sanic\n"
  },
  {
    "path": "guide/config/en/sidebar.yaml",
    "content": "root:\n    - label: User Guide\n      items:\n          - label: General\n            items:\n                - label: Introduction\n                  path: guide/introduction.html\n                - label: Getting Started\n                  path: guide/getting-started.html\n          - label: Basics\n            items:\n                - label: Sanic Application\n                  path: guide/basics/app.html\n                - label: Handlers\n                  path: guide/basics/handlers.html\n                - label: Request\n                  path: guide/basics/request.html\n                - label: Response\n                  path: guide/basics/response.html\n                - label: Routing\n                  path: guide/basics/routing.html\n                - label: Listeners\n                  path: guide/basics/listeners.html\n                - label: Middleware\n                  path: guide/basics/middleware.html\n                - label: Headers\n                  path: guide/basics/headers.html\n                - label: Cookies\n                  path: guide/basics/cookies.html\n                - label: Background Tasks\n                  path: guide/basics/tasks.html\n          - label: Advanced\n            items:\n                - label: Class Based Views\n                  path: guide/advanced/class-based-views.html\n                - label: Proxy Configuration\n                  path: guide/advanced/proxy-headers.html\n                - label: Streaming\n                  path: guide/advanced/streaming.html\n                - label: Websockets\n                  path: guide/advanced/websockets.html\n                - label: Versioning\n                  path: guide/advanced/versioning.html\n                - label: Signals\n                  path: guide/advanced/signals.html\n                - label: Custom CLI Commands\n                  path: guide/advanced/commands.html\n          - label: Best Practices\n            items:\n                - label: Blueprints\n                  path: guide/best-practices/blueprints.html\n                - label: Exceptions\n                  path: guide/best-practices/exceptions.html\n                - label: Decorators\n                  path: guide/best-practices/decorators.html\n                - label: Logging\n                  path: guide/best-practices/logging.html\n                - label: Testing\n                  path: guide/best-practices/testing.html\n          - label: Running Sanic\n            items:\n                - label: Configuration\n                  path: guide/running/configuration.html\n                - label: Development\n                  path: guide/running/development.html\n                - label: Running Sanic\n                  path: guide/running/running.html\n                - label: Worker Manager\n                  path: guide/running/manager.html\n                - label: Dynamic Applications\n                  path: guide/running/app-loader.html\n                - label: Inspector\n                  path: guide/running/inspector.html\n          - label: Deployment\n            items:\n                - label: Caddy\n                  path: guide/deployment/caddy.html\n                - label: Nginx\n                  path: guide/deployment/nginx.html\n                - label: Docker\n                  path: guide/deployment/docker.html\n          - label: How to ...\n            items:\n                - label: Table of Contents\n                  path: guide/how-to/table-of-contents.html\n                - label: Application Mounting\n                  path: guide/how-to/mounting.html\n                - label: Authentication\n                  path: guide/how-to/authentication.html\n                - label: Autodiscovery\n                  path: guide/how-to/autodiscovery.html\n                - label: CORS\n                  path: guide/how-to/cors.html\n                - label: ORM\n                  path: guide/how-to/orm.html\n                - label: Static Redirects\n                  path: guide/how-to/static-redirects.html\n                - label: TLS/SSL/HTTPS\n                  path: guide/how-to/tls.html\n    - label: Plugins\n      items:\n          - label: Sanic Extensions\n            items:\n                - label: Getting Started\n                  path: plugins/sanic-ext/getting-started.html\n                - label: HTTP - Methods\n                  path: plugins/sanic-ext/http/methods.html\n                - label: HTTP - CORS Protection\n                  path: plugins/sanic-ext/http/cors.html\n                - label: OpenAPI - Basics\n                  path: plugins/sanic-ext/openapi/basics.html\n                - label: OpenAPI - UI\n                  path: plugins/sanic-ext/openapi/ui.html\n                - label: OpenAPI - Decorators\n                  path: plugins/sanic-ext/openapi/decorators.html\n                # - label: OpenAPI - Advanced\n                #   path: plugins/sanic-ext/openapi/advanced.html\n                - label: OpenAPI - Auto Documentation\n                  path: plugins/sanic-ext/openapi/autodoc.html\n                - label: OpenAPI - Security\n                  path: plugins/sanic-ext/openapi/security.html\n                - label: Convenience\n                  path: plugins/sanic-ext/convenience.html\n                - label: Templating - Jinja\n                  path: plugins/sanic-ext/templating/jinja.html\n                - label: Templating - html5tagger\n                  path: plugins/sanic-ext/templating/html5tagger.html\n                - label: Dependency Injection\n                  path: plugins/sanic-ext/injection.html\n                - label: Validation\n                  path: plugins/sanic-ext/validation.html\n                - label: Health Monitor\n                  path: plugins/sanic-ext/health-monitor.html\n                - label: Background Logger\n                  path: plugins/sanic-ext/logger.html\n                - label: Configuration\n                  path: plugins/sanic-ext/configuration.html\n                - label: Custom Extensions\n                  path: plugins/sanic-ext/custom.html\n          - label: Sanic Testing\n            items:\n                - label: Getting Started\n                  path: plugins/sanic-testing/getting-started.html\n                - label: Test Clients\n                  path: plugins/sanic-testing/clients.html\n    - label: Release Notes\n      items:\n          - label: \"2025\"\n            items:\n                - label: Sanic 25.12\n                  path: release-notes/2025/v25.12.html\n                - label: Sanic 25.3\n                  path: release-notes/2025/v25.3.html\n          - label: \"2024\"\n            items:\n                - label: Sanic 24.12\n                  path: release-notes/2024/v24.12.html\n                - label: Sanic 24.6\n                  path: release-notes/2024/v24.6.html\n          - label: \"2023\"\n            items:\n                - label: Sanic 23.12\n                  path: release-notes/2023/v23.12.html\n                - label: Sanic 23.9\n                  path: release-notes/2023/v23.9.html\n                - label: Sanic 23.6\n                  path: release-notes/2023/v23.6.html\n                - label: Sanic 23.3\n                  path: release-notes/2023/v23.3.html\n          - label: \"2022\"\n            items:\n                - label: Sanic 22.12\n                  path: release-notes/2022/v22.12.html\n                - label: Sanic 22.9\n                  path: release-notes/2022/v22.9.html\n                - label: Sanic 22.6\n                  path: release-notes/2022/v22.6.html\n                - label: Sanic 22.3\n                  path: release-notes/2022/v22.3.html\n          - label: \"2021\"\n            items:\n                - label: Sanic 21.12\n                  path: release-notes/2021/v21.12.html\n                - label: Sanic 21.9\n                  path: release-notes/2021/v21.9.html\n                - label: Sanic 21.6\n                  path: release-notes/2021/v21.6.html\n                - label: Sanic 21.3\n                  path: release-notes/2021/v21.3.html\n          - label: Changelog\n            path: release-notes/changelog.html\n    - label: Organization\n      items:\n          - label: Contributing\n            path: organization/contributing.html\n          - label: Code of Conduct\n            path: organization/code-of-conduct.html\n          - label: S.C.O.P.E. (Governance)\n            path: organization/scope.html\n          - label: Policies\n            path: organization/policies.html\n    - label: API Reference\n      items:\n          - label: Application\n            items:\n                - label: sanic.app\n                  path: /api/sanic.app.html\n                - label: sanic.config\n                  path: /api/sanic.config.html\n                - label: sanic.application\n                  path: /api/sanic.application.html\n          - label: Blueprint\n            items:\n                - label: sanic.blueprints\n                  path: /api/sanic.blueprints.html\n                - label: sanic.blueprint_group\n                  path: /api/sanic.blueprint_group.html\n          - label: Constant\n            items:\n                - label: sanic.constants\n                  path: /api/sanic.constants.html\n          - label: Core\n            items:\n                - label: sanic.cookies\n                  path: /api/sanic.cookies.html\n                - label: sanic.handlers\n                  path: /api/sanic.handlers.html\n                - label: sanic.headers\n                  path: /api/sanic.headers.html\n                - label: sanic.middleware\n                  path: /api/sanic.middleware.html\n                - label: sanic.mixins\n                  path: /api/sanic.mixins.html\n                - label: sanic.request\n                  path: /api/sanic.request.html\n                - label: sanic.response\n                  path: /api/sanic.response.html\n                - label: sanic.views\n                  path: /api/sanic.views.html\n          - label: Display\n            items:\n                - label: sanic.pages\n                  path: /api/sanic.pages.html\n          - label: Exception\n            items:\n                - label: sanic.errorpages\n                  path: /api/sanic.errorpages.html\n                - label: sanic.exceptions\n                  path: /api/sanic.exceptions.html\n          - label: Model\n            items:\n                - label: sanic.models\n                  path: /api/sanic.models.html\n          - label: Routing\n            items:\n                - label: sanic.router\n                  path: /api/sanic.router.html\n                - label: sanic.signals\n                  path: /api/sanic.signals.html\n          - label: Server\n            items:\n                - label: sanic.http\n                  path: /api/sanic.http.html\n                - label: sanic.server\n                  path: /api/sanic.server.html\n                - label: sanic.worker\n                  path: /api/sanic.worker.html\n          - label: Utility\n            items:\n                - label: sanic.compat\n                  path: /api/sanic.compat.html\n                - label: sanic.helpers\n                  path: /api/sanic.helpers.html\n                - label: sanic.logging\n                  path: /api/sanic.logging.html\n                - label: sanic.utils\n                  path: /api/sanic.utils.html\n"
  },
  {
    "path": "guide/content/en/built-with-sanic.md",
    "content": "---\ntitle: Full Speed Ahead - How We Built This Site with Sanic\nlayout: main\n---\n\n.. attrs::\n    :class: title\n\n    Full Speed Ahead:\n\n.. attrs::\n    :class: subtitle\n\n    How We Built This Site with Sanic\n\nWelcome to our little corner of the Internet where we proudly say, \"Yes, we built this with Sanic!\" This isn't just a website; it's our playground, our test lab, our battlefield, and, well, our home.\n\n![](/assets/images/built-with-sanic.png)\n\n### The Story: \"We Drink Our Own Champagne\"\n\nWe believe in Sanic so much that we decided to put it to the ultimate test—running our own website. It's like a chef eating at their own restaurant, only with less risk of food poisoning.\n\nWhy? Because building a website or web application is hard. There are countless moving parts, a plethora of challenges, and the ever-present need for speed and reliability. We want to show you just one of the many ways you *could* do it.\n\nIn this high-stakes digital kitchen, Sanic is our secret ingredient. By deploying our own website on Sanic, we're not just showcasing its capabilities; we're stress-testing them in the real world. This is our chance to walk the walk, proving that Sanic isn't just good on paper—it's a robust, high-performance framework that can handle everything from the smallest blog to the busiest e-commerce site. \n\nSo, here we are, sipping our own champagne, confident in the knowledge that if Sanic can run our site, it can power yours too. Cheers to coding at the speed of thought! 🥂\n\n### The Setup: Digital Ocean, Ahoy!\n\nWe launched our site on Digital Ocean's App Platform because we love high-performance cloud sailing. Think of it as having a Ferrari in the cloud—fast, sleek, but way easier to handle.\n\nWhy go for simplicity? With a lean team and no DevOps gurus, we needed a no-fuss, straightforward solution. Digital Ocean gives us that smooth sailing platform-as-a-service (PaaS) experience. It’s perfect for our needs: easy setup, automatic deployments, and the kind of reliability that lets you sleep soundly.\n\nOur choice reflects our ethos: focus on your strengths and let the platform do the heavy lifting. For us, it means creating amazing web experiences with Sanic, supported by a deployment solution that's simple yet powerful. ⛵\n\n### The Code: GitHub's Where It's At\n\nAll our code is out in the open, basking in the glory of public scrutiny on GitHub. Why hide the magic? It's right there, in full view, at [our GitHub repository](https://github.com/sanic-org/sanic/tree/main/guide). Go ahead, take a peek, fork it, play with it, break it (and then kindly fix it).\n\nOpen-source isn't just a buzzword for us; it's our ethos. It's about building something bigger than ourselves, together. Our code is a testament to collaborative innovation, a playground for development, and a real-life example of Sanic in action.\n\nEvery line of code, every commit, reflects our journey with Sanic, showcasing how we leverage its speed and scalability. Your contributions, whether fixing a bug, suggesting a feature, or enhancing documentation, are what propel this project forward.\n\nSo, dive in, contribute your genius, and let's keep shaping the future of web development with Sanic. Together, we're not just coding – we're creating a community-driven powerhouse. 🚀\n\n### The Invitation: Write, Code, Break, Fix!\n\n- **Documentarians**: Love making complex stuff sound easy? Our docs are your canvas. Paint away in words! 🎨\n\n- **Code Ninjas**: Find bugs? Squash 'em. Got ideas? Code 'em. Make pull requests rain! 🥷\n\n- **Bug Hunters**: If you find bugs, don't just stare. Let us know. We love a good bug hunt. 🐛\n \n### The Bottom Line\n\nWe built this site with Sanic to show off what it can do. It's fast, it's fun, and it's what we use. So, if things load swiftly, pat us on the back. If they don't, well, uh... we blame cosmic rays?\n\nJoin us in making Sanic not just good, but \"I-can't-believe-it's-not-butter\" good!\n\nCheers,\nThe Sanic Team (who occasionally wear capes)\n"
  },
  {
    "path": "guide/content/en/emoji.py",
    "content": "EMOJI = {\n    \"1st_place_medal\": \"🥇\",\n    \"2nd_place_medal\": \"🥈\",\n    \"3rd_place_medal\": \"🥉\",\n    \"AB_button_(blood_type)\": \"🆎\",\n    \"ATM_sign\": \"🏧\",\n    \"A_button_(blood_type)\": \"🅰\",\n    \"Afghanistan\": \"🇦🇫\",\n    \"Albania\": \"🇦🇱\",\n    \"Algeria\": \"🇩🇿\",\n    \"American_Samoa\": \"🇦🇸\",\n    \"Andorra\": \"🇦🇩\",\n    \"Angola\": \"🇦🇴\",\n    \"Anguilla\": \"🇦🇮\",\n    \"Antarctica\": \"🇦🇶\",\n    \"Antigua_&_Barbuda\": \"🇦🇬\",\n    \"Aquarius\": \"♒\",\n    \"Argentina\": \"🇦🇷\",\n    \"Aries\": \"♈\",\n    \"Armenia\": \"🇦🇲\",\n    \"Aruba\": \"🇦🇼\",\n    \"Ascension_Island\": \"🇦🇨\",\n    \"Australia\": \"🇦🇺\",\n    \"Austria\": \"🇦🇹\",\n    \"Azerbaijan\": \"🇦🇿\",\n    \"BACK_arrow\": \"🔙\",\n    \"B_button_(blood_type)\": \"🅱\",\n    \"Bahamas\": \"🇧🇸\",\n    \"Bahrain\": \"🇧🇭\",\n    \"Bangladesh\": \"🇧🇩\",\n    \"Barbados\": \"🇧🇧\",\n    \"Belarus\": \"🇧🇾\",\n    \"Belgium\": \"🇧🇪\",\n    \"Belize\": \"🇧🇿\",\n    \"Benin\": \"🇧🇯\",\n    \"Bermuda\": \"🇧🇲\",\n    \"Bhutan\": \"🇧🇹\",\n    \"Bolivia\": \"🇧🇴\",\n    \"Bosnia_&_Herzegovina\": \"🇧🇦\",\n    \"Botswana\": \"🇧🇼\",\n    \"Bouvet_Island\": \"🇧🇻\",\n    \"Brazil\": \"🇧🇷\",\n    \"British_Indian_Ocean_Territory\": \"🇮🇴\",\n    \"British_Virgin_Islands\": \"🇻🇬\",\n    \"Brunei\": \"🇧🇳\",\n    \"Bulgaria\": \"🇧🇬\",\n    \"Burkina_Faso\": \"🇧🇫\",\n    \"Burundi\": \"🇧🇮\",\n    \"CL_button\": \"🆑\",\n    \"COOL_button\": \"🆒\",\n    \"Cambodia\": \"🇰🇭\",\n    \"Cameroon\": \"🇨🇲\",\n    \"Canada\": \"🇨🇦\",\n    \"Canary_Islands\": \"🇮🇨\",\n    \"Cancer\": \"♋\",\n    \"Cape_Verde\": \"🇨🇻\",\n    \"Capricorn\": \"♑\",\n    \"Caribbean_Netherlands\": \"🇧🇶\",\n    \"Cayman_Islands\": \"🇰🇾\",\n    \"Central_African_Republic\": \"🇨🇫\",\n    \"Ceuta_&_Melilla\": \"🇪🇦\",\n    \"Chad\": \"🇹🇩\",\n    \"Chile\": \"🇨🇱\",\n    \"China\": \"🇨🇳\",\n    \"Christmas_Island\": \"🇨🇽\",\n    \"Christmas_tree\": \"🎄\",\n    \"Clipperton_Island\": \"🇨🇵\",\n    \"Cocos_(Keeling)_Islands\": \"🇨🇨\",\n    \"Colombia\": \"🇨🇴\",\n    \"Comoros\": \"🇰🇲\",\n    \"Congo-Brazzaville\": \"🇨🇬\",\n    \"Congo-Kinshasa\": \"🇨🇩\",\n    \"Cook_Islands\": \"🇨🇰\",\n    \"Costa_Rica\": \"🇨🇷\",\n    \"Croatia\": \"🇭🇷\",\n    \"Cuba\": \"🇨🇺\",\n    \"Curaçao\": \"🇨🇼\",\n    \"Cyprus\": \"🇨🇾\",\n    \"Czechia\": \"🇨🇿\",\n    \"Côte_d’Ivoire\": \"🇨🇮\",\n    \"Denmark\": \"🇩🇰\",\n    \"Diego_Garcia\": \"🇩🇬\",\n    \"Djibouti\": \"🇩🇯\",\n    \"Dominica\": \"🇩🇲\",\n    \"Dominican_Republic\": \"🇩🇴\",\n    \"END_arrow\": \"🔚\",\n    \"Ecuador\": \"🇪🇨\",\n    \"Egypt\": \"🇪🇬\",\n    \"El_Salvador\": \"🇸🇻\",\n    \"England\": \"🏴󠁧󠁢󠁥󠁮󠁧󠁿\",\n    \"Equatorial_Guinea\": \"🇬🇶\",\n    \"Eritrea\": \"🇪🇷\",\n    \"Estonia\": \"🇪🇪\",\n    \"Eswatini\": \"🇸🇿\",\n    \"Ethiopia\": \"🇪🇹\",\n    \"European_Union\": \"🇪🇺\",\n    \"FREE_button\": \"🆓\",\n    \"Falkland_Islands\": \"🇫🇰\",\n    \"Faroe_Islands\": \"🇫🇴\",\n    \"Fiji\": \"🇫🇯\",\n    \"Finland\": \"🇫🇮\",\n    \"France\": \"🇫🇷\",\n    \"French_Guiana\": \"🇬🇫\",\n    \"French_Polynesia\": \"🇵🇫\",\n    \"French_Southern_Territories\": \"🇹🇫\",\n    \"Gabon\": \"🇬🇦\",\n    \"Gambia\": \"🇬🇲\",\n    \"Gemini\": \"♊\",\n    \"Georgia\": \"🇬🇪\",\n    \"Germany\": \"🇩🇪\",\n    \"Ghana\": \"🇬🇭\",\n    \"Gibraltar\": \"🇬🇮\",\n    \"Greece\": \"🇬🇷\",\n    \"Greenland\": \"🇬🇱\",\n    \"Grenada\": \"🇬🇩\",\n    \"Guadeloupe\": \"🇬🇵\",\n    \"Guam\": \"🇬🇺\",\n    \"Guatemala\": \"🇬🇹\",\n    \"Guernsey\": \"🇬🇬\",\n    \"Guinea\": \"🇬🇳\",\n    \"Guinea-Bissau\": \"🇬🇼\",\n    \"Guyana\": \"🇬🇾\",\n    \"Haiti\": \"🇭🇹\",\n    \"Heard_&_McDonald_Islands\": \"🇭🇲\",\n    \"Honduras\": \"🇭🇳\",\n    \"Hong_Kong_SAR_China\": \"🇭🇰\",\n    \"Hungary\": \"🇭🇺\",\n    \"ID_button\": \"🆔\",\n    \"Iceland\": \"🇮🇸\",\n    \"India\": \"🇮🇳\",\n    \"Indonesia\": \"🇮🇩\",\n    \"Iran\": \"🇮🇷\",\n    \"Iraq\": \"🇮🇶\",\n    \"Ireland\": \"🇮🇪\",\n    \"Isle_of_Man\": \"🇮🇲\",\n    \"Israel\": \"🇮🇱\",\n    \"Italy\": \"🇮🇹\",\n    \"Jamaica\": \"🇯🇲\",\n    \"Japan\": \"🇯🇵\",\n    \"Japanese_acceptable_button\": \"🉑\",\n    \"Japanese_application_button\": \"🈸\",\n    \"Japanese_bargain_button\": \"🉐\",\n    \"Japanese_castle\": \"🏯\",\n    \"Japanese_congratulations_button\": \"㊗\",\n    \"Japanese_discount_button\": \"🈹\",\n    \"Japanese_dolls\": \"🎎\",\n    \"Japanese_free_of_charge_button\": \"🈚\",\n    \"Japanese_here_button\": \"🈁\",\n    \"Japanese_monthly_amount_button\": \"🈷\",\n    \"Japanese_no_vacancy_button\": \"🈵\",\n    \"Japanese_not_free_of_charge_button\": \"🈶\",\n    \"Japanese_open_for_business_button\": \"🈺\",\n    \"Japanese_passing_grade_button\": \"🈴\",\n    \"Japanese_post_office\": \"🏣\",\n    \"Japanese_prohibited_button\": \"🈲\",\n    \"Japanese_reserved_button\": \"🈯\",\n    \"Japanese_secret_button\": \"㊙\",\n    \"Japanese_service_charge_button\": \"🈂\",\n    \"Japanese_symbol_for_beginner\": \"🔰\",\n    \"Japanese_vacancy_button\": \"🈳\",\n    \"Jersey\": \"🇯🇪\",\n    \"Jordan\": \"🇯🇴\",\n    \"Kazakhstan\": \"🇰🇿\",\n    \"Kenya\": \"🇰🇪\",\n    \"Kiribati\": \"🇰🇮\",\n    \"Kosovo\": \"🇽🇰\",\n    \"Kuwait\": \"🇰🇼\",\n    \"Kyrgyzstan\": \"🇰🇬\",\n    \"Laos\": \"🇱🇦\",\n    \"Latvia\": \"🇱🇻\",\n    \"Lebanon\": \"🇱🇧\",\n    \"Leo\": \"♌\",\n    \"Lesotho\": \"🇱🇸\",\n    \"Liberia\": \"🇱🇷\",\n    \"Libra\": \"♎\",\n    \"Libya\": \"🇱🇾\",\n    \"Liechtenstein\": \"🇱🇮\",\n    \"Lithuania\": \"🇱🇹\",\n    \"Luxembourg\": \"🇱🇺\",\n    \"Macao_SAR_China\": \"🇲🇴\",\n    \"Madagascar\": \"🇲🇬\",\n    \"Malawi\": \"🇲🇼\",\n    \"Malaysia\": \"🇲🇾\",\n    \"Maldives\": \"🇲🇻\",\n    \"Mali\": \"🇲🇱\",\n    \"Malta\": \"🇲🇹\",\n    \"Marshall_Islands\": \"🇲🇭\",\n    \"Martinique\": \"🇲🇶\",\n    \"Mauritania\": \"🇲🇷\",\n    \"Mauritius\": \"🇲🇺\",\n    \"Mayotte\": \"🇾🇹\",\n    \"Mexico\": \"🇲🇽\",\n    \"Micronesia\": \"🇫🇲\",\n    \"Moldova\": \"🇲🇩\",\n    \"Monaco\": \"🇲🇨\",\n    \"Mongolia\": \"🇲🇳\",\n    \"Montenegro\": \"🇲🇪\",\n    \"Montserrat\": \"🇲🇸\",\n    \"Morocco\": \"🇲🇦\",\n    \"Mozambique\": \"🇲🇿\",\n    \"Mrs._Claus\": \"🤶\",\n    \"Mrs._Claus_dark_skin_tone\": \"🤶🏿\",\n    \"Mrs._Claus_light_skin_tone\": \"🤶🏻\",\n    \"Mrs._Claus_medium-dark_skin_tone\": \"🤶🏾\",\n    \"Mrs._Claus_medium-light_skin_tone\": \"🤶🏼\",\n    \"Mrs._Claus_medium_skin_tone\": \"🤶🏽\",\n    \"Myanmar_(Burma)\": \"🇲🇲\",\n    \"NEW_button\": \"🆕\",\n    \"NG_button\": \"🆖\",\n    \"Namibia\": \"🇳🇦\",\n    \"Nauru\": \"🇳🇷\",\n    \"Nepal\": \"🇳🇵\",\n    \"Netherlands\": \"🇳🇱\",\n    \"New_Caledonia\": \"🇳🇨\",\n    \"New_Zealand\": \"🇳🇿\",\n    \"Nicaragua\": \"🇳🇮\",\n    \"Niger\": \"🇳🇪\",\n    \"Nigeria\": \"🇳🇬\",\n    \"Niue\": \"🇳🇺\",\n    \"Norfolk_Island\": \"🇳🇫\",\n    \"North_Korea\": \"🇰🇵\",\n    \"North_Macedonia\": \"🇲🇰\",\n    \"Northern_Mariana_Islands\": \"🇲🇵\",\n    \"Norway\": \"🇳🇴\",\n    \"OK_button\": \"🆗\",\n    \"OK_hand\": \"👌\",\n    \"OK_hand_dark_skin_tone\": \"👌🏿\",\n    \"OK_hand_light_skin_tone\": \"👌🏻\",\n    \"OK_hand_medium-dark_skin_tone\": \"👌🏾\",\n    \"OK_hand_medium-light_skin_tone\": \"👌🏼\",\n    \"OK_hand_medium_skin_tone\": \"👌🏽\",\n    \"ON!_arrow\": \"🔛\",\n    \"O_button_(blood_type)\": \"🅾\",\n    \"Oman\": \"🇴🇲\",\n    \"Ophiuchus\": \"⛎\",\n    \"P_button\": \"🅿\",\n    \"Pakistan\": \"🇵🇰\",\n    \"Palau\": \"🇵🇼\",\n    \"Palestinian_Territories\": \"🇵🇸\",\n    \"Panama\": \"🇵🇦\",\n    \"Papua_New_Guinea\": \"🇵🇬\",\n    \"Paraguay\": \"🇵🇾\",\n    \"Peru\": \"🇵🇪\",\n    \"Philippines\": \"🇵🇭\",\n    \"Pisces\": \"♓\",\n    \"Pitcairn_Islands\": \"🇵🇳\",\n    \"Poland\": \"🇵🇱\",\n    \"Portugal\": \"🇵🇹\",\n    \"Puerto_Rico\": \"🇵🇷\",\n    \"Qatar\": \"🇶🇦\",\n    \"Romania\": \"🇷🇴\",\n    \"Russia\": \"🇷🇺\",\n    \"Rwanda\": \"🇷🇼\",\n    \"Réunion\": \"🇷🇪\",\n    \"SOON_arrow\": \"🔜\",\n    \"SOS_button\": \"🆘\",\n    \"Sagittarius\": \"♐\",\n    \"Samoa\": \"🇼🇸\",\n    \"San_Marino\": \"🇸🇲\",\n    \"Santa_Claus\": \"🎅\",\n    \"Santa_Claus_dark_skin_tone\": \"🎅🏿\",\n    \"Santa_Claus_light_skin_tone\": \"🎅🏻\",\n    \"Santa_Claus_medium-dark_skin_tone\": \"🎅🏾\",\n    \"Santa_Claus_medium-light_skin_tone\": \"🎅🏼\",\n    \"Santa_Claus_medium_skin_tone\": \"🎅🏽\",\n    \"Saudi_Arabia\": \"🇸🇦\",\n    \"Scorpio\": \"♏\",\n    \"Scotland\": \"🏴󠁧󠁢󠁳󠁣󠁴󠁿\",\n    \"Senegal\": \"🇸🇳\",\n    \"Serbia\": \"🇷🇸\",\n    \"Seychelles\": \"🇸🇨\",\n    \"Sierra_Leone\": \"🇸🇱\",\n    \"Singapore\": \"🇸🇬\",\n    \"Sint_Maarten\": \"🇸🇽\",\n    \"Slovakia\": \"🇸🇰\",\n    \"Slovenia\": \"🇸🇮\",\n    \"Solomon_Islands\": \"🇸🇧\",\n    \"Somalia\": \"🇸🇴\",\n    \"South_Africa\": \"🇿🇦\",\n    \"South_Georgia_&_South_Sandwich_Islands\": \"🇬🇸\",\n    \"South_Korea\": \"🇰🇷\",\n    \"South_Sudan\": \"🇸🇸\",\n    \"Spain\": \"🇪🇸\",\n    \"Sri_Lanka\": \"🇱🇰\",\n    \"St._Barthélemy\": \"🇧🇱\",\n    \"St._Helena\": \"🇸🇭\",\n    \"St._Kitts_&_Nevis\": \"🇰🇳\",\n    \"St._Lucia\": \"🇱🇨\",\n    \"St._Martin\": \"🇲🇫\",\n    \"St._Pierre_&_Miquelon\": \"🇵🇲\",\n    \"St._Vincent_&_Grenadines\": \"🇻🇨\",\n    \"Statue_of_Liberty\": \"🗽\",\n    \"Sudan\": \"🇸🇩\",\n    \"Suriname\": \"🇸🇷\",\n    \"Svalbard_&_Jan_Mayen\": \"🇸🇯\",\n    \"Sweden\": \"🇸🇪\",\n    \"Switzerland\": \"🇨🇭\",\n    \"Syria\": \"🇸🇾\",\n    \"São_Tomé_&_Príncipe\": \"🇸🇹\",\n    \"T-Rex\": \"🦖\",\n    \"TOP_arrow\": \"🔝\",\n    \"Taiwan\": \"🇹🇼\",\n    \"Tajikistan\": \"🇹🇯\",\n    \"Tanzania\": \"🇹🇿\",\n    \"Taurus\": \"♉\",\n    \"Thailand\": \"🇹🇭\",\n    \"Timor-Leste\": \"🇹🇱\",\n    \"Togo\": \"🇹🇬\",\n    \"Tokelau\": \"🇹🇰\",\n    \"Tokyo_tower\": \"🗼\",\n    \"Tonga\": \"🇹🇴\",\n    \"Trinidad_&_Tobago\": \"🇹🇹\",\n    \"Tristan_da_Cunha\": \"🇹🇦\",\n    \"Tunisia\": \"🇹🇳\",\n    \"Turkey\": \"🇹🇷\",\n    \"Turkmenistan\": \"🇹🇲\",\n    \"Turks_&_Caicos_Islands\": \"🇹🇨\",\n    \"Tuvalu\": \"🇹🇻\",\n    \"U.S._Outlying_Islands\": \"🇺🇲\",\n    \"U.S._Virgin_Islands\": \"🇻🇮\",\n    \"UP!_button\": \"🆙\",\n    \"Uganda\": \"🇺🇬\",\n    \"Ukraine\": \"🇺🇦\",\n    \"United_Arab_Emirates\": \"🇦🇪\",\n    \"United_Kingdom\": \"🇬🇧\",\n    \"United_Nations\": \"🇺🇳\",\n    \"United_States\": \"🇺🇸\",\n    \"Uruguay\": \"🇺🇾\",\n    \"Uzbekistan\": \"🇺🇿\",\n    \"VS_button\": \"🆚\",\n    \"Vanuatu\": \"🇻🇺\",\n    \"Vatican_City\": \"🇻🇦\",\n    \"Venezuela\": \"🇻🇪\",\n    \"Vietnam\": \"🇻🇳\",\n    \"Virgo\": \"♍\",\n    \"Wales\": \"🏴󠁧󠁢󠁷󠁬󠁳󠁿\",\n    \"Wallis_&_Futuna\": \"🇼🇫\",\n    \"Western_Sahara\": \"🇪🇭\",\n    \"Yemen\": \"🇾🇪\",\n    \"ZZZ\": \"💤\",\n    \"Zambia\": \"🇿🇲\",\n    \"Zimbabwe\": \"🇿🇼\",\n    \"abacus\": \"🧮\",\n    \"accordion\": \"🪗\",\n    \"adhesive_bandage\": \"🩹\",\n    \"admission_tickets\": \"🎟\",\n    \"aerial_tramway\": \"🚡\",\n    \"airplane\": \"✈\",\n    \"airplane_arrival\": \"🛬\",\n    \"airplane_departure\": \"🛫\",\n    \"alarm_clock\": \"⏰\",\n    \"alembic\": \"⚗\",\n    \"alien\": \"👽\",\n    \"alien_monster\": \"👾\",\n    \"ambulance\": \"🚑\",\n    \"american_football\": \"🏈\",\n    \"amphora\": \"🏺\",\n    \"anatomical_heart\": \"🫀\",\n    \"anchor\": \"⚓\",\n    \"anger_symbol\": \"💢\",\n    \"angry_face\": \"😠\",\n    \"angry_face_with_horns\": \"👿\",\n    \"anguished_face\": \"😧\",\n    \"ant\": \"🐜\",\n    \"antenna_bars\": \"📶\",\n    \"anxious_face_with_sweat\": \"😰\",\n    \"articulated_lorry\": \"🚛\",\n    \"artist\": \"🧑‍🎨\",\n    \"artist_dark_skin_tone\": \"🧑🏿‍🎨\",\n    \"artist_light_skin_tone\": \"🧑🏻‍🎨\",\n    \"artist_medium-dark_skin_tone\": \"🧑🏾‍🎨\",\n    \"artist_medium-light_skin_tone\": \"🧑🏼‍🎨\",\n    \"artist_medium_skin_tone\": \"🧑🏽‍🎨\",\n    \"artist_palette\": \"🎨\",\n    \"astonished_face\": \"😲\",\n    \"astronaut\": \"🧑‍🚀\",\n    \"astronaut_dark_skin_tone\": \"🧑🏿‍🚀\",\n    \"astronaut_light_skin_tone\": \"🧑🏻‍🚀\",\n    \"astronaut_medium-dark_skin_tone\": \"🧑🏾‍🚀\",\n    \"astronaut_medium-light_skin_tone\": \"🧑🏼‍🚀\",\n    \"astronaut_medium_skin_tone\": \"🧑🏽‍🚀\",\n    \"atom_symbol\": \"⚛\",\n    \"auto_rickshaw\": \"🛺\",\n    \"automobile\": \"🚗\",\n    \"avocado\": \"🥑\",\n    \"axe\": \"🪓\",\n    \"baby\": \"👶\",\n    \"baby_angel\": \"👼\",\n    \"baby_angel_dark_skin_tone\": \"👼🏿\",\n    \"baby_angel_light_skin_tone\": \"👼🏻\",\n    \"baby_angel_medium-dark_skin_tone\": \"👼🏾\",\n    \"baby_angel_medium-light_skin_tone\": \"👼🏼\",\n    \"baby_angel_medium_skin_tone\": \"👼🏽\",\n    \"baby_bottle\": \"🍼\",\n    \"baby_chick\": \"🐤\",\n    \"baby_dark_skin_tone\": \"👶🏿\",\n    \"baby_light_skin_tone\": \"👶🏻\",\n    \"baby_medium-dark_skin_tone\": \"👶🏾\",\n    \"baby_medium-light_skin_tone\": \"👶🏼\",\n    \"baby_medium_skin_tone\": \"👶🏽\",\n    \"baby_symbol\": \"🚼\",\n    \"backhand_index_pointing_down\": \"👇\",\n    \"backhand_index_pointing_down_dark_skin_tone\": \"👇🏿\",\n    \"backhand_index_pointing_down_light_skin_tone\": \"👇🏻\",\n    \"backhand_index_pointing_down_medium-dark_skin_tone\": \"👇🏾\",\n    \"backhand_index_pointing_down_medium-light_skin_tone\": \"👇🏼\",\n    \"backhand_index_pointing_down_medium_skin_tone\": \"👇🏽\",\n    \"backhand_index_pointing_left\": \"👈\",\n    \"backhand_index_pointing_left_dark_skin_tone\": \"👈🏿\",\n    \"backhand_index_pointing_left_light_skin_tone\": \"👈🏻\",\n    \"backhand_index_pointing_left_medium-dark_skin_tone\": \"👈🏾\",\n    \"backhand_index_pointing_left_medium-light_skin_tone\": \"👈🏼\",\n    \"backhand_index_pointing_left_medium_skin_tone\": \"👈🏽\",\n    \"backhand_index_pointing_right\": \"👉\",\n    \"backhand_index_pointing_right_dark_skin_tone\": \"👉🏿\",\n    \"backhand_index_pointing_right_light_skin_tone\": \"👉🏻\",\n    \"backhand_index_pointing_right_medium-dark_skin_tone\": \"👉🏾\",\n    \"backhand_index_pointing_right_medium-light_skin_tone\": \"👉🏼\",\n    \"backhand_index_pointing_right_medium_skin_tone\": \"👉🏽\",\n    \"backhand_index_pointing_up\": \"👆\",\n    \"backhand_index_pointing_up_dark_skin_tone\": \"👆🏿\",\n    \"backhand_index_pointing_up_light_skin_tone\": \"👆🏻\",\n    \"backhand_index_pointing_up_medium-dark_skin_tone\": \"👆🏾\",\n    \"backhand_index_pointing_up_medium-light_skin_tone\": \"👆🏼\",\n    \"backhand_index_pointing_up_medium_skin_tone\": \"👆🏽\",\n    \"backpack\": \"🎒\",\n    \"bacon\": \"🥓\",\n    \"badger\": \"🦡\",\n    \"badminton\": \"🏸\",\n    \"bagel\": \"🥯\",\n    \"baggage_claim\": \"🛄\",\n    \"baguette_bread\": \"🥖\",\n    \"balance_scale\": \"⚖\",\n    \"bald\": \"🦲\",\n    \"ballet_shoes\": \"🩰\",\n    \"balloon\": \"🎈\",\n    \"ballot_box_with_ballot\": \"🗳\",\n    \"banana\": \"🍌\",\n    \"banjo\": \"🪕\",\n    \"bank\": \"🏦\",\n    \"bar_chart\": \"📊\",\n    \"barber_pole\": \"💈\",\n    \"baseball\": \"⚾\",\n    \"basket\": \"🧺\",\n    \"basketball\": \"🏀\",\n    \"bat\": \"🦇\",\n    \"bathtub\": \"🛁\",\n    \"battery\": \"🔋\",\n    \"beach_with_umbrella\": \"🏖\",\n    \"beaming_face_with_smiling_eyes\": \"😁\",\n    \"beans\": \"🫘\",\n    \"bear\": \"🐻\",\n    \"beating_heart\": \"💓\",\n    \"beaver\": \"🦫\",\n    \"bed\": \"🛏\",\n    \"beer_mug\": \"🍺\",\n    \"beetle\": \"🪲\",\n    \"bell\": \"🔔\",\n    \"bell_pepper\": \"🫑\",\n    \"bell_with_slash\": \"🔕\",\n    \"bellhop_bell\": \"🛎\",\n    \"bento_box\": \"🍱\",\n    \"beverage_box\": \"🧃\",\n    \"bicycle\": \"🚲\",\n    \"bikini\": \"👙\",\n    \"billed_cap\": \"🧢\",\n    \"biohazard\": \"☣\",\n    \"bird\": \"🐦\",\n    \"birthday_cake\": \"🎂\",\n    \"bison\": \"🦬\",\n    \"biting_lip\": \"🫦\",\n    \"black_bird\": \"🐦‍⬛\",\n    \"black_cat\": \"🐈‍⬛\",\n    \"black_circle\": \"⚫\",\n    \"black_flag\": \"🏴\",\n    \"black_heart\": \"🖤\",\n    \"black_large_square\": \"⬛\",\n    \"black_medium-small_square\": \"◾\",\n    \"black_medium_square\": \"◼\",\n    \"black_nib\": \"✒\",\n    \"black_small_square\": \"▪\",\n    \"black_square_button\": \"🔲\",\n    \"blossom\": \"🌼\",\n    \"blowfish\": \"🐡\",\n    \"blue_book\": \"📘\",\n    \"blue_circle\": \"🔵\",\n    \"blue_heart\": \"💙\",\n    \"blue_square\": \"🟦\",\n    \"blueberries\": \"🫐\",\n    \"boar\": \"🐗\",\n    \"bomb\": \"💣\",\n    \"bone\": \"🦴\",\n    \"bookmark\": \"🔖\",\n    \"bookmark_tabs\": \"📑\",\n    \"books\": \"📚\",\n    \"boomerang\": \"🪃\",\n    \"bottle_with_popping_cork\": \"🍾\",\n    \"bouquet\": \"💐\",\n    \"bow_and_arrow\": \"🏹\",\n    \"bowl_with_spoon\": \"🥣\",\n    \"bowling\": \"🎳\",\n    \"boxing_glove\": \"🥊\",\n    \"boy\": \"👦\",\n    \"boy_dark_skin_tone\": \"👦🏿\",\n    \"boy_light_skin_tone\": \"👦🏻\",\n    \"boy_medium-dark_skin_tone\": \"👦🏾\",\n    \"boy_medium-light_skin_tone\": \"👦🏼\",\n    \"boy_medium_skin_tone\": \"👦🏽\",\n    \"brain\": \"🧠\",\n    \"bread\": \"🍞\",\n    \"breast-feeding\": \"🤱\",\n    \"breast-feeding_dark_skin_tone\": \"🤱🏿\",\n    \"breast-feeding_light_skin_tone\": \"🤱🏻\",\n    \"breast-feeding_medium-dark_skin_tone\": \"🤱🏾\",\n    \"breast-feeding_medium-light_skin_tone\": \"🤱🏼\",\n    \"breast-feeding_medium_skin_tone\": \"🤱🏽\",\n    \"brick\": \"🧱\",\n    \"bridge_at_night\": \"🌉\",\n    \"briefcase\": \"💼\",\n    \"briefs\": \"🩲\",\n    \"bright_button\": \"🔆\",\n    \"broccoli\": \"🥦\",\n    \"broken_heart\": \"💔\",\n    \"broom\": \"🧹\",\n    \"brown_circle\": \"🟤\",\n    \"brown_heart\": \"🤎\",\n    \"brown_square\": \"🟫\",\n    \"bubble_tea\": \"🧋\",\n    \"bubbles\": \"🫧\",\n    \"bucket\": \"🪣\",\n    \"bug\": \"🐛\",\n    \"building_construction\": \"🏗\",\n    \"bullet_train\": \"🚅\",\n    \"bullseye\": \"🎯\",\n    \"burrito\": \"🌯\",\n    \"bus\": \"🚌\",\n    \"bus_stop\": \"🚏\",\n    \"bust_in_silhouette\": \"👤\",\n    \"busts_in_silhouette\": \"👥\",\n    \"butter\": \"🧈\",\n    \"butterfly\": \"🦋\",\n    \"cactus\": \"🌵\",\n    \"calendar\": \"📅\",\n    \"call_me_hand\": \"🤙\",\n    \"call_me_hand_dark_skin_tone\": \"🤙🏿\",\n    \"call_me_hand_light_skin_tone\": \"🤙🏻\",\n    \"call_me_hand_medium-dark_skin_tone\": \"🤙🏾\",\n    \"call_me_hand_medium-light_skin_tone\": \"🤙🏼\",\n    \"call_me_hand_medium_skin_tone\": \"🤙🏽\",\n    \"camel\": \"🐪\",\n    \"camera\": \"📷\",\n    \"camera_with_flash\": \"📸\",\n    \"camping\": \"🏕\",\n    \"candle\": \"🕯\",\n    \"candy\": \"🍬\",\n    \"canned_food\": \"🥫\",\n    \"canoe\": \"🛶\",\n    \"card_file_box\": \"🗃\",\n    \"card_index\": \"📇\",\n    \"card_index_dividers\": \"🗂\",\n    \"carousel_horse\": \"🎠\",\n    \"carp_streamer\": \"🎏\",\n    \"carpentry_saw\": \"🪚\",\n    \"carrot\": \"🥕\",\n    \"castle\": \"🏰\",\n    \"cat\": \"🐈\",\n    \"cat_face\": \"🐱\",\n    \"cat_with_tears_of_joy\": \"😹\",\n    \"cat_with_wry_smile\": \"😼\",\n    \"chains\": \"⛓\",\n    \"chair\": \"🪑\",\n    \"chart_decreasing\": \"📉\",\n    \"chart_increasing\": \"📈\",\n    \"chart_increasing_with_yen\": \"💹\",\n    \"check_box_with_check\": \"☑\",\n    \"check_mark\": \"✔\",\n    \"check_mark_button\": \"✅\",\n    \"cheese_wedge\": \"🧀\",\n    \"chequered_flag\": \"🏁\",\n    \"cherries\": \"🍒\",\n    \"cherry_blossom\": \"🌸\",\n    \"chess_pawn\": \"♟\",\n    \"chestnut\": \"🌰\",\n    \"chicken\": \"🐔\",\n    \"child\": \"🧒\",\n    \"child_dark_skin_tone\": \"🧒🏿\",\n    \"child_light_skin_tone\": \"🧒🏻\",\n    \"child_medium-dark_skin_tone\": \"🧒🏾\",\n    \"child_medium-light_skin_tone\": \"🧒🏼\",\n    \"child_medium_skin_tone\": \"🧒🏽\",\n    \"children_crossing\": \"🚸\",\n    \"chipmunk\": \"🐿\",\n    \"chocolate_bar\": \"🍫\",\n    \"chopsticks\": \"🥢\",\n    \"church\": \"⛪\",\n    \"cigarette\": \"🚬\",\n    \"cinema\": \"🎦\",\n    \"circled_M\": \"Ⓜ\",\n    \"circus_tent\": \"🎪\",\n    \"cityscape\": \"🏙\",\n    \"cityscape_at_dusk\": \"🌆\",\n    \"clamp\": \"🗜\",\n    \"clapper_board\": \"🎬\",\n    \"clapping_hands\": \"👏\",\n    \"clapping_hands_dark_skin_tone\": \"👏🏿\",\n    \"clapping_hands_light_skin_tone\": \"👏🏻\",\n    \"clapping_hands_medium-dark_skin_tone\": \"👏🏾\",\n    \"clapping_hands_medium-light_skin_tone\": \"👏🏼\",\n    \"clapping_hands_medium_skin_tone\": \"👏🏽\",\n    \"classical_building\": \"🏛\",\n    \"clinking_beer_mugs\": \"🍻\",\n    \"clinking_glasses\": \"🥂\",\n    \"clipboard\": \"📋\",\n    \"clockwise_vertical_arrows\": \"🔃\",\n    \"closed_book\": \"📕\",\n    \"closed_mailbox_with_lowered_flag\": \"📪\",\n    \"closed_mailbox_with_raised_flag\": \"📫\",\n    \"closed_umbrella\": \"🌂\",\n    \"cloud\": \"☁\",\n    \"cloud_with_lightning\": \"🌩\",\n    \"cloud_with_lightning_and_rain\": \"⛈\",\n    \"cloud_with_rain\": \"🌧\",\n    \"cloud_with_snow\": \"🌨\",\n    \"clown_face\": \"🤡\",\n    \"club_suit\": \"♣\",\n    \"clutch_bag\": \"👝\",\n    \"coat\": \"🧥\",\n    \"cockroach\": \"🪳\",\n    \"cocktail_glass\": \"🍸\",\n    \"coconut\": \"🥥\",\n    \"coffin\": \"⚰\",\n    \"coin\": \"🪙\",\n    \"cold_face\": \"🥶\",\n    \"collision\": \"💥\",\n    \"comet\": \"☄\",\n    \"compass\": \"🧭\",\n    \"computer_disk\": \"💽\",\n    \"computer_mouse\": \"🖱\",\n    \"confetti_ball\": \"🎊\",\n    \"confounded_face\": \"😖\",\n    \"confused_face\": \"😕\",\n    \"construction\": \"🚧\",\n    \"construction_worker\": \"👷\",\n    \"construction_worker_dark_skin_tone\": \"👷🏿\",\n    \"construction_worker_light_skin_tone\": \"👷🏻\",\n    \"construction_worker_medium-dark_skin_tone\": \"👷🏾\",\n    \"construction_worker_medium-light_skin_tone\": \"👷🏼\",\n    \"construction_worker_medium_skin_tone\": \"👷🏽\",\n    \"control_knobs\": \"🎛\",\n    \"convenience_store\": \"🏪\",\n    \"cook\": \"🧑‍🍳\",\n    \"cook_dark_skin_tone\": \"🧑🏿‍🍳\",\n    \"cook_light_skin_tone\": \"🧑🏻‍🍳\",\n    \"cook_medium-dark_skin_tone\": \"🧑🏾‍🍳\",\n    \"cook_medium-light_skin_tone\": \"🧑🏼‍🍳\",\n    \"cook_medium_skin_tone\": \"🧑🏽‍🍳\",\n    \"cooked_rice\": \"🍚\",\n    \"cookie\": \"🍪\",\n    \"cooking\": \"🍳\",\n    \"copyright\": \"©\",\n    \"coral\": \"🪸\",\n    \"couch_and_lamp\": \"🛋\",\n    \"counterclockwise_arrows_button\": \"🔄\",\n    \"couple_with_heart\": \"💑\",\n    \"couple_with_heart_dark_skin_tone\": \"💑🏿\",\n    \"couple_with_heart_light_skin_tone\": \"💑🏻\",\n    \"couple_with_heart_man_man\": \"👨‍❤‍👨\",\n    \"couple_with_heart_man_man_dark_skin_tone\": \"👨🏿‍❤‍👨🏿\",\n    \"couple_with_heart_man_man_dark_skin_tone_light_skin_tone\": \"👨🏿‍❤‍👨🏻\",\n    (\n        \"couple_with_heart_man_man_dark_skin_tone_medium-dark_skin_tone\"\n    ): \"👨🏿‍❤‍👨🏾\",\n    (\n        \"couple_with_heart_man_man_dark_skin_tone_medium-light_skin_tone\"\n    ): \"👨🏿‍❤‍👨🏼\",\n    \"couple_with_heart_man_man_dark_skin_tone_medium_skin_tone\": \"👨🏿‍❤‍👨🏽\",\n    \"couple_with_heart_man_man_light_skin_tone\": \"👨🏻‍❤‍👨🏻\",\n    \"couple_with_heart_man_man_light_skin_tone_dark_skin_tone\": \"👨🏻‍❤‍👨🏿\",\n    (\n        \"couple_with_heart_man_man_light_skin_tone_medium-dark_skin_tone\"\n    ): \"👨🏻‍❤‍👨🏾\",\n    (\n        \"couple_with_heart_man_man_light_skin_tone_medium-light_skin_tone\"\n    ): \"👨🏻‍❤‍👨🏼\",\n    \"couple_with_heart_man_man_light_skin_tone_medium_skin_tone\": \"👨🏻‍❤‍👨🏽\",\n    \"couple_with_heart_man_man_medium-dark_skin_tone\": \"👨🏾‍❤‍👨🏾\",\n    (\n        \"couple_with_heart_man_man_medium-dark_skin_tone_dark_skin_tone\"\n    ): \"👨🏾‍❤‍👨🏿\",\n    (\n        \"couple_with_heart_man_man_medium-dark_skin_tone_light_skin_tone\"\n    ): \"👨🏾‍❤‍👨🏻\",\n    (\n        \"couple_with_heart_man_man_medium\"\n        \"-dark_skin_tone_medium-light_skin_tone\"\n    ): \"👨🏾‍❤‍👨🏼\",\n    (\n        \"couple_with_heart_man_man_medium-dark_skin_tone_medium_skin_tone\"\n    ): \"👨🏾‍❤‍👨🏽\",\n    \"couple_with_heart_man_man_medium-light_skin_tone\": \"👨🏼‍❤‍👨🏼\",\n    (\n        \"couple_with_heart_man_man_medium-light_skin_tone_dark_skin_tone\"\n    ): \"👨🏼‍❤‍👨🏿\",\n    (\n        \"couple_with_heart_man_man_medium-light_skin_tone_light_skin_tone\"\n    ): \"👨🏼‍❤‍👨🏻\",\n    (\n        \"couple_with_heart_man_man_medium\"\n        \"-light_skin_tone_medium-dark_skin_tone\"\n    ): \"👨🏼‍❤‍👨🏾\",\n    (\n        \"couple_with_heart_man_man_medium-light_skin_tone_medium_skin_tone\"\n    ): \"👨🏼‍❤‍👨🏽\",\n    \"couple_with_heart_man_man_medium_skin_tone\": \"👨🏽‍❤‍👨🏽\",\n    \"couple_with_heart_man_man_medium_skin_tone_dark_skin_tone\": \"👨🏽‍❤‍👨🏿\",\n    \"couple_with_heart_man_man_medium_skin_tone_light_skin_tone\": \"👨🏽‍❤‍👨🏻\",\n    (\n        \"couple_with_heart_man_man_medium_skin_tone_medium-dark_skin_tone\"\n    ): \"👨🏽‍❤‍👨🏾\",\n    (\n        \"couple_with_heart_man_man_medium_skin_tone_medium-light_skin_tone\"\n    ): \"👨🏽‍❤‍👨🏼\",\n    \"couple_with_heart_medium-dark_skin_tone\": \"💑🏾\",\n    \"couple_with_heart_medium-light_skin_tone\": \"💑🏼\",\n    \"couple_with_heart_medium_skin_tone\": \"💑🏽\",\n    (\n        \"couple_with_heart_person_person_dark_skin_tone_light_skin_tone\"\n    ): \"🧑🏿‍❤‍🧑🏻\",\n    (\n        \"couple_with_heart_person_person_dark_skin_tone_medium-dark_skin_tone\"\n    ): \"🧑🏿‍❤‍🧑🏾\",\n    (\n        \"couple_with_heart_person_person_dark_skin_tone_medium-light_skin_tone\"\n    ): \"🧑🏿‍❤‍🧑🏼\",\n    (\n        \"couple_with_heart_person_person_dark_skin_tone_medium_skin_tone\"\n    ): \"🧑🏿‍❤‍🧑🏽\",\n    (\n        \"couple_with_heart_person_person_light_skin_tone_dark_skin_tone\"\n    ): \"🧑🏻‍❤‍🧑🏿\",\n    (\n        \"couple_with_heart_person_person_light_skin_tone_medium-dark_skin_tone\"\n    ): \"🧑🏻‍❤‍🧑🏾\",\n    (\n        \"couple_with_heart_person_person_\"\n        \"light_skin_tone_medium-light_skin_tone\"\n    ): \"🧑🏻‍❤‍🧑🏼\",\n    (\n        \"couple_with_heart_person_person_light_skin_tone_medium_skin_tone\"\n    ): \"🧑🏻‍❤‍🧑🏽\",\n    (\n        \"couple_with_heart_person_person_medium-dark_skin_tone_dark_skin_tone\"\n    ): \"🧑🏾‍❤‍🧑🏿\",\n    (\n        \"couple_with_heart_person_person_medium-dark_skin_tone_light_skin_tone\"\n    ): \"🧑🏾‍❤‍🧑🏻\",\n    (\n        \"couple_with_heart_person_person_medium\"\n        \"-dark_skin_tone_medium-light_skin_tone\"\n    ): \"🧑🏾‍❤‍🧑🏼\",\n    (\n        \"couple_with_heart_person_person\"\n        \"_medium-dark_skin_tone_medium_skin_tone\"\n    ): \"🧑🏾‍❤‍🧑🏽\",\n    (\n        \"couple_with_heart_person_person_medium-light_skin_tone_dark_skin_tone\"\n    ): \"🧑🏼‍❤‍🧑🏿\",\n    (\n        \"couple_with_heart_person_person\"\n        \"_medium-light_skin_tone_light_skin_tone\"\n    ): \"🧑🏼‍❤‍🧑🏻\",\n    (\n        \"couple_with_heart_person_person_medium\"\n        \"-light_skin_tone_medium-dark_skin_tone\"\n    ): \"🧑🏼‍❤‍🧑🏾\",\n    (\n        \"couple_with_heart_person_person\"\n        \"_medium-light_skin_tone_medium_skin_tone\"\n    ): \"🧑🏼‍❤‍🧑🏽\",\n    (\n        \"couple_with_heart_person_person_medium_skin_tone_dark_skin_tone\"\n    ): \"🧑🏽‍❤‍🧑🏿\",\n    (\n        \"couple_with_heart_person_person_medium_skin_tone_light_skin_tone\"\n    ): \"🧑🏽‍❤‍🧑🏻\",\n    (\n        \"couple_with_heart_person_person\"\n        \"_medium_skin_tone_medium-dark_skin_tone\"\n    ): \"🧑🏽‍❤‍🧑🏾\",\n    (\n        \"couple_with_heart_person_person\"\n        \"_medium_skin_tone_medium-light_skin_tone\"\n    ): \"🧑🏽‍❤‍🧑🏼\",\n    \"couple_with_heart_woman_man\": \"👩‍❤‍👨\",\n    \"couple_with_heart_woman_man_dark_skin_tone\": \"👩🏿‍❤‍👨🏿\",\n    \"couple_with_heart_woman_man_dark_skin_tone_light_skin_tone\": \"👩🏿‍❤‍👨🏻\",\n    (\n        \"couple_with_heart_woman_man_dark_skin_tone_medium-dark_skin_tone\"\n    ): \"👩🏿‍❤‍👨🏾\",\n    (\n        \"couple_with_heart_woman_man_dark_skin_tone_medium-light_skin_tone\"\n    ): \"👩🏿‍❤‍👨🏼\",\n    \"couple_with_heart_woman_man_dark_skin_tone_medium_skin_tone\": \"👩🏿‍❤‍👨🏽\",\n    \"couple_with_heart_woman_man_light_skin_tone\": \"👩🏻‍❤‍👨🏻\",\n    \"couple_with_heart_woman_man_light_skin_tone_dark_skin_tone\": \"👩🏻‍❤‍👨🏿\",\n    (\n        \"couple_with_heart_woman_man_light_skin_tone_medium-dark_skin_tone\"\n    ): \"👩🏻‍❤‍👨🏾\",\n    (\n        \"couple_with_heart_woman_man_light_skin_tone_medium-light_skin_tone\"\n    ): \"👩🏻‍❤‍👨🏼\",\n    (\n        \"couple_with_heart_woman_man_light_skin_tone_medium_skin_tone\"\n    ): \"👩🏻‍❤‍👨🏽\",\n    \"couple_with_heart_woman_man_medium-dark_skin_tone\": \"👩🏾‍❤‍👨🏾\",\n    (\n        \"couple_with_heart_woman_man_medium-dark_skin_tone_dark_skin_tone\"\n    ): \"👩🏾‍❤‍👨🏿\",\n    (\n        \"couple_with_heart_woman_man_medium-dark_skin_tone_light_skin_tone\"\n    ): \"👩🏾‍❤‍👨🏻\",\n    (\n        \"couple_with_heart_woman_man_medium\"\n        \"-dark_skin_tone_medium-light_skin_tone\"\n    ): \"👩🏾‍❤‍👨🏼\",\n    (\n        \"couple_with_heart_woman_man_medium-dark_skin_tone_medium_skin_tone\"\n    ): \"👩🏾‍❤‍👨🏽\",\n    \"couple_with_heart_woman_man_medium-light_skin_tone\": \"👩🏼‍❤‍👨🏼\",\n    (\n        \"couple_with_heart_woman_man_medium-light_skin_tone_dark_skin_tone\"\n    ): \"👩🏼‍❤‍👨🏿\",\n    (\n        \"couple_with_heart_woman_man_medium-light_skin_tone_light_skin_tone\"\n    ): \"👩🏼‍❤‍👨🏻\",\n    (\n        \"couple_with_heart_woman_man_medium\"\n        \"-light_skin_tone_medium-dark_skin_tone\"\n    ): \"👩🏼‍❤‍👨🏾\",\n    (\n        \"couple_with_heart_woman_man_medium-light_skin_tone_medium_skin_tone\"\n    ): \"👩🏼‍❤‍👨🏽\",\n    \"couple_with_heart_woman_man_medium_skin_tone\": \"👩🏽‍❤‍👨🏽\",\n    \"couple_with_heart_woman_man_medium_skin_tone_dark_skin_tone\": \"👩🏽‍❤‍👨🏿\",\n    (\n        \"couple_with_heart_woman_man_medium_skin_tone_light_skin_tone\"\n    ): \"👩🏽‍❤‍👨🏻\",\n    (\n        \"couple_with_heart_woman_man_medium_skin_tone_medium-dark_skin_tone\"\n    ): \"👩🏽‍❤‍👨🏾\",\n    (\n        \"couple_with_heart_woman_man_medium_skin_tone_medium-light_skin_tone\"\n    ): \"👩🏽‍❤‍👨🏼\",\n    \"couple_with_heart_woman_woman\": \"👩‍❤‍👩\",\n    \"couple_with_heart_woman_woman_dark_skin_tone\": \"👩🏿‍❤‍👩🏿\",\n    (\n        \"couple_with_heart_woman_woman_dark_skin_tone_light_skin_tone\"\n    ): \"👩🏿‍❤‍👩🏻\",\n    (\n        \"couple_with_heart_woman_woman_dark_skin_tone_medium-dark_skin_tone\"\n    ): \"👩🏿‍❤‍👩🏾\",\n    (\n        \"couple_with_heart_woman_woman_dark_skin_tone_medium-light_skin_tone\"\n    ): \"👩🏿‍❤‍👩🏼\",\n    (\n        \"couple_with_heart_woman_woman_dark_skin_tone_medium_skin_tone\"\n    ): \"👩🏿‍❤‍👩🏽\",\n    \"couple_with_heart_woman_woman_light_skin_tone\": \"👩🏻‍❤‍👩🏻\",\n    (\n        \"couple_with_heart_woman_woman_light_skin_tone_dark_skin_tone\"\n    ): \"👩🏻‍❤‍👩🏿\",\n    (\n        \"couple_with_heart_woman_woman_light_skin_tone_medium-dark_skin_tone\"\n    ): \"👩🏻‍❤‍👩🏾\",\n    (\n        \"couple_with_heart_woman_woman_light_skin_tone_medium-light_skin_tone\"\n    ): \"👩🏻‍❤‍👩🏼\",\n    (\n        \"couple_with_heart_woman_woman_light_skin_tone_medium_skin_tone\"\n    ): \"👩🏻‍❤‍👩🏽\",\n    \"couple_with_heart_woman_woman_medium-dark_skin_tone\": \"👩🏾‍❤‍👩🏾\",\n    (\n        \"couple_with_heart_woman_woman_medium-dark_skin_tone_dark_skin_tone\"\n    ): \"👩🏾‍❤‍👩🏿\",\n    (\n        \"couple_with_heart_woman_woman_medium-dark_skin_tone_light_skin_tone\"\n    ): \"👩🏾‍❤‍👩🏻\",\n    (\n        \"couple_with_heart_woman_woman_medium\"\n        \"-dark_skin_tone_medium-light_skin_tone\"\n    ): \"👩🏾‍❤‍👩🏼\",\n    (\n        \"couple_with_heart_woman_woman_medium-dark_skin_tone_medium_skin_tone\"\n    ): \"👩🏾‍❤‍👩🏽\",\n    \"couple_with_heart_woman_woman_medium-light_skin_tone\": \"👩🏼‍❤‍👩🏼\",\n    (\n        \"couple_with_heart_woman_woman_medium-light_skin_tone_dark_skin_tone\"\n    ): \"👩🏼‍❤‍👩🏿\",\n    (\n        \"couple_with_heart_woman_woman_medium-light_skin_tone_light_skin_tone\"\n    ): \"👩🏼‍❤‍👩🏻\",\n    (\n        \"couple_with_heart_woman_woman_medium\"\n        \"-light_skin_tone_medium-dark_skin_tone\"\n    ): \"👩🏼‍❤‍👩🏾\",\n    (\n        \"couple_with_heart_woman_woman_medium-light_skin_tone_medium_skin_tone\"\n    ): \"👩🏼‍❤‍👩🏽\",\n    \"couple_with_heart_woman_woman_medium_skin_tone\": \"👩🏽‍❤‍👩🏽\",\n    (\n        \"couple_with_heart_woman_woman_medium_skin_tone_dark_skin_tone\"\n    ): \"👩🏽‍❤‍👩🏿\",\n    (\n        \"couple_with_heart_woman_woman_medium_skin_tone_light_skin_tone\"\n    ): \"👩🏽‍❤‍👩🏻\",\n    (\n        \"couple_with_heart_woman_woman_medium_skin_tone_medium-dark_skin_tone\"\n    ): \"👩🏽‍❤‍👩🏾\",\n    (\n        \"couple_with_heart_woman_woman_medium_skin_tone_medium-light_skin_tone\"\n    ): \"👩🏽‍❤‍👩🏼\",\n    \"cow\": \"🐄\",\n    \"cow_face\": \"🐮\",\n    \"cowboy_hat_face\": \"🤠\",\n    \"crab\": \"🦀\",\n    \"crayon\": \"🖍\",\n    \"credit_card\": \"💳\",\n    \"crescent_moon\": \"🌙\",\n    \"cricket\": \"🦗\",\n    \"cricket_game\": \"🏏\",\n    \"crocodile\": \"🐊\",\n    \"croissant\": \"🥐\",\n    \"cross_mark\": \"❌\",\n    \"cross_mark_button\": \"❎\",\n    \"crossed_fingers\": \"🤞\",\n    \"crossed_fingers_dark_skin_tone\": \"🤞🏿\",\n    \"crossed_fingers_light_skin_tone\": \"🤞🏻\",\n    \"crossed_fingers_medium-dark_skin_tone\": \"🤞🏾\",\n    \"crossed_fingers_medium-light_skin_tone\": \"🤞🏼\",\n    \"crossed_fingers_medium_skin_tone\": \"🤞🏽\",\n    \"crossed_flags\": \"🎌\",\n    \"crossed_swords\": \"⚔\",\n    \"crown\": \"👑\",\n    \"crutch\": \"🩼\",\n    \"crying_cat\": \"😿\",\n    \"crying_face\": \"😢\",\n    \"crystal_ball\": \"🔮\",\n    \"cucumber\": \"🥒\",\n    \"cup_with_straw\": \"🥤\",\n    \"cupcake\": \"🧁\",\n    \"curling_stone\": \"🥌\",\n    \"curly_hair\": \"🦱\",\n    \"curly_loop\": \"➰\",\n    \"currency_exchange\": \"💱\",\n    \"curry_rice\": \"🍛\",\n    \"custard\": \"🍮\",\n    \"customs\": \"🛃\",\n    \"cut_of_meat\": \"🥩\",\n    \"cyclone\": \"🌀\",\n    \"dagger\": \"🗡\",\n    \"dango\": \"🍡\",\n    \"dark_skin_tone\": \"🏿\",\n    \"dashing_away\": \"💨\",\n    \"deaf_man\": \"🧏‍♂\",\n    \"deaf_man_dark_skin_tone\": \"🧏🏿‍♂\",\n    \"deaf_man_light_skin_tone\": \"🧏🏻‍♂\",\n    \"deaf_man_medium-dark_skin_tone\": \"🧏🏾‍♂\",\n    \"deaf_man_medium-light_skin_tone\": \"🧏🏼‍♂\",\n    \"deaf_man_medium_skin_tone\": \"🧏🏽‍♂\",\n    \"deaf_person\": \"🧏\",\n    \"deaf_person_dark_skin_tone\": \"🧏🏿\",\n    \"deaf_person_light_skin_tone\": \"🧏🏻\",\n    \"deaf_person_medium-dark_skin_tone\": \"🧏🏾\",\n    \"deaf_person_medium-light_skin_tone\": \"🧏🏼\",\n    \"deaf_person_medium_skin_tone\": \"🧏🏽\",\n    \"deaf_woman\": \"🧏‍♀\",\n    \"deaf_woman_dark_skin_tone\": \"🧏🏿‍♀\",\n    \"deaf_woman_light_skin_tone\": \"🧏🏻‍♀\",\n    \"deaf_woman_medium-dark_skin_tone\": \"🧏🏾‍♀\",\n    \"deaf_woman_medium-light_skin_tone\": \"🧏🏼‍♀\",\n    \"deaf_woman_medium_skin_tone\": \"🧏🏽‍♀\",\n    \"deciduous_tree\": \"🌳\",\n    \"deer\": \"🦌\",\n    \"delivery_truck\": \"🚚\",\n    \"department_store\": \"🏬\",\n    \"derelict_house\": \"🏚\",\n    \"desert\": \"🏜\",\n    \"desert_island\": \"🏝\",\n    \"desktop_computer\": \"🖥\",\n    \"detective\": \"🕵\",\n    \"detective_dark_skin_tone\": \"🕵🏿\",\n    \"detective_light_skin_tone\": \"🕵🏻\",\n    \"detective_medium-dark_skin_tone\": \"🕵🏾\",\n    \"detective_medium-light_skin_tone\": \"🕵🏼\",\n    \"detective_medium_skin_tone\": \"🕵🏽\",\n    \"diamond_suit\": \"♦\",\n    \"diamond_with_a_dot\": \"💠\",\n    \"dim_button\": \"🔅\",\n    \"disappointed_face\": \"😞\",\n    \"disguised_face\": \"🥸\",\n    \"divide\": \"➗\",\n    \"diving_mask\": \"🤿\",\n    \"diya_lamp\": \"🪔\",\n    \"dizzy\": \"💫\",\n    \"dna\": \"🧬\",\n    \"dodo\": \"🦤\",\n    \"dog\": \"🐕\",\n    \"dog_face\": \"🐶\",\n    \"dollar_banknote\": \"💵\",\n    \"dolphin\": \"🐬\",\n    \"donkey\": \"🫏\",\n    \"door\": \"🚪\",\n    \"dotted_line_face\": \"🫥\",\n    \"dotted_six-pointed_star\": \"🔯\",\n    \"double_curly_loop\": \"➿\",\n    \"double_exclamation_mark\": \"‼\",\n    \"doughnut\": \"🍩\",\n    \"dove\": \"🕊\",\n    \"down-left_arrow\": \"↙\",\n    \"down-right_arrow\": \"↘\",\n    \"down_arrow\": \"⬇\",\n    \"downcast_face_with_sweat\": \"😓\",\n    \"downwards_button\": \"🔽\",\n    \"dragon\": \"🐉\",\n    \"dragon_face\": \"🐲\",\n    \"dress\": \"👗\",\n    \"drooling_face\": \"🤤\",\n    \"drop_of_blood\": \"🩸\",\n    \"droplet\": \"💧\",\n    \"drum\": \"🥁\",\n    \"duck\": \"🦆\",\n    \"dumpling\": \"🥟\",\n    \"dvd\": \"📀\",\n    \"e-mail\": \"📧\",\n    \"eagle\": \"🦅\",\n    \"ear\": \"👂\",\n    \"ear_dark_skin_tone\": \"👂🏿\",\n    \"ear_light_skin_tone\": \"👂🏻\",\n    \"ear_medium-dark_skin_tone\": \"👂🏾\",\n    \"ear_medium-light_skin_tone\": \"👂🏼\",\n    \"ear_medium_skin_tone\": \"👂🏽\",\n    \"ear_of_corn\": \"🌽\",\n    \"ear_with_hearing_aid\": \"🦻\",\n    \"ear_with_hearing_aid_dark_skin_tone\": \"🦻🏿\",\n    \"ear_with_hearing_aid_light_skin_tone\": \"🦻🏻\",\n    \"ear_with_hearing_aid_medium-dark_skin_tone\": \"🦻🏾\",\n    \"ear_with_hearing_aid_medium-light_skin_tone\": \"🦻🏼\",\n    \"ear_with_hearing_aid_medium_skin_tone\": \"🦻🏽\",\n    \"egg\": \"🥚\",\n    \"eggplant\": \"🍆\",\n    \"eight-pointed_star\": \"✴\",\n    \"eight-spoked_asterisk\": \"✳\",\n    \"eight-thirty\": \"🕣\",\n    \"eight_o’clock\": \"🕗\",\n    \"eject_button\": \"⏏\",\n    \"electric_plug\": \"🔌\",\n    \"elephant\": \"🐘\",\n    \"elevator\": \"🛗\",\n    \"eleven-thirty\": \"🕦\",\n    \"eleven_o’clock\": \"🕚\",\n    \"elf\": \"🧝\",\n    \"elf_dark_skin_tone\": \"🧝🏿\",\n    \"elf_light_skin_tone\": \"🧝🏻\",\n    \"elf_medium-dark_skin_tone\": \"🧝🏾\",\n    \"elf_medium-light_skin_tone\": \"🧝🏼\",\n    \"elf_medium_skin_tone\": \"🧝🏽\",\n    \"empty_nest\": \"🪹\",\n    \"enraged_face\": \"😡\",\n    \"envelope\": \"✉\",\n    \"envelope_with_arrow\": \"📩\",\n    \"euro_banknote\": \"💶\",\n    \"evergreen_tree\": \"🌲\",\n    \"ewe\": \"🐑\",\n    \"exclamation_question_mark\": \"⁉\",\n    \"exploding_head\": \"🤯\",\n    \"expressionless_face\": \"😑\",\n    \"eye\": \"👁\",\n    \"eye_in_speech_bubble\": \"👁‍🗨\",\n    \"eyes\": \"👀\",\n    \"face_blowing_a_kiss\": \"😘\",\n    \"face_exhaling\": \"😮‍💨\",\n    \"face_holding_back_tears\": \"🥹\",\n    \"face_in_clouds\": \"😶‍🌫\",\n    \"face_savoring_food\": \"😋\",\n    \"face_screaming_in_fear\": \"😱\",\n    \"face_vomiting\": \"🤮\",\n    \"face_with_crossed-out_eyes\": \"😵\",\n    \"face_with_diagonal_mouth\": \"🫤\",\n    \"face_with_hand_over_mouth\": \"🤭\",\n    \"face_with_head-bandage\": \"🤕\",\n    \"face_with_medical_mask\": \"😷\",\n    \"face_with_monocle\": \"🧐\",\n    \"face_with_open_eyes_and_hand_over_mouth\": \"🫢\",\n    \"face_with_open_mouth\": \"😮\",\n    \"face_with_peeking_eye\": \"🫣\",\n    \"face_with_raised_eyebrow\": \"🤨\",\n    \"face_with_rolling_eyes\": \"🙄\",\n    \"face_with_spiral_eyes\": \"😵‍💫\",\n    \"face_with_steam_from_nose\": \"😤\",\n    \"face_with_symbols_on_mouth\": \"🤬\",\n    \"face_with_tears_of_joy\": \"😂\",\n    \"face_with_thermometer\": \"🤒\",\n    \"face_with_tongue\": \"😛\",\n    \"face_without_mouth\": \"😶\",\n    \"factory\": \"🏭\",\n    \"factory_worker\": \"🧑‍🏭\",\n    \"factory_worker_dark_skin_tone\": \"🧑🏿‍🏭\",\n    \"factory_worker_light_skin_tone\": \"🧑🏻‍🏭\",\n    \"factory_worker_medium-dark_skin_tone\": \"🧑🏾‍🏭\",\n    \"factory_worker_medium-light_skin_tone\": \"🧑🏼‍🏭\",\n    \"factory_worker_medium_skin_tone\": \"🧑🏽‍🏭\",\n    \"fairy\": \"🧚\",\n    \"fairy_dark_skin_tone\": \"🧚🏿\",\n    \"fairy_light_skin_tone\": \"🧚🏻\",\n    \"fairy_medium-dark_skin_tone\": \"🧚🏾\",\n    \"fairy_medium-light_skin_tone\": \"🧚🏼\",\n    \"fairy_medium_skin_tone\": \"🧚🏽\",\n    \"falafel\": \"🧆\",\n    \"fallen_leaf\": \"🍂\",\n    \"family\": \"👪\",\n    \"family_man_boy\": \"👨‍👦\",\n    \"family_man_boy_boy\": \"👨‍👦‍👦\",\n    \"family_man_girl\": \"👨‍👧\",\n    \"family_man_girl_boy\": \"👨‍👧‍👦\",\n    \"family_man_girl_girl\": \"👨‍👧‍👧\",\n    \"family_man_man_boy\": \"👨‍👨‍👦\",\n    \"family_man_man_boy_boy\": \"👨‍👨‍👦‍👦\",\n    \"family_man_man_girl\": \"👨‍👨‍👧\",\n    \"family_man_man_girl_boy\": \"👨‍👨‍👧‍👦\",\n    \"family_man_man_girl_girl\": \"👨‍👨‍👧‍👧\",\n    \"family_man_woman_boy\": \"👨‍👩‍👦\",\n    \"family_man_woman_boy_boy\": \"👨‍👩‍👦‍👦\",\n    \"family_man_woman_girl\": \"👨‍👩‍👧\",\n    \"family_man_woman_girl_boy\": \"👨‍👩‍👧‍👦\",\n    \"family_man_woman_girl_girl\": \"👨‍👩‍👧‍👧\",\n    \"family_woman_boy\": \"👩‍👦\",\n    \"family_woman_boy_boy\": \"👩‍👦‍👦\",\n    \"family_woman_girl\": \"👩‍👧\",\n    \"family_woman_girl_boy\": \"👩‍👧‍👦\",\n    \"family_woman_girl_girl\": \"👩‍👧‍👧\",\n    \"family_woman_woman_boy\": \"👩‍👩‍👦\",\n    \"family_woman_woman_boy_boy\": \"👩‍👩‍👦‍👦\",\n    \"family_woman_woman_girl\": \"👩‍👩‍👧\",\n    \"family_woman_woman_girl_boy\": \"👩‍👩‍👧‍👦\",\n    \"family_woman_woman_girl_girl\": \"👩‍👩‍👧‍👧\",\n    \"farmer\": \"🧑‍🌾\",\n    \"farmer_dark_skin_tone\": \"🧑🏿‍🌾\",\n    \"farmer_light_skin_tone\": \"🧑🏻‍🌾\",\n    \"farmer_medium-dark_skin_tone\": \"🧑🏾‍🌾\",\n    \"farmer_medium-light_skin_tone\": \"🧑🏼‍🌾\",\n    \"farmer_medium_skin_tone\": \"🧑🏽‍🌾\",\n    \"fast-forward_button\": \"⏩\",\n    \"fast_down_button\": \"⏬\",\n    \"fast_reverse_button\": \"⏪\",\n    \"fast_up_button\": \"⏫\",\n    \"fax_machine\": \"📠\",\n    \"fearful_face\": \"😨\",\n    \"feather\": \"🪶\",\n    \"female_sign\": \"♀\",\n    \"ferris_wheel\": \"🎡\",\n    \"ferry\": \"⛴\",\n    \"field_hockey\": \"🏑\",\n    \"file_cabinet\": \"🗄\",\n    \"file_folder\": \"📁\",\n    \"film_frames\": \"🎞\",\n    \"film_projector\": \"📽\",\n    \"fire\": \"🔥\",\n    \"fire_engine\": \"🚒\",\n    \"fire_extinguisher\": \"🧯\",\n    \"firecracker\": \"🧨\",\n    \"firefighter\": \"🧑‍🚒\",\n    \"firefighter_dark_skin_tone\": \"🧑🏿‍🚒\",\n    \"firefighter_light_skin_tone\": \"🧑🏻‍🚒\",\n    \"firefighter_medium-dark_skin_tone\": \"🧑🏾‍🚒\",\n    \"firefighter_medium-light_skin_tone\": \"🧑🏼‍🚒\",\n    \"firefighter_medium_skin_tone\": \"🧑🏽‍🚒\",\n    \"fireworks\": \"🎆\",\n    \"first_quarter_moon\": \"🌓\",\n    \"first_quarter_moon_face\": \"🌛\",\n    \"fish\": \"🐟\",\n    \"fish_cake_with_swirl\": \"🍥\",\n    \"fishing_pole\": \"🎣\",\n    \"five-thirty\": \"🕠\",\n    \"five_o’clock\": \"🕔\",\n    \"flag_in_hole\": \"⛳\",\n    \"flamingo\": \"🦩\",\n    \"flashlight\": \"🔦\",\n    \"flat_shoe\": \"🥿\",\n    \"flatbread\": \"🫓\",\n    \"fleur-de-lis\": \"⚜\",\n    \"flexed_biceps\": \"💪\",\n    \"flexed_biceps_dark_skin_tone\": \"💪🏿\",\n    \"flexed_biceps_light_skin_tone\": \"💪🏻\",\n    \"flexed_biceps_medium-dark_skin_tone\": \"💪🏾\",\n    \"flexed_biceps_medium-light_skin_tone\": \"💪🏼\",\n    \"flexed_biceps_medium_skin_tone\": \"💪🏽\",\n    \"floppy_disk\": \"💾\",\n    \"flower_playing_cards\": \"🎴\",\n    \"flushed_face\": \"😳\",\n    \"flute\": \"🪈\",\n    \"fly\": \"🪰\",\n    \"flying_disc\": \"🥏\",\n    \"flying_saucer\": \"🛸\",\n    \"fog\": \"🌫\",\n    \"foggy\": \"🌁\",\n    \"folded_hands\": \"🙏\",\n    \"folded_hands_dark_skin_tone\": \"🙏🏿\",\n    \"folded_hands_light_skin_tone\": \"🙏🏻\",\n    \"folded_hands_medium-dark_skin_tone\": \"🙏🏾\",\n    \"folded_hands_medium-light_skin_tone\": \"🙏🏼\",\n    \"folded_hands_medium_skin_tone\": \"🙏🏽\",\n    \"folding_hand_fan\": \"🪭\",\n    \"fondue\": \"🫕\",\n    \"foot\": \"🦶\",\n    \"foot_dark_skin_tone\": \"🦶🏿\",\n    \"foot_light_skin_tone\": \"🦶🏻\",\n    \"foot_medium-dark_skin_tone\": \"🦶🏾\",\n    \"foot_medium-light_skin_tone\": \"🦶🏼\",\n    \"foot_medium_skin_tone\": \"🦶🏽\",\n    \"footprints\": \"👣\",\n    \"fork_and_knife\": \"🍴\",\n    \"fork_and_knife_with_plate\": \"🍽\",\n    \"fortune_cookie\": \"🥠\",\n    \"fountain\": \"⛲\",\n    \"fountain_pen\": \"🖋\",\n    \"four-thirty\": \"🕟\",\n    \"four_leaf_clover\": \"🍀\",\n    \"four_o’clock\": \"🕓\",\n    \"fox\": \"🦊\",\n    \"framed_picture\": \"🖼\",\n    \"french_fries\": \"🍟\",\n    \"fried_shrimp\": \"🍤\",\n    \"frog\": \"🐸\",\n    \"front-facing_baby_chick\": \"🐥\",\n    \"frowning_face\": \"☹\",\n    \"frowning_face_with_open_mouth\": \"😦\",\n    \"fuel_pump\": \"⛽\",\n    \"full_moon\": \"🌕\",\n    \"full_moon_face\": \"🌝\",\n    \"funeral_urn\": \"⚱\",\n    \"game_die\": \"🎲\",\n    \"garlic\": \"🧄\",\n    \"gear\": \"⚙\",\n    \"gem_stone\": \"💎\",\n    \"genie\": \"🧞\",\n    \"ghost\": \"👻\",\n    \"ginger_root\": \"🫚\",\n    \"giraffe\": \"🦒\",\n    \"girl\": \"👧\",\n    \"girl_dark_skin_tone\": \"👧🏿\",\n    \"girl_light_skin_tone\": \"👧🏻\",\n    \"girl_medium-dark_skin_tone\": \"👧🏾\",\n    \"girl_medium-light_skin_tone\": \"👧🏼\",\n    \"girl_medium_skin_tone\": \"👧🏽\",\n    \"glass_of_milk\": \"🥛\",\n    \"glasses\": \"👓\",\n    \"globe_showing_Americas\": \"🌎\",\n    \"globe_showing_Asia-Australia\": \"🌏\",\n    \"globe_showing_Europe-Africa\": \"🌍\",\n    \"globe_with_meridians\": \"🌐\",\n    \"gloves\": \"🧤\",\n    \"glowing_star\": \"🌟\",\n    \"goal_net\": \"🥅\",\n    \"goat\": \"🐐\",\n    \"goblin\": \"👺\",\n    \"goggles\": \"🥽\",\n    \"goose\": \"🪿\",\n    \"gorilla\": \"🦍\",\n    \"graduation_cap\": \"🎓\",\n    \"grapes\": \"🍇\",\n    \"green_apple\": \"🍏\",\n    \"green_book\": \"📗\",\n    \"green_circle\": \"🟢\",\n    \"green_heart\": \"💚\",\n    \"green_salad\": \"🥗\",\n    \"green_square\": \"🟩\",\n    \"grey_heart\": \"🩶\",\n    \"grimacing_face\": \"😬\",\n    \"grinning_cat\": \"😺\",\n    \"grinning_cat_with_smiling_eyes\": \"😸\",\n    \"grinning_face\": \"😀\",\n    \"grinning_face_with_big_eyes\": \"😃\",\n    \"grinning_face_with_smiling_eyes\": \"😄\",\n    \"grinning_face_with_sweat\": \"😅\",\n    \"grinning_squinting_face\": \"😆\",\n    \"growing_heart\": \"💗\",\n    \"guard\": \"💂\",\n    \"guard_dark_skin_tone\": \"💂🏿\",\n    \"guard_light_skin_tone\": \"💂🏻\",\n    \"guard_medium-dark_skin_tone\": \"💂🏾\",\n    \"guard_medium-light_skin_tone\": \"💂🏼\",\n    \"guard_medium_skin_tone\": \"💂🏽\",\n    \"guide_dog\": \"🦮\",\n    \"guitar\": \"🎸\",\n    \"hair_pick\": \"🪮\",\n    \"hamburger\": \"🍔\",\n    \"hammer\": \"🔨\",\n    \"hammer_and_pick\": \"⚒\",\n    \"hammer_and_wrench\": \"🛠\",\n    \"hamsa\": \"🪬\",\n    \"hamster\": \"🐹\",\n    \"hand_with_fingers_splayed\": \"🖐\",\n    \"hand_with_fingers_splayed_dark_skin_tone\": \"🖐🏿\",\n    \"hand_with_fingers_splayed_light_skin_tone\": \"🖐🏻\",\n    \"hand_with_fingers_splayed_medium-dark_skin_tone\": \"🖐🏾\",\n    \"hand_with_fingers_splayed_medium-light_skin_tone\": \"🖐🏼\",\n    \"hand_with_fingers_splayed_medium_skin_tone\": \"🖐🏽\",\n    \"hand_with_index_finger_and_thumb_crossed\": \"🫰\",\n    \"hand_with_index_finger_and_thumb_crossed_dark_skin_tone\": \"🫰🏿\",\n    \"hand_with_index_finger_and_thumb_crossed_light_skin_tone\": \"🫰🏻\",\n    \"hand_with_index_finger_and_thumb_crossed_medium-dark_skin_tone\": \"🫰🏾\",\n    \"hand_with_index_finger_and_thumb_crossed_medium-light_skin_tone\": \"🫰🏼\",\n    \"hand_with_index_finger_and_thumb_crossed_medium_skin_tone\": \"🫰🏽\",\n    \"handbag\": \"👜\",\n    \"handshake\": \"🤝\",\n    \"handshake_dark_skin_tone\": \"🤝🏿\",\n    \"handshake_dark_skin_tone_light_skin_tone\": \"🫱🏿‍🫲🏻\",\n    \"handshake_dark_skin_tone_medium-dark_skin_tone\": \"🫱🏿‍🫲🏾\",\n    \"handshake_dark_skin_tone_medium-light_skin_tone\": \"🫱🏿‍🫲🏼\",\n    \"handshake_dark_skin_tone_medium_skin_tone\": \"🫱🏿‍🫲🏽\",\n    \"handshake_light_skin_tone\": \"🤝🏻\",\n    \"handshake_light_skin_tone_dark_skin_tone\": \"🫱🏻‍🫲🏿\",\n    \"handshake_light_skin_tone_medium-dark_skin_tone\": \"🫱🏻‍🫲🏾\",\n    \"handshake_light_skin_tone_medium-light_skin_tone\": \"🫱🏻‍🫲🏼\",\n    \"handshake_light_skin_tone_medium_skin_tone\": \"🫱🏻‍🫲🏽\",\n    \"handshake_medium-dark_skin_tone\": \"🤝🏾\",\n    \"handshake_medium-dark_skin_tone_dark_skin_tone\": \"🫱🏾‍🫲🏿\",\n    \"handshake_medium-dark_skin_tone_light_skin_tone\": \"🫱🏾‍🫲🏻\",\n    \"handshake_medium-dark_skin_tone_medium-light_skin_tone\": \"🫱🏾‍🫲🏼\",\n    \"handshake_medium-dark_skin_tone_medium_skin_tone\": \"🫱🏾‍🫲🏽\",\n    \"handshake_medium-light_skin_tone\": \"🤝🏼\",\n    \"handshake_medium-light_skin_tone_dark_skin_tone\": \"🫱🏼‍🫲🏿\",\n    \"handshake_medium-light_skin_tone_light_skin_tone\": \"🫱🏼‍🫲🏻\",\n    \"handshake_medium-light_skin_tone_medium-dark_skin_tone\": \"🫱🏼‍🫲🏾\",\n    \"handshake_medium-light_skin_tone_medium_skin_tone\": \"🫱🏼‍🫲🏽\",\n    \"handshake_medium_skin_tone\": \"🤝🏽\",\n    \"handshake_medium_skin_tone_dark_skin_tone\": \"🫱🏽‍🫲🏿\",\n    \"handshake_medium_skin_tone_light_skin_tone\": \"🫱🏽‍🫲🏻\",\n    \"handshake_medium_skin_tone_medium-dark_skin_tone\": \"🫱🏽‍🫲🏾\",\n    \"handshake_medium_skin_tone_medium-light_skin_tone\": \"🫱🏽‍🫲🏼\",\n    \"hatching_chick\": \"🐣\",\n    \"headphone\": \"🎧\",\n    \"headstone\": \"🪦\",\n    \"health_worker\": \"🧑‍⚕\",\n    \"health_worker_dark_skin_tone\": \"🧑🏿‍⚕\",\n    \"health_worker_light_skin_tone\": \"🧑🏻‍⚕\",\n    \"health_worker_medium-dark_skin_tone\": \"🧑🏾‍⚕\",\n    \"health_worker_medium-light_skin_tone\": \"🧑🏼‍⚕\",\n    \"health_worker_medium_skin_tone\": \"🧑🏽‍⚕\",\n    \"hear-no-evil_monkey\": \"🙉\",\n    \"heart_decoration\": \"💟\",\n    \"heart_exclamation\": \"❣\",\n    \"heart_hands\": \"🫶\",\n    \"heart_hands_dark_skin_tone\": \"🫶🏿\",\n    \"heart_hands_light_skin_tone\": \"🫶🏻\",\n    \"heart_hands_medium-dark_skin_tone\": \"🫶🏾\",\n    \"heart_hands_medium-light_skin_tone\": \"🫶🏼\",\n    \"heart_hands_medium_skin_tone\": \"🫶🏽\",\n    \"heart_on_fire\": \"❤‍🔥\",\n    \"heart_suit\": \"♥\",\n    \"heart_with_arrow\": \"💘\",\n    \"heart_with_ribbon\": \"💝\",\n    \"heavy_dollar_sign\": \"💲\",\n    \"heavy_equals_sign\": \"🟰\",\n    \"hedgehog\": \"🦔\",\n    \"helicopter\": \"🚁\",\n    \"herb\": \"🌿\",\n    \"hibiscus\": \"🌺\",\n    \"high-heeled_shoe\": \"👠\",\n    \"high-speed_train\": \"🚄\",\n    \"high_voltage\": \"⚡\",\n    \"hiking_boot\": \"🥾\",\n    \"hindu_temple\": \"🛕\",\n    \"hippopotamus\": \"🦛\",\n    \"hole\": \"🕳\",\n    \"hollow_red_circle\": \"⭕\",\n    \"honey_pot\": \"🍯\",\n    \"honeybee\": \"🐝\",\n    \"hook\": \"🪝\",\n    \"horizontal_traffic_light\": \"🚥\",\n    \"horse\": \"🐎\",\n    \"horse_face\": \"🐴\",\n    \"horse_racing\": \"🏇\",\n    \"horse_racing_dark_skin_tone\": \"🏇🏿\",\n    \"horse_racing_light_skin_tone\": \"🏇🏻\",\n    \"horse_racing_medium-dark_skin_tone\": \"🏇🏾\",\n    \"horse_racing_medium-light_skin_tone\": \"🏇🏼\",\n    \"horse_racing_medium_skin_tone\": \"🏇🏽\",\n    \"hospital\": \"🏥\",\n    \"hot_beverage\": \"☕\",\n    \"hot_dog\": \"🌭\",\n    \"hot_face\": \"🥵\",\n    \"hot_pepper\": \"🌶\",\n    \"hot_springs\": \"♨\",\n    \"hotel\": \"🏨\",\n    \"hourglass_done\": \"⌛\",\n    \"hourglass_not_done\": \"⏳\",\n    \"house\": \"🏠\",\n    \"house_with_garden\": \"🏡\",\n    \"houses\": \"🏘\",\n    \"hundred_points\": \"💯\",\n    \"hushed_face\": \"😯\",\n    \"hut\": \"🛖\",\n    \"hyacinth\": \"🪻\",\n    \"ice\": \"🧊\",\n    \"ice_cream\": \"🍨\",\n    \"ice_hockey\": \"🏒\",\n    \"ice_skate\": \"⛸\",\n    \"identification_card\": \"🪪\",\n    \"inbox_tray\": \"📥\",\n    \"incoming_envelope\": \"📨\",\n    \"index_pointing_at_the_viewer\": \"🫵\",\n    \"index_pointing_at_the_viewer_dark_skin_tone\": \"🫵🏿\",\n    \"index_pointing_at_the_viewer_light_skin_tone\": \"🫵🏻\",\n    \"index_pointing_at_the_viewer_medium-dark_skin_tone\": \"🫵🏾\",\n    \"index_pointing_at_the_viewer_medium-light_skin_tone\": \"🫵🏼\",\n    \"index_pointing_at_the_viewer_medium_skin_tone\": \"🫵🏽\",\n    \"index_pointing_up\": \"☝\",\n    \"index_pointing_up_dark_skin_tone\": \"☝🏿\",\n    \"index_pointing_up_light_skin_tone\": \"☝🏻\",\n    \"index_pointing_up_medium-dark_skin_tone\": \"☝🏾\",\n    \"index_pointing_up_medium-light_skin_tone\": \"☝🏼\",\n    \"index_pointing_up_medium_skin_tone\": \"☝🏽\",\n    \"infinity\": \"♾\",\n    \"information\": \"ℹ\",\n    \"input_latin_letters\": \"🔤\",\n    \"input_latin_lowercase\": \"🔡\",\n    \"input_latin_uppercase\": \"🔠\",\n    \"input_numbers\": \"🔢\",\n    \"input_symbols\": \"🔣\",\n    \"jack-o-lantern\": \"🎃\",\n    \"jar\": \"🫙\",\n    \"jeans\": \"👖\",\n    \"jellyfish\": \"🪼\",\n    \"joker\": \"🃏\",\n    \"joystick\": \"🕹\",\n    \"judge\": \"🧑‍⚖\",\n    \"judge_dark_skin_tone\": \"🧑🏿‍⚖\",\n    \"judge_light_skin_tone\": \"🧑🏻‍⚖\",\n    \"judge_medium-dark_skin_tone\": \"🧑🏾‍⚖\",\n    \"judge_medium-light_skin_tone\": \"🧑🏼‍⚖\",\n    \"judge_medium_skin_tone\": \"🧑🏽‍⚖\",\n    \"kaaba\": \"🕋\",\n    \"kangaroo\": \"🦘\",\n    \"key\": \"🔑\",\n    \"keyboard\": \"⌨\",\n    \"keycap_#\": \"#⃣\",\n    \"keycap_*\": \"*⃣\",\n    \"keycap_0\": \"0⃣\",\n    \"keycap_1\": \"1⃣\",\n    \"keycap_10\": \"🔟\",\n    \"keycap_2\": \"2⃣\",\n    \"keycap_3\": \"3⃣\",\n    \"keycap_4\": \"4⃣\",\n    \"keycap_5\": \"5⃣\",\n    \"keycap_6\": \"6⃣\",\n    \"keycap_7\": \"7⃣\",\n    \"keycap_8\": \"8⃣\",\n    \"keycap_9\": \"9⃣\",\n    \"khanda\": \"🪯\",\n    \"kick_scooter\": \"🛴\",\n    \"kimono\": \"👘\",\n    \"kiss\": \"💏\",\n    \"kiss_dark_skin_tone\": \"💏🏿\",\n    \"kiss_light_skin_tone\": \"💏🏻\",\n    \"kiss_man_man\": \"👨‍❤‍💋‍👨\",\n    \"kiss_man_man_dark_skin_tone\": \"👨🏿‍❤‍💋‍👨🏿\",\n    \"kiss_man_man_dark_skin_tone_light_skin_tone\": \"👨🏿‍❤‍💋‍👨🏻\",\n    \"kiss_man_man_dark_skin_tone_medium-dark_skin_tone\": \"👨🏿‍❤‍💋‍👨🏾\",\n    \"kiss_man_man_dark_skin_tone_medium-light_skin_tone\": \"👨🏿‍❤‍💋‍👨🏼\",\n    \"kiss_man_man_dark_skin_tone_medium_skin_tone\": \"👨🏿‍❤‍💋‍👨🏽\",\n    \"kiss_man_man_light_skin_tone\": \"👨🏻‍❤‍💋‍👨🏻\",\n    \"kiss_man_man_light_skin_tone_dark_skin_tone\": \"👨🏻‍❤‍💋‍👨🏿\",\n    \"kiss_man_man_light_skin_tone_medium-dark_skin_tone\": \"👨🏻‍❤‍💋‍👨🏾\",\n    \"kiss_man_man_light_skin_tone_medium-light_skin_tone\": \"👨🏻‍❤‍💋‍👨🏼\",\n    \"kiss_man_man_light_skin_tone_medium_skin_tone\": \"👨🏻‍❤‍💋‍👨🏽\",\n    \"kiss_man_man_medium-dark_skin_tone\": \"👨🏾‍❤‍💋‍👨🏾\",\n    \"kiss_man_man_medium-dark_skin_tone_dark_skin_tone\": \"👨🏾‍❤‍💋‍👨🏿\",\n    \"kiss_man_man_medium-dark_skin_tone_light_skin_tone\": \"👨🏾‍❤‍💋‍👨🏻\",\n    \"kiss_man_man_medium-dark_skin_tone_medium-light_skin_tone\": \"👨🏾‍❤‍💋‍👨🏼\",\n    \"kiss_man_man_medium-dark_skin_tone_medium_skin_tone\": \"👨🏾‍❤‍💋‍👨🏽\",\n    \"kiss_man_man_medium-light_skin_tone\": \"👨🏼‍❤‍💋‍👨🏼\",\n    \"kiss_man_man_medium-light_skin_tone_dark_skin_tone\": \"👨🏼‍❤‍💋‍👨🏿\",\n    \"kiss_man_man_medium-light_skin_tone_light_skin_tone\": \"👨🏼‍❤‍💋‍👨🏻\",\n    \"kiss_man_man_medium-light_skin_tone_medium-dark_skin_tone\": \"👨🏼‍❤‍💋‍👨🏾\",\n    \"kiss_man_man_medium-light_skin_tone_medium_skin_tone\": \"👨🏼‍❤‍💋‍👨🏽\",\n    \"kiss_man_man_medium_skin_tone\": \"👨🏽‍❤‍💋‍👨🏽\",\n    \"kiss_man_man_medium_skin_tone_dark_skin_tone\": \"👨🏽‍❤‍💋‍👨🏿\",\n    \"kiss_man_man_medium_skin_tone_light_skin_tone\": \"👨🏽‍❤‍💋‍👨🏻\",\n    \"kiss_man_man_medium_skin_tone_medium-dark_skin_tone\": \"👨🏽‍❤‍💋‍👨🏾\",\n    \"kiss_man_man_medium_skin_tone_medium-light_skin_tone\": \"👨🏽‍❤‍💋‍👨🏼\",\n    \"kiss_mark\": \"💋\",\n    \"kiss_medium-dark_skin_tone\": \"💏🏾\",\n    \"kiss_medium-light_skin_tone\": \"💏🏼\",\n    \"kiss_medium_skin_tone\": \"💏🏽\",\n    \"kiss_person_person_dark_skin_tone_light_skin_tone\": \"🧑🏿‍❤‍💋‍🧑🏻\",\n    \"kiss_person_person_dark_skin_tone_medium-dark_skin_tone\": \"🧑🏿‍❤‍💋‍🧑🏾\",\n    \"kiss_person_person_dark_skin_tone_medium-light_skin_tone\": \"🧑🏿‍❤‍💋‍🧑🏼\",\n    \"kiss_person_person_dark_skin_tone_medium_skin_tone\": \"🧑🏿‍❤‍💋‍🧑🏽\",\n    \"kiss_person_person_light_skin_tone_dark_skin_tone\": \"🧑🏻‍❤‍💋‍🧑🏿\",\n    \"kiss_person_person_light_skin_tone_medium-dark_skin_tone\": \"🧑🏻‍❤‍💋‍🧑🏾\",\n    \"kiss_person_person_light_skin_tone_medium-light_skin_tone\": \"🧑🏻‍❤‍💋‍🧑🏼\",\n    \"kiss_person_person_light_skin_tone_medium_skin_tone\": \"🧑🏻‍❤‍💋‍🧑🏽\",\n    \"kiss_person_person_medium-dark_skin_tone_dark_skin_tone\": \"🧑🏾‍❤‍💋‍🧑🏿\",\n    \"kiss_person_person_medium-dark_skin_tone_light_skin_tone\": \"🧑🏾‍❤‍💋‍🧑🏻\",\n    (\n        \"kiss_person_person_medium-dark_skin_tone_medium-light_skin_tone\"\n    ): \"🧑🏾‍❤‍💋‍🧑🏼\",\n    \"kiss_person_person_medium-dark_skin_tone_medium_skin_tone\": \"🧑🏾‍❤‍💋‍🧑🏽\",\n    \"kiss_person_person_medium-light_skin_tone_dark_skin_tone\": \"🧑🏼‍❤‍💋‍🧑🏿\",\n    \"kiss_person_person_medium-light_skin_tone_light_skin_tone\": \"🧑🏼‍❤‍💋‍🧑🏻\",\n    (\n        \"kiss_person_person_medium-light_skin_tone_medium-dark_skin_tone\"\n    ): \"🧑🏼‍❤‍💋‍🧑🏾\",\n    (\n        \"kiss_person_person_medium-light_skin_tone_medium_skin_tone\"\n    ): \"🧑🏼‍❤‍💋‍🧑🏽\",\n    \"kiss_person_person_medium_skin_tone_dark_skin_tone\": \"🧑🏽‍❤‍💋‍🧑🏿\",\n    \"kiss_person_person_medium_skin_tone_light_skin_tone\": \"🧑🏽‍❤‍💋‍🧑🏻\",\n    \"kiss_person_person_medium_skin_tone_medium-dark_skin_tone\": \"🧑🏽‍❤‍💋‍🧑🏾\",\n    (\n        \"kiss_person_person_medium_skin_tone_medium-light_skin_tone\"\n    ): \"🧑🏽‍❤‍💋‍🧑🏼\",\n    \"kiss_woman_man\": \"👩‍❤‍💋‍👨\",\n    \"kiss_woman_man_dark_skin_tone\": \"👩🏿‍❤‍💋‍👨🏿\",\n    \"kiss_woman_man_dark_skin_tone_light_skin_tone\": \"👩🏿‍❤‍💋‍👨🏻\",\n    \"kiss_woman_man_dark_skin_tone_medium-dark_skin_tone\": \"👩🏿‍❤‍💋‍👨🏾\",\n    \"kiss_woman_man_dark_skin_tone_medium-light_skin_tone\": \"👩🏿‍❤‍💋‍👨🏼\",\n    \"kiss_woman_man_dark_skin_tone_medium_skin_tone\": \"👩🏿‍❤‍💋‍👨🏽\",\n    \"kiss_woman_man_light_skin_tone\": \"👩🏻‍❤‍💋‍👨🏻\",\n    \"kiss_woman_man_light_skin_tone_dark_skin_tone\": \"👩🏻‍❤‍💋‍👨🏿\",\n    \"kiss_woman_man_light_skin_tone_medium-dark_skin_tone\": \"👩🏻‍❤‍💋‍👨🏾\",\n    \"kiss_woman_man_light_skin_tone_medium-light_skin_tone\": \"👩🏻‍❤‍💋‍👨🏼\",\n    \"kiss_woman_man_light_skin_tone_medium_skin_tone\": \"👩🏻‍❤‍💋‍👨🏽\",\n    \"kiss_woman_man_medium-dark_skin_tone\": \"👩🏾‍❤‍💋‍👨🏾\",\n    \"kiss_woman_man_medium-dark_skin_tone_dark_skin_tone\": \"👩🏾‍❤‍💋‍👨🏿\",\n    \"kiss_woman_man_medium-dark_skin_tone_light_skin_tone\": \"👩🏾‍❤‍💋‍👨🏻\",\n    (\n        \"kiss_woman_man_medium-dark_skin_tone_medium-light_skin_tone\"\n    ): \"👩🏾‍❤‍💋‍👨🏼\",\n    \"kiss_woman_man_medium-dark_skin_tone_medium_skin_tone\": \"👩🏾‍❤‍💋‍👨🏽\",\n    \"kiss_woman_man_medium-light_skin_tone\": \"👩🏼‍❤‍💋‍👨🏼\",\n    \"kiss_woman_man_medium-light_skin_tone_dark_skin_tone\": \"👩🏼‍❤‍💋‍👨🏿\",\n    \"kiss_woman_man_medium-light_skin_tone_light_skin_tone\": \"👩🏼‍❤‍💋‍👨🏻\",\n    (\n        \"kiss_woman_man_medium-light_skin_tone_medium-dark_skin_tone\"\n    ): \"👩🏼‍❤‍💋‍👨🏾\",\n    \"kiss_woman_man_medium-light_skin_tone_medium_skin_tone\": \"👩🏼‍❤‍💋‍👨🏽\",\n    \"kiss_woman_man_medium_skin_tone\": \"👩🏽‍❤‍💋‍👨🏽\",\n    \"kiss_woman_man_medium_skin_tone_dark_skin_tone\": \"👩🏽‍❤‍💋‍👨🏿\",\n    \"kiss_woman_man_medium_skin_tone_light_skin_tone\": \"👩🏽‍❤‍💋‍👨🏻\",\n    \"kiss_woman_man_medium_skin_tone_medium-dark_skin_tone\": \"👩🏽‍❤‍💋‍👨🏾\",\n    \"kiss_woman_man_medium_skin_tone_medium-light_skin_tone\": \"👩🏽‍❤‍💋‍👨🏼\",\n    \"kiss_woman_woman\": \"👩‍❤‍💋‍👩\",\n    \"kiss_woman_woman_dark_skin_tone\": \"👩🏿‍❤‍💋‍👩🏿\",\n    \"kiss_woman_woman_dark_skin_tone_light_skin_tone\": \"👩🏿‍❤‍💋‍👩🏻\",\n    \"kiss_woman_woman_dark_skin_tone_medium-dark_skin_tone\": \"👩🏿‍❤‍💋‍👩🏾\",\n    \"kiss_woman_woman_dark_skin_tone_medium-light_skin_tone\": \"👩🏿‍❤‍💋‍👩🏼\",\n    \"kiss_woman_woman_dark_skin_tone_medium_skin_tone\": \"👩🏿‍❤‍💋‍👩🏽\",\n    \"kiss_woman_woman_light_skin_tone\": \"👩🏻‍❤‍💋‍👩🏻\",\n    \"kiss_woman_woman_light_skin_tone_dark_skin_tone\": \"👩🏻‍❤‍💋‍👩🏿\",\n    \"kiss_woman_woman_light_skin_tone_medium-dark_skin_tone\": \"👩🏻‍❤‍💋‍👩🏾\",\n    \"kiss_woman_woman_light_skin_tone_medium-light_skin_tone\": \"👩🏻‍❤‍💋‍👩🏼\",\n    \"kiss_woman_woman_light_skin_tone_medium_skin_tone\": \"👩🏻‍❤‍💋‍👩🏽\",\n    \"kiss_woman_woman_medium-dark_skin_tone\": \"👩🏾‍❤‍💋‍👩🏾\",\n    \"kiss_woman_woman_medium-dark_skin_tone_dark_skin_tone\": \"👩🏾‍❤‍💋‍👩🏿\",\n    \"kiss_woman_woman_medium-dark_skin_tone_light_skin_tone\": \"👩🏾‍❤‍💋‍👩🏻\",\n    (\n        \"kiss_woman_woman_medium-dark_skin_tone_medium-light_skin_tone\"\n    ): \"👩🏾‍❤‍💋‍👩🏼\",\n    \"kiss_woman_woman_medium-dark_skin_tone_medium_skin_tone\": \"👩🏾‍❤‍💋‍👩🏽\",\n    \"kiss_woman_woman_medium-light_skin_tone\": \"👩🏼‍❤‍💋‍👩🏼\",\n    \"kiss_woman_woman_medium-light_skin_tone_dark_skin_tone\": \"👩🏼‍❤‍💋‍👩🏿\",\n    \"kiss_woman_woman_medium-light_skin_tone_light_skin_tone\": \"👩🏼‍❤‍💋‍👩🏻\",\n    (\n        \"kiss_woman_woman_medium-light_skin_tone_medium-dark_skin_tone\"\n    ): \"👩🏼‍❤‍💋‍👩🏾\",\n    \"kiss_woman_woman_medium-light_skin_tone_medium_skin_tone\": \"👩🏼‍❤‍💋‍👩🏽\",\n    \"kiss_woman_woman_medium_skin_tone\": \"👩🏽‍❤‍💋‍👩🏽\",\n    \"kiss_woman_woman_medium_skin_tone_dark_skin_tone\": \"👩🏽‍❤‍💋‍👩🏿\",\n    \"kiss_woman_woman_medium_skin_tone_light_skin_tone\": \"👩🏽‍❤‍💋‍👩🏻\",\n    \"kiss_woman_woman_medium_skin_tone_medium-dark_skin_tone\": \"👩🏽‍❤‍💋‍👩🏾\",\n    \"kiss_woman_woman_medium_skin_tone_medium-light_skin_tone\": \"👩🏽‍❤‍💋‍👩🏼\",\n    \"kissing_cat\": \"😽\",\n    \"kissing_face\": \"😗\",\n    \"kissing_face_with_closed_eyes\": \"😚\",\n    \"kissing_face_with_smiling_eyes\": \"😙\",\n    \"kitchen_knife\": \"🔪\",\n    \"kite\": \"🪁\",\n    \"kiwi_fruit\": \"🥝\",\n    \"knot\": \"🪢\",\n    \"koala\": \"🐨\",\n    \"lab_coat\": \"🥼\",\n    \"label\": \"🏷\",\n    \"lacrosse\": \"🥍\",\n    \"ladder\": \"🪜\",\n    \"lady_beetle\": \"🐞\",\n    \"laptop\": \"💻\",\n    \"large_blue_diamond\": \"🔷\",\n    \"large_orange_diamond\": \"🔶\",\n    \"last_quarter_moon\": \"🌗\",\n    \"last_quarter_moon_face\": \"🌜\",\n    \"last_track_button\": \"⏮\",\n    \"latin_cross\": \"✝\",\n    \"leaf_fluttering_in_wind\": \"🍃\",\n    \"leafy_green\": \"🥬\",\n    \"ledger\": \"📒\",\n    \"left-facing_fist\": \"🤛\",\n    \"left-facing_fist_dark_skin_tone\": \"🤛🏿\",\n    \"left-facing_fist_light_skin_tone\": \"🤛🏻\",\n    \"left-facing_fist_medium-dark_skin_tone\": \"🤛🏾\",\n    \"left-facing_fist_medium-light_skin_tone\": \"🤛🏼\",\n    \"left-facing_fist_medium_skin_tone\": \"🤛🏽\",\n    \"left-right_arrow\": \"↔\",\n    \"left_arrow\": \"⬅\",\n    \"left_arrow_curving_right\": \"↪\",\n    \"left_luggage\": \"🛅\",\n    \"left_speech_bubble\": \"🗨\",\n    \"leftwards_hand\": \"🫲\",\n    \"leftwards_hand_dark_skin_tone\": \"🫲🏿\",\n    \"leftwards_hand_light_skin_tone\": \"🫲🏻\",\n    \"leftwards_hand_medium-dark_skin_tone\": \"🫲🏾\",\n    \"leftwards_hand_medium-light_skin_tone\": \"🫲🏼\",\n    \"leftwards_hand_medium_skin_tone\": \"🫲🏽\",\n    \"leftwards_pushing_hand\": \"🫷\",\n    \"leftwards_pushing_hand_dark_skin_tone\": \"🫷🏿\",\n    \"leftwards_pushing_hand_light_skin_tone\": \"🫷🏻\",\n    \"leftwards_pushing_hand_medium-dark_skin_tone\": \"🫷🏾\",\n    \"leftwards_pushing_hand_medium-light_skin_tone\": \"🫷🏼\",\n    \"leftwards_pushing_hand_medium_skin_tone\": \"🫷🏽\",\n    \"leg\": \"🦵\",\n    \"leg_dark_skin_tone\": \"🦵🏿\",\n    \"leg_light_skin_tone\": \"🦵🏻\",\n    \"leg_medium-dark_skin_tone\": \"🦵🏾\",\n    \"leg_medium-light_skin_tone\": \"🦵🏼\",\n    \"leg_medium_skin_tone\": \"🦵🏽\",\n    \"lemon\": \"🍋\",\n    \"leopard\": \"🐆\",\n    \"level_slider\": \"🎚\",\n    \"light_blue_heart\": \"🩵\",\n    \"light_bulb\": \"💡\",\n    \"light_rail\": \"🚈\",\n    \"light_skin_tone\": \"🏻\",\n    \"link\": \"🔗\",\n    \"linked_paperclips\": \"🖇\",\n    \"lion\": \"🦁\",\n    \"lipstick\": \"💄\",\n    \"litter_in_bin_sign\": \"🚮\",\n    \"lizard\": \"🦎\",\n    \"llama\": \"🦙\",\n    \"lobster\": \"🦞\",\n    \"locked\": \"🔒\",\n    \"locked_with_key\": \"🔐\",\n    \"locked_with_pen\": \"🔏\",\n    \"locomotive\": \"🚂\",\n    \"lollipop\": \"🍭\",\n    \"long_drum\": \"🪘\",\n    \"lotion_bottle\": \"🧴\",\n    \"lotus\": \"🪷\",\n    \"loudly_crying_face\": \"😭\",\n    \"loudspeaker\": \"📢\",\n    \"love-you_gesture\": \"🤟\",\n    \"love-you_gesture_dark_skin_tone\": \"🤟🏿\",\n    \"love-you_gesture_light_skin_tone\": \"🤟🏻\",\n    \"love-you_gesture_medium-dark_skin_tone\": \"🤟🏾\",\n    \"love-you_gesture_medium-light_skin_tone\": \"🤟🏼\",\n    \"love-you_gesture_medium_skin_tone\": \"🤟🏽\",\n    \"love_hotel\": \"🏩\",\n    \"love_letter\": \"💌\",\n    \"low_battery\": \"🪫\",\n    \"luggage\": \"🧳\",\n    \"lungs\": \"🫁\",\n    \"lying_face\": \"🤥\",\n    \"mage\": \"🧙\",\n    \"mage_dark_skin_tone\": \"🧙🏿\",\n    \"mage_light_skin_tone\": \"🧙🏻\",\n    \"mage_medium-dark_skin_tone\": \"🧙🏾\",\n    \"mage_medium-light_skin_tone\": \"🧙🏼\",\n    \"mage_medium_skin_tone\": \"🧙🏽\",\n    \"magic_wand\": \"🪄\",\n    \"magnet\": \"🧲\",\n    \"magnifying_glass_tilted_left\": \"🔍\",\n    \"magnifying_glass_tilted_right\": \"🔎\",\n    \"mahjong_red_dragon\": \"🀄\",\n    \"male_sign\": \"♂\",\n    \"mammoth\": \"🦣\",\n    \"man\": \"👨\",\n    \"man_artist\": \"👨‍🎨\",\n    \"man_artist_dark_skin_tone\": \"👨🏿‍🎨\",\n    \"man_artist_light_skin_tone\": \"👨🏻‍🎨\",\n    \"man_artist_medium-dark_skin_tone\": \"👨🏾‍🎨\",\n    \"man_artist_medium-light_skin_tone\": \"👨🏼‍🎨\",\n    \"man_artist_medium_skin_tone\": \"👨🏽‍🎨\",\n    \"man_astronaut\": \"👨‍🚀\",\n    \"man_astronaut_dark_skin_tone\": \"👨🏿‍🚀\",\n    \"man_astronaut_light_skin_tone\": \"👨🏻‍🚀\",\n    \"man_astronaut_medium-dark_skin_tone\": \"👨🏾‍🚀\",\n    \"man_astronaut_medium-light_skin_tone\": \"👨🏼‍🚀\",\n    \"man_astronaut_medium_skin_tone\": \"👨🏽‍🚀\",\n    \"man_bald\": \"👨‍🦲\",\n    \"man_beard\": \"🧔‍♂\",\n    \"man_biking\": \"🚴‍♂\",\n    \"man_biking_dark_skin_tone\": \"🚴🏿‍♂\",\n    \"man_biking_light_skin_tone\": \"🚴🏻‍♂\",\n    \"man_biking_medium-dark_skin_tone\": \"🚴🏾‍♂\",\n    \"man_biking_medium-light_skin_tone\": \"🚴🏼‍♂\",\n    \"man_biking_medium_skin_tone\": \"🚴🏽‍♂\",\n    \"man_blond_hair\": \"👱‍♂\",\n    \"man_bouncing_ball\": \"⛹‍♂\",\n    \"man_bouncing_ball_dark_skin_tone\": \"⛹🏿‍♂\",\n    \"man_bouncing_ball_light_skin_tone\": \"⛹🏻‍♂\",\n    \"man_bouncing_ball_medium-dark_skin_tone\": \"⛹🏾‍♂\",\n    \"man_bouncing_ball_medium-light_skin_tone\": \"⛹🏼‍♂\",\n    \"man_bouncing_ball_medium_skin_tone\": \"⛹🏽‍♂\",\n    \"man_bowing\": \"🙇‍♂\",\n    \"man_bowing_dark_skin_tone\": \"🙇🏿‍♂\",\n    \"man_bowing_light_skin_tone\": \"🙇🏻‍♂\",\n    \"man_bowing_medium-dark_skin_tone\": \"🙇🏾‍♂\",\n    \"man_bowing_medium-light_skin_tone\": \"🙇🏼‍♂\",\n    \"man_bowing_medium_skin_tone\": \"🙇🏽‍♂\",\n    \"man_cartwheeling\": \"🤸‍♂\",\n    \"man_cartwheeling_dark_skin_tone\": \"🤸🏿‍♂\",\n    \"man_cartwheeling_light_skin_tone\": \"🤸🏻‍♂\",\n    \"man_cartwheeling_medium-dark_skin_tone\": \"🤸🏾‍♂\",\n    \"man_cartwheeling_medium-light_skin_tone\": \"🤸🏼‍♂\",\n    \"man_cartwheeling_medium_skin_tone\": \"🤸🏽‍♂\",\n    \"man_climbing\": \"🧗‍♂\",\n    \"man_climbing_dark_skin_tone\": \"🧗🏿‍♂\",\n    \"man_climbing_light_skin_tone\": \"🧗🏻‍♂\",\n    \"man_climbing_medium-dark_skin_tone\": \"🧗🏾‍♂\",\n    \"man_climbing_medium-light_skin_tone\": \"🧗🏼‍♂\",\n    \"man_climbing_medium_skin_tone\": \"🧗🏽‍♂\",\n    \"man_construction_worker\": \"👷‍♂\",\n    \"man_construction_worker_dark_skin_tone\": \"👷🏿‍♂\",\n    \"man_construction_worker_light_skin_tone\": \"👷🏻‍♂\",\n    \"man_construction_worker_medium-dark_skin_tone\": \"👷🏾‍♂\",\n    \"man_construction_worker_medium-light_skin_tone\": \"👷🏼‍♂\",\n    \"man_construction_worker_medium_skin_tone\": \"👷🏽‍♂\",\n    \"man_cook\": \"👨‍🍳\",\n    \"man_cook_dark_skin_tone\": \"👨🏿‍🍳\",\n    \"man_cook_light_skin_tone\": \"👨🏻‍🍳\",\n    \"man_cook_medium-dark_skin_tone\": \"👨🏾‍🍳\",\n    \"man_cook_medium-light_skin_tone\": \"👨🏼‍🍳\",\n    \"man_cook_medium_skin_tone\": \"👨🏽‍🍳\",\n    \"man_curly_hair\": \"👨‍🦱\",\n    \"man_dancing\": \"🕺\",\n    \"man_dancing_dark_skin_tone\": \"🕺🏿\",\n    \"man_dancing_light_skin_tone\": \"🕺🏻\",\n    \"man_dancing_medium-dark_skin_tone\": \"🕺🏾\",\n    \"man_dancing_medium-light_skin_tone\": \"🕺🏼\",\n    \"man_dancing_medium_skin_tone\": \"🕺🏽\",\n    \"man_dark_skin_tone\": \"👨🏿\",\n    \"man_dark_skin_tone_bald\": \"👨🏿‍🦲\",\n    \"man_dark_skin_tone_beard\": \"🧔🏿‍♂\",\n    \"man_dark_skin_tone_blond_hair\": \"👱🏿‍♂\",\n    \"man_dark_skin_tone_curly_hair\": \"👨🏿‍🦱\",\n    \"man_dark_skin_tone_red_hair\": \"👨🏿‍🦰\",\n    \"man_dark_skin_tone_white_hair\": \"👨🏿‍🦳\",\n    \"man_detective\": \"🕵‍♂\",\n    \"man_detective_dark_skin_tone\": \"🕵🏿‍♂\",\n    \"man_detective_light_skin_tone\": \"🕵🏻‍♂\",\n    \"man_detective_medium-dark_skin_tone\": \"🕵🏾‍♂\",\n    \"man_detective_medium-light_skin_tone\": \"🕵🏼‍♂\",\n    \"man_detective_medium_skin_tone\": \"🕵🏽‍♂\",\n    \"man_elf\": \"🧝‍♂\",\n    \"man_elf_dark_skin_tone\": \"🧝🏿‍♂\",\n    \"man_elf_light_skin_tone\": \"🧝🏻‍♂\",\n    \"man_elf_medium-dark_skin_tone\": \"🧝🏾‍♂\",\n    \"man_elf_medium-light_skin_tone\": \"🧝🏼‍♂\",\n    \"man_elf_medium_skin_tone\": \"🧝🏽‍♂\",\n    \"man_facepalming\": \"🤦‍♂\",\n    \"man_facepalming_dark_skin_tone\": \"🤦🏿‍♂\",\n    \"man_facepalming_light_skin_tone\": \"🤦🏻‍♂\",\n    \"man_facepalming_medium-dark_skin_tone\": \"🤦🏾‍♂\",\n    \"man_facepalming_medium-light_skin_tone\": \"🤦🏼‍♂\",\n    \"man_facepalming_medium_skin_tone\": \"🤦🏽‍♂\",\n    \"man_factory_worker\": \"👨‍🏭\",\n    \"man_factory_worker_dark_skin_tone\": \"👨🏿‍🏭\",\n    \"man_factory_worker_light_skin_tone\": \"👨🏻‍🏭\",\n    \"man_factory_worker_medium-dark_skin_tone\": \"👨🏾‍🏭\",\n    \"man_factory_worker_medium-light_skin_tone\": \"👨🏼‍🏭\",\n    \"man_factory_worker_medium_skin_tone\": \"👨🏽‍🏭\",\n    \"man_fairy\": \"🧚‍♂\",\n    \"man_fairy_dark_skin_tone\": \"🧚🏿‍♂\",\n    \"man_fairy_light_skin_tone\": \"🧚🏻‍♂\",\n    \"man_fairy_medium-dark_skin_tone\": \"🧚🏾‍♂\",\n    \"man_fairy_medium-light_skin_tone\": \"🧚🏼‍♂\",\n    \"man_fairy_medium_skin_tone\": \"🧚🏽‍♂\",\n    \"man_farmer\": \"👨‍🌾\",\n    \"man_farmer_dark_skin_tone\": \"👨🏿‍🌾\",\n    \"man_farmer_light_skin_tone\": \"👨🏻‍🌾\",\n    \"man_farmer_medium-dark_skin_tone\": \"👨🏾‍🌾\",\n    \"man_farmer_medium-light_skin_tone\": \"👨🏼‍🌾\",\n    \"man_farmer_medium_skin_tone\": \"👨🏽‍🌾\",\n    \"man_feeding_baby\": \"👨‍🍼\",\n    \"man_feeding_baby_dark_skin_tone\": \"👨🏿‍🍼\",\n    \"man_feeding_baby_light_skin_tone\": \"👨🏻‍🍼\",\n    \"man_feeding_baby_medium-dark_skin_tone\": \"👨🏾‍🍼\",\n    \"man_feeding_baby_medium-light_skin_tone\": \"👨🏼‍🍼\",\n    \"man_feeding_baby_medium_skin_tone\": \"👨🏽‍🍼\",\n    \"man_firefighter\": \"👨‍🚒\",\n    \"man_firefighter_dark_skin_tone\": \"👨🏿‍🚒\",\n    \"man_firefighter_light_skin_tone\": \"👨🏻‍🚒\",\n    \"man_firefighter_medium-dark_skin_tone\": \"👨🏾‍🚒\",\n    \"man_firefighter_medium-light_skin_tone\": \"👨🏼‍🚒\",\n    \"man_firefighter_medium_skin_tone\": \"👨🏽‍🚒\",\n    \"man_frowning\": \"🙍‍♂\",\n    \"man_frowning_dark_skin_tone\": \"🙍🏿‍♂\",\n    \"man_frowning_light_skin_tone\": \"🙍🏻‍♂\",\n    \"man_frowning_medium-dark_skin_tone\": \"🙍🏾‍♂\",\n    \"man_frowning_medium-light_skin_tone\": \"🙍🏼‍♂\",\n    \"man_frowning_medium_skin_tone\": \"🙍🏽‍♂\",\n    \"man_genie\": \"🧞‍♂\",\n    \"man_gesturing_NO\": \"🙅‍♂\",\n    \"man_gesturing_NO_dark_skin_tone\": \"🙅🏿‍♂\",\n    \"man_gesturing_NO_light_skin_tone\": \"🙅🏻‍♂\",\n    \"man_gesturing_NO_medium-dark_skin_tone\": \"🙅🏾‍♂\",\n    \"man_gesturing_NO_medium-light_skin_tone\": \"🙅🏼‍♂\",\n    \"man_gesturing_NO_medium_skin_tone\": \"🙅🏽‍♂\",\n    \"man_gesturing_OK\": \"🙆‍♂\",\n    \"man_gesturing_OK_dark_skin_tone\": \"🙆🏿‍♂\",\n    \"man_gesturing_OK_light_skin_tone\": \"🙆🏻‍♂\",\n    \"man_gesturing_OK_medium-dark_skin_tone\": \"🙆🏾‍♂\",\n    \"man_gesturing_OK_medium-light_skin_tone\": \"🙆🏼‍♂\",\n    \"man_gesturing_OK_medium_skin_tone\": \"🙆🏽‍♂\",\n    \"man_getting_haircut\": \"💇‍♂\",\n    \"man_getting_haircut_dark_skin_tone\": \"💇🏿‍♂\",\n    \"man_getting_haircut_light_skin_tone\": \"💇🏻‍♂\",\n    \"man_getting_haircut_medium-dark_skin_tone\": \"💇🏾‍♂\",\n    \"man_getting_haircut_medium-light_skin_tone\": \"💇🏼‍♂\",\n    \"man_getting_haircut_medium_skin_tone\": \"💇🏽‍♂\",\n    \"man_getting_massage\": \"💆‍♂\",\n    \"man_getting_massage_dark_skin_tone\": \"💆🏿‍♂\",\n    \"man_getting_massage_light_skin_tone\": \"💆🏻‍♂\",\n    \"man_getting_massage_medium-dark_skin_tone\": \"💆🏾‍♂\",\n    \"man_getting_massage_medium-light_skin_tone\": \"💆🏼‍♂\",\n    \"man_getting_massage_medium_skin_tone\": \"💆🏽‍♂\",\n    \"man_golfing\": \"🏌‍♂\",\n    \"man_golfing_dark_skin_tone\": \"🏌🏿‍♂\",\n    \"man_golfing_light_skin_tone\": \"🏌🏻‍♂\",\n    \"man_golfing_medium-dark_skin_tone\": \"🏌🏾‍♂\",\n    \"man_golfing_medium-light_skin_tone\": \"🏌🏼‍♂\",\n    \"man_golfing_medium_skin_tone\": \"🏌🏽‍♂\",\n    \"man_guard\": \"💂‍♂\",\n    \"man_guard_dark_skin_tone\": \"💂🏿‍♂\",\n    \"man_guard_light_skin_tone\": \"💂🏻‍♂\",\n    \"man_guard_medium-dark_skin_tone\": \"💂🏾‍♂\",\n    \"man_guard_medium-light_skin_tone\": \"💂🏼‍♂\",\n    \"man_guard_medium_skin_tone\": \"💂🏽‍♂\",\n    \"man_health_worker\": \"👨‍⚕\",\n    \"man_health_worker_dark_skin_tone\": \"👨🏿‍⚕\",\n    \"man_health_worker_light_skin_tone\": \"👨🏻‍⚕\",\n    \"man_health_worker_medium-dark_skin_tone\": \"👨🏾‍⚕\",\n    \"man_health_worker_medium-light_skin_tone\": \"👨🏼‍⚕\",\n    \"man_health_worker_medium_skin_tone\": \"👨🏽‍⚕\",\n    \"man_in_lotus_position\": \"🧘‍♂\",\n    \"man_in_lotus_position_dark_skin_tone\": \"🧘🏿‍♂\",\n    \"man_in_lotus_position_light_skin_tone\": \"🧘🏻‍♂\",\n    \"man_in_lotus_position_medium-dark_skin_tone\": \"🧘🏾‍♂\",\n    \"man_in_lotus_position_medium-light_skin_tone\": \"🧘🏼‍♂\",\n    \"man_in_lotus_position_medium_skin_tone\": \"🧘🏽‍♂\",\n    \"man_in_manual_wheelchair\": \"👨‍🦽\",\n    \"man_in_manual_wheelchair_dark_skin_tone\": \"👨🏿‍🦽\",\n    \"man_in_manual_wheelchair_light_skin_tone\": \"👨🏻‍🦽\",\n    \"man_in_manual_wheelchair_medium-dark_skin_tone\": \"👨🏾‍🦽\",\n    \"man_in_manual_wheelchair_medium-light_skin_tone\": \"👨🏼‍🦽\",\n    \"man_in_manual_wheelchair_medium_skin_tone\": \"👨🏽‍🦽\",\n    \"man_in_motorized_wheelchair\": \"👨‍🦼\",\n    \"man_in_motorized_wheelchair_dark_skin_tone\": \"👨🏿‍🦼\",\n    \"man_in_motorized_wheelchair_light_skin_tone\": \"👨🏻‍🦼\",\n    \"man_in_motorized_wheelchair_medium-dark_skin_tone\": \"👨🏾‍🦼\",\n    \"man_in_motorized_wheelchair_medium-light_skin_tone\": \"👨🏼‍🦼\",\n    \"man_in_motorized_wheelchair_medium_skin_tone\": \"👨🏽‍🦼\",\n    \"man_in_steamy_room\": \"🧖‍♂\",\n    \"man_in_steamy_room_dark_skin_tone\": \"🧖🏿‍♂\",\n    \"man_in_steamy_room_light_skin_tone\": \"🧖🏻‍♂\",\n    \"man_in_steamy_room_medium-dark_skin_tone\": \"🧖🏾‍♂\",\n    \"man_in_steamy_room_medium-light_skin_tone\": \"🧖🏼‍♂\",\n    \"man_in_steamy_room_medium_skin_tone\": \"🧖🏽‍♂\",\n    \"man_in_tuxedo\": \"🤵‍♂\",\n    \"man_in_tuxedo_dark_skin_tone\": \"🤵🏿‍♂\",\n    \"man_in_tuxedo_light_skin_tone\": \"🤵🏻‍♂\",\n    \"man_in_tuxedo_medium-dark_skin_tone\": \"🤵🏾‍♂\",\n    \"man_in_tuxedo_medium-light_skin_tone\": \"🤵🏼‍♂\",\n    \"man_in_tuxedo_medium_skin_tone\": \"🤵🏽‍♂\",\n    \"man_judge\": \"👨‍⚖\",\n    \"man_judge_dark_skin_tone\": \"👨🏿‍⚖\",\n    \"man_judge_light_skin_tone\": \"👨🏻‍⚖\",\n    \"man_judge_medium-dark_skin_tone\": \"👨🏾‍⚖\",\n    \"man_judge_medium-light_skin_tone\": \"👨🏼‍⚖\",\n    \"man_judge_medium_skin_tone\": \"👨🏽‍⚖\",\n    \"man_juggling\": \"🤹‍♂\",\n    \"man_juggling_dark_skin_tone\": \"🤹🏿‍♂\",\n    \"man_juggling_light_skin_tone\": \"🤹🏻‍♂\",\n    \"man_juggling_medium-dark_skin_tone\": \"🤹🏾‍♂\",\n    \"man_juggling_medium-light_skin_tone\": \"🤹🏼‍♂\",\n    \"man_juggling_medium_skin_tone\": \"🤹🏽‍♂\",\n    \"man_kneeling\": \"🧎‍♂\",\n    \"man_kneeling_dark_skin_tone\": \"🧎🏿‍♂\",\n    \"man_kneeling_light_skin_tone\": \"🧎🏻‍♂\",\n    \"man_kneeling_medium-dark_skin_tone\": \"🧎🏾‍♂\",\n    \"man_kneeling_medium-light_skin_tone\": \"🧎🏼‍♂\",\n    \"man_kneeling_medium_skin_tone\": \"🧎🏽‍♂\",\n    \"man_lifting_weights\": \"🏋‍♂\",\n    \"man_lifting_weights_dark_skin_tone\": \"🏋🏿‍♂\",\n    \"man_lifting_weights_light_skin_tone\": \"🏋🏻‍♂\",\n    \"man_lifting_weights_medium-dark_skin_tone\": \"🏋🏾‍♂\",\n    \"man_lifting_weights_medium-light_skin_tone\": \"🏋🏼‍♂\",\n    \"man_lifting_weights_medium_skin_tone\": \"🏋🏽‍♂\",\n    \"man_light_skin_tone\": \"👨🏻\",\n    \"man_light_skin_tone_bald\": \"👨🏻‍🦲\",\n    \"man_light_skin_tone_beard\": \"🧔🏻‍♂\",\n    \"man_light_skin_tone_blond_hair\": \"👱🏻‍♂\",\n    \"man_light_skin_tone_curly_hair\": \"👨🏻‍🦱\",\n    \"man_light_skin_tone_red_hair\": \"👨🏻‍🦰\",\n    \"man_light_skin_tone_white_hair\": \"👨🏻‍🦳\",\n    \"man_mage\": \"🧙‍♂\",\n    \"man_mage_dark_skin_tone\": \"🧙🏿‍♂\",\n    \"man_mage_light_skin_tone\": \"🧙🏻‍♂\",\n    \"man_mage_medium-dark_skin_tone\": \"🧙🏾‍♂\",\n    \"man_mage_medium-light_skin_tone\": \"🧙🏼‍♂\",\n    \"man_mage_medium_skin_tone\": \"🧙🏽‍♂\",\n    \"man_mechanic\": \"👨‍🔧\",\n    \"man_mechanic_dark_skin_tone\": \"👨🏿‍🔧\",\n    \"man_mechanic_light_skin_tone\": \"👨🏻‍🔧\",\n    \"man_mechanic_medium-dark_skin_tone\": \"👨🏾‍🔧\",\n    \"man_mechanic_medium-light_skin_tone\": \"👨🏼‍🔧\",\n    \"man_mechanic_medium_skin_tone\": \"👨🏽‍🔧\",\n    \"man_medium-dark_skin_tone\": \"👨🏾\",\n    \"man_medium-dark_skin_tone_bald\": \"👨🏾‍🦲\",\n    \"man_medium-dark_skin_tone_beard\": \"🧔🏾‍♂\",\n    \"man_medium-dark_skin_tone_blond_hair\": \"👱🏾‍♂\",\n    \"man_medium-dark_skin_tone_curly_hair\": \"👨🏾‍🦱\",\n    \"man_medium-dark_skin_tone_red_hair\": \"👨🏾‍🦰\",\n    \"man_medium-dark_skin_tone_white_hair\": \"👨🏾‍🦳\",\n    \"man_medium-light_skin_tone\": \"👨🏼\",\n    \"man_medium-light_skin_tone_bald\": \"👨🏼‍🦲\",\n    \"man_medium-light_skin_tone_beard\": \"🧔🏼‍♂\",\n    \"man_medium-light_skin_tone_blond_hair\": \"👱🏼‍♂\",\n    \"man_medium-light_skin_tone_curly_hair\": \"👨🏼‍🦱\",\n    \"man_medium-light_skin_tone_red_hair\": \"👨🏼‍🦰\",\n    \"man_medium-light_skin_tone_white_hair\": \"👨🏼‍🦳\",\n    \"man_medium_skin_tone\": \"👨🏽\",\n    \"man_medium_skin_tone_bald\": \"👨🏽‍🦲\",\n    \"man_medium_skin_tone_beard\": \"🧔🏽‍♂\",\n    \"man_medium_skin_tone_blond_hair\": \"👱🏽‍♂\",\n    \"man_medium_skin_tone_curly_hair\": \"👨🏽‍🦱\",\n    \"man_medium_skin_tone_red_hair\": \"👨🏽‍🦰\",\n    \"man_medium_skin_tone_white_hair\": \"👨🏽‍🦳\",\n    \"man_mountain_biking\": \"🚵‍♂\",\n    \"man_mountain_biking_dark_skin_tone\": \"🚵🏿‍♂\",\n    \"man_mountain_biking_light_skin_tone\": \"🚵🏻‍♂\",\n    \"man_mountain_biking_medium-dark_skin_tone\": \"🚵🏾‍♂\",\n    \"man_mountain_biking_medium-light_skin_tone\": \"🚵🏼‍♂\",\n    \"man_mountain_biking_medium_skin_tone\": \"🚵🏽‍♂\",\n    \"man_office_worker\": \"👨‍💼\",\n    \"man_office_worker_dark_skin_tone\": \"👨🏿‍💼\",\n    \"man_office_worker_light_skin_tone\": \"👨🏻‍💼\",\n    \"man_office_worker_medium-dark_skin_tone\": \"👨🏾‍💼\",\n    \"man_office_worker_medium-light_skin_tone\": \"👨🏼‍💼\",\n    \"man_office_worker_medium_skin_tone\": \"👨🏽‍💼\",\n    \"man_pilot\": \"👨‍✈\",\n    \"man_pilot_dark_skin_tone\": \"👨🏿‍✈\",\n    \"man_pilot_light_skin_tone\": \"👨🏻‍✈\",\n    \"man_pilot_medium-dark_skin_tone\": \"👨🏾‍✈\",\n    \"man_pilot_medium-light_skin_tone\": \"👨🏼‍✈\",\n    \"man_pilot_medium_skin_tone\": \"👨🏽‍✈\",\n    \"man_playing_handball\": \"🤾‍♂\",\n    \"man_playing_handball_dark_skin_tone\": \"🤾🏿‍♂\",\n    \"man_playing_handball_light_skin_tone\": \"🤾🏻‍♂\",\n    \"man_playing_handball_medium-dark_skin_tone\": \"🤾🏾‍♂\",\n    \"man_playing_handball_medium-light_skin_tone\": \"🤾🏼‍♂\",\n    \"man_playing_handball_medium_skin_tone\": \"🤾🏽‍♂\",\n    \"man_playing_water_polo\": \"🤽‍♂\",\n    \"man_playing_water_polo_dark_skin_tone\": \"🤽🏿‍♂\",\n    \"man_playing_water_polo_light_skin_tone\": \"🤽🏻‍♂\",\n    \"man_playing_water_polo_medium-dark_skin_tone\": \"🤽🏾‍♂\",\n    \"man_playing_water_polo_medium-light_skin_tone\": \"🤽🏼‍♂\",\n    \"man_playing_water_polo_medium_skin_tone\": \"🤽🏽‍♂\",\n    \"man_police_officer\": \"👮‍♂\",\n    \"man_police_officer_dark_skin_tone\": \"👮🏿‍♂\",\n    \"man_police_officer_light_skin_tone\": \"👮🏻‍♂\",\n    \"man_police_officer_medium-dark_skin_tone\": \"👮🏾‍♂\",\n    \"man_police_officer_medium-light_skin_tone\": \"👮🏼‍♂\",\n    \"man_police_officer_medium_skin_tone\": \"👮🏽‍♂\",\n    \"man_pouting\": \"🙎‍♂\",\n    \"man_pouting_dark_skin_tone\": \"🙎🏿‍♂\",\n    \"man_pouting_light_skin_tone\": \"🙎🏻‍♂\",\n    \"man_pouting_medium-dark_skin_tone\": \"🙎🏾‍♂\",\n    \"man_pouting_medium-light_skin_tone\": \"🙎🏼‍♂\",\n    \"man_pouting_medium_skin_tone\": \"🙎🏽‍♂\",\n    \"man_raising_hand\": \"🙋‍♂\",\n    \"man_raising_hand_dark_skin_tone\": \"🙋🏿‍♂\",\n    \"man_raising_hand_light_skin_tone\": \"🙋🏻‍♂\",\n    \"man_raising_hand_medium-dark_skin_tone\": \"🙋🏾‍♂\",\n    \"man_raising_hand_medium-light_skin_tone\": \"🙋🏼‍♂\",\n    \"man_raising_hand_medium_skin_tone\": \"🙋🏽‍♂\",\n    \"man_red_hair\": \"👨‍🦰\",\n    \"man_rowing_boat\": \"🚣‍♂\",\n    \"man_rowing_boat_dark_skin_tone\": \"🚣🏿‍♂\",\n    \"man_rowing_boat_light_skin_tone\": \"🚣🏻‍♂\",\n    \"man_rowing_boat_medium-dark_skin_tone\": \"🚣🏾‍♂\",\n    \"man_rowing_boat_medium-light_skin_tone\": \"🚣🏼‍♂\",\n    \"man_rowing_boat_medium_skin_tone\": \"🚣🏽‍♂\",\n    \"man_running\": \"🏃‍♂\",\n    \"man_running_dark_skin_tone\": \"🏃🏿‍♂\",\n    \"man_running_light_skin_tone\": \"🏃🏻‍♂\",\n    \"man_running_medium-dark_skin_tone\": \"🏃🏾‍♂\",\n    \"man_running_medium-light_skin_tone\": \"🏃🏼‍♂\",\n    \"man_running_medium_skin_tone\": \"🏃🏽‍♂\",\n    \"man_scientist\": \"👨‍🔬\",\n    \"man_scientist_dark_skin_tone\": \"👨🏿‍🔬\",\n    \"man_scientist_light_skin_tone\": \"👨🏻‍🔬\",\n    \"man_scientist_medium-dark_skin_tone\": \"👨🏾‍🔬\",\n    \"man_scientist_medium-light_skin_tone\": \"👨🏼‍🔬\",\n    \"man_scientist_medium_skin_tone\": \"👨🏽‍🔬\",\n    \"man_shrugging\": \"🤷‍♂\",\n    \"man_shrugging_dark_skin_tone\": \"🤷🏿‍♂\",\n    \"man_shrugging_light_skin_tone\": \"🤷🏻‍♂\",\n    \"man_shrugging_medium-dark_skin_tone\": \"🤷🏾‍♂\",\n    \"man_shrugging_medium-light_skin_tone\": \"🤷🏼‍♂\",\n    \"man_shrugging_medium_skin_tone\": \"🤷🏽‍♂\",\n    \"man_singer\": \"👨‍🎤\",\n    \"man_singer_dark_skin_tone\": \"👨🏿‍🎤\",\n    \"man_singer_light_skin_tone\": \"👨🏻‍🎤\",\n    \"man_singer_medium-dark_skin_tone\": \"👨🏾‍🎤\",\n    \"man_singer_medium-light_skin_tone\": \"👨🏼‍🎤\",\n    \"man_singer_medium_skin_tone\": \"👨🏽‍🎤\",\n    \"man_standing\": \"🧍‍♂\",\n    \"man_standing_dark_skin_tone\": \"🧍🏿‍♂\",\n    \"man_standing_light_skin_tone\": \"🧍🏻‍♂\",\n    \"man_standing_medium-dark_skin_tone\": \"🧍🏾‍♂\",\n    \"man_standing_medium-light_skin_tone\": \"🧍🏼‍♂\",\n    \"man_standing_medium_skin_tone\": \"🧍🏽‍♂\",\n    \"man_student\": \"👨‍🎓\",\n    \"man_student_dark_skin_tone\": \"👨🏿‍🎓\",\n    \"man_student_light_skin_tone\": \"👨🏻‍🎓\",\n    \"man_student_medium-dark_skin_tone\": \"👨🏾‍🎓\",\n    \"man_student_medium-light_skin_tone\": \"👨🏼‍🎓\",\n    \"man_student_medium_skin_tone\": \"👨🏽‍🎓\",\n    \"man_superhero\": \"🦸‍♂\",\n    \"man_superhero_dark_skin_tone\": \"🦸🏿‍♂\",\n    \"man_superhero_light_skin_tone\": \"🦸🏻‍♂\",\n    \"man_superhero_medium-dark_skin_tone\": \"🦸🏾‍♂\",\n    \"man_superhero_medium-light_skin_tone\": \"🦸🏼‍♂\",\n    \"man_superhero_medium_skin_tone\": \"🦸🏽‍♂\",\n    \"man_supervillain\": \"🦹‍♂\",\n    \"man_supervillain_dark_skin_tone\": \"🦹🏿‍♂\",\n    \"man_supervillain_light_skin_tone\": \"🦹🏻‍♂\",\n    \"man_supervillain_medium-dark_skin_tone\": \"🦹🏾‍♂\",\n    \"man_supervillain_medium-light_skin_tone\": \"🦹🏼‍♂\",\n    \"man_supervillain_medium_skin_tone\": \"🦹🏽‍♂\",\n    \"man_surfing\": \"🏄‍♂\",\n    \"man_surfing_dark_skin_tone\": \"🏄🏿‍♂\",\n    \"man_surfing_light_skin_tone\": \"🏄🏻‍♂\",\n    \"man_surfing_medium-dark_skin_tone\": \"🏄🏾‍♂\",\n    \"man_surfing_medium-light_skin_tone\": \"🏄🏼‍♂\",\n    \"man_surfing_medium_skin_tone\": \"🏄🏽‍♂\",\n    \"man_swimming\": \"🏊‍♂\",\n    \"man_swimming_dark_skin_tone\": \"🏊🏿‍♂\",\n    \"man_swimming_light_skin_tone\": \"🏊🏻‍♂\",\n    \"man_swimming_medium-dark_skin_tone\": \"🏊🏾‍♂\",\n    \"man_swimming_medium-light_skin_tone\": \"🏊🏼‍♂\",\n    \"man_swimming_medium_skin_tone\": \"🏊🏽‍♂\",\n    \"man_teacher\": \"👨‍🏫\",\n    \"man_teacher_dark_skin_tone\": \"👨🏿‍🏫\",\n    \"man_teacher_light_skin_tone\": \"👨🏻‍🏫\",\n    \"man_teacher_medium-dark_skin_tone\": \"👨🏾‍🏫\",\n    \"man_teacher_medium-light_skin_tone\": \"👨🏼‍🏫\",\n    \"man_teacher_medium_skin_tone\": \"👨🏽‍🏫\",\n    \"man_technologist\": \"👨‍💻\",\n    \"man_technologist_dark_skin_tone\": \"👨🏿‍💻\",\n    \"man_technologist_light_skin_tone\": \"👨🏻‍💻\",\n    \"man_technologist_medium-dark_skin_tone\": \"👨🏾‍💻\",\n    \"man_technologist_medium-light_skin_tone\": \"👨🏼‍💻\",\n    \"man_technologist_medium_skin_tone\": \"👨🏽‍💻\",\n    \"man_tipping_hand\": \"💁‍♂\",\n    \"man_tipping_hand_dark_skin_tone\": \"💁🏿‍♂\",\n    \"man_tipping_hand_light_skin_tone\": \"💁🏻‍♂\",\n    \"man_tipping_hand_medium-dark_skin_tone\": \"💁🏾‍♂\",\n    \"man_tipping_hand_medium-light_skin_tone\": \"💁🏼‍♂\",\n    \"man_tipping_hand_medium_skin_tone\": \"💁🏽‍♂\",\n    \"man_vampire\": \"🧛‍♂\",\n    \"man_vampire_dark_skin_tone\": \"🧛🏿‍♂\",\n    \"man_vampire_light_skin_tone\": \"🧛🏻‍♂\",\n    \"man_vampire_medium-dark_skin_tone\": \"🧛🏾‍♂\",\n    \"man_vampire_medium-light_skin_tone\": \"🧛🏼‍♂\",\n    \"man_vampire_medium_skin_tone\": \"🧛🏽‍♂\",\n    \"man_walking\": \"🚶‍♂\",\n    \"man_walking_dark_skin_tone\": \"🚶🏿‍♂\",\n    \"man_walking_light_skin_tone\": \"🚶🏻‍♂\",\n    \"man_walking_medium-dark_skin_tone\": \"🚶🏾‍♂\",\n    \"man_walking_medium-light_skin_tone\": \"🚶🏼‍♂\",\n    \"man_walking_medium_skin_tone\": \"🚶🏽‍♂\",\n    \"man_wearing_turban\": \"👳‍♂\",\n    \"man_wearing_turban_dark_skin_tone\": \"👳🏿‍♂\",\n    \"man_wearing_turban_light_skin_tone\": \"👳🏻‍♂\",\n    \"man_wearing_turban_medium-dark_skin_tone\": \"👳🏾‍♂\",\n    \"man_wearing_turban_medium-light_skin_tone\": \"👳🏼‍♂\",\n    \"man_wearing_turban_medium_skin_tone\": \"👳🏽‍♂\",\n    \"man_white_hair\": \"👨‍🦳\",\n    \"man_with_veil\": \"👰‍♂\",\n    \"man_with_veil_dark_skin_tone\": \"👰🏿‍♂\",\n    \"man_with_veil_light_skin_tone\": \"👰🏻‍♂\",\n    \"man_with_veil_medium-dark_skin_tone\": \"👰🏾‍♂\",\n    \"man_with_veil_medium-light_skin_tone\": \"👰🏼‍♂\",\n    \"man_with_veil_medium_skin_tone\": \"👰🏽‍♂\",\n    \"man_with_white_cane\": \"👨‍🦯\",\n    \"man_with_white_cane_dark_skin_tone\": \"👨🏿‍🦯\",\n    \"man_with_white_cane_light_skin_tone\": \"👨🏻‍🦯\",\n    \"man_with_white_cane_medium-dark_skin_tone\": \"👨🏾‍🦯\",\n    \"man_with_white_cane_medium-light_skin_tone\": \"👨🏼‍🦯\",\n    \"man_with_white_cane_medium_skin_tone\": \"👨🏽‍🦯\",\n    \"man_zombie\": \"🧟‍♂\",\n    \"mango\": \"🥭\",\n    \"mantelpiece_clock\": \"🕰\",\n    \"manual_wheelchair\": \"🦽\",\n    \"man’s_shoe\": \"👞\",\n    \"map_of_Japan\": \"🗾\",\n    \"maple_leaf\": \"🍁\",\n    \"maracas\": \"🪇\",\n    \"martial_arts_uniform\": \"🥋\",\n    \"mate\": \"🧉\",\n    \"meat_on_bone\": \"🍖\",\n    \"mechanic\": \"🧑‍🔧\",\n    \"mechanic_dark_skin_tone\": \"🧑🏿‍🔧\",\n    \"mechanic_light_skin_tone\": \"🧑🏻‍🔧\",\n    \"mechanic_medium-dark_skin_tone\": \"🧑🏾‍🔧\",\n    \"mechanic_medium-light_skin_tone\": \"🧑🏼‍🔧\",\n    \"mechanic_medium_skin_tone\": \"🧑🏽‍🔧\",\n    \"mechanical_arm\": \"🦾\",\n    \"mechanical_leg\": \"🦿\",\n    \"medical_symbol\": \"⚕\",\n    \"medium-dark_skin_tone\": \"🏾\",\n    \"medium-light_skin_tone\": \"🏼\",\n    \"medium_skin_tone\": \"🏽\",\n    \"megaphone\": \"📣\",\n    \"melon\": \"🍈\",\n    \"melting_face\": \"🫠\",\n    \"memo\": \"📝\",\n    \"men_holding_hands\": \"👬\",\n    \"men_holding_hands_dark_skin_tone\": \"👬🏿\",\n    \"men_holding_hands_dark_skin_tone_light_skin_tone\": \"👨🏿‍🤝‍👨🏻\",\n    \"men_holding_hands_dark_skin_tone_medium-dark_skin_tone\": \"👨🏿‍🤝‍👨🏾\",\n    \"men_holding_hands_dark_skin_tone_medium-light_skin_tone\": \"👨🏿‍🤝‍👨🏼\",\n    \"men_holding_hands_dark_skin_tone_medium_skin_tone\": \"👨🏿‍🤝‍👨🏽\",\n    \"men_holding_hands_light_skin_tone\": \"👬🏻\",\n    \"men_holding_hands_light_skin_tone_dark_skin_tone\": \"👨🏻‍🤝‍👨🏿\",\n    \"men_holding_hands_light_skin_tone_medium-dark_skin_tone\": \"👨🏻‍🤝‍👨🏾\",\n    \"men_holding_hands_light_skin_tone_medium-light_skin_tone\": \"👨🏻‍🤝‍👨🏼\",\n    \"men_holding_hands_light_skin_tone_medium_skin_tone\": \"👨🏻‍🤝‍👨🏽\",\n    \"men_holding_hands_medium-dark_skin_tone\": \"👬🏾\",\n    \"men_holding_hands_medium-dark_skin_tone_dark_skin_tone\": \"👨🏾‍🤝‍👨🏿\",\n    \"men_holding_hands_medium-dark_skin_tone_light_skin_tone\": \"👨🏾‍🤝‍👨🏻\",\n    (\n        \"men_holding_hands_medium-dark_skin_tone_medium-light_skin_tone\"\n    ): \"👨🏾‍🤝‍👨🏼\",\n    \"men_holding_hands_medium-dark_skin_tone_medium_skin_tone\": \"👨🏾‍🤝‍👨🏽\",\n    \"men_holding_hands_medium-light_skin_tone\": \"👬🏼\",\n    \"men_holding_hands_medium-light_skin_tone_dark_skin_tone\": \"👨🏼‍🤝‍👨🏿\",\n    \"men_holding_hands_medium-light_skin_tone_light_skin_tone\": \"👨🏼‍🤝‍👨🏻\",\n    (\n        \"men_holding_hands_medium-light_skin_tone_medium-dark_skin_tone\"\n    ): \"👨🏼‍🤝‍👨🏾\",\n    \"men_holding_hands_medium-light_skin_tone_medium_skin_tone\": \"👨🏼‍🤝‍👨🏽\",\n    \"men_holding_hands_medium_skin_tone\": \"👬🏽\",\n    \"men_holding_hands_medium_skin_tone_dark_skin_tone\": \"👨🏽‍🤝‍👨🏿\",\n    \"men_holding_hands_medium_skin_tone_light_skin_tone\": \"👨🏽‍🤝‍👨🏻\",\n    \"men_holding_hands_medium_skin_tone_medium-dark_skin_tone\": \"👨🏽‍🤝‍👨🏾\",\n    \"men_holding_hands_medium_skin_tone_medium-light_skin_tone\": \"👨🏽‍🤝‍👨🏼\",\n    \"men_with_bunny_ears\": \"👯‍♂\",\n    \"men_wrestling\": \"🤼‍♂\",\n    \"mending_heart\": \"❤‍🩹\",\n    \"menorah\": \"🕎\",\n    \"men’s_room\": \"🚹\",\n    \"mermaid\": \"🧜‍♀\",\n    \"mermaid_dark_skin_tone\": \"🧜🏿‍♀\",\n    \"mermaid_light_skin_tone\": \"🧜🏻‍♀\",\n    \"mermaid_medium-dark_skin_tone\": \"🧜🏾‍♀\",\n    \"mermaid_medium-light_skin_tone\": \"🧜🏼‍♀\",\n    \"mermaid_medium_skin_tone\": \"🧜🏽‍♀\",\n    \"merman\": \"🧜‍♂\",\n    \"merman_dark_skin_tone\": \"🧜🏿‍♂\",\n    \"merman_light_skin_tone\": \"🧜🏻‍♂\",\n    \"merman_medium-dark_skin_tone\": \"🧜🏾‍♂\",\n    \"merman_medium-light_skin_tone\": \"🧜🏼‍♂\",\n    \"merman_medium_skin_tone\": \"🧜🏽‍♂\",\n    \"merperson\": \"🧜\",\n    \"merperson_dark_skin_tone\": \"🧜🏿\",\n    \"merperson_light_skin_tone\": \"🧜🏻\",\n    \"merperson_medium-dark_skin_tone\": \"🧜🏾\",\n    \"merperson_medium-light_skin_tone\": \"🧜🏼\",\n    \"merperson_medium_skin_tone\": \"🧜🏽\",\n    \"metro\": \"🚇\",\n    \"microbe\": \"🦠\",\n    \"microphone\": \"🎤\",\n    \"microscope\": \"🔬\",\n    \"middle_finger\": \"🖕\",\n    \"middle_finger_dark_skin_tone\": \"🖕🏿\",\n    \"middle_finger_light_skin_tone\": \"🖕🏻\",\n    \"middle_finger_medium-dark_skin_tone\": \"🖕🏾\",\n    \"middle_finger_medium-light_skin_tone\": \"🖕🏼\",\n    \"middle_finger_medium_skin_tone\": \"🖕🏽\",\n    \"military_helmet\": \"🪖\",\n    \"military_medal\": \"🎖\",\n    \"milky_way\": \"🌌\",\n    \"minibus\": \"🚐\",\n    \"minus\": \"➖\",\n    \"mirror\": \"🪞\",\n    \"mirror_ball\": \"🪩\",\n    \"moai\": \"🗿\",\n    \"mobile_phone\": \"📱\",\n    \"mobile_phone_off\": \"📴\",\n    \"mobile_phone_with_arrow\": \"📲\",\n    \"money-mouth_face\": \"🤑\",\n    \"money_bag\": \"💰\",\n    \"money_with_wings\": \"💸\",\n    \"monkey\": \"🐒\",\n    \"monkey_face\": \"🐵\",\n    \"monorail\": \"🚝\",\n    \"moon_cake\": \"🥮\",\n    \"moon_viewing_ceremony\": \"🎑\",\n    \"moose\": \"🫎\",\n    \"mosque\": \"🕌\",\n    \"mosquito\": \"🦟\",\n    \"motor_boat\": \"🛥\",\n    \"motor_scooter\": \"🛵\",\n    \"motorcycle\": \"🏍\",\n    \"motorized_wheelchair\": \"🦼\",\n    \"motorway\": \"🛣\",\n    \"mount_fuji\": \"🗻\",\n    \"mountain\": \"⛰\",\n    \"mountain_cableway\": \"🚠\",\n    \"mountain_railway\": \"🚞\",\n    \"mouse\": \"🐁\",\n    \"mouse_face\": \"🐭\",\n    \"mouse_trap\": \"🪤\",\n    \"mouth\": \"👄\",\n    \"movie_camera\": \"🎥\",\n    \"multiply\": \"✖\",\n    \"mushroom\": \"🍄\",\n    \"musical_keyboard\": \"🎹\",\n    \"musical_note\": \"🎵\",\n    \"musical_notes\": \"🎶\",\n    \"musical_score\": \"🎼\",\n    \"muted_speaker\": \"🔇\",\n    \"mx_claus\": \"🧑‍🎄\",\n    \"mx_claus_dark_skin_tone\": \"🧑🏿‍🎄\",\n    \"mx_claus_light_skin_tone\": \"🧑🏻‍🎄\",\n    \"mx_claus_medium-dark_skin_tone\": \"🧑🏾‍🎄\",\n    \"mx_claus_medium-light_skin_tone\": \"🧑🏼‍🎄\",\n    \"mx_claus_medium_skin_tone\": \"🧑🏽‍🎄\",\n    \"nail_polish\": \"💅\",\n    \"nail_polish_dark_skin_tone\": \"💅🏿\",\n    \"nail_polish_light_skin_tone\": \"💅🏻\",\n    \"nail_polish_medium-dark_skin_tone\": \"💅🏾\",\n    \"nail_polish_medium-light_skin_tone\": \"💅🏼\",\n    \"nail_polish_medium_skin_tone\": \"💅🏽\",\n    \"name_badge\": \"📛\",\n    \"national_park\": \"🏞\",\n    \"nauseated_face\": \"🤢\",\n    \"nazar_amulet\": \"🧿\",\n    \"necktie\": \"👔\",\n    \"nerd_face\": \"🤓\",\n    \"nest_with_eggs\": \"🪺\",\n    \"nesting_dolls\": \"🪆\",\n    \"neutral_face\": \"😐\",\n    \"new_moon\": \"🌑\",\n    \"new_moon_face\": \"🌚\",\n    \"newspaper\": \"📰\",\n    \"next_track_button\": \"⏭\",\n    \"night_with_stars\": \"🌃\",\n    \"nine-thirty\": \"🕤\",\n    \"nine_o’clock\": \"🕘\",\n    \"ninja\": \"🥷\",\n    \"ninja_dark_skin_tone\": \"🥷🏿\",\n    \"ninja_light_skin_tone\": \"🥷🏻\",\n    \"ninja_medium-dark_skin_tone\": \"🥷🏾\",\n    \"ninja_medium-light_skin_tone\": \"🥷🏼\",\n    \"ninja_medium_skin_tone\": \"🥷🏽\",\n    \"no_bicycles\": \"🚳\",\n    \"no_entry\": \"⛔\",\n    \"no_littering\": \"🚯\",\n    \"no_mobile_phones\": \"📵\",\n    \"no_one_under_eighteen\": \"🔞\",\n    \"no_pedestrians\": \"🚷\",\n    \"no_smoking\": \"🚭\",\n    \"non-potable_water\": \"🚱\",\n    \"nose\": \"👃\",\n    \"nose_dark_skin_tone\": \"👃🏿\",\n    \"nose_light_skin_tone\": \"👃🏻\",\n    \"nose_medium-dark_skin_tone\": \"👃🏾\",\n    \"nose_medium-light_skin_tone\": \"👃🏼\",\n    \"nose_medium_skin_tone\": \"👃🏽\",\n    \"notebook\": \"📓\",\n    \"notebook_with_decorative_cover\": \"📔\",\n    \"nut_and_bolt\": \"🔩\",\n    \"octopus\": \"🐙\",\n    \"oden\": \"🍢\",\n    \"office_building\": \"🏢\",\n    \"office_worker\": \"🧑‍💼\",\n    \"office_worker_dark_skin_tone\": \"🧑🏿‍💼\",\n    \"office_worker_light_skin_tone\": \"🧑🏻‍💼\",\n    \"office_worker_medium-dark_skin_tone\": \"🧑🏾‍💼\",\n    \"office_worker_medium-light_skin_tone\": \"🧑🏼‍💼\",\n    \"office_worker_medium_skin_tone\": \"🧑🏽‍💼\",\n    \"ogre\": \"👹\",\n    \"oil_drum\": \"🛢\",\n    \"old_key\": \"🗝\",\n    \"old_man\": \"👴\",\n    \"old_man_dark_skin_tone\": \"👴🏿\",\n    \"old_man_light_skin_tone\": \"👴🏻\",\n    \"old_man_medium-dark_skin_tone\": \"👴🏾\",\n    \"old_man_medium-light_skin_tone\": \"👴🏼\",\n    \"old_man_medium_skin_tone\": \"👴🏽\",\n    \"old_woman\": \"👵\",\n    \"old_woman_dark_skin_tone\": \"👵🏿\",\n    \"old_woman_light_skin_tone\": \"👵🏻\",\n    \"old_woman_medium-dark_skin_tone\": \"👵🏾\",\n    \"old_woman_medium-light_skin_tone\": \"👵🏼\",\n    \"old_woman_medium_skin_tone\": \"👵🏽\",\n    \"older_person\": \"🧓\",\n    \"older_person_dark_skin_tone\": \"🧓🏿\",\n    \"older_person_light_skin_tone\": \"🧓🏻\",\n    \"older_person_medium-dark_skin_tone\": \"🧓🏾\",\n    \"older_person_medium-light_skin_tone\": \"🧓🏼\",\n    \"older_person_medium_skin_tone\": \"🧓🏽\",\n    \"olive\": \"🫒\",\n    \"om\": \"🕉\",\n    \"oncoming_automobile\": \"🚘\",\n    \"oncoming_bus\": \"🚍\",\n    \"oncoming_fist\": \"👊\",\n    \"oncoming_fist_dark_skin_tone\": \"👊🏿\",\n    \"oncoming_fist_light_skin_tone\": \"👊🏻\",\n    \"oncoming_fist_medium-dark_skin_tone\": \"👊🏾\",\n    \"oncoming_fist_medium-light_skin_tone\": \"👊🏼\",\n    \"oncoming_fist_medium_skin_tone\": \"👊🏽\",\n    \"oncoming_police_car\": \"🚔\",\n    \"oncoming_taxi\": \"🚖\",\n    \"one-piece_swimsuit\": \"🩱\",\n    \"one-thirty\": \"🕜\",\n    \"one_o’clock\": \"🕐\",\n    \"onion\": \"🧅\",\n    \"open_book\": \"📖\",\n    \"open_file_folder\": \"📂\",\n    \"open_hands\": \"👐\",\n    \"open_hands_dark_skin_tone\": \"👐🏿\",\n    \"open_hands_light_skin_tone\": \"👐🏻\",\n    \"open_hands_medium-dark_skin_tone\": \"👐🏾\",\n    \"open_hands_medium-light_skin_tone\": \"👐🏼\",\n    \"open_hands_medium_skin_tone\": \"👐🏽\",\n    \"open_mailbox_with_lowered_flag\": \"📭\",\n    \"open_mailbox_with_raised_flag\": \"📬\",\n    \"optical_disk\": \"💿\",\n    \"orange_book\": \"📙\",\n    \"orange_circle\": \"🟠\",\n    \"orange_heart\": \"🧡\",\n    \"orange_square\": \"🟧\",\n    \"orangutan\": \"🦧\",\n    \"orthodox_cross\": \"☦\",\n    \"otter\": \"🦦\",\n    \"outbox_tray\": \"📤\",\n    \"owl\": \"🦉\",\n    \"ox\": \"🐂\",\n    \"oyster\": \"🦪\",\n    \"package\": \"📦\",\n    \"page_facing_up\": \"📄\",\n    \"page_with_curl\": \"📃\",\n    \"pager\": \"📟\",\n    \"paintbrush\": \"🖌\",\n    \"palm_down_hand\": \"🫳\",\n    \"palm_down_hand_dark_skin_tone\": \"🫳🏿\",\n    \"palm_down_hand_light_skin_tone\": \"🫳🏻\",\n    \"palm_down_hand_medium-dark_skin_tone\": \"🫳🏾\",\n    \"palm_down_hand_medium-light_skin_tone\": \"🫳🏼\",\n    \"palm_down_hand_medium_skin_tone\": \"🫳🏽\",\n    \"palm_tree\": \"🌴\",\n    \"palm_up_hand\": \"🫴\",\n    \"palm_up_hand_dark_skin_tone\": \"🫴🏿\",\n    \"palm_up_hand_light_skin_tone\": \"🫴🏻\",\n    \"palm_up_hand_medium-dark_skin_tone\": \"🫴🏾\",\n    \"palm_up_hand_medium-light_skin_tone\": \"🫴🏼\",\n    \"palm_up_hand_medium_skin_tone\": \"🫴🏽\",\n    \"palms_up_together\": \"🤲\",\n    \"palms_up_together_dark_skin_tone\": \"🤲🏿\",\n    \"palms_up_together_light_skin_tone\": \"🤲🏻\",\n    \"palms_up_together_medium-dark_skin_tone\": \"🤲🏾\",\n    \"palms_up_together_medium-light_skin_tone\": \"🤲🏼\",\n    \"palms_up_together_medium_skin_tone\": \"🤲🏽\",\n    \"pancakes\": \"🥞\",\n    \"panda\": \"🐼\",\n    \"paperclip\": \"📎\",\n    \"parachute\": \"🪂\",\n    \"parrot\": \"🦜\",\n    \"part_alternation_mark\": \"〽\",\n    \"party_popper\": \"🎉\",\n    \"partying_face\": \"🥳\",\n    \"passenger_ship\": \"🛳\",\n    \"passport_control\": \"🛂\",\n    \"pause_button\": \"⏸\",\n    \"paw_prints\": \"🐾\",\n    \"pea_pod\": \"🫛\",\n    \"peace_symbol\": \"☮\",\n    \"peach\": \"🍑\",\n    \"peacock\": \"🦚\",\n    \"peanuts\": \"🥜\",\n    \"pear\": \"🍐\",\n    \"pen\": \"🖊\",\n    \"pencil\": \"✏\",\n    \"penguin\": \"🐧\",\n    \"pensive_face\": \"😔\",\n    \"people_holding_hands\": \"🧑‍🤝‍🧑\",\n    \"people_holding_hands_dark_skin_tone\": \"🧑🏿‍🤝‍🧑🏿\",\n    \"people_holding_hands_dark_skin_tone_light_skin_tone\": \"🧑🏿‍🤝‍🧑🏻\",\n    \"people_holding_hands_dark_skin_tone_medium-dark_skin_tone\": \"🧑🏿‍🤝‍🧑🏾\",\n    \"people_holding_hands_dark_skin_tone_medium-light_skin_tone\": \"🧑🏿‍🤝‍🧑🏼\",\n    \"people_holding_hands_dark_skin_tone_medium_skin_tone\": \"🧑🏿‍🤝‍🧑🏽\",\n    \"people_holding_hands_light_skin_tone\": \"🧑🏻‍🤝‍🧑🏻\",\n    \"people_holding_hands_light_skin_tone_dark_skin_tone\": \"🧑🏻‍🤝‍🧑🏿\",\n    \"people_holding_hands_light_skin_tone_medium-dark_skin_tone\": \"🧑🏻‍🤝‍🧑🏾\",\n    (\n        \"people_holding_hands_light_skin_tone_medium-light_skin_tone\"\n    ): \"🧑🏻‍🤝‍🧑🏼\",\n    \"people_holding_hands_light_skin_tone_medium_skin_tone\": \"🧑🏻‍🤝‍🧑🏽\",\n    \"people_holding_hands_medium-dark_skin_tone\": \"🧑🏾‍🤝‍🧑🏾\",\n    \"people_holding_hands_medium-dark_skin_tone_dark_skin_tone\": \"🧑🏾‍🤝‍🧑🏿\",\n    \"people_holding_hands_medium-dark_skin_tone_light_skin_tone\": \"🧑🏾‍🤝‍🧑🏻\",\n    (\n        \"people_holding_hands_medium-dark_skin_tone_medium-light_skin_tone\"\n    ): \"🧑🏾‍🤝‍🧑🏼\",\n    (\n        \"people_holding_hands_medium-dark_skin_tone_medium_skin_tone\"\n    ): \"🧑🏾‍🤝‍🧑🏽\",\n    \"people_holding_hands_medium-light_skin_tone\": \"🧑🏼‍🤝‍🧑🏼\",\n    \"people_holding_hands_medium-light_skin_tone_dark_skin_tone\": \"🧑🏼‍🤝‍🧑🏿\",\n    (\n        \"people_holding_hands_medium-light_skin_tone_light_skin_tone\"\n    ): \"🧑🏼‍🤝‍🧑🏻\",\n    (\n        \"people_holding_hands_medium-light_skin_tone_medium-dark_skin_tone\"\n    ): \"🧑🏼‍🤝‍🧑🏾\",\n    (\n        \"people_holding_hands_medium-light_skin_tone_medium_skin_tone\"\n    ): \"🧑🏼‍🤝‍🧑🏽\",\n    \"people_holding_hands_medium_skin_tone\": \"🧑🏽‍🤝‍🧑🏽\",\n    \"people_holding_hands_medium_skin_tone_dark_skin_tone\": \"🧑🏽‍🤝‍🧑🏿\",\n    \"people_holding_hands_medium_skin_tone_light_skin_tone\": \"🧑🏽‍🤝‍🧑🏻\",\n    (\n        \"people_holding_hands_medium_skin_tone_medium-dark_skin_tone\"\n    ): \"🧑🏽‍🤝‍🧑🏾\",\n    (\n        \"people_holding_hands_medium_skin_tone_medium-light_skin_tone\"\n    ): \"🧑🏽‍🤝‍🧑🏼\",\n    \"people_hugging\": \"🫂\",\n    \"people_with_bunny_ears\": \"👯\",\n    \"people_wrestling\": \"🤼\",\n    \"performing_arts\": \"🎭\",\n    \"persevering_face\": \"😣\",\n    \"person\": \"🧑\",\n    \"person_bald\": \"🧑‍🦲\",\n    \"person_beard\": \"🧔\",\n    \"person_biking\": \"🚴\",\n    \"person_biking_dark_skin_tone\": \"🚴🏿\",\n    \"person_biking_light_skin_tone\": \"🚴🏻\",\n    \"person_biking_medium-dark_skin_tone\": \"🚴🏾\",\n    \"person_biking_medium-light_skin_tone\": \"🚴🏼\",\n    \"person_biking_medium_skin_tone\": \"🚴🏽\",\n    \"person_blond_hair\": \"👱\",\n    \"person_bouncing_ball\": \"⛹\",\n    \"person_bouncing_ball_dark_skin_tone\": \"⛹🏿\",\n    \"person_bouncing_ball_light_skin_tone\": \"⛹🏻\",\n    \"person_bouncing_ball_medium-dark_skin_tone\": \"⛹🏾\",\n    \"person_bouncing_ball_medium-light_skin_tone\": \"⛹🏼\",\n    \"person_bouncing_ball_medium_skin_tone\": \"⛹🏽\",\n    \"person_bowing\": \"🙇\",\n    \"person_bowing_dark_skin_tone\": \"🙇🏿\",\n    \"person_bowing_light_skin_tone\": \"🙇🏻\",\n    \"person_bowing_medium-dark_skin_tone\": \"🙇🏾\",\n    \"person_bowing_medium-light_skin_tone\": \"🙇🏼\",\n    \"person_bowing_medium_skin_tone\": \"🙇🏽\",\n    \"person_cartwheeling\": \"🤸\",\n    \"person_cartwheeling_dark_skin_tone\": \"🤸🏿\",\n    \"person_cartwheeling_light_skin_tone\": \"🤸🏻\",\n    \"person_cartwheeling_medium-dark_skin_tone\": \"🤸🏾\",\n    \"person_cartwheeling_medium-light_skin_tone\": \"🤸🏼\",\n    \"person_cartwheeling_medium_skin_tone\": \"🤸🏽\",\n    \"person_climbing\": \"🧗\",\n    \"person_climbing_dark_skin_tone\": \"🧗🏿\",\n    \"person_climbing_light_skin_tone\": \"🧗🏻\",\n    \"person_climbing_medium-dark_skin_tone\": \"🧗🏾\",\n    \"person_climbing_medium-light_skin_tone\": \"🧗🏼\",\n    \"person_climbing_medium_skin_tone\": \"🧗🏽\",\n    \"person_curly_hair\": \"🧑‍🦱\",\n    \"person_dark_skin_tone\": \"🧑🏿\",\n    \"person_dark_skin_tone_bald\": \"🧑🏿‍🦲\",\n    \"person_dark_skin_tone_beard\": \"🧔🏿\",\n    \"person_dark_skin_tone_blond_hair\": \"👱🏿\",\n    \"person_dark_skin_tone_curly_hair\": \"🧑🏿‍🦱\",\n    \"person_dark_skin_tone_red_hair\": \"🧑🏿‍🦰\",\n    \"person_dark_skin_tone_white_hair\": \"🧑🏿‍🦳\",\n    \"person_facepalming\": \"🤦\",\n    \"person_facepalming_dark_skin_tone\": \"🤦🏿\",\n    \"person_facepalming_light_skin_tone\": \"🤦🏻\",\n    \"person_facepalming_medium-dark_skin_tone\": \"🤦🏾\",\n    \"person_facepalming_medium-light_skin_tone\": \"🤦🏼\",\n    \"person_facepalming_medium_skin_tone\": \"🤦🏽\",\n    \"person_feeding_baby\": \"🧑‍🍼\",\n    \"person_feeding_baby_dark_skin_tone\": \"🧑🏿‍🍼\",\n    \"person_feeding_baby_light_skin_tone\": \"🧑🏻‍🍼\",\n    \"person_feeding_baby_medium-dark_skin_tone\": \"🧑🏾‍🍼\",\n    \"person_feeding_baby_medium-light_skin_tone\": \"🧑🏼‍🍼\",\n    \"person_feeding_baby_medium_skin_tone\": \"🧑🏽‍🍼\",\n    \"person_fencing\": \"🤺\",\n    \"person_frowning\": \"🙍\",\n    \"person_frowning_dark_skin_tone\": \"🙍🏿\",\n    \"person_frowning_light_skin_tone\": \"🙍🏻\",\n    \"person_frowning_medium-dark_skin_tone\": \"🙍🏾\",\n    \"person_frowning_medium-light_skin_tone\": \"🙍🏼\",\n    \"person_frowning_medium_skin_tone\": \"🙍🏽\",\n    \"person_gesturing_NO\": \"🙅\",\n    \"person_gesturing_NO_dark_skin_tone\": \"🙅🏿\",\n    \"person_gesturing_NO_light_skin_tone\": \"🙅🏻\",\n    \"person_gesturing_NO_medium-dark_skin_tone\": \"🙅🏾\",\n    \"person_gesturing_NO_medium-light_skin_tone\": \"🙅🏼\",\n    \"person_gesturing_NO_medium_skin_tone\": \"🙅🏽\",\n    \"person_gesturing_OK\": \"🙆\",\n    \"person_gesturing_OK_dark_skin_tone\": \"🙆🏿\",\n    \"person_gesturing_OK_light_skin_tone\": \"🙆🏻\",\n    \"person_gesturing_OK_medium-dark_skin_tone\": \"🙆🏾\",\n    \"person_gesturing_OK_medium-light_skin_tone\": \"🙆🏼\",\n    \"person_gesturing_OK_medium_skin_tone\": \"🙆🏽\",\n    \"person_getting_haircut\": \"💇\",\n    \"person_getting_haircut_dark_skin_tone\": \"💇🏿\",\n    \"person_getting_haircut_light_skin_tone\": \"💇🏻\",\n    \"person_getting_haircut_medium-dark_skin_tone\": \"💇🏾\",\n    \"person_getting_haircut_medium-light_skin_tone\": \"💇🏼\",\n    \"person_getting_haircut_medium_skin_tone\": \"💇🏽\",\n    \"person_getting_massage\": \"💆\",\n    \"person_getting_massage_dark_skin_tone\": \"💆🏿\",\n    \"person_getting_massage_light_skin_tone\": \"💆🏻\",\n    \"person_getting_massage_medium-dark_skin_tone\": \"💆🏾\",\n    \"person_getting_massage_medium-light_skin_tone\": \"💆🏼\",\n    \"person_getting_massage_medium_skin_tone\": \"💆🏽\",\n    \"person_golfing\": \"🏌\",\n    \"person_golfing_dark_skin_tone\": \"🏌🏿\",\n    \"person_golfing_light_skin_tone\": \"🏌🏻\",\n    \"person_golfing_medium-dark_skin_tone\": \"🏌🏾\",\n    \"person_golfing_medium-light_skin_tone\": \"🏌🏼\",\n    \"person_golfing_medium_skin_tone\": \"🏌🏽\",\n    \"person_in_bed\": \"🛌\",\n    \"person_in_bed_dark_skin_tone\": \"🛌🏿\",\n    \"person_in_bed_light_skin_tone\": \"🛌🏻\",\n    \"person_in_bed_medium-dark_skin_tone\": \"🛌🏾\",\n    \"person_in_bed_medium-light_skin_tone\": \"🛌🏼\",\n    \"person_in_bed_medium_skin_tone\": \"🛌🏽\",\n    \"person_in_lotus_position\": \"🧘\",\n    \"person_in_lotus_position_dark_skin_tone\": \"🧘🏿\",\n    \"person_in_lotus_position_light_skin_tone\": \"🧘🏻\",\n    \"person_in_lotus_position_medium-dark_skin_tone\": \"🧘🏾\",\n    \"person_in_lotus_position_medium-light_skin_tone\": \"🧘🏼\",\n    \"person_in_lotus_position_medium_skin_tone\": \"🧘🏽\",\n    \"person_in_manual_wheelchair\": \"🧑‍🦽\",\n    \"person_in_manual_wheelchair_dark_skin_tone\": \"🧑🏿‍🦽\",\n    \"person_in_manual_wheelchair_light_skin_tone\": \"🧑🏻‍🦽\",\n    \"person_in_manual_wheelchair_medium-dark_skin_tone\": \"🧑🏾‍🦽\",\n    \"person_in_manual_wheelchair_medium-light_skin_tone\": \"🧑🏼‍🦽\",\n    \"person_in_manual_wheelchair_medium_skin_tone\": \"🧑🏽‍🦽\",\n    \"person_in_motorized_wheelchair\": \"🧑‍🦼\",\n    \"person_in_motorized_wheelchair_dark_skin_tone\": \"🧑🏿‍🦼\",\n    \"person_in_motorized_wheelchair_light_skin_tone\": \"🧑🏻‍🦼\",\n    \"person_in_motorized_wheelchair_medium-dark_skin_tone\": \"🧑🏾‍🦼\",\n    \"person_in_motorized_wheelchair_medium-light_skin_tone\": \"🧑🏼‍🦼\",\n    \"person_in_motorized_wheelchair_medium_skin_tone\": \"🧑🏽‍🦼\",\n    \"person_in_steamy_room\": \"🧖\",\n    \"person_in_steamy_room_dark_skin_tone\": \"🧖🏿\",\n    \"person_in_steamy_room_light_skin_tone\": \"🧖🏻\",\n    \"person_in_steamy_room_medium-dark_skin_tone\": \"🧖🏾\",\n    \"person_in_steamy_room_medium-light_skin_tone\": \"🧖🏼\",\n    \"person_in_steamy_room_medium_skin_tone\": \"🧖🏽\",\n    \"person_in_suit_levitating\": \"🕴\",\n    \"person_in_suit_levitating_dark_skin_tone\": \"🕴🏿\",\n    \"person_in_suit_levitating_light_skin_tone\": \"🕴🏻\",\n    \"person_in_suit_levitating_medium-dark_skin_tone\": \"🕴🏾\",\n    \"person_in_suit_levitating_medium-light_skin_tone\": \"🕴🏼\",\n    \"person_in_suit_levitating_medium_skin_tone\": \"🕴🏽\",\n    \"person_in_tuxedo\": \"🤵\",\n    \"person_in_tuxedo_dark_skin_tone\": \"🤵🏿\",\n    \"person_in_tuxedo_light_skin_tone\": \"🤵🏻\",\n    \"person_in_tuxedo_medium-dark_skin_tone\": \"🤵🏾\",\n    \"person_in_tuxedo_medium-light_skin_tone\": \"🤵🏼\",\n    \"person_in_tuxedo_medium_skin_tone\": \"🤵🏽\",\n    \"person_juggling\": \"🤹\",\n    \"person_juggling_dark_skin_tone\": \"🤹🏿\",\n    \"person_juggling_light_skin_tone\": \"🤹🏻\",\n    \"person_juggling_medium-dark_skin_tone\": \"🤹🏾\",\n    \"person_juggling_medium-light_skin_tone\": \"🤹🏼\",\n    \"person_juggling_medium_skin_tone\": \"🤹🏽\",\n    \"person_kneeling\": \"🧎\",\n    \"person_kneeling_dark_skin_tone\": \"🧎🏿\",\n    \"person_kneeling_light_skin_tone\": \"🧎🏻\",\n    \"person_kneeling_medium-dark_skin_tone\": \"🧎🏾\",\n    \"person_kneeling_medium-light_skin_tone\": \"🧎🏼\",\n    \"person_kneeling_medium_skin_tone\": \"🧎🏽\",\n    \"person_lifting_weights\": \"🏋\",\n    \"person_lifting_weights_dark_skin_tone\": \"🏋🏿\",\n    \"person_lifting_weights_light_skin_tone\": \"🏋🏻\",\n    \"person_lifting_weights_medium-dark_skin_tone\": \"🏋🏾\",\n    \"person_lifting_weights_medium-light_skin_tone\": \"🏋🏼\",\n    \"person_lifting_weights_medium_skin_tone\": \"🏋🏽\",\n    \"person_light_skin_tone\": \"🧑🏻\",\n    \"person_light_skin_tone_bald\": \"🧑🏻‍🦲\",\n    \"person_light_skin_tone_beard\": \"🧔🏻\",\n    \"person_light_skin_tone_blond_hair\": \"👱🏻\",\n    \"person_light_skin_tone_curly_hair\": \"🧑🏻‍🦱\",\n    \"person_light_skin_tone_red_hair\": \"🧑🏻‍🦰\",\n    \"person_light_skin_tone_white_hair\": \"🧑🏻‍🦳\",\n    \"person_medium-dark_skin_tone\": \"🧑🏾\",\n    \"person_medium-dark_skin_tone_bald\": \"🧑🏾‍🦲\",\n    \"person_medium-dark_skin_tone_beard\": \"🧔🏾\",\n    \"person_medium-dark_skin_tone_blond_hair\": \"👱🏾\",\n    \"person_medium-dark_skin_tone_curly_hair\": \"🧑🏾‍🦱\",\n    \"person_medium-dark_skin_tone_red_hair\": \"🧑🏾‍🦰\",\n    \"person_medium-dark_skin_tone_white_hair\": \"🧑🏾‍🦳\",\n    \"person_medium-light_skin_tone\": \"🧑🏼\",\n    \"person_medium-light_skin_tone_bald\": \"🧑🏼‍🦲\",\n    \"person_medium-light_skin_tone_beard\": \"🧔🏼\",\n    \"person_medium-light_skin_tone_blond_hair\": \"👱🏼\",\n    \"person_medium-light_skin_tone_curly_hair\": \"🧑🏼‍🦱\",\n    \"person_medium-light_skin_tone_red_hair\": \"🧑🏼‍🦰\",\n    \"person_medium-light_skin_tone_white_hair\": \"🧑🏼‍🦳\",\n    \"person_medium_skin_tone\": \"🧑🏽\",\n    \"person_medium_skin_tone_bald\": \"🧑🏽‍🦲\",\n    \"person_medium_skin_tone_beard\": \"🧔🏽\",\n    \"person_medium_skin_tone_blond_hair\": \"👱🏽\",\n    \"person_medium_skin_tone_curly_hair\": \"🧑🏽‍🦱\",\n    \"person_medium_skin_tone_red_hair\": \"🧑🏽‍🦰\",\n    \"person_medium_skin_tone_white_hair\": \"🧑🏽‍🦳\",\n    \"person_mountain_biking\": \"🚵\",\n    \"person_mountain_biking_dark_skin_tone\": \"🚵🏿\",\n    \"person_mountain_biking_light_skin_tone\": \"🚵🏻\",\n    \"person_mountain_biking_medium-dark_skin_tone\": \"🚵🏾\",\n    \"person_mountain_biking_medium-light_skin_tone\": \"🚵🏼\",\n    \"person_mountain_biking_medium_skin_tone\": \"🚵🏽\",\n    \"person_playing_handball\": \"🤾\",\n    \"person_playing_handball_dark_skin_tone\": \"🤾🏿\",\n    \"person_playing_handball_light_skin_tone\": \"🤾🏻\",\n    \"person_playing_handball_medium-dark_skin_tone\": \"🤾🏾\",\n    \"person_playing_handball_medium-light_skin_tone\": \"🤾🏼\",\n    \"person_playing_handball_medium_skin_tone\": \"🤾🏽\",\n    \"person_playing_water_polo\": \"🤽\",\n    \"person_playing_water_polo_dark_skin_tone\": \"🤽🏿\",\n    \"person_playing_water_polo_light_skin_tone\": \"🤽🏻\",\n    \"person_playing_water_polo_medium-dark_skin_tone\": \"🤽🏾\",\n    \"person_playing_water_polo_medium-light_skin_tone\": \"🤽🏼\",\n    \"person_playing_water_polo_medium_skin_tone\": \"🤽🏽\",\n    \"person_pouting\": \"🙎\",\n    \"person_pouting_dark_skin_tone\": \"🙎🏿\",\n    \"person_pouting_light_skin_tone\": \"🙎🏻\",\n    \"person_pouting_medium-dark_skin_tone\": \"🙎🏾\",\n    \"person_pouting_medium-light_skin_tone\": \"🙎🏼\",\n    \"person_pouting_medium_skin_tone\": \"🙎🏽\",\n    \"person_raising_hand\": \"🙋\",\n    \"person_raising_hand_dark_skin_tone\": \"🙋🏿\",\n    \"person_raising_hand_light_skin_tone\": \"🙋🏻\",\n    \"person_raising_hand_medium-dark_skin_tone\": \"🙋🏾\",\n    \"person_raising_hand_medium-light_skin_tone\": \"🙋🏼\",\n    \"person_raising_hand_medium_skin_tone\": \"🙋🏽\",\n    \"person_red_hair\": \"🧑‍🦰\",\n    \"person_rowing_boat\": \"🚣\",\n    \"person_rowing_boat_dark_skin_tone\": \"🚣🏿\",\n    \"person_rowing_boat_light_skin_tone\": \"🚣🏻\",\n    \"person_rowing_boat_medium-dark_skin_tone\": \"🚣🏾\",\n    \"person_rowing_boat_medium-light_skin_tone\": \"🚣🏼\",\n    \"person_rowing_boat_medium_skin_tone\": \"🚣🏽\",\n    \"person_running\": \"🏃\",\n    \"person_running_dark_skin_tone\": \"🏃🏿\",\n    \"person_running_light_skin_tone\": \"🏃🏻\",\n    \"person_running_medium-dark_skin_tone\": \"🏃🏾\",\n    \"person_running_medium-light_skin_tone\": \"🏃🏼\",\n    \"person_running_medium_skin_tone\": \"🏃🏽\",\n    \"person_shrugging\": \"🤷\",\n    \"person_shrugging_dark_skin_tone\": \"🤷🏿\",\n    \"person_shrugging_light_skin_tone\": \"🤷🏻\",\n    \"person_shrugging_medium-dark_skin_tone\": \"🤷🏾\",\n    \"person_shrugging_medium-light_skin_tone\": \"🤷🏼\",\n    \"person_shrugging_medium_skin_tone\": \"🤷🏽\",\n    \"person_standing\": \"🧍\",\n    \"person_standing_dark_skin_tone\": \"🧍🏿\",\n    \"person_standing_light_skin_tone\": \"🧍🏻\",\n    \"person_standing_medium-dark_skin_tone\": \"🧍🏾\",\n    \"person_standing_medium-light_skin_tone\": \"🧍🏼\",\n    \"person_standing_medium_skin_tone\": \"🧍🏽\",\n    \"person_surfing\": \"🏄\",\n    \"person_surfing_dark_skin_tone\": \"🏄🏿\",\n    \"person_surfing_light_skin_tone\": \"🏄🏻\",\n    \"person_surfing_medium-dark_skin_tone\": \"🏄🏾\",\n    \"person_surfing_medium-light_skin_tone\": \"🏄🏼\",\n    \"person_surfing_medium_skin_tone\": \"🏄🏽\",\n    \"person_swimming\": \"🏊\",\n    \"person_swimming_dark_skin_tone\": \"🏊🏿\",\n    \"person_swimming_light_skin_tone\": \"🏊🏻\",\n    \"person_swimming_medium-dark_skin_tone\": \"🏊🏾\",\n    \"person_swimming_medium-light_skin_tone\": \"🏊🏼\",\n    \"person_swimming_medium_skin_tone\": \"🏊🏽\",\n    \"person_taking_bath\": \"🛀\",\n    \"person_taking_bath_dark_skin_tone\": \"🛀🏿\",\n    \"person_taking_bath_light_skin_tone\": \"🛀🏻\",\n    \"person_taking_bath_medium-dark_skin_tone\": \"🛀🏾\",\n    \"person_taking_bath_medium-light_skin_tone\": \"🛀🏼\",\n    \"person_taking_bath_medium_skin_tone\": \"🛀🏽\",\n    \"person_tipping_hand\": \"💁\",\n    \"person_tipping_hand_dark_skin_tone\": \"💁🏿\",\n    \"person_tipping_hand_light_skin_tone\": \"💁🏻\",\n    \"person_tipping_hand_medium-dark_skin_tone\": \"💁🏾\",\n    \"person_tipping_hand_medium-light_skin_tone\": \"💁🏼\",\n    \"person_tipping_hand_medium_skin_tone\": \"💁🏽\",\n    \"person_walking\": \"🚶\",\n    \"person_walking_dark_skin_tone\": \"🚶🏿\",\n    \"person_walking_light_skin_tone\": \"🚶🏻\",\n    \"person_walking_medium-dark_skin_tone\": \"🚶🏾\",\n    \"person_walking_medium-light_skin_tone\": \"🚶🏼\",\n    \"person_walking_medium_skin_tone\": \"🚶🏽\",\n    \"person_wearing_turban\": \"👳\",\n    \"person_wearing_turban_dark_skin_tone\": \"👳🏿\",\n    \"person_wearing_turban_light_skin_tone\": \"👳🏻\",\n    \"person_wearing_turban_medium-dark_skin_tone\": \"👳🏾\",\n    \"person_wearing_turban_medium-light_skin_tone\": \"👳🏼\",\n    \"person_wearing_turban_medium_skin_tone\": \"👳🏽\",\n    \"person_white_hair\": \"🧑‍🦳\",\n    \"person_with_crown\": \"🫅\",\n    \"person_with_crown_dark_skin_tone\": \"🫅🏿\",\n    \"person_with_crown_light_skin_tone\": \"🫅🏻\",\n    \"person_with_crown_medium-dark_skin_tone\": \"🫅🏾\",\n    \"person_with_crown_medium-light_skin_tone\": \"🫅🏼\",\n    \"person_with_crown_medium_skin_tone\": \"🫅🏽\",\n    \"person_with_skullcap\": \"👲\",\n    \"person_with_skullcap_dark_skin_tone\": \"👲🏿\",\n    \"person_with_skullcap_light_skin_tone\": \"👲🏻\",\n    \"person_with_skullcap_medium-dark_skin_tone\": \"👲🏾\",\n    \"person_with_skullcap_medium-light_skin_tone\": \"👲🏼\",\n    \"person_with_skullcap_medium_skin_tone\": \"👲🏽\",\n    \"person_with_veil\": \"👰\",\n    \"person_with_veil_dark_skin_tone\": \"👰🏿\",\n    \"person_with_veil_light_skin_tone\": \"👰🏻\",\n    \"person_with_veil_medium-dark_skin_tone\": \"👰🏾\",\n    \"person_with_veil_medium-light_skin_tone\": \"👰🏼\",\n    \"person_with_veil_medium_skin_tone\": \"👰🏽\",\n    \"person_with_white_cane\": \"🧑‍🦯\",\n    \"person_with_white_cane_dark_skin_tone\": \"🧑🏿‍🦯\",\n    \"person_with_white_cane_light_skin_tone\": \"🧑🏻‍🦯\",\n    \"person_with_white_cane_medium-dark_skin_tone\": \"🧑🏾‍🦯\",\n    \"person_with_white_cane_medium-light_skin_tone\": \"🧑🏼‍🦯\",\n    \"person_with_white_cane_medium_skin_tone\": \"🧑🏽‍🦯\",\n    \"petri_dish\": \"🧫\",\n    \"pick\": \"⛏\",\n    \"pickup_truck\": \"🛻\",\n    \"pie\": \"🥧\",\n    \"pig\": \"🐖\",\n    \"pig_face\": \"🐷\",\n    \"pig_nose\": \"🐽\",\n    \"pile_of_poo\": \"💩\",\n    \"pill\": \"💊\",\n    \"pilot\": \"🧑‍✈\",\n    \"pilot_dark_skin_tone\": \"🧑🏿‍✈\",\n    \"pilot_light_skin_tone\": \"🧑🏻‍✈\",\n    \"pilot_medium-dark_skin_tone\": \"🧑🏾‍✈\",\n    \"pilot_medium-light_skin_tone\": \"🧑🏼‍✈\",\n    \"pilot_medium_skin_tone\": \"🧑🏽‍✈\",\n    \"pinched_fingers\": \"🤌\",\n    \"pinched_fingers_dark_skin_tone\": \"🤌🏿\",\n    \"pinched_fingers_light_skin_tone\": \"🤌🏻\",\n    \"pinched_fingers_medium-dark_skin_tone\": \"🤌🏾\",\n    \"pinched_fingers_medium-light_skin_tone\": \"🤌🏼\",\n    \"pinched_fingers_medium_skin_tone\": \"🤌🏽\",\n    \"pinching_hand\": \"🤏\",\n    \"pinching_hand_dark_skin_tone\": \"🤏🏿\",\n    \"pinching_hand_light_skin_tone\": \"🤏🏻\",\n    \"pinching_hand_medium-dark_skin_tone\": \"🤏🏾\",\n    \"pinching_hand_medium-light_skin_tone\": \"🤏🏼\",\n    \"pinching_hand_medium_skin_tone\": \"🤏🏽\",\n    \"pine_decoration\": \"🎍\",\n    \"pineapple\": \"🍍\",\n    \"ping_pong\": \"🏓\",\n    \"pink_heart\": \"🩷\",\n    \"pirate_flag\": \"🏴‍☠\",\n    \"pizza\": \"🍕\",\n    \"piñata\": \"🪅\",\n    \"placard\": \"🪧\",\n    \"place_of_worship\": \"🛐\",\n    \"play_button\": \"▶\",\n    \"play_or_pause_button\": \"⏯\",\n    \"playground_slide\": \"🛝\",\n    \"pleading_face\": \"🥺\",\n    \"plunger\": \"🪠\",\n    \"plus\": \"➕\",\n    \"polar_bear\": \"🐻‍❄\",\n    \"police_car\": \"🚓\",\n    \"police_car_light\": \"🚨\",\n    \"police_officer\": \"👮\",\n    \"police_officer_dark_skin_tone\": \"👮🏿\",\n    \"police_officer_light_skin_tone\": \"👮🏻\",\n    \"police_officer_medium-dark_skin_tone\": \"👮🏾\",\n    \"police_officer_medium-light_skin_tone\": \"👮🏼\",\n    \"police_officer_medium_skin_tone\": \"👮🏽\",\n    \"poodle\": \"🐩\",\n    \"pool_8_ball\": \"🎱\",\n    \"popcorn\": \"🍿\",\n    \"post_office\": \"🏤\",\n    \"postal_horn\": \"📯\",\n    \"postbox\": \"📮\",\n    \"pot_of_food\": \"🍲\",\n    \"potable_water\": \"🚰\",\n    \"potato\": \"🥔\",\n    \"potted_plant\": \"🪴\",\n    \"poultry_leg\": \"🍗\",\n    \"pound_banknote\": \"💷\",\n    \"pouring_liquid\": \"🫗\",\n    \"pouting_cat\": \"😾\",\n    \"prayer_beads\": \"📿\",\n    \"pregnant_man\": \"🫃\",\n    \"pregnant_man_dark_skin_tone\": \"🫃🏿\",\n    \"pregnant_man_light_skin_tone\": \"🫃🏻\",\n    \"pregnant_man_medium-dark_skin_tone\": \"🫃🏾\",\n    \"pregnant_man_medium-light_skin_tone\": \"🫃🏼\",\n    \"pregnant_man_medium_skin_tone\": \"🫃🏽\",\n    \"pregnant_person\": \"🫄\",\n    \"pregnant_person_dark_skin_tone\": \"🫄🏿\",\n    \"pregnant_person_light_skin_tone\": \"🫄🏻\",\n    \"pregnant_person_medium-dark_skin_tone\": \"🫄🏾\",\n    \"pregnant_person_medium-light_skin_tone\": \"🫄🏼\",\n    \"pregnant_person_medium_skin_tone\": \"🫄🏽\",\n    \"pregnant_woman\": \"🤰\",\n    \"pregnant_woman_dark_skin_tone\": \"🤰🏿\",\n    \"pregnant_woman_light_skin_tone\": \"🤰🏻\",\n    \"pregnant_woman_medium-dark_skin_tone\": \"🤰🏾\",\n    \"pregnant_woman_medium-light_skin_tone\": \"🤰🏼\",\n    \"pregnant_woman_medium_skin_tone\": \"🤰🏽\",\n    \"pretzel\": \"🥨\",\n    \"prince\": \"🤴\",\n    \"prince_dark_skin_tone\": \"🤴🏿\",\n    \"prince_light_skin_tone\": \"🤴🏻\",\n    \"prince_medium-dark_skin_tone\": \"🤴🏾\",\n    \"prince_medium-light_skin_tone\": \"🤴🏼\",\n    \"prince_medium_skin_tone\": \"🤴🏽\",\n    \"princess\": \"👸\",\n    \"princess_dark_skin_tone\": \"👸🏿\",\n    \"princess_light_skin_tone\": \"👸🏻\",\n    \"princess_medium-dark_skin_tone\": \"👸🏾\",\n    \"princess_medium-light_skin_tone\": \"👸🏼\",\n    \"princess_medium_skin_tone\": \"👸🏽\",\n    \"printer\": \"🖨\",\n    \"prohibited\": \"🚫\",\n    \"purple_circle\": \"🟣\",\n    \"purple_heart\": \"💜\",\n    \"purple_square\": \"🟪\",\n    \"purse\": \"👛\",\n    \"pushpin\": \"📌\",\n    \"puzzle_piece\": \"🧩\",\n    \"rabbit\": \"🐇\",\n    \"rabbit_face\": \"🐰\",\n    \"raccoon\": \"🦝\",\n    \"racing_car\": \"🏎\",\n    \"radio\": \"📻\",\n    \"radio_button\": \"🔘\",\n    \"radioactive\": \"☢\",\n    \"railway_car\": \"🚃\",\n    \"railway_track\": \"🛤\",\n    \"rainbow\": \"🌈\",\n    \"rainbow_flag\": \"🏳‍🌈\",\n    \"raised_back_of_hand\": \"🤚\",\n    \"raised_back_of_hand_dark_skin_tone\": \"🤚🏿\",\n    \"raised_back_of_hand_light_skin_tone\": \"🤚🏻\",\n    \"raised_back_of_hand_medium-dark_skin_tone\": \"🤚🏾\",\n    \"raised_back_of_hand_medium-light_skin_tone\": \"🤚🏼\",\n    \"raised_back_of_hand_medium_skin_tone\": \"🤚🏽\",\n    \"raised_fist\": \"✊\",\n    \"raised_fist_dark_skin_tone\": \"✊🏿\",\n    \"raised_fist_light_skin_tone\": \"✊🏻\",\n    \"raised_fist_medium-dark_skin_tone\": \"✊🏾\",\n    \"raised_fist_medium-light_skin_tone\": \"✊🏼\",\n    \"raised_fist_medium_skin_tone\": \"✊🏽\",\n    \"raised_hand\": \"✋\",\n    \"raised_hand_dark_skin_tone\": \"✋🏿\",\n    \"raised_hand_light_skin_tone\": \"✋🏻\",\n    \"raised_hand_medium-dark_skin_tone\": \"✋🏾\",\n    \"raised_hand_medium-light_skin_tone\": \"✋🏼\",\n    \"raised_hand_medium_skin_tone\": \"✋🏽\",\n    \"raising_hands\": \"🙌\",\n    \"raising_hands_dark_skin_tone\": \"🙌🏿\",\n    \"raising_hands_light_skin_tone\": \"🙌🏻\",\n    \"raising_hands_medium-dark_skin_tone\": \"🙌🏾\",\n    \"raising_hands_medium-light_skin_tone\": \"🙌🏼\",\n    \"raising_hands_medium_skin_tone\": \"🙌🏽\",\n    \"ram\": \"🐏\",\n    \"rat\": \"🐀\",\n    \"razor\": \"🪒\",\n    \"receipt\": \"🧾\",\n    \"record_button\": \"⏺\",\n    \"recycling_symbol\": \"♻\",\n    \"red_apple\": \"🍎\",\n    \"red_circle\": \"🔴\",\n    \"red_envelope\": \"🧧\",\n    \"red_exclamation_mark\": \"❗\",\n    \"red_hair\": \"🦰\",\n    \"red_heart\": \"❤\",\n    \"red_paper_lantern\": \"🏮\",\n    \"red_question_mark\": \"❓\",\n    \"red_square\": \"🟥\",\n    \"red_triangle_pointed_down\": \"🔻\",\n    \"red_triangle_pointed_up\": \"🔺\",\n    \"registered\": \"®\",\n    \"relieved_face\": \"😌\",\n    \"reminder_ribbon\": \"🎗\",\n    \"repeat_button\": \"🔁\",\n    \"repeat_single_button\": \"🔂\",\n    \"rescue_worker’s_helmet\": \"⛑\",\n    \"restroom\": \"🚻\",\n    \"reverse_button\": \"◀\",\n    \"revolving_hearts\": \"💞\",\n    \"rhinoceros\": \"🦏\",\n    \"ribbon\": \"🎀\",\n    \"rice_ball\": \"🍙\",\n    \"rice_cracker\": \"🍘\",\n    \"right-facing_fist\": \"🤜\",\n    \"right-facing_fist_dark_skin_tone\": \"🤜🏿\",\n    \"right-facing_fist_light_skin_tone\": \"🤜🏻\",\n    \"right-facing_fist_medium-dark_skin_tone\": \"🤜🏾\",\n    \"right-facing_fist_medium-light_skin_tone\": \"🤜🏼\",\n    \"right-facing_fist_medium_skin_tone\": \"🤜🏽\",\n    \"right_anger_bubble\": \"🗯\",\n    \"right_arrow\": \"➡\",\n    \"right_arrow_curving_down\": \"⤵\",\n    \"right_arrow_curving_left\": \"↩\",\n    \"right_arrow_curving_up\": \"⤴\",\n    \"rightwards_hand\": \"🫱\",\n    \"rightwards_hand_dark_skin_tone\": \"🫱🏿\",\n    \"rightwards_hand_light_skin_tone\": \"🫱🏻\",\n    \"rightwards_hand_medium-dark_skin_tone\": \"🫱🏾\",\n    \"rightwards_hand_medium-light_skin_tone\": \"🫱🏼\",\n    \"rightwards_hand_medium_skin_tone\": \"🫱🏽\",\n    \"rightwards_pushing_hand\": \"🫸\",\n    \"rightwards_pushing_hand_dark_skin_tone\": \"🫸🏿\",\n    \"rightwards_pushing_hand_light_skin_tone\": \"🫸🏻\",\n    \"rightwards_pushing_hand_medium-dark_skin_tone\": \"🫸🏾\",\n    \"rightwards_pushing_hand_medium-light_skin_tone\": \"🫸🏼\",\n    \"rightwards_pushing_hand_medium_skin_tone\": \"🫸🏽\",\n    \"ring\": \"💍\",\n    \"ring_buoy\": \"🛟\",\n    \"ringed_planet\": \"🪐\",\n    \"roasted_sweet_potato\": \"🍠\",\n    \"robot\": \"🤖\",\n    \"rock\": \"🪨\",\n    \"rocket\": \"🚀\",\n    \"roll_of_paper\": \"🧻\",\n    \"rolled-up_newspaper\": \"🗞\",\n    \"roller_coaster\": \"🎢\",\n    \"roller_skate\": \"🛼\",\n    \"rolling_on_the_floor_laughing\": \"🤣\",\n    \"rooster\": \"🐓\",\n    \"rose\": \"🌹\",\n    \"rosette\": \"🏵\",\n    \"round_pushpin\": \"📍\",\n    \"rugby_football\": \"🏉\",\n    \"running_shirt\": \"🎽\",\n    \"running_shoe\": \"👟\",\n    \"sad_but_relieved_face\": \"😥\",\n    \"safety_pin\": \"🧷\",\n    \"safety_vest\": \"🦺\",\n    \"sailboat\": \"⛵\",\n    \"sake\": \"🍶\",\n    \"salt\": \"🧂\",\n    \"saluting_face\": \"🫡\",\n    \"sandwich\": \"🥪\",\n    \"sari\": \"🥻\",\n    \"satellite\": \"🛰\",\n    \"satellite_antenna\": \"📡\",\n    \"sauropod\": \"🦕\",\n    \"saxophone\": \"🎷\",\n    \"scarf\": \"🧣\",\n    \"school\": \"🏫\",\n    \"scientist\": \"🧑‍🔬\",\n    \"scientist_dark_skin_tone\": \"🧑🏿‍🔬\",\n    \"scientist_light_skin_tone\": \"🧑🏻‍🔬\",\n    \"scientist_medium-dark_skin_tone\": \"🧑🏾‍🔬\",\n    \"scientist_medium-light_skin_tone\": \"🧑🏼‍🔬\",\n    \"scientist_medium_skin_tone\": \"🧑🏽‍🔬\",\n    \"scissors\": \"✂\",\n    \"scorpion\": \"🦂\",\n    \"screwdriver\": \"🪛\",\n    \"scroll\": \"📜\",\n    \"seal\": \"🦭\",\n    \"seat\": \"💺\",\n    \"see-no-evil_monkey\": \"🙈\",\n    \"see_no_evil\": \"🙈\",\n    \"seedling\": \"🌱\",\n    \"selfie\": \"🤳\",\n    \"selfie_dark_skin_tone\": \"🤳🏿\",\n    \"selfie_light_skin_tone\": \"🤳🏻\",\n    \"selfie_medium-dark_skin_tone\": \"🤳🏾\",\n    \"selfie_medium-light_skin_tone\": \"🤳🏼\",\n    \"selfie_medium_skin_tone\": \"🤳🏽\",\n    \"service_dog\": \"🐕‍🦺\",\n    \"seven-thirty\": \"🕢\",\n    \"seven_o’clock\": \"🕖\",\n    \"sewing_needle\": \"🪡\",\n    \"shaking_face\": \"🫨\",\n    \"shallow_pan_of_food\": \"🥘\",\n    \"shamrock\": \"☘\",\n    \"shark\": \"🦈\",\n    \"shaved_ice\": \"🍧\",\n    \"sheaf_of_rice\": \"🌾\",\n    \"shield\": \"🛡\",\n    \"shinto_shrine\": \"⛩\",\n    \"ship\": \"🚢\",\n    \"shooting_star\": \"🌠\",\n    \"shopping_bags\": \"🛍\",\n    \"shopping_cart\": \"🛒\",\n    \"shortcake\": \"🍰\",\n    \"shorts\": \"🩳\",\n    \"shower\": \"🚿\",\n    \"shrimp\": \"🦐\",\n    \"shuffle_tracks_button\": \"🔀\",\n    \"shushing_face\": \"🤫\",\n    \"sign_of_the_horns\": \"🤘\",\n    \"sign_of_the_horns_dark_skin_tone\": \"🤘🏿\",\n    \"sign_of_the_horns_light_skin_tone\": \"🤘🏻\",\n    \"sign_of_the_horns_medium-dark_skin_tone\": \"🤘🏾\",\n    \"sign_of_the_horns_medium-light_skin_tone\": \"🤘🏼\",\n    \"sign_of_the_horns_medium_skin_tone\": \"🤘🏽\",\n    \"singer\": \"🧑‍🎤\",\n    \"singer_dark_skin_tone\": \"🧑🏿‍🎤\",\n    \"singer_light_skin_tone\": \"🧑🏻‍🎤\",\n    \"singer_medium-dark_skin_tone\": \"🧑🏾‍🎤\",\n    \"singer_medium-light_skin_tone\": \"🧑🏼‍🎤\",\n    \"singer_medium_skin_tone\": \"🧑🏽‍🎤\",\n    \"six-thirty\": \"🕡\",\n    \"six_o’clock\": \"🕕\",\n    \"skateboard\": \"🛹\",\n    \"skier\": \"⛷\",\n    \"skis\": \"🎿\",\n    \"skull\": \"💀\",\n    \"skull_and_crossbones\": \"☠\",\n    \"skunk\": \"🦨\",\n    \"sled\": \"🛷\",\n    \"sleeping_face\": \"😴\",\n    \"sleepy_face\": \"😪\",\n    \"slightly_frowning_face\": \"🙁\",\n    \"slightly_smiling_face\": \"🙂\",\n    \"slot_machine\": \"🎰\",\n    \"sloth\": \"🦥\",\n    \"small_airplane\": \"🛩\",\n    \"small_blue_diamond\": \"🔹\",\n    \"small_orange_diamond\": \"🔸\",\n    \"smiling_cat_with_heart-eyes\": \"😻\",\n    \"smiling_face\": \"☺\",\n    \"smiling_face_with_halo\": \"😇\",\n    \"smiling_face_with_heart-eyes\": \"😍\",\n    \"smiling_face_with_hearts\": \"🥰\",\n    \"smiling_face_with_horns\": \"😈\",\n    \"smiling_face_with_open_hands\": \"🤗\",\n    \"smiling_face_with_smiling_eyes\": \"😊\",\n    \"smiling_face_with_sunglasses\": \"😎\",\n    \"smiling_face_with_tear\": \"🥲\",\n    \"smirking_face\": \"😏\",\n    \"snail\": \"🐌\",\n    \"snake\": \"🐍\",\n    \"sneezing_face\": \"🤧\",\n    \"snow-capped_mountain\": \"🏔\",\n    \"snowboarder\": \"🏂\",\n    \"snowboarder_dark_skin_tone\": \"🏂🏿\",\n    \"snowboarder_light_skin_tone\": \"🏂🏻\",\n    \"snowboarder_medium-dark_skin_tone\": \"🏂🏾\",\n    \"snowboarder_medium-light_skin_tone\": \"🏂🏼\",\n    \"snowboarder_medium_skin_tone\": \"🏂🏽\",\n    \"snowflake\": \"❄\",\n    \"snowman\": \"☃\",\n    \"snowman_without_snow\": \"⛄\",\n    \"soap\": \"🧼\",\n    \"soccer_ball\": \"⚽\",\n    \"socks\": \"🧦\",\n    \"soft_ice_cream\": \"🍦\",\n    \"softball\": \"🥎\",\n    \"spade_suit\": \"♠\",\n    \"spaghetti\": \"🍝\",\n    \"sparkle\": \"❇\",\n    \"sparkler\": \"🎇\",\n    \"sparkles\": \"✨\",\n    \"sparkling_heart\": \"💖\",\n    \"speak-no-evil_monkey\": \"🙊\",\n    \"speaker_high_volume\": \"🔊\",\n    \"speaker_low_volume\": \"🔈\",\n    \"speaker_medium_volume\": \"🔉\",\n    \"speaking_head\": \"🗣\",\n    \"speech_balloon\": \"💬\",\n    \"speedboat\": \"🚤\",\n    \"spider\": \"🕷\",\n    \"spider_web\": \"🕸\",\n    \"spiral_calendar\": \"🗓\",\n    \"spiral_notepad\": \"🗒\",\n    \"spiral_shell\": \"🐚\",\n    \"sponge\": \"🧽\",\n    \"spoon\": \"🥄\",\n    \"sport_utility_vehicle\": \"🚙\",\n    \"sports_medal\": \"🏅\",\n    \"spouting_whale\": \"🐳\",\n    \"squid\": \"🦑\",\n    \"squinting_face_with_tongue\": \"😝\",\n    \"stadium\": \"🏟\",\n    \"star\": \"⭐\",\n    \"star-struck\": \"🤩\",\n    \"star_and_crescent\": \"☪\",\n    \"star_of_David\": \"✡\",\n    \"station\": \"🚉\",\n    \"steaming_bowl\": \"🍜\",\n    \"stethoscope\": \"🩺\",\n    \"stop_button\": \"⏹\",\n    \"stop_sign\": \"🛑\",\n    \"stopwatch\": \"⏱\",\n    \"straight_ruler\": \"📏\",\n    \"strawberry\": \"🍓\",\n    \"student\": \"🧑‍🎓\",\n    \"student_dark_skin_tone\": \"🧑🏿‍🎓\",\n    \"student_light_skin_tone\": \"🧑🏻‍🎓\",\n    \"student_medium-dark_skin_tone\": \"🧑🏾‍🎓\",\n    \"student_medium-light_skin_tone\": \"🧑🏼‍🎓\",\n    \"student_medium_skin_tone\": \"🧑🏽‍🎓\",\n    \"studio_microphone\": \"🎙\",\n    \"stuffed_flatbread\": \"🥙\",\n    \"sun\": \"☀\",\n    \"sun_behind_cloud\": \"⛅\",\n    \"sun_behind_large_cloud\": \"🌥\",\n    \"sun_behind_rain_cloud\": \"🌦\",\n    \"sun_behind_small_cloud\": \"🌤\",\n    \"sun_with_face\": \"🌞\",\n    \"sunflower\": \"🌻\",\n    \"sunglasses\": \"🕶\",\n    \"sunrise\": \"🌅\",\n    \"sunrise_over_mountains\": \"🌄\",\n    \"sunset\": \"🌇\",\n    \"superhero\": \"🦸\",\n    \"superhero_dark_skin_tone\": \"🦸🏿\",\n    \"superhero_light_skin_tone\": \"🦸🏻\",\n    \"superhero_medium-dark_skin_tone\": \"🦸🏾\",\n    \"superhero_medium-light_skin_tone\": \"🦸🏼\",\n    \"superhero_medium_skin_tone\": \"🦸🏽\",\n    \"supervillain\": \"🦹\",\n    \"supervillain_dark_skin_tone\": \"🦹🏿\",\n    \"supervillain_light_skin_tone\": \"🦹🏻\",\n    \"supervillain_medium-dark_skin_tone\": \"🦹🏾\",\n    \"supervillain_medium-light_skin_tone\": \"🦹🏼\",\n    \"supervillain_medium_skin_tone\": \"🦹🏽\",\n    \"sushi\": \"🍣\",\n    \"suspension_railway\": \"🚟\",\n    \"swan\": \"🦢\",\n    \"sweat_droplets\": \"💦\",\n    \"synagogue\": \"🕍\",\n    \"syringe\": \"💉\",\n    \"t-shirt\": \"👕\",\n    \"taco\": \"🌮\",\n    \"takeout_box\": \"🥡\",\n    \"tamale\": \"🫔\",\n    \"tanabata_tree\": \"🎋\",\n    \"tangerine\": \"🍊\",\n    \"taxi\": \"🚕\",\n    \"teacher\": \"🧑‍🏫\",\n    \"teacher_dark_skin_tone\": \"🧑🏿‍🏫\",\n    \"teacher_light_skin_tone\": \"🧑🏻‍🏫\",\n    \"teacher_medium-dark_skin_tone\": \"🧑🏾‍🏫\",\n    \"teacher_medium-light_skin_tone\": \"🧑🏼‍🏫\",\n    \"teacher_medium_skin_tone\": \"🧑🏽‍🏫\",\n    \"teacup_without_handle\": \"🍵\",\n    \"teapot\": \"🫖\",\n    \"tear-off_calendar\": \"📆\",\n    \"technologist\": \"🧑‍💻\",\n    \"technologist_dark_skin_tone\": \"🧑🏿‍💻\",\n    \"technologist_light_skin_tone\": \"🧑🏻‍💻\",\n    \"technologist_medium-dark_skin_tone\": \"🧑🏾‍💻\",\n    \"technologist_medium-light_skin_tone\": \"🧑🏼‍💻\",\n    \"technologist_medium_skin_tone\": \"🧑🏽‍💻\",\n    \"teddy_bear\": \"🧸\",\n    \"telephone\": \"☎\",\n    \"telephone_receiver\": \"📞\",\n    \"telescope\": \"🔭\",\n    \"television\": \"📺\",\n    \"ten-thirty\": \"🕥\",\n    \"ten_o’clock\": \"🕙\",\n    \"tennis\": \"🎾\",\n    \"tent\": \"⛺\",\n    \"test_tube\": \"🧪\",\n    \"thermometer\": \"🌡\",\n    \"thinking_face\": \"🤔\",\n    \"thong_sandal\": \"🩴\",\n    \"thought_balloon\": \"💭\",\n    \"thread\": \"🧵\",\n    \"three-thirty\": \"🕞\",\n    \"three_o’clock\": \"🕒\",\n    \"thumbs_down\": \"👎\",\n    \"thumbs_down_dark_skin_tone\": \"👎🏿\",\n    \"thumbs_down_light_skin_tone\": \"👎🏻\",\n    \"thumbs_down_medium-dark_skin_tone\": \"👎🏾\",\n    \"thumbs_down_medium-light_skin_tone\": \"👎🏼\",\n    \"thumbs_down_medium_skin_tone\": \"👎🏽\",\n    \"thumbs_up\": \"👍\",\n    \"thumbs_up_dark_skin_tone\": \"👍🏿\",\n    \"thumbs_up_light_skin_tone\": \"👍🏻\",\n    \"thumbs_up_medium-dark_skin_tone\": \"👍🏾\",\n    \"thumbs_up_medium-light_skin_tone\": \"👍🏼\",\n    \"thumbs_up_medium_skin_tone\": \"👍🏽\",\n    \"ticket\": \"🎫\",\n    \"tiger\": \"🐅\",\n    \"tiger_face\": \"🐯\",\n    \"timer_clock\": \"⏲\",\n    \"tired_face\": \"😫\",\n    \"toilet\": \"🚽\",\n    \"tomato\": \"🍅\",\n    \"tongue\": \"👅\",\n    \"toolbox\": \"🧰\",\n    \"tooth\": \"🦷\",\n    \"toothbrush\": \"🪥\",\n    \"top_hat\": \"🎩\",\n    \"tornado\": \"🌪\",\n    \"trackball\": \"🖲\",\n    \"tractor\": \"🚜\",\n    \"trade_mark\": \"™\",\n    \"train\": \"🚆\",\n    \"tram\": \"🚊\",\n    \"tram_car\": \"🚋\",\n    \"transgender_flag\": \"🏳‍⚧\",\n    \"transgender_symbol\": \"⚧\",\n    \"triangular_flag\": \"🚩\",\n    \"triangular_ruler\": \"📐\",\n    \"trident_emblem\": \"🔱\",\n    \"troll\": \"🧌\",\n    \"trolleybus\": \"🚎\",\n    \"trophy\": \"🏆\",\n    \"tropical_drink\": \"🍹\",\n    \"tropical_fish\": \"🐠\",\n    \"trumpet\": \"🎺\",\n    \"tulip\": \"🌷\",\n    \"tumbler_glass\": \"🥃\",\n    \"turkey\": \"🦃\",\n    \"turtle\": \"🐢\",\n    \"twelve-thirty\": \"🕧\",\n    \"twelve_o’clock\": \"🕛\",\n    \"two-hump_camel\": \"🐫\",\n    \"two-thirty\": \"🕝\",\n    \"two_hearts\": \"💕\",\n    \"two_o’clock\": \"🕑\",\n    \"umbrella\": \"☂\",\n    \"umbrella_on_ground\": \"⛱\",\n    \"umbrella_with_rain_drops\": \"☔\",\n    \"unamused_face\": \"😒\",\n    \"unicorn\": \"🦄\",\n    \"unlocked\": \"🔓\",\n    \"up-down_arrow\": \"↕\",\n    \"up-left_arrow\": \"↖\",\n    \"up-right_arrow\": \"↗\",\n    \"up_arrow\": \"⬆\",\n    \"upside-down_face\": \"🙃\",\n    \"upwards_button\": \"🔼\",\n    \"vampire\": \"🧛\",\n    \"vampire_dark_skin_tone\": \"🧛🏿\",\n    \"vampire_light_skin_tone\": \"🧛🏻\",\n    \"vampire_medium-dark_skin_tone\": \"🧛🏾\",\n    \"vampire_medium-light_skin_tone\": \"🧛🏼\",\n    \"vampire_medium_skin_tone\": \"🧛🏽\",\n    \"vertical_traffic_light\": \"🚦\",\n    \"vibration_mode\": \"📳\",\n    \"victory_hand\": \"✌\",\n    \"victory_hand_dark_skin_tone\": \"✌🏿\",\n    \"victory_hand_light_skin_tone\": \"✌🏻\",\n    \"victory_hand_medium-dark_skin_tone\": \"✌🏾\",\n    \"victory_hand_medium-light_skin_tone\": \"✌🏼\",\n    \"victory_hand_medium_skin_tone\": \"✌🏽\",\n    \"video_camera\": \"📹\",\n    \"video_game\": \"🎮\",\n    \"videocassette\": \"📼\",\n    \"violin\": \"🎻\",\n    \"volcano\": \"🌋\",\n    \"volleyball\": \"🏐\",\n    \"vulcan_salute\": \"🖖\",\n    \"vulcan_salute_dark_skin_tone\": \"🖖🏿\",\n    \"vulcan_salute_light_skin_tone\": \"🖖🏻\",\n    \"vulcan_salute_medium-dark_skin_tone\": \"🖖🏾\",\n    \"vulcan_salute_medium-light_skin_tone\": \"🖖🏼\",\n    \"vulcan_salute_medium_skin_tone\": \"🖖🏽\",\n    \"waffle\": \"🧇\",\n    \"waning_crescent_moon\": \"🌘\",\n    \"waning_gibbous_moon\": \"🌖\",\n    \"warning\": \"⚠\",\n    \"wastebasket\": \"🗑\",\n    \"watch\": \"⌚\",\n    \"water_buffalo\": \"🐃\",\n    \"water_closet\": \"🚾\",\n    \"water_pistol\": \"🔫\",\n    \"water_wave\": \"🌊\",\n    \"watermelon\": \"🍉\",\n    \"waving_hand\": \"👋\",\n    \"waving_hand_dark_skin_tone\": \"👋🏿\",\n    \"waving_hand_light_skin_tone\": \"👋🏻\",\n    \"waving_hand_medium-dark_skin_tone\": \"👋🏾\",\n    \"waving_hand_medium-light_skin_tone\": \"👋🏼\",\n    \"waving_hand_medium_skin_tone\": \"👋🏽\",\n    \"wavy_dash\": \"〰\",\n    \"waxing_crescent_moon\": \"🌒\",\n    \"waxing_gibbous_moon\": \"🌔\",\n    \"weary_cat\": \"🙀\",\n    \"weary_face\": \"😩\",\n    \"wedding\": \"💒\",\n    \"whale\": \"🐋\",\n    \"wheel\": \"🛞\",\n    \"wheel_of_dharma\": \"☸\",\n    \"wheelchair_symbol\": \"♿\",\n    \"white_cane\": \"🦯\",\n    \"white_circle\": \"⚪\",\n    \"white_exclamation_mark\": \"❕\",\n    \"white_flag\": \"🏳\",\n    \"white_flower\": \"💮\",\n    \"white_hair\": \"🦳\",\n    \"white_heart\": \"🤍\",\n    \"white_large_square\": \"⬜\",\n    \"white_medium-small_square\": \"◽\",\n    \"white_medium_square\": \"◻\",\n    \"white_question_mark\": \"❔\",\n    \"white_small_square\": \"▫\",\n    \"white_square_button\": \"🔳\",\n    \"wilted_flower\": \"🥀\",\n    \"wind_chime\": \"🎐\",\n    \"wind_face\": \"🌬\",\n    \"window\": \"🪟\",\n    \"wine_glass\": \"🍷\",\n    \"wing\": \"🪽\",\n    \"winking_face\": \"😉\",\n    \"winking_face_with_tongue\": \"😜\",\n    \"wireless\": \"🛜\",\n    \"wolf\": \"🐺\",\n    \"woman\": \"👩\",\n    \"woman_and_man_holding_hands\": \"👫\",\n    \"woman_and_man_holding_hands_dark_skin_tone\": \"👫🏿\",\n    \"woman_and_man_holding_hands_dark_skin_tone_light_skin_tone\": \"👩🏿‍🤝‍👨🏻\",\n    (\n        \"woman_and_man_holding_hands_dark_skin_tone_medium-dark_skin_tone\"\n    ): \"👩🏿‍🤝‍👨🏾\",\n    (\n        \"woman_and_man_holding_hands_dark_skin_tone_medium-light_skin_tone\"\n    ): \"👩🏿‍🤝‍👨🏼\",\n    (\n        \"woman_and_man_holding_hands_dark_skin_tone_medium_skin_tone\"\n    ): \"👩🏿‍🤝‍👨🏽\",\n    \"woman_and_man_holding_hands_light_skin_tone\": \"👫🏻\",\n    \"woman_and_man_holding_hands_light_skin_tone_dark_skin_tone\": \"👩🏻‍🤝‍👨🏿\",\n    (\n        \"woman_and_man_holding_hands_light_skin_tone_medium-dark_skin_tone\"\n    ): \"👩🏻‍🤝‍👨🏾\",\n    (\n        \"woman_and_man_holding_hands_light_skin_tone_medium-light_skin_tone\"\n    ): \"👩🏻‍🤝‍👨🏼\",\n    (\n        \"woman_and_man_holding_hands_light_skin_tone_medium_skin_tone\"\n    ): \"👩🏻‍🤝‍👨🏽\",\n    \"woman_and_man_holding_hands_medium-dark_skin_tone\": \"👫🏾\",\n    (\n        \"woman_and_man_holding_hands_medium-dark_skin_tone_dark_skin_tone\"\n    ): \"👩🏾‍🤝‍👨🏿\",\n    (\n        \"woman_and_man_holding_hands_medium-dark_skin_tone_light_skin_tone\"\n    ): \"👩🏾‍🤝‍👨🏻\",\n    (\n        \"woman_and_man_holding_hands_medium\"\n        \"-dark_skin_tone_medium-light_skin_tone\"\n    ): \"👩🏾‍🤝‍👨🏼\",\n    (\n        \"woman_and_man_holding_hands_medium-dark_skin_tone_medium_skin_tone\"\n    ): \"👩🏾‍🤝‍👨🏽\",\n    \"woman_and_man_holding_hands_medium-light_skin_tone\": \"👫🏼\",\n    (\n        \"woman_and_man_holding_hands_medium-light_skin_tone_dark_skin_tone\"\n    ): \"👩🏼‍🤝‍👨🏿\",\n    (\n        \"woman_and_man_holding_hands_medium-light_skin_tone_light_skin_tone\"\n    ): \"👩🏼‍🤝‍👨🏻\",\n    (\n        \"woman_and_man_holding_hands_medium-\"\n        \"light_skin_tone_medium-dark_skin_tone\"\n    ): \"👩🏼‍🤝‍👨🏾\",\n    (\n        \"woman_and_man_holding_hands_medium-light_skin_tone_medium_skin_tone\"\n    ): \"👩🏼‍🤝‍👨🏽\",\n    \"woman_and_man_holding_hands_medium_skin_tone\": \"👫🏽\",\n    (\n        \"woman_and_man_holding_hands_medium_skin_tone_dark_skin_tone\"\n    ): \"👩🏽‍🤝‍👨🏿\",\n    (\n        \"woman_and_man_holding_hands_medium_skin_tone_light_skin_tone\"\n    ): \"👩🏽‍🤝‍👨🏻\",\n    (\n        \"woman_and_man_holding_hands_medium_skin_tone_medium-dark_skin_tone\"\n    ): \"👩🏽‍🤝‍👨🏾\",\n    (\n        \"woman_and_man_holding_hands_medium_skin_tone_medium-light_skin_tone\"\n    ): \"👩🏽‍🤝‍👨🏼\",\n    \"woman_artist\": \"👩‍🎨\",\n    \"woman_artist_dark_skin_tone\": \"👩🏿‍🎨\",\n    \"woman_artist_light_skin_tone\": \"👩🏻‍🎨\",\n    \"woman_artist_medium-dark_skin_tone\": \"👩🏾‍🎨\",\n    \"woman_artist_medium-light_skin_tone\": \"👩🏼‍🎨\",\n    \"woman_artist_medium_skin_tone\": \"👩🏽‍🎨\",\n    \"woman_astronaut\": \"👩‍🚀\",\n    \"woman_astronaut_dark_skin_tone\": \"👩🏿‍🚀\",\n    \"woman_astronaut_light_skin_tone\": \"👩🏻‍🚀\",\n    \"woman_astronaut_medium-dark_skin_tone\": \"👩🏾‍🚀\",\n    \"woman_astronaut_medium-light_skin_tone\": \"👩🏼‍🚀\",\n    \"woman_astronaut_medium_skin_tone\": \"👩🏽‍🚀\",\n    \"woman_bald\": \"👩‍🦲\",\n    \"woman_beard\": \"🧔‍♀\",\n    \"woman_biking\": \"🚴‍♀\",\n    \"woman_biking_dark_skin_tone\": \"🚴🏿‍♀\",\n    \"woman_biking_light_skin_tone\": \"🚴🏻‍♀\",\n    \"woman_biking_medium-dark_skin_tone\": \"🚴🏾‍♀\",\n    \"woman_biking_medium-light_skin_tone\": \"🚴🏼‍♀\",\n    \"woman_biking_medium_skin_tone\": \"🚴🏽‍♀\",\n    \"woman_blond_hair\": \"👱‍♀\",\n    \"woman_bouncing_ball\": \"⛹‍♀\",\n    \"woman_bouncing_ball_dark_skin_tone\": \"⛹🏿‍♀\",\n    \"woman_bouncing_ball_light_skin_tone\": \"⛹🏻‍♀\",\n    \"woman_bouncing_ball_medium-dark_skin_tone\": \"⛹🏾‍♀\",\n    \"woman_bouncing_ball_medium-light_skin_tone\": \"⛹🏼‍♀\",\n    \"woman_bouncing_ball_medium_skin_tone\": \"⛹🏽‍♀\",\n    \"woman_bowing\": \"🙇‍♀\",\n    \"woman_bowing_dark_skin_tone\": \"🙇🏿‍♀\",\n    \"woman_bowing_light_skin_tone\": \"🙇🏻‍♀\",\n    \"woman_bowing_medium-dark_skin_tone\": \"🙇🏾‍♀\",\n    \"woman_bowing_medium-light_skin_tone\": \"🙇🏼‍♀\",\n    \"woman_bowing_medium_skin_tone\": \"🙇🏽‍♀\",\n    \"woman_cartwheeling\": \"🤸‍♀\",\n    \"woman_cartwheeling_dark_skin_tone\": \"🤸🏿‍♀\",\n    \"woman_cartwheeling_light_skin_tone\": \"🤸🏻‍♀\",\n    \"woman_cartwheeling_medium-dark_skin_tone\": \"🤸🏾‍♀\",\n    \"woman_cartwheeling_medium-light_skin_tone\": \"🤸🏼‍♀\",\n    \"woman_cartwheeling_medium_skin_tone\": \"🤸🏽‍♀\",\n    \"woman_climbing\": \"🧗‍♀\",\n    \"woman_climbing_dark_skin_tone\": \"🧗🏿‍♀\",\n    \"woman_climbing_light_skin_tone\": \"🧗🏻‍♀\",\n    \"woman_climbing_medium-dark_skin_tone\": \"🧗🏾‍♀\",\n    \"woman_climbing_medium-light_skin_tone\": \"🧗🏼‍♀\",\n    \"woman_climbing_medium_skin_tone\": \"🧗🏽‍♀\",\n    \"woman_construction_worker\": \"👷‍♀\",\n    \"woman_construction_worker_dark_skin_tone\": \"👷🏿‍♀\",\n    \"woman_construction_worker_light_skin_tone\": \"👷🏻‍♀\",\n    \"woman_construction_worker_medium-dark_skin_tone\": \"👷🏾‍♀\",\n    \"woman_construction_worker_medium-light_skin_tone\": \"👷🏼‍♀\",\n    \"woman_construction_worker_medium_skin_tone\": \"👷🏽‍♀\",\n    \"woman_cook\": \"👩‍🍳\",\n    \"woman_cook_dark_skin_tone\": \"👩🏿‍🍳\",\n    \"woman_cook_light_skin_tone\": \"👩🏻‍🍳\",\n    \"woman_cook_medium-dark_skin_tone\": \"👩🏾‍🍳\",\n    \"woman_cook_medium-light_skin_tone\": \"👩🏼‍🍳\",\n    \"woman_cook_medium_skin_tone\": \"👩🏽‍🍳\",\n    \"woman_curly_hair\": \"👩‍🦱\",\n    \"woman_dancing\": \"💃\",\n    \"woman_dancing_dark_skin_tone\": \"💃🏿\",\n    \"woman_dancing_light_skin_tone\": \"💃🏻\",\n    \"woman_dancing_medium-dark_skin_tone\": \"💃🏾\",\n    \"woman_dancing_medium-light_skin_tone\": \"💃🏼\",\n    \"woman_dancing_medium_skin_tone\": \"💃🏽\",\n    \"woman_dark_skin_tone\": \"👩🏿\",\n    \"woman_dark_skin_tone_bald\": \"👩🏿‍🦲\",\n    \"woman_dark_skin_tone_beard\": \"🧔🏿‍♀\",\n    \"woman_dark_skin_tone_blond_hair\": \"👱🏿‍♀\",\n    \"woman_dark_skin_tone_curly_hair\": \"👩🏿‍🦱\",\n    \"woman_dark_skin_tone_red_hair\": \"👩🏿‍🦰\",\n    \"woman_dark_skin_tone_white_hair\": \"👩🏿‍🦳\",\n    \"woman_detective\": \"🕵‍♀\",\n    \"woman_detective_dark_skin_tone\": \"🕵🏿‍♀\",\n    \"woman_detective_light_skin_tone\": \"🕵🏻‍♀\",\n    \"woman_detective_medium-dark_skin_tone\": \"🕵🏾‍♀\",\n    \"woman_detective_medium-light_skin_tone\": \"🕵🏼‍♀\",\n    \"woman_detective_medium_skin_tone\": \"🕵🏽‍♀\",\n    \"woman_elf\": \"🧝‍♀\",\n    \"woman_elf_dark_skin_tone\": \"🧝🏿‍♀\",\n    \"woman_elf_light_skin_tone\": \"🧝🏻‍♀\",\n    \"woman_elf_medium-dark_skin_tone\": \"🧝🏾‍♀\",\n    \"woman_elf_medium-light_skin_tone\": \"🧝🏼‍♀\",\n    \"woman_elf_medium_skin_tone\": \"🧝🏽‍♀\",\n    \"woman_facepalming\": \"🤦‍♀\",\n    \"woman_facepalming_dark_skin_tone\": \"🤦🏿‍♀\",\n    \"woman_facepalming_light_skin_tone\": \"🤦🏻‍♀\",\n    \"woman_facepalming_medium-dark_skin_tone\": \"🤦🏾‍♀\",\n    \"woman_facepalming_medium-light_skin_tone\": \"🤦🏼‍♀\",\n    \"woman_facepalming_medium_skin_tone\": \"🤦🏽‍♀\",\n    \"woman_factory_worker\": \"👩‍🏭\",\n    \"woman_factory_worker_dark_skin_tone\": \"👩🏿‍🏭\",\n    \"woman_factory_worker_light_skin_tone\": \"👩🏻‍🏭\",\n    \"woman_factory_worker_medium-dark_skin_tone\": \"👩🏾‍🏭\",\n    \"woman_factory_worker_medium-light_skin_tone\": \"👩🏼‍🏭\",\n    \"woman_factory_worker_medium_skin_tone\": \"👩🏽‍🏭\",\n    \"woman_fairy\": \"🧚‍♀\",\n    \"woman_fairy_dark_skin_tone\": \"🧚🏿‍♀\",\n    \"woman_fairy_light_skin_tone\": \"🧚🏻‍♀\",\n    \"woman_fairy_medium-dark_skin_tone\": \"🧚🏾‍♀\",\n    \"woman_fairy_medium-light_skin_tone\": \"🧚🏼‍♀\",\n    \"woman_fairy_medium_skin_tone\": \"🧚🏽‍♀\",\n    \"woman_farmer\": \"👩‍🌾\",\n    \"woman_farmer_dark_skin_tone\": \"👩🏿‍🌾\",\n    \"woman_farmer_light_skin_tone\": \"👩🏻‍🌾\",\n    \"woman_farmer_medium-dark_skin_tone\": \"👩🏾‍🌾\",\n    \"woman_farmer_medium-light_skin_tone\": \"👩🏼‍🌾\",\n    \"woman_farmer_medium_skin_tone\": \"👩🏽‍🌾\",\n    \"woman_feeding_baby\": \"👩‍🍼\",\n    \"woman_feeding_baby_dark_skin_tone\": \"👩🏿‍🍼\",\n    \"woman_feeding_baby_light_skin_tone\": \"👩🏻‍🍼\",\n    \"woman_feeding_baby_medium-dark_skin_tone\": \"👩🏾‍🍼\",\n    \"woman_feeding_baby_medium-light_skin_tone\": \"👩🏼‍🍼\",\n    \"woman_feeding_baby_medium_skin_tone\": \"👩🏽‍🍼\",\n    \"woman_firefighter\": \"👩‍🚒\",\n    \"woman_firefighter_dark_skin_tone\": \"👩🏿‍🚒\",\n    \"woman_firefighter_light_skin_tone\": \"👩🏻‍🚒\",\n    \"woman_firefighter_medium-dark_skin_tone\": \"👩🏾‍🚒\",\n    \"woman_firefighter_medium-light_skin_tone\": \"👩🏼‍🚒\",\n    \"woman_firefighter_medium_skin_tone\": \"👩🏽‍🚒\",\n    \"woman_frowning\": \"🙍‍♀\",\n    \"woman_frowning_dark_skin_tone\": \"🙍🏿‍♀\",\n    \"woman_frowning_light_skin_tone\": \"🙍🏻‍♀\",\n    \"woman_frowning_medium-dark_skin_tone\": \"🙍🏾‍♀\",\n    \"woman_frowning_medium-light_skin_tone\": \"🙍🏼‍♀\",\n    \"woman_frowning_medium_skin_tone\": \"🙍🏽‍♀\",\n    \"woman_genie\": \"🧞‍♀\",\n    \"woman_gesturing_NO\": \"🙅‍♀\",\n    \"woman_gesturing_NO_dark_skin_tone\": \"🙅🏿‍♀\",\n    \"woman_gesturing_NO_light_skin_tone\": \"🙅🏻‍♀\",\n    \"woman_gesturing_NO_medium-dark_skin_tone\": \"🙅🏾‍♀\",\n    \"woman_gesturing_NO_medium-light_skin_tone\": \"🙅🏼‍♀\",\n    \"woman_gesturing_NO_medium_skin_tone\": \"🙅🏽‍♀\",\n    \"woman_gesturing_OK\": \"🙆‍♀\",\n    \"woman_gesturing_OK_dark_skin_tone\": \"🙆🏿‍♀\",\n    \"woman_gesturing_OK_light_skin_tone\": \"🙆🏻‍♀\",\n    \"woman_gesturing_OK_medium-dark_skin_tone\": \"🙆🏾‍♀\",\n    \"woman_gesturing_OK_medium-light_skin_tone\": \"🙆🏼‍♀\",\n    \"woman_gesturing_OK_medium_skin_tone\": \"🙆🏽‍♀\",\n    \"woman_getting_haircut\": \"💇‍♀\",\n    \"woman_getting_haircut_dark_skin_tone\": \"💇🏿‍♀\",\n    \"woman_getting_haircut_light_skin_tone\": \"💇🏻‍♀\",\n    \"woman_getting_haircut_medium-dark_skin_tone\": \"💇🏾‍♀\",\n    \"woman_getting_haircut_medium-light_skin_tone\": \"💇🏼‍♀\",\n    \"woman_getting_haircut_medium_skin_tone\": \"💇🏽‍♀\",\n    \"woman_getting_massage\": \"💆‍♀\",\n    \"woman_getting_massage_dark_skin_tone\": \"💆🏿‍♀\",\n    \"woman_getting_massage_light_skin_tone\": \"💆🏻‍♀\",\n    \"woman_getting_massage_medium-dark_skin_tone\": \"💆🏾‍♀\",\n    \"woman_getting_massage_medium-light_skin_tone\": \"💆🏼‍♀\",\n    \"woman_getting_massage_medium_skin_tone\": \"💆🏽‍♀\",\n    \"woman_golfing\": \"🏌‍♀\",\n    \"woman_golfing_dark_skin_tone\": \"🏌🏿‍♀\",\n    \"woman_golfing_light_skin_tone\": \"🏌🏻‍♀\",\n    \"woman_golfing_medium-dark_skin_tone\": \"🏌🏾‍♀\",\n    \"woman_golfing_medium-light_skin_tone\": \"🏌🏼‍♀\",\n    \"woman_golfing_medium_skin_tone\": \"🏌🏽‍♀\",\n    \"woman_guard\": \"💂‍♀\",\n    \"woman_guard_dark_skin_tone\": \"💂🏿‍♀\",\n    \"woman_guard_light_skin_tone\": \"💂🏻‍♀\",\n    \"woman_guard_medium-dark_skin_tone\": \"💂🏾‍♀\",\n    \"woman_guard_medium-light_skin_tone\": \"💂🏼‍♀\",\n    \"woman_guard_medium_skin_tone\": \"💂🏽‍♀\",\n    \"woman_health_worker\": \"👩‍⚕\",\n    \"woman_health_worker_dark_skin_tone\": \"👩🏿‍⚕\",\n    \"woman_health_worker_light_skin_tone\": \"👩🏻‍⚕\",\n    \"woman_health_worker_medium-dark_skin_tone\": \"👩🏾‍⚕\",\n    \"woman_health_worker_medium-light_skin_tone\": \"👩🏼‍⚕\",\n    \"woman_health_worker_medium_skin_tone\": \"👩🏽‍⚕\",\n    \"woman_in_lotus_position\": \"🧘‍♀\",\n    \"woman_in_lotus_position_dark_skin_tone\": \"🧘🏿‍♀\",\n    \"woman_in_lotus_position_light_skin_tone\": \"🧘🏻‍♀\",\n    \"woman_in_lotus_position_medium-dark_skin_tone\": \"🧘🏾‍♀\",\n    \"woman_in_lotus_position_medium-light_skin_tone\": \"🧘🏼‍♀\",\n    \"woman_in_lotus_position_medium_skin_tone\": \"🧘🏽‍♀\",\n    \"woman_in_manual_wheelchair\": \"👩‍🦽\",\n    \"woman_in_manual_wheelchair_dark_skin_tone\": \"👩🏿‍🦽\",\n    \"woman_in_manual_wheelchair_light_skin_tone\": \"👩🏻‍🦽\",\n    \"woman_in_manual_wheelchair_medium-dark_skin_tone\": \"👩🏾‍🦽\",\n    \"woman_in_manual_wheelchair_medium-light_skin_tone\": \"👩🏼‍🦽\",\n    \"woman_in_manual_wheelchair_medium_skin_tone\": \"👩🏽‍🦽\",\n    \"woman_in_motorized_wheelchair\": \"👩‍🦼\",\n    \"woman_in_motorized_wheelchair_dark_skin_tone\": \"👩🏿‍🦼\",\n    \"woman_in_motorized_wheelchair_light_skin_tone\": \"👩🏻‍🦼\",\n    \"woman_in_motorized_wheelchair_medium-dark_skin_tone\": \"👩🏾‍🦼\",\n    \"woman_in_motorized_wheelchair_medium-light_skin_tone\": \"👩🏼‍🦼\",\n    \"woman_in_motorized_wheelchair_medium_skin_tone\": \"👩🏽‍🦼\",\n    \"woman_in_steamy_room\": \"🧖‍♀\",\n    \"woman_in_steamy_room_dark_skin_tone\": \"🧖🏿‍♀\",\n    \"woman_in_steamy_room_light_skin_tone\": \"🧖🏻‍♀\",\n    \"woman_in_steamy_room_medium-dark_skin_tone\": \"🧖🏾‍♀\",\n    \"woman_in_steamy_room_medium-light_skin_tone\": \"🧖🏼‍♀\",\n    \"woman_in_steamy_room_medium_skin_tone\": \"🧖🏽‍♀\",\n    \"woman_in_tuxedo\": \"🤵‍♀\",\n    \"woman_in_tuxedo_dark_skin_tone\": \"🤵🏿‍♀\",\n    \"woman_in_tuxedo_light_skin_tone\": \"🤵🏻‍♀\",\n    \"woman_in_tuxedo_medium-dark_skin_tone\": \"🤵🏾‍♀\",\n    \"woman_in_tuxedo_medium-light_skin_tone\": \"🤵🏼‍♀\",\n    \"woman_in_tuxedo_medium_skin_tone\": \"🤵🏽‍♀\",\n    \"woman_judge\": \"👩‍⚖\",\n    \"woman_judge_dark_skin_tone\": \"👩🏿‍⚖\",\n    \"woman_judge_light_skin_tone\": \"👩🏻‍⚖\",\n    \"woman_judge_medium-dark_skin_tone\": \"👩🏾‍⚖\",\n    \"woman_judge_medium-light_skin_tone\": \"👩🏼‍⚖\",\n    \"woman_judge_medium_skin_tone\": \"👩🏽‍⚖\",\n    \"woman_juggling\": \"🤹‍♀\",\n    \"woman_juggling_dark_skin_tone\": \"🤹🏿‍♀\",\n    \"woman_juggling_light_skin_tone\": \"🤹🏻‍♀\",\n    \"woman_juggling_medium-dark_skin_tone\": \"🤹🏾‍♀\",\n    \"woman_juggling_medium-light_skin_tone\": \"🤹🏼‍♀\",\n    \"woman_juggling_medium_skin_tone\": \"🤹🏽‍♀\",\n    \"woman_kneeling\": \"🧎‍♀\",\n    \"woman_kneeling_dark_skin_tone\": \"🧎🏿‍♀\",\n    \"woman_kneeling_light_skin_tone\": \"🧎🏻‍♀\",\n    \"woman_kneeling_medium-dark_skin_tone\": \"🧎🏾‍♀\",\n    \"woman_kneeling_medium-light_skin_tone\": \"🧎🏼‍♀\",\n    \"woman_kneeling_medium_skin_tone\": \"🧎🏽‍♀\",\n    \"woman_lifting_weights\": \"🏋‍♀\",\n    \"woman_lifting_weights_dark_skin_tone\": \"🏋🏿‍♀\",\n    \"woman_lifting_weights_light_skin_tone\": \"🏋🏻‍♀\",\n    \"woman_lifting_weights_medium-dark_skin_tone\": \"🏋🏾‍♀\",\n    \"woman_lifting_weights_medium-light_skin_tone\": \"🏋🏼‍♀\",\n    \"woman_lifting_weights_medium_skin_tone\": \"🏋🏽‍♀\",\n    \"woman_light_skin_tone\": \"👩🏻\",\n    \"woman_light_skin_tone_bald\": \"👩🏻‍🦲\",\n    \"woman_light_skin_tone_beard\": \"🧔🏻‍♀\",\n    \"woman_light_skin_tone_blond_hair\": \"👱🏻‍♀\",\n    \"woman_light_skin_tone_curly_hair\": \"👩🏻‍🦱\",\n    \"woman_light_skin_tone_red_hair\": \"👩🏻‍🦰\",\n    \"woman_light_skin_tone_white_hair\": \"👩🏻‍🦳\",\n    \"woman_mage\": \"🧙‍♀\",\n    \"woman_mage_dark_skin_tone\": \"🧙🏿‍♀\",\n    \"woman_mage_light_skin_tone\": \"🧙🏻‍♀\",\n    \"woman_mage_medium-dark_skin_tone\": \"🧙🏾‍♀\",\n    \"woman_mage_medium-light_skin_tone\": \"🧙🏼‍♀\",\n    \"woman_mage_medium_skin_tone\": \"🧙🏽‍♀\",\n    \"woman_mechanic\": \"👩‍🔧\",\n    \"woman_mechanic_dark_skin_tone\": \"👩🏿‍🔧\",\n    \"woman_mechanic_light_skin_tone\": \"👩🏻‍🔧\",\n    \"woman_mechanic_medium-dark_skin_tone\": \"👩🏾‍🔧\",\n    \"woman_mechanic_medium-light_skin_tone\": \"👩🏼‍🔧\",\n    \"woman_mechanic_medium_skin_tone\": \"👩🏽‍🔧\",\n    \"woman_medium-dark_skin_tone\": \"👩🏾\",\n    \"woman_medium-dark_skin_tone_bald\": \"👩🏾‍🦲\",\n    \"woman_medium-dark_skin_tone_beard\": \"🧔🏾‍♀\",\n    \"woman_medium-dark_skin_tone_blond_hair\": \"👱🏾‍♀\",\n    \"woman_medium-dark_skin_tone_curly_hair\": \"👩🏾‍🦱\",\n    \"woman_medium-dark_skin_tone_red_hair\": \"👩🏾‍🦰\",\n    \"woman_medium-dark_skin_tone_white_hair\": \"👩🏾‍🦳\",\n    \"woman_medium-light_skin_tone\": \"👩🏼\",\n    \"woman_medium-light_skin_tone_bald\": \"👩🏼‍🦲\",\n    \"woman_medium-light_skin_tone_beard\": \"🧔🏼‍♀\",\n    \"woman_medium-light_skin_tone_blond_hair\": \"👱🏼‍♀\",\n    \"woman_medium-light_skin_tone_curly_hair\": \"👩🏼‍🦱\",\n    \"woman_medium-light_skin_tone_red_hair\": \"👩🏼‍🦰\",\n    \"woman_medium-light_skin_tone_white_hair\": \"👩🏼‍🦳\",\n    \"woman_medium_skin_tone\": \"👩🏽\",\n    \"woman_medium_skin_tone_bald\": \"👩🏽‍🦲\",\n    \"woman_medium_skin_tone_beard\": \"🧔🏽‍♀\",\n    \"woman_medium_skin_tone_blond_hair\": \"👱🏽‍♀\",\n    \"woman_medium_skin_tone_curly_hair\": \"👩🏽‍🦱\",\n    \"woman_medium_skin_tone_red_hair\": \"👩🏽‍🦰\",\n    \"woman_medium_skin_tone_white_hair\": \"👩🏽‍🦳\",\n    \"woman_mountain_biking\": \"🚵‍♀\",\n    \"woman_mountain_biking_dark_skin_tone\": \"🚵🏿‍♀\",\n    \"woman_mountain_biking_light_skin_tone\": \"🚵🏻‍♀\",\n    \"woman_mountain_biking_medium-dark_skin_tone\": \"🚵🏾‍♀\",\n    \"woman_mountain_biking_medium-light_skin_tone\": \"🚵🏼‍♀\",\n    \"woman_mountain_biking_medium_skin_tone\": \"🚵🏽‍♀\",\n    \"woman_office_worker\": \"👩‍💼\",\n    \"woman_office_worker_dark_skin_tone\": \"👩🏿‍💼\",\n    \"woman_office_worker_light_skin_tone\": \"👩🏻‍💼\",\n    \"woman_office_worker_medium-dark_skin_tone\": \"👩🏾‍💼\",\n    \"woman_office_worker_medium-light_skin_tone\": \"👩🏼‍💼\",\n    \"woman_office_worker_medium_skin_tone\": \"👩🏽‍💼\",\n    \"woman_pilot\": \"👩‍✈\",\n    \"woman_pilot_dark_skin_tone\": \"👩🏿‍✈\",\n    \"woman_pilot_light_skin_tone\": \"👩🏻‍✈\",\n    \"woman_pilot_medium-dark_skin_tone\": \"👩🏾‍✈\",\n    \"woman_pilot_medium-light_skin_tone\": \"👩🏼‍✈\",\n    \"woman_pilot_medium_skin_tone\": \"👩🏽‍✈\",\n    \"woman_playing_handball\": \"🤾‍♀\",\n    \"woman_playing_handball_dark_skin_tone\": \"🤾🏿‍♀\",\n    \"woman_playing_handball_light_skin_tone\": \"🤾🏻‍♀\",\n    \"woman_playing_handball_medium-dark_skin_tone\": \"🤾🏾‍♀\",\n    \"woman_playing_handball_medium-light_skin_tone\": \"🤾🏼‍♀\",\n    \"woman_playing_handball_medium_skin_tone\": \"🤾🏽‍♀\",\n    \"woman_playing_water_polo\": \"🤽‍♀\",\n    \"woman_playing_water_polo_dark_skin_tone\": \"🤽🏿‍♀\",\n    \"woman_playing_water_polo_light_skin_tone\": \"🤽🏻‍♀\",\n    \"woman_playing_water_polo_medium-dark_skin_tone\": \"🤽🏾‍♀\",\n    \"woman_playing_water_polo_medium-light_skin_tone\": \"🤽🏼‍♀\",\n    \"woman_playing_water_polo_medium_skin_tone\": \"🤽🏽‍♀\",\n    \"woman_police_officer\": \"👮‍♀\",\n    \"woman_police_officer_dark_skin_tone\": \"👮🏿‍♀\",\n    \"woman_police_officer_light_skin_tone\": \"👮🏻‍♀\",\n    \"woman_police_officer_medium-dark_skin_tone\": \"👮🏾‍♀\",\n    \"woman_police_officer_medium-light_skin_tone\": \"👮🏼‍♀\",\n    \"woman_police_officer_medium_skin_tone\": \"👮🏽‍♀\",\n    \"woman_pouting\": \"🙎‍♀\",\n    \"woman_pouting_dark_skin_tone\": \"🙎🏿‍♀\",\n    \"woman_pouting_light_skin_tone\": \"🙎🏻‍♀\",\n    \"woman_pouting_medium-dark_skin_tone\": \"🙎🏾‍♀\",\n    \"woman_pouting_medium-light_skin_tone\": \"🙎🏼‍♀\",\n    \"woman_pouting_medium_skin_tone\": \"🙎🏽‍♀\",\n    \"woman_raising_hand\": \"🙋‍♀\",\n    \"woman_raising_hand_dark_skin_tone\": \"🙋🏿‍♀\",\n    \"woman_raising_hand_light_skin_tone\": \"🙋🏻‍♀\",\n    \"woman_raising_hand_medium-dark_skin_tone\": \"🙋🏾‍♀\",\n    \"woman_raising_hand_medium-light_skin_tone\": \"🙋🏼‍♀\",\n    \"woman_raising_hand_medium_skin_tone\": \"🙋🏽‍♀\",\n    \"woman_red_hair\": \"👩‍🦰\",\n    \"woman_rowing_boat\": \"🚣‍♀\",\n    \"woman_rowing_boat_dark_skin_tone\": \"🚣🏿‍♀\",\n    \"woman_rowing_boat_light_skin_tone\": \"🚣🏻‍♀\",\n    \"woman_rowing_boat_medium-dark_skin_tone\": \"🚣🏾‍♀\",\n    \"woman_rowing_boat_medium-light_skin_tone\": \"🚣🏼‍♀\",\n    \"woman_rowing_boat_medium_skin_tone\": \"🚣🏽‍♀\",\n    \"woman_running\": \"🏃‍♀\",\n    \"woman_running_dark_skin_tone\": \"🏃🏿‍♀\",\n    \"woman_running_light_skin_tone\": \"🏃🏻‍♀\",\n    \"woman_running_medium-dark_skin_tone\": \"🏃🏾‍♀\",\n    \"woman_running_medium-light_skin_tone\": \"🏃🏼‍♀\",\n    \"woman_running_medium_skin_tone\": \"🏃🏽‍♀\",\n    \"woman_scientist\": \"👩‍🔬\",\n    \"woman_scientist_dark_skin_tone\": \"👩🏿‍🔬\",\n    \"woman_scientist_light_skin_tone\": \"👩🏻‍🔬\",\n    \"woman_scientist_medium-dark_skin_tone\": \"👩🏾‍🔬\",\n    \"woman_scientist_medium-light_skin_tone\": \"👩🏼‍🔬\",\n    \"woman_scientist_medium_skin_tone\": \"👩🏽‍🔬\",\n    \"woman_shrugging\": \"🤷‍♀\",\n    \"woman_shrugging_dark_skin_tone\": \"🤷🏿‍♀\",\n    \"woman_shrugging_light_skin_tone\": \"🤷🏻‍♀\",\n    \"woman_shrugging_medium-dark_skin_tone\": \"🤷🏾‍♀\",\n    \"woman_shrugging_medium-light_skin_tone\": \"🤷🏼‍♀\",\n    \"woman_shrugging_medium_skin_tone\": \"🤷🏽‍♀\",\n    \"woman_singer\": \"👩‍🎤\",\n    \"woman_singer_dark_skin_tone\": \"👩🏿‍🎤\",\n    \"woman_singer_light_skin_tone\": \"👩🏻‍🎤\",\n    \"woman_singer_medium-dark_skin_tone\": \"👩🏾‍🎤\",\n    \"woman_singer_medium-light_skin_tone\": \"👩🏼‍🎤\",\n    \"woman_singer_medium_skin_tone\": \"👩🏽‍🎤\",\n    \"woman_standing\": \"🧍‍♀\",\n    \"woman_standing_dark_skin_tone\": \"🧍🏿‍♀\",\n    \"woman_standing_light_skin_tone\": \"🧍🏻‍♀\",\n    \"woman_standing_medium-dark_skin_tone\": \"🧍🏾‍♀\",\n    \"woman_standing_medium-light_skin_tone\": \"🧍🏼‍♀\",\n    \"woman_standing_medium_skin_tone\": \"🧍🏽‍♀\",\n    \"woman_student\": \"👩‍🎓\",\n    \"woman_student_dark_skin_tone\": \"👩🏿‍🎓\",\n    \"woman_student_light_skin_tone\": \"👩🏻‍🎓\",\n    \"woman_student_medium-dark_skin_tone\": \"👩🏾‍🎓\",\n    \"woman_student_medium-light_skin_tone\": \"👩🏼‍🎓\",\n    \"woman_student_medium_skin_tone\": \"👩🏽‍🎓\",\n    \"woman_superhero\": \"🦸‍♀\",\n    \"woman_superhero_dark_skin_tone\": \"🦸🏿‍♀\",\n    \"woman_superhero_light_skin_tone\": \"🦸🏻‍♀\",\n    \"woman_superhero_medium-dark_skin_tone\": \"🦸🏾‍♀\",\n    \"woman_superhero_medium-light_skin_tone\": \"🦸🏼‍♀\",\n    \"woman_superhero_medium_skin_tone\": \"🦸🏽‍♀\",\n    \"woman_supervillain\": \"🦹‍♀\",\n    \"woman_supervillain_dark_skin_tone\": \"🦹🏿‍♀\",\n    \"woman_supervillain_light_skin_tone\": \"🦹🏻‍♀\",\n    \"woman_supervillain_medium-dark_skin_tone\": \"🦹🏾‍♀\",\n    \"woman_supervillain_medium-light_skin_tone\": \"🦹🏼‍♀\",\n    \"woman_supervillain_medium_skin_tone\": \"🦹🏽‍♀\",\n    \"woman_surfing\": \"🏄‍♀\",\n    \"woman_surfing_dark_skin_tone\": \"🏄🏿‍♀\",\n    \"woman_surfing_light_skin_tone\": \"🏄🏻‍♀\",\n    \"woman_surfing_medium-dark_skin_tone\": \"🏄🏾‍♀\",\n    \"woman_surfing_medium-light_skin_tone\": \"🏄🏼‍♀\",\n    \"woman_surfing_medium_skin_tone\": \"🏄🏽‍♀\",\n    \"woman_swimming\": \"🏊‍♀\",\n    \"woman_swimming_dark_skin_tone\": \"🏊🏿‍♀\",\n    \"woman_swimming_light_skin_tone\": \"🏊🏻‍♀\",\n    \"woman_swimming_medium-dark_skin_tone\": \"🏊🏾‍♀\",\n    \"woman_swimming_medium-light_skin_tone\": \"🏊🏼‍♀\",\n    \"woman_swimming_medium_skin_tone\": \"🏊🏽‍♀\",\n    \"woman_teacher\": \"👩‍🏫\",\n    \"woman_teacher_dark_skin_tone\": \"👩🏿‍🏫\",\n    \"woman_teacher_light_skin_tone\": \"👩🏻‍🏫\",\n    \"woman_teacher_medium-dark_skin_tone\": \"👩🏾‍🏫\",\n    \"woman_teacher_medium-light_skin_tone\": \"👩🏼‍🏫\",\n    \"woman_teacher_medium_skin_tone\": \"👩🏽‍🏫\",\n    \"woman_technologist\": \"👩‍💻\",\n    \"woman_technologist_dark_skin_tone\": \"👩🏿‍💻\",\n    \"woman_technologist_light_skin_tone\": \"👩🏻‍💻\",\n    \"woman_technologist_medium-dark_skin_tone\": \"👩🏾‍💻\",\n    \"woman_technologist_medium-light_skin_tone\": \"👩🏼‍💻\",\n    \"woman_technologist_medium_skin_tone\": \"👩🏽‍💻\",\n    \"woman_tipping_hand\": \"💁‍♀\",\n    \"woman_tipping_hand_dark_skin_tone\": \"💁🏿‍♀\",\n    \"woman_tipping_hand_light_skin_tone\": \"💁🏻‍♀\",\n    \"woman_tipping_hand_medium-dark_skin_tone\": \"💁🏾‍♀\",\n    \"woman_tipping_hand_medium-light_skin_tone\": \"💁🏼‍♀\",\n    \"woman_tipping_hand_medium_skin_tone\": \"💁🏽‍♀\",\n    \"woman_vampire\": \"🧛‍♀\",\n    \"woman_vampire_dark_skin_tone\": \"🧛🏿‍♀\",\n    \"woman_vampire_light_skin_tone\": \"🧛🏻‍♀\",\n    \"woman_vampire_medium-dark_skin_tone\": \"🧛🏾‍♀\",\n    \"woman_vampire_medium-light_skin_tone\": \"🧛🏼‍♀\",\n    \"woman_vampire_medium_skin_tone\": \"🧛🏽‍♀\",\n    \"woman_walking\": \"🚶‍♀\",\n    \"woman_walking_dark_skin_tone\": \"🚶🏿‍♀\",\n    \"woman_walking_light_skin_tone\": \"🚶🏻‍♀\",\n    \"woman_walking_medium-dark_skin_tone\": \"🚶🏾‍♀\",\n    \"woman_walking_medium-light_skin_tone\": \"🚶🏼‍♀\",\n    \"woman_walking_medium_skin_tone\": \"🚶🏽‍♀\",\n    \"woman_wearing_turban\": \"👳‍♀\",\n    \"woman_wearing_turban_dark_skin_tone\": \"👳🏿‍♀\",\n    \"woman_wearing_turban_light_skin_tone\": \"👳🏻‍♀\",\n    \"woman_wearing_turban_medium-dark_skin_tone\": \"👳🏾‍♀\",\n    \"woman_wearing_turban_medium-light_skin_tone\": \"👳🏼‍♀\",\n    \"woman_wearing_turban_medium_skin_tone\": \"👳🏽‍♀\",\n    \"woman_white_hair\": \"👩‍🦳\",\n    \"woman_with_headscarf\": \"🧕\",\n    \"woman_with_headscarf_dark_skin_tone\": \"🧕🏿\",\n    \"woman_with_headscarf_light_skin_tone\": \"🧕🏻\",\n    \"woman_with_headscarf_medium-dark_skin_tone\": \"🧕🏾\",\n    \"woman_with_headscarf_medium-light_skin_tone\": \"🧕🏼\",\n    \"woman_with_headscarf_medium_skin_tone\": \"🧕🏽\",\n    \"woman_with_veil\": \"👰‍♀\",\n    \"woman_with_veil_dark_skin_tone\": \"👰🏿‍♀\",\n    \"woman_with_veil_light_skin_tone\": \"👰🏻‍♀\",\n    \"woman_with_veil_medium-dark_skin_tone\": \"👰🏾‍♀\",\n    \"woman_with_veil_medium-light_skin_tone\": \"👰🏼‍♀\",\n    \"woman_with_veil_medium_skin_tone\": \"👰🏽‍♀\",\n    \"woman_with_white_cane\": \"👩‍🦯\",\n    \"woman_with_white_cane_dark_skin_tone\": \"👩🏿‍🦯\",\n    \"woman_with_white_cane_light_skin_tone\": \"👩🏻‍🦯\",\n    \"woman_with_white_cane_medium-dark_skin_tone\": \"👩🏾‍🦯\",\n    \"woman_with_white_cane_medium-light_skin_tone\": \"👩🏼‍🦯\",\n    \"woman_with_white_cane_medium_skin_tone\": \"👩🏽‍🦯\",\n    \"woman_zombie\": \"🧟‍♀\",\n    \"woman’s_boot\": \"👢\",\n    \"woman’s_clothes\": \"👚\",\n    \"woman’s_hat\": \"👒\",\n    \"woman’s_sandal\": \"👡\",\n    \"women_holding_hands\": \"👭\",\n    \"women_holding_hands_dark_skin_tone\": \"👭🏿\",\n    \"women_holding_hands_dark_skin_tone_light_skin_tone\": \"👩🏿‍🤝‍👩🏻\",\n    \"women_holding_hands_dark_skin_tone_medium-dark_skin_tone\": \"👩🏿‍🤝‍👩🏾\",\n    \"women_holding_hands_dark_skin_tone_medium-light_skin_tone\": \"👩🏿‍🤝‍👩🏼\",\n    \"women_holding_hands_dark_skin_tone_medium_skin_tone\": \"👩🏿‍🤝‍👩🏽\",\n    \"women_holding_hands_light_skin_tone\": \"👭🏻\",\n    \"women_holding_hands_light_skin_tone_dark_skin_tone\": \"👩🏻‍🤝‍👩🏿\",\n    \"women_holding_hands_light_skin_tone_medium-dark_skin_tone\": \"👩🏻‍🤝‍👩🏾\",\n    \"women_holding_hands_light_skin_tone_medium-light_skin_tone\": \"👩🏻‍🤝‍👩🏼\",\n    \"women_holding_hands_light_skin_tone_medium_skin_tone\": \"👩🏻‍🤝‍👩🏽\",\n    \"women_holding_hands_medium-dark_skin_tone\": \"👭🏾\",\n    \"women_holding_hands_medium-dark_skin_tone_dark_skin_tone\": \"👩🏾‍🤝‍👩🏿\",\n    \"women_holding_hands_medium-dark_skin_tone_light_skin_tone\": \"👩🏾‍🤝‍👩🏻\",\n    (\n        \"women_holding_hands_medium-dark_skin_tone_medium-light_skin_tone\"\n    ): \"👩🏾‍🤝‍👩🏼\",\n    \"women_holding_hands_medium-dark_skin_tone_medium_skin_tone\": \"👩🏾‍🤝‍👩🏽\",\n    \"women_holding_hands_medium-light_skin_tone\": \"👭🏼\",\n    \"women_holding_hands_medium-light_skin_tone_dark_skin_tone\": \"👩🏼‍🤝‍👩🏿\",\n    \"women_holding_hands_medium-light_skin_tone_light_skin_tone\": \"👩🏼‍🤝‍👩🏻\",\n    (\n        \"women_holding_hands_medium-light_skin_tone_medium-dark_skin_tone\"\n    ): \"👩🏼‍🤝‍👩🏾\",\n    (\n        \"women_holding_hands_medium-light_skin_tone_medium_skin_tone\"\n    ): \"👩🏼‍🤝‍👩🏽\",\n    \"women_holding_hands_medium_skin_tone\": \"👭🏽\",\n    \"women_holding_hands_medium_skin_tone_dark_skin_tone\": \"👩🏽‍🤝‍👩🏿\",\n    \"women_holding_hands_medium_skin_tone_light_skin_tone\": \"👩🏽‍🤝‍👩🏻\",\n    \"women_holding_hands_medium_skin_tone_medium-dark_skin_tone\": \"👩🏽‍🤝‍👩🏾\",\n    (\n        \"women_holding_hands_medium_skin_tone_medium-light_skin_tone\"\n    ): \"👩🏽‍🤝‍👩🏼\",\n    \"women_with_bunny_ears\": \"👯‍♀\",\n    \"women_wrestling\": \"🤼‍♀\",\n    \"women’s_room\": \"🚺\",\n    \"wood\": \"🪵\",\n    \"woozy_face\": \"🥴\",\n    \"flushed\": \"🥴\",\n    \"world_map\": \"🗺\",\n    \"worm\": \"🪱\",\n    \"worried_face\": \"😟\",\n    \"wrapped_gift\": \"🎁\",\n    \"wrench\": \"🔧\",\n    \"writing_hand\": \"✍\",\n    \"writing_hand_dark_skin_tone\": \"✍🏿\",\n    \"writing_hand_light_skin_tone\": \"✍🏻\",\n    \"writing_hand_medium-dark_skin_tone\": \"✍🏾\",\n    \"writing_hand_medium-light_skin_tone\": \"✍🏼\",\n    \"writing_hand_medium_skin_tone\": \"✍🏽\",\n    \"x-ray\": \"🩻\",\n    \"yarn\": \"🧶\",\n    \"yawning_face\": \"🥱\",\n    \"yellow_circle\": \"🟡\",\n    \"yellow_heart\": \"💛\",\n    \"yellow_square\": \"🟨\",\n    \"yen_banknote\": \"💴\",\n    \"yin_yang\": \"☯\",\n    \"yo-yo\": \"🪀\",\n    \"zany_face\": \"🤪\",\n    \"zebra\": \"🦓\",\n    \"zipper-mouth_face\": \"🤐\",\n    \"zombie\": \"🧟\",\n    \"Åland_Islands\": \"🇦🇽\",\n}\n"
  },
  {
    "path": "guide/content/en/guide/advanced/class-based-views.md",
    "content": "# Class Based Views\n\n## Why use them?\n\n.. column::\n\n    ### The problem\n\n    A common pattern when designing an API is to have multiple functionality on the same endpoint that depends upon the HTTP method.\n\n    While both of these options work, they are not good design practices and may be hard to maintain over time as your project grows.\n\n.. column::\n\n    ```python\n    @app.get(\"/foo\")\n    async def foo_get(request):\n        ...\n\n    @app.post(\"/foo\")\n    async def foo_post(request):\n        ...\n\n    @app.put(\"/foo\")\n    async def foo_put(request):\n        ...\n\n    @app.route(\"/bar\", methods=[\"GET\", \"POST\", \"PATCH\"])\n    async def bar(request):\n        if request.method == \"GET\":\n            ...\n\n        elif request.method == \"POST\":\n            ...\n            \n        elif request.method == \"PATCH\":\n            ...\n    ```\n\n\n.. column::\n\n    ### The solution\n\n    Class-based views are simply classes that implement response behavior to requests. They provide a way to compartmentalize handling of different HTTP request types at the same endpoint.\n\n.. column::\n\n    ```python\n    from sanic.views import HTTPMethodView\n\n    class FooBar(HTTPMethodView):\n        async def get(self, request):\n            ...\n        \n        async def post(self, request):\n            ...\n        \n        async def put(self, request):\n            ...\n\n    app.add_route(FooBar.as_view(), \"/foobar\")\n    ```\n\n## Defining a view\n\nA class-based view should subclass :class:`sanic.views.HTTPMethodView`. You can then implement class methods with the name of the corresponding HTTP method. If a request is received that has no defined method, a `405: Method not allowed` response will be generated.\n\n.. column::\n\n    To register a class-based view on an endpoint, the `app.add_route` method is used. The first argument should be the defined class with the method `as_view` invoked, and the second should be the URL endpoint.\n\n    The available methods are:\n\n    - get\n    - post\n    - put\n    - patch\n    - delete\n    - head\n    - options\n\n.. column::\n\n    ```python\n    from sanic.views import HTTPMethodView\n    from sanic.response import text\n\n    class SimpleView(HTTPMethodView):\n\n      def get(self, request):\n          return text(\"I am get method\")\n\n      # You can also use async syntax\n      async def post(self, request):\n          return text(\"I am post method\")\n\n      def put(self, request):\n          return text(\"I am put method\")\n\n      def patch(self, request):\n          return text(\"I am patch method\")\n\n      def delete(self, request):\n          return text(\"I am delete method\")\n\n    app.add_route(SimpleView.as_view(), \"/\")\n    ```\n\n## Path parameters\n\n\n.. column::\n\n    You can use path parameters exactly as discussed in [the routing section](../basics/routing.md).\n\n.. column::\n\n    ```python\n    class NameView(HTTPMethodView):\n\n      def get(self, request, name):\n        return text(\"Hello {}\".format(name))\n\n    app.add_route(NameView.as_view(), \"/<name>\")\n    ```\n\n## Decorators\n\nAs discussed in [the decorators section](../best-practices/decorators.md), often you will need to add functionality to endpoints with the use of decorators. You have two options with CBV:\n\n1. Apply to _all_ HTTP methods in the view\n2. Apply individually to HTTP methods in the view\n\nLet's see what the options look like:\n\n.. column::\n\n    ### Apply to all methods\n\n    If you want to add any decorators to the class, you can set the `decorators` class variable. These will be applied to the class when `as_view` is called.\n\n.. column::\n\n    ```python\n    class ViewWithDecorator(HTTPMethodView):\n      decorators = [some_decorator_here]\n\n      def get(self, request, name):\n        return text(\"Hello I have a decorator\")\n\n      def post(self, request, name):\n        return text(\"Hello I also have a decorator\")\n\n    app.add_route(ViewWithDecorator.as_view(), \"/url\")\n    ```\n\n\n.. column::\n\n    ### Apply to individual methods\n\n    But if you just want to decorate some methods and not all methods, you can as shown here.\n\n.. column::\n\n    ```python\n    class ViewWithSomeDecorator(HTTPMethodView):\n\n        @staticmethod\n        @some_decorator_here\n        def get(request, name):\n            return text(\"Hello I have a decorator\")\n\n        def post(self, request, name):\n            return text(\"Hello I do not have any decorators\")\n\n        @some_decorator_here\n        def patch(self, request, name):\n            return text(\"Hello I have a decorator\")\n    ```\n\n## Generating a URL\n\n.. column::\n\n    This works just like [generating any other URL](../basics/routing.md#generating-a-url), except that the class name is a part of the endpoint.\n\n.. column::\n\n    ```python\n    @app.route(\"/\")\n    def index(request):\n        url = app.url_for(\"SpecialClassView\")\n        return redirect(url)\n\n    class SpecialClassView(HTTPMethodView):\n        def get(self, request):\n            return text(\"Hello from the Special Class View!\")\n\n    app.add_route(SpecialClassView.as_view(), \"/special_class_view\")\n    ```\n\n"
  },
  {
    "path": "guide/content/en/guide/advanced/commands.md",
    "content": "# Custom CLI Commands\n\nSanic ships with a [CLI](../running/running.md#running-via-command) for running the Sanic server. Sometimes, you may have the need to enhance that CLI to run your own custom commands. Commands are invoked using the following basic pattern:\n\n```sh\nsanic path.to:app exec <command> [--arg=value]\n```\n\n.. column::\n\n    To enable this, you can use your `Sanic` app instance to wrap functions that can be callable from the CLI using the `@app.command` decorator.\n\n.. column::\n\n    ```python\n    @app.command\n    async def hello(name=\"world\"):\n        print(f\"Hello, {name}.\")\n    ```\n    \n.. column::\n\n    Now, you can easily invoke this command using the `exec` action. \n    \n.. column::\n\n    ```sh\n    sanic path.to:app exec hello --name=Adam\n    ```\n    \nCommand handlers can be either synchronous or asynchronous. The handler can accept any number of keyword arguments, which will be passed in from the CLI.\n\n.. column::\n\n    By default, the name of the function will be the command name. You can override this by passing the `name` argument to the decorator.\n    \n.. column::\n\n    ```python\n    @app.command(name=\"greet\")\n    async def hello(name=\"world\"):\n        print(f\"Hello, {name}.\")\n    ```\n    \n    ```sh\n    sanic path.to:app exec greet --name=Adam\n    ```\n\n.. warning::\n\n    This feature is still in **BETA** and may change in future versions. There is no type coercion or validation on the arguments passed in from the CLI, and the CLI will ignore any return values from the command handler. Future enhancements and changes are likely.\n\n*Added in v24.12*\n"
  },
  {
    "path": "guide/content/en/guide/advanced/proxy-headers.md",
    "content": "# Proxy configuration\n\nWhen you use a reverse proxy server (e.g. nginx), the value of `request.ip` will contain the IP of a proxy, typically `127.0.0.1`. Almost always, this is **not** what you will want.\n\nSanic may be configured to use proxy headers for determining the true client IP, available as `request.remote_addr`. The full external URL is also constructed from header fields _if available_.\n\n\n.. tip:: Heads up\n\n    Without proper precautions, a malicious client may use proxy headers to spoof its own IP. To avoid such issues, Sanic does not use any proxy headers unless explicitly enabled.\n\n\n\n.. column::\n\n    Services behind reverse proxies must configure one or more of the following [configuration values](../deployment/configuration.md):\n\n    - `FORWARDED_SECRET`\n    - `REAL_IP_HEADER`\n    - `PROXIES_COUNT`\n\n.. column::\n\n    ```python\n    app.config.FORWARDED_SECRET = \"super-duper-secret\"\n    app.config.REAL_IP_HEADER = \"CF-Connecting-IP\"\n    app.config.PROXIES_COUNT = 2\n    ```\n\n## Forwarded header\n\nIn order to use the `Forwarded` header, you should set `app.config.FORWARDED_SECRET` to a value known to the trusted proxy server. The secret is used to securely identify a specific proxy server.\n\nSanic ignores any elements without the secret key, and will not even parse the header if no secret is set.\n\nAll other proxy headers are ignored once a trusted forwarded element is found, as it already carries complete information about the client.\n\nTo learn more about the `Forwarded` header, read the related [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded) and [Nginx](https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/) articles.\n\n## Traditional proxy headers\n\n### IP Headers\n\nWhen your proxy forwards you the IP address in a known header, you can tell Sanic what that is with the `REAL_IP_HEADER` config value.\n\n### X-Forwarded-For\n\nThis header typically contains a chain of IP addresses through each layer of a proxy. Setting `PROXIES_COUNT` tells Sanic how deep to look to get an actual IP address for the client. This value should equal the _expected_ number of IP addresses in the chain.\n\n### Other X-headers\n\nIf a client IP is found by one of these methods, Sanic uses the following headers for URL parts:\n\n- x-forwarded-proto\n- x-forwarded-host\n- x-forwarded-port\n- x-forwarded-path\n- x-scheme\n\n## Examples\n\nIn the following examples, all requests will assume that the endpoint looks like this:\n```python\n@app.route(\"/fwd\")\nasync def forwarded(request):\n    return json(\n        {\n            \"remote_addr\": request.remote_addr,\n            \"scheme\": request.scheme,\n            \"server_name\": request.server_name,\n            \"server_port\": request.server_port,\n            \"forwarded\": request.forwarded,\n        }\n    )\n```\n\n---\n\n##### Example 1\n\nWithout configured FORWARDED_SECRET, x-headers should be respected\n\n```sh\ncurl localhost:8000/fwd \\\n\t-H 'Forwarded: for=1.1.1.1, for=injected;host=\", for=\"[::2]\";proto=https;host=me.tld;path=\"/app/\";secret=mySecret,for=broken;;secret=b0rked, for=127.0.0.3;scheme=http;port=1234' \\\n\t-H \"X-Real-IP: 127.0.0.2\" \\\n\t-H \"X-Forwarded-For: 127.0.1.1\" \\\n\t-H \"X-Scheme: ws\" \\\n\t-H \"Host: local.site\" | jq\n```\n\n.. column::\n\n    ```python\n    # Sanic application config\n    app.config.PROXIES_COUNT = 1\n    app.config.REAL_IP_HEADER = \"x-real-ip\"\n    ```\n\n.. column::\n\n    ```bash\n    # curl response\n    {\n      \"remote_addr\": \"127.0.0.2\",\n      \"scheme\": \"ws\",\n      \"server_name\": \"local.site\",\n      \"server_port\": 80,\n      \"forwarded\": {\n        \"for\": \"127.0.0.2\",\n        \"proto\": \"ws\"\n      }\n    }\n    ```\n\n---\n\n##### Example 2\n\nFORWARDED_SECRET now configured\n\n```sh\ncurl localhost:8000/fwd \\\n\t-H 'Forwarded: for=1.1.1.1, for=injected;host=\", for=\"[::2]\";proto=https;host=me.tld;path=\"/app/\";secret=mySecret,for=broken;;secret=b0rked, for=127.0.0.3;scheme=http;port=1234' \\\n\t-H \"X-Real-IP: 127.0.0.2\" \\\n\t-H \"X-Forwarded-For: 127.0.1.1\" \\\n\t-H \"X-Scheme: ws\" \\\n\t-H \"Host: local.site\" | jq\n```\n\n.. column::\n\n    ```python\n    # Sanic application config\n    app.config.PROXIES_COUNT = 1\n    app.config.REAL_IP_HEADER = \"x-real-ip\"\n    app.config.FORWARDED_SECRET = \"mySecret\"\n    ```\n\n.. column::\n\n    ```bash\n    # curl response\n    {\n      \"remote_addr\": \"[::2]\",\n      \"scheme\": \"https\",\n      \"server_name\": \"me.tld\",\n      \"server_port\": 443,\n      \"forwarded\": {\n        \"for\": \"[::2]\",\n        \"proto\": \"https\",\n        \"host\": \"me.tld\",\n        \"path\": \"/app/\",\n        \"secret\": \"mySecret\"\n      }\n    }\n    ```\n\n---\n\n##### Example 3\n\nEmpty Forwarded header -> use X-headers\n\n```sh\ncurl localhost:8000/fwd \\\n\t-H \"X-Real-IP: 127.0.0.2\" \\\n\t-H \"X-Forwarded-For: 127.0.1.1\" \\\n\t-H \"X-Scheme: ws\" \\\n\t-H \"Host: local.site\" | jq\n```\n\n.. column::\n\n    ```python\n    # Sanic application config\n    app.config.PROXIES_COUNT = 1\n    app.config.REAL_IP_HEADER = \"x-real-ip\"\n    app.config.FORWARDED_SECRET = \"mySecret\"\n    ```\n\n.. column::\n\n    ```bash\n    # curl response\n    {\n      \"remote_addr\": \"127.0.0.2\",\n      \"scheme\": \"ws\",\n      \"server_name\": \"local.site\",\n      \"server_port\": 80,\n      \"forwarded\": {\n        \"for\": \"127.0.0.2\",\n        \"proto\": \"ws\"\n      }\n    }\n    ```\n\n---\n\n##### Example 4\n\nHeader present but not matching anything\n\n```sh\ncurl localhost:8000/fwd \\\n\t-H \"Forwarded: nomatch\" | jq\n```\n\n.. column::\n\n    ```python\n    # Sanic application config\n    app.config.PROXIES_COUNT = 1\n    app.config.REAL_IP_HEADER = \"x-real-ip\"\n    app.config.FORWARDED_SECRET = \"mySecret\"\n    ```\n\n.. column::\n\n    ```bash\n    # curl response\n    {\n      \"remote_addr\": \"\",\n      \"scheme\": \"http\",\n      \"server_name\": \"localhost\",\n      \"server_port\": 8000,\n      \"forwarded\": {}\n    }\n\n    ```\n\n---\n\n##### Example 5\n\nForwarded header present but no matching secret -> use X-headers\n\n```sh\ncurl localhost:8000/fwd \\\n\t-H \"Forwarded: for=1.1.1.1;secret=x, for=127.0.0.1\" \\\n\t-H \"X-Real-IP: 127.0.0.2\" | jq\n```\n\n.. column::\n\n    ```python\n    # Sanic application config\n    app.config.PROXIES_COUNT = 1\n    app.config.REAL_IP_HEADER = \"x-real-ip\"\n    app.config.FORWARDED_SECRET = \"mySecret\"\n    ```\n\n.. column::\n\n    ```bash\n    # curl response\n    {\n      \"remote_addr\": \"127.0.0.2\",\n      \"scheme\": \"http\",\n      \"server_name\": \"localhost\",\n      \"server_port\": 8000,\n      \"forwarded\": {\n        \"for\": \"127.0.0.2\"\n      }\n    }\n    ```\n\n---\n\n##### Example 6\n\nDifferent formatting and hitting both ends of the header\n\n```sh\ncurl localhost:8000/fwd \\\n\t-H 'Forwarded: Secret=\"mySecret\";For=127.0.0.4;Port=1234' | jq\n```\n\n.. column::\n\n    ```python\n    # Sanic application config\n    app.config.PROXIES_COUNT = 1\n    app.config.REAL_IP_HEADER = \"x-real-ip\"\n    app.config.FORWARDED_SECRET = \"mySecret\"\n    ```\n\n.. column::\n\n    ```bash\n    # curl response\n    {\n      \"remote_addr\": \"127.0.0.4\",\n      \"scheme\": \"http\",\n      \"server_name\": \"localhost\",\n      \"server_port\": 1234,\n      \"forwarded\": {\n        \"secret\": \"mySecret\",\n        \"for\": \"127.0.0.4\",\n        \"port\": 1234\n      }\n    }\n    ```\n\n---\n\n##### Example 7\n\nTest escapes (modify this if you see anyone implementing quoted-pairs)\n\n```sh\ncurl localhost:8000/fwd \\\n\t-H 'Forwarded: for=test;quoted=\"\\,x=x;y=\\\";secret=mySecret' | jq\n```\n\n.. column::\n\n    ```python\n    # Sanic application config\n    app.config.PROXIES_COUNT = 1\n    app.config.REAL_IP_HEADER = \"x-real-ip\"\n    app.config.FORWARDED_SECRET = \"mySecret\"\n    ```\n\n.. column::\n\n    ```bash\n    # curl response\n    {\n      \"remote_addr\": \"test\",\n      \"scheme\": \"http\",\n      \"server_name\": \"localhost\",\n      \"server_port\": 8000,\n      \"forwarded\": {\n        \"for\": \"test\",\n        \"quoted\": \"\\\\,x=x;y=\\\\\",\n        \"secret\": \"mySecret\"\n      }\n    }\n    ```\n\n---\n\n##### Example 8\n\nSecret insulated by malformed field #1\n\n```sh\ncurl localhost:8000/fwd \\\n\t-H 'Forwarded: for=test;secret=mySecret;b0rked;proto=wss;' | jq\n```\n\n.. column::\n\n    ```python\n    # Sanic application config\n    app.config.PROXIES_COUNT = 1\n    app.config.REAL_IP_HEADER = \"x-real-ip\"\n    app.config.FORWARDED_SECRET = \"mySecret\"\n    ```\n\n.. column::\n\n    ```bash\n    # curl response\n    {\n      \"remote_addr\": \"test\",\n      \"scheme\": \"http\",\n      \"server_name\": \"localhost\",\n      \"server_port\": 8000,\n      \"forwarded\": {\n        \"for\": \"test\",\n        \"secret\": \"mySecret\"\n      }\n    }\n    ```\n\n---\n\n##### Example 9\n\nSecret insulated by malformed field #2\n\n```sh\ncurl localhost:8000/fwd \\\n\t-H 'Forwarded: for=test;b0rked;secret=mySecret;proto=wss' | jq\n```\n\n.. column::\n\n    ```python\n    # Sanic application config\n    app.config.PROXIES_COUNT = 1\n    app.config.REAL_IP_HEADER = \"x-real-ip\"\n    app.config.FORWARDED_SECRET = \"mySecret\"\n    ```\n\n.. column::\n\n    ```bash\n    # curl response\n    {\n      \"remote_addr\": \"\",\n      \"scheme\": \"wss\",\n      \"server_name\": \"localhost\",\n      \"server_port\": 8000,\n      \"forwarded\": {\n        \"secret\": \"mySecret\",\n        \"proto\": \"wss\"\n      }\n    }\n    ```\n\n---\n\n##### Example 10\n\nUnexpected termination should not lose existing acceptable values\n\n```sh\ncurl localhost:8000/fwd \\\n\t-H 'Forwarded: b0rked;secret=mySecret;proto=wss' | jq\n```\n\n.. column::\n\n    ```python\n    # Sanic application config\n    app.config.PROXIES_COUNT = 1\n    app.config.REAL_IP_HEADER = \"x-real-ip\"\n    app.config.FORWARDED_SECRET = \"mySecret\"\n    ```\n\n.. column::\n\n    ```bash\n    # curl response\n    {\n      \"remote_addr\": \"\",\n      \"scheme\": \"wss\",\n      \"server_name\": \"localhost\",\n      \"server_port\": 8000,\n      \"forwarded\": {\n        \"secret\": \"mySecret\",\n        \"proto\": \"wss\"\n      }\n    }\n    ```\n\n---\n\n##### Example 11\n\nField normalization\n\n```sh\ncurl localhost:8000/fwd \\\n\t-H 'Forwarded: PROTO=WSS;BY=\"CAFE::8000\";FOR=unknown;PORT=X;HOST=\"A:2\";PATH=\"/With%20Spaces%22Quoted%22/sanicApp?key=val\";SECRET=mySecret' | jq\n```\n\n.. column::\n\n    ```python\n    # Sanic application config\n    app.config.PROXIES_COUNT = 1\n    app.config.REAL_IP_HEADER = \"x-real-ip\"\n    app.config.FORWARDED_SECRET = \"mySecret\"\n    ```\n\n.. column::\n\n    ```bash\n    # curl response\n    {\n      \"remote_addr\": \"\",\n      \"scheme\": \"wss\",\n      \"server_name\": \"a\",\n      \"server_port\": 2,\n      \"forwarded\": {\n        \"proto\": \"wss\",\n        \"by\": \"[cafe::8000]\",\n        \"host\": \"a:2\",\n        \"path\": \"/With Spaces\\\"Quoted\\\"/sanicApp?key=val\",\n        \"secret\": \"mySecret\"\n      }\n    }\n    ```\n\n---\n\n##### Example 12\n\nUsing \"by\" field as secret\n\n```sh\ncurl localhost:8000/fwd \\\n\t-H 'Forwarded: for=1.2.3.4; by=_proxySecret' | jq\n```\n\n.. column::\n\n    ```python\n    # Sanic application config\n    app.config.PROXIES_COUNT = 1\n    app.config.REAL_IP_HEADER = \"x-real-ip\"\n    app.config.FORWARDED_SECRET = \"_proxySecret\"\n    ```\n\n.. column::\n\n    ```bash\n    # curl response\n    {\n      \"remote_addr\": \"1.2.3.4\",\n      \"scheme\": \"http\",\n      \"server_name\": \"localhost\",\n      \"server_port\": 8000,\n      \"forwarded\": {\n        \"for\": \"1.2.3.4\",\n        \"by\": \"_proxySecret\"\n      }\n    }\n\n    ```\n\n"
  },
  {
    "path": "guide/content/en/guide/advanced/signals.md",
    "content": "# Signals\n\nSignals provide a way for one part of your application to tell another part that something happened.\n\n```python\n@app.signal(\"user.registration.created\")\nasync def send_registration_email(**context):\n    await send_email(context[\"email\"], template=\"registration\")\n\n@app.post(\"/register\")\nasync def handle_registration(request):\n    await do_registration(request)\n    await request.app.dispatch(\n        \"user.registration.created\",\n        context={\"email\": request.json.email}\n    })\n```\n\n## Adding a signal\n\n.. column::\n\n    The API for adding a signal is very similar to adding a route.\n\n.. column::\n\n    ```python\n    async def my_signal_handler():\n        print(\"something happened\")\n\n    app.add_signal(my_signal_handler, \"something.happened.ohmy\")\n    ```\n\n\n.. column::\n\n    But, perhaps a slightly more convenient method is to use the built-in decorators.\n\n.. column::\n\n    ```python\n    @app.signal(\"something.happened.ohmy\")\n    async def my_signal_handler():\n        print(\"something happened\")\n    ```\n\n\n.. column::\n\n    If the signal requires conditions, make sure to add them while adding the handler.\n\n.. column::\n\n    ```python\n    async def my_signal_handler1():\n        print(\"something happened\")\n\n    app.add_signal(\n        my_signal_handler,\n        \"something.happened.ohmy1\",\n        conditions={\"some_condition\": \"value\"}\n    )\n\n    @app.signal(\"something.happened.ohmy2\", conditions={\"some_condition\": \"value\"})\n    async def my_signal_handler2():\n        print(\"something happened\")\n    ```\n\n\n.. column::\n\n    Signals can also be declared on blueprints\n\n.. column::\n\n    ```python\n    bp = Blueprint(\"foo\")\n\n    @bp.signal(\"something.happened.ohmy\")\n    async def my_signal_handler():\n        print(\"something happened\")\n    ```\n\n## Built-in signals\n\nIn addition to creating a new signal, there are a number of built-in signals that are dispatched from Sanic itself. These signals exist to provide developers with more opportunities to add functionality into the request and server lifecycles.\n\n*Added in v21.9*\n\n.. column::\n\n    You can attach them just like any other signal to an application or blueprint instance.\n\n.. column::\n\n    ```python\n    @app.signal(\"http.lifecycle.complete\")\n    async def my_signal_handler(conn_info):\n        print(\"Connection has been closed\")\n    ```\n\nThese signals are the signals that are available, along with the arguments that the handlers take, and the conditions that attach (if any).\n\n| Event name                 | Arguments                       | Conditions                                                |\n| -------------------------- | ------------------------------- | --------------------------------------------------------- |\n| `http.routing.before`      | request                         |                                                           |\n| `http.routing.after`       | request, route, kwargs, handler |                                                           |\n| `http.handler.before`      | request                         |                                                           |\n| `http.handler.after`       | request                         |                                                           |\n| `http.lifecycle.begin`     | conn_info                       |                                                           |\n| `http.lifecycle.read_head` | head                            |                                                           |\n| `http.lifecycle.request`   | request                         |                                                           |\n| `http.lifecycle.handle`    | request                         |                                                           |\n| `http.lifecycle.read_body` | body                            |                                                           |\n| `http.lifecycle.exception` | request, exception              |                                                           |\n| `http.lifecycle.response`  | request, response               |                                                           |\n| `http.lifecycle.send`      | data                            |                                                           |\n| `http.lifecycle.complete`  | conn_info                       |                                                           |\n| `http.middleware.before`   | request, response               | `{\"attach_to\": \"request\"}` or `{\"attach_to\": \"response\"}` |\n| `http.middleware.after`    | request, response               | `{\"attach_to\": \"request\"}` or `{\"attach_to\": \"response\"}` |\n| `server.exception.report`  | app, exception                  |                                                           |\n| `server.init.before`       | app, loop                       |                                                           |\n| `server.init.after`        | app, loop                       |                                                           |\n| `server.shutdown.before`   | app, loop                       |                                                           |\n| `server.shutdown.after`    | app, loop                       |                                                           |\n\nVersion 22.9 added `http.handler.before` and `http.handler.after`.\n\nVersion 23.6 added `server.exception.report`.\n\n.. column::\n\n    To make using the built-in signals easier, there is an `Enum` object that contains all of the allowed built-ins. With a modern IDE this will help so that you do not need to remember the full list of event names as strings.\n\n    *Added in v21.12*\n\n.. column::\n\n    ```python\n    from sanic.signals import Event\n\n    @app.signal(Event.HTTP_LIFECYCLE_COMPLETE)\n    async def my_signal_handler(conn_info):\n        print(\"Connection has been closed\")\n    ```\n\n## Events\n\n.. column::\n\n    Signals are based off of an _event_. An event, is simply a string in the following pattern:\n\n.. column::\n\n    ```\n    namespace.reference.action\n    ```\n\n\n\n.. tip:: Events must have three parts. If you do not know what to use, try these patterns:\n\n    - `my_app.something.happened`\n    - `sanic.notice.hello`\n\n\n### Event parameters\n\n.. column::\n\n    An event can be \"dynamic\" and declared using the same syntax as [path parameters](../basics/routing.md#path-parameters). This allows matching based upon arbitrary values.\n\n.. column::\n\n    ```python\n    @app.signal(\"foo.bar.<thing>\")\n    async def signal_handler(thing):\n        print(f\"[signal_handler] {thing=}\")\n\n    @app.get(\"/\")\n    async def trigger(request):\n        await app.dispatch(\"foo.bar.baz\")\n        return response.text(\"Done.\")\n    ```\n\nCheckout [path parameters](../basics/routing.md#path-parameters) for more information on allowed type definitions.\n\n\n.. info:: Only the third part of an event (the action) may be dynamic:\n\n    - `foo.bar.<thing>` 🆗\n    - `foo.<bar>.baz` ❌\n\n\n### Waiting\n\n.. column::\n\n    In addition to executing a signal handler, your application can wait for an event to be triggered.\n\n.. column::\n\n    ```python\n    await app.event(\"foo.bar.baz\")\n    ```\n\n\n.. column::\n\n    **IMPORTANT**: waiting is a blocking function. Therefore, you likely will want this to run in a [background task](../basics/tasks.md).\n\n.. column::\n\n    ```python\n    async def wait_for_event(app):\n        while True:\n            print(\"> waiting\")\n            await app.event(\"foo.bar.baz\")\n            print(\"> event found\\n\")\n\n    @app.after_server_start\n    async def after_server_start(app, loop):\n        app.add_task(wait_for_event(app))\n    ```\n\n\n.. column::\n\n    If your event was defined with a dynamic path, you can use `*` to catch any action.\n\n.. column::\n\n    ```python\n    @app.signal(\"foo.bar.<thing>\")\n\n    ...\n\n    await app.event(\"foo.bar.*\")\n    ```\n\n## Dispatching\n\n*In the future, Sanic will dispatch some events automatically to assist developers to hook into life cycle events.*\n\n.. column::\n\n    Dispatching an event will do two things:\n\n    1. execute any signal handlers defined on the event, and\n    2. resolve anything that is \"waiting\" for the event to complete.\n\n.. column::\n\n    ```python\n    @app.signal(\"foo.bar.<thing>\")\n    async def foo_bar(thing):\n        print(f\"{thing=}\")\n\n    await app.dispatch(\"foo.bar.baz\")\n    ```\n    ```\n    thing=baz\n    ```\n\n### Context\n\n.. column::\n\n    Sometimes you may find the need to pass extra information into the signal handler. In our first example above, we wanted our email registration process to have the email address for the user.\n\n.. column::\n\n    ```python\n    @app.signal(\"user.registration.created\")\n    async def send_registration_email(**context):\n        print(context)\n\n    await app.dispatch(\n        \"user.registration.created\",\n        context={\"hello\": \"world\"}\n    )\n    ```\n    ```\n    {'hello': 'world'}\n    ```\n\n\n\n.. tip:: FYI\n\n    Signals are dispatched in a background task.\n\n\n### Blueprints\n\nDispatching blueprint signals works similar in concept to [middleware](../basics/middleware.md). Anything that is done from the app level, will trickle down to the blueprints. However, dispatching on a blueprint, will only execute the signals that are defined on that blueprint.\n\n.. column::\n\n    Perhaps an example is easier to explain:\n\n.. column::\n\n    ```python\n    bp = Blueprint(\"bp\")\n\n    app_counter = 0\n    bp_counter = 0\n\n    @app.signal(\"foo.bar.baz\")\n    def app_signal():\n        nonlocal app_counter\n        app_counter += 1\n\n    @bp.signal(\"foo.bar.baz\")\n    def bp_signal():\n        nonlocal bp_counter\n        bp_counter += 1\n    ```\n\n\n.. column::\n\n    Running `app.dispatch(\"foo.bar.baz\")` will execute both signals.\n\n.. column::\n\n    ```python\n    await app.dispatch(\"foo.bar.baz\")\n    assert app_counter == 1\n    assert bp_counter == 1\n    ```\n\n\n.. column::\n\n    Running `bp.dispatch(\"foo.bar.baz\")` will execute only the blueprint signal.\n\n.. column::\n\n    ```python\n    await bp.dispatch(\"foo.bar.baz\")\n    assert app_counter == 1\n    assert bp_counter == 2\n    ```\n\n"
  },
  {
    "path": "guide/content/en/guide/advanced/streaming.md",
    "content": "# Streaming\n\n## Request streaming\n\nSanic allows you to stream data sent by the client to begin processing data as the bytes arrive.\n\n.. column::\n\n    When enabled on an endpoint, you can stream the request body using `await request.stream.read()`.\n\n    That method will return `None` when the body is completed.\n\n.. column::\n\n    ```python\n    from sanic.views import stream\n\n    class SimpleView(HTTPMethodView):\n        @stream\n        async def post(self, request):\n            result = \"\"\n            while True:\n                body = await request.stream.read()\n                if body is None:\n                    break\n                result += body.decode(\"utf-8\")\n            return text(result)\n    ```\n\n\n.. column::\n\n    It also can be enabled with a keyword argument in the decorator...\n\n.. column::\n\n    ```python\n    @app.post(\"/stream\", stream=True)\n    async def handler(request):\n            ...\n            body = await request.stream.read()\n            ...\n    ```\n\n\n.. column::\n\n    ... or the `add_route()` method.\n\n.. column::\n\n    ```python\n    bp.add_route(\n        bp_handler,\n        \"/bp_stream\",\n        methods=[\"POST\"],\n        stream=True,\n    )\n    ```\n\n\n\n.. tip:: FYI\n\n    Only post, put and patch decorators have stream argument.\n\n\n## Response streaming\n\n.. column::\n\n    Sanic allows you to stream content to the client.\n\n.. column::\n\n    ```python\n    @app.route(\"/\")\n    async def test(request):\n        response = await request.respond(content_type=\"text/csv\")\n        await response.send(\"foo,\")\n        await response.send(\"bar\")\n\n        # Optionally, you can explicitly end the stream by calling:\n        await response.eof()\n    ```\n\nThis is useful in situations where you want to stream content to the client that originates in an external service, like a database. For example, you can stream database records to the client with the asynchronous cursor that `asyncpg` provides.\n\n```python\n@app.route(\"/\")\nasync def index(request):\n    response = await request.respond()\n    conn = await asyncpg.connect(database='test')\n    async with conn.transaction():\n        async for record in conn.cursor('SELECT generate_series(0, 10)'):\n            await response.send(record[0])\n```\n\n\nYou can explicitly end a stream by calling `await response.eof()`. It a convenience method to replace `await response.send(\"\", True)`. It should be called **one time** *after* your handler has determined that it has nothing left to send back to the client. While it is *optional* to use with Sanic server, if you are running Sanic in ASGI mode, then you **must** explicitly terminate the stream.\n\n*Calling `eof` became optional in v21.6*\n\n## File streaming\n\n.. column::\n\n    Sanic provides `sanic.response.file_stream` function that is useful when you want to send a large file. It returns a `StreamingHTTPResponse` object and will use chunked transfer encoding by default; for this reason Sanic doesn’t add `Content-Length` HTTP header in the response.\n\n    A typical use case might be streaming an video file.\n\n.. column::\n\n    ```python\n    @app.route(\"/mp4\")\n    async def handler_file_stream(request):\n        return await response.file_stream(\n            \"/path/to/sample.mp4\",\n            chunk_size=1024,\n            mime_type=\"application/metalink4+xml\",\n            headers={\n                \"Content-Disposition\": 'Attachment; filename=\"nicer_name.meta4\"',\n                \"Content-Type\": \"application/metalink4+xml\",\n            },\n        )\n    ```\n\n\n.. column::\n\n    If you want to use the `Content-Length` header, you can disable chunked transfer encoding and add it manually simply by adding the `Content-Length` header.\n\n.. column::\n\n    ```python\n    from aiofiles import os as async_os\n    from sanic.response import file_stream\n\n    @app.route(\"/\")\n    async def index(request):\n        file_path = \"/srv/www/whatever.png\"\n\n        file_stat = await async_os.stat(file_path)\n        headers = {\"Content-Length\": str(file_stat.st_size)}\n\n        return await file_stream(\n            file_path,\n            headers=headers,\n        )\n    ```\n\n"
  },
  {
    "path": "guide/content/en/guide/advanced/versioning.md",
    "content": "# Versioning\n\nIt is standard practice in API building to add versions to your endpoints. This allows you to easily differentiate incompatible endpoints when you try and change your API down the road in a breaking manner.\n\nAdding a version will add a `/v{version}` url prefix to your endpoints.\n\nThe version can be a `int`, `float`, or `str`. Acceptable values:\n\n- `1`, `2`, `3`\n- `1.1`, `2.25`, `3.0`\n- `\"1\"`, `\"v1\"`, `\"v1.1\"`\n\n## Per route\n\n.. column::\n\n    You can pass a version number to the routes directly.\n\n.. column::\n\n    ```python\n    # /v1/text\n    @app.route(\"/text\", version=1)\n    def handle_request(request):\n        return response.text(\"Hello world! Version 1\")\n\n    # /v2/text\n    @app.route(\"/text\", version=2)\n    def handle_request(request):\n        return response.text(\"Hello world! Version 2\")\n    ```\n\n## Per Blueprint\n\n.. column::\n\n    You can also pass a version number to the blueprint, which will apply to all routes in that blueprint.\n\n.. column::\n\n    ```python\n    bp = Blueprint(\"test\", url_prefix=\"/foo\", version=1)\n\n    # /v1/foo/html\n    @bp.route(\"/html\")\n    def handle_request(request):\n        return response.html(\"<p>Hello world!</p>\")\n    ```\n\n## Per Blueprint Group\n\n.. column::\n\n    In order to simplify the management of the versioned blueprints, you can provide a version number in the blueprint\n    group. The same will be inherited to all the blueprint grouped under it if the blueprints don't already override the\n    same information with a value specified while creating a blueprint instance.\n\n    When using blueprint groups for managing the versions, the following order is followed to apply the Version prefix to\n    the routes being registered.\n\n    1. Route Level configuration\n    2. Blueprint level configuration\n    3. Blueprint Group level configuration\n\n    If we find a more pointed versioning specification, we will pick that over the more generic versioning specification\n    provided under the Blueprint or Blueprint Group\n\n.. column::\n\n    ```python\n    from sanic.blueprints import Blueprint\n    from sanic.response import json\n\n    bp1 = Blueprint(\n        name=\"blueprint-1\",\n        url_prefix=\"/bp1\",\n        version=1.25,\n    )\n    bp2 = Blueprint(\n        name=\"blueprint-2\",\n        url_prefix=\"/bp2\",\n    )\n\n    group = Blueprint.group(\n        [bp1, bp2],\n        url_prefix=\"/bp-group\",\n        version=\"v2\",\n    )\n\n    # GET /v1.25/bp-group/bp1/endpoint-1\n    @bp1.get(\"/endpoint-1\")\n    async def handle_endpoint_1_bp1(request):\n        return json({\"Source\": \"blueprint-1/endpoint-1\"})\n\n    # GET /v2/bp-group/bp2/endpoint-2\n    @bp2.get(\"/endpoint-1\")\n    async def handle_endpoint_1_bp2(request):\n        return json({\"Source\": \"blueprint-2/endpoint-1\"})\n\n    # GET /v1/bp-group/bp2/endpoint-2\n    @bp2.get(\"/endpoint-2\", version=1)\n    async def handle_endpoint_2_bp2(request):\n        return json({\"Source\": \"blueprint-2/endpoint-2\"})\n    ```\n\n## Version prefix\n\nAs seen above, the `version` that is applied to a route is **always** the first segment in the generated URI path. Therefore, to make it possible to add path segments before the version, every place that a `version` argument is passed, you can also pass `version_prefix`. \n\nThe `version_prefix` argument can be defined in:\n\n- `app.route` and `bp.route` decorators (and all the convenience decorators also)\n- `Blueprint` instantiation\n- `Blueprint.group` constructor\n- `BlueprintGroup` instantiation\n- `app.blueprint` registration\n\nIf there are definitions in multiple places, a more specific definition overrides a more general. This list provides that hierarchy.\n\nThe default value of `version_prefix` is `/v`.\n\n.. column::\n\n    An often requested feature is to be able to mount versioned routes on `/api`. This can easily be accomplished with `version_prefix`.\n\n.. column::\n\n    ```python\n    # /v1/my/path\n    app.route(\"/my/path\", version=1, version_prefix=\"/api/v\")\n    ```\n\n\n.. column::\n\n    Perhaps a more compelling usage is to load all `/api` routes into a single `BlueprintGroup`.\n\n.. column::\n\n    ```python\n    # /v1/my/path\n    app = Sanic(__name__)\n    v2ip = Blueprint(\"v2ip\", url_prefix=\"/ip\", version=2)\n    api = Blueprint.group(v2ip, version_prefix=\"/api/version\")\n\n    # /api/version2/ip\n    @v2ip.get(\"/\")\n    async def handler(request):\n        return text(request.ip)\n\n    app.blueprint(api)\n    ```\n\nWe can therefore learn that a route's URI is:\n\n```\nversion_prefix + version + url_prefix + URI definition\n```\n\n\n.. tip:: \n    \n    Just like with `url_prefix`, it is possible to define path parameters inside a `version_prefix`. It is perfectly legitimate to do this. Just remember that every route will have that parameter injected into the handler.\n\n    ```python\n    version_prefix=\"/<foo:str>/v\"\n    ```\n\n\n*Added in v21.6*\n"
  },
  {
    "path": "guide/content/en/guide/advanced/websockets.md",
    "content": "# Websockets\n\nSanic provides an easy to use abstraction on top of [websockets](https://websockets.readthedocs.io/en/stable/).\n\n## Routing\n\n.. column::\n\n    Websocket handlers can be hooked up to the router similar to regular handlers.\n\n.. column::\n\n    ```python\n    from sanic import Request, Websocket\n\n    async def feed(request: Request, ws: Websocket):\n        pass\n\n    app.add_websocket_route(feed, \"/feed\")\n    ```\n    ```python\n    from sanic import Request, Websocket\n\n    @app.websocket(\"/feed\")\n    async def feed(request: Request, ws: Websocket):\n        pass\n    ```\n\n## Handler\n\n\n.. column::\n\n    Typically, a websocket handler will want to hold open a loop.\n\n    It can then use the `send()` and `recv()` methods on the second object injected into the handler.\n\n    This example is a simple endpoint that echos back to the client messages that it receives.\n\n.. column::\n\n    ```python\n    from sanic import Request, Websocket\n\n    @app.websocket(\"/feed\")\n    async def feed(request: Request, ws: Websocket):\n        while True:\n            data = \"hello!\"\n            print(\"Sending: \" + data)\n            await ws.send(data)\n            data = await ws.recv()\n            print(\"Received: \" + data)\n    ```\n\n\n.. column::\n\n    You can simplify your loop by just iterating over the `Websocket` object in a for loop.\n\n    *Added in v22.9*\n\n.. column::\n\n    ```python\n    from sanic import Request, Websocket\n\n    @app.websocket(\"/feed\")\n    async def feed(request: Request, ws: Websocket):\n        async for msg in ws:\n            await ws.send(msg)\n    ```\n\n\n## Configuration\n\nSee [configuration section](../running/configuration.md) for more details, however the defaults are shown below.\n\n```python\napp.config.WEBSOCKET_MAX_SIZE = 2 ** 20\napp.config.WEBSOCKET_PING_INTERVAL = 20\napp.config.WEBSOCKET_PING_TIMEOUT = 20\n```\n"
  },
  {
    "path": "guide/content/en/guide/basics/README.md",
    "content": "# Basics\n"
  },
  {
    "path": "guide/content/en/guide/basics/app.md",
    "content": "---\ntitle: Sanic Application\n---\n\n# Sanic Application\n\nSee API docs: [sanic.app](/api/sanic.app)\n\n## Instance\n\n.. column::\n\n    The most basic building block is the :class:`sanic.app.Sanic` instance. It is not required, but the custom is to instantiate this in a file called `server.py`.\n\n.. column::\n\n    ```python\n    # /path/to/server.py\n\n    from sanic import Sanic\n\n    app = Sanic(\"MyHelloWorldApp\")\n    ```\n\n## Application context\n\nMost applications will have the need to share/reuse data or objects across different parts of the code base. Sanic helps be providing the `ctx` object on application instances. It is a free space for the developer to attach any objects or data that should existe throughout the lifetime of the application.\n\n\n.. column::\n\n    The most common pattern is to attach a database instance to the application.\n\n.. column::\n\n    ```python\n    app = Sanic(\"MyApp\")\n    app.ctx.db = Database()\n    ```\n\n\n.. column::\n\n    While the previous example will work and is illustrative, it is typically considered best practice to attach objects in one of the two application startup [listeners](./listeners).\n\n.. column::\n\n    ```python\n    app = Sanic(\"MyApp\")\n\n    @app.before_server_start\n    async def attach_db(app, loop):\n        app.ctx.db = Database()\n    ```\n\n\n## App Registry\n\n.. column::\n\n    When you instantiate a Sanic instance, that can be retrieved at a later time from the Sanic app registry. This can be useful, for example, if you need to access your Sanic instance from a location where it is not otherwise accessible.\n\n.. column::\n\n    ```python\n    # ./path/to/server.py\n    from sanic import Sanic\n\n    app = Sanic(\"my_awesome_server\")\n\n    # ./path/to/somewhere_else.py\n    from sanic import Sanic\n\n    app = Sanic.get_app(\"my_awesome_server\")\n    ```\n\n\n.. column::\n\n    If you call `Sanic.get_app(\"non-existing\")` on an app that does not exist, it will raise :class:`sanic.exceptions.SanicException` by default. You can, instead, force the method to return a new instance of Sanic with that name.\n\n.. column::\n\n    ```python\n    app = Sanic.get_app(\n        \"non-existing\",\n        force_create=True,\n    )\n    ```\n\n\n.. column::\n\n    If there is **only one** Sanic instance registered, then calling `Sanic.get_app()` with no arguments will return that instance\n\n.. column::\n\n    ```python\n    Sanic(\"My only app\")\n\n    app = Sanic.get_app()\n    ```\n\n## Configuration\n\n.. column::\n\n    Sanic holds the configuration in the `config` attribute of the `Sanic` instance. Configuration can be modified **either** using dot-notation **OR** like a dictionary.\n\n.. column::\n\n    ```python\n    app = Sanic('myapp')\n\n    app.config.DB_NAME = 'appdb'\n    app.config['DB_USER'] = 'appuser'\n\n    db_settings = {\n        'DB_HOST': 'localhost',\n        'DB_NAME': 'appdb',\n        'DB_USER': 'appuser'\n    }\n    app.config.update(db_settings)\n    ```\n\n\n\n.. note:: Heads up\n\n    Config keys _should_ be uppercase. But, this is mainly by convention, and lowercase will work most of the time.\n    ```python\n    app.config.GOOD = \"yay!\"\n    app.config.bad = \"boo\"\n    ```\n\n\nThere is much [more detail about configuration](../running/configuration.md) later on.\n\n## Factory pattern\n\nMany of the examples in these docs will show the instantiation of the :class:`sanic.app.Sanic` instance in a file called `server.py` in the \"global scope\" (i.e. not inside a function). This is a common pattern for very simple \"hello world\" style applications, but it is often beneficial to use a factory pattern instead.\n\nA \"factory\" is just a function that returns an instance of the object you want to use. This allows you to abstract the instantiation of the object, but also may make it easier to isolate the application instance.\n\n.. column::\n\n    A super simple factory pattern could look like this:\n    \n.. column::\n\n    ```python\n    # ./path/to/server.py\n    from sanic import Sanic\n    from .path.to.config import MyConfig\n    from .path.to.some.blueprint import bp\n    \n    \n    def create_app(config=MyConfig) -> Sanic:\n        app = Sanic(\"MyApp\", config=config)\n        app.blueprint(bp)\n        return app\n    ```\n\n.. column::\n\n    When we get to running Sanic later, you will learn that the Sanic CLI can detect this pattern and use it to run your application.\n    \n.. column::\n\n    ```sh\n    sanic path.to.server:create_app\n    ```\n\n## Customization\n\nThe Sanic application instance can be customized for your application needs in a variety of ways at instantiation.\n\nFor complete details, see the [API docs](/api/sanic.app).\n\n### Custom configuration\n\n.. column::\n\n    This simplest form of custom configuration would be to pass your own object directly into that Sanic application instance\n\n    If you create a custom configuration object, it is *highly* recommended that you subclass the :class:`sanic.config.Config` option to inherit its behavior. You could use this option for adding properties, or your own set of custom logic.\n\n    *Added in v21.6*\n\n.. column::\n\n    ```python\n    from sanic.config import Config\n\n    class MyConfig(Config):\n        FOO = \"bar\"\n\n    app = Sanic(..., config=MyConfig())\n    ```\n\n\n.. column::\n\n    A useful example of this feature would be if you wanted to use a config file in a form that differs from what is [supported](../running/configuration.md#using-sanicupdateconfig).\n\n.. column::\n\n    ```python\n    from sanic import Sanic, text\n    from sanic.config import Config\n\n    class TomlConfig(Config):\n        def __init__(self, *args, path: str, **kwargs):\n            super().__init__(*args, **kwargs)\n\n            with open(path, \"r\") as f:\n                self.apply(toml.load(f))\n\n        def apply(self, config):\n            self.update(self._to_uppercase(config))\n\n        def _to_uppercase(self, obj: Dict[str, Any]) -> Dict[str, Any]:\n            retval: Dict[str, Any] = {}\n            for key, value in obj.items():\n                upper_key = key.upper()\n                if isinstance(value, list):\n                    retval[upper_key] = [\n                        self._to_uppercase(item) for item in value\n                    ]\n                elif isinstance(value, dict):\n                    retval[upper_key] = self._to_uppercase(value)\n                else:\n                    retval[upper_key] = value\n            return retval\n\n    toml_config = TomlConfig(path=\"/path/to/config.toml\")\n    app = Sanic(toml_config.APP_NAME, config=toml_config)\n    ```\n\n### Custom context\n\n.. column::\n\n    By default, the application context is a [`SimpleNamespace()`](https://docs.python.org/3/library/types.html#types.SimpleNamespace) that allows you to set any properties you want on it. However, you also have the option of passing any object whatsoever instead.\n\n    *Added in v21.6*\n\n.. column::\n\n    ```python\n    app = Sanic(..., ctx=1)\n    ```\n\n    ```python\n    app = Sanic(..., ctx={})\n    ```\n\n    ```python\n    class MyContext:\n        ...\n\n    app = Sanic(..., ctx=MyContext())\n    ```\n\n### Custom requests\n\n.. column::\n\n    It is sometimes helpful to have your own `Request` class, and tell Sanic to use that instead of the default. One example is if you wanted to modify the default `request.id` generator.\n\n    \n\n    .. note:: Important\n\n        It is important to remember that you are passing the *class* not an instance of the class.\n\n\n.. column::\n\n    ```python\n    import time\n\n    from sanic import Request, Sanic, text\n\n    class NanoSecondRequest(Request):\n        @classmethod\n        def generate_id(*_):\n            return time.time_ns()\n\n    app = Sanic(..., request_class=NanoSecondRequest)\n\n    @app.get(\"/\")\n    async def handler(request):\n        return text(str(request.id))\n    ```\n\n### Custom error handler\n\n.. column::\n\n    See [exception handling](../best-practices/exceptions.md#custom-error-handling) for more\n\n.. column::\n\n    ```python\n    from sanic.handlers import ErrorHandler\n\n    class CustomErrorHandler(ErrorHandler):\n        def default(self, request, exception):\n            ''' handles errors that have no error handlers assigned '''\n            # You custom error handling logic...\n            return super().default(request, exception)\n\n    app = Sanic(..., error_handler=CustomErrorHandler())\n    ```\n\n### Custom dumps function\n\n.. column::\n\n    It may sometimes be necessary or desirable to provide a custom function that serializes an object to JSON data.\n\n.. column::\n\n    ```python\n    import ujson\n\n    dumps = partial(ujson.dumps, escape_forward_slashes=False)\n    app = Sanic(__name__, dumps=dumps)\n    ```\n\n\n.. column::\n\n    Or, perhaps use another library or create your own.\n\n.. column::\n\n    ```python\n    from orjson import dumps\n\n    app = Sanic(\"MyApp\", dumps=dumps)\n    ```\n\n### Custom loads function\n\n.. column::\n\n    Similar to `dumps`, you can also provide a custom function for deserializing data.\n\n    *Added in v22.9*\n\n.. column::\n\n    ```python\n    from orjson import loads\n\n    app = Sanic(\"MyApp\", loads=loads)\n    ```\n\n\n\n### Custom typed application\n\nBeginning in v23.6, the correct type annotation of a default Sanic application instance is:\n\n```python\nsanic.app.Sanic[sanic.config.Config, types.SimpleNamespace]\n```\n\nIt refers to two generic types:\n\n1. The first is the type of the configuration object. It defaults to :class:`sanic.config.Config`, but can be any subclass of that.\n2. The second is the type of the application context. It defaults to [`SimpleNamespace()`](https://docs.python.org/3/library/types.html#types.SimpleNamespace), but can be **any object** as show above.\n\nLet's look at some examples of how the type will change.\n\n.. column::\n\n    Consider this example where we pass a custom subclass of :class:`sanic.config.Config` and a custom context object.\n\n.. column::\n\n    ```python\n    from sanic import Sanic\n    from sanic.config import Config\n\n    class CustomConfig(Config):\n        pass\n\n    app = Sanic(\"test\", config=CustomConfig())\n    reveal_type(app) # N: Revealed type is \"sanic.app.Sanic[main.CustomConfig, types.SimpleNamespace]\"\n    ```\n    ```\n    sanic.app.Sanic[main.CustomConfig, types.SimpleNamespace]\n    ```\n\n\n.. column::\n\n    Similarly, when passing a custom context object, the type will change to reflect that.\n\n.. column::\n\n    ```python\n    from sanic import Sanic\n\n    class Foo:\n        pass\n\n    app = Sanic(\"test\", ctx=Foo())\n    reveal_type(app)  # N: Revealed type is \"sanic.app.Sanic[sanic.config.Config, main.Foo]\"\n    ```\n    ```\n    sanic.app.Sanic[sanic.config.Config, main.Foo]\n    ```\n\n\n.. column::\n\n    Of course, you can set both the config and context to custom types.\n\n.. column::\n\n    ```python\n    from sanic import Sanic\n    from sanic.config import Config\n\n    class CustomConfig(Config):\n        pass\n\n    class Foo:\n        pass\n\n    app = Sanic(\"test\", config=CustomConfig(), ctx=Foo())\n    reveal_type(app)  # N: Revealed type is \"sanic.app.Sanic[main.CustomConfig, main.Foo]\"\n    ```\n    ```\n    sanic.app.Sanic[main.CustomConfig, main.Foo]\n    ```\n\nThis pattern is particularly useful if you create a custom type alias for your application instance so that you can use it to annotate listeners and handlers.\n\n```python\n# ./path/to/types.py\nfrom sanic.app import Sanic\nfrom sanic.config import Config\nfrom myapp.context import MyContext\nfrom typing import TypeAlias\n\nMyApp = TypeAlias(\"MyApp\", Sanic[Config, MyContext])\n```\n\n```python\n# ./path/to/listeners.py\nfrom myapp.types import MyApp\n\ndef add_listeners(app: MyApp):\n    @app.before_server_start\n    async def before_server_start(app: MyApp):\n        # do something with your fully typed app instance\n        await app.ctx.db.connect()\n```\n\n```python\n# ./path/to/server.py\nfrom myapp.types import MyApp\nfrom myapp.context import MyContext\nfrom myapp.config import MyConfig\nfrom myapp.listeners import add_listeners\n\napp = Sanic(\"myapp\", config=MyConfig(), ctx=MyContext())\nadd_listeners(app)\n```\n\n*Added in v23.6*\n\n### Custom typed request\n\nSanic also allows you to customize the type of the request object. This is useful if you want to add custom properties to the request object, or be able to access your custom properties of a typed application instance.\n\nThe correct, default type of a Sanic request instance is:\n\n```python\nsanic.request.Request[\n    sanic.app.Sanic[sanic.config.Config, types.SimpleNamespace],\n    types.SimpleNamespace\n]\n```\n\nIt refers to two generic types:\n\n1. The first is the type of the application instance. It defaults to `sanic.app.Sanic[sanic.config.Config, types.SimpleNamespace]`, but can be any subclass of that.\n2. The second is the type of the request context. It defaults to `types.SimpleNamespace`, but can be **any object** as show above in [custom requests](#custom-requests).\n\nLet's look at some examples of how the type will change.\n\n.. column::\n\n    Expanding upon the full example above where there is a type alias for a customized application instance, we can also create a custom request type so that we can access those same type annotations.\n\n    Of course, you do not need type aliases for this to work. We are only showing them here to cut down on the amount of code shown.\n\n.. column::\n\n    ```python\n    from sanic import Request\n    from myapp.types import MyApp\n    from types import SimpleNamespace\n\n    def add_routes(app: MyApp):\n        @app.get(\"/\")\n        async def handler(request: Request[MyApp, SimpleNamespace]):\n            # do something with your fully typed app instance\n            results = await request.app.ctx.db.query(\"SELECT * FROM foo\")\n    ```\n\n\n.. column::\n\n    Perhaps you have a custom request object that generates a custom context object. You can type annotate it to properly access those properties with your IDE as shown here.\n\n.. column::\n\n    ```python\n    from sanic import Request, Sanic\n    from sanic.config import Config\n\n    class CustomConfig(Config):\n        pass\n\n    class Foo:\n        pass\n\n    class RequestContext:\n        foo: Foo\n\n    class CustomRequest(Request[Sanic[CustomConfig, Foo], RequestContext]):\n        @staticmethod\n        def make_context() -> RequestContext:\n            ctx = RequestContext()\n            ctx.foo = Foo()\n            return ctx\n\n    app = Sanic(\n        \"test\", config=CustomConfig(), ctx=Foo(), request_class=CustomRequest\n    )\n\n    @app.get(\"/\")\n    async def handler(request: CustomRequest):\n        # Full access to typed:\n        # - custom application configuration object\n        # - custom application context object\n        # - custom request context object\n        pass\n    ```\n\nSee more information in the [custom request context](./request.md#custom-request-context) section.\n\n*Added in v23.6*\n\n"
  },
  {
    "path": "guide/content/en/guide/basics/cookies.md",
    "content": "# Cookies\n\n## Reading\n\n.. column::\n\n    Cookies can be accessed via the `Request` object’s `cookies` dictionary.\n\n.. column::\n\n    ```python\n    @app.route(\"/cookie\")\n    async def test(request):\n        test_cookie = request.cookies.get(\"test\")\n        return text(f\"Test cookie: {test_cookie}\")\n    ```\n\n\n\n.. tip:: FYI\n\n    💡 The `request.cookies` object is one of a few types that is a dictionary with each value being a `list`. This is because HTTP allows a single key to be reused to send multiple values.\n\n    Most of the time you will want to use the `.get()` method to access the first element and not a `list`. If you do want a `list` of all items, you can use `.getlist()`.\n\n    *Added in v23.3*\n\n\n\n## Writing\n\n.. column::\n\n    When returning a response, cookies can be set on the `Response` object: `response.cookies`. This object is an instance of `CookieJar` which is a special sort of dictionary that automatically will write the response headers for you.\n\n.. column::\n\n    ```python\n    @app.route(\"/cookie\")\n    async def test(request):\n        response = text(\"There's a cookie up in this response\")\n        response.add_cookie(\n            \"test\",\n            \"It worked!\",\n            domain=\".yummy-yummy-cookie.com\",\n            httponly=True\n        )\n        return response\n    ```\n\nResponse cookies can be set like dictionary values and have the following parameters available:\n\n- `path: str` - The subset of URLs to which this cookie applies. Defaults to `/`.\n- `domain: str` - Specifies the domain for which the cookie is valid. An explicitly specified domain must always start with a dot.\n- `max_age: int` - Number of seconds the cookie should live for.\n- `expires: datetime` - The time for the cookie to expire on the client’s browser. Usually it is better to use max-age instead.\n- `secure: bool` - Specifies whether the cookie will only be sent via HTTPS. Defaults to `True`.\n- `httponly: bool` - Specifies whether the cookie cannot be read by JavaScript.\n- `samesite: str` - Available values: Lax, Strict, and None. Defaults to `Lax`.\n- `comment: str` - A comment (metadata).\n- `host_prefix: bool` - Whether to add the `__Host-` prefix to the cookie.\n- `secure_prefix: bool` - Whether to add the `__Secure-` prefix to the cookie.\n- `partitioned: bool` - Whether to mark the cookie as partitioned.\n\nTo better understand the implications and usage of these values, it might be helpful to read the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies) on [setting cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie).\n\n\n.. tip:: FYI\n\n    By default, Sanic will set the `secure` flag to `True` to ensure that cookies are only sent over HTTPS as a sensible default. This should not be impactful for local development since secure cookies over HTTP should still be sent to `localhost`. For more information, you should read the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies) on [secure cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#Secure).\n\n\n## Deleting\n\n.. column::\n\n    Cookies can be removed semantically or explicitly.\n\n.. column::\n\n    ```python\n    @app.route(\"/cookie\")\n    async def test(request):\n        response = text(\"Time to eat some cookies muahaha\")\n\n        # This cookie will be set to expire in 0 seconds\n        response.delete_cookie(\"eat_me\")\n\n        # This cookie will self destruct in 5 seconds\n        response.add_cookie(\"fast_bake\", \"Be quick!\", max_age=5)\n\n        return response\n    ```\n\n    *Don't forget to add `path` or `domain` if needed!*\n\n## Eating\n\n.. column::\n\n    Sanic likes cookies\n    \n.. column::\n\n    .. attrs::\n        :class: is-size-1 has-text-centered\n        \n        🍪\n"
  },
  {
    "path": "guide/content/en/guide/basics/handlers.md",
    "content": "# Handlers\n\nThe next important building block are your _handlers_. These are also sometimes called \"views\".\n\nIn Sanic, a handler is any callable that takes at least a :class:`sanic.request.Request` instance as an argument, and returns either an :class:`sanic.response.HTTPResponse` instance, or a coroutine that does the same.\n\n.. column::\n\n    Huh? 😕\n\n    It is a **function**; either synchronous or asynchronous.\n\n    The job of the handler is to respond to an endpoint and do something. This is where the majority of your business logic will go.\n\n.. column::\n\n    ```python\n    def i_am_a_handler(request):\n        return HTTPResponse()\n\n    async def i_am_ALSO_a_handler(request):\n        return HTTPResponse()\n    ```\n\nTwo more important items to note:\n\n1. You almost *never* will want to use :class:`sanic.response.HTTPresponse` directly. It is much simpler to use one of the [convenience methods](./response.md#methods).\n\n    - `from sanic import json`\n    - `from sanic import html`\n    - `from sanic import redirect`\n    - *etc*\n    \n1. As we will see in [the streaming section](../advanced/streaming.md#response-streaming), you do not always need to return an object. If you use this lower-level API, you can control the flow of the response from within the handler, and a return object is not used.\n\n.. tip:: Heads up\n\n    If you want to learn more about encapsulating your logic, checkout [class based views](../advanced/class-based-views.md). For now, we will continue forward with just function-based views.\n\n\n### A simple function-based handler\n\nThe most common way to create a route handler is to decorate the function. It creates a visually simple identification of a route definition. We'll learn more about [routing soon](./routing.md).\n\n.. column::\n\n    Let's look at a practical example.\n\n    - We use a convenience decorator on our app instance: `@app.get()`\n    - And a handy convenience method for generating out response object: `text()`\n\n    Mission accomplished 💪\n\n.. column::\n\n    ```python\n    from sanic import text\n\n    @app.get(\"/foo\")\n    async def foo_handler(request):\n        return text(\"I said foo!\")\n    ```\n\n---\n\n## A word about _async_...\n\n.. column::\n\n    It is entirely possible to write handlers that are synchronous.\n\n    In this example, we are using the _blocking_ `time.sleep()` to simulate 100ms of processing time. Perhaps this represents fetching data from a DB, or a 3rd-party website.\n\n    Using four (4) worker processes and a common benchmarking tool:\n\n    - **956** requests in 30.10s\n    - Or, about **31.76** requests/second\n\n.. column::\n\n    ```python\n    @app.get(\"/sync\")\n    def sync_handler(request):\n        time.sleep(0.1)\n        return text(\"Done.\")\n    ```\n\n\n.. column::\n\n    Just by changing to the asynchronous alternative `asyncio.sleep()`, we see an incredible change in performance. 🚀\n\n    Using the same four (4) worker processes:\n\n    - **115,590** requests in 30.08s\n    - Or, about **3,843.17** requests/second\n\n    .. attrs::\n        :class: is-size-2\n    \n        🤯\n\n.. column::\n\n    ```python\n    @app.get(\"/async\")\n    async def async_handler(request):\n        await asyncio.sleep(0.1)\n        return text(\"Done.\")\n    ```\n\n\nOkay... this is a ridiculously overdramatic result. And any benchmark you see is inherently very biased. This example is meant to over-the-top show the benefit of `async/await` in the web world. Results will certainly vary. Tools like Sanic and other async Python libraries are not magic bullets that make things faster. They make them _more efficient_.\n\nIn our example, the asynchronous version is so much better because while one request is sleeping, it is able to start another one, and another one, and another one, and another one...\n\nBut, this is the point! Sanic is fast because it takes the available resources and squeezes performance out of them. It can handle many requests concurrently, which means more requests per second.\n\n\n.. tip:: A common mistake!\n\n    Don't do this! You need to ping a website. What do you use? `pip install your-fav-request-library` 🙈\n\n    Instead, try using a client that is `async/await` capable. Your server will thank you. Avoid using blocking tools, and favor those that play well in the asynchronous ecosystem. If you need recommendations, check out [Awesome Sanic](https://github.com/mekicha/awesome-sanic).\n\n    Sanic uses [httpx](https://www.python-httpx.org/) inside of its testing package (sanic-testing) 😉.\n\n\n---\n\n## A fully annotated handler\n\nFor those that are using type annotations...\n\n```python\nfrom sanic.response import HTTPResponse, text\nfrom sanic.request import Request\n\n@app.get(\"/typed\")\nasync def typed_handler(request: Request) -> HTTPResponse:\n    return text(\"Done.\")\n```\n\n## Naming your handlers\n\nAll handlers are named automatically. This is useful for debugging, and for generating URLs in templates. When not specified, the name that will be used is the name of the function.\n\n.. column::\n\n    For example, this handler will be named `foo_handler`.\n\n.. column::\n\n    ```python\n    # Handler name will be \"foo_handler\"\n    @app.get(\"/foo\")\n    async def foo_handler(request):\n        return text(\"I said foo!\")\n    ```\n\n.. column::\n\n    However, you can override this by passing the `name` argument to the decorator.\n\n.. column::\n\n    ```python\n    # Handler name will be \"foo\"\n    @app.get(\"/foo\", name=\"foo\")\n    async def foo_handler(request):\n        return text(\"I said foo!\")\n    ```\n\n.. column::\n\n    In fact, as you will, there may be times when you **MUST** supply a name. For example, if you use two decorators on the same function, you will need to supply a name for at least one of them.\n    \n    If you do not, you will get an error and your app will not start. Names **must** be unique within your app.\n\n.. column::\n\n    ```python\n    # Two handlers, same function,\n    # different names:\n    # - \"foo_arg\"\n    # - \"foo\"\n    @app.get(\"/foo/<arg>\", name=\"foo_arg\")\n    @app.get(\"/foo\")\n    async def foo(request, arg=None):\n        return text(\"I said foo!\")\n    ```\n"
  },
  {
    "path": "guide/content/en/guide/basics/headers.md",
    "content": "# Headers\n\nRequest and response headers are available in the `Request` and `HTTPResponse` objects, respectively. They make use of the [`multidict` package](https://multidict.readthedocs.io/en/stable/multidict.html#cimultidict) that allows a single key to have multiple values.\n\n\n.. tip:: FYI\n\n    Header keys are converted to *lowercase* when parsed. Capitalization is not considered for headers.\n\n\n## Request\n\nSanic does attempt to do some normalization on request headers before presenting them to the developer, and also make some potentially meaningful extractions for common use cases.\n\n.. column::\n\n    #### Tokens\n\n    Authorization tokens in the form `Token <token>` or `Bearer <token>` are extracted to the request object: `request.token`.\n\n.. column::\n\n    ```python\n    @app.route(\"/\")\n    async def handler(request):\n        return text(request.token)\n    ```\n\n    ```sh\n    curl localhost:8000 \\\n        -H \"Authorization: Token ABCDEF12345679\"\n    ABCDEF12345679\n    ```\n\n    ```sh\n    curl localhost:8000 \\\n        -H \"Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c\"\n    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c\n    ```\n\n### Proxy headers\n\nSanic has special handling for proxy headers. See the [proxy headers](../advanced/proxy-headers.md) section for more details.\n\n### Host header and dynamic URL construction\n\n.. column::\n\n    The *effective host* is available via `request.host`. This is not necessarily the same as the host header, as it prefers proxy-forwarded host and can be forced by the server name setting.\n\n    Webapps should generally use this accessor so that they can function the same no matter how they are deployed. The actual host header, if needed, can be found via `request.headers`\n\n    The effective host is also used in dynamic URL construction via `request.url_for`, which uses the request to determine the external address of a handler.\n\n    .. tip:: Be wary of malicious clients\n\n        These URLs can be manipulated by sending misleading host headers. `app.url_for` should be used instead if this is a concern.\n\n.. column::\n\n    ```python\n    app.config.SERVER_NAME = \"https://example.com\"\n\n    @app.route(\"/hosts\", name=\"foo\")\n    async def handler(request):\n        return json(\n            {\n                \"effective host\": request.host,\n                \"host header\": request.headers.get(\"host\"),\n                \"forwarded host\": request.forwarded.get(\"host\"),\n                \"you are here\": request.url_for(\"foo\"),\n            }\n        )\n    ```\n\n    ```sh\n    curl localhost:8000/hosts\n    {\n      \"effective host\": \"example.com\",\n      \"host header\": \"localhost:8000\",\n      \"forwarded host\": null,\n      \"you are here\": \"https://example.com/hosts\"\n    }\n    ```\n\n### Other headers\n\n.. column::\n\n\n    All request headers are available on `request.headers`, and can be accessed in dictionary form. Capitalization is not considered for headers, and can be accessed using either uppercase or lowercase keys.\n\n.. column::\n\n    ```python\n    @app.route(\"/\")\n    async def handler(request):\n        return json(\n            {\n                \"foo_weakref\": request.headers[\"foo\"],\n                \"foo_get\": request.headers.get(\"Foo\"),\n                \"foo_getone\": request.headers.getone(\"FOO\"),\n                \"foo_getall\": request.headers.getall(\"fOo\"),\n                \"all\": list(request.headers.items()),\n            }\n        )\n    ```\n\n    ```sh\n    curl localhost:9999/headers -H \"Foo: one\" -H \"FOO: two\"|jq\n    {\n      \"foo_weakref\": \"one\",\n      \"foo_get\": \"one\",\n      \"foo_getone\": \"one\",\n      \"foo_getall\": [\n        \"one\",\n        \"two\"\n      ],\n      \"all\": [\n        [\n          \"host\",\n          \"localhost:9999\"\n        ],\n        [\n          \"user-agent\",\n          \"curl/7.76.1\"\n        ],\n        [\n          \"accept\",\n          \"*/*\"\n        ],\n        [\n          \"foo\",\n          \"one\"\n        ],\n        [\n          \"foo\",\n          \"two\"\n        ]\n      ]\n    }\n    ```\n\n\n\n.. tip:: FYI\n\n    💡 The request.headers object is one of a few types that is a dictionary with each value being a list. This is because HTTP allows a single key to be reused to send multiple values.\n\n    Most of the time you will want to use the .get() or .getone() methods to access the first element and not a list. If you do want a list of all items, you can use .getall().\n\n\n### Request ID\n\n.. column::\n\n    Often it is convenient or necessary to track a request by its `X-Request-ID` header. You can easily access that as: `request.id`.\n\n.. column::\n\n    ```python\n    @app.route(\"/\")\n    async def handler(request):\n        return text(request.id)\n    ```\n\n    ```sh\n    curl localhost:8000 \\\n        -H \"X-Request-ID: ABCDEF12345679\"\n    ABCDEF12345679\n    ```\n\n## Response\n\nSanic will automatically set the following response headers (when appropriate) for you:\n\n- `content-length`\n- `content-type`\n- `connection`\n- `transfer-encoding`\n\nIn most circumstances, you should never need to worry about setting these headers.\n\n.. column::\n\n    Any other header that you would like to set can be done either in the route handler, or a response middleware.\n\n.. column::\n\n    ```python\n    @app.route(\"/\")\n    async def handler(request):\n        return text(\"Done.\", headers={\"content-language\": \"en-US\"})\n\n    @app.middleware(\"response\")\n    async def add_csp(request, response):\n        response.headers[\"content-security-policy\"] = \"default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self';base-uri 'self';form-action 'self'\"\n    ```\n\n\n.. column::\n\n    A common [middleware](middleware.md) you might want is to add a `X-Request-ID` header to every response. As stated above: `request.id` will provide the ID from the incoming request. But, even if no ID was supplied in the request headers, one will be automatically supplied for you.\n\n    [See API docs for more details](https://sanic.readthedocs.io/en/latest/sanic/api_reference.html#sanic.request.Request.id)\n\n.. column::\n\n    ```python\n    @app.route(\"/\")\n    async def handler(request):\n        return text(str(request.id))\n\n    @app.on_response\n    async def add_request_id_header(request, response):\n        response.headers[\"X-Request-ID\"] = request.id\n    ```\n\n    ```sh\n    curl localhost:8000 -i\n    HTTP/1.1 200 OK\n    X-Request-ID: 805a958e-9906-4e7a-8fe0-cbe83590431b\n    content-length: 36\n    connection: keep-alive\n    content-type: text/plain; charset=utf-8\n\n    805a958e-9906-4e7a-8fe0-cbe83590431b\n    ```\n\n"
  },
  {
    "path": "guide/content/en/guide/basics/listeners.md",
    "content": "# Listeners\n\nSanic provides you with eight (8) opportunities to inject an operation into the life cycle of your application server. This does not include the [signals](../advanced/signals.md), which allow further injection customization.\n\nThere are two (2) that run **only** on your main Sanic process (ie, once per call to `sanic server.app`.)\n\n- `main_process_start`\n- `main_process_stop`\n\nThere are also two (2) that run **only** in a reloader process if auto-reload has been turned on.\n\n- `reload_process_start`\n- `reload_process_stop`\n\n*Added `reload_process_start` and `reload_process_stop` in v22.3*\n\nThere are four (4) that enable you to execute startup/teardown code as your server starts or closes.\n\n- `before_server_start`\n- `after_server_start`\n- `before_server_stop`\n- `after_server_stop`\n\nThe life cycle of a worker process looks like this:\n\n.. mermaid::\n\n    sequenceDiagram\n    autonumber\n    participant Process\n    participant Worker\n    participant Listener\n    participant Handler\n    Note over Process: sanic server.app\n    loop\n        Process->>Listener: @app.main_process_start\n        Listener->>Handler: Invoke event handler\n    end\n    Process->>Worker: Run workers\n    loop Start each worker\n        loop\n            Worker->>Listener: @app.before_server_start\n            Listener->>Handler: Invoke event handler\n        end\n        Note over Worker: Server status: started\n        loop\n            Worker->>Listener: @app.after_server_start\n            Listener->>Handler: Invoke event handler\n        end\n        Note over Worker: Server status: ready\n    end\n    Process->>Worker: Graceful shutdown\n    loop Stop each worker\n        loop\n            Worker->>Listener: @app.before_server_stop\n            Listener->>Handler: Invoke event handler\n        end\n        Note over Worker: Server status: stopped\n        loop\n            Worker->>Listener: @app.after_server_stop\n            Listener->>Handler: Invoke event handler\n        end\n        Note over Worker: Server status: closed\n    end\n    loop\n        Process->>Listener: @app.main_process_stop\n        Listener->>Handler: Invoke event handler\n    end\n    Note over Process: exit\n\n\nThe reloader process live outside of this worker process inside of a process that is responsible for starting and stopping the Sanic processes. Consider the following example:\n\n```python\n@app.reload_process_start\nasync def reload_start(*_):\n    print(\">>>>>> reload_start <<<<<<\")\n\n@app.main_process_start\nasync def main_start(*_):\n    print(\">>>>>> main_start <<<<<<\")\n\t\n@app.before_server_start\nasync def before_start(*_):\n\tprint(\">>>>>> before_start <<<<<<\")\n```\n\nIf this application were run with auto-reload turned on, the `reload_start` function would be called once when the reloader process starts. The `main_start` function would also be called once when the main process starts. **HOWEVER**, the `before_start` function would be called once for each worker process that is started, and subsequently every time that a file is saved and the worker is restarted.\n\n## Attaching a listener\n\n.. column::\n\n    The process to setup a function as a listener is similar to declaring a route.\n\n    The currently running `Sanic()` instance is injected into the listener.\n\n.. column::\n\n    ```python\n    async def setup_db(app):\n        app.ctx.db = await db_setup()\n\n    app.register_listener(setup_db, \"before_server_start\")\n    ```\n\n\n.. column::\n\n    The `Sanic` app instance also has a convenience decorator.\n\n.. column::\n\n    ```python\n    @app.listener(\"before_server_start\")\n    async def setup_db(app):\n        app.ctx.db = await db_setup()\n    ```\n\n\n.. column::\n\n    Prior to v22.3, both the application instance and the current event loop were injected into the function. However, only the application instance is injected by default. If your function signature will accept both, then both the application and the loop will be injected as shown here.\n\n.. column::\n\n    ```python\n    @app.listener(\"before_server_start\")\n    async def setup_db(app, loop):\n        app.ctx.db = await db_setup()\n    ```\n\n\n.. column::\n\n    You can shorten the decorator even further. This is helpful if you have an IDE with autocomplete.\n\n.. column::\n\n    ```python\n    @app.before_server_start\n    async def setup_db(app):\n        app.ctx.db = await db_setup()\n    ```\n\n## Order of execution\n\nListeners are executed in the order they are declared during startup, and reverse order of declaration during teardown\n\n|                       | Phase           | Order   |\n|-----------------------|-----------------|---------|\n| `main_process_start`  | main startup    | regular 🙂 ⬇️ |\n| `before_server_start` | worker startup  | regular 🙂 ⬇️ |\n| `after_server_start`  | worker startup  | regular 🙂 ⬇️ |\n| `before_server_stop`  | worker shutdown | 🙃 ⬆️ reverse |\n| `after_server_stop`   | worker shutdown | 🙃 ⬆️ reverse |\n| `main_process_stop`   | main shutdown   | 🙃 ⬆️ reverse |\n\nGiven the following setup, we should expect to see this in the console if we run two workers.\n\n.. column::\n\n    ```python\n    @app.listener(\"before_server_start\")\n    async def listener_1(app, loop):\n        print(\"listener_1\")\n\n    @app.before_server_start\n    async def listener_2(app, loop):\n        print(\"listener_2\")\n\n    @app.listener(\"after_server_start\")\n    async def listener_3(app, loop):\n        print(\"listener_3\")\n\n    @app.after_server_start\n    async def listener_4(app, loop):\n        print(\"listener_4\")\n\n    @app.listener(\"before_server_stop\")\n    async def listener_5(app, loop):\n        print(\"listener_5\")\n\n    @app.before_server_stop\n    async def listener_6(app, loop):\n        print(\"listener_6\")\n\n    @app.listener(\"after_server_stop\")\n    async def listener_7(app, loop):\n        print(\"listener_7\")\n\n    @app.after_server_stop\n    async def listener_8(app, loop):\n        print(\"listener_8\")\n    ```\n\n.. column::\n\n    ```bash\n    [pid: 1000000] [INFO] Goin' Fast @ http://127.0.0.1:9999\n    [pid: 1000000] [INFO] listener_0\n    [pid: 1111111] [INFO] listener_1\n    [pid: 1111111] [INFO] listener_2\n    [pid: 1111111] [INFO] listener_3\n    [pid: 1111111] [INFO] listener_4\n    [pid: 1111111] [INFO] Starting worker [1111111]\n    [pid: 1222222] [INFO] listener_1\n    [pid: 1222222] [INFO] listener_2\n    [pid: 1222222] [INFO] listener_3\n    [pid: 1222222] [INFO] listener_4\n    [pid: 1222222] [INFO] Starting worker [1222222]\n    [pid: 1111111] [INFO] Stopping worker [1111111]\n    [pid: 1222222] [INFO] Stopping worker [1222222]\n    [pid: 1222222] [INFO] listener_6\n    [pid: 1222222] [INFO] listener_5\n    [pid: 1222222] [INFO] listener_8\n    [pid: 1222222] [INFO] listener_7\n    [pid: 1111111] [INFO] listener_6\n    [pid: 1111111] [INFO] listener_5\n    [pid: 1111111] [INFO] listener_8\n    [pid: 1111111] [INFO] listener_7\n    [pid: 1000000] [INFO] listener_9\n    [pid: 1000000] [INFO] Server Stopped\n    ```\n    In the above example, notice how there are three processes running:\n\n    - `pid: 1000000` - The *main* process\n    - `pid: 1111111` - Worker 1\n    - `pid: 1222222` - Worker 2\n\n    *Just because our example groups all of one worker and then all of another, in reality since these are running on separate processes, the ordering between processes is not guaranteed. But, you can be sure that a single worker will **always** maintain its order.*\n\n\n\n.. tip:: FYI\n\n    The practical result of this is that if the first listener in `before_server_start` handler setups a database connection, listeners that are registered after it can rely upon that connection being alive both when they are started and stopped.\n\n### Priority\n\nIn v23.12, the `priority` keyword argument was added to listeners. This allows for fine-tuning the order of execution of listeners. The default priority is `0`. Listeners with a higher priority will be executed first. Listeners with the same priority will be executed in the order they were registered. Furthermore, listeners attached to the `app` instance will be executed before listeners attached to a `Blueprint` instance.\n\t\nOverall the rules for deciding the order of execution are as follows:\n\n1. Priority in descending order\n2. Application listeners before Blueprint listeners\n3. Registration order\n\n.. column::\n\n    As an example, consider the following, which will print:\n\n    ```bash\n    third\n    bp_third\n    second\n    bp_second\n    first\n    fourth\n    bp_first\n    ```\n\n.. column::\n\n    ```python\n    @app.before_server_start\n    async def first(app):\n        print(\"first\")\n\n    @app.listener(\"before_server_start\", priority=2)\n    async def second(app):\n        print(\"second\")\n\n    @app.before_server_start(priority=3)\n    async def third(app):\n        print(\"third\")\n\n    @bp.before_server_start\n    async def bp_first(app):\n        print(\"bp_first\")\n\n    @bp.listener(\"before_server_start\", priority=2)\n    async def bp_second(app):\n        print(\"bp_second\")\n\n    @bp.before_server_start(priority=3)\n    async def bp_third(app):\n        print(\"bp_third\")\n\n    @app.before_server_start\n    async def fourth(app):\n        print(\"fourth\")\n\n    app.blueprint(bp)\n    ```\n\n## ASGI Mode\n\nIf you are running your application with an ASGI server, then make note of the following changes:\n\n- `reload_process_start` and `reload_process_stop` will be **ignored**\n- `main_process_start` and `main_process_stop` will be **ignored**\n- `before_server_start` will run as early as it can, and will be before `after_server_start`, but technically, the server is already running at that point\n- `after_server_stop` will run as late as it can, and will be after `before_server_stop`, but technically, the server is still running at that point\n"
  },
  {
    "path": "guide/content/en/guide/basics/middleware.md",
    "content": "# Middleware\n\nWhereas listeners allow you to attach functionality to the lifecycle of a worker process, middleware allows you to attach functionality to the lifecycle of an HTTP stream.\n\n```python\n@app.on_request\nasync def example(request):\n\tprint(\"I execute before the handler.\")\n```\n\nYou can execute middleware either _before_ the handler is executed, or _after_.\n\n```python\n@app.on_response\nasync def example(request, response):\n\tprint(\"I execute after the handler.\")\n```\n\n.. mermaid::\n\n    sequenceDiagram\n    autonumber\n    participant Worker\n    participant Middleware\n    participant MiddlewareHandler\n    participant RouteHandler\n    Note over Worker: Incoming HTTP request\n    loop\n        Worker->>Middleware: @app.on_request\n        Middleware->>MiddlewareHandler: Invoke middleware handler\n        MiddlewareHandler-->>Worker: Return response (optional)\n    end\n    rect rgba(255, 13, 104, .1)\n    Worker->>RouteHandler: Invoke route handler\n    RouteHandler->>Worker: Return response\n    end\n    loop\n        Worker->>Middleware: @app.on_response\n        Middleware->>MiddlewareHandler: Invoke middleware handler\n        MiddlewareHandler-->>Worker: Return response (optional)\n    end\n    Note over Worker: Deliver response\n\n## Attaching middleware\n\n.. column::\n\n    This should probably look familiar by now. All you need to do is declare when you would like the middleware to execute: on the `request` or on the `response`.\n\n.. column::\n\n    ```python\n    async def extract_user(request):\n        request.ctx.user = await extract_user_from_request(request)\n\n    app.register_middleware(extract_user, \"request\")\n    ```\n\n.. column::\n\n    Again, the `Sanic` app instance also has a convenience decorator.\n\n.. column::\n\n    ```python\n    @app.middleware(\"request\")\n    async def extract_user(request):\n        request.ctx.user = await extract_user_from_request(request)\n    ```\n\n.. column::\n\n    Response middleware receives both the `request` and `response` arguments.\n\n.. column::\n\n    ```python\n    @app.middleware('response')\n    async def prevent_xss(request, response):\n        response.headers[\"x-xss-protection\"] = \"1; mode=block\"\n    ```\n\n.. column::\n\n    You can shorten the decorator even further. This is helpful if you have an IDE with autocomplete.\n\n    This is the preferred usage, and is what we will use going forward.\n\n.. column::\n\n    ```python\n    @app.on_request\n    async def extract_user(request):\n        ...\n\n    @app.on_response\n    async def prevent_xss(request, response):\n        ...\n    ```\n\n## Modification\n\nMiddleware can modify the request or response parameter it is given, _as long as it does not return it_.\n\n\n.. column::\n\n    #### Order of execution\n\n    1. Request middleware: `add_key`\n    2. Route handler: `index`\n    3. Response middleware: `prevent_xss`\n    4. Response middleware: `custom_banner`\n\n.. column::\n\n    ```python\n    @app.on_request\n    async def add_key(request):\n        # Arbitrary data may be stored in request context:\n        request.ctx.foo = \"bar\"\n\n    @app.on_response\n    async def custom_banner(request, response):\n        response.headers[\"Server\"] = \"Fake-Server\"\n\n    @app.on_response\n    async def prevent_xss(request, response):\n        response.headers[\"x-xss-protection\"] = \"1; mode=block\"\n\n    @app.get(\"/\")\n    async def index(request):\n        return text(request.ctx.foo)\n\n    ```\n\n.. column::\n\n    You can modify the `request.match_info`. A useful feature that could be used, for example, in middleware to convert `a-slug` to `a_slug`.\n\n.. column::\n\n    ```python\n    @app.on_request\n    def convert_slug_to_underscore(request: Request):\n        request.match_info[\"slug\"] = request.match_info[\"slug\"].replace(\"-\", \"_\")\n\n    @app.get(\"/<slug:slug>\")\n    async def handler(request, slug):\n        return text(slug)\n    ```\n    ```\n    $ curl localhost:9999/foo-bar-baz\n    foo_bar_baz\n    ```\n\n## Responding early\n\n.. column::\n\n    If middleware returns a `HTTPResponse` object, the request will stop processing and the response will be returned. If this occurs to a request before the route handler is reached, the handler will **not** be called. Returning a response will also prevent any further middleware from running.\n\n    \n\n.. tip:: \n\n    You can return a `None` value to stop the execution of the middleware handler to allow the request to process as normal. This can be useful when using early return to avoid processing requests inside of that middleware handler.\n\n.. column::\n\n    ```python\n    @app.on_request\n    async def halt_request(request):\n        return text(\"I halted the request\")\n\n    @app.on_response\n    async def halt_response(request, response):\n        return text(\"I halted the response\")\n    ```\n\n## Order of execution\n\nRequest middleware is executed in the order declared. Response middleware is executed in **reverse order**.\n\nGiven the following setup, we should expect to see this in the console.\n\n.. column::\n\n    ```python\n    @app.on_request\n    async def middleware_1(request):\n        print(\"middleware_1\")\n\n    @app.on_request\n    async def middleware_2(request):\n        print(\"middleware_2\")\n\n    @app.on_response\n    async def middleware_3(request, response):\n        print(\"middleware_3\")\n\n    @app.on_response\n    async def middleware_4(request, response):\n        print(\"middleware_4\")\n    \n    @app.get(\"/handler\")\n    async def handler(request):\n        print(\"~ handler ~\")\n        return text(\"Done.\")\n    ```\n\n.. column::\n\n    ```bash\n    middleware_1\n    middleware_2\n    ~ handler ~\n    middleware_4\n    middleware_3\n    [INFO][127.0.0.1:44788]: GET http://localhost:8000/handler  200 5\n    ```\n\n### Middleware priority\n\n.. column::\n\n    You can modify the order of execution of middleware by assigning it a higher priority. This happens inside of the middleware definition. The higher the value, the earlier it will execute relative to other middleware. The default priority for middleware is `0`.\n\n.. column::\n\n    ```python\n    @app.on_request\n    async def low_priority(request):\n        ...\n\n    @app.on_request(priority=99)\n    async def high_priority(request):\n        ...\n    ```\n\n*Added in v22.9*\n"
  },
  {
    "path": "guide/content/en/guide/basics/request.md",
    "content": "# Request\n\nSee API docs: [sanic.request](/api/sanic.request)\n\nThe :class:`sanic.request.Request` instance contains **a lot** of helpful information available on its parameters. Refer to the [API documentation](https://sanic.readthedocs.io/) for full details.\n\nAs we saw in the section on [handlers](./handlers.md), the first argument in a route handler is usually the :class:`sanic.request.Request` object. Because Sanic is an async framework, the handler will run inside of a [`asyncio.Task`](https://docs.python.org/3/library/asyncio-task.html#asyncio.Task) and will be scheduled by the event loop. This means that the handler will be executed in an isolated context and the request object will be unique to that handler's task.\n\n.. column::\n\n    By convention, the argument is named `request`, but you can name it whatever you want. The name of the argument is not important. Both of the following handlers are valid.\n    \n.. column::\n\n    ```python\n    @app.get(\"/foo\")\n    async def typical_use_case(request):\n        return text(\"I said foo!\")\n    ```\n\n    ```python\n    @app.get(\"/foo\")\n    async def atypical_use_case(req):\n        return text(\"I said foo!\")\n    ```\n\n.. column::\n\n    Annotating a request object is super simple.\n        \n.. column::\n\n    ```python\n    from sanic.request import Request\n    from sanic.response import text\n\n    @app.get(\"/typed\")\n    async def typed_handler(request: Request):\n        return text(\"Done.\")\n    ```\n\n.. tip::\n    \n    For your convenience, assuming you are using a modern IDE, you should leverage type annotations to help with code completion and documentation. This is especially helpful when using the `request` object as it has **MANY** properties and methods.\n        \n    To see the full list of available properties and methods, refer to the [API documentation](/api/sanic.request).\n\n## Body\n\nThe `Request` object allows you to access the content of the request body in a few different ways.\n\n### JSON\n\n.. column::\n\n    **Parameter**: `request.json`  \n    **Description**: The parsed JSON object\n\n.. column::\n\n    ```bash\n    $ curl localhost:8000 -d '{\"foo\": \"bar\"}'\n    ```\n\n    ```python\n    >>> print(request.json)\n    {'foo': 'bar'}\n    ```\n\n### Raw\n\n.. column::\n\n    **Parameter**: `request.body`  \n    **Description**: The raw bytes from the request body\n\n.. column::\n\n    ```bash\n    $ curl localhost:8000 -d '{\"foo\": \"bar\"}'\n    ```\n\n    ```python\n    >>> print(request.body)\n    b'{\"foo\": \"bar\"}'\n    ```\n\n### Form\n\n.. column::\n\n    **Parameter**: `request.form`  \n    **Description**: The form data\n\n    .. tip:: FYI\n\n        The `request.form` object is one of a few types that is a dictionary with each value being a list. This is because HTTP allows a single key to be reused to send multiple values.  \n\n        Most of the time you will want to use the `.get()` method to access the first element and not a list. If you do want a list of all items, you can use `.getlist()`.\n\n.. column::\n\n    ```bash\n    $ curl localhost:8000 -d 'foo=bar'\n    ```\n\n    ```python\n    >>> print(request.body)\n    b'foo=bar'\n\n    >>> print(request.form)\n    {'foo': ['bar']}\n\n    >>> print(request.form.get(\"foo\"))\n    bar\n\n    >>> print(request.form.getlist(\"foo\"))\n    ['bar']\n    ```\n\n### Uploaded\n\n.. column::\n\n    **Parameter**: `request.files`  \n    **Description**: The files uploaded to the server\n\n    .. tip:: FYI\n\n        The `request.files` object is one of a few types that is a dictionary with each value being a list. This is because HTTP allows a single key to be reused to send multiple values.  \n\n        Most of the time you will want to use the `.get()` method to access the first element and not a list. If you do want a list of all items, you can use `.getlist()`.\n\n.. column::\n\n    ```bash\n    $ curl -F 'my_file=@/path/to/TEST' http://localhost:8000\n    ```\n\n    ```python\n    >>> print(request.body)\n    b'--------------------------cb566ad845ad02d3\\r\\nContent-Disposition: form-data; name=\"my_file\"; filename=\"TEST\"\\r\\nContent-Type: application/octet-stream\\r\\n\\r\\nhello\\n\\r\\n--------------------------cb566ad845ad02d3--\\r\\n'\n\n    >>> print(request.files)\n    {'my_file': [File(type='application/octet-stream', body=b'hello\\n', name='TEST')]}\n\n    >>> print(request.files.get(\"my_file\"))\n    File(type='application/octet-stream', body=b'hello\\n', name='TEST')\n\n    >>> print(request.files.getlist(\"my_file\"))\n    [File(type='application/octet-stream', body=b'hello\\n', name='TEST')]\n    ```\n\n## Context\n\n### Request context\n\nThe `request.ctx` object is your playground to store whatever information you need to about the request. This lives only for the duration of the request and is unique to the request.\n\nThis can be constrasted with the `app.ctx` object which is shared across all requests. Be careful not to confuse them! \n\nThe `request.ctx` object by default is a `SimpleNamespace` object allowing you to set arbitrary attributes on it. Sanic will not use this object for anything, so you are free to use it however you want without worrying about name clashes.\n\n### Typical use case\n\nThis is often used to store items like authenticated user details. We will get more into [middleware](./middleware.md) later, but here is a simple example.\n\n```python\n@app.on_request\nasync def run_before_handler(request):\n    request.ctx.user = await fetch_user_by_token(request.token)\n\n@app.route('/hi')\nasync def hi_my_name_is(request):\n    if not request.ctx.user:\n        return text(\"Hmm... I don't know you)\n    return text(f\"Hi, my name is {request.ctx.user.name}\")\n```\n\nAs you can see, the `request.ctx` object is a great place to store information that you need to access in multiple handlers making your code more DRY and easier to maintain. But, as we will learn in the [middleware section](./middleware.md), you can also use it to store information from one middleware that will be used in another.\n\n### Connection context\n\n.. column::\n\n    Often times your API will need to serve multiple concurrent (or consecutive) requests to the same client. This happens, for example, very often with progressive web apps that need to query multiple endpoints to get data.\n\n    The HTTP protocol calls for an easing of overhead time caused by the connection with the use of [keep alive headers](../running/configuration.md#keep-alive-timeout).\n\n    When multiple requests share a single connection, Sanic provides a context object to allow those requests to share state.\n\n.. column::\n\n    ```python\n    @app.on_request\n    async def increment_foo(request):\n        if not hasattr(request.conn_info.ctx, \"foo\"):\n            request.conn_info.ctx.foo = 0\n        request.conn_info.ctx.foo += 1\n\n    @app.get(\"/\")\n    async def count_foo(request):\n        return text(f\"request.conn_info.ctx.foo={request.conn_info.ctx.foo}\")\n    ```\n\n    ```bash\n    $ curl localhost:8000 localhost:8000 localhost:8000\n    request.conn_info.ctx.foo=1\n    request.conn_info.ctx.foo=2\n    request.conn_info.ctx.foo=3\n    ```\n\n.. warning::\n\n    While this looks like a convenient place to store information between requests by a single HTTP connection, do not assume that all requests on a single connection came from a single end user. This is because HTTP proxies and load balancers can multiplex multiple connections into a single connection to your server.\n    \n    **DO NOT** use this to store information about a single user. Use the `request.ctx` object for that.\n\n### Custom Request Objects\n\nAs discussed in [application customization](./app.md#custom-requests), you can create a subclass of :class:`sanic.request.Request` to add additional functionality to the request object. This is useful for adding additional attributes or methods that are specific to your application.\n\n.. column::\n\n    For example, imagine your application sends a custom header that contains a user ID. You can create a custom request object that will parse that header and store the user ID for you.\n\n.. column::\n\n    ```python\n    from sanic import Sanic, Request\n\n    class CustomRequest(Request):\n        def __init__(self, *args, **kwargs):\n            super().__init__(*args, **kwargs)\n            self.user_id = self.headers.get(\"X-User-ID\")\n\n    app = Sanic(\"Example\", request_class=CustomRequest)\n    ```\n\n\n.. column::\n\n    Now, in your handlers, you can access the `user_id` attribute.\n\n.. column::\n\n    ```python\n    @app.route(\"/\")\n    async def handler(request: CustomRequest):\n        return text(f\"User ID: {request.user_id}\")\n    ```\n\n\n### Custom Request Context\n\nBy default, the request context (`request.ctx`) is a [`Simplenamespace`](https://docs.python.org/3/library/types.html#types.SimpleNamespace) object allowing you to set arbitrary attributes on it. While this is super helpful to reuse logic across your application, it can be difficult in the development experience since the IDE will not know what attributes are available.\n\nTo help with this, you can create a custom request context object that will be used instead of the default `SimpleNamespace`. This allows you to add type hints to the context object and have them be available in your IDE.\n\n.. column::\n\n    Start by subclassing the :class:`sanic.request.Request` class to create a custom request type. Then, you will need to add a `make_context()` method that returns an instance of your custom context object. *NOTE: the `make_context` method should be a static method.*\n\n.. column::\n\n    ```python\n    from sanic import Sanic, Request\n    from types import SimpleNamespace\n\n    class CustomRequest(Request):\n        def __init__(self, *args, **kwargs):\n            super().__init__(*args, **kwargs)\n            self.ctx.user_id = self.headers.get(\"X-User-ID\")\n\n        @staticmethod\n        def make_context() -> CustomContext:\n            return CustomContext()\n\n    @dataclass\n    class CustomContext:\n        user_id: str = None\n    ```\n\n.. note::\n\n    This is a Sanic poweruser feature that makes it super convenient in large codebases to have typed request context objects. It is of course not required, but can be very helpful.\n\n*Added in v23.6*\n\n\n## Parameters\n\n.. column::\n\n    Values that are extracted from the path parameters are injected into the handler as argumets, or more specifically as keyword arguments. There is much more detail about this in the [Routing section](./routing.md).\n\n.. column::\n\n    ```python\n    @app.route('/tag/<tag>')\n    async def tag_handler(request, tag):\n        return text(\"Tag - {}\".format(tag))\n    \n    # or, explicitly as keyword arguments\n    @app.route('/tag/<tag>')\n    async def tag_handler(request, *, tag):\n        return text(\"Tag - {}\".format(tag))\n    ```\n\n\n## Arguments\n\nThere are two attributes on the `request` instance to get query parameters:\n\n- `request.args`\n- `request.query_args`\n\nThese allow you to access the query parameters from the request path (the part after the `?` in the URL).\n\n### Typical use case\n\nIn most use cases, you will want to use the `request.args` object to access the query parameters. This will be the parsed query string as a dictionary.\n\nThis is by far the most common pattern.\n\n.. column::\n\n    Consider the example where we have a `/search` endpoint with a `q` parameter that we want to use to search for something.\n\n.. column::\n\n    ```python\n    @app.get(\"/search\")\n    async def search(request):\n       query = request.args.get(\"q\")\n        if not query:\n            return text(\"No query string provided\")\n        return text(f\"Searching for: {query}\")\n    ```\n\n### Parsing the query string\n\nSometimes, however, you may want to access the query string as a raw string or as a list of tuples. For this, you can use the `request.query_string` and `request.query_args` attributes. \n\nIt also should be noted that HTTP allows multiple values for a single key. Although `request.args` may seem like a regular dictionary, it is actually a special type that allows for multiple values for a single key. You can access this by using the `request.args.getlist()` method.\n\n- `request.query_string` - The raw query string\n- `request.query_args` - The parsed query string as a list of tuples\n- `request.args` - The parsed query string as a *special* dictionary\n  - `request.args.get()` - Get the first value for a key (behaves like a regular dictionary)\n  - `request.args.getlist()` - Get all values for a key\n\n```sh\ncurl \"http://localhost:8000?key1=val1&key2=val2&key1=val3\"\n```\n\n```python\n>>> print(request.args)\n{'key1': ['val1', 'val3'], 'key2': ['val2']}\n\n>>> print(request.args.get(\"key1\"))\nval1\n\n>>> print(request.args.getlist(\"key1\"))\n['val1', 'val3']\n\n>>> print(request.query_args)\n[('key1', 'val1'), ('key2', 'val2'), ('key1', 'val3')]\n\n>>> print(request.query_string)\nkey1=val1&key2=val2&key1=val3\n\n```\n\n\n.. tip:: FYI\n\n    The `request.args` object is one of a few types that is a dictionary with each value being a list. This is because HTTP allows a single key to be reused to send multiple values.  \n\n    Most of the time you will want to use the `.get()` method to access the first element and not a list. If you do want a list of all items, you can use `.getlist()`.\n\n## Current request getter\n\nSometimes you may find that you need access to the current request in your application in a location where it is not accessible. A typical example might be in a `logging` format. You can use `Request.get_current()` to fetch the current request (if any).\n\nRemember, the request object is confined to the single [`asyncio.Task`](https://docs.python.org/3/library/asyncio-task.html#asyncio.Task) that is running the handler. If you are not in that task, there is no request object.\n\n```python\nimport logging\n\nfrom sanic import Request, Sanic, json\nfrom sanic.exceptions import SanicException\nfrom sanic.log import LOGGING_CONFIG_DEFAULTS\n\nLOGGING_FORMAT = (\n    \"%(asctime)s - (%(name)s)[%(levelname)s][%(host)s]: \"\n    \"%(request_id)s %(request)s %(message)s %(status)d %(byte)d\"\n)\n\nold_factory = logging.getLogRecordFactory()\n\ndef record_factory(*args, **kwargs):\n    record = old_factory(*args, **kwargs)\n    record.request_id = \"\"\n\n    try:\n        request = Request.get_current()\n    except SanicException:\n        ...\n    else:\n        record.request_id = str(request.id)\n\n    return record\n\nlogging.setLogRecordFactory(record_factory)\n\n\nLOGGING_CONFIG_DEFAULTS[\"formatters\"][\"access\"][\"format\"] = LOGGING_FORMAT\napp = Sanic(\"Example\", log_config=LOGGING_CONFIG_DEFAULTS)\n```\n\nIn this example, we are adding the `request.id` to every access log message.\n\n*Added in v22.6*\n"
  },
  {
    "path": "guide/content/en/guide/basics/response.md",
    "content": "# Response\n\nAll [handlers](./handlers.md) *usually* return a response object, and [middleware](./middleware.md) may optionally return a response object.\n\nTo clarify that statement:\n- unless the handler is a streaming endpoint handling its own pattern for sending bytes to the client, the return value must be an instance of :class:`sanic.response.HTTPResponse` (to learn more about this exception see [streaming responses](../advanced/streaming.md#response-streaming)). In **most** use cases, you will need to return a response.\n- if a middleware does return a response object, that will be used instead of whatever the handler would do (see [middleware](./middleware.md) to learn more).\n\nA most basic handler would look like the following. The :class:`sanic.response.HTTPResponse` object will allow you to set the status, body, and headers to be returned to the client.\n\n```python\nfrom sanic import HTTPResponse, Sanic\n\napp = Sanic(\"TestApp\")\n\n@app.route(\"\")\ndef handler(_):\n    return HTTPResponse()\n```\n\nHowever, usually it is easier to use one of the convenience methods discussed below.\n\n## Methods\n\nThe easiest way to generate a response object is to use one of the convenience functions.\n\n### Text\n\n.. column::\n\n    **Default Content-Type**: `text/plain; charset=utf-8`  \n    **Description**: Returns plain text\n\n.. column::\n\n    ```python\n    from sanic import text\n\n    @app.route(\"/\")\n    async def handler(request):\n        return text(\"Hi 😎\")\n    ```\n\n### HTML\n\n.. column::\n\n    **Default Content-Type**: `text/html; charset=utf-8`  \n    **Description**: Returns an HTML document\n\n.. column::\n\n    ```python\n    from sanic import html\n\n    @app.route(\"/\")\n    async def handler(request):\n        return html('<!DOCTYPE html><html lang=\"en\"><meta charset=\"UTF-8\"><div>Hi 😎</div>')\n    ```\n\n### JSON\n\n.. column::\n\n    **Default Content-Type**: `application/json`  \n    **Description**: Returns a JSON document\n\n.. column::\n\n    ```python\n    from sanic import json\n\n    @app.route(\"/\")\n    async def handler(request):\n        return json({\"foo\": \"bar\"})\n    ```\n\nBy default, Sanic ships with [`ujson`](https://github.com/ultrajson/ultrajson) as its JSON encoder of choice. If `ujson` is not installed, it will fall back to the standard library `json` module.\n\nIt is super simple to change this if you want.\n\n```python\nfrom sanic import json\nfrom orjson import dumps\n\njson({\"foo\": \"bar\"}, dumps=dumps)\n```\n\nYou may additionally declare which implementation to use globally across your application at initialization:\n\n```python\nfrom orjson import dumps\n\napp = Sanic(..., dumps=dumps)\n```\n\n### File\n\n.. column::\n\n    **Default Content-Type**: N/A  \n    **Description**: Returns a file\n\n.. column::\n\n    ```python\n    from sanic import file\n\n    @app.route(\"/\")\n    async def handler(request):\n        return await file(\"/path/to/whatever.png\")\n    ```\n\nSanic will examine the file, and try and guess its mime type and use an appropriate value for the content type. You could be explicit, if you would like:\n\n```python\nfile(\"/path/to/whatever.png\", mime_type=\"image/png\")\n```\n\nYou can also choose to override the file name:\n\n```python\nfile(\"/path/to/whatever.png\", filename=\"super-awesome-incredible.png\")\n```\n\n### File Streaming\n\n.. column::\n\n    **Default Content-Type**: N/A  \n    **Description**: Streams a file to a client, useful when streaming large files, like a video\n\n.. column::\n\n    ```python\n    from sanic.response import file_stream\n\n    @app.route(\"/\")\n    async def handler(request):\n        return await file_stream(\"/path/to/whatever.mp4\")\n    ```\n\nLike the `file()` method, `file_stream()` will attempt to determine the mime type of the file.\n\n\n\n### Raw\n\n.. column::\n\n    **Default Content-Type**: `application/octet-stream`  \n    **Description**: Send raw bytes without encoding the body\n\n.. column::\n\n    ```python\n    from sanic import raw\n\n    @app.route(\"/\")\n    async def handler(request):\n        return raw(b\"raw bytes\")\n    ```\n\n### Redirect\n\n.. column::\n\n    **Default Content-Type**: `text/html; charset=utf-8`  \n    **Description**: Send a `302` response to redirect the client to a different path\n\n.. column::\n\n    ```python\n    from sanic import redirect\n\n    @app.route(\"/\")\n    async def handler(request):\n        return redirect(\"/login\")\n    ```\n\n### Empty\n\n.. column::\n\n    **Default Content-Type**: N/A  \n    **Description**: For responding with an empty message as defined by [RFC 2616](https://tools.ietf.org/search/rfc2616#section-7.2.1)\n\n.. column::\n\n    ```python\n    from sanic import empty\n\n    @app.route(\"/\")\n    async def handler(request):\n        return empty()\n    ```\n\n    Defaults to a `204` status.\n\n## Default status\n\nThe default HTTP status code for the response is `200`. If you need to change it, it can be done by the response method.\n\n```python\n@app.post(\"/\")\nasync def create_new(request):\n    new_thing = await do_create(request)\n    return json({\"created\": True, \"id\": new_thing.thing_id}, status=201)\n```\n\n## Returning JSON data\n\nStarting in v22.12, When you use the `sanic.json` convenience method, it will return a subclass of `HTTPResponse` called :class:`sanic.response.types.JSONResponse`. This object will \nhave several convenient methods available to modify common JSON body.\n\n```python\nfrom sanic import json\n\nresp = json(...)\n```\n\n- `resp.set_body(<raw_body>)` - Set the body of the JSON object to the value passed\n- `resp.append(<value>)` - Append a value to the body like `list.append` (only works if the root JSON is an array)\n- `resp.extend(<value>)` - Extend a value to the body like `list.extend` (only works if the root JSON is an array)\n- `resp.update(<value>)` - Update the body with a value like `dict.update` (only works if the root JSON is an object)\n- `resp.pop()` - Pop a value like `list.pop` or `dict.pop` (only works if the root JSON is an array or an object)\n\n.. warning::\n\n    The raw Python object is stored on the `JSONResponse` object as `raw_body`. While it is safe to overwrite this value with a new one, you should **not** attempt to mutate it. You should instead use the methods listed above.\n\n```python\nresp = json({\"foo\": \"bar\"})\n\n# This is OKAY\nresp.raw_body = {\"foo\": \"bar\", \"something\": \"else\"}\n\n# This is better\nresp.set_body({\"foo\": \"bar\", \"something\": \"else\"})\n\n# This is also works well\nresp.update({\"something\": \"else\"})\n\n# This is NOT OKAY\nresp.raw_body.update({\"something\": \"else\"})\n```\n\n```python\n# Or, even treat it like a list\nresp = json([\"foo\", \"bar\"])\n\n# This is OKAY\nresp.raw_body = [\"foo\", \"bar\", \"something\", \"else\"]\n\n# This is better\nresp.extend([\"something\", \"else\"])\n\n# This is also works well\nresp.append(\"something\")\nresp.append(\"else\")\n\n# This is NOT OKAY\nresp.raw_body.append(\"something\")\n```\n\n*Added in v22.9*\n"
  },
  {
    "path": "guide/content/en/guide/basics/routing.md",
    "content": "# Routing\n\n.. column::\n\n    So far we have seen a lot of this decorator in different forms.\n\n    But what is it? And how do we use it?\n\n.. column::\n\n    ```python\n    @app.route(\"/stairway\")\n        ...\n\n    @app.get(\"/to\")\n        ...\n\n    @app.post(\"/heaven\")\n        ...\n    ```\n\n## Adding a route\n\n.. column::\n\n    The most basic way to wire up a handler to an endpoint is with `app.add_route()`.\n\n    See [API docs](https://sanic.readthedocs.io/en/stable/sanic/api_reference.html#sanic.app.Sanic.url_for) for more details.\n\n.. column::\n\n    ```python\n    async def handler(request):\n        return text(\"OK\")\n\n    app.add_route(handler, \"/test\")\n    ```\n\n\n.. column::\n\n    By default, routes are available as an HTTP `GET` call. You can change a handler to respond to one or more HTTP methods.\n\n.. column::\n\n    ```python\n    app.add_route(\n        handler,\n        '/test',\n        methods=[\"POST\", \"PUT\"],\n    )\n    ```\n\n\n.. column::\n\n    Using the decorator syntax, the previous example is identical to this.\n\n.. column::\n\n    ```python\n    @app.route('/test', methods=[\"POST\", \"PUT\"])\n    async def handler(request):\n        return text('OK')\n    ```\n\n## HTTP methods\n\nEach of the standard HTTP methods has a convenience decorator.\n\n### GET\n\n```python\n@app.get('/test')\nasync def handler(request):\n    return text('OK')\n```\n\n[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET)\n\n### POST\n\n```python\n@app.post('/test')\nasync def handler(request):\n    return text('OK')\n```\n\n[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST)\n\n### PUT\n\n```python\n@app.put('/test')\nasync def handler(request):\n    return text('OK')\n```\n\n[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT)\n\n### PATCH\n\n```python\n@app.patch('/test')\nasync def handler(request):\n    return text('OK')\n```\n\n[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH)\n\n### DELETE\n\n```python\n@app.delete('/test')\nasync def handler(request):\n    return text('OK')\n```\n\n[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/DELETE)\n\n### HEAD\n\n```python\n@app.head('/test')\nasync def handler(request):\n    return empty()\n```\n\n[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD)\n\n### OPTIONS\n\n```python\n@app.options('/test')\nasync def handler(request):\n    return empty()\n```\n\n[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS)\n\n\n.. warning:: \n\n    By default, Sanic will **only** consume the incoming request body on non-safe HTTP methods: `POST`, `PUT`, `PATCH`, `DELETE`. If you want to receive data in the HTTP request on any other method, you will need to do one of the following two options:\n\n    **Option #1 - Tell Sanic to consume the body using `ignore_body`**\n    ```python\n    @app.request(\"/path\", ignore_body=False)\n    async def handler(_):\n        ...\n    ```\n\n    **Option #2 - Manually consume the body in the handler using `receive_body`**\n    ```python\n    @app.get(\"/path\")\n    async def handler(request: Request):\n        await request.receive_body()\n    ```\n\n\n## Path parameters\n\n.. column::\n\n    Sanic allows for pattern matching, and for extracting values from URL paths. These parameters are then injected as keyword arguments in the route handler.\n\n.. column::\n\n    ```python\n    @app.get(\"/tag/<tag>\")\n    async def tag_handler(request, tag):\n        return text(\"Tag - {}\".format(tag))\n    ```\n\n.. column::\n\n    You can declare a type for the parameter. This will be enforced when matching, and also will type cast the variable.\n\n.. column::\n\n    ```python\n    @app.get(\"/foo/<foo_id:uuid>\")\n    async def uuid_handler(request, foo_id: UUID):\n        return text(\"UUID - {}\".format(foo_id))\n    ```\n\n.. column::\n\n    For some standard types like `str`, `int`, and `UUID`, Sanic can infer the path parameter type from the function signature. This means that it may not always be necessary to include the type in the path parameter definition.\n\n.. column::\n\n    ```python\n    @app.get(\"/foo/<foo_id>\")  # Notice there is no :uuid in the path parameter\n    async def uuid_handler(request, foo_id: UUID):\n        return text(\"UUID - {}\".format(foo_id))\n    ```\n\n### Supported types\n\n### `str`\n\n.. column::\n\n    **Regular expression applied**: `r\"[^/]+\"`  \n    **Cast type**: `str`  \n    **Example matches**:  \n\n    - `/path/to/Bob`\n    - `/path/to/Python%203`\n\n    Beginning in v22.3 `str` will *not* match on empty strings. See `strorempty` for this behavior.\n\n.. column::\n\n    ```python\n    @app.route(\"/path/to/<foo:str>\")\n    async def handler(request, foo: str):\n        ...\n    ```\n\n### `strorempty`\n\n.. column::\n\n    **Regular expression applied**: `r\"[^/]*\"`  \n    **Cast type**: `str`  \n    **Example matches**:\n\n    - `/path/to/Bob`\n    - `/path/to/Python%203`\n    - `/path/to/`\n\n    Unlike the `str` path parameter type, `strorempty` can also match on an empty string path segment.\n\n    *Added in v22.3*\n\n.. column::\n\n    ```python\n    @app.route(\"/path/to/<foo:strorempty>\")\n    async def handler(request, foo: str):\n        ...\n    ```\n\n### `int`\n\n.. column::\n\n    **Regular expression applied**: `r\"-?\\d+\"`  \n    **Cast type**: `int`  \n    **Example matches**:  \n\n    - `/path/to/10`\n    - `/path/to/-10`\n\n    _Does not match float, hex, octal, etc_\n\n.. column::\n\n    ```python\n    @app.route(\"/path/to/<foo:int>\")\n    async def handler(request, foo: int):\n        ...\n    ```\n\n### `float`\n\n.. column::\n\n    **Regular expression applied**: `r\"-?(?:\\d+(?:\\.\\d*)?|\\.\\d+)\"`  \n    **Cast type**: `float`  \n    **Example matches**:  \n\n    - `/path/to/10`\n    - `/path/to/-10`\n    - `/path/to/1.5`\n\n.. column::\n\n    ```python\n    @app.route(\"/path/to/<foo:float>\")\n    async def handler(request, foo: float):\n        ...\n    ```\n\n### `alpha`\n\n.. column::\n\n    **Regular expression applied**: `r\"[A-Za-z]+\"`  \n    **Cast type**: `str`  \n    **Example matches**:  \n\n    - `/path/to/Bob`\n    - `/path/to/Python`\n\n    _Does not match a digit, or a space or other special character_\n\n.. column::\n\n    ```python\n    @app.route(\"/path/to/<foo:alpha>\")\n    async def handler(request, foo: str):\n        ...\n    ```\n\n### `slug`\n\n.. column::\n\n    **Regular expression applied**: `r\"[a-z0-9]+(?:-[a-z0-9]+)*\"`  \n    **Cast type**: `str`  \n    **Example matches**:  \n\n    - `/path/to/some-news-story`\n    - `/path/to/or-has-digits-123`\n\n    *Added in v21.6*\n\n.. column::\n\n\n    ```python\n    @app.route(\"/path/to/<article:slug>\")\n    async def handler(request, article: str):\n        ...\n    ```\n\n### `path`\n\n.. column::\n\n    **Regular expression applied**: `r\"[^/].*?\"`  \n    **Cast type**: `str`  \n    **Example matches**:\n    - `/path/to/hello`\n    - `/path/to/hello.txt`\n    - `/path/to/hello/world.txt`\n\n\n.. column::\n\n    ```python\n    @app.route(\"/path/to/<foo:path>\")\n    async def handler(request, foo: str):\n        ...\n    ```\n.. warning::\n\n    Because this will match on `/`, you should be careful and thoroughly test your patterns that use `path` so they do not capture traffic intended for another endpoint. Additionally, depending on how you use this type, you may be creating a path traversal vulnerability in your application. It is your job to protect your endpoint against this, but feel free to ask in our community channels for help if you need it :)\n\n### `ymd`\n\n.. column::\n\n    **Regular expression applied**: `r\"^([12]\\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01]))\"`  \n    **Cast type**: `datetime.date`  \n    **Example matches**:  \n\n    - `/path/to/2021-03-28`\n\n.. column::\n\n    ```python\n    @app.route(\"/path/to/<foo:ymd>\")\n    async def handler(request, foo: datetime.date):\n        ...\n    ```\n\n### `uuid`\n\n.. column::\n\n    **Regular expression applied**: `r\"[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}\"`  \n    **Cast type**: `UUID`  \n    **Example matches**:  \n\n    - `/path/to/123a123a-a12a-1a1a-a1a1-1a12a1a12345`\n\n.. column::\n\n    ```python\n    @app.route(\"/path/to/<foo:uuid>\")\n    async def handler(request, foo: UUID):\n        ...\n    ```\n\n### ext\n\n.. column::\n\n    **Regular expression applied**: n/a\n    **Cast type**: *varies*\n    **Example matches**:\n\n.. column::\n\n    ```python\n    @app.route(\"/path/to/<foo:ext>\")\n    async def handler(request, foo: str, ext: str):\n        ...\n    ```\n\n| definition                        | example     | filename    | extension  |\n| --------------------------------- | ----------- | ----------- | ---------- |\n| \\<file:ext>                       | page.txt    | `\"page\"`    | `\"txt\"`    |\n| \\<file:ext=jpg>                   | cat.jpg     | `\"cat\"`     | `\"jpg\"`    |\n| \\<file:ext=jpg\\|png\\|gif\\|svg>    | cat.jpg     | `\"cat\"`     | `\"jpg\"`    |\n| <file=int:ext>                    | 123.txt     | `123`       | `\"txt\"`    |\n| <file=int:ext=jpg\\|png\\|gif\\|svg> | 123.svg     | `123`       | `\"svg\"`    |\n| <file=float:ext=tar.gz>           | 3.14.tar.gz | `3.14`      | `\"tar.gz\"` |\n\nFile extensions can be matched using the special `ext` parameter type. It uses a special format that allows you to specify other types of parameter types as the file name, and one or more specific extensions as shown in the example table above.\n\nIt does *not* support the `path` parameter type.\n\n*Added in v22.3*\n\n### regex\n\n.. column::\n\n    **Regular expression applied**: _whatever you insert_  \n    **Cast type**: `str`  \n    **Example matches**:  \n\n    - `/path/to/2021-01-01`\n\n    This gives you the freedom to define specific matching patterns for your use case.\n\n    In the example shown, we are looking for a date that is in `YYYY-MM-DD` format.\n\n.. column::\n\n    ```python\n    @app.route(r\"/path/to/<foo:([12]\\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01]))>\")\n    async def handler(request, foo: str):\n        ...\n    ```\n\n### Regex Matching\n\n\nMore often than not, compared with complex routing, the above example is too simple, and we use a completely different routing matching pattern, so here we will explain the advanced usage of regex matching in detail.\n\nSometimes, you want to match a part of a route:\n\n```text\n/image/123456789.jpg\n```\n\nIf you wanted to match the file pattern, but only capture the numeric portion, you need to do some regex fun 😄:\n\n```python\napp.route(r\"/image/<img_id:(?P<img_id>\\d+)\\.jpg>\")\n```\n\nFurther, these should all be acceptable:\n\n```python\n@app.get(r\"/<foo:[a-z]{3}.txt>\")                # matching on the full pattern\n@app.get(r\"/<foo:([a-z]{3}).txt>\")              # defining a single matching group\n@app.get(r\"/<foo:(?P<foo>[a-z]{3}).txt>\")       # defining a single named matching group\n@app.get(r\"/<foo:(?P<foo>[a-z]{3}).(?:txt)>\")   # defining a single named matching group, with one or more non-matching groups\n```\n\nAlso, if using a named matching group, it must be the same as the segment label.\n\n```python\n@app.get(r\"/<foo:(?P<foo>\\d+).jpg>\")  # OK\n@app.get(r\"/<foo:(?P<bar>\\d+).jpg>\")  # NOT OK\n```\n\nFor more regular usage methods, please refer to [Regular expression operations](https://docs.python.org/3/library/re.html)\n\n## Generating a URL\n\n.. column::\n\n    Sanic provides a method to generate URLs based on the handler method name: `app.url_for()`. This is useful if you want to avoid hardcoding url paths into your app; instead, you can just reference the handler name.\n\n.. column::\n\n    ```python\n    @app.route('/')\n    async def index(request):\n        # generate a URL for the endpoint `post_handler`\n        url = app.url_for('post_handler', post_id=5)\n\n        # Redirect to `/posts/5`\n        return redirect(url)\n\n    @app.route('/posts/<post_id>')\n    async def post_handler(request, post_id):\n        ...\n    ```\n\n\n.. column::\n\n    You can pass any arbitrary number of keyword arguments. Anything that is _not_ a request parameter will be implemented as a part of the query string.\n\n.. column::\n\n    ```python\n    assert app.url_for(\n        \"post_handler\",\n        post_id=5,\n        arg_one=\"one\",\n        arg_two=\"two\",\n    ) == \"/posts/5?arg_one=one&arg_two=two\"\n    ```\n\n\n.. column::\n\n    Also supported is passing multiple values for a single query key.\n\n.. column::\n\n    ```python\n    assert app.url_for(\n        \"post_handler\",\n        post_id=5,\n        arg_one=[\"one\", \"two\"],\n    ) == \"/posts/5?arg_one=one&arg_one=two\"\n    ```\n\n### Special keyword arguments\n\nSee [API Docs]() for more details.\n\n```python\napp.url_for(\"post_handler\", post_id=5, arg_one=\"one\", _anchor=\"anchor\")\n# '/posts/5?arg_one=one#anchor'\n\n# _external requires you to pass an argument _server or set SERVER_NAME in app.config if not url will be same as no _external\napp.url_for(\"post_handler\", post_id=5, arg_one=\"one\", _external=True)\n# '//server/posts/5?arg_one=one'\n\n# when specifying _scheme, _external must be True\napp.url_for(\"post_handler\", post_id=5, arg_one=\"one\", _scheme=\"http\", _external=True)\n# 'http://server/posts/5?arg_one=one'\n\n# you can pass all special arguments at once\napp.url_for(\"post_handler\", post_id=5, arg_one=[\"one\", \"two\"], arg_two=2, _anchor=\"anchor\", _scheme=\"http\", _external=True, _server=\"another_server:8888\")\n# 'http://another_server:8888/posts/5?arg_one=one&arg_one=two&arg_two=2#anchor'\n```\n\n### Customizing a route name\n\n.. column::\n\n    A custom route name can be used by passing a `name` argument while registering the route.\n\n.. column::\n\n    ```python\n    @app.get(\"/get\", name=\"get_handler\")\n    def handler(request):\n        return text(\"OK\")\n    ```\n\n\n.. column::\n\n    Now, use this custom name to retrieve the URL\n\n.. column::\n\n    ```python\n    assert app.url_for(\"get_handler\", foo=\"bar\") == \"/get?foo=bar\"\n    ```\n\n## Websockets routes\n\n.. column::\n\n    Websocket routing works similar to HTTP methods.\n\n.. column::\n\n    ```python\n    async def handler(request, ws):\n        message = \"Start\"\n        while True:\n            await ws.send(message)\n            message = await ws.recv()\n\n    app.add_websocket_route(handler, \"/test\")\n    ```\n\n\n.. column::\n\n    It also has a convenience decorator.\n\n.. column::\n\n    ```python\n    @app.websocket(\"/test\")\n    async def handler(request, ws):\n        message = \"Start\"\n        while True:\n            await ws.send(message)\n            message = await ws.recv()\n    ```\n\nRead the [websockets section](../advanced/websockets.md) to learn more about how they work.\n\n## Strict slashes\n\n\n.. column::\n\n    Sanic routes can be configured to strictly match on whether or not there is a trailing slash: `/`. This can be configured at a few levels and follows this order of precedence:\n\n    1. Route\n    2. Blueprint\n    3. BlueprintGroup\n    4. Application\n\n.. column::\n\n    ```python\n    # provide default strict_slashes value for all routes\n    app = Sanic(__file__, strict_slashes=True)\n    ```\n\n    ```python\n    # overwrite strict_slashes value for specific route\n    @app.get(\"/get\", strict_slashes=False)\n    def handler(request):\n        return text(\"OK\")\n    ```\n\n    ```python\n    # it also works for blueprints\n    bp = Blueprint(__file__, strict_slashes=True)\n\n    @bp.get(\"/bp/get\", strict_slashes=False)\n    def handler(request):\n        return text(\"OK\")\n    ```\n\n    ```python\n    bp1 = Blueprint(name=\"bp1\", url_prefix=\"/bp1\")\n    bp2 = Blueprint(\n        name=\"bp2\",\n        url_prefix=\"/bp2\",\n        strict_slashes=False,\n    )\n\n    # This will enforce strict slashes check on the routes\n    # under bp1 but ignore bp2 as that has an explicitly\n    # set the strict slashes check to false\n    group = Blueprint.group([bp1, bp2], strict_slashes=True)\n    ```\n\n## Static files\n\n.. column::\n\n    In order to serve static files from Sanic, use `app.static()`.\n\n    The order of arguments is important:\n\n    1. Route the files will be served from\n    2. Path to the files on the server\n\n    See [API docs](https://sanic.readthedocs.io/en/stable/sanic/api/app.html#sanic.app.Sanic.static) for more details.\n\n.. column::\n\n    ```python\n    app.static(\"/static/\", \"/path/to/directory/\")\n    ```\n\n\n\n.. tip:: \n\n    It is generally best practice to end your directory paths with a trailing slash (`/this/is/a/directory/`). This removes ambiguity by being more explicit.\n\n\n\n\n.. column::\n\n    You can also serve individual files.\n\n.. column::\n\n    ```python\n    app.static(\"/\", \"/path/to/index.html\")\n    ```\n\n\n.. column::\n\n    It is also sometimes helpful to name your endpoint\n\n.. column::\n\n    ```python\n    app.static(\n        \"/user/uploads/\",\n        \"/path/to/uploads/\",\n        name=\"uploads\",\n    )\n    ```\n\n\n.. column::\n\n    Retrieving the URLs works similar to handlers. But, we can also add the `filename` argument when we need a specific file inside a directory.\n\n.. column::\n\n    ```python\n    assert app.url_for(\n        \"static\",\n        name=\"static\",\n        filename=\"file.txt\",\n    ) == \"/static/file.txt\"\n    ```\n    ```python\n    assert app.url_for(\n        \"static\",\n        name=\"uploads\",\n        filename=\"image.png\",\n    ) == \"/user/uploads/image.png\"\n\n    ```\n\n\n\n.. tip:: \n\n    If you are going to have multiple `static()` routes, then it is *highly* suggested that you manually name them. This will almost certainly alleviate potential hard to discover bugs.\n\n    ```python\n    app.static(\"/user/uploads/\", \"/path/to/uploads/\", name=\"uploads\")\n    app.static(\"/user/profile/\", \"/path/to/profile/\", name=\"profile_pics\")\n    ```\n\n\n#### Auto index serving\n\n.. column::\n\n    If you have a directory of static files that should be served by an index page, you can provide the filename of the index. Now, when reaching that directory URL, the index page will be served.\n\n.. column::\n\n    ```python\n    app.static(\"/foo/\", \"/path/to/foo/\", index=\"index.html\")\n    ```\n\n*Added in v23.3*\n\n#### File browser\n\n.. column::\n\n    When serving a directory from a static handler, Sanic can be configured to show a basic file browser instead using `directory_view=True`.\n\n.. column::\n\n    ```python\n    app.static(\"/uploads/\", \"/path/to/dir\", directory_view=True)\n    ```\n\nYou now have a browsable directory in your web browser:\n\n![image](/assets/images/directory-view.png)\n\n*Added in v23.3*\n\n#### Symlink control\n\n.. new:: New in v25.12\n\n    This feature was added in version 25.12\n\n.. column::\n\n    By default, Sanic will not follow symlinks that point outside the static root directory for security reasons. You can enable external symlinks separately for files and directories using `follow_external_symlink_files` and `follow_external_symlink_dirs`.\n\n.. column::\n\n    ```python\n    # Allow file symlinks pointing outside static root\n    app.static(\n        \"/static\",\n        \"/var/www/static\",\n        follow_external_symlink_files=True,\n    )\n\n    # Allow directory symlinks pointing outside static root\n    app.static(\n        \"/static\",\n        \"/var/www/static\",\n        follow_external_symlink_dirs=True,\n    )\n    ```\n\nSymlinks within the static root always work regardless of these settings. Broken symlinks are always hidden from directory listings and return 404.\n\n*Added in v25.12*\n\n## Route context\n\n\n.. column::\n\n    When a route is defined, you can add any number of keyword arguments with a `ctx_` prefix. These values will be injected into the route `ctx` object.\n\n.. column::\n\n    ```python\n    @app.get(\"/1\", ctx_label=\"something\")\n    async def handler1(request):\n        ...\n\n    @app.get(\"/2\", ctx_label=\"something\")\n    async def handler2(request):\n        ...\n\n    @app.get(\"/99\")\n    async def handler99(request):\n        ...\n\n    @app.on_request\n    async def do_something(request):\n        if request.route.ctx.label == \"something\":\n            ...\n    ```\n\n*Added in v21.12*\n"
  },
  {
    "path": "guide/content/en/guide/basics/tasks.md",
    "content": "---\ntitle: Background tasks\n---\n\n# Background tasks\n\n## Creating Tasks\nIt is often desirable and very convenient to make usage of [tasks](https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task) in async Python. Sanic provides a convenient method to add tasks to the currently **running** loop. It is somewhat similar to `asyncio.create_task`. For adding tasks before the 'App' loop is running, see next section.\n\n```python\nasync def notify_server_started_after_five_seconds():\n    await asyncio.sleep(5)\n    print('Server successfully started!')\n\napp.add_task(notify_server_started_after_five_seconds())\n```\n\n.. column::\n\n    Sanic will attempt to automatically inject the app, passing it as an argument to the task.\n\n.. column::\n\n    ```python\n    async def auto_inject(app):\n        await asyncio.sleep(5)\n        print(app.name)\n\n    app.add_task(auto_inject)\n    ```\n\n\n.. column::\n\n    Or you can pass the `app` argument explicitly.\n\n.. column::\n\n    ```python\n    async def explicit_inject(app):\n        await asyncio.sleep(5)\n        print(app.name)\n\n    app.add_task(explicit_inject(app))\n    ```\n\n.. column::\n\n    The `add_task` method returns the created `asyncio.Task` object, allowing you to await or check its result later.\n\n    *Added in v25.12*\n\n.. column::\n\n    ```python\n    task = app.add_task(some_coroutine())\n    # Later...\n    result = await task\n    ```\n\n## Adding tasks before `app.run`\n\nIt is possible to add background tasks before the App is run ie. before `app.run`. To add a task before the App is run, it is recommended to not pass the coroutine object (ie. one created by calling the `async` callable), but instead just pass the callable and Sanic will create the coroutine object on **each worker**. Note: the tasks that are added such are run as `before_server_start` jobs and thus run on every worker (and not in the main process). This has certain consequences, please read [this comment](https://github.com/sanic-org/sanic/issues/2139#issuecomment-868993668) on [this issue](https://github.com/sanic-org/sanic/issues/2139) for further details.\n\nTo add work on the main process, consider adding work to [`@app.main_process_start`](./listeners.md). Note: the workers won't start until this work is completed.\n\n.. column::\n\n    Example to add a task before `app.run`\n\n.. column::\n\n    ```python\n    async def slow_work():\n       ...\n   \n    async def even_slower(num):\n       ...\n\n    app = Sanic(...)\n    app.add_task(slow_work) # Note: we are passing the callable and not coroutine object ...\n    app.add_task(even_slower(10)) # ... or we can call the function and pass the coroutine.\n    app.run(...)\n    ```\n\n## Named tasks\n\n.. column::\n\n    When creating a task, you can ask Sanic to keep track of it for you by providing a `name`.\n\n.. column::\n\n    ```python\n    app.add_task(slow_work, name=\"slow_task\")\n    ```\n\n\n.. column::\n\n    You can now retrieve that task instance from anywhere in your application using `get_task`.\n\n.. column::\n\n    ```python\n    task = app.get_task(\"slow_task\")\n    ```\n\n.. column::\n\n    If that task needs to be cancelled, you can do that with `cancel_task`. Make sure that you `await` it.\n\n.. column::\n\n    ```python\n    await app.cancel_task(\"slow_task\")\n    ```\n\n.. column::\n\n    All registered tasks can be found in the `app.tasks` property. To prevent cancelled tasks from filling up, you may want to run `app.purge_tasks` that will clear out any completed or cancelled tasks.\n\n.. column::\n\n    ```python\n    app.purge_tasks()\n    ```\n\nThis pattern can be particularly useful with `websockets`:\n\n```python\nasync def receiver(ws):\n    while True:\n        message = await ws.recv()\n        if not message:\n            break\n        print(f\"Received: {message}\")\n\n@app.websocket(\"/feed\")\nasync def feed(request, ws):\n    task_name = f\"receiver:{request.id}\"\n    request.app.add_task(receiver(ws), name=task_name)\n    try:\n        while True:\n            await request.app.event(\"my.custom.event\")\n            await ws.send(\"A message\")\n    finally:\n        # When the websocket closes, let's cleanup the task\n        await request.app.cancel_task(task_name)\n        request.app.purge_tasks()\n```\n\n*Added in v21.12*\n"
  },
  {
    "path": "guide/content/en/guide/best-practices/blueprints.md",
    "content": "# Blueprints\n\n## Overview\n\nBlueprints are objects that can be used for sub-routing within an application. Instead of adding routes to the application instance, blueprints define similar methods for adding routes, which are then registered with the application in a flexible and pluggable manner.\n\nBlueprints are especially useful for larger applications, where your application logic can be broken down into several groups or areas of responsibility.\n\n## Creating and registering\n\n.. column::\n\n    First, you must create a blueprint. It has a very similar API as the `Sanic()` app instance with many of the same decorators.\n\n.. column::\n\n    ```python\n    # ./my_blueprint.py\n    from sanic.response import json\n    from sanic import Blueprint\n\n    bp = Blueprint(\"my_blueprint\")\n\n    @bp.route(\"/\")\n    async def bp_root(request):\n        return json({\"my\": \"blueprint\"})\n    ```\n\n\n\n.. column::\n\n    Next, you register it with the app instance.\n\n.. column::\n\n    ```python\n    from sanic import Sanic\n    from my_blueprint import bp\n\n    app = Sanic(__name__)\n    app.blueprint(bp)\n    ```\n\nBlueprints also have the same `websocket()` decorator and `add_websocket_route` method for implementing websockets.\n\n.. column::\n\n    Beginning in v21.12, a Blueprint may be registered before or after adding objects to it. Previously, only objects attached to the Blueprint at the time of registration would be loaded into application instance.\n\n.. column::\n\n    ```python\n    app.blueprint(bp)\n\n    @bp.route(\"/\")\n    async def bp_root(request):\n        ...\n    ```\n\n## Copying\n\n.. column::\n\n    Blueprints along with everything that is attached to them can be copied to new instances using the `copy()` method. The only required argument is to pass it a new `name`. However, you could also use this to override any of the values from the old blueprint.\n\n.. column::\n\n    ```python\n    v1 = Blueprint(\"Version1\", version=1)\n\n    @v1.route(\"/something\")\n    def something(request):\n        pass\n\n    v2 = v1.copy(\"Version2\", version=2)\n\n    app.blueprint(v1)\n    app.blueprint(v2)\n    ```\n\n    ```\n    Available routes:\n    /v1/something\n    /v2/something\n\n    ```\n\n*Added in v21.9*\n\n## Blueprint groups\n\nBlueprints may also be registered as part of a list or tuple, where the registrar will recursively cycle through any sub-sequences of blueprints and register them accordingly. The Blueprint.group method is provided to simplify this process, allowing a ‘mock’ backend directory structure mimicking what’s seen from the front end. Consider this (quite contrived) example:\n\n```text\napi/\n├──content/\n│ ├──authors.py\n│ ├──static.py\n│ └──__init__.py\n├──info.py\n└──__init__.py\napp.py\n```\n\n.. column::\n\n    #### First blueprint\n\n.. column::\n\n    ```python\n    # api/content/authors.py\n    from sanic import Blueprint\n\n    authors = Blueprint(\"content_authors\", url_prefix=\"/authors\")\n    ```\n\n\n.. column::\n\n    #### Second blueprint\n\n.. column::\n\n    ```python\n    # api/content/static.py\n    from sanic import Blueprint\n\n    static = Blueprint(\"content_static\", url_prefix=\"/static\")\n    ```\n\n\n.. column::\n\n    #### Blueprint group\n\n.. column::\n\n    ```python\n    # api/content/__init__.py\n    from sanic import Blueprint\n    from .static import static\n    from .authors import authors\n\n    content = Blueprint.group(static, authors, url_prefix=\"/content\")\n    ```\n\n\n.. column::\n\n    #### Third blueprint\n\n.. column::\n\n    ```python\n    # api/info.py\n    from sanic import Blueprint\n\n    info = Blueprint(\"info\", url_prefix=\"/info\")\n    ```\n\n\n.. column::\n\n    #### Another blueprint group\n\n.. column::\n\n    ```python\n    # api/__init__.py\n    from sanic import Blueprint\n    from .content import content\n    from .info import info\n\n    api = Blueprint.group(content, info, url_prefix=\"/api\")\n    ```\n\n\n.. column::\n\n    #### Main server\n\n    All blueprints are now registered\n\n.. column::\n\n    ```python\n    # app.py\n    from sanic import Sanic\n    from .api import api\n\n    app = Sanic(__name__)\n    app.blueprint(api)\n    ```\n\n### Blueprint group prefixes and composability\n\nAs shown in the code above, when you create a group of blueprints you can extend the URL prefix of all the blueprints in the group by passing the `url_prefix` argument to the `Blueprint.group` method. This is useful for creating a mock directory structure for your API.\n\n\nIn addition, there is a `name_prefix` argument that can be used to make blueprints reusable and composable. The is specifically necessary when applying a single blueprint to multiple groups. By doing this, the blueprint will be registered with a unique name for each group, which allows the blueprint to be registered multiple times and have its routes each properly named with a unique identifier.\n\n.. column::\n\n    Consider this example. The routes built will be named as follows:\n    - `TestApp.group-a_bp1.route1`\n    - `TestApp.group-a_bp2.route2`\n    - `TestApp.group-b_bp1.route1`\n    - `TestApp.group-b_bp2.route2`\n\n.. column::\n\n    ```python\n    bp1 = Blueprint(\"bp1\", url_prefix=\"/bp1\")\n    bp2 = Blueprint(\"bp2\", url_prefix=\"/bp2\")\n\n    bp1.add_route(lambda _: ..., \"/\", name=\"route1\")\n    bp2.add_route(lambda _: ..., \"/\", name=\"route2\")\n\n    group_a = Blueprint.group(\n        bp1, bp2, url_prefix=\"/group-a\", name_prefix=\"group-a\"\n    )\n    group_b = Blueprint.group(\n        bp1, bp2, url_prefix=\"/group-b\", name_prefix=\"group-b\"\n    )\n\n    app = Sanic(\"TestApp\")\n    app.blueprint(group_a)\n    app.blueprint(group_b)\n    ```\n\n*Name prefixing added in v23.6*\n\n\n## Middleware\n\n.. column::\n\n    Blueprints can also have middleware that is specifically registered for its endpoints only.\n\n.. column::\n\n    ```python\n    @bp.middleware\n    async def print_on_request(request):\n        print(\"I am a spy\")\n\n    @bp.middleware(\"request\")\n    async def halt_request(request):\n        return text(\"I halted the request\")\n\n    @bp.middleware(\"response\")\n    async def halt_response(request, response):\n        return text(\"I halted the response\")\n    ```\n\n\n.. column::\n\n    Similarly, using blueprint groups, it is possible to apply middleware to an entire group of nested blueprints.\n\n.. column::\n\n    ```python\n    bp1 = Blueprint(\"bp1\", url_prefix=\"/bp1\")\n    bp2 = Blueprint(\"bp2\", url_prefix=\"/bp2\")\n\n    @bp1.middleware(\"request\")\n    async def bp1_only_middleware(request):\n        print(\"applied on Blueprint : bp1 Only\")\n\n    @bp1.route(\"/\")\n    async def bp1_route(request):\n        return text(\"bp1\")\n\n    @bp2.route(\"/<param>\")\n    async def bp2_route(request, param):\n        return text(param)\n\n    group = Blueprint.group(bp1, bp2)\n\n    @group.middleware(\"request\")\n    async def group_middleware(request):\n        print(\"common middleware applied for both bp1 and bp2\")\n\n    # Register Blueprint group under the app\n    app.blueprint(group)\n    ```\n\n## Exceptions\n\n.. column::\n\n    Just like other [exception handling](./exceptions.md), you can define blueprint specific handlers.\n\n.. column::\n\n    ```python\n    @bp.exception(NotFound)\n    def ignore_404s(request, exception):\n        return text(\"Yep, I totally found the page: {}\".format(request.url))\n    ```\n\n## Static files\n\n.. column::\n\n    Blueprints can also have their own static handlers\n\n.. column::\n\n    ```python\n    bp = Blueprint(\"bp\", url_prefix=\"/bp\")\n    bp.static(\"/web/path\", \"/folder/to/serve\")\n    bp.static(\"/web/path\", \"/folder/to/server\", name=\"uploads\")\n    ```\n\n\n.. column::\n\n    Which can then be retrieved using `url_for()`. See [routing](../basics/routing.md) for more information.\n\n.. column::\n\n    ```python\n    >>> print(app.url_for(\"static\", name=\"bp.uploads\", filename=\"file.txt\"))\n    '/bp/web/path/file.txt'\n    ```\n\n## Listeners\n\n.. column::\n\n    Blueprints can also implement [listeners](../basics/listeners.md).\n\n.. column::\n\n    ```python\n    @bp.listener(\"before_server_start\")\n    async def before_server_start(app, loop):\n        ...\n\n    @bp.listener(\"after_server_stop\")\n    async def after_server_stop(app, loop):\n        ...\n    ```\n\n## Versioning\n\nAs discussed in the [versioning section](../advanced/versioning.md), blueprints can be used to implement different versions of a web API.\n\n.. column::\n\n    The `version` will be prepended to the routes as `/v1` or `/v2`, etc.\n\n.. column::\n\n    ```python\n    auth1 = Blueprint(\"auth\", url_prefix=\"/auth\", version=1)\n    auth2 = Blueprint(\"auth\", url_prefix=\"/auth\", version=2)\n    ```\n\n\n.. column::\n\n    When we register our blueprints on the app, the routes `/v1/auth` and `/v2/auth` will now point to the individual blueprints, which allows the creation of sub-sites for each API version.\n\n.. column::\n\n    ```python\n    from auth_blueprints import auth1, auth2\n\n    app = Sanic(__name__)\n    app.blueprint(auth1)\n    app.blueprint(auth2)\n    ```\n\n\n.. column::\n\n    It is also possible to group the blueprints under a `BlueprintGroup` entity and version multiple of them together at the\n    same time.\n\n.. column::\n\n    ```python\n    auth = Blueprint(\"auth\", url_prefix=\"/auth\")\n    metrics = Blueprint(\"metrics\", url_prefix=\"/metrics\")\n\n    group = Blueprint.group(auth, metrics, version=\"v1\")\n\n    # This will provide APIs prefixed with the following URL path\n    # /v1/auth/ and /v1/metrics\n    ```\n\n## Composable\n\nA `Blueprint` may be registered to multiple groups, and each of `BlueprintGroup` itself could be registered and nested further. This creates a limitless possibility `Blueprint` composition.\n\n*Added in v21.6*\n\n.. column::\n\n    Take a look at this example and see how the two handlers are actually mounted as five (5) distinct routes.\n\n.. column::\n\n    ```python\n    app = Sanic(__name__)\n    blueprint_1 = Blueprint(\"blueprint_1\", url_prefix=\"/bp1\")\n    blueprint_2 = Blueprint(\"blueprint_2\", url_prefix=\"/bp2\")\n    group = Blueprint.group(\n        blueprint_1,\n        blueprint_2,\n        version=1,\n        version_prefix=\"/api/v\",\n        url_prefix=\"/grouped\",\n        strict_slashes=True,\n    )\n    primary = Blueprint.group(group, url_prefix=\"/primary\")\n\n    @blueprint_1.route(\"/\")\n    def blueprint_1_default_route(request):\n        return text(\"BP1_OK\")\n\n    @blueprint_2.route(\"/\")\n    def blueprint_2_default_route(request):\n        return text(\"BP2_OK\")\n\n    app.blueprint(group)\n    app.blueprint(primary)\n    app.blueprint(blueprint_1)\n\n    # The mounted paths:\n    # /api/v1/grouped/bp1/\n    # /api/v1/grouped/bp2/\n    # /api/v1/primary/grouped/bp1\n    # /api/v1/primary/grouped/bp2\n    # /bp1\n\n    ```\n\n\n## Generating a URL\n\nWhen generating a url with `url_for()`, the endpoint name will be in the form:\n\n```text\n{blueprint_name}.{handler_name}\n```\n"
  },
  {
    "path": "guide/content/en/guide/best-practices/decorators.md",
    "content": "# Decorators\n\nOne of the best ways to create a consistent and DRY web API is to make use of decorators to remove functionality from the handlers, and make it repeatable across your views.\n\n.. column::\n\n    Therefore, it is very common to see a Sanic view handler with several decorators on it.\n\n.. column::\n\n    ```python\n    @app.get(\"/orders\")\n    @authorized(\"view_order\")\n    @validate_list_params()\n    @inject_user()\n    async def get_order_details(request, params, user):\n        ...\n    ```\n\n\n## Example\n\nHere is a starter template to help you create decorators.\n\nIn this example, let’s say you want to check that a user is authorized to access a particular endpoint. You can create a decorator that wraps a handler function, checks a request if the client is authorized to access a resource, and sends the appropriate response.\n```python\nfrom functools import wraps\nfrom sanic.response import json\n\ndef authorized():\n    def decorator(f):\n        @wraps(f)\n        async def decorated_function(request, *args, **kwargs):\n            # run some method that checks the request\n            # for the client's authorization status\n            is_authorized = await check_request_for_authorization_status(request)\n\n            if is_authorized:\n                # the user is authorized.\n                # run the handler method and return the response\n                response = await f(request, *args, **kwargs)\n                return response\n            else:\n                # the user is not authorized.\n                return json({\"status\": \"not_authorized\"}, 403)\n        return decorated_function\n    return decorator\n\n@app.route(\"/\")\n@authorized()\nasync def test(request):\n    return json({\"status\": \"authorized\"})\n```\n\n## Templates\n\nDecorators are **fundamental** to building applications with Sanic. They increase the portability and maintainablity of your code. \n\nIn paraphrasing the Zen of Python: \"[decorators] are one honking great idea -- let's do more of those!\"\n\nTo make it easier to implement them, here are three examples of copy/pastable code to get you started.\n\n.. column::\n\n    Don't forget to add these import statements. Although it is *not* necessary, using `@wraps` helps keep some of the metadata of your function intact. [See docs](https://docs.python.org/3/library/functools.html#functools.wraps). Also, we use the `isawaitable` pattern here to allow the route handlers to by regular or asynchronous functions.\n\n.. column::\n\n    ```python\n    from inspect import isawaitable\n    from functools import wraps\n    ```\n\n### With args\n\n.. column::\n\n    Often, you will want a decorator that will *always* need arguments. Therefore, when it is implemented you will always be calling it.\n\n    ```python\n    @app.get(\"/\")\n    @foobar(1, 2)\n    async def handler(request: Request):\n        return text(\"hi\")\n    ```\n\n.. column::\n\n    ```python\n    def foobar(arg1, arg2):\n        def decorator(f):\n            @wraps(f)\n            async def decorated_function(request, *args, **kwargs):\n\n                response = f(request, *args, **kwargs)\n                if isawaitable(response):\n                    response = await response\n\n                return response\n\n            return decorated_function\n\n        return decorator\n    ```\n\n### Without args\n\n.. column::\n\n    Sometimes you want a decorator that will not take arguments. When this is the case, it is a nice convenience not to have to call it\n\n    ```python\n    @app.get(\"/\")\n    @foobar\n    async def handler(request: Request):\n        return text(\"hi\")\n    ```\n\n.. column::\n\n    ```python\n    def foobar(func):\n        def decorator(f):\n            @wraps(f)\n            async def decorated_function(request, *args, **kwargs):\n\n                response = f(request, *args, **kwargs)\n                if isawaitable(response):\n                    response = await response\n\n                return response\n\n            return decorated_function\n\n        return decorator(func)\n    ```\n\n### With or Without args\n\n.. column::\n\n    If you want a decorator with the ability to be called or not, you can follow this pattern. Using keyword only arguments is not necessary, but might make implementation simpler.\n\n    ```python\n    @app.get(\"/\")\n    @foobar(arg1=1, arg2=2)\n    async def handler(request: Request):\n        return text(\"hi\")\n    ```\n\n    ```python\n    @app.get(\"/\")\n    @foobar\n    async def handler(request: Request):\n        return text(\"hi\")\n    ```\n\n.. column::\n\n    ```python\n    def foobar(maybe_func=None, *, arg1=None, arg2=None):\n        def decorator(f):\n            @wraps(f)\n            async def decorated_function(request, *args, **kwargs):\n\n                response = f(request, *args, **kwargs)\n                if isawaitable(response):\n                    response = await response\n\n                return response\n\n            return decorated_function\n\n        return decorator(maybe_func) if maybe_func else decorator\n    ```\n\n"
  },
  {
    "path": "guide/content/en/guide/best-practices/exceptions.md",
    "content": "# Exceptions\n\n## Using Sanic exceptions\n\nSometimes you just need to tell Sanic to halt execution of a handler and send back a status code response. You can raise a `SanicException` for this and Sanic will do the rest for you.\n\nYou can pass an optional `status_code` argument. By default, a SanicException will return an internal server error 500 response.\n\n```python\nfrom sanic.exceptions import SanicException\n\n@app.route(\"/youshallnotpass\")\nasync def no_no(request):\n        raise SanicException(\"Something went wrong.\", status_code=501)\n```\n\nSanic provides a number of standard exceptions. They each automatically will raise the appropriate HTTP status code in your response. [Check the API reference](https://sanic.readthedocs.io/en/latest/sanic/api_reference.html#module-sanic.exceptions) for more details. \n\n.. column::\n\n    The more common exceptions you _should_ implement yourself include:\n\n    - `BadRequest` (400)\n    - `Unauthorized` (401)\n    - `Forbidden` (403)\n    - `NotFound` (404)\n    - `ServerError` (500)\n\n.. column::\n\n    ```python\n    from sanic import exceptions\n\n    @app.route(\"/login\")\n    async def login(request):\n        user = await some_login_func(request)\n        if not user:\n            raise exceptions.NotFound(\n                f\"Could not find user with username={request.json.username}\"\n            )\n    ```\n\n## Exception properties\n\nAll exceptions in Sanic derive from `SanicException`. That class has a few properties on it that assist the developer in consistently reporting their exceptions across an application.\n\n- `message`\n- `status_code`\n- `quiet`\n- `headers`\n- `context`\n- `extra`\n\nAll of these properties can be passed to the exception when it is created, but the first three can also be used as class variables as we will see.\n\n.. column::\n\n    ### `message`\n\n    The `message` property obviously controls the message that will be displayed as with any other exception in Python. What is particularly useful is that you can set the `message` property on the class definition allowing for easy standardization of language across an application\n\n.. column::\n\n    ```python\n    class CustomError(SanicException):\n        message = \"Something bad happened\"\n\n    raise CustomError\n    # or\n    raise CustomError(\"Override the default message with something else\")\n    ```\n\n\n.. column::\n\n    ### `status_code`\n\n    This property is used to set the response code when the exception is raised. This can particularly be useful when creating custom 400 series exceptions that are usually in response to bad information coming from the client.\n\n.. column::\n\n    ```python\n    class TeapotError(SanicException):\n        status_code = 418\n        message = \"Sorry, I cannot brew coffee\"\n\n    raise TeapotError\n    # or\n    raise TeapotError(status_code=400)\n    ```\n\n\n.. column::\n\n    ### `quiet`\n\n    By default, exceptions will be output by Sanic to the `error_logger`. Sometimes this may not be desirable, especially if you are using exceptions to trigger events in exception handlers (see [the following section](./exceptions.md#handling)). You can suppress the log output using `quiet=True`.\n\n.. column::\n\n    ```python\n    class SilentError(SanicException):\n        message = \"Something happened, but not shown in logs\"\n        quiet = True\n\n    raise SilentError\n    # or\n    raise InvalidUsage(\"blah blah\", quiet=True)\n    ```\n\n\n.. column::\n\n    Sometimes while debugging you may want to globally ignore the `quiet=True` property. You can force Sanic to log out all exceptions regardless of this property using `NOISY_EXCEPTIONS`\n\n    *Added in v21.12*\n\n.. column::\n\n    ```python\n    app.config.NOISY_EXCEPTIONS = True\n    ```\n\n\n.. column::\n\n    ### `headers`\n\n    Using `SanicException` as a tool for creating responses is super powerful. This is in part because not only can you control the `status_code`, but you can also control reponse headers directly from the exception.\n\n.. column::\n\n    ```python\n    class MyException(SanicException):\n        headers = {\n          \"X-Foo\": \"bar\"\n        }\n\n    raise MyException\n    # or\n    raise InvalidUsage(\"blah blah\", headers={\n        \"X-Foo\": \"bar\"\n    })\n    ```\n\n\n.. column::\n\n    ### `extra`\n\n    See [contextual exceptions](./exceptions.md#contextual-exceptions)\n\n    *Added in v21.12*\n\n.. column::\n\n    ```python\n    raise SanicException(..., extra={\"name\": \"Adam\"})\n    ```\n\n\n.. column::\n\n    ### `context`\n\n    See [contextual exceptions](./exceptions.md#contextual-exceptions)\n\n    *Added in v21.12*\n\n.. column::\n\n    ```python\n    raise SanicException(..., context={\"foo\": \"bar\"})\n    ```\n\n\n## Handling\n\nSanic handles exceptions automatically by rendering an error page, so in many cases you don't need to handle them yourself. However, if you would like more control on what to do when an exception is raised, you can implement a handler yourself.\n\nSanic provides a decorator for this, which applies to not only the Sanic standard exceptions, but **any** exception that your application might throw.\n\n.. column::\n\n    The easiest method to add a handler is to use `@app.exception()` and pass it one or more exceptions.\n\n.. column::\n\n    ```python\n    from sanic.exceptions import NotFound\n\n    @app.exception(NotFound, SomeCustomException)\n    async def ignore_404s(request, exception):\n        return text(\"Yep, I totally found the page: {}\".format(request.url))\n    ```\n\n\n.. column::\n\n    You can also create a catchall handler by catching `Exception`.\n\n.. column::\n\n    ```python\n    @app.exception(Exception)\n    async def catch_anything(request, exception):\n        ...\n    ```\n\n\n.. column::\n\n    You can also use `app.error_handler.add()` to add error handlers.\n\n.. column::\n\n    ```python\n    async def server_error_handler(request, exception):\n        return text(\"Oops, server error\", status=500)\n\n    app.error_handler.add(Exception, server_error_handler)\n    ```\n\n## Built-in error handling\n\nSanic ships with three formats for exceptions: HTML, JSON, and text. You can see examples of them below in the [Fallback handler](#fallback-handler) section.\n\n.. column::\n\n    You can control _per route_ which format to use with the `error_format` keyword argument.\n\n    *Added in v21.9*\n\n.. column::\n\n    ```python\n    @app.request(\"/\", error_format=\"text\")\n    async def handler(request):\n        ...\n    ```\n\n\n## Custom error handling\n\nIn some cases, you might want to add some more error handling functionality to what is provided by default. In that case, you can subclass Sanic's default error handler as such:\n\n```python\nfrom sanic.handlers import ErrorHandler\n\nclass CustomErrorHandler(ErrorHandler):\n    def default(self, request: Request, exception: Exception) -> HTTPResponse:\n        ''' handles errors that have no error handlers assigned '''\n        # You custom error handling logic...\n        status_code = getattr(exception, \"status_code\", 500)\n        return json({\n          \"error\": str(exception),\n          \"foo\": \"bar\"\n        }, status=status_code)\n\napp.error_handler = CustomErrorHandler()\n```\n\n## Fallback handler\n\nSanic comes with three fallback exception handlers:\n\n1. HTML\n2. Text\n3. JSON\n\nThese handlers present differing levels of detail depending upon whether your application is in [debug mode](../running/development.md) or not.\n\nBy default, Sanic will be in \"auto\" mode, which means that it will using the incoming request and potential matching handler to choose the appropriate response format. For example, when in a browser it should always provide an HTML error page. When using curl, you might see JSON or plain text.\n\n### HTML\n\n```python\napp.config.FALLBACK_ERROR_FORMAT = \"html\"\n```\n\n.. column::\n\n    ```python\n    app.config.DEBUG = True\n    ```\n\n    ![Error](/assets/images/error-display-html-debug.png)\n\n.. column::\n\n    ```python\n    app.config.DEBUG = False\n    ```\n\n    ![Error](/assets/images/error-display-html-prod.png)\n\n### Text\n\n```python\napp.config.FALLBACK_ERROR_FORMAT = \"text\"\n```\n\n.. column::\n\n    ```python\n    app.config.DEBUG = True\n    ```\n\n    ```sh\n    curl localhost:8000/exc -i\n    HTTP/1.1 500 Internal Server Error\n    content-length: 620\n    connection: keep-alive\n    content-type: text/plain; charset=utf-8\n\n    ⚠️ 500 — Internal Server Error\n    ==============================\n    That time when that thing broke that other thing? That happened.\n\n    ServerError: That time when that thing broke that other thing? That happened. while handling path /exc\n    Traceback of TestApp (most recent call last):\n\n      ServerError: That time when that thing broke that other thing? That happened.\n        File /path/to/sanic/app.py, line 979, in handle_request\n        response = await response\n\n        File /path/to/server.py, line 16, in handler\n        do_something(cause_error=True)\n\n        File /path/to/something.py, line 9, in do_something\n        raise ServerError(\n    ```\n\n.. column::\n\n    ```python\n    app.config.DEBUG = False\n    ```\n\n    ```sh\n    curl localhost:8000/exc -i\n    HTTP/1.1 500 Internal Server Error\n    content-length: 134\n    connection: keep-alive\n    content-type: text/plain; charset=utf-8\n\n    ⚠️ 500 — Internal Server Error\n    ==============================\n    That time when that thing broke that other thing? That happened.\n    ```\n\n### JSON\n\n```python\napp.config.FALLBACK_ERROR_FORMAT = \"json\"\n```\n\n.. column::\n\n    ```python\n    app.config.DEBUG = True\n    ```\n\n    ```sh\n    curl localhost:8000/exc -i\n    HTTP/1.1 500 Internal Server Error\n    content-length: 572\n    connection: keep-alive\n    content-type: application/jso\n\n    {\n      \"description\": \"Internal Server Error\",\n      \"status\": 500,\n      \"message\": \"That time when that thing broke that other thing? That happened.\",\n      \"path\": \"/exc\",\n      \"args\": {},\n      \"exceptions\": [\n        {\n          \"type\": \"ServerError\",\n          \"exception\": \"That time when that thing broke that other thing? That happened.\",\n          \"frames\": [\n            {\n              \"file\": \"/path/to/sanic/app.py\",\n              \"line\": 979,\n              \"name\": \"handle_request\",\n              \"src\": \"response = await response\"\n            },\n            {\n              \"file\": \"/path/to/server.py\",\n              \"line\": 16,\n              \"name\": \"handler\",\n              \"src\": \"do_something(cause_error=True)\"\n            },\n            {\n              \"file\": \"/path/to/something.py\",\n              \"line\": 9,\n              \"name\": \"do_something\",\n              \"src\": \"raise ServerError(\"\n            }\n          ]\n        }\n      ]\n    }\n    ```\n\n.. column::\n\n    ```python\n    app.config.DEBUG = False\n    ```\n\n    ```sh\n    curl localhost:8000/exc -i\n    HTTP/1.1 500 Internal Server Error\n    content-length: 129\n    connection: keep-alive\n    content-type: application/json\n\n    {\n      \"description\": \"Internal Server Error\",\n      \"status\": 500,\n      \"message\": \"That time when that thing broke that other thing? That happened.\"\n    }\n\n    ```\n\n### Auto\n\nSanic also provides an option for guessing which fallback option to use.\n\n```python\napp.config.FALLBACK_ERROR_FORMAT = \"auto\"\n```\n## Contextual Exceptions\n\nDefault exception messages that simplify the ability to consistently raise exceptions throughout your application.\n\n```python\nclass TeapotError(SanicException):\n    status_code = 418\n    message = \"Sorry, I cannot brew coffee\"\n\nraise TeapotError\n```\n\nBut this lacks two things:\n\n1. A dynamic and predictable message format\n2. The ability to add additional context to an error message (more on this in a moment)\n\n*Added in v21.12*\n\nUsing one of Sanic's exceptions, you have two options to provide additional details at runtime:\n\n```python\nraise TeapotError(extra={\"foo\": \"bar\"}, context={\"foo\": \"bar\"})\n```\n\nWhat's the difference and when should you decide to use each?\n\n- `extra` - The object itself will **never** be sent to a production client. It is meant for internal use only. What could it be used for?\n  - Generating (as we will see in a minute) a dynamic error message\n  - Providing runtime details to a logger\n  - Debug information (when in development mode, it is rendered)\n- `context` - This object is **always** sent to production clients. It is generally meant to be used to send additional details about the context of what happened. What could it be used for?\n  - Providing alternative values on a `BadRequest` validation issue\n  - Responding with helpful details for your customers to open a support ticket\n  - Displaying state information like current logged in user info\n\n### Dynamic and predictable message using `extra`\n\nSanic exceptions can be raised using `extra` keyword arguments to provide additional information to a raised exception instance.\n\n```python\nclass TeapotError(SanicException):\n    status_code = 418\n\n    @property\n    def message(self):\n        return f\"Sorry {self.extra['name']}, I cannot make you coffee\"\n\nraise TeapotError(extra={\"name\": \"Adam\"})\n```\n\nThe new feature allows the passing of `extra` meta to the exception instance, which can be particularly useful as in the above example to pass dynamic data into the message text. This `extra` info object **will be suppressed** when in `PRODUCTION` mode, but displayed in `DEVELOPMENT` mode.\n\n.. column::\n\n    **DEVELOPMENT**\n\n    ![image](~@assets/images/error-extra-debug.png)\n\n.. column::\n\n    **PRODUCTION**\n\n    ![image](~@assets/images/error-extra-prod.png)\n\n### Additional `context` to an error message\n\nSanic exceptions can also be raised with a `context` argument to pass intended information along to the user about what happened. This is particularly useful when creating microservices or an API intended to pass error messages in JSON format. In this use case, we want to have some context around the exception beyond just a parseable error message to return details to the client.\n\n```python\nraise TeapotError(context={\"foo\": \"bar\"})\n```\n\nThis is information **that we want** to always be passed in the error (when it is available). Here is what it should look like:\n\n.. column::\n\n    **PRODUCTION**\n\n    ```json\n    {\n      \"description\": \"I'm a teapot\",\n      \"status\": 418,\n      \"message\": \"Sorry Adam, I cannot make you coffee\",\n      \"context\": {\n        \"foo\": \"bar\"\n      }\n    }\n    ```\n\n.. column::\n\n    **DEVELOPMENT**\n\n    ```json\n    {\n      \"description\": \"I'm a teapot\",\n      \"status\": 418,\n      \"message\": \"Sorry Adam, I cannot make you coffee\",\n      \"context\": {\n        \"foo\": \"bar\"\n      },\n      \"path\": \"/\",\n      \"args\": {},\n      \"exceptions\": [\n        {\n          \"type\": \"TeapotError\",\n          \"exception\": \"Sorry Adam, I cannot make you coffee\",\n          \"frames\": [\n            {\n              \"file\": \"handle_request\",\n              \"line\": 83,\n              \"name\": \"handle_request\",\n              \"src\": \"\"\n            },\n            {\n              \"file\": \"/tmp/p.py\",\n              \"line\": 17,\n              \"name\": \"handler\",\n              \"src\": \"raise TeapotError(\"\n            }\n          ]\n        }\n      ]\n    }\n    ```\n\n\n\n## Error reporting\n\nSanic has a [signal](../advanced/signals.md#built-in-signals) that allows you to hook into the exception reporting process. This is useful if you want to send exception information to a third party service like Sentry or Rollbar. This can be conveniently accomplished by attaching an error reporting handler as show below:\n\n```python\n@app.report_exception\nasync def catch_any_exception(app: Sanic, exception: Exception):\nprint(\"Caught exception:\", exception)\n```\n\n.. note::\n\n    This handler will be dispatched into a background task and **IS NOT** intended for use to manipulate any response data. It is intended to be used for logging or reporting purposes only, and should not impact the ability of your application to return the error response to the client.\n\n*Added in v23.6*\n\n"
  },
  {
    "path": "guide/content/en/guide/best-practices/logging.md",
    "content": "# Logging\n\nSanic allows you to do different types of logging (access log, error log) on the requests based on the [Python logging API](https://docs.python.org/3/howto/logging.html). You should have some basic knowledge on Python logging if you want to create a new configuration.\n\nBut, don't worry, out of the box Sanic ships with some sensible logging defaults. Out of the box it uses an `AutoFormatter` that will format the logs depending upon whether you are in debug mode or not. We will show you how to force this later on.\n\n## Quick Start\n\nLet's start by looking at what logging might look like in local development. For this, we will use the default logging configuration that Sanic provides and make sure to run Sanic in development mode.\n\n.. column::\n\n    A simple example using default settings would be like this:\n\n.. column::\n\n    ```python\n    from sanic import Sanic\n    from sanic.log import logger\n    from sanic.response import text\n\n    app = Sanic('logging_example')\n\n    @app.route('/')\n    async def test(request):\n        logger.info('Here is your log')\n        return text('Hello World!')\n    ```\n\n.. column::\n\n    Because we are specifically trying to look at the development logs, we will make sure to run Sanic in development mode.\n\n.. column::\n\n    ```sh\n    sanic path.to.server:app --dev\n    ```    \n    \n\nAfter the server is running, you should see logs like this.\n\n![Sanic Logging Start](/assets/images/logging-debug-start.png)\n\nYou can send a request to server and it will print the log messages.\n\n![Sanic Logging Access](/assets/images/logging-debug-access.png)\n\nSome important points to note:\n\n- The default log level in **production** mode is `INFO`.\n- The default log level in **debug** mode is `DEBUG`.\n- When in **debug** mode, the log messages will not have a timestamp (except on access logs).\n- Sanic will try to colorize the logs if the terminal supports it. If you are running in Docker with docker-compose, you may need to set `tty: true` in your `docker-compose.yml` file to see the colors.\n\n## Sanic's loggers\n\nOut of the box, Sanic ships with five loggers:\n\n| **Logger Name**   | **Use Case**                  |\n|-------------------|-------------------------------|\n| `sanic.root`      | Used to log internal messages. |\n| `sanic.error`     | Used to log error logs.       |\n| `sanic.access`    | Used to log access logs.      |\n| `sanic.server`    | Used to log server logs.      |\n| `sanic.websockets`| Used to log websocket logs.   |\n\n.. column::\n\n    If you want to use these loggers yourself, you can import them from `sanic.log`.\n    \n.. column::\n\n    ```python\n    from sanic.log import logger, error_logger, access_logger, server_logger, websockets_logger\n    \n    logger.info('This is a root logger message')\n    ```\n    \n.. warning::\n\n    Feel free to use the root logger and the error logger yourself. But, you probably don't want to use the access logger, server logger, or websockets logger directly. These are used internally by Sanic and are configured to log in a specific way. If you want to change the way these loggers log, you should change the logging configuration.\n    \n## Default logging configuration\n\nSanic ships with a default logging configuration that is used when you do not provide your own. This configuration is stored in `sanic.log.LOGGING_CONFIG_DEFAULTS`.\n\n```python\n{\n    'version': 1,\n    'disable_existing_loggers': False,\n    'loggers': {\n        'sanic.root': {'level': 'INFO', 'handlers': ['console']},\n        'sanic.error': {\n            'level': 'INFO',\n            'handlers': ['error_console'],\n            'propagate': True,\n            'qualname': 'sanic.error'\n        },\n        'sanic.access': {\n            'level': 'INFO',\n            'handlers': ['access_console'],\n            'propagate': True,\n            'qualname': 'sanic.access'\n        },\n        'sanic.server': {\n            'level': 'INFO',\n            'handlers': ['console'],\n            'propagate': True,\n            'qualname': 'sanic.server'\n        },\n        'sanic.websockets': {\n            'level': 'INFO',\n            'handlers': ['console'],\n            'propagate': True,\n            'qualname': 'sanic.websockets'\n        }\n    },\n    'handlers': {\n        'console': {\n            'class': 'logging.StreamHandler',\n            'formatter': 'generic',\n            'stream': sys.stdout\n        },\n        'error_console': {\n            'class': 'logging.StreamHandler',\n            'formatter': 'generic',\n            'stream': sys.stderr\n        },\n        'access_console': {\n            'class': 'logging.StreamHandler',\n            'formatter': 'access',\n            'stream': sys.stdout\n        }\n    },\n    'formatters': {\n        'generic': {'class': 'sanic.logging.formatter.AutoFormatter'},\n        'access': {'class': 'sanic.logging.formatter.AutoAccessFormatter'}\n    }\n}\n```\n\n## Changing Sanic loggers\n\n.. column::\n\n    To use your own logging config, simply use `logging.config.dictConfig`, or pass `log_config` when you initialize Sanic app.\n\n.. column::\n\n    ```python\n    app = Sanic('logging_example', log_config=LOGGING_CONFIG)\n\n    if __name__ == \"__main__\":\n        app.run(access_log=False)\n    ```\n\n.. column::\n\n    But, what if you do not want to control the logging completely, just change the formatter for example? Here, we will import the default logging config and modify only the parts that we want to force (for example) to use the `ProdFormatter` all of the time.\n    \n.. column::\n\n    ```python\n    from sanic.log import LOGGING_CONFIG_DEFAULTS\n    \n    LOGGING_CONFIG_DEFAULTS['formatters']['generic']['class'] = 'sanic.logging.formatter.ProdFormatter'\n    LOGGING_CONFIG_DEFAULTS['formatters']['access']['class'] = 'sanic.logging.formatter.ProdAccessFormatter'\n    \n    app = Sanic('logging_example', log_config=LOGGING_CONFIG_DEFAULTS)\n    ```\n\n\n.. tip:: FYI\n\n    Logging in Python is a relatively cheap operation. However, if you are serving a high number of requests and performance is a concern, all of that time logging out access logs adds up and becomes quite expensive.  \n\n    This is a good opportunity to place Sanic behind a proxy (like nginx) and to do your access logging there. You will see a *significant* increase in overall performance by disabling the `access_log`.  \n\n    For optimal production performance, it is advised to run Sanic with `debug` and `access_log` disabled: `app.run(debug=False, access_log=False)`\n\n\n## Access logger additional parameters\n\nSanic provides additional parameters to the access logger.\n\n| Log Context Parameter | Parameter Value                       | Datatype |\n|-----------------------|---------------------------------------|----------|\n| `host`                | `request.ip`                          | `str`    |\n| `request`             | `request.method + \" \" + request.url`  | `str`    |\n| `status`              | `response`                            | `int`    |\n| `byte`                | `len(response.body)`                  | `int`    |\n| `duration`            | <calculated>                          | `float`  |\n\n## Legacy logging\n\nMany logging changes were introduced in Sanic 24.3. The main changes were related to logging formats. If you prefer the legacy logging format, you can use the `sanic.logging.formatter.LegacyFormatter` and `sanic.logging.formatter.LegacyAccessFormatter` formatters.\n"
  },
  {
    "path": "guide/content/en/guide/best-practices/testing.md",
    "content": "# Testing\n\nSee [sanic-testing](../../plugins/sanic-testing/getting-started.md)\n"
  },
  {
    "path": "guide/content/en/guide/deployment/caddy.md",
    "content": "# Caddy Deployment\n\n## Introduction\n\nCaddy is a state-of-the-art web server and proxy that supports up to HTTP/3. Its simplicity lies in its minimalistic configuration and the inbuilt ability to automatically procure TLS certificates for your domains from Let's Encrypt. In this setup, we will configure the Sanic application to serve locally at 127.0.0.1:8001, with Caddy playing the role of the public-facing server for the domain example.com.\n\nYou may install Caddy from your favorite package menager on Windows, Linux and Mac. The package is named `caddy`.\n\n## Proxied Sanic app\n\n```python\nfrom sanic import Sanic\nfrom sanic.response import text\n\napp = Sanic(\"proxied_example\")\n\n@app.get(\"/\")\ndef index(request):\n    # This should display external (public) addresses:\n    return text(\n        f\"{request.remote_addr} connected to {request.url_for('index')}\\n\"\n        f\"Forwarded: {request.forwarded}\\n\"\n    )\n```\n\nTo run this application, save as `proxied_example.py`, and use the sanic command-line interface as follows:\n\n```bash\nSANIC_PROXIES_COUNT=1 sanic proxied_example --port 8001\n```\n\nSetting the SANIC_PROXIES_COUNT environment variable instructs Sanic to trust the X-Forwarded-* headers sent by Caddy, allowing it to correctly identify the client's IP address and other information.\n\n## Caddy is simple\n\nIf you have no other web servers running, you can simply run Caddy CLI (needs `sudo` on Linux):\n\n```bash\ncaddy reverse-proxy --from example.com --to :8001\n```\n\nThis is a complete server that includes a certificate for your domain, http-to-https redirect, proxy headers, streaming and WebSockets. Your Sanic application should now be available on the domain you specified by HTTP versions 1, 2 and 3. Remember to open up UDP/443 on your firewall to enable H3 communications.\n\nAll done?\n\nSoon enough you'll be needing more than one server, or more control over details, which is where the configuration files come in. The above command is equivalent to this `Caddyfile`, serving as a good starting point for your install:\n\n```\nexample.com {\n    reverse_proxy localhost:8001\n}\n```\n\nSome Linux distributions install Caddy such that it reads configuration from `/etc/caddy/Caddyfile`, which `import /etc/caddy/conf.d/*` for each site you are running. If not, you'll need to manually run `caddy run` as a system service, pointing it at the proper config file. Alternatively, use Caddy API mode with `caddy run --resume` for persistent config changes. Note that any Caddyfile loading will replace all prior configuration and thus `caddy-api` is not configurable in this traditional manner.\n\n## Advanced configuration\n\nAt times, you might need to mix static files and handlers at the site root for cleaner URLs. In Sanic, you'd use `app.static(\"/\", \"static\", index=\"index.html\")` to achieve this. However, for improved performance, you can offload serving static files to Caddy:\n\n```\napp.example.com {\n    # Look for static files first, proxy to Sanic if not found\n    route {\n        file_server {\n            root /srv/sanicexample/static\n            precompress br                     # brotli your large scripts and styles\n            pass_thru\n        }\n        reverse_proxy unix//tmp/sanic.socket   # sanic --unix /tmp/sanic.socket\n    }\n}\n```\n\nPlease refer to [Caddy documentation](https://caddyserver.com/docs/) for more options.\n"
  },
  {
    "path": "guide/content/en/guide/deployment/docker.md",
    "content": "# Docker Deployment\n\n## Introduction\n\nFor a long time, the environment has always been a difficult problem for deployment. If there are conflicting configurations in your project, you have to spend a lot of time resolving them. Fortunately, virtualization provides us with a good solution. Docker is one of them. If you don't know Docker, you can visit [Docker official website](https://www.docker.com/) to learn more.\n\n## Build Image\n\nLet's start with a simple project. We will use a Sanic project as an example. Assume the project path is `/path/to/SanicDocker`.\n\n.. column::\n\n    The directory structure looks like this:\n\n.. column::\n\n    ```text\n    # /path/to/SanicDocker\n    SanicDocker\n    ├── requirements.txt\n    ├── dockerfile\n    └── server.py\n    ```\n\n\n.. column::\n\n    And the `server.py` code looks like this:\n\n.. column::\n\n    ```python\n    app = Sanic(\"MySanicApp\")\n\n    @app.get('/')\n    async def hello(request):\n        return text(\"OK!\")\n\n    if __name__ == '__main__':\n        app.run(host='0.0.0.0', port=8000)\n    ```\n\n\n\n.. note:: \n\n    Please note that the host cannot be 127.0.0.1 . In docker container, 127.0.0.1 is the default network interface of the container, only the container can communicate with other containers. more information please visit [Docker network](https://docs.docker.com/engine/reference/commandline/network/)\n\n\n\nCode is ready, let's write the `Dockerfile`:\n\n```Dockerfile\n\nFROM sanicframework/sanic:3.8-latest\n\nWORKDIR /sanic\n\nCOPY . .\n\nRUN pip install -r requirements.txt\n\nEXPOSE 8000\n\nCMD [\"python\", \"server.py\"]\n```\n\nRun the following command to build the image:\n\n```shell\ndocker build -t my-sanic-image .\n```\n\n## Start Container\n\n.. column::\n\n    After the image built, we can start the container use `my-sanic-image`:\n\n.. column::\n\n    ```shell\n    docker run --name mysanic -p 8000:8000 -d my-sanic-image\n    ```\n\n\n.. column::\n\n    Now we can visit `http://localhost:8000` to see the result:\n\n.. column::\n\n    ```text\n    OK!\n    ```\n\n## Use docker-compose\n\nIf your project consist of multiple services, you can use [docker-compose](https://docs.docker.com/compose/) to manage them.\n\nfor example, we will deploy `my-sanic-image` and `nginx`, achieve through nginx access sanic server.\n\n.. column::\n\n    First of all, we need prepare nginx configuration file. create a file named `mysanic.conf`:\n\n.. column::\n\n    ```nginx\n    server {\n        listen 80;\n        listen [::]:80;\n        location / {\n          proxy_pass http://mysanic:8000/;\n          proxy_set_header Upgrade $http_upgrade;\n          proxy_set_header Connection upgrade;\n          proxy_set_header Accept-Encoding gzip;\n        }\n    }\n    ```\n\n\n.. column::\n\n    Then, we need to prepare `docker-compose.yml` file. The content follows:\n\n.. column::\n\n    ```yaml\n    version: \"3\"\n\n    services:\n      mysanic:\n        image: my-sanic-image\n        ports:\n          - \"8000:8000\"\n        restart: always\n\n      mynginx:\n        image: nginx:1.13.6-alpine\n        ports:\n          - \"80:80\"\n        depends_on:\n          - mysanic\n        volumes:\n          - ./mysanic.conf:/etc/nginx/conf.d/mysanic.conf\n        restart: always\n\n    networks:\n      default:\n        driver: bridge\n    ```\n\n\n.. column::\n\n    After that, we can start them:\n\n.. column::\n\n    ```shell\n    docker-compose up -d\n    ```\n\n\n.. column::\n\n    Now, we can visit `http://localhost:80` to see the result:\n\n.. column::\n\n    ```text\n    OK!\n    ```\n\n"
  },
  {
    "path": "guide/content/en/guide/deployment/kubernetes.md",
    "content": "# Kubernetes\n"
  },
  {
    "path": "guide/content/en/guide/deployment/nginx.md",
    "content": "# Nginx Deployment\n\n## Introduction\n\nAlthough Sanic can be run directly on Internet, it may be useful to use a proxy\nserver such as Nginx in front of it. This is particularly useful for running\nmultiple virtual hosts on the same IP, serving NodeJS or other services beside\na single Sanic app, and it also allows for efficient serving of static files.\nTLS and HTTP/2 are also easily implemented on such proxy.\n\nWe are setting the Sanic app to serve only locally at 127.0.0.1:8001, while the\nNginx installation is responsible for providing the service to public Internet\non domain example.com. Static files will be served by Nginx for maximal\nperformance.\n\n## Proxied Sanic app\n\n```python\nfrom sanic import Sanic\nfrom sanic.response import text\n\napp = Sanic(\"proxied_example\")\n\n@app.get(\"/\")\ndef index(request):\n    # This should display external (public) addresses:\n    return text(\n        f\"{request.remote_addr} connected to {request.url_for('index')}\\n\"\n        f\"Forwarded: {request.forwarded}\\n\"\n    )\n```\n\nSince this is going to be a system service, save your code to\n`/srv/sanicservice/proxied_example.py`.\n\nFor testing, run your app in a terminal using the `sanic` CLI in the folder where you saved the file.\n\n```bash\nSANIC_FORWARDED_SECRET=_hostname sanic proxied_example --port 8001\n```\n\nWe provide Sanic config `FORWARDED_SECRET` to identify which proxy it gets\nthe remote addresses from. Note the `_` in front of the local hostname.\nThis gives basic protection against users spoofing these headers and faking\ntheir IP addresses and more.\n\n## SSL certificates\n\nInstall Certbot and obtain a certicate for all your domains. This will spin up its own webserver on port 80 for a moment to verify you control the given domain names.\n\n```bash\ncertbot -d example.com -d www.example.com\n```\n\n## Nginx configuration\n\nQuite much configuration is required to allow fast transparent proxying, but\nfor the most part these don't need to be modified, so bear with me.\n\n\n.. tip:: Note\n\n    Separate upstream section, rather than simply adding the IP after `proxy_pass`\n    as in most tutorials, is needed for HTTP keep-alive. We also enable streaming,\n    WebSockets and Nginx serving static files.\n\n\nThe following config goes inside the `http` section of `nginx.conf` or if your\nsystem uses multiple config files, `/etc/nginx/sites-available/default` or\nyour own files (be sure to symlink them to `sites-enabled`):\n\n```nginx\n# Files managed by Certbot\nssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;\nssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;\n\n# Sanic service\nupstream example.com {\n  keepalive 100;\n  server 127.0.0.1:8001;\n  #server unix:/tmp//sanic.sock;\n}\n\nserver {\n  server_name example.com;\n  listen 443 ssl http2 default_server;\n  listen [::]:443 ssl http2 default_server;\n  # Serve static files if found, otherwise proxy to Sanic\n  location / {\n    root /srv/sanicexample/static;\n    try_files $uri @sanic;\n  }\n  location @sanic {\n    proxy_pass http://$server_name;\n    # Allow fast streaming HTTP/1.1 pipes (keep-alive, unbuffered)\n    proxy_http_version 1.1;\n    proxy_request_buffering off;\n    proxy_buffering off;\n    proxy_set_header forwarded 'by=\\\"_$hostname\\\";$for_addr;proto=$scheme;host=\\\"$http_host\\\"';\n    # Allow websockets and keep-alive (avoid connection: close)\n    proxy_set_header connection \"upgrade\";\n    proxy_set_header upgrade $http_upgrade;\n  }\n}\n\n# Redirect WWW to no-WWW\nserver {\n  listen 443 ssl http2;\n  listen [::]:443 ssl http2;\n  server_name ~^www\\.(.*)$;\n  return 308 $scheme://$1$request_uri;\n}\n\n# Redirect all HTTP to HTTPS with no-WWW\nserver {\n  listen 80 default_server;\n  listen [::]:80 default_server;\n  server_name ~^(?:www\\.)?(.*)$;\n  return 308 https://$1$request_uri;\n}\n\n# Forwarded for= client IP address formatting\nmap $remote_addr $for_addr {\n  ~^[0-9.]+$          \"for=$remote_addr\";        # IPv4 client address\n  ~^[0-9A-Fa-f:.]+$   \"for=\\\"[$remote_addr]\\\"\";  # IPv6 bracketed and quoted\n  default             \"for=unknown\";             # Unix socket\n}\n```\n\nStart or restart Nginx for changes to take effect. E.g.\n\n```bash\nsystemctl restart nginx\n```\n\nYou should be able to connect your app on `https://example.com`. Any 404\nerrors and such will be handled by Sanic's error pages, and whenever a static\nfile is present at a given path, it will be served by Nginx.\n\n## Running as a service\n\nThis part is for Linux distributions based on `systemd`. Create a unit file\n`/etc/systemd/system/sanicexample.service`\n\n```\n[Unit]\nDescription=Sanic Example\n\n[Service]\nDynamicUser=Yes\nWorkingDirectory=/srv/sanicservice\nEnvironment=SANIC_PROXY_SECRET=_hostname\nExecStart=sanic proxied_example --port 8001 --fast\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\n```\n\nThen reload service files, start your service and enable it on boot:\n\n```bash\nsystemctl daemon-reload\nsystemctl start sanicexample\nsystemctl enable sanicexample\n```\n\n\n.. tip:: Note\n\n    For brevity we skipped setting up a separate user account and a Python virtual environment or installing your app as a Python module. There are good tutorials on those topics elsewhere that easily apply to Sanic as well. The DynamicUser setting creates a strong sandbox which basically means your application cannot store its data in files, so you may consider setting `User=sanicexample` instead if you need that.\n\n"
  },
  {
    "path": "guide/content/en/guide/getting-started.md",
    "content": "# Getting Started\n\nBefore we begin, make sure you are running Python 3.9 or higher. Currently, Sanic is works with Python versions 3.9 – 3.13.\n\n## Install\n\n```sh\npip install sanic\n```\n\n## Hello, world application\n\n.. column::\n\n    If you have ever used one of the many decorator based frameworks, this probably looks somewhat familiar to you.\n\n    \n\n    .. note:: \n\n        If you are coming from Flask or another framework, there are a few important things to point out. Remember, Sanic aims for performance, flexibility, and ease of use. These guiding principles have tangible impact on the API and how it works.\n\n.. column::\n\n    ```python\n    from sanic import Sanic\n    from sanic.response import text\n\n    app = Sanic(\"MyHelloWorldApp\")\n\n    @app.get(\"/\")\n    async def hello_world(request):\n        return text(\"Hello, world.\")\n    ```\n\n### Important to note\n\n- Every request handler can either be sync (`def hello_world`) or async (`async def hello_world`). Unless you have a clear reason for it, always go with `async`.\n- The `request` object is always the first argument of your handler. Other frameworks pass this around in a context variable to be imported. In the `async` world, this would not work so well and it is far easier (not to mention cleaner and more performant) to be explicit about it. \n- You **must** use a response type. MANY other frameworks allow you to have a return value like this: `return \"Hello, world.\"` or this: `return {\"foo\": \"bar\"}`. But, in order to do this implicit calling, somewhere in the chain needs to spend valuable time trying to determine what you meant. So, at the expense of this ease, Sanic has decided to require an explicit call.\n\n### Running\n\n.. column::\n\n    Let's save the above file as `server.py`. And launch it.\n\n.. column::\n\n    ```sh\n    sanic server\n    ```\n\n.. note:: \n\n    This **another** important distinction. Other frameworks come with a built in development server and explicitly say that it is _only_ intended for development use. The opposite is true with Sanic. \n\n    **The packaged server is production ready.**\n\n\n## Sanic Extensions\n\nSanic intentionally aims for a clean and unopinionated feature list. The project does not want to require you to build your application in a certain way, and tries to avoid prescribing specific development patterns. There are a number of third-party plugins that are built and maintained by the community to add additional features that do not otherwise meet the requirements of the core repository.\n\nHowever, in order **to help API developers**, the Sanic organization maintains an official plugin called [Sanic Extensions](../plugins/sanic-ext/getting-started.md) to provide all sorts of goodies, including:\n\n- **OpenAPI** documentation with Redoc and/or Swagger\n- **CORS** protection\n- **Dependency injection** into route handlers\n- Request query arguments and body input **validation**\n- Auto create `HEAD`, `OPTIONS`, and `TRACE` endpoints\n- Predefined, endpoint-specific response serializers\n\nThe preferred method to set it up is to install it along with Sanic, but you can also install the packages on their own.\n\n.. column::\n\n    ```sh\n    pip install sanic[ext]\n    ```\n\n.. column::\n\n    ```sh\n    pip install sanic sanic-ext\n    ```\n\nStarting in v21.12, Sanic will automatically setup Sanic Extensions if it is in the same environment. You will also have access to two additional application properties:\n\n- `app.extend()` - used to configure Sanic Extensions\n- `app.ext` - the `Extend` instance attached to the application\n\nSee [the plugin documentation](../plugins/sanic-ext/getting-started.md) for more information about how to use and work with the plugin\n"
  },
  {
    "path": "guide/content/en/guide/how-to/README.md",
    "content": "# How to ...\n"
  },
  {
    "path": "guide/content/en/guide/how-to/authentication.md",
    "content": "# Authentication\n\n> How do I control authentication and authorization?\n\nThis is an _extremely_ complicated subject to cram into a few snippets. But, this should provide you with an idea on ways to tackle this problem. This example uses [JWTs](https://jwt.io/), but the concepts should be equally applicable to sessions or some other scheme.\n\n## `server.py`\n\n```python\nfrom sanic import Sanic, text\n\nfrom auth import protected\nfrom login import login\n\napp = Sanic(\"AuthApp\")\napp.config.SECRET = \"KEEP_IT_SECRET_KEEP_IT_SAFE\"\napp.blueprint(login)\n\n@app.get(\"/secret\")\n@protected\nasync def secret(request):\n    return text(\"To go fast, you must be fast.\")\n```\n\n## `login.py`\n\n```python\nimport jwt\nfrom sanic import Blueprint, text\n\nlogin = Blueprint(\"login\", url_prefix=\"/login\")\n\n@login.post(\"/\")\nasync def do_login(request):\n    token = jwt.encode({}, request.app.config.SECRET)\n    return text(token)\n```\n\n\n\n## `auth.py`\n\n```python\nfrom functools import wraps\n\nimport jwt\nfrom sanic import text\n\ndef check_token(request):\n    if not request.token:\n        return False\n\n    try:\n        jwt.decode(\n            request.token, request.app.config.SECRET, algorithms=[\"HS256\"]\n        )\n    except jwt.exceptions.InvalidTokenError:\n        return False\n    else:\n        return True\n\ndef protected(wrapped):\n    def decorator(f):\n        @wraps(f)\n        async def decorated_function(request, *args, **kwargs):\n            is_authenticated = check_token(request)\n\n            if is_authenticated:\n                response = await f(request, *args, **kwargs)\n                return response\n            else:\n                return text(\"You are unauthorized.\", 401)\n\n        return decorated_function\n\n    return decorator(wrapped)\n```\nThis decorator pattern is taken from the [decorators page](../best-practices/decorators.md).\n\n---\n\n```bash\n$ curl localhost:9999/secret -i\nHTTP/1.1 401 Unauthorized\ncontent-length: 21\nconnection: keep-alive\ncontent-type: text/plain; charset=utf-8\n\nYou are unauthorized.\n\n\n$ curl localhost:9999/login -X POST\neyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.rjxS7ztIGt5tpiRWS8BGLUqjQFca4QOetHcZTi061DE\n\n\n$ curl localhost:9999/secret -i -H \"Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.rjxS7ztIGt5tpiRWS8BGLUqjQFca4QOetHcZTi061DE\"\nHTTP/1.1 200 OK\ncontent-length: 29\nconnection: keep-alive\ncontent-type: text/plain; charset=utf-8\n\nTo go fast, you must be fast.\n\n\n$ curl localhost:9999/secret -i -H \"Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.BAD\"                                        \nHTTP/1.1 401 Unauthorized\ncontent-length: 21\nconnection: keep-alive\ncontent-type: text/plain; charset=utf-8\n\nYou are unauthorized.\n```\n\nAlso, checkout some resources from the community:\n\n- Awesome Sanic - [Authorization](https://github.com/mekicha/awesome-sanic/blob/master/README.md#authentication) & [Session](https://github.com/mekicha/awesome-sanic/blob/master/README.md#session)\n- [EuroPython 2020 - Overcoming access control in web APIs](https://www.youtube.com/watch?v=Uqgoj43ky6A)\n"
  },
  {
    "path": "guide/content/en/guide/how-to/autodiscovery.md",
    "content": "---\ntitle: Autodiscovery\n---\n\n# Autodiscovery of Blueprints, Middleware, and Listeners\n\n> How do I autodiscover the components I am using to build my application?\n\nOne of the first problems someone faces when building an application, is *how* to structure the project. Sanic makes heavy use of decorators to register route handlers, middleware, and listeners. And, after creating blueprints, they need to be mounted to the application.\n\nA possible solution is a single file in which **everything** is imported and applied to the Sanic instance. Another is passing around the Sanic instance as a global variable. Both of these solutions have their drawbacks.\n\nAn alternative is autodiscovery. You point your application at modules (already imported, or strings), and let it wire everything up.\n\n## `server.py`\n\n```python\nfrom sanic import Sanic\nfrom sanic.response import empty\n\nimport blueprints\nfrom utility import autodiscover\n\napp = Sanic(\"auto\", register=True)\nautodiscover(\n    app,\n    blueprints,\n    \"parent.child\",\n    \"listeners.something\",\n    recursive=True,\n)\n\napp.route(\"/\")(lambda _: empty())\n```\n```bash\n[2021-03-02 21:37:02 +0200] [880451] [INFO] Goin' Fast @ http://127.0.0.1:9999\n[2021-03-02 21:37:02 +0200] [880451] [DEBUG] something\n[2021-03-02 21:37:02 +0200] [880451] [DEBUG] something @ nested\n[2021-03-02 21:37:02 +0200] [880451] [DEBUG] something @ level1\n[2021-03-02 21:37:02 +0200] [880451] [DEBUG] something @ level3\n[2021-03-02 21:37:02 +0200] [880451] [DEBUG] something inside __init__.py\n[2021-03-02 21:37:02 +0200] [880451] [INFO] Starting worker [880451]\n```\n\n## `utility.py`\n\n```python\n\nfrom glob import glob\nfrom importlib import import_module, util\nfrom inspect import getmembers\nfrom pathlib import Path\nfrom types import ModuleType\nfrom typing import Union\n\nfrom sanic.blueprints import Blueprint\n\ndef autodiscover(\n    app, *module_names: Union[str, ModuleType], recursive: bool = False\n):\n    mod = app.__module__\n    blueprints = set()\n    _imported = set()\n\n    def _find_bps(module):\n        nonlocal blueprints\n\n        for _, member in getmembers(module):\n            if isinstance(member, Blueprint):\n                blueprints.add(member)\n\n    for module in module_names:\n        if isinstance(module, str):\n            module = import_module(module, mod)\n            _imported.add(module.__file__)\n        _find_bps(module)\n\n        if recursive:\n            base = Path(module.__file__).parent\n            for path in glob(f\"{base}/**/*.py\", recursive=True):\n                if path not in _imported:\n                    name = \"module\"\n                    if \"__init__\" in path:\n                        *_, name, __ = path.split(\"/\")\n                    spec = util.spec_from_file_location(name, path)\n                    specmod = util.module_from_spec(spec)\n                    _imported.add(path)\n                    spec.loader.exec_module(specmod)\n                    _find_bps(specmod)\n\n    for bp in blueprints:\n        app.blueprint(bp)\n```\n\n## `blueprints/level1.py`\n\n```python\nfrom sanic import Blueprint\nfrom sanic.log import logger\n\nlevel1 = Blueprint(\"level1\")\n\n@level1.after_server_start\ndef print_something(app, loop):\n    logger.debug(\"something @ level1\")\n```\n\n## `blueprints/one/two/level3.py`\n\n```python\nfrom sanic import Blueprint\nfrom sanic.log import logger\n\nlevel3 = Blueprint(\"level3\")\n\n@level3.after_server_start\ndef print_something(app, loop):\n    logger.debug(\"something @ level3\")\n```\n\n## `listeners/something.py`\n\n```python\nfrom sanic import Sanic\nfrom sanic.log import logger\n\napp = Sanic.get_app(\"auto\")\n\n@app.after_server_start\ndef print_something(app, loop):\n    logger.debug(\"something\")\n```\n\n## `parent/child/__init__.py`\n\n```python\nfrom sanic import Blueprint\nfrom sanic.log import logger\n\nbp = Blueprint(\"__init__\")\n\n@bp.after_server_start\ndef print_something(app, loop):\n    logger.debug(\"something inside __init__.py\")\n```\n\n## `parent/child/nested.py`\n\n```python\nfrom sanic import Blueprint\nfrom sanic.log import logger\n\nnested = Blueprint(\"nested\")\n\n@nested.after_server_start\ndef print_something(app, loop):\n    logger.debug(\"something @ nested\")\n```\n\n---\n\n```text\nhere is the dir tree\ngenerate with 'find . -type d -name \"__pycache__\" -exec rm -rf {} +; tree'\n\n. # run 'sanic sever -d' here\n├── blueprints\n│   ├── __init__.py # you need add this file, just empty\n│   ├── level1.py\n│   └── one\n│       └── two\n│           └── level3.py\n├── listeners\n│   └── something.py\n├── parent\n│   └── child\n│       ├── __init__.py\n│       └── nested.py\n├── server.py\n└── utility.py\n```\n\n```sh\nsource ./.venv/bin/activate # activate the python venv which sanic is installed in\nsanic sever -d # run this in the directory containing server.py\n```\n\n```text\nyou will see \"something ***\" like this:\n\n[2023-07-12 11:23:36 +0000] [113704] [DEBUG] something\n[2023-07-12 11:23:36 +0000] [113704] [DEBUG] something inside __init__.py\n[2023-07-12 11:23:36 +0000] [113704] [DEBUG] something @ level3\n[2023-07-12 11:23:36 +0000] [113704] [DEBUG] something @ level1\n[2023-07-12 11:23:36 +0000] [113704] [DEBUG] something @ nested\n```\n\n"
  },
  {
    "path": "guide/content/en/guide/how-to/cors.md",
    "content": "---\ntitle: CORS\n---\n\n# Cross-origin resource sharing (CORS)\n\n> How do I configure my application for CORS?\n\n.. note::\n\n    🏆 The best solution is to use [Sanic Extensions](../../plugins/sanic-ext/http/cors.md). \n    \n    However, if you would like to build your own version, you could use this limited example as a starting point.\n\n### `server.py`\n\n```python\nfrom sanic import Sanic, text\n\nfrom cors import add_cors_headers\nfrom options import setup_options\n\napp = Sanic(\"app\")\n\n@app.route(\"/\", methods=[\"GET\", \"POST\"])\nasync def do_stuff(request):\n    return text(\"...\")\n\n# Add OPTIONS handlers to any route that is missing it\napp.register_listener(setup_options, \"before_server_start\")\n\n# Fill in CORS headers\napp.register_middleware(add_cors_headers, \"response\")\n```\n\n## `cors.py`\n\n```python\nfrom typing import Iterable\n\ndef _add_cors_headers(response, methods: Iterable[str]) -> None:\n    allow_methods = list(set(methods))\n    if \"OPTIONS\" not in allow_methods:\n        allow_methods.append(\"OPTIONS\")\n    headers = {\n        \"Access-Control-Allow-Methods\": \",\".join(allow_methods),\n        \"Access-Control-Allow-Origin\": \"mydomain.com\",\n        \"Access-Control-Allow-Credentials\": \"true\",\n        \"Access-Control-Allow-Headers\": (\n            \"origin, content-type, accept, \"\n            \"authorization, x-xsrf-token, x-request-id\"\n        ),\n    }\n    response.headers.extend(headers)\n\ndef add_cors_headers(request, response):\n    if request.method != \"OPTIONS\":\n        methods = [method for method in request.route.methods]\n        _add_cors_headers(response, methods)\n```\n\n## `options.py`\n\n```python\nfrom collections import defaultdict\nfrom typing import Dict, FrozenSet\n\nfrom sanic import Sanic, response\nfrom sanic.router import Route\n\nfrom cors import _add_cors_headers\n\ndef _compile_routes_needing_options(\n    routes: Dict[str, Route]\n) -> Dict[str, FrozenSet]:\n    needs_options = defaultdict(list)\n    # This is 21.12 and later. You will need to change this for older versions.\n    for route in routes.values():\n        if \"OPTIONS\" not in route.methods:\n            needs_options[route.uri].extend(route.methods)\n\n    return {\n        uri: frozenset(methods) for uri, methods in dict(needs_options).items()\n    }\n\ndef _options_wrapper(handler, methods):\n    def wrapped_handler(request, *args, **kwargs):\n        nonlocal methods\n        return handler(request, methods)\n\n    return wrapped_handler\n\nasync def options_handler(request, methods) -> response.HTTPResponse:\n    resp = response.empty()\n    _add_cors_headers(resp, methods)\n    return resp\n\ndef setup_options(app: Sanic, _):\n    app.router.reset()\n    needs_options = _compile_routes_needing_options(app.router.routes_all)\n    for uri, methods in needs_options.items():\n        app.add_route(\n            _options_wrapper(options_handler, methods),\n            uri,\n            methods=[\"OPTIONS\"],\n        )\n    app.router.finalize()\n```\n\n---\n\n```\n$ curl localhost:9999/ -i\nHTTP/1.1 200 OK\nAccess-Control-Allow-Methods: OPTIONS,POST,GET\nAccess-Control-Allow-Origin: mydomain.com\nAccess-Control-Allow-Credentials: true\nAccess-Control-Allow-Headers: origin, content-type, accept, authorization, x-xsrf-token, x-request-id\ncontent-length: 3\nconnection: keep-alive\ncontent-type: text/plain; charset=utf-8\n\n...\n\n$ curl localhost:9999/ -i -X OPTIONS     \nHTTP/1.1 204 No Content\nAccess-Control-Allow-Methods: GET,POST,OPTIONS\nAccess-Control-Allow-Origin: mydomain.com\nAccess-Control-Allow-Credentials: true\nAccess-Control-Allow-Headers: origin, content-type, accept, authorization, x-xsrf-token, x-request-id\nconnection: keep-alive\n```\nAlso, checkout some resources from the community:\n\n- [Awesome Sanic](https://github.com/mekicha/awesome-sanic/blob/master/README.md#frontend) \n"
  },
  {
    "path": "guide/content/en/guide/how-to/csrf.md",
    "content": "csrf\n"
  },
  {
    "path": "guide/content/en/guide/how-to/db.md",
    "content": "connecting to data sources\n"
  },
  {
    "path": "guide/content/en/guide/how-to/decorators.md",
    "content": "decorators\n"
  },
  {
    "path": "guide/content/en/guide/how-to/ipv6.md",
    "content": ""
  },
  {
    "path": "guide/content/en/guide/how-to/mounting.md",
    "content": "# Application Mounting\n\n> How do I mount my application at some path above the root?\n\n```python\n# server.py\nfrom sanic import Sanic, text\n\napp = Sanic(\"app\")\napp.config.SERVER_NAME = \"example.com/api\"\n\n@app.route(\"/foo\")\ndef handler(request):\n    url = app.url_for(\"handler\", _external=True)\n    return text(f\"URL: {url}\")\n```\n\n```yaml\n# docker-compose.yml\nversion: \"3.7\"\nservices:\n  app:\n    image: nginx:alpine\n    ports:\n      - 80:80\n    volumes:\n      - type: bind\n        source: ./conf\n        target: /etc/nginx/conf.d/default.conf\n```\n\n```nginx\n# conf\nserver {\n    listen 80;\n\n    # Computed data service\n    location /api/ {\n        proxy_pass http://<YOUR IP ADDRESS>:9999/;\n        proxy_set_header Host example.com;\n    }\n}\n```\n```bash\n$ docker-compose up -d\n$ sanic server.app --port=9999 --host=0.0.0.0\n```\n```bash\n$ curl localhost/api/foo\nURL: http://example.com/api/foo\n```\n"
  },
  {
    "path": "guide/content/en/guide/how-to/orm.md",
    "content": "# ORM\n\n>  How do I use SQLAlchemy with Sanic ?\n\nAll ORM tools can work with Sanic, but non-async ORM tool have a impact on Sanic performance.\nThere are some orm packages who support\n\nAt present, there are many ORMs that support Python's `async`/`await` keywords. Some possible choices include：\n\n- [Mayim](https://ahopkins.github.io/mayim/)\n- [SQLAlchemy 1.4](https://docs.sqlalchemy.org/en/14/changelog/changelog_14.html)\n- [tortoise-orm](https://github.com/tortoise/tortoise-orm)\n\nIntegration in to your Sanic application is fairly simple:\n\n## Mayim\n\nMayim ships with [an extension for Sanic Extensions](https://ahopkins.github.io/mayim/guide/extensions.html#sanic), which makes it super simple to get started with Sanic. It is certainly possible to run Mayim with Sanic without the extension, but it is recommended because it handles all of the [lifecycle events](https://sanic.dev/en/guide/basics/listeners.html) and [dependency injections](https://sanic.dev/en/plugins/sanic-ext/injection.html).\n\n.. column::\n\n    ### Dependencies\n\n    First, we need to install the required dependencies. See [Mayim docs](https://ahopkins.github.io/mayim/guide/install.html#postgres) for the installation needed for your DB driver.\n\n.. column::\n\n    ```shell\n    pip install sanic-ext\n    pip install mayim[postgres]\n    ```\n\n\n.. column::\n\n    ### Define ORM Model\n\n    Mayim allows you to use whatever you want for models. Whether it is [dataclasses](https://docs.python.org/3/library/dataclasses.html), [pydantic](https://pydantic-docs.helpmanual.io/), [attrs](https://www.attrs.org/en/stable/), or even just plain `dict` objects. Since it works very nicely [out of the box with Pydantic](https://ahopkins.github.io/mayim/guide/pydantic.html), that is what we will use here.\n\n.. column::\n\n    ```python\n    # ./models.py\n    from pydantic import BaseModel\n\n    class City(BaseModel):\n        id: int\n        name: str\n        district: str\n        population: int\n\n    class Country(BaseModel):\n        code: str\n        name: str\n        continent: str\n        region: str\n        capital: City\n    ```\n\n\n.. column::\n\n    ### Define SQL\n\n    If you are unfamiliar, Mayim is different from other ORMs in that it is one-way, SQL-first. This means you define your own queries either inline, or in a separate `.sql` file, which is what we will do here.\n\n.. column::\n\n    ```sql\n    -- ./queries/select_all_countries.sql\n    SELECT country.code,\n        country.name,\n        country.continent,\n        country.region,\n        (\n            SELECT row_to_json(q)\n            FROM (\n                    SELECT city.id,\n                        city.name,\n                        city.district,\n                        city.population\n                ) q\n        ) capital\n    FROM country\n        JOIN city ON country.capital = city.id\n    ORDER BY country.name ASC\n    LIMIT $limit OFFSET $offset;\n    ```\n\n\n.. column::\n\n    ### Create Sanic App and Async Engine\n\n    We need to create the app instance and attach the `SanicMayimExtension` with any executors.\n\n.. column::\n\n    ```python\n    # ./server.py\n    from sanic import Sanic, Request, json\n    from sanic_ext import Extend\n    from mayim.executor import PostgresExecutor\n    from mayim.extensions import SanicMayimExtension\n    from models import Country\n\n    class CountryExecutor(PostgresExecutor):\n        async def select_all_countries(\n            self, limit: int = 4, offset: int = 0\n        ) -> list[Country]:\n            ...\n\n    app = Sanic(\"Test\")\n    Extend.register(\n        SanicMayimExtension(\n            executors=[CountryExecutor],\n            dsn=\"postgres://...\",\n        )\n    )\n    ```\n\n\n.. column::\n\n    ### Register Routes\n\n    Because we are using Mayim's extension for Sanic, we have the automatic `CountryExecutor` injection into the route handler. It makes for an easy, type-annotated development experience.\n\n.. column::\n\n    ```python\n    @app.get(\"/\")\n    async def handler(request: Request, executor: CountryExecutor):\n        countries = await executor.select_all_countries()\n        return json({\"countries\": [country.dict() for country in co\n    ```\n\n\n.. column::\n\n    ### Send Requests\n\n.. column::\n\n    ```sh\n    curl 'http://127.0.0.1:8000'\n    {\"countries\":[{\"code\":\"AFG\",\"name\":\"Afghanistan\",\"continent\":\"Asia\",\"region\":\"Southern and Central Asia\",\"capital\":{\"id\":1,\"name\":\"Kabul\",\"district\":\"Kabol\",\"population\":1780000}},{\"code\":\"ALB\",\"name\":\"Albania\",\"continent\":\"Europe\",\"region\":\"Southern Europe\",\"capital\":{\"id\":34,\"name\":\"Tirana\",\"district\":\"Tirana\",\"population\":270000}},{\"code\":\"DZA\",\"name\":\"Algeria\",\"continent\":\"Africa\",\"region\":\"Northern Africa\",\"capital\":{\"id\":35,\"name\":\"Alger\",\"district\":\"Alger\",\"population\":2168000}},{\"code\":\"ASM\",\"name\":\"American Samoa\",\"continent\":\"Oceania\",\"region\":\"Polynesia\",\"capital\":{\"id\":54,\"name\":\"Fagatogo\",\"district\":\"Tutuila\",\"population\":2323}}]}\n    ```\n\n\n## SQLAlchemy\n\nBecause [SQLAlchemy 1.4](https://docs.sqlalchemy.org/en/14/changelog/changelog_14.html) has added native support for `asyncio`, Sanic can finally work well with SQLAlchemy. Be aware that this functionality is still considered *beta* by the SQLAlchemy project.\n\n\n.. column::\n\n    ### Dependencies\n\n    First, we need to install the required dependencies. In the past, the dependencies installed were `sqlalchemy` and `pymysql`, but now `sqlalchemy` and `aiomysql` are needed.\n\n.. column::\n\n    ```shell\n    pip install -U sqlalchemy\n    pip install -U aiomysql\n    ```\n\n\n.. column::\n\n    ### Define ORM Model\n\n    ORM model creation remains the same.\n\n.. column::\n\n    ```python\n    # ./models.py\n    from sqlalchemy import INTEGER, Column, ForeignKey, String\n    from sqlalchemy.orm import declarative_base, relationship\n\n    Base = declarative_base()\n\n    class BaseModel(Base):\n        __abstract__ = True\n        id = Column(INTEGER(), primary_key=True)\n\n    class Person(BaseModel):\n        __tablename__ = \"person\"\n        name = Column(String())\n        cars = relationship(\"Car\")\n\n        def to_dict(self):\n            return {\"name\": self.name, \"cars\": [{\"brand\": car.brand} for car in self.cars]}\n\n    class Car(BaseModel):\n        __tablename__ = \"car\"\n\n        brand = Column(String())\n        user_id = Column(ForeignKey(\"person.id\"))\n        user = relationship(\"Person\", back_populates=\"cars\")\n    ```\n\n\n.. column::\n\n    ### Create Sanic App and Async Engine\n\n    Here we use mysql as the database, and you can also choose PostgreSQL/SQLite. Pay attention to changing the driver from `aiomysql` to `asyncpg`/`aiosqlite`.\n\n.. column::\n\n    ```python\n    # ./server.py\n    from sanic import Sanic\n    from sqlalchemy.ext.asyncio import create_async_engine\n\n    app = Sanic(\"my_app\")\n\n    bind = create_async_engine(\"mysql+aiomysql://root:root@localhost/test\", echo=True)\n    ```\n\n\n.. column::\n\n    ### Register Middlewares\n\n    The request middleware creates an usable `AsyncSession` object and set it to `request.ctx` and `_base_model_session_ctx`.\n\n    Thread-safe variable `_base_model_session_ctx` helps you to use the session object instead of fetching it from `request.ctx`.\n\n.. column::\n\n    ```python\n    # ./server.py\n    from contextvars import ContextVar\n\n    from sqlalchemy.ext.asyncio import AsyncSession\n    from sqlalchemy.orm import sessionmaker\n\n    _sessionmaker = sessionmaker(bind, AsyncSession, expire_on_commit=False)\n\n    _base_model_session_ctx = ContextVar(\"session\")\n\n    @app.middleware(\"request\")\n    async def inject_session(request):\n        request.ctx.session = _sessionmaker()\n        request.ctx.session_ctx_token = _base_model_session_ctx.set(request.ctx.session)\n\n    @app.middleware(\"response\")\n    async def close_session(request, response):\n        if hasattr(request.ctx, \"session_ctx_token\"):\n            _base_model_session_ctx.reset(request.ctx.session_ctx_token)\n            await request.ctx.session.close()\n    ```\n\n\n.. column::\n\n    ### Register Routes\n\n    According to sqlalchemy official docs, `session.query` will be legacy in 2.0, and the 2.0 way to query an ORM object is using `select`.\n\n.. column::\n\n    ```python\n    # ./server.py\n    from sqlalchemy import select\n    from sqlalchemy.orm import selectinload\n    from sanic.response import json\n\n    from models import Car, Person\n\n    @app.post(\"/user\")\n    async def create_user(request):\n        session = request.ctx.session\n        async with session.begin():\n            car = Car(brand=\"Tesla\")\n            person = Person(name=\"foo\", cars=[car])\n            session.add_all([person])\n        return json(person.to_dict())\n\n    @app.get(\"/user/<pk:int>\")\n    async def get_user(request, pk):\n        session = request.ctx.session\n        async with session.begin():\n            stmt = select(Person).where(Person.id == pk).options(selectinload(Person.cars))\n            result = await session.execute(stmt)\n            person = result.scalar()\n\n        if not person:\n            return json({})\n\n        return json(person.to_dict())\n    ```\n\n\n.. column::\n\n    ### Send Requests\n\n.. column::\n\n    ```sh\n    curl --location --request POST 'http://127.0.0.1:8000/user'\n    {\"name\":\"foo\",\"cars\":[{\"brand\":\"Tesla\"}]}\n    ```\n\n    ```sh\n    curl --location --request GET 'http://127.0.0.1:8000/user/1'\n    {\"name\":\"foo\",\"cars\":[{\"brand\":\"Tesla\"}]}\n    ```\n\n\n## Tortoise-ORM\n\n.. column::\n\n    ### Dependencies\n\n    tortoise-orm's dependency is very simple, you just need install tortoise-orm.\n\n.. column::\n\n    ```shell\n    pip install -U tortoise-orm\n    ```\n\n\n.. column::\n\n    ### Define ORM Model\n\n    If you are familiar with Django, you should find this part very familiar.\n\n.. column::\n\n    ```python\n    # ./models.py\n    from tortoise import Model, fields\n\n    class Users(Model):\n        id = fields.IntField(pk=True)\n        name = fields.CharField(50)\n\n        def __str__(self):\n            return f\"I am {self.name}\"\n    ```\n\n\n\n.. column::\n\n    ### Create Sanic App and Async Engine\n\n    Tortoise-orm provides a set of registration interface, which is convenient for users, and you can use it to create database connection easily.\n\n.. column::\n\n    ```python\n    # ./main.py\n\n    from models import Users\n    from tortoise.contrib.sanic import register_tortoise\n\n    app = Sanic(__name__)\n\n    register_tortoise(\n        app, db_url=\"mysql://root:root@localhost/test\", modules={\"models\": [\"models\"]}, generate_schemas=True\n    )\n\n    ```\n\n\n.. column::\n\n    ### Register Routes\n\n.. column::\n\n    ```python\n    # ./main.py\n\n    from models import Users\n    from sanic import Sanic, response\n\n    @app.route(\"/user\")\n    async def list_all(request):\n        users = await Users.all()\n        return response.json({\"users\": [str(user) for user in users]})\n\n    @app.route(\"/user/<pk:int>\")\n    async def get_user(request, pk):\n        user = await Users.query(pk=pk)\n        return response.json({\"user\": str(user)})\n\n    if __name__ == \"__main__\":\n        app.run(port=5000)\n    ```\n\n\n.. column::\n\n    ### Send Requests\n\n.. column::\n\n    ```sh\n    curl --location --request POST 'http://127.0.0.1:8000/user'\n    {\"users\":[\"I am foo\", \"I am bar\"]}\n    ```\n\n    ```sh\n    curl --location --request GET 'http://127.0.0.1:8000/user/1'\n    {\"user\": \"I am foo\"}\n    ```\n\n"
  },
  {
    "path": "guide/content/en/guide/how-to/request-id-logging.md",
    "content": ""
  },
  {
    "path": "guide/content/en/guide/how-to/serialization.md",
    "content": "# Serialization\n"
  },
  {
    "path": "guide/content/en/guide/how-to/server-sent-events.md",
    "content": "sse\n"
  },
  {
    "path": "guide/content/en/guide/how-to/static-redirects.md",
    "content": "# \"Static\" Redirects\n\n> How do I configure static redirects?\n\n## `app.py`\n\n```python\n### SETUP ###\nimport typing\nimport sanic, sanic.response\n\n# Create the Sanic app\napp = sanic.Sanic(__name__)\n\n# This dictionary represents your \"static\"\n# redirects. For example, these values\n# could be pulled from a configuration file.\nREDIRECTS = {\n    '/':'/hello_world',                     # Redirect '/' to '/hello_world'\n    '/hello_world':'/hello_world.html'      # Redirect '/hello_world' to 'hello_world.html'\n}\n\n# This function will return another function\n# that will return the configured value\n# regardless of the arguments passed to it.\ndef get_static_function(value:typing.Any) -> typing.Callable[..., typing.Any]:\n    return lambda *_, **__: value\n\n### ROUTING ###\n# Iterate through the redirects\nfor src, dest in REDIRECTS.items():                            \n    # Create the redirect response object         \n    response:sanic.HTTPResponse = sanic.response.redirect(dest)\n\n    # Create the handler function. Typically,\n    # only a sanic.Request object is passed\n    # to the function. This object will be \n    # ignored.\n    handler = get_static_function(response)\n\n    # Route the src path to the handler\n    app.route(src)(handler)\n\n# Route some file and client resources\napp.static('/files/', 'files')\napp.static('/', 'client')\n\n### RUN ###\nif __name__ == '__main__':\n    app.run(\n        '127.0.0.1',\n        10000\n    )\n```\n\n## `client/hello_world.html`\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Hello World</title>\n</head>\n<link rel=\"stylesheet\" href=\"/hello_world.css\">\n<body>\n    <div id='hello_world'>\n        Hello world!\n    </div>\n</body>\n</html>\n```\n\n## `client/hello_world.css`\n\n```css\n#hello_world {\n    width: 1000px;\n    margin-left: auto;\n    margin-right: auto;\n    margin-top: 100px;\n\n    padding: 100px;\n    color: aqua;\n    text-align: center;\n    font-size: 100px;\n    font-family: monospace;\n\n    background-color: rgba(0, 0, 0, 0.75);\n\n    border-radius: 10px;\n    box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.75);\n}\n\nbody {\n    background-image: url(\"/files/grottoes.jpg\");\n    background-repeat: no-repeat;\n    background-size: cover;\n}\n```\n\n## `files/grottoes.jpg`\n\n![lake](/assets/images/grottoes.jpg)\n\n---\n\nAlso, checkout some resources from the community:\n\n- [Static Routing Example](https://github.com/Perzan/sanic-static-routing-example)\n"
  },
  {
    "path": "guide/content/en/guide/how-to/table-of-contents.md",
    "content": "---\ntitle: Table of Contents\n---\n\n# Table of Contents\n\nWe have compiled fully working examples to answer common questions and user cases. For the most part, the examples are as minimal as possible, but should be complete and runnable solutions.\n\n| Page | How do I ... |\n|:-----|:------------|\n| [Application mounting](./mounting.md)       | ... mount my application at some path above the root? |\n| [Authentication](./authentication.md)       | ... control authentication and authorization? |\n| [Autodiscovery](./autodiscovery.md)         | ... autodiscover the components I am using to build my application? |\n| [CORS](./cors.md)                           | ... configure my application for CORS? |\n| [ORM](./orm)                                | ... use an ORM with Sanic? |\n| [\"Static\" Redirects](./static-redirects.md) | ... configure static redirects |\n| [TLS/SSL/HTTPS](./tls.md)                   | ... run Sanic via HTTPS?<br> ... redirect HTTP to HTTPS? |\n"
  },
  {
    "path": "guide/content/en/guide/how-to/task-queue.md",
    "content": "task queue\n"
  },
  {
    "path": "guide/content/en/guide/how-to/tls.md",
    "content": "---\ntitle: TLS/SSL/HTTPS\n---\n\n# TLS/SSL/HTTPS\n\n> How do I run Sanic via HTTPS? \n\nIf you do not have TLS certificates yet, [see the end of this page](./tls.md#get-certificates-for-your-domain-names).\n\n## Single domain and single certificate\n\n.. column::\n\n    Let Sanic automatically load your certificate files, which need to be named `fullchain.pem` and `privkey.pem` in the given folder:\n\n.. column::\n\n    ```sh\n    sudo sanic myserver:app -H :: -p 443 \\\n      --tls /etc/letsencrypt/live/example.com/\n    ```\n    ```python\n    app.run(\"::\", 443, ssl=\"/etc/letsencrypt/live/example.com/\")\n    ```\n\n\n.. column::\n\n    Or, you can pass cert and key filenames separately as a dictionary:\n\n    Additionally, `password` may be added if the key is encrypted, all fields except for the password are passed to `request.conn_info.cert`.\n\n.. column::\n\n    ```python\n    ssl = {\n        \"cert\": \"/path/to/fullchain.pem\",\n        \"key\": \"/path/to/privkey.pem\",\n        \"password\": \"for encrypted privkey file\",   # Optional\n    }\n    app.run(host=\"0.0.0.0\", port=8443, ssl=ssl)\n    ```\n\n\n.. column::\n\n    Alternatively, [`ssl.SSLContext`](https://docs.python.org/3/library/ssl.html) may be passed, if you need full control over details such as which crypto algorithms are permitted. By default Sanic only allows secure algorithms, which may restrict access from very old devices.\n\n.. column::\n\n    ```python\n    import ssl\n\n    context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)\n    context.load_cert_chain(\"certs/fullchain.pem\", \"certs/privkey.pem\")\n\n    app.run(host=\"0.0.0.0\", port=8443, ssl=context)\n    ```\n\n\n## Multiple domains with separate certificates\n\n.. column::\n\n    A list of multiple certificates may be provided, in which case Sanic chooses the one matching the hostname the user is connecting to. This occurs so early in the TLS handshake that Sanic has not sent any packets to the client yet.\n\n    If the client sends no SNI (Server Name Indication), the first certificate on the list will be used even though on the client browser it will likely fail with a TLS error due to name mismatch. To prevent this fallback and to cause immediate disconnection of clients without a known hostname, add `None` as the first entry on the list. `--tls-strict-host` is the equivalent CLI option.\n\n.. column::\n\n    ```python\n    ssl = [\"certs/example.com/\", \"certs/bigcorp.test/\"]\n    app.run(host=\"0.0.0.0\", port=8443, ssl=ssl)\n    ```\n    ```sh\n    sanic myserver:app\n        --tls certs/example.com/\n        --tls certs/bigcorp.test/\n        --tls-strict-host\n    ```\n\n.. tip:: \n\n    You may also use `None` in front of a single certificate if you do not wish to reveal your certificate, true hostname or site content to anyone connecting to the IP address instead of the proper DNS name.\n\n.. column::\n\n    Dictionaries can be used on the list. This allows also specifying which domains a certificate matches to, although the names present on the certificate itself cannot be controlled from here. If names are not specified, the names from the certificate itself are used.\n\n    To only allow connections to the main domain **example.com** and only to subdomains of **bigcorp.test**:\n\n.. column::\n\n    ```python\n    ssl = [\n        None,  # No fallback if names do not match!\n        {\n            \"cert\": \"certs/example.com/fullchain.pem\",\n            \"key\": \"certs/example.com/privkey.pem\",\n            \"names\": [\"example.com\", \"*.bigcorp.test\"],\n        }\n    ]\n    app.run(host=\"0.0.0.0\", port=8443, ssl=ssl)\n    ```\n\n## Accessing TLS information in handlers via `request.conn_info` fields\n\n* `.ssl` - is the connection secure (bool)\n* `.cert` - certificate info and dict fields of the currently active cert (dict)\n* `.server_name` - the SNI sent by the client (str, may be empty)\n\nDo note that all `conn_info` fields are per connection, where there may be many requests over time. If a proxy is used in front of your server, these requests on the same pipe may even come from different users.\n\n## Redirect HTTP to HTTPS, with certificate requests still over HTTP\n\nIn addition to your normal server(s) running HTTPS, run another server for redirection, `http_redir.py`:\n\n```python\nfrom sanic import Sanic, exceptions, response\n\napp = Sanic(\"http_redir\")\n\n# Serve ACME/certbot files without HTTPS, for certificate renewals\napp.static(\"/.well-known\", \"/var/www/.well-known\", resource_type=\"dir\")\n\n@app.exception(exceptions.NotFound, exceptions.MethodNotSupported)\ndef redirect_everything_else(request, exception):\n    server, path = request.server_name, request.path\n    if server and path.startswith(\"/\"):\n        return response.redirect(f\"https://{server}{path}\", status=308)\n    return response.text(\"Bad Request. Please use HTTPS!\", status=400)\n```\n\nIt is best to setup this as a systemd unit separate of your HTTPS servers. You may need to run HTTP while initially requesting your certificates, while you cannot run the HTTPS server yet. Start for IPv4 and IPv6:\n\n```\nsanic http_redir:app -H 0.0.0.0 -p 80\nsanic http_redir:app -H :: -p 80\n```\n\nAlternatively, it is possible to run the HTTP redirect application from the main application:\n\n```python\n# app == Your main application\n# redirect == Your http_redir application\n@app.before_server_start\nasync def start(app, _):\n    app.ctx.redirect = await redirect.create_server(\n        port=80, return_asyncio_server=True\n    )\n    app.add_task(runner(redirect, app.ctx.redirect))\n\n@app.before_server_stop\nasync def stop(app, _):\n    await app.ctx.redirect.close()\n\nasync def runner(app, app_server):\n    app.state.is_running = True\n    try:\n        app.signalize()\n        app.finalize()\n        app.state.is_started = True\n        await app_server.serve_forever()\n    finally:\n        app.state.is_running = False\n        app.state.is_stopping = True\n```\n\n## Get certificates for your domain names\n\nYou can get free certificates from [Let's Encrypt](https://letsencrypt.org/). Install [certbot](https://certbot.eff.org/) via your package manager, and request a certificate:\n\n```sh\nsudo certbot certonly --key-type ecdsa --preferred-chain \"ISRG Root X1\" -d example.com -d www.example.com\n```\n\nMultiple domain names may be added by further `-d` arguments, all stored into a single certificate which gets saved to `/etc/letsencrypt/live/example.com/` as per **the first domain** that you list here.\n\nThe key type and preferred chain options are necessary for getting a minimal size certificate file, essential for making your server run as *fast* as possible. The chain will still contain one RSA certificate until when Let's Encrypt gets their new EC chain trusted in all major browsers.\n"
  },
  {
    "path": "guide/content/en/guide/how-to/validation.md",
    "content": "validation\n"
  },
  {
    "path": "guide/content/en/guide/how-to/websocket-feed.md",
    "content": "websocket feed\n"
  },
  {
    "path": "guide/content/en/guide/introduction.md",
    "content": "# Introduction\n\nSanic is a Python 3.10+ web server and web framework that's written to go fast. It allows the usage of the async/await syntax added in Python 3.5, which makes your code non-blocking and speedy.\n\n.. attrs::\n    :class: introduction-table\n\n    |  |  |\n    |--|--|\n    | Build    | [![Tests](https://github.com/sanic-org/sanic/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/sanic-org/sanic/actions/workflows/tests.yml) |\n    | Docs     | [![User Guide](https://img.shields.io/badge/user%20guide-sanic-ff0068)](https://sanicframework.org/) [![Documentation](https://readthedocs.org/projects/sanic/badge/?version=latest)](http://sanic.readthedocs.io/en/latest/?badge=latest) |\n    | Package  | [![PyPI](https://img.shields.io/pypi/v/sanic.svg)](https://pypi.python.org/pypi/sanic/) [![PyPI version](https://img.shields.io/pypi/pyversions/sanic.svg)](https://pypi.python.org/pypi/sanic/) [![Wheel](https://img.shields.io/pypi/wheel/sanic.svg)](https://pypi.python.org/pypi/sanic) [![Supported implementations](https://img.shields.io/pypi/implementation/sanic.svg)](https://pypi.python.org/pypi/sanic) [![Code style black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) |\n    | Support  | [![Forums](https://img.shields.io/badge/forums-community-ff0068.svg)](https://community.sanicframework.org/) [![Discord](https://img.shields.io/discord/812221182594121728?logo=discord)](https://discord.gg/FARQzAEMAA) [![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/mekicha/awesome-sanic) |\n    | Stats    | [![Monthly Downloads](https://img.shields.io/pypi/dm/sanic.svg)](https://pepy.tech/project/sanic) [![Weekly Downloads](https://img.shields.io/pypi/dw/sanic.svg)](https://pepy.tech/project/sanic) [![Conda downloads](https://img.shields.io/conda/dn/conda-forge/sanic.svg)](https://anaconda.org/conda-forge/sanic) |\n\n\n\n## What is it?\n\nFirst things first, before you jump in the water, you should know that Sanic is different than other frameworks.\n\nRight there in that first sentence there is a huge mistake because Sanic is _both_ a **framework** and a **web server**. In the deployment section we will talk a little bit more about this. \n\nBut, remember, out of the box Sanic comes with everything you need to write, deploy, and scale a production grade web application. 🚀\n\n## Goal\n\n> To provide a simple way to get up and running a highly performant HTTP server that is easy to build, to expand, and ultimately to scale.\n## Features\n\n.. column::\n\n    ### Core\n\n    - Built in, **_fast_** web server\n    - Production ready\n    - Highly scalable\n    - ASGI compliant\n    - Simple and intuitive API design\n    - By the community, for the community\n\n.. column::\n\n    ### Sanic Extensions [[learn more](../plugins/sanic-ext/getting-started.md)]\n\n    - CORS protection\n    - Template rendering with Jinja\n    - Dependency injection into route handlers\n    - OpenAPI documentation with Redoc and/or Swagger\n    - Predefined, endpoint-specific response serializers\n    - Request query arguments and body input validation\n    - Auto create `HEAD`, `OPTIONS`, and `TRACE` endpoints\n\n## Sponsor\n\nCheck out [open collective](https://opencollective.com/sanic-org) to learn more about helping to fund Sanic.\n\n\n## Join the Community\n\nThe main channel for discussion is at the [community forums](https://community.sanicframework.org/). There also is a [Discord Server](https://discord.gg/FARQzAEMAA) for live discussion and chat.\n\nThe Stackoverflow `[sanic]` tag is [actively monitored](https://stackoverflow.com/questions/tagged/sanic) by project maintainers.\n\n## Contribution\n\nWe are always happy to have new contributions. We have [marked issues good for anyone looking to get started](https://github.com/sanic-org/sanic/issues?q=is%3Aopen+is%3Aissue+label%3Abeginner), and welcome [questions/answers/discussion on the forums](https://community.sanicframework.org/). Please take a look at our [Contribution guidelines](https://github.com/sanic-org/sanic/blob/master/CONTRIBUTING.rst).\n"
  },
  {
    "path": "guide/content/en/guide/running/app-loader.md",
    "content": "---\ntitle: Dynamic Applications\n---\n\n# Dynamic Applications\n\nRunning Sanic has been optimized to work with the CLI. If you have not read it yet, you should read [Running Sanic](./running.md#sanic-server) to become familiar with the options. \n\n.. column::\n\n    This includes running it as a global scope object...\n\n.. column::\n\n    ```sh\n    sanic path.to.server:app\n    ```\n    ```python\n    # server.py\n    app = Sanic(\"TestApp\")\n\n    @app.get(\"/\")\n    async def handler(request: Request):\n        return json({\"foo\": \"bar\"})\n    ```\n\n\n\n.. column::\n\n    ...or, a factory function that creates the `Sanic` application object.\n\n.. column::\n\n    ```sh\n    sanic path.to.server:create_app --factory\n    ```\n    ```python\n    # server.py\n    def create_app():\n        app = Sanic(\"TestApp\")\n\n        @app.get(\"/\")\n        async def handler(request: Request):\n            return json({\"foo\": \"bar\"})\n\n        return app\n    ```\n\n\n**Sometimes, this is not enough ... 🤔**\n\nIntroduced in [v22.9](../../release-notes/2022/v22.9.md), Sanic has an `AppLoader` object that is responsible for creating an application in the various [worker processes](./manager.md#how-sanic-server-starts-processes). You can take advantage of this if you need to create a more dynamic startup experience for your application.\n\n.. column::\n\n    An `AppLoader` can be passed a callable that returns a `Sanic` instance. That `AppLoader` could be used with the low-level application running API.\n\n.. column::\n\n    ```python\n    import sys\n    from functools import partial\n\n    from sanic import Request, Sanic, json\n    from sanic.worker.loader import AppLoader\n\n    def attach_endpoints(app: Sanic):\n        @app.get(\"/\")\n        async def handler(request: Request):\n            return json({\"app_name\": request.app.name})\n\n    def create_app(app_name: str) -> Sanic:\n        app = Sanic(app_name)\n        attach_endpoints(app)\n        return app\n\n    if __name__ == \"__main__\":\n        app_name = sys.argv[-1]\n        loader = AppLoader(factory=partial(create_app, app_name))\n        app = loader.load()\n        app.prepare(port=9999, dev=True)\n        Sanic.serve(primary=app, app_loader=loader)\n    ```\n    ```sh\n    python path/to/server.py MyTestAppName\n    ```\n\nIn the above example, the `AppLoader` is created with a `factory` that can be used to create copies of the same application across processes. When doing this, you should explicitly use the `Sanic.serve` pattern shown above so that the `AppLoader` that you create is not replaced.\n"
  },
  {
    "path": "guide/content/en/guide/running/configuration.md",
    "content": "# Configuration\n\n## Basics\n\n\n.. column::\n\n    Sanic holds the configuration in the config attribute of the application object. The configuration object is merely an object that can be modified either using dot-notation or like a dictionary.\n\n.. column::\n\n    ```python\n    app = Sanic(\"myapp\")\n    app.config.DB_NAME = \"appdb\"\n    app.config[\"DB_USER\"] = \"appuser\"\n    ```\n\n\n.. column::\n\n    You can also use the `update()` method like on regular dictionaries.\n\n.. column::\n\n    ```python\n    db_settings = {\n        'DB_HOST': 'localhost',\n        'DB_NAME': 'appdb',\n        'DB_USER': 'appuser'\n    }\n    app.config.update(db_settings)\n    ```\n\n\n\n.. note:: \n\n    It is standard practice in Sanic to name your config values in **uppercase letters**. Indeed, you may experience weird behaviors if you start mixing uppercase and lowercase names.\n\n\n\n## Loading\n\n### Environment variables\n\n.. column::\n\n    Any environment variables defined with the `SANIC_` prefix will be applied to the Sanic config. For example, setting `SANIC_REQUEST_TIMEOUT` will be loaded by the application automatically and fed into the `REQUEST_TIMEOUT` config variable.\n\n.. column::\n\n    ```bash\n    $ export SANIC_REQUEST_TIMEOUT=10\n    ```\n    ```python\n    >>> print(app.config.REQUEST_TIMEOUT)\n    10\n    ```\n\n\n.. column::\n\n    You can change the prefix that Sanic is expecting at startup.\n\n.. column::\n\n    ```bash\n    $ export MYAPP_REQUEST_TIMEOUT=10\n    ```\n    ```python\n    >>> app = Sanic(__name__, env_prefix='MYAPP_')\n    >>> print(app.config.REQUEST_TIMEOUT)\n    10\n    ```\n\n\n.. column::\n\n    You can also disable environment variable loading completely.\n\n.. column::\n\n    ```python\n    app = Sanic(__name__, load_env=False)\n    ```\n\n### Using Sanic.update_config\n\nThe `Sanic` instance has a _very_ versatile method for loading config: `app.update_config`. You can feed it a path to a file, a dictionary, a class, or just about any other sort of object.\n\n#### From a file\n\n.. column::\n\n    Let's say you have `my_config.py` file that looks like this.\n\n.. column::\n\n    ```python\n    # my_config.py\n    A = 1\n    B = 2\n    ```\n\n\n.. column::\n\n    You can load this as config values by passing its path to `app.update_config`.\n\n.. column::\n\n    ```python\n    >>> app.update_config(\"/path/to/my_config.py\")\n    >>> print(app.config.A)\n    1\n    ```\n\n\n.. column::\n\n    This path also accepts bash style environment variables.\n\n.. column::\n\n    ```bash\n    $ export my_path=\"/path/to\"\n    ```\n    ```python\n    app.update_config(\"${my_path}/my_config.py\")\n    ```\n\n\n\n.. note:: \n    \n    Just remember that you have to provide environment variables in the format `${environment_variable}` and that `$environment_variable` is not expanded (is treated as \"plain\" text).\n\n\n\n#### From a dict\n\n.. column::\n\n    The `app.update_config` method also works on plain dictionaries.\n\n.. column::\n\n    ```python\n    app.update_config({\"A\": 1, \"B\": 2})\n    ```\n\n#### From a class or object\n\n.. column::\n\n    You can define your own config class, and pass it to `app.update_config`\n\n.. column::\n\n    ```python\n    class MyConfig:\n        A = 1\n        B = 2\n\n    app.update_config(MyConfig)\n    ```\n\n\n.. column::\n\n    It even could be instantiated.\n\n.. column::\n\n    ```python\n    app.update_config(MyConfig())\n    ```\n\n### Type casting\n\nWhen loading from environment variables, Sanic will attempt to cast the values to expected Python types. This particularly applies to:\n\n- `int`\n- `float`\n- `bool`\n\nIn regards to `bool`, the following _case insensitive_ values are allowed:\n\n- **`True`**: `y`, `yes`, `yep`, `yup`, `t`, `true`, `on`, `enable`, `enabled`, `1`\n- **`False`**: `n`, `no`, `f`, `nope`, `false`, `off`, `disable`, `disabled`, `0`\n\nIf a value cannot be cast, it will default to a `str`.\n\n.. column::\n\n    Additionally, Sanic can be configured to cast additional types using additional type converters. This should be any callable that returns the value or raises a `ValueError`.\n\n    *Added in v21.12*\n\n.. column::\n\n    ```python\n    app = Sanic(..., config=Config(converters=[UUID]))\n    ```\n\n#### Advanced Type Converters\n\n.. new:: New in v25.12\n\n    This feature was added in version 25.12\n\n.. column::\n\n    For more sophisticated conversion logic that needs access to the full environment variable context, you can use `DetailedConverter`. This abstract base class provides access to the full environment variable key, the raw value, and the current config defaults.\n\n    This is useful when you need to:\n    - Cast values to the type of their defaults\n    - Perform validation based on the variable name pattern\n    - Use default values for fallback logic\n    - Access configuration context during conversion\n\n.. column::\n\n    ```python\n    from sanic.config import DetailedConverter\n\n    class DefaultsTypeCastingConverter(DetailedConverter):\n        def __call__(self, full_key: str, config_key: str, value: str, defaults: dict):\n            try:\n                if config_key in defaults:\n                    return type(defaults[config_key])(value)\n            except (ValueError, TypeError) as e:\n                raise TypeError(f\"Configuration environment variable '{full_key}' type mismatch: expected\"\n                                f\" {type(defaults[config_key]).__name__}, got {type(value).__name__}\") from e\n\n    app = Sanic(..., config=Config(converters=[DefaultsTypeCastingConverter()]))\n    ```\n\n.. column::\n\n    The `DetailedConverter.__call__` method receives four parameters:\n\n    - `full_key`: The full environment variable name with prefix (e.g., \"SANIC_DATABASE_URL\")\n    - `config_key`: The config key without prefix (e.g., \"DATABASE_URL\")\n    - `value`: The raw string value from the environment\n    - `defaults`: The current default configuration values\n\n.. column::\n\n    ```python\n    class ValidationConverter(DetailedConverter):\n        def __call__(self, full_key: str, config_key: str, value: str, defaults: dict):\n            if config_key.endswith('_PORT'):\n                port = int(value)\n                if not 1 <= port <= 65535:\n                    raise ValueError(f\"Invalid port: {port}\")\n                return port\n            raise ValueError  # Let other converters handle it\n    ```\n\n## Builtin values\n\n| **Variable**              | **Default**      | **Description**                                                                                                                       |\n|---------------------------|------------------|---------------------------------------------------------------------------------------------------------------------------------------|\n| ACCESS_LOG                | True             | Disable or enable access log                                                                                                          |\n| AUTO_EXTEND               | True             | Control whether [Sanic Extensions](../../plugins/sanic-ext/getting-started.md) will load if it is in the existing virtual environment |\n| AUTO_RELOAD               | True             | Control whether the application will automatically reload when a file changes                                                         |\n| EVENT_AUTOREGISTER        | True             | When `True` using the `app.event()` method on a non-existing signal will automatically create it and not raise an exception           |\n| FALLBACK_ERROR_FORMAT     | html             | Format of error response if an exception is not caught and handled                                                                    |\n| FORWARDED_FOR_HEADER      | X-Forwarded-For  | The name of \"X-Forwarded-For\" HTTP header that contains client and proxy ip                                                           |\n| FORWARDED_SECRET          | None             | Used to securely identify a specific proxy server (see below)                                                                         |\n| GRACEFUL_SHUTDOWN_TIMEOUT | 15.0             | How long to wait to force close non-idle connection (sec)                                                                             |\n| INSPECTOR                 | False            | Whether to enable the Inspector                                                                                                       |\n| INSPECTOR_HOST            | localhost        | The host for the Inspector                                                                                                            |\n| INSPECTOR_PORT            | 6457             | The port for the Inspector                                                                                                            |\n| INSPECTOR_TLS_KEY         | -                | The TLS key for the Inspector                                                                                                         |\n| INSPECTOR_TLS_CERT        | -                | The TLS certificate for the Inspector                                                                                                 |\n| INSPECTOR_API_KEY         | -                | The API key for the Inspector                                                                                                         |\n| KEEP_ALIVE_TIMEOUT        | 120              | How long to hold a TCP connection open (sec)                                                                                          |\n| KEEP_ALIVE                | True             | Disables keep-alive when False                                                                                                        |\n| MOTD                      | True             | Whether to display the MOTD (message of the day) at startup                                                                           |\n| MOTD_DISPLAY              | {}               | Key/value pairs to display additional, arbitrary data in the MOTD                                                                     |\n| NOISY_EXCEPTIONS          | False            | Force all `quiet` exceptions to be logged                                                                                             |\n| PROXIES_COUNT             | None             | The number of proxy servers in front of the app (e.g. nginx; see below)                                                               |\n| REAL_IP_HEADER            | None             | The name of \"X-Real-IP\" HTTP header that contains real client ip                                                                      |\n| REGISTER                  | True             | Whether the app registry should be enabled                                                                                            |\n| REQUEST_BUFFER_SIZE       | 65536            | Request buffer size before request is paused, default is 64 Kib                                                                       |\n| REQUEST_ID_HEADER         | X-Request-ID     | The name of \"X-Request-ID\" HTTP header that contains request/correlation ID                                                           |\n| REQUEST_MAX_SIZE          | 100000000        | How big a request may be (bytes), default is 100 megabytes                                                                            |\n| REQUEST_MAX_HEADER_SIZE   | 8192            | How big a request header may be (bytes), default is 8192 bytes                                                                         |\n| REQUEST_TIMEOUT           | 60               | How long a request can take to arrive (sec)                                                                                           |\n| RESPONSE_TIMEOUT          | 60               | How long a response can take to process (sec)                                                                                         |\n| USE_UVLOOP                | True             | Whether to override the loop policy to use `uvloop`. Supported only with `app.run`.                                                   |\n| WEBSOCKET_MAX_SIZE        | 2^20             | Maximum size for incoming messages (bytes)                                                                                            |\n| WEBSOCKET_PING_INTERVAL   | 20               | A Ping frame is sent every ping_interval seconds.                                                                                     |\n| WEBSOCKET_PING_TIMEOUT    | 20               | Connection is closed when Pong is not received after ping_timeout seconds                                                             |\n\n\n.. tip:: FYI\n\n    - The `USE_UVLOOP` value will be ignored if running with Gunicorn. Defaults to `False` on non-supported platforms (Windows).\n    - The `WEBSOCKET_` values will be ignored if in ASGI mode.\n    - v21.12 added: `AUTO_EXTEND`, `MOTD`, `MOTD_DISPLAY`, `NOISY_EXCEPTIONS`\n    - v22.9 added: `INSPECTOR`\n    - v22.12 added: `INSPECTOR_HOST`, `INSPECTOR_PORT`, `INSPECTOR_TLS_KEY`, `INSPECTOR_TLS_CERT`, `INSPECTOR_API_KEY`\n\n\n## Timeouts\n\n### REQUEST_TIMEOUT\n\nA request timeout measures the duration of time between the instant when a new open TCP connection is passed to the\nSanic backend server, and the instant when the whole HTTP request is received. If the time taken exceeds the\n`REQUEST_TIMEOUT` value (in seconds), this is considered a Client Error so Sanic generates an `HTTP 408` response\nand sends that to the client. Set this parameter's value higher if your clients routinely pass very large request payloads\nor upload requests very slowly.\n\n### RESPONSE_TIMEOUT\n\nA response timeout measures the duration of time between the instant the Sanic server passes the HTTP request to the Sanic App, and the instant a HTTP response is sent to the client. If the time taken exceeds the `RESPONSE_TIMEOUT` value (in seconds), this is considered a Server Error so Sanic generates an `HTTP 503` response and sends that to the client. Set this parameter's value higher if your application is likely to have long-running process that delay the\ngeneration of a response.\n\n### KEEP_ALIVE_TIMEOUT\n\n#### What is Keep Alive? And what does the Keep Alive Timeout value do?\n\n`Keep-Alive` is a HTTP feature introduced in `HTTP 1.1`. When sending a HTTP request, the client (usually a web browser application) can set a `Keep-Alive` header to indicate the http server (Sanic) to not close the TCP connection after it has send the response. This allows the client to reuse the existing TCP connection to send subsequent HTTP requests, and ensures more efficient network traffic for both the client and the server.\n\nThe `KEEP_ALIVE` config variable is set to `True` in Sanic by default. If you don't need this feature in your application, set it to `False` to cause all client connections to close immediately after a response is sent, regardless of the `Keep-Alive` header on the request.\n\nThe amount of time the server holds the TCP connection open is decided by the server itself. In Sanic, that value is configured using the `KEEP_ALIVE_TIMEOUT` value. By default, **it is set to 120 seconds**. This means that if the client sends a `Keep-Alive` header, the server will hold the TCP connection open for 120 seconds after sending the response, and the client can reuse the connection to send another HTTP request within that time.\n\nFor reference:\n\n* Apache httpd server default keepalive timeout = 5 seconds\n* Nginx server default keepalive timeout = 75 seconds\n* Nginx performance tuning guidelines uses keepalive = 15 seconds\n* Caddy server default keepalive timeout = 120 seconds\n* IE (5-9) client hard keepalive limit = 60 seconds\n* Firefox client hard keepalive limit = 115 seconds\n* Opera 11 client hard keepalive limit = 120 seconds\n* Chrome 13+ client keepalive limit > 300+ seconds\n\n## Proxy configuration\n\nSee [proxy configuration section](../advanced/proxy-headers.md)\n"
  },
  {
    "path": "guide/content/en/guide/running/development.md",
    "content": "# Development\n\nThe first thing that should be mentioned is that the webserver that is integrated into Sanic is **not** just a development server. \n\nIt is production ready out-of-the-box, *unless you enable in debug mode*.\n\n## Debug mode\n\nBy setting the debug mode, Sanic will be more verbose in its output and will disable several run-time optimizations.\n\n```python\n# server.py\nfrom sanic import Sanic\nfrom sanic.response import json\n\napp = Sanic(__name__)\n\n@app.route(\"/\")\nasync def hello_world(request):\n    return json({\"hello\": \"world\"})\n```\n```sh\nsanic server:app --host=0.0.0.0 --port=1234 --debug\n```\n\n\n.. danger:: \n    \n    Sanic's debug mode will slow down the server's performance, and is **NOT** intended for production environments.\n\n    **DO NOT** enable debug mode in production.\n\n\n\n## Automatic Reloader\n\n.. column::\n\n    Sanic offers a way to enable or disable the Automatic Reloader. The easiest way to enable it is using the CLI's `--reload` argument to activate the Automatic Reloader. Every time a Python file is changed, the reloader will restart your application automatically. This is very convenient while developing.\n\n    .. note:: \n    \n        The reloader is only available when using Sanic's [worker manager](./manager.md). If you have disabled it using `--single-process` then the reloader will not be available to you.\n\n.. column::\n\n    ```sh\n    sanic path.to:app --reload\n    ```\n    You can also use the shorthand property\n    ```sh\n    sanic path.to:app -r\n    ```\n\n\n.. column::\n\n    If you have additional directories that you would like to automatically reload on file save (for example, a directory of HTML templates), you can add that using `--reload-dir`.\n\n.. column::\n\n    ```sh\n    sanic path.to:app --reload --reload-dir=/path/to/templates\n    ```\n    Or multiple directories, shown here using the shorthand properties\n    ```sh\n    sanic path.to:app -r -R /path/to/one -R /path/to/two\n    ```\n\n\n## Development REPL\n\nThe Sanic CLI comes with a REPL (aka \"read-eval-print loop\") that can be used to interact with your application. This is useful for debugging and testing. A REPL is the interactive shell that you get when you run `python` without any arguments.\n\n.. column::\n\n    You can start the REPL by passing the `--repl` argument to the Sanic CLI.\n\n.. column::\n\n    ```sh\n    sanic path.to.server:app --repl\n    ```\n\n.. column::\n\n    Or, perhaps more conveniently, when you run `--dev`, Sanic will automatically start the REPL for you. However, in this case you might be prompted to hit the \"ENTER\" key before actually starting the REPL.\n\n.. column::\n\n    ```sh\n    sanic path.to.server:app --dev\n    ```\n\n![](/assets/images/repl.png)\n\nAs seen in the screenshot above, the REPL will automatically add a few variables to the global namespace. These are:\n\n- `app` - The Sanic application instance. This is the same instance that is passed to the `sanic` CLI.\n- `sanic` - The `sanic` module. This is the same module that is imported when you run `import sanic`.\n- `do` - A function that will create a mock `Request` object and pass it to your application. This is useful for testing your application from the REPL.\n- `client` - An instance of `httpx.Client` that is configured to make requests to your application. This is useful for testing your application from the REPL. **Note:** This is only available if `httpx` is installed in your environment.\n\n### Async/Await support\n\n.. column::\n\n    The REPL supports `async`/`await` syntax. This means that you can use `await` in the REPL to wait for asynchronous operations to complete. This is useful for testing asynchronous code.\n\n.. column::\n\n    ```python\n    >>> await app.ctx.db.fetchval(\"SELECT 1\")\n    1 \n    ```\n\n### The `app` variable\n\nYou need to keep in mind that the `app` variable is your app instance as it existed when the REPL was started. It is the instance that is loaded when running the CLI command. This means that any changes that are made to your source code and subsequently reloaded in the workers will not be reflected in the `app` variable. If you want to interact with the reloaded app instance, you will need to restart the REPL.\n\nHowever, it is also very useful to have access to the original app instance in the REPL for adhoc testing and debugging.\n\n### The `client` variable\n\nWhen [httpx](https://www.python-httpx.org/) is installed in your environment, the `client` variable will be available in the REPL. This is an instance of `httpx.Client` that is configured to make requests to your running application.\n\n.. column::\n\n    To use it, simply call one of the HTTP methods on the client. See the [httpx documentation](https://www.python-httpx.org/api/#client) for more information.\n\n.. column::\n\n    ```python\n    >>> client.get(\"/\")\n    <Response [200 OK]>\n    ```\n\n### The `do` function\n\nAs discussed above, the `app` instance exists as it did at the time the REPL was started, and as was modified inside the REPL. Any changes to the instance that cause a server to be reloaded will not be reflected in the `app` variable. This is where the `do` function comes in.\n\nLet's say that you have modified your application inside the REPL to add a new route:\n\n```python\n>>> @app.get(\"/new-route\")\n... async def new_route(request):\n...     return sanic.json({\"hello\": \"world\"})\n...\n>>>\n```\n\nYou can use the `do` function to mock out a request, and pass it to the application as if it were a real HTTP request. This will allow you to test your new route without having to restart the REPL.\n\n```python\n>>> await do(\"/new-route\")\nResult(request=<Request: GET /new-route>, response=<JSONResponse: 200 application/json>)\n```\n\nThe `do` function returns a `Result` object that contains the `Request` and `Response` objects that were returned by your application. It is a `NamedTuple`, so you can access the values by name:\n\n```python\n>>> result = await do(\"/new-route\")\n>>> result.request\n<Request: GET /new-route>\n>>> result.response\n<JSONResponse: 200 application/json>\n```\n\nOr, by destructuring the tuple:\n\n```python\n>>> request, response = await do(\"/new-route\")\n>>> request\n<Request: GET /new-route>\n>>> response\n<JSONResponse: 200 application/json>\n```\n\n### When to use `do` vs `client`?\n\n.. column::\n\n    **Use `do` when ...**\n\n    - You want to test a route that does not exist in the running application\n    - You want to test a route that has been modified in the REPL\n    - You make a change to your application inside the REPL\n\n.. column::\n\n    **Use `client` when ...**\n\n    - You want to test a route that already exists in the running application\n    - You want to test a route that has been modified in your source code\n    - You want to send an actual HTTP request to your application\n\n*Added in v23.12*\n\n## Complete development mode\n\n.. column::\n\n    If you would like to be in debug mode **and** have the Automatic Reloader running, you can pass `dev=True`. This is equivalent to **debug + auto reload + REPL**.\n\n    *Added in v22.3*\n\n.. column::\n\n    ```sh\n    sanic path.to:app --dev\n    ```\n    You can also use the shorthand property\n    ```sh\n    sanic path.to:app -d\n    ```\n\nAdded to the `--dev` flag in v23.12 is the ability to start a REPL. See the [Development REPL](./development.md#development-repl) section for more information.\n    \nAs of v23.12, the `--dev` flag is roughly equivalent to `--debug --reload --repl`. Using `--dev` will require you to expressly begin the REPL by hitting \"ENTER\", while passing the `--repl` flag explicitly starts it.\nBefore v23.12, the `--dev` flag is more similar to `--debug --reload`.\n\t\n.. column::\n\n    If you would like to disable the REPL while using the `--dev` flag, you can pass `--no-repl`.\n\n.. column::\n\n    ```sh\n    sanic path.to:app --dev --no-repl\n    ```\n\n## Automatic TLS certificate\n\nWhen running in `DEBUG` mode, you can ask Sanic to handle setting up localhost temporary TLS certificates. This is helpful if you want to access your local development environment with `https://`.\n\nThis functionality is provided by either [mkcert](https://github.com/FiloSottile/mkcert) or [trustme](https://github.com/python-trio/trustme). Both are good choices, but there are some differences. `trustme` is a Python library and can be installed into your environment with `pip`. This makes for easy envrionment handling, but it is not compatible when running a HTTP/3 server. `mkcert` might be a more involved installation process, but can install a local CA and make it easier to use.\n\n.. column::\n\n    You can choose which platform to use by setting `config.LOCAL_CERT_CREATOR`. When set to `\"auto\"`, it will select either option, preferring `mkcert` if possible.\n\n.. column::\n\n    ```python\n    app.config.LOCAL_CERT_CREATOR = \"auto\"\n    app.config.LOCAL_CERT_CREATOR = \"mkcert\"\n    app.config.LOCAL_CERT_CREATOR = \"trustme\"\n    ```\n\n.. column::\n\n    Automatic TLS can be enabled at Sanic server run time:\n\n.. column::\n\n    ```sh\n    sanic path.to.server:app --auto-tls --debug\n    ```\n\n.. warning:: \n\n    Localhost TLS certificates (like those generated by both `mkcert` and `trustme`) are **NOT** suitable for production environments. If you are not familiar with how to obtain a *real* TLS certificate, checkout the [How to...](../how-to/tls.md) section.\n\n*Added in v22.6*\n"
  },
  {
    "path": "guide/content/en/guide/running/inspector.md",
    "content": "# Inspector\n\nThe Sanic Inspector is a feature of Sanic Server. It is *only* available when running Sanic with the built-in [worker manager](./manager.md).\n\nIt is an HTTP application that *optionally* runs in the background of your application to allow you to interact with the running instance of your application.\n\n\n.. tip:: INFO\n\n    The Inspector was introduced in limited capacity in v22.9, but the documentation on this page assumes you are using v22.12 or higher.\n\n\n## Getting Started\n\nThe inspector is disabled by default. To enable it, you have two options.\n\n.. column::\n\n    Set a flag when creating your application instance.\n\n.. column::\n\n    ```python\n    app = Sanic(\"TestApp\", inspector=True)\n    ```\n\n\n.. column::\n\n    Or, set a configuration value.\n\n.. column::\n\n    ```python\n    app = Sanic(\"TestApp\")\n    app.config.INSPECTOR = True\n    ```\n\n\n\n.. warning:: \n\n    If you are using the configuration value, it *must* be done early and before the main worker process starts. This means that it should either be an environment variable, or it should be set shortly after creating the application instance as shown above.\n\n\n\n## Using the Inspector\n\nOnce the inspector is running, you will have access to it via the CLI or by directly accessing its web API via HTTP.\n\n.. column::\n\n    **Via CLI**\n    ```sh\n    sanic inspect\n    ```\n\n.. column::\n\n    **Via HTTP**\n    ```sh\n    curl http://localhost:6457\n    ```\n\n\n\n.. note:: \n\n    Remember, the Inspector is not running on your Sanic application. It is a seperate process, with a seperate application, and exposed on a seperate socket.\n\n\n\n## Built-in Commands\n\nThe Inspector comes with the following built-in commands. \n\n| CLI Command        | HTTP Action                        | Description                                                              |\n|--------------------|------------------------------------|--------------------------------------------------------------------------|\n| `inspect`          | `GET /`                            | Display basic details about the running application.                     |\n| `inspect reload`   | `POST /reload`                     | Trigger a reload of all server workers.                                  |\n| `inspect shutdown` | `POST /shutdown`                   | Trigger a shutdown of all processes.                                     |\n| `inspect scale N`  | `POST /scale`<br>`{\"replicas\": N}` | Scale the number of workers. Where `N` is the target number of replicas. |\n\n## Custom Commands\n\nThe Inspector is easily extendable to add custom commands (and endpoints).\n\n.. column::\n\n    Subclass the `Inspector` class and create arbitrary methods. As long as the method name is not preceded by an underscore (`_`), then the name of the method will be a new subcommand on the inspector.\n\n.. column::\n\n    ```python\n    from sanic import json\n    from sanic.worker.inspector import Inspector\n\n    class MyInspector(Inspector):\n        async def something(self, *args, **kwargs):\n            print(args)\n            print(kwargs)\n\n    app = Sanic(\"TestApp\", inspector_class=MyInspector, inspector=True)\n    ```\n\nThis will expose custom methods in the general pattern:\n\n- CLI: `sanic inspect <method_name>`\n- HTTP: `POST /<method_name>`\n\nIt is important to note that the arguments that the new method accepts are derived from how you intend to use the command. For example, the above `something` method accepts all positional and keyword based parameters.\n\n.. column::\n\n    In the CLI, the positional and keyword parameters are passed as either positional or keyword arguments to your method. All values will be a `str` with the following exceptions:\n\n    - A keyword parameter with no assigned value will be: `True`\n    - Unless the parameter is prefixed with `no-`, then it will be: `False`\n\n.. column::\n\n    ```sh\n    sanic inspect something one two three --four --no-five --six=6\n    ```\n    In your application log console, you will see:\n    ```\n    ('one', 'two', 'three')\n    {'four': True, 'five': False, 'six': '6'}\n    ```\n\n\n.. column::\n\n    The same can be achieved by hitting the API directly. You can pass arguments to the method by exposing them in a JSON payload. The only thing to note is that the positional arguments should be exposed as `{\"args\": [...]}`.\n\n.. column::\n\n    ```sh\n    curl http://localhost:6457/something \\\n      --json '{\"args\":[\"one\", \"two\", \"three\"], \"four\":true, \"five\":false, \"six\":6}'\n    ```\n    In your application log console, you will see:\n    ```\n    ('one', 'two', 'three')\n    {'four': True, 'five': False, 'six': 6}\n    ```\n\n\n## Using in production\n\n\n.. danger:: \n\n    Before exposing the Inspector on a product, please consider all of the options in this section carefully.\n\n\nWhen running Inspector on a remote production instance, you can protect the endpoints by requiring TLS encryption, and requiring API key authentication.\n\n### TLS encryption\n\n.. column::\n\n    To the Inspector HTTP instance over TLS, pass the paths to your certificate and key.\n\n.. column::\n\n    ```python\n    app.config.INSPECTOR_TLS_CERT = \"/path/to/cert.pem\"\n    app.config.INSPECTOR_TLS_KEY = \"/path/to/key.pem\"\n    ```\n\n\n.. column::\n\n    This will require use of the `--secure` flag, or `https://`.\n\n.. column::\n\n    ```sh\n    sanic inspect --secure --host=<somewhere>\n    ```\n    ```sh\n    curl https://<somewhere>:6457\n    ```\n\n### API Key Authentication\n\n.. column::\n\n    You can secure the API with bearer token authentication.\n\n.. column::\n\n    ```python\n    app.config.INSPECTOR_API_KEY = \"Super-Secret-200\"\n    ```\n\n\n.. column::\n\n    This will require the `--api-key` parameter, or bearer token authorization header.\n\n.. column::\n\n    ```sh\n    sanic inspect --api-key=Super-Secret-200\n    ```\n    ```sh\n    curl http://localhost:6457  -H \"Authorization: Bearer Super-Secret-200\"\n    ```\n\n## Configuration\n\nSee [configuration](./configuration.md)\n"
  },
  {
    "path": "guide/content/en/guide/running/manager.md",
    "content": "---\ntitle: Worker Manager\n---\n\n# Worker Manager\n\nThe worker manager and its functionality was introduced in version 22.9.\n\n*The details of this section are intended for more advanced usages and **not** necessary to get started.*\n\nThe purpose of the manager is to create consistency and flexibility between development and production environments. Whether you intend to run a single worker, or multiple workers, whether with, or without auto-reload: the experience will be the same.\n\nIn general it looks like this:\n\n![](https://user-images.githubusercontent.com/166269/178677618-3b4089c3-6c6a-4ecc-8d7a-7eba2a7f29b0.png)\n\nWhen you run Sanic, the main process instantiates a `WorkerManager`. That manager is in charge of running one or more `WorkerProcess`. There generally are two kinds of processes:\n\n- server processes, and\n- non-server processes.\n\nFor the sake of ease, the User Guide generally will use the term \"worker\" or \"worker process\" to mean a server process, and \"Manager\" to mean the single worker manager running in your main process.\n\n## How Sanic Server starts processes\n\nSanic will start processes using the [spawn](https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods) start method. This means that for every process/worker, the global scope of your application will be run on its own thread. The practical impact of this that *if* you do not run Sanic with the CLI, you will need to nest the execution code inside a block to make sure it only runs on `__main__`.\n\n```python\nif __name__ == \"__main__\":\n    app.run()\n```\n\nIf you do not, you are likely to see an error message like this:\n\n```\nsanic.exceptions.ServerError: Sanic server could not start: [Errno 98] Address already in use.\n\nThis may have happened if you are running Sanic in the global scope and not inside of a `if __name__ == \"__main__\"` block.\n\nSee more information: https://sanic.dev/en/guide/deployment/manager.html#how-sanic-server-starts-processes\n```\n\nThe likely fix for this problem is nesting your Sanic run call inside of the `__name__ == \"__main__\"` block. If you continue to receive this message after nesting, or if you see this while using the CLI, then it means the port you are trying to use is not available on your machine and you must select another port.\n\n### Starting a worker\n\nAll worker processes *must* send an acknowledgement when starting. This happens under the hood, and you as a developer do not need to do anything. However, the Manager will exit with a status code `1` if one or more workers do not send that `ack` message, or a worker process throws an exception while trying to start. If no exceptions are encountered, the Manager will wait for up to thirty (30) seconds for the acknowledgement.\n\n.. column::\n\n    In the situation when you know that you will need more time to start, you can monkeypatch the Manager. The threshold does not include anything inside of a listener, and is limited to the execution time of everything in the global scope of your application.\n\n    If you run into this issue, it may indicate a need to look deeper into what is causing the slow startup.\n\n.. column::\n\n    ```python\n    from sanic.worker.manager import WorkerManager\n\n    WorkerManager.THRESHOLD = 100  # Value is in 0.1s\n    ```\n\nSee [worker ack](#worker-ack) for more information.\n\n.. column::\n\n    As stated above, Sanic will use [spawn](https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods) to start worker processes. If you would like to change this behavior and are aware of the implications of using different start methods, you can modify as shown here.\n\n.. column::\n\n    ```python\n    from sanic import Sanic\n\n    Sanic.start_method = \"fork\"\n    ```\n\n\n### Worker ack\n\nWhen all of your workers are running in a subprocess a potential problem is created: deadlock. This can occur when the child processes cease to function, but the main process is unaware that this happened. Therefore, Sanic servers will automatically send an `ack` message (short for acknowledge) to the main process after startup.\n\nIn version 22.9, the `ack` timeout was short and limited to `5s`. In version 22.12, the timeout was lengthened to `30s`. If your application is shutting down after thirty seconds then it might be necessary to manually increase this threshhold.\n\n.. column::\n\n    The value of `WorkerManager.THRESHOLD` is in `0.1s` increments. Therefore, to set it to one minute, you should set the value to `600`.\n\n    This value should be set as early as possible in your application, and should ideally happen in the global scope.  Setting it after the main process has started will not work.\n\n.. column::\n\n    ```python\n    from sanic.worker.manager import WorkerManager\n\n    WorkerManager.THRESHOLD = 600\n    ```\n\n### Zero downtime restarts\n\nBy default, when restarting workers, Sanic will teardown the existing process first before starting a new one. \n\nIf you are intending to use the restart functionality in production then you may be interested in having zero-downtime reloading. This can be accomplished by forcing the reloader to change the order to start a new process, wait for it to [ack](#worker-ack), and then teardown the old process.\n\n.. column::\n\n    From the multiplexer, use the `zero_downtime` argument\n\n.. column::\n\n    ```python\n    app.m.restart(zero_downtime=True)\n    ```\n\n*Added in v22.12*\n\n## Using shared context between worker processes\n\nPython provides a few methods for [exchanging objects](https://docs.python.org/3/library/multiprocessing.html#exchanging-objects-between-processes), [synchronizing](https://docs.python.org/3/library/multiprocessing.html#synchronization-between-processes), and [sharing state](https://docs.python.org/3/library/multiprocessing.html#sharing-state-between-processes) between processes. This usually involves objects from the `multiprocessing` and `ctypes` modules.\n\nIf you are familiar with these objects and how to work with them, you will be happy to know that Sanic provides an API for sharing these objects between your worker processes. If you are not familiar, you are encouraged to read through the Python documentation linked above and try some of the examples before proceeding with implementing shared context.\n\nSimilar to how [application context](../basics/app.md#application-context) allows an applicaiton to share state across the lifetime of the application with `app.ctx`, shared context provides the same for the special objects mentioned above. This context is available as `app.shared_ctx` and should **ONLY** be used to share objects intended for this purpose.\n\nThe `shared_ctx` will:\n\n- *NOT* share regular objects like `int`, `dict`, or `list`\n- *NOT* share state between Sanic instances running on different machines\n- *NOT* share state to non-worker processes\n- **only** share state between server workers managed by the same Manager\n\nAttaching an inappropriate object to `shared_ctx` will likely result in a warning, and not an error. You should be careful to not accidentally add an unsafe object to `shared_ctx` as it may not work as expected. If you are directed here because of one of those warnings, you might have accidentally used an unsafe object in `shared_ctx`.\n\n.. column::\n\n    In order to create a shared object you **must** create it in the main process and attach it inside of the `main_process_start` listener.\n\n.. column::\n\n    ```python\n    from multiprocessing import Queue\n\n    @app.main_process_start\n    async def main_process_start(app):\n        app.shared_ctx.queue = Queue()\n    ```\n\nTrying to attach to the `shared_ctx` object outside of this listener may result in a `RuntimeError`.\n\n.. column::\n\n    After creating the objects in the `main_process_start` listener and attaching to the `shared_ctx`, they will be available in your workers wherever the application instance is available (example: listeners, middleware, request handlers).\n\n.. column::\n\n    ```python\n    from multiprocessing import Queue\n\n    @app.get(\"\")\n    async def handler(request):\n        request.app.shared_ctx.queue.put(1)\n        ...\n    ```\n\n## Access to the multiplexer\n\nThe application instance has access to an object that provides access to interacting with the Manager and other worker processes. The object is attached as the `app.multiplexer` property, but it is more easily accessed by its alias: `app.m`.\n\n.. column::\n\n    For example, you can get access to the current worker state.\n\n.. column::\n\n    ```python\n    @app.on_request\n    async def print_state(request: Request):\n        print(request.app.m.name)\n        print(request.app.m.pid)\n        print(request.app.m.state)\n    ```\n    ```\n    Sanic-Server-0-0\n    99999\n    {'server': True, 'state': 'ACKED', 'pid': 99999, 'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc), 'starts': 2, 'restart_at': datetime.datetime(2022, 10, 1, 0, 0, 12, 861332, tzinfo=datetime.timezone.utc)}\n    ```\n\n\n.. column::\n\n    The `multiplexer` also has access to terminate the Manager, or restart worker processes\n\n.. column::\n\n    ```python\n    # shutdown the entire application and all processes\n    app.m.name.terminate()\n\n    # restart the current worker only\n    app.m.name.restart()\n\n    # restart specific workers only (comma delimited)\n    app.m.name.restart(\"Sanic-Server-4-0,Sanic-Server-7-0\")\n\n    # restart ALL workers\n    app.m.name.restart(all_workers=True)  # Available v22.12+\n    ```\n\n## Worker state\n\n.. column::\n\n    As shown above, the `multiplexer` has access to report upon the state of the current running worker. However, it also contains the state for ALL processes running.\n\n.. column::\n\n    ```python\n    @app.on_request\n    async def print_state(request: Request):\n        print(request.app.m.workers)\n    ```\n    ```\n    {\n        'Sanic-Main': {'pid': 99997},\n        'Sanic-Server-0-0': {\n            'server': True,\n            'state': 'ACKED',\n            'pid': 9999,\n            'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc),\n            'starts': 2,\n            'restart_at': datetime.datetime(2022, 10, 1, 0, 0, 12, 861332, tzinfo=datetime.timezone.utc)\n        },\n        'Sanic-Reloader-0': {\n            'server': False,\n            'state': 'STARTED',\n            'pid': 99998,\n            'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc),\n            'starts': 1\n        }\n    }\n    ```\n\nThe possible states are:\n\n- `NONE` - The worker has been created, but there is no process yet\n- `IDLE` - The process has been created, but is not running yet\n- `STARTING` - The process is starting\n- `STARTED` - The process has started\n- `ACKED` - The process has started and sent an acknowledgement (usually only for server processes)\n- `JOINED` - The process has exited and joined the main process\n- `TERMINATED` - The process has exited and terminated\n- `RESTARTING` - The process is restarting\n- `FAILED` - The process encountered an exception and is no longer running\n- `COMPLETED` - The process has completed its work and exited successfully\n\n## Built-in non-server processes\n\nAs mentioned, the Manager also has the ability to run non-server processes. Sanic comes with two built-in types of non-server processes, and allows for [creating custom processes](#running-custom-processes).\n\nThe two built-in processes are\n\n- the [auto-reloader](./development.md#automatic-reloader), optionally enabled to watch the file system for changes and trigger a restart\n- [inspector](#inspector), optionally enabled to provide external access to the state of the running instance\n\n## Inspector\n\nSanic has the ability to expose the state and the functionality of the `multiplexer` to the CLI. Currently, this requires the CLI command to be run on the same machine as the running Sanic instance. By default the inspector is disabled.\n\n.. column::\n\n    To enable it, set the config value to `True`.\n\n.. column::\n\n    ```python\n    app.config.INSPECTOR = True\n    ```\n\nYou will now have access to execute any of these CLI commands:\n\n```\nsanic inspect reload                      Trigger a reload of the server workers\nsanic inspect shutdown                    Shutdown the application and all processes\nsanic inspect scale N                     Scale the number of workers to N\nsanic inspect <custom>                    Run a custom command\n```\n\n![](https://user-images.githubusercontent.com/166269/190099384-2f2f3fae-22d5-4529-b279-8446f6b5f9bd.png)\n\n.. column::\n\n    This works by exposing a small HTTP service on your machine. You can control the location using configuration values:\n\n.. column::\n\n    ```python\n    app.config.INSPECTOR_HOST =  \"localhost\"\n    app.config.INSPECTOR_PORT =  6457\n    ```\n\n[Learn more](./inspector.md) to find out what is possible with the Inspector.\n\n## Running custom processes\n\nTo run a managed custom process on Sanic, you must create a callable. If that process is meant to be long-running, then it should handle a shutdown call by a `SIGINT` or `SIGTERM` signal.\n\n.. column::\n\n    The simplest method for doing that in Python will be to just wrap your loop in `KeyboardInterrupt`.\n\n    If you intend to run another application, like a bot, then it is likely that it already has capability to handle this signal and you likely do not need to do anything.\n\n.. column::\n\n    ```python\n    from time import sleep\n\n    def my_process(foo):\n        try:\n            while True:\n                sleep(1)\n        except KeyboardInterrupt:\n            print(\"done\")\n    ```\n\n\n.. column::\n\n    That callable must be registered in the `main_process_ready` listener. It is important to note that is is **NOT** the same location that you should register [shared context](#using-shared-context-between-worker-processes) objects.\n\n.. column::\n\n    ```python\n    @app.main_process_ready\n    async def ready(app: Sanic, _):\n    #   app.manager.manage(<name>, <callable>, <kwargs>)\n        app.manager.manage(\"MyProcess\", my_process, {\"foo\": \"bar\"})\n    ```\n\n### Transient v. durable processes\n\n.. column::\n\n    When you manage a process with the `manage` method, you have the option to make it transient or durable. A transient process will be restarted by the auto-reloader, and a durable process will not.\n    \n    By default, all processes are durable.\n    \n.. column::\n\n    ```python\n    @app.main_process_ready\n    async def ready(app: Sanic, _):\n        app.manager.manage(\n            \"MyProcess\",\n            my_process,\n            {\"foo\": \"bar\"},\n            transient=True,\n        )\n    ```\n\n\n### Tracked v. untracked processes\n\nOut of the box, Sanic will track the state of all processes. This means that you can access the state of the process from the [multiplexer](./manager.md#access-to-the-multiplexer) object, or from the [Inspector](./manager.md#inspector).\n\nSee [worker state](./manager.md#worker-state) for more information.\n    \nSometimes it is helpful to run background processes that are not long-running. You run them once until completion and then they exit. Upon completion, they will either be in `FAILED` or `COMPLETED` state.\n    \n.. column::\n\n    When you are running a non-long-running process, you can opt out of tracking it by setting `tracked=False` in the `manage` method. This means that upon completion of the process it will be removed from the list of tracked processes. You will only be able to check the state of the process while it is running.\n    \n.. column::\n\n    ```python\n    @app.main_process_ready\n    async def ready(app: Sanic, _):\n        app.manager.manage(\n            \"OneAndDone\",\n            do_once,\n            {},\n            tracked=False,\n        )\n    ```\n\n*Added in v23.12*\n\n### Restartable custom processes\n\nA custom process that is transient will **always** be restartable. That means the auto-restart will work as expected. However, what if you want to be able to *manually* restart a process, but not have it be restarted by the auto-reloader?\n    \n.. column::\n\n    In this scenario, you can set `restartable=True` in the `manage` method. This will allow you to manually restart the process, but it will not be restarted by the auto-reloader.\n    \n.. column::\n\n    ```python\n    @app.main_process_ready\n    async def ready(app: Sanic, _):\n        app.manager.manage(\n            \"MyProcess\",\n            my_process,\n            {\"foo\": \"bar\"},\n            restartable=True,\n        )\n    ```\n    \n.. column::\n\n    You could now manually restart that process from the multiplexer.\n    \n.. column::\n\n    ```python\n    @app.get(\"/restart\")\n    async def restart_handler(request: Request):\n        request.app.m.restart(\"Sanic-MyProcess-0\")\n        return json({\"foo\": request.app.m.name})\n    ```\n    \n*Added in v23.12*\n\n### On the fly process management\n\nCustom processes are usually added in the `main_process_ready` listener. However, there may be times when you want to add a process after the application has started. For example, you may want to add a process from a request handler. The multiplexer provides a method for doing this.\n    \n.. column::\n\n    Once you have a reference to the multiplexer, you can call `manage` to add a process. It works the same as the `manage` method on the Manager.\n    \n.. column::\n\n    ```python\n    @app.post(\"/start\")\n    async def start_handler(request: Request):\n        request.app.m.manage(\n            \"MyProcess\",\n            my_process,\n            {\"foo\": \"bar\"},\n            workers=2,\n        )\n        return json({\"foo\": request.app.m.name})\n    ```\n    \n*Added in v23.12*\n\n## Single process mode\n\n.. column::\n\n    If you would like to opt out of running multiple processes, you can run Sanic in a single process only. In this case, the Manager will not run. You will also not have access to any features that require processes (auto-reload, the inspector, etc).\n\n.. column::\n\n    ```sh\n    sanic path.to.server:app --single-process\n    ```\n    ```python\n    if __name__ == \"__main__\":\n        app.run(single_process=True)\n    ```\n    ```python\n    if __name__ == \"__main__\":\n        app.prepare(single_process=True)\n        Sanic.serve_single()\n    ```\n\n## Sanic and multiprocessing\n\nSanic makes heavy use of the [`multiprocessing` module](https://docs.python.org/3/library/multiprocessing.html) to manage the worker processes. You should generally avoid lower level usage of this module (like setting the start method) as it may interfere with the functionality of Sanic.\n\n### Start methods in Python\n\nBefore explaining what Sanic tries to do, it is important to understand what the `start_method` is and why it is important. Python generally allows for three different methods of starting a process:\n\n- `fork`\n- `spawn`\n- `forkserver`\n\nThe `fork` and `forkserver` methods are only available on Unix systems, and `spawn` is the only method available on Windows. On Unix systems where you have a choice, `fork` is generally the default system method.\n\nYou are encouraged to read the [Python documentation](https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods) to learn more about the differences between these methods. However, the important thing to know is that `fork` basically copies the entire memory of the parent process into the child process, whereas `spawn` will create a new process and then load the application into that process. This is the reason why you need to nest your Sanic `run` call inside of the `__name__ == \"__main__\"` block if you are not using the CLI.\n\n### Sanic and start methods\n\nBy default, Sanic will try and use `spawn` as the start method. This is because it is the only method available on Windows, and it is the safest method on Unix systems. \n\n.. column::\n\n    However, if you are running Sanic on a Unix system and you would like to use `fork` instead, you can do so by setting the `start_method` on the `Sanic` class. You will want to do this as early as possible in your application, and ideally in the global scope before you import any other modules.\n\n.. column::\n\n    ```python\n    from sanic import Sanic\n    \n    Sanic.start_method = \"fork\"\n    ```\n\n### Overcoming a `RuntimeError`\n\nYou might have received a `RuntimeError` that looks like this:\n\n```\nRuntimeError: Start method 'spawn' was requested, but 'fork' was already set.\n```\n\nIf so, that means somewhere in your application you are trying to set the start method that conflicts with what Sanic is trying to do. You have a few options to resolve this:\n\n.. column::\n\n    **OPTION 1:** You can tell Sanic that the start method has been set and to not try and set it again.\n\n.. column::\n\n    ```python\n    from sanic import Sanic\n\n    Sanic.START_METHOD_SET = True\n    ```\n\n.. column::\n\n    **OPTION 2:** You could tell Sanic that you intend to use `fork` and to not try and set it to `spawn`.\n\n.. column::\n\n    ```python\n    from sanic import Sanic\n\n    Sanic.start_method = \"fork\"\n    ```\n\n.. column::\n\n    **OPTION 3:** You can tell Python to use `spawn` instead of `fork` by setting the `multiprocessing` start method.\n\n.. column::\n\n    ```python\n    import multiprocessing\n\n    multiprocessing.set_start_method(\"spawn\")\n    ```\n\nIn any of these options, you should run this code as early as possible in your application. Depending upon exactly what your specific scenario is, you may need to combine some of the options.\n\n.. note::\n\n    The potential issues that arise from this problem are usually easily solved by just allowing Sanic to be in charge of multiprocessing. This usually means making use of the `main_process_start` and `main_process_ready` listeners to deal with multiprocessing issues. For example, you should move instantiating multiprocessing primitives that do a lot of work under the hood from the global scope and into a listener.\n    \n    ```python\n    # This is BAD; avoid the global scope\n    from multiprocessing import Queue\n    \n    q = Queue()\n    ```\n    \n    ```python\n    # This is GOOD; the queue is made in a listener and shared to all the processes on the shared_ctx\n    from multiprocessing import Queue\n\n    @app.main_process_start\n    async def main_process_start(app):\n        app.shared_ctx.q = Queue()\n    ```\n"
  },
  {
    "path": "guide/content/en/guide/running/running.md",
    "content": "---\ntitle: Running Sanic\n---\n\n# Running Sanic\n\nSanic ships with its own internal web server. Under most circumstances, this is the preferred method for deployment. In addition, you can also deploy Sanic as an ASGI app bundled with an ASGI-able web server.\n\n## Sanic Server\n\nThe main way to run Sanic is to use the included [CLI](#sanic-cli).\n\n```sh\nsanic path.to.server:app\n```\n\nIn this example, Sanic is instructed to look for a python module called `path.to.server`. Inside of that module, it will look for a global variable called `app`, which should be an instance of `Sanic(...)`.\n\n```python\n# ./path/to/server.py\nfrom sanic import Sanic, Request, json\n\napp = Sanic(\"TestApp\")\n\n@app.get(\"/\")\nasync def handler(request: Request):\n    return json({\"foo\": \"bar\"})\n```\n\nYou may also dropdown to the [lower level API](#low-level-apprun) to call `app.run` as a script. However, if you choose this option you should be more comfortable handling issues that may arise with `multiprocessing`.\n\n### Workers\n\n.. column::\n\n    By default, Sanic runs a main process and a single worker process (see [worker manager](./manager.md) for more details).\n\n    To crank up the juice, just specify the number of workers in the run arguments.\n\n.. column::\n\n    ```sh\n    sanic server:app --host=0.0.0.0 --port=1337 --workers=4\n    ```\n\nSanic will automatically spin up multiple processes and route traffic between them. We recommend as many workers as you have available processors.\n\n.. column::\n\n    The easiest way to get the maximum CPU performance is to use the `--fast` option. This will automatically run the maximum number of workers given the system constraints.\n\n    *Added in v21.12*\n\n.. column::\n\n    ```sh\n    sanic server:app --host=0.0.0.0 --port=1337 --fast\n    ```\n\nIn version 22.9, Sanic introduced a new worker manager to provide more consistency and flexibility between development and production servers. Read [about the manager](./manager.md) for more details about workers.\n\n.. column::\n\n    If you only want to run Sanic with a single process, specify `single_process` in the run arguments. This means that auto-reload, and the worker manager will be unavailable.\n\n    *Added in v22.9*\n\n.. column::\n\n    ```sh\n    sanic server:app --host=0.0.0.0 --port=1337 --single-process\n    ```\n\n### Running via command\n\n#### Sanic CLI\n\nUse `sanic --help` to see all the options.\n\n\n.. attrs::\n    :title: Sanic CLI help output\n    :class: details\n\n    ```text\n    $ sanic --help\n\n       ▄███ █████ ██      ▄█▄      ██       █   █   ▄██████████\n      ██                 █   █     █ ██     █   █  ██\n       ▀███████ ███▄    ▀     █    █   ██   ▄   █  ██\n                   ██  █████████   █     ██ █   █  ▄▄\n      ████ ████████▀  █         █  █       ██   █   ▀██ ███████\n\n     To start running a Sanic application, provide a path to the module, where\n     app is a Sanic() instance:\n\n         $ sanic path.to.server:app\n\n     Or, a path to a callable that returns a Sanic() instance:\n\n         $ sanic path.to.factory:create_app --factory\n\n     Or, a path to a directory to run as a simple HTTP server:\n\n         $ sanic ./path/to/static --simple\n\n    Required\n    ========\n      Positional:\n        module              Path to your Sanic app. Example: path.to.server:app\n                            If running a Simple Server, path to directory to serve. Example: ./\n\n    Optional\n    ========\n      General:\n        -h, --help          show this help message and exit\n        --version           show program's version number and exit\n\n      Application:\n        --factory           Treat app as an application factory, i.e. a () -> <Sanic app> callable\n        -s, --simple        Run Sanic as a Simple Server, and serve the contents of a directory\n                            (module arg should be a path)\n        --inspect           Inspect the state of a running instance, human readable\n        --inspect-raw       Inspect the state of a running instance, JSON output\n        --trigger-reload    Trigger worker processes to reload\n        --trigger-shutdown  Trigger all processes to shutdown\n\n      HTTP version:\n        --http {1,3}        Which HTTP version to use: HTTP/1.1 or HTTP/3. Value should\n                            be either 1, or 3. [default 1]\n        -1                  Run Sanic server using HTTP/1.1\n        -3                  Run Sanic server using HTTP/3\n\n      Socket binding:\n        -H HOST, --host HOST\n                            Host address [default 127.0.0.1]\n        -p PORT, --port PORT\n                            Port to serve on [default 8000]\n        -u UNIX, --unix UNIX\n                            location of unix socket\n\n      TLS certificate:\n        --cert CERT         Location of fullchain.pem, bundle.crt or equivalent\n        --key KEY           Location of privkey.pem or equivalent .key file\n        --tls DIR           TLS certificate folder with fullchain.pem and privkey.pem\n                            May be specified multiple times to choose multiple certificates\n        --tls-strict-host   Only allow clients that send an SNI matching server certs\n\n      Worker:\n        -w WORKERS, --workers WORKERS\n                            Number of worker processes [default 1]\n        --fast              Set the number of workers to max allowed\n        --single-process    Do not use multiprocessing, run server in a single process\n        --legacy            Use the legacy server manager\n        --access-logs       Display access logs\n        --no-access-logs    No display access logs\n\n      Development:\n        --debug             Run the server in debug mode\n        -r, --reload, --auto-reload\n                            Watch source directory for file changes and reload on changes\n        -R PATH, --reload-dir PATH\n                            Extra directories to watch and reload on changes\n        -d, --dev           debug + auto reload\n        --auto-tls          Create a temporary TLS certificate for local development (requires mkcert or trustme)\n\n      Output:\n        --coffee            Uhm, coffee?\n        --no-coffee         No uhm, coffee?\n        --motd              Show the startup display\n        --no-motd           No show the startup display\n        -v, --verbosity     Control logging noise, eg. -vv or --verbosity=2 [default 0]\n        --noisy-exceptions  Output stack traces for all exceptions\n        --no-noisy-exceptions\n                            No output stack traces for all exceptions\n\n    ```\n\n\n#### As a module\n\n.. column::\n\n    Sanic applications can also be called directly as a module.\n\n.. column::\n\n    ```bash\n    python -m sanic server.app --host=0.0.0.0 --port=1337 --workers=4\n    ```\n\n#### Using a factory\n\nA very common solution is to develop your application *not* as a global variable, but instead using the factory pattern. In this context, \"factory\" means a function that returns an instance of `Sanic(...)`.\n\n.. column::\n\n    Suppose that you have this in your `server.py`\n\n.. column::\n\n    ```python\n    from sanic import Sanic\n\n    def create_app() -> Sanic:\n        app = Sanic(\"MyApp\")\n\n        return app\n    ```\n\n\n.. column::\n\n    You can run this application now by referencing it in the CLI explicitly as a factory:\n\n.. column::\n\n    ```sh\n    sanic server:create_app --factory\n    ```\n    Or, explicitly like this:\n    ```sh\n    sanic \"server:create_app()\"\n    ```\n    Or, implicitly like this:\n    ```sh\n    sanic server:create_app\n    ```\n\n    *Implicit command added in v23.3*\n\n### Low level `app.run`\n\nWhen using `app.run` you will just call your Python file like any other script.\n\n.. column::\n\n    `app.run` must be properly nested inside of a name-main block.\n\n.. column::\n\n    ```python\n    # server.py\n    app = Sanic(\"MyApp\")\n\n    if __name__ == \"__main__\":\n        app.run()\n    ```\n\n\n\n.. danger:: \n\n    Be *careful* when using this pattern. A very common mistake is to put too much logic inside of the `if __name__ == \"__main__\":` block.\n\n    🚫 This is a mistake\n\n    ```python\n    from sanic import Sanic\n    from my.other.module import bp\n\n    app = Sanic(\"MyApp\")\n\n    if __name__ == \"__main__\":\n        app.blueprint(bp)\n        app.run()\n    ```\n\n    If you do this, your [blueprint](../best-practices/blueprints.md) will not be attached to your application. This is because the `__main__` block will only run on Sanic's main worker process, **NOT** any of its [worker processes](../deployment/manager.md). This goes for anything else that might impact your application (like attaching listeners, signals, middleware, etc). The only safe operations are anything that is meant for the main process, like the `app.main_*` listeners.\n\n    Perhaps something like this is more appropriate:\n\n    ```python\n    from sanic import Sanic\n    from my.other.module import bp\n\n    app = Sanic(\"MyApp\")\n\n    if __name__ == \"__mp_main__\":\n        app.blueprint(bp)\n    elif __name__ == \"__main__\":\n        app.run()\n    ```\n\n\nTo use the low-level `run` API, after defining an instance of `sanic.Sanic`, we can call the run method with the following keyword arguments:\n\n|       Parameter       |     Default      |                                           Description                                     |\n| :-------------------: | :--------------: | :---------------------------------------------------------------------------------------- |\n|  **host**             | `\"127.0.0.1\"`    | Address to host the server on.                                                            |\n|  **port**             | `8000`           | Port to host the server on.                                                               |\n|  **unix**             | `None`           | Unix socket name to host the server on (instead of TCP).                                  |\n|  **dev**              | `False`          | Equivalent to `debug=True` and `auto_reload=True`.                                        |\n|  **debug**            | `False`          | Enables debug output (slows server).                                                      |\n|  **ssl**              | `None`           | SSLContext for SSL encryption of worker(s).                                               |\n|  **sock**             | `None`           | Socket for the server to accept connections from.                                         |\n|  **workers**          | `1`              | Number of worker processes to spawn. Cannot be used with fast.                            |\n|  **loop**             | `None`           | An asyncio-compatible event loop. If none is specified, Sanic creates its own event loop. |\n|  **protocol**         | `HttpProtocol`   | Subclass of asyncio.protocol.                                                             |\n|  **version**          | `HTTP.VERSION_1` | The HTTP version to use (`HTTP.VERSION_1` or `HTTP.VERSION_3`).                           |\n|  **access_log**       | `True`           | Enables log on handling requests (significantly slows server).                            |\n|  **auto_reload**      | `None`           | Enables auto-reload on the source directory.                                              |\n|  **reload_dir**       | `None`           | A path or list of paths to directories the auto-reloader should watch.                    |\n|  **noisy_exceptions** | `None`           | Whether to set noisy exceptions globally. None means leave as default.                    |\n|  **motd**             | `True`           | Whether to display the startup message.                                                   |\n|  **motd_display**     | `None`           | A dict with extra key/value information to display in the startup message                 |\n|  **fast**             | `False`          | Whether to maximize worker processes.  Cannot be used with workers.                       |\n|  **verbosity**        | `0`              | Level of logging detail. Max is 2.                                                        |\n|  **auto_tls**         | `False`          | Whether to auto-create a TLS certificate for local development. Not for production.       |\n|  **single_process**   | `False`          | Whether to run Sanic in a single process.                                                 |\n\n.. column::\n\n    For example, we can turn off the access log in order to increase performance, and bind to a custom host and port.\n\n.. column::\n\n    ```python\n    # server.py\n    app = Sanic(\"MyApp\")\n\n    if __name__ == \"__main__\":\n        app.run(host='0.0.0.0', port=1337, access_log=False)\n    ```\n\n\n.. column::\n\n    Now, just execute the python script that has `app.run(...)`\n\n.. column::\n\n    ```sh\n    python server.py\n    ```\n\nFor a slightly more advanced implementation, it is good to know that `app.run` will call `app.prepare` and `Sanic.serve` under the hood.\n\n.. column::\n\n    Therefore, these are equivalent:\n\n.. column::\n\n    ```python\n    if __name__ == \"__main__\":\n        app.run(host='0.0.0.0', port=1337, access_log=False)\n    ```\n    ```python\n    if __name__ == \"__main__\":\n        app.prepare(host='0.0.0.0', port=1337, access_log=False)\n        Sanic.serve()\n    ```\n\n.. column::\n\n    This can be useful if you need to bind your appliction(s) to multiple ports.\n\n.. column::\n\n    ```python\n    if __name__ == \"__main__\":\n        app1.prepare(host='0.0.0.0', port=9990)\n        app1.prepare(host='0.0.0.0', port=9991)\n        app2.prepare(host='0.0.0.0', port=5555)\n        Sanic.serve()\n    ```\n\n### Sanic Simple Server\n\n.. column::\n\n    Sometimes you just have a directory of static files that need to be served. This especially can be handy for quickly standing up a localhost server. Sanic ships with a Simple Server, where you only need to point it at a directory.\n\n.. column::\n\n    ```sh\n    sanic ./path/to/dir --simple\n    ```\n\n\n.. column::\n\n    This could also be paired with auto-reloading.\n\n.. column::\n\n    ```sh\n    sanic ./path/to/dir --simple --reload --reload-dir=./path/to/dir\n    ```\n\n*Added in v21.6*\n\n### Daemon mode\n\n.. new:: New in v25.12\n\n    This feature was added in version 25.12\n\n.. column::\n\n    Sanic can run as a background daemon process. Use the `-D` or `--daemon` flag to start the server in the background.\n\n.. column::\n\n    ```sh\n    sanic path.to.server:app --daemon\n    sanic path.to.server:app -D\n    ```\n\n.. column::\n\n    You can manage the daemon with additional commands:\n\n.. column::\n\n    ```sh\n    sanic path.to.server:app status   # Check if running\n    sanic path.to.server:app stop     # Stop the daemon\n    ```\n\n.. column::\n\n    Additional options are available for daemon configuration:\n\n.. column::\n\n    ```sh\n    sanic path.to.server:app -D --pidfile=/var/run/sanic.pid\n    sanic path.to.server:app -D --logfile=/var/log/sanic.log\n    sanic path.to.server:app -D --user=www-data\n    sanic path.to.server:app -D --group=www-data\n    ```\n\nLower-level commands are also available to manage processes by PID:\n\n```sh\nsanic kill --pid=<PID>\nsanic kill --pidfile=/var/run/sanic.pid\nsanic status --pid=<PID>\nsanic status --pidfile=/var/run/sanic.pid\n```\n\n*Added in v25.12*\n\n### HTTP/3\n\nSanic server offers HTTP/3 support using [aioquic](https://github.com/aiortc/aioquic). This **must** be installed to use HTTP/3:\n\n```sh\npip install sanic aioquic\n```\n\n```sh\npip install sanic[http3]\n```\n\nTo start HTTP/3, you must explicitly request it when running your application.\n\n.. column::\n\n    ```sh\n    sanic path.to.server:app --http=3\n    ```\n\n    ```sh\n    sanic path.to.server:app -3\n    ```\n\n.. column::\n\n    ```python\n    app.run(version=3)\n    ```\n\nTo run both an HTTP/3 and HTTP/1.1 server simultaneously, you can use [application multi-serve](../../release-notes/2022/v22.3.md#application-multi-serve) introduced in v22.3. This will automatically add an [Alt-Svc](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Alt-Svc) header to your HTTP/1.1 requests to let the client know that it is also available as HTTP/3.\n\n\n.. column::\n\n    ```sh\n    sanic path.to.server:app --http=3 --http=1\n    ```\n\n    ```sh\n    sanic path.to.server:app -3 -1\n    ```\n\n.. column::\n\n    ```python\n    app.prepare(version=3)\n    app.prepare(version=1)\n    Sanic.serve()\n    ```\n\nBecause HTTP/3 requires TLS, you cannot start a HTTP/3 server without a TLS certificate. You should [set it up yourself](../how-to/tls.md) or use `mkcert` if in `DEBUG` mode. Currently, automatic TLS setup for HTTP/3 is not compatible with `trustme`. See [development](./development.md) for more details.\n\n*Added in v22.6*\n\n## ASGI\n\nSanic is also ASGI-compliant. This means you can use your preferred ASGI webserver to run Sanic. The three main implementations of ASGI are [Daphne](http://github.com/django/daphne), [Uvicorn](https://www.uvicorn.org/), and [Hypercorn](https://pgjones.gitlab.io/hypercorn/index.html).\n\n\n.. warning:: \n\n    Daphne does not support the ASGI `lifespan` protocol, and therefore cannot be used to run Sanic. See [Issue #264](https://github.com/django/daphne/issues/264) for more details.\n\n\n\nFollow their documentation for the proper way to run them, but it should look something like:\n\n```sh\nuvicorn myapp:app\n```\n```sh\nhypercorn myapp:app\n```\n\nA couple things to note when using ASGI:\n\n1. When using the Sanic webserver, websockets will run using the `websockets` package. In ASGI mode, there is no need for this package since websockets are managed in the ASGI server. \n2. The ASGI lifespan protocol <https://asgi.readthedocs.io/en/latest/specs/lifespan.html>, supports only two server events: startup and shutdown. Sanic has four: before startup, after startup, before shutdown, and after shutdown. Therefore, in ASGI mode, the startup and shutdown events will run consecutively and not actually around the server process beginning and ending (since that is now controlled by the ASGI server). Therefore, it is best to use `after_server_start` and `before_server_stop`.\n\n### Trio\n\nSanic has experimental support for running on Trio with:\n\n```sh\nhypercorn -k trio myapp:app\n```\n\n## Gunicorn\n\n[Gunicorn](http://gunicorn.org/) (\"Green Unicorn\") is a WSGI HTTP Server for UNIX based operating systems. It is a pre-fork worker model ported from Ruby’s Unicorn project.\n\nIn order to run Sanic application with Gunicorn, you need to use it with the adapter from [uvicorn](https://www.uvicorn.org/). Make sure uvicorn is installed and run it with `uvicorn.workers.UvicornWorker` for Gunicorn worker-class argument:\n\n```sh\ngunicorn myapp:app --bind 0.0.0.0:1337 --worker-class uvicorn.workers.UvicornWorker\n```\n\nSee the [Gunicorn Docs](http://docs.gunicorn.org/en/latest/settings.html#max-requests) for more information.\n\n\n.. warning:: \n\n    It is generally advised to not use `gunicorn` unless you need it. The Sanic Server is primed for running Sanic in production. Weigh your considerations carefully before making this choice. Gunicorn does provide a lot of configuration options, but it is not the best choice for getting Sanic to run at its fastest.\n\n\n\n## Performance considerations\n\n.. column::\n\n    When running in production, make sure you turn off `debug`.\n\n.. column::\n\n    ```sh\n    sanic path.to.server:app\n    ```\n\n\n.. column::\n\n    Sanic will also perform fastest if you turn off `access_log`.\n\n    If you still require access logs, but want to enjoy this performance boost, consider using [Nginx as a proxy](./../deployment/nginx.md), and letting that handle your access logging. It will be much faster than anything Python can handle.\n\n.. column::\n\n    ```sh\n    sanic path.to.server:app --no-access-logs\n    ```\n\n"
  },
  {
    "path": "guide/content/en/help.md",
    "content": "---\ntitle: Need some help?\nlayout: main\n---\n\n# Need some help?\n\nAs an active community of developers, we try to support each other. If you need some help, try one of the following:\n\n.. column::\n\n    ### Discord 💬\n\n    Best place to turn for quick answers and live chat\n\n    `#sanic-support` channel on the [Discord server](https://discord.gg/FARQzAEMAA)\n\n.. column::\n\n    ### Community Forums 👥\n\n    Good for sharing snippets of code and longer support queries\n\n    `Questions and Help` category on the [Forums](https://community.sanicframework.org/c/questions-and-help/6)\n\n---\n\nWe also actively monitor the `[sanic]` tag on [Stack Overflow](https://stackoverflow.com/questions/tagged/sanic).\n"
  },
  {
    "path": "guide/content/en/index.md",
    "content": "---\ntitle: The lightning-fast asynchronous Python web framework\nlayout: home\nfeatures:\n- title: Simple and lightweight\n  details: Intuitive API with smart defaults and no bloat allows you to get straight to work building your app.\n- title: Unopinionated and flexible\n  details: Build the way you want to build without letting your tooling constrain you.\n- title: Performant and scalable\n  details: Built from the ground up with speed and scalability as a main concern. It is ready to power web applications big and small.\n- title: Production ready\n  details: Out of the box, it comes bundled with a web server ready to power your web applications.\n- title: Trusted by millions\n  details: Sanic is one of the overall most popular frameworks on PyPI, and the top async enabled framework\n- title: Community driven\n  details: The project is maintained and run by the community for the community.\n---\n\n### ⚡ The lightning-fast asynchronous Python web framework\n\n.. attrs::\n    :class: columns is-multiline mt-6\n\n    .. attrs::\n        :class: column is-4\n\n        #### Simple and lightweight\n\n        Intuitive API with smart defaults and no bloat allows you to get straight to work building your app.\n\n    .. attrs::\n        :class: column is-4\n\n        #### Unopinionated and flexible\n\n        Build the way you want to build without letting your tooling constrain you.\n\n    .. attrs::\n        :class: column is-4\n\n        #### Performant and scalable\n\n        Built from the ground up with speed and scalability as a main concern. It is ready to power web applications big and small.\n\n    .. attrs::\n        :class: column is-4\n\n        #### Production ready\n\n        Out of the box, it comes bundled with a web server ready to power your web applications.\n\n    .. attrs::\n        :class: column is-4\n\n        #### Trusted by millions\n\n        Sanic is one of the overall most popular frameworks on PyPI, and the top async enabled framework\n\n    .. attrs::\n        :class: column is-4\n\n        #### Community driven\n\n        The project is maintained and run by the community for the community.\n\n\n.. attrs::\n    :class: is-size-3 mt-6\n\n    **With the features and tools you'd expect.**\n\n.. attrs::\n    :class: is-size-3 ml-6\n    \n    **And some {span:has-text-primary:you wouldn't believe}.**\n\n.. tab:: Production-grade\n    \n    After installing, Sanic has all the tools you need for a scalable, production-grade server—out of the box!\n\n    Including [full TLS support](/en/guide/how-to/tls.md).\n\n    ```python\n    from sanic import Sanic\n    from sanic.response import text\n\n    app = Sanic(\"MyHelloWorldApp\")\n\n    @app.get(\"/\")\n    async def hello_world(request):\n        return text(\"Hello, world.\")\n    ```\n\n    ```sh\n    sanic path.to.server:app\n    [2023-01-31 12:34:56 +0000] [999996] [INFO] Sanic v22.12.0\n    [2023-01-31 12:34:56 +0000] [999996] [INFO] Goin' Fast @ http://127.0.0.1:8000\n    [2023-01-31 12:34:56 +0000] [999996] [INFO] mode: production, single worker\n    [2023-01-31 12:34:56 +0000] [999996] [INFO] server: sanic, HTTP/1.1\n    [2023-01-31 12:34:56 +0000] [999996] [INFO] python: 3.10.9\n    [2023-01-31 12:34:56 +0000] [999996] [INFO] platform: SomeOS-9.8.7\n    [2023-01-31 12:34:56 +0000] [999996] [INFO] packages: sanic-routing==22.8.0\n    [2023-01-31 12:34:56 +0000] [999997] [INFO] Starting worker [999997]\n    ```\n\n.. tab:: TLS server\n    \n    Running Sanic with TLS enabled is as simple as passing it the file paths...\n    ```sh\n    sanic path.to.server:app --cert=/path/to/bundle.crt --key=/path/to/privkey.pem\n    ```\n\n    ... or the a directory containing `fullchain.pem` and `privkey.pem`\n\n    ```sh\n    sanic path.to.server:app --tls=/path/to/certs\n    ```\n\n    **Even better**, while you are developing, let Sanic handle setting up local TLS certificates so you can access your site over TLS at [https://localhost:8443](https://localhost:8443)\n\n    ```sh\n    sanic path.to.server:app --dev --auto-tls\n    ```\n\n.. tab:: Websockets\n\n    Up and running with websockets in no time using the [websockets](https://websockets.readthedocs.io) package.\n    ```python\n    from sanic import Request, Websocket\n\n    @app.websocket(\"/feed\")\n    async def feed(request: Request, ws: Websocket):\n        async for msg in ws:\n            await ws.send(msg)\n    ```\n\n.. tab:: Static files\n\n    Serving static files is of course intuitive and easy. Just name an endpoint and either a file or directory that should be served.\n\n    ```python\n    app.static(\"/\", \"/path/to/index.html\")\n    app.static(\"/uploads/\", \"/path/to/uploads/\")\n    ```\n\n    Moreover, serving a directory has two additional features: automatically serving an index, and automatically serving a file browser.\n    \n    Sanic can automatically serve `index.html` (or any other named file) as an index page in a directory or its subdirectories.\n    \n    ```python\n    app.static(\n        \"/uploads/\",\n        \"/path/to/uploads/\",\n        index=\"index.html\"\n    )\n    ```\n\n    And/or, setup Sanic to display a file browser.\n\n    \n    ![image](/assets/images/directory-view.png)\n\n    ```python\n    app.static(\n        \"/uploads/\",\n        \"/path/to/uploads/\",\n        directory_view=True\n    )\n    ```\n\n.. tab:: Lifecycle\n\n    Beginning or ending a route with functionality is as simple as adding a decorator.\n\n    ```python\n    @app.on_request\n    async def add_key(request):\n        request.ctx.foo = \"bar\"\n\n    @app.on_response\n    async def custom_banner(request, response):\n        response.headers[\"X-Foo\"] = request.ctx.foo\n    ```\n\n    Same with server events.\n\n    ```python\n    @app.before_server_start\n    async def setup_db(app):\n        app.ctx.db_pool = await db_setup()\n\n    @app.after_server_stop\n    async def setup_db(app):\n        await app.ctx.db_pool.shutdown()\n    ```\n\n    But, Sanic also allows you to tie into a bunch of built-in events (called signals), or create and dispatch your own.\n\n    ```python\n    @app.signal(\"http.lifecycle.complete\")  # built-in\n    async def my_signal_handler(conn_info):\n        print(\"Connection has been closed\")\n\n    @app.signal(\"something.happened.ohmy\")  # custom\n    async def my_signal_handler():\n        print(\"something happened\")\n\n    await app.dispatch(\"something.happened.ohmy\")\n    ```\n\n.. tab:: Smart error handling\n\n    Raising errors will intuitively result in proper HTTP errors:\n\n    ```python\n    raise sanic.exceptions.NotFound  # Automatically responds with HTTP 404\n    ```\n\n    Or, make your own:\n\n    ```python\n    from sanic.exceptions import SanicException\n\n    class TeapotError(SanicException):\n        status_code = 418\n        message = \"Sorry, I cannot brew coffee\"\n\n    raise TeapotError\n    ```\n\n    And, when an error does happen, Sanic's beautiful DEV mode error page will help you drill down to the bug quickly.\n\n    ![image](../assets/images/error-div-by-zero.png)\n\n    Regardless, Sanic comes with an algorithm that attempts to respond with HTML, JSON, or text-based errors as appropriate. Don't worry, it is super easy to setup and customize your error handling to your exact needs.\n\n.. tab:: App Inspector\n\n    Check in on your live, running applications (whether local or remote).\n    ```sh\n    sanic inspect      \n\n    ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐\n    │                                                        Sanic                                                        │\n    │                                          Inspecting @ http://localhost:6457                                         │\n    ├───────────────────────┬─────────────────────────────────────────────────────────────────────────────────────────────┤\n    │                       │     mode: production, single worker                                                         │\n    │     ▄███ █████ ██     │   server: unknown                                                                           │\n    │    ██                 │   python: 3.10.9                                                                            │\n    │     ▀███████ ███▄     │ platform: SomeOS-9.8.7\n    │                 ██    │ packages: sanic==22.12.0, sanic-routing==22.8.0, sanic-testing==22.12.0, sanic-ext==22.12.0 │\n    │    ████ ████████▀     │                                                                                             │\n    │                       │                                                                                             │\n    │ Build Fast. Run Fast. │                                                                                             │\n    └───────────────────────┴─────────────────────────────────────────────────────────────────────────────────────────────┘\n\n    Sanic-Main\n        pid: 999996\n\n    Sanic-Server-0-0\n        server: True\n        state: ACKED\n        pid: 999997\n        start_at: 2023-01-31T12:34:56.00000+00:00\n        starts: 1\n\n    Sanic-Inspector-0\n        server: False\n        state: STARTED\n        pid: 999998\n        start_at: 2023-01-31T12:34:56.00000+00:00\n        starts: 1\n    ```\n\n    And, issue commands like `reload`, `shutdown`, `scale`...\n\n    ```sh\n    sanic inspect scale 4\n    ```\n\n    ... or even create your own!\n\n    ```sh\n    sanic inspect migrations\n    ```\n\n.. tab:: Extendable\n\n    In addition to the tools that Sanic comes with, the officially supported [Sanic Extensions](./plugins/sanic-ext/getting-started.md) provides lots of extra goodies to make development easier.\n\n    - **CORS** protection\n    - Template rendering with **Jinja**\n    - **Dependency injection** into route handlers\n    - OpenAPI documentation with **Redoc** and/or **Swagger**\n    - Predefined, endpoint-specific response **serializers**\n    - Request query arguments and body input **validation**\n    - **Auto create** HEAD, OPTIONS, and TRACE endpoints\n    - Live **health monitor**\n\n.. tab:: Developer Experience\n\n    Sanic is **built for building**.\n\n    From the moment it is installed, Sanic includes helpful tools to help the developer get their job done.\n\n    - **One server** - Develop locally in DEV mode on the same server that will run your PRODUCTION application\n    - **Auto reload** - Reload running applications every time you save a Python file, but also auto-reload **on any arbitrary directory** like HTML template directories\n    - **Debugging tools** - Super helpful (and beautiful) [error pages](/en/guide/best-practices/exceptions) that help you traverse the trace stack easily\n    - **Auto TLS** - Running a localhost website with `https` can be difficult, [Sanic makes it easy](/en/guide/how-to/tls.md)\n    - **Streamlined testing** - Built-in testing capabilities, making it easier for developers to create and run tests, ensuring the quality and reliability of their services\n    - **Modern Python** - Thoughtful use of type hints to help the developer IDE experience\n"
  },
  {
    "path": "guide/content/en/migrate.py",
    "content": "import re\n\nfrom pathlib import Path\nfrom textwrap import indent\n\nfrom emoji import EMOJI\n\n\nCOLUMN_PATTERN = re.compile(r\"---:1\\s*(.*?)\\s*:--:1\\s*(.*?)\\s*:---\", re.DOTALL)\nPYTHON_HIGHLIGHT_PATTERN = re.compile(r\"```python\\{+.*?\\}\", re.DOTALL)\nBASH_HIGHLIGHT_PATTERN = re.compile(r\"```bash\\{+.*?\\}\", re.DOTALL)\nNOTIFICATION_PATTERN = re.compile(\n    r\":::\\s*(\\w+)\\s*(.*?)\\n([\\s\\S]*?):::\", re.MULTILINE\n)\nEMOJI_PATTERN = re.compile(r\":(\\w+):\")\nCURRENT_DIR = Path(__file__).parent\nSOURCE_DIR = (\n    CURRENT_DIR.parent.parent.parent.parent / \"sanic-guide\" / \"src\" / \"en\"\n)\n\n\ndef convert_columns(content: str):\n    def replacer(match: re.Match):\n        left, right = match.groups()\n        left = indent(left.strip(), \" \" * 4)\n        right = indent(right.strip(), \" \" * 4)\n        return f\"\"\"\n.. column::\n\n{left}\n\n.. column::\n\n{right}\n\"\"\"\n\n    return COLUMN_PATTERN.sub(replacer, content)\n\n\ndef cleanup_highlights(content: str):\n    content = PYTHON_HIGHLIGHT_PATTERN.sub(\"```python\", content)\n    content = BASH_HIGHLIGHT_PATTERN.sub(\"```bash\", content)\n    return content\n\n\ndef convert_notifications(content: str):\n    def replacer(match: re.Match):\n        type_, title, body = match.groups()\n        body = indent(body.strip(), \" \" * 4)\n        return f\"\"\"\n\n.. {type_}:: {title}\n\n{body}\n\n\"\"\"\n\n    return NOTIFICATION_PATTERN.sub(replacer, content)\n\n\ndef convert_emoji(content: str):\n    def replace(match):\n        return EMOJI.get(match.group(1), match.group(0))\n\n    return EMOJI_PATTERN.sub(replace, content)\n\n\ndef convert_code_blocks(content: str):\n    for src, dest in (\n        (\"yml\", \"yaml\"),\n        (\"caddy\", \"\"),\n        (\"systemd\", \"\"),\n        (\"mermaid\", \"\\nmermaid\"),\n    ):\n        content = content.replace(f\"```{src}\", f\"```{dest}\")\n    return content\n\n\ndef cleanup_multibreaks(content: str):\n    return content.replace(\"\\n\\n\\n\", \"\\n\\n\")\n\n\ndef convert(content: str):\n    content = convert_emoji(content)\n    content = convert_columns(content)\n    content = cleanup_highlights(content)\n    content = convert_code_blocks(content)\n    content = convert_notifications(content)\n    content = cleanup_multibreaks(content)\n    return content\n\n\ndef convert_file(src: Path, dest: Path):\n    short_src = src.relative_to(SOURCE_DIR)\n    short_dest = dest.relative_to(CURRENT_DIR)\n    print(f\"Converting {short_src} -> {short_dest}\")\n    content = src.read_text()\n    new_content = convert(content)\n    dest.parent.mkdir(parents=True, exist_ok=True)\n    dest.touch()\n    dest.write_text(new_content)\n\n\ndef translate_path(source_dir: Path, source_path: Path, dest_dir: Path):\n    rel_path = source_path.relative_to(source_dir)\n    dest_path = dest_dir / rel_path\n    return dest_path\n\n\ndef main():\n    print(f\"Source: {SOURCE_DIR}\")\n\n    for path in SOURCE_DIR.glob(\"**/*.md\"):\n        if path.name in (\"index.md\", \"README.md\"):\n            continue\n        dest_path = translate_path(SOURCE_DIR, path, CURRENT_DIR)\n        convert_file(path, dest_path)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "guide/content/en/organization/code-of-conduct.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, gender identity and expression, level of experience,\nnationality, personal appearance, race, religion, or sexual identity and\norientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or\nadvances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic\n  address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at adam@sanicframework.org. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at [http://contributor-covenant.org/version/1/4][version]\n\n[homepage]: http://contributor-covenant.org\n[version]: http://contributor-covenant.org/version/1/4/\n"
  },
  {
    "path": "guide/content/en/organization/contributing.md",
    "content": "# Contributing\n\nThank you for your interest! Sanic is always looking for contributors. If you don't feel comfortable contributing code, adding docstrings to the source files, or helping with the [Sanic User Guide](https://github.com/sanic-org/sanic-guide) by providing documentation or implementation examples would be appreciated!\n\nWe are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, disability, ethnicity, religion, or similar personal characteristic. Our [code of conduct](https://github.com/sanic-org/sanic/blob/master/CONDUCT.md) sets the standards for behavior.\n\n## Installation\n\nTo develop on Sanic (and mainly to just run the tests) it is highly recommend to install from sources.\n\nSo assume you have already cloned the repo and are in the working directory with a virtual environment already set up, then run:\n\n```sh\npip install -e \".[dev]\"\n```\n\n## Dependency Changes\n\n`Sanic` doesn't use `requirements*.txt` files to manage any kind of dependencies related to it in order to simplify the effort required in managing the dependencies. Please make sure you have read and understood the following section of the document that explains the way `sanic` manages dependencies inside the `setup.py` file.\n\n| Dependency Type                 | Usage                                             | Installation                 |\n| ------------------------------- | ------------------------------------------------- | ---------------------------- |\n| requirements                    | Bare minimum dependencies required for sanic to function | `pip3 install -e .`         |\n| tests_require / extras_require['test'] | Dependencies required to run the Unit Tests for `sanic` | `pip3 install -e '.[test]'` |\n| extras_require['dev']           | Additional Development requirements to add contributing | `pip3 install -e '.[dev]'`  |\n| extras_require['docs']          | Dependencies required to enable building and enhancing sanic documentation | `pip3 install -e '.[docs]'` |\n\n## Running all tests\n\nTo run the tests for Sanic it is recommended to use tox like so:\n\n```sh\ntox\n```\n\nSee it's that simple!\n\n`tox.ini` contains different environments. Running `tox` without any arguments will\nrun all unittests, perform lint and other checks.\n\n## Run unittests\n\n`tox` environment -> `[testenv]`\n\nTo execute only unittests, run `tox` with environment like so:\n\n```sh\n\ntox -e py37 -v -- tests/test_config.py\n# or\ntox -e py310 -v -- tests/test_config.py\n```\n\n## Run lint checks\n\n`tox` environment -> `[testenv:lint]`\n\nPermform `flake8`\\ , `black` and `isort` checks.\n\n\n```sh\ntox -e lint\n```\n\n## Run type annotation checks\n\n`tox` environment -> `[testenv:type-checking]`\n\nPermform `mypy` checks.\n\n```sh\ntox -e type-checking\n```\n\n## Run other checks\n\n`tox` environment -> `[testenv:check]`\n\nPerform other checks.\n\n```sh\ntox -e check\n```\n\n## Run Static Analysis\n\n`tox` environment -> `[testenv:security]`\n\nPerform static analysis security scan\n\n```sh\ntox -e security\n```\n\n## Run Documentation sanity check\n\n`tox` environment -> `[testenv:docs]`\n\nPerform sanity check on documentation\n\n```sh\ntox -e docs\n```\n## Code Style\n\nTo maintain the code consistency, Sanic uses the following tools:\n\n1. [isort](https://github.com/timothycrosley/isort)\n2. [black](https://github.com/python/black)\n3. [flake8](https://github.com/PyCQA/flake8)\n4. [slotscheck](https://github.com/ariebovenberg/slotscheck)\n\n### isort\n\n`isort` sorts Python imports. It divides imports into three categories sorted each in alphabetical order:\n\n1. built-in\n2. third-party\n3. project-specific\n\n### black\n\n`black` is a Python code formatter.\n\n### flake8\n\n`flake8` is a Python style guide that wraps the following tools into one:\n\n1. PyFlakes\n2. pycodestyle\n3. Ned Batchelder's McCabe script\n\n### slotscheck\n\n`slotscheck` ensures that there are no problems with `__slots__` (e.g., overlaps, or missing slots in base classes).\n\n`isort`, `black`, `flake8`, and `slotscheck` checks are performed during `tox` lint checks.\n\nThe **easiest** way to make your code conform is to run the following before committing:\n\n```bash\nmake pretty\n```\n\nRefer to [tox documentation](https://tox.readthedocs.io/en/latest/index.html) for more details.\n\n## Pull requests\n\nSo the pull request approval rules are pretty simple:\n\n1. All pull requests must pass unit tests.\n2. All pull requests must be reviewed and approved by at least one current member of the Core Developer team.\n3. All pull requests must pass flake8 checks.\n4. All pull requests must match `isort` and `black` requirements.\n5. All pull requests must be **PROPERLY** type annotated, unless exemption is given.\n6. All pull requests must be consistent with the existing code.\n7. If you decide to remove/change anything from any common interface a deprecation message should accompany it in accordance with our [deprecation policy](https://sanicframework.org/en/guide/project/policies.html#deprecation).\n8. If you implement a new feature you should have at least one unit test to accompany it.\n9. An example must be one of the following:\n    * Example of how to use Sanic\n    * Example of how to use Sanic extensions\n    * Example of how to use Sanic and asynchronous library\n\n## Documentation\n\n_Check back. We are reworking our documentation so this will change._\n"
  },
  {
    "path": "guide/content/en/organization/policies.md",
    "content": "# Policies\n\n## Versioning\n\nSanic uses [calendar versioning](https://calver.org/), aka \"calver\". To be more specific, the pattern follows:\n\n```\nYY.MM.MICRO\n```\n\nGenerally, versions are referred to in their ``YY.MM`` form. The `MICRO` number indicates an incremental patch version, starting at `0`.\n\n## Reporting a Vulnerability\n\nIf you discover a security vulnerability, we ask that you **do not** create an issue on GitHub. Instead, please [send a message to the core-devs](https://community.sanicframework.org/g/core-devs) on the community forums. Once logged in, you can send a message to the core-devs by clicking the message button.\n\nAlternatively, you can send a private message to Adam Hopkins on Discord. Find him on the [Sanic discord server](https://discord.gg/FARQzAEMAA).\n\nThis will help to not publicize the issue until the team can address it and resolve it.\n\n## Release Schedule\n\nThere are four (4) scheduled releases per year: March, June, September, and December. Therefore, there are four (4) released versions per year: `YY.3`, `YY.6`, `YY.9`, and `YY.12`. \n\nThis release schedule provides:\n\n- a predictable release cadence,\n- relatively short development windows allowing features to be regularly released,\n- controlled [deprecations](#deprecation), and\n- consistent stability with a yearly LTS.\n\nWe also use the yearly release cycle in conjunction with our governance model, covered by the [S.C.O.P.E.](./scope.md)\n\n### Long term support v Interim releases\n\nSanic releases a long term support release (aka \"LTS\") once a year in December. The LTS releases receive bug fixes and security updates for **24 months**. Interim releases throughout the year occur every three months, and are supported until the subsequent release.\n\n| Version | Release    | LTS           | Supported       |\n|---------|------------|---------------|-----------------|\n| 24.12   | 2024-12-31 | until 2026-12 | ✅              |\n| 24.6    | 2024-06-30 |               | ⚪              |\n| 23.12   | 2023-12-31 | until 2025-12 | ☑️              |\n| 23.6    | 2023-07-25 |               | ⚪              |\n| 23.3    | 2023-03-26 |               | ⚪              |\n| 22.12   | 2022-12-27 |               | ☑️              |\n| 22.9    | 2022-09-29 |               | ⚪              |\n| 22.6    | 2022-06-30 |               | ⚪              |\n| 22.3    | 2022-03-31 |               | ⚪              |\n| 21.12   | 2021-12-26 |               | ⚪              |\n| 21.9    | 2021-09-30 |               | ⚪              |\n| 21.6    | 2021-06-27 |               | ⚪              |\n| 21.3    | 2021-03-21 |               | ⚪              |\n| 20.12   | 2020-12-29 |               | ⚪              |\n| 20.9    | 2020-09-30 |               | ⚪              |\n| 20.6    | 2020-06-28 |               | ⚪              |\n| 20.3    | 2020-05-14 |               | ⚪              |\n| 19.12   | 2019-12-27 |               | ⚪              |\n| 19.9    | 2019-10-12 |               | ⚪              |\n| 19.6    | 2019-06-21 |               | ⚪              |\n| 19.3    | 2019-03-23 |               | ⚪              |\n| 18.12   | 2018-12-27 |               | ⚪              |\n| 0.8.3   | 2018-09-13 |               | ⚪              |\n| 0.7.0   | 2017-12-06 |               | ⚪              |\n| 0.6.0   | 2017-08-03 |               | ⚪              |\n| 0.5.4   | 2017-05-09 |               | ⚪              |\n| 0.4.1   | 2017-02-28 |               | ⚪              |\n| 0.3.1   | 2017-02-09 |               | ⚪              |\n| 0.2.0   | 2017-01-14 |               | ⚪              |\n| 0.1.9   | 2016-12-25 |               | ⚪              |\n| 0.1.0   | 2016-10-16 |               | ⚪              |\n\n☑️ = security fixes  \n✅ = full support  \n⚪ = no support\n\n## Deprecation\n\nBefore a feature is deprecated, or breaking changes are introduced into the API, it shall be publicized and shall appear with deprecation warnings through two release cycles. No deprecations shall be made in an LTS release.\n\nBreaking changes or feature removal may happen outside of these guidelines when absolutely warranted. These circumstances should be rare. For example, it might happen when no alternative is available to curtail a major security issue.\n"
  },
  {
    "path": "guide/content/en/organization/scope.md",
    "content": "\n# Sanic Community Organization Policy E-manual (SCOPE)\n\n.. attrs::\n    :class: is-size-7\n\n    _December 2019, version 1_\n\n## Goals\n\nTo create a sustainable, community-driven organization around the Sanic projects that promote: (1) stability and predictability, (2) quick iteration and enhancement cycles, (3) engagement from outside contributors, (4) overall reliable software, and (5) a safe, rewarding environment for the community members.\n\n## Overview\n\nThis Policy is the governance model for the Sanic Community Organization (“SCO”). The SCO is a meritocratic, consensus-based community organization responsible for all projects adopted by it. Anyone with an interest in one of the projects can join the community, contribute to the community or projects, and participate in the decision making process. This document describes how that participation takes place and how to set about earning merit within the project community.\n\n## Structure\n\nThe SCO has multiple **projects**. Each project is represented by a single GitHub repository under the Sanic community umbrella. These projects are used by **users**, developed by **contributors**, governed by **core developers**, released by **release managers**, and ultimately overseen by a **steering council**. If this sounds similar to the Python project and PEP 8016 that is because it is intentionally designed that way.\n\n## Roles and responsibilities\n\n### Users\n\nUsers are community members who have a need for the projects. They are the developers and personnel that download and install the packages. Users are the **most important** members of the community and without them the projects would have no purpose. Anyone can be a user and the licenses adopted by the projects shall be appropriate open source licenses.\n\n_The SCO asks its users to participate in the project and community as much as possible._\n\nUser contributions enable the project team to ensure that they are satisfying the needs of those users. Common user contributions include (but are not limited to):\n\n*   evangelizing about the project (e.g. a link on a website and word-of-mouth awareness raising)\n*   informing developers of strengths and weaknesses from a new user perspective\n*   providing moral support (a ‘thank you’ goes a long way)\n*   providing financial support (the software is open source, but its developers need to eat)\n\nUsers who continue to engage with the SCO, its projects, and its community will often become more and more involved. Such users may find themselves becoming contributors, as described in the next section.\n\n### Contributors\n\nContributors are community members who contribute in concrete ways to one or more of the projects. Anyone can become a contributor and contributions can take many forms. Contributions and requirements are governed by each project separately by a contribution policy.\n\nThere is **no expectation** of commitment to the project, **no specific skill requirements** and **no selection process**.\n\nIn addition to their actions as users, contributors may also find themselves doing one or more of the following:\n\n*   supporting new users (existing users are often the best people to support new users)\n*   reporting bugs\n*   identifying requirements\n*   providing graphics and web design\n*   Programming\n*   example use cases\n*   assisting with project infrastructure\n*   writing documentation\n*   fixing bugs\n*   adding features\n*   providing constructive opinions and engaging in community discourse\n\nContributors engage with the projects through GitHub and the Community Forums. They submit changes to the projects itself via pull requests, which will be considered for inclusion in the project by the community at large. The Community Forums are the most appropriate place to ask for help when making that first contribution.\n\nIndeed one of the most important roles of a contributor may be to **simply engage in the community conversation**. Most decisions about the direction of a project are made by consensus. This is discussed in more detail below. In general, however, it is helpful for the health and direction of the projects for the contributors to **speak freely** (within the confines of the code of conduct) and **express their opinions and experiences** to help drive the consensus building.\n\nAs contributors gain experience and familiarity with a project, their profile within, and commitment to, the community will increase. At some stage, they may find themselves being nominated for a core developer team.\n\n### Core Developer\n\nEach project under the SCO umbrella has its own team of core developers. They are the people in charge of that project.\n\n_What is a core developer?_\n\nCore developers are community members who have shown that they are committed to the continued development of the project through ongoing engagement with the community. Being a core developer allows contributors to more easily carry on with their project related activities by giving them direct access to the project’s resources. They can make changes directly to the project repository without having to submit changes via pull requests from a fork.\n\nThis does not mean that a core developer is free to do what they want. In fact, core developers have no more direct authority over the final release of a package than do contributors. While this honor does indicate a valued member of the community who has demonstrated a healthy respect for the project’s aims and objectives, their work continues to be reviewed by the community before acceptance in an official release.\n\n_What can a core developer do on a project?_\n\nEach project might define this role slightly differently. However, the general usage of this designation is that an individual has risen to a level of trust within the community such that they now are given some control. This comes in the form of push rights to non-protected branches, and the ability to have a voice in the approval of pull requests.\n\nThe projects employ various communication mechanisms to ensure that all contributions are reviewed by the community as a whole. This includes tools provided by GitHub, as well as the Community Forums. By the time a contributor is invited to become a core developer, they should be familiar with the various tools and workflows as a user and then as a contributor.\n\n_How to become a core developer?_\n\nAnyone can become a core developer; there are no special requirements, other than to have shown a willingness and ability to positively participate in the project as a team player.\n\nTypically, a potential core developer will need to show that they have an understanding of the project, its objectives and its strategy. They will also have provided valuable contributions to the project over a period of time. However, there is **no technical or other skill** requirement for eligibility.\n\nNew core developers can be **nominated by any existing core developer** at any time. At least twice a year (April and October) there will be a ballot process run by the Steering Council. Voting should be done by secret ballot. Each existing core developer for that project receives a number of votes equivalent to the number of nominees on the ballot. For example, if there are four nominees, then each existing core developer has four votes. The core developer may cast those votes however they choose, but may not vote for a single nominee more than once. A nominee must receive two-thirds approval from the number of cast ballots (not the number of eligible ballots). Once accepted by the core developers, it is the responsibility of the Steering Council to approve and finalize the nomination. The Steering Council does not have the right to determine whether a nominee is meritorious enough to receive the core developer title. However, they do retain the right to override a vote in cases where the health of the community would so require.\n\nOnce the vote has been held, the aggregated voting results are published on the Community Forums. The nominee is entitled to request an explanation of any override against them. A nominee that fails to be admitted as a core developer may be nominated again in the future.\n\nIt is important to recognize that being a core developer is a privilege, not a right. That privilege must be earned and once earned it can be removed by the Steering Council (see next section) in extreme circumstances. However, under normal circumstances the core developer title exists for as long as the individual wishes to continue engaging with the project and community.\n\nA committer who shows an above-average level of contribution to the project, particularly with respect to its strategic direction and long-term health, may be nominated to become a member of the Steering Council, or a Release Manager. This role is described below.\n\n_What are the rights and responsibilities of core developers?_\n\nAs discussed, the majority of decisions to be made are by consensus building. In certain circumstances where an issue has become more contentious, or a major decision needs to be made, the Release Manager or Steering Council may decide (or be required) to implement the RFC process, which is outlined in more detail below.\n\nIt is also incumbent upon core developers to have a voice in the governance of the community. All core developers for all of the projects have the ability to be nominated to be on the Steering Council and vote in their elections.\n\nThis Policy (the “SCOPE”) may only be changed under the authority of two-thirds of active core developers, except that in the first six (6) months after adoption, the core developers reserve the right to make changes under the authority of a simple majority of active core developers.\n\n_What if a core developer becomes inactive?_\n\nIt is hoped that all core developers participate and remain active on a regular basis in their projects. However, it is also understood that such commitments may not be realistic or possible from time to time.\n\nTherefore, the Steering Council has the duty to encourage participation and the responsibility to place core developers into an inactive status if they are no longer willing or capable to participate. The main purpose of this is **not to punish** a person for behavior, but to help the development process to continue for those that do remain active.\n\nTo this end, a core developer that becomes “inactive” shall not have commit rights to a repository, and shall not participate in any votes. To be eligible to vote in an election, a core developer **must have been active** at the time of the previous scheduled project release.\n\nInactive members may ask the Steering Council to reinstate their status at any time, and upon such request the Steering Council shall make the core developer active again.\n\nIndividuals that know they will be unable to maintain their active status for a period are asked to be in communication with the Steering Council and declare themselves inactive if necessary.\n\nAn “active” core developer is an individual that has participated in a meaningful way during the previous six months. Any further definition is within the discretion of the Steering Council.\n\n### Release Manager\n\nCore developers shall have access only to make commits and merges on non-protected branches. The “master” branch and other protected branches are controlled by the release management team for that project. Release managers shall be elected from the core development team by the core development team, and shall serve for a full release cycle.\n\nEach core developer team may decide how many release managers to have for each release cycle. It is highly encouraged that there be at least two release managers for a release cycle to help divide the responsibilities and not force too much effort upon a single person. However, there also should not be so many managers that their efforts are impeded.\n\nThe main responsibilities of the release management team include:\n\n*   push the development cycle forward by monitoring and facilitating technical discussions\n*   establish a release calendar and perform actions required to release packages\n*   approve pull requests to the master branch and other protected branches\n*   merge pull requests to the master branch and other protected branches\n\nThe release managers **do not have the authority to veto or withhold a merge** of a pull request that otherwise meets contribution criteria and has been accepted by the community. It is not their responsibility to decide what should be developed, but rather that the decisions of the community are carried out and that the project is being moved forward.\n\nFrom time to time, a decision may need to be made that cannot be achieved through consensus. In that case, the release managers have the authority to call upon the removal of the decision to the RFC process. This should not occur regularly (unless required as discussed below), and its use should be discouraged in favor of the more communal consensus building strategy.\n\nSince not all projects have the same requirements, the specifics governing release managers on a project shall be set forth in an Appendix to this Policy, or in the project’s contribution guidelines.\n\nIf necessary, the Steering Council has the right to remove a release manager that is derelict in their duties, or for other good cause.\n\n### Steering Council\n\nThe Steering Council is the governing body consisting of those individuals identified as the “project owner” and having control of the resources and assets of the SCO. Their ultimate goal is to ensure the smooth operation of the projects by removing impediments, and assisting the members as needed. It is expected that they will be regular voices in the community.\n\n_What can the Steering Council do?_\n\nThe members of the Steering Council **do not individually have any more authority than any other core developer**, and shall not have any additional rights to make decisions, commits, merges, or the like on a project.\n\nHowever, as a body, the Steering Council has the following capacity:\n\n*   accept, remand, and reject all RFCs\n*   enforce the community code of conduct\n*   administer community assets such as repositories, servers, forums, integration services, and the like (or, to delegate such authority to someone else)\n*   place core developers into inactive status where appropriate take any other enforcement measures afforded to it in this Policy, including, in extreme cases, removing core developers\n*   adopt or remove projects from the community umbrella\n\nIt is highly encouraged that the Steering Council delegate its authority as much as possible, and where appropriate, to other willing community members.\n\nThe Steering Council **does not have the authority** to change this Policy.\n\n_How many members are on the Steering Council?_\n\nFour.\n\nWhile it seems like a committee with four votes may potentially end in a deadlock with no way to break a majority vote, the Steering Council is discouraged from voting as much as possible. Instead, it should try to work by consensus, and requires three consenting votes when it is necessary to vote on a matter.\n\n_How long do members serve on the Steering Council?_\n\nA single term shall be for two calendar years starting in January. Terms shall be staggered so that each year there are two members continuing from the previous year’s council.\n\nTherefore, the inaugural vote shall have two positions available for a two year term, and two positions available for a one year term.\n\nThere are no limits to the number of terms that can be served, and it is possible for an individual to serve consecutive terms.\n\n_Who runs the Steering Council?_\n\nAfter the Steering Council is elected, the group shall collectively decide upon one person to act as the Chair. The Chair does not have any additional rights or authority over any other member of the Steering Council.\n\nThe role of the Chair is merely as a coordinator and facilitator. The Chair is expected to ensure that all governance processes are adhered to. The position is more administrative and clerical, and is expected that the Chair sets agendas and coordinates discussion of the group.\n\n_How are council members elected?_\n\nOnce a year, **all eligible core developers** for each of the projects shall have the right to elect members to the Steering Council.\n\nNominations shall be open from September 1 and shall close on September 30. After that, voting shall begin on October 1 and shall close on October 31. Every core developer active on the date of the June release of the Sanic Framework for that year shall be eligible to receive one vote per vacant seat on the Steering Council. For the sake of clarity, to be eligible to vote, a core developer **does not** need to be a core developer on Sanic Framework, but rather just have been active within their respective project on that date.\n\nThe top recipients of votes shall be declared the winners. If there is any tie, it is highly encouraged that the tied nominees themselves resolve the dispute before a decision is made at random.\n\nIn regards to the inaugural vote of the Steering Council, the top two vote-recipients shall serve for two years, and the next two vote-recipients shall assume the one-year seats.\n\nTo be an eligible candidate for the Steering Council, the individual must have been a core developer in active status on at least one project for the previous twelve months.\n\n_What if there is a vacancy?_\n\nIf a vacancy on the Steering Council exists during a term, then the next highest vote-recipient in the previous election shall be offered to complete the remainder of the term. If one cannot be found this way, the Steering Council may decide the most appropriate course of action to fill the seat (whether by appointment, vote, or other means).\n\nIf a member of the Steering Council becomes inactive, then that individual shall be removed from the Steering Council immediately and the seat shall become vacant.\n\nIn extreme cases, the body of all core developers has the right to bring a vote to remove a member of the Steering Council for cause by a two-thirds majority of all eligible voting core developers.\n\n_How shall the Steering Council conduct its business?_\n\nAs much as possible, the Steering Council shall conduct its business and discussions in the open. Any member of the community should be allowed to enter the conversation with them. However, at times it may be necessary or appropriate for discussions to be held privately. Selecting the proper venue for conversations is part of the administrative duties of the Chair.\n\nWhile the specifics of how to operate are beyond the scope of the Policy, it is encouraged that the Steering Council attempt to meet at least one time per quarter in a “real-time” discussion. This could be achieved via video conferencing, live chatting, or other appropriate means.\n\nSupport\n-------\n\nAll participants in the community are encouraged to provide support for users within the project management infrastructure. This support is provided as a way of growing the community. Those seeking support should recognize that all support activity within the project is voluntary and is therefore provided as and when time allows. A user requiring guaranteed response times or results should therefore seek to purchase a support contract from a community member. However, for those willing to engage with the project on its own terms, and willing to help support other users, the community support channels are ideal.\n\nDecision making process\n-----------------------\n\nDecisions about the future of the projects are made through discussion with all members of the community, from the newest user to the most experienced member. Everyone has a voice.\n\nAll non-sensitive project management discussion takes place on the community forums, or other designated channels. Occasionally, sensitive discussions may occur in private.\n\nIn order to ensure that the project is not bogged down by endless discussion and continual voting, the project operates a policy of **lazy consensus**. This allows the majority of decisions to be made without resorting to a formal vote. For any **major decision** (as defined below), there is a separate Request for Comment (RFC) process.\n\n### Technical decisions\n\nPull requests and technical decisions should generally fall into the following categories.\n\n*   **Routine**: Documentation fixes, code changes that are for cleanup or additional testing. No functionality changes.\n*   **Minor**: Changes to the code base that either fix a bug, or introduce a trivial feature. No breaking changes.\n*   **Major**: Any change to the code base that breaks or deprecates existing API, alters operation in a non-trivial manner, or adds a significant feature.\n\nIt is generally the responsibility of the release managers to make sure that changes to the repositories receive the proper authorization before merge.\n\nThe release managers retain the authority to individually review and accept routine decisions that meet standards for code quality without additional input.\n\n### Lazy consensus\n\nDecision making (whether by the community or Steering Council) typically involves the following steps:\n\n*   proposal\n*   discussion\n*   vote (if consensus is not reached through discussion)\n*   decision\n\nAny community member can make a proposal for consideration by the community. In order to initiate a discussion about a new idea, they should post a message on the appropriate channel on the Community forums, or submit a pull request implementing the idea on GitHub. This will prompt a review and, if necessary, a discussion of the idea.\n\nThe goal of this review and discussion is to gain approval for the contribution. Since most people in the project community have a shared vision, there is often little need for discussion in order to reach consensus.\n\nIn general, as long as nobody explicitly opposes a proposal or patch, it is recognized as having the support of the community. This is called lazy consensus; that is, those who have not stated their opinion explicitly have implicitly agreed to the implementation of the proposal.\n\nLazy consensus is a very important concept within the SCO. It is this process that allows a large group of people to efficiently reach consensus, as someone with no objections to a proposal need not spend time stating their position, and others need not spend time reading such messages.\n\nFor lazy consensus to be effective, it is necessary to allow an appropriate amount of time before assuming that there are no objections to the proposal. This is somewhat dependent upon the circumstances, but it is generally assumed that 72 hours is reasonable. This requirement ensures that everyone is given enough time to read, digest and respond to the proposal. This time period is chosen so as to be as inclusive as possible of all participants, regardless of their location and time commitments. The facilitators of discussion (whether it be the Chair or the Release Managers, where applicable) shall be charged with determining the proper length of time for such consensus to be reached.\n\nAs discussed above regarding so-called routine decisions, the release managers have the right to make decisions within a shorter period of time. In such cases, lazy consensus shall be implied.\n\n### Request for Comment (RFC)\n\nThe Steering Council shall be in charge of overseeing the RFC process. It shall be a process that remains open to debate to all members of the community, and shall allow for ample time to consider a proposal and for members to respond and engage in meaningful discussion.\n\nThe final decision is vested with the Steering Council. However, it is strongly discouraged that the Steering Council adopt a decision that is contrary to any consensus that may exist in the community. From time to time this may happen if there is a conflict between consensus and the overall project and community goals.\n\nAn RFC shall be initiated by submission to the Steering Council in the public manner as set forth by the Steering Council. Debate shall continue and be facilitated by the Steering Council in general, and the Chair specifically.\n\nIn circumstances that the Steering Council feels it is appropriate, the RFC process may be waived in favor of lazy consensus.\n"
  },
  {
    "path": "guide/content/en/plugins/sanic-ext/configuration.md",
    "content": "---\ntitle: Sanic Extensions - Configuration\n---\n\n# Configuration\n\nSanic Extensions can be configured in all of the same ways that [you can configure Sanic](../../guide/running/configuration.md). That makes configuring Sanic Extensions very easy.\n\n```python\napp = Sanic(\"MyApp\")\napp.config.OAS_URL_PREFIX = \"/apidocs\"\n```\n\nHowever, there are a few more configuration options that should be considered.\n\n## Manual `extend`\n\n.. column:: \n\n    Even though Sanic Extensions will automatically attach to your application, you can manually choose `extend`. When you do that, you can pass all of the configuration values as a keyword arguments (lowercase).\n\n.. column:: \n\n    ```python\n    app = Sanic(\"MyApp\")\n    app.extend(oas_url_prefix=\"/apidocs\")\n    ```\n\n.. column:: \n\n    Or, alternatively they could be passed all at once as a single `dict`.\n\n.. column:: \n\n    ```python\n    app = Sanic(\"MyApp\")\n    app.extend(config={\"oas_url_prefix\": \"/apidocs\"})\n    ```\n\n.. column:: \n\n    Both of these solutions suffers from the fact that the names of the configuration settings are not discoverable by an IDE. Therefore, there is also a type annotated object that you can use. This should help the development experience.\n\n.. column:: \n\n    ```python\n    from sanic_ext import Config\n\n    app = Sanic(\"MyApp\")\n    app.extend(config=Config(oas_url_prefix=\"/apidocs\"))\n    ```\n\n## Settings\n\n.. note::\n\n    Often, the easiest way to change these for an application (since they likely are not going to change dependent upon an environment), is to set them directly on the `app.config` object.\n\n    Simply use the capitalized version of the configuration key as shown here:\n\n    ```python\n    app = Sanic(\"MyApp\")\n    app.config.OAS_URL_PREFIX = \"/apidocs\"\n    ```\n\n### `cors`\n\n- **Type**: `bool`\n- **Default**: `True`\n- **Description**: Whether to enable CORS protection\n\n### `cors_allow_headers`\n\n- **Type**: `str`\n- **Default**: `\"*\"`\n- **Description**: Value of the header: `access-control-allow-headers`\n\n### `cors_always_send`\n\n- **Type**: `bool`\n- **Default**: `True`\n- **Description**: Whether to always send the header: `access-control-allow-origin`\n\n### `cors_automatic_options`\n\n- **Type**: `bool`\n- **Default**: `True`\n- **Description**: Whether to automatically generate `OPTIONS` endpoints for routes that do *not* already have one defined\n\n### `cors_expose_headers`\n\n- **Type**: `str`\n- **Default**: `\"\"`\n- **Description**: Value of the header: `access-control-expose-headers`\n\n### `cors_max_age`\n\n- **Type**: `int`\n- **Default**: `5`\n- **Description**: Value of the header: `access-control-max-age`\n\n### `cors_methods`\n\n- **Type**: `str`\n- **Default**: `\"\"`\n- **Description**: Value of the header: `access-control-access-control-allow-methods`\n\n### `cors_origins`\n\n- **Type**: `str`\n- **Default**: `\"\"`\n- **Description**: Value of the header: `access-control-allow-origin`\n\n\n.. warning:: \n    \n    Be very careful if you place `*` here. Do not do this unless you know what you are doing as it can be a security issue.\n\n\n### `cors_send_wildcard`\n\n- **Type**: `bool`\n- **Default**: `False`\n- **Description**: Whether to send a wildcard origin instead of the incoming request origin\n\n### `cors_supports_credentials`\n\n- **Type**: `bool`\n- **Default**: `False`\n- **Description**: Value of the header: `access-control-allow-credentials`\n\n### `cors_vary_header`\n\n- **Type**: `bool`\n- **Default**: `True`\n- **Description**: Whether to add the `vary` header\n\n### `http_all_methods`\n\n- **Type**: `bool`\n- **Default**: `True`\n- **Description**: Adds the HTTP `CONNECT` and `TRACE` methods as allowable\n\n### `http_auto_head`\n\n- **Type**: `bool`\n- **Default**: `True`\n- **Description**: Automatically adds `HEAD` handlers to any `GET` routes\n\n### `http_auto_options`\n\n- **Type**: `bool`\n- **Default**: `True`\n- **Description**: Automatically adds `OPTIONS` handlers to any routes without\n\n### `http_auto_trace`\n\n- **Type**: `bool`\n- **Default**: `False`\n- **Description**: Automatically adds `TRACE` handlers to any routes without\n\n### `oas`\n\n- **Type**: `bool`\n- **Default**: `True`\n- **Description**: Whether to enable OpenAPI specification generation\n\n### `oas_autodoc`\n\n- **Type**: `bool`\n- **Default**: `True`\n- **Description**: Whether to automatically extract OpenAPI details from the docstring of a route function\n\n### `oas_ignore_head`\n\n- **Type**: `bool`\n- **Default**: `True`\n- **Description**: WHen `True`, it will not add `HEAD` endpoints into the OpenAPI specification\n\n### `oas_ignore_options`\n\n- **Type**: `bool`\n- **Default**: `True`\n- **Description**: WHen `True`, it will not add `OPTIONS` endpoints into the OpenAPI specification\n\n### `oas_path_to_redoc_html`\n\n- **Type**: `Optional[str]`\n- **Default**: `None`\n- **Description**: Path to HTML file to override the existing Redoc HTML\n\n### `oas_path_to_swagger_html`\n\n- **Type**: `Optional[str]`\n- **Default**: `None`\n- **Description**: Path to HTML file to override the existing Swagger HTML\n\n### `oas_ui_default`\n\n- **Type**: `Optional[str]`\n- **Default**: `\"redoc\"`\n- **Description**: Which OAS documentation to serve on the bare `oas_url_prefix` endpoint; when `None` there will be no documentation at that location\n\n### `oas_ui_redoc`\n\n- **Type**: `bool`\n- **Default**: `True`\n- **Description**: Whether to enable the Redoc UI\n\n### `oas_ui_swagger`\n\n- **Type**: `bool`\n- **Default**: `True`\n- **Description**: Whether to enable the Swagger UI\n\n### `oas_ui_swagger_version`\n\n- **Type**: `str`\n- **Default**: `\"4.1.0\"`\n- **Description**: Which Swagger version to use\n\n### `oas_uri_to_config`\n\n- **Type**: `str`\n- **Default**: `\"/swagger-config\"`\n- **Description**: Path to serve the Swagger configuration\n\n### `oas_uri_to_json`\n\n- **Type**: `str`\n- **Default**: `\"/openapi.json\"`\n- **Description**: Path to serve the OpenAPI JSON\n\n### `oas_uri_to_redoc`\n\n- **Type**: `str`\n- **Default**: `\"/redoc\"`\n- **Description**: Path to Redoc\n\n### `oas_uri_to_swagger`\n\n- **Type**: `str`\n- **Default**: `\"/swagger\"`\n- **Description**: Path to Swagger\n\n### `oas_url_prefix`\n\n- **Type**: `str`\n- **Default**: `\"/docs\"`\n- **Description**: URL prefix for the Blueprint that all of the OAS documentation witll attach to\n\n### `swagger_ui_configuration`\n\n- **Type**: `Dict[str, Any]`\n- **Default**: `{\"apisSorter\": \"alpha\", \"operationsSorter\": \"alpha\", \"docExpansion\": \"full\"}`\n- **Description**: The Swagger documentation to be served to the frontend\n\n### `templating_enable_async`\n\n- **Type**: `bool`\n- **Default**: `True`\n- **Description**: Whether to set `enable_async` on the Jinja `Environment`\n\n### `templating_path_to_templates`\n\n- **Type**: `Union[str, os.PathLike, Sequence[Union[str, os.PathLike]]] `\n- **Default**: `templates`\n- **Description**: A single path, or multiple paths to where your template files are located\n\n### `trace_excluded_headers`\n\n- **Type**: `Sequence[str]`\n- **Default**: `(\"authorization\", \"cookie\")`\n- **Description**: Which headers should be suppresed from responses to `TRACE` requests\n"
  },
  {
    "path": "guide/content/en/plugins/sanic-ext/convenience.md",
    "content": "---\ntitle: Sanic Extensions - Convenience\n---\n\n# Convenience\n\n## Fixed serializer\n\n.. column::\n\n    Often when developing an application, there will be certain routes that always return the same sort of response. When this is the case, you can predefine the return serializer and on the endpoint, and then all that needs to be returned is the content.\n\n.. column::\n\n    ```python\n    from sanic_ext import serializer\n\n    @app.get(\"/<name>\")\n    @serializer(text)\n    async def hello_world(request, name: str):\n        if name.isnumeric():\n            return \"hello \" * int(name)\n        return f\"Hello, {name}\"\n    ```\n\n\n\n.. column::\n\n    The `serializer` decorator also can add status codes.\n\n.. column::\n\n    ```python\n    from sanic_ext import serializer\n\n    @app.post(\"/\")\n    @serializer(text, status=202)\n    async def create_something(request):\n        ...\n    ```\n\n## Custom serializer\n\n.. column::\n\n    Using the `@serializer` decorator, you can also pass your own custom functions as long as they also return a valid type (`HTTPResonse`).\n\n.. column::\n\n    ```python\n    def message(retval, request, action, status):\n        return json(\n            {\n                \"request_id\": str(request.id),\n                \"action\": action,\n                \"message\": retval,\n            },\n            status=status,\n        )\n\n    @app.post(\"/<action>\")\n    @serializer(message)\n    async def do_action(request, action: str):\n        return \"This is a message\"\n    ```\n\n\n.. column::\n\n    Now, returning just a string should return a nice serialized output.\n\n.. column::\n\n    ```python\n    $ curl localhost:8000/eat_cookies -X POST\n    {\n      \"request_id\": \"ef81c45b-235c-46dd-9dbd-b550f8fa77f9\",\n      \"action\": \"eat_cookies\",\n      \"message\": \"This is a message\"\n    }\n\n    ```\n\n\n## Request counter\n\n.. column::\n\n    Sanic Extensions comes with a subclass of `Request` that can be setup to automatically keep track of the number of requests processed per worker process. To enable this, you should pass the `CountedRequest` class to your application contructor.\n\n.. column::\n\n    ```python\n    from sanic_ext import CountedRequest\n\n    app = Sanic(..., request_class=CountedRequest)\n    ```\n\n\n.. column::\n\n    You will now have access to the number of requests served during the lifetime of the worker process.\n\n.. column::\n\n    ```python\n    @app.get(\"/\")\n    async def handler(request: CountedRequest):\n        return json({\"count\": request.count})\n    ```\n\nIf possible, the request count will also be added to the [worker state](../../guide/running/manager.md#worker-state).\n\n![](https://user-images.githubusercontent.com/166269/190922460-43bd2cfc-f81a-443b-b84f-07b6ce475cbf.png)\n"
  },
  {
    "path": "guide/content/en/plugins/sanic-ext/custom.md",
    "content": "---\ntitle: Sanic Extensions - Custom\n---\n\n# Custom extensions\n\nIt is possible to create your own custom extensions.\n\nVersion 22.9 added the `Extend.register` [method](#extension-preregistration). This makes it extremely easy to add custom expensions to an application.\n\n## Anatomy of an extension\n\nAll extensions must subclass `Extension`.\n\n### Required\n\n- `name`: By convention, the name is an all-lowercase string\n- `startup`: A method that runs when the extension is added\n\n### Optional\n\n- `label`: A method that returns additional information about the extension in the MOTD\n- `included`: A method that returns a boolean whether the extension should be enabled or not (could be used for example to check config state)\n\n### Example\n\n```python\nfrom sanic import Request, Sanic, json\nfrom sanic_ext import Extend, Extension\n\napp = Sanic(__name__)\napp.config.MONITOR = True\n\nclass AutoMonitor(Extension):\n    name = \"automonitor\"\n\n    def startup(self, bootstrap) -> None:\n        if self.included():\n            self.app.before_server_start(self.ensure_monitor_set)\n            self.app.on_request(self.monitor)\n\n    @staticmethod\n    async def monitor(request: Request):\n        if request.route and request.route.ctx.monitor:\n            print(\"....\")\n\n    @staticmethod\n    async def ensure_monitor_set(app: Sanic):\n        for route in app.router.routes:\n            if not hasattr(route.ctx, \"monitor\"):\n                route.ctx.monitor = False\n\n    def label(self):\n        has_monitor = [\n            route\n            for route in self.app.router.routes\n            if getattr(route.ctx, \"monitor\", None)\n        ]\n        return f\"{len(has_monitor)} endpoint(s)\"\n\n    def included(self):\n        return self.app.config.MONITOR\n\nExtend.register(AutoMonitor)\n\n@app.get(\"/\", ctx_monitor=True)\nasync def handler(request: Request):\n    return json({\"foo\": \"bar\"})\n```\n\n## Extension preregistration\n\n.. column::\n\n    `Extend.register` simplifies the addition of custom extensions.\n\n.. column::\n\n    ```python\n    from sanic_ext import Extend, Extension\n\n    class MyCustomExtension(Extension):\n        ...\n\n    Extend.register(MyCustomExtension())\n    ```\n\n*Added in v22.9*\n"
  },
  {
    "path": "guide/content/en/plugins/sanic-ext/getting-started.md",
    "content": "---\ntitle: Sanic Extensions - Getting Started\n---\n\n# Getting Started\n\nSanic Extensions is an *officially supported* plugin developed, and maintained by the SCO. The primary goal of this project is to add additional features to help Web API and Web application development easier.\n\n## Features\n\n- CORS protection\n- Template rendering with Jinja\n- Dependency injection into route handlers\n- OpenAPI documentation with Redoc and/or Swagger\n- Predefined, endpoint-specific response serializers\n- Request query arguments and body input validation\n- Auto create `HEAD`, `OPTIONS`, and `TRACE` endpoints\n\n## Minimum requirements\n\n- **Python**: 3.8+\n- **Sanic**: 21.9+\n\n## Install\n\nThe best method is to just install Sanic Extensions along with Sanic itself:\n\n```bash\npip install sanic[ext]\n```\n\nYou can of course also just install it by itself.\n\n```bash\npip install sanic-ext\n```\n\n## Extend your application\n\nOut of the box, Sanic Extensions will enable a bunch of features for you. \n\n.. column::\n\n    To setup Sanic Extensions (v21.12+), you need to do: **nothing**. If it is installed in the environment, it is setup and ready to go.\n\n    This code is the Hello, world app in the [Sanic Getting Started page](../../guide/getting-started.md) _without any changes_, but using Sanic Extensions with `sanic-ext` installed in the background.\n\n.. column::\n\n    ```python\n    from sanic import Sanic\n    from sanic.response import text\n\n    app = Sanic(\"MyHelloWorldApp\")\n\n    @app.get(\"/\")\n    async def hello_world(request):\n        return text(\"Hello, world.\")\n    ```\n\n\n.. column::\n\n    **_OLD DEPRECATED SETUP_**\n\n    In v21.9, the easiest way to get started is to instantiate it with `Extend`.\n\n    If you look back at the Hello, world app in the [Sanic Getting Started page](../../guide/getting-started.md), you will see the only additions here are the two highlighted lines.\n\n.. column::\n\n    ```python\n    from sanic import Sanic\n    from sanic.response import text\n    from sanic_ext import Extend\n\n    app = Sanic(\"MyHelloWorldApp\")\n    Extend(app)\n\n    @app.get(\"/\")\n    async def hello_world(request):\n        return text(\"Hello, world.\")\n    ```\n\nRegardless of how it is setup, you should now be able to view the OpenAPI documentation and see some of the functionality in action: [http://localhost:8000/docs](http://localhost:8000/docs).\n"
  },
  {
    "path": "guide/content/en/plugins/sanic-ext/health-monitor.md",
    "content": "---\ntitle: Sanic Extensions - Health Monitor\n---\n\n# Health monitor\n\nThe health monitor requires both `sanic>=22.9` and `sanic-ext>=22.9`.\n\nYou can setup Sanic Extensions to monitor the health of your worker processes. This requires that you not be in [single process mode](../../guide/running/manager.md#single-process-mode).\n\n## Setup\n\n.. column::\n\n    Out of the box, the health monitor is disabled. You will need to opt-in and enable the endpoint if you would like to use it.\n\n.. column::\n\n    ```python\n    app.config.HEALTH = True\n    app.config.HEALTH_ENDPOINT = True\n    ```\n\n## How does it work\n\nThe monitor sets up a new background process that will periodically receive acknowledgements of liveliness from each worker process. If a worker process misses a report too many times, then the monitor will restart that one worker.\n\n## Diagnostics endpoint\n\n.. column::\n\n    The health monitor will also enable a diagnostics endpoint that outputs the [worker state](../../guide/running/manager.md#worker-state). By default is id disabled.\n\n    .. danger:: \n\n        The diagnostics endpoint is not secured. If you are deploying it in a production environment, you should take steps to protect it with a proxy server if you are using one. If not, you may want to consider disabling this feature in production since it will leak details about your server state.\n\n.. column::\n\n    ```\n    $ curl http://localhost:8000/__health__\n    {\n        'Sanic-Main': {'pid': 99997},\n        'Sanic-Server-0-0': {\n            'server': True,\n            'state': 'ACKED',\n            'pid': 9999,\n            'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc),\n            'starts': 2,\n            'restart_at': datetime.datetime(2022, 10, 1, 0, 0, 12, 861332, tzinfo=datetime.timezone.utc)\n        },\n        'Sanic-Reloader-0': {\n            'server': False,\n            'state': 'STARTED',\n            'pid': 99998,\n            'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc),\n            'starts': 1\n        }\n    }\n    ```\n\n\n## Configuration\n\n| Key | Type | Default| Description |\n|--|--|--|--|\n| HEALTH | `bool` | `False` | Whether to enable this extension. |\n| HEALTH_ENDPOINT | `bool` | `False` | Whether to enable the diagnostics endpoint. |\n| HEALTH_MAX_MISSES | `int` | `3` | The number of consecutive misses before a worker process is restarted. |\n| HEALTH_MISSED_THRESHHOLD | `int` | `10` | The number of seconds the monitor checks for worker process health. |\n| HEALTH_MONITOR | `bool` | `True` | Whether to enable the health monitor. |\n| HEALTH_REPORT_INTERVAL | `int` | `5` | The number of seconds between reporting each acknowledgement of liveliness. |\n| HEALTH_URI_TO_INFO | `str` | `\"\"` | The URI path of the diagnostics endpoint. |\n| HEALTH_URL_PREFIX | `str` | `\"/__health__\"` | The URI prefix of the diagnostics blueprint. |\n"
  },
  {
    "path": "guide/content/en/plugins/sanic-ext/http/cors.md",
    "content": "---\ntitle: Sanic Extensions - CORS protection\n---\n\n# CORS protection\n\nCross-Origin Resource Sharing (aka CORS) is a *huge* topic by itself. The documentation here cannot go into enough detail about *what* it is. You are highly encouraged to do some research on your own to understand the security problem presented by it, and the theory behind the solutions. [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) are a great first step.\n\nIn super brief terms, CORS protection is a framework that browsers use to facilitate how and when a web page can access information from another domain. It is extremely relevant to anyone building a single-page application. Often times your frontend might be on a domain like `https://portal.myapp.com`, but it needs to access the backend from `https://api.myapp.com`.\n\nThe implementation here is heavily inspired by [`sanic-cors`](https://github.com/ashleysommer/sanic-cors), which is in turn based upon [`flask-cors`](https://github.com/corydolphin/flask-cors). It is therefore very likely that you can achieve a near drop-in replacement of `sanic-cors` with `sanic-ext`.\n\n## Basic implementation\n\n.. column::\n\n    As shown in the example in the [auto-endpoints example](methods.md#options), Sanic Extensions will automatically enable CORS protection without further action. But, it does not offer too much out of the box.\n\n    At a *bare minimum*, it is **highly** recommended that you set `config.CORS_ORIGINS` to the intended origin(s) that will be accessing the application.\n\n.. column::\n\n    ```python\n    from sanic import Sanic, text\n    from sanic_ext import Extend\n\n    app = Sanic(__name__)\n    app.config.CORS_ORIGINS = \"http://foobar.com,http://bar.com\"\n    Extend(app)\n\n    @app.get(\"/\")\n    async def hello_world(request):\n        return text(\"Hello, world.\")\n    ```\n\n    ```\n    $ curl localhost:8000 -X OPTIONS -i\n    HTTP/1.1 204 No Content\n    allow: GET,HEAD,OPTIONS\n    access-control-allow-origin: http://foobar.com\n    connection: keep-alive\n    ```\n\n## Configuration\n\nThe true power of CORS protection, however, comes into play once you start configuring it. Here is a table of all of the options.\n\n| Key | Type | Default| Description |\n|--|--|--|--|\n| `CORS_ALLOW_HEADERS` | `str` or `List[str]` | `\"*\"` | The list of headers that will appear in `access-control-allow-headers`. |\n| `CORS_ALWAYS_SEND` | `bool` | `True` | When `True`, will always set a value for `access-control-allow-origin`. When `False`, will only set it if there is an `Origin` header. |\n| `CORS_AUTOMATIC_OPTIONS` | `bool` | `True` | When the incoming preflight request is received, whether to automatically set values for `access-control-allow-headers`, `access-control-max-age`, and `access-control-allow-methods` headers. If `False` these values will only be set on routes that are decorated with the `@cors` decorator. |\n| `CORS_EXPOSE_HEADERS` | `str` or `List[str]` | `\"\"` | Specific list of headers to be set in `access-control-expose-headers` header. |\n| `CORS_MAX_AGE` | `str`, `int`, `timedelta` | `0` | The maximum number of seconds the preflight response may be cached using the `access-control-max-age` header. A falsey value will cause the header to not be set. |\n| `CORS_METHODS` | `str` or `List[str]` | `\"\"` | The HTTP methods that the allowed origins can access, as set on the `access-control-allow-methods` header. |\n| `CORS_ORIGINS` | `str`, `List[str]`, `re.Pattern` | `\"*\"` | The origins that are allowed to access the resource, as set on the `access-control-allow-origin` header. |\n| `CORS_SEND_WILDCARD` | `bool` | `False` | If `True`, will send the wildcard `*` origin instead of the `origin` request header. |\n| `CORS_SUPPORTS_CREDENTIALS` | `bool` | `False` | Whether to set the `access-control-allow-credentials` header. |\n| `CORS_VARY_HEADER` | `bool` | `True` | Whether to add `vary` header, when appropriate. |\n\n*For the sake of brevity, where the above says `List[str]` any instance of a `list`, `set`, `frozenset`, or `tuple` will be acceptable. Alternatively, if the value is a `str`, it can be a comma delimited list.*\n\n## Route level overrides\n\n.. column::\n\n    It may sometimes be necessary to override app-wide settings for a specific route. To allow for this, you can use the `@sanic_ext.cors()` decorator to set different route-specific values.\n\n    The values that can be overridden with this decorator are:\n\n    - `origin`\n    - `expose_headers`\n    - `allow_headers`\n    - `allow_methods`\n    - `supports_credentials`\n    - `max_age`\n\n.. column::\n\n    ```python\n    from sanic_ext import cors\n\n    app.config.CORS_ORIGINS = \"https://foo.com\"\n\n    @app.get(\"/\", host=\"bar.com\")\n    @cors(origin=\"https://bar.com\")\n    async def hello_world(request):\n        return text(\"Hello, world.\")\n    ```\n\n"
  },
  {
    "path": "guide/content/en/plugins/sanic-ext/http/methods.md",
    "content": "---\ntitle: Sanic Extensions - HTTP Methods\n---\n\n# HTTP Methods\n\n## Auto-endpoints\n\nThe default behavior is to automatically generate `HEAD` endpoints for all `GET` routes, and `OPTIONS` endpoints for all\nroutes. Additionally, there is the option to automatically generate `TRACE` endpoints. However, these are not enabled by\ndefault.\n\n### HEAD\n\n.. column::\n\n    - **Configuration**: `AUTO_HEAD` (default `True`)\n    - **MDN**: [Read more](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD)\n\n    A `HEAD` request provides the headers and an otherwise identical response to what a `GET` request would provide.\n    However, it does not actually return the body.\n\n.. column::\n\n    ```python\n    @app.get(\"/\")\n    async def hello_world(request):\n        return text(\"Hello, world.\")\n    ```\n\n    Given the above route definition, Sanic Extensions will enable `HEAD` responses, as seen here.\n\n    ```\n    $ curl localhost:8000 --head\n    HTTP/1.1 200 OK\n    access-control-allow-origin: *\n    content-length: 13\n    connection: keep-alive\n    content-type: text/plain; charset=utf-8\n    ```\n\n### OPTIONS\n\n.. column::\n\n    - **Configuration**: `AUTO_OPTIONS` (default `True`)\n    - **MDN**: [Read more](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS)\n\n    `OPTIONS` requests provide the recipient with details about how the client is allowed to communicate with a given\n    endpoint.\n\n.. column::\n\n    ```python\n    @app.get(\"/\")\n    async def hello_world(request):\n        return text(\"Hello, world.\")\n    ```\n\n    Given the above route definition, Sanic Extensions will enable `OPTIONS` responses, as seen here.\n\n    It is important to note that we also see `access-control-allow-origins` in this example. This is because\n    the [CORS protection](cors.md) is enabled by default.\n\n    ```\n    $ curl localhost:8000 -X OPTIONS -i\n    HTTP/1.1 204 No Content\n    allow: GET,HEAD,OPTIONS\n    access-control-allow-origin: *\n    connection: keep-alive\n    ```\n\n.. tip::\n    \n    Even though Sanic Extensions will setup these routes for you automatically, if you decide to manually create an `@app.options` route, it will *not* be overridden.\n\n### TRACE\n\n.. column::\n\n    - **Configuration**: `AUTO_TRACE` (default `False`)\n    - **MDN**: [Read more](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/TRACE)\n\n    By default, `TRACE` endpoints will **not** be automatically created. However, Sanic Extensions **will allow** you to\n    create them if you wanted. This is something that is not allowed in vanilla Sanic.\n\n.. column::\n\n    ```python\n    @app.route(\"/\", methods=[\"trace\"])\n    async def handler(request):\n        ...\n    ```\n\n    To enable auto-creation of these endpoints, you must first enable them when extending Sanic.\n\n    ```python\n    from sanic_ext import Extend, Config\n\n    app.extend(config=Config(http_auto_trace=True))\n    ```\n\n    Now, assuming you have some endpoints setup, you can trace them as shown here:\n\n    ```\n    $ curl localhost:8000 -X TRACE\n    TRACE / HTTP/1.1\n    Host: localhost:9999\n    User-Agent: curl/7.76.1\n    Accept: */*\n    ```\n\n.. tip:: \n\n    Setting up `AUTO_TRACE` can be super helpful, especially when your application is deployed behind a proxy since it will help you determine how the proxy is behaving.\n\n## Additional method support\n\nVanilla Sanic allows you to build endpoints with the following HTTP methods:\n\n- [GET](/en/guide/basics/routing.html#get)\n- [POST](/en/guide/basics/routing.html#post)\n- [PUT](/en/guide/basics/routing.html#put)\n- [HEAD](/en/guide/basics/routing.html#head)\n- [OPTIONS](/en/guide/basics/routing.html#options)\n- [PATCH](/en/guide/basics/routing.html#patch)\n- [DELETE](/en/guide/basics/routing.html#delete)\n\nSee [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) for more.\n\n.. column::\n\n    There are, however, two more \"standard\" HTTP methods: `TRACE` and `CONNECT`. Sanic Extensions will allow you to build\n    endpoints using these methods, which would otherwise not be allowed.\n\n    It is worth pointing out that this will *NOT* enable convenience methods: `@app.trace` or `@app.connect`. You need to\n    use `@app.route` as shown in the example here.\n\n.. column::\n\n    ```python\n    @app.route(\"/\", methods=[\"trace\", \"connect\"])\n    async def handler(_):\n        return empty()\n    ```\n\n"
  },
  {
    "path": "guide/content/en/plugins/sanic-ext/injection.md",
    "content": "---\ntitle: Sanic Extensions - Dependency Injection\n---\n\n# Dependency Injection\n\nDependency injection is a method to add arguments to a route handler based upon the defined function signature. Specifically, it looks at the **type annotations** of the arguments in the handler. This can be useful in a number of cases like:\n\n- Fetching an object based upon request headers (like the current session user)\n- Recasting certain objects into a specific type\n- Using the request object to prefetch data\n- Auto inject services\n\nThe `Extend` instance has two basic methods on it used for dependency injection: a lower level `add_dependency`, and a higher level `dependency`. \n\n**Lower level**: `app.ext.add_dependency(...)`\n\n- `type: Type,`: some unique class that will be the type of the object\n- `constructor: Optional[Callable[..., Any]],` (OPTIONAL): a function that will return that type\n\n**Higher level**: `app.ext.dependency(...)`\n\n- `obj: Any`: any object that you would like injected\n- `name: Optional[str]`: some name that could alternately be used as a reference\n\nLet's explore some use cases here.\n\n\n.. warning:: \n\n    If you used dependency injection prior to v21.12, the lower level API method was called `injection`. It has since been renamed to `add_dependency` and starting in v21.12 `injection` is an alias for `add_dependency`. The `injection` method has been deprecated for removal in v22.6.\n\n\n\n## Basic implementation\n\nThe simplest use case would be simply to recast a value.\n\n.. column::\n\n    This could be useful if you have a model that you want to generate based upon the matched path parameters.\n\n.. column::\n\n    ```python\n    @dataclass\n    class IceCream:\n        flavor: str\n\n        def __str__(self) -> str:\n            return f\"{self.flavor.title()} (Yum!)\"\n\n    app.ext.add_dependency(IceCream)\n\n    @app.get(\"/<flavor:str>\")\n    async def ice_cream(request, flavor: IceCream):\n        return text(f\"You chose: {flavor}\")\n    ```\n\n    ```\n    $ curl localhost:8000/chocolate\n    You chose Chocolate (Yum!)\n    ```\n\n\n.. column::\n\n    This works by passing a keyword argument to the constructor of the `type` argument. The previous example is equivalent to this.\n\n.. column::\n\n    ```python\n    flavor = IceCream(flavor=\"chocolate\")\n    ```\n\n## Additional constructors\n\n.. column::\n\n    Sometimes you may need to also pass a constructor. This could be a function, or perhaps even a classmethod that acts as a constructor. In this example, we are creating an injection that will call `Person.create` first.\n\n    Also important to note on this example, we are actually injecting **two (2)** objects! It of course does not need to be this way, but we will inject objects based upon the function signature.\n\n.. column::\n\n    ```python\n    @dataclass\n    class PersonID:\n        person_id: int\n\n    @dataclass\n    class Person:\n        person_id: PersonID\n        name: str\n        age: int\n\n        @classmethod\n        async def create(cls, request: Request, person_id: int):\n            return cls(person_id=PersonID(person_id), name=\"noname\", age=111)\n\n\n    app.ext.add_dependency(Person, Person.create)\n    app.ext.add_dependency(PersonID)\n\n    @app.get(\"/person/<person_id:int>\")\n    async def person_details(\n        request: Request, person_id: PersonID, person: Person\n    ):\n        return text(f\"{person_id}\\n{person}\")\n    ```\n\n    ```\n    $ curl localhost:8000/person/123\n    PersonID(person_id=123)\n    Person(person_id=PersonID(person_id=123), name='noname', age=111)\n    ```\n\nWhen a `constructor` is passed to `ext.add_dependency` (like in this example) that will be called. If not, then the object will be created by calling the `type`. A couple of important things to note about passing a `constructor`:\n\n1. A positional `request: Request` argument is *usually* expected. See the `Person.create` method above as an example using a `request` and [arbitrary constructors](#arbitrary-constructors) for how to use a callable that does not require a `request`.\n1. All matched path parameters are injected as keyword arguments.\n1. Dependencies can be chained and nested. Notice how in the previous example the `Person` dataclass has a `PersonID`? That means that `PersonID` will be called first, and that value is added to the keyword arguments when calling `Person.create`.\n\n## Arbitrary constructors\n\n.. column::\n\n    Sometimes you may want to construct your injectable _without_ the `Request` object. This is useful if you have arbitrary classes or functions that create your objects. If the callable does have any required arguments, then they should themselves be injectable objects.\n\n    This is very useful if you have services or other types of objects that should only exist for the lifetime of a single request. For example, you might use this pattern to pull a single connection from your database pool.\n\n.. column::\n\n    ```python\n    class Alpha:\n        ...\n\n    class Beta:\n        def __init__(self, alpha: Alpha) -> None:\n            self.alpha = alpha\n\n    app.ext.add_dependency(Alpha)\n    app.ext.add_dependency(Beta)\n\n    @app.get(\"/beta\")\n    async def handler(request: Request, beta: Beta):\n        assert isinstance(beta.alpha, Alpha)\n    ```\n\n*Added in v22.9*\n\n## Objects from the `Request`\n\n.. column::\n\n    Sometimes you may want to extract details from the request and preprocess them. You could, for example, cast the request JSON to a Python object, and then add some additional logic based upon DB queries.\n\n    .. warning:: \n\n        If you plan to use this method, you should note that the injection actually happens *before* Sanic has had a chance to read the request body. The headers should already have been consumed. So, if you do want access to the body, you will need to manually consume as seen in this example.\n\n        ```python\n            await request.receive_body()\n        ```\n\n\n        This could be used in cases where you otherwise might:\n\n        - use middleware to preprocess and add something to the `request.ctx`\n        - use decorators to preprocess and inject arguments into the request handler\n\n        In this example, we are using the `Request` object in the `compile_profile` constructor to run a fake DB query to generate and return a `UserProfile` object.\n\n.. column::\n\n    ```python\n    @dataclass\n    class User:\n        name: str\n\n    @dataclass\n    class UserProfile:\n        user: User\n        age: int = field(default=0)\n        email: str = field(default=\"\")\n\n        def __json__(self):\n            return ujson.dumps(\n                {\n                    \"name\": self.user.name,\n                    \"age\": self.age,\n                    \"email\": self.email,\n                }\n            )\n\n    async def fake_request_to_db(body):\n        today = date.today()\n        email = f'{body[\"name\"]}@something.com'.lower()\n        difference = today - date.fromisoformat(body[\"birthday\"])\n        age = int(difference.days / 365)\n        return UserProfile(\n            User(body[\"name\"]),\n            age=age,\n            email=email,\n        )\n\n    async def compile_profile(request: Request):\n        await request.receive_body()\n        profile = await fake_request_to_db(request.json)\n        return profile\n\n    app.ext.add_dependency(UserProfile, compile_profile)\n\n    @app.patch(\"/profile\")\n    async def update_profile(request, profile: UserProfile):\n        return json(profile)\n    ```\n\n    ```\n    $ curl localhost:8000/profile -X PATCH -d '{\"name\": \"Alice\", \"birthday\": \"2000-01-01\"}'\n    {\n        \"name\":\"Alice\",\n        \"age\":21,\n        \"email\":\"alice@something.com\"\n    }\n    ```\n\n## Injecting services\n\nIt is a common pattern to create things like database connection pools and store them on the `app.ctx` object. This makes them available throughout your application, which is certainly a convenience. One downside, however, is that you no longer have a typed object to work with. You can use dependency injections to fix this. First we will show the concept using the lower level `add_dependency` like we have been using in the previous examples. But, there is a better way using the higher level `dependency` method.\n\n### The lower level API using `add_dependency`\n\n.. column::\n\n\n    This works very similar to the [last example](#objects-from-the-request) where the goal is the extract something from the `Request` object. In this example, a database object was created on the `app.ctx` instance, and is being returned in the dependency injection constructor.\n\n.. column::\n\n    ```python\n    class FakeConnection:\n        async def execute(self, query: str, **arguments):\n            return \"result\"\n\n    @app.before_server_start\n    async def setup_db(app, _):\n        app.ctx.db_conn = FakeConnection()\n        app.ext.add_dependency(FakeConnection, get_db)\n\n    def get_db(request: Request):\n        return request.app.ctx.db_conn\n\n\n\n    @app.get(\"/\")\n    async def handler(request, conn: FakeConnection):\n        response = await conn.execute(\"...\")\n        return text(response)\n    ```\n    ```\n    $ curl localhost:8000/\n    result\n    ```\n\n### The higher level API using `dependency`\n\n.. column::\n\n    Since we have an actual *object* that is available when adding the dependency injection, we can use the higher level `dependency` method. This will make the pattern much easier to write.\n\n    This method should always be used when you want to inject something that exists throughout the lifetime of the application instance and is not request specific. It is very useful for services, third party clients, and connection pools since they are not request specific.\n\n.. column::\n\n    ```python\n    class FakeConnection:\n        async def execute(self, query: str, **arguments):\n            return \"result\"\n\n    @app.before_server_start\n    async def setup_db(app, _):\n        db_conn = FakeConnection()\n        app.ext.dependency(db_conn)\n\n    @app.get(\"/\")\n    async def handler(request, conn: FakeConnection):\n        response = await conn.execute(\"...\")\n        return text(response)\n    ```\n    ```\n    $ curl localhost:8000/\n    result\n    ```\n\n## Generic types\n\nBe carefule when using a [generic type](https://docs.python.org/3/library/typing.html#typing.Generic). The way that Sanic's dependency injection works is by matching the entire type definition. Therefore, `Foo` is not the same as `Foo[str]`. This can be particularly tricky when trying to use the [higher-level `dependency` method](#the-higher-level-api-using-dependency) since the type is inferred.\n\n.. column::\n\n    For example, this will **NOT** work as expected since there is no definition for `Test[str]`.\n\n.. column::\n\n    ```python\n    import typing\n    from sanic import Sanic, text\n\n    T = typing.TypeVar(\"T\")\n\n    class Test(typing.Generic[T]):\n        test: T\n\n    app = Sanic(\"testapp\")\n    app.ext.dependency(Test())\n\n    @app.get(\"/\")\n    def test(request, test: Test[str]):\n        ...\n    ```\n\n\n.. column::\n\n    To get this example to work, you will need to add an explicit definition for the type you intend to be injected.\n\n.. column::\n\n    ```python\n    import typing\n    from sanic import Sanic, text\n\n    T = typing.TypeVar(\"T\")\n\n    class Test(typing.Generic[T]):\n        test: T\n\n    app = Sanic(\"testapp\")\n    _singleton = Test()\n    app.ext.add_dependency(Test[str], lambda: _singleton)\n\n    @app.get(\"/\")\n    def test(request, test: Test[str]):\n        ...\n    ```\n\n## Configuration\n\n.. column::\n\n    By default, dependencies will be injected after the `http.routing.after` [signal](../../guide/advanced/signals.md#built-in-signals). Starting in v22.9, you can change this to the `http.handler.before` signal.\n\n.. column::\n\n    ```python\n    app.config.INJECTION_SIGNAL = \"http.handler.before\"\n    ```\n\n*Added in v22.9*\n"
  },
  {
    "path": "guide/content/en/plugins/sanic-ext/logger.md",
    "content": "---\ntitle: Sanic Extensions - Background logger\n---\n\n# Background logger\n\nThe background logger requires both `sanic>=22.9` and `sanic-ext>=22.9`.\n\nYou can setup Sanic Extensions to log all of your messages from a background process. This requires that you not be in [single process mode](../../guide/running/manager.md#single-process-mode).\n\nLogging can sometimes be an expensive operation. By pushing all logging off to a background process, you can potentially gain some performance benefits.\n\n## Setup\n\n.. column::\n\n    Out of the box, the background logger is disabled. You will need to opt-in if you would like to use it.\n\n.. column::\n\n    ```python\n    app.config.LOGGING = True\n    ```\n\n## How does it work\n\nWhen enabled, the extension will create a `multiprocessing.Queue`. It will remove all handlers on the [default Sanic loggers](../../guide/best-practices/logging.md) and replace them with a [`QueueHandler`](https://docs.python.org/3/library/logging.handlers.html#queuehandler). When a message is logged, it will be pushed into the queue by the handler, and read by the background process to the log handlers that were originally in place. This means you can still configure logging as normal and it should \"just work.\"\n\n## Configuration\n\n| Key | Type | Default| Description |\n|--|--|--|--|\n| LOGGING | `bool` | `False` | Whether to enable this extension. |\n| LOGGING_QUEUE_MAX_SIZE | `int` | `4096` | The max size of the queue before messages are rejected. |\n"
  },
  {
    "path": "guide/content/en/plugins/sanic-ext/openapi/advanced.md",
    "content": "---\ntitle: Sanic Extensions - Advanced OAS\n---\n\n# Advanced\n\n_Documentation in progress_\n\n## CBV\n\n## Blueprints\n\n## Components\n"
  },
  {
    "path": "guide/content/en/plugins/sanic-ext/openapi/autodoc.md",
    "content": "---\ntitle: Sanic Extensions - Auto-documentation\n---\n\n# Auto-documentation\n\nTo make documenting endpoints easier, Sanic Extensions will use a function's docstring to populate your documentation. \n\n## Summary and description\n\n.. column::\n\n    A function's docstring will be used to create the summary and description. As you can see from this example here, the docstring has been parsed to use the first line as the summary, and the remainder of the string as the description.\n\n.. column::\n\n    ```python\n    @app.get(\"/foo\")\n    async def handler(request, something: str):\n        \"\"\"This is a simple foo handler\n\n        It is helpful to know that you could also use **markdown** inside your\n        docstrings.\n\n        - one\n        - two\n        - three\"\"\"\n        return text(\">>>\")\n    ```\n    ```json\n    \"paths\": {\n      \"/foo\": {\n        \"get\": {\n          \"summary\": \"This is a simple foo handler\",\n          \"description\": \"It is helpful to know that you could also use **markdown** inside your<br>docstrings.<br><br>- one<br>- two<br>- three\",\n          \"responses\": {\n            \"default\": {\n              \"description\": \"OK\"\n            }\n          },\n          \"operationId\": \"get_handler\"\n        }\n      }\n    }\n    ```\n\n## Operation level YAML\n\n.. column::\n\n    You can expand upon this by adding valid OpenAPI YAML to the docstring. Simply add a line that contains `openapi:`, followed by your YAML. \n\n    The `---` shown in the example is *not* necessary. It is just there to help visually identify the YAML as a distinct section of the docstring.\n\n.. column::\n\n    ```python\n    @app.get(\"/foo\")\n    async def handler(request, something: str):\n        \"\"\"This is a simple foo handler\n\n        Now we will add some more details\n\n        openapi:\n        ---\n        operationId: fooDots\n        tags:\n          - one\n          - two\n        parameters:\n          - name: limit\n            in: query\n            description: How many items to return at one time (max 100)\n            required: false\n            schema:\n              type: integer\n              format: int32\n        responses:\n          '200':\n            description: Just some dots\n        \"\"\"\n        return text(\"...\")\n    ```\n    ```json\n    \"paths\": {\n      \"/foo\": {\n        \"get\": {\n          \"operationId\": \"fooDots\",\n          \"summary\": \"This is a simple foo handler\",\n          \"description\": \"Now we will add some more details\",\n          \"tags\": [\n            \"one\",\n            \"two\"\n          ],\n          \"parameters\": [\n            {\n              \"name\": \"limit\",\n              \"in\": \"query\",\n              \"description\": \"How many items to return at one time (max 100)\",\n              \"required\": false,\n              \"schema\": {\n                \"type\": \"integer\",\n                \"format\": \"int32\"\n              }\n            }\n          ],\n          \"responses\": {\n            \"200\": {\n              \"description\": \"Just some dots\"\n            }\n          }\n        }\n      }\n    }\n    ```\n\n\n\n.. note:: \n\n    When both YAML documentation and decorators are used, it is the content from the decorators that will take priority when generating the documentation.\n\n\n\n## Excluding docstrings\n\n.. column::\n\n    Sometimes a function may contain a docstring that is not meant to be consumed inside the documentation.\n\n    **Option 1**: Globally turn off auto-documentation `app.config.OAS_AUTODOC = False`\n\n    **Option 2**: Disable it for the single handler with the `@openapi.no_autodoc` decorator\n\n.. column::\n\n    ```python\n    @app.get(\"/foo\")\n    @openapi.no_autodoc\n    async def handler(request, something: str):\n        \"\"\"This is a docstring about internal info only. Do not parse it.\n        \"\"\"\n        return text(\"...\")\n    ```\n\n"
  },
  {
    "path": "guide/content/en/plugins/sanic-ext/openapi/basics.md",
    "content": "---\ntitle: Sanic Extensions - Basic OAS\n---\n\n# Basics\n\n\n.. note::\n\n    The OpenAPI implementation in Sanic Extensions is based upon the OAS3 implementation from [`sanic-openapi`](https://github.com/sanic-org/sanic-openapi). In fact, Sanic Extensions is in a large way the successor to that project, which entered maintenance mode upon the release of Sanic Extensions. If you were previously using OAS3 with `sanic-openapi` you should have an easy path to upgrading to Sanic Extensions. Unfortunately, this project does *NOT* support the OAS2 specification.\n\n\n\n.. column::\n\n    Out of the box, Sanic Extensions provides automatically generated API documentation using the [v3.0 OpenAPI specification](https://swagger.io/specification/). There is nothing special that you need to do\n\n.. column::\n\n    ```python\n    from sanic import Sanic\n\n    app = Sanic(\"MyApp\")\n\n    # Add all of your views\n    ```\n\nAfter doing this, you will now have beautiful documentation already generated for you based upon your existing application:\n\n- [http://localhost:8000/docs](http://localhost:8000/docs)\n- [http://localhost:8000/docs/redoc](http://localhost:8000/docs/redoc)\n- [http://localhost:8000/docs/swagger](http://localhost:8000/docs/swagger)\n\nCheckout the [section on configuration](../configuration.md) to learn about changing the routes for the docs. You can also turn off one of the two UIs, and customize which UI will be available on the `/docs` route.\n\n.. column::\n\n    Using [Redoc](https://github.com/Redocly/redoc)\n\n    ![Redoc](/assets/images/sanic-ext-redoc.png)\n\n.. column::\n\n    or [Swagger UI](https://github.com/swagger-api/swagger-ui)\n\n    ![Swagger UI](/assets/images/sanic-ext-swagger.png)\n\n## Changing specification metadata\n\n.. column::\n\n    If you want to change any of the metada, you should use the `describe` method.\n\n    In this example `dedent` is being used with the `description` argument to make multi-line strings a little cleaner. This is not necessary, you can pass any string value here.\n\n.. column::\n\n    ```python\n    from textwrap import dedent\n\n    app.ext.openapi.describe(\n        \"Testing API\",\n        version=\"1.2.3\",\n        description=dedent(\n            \"\"\"\n            # Info\n            This is a description. It is a good place to add some _extra_ doccumentation.\n\n            **MARKDOWN** is supported.\n            \"\"\"\n        ),\n    )\n    ```\n\n"
  },
  {
    "path": "guide/content/en/plugins/sanic-ext/openapi/decorators.md",
    "content": "---\ntitle: Sanic Extensions - OAS Decorators\n---\n\n# Decorators\n\nThe primary mechanism for adding content to your schema is by decorating your endpoints. If you have\nused `sanic-openapi` in the past, this should be familiar to you. The decorators and their arguments match closely\nthe [OAS v3.0 specification](https://swagger.io/specification/).\n\n.. column::\n\n    All of the examples show will wrap around a route definition. When you are creating these, you should make sure that\n    your Sanic route decorator (`@app.route`, `@app.get`, etc) is the outermost decorator. That is to say that you should\n    put that first and then one or more of the below decorators after.\n\n.. column::\n\n    ```python\n    from sanic_ext import openapi\n\n    @app.get(\"/path/to/<something>\")\n    @openapi.summary(\"This is a summary\")\n    @openapi.description(\"This is a description\")\n    async def handler(request, something: str):\n        ...\n    ```\n\n\n.. column::\n\n    You will also see a lot of the below examples reference a model object. For the sake of simplicity, the examples will\n    use `UserProfile` that will look like this. The point is that it can be any well-typed class. You could easily imagine\n    this being a `dataclass` or some other kind of model object.\n\n.. column::\n\n    ```python\n    class UserProfile:\n        name: str\n        age: int\n        email: str\n    ```\n\n## Definition decorator\n\n### `@openapi.definition`\n\nThe `@openapi.definition` decorator allows you to define all parts of an operations on a path at once. It is an omnibums\ndecorator in that it has the same capabilities to create operation definitions as the rest of the decorators. Using\nmultiple field-specific decorators or a single decorator is a style choice for you the developer.\n\nThe fields are purposely permissive in accepting multiple types to make it easiest for you to define your operation.\n\n**Arguments**\n\n| Field         | Type                                                                      |\n| ------------- | --------------------------------------------------------------------------|\n| `body`        | **dict, RequestBody, *YourModel***                                        |\n| `deprecated`  | **bool**                                                                  |\n| `description` | **str**                                                                   |\n| `document`    | **str, ExternalDocumentation**                                            | \n| `exclude`     | **bool**                                                                  |\n| `operation`   | **str**                                                                   |\n| `parameter`   | **str, dict, Parameter, [str], [dict], [Parameter]**                      |\n| `response`    | **dict, Response, *YourModel*, [dict], [Response]**                       |\n| `summary`     | **str**                                                                   |\n| `tag`         | **str, Tag, [str], [Tag]**                                                |\n| `secured`     | **Dict[str, Any]**                                                        |\n\n**Examples**\n\n.. column::\n\n    ```python\n    @openapi.definition(\n        body=RequestBody(UserProfile, required=True),\n        summary=\"User profile update\",\n        tag=\"one\",\n        response=[Success, Response(Failure, status=400)],\n    )\n    ```\n\n.. column::\n\n\n\n*See below examples for more examples. Any of the values for the below decorators can be used in the corresponding\nkeyword argument.*\n\n## Field-specific decorators\n\nAll the following decorators are based on `@openapi`\n\n### body\n\n**Arguments**\n\n| Field       | Type                               |\n| ----------- | ---------------------------------- |\n| **content** | ***YourModel*, dict, RequestBody** |\n\n**Examples**\n\n.. column::\n\n    ```python\n    @openapi.body(UserProfile)\n    ```\n\n    ```python\n    @openapi.body({\"application/json\": UserProfile})\n    ```\n\n    ```python\n    @openapi.body(RequestBody({\"application/json\": UserProfile}))\n    ```\n\n.. column::\n\n    ```python\n    @openapi.body({\"content\": UserProfile})\n    ```\n\n    ```python\n    @openapi.body(RequestBody(UserProfile))\n    ```\n\n    ```python\n    @openapi.body({\"application/json\": {\"description\": ...}})\n    ```\n\n### deprecated\n\n**Arguments**\n\n*None*\n\n**Examples**\n\n.. column::\n\n    ```python\n    @openapi.deprecated()\n    ```\n\n.. column::\n\n    ```python\n    @openapi.deprecated\n    ```\n\n### description\n\n**Arguments**\n\n| Field  | Type    |\n| ------ | ------- |\n| `text` | **str** |\n\n**Examples**\n\n.. column::\n\n    ```python\n    @openapi.description(\n        \"\"\"This is a **description**.\n\n    ## You can use `markdown`\n\n    - And\n    - make\n    - lists.\n    \"\"\"\n    )\n    ```\n\n.. column::\n\n\n### document\n\n**Arguments**\n\n| Field         | Type    |\n| ------------- | ------- |\n| `url`         | **str** |\n| `description` | **str** |\n\n**Examples**\n\n.. column::\n\n    ```python\n    @openapi.document(\"http://example.com/docs\")\n    ```\n\n.. column::\n\n    ```python\n    @openapi.document(ExternalDocumentation(\"http://example.com/more\"))\n    ```\n\n### exclude\n\nCan be used on route definitions like all of the other decorators, or can be called on a Blueprint\n\n**Arguments**\n\n| Field  | Type          | Default  |\n| ------ | ------------- | -------- |\n| `flag` | **bool**      | **True** |\n| `bp`   | **Blueprint** |          |\n\n**Examples**\n\n.. column::\n\n    ```python\n    @openapi.exclude()\n    ```\n\n.. column::\n\n    ```python\n    openapi.exclude(bp=some_blueprint)\n    ```\n\n### operation\n\nSets the operation ID.\n\n**Arguments**\n\n| Field  | Type    |\n| ------ | ------- |\n| `name` | **str** |\n\n**Examples**\n\n.. column::\n\n    ```python\n    @openapi.operation(\"doNothing\")\n    ```\n\n.. column::\n\n\n\n\n**Arguments**\n\n| Field      | Type                                      | Default     |\n| ---------- | ----------------------------------------- | ----------- |\n| `name`     | **str**                                   |             |\n| `schema`   | ***type***                                | **str**     |\n| `location` | **\"query\", \"header\", \"path\" or \"cookie\"** | **\"query\"** |\n\n**Examples**\n\n.. column::\n\n    ```python\n    @openapi.parameter(\"thing\")\n    ```\n\n    ```python\n    @openapi.parameter(parameter=Parameter(\"foobar\", deprecated=True))\n    ```\n\n.. column::\n\n    ```python\n    @openapi.parameter(\"Authorization\", str, \"header\")\n    ```\n\n    ```python\n    @openapi.parameter(\"thing\", required=True, allowEmptyValue=False)\n    ```\n\n### response\n\n**Arguments**\n\nIf using a `Response` object, you should not pass any other arguments.\n\n| Field         | Type                          |\n| ------------- | ----------------------------- |\n| `status`      | **int**                       |\n| `content`     | ***type*, *YourModel*, dict** |\n| `description` | **str**                       |\n| `response`    | **Response**                  |\n\n**Examples**\n\n.. column::\n\n    ```python\n    @openapi.response(200, str, \"This is endpoint returns a string\")\n    ```\n\n    ```python\n    @openapi.response(200, {\"text/plain\": str}, \"...\")\n    ```\n\n    ```python\n    @openapi.response(response=Response(UserProfile, description=\"...\"))\n    ```\n\n    ```python\n    @openapi.response(\n        response=Response(\n            {\n                \"application/json\": UserProfile,\n            },\n            description=\"...\",\n            status=201,\n        )\n    )\n    ```\n\n.. column::\n\n    ```python\n    @openapi.response(200, UserProfile, \"...\")\n    ```\n\n    ```python\n    @openapi.response(\n        200,\n        {\n            \"application/json\": UserProfile,\n        },\n        \"Description...\",\n    )\n    ```\n\n### summary\n\n**Arguments**\n\n| Field  | Type    |\n| ------ | ------- |\n| `text` | **str** |\n\n**Examples**\n\n.. column::\n\n    ```python\n    @openapi.summary(\"This is an endpoint\")\n    ```\n\n.. column::\n\n\n### tag\n\n**Arguments**\n\n| Field   | Type         |\n| ------- | ------------ |\n| `*args` | **str, Tag** |\n\n**Examples**\n\n.. column::\n\n    ```python\n    @openapi.tag(\"foo\")\n    ```\n\n.. column::\n\n    ```python\n    @openapi.tag(\"foo\", Tag(\"bar\"))\n    ```\n\n\n### secured\n\n**Arguments**\n\n| Field             | Type                    |\n| ----------------- | ----------------------- |\n| `*args, **kwargs` | **str, Dict[str, Any]** |\n\n**Examples**\n\n.. column::\n\n    ```python\n    @openapi.secured()\n    ```\n\n.. column::\n\n\n\n.. column::\n\n    ```python\n    @openapi.secured(\"foo\")\n    ```\n\n.. column::\n\n    ```python\n    @openapi.secured(\"token1\", \"token2\")\n    ```\n\n\n.. column::\n\n    ```python\n    @openapi.secured({\"my_api_key\": []})\n    ```\n\n.. column::\n\n    ```python\n    @openapi.secured(my_api_key=[])\n    ```\n\nDo not forget to use `add_security_scheme`. See [security](./security.md) for more details.\n``\n\n## Integration with Pydantic\n\nPydantic models have the ability to [generate OpenAPI schema](https://pydantic-docs.helpmanual.io/usage/schema/). \n\n.. column::\n\n    To take advantage of Pydantic model schema generation, pass the output in place of the schema.\n\n.. column::\n\n    ```python\n    from sanic import Sanic, json\n    from sanic_ext import validate, openapi\n    from pydantic import BaseModel, Field\n\n    @openapi.component()\n    class Item(BaseModel):\n        name: str\n        description: str = None\n        price: float\n        tax: float = None\n\n    class ItemList(BaseModel):\n        items: List[Item]\n\n    app = Sanic(\"test\")\n\n    @app.get(\"/\")\n    @openapi.definition(\n        body={\n            \"application/json\": ItemList.model_json_schema(\n                ref_template=\"#/components/schemas/{model}\"\n            )\n        },\n    )\n    async def get(request):\n        return json({})\n    ```\n\n.. note::\n\n    It is important to set that `ref_template`. By default Pydantic will select a template that is not standard OAS. This will cause the schema to not be found when generating the final document.\n\n*Added in v22.9*\n"
  },
  {
    "path": "guide/content/en/plugins/sanic-ext/openapi/security.md",
    "content": "---\ntitle: Sanic Extensions - OAS Security Schemes\n---\n\n# Security Schemes\n\nTo document authentication schemes, there are two steps.\n\n_Security is only available starting in v21.12.2_\n\n## Document the scheme\n\n.. column::\n\n    The first thing that you need to do is define one or more security schemes. The basic pattern will be to define it as:\n\n    ```python\n    add_security_scheme(\"<NAME>\", \"<TYPE>\")\n    ```\n\n    The `type` should correspond to one of the allowed security schemes: `\"apiKey\"`, `\"http\"`, `\"oauth2\"`, `\"openIdConnect\"`. You can then pass appropriate keyword arguments as allowed by the specification.\n\n    You should consult the [OpenAPI Specification](https://swagger.io/specification/) for details on what values are appropriate.\n\n.. column::\n\n    ```python\n    app.ext.openapi.add_security_scheme(\"api_key\", \"apiKey\")\n    app.ext.openapi.add_security_scheme(\n        \"token\",\n        \"http\",\n        scheme=\"bearer\",\n        bearer_format=\"JWT\",\n    )\n    app.ext.openapi.add_security_scheme(\"token2\", \"http\")\n    app.ext.openapi.add_security_scheme(\n        \"oldschool\",\n        \"http\",\n        scheme=\"basic\",\n    )\n    app.ext.openapi.add_security_scheme(\n        \"oa2\",\n        \"oauth2\",\n        flows={\n            \"implicit\": {\n                \"authorizationUrl\": \"http://example.com/auth\",\n                \"scopes\": {\n                    \"on:two\": \"something\",\n                    \"three:four\": \"something else\",\n                    \"threefour\": \"something else...\",\n                },\n            }\n        },\n    )\n    ```\n\n## Document the endpoints\n\n.. column::\n\n    There are two options, document _all_ endpoints.\n\n.. column::\n\n    ```python\n    app.ext.openapi.secured()\n    app.ext.openapi.secured(\"token\")\n    ```\n\n\n.. column::\n\n    Or, document only specific routes.\n\n.. column::\n\n    ```python\n    @app.route(\"/one\")\n    async def handler1(request):\n        \"\"\"\n        openapi:\n        ---\n        security:\n            - foo: []\n        \"\"\"\n\n    @app.route(\"/two\")\n    @openapi.secured(\"foo\")\n    @openapi.secured({\"bar\": []})\n    @openapi.secured(baz=[])\n    async def handler2(request):\n        ...\n\n    @app.route(\"/three\")\n    @openapi.definition(secured=\"foo\")\n    @openapi.definition(secured={\"bar\": []})\n    async def handler3(request):\n        ...\n    ```\n\n"
  },
  {
    "path": "guide/content/en/plugins/sanic-ext/openapi/ui.md",
    "content": "---\ntitle: Sanic Extensions - OAS UI\n---\n\n# UI\n\nSanic Extensions comes with both Redoc and Swagger interfaces. You have a choice to use one, or both of them. Out of the box, the following endpoints are setup for you, with the bare `/docs` displaying Redoc.\n\n- `/docs`\n- `/docs/openapi.json`\n- `/docs/redoc`\n- `/docs/swagger`\n- `/docs/openapi-config`\n\n## Config options\n\n| **Key**                    | **Type**        | **Default**         | **Desctiption**                                              |\n| -------------------------- | --------------- | ------------------- | ------------------------------------------------------------ |\n| `OAS_IGNORE_HEAD`          | `bool`          | `True`              | Whether to display `HEAD` endpoints.                         |\n| `OAS_IGNORE_OPTIONS`       | `bool`          | `True`              | Whether to display `OPTIONS` endpoints.                      |\n| `OAS_PATH_TO_REDOC_HTML`   | `Optional[str]` | `None`              | Path to HTML to override the default Redoc HTML              |\n| `OAS_PATH_TO_SWAGGER_HTML` | `Optional[str]` | `None`              | Path to HTML to override the default Swagger HTML            |\n| `OAS_UI_DEFAULT`           | `Optional[str]` | `\"redoc\"`           | Can be set to `redoc` or `swagger`. Controls which UI to display on the base route. If set to `None`, then the base route will not be setup. |\n| `OAS_UI_REDOC`             | `bool`          | `True`              | Whether to enable Redoc UI.                                  |\n| `OAS_UI_SWAGGER`           | `bool`          | `True`              | Whether to enable Swagger UI.                                |\n| `OAS_URI_TO_CONFIG`        | `str`           | `\"/openapi-config\"` | URI path to the OpenAPI config used by Swagger               |\n| `OAS_URI_TO_JSON`          | `str`           | `\"/openapi.json\"`   | URI path to the JSON document.                               |\n| `OAS_URI_TO_REDOC`         | `str`           | `\"/redoc\"`          | URI path to Redoc.                                           |\n| `OAS_URI_TO_SWAGGER`       | `str`           | `\"/swagger\"`        | URI path to Swagger.                                         |\n| `OAS_URL_PREFIX`           | `str`           | `\"/docs\"`           | URL prefix to use for the Blueprint for OpenAPI docs.        |\n"
  },
  {
    "path": "guide/content/en/plugins/sanic-ext/openapi.md",
    "content": "---\ntitle: Sanic Extensions - OAS\n---\n\n# Openapi\n\n- Adding documentation with decorators\n- Documenting CBV\n- Using autodoc\n- Rendering docs with redoc/swagger\n- Validation\n"
  },
  {
    "path": "guide/content/en/plugins/sanic-ext/templating/html5tagger.md",
    "content": "---\ntitle: Sanic Extensions - html5tagger\n---\n\n# Coming soon\n\nSee [sanic-org/html5tagger on GitHub](https://github.com/sanic-org/html5tagger/)\n"
  },
  {
    "path": "guide/content/en/plugins/sanic-ext/templating/jinja.md",
    "content": "---\ntitle: Sanic Extensions - Jinja\n---\n\n# Templating\n\nSanic Extensions can easily help you integrate templates into your route handlers. \n\n## Dependencies\n\n**Currently, we only support [Jinja](https://github.com/pallets/jinja/).**\n\n[Read the Jinja docs first](https://jinja.palletsprojects.com/en/3.1.x/) if you are unfamiliar with how to create templates.\n\nSanic Extensions will automatically setup and load Jinja for you if it is installed in your environment. Therefore, the only setup that you need to do is install Jinja:\n\n```\npip install Jinja2\n```\n\n## Rendering a template from a file\n\nThere are three (3) ways for you:\n\n1. Using a decorator to pre-load the template file\n1. Returning a rendered `HTTPResponse` object\n1. Hybrid pattern that creates a `LazyResponse`\n\nLet's imagine you have a file called `./templates/foo.html`:\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n    <head>\n        <title>My Webpage</title>\n    </head>\n\n    <body>\n        <h1>Hello, world!!!!</h1>\n        <ul>\n            {% for item in seq %}\n            <li>{{ item }}</li>\n            {% endfor %}\n        </ul>\n    </body>\n\n</html>\n```\n\nLet's see how you could render it with Sanic + Jinja.\n\n### Option 1 - as a decorator\n\n.. column::\n\n    The benefit of this approach is that the templates can be predefined at startup time. This will mean that less fetching needs to happen in the handler, and should therefore be the fastest option.\n\n.. column::\n\n    ```python\n    @app.get(\"/\")\n    @app.ext.template(\"foo.html\")\n    async def handler(request: Request):\n        return {\"seq\": [\"one\", \"two\"]}\n    ```\n\n### Option 2 - as a return object\n\n.. column::\n\n    This is meant to mimic the `text`, `json`, `html`, `file`, etc pattern of core Sanic. It will allow the most customization to the response object since it has direct control of it. Just like in other `HTTPResponse` objects, you can control headers, cookies, etc.\n\n.. column::\n\n    ```python\n    from sanic_ext import render\n\n    @app.get(\"/alt\")\n    async def handler(request: Request):\n        return await render(\n            \"foo.html\", context={\"seq\": [\"three\", \"four\"]}, status=400\n        )\n    ```\n\n### Option 3 - hybrid/lazy\n\n.. column::\n\n    In this approach, the template is defined up front and not inside the handler (for performance). Then, the `render` function returns a `LazyResponse` that can be used to build a proper `HTTPResponse` inside the decorator.\n\n.. column::\n\n    ```python\n    from sanic_ext import render\n\n    @app.get(\"/\")\n    @app.ext.template(\"foo.html\")\n    async def handler(request: Request):\n        return await render(context={\"seq\": [\"five\", \"six\"]}, status=400)\n    ```\n\n## Rendering a template from a string\n\n.. column::\n\n    Sometimes you may want to write (or generate) your template inside of Python code and _not_ read it from an HTML file. In this case, you can still use the `render` function we saw above. Just use `template_source`.\n\n.. column::\n\n    ```python\n    from sanic_ext import render\n    from textwrap import dedent\n\n    @app.get(\"/\")\n    async def handler(request):\n        template = dedent(\"\"\"\n            <!DOCTYPE html>\n            <html lang=\"en\">\n\n                <head>\n                    <title>My Webpage</title>\n                </head>\n\n                <body>\n                    <h1>Hello, world!!!!</h1>\n                    <ul>\n                        {% for item in seq %}\n                        <li>{{ item }}</li>\n                        {% endfor %}\n                    </ul>\n                </body>\n\n            </html>\n        \"\"\")\n        return await render(\n            template_source=template,\n            context={\"seq\": [\"three\", \"four\"]},\n            app=app,\n        )\n    ```\n\n\n\n.. note:: \n\n    In this example, we use `textwrap.dedent` to remove the whitespace in the beginning of each line of the multi-line string. It is not necessary, but just a nice touch to keep both the code and the generated source clean.\n\n\n\n## Development and auto-reload\n\nIf auto-reload is turned on, then changes to your template files should trigger a reload of the server.\n\n## Configuration\n\nSee `templating_enable_async` and `templating_path_to_templates` in [settings](./configuration.md#settings).\n"
  },
  {
    "path": "guide/content/en/plugins/sanic-ext/validation.md",
    "content": "---\ntitle: Sanic Extensions - Validation\n---\n\n# Validation\n\nOne of the most commonly implemented features of a web application is user-input validation. For obvious reasons, this is not only a security issue, but also just plain good practice. You want to make sure your data conforms to expectations, and throw a `400` response when it does not.\n\n## Implementation\n\n### Validation with Dataclasses\n\nWith the introduction of [Data Classes](https://docs.python.org/3/library/dataclasses.html), Python made it super simple to create objects that meet a defined schema. However, the standard library only supports type checking validation, **not** runtime validation. Sanic Extensions adds the ability to do runtime validations on incoming requests using `dataclasses` out of the box. If you also have either `pydantic` or `attrs` installed, you can alternatively use one of those libraries.\n\n.. column::\n\n    First, define a model.\n\n.. column::\n\n    ```python\n    @dataclass\n    class SearchParams:\n        q: str\n    ```\n\n\n.. column::\n\n    Then, attach it to your route\n\n.. column::\n\n    ```python\n    from sanic_ext import validate\n\n    @app.route(\"/search\")\n    @validate(query=SearchParams)\n    async def handler(request, query: SearchParams):\n        return json(asdict(query))\n    ```\n\n\n.. column::\n\n    You should now have validation on the incoming request.\n\n.. column::\n\n    ```\n    $ curl localhost:8000/search                                       \n    ⚠️ 400 — Bad Request\n    ====================\n    Invalid request body: SearchParams. Error: missing a required argument: 'q'\n    ```\n    ```\n    $ curl localhost:8000/search\\?q=python                             \n    {\"q\":\"python\"}\n    ```\n\n### Validation with Pydantic\n\nYou can use Pydantic models also.\n\n.. column::\n\n    First, define a model.\n\n.. column::\n\n    ```python\n    class Person(BaseModel):\n        name: str\n        age: int\n    ```\n\n\n.. column::\n\n    Then, attach it to your route\n\n.. column::\n\n    ```python\n    from sanic_ext import validate\n\n    @app.post(\"/person\")\n    @validate(json=Person)\n    async def handler(request, body: Person):\n        return json(body.dict())\n    ```\n\n\n.. column::\n\n    You should now have validation on the incoming request.\n\n.. column::\n\n    ```\n    $ curl localhost:8000/person -d '{\"name\": \"Alice\", \"age\": 21}' -X POST  \n    {\"name\":\"Alice\",\"age\":21}\n    ```\n\n### Validation with Attrs\n\nYou can use Attrs also.\n\n.. column::\n\n    First, define a model.\n\n.. column::\n\n    ```python\n    @attrs.define\n    class Person:\n        name: str\n        age: int\n\n    ```\n\n\n.. column::\n\n    Then, attach it to your route\n\n.. column::\n\n    ```python\n    from sanic_ext import validate\n\n    @app.post(\"/person\")\n    @validate(json=Person)\n    async def handler(request, body: Person):\n        return json(attrs.asdict(body))\n    ```\n\n\n.. column::\n\n    You should now have validation on the incoming request.\n\n.. column::\n\n    ```\n    $ curl localhost:8000/person -d '{\"name\": \"Alice\", \"age\": 21}' -X POST  \n    {\"name\":\"Alice\",\"age\":21}\n    ```\n\n## What can be validated?\n\nThe `validate` decorator can be used to validate incoming user data from three places: JSON body data (`request.json`), form body data (`request.form`), and query parameters (`request.args`).\n\n.. column::\n\n    As you might expect, you can attach your model using the keyword arguments of the decorator.\n\n.. column::\n\n    ```python\n    @validate(\n        json=ModelA,\n        query=ModelB,\n        form=ModelC,\n    )\n    ```\n\n"
  },
  {
    "path": "guide/content/en/plugins/sanic-testing/README.md",
    "content": ""
  },
  {
    "path": "guide/content/en/plugins/sanic-testing/clients.md",
    "content": "---\ntitle: Sanic Testing - Test Clients\n---\n\n# Test Clients\n\nThere are three different test clients available to you, each of them presents different capabilities.\n\n## Regular sync client: `SanicTestClient`\n\nThe `SanicTestClient` runs an actual version of the Sanic Server on your local network to run its tests. Each time it calls an endpoint it will spin up a version of the application and bind it to a socket on the host OS. Then, it will use `httpx` to make calls directly to that application.\n\nThis is the typical way that Sanic applications are tested.\n\n.. column::\n\n    Once installing Sanic Testing, the regular `SanicTestClient` can be used without further setup. This is because Sanic does the leg work for you under the hood. \n\n.. column::\n\n    ```python\n    app.test_client.get(\"/path/to/endpoint\")\n    ```\n\n.. column::\n\n    However, you may find it desirable to instantiate the client yourself.\n\n.. column::\n\n    ```python\n    from sanic_testing.testing import SanicTestClient\n\n    test_client = SanicTestClient(app)\n    test_client.get(\"/path/to/endpoint\")\n    ```\n\n.. column::\n\n    A third option for starting the test client is to use the `TestManager`. This is a convenience object that sets up both the `SanicTestClient` and the `SanicASGITestClient`.\n\n.. column::\n\n    ```python\n    from sanic_testing import TestManager\n\n    mgr = TestManager(app)\n    app.test_client.get(\"/path/to/endpoint\")\n    # or\n    mgr.test_client.get(\"/path/to/endpoint\")\n    ```\n\nYou can make a request by using one of the following methods\n\n- `SanicTestClient.get`\n- `SanicTestClient.post`\n- `SanicTestClient.put`\n- `SanicTestClient.patch`\n- `SanicTestClient.delete`\n- `SanicTestClient.options`\n- `SanicTestClient.head`\n- `SanicTestClient.websocket`\n- `SanicTestClient.request`\n\nYou can use these methods *almost* identically as you would when using `httpx`. Any argument that you would pass to `httpx` will be accepted, **with one caveat**: If you are using `test_client.request` and want to manually specify the HTTP method, you should use: `http_method`:\n\n```python\ntest_client.request(\"/path/to/endpoint\", http_method=\"get\")\n```\n\n## ASGI async client: `SanicASGITestClient`\n\nUnlike the `SanicTestClient` that spins up a server on every request, the `SanicASGITestClient` does not. Instead it makes use of the `httpx` library to execute Sanic as an ASGI application to reach inside and execute the route handlers.\n\n.. column::\n\n    This test client provides all of the same methods and generally works as the `SanicTestClient`. The only difference is that you will need to add an `await` to each call:\n\n.. column::\n\n    ```python\n    await app.test_client.get(\"/path/to/endpoint\")\n    ```\n\nThe `SanicASGITestClient` can be used in the exact same three ways as the `SanicTestClient`.\n\n\n.. note::\n\n    The `SanicASGITestClient` does not need to only be used with ASGI applications. The same way that the `SanicTestClient` does not need to only test sync endpoints. Both of these clients are capable of testing *any* Sanic application.\n\n\n## Persistent service client: `ReusableClient`\n\nThis client works under a similar premise as the `SanicTestClient` in that it stands up an instance of your application and makes real HTTP requests to it. However, unlike the `SanicTestClient`, when using the `ReusableClient` you control the lifecycle of the application.\n\nThat means that every request **does not** start a new web server. Instead you will start the server and stop it as needed and can make multiple requests to the same running instance.\n\n.. column::\n\n    Unlike the other two clients, you **must** instantiate this client for use:\n\n.. column::\n\n    ```python\n    from sanic_testing.reusable import ReusableClient\n\n    client = ReusableClient(app)\n    ```\n\n.. column::\n\n    Once created, you will use the client inside of a context manager. Once outside of the scope of the manager, the server will shutdown.\n\n.. column::\n\n    ```python\n    from sanic_testing.reusable import ReusableClient\n\n    def test_multiple_endpoints_on_same_server(app):\n        client = ReusableClient(app)\n        with client:\n            _, response = client.get(\"/path/to/1\")\n            assert response.status == 200\n\n            _, response = client.get(\"/path/to/2\")\n            assert response.status == 200\n    ```\n"
  },
  {
    "path": "guide/content/en/plugins/sanic-testing/getting-started.md",
    "content": "---\ntitle: Sanic Testing - Getting Started\n---\n\n# Getting Started\n\nSanic Testing is the *official* testing client for Sanic. Its primary use is to power the tests of the Sanic project itself. However, it is also meant as an easy-to-use client for getting your API tests up and running quickly.\n\n## Minimum requirements\n\n- **Python**: 3.7+\n- **Sanic**: 21.3+\n\nVersions of Sanic older than 21.3 have this module integrated into Sanic itself as `sanic.testing`.\n\n## Install\n\nSanic Testing can be installed from PyPI:\n\n```\npip install sanic-testing\n```\n\n## Basic Usage\n\nAs long as the `sanic-testing` package is in the environment, there is nothing you need to do to start using it.\n\n### Writing a sync test\n\nIn order to use the test client, you just need to access the property `test_client` on your application instance:\n\n```python\nimport pytest\nfrom sanic import Sanic, response\n\n@pytest.fixture\ndef app():\n    sanic_app = Sanic(\"TestSanic\")\n\n    @sanic_app.get(\"/\")\n    def basic(request):\n        return response.text(\"foo\")\n\n    return sanic_app\n\ndef test_basic_test_client(app):\n    request, response = app.test_client.get(\"/\")\n\n    assert request.method.lower() == \"get\"\n    assert response.body == b\"foo\"\n    assert response.status == 200\n```\n\n### Writing an async test\n\nIn order to use the async test client in `pytest`, you should install the `pytest-asyncio` plugin.\n\n```\npip install pytest-asyncio\n```\n\nYou can then create an async test and use the ASGI client:\n\n```python\nimport pytest\nfrom sanic import Sanic, response\n\n@pytest.fixture\ndef app():\n    sanic_app = Sanic(__name__)\n\n    @sanic_app.get(\"/\")\n    def basic(request):\n        return response.text(\"foo\")\n\n    return sanic_app\n\n@pytest.mark.asyncio\nasync def test_basic_asgi_client(app):\n    request, response = await app.asgi_client.get(\"/\")\n\n    assert request.method.lower() == \"get\"\n    assert response.body == b\"foo\"\n    assert response.status == 200\n```\n"
  },
  {
    "path": "guide/content/en/release-notes/2021/v21.12.md",
    "content": "---\ntitle: Version 21.12 (LTS)\n---\n\n# Version 21.12 (LTS)\n\n.. toc::\n\n## Introduction\n\nThis is the final release of the version 21 [release cycle](../../organization/policies.md#release-schedule). Version 21 will now enter long-term support and will be supported for two years until December 2023.\n\n## What to know\n\nMore details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade...\n\n### Strict application and blueprint names\n\nIn [v21.6](./v21.6.md#stricter-application-and-blueprint-names-and-deprecation) application and blueprint names were required to conform to a new set of restrictions. That change is now being enforced at startup time.\n\nNames **must**:\n\n1. Only use alphanumeric characters (`a-zA-Z0-9`)\n2. May contain a hyphen (`-`) or an underscore (`_`)\n3. Must begin with a letter or underscore (`a-zA-Z_`)\n\n### Strict application and blueprint properties\n\nThe old leniency to allow directly setting properties of a `Sanic` or `Blueprint` object was deprecated and no longer allowed. You must use the `ctx` object.\n\n```python\napp = Sanic(\"MyApp\")\napp.ctx.db = Database()\n```\n\n### Removals\n\nThe following deprecated features no longer exist:\n\n- `sanic.exceptions.abort`\n- `sanic.views.CompositionView`\n- `sanic.response.StreamingHTTPResponse`\n\n### Upgrade your streaming responses (if not already)\n\nThe `sanic.response.stream` response method has been **deprecated** and will be removed in v22.6. If you are sill using an old school streaming response, please upgrade it.\n\n**OLD - Deprecated**\n\n```python\nasync def sample_streaming_fn(response):\n    await response.write(\"foo,\")\n    await response.write(\"bar\")\n\n@app.route(\"/\")\nasync def test(request: Request):\n    return stream(sample_streaming_fn, content_type=\"text/csv\")\n```\n\n**Current**\n\n```python\nasync def sample_streaming_fn(response):\n    await response.write(\"foo,\")\n    await response.write(\"bar\")\n\n@app.route(\"/\")\nasync def test(request: Request):\n    response = await request.respond(content_type=\"text/csv\")\n    await response.send(\"foo,\")\n    await response.send(\"bar\")\n```\n\n### CLI overhaul and MOTD (Message of the Day)\n\nThe Sanic CLI has received a fairly extensive upgrade. It adds a bunch of new features to make it on par with `app.run()`. It also includes a new MOTD display to provide quick, at-a-glance highlights about your running environment. The MOTD is TTY-aware, and therefore will be less verbose in server logs. It is mainly intended as a convenience during application development.\n\n```\n$ sanic --help\nusage: sanic [-h] [--version] [--factory] [-s] [-H HOST] [-p PORT] [-u UNIX] [--cert CERT] [--key KEY] [--tls DIR] [--tls-strict-host]\n             [-w WORKERS | --fast] [--access-logs | --no-access-logs] [--debug] [-d] [-r] [-R PATH] [--motd | --no-motd] [-v]\n             [--noisy-exceptions | --no-noisy-exceptions]\n             module\n\n   ▄███ █████ ██      ▄█▄      ██       █   █   ▄██████████\n  ██                 █   █     █ ██     █   █  ██\n   ▀███████ ███▄    ▀     █    █   ██   ▄   █  ██\n               ██  █████████   █     ██ █   █  ▄▄\n  ████ ████████▀  █         █  █       ██   █   ▀██ ███████\n\n To start running a Sanic application, provide a path to the module, where\n app is a Sanic() instance:\n\n     $ sanic path.to.server:app\n\n Or, a path to a callable that returns a Sanic() instance:\n\n     $ sanic path.to.factory:create_app --factory\n\n Or, a path to a directory to run as a simple HTTP server:\n\n     $ sanic ./path/to/static --simple\n\nRequired\n========\n  Positional:\n    module                         Path to your Sanic app. Example: path.to.server:app\n                                   If running a Simple Server, path to directory to serve. Example: ./\n\nOptional\n========\n  General:\n    -h, --help                     show this help message and exit\n    --version                      show program's version number and exit\n\n  Application:\n    --factory                      Treat app as an application factory, i.e. a () -> <Sanic app> callable\n    -s, --simple                   Run Sanic as a Simple Server, and serve the contents of a directory\n                                   (module arg should be a path)\n\n  Socket binding:\n    -H HOST, --host HOST           Host address [default 127.0.0.1]\n    -p PORT, --port PORT           Port to serve on [default 8000]\n    -u UNIX, --unix UNIX           location of unix socket\n\n  TLS certificate:\n    --cert CERT                    Location of fullchain.pem, bundle.crt or equivalent\n    --key KEY                      Location of privkey.pem or equivalent .key file\n    --tls DIR                      TLS certificate folder with fullchain.pem and privkey.pem\n                                   May be specified multiple times to choose multiple certificates\n    --tls-strict-host              Only allow clients that send an SNI matching server certs\n\n  Worker:\n    -w WORKERS, --workers WORKERS  Number of worker processes [default 1]\n    --fast                         Set the number of workers to max allowed\n    --access-logs                  Display access logs\n    --no-access-logs               No display access logs\n\n  Development:\n    --debug                        Run the server in debug mode\n    -d, --dev                      Currently is an alias for --debug. But starting in v22.3, \n                                   --debug will no longer automatically trigger auto_restart. \n                                   However, --dev will continue, effectively making it the \n                                   same as debug + auto_reload.\n    -r, --reload, --auto-reload    Watch source directory for file changes and reload on changes\n    -R PATH, --reload-dir PATH     Extra directories to watch and reload on changes\n\n  Output:\n    --motd                         Show the startup display\n    --no-motd                      No show the startup display\n    -v, --verbosity                Control logging noise, eg. -vv or --verbosity=2 [default 0]\n    --noisy-exceptions             Output stack traces for all exceptions\n    --no-noisy-exceptions          No output stack traces for all exceptions\n```\n\n### Server running modes and changes coming to `debug`\n\nThere are now two running modes: `DEV` and `PRODUCTION`. By default, Sanic server will run under `PRODUCTION` mode. This is intended for deployments.\n\nCurrently, `DEV` mode will operate very similarly to how `debug=True` does in older Sanic versions. However, in v22.3. `debug=True` will **no longer** enable auto-reload. If you would like to have debugging and auto-reload, you should enable `DEV` mode.\n\n**DEVELOPMENT**\n\n```\n$ sanic server:app --dev\n```\n\n```python\napp.run(debug=True, auto_reload=True)\n```\n\n**PRODUCTION**\n\n```\n$ sanic server:app\n```\n\n```python\napp.run()\n```\n\nBeginning in v22.3, `PRODUCTION` mode will no longer enable access logs by default.\n\nA summary of the changes are as follows:\n\n| Flag    | Mode  | Tracebacks | Logging | Access logs | Reload | Max workers |\n|---------|-------|------------|---------|-------------|--------|-------------|\n| --debug | DEBUG | yes        | DEBUG   | yes         | ^1     |             |\n|         | PROD  | no         | INFO ^2 | ^3          |        |             |\n| --dev   | DEBUG | yes        | DEBUG   | yes         | yes    |             |\n| --fast  |       |            |         |             |        | yes         |\n\n- ^1 `--debug` to deprecate auto-reloading and remove in 22.3\n- ^2 After 22.3 this moves to WARNING\n- ^3 After 22.3: no\n\n### Max allowed workers\n\nYou can easily spin up the maximum number of allowed workers using `--fast`.\n\n```\n$ sanic server:app --fast\n```\n\n```python\napp.run(fast=True)\n```\n\n### First-class Sanic Extensions support\n\n[Sanic Extensions](../../plugins/sanic-ext/getting-started.md) provides a number of additional features specifically intended for API developers. You can now easily implement all of the functionality it has to offer without additional setup as long as the package is in the environment. These features include:\n\n- Auto create `HEAD`, `OPTIONS`, and `TRACE` endpoints\n- CORS protection\n- Predefined, endpoint-specific response serializers\n- Dependency injection into route handlers\n- OpenAPI documentation with Redoc and/or Swagger\n- Request query arguments and body input validation\n\nThe preferred method is to install it along with Sanic, but you can also install the packages on their own.\n\n.. column::\n\n    ```\n    $ pip install sanic[ext]\n    ```\n\n.. column::\n\n    ```\n    $ pip install sanic sanic-ext\n    ```\n\nAfter that, no additional configuration is required. Sanic Extensions will be attached to your application and provide all of the additional functionality with **no further configuration**.\n\nIf you want to change how it works, or provide additional configuration, you can change Sanic extensions using `app.extend`. The following examples are equivalent. The `Config` object is to provide helpful type annotations for IDE development.\n\n.. column::\n\n    ```python\n    # This is optional, not required\n    app = Sanic(\"MyApp\")\n    app.extend(config={\"oas_url_prefix\": \"/apidocs\"})\n    ```\n\n.. column::\n\n    ```python\n    # This is optional, not required\n    app = Sanic(\"MyApp\")\n    app.config.OAS_URL_PREFIX = \"/apidocs\"\n    ```\n\n.. column::\n\n    ```python\n    # This is optional, not required\n    from sanic_ext import Config\n\n    app = Sanic(\"MyApp\")\n    app.extend(config=Config(oas_url_prefix=\"/apidocs\"))\n    ```\n\n.. column::\n\n\n  ### Contextual exceptions\n\n  In [v21.9](./v21.9.md#default-exception-messages) we added default messages to exceptions that simplify the ability to consistently raise exceptions throughout your application.\n\n  ```python\n  class TeapotError(SanicException):\n      status_code = 418\n      message = \"Sorry, I cannot brew coffee\"\n\n  raise TeapotError\n  ```\n\n  But this lacked two things:\n\n  1. A dynamic and predictable message format\n  2. The ability to add additional context to an error message (more on this in a moment)\n\n  The current release allows any Sanic exception to have additional information to when raised to provide context when writing an error message:\n\n  ```python\n  class TeapotError(SanicException):\n      status_code = 418\n\n      @property\n      def message(self):\n          return f\"Sorry {self.extra['name']}, I cannot make you coffee\"\n\n  raise TeapotError(extra={\"name\": \"Adam\"})\n  ```\n\n  The new feature allows the passing of `extra` meta to the exception instance. This `extra` info object **will be suppressed** when in `PRODUCTION` mode, but displayed in `DEVELOPMENT` mode.\n\n.. column::\n    \n    **PRODUCTION**\n\n    ![image](https://user-images.githubusercontent.com/166269/139014161-cda67cd1-843f-4ad2-9fa1-acb94a59fc4d.png)\n\n.. column::\n\n    **DEVELOPMENT**\n\n    ![image](https://user-images.githubusercontent.com/166269/139014121-0596b084-b3c5-4adb-994e-31ba6eba6dad.png)\n\nGetting back to item 2 from above: _The ability to add additional context to an error message_\n\nThis is particularly useful when creating microservices or an API that you intend to pass error messages back in JSON format. In this use case, we want to have some context around the exception beyond just a parseable error message to return details to the client.\n\n```python\nraise TeapotError(context={\"foo\": \"bar\"})\n```\n\nThis is information **that we want** to always be passed in the error (when it is available). Here is what it should look like:\n\n.. column::\n\n    **PRODUCTION**\n\n    ```json\n    {\n      \"description\": \"I'm a teapot\",\n      \"status\": 418,\n      \"message\": \"Sorry Adam, I cannot make you coffee\",\n      \"context\": {\n        \"foo\": \"bar\"\n      }\n    }\n    ```\n\n.. column::\n\n    **DEVELOPMENT**\n\n    ```json\n    {\n      \"description\": \"I'm a teapot\",\n      \"status\": 418,\n      \"message\": \"Sorry Adam, I cannot make you coffee\",\n      \"context\": {\n        \"foo\": \"bar\"\n      },\n      \"extra\": {\n        \"name\": \"Adam\",\n        \"more\": \"lines\",\n        \"complex\": {\n          \"one\": \"two\"\n        }\n      },\n      \"path\": \"/\",\n      \"args\": {},\n      \"exceptions\": [\n        {\n          \"type\": \"TeapotError\",\n          \"exception\": \"Sorry Adam, I cannot make you coffee\",\n          \"frames\": [\n            {\n              \"file\": \"handle_request\",\n              \"line\": 83,\n              \"name\": \"handle_request\",\n              \"src\": \"\"\n            },\n            {\n              \"file\": \"/tmp/p.py\",\n              \"line\": 17,\n              \"name\": \"handler\",\n              \"src\": \"raise TeapotError(\"\n            }\n          ]\n        }\n      ]\n    }\n    ```\n\n### Background task management\n\nWhen using the `app.add_task` method to create a background task, there now is the option to pass an optional `name` keyword argument that allows it to be fetched, or cancelled.\n\n```python\napp.add_task(dummy, name=\"dummy_task\")\ntask = app.get_task(\"dummy_task\")\n\napp.cancel_task(\"dummy_task\")\n```\n\n### Route context kwargs in definitions\n\nWhen a route is defined, you can add any number of keyword arguments with a `ctx_` prefix. These values will be injected into the route `ctx` object.\n\n```python\n@app.get(\"/1\", ctx_label=\"something\")\nasync def handler1(request):\n    ...\n\n@app.get(\"/2\", ctx_label=\"something\")\nasync def handler2(request):\n    ...\n\n@app.get(\"/99\")\nasync def handler99(request):\n    ...\n\n@app.on_request\nasync def do_something(request):\n    if request.route.ctx.label == \"something\":\n        ...\n```\n\n### Blueprints can be registered at any time\n\nIn previous versions of Sanic, there was a strict ordering of when a Blueprint could be attached to an application. If you ran `app.blueprint(bp)` *before* attaching all objects to the Blueprint instance, they would be missed.\n\nNow, you can attach a Blueprint at anytime and everything attached to it will be included at startup.\n\n### Noisy exceptions (force all exceptions to logs)\n\nThere is a new `NOISY_EXCEPTIONS` config value. When it is `False` (which is the default), Sanic will respect the `quiet` property of any `SanicException`. This means that an exception with `quiet=True` will not be displayed to the log output.\n\nHowever, when setting `NOISY_EXCEPTIONS=True`, all exceptions will be logged regardless of the `quiet` value.\n\nThis can be helpful when debugging.\n\n```python\napp.config.NOISY_EXCEPTIONS = True\n```\n\n### Signal events as `Enum`\n\nThere is an `Enum` with all of the built-in signal values for convenience.\n\n```python\nfrom sanic.signals import Event\n\n@app.signal(Event.HTTP_LIFECYCLE_BEGIN)\nasync def connection_opened(conn_info):\n    ...\n```\n\n### Custom type casting of environment variables\n\nBy default, Sanic will convert an `int`, `float`, or a `bool` value when applying environment variables to the `config` instance. You can extend this with your own converter:\n\n```python\napp = Sanic(..., config=Config(converters=[UUID]))\n```\n\n### Disable `uvloop` by configuration value\n\nThe usage of `uvloop` can be controlled by configuration value:\n\n```python\napp.config.USE_UVLOOP = False\n```\n\n### Run Sanic server with multiple TLS certificates\n\nSanic can be run with multiple TLS certificates:\n\n```python\napp.run(\n    ssl=[\n        \"/etc/letsencrypt/live/example.com/\",\n        \"/etc/letsencrypt/live/mysite.example/\",\n    ]\n)\n```\n\n## News\n\n### Coming Soon: Python Web Development with Sanic\n\nA book about Sanic is coming soon by one of the core developers, [@ahopkins](https://github.com/ahopkins).\n\nLearn more at [sanicbook.com](https://sanicbook.com).\n\n> Get equipped with the practical knowledge of working with Sanic to increase the performance and scalability of your web applications. While doing that, we will level-up your development skills as you learn to customize your application to meet the changing business needs without having to significantly over-engineer the app.\n\nA portion of book proceeds goes into the Sanic Community Organization to help fund the development and operation of Sanic. So, buying the book is another way you can support Sanic.\n\n### Dark mode for the docs\n\nIf you have not already noticed, this Sanic website is now available in a native dark mode. You can toggle the theme at the top right of the page.\n\n## Thank you\n\nThank you to everyone that participated in this release: :clap:\n\n[@adarsharegmi](https://github.com/adarsharegmi)\n[@ahopkins](https://github.com/ahopkins)\n[@ashleysommer](https://github.com/ashleysommer)\n[@ChihweiLHBird](https://github.com/ChihweiLHBird)\n[@cnicodeme](https://github.com/cnicodeme)\n[@kianmeng](https://github.com/kianmeng)\n[@meysam81](https://github.com/meysam81)\n[@nuxion](https://github.com/nuxion)\n[@prryplatypus](https://github.com/prryplatypus)\n[@realDragonium](https://github.com/realDragonium)\n[@SaidBySolo](https://github.com/SaidBySolo)\n[@sjsadowski](https://github.com/sjsadowski)\n[@Tronic](https://github.com/tronic)\n[@Varriount](https://github.com/Varriount)\n[@vltr](https://github.com/vltr)\n[@whos4n3](https://github.com/whos4n3)\n\nAnd, a special thank you to [@miss85246](https://github.com/miss85246) and [@ZinkLu](https://github.com/ZinkLu) for their tremendous work keeping the documentation synced and translated into Chinese.\n\n---\n\nIf you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/).\n"
  },
  {
    "path": "guide/content/en/release-notes/2021/v21.3.md",
    "content": "---\ntitle: Version 21.3\n---\n\n# Version 21.3\n\n.. toc::\n\n## Introduction\n\nSanic is now faster.\n\nWell, it already was fast. But with the first iteration of the v21 release, we incorporated a few major milestones that have made some tangible improvements. These encompass some ideas that have been in the works for years, and have finally made it into the released version.\n\n\n.. warning:: Breaking changes\n\n    Version 21.3 introduces a lot of new features. But, it also includes some breaking changes. This is why these changes were introduced after the last LTS. If you rely upon something that has been removed, you should continue to use v20.12LTS until you are able to upgrade.\n\n    ```bash\n    pip install \"sanic>=20.12,<20.13\"\n    pip freeze > requirements.txt\n    ```\n\n    For most typical installations, you should be able to upgrade without a problem.\n\n\n## What to know\n\nNotable new or breaking features, and what to upgrade...\n\n### Python 3.7+ Only\n\nThis version drops Python 3.6 support. Version 20.12LTS will continue to support Python 3.6 until its EOL in December, 2022, and version 19.12LTS will support it until its EOL in December, 2021.\n\nRead more about our [LTS policy](../../organization/policies.md#long-term-support-v-interim-releases).\n\n### Streaming as first class citizen\n\nThe biggest speed improvement came from unifying the request/response cycle into a single flow. Previously, there was a difference between regular cycles, and streaming cycles. This has been simplified under the hood, even though the API is staying the same right now for compatibility. The net benefit is that **all** requests now should see a new benefit.\n\nRead more about [streaming changes](../../guide/advanced/streaming.md#response-streaming).\n\n### Router overhaul\n\nThe old Sanic router was based upon regular expressions. In addition it suffered from a number of quirks that made it hard to modify at run time, and resulted in some performance issues. This change has been years in the making and now [converts the router to a compiled tree at startup](https://community.sanicframework.org/t/a-fast-new-router/649/41). Look for additional improvements throughout the year.\n\nThe outward facing API has kept backwards compatibility. However, if you were accessing anything inside the router specifically, you many notice some changes. For example:\n\n1. `Router.get()` has a new return value\n2. `Route` is now a proper class object and not a `namedtuple`\n3. If building the router manually, you will need to call `Router.finalize()` before it is usable\n4. There is a new `<date:ymd>` pattern that can be matched in your routes\n5. You cannot startup an application without at least one route defined\n\nThe router is now located in its own repository: [sanic-org/sanic-router](https://github.com/sanic-org/sanic-router) and is also its own [standalone package on PyPI](https://pypi.org/project/sanic-routing/).\n\n### Signals API ⭐️\n\n_BETA Feature: API to be finalized in v21.6_\n\nA side benefit of the new router is that it can do double duty also powering the [new signals API](https://github.com/sanic-org/sanic/issues/1630). This feature is being released for public usage now, and likely the public API will not change in its final form.\n\nThe core ideas of this feature are:\n\n1. to allow the developer greater control and access to plugging into the server and request lifecycles,\n2. to provide new tools to synchronize and send messages through your application, and\n3. to ultimately further increase performance.\n\nThe API introduces three new methods:\n\n- `@app.signal(...)` - For defining a signal handler. It looks and operates very much like a route. Whenever that signal is dispatched, this handler will be executed.\n- `app.event(...)` - An awaitable that can be used anywhere in your application to pause execution until the event is triggered.\n- `app.dispatch(...)` - Trigger an event and cause the signal handlers to execute.\n\n```python\n@app.signal(\"foo.bar.<thing>\")\nasync def signal_handler(thing, **kwargs):\n    print(f\"[signal_handler] {thing=}\", kwargs)\n\nasync def wait_for_event(app):\n    while True:\n        print(\"> waiting\")\n        await app.event(\"foo.bar.*\")\n        print(\"> event found\\n\")\n\n@app.after_server_start\nasync def after_server_start(app, loop):\n    app.add_task(wait_for_event(app))\n\n@app.get(\"/\")\nasync def trigger(request):\n    await app.dispatch(\"foo.bar.baz\")\n    return response.text(\"Done.\")\n```\n\n### Route naming\n\nRoutes used to be referenced by both `route.name` and `route.endpoint`. While similar, they were slightly different. Now, all routes will be **consistently** namespaced and referenced.\n\n```text\n<app name>.[optional:<blueprint name>.]<handler name>\n```\n\nThis new \"name\" is assigned to the property `route.name`. We are deprecating `route.endpoint`, and will remove that property in v21.9. Until then, it will be an alias for `route.name`.\n\nIn addition, naming prefixes that had been in use for things like static, websocket, and blueprint routes have been removed.\n\n### New decorators\n\nSeveral new convenience decorators to help IDEs with autocomplete.\n\n```python\n# Alias to @app.listener(\"...\")\n@app.before_server_start\n@app.after_server_stop\n@app.before_server_start\n@app.after_server_stop\n\n# Alias to @app.middleware(\"...\")\n@app.on_request\n@app.on_response\n```\n\n### Unquote in route\n\nIf you have a route that uses non-ascii characters, Sanic will no longer `unquote` the text for you. You will need to specifically tell the route definition that it should do so.\n\n```python\n@app.route(\"/overload/<param>\", methods=[\"GET\"], unquote=True)\nasync def handler2(request, param):\n    return text(\"OK2 \" + param)\n\nrequest, response = app.test_client.get(\"/overload/您好\")\nassert response.text == \"OK2 您好\"\n```\n\nIf you forget to do so, your text will remain encoded.\n\n### Alter `Request.match_info`\n\nThe `match_info` has always provided the data for the matched path parameters. You now have access to modify that, for example in middleware.\n\n```python\n@app.on_request\ndef convert_to_snake_case(request):\n    request.match_info = to_snake(request.match_info)\n```\n\n### Version types in routes\n\nThe `version` argument in routes can now be:\n\n- `str`\n- `int`\n- `float`\n\n```python\n@app.route(\"/foo\", version=\"2.1.1\")\n@app.route(\"/foo\", version=2)\n@app.route(\"/foo\", version=2.1)\n```\n### Safe method handling with body\n\nRoute handlers for `GET`, `HEAD`, `OPTIONS` and `DELETE` will not decode any HTTP body passed to it. You can override this:\n\n```python\n@app.delete(..., ignore_body=False)\n```\n\n### Application, Blueprint and Blueprint Group parity\n\nThe `Sanic` and `Blueprint` classes share a common base. Previously they duplicated a lot of functionality, that lead to slightly different implementations between them. Now that they both inherit the same base class, developers and plugins should have a more consistent API to work with.\n\nAlso, Blueprint Groups now also support common URL extensions like the `version` and `strict_slashes` keyword arguments.\n\n### Dropped `httpx` from dependencies\n\nThere is no longer a dependency on `httpx`.\n\n### Removed `testing` library\n\nSanic internal testing client has been removed. It is now located in its own repository: [sanic-org/sanic-testing](https://github.com/sanic-org/sanic-testing) and is also its own [standalone package on PyPI](https://pypi.org/project/sanic-testing/).\n\nIf you have `sanic-testing` installed, it will be available and usable on your `Sanic()` application instances as before. So, the **only** change you will need to make is to add `sanic-testing` to your test suite requirements.\n\n### Application and connection level context (`ctx`) objects\n\nVersion 19.9 [added ](https://github.com/sanic-org/sanic/pull/1666/files) the `request.ctx` API. This helpful construct easily allows for attaching properties and data to a request object (for example, in middleware), and reusing the information elsewhere int he application.\n\nSimilarly, this concept is being extended in two places:\n\n1. the application instance, and\n2. a transport connection.\n\n#### Application context\n\nA common use case is to attach properties to the app instance. For the sake of consistency, and to avoid the issue of name collision with Sanic properties, the `ctx` object now exists on `Sanic` instances.\n\n```python\n@app.before_server_startup\nasync def startup_db(app, _):\n    # WRONG\n    app.db = await connect_to_db()\n\n    # CORRECT\n    app.ctx.db = await connect_to_db()\n```\n\n#### Connection context\n\nWhen a client sends a keep alive header, Sanic will attempt to keep the transport socket [open for a period of time](../../guide/running/configuration.md#keep-alive-timeout). That transport object now has a `ctx` object available on it. This effectively means that multiple requests from a single client (where the transport layer is being reused) may share state.\n\n```python\n@app.on_request\nasync def increment_foo(request):\n    if not hasattr(request.conn_info.ctx, \"foo\"):\n        request.conn_info.ctx.foo = 0\n    request.conn_info.ctx.foo += 1\n\n@app.get(\"/\")\nasync def count_foo(request):\n    return text(f\"request.conn_info.ctx.foo={request.conn_info.ctx.foo}\")\n```\n\n```bash\n$ curl localhost:8000 localhost:8000 localhost:8000\nrequest.conn_info.ctx.foo=1\nrequest.conn_info.ctx.foo=2\nrequest.conn_info.ctx.foo=3\n```\n\n\n.. warning:: \n\n    Connection level context is an experimental feature, and should be finalized in v21.6.\n\n\n\n## News\n\n### A NEW frontpage 🎉\n\nWe have split the documentation into two. The docstrings inside the codebase will still continue to build sphinx docs to ReadTheDocs. However, it will be limited to API documentation. The new frontpage will house the \"Sanic User Guide\".\n\nThe new site runs on Vuepress. Contributions are welcome. We also invite help in translating the documents.\n\nAs a part of this, we also freshened up the RTD documentation and changed it to API docs only.\n\n### Chat has moved to Discord\n\nThe Gitter chatroom has taken one step closer to being phased out. In its place we opened a [Discord server](https://discord.gg/FARQzAEMAA).\n\n### Open Collective\n\nThe Sanic Community Organization has [opened a page on Open Collective](https://opencollective.com/sanic-org) to enable anyone that would like to financially support the development of Sanic.\n\n### 2021 Release Managers\n\nThank you to @sjsadowski and @yunstanford for acting as release managers for both 2019 and 2020. This year's release managers are @ahopkins and @vltr.\n\n## Thank you\n\nThank you to everyone that participated in this release: :clap:\n\n[@ahopkins](https://github.com/ahopkins) [@akshgpt7](https://github.com/akshgpt7) [@artcg](https://github.com/artcg) [@ashleysommer](https://github.com/ashleysommer) [@elis-k](https://github.com/elis-k) [@harshanarayana](https://github.com/harshanarayana) [@sjsadowski](https://github.com/sjsadowski) [@tronic](https://github.com/tronic) [@vltr](https://github.com/vltr),\n\nTo [@ConnorZhang](https://github.com/miss85246) and [@ZinkLu](https://github.com/ZinkLu) for translating our documents into Chinese,\n\n---\n\nMake sure to checkout the changelog to get links to all the PRs, etc.\n"
  },
  {
    "path": "guide/content/en/release-notes/2021/v21.6.md",
    "content": "---\ntitle: Version 21.6\n---\n\n# Version 21.6\n\n.. toc::\n\n## Introduction\n\nThis is the second release of the version 21 [release cycle](../../organization/policies.md#release-schedule). There will be one more release in September before version 21 is \"finalized\" in the December long-term support version. One thing users may have noticed starting in 21.3, the router was moved to its own package: [`sanic-routing`](https://pypi.org/project/sanic-routing). This change is likely to stay for now. Starting with this release, the minimum required version is 0.7.0.\n\n## What to know\n\nMore details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade...\n\n### Deprecation of `StreamingHTTPResponse`\n\nThe use of `StreamingHTTPResponse` has been deprecated and will be removed in the 21.12 release. This impacts both `sanic.response.stream` and `sanic.response.file_stream`, which both under the hood instantiate `StreamingHTTPResponse`. \n\nAlthough the exact migration path has yet to be determined, `sanic.response.stream` and `sanic.response.file_stream` will continue to exist in v21.12 in some form as convenience operators. Look for more details throughout this Summer as we hope to have this finalized by the September release.\n\n### Deprecation of `CompositionView`\n\nUsage of `CompositionView` has been deprecated and will be removed in 21.12.\n\n### Deprecation of path parameter types: `string` and `number`\n\nGoing forward, you should use `str` and `float` for path param types instead of `string` and `number`.\n\n```python\n@app.get(\"/<foo:str>/<bar:float>\")\nasync def handler(request, foo: str, bar: float):\n    ...\n```\n\nExisting `string` and `number` types are aliased and will continue to work, but will be removed in v21.12.\n\n### Version 0.7 router upgrades\n\nThis includes a number of bug fixes and more gracefully handles a wider array of edge cases than v0.6. If you experience any patterns that are not supported, [please report them](https://github.com/sanic-org/sanic-routing/issues). You can see some of the issues resolved on the `sanic-routing` [release notes](https://github.com/sanic-org/sanic-routing/releases).\n\n### Inline streaming with `eof()`\n\nVersion 21.3 included [big changes in how streaming is handled](https://sanic.dev/en/guide/release-notes/v21.3.html#what-to-know). The pattern introduced will become the default (see below). As a convenience, a new `response.eof()` method has been included. It should be called once the final data has been pushed to the client:\n\n```python\n@app.route(\"/\")\nasync def test(request):\n    response = await request.respond(content_type=\"text/csv\")\n    await response.send(\"foo,\")\n    await response.send(\"bar\")\n    await response.eof()\n    return response\n```\n\n### New path parameter type: `slug`\n\nYou can now specify a dynamic path segment as a `slug` with appropriate matching:\n\n```python\n@app.get(\"/articles/<article_slug:slug>\")\nasync def article(request, article_slug: str):\n    ...\n```\n\nSlugs must consist of lowercase letters or digits. They may contain a hyphen (`-`), but it cannot be the first character.\n\n```\nthis-is-a-slug\nwith-123-is-also-a-slug\n111-at-start-is-a-slug\nNOT-a-slug\n-NOT-a-slug\n```\n\n### Stricter application and blueprint names, and deprecation\n\nYour application and `Blueprint` instances must conform to a stricter set of requirements:\n\n1. Only consisting of alphanumeric characters\n2. May contain a hyphen (`-`) or an underscore (`_`)\n3. Must begin with a letter (uppercase or lowercase)\n\nThe naming convention is similar to Python variable naming conventions, with the addition of allowing hyphens (`-`).\n\nThe looser standard has been deprecatated. Beginning in 21.12, non-conformance will be a startup time error.\n\n### A new access on `Route` object: `route.uri`\n\nThe `Route` object in v21.3 no longer had a `uri` attribute. Instead, the closes you could get was `route.path`. However, because of how `sanic-routing` works, the `path` property does *not* have a leading `/`. This has been corrected so that now there is a `route.uri` with a leading slash:\n\n```python\nroute.uri == f\"/{route.path}\"\n```\n\n### A new accessor on `Request` object impacting IPs\n\nTo access the IP address of the incoming request, Sanic has had a convenience accessor on the request object: `request.ip`. That is not new, and comes from an underlying object that provides details about the open HTTP connection: `request.conn_info`.\n\nThe current version adds a new `client_ip` accessor to that `conn_info` object. For IPv4, you will not notice a difference. However, for IPv6 applications, the new accessor will provide an \"unwrapped\" version of the address. Consider the following example:\n\n```python\n@app.get(\"/\")\nasync def handler(request):\n    return json(\n        {\n            \"request.ip\": request.ip,\n            \"request.conn_info.client\": request.conn_info.client,\n            \"request.conn_info.client_ip\": request.conn_info.client_ip,\n        }\n    )\n\napp.run(sock=my_ipv6_sock)\n```\n\n```bash\n$ curl http://\\[::1\\]:8000\n{\n  \"request.ip\": \"::1\",\n  \"request.conn_info.client\": \"[::1]\",\n  \"request.conn_info.client_ip\": \"::1\"\n}\n\n```\n\n### Alternate `Config` and `Sanic.ctx` objects\n\nYou can now pass your own config and context objects to your Sanic applications. A custom configuration *should* be a subclass of `sanic.config.Config`. The context object can be anything you want, with no restrictions whatsoever.\n\n```python\nclass CustomConfig(Config):\n    ...\n\nconfig = CustomConfig()\napp = Sanic(\"custom\", config=config)\nassert isinstance(app.config, CustomConfig)\n```\n\nAnd...\n\n```python\nclass CustomContext:\n    ...\n\nctx = CustomContext()\napp = Sanic(\"custom\", ctx=ctx)\nassert isinstance(app.ctx, CustomContext)\n```\n\n### Sanic CLI improvements\n\n1. New flag for existing feature: `--auto-reload`\n2. Some new shorthand flags for existing arguments\n3. New feature: `--factory`\n4. New feature: `--simple`\n5. New feature: `--reload-dir`\n\n#### Factory applications\n\nFor applications that follow the factory pattern (a function that returns a `sanic.Sanic` instance), you can now launch your application from the Sanic CLI using the `--factory` flag.\n\n```python\nfrom sanic import Blueprint, Sanic, text\n\nbp = Blueprint(__file__)\n\n@bp.get(\"/\")\nasync def handler(request):\n    return text(\"😎\")\n\ndef create_app() -> Sanic:\n    app = Sanic(__file__)\n    app.blueprint(bp)\n    return app\n```\n\nYou can now run it:\n\n```bash\n$ sanic path.to:create_app --factory \n```\n\n#### Sanic Simple Server\n\nSanic CLI now includes a simple pattern to serve a directory as a web server. It will look for an `index.html` at the directory root.\n\n```bash\n$ sanic ./path/to/dir --simple\n```\n\n\n.. warning:: \n\n    This feature is still in early *beta* mode. It is likely to change in scope.\n\n\n#### Additional reload directories\n\nWhen using either `debug` or `auto-reload`, you can include additional directories for Sanic to watch for new files.\n\n```bash\nsanic ... --reload-dir=/path/to/foo --reload-dir=/path/to/bar\n```\n\n\n.. tip:: \n\n    You do *NOT* need to include this on your application directory. Sanic will automatically reload when any Python file in your application changes. You should use the `reload-dir` argument when you want to listen and update your application when static files are updated.\n\n\n\n### Version prefix\n\nWhen adding `version`, your route is prefixed with `/v<YOUR_VERSION_NUM>`. This will always be at the beginning of the path. This is not new.\n\n```python\n# /v1/my/path\napp.route(\"/my/path\", version=1)\n```\n\nNow, you can alter the prefix (and therefore add path segments *before* the version).\n\n```python\n# /api/v1/my/path\napp.route(\"/my/path\", version=1, version_prefix=\"/api/v\")\n```\n\nThe `version_prefix` argument is can be defined in:\n\n- `app.route` and `bp.route` decorators (and all the convenience decorators also)\n- `Blueprint` instantiation\n- `Blueprint.group` constructor\n- `BlueprintGroup` instantiation\n- `app.blueprint` registration\n\n### Signal event auto-registration\n\nSetting `config.EVENT_AUTOREGISTER` to `True` will allow you to await any signal event even if it has not previously been defined with a signal handler.\n\n```python\n@app.signal(\"do.something.start\")\nasync def signal_handler():\n    await do_something()\n    await app.dispatch(\"do.something.complete\")\n\n# somethere else in your app:\nawait app.event(\"do.something.complete\")\n```\n\n### Infinitely reusable and nestable `Blueprint` and `BlueprintGroup`\n\nA single `Blueprint` may not be assigned and reused to multiple groups. The groups themselves can also by infinitely nested into one or more other groups. This allows for an unlimited range of composition.\n\n### HTTP methods as `Enum`\n\nSanic now has `sanic.HTTPMethod`, which is an `Enum`. It can be used interchangeably with strings:\n\n```python\nfrom sanic import Sanic, HTTPMethod\n\n@app.route(\"/\", methods=[\"post\", \"PUT\", HTTPMethod.PATCH])\nasync def handler(...):\n    ...\n```\n\n### Expansion of `HTTPMethodView`\n\nClass based views may be attached now in one of three ways:\n\n**Option 1 - Existing**\n```python\nclass DummyView(HTTPMethodView):\n    ...\n\napp.add_route(DummyView.as_view(), \"/dummy\")\n```\n\n**Option 2 - From `attach` method**\n```python\nclass DummyView(HTTPMethodView):\n    ...\n\nDummyView.attach(app, \"/\")\n```\n\n**Option 3 - From class definition at `__init_subclass__`**\n```python\nclass DummyView(HTTPMethodView, attach=app, uri=\"/\"):\n    ...\n```\n\nOptions 2 and 3 are useful if your CBV is located in another file:\n\n```python\nfrom sanic import Sanic, HTTPMethodView\n\nclass DummyView(HTTPMethodView, attach=Sanic.get_app(), uri=\"/\"):\n    ...\n```\n\n## News\n\n### Discord and support forums\n\nIf you have not already joined our community, you can become a part by joining the [Discord server](https://discord.gg/FARQzAEMAA) and the [Community Forums](https://community.sanicframework.org/). Also, follow [@sanicframework](https://twitter.com/sanicframework) on Twitter.\n\n### SCO 2022 elections\n\nThe Summer 🏝/Winter ❄️ (choose your Hemisphere) is upon us. That means we will be holding elections for the SCO. This year, we will have the following positions to fill:\n\n- Steering Council Member (2 year term)\n- Steering Council Member (2 year term)\n- Steering Council Member (1 year term)\n- Release Manager v22\n- Release Manager v22\n\n[@vltr](https://github.com/vltr) will be staying on to complete his second year on the Steering Council.\n\nIf you are interested in learning more, you can read about the SCO [roles and responsibilities](../../organization/scope.md#roles-and-responsibilities), or Adam Hopkins on Discord.\n\nNominations will begin September 1. More details will be available on the Forums as we get closer.\n\n### New project underway\n\nWe have added a new project to the SCO umbrella: [`sanic-ext`](https://github.com/sanic-org/sanic-ext). It is not yet released, and in heavy active development. The goal for the project will ultimately be to replace [`sanic-openapi`](https://github.com/sanic-org/sanic-openapi) with something that provides more features for web application developers, including input validation, CORS handling, and HTTP auto-method handlers. If you are interested in helping out, let us know on Discord. Look for an initial release of this project sometime (hopefully) before the September release.\n\n## Thank you\n\nThank you to everyone that participated in this release: :clap:\n\n[@aaugustin](https://github.com/aaugustin)\n[@ahopkins](https://github.com/ahopkins)\n[@ajaygupta2790](https://github.com/ajaygupta2790)\n[@ashleysommer](https://github.com/ashleysommer)\n[@ENT8R](https://github.com/ent8r)\n[@fredlllll](https://github.com/fredlllll)\n[@graingert](https://github.com/graingert)\n[@harshanarayana](https://github.com/harshanarayana)\n[@jdraymon](https://github.com/jdraymon)\n[@Kyle-Verhoog](https://github.com/kyle-verhoog)\n[@sanjeevanahilan](https://github.com/sanjeevanahilan)\n[@sjsadowski](https://github.com/sjsadowski)\n[@Tronic](https://github.com/tronic)\n[@vltr](https://github.com/vltr)\n[@ZinkLu](https://github.com/zinklu)\n\n---\n\nIf you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able, [financial contributions](https://opencollective.com/sanic-org/).\n"
  },
  {
    "path": "guide/content/en/release-notes/2021/v21.9.md",
    "content": "---\ntitle: Version 21.9\n---\n\n# Version 21.9\n\n.. toc::\n\n## Introduction\n\nThis is the third release of the version 21 [release cycle](../../organization/policies.md#release-schedule). Version 21 will be \"finalized\" in the December long-term support version release. \n\n## What to know\n\nMore details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade...\n\n### Removal of config values: `WEBSOCKET_READ_LIMIT`, `WEBSOCKET_WRITE_LIMIT` and `WEBSOCKET_MAX_QUEUE`\n\nWith the complete overhaul of the websocket implementation, these configuration values were removed. There currently is not a plan to replace them.\n\n### Deprecation of default value of `FALLBACK_ERROR_FORMAT`\n\nWhen no error handler is attached, Sanic has used `html` as the fallback format-type. This has been deprecated and will change to `text` starting in v22.3. While the value of this has changed to `auto`, it will still continue to use HTML as the last resort thru v21.12LTS before changing.\n\n### `ErrorHandler.lookup` signature deprecation\n\nThe `ErrorHandler.lookup` now **requires** two positional arguments:\n\n```python\ndef lookup(self, exception, route_name: Optional[str]):\n```\n\nA non-conforming method will cause Blueprint-specific exception handlers to not properly attach.\n\n### Reminder of upcoming removals\n\nAs a reminder, the following items have already been deprecated, and will be removed in version 21.12LTS\n\n- `CompositionView`\n- `load_env` (use `env_prefix` instead)\n- Sanic objects (application instances, blueprints, and routes) must by alphanumeric conforming to: `^[a-zA-Z][a-zA-Z0-9_\\-]*$`\n- Arbitrary assignment of objects to application and blueprint instances (use `ctx` instead; removal of this has been bumped from 21.9 to 21.12)\n\n### Overhaul of websockets\n\nThere has been a huge overhaul to the handling of websocket connections. Thanks to [@aaugustin](https://github.com/aaugustin) the [`websockets`](https://websockets.readthedocs.io/en/stable/index.html) now has a new implementation that allows Sanic to handle the I/O of websocket connections on its own. Therefore, Sanic has bumped the minimum version to `websockets>=10.0`.\n\nThe change should mostly be unnoticeable to developers, except that some of the oddities around websocket handlers in Sanic have been corrected. For example, you now should be able to catch the `CancellError` yourself when someone disconnects:\n\n```python\n@app.websocket(\"/\")\nasync def handler(request, ws):\n    try:\n        while True:\n            await asyncio.sleep(0.25)\n    except asyncio.CancelledError:\n        print(\"User closed connection\")\n```\n\n### Built-in signals\n\nVersion [21.3](./v21.3.md) introduced [signals](../../guide/advanced/signals.md). Now, Sanic dispatches signal events **from within the codebase** itself. This means that developers now have the ability to hook into the request/response cycle at a much closer level than before.\n\nPreviously, if you wanted to inject some logic you were limited to middleware. Think of integrated signals as _super_-middleware. The events that are dispatched now include:\n\n- `http.lifecycle.begin`\n- `http.lifecycle.complete`\n- `http.lifecycle.exception`\n- `http.lifecycle.handle`\n- `http.lifecycle.read_body`\n- `http.lifecycle.read_head`\n- `http.lifecycle.request`\n- `http.lifecycle.response`\n- `http.lifecycle.send`\n- `http.middleware.after`\n- `http.middleware.before`\n- `http.routing.after`\n- `http.routing.before`\n- `server.init.after`\n- `server.init.before`\n- `server.shutdown.after`\n- `server.shutdown.before`\n\n\n.. note::\n\n    The `server` signals are the same as the four (4) main server listener events. In fact, those listeners themselves are now just convenience wrappers to signal implementations.\n\n\n### Smarter `auto` exception formatting\n\nSanic will now try to respond with an appropriate exception format based upon the endpoint and the client. For example, if your endpoint always returns a `sanic.response.json` object, then any exceptions will automatically be formatted in JSON. The same is true for `text` and `html` responses.\n\nFurthermore, you now can _explicitly_ control which formatter to use on a route-by-route basis using the route definition:\n\n```python\n@app.route(\"/\", error_format=\"json\")\nasync def handler(request):\n    pass\n```\n\n### Blueprint copying\n\nBlueprints can be copied to new instances. This will carry forward everything attached to it, like routes, middleware, etc.\n\n```python\nv1 = Blueprint(\"Version1\", version=1)\n\n@v1.route(\"/something\")\ndef something(request):\n    pass\n\nv2 = v1.copy(\"Version2\", version=2)\n\napp.blueprint(v1)\napp.blueprint(v2)\n```\n\n```\n/v1/something\n/v2/something\n```\n### Blueprint group convenience methods\n\nBlueprint groups should now have all of the same methods available to them as regular Blueprints. With this, along with Blueprint copying, Blueprints should now be very composable and flexible.\n\n### Accept header parsing\n\nSanic `Request` objects can parse an `Accept` header to provide an ordered list of the client's content-type preference. You can simply access it as an accessor:\n\n```python\nprint(request.accept)\n# [\"*/*\"]\n```\n\nIt also is capable of handling wildcard matching. For example, assuming the incoming request included:\n\n```\nAccept: */*\n```\n\nThen, the following is `True`:\n\n```python\n\"text/plain\" in request.accept\n```\n\n### Default exception messages\n\nAny exception that derives from `SanicException` can now define a default exception message. This makes it more convenient and maintainable to reuse the same exception in multiple places without running into DRY issues with the message that the exception provides.\n\n```python\nclass TeaError(SanicException):\n    message = \"Tempest in a teapot\"\n\nraise TeaError\n```\n\n### Type annotation conveniences\n\nIt is now possible to control the path parameter types using Python's type annotations. Instead of doing this:\n\n```python\n@app.route(\"/<one:int>/<two:float>/<three:uuid>\")\ndef handler(request: Request, one: int, two: float, three: UUID):\n    ...\n```\n\nYou can now simply do this:\n\n```python\n@app.route(\"/<one>/<two>/<three>\")\ndef handler(request: Request, one: int, two: float, three: UUID):\n    ...\n```\n\nBoth of these examples will result in the same routing principles to be applied.\n\n### Explicit static resource type\n\nYou can now explicitly tell a `static` endpoint whether it is supposed to treat the resource as a file or a directory:\n\n```python\nstatic(\"/\", \"/path/to/some/file\", resource_type=\"file\"))\n```\n\n## News\n\n### Release of `sanic-ext` and deprecation of `sanic-openapi`\n\nOne of the core principles of Sanic is that it is meant to be a tool, not a dictator. As the frontpage of this website states:\n\n> Build the way you want to build without letting your tooling constrain you.\n\nThis means that a lot of common features used (specifically by Web API developers) do not exist in the `sanic` repository. This is for good reason. Being unopinionated provides the developer freedom and flexibility.\n\nBut, sometimes you do not want to have to build and rebuild the same things. Sanic has until now really relied upon the awesome support of the community to fill in the gaps with plugins.\n\nFrom the early days, there has been an official `sanic-openapi` package that offered the ability to create OpenAPI documentation based upon your application. But, that project has been plagued over the years and has not been given as much priority as the main project.\n\nStarting with the release of v21.9, the SCO is deprecating the `sanic-openapi` package and moving it to maintenance mode. This means that it will continue to get updates as needed to maintain it for the current future, but it will not receive any new feature enhancements.\n\nA new project called `sanic-ext` is taking its place. This package provides not only the ability to build OAS3 documentation, but fills in many of the gaps that API developers may want in their applications. For example, out of the box it will setup CORS, and auto enable `HEAD` and `OPTIONS` responses where needed. It also has the ability validate incoming data using either standard library Dataclasses or Pydantic models.\n\nThe list of goodies includes:\n- CORS protection\n- incoming request validation\n- auto OAS3 documentation using Redoc and/or Swagger UI\n- auto `HEAD`, `OPTIONS`, and `TRACE` responses\n- dependency injection\n- response serialization\n\nThis project is still in `alpha` mode for now and is subject to change. While it is considered to be production capable, there may be some need to change the API as we continue to add features.\n\nCheckout the [documentation](../../plugins/sanic-ext/getting-started.md) for more details.\n\n## Thank you\n\nThank you to everyone that participated in this release: :clap:\n\n[@aaugustin](https://github.com/aaugustin)\n[@ahopkins](https://github.com/ahopkins)\n[@ashleysommer](https://github.com/ashleysommer)\n[@cansarigol3megawatt](https://github.com/cansarigol3megawatt)\n[@ChihweiLHBird](https://github.com/ChihweiLHBird)\n[@gluhar2006](https://github.com/gluhar2006)\n[@komar007](https://github.com/komar007)\n[@ombe1229](https://github.com/ombe1229)\n[@prryplatypus](https://github.com/prryplatypus)\n[@SaidBySolo](https://github.com/SaidBySolo)\n[@Tronic](https://github.com/tronic)\n[@vltr](https://github.com/vltr)\n\nAnd, a special thank you to [@miss85246](https://github.com/miss85246) and [@ZinkLu](https://github.com/ZinkLu) for their tremendous work keeping the documentation synced and translated into Chinese.\n\n---\n\nIf you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able, [financial contributions](https://opencollective.com/sanic-org/).\n"
  },
  {
    "path": "guide/content/en/release-notes/2022/v22.12.md",
    "content": "---\ntitle: Version 22.12 (LTS)\n---\n\n# Version 22.12 (LTS)\n\n.. toc::\n\n## Introduction\n\nThis is the final release of the version 22 [release cycle](../../organization/policies.md#release-schedule). As such it is a **long-term support** release, and will be supported as stated in the [policies](../../org/policies.md#long-term-support-v-interim-releases).\n\n## What to know\n\nMore details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade...\n\n### 🚨 *BREAKING CHANGE* - Sanic Inspector is now an HTTP server\n\nSanic v22.9 introduced the [Inspector](./v22.9.md#inspector) to allow live inspection of a running Sanic instance. This feature relied upon opening a TCP socket and communicating over a custom protocol. That basic TCP protocol has been dropped in favor of running a full HTTP service in its place. [Learn more about the Inspector](../deployment/inspector.md).\n\nThe current release introduces a new HTTP server and a refreshed CLI experience. This enables several new features highlighted here. Perhaps the most significant change, however, is to move all of the Inspector's commands to a subparser on the CLI instance.\n\n```\n$ sanic inspect --help                             \n\n  ▄███ █████ ██      ▄█▄      ██       █   █   ▄██████████\n ██                 █   █     █ ██     █   █  ██\n  ▀███████ ███▄    ▀     █    █   ██   ▄   █  ██\n              ██  █████████   █     ██ █   █  ▄▄\n ████ ████████▀  █         █  █       ██   █   ▀██ ███████\n\nOptional\n========\n  General:\n    -h, --help                      show this help message and exit\n    --host HOST, -H HOST            Inspector host address [default 127.0.0.1]\n    --port PORT, -p PORT            Inspector port [default 6457]\n    --secure, -s                    Whether to access the Inspector via TLS encryption\n    --api-key API_KEY, -k API_KEY   Inspector authentication key\n    --raw                           Whether to output the raw response information\n\n  Subcommands:\n    Run one or none of the below subcommands. Using inspect without a subcommand will fetch general information about the state of the application instance.\n    \n    Or, you can optionally follow inspect with a subcommand. If you have created a custom Inspector instance, then you can run custom commands. See https://sanic.dev/en/guide/deployment/inspector.html for more details.\n\n    {reload,shutdown,scale,<custom>}\n        reload                      Trigger a reload of the server workers\n        shutdown                    Shutdown the application and all processes\n        scale                       Scale the number of workers\n        <custom>                    Run a custom command\n```\n\n#### CLI remote access now available\n\nThe `host` and `port` of the Inspector are now explicitly exposed on the CLI as shown above. Previously in v22.9, they were inferred by reference to the application instance. Because of this change, it will be more possible to expose the Inspector on live production instances and access from a remote installation of the CLI. \n\nFor example, you can check your running production deployment from your local development machine.\n\n```\n$ sanic inspect --host=1.2.3.4\n```\n\n\n.. warning:: \n\n    For **production** instances, make sure you are _using TLS and authentication_ described below.\n\n#### TLS encryption now available\n\nYou can secure your remote Inspector access by providing a TLS certificate to encrypt the web traffic.\n\n```python\napp.config.INSPECTOR_TLS_CERT = \"/path/to/cert.pem\"\napp.config.INSPECTOR_TLS_KEY = \"/path/to/key.pem\"\n```\n\nTo access an encrypted installation via the CLI, use the `--secure` flag.\n\n```\n$ sanic inspect --secure\n```\n\n#### Authentication now available\n\nTo control access to the remote Inspector, you can protect the endpoints using an API key.\n\n```python\napp.config.INSPECTOR_API_KEY = \"Super-Secret-200\"\n```\n\nTo access a protected installation via the CLI, use the `--api-key` flag.\n\n```\n$ sanic inspect --api-key=Super-Secret-200\n```\n\nThis is equivalent to the header: `Authorization: Bearer <KEY>`.\n\n```\n$ curl http://localhost:6457  -H \"Authorization: Bearer Super-Secret-200\"\n```\n\n### Scale number of running server workers\n\nThe Inspector is now capable of scaling the number of worker processes. For example, to scale to 3 replicas, use the following command:\n\n```\n$ sanic inspect scale 3\n```\n\n### Extend Inspector with custom commands\n\nThe Inspector is now fully extendable to allow for adding custom commands to the CLI. For more information see [Custom Commands](../deployment/inspector.md#custom-commands).\n\n```\n$ sanic inspect foo --bar\n```\n\n### Early worker exit on failure\n\nThe process manager shipped with v22.9 had a very short startup timeout. This was to protect against deadlock. This was increased to 30s, and a new mechanism has been added to fail early if there is a crash in a worker process on startup.\n\n### Introduce `JSONResponse` with convenience methods to update a JSON response body\n\nThe `sanic.response.json` convenience method now returns a new subclass of `HTTPResponse` appropriately named: `JSONResponse`. This new type has some convenient methods for handling changes to a response body after its creation.\n\n```python\nresp = json({\"foo\": \"bar\"})\nresp.update({\"another\": \"value\"})\n```\n\nSee [Returning JSON Data](../basics/response.md#returning-json-data) for more information.\n\n### Updates to downstream requirements: `uvloop` and `websockets`\n\nMinimum `uvloop` was set to `0.15.0`. Changes were added to make Sanic compliant with `websockets` version `11.0`.\n\n### Force exit on 2nd `ctrl+c`\n\nOn supporting operating systems, the existing behavior is for Sanic server to try to perform a graceful shutdown when hitting `ctrl+c`. This new release will perform an immediate shutdown on subsequent `ctrl+c` after the initial shutdown has begun.\n\n### Deprecations and Removals\n\n1. *DEPRECATED* - The `--inspect*` commands introduced in v22.9 have been replaced with a new subcommand parser available as `inspect`. The flag versions will continue to operate until v23.3. You are encouraged to use the replacements. While this short deprecation period is a deviation from the standard two-cycles, we hope this change will be minimally disruptive.\n    ```\n    OLD   sanic ... --inspect\n    NEW   sanic ... inspect\n    \n    OLD   sanic ... --inspect-raw\n    NEW   sanic ... inspect --raw\n    \n    OLD   sanic ... --inspect-reload\n    NEW   sanic ... inspect reload\n    \n    OLD   sanic ... --inspect-shutdown\n    NEW   sanic ... inspect shutdown\n    ```\n\n## News\n\nThe Sanic Community Organization will be headed by a new Steering Council for 2023. There are two returning and two new members. \n\n[@ahopkins](https://github.com/ahopkins)  *returning* \\\n[@prryplatypus](https://github.com/prryplatypus)  *returning* \\\n[@sjsadowski](https://github.com/sjsadowski)  *NEW* \\\n[@Tronic](https://github.com/Tronic)  *NEW*\n\nThe 2023 release managers are [@ahopkins](https://github.com/ahopkins) and [@sjsadowski](https://github.com/sjsadowski).\n\nIf you are interested in getting more involved with Sanic, contact us on the [Discord server](https://discord.gg/FARQzAEMAA).\n\n## Thank you\n\nThank you to everyone that participated in this release: :clap:\n\n[@aaugustin](https://github.com/aaugustin)\n[@ahopkins](https://github.com/ahopkins)\n[@ChihweiLHBird](https://github.com/ChihweiLHBird)\n[@kijk2869](https://github.com/kijk2869)\n[@LiraNuna](https://github.com/LiraNuna)\n[@prryplatypus](https://github.com/prryplatypus)\n[@sjsadowski](https://github.com/sjsadowski)\n[@todddialpad](https://github.com/todddialpad)\n[@Tronic](https://github.com/Tronic)\n\n---\n\nIf you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/).\n"
  },
  {
    "path": "guide/content/en/release-notes/2022/v22.3.md",
    "content": "---\ntitle: Version 22.3\n---\n\n# Version 22.3\n\n.. toc::\n\n## Introduction\n\nThis is the first release of the version 22 [release cycle](../../organization/policies.md#release-schedule). All of the standard SCO libraries are now entering the same release cycle and will follow the same versioning pattern. Those packages are:\n\n- [`sanic-routing`](https://github.com/sanic-org/sanic-routing)\n- [`sanic-testing`](https://github.com/sanic-org/sanic-testing)\n- [`sanic-ext`](https://github.com/sanic-org/sanic-ext)\n\n## What to know\n\nMore details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade...\n\n### Application multi-serve\n\nThe Sanic server now has an API to allow you to run multiple applications side-by-side in the same process. This is done by calling `app.prepare(...)` on one or more application instances, one or many times. Each time it should be bound to a unique host/port combination. Then, you begin serving the applications by calling `Sanic.serve()`.\n\n```python\napp = Sanic(\"One\")\napp2 = Sanic(\"Two\")\n\napp.prepare(port=9999)\napp.prepare(port=9998)\napp.prepare(port=9997)\napp2.prepare(port=8888)\napp2.prepare(port=8887)\n\nSanic.serve()\n```\n\nIn the above snippet, there are two applications that will be run concurrently and bound to multiple ports. This feature is *not* supported in the CLI.\n\nThis pattern is meant to be an alternative to running `app.run(...)`. It should be noted that `app.run` is now just a shorthand for the above pattern and is still fully supported.\n\n### 👶 *BETA FEATURE* - New path parameter type: file extensions\n\nA very common pattern is to create a route that dynamically generates a file. The endpoint is meant to match on a file with an extension. There is a new path parameter to match files: `<foo:ext>`.\n\n```python\n@app.get(\"/path/to/<filename:ext>\")\nasync def handler(request, filename, ext):\n    ...\n```\n\nThis will catch any pattern that ends with a file extension. You may, however want to expand this by specifying which extensions, and also by using other path parameter types for the file name. \n\nFor example, if you want to catch a `.jpg` file that is only numbers:\n\n```python\n@app.get(\"/path/to/<filename=int:ext=jpg>\")\nasync def handler(request, filename, ext):\n    ...\n```\n\nSome potential examples:\n\n| definition                        | example     | filename    | extension  |\n| --------------------------------- | ----------- | ----------- | ---------- |\n| \\<file:ext>                        | page.txt    | `\"page\"`    | `\"txt\"`    |\n| \\<file:ext=jpg>                    | cat.jpg     | `\"cat\"`     | `\"jpg\"`    |\n| \\<file:ext=jpg\\|png\\|gif\\|svg>     | cat.jpg     | `\"cat\"`     | `\"jpg\"`    |\n| <file=int:ext>                    | 123.txt     | `123`       | `\"txt\"`    |\n| <file=int:ext=jpg\\|png\\|gif\\|svg> | 123.svg     | `123`       | `\"svg\"`    |\n| <file=float:ext=tar.gz>           | 3.14.tar.gz | `3.14`      | `\"tar.gz\"` | \n\n### 🚨 *BREAKING CHANGE* - Path parameter matching of non-empty strings\n\nA dynamic path parameter will only match on a non-empty string. \n\nPreviously a route with a dynamic string parameter (`/<foo>` or `/<foo:str>`) would match on any string, including empty strings. It will now only match a non-empty string. To retain the old behavior, you should use the new parameter type: `/<foo:strorempty>`.\n\n```python\n@app.get(\"/path/to/<foo:strorempty>\")\nasync def handler(request, foo)\n    ...\n```\n\n### 🚨 *BREAKING CHANGE* - `sanic.worker.GunicornWorker` has been removed\n\nDeparting from our normal deprecation policy, the `GunicornWorker` was removed as a part of the process of upgrading the Sanic server to include multi-serve. This decision was made largely in part because even while it existed it was not an optimal strategy for deploying Sanic.\n\nIf you want to deploy Sanic using `gunicorn`, then you are advised to do it using [the strategy implemented by `uvicorn`](https://www.uvicorn.org/#running-with-gunicorn). This will effectively run Sanic as an ASGI application through `uvicorn`. You can upgrade to this pattern by installing `uvicorn`:\n\n```\npip install uvicorn\n```\n\nThen, you should be able to run it with a pattern like this:\n\n```\ngunicorn path.to.sanic:app -k uvicorn.workers.UvicornWorker\n```\n\n### Authorization header parsing\n\nThe `Authorization` header has been partially parseable for some time now. You have been able to use `request.token` to gain access to a header that was in one of the following two forms:\n\n```\nAuthorization: Token <SOME TOKEN HERE>\nAuthorization: Bearer <SOME TOKEN HERE>\n```\n\nSanic can now parse more credential types like `BASIC`:\n\n```\nAuthorization: Basic Z2lsLWJhdGVzOnBhc3N3b3JkMTIz\n```\n\nThis can be accessed now as `request.credentials`:\n\n```python\nprint(request.credentials)\n# Credentials(auth_type='Basic', token='Z2lsLWJhdGVzOnBhc3N3b3JkMTIz', _username='gil-bates', _password='password123')\n```\n\n### CLI arguments optionally injected into application factory\n\nSanic will now attempt to inject the parsed CLI arguments into your factory if you are using one.\n\n```python\ndef create_app(args):\n    app = Sanic(\"MyApp\")\n    print(args)\n    return app\n```\n```\n$sanic p:create_app --factory\nNamespace(module='p:create_app', factory=True, simple=False, host='127.0.0.1', port=8000, unix='', cert=None, key=None, tls=None, tlshost=False, workers=1, fast=False, access_log=False, debug=False, auto_reload=False, path=None, dev=False, motd=True, verbosity=None, noisy_exceptions=False)\n```\n\nIf you are running the CLI with `--factory`, you also have the option of passing arbitrary arguments to the command, which will be injected into the argument `Namespace`.\n\n```\nsanic p:create_app --factory --foo=bar\nNamespace(module='p:create_app', factory=True, simple=False, host='127.0.0.1', port=8000, unix='', cert=None, key=None, tls=None, tlshost=False, workers=1, fast=False, access_log=False, debug=False, auto_reload=False, path=None, dev=False, motd=True, verbosity=None, noisy_exceptions=False, foo='bar')\n```\n\n### New reloader process listener events\n\nWhen running Sanic server with auto-reload, there are two new events that trigger a listener *only* on the reloader process:\n\n- `reload_process_start`\n- `reload_process_stop`\n\nThese are only triggered if the reloader is running.\n\n```python\n@app.reload_process_start\nasync def reload_start(*_):\n    print(\">>>>>> reload_start <<<<<<\")\n\n@app.reload_process_stop\nasync def reload_stop(*_):\n    print(\">>>>>> reload_stop <<<<<<\")\n```\n\n### The event loop is no longer a required argument of a listener\n\nYou can leave out the `loop` argument of a listener. Both of these examples work as expected:\n\n```python\n@app.before_server_start\nasync def without(app):\n    ...\n\n@app.before_server_start\nasync def with(app, loop):\n    ...\n```\n\n### Removal - Debug mode does not automatically start the reloader\n\nWhen running with `--debug` or `debug=True`, the Sanic server will not automatically start the auto-reloader. This feature of doing both on debug was deprecated in v21 and removed in this release. If you would like to have *both* debug mode and auto-reload, you can use `--dev` or `dev=True`.\n\n**dev = debug mode + auto reloader**\n\n### Deprecation - Loading of lower case environment variables\n\nSanic loads prefixed environment variables as configuration values. It has not distinguished between uppercase and lowercase as long as the prefix matches. However, it has always been the convention that the keys should be uppercase. This is deprecated and you will receive a warning if the value is not uppercase. In v22.9 only uppercase and prefixed keys will be loaded.\n\n## News\n\n### Packt publishes new book on Sanic web development\n\n.. column::\n\n    There is a new book on **Python Web Development with Sanic** by [@ahopkins](https://github.com/ahopkins). The book is endorsed by the SCO and part of the proceeds of all sales go directly to the SCO for further development of Sanic.\n\n    You can learn more at [sanicbook.com](https://sanicbook.com/)\n\n.. column::\n\n    ![Python Web Development with Sanic](https://sanicbook.com/images/SanicCoverFinal.png)\n\n## Thank you\n\nThank you to everyone that participated in this release: :clap:\n\n[@aericson](https://github.com/aericson)\n[@ahankinson](https://github.com/ahankinson)\n[@ahopkins](https://github.com/ahopkins)\n[@ariebovenberg](https://github.com/ariebovenberg)\n[@ashleysommer](https://github.com/ashleysommer)\n[@Bluenix2](https://github.com/Bluenix2)\n[@ChihweiLHBird](https://github.com/ChihweiLHBird)\n[@dotlambda](https://github.com/dotlambda)\n[@eric-spitler](https://github.com/eric-spitler)\n[@howzitcdf](https://github.com/howzitcdf)\n[@jonra1993](https://github.com/jonra1993)\n[@prryplatypus](https://github.com/prryplatypus)\n[@raphaelauv](https://github.com/raphaelauv)\n[@SaidBySolo](https://github.com/SaidBySolo)\n[@SerGeRybakov](https://github.com/SerGeRybakov)\n[@Tronic](https://github.com/Tronic)\n\n---\n\nIf you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/).\n"
  },
  {
    "path": "guide/content/en/release-notes/2022/v22.6.md",
    "content": "---\ntitle: Version 22.6\n---\n\n# Version 22.6\n\n.. toc::\n\n## Introduction\n\nThis is the second release of the version 22 [release cycle](../../organization/policies.md#release-schedule). Version 22 will be \"finalized\" in the December long-term support version release. \n\n## What to know\n\nMore details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade...\n\n### Automatic TLS setup in `DEBUG` mode\n\nThe Sanic server can automatically setup a TLS certificate using either [mkcert](https://github.com/FiloSottile/mkcert) or [trustme](https://github.com/python-trio/trustme). This certificate will enable `https://localhost` (or another local address) for local development environments. You must install either `mkcert` or `trustme` on your own for this to work.\n\n.. column::\n\n    ```\n    $ sanic path.to.server:app --auto-tls --debug\n    ```\n\n.. column::\n\n    ```python\n    app.run(debug=True, auto_tls=True)\n    ```\n\nThis feature is not available when running in `ASGI` mode, or in `PRODUCTION` mode. When running Sanic in production, you should be using a real TLS certificate either purchased through a legitimate vendor, or using [Let's Encrypt](https://letsencrypt.org/).\n\n### HTTP/3 Server 🚀\n\nIn June 2022, the IETF finalized and published [RFC 9114](https://www.rfc-editor.org/rfc/rfc9114.html), the specification for HTTP/3. In short, HTTP/3 is a **very** different protocol than HTTP/1.1 and HTTP/2 because it implements HTTP over UDP instead of TCP. The new HTTP protocol promises faster webpage load times and solving some of the problems of the older standards. You are encouraged to [read more about](https://http3-explained.haxx.se/) this new web technology. You likely will need to install a [capable client](https://curl.se/docs/http3.html) as traditional tooling will not work.\n\nSanic server offers HTTP/3 support using [aioquic](https://github.com/aiortc/aioquic). This **must** be installed:\n\n```\npip install sanic aioquic\n```\n\n```\npip install sanic[http3]\n```\n\nTo start HTTP/3, you must explicitly request it when running your application.\n\n.. column::\n\n    ```\n    $ sanic path.to.server:app --http=3\n    ```\n\n    ```\n    $ sanic path.to.server:app -3\n    ```\n\n.. column::\n\n    ```python\n    app.run(version=3)\n    ```\n\nTo run both an HTTP/3 and HTTP/1.1 server simultaneously, you can use [application multi-serve](./v22.3.html#application-multi-serve) introduced in v22.3.\n\n\n.. column::\n\n    ```\n    $ sanic path.to.server:app --http=3 --http=1\n    ```\n\n    ```\n    $ sanic path.to.server:app -3 -1\n    ```\n\n.. column::\n\n    ```python\n    app.prepre(version=3)\n    app.prepre(version=1)\n    Sanic.serve()\n    ```\n\nBecause HTTP/3 requires TLS, you cannot start a HTTP/3 server without a TLS certificate. You should [set it up yourself](../../guide/how-to/tls.md) or use `mkcert` if in `DEBUG` mode. Currently, automatic TLS setup for HTTP/3 is not compatible with `trustme`.\n\n**👶 This feature is being released as an *EARLY RELEASE FEATURE*.** It is **not** yet fully compliant with the HTTP/3 specification, lacking some features like [websockets](https://websockets.spec.whatwg.org/), [webtransport](https://w3c.github.io/webtransport/), and [push responses](https://http3-explained.haxx.se/en/h3/h3-push). Instead the intent of this release is to bring the existing HTTP request/response cycle towards feature parity with HTTP/3. Over the next several releases, more HTTP/3 features will be added and the API for it finalized.\n\n### Consistent exception naming\n\nSome of the Sanic exceptions have been renamed to be more compliant with standard HTTP response names.\n\n- `InvalidUsage` >> `BadRequest`\n- `MethodNotSupported` >> `MethodNotAllowed`\n- `ContentRangeError` >> `RangeNotSatisfiable`\n\nAll old names have been aliased and will remain backwards compatible.\n\n### Current request getter\n\nSimilar to the API to access an application (`Sanic.get_app()`), there is a new method for retrieving the current request when outside of a request handler.\n\n```python\nfrom sanic import Request\n\nRequest.get_current()\n```\n\n### Improved API support for setting cache control headers\n\nThe `file` response helper has some added parameters to make it easier to handle setting of the [Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) header.\n\n```python\nfile(\n    ...,\n    last_modified=...,\n    max_age=...,\n    no_store=...,\n)\n```\n\n### Custom `loads` function\n\nJust like Sanic supports globally setting a custom `dumps`, you can now set a global custom `loads`.\n\n```python\nfrom orjson import loads\n\napp = Sanic(\"Test\", loads=loads)\n```\n\n### Deprecations and Removals\n\n1. *REMOVED* - Applications may no longer opt-out of the application registry\n1. *REMOVED* - Custom exception handlers will no longer run after some part of an exception has been sent\n1. *REMOVED* - Fallback error formats cannot be set on the `ErrorHandler` and must **only** be set in the `Config`\n1. *REMOVED* - Setting a custom `LOGO` for startup is no longer allowed\n1. *REMOVED* - The old `stream` response convenience method has been removed\n1. *REMOVED* - `AsyncServer.init` is removed and no longer an alias of `AsyncServer.app.state.is_started`\n\n\n## Thank you\n\nThank you to everyone that participated in this release: :clap:\n\n[@ahopkins](https://github.com/ahopkins)\n[@amitay87](https://github.com/amitay87 )\n[@ashleysommer](https://github.com/ashleysommer)\n[@azimovMichael](https://github.com/azimovMichael)\n[@ChihweiLHBird](https://github.com/ChihweiLHBird)\n[@kijk2869](https://github.com/kijk2869)\n[@prryplatypus](https://github.com/prryplatypus)\n[@SaidBySolo](https://github.com/SaidBySolo)\n[@sjsadowski](https://github.com/sjsadowski)\n[@timmo001](https://github.com/timmo001)\n[@zozzz](https://github.com/zozzz)\n\n---\n\nIf you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/).\n"
  },
  {
    "path": "guide/content/en/release-notes/2022/v22.9.md",
    "content": "---\ntitle: Version 22.9\n---\n\n# Version 22.9\n\n.. toc::\n\n## Introduction\n\nThis is the third release of the version 22 [release cycle](../../organization/policies.md#release-schedule). Version 22 will be \"finalized\" in the December long-term support version release. \n\n## What to know\n\nMore details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade...\n\n### ⚠ *IMPORTANT* - New worker manager 🚀\n\nSanic server has been overhauled to provide more consistency and flexbility in how it operates. More details about the motivations are outlined in [PR #2499](https://github.com/sanic-org/sanic/pull/2499) and discussed in a live stream [discussion held on YouTube](https://youtu.be/m8HCO8NK7HE).\n\nThis **does NOT apply** to Sanic in ASGI mode\n\n#### Overview of the changes\n\n- The worker servers will **always** run in a child process.\n    - Previously this could change depending upon one versus many workers, and the usage or not of the reloader. This should lead to much more predictable development environments that more closely match their production counterparts.\n- Multi-workers is **now supported on Windows**.\n    - This was impossible because Sanic relied upon the `multiprocessing` module using `fork`, which is not available on Windows.\n    - Now, Sanic will always use `spawn`. This does have some noticeable differences, particularly if you are running Sanic in the global scope with `app.run` (see below re: upgrade issues).\n- The application instance now has a new `multiplexer` object that can be used to restart one or many workers. This could, for example, be triggered by a request.\n- There is a new Inspector that can provide details on the state of your server.\n- Sanic worker manager can run arbitrary processes.\n    - This allows developers to add any process they want from within Sanic.\n    - Possible use cases:\n        - Health monitor, see [Sanic Extensions]()\n        - Logging queue, see [Sanic Extensions]()\n        - Background worker queue in a seperate process\n        - Running another application, like a bot\n- There is a new listener called: `main_process_ready`. It really should only be used for adding arbitrary processes to Sanic.\n- Passing shared objects between workers.\n    - Python does allow some types of objects to share state between processes, whether through shared memory, pipes, etc.\n    - Sanic will now allow these types of object to be shared on the `app.shared_ctx` object.\n    - Since this feature relies upon Pythons `multiprocessing` library, it obviously will only work to share state between Sanic worker instances that are instantiated from the same execution. This is *not* meant to provide an API for horizontal scaling across multiple machines for example.\n\n#### Adding a shared context object\n\nTo share an object between worker processes, it *MUST* be assigned inside of the `main_process_start` listener.\n\n```python\nfrom multiprocessing import Queue\n\n@app.main_process_start\nasync def main_process_start(app):\n    app.shared_ctx.queue = Queue()\n```\n\nAll objects on `shared_ctx` will be available now within each worker process. \n\n```python\n@app.before_server_starts\nasync def before_server_starts(app):\n    assert isinstance(app.shared_ctx.queue, Queue)\n\n@app.on_request\nasync def on_request(request):\n    assert isinstance(request.app.shared_ctx.queue, Queue)\n\n@app.get(\"/\")\nasync def handler(request):\n    assert isinstance(request.app.shared_ctx.queue, Queue)\n```\n\n*NOTE: Sanic will not stop you from registering an unsafe object, but may warn you. Be careful not to just add a regular list object, for example, and expect it to work. You should have an understanding of how to share state between processes.*\n\n#### Running arbitrary processes\n\nSanic can run any arbitrary process for you. It should be capable of being stopped by a `SIGINT` or `SIGTERM` OS signal.\n\nThese processes should be registered inside of the `main_process_ready` listener.\n\n```python\n@app.main_process_ready\nasync def ready(app: Sanic, _):\n    app.manager.manage(\"MyProcess\", my_process, {\"foo\": \"bar\"})\n#   app.manager.manage(<name>, <callable>, <kwargs>)\n```\n\n#### Inspector\n\nSanic ships with an optional Inspector, which is a special process that allows for the CLI to inspect the running state of an application and issue commands. It currently will only work if the CLI is being run on the same machine as the Sanic instance.\n\n```\nsanic path.to:app --inspect\n```\n\n![Sanic inspector](https://user-images.githubusercontent.com/166269/190099384-2f2f3fae-22d5-4529-b279-8446f6b5f9bd.png)\n\nThe new CLI commands are:\n\n```\n    --inspect                      Inspect the state of a running instance, human readable\n    --inspect-raw                  Inspect the state of a running instance, JSON output\n    --trigger-reload               Trigger worker processes to reload\n    --trigger-shutdown             Trigger all processes to shutdown\n```\n\nThis is not enabled by default. In order to have it available, you must opt in:\n\n```python\napp.config.INSPECTOR = True\n```\n\n*Note: [Sanic Extensions]() provides a [custom request](../../guide/basics/app.md#custom-requests) class that will add a request counter to the server state.\n\n#### Application multiplexer\n\nMany of the same information and functionality is available on the application instance itself. There is a new `multiplexer` object on the application instance that has the ability to restart one or more workers, and fetch information about the current state.\n\nYou can access it as `app.multiplexer`, or more likely by its short alias `app.m`.\n\n```python\n@app.on_request\nasync def print_state(request: Request):\n    print(request.app.m.state)\n```\n\n#### Potential upgrade issues\n\nBecause of the switch from `fork` to `spawn`, if you try running the server in the global scope you will receive an error. If you see something like this:\n\n```\nsanic.exceptions.ServerError: Sanic server could not start: [Errno 98] Address already in use.\nThis may have happened if you are running Sanic in the global scope and not inside of a `if __name__ == \"__main__\"` block.\n```\n\n... then the change is simple. Make sure `app.run` is inside a block.\n\n```python\nif __name__ == \"__main__\":\n    app.run(port=9999, dev=True)\n```\n\n#### Opting out of the new functionality\n\nIf you would like to run Sanic without the new process manager, you may easily use the legacy runners. Please note that support for them **will be removed** in the future. A date has not yet been set, but will likely be sometime in 2023.\n\nTo opt out of the new server and use the legacy, choose the appropriate method depending upon how you run Sanic:\n\n.. column::\n\n    If you use the CLI...\n\n.. column::\n\n    ```\n    sanic path.to:app --legacy\n    ```\n\n\n.. column::\n\n    If you use `app.run`...\n\n.. column::\n\n    ```\n    app.run(..., legacy=True)\n    ```\n\n\n.. column::\n\n    If you `app.prepare`...\n\n.. column::\n\n    ```\n    app.prepare(...)\n    Sanic.serve_legacy()\n    ```\n\nSimilarly, you can force Sanic to run in a single process. This however means there will not be any access to the auto-reloader.\n\n.. column::\n\n    If you use the CLI...\n\n.. column::\n\n    ```\n    sanic path.to:app --single-process\n    ```\n\n\n.. column::\n\n    If you use `app.run`...\n\n.. column::\n\n    ```\n    app.run(..., single_process=True)\n    ```\n\n\n.. column::\n\n    If you `app.prepare`...\n\n.. column::\n\n    ```\n    app.prepare(...)\n    Sanic.serve_single()\n    ```\n\n### Middleware priority\n\nMiddleware is executed in an order based upon when it was defined. Request middleware are executed in sequence and response middleware in reverse. This could have an unfortunate impact if your ordering is strictly based upon import ordering with global variables for example.\n\nA new addition is to break-out of the strict construct and allow a priority to be assigned to a middleware. The higher the number for a middleware definition, the earlier in the sequence it will be executed. This applies to **both** request and response middleware.\n\n```python\n@app.on_request\nasync def low_priority(_):\n    ...\n\n@app.on_request(priority=10)\nasync def high_priority(_):\n    ...\n```\n\nIn the above example, even though `low_priority` is defined first, `high_priority` will run first.\n\n### Custom `loads` function\n\nSanic has supported the ability to add a [custom `dumps` function](https://sanic.readthedocs.io/en/stable/sanic/api/app.html#sanic.app.Sanic) when instantiating an app. The same functionality has been extended to `loads`, which will be used when deserializing.\n\n```python\nfrom json import loads\n\nSanic(\"Test\", loads=loads)\n```\n\n### Websocket objects are now iterable\n\nRather than calling `recv` in a loop on a `Websocket` object, you can iterate on it in a `for` loop.\n\n```python\nfrom sanic import Request, Websocket\n\n@app.websocket(\"/ws\")\nasync def ws_echo_handler(request: Request, ws: Websocket):\n    async for msg in ws:\n        await ws.send(msg)\n```\n\n### Appropriately respond with 304 on static files\n\nWhen serving a static file, the Sanic server can respond appropriately to a request with `If-Modified-Since` using a `304` response instead of resending a file.\n\n### Two new signals to wrap handler execution\n\nTwo new [signals](../../guide/advanced/signals.md) have been added that wrap the execution of a request handler.\n\n- `http.handler.before` - runs after request middleware but before the route handler\n- `http.handler.after` - runs after the route handler\n    - In *most* circumstances, this also means that it will run before response middleware. However, if you call `request.respond` from inside of a route handler, then your middleware will come first\n\n### New Request properties for HTTP method information\n\nThe HTTP specification defines which HTTP methods are: safe, idempotent, and cacheable. New properties have been added that will respond with a boolean flag to help identify the request property based upon the method.\n\n```python\nrequest.is_safe\nrequest.is_idempotent\nrequest.is_cacheable\n```\n\n### 🚨 *BREAKING CHANGE* - Improved cancel request exception\n\nIn prior version of Sanic, if a `CancelledError` was caught it could bubble up and cause the server to respond with a `503`. This is not always the desired outcome, and it prevented the usage of that error in other circumstances. As a result, Sanic will now use a subclass of `CancelledError` called: `RequestCancelled` for this functionality. It likely should have little impact unless you were explicitly relying upon the old behavior.\n\nFor more details on the specifics of these properties, checkout the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods).\n\n### New deprecation warning filter\n\nYou can control the level of deprecation warnings from Sanic using [standard library warning filter values](https://docs.python.org/3/library/warnings.html#the-warnings-filter). Default is `\"once\"`.\n\n```python\napp.config.DEPRECATION_FILTER = \"ignore\"\n```\n\n### Deprecations and Removals\n\n1. *DEPRECATED* - Duplicate route names have been deprecated and will be removed in v23.3\n1. *DEPRECATED* - Registering duplicate exception handlers has been deprecated and will be removed in v23.3\n1. *REMOVED* - `route.ctx` not set by Sanic, and is a blank object for users, therefore ...\n    - `route.ctx.ignore_body` >> `route.extra.ignore_body`\n    - `route.ctx.stream` >> `route.extra.stream`\n    - `route.ctx.hosts` >> `route.extra.hosts`\n    - `route.ctx.static` >> `route.extra.static`\n    - `route.ctx.error_format` >> `route.extra.error_format`\n    - `route.ctx.websocket` >> `route.extra.websocket`\n1. *REMOVED* - `app.debug` is READ-ONLY\n1. *REMOVED* - `app.is_running` removed\n1. *REMOVED* - `app.is_stopping` removed\n1. *REMOVED* - `Sanic._uvloop_setting` removed\n1. *REMOVED* - Prefixed environment variables will be ignored if not uppercase\n\n## Thank you\n\nThank you to everyone that participated in this release: :clap:\n\n[@ahopkins](https://github.com/ahopkins)\n[@azimovMichael](https://github.com/azimovMichael)\n[@ChihweiLHBird](https://github.com/ChihweiLHBird)\n[@huntzhan](https://github.com/huntzhan)\n[@monosans](https://github.com/monosans)\n[@prryplatypus](https://github.com/prryplatypus)\n[@SaidBySolo](https://github.com/SaidBySolo)\n[@seemethere](https://github.com/seemethere)\n[@sjsadowski](https://github.com/sjsadowski)\n[@timgates42](https://github.com/timgates42)\n[@Tronic](https://github.com/Tronic)\n\n---\n\nIf you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/).\n"
  },
  {
    "path": "guide/content/en/release-notes/2023/v23.12.md",
    "content": "---\ntitle: Version 23.12\n---\n\n# Version 23.12 (LTS)\n\n.. toc::\n\n\n## Introduction\n\nThis is the final release of the version 23 [release cycle](../../organization/policies.md#release-schedule). It is designated as a **long-term support (\"LTS\") release**, which means it will receive support for two years as stated in the support policy. If you run into any issues, please raise a concern on [GitHub](https://github.com/sanic-org/sanic/issues/new/choose.)\n\n## What to know\n\nMore details in the [Changelog](../changelog.html). Notable new or breaking features, and what to upgrade:\n\n### 🎉 Documentation now using {span:has-text-primary:Sanic}\n\n![](http://127.0.0.1:8000/assets/images/sanic-framework-logo-circle-128x128.png)\n\nYou can read [all about how](/en/built-with-sanic.html), but we converted this documentation site to using the SHH 🤫 stack.\n\n- [Sanic](https://sanic.dev)\n- [html5tagger](https://github.com/sanic-org/html5tagger)\n- [HTMX](https://htmx.org/)\n\n### 👶 *BETA* Welcome to the Sanic interactive console\n\nThat's right, Sanic now ships with a REPL!\n\n![](/assets/images/repl.png)\n\nWhen using the Sanic CLI, you can pass the `--repl` argument to automatically run an interactive console along side your application. This is extremely helpful while developing, and allows you access to the application instance, as well as a built-in client enabled to send HTTP requests to the running instance.\n\nIf you use the `--dev` flag, this feature is quasi-enabled by default. While it will not outright run the REPL, it will start the process and allow you to enter the REPL at anytime by hitting `<ENTER>` on your keyboard.\n\n*This is still in BETA mode. We would appreciate you letting us know about any any enhancement requests or issues.*\n\n### Python 3.12 support\n\nWe have added Python 3.12 to the supported versions.\n\n### Start and restart arbitrary processes\n\nUsing the [multiplexer](../../guide/running/manager.md#access-to-the-multiplexer), you can now start and restart arbitrary or pre-existing processes. This enabled the following new features in the way the multiplexer and worker manager operate:\n\n1. `multiplexer.restart(\"<process name>\")` will now restart a targeted single process\n2. `multiplexer.manage(...)` is a new method that works exactly like `manager.manage(...)`\n3. The `manage` methods now have additional keyword arguments:\n   - `tracked` - whether the state of the process is tracked after the process completes\n   - `restartable` - whether the process should be allowed to be restarted\n   - `auto_start` - whether the process should be started immediately after it is created\n\n```python\ndef task(n: int = 10, **kwargs):\n    print(\"TASK STARTED\", kwargs)\n    for i in range(n):\n        print(f\"Running task - Step {i+1} of {n}\")\n        sleep(1)\n\n@app.get(\"/restart\")\nasync def restart_handler(request: Request):\n    request.app.m.restart(\"Sanic-TEST-0\")\n    return json({\"foo\": request.app.m.name})\n\n\n@app.get(\"/start\")\nasync def start_handler(request: Request):\n    request.app.m.manage(\"NEW\", task, kwargs={\"n\": 7}, workers=2)\n    return json({\"foo\": request.app.m.name})\n\n@app.main_process_ready\ndef start_process(app: Sanic):\n    app.manager.manage(\"TEST\", task, kwargs={\"n\": 3}, restartable=True)\n```\n\n### Prioritized listeners and signals\n\nIn [v22.9](../2022/v22.9.md) Sanic added prioritization to middleware to allow arbitrary ordering of middleware. This same concept has now been extended to listeners and signals. This will allow a priority number to be assigned at creation time that will override its default position in the execution timeline.\n\n```python\n@app.before_server_start(priority=3)\nasync def sample(app):\n    ...\n```\n\nThe higher the number, the higher priority it will receive. Overall the rules for deciding the order of execution are as follows:\n\n1. Priority in descending order\n2. Application listeners before Blueprint listeners\n3. Registration order\n\n*Remember, some listeners are executed in reverse order*\n\n### Websocket signals\n\nWe have added three new signals for websockets:\n\n1. `websocket.handler.before`\n2. `websocket.handler.after`\n3. `websocket.handler.exception`\n\n```python\n@app.signal(\"websocket.handler.before\")\nasync def ws_before(request: Request, websocket: Websocket):\n    ...\n\n@app.signal(\"websocket.handler.after\")\nasync def ws_after(request: Request, websocket: Websocket):\n    ...\n    \n@app.signal(\"websocket.handler.exception\")\nasync def ws_exception(\n    request: Request, websocket: Websocket, exception: Exception\n):\n    ...\n```\n\n![](https://camo.githubusercontent.com/ea2894c88bedf37a4f12f129569e8fd14bfceaa36d4452c7b7a1869d2f1cdb18/68747470733a2f2f7a692e66692f77732d7369676e616c732e706e67)\n\n### Simplified signals\n\nSanic has always enforced a three part naming convention for signals: `one.two.three`. However, now you can create simpler names that are only a single part.\n\n```python\n@app.signal(\"foo\")\nasync def foo():\n    ...\n```\n\nYou can make that part dynamic just like with regular signals and routes:\n\n```python\n@app.signal(\"<thing>\")\nasync def handler(**kwargs):\n    print(\"foobar signal received\")\n    print(kwargs)\n\n\n@app.route(\"/\")\nasync def test(request: Request):\n    await request.app.dispatch(\"foobar\")\n    return json({\"hello\": \"world\"})\n```\n\nIf you need to have multiple dynamic signals, then you should use the longer three-part format.\n\n### The `event` method has been updated\n\nA number of changes have been made to both `app.event()` and `blueprint.event()`.\n\n- `condition` and `exclusive` are keywords to control matching conditions (similar to the `signal()` methods)\n- You can pass either a `str` or an `Enum` (just like `signal()`)\n- returns a copy of the context that was passed to the `dispatch()` method\n\n### Reload trigger gets changed files\n\nThe files changed by the reloader are now injected into the listener. This will allow the trigger to do something with knowledge of what those changed files were.\n\n```python\n@app.after_reload_trigger\nasync def after_reload_trigger(_, changed):\n    print(changed)\n```\n\n## Thank you\n\nThank you to everyone that participated in this release: :clap:\n\n[@ahopkins](https://github.com/ahopkins)\n[@ChihweiLHBird](https://github.com/ChihweiLHBird)\n[@freddiewanah](https://github.com/freddiewanah)\n[@gluhar2006](https://github.com/gluhar2006)\n[@iAndriy](https://github.com/iAndriy)\n[@MichaelHinrichs](https://github.com/MichaelHinrichs)\n[@prryplatypus](https://github.com/prryplatypus)\n[@SaidBySolo](https://github.com/SaidBySolo)\n[@sjsadowski](https://github.com/sjsadowski)\n[@talljosh](https://github.com/talljosh)\n[@tjni](https://github.com/tjni)\n[@Tronic](https://github.com/Tronic)\n\n---\n\nIf you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/).\n"
  },
  {
    "path": "guide/content/en/release-notes/2023/v23.3.md",
    "content": "---\ntitle: Version 23.3\n---\n\n# Version 23.3\n\n.. toc::\n\n## Introduction\n\nThis is the first release of the version 23 [release cycle](../../organization/policies.md#release-schedule). As such contains some deprecations and hopefully some *small* breaking changes. If you run into any issues, please raise a concern on [GitHub](https://github.com/sanic-org/sanic/issues/new/choose).\n\n## What to know\n\nMore details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade...\n\n### Nicer traceback formatting\n\nThe SCO adopted two projects into the Sanic namespace on GitHub: [tracerite](https://github.com/sanic-org/tracerite) and [html5tagger](https://github.com/sanic-org/html5tagger). These projects team up to provide and incredible new error page with more details to help the debugging process.\n\nThis is provided out of the box, and will adjust to display only relevant information whether in DEBUG more or PROD mode.\n\n.. column::\n\n    **Using PROD mode**\n    ![image](/assets/images/error-html-no-debug.png)\n\n.. column::\n\n    **Using DEBUG mode**\n    ![image](/assets/images/error-html-debug.png)\n\nLight and dark mode HTML pages are available and will be used implicitly.\n\n### Basic file browser on directories\n\nWhen serving a directory from a static handler, Sanic can be configured to show a basic file browser instead using `directory_view=True`.\n\n.. column::\n\n    ```python\n    app.static(\"/uploads/\", \"/path/to/dir/\", directory_view=True)\n    ```\n\n.. column::\n\n    ![image](/assets/images/directory-view.png)\n\nLight and dark mode HTML pages are available and will be used implicitly.\n\n### HTML templating with Python\n\nBecause Sanic is using [html5tagger](https://github.com/sanic-org/html5tagger) under the hood to render the [new error pages](#nicer-traceback-formatting), you now have the package available to you to easily generate HTML pages in Python code:\n\n.. column::\n\n    ```python\n    from html5tagger import Document\n    from sanic import Request, Sanic, html\n\n    app = Sanic(\"TestApp\")\n\n    @app.get(\"/\")\n    async def handler(request: Request):\n        doc = Document(\"My Website\")\n        doc.h1(\"Hello, world.\")\n        with doc.table(id=\"data\"):\n            doc.tr.th(\"First\").th(\"Second\").th(\"Third\")\n            doc.tr.td(1).td(2).td(3)\n        doc.p(class_=\"text\")(\"A paragraph with \")\n        doc.a(href=\"/files\")(\"a link\")(\" and \").em(\"formatting\")\n        return html(doc)\n    ```\n\n.. column::\n\n    ```html\n    <!DOCTYPE html>\n    <meta charset=\"utf-8\">\n    <title>My Website</title>\n    <h1>Hello, world.</h1>\n    <table id=data>\n        <tr>\n            <th>First\n            <th>Second\n            <th>Third\n        <tr>\n            <td>1\n            <td>2\n            <td>3\n    </table>\n    <p class=text>\n        A paragraph with <a href=\"/files\">a link</a> and <em>formatting</em>\n    ```\n\n### Auto-index serving is available on static handlers\n\nSanic can now be configured to serve an index file when serving a static directory.\n\n```python\napp.static(\"/assets/\", \"/path/to/some/dir\", index=\"index.html\")\n```\n\nWhen using the above, requests to `http://example.com/assets/` will automatically serve the `index.html` file located in that directory.\n\n### Simpler CLI targets\n\nIt is common practice for Sanic applications to use the variable `app` as the application instance. Because of this, the CLI application target (the second value of the `sanic` CLI command) now tries to infer the application instance based upon what the target is. If the target is a module that contains an `app` variable, it will use that.\n\nThere are now four possible ways to launch a Sanic application from the CLI.\n\n#### 1. Application instance\n\nAs normal, providing a path to a module and an application instance will work as expected.\n\n```sh\nsanic path.to.module:app          # global app instance\n```\n\n#### 2. Application factory\n\nPreviously, to serve the factory pattern, you would need to use the `--factory` flag. This can be omitted now.\n\n```sh\nsanic path.to.module:create_app   # factory pattern\n```\n\n#### 3. Path to launch Sanic Simple Server\n\nSimilarly, to launch the Sanic simple server (serve static directory), you previously needed to use the `--simple` flag. This can be omitted now, and instead simply provide the path to the directory.\n\n```sh\nsanic ./path/to/directory/        # simple serve\n```\n\n#### 4. Python module containing an `app` variable\n\nAs stated above, if the target is a module that contains an `app` variable, it will use that (assuming that `app` variable is a `Sanic` instance).\n\n```sh\nsanic path.to.module              # module with app instance\n```\n\n### More convenient methods for setting and deleting cookies\n\nThe old cookie pattern was awkward and clunky. It didn't look like regular Python because of the \"magic\" going on under the hood.\n\n.. column::\n\n    😱 This is not intuitive and is confusing for newcomers.\n\n.. column::\n\n    ```python\n    response = text(\"There's a cookie up in this response\")\n    response.cookies[\"test\"] = \"It worked!\"\n    response.cookies[\"test\"][\"domain\"] = \".yummy-yummy-cookie.com\"\n    response.cookies[\"test\"][\"httponly\"] = True\n    ```\n\nThere are now new methods (and completely overhauled `Cookie` and `CookieJar` objects) to make this process more convenient.\n\n.. column::\n\n    😌 Ahh... Much nicer.\n\n.. column::\n\n    ```python\n    response = text(\"There's a cookie up in this response\")\n    response.add_cookie(\n        \"test\",\n        \"It worked!\",\n        domain=\".yummy-yummy-cookie.com\",\n        httponly=True\n    )\n    ```\n\n### Better cookie compatibility\n\nSanic has added support for [cookie prefixes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#cookie_prefixes), making it seemless and easy to read and write cookies with the values.\n\nWhile setting the cookie...\n\n```py\nresponse.cookies.add_cookie(\"foo\", \"bar\", host_prefix=True)\n```\n\nThis will create the prefixed cookie: `__Host-foo`. However, when accessing the cookie on an incoming request, you can do so without knowing about the existence of the header.\n\n```py\nrequest.cookies.get(\"foo\")\n```\n\nIt should also be noted, cookies can be accessed as properties just like [headers](#access-any-header-as-a-property).\n\n```python\nrequest.cookies.foo\n```\n\nAnd, cookies are similar to the `request.args` and `request.form` objects in that multiple values can be retrieved using `getlist`.\n\n```py\nrequest.cookies.getlist(\"foo\")\n```\n\nAlso added is support for creating [partitioned cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#partitioned_cookie).\n\n```py\nresponse.cookies.add_cookie(..., partitioned=True)\n```\n\n### 🚨 *BREAKING CHANGE* - More consistent and powerful `SanicException`\n\nSanic has for a while included the `SanicException` as a base class exception. This could be extended to add `status_code`, etc. [See more details](http://localhost:8080/en/guide/best-practices/exceptions.html).\n\n**NOW**, using all of the various exceptions has become easier. The commonly used exceptions can be imported directly from the root level module.\n\n```python\nfrom sanic import NotFound, Unauthorized, BadRequest, ServerError\n```\n\nIn addition, all of these arguments are available as keyword arguments on every exception type:\n\n| argument | type | description |\n|--|--|--|\n| `quiet` | `bool` | Suppress the traceback from the logs |\n| `context` | `dict` | Additional information shown in error pages *always* |\n| `extra` | `dict` | Additional information shown in error pages in *DEBUG* mode |\n| `headers` | `dict` | Additional headers sent in the response |\n\nNone of these are themselves new features. However, they are more consistent in how you can use them, thus creating a powerful way to control error responses directly.\n\n```py\nraise ServerError(headers={\"foo\": \"bar\"})\n```\n\nThe part of this that is a breaking change is that some formerly positional arguments are now keyword only.\n\nYou are encouraged to look at the specific implementations for each error in the [API documents](https://sanic.readthedocs.io/en/stable/sanic/api/exceptions.html#module-sanic.exceptions).\n\n### 🚨 *BREAKING CHANGE* - Refresh `Request.accept` functionality to be more performant and spec-compliant\n\nParsing od the `Accept` headers into the `Request.accept` accessor has been improved. If you were using this property and relying upon its equality operation, this has changed. You should probably transition to using the `request.accept.match()` method.\n\n### Access any header as a property\n\nTo simplify access to headers, you can access a raw (unparsed) version of the header as a property. The name of the header is the name of the property in all lowercase letters, and switching any hyphens (`-`) to underscores (`_`).\n\nFor example:\n\n.. column::\n\n    ```\n    GET /foo/bar HTTP/1.1\n    Host: localhost\n    User-Agent: curl/7.88.1\n    X-Request-ID: 123ABC\n    ```\n\n.. column::\n\n    ```py\n    request.headers.host\n    request.headers.user_agent\n    request.headers.x_request_id\n    ```\n\n### Consume `DELETE` body by default\n\nBy default, the body of a `DELETE` request will now be consumed and read onto the `Request` object. This will make `body` available  like on `POST`, `PUT`, and `PATCH` requests without any further action.\n\n### Custom `CertLoader` for direct control of creating `SSLContext`\n\nSometimes you may want to create your own `SSLContext` object. To do this, you can create your own subclass of `CertLoader` that will generate your desired context object.\n\n```python\nfrom sanic.worker.loader import CertLoader\n\nclass MyCertLoader(CertLoader):\n    def load(self, app: Sanic) -> SSLContext:\n        ...\n\napp = Sanic(..., certloader_class=MyCertLoader)\n```\n\n### Deprecations and Removals\n\n1. *DEPRECATED* - Dict-style cookie setting\n1. *DEPRECATED* - Using existence of JSON data on the request for one factor in using JSON error formatter\n1. *REMOVED* -  Remove deprecated `__blueprintname__` property\n1. *REMOVED* -  duplicate route names\n1. *REMOVED* -  duplicate exception handler definitions\n1. *REMOVED* -  inspector CLI with flags\n1. *REMOVED* -  legacy server (including `sanic.server.serve_single` and `sanic.server.serve_multiple`)\n1. *REMOVED* -  serving directory with bytes string\n1. *REMOVED* -  `Request.request_middleware_started`\n1. *REMOVED* -  `Websocket.connection`\n\n#### Duplicated route names are no longer allowed\n\nIn version 22.9, Sanic announced that v23.3 would deprecate allowing routes to be registered with duplicate names. If you see the following error, it is because of that change:\n\n> sanic.exceptions.ServerError: Duplicate route names detected: SomeApp.some_handler. You should rename one or more of them explicitly by using the `name` param, or changing the implicit name derived from the class and function name. For more details, please see https://sanic.dev/en/guide/release-notes/v23.3.html#duplicated-route-names-are-no-longer-allowed\n\nIf you are seeing this, you should opt-in to using explicit names for your routes.\n\n.. column::\n\n    **BAD**\n    ```python\n    app = Sanic(\"SomeApp\")\n\n    @app.get(\"/\")\n    @app.get(\"/foo\")\n    async def handler(request: Request):\n    ```\n\n.. column::\n\n    **GOOD**\n    ```python\n    app = Sanic(\"SomeApp\")\n\n    @app.get(\"/\", name=\"root\")\n    @app.get(\"/foo\", name=\"foo\")\n    async def handler(request: Request):\n    ```\n\n#### Response cookies\n\nResponse cookies act as a `dict` for compatibility purposes only. In version 24.3, all `dict` methods will be removed and response cookies will be objects only.\n\nTherefore, if you are using this pattern to set cookie properties, you will need to upgrade it before version 24.3.\n\n```python\nresp = HTTPResponse()\nresp.cookies[\"foo\"] = \"bar\"\nresp.cookies[\"foo\"][\"httponly\"] = True\n```\n\nInstead, you should be using the `add_cookie` method:\n\n```python\nresp = HTTPResponse()\nresp.add_cookie(\"foo\", \"bar\", httponly=True)\n```\n\n#### Request cookies\n\nSanic has added support for reading duplicated cookie keys to be more in compliance with RFC specifications. To retain backwards compatibility, accessing a cookie value using `__getitem__` will continue to work to fetch the first value sent. Therefore, in version 23.3 and prior versions this will be `True`.\n\n```python\nassert request.cookies[\"foo\"] == \"bar\"\nassert request.cookies.get(\"foo\") == \"bar\"\n```\n\nVersion 23.3 added `getlist`\n\n```python\nassert request.cookies.getlist(\"foo\") == [\"bar\"]\n```\n\nAs stated above, the `get` and `getlist` methods are available similar to how they exist on other request properties (`request.args`, `request.form`, etc). Starting in v24.3, the `__getitem__` method for cookies will work exactly like those properties. This means that `__getitem__` will return a list of values.\n\nTherefore, if you are relying upon this functionality to return only one value, you should upgrade to the following pattern before v24.3.\n\n```python\nassert request.cookies[\"foo\"] == [\"bar\"]\nassert request.cookies.get(\"foo\") == \"bar\"\nassert request.cookies.getlist(\"foo\") == [\"bar\"]\n```\n\n## Thank you\n\nThank you to everyone that participated in this release: :clap:\n\n[@ahopkins](https://github.com/ahopkins)\n[@ChihweiLHBird](https://github.com/ChihweiLHBird)\n[@deounix](https://github.com/deounix)\n[@Kludex](https://github.com/Kludex)\n[@mbendiksen](https://github.com/mbendiksen)\n[@prryplatypus](https://github.com/prryplatypus)\n[@r0x0d](https://github.com/r0x0d)\n[@SaidBySolo](https://github.com/SaidBySolo)\n[@sjsadowski](https://github.com/sjsadowski)\n[@stricaud](https://github.com/stricaud)\n[@Tracyca209](https://github.com/Tracyca209)\n[@Tronic](https://github.com/Tronic)\n\n---\n\nIf you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/).\n"
  },
  {
    "path": "guide/content/en/release-notes/2023/v23.6.md",
    "content": "---\ntitle: Version 23.6\n---\n\n# Version 23.6\n\n.. toc::\n\n\n## Introduction\n\nThis is the second release of the version 23 [release cycle](../../organization/policies.md#release-schedule). If you run into any issues, please raise a concern on [GitHub](https://github.com/sanic-org/sanic/issues/new/choose).\n\n## What to know\n\nMore details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade...\n\n### Remove Python 3.7 support\n\nPython 3.7 is due to reach its scheduled upstream end-of-life on 2023-06-27. Sanic is now dropping support for Python 3.7, and requires Python 3.8 or newer.\n\nSee [#2777](https://github.com/sanic-org/sanic/pull/2777).\n\n### Resolve pypy compatibility issues\n\nA small patch was added to the `os` module to once again allow for Sanic to run with PyPy. This workaround replaces the missing `readlink` function (missing in PyPy `os` module) with the function `os.path.realpath`, which serves to the same purpose.\n\nSee [#2782](https://github.com/sanic-org/sanic/pull/2782).\n\n### Add custom typing to config and ctx objects\n\nThe `sanic.Sanic` and `sanic.Request` object have become generic types that will make it more convenient to have fully typed `config` and `ctx` objects.\n\nIn the most simple form, the `Sanic` object is typed as:\n\n```python\nfrom sanic import Sanic\napp = Sanic(\"test\")\nreveal_type(app)  # N: Revealed type is \"sanic.app.Sanic[sanic.config.Config, types.SimpleNamespace]\"\n```\n\n\n.. tip:: Note\n\n    It should be noted, there is *no* requirement to use the generic types. The default types are `sanic.config.Config` and `types.SimpleNamespace`. This new feature is just an option for those that want to use it and existing types of `app: Sanic` and `request: Request` should work just fine.\n\n\nNow it is possible to have a fully-type `app.config`, `app.ctx`, and `request.ctx` objects though generics. This allows for better integration with auto completion tools in IDEs improving the developer experience.\n\n```python\nfrom sanic import Request, Sanic\nfrom sanic.config import Config\n\nclass CustomConfig(Config):\n    pass\n\nclass Foo:\n    pass\n\nclass RequestContext:\n    foo: Foo\n\nclass CustomRequest(Request[Sanic[CustomConfig, Foo], RequestContext]):\n    @staticmethod\n    def make_context() -> RequestContext:\n        ctx = RequestContext()\n        ctx.foo = Foo()\n        return ctx\n\napp = Sanic(\n    \"test\", config=CustomConfig(), ctx=Foo(), request_class=CustomRequest\n)\n\n@app.get(\"/\")\nasync def handler(request: CustomRequest):\n   ...\n```\n\nAs a side effect, now `request.ctx` is lazy initialized, which should reduce some overhead when the `request.ctx` is unused.\n\nOne further change you may have noticed in the above snippet is the `make_context` method. This new method can be used by custom `Request` types to inject an object different from a `SimpleNamespace` similar to how Sanic has allowed custom application context objects for a while.\n\nFor a more thorough discussion, see [custom typed application](../../guide/basics/app.md#custom-typed-application) and [custom typed request](../../guide/basics/app.md#custom-typed-request).\n\nSee [#2785](https://github.com/sanic-org/sanic/pull/2785).\n\n### Universal exception signal\n\nA new exception signal added for **ALL** exceptions raised while the server is running: `\"server.exception.reporting\"`. This is a universal signal that will be emitted for any exception raised, and dispatched as its own task. This means that it will *not* block the request handler, and will *not* be affected by any middleware.\n\nThis is useful for catching exceptions that may occur outside of the request handler (for example in signals, or in a background task), and it intended for use to create a consistent error handling experience for the user.\n\n```python\nfrom sanic.signals import Event\n\n@app.signal(Event.SERVER_LIFECYCLE_EXCEPTION)\nasync def catch_any_exception(app: Sanic, exception: Exception):\n    app.ctx.my_error_reporter_utility.error(exception)\n```\n\nThis pattern can be simplified with a new decorator `@app.report_exception`:\n\n```python\n@app.report_exception\nasync def catch_any_exception(app: Sanic, exception: Exception):\n    print(\"Caught exception:\", exception)\n```\n\nIt should be pointed out that this happens in a background task and is **NOT** for manipulation of an error response. It is only for reporting, logging, or other purposes that should be triggered when an application error occurs.\n\nSee [#2724](https://github.com/sanic-org/sanic/pull/2724) and [#2792](https://github.com/sanic-org/sanic/pull/2792).\n\n### Add name prefixing to BP groups\n\nSanic had been raising a warning on duplicate route names for a while, and started to enforce route name uniqueness in [v23.3](https://sanic.dev/en/guide/release-notes/v23.3.html#deprecations-and-removals). This created a complication for blueprint composition.\n\nNew name prefixing parameter for blueprints groups has been added to alleviate this issue. It allows nesting of blueprints and groups to make them composable.\n\nThe addition is the new `name_prefix` parameter as shown in this snippet.\n\n```python\nbp1 = Blueprint(\"bp1\", url_prefix=\"/bp1\")\nbp2 = Blueprint(\"bp2\", url_prefix=\"/bp2\")\n\nbp1.add_route(lambda _: ..., \"/\", name=\"route1\")\nbp2.add_route(lambda _: ..., \"/\", name=\"route2\")\n\ngroup_a = Blueprint.group(\n    bp1, bp2, url_prefix=\"/group-a\", name_prefix=\"group-a\"\n)\ngroup_b = Blueprint.group(\n    bp1, bp2, url_prefix=\"/group-b\", name_prefix=\"group-b\"\n)\n\napp = Sanic(\"TestApp\")\napp.blueprint(group_a)\napp.blueprint(group_b)\n```\n\nThe routes built will be named as follows:\n- `TestApp.group-a_bp1.route1`\n- `TestApp.group-a_bp2.route2`\n- `TestApp.group-b_bp1.route1`\n- `TestApp.group-b_bp2.route2`\n\nSee [#2727](https://github.com/sanic-org/sanic/pull/2727).\n\n### Add `request.client_ip`\n\nSanic has introduced `request.client_ip`, a new accessor that provides client's IP address from both local and proxy data. It allows running the application directly on Internet or behind a proxy. This is equivalent to `request.remote_addr or request.ip`, providing the client IP regardless of how the application is deployed.\n\nSee [#2790](https://github.com/sanic-org/sanic/pull/2790).\n\n### Increase of `KEEP_ALIVE_TIMEOUT` default to 120 seconds\n\nThe default `KEEP_ALIVE_TIMEOUT` value changed from 5 seconds to 120 seconds. It is of course still configurable, but this change should improve performance on long latency connections, where reconnecting is expensive, and better fits typical user flow browsing pages with longer-than-5-second intervals.\n\nSanic has historically used 5 second timeouts to quickly close idle connections. The chosen value of **120 seconds** is indeed larger than Nginx default of 75, and is the same value that Caddy server has by default.\n\nRelated to [#2531](https://github.com/sanic-org/sanic/issues/2531) and \n[#2681](https://github.com/sanic-org/sanic/issues/2681). \n\nSee [#2670](https://github.com/sanic-org/sanic/pull/2670).\n\n### Set multiprocessing start method early\n\nDue to how Python handles `multiprocessing`, it may be confusing to some users how to properly create synchronization primitives. This is due to how Sanic creates the `multiprocessing` context. This change sets the start method early so that any primitives created will properly attach to the correct context.\n\nFor most users, this should not be noticeable or impactful. But, it should make creation of something like this easier and work as expected.\n\n```python\nfrom multiprocessing import Queue\n\n@app.main_process_start\nasync def main_process_start(app):\n    app.shared_ctx.queue = Queue()\n```\n\nSee [#2776](https://github.com/sanic-org/sanic/pull/2776).\n\n## Thank you\n\nThank you to everyone that participated in this release: :clap:\n\n[@ahopkins](https://github.com/ahopkins)\n[@ChihweiLHBird](https://github.com/ChihweiLHBird)\n[@chuckds](https://github.com/chuckds)\n[@deounix](https://github.com/deounix)\n[@guacs](https://github.com/guacs)\n[@liamcoatman](https://github.com/liamcoatman)\n[@moshe742](https://github.com/moshe742)\n[@prryplatypus](https://github.com/prryplatypus)\n[@SaidBySolo](https://github.com/SaidBySolo)\n[@Thirumalai](https://github.com/Thirumalai)\n[@Tronic](https://github.com/Tronic)\n\n---\n\nIf you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/).\n"
  },
  {
    "path": "guide/content/en/release-notes/2023/v23.9.md",
    "content": "---\ntitle: Version 23.9\n---\n\n# Version 23.9\n\n_Due to circumstances at the time, v.23.9 was skipped._\n"
  },
  {
    "path": "guide/content/en/release-notes/2024/v24.12.md",
    "content": "---\ntitle: Version 24.12\n---\n\n# Version 24.12\n\n.. toc::\n\n\n## Introduction\n\nThis is the first release of the version 24 [release cycle](../../organization/policies.md#release-schedule).  The release cadence for v24 may be slightly altered from years past. Make sure to stay up to date in the Discord server for latest updates. If you run into any issues, please raise a concern on [GitHub](https://github.com/sanic-org/sanic/issues/new/choose).\n\n## What to know\n\nMore details in the [Changelog](../changelog.html). Notable new or breaking features, and what to upgrade:\n\n### 👶 _BETA_ Custom CLI commands\n\nThe `sanic` CLI utility now allows for custom commands to be invoked. Commands can be added using the decorator syntax below.\n\n```python\n@app.command\nasync def foo(one, two: str, three: str = \"...\"):\n    logger.info(f\"FOO {one=} {two=} {three=}\")\n\n\n@app.command\ndef bar():\n    logger.info(\"BAR\")\n\n\n@app.command(name=\"qqq\")\nasync def baz():\n    logger.info(\"BAZ\")\n```\n\nThese are invoked using the `exec` command as follows.\n\n```sh\nsanic server:app exec <command> [--arg=value]\n```\n\nAny arguments in the function's signature will be added as arguments. For example:\n\n```sh\nsanic server:app exec command --one=1 --two=2 --three=3\n```\n\n.. warning::\n\n    This is in **BETA** and the functionality is subject to change in upcoming versions.\n\n### Add Python 3.13 support\n\nWe have added Python 3.13 to the supported versions.\n\n### Remove Python 3.8 support\n\nPython 3.8 reached end-of-life. Sanic is now dropping support for Python 3.8, and requires Python 3.9 or newer.\n\n### Old response cookie accessors removed\n\nPrior to v23, cookies on `Response` objects were set and accessed as dictionary objects. That was deprecated in v23.3 when the new [convenience methods](../2023/v23.3.html#more-convenient-methods-for-setting-and-deleting-cookies) were added. The old patterns have been removed.\n\n## Thank you\n\nThank you to everyone that participated in this release: :clap:\n\n[@ahopkins](https://github.com/ahopkins)\n[@C5H12O5](https://github.com/C5H12O5)\n[@ChihweiLHBird](https://github.com/ChihweiLHBird)\n[@HyperKiko](https://github.com/HyperKiko)\n[@imnotjames](https://github.com/imnotjames)\n[@pygeek](https://github.com/pygeek)\n\n---\n\nIf you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/).\n"
  },
  {
    "path": "guide/content/en/release-notes/2024/v24.6.md",
    "content": "---\ntitle: Version 24.6\n---\n\n# Version 24.6\n\n.. toc::\n\n\n## Introduction\n\nThis is the first release of the version 24 [release cycle](../../organization/policies.md#release-schedule).  The release cadence for v24 may be slightly altered from years past. Make sure to stay up to date in the Discord server for latest updates. If you run into any issues, please raise a concern on [GitHub](https://github.com/sanic-org/sanic/issues/new/choose).\n\n## What to know\n\nMore details in the [Changelog](../changelog.html). Notable new or breaking features, and what to upgrade:\n\n### Logging improvements\n\nThe default logging patterns have been cleaned up to make them much more developer-friendly when viewing from a terminal session. This includes the use of color and less verbose formatting.\n\nSanic will select between two slight variations depending upon whether your server is in DEBUG mode. You can always opt to remove colors by using:\n\n```python\napp.config.NO_COLOR = True\n```\n\nThe color will automatically be stripped out from logs not in TTY terminal.\n\nSanic will switch between the DEBUG and PROD formatters automatically using `sanic.logging.formatter.AutoFormatter` and `sanic.logging.formatter.AutoAccessFormatter`. Of course, you can force one version or the other using the appropriately named formatters\n\n#### In DEBUG mode\n\n```python\nsanic.logging.formatter.DebugFormatter\nsanic.logging.formatter.DebugAccessFormatter\n```\n\n![](/assets/images/logging-dev.png)\n\n#### In PROD mode\n\n\n```python\nsanic.logging.formatter.ProdFormatter\nsanic.logging.formatter.ProdAccessFormatter\n```\n\n![](/assets/images/logging-prod.png)\n\n#### Legacy\n\nIf you prefer the old-style of logging, these have been preserved for you as logging formatters: `sanic.logging.formatter.LegacyFormatter` and `sanic.logging.formatter.LegacyAccessFormatter`.\n\nOne way to implement these formatters:\n\n```python\nfrom sanic.log import LOGGING_CONFIG_DEFAULTS\n\nLOGGING_CONFIG_DEFAULTS[\"formatters\"] = {\n    \"generic\": {\n        \"class\": \"sanic.logging.formatter.LegacyFormatter\"\n    },\n    \"access\": {\n        \"class\": \"sanic.logging.formatter.LegacyAccessFormatter\"\n    },\n}\n```\n\n#### New JSON formatter\n\nThere also is a new JSON log formatter that will output the logs in JSON format for integration with other third part logging platforms.\n\n\n```python\nfrom sanic.log import LOGGING_CONFIG_DEFAULTS\n\nLOGGING_CONFIG_DEFAULTS[\"formatters\"] = {\n    \"generic\": {\n        \"class\": \"sanic.logging.formatter.JSONFormatter\"\n    },\n    \"access\": {\n        \"class\": \"sanic.logging.formatter.JSONAccessFormatter\"\n    },\n}\n```\n\n### Using Paths in unix sockets\n\nWhen creating a unix socket for your server, you can now perform that by passing a `pathlib.Path` object instead of just a string-based path\n\n### Custom route names\n\nYou can override the `generate_name` method on either a custom `Sanic` or a `Blueprint`. This will allow you to modify the route names at will.\n\n```python\nfrom sanic import Sanic, text,\n\nclass Custom(Sanic):\n    def generate_name(self, *objects):\n        existing = self._generate_name(*objects)\n        return existing.upper()\n        \napp = Sanic(\"Foo\")\n\n@app.get(\"/\")\nasync def handler(request):\n    return text(request.name)  # FOO.HANDLER\n\n    \nreturn app\n```\n\n### 🚨 BREAKING CHANGES\n\n1. `Request.cookies.getlist` always returns a `list`. This means when no cookie of `key` exists, it will be an empty `list` instead of `None`. Use `Request.cookies.getlist(\"something\", None)` to retain existing behavior.\n\n\n## Thank you\n\nThank you to everyone that participated in this release: :clap:\n\n[@ahopkins](https://github.com/ahopkins)\n[@ashleysommer](https://github.com/ashleysommer)\n[@ChihweiLHBird](https://github.com/ChihweiLHBird)\n[@DeJayDev](https://github.com/DeJayDev)\n[@ekzhang](https://github.com/ekzhang)\n[@Huy-Ngo](https://github.com/Huy-Ngo)\n[@iAndriy](https://github.com/iAndriy)\n[@jakkaz](https://github.com/jakkaz)\n[@Nano112](https://github.com/Nano112)\n[@prryplatypus](https://github.com/prryplatypus)\n[@razodactyl](https://github.com/razodactyl)\n[@Tronic](https://github.com/Tronic)\n[@wieczorek1990](https://github.com/wieczorek1990)\n\n---\n\nIf you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/).\n"
  },
  {
    "path": "guide/content/en/release-notes/2025/v25.12.md",
    "content": "---\ntitle: Version 25.12 (LTS)\n---\n\n# Version 25.12 (LTS)\n\n.. toc::\n\n\n## Introduction\n\nVersion 25.12 is the **Long Term Support (LTS)** release for the version 25 [release cycle](../../organization/policies.md#release-schedule). As an LTS release, it will receive security updates and critical bug fixes for an extended period. If you run into any issues, please raise a concern on [GitHub](https://github.com/sanic-org/sanic/issues/new/choose).\n\n## What to know\n\nMore details in the [Changelog](../changelog.html). Notable new or breaking features, and what to upgrade:\n\n### Python 3.9 removed, Python 3.14 added\n\nSanic now requires **Python 3.10 or newer**. Python 3.9 has been dropped, and Python 3.14 support has been added.\n\n### Daemon mode\n\nYou can now run Sanic as a background daemon process directly from the CLI.\n\n```sh\nsanic path.to.app -D\nsanic path.to.app --daemon\n```\n\nThis also introduces convenience commands for managing daemon processes:\n\n```sh\nsanic path.to.app status    # Check if running\nsanic path.to.app stop      # Stop the daemon\n```\n\nAdditional options are available:\n\n```sh\n--pidfile PATH    # Custom PID file location\n--logfile PATH    # Log output to file\n--user USER       # Run as specified user\n--group GROUP     # Run as specified group\n```\n\nLower-level commands are also available:\n\n```sh\nsanic kill --pid=<PID>\nsanic kill --pidfile=<PATH>\nsanic status --pid=<PID>\nsanic status --pidfile=<PATH>\n```\n\n### Symlink control for static files\n\nSanic now provides granular control over symlinks in static file serving with two new parameters:\n\n| Parameter                     | Default | Description                                                                    |\n|-------------------------------|---------|--------------------------------------------------------------------------------|\n| `follow_external_symlink_files` | `False`   | Allow serving file symlinks that point outside the static root                 |\n| `follow_external_symlink_dirs`  | `False`   | Allow serving files from directory symlinks that point outside the static root |\n\n**Examples**\n\nSecure defaults (block external symlinks):\n```python\n# Symlinks pointing outside /var/www/static will return 404\napp.static(\"/static\", \"/var/www/static\")\n```\n\nAllow file symlinks only:\n```python\n# Serves /var/www/static/config.json -> /etc/app/config.json\napp.static(\"/static\", \"/var/www/static\", follow_external_symlink_files=True)\n```\n\nAllow directory symlinks only:\n```python\n# Serves files from /var/www/static/images/ -> /shared/images/\napp.static(\"/static\", \"/var/www/static\", follow_external_symlink_dirs=True)\n```\n\n### Custom configuration converters\n\nYou can now extend how Sanic parses environment variables into configuration values. By default, Sanic converts environment variables using `str`, `str_to_bool`, `float`, and `int` converters (tried in reverse order). You can add your own converters to handle custom types.\n\n**Simple converter** - a callable that takes a string and returns a converted value:\n\n```python\nclass UltimateAnswer:\n    def __init__(self, answer):\n        self.answer = int(answer)\n\nconfig = Config(converters=[UltimateAnswer])\napp = Sanic(\"MyApp\", config=config)\n\n# Or register after creation\napp.config.register_type(UltimateAnswer)\n```\n\n**DetailedConverter** - for converters that need more context (full key name, config key, value, and defaults):\n\n```python\nfrom sanic.config import Config, DetailedConverter\n\nclass TypeAwareConverter(DetailedConverter):\n    def __call__(self, full_key: str, config_key: str, value: str, defaults: dict):\n        if config_key in defaults:\n            # Cast to the same type as the default value\n            return type(defaults[config_key])(value)\n        raise ValueError  # Fall through to next converter\n\nconfig = Config(\n    defaults={\"PORT\": 8000, \"DEBUG\": False},\n    converters=[TypeAwareConverter()]\n)\n```\n\n### Automatic charset for text content types\n\nText content types (`text/*`) now automatically include `charset=UTF-8` when serving static files and file responses.\n\n### Task creation returns the task\n\nWhen creating a task via `app.add_task()`, the asyncio `Task` object is now returned, allowing you to await or check its result later.\n\n```python\ntask = app.add_task(some_coroutine())\n# Later...\nresult = await task\n```\n\n### Improved CLI error messages\n\nTracerite has been upgraded to v2.2.0, providing better formatted exception tracebacks in the CLI with improved chained exception display.\n\n## Thank you\n\nThank you to everyone that participated in this release: :clap:\n\n[@ahopkins](https://github.com/ahopkins)\n[@amarquard089](https://github.com/amarquard089)\n[@ChihweiLHBird](https://github.com/ChihweiLHBird)\n[@dhensen](https://github.com/dhensen)\n[@dungarpan](https://github.com/dungarpan)\n[@gazpachoking](https://github.com/gazpachoking)\n[@helioascorreia](https://github.com/helioascorreia)\n[@jameslovespancakes](https://github.com/jameslovespancakes)\n[@Peopl3s](https://github.com/Peopl3s)\n[@tdaron](https://github.com/tdaron)\n[@tiejunhu](https://github.com/tiejunhu)\n[@tkosman](https://github.com/tkosman)\n[@Tronic](https://github.com/Tronic)\n[@wojonet](https://github.com/wojonet)\n\n\n---\n\nIf you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/).\n"
  },
  {
    "path": "guide/content/en/release-notes/2025/v25.3.md",
    "content": "---\ntitle: Version 25.3\n---\n\n# Version 25.3\n\n.. toc::\n\n\n## Introduction\n\nThis is the first release of the version 25 [release cycle](../../organization/policies.md#release-schedule).  The release cadence for v24 may be slightly altered from years past. Make sure to stay up to date in the Discord server for latest updates. If you run into any issues, please raise a concern on [GitHub](https://github.com/sanic-org/sanic/issues/new/choose).\n\n## What to know\n\nMore details in the [Changelog](../changelog.html). Notable new or breaking features, and what to upgrade:\n\n### REPL Context\n\nIn [v23.12](../2023/v23.12.md#-embetaem-welcome-to-the-sanic-interactive-console) we introduced the [Development REPL](/en/guide/running/development.html#development-repl). The goal was to allow you to enter into an enhanced Python REPL from inside your running Sanic application. One of its features is several built-ins to allow you to easily interact with your app and with Sanic.\n\nYou now can add your own custom objects to the REPL\n\n```python\nimport os\n\ndef foo():\n    \"\"\"This docstring will show in the REPL\"\"\"\n    \napp.repl_ctx.foo = foo\napp.repl_ctx.os = os\n```\n\nThere is also a lower-level API that you can use if (for example) you want to control the help test displayed in the REPL for your objects:\n\n```python\napp.repl_ctx.add(foo)\napp.repl_ctx.add(os, desc=\"Standard os module.\")\n```\n\n\n## Thank you\n\nThank you to everyone that participated in this release: :clap:\n\n[@ahopkins](https://github.com/ahopkins)\n[@erhuabushuo](https://github.com/erhuabushuo)\n[@eric-spitler](https://github.com/eric-spitler)\n[@goodki-d](https://github.com/goodki-d)\n[@SaidBySolo](https://github.com/SaidBySolo)\n\n\n---\n\nIf you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/).\n"
  },
  {
    "path": "guide/content/en/release-notes/changelog.md",
    "content": "---\ncontent_class: changelog\n---\n\n# Changelog\n\n🔶 Current release  \n🔷 In support LTS release\n\n## Version 25.12.0 🔶\n\n_Current LTS version_\n\n### Features\n- [#3071](https://github.com/sanic-org/sanic/pull/3071) Add automatic `charset=UTF-8` to text content types\n- [#3079](https://github.com/sanic-org/sanic/pull/3079) Add `DetailedConverter` for advanced environment variable conversion\n- [#3080](https://github.com/sanic-org/sanic/pull/3080) Update `str_to_bool` function to include `nope` as a valid false value\n- [#3102](https://github.com/sanic-org/sanic/pull/3102) Better server error messaging\n- [#3110](https://github.com/sanic-org/sanic/pull/3110) Add daemon mode to Sanic CLI\n- [#3114](https://github.com/sanic-org/sanic/pull/3114) Return task when creating a task\n- [#3117](https://github.com/sanic-org/sanic/pull/3117) Explicit symlink params for static files/dirs\n\n### Bugfixes\n- [#3064](https://github.com/sanic-org/sanic/pull/3064) Change the log type to debug\n- [#3068](https://github.com/sanic-org/sanic/pull/3068) Silent on `RuntimeError` when `write_eof`\n- [#3077](https://github.com/sanic-org/sanic/pull/3077) Fix `WorkerManager.kill` on Windows\n- [#3085](https://github.com/sanic-org/sanic/pull/3085) Fix `AttributeError` in `close_if_idle()` when `_http` is not initialized\n- [#3086](https://github.com/sanic-org/sanic/pull/3086) Fix race condition in worker restart causing spawn failure\n- [#3088](https://github.com/sanic-org/sanic/pull/3088) Fixing bad term cleanup at exit\n- [#3119](https://github.com/sanic-org/sanic/pull/3119) Fix static file serving for directories with CJK characters\n- [#3120](https://github.com/sanic-org/sanic/pull/3120) Respect `LOG_EXTRA` in all cases\n- [#3121](https://github.com/sanic-org/sanic/pull/3121) Respect `KEEP_ALIVE` config\n- [#3122](https://github.com/sanic-org/sanic/pull/3122) Check state in shutdown for handling uvloop double kill\n\n### Deprecations and Removals\n- [#3115](https://github.com/sanic-org/sanic/pull/3115) Remove Python 3.9 support and add Python 3.14\n\n### Developer infrastructure\n- [#3083](https://github.com/sanic-org/sanic/pull/3083) Add typing for parameters of constructor of `WorkerManager`\n- [#3084](https://github.com/sanic-org/sanic/pull/3084) Create baseline for bandit to remove false positives\n- [#3091](https://github.com/sanic-org/sanic/pull/3091) Use secrets for generating unique ping payloads\n- [#3094](https://github.com/sanic-org/sanic/pull/3094) Add some typing and fix some tests\n- [#3095](https://github.com/sanic-org/sanic/pull/3095) Update required Python to >=3.10\n- [#3101](https://github.com/sanic-org/sanic/pull/3101) Upgrade tracerite to latest\n- [#3107](https://github.com/sanic-org/sanic/pull/3107) Testing improvements\n- [#3108](https://github.com/sanic-org/sanic/pull/3108) Move to 2-stage coverage check\n- [#3109](https://github.com/sanic-org/sanic/pull/3109) Bump `dawidd6/action-download-artifact` from 3 to 6\n\n### Improved Documentation\n- [#3035](https://github.com/sanic-org/sanic/pull/3035) Fixes `sanic_ext` openapi component documentation\n- [#3054](https://github.com/sanic-org/sanic/pull/3054) Fix 'how we built sanic' sidebar link\n- [#3056](https://github.com/sanic-org/sanic/pull/3056) Fix broken link in website\n- [#3057](https://github.com/sanic-org/sanic/pull/3057) Add correct path for Contribution guidelines\n- [#3065](https://github.com/sanic-org/sanic/pull/3065) Fixed incorrect links throughout the documentation\n- [#3066](https://github.com/sanic-org/sanic/pull/3066) Single letter typo fix for request.md documentation\n\n\n## Version 25.3.0\n\n### Features\n- [#3030](https://github.com/sanic-org/sanic/pull/3030) Improve `websockets` import ordering\n- [#3042](https://github.com/sanic-org/sanic/pull/3042) Add REPL context\n- [#3046](https://github.com/sanic-org/sanic/pull/3046) Support latest v14 `websockets`\n- [#3049](https://github.com/sanic-org/sanic/pull/3049) Subclassing `HTTPMethodView` to allow generics\n\n### Bugfixes\n- [#3047](https://github.com/sanic-org/sanic/pull/3047) Add default to `response.cookies`\n- [#3048](https://github.com/sanic-org/sanic/pull/3048) Add exception logging on connection auto close\n\n\n### Developer infrastructure\n- [#3023](https://github.com/sanic-org/sanic/pull/3023) Cleanup from Python 3.8 removal\n- [#3024](https://github.com/sanic-org/sanic/pull/3024) Improve type hinting\n- [#3028](https://github.com/sanic-org/sanic/pull/3028) Add missing tests\n- [#3041](https://github.com/sanic-org/sanic/pull/3041) Improve GitHub Actions checks\n\n\n## Version 24.12.0 🔷\n\n### Features\n- [#3019](https://github.com/sanic-org/sanic/pull/3019) Add custom commands to `sanic` CLI\n\n### Bugfixes\n- [#2992](https://github.com/sanic-org/sanic/pull/2992) Fix `mixins.startup.serve` UnboundLocalError\n- [#3000](https://github.com/sanic-org/sanic/pull/3000) Fix type annocation for `JSONResponse` method for return type `bytes` allowed for `dumps` callable\n- [#3009](https://github.com/sanic-org/sanic/pull/3009) Fix `SanicException.quiet` attribute handling when set to `False`\n- [#3014](https://github.com/sanic-org/sanic/pull/3014) Cleanup some typing\n- [#3015](https://github.com/sanic-org/sanic/pull/3015) Kill the entire process group if applicable\n- [#3016](https://github.com/sanic-org/sanic/pull/3016) Fix incompatible type annotation of get method in the HTTPMethodView class\n\n### Deprecations and Removals\n- [#3020](https://github.com/sanic-org/sanic/pull/3020) Remove Python 3.8 support\n\n### Developer infrastructure\n- [#3017](https://github.com/sanic-org/sanic/pull/3017) Cleanup setup.cfg\n\n### Improved Documentation\n- [#3007](https://github.com/sanic-org/sanic/pull/3007) Fix typo in documentation for `sanic-ext`\n\n## Version 24.6.0\n\n### Features\n- [#2838](https://github.com/sanic-org/sanic/pull/2838) Simplify request cookies `getlist`\n- [#2850](https://github.com/sanic-org/sanic/pull/2850) Unix sockets can now use `pathlib.Path`\n- [#2931](https://github.com/sanic-org/sanic/pull/2931) [#2958](https://github.com/sanic-org/sanic/pull/2958) Logging improvements\n- [#2947](https://github.com/sanic-org/sanic/pull/2947) Make the .message field on exceptions non-empty\n- [#2961](https://github.com/sanic-org/sanic/pull/2961) [#2964](https://github.com/sanic-org/sanic/pull/2964) Allow for custom name generation\n\n### Bugfixes\n- [#2919](https://github.com/sanic-org/sanic/pull/2919) Remove deprecation notice in websockets\n- [#2937](https://github.com/sanic-org/sanic/pull/2937) Resolve response streaming error when in ASGI mode\n- [#2959](https://github.com/sanic-org/sanic/pull/2959) Resolve Python 3.12 deprecation notic\n- [#2960](https://github.com/sanic-org/sanic/pull/2960) Ensure proper intent for noisy exceptions\n- [#2970](https://github.com/sanic-org/sanic/pull/2970) [#2978](https://github.com/sanic-org/sanic/pull/2978) Fix missing dependencies for 3.12\n- [#2971](https://github.com/sanic-org/sanic/pull/2971) Fix middleware exceptions on Not Found routes with error in middleware\n- [#2973](https://github.com/sanic-org/sanic/pull/2973) Resolve cheduling logic for `transport.close` and `transport.abort`\n- [#2976](https://github.com/sanic-org/sanic/pull/2976) Fix deleting a cookie that was created with `secure=False`\n- [#2979](https://github.com/sanic-org/sanic/pull/2979) Throw error on bad body length\n- [#2980](https://github.com/sanic-org/sanic/pull/2980) Throw error on bad body encoding\n\n### Deprecations and Removals\n- [#2899](https://github.com/sanic-org/sanic/pull/2899) Remove erroneous line from REPL impacting environments without HTTPX\n- [#2962](https://github.com/sanic-org/sanic/pull/2962) Merge entity header removal\n\n### Developer infrastructure\n- [#2882](https://github.com/sanic-org/sanic/pull/2882) [#2896](https://github.com/sanic-org/sanic/pull/2896) Apply dynamic port fixture for improving tests with port selection\n- [#2887](https://github.com/sanic-org/sanic/pull/2887) Updates to docker image builds\n- [#2932](https://github.com/sanic-org/sanic/pull/2932) Cleanup code base with Ruff\n\n### Improved Documentation\n- [#2924](https://github.com/sanic-org/sanic/pull/2924) Cleanup markdown on html5tagger page\n- [#2930](https://github.com/sanic-org/sanic/pull/2930) Cleanup typo on Sanic Extensions README.md\n- [#2934](https://github.com/sanic-org/sanic/pull/2934) Add more context to the health check documents\n- [#2936](https://github.com/sanic-org/sanic/pull/2936) Improve worker manager documentation\n- [#2955](https://github.com/sanic-org/sanic/pull/2955) Fixed wrong formatting in `request.md`\n\n## Version 23.12.0 🔷\n\n### Features\n- [#2775](https://github.com/sanic-org/sanic/pull/2775) Start and restart arbitrary processes\n- [#2811](https://github.com/sanic-org/sanic/pull/2811) Cleaner process management in shutdown\n- [#2812](https://github.com/sanic-org/sanic/pull/2812) Suppress task cancel traceback on open websocket\n- [#2822](https://github.com/sanic-org/sanic/pull/2822) Listener and signal prioritization\n- [#2831](https://github.com/sanic-org/sanic/pull/2831) Reduce memory consumption\n- [#2837](https://github.com/sanic-org/sanic/pull/2837) Accept bare cookies\n- [#2841](https://github.com/sanic-org/sanic/pull/2841) Add `websocket.handler.<before/after/exception>` signals\n- [#2805](https://github.com/sanic-org/sanic/pull/2805) Add changed files to reload trigger listeners\n- [#2813](https://github.com/sanic-org/sanic/pull/2813) Allow for simple signals\n- [#2827](https://github.com/sanic-org/sanic/pull/2827) Improve functionality and consistency of `Sanic.event()`\n- [#2851](https://github.com/sanic-org/sanic/pull/2851) Allow range requests for a single byte\n- [#2854](https://github.com/sanic-org/sanic/pull/2854) Better `Request.scheme` for websocket requests\n- [#2858](https://github.com/sanic-org/sanic/pull/2858) Convert Sanic `Request` to a Websockets `Request` for handshake\n- [#2859](https://github.com/sanic-org/sanic/pull/2859) Add a REPL to the `sanic` CLI\n- [#2870](https://github.com/sanic-org/sanic/pull/2870) Add Python 3.12 support\n- [#2875](https://github.com/sanic-org/sanic/pull/2875) Better exception on multiprocessing context conflicts\n\n### Bugfixes\n- [#2803](https://github.com/sanic-org/sanic/pull/2803) Fix MOTD display for extra data\n\n### Developer infrastructure\n- [#2796](https://github.com/sanic-org/sanic/pull/2796) Refactor unit test cases\n- [#2801](https://github.com/sanic-org/sanic/pull/2801) Fix `test_fast` when there is only one CPU\n- [#2807](https://github.com/sanic-org/sanic/pull/2807) Add constraint for autodocsum (lint issue in old package version)\n- [#2808](https://github.com/sanic-org/sanic/pull/2808) Refactor GitHub Actions\n- [#2814](https://github.com/sanic-org/sanic/pull/2814) Run CI pipeline on git push\n- [#2846](https://github.com/sanic-org/sanic/pull/2846) Drop old performance tests/benchmarks\n- [#2848](https://github.com/sanic-org/sanic/pull/2848) Makefile cleanup\n- [#2865](https://github.com/sanic-org/sanic/pull/2865)\n  [#2869](https://github.com/sanic-org/sanic/pull/2869)\n  [#2872](https://github.com/sanic-org/sanic/pull/2872)\n  [#2879](https://github.com/sanic-org/sanic/pull/2879)\n  Add ruff to toolchain\n- [#2866](https://github.com/sanic-org/sanic/pull/2866) Fix the alt svc test to run locally with explicit buffer nbytes\n- [#2877](https://github.com/sanic-org/sanic/pull/2877) Use Python's trusted publisher in deployments\n- [#2882](https://github.com/sanic-org/sanic/pull/2882) Introduce dynamic port fixture in targeted locations in the test suite\n\n### Improved Documentation\n- [#2781](https://github.com/sanic-org/sanic/pull/2781)\n  [#2821](https://github.com/sanic-org/sanic/pull/2821)\n  [#2861](https://github.com/sanic-org/sanic/pull/2861)\n  [#2863](https://github.com/sanic-org/sanic/pull/2863)\n  Conversion of User Guide to the SHH (Sanic, html5tagger, HTMX) stack\n- [#2810](https://github.com/sanic-org/sanic/pull/2810) Update README\n- [#2855](https://github.com/sanic-org/sanic/pull/2855) Edit Discord badge\n- [#2864](https://github.com/sanic-org/sanic/pull/2864) Adjust documentation for using state properties within http/https redirection document\n\n\n## Version 23.9.0\n\n_Due to circumstances at the time, v.23.9 was skipped._\n\n\n## Version 23.6.0\n\n### Features\n- [#2670](https://github.com/sanic-org/sanic/pull/2670) Increase `KEEP_ALIVE_TIMEOUT` default to 120 seconds\n- [#2716](https://github.com/sanic-org/sanic/pull/2716) Adding allow route overwrite option in blueprint\n- [#2724](https://github.com/sanic-org/sanic/pull/2724) and [#2792](https://github.com/sanic-org/sanic/pull/2792) Add a new exception signal for ALL exceptions raised anywhere in application\n- [#2727](https://github.com/sanic-org/sanic/pull/2727) Add name prefixing to BP groups\n- [#2754](https://github.com/sanic-org/sanic/pull/2754) Update request type on middleware types\n- [#2770](https://github.com/sanic-org/sanic/pull/2770) Better exception message on startup time application induced import error\n- [#2776](https://github.com/sanic-org/sanic/pull/2776) Set multiprocessing start method early\n- [#2785](https://github.com/sanic-org/sanic/pull/2785) Add custom typing to config and ctx objects\n- [#2790](https://github.com/sanic-org/sanic/pull/2790) Add `request.client_ip`\n\n### Bugfixes\n- [#2728](https://github.com/sanic-org/sanic/pull/2728) Fix traversals for intended results\n- [#2729](https://github.com/sanic-org/sanic/pull/2729) Handle case when headers argument of ResponseStream constructor is None\n- [#2737](https://github.com/sanic-org/sanic/pull/2737) Fix type annotation for `JSONREsponse` default content type\n- [#2740](https://github.com/sanic-org/sanic/pull/2740) Use Sanic's serializer for JSON responses in the Inspector\n- [#2760](https://github.com/sanic-org/sanic/pull/2760) Support for `Request.get_current` in ASGI mode\n- [#2773](https://github.com/sanic-org/sanic/pull/2773) Alow Blueprint routes to explicitly define error_format\n- [#2774](https://github.com/sanic-org/sanic/pull/2774) Resolve headers on different renderers\n- [#2782](https://github.com/sanic-org/sanic/pull/2782) Resolve pypy compatibility issues\n\n### Deprecations and Removals\n- [#2777](https://github.com/sanic-org/sanic/pull/2777) Remove Python 3.7 support\n\n### Developer infrastructure\n- [#2766](https://github.com/sanic-org/sanic/pull/2766) Unpin setuptools version\n- [#2779](https://github.com/sanic-org/sanic/pull/2779) Run keep alive tests in loop to get available port\n\n### Improved Documentation\n- [#2741](https://github.com/sanic-org/sanic/pull/2741) Better documentation examples about running Sanic\nFrom that list, the items to highlight in the release notes:\n\n\n## Version 23.3.0\n\n### Features\n- [#2545](https://github.com/sanic-org/sanic/pull/2545) Standardize init of exceptions for more consistent control of HTTP responses using exceptions\n- [#2606](https://github.com/sanic-org/sanic/pull/2606) Decode headers as UTF-8 also in ASGI\n- [#2646](https://github.com/sanic-org/sanic/pull/2646) Separate ASGI request and lifespan callables\n- [#2659](https://github.com/sanic-org/sanic/pull/2659) Use ``FALLBACK_ERROR_FORMAT`` for handlers that return ``empty()``\n- [#2662](https://github.com/sanic-org/sanic/pull/2662) Add basic file browser (HTML page) and auto-index serving\n- [#2667](https://github.com/sanic-org/sanic/pull/2667) Nicer traceback formatting (HTML page)\n- [#2668](https://github.com/sanic-org/sanic/pull/2668) Smarter error page rendering format selection; more reliant upon header and \"common sense\" defaults\n- [#2680](https://github.com/sanic-org/sanic/pull/2680) Check the status of socket before shutting down with ``SHUT_RDWR``\n- [#2687](https://github.com/sanic-org/sanic/pull/2687) Refresh ``Request.accept`` functionality to be more performant and spec-compliant\n- [#2696](https://github.com/sanic-org/sanic/pull/2696) Add header accessors as properties\n    ```\n    Example-Field: Foo, Bar\n    Example-Field: Baz\n    ```\n    ```python\n    request.headers.example_field == \"Foo, Bar,Baz\"\n    ```\n- [#2700](https://github.com/sanic-org/sanic/pull/2700) Simpler CLI targets\n\n    ```sh\n    $ sanic path.to.module:app          # global app instance\n    $ sanic path.to.module:create_app   # factory pattern\n    $ sanic ./path/to/directory/        # simple serve\n    ```\n- [#2701](https://github.com/sanic-org/sanic/pull/2701) API to define a number of workers in managed processes\n- [#2704](https://github.com/sanic-org/sanic/pull/2704) Add convenience for dynamic changes to routing\n- [#2706](https://github.com/sanic-org/sanic/pull/2706) Add convenience methods for cookie creation and deletion\n    \n    ```python\n    response = text(\"...\")\n    response.add_cookie(\"test\", \"It worked!\", domain=\".yummy-yummy-cookie.com\")\n    ```\n- [#2707](https://github.com/sanic-org/sanic/pull/2707) Simplified ``parse_content_header`` escaping to be RFC-compliant and remove outdated FF hack\n- [#2710](https://github.com/sanic-org/sanic/pull/2710) Stricter charset handling and escaping of request URLs\n- [#2711](https://github.com/sanic-org/sanic/pull/2711) Consume body on ``DELETE`` by default\n- [#2719](https://github.com/sanic-org/sanic/pull/2719) Allow ``password`` to be passed to TLS context\n- [#2720](https://github.com/sanic-org/sanic/pull/2720) Skip middleware on ``RequestCancelled``\n- [#2721](https://github.com/sanic-org/sanic/pull/2721) Change access logging format to ``%s``\n- [#2722](https://github.com/sanic-org/sanic/pull/2722) Add ``CertLoader`` as application option for directly controlling ``SSLContext`` objects\n- [#2725](https://github.com/sanic-org/sanic/pull/2725) Worker sync state tolerance on race condition\n\n### Bugfixes\n- [#2651](https://github.com/sanic-org/sanic/pull/2651) ASGI websocket to pass thru bytes as is\n- [#2697](https://github.com/sanic-org/sanic/pull/2697) Fix comparison between datetime aware and naive in ``file`` when using ``If-Modified-Since``\n\n### Deprecations and Removals\n- [#2666](https://github.com/sanic-org/sanic/pull/2666) Remove deprecated ``__blueprintname__`` property\n\n### Improved Documentation\n- [#2712](https://github.com/sanic-org/sanic/pull/2712) Improved example using ``'https'`` to create the redirect\n\n\n## Version 22.12.0\n\n_Current LTS version_\n\n### Features\n\n- [#2569](https://github.com/sanic-org/sanic/pull/2569) Add `JSONResponse` class with some convenient methods when updating a response object\n- [#2598](https://github.com/sanic-org/sanic/pull/2598) Change `uvloop` requirement to `>=0.15.0`\n- [#2609](https://github.com/sanic-org/sanic/pull/2609) Add compatibility with `websockets` v11.0\n- [#2610](https://github.com/sanic-org/sanic/pull/2610) Kill server early on worker error\n    - Raise deadlock timeout to 30s\n- [#2617](https://github.com/sanic-org/sanic/pull/2617) Scale number of running server workers\n- [#2621](https://github.com/sanic-org/sanic/pull/2621) [#2634](https://github.com/sanic-org/sanic/pull/2634) Send `SIGKILL` on subsequent `ctrl+c` to force worker exit\n- [#2622](https://github.com/sanic-org/sanic/pull/2622) Add API to restart all workers from the multiplexer\n- [#2624](https://github.com/sanic-org/sanic/pull/2624) Default to `spawn` for all subprocesses unless specifically set:\n    ```python\n    from sanic import Sanic\n    \n    Sanic.start_method = \"fork\"\n    ```\n- [#2625](https://github.com/sanic-org/sanic/pull/2625) Filename normalisation of form-data/multipart file uploads\n- [#2626](https://github.com/sanic-org/sanic/pull/2626) Move to HTTP Inspector:\n    - Remote access to inspect running Sanic instances\n    - TLS support for encrypted calls to Inspector\n    - Authentication to Inspector with API key\n    - Ability to extend Inspector with custom commands\n- [#2632](https://github.com/sanic-org/sanic/pull/2632) Control order of restart operations\n- [#2633](https://github.com/sanic-org/sanic/pull/2633) Move reload interval to class variable\n- [#2636](https://github.com/sanic-org/sanic/pull/2636) Add `priority` to `register_middleware` method\n- [#2639](https://github.com/sanic-org/sanic/pull/2639) Add `unquote` to `add_route` method\n- [#2640](https://github.com/sanic-org/sanic/pull/2640) ASGI websockets to receive `text` or `bytes`\n\n\n### Bugfixes\n\n- [#2607](https://github.com/sanic-org/sanic/pull/2607) Force socket shutdown before close to allow rebinding\n- [#2590](https://github.com/sanic-org/sanic/pull/2590) Use actual `StrEnum` in Python 3.11+\n- [#2615](https://github.com/sanic-org/sanic/pull/2615) Ensure middleware executes only once per request timeout\n- [#2627](https://github.com/sanic-org/sanic/pull/2627) Crash ASGI application on lifespan failure\n- [#2635](https://github.com/sanic-org/sanic/pull/2635) Resolve error with low-level server creation on Windows\n\n\n### Deprecations and Removals\n\n- [#2608](https://github.com/sanic-org/sanic/pull/2608) [#2630](https://github.com/sanic-org/sanic/pull/2630) Signal conditions and triggers saved on `signal.extra` \n- [#2626](https://github.com/sanic-org/sanic/pull/2626) Move to HTTP Inspector\n    - 🚨 *BREAKING CHANGE*: Moves the Inspector to a Sanic app from a simple TCP socket with a custom protocol\n    - *DEPRECATE*: The `--inspect*` commands have been deprecated in favor of `inspect ...` commands\n- [#2628](https://github.com/sanic-org/sanic/pull/2628) Replace deprecated `distutils.strtobool`\n\n\n### Developer infrastructure\n\n- [#2612](https://github.com/sanic-org/sanic/pull/2612) Add CI testing for Python 3.11\n\n\n## Version 22.9.1\n\n### Features\n\n- [#2585](https://github.com/sanic-org/sanic/pull/2585) Improved error message when no applications have been registered\n\n\n### Bugfixes\n\n- [#2578](https://github.com/sanic-org/sanic/pull/2578) Add certificate loader for in process certificate creation\n- [#2591](https://github.com/sanic-org/sanic/pull/2591) Do not use sentinel identity for `spawn` compatibility\n- [#2592](https://github.com/sanic-org/sanic/pull/2592) Fix properties in nested blueprint groups\n- [#2595](https://github.com/sanic-org/sanic/pull/2595) Introduce sleep interval on new worker reloader\n\n\n### Deprecations and Removals\n\n\n### Developer infrastructure\n\n- [#2588](https://github.com/sanic-org/sanic/pull/2588) Markdown templates on issue forms\n\n\n### Improved Documentation\n\n- [#2556](https://github.com/sanic-org/sanic/pull/2556) v22.9 documentation\n- [#2582](https://github.com/sanic-org/sanic/pull/2582) Cleanup documentation on Windows support\n\n\n## Version 22.9.0\n\n### Features\n\n- [#2445](https://github.com/sanic-org/sanic/pull/2445) Add custom loads function \n- [#2490](https://github.com/sanic-org/sanic/pull/2490) Make `WebsocketImplProtocol` async iterable\n- [#2499](https://github.com/sanic-org/sanic/pull/2499) Sanic Server WorkerManager refactor\n- [#2506](https://github.com/sanic-org/sanic/pull/2506) Use `pathlib` for path resolution (for static file serving)\n- [#2508](https://github.com/sanic-org/sanic/pull/2508) Use `path.parts` instead of `match` (for static file serving)\n- [#2513](https://github.com/sanic-org/sanic/pull/2513) Better request cancel handling\n- [#2516](https://github.com/sanic-org/sanic/pull/2516) Add request properties for HTTP method info:\n    - `request.is_safe`\n    - `request.is_idempotent`\n    - `request.is_cacheable`\n    - *See* [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) *for more information about when these apply*\n- [#2522](https://github.com/sanic-org/sanic/pull/2522) Always show server location in ASGI\n- [#2526](https://github.com/sanic-org/sanic/pull/2526) Cache control support for static files for returning 304 when appropriate\n- [#2533](https://github.com/sanic-org/sanic/pull/2533) Refactor `_static_request_handler`\n- [#2540](https://github.com/sanic-org/sanic/pull/2540) Add signals before and after handler execution\n    - `http.handler.before`\n    - `http.handler.after`\n- [#2542](https://github.com/sanic-org/sanic/pull/2542) Add *[redacted]* to CLI :)\n- [#2546](https://github.com/sanic-org/sanic/pull/2546) Add deprecation warning filter\n- [#2550](https://github.com/sanic-org/sanic/pull/2550) Middleware priority and performance enhancements\n\n### Bugfixes\n\n- [#2495](https://github.com/sanic-org/sanic/pull/2495) Prevent directory traversion with static files\n- [#2515](https://github.com/sanic-org/sanic/pull/2515) Do not apply double slash to paths in certain static dirs in Blueprints\n\n### Deprecations and Removals\n\n- [#2525](https://github.com/sanic-org/sanic/pull/2525) Warn on duplicate route names, will be prevented outright in v23.3\n- [#2537](https://github.com/sanic-org/sanic/pull/2537) Raise warning and deprecation notice on duplicate exceptions, will be prevented outright in v23.3\n\n### Developer infrastructure\n\n- [#2504](https://github.com/sanic-org/sanic/pull/2504) Cleanup test suite\n- [#2505](https://github.com/sanic-org/sanic/pull/2505) Replace Unsupported Python Version Number from the Contributing Doc\n- [#2530](https://github.com/sanic-org/sanic/pull/2530) Do not include tests folder in installed package resolver\n\n### Improved Documentation\n\n- [#2502](https://github.com/sanic-org/sanic/pull/2502) Fix a few typos\n- [#2517](https://github.com/sanic-org/sanic/pull/2517) [#2536](https://github.com/sanic-org/sanic/pull/2536) Add some type hints\n\n\n## Version 22.6.2\n\n### Bugfixes\n\n- [#2522](https://github.com/sanic-org/sanic/pull/2522) Always show server location in ASGI\n\n## Version 22.6.1\n\n### Bugfixes\n\n- [#2477](https://github.com/sanic-org/sanic/pull/2477) Sanic static directory fails when folder name ends with \"..\"\n\n\n## Version 22.6.0\n\n### Features\n- [#2378](https://github.com/sanic-org/sanic/pull/2378) Introduce HTTP/3 and autogeneration of TLS certificates in `DEBUG` mode\n    - 👶 *EARLY RELEASE FEATURE*: Serving Sanic over HTTP/3 is an early release feature. It does not yet fully cover the HTTP/3 spec, but instead aims for feature parity with Sanic's existing HTTP/1.1 server. Websockets, WebTransport, push responses are examples of some features not yet implemented.\n    - 📦 *EXTRA REQUIREMENT*: Not all HTTP clients are capable of interfacing with HTTP/3 servers. You may need to install a [HTTP/3 capable client](https://curl.se/docs/http3.html).\n    - 📦 *EXTRA REQUIREMENT*: In order to use TLS autogeneration, you must install either [mkcert](https://github.com/FiloSottile/mkcert) or [trustme](https://github.com/python-trio/trustme).\n- [#2416](https://github.com/sanic-org/sanic/pull/2416) Add message to `task.cancel`\n- [#2420](https://github.com/sanic-org/sanic/pull/2420) Add exception aliases for more consistent naming with standard HTTP response types (`BadRequest`, `MethodNotAllowed`, `RangeNotSatisfiable`)\n- [#2432](https://github.com/sanic-org/sanic/pull/2432) Expose ASGI `scope` as a property on the `Request` object\n- [#2438](https://github.com/sanic-org/sanic/pull/2438) Easier access to websocket class for annotation: `from sanic import Websocket`\n- [#2439](https://github.com/sanic-org/sanic/pull/2439) New API for reading form values with options: `Request.get_form` \n- [#2445](https://github.com/sanic-org/sanic/pull/2445) Add custom `loads` function\n- [#2447](https://github.com/sanic-org/sanic/pull/2447), [#2486](https://github.com/sanic-org/sanic/pull/2486) Improved API to support setting cache control headers\n- [#2453](https://github.com/sanic-org/sanic/pull/2453) Move verbosity filtering to logger\n- [#2475](https://github.com/sanic-org/sanic/pull/2475) Expose getter for current request using `Request.get_current()`\n\n### Bugfixes\n- [#2448](https://github.com/sanic-org/sanic/pull/2448) Fix to allow running with `pythonw.exe` or places where there is no `sys.stdout`\n- [#2451](https://github.com/sanic-org/sanic/pull/2451) Trigger `http.lifecycle.request` signal in ASGI mode\n- [#2455](https://github.com/sanic-org/sanic/pull/2455) Resolve typing of stacked route definitions\n- [#2463](https://github.com/sanic-org/sanic/pull/2463) Properly catch websocket CancelledError in websocket handler in Python 3.7\n\n### Deprecations and Removals\n- [#2487](https://github.com/sanic-org/sanic/pull/2487) v22.6 deprecations and changes\n    1. Optional application registry\n    1. Execution of custom handlers after some part of response was sent\n    1. Configuring fallback handlers on the `ErrorHandler`\n    1. Custom `LOGO` setting\n    1. `sanic.response.stream`\n    1. `AsyncioServer.init`\n\n### Developer infrastructure\n- [#2449](https://github.com/sanic-org/sanic/pull/2449) Clean up `black` and `isort` config\n- [#2479](https://github.com/sanic-org/sanic/pull/2479) Fix some flappy tests\n\n### Improved Documentation\n- [#2461](https://github.com/sanic-org/sanic/pull/2461) Update example to match current application naming standards\n- [#2466](https://github.com/sanic-org/sanic/pull/2466) Better type annotation for `Extend`\n- [#2485](https://github.com/sanic-org/sanic/pull/2485) Improved help messages in CLI\n\n\n## Version 22.3.0\n\n### Features\n- [#2347](https://github.com/sanic-org/sanic/pull/2347) API for multi-application server\n    - 🚨 *BREAKING CHANGE*: The old `sanic.worker.GunicornWorker` has been **removed**. To run Sanic with `gunicorn`, you should use it thru `uvicorn` [as described in their docs](https://www.uvicorn.org/#running-with-gunicorn).\n    - 🧁 *SIDE EFFECT*: Named background tasks are now supported, even in Python 3.7\n- [#2357](https://github.com/sanic-org/sanic/pull/2357) Parse `Authorization` header as `Request.credentials`\n- [#2361](https://github.com/sanic-org/sanic/pull/2361) Add config option to skip `Touchup` step in application startup\n- [#2372](https://github.com/sanic-org/sanic/pull/2372) Updates to CLI help messaging\n- [#2382](https://github.com/sanic-org/sanic/pull/2382) Downgrade warnings to backwater debug messages \n- [#2396](https://github.com/sanic-org/sanic/pull/2396) Allow for `multidict` v0.6\n- [#2401](https://github.com/sanic-org/sanic/pull/2401) Upgrade CLI catching for alternative application run types\n- [#2402](https://github.com/sanic-org/sanic/pull/2402) Conditionally inject CLI arguments into factory\n- [#2413](https://github.com/sanic-org/sanic/pull/2413) Add new start and stop event listeners to reloader process\n- [#2414](https://github.com/sanic-org/sanic/pull/2414) Remove loop as required listener arg\n- [#2415](https://github.com/sanic-org/sanic/pull/2415) Better exception for bad URL parsing\n- [sanic-routing#47](https://github.com/sanic-org/sanic-routing/pull/47) Add a new extention parameter type: `<file:ext>`, `<file:ext=jpg>`, `<file:ext=jpg|png|gif|svg>`, `<file=int:ext>`, `<file=int:ext=jpg|png|gif|svg>`, `<file=float:ext=tar.gz>`\n    - 👶 *BETA FEATURE*: This feature will not work with `path` type matching, and is being released as a beta feature only.\n- [sanic-routing#57](https://github.com/sanic-org/sanic-routing/pull/57) Change `register_pattern` to accept a `str` or `Pattern`\n- [sanic-routing#58](https://github.com/sanic-org/sanic-routing/pull/58) Default matching on non-empty strings only, and new `strorempty` pattern type\n    - 🚨 *BREAKING CHANGE*: Previously a route with a dynamic string parameter (`/<foo>` or `/<foo:str>`) would match on any string, including empty strings. It will now **only** match a non-empty string. To retain the old behavior, you should use the new parameter type: `/<foo:strorempty>`.\n\n### Bugfixes\n- [#2373](https://github.com/sanic-org/sanic/pull/2373) Remove `error_logger` on websockets\n- [#2381](https://github.com/sanic-org/sanic/pull/2381) Fix newly assigned `None` in task registry\n- [sanic-routing#52](https://github.com/sanic-org/sanic-routing/pull/52) Add type casting to regex route matching\n- [sanic-routing#60](https://github.com/sanic-org/sanic-routing/pull/60) Add requirements check on regex routes (this resolves, for example, multiple static directories with differing `host` values)\n\n### Deprecations and Removals\n- [#2362](https://github.com/sanic-org/sanic/pull/2362) 22.3 Deprecations and changes\n    1. `debug=True` and `--debug` do _NOT_ automatically run `auto_reload`\n    2. Default error render is with plain text (browsers still get HTML by default because `auto` looks at headers)\n    3. `config` is required for `ErrorHandler.finalize`\n    4. `ErrorHandler.lookup` requires two positional args\n    5. Unused websocket protocol args removed\n- [#2344](https://github.com/sanic-org/sanic/pull/2344) Deprecate loading of lowercase environment variables\n\n### Developer infrastructure\n- [#2363](https://github.com/sanic-org/sanic/pull/2363) Revert code coverage back to Codecov\n- [#2405](https://github.com/sanic-org/sanic/pull/2405) Upgrade tests for `sanic-routing` changes\n- [sanic-testing#35](https://github.com/sanic-org/sanic-testing/pull/35) Allow for httpx v0.22\n\n### Improved Documentation\n- [#2350](https://github.com/sanic-org/sanic/pull/2350) Fix link in README for ASGI\n- [#2398](https://github.com/sanic-org/sanic/pull/2398) Document middleware on_request and on_response\n- [#2409](https://github.com/sanic-org/sanic/pull/2409) Add missing documentation for `Request.respond`\n\n### Miscellaneous\n- [#2376](https://github.com/sanic-org/sanic/pull/2376) Fix typing for `ListenerMixin.listener`\n- [#2383](https://github.com/sanic-org/sanic/pull/2383) Clear deprecation warning in `asyncio.wait`\n- [#2387](https://github.com/sanic-org/sanic/pull/2387) Cleanup `__slots__` implementations\n- [#2390](https://github.com/sanic-org/sanic/pull/2390) Clear deprecation warning in `asyncio.get_event_loop`\n\n\n## Version 21.12.1\n\n- [#2349](https://github.com/sanic-org/sanic/pull/2349) Only display MOTD on startup\n- [#2354](https://github.com/sanic-org/sanic/pull/2354) Ignore name argument in Python 3.7\n- [#2355](https://github.com/sanic-org/sanic/pull/2355) Add config.update support for all config values\n\n## Version 21.12.0\n\n### Features\n- [#2260](https://github.com/sanic-org/sanic/pull/2260) Allow early Blueprint registrations to still apply later added objects\n- [#2262](https://github.com/sanic-org/sanic/pull/2262) Noisy exceptions - force logging of all exceptions\n- [#2264](https://github.com/sanic-org/sanic/pull/2264) Optional `uvloop` by configuration\n- [#2270](https://github.com/sanic-org/sanic/pull/2270) Vhost support using multiple TLS certificates\n- [#2277](https://github.com/sanic-org/sanic/pull/2277) Change signal routing for increased consistency\n    - *BREAKING CHANGE*: If you were manually routing signals there is a breaking change. The signal router's `get` is no longer 100% determinative. There is now an additional step to loop thru the returned signals for proper matching on the requirements. If signals are being dispatched using `app.dispatch` or `bp.dispatch`, there is no change.\n- [#2290](https://github.com/sanic-org/sanic/pull/2290) Add contextual exceptions\n- [#2291](https://github.com/sanic-org/sanic/pull/2291) Increase join concat performance \n- [#2295](https://github.com/sanic-org/sanic/pull/2295), [#2316](https://github.com/sanic-org/sanic/pull/2316), [#2331](https://github.com/sanic-org/sanic/pull/2331) Restructure of CLI and application state with new displays and more command parity with `app.run`\n- [#2302](https://github.com/sanic-org/sanic/pull/2302) Add route context at definition time\n- [#2304](https://github.com/sanic-org/sanic/pull/2304) Named tasks and new API for managing background tasks\n- [#2307](https://github.com/sanic-org/sanic/pull/2307) On app auto-reload, provide insight of changed files\n- [#2308](https://github.com/sanic-org/sanic/pull/2308) Auto extend application with [Sanic Extensions](https://sanicframework.org/en/plugins/sanic-ext/getting-started.html) if it is installed, and provide first class support for accessing the extensions\n- [#2309](https://github.com/sanic-org/sanic/pull/2309) Builtin signals changed to `Enum`\n- [#2313](https://github.com/sanic-org/sanic/pull/2313) Support additional config implementation use case\n- [#2321](https://github.com/sanic-org/sanic/pull/2321) Refactor environment variable hydration logic\n- [#2327](https://github.com/sanic-org/sanic/pull/2327) Prevent sending multiple or mixed responses on a single request\n- [#2330](https://github.com/sanic-org/sanic/pull/2330) Custom type casting on environment variables\n- [#2332](https://github.com/sanic-org/sanic/pull/2332) Make all deprecation notices consistent\n- [#2335](https://github.com/sanic-org/sanic/pull/2335) Allow underscore to start instance names\n\n### Bugfixes\n- [#2273](https://github.com/sanic-org/sanic/pull/2273) Replace assignation by typing for `websocket_handshake`\n- [#2285](https://github.com/sanic-org/sanic/pull/2285) Fix IPv6 display in startup logs\n- [#2299](https://github.com/sanic-org/sanic/pull/2299) Dispatch `http.lifecyle.response` from exception handler\n\n### Deprecations and Removals\n- [#2306](https://github.com/sanic-org/sanic/pull/2306) Removal of deprecated items\n    - `Sanic` and `Blueprint` may no longer have arbitrary properties attached to them\n    - `Sanic` and `Blueprint` forced to have compliant names\n        - alphanumeric + `_` + `-`\n        - must start with letter or `_`\n    - `load_env` keyword argument of `Sanic`\n    - `sanic.exceptions.abort`\n    - `sanic.views.CompositionView`\n    - `sanic.response.StreamingHTTPResponse`\n        - *NOTE:* the `stream()` response method (where you pass a callable streaming function) has been deprecated and will be removed in v22.6. You should upgrade all streaming responses to the new style: https://sanicframework.org/en/guide/advanced/streaming.html#response-streaming\n- [#2320](https://github.com/sanic-org/sanic/pull/2320) Remove app instance from Config for error handler setting\n\n### Developer infrastructure\n- [#2251](https://github.com/sanic-org/sanic/pull/2251) Change dev install command\n- [#2286](https://github.com/sanic-org/sanic/pull/2286) Change codeclimate complexity threshold from 5 to 10\n- [#2287](https://github.com/sanic-org/sanic/pull/2287) Update host test function names so they are not overwritten\n- [#2292](https://github.com/sanic-org/sanic/pull/2292) Fail CI on error\n- [#2311](https://github.com/sanic-org/sanic/pull/2311), [#2324](https://github.com/sanic-org/sanic/pull/2324) Do not run tests for draft PRs\n- [#2336](https://github.com/sanic-org/sanic/pull/2336) Remove paths from coverage checks\n- [#2338](https://github.com/sanic-org/sanic/pull/2338) Cleanup ports on tests\n\n### Improved Documentation\n- [#2269](https://github.com/sanic-org/sanic/pull/2269), [#2329](https://github.com/sanic-org/sanic/pull/2329), [#2333](https://github.com/sanic-org/sanic/pull/2333) Cleanup typos and fix language\n\n### Miscellaneous\n- [#2257](https://github.com/sanic-org/sanic/pull/2257), [#2294](https://github.com/sanic-org/sanic/pull/2294), [#2341](https://github.com/sanic-org/sanic/pull/2341) Add Python 3.10 support\n- [#2279](https://github.com/sanic-org/sanic/pull/2279), [#2317](https://github.com/sanic-org/sanic/pull/2317), [#2322](https://github.com/sanic-org/sanic/pull/2322) Add/correct missing type annotations\n- [#2305](https://github.com/sanic-org/sanic/pull/2305) Fix examples to use modern implementations\n\n\n## Version 21.9.3\n*Rerelease of v21.9.2 with some cleanup*\n\n## Version 21.9.2\n- [#2268](https://github.com/sanic-org/sanic/pull/2268) Make HTTP connections start in IDLE stage, avoiding delays and error messages\n- [#2310](https://github.com/sanic-org/sanic/pull/2310) More consistent config setting with post-FALLBACK_ERROR_FORMAT apply\n\n## Version 21.9.1\n- [#2259](https://github.com/sanic-org/sanic/pull/2259) Allow non-conforming ErrorHandlers\n\n## Version 21.9.0\n\n### Features\n- [#2158](https://github.com/sanic-org/sanic/pull/2158), [#2248](https://github.com/sanic-org/sanic/pull/2248) Complete overhaul of I/O to websockets\n- [#2160](https://github.com/sanic-org/sanic/pull/2160) Add new 17 signals into server and request lifecycles\n- [#2162](https://github.com/sanic-org/sanic/pull/2162) Smarter `auto` fallback formatting upon exception\n- [#2184](https://github.com/sanic-org/sanic/pull/2184) Introduce implementation for copying a Blueprint\n- [#2200](https://github.com/sanic-org/sanic/pull/2200) Accept header parsing\n- [#2207](https://github.com/sanic-org/sanic/pull/2207) Log remote address if available\n- [#2209](https://github.com/sanic-org/sanic/pull/2209) Add convenience methods to BP groups\n- [#2216](https://github.com/sanic-org/sanic/pull/2216) Add default messages to SanicExceptions\n- [#2225](https://github.com/sanic-org/sanic/pull/2225) Type annotation convenience for annotated handlers with path parameters\n- [#2236](https://github.com/sanic-org/sanic/pull/2236) Allow Falsey (but not-None) responses from route handlers\n- [#2238](https://github.com/sanic-org/sanic/pull/2238) Add `exception` decorator to Blueprint Groups\n- [#2244](https://github.com/sanic-org/sanic/pull/2244) Explicit static directive for serving file or dir (ex: `static(..., resource_type=\"file\")`)\n- [#2245](https://github.com/sanic-org/sanic/pull/2245) Close HTTP loop when connection task cancelled\n\n### Bugfixes\n- [#2188](https://github.com/sanic-org/sanic/pull/2188) Fix the handling of the end of a chunked request\n- [#2195](https://github.com/sanic-org/sanic/pull/2195) Resolve unexpected error handling on static requests\n- [#2208](https://github.com/sanic-org/sanic/pull/2208) Make blueprint-based exceptions attach and trigger in a more intuitive manner\n- [#2211](https://github.com/sanic-org/sanic/pull/2211) Fixed for handling exceptions of asgi app call\n- [#2213](https://github.com/sanic-org/sanic/pull/2213) Fix bug where ws exceptions not being logged\n- [#2231](https://github.com/sanic-org/sanic/pull/2231) Cleaner closing of tasks by using `abort()` in strategic places to avoid dangling sockets\n- [#2247](https://github.com/sanic-org/sanic/pull/2247) Fix logging of auto-reload status in debug mode\n- [#2246](https://github.com/sanic-org/sanic/pull/2246) Account for BP with exception handler but no routes\n\n### Developer infrastructure  \n- [#2194](https://github.com/sanic-org/sanic/pull/2194) HTTP unit tests with raw client\n- [#2199](https://github.com/sanic-org/sanic/pull/2199) Switch to codeclimate\n- [#2214](https://github.com/sanic-org/sanic/pull/2214) Try Reopening Windows Tests\n- [#2229](https://github.com/sanic-org/sanic/pull/2229) Refactor `HttpProtocol` into a base class\n- [#2230](https://github.com/sanic-org/sanic/pull/2230) Refactor `server.py` into multi-file module\n\n### Miscellaneous\n- [#2173](https://github.com/sanic-org/sanic/pull/2173) Remove Duplicated Dependencies and PEP 517 Support \n- [#2193](https://github.com/sanic-org/sanic/pull/2193), [#2196](https://github.com/sanic-org/sanic/pull/2196), [#2217](https://github.com/sanic-org/sanic/pull/2217) Type annotation changes\n\n\n## Version 21.6.1\n\n**Bugfixes**\n\n-   [#2178](https://github.com/sanic-org/sanic/pull/2178) Update\n    sanic-routing to allow for better splitting of complex URI\n    templates\n-   [#2183](https://github.com/sanic-org/sanic/pull/2183) Proper\n    handling of chunked request bodies to resolve phantom 503 in logs\n-   [#2181](https://github.com/sanic-org/sanic/pull/2181) Resolve\n    regression in exception logging\n-   [#2201](https://github.com/sanic-org/sanic/pull/2201) Cleanup\n    request info in pipelined requests\n\n## Version 21.6.0\n\n**Features**\n\n-   [#2094](https://github.com/sanic-org/sanic/pull/2094) Add\n    `response.eof()` method for closing a stream in a handler\n\n-   [#2097](https://github.com/sanic-org/sanic/pull/2097) Allow\n    case-insensitive HTTP Upgrade header\n\n-   [#2104](https://github.com/sanic-org/sanic/pull/2104) Explicit\n    usage of CIMultiDict getters\n\n-   [#2109](https://github.com/sanic-org/sanic/pull/2109) Consistent\n    use of error loggers\n\n-   [#2114](https://github.com/sanic-org/sanic/pull/2114) New\n    `client_ip` access of connection info instance\n\n-   [#2119](https://github.com/sanic-org/sanic/pull/2119) Alternatate\n    classes on instantiation for `Config` and `Sanic.ctx`\n\n-   [#2133](https://github.com/sanic-org/sanic/pull/2133) Implement\n    new version of AST router\n\n    -   Proper differentiation between `alpha` and `string` param\n        types\n    -   Adds a `slug` param type, example: `<foo:slug>`\n    -   Deprecates `<foo:string>` in favor of `<foo:str>`\n    -   Deprecates `<foo:number>` in favor of `<foo:float>`\n    -   Adds a `route.uri` accessor\n\n-   [#2136](https://github.com/sanic-org/sanic/pull/2136) CLI\n    improvements with new optional params\n\n-   [#2137](https://github.com/sanic-org/sanic/pull/2137) Add\n    `version_prefix` to URL builders\n\n-   [#2140](https://github.com/sanic-org/sanic/pull/2140) Event\n    autoregistration with `EVENT_AUTOREGISTER`\n\n-   [#2146](https://github.com/sanic-org/sanic/pull/2146),\n    [#2147](https://github.com/sanic-org/sanic/pull/2147) Require\n    stricter names on `Sanic()` and `Blueprint()`\n\n-   [#2150](https://github.com/sanic-org/sanic/pull/2150) Infinitely\n    reusable and nestable `Blueprint` and `BlueprintGroup`\n\n-   [#2154](https://github.com/sanic-org/sanic/pull/2154) Upgrade\n    `websockets` dependency to min version\n\n-   [#2155](https://github.com/sanic-org/sanic/pull/2155) Allow for\n    maximum header sizes to be increased: `REQUEST_MAX_HEADER_SIZE`\n\n-   [#2157](https://github.com/sanic-org/sanic/pull/2157) Allow app\n    factory pattern in CLI\n\n-   [#2165](https://github.com/sanic-org/sanic/pull/2165) Change HTTP\n    methods to enums\n\n-   [#2167](https://github.com/sanic-org/sanic/pull/2167) Allow\n    auto-reloading on additional directories\n\n-   [#2168](https://github.com/sanic-org/sanic/pull/2168) Add simple\n    HTTP server to CLI\n\n-   [#2170](https://github.com/sanic-org/sanic/pull/2170) Additional\n    methods for attaching `HTTPMethodView`\n\n**Bugfixes**\n\n-   [#2091](https://github.com/sanic-org/sanic/pull/2091) Fix\n    `UserWarning` in ASGI mode for missing `__slots__`\n-   [#2099](https://github.com/sanic-org/sanic/pull/2099) Fix static\n    request handler logging exception on 404\n-   [#2110](https://github.com/sanic-org/sanic/pull/2110) Fix\n    request.args.pop removes parameters inconsistently\n-   [#2107](https://github.com/sanic-org/sanic/pull/2107) Fix type\n    hinting for load_env\n-   [#2127](https://github.com/sanic-org/sanic/pull/2127) Make sure\n    ASGI ws subprotocols is a list\n-   [#2128](https://github.com/sanic-org/sanic/pull/2128) Fix issue\n    where Blueprint exception handlers do not consistently route to\n    proper handler\n\n**Deprecations and Removals**\n\n-   [#2156](https://github.com/sanic-org/sanic/pull/2156) Remove\n    config value `REQUEST_BUFFER_QUEUE_SIZE`\n-   [#2170](https://github.com/sanic-org/sanic/pull/2170)\n    `CompositionView` deprecated and marked for removal in 21.12\n-   [#2172](https://github.com/sanic-org/sanic/pull/2170) Deprecate\n    StreamingHTTPResponse\n\n**Developer infrastructure**\n\n-   [#2149](https://github.com/sanic-org/sanic/pull/2149) Remove\n    Travis CI in favor of GitHub Actions\n\n**Improved Documentation**\n\n-   [#2164](https://github.com/sanic-org/sanic/pull/2164) Fix typo in\n    documentation\n-   [#2100](https://github.com/sanic-org/sanic/pull/2100) Remove\n    documentation for non-existent arguments\n\n## Version 21.3.2\n\n**Bugfixes**\n\n-   [#2081](https://github.com/sanic-org/sanic/pull/2081) Disable\n    response timeout on websocket connections\n-   [#2085](https://github.com/sanic-org/sanic/pull/2085) Make sure\n    that blueprints with no slash is maintained when applied\n\n## Version 21.3.1\n\n**Bugfixes**\n\n-   [#2076](https://github.com/sanic-org/sanic/pull/2076) Static files\n    inside subfolders are not accessible (404)\n\n## Version 21.3.0\n\n[Release\nNotes](https://sanicframework.org/en/guide/release-notes/v21.3.html)\n\n**Features**\n\n-   [#1876](https://github.com/sanic-org/sanic/pull/1876) Unified\n    streaming server\n-   [#2005](https://github.com/sanic-org/sanic/pull/2005) New\n    `Request.id` property\n-   [#2008](https://github.com/sanic-org/sanic/pull/2008) Allow\n    Pathlib Path objects to be passed to `app.static()` helper\n-   [#2010](https://github.com/sanic-org/sanic/pull/2010),\n    [#2031](https://github.com/sanic-org/sanic/pull/2031) New\n    startup-optimized router\n-   [#2018](https://github.com/sanic-org/sanic/pull/2018)\n    [#2064](https://github.com/sanic-org/sanic/pull/2064) Listeners\n    for main server process\n-   [#2032](https://github.com/sanic-org/sanic/pull/2032) Add raw\n    header info to request object\n-   [#2042](https://github.com/sanic-org/sanic/pull/2042)\n    [#2060](https://github.com/sanic-org/sanic/pull/2060)\n    [#2061](https://github.com/sanic-org/sanic/pull/2061) Introduce\n    Signals API\n-   [#2043](https://github.com/sanic-org/sanic/pull/2043) Add\n    `__str__` and `__repr__` to Sanic and Blueprint\n-   [#2047](https://github.com/sanic-org/sanic/pull/2047) Enable\n    versioning and strict slash on BlueprintGroup\n-   [#2053](https://github.com/sanic-org/sanic/pull/2053) Make\n    `get_app` name argument optional\n-   [#2055](https://github.com/sanic-org/sanic/pull/2055) JSON encoder\n    change via app\n-   [#2063](https://github.com/sanic-org/sanic/pull/2063) App and\n    connection level context objects\n\n**Bugfixes**\n\n-   Resolve [#1420](https://github.com/sanic-org/sanic/pull/1420)\n    `url_for` where `strict_slashes` are on for a path ending in `/`\n-   Resolve [#1525](https://github.com/sanic-org/sanic/pull/1525)\n    Routing is incorrect with some special characters\n-   Resolve [#1653](https://github.com/sanic-org/sanic/pull/1653) ASGI\n    headers in body\n-   Resolve [#1722](https://github.com/sanic-org/sanic/pull/1722)\n    Using curl in chunk mode\n-   Resolve [#1730](https://github.com/sanic-org/sanic/pull/1730)\n    Extra content in ASGI streaming response\n-   Resolve [#1749](https://github.com/sanic-org/sanic/pull/1749)\n    Restore broken middleware edge cases\n-   Resolve [#1785](https://github.com/sanic-org/sanic/pull/1785)\n    [#1804](https://github.com/sanic-org/sanic/pull/1804) Synchronous\n    error handlers\n-   Resolve [#1790](https://github.com/sanic-org/sanic/pull/1790)\n    Protocol errors did not support async error handlers #1790\n-   Resolve [#1824](https://github.com/sanic-org/sanic/pull/1824)\n    Timeout on specific methods\n-   Resolve [#1875](https://github.com/sanic-org/sanic/pull/1875)\n    Response timeout error from all routes after returning several\n    timeouts from a specific route\n-   Resolve [#1988](https://github.com/sanic-org/sanic/pull/1988)\n    Handling of safe methods with body\n-   [#2001](https://github.com/sanic-org/sanic/pull/2001) Raise\n    ValueError when cookie max-age is not an integer\n\n**Deprecations and Removals**\n\n-   [#2007](https://github.com/sanic-org/sanic/pull/2007) \\* Config\n    using `from_envvar` \\* Config using `from_pyfile` \\* Config using\n    `from_object`\n-   [#2009](https://github.com/sanic-org/sanic/pull/2009) Remove Sanic\n    test client to its own package\n-   [#2036](https://github.com/sanic-org/sanic/pull/2036),\n    [#2037](https://github.com/sanic-org/sanic/pull/2037) Drop Python\n    3.6 support\n-   `Request.endpoint` deprecated in favor of `Request.name`\n-   handler type name prefixes removed (static, websocket, etc)\n\n**Developer infrastructure**\n\n-   [#1995](https://github.com/sanic-org/sanic/pull/1995) Create\n    FUNDING.yml\n-   [#2013](https://github.com/sanic-org/sanic/pull/2013) Add codeql\n    to CI pipeline\n-   [#2038](https://github.com/sanic-org/sanic/pull/2038) Codecov\n    configuration updates\n-   [#2049](https://github.com/sanic-org/sanic/pull/2049) Updated\n    setup.py to use `find_packages`\n\n**Improved Documentation**\n\n-   [#1218](https://github.com/sanic-org/sanic/pull/1218)\n    Documentation for sanic.log.\\* is missing\n-   [#1608](https://github.com/sanic-org/sanic/pull/1608) Add\n    documentation on calver and LTS\n-   [#1731](https://github.com/sanic-org/sanic/pull/1731) Support\n    mounting application elsewhere than at root path\n-   [#2006](https://github.com/sanic-org/sanic/pull/2006) Upgraded\n    type annotations and improved docstrings and API documentation\n-   [#2052](https://github.com/sanic-org/sanic/pull/2052) Fix some\n    examples and docs\n\n**Miscellaneous**\n\n-   `Request.route` property\n-   Better websocket subprotocols support\n-   Resolve bug with middleware in Blueprint Group when passed\n    callable\n-   Moves common logic between Blueprint and Sanic into mixins\n-   Route naming changed to be more consistent\n    -   request endpoint is the route name\n    -   route names are fully namespaced\n-   Some new convenience decorators:\n    -   `@app.main_process_start`\n    -   `@app.main_process_stop`\n    -   `@app.before_server_start`\n    -   `@app.after_server_start`\n    -   `@app.before_server_stop`\n    -   `@app.after_server_stop`\n    -   `@app.on_request`\n    -   `@app.on_response`\n-   Fixes `Allow` header that did not include `HEAD`\n-   Using \\\"name\\\" keyword in `url_for` for a \\\"static\\\" route where\n    name does not exist\n-   Cannot have multiple `app.static()` without using the named param\n-   Using \\\"filename\\\" keyword in `url_for` on a file route\n-   `unquote` in route def (not automatic)\n-   `routes_all` is tuples\n-   Handler arguments are kwarg only\n-   `request.match_info` is now a cached (and not computed) property\n-   Unknown static file mimetype is sent as `application/octet-stream`\n-   `_host` keyword in `url_for`\n-   Add charset default to `utf-8` for text and js content types if\n    not specified\n-   Version for a route can be str, float, or int\n-   Route has ctx property\n-   App has `routes_static`, `routes_dynamic`, `routes_regex`\n-   [#2044](https://github.com/sanic-org/sanic/pull/2044) Code cleanup\n    and refactoring\n-   [#2072](https://github.com/sanic-org/sanic/pull/2072) Remove\n    `BaseSanic` metaclass\n-   [#2074](https://github.com/sanic-org/sanic/pull/2074) Performance\n    adjustments in `handle_request_`\n\n## Version 20.12.3\n\n**Bugfixes**\n\n-   [#2021](https://github.com/sanic-org/sanic/pull/2021) Remove\n    prefix from websocket handler name\n\n## Version 20.12.2\n\n**Dependencies**\n\n-   [#2026](https://github.com/sanic-org/sanic/pull/2026) Fix uvloop\n    to 0.14 because 0.15 drops Python 3.6 support\n-   [#2029](https://github.com/sanic-org/sanic/pull/2029) Remove old\n    chardet requirement, add in hard multidict requirement\n\n## Version 19.12.5\n\n**Dependencies**\n\n-   [#2025](https://github.com/sanic-org/sanic/pull/2025) Fix uvloop\n    to 0.14 because 0.15 drops Python 3.6 support\n-   [#2027](https://github.com/sanic-org/sanic/pull/2027) Remove old\n    chardet requirement, add in hard multidict requirement\n\n## Version 20.12.0\n\n**Features**\n\n-   [#1993](https://github.com/sanic-org/sanic/pull/1993) Add disable\n    app registry\n-   [#1945](https://github.com/sanic-org/sanic/pull/1945) Static route\n    more verbose if file not found\n-   [#1954](https://github.com/sanic-org/sanic/pull/1954) Fix static\n    routes registration on a blueprint\n-   [#1961](https://github.com/sanic-org/sanic/pull/1961) Add Python\n    3.9 support\n-   [#1962](https://github.com/sanic-org/sanic/pull/1962) Sanic CLI\n    upgrade\n-   [#1967](https://github.com/sanic-org/sanic/pull/1967) Update\n    aiofile version requirements\n-   [#1969](https://github.com/sanic-org/sanic/pull/1969) Update\n    multidict version requirements\n-   [#1970](https://github.com/sanic-org/sanic/pull/1970) Add py.typed\n    file\n-   [#1972](https://github.com/sanic-org/sanic/pull/1972) Speed\n    optimization in request handler\n-   [#1979](https://github.com/sanic-org/sanic/pull/1979) Add app\n    registry and Sanic class level app retrieval\n\n**Bugfixes**\n\n-   [#1965](https://github.com/sanic-org/sanic/pull/1965) Fix Chunked\n    Transport-Encoding in ASGI streaming response\n\n**Deprecations and Removals**\n\n-   [#1981](https://github.com/sanic-org/sanic/pull/1981) Cleanup and\n    remove deprecated code\n\n**Developer infrastructure**\n\n-   [#1956](https://github.com/sanic-org/sanic/pull/1956) Fix load\n    module test\n-   [#1973](https://github.com/sanic-org/sanic/pull/1973) Transition\n    Travis from .org to .com\n-   [#1986](https://github.com/sanic-org/sanic/pull/1986) Update tox\n    requirements\n\n**Improved Documentation**\n\n-   [#1951](https://github.com/sanic-org/sanic/pull/1951)\n    Documentation improvements\n-   [#1983](https://github.com/sanic-org/sanic/pull/1983) Remove\n    duplicate contents in testing.rst\n-   [#1984](https://github.com/sanic-org/sanic/pull/1984) Fix typo in\n    routing.rst\n\n## Version 20.9.1\n\n**Bugfixes**\n\n-   [#1954](https://github.com/sanic-org/sanic/pull/1954) Fix static\n    route registration on blueprints\n-   [#1957](https://github.com/sanic-org/sanic/pull/1957) Removes\n    duplicate headers in ASGI streaming body\n\n## Version 19.12.3\n\n**Bugfixes**\n\n-   [#1959](https://github.com/sanic-org/sanic/pull/1959) Removes\n    duplicate headers in ASGI streaming body\n\n## Version 20.9.0\n\n**Features**\n\n-   [#1887](https://github.com/sanic-org/sanic/pull/1887) Pass\n    subprotocols in websockets (both sanic server and ASGI)\n-   [#1894](https://github.com/sanic-org/sanic/pull/1894)\n    Automatically set `test_mode` flag on app instance\n-   [#1903](https://github.com/sanic-org/sanic/pull/1903) Add new\n    unified method for updating app values\n-   [#1906](https://github.com/sanic-org/sanic/pull/1906),\n    [#1909](https://github.com/sanic-org/sanic/pull/1909) Adds\n    WEBSOCKET_PING_TIMEOUT and WEBSOCKET_PING_INTERVAL configuration\n    values\n-   [#1935](https://github.com/sanic-org/sanic/pull/1935) httpx\n    version dependency updated, it is slated for removal as a\n    dependency in v20.12\n-   [#1937](https://github.com/sanic-org/sanic/pull/1937) Added auto,\n    text, and json fallback error handlers (in v21.3, the default will\n    change form html to auto)\n\n**Bugfixes**\n\n-   [#1897](https://github.com/sanic-org/sanic/pull/1897) Resolves\n    exception from unread bytes in stream\n\n**Deprecations and Removals**\n\n-   [#1903](https://github.com/sanic-org/sanic/pull/1903)\n    config.from_envar, config.from_pyfile, and config.from_object are\n    deprecated and set to be removed in v21.3\n\n**Developer infrastructure**\n\n-   [#1890](https://github.com/sanic-org/sanic/pull/1890),\n    [#1891](https://github.com/sanic-org/sanic/pull/1891) Update isort\n    calls to be compatible with new API\n-   [#1893](https://github.com/sanic-org/sanic/pull/1893) Remove\n    version section from setup.cfg\n-   [#1924](https://github.com/sanic-org/sanic/pull/1924) Adding\n    \\--strict-markers for pytest\n\n**Improved Documentation**\n\n-   [#1922](https://github.com/sanic-org/sanic/pull/1922) Add explicit\n    ASGI compliance to the README\n\n## Version 20.6.3\n\n**Bugfixes**\n\n-   [#1884](https://github.com/sanic-org/sanic/pull/1884) Revert\n    change to multiprocessing mode\n\n## Version 20.6.2\n\n**Features**\n\n-   [#1641](https://github.com/sanic-org/sanic/pull/1641) Socket\n    binding implemented properly for IPv6 and UNIX sockets\n\n## Version 20.6.1\n\n**Features**\n\n-   [#1760](https://github.com/sanic-org/sanic/pull/1760) Add version\n    parameter to websocket routes\n-   [#1866](https://github.com/sanic-org/sanic/pull/1866) Add `sanic`\n    as an entry point command\n-   [#1880](https://github.com/sanic-org/sanic/pull/1880) Add handler\n    names for websockets for url_for usage\n\n**Bugfixes**\n\n-   [#1776](https://github.com/sanic-org/sanic/pull/1776) Bug fix for\n    host parameter issue with lists\n-   [#1842](https://github.com/sanic-org/sanic/pull/1842) Fix static\n    \\_handler pickling error\n-   [#1827](https://github.com/sanic-org/sanic/pull/1827) Fix reloader\n    on OSX py38 and Windows\n-   [#1848](https://github.com/sanic-org/sanic/pull/1848) Reverse\n    named_response_middlware execution order, to match normal response\n    middleware execution order\n-   [#1853](https://github.com/sanic-org/sanic/pull/1853) Fix pickle\n    error when attempting to pickle an application which contains\n    websocket routes\n\n**Deprecations and Removals**\n\n-   [#1739](https://github.com/sanic-org/sanic/pull/1739) Deprecate\n    body_bytes to merge into body\n\n**Developer infrastructure**\n\n-   [#1852](https://github.com/sanic-org/sanic/pull/1852) Fix naming\n    of CI test env on Python nightlies\n-   [#1857](https://github.com/sanic-org/sanic/pull/1857) Adjust\n    websockets version to setup.py\n-   [#1869](https://github.com/sanic-org/sanic/pull/1869) Wrap\n    run()\\'s \\\"protocol\\\" type annotation in Optional\\[\\]\n\n**Improved Documentation**\n\n-   [#1846](https://github.com/sanic-org/sanic/pull/1846) Update docs\n    to clarify response middleware execution order\n-   [#1865](https://github.com/sanic-org/sanic/pull/1865) Fixing rst\n    format issue that was hiding documentation\n\n## Version 20.6.0\n\n*Released, but unintentionally omitting PR #1880, so was replaced by\n20.6.1*\n\n## Version 20.3.0\n\n**Features**\n\n-   [#1762](https://github.com/sanic-org/sanic/pull/1762) Add\n    `srv.start_serving()` and `srv.serve_forever()` to `AsyncioServer`\n-   [#1767](https://github.com/sanic-org/sanic/pull/1767) Make Sanic\n    usable on `hypercorn -k trio myweb.app`\n-   [#1768](https://github.com/sanic-org/sanic/pull/1768) No\n    tracebacks on normal errors and prettier error pages\n-   [#1769](https://github.com/sanic-org/sanic/pull/1769) Code cleanup\n    in file responses\n-   [#1793](https://github.com/sanic-org/sanic/pull/1793) and\n    [#1819](https://github.com/sanic-org/sanic/pull/1819) Upgrade\n    `str.format()` to f-strings\n-   [#1798](https://github.com/sanic-org/sanic/pull/1798) Allow\n    multiple workers on MacOS with Python 3.8\n-   [#1820](https://github.com/sanic-org/sanic/pull/1820) Do not set\n    content-type and content-length headers in exceptions\n\n**Bugfixes**\n\n-   [#1748](https://github.com/sanic-org/sanic/pull/1748) Remove loop\n    argument in `asyncio.Event` in Python 3.8\n-   [#1764](https://github.com/sanic-org/sanic/pull/1764) Allow route\n    decorators to stack up again\n-   [#1789](https://github.com/sanic-org/sanic/pull/1789) Fix tests\n    using hosts yielding incorrect `url_for`\n-   [#1808](https://github.com/sanic-org/sanic/pull/1808) Fix Ctrl+C\n    and tests on Windows\n\n**Deprecations and Removals**\n\n-   [#1800](https://github.com/sanic-org/sanic/pull/1800) Begin\n    deprecation in way of first-class streaming, removal of\n    `body_init`, `body_push`, and `body_finish`\n-   [#1801](https://github.com/sanic-org/sanic/pull/1801) Complete\n    deprecation from\n    [#1666](https://github.com/sanic-org/sanic/pull/1666) of\n    dictionary context on `request` objects.\n-   [#1807](https://github.com/sanic-org/sanic/pull/1807) Remove\n    server config args that can be read directly from app\n-   [#1818](https://github.com/sanic-org/sanic/pull/1818) Complete\n    deprecation of `app.remove_route` and `request.raw_args`\n\n**Dependencies**\n\n-   [#1794](https://github.com/sanic-org/sanic/pull/1794) Bump `httpx`\n    to 0.11.1\n-   [#1806](https://github.com/sanic-org/sanic/pull/1806) Import\n    `ASGIDispatch` from top-level `httpx` (from third-party\n    deprecation)\n\n**Developer infrastructure**\n\n-   [#1833](https://github.com/sanic-org/sanic/pull/1833) Resolve\n    broken documentation builds\n\n**Improved Documentation**\n\n-   [#1755](https://github.com/sanic-org/sanic/pull/1755) Usage of\n    `response.empty()`\n-   [#1778](https://github.com/sanic-org/sanic/pull/1778) Update\n    README\n-   [#1783](https://github.com/sanic-org/sanic/pull/1783) Fix typo\n-   [#1784](https://github.com/sanic-org/sanic/pull/1784) Corrected\n    changelog for docs move of MD to RST\n    ([#1691](https://github.com/sanic-org/sanic/pull/1691))\n-   [#1803](https://github.com/sanic-org/sanic/pull/1803) Update\n    config docs to match DEFAULT_CONFIG\n-   [#1814](https://github.com/sanic-org/sanic/pull/1814) Update\n    getting_started.rst\n-   [#1821](https://github.com/sanic-org/sanic/pull/1821) Update to\n    deployment\n-   [#1822](https://github.com/sanic-org/sanic/pull/1822) Update docs\n    with changes done in 20.3\n-   [#1834](https://github.com/sanic-org/sanic/pull/1834) Order of\n    listeners\n\n## Version 19.12.0\n\n**Bugfixes**\n\n-   Fix blueprint middleware application\n\n    Currently, any blueprint middleware registered, irrespective of\n    which blueprint was used to do so, was being applied to all of the\n    routes created by the `@app` and `@blueprint` alike.\n\n    As part of this change, the blueprint based middleware application\n    is enforced based on where they are registered.\n\n    -   If you register a middleware via `@blueprint.middleware` then it\n        will apply only to the routes defined by the blueprint.\n    -   If you register a middleware via `@blueprint_group.middleware`\n        then it will apply to all blueprint based routes that are part\n        of the group.\n    -   If you define a middleware via `@app.middleware` then it will be\n        applied on all available routes\n        ([#37](https://github.com/sanic-org/sanic/issues/37))\n\n-   Fix [url_for]{.title-ref} behavior with missing SERVER_NAME\n\n    If the [SERVER_NAME]{.title-ref} was missing in the\n    [app.config]{.title-ref} entity, the [url_for]{.title-ref} on the\n    [request]{.title-ref} and [app]{.title-ref} were failing due to an\n    [AttributeError]{.title-ref}. This fix makes the availability of\n    [SERVER_NAME]{.title-ref} on our [app.config]{.title-ref} an\n    optional behavior.\n    ([#1707](https://github.com/sanic-org/sanic/issues/1707))\n\n**Improved Documentation**\n\n-   Move docs from MD to RST\n\n    Moved all docs from markdown to restructured text like the rest of\n    the docs to unify the scheme and make it easier in the future to\n    update documentation.\n    ([#1691](https://github.com/sanic-org/sanic/issues/1691))\n\n-   Fix documentation for [get]{.title-ref} and [getlist]{.title-ref} of\n    the [request.args]{.title-ref}\n\n    Add additional example for showing the usage of\n    [getlist]{.title-ref} and fix the documentation string for\n    [request.args]{.title-ref} behavior\n    ([#1704](https://github.com/sanic-org/sanic/issues/1704))\n\n## Version 19.6.3\n\n**Features**\n\n-   Enable Towncrier Support\n\n    As part of this feature, [towncrier]{.title-ref} is being introduced\n    as a mechanism to partially automate the process of generating and\n    managing change logs as part of each of pull requests.\n    ([#1631](https://github.com/sanic-org/sanic/issues/1631))\n\n**Improved Documentation**\n\n-   Documentation infrastructure changes\n    -   Enable having a single common [CHANGELOG]{.title-ref} file for\n        both GitHub page and documentation\n    -   Fix Sphinix deprecation warnings\n    -   Fix documentation warnings due to invalid [rst]{.title-ref}\n        indentation\n    -   Enable common contribution guidelines file across GitHub and\n        documentation via [CONTRIBUTING.rst]{.title-ref}\n        ([#1631](https://github.com/sanic-org/sanic/issues/1631))\n\n## Version 19.6.2\n\n**Features**\n\n-   [#1562](https://github.com/sanic-org/sanic/pull/1562) Remove\n    `aiohttp` dependency and create new `SanicTestClient` based upon\n    [requests-async](https://github.com/encode/requests-async)\n-   [#1475](https://github.com/sanic-org/sanic/pull/1475) Added ASGI\n    support (Beta)\n-   [#1436](https://github.com/sanic-org/sanic/pull/1436) Add\n    Configure support from object string\n\n**Bugfixes**\n\n-   [#1587](https://github.com/sanic-org/sanic/pull/1587) Add missing\n    handle for Expect header.\n-   [#1560](https://github.com/sanic-org/sanic/pull/1560) Allow to\n    disable Transfer-Encoding: chunked.\n-   [#1558](https://github.com/sanic-org/sanic/pull/1558) Fix graceful\n    shutdown.\n-   [#1594](https://github.com/sanic-org/sanic/pull/1594) Strict\n    Slashes behavior fix\n\n**Deprecations and Removals**\n\n-   [#1544](https://github.com/sanic-org/sanic/pull/1544) Drop\n    dependency on distutil\n-   [#1562](https://github.com/sanic-org/sanic/pull/1562) Drop support\n    for Python 3.5\n-   [#1568](https://github.com/sanic-org/sanic/pull/1568) Deprecate\n    route removal.\n\n.. warning:: Warning\n\n    Sanic will not support Python 3.5 from version 19.6 and forward. \n    However, version 18.12LTS will have its support period extended thru\n    December 2020, and therefore passing Python\\'s official support version\n    3.5, which is set to expire in September 2020.\n\n## Version 19.3\n\n**Features**\n\n-   [#1497](https://github.com/sanic-org/sanic/pull/1497) Add support\n    for zero-length and RFC 5987 encoded filename for\n    multipart/form-data requests.\n\n-   [#1484](https://github.com/sanic-org/sanic/pull/1484) The type of\n    `expires` attribute of `sanic.cookies.Cookie` is now enforced to\n    be of type `datetime`.\n\n-   [#1482](https://github.com/sanic-org/sanic/pull/1482) Add support\n    for the `stream` parameter of `sanic.Sanic.add_route()` available\n    to `sanic.Blueprint.add_route()`.\n\n-   [#1481](https://github.com/sanic-org/sanic/pull/1481) Accept\n    negative values for route parameters with type `int` or `number`.\n\n-   [#1476](https://github.com/sanic-org/sanic/pull/1476) Deprecated\n    the use of `sanic.request.Request.raw_args` - it has a fundamental\n    flaw in which is drops repeated query string parameters. Added\n    `sanic.request.Request.query_args` as a replacement for the\n    original use-case.\n\n-   [#1472](https://github.com/sanic-org/sanic/pull/1472) Remove an\n    unwanted `None` check in Request class `repr` implementation. This\n    changes the default `repr` of a Request from `<Request>` to\n    `<Request: None />`\n\n-   [#1470](https://github.com/sanic-org/sanic/pull/1470) Added 2 new\n    parameters to `sanic.app.Sanic.create_server`:\n\n    -   `return_asyncio_server` - whether to return an\n        asyncio.Server.\n    -   `asyncio_server_kwargs` - kwargs to pass to\n        `loop.create_server` for the event loop that sanic is using.\n    >\n    This is a breaking change.\n\n-   [#1499](https://github.com/sanic-org/sanic/pull/1499) Added a set\n    of test cases that test and benchmark route resolution.\n\n-   [#1457](https://github.com/sanic-org/sanic/pull/1457) The type of\n    the `\"max-age\"` value in a `sanic.cookies.Cookie` is now enforced\n    to be an integer. Non-integer values are replaced with `0`.\n\n-   [#1445](https://github.com/sanic-org/sanic/pull/1445) Added the\n    `endpoint` attribute to an incoming `request`, containing the name\n    of the handler function.\n\n-   [#1423](https://github.com/sanic-org/sanic/pull/1423) Improved\n    request streaming. `request.stream` is now a bounded-size buffer\n    instead of an unbounded queue. Callers must now call\n    `await request.stream.read()` instead of\n    `await request.stream.get()` to read each portion of the body.\n\n    This is a breaking change.\n\n**Bugfixes**\n\n-   [#1502](https://github.com/sanic-org/sanic/pull/1502) Sanic was\n    prefetching `time.time()` and updating it once per second to avoid\n    excessive `time.time()` calls. The implementation was observed to\n    cause memory leaks in some cases. The benefit of the prefetch\n    appeared to negligible, so this has been removed. Fixes\n    [#1500](https://github.com/sanic-org/sanic/pull/1500)\n-   [#1501](https://github.com/sanic-org/sanic/pull/1501) Fix a bug in\n    the auto-reloader when the process was launched as a module i.e.\n    `python -m init0.mod1` where the sanic server is started in\n    `init0/mod1.py` with `debug` enabled and imports another module in\n    `init0`.\n-   [#1376](https://github.com/sanic-org/sanic/pull/1376) Allow sanic\n    test client to bind to a random port by specifying `port=None`\n    when constructing a `SanicTestClient`\n-   [#1399](https://github.com/sanic-org/sanic/pull/1399) Added the\n    ability to specify middleware on a blueprint group, so that all\n    routes produced from the blueprints in the group have the\n    middleware applied.\n-   [#1442](https://github.com/sanic-org/sanic/pull/1442) Allow the\n    the use the `SANIC_ACCESS_LOG` environment variable to\n    enable/disable the access log when not explicitly passed to\n    `app.run()`. This allows the access log to be disabled for example\n    when running via gunicorn.\n\n**Developer infrastructure**\n\n-   [#1529](https://github.com/sanic-org/sanic/pull/1529) Update\n    project PyPI credentials\n-   [#1515](https://github.com/sanic-org/sanic/pull/1515) fix linter\n    issue causing travis build failures (fix #1514)\n-   [#1490](https://github.com/sanic-org/sanic/pull/1490) Fix python\n    version in doc build\n-   [#1478](https://github.com/sanic-org/sanic/pull/1478) Upgrade\n    setuptools version and use native docutils in doc build\n-   [#1464](https://github.com/sanic-org/sanic/pull/1464) Upgrade\n    pytest, and fix caplog unit tests\n\n**Improved Documentation**\n\n-   [#1516](https://github.com/sanic-org/sanic/pull/1516) Fix typo at\n    the exception documentation\n-   [#1510](https://github.com/sanic-org/sanic/pull/1510) fix typo in\n    Asyncio example\n-   [#1486](https://github.com/sanic-org/sanic/pull/1486)\n    Documentation typo\n-   [#1477](https://github.com/sanic-org/sanic/pull/1477) Fix grammar\n    in README.md\n-   [#1489](https://github.com/sanic-org/sanic/pull/1489) Added\n    \\\"databases\\\" to the extensions list\n-   [#1483](https://github.com/sanic-org/sanic/pull/1483) Add\n    sanic-zipkin to extensions list\n-   [#1487](https://github.com/sanic-org/sanic/pull/1487) Removed link\n    to deleted repo, Sanic-OAuth, from the extensions list\n-   [#1460](https://github.com/sanic-org/sanic/pull/1460) 18.12\n    changelog\n-   [#1449](https://github.com/sanic-org/sanic/pull/1449) Add example\n    of amending request object\n-   [#1446](https://github.com/sanic-org/sanic/pull/1446) Update\n    README\n-   [#1444](https://github.com/sanic-org/sanic/pull/1444) Update\n    README\n-   [#1443](https://github.com/sanic-org/sanic/pull/1443) Update\n    README, including new logo\n-   [#1440](https://github.com/sanic-org/sanic/pull/1440) fix minor\n    type and pip install instruction mismatch\n-   [#1424](https://github.com/sanic-org/sanic/pull/1424)\n    Documentation Enhancements\n\nNote: 19.3.0 was skipped for packagement purposes and not released on\nPyPI\n\n## Version 18.12\n\n### 18.12.0\n\n-   Changes:\n\n    -   Improved codebase test coverage from 81% to 91%.\n    -   Added stream_large_files and host examples in static_file\n        document\n    -   Added methods to append and finish body content on Request\n        (#1379)\n    -   Integrated with .appveyor.yml for windows ci support\n    -   Added documentation for AF_INET6 and AF_UNIX socket usage\n    -   Adopt black/isort for codestyle\n    -   Cancel task when connection_lost\n    -   Simplify request ip and port retrieval logic\n    -   Handle config error in load config file.\n    -   Integrate with codecov for CI\n    -   Add missed documentation for config section.\n    -   Deprecate Handler.log\n    -   Pinned httptools requirement to version 0.0.10+\n\n-   Fixes:\n\n    -   Fix `remove_entity_headers` helper function (#1415)\n    -   Fix TypeError when use Blueprint.group() to group blueprint\n        with default url_prefix, Use os.path.normpath to avoid invalid\n        url_prefix like api//v1 f8a6af1 Rename the `http` module to\n        `helpers` to prevent conflicts with the built-in Python http\n        library (fixes #1323)\n    -   Fix unittests on windows\n    -   Fix Namespacing of sanic logger\n    -   Fix missing quotes in decorator example\n    -   Fix redirect with quoted param\n    -   Fix doc for latest blueprint code\n    -   Fix build of latex documentation relating to markdown lists\n    -   Fix loop exception handling in app.py\n    -   Fix content length mismatch in windows and other platform\n    -   Fix Range header handling for static files (#1402)\n    -   Fix the logger and make it work (#1397)\n    -   Fix type pikcle-\\>pickle in multiprocessing test\n    -   Fix pickling blueprints Change the string passed in the\n        \\\"name\\\" section of the namedtuples in Blueprint to match the\n        name of the Blueprint module attribute name. This allows\n        blueprints to be pickled and unpickled, without errors, which\n        is a requirement of running Sanic in multiprocessing mode in\n        Windows. Added a test for pickling and unpickling blueprints\n        Added a test for pickling and unpickling sanic itself Added a\n        test for enabling multiprocessing on an app with a blueprint\n        (only useful to catch this bug if the tests are run on\n        Windows).\n    -   Fix document for logging\n\n## Version 0.8\n\n**0.8.3**\n\n-   Changes:\n    -   Ownership changed to org \\'sanic-org\\'\n\n**0.8.0**\n\n-   Changes:\n    -   Add Server-Sent Events extension (Innokenty Lebedev)\n    -   Graceful handling of request_handler_task cancellation (Ashley\n        Sommer)\n    -   Sanitize URL before redirection (aveao)\n    -   Add url_bytes to request (johndoe46)\n    -   py37 support for travisci (yunstanford)\n    -   Auto reloader support for OSX (garyo)\n    -   Add UUID route support (Volodymyr Maksymiv)\n    -   Add pausable response streams (Ashley Sommer)\n    -   Add weakref to request slots (vopankov)\n    -   remove ubuntu 12.04 from test fixture due to deprecation\n        (yunstanford)\n    -   Allow streaming handlers in add_route (kinware)\n    -   use travis_retry for tox (Raphael Deem)\n    -   update aiohttp version for test client (yunstanford)\n    -   add redirect import for clarity (yingshaoxo)\n    -   Update HTTP Entity headers (Arnulfo Solís)\n    -   Add register_listener method (Stephan Fitzpatrick)\n    -   Remove uvloop/ujson dependencies for Windows (abuckenheimer)\n    -   Content-length header on 204/304 responses (Arnulfo Solís)\n    -   Extend WebSocketProtocol arguments and add docs (Bob Olde\n        Hampsink, yunstanford)\n    -   Update development status from pre-alpha to beta (Maksim\n        Anisenkov)\n    -   KeepAlive Timeout log level changed to debug (Arnulfo Solís)\n    -   Pin pytest to 3.3.2 because of pytest-dev/pytest#3170 (Maksim\n        Aniskenov)\n    -   Install Python 3.5 and 3.6 on docker container for tests (Shahin\n        Azad)\n    -   Add support for blueprint groups and nesting (Elias Tarhini)\n    -   Remove uvloop for windows setup (Aleksandr Kurlov)\n    -   Auto Reload (Yaser Amari)\n    -   Documentation updates/fixups (multiple contributors)\n-   Fixes:\n    -   Fix: auto_reload in Linux (Ashley Sommer)\n    -   Fix: broken tests for aiohttp \\>= 3.3.0 (Ashley Sommer)\n    -   Fix: disable auto_reload by default on windows (abuckenheimer)\n    -   Fix (1143): Turn off access log with gunicorn (hqy)\n    -   Fix (1268): Support status code for file response (Cosmo Borsky)\n    -   Fix (1266): Add content_type flag to Sanic.static (Cosmo Borsky)\n    -   Fix: subprotocols parameter missing from add_websocket_route\n        (ciscorn)\n    -   Fix (1242): Responses for CI header (yunstanford)\n    -   Fix (1237): add version constraint for websockets (yunstanford)\n    -   Fix (1231): memory leak - always release resource (Phillip Xu)\n    -   Fix (1221): make request truthy if transport exists (Raphael\n        Deem)\n    -   Fix failing tests for aiohttp\\>=3.1.0 (Ashley Sommer)\n    -   Fix try_everything examples (PyManiacGR, kot83)\n    -   Fix (1158): default to auto_reload in debug mode (Raphael Deem)\n    -   Fix (1136): ErrorHandler.response handler call too restrictive\n        (Julien Castiaux)\n    -   Fix: raw requires bytes-like object (cloudship)\n    -   Fix (1120): passing a list in to a route decorator\\'s host arg\n        (Timothy Ebiuwhe)\n    -   Fix: Bug in multipart/form-data parser (DirkGuijt)\n    -   Fix: Exception for missing parameter when value is null\n        (NyanKiyoshi)\n    -   Fix: Parameter check (Howie Hu)\n    -   Fix (1089): Routing issue with named parameters and different\n        methods (yunstanford)\n    -   Fix (1085): Signal handling in multi-worker mode (yunstanford)\n    -   Fix: single quote in readme.rst (Cosven)\n    -   Fix: method typos (Dmitry Dygalo)\n    -   Fix: log_response correct output for ip and port (Wibowo\n        Arindrarto)\n    -   Fix (1042): Exception Handling (Raphael Deem)\n    -   Fix: Chinese URIs (Howie Hu)\n    -   Fix (1079): timeout bug when self.transport is None (Raphael\n        Deem)\n    -   Fix (1074): fix strict_slashes when route has slash (Raphael\n        Deem)\n    -   Fix (1050): add samesite cookie to cookie keys (Raphael Deem)\n    -   Fix (1065): allow add_task after server starts (Raphael Deem)\n    -   Fix (1061): double quotes in unauthorized exception (Raphael\n        Deem)\n    -   Fix (1062): inject the app in add_task method (Raphael Deem)\n    -   Fix: update environment.yml for readthedocs (Eli Uriegas)\n    -   Fix: Cancel request task when response timeout is triggered\n        (Jeong YunWon)\n    -   Fix (1052): Method not allowed response for RFC7231 compliance\n        (Raphael Deem)\n    -   Fix: IPv6 Address and Socket Data Format (Dan Palmer)\n\nNote: Changelog was unmaintained between 0.1 and 0.7\n\n## Version 0.1\n\n**0.1.7**\n\n-   Reversed static url and directory arguments to meet spec\n\n**0.1.6**\n\n-   Static files\n-   Lazy Cookie Loading\n\n**0.1.5**\n\n-   Cookies\n-   Blueprint listeners and ordering\n-   Faster Router\n-   Fix: Incomplete file reads on medium+ sized post requests\n-   Breaking: after_start and before_stop now pass sanic as their\n    first argument\n\n**0.1.4**\n\n-   Multiprocessing\n\n**0.1.3**\n\n-   Blueprint support\n-   Faster Response processing\n\n**0.1.1 - 0.1.2**\n\n-   Struggling to update pypi via CI\n\n**0.1.0**\n\n-   Released to public\n"
  },
  {
    "path": "guide/public/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "guide/public/assets/code.css",
    "content": "pre { line-height: 125%; }\ntd.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }\nspan.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }\ntd.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }\nspan.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }\n.hll { background-color: #ffffcc }\n.c { color: #A2A2A2; font-style: italic } /* Comment */\n.err { color: #777 } /* Error */\n.esc { color: #777 } /* Escape */\n.g { color: #777 } /* Generic */\n.k { color: #FF0D68 } /* Keyword */\n.l { color: #777 } /* Literal */\n.n { color: #333 } /* Name */\n.o { color: #777 } /* Operator */\n.x { color: #777 } /* Other */\n.p { color: #777 } /* Punctuation */\n.ch { color: #A2A2A2; font-style: italic } /* Comment.Hashbang */\n.cm { color: #A2A2A2; font-style: italic } /* Comment.Multiline */\n.cp { color: #A2A2A2; font-style: italic } /* Comment.Preproc */\n.cpf { color: #A2A2A2; font-style: italic } /* Comment.PreprocFile */\n.c1 { color: #A2A2A2; font-style: italic } /* Comment.Single */\n.cs { color: #A2A2A2; font-style: italic } /* Comment.Special */\n.gd { color: #777 } /* Generic.Deleted */\n.ge { color: #777 } /* Generic.Emph */\n.ges { color: #777 } /* Generic.EmphStrong */\n.gr { color: #777 } /* Generic.Error */\n.gh { color: #777 } /* Generic.Heading */\n.gi { color: #777 } /* Generic.Inserted */\n.go { color: #777 } /* Generic.Output */\n.gp { color: #777 } /* Generic.Prompt */\n.gs { color: #777 } /* Generic.Strong */\n.gu { color: #777 } /* Generic.Subheading */\n.gt { color: #777 } /* Generic.Traceback */\n.kc { color: #FF0D68 } /* Keyword.Constant */\n.kd { color: #FF0D68 } /* Keyword.Declaration */\n.kn { color: #FF0D68 } /* Keyword.Namespace */\n.kp { color: #FF0D68 } /* Keyword.Pseudo */\n.kr { color: #FF0D68 } /* Keyword.Reserved */\n.kt { color: #FF0D68 } /* Keyword.Type */\n.ld { color: #777 } /* Literal.Date */\n.m { color: #777 } /* Literal.Number */\n.s { color: #833FE3; background-color: #EEE } /* Literal.String */\n.na { color: #333 } /* Name.Attribute */\n.nb { color: #333 } /* Name.Builtin */\n.nc { color: #37AE6F; font-weight: bold } /* Name.Class */\n.no { color: #333 } /* Name.Constant */\n.nd { color: #333 } /* Name.Decorator */\n.ni { color: #333 } /* Name.Entity */\n.ne { color: #333 } /* Name.Exception */\n.nf { color: #0092FF } /* Name.Function */\n.nl { color: #333 } /* Name.Label */\n.nn { color: #333 } /* Name.Namespace */\n.nx { color: #333 } /* Name.Other */\n.py { color: #333 } /* Name.Property */\n.nt { color: #333 } /* Name.Tag */\n.nv { color: #333 } /* Name.Variable */\n.ow { color: #777 } /* Operator.Word */\n.pm { color: #777 } /* Punctuation.Marker */\n.w { color: #777 } /* Text.Whitespace */\n.mb { color: #777 } /* Literal.Number.Bin */\n.mf { color: #777 } /* Literal.Number.Float */\n.mh { color: #777 } /* Literal.Number.Hex */\n.mi { color: #777 } /* Literal.Number.Integer */\n.mo { color: #777 } /* Literal.Number.Oct */\n.sa { color: #833FE3; background-color: #EEE } /* Literal.String.Affix */\n.sb { color: #833FE3; background-color: #EEE } /* Literal.String.Backtick */\n.sc { color: #833FE3; background-color: #EEE } /* Literal.String.Char */\n.dl { color: #833FE3; background-color: #EEE } /* Literal.String.Delimiter */\n.sd { color: #833FE3; background-color: #EEE } /* Literal.String.Doc */\n.s2 { color: #833FE3; background-color: #EEE } /* Literal.String.Double */\n.se { color: #833FE3; background-color: #EEE } /* Literal.String.Escape */\n.sh { color: #833FE3; background-color: #EEE } /* Literal.String.Heredoc */\n.si { color: #833FE3; background-color: #EEE } /* Literal.String.Interpol */\n.sx { color: #833FE3; background-color: #EEE } /* Literal.String.Other */\n.sr { color: #833FE3; background-color: #EEE } /* Literal.String.Regex */\n.s1 { color: #833FE3; background-color: #EEE } /* Literal.String.Single */\n.ss { color: #833FE3; background-color: #EEE } /* Literal.String.Symbol */\n.bp { color: #333 } /* Name.Builtin.Pseudo */\n.fm { color: #0092FF } /* Name.Function.Magic */\n.vc { color: #333 } /* Name.Variable.Class */\n.vg { color: #333 } /* Name.Variable.Global */\n.vi { color: #333 } /* Name.Variable.Instance */\n.vm { color: #333 } /* Name.Variable.Magic */\n.il { color: #777 } /* Literal.Number.Integer.Long */"
  },
  {
    "path": "guide/public/assets/docs.js",
    "content": "let burger;\nlet menu;\nlet menuLinks;\nlet menuGroups;\nlet anchors;\nlet lastUpdated = 0;\nlet updateFrequency = 300;\nfunction trigger(el, eventType) {\n    if (typeof eventType === \"string\" && typeof el[eventType] === \"function\") {\n        el[eventType]();\n    } else {\n        const event =\n            typeof eventType === \"string\"\n                ? new Event(eventType, { bubbles: true })\n                : eventType;\n        el.dispatchEvent(event);\n    }\n}\nfunction refreshBurger() {\n    burger = document.querySelector(\".burger\");\n}\nfunction refreshMenu() {\n    menu = document.querySelector(\".menu\");\n}\nfunction refreshMenuLinks() {\n    menuLinks = document.querySelectorAll(\".menu a[hx-get]\");\n}\nfunction refreshMenuGroups() {\n    menuGroups = document.querySelectorAll(\".menu li.is-group a:not([href])\");\n}\nfunction hasActiveLink(element) {\n    if (!element) {\n        return false;\n    }\n    let nextElementSibling = element.nextElementSibling;\n    let menuList = null;\n    while (!menuList && nextElementSibling) {\n        if (nextElementSibling.classList.contains(\"menu-list\")) {\n            menuList = nextElementSibling;\n        } else {\n            nextElementSibling = nextElementSibling.nextElementSibling;\n        }\n    }\n    if (menuList) {\n        const siblinkLinks = [...menuList.querySelectorAll(\"a\")];\n        return siblinkLinks.some((el) => el.classList.contains(\"is-active\"));\n    }\n    return false;\n}\nfunction scrollHandler(e) {\n    let now = Date.now();\n    if (now - lastUpdated < updateFrequency) return;\n    \n    let closestAnchor = null;\n    let closestDistance = Infinity;\n\n    if (!anchors) { return; }\n\n    anchors.forEach(anchor => {\n        const rect = anchor.getBoundingClientRect();\n        const distance = Math.abs(rect.top);\n\n        if (distance < closestDistance) {\n            closestDistance = distance;\n            closestAnchor = anchor;\n        }\n    });\n\n    if (closestAnchor) {\n        history.replaceState(null, null, \"#\" + closestAnchor.id);\n\tlastUpdated = now;\n    }\n}\nfunction initBurger() {\n    if (!burger || !menu) {\n        return;\n    }\n    burger.addEventListener(\"click\", (e) => {\n        e.preventDefault();\n        burger.classList.toggle(\"is-active\");\n        menu.classList.toggle(\"is-active\");\n    });\n}\nfunction initMenuGroups() {\n    menuGroups.forEach((el) => {\n        el.addEventListener(\"click\", (e) => {\n            e.preventDefault();\n            menuGroups.forEach((g) => {\n                if (g === el) {\n                    g.classList.toggle(\"is-open\");\n                } else {\n                    g.classList.remove(\"is-open\");\n                }\n            });\n        });\n    });\n}\nfunction initDetails() {\n    const detailsElements = document.querySelectorAll(\".details\");\n    detailsElements.forEach((element) => {\n        element.addEventListener(\"click\", function () {\n            this.classList.toggle(\"is-active\");\n        });\n    });\n}\nfunction initTabs() {\n    const tabsElements = document.querySelectorAll(\".tabs\");\n\n    tabsElements.forEach((element) => {\n        const tabTriggers = element.querySelectorAll(\"li>a\");\n        const tabs = element.querySelectorAll(\"li\");\n        tabTriggers.forEach((trigger) => {\n            trigger.addEventListener(\"click\", function () {\n                tabs.forEach((tab) => {\n                    tab.classList.remove(\"is-active\");\n                });\n                const content = this.nextElementSibling;\n                // this.parentElement.querySelector(\".tab-content\");\n                this.parentElement.classList.add(\"is-active\");\n                const tabDisplay =\n                    this.parentElement.parentElement.parentElement\n                        .nextElementSibling;\n                tabDisplay.innerHTML = content.innerHTML;\n            });\n        });\n        const firstTabTrigger = tabTriggers[0];\n        firstTabTrigger.click();\n    });\n}\nfunction initSearch() {\n    const searchInput = document.querySelector(\"#search\");\n    if (!searchInput) { return; }\n    searchInput.addEventListener(\"keyup\", () => {\n        const value = searchInput.value;\n        searchInput.setAttribute(\n            \"hx-vals\",\n            `{\"q\": \"${encodeURIComponent(value)}\"}`\n        );\n    });\n}\nfunction initMermaid() {\n    const mermaids = document.querySelectorAll(\".mermaid\");\n    mermaid.init(undefined, mermaids);\n}\nfunction refreshAnchors() {\n    anchors = document.querySelectorAll(\"h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]\");\n};\n\nfunction setMenuLinkActive(href) {\n    burger.classList.remove(\"is-active\");\n    menu.classList.remove(\"is-active\");\n    menuLinks.forEach((element) => {\n        if (element.attributes.href.value === href) {\n            element.classList.add(\"is-active\");\n        } else {\n            element.classList.remove(\"is-active\");\n        }\n    });\n    menuGroups.forEach((g) => {\n        if (hasActiveLink(g)) {\n            g.classList.add(\"is-open\");\n        } else {\n            g.classList.remove(\"is-open\");\n        }\n    });\n}\t\nfunction copyCode(button) {\n    const codeBlock = button.parentElement;\n    const code = codeBlock.querySelector(\"code\").innerText;\n    const textarea = document.createElement(\"textarea\");\n    textarea.value = code;\n    document.body.appendChild(textarea);\n    textarea.select();\n    document.execCommand(\"copy\");\n    document.body.removeChild(textarea);\n\n    button.classList.add(\"clicked\"); // Add class for animation\n\n    setTimeout(() => {\n        button.classList.remove(\"clicked\"); // Remove class to revert to original state\n    }, 1500);\n}\nfunction init() {\n    refreshBurger();\n    refreshMenu();\n    refreshMenuLinks();\n    refreshMenuGroups();\n    refreshAnchors();\n    initBurger();\n    initMenuGroups();\n    initDetails();\n    initTabs();\n    initSearch();\n}\n\nfunction afterSwap(e) {\n    setMenuLinkActive(e.detail.pathInfo.requestPath);\n    initMermaid();\n    window.scrollTo(0, 0);\n    const newTitle = event.detail.xhr.getResponseHeader('X-Title');\n    if (newTitle) {\n\tdocument.title = newTitle;\n    }\n}\nfunction beforeRequest() {\n    console.log(\"beforeRequest\")\n    document.querySelector(\".loading-bar\").classList.add(\"is-loading\");\n    this.requestStartTime = new Date().getTime();\n}\nfunction afterRequest() {\n    const currentTime = new Date().getTime();\n    const elapsedTime = currentTime - this.requestStartTime;\n    const delay = Math.max(0, 1000 - elapsedTime);\n    setTimeout(() => {\n\tconsole.log(\"afterRequest\")\n\tdocument.querySelector(\".loading-bar\").classList.remove(\"is-loading\");\n    }, delay);\n}\ndocument.addEventListener(\"DOMContentLoaded\", init);\ndocument.body.addEventListener(\"htmx:afterSwap\", afterSwap);\ndocument.addEventListener(\"scroll\", scrollHandler);\ndocument.body.addEventListener(\"htmx:beforeRequest\", beforeRequest);\ndocument.body.addEventListener(\"htmx:afterRequest\", afterRequest);\n"
  },
  {
    "path": "guide/public/assets/style.css",
    "content": "@charset \"UTF-8\";\n.has-text-purple {\n  color: #833FE3; }\n\n/*! bulma.io v0.9.4 | MIT License | github.com/jgthms/bulma */\n/* Bulma Utilities */\n.pagination-previous,\n.pagination-next,\n.pagination-link,\n.pagination-ellipsis, .file-cta,\n.file-name, .select select, .textarea, .input, .button {\n  -moz-appearance: none;\n  -webkit-appearance: none;\n  align-items: center;\n  border: 1px solid transparent;\n  border-radius: 4px;\n  box-shadow: none;\n  display: inline-flex;\n  font-size: 1rem;\n  height: 2.5em;\n  justify-content: flex-start;\n  line-height: 1.5;\n  padding-bottom: calc(0.5em - 1px);\n  padding-left: calc(0.75em - 1px);\n  padding-right: calc(0.75em - 1px);\n  padding-top: calc(0.5em - 1px);\n  position: relative;\n  vertical-align: top; }\n  .pagination-previous:focus,\n  .pagination-next:focus,\n  .pagination-link:focus,\n  .pagination-ellipsis:focus, .file-cta:focus,\n  .file-name:focus, .select select:focus, .textarea:focus, .input:focus, .button:focus, .is-focused.pagination-previous,\n  .is-focused.pagination-next,\n  .is-focused.pagination-link,\n  .is-focused.pagination-ellipsis, .is-focused.file-cta,\n  .is-focused.file-name, .select select.is-focused, .is-focused.textarea, .is-focused.input, .is-focused.button, .pagination-previous:active,\n  .pagination-next:active,\n  .pagination-link:active,\n  .pagination-ellipsis:active, .file-cta:active,\n  .file-name:active, .select select:active, .textarea:active, .input:active, .button:active, .is-active.pagination-previous,\n  .is-active.pagination-next,\n  .is-active.pagination-link,\n  .is-active.pagination-ellipsis, .is-active.file-cta,\n  .is-active.file-name, .select select.is-active, .is-active.textarea, .is-active.input, .is-active.button {\n    outline: none; }\n  [disabled].pagination-previous,\n  [disabled].pagination-next,\n  [disabled].pagination-link,\n  [disabled].pagination-ellipsis, [disabled].file-cta,\n  [disabled].file-name, .select select[disabled], [disabled].textarea, [disabled].input, [disabled].button, fieldset[disabled] .pagination-previous,\n  fieldset[disabled] .pagination-next,\n  fieldset[disabled] .pagination-link,\n  fieldset[disabled] .pagination-ellipsis, fieldset[disabled] .file-cta,\n  fieldset[disabled] .file-name, fieldset[disabled] .select select, .select fieldset[disabled] select, fieldset[disabled] .textarea, fieldset[disabled] .input, fieldset[disabled] .button {\n    cursor: not-allowed; }\n\n.is-unselectable, .tabs, .pagination-previous,\n.pagination-next,\n.pagination-link,\n.pagination-ellipsis, .breadcrumb, .file, .button {\n  -webkit-touch-callout: none;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none; }\n\n.navbar-link:not(.is-arrowless)::after, .select:not(.is-multiple):not(.is-loading)::after {\n  border: 3px solid transparent;\n  border-radius: 2px;\n  border-right: 0;\n  border-top: 0;\n  content: \" \";\n  display: block;\n  height: 0.625em;\n  margin-top: -0.4375em;\n  pointer-events: none;\n  position: absolute;\n  top: 50%;\n  transform: rotate(-45deg);\n  transform-origin: center;\n  width: 0.625em; }\n\n.tabs:not(:last-child), .pagination:not(:last-child), .message:not(:last-child), .level:not(:last-child), .breadcrumb:not(:last-child), .block:not(:last-child), .title:not(:last-child),\n.subtitle:not(:last-child), .table-container:not(:last-child), .table:not(:last-child), .progress:not(:last-child), .notification:not(:last-child), .content:not(:last-child), .box:not(:last-child) {\n  margin-bottom: 1.5rem; }\n\n.modal-close, .delete {\n  -webkit-touch-callout: none;\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n  -moz-appearance: none;\n  -webkit-appearance: none;\n  background-color: rgba(10, 10, 10, 0.2);\n  border: none;\n  border-radius: 9999px;\n  cursor: pointer;\n  pointer-events: auto;\n  display: inline-block;\n  flex-grow: 0;\n  flex-shrink: 0;\n  font-size: 0;\n  height: 20px;\n  max-height: 20px;\n  max-width: 20px;\n  min-height: 20px;\n  min-width: 20px;\n  outline: none;\n  position: relative;\n  vertical-align: top;\n  width: 20px; }\n  .modal-close::before, .delete::before, .modal-close::after, .delete::after {\n    background-color: white;\n    content: \"\";\n    display: block;\n    left: 50%;\n    position: absolute;\n    top: 50%;\n    transform: translateX(-50%) translateY(-50%) rotate(45deg);\n    transform-origin: center center; }\n  .modal-close::before, .delete::before {\n    height: 2px;\n    width: 50%; }\n  .modal-close::after, .delete::after {\n    height: 50%;\n    width: 2px; }\n  .modal-close:hover, .delete:hover, .modal-close:focus, .delete:focus {\n    background-color: rgba(10, 10, 10, 0.3); }\n  .modal-close:active, .delete:active {\n    background-color: rgba(10, 10, 10, 0.4); }\n  .is-small.modal-close, .is-small.delete {\n    height: 16px;\n    max-height: 16px;\n    max-width: 16px;\n    min-height: 16px;\n    min-width: 16px;\n    width: 16px; }\n  .is-medium.modal-close, .is-medium.delete {\n    height: 24px;\n    max-height: 24px;\n    max-width: 24px;\n    min-height: 24px;\n    min-width: 24px;\n    width: 24px; }\n  .is-large.modal-close, .is-large.delete {\n    height: 32px;\n    max-height: 32px;\n    max-width: 32px;\n    min-height: 32px;\n    min-width: 32px;\n    width: 32px; }\n\n.control.is-loading::after, .select.is-loading::after, .loader, .button.is-loading::after {\n  animation: spinAround 500ms infinite linear;\n  border: 2px solid #dbdbdb;\n  border-radius: 9999px;\n  border-right-color: transparent;\n  border-top-color: transparent;\n  content: \"\";\n  display: block;\n  height: 1em;\n  position: relative;\n  width: 1em; }\n\n.hero-video, .is-overlay, .modal-background, .modal, .image.is-square img,\n.image.is-square .has-ratio, .image.is-1by1 img,\n.image.is-1by1 .has-ratio, .image.is-5by4 img,\n.image.is-5by4 .has-ratio, .image.is-4by3 img,\n.image.is-4by3 .has-ratio, .image.is-3by2 img,\n.image.is-3by2 .has-ratio, .image.is-5by3 img,\n.image.is-5by3 .has-ratio, .image.is-16by9 img,\n.image.is-16by9 .has-ratio, .image.is-2by1 img,\n.image.is-2by1 .has-ratio, .image.is-3by1 img,\n.image.is-3by1 .has-ratio, .image.is-4by5 img,\n.image.is-4by5 .has-ratio, .image.is-3by4 img,\n.image.is-3by4 .has-ratio, .image.is-2by3 img,\n.image.is-2by3 .has-ratio, .image.is-3by5 img,\n.image.is-3by5 .has-ratio, .image.is-9by16 img,\n.image.is-9by16 .has-ratio, .image.is-1by2 img,\n.image.is-1by2 .has-ratio, .image.is-1by3 img,\n.image.is-1by3 .has-ratio {\n  bottom: 0;\n  left: 0;\n  position: absolute;\n  right: 0;\n  top: 0; }\n\n.navbar-burger {\n  -moz-appearance: none;\n  -webkit-appearance: none;\n  appearance: none;\n  background: none;\n  border: none;\n  color: currentColor;\n  font-family: inherit;\n  font-size: 1em;\n  margin: 0;\n  padding: 0; }\n\n/* Bulma Base */\n/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */\nhtml,\nbody,\np,\nol,\nul,\nli,\ndl,\ndt,\ndd,\nblockquote,\nfigure,\nfieldset,\nlegend,\ntextarea,\npre,\niframe,\nhr,\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n  margin: 0;\n  padding: 0; }\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n  font-size: 100%;\n  font-weight: normal; }\n\nul {\n  list-style: none; }\n\nbutton,\ninput,\nselect,\ntextarea {\n  margin: 0; }\n\nhtml {\n  box-sizing: border-box; }\n\n*, *::before, *::after {\n  box-sizing: inherit; }\n\nimg,\nvideo {\n  height: auto;\n  max-width: 100%; }\n\niframe {\n  border: 0; }\n\ntable {\n  border-collapse: collapse;\n  border-spacing: 0; }\n\ntd,\nth {\n  padding: 0; }\n  td:not([align]),\n  th:not([align]) {\n    text-align: inherit; }\n\nhtml {\n  background-color: white;\n  font-size: 20px;\n  -moz-osx-font-smoothing: grayscale;\n  -webkit-font-smoothing: antialiased;\n  min-width: 300px;\n  overflow-x: hidden;\n  overflow-y: scroll;\n  text-rendering: optimizeLegibility;\n  text-size-adjust: 100%; }\n\narticle,\naside,\nfigure,\nfooter,\nheader,\nhgroup,\nsection {\n  display: block; }\n\nbody,\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n  font-family: BlinkMacSystemFont, -apple-system, \"Segoe UI\", \"Roboto\", \"Oxygen\", \"Ubuntu\", \"Cantarell\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\", \"Helvetica\", \"Arial\", sans-serif; }\n\ncode,\npre {\n  -moz-osx-font-smoothing: auto;\n  -webkit-font-smoothing: auto;\n  font-family: monospace; }\n\nbody {\n  color: #4a4a4a;\n  font-size: 1em;\n  font-weight: 400;\n  line-height: 1.5; }\n\na {\n  color: #ff0d68;\n  cursor: pointer;\n  text-decoration: none; }\n  a strong {\n    color: currentColor; }\n  a:hover {\n    color: #363636; }\n\ncode {\n  background-color: whitesmoke;\n  color: #da1039;\n  font-size: 0.875em;\n  font-weight: normal;\n  padding: 0.25em 0.5em 0.25em; }\n\nhr {\n  background-color: whitesmoke;\n  border: none;\n  display: block;\n  height: 2px;\n  margin: 1.5rem 0; }\n\nimg {\n  height: auto;\n  max-width: 100%; }\n\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n  vertical-align: baseline; }\n\nsmall {\n  font-size: 0.875em; }\n\nspan {\n  font-style: inherit;\n  font-weight: inherit; }\n\nstrong {\n  color: #363636;\n  font-weight: 700; }\n\nfieldset {\n  border: none; }\n\npre {\n  -webkit-overflow-scrolling: touch;\n  background-color: whitesmoke;\n  color: #4a4a4a;\n  font-size: 0.875em;\n  overflow-x: auto;\n  padding: 1.25rem 1.5rem;\n  white-space: pre;\n  word-wrap: normal; }\n  pre code {\n    background-color: transparent;\n    color: currentColor;\n    font-size: 1em;\n    padding: 0; }\n\ntable td,\ntable th {\n  vertical-align: top; }\n  table td:not([align]),\n  table th:not([align]) {\n    text-align: inherit; }\n\ntable th {\n  color: #363636; }\n\n@keyframes spinAround {\n  from {\n    transform: rotate(0deg); }\n  to {\n    transform: rotate(359deg); } }\n\n/* Bulma Elements */\n.box {\n  background-color: white;\n  border-radius: 6px;\n  box-shadow: 0 0.5em 1em -0.125em rgba(10, 10, 10, 0.1), 0 0px 0 1px rgba(10, 10, 10, 0.02);\n  color: #4a4a4a;\n  display: block;\n  padding: 1.25rem; }\n\na.box:hover, a.box:focus {\n  box-shadow: 0 0.5em 1em -0.125em rgba(10, 10, 10, 0.1), 0 0 0 1px #ff0d68; }\n\na.box:active {\n  box-shadow: inset 0 1px 2px rgba(10, 10, 10, 0.2), 0 0 0 1px #ff0d68; }\n\n.button {\n  background-color: white;\n  border-color: #dbdbdb;\n  border-width: 1px;\n  color: #363636;\n  cursor: pointer;\n  justify-content: center;\n  padding-bottom: calc(0.5em - 1px);\n  padding-left: 1em;\n  padding-right: 1em;\n  padding-top: calc(0.5em - 1px);\n  text-align: center;\n  white-space: nowrap; }\n  .button strong {\n    color: inherit; }\n  .button .icon, .button .icon.is-small, .button .icon.is-medium, .button .icon.is-large {\n    height: 1.5em;\n    width: 1.5em; }\n  .button .icon:first-child:not(:last-child) {\n    margin-left: calc(-0.5em - 1px);\n    margin-right: 0.25em; }\n  .button .icon:last-child:not(:first-child) {\n    margin-left: 0.25em;\n    margin-right: calc(-0.5em - 1px); }\n  .button .icon:first-child:last-child {\n    margin-left: calc(-0.5em - 1px);\n    margin-right: calc(-0.5em - 1px); }\n  .button:hover, .button.is-hovered {\n    border-color: #b5b5b5;\n    color: #363636; }\n  .button:focus, .button.is-focused {\n    border-color: #0092FF;\n    color: #363636; }\n    .button:focus:not(:active), .button.is-focused:not(:active) {\n      box-shadow: 0 0 0 0.125em rgba(255, 13, 104, 0.25); }\n  .button:active, .button.is-active {\n    border-color: #4a4a4a;\n    color: #363636; }\n  .button.is-text {\n    background-color: transparent;\n    border-color: transparent;\n    color: #4a4a4a;\n    text-decoration: underline; }\n    .button.is-text:hover, .button.is-text.is-hovered, .button.is-text:focus, .button.is-text.is-focused {\n      background-color: whitesmoke;\n      color: #363636; }\n    .button.is-text:active, .button.is-text.is-active {\n      background-color: #e8e8e8;\n      color: #363636; }\n    .button.is-text[disabled], fieldset[disabled] .button.is-text {\n      background-color: transparent;\n      border-color: transparent;\n      box-shadow: none; }\n  .button.is-ghost {\n    background: none;\n    border-color: transparent;\n    color: #ff0d68;\n    text-decoration: none; }\n    .button.is-ghost:hover, .button.is-ghost.is-hovered {\n      color: #ff0d68;\n      text-decoration: underline; }\n  .button.is-white {\n    background-color: white;\n    border-color: transparent;\n    color: #0a0a0a; }\n    .button.is-white:hover, .button.is-white.is-hovered {\n      background-color: #f9f9f9;\n      border-color: transparent;\n      color: #0a0a0a; }\n    .button.is-white:focus, .button.is-white.is-focused {\n      border-color: transparent;\n      color: #0a0a0a; }\n      .button.is-white:focus:not(:active), .button.is-white.is-focused:not(:active) {\n        box-shadow: 0 0 0 0.125em rgba(255, 255, 255, 0.25); }\n    .button.is-white:active, .button.is-white.is-active {\n      background-color: #f2f2f2;\n      border-color: transparent;\n      color: #0a0a0a; }\n    .button.is-white[disabled], fieldset[disabled] .button.is-white {\n      background-color: white;\n      border-color: white;\n      box-shadow: none; }\n    .button.is-white.is-inverted {\n      background-color: #0a0a0a;\n      color: white; }\n      .button.is-white.is-inverted:hover, .button.is-white.is-inverted.is-hovered {\n        background-color: black; }\n      .button.is-white.is-inverted[disabled], fieldset[disabled] .button.is-white.is-inverted {\n        background-color: #0a0a0a;\n        border-color: transparent;\n        box-shadow: none;\n        color: white; }\n    .button.is-white.is-loading::after {\n      border-color: transparent transparent #0a0a0a #0a0a0a !important; }\n    .button.is-white.is-outlined {\n      background-color: transparent;\n      border-color: white;\n      color: white; }\n      .button.is-white.is-outlined:hover, .button.is-white.is-outlined.is-hovered, .button.is-white.is-outlined:focus, .button.is-white.is-outlined.is-focused {\n        background-color: white;\n        border-color: white;\n        color: #0a0a0a; }\n      .button.is-white.is-outlined.is-loading::after {\n        border-color: transparent transparent white white !important; }\n      .button.is-white.is-outlined.is-loading:hover::after, .button.is-white.is-outlined.is-loading.is-hovered::after, .button.is-white.is-outlined.is-loading:focus::after, .button.is-white.is-outlined.is-loading.is-focused::after {\n        border-color: transparent transparent #0a0a0a #0a0a0a !important; }\n      .button.is-white.is-outlined[disabled], fieldset[disabled] .button.is-white.is-outlined {\n        background-color: transparent;\n        border-color: white;\n        box-shadow: none;\n        color: white; }\n    .button.is-white.is-inverted.is-outlined {\n      background-color: transparent;\n      border-color: #0a0a0a;\n      color: #0a0a0a; }\n      .button.is-white.is-inverted.is-outlined:hover, .button.is-white.is-inverted.is-outlined.is-hovered, .button.is-white.is-inverted.is-outlined:focus, .button.is-white.is-inverted.is-outlined.is-focused {\n        background-color: #0a0a0a;\n        color: white; }\n      .button.is-white.is-inverted.is-outlined.is-loading:hover::after, .button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after, .button.is-white.is-inverted.is-outlined.is-loading:focus::after, .button.is-white.is-inverted.is-outlined.is-loading.is-focused::after {\n        border-color: transparent transparent white white !important; }\n      .button.is-white.is-inverted.is-outlined[disabled], fieldset[disabled] .button.is-white.is-inverted.is-outlined {\n        background-color: transparent;\n        border-color: #0a0a0a;\n        box-shadow: none;\n        color: #0a0a0a; }\n  .button.is-black {\n    background-color: #0a0a0a;\n    border-color: transparent;\n    color: white; }\n    .button.is-black:hover, .button.is-black.is-hovered {\n      background-color: #040404;\n      border-color: transparent;\n      color: white; }\n    .button.is-black:focus, .button.is-black.is-focused {\n      border-color: transparent;\n      color: white; }\n      .button.is-black:focus:not(:active), .button.is-black.is-focused:not(:active) {\n        box-shadow: 0 0 0 0.125em rgba(10, 10, 10, 0.25); }\n    .button.is-black:active, .button.is-black.is-active {\n      background-color: black;\n      border-color: transparent;\n      color: white; }\n    .button.is-black[disabled], fieldset[disabled] .button.is-black {\n      background-color: #0a0a0a;\n      border-color: #0a0a0a;\n      box-shadow: none; }\n    .button.is-black.is-inverted {\n      background-color: white;\n      color: #0a0a0a; }\n      .button.is-black.is-inverted:hover, .button.is-black.is-inverted.is-hovered {\n        background-color: #f2f2f2; }\n      .button.is-black.is-inverted[disabled], fieldset[disabled] .button.is-black.is-inverted {\n        background-color: white;\n        border-color: transparent;\n        box-shadow: none;\n        color: #0a0a0a; }\n    .button.is-black.is-loading::after {\n      border-color: transparent transparent white white !important; }\n    .button.is-black.is-outlined {\n      background-color: transparent;\n      border-color: #0a0a0a;\n      color: #0a0a0a; }\n      .button.is-black.is-outlined:hover, .button.is-black.is-outlined.is-hovered, .button.is-black.is-outlined:focus, .button.is-black.is-outlined.is-focused {\n        background-color: #0a0a0a;\n        border-color: #0a0a0a;\n        color: white; }\n      .button.is-black.is-outlined.is-loading::after {\n        border-color: transparent transparent #0a0a0a #0a0a0a !important; }\n      .button.is-black.is-outlined.is-loading:hover::after, .button.is-black.is-outlined.is-loading.is-hovered::after, .button.is-black.is-outlined.is-loading:focus::after, .button.is-black.is-outlined.is-loading.is-focused::after {\n        border-color: transparent transparent white white !important; }\n      .button.is-black.is-outlined[disabled], fieldset[disabled] .button.is-black.is-outlined {\n        background-color: transparent;\n        border-color: #0a0a0a;\n        box-shadow: none;\n        color: #0a0a0a; }\n    .button.is-black.is-inverted.is-outlined {\n      background-color: transparent;\n      border-color: white;\n      color: white; }\n      .button.is-black.is-inverted.is-outlined:hover, .button.is-black.is-inverted.is-outlined.is-hovered, .button.is-black.is-inverted.is-outlined:focus, .button.is-black.is-inverted.is-outlined.is-focused {\n        background-color: white;\n        color: #0a0a0a; }\n      .button.is-black.is-inverted.is-outlined.is-loading:hover::after, .button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after, .button.is-black.is-inverted.is-outlined.is-loading:focus::after, .button.is-black.is-inverted.is-outlined.is-loading.is-focused::after {\n        border-color: transparent transparent #0a0a0a #0a0a0a !important; }\n      .button.is-black.is-inverted.is-outlined[disabled], fieldset[disabled] .button.is-black.is-inverted.is-outlined {\n        background-color: transparent;\n        border-color: white;\n        box-shadow: none;\n        color: white; }\n  .button.is-light {\n    background-color: whitesmoke;\n    border-color: transparent;\n    color: rgba(0, 0, 0, 0.7); }\n    .button.is-light:hover, .button.is-light.is-hovered {\n      background-color: #eeeeee;\n      border-color: transparent;\n      color: rgba(0, 0, 0, 0.7); }\n    .button.is-light:focus, .button.is-light.is-focused {\n      border-color: transparent;\n      color: rgba(0, 0, 0, 0.7); }\n      .button.is-light:focus:not(:active), .button.is-light.is-focused:not(:active) {\n        box-shadow: 0 0 0 0.125em rgba(245, 245, 245, 0.25); }\n    .button.is-light:active, .button.is-light.is-active {\n      background-color: #e8e8e8;\n      border-color: transparent;\n      color: rgba(0, 0, 0, 0.7); }\n    .button.is-light[disabled], fieldset[disabled] .button.is-light {\n      background-color: whitesmoke;\n      border-color: whitesmoke;\n      box-shadow: none; }\n    .button.is-light.is-inverted {\n      background-color: rgba(0, 0, 0, 0.7);\n      color: whitesmoke; }\n      .button.is-light.is-inverted:hover, .button.is-light.is-inverted.is-hovered {\n        background-color: rgba(0, 0, 0, 0.7); }\n      .button.is-light.is-inverted[disabled], fieldset[disabled] .button.is-light.is-inverted {\n        background-color: rgba(0, 0, 0, 0.7);\n        border-color: transparent;\n        box-shadow: none;\n        color: whitesmoke; }\n    .button.is-light.is-loading::after {\n      border-color: transparent transparent rgba(0, 0, 0, 0.7) rgba(0, 0, 0, 0.7) !important; }\n    .button.is-light.is-outlined {\n      background-color: transparent;\n      border-color: whitesmoke;\n      color: whitesmoke; }\n      .button.is-light.is-outlined:hover, .button.is-light.is-outlined.is-hovered, .button.is-light.is-outlined:focus, .button.is-light.is-outlined.is-focused {\n        background-color: whitesmoke;\n        border-color: whitesmoke;\n        color: rgba(0, 0, 0, 0.7); }\n      .button.is-light.is-outlined.is-loading::after {\n        border-color: transparent transparent whitesmoke whitesmoke !important; }\n      .button.is-light.is-outlined.is-loading:hover::after, .button.is-light.is-outlined.is-loading.is-hovered::after, .button.is-light.is-outlined.is-loading:focus::after, .button.is-light.is-outlined.is-loading.is-focused::after {\n        border-color: transparent transparent rgba(0, 0, 0, 0.7) rgba(0, 0, 0, 0.7) !important; }\n      .button.is-light.is-outlined[disabled], fieldset[disabled] .button.is-light.is-outlined {\n        background-color: transparent;\n        border-color: whitesmoke;\n        box-shadow: none;\n        color: whitesmoke; }\n    .button.is-light.is-inverted.is-outlined {\n      background-color: transparent;\n      border-color: rgba(0, 0, 0, 0.7);\n      color: rgba(0, 0, 0, 0.7); }\n      .button.is-light.is-inverted.is-outlined:hover, .button.is-light.is-inverted.is-outlined.is-hovered, .button.is-light.is-inverted.is-outlined:focus, .button.is-light.is-inverted.is-outlined.is-focused {\n        background-color: rgba(0, 0, 0, 0.7);\n        color: whitesmoke; }\n      .button.is-light.is-inverted.is-outlined.is-loading:hover::after, .button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after, .button.is-light.is-inverted.is-outlined.is-loading:focus::after, .button.is-light.is-inverted.is-outlined.is-loading.is-focused::after {\n        border-color: transparent transparent whitesmoke whitesmoke !important; }\n      .button.is-light.is-inverted.is-outlined[disabled], fieldset[disabled] .button.is-light.is-inverted.is-outlined {\n        background-color: transparent;\n        border-color: rgba(0, 0, 0, 0.7);\n        box-shadow: none;\n        color: rgba(0, 0, 0, 0.7); }\n  .button.is-dark {\n    background-color: #363636;\n    border-color: transparent;\n    color: #fff; }\n    .button.is-dark:hover, .button.is-dark.is-hovered {\n      background-color: #2f2f2f;\n      border-color: transparent;\n      color: #fff; }\n    .button.is-dark:focus, .button.is-dark.is-focused {\n      border-color: transparent;\n      color: #fff; }\n      .button.is-dark:focus:not(:active), .button.is-dark.is-focused:not(:active) {\n        box-shadow: 0 0 0 0.125em rgba(54, 54, 54, 0.25); }\n    .button.is-dark:active, .button.is-dark.is-active {\n      background-color: #292929;\n      border-color: transparent;\n      color: #fff; }\n    .button.is-dark[disabled], fieldset[disabled] .button.is-dark {\n      background-color: #363636;\n      border-color: #363636;\n      box-shadow: none; }\n    .button.is-dark.is-inverted {\n      background-color: #fff;\n      color: #363636; }\n      .button.is-dark.is-inverted:hover, .button.is-dark.is-inverted.is-hovered {\n        background-color: #f2f2f2; }\n      .button.is-dark.is-inverted[disabled], fieldset[disabled] .button.is-dark.is-inverted {\n        background-color: #fff;\n        border-color: transparent;\n        box-shadow: none;\n        color: #363636; }\n    .button.is-dark.is-loading::after {\n      border-color: transparent transparent #fff #fff !important; }\n    .button.is-dark.is-outlined {\n      background-color: transparent;\n      border-color: #363636;\n      color: #363636; }\n      .button.is-dark.is-outlined:hover, .button.is-dark.is-outlined.is-hovered, .button.is-dark.is-outlined:focus, .button.is-dark.is-outlined.is-focused {\n        background-color: #363636;\n        border-color: #363636;\n        color: #fff; }\n      .button.is-dark.is-outlined.is-loading::after {\n        border-color: transparent transparent #363636 #363636 !important; }\n      .button.is-dark.is-outlined.is-loading:hover::after, .button.is-dark.is-outlined.is-loading.is-hovered::after, .button.is-dark.is-outlined.is-loading:focus::after, .button.is-dark.is-outlined.is-loading.is-focused::after {\n        border-color: transparent transparent #fff #fff !important; }\n      .button.is-dark.is-outlined[disabled], fieldset[disabled] .button.is-dark.is-outlined {\n        background-color: transparent;\n        border-color: #363636;\n        box-shadow: none;\n        color: #363636; }\n    .button.is-dark.is-inverted.is-outlined {\n      background-color: transparent;\n      border-color: #fff;\n      color: #fff; }\n      .button.is-dark.is-inverted.is-outlined:hover, .button.is-dark.is-inverted.is-outlined.is-hovered, .button.is-dark.is-inverted.is-outlined:focus, .button.is-dark.is-inverted.is-outlined.is-focused {\n        background-color: #fff;\n        color: #363636; }\n      .button.is-dark.is-inverted.is-outlined.is-loading:hover::after, .button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after, .button.is-dark.is-inverted.is-outlined.is-loading:focus::after, .button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after {\n        border-color: transparent transparent #363636 #363636 !important; }\n      .button.is-dark.is-inverted.is-outlined[disabled], fieldset[disabled] .button.is-dark.is-inverted.is-outlined {\n        background-color: transparent;\n        border-color: #fff;\n        box-shadow: none;\n        color: #fff; }\n  .button.is-primary {\n    background-color: #ff0d68;\n    border-color: transparent;\n    color: #fff; }\n    .button.is-primary:hover, .button.is-primary.is-hovered {\n      background-color: #ff0060;\n      border-color: transparent;\n      color: #fff; }\n    .button.is-primary:focus, .button.is-primary.is-focused {\n      border-color: transparent;\n      color: #fff; }\n      .button.is-primary:focus:not(:active), .button.is-primary.is-focused:not(:active) {\n        box-shadow: 0 0 0 0.125em rgba(255, 13, 104, 0.25); }\n    .button.is-primary:active, .button.is-primary.is-active {\n      background-color: #f3005b;\n      border-color: transparent;\n      color: #fff; }\n    .button.is-primary[disabled], fieldset[disabled] .button.is-primary {\n      background-color: #ff0d68;\n      border-color: #ff0d68;\n      box-shadow: none; }\n    .button.is-primary.is-inverted {\n      background-color: #fff;\n      color: #ff0d68; }\n      .button.is-primary.is-inverted:hover, .button.is-primary.is-inverted.is-hovered {\n        background-color: #f2f2f2; }\n      .button.is-primary.is-inverted[disabled], fieldset[disabled] .button.is-primary.is-inverted {\n        background-color: #fff;\n        border-color: transparent;\n        box-shadow: none;\n        color: #ff0d68; }\n    .button.is-primary.is-loading::after {\n      border-color: transparent transparent #fff #fff !important; }\n    .button.is-primary.is-outlined {\n      background-color: transparent;\n      border-color: #ff0d68;\n      color: #ff0d68; }\n      .button.is-primary.is-outlined:hover, .button.is-primary.is-outlined.is-hovered, .button.is-primary.is-outlined:focus, .button.is-primary.is-outlined.is-focused {\n        background-color: #ff0d68;\n        border-color: #ff0d68;\n        color: #fff; }\n      .button.is-primary.is-outlined.is-loading::after {\n        border-color: transparent transparent #ff0d68 #ff0d68 !important; }\n      .button.is-primary.is-outlined.is-loading:hover::after, .button.is-primary.is-outlined.is-loading.is-hovered::after, .button.is-primary.is-outlined.is-loading:focus::after, .button.is-primary.is-outlined.is-loading.is-focused::after {\n        border-color: transparent transparent #fff #fff !important; }\n      .button.is-primary.is-outlined[disabled], fieldset[disabled] .button.is-primary.is-outlined {\n        background-color: transparent;\n        border-color: #ff0d68;\n        box-shadow: none;\n        color: #ff0d68; }\n    .button.is-primary.is-inverted.is-outlined {\n      background-color: transparent;\n      border-color: #fff;\n      color: #fff; }\n      .button.is-primary.is-inverted.is-outlined:hover, .button.is-primary.is-inverted.is-outlined.is-hovered, .button.is-primary.is-inverted.is-outlined:focus, .button.is-primary.is-inverted.is-outlined.is-focused {\n        background-color: #fff;\n        color: #ff0d68; }\n      .button.is-primary.is-inverted.is-outlined.is-loading:hover::after, .button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after, .button.is-primary.is-inverted.is-outlined.is-loading:focus::after, .button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after {\n        border-color: transparent transparent #ff0d68 #ff0d68 !important; }\n      .button.is-primary.is-inverted.is-outlined[disabled], fieldset[disabled] .button.is-primary.is-inverted.is-outlined {\n        background-color: transparent;\n        border-color: #fff;\n        box-shadow: none;\n        color: #fff; }\n    .button.is-primary.is-light {\n      background-color: #ffebf2;\n      color: #e60056; }\n      .button.is-primary.is-light:hover, .button.is-primary.is-light.is-hovered {\n        background-color: #ffdeea;\n        border-color: transparent;\n        color: #e60056; }\n      .button.is-primary.is-light:active, .button.is-primary.is-light.is-active {\n        background-color: #ffd1e2;\n        border-color: transparent;\n        color: #e60056; }\n  .button.is-link {\n    background-color: #ff0d68;\n    border-color: transparent;\n    color: #fff; }\n    .button.is-link:hover, .button.is-link.is-hovered {\n      background-color: #ff0060;\n      border-color: transparent;\n      color: #fff; }\n    .button.is-link:focus, .button.is-link.is-focused {\n      border-color: transparent;\n      color: #fff; }\n      .button.is-link:focus:not(:active), .button.is-link.is-focused:not(:active) {\n        box-shadow: 0 0 0 0.125em rgba(255, 13, 104, 0.25); }\n    .button.is-link:active, .button.is-link.is-active {\n      background-color: #f3005b;\n      border-color: transparent;\n      color: #fff; }\n    .button.is-link[disabled], fieldset[disabled] .button.is-link {\n      background-color: #ff0d68;\n      border-color: #ff0d68;\n      box-shadow: none; }\n    .button.is-link.is-inverted {\n      background-color: #fff;\n      color: #ff0d68; }\n      .button.is-link.is-inverted:hover, .button.is-link.is-inverted.is-hovered {\n        background-color: #f2f2f2; }\n      .button.is-link.is-inverted[disabled], fieldset[disabled] .button.is-link.is-inverted {\n        background-color: #fff;\n        border-color: transparent;\n        box-shadow: none;\n        color: #ff0d68; }\n    .button.is-link.is-loading::after {\n      border-color: transparent transparent #fff #fff !important; }\n    .button.is-link.is-outlined {\n      background-color: transparent;\n      border-color: #ff0d68;\n      color: #ff0d68; }\n      .button.is-link.is-outlined:hover, .button.is-link.is-outlined.is-hovered, .button.is-link.is-outlined:focus, .button.is-link.is-outlined.is-focused {\n        background-color: #ff0d68;\n        border-color: #ff0d68;\n        color: #fff; }\n      .button.is-link.is-outlined.is-loading::after {\n        border-color: transparent transparent #ff0d68 #ff0d68 !important; }\n      .button.is-link.is-outlined.is-loading:hover::after, .button.is-link.is-outlined.is-loading.is-hovered::after, .button.is-link.is-outlined.is-loading:focus::after, .button.is-link.is-outlined.is-loading.is-focused::after {\n        border-color: transparent transparent #fff #fff !important; }\n      .button.is-link.is-outlined[disabled], fieldset[disabled] .button.is-link.is-outlined {\n        background-color: transparent;\n        border-color: #ff0d68;\n        box-shadow: none;\n        color: #ff0d68; }\n    .button.is-link.is-inverted.is-outlined {\n      background-color: transparent;\n      border-color: #fff;\n      color: #fff; }\n      .button.is-link.is-inverted.is-outlined:hover, .button.is-link.is-inverted.is-outlined.is-hovered, .button.is-link.is-inverted.is-outlined:focus, .button.is-link.is-inverted.is-outlined.is-focused {\n        background-color: #fff;\n        color: #ff0d68; }\n      .button.is-link.is-inverted.is-outlined.is-loading:hover::after, .button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after, .button.is-link.is-inverted.is-outlined.is-loading:focus::after, .button.is-link.is-inverted.is-outlined.is-loading.is-focused::after {\n        border-color: transparent transparent #ff0d68 #ff0d68 !important; }\n      .button.is-link.is-inverted.is-outlined[disabled], fieldset[disabled] .button.is-link.is-inverted.is-outlined {\n        background-color: transparent;\n        border-color: #fff;\n        box-shadow: none;\n        color: #fff; }\n    .button.is-link.is-light {\n      background-color: #ffebf2;\n      color: #e60056; }\n      .button.is-link.is-light:hover, .button.is-link.is-light.is-hovered {\n        background-color: #ffdeea;\n        border-color: transparent;\n        color: #e60056; }\n      .button.is-link.is-light:active, .button.is-link.is-light.is-active {\n        background-color: #ffd1e2;\n        border-color: transparent;\n        color: #e60056; }\n  .button.is-info {\n    background-color: #0092FF;\n    border-color: transparent;\n    color: #fff; }\n    .button.is-info:hover, .button.is-info.is-hovered {\n      background-color: #008bf2;\n      border-color: transparent;\n      color: #fff; }\n    .button.is-info:focus, .button.is-info.is-focused {\n      border-color: transparent;\n      color: #fff; }\n      .button.is-info:focus:not(:active), .button.is-info.is-focused:not(:active) {\n        box-shadow: 0 0 0 0.125em rgba(0, 146, 255, 0.25); }\n    .button.is-info:active, .button.is-info.is-active {\n      background-color: #0083e6;\n      border-color: transparent;\n      color: #fff; }\n    .button.is-info[disabled], fieldset[disabled] .button.is-info {\n      background-color: #0092FF;\n      border-color: #0092FF;\n      box-shadow: none; }\n    .button.is-info.is-inverted {\n      background-color: #fff;\n      color: #0092FF; }\n      .button.is-info.is-inverted:hover, .button.is-info.is-inverted.is-hovered {\n        background-color: #f2f2f2; }\n      .button.is-info.is-inverted[disabled], fieldset[disabled] .button.is-info.is-inverted {\n        background-color: #fff;\n        border-color: transparent;\n        box-shadow: none;\n        color: #0092FF; }\n    .button.is-info.is-loading::after {\n      border-color: transparent transparent #fff #fff !important; }\n    .button.is-info.is-outlined {\n      background-color: transparent;\n      border-color: #0092FF;\n      color: #0092FF; }\n      .button.is-info.is-outlined:hover, .button.is-info.is-outlined.is-hovered, .button.is-info.is-outlined:focus, .button.is-info.is-outlined.is-focused {\n        background-color: #0092FF;\n        border-color: #0092FF;\n        color: #fff; }\n      .button.is-info.is-outlined.is-loading::after {\n        border-color: transparent transparent #0092FF #0092FF !important; }\n      .button.is-info.is-outlined.is-loading:hover::after, .button.is-info.is-outlined.is-loading.is-hovered::after, .button.is-info.is-outlined.is-loading:focus::after, .button.is-info.is-outlined.is-loading.is-focused::after {\n        border-color: transparent transparent #fff #fff !important; }\n      .button.is-info.is-outlined[disabled], fieldset[disabled] .button.is-info.is-outlined {\n        background-color: transparent;\n        border-color: #0092FF;\n        box-shadow: none;\n        color: #0092FF; }\n    .button.is-info.is-inverted.is-outlined {\n      background-color: transparent;\n      border-color: #fff;\n      color: #fff; }\n      .button.is-info.is-inverted.is-outlined:hover, .button.is-info.is-inverted.is-outlined.is-hovered, .button.is-info.is-inverted.is-outlined:focus, .button.is-info.is-inverted.is-outlined.is-focused {\n        background-color: #fff;\n        color: #0092FF; }\n      .button.is-info.is-inverted.is-outlined.is-loading:hover::after, .button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after, .button.is-info.is-inverted.is-outlined.is-loading:focus::after, .button.is-info.is-inverted.is-outlined.is-loading.is-focused::after {\n        border-color: transparent transparent #0092FF #0092FF !important; }\n      .button.is-info.is-inverted.is-outlined[disabled], fieldset[disabled] .button.is-info.is-inverted.is-outlined {\n        background-color: transparent;\n        border-color: #fff;\n        box-shadow: none;\n        color: #fff; }\n    .button.is-info.is-light {\n      background-color: #ebf6ff;\n      color: #0075cc; }\n      .button.is-info.is-light:hover, .button.is-info.is-light.is-hovered {\n        background-color: #def1ff;\n        border-color: transparent;\n        color: #0075cc; }\n      .button.is-info.is-light:active, .button.is-info.is-light.is-active {\n        background-color: #d1ebff;\n        border-color: transparent;\n        color: #0075cc; }\n  .button.is-success {\n    background-color: #16DB93;\n    border-color: transparent;\n    color: rgba(0, 0, 0, 0.7); }\n    .button.is-success:hover, .button.is-success.is-hovered {\n      background-color: #15cf8b;\n      border-color: transparent;\n      color: rgba(0, 0, 0, 0.7); }\n    .button.is-success:focus, .button.is-success.is-focused {\n      border-color: transparent;\n      color: rgba(0, 0, 0, 0.7); }\n      .button.is-success:focus:not(:active), .button.is-success.is-focused:not(:active) {\n        box-shadow: 0 0 0 0.125em rgba(22, 219, 147, 0.25); }\n    .button.is-success:active, .button.is-success.is-active {\n      background-color: #14c483;\n      border-color: transparent;\n      color: rgba(0, 0, 0, 0.7); }\n    .button.is-success[disabled], fieldset[disabled] .button.is-success {\n      background-color: #16DB93;\n      border-color: #16DB93;\n      box-shadow: none; }\n    .button.is-success.is-inverted {\n      background-color: rgba(0, 0, 0, 0.7);\n      color: #16DB93; }\n      .button.is-success.is-inverted:hover, .button.is-success.is-inverted.is-hovered {\n        background-color: rgba(0, 0, 0, 0.7); }\n      .button.is-success.is-inverted[disabled], fieldset[disabled] .button.is-success.is-inverted {\n        background-color: rgba(0, 0, 0, 0.7);\n        border-color: transparent;\n        box-shadow: none;\n        color: #16DB93; }\n    .button.is-success.is-loading::after {\n      border-color: transparent transparent rgba(0, 0, 0, 0.7) rgba(0, 0, 0, 0.7) !important; }\n    .button.is-success.is-outlined {\n      background-color: transparent;\n      border-color: #16DB93;\n      color: #16DB93; }\n      .button.is-success.is-outlined:hover, .button.is-success.is-outlined.is-hovered, .button.is-success.is-outlined:focus, .button.is-success.is-outlined.is-focused {\n        background-color: #16DB93;\n        border-color: #16DB93;\n        color: rgba(0, 0, 0, 0.7); }\n      .button.is-success.is-outlined.is-loading::after {\n        border-color: transparent transparent #16DB93 #16DB93 !important; }\n      .button.is-success.is-outlined.is-loading:hover::after, .button.is-success.is-outlined.is-loading.is-hovered::after, .button.is-success.is-outlined.is-loading:focus::after, .button.is-success.is-outlined.is-loading.is-focused::after {\n        border-color: transparent transparent rgba(0, 0, 0, 0.7) rgba(0, 0, 0, 0.7) !important; }\n      .button.is-success.is-outlined[disabled], fieldset[disabled] .button.is-success.is-outlined {\n        background-color: transparent;\n        border-color: #16DB93;\n        box-shadow: none;\n        color: #16DB93; }\n    .button.is-success.is-inverted.is-outlined {\n      background-color: transparent;\n      border-color: rgba(0, 0, 0, 0.7);\n      color: rgba(0, 0, 0, 0.7); }\n      .button.is-success.is-inverted.is-outlined:hover, .button.is-success.is-inverted.is-outlined.is-hovered, .button.is-success.is-inverted.is-outlined:focus, .button.is-success.is-inverted.is-outlined.is-focused {\n        background-color: rgba(0, 0, 0, 0.7);\n        color: #16DB93; }\n      .button.is-success.is-inverted.is-outlined.is-loading:hover::after, .button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after, .button.is-success.is-inverted.is-outlined.is-loading:focus::after, .button.is-success.is-inverted.is-outlined.is-loading.is-focused::after {\n        border-color: transparent transparent #16DB93 #16DB93 !important; }\n      .button.is-success.is-inverted.is-outlined[disabled], fieldset[disabled] .button.is-success.is-inverted.is-outlined {\n        background-color: transparent;\n        border-color: rgba(0, 0, 0, 0.7);\n        box-shadow: none;\n        color: rgba(0, 0, 0, 0.7); }\n    .button.is-success.is-light {\n      background-color: #ecfdf7;\n      color: #0e865a; }\n      .button.is-success.is-light:hover, .button.is-success.is-light.is-hovered {\n        background-color: #e1fcf2;\n        border-color: transparent;\n        color: #0e865a; }\n      .button.is-success.is-light:active, .button.is-success.is-light.is-active {\n        background-color: #d5fbed;\n        border-color: transparent;\n        color: #0e865a; }\n  .button.is-warning {\n    background-color: #FFE900;\n    border-color: transparent;\n    color: rgba(0, 0, 0, 0.7); }\n    .button.is-warning:hover, .button.is-warning.is-hovered {\n      background-color: #f2dd00;\n      border-color: transparent;\n      color: rgba(0, 0, 0, 0.7); }\n    .button.is-warning:focus, .button.is-warning.is-focused {\n      border-color: transparent;\n      color: rgba(0, 0, 0, 0.7); }\n      .button.is-warning:focus:not(:active), .button.is-warning.is-focused:not(:active) {\n        box-shadow: 0 0 0 0.125em rgba(255, 233, 0, 0.25); }\n    .button.is-warning:active, .button.is-warning.is-active {\n      background-color: #e6d200;\n      border-color: transparent;\n      color: rgba(0, 0, 0, 0.7); }\n    .button.is-warning[disabled], fieldset[disabled] .button.is-warning {\n      background-color: #FFE900;\n      border-color: #FFE900;\n      box-shadow: none; }\n    .button.is-warning.is-inverted {\n      background-color: rgba(0, 0, 0, 0.7);\n      color: #FFE900; }\n      .button.is-warning.is-inverted:hover, .button.is-warning.is-inverted.is-hovered {\n        background-color: rgba(0, 0, 0, 0.7); }\n      .button.is-warning.is-inverted[disabled], fieldset[disabled] .button.is-warning.is-inverted {\n        background-color: rgba(0, 0, 0, 0.7);\n        border-color: transparent;\n        box-shadow: none;\n        color: #FFE900; }\n    .button.is-warning.is-loading::after {\n      border-color: transparent transparent rgba(0, 0, 0, 0.7) rgba(0, 0, 0, 0.7) !important; }\n    .button.is-warning.is-outlined {\n      background-color: transparent;\n      border-color: #FFE900;\n      color: #FFE900; }\n      .button.is-warning.is-outlined:hover, .button.is-warning.is-outlined.is-hovered, .button.is-warning.is-outlined:focus, .button.is-warning.is-outlined.is-focused {\n        background-color: #FFE900;\n        border-color: #FFE900;\n        color: rgba(0, 0, 0, 0.7); }\n      .button.is-warning.is-outlined.is-loading::after {\n        border-color: transparent transparent #FFE900 #FFE900 !important; }\n      .button.is-warning.is-outlined.is-loading:hover::after, .button.is-warning.is-outlined.is-loading.is-hovered::after, .button.is-warning.is-outlined.is-loading:focus::after, .button.is-warning.is-outlined.is-loading.is-focused::after {\n        border-color: transparent transparent rgba(0, 0, 0, 0.7) rgba(0, 0, 0, 0.7) !important; }\n      .button.is-warning.is-outlined[disabled], fieldset[disabled] .button.is-warning.is-outlined {\n        background-color: transparent;\n        border-color: #FFE900;\n        box-shadow: none;\n        color: #FFE900; }\n    .button.is-warning.is-inverted.is-outlined {\n      background-color: transparent;\n      border-color: rgba(0, 0, 0, 0.7);\n      color: rgba(0, 0, 0, 0.7); }\n      .button.is-warning.is-inverted.is-outlined:hover, .button.is-warning.is-inverted.is-outlined.is-hovered, .button.is-warning.is-inverted.is-outlined:focus, .button.is-warning.is-inverted.is-outlined.is-focused {\n        background-color: rgba(0, 0, 0, 0.7);\n        color: #FFE900; }\n      .button.is-warning.is-inverted.is-outlined.is-loading:hover::after, .button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after, .button.is-warning.is-inverted.is-outlined.is-loading:focus::after, .button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after {\n        border-color: transparent transparent #FFE900 #FFE900 !important; }\n      .button.is-warning.is-inverted.is-outlined[disabled], fieldset[disabled] .button.is-warning.is-inverted.is-outlined {\n        background-color: transparent;\n        border-color: rgba(0, 0, 0, 0.7);\n        box-shadow: none;\n        color: rgba(0, 0, 0, 0.7); }\n    .button.is-warning.is-light {\n      background-color: #fffdeb;\n      color: #948700; }\n      .button.is-warning.is-light:hover, .button.is-warning.is-light.is-hovered {\n        background-color: #fffcde;\n        border-color: transparent;\n        color: #948700; }\n      .button.is-warning.is-light:active, .button.is-warning.is-light.is-active {\n        background-color: #fffbd1;\n        border-color: transparent;\n        color: #948700; }\n  .button.is-danger {\n    background-color: #f14668;\n    border-color: transparent;\n    color: #fff; }\n    .button.is-danger:hover, .button.is-danger.is-hovered {\n      background-color: #f03a5f;\n      border-color: transparent;\n      color: #fff; }\n    .button.is-danger:focus, .button.is-danger.is-focused {\n      border-color: transparent;\n      color: #fff; }\n      .button.is-danger:focus:not(:active), .button.is-danger.is-focused:not(:active) {\n        box-shadow: 0 0 0 0.125em rgba(241, 70, 104, 0.25); }\n    .button.is-danger:active, .button.is-danger.is-active {\n      background-color: #ef2e55;\n      border-color: transparent;\n      color: #fff; }\n    .button.is-danger[disabled], fieldset[disabled] .button.is-danger {\n      background-color: #f14668;\n      border-color: #f14668;\n      box-shadow: none; }\n    .button.is-danger.is-inverted {\n      background-color: #fff;\n      color: #f14668; }\n      .button.is-danger.is-inverted:hover, .button.is-danger.is-inverted.is-hovered {\n        background-color: #f2f2f2; }\n      .button.is-danger.is-inverted[disabled], fieldset[disabled] .button.is-danger.is-inverted {\n        background-color: #fff;\n        border-color: transparent;\n        box-shadow: none;\n        color: #f14668; }\n    .button.is-danger.is-loading::after {\n      border-color: transparent transparent #fff #fff !important; }\n    .button.is-danger.is-outlined {\n      background-color: transparent;\n      border-color: #f14668;\n      color: #f14668; }\n      .button.is-danger.is-outlined:hover, .button.is-danger.is-outlined.is-hovered, .button.is-danger.is-outlined:focus, .button.is-danger.is-outlined.is-focused {\n        background-color: #f14668;\n        border-color: #f14668;\n        color: #fff; }\n      .button.is-danger.is-outlined.is-loading::after {\n        border-color: transparent transparent #f14668 #f14668 !important; }\n      .button.is-danger.is-outlined.is-loading:hover::after, .button.is-danger.is-outlined.is-loading.is-hovered::after, .button.is-danger.is-outlined.is-loading:focus::after, .button.is-danger.is-outlined.is-loading.is-focused::after {\n        border-color: transparent transparent #fff #fff !important; }\n      .button.is-danger.is-outlined[disabled], fieldset[disabled] .button.is-danger.is-outlined {\n        background-color: transparent;\n        border-color: #f14668;\n        box-shadow: none;\n        color: #f14668; }\n    .button.is-danger.is-inverted.is-outlined {\n      background-color: transparent;\n      border-color: #fff;\n      color: #fff; }\n      .button.is-danger.is-inverted.is-outlined:hover, .button.is-danger.is-inverted.is-outlined.is-hovered, .button.is-danger.is-inverted.is-outlined:focus, .button.is-danger.is-inverted.is-outlined.is-focused {\n        background-color: #fff;\n        color: #f14668; }\n      .button.is-danger.is-inverted.is-outlined.is-loading:hover::after, .button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after, .button.is-danger.is-inverted.is-outlined.is-loading:focus::after, .button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after {\n        border-color: transparent transparent #f14668 #f14668 !important; }\n      .button.is-danger.is-inverted.is-outlined[disabled], fieldset[disabled] .button.is-danger.is-inverted.is-outlined {\n        background-color: transparent;\n        border-color: #fff;\n        box-shadow: none;\n        color: #fff; }\n    .button.is-danger.is-light {\n      background-color: #feecf0;\n      color: #cc0f35; }\n      .button.is-danger.is-light:hover, .button.is-danger.is-light.is-hovered {\n        background-color: #fde0e6;\n        border-color: transparent;\n        color: #cc0f35; }\n      .button.is-danger.is-light:active, .button.is-danger.is-light.is-active {\n        background-color: #fcd4dc;\n        border-color: transparent;\n        color: #cc0f35; }\n  .button.is-small {\n    font-size: 0.75rem; }\n    .button.is-small:not(.is-rounded) {\n      border-radius: 2px; }\n  .button.is-normal {\n    font-size: 1rem; }\n  .button.is-medium {\n    font-size: 1.25rem; }\n  .button.is-large {\n    font-size: 1.5rem; }\n  .button[disabled], fieldset[disabled] .button {\n    background-color: white;\n    border-color: #dbdbdb;\n    box-shadow: none;\n    opacity: 0.5; }\n  .button.is-fullwidth {\n    display: flex;\n    width: 100%; }\n  .button.is-loading {\n    color: transparent !important;\n    pointer-events: none; }\n    .button.is-loading::after {\n      position: absolute;\n      left: calc(50% - (1em * 0.5));\n      top: calc(50% - (1em * 0.5));\n      position: absolute !important; }\n  .button.is-static {\n    background-color: whitesmoke;\n    border-color: #dbdbdb;\n    color: #7a7a7a;\n    box-shadow: none;\n    pointer-events: none; }\n  .button.is-rounded {\n    border-radius: 9999px;\n    padding-left: calc(1em + 0.25em);\n    padding-right: calc(1em + 0.25em); }\n\n.buttons {\n  align-items: center;\n  display: flex;\n  flex-wrap: wrap;\n  justify-content: flex-start; }\n  .buttons .button {\n    margin-bottom: 0.5rem; }\n    .buttons .button:not(:last-child):not(.is-fullwidth) {\n      margin-right: 0.5rem; }\n  .buttons:last-child {\n    margin-bottom: -0.5rem; }\n  .buttons:not(:last-child) {\n    margin-bottom: 1rem; }\n  .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large) {\n    font-size: 0.75rem; }\n    .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large):not(.is-rounded) {\n      border-radius: 2px; }\n  .buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large) {\n    font-size: 1.25rem; }\n  .buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium) {\n    font-size: 1.5rem; }\n  .buttons.has-addons .button:not(:first-child) {\n    border-bottom-left-radius: 0;\n    border-top-left-radius: 0; }\n  .buttons.has-addons .button:not(:last-child) {\n    border-bottom-right-radius: 0;\n    border-top-right-radius: 0;\n    margin-right: -1px; }\n  .buttons.has-addons .button:last-child {\n    margin-right: 0; }\n  .buttons.has-addons .button:hover, .buttons.has-addons .button.is-hovered {\n    z-index: 2; }\n  .buttons.has-addons .button:focus, .buttons.has-addons .button.is-focused, .buttons.has-addons .button:active, .buttons.has-addons .button.is-active, .buttons.has-addons .button.is-selected {\n    z-index: 3; }\n    .buttons.has-addons .button:focus:hover, .buttons.has-addons .button.is-focused:hover, .buttons.has-addons .button:active:hover, .buttons.has-addons .button.is-active:hover, .buttons.has-addons .button.is-selected:hover {\n      z-index: 4; }\n  .buttons.has-addons .button.is-expanded {\n    flex-grow: 1;\n    flex-shrink: 1; }\n  .buttons.is-centered {\n    justify-content: center; }\n    .buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth) {\n      margin-left: 0.25rem;\n      margin-right: 0.25rem; }\n  .buttons.is-right {\n    justify-content: flex-end; }\n    .buttons.is-right:not(.has-addons) .button:not(.is-fullwidth) {\n      margin-left: 0.25rem;\n      margin-right: 0.25rem; }\n\n@media screen and (max-width: 768px) {\n  .button.is-responsive.is-small {\n    font-size: 0.5625rem; }\n  .button.is-responsive,\n  .button.is-responsive.is-normal {\n    font-size: 0.65625rem; }\n  .button.is-responsive.is-medium {\n    font-size: 0.75rem; }\n  .button.is-responsive.is-large {\n    font-size: 1rem; } }\n\n@media screen and (min-width: 769px) and (max-width: 1023px) {\n  .button.is-responsive.is-small {\n    font-size: 0.65625rem; }\n  .button.is-responsive,\n  .button.is-responsive.is-normal {\n    font-size: 0.75rem; }\n  .button.is-responsive.is-medium {\n    font-size: 1rem; }\n  .button.is-responsive.is-large {\n    font-size: 1.25rem; } }\n\n.container {\n  flex-grow: 1;\n  margin: 0 auto;\n  position: relative;\n  width: auto; }\n  .container.is-fluid {\n    max-width: none !important;\n    padding-left: 32px;\n    padding-right: 32px;\n    width: 100%; }\n  @media screen and (min-width: 1024px) {\n    .container {\n      max-width: 960px; } }\n  @media screen and (max-width: 1215px) {\n    .container.is-widescreen:not(.is-max-desktop) {\n      max-width: 1152px; } }\n  @media screen and (max-width: 1407px) {\n    .container.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen) {\n      max-width: 1344px; } }\n  @media screen and (min-width: 1216px) {\n    .container:not(.is-max-desktop) {\n      max-width: 1152px; } }\n  @media screen and (min-width: 1408px) {\n    .container:not(.is-max-desktop):not(.is-max-widescreen) {\n      max-width: 1344px; } }\n.content li + li {\n  margin-top: 0.25em; }\n\n.content p:not(:last-child),\n.content dl:not(:last-child),\n.content ol:not(:last-child),\n.content ul:not(:last-child),\n.content blockquote:not(:last-child),\n.content pre:not(:last-child),\n.content table:not(:last-child) {\n  margin-bottom: 1em; }\n\n.content h1,\n.content h2,\n.content h3,\n.content h4,\n.content h5,\n.content h6 {\n  color: #363636;\n  font-weight: 600;\n  line-height: 1.125; }\n\n.content h1 {\n  font-size: 2em;\n  margin-bottom: 0.5em; }\n  .content h1:not(:first-child) {\n    margin-top: 1em; }\n\n.content h2 {\n  font-size: 1.75em;\n  margin-bottom: 0.5714em; }\n  .content h2:not(:first-child) {\n    margin-top: 1.1428em; }\n\n.content h3 {\n  font-size: 1.5em;\n  margin-bottom: 0.6666em; }\n  .content h3:not(:first-child) {\n    margin-top: 1.3333em; }\n\n.content h4 {\n  font-size: 1.25em;\n  margin-bottom: 0.8em; }\n\n.content h5 {\n  font-size: 1.125em;\n  margin-bottom: 0.8888em; }\n\n.content h6 {\n  font-size: 1em;\n  margin-bottom: 1em; }\n\n.content blockquote {\n  background-color: whitesmoke;\n  border-left: 5px solid #dbdbdb;\n  padding: 1.25em 1.5em; }\n\n.content ol {\n  list-style-position: outside;\n  margin-left: 2em;\n  margin-top: 1em; }\n  .content ol:not([type]) {\n    list-style-type: decimal; }\n    .content ol:not([type]).is-lower-alpha {\n      list-style-type: lower-alpha; }\n    .content ol:not([type]).is-lower-roman {\n      list-style-type: lower-roman; }\n    .content ol:not([type]).is-upper-alpha {\n      list-style-type: upper-alpha; }\n    .content ol:not([type]).is-upper-roman {\n      list-style-type: upper-roman; }\n\n.content ul {\n  list-style: disc outside;\n  margin-left: 2em;\n  margin-top: 1em; }\n  .content ul ul {\n    list-style-type: circle;\n    margin-top: 0.5em; }\n    .content ul ul ul {\n      list-style-type: square; }\n\n.content dd {\n  margin-left: 2em; }\n\n.content figure {\n  margin-left: 2em;\n  margin-right: 2em;\n  text-align: center; }\n  .content figure:not(:first-child) {\n    margin-top: 2em; }\n  .content figure:not(:last-child) {\n    margin-bottom: 2em; }\n  .content figure img {\n    display: inline-block; }\n  .content figure figcaption {\n    font-style: italic; }\n\n.content pre {\n  -webkit-overflow-scrolling: touch;\n  overflow-x: auto;\n  padding: 1.25em 1.5em;\n  white-space: pre;\n  word-wrap: normal; }\n\n.content sup,\n.content sub {\n  font-size: 75%; }\n\n.content table {\n  width: 100%; }\n  .content table td,\n  .content table th {\n    border: 1px solid #dbdbdb;\n    border-width: 0 0 1px;\n    padding: 0.5em 0.75em;\n    vertical-align: top; }\n  .content table th {\n    color: #363636; }\n    .content table th:not([align]) {\n      text-align: inherit; }\n  .content table thead td,\n  .content table thead th {\n    border-width: 0 0 2px;\n    color: #363636; }\n  .content table tfoot td,\n  .content table tfoot th {\n    border-width: 2px 0 0;\n    color: #363636; }\n  .content table tbody tr:last-child td,\n  .content table tbody tr:last-child th {\n    border-bottom-width: 0; }\n\n.content .tabs li + li {\n  margin-top: 0; }\n\n.content.is-small {\n  font-size: 0.75rem; }\n\n.content.is-normal {\n  font-size: 1rem; }\n\n.content.is-medium {\n  font-size: 1.25rem; }\n\n.content.is-large {\n  font-size: 1.5rem; }\n\n.icon {\n  align-items: center;\n  display: inline-flex;\n  justify-content: center;\n  height: 1.5rem;\n  width: 1.5rem; }\n  .icon.is-small {\n    height: 1rem;\n    width: 1rem; }\n  .icon.is-medium {\n    height: 2rem;\n    width: 2rem; }\n  .icon.is-large {\n    height: 3rem;\n    width: 3rem; }\n\n.icon-text {\n  align-items: flex-start;\n  color: inherit;\n  display: inline-flex;\n  flex-wrap: wrap;\n  line-height: 1.5rem;\n  vertical-align: top; }\n  .icon-text .icon {\n    flex-grow: 0;\n    flex-shrink: 0; }\n    .icon-text .icon:not(:last-child) {\n      margin-right: 0.25em; }\n    .icon-text .icon:not(:first-child) {\n      margin-left: 0.25em; }\n\ndiv.icon-text {\n  display: flex; }\n\n.image {\n  display: block;\n  position: relative; }\n  .image img {\n    display: block;\n    height: auto;\n    width: 100%; }\n    .image img.is-rounded {\n      border-radius: 9999px; }\n  .image.is-fullwidth {\n    width: 100%; }\n  .image.is-square img,\n  .image.is-square .has-ratio, .image.is-1by1 img,\n  .image.is-1by1 .has-ratio, .image.is-5by4 img,\n  .image.is-5by4 .has-ratio, .image.is-4by3 img,\n  .image.is-4by3 .has-ratio, .image.is-3by2 img,\n  .image.is-3by2 .has-ratio, .image.is-5by3 img,\n  .image.is-5by3 .has-ratio, .image.is-16by9 img,\n  .image.is-16by9 .has-ratio, .image.is-2by1 img,\n  .image.is-2by1 .has-ratio, .image.is-3by1 img,\n  .image.is-3by1 .has-ratio, .image.is-4by5 img,\n  .image.is-4by5 .has-ratio, .image.is-3by4 img,\n  .image.is-3by4 .has-ratio, .image.is-2by3 img,\n  .image.is-2by3 .has-ratio, .image.is-3by5 img,\n  .image.is-3by5 .has-ratio, .image.is-9by16 img,\n  .image.is-9by16 .has-ratio, .image.is-1by2 img,\n  .image.is-1by2 .has-ratio, .image.is-1by3 img,\n  .image.is-1by3 .has-ratio {\n    height: 100%;\n    width: 100%; }\n  .image.is-square, .image.is-1by1 {\n    padding-top: 100%; }\n  .image.is-5by4 {\n    padding-top: 80%; }\n  .image.is-4by3 {\n    padding-top: 75%; }\n  .image.is-3by2 {\n    padding-top: 66.6666%; }\n  .image.is-5by3 {\n    padding-top: 60%; }\n  .image.is-16by9 {\n    padding-top: 56.25%; }\n  .image.is-2by1 {\n    padding-top: 50%; }\n  .image.is-3by1 {\n    padding-top: 33.3333%; }\n  .image.is-4by5 {\n    padding-top: 125%; }\n  .image.is-3by4 {\n    padding-top: 133.3333%; }\n  .image.is-2by3 {\n    padding-top: 150%; }\n  .image.is-3by5 {\n    padding-top: 166.6666%; }\n  .image.is-9by16 {\n    padding-top: 177.7777%; }\n  .image.is-1by2 {\n    padding-top: 200%; }\n  .image.is-1by3 {\n    padding-top: 300%; }\n  .image.is-16x16 {\n    height: 16px;\n    width: 16px; }\n  .image.is-24x24 {\n    height: 24px;\n    width: 24px; }\n  .image.is-32x32 {\n    height: 32px;\n    width: 32px; }\n  .image.is-48x48 {\n    height: 48px;\n    width: 48px; }\n  .image.is-64x64 {\n    height: 64px;\n    width: 64px; }\n  .image.is-96x96 {\n    height: 96px;\n    width: 96px; }\n  .image.is-128x128 {\n    height: 128px;\n    width: 128px; }\n\n.notification {\n  background-color: whitesmoke;\n  border-radius: 4px;\n  position: relative;\n  padding: 1.25rem 2.5rem 1.25rem 1.5rem; }\n  .notification a:not(.button):not(.dropdown-item) {\n    color: currentColor;\n    text-decoration: underline; }\n  .notification strong {\n    color: currentColor; }\n  .notification code,\n  .notification pre {\n    background: white; }\n  .notification pre code {\n    background: transparent; }\n  .notification > .delete {\n    right: 0.5rem;\n    position: absolute;\n    top: 0.5rem; }\n  .notification .title,\n  .notification .subtitle,\n  .notification .content {\n    color: currentColor; }\n  .notification.is-white {\n    background-color: white;\n    color: #0a0a0a; }\n  .notification.is-black {\n    background-color: #0a0a0a;\n    color: white; }\n  .notification.is-light {\n    background-color: whitesmoke;\n    color: rgba(0, 0, 0, 0.7); }\n  .notification.is-dark {\n    background-color: #363636;\n    color: #fff; }\n  .notification.is-primary {\n    background-color: #ff0d68;\n    color: #fff; }\n    .notification.is-primary.is-light {\n      background-color: #ffebf2;\n      color: #e60056; }\n  .notification.is-link {\n    background-color: #ff0d68;\n    color: #fff; }\n    .notification.is-link.is-light {\n      background-color: #ffebf2;\n      color: #e60056; }\n  .notification.is-info {\n    background-color: #0092FF;\n    color: #fff; }\n    .notification.is-info.is-light {\n      background-color: #ebf6ff;\n      color: #0075cc; }\n  .notification.is-success {\n    background-color: #16DB93;\n    color: rgba(0, 0, 0, 0.7); }\n    .notification.is-success.is-light {\n      background-color: #ecfdf7;\n      color: #0e865a; }\n  .notification.is-warning {\n    background-color: #FFE900;\n    color: rgba(0, 0, 0, 0.7); }\n    .notification.is-warning.is-light {\n      background-color: #fffdeb;\n      color: #948700; }\n  .notification.is-danger {\n    background-color: #f14668;\n    color: #fff; }\n    .notification.is-danger.is-light {\n      background-color: #feecf0;\n      color: #cc0f35; }\n\n.progress {\n  -moz-appearance: none;\n  -webkit-appearance: none;\n  border: none;\n  border-radius: 9999px;\n  display: block;\n  height: 1rem;\n  overflow: hidden;\n  padding: 0;\n  width: 100%; }\n  .progress::-webkit-progress-bar {\n    background-color: #ededed; }\n  .progress::-webkit-progress-value {\n    background-color: #4a4a4a; }\n  .progress::-moz-progress-bar {\n    background-color: #4a4a4a; }\n  .progress::-ms-fill {\n    background-color: #4a4a4a;\n    border: none; }\n  .progress.is-white::-webkit-progress-value {\n    background-color: white; }\n  .progress.is-white::-moz-progress-bar {\n    background-color: white; }\n  .progress.is-white::-ms-fill {\n    background-color: white; }\n  .progress.is-white:indeterminate {\n    background-image: linear-gradient(to right, white 30%, #ededed 30%); }\n  .progress.is-black::-webkit-progress-value {\n    background-color: #0a0a0a; }\n  .progress.is-black::-moz-progress-bar {\n    background-color: #0a0a0a; }\n  .progress.is-black::-ms-fill {\n    background-color: #0a0a0a; }\n  .progress.is-black:indeterminate {\n    background-image: linear-gradient(to right, #0a0a0a 30%, #ededed 30%); }\n  .progress.is-light::-webkit-progress-value {\n    background-color: whitesmoke; }\n  .progress.is-light::-moz-progress-bar {\n    background-color: whitesmoke; }\n  .progress.is-light::-ms-fill {\n    background-color: whitesmoke; }\n  .progress.is-light:indeterminate {\n    background-image: linear-gradient(to right, whitesmoke 30%, #ededed 30%); }\n  .progress.is-dark::-webkit-progress-value {\n    background-color: #363636; }\n  .progress.is-dark::-moz-progress-bar {\n    background-color: #363636; }\n  .progress.is-dark::-ms-fill {\n    background-color: #363636; }\n  .progress.is-dark:indeterminate {\n    background-image: linear-gradient(to right, #363636 30%, #ededed 30%); }\n  .progress.is-primary::-webkit-progress-value {\n    background-color: #ff0d68; }\n  .progress.is-primary::-moz-progress-bar {\n    background-color: #ff0d68; }\n  .progress.is-primary::-ms-fill {\n    background-color: #ff0d68; }\n  .progress.is-primary:indeterminate {\n    background-image: linear-gradient(to right, #ff0d68 30%, #ededed 30%); }\n  .progress.is-link::-webkit-progress-value {\n    background-color: #ff0d68; }\n  .progress.is-link::-moz-progress-bar {\n    background-color: #ff0d68; }\n  .progress.is-link::-ms-fill {\n    background-color: #ff0d68; }\n  .progress.is-link:indeterminate {\n    background-image: linear-gradient(to right, #ff0d68 30%, #ededed 30%); }\n  .progress.is-info::-webkit-progress-value {\n    background-color: #0092FF; }\n  .progress.is-info::-moz-progress-bar {\n    background-color: #0092FF; }\n  .progress.is-info::-ms-fill {\n    background-color: #0092FF; }\n  .progress.is-info:indeterminate {\n    background-image: linear-gradient(to right, #0092FF 30%, #ededed 30%); }\n  .progress.is-success::-webkit-progress-value {\n    background-color: #16DB93; }\n  .progress.is-success::-moz-progress-bar {\n    background-color: #16DB93; }\n  .progress.is-success::-ms-fill {\n    background-color: #16DB93; }\n  .progress.is-success:indeterminate {\n    background-image: linear-gradient(to right, #16DB93 30%, #ededed 30%); }\n  .progress.is-warning::-webkit-progress-value {\n    background-color: #FFE900; }\n  .progress.is-warning::-moz-progress-bar {\n    background-color: #FFE900; }\n  .progress.is-warning::-ms-fill {\n    background-color: #FFE900; }\n  .progress.is-warning:indeterminate {\n    background-image: linear-gradient(to right, #FFE900 30%, #ededed 30%); }\n  .progress.is-danger::-webkit-progress-value {\n    background-color: #f14668; }\n  .progress.is-danger::-moz-progress-bar {\n    background-color: #f14668; }\n  .progress.is-danger::-ms-fill {\n    background-color: #f14668; }\n  .progress.is-danger:indeterminate {\n    background-image: linear-gradient(to right, #f14668 30%, #ededed 30%); }\n  .progress:indeterminate {\n    animation-duration: 1.5s;\n    animation-iteration-count: infinite;\n    animation-name: moveIndeterminate;\n    animation-timing-function: linear;\n    background-color: #ededed;\n    background-image: linear-gradient(to right, #4a4a4a 30%, #ededed 30%);\n    background-position: top left;\n    background-repeat: no-repeat;\n    background-size: 150% 150%; }\n    .progress:indeterminate::-webkit-progress-bar {\n      background-color: transparent; }\n    .progress:indeterminate::-moz-progress-bar {\n      background-color: transparent; }\n    .progress:indeterminate::-ms-fill {\n      animation-name: none; }\n  .progress.is-small {\n    height: 0.75rem; }\n  .progress.is-medium {\n    height: 1.25rem; }\n  .progress.is-large {\n    height: 1.5rem; }\n\n@keyframes moveIndeterminate {\n  from {\n    background-position: 200% 0; }\n  to {\n    background-position: -200% 0; } }\n\n.table {\n  background-color: white;\n  color: #363636; }\n  .table td,\n  .table th {\n    border: 1px solid #dbdbdb;\n    border-width: 0 0 1px;\n    padding: 0.5em 0.75em;\n    vertical-align: top; }\n    .table td.is-white,\n    .table th.is-white {\n      background-color: white;\n      border-color: white;\n      color: #0a0a0a; }\n    .table td.is-black,\n    .table th.is-black {\n      background-color: #0a0a0a;\n      border-color: #0a0a0a;\n      color: white; }\n    .table td.is-light,\n    .table th.is-light {\n      background-color: whitesmoke;\n      border-color: whitesmoke;\n      color: rgba(0, 0, 0, 0.7); }\n    .table td.is-dark,\n    .table th.is-dark {\n      background-color: #363636;\n      border-color: #363636;\n      color: #fff; }\n    .table td.is-primary,\n    .table th.is-primary {\n      background-color: #ff0d68;\n      border-color: #ff0d68;\n      color: #fff; }\n    .table td.is-link,\n    .table th.is-link {\n      background-color: #ff0d68;\n      border-color: #ff0d68;\n      color: #fff; }\n    .table td.is-info,\n    .table th.is-info {\n      background-color: #0092FF;\n      border-color: #0092FF;\n      color: #fff; }\n    .table td.is-success,\n    .table th.is-success {\n      background-color: #16DB93;\n      border-color: #16DB93;\n      color: rgba(0, 0, 0, 0.7); }\n    .table td.is-warning,\n    .table th.is-warning {\n      background-color: #FFE900;\n      border-color: #FFE900;\n      color: rgba(0, 0, 0, 0.7); }\n    .table td.is-danger,\n    .table th.is-danger {\n      background-color: #f14668;\n      border-color: #f14668;\n      color: #fff; }\n    .table td.is-narrow,\n    .table th.is-narrow {\n      white-space: nowrap;\n      width: 1%; }\n    .table td.is-selected,\n    .table th.is-selected {\n      background-color: #ff0d68;\n      color: #fff; }\n      .table td.is-selected a,\n      .table td.is-selected strong,\n      .table th.is-selected a,\n      .table th.is-selected strong {\n        color: currentColor; }\n    .table td.is-vcentered,\n    .table th.is-vcentered {\n      vertical-align: middle; }\n  .table th {\n    color: #363636; }\n    .table th:not([align]) {\n      text-align: left; }\n  .table tr.is-selected {\n    background-color: #ff0d68;\n    color: #fff; }\n    .table tr.is-selected a,\n    .table tr.is-selected strong {\n      color: currentColor; }\n    .table tr.is-selected td,\n    .table tr.is-selected th {\n      border-color: #fff;\n      color: currentColor; }\n  .table thead {\n    background-color: transparent; }\n    .table thead td,\n    .table thead th {\n      border-width: 0 0 2px;\n      color: #363636; }\n  .table tfoot {\n    background-color: transparent; }\n    .table tfoot td,\n    .table tfoot th {\n      border-width: 2px 0 0;\n      color: #363636; }\n  .table tbody {\n    background-color: transparent; }\n    .table tbody tr:last-child td,\n    .table tbody tr:last-child th {\n      border-bottom-width: 0; }\n  .table.is-bordered td,\n  .table.is-bordered th {\n    border-width: 1px; }\n  .table.is-bordered tr:last-child td,\n  .table.is-bordered tr:last-child th {\n    border-bottom-width: 1px; }\n  .table.is-fullwidth {\n    width: 100%; }\n  .table.is-hoverable tbody tr:not(.is-selected):hover {\n    background-color: #fafafa; }\n  .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover {\n    background-color: #fafafa; }\n    .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even) {\n      background-color: whitesmoke; }\n  .table.is-narrow td,\n  .table.is-narrow th {\n    padding: 0.25em 0.5em; }\n  .table.is-striped tbody tr:not(.is-selected):nth-child(even) {\n    background-color: #fafafa; }\n\n.table-container {\n  -webkit-overflow-scrolling: touch;\n  overflow: auto;\n  overflow-y: hidden;\n  max-width: 100%; }\n\n.tags {\n  align-items: center;\n  display: flex;\n  flex-wrap: wrap;\n  justify-content: flex-start; }\n  .tags .tag {\n    margin-bottom: 0.5rem; }\n    .tags .tag:not(:last-child) {\n      margin-right: 0.5rem; }\n  .tags:last-child {\n    margin-bottom: -0.5rem; }\n  .tags:not(:last-child) {\n    margin-bottom: 1rem; }\n  .tags.are-medium .tag:not(.is-normal):not(.is-large) {\n    font-size: 1rem; }\n  .tags.are-large .tag:not(.is-normal):not(.is-medium) {\n    font-size: 1.25rem; }\n  .tags.is-centered {\n    justify-content: center; }\n    .tags.is-centered .tag {\n      margin-right: 0.25rem;\n      margin-left: 0.25rem; }\n  .tags.is-right {\n    justify-content: flex-end; }\n    .tags.is-right .tag:not(:first-child) {\n      margin-left: 0.5rem; }\n    .tags.is-right .tag:not(:last-child) {\n      margin-right: 0; }\n  .tags.has-addons .tag {\n    margin-right: 0; }\n    .tags.has-addons .tag:not(:first-child) {\n      margin-left: 0;\n      border-top-left-radius: 0;\n      border-bottom-left-radius: 0; }\n    .tags.has-addons .tag:not(:last-child) {\n      border-top-right-radius: 0;\n      border-bottom-right-radius: 0; }\n\n.tag:not(body) {\n  align-items: center;\n  background-color: whitesmoke;\n  border-radius: 4px;\n  color: #4a4a4a;\n  display: inline-flex;\n  font-size: 0.75rem;\n  height: 2em;\n  justify-content: center;\n  line-height: 1.5;\n  padding-left: 0.75em;\n  padding-right: 0.75em;\n  white-space: nowrap; }\n  .tag:not(body) .delete {\n    margin-left: 0.25rem;\n    margin-right: -0.375rem; }\n  .tag:not(body).is-white {\n    background-color: white;\n    color: #0a0a0a; }\n  .tag:not(body).is-black {\n    background-color: #0a0a0a;\n    color: white; }\n  .tag:not(body).is-light {\n    background-color: whitesmoke;\n    color: rgba(0, 0, 0, 0.7); }\n  .tag:not(body).is-dark {\n    background-color: #363636;\n    color: #fff; }\n  .tag:not(body).is-primary {\n    background-color: #ff0d68;\n    color: #fff; }\n    .tag:not(body).is-primary.is-light {\n      background-color: #ffebf2;\n      color: #e60056; }\n  .tag:not(body).is-link {\n    background-color: #ff0d68;\n    color: #fff; }\n    .tag:not(body).is-link.is-light {\n      background-color: #ffebf2;\n      color: #e60056; }\n  .tag:not(body).is-info {\n    background-color: #0092FF;\n    color: #fff; }\n    .tag:not(body).is-info.is-light {\n      background-color: #ebf6ff;\n      color: #0075cc; }\n  .tag:not(body).is-success {\n    background-color: #16DB93;\n    color: rgba(0, 0, 0, 0.7); }\n    .tag:not(body).is-success.is-light {\n      background-color: #ecfdf7;\n      color: #0e865a; }\n  .tag:not(body).is-warning {\n    background-color: #FFE900;\n    color: rgba(0, 0, 0, 0.7); }\n    .tag:not(body).is-warning.is-light {\n      background-color: #fffdeb;\n      color: #948700; }\n  .tag:not(body).is-danger {\n    background-color: #f14668;\n    color: #fff; }\n    .tag:not(body).is-danger.is-light {\n      background-color: #feecf0;\n      color: #cc0f35; }\n  .tag:not(body).is-normal {\n    font-size: 0.75rem; }\n  .tag:not(body).is-medium {\n    font-size: 1rem; }\n  .tag:not(body).is-large {\n    font-size: 1.25rem; }\n  .tag:not(body) .icon:first-child:not(:last-child) {\n    margin-left: -0.375em;\n    margin-right: 0.1875em; }\n  .tag:not(body) .icon:last-child:not(:first-child) {\n    margin-left: 0.1875em;\n    margin-right: -0.375em; }\n  .tag:not(body) .icon:first-child:last-child {\n    margin-left: -0.375em;\n    margin-right: -0.375em; }\n  .tag:not(body).is-delete {\n    margin-left: 1px;\n    padding: 0;\n    position: relative;\n    width: 2em; }\n    .tag:not(body).is-delete::before, .tag:not(body).is-delete::after {\n      background-color: currentColor;\n      content: \"\";\n      display: block;\n      left: 50%;\n      position: absolute;\n      top: 50%;\n      transform: translateX(-50%) translateY(-50%) rotate(45deg);\n      transform-origin: center center; }\n    .tag:not(body).is-delete::before {\n      height: 1px;\n      width: 50%; }\n    .tag:not(body).is-delete::after {\n      height: 50%;\n      width: 1px; }\n    .tag:not(body).is-delete:hover, .tag:not(body).is-delete:focus {\n      background-color: #e8e8e8; }\n    .tag:not(body).is-delete:active {\n      background-color: #dbdbdb; }\n  .tag:not(body).is-rounded {\n    border-radius: 9999px; }\n\na.tag:hover {\n  text-decoration: underline; }\n\n.title,\n.subtitle {\n  word-break: break-word; }\n  .title em,\n  .title span,\n  .subtitle em,\n  .subtitle span {\n    font-weight: inherit; }\n  .title sub,\n  .subtitle sub {\n    font-size: 0.75em; }\n  .title sup,\n  .subtitle sup {\n    font-size: 0.75em; }\n  .title .tag,\n  .subtitle .tag {\n    vertical-align: middle; }\n\n.title {\n  color: #363636;\n  font-size: 2rem;\n  font-weight: 600;\n  line-height: 1.125; }\n  .title strong {\n    color: inherit;\n    font-weight: inherit; }\n  .title:not(.is-spaced) + .subtitle {\n    margin-top: -1.25rem; }\n  .title.is-1 {\n    font-size: 3rem; }\n  .title.is-2 {\n    font-size: 2.5rem; }\n  .title.is-3 {\n    font-size: 2rem; }\n  .title.is-4 {\n    font-size: 1.5rem; }\n  .title.is-5 {\n    font-size: 1.25rem; }\n  .title.is-6 {\n    font-size: 1rem; }\n  .title.is-7 {\n    font-size: 0.75rem; }\n\n.subtitle {\n  color: #4a4a4a;\n  font-size: 1.25rem;\n  font-weight: 400;\n  line-height: 1.25; }\n  .subtitle strong {\n    color: #363636;\n    font-weight: 600; }\n  .subtitle:not(.is-spaced) + .title {\n    margin-top: -1.25rem; }\n  .subtitle.is-1 {\n    font-size: 3rem; }\n  .subtitle.is-2 {\n    font-size: 2.5rem; }\n  .subtitle.is-3 {\n    font-size: 2rem; }\n  .subtitle.is-4 {\n    font-size: 1.5rem; }\n  .subtitle.is-5 {\n    font-size: 1.25rem; }\n  .subtitle.is-6 {\n    font-size: 1rem; }\n  .subtitle.is-7 {\n    font-size: 0.75rem; }\n\n.heading {\n  display: block;\n  font-size: 11px;\n  letter-spacing: 1px;\n  margin-bottom: 5px;\n  text-transform: uppercase; }\n\n.number {\n  align-items: center;\n  background-color: whitesmoke;\n  border-radius: 9999px;\n  display: inline-flex;\n  font-size: 1.25rem;\n  height: 2em;\n  justify-content: center;\n  margin-right: 1.5rem;\n  min-width: 2.5em;\n  padding: 0.25rem 0.5rem;\n  text-align: center;\n  vertical-align: top; }\n\n/* Bulma Form */\n.select select, .textarea, .input {\n  background-color: white;\n  border-color: #dbdbdb;\n  border-radius: 4px;\n  color: #363636; }\n  .select select::-moz-placeholder, .textarea::-moz-placeholder, .input::-moz-placeholder {\n    color: rgba(54, 54, 54, 0.3); }\n  .select select::-webkit-input-placeholder, .textarea::-webkit-input-placeholder, .input::-webkit-input-placeholder {\n    color: rgba(54, 54, 54, 0.3); }\n  .select select:-moz-placeholder, .textarea:-moz-placeholder, .input:-moz-placeholder {\n    color: rgba(54, 54, 54, 0.3); }\n  .select select:-ms-input-placeholder, .textarea:-ms-input-placeholder, .input:-ms-input-placeholder {\n    color: rgba(54, 54, 54, 0.3); }\n  .select select:hover, .textarea:hover, .input:hover, .select select.is-hovered, .is-hovered.textarea, .is-hovered.input {\n    border-color: #b5b5b5; }\n  .select select:focus, .textarea:focus, .input:focus, .select select.is-focused, .is-focused.textarea, .is-focused.input, .select select:active, .textarea:active, .input:active, .select select.is-active, .is-active.textarea, .is-active.input {\n    border-color: #ff0d68;\n    box-shadow: 0 0 0 0.125em rgba(255, 13, 104, 0.25); }\n  .select select[disabled], [disabled].textarea, [disabled].input, fieldset[disabled] .select select, .select fieldset[disabled] select, fieldset[disabled] .textarea, fieldset[disabled] .input {\n    background-color: whitesmoke;\n    border-color: whitesmoke;\n    box-shadow: none;\n    color: #7a7a7a; }\n    .select select[disabled]::-moz-placeholder, [disabled].textarea::-moz-placeholder, [disabled].input::-moz-placeholder, fieldset[disabled] .select select::-moz-placeholder, .select fieldset[disabled] select::-moz-placeholder, fieldset[disabled] .textarea::-moz-placeholder, fieldset[disabled] .input::-moz-placeholder {\n      color: rgba(122, 122, 122, 0.3); }\n    .select select[disabled]::-webkit-input-placeholder, [disabled].textarea::-webkit-input-placeholder, [disabled].input::-webkit-input-placeholder, fieldset[disabled] .select select::-webkit-input-placeholder, .select fieldset[disabled] select::-webkit-input-placeholder, fieldset[disabled] .textarea::-webkit-input-placeholder, fieldset[disabled] .input::-webkit-input-placeholder {\n      color: rgba(122, 122, 122, 0.3); }\n    .select select[disabled]:-moz-placeholder, [disabled].textarea:-moz-placeholder, [disabled].input:-moz-placeholder, fieldset[disabled] .select select:-moz-placeholder, .select fieldset[disabled] select:-moz-placeholder, fieldset[disabled] .textarea:-moz-placeholder, fieldset[disabled] .input:-moz-placeholder {\n      color: rgba(122, 122, 122, 0.3); }\n    .select select[disabled]:-ms-input-placeholder, [disabled].textarea:-ms-input-placeholder, [disabled].input:-ms-input-placeholder, fieldset[disabled] .select select:-ms-input-placeholder, .select fieldset[disabled] select:-ms-input-placeholder, fieldset[disabled] .textarea:-ms-input-placeholder, fieldset[disabled] .input:-ms-input-placeholder {\n      color: rgba(122, 122, 122, 0.3); }\n\n.textarea, .input {\n  box-shadow: inset 0 0.0625em 0.125em rgba(10, 10, 10, 0.05);\n  max-width: 100%;\n  width: 100%; }\n  [readonly].textarea, [readonly].input {\n    box-shadow: none; }\n  .is-white.textarea, .is-white.input {\n    border-color: white; }\n    .is-white.textarea:focus, .is-white.input:focus, .is-white.is-focused.textarea, .is-white.is-focused.input, .is-white.textarea:active, .is-white.input:active, .is-white.is-active.textarea, .is-white.is-active.input {\n      box-shadow: 0 0 0 0.125em rgba(255, 255, 255, 0.25); }\n  .is-black.textarea, .is-black.input {\n    border-color: #0a0a0a; }\n    .is-black.textarea:focus, .is-black.input:focus, .is-black.is-focused.textarea, .is-black.is-focused.input, .is-black.textarea:active, .is-black.input:active, .is-black.is-active.textarea, .is-black.is-active.input {\n      box-shadow: 0 0 0 0.125em rgba(10, 10, 10, 0.25); }\n  .is-light.textarea, .is-light.input {\n    border-color: whitesmoke; }\n    .is-light.textarea:focus, .is-light.input:focus, .is-light.is-focused.textarea, .is-light.is-focused.input, .is-light.textarea:active, .is-light.input:active, .is-light.is-active.textarea, .is-light.is-active.input {\n      box-shadow: 0 0 0 0.125em rgba(245, 245, 245, 0.25); }\n  .is-dark.textarea, .is-dark.input {\n    border-color: #363636; }\n    .is-dark.textarea:focus, .is-dark.input:focus, .is-dark.is-focused.textarea, .is-dark.is-focused.input, .is-dark.textarea:active, .is-dark.input:active, .is-dark.is-active.textarea, .is-dark.is-active.input {\n      box-shadow: 0 0 0 0.125em rgba(54, 54, 54, 0.25); }\n  .is-primary.textarea, .is-primary.input {\n    border-color: #ff0d68; }\n    .is-primary.textarea:focus, .is-primary.input:focus, .is-primary.is-focused.textarea, .is-primary.is-focused.input, .is-primary.textarea:active, .is-primary.input:active, .is-primary.is-active.textarea, .is-primary.is-active.input {\n      box-shadow: 0 0 0 0.125em rgba(255, 13, 104, 0.25); }\n  .is-link.textarea, .is-link.input {\n    border-color: #ff0d68; }\n    .is-link.textarea:focus, .is-link.input:focus, .is-link.is-focused.textarea, .is-link.is-focused.input, .is-link.textarea:active, .is-link.input:active, .is-link.is-active.textarea, .is-link.is-active.input {\n      box-shadow: 0 0 0 0.125em rgba(255, 13, 104, 0.25); }\n  .is-info.textarea, .is-info.input {\n    border-color: #0092FF; }\n    .is-info.textarea:focus, .is-info.input:focus, .is-info.is-focused.textarea, .is-info.is-focused.input, .is-info.textarea:active, .is-info.input:active, .is-info.is-active.textarea, .is-info.is-active.input {\n      box-shadow: 0 0 0 0.125em rgba(0, 146, 255, 0.25); }\n  .is-success.textarea, .is-success.input {\n    border-color: #16DB93; }\n    .is-success.textarea:focus, .is-success.input:focus, .is-success.is-focused.textarea, .is-success.is-focused.input, .is-success.textarea:active, .is-success.input:active, .is-success.is-active.textarea, .is-success.is-active.input {\n      box-shadow: 0 0 0 0.125em rgba(22, 219, 147, 0.25); }\n  .is-warning.textarea, .is-warning.input {\n    border-color: #FFE900; }\n    .is-warning.textarea:focus, .is-warning.input:focus, .is-warning.is-focused.textarea, .is-warning.is-focused.input, .is-warning.textarea:active, .is-warning.input:active, .is-warning.is-active.textarea, .is-warning.is-active.input {\n      box-shadow: 0 0 0 0.125em rgba(255, 233, 0, 0.25); }\n  .is-danger.textarea, .is-danger.input {\n    border-color: #f14668; }\n    .is-danger.textarea:focus, .is-danger.input:focus, .is-danger.is-focused.textarea, .is-danger.is-focused.input, .is-danger.textarea:active, .is-danger.input:active, .is-danger.is-active.textarea, .is-danger.is-active.input {\n      box-shadow: 0 0 0 0.125em rgba(241, 70, 104, 0.25); }\n  .is-small.textarea, .is-small.input {\n    border-radius: 2px;\n    font-size: 0.75rem; }\n  .is-medium.textarea, .is-medium.input {\n    font-size: 1.25rem; }\n  .is-large.textarea, .is-large.input {\n    font-size: 1.5rem; }\n  .is-fullwidth.textarea, .is-fullwidth.input {\n    display: block;\n    width: 100%; }\n  .is-inline.textarea, .is-inline.input {\n    display: inline;\n    width: auto; }\n\n.input.is-rounded {\n  border-radius: 9999px;\n  padding-left: calc(calc(0.75em - 1px) + 0.375em);\n  padding-right: calc(calc(0.75em - 1px) + 0.375em); }\n\n.input.is-static {\n  background-color: transparent;\n  border-color: transparent;\n  box-shadow: none;\n  padding-left: 0;\n  padding-right: 0; }\n\n.textarea {\n  display: block;\n  max-width: 100%;\n  min-width: 100%;\n  padding: calc(0.75em - 1px);\n  resize: vertical; }\n  .textarea:not([rows]) {\n    max-height: 40em;\n    min-height: 8em; }\n  .textarea[rows] {\n    height: initial; }\n  .textarea.has-fixed-size {\n    resize: none; }\n\n.radio, .checkbox {\n  cursor: pointer;\n  display: inline-block;\n  line-height: 1.25;\n  position: relative; }\n  .radio input, .checkbox input {\n    cursor: pointer; }\n  .radio:hover, .checkbox:hover {\n    color: #363636; }\n  [disabled].radio, [disabled].checkbox, fieldset[disabled] .radio, fieldset[disabled] .checkbox,\n  .radio input[disabled],\n  .checkbox input[disabled] {\n    color: #7a7a7a;\n    cursor: not-allowed; }\n\n.radio + .radio {\n  margin-left: 0.5em; }\n\n.select {\n  display: inline-block;\n  max-width: 100%;\n  position: relative;\n  vertical-align: top; }\n  .select:not(.is-multiple) {\n    height: 2.5em; }\n  .select:not(.is-multiple):not(.is-loading)::after {\n    border-color: #ff0d68;\n    right: 1.125em;\n    z-index: 4; }\n  .select.is-rounded select {\n    border-radius: 9999px;\n    padding-left: 1em; }\n  .select select {\n    cursor: pointer;\n    display: block;\n    font-size: 1em;\n    max-width: 100%;\n    outline: none; }\n    .select select::-ms-expand {\n      display: none; }\n    .select select[disabled]:hover, fieldset[disabled] .select select:hover {\n      border-color: whitesmoke; }\n    .select select:not([multiple]) {\n      padding-right: 2.5em; }\n    .select select[multiple] {\n      height: auto;\n      padding: 0; }\n      .select select[multiple] option {\n        padding: 0.5em 1em; }\n  .select:not(.is-multiple):not(.is-loading):hover::after {\n    border-color: #363636; }\n  .select.is-white:not(:hover)::after {\n    border-color: white; }\n  .select.is-white select {\n    border-color: white; }\n    .select.is-white select:hover, .select.is-white select.is-hovered {\n      border-color: #f2f2f2; }\n    .select.is-white select:focus, .select.is-white select.is-focused, .select.is-white select:active, .select.is-white select.is-active {\n      box-shadow: 0 0 0 0.125em rgba(255, 255, 255, 0.25); }\n  .select.is-black:not(:hover)::after {\n    border-color: #0a0a0a; }\n  .select.is-black select {\n    border-color: #0a0a0a; }\n    .select.is-black select:hover, .select.is-black select.is-hovered {\n      border-color: black; }\n    .select.is-black select:focus, .select.is-black select.is-focused, .select.is-black select:active, .select.is-black select.is-active {\n      box-shadow: 0 0 0 0.125em rgba(10, 10, 10, 0.25); }\n  .select.is-light:not(:hover)::after {\n    border-color: whitesmoke; }\n  .select.is-light select {\n    border-color: whitesmoke; }\n    .select.is-light select:hover, .select.is-light select.is-hovered {\n      border-color: #e8e8e8; }\n    .select.is-light select:focus, .select.is-light select.is-focused, .select.is-light select:active, .select.is-light select.is-active {\n      box-shadow: 0 0 0 0.125em rgba(245, 245, 245, 0.25); }\n  .select.is-dark:not(:hover)::after {\n    border-color: #363636; }\n  .select.is-dark select {\n    border-color: #363636; }\n    .select.is-dark select:hover, .select.is-dark select.is-hovered {\n      border-color: #292929; }\n    .select.is-dark select:focus, .select.is-dark select.is-focused, .select.is-dark select:active, .select.is-dark select.is-active {\n      box-shadow: 0 0 0 0.125em rgba(54, 54, 54, 0.25); }\n  .select.is-primary:not(:hover)::after {\n    border-color: #ff0d68; }\n  .select.is-primary select {\n    border-color: #ff0d68; }\n    .select.is-primary select:hover, .select.is-primary select.is-hovered {\n      border-color: #f3005b; }\n    .select.is-primary select:focus, .select.is-primary select.is-focused, .select.is-primary select:active, .select.is-primary select.is-active {\n      box-shadow: 0 0 0 0.125em rgba(255, 13, 104, 0.25); }\n  .select.is-link:not(:hover)::after {\n    border-color: #ff0d68; }\n  .select.is-link select {\n    border-color: #ff0d68; }\n    .select.is-link select:hover, .select.is-link select.is-hovered {\n      border-color: #f3005b; }\n    .select.is-link select:focus, .select.is-link select.is-focused, .select.is-link select:active, .select.is-link select.is-active {\n      box-shadow: 0 0 0 0.125em rgba(255, 13, 104, 0.25); }\n  .select.is-info:not(:hover)::after {\n    border-color: #0092FF; }\n  .select.is-info select {\n    border-color: #0092FF; }\n    .select.is-info select:hover, .select.is-info select.is-hovered {\n      border-color: #0083e6; }\n    .select.is-info select:focus, .select.is-info select.is-focused, .select.is-info select:active, .select.is-info select.is-active {\n      box-shadow: 0 0 0 0.125em rgba(0, 146, 255, 0.25); }\n  .select.is-success:not(:hover)::after {\n    border-color: #16DB93; }\n  .select.is-success select {\n    border-color: #16DB93; }\n    .select.is-success select:hover, .select.is-success select.is-hovered {\n      border-color: #14c483; }\n    .select.is-success select:focus, .select.is-success select.is-focused, .select.is-success select:active, .select.is-success select.is-active {\n      box-shadow: 0 0 0 0.125em rgba(22, 219, 147, 0.25); }\n  .select.is-warning:not(:hover)::after {\n    border-color: #FFE900; }\n  .select.is-warning select {\n    border-color: #FFE900; }\n    .select.is-warning select:hover, .select.is-warning select.is-hovered {\n      border-color: #e6d200; }\n    .select.is-warning select:focus, .select.is-warning select.is-focused, .select.is-warning select:active, .select.is-warning select.is-active {\n      box-shadow: 0 0 0 0.125em rgba(255, 233, 0, 0.25); }\n  .select.is-danger:not(:hover)::after {\n    border-color: #f14668; }\n  .select.is-danger select {\n    border-color: #f14668; }\n    .select.is-danger select:hover, .select.is-danger select.is-hovered {\n      border-color: #ef2e55; }\n    .select.is-danger select:focus, .select.is-danger select.is-focused, .select.is-danger select:active, .select.is-danger select.is-active {\n      box-shadow: 0 0 0 0.125em rgba(241, 70, 104, 0.25); }\n  .select.is-small {\n    border-radius: 2px;\n    font-size: 0.75rem; }\n  .select.is-medium {\n    font-size: 1.25rem; }\n  .select.is-large {\n    font-size: 1.5rem; }\n  .select.is-disabled::after {\n    border-color: #7a7a7a !important;\n    opacity: 0.5; }\n  .select.is-fullwidth {\n    width: 100%; }\n    .select.is-fullwidth select {\n      width: 100%; }\n  .select.is-loading::after {\n    margin-top: 0;\n    position: absolute;\n    right: 0.625em;\n    top: 0.625em;\n    transform: none; }\n  .select.is-loading.is-small:after {\n    font-size: 0.75rem; }\n  .select.is-loading.is-medium:after {\n    font-size: 1.25rem; }\n  .select.is-loading.is-large:after {\n    font-size: 1.5rem; }\n\n.file {\n  align-items: stretch;\n  display: flex;\n  justify-content: flex-start;\n  position: relative; }\n  .file.is-white .file-cta {\n    background-color: white;\n    border-color: transparent;\n    color: #0a0a0a; }\n  .file.is-white:hover .file-cta, .file.is-white.is-hovered .file-cta {\n    background-color: #f9f9f9;\n    border-color: transparent;\n    color: #0a0a0a; }\n  .file.is-white:focus .file-cta, .file.is-white.is-focused .file-cta {\n    border-color: transparent;\n    box-shadow: 0 0 0.5em rgba(255, 255, 255, 0.25);\n    color: #0a0a0a; }\n  .file.is-white:active .file-cta, .file.is-white.is-active .file-cta {\n    background-color: #f2f2f2;\n    border-color: transparent;\n    color: #0a0a0a; }\n  .file.is-black .file-cta {\n    background-color: #0a0a0a;\n    border-color: transparent;\n    color: white; }\n  .file.is-black:hover .file-cta, .file.is-black.is-hovered .file-cta {\n    background-color: #040404;\n    border-color: transparent;\n    color: white; }\n  .file.is-black:focus .file-cta, .file.is-black.is-focused .file-cta {\n    border-color: transparent;\n    box-shadow: 0 0 0.5em rgba(10, 10, 10, 0.25);\n    color: white; }\n  .file.is-black:active .file-cta, .file.is-black.is-active .file-cta {\n    background-color: black;\n    border-color: transparent;\n    color: white; }\n  .file.is-light .file-cta {\n    background-color: whitesmoke;\n    border-color: transparent;\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-light:hover .file-cta, .file.is-light.is-hovered .file-cta {\n    background-color: #eeeeee;\n    border-color: transparent;\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-light:focus .file-cta, .file.is-light.is-focused .file-cta {\n    border-color: transparent;\n    box-shadow: 0 0 0.5em rgba(245, 245, 245, 0.25);\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-light:active .file-cta, .file.is-light.is-active .file-cta {\n    background-color: #e8e8e8;\n    border-color: transparent;\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-dark .file-cta {\n    background-color: #363636;\n    border-color: transparent;\n    color: #fff; }\n  .file.is-dark:hover .file-cta, .file.is-dark.is-hovered .file-cta {\n    background-color: #2f2f2f;\n    border-color: transparent;\n    color: #fff; }\n  .file.is-dark:focus .file-cta, .file.is-dark.is-focused .file-cta {\n    border-color: transparent;\n    box-shadow: 0 0 0.5em rgba(54, 54, 54, 0.25);\n    color: #fff; }\n  .file.is-dark:active .file-cta, .file.is-dark.is-active .file-cta {\n    background-color: #292929;\n    border-color: transparent;\n    color: #fff; }\n  .file.is-primary .file-cta {\n    background-color: #ff0d68;\n    border-color: transparent;\n    color: #fff; }\n  .file.is-primary:hover .file-cta, .file.is-primary.is-hovered .file-cta {\n    background-color: #ff0060;\n    border-color: transparent;\n    color: #fff; }\n  .file.is-primary:focus .file-cta, .file.is-primary.is-focused .file-cta {\n    border-color: transparent;\n    box-shadow: 0 0 0.5em rgba(255, 13, 104, 0.25);\n    color: #fff; }\n  .file.is-primary:active .file-cta, .file.is-primary.is-active .file-cta {\n    background-color: #f3005b;\n    border-color: transparent;\n    color: #fff; }\n  .file.is-link .file-cta {\n    background-color: #ff0d68;\n    border-color: transparent;\n    color: #fff; }\n  .file.is-link:hover .file-cta, .file.is-link.is-hovered .file-cta {\n    background-color: #ff0060;\n    border-color: transparent;\n    color: #fff; }\n  .file.is-link:focus .file-cta, .file.is-link.is-focused .file-cta {\n    border-color: transparent;\n    box-shadow: 0 0 0.5em rgba(255, 13, 104, 0.25);\n    color: #fff; }\n  .file.is-link:active .file-cta, .file.is-link.is-active .file-cta {\n    background-color: #f3005b;\n    border-color: transparent;\n    color: #fff; }\n  .file.is-info .file-cta {\n    background-color: #0092FF;\n    border-color: transparent;\n    color: #fff; }\n  .file.is-info:hover .file-cta, .file.is-info.is-hovered .file-cta {\n    background-color: #008bf2;\n    border-color: transparent;\n    color: #fff; }\n  .file.is-info:focus .file-cta, .file.is-info.is-focused .file-cta {\n    border-color: transparent;\n    box-shadow: 0 0 0.5em rgba(0, 146, 255, 0.25);\n    color: #fff; }\n  .file.is-info:active .file-cta, .file.is-info.is-active .file-cta {\n    background-color: #0083e6;\n    border-color: transparent;\n    color: #fff; }\n  .file.is-success .file-cta {\n    background-color: #16DB93;\n    border-color: transparent;\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-success:hover .file-cta, .file.is-success.is-hovered .file-cta {\n    background-color: #15cf8b;\n    border-color: transparent;\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-success:focus .file-cta, .file.is-success.is-focused .file-cta {\n    border-color: transparent;\n    box-shadow: 0 0 0.5em rgba(22, 219, 147, 0.25);\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-success:active .file-cta, .file.is-success.is-active .file-cta {\n    background-color: #14c483;\n    border-color: transparent;\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-warning .file-cta {\n    background-color: #FFE900;\n    border-color: transparent;\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-warning:hover .file-cta, .file.is-warning.is-hovered .file-cta {\n    background-color: #f2dd00;\n    border-color: transparent;\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-warning:focus .file-cta, .file.is-warning.is-focused .file-cta {\n    border-color: transparent;\n    box-shadow: 0 0 0.5em rgba(255, 233, 0, 0.25);\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-warning:active .file-cta, .file.is-warning.is-active .file-cta {\n    background-color: #e6d200;\n    border-color: transparent;\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-danger .file-cta {\n    background-color: #f14668;\n    border-color: transparent;\n    color: #fff; }\n  .file.is-danger:hover .file-cta, .file.is-danger.is-hovered .file-cta {\n    background-color: #f03a5f;\n    border-color: transparent;\n    color: #fff; }\n  .file.is-danger:focus .file-cta, .file.is-danger.is-focused .file-cta {\n    border-color: transparent;\n    box-shadow: 0 0 0.5em rgba(241, 70, 104, 0.25);\n    color: #fff; }\n  .file.is-danger:active .file-cta, .file.is-danger.is-active .file-cta {\n    background-color: #ef2e55;\n    border-color: transparent;\n    color: #fff; }\n  .file.is-small {\n    font-size: 0.75rem; }\n  .file.is-normal {\n    font-size: 1rem; }\n  .file.is-medium {\n    font-size: 1.25rem; }\n    .file.is-medium .file-icon .fa {\n      font-size: 21px; }\n  .file.is-large {\n    font-size: 1.5rem; }\n    .file.is-large .file-icon .fa {\n      font-size: 28px; }\n  .file.has-name .file-cta {\n    border-bottom-right-radius: 0;\n    border-top-right-radius: 0; }\n  .file.has-name .file-name {\n    border-bottom-left-radius: 0;\n    border-top-left-radius: 0; }\n  .file.has-name.is-empty .file-cta {\n    border-radius: 4px; }\n  .file.has-name.is-empty .file-name {\n    display: none; }\n  .file.is-boxed .file-label {\n    flex-direction: column; }\n  .file.is-boxed .file-cta {\n    flex-direction: column;\n    height: auto;\n    padding: 1em 3em; }\n  .file.is-boxed .file-name {\n    border-width: 0 1px 1px; }\n  .file.is-boxed .file-icon {\n    height: 1.5em;\n    width: 1.5em; }\n    .file.is-boxed .file-icon .fa {\n      font-size: 21px; }\n  .file.is-boxed.is-small .file-icon .fa {\n    font-size: 14px; }\n  .file.is-boxed.is-medium .file-icon .fa {\n    font-size: 28px; }\n  .file.is-boxed.is-large .file-icon .fa {\n    font-size: 35px; }\n  .file.is-boxed.has-name .file-cta {\n    border-radius: 4px 4px 0 0; }\n  .file.is-boxed.has-name .file-name {\n    border-radius: 0 0 4px 4px;\n    border-width: 0 1px 1px; }\n  .file.is-centered {\n    justify-content: center; }\n  .file.is-fullwidth .file-label {\n    width: 100%; }\n  .file.is-fullwidth .file-name {\n    flex-grow: 1;\n    max-width: none; }\n  .file.is-right {\n    justify-content: flex-end; }\n    .file.is-right .file-cta {\n      border-radius: 0 4px 4px 0; }\n    .file.is-right .file-name {\n      border-radius: 4px 0 0 4px;\n      border-width: 1px 0 1px 1px;\n      order: -1; }\n\n.file-label {\n  align-items: stretch;\n  display: flex;\n  cursor: pointer;\n  justify-content: flex-start;\n  overflow: hidden;\n  position: relative; }\n  .file-label:hover .file-cta {\n    background-color: #eeeeee;\n    color: #363636; }\n  .file-label:hover .file-name {\n    border-color: #d5d5d5; }\n  .file-label:active .file-cta {\n    background-color: #e8e8e8;\n    color: #363636; }\n  .file-label:active .file-name {\n    border-color: #cfcfcf; }\n\n.file-input {\n  height: 100%;\n  left: 0;\n  opacity: 0;\n  outline: none;\n  position: absolute;\n  top: 0;\n  width: 100%; }\n\n.file-cta,\n.file-name {\n  border-color: #dbdbdb;\n  border-radius: 4px;\n  font-size: 1em;\n  padding-left: 1em;\n  padding-right: 1em;\n  white-space: nowrap; }\n\n.file-cta {\n  background-color: whitesmoke;\n  color: #4a4a4a; }\n\n.file-name {\n  border-color: #dbdbdb;\n  border-style: solid;\n  border-width: 1px 1px 1px 0;\n  display: block;\n  max-width: 16em;\n  overflow: hidden;\n  text-align: inherit;\n  text-overflow: ellipsis; }\n\n.file-icon {\n  align-items: center;\n  display: flex;\n  height: 1em;\n  justify-content: center;\n  margin-right: 0.5em;\n  width: 1em; }\n  .file-icon .fa {\n    font-size: 14px; }\n\n.label {\n  color: #363636;\n  display: block;\n  font-size: 1rem;\n  font-weight: 700; }\n  .label:not(:last-child) {\n    margin-bottom: 0.5em; }\n  .label.is-small {\n    font-size: 0.75rem; }\n  .label.is-medium {\n    font-size: 1.25rem; }\n  .label.is-large {\n    font-size: 1.5rem; }\n\n.help {\n  display: block;\n  font-size: 0.75rem;\n  margin-top: 0.25rem; }\n  .help.is-white {\n    color: white; }\n  .help.is-black {\n    color: #0a0a0a; }\n  .help.is-light {\n    color: whitesmoke; }\n  .help.is-dark {\n    color: #363636; }\n  .help.is-primary {\n    color: #ff0d68; }\n  .help.is-link {\n    color: #ff0d68; }\n  .help.is-info {\n    color: #0092FF; }\n  .help.is-success {\n    color: #16DB93; }\n  .help.is-warning {\n    color: #FFE900; }\n  .help.is-danger {\n    color: #f14668; }\n\n.field:not(:last-child) {\n  margin-bottom: 0.75rem; }\n\n.field.has-addons {\n  display: flex;\n  justify-content: flex-start; }\n  .field.has-addons .control:not(:last-child) {\n    margin-right: -1px; }\n  .field.has-addons .control:not(:first-child):not(:last-child) .button,\n  .field.has-addons .control:not(:first-child):not(:last-child) .input,\n  .field.has-addons .control:not(:first-child):not(:last-child) .select select {\n    border-radius: 0; }\n  .field.has-addons .control:first-child:not(:only-child) .button,\n  .field.has-addons .control:first-child:not(:only-child) .input,\n  .field.has-addons .control:first-child:not(:only-child) .select select {\n    border-bottom-right-radius: 0;\n    border-top-right-radius: 0; }\n  .field.has-addons .control:last-child:not(:only-child) .button,\n  .field.has-addons .control:last-child:not(:only-child) .input,\n  .field.has-addons .control:last-child:not(:only-child) .select select {\n    border-bottom-left-radius: 0;\n    border-top-left-radius: 0; }\n  .field.has-addons .control .button:not([disabled]):hover, .field.has-addons .control .button:not([disabled]).is-hovered,\n  .field.has-addons .control .input:not([disabled]):hover,\n  .field.has-addons .control .input:not([disabled]).is-hovered,\n  .field.has-addons .control .select select:not([disabled]):hover,\n  .field.has-addons .control .select select:not([disabled]).is-hovered {\n    z-index: 2; }\n  .field.has-addons .control .button:not([disabled]):focus, .field.has-addons .control .button:not([disabled]).is-focused, .field.has-addons .control .button:not([disabled]):active, .field.has-addons .control .button:not([disabled]).is-active,\n  .field.has-addons .control .input:not([disabled]):focus,\n  .field.has-addons .control .input:not([disabled]).is-focused,\n  .field.has-addons .control .input:not([disabled]):active,\n  .field.has-addons .control .input:not([disabled]).is-active,\n  .field.has-addons .control .select select:not([disabled]):focus,\n  .field.has-addons .control .select select:not([disabled]).is-focused,\n  .field.has-addons .control .select select:not([disabled]):active,\n  .field.has-addons .control .select select:not([disabled]).is-active {\n    z-index: 3; }\n    .field.has-addons .control .button:not([disabled]):focus:hover, .field.has-addons .control .button:not([disabled]).is-focused:hover, .field.has-addons .control .button:not([disabled]):active:hover, .field.has-addons .control .button:not([disabled]).is-active:hover,\n    .field.has-addons .control .input:not([disabled]):focus:hover,\n    .field.has-addons .control .input:not([disabled]).is-focused:hover,\n    .field.has-addons .control .input:not([disabled]):active:hover,\n    .field.has-addons .control .input:not([disabled]).is-active:hover,\n    .field.has-addons .control .select select:not([disabled]):focus:hover,\n    .field.has-addons .control .select select:not([disabled]).is-focused:hover,\n    .field.has-addons .control .select select:not([disabled]):active:hover,\n    .field.has-addons .control .select select:not([disabled]).is-active:hover {\n      z-index: 4; }\n  .field.has-addons .control.is-expanded {\n    flex-grow: 1;\n    flex-shrink: 1; }\n  .field.has-addons.has-addons-centered {\n    justify-content: center; }\n  .field.has-addons.has-addons-right {\n    justify-content: flex-end; }\n  .field.has-addons.has-addons-fullwidth .control {\n    flex-grow: 1;\n    flex-shrink: 0; }\n\n.field.is-grouped {\n  display: flex;\n  justify-content: flex-start; }\n  .field.is-grouped > .control {\n    flex-shrink: 0; }\n    .field.is-grouped > .control:not(:last-child) {\n      margin-bottom: 0;\n      margin-right: 0.75rem; }\n    .field.is-grouped > .control.is-expanded {\n      flex-grow: 1;\n      flex-shrink: 1; }\n  .field.is-grouped.is-grouped-centered {\n    justify-content: center; }\n  .field.is-grouped.is-grouped-right {\n    justify-content: flex-end; }\n  .field.is-grouped.is-grouped-multiline {\n    flex-wrap: wrap; }\n    .field.is-grouped.is-grouped-multiline > .control:last-child, .field.is-grouped.is-grouped-multiline > .control:not(:last-child) {\n      margin-bottom: 0.75rem; }\n    .field.is-grouped.is-grouped-multiline:last-child {\n      margin-bottom: -0.75rem; }\n    .field.is-grouped.is-grouped-multiline:not(:last-child) {\n      margin-bottom: 0; }\n\n@media screen and (min-width: 769px), print {\n  .field.is-horizontal {\n    display: flex; } }\n\n.field-label .label {\n  font-size: inherit; }\n\n@media screen and (max-width: 768px) {\n  .field-label {\n    margin-bottom: 0.5rem; } }\n\n@media screen and (min-width: 769px), print {\n  .field-label {\n    flex-basis: 0;\n    flex-grow: 1;\n    flex-shrink: 0;\n    margin-right: 1.5rem;\n    text-align: right; }\n    .field-label.is-small {\n      font-size: 0.75rem;\n      padding-top: 0.375em; }\n    .field-label.is-normal {\n      padding-top: 0.375em; }\n    .field-label.is-medium {\n      font-size: 1.25rem;\n      padding-top: 0.375em; }\n    .field-label.is-large {\n      font-size: 1.5rem;\n      padding-top: 0.375em; } }\n\n.field-body .field .field {\n  margin-bottom: 0; }\n\n@media screen and (min-width: 769px), print {\n  .field-body {\n    display: flex;\n    flex-basis: 0;\n    flex-grow: 5;\n    flex-shrink: 1; }\n    .field-body .field {\n      margin-bottom: 0; }\n    .field-body > .field {\n      flex-shrink: 1; }\n      .field-body > .field:not(.is-narrow) {\n        flex-grow: 1; }\n      .field-body > .field:not(:last-child) {\n        margin-right: 0.75rem; } }\n\n.control {\n  box-sizing: border-box;\n  clear: both;\n  font-size: 1rem;\n  position: relative;\n  text-align: inherit; }\n  .control.has-icons-left .input:focus ~ .icon,\n  .control.has-icons-left .select:focus ~ .icon, .control.has-icons-right .input:focus ~ .icon,\n  .control.has-icons-right .select:focus ~ .icon {\n    color: #4a4a4a; }\n  .control.has-icons-left .input.is-small ~ .icon,\n  .control.has-icons-left .select.is-small ~ .icon, .control.has-icons-right .input.is-small ~ .icon,\n  .control.has-icons-right .select.is-small ~ .icon {\n    font-size: 0.75rem; }\n  .control.has-icons-left .input.is-medium ~ .icon,\n  .control.has-icons-left .select.is-medium ~ .icon, .control.has-icons-right .input.is-medium ~ .icon,\n  .control.has-icons-right .select.is-medium ~ .icon {\n    font-size: 1.25rem; }\n  .control.has-icons-left .input.is-large ~ .icon,\n  .control.has-icons-left .select.is-large ~ .icon, .control.has-icons-right .input.is-large ~ .icon,\n  .control.has-icons-right .select.is-large ~ .icon {\n    font-size: 1.5rem; }\n  .control.has-icons-left .icon, .control.has-icons-right .icon {\n    color: #dbdbdb;\n    height: 2.5em;\n    pointer-events: none;\n    position: absolute;\n    top: 0;\n    width: 2.5em;\n    z-index: 4; }\n  .control.has-icons-left .input,\n  .control.has-icons-left .select select {\n    padding-left: 2.5em; }\n  .control.has-icons-left .icon.is-left {\n    left: 0; }\n  .control.has-icons-right .input,\n  .control.has-icons-right .select select {\n    padding-right: 2.5em; }\n  .control.has-icons-right .icon.is-right {\n    right: 0; }\n  .control.is-loading::after {\n    position: absolute !important;\n    right: 0.625em;\n    top: 0.625em;\n    z-index: 4; }\n  .control.is-loading.is-small:after {\n    font-size: 0.75rem; }\n  .control.is-loading.is-medium:after {\n    font-size: 1.25rem; }\n  .control.is-loading.is-large:after {\n    font-size: 1.5rem; }\n\n/* Bulma Components */\n.breadcrumb {\n  font-size: 1rem;\n  white-space: nowrap; }\n  .breadcrumb a {\n    align-items: center;\n    color: #ff0d68;\n    display: flex;\n    justify-content: center;\n    padding: 0 0.75em; }\n    .breadcrumb a:hover {\n      color: #363636; }\n  .breadcrumb li {\n    align-items: center;\n    display: flex; }\n    .breadcrumb li:first-child a {\n      padding-left: 0; }\n    .breadcrumb li.is-active a {\n      color: #363636;\n      cursor: default;\n      pointer-events: none; }\n    .breadcrumb li + li::before {\n      color: #b5b5b5;\n      content: \"\\0002f\"; }\n  .breadcrumb ul,\n  .breadcrumb ol {\n    align-items: flex-start;\n    display: flex;\n    flex-wrap: wrap;\n    justify-content: flex-start; }\n  .breadcrumb .icon:first-child {\n    margin-right: 0.5em; }\n  .breadcrumb .icon:last-child {\n    margin-left: 0.5em; }\n  .breadcrumb.is-centered ol,\n  .breadcrumb.is-centered ul {\n    justify-content: center; }\n  .breadcrumb.is-right ol,\n  .breadcrumb.is-right ul {\n    justify-content: flex-end; }\n  .breadcrumb.is-small {\n    font-size: 0.75rem; }\n  .breadcrumb.is-medium {\n    font-size: 1.25rem; }\n  .breadcrumb.is-large {\n    font-size: 1.5rem; }\n  .breadcrumb.has-arrow-separator li + li::before {\n    content: \"\\02192\"; }\n  .breadcrumb.has-bullet-separator li + li::before {\n    content: \"\\02022\"; }\n  .breadcrumb.has-dot-separator li + li::before {\n    content: \"\\000b7\"; }\n  .breadcrumb.has-succeeds-separator li + li::before {\n    content: \"\\0227B\"; }\n\n.card {\n  background-color: white;\n  border-radius: 0.25rem;\n  box-shadow: 0 0.5em 1em -0.125em rgba(10, 10, 10, 0.1), 0 0px 0 1px rgba(10, 10, 10, 0.02);\n  color: #4a4a4a;\n  max-width: 100%;\n  position: relative; }\n\n.card-footer:first-child, .card-content:first-child, .card-header:first-child {\n  border-top-left-radius: 0.25rem;\n  border-top-right-radius: 0.25rem; }\n\n.card-footer:last-child, .card-content:last-child, .card-header:last-child {\n  border-bottom-left-radius: 0.25rem;\n  border-bottom-right-radius: 0.25rem; }\n\n.card-header {\n  background-color: transparent;\n  align-items: stretch;\n  box-shadow: 0 0.125em 0.25em rgba(10, 10, 10, 0.1);\n  display: flex; }\n\n.card-header-title {\n  align-items: center;\n  color: #363636;\n  display: flex;\n  flex-grow: 1;\n  font-weight: 700;\n  padding: 0.75rem 1rem; }\n  .card-header-title.is-centered {\n    justify-content: center; }\n\n.card-header-icon {\n  -moz-appearance: none;\n  -webkit-appearance: none;\n  appearance: none;\n  background: none;\n  border: none;\n  color: currentColor;\n  font-family: inherit;\n  font-size: 1em;\n  margin: 0;\n  padding: 0;\n  align-items: center;\n  cursor: pointer;\n  display: flex;\n  justify-content: center;\n  padding: 0.75rem 1rem; }\n\n.card-image {\n  display: block;\n  position: relative; }\n  .card-image:first-child img {\n    border-top-left-radius: 0.25rem;\n    border-top-right-radius: 0.25rem; }\n  .card-image:last-child img {\n    border-bottom-left-radius: 0.25rem;\n    border-bottom-right-radius: 0.25rem; }\n\n.card-content {\n  background-color: transparent;\n  padding: 1.5rem; }\n\n.card-footer {\n  background-color: transparent;\n  border-top: 1px solid #ededed;\n  align-items: stretch;\n  display: flex; }\n\n.card-footer-item {\n  align-items: center;\n  display: flex;\n  flex-basis: 0;\n  flex-grow: 1;\n  flex-shrink: 0;\n  justify-content: center;\n  padding: 0.75rem; }\n  .card-footer-item:not(:last-child) {\n    border-right: 1px solid #ededed; }\n\n.card .media:not(:last-child) {\n  margin-bottom: 1.5rem; }\n\n.dropdown {\n  display: inline-flex;\n  position: relative;\n  vertical-align: top; }\n  .dropdown.is-active .dropdown-menu, .dropdown.is-hoverable:hover .dropdown-menu {\n    display: block; }\n  .dropdown.is-right .dropdown-menu {\n    left: auto;\n    right: 0; }\n  .dropdown.is-up .dropdown-menu {\n    bottom: 100%;\n    padding-bottom: 4px;\n    padding-top: initial;\n    top: auto; }\n\n.dropdown-menu {\n  display: none;\n  left: 0;\n  min-width: 12rem;\n  padding-top: 4px;\n  position: absolute;\n  top: 100%;\n  z-index: 20; }\n\n.dropdown-content {\n  background-color: white;\n  border-radius: 4px;\n  box-shadow: 0 0.5em 1em -0.125em rgba(10, 10, 10, 0.1), 0 0px 0 1px rgba(10, 10, 10, 0.02);\n  padding-bottom: 0.5rem;\n  padding-top: 0.5rem; }\n\n.dropdown-item {\n  color: #4a4a4a;\n  display: block;\n  font-size: 0.875rem;\n  line-height: 1.5;\n  padding: 0.375rem 1rem;\n  position: relative; }\n\na.dropdown-item,\nbutton.dropdown-item {\n  padding-right: 3rem;\n  text-align: inherit;\n  white-space: nowrap;\n  width: 100%; }\n  a.dropdown-item:hover,\n  button.dropdown-item:hover {\n    background-color: whitesmoke;\n    color: #0a0a0a; }\n  a.dropdown-item.is-active,\n  button.dropdown-item.is-active {\n    background-color: #ff0d68;\n    color: #fff; }\n\n.dropdown-divider {\n  background-color: #ededed;\n  border: none;\n  display: block;\n  height: 1px;\n  margin: 0.5rem 0; }\n\n.level {\n  align-items: center;\n  justify-content: space-between; }\n  .level code {\n    border-radius: 4px; }\n  .level img {\n    display: inline-block;\n    vertical-align: top; }\n  .level.is-mobile {\n    display: flex; }\n    .level.is-mobile .level-left,\n    .level.is-mobile .level-right {\n      display: flex; }\n    .level.is-mobile .level-left + .level-right {\n      margin-top: 0; }\n    .level.is-mobile .level-item:not(:last-child) {\n      margin-bottom: 0;\n      margin-right: 0.75rem; }\n    .level.is-mobile .level-item:not(.is-narrow) {\n      flex-grow: 1; }\n  @media screen and (min-width: 769px), print {\n    .level {\n      display: flex; }\n      .level > .level-item:not(.is-narrow) {\n        flex-grow: 1; } }\n.level-item {\n  align-items: center;\n  display: flex;\n  flex-basis: auto;\n  flex-grow: 0;\n  flex-shrink: 0;\n  justify-content: center; }\n  .level-item .title,\n  .level-item .subtitle {\n    margin-bottom: 0; }\n  @media screen and (max-width: 768px) {\n    .level-item:not(:last-child) {\n      margin-bottom: 0.75rem; } }\n.level-left,\n.level-right {\n  flex-basis: auto;\n  flex-grow: 0;\n  flex-shrink: 0; }\n  .level-left .level-item.is-flexible,\n  .level-right .level-item.is-flexible {\n    flex-grow: 1; }\n  @media screen and (min-width: 769px), print {\n    .level-left .level-item:not(:last-child),\n    .level-right .level-item:not(:last-child) {\n      margin-right: 0.75rem; } }\n.level-left {\n  align-items: center;\n  justify-content: flex-start; }\n  @media screen and (max-width: 768px) {\n    .level-left + .level-right {\n      margin-top: 1.5rem; } }\n  @media screen and (min-width: 769px), print {\n    .level-left {\n      display: flex; } }\n.level-right {\n  align-items: center;\n  justify-content: flex-end; }\n  @media screen and (min-width: 769px), print {\n    .level-right {\n      display: flex; } }\n.media {\n  align-items: flex-start;\n  display: flex;\n  text-align: inherit; }\n  .media .content:not(:last-child) {\n    margin-bottom: 0.75rem; }\n  .media .media {\n    border-top: 1px solid rgba(219, 219, 219, 0.5);\n    display: flex;\n    padding-top: 0.75rem; }\n    .media .media .content:not(:last-child),\n    .media .media .control:not(:last-child) {\n      margin-bottom: 0.5rem; }\n    .media .media .media {\n      padding-top: 0.5rem; }\n      .media .media .media + .media {\n        margin-top: 0.5rem; }\n  .media + .media {\n    border-top: 1px solid rgba(219, 219, 219, 0.5);\n    margin-top: 1rem;\n    padding-top: 1rem; }\n  .media.is-large + .media {\n    margin-top: 1.5rem;\n    padding-top: 1.5rem; }\n\n.media-left,\n.media-right {\n  flex-basis: auto;\n  flex-grow: 0;\n  flex-shrink: 0; }\n\n.media-left {\n  margin-right: 1rem; }\n\n.media-right {\n  margin-left: 1rem; }\n\n.media-content {\n  flex-basis: auto;\n  flex-grow: 1;\n  flex-shrink: 1;\n  text-align: inherit; }\n\n@media screen and (max-width: 768px) {\n  .media-content {\n    overflow-x: auto; } }\n\n.menu {\n  font-size: 1rem; }\n  .menu.is-small {\n    font-size: 0.75rem; }\n  .menu.is-medium {\n    font-size: 1.25rem; }\n  .menu.is-large {\n    font-size: 1.5rem; }\n\n.menu-list {\n  line-height: 1.25; }\n  .menu-list a {\n    border-radius: 2px;\n    color: #4a4a4a;\n    display: block;\n    padding: 0.5em 0.75em; }\n    .menu-list a:hover {\n      background-color: whitesmoke;\n      color: #363636; }\n    .menu-list a.is-active {\n      background-color: #ff0d68;\n      color: #fff; }\n  .menu-list li ul {\n    border-left: 1px solid #dbdbdb;\n    margin: 0.75em;\n    padding-left: 0.75em; }\n\n.menu-label {\n  color: #7a7a7a;\n  font-size: 0.75em;\n  letter-spacing: 0.1em;\n  text-transform: uppercase; }\n  .menu-label:not(:first-child) {\n    margin-top: 1em; }\n  .menu-label:not(:last-child) {\n    margin-bottom: 1em; }\n\n.message {\n  background-color: whitesmoke;\n  border-radius: 4px;\n  font-size: 1rem; }\n  .message strong {\n    color: currentColor; }\n  .message a:not(.button):not(.tag):not(.dropdown-item) {\n    color: currentColor;\n    text-decoration: underline; }\n  .message.is-small {\n    font-size: 0.75rem; }\n  .message.is-medium {\n    font-size: 1.25rem; }\n  .message.is-large {\n    font-size: 1.5rem; }\n  .message.is-white {\n    background-color: white; }\n    .message.is-white .message-header {\n      background-color: white;\n      color: #0a0a0a; }\n    .message.is-white .message-body {\n      border-color: white; }\n  .message.is-black {\n    background-color: #fafafa; }\n    .message.is-black .message-header {\n      background-color: #0a0a0a;\n      color: white; }\n    .message.is-black .message-body {\n      border-color: #0a0a0a; }\n  .message.is-light {\n    background-color: #fafafa; }\n    .message.is-light .message-header {\n      background-color: whitesmoke;\n      color: rgba(0, 0, 0, 0.7); }\n    .message.is-light .message-body {\n      border-color: whitesmoke; }\n  .message.is-dark {\n    background-color: #fafafa; }\n    .message.is-dark .message-header {\n      background-color: #363636;\n      color: #fff; }\n    .message.is-dark .message-body {\n      border-color: #363636; }\n  .message.is-primary {\n    background-color: #ffebf2; }\n    .message.is-primary .message-header {\n      background-color: #ff0d68;\n      color: #fff; }\n    .message.is-primary .message-body {\n      border-color: #ff0d68;\n      color: #e60056; }\n  .message.is-link {\n    background-color: #ffebf2; }\n    .message.is-link .message-header {\n      background-color: #ff0d68;\n      color: #fff; }\n    .message.is-link .message-body {\n      border-color: #ff0d68;\n      color: #e60056; }\n  .message.is-info {\n    background-color: #ebf6ff; }\n    .message.is-info .message-header {\n      background-color: #0092FF;\n      color: #fff; }\n    .message.is-info .message-body {\n      border-color: #0092FF;\n      color: #0075cc; }\n  .message.is-success {\n    background-color: #ecfdf7; }\n    .message.is-success .message-header {\n      background-color: #16DB93;\n      color: rgba(0, 0, 0, 0.7); }\n    .message.is-success .message-body {\n      border-color: #16DB93;\n      color: #0e865a; }\n  .message.is-warning {\n    background-color: #fffdeb; }\n    .message.is-warning .message-header {\n      background-color: #FFE900;\n      color: rgba(0, 0, 0, 0.7); }\n    .message.is-warning .message-body {\n      border-color: #FFE900;\n      color: #948700; }\n  .message.is-danger {\n    background-color: #feecf0; }\n    .message.is-danger .message-header {\n      background-color: #f14668;\n      color: #fff; }\n    .message.is-danger .message-body {\n      border-color: #f14668;\n      color: #cc0f35; }\n\n.message-header {\n  align-items: center;\n  background-color: #4a4a4a;\n  border-radius: 4px 4px 0 0;\n  color: #fff;\n  display: flex;\n  font-weight: 700;\n  justify-content: space-between;\n  line-height: 1.25;\n  padding: 0.75em 1em;\n  position: relative; }\n  .message-header .delete {\n    flex-grow: 0;\n    flex-shrink: 0;\n    margin-left: 0.75em; }\n  .message-header + .message-body {\n    border-width: 0;\n    border-top-left-radius: 0;\n    border-top-right-radius: 0; }\n\n.message-body {\n  border-color: #dbdbdb;\n  border-radius: 4px;\n  border-style: solid;\n  border-width: 0 0 0 4px;\n  color: #4a4a4a;\n  padding: 1.25em 1.5em; }\n  .message-body code,\n  .message-body pre {\n    background-color: white; }\n  .message-body pre code {\n    background-color: transparent; }\n\n.modal {\n  align-items: center;\n  display: none;\n  flex-direction: column;\n  justify-content: center;\n  overflow: hidden;\n  position: fixed;\n  z-index: 40; }\n  .modal.is-active {\n    display: flex; }\n\n.modal-background {\n  background-color: rgba(10, 10, 10, 0.86); }\n\n.modal-content,\n.modal-card {\n  margin: 0 20px;\n  max-height: calc(100vh - 160px);\n  overflow: auto;\n  position: relative;\n  width: 100%; }\n  @media screen and (min-width: 769px) {\n    .modal-content,\n    .modal-card {\n      margin: 0 auto;\n      max-height: calc(100vh - 40px);\n      width: 640px; } }\n.modal-close {\n  background: none;\n  height: 40px;\n  position: fixed;\n  right: 20px;\n  top: 20px;\n  width: 40px; }\n\n.modal-card {\n  display: flex;\n  flex-direction: column;\n  max-height: calc(100vh - 40px);\n  overflow: hidden;\n  -ms-overflow-y: visible; }\n\n.modal-card-head,\n.modal-card-foot {\n  align-items: center;\n  background-color: whitesmoke;\n  display: flex;\n  flex-shrink: 0;\n  justify-content: flex-start;\n  padding: 20px;\n  position: relative; }\n\n.modal-card-head {\n  border-bottom: 1px solid #dbdbdb;\n  border-top-left-radius: 6px;\n  border-top-right-radius: 6px; }\n\n.modal-card-title {\n  color: #363636;\n  flex-grow: 1;\n  flex-shrink: 0;\n  font-size: 1.5rem;\n  line-height: 1; }\n\n.modal-card-foot {\n  border-bottom-left-radius: 6px;\n  border-bottom-right-radius: 6px;\n  border-top: 1px solid #dbdbdb; }\n  .modal-card-foot .button:not(:last-child) {\n    margin-right: 0.5em; }\n\n.modal-card-body {\n  -webkit-overflow-scrolling: touch;\n  background-color: white;\n  flex-grow: 1;\n  flex-shrink: 1;\n  overflow: auto;\n  padding: 20px; }\n\n.navbar {\n  background-color: white;\n  min-height: 3.25rem;\n  position: relative;\n  z-index: 30; }\n  .navbar.is-white {\n    background-color: white;\n    color: #0a0a0a; }\n    .navbar.is-white .navbar-brand > .navbar-item,\n    .navbar.is-white .navbar-brand .navbar-link {\n      color: #0a0a0a; }\n    .navbar.is-white .navbar-brand > a.navbar-item:focus, .navbar.is-white .navbar-brand > a.navbar-item:hover, .navbar.is-white .navbar-brand > a.navbar-item.is-active,\n    .navbar.is-white .navbar-brand .navbar-link:focus,\n    .navbar.is-white .navbar-brand .navbar-link:hover,\n    .navbar.is-white .navbar-brand .navbar-link.is-active {\n      background-color: #f2f2f2;\n      color: #0a0a0a; }\n    .navbar.is-white .navbar-brand .navbar-link::after {\n      border-color: #0a0a0a; }\n    .navbar.is-white .navbar-burger {\n      color: #0a0a0a; }\n    @media screen and (min-width: 1024px) {\n      .navbar.is-white .navbar-start > .navbar-item,\n      .navbar.is-white .navbar-start .navbar-link,\n      .navbar.is-white .navbar-end > .navbar-item,\n      .navbar.is-white .navbar-end .navbar-link {\n        color: #0a0a0a; }\n      .navbar.is-white .navbar-start > a.navbar-item:focus, .navbar.is-white .navbar-start > a.navbar-item:hover, .navbar.is-white .navbar-start > a.navbar-item.is-active,\n      .navbar.is-white .navbar-start .navbar-link:focus,\n      .navbar.is-white .navbar-start .navbar-link:hover,\n      .navbar.is-white .navbar-start .navbar-link.is-active,\n      .navbar.is-white .navbar-end > a.navbar-item:focus,\n      .navbar.is-white .navbar-end > a.navbar-item:hover,\n      .navbar.is-white .navbar-end > a.navbar-item.is-active,\n      .navbar.is-white .navbar-end .navbar-link:focus,\n      .navbar.is-white .navbar-end .navbar-link:hover,\n      .navbar.is-white .navbar-end .navbar-link.is-active {\n        background-color: #f2f2f2;\n        color: #0a0a0a; }\n      .navbar.is-white .navbar-start .navbar-link::after,\n      .navbar.is-white .navbar-end .navbar-link::after {\n        border-color: #0a0a0a; }\n      .navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,\n      .navbar.is-white .navbar-item.has-dropdown:hover .navbar-link,\n      .navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link {\n        background-color: #f2f2f2;\n        color: #0a0a0a; }\n      .navbar.is-white .navbar-dropdown a.navbar-item.is-active {\n        background-color: white;\n        color: #0a0a0a; } }\n  .navbar.is-black {\n    background-color: #0a0a0a;\n    color: white; }\n    .navbar.is-black .navbar-brand > .navbar-item,\n    .navbar.is-black .navbar-brand .navbar-link {\n      color: white; }\n    .navbar.is-black .navbar-brand > a.navbar-item:focus, .navbar.is-black .navbar-brand > a.navbar-item:hover, .navbar.is-black .navbar-brand > a.navbar-item.is-active,\n    .navbar.is-black .navbar-brand .navbar-link:focus,\n    .navbar.is-black .navbar-brand .navbar-link:hover,\n    .navbar.is-black .navbar-brand .navbar-link.is-active {\n      background-color: black;\n      color: white; }\n    .navbar.is-black .navbar-brand .navbar-link::after {\n      border-color: white; }\n    .navbar.is-black .navbar-burger {\n      color: white; }\n    @media screen and (min-width: 1024px) {\n      .navbar.is-black .navbar-start > .navbar-item,\n      .navbar.is-black .navbar-start .navbar-link,\n      .navbar.is-black .navbar-end > .navbar-item,\n      .navbar.is-black .navbar-end .navbar-link {\n        color: white; }\n      .navbar.is-black .navbar-start > a.navbar-item:focus, .navbar.is-black .navbar-start > a.navbar-item:hover, .navbar.is-black .navbar-start > a.navbar-item.is-active,\n      .navbar.is-black .navbar-start .navbar-link:focus,\n      .navbar.is-black .navbar-start .navbar-link:hover,\n      .navbar.is-black .navbar-start .navbar-link.is-active,\n      .navbar.is-black .navbar-end > a.navbar-item:focus,\n      .navbar.is-black .navbar-end > a.navbar-item:hover,\n      .navbar.is-black .navbar-end > a.navbar-item.is-active,\n      .navbar.is-black .navbar-end .navbar-link:focus,\n      .navbar.is-black .navbar-end .navbar-link:hover,\n      .navbar.is-black .navbar-end .navbar-link.is-active {\n        background-color: black;\n        color: white; }\n      .navbar.is-black .navbar-start .navbar-link::after,\n      .navbar.is-black .navbar-end .navbar-link::after {\n        border-color: white; }\n      .navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,\n      .navbar.is-black .navbar-item.has-dropdown:hover .navbar-link,\n      .navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link {\n        background-color: black;\n        color: white; }\n      .navbar.is-black .navbar-dropdown a.navbar-item.is-active {\n        background-color: #0a0a0a;\n        color: white; } }\n  .navbar.is-light {\n    background-color: whitesmoke;\n    color: rgba(0, 0, 0, 0.7); }\n    .navbar.is-light .navbar-brand > .navbar-item,\n    .navbar.is-light .navbar-brand .navbar-link {\n      color: rgba(0, 0, 0, 0.7); }\n    .navbar.is-light .navbar-brand > a.navbar-item:focus, .navbar.is-light .navbar-brand > a.navbar-item:hover, .navbar.is-light .navbar-brand > a.navbar-item.is-active,\n    .navbar.is-light .navbar-brand .navbar-link:focus,\n    .navbar.is-light .navbar-brand .navbar-link:hover,\n    .navbar.is-light .navbar-brand .navbar-link.is-active {\n      background-color: #e8e8e8;\n      color: rgba(0, 0, 0, 0.7); }\n    .navbar.is-light .navbar-brand .navbar-link::after {\n      border-color: rgba(0, 0, 0, 0.7); }\n    .navbar.is-light .navbar-burger {\n      color: rgba(0, 0, 0, 0.7); }\n    @media screen and (min-width: 1024px) {\n      .navbar.is-light .navbar-start > .navbar-item,\n      .navbar.is-light .navbar-start .navbar-link,\n      .navbar.is-light .navbar-end > .navbar-item,\n      .navbar.is-light .navbar-end .navbar-link {\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-light .navbar-start > a.navbar-item:focus, .navbar.is-light .navbar-start > a.navbar-item:hover, .navbar.is-light .navbar-start > a.navbar-item.is-active,\n      .navbar.is-light .navbar-start .navbar-link:focus,\n      .navbar.is-light .navbar-start .navbar-link:hover,\n      .navbar.is-light .navbar-start .navbar-link.is-active,\n      .navbar.is-light .navbar-end > a.navbar-item:focus,\n      .navbar.is-light .navbar-end > a.navbar-item:hover,\n      .navbar.is-light .navbar-end > a.navbar-item.is-active,\n      .navbar.is-light .navbar-end .navbar-link:focus,\n      .navbar.is-light .navbar-end .navbar-link:hover,\n      .navbar.is-light .navbar-end .navbar-link.is-active {\n        background-color: #e8e8e8;\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-light .navbar-start .navbar-link::after,\n      .navbar.is-light .navbar-end .navbar-link::after {\n        border-color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,\n      .navbar.is-light .navbar-item.has-dropdown:hover .navbar-link,\n      .navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link {\n        background-color: #e8e8e8;\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-light .navbar-dropdown a.navbar-item.is-active {\n        background-color: whitesmoke;\n        color: rgba(0, 0, 0, 0.7); } }\n  .navbar.is-dark {\n    background-color: #363636;\n    color: #fff; }\n    .navbar.is-dark .navbar-brand > .navbar-item,\n    .navbar.is-dark .navbar-brand .navbar-link {\n      color: #fff; }\n    .navbar.is-dark .navbar-brand > a.navbar-item:focus, .navbar.is-dark .navbar-brand > a.navbar-item:hover, .navbar.is-dark .navbar-brand > a.navbar-item.is-active,\n    .navbar.is-dark .navbar-brand .navbar-link:focus,\n    .navbar.is-dark .navbar-brand .navbar-link:hover,\n    .navbar.is-dark .navbar-brand .navbar-link.is-active {\n      background-color: #292929;\n      color: #fff; }\n    .navbar.is-dark .navbar-brand .navbar-link::after {\n      border-color: #fff; }\n    .navbar.is-dark .navbar-burger {\n      color: #fff; }\n    @media screen and (min-width: 1024px) {\n      .navbar.is-dark .navbar-start > .navbar-item,\n      .navbar.is-dark .navbar-start .navbar-link,\n      .navbar.is-dark .navbar-end > .navbar-item,\n      .navbar.is-dark .navbar-end .navbar-link {\n        color: #fff; }\n      .navbar.is-dark .navbar-start > a.navbar-item:focus, .navbar.is-dark .navbar-start > a.navbar-item:hover, .navbar.is-dark .navbar-start > a.navbar-item.is-active,\n      .navbar.is-dark .navbar-start .navbar-link:focus,\n      .navbar.is-dark .navbar-start .navbar-link:hover,\n      .navbar.is-dark .navbar-start .navbar-link.is-active,\n      .navbar.is-dark .navbar-end > a.navbar-item:focus,\n      .navbar.is-dark .navbar-end > a.navbar-item:hover,\n      .navbar.is-dark .navbar-end > a.navbar-item.is-active,\n      .navbar.is-dark .navbar-end .navbar-link:focus,\n      .navbar.is-dark .navbar-end .navbar-link:hover,\n      .navbar.is-dark .navbar-end .navbar-link.is-active {\n        background-color: #292929;\n        color: #fff; }\n      .navbar.is-dark .navbar-start .navbar-link::after,\n      .navbar.is-dark .navbar-end .navbar-link::after {\n        border-color: #fff; }\n      .navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,\n      .navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link,\n      .navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link {\n        background-color: #292929;\n        color: #fff; }\n      .navbar.is-dark .navbar-dropdown a.navbar-item.is-active {\n        background-color: #363636;\n        color: #fff; } }\n  .navbar.is-primary {\n    background-color: #ff0d68;\n    color: #fff; }\n    .navbar.is-primary .navbar-brand > .navbar-item,\n    .navbar.is-primary .navbar-brand .navbar-link {\n      color: #fff; }\n    .navbar.is-primary .navbar-brand > a.navbar-item:focus, .navbar.is-primary .navbar-brand > a.navbar-item:hover, .navbar.is-primary .navbar-brand > a.navbar-item.is-active,\n    .navbar.is-primary .navbar-brand .navbar-link:focus,\n    .navbar.is-primary .navbar-brand .navbar-link:hover,\n    .navbar.is-primary .navbar-brand .navbar-link.is-active {\n      background-color: #f3005b;\n      color: #fff; }\n    .navbar.is-primary .navbar-brand .navbar-link::after {\n      border-color: #fff; }\n    .navbar.is-primary .navbar-burger {\n      color: #fff; }\n    @media screen and (min-width: 1024px) {\n      .navbar.is-primary .navbar-start > .navbar-item,\n      .navbar.is-primary .navbar-start .navbar-link,\n      .navbar.is-primary .navbar-end > .navbar-item,\n      .navbar.is-primary .navbar-end .navbar-link {\n        color: #fff; }\n      .navbar.is-primary .navbar-start > a.navbar-item:focus, .navbar.is-primary .navbar-start > a.navbar-item:hover, .navbar.is-primary .navbar-start > a.navbar-item.is-active,\n      .navbar.is-primary .navbar-start .navbar-link:focus,\n      .navbar.is-primary .navbar-start .navbar-link:hover,\n      .navbar.is-primary .navbar-start .navbar-link.is-active,\n      .navbar.is-primary .navbar-end > a.navbar-item:focus,\n      .navbar.is-primary .navbar-end > a.navbar-item:hover,\n      .navbar.is-primary .navbar-end > a.navbar-item.is-active,\n      .navbar.is-primary .navbar-end .navbar-link:focus,\n      .navbar.is-primary .navbar-end .navbar-link:hover,\n      .navbar.is-primary .navbar-end .navbar-link.is-active {\n        background-color: #f3005b;\n        color: #fff; }\n      .navbar.is-primary .navbar-start .navbar-link::after,\n      .navbar.is-primary .navbar-end .navbar-link::after {\n        border-color: #fff; }\n      .navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,\n      .navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link,\n      .navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link {\n        background-color: #f3005b;\n        color: #fff; }\n      .navbar.is-primary .navbar-dropdown a.navbar-item.is-active {\n        background-color: #ff0d68;\n        color: #fff; } }\n  .navbar.is-link {\n    background-color: #ff0d68;\n    color: #fff; }\n    .navbar.is-link .navbar-brand > .navbar-item,\n    .navbar.is-link .navbar-brand .navbar-link {\n      color: #fff; }\n    .navbar.is-link .navbar-brand > a.navbar-item:focus, .navbar.is-link .navbar-brand > a.navbar-item:hover, .navbar.is-link .navbar-brand > a.navbar-item.is-active,\n    .navbar.is-link .navbar-brand .navbar-link:focus,\n    .navbar.is-link .navbar-brand .navbar-link:hover,\n    .navbar.is-link .navbar-brand .navbar-link.is-active {\n      background-color: #f3005b;\n      color: #fff; }\n    .navbar.is-link .navbar-brand .navbar-link::after {\n      border-color: #fff; }\n    .navbar.is-link .navbar-burger {\n      color: #fff; }\n    @media screen and (min-width: 1024px) {\n      .navbar.is-link .navbar-start > .navbar-item,\n      .navbar.is-link .navbar-start .navbar-link,\n      .navbar.is-link .navbar-end > .navbar-item,\n      .navbar.is-link .navbar-end .navbar-link {\n        color: #fff; }\n      .navbar.is-link .navbar-start > a.navbar-item:focus, .navbar.is-link .navbar-start > a.navbar-item:hover, .navbar.is-link .navbar-start > a.navbar-item.is-active,\n      .navbar.is-link .navbar-start .navbar-link:focus,\n      .navbar.is-link .navbar-start .navbar-link:hover,\n      .navbar.is-link .navbar-start .navbar-link.is-active,\n      .navbar.is-link .navbar-end > a.navbar-item:focus,\n      .navbar.is-link .navbar-end > a.navbar-item:hover,\n      .navbar.is-link .navbar-end > a.navbar-item.is-active,\n      .navbar.is-link .navbar-end .navbar-link:focus,\n      .navbar.is-link .navbar-end .navbar-link:hover,\n      .navbar.is-link .navbar-end .navbar-link.is-active {\n        background-color: #f3005b;\n        color: #fff; }\n      .navbar.is-link .navbar-start .navbar-link::after,\n      .navbar.is-link .navbar-end .navbar-link::after {\n        border-color: #fff; }\n      .navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,\n      .navbar.is-link .navbar-item.has-dropdown:hover .navbar-link,\n      .navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link {\n        background-color: #f3005b;\n        color: #fff; }\n      .navbar.is-link .navbar-dropdown a.navbar-item.is-active {\n        background-color: #ff0d68;\n        color: #fff; } }\n  .navbar.is-info {\n    background-color: #0092FF;\n    color: #fff; }\n    .navbar.is-info .navbar-brand > .navbar-item,\n    .navbar.is-info .navbar-brand .navbar-link {\n      color: #fff; }\n    .navbar.is-info .navbar-brand > a.navbar-item:focus, .navbar.is-info .navbar-brand > a.navbar-item:hover, .navbar.is-info .navbar-brand > a.navbar-item.is-active,\n    .navbar.is-info .navbar-brand .navbar-link:focus,\n    .navbar.is-info .navbar-brand .navbar-link:hover,\n    .navbar.is-info .navbar-brand .navbar-link.is-active {\n      background-color: #0083e6;\n      color: #fff; }\n    .navbar.is-info .navbar-brand .navbar-link::after {\n      border-color: #fff; }\n    .navbar.is-info .navbar-burger {\n      color: #fff; }\n    @media screen and (min-width: 1024px) {\n      .navbar.is-info .navbar-start > .navbar-item,\n      .navbar.is-info .navbar-start .navbar-link,\n      .navbar.is-info .navbar-end > .navbar-item,\n      .navbar.is-info .navbar-end .navbar-link {\n        color: #fff; }\n      .navbar.is-info .navbar-start > a.navbar-item:focus, .navbar.is-info .navbar-start > a.navbar-item:hover, .navbar.is-info .navbar-start > a.navbar-item.is-active,\n      .navbar.is-info .navbar-start .navbar-link:focus,\n      .navbar.is-info .navbar-start .navbar-link:hover,\n      .navbar.is-info .navbar-start .navbar-link.is-active,\n      .navbar.is-info .navbar-end > a.navbar-item:focus,\n      .navbar.is-info .navbar-end > a.navbar-item:hover,\n      .navbar.is-info .navbar-end > a.navbar-item.is-active,\n      .navbar.is-info .navbar-end .navbar-link:focus,\n      .navbar.is-info .navbar-end .navbar-link:hover,\n      .navbar.is-info .navbar-end .navbar-link.is-active {\n        background-color: #0083e6;\n        color: #fff; }\n      .navbar.is-info .navbar-start .navbar-link::after,\n      .navbar.is-info .navbar-end .navbar-link::after {\n        border-color: #fff; }\n      .navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,\n      .navbar.is-info .navbar-item.has-dropdown:hover .navbar-link,\n      .navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link {\n        background-color: #0083e6;\n        color: #fff; }\n      .navbar.is-info .navbar-dropdown a.navbar-item.is-active {\n        background-color: #0092FF;\n        color: #fff; } }\n  .navbar.is-success {\n    background-color: #16DB93;\n    color: rgba(0, 0, 0, 0.7); }\n    .navbar.is-success .navbar-brand > .navbar-item,\n    .navbar.is-success .navbar-brand .navbar-link {\n      color: rgba(0, 0, 0, 0.7); }\n    .navbar.is-success .navbar-brand > a.navbar-item:focus, .navbar.is-success .navbar-brand > a.navbar-item:hover, .navbar.is-success .navbar-brand > a.navbar-item.is-active,\n    .navbar.is-success .navbar-brand .navbar-link:focus,\n    .navbar.is-success .navbar-brand .navbar-link:hover,\n    .navbar.is-success .navbar-brand .navbar-link.is-active {\n      background-color: #14c483;\n      color: rgba(0, 0, 0, 0.7); }\n    .navbar.is-success .navbar-brand .navbar-link::after {\n      border-color: rgba(0, 0, 0, 0.7); }\n    .navbar.is-success .navbar-burger {\n      color: rgba(0, 0, 0, 0.7); }\n    @media screen and (min-width: 1024px) {\n      .navbar.is-success .navbar-start > .navbar-item,\n      .navbar.is-success .navbar-start .navbar-link,\n      .navbar.is-success .navbar-end > .navbar-item,\n      .navbar.is-success .navbar-end .navbar-link {\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-success .navbar-start > a.navbar-item:focus, .navbar.is-success .navbar-start > a.navbar-item:hover, .navbar.is-success .navbar-start > a.navbar-item.is-active,\n      .navbar.is-success .navbar-start .navbar-link:focus,\n      .navbar.is-success .navbar-start .navbar-link:hover,\n      .navbar.is-success .navbar-start .navbar-link.is-active,\n      .navbar.is-success .navbar-end > a.navbar-item:focus,\n      .navbar.is-success .navbar-end > a.navbar-item:hover,\n      .navbar.is-success .navbar-end > a.navbar-item.is-active,\n      .navbar.is-success .navbar-end .navbar-link:focus,\n      .navbar.is-success .navbar-end .navbar-link:hover,\n      .navbar.is-success .navbar-end .navbar-link.is-active {\n        background-color: #14c483;\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-success .navbar-start .navbar-link::after,\n      .navbar.is-success .navbar-end .navbar-link::after {\n        border-color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,\n      .navbar.is-success .navbar-item.has-dropdown:hover .navbar-link,\n      .navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link {\n        background-color: #14c483;\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-success .navbar-dropdown a.navbar-item.is-active {\n        background-color: #16DB93;\n        color: rgba(0, 0, 0, 0.7); } }\n  .navbar.is-warning {\n    background-color: #FFE900;\n    color: rgba(0, 0, 0, 0.7); }\n    .navbar.is-warning .navbar-brand > .navbar-item,\n    .navbar.is-warning .navbar-brand .navbar-link {\n      color: rgba(0, 0, 0, 0.7); }\n    .navbar.is-warning .navbar-brand > a.navbar-item:focus, .navbar.is-warning .navbar-brand > a.navbar-item:hover, .navbar.is-warning .navbar-brand > a.navbar-item.is-active,\n    .navbar.is-warning .navbar-brand .navbar-link:focus,\n    .navbar.is-warning .navbar-brand .navbar-link:hover,\n    .navbar.is-warning .navbar-brand .navbar-link.is-active {\n      background-color: #e6d200;\n      color: rgba(0, 0, 0, 0.7); }\n    .navbar.is-warning .navbar-brand .navbar-link::after {\n      border-color: rgba(0, 0, 0, 0.7); }\n    .navbar.is-warning .navbar-burger {\n      color: rgba(0, 0, 0, 0.7); }\n    @media screen and (min-width: 1024px) {\n      .navbar.is-warning .navbar-start > .navbar-item,\n      .navbar.is-warning .navbar-start .navbar-link,\n      .navbar.is-warning .navbar-end > .navbar-item,\n      .navbar.is-warning .navbar-end .navbar-link {\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-warning .navbar-start > a.navbar-item:focus, .navbar.is-warning .navbar-start > a.navbar-item:hover, .navbar.is-warning .navbar-start > a.navbar-item.is-active,\n      .navbar.is-warning .navbar-start .navbar-link:focus,\n      .navbar.is-warning .navbar-start .navbar-link:hover,\n      .navbar.is-warning .navbar-start .navbar-link.is-active,\n      .navbar.is-warning .navbar-end > a.navbar-item:focus,\n      .navbar.is-warning .navbar-end > a.navbar-item:hover,\n      .navbar.is-warning .navbar-end > a.navbar-item.is-active,\n      .navbar.is-warning .navbar-end .navbar-link:focus,\n      .navbar.is-warning .navbar-end .navbar-link:hover,\n      .navbar.is-warning .navbar-end .navbar-link.is-active {\n        background-color: #e6d200;\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-warning .navbar-start .navbar-link::after,\n      .navbar.is-warning .navbar-end .navbar-link::after {\n        border-color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,\n      .navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link,\n      .navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link {\n        background-color: #e6d200;\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-warning .navbar-dropdown a.navbar-item.is-active {\n        background-color: #FFE900;\n        color: rgba(0, 0, 0, 0.7); } }\n  .navbar.is-danger {\n    background-color: #f14668;\n    color: #fff; }\n    .navbar.is-danger .navbar-brand > .navbar-item,\n    .navbar.is-danger .navbar-brand .navbar-link {\n      color: #fff; }\n    .navbar.is-danger .navbar-brand > a.navbar-item:focus, .navbar.is-danger .navbar-brand > a.navbar-item:hover, .navbar.is-danger .navbar-brand > a.navbar-item.is-active,\n    .navbar.is-danger .navbar-brand .navbar-link:focus,\n    .navbar.is-danger .navbar-brand .navbar-link:hover,\n    .navbar.is-danger .navbar-brand .navbar-link.is-active {\n      background-color: #ef2e55;\n      color: #fff; }\n    .navbar.is-danger .navbar-brand .navbar-link::after {\n      border-color: #fff; }\n    .navbar.is-danger .navbar-burger {\n      color: #fff; }\n    @media screen and (min-width: 1024px) {\n      .navbar.is-danger .navbar-start > .navbar-item,\n      .navbar.is-danger .navbar-start .navbar-link,\n      .navbar.is-danger .navbar-end > .navbar-item,\n      .navbar.is-danger .navbar-end .navbar-link {\n        color: #fff; }\n      .navbar.is-danger .navbar-start > a.navbar-item:focus, .navbar.is-danger .navbar-start > a.navbar-item:hover, .navbar.is-danger .navbar-start > a.navbar-item.is-active,\n      .navbar.is-danger .navbar-start .navbar-link:focus,\n      .navbar.is-danger .navbar-start .navbar-link:hover,\n      .navbar.is-danger .navbar-start .navbar-link.is-active,\n      .navbar.is-danger .navbar-end > a.navbar-item:focus,\n      .navbar.is-danger .navbar-end > a.navbar-item:hover,\n      .navbar.is-danger .navbar-end > a.navbar-item.is-active,\n      .navbar.is-danger .navbar-end .navbar-link:focus,\n      .navbar.is-danger .navbar-end .navbar-link:hover,\n      .navbar.is-danger .navbar-end .navbar-link.is-active {\n        background-color: #ef2e55;\n        color: #fff; }\n      .navbar.is-danger .navbar-start .navbar-link::after,\n      .navbar.is-danger .navbar-end .navbar-link::after {\n        border-color: #fff; }\n      .navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,\n      .navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link,\n      .navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link {\n        background-color: #ef2e55;\n        color: #fff; }\n      .navbar.is-danger .navbar-dropdown a.navbar-item.is-active {\n        background-color: #f14668;\n        color: #fff; } }\n  .navbar > .container {\n    align-items: stretch;\n    display: flex;\n    min-height: 3.25rem;\n    width: 100%; }\n  .navbar.has-shadow {\n    box-shadow: 0 2px 0 0 whitesmoke; }\n  .navbar.is-fixed-bottom, .navbar.is-fixed-top {\n    left: 0;\n    position: fixed;\n    right: 0;\n    z-index: 30; }\n  .navbar.is-fixed-bottom {\n    bottom: 0; }\n    .navbar.is-fixed-bottom.has-shadow {\n      box-shadow: 0 -2px 0 0 whitesmoke; }\n  .navbar.is-fixed-top {\n    top: 0; }\n\nhtml.has-navbar-fixed-top,\nbody.has-navbar-fixed-top {\n  padding-top: 3.25rem; }\n\nhtml.has-navbar-fixed-bottom,\nbody.has-navbar-fixed-bottom {\n  padding-bottom: 3.25rem; }\n\n.navbar-brand,\n.navbar-tabs {\n  align-items: stretch;\n  display: flex;\n  flex-shrink: 0;\n  min-height: 3.25rem; }\n\n.navbar-brand a.navbar-item:focus, .navbar-brand a.navbar-item:hover {\n  background-color: transparent; }\n\n.navbar-tabs {\n  -webkit-overflow-scrolling: touch;\n  max-width: 100vw;\n  overflow-x: auto;\n  overflow-y: hidden; }\n\n.navbar-burger {\n  color: #4a4a4a;\n  -moz-appearance: none;\n  -webkit-appearance: none;\n  appearance: none;\n  background: none;\n  border: none;\n  cursor: pointer;\n  display: block;\n  height: 3.25rem;\n  position: relative;\n  width: 3.25rem;\n  margin-left: auto; }\n  .navbar-burger span {\n    background-color: currentColor;\n    display: block;\n    height: 1px;\n    left: calc(50% - 8px);\n    position: absolute;\n    transform-origin: center;\n    transition-duration: 86ms;\n    transition-property: background-color, opacity, transform;\n    transition-timing-function: ease-out;\n    width: 16px; }\n    .navbar-burger span:nth-child(1) {\n      top: calc(50% - 6px); }\n    .navbar-burger span:nth-child(2) {\n      top: calc(50% - 1px); }\n    .navbar-burger span:nth-child(3) {\n      top: calc(50% + 4px); }\n  .navbar-burger:hover {\n    background-color: rgba(0, 0, 0, 0.05); }\n  .navbar-burger.is-active span:nth-child(1) {\n    transform: translateY(5px) rotate(45deg); }\n  .navbar-burger.is-active span:nth-child(2) {\n    opacity: 0; }\n  .navbar-burger.is-active span:nth-child(3) {\n    transform: translateY(-5px) rotate(-45deg); }\n\n.navbar-menu {\n  display: none; }\n\n.navbar-item,\n.navbar-link {\n  color: #4a4a4a;\n  display: block;\n  line-height: 1.5;\n  padding: 0.5rem 0.75rem;\n  position: relative; }\n  .navbar-item .icon:only-child,\n  .navbar-link .icon:only-child {\n    margin-left: -0.25rem;\n    margin-right: -0.25rem; }\n\na.navbar-item,\n.navbar-link {\n  cursor: pointer; }\n  a.navbar-item:focus, a.navbar-item:focus-within, a.navbar-item:hover, a.navbar-item.is-active,\n  .navbar-link:focus,\n  .navbar-link:focus-within,\n  .navbar-link:hover,\n  .navbar-link.is-active {\n    background-color: #fafafa;\n    color: #ff0d68; }\n\n.navbar-item {\n  flex-grow: 0;\n  flex-shrink: 0; }\n  .navbar-item img {\n    max-height: 1.75rem; }\n  .navbar-item.has-dropdown {\n    padding: 0; }\n  .navbar-item.is-expanded {\n    flex-grow: 1;\n    flex-shrink: 1; }\n  .navbar-item.is-tab {\n    border-bottom: 1px solid transparent;\n    min-height: 3.25rem;\n    padding-bottom: calc(0.5rem - 1px); }\n    .navbar-item.is-tab:focus, .navbar-item.is-tab:hover {\n      background-color: transparent;\n      border-bottom-color: #ff0d68; }\n    .navbar-item.is-tab.is-active {\n      background-color: transparent;\n      border-bottom-color: #ff0d68;\n      border-bottom-style: solid;\n      border-bottom-width: 3px;\n      color: #ff0d68;\n      padding-bottom: calc(0.5rem - 3px); }\n\n.navbar-content {\n  flex-grow: 1;\n  flex-shrink: 1; }\n\n.navbar-link:not(.is-arrowless) {\n  padding-right: 2.5em; }\n  .navbar-link:not(.is-arrowless)::after {\n    border-color: #ff0d68;\n    margin-top: -0.375em;\n    right: 1.125em; }\n\n.navbar-dropdown {\n  font-size: 0.875rem;\n  padding-bottom: 0.5rem;\n  padding-top: 0.5rem; }\n  .navbar-dropdown .navbar-item {\n    padding-left: 1.5rem;\n    padding-right: 1.5rem; }\n\n.navbar-divider {\n  background-color: whitesmoke;\n  border: none;\n  display: none;\n  height: 2px;\n  margin: 0.5rem 0; }\n\n@media screen and (max-width: 1023px) {\n  .navbar > .container {\n    display: block; }\n  .navbar-brand .navbar-item,\n  .navbar-tabs .navbar-item {\n    align-items: center;\n    display: flex; }\n  .navbar-link::after {\n    display: none; }\n  .navbar-menu {\n    background-color: white;\n    box-shadow: 0 8px 16px rgba(10, 10, 10, 0.1);\n    padding: 0.5rem 0; }\n    .navbar-menu.is-active {\n      display: block; }\n  .navbar.is-fixed-bottom-touch, .navbar.is-fixed-top-touch {\n    left: 0;\n    position: fixed;\n    right: 0;\n    z-index: 30; }\n  .navbar.is-fixed-bottom-touch {\n    bottom: 0; }\n    .navbar.is-fixed-bottom-touch.has-shadow {\n      box-shadow: 0 -2px 3px rgba(10, 10, 10, 0.1); }\n  .navbar.is-fixed-top-touch {\n    top: 0; }\n  .navbar.is-fixed-top .navbar-menu, .navbar.is-fixed-top-touch .navbar-menu {\n    -webkit-overflow-scrolling: touch;\n    max-height: calc(100vh - 3.25rem);\n    overflow: auto; }\n  html.has-navbar-fixed-top-touch,\n  body.has-navbar-fixed-top-touch {\n    padding-top: 3.25rem; }\n  html.has-navbar-fixed-bottom-touch,\n  body.has-navbar-fixed-bottom-touch {\n    padding-bottom: 3.25rem; } }\n\n@media screen and (min-width: 1024px) {\n  .navbar,\n  .navbar-menu,\n  .navbar-start,\n  .navbar-end {\n    align-items: stretch;\n    display: flex; }\n  .navbar {\n    min-height: 3.25rem; }\n    .navbar.is-spaced {\n      padding: 1rem 2rem; }\n      .navbar.is-spaced .navbar-start,\n      .navbar.is-spaced .navbar-end {\n        align-items: center; }\n      .navbar.is-spaced a.navbar-item,\n      .navbar.is-spaced .navbar-link {\n        border-radius: 4px; }\n    .navbar.is-transparent a.navbar-item:focus, .navbar.is-transparent a.navbar-item:hover, .navbar.is-transparent a.navbar-item.is-active,\n    .navbar.is-transparent .navbar-link:focus,\n    .navbar.is-transparent .navbar-link:hover,\n    .navbar.is-transparent .navbar-link.is-active {\n      background-color: transparent !important; }\n    .navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link, .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link, .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link, .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link {\n      background-color: transparent !important; }\n    .navbar.is-transparent .navbar-dropdown a.navbar-item:focus, .navbar.is-transparent .navbar-dropdown a.navbar-item:hover {\n      background-color: whitesmoke;\n      color: #0a0a0a; }\n    .navbar.is-transparent .navbar-dropdown a.navbar-item.is-active {\n      background-color: whitesmoke;\n      color: #ff0d68; }\n  .navbar-burger {\n    display: none; }\n  .navbar-item,\n  .navbar-link {\n    align-items: center;\n    display: flex; }\n  .navbar-item.has-dropdown {\n    align-items: stretch; }\n  .navbar-item.has-dropdown-up .navbar-link::after {\n    transform: rotate(135deg) translate(0.25em, -0.25em); }\n  .navbar-item.has-dropdown-up .navbar-dropdown {\n    border-bottom: 2px solid #dbdbdb;\n    border-radius: 6px 6px 0 0;\n    border-top: none;\n    bottom: 100%;\n    box-shadow: 0 -8px 8px rgba(10, 10, 10, 0.1);\n    top: auto; }\n  .navbar-item.is-active .navbar-dropdown, .navbar-item.is-hoverable:focus .navbar-dropdown, .navbar-item.is-hoverable:focus-within .navbar-dropdown, .navbar-item.is-hoverable:hover .navbar-dropdown {\n    display: block; }\n    .navbar.is-spaced .navbar-item.is-active .navbar-dropdown, .navbar-item.is-active .navbar-dropdown.is-boxed, .navbar.is-spaced .navbar-item.is-hoverable:focus .navbar-dropdown, .navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed, .navbar.is-spaced .navbar-item.is-hoverable:focus-within .navbar-dropdown, .navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed, .navbar.is-spaced .navbar-item.is-hoverable:hover .navbar-dropdown, .navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed {\n      opacity: 1;\n      pointer-events: auto;\n      transform: translateY(0); }\n  .navbar-menu {\n    flex-grow: 1;\n    flex-shrink: 0; }\n  .navbar-start {\n    justify-content: flex-start;\n    margin-right: auto; }\n  .navbar-end {\n    justify-content: flex-end;\n    margin-left: auto; }\n  .navbar-dropdown {\n    background-color: white;\n    border-bottom-left-radius: 6px;\n    border-bottom-right-radius: 6px;\n    border-top: 2px solid #dbdbdb;\n    box-shadow: 0 8px 8px rgba(10, 10, 10, 0.1);\n    display: none;\n    font-size: 0.875rem;\n    left: 0;\n    min-width: 100%;\n    position: absolute;\n    top: 100%;\n    z-index: 20; }\n    .navbar-dropdown .navbar-item {\n      padding: 0.375rem 1rem;\n      white-space: nowrap; }\n    .navbar-dropdown a.navbar-item {\n      padding-right: 3rem; }\n      .navbar-dropdown a.navbar-item:focus, .navbar-dropdown a.navbar-item:hover {\n        background-color: whitesmoke;\n        color: #0a0a0a; }\n      .navbar-dropdown a.navbar-item.is-active {\n        background-color: whitesmoke;\n        color: #ff0d68; }\n    .navbar.is-spaced .navbar-dropdown, .navbar-dropdown.is-boxed {\n      border-radius: 6px;\n      border-top: none;\n      box-shadow: 0 8px 8px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1);\n      display: block;\n      opacity: 0;\n      pointer-events: none;\n      top: calc(100% + (-4px));\n      transform: translateY(-5px);\n      transition-duration: 86ms;\n      transition-property: opacity, transform; }\n    .navbar-dropdown.is-right {\n      left: auto;\n      right: 0; }\n  .navbar-divider {\n    display: block; }\n  .navbar > .container .navbar-brand,\n  .container > .navbar .navbar-brand {\n    margin-left: -0.75rem; }\n  .navbar > .container .navbar-menu,\n  .container > .navbar .navbar-menu {\n    margin-right: -0.75rem; }\n  .navbar.is-fixed-bottom-desktop, .navbar.is-fixed-top-desktop {\n    left: 0;\n    position: fixed;\n    right: 0;\n    z-index: 30; }\n  .navbar.is-fixed-bottom-desktop {\n    bottom: 0; }\n    .navbar.is-fixed-bottom-desktop.has-shadow {\n      box-shadow: 0 -2px 3px rgba(10, 10, 10, 0.1); }\n  .navbar.is-fixed-top-desktop {\n    top: 0; }\n  html.has-navbar-fixed-top-desktop,\n  body.has-navbar-fixed-top-desktop {\n    padding-top: 3.25rem; }\n  html.has-navbar-fixed-bottom-desktop,\n  body.has-navbar-fixed-bottom-desktop {\n    padding-bottom: 3.25rem; }\n  html.has-spaced-navbar-fixed-top,\n  body.has-spaced-navbar-fixed-top {\n    padding-top: 5.25rem; }\n  html.has-spaced-navbar-fixed-bottom,\n  body.has-spaced-navbar-fixed-bottom {\n    padding-bottom: 5.25rem; }\n  a.navbar-item.is-active,\n  .navbar-link.is-active {\n    color: #0a0a0a; }\n  a.navbar-item.is-active:not(:focus):not(:hover),\n  .navbar-link.is-active:not(:focus):not(:hover) {\n    background-color: transparent; }\n  .navbar-item.has-dropdown:focus .navbar-link, .navbar-item.has-dropdown:hover .navbar-link, .navbar-item.has-dropdown.is-active .navbar-link {\n    background-color: #fafafa; } }\n\n.hero.is-fullheight-with-navbar {\n  min-height: calc(100vh - 3.25rem); }\n\n.pagination {\n  font-size: 1rem;\n  margin: -0.25rem; }\n  .pagination.is-small {\n    font-size: 0.75rem; }\n  .pagination.is-medium {\n    font-size: 1.25rem; }\n  .pagination.is-large {\n    font-size: 1.5rem; }\n  .pagination.is-rounded .pagination-previous,\n  .pagination.is-rounded .pagination-next {\n    padding-left: 1em;\n    padding-right: 1em;\n    border-radius: 9999px; }\n  .pagination.is-rounded .pagination-link {\n    border-radius: 9999px; }\n\n.pagination,\n.pagination-list {\n  align-items: center;\n  display: flex;\n  justify-content: center;\n  text-align: center; }\n\n.pagination-previous,\n.pagination-next,\n.pagination-link,\n.pagination-ellipsis {\n  font-size: 1em;\n  justify-content: center;\n  margin: 0.25rem;\n  padding-left: 0.5em;\n  padding-right: 0.5em;\n  text-align: center; }\n\n.pagination-previous,\n.pagination-next,\n.pagination-link {\n  border-color: #dbdbdb;\n  color: #363636;\n  min-width: 2.5em; }\n  .pagination-previous:hover,\n  .pagination-next:hover,\n  .pagination-link:hover {\n    border-color: #b5b5b5;\n    color: #363636; }\n  .pagination-previous:focus,\n  .pagination-next:focus,\n  .pagination-link:focus {\n    border-color: #0092FF; }\n  .pagination-previous:active,\n  .pagination-next:active,\n  .pagination-link:active {\n    box-shadow: inset 0 1px 2px rgba(10, 10, 10, 0.2); }\n  .pagination-previous[disabled], .pagination-previous.is-disabled,\n  .pagination-next[disabled],\n  .pagination-next.is-disabled,\n  .pagination-link[disabled],\n  .pagination-link.is-disabled {\n    background-color: #dbdbdb;\n    border-color: #dbdbdb;\n    box-shadow: none;\n    color: #7a7a7a;\n    opacity: 0.5; }\n\n.pagination-previous,\n.pagination-next {\n  padding-left: 0.75em;\n  padding-right: 0.75em;\n  white-space: nowrap; }\n\n.pagination-link.is-current {\n  background-color: #ff0d68;\n  border-color: #ff0d68;\n  color: #fff; }\n\n.pagination-ellipsis {\n  color: #b5b5b5;\n  pointer-events: none; }\n\n.pagination-list {\n  flex-wrap: wrap; }\n  .pagination-list li {\n    list-style: none; }\n\n@media screen and (max-width: 768px) {\n  .pagination {\n    flex-wrap: wrap; }\n  .pagination-previous,\n  .pagination-next {\n    flex-grow: 1;\n    flex-shrink: 1; }\n  .pagination-list li {\n    flex-grow: 1;\n    flex-shrink: 1; } }\n\n@media screen and (min-width: 769px), print {\n  .pagination-list {\n    flex-grow: 1;\n    flex-shrink: 1;\n    justify-content: flex-start;\n    order: 1; }\n  .pagination-previous,\n  .pagination-next,\n  .pagination-link,\n  .pagination-ellipsis {\n    margin-bottom: 0;\n    margin-top: 0; }\n  .pagination-previous {\n    order: 2; }\n  .pagination-next {\n    order: 3; }\n  .pagination {\n    justify-content: space-between;\n    margin-bottom: 0;\n    margin-top: 0; }\n    .pagination.is-centered .pagination-previous {\n      order: 1; }\n    .pagination.is-centered .pagination-list {\n      justify-content: center;\n      order: 2; }\n    .pagination.is-centered .pagination-next {\n      order: 3; }\n    .pagination.is-right .pagination-previous {\n      order: 1; }\n    .pagination.is-right .pagination-next {\n      order: 2; }\n    .pagination.is-right .pagination-list {\n      justify-content: flex-end;\n      order: 3; } }\n\n.panel {\n  border-radius: 6px;\n  box-shadow: 0 0.5em 1em -0.125em rgba(10, 10, 10, 0.1), 0 0px 0 1px rgba(10, 10, 10, 0.02);\n  font-size: 1rem; }\n  .panel:not(:last-child) {\n    margin-bottom: 1.5rem; }\n  .panel.is-white .panel-heading {\n    background-color: white;\n    color: #0a0a0a; }\n  .panel.is-white .panel-tabs a.is-active {\n    border-bottom-color: white; }\n  .panel.is-white .panel-block.is-active .panel-icon {\n    color: white; }\n  .panel.is-black .panel-heading {\n    background-color: #0a0a0a;\n    color: white; }\n  .panel.is-black .panel-tabs a.is-active {\n    border-bottom-color: #0a0a0a; }\n  .panel.is-black .panel-block.is-active .panel-icon {\n    color: #0a0a0a; }\n  .panel.is-light .panel-heading {\n    background-color: whitesmoke;\n    color: rgba(0, 0, 0, 0.7); }\n  .panel.is-light .panel-tabs a.is-active {\n    border-bottom-color: whitesmoke; }\n  .panel.is-light .panel-block.is-active .panel-icon {\n    color: whitesmoke; }\n  .panel.is-dark .panel-heading {\n    background-color: #363636;\n    color: #fff; }\n  .panel.is-dark .panel-tabs a.is-active {\n    border-bottom-color: #363636; }\n  .panel.is-dark .panel-block.is-active .panel-icon {\n    color: #363636; }\n  .panel.is-primary .panel-heading {\n    background-color: #ff0d68;\n    color: #fff; }\n  .panel.is-primary .panel-tabs a.is-active {\n    border-bottom-color: #ff0d68; }\n  .panel.is-primary .panel-block.is-active .panel-icon {\n    color: #ff0d68; }\n  .panel.is-link .panel-heading {\n    background-color: #ff0d68;\n    color: #fff; }\n  .panel.is-link .panel-tabs a.is-active {\n    border-bottom-color: #ff0d68; }\n  .panel.is-link .panel-block.is-active .panel-icon {\n    color: #ff0d68; }\n  .panel.is-info .panel-heading {\n    background-color: #0092FF;\n    color: #fff; }\n  .panel.is-info .panel-tabs a.is-active {\n    border-bottom-color: #0092FF; }\n  .panel.is-info .panel-block.is-active .panel-icon {\n    color: #0092FF; }\n  .panel.is-success .panel-heading {\n    background-color: #16DB93;\n    color: rgba(0, 0, 0, 0.7); }\n  .panel.is-success .panel-tabs a.is-active {\n    border-bottom-color: #16DB93; }\n  .panel.is-success .panel-block.is-active .panel-icon {\n    color: #16DB93; }\n  .panel.is-warning .panel-heading {\n    background-color: #FFE900;\n    color: rgba(0, 0, 0, 0.7); }\n  .panel.is-warning .panel-tabs a.is-active {\n    border-bottom-color: #FFE900; }\n  .panel.is-warning .panel-block.is-active .panel-icon {\n    color: #FFE900; }\n  .panel.is-danger .panel-heading {\n    background-color: #f14668;\n    color: #fff; }\n  .panel.is-danger .panel-tabs a.is-active {\n    border-bottom-color: #f14668; }\n  .panel.is-danger .panel-block.is-active .panel-icon {\n    color: #f14668; }\n\n.panel-tabs:not(:last-child),\n.panel-block:not(:last-child) {\n  border-bottom: 1px solid #ededed; }\n\n.panel-heading {\n  background-color: #ededed;\n  border-radius: 6px 6px 0 0;\n  color: #363636;\n  font-size: 1.25em;\n  font-weight: 700;\n  line-height: 1.25;\n  padding: 0.75em 1em; }\n\n.panel-tabs {\n  align-items: flex-end;\n  display: flex;\n  font-size: 0.875em;\n  justify-content: center; }\n  .panel-tabs a {\n    border-bottom: 1px solid #dbdbdb;\n    margin-bottom: -1px;\n    padding: 0.5em; }\n    .panel-tabs a.is-active {\n      border-bottom-color: #4a4a4a;\n      color: #363636; }\n\n.panel-list a {\n  color: #4a4a4a; }\n  .panel-list a:hover {\n    color: #ff0d68; }\n\n.panel-block {\n  align-items: center;\n  color: #363636;\n  display: flex;\n  justify-content: flex-start;\n  padding: 0.5em 0.75em; }\n  .panel-block input[type=\"checkbox\"] {\n    margin-right: 0.75em; }\n  .panel-block > .control {\n    flex-grow: 1;\n    flex-shrink: 1;\n    width: 100%; }\n  .panel-block.is-wrapped {\n    flex-wrap: wrap; }\n  .panel-block.is-active {\n    border-left-color: #ff0d68;\n    color: #363636; }\n    .panel-block.is-active .panel-icon {\n      color: #ff0d68; }\n  .panel-block:last-child {\n    border-bottom-left-radius: 6px;\n    border-bottom-right-radius: 6px; }\n\na.panel-block,\nlabel.panel-block {\n  cursor: pointer; }\n  a.panel-block:hover,\n  label.panel-block:hover {\n    background-color: whitesmoke; }\n\n.panel-icon {\n  display: inline-block;\n  font-size: 14px;\n  height: 1em;\n  line-height: 1em;\n  text-align: center;\n  vertical-align: top;\n  width: 1em;\n  color: #7a7a7a;\n  margin-right: 0.75em; }\n  .panel-icon .fa {\n    font-size: inherit;\n    line-height: inherit; }\n\n.tabs {\n  -webkit-overflow-scrolling: touch;\n  align-items: stretch;\n  display: flex;\n  font-size: 1rem;\n  justify-content: space-between;\n  overflow: hidden;\n  overflow-x: auto;\n  white-space: nowrap; }\n  .tabs a {\n    align-items: center;\n    border-bottom-color: #dbdbdb;\n    border-bottom-style: solid;\n    border-bottom-width: 1px;\n    color: #4a4a4a;\n    display: flex;\n    justify-content: center;\n    margin-bottom: -1px;\n    padding: 0.5em 1em;\n    vertical-align: top; }\n    .tabs a:hover {\n      border-bottom-color: #363636;\n      color: #363636; }\n  .tabs li {\n    display: block; }\n    .tabs li.is-active a {\n      border-bottom-color: #ff0d68;\n      color: #ff0d68; }\n  .tabs ul {\n    align-items: center;\n    border-bottom-color: #dbdbdb;\n    border-bottom-style: solid;\n    border-bottom-width: 1px;\n    display: flex;\n    flex-grow: 1;\n    flex-shrink: 0;\n    justify-content: flex-start; }\n    .tabs ul.is-left {\n      padding-right: 0.75em; }\n    .tabs ul.is-center {\n      flex: none;\n      justify-content: center;\n      padding-left: 0.75em;\n      padding-right: 0.75em; }\n    .tabs ul.is-right {\n      justify-content: flex-end;\n      padding-left: 0.75em; }\n  .tabs .icon:first-child {\n    margin-right: 0.5em; }\n  .tabs .icon:last-child {\n    margin-left: 0.5em; }\n  .tabs.is-centered ul {\n    justify-content: center; }\n  .tabs.is-right ul {\n    justify-content: flex-end; }\n  .tabs.is-boxed a {\n    border: 1px solid transparent;\n    border-radius: 4px 4px 0 0; }\n    .tabs.is-boxed a:hover {\n      background-color: whitesmoke;\n      border-bottom-color: #dbdbdb; }\n  .tabs.is-boxed li.is-active a {\n    background-color: white;\n    border-color: #dbdbdb;\n    border-bottom-color: transparent !important; }\n  .tabs.is-fullwidth li {\n    flex-grow: 1;\n    flex-shrink: 0; }\n  .tabs.is-toggle a {\n    border-color: #dbdbdb;\n    border-style: solid;\n    border-width: 1px;\n    margin-bottom: 0;\n    position: relative; }\n    .tabs.is-toggle a:hover {\n      background-color: whitesmoke;\n      border-color: #b5b5b5;\n      z-index: 2; }\n  .tabs.is-toggle li + li {\n    margin-left: -1px; }\n  .tabs.is-toggle li:first-child a {\n    border-top-left-radius: 4px;\n    border-bottom-left-radius: 4px; }\n  .tabs.is-toggle li:last-child a {\n    border-top-right-radius: 4px;\n    border-bottom-right-radius: 4px; }\n  .tabs.is-toggle li.is-active a {\n    background-color: #ff0d68;\n    border-color: #ff0d68;\n    color: #fff;\n    z-index: 1; }\n  .tabs.is-toggle ul {\n    border-bottom: none; }\n  .tabs.is-toggle.is-toggle-rounded li:first-child a {\n    border-bottom-left-radius: 9999px;\n    border-top-left-radius: 9999px;\n    padding-left: 1.25em; }\n  .tabs.is-toggle.is-toggle-rounded li:last-child a {\n    border-bottom-right-radius: 9999px;\n    border-top-right-radius: 9999px;\n    padding-right: 1.25em; }\n  .tabs.is-small {\n    font-size: 0.75rem; }\n  .tabs.is-medium {\n    font-size: 1.25rem; }\n  .tabs.is-large {\n    font-size: 1.5rem; }\n\n/* Bulma Grid */\n.column {\n  display: block;\n  flex-basis: 0;\n  flex-grow: 1;\n  flex-shrink: 1;\n  padding: 0.75rem; }\n  .columns.is-mobile > .column.is-narrow {\n    flex: none;\n    width: unset; }\n  .columns.is-mobile > .column.is-full {\n    flex: none;\n    width: 100%; }\n  .columns.is-mobile > .column.is-three-quarters {\n    flex: none;\n    width: 75%; }\n  .columns.is-mobile > .column.is-two-thirds {\n    flex: none;\n    width: 66.6666%; }\n  .columns.is-mobile > .column.is-half {\n    flex: none;\n    width: 50%; }\n  .columns.is-mobile > .column.is-one-third {\n    flex: none;\n    width: 33.3333%; }\n  .columns.is-mobile > .column.is-one-quarter {\n    flex: none;\n    width: 25%; }\n  .columns.is-mobile > .column.is-one-fifth {\n    flex: none;\n    width: 20%; }\n  .columns.is-mobile > .column.is-two-fifths {\n    flex: none;\n    width: 40%; }\n  .columns.is-mobile > .column.is-three-fifths {\n    flex: none;\n    width: 60%; }\n  .columns.is-mobile > .column.is-four-fifths {\n    flex: none;\n    width: 80%; }\n  .columns.is-mobile > .column.is-offset-three-quarters {\n    margin-left: 75%; }\n  .columns.is-mobile > .column.is-offset-two-thirds {\n    margin-left: 66.6666%; }\n  .columns.is-mobile > .column.is-offset-half {\n    margin-left: 50%; }\n  .columns.is-mobile > .column.is-offset-one-third {\n    margin-left: 33.3333%; }\n  .columns.is-mobile > .column.is-offset-one-quarter {\n    margin-left: 25%; }\n  .columns.is-mobile > .column.is-offset-one-fifth {\n    margin-left: 20%; }\n  .columns.is-mobile > .column.is-offset-two-fifths {\n    margin-left: 40%; }\n  .columns.is-mobile > .column.is-offset-three-fifths {\n    margin-left: 60%; }\n  .columns.is-mobile > .column.is-offset-four-fifths {\n    margin-left: 80%; }\n  .columns.is-mobile > .column.is-0 {\n    flex: none;\n    width: 0%; }\n  .columns.is-mobile > .column.is-offset-0 {\n    margin-left: 0%; }\n  .columns.is-mobile > .column.is-1 {\n    flex: none;\n    width: 8.33333%; }\n  .columns.is-mobile > .column.is-offset-1 {\n    margin-left: 8.33333%; }\n  .columns.is-mobile > .column.is-2 {\n    flex: none;\n    width: 16.66667%; }\n  .columns.is-mobile > .column.is-offset-2 {\n    margin-left: 16.66667%; }\n  .columns.is-mobile > .column.is-3 {\n    flex: none;\n    width: 25%; }\n  .columns.is-mobile > .column.is-offset-3 {\n    margin-left: 25%; }\n  .columns.is-mobile > .column.is-4 {\n    flex: none;\n    width: 33.33333%; }\n  .columns.is-mobile > .column.is-offset-4 {\n    margin-left: 33.33333%; }\n  .columns.is-mobile > .column.is-5 {\n    flex: none;\n    width: 41.66667%; }\n  .columns.is-mobile > .column.is-offset-5 {\n    margin-left: 41.66667%; }\n  .columns.is-mobile > .column.is-6 {\n    flex: none;\n    width: 50%; }\n  .columns.is-mobile > .column.is-offset-6 {\n    margin-left: 50%; }\n  .columns.is-mobile > .column.is-7 {\n    flex: none;\n    width: 58.33333%; }\n  .columns.is-mobile > .column.is-offset-7 {\n    margin-left: 58.33333%; }\n  .columns.is-mobile > .column.is-8 {\n    flex: none;\n    width: 66.66667%; }\n  .columns.is-mobile > .column.is-offset-8 {\n    margin-left: 66.66667%; }\n  .columns.is-mobile > .column.is-9 {\n    flex: none;\n    width: 75%; }\n  .columns.is-mobile > .column.is-offset-9 {\n    margin-left: 75%; }\n  .columns.is-mobile > .column.is-10 {\n    flex: none;\n    width: 83.33333%; }\n  .columns.is-mobile > .column.is-offset-10 {\n    margin-left: 83.33333%; }\n  .columns.is-mobile > .column.is-11 {\n    flex: none;\n    width: 91.66667%; }\n  .columns.is-mobile > .column.is-offset-11 {\n    margin-left: 91.66667%; }\n  .columns.is-mobile > .column.is-12 {\n    flex: none;\n    width: 100%; }\n  .columns.is-mobile > .column.is-offset-12 {\n    margin-left: 100%; }\n  @media screen and (max-width: 768px) {\n    .column.is-narrow-mobile {\n      flex: none;\n      width: unset; }\n    .column.is-full-mobile {\n      flex: none;\n      width: 100%; }\n    .column.is-three-quarters-mobile {\n      flex: none;\n      width: 75%; }\n    .column.is-two-thirds-mobile {\n      flex: none;\n      width: 66.6666%; }\n    .column.is-half-mobile {\n      flex: none;\n      width: 50%; }\n    .column.is-one-third-mobile {\n      flex: none;\n      width: 33.3333%; }\n    .column.is-one-quarter-mobile {\n      flex: none;\n      width: 25%; }\n    .column.is-one-fifth-mobile {\n      flex: none;\n      width: 20%; }\n    .column.is-two-fifths-mobile {\n      flex: none;\n      width: 40%; }\n    .column.is-three-fifths-mobile {\n      flex: none;\n      width: 60%; }\n    .column.is-four-fifths-mobile {\n      flex: none;\n      width: 80%; }\n    .column.is-offset-three-quarters-mobile {\n      margin-left: 75%; }\n    .column.is-offset-two-thirds-mobile {\n      margin-left: 66.6666%; }\n    .column.is-offset-half-mobile {\n      margin-left: 50%; }\n    .column.is-offset-one-third-mobile {\n      margin-left: 33.3333%; }\n    .column.is-offset-one-quarter-mobile {\n      margin-left: 25%; }\n    .column.is-offset-one-fifth-mobile {\n      margin-left: 20%; }\n    .column.is-offset-two-fifths-mobile {\n      margin-left: 40%; }\n    .column.is-offset-three-fifths-mobile {\n      margin-left: 60%; }\n    .column.is-offset-four-fifths-mobile {\n      margin-left: 80%; }\n    .column.is-0-mobile {\n      flex: none;\n      width: 0%; }\n    .column.is-offset-0-mobile {\n      margin-left: 0%; }\n    .column.is-1-mobile {\n      flex: none;\n      width: 8.33333%; }\n    .column.is-offset-1-mobile {\n      margin-left: 8.33333%; }\n    .column.is-2-mobile {\n      flex: none;\n      width: 16.66667%; }\n    .column.is-offset-2-mobile {\n      margin-left: 16.66667%; }\n    .column.is-3-mobile {\n      flex: none;\n      width: 25%; }\n    .column.is-offset-3-mobile {\n      margin-left: 25%; }\n    .column.is-4-mobile {\n      flex: none;\n      width: 33.33333%; }\n    .column.is-offset-4-mobile {\n      margin-left: 33.33333%; }\n    .column.is-5-mobile {\n      flex: none;\n      width: 41.66667%; }\n    .column.is-offset-5-mobile {\n      margin-left: 41.66667%; }\n    .column.is-6-mobile {\n      flex: none;\n      width: 50%; }\n    .column.is-offset-6-mobile {\n      margin-left: 50%; }\n    .column.is-7-mobile {\n      flex: none;\n      width: 58.33333%; }\n    .column.is-offset-7-mobile {\n      margin-left: 58.33333%; }\n    .column.is-8-mobile {\n      flex: none;\n      width: 66.66667%; }\n    .column.is-offset-8-mobile {\n      margin-left: 66.66667%; }\n    .column.is-9-mobile {\n      flex: none;\n      width: 75%; }\n    .column.is-offset-9-mobile {\n      margin-left: 75%; }\n    .column.is-10-mobile {\n      flex: none;\n      width: 83.33333%; }\n    .column.is-offset-10-mobile {\n      margin-left: 83.33333%; }\n    .column.is-11-mobile {\n      flex: none;\n      width: 91.66667%; }\n    .column.is-offset-11-mobile {\n      margin-left: 91.66667%; }\n    .column.is-12-mobile {\n      flex: none;\n      width: 100%; }\n    .column.is-offset-12-mobile {\n      margin-left: 100%; } }\n  @media screen and (min-width: 769px), print {\n    .column.is-narrow, .column.is-narrow-tablet {\n      flex: none;\n      width: unset; }\n    .column.is-full, .column.is-full-tablet {\n      flex: none;\n      width: 100%; }\n    .column.is-three-quarters, .column.is-three-quarters-tablet {\n      flex: none;\n      width: 75%; }\n    .column.is-two-thirds, .column.is-two-thirds-tablet {\n      flex: none;\n      width: 66.6666%; }\n    .column.is-half, .column.is-half-tablet {\n      flex: none;\n      width: 50%; }\n    .column.is-one-third, .column.is-one-third-tablet {\n      flex: none;\n      width: 33.3333%; }\n    .column.is-one-quarter, .column.is-one-quarter-tablet {\n      flex: none;\n      width: 25%; }\n    .column.is-one-fifth, .column.is-one-fifth-tablet {\n      flex: none;\n      width: 20%; }\n    .column.is-two-fifths, .column.is-two-fifths-tablet {\n      flex: none;\n      width: 40%; }\n    .column.is-three-fifths, .column.is-three-fifths-tablet {\n      flex: none;\n      width: 60%; }\n    .column.is-four-fifths, .column.is-four-fifths-tablet {\n      flex: none;\n      width: 80%; }\n    .column.is-offset-three-quarters, .column.is-offset-three-quarters-tablet {\n      margin-left: 75%; }\n    .column.is-offset-two-thirds, .column.is-offset-two-thirds-tablet {\n      margin-left: 66.6666%; }\n    .column.is-offset-half, .column.is-offset-half-tablet {\n      margin-left: 50%; }\n    .column.is-offset-one-third, .column.is-offset-one-third-tablet {\n      margin-left: 33.3333%; }\n    .column.is-offset-one-quarter, .column.is-offset-one-quarter-tablet {\n      margin-left: 25%; }\n    .column.is-offset-one-fifth, .column.is-offset-one-fifth-tablet {\n      margin-left: 20%; }\n    .column.is-offset-two-fifths, .column.is-offset-two-fifths-tablet {\n      margin-left: 40%; }\n    .column.is-offset-three-fifths, .column.is-offset-three-fifths-tablet {\n      margin-left: 60%; }\n    .column.is-offset-four-fifths, .column.is-offset-four-fifths-tablet {\n      margin-left: 80%; }\n    .column.is-0, .column.is-0-tablet {\n      flex: none;\n      width: 0%; }\n    .column.is-offset-0, .column.is-offset-0-tablet {\n      margin-left: 0%; }\n    .column.is-1, .column.is-1-tablet {\n      flex: none;\n      width: 8.33333%; }\n    .column.is-offset-1, .column.is-offset-1-tablet {\n      margin-left: 8.33333%; }\n    .column.is-2, .column.is-2-tablet {\n      flex: none;\n      width: 16.66667%; }\n    .column.is-offset-2, .column.is-offset-2-tablet {\n      margin-left: 16.66667%; }\n    .column.is-3, .column.is-3-tablet {\n      flex: none;\n      width: 25%; }\n    .column.is-offset-3, .column.is-offset-3-tablet {\n      margin-left: 25%; }\n    .column.is-4, .column.is-4-tablet {\n      flex: none;\n      width: 33.33333%; }\n    .column.is-offset-4, .column.is-offset-4-tablet {\n      margin-left: 33.33333%; }\n    .column.is-5, .column.is-5-tablet {\n      flex: none;\n      width: 41.66667%; }\n    .column.is-offset-5, .column.is-offset-5-tablet {\n      margin-left: 41.66667%; }\n    .column.is-6, .column.is-6-tablet {\n      flex: none;\n      width: 50%; }\n    .column.is-offset-6, .column.is-offset-6-tablet {\n      margin-left: 50%; }\n    .column.is-7, .column.is-7-tablet {\n      flex: none;\n      width: 58.33333%; }\n    .column.is-offset-7, .column.is-offset-7-tablet {\n      margin-left: 58.33333%; }\n    .column.is-8, .column.is-8-tablet {\n      flex: none;\n      width: 66.66667%; }\n    .column.is-offset-8, .column.is-offset-8-tablet {\n      margin-left: 66.66667%; }\n    .column.is-9, .column.is-9-tablet {\n      flex: none;\n      width: 75%; }\n    .column.is-offset-9, .column.is-offset-9-tablet {\n      margin-left: 75%; }\n    .column.is-10, .column.is-10-tablet {\n      flex: none;\n      width: 83.33333%; }\n    .column.is-offset-10, .column.is-offset-10-tablet {\n      margin-left: 83.33333%; }\n    .column.is-11, .column.is-11-tablet {\n      flex: none;\n      width: 91.66667%; }\n    .column.is-offset-11, .column.is-offset-11-tablet {\n      margin-left: 91.66667%; }\n    .column.is-12, .column.is-12-tablet {\n      flex: none;\n      width: 100%; }\n    .column.is-offset-12, .column.is-offset-12-tablet {\n      margin-left: 100%; } }\n  @media screen and (max-width: 1023px) {\n    .column.is-narrow-touch {\n      flex: none;\n      width: unset; }\n    .column.is-full-touch {\n      flex: none;\n      width: 100%; }\n    .column.is-three-quarters-touch {\n      flex: none;\n      width: 75%; }\n    .column.is-two-thirds-touch {\n      flex: none;\n      width: 66.6666%; }\n    .column.is-half-touch {\n      flex: none;\n      width: 50%; }\n    .column.is-one-third-touch {\n      flex: none;\n      width: 33.3333%; }\n    .column.is-one-quarter-touch {\n      flex: none;\n      width: 25%; }\n    .column.is-one-fifth-touch {\n      flex: none;\n      width: 20%; }\n    .column.is-two-fifths-touch {\n      flex: none;\n      width: 40%; }\n    .column.is-three-fifths-touch {\n      flex: none;\n      width: 60%; }\n    .column.is-four-fifths-touch {\n      flex: none;\n      width: 80%; }\n    .column.is-offset-three-quarters-touch {\n      margin-left: 75%; }\n    .column.is-offset-two-thirds-touch {\n      margin-left: 66.6666%; }\n    .column.is-offset-half-touch {\n      margin-left: 50%; }\n    .column.is-offset-one-third-touch {\n      margin-left: 33.3333%; }\n    .column.is-offset-one-quarter-touch {\n      margin-left: 25%; }\n    .column.is-offset-one-fifth-touch {\n      margin-left: 20%; }\n    .column.is-offset-two-fifths-touch {\n      margin-left: 40%; }\n    .column.is-offset-three-fifths-touch {\n      margin-left: 60%; }\n    .column.is-offset-four-fifths-touch {\n      margin-left: 80%; }\n    .column.is-0-touch {\n      flex: none;\n      width: 0%; }\n    .column.is-offset-0-touch {\n      margin-left: 0%; }\n    .column.is-1-touch {\n      flex: none;\n      width: 8.33333%; }\n    .column.is-offset-1-touch {\n      margin-left: 8.33333%; }\n    .column.is-2-touch {\n      flex: none;\n      width: 16.66667%; }\n    .column.is-offset-2-touch {\n      margin-left: 16.66667%; }\n    .column.is-3-touch {\n      flex: none;\n      width: 25%; }\n    .column.is-offset-3-touch {\n      margin-left: 25%; }\n    .column.is-4-touch {\n      flex: none;\n      width: 33.33333%; }\n    .column.is-offset-4-touch {\n      margin-left: 33.33333%; }\n    .column.is-5-touch {\n      flex: none;\n      width: 41.66667%; }\n    .column.is-offset-5-touch {\n      margin-left: 41.66667%; }\n    .column.is-6-touch {\n      flex: none;\n      width: 50%; }\n    .column.is-offset-6-touch {\n      margin-left: 50%; }\n    .column.is-7-touch {\n      flex: none;\n      width: 58.33333%; }\n    .column.is-offset-7-touch {\n      margin-left: 58.33333%; }\n    .column.is-8-touch {\n      flex: none;\n      width: 66.66667%; }\n    .column.is-offset-8-touch {\n      margin-left: 66.66667%; }\n    .column.is-9-touch {\n      flex: none;\n      width: 75%; }\n    .column.is-offset-9-touch {\n      margin-left: 75%; }\n    .column.is-10-touch {\n      flex: none;\n      width: 83.33333%; }\n    .column.is-offset-10-touch {\n      margin-left: 83.33333%; }\n    .column.is-11-touch {\n      flex: none;\n      width: 91.66667%; }\n    .column.is-offset-11-touch {\n      margin-left: 91.66667%; }\n    .column.is-12-touch {\n      flex: none;\n      width: 100%; }\n    .column.is-offset-12-touch {\n      margin-left: 100%; } }\n  @media screen and (min-width: 1024px) {\n    .column.is-narrow-desktop {\n      flex: none;\n      width: unset; }\n    .column.is-full-desktop {\n      flex: none;\n      width: 100%; }\n    .column.is-three-quarters-desktop {\n      flex: none;\n      width: 75%; }\n    .column.is-two-thirds-desktop {\n      flex: none;\n      width: 66.6666%; }\n    .column.is-half-desktop {\n      flex: none;\n      width: 50%; }\n    .column.is-one-third-desktop {\n      flex: none;\n      width: 33.3333%; }\n    .column.is-one-quarter-desktop {\n      flex: none;\n      width: 25%; }\n    .column.is-one-fifth-desktop {\n      flex: none;\n      width: 20%; }\n    .column.is-two-fifths-desktop {\n      flex: none;\n      width: 40%; }\n    .column.is-three-fifths-desktop {\n      flex: none;\n      width: 60%; }\n    .column.is-four-fifths-desktop {\n      flex: none;\n      width: 80%; }\n    .column.is-offset-three-quarters-desktop {\n      margin-left: 75%; }\n    .column.is-offset-two-thirds-desktop {\n      margin-left: 66.6666%; }\n    .column.is-offset-half-desktop {\n      margin-left: 50%; }\n    .column.is-offset-one-third-desktop {\n      margin-left: 33.3333%; }\n    .column.is-offset-one-quarter-desktop {\n      margin-left: 25%; }\n    .column.is-offset-one-fifth-desktop {\n      margin-left: 20%; }\n    .column.is-offset-two-fifths-desktop {\n      margin-left: 40%; }\n    .column.is-offset-three-fifths-desktop {\n      margin-left: 60%; }\n    .column.is-offset-four-fifths-desktop {\n      margin-left: 80%; }\n    .column.is-0-desktop {\n      flex: none;\n      width: 0%; }\n    .column.is-offset-0-desktop {\n      margin-left: 0%; }\n    .column.is-1-desktop {\n      flex: none;\n      width: 8.33333%; }\n    .column.is-offset-1-desktop {\n      margin-left: 8.33333%; }\n    .column.is-2-desktop {\n      flex: none;\n      width: 16.66667%; }\n    .column.is-offset-2-desktop {\n      margin-left: 16.66667%; }\n    .column.is-3-desktop {\n      flex: none;\n      width: 25%; }\n    .column.is-offset-3-desktop {\n      margin-left: 25%; }\n    .column.is-4-desktop {\n      flex: none;\n      width: 33.33333%; }\n    .column.is-offset-4-desktop {\n      margin-left: 33.33333%; }\n    .column.is-5-desktop {\n      flex: none;\n      width: 41.66667%; }\n    .column.is-offset-5-desktop {\n      margin-left: 41.66667%; }\n    .column.is-6-desktop {\n      flex: none;\n      width: 50%; }\n    .column.is-offset-6-desktop {\n      margin-left: 50%; }\n    .column.is-7-desktop {\n      flex: none;\n      width: 58.33333%; }\n    .column.is-offset-7-desktop {\n      margin-left: 58.33333%; }\n    .column.is-8-desktop {\n      flex: none;\n      width: 66.66667%; }\n    .column.is-offset-8-desktop {\n      margin-left: 66.66667%; }\n    .column.is-9-desktop {\n      flex: none;\n      width: 75%; }\n    .column.is-offset-9-desktop {\n      margin-left: 75%; }\n    .column.is-10-desktop {\n      flex: none;\n      width: 83.33333%; }\n    .column.is-offset-10-desktop {\n      margin-left: 83.33333%; }\n    .column.is-11-desktop {\n      flex: none;\n      width: 91.66667%; }\n    .column.is-offset-11-desktop {\n      margin-left: 91.66667%; }\n    .column.is-12-desktop {\n      flex: none;\n      width: 100%; }\n    .column.is-offset-12-desktop {\n      margin-left: 100%; } }\n  @media screen and (min-width: 1216px) {\n    .column.is-narrow-widescreen {\n      flex: none;\n      width: unset; }\n    .column.is-full-widescreen {\n      flex: none;\n      width: 100%; }\n    .column.is-three-quarters-widescreen {\n      flex: none;\n      width: 75%; }\n    .column.is-two-thirds-widescreen {\n      flex: none;\n      width: 66.6666%; }\n    .column.is-half-widescreen {\n      flex: none;\n      width: 50%; }\n    .column.is-one-third-widescreen {\n      flex: none;\n      width: 33.3333%; }\n    .column.is-one-quarter-widescreen {\n      flex: none;\n      width: 25%; }\n    .column.is-one-fifth-widescreen {\n      flex: none;\n      width: 20%; }\n    .column.is-two-fifths-widescreen {\n      flex: none;\n      width: 40%; }\n    .column.is-three-fifths-widescreen {\n      flex: none;\n      width: 60%; }\n    .column.is-four-fifths-widescreen {\n      flex: none;\n      width: 80%; }\n    .column.is-offset-three-quarters-widescreen {\n      margin-left: 75%; }\n    .column.is-offset-two-thirds-widescreen {\n      margin-left: 66.6666%; }\n    .column.is-offset-half-widescreen {\n      margin-left: 50%; }\n    .column.is-offset-one-third-widescreen {\n      margin-left: 33.3333%; }\n    .column.is-offset-one-quarter-widescreen {\n      margin-left: 25%; }\n    .column.is-offset-one-fifth-widescreen {\n      margin-left: 20%; }\n    .column.is-offset-two-fifths-widescreen {\n      margin-left: 40%; }\n    .column.is-offset-three-fifths-widescreen {\n      margin-left: 60%; }\n    .column.is-offset-four-fifths-widescreen {\n      margin-left: 80%; }\n    .column.is-0-widescreen {\n      flex: none;\n      width: 0%; }\n    .column.is-offset-0-widescreen {\n      margin-left: 0%; }\n    .column.is-1-widescreen {\n      flex: none;\n      width: 8.33333%; }\n    .column.is-offset-1-widescreen {\n      margin-left: 8.33333%; }\n    .column.is-2-widescreen {\n      flex: none;\n      width: 16.66667%; }\n    .column.is-offset-2-widescreen {\n      margin-left: 16.66667%; }\n    .column.is-3-widescreen {\n      flex: none;\n      width: 25%; }\n    .column.is-offset-3-widescreen {\n      margin-left: 25%; }\n    .column.is-4-widescreen {\n      flex: none;\n      width: 33.33333%; }\n    .column.is-offset-4-widescreen {\n      margin-left: 33.33333%; }\n    .column.is-5-widescreen {\n      flex: none;\n      width: 41.66667%; }\n    .column.is-offset-5-widescreen {\n      margin-left: 41.66667%; }\n    .column.is-6-widescreen {\n      flex: none;\n      width: 50%; }\n    .column.is-offset-6-widescreen {\n      margin-left: 50%; }\n    .column.is-7-widescreen {\n      flex: none;\n      width: 58.33333%; }\n    .column.is-offset-7-widescreen {\n      margin-left: 58.33333%; }\n    .column.is-8-widescreen {\n      flex: none;\n      width: 66.66667%; }\n    .column.is-offset-8-widescreen {\n      margin-left: 66.66667%; }\n    .column.is-9-widescreen {\n      flex: none;\n      width: 75%; }\n    .column.is-offset-9-widescreen {\n      margin-left: 75%; }\n    .column.is-10-widescreen {\n      flex: none;\n      width: 83.33333%; }\n    .column.is-offset-10-widescreen {\n      margin-left: 83.33333%; }\n    .column.is-11-widescreen {\n      flex: none;\n      width: 91.66667%; }\n    .column.is-offset-11-widescreen {\n      margin-left: 91.66667%; }\n    .column.is-12-widescreen {\n      flex: none;\n      width: 100%; }\n    .column.is-offset-12-widescreen {\n      margin-left: 100%; } }\n  @media screen and (min-width: 1408px) {\n    .column.is-narrow-fullhd {\n      flex: none;\n      width: unset; }\n    .column.is-full-fullhd {\n      flex: none;\n      width: 100%; }\n    .column.is-three-quarters-fullhd {\n      flex: none;\n      width: 75%; }\n    .column.is-two-thirds-fullhd {\n      flex: none;\n      width: 66.6666%; }\n    .column.is-half-fullhd {\n      flex: none;\n      width: 50%; }\n    .column.is-one-third-fullhd {\n      flex: none;\n      width: 33.3333%; }\n    .column.is-one-quarter-fullhd {\n      flex: none;\n      width: 25%; }\n    .column.is-one-fifth-fullhd {\n      flex: none;\n      width: 20%; }\n    .column.is-two-fifths-fullhd {\n      flex: none;\n      width: 40%; }\n    .column.is-three-fifths-fullhd {\n      flex: none;\n      width: 60%; }\n    .column.is-four-fifths-fullhd {\n      flex: none;\n      width: 80%; }\n    .column.is-offset-three-quarters-fullhd {\n      margin-left: 75%; }\n    .column.is-offset-two-thirds-fullhd {\n      margin-left: 66.6666%; }\n    .column.is-offset-half-fullhd {\n      margin-left: 50%; }\n    .column.is-offset-one-third-fullhd {\n      margin-left: 33.3333%; }\n    .column.is-offset-one-quarter-fullhd {\n      margin-left: 25%; }\n    .column.is-offset-one-fifth-fullhd {\n      margin-left: 20%; }\n    .column.is-offset-two-fifths-fullhd {\n      margin-left: 40%; }\n    .column.is-offset-three-fifths-fullhd {\n      margin-left: 60%; }\n    .column.is-offset-four-fifths-fullhd {\n      margin-left: 80%; }\n    .column.is-0-fullhd {\n      flex: none;\n      width: 0%; }\n    .column.is-offset-0-fullhd {\n      margin-left: 0%; }\n    .column.is-1-fullhd {\n      flex: none;\n      width: 8.33333%; }\n    .column.is-offset-1-fullhd {\n      margin-left: 8.33333%; }\n    .column.is-2-fullhd {\n      flex: none;\n      width: 16.66667%; }\n    .column.is-offset-2-fullhd {\n      margin-left: 16.66667%; }\n    .column.is-3-fullhd {\n      flex: none;\n      width: 25%; }\n    .column.is-offset-3-fullhd {\n      margin-left: 25%; }\n    .column.is-4-fullhd {\n      flex: none;\n      width: 33.33333%; }\n    .column.is-offset-4-fullhd {\n      margin-left: 33.33333%; }\n    .column.is-5-fullhd {\n      flex: none;\n      width: 41.66667%; }\n    .column.is-offset-5-fullhd {\n      margin-left: 41.66667%; }\n    .column.is-6-fullhd {\n      flex: none;\n      width: 50%; }\n    .column.is-offset-6-fullhd {\n      margin-left: 50%; }\n    .column.is-7-fullhd {\n      flex: none;\n      width: 58.33333%; }\n    .column.is-offset-7-fullhd {\n      margin-left: 58.33333%; }\n    .column.is-8-fullhd {\n      flex: none;\n      width: 66.66667%; }\n    .column.is-offset-8-fullhd {\n      margin-left: 66.66667%; }\n    .column.is-9-fullhd {\n      flex: none;\n      width: 75%; }\n    .column.is-offset-9-fullhd {\n      margin-left: 75%; }\n    .column.is-10-fullhd {\n      flex: none;\n      width: 83.33333%; }\n    .column.is-offset-10-fullhd {\n      margin-left: 83.33333%; }\n    .column.is-11-fullhd {\n      flex: none;\n      width: 91.66667%; }\n    .column.is-offset-11-fullhd {\n      margin-left: 91.66667%; }\n    .column.is-12-fullhd {\n      flex: none;\n      width: 100%; }\n    .column.is-offset-12-fullhd {\n      margin-left: 100%; } }\n.columns {\n  margin-left: -0.75rem;\n  margin-right: -0.75rem;\n  margin-top: -0.75rem; }\n  .columns:last-child {\n    margin-bottom: -0.75rem; }\n  .columns:not(:last-child) {\n    margin-bottom: calc(1.5rem - 0.75rem); }\n  .columns.is-centered {\n    justify-content: center; }\n  .columns.is-gapless {\n    margin-left: 0;\n    margin-right: 0;\n    margin-top: 0; }\n    .columns.is-gapless > .column {\n      margin: 0;\n      padding: 0 !important; }\n    .columns.is-gapless:not(:last-child) {\n      margin-bottom: 1.5rem; }\n    .columns.is-gapless:last-child {\n      margin-bottom: 0; }\n  .columns.is-mobile {\n    display: flex; }\n  .columns.is-multiline {\n    flex-wrap: wrap; }\n  .columns.is-vcentered {\n    align-items: center; }\n  @media screen and (min-width: 769px), print {\n    .columns:not(.is-desktop) {\n      display: flex; } }\n  @media screen and (min-width: 1024px) {\n    .columns.is-desktop {\n      display: flex; } }\n.columns.is-variable {\n  --columnGap: 0.75rem;\n  margin-left: calc(-1 * var(--columnGap));\n  margin-right: calc(-1 * var(--columnGap)); }\n  .columns.is-variable > .column {\n    padding-left: var(--columnGap);\n    padding-right: var(--columnGap); }\n  .columns.is-variable.is-0 {\n    --columnGap: 0rem; }\n  @media screen and (max-width: 768px) {\n    .columns.is-variable.is-0-mobile {\n      --columnGap: 0rem; } }\n  @media screen and (min-width: 769px), print {\n    .columns.is-variable.is-0-tablet {\n      --columnGap: 0rem; } }\n  @media screen and (min-width: 769px) and (max-width: 1023px) {\n    .columns.is-variable.is-0-tablet-only {\n      --columnGap: 0rem; } }\n  @media screen and (max-width: 1023px) {\n    .columns.is-variable.is-0-touch {\n      --columnGap: 0rem; } }\n  @media screen and (min-width: 1024px) {\n    .columns.is-variable.is-0-desktop {\n      --columnGap: 0rem; } }\n  @media screen and (min-width: 1024px) and (max-width: 1215px) {\n    .columns.is-variable.is-0-desktop-only {\n      --columnGap: 0rem; } }\n  @media screen and (min-width: 1216px) {\n    .columns.is-variable.is-0-widescreen {\n      --columnGap: 0rem; } }\n  @media screen and (min-width: 1216px) and (max-width: 1407px) {\n    .columns.is-variable.is-0-widescreen-only {\n      --columnGap: 0rem; } }\n  @media screen and (min-width: 1408px) {\n    .columns.is-variable.is-0-fullhd {\n      --columnGap: 0rem; } }\n  .columns.is-variable.is-1 {\n    --columnGap: 0.25rem; }\n  @media screen and (max-width: 768px) {\n    .columns.is-variable.is-1-mobile {\n      --columnGap: 0.25rem; } }\n  @media screen and (min-width: 769px), print {\n    .columns.is-variable.is-1-tablet {\n      --columnGap: 0.25rem; } }\n  @media screen and (min-width: 769px) and (max-width: 1023px) {\n    .columns.is-variable.is-1-tablet-only {\n      --columnGap: 0.25rem; } }\n  @media screen and (max-width: 1023px) {\n    .columns.is-variable.is-1-touch {\n      --columnGap: 0.25rem; } }\n  @media screen and (min-width: 1024px) {\n    .columns.is-variable.is-1-desktop {\n      --columnGap: 0.25rem; } }\n  @media screen and (min-width: 1024px) and (max-width: 1215px) {\n    .columns.is-variable.is-1-desktop-only {\n      --columnGap: 0.25rem; } }\n  @media screen and (min-width: 1216px) {\n    .columns.is-variable.is-1-widescreen {\n      --columnGap: 0.25rem; } }\n  @media screen and (min-width: 1216px) and (max-width: 1407px) {\n    .columns.is-variable.is-1-widescreen-only {\n      --columnGap: 0.25rem; } }\n  @media screen and (min-width: 1408px) {\n    .columns.is-variable.is-1-fullhd {\n      --columnGap: 0.25rem; } }\n  .columns.is-variable.is-2 {\n    --columnGap: 0.5rem; }\n  @media screen and (max-width: 768px) {\n    .columns.is-variable.is-2-mobile {\n      --columnGap: 0.5rem; } }\n  @media screen and (min-width: 769px), print {\n    .columns.is-variable.is-2-tablet {\n      --columnGap: 0.5rem; } }\n  @media screen and (min-width: 769px) and (max-width: 1023px) {\n    .columns.is-variable.is-2-tablet-only {\n      --columnGap: 0.5rem; } }\n  @media screen and (max-width: 1023px) {\n    .columns.is-variable.is-2-touch {\n      --columnGap: 0.5rem; } }\n  @media screen and (min-width: 1024px) {\n    .columns.is-variable.is-2-desktop {\n      --columnGap: 0.5rem; } }\n  @media screen and (min-width: 1024px) and (max-width: 1215px) {\n    .columns.is-variable.is-2-desktop-only {\n      --columnGap: 0.5rem; } }\n  @media screen and (min-width: 1216px) {\n    .columns.is-variable.is-2-widescreen {\n      --columnGap: 0.5rem; } }\n  @media screen and (min-width: 1216px) and (max-width: 1407px) {\n    .columns.is-variable.is-2-widescreen-only {\n      --columnGap: 0.5rem; } }\n  @media screen and (min-width: 1408px) {\n    .columns.is-variable.is-2-fullhd {\n      --columnGap: 0.5rem; } }\n  .columns.is-variable.is-3 {\n    --columnGap: 0.75rem; }\n  @media screen and (max-width: 768px) {\n    .columns.is-variable.is-3-mobile {\n      --columnGap: 0.75rem; } }\n  @media screen and (min-width: 769px), print {\n    .columns.is-variable.is-3-tablet {\n      --columnGap: 0.75rem; } }\n  @media screen and (min-width: 769px) and (max-width: 1023px) {\n    .columns.is-variable.is-3-tablet-only {\n      --columnGap: 0.75rem; } }\n  @media screen and (max-width: 1023px) {\n    .columns.is-variable.is-3-touch {\n      --columnGap: 0.75rem; } }\n  @media screen and (min-width: 1024px) {\n    .columns.is-variable.is-3-desktop {\n      --columnGap: 0.75rem; } }\n  @media screen and (min-width: 1024px) and (max-width: 1215px) {\n    .columns.is-variable.is-3-desktop-only {\n      --columnGap: 0.75rem; } }\n  @media screen and (min-width: 1216px) {\n    .columns.is-variable.is-3-widescreen {\n      --columnGap: 0.75rem; } }\n  @media screen and (min-width: 1216px) and (max-width: 1407px) {\n    .columns.is-variable.is-3-widescreen-only {\n      --columnGap: 0.75rem; } }\n  @media screen and (min-width: 1408px) {\n    .columns.is-variable.is-3-fullhd {\n      --columnGap: 0.75rem; } }\n  .columns.is-variable.is-4 {\n    --columnGap: 1rem; }\n  @media screen and (max-width: 768px) {\n    .columns.is-variable.is-4-mobile {\n      --columnGap: 1rem; } }\n  @media screen and (min-width: 769px), print {\n    .columns.is-variable.is-4-tablet {\n      --columnGap: 1rem; } }\n  @media screen and (min-width: 769px) and (max-width: 1023px) {\n    .columns.is-variable.is-4-tablet-only {\n      --columnGap: 1rem; } }\n  @media screen and (max-width: 1023px) {\n    .columns.is-variable.is-4-touch {\n      --columnGap: 1rem; } }\n  @media screen and (min-width: 1024px) {\n    .columns.is-variable.is-4-desktop {\n      --columnGap: 1rem; } }\n  @media screen and (min-width: 1024px) and (max-width: 1215px) {\n    .columns.is-variable.is-4-desktop-only {\n      --columnGap: 1rem; } }\n  @media screen and (min-width: 1216px) {\n    .columns.is-variable.is-4-widescreen {\n      --columnGap: 1rem; } }\n  @media screen and (min-width: 1216px) and (max-width: 1407px) {\n    .columns.is-variable.is-4-widescreen-only {\n      --columnGap: 1rem; } }\n  @media screen and (min-width: 1408px) {\n    .columns.is-variable.is-4-fullhd {\n      --columnGap: 1rem; } }\n  .columns.is-variable.is-5 {\n    --columnGap: 1.25rem; }\n  @media screen and (max-width: 768px) {\n    .columns.is-variable.is-5-mobile {\n      --columnGap: 1.25rem; } }\n  @media screen and (min-width: 769px), print {\n    .columns.is-variable.is-5-tablet {\n      --columnGap: 1.25rem; } }\n  @media screen and (min-width: 769px) and (max-width: 1023px) {\n    .columns.is-variable.is-5-tablet-only {\n      --columnGap: 1.25rem; } }\n  @media screen and (max-width: 1023px) {\n    .columns.is-variable.is-5-touch {\n      --columnGap: 1.25rem; } }\n  @media screen and (min-width: 1024px) {\n    .columns.is-variable.is-5-desktop {\n      --columnGap: 1.25rem; } }\n  @media screen and (min-width: 1024px) and (max-width: 1215px) {\n    .columns.is-variable.is-5-desktop-only {\n      --columnGap: 1.25rem; } }\n  @media screen and (min-width: 1216px) {\n    .columns.is-variable.is-5-widescreen {\n      --columnGap: 1.25rem; } }\n  @media screen and (min-width: 1216px) and (max-width: 1407px) {\n    .columns.is-variable.is-5-widescreen-only {\n      --columnGap: 1.25rem; } }\n  @media screen and (min-width: 1408px) {\n    .columns.is-variable.is-5-fullhd {\n      --columnGap: 1.25rem; } }\n  .columns.is-variable.is-6 {\n    --columnGap: 1.5rem; }\n  @media screen and (max-width: 768px) {\n    .columns.is-variable.is-6-mobile {\n      --columnGap: 1.5rem; } }\n  @media screen and (min-width: 769px), print {\n    .columns.is-variable.is-6-tablet {\n      --columnGap: 1.5rem; } }\n  @media screen and (min-width: 769px) and (max-width: 1023px) {\n    .columns.is-variable.is-6-tablet-only {\n      --columnGap: 1.5rem; } }\n  @media screen and (max-width: 1023px) {\n    .columns.is-variable.is-6-touch {\n      --columnGap: 1.5rem; } }\n  @media screen and (min-width: 1024px) {\n    .columns.is-variable.is-6-desktop {\n      --columnGap: 1.5rem; } }\n  @media screen and (min-width: 1024px) and (max-width: 1215px) {\n    .columns.is-variable.is-6-desktop-only {\n      --columnGap: 1.5rem; } }\n  @media screen and (min-width: 1216px) {\n    .columns.is-variable.is-6-widescreen {\n      --columnGap: 1.5rem; } }\n  @media screen and (min-width: 1216px) and (max-width: 1407px) {\n    .columns.is-variable.is-6-widescreen-only {\n      --columnGap: 1.5rem; } }\n  @media screen and (min-width: 1408px) {\n    .columns.is-variable.is-6-fullhd {\n      --columnGap: 1.5rem; } }\n  .columns.is-variable.is-7 {\n    --columnGap: 1.75rem; }\n  @media screen and (max-width: 768px) {\n    .columns.is-variable.is-7-mobile {\n      --columnGap: 1.75rem; } }\n  @media screen and (min-width: 769px), print {\n    .columns.is-variable.is-7-tablet {\n      --columnGap: 1.75rem; } }\n  @media screen and (min-width: 769px) and (max-width: 1023px) {\n    .columns.is-variable.is-7-tablet-only {\n      --columnGap: 1.75rem; } }\n  @media screen and (max-width: 1023px) {\n    .columns.is-variable.is-7-touch {\n      --columnGap: 1.75rem; } }\n  @media screen and (min-width: 1024px) {\n    .columns.is-variable.is-7-desktop {\n      --columnGap: 1.75rem; } }\n  @media screen and (min-width: 1024px) and (max-width: 1215px) {\n    .columns.is-variable.is-7-desktop-only {\n      --columnGap: 1.75rem; } }\n  @media screen and (min-width: 1216px) {\n    .columns.is-variable.is-7-widescreen {\n      --columnGap: 1.75rem; } }\n  @media screen and (min-width: 1216px) and (max-width: 1407px) {\n    .columns.is-variable.is-7-widescreen-only {\n      --columnGap: 1.75rem; } }\n  @media screen and (min-width: 1408px) {\n    .columns.is-variable.is-7-fullhd {\n      --columnGap: 1.75rem; } }\n  .columns.is-variable.is-8 {\n    --columnGap: 2rem; }\n  @media screen and (max-width: 768px) {\n    .columns.is-variable.is-8-mobile {\n      --columnGap: 2rem; } }\n  @media screen and (min-width: 769px), print {\n    .columns.is-variable.is-8-tablet {\n      --columnGap: 2rem; } }\n  @media screen and (min-width: 769px) and (max-width: 1023px) {\n    .columns.is-variable.is-8-tablet-only {\n      --columnGap: 2rem; } }\n  @media screen and (max-width: 1023px) {\n    .columns.is-variable.is-8-touch {\n      --columnGap: 2rem; } }\n  @media screen and (min-width: 1024px) {\n    .columns.is-variable.is-8-desktop {\n      --columnGap: 2rem; } }\n  @media screen and (min-width: 1024px) and (max-width: 1215px) {\n    .columns.is-variable.is-8-desktop-only {\n      --columnGap: 2rem; } }\n  @media screen and (min-width: 1216px) {\n    .columns.is-variable.is-8-widescreen {\n      --columnGap: 2rem; } }\n  @media screen and (min-width: 1216px) and (max-width: 1407px) {\n    .columns.is-variable.is-8-widescreen-only {\n      --columnGap: 2rem; } }\n  @media screen and (min-width: 1408px) {\n    .columns.is-variable.is-8-fullhd {\n      --columnGap: 2rem; } }\n.tile {\n  align-items: stretch;\n  display: block;\n  flex-basis: 0;\n  flex-grow: 1;\n  flex-shrink: 1;\n  min-height: min-content; }\n  .tile.is-ancestor {\n    margin-left: -0.75rem;\n    margin-right: -0.75rem;\n    margin-top: -0.75rem; }\n    .tile.is-ancestor:last-child {\n      margin-bottom: -0.75rem; }\n    .tile.is-ancestor:not(:last-child) {\n      margin-bottom: 0.75rem; }\n  .tile.is-child {\n    margin: 0 !important; }\n  .tile.is-parent {\n    padding: 0.75rem; }\n  .tile.is-vertical {\n    flex-direction: column; }\n    .tile.is-vertical > .tile.is-child:not(:last-child) {\n      margin-bottom: 1.5rem !important; }\n  @media screen and (min-width: 769px), print {\n    .tile:not(.is-child) {\n      display: flex; }\n    .tile.is-1 {\n      flex: none;\n      width: 8.33333%; }\n    .tile.is-2 {\n      flex: none;\n      width: 16.66667%; }\n    .tile.is-3 {\n      flex: none;\n      width: 25%; }\n    .tile.is-4 {\n      flex: none;\n      width: 33.33333%; }\n    .tile.is-5 {\n      flex: none;\n      width: 41.66667%; }\n    .tile.is-6 {\n      flex: none;\n      width: 50%; }\n    .tile.is-7 {\n      flex: none;\n      width: 58.33333%; }\n    .tile.is-8 {\n      flex: none;\n      width: 66.66667%; }\n    .tile.is-9 {\n      flex: none;\n      width: 75%; }\n    .tile.is-10 {\n      flex: none;\n      width: 83.33333%; }\n    .tile.is-11 {\n      flex: none;\n      width: 91.66667%; }\n    .tile.is-12 {\n      flex: none;\n      width: 100%; } }\n/* Bulma Helpers */\n.has-text-white {\n  color: white !important; }\n\na.has-text-white:hover, a.has-text-white:focus {\n  color: #e6e6e6 !important; }\n\n.has-background-white {\n  background-color: white !important; }\n\n.has-text-black {\n  color: #0a0a0a !important; }\n\na.has-text-black:hover, a.has-text-black:focus {\n  color: black !important; }\n\n.has-background-black {\n  background-color: #0a0a0a !important; }\n\n.has-text-light {\n  color: whitesmoke !important; }\n\na.has-text-light:hover, a.has-text-light:focus {\n  color: #dbdbdb !important; }\n\n.has-background-light {\n  background-color: whitesmoke !important; }\n\n.has-text-dark {\n  color: #363636 !important; }\n\na.has-text-dark:hover, a.has-text-dark:focus {\n  color: #1c1c1c !important; }\n\n.has-background-dark {\n  background-color: #363636 !important; }\n\n.has-text-primary {\n  color: #ff0d68 !important; }\n\na.has-text-primary:hover, a.has-text-primary:focus {\n  color: #d90052 !important; }\n\n.has-background-primary {\n  background-color: #ff0d68 !important; }\n\n.has-text-primary-light {\n  color: #ffebf2 !important; }\n\na.has-text-primary-light:hover, a.has-text-primary-light:focus {\n  color: #ffb8d2 !important; }\n\n.has-background-primary-light {\n  background-color: #ffebf2 !important; }\n\n.has-text-primary-dark {\n  color: #e60056 !important; }\n\na.has-text-primary-dark:hover, a.has-text-primary-dark:focus {\n  color: #ff1a70 !important; }\n\n.has-background-primary-dark {\n  background-color: #e60056 !important; }\n\n.has-text-link {\n  color: #ff0d68 !important; }\n\na.has-text-link:hover, a.has-text-link:focus {\n  color: #d90052 !important; }\n\n.has-background-link {\n  background-color: #ff0d68 !important; }\n\n.has-text-link-light {\n  color: #ffebf2 !important; }\n\na.has-text-link-light:hover, a.has-text-link-light:focus {\n  color: #ffb8d2 !important; }\n\n.has-background-link-light {\n  background-color: #ffebf2 !important; }\n\n.has-text-link-dark {\n  color: #e60056 !important; }\n\na.has-text-link-dark:hover, a.has-text-link-dark:focus {\n  color: #ff1a70 !important; }\n\n.has-background-link-dark {\n  background-color: #e60056 !important; }\n\n.has-text-info {\n  color: #0092FF !important; }\n\na.has-text-info:hover, a.has-text-info:focus {\n  color: #0075cc !important; }\n\n.has-background-info {\n  background-color: #0092FF !important; }\n\n.has-text-info-light {\n  color: #ebf6ff !important; }\n\na.has-text-info-light:hover, a.has-text-info-light:focus {\n  color: #b8e0ff !important; }\n\n.has-background-info-light {\n  background-color: #ebf6ff !important; }\n\n.has-text-info-dark {\n  color: #0075cc !important; }\n\na.has-text-info-dark:hover, a.has-text-info-dark:focus {\n  color: #0092ff !important; }\n\n.has-background-info-dark {\n  background-color: #0075cc !important; }\n\n.has-text-success {\n  color: #16DB93 !important; }\n\na.has-text-success:hover, a.has-text-success:focus {\n  color: #11ad74 !important; }\n\n.has-background-success {\n  background-color: #16DB93 !important; }\n\n.has-text-success-light {\n  color: #ecfdf7 !important; }\n\na.has-text-success-light:hover, a.has-text-success-light:focus {\n  color: #bef8e3 !important; }\n\n.has-background-success-light {\n  background-color: #ecfdf7 !important; }\n\n.has-text-success-dark {\n  color: #0e865a !important; }\n\na.has-text-success-dark:hover, a.has-text-success-dark:focus {\n  color: #12b579 !important; }\n\n.has-background-success-dark {\n  background-color: #0e865a !important; }\n\n.has-text-warning {\n  color: #FFE900 !important; }\n\na.has-text-warning:hover, a.has-text-warning:focus {\n  color: #ccba00 !important; }\n\n.has-background-warning {\n  background-color: #FFE900 !important; }\n\n.has-text-warning-light {\n  color: #fffdeb !important; }\n\na.has-text-warning-light:hover, a.has-text-warning-light:focus {\n  color: #fff9b8 !important; }\n\n.has-background-warning-light {\n  background-color: #fffdeb !important; }\n\n.has-text-warning-dark {\n  color: #948700 !important; }\n\na.has-text-warning-dark:hover, a.has-text-warning-dark:focus {\n  color: #c7b600 !important; }\n\n.has-background-warning-dark {\n  background-color: #948700 !important; }\n\n.has-text-danger {\n  color: #f14668 !important; }\n\na.has-text-danger:hover, a.has-text-danger:focus {\n  color: #ee1742 !important; }\n\n.has-background-danger {\n  background-color: #f14668 !important; }\n\n.has-text-danger-light {\n  color: #feecf0 !important; }\n\na.has-text-danger-light:hover, a.has-text-danger-light:focus {\n  color: #fabdc9 !important; }\n\n.has-background-danger-light {\n  background-color: #feecf0 !important; }\n\n.has-text-danger-dark {\n  color: #cc0f35 !important; }\n\na.has-text-danger-dark:hover, a.has-text-danger-dark:focus {\n  color: #ee2049 !important; }\n\n.has-background-danger-dark {\n  background-color: #cc0f35 !important; }\n\n.has-text-black-bis {\n  color: #121212 !important; }\n\n.has-background-black-bis {\n  background-color: #121212 !important; }\n\n.has-text-black-ter {\n  color: #242424 !important; }\n\n.has-background-black-ter {\n  background-color: #242424 !important; }\n\n.has-text-grey-darker {\n  color: #363636 !important; }\n\n.has-background-grey-darker {\n  background-color: #363636 !important; }\n\n.has-text-grey-dark {\n  color: #4a4a4a !important; }\n\n.has-background-grey-dark {\n  background-color: #4a4a4a !important; }\n\n.has-text-grey {\n  color: #7a7a7a !important; }\n\n.has-background-grey {\n  background-color: #7a7a7a !important; }\n\n.has-text-grey-light {\n  color: #b5b5b5 !important; }\n\n.has-background-grey-light {\n  background-color: #b5b5b5 !important; }\n\n.has-text-grey-lighter {\n  color: #dbdbdb !important; }\n\n.has-background-grey-lighter {\n  background-color: #dbdbdb !important; }\n\n.has-text-white-ter {\n  color: whitesmoke !important; }\n\n.has-background-white-ter {\n  background-color: whitesmoke !important; }\n\n.has-text-white-bis {\n  color: #fafafa !important; }\n\n.has-background-white-bis {\n  background-color: #fafafa !important; }\n\n.is-flex-direction-row {\n  flex-direction: row !important; }\n\n.is-flex-direction-row-reverse {\n  flex-direction: row-reverse !important; }\n\n.is-flex-direction-column {\n  flex-direction: column !important; }\n\n.is-flex-direction-column-reverse {\n  flex-direction: column-reverse !important; }\n\n.is-flex-wrap-nowrap {\n  flex-wrap: nowrap !important; }\n\n.is-flex-wrap-wrap {\n  flex-wrap: wrap !important; }\n\n.is-flex-wrap-wrap-reverse {\n  flex-wrap: wrap-reverse !important; }\n\n.is-justify-content-flex-start {\n  justify-content: flex-start !important; }\n\n.is-justify-content-flex-end {\n  justify-content: flex-end !important; }\n\n.is-justify-content-center {\n  justify-content: center !important; }\n\n.is-justify-content-space-between {\n  justify-content: space-between !important; }\n\n.is-justify-content-space-around {\n  justify-content: space-around !important; }\n\n.is-justify-content-space-evenly {\n  justify-content: space-evenly !important; }\n\n.is-justify-content-start {\n  justify-content: start !important; }\n\n.is-justify-content-end {\n  justify-content: end !important; }\n\n.is-justify-content-left {\n  justify-content: left !important; }\n\n.is-justify-content-right {\n  justify-content: right !important; }\n\n.is-align-content-flex-start {\n  align-content: flex-start !important; }\n\n.is-align-content-flex-end {\n  align-content: flex-end !important; }\n\n.is-align-content-center {\n  align-content: center !important; }\n\n.is-align-content-space-between {\n  align-content: space-between !important; }\n\n.is-align-content-space-around {\n  align-content: space-around !important; }\n\n.is-align-content-space-evenly {\n  align-content: space-evenly !important; }\n\n.is-align-content-stretch {\n  align-content: stretch !important; }\n\n.is-align-content-start {\n  align-content: start !important; }\n\n.is-align-content-end {\n  align-content: end !important; }\n\n.is-align-content-baseline {\n  align-content: baseline !important; }\n\n.is-align-items-stretch {\n  align-items: stretch !important; }\n\n.is-align-items-flex-start {\n  align-items: flex-start !important; }\n\n.is-align-items-flex-end {\n  align-items: flex-end !important; }\n\n.is-align-items-center {\n  align-items: center !important; }\n\n.is-align-items-baseline {\n  align-items: baseline !important; }\n\n.is-align-items-start {\n  align-items: start !important; }\n\n.is-align-items-end {\n  align-items: end !important; }\n\n.is-align-items-self-start {\n  align-items: self-start !important; }\n\n.is-align-items-self-end {\n  align-items: self-end !important; }\n\n.is-align-self-auto {\n  align-self: auto !important; }\n\n.is-align-self-flex-start {\n  align-self: flex-start !important; }\n\n.is-align-self-flex-end {\n  align-self: flex-end !important; }\n\n.is-align-self-center {\n  align-self: center !important; }\n\n.is-align-self-baseline {\n  align-self: baseline !important; }\n\n.is-align-self-stretch {\n  align-self: stretch !important; }\n\n.is-flex-grow-0 {\n  flex-grow: 0 !important; }\n\n.is-flex-grow-1 {\n  flex-grow: 1 !important; }\n\n.is-flex-grow-2 {\n  flex-grow: 2 !important; }\n\n.is-flex-grow-3 {\n  flex-grow: 3 !important; }\n\n.is-flex-grow-4 {\n  flex-grow: 4 !important; }\n\n.is-flex-grow-5 {\n  flex-grow: 5 !important; }\n\n.is-flex-shrink-0 {\n  flex-shrink: 0 !important; }\n\n.is-flex-shrink-1 {\n  flex-shrink: 1 !important; }\n\n.is-flex-shrink-2 {\n  flex-shrink: 2 !important; }\n\n.is-flex-shrink-3 {\n  flex-shrink: 3 !important; }\n\n.is-flex-shrink-4 {\n  flex-shrink: 4 !important; }\n\n.is-flex-shrink-5 {\n  flex-shrink: 5 !important; }\n\n.is-clearfix::after {\n  clear: both;\n  content: \" \";\n  display: table; }\n\n.is-pulled-left {\n  float: left !important; }\n\n.is-pulled-right {\n  float: right !important; }\n\n.is-radiusless {\n  border-radius: 0 !important; }\n\n.is-shadowless {\n  box-shadow: none !important; }\n\n.is-clickable {\n  cursor: pointer !important;\n  pointer-events: all !important; }\n\n.is-clipped {\n  overflow: hidden !important; }\n\n.is-relative {\n  position: relative !important; }\n\n.is-marginless {\n  margin: 0 !important; }\n\n.is-paddingless {\n  padding: 0 !important; }\n\n.m-0 {\n  margin: 0 !important; }\n\n.mt-0 {\n  margin-top: 0 !important; }\n\n.mr-0 {\n  margin-right: 0 !important; }\n\n.mb-0 {\n  margin-bottom: 0 !important; }\n\n.ml-0 {\n  margin-left: 0 !important; }\n\n.mx-0 {\n  margin-left: 0 !important;\n  margin-right: 0 !important; }\n\n.my-0 {\n  margin-top: 0 !important;\n  margin-bottom: 0 !important; }\n\n.m-1 {\n  margin: 0.25rem !important; }\n\n.mt-1 {\n  margin-top: 0.25rem !important; }\n\n.mr-1 {\n  margin-right: 0.25rem !important; }\n\n.mb-1 {\n  margin-bottom: 0.25rem !important; }\n\n.ml-1 {\n  margin-left: 0.25rem !important; }\n\n.mx-1 {\n  margin-left: 0.25rem !important;\n  margin-right: 0.25rem !important; }\n\n.my-1 {\n  margin-top: 0.25rem !important;\n  margin-bottom: 0.25rem !important; }\n\n.m-2 {\n  margin: 0.5rem !important; }\n\n.mt-2 {\n  margin-top: 0.5rem !important; }\n\n.mr-2 {\n  margin-right: 0.5rem !important; }\n\n.mb-2 {\n  margin-bottom: 0.5rem !important; }\n\n.ml-2 {\n  margin-left: 0.5rem !important; }\n\n.mx-2 {\n  margin-left: 0.5rem !important;\n  margin-right: 0.5rem !important; }\n\n.my-2 {\n  margin-top: 0.5rem !important;\n  margin-bottom: 0.5rem !important; }\n\n.m-3 {\n  margin: 0.75rem !important; }\n\n.mt-3 {\n  margin-top: 0.75rem !important; }\n\n.mr-3 {\n  margin-right: 0.75rem !important; }\n\n.mb-3 {\n  margin-bottom: 0.75rem !important; }\n\n.ml-3 {\n  margin-left: 0.75rem !important; }\n\n.mx-3 {\n  margin-left: 0.75rem !important;\n  margin-right: 0.75rem !important; }\n\n.my-3 {\n  margin-top: 0.75rem !important;\n  margin-bottom: 0.75rem !important; }\n\n.m-4 {\n  margin: 1rem !important; }\n\n.mt-4 {\n  margin-top: 1rem !important; }\n\n.mr-4 {\n  margin-right: 1rem !important; }\n\n.mb-4 {\n  margin-bottom: 1rem !important; }\n\n.ml-4 {\n  margin-left: 1rem !important; }\n\n.mx-4 {\n  margin-left: 1rem !important;\n  margin-right: 1rem !important; }\n\n.my-4 {\n  margin-top: 1rem !important;\n  margin-bottom: 1rem !important; }\n\n.m-5 {\n  margin: 1.5rem !important; }\n\n.mt-5 {\n  margin-top: 1.5rem !important; }\n\n.mr-5 {\n  margin-right: 1.5rem !important; }\n\n.mb-5 {\n  margin-bottom: 1.5rem !important; }\n\n.ml-5 {\n  margin-left: 1.5rem !important; }\n\n.mx-5 {\n  margin-left: 1.5rem !important;\n  margin-right: 1.5rem !important; }\n\n.my-5 {\n  margin-top: 1.5rem !important;\n  margin-bottom: 1.5rem !important; }\n\n.m-6 {\n  margin: 3rem !important; }\n\n.mt-6 {\n  margin-top: 3rem !important; }\n\n.mr-6 {\n  margin-right: 3rem !important; }\n\n.mb-6 {\n  margin-bottom: 3rem !important; }\n\n.ml-6 {\n  margin-left: 3rem !important; }\n\n.mx-6 {\n  margin-left: 3rem !important;\n  margin-right: 3rem !important; }\n\n.my-6 {\n  margin-top: 3rem !important;\n  margin-bottom: 3rem !important; }\n\n.m-auto {\n  margin: auto !important; }\n\n.mt-auto {\n  margin-top: auto !important; }\n\n.mr-auto {\n  margin-right: auto !important; }\n\n.mb-auto {\n  margin-bottom: auto !important; }\n\n.ml-auto {\n  margin-left: auto !important; }\n\n.mx-auto {\n  margin-left: auto !important;\n  margin-right: auto !important; }\n\n.my-auto {\n  margin-top: auto !important;\n  margin-bottom: auto !important; }\n\n.p-0 {\n  padding: 0 !important; }\n\n.pt-0 {\n  padding-top: 0 !important; }\n\n.pr-0 {\n  padding-right: 0 !important; }\n\n.pb-0 {\n  padding-bottom: 0 !important; }\n\n.pl-0 {\n  padding-left: 0 !important; }\n\n.px-0 {\n  padding-left: 0 !important;\n  padding-right: 0 !important; }\n\n.py-0 {\n  padding-top: 0 !important;\n  padding-bottom: 0 !important; }\n\n.p-1 {\n  padding: 0.25rem !important; }\n\n.pt-1 {\n  padding-top: 0.25rem !important; }\n\n.pr-1 {\n  padding-right: 0.25rem !important; }\n\n.pb-1 {\n  padding-bottom: 0.25rem !important; }\n\n.pl-1 {\n  padding-left: 0.25rem !important; }\n\n.px-1 {\n  padding-left: 0.25rem !important;\n  padding-right: 0.25rem !important; }\n\n.py-1 {\n  padding-top: 0.25rem !important;\n  padding-bottom: 0.25rem !important; }\n\n.p-2 {\n  padding: 0.5rem !important; }\n\n.pt-2 {\n  padding-top: 0.5rem !important; }\n\n.pr-2 {\n  padding-right: 0.5rem !important; }\n\n.pb-2 {\n  padding-bottom: 0.5rem !important; }\n\n.pl-2 {\n  padding-left: 0.5rem !important; }\n\n.px-2 {\n  padding-left: 0.5rem !important;\n  padding-right: 0.5rem !important; }\n\n.py-2 {\n  padding-top: 0.5rem !important;\n  padding-bottom: 0.5rem !important; }\n\n.p-3 {\n  padding: 0.75rem !important; }\n\n.pt-3 {\n  padding-top: 0.75rem !important; }\n\n.pr-3 {\n  padding-right: 0.75rem !important; }\n\n.pb-3 {\n  padding-bottom: 0.75rem !important; }\n\n.pl-3 {\n  padding-left: 0.75rem !important; }\n\n.px-3 {\n  padding-left: 0.75rem !important;\n  padding-right: 0.75rem !important; }\n\n.py-3 {\n  padding-top: 0.75rem !important;\n  padding-bottom: 0.75rem !important; }\n\n.p-4 {\n  padding: 1rem !important; }\n\n.pt-4 {\n  padding-top: 1rem !important; }\n\n.pr-4 {\n  padding-right: 1rem !important; }\n\n.pb-4 {\n  padding-bottom: 1rem !important; }\n\n.pl-4 {\n  padding-left: 1rem !important; }\n\n.px-4 {\n  padding-left: 1rem !important;\n  padding-right: 1rem !important; }\n\n.py-4 {\n  padding-top: 1rem !important;\n  padding-bottom: 1rem !important; }\n\n.p-5 {\n  padding: 1.5rem !important; }\n\n.pt-5 {\n  padding-top: 1.5rem !important; }\n\n.pr-5 {\n  padding-right: 1.5rem !important; }\n\n.pb-5 {\n  padding-bottom: 1.5rem !important; }\n\n.pl-5 {\n  padding-left: 1.5rem !important; }\n\n.px-5 {\n  padding-left: 1.5rem !important;\n  padding-right: 1.5rem !important; }\n\n.py-5 {\n  padding-top: 1.5rem !important;\n  padding-bottom: 1.5rem !important; }\n\n.p-6 {\n  padding: 3rem !important; }\n\n.pt-6 {\n  padding-top: 3rem !important; }\n\n.pr-6 {\n  padding-right: 3rem !important; }\n\n.pb-6 {\n  padding-bottom: 3rem !important; }\n\n.pl-6 {\n  padding-left: 3rem !important; }\n\n.px-6 {\n  padding-left: 3rem !important;\n  padding-right: 3rem !important; }\n\n.py-6 {\n  padding-top: 3rem !important;\n  padding-bottom: 3rem !important; }\n\n.p-auto {\n  padding: auto !important; }\n\n.pt-auto {\n  padding-top: auto !important; }\n\n.pr-auto {\n  padding-right: auto !important; }\n\n.pb-auto {\n  padding-bottom: auto !important; }\n\n.pl-auto {\n  padding-left: auto !important; }\n\n.px-auto {\n  padding-left: auto !important;\n  padding-right: auto !important; }\n\n.py-auto {\n  padding-top: auto !important;\n  padding-bottom: auto !important; }\n\n.is-size-1 {\n  font-size: 3rem !important; }\n\n.is-size-2 {\n  font-size: 2.5rem !important; }\n\n.is-size-3 {\n  font-size: 2rem !important; }\n\n.is-size-4 {\n  font-size: 1.5rem !important; }\n\n.is-size-5 {\n  font-size: 1.25rem !important; }\n\n.is-size-6 {\n  font-size: 1rem !important; }\n\n.is-size-7 {\n  font-size: 0.75rem !important; }\n\n@media screen and (max-width: 768px) {\n  .is-size-1-mobile {\n    font-size: 3rem !important; }\n  .is-size-2-mobile {\n    font-size: 2.5rem !important; }\n  .is-size-3-mobile {\n    font-size: 2rem !important; }\n  .is-size-4-mobile {\n    font-size: 1.5rem !important; }\n  .is-size-5-mobile {\n    font-size: 1.25rem !important; }\n  .is-size-6-mobile {\n    font-size: 1rem !important; }\n  .is-size-7-mobile {\n    font-size: 0.75rem !important; } }\n\n@media screen and (min-width: 769px), print {\n  .is-size-1-tablet {\n    font-size: 3rem !important; }\n  .is-size-2-tablet {\n    font-size: 2.5rem !important; }\n  .is-size-3-tablet {\n    font-size: 2rem !important; }\n  .is-size-4-tablet {\n    font-size: 1.5rem !important; }\n  .is-size-5-tablet {\n    font-size: 1.25rem !important; }\n  .is-size-6-tablet {\n    font-size: 1rem !important; }\n  .is-size-7-tablet {\n    font-size: 0.75rem !important; } }\n\n@media screen and (max-width: 1023px) {\n  .is-size-1-touch {\n    font-size: 3rem !important; }\n  .is-size-2-touch {\n    font-size: 2.5rem !important; }\n  .is-size-3-touch {\n    font-size: 2rem !important; }\n  .is-size-4-touch {\n    font-size: 1.5rem !important; }\n  .is-size-5-touch {\n    font-size: 1.25rem !important; }\n  .is-size-6-touch {\n    font-size: 1rem !important; }\n  .is-size-7-touch {\n    font-size: 0.75rem !important; } }\n\n@media screen and (min-width: 1024px) {\n  .is-size-1-desktop {\n    font-size: 3rem !important; }\n  .is-size-2-desktop {\n    font-size: 2.5rem !important; }\n  .is-size-3-desktop {\n    font-size: 2rem !important; }\n  .is-size-4-desktop {\n    font-size: 1.5rem !important; }\n  .is-size-5-desktop {\n    font-size: 1.25rem !important; }\n  .is-size-6-desktop {\n    font-size: 1rem !important; }\n  .is-size-7-desktop {\n    font-size: 0.75rem !important; } }\n\n@media screen and (min-width: 1216px) {\n  .is-size-1-widescreen {\n    font-size: 3rem !important; }\n  .is-size-2-widescreen {\n    font-size: 2.5rem !important; }\n  .is-size-3-widescreen {\n    font-size: 2rem !important; }\n  .is-size-4-widescreen {\n    font-size: 1.5rem !important; }\n  .is-size-5-widescreen {\n    font-size: 1.25rem !important; }\n  .is-size-6-widescreen {\n    font-size: 1rem !important; }\n  .is-size-7-widescreen {\n    font-size: 0.75rem !important; } }\n\n@media screen and (min-width: 1408px) {\n  .is-size-1-fullhd {\n    font-size: 3rem !important; }\n  .is-size-2-fullhd {\n    font-size: 2.5rem !important; }\n  .is-size-3-fullhd {\n    font-size: 2rem !important; }\n  .is-size-4-fullhd {\n    font-size: 1.5rem !important; }\n  .is-size-5-fullhd {\n    font-size: 1.25rem !important; }\n  .is-size-6-fullhd {\n    font-size: 1rem !important; }\n  .is-size-7-fullhd {\n    font-size: 0.75rem !important; } }\n\n.has-text-centered {\n  text-align: center !important; }\n\n.has-text-justified {\n  text-align: justify !important; }\n\n.has-text-left {\n  text-align: left !important; }\n\n.has-text-right {\n  text-align: right !important; }\n\n@media screen and (max-width: 768px) {\n  .has-text-centered-mobile {\n    text-align: center !important; } }\n\n@media screen and (min-width: 769px), print {\n  .has-text-centered-tablet {\n    text-align: center !important; } }\n\n@media screen and (min-width: 769px) and (max-width: 1023px) {\n  .has-text-centered-tablet-only {\n    text-align: center !important; } }\n\n@media screen and (max-width: 1023px) {\n  .has-text-centered-touch {\n    text-align: center !important; } }\n\n@media screen and (min-width: 1024px) {\n  .has-text-centered-desktop {\n    text-align: center !important; } }\n\n@media screen and (min-width: 1024px) and (max-width: 1215px) {\n  .has-text-centered-desktop-only {\n    text-align: center !important; } }\n\n@media screen and (min-width: 1216px) {\n  .has-text-centered-widescreen {\n    text-align: center !important; } }\n\n@media screen and (min-width: 1216px) and (max-width: 1407px) {\n  .has-text-centered-widescreen-only {\n    text-align: center !important; } }\n\n@media screen and (min-width: 1408px) {\n  .has-text-centered-fullhd {\n    text-align: center !important; } }\n\n@media screen and (max-width: 768px) {\n  .has-text-justified-mobile {\n    text-align: justify !important; } }\n\n@media screen and (min-width: 769px), print {\n  .has-text-justified-tablet {\n    text-align: justify !important; } }\n\n@media screen and (min-width: 769px) and (max-width: 1023px) {\n  .has-text-justified-tablet-only {\n    text-align: justify !important; } }\n\n@media screen and (max-width: 1023px) {\n  .has-text-justified-touch {\n    text-align: justify !important; } }\n\n@media screen and (min-width: 1024px) {\n  .has-text-justified-desktop {\n    text-align: justify !important; } }\n\n@media screen and (min-width: 1024px) and (max-width: 1215px) {\n  .has-text-justified-desktop-only {\n    text-align: justify !important; } }\n\n@media screen and (min-width: 1216px) {\n  .has-text-justified-widescreen {\n    text-align: justify !important; } }\n\n@media screen and (min-width: 1216px) and (max-width: 1407px) {\n  .has-text-justified-widescreen-only {\n    text-align: justify !important; } }\n\n@media screen and (min-width: 1408px) {\n  .has-text-justified-fullhd {\n    text-align: justify !important; } }\n\n@media screen and (max-width: 768px) {\n  .has-text-left-mobile {\n    text-align: left !important; } }\n\n@media screen and (min-width: 769px), print {\n  .has-text-left-tablet {\n    text-align: left !important; } }\n\n@media screen and (min-width: 769px) and (max-width: 1023px) {\n  .has-text-left-tablet-only {\n    text-align: left !important; } }\n\n@media screen and (max-width: 1023px) {\n  .has-text-left-touch {\n    text-align: left !important; } }\n\n@media screen and (min-width: 1024px) {\n  .has-text-left-desktop {\n    text-align: left !important; } }\n\n@media screen and (min-width: 1024px) and (max-width: 1215px) {\n  .has-text-left-desktop-only {\n    text-align: left !important; } }\n\n@media screen and (min-width: 1216px) {\n  .has-text-left-widescreen {\n    text-align: left !important; } }\n\n@media screen and (min-width: 1216px) and (max-width: 1407px) {\n  .has-text-left-widescreen-only {\n    text-align: left !important; } }\n\n@media screen and (min-width: 1408px) {\n  .has-text-left-fullhd {\n    text-align: left !important; } }\n\n@media screen and (max-width: 768px) {\n  .has-text-right-mobile {\n    text-align: right !important; } }\n\n@media screen and (min-width: 769px), print {\n  .has-text-right-tablet {\n    text-align: right !important; } }\n\n@media screen and (min-width: 769px) and (max-width: 1023px) {\n  .has-text-right-tablet-only {\n    text-align: right !important; } }\n\n@media screen and (max-width: 1023px) {\n  .has-text-right-touch {\n    text-align: right !important; } }\n\n@media screen and (min-width: 1024px) {\n  .has-text-right-desktop {\n    text-align: right !important; } }\n\n@media screen and (min-width: 1024px) and (max-width: 1215px) {\n  .has-text-right-desktop-only {\n    text-align: right !important; } }\n\n@media screen and (min-width: 1216px) {\n  .has-text-right-widescreen {\n    text-align: right !important; } }\n\n@media screen and (min-width: 1216px) and (max-width: 1407px) {\n  .has-text-right-widescreen-only {\n    text-align: right !important; } }\n\n@media screen and (min-width: 1408px) {\n  .has-text-right-fullhd {\n    text-align: right !important; } }\n\n.is-capitalized {\n  text-transform: capitalize !important; }\n\n.is-lowercase {\n  text-transform: lowercase !important; }\n\n.is-uppercase {\n  text-transform: uppercase !important; }\n\n.is-italic {\n  font-style: italic !important; }\n\n.is-underlined {\n  text-decoration: underline !important; }\n\n.has-text-weight-light {\n  font-weight: 300 !important; }\n\n.has-text-weight-normal {\n  font-weight: 400 !important; }\n\n.has-text-weight-medium {\n  font-weight: 500 !important; }\n\n.has-text-weight-semibold {\n  font-weight: 600 !important; }\n\n.has-text-weight-bold {\n  font-weight: 700 !important; }\n\n.is-family-primary {\n  font-family: BlinkMacSystemFont, -apple-system, \"Segoe UI\", \"Roboto\", \"Oxygen\", \"Ubuntu\", \"Cantarell\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\", \"Helvetica\", \"Arial\", sans-serif !important; }\n\n.is-family-secondary {\n  font-family: BlinkMacSystemFont, -apple-system, \"Segoe UI\", \"Roboto\", \"Oxygen\", \"Ubuntu\", \"Cantarell\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\", \"Helvetica\", \"Arial\", sans-serif !important; }\n\n.is-family-sans-serif {\n  font-family: BlinkMacSystemFont, -apple-system, \"Segoe UI\", \"Roboto\", \"Oxygen\", \"Ubuntu\", \"Cantarell\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\", \"Helvetica\", \"Arial\", sans-serif !important; }\n\n.is-family-monospace {\n  font-family: monospace !important; }\n\n.is-family-code {\n  font-family: monospace !important; }\n\n.is-block {\n  display: block !important; }\n\n@media screen and (max-width: 768px) {\n  .is-block-mobile {\n    display: block !important; } }\n\n@media screen and (min-width: 769px), print {\n  .is-block-tablet {\n    display: block !important; } }\n\n@media screen and (min-width: 769px) and (max-width: 1023px) {\n  .is-block-tablet-only {\n    display: block !important; } }\n\n@media screen and (max-width: 1023px) {\n  .is-block-touch {\n    display: block !important; } }\n\n@media screen and (min-width: 1024px) {\n  .is-block-desktop {\n    display: block !important; } }\n\n@media screen and (min-width: 1024px) and (max-width: 1215px) {\n  .is-block-desktop-only {\n    display: block !important; } }\n\n@media screen and (min-width: 1216px) {\n  .is-block-widescreen {\n    display: block !important; } }\n\n@media screen and (min-width: 1216px) and (max-width: 1407px) {\n  .is-block-widescreen-only {\n    display: block !important; } }\n\n@media screen and (min-width: 1408px) {\n  .is-block-fullhd {\n    display: block !important; } }\n\n.is-flex {\n  display: flex !important; }\n\n@media screen and (max-width: 768px) {\n  .is-flex-mobile {\n    display: flex !important; } }\n\n@media screen and (min-width: 769px), print {\n  .is-flex-tablet {\n    display: flex !important; } }\n\n@media screen and (min-width: 769px) and (max-width: 1023px) {\n  .is-flex-tablet-only {\n    display: flex !important; } }\n\n@media screen and (max-width: 1023px) {\n  .is-flex-touch {\n    display: flex !important; } }\n\n@media screen and (min-width: 1024px) {\n  .is-flex-desktop {\n    display: flex !important; } }\n\n@media screen and (min-width: 1024px) and (max-width: 1215px) {\n  .is-flex-desktop-only {\n    display: flex !important; } }\n\n@media screen and (min-width: 1216px) {\n  .is-flex-widescreen {\n    display: flex !important; } }\n\n@media screen and (min-width: 1216px) and (max-width: 1407px) {\n  .is-flex-widescreen-only {\n    display: flex !important; } }\n\n@media screen and (min-width: 1408px) {\n  .is-flex-fullhd {\n    display: flex !important; } }\n\n.is-inline {\n  display: inline !important; }\n\n@media screen and (max-width: 768px) {\n  .is-inline-mobile {\n    display: inline !important; } }\n\n@media screen and (min-width: 769px), print {\n  .is-inline-tablet {\n    display: inline !important; } }\n\n@media screen and (min-width: 769px) and (max-width: 1023px) {\n  .is-inline-tablet-only {\n    display: inline !important; } }\n\n@media screen and (max-width: 1023px) {\n  .is-inline-touch {\n    display: inline !important; } }\n\n@media screen and (min-width: 1024px) {\n  .is-inline-desktop {\n    display: inline !important; } }\n\n@media screen and (min-width: 1024px) and (max-width: 1215px) {\n  .is-inline-desktop-only {\n    display: inline !important; } }\n\n@media screen and (min-width: 1216px) {\n  .is-inline-widescreen {\n    display: inline !important; } }\n\n@media screen and (min-width: 1216px) and (max-width: 1407px) {\n  .is-inline-widescreen-only {\n    display: inline !important; } }\n\n@media screen and (min-width: 1408px) {\n  .is-inline-fullhd {\n    display: inline !important; } }\n\n.is-inline-block {\n  display: inline-block !important; }\n\n@media screen and (max-width: 768px) {\n  .is-inline-block-mobile {\n    display: inline-block !important; } }\n\n@media screen and (min-width: 769px), print {\n  .is-inline-block-tablet {\n    display: inline-block !important; } }\n\n@media screen and (min-width: 769px) and (max-width: 1023px) {\n  .is-inline-block-tablet-only {\n    display: inline-block !important; } }\n\n@media screen and (max-width: 1023px) {\n  .is-inline-block-touch {\n    display: inline-block !important; } }\n\n@media screen and (min-width: 1024px) {\n  .is-inline-block-desktop {\n    display: inline-block !important; } }\n\n@media screen and (min-width: 1024px) and (max-width: 1215px) {\n  .is-inline-block-desktop-only {\n    display: inline-block !important; } }\n\n@media screen and (min-width: 1216px) {\n  .is-inline-block-widescreen {\n    display: inline-block !important; } }\n\n@media screen and (min-width: 1216px) and (max-width: 1407px) {\n  .is-inline-block-widescreen-only {\n    display: inline-block !important; } }\n\n@media screen and (min-width: 1408px) {\n  .is-inline-block-fullhd {\n    display: inline-block !important; } }\n\n.is-inline-flex {\n  display: inline-flex !important; }\n\n@media screen and (max-width: 768px) {\n  .is-inline-flex-mobile {\n    display: inline-flex !important; } }\n\n@media screen and (min-width: 769px), print {\n  .is-inline-flex-tablet {\n    display: inline-flex !important; } }\n\n@media screen and (min-width: 769px) and (max-width: 1023px) {\n  .is-inline-flex-tablet-only {\n    display: inline-flex !important; } }\n\n@media screen and (max-width: 1023px) {\n  .is-inline-flex-touch {\n    display: inline-flex !important; } }\n\n@media screen and (min-width: 1024px) {\n  .is-inline-flex-desktop {\n    display: inline-flex !important; } }\n\n@media screen and (min-width: 1024px) and (max-width: 1215px) {\n  .is-inline-flex-desktop-only {\n    display: inline-flex !important; } }\n\n@media screen and (min-width: 1216px) {\n  .is-inline-flex-widescreen {\n    display: inline-flex !important; } }\n\n@media screen and (min-width: 1216px) and (max-width: 1407px) {\n  .is-inline-flex-widescreen-only {\n    display: inline-flex !important; } }\n\n@media screen and (min-width: 1408px) {\n  .is-inline-flex-fullhd {\n    display: inline-flex !important; } }\n\n.is-hidden {\n  display: none !important; }\n\n.is-sr-only {\n  border: none !important;\n  clip: rect(0, 0, 0, 0) !important;\n  height: 0.01em !important;\n  overflow: hidden !important;\n  padding: 0 !important;\n  position: absolute !important;\n  white-space: nowrap !important;\n  width: 0.01em !important; }\n\n@media screen and (max-width: 768px) {\n  .is-hidden-mobile {\n    display: none !important; } }\n\n@media screen and (min-width: 769px), print {\n  .is-hidden-tablet {\n    display: none !important; } }\n\n@media screen and (min-width: 769px) and (max-width: 1023px) {\n  .is-hidden-tablet-only {\n    display: none !important; } }\n\n@media screen and (max-width: 1023px) {\n  .is-hidden-touch {\n    display: none !important; } }\n\n@media screen and (min-width: 1024px) {\n  .is-hidden-desktop {\n    display: none !important; } }\n\n@media screen and (min-width: 1024px) and (max-width: 1215px) {\n  .is-hidden-desktop-only {\n    display: none !important; } }\n\n@media screen and (min-width: 1216px) {\n  .is-hidden-widescreen {\n    display: none !important; } }\n\n@media screen and (min-width: 1216px) and (max-width: 1407px) {\n  .is-hidden-widescreen-only {\n    display: none !important; } }\n\n@media screen and (min-width: 1408px) {\n  .is-hidden-fullhd {\n    display: none !important; } }\n\n.is-invisible {\n  visibility: hidden !important; }\n\n@media screen and (max-width: 768px) {\n  .is-invisible-mobile {\n    visibility: hidden !important; } }\n\n@media screen and (min-width: 769px), print {\n  .is-invisible-tablet {\n    visibility: hidden !important; } }\n\n@media screen and (min-width: 769px) and (max-width: 1023px) {\n  .is-invisible-tablet-only {\n    visibility: hidden !important; } }\n\n@media screen and (max-width: 1023px) {\n  .is-invisible-touch {\n    visibility: hidden !important; } }\n\n@media screen and (min-width: 1024px) {\n  .is-invisible-desktop {\n    visibility: hidden !important; } }\n\n@media screen and (min-width: 1024px) and (max-width: 1215px) {\n  .is-invisible-desktop-only {\n    visibility: hidden !important; } }\n\n@media screen and (min-width: 1216px) {\n  .is-invisible-widescreen {\n    visibility: hidden !important; } }\n\n@media screen and (min-width: 1216px) and (max-width: 1407px) {\n  .is-invisible-widescreen-only {\n    visibility: hidden !important; } }\n\n@media screen and (min-width: 1408px) {\n  .is-invisible-fullhd {\n    visibility: hidden !important; } }\n\n/* Bulma Layout */\n.hero {\n  align-items: stretch;\n  display: flex;\n  flex-direction: column;\n  justify-content: space-between; }\n  .hero .navbar {\n    background: none; }\n  .hero .tabs ul {\n    border-bottom: none; }\n  .hero.is-white {\n    background-color: white;\n    color: #0a0a0a; }\n    .hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),\n    .hero.is-white strong {\n      color: inherit; }\n    .hero.is-white .title {\n      color: #0a0a0a; }\n    .hero.is-white .subtitle {\n      color: rgba(10, 10, 10, 0.9); }\n      .hero.is-white .subtitle a:not(.button),\n      .hero.is-white .subtitle strong {\n        color: #0a0a0a; }\n    @media screen and (max-width: 1023px) {\n      .hero.is-white .navbar-menu {\n        background-color: white; } }\n    .hero.is-white .navbar-item,\n    .hero.is-white .navbar-link {\n      color: rgba(10, 10, 10, 0.7); }\n    .hero.is-white a.navbar-item:hover, .hero.is-white a.navbar-item.is-active,\n    .hero.is-white .navbar-link:hover,\n    .hero.is-white .navbar-link.is-active {\n      background-color: #f2f2f2;\n      color: #0a0a0a; }\n    .hero.is-white .tabs a {\n      color: #0a0a0a;\n      opacity: 0.9; }\n      .hero.is-white .tabs a:hover {\n        opacity: 1; }\n    .hero.is-white .tabs li.is-active a {\n      color: white !important;\n      opacity: 1; }\n    .hero.is-white .tabs.is-boxed a, .hero.is-white .tabs.is-toggle a {\n      color: #0a0a0a; }\n      .hero.is-white .tabs.is-boxed a:hover, .hero.is-white .tabs.is-toggle a:hover {\n        background-color: rgba(10, 10, 10, 0.1); }\n    .hero.is-white .tabs.is-boxed li.is-active a, .hero.is-white .tabs.is-boxed li.is-active a:hover, .hero.is-white .tabs.is-toggle li.is-active a, .hero.is-white .tabs.is-toggle li.is-active a:hover {\n      background-color: #0a0a0a;\n      border-color: #0a0a0a;\n      color: white; }\n    .hero.is-white.is-bold {\n      background-image: linear-gradient(141deg, #e8e3e4 0%, white 71%, white 100%); }\n      @media screen and (max-width: 768px) {\n        .hero.is-white.is-bold .navbar-menu {\n          background-image: linear-gradient(141deg, #e8e3e4 0%, white 71%, white 100%); } }\n  .hero.is-black {\n    background-color: #0a0a0a;\n    color: white; }\n    .hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),\n    .hero.is-black strong {\n      color: inherit; }\n    .hero.is-black .title {\n      color: white; }\n    .hero.is-black .subtitle {\n      color: rgba(255, 255, 255, 0.9); }\n      .hero.is-black .subtitle a:not(.button),\n      .hero.is-black .subtitle strong {\n        color: white; }\n    @media screen and (max-width: 1023px) {\n      .hero.is-black .navbar-menu {\n        background-color: #0a0a0a; } }\n    .hero.is-black .navbar-item,\n    .hero.is-black .navbar-link {\n      color: rgba(255, 255, 255, 0.7); }\n    .hero.is-black a.navbar-item:hover, .hero.is-black a.navbar-item.is-active,\n    .hero.is-black .navbar-link:hover,\n    .hero.is-black .navbar-link.is-active {\n      background-color: black;\n      color: white; }\n    .hero.is-black .tabs a {\n      color: white;\n      opacity: 0.9; }\n      .hero.is-black .tabs a:hover {\n        opacity: 1; }\n    .hero.is-black .tabs li.is-active a {\n      color: #0a0a0a !important;\n      opacity: 1; }\n    .hero.is-black .tabs.is-boxed a, .hero.is-black .tabs.is-toggle a {\n      color: white; }\n      .hero.is-black .tabs.is-boxed a:hover, .hero.is-black .tabs.is-toggle a:hover {\n        background-color: rgba(10, 10, 10, 0.1); }\n    .hero.is-black .tabs.is-boxed li.is-active a, .hero.is-black .tabs.is-boxed li.is-active a:hover, .hero.is-black .tabs.is-toggle li.is-active a, .hero.is-black .tabs.is-toggle li.is-active a:hover {\n      background-color: white;\n      border-color: white;\n      color: #0a0a0a; }\n    .hero.is-black.is-bold {\n      background-image: linear-gradient(141deg, black 0%, #0a0a0a 71%, #181616 100%); }\n      @media screen and (max-width: 768px) {\n        .hero.is-black.is-bold .navbar-menu {\n          background-image: linear-gradient(141deg, black 0%, #0a0a0a 71%, #181616 100%); } }\n  .hero.is-light {\n    background-color: whitesmoke;\n    color: rgba(0, 0, 0, 0.7); }\n    .hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),\n    .hero.is-light strong {\n      color: inherit; }\n    .hero.is-light .title {\n      color: rgba(0, 0, 0, 0.7); }\n    .hero.is-light .subtitle {\n      color: rgba(0, 0, 0, 0.9); }\n      .hero.is-light .subtitle a:not(.button),\n      .hero.is-light .subtitle strong {\n        color: rgba(0, 0, 0, 0.7); }\n    @media screen and (max-width: 1023px) {\n      .hero.is-light .navbar-menu {\n        background-color: whitesmoke; } }\n    .hero.is-light .navbar-item,\n    .hero.is-light .navbar-link {\n      color: rgba(0, 0, 0, 0.7); }\n    .hero.is-light a.navbar-item:hover, .hero.is-light a.navbar-item.is-active,\n    .hero.is-light .navbar-link:hover,\n    .hero.is-light .navbar-link.is-active {\n      background-color: #e8e8e8;\n      color: rgba(0, 0, 0, 0.7); }\n    .hero.is-light .tabs a {\n      color: rgba(0, 0, 0, 0.7);\n      opacity: 0.9; }\n      .hero.is-light .tabs a:hover {\n        opacity: 1; }\n    .hero.is-light .tabs li.is-active a {\n      color: whitesmoke !important;\n      opacity: 1; }\n    .hero.is-light .tabs.is-boxed a, .hero.is-light .tabs.is-toggle a {\n      color: rgba(0, 0, 0, 0.7); }\n      .hero.is-light .tabs.is-boxed a:hover, .hero.is-light .tabs.is-toggle a:hover {\n        background-color: rgba(10, 10, 10, 0.1); }\n    .hero.is-light .tabs.is-boxed li.is-active a, .hero.is-light .tabs.is-boxed li.is-active a:hover, .hero.is-light .tabs.is-toggle li.is-active a, .hero.is-light .tabs.is-toggle li.is-active a:hover {\n      background-color: rgba(0, 0, 0, 0.7);\n      border-color: rgba(0, 0, 0, 0.7);\n      color: whitesmoke; }\n    .hero.is-light.is-bold {\n      background-image: linear-gradient(141deg, #dfd8d9 0%, whitesmoke 71%, white 100%); }\n      @media screen and (max-width: 768px) {\n        .hero.is-light.is-bold .navbar-menu {\n          background-image: linear-gradient(141deg, #dfd8d9 0%, whitesmoke 71%, white 100%); } }\n  .hero.is-dark {\n    background-color: #363636;\n    color: #fff; }\n    .hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),\n    .hero.is-dark strong {\n      color: inherit; }\n    .hero.is-dark .title {\n      color: #fff; }\n    .hero.is-dark .subtitle {\n      color: rgba(255, 255, 255, 0.9); }\n      .hero.is-dark .subtitle a:not(.button),\n      .hero.is-dark .subtitle strong {\n        color: #fff; }\n    @media screen and (max-width: 1023px) {\n      .hero.is-dark .navbar-menu {\n        background-color: #363636; } }\n    .hero.is-dark .navbar-item,\n    .hero.is-dark .navbar-link {\n      color: rgba(255, 255, 255, 0.7); }\n    .hero.is-dark a.navbar-item:hover, .hero.is-dark a.navbar-item.is-active,\n    .hero.is-dark .navbar-link:hover,\n    .hero.is-dark .navbar-link.is-active {\n      background-color: #292929;\n      color: #fff; }\n    .hero.is-dark .tabs a {\n      color: #fff;\n      opacity: 0.9; }\n      .hero.is-dark .tabs a:hover {\n        opacity: 1; }\n    .hero.is-dark .tabs li.is-active a {\n      color: #363636 !important;\n      opacity: 1; }\n    .hero.is-dark .tabs.is-boxed a, .hero.is-dark .tabs.is-toggle a {\n      color: #fff; }\n      .hero.is-dark .tabs.is-boxed a:hover, .hero.is-dark .tabs.is-toggle a:hover {\n        background-color: rgba(10, 10, 10, 0.1); }\n    .hero.is-dark .tabs.is-boxed li.is-active a, .hero.is-dark .tabs.is-boxed li.is-active a:hover, .hero.is-dark .tabs.is-toggle li.is-active a, .hero.is-dark .tabs.is-toggle li.is-active a:hover {\n      background-color: #fff;\n      border-color: #fff;\n      color: #363636; }\n    .hero.is-dark.is-bold {\n      background-image: linear-gradient(141deg, #1f191a 0%, #363636 71%, #46403f 100%); }\n      @media screen and (max-width: 768px) {\n        .hero.is-dark.is-bold .navbar-menu {\n          background-image: linear-gradient(141deg, #1f191a 0%, #363636 71%, #46403f 100%); } }\n  .hero.is-primary {\n    background-color: #ff0d68;\n    color: #fff; }\n    .hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),\n    .hero.is-primary strong {\n      color: inherit; }\n    .hero.is-primary .title {\n      color: #fff; }\n    .hero.is-primary .subtitle {\n      color: rgba(255, 255, 255, 0.9); }\n      .hero.is-primary .subtitle a:not(.button),\n      .hero.is-primary .subtitle strong {\n        color: #fff; }\n    @media screen and (max-width: 1023px) {\n      .hero.is-primary .navbar-menu {\n        background-color: #ff0d68; } }\n    .hero.is-primary .navbar-item,\n    .hero.is-primary .navbar-link {\n      color: rgba(255, 255, 255, 0.7); }\n    .hero.is-primary a.navbar-item:hover, .hero.is-primary a.navbar-item.is-active,\n    .hero.is-primary .navbar-link:hover,\n    .hero.is-primary .navbar-link.is-active {\n      background-color: #f3005b;\n      color: #fff; }\n    .hero.is-primary .tabs a {\n      color: #fff;\n      opacity: 0.9; }\n      .hero.is-primary .tabs a:hover {\n        opacity: 1; }\n    .hero.is-primary .tabs li.is-active a {\n      color: #ff0d68 !important;\n      opacity: 1; }\n    .hero.is-primary .tabs.is-boxed a, .hero.is-primary .tabs.is-toggle a {\n      color: #fff; }\n      .hero.is-primary .tabs.is-boxed a:hover, .hero.is-primary .tabs.is-toggle a:hover {\n        background-color: rgba(10, 10, 10, 0.1); }\n    .hero.is-primary .tabs.is-boxed li.is-active a, .hero.is-primary .tabs.is-boxed li.is-active a:hover, .hero.is-primary .tabs.is-toggle li.is-active a, .hero.is-primary .tabs.is-toggle li.is-active a:hover {\n      background-color: #fff;\n      border-color: #fff;\n      color: #ff0d68; }\n    .hero.is-primary.is-bold {\n      background-image: linear-gradient(141deg, #d90076 0%, #ff0d68 71%, #ff2754 100%); }\n      @media screen and (max-width: 768px) {\n        .hero.is-primary.is-bold .navbar-menu {\n          background-image: linear-gradient(141deg, #d90076 0%, #ff0d68 71%, #ff2754 100%); } }\n  .hero.is-link {\n    background-color: #ff0d68;\n    color: #fff; }\n    .hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),\n    .hero.is-link strong {\n      color: inherit; }\n    .hero.is-link .title {\n      color: #fff; }\n    .hero.is-link .subtitle {\n      color: rgba(255, 255, 255, 0.9); }\n      .hero.is-link .subtitle a:not(.button),\n      .hero.is-link .subtitle strong {\n        color: #fff; }\n    @media screen and (max-width: 1023px) {\n      .hero.is-link .navbar-menu {\n        background-color: #ff0d68; } }\n    .hero.is-link .navbar-item,\n    .hero.is-link .navbar-link {\n      color: rgba(255, 255, 255, 0.7); }\n    .hero.is-link a.navbar-item:hover, .hero.is-link a.navbar-item.is-active,\n    .hero.is-link .navbar-link:hover,\n    .hero.is-link .navbar-link.is-active {\n      background-color: #f3005b;\n      color: #fff; }\n    .hero.is-link .tabs a {\n      color: #fff;\n      opacity: 0.9; }\n      .hero.is-link .tabs a:hover {\n        opacity: 1; }\n    .hero.is-link .tabs li.is-active a {\n      color: #ff0d68 !important;\n      opacity: 1; }\n    .hero.is-link .tabs.is-boxed a, .hero.is-link .tabs.is-toggle a {\n      color: #fff; }\n      .hero.is-link .tabs.is-boxed a:hover, .hero.is-link .tabs.is-toggle a:hover {\n        background-color: rgba(10, 10, 10, 0.1); }\n    .hero.is-link .tabs.is-boxed li.is-active a, .hero.is-link .tabs.is-boxed li.is-active a:hover, .hero.is-link .tabs.is-toggle li.is-active a, .hero.is-link .tabs.is-toggle li.is-active a:hover {\n      background-color: #fff;\n      border-color: #fff;\n      color: #ff0d68; }\n    .hero.is-link.is-bold {\n      background-image: linear-gradient(141deg, #d90076 0%, #ff0d68 71%, #ff2754 100%); }\n      @media screen and (max-width: 768px) {\n        .hero.is-link.is-bold .navbar-menu {\n          background-image: linear-gradient(141deg, #d90076 0%, #ff0d68 71%, #ff2754 100%); } }\n  .hero.is-info {\n    background-color: #0092FF;\n    color: #fff; }\n    .hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),\n    .hero.is-info strong {\n      color: inherit; }\n    .hero.is-info .title {\n      color: #fff; }\n    .hero.is-info .subtitle {\n      color: rgba(255, 255, 255, 0.9); }\n      .hero.is-info .subtitle a:not(.button),\n      .hero.is-info .subtitle strong {\n        color: #fff; }\n    @media screen and (max-width: 1023px) {\n      .hero.is-info .navbar-menu {\n        background-color: #0092FF; } }\n    .hero.is-info .navbar-item,\n    .hero.is-info .navbar-link {\n      color: rgba(255, 255, 255, 0.7); }\n    .hero.is-info a.navbar-item:hover, .hero.is-info a.navbar-item.is-active,\n    .hero.is-info .navbar-link:hover,\n    .hero.is-info .navbar-link.is-active {\n      background-color: #0083e6;\n      color: #fff; }\n    .hero.is-info .tabs a {\n      color: #fff;\n      opacity: 0.9; }\n      .hero.is-info .tabs a:hover {\n        opacity: 1; }\n    .hero.is-info .tabs li.is-active a {\n      color: #0092FF !important;\n      opacity: 1; }\n    .hero.is-info .tabs.is-boxed a, .hero.is-info .tabs.is-toggle a {\n      color: #fff; }\n      .hero.is-info .tabs.is-boxed a:hover, .hero.is-info .tabs.is-toggle a:hover {\n        background-color: rgba(10, 10, 10, 0.1); }\n    .hero.is-info .tabs.is-boxed li.is-active a, .hero.is-info .tabs.is-boxed li.is-active a:hover, .hero.is-info .tabs.is-toggle li.is-active a, .hero.is-info .tabs.is-toggle li.is-active a:hover {\n      background-color: #fff;\n      border-color: #fff;\n      color: #0092FF; }\n    .hero.is-info.is-bold {\n      background-image: linear-gradient(141deg, #0097cc 0%, #0092FF 71%, #1a77ff 100%); }\n      @media screen and (max-width: 768px) {\n        .hero.is-info.is-bold .navbar-menu {\n          background-image: linear-gradient(141deg, #0097cc 0%, #0092FF 71%, #1a77ff 100%); } }\n  .hero.is-success {\n    background-color: #16DB93;\n    color: rgba(0, 0, 0, 0.7); }\n    .hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),\n    .hero.is-success strong {\n      color: inherit; }\n    .hero.is-success .title {\n      color: rgba(0, 0, 0, 0.7); }\n    .hero.is-success .subtitle {\n      color: rgba(0, 0, 0, 0.9); }\n      .hero.is-success .subtitle a:not(.button),\n      .hero.is-success .subtitle strong {\n        color: rgba(0, 0, 0, 0.7); }\n    @media screen and (max-width: 1023px) {\n      .hero.is-success .navbar-menu {\n        background-color: #16DB93; } }\n    .hero.is-success .navbar-item,\n    .hero.is-success .navbar-link {\n      color: rgba(0, 0, 0, 0.7); }\n    .hero.is-success a.navbar-item:hover, .hero.is-success a.navbar-item.is-active,\n    .hero.is-success .navbar-link:hover,\n    .hero.is-success .navbar-link.is-active {\n      background-color: #14c483;\n      color: rgba(0, 0, 0, 0.7); }\n    .hero.is-success .tabs a {\n      color: rgba(0, 0, 0, 0.7);\n      opacity: 0.9; }\n      .hero.is-success .tabs a:hover {\n        opacity: 1; }\n    .hero.is-success .tabs li.is-active a {\n      color: #16DB93 !important;\n      opacity: 1; }\n    .hero.is-success .tabs.is-boxed a, .hero.is-success .tabs.is-toggle a {\n      color: rgba(0, 0, 0, 0.7); }\n      .hero.is-success .tabs.is-boxed a:hover, .hero.is-success .tabs.is-toggle a:hover {\n        background-color: rgba(10, 10, 10, 0.1); }\n    .hero.is-success .tabs.is-boxed li.is-active a, .hero.is-success .tabs.is-boxed li.is-active a:hover, .hero.is-success .tabs.is-toggle li.is-active a, .hero.is-success .tabs.is-toggle li.is-active a:hover {\n      background-color: rgba(0, 0, 0, 0.7);\n      border-color: rgba(0, 0, 0, 0.7);\n      color: #16DB93; }\n    .hero.is-success.is-bold {\n      background-image: linear-gradient(141deg, #08b659 0%, #16DB93 71%, #1cefc5 100%); }\n      @media screen and (max-width: 768px) {\n        .hero.is-success.is-bold .navbar-menu {\n          background-image: linear-gradient(141deg, #08b659 0%, #16DB93 71%, #1cefc5 100%); } }\n  .hero.is-warning {\n    background-color: #FFE900;\n    color: rgba(0, 0, 0, 0.7); }\n    .hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),\n    .hero.is-warning strong {\n      color: inherit; }\n    .hero.is-warning .title {\n      color: rgba(0, 0, 0, 0.7); }\n    .hero.is-warning .subtitle {\n      color: rgba(0, 0, 0, 0.9); }\n      .hero.is-warning .subtitle a:not(.button),\n      .hero.is-warning .subtitle strong {\n        color: rgba(0, 0, 0, 0.7); }\n    @media screen and (max-width: 1023px) {\n      .hero.is-warning .navbar-menu {\n        background-color: #FFE900; } }\n    .hero.is-warning .navbar-item,\n    .hero.is-warning .navbar-link {\n      color: rgba(0, 0, 0, 0.7); }\n    .hero.is-warning a.navbar-item:hover, .hero.is-warning a.navbar-item.is-active,\n    .hero.is-warning .navbar-link:hover,\n    .hero.is-warning .navbar-link.is-active {\n      background-color: #e6d200;\n      color: rgba(0, 0, 0, 0.7); }\n    .hero.is-warning .tabs a {\n      color: rgba(0, 0, 0, 0.7);\n      opacity: 0.9; }\n      .hero.is-warning .tabs a:hover {\n        opacity: 1; }\n    .hero.is-warning .tabs li.is-active a {\n      color: #FFE900 !important;\n      opacity: 1; }\n    .hero.is-warning .tabs.is-boxed a, .hero.is-warning .tabs.is-toggle a {\n      color: rgba(0, 0, 0, 0.7); }\n      .hero.is-warning .tabs.is-boxed a:hover, .hero.is-warning .tabs.is-toggle a:hover {\n        background-color: rgba(10, 10, 10, 0.1); }\n    .hero.is-warning .tabs.is-boxed li.is-active a, .hero.is-warning .tabs.is-boxed li.is-active a:hover, .hero.is-warning .tabs.is-toggle li.is-active a, .hero.is-warning .tabs.is-toggle li.is-active a:hover {\n      background-color: rgba(0, 0, 0, 0.7);\n      border-color: rgba(0, 0, 0, 0.7);\n      color: #FFE900; }\n    .hero.is-warning.is-bold {\n      background-image: linear-gradient(141deg, #cc9800 0%, #FFE900 71%, #edff1a 100%); }\n      @media screen and (max-width: 768px) {\n        .hero.is-warning.is-bold .navbar-menu {\n          background-image: linear-gradient(141deg, #cc9800 0%, #FFE900 71%, #edff1a 100%); } }\n  .hero.is-danger {\n    background-color: #f14668;\n    color: #fff; }\n    .hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),\n    .hero.is-danger strong {\n      color: inherit; }\n    .hero.is-danger .title {\n      color: #fff; }\n    .hero.is-danger .subtitle {\n      color: rgba(255, 255, 255, 0.9); }\n      .hero.is-danger .subtitle a:not(.button),\n      .hero.is-danger .subtitle strong {\n        color: #fff; }\n    @media screen and (max-width: 1023px) {\n      .hero.is-danger .navbar-menu {\n        background-color: #f14668; } }\n    .hero.is-danger .navbar-item,\n    .hero.is-danger .navbar-link {\n      color: rgba(255, 255, 255, 0.7); }\n    .hero.is-danger a.navbar-item:hover, .hero.is-danger a.navbar-item.is-active,\n    .hero.is-danger .navbar-link:hover,\n    .hero.is-danger .navbar-link.is-active {\n      background-color: #ef2e55;\n      color: #fff; }\n    .hero.is-danger .tabs a {\n      color: #fff;\n      opacity: 0.9; }\n      .hero.is-danger .tabs a:hover {\n        opacity: 1; }\n    .hero.is-danger .tabs li.is-active a {\n      color: #f14668 !important;\n      opacity: 1; }\n    .hero.is-danger .tabs.is-boxed a, .hero.is-danger .tabs.is-toggle a {\n      color: #fff; }\n      .hero.is-danger .tabs.is-boxed a:hover, .hero.is-danger .tabs.is-toggle a:hover {\n        background-color: rgba(10, 10, 10, 0.1); }\n    .hero.is-danger .tabs.is-boxed li.is-active a, .hero.is-danger .tabs.is-boxed li.is-active a:hover, .hero.is-danger .tabs.is-toggle li.is-active a, .hero.is-danger .tabs.is-toggle li.is-active a:hover {\n      background-color: #fff;\n      border-color: #fff;\n      color: #f14668; }\n    .hero.is-danger.is-bold {\n      background-image: linear-gradient(141deg, #fa0a62 0%, #f14668 71%, #f7595f 100%); }\n      @media screen and (max-width: 768px) {\n        .hero.is-danger.is-bold .navbar-menu {\n          background-image: linear-gradient(141deg, #fa0a62 0%, #f14668 71%, #f7595f 100%); } }\n  .hero.is-small .hero-body {\n    padding: 1.5rem; }\n  @media screen and (min-width: 769px), print {\n    .hero.is-medium .hero-body {\n      padding: 9rem 4.5rem; } }\n  @media screen and (min-width: 769px), print {\n    .hero.is-large .hero-body {\n      padding: 18rem 6rem; } }\n  .hero.is-halfheight .hero-body, .hero.is-fullheight .hero-body, .hero.is-fullheight-with-navbar .hero-body {\n    align-items: center;\n    display: flex; }\n    .hero.is-halfheight .hero-body > .container, .hero.is-fullheight .hero-body > .container, .hero.is-fullheight-with-navbar .hero-body > .container {\n      flex-grow: 1;\n      flex-shrink: 1; }\n  .hero.is-halfheight {\n    min-height: 50vh; }\n  .hero.is-fullheight {\n    min-height: 100vh; }\n\n.hero-video {\n  overflow: hidden; }\n  .hero-video video {\n    left: 50%;\n    min-height: 100%;\n    min-width: 100%;\n    position: absolute;\n    top: 50%;\n    transform: translate3d(-50%, -50%, 0); }\n  .hero-video.is-transparent {\n    opacity: 0.3; }\n  @media screen and (max-width: 768px) {\n    .hero-video {\n      display: none; } }\n.hero-buttons {\n  margin-top: 1.5rem; }\n  @media screen and (max-width: 768px) {\n    .hero-buttons .button {\n      display: flex; }\n      .hero-buttons .button:not(:last-child) {\n        margin-bottom: 0.75rem; } }\n  @media screen and (min-width: 769px), print {\n    .hero-buttons {\n      display: flex;\n      justify-content: center; }\n      .hero-buttons .button:not(:last-child) {\n        margin-right: 1.5rem; } }\n.hero-head,\n.hero-foot {\n  flex-grow: 0;\n  flex-shrink: 0; }\n\n.hero-body {\n  flex-grow: 1;\n  flex-shrink: 0;\n  padding: 3rem 1.5rem; }\n  @media screen and (min-width: 769px), print {\n    .hero-body {\n      padding: 3rem 3rem; } }\n.section {\n  padding: 3rem 1.5rem; }\n  @media screen and (min-width: 1024px) {\n    .section {\n      padding: 3rem 3rem; }\n      .section.is-medium {\n        padding: 9rem 4.5rem; }\n      .section.is-large {\n        padding: 18rem 6rem; } }\n.footer {\n  background-color: #fafafa;\n  padding: 3rem 1.5rem 6rem; }\n\n/*! Bulma Prefers Dark  | MIT License | github.com/jloh/bulma-prefers-dark */\n@media (prefers-color-scheme: dark) {\n  html {\n    background-color: #17181c; }\n  body {\n    color: #b5b5b5; }\n  a {\n    color: #e60056; }\n    a:hover {\n      color: #dbdbdb; }\n  code {\n    background-color: #242424;\n    color: #da1039; }\n  hr {\n    background-color: #242424; }\n  strong {\n    color: #dbdbdb; }\n  pre {\n    background-color: #242424;\n    color: #b5b5b5; }\n  table th {\n    color: #dbdbdb; }\n  .has-text-white-dark {\n    color: white !important; }\n  a.has-text-white-dark:hover, a.has-text-white-dark:focus {\n    color: white !important; }\n  .has-background-white-dark {\n    background-color: white !important; }\n  .has-text-black-dark {\n    color: #0a0a0a !important; }\n  a.has-text-black-dark:hover, a.has-text-black-dark:focus {\n    color: #242424 !important; }\n  .has-background-black-dark {\n    background-color: #0a0a0a !important; }\n  .has-text-light-dark {\n    color: whitesmoke !important; }\n  a.has-text-light-dark:hover, a.has-text-light-dark:focus {\n    color: white !important; }\n  .has-background-light-dark {\n    background-color: whitesmoke !important; }\n  .has-text-dark-dark {\n    color: #363636 !important; }\n  a.has-text-dark-dark:hover, a.has-text-dark-dark:focus {\n    color: #4f4f4f !important; }\n  .has-background-dark-dark {\n    background-color: #363636 !important; }\n  .has-text-primary-dark {\n    color: #ff0d68 !important; }\n  a.has-text-primary-dark:hover, a.has-text-primary-dark:focus {\n    color: #ff4088 !important; }\n  .has-background-primary-dark {\n    background-color: #ff0d68 !important; }\n  .has-text-link-dark {\n    color: #ff0d68 !important; }\n  a.has-text-link-dark:hover, a.has-text-link-dark:focus {\n    color: #ff4088 !important; }\n  .has-background-link-dark {\n    background-color: #ff0d68 !important; }\n  .has-text-info-dark {\n    color: #0092FF !important; }\n  a.has-text-info-dark:hover, a.has-text-info-dark:focus {\n    color: #33a8ff !important; }\n  .has-background-info-dark {\n    background-color: #0092FF !important; }\n  .has-text-success-dark {\n    color: #16DB93 !important; }\n  a.has-text-success-dark:hover, a.has-text-success-dark:focus {\n    color: #39ebaa !important; }\n  .has-background-success-dark {\n    background-color: #16DB93 !important; }\n  .has-text-warning-dark {\n    color: #FFE900 !important; }\n  a.has-text-warning-dark:hover, a.has-text-warning-dark:focus {\n    color: #ffed33 !important; }\n  .has-background-warning-dark {\n    background-color: #FFE900 !important; }\n  .has-text-danger-dark {\n    color: #f14668 !important; }\n  a.has-text-danger-dark:hover, a.has-text-danger-dark:focus {\n    color: #f5758f !important; }\n  .has-background-danger-dark {\n    background-color: #f14668 !important; }\n  .has-text-black-bis-dark {\n    color: #121212 !important; }\n  .has-background-black-bis-dark {\n    background-color: #121212 !important; }\n  .has-text-black-ter-dark {\n    color: #242424 !important; }\n  .has-background-black-ter-dark {\n    background-color: #242424 !important; }\n  .has-text-grey-darker-dark {\n    color: #363636 !important; }\n  .has-background-grey-darker-dark {\n    background-color: #363636 !important; }\n  .has-text-grey-dark-dark {\n    color: #4a4a4a !important; }\n  .has-background-grey-dark-dark {\n    background-color: #4a4a4a !important; }\n  .has-text-grey-dark {\n    color: #7a7a7a !important; }\n  .has-background-grey-dark {\n    background-color: #7a7a7a !important; }\n  .has-text-grey-light-dark {\n    color: #b5b5b5 !important; }\n  .has-background-grey-light-dark {\n    background-color: #b5b5b5 !important; }\n  .has-text-grey-lighter-dark {\n    color: #dbdbdb !important; }\n  .has-background-grey-lighter-dark {\n    background-color: #dbdbdb !important; }\n  .has-text-white-ter-dark {\n    color: whitesmoke !important; }\n  .has-background-white-ter-dark {\n    background-color: whitesmoke !important; }\n  .has-text-white-bis-dark {\n    color: #fafafa !important; }\n  .has-background-white-bis-dark {\n    background-color: #fafafa !important; }\n  .box {\n    background-color: #0a0a0a;\n    box-shadow: 0 2px 3px rgba(255, 255, 255, 0.1), 0 0 0 1px rgba(255, 255, 255, 0.1);\n    color: #b5b5b5; }\n  a.box:hover, a.box:focus {\n    box-shadow: 0 2px 3px rgba(255, 255, 255, 0.1), 0 0 0 1px #e60056; }\n  a.box:active {\n    box-shadow: inset 0 1px 2px rgba(255, 255, 255, 0.2), 0 0 0 1px #e60056; }\n  .button {\n    background-color: #0a0a0a;\n    border-color: #363636;\n    color: #dbdbdb; }\n    .button:hover, .button.is-hovered {\n      border-color: #4a4a4a;\n      color: #dbdbdb; }\n    .button:focus, .button.is-focused {\n      border-color: #5ea3e4;\n      color: #dbdbdb; }\n      .button:focus:not(:active), .button.is-focused:not(:active) {\n        box-shadow: 0 0 0 0.125em rgba(230, 0, 86, 0.25); }\n    .button:active, .button.is-active {\n      border-color: #b5b5b5;\n      color: #dbdbdb; }\n    .button.is-text {\n      color: #b5b5b5; }\n      .button.is-text:hover, .button.is-text.is-hovered, .button.is-text:focus, .button.is-text.is-focused {\n        background-color: #242424;\n        color: #dbdbdb; }\n      .button.is-text:active, .button.is-text.is-active {\n        background-color: #171717;\n        color: #dbdbdb; }\n    .button.is-white {\n      background-color: #e6e6e6;\n      border-color: transparent;\n      color: #0a0a0a; }\n      .button.is-white:hover, .button.is-white.is-hovered {\n        background-color: #dfdfdf;\n        border-color: transparent;\n        color: #0a0a0a; }\n      .button.is-white:focus, .button.is-white.is-focused {\n        border-color: transparent;\n        color: #0a0a0a; }\n        .button.is-white:focus:not(:active), .button.is-white.is-focused:not(:active) {\n          box-shadow: 0 0 0 0.125em rgba(230, 230, 230, 0.25); }\n      .button.is-white:active, .button.is-white.is-active {\n        background-color: #d9d9d9;\n        border-color: transparent;\n        color: #0a0a0a; }\n      .button.is-white[disabled], fieldset[disabled] .button.is-white {\n        background-color: #e6e6e6;\n        border-color: transparent;\n        box-shadow: none; }\n      .button.is-white.is-inverted {\n        background-color: #0a0a0a;\n        color: #e6e6e6; }\n        .button.is-white.is-inverted:hover {\n          background-color: black; }\n        .button.is-white.is-inverted[disabled], fieldset[disabled] .button.is-white.is-inverted {\n          background-color: #0a0a0a;\n          border-color: transparent;\n          box-shadow: none;\n          color: #e6e6e6; }\n      .button.is-white.is-loading::after {\n        border-color: transparent transparent #0a0a0a #0a0a0a !important; }\n      .button.is-white.is-outlined {\n        background-color: transparent;\n        border-color: #e6e6e6;\n        color: #e6e6e6; }\n        .button.is-white.is-outlined:hover, .button.is-white.is-outlined:focus {\n          background-color: #e6e6e6;\n          border-color: #e6e6e6;\n          color: #0a0a0a; }\n        .button.is-white.is-outlined.is-loading::after {\n          border-color: transparent transparent #e6e6e6 #e6e6e6 !important; }\n        .button.is-white.is-outlined[disabled], fieldset[disabled] .button.is-white.is-outlined {\n          background-color: transparent;\n          border-color: #e6e6e6;\n          box-shadow: none;\n          color: #e6e6e6; }\n      .button.is-white.is-inverted.is-outlined {\n        background-color: transparent;\n        border-color: #0a0a0a;\n        color: #0a0a0a; }\n        .button.is-white.is-inverted.is-outlined:hover, .button.is-white.is-inverted.is-outlined:focus {\n          background-color: #0a0a0a;\n          color: #e6e6e6; }\n        .button.is-white.is-inverted.is-outlined[disabled], fieldset[disabled] .button.is-white.is-inverted.is-outlined {\n          background-color: transparent;\n          border-color: #0a0a0a;\n          box-shadow: none;\n          color: #0a0a0a; }\n    .button.is-black {\n      background-color: black;\n      border-color: transparent;\n      color: white; }\n      .button.is-black:hover, .button.is-black.is-hovered {\n        background-color: black;\n        border-color: transparent;\n        color: white; }\n      .button.is-black:focus, .button.is-black.is-focused {\n        border-color: transparent;\n        color: white; }\n        .button.is-black:focus:not(:active), .button.is-black.is-focused:not(:active) {\n          box-shadow: 0 0 0 0.125em rgba(0, 0, 0, 0.25); }\n      .button.is-black:active, .button.is-black.is-active {\n        background-color: black;\n        border-color: transparent;\n        color: white; }\n      .button.is-black[disabled], fieldset[disabled] .button.is-black {\n        background-color: black;\n        border-color: transparent;\n        box-shadow: none; }\n      .button.is-black.is-inverted {\n        background-color: white;\n        color: black; }\n        .button.is-black.is-inverted:hover {\n          background-color: #f2f2f2; }\n        .button.is-black.is-inverted[disabled], fieldset[disabled] .button.is-black.is-inverted {\n          background-color: white;\n          border-color: transparent;\n          box-shadow: none;\n          color: black; }\n      .button.is-black.is-loading::after {\n        border-color: transparent transparent white white !important; }\n      .button.is-black.is-outlined {\n        background-color: transparent;\n        border-color: black;\n        color: black; }\n        .button.is-black.is-outlined:hover, .button.is-black.is-outlined:focus {\n          background-color: black;\n          border-color: black;\n          color: white; }\n        .button.is-black.is-outlined.is-loading::after {\n          border-color: transparent transparent black black !important; }\n        .button.is-black.is-outlined[disabled], fieldset[disabled] .button.is-black.is-outlined {\n          background-color: transparent;\n          border-color: black;\n          box-shadow: none;\n          color: black; }\n      .button.is-black.is-inverted.is-outlined {\n        background-color: transparent;\n        border-color: white;\n        color: white; }\n        .button.is-black.is-inverted.is-outlined:hover, .button.is-black.is-inverted.is-outlined:focus {\n          background-color: white;\n          color: black; }\n        .button.is-black.is-inverted.is-outlined[disabled], fieldset[disabled] .button.is-black.is-inverted.is-outlined {\n          background-color: transparent;\n          border-color: white;\n          box-shadow: none;\n          color: white; }\n    .button.is-light {\n      background-color: #dbdbdb;\n      border-color: transparent;\n      color: rgba(0, 0, 0, 0.7); }\n      .button.is-light:hover, .button.is-light.is-hovered {\n        background-color: #d5d5d5;\n        border-color: transparent;\n        color: rgba(0, 0, 0, 0.7); }\n      .button.is-light:focus, .button.is-light.is-focused {\n        border-color: transparent;\n        color: rgba(0, 0, 0, 0.7); }\n        .button.is-light:focus:not(:active), .button.is-light.is-focused:not(:active) {\n          box-shadow: 0 0 0 0.125em rgba(219, 219, 219, 0.25); }\n      .button.is-light:active, .button.is-light.is-active {\n        background-color: #cfcfcf;\n        border-color: transparent;\n        color: rgba(0, 0, 0, 0.7); }\n      .button.is-light[disabled], fieldset[disabled] .button.is-light {\n        background-color: #dbdbdb;\n        border-color: transparent;\n        box-shadow: none; }\n      .button.is-light.is-inverted {\n        background-color: rgba(0, 0, 0, 0.7);\n        color: #dbdbdb; }\n        .button.is-light.is-inverted:hover {\n          background-color: rgba(0, 0, 0, 0.7); }\n        .button.is-light.is-inverted[disabled], fieldset[disabled] .button.is-light.is-inverted {\n          background-color: rgba(0, 0, 0, 0.7);\n          border-color: transparent;\n          box-shadow: none;\n          color: #dbdbdb; }\n      .button.is-light.is-loading::after {\n        border-color: transparent transparent rgba(0, 0, 0, 0.7) rgba(0, 0, 0, 0.7) !important; }\n      .button.is-light.is-outlined {\n        background-color: transparent;\n        border-color: #dbdbdb;\n        color: #dbdbdb; }\n        .button.is-light.is-outlined:hover, .button.is-light.is-outlined:focus {\n          background-color: #dbdbdb;\n          border-color: #dbdbdb;\n          color: rgba(0, 0, 0, 0.7); }\n        .button.is-light.is-outlined.is-loading::after {\n          border-color: transparent transparent #dbdbdb #dbdbdb !important; }\n        .button.is-light.is-outlined[disabled], fieldset[disabled] .button.is-light.is-outlined {\n          background-color: transparent;\n          border-color: #dbdbdb;\n          box-shadow: none;\n          color: #dbdbdb; }\n      .button.is-light.is-inverted.is-outlined {\n        background-color: transparent;\n        border-color: rgba(0, 0, 0, 0.7);\n        color: rgba(0, 0, 0, 0.7); }\n        .button.is-light.is-inverted.is-outlined:hover, .button.is-light.is-inverted.is-outlined:focus {\n          background-color: rgba(0, 0, 0, 0.7);\n          color: #dbdbdb; }\n        .button.is-light.is-inverted.is-outlined[disabled], fieldset[disabled] .button.is-light.is-inverted.is-outlined {\n          background-color: transparent;\n          border-color: rgba(0, 0, 0, 0.7);\n          box-shadow: none;\n          color: rgba(0, 0, 0, 0.7); }\n    .button.is-dark {\n      background-color: #1c1c1c;\n      border-color: transparent;\n      color: #fff; }\n      .button.is-dark:hover, .button.is-dark.is-hovered {\n        background-color: #161616;\n        border-color: transparent;\n        color: #fff; }\n      .button.is-dark:focus, .button.is-dark.is-focused {\n        border-color: transparent;\n        color: #fff; }\n        .button.is-dark:focus:not(:active), .button.is-dark.is-focused:not(:active) {\n          box-shadow: 0 0 0 0.125em rgba(28, 28, 28, 0.25); }\n      .button.is-dark:active, .button.is-dark.is-active {\n        background-color: #0f0f0f;\n        border-color: transparent;\n        color: #fff; }\n      .button.is-dark[disabled], fieldset[disabled] .button.is-dark {\n        background-color: #1c1c1c;\n        border-color: transparent;\n        box-shadow: none; }\n      .button.is-dark.is-inverted {\n        background-color: #fff;\n        color: #1c1c1c; }\n        .button.is-dark.is-inverted:hover {\n          background-color: #f2f2f2; }\n        .button.is-dark.is-inverted[disabled], fieldset[disabled] .button.is-dark.is-inverted {\n          background-color: #fff;\n          border-color: transparent;\n          box-shadow: none;\n          color: #1c1c1c; }\n      .button.is-dark.is-loading::after {\n        border-color: transparent transparent #fff #fff !important; }\n      .button.is-dark.is-outlined {\n        background-color: transparent;\n        border-color: #1c1c1c;\n        color: #1c1c1c; }\n        .button.is-dark.is-outlined:hover, .button.is-dark.is-outlined:focus {\n          background-color: #1c1c1c;\n          border-color: #1c1c1c;\n          color: #fff; }\n        .button.is-dark.is-outlined.is-loading::after {\n          border-color: transparent transparent #1c1c1c #1c1c1c !important; }\n        .button.is-dark.is-outlined[disabled], fieldset[disabled] .button.is-dark.is-outlined {\n          background-color: transparent;\n          border-color: #1c1c1c;\n          box-shadow: none;\n          color: #1c1c1c; }\n      .button.is-dark.is-inverted.is-outlined {\n        background-color: transparent;\n        border-color: #fff;\n        color: #fff; }\n        .button.is-dark.is-inverted.is-outlined:hover, .button.is-dark.is-inverted.is-outlined:focus {\n          background-color: #fff;\n          color: #1c1c1c; }\n        .button.is-dark.is-inverted.is-outlined[disabled], fieldset[disabled] .button.is-dark.is-inverted.is-outlined {\n          background-color: transparent;\n          border-color: #fff;\n          box-shadow: none;\n          color: #fff; }\n    .button.is-primary {\n      background-color: #d90052;\n      border-color: transparent;\n      color: #fff; }\n      .button.is-primary:hover, .button.is-primary.is-hovered {\n        background-color: #cc004d;\n        border-color: transparent;\n        color: #fff; }\n      .button.is-primary:focus, .button.is-primary.is-focused {\n        border-color: transparent;\n        color: #fff; }\n        .button.is-primary:focus:not(:active), .button.is-primary.is-focused:not(:active) {\n          box-shadow: 0 0 0 0.125em rgba(217, 0, 82, 0.25); }\n      .button.is-primary:active, .button.is-primary.is-active {\n        background-color: #c00048;\n        border-color: transparent;\n        color: #fff; }\n      .button.is-primary[disabled], fieldset[disabled] .button.is-primary {\n        background-color: #d90052;\n        border-color: transparent;\n        box-shadow: none; }\n      .button.is-primary.is-inverted {\n        background-color: #fff;\n        color: #d90052; }\n        .button.is-primary.is-inverted:hover {\n          background-color: #f2f2f2; }\n        .button.is-primary.is-inverted[disabled], fieldset[disabled] .button.is-primary.is-inverted {\n          background-color: #fff;\n          border-color: transparent;\n          box-shadow: none;\n          color: #d90052; }\n      .button.is-primary.is-loading::after {\n        border-color: transparent transparent #fff #fff !important; }\n      .button.is-primary.is-outlined {\n        background-color: transparent;\n        border-color: #d90052;\n        color: #d90052; }\n        .button.is-primary.is-outlined:hover, .button.is-primary.is-outlined:focus {\n          background-color: #d90052;\n          border-color: #d90052;\n          color: #fff; }\n        .button.is-primary.is-outlined.is-loading::after {\n          border-color: transparent transparent #d90052 #d90052 !important; }\n        .button.is-primary.is-outlined[disabled], fieldset[disabled] .button.is-primary.is-outlined {\n          background-color: transparent;\n          border-color: #d90052;\n          box-shadow: none;\n          color: #d90052; }\n      .button.is-primary.is-inverted.is-outlined {\n        background-color: transparent;\n        border-color: #fff;\n        color: #fff; }\n        .button.is-primary.is-inverted.is-outlined:hover, .button.is-primary.is-inverted.is-outlined:focus {\n          background-color: #fff;\n          color: #d90052; }\n        .button.is-primary.is-inverted.is-outlined[disabled], fieldset[disabled] .button.is-primary.is-inverted.is-outlined {\n          background-color: transparent;\n          border-color: #fff;\n          box-shadow: none;\n          color: #fff; }\n    .button.is-link {\n      background-color: #d90052;\n      border-color: transparent;\n      color: #fff; }\n      .button.is-link:hover, .button.is-link.is-hovered {\n        background-color: #cc004d;\n        border-color: transparent;\n        color: #fff; }\n      .button.is-link:focus, .button.is-link.is-focused {\n        border-color: transparent;\n        color: #fff; }\n        .button.is-link:focus:not(:active), .button.is-link.is-focused:not(:active) {\n          box-shadow: 0 0 0 0.125em rgba(217, 0, 82, 0.25); }\n      .button.is-link:active, .button.is-link.is-active {\n        background-color: #c00048;\n        border-color: transparent;\n        color: #fff; }\n      .button.is-link[disabled], fieldset[disabled] .button.is-link {\n        background-color: #d90052;\n        border-color: transparent;\n        box-shadow: none; }\n      .button.is-link.is-inverted {\n        background-color: #fff;\n        color: #d90052; }\n        .button.is-link.is-inverted:hover {\n          background-color: #f2f2f2; }\n        .button.is-link.is-inverted[disabled], fieldset[disabled] .button.is-link.is-inverted {\n          background-color: #fff;\n          border-color: transparent;\n          box-shadow: none;\n          color: #d90052; }\n      .button.is-link.is-loading::after {\n        border-color: transparent transparent #fff #fff !important; }\n      .button.is-link.is-outlined {\n        background-color: transparent;\n        border-color: #d90052;\n        color: #d90052; }\n        .button.is-link.is-outlined:hover, .button.is-link.is-outlined:focus {\n          background-color: #d90052;\n          border-color: #d90052;\n          color: #fff; }\n        .button.is-link.is-outlined.is-loading::after {\n          border-color: transparent transparent #d90052 #d90052 !important; }\n        .button.is-link.is-outlined[disabled], fieldset[disabled] .button.is-link.is-outlined {\n          background-color: transparent;\n          border-color: #d90052;\n          box-shadow: none;\n          color: #d90052; }\n      .button.is-link.is-inverted.is-outlined {\n        background-color: transparent;\n        border-color: #fff;\n        color: #fff; }\n        .button.is-link.is-inverted.is-outlined:hover, .button.is-link.is-inverted.is-outlined:focus {\n          background-color: #fff;\n          color: #d90052; }\n        .button.is-link.is-inverted.is-outlined[disabled], fieldset[disabled] .button.is-link.is-inverted.is-outlined {\n          background-color: transparent;\n          border-color: #fff;\n          box-shadow: none;\n          color: #fff; }\n    .button.is-info {\n      background-color: #0075cc;\n      border-color: transparent;\n      color: #fff; }\n      .button.is-info:hover, .button.is-info.is-hovered {\n        background-color: #006ebf;\n        border-color: transparent;\n        color: #fff; }\n      .button.is-info:focus, .button.is-info.is-focused {\n        border-color: transparent;\n        color: #fff; }\n        .button.is-info:focus:not(:active), .button.is-info.is-focused:not(:active) {\n          box-shadow: 0 0 0 0.125em rgba(0, 117, 204, 0.25); }\n      .button.is-info:active, .button.is-info.is-active {\n        background-color: #0066b3;\n        border-color: transparent;\n        color: #fff; }\n      .button.is-info[disabled], fieldset[disabled] .button.is-info {\n        background-color: #0075cc;\n        border-color: transparent;\n        box-shadow: none; }\n      .button.is-info.is-inverted {\n        background-color: #fff;\n        color: #0075cc; }\n        .button.is-info.is-inverted:hover {\n          background-color: #f2f2f2; }\n        .button.is-info.is-inverted[disabled], fieldset[disabled] .button.is-info.is-inverted {\n          background-color: #fff;\n          border-color: transparent;\n          box-shadow: none;\n          color: #0075cc; }\n      .button.is-info.is-loading::after {\n        border-color: transparent transparent #fff #fff !important; }\n      .button.is-info.is-outlined {\n        background-color: transparent;\n        border-color: #0075cc;\n        color: #0075cc; }\n        .button.is-info.is-outlined:hover, .button.is-info.is-outlined:focus {\n          background-color: #0075cc;\n          border-color: #0075cc;\n          color: #fff; }\n        .button.is-info.is-outlined.is-loading::after {\n          border-color: transparent transparent #0075cc #0075cc !important; }\n        .button.is-info.is-outlined[disabled], fieldset[disabled] .button.is-info.is-outlined {\n          background-color: transparent;\n          border-color: #0075cc;\n          box-shadow: none;\n          color: #0075cc; }\n      .button.is-info.is-inverted.is-outlined {\n        background-color: transparent;\n        border-color: #fff;\n        color: #fff; }\n        .button.is-info.is-inverted.is-outlined:hover, .button.is-info.is-inverted.is-outlined:focus {\n          background-color: #fff;\n          color: #0075cc; }\n        .button.is-info.is-inverted.is-outlined[disabled], fieldset[disabled] .button.is-info.is-inverted.is-outlined {\n          background-color: transparent;\n          border-color: #fff;\n          box-shadow: none;\n          color: #fff; }\n    .button.is-success {\n      background-color: #11ad74;\n      border-color: transparent;\n      color: rgba(0, 0, 0, 0.7); }\n      .button.is-success:hover, .button.is-success.is-hovered {\n        background-color: #10a16c;\n        border-color: transparent;\n        color: rgba(0, 0, 0, 0.7); }\n      .button.is-success:focus, .button.is-success.is-focused {\n        border-color: transparent;\n        color: rgba(0, 0, 0, 0.7); }\n        .button.is-success:focus:not(:active), .button.is-success.is-focused:not(:active) {\n          box-shadow: 0 0 0 0.125em rgba(17, 173, 116, 0.25); }\n      .button.is-success:active, .button.is-success.is-active {\n        background-color: #0f9564;\n        border-color: transparent;\n        color: rgba(0, 0, 0, 0.7); }\n      .button.is-success[disabled], fieldset[disabled] .button.is-success {\n        background-color: #11ad74;\n        border-color: transparent;\n        box-shadow: none; }\n      .button.is-success.is-inverted {\n        background-color: rgba(0, 0, 0, 0.7);\n        color: #11ad74; }\n        .button.is-success.is-inverted:hover {\n          background-color: rgba(0, 0, 0, 0.7); }\n        .button.is-success.is-inverted[disabled], fieldset[disabled] .button.is-success.is-inverted {\n          background-color: rgba(0, 0, 0, 0.7);\n          border-color: transparent;\n          box-shadow: none;\n          color: #11ad74; }\n      .button.is-success.is-loading::after {\n        border-color: transparent transparent rgba(0, 0, 0, 0.7) rgba(0, 0, 0, 0.7) !important; }\n      .button.is-success.is-outlined {\n        background-color: transparent;\n        border-color: #11ad74;\n        color: #11ad74; }\n        .button.is-success.is-outlined:hover, .button.is-success.is-outlined:focus {\n          background-color: #11ad74;\n          border-color: #11ad74;\n          color: rgba(0, 0, 0, 0.7); }\n        .button.is-success.is-outlined.is-loading::after {\n          border-color: transparent transparent #11ad74 #11ad74 !important; }\n        .button.is-success.is-outlined[disabled], fieldset[disabled] .button.is-success.is-outlined {\n          background-color: transparent;\n          border-color: #11ad74;\n          box-shadow: none;\n          color: #11ad74; }\n      .button.is-success.is-inverted.is-outlined {\n        background-color: transparent;\n        border-color: rgba(0, 0, 0, 0.7);\n        color: rgba(0, 0, 0, 0.7); }\n        .button.is-success.is-inverted.is-outlined:hover, .button.is-success.is-inverted.is-outlined:focus {\n          background-color: rgba(0, 0, 0, 0.7);\n          color: #11ad74; }\n        .button.is-success.is-inverted.is-outlined[disabled], fieldset[disabled] .button.is-success.is-inverted.is-outlined {\n          background-color: transparent;\n          border-color: rgba(0, 0, 0, 0.7);\n          box-shadow: none;\n          color: rgba(0, 0, 0, 0.7); }\n    .button.is-warning {\n      background-color: #ccba00;\n      border-color: transparent;\n      color: rgba(0, 0, 0, 0.7); }\n      .button.is-warning:hover, .button.is-warning.is-hovered {\n        background-color: #bfaf00;\n        border-color: transparent;\n        color: rgba(0, 0, 0, 0.7); }\n      .button.is-warning:focus, .button.is-warning.is-focused {\n        border-color: transparent;\n        color: rgba(0, 0, 0, 0.7); }\n        .button.is-warning:focus:not(:active), .button.is-warning.is-focused:not(:active) {\n          box-shadow: 0 0 0 0.125em rgba(204, 186, 0, 0.25); }\n      .button.is-warning:active, .button.is-warning.is-active {\n        background-color: #b3a300;\n        border-color: transparent;\n        color: rgba(0, 0, 0, 0.7); }\n      .button.is-warning[disabled], fieldset[disabled] .button.is-warning {\n        background-color: #ccba00;\n        border-color: transparent;\n        box-shadow: none; }\n      .button.is-warning.is-inverted {\n        background-color: rgba(0, 0, 0, 0.7);\n        color: #ccba00; }\n        .button.is-warning.is-inverted:hover {\n          background-color: rgba(0, 0, 0, 0.7); }\n        .button.is-warning.is-inverted[disabled], fieldset[disabled] .button.is-warning.is-inverted {\n          background-color: rgba(0, 0, 0, 0.7);\n          border-color: transparent;\n          box-shadow: none;\n          color: #ccba00; }\n      .button.is-warning.is-loading::after {\n        border-color: transparent transparent rgba(0, 0, 0, 0.7) rgba(0, 0, 0, 0.7) !important; }\n      .button.is-warning.is-outlined {\n        background-color: transparent;\n        border-color: #ccba00;\n        color: #ccba00; }\n        .button.is-warning.is-outlined:hover, .button.is-warning.is-outlined:focus {\n          background-color: #ccba00;\n          border-color: #ccba00;\n          color: rgba(0, 0, 0, 0.7); }\n        .button.is-warning.is-outlined.is-loading::after {\n          border-color: transparent transparent #ccba00 #ccba00 !important; }\n        .button.is-warning.is-outlined[disabled], fieldset[disabled] .button.is-warning.is-outlined {\n          background-color: transparent;\n          border-color: #ccba00;\n          box-shadow: none;\n          color: #ccba00; }\n      .button.is-warning.is-inverted.is-outlined {\n        background-color: transparent;\n        border-color: rgba(0, 0, 0, 0.7);\n        color: rgba(0, 0, 0, 0.7); }\n        .button.is-warning.is-inverted.is-outlined:hover, .button.is-warning.is-inverted.is-outlined:focus {\n          background-color: rgba(0, 0, 0, 0.7);\n          color: #ccba00; }\n        .button.is-warning.is-inverted.is-outlined[disabled], fieldset[disabled] .button.is-warning.is-inverted.is-outlined {\n          background-color: transparent;\n          border-color: rgba(0, 0, 0, 0.7);\n          box-shadow: none;\n          color: rgba(0, 0, 0, 0.7); }\n    .button.is-danger {\n      background-color: #ee1742;\n      border-color: transparent;\n      color: #fff; }\n      .button.is-danger:hover, .button.is-danger.is-hovered {\n        background-color: #e6113c;\n        border-color: transparent;\n        color: #fff; }\n      .button.is-danger:focus, .button.is-danger.is-focused {\n        border-color: transparent;\n        color: #fff; }\n        .button.is-danger:focus:not(:active), .button.is-danger.is-focused:not(:active) {\n          box-shadow: 0 0 0 0.125em rgba(238, 23, 66, 0.25); }\n      .button.is-danger:active, .button.is-danger.is-active {\n        background-color: #da1039;\n        border-color: transparent;\n        color: #fff; }\n      .button.is-danger[disabled], fieldset[disabled] .button.is-danger {\n        background-color: #ee1742;\n        border-color: transparent;\n        box-shadow: none; }\n      .button.is-danger.is-inverted {\n        background-color: #fff;\n        color: #ee1742; }\n        .button.is-danger.is-inverted:hover {\n          background-color: #f2f2f2; }\n        .button.is-danger.is-inverted[disabled], fieldset[disabled] .button.is-danger.is-inverted {\n          background-color: #fff;\n          border-color: transparent;\n          box-shadow: none;\n          color: #ee1742; }\n      .button.is-danger.is-loading::after {\n        border-color: transparent transparent #fff #fff !important; }\n      .button.is-danger.is-outlined {\n        background-color: transparent;\n        border-color: #ee1742;\n        color: #ee1742; }\n        .button.is-danger.is-outlined:hover, .button.is-danger.is-outlined:focus {\n          background-color: #ee1742;\n          border-color: #ee1742;\n          color: #fff; }\n        .button.is-danger.is-outlined.is-loading::after {\n          border-color: transparent transparent #ee1742 #ee1742 !important; }\n        .button.is-danger.is-outlined[disabled], fieldset[disabled] .button.is-danger.is-outlined {\n          background-color: transparent;\n          border-color: #ee1742;\n          box-shadow: none;\n          color: #ee1742; }\n      .button.is-danger.is-inverted.is-outlined {\n        background-color: transparent;\n        border-color: #fff;\n        color: #fff; }\n        .button.is-danger.is-inverted.is-outlined:hover, .button.is-danger.is-inverted.is-outlined:focus {\n          background-color: #fff;\n          color: #ee1742; }\n        .button.is-danger.is-inverted.is-outlined[disabled], fieldset[disabled] .button.is-danger.is-inverted.is-outlined {\n          background-color: transparent;\n          border-color: #fff;\n          box-shadow: none;\n          color: #fff; }\n    .button.is-white-dark {\n      background-color: white;\n      border-color: transparent;\n      color: #0a0a0a; }\n      .button.is-white-dark:hover, .button.is-white-dark.is-hovered {\n        background-color: #f9f9f9;\n        border-color: transparent;\n        color: #0a0a0a; }\n      .button.is-white-dark:focus, .button.is-white-dark.is-focused {\n        border-color: transparent;\n        color: #0a0a0a; }\n        .button.is-white-dark:focus:not(:active), .button.is-white-dark.is-focused:not(:active) {\n          box-shadow: 0 0 0 0.125em rgba(255, 255, 255, 0.25); }\n      .button.is-white-dark:active, .button.is-white-dark.is-active {\n        background-color: #f2f2f2;\n        border-color: transparent;\n        color: #0a0a0a; }\n      .button.is-white-dark[disabled], fieldset[disabled] .button.is-white-dark {\n        background-color: white;\n        border-color: transparent;\n        box-shadow: none; }\n      .button.is-white-dark.is-inverted {\n        background-color: #0a0a0a;\n        color: white; }\n        .button.is-white-dark.is-inverted:hover {\n          background-color: black; }\n        .button.is-white-dark.is-inverted[disabled], fieldset[disabled] .button.is-white-dark.is-inverted {\n          background-color: #0a0a0a;\n          border-color: transparent;\n          box-shadow: none;\n          color: white; }\n      .button.is-white-dark.is-loading::after {\n        border-color: transparent transparent #0a0a0a #0a0a0a !important; }\n      .button.is-white-dark.is-outlined {\n        background-color: transparent;\n        border-color: white;\n        color: white; }\n        .button.is-white-dark.is-outlined:hover, .button.is-white-dark.is-outlined:focus {\n          background-color: white;\n          border-color: white;\n          color: #0a0a0a; }\n        .button.is-white-dark.is-outlined.is-loading::after {\n          border-color: transparent transparent white white !important; }\n        .button.is-white-dark.is-outlined[disabled], fieldset[disabled] .button.is-white-dark.is-outlined {\n          background-color: transparent;\n          border-color: white;\n          box-shadow: none;\n          color: white; }\n      .button.is-white-dark.is-inverted.is-outlined {\n        background-color: transparent;\n        border-color: #0a0a0a;\n        color: #0a0a0a; }\n        .button.is-white-dark.is-inverted.is-outlined:hover, .button.is-white-dark.is-inverted.is-outlined:focus {\n          background-color: #0a0a0a;\n          color: white; }\n        .button.is-white-dark.is-inverted.is-outlined[disabled], fieldset[disabled] .button.is-white-dark.is-inverted.is-outlined {\n          background-color: transparent;\n          border-color: #0a0a0a;\n          box-shadow: none;\n          color: #0a0a0a; }\n    .button.is-black-dark {\n      background-color: #0a0a0a;\n      border-color: transparent;\n      color: white; }\n      .button.is-black-dark:hover, .button.is-black-dark.is-hovered {\n        background-color: #040404;\n        border-color: transparent;\n        color: white; }\n      .button.is-black-dark:focus, .button.is-black-dark.is-focused {\n        border-color: transparent;\n        color: white; }\n        .button.is-black-dark:focus:not(:active), .button.is-black-dark.is-focused:not(:active) {\n          box-shadow: 0 0 0 0.125em rgba(10, 10, 10, 0.25); }\n      .button.is-black-dark:active, .button.is-black-dark.is-active {\n        background-color: black;\n        border-color: transparent;\n        color: white; }\n      .button.is-black-dark[disabled], fieldset[disabled] .button.is-black-dark {\n        background-color: #0a0a0a;\n        border-color: transparent;\n        box-shadow: none; }\n      .button.is-black-dark.is-inverted {\n        background-color: white;\n        color: #0a0a0a; }\n        .button.is-black-dark.is-inverted:hover {\n          background-color: #f2f2f2; }\n        .button.is-black-dark.is-inverted[disabled], fieldset[disabled] .button.is-black-dark.is-inverted {\n          background-color: white;\n          border-color: transparent;\n          box-shadow: none;\n          color: #0a0a0a; }\n      .button.is-black-dark.is-loading::after {\n        border-color: transparent transparent white white !important; }\n      .button.is-black-dark.is-outlined {\n        background-color: transparent;\n        border-color: #0a0a0a;\n        color: #0a0a0a; }\n        .button.is-black-dark.is-outlined:hover, .button.is-black-dark.is-outlined:focus {\n          background-color: #0a0a0a;\n          border-color: #0a0a0a;\n          color: white; }\n        .button.is-black-dark.is-outlined.is-loading::after {\n          border-color: transparent transparent #0a0a0a #0a0a0a !important; }\n        .button.is-black-dark.is-outlined[disabled], fieldset[disabled] .button.is-black-dark.is-outlined {\n          background-color: transparent;\n          border-color: #0a0a0a;\n          box-shadow: none;\n          color: #0a0a0a; }\n      .button.is-black-dark.is-inverted.is-outlined {\n        background-color: transparent;\n        border-color: white;\n        color: white; }\n        .button.is-black-dark.is-inverted.is-outlined:hover, .button.is-black-dark.is-inverted.is-outlined:focus {\n          background-color: white;\n          color: #0a0a0a; }\n        .button.is-black-dark.is-inverted.is-outlined[disabled], fieldset[disabled] .button.is-black-dark.is-inverted.is-outlined {\n          background-color: transparent;\n          border-color: white;\n          box-shadow: none;\n          color: white; }\n    .button.is-light-dark {\n      background-color: whitesmoke;\n      border-color: transparent;\n      color: rgba(0, 0, 0, 0.7); }\n      .button.is-light-dark:hover, .button.is-light-dark.is-hovered {\n        background-color: #eeeeee;\n        border-color: transparent;\n        color: rgba(0, 0, 0, 0.7); }\n      .button.is-light-dark:focus, .button.is-light-dark.is-focused {\n        border-color: transparent;\n        color: rgba(0, 0, 0, 0.7); }\n        .button.is-light-dark:focus:not(:active), .button.is-light-dark.is-focused:not(:active) {\n          box-shadow: 0 0 0 0.125em rgba(245, 245, 245, 0.25); }\n      .button.is-light-dark:active, .button.is-light-dark.is-active {\n        background-color: #e8e8e8;\n        border-color: transparent;\n        color: rgba(0, 0, 0, 0.7); }\n      .button.is-light-dark[disabled], fieldset[disabled] .button.is-light-dark {\n        background-color: whitesmoke;\n        border-color: transparent;\n        box-shadow: none; }\n      .button.is-light-dark.is-inverted {\n        background-color: rgba(0, 0, 0, 0.7);\n        color: whitesmoke; }\n        .button.is-light-dark.is-inverted:hover {\n          background-color: rgba(0, 0, 0, 0.7); }\n        .button.is-light-dark.is-inverted[disabled], fieldset[disabled] .button.is-light-dark.is-inverted {\n          background-color: rgba(0, 0, 0, 0.7);\n          border-color: transparent;\n          box-shadow: none;\n          color: whitesmoke; }\n      .button.is-light-dark.is-loading::after {\n        border-color: transparent transparent rgba(0, 0, 0, 0.7) rgba(0, 0, 0, 0.7) !important; }\n      .button.is-light-dark.is-outlined {\n        background-color: transparent;\n        border-color: whitesmoke;\n        color: whitesmoke; }\n        .button.is-light-dark.is-outlined:hover, .button.is-light-dark.is-outlined:focus {\n          background-color: whitesmoke;\n          border-color: whitesmoke;\n          color: rgba(0, 0, 0, 0.7); }\n        .button.is-light-dark.is-outlined.is-loading::after {\n          border-color: transparent transparent whitesmoke whitesmoke !important; }\n        .button.is-light-dark.is-outlined[disabled], fieldset[disabled] .button.is-light-dark.is-outlined {\n          background-color: transparent;\n          border-color: whitesmoke;\n          box-shadow: none;\n          color: whitesmoke; }\n      .button.is-light-dark.is-inverted.is-outlined {\n        background-color: transparent;\n        border-color: rgba(0, 0, 0, 0.7);\n        color: rgba(0, 0, 0, 0.7); }\n        .button.is-light-dark.is-inverted.is-outlined:hover, .button.is-light-dark.is-inverted.is-outlined:focus {\n          background-color: rgba(0, 0, 0, 0.7);\n          color: whitesmoke; }\n        .button.is-light-dark.is-inverted.is-outlined[disabled], fieldset[disabled] .button.is-light-dark.is-inverted.is-outlined {\n          background-color: transparent;\n          border-color: rgba(0, 0, 0, 0.7);\n          box-shadow: none;\n          color: rgba(0, 0, 0, 0.7); }\n    .button.is-dark-dark {\n      background-color: #363636;\n      border-color: transparent;\n      color: #fff; }\n      .button.is-dark-dark:hover, .button.is-dark-dark.is-hovered {\n        background-color: #2f2f2f;\n        border-color: transparent;\n        color: #fff; }\n      .button.is-dark-dark:focus, .button.is-dark-dark.is-focused {\n        border-color: transparent;\n        color: #fff; }\n        .button.is-dark-dark:focus:not(:active), .button.is-dark-dark.is-focused:not(:active) {\n          box-shadow: 0 0 0 0.125em rgba(54, 54, 54, 0.25); }\n      .button.is-dark-dark:active, .button.is-dark-dark.is-active {\n        background-color: #292929;\n        border-color: transparent;\n        color: #fff; }\n      .button.is-dark-dark[disabled], fieldset[disabled] .button.is-dark-dark {\n        background-color: #363636;\n        border-color: transparent;\n        box-shadow: none; }\n      .button.is-dark-dark.is-inverted {\n        background-color: #fff;\n        color: #363636; }\n        .button.is-dark-dark.is-inverted:hover {\n          background-color: #f2f2f2; }\n        .button.is-dark-dark.is-inverted[disabled], fieldset[disabled] .button.is-dark-dark.is-inverted {\n          background-color: #fff;\n          border-color: transparent;\n          box-shadow: none;\n          color: #363636; }\n      .button.is-dark-dark.is-loading::after {\n        border-color: transparent transparent #fff #fff !important; }\n      .button.is-dark-dark.is-outlined {\n        background-color: transparent;\n        border-color: #363636;\n        color: #363636; }\n        .button.is-dark-dark.is-outlined:hover, .button.is-dark-dark.is-outlined:focus {\n          background-color: #363636;\n          border-color: #363636;\n          color: #fff; }\n        .button.is-dark-dark.is-outlined.is-loading::after {\n          border-color: transparent transparent #363636 #363636 !important; }\n        .button.is-dark-dark.is-outlined[disabled], fieldset[disabled] .button.is-dark-dark.is-outlined {\n          background-color: transparent;\n          border-color: #363636;\n          box-shadow: none;\n          color: #363636; }\n      .button.is-dark-dark.is-inverted.is-outlined {\n        background-color: transparent;\n        border-color: #fff;\n        color: #fff; }\n        .button.is-dark-dark.is-inverted.is-outlined:hover, .button.is-dark-dark.is-inverted.is-outlined:focus {\n          background-color: #fff;\n          color: #363636; }\n        .button.is-dark-dark.is-inverted.is-outlined[disabled], fieldset[disabled] .button.is-dark-dark.is-inverted.is-outlined {\n          background-color: transparent;\n          border-color: #fff;\n          box-shadow: none;\n          color: #fff; }\n    .button.is-primary-dark {\n      background-color: #ff0d68;\n      border-color: transparent;\n      color: #fff; }\n      .button.is-primary-dark:hover, .button.is-primary-dark.is-hovered {\n        background-color: #ff0060;\n        border-color: transparent;\n        color: #fff; }\n      .button.is-primary-dark:focus, .button.is-primary-dark.is-focused {\n        border-color: transparent;\n        color: #fff; }\n        .button.is-primary-dark:focus:not(:active), .button.is-primary-dark.is-focused:not(:active) {\n          box-shadow: 0 0 0 0.125em rgba(255, 13, 104, 0.25); }\n      .button.is-primary-dark:active, .button.is-primary-dark.is-active {\n        background-color: #f3005b;\n        border-color: transparent;\n        color: #fff; }\n      .button.is-primary-dark[disabled], fieldset[disabled] .button.is-primary-dark {\n        background-color: #ff0d68;\n        border-color: transparent;\n        box-shadow: none; }\n      .button.is-primary-dark.is-inverted {\n        background-color: #fff;\n        color: #ff0d68; }\n        .button.is-primary-dark.is-inverted:hover {\n          background-color: #f2f2f2; }\n        .button.is-primary-dark.is-inverted[disabled], fieldset[disabled] .button.is-primary-dark.is-inverted {\n          background-color: #fff;\n          border-color: transparent;\n          box-shadow: none;\n          color: #ff0d68; }\n      .button.is-primary-dark.is-loading::after {\n        border-color: transparent transparent #fff #fff !important; }\n      .button.is-primary-dark.is-outlined {\n        background-color: transparent;\n        border-color: #ff0d68;\n        color: #ff0d68; }\n        .button.is-primary-dark.is-outlined:hover, .button.is-primary-dark.is-outlined:focus {\n          background-color: #ff0d68;\n          border-color: #ff0d68;\n          color: #fff; }\n        .button.is-primary-dark.is-outlined.is-loading::after {\n          border-color: transparent transparent #ff0d68 #ff0d68 !important; }\n        .button.is-primary-dark.is-outlined[disabled], fieldset[disabled] .button.is-primary-dark.is-outlined {\n          background-color: transparent;\n          border-color: #ff0d68;\n          box-shadow: none;\n          color: #ff0d68; }\n      .button.is-primary-dark.is-inverted.is-outlined {\n        background-color: transparent;\n        border-color: #fff;\n        color: #fff; }\n        .button.is-primary-dark.is-inverted.is-outlined:hover, .button.is-primary-dark.is-inverted.is-outlined:focus {\n          background-color: #fff;\n          color: #ff0d68; }\n        .button.is-primary-dark.is-inverted.is-outlined[disabled], fieldset[disabled] .button.is-primary-dark.is-inverted.is-outlined {\n          background-color: transparent;\n          border-color: #fff;\n          box-shadow: none;\n          color: #fff; }\n    .button.is-link-dark {\n      background-color: #ff0d68;\n      border-color: transparent;\n      color: #fff; }\n      .button.is-link-dark:hover, .button.is-link-dark.is-hovered {\n        background-color: #ff0060;\n        border-color: transparent;\n        color: #fff; }\n      .button.is-link-dark:focus, .button.is-link-dark.is-focused {\n        border-color: transparent;\n        color: #fff; }\n        .button.is-link-dark:focus:not(:active), .button.is-link-dark.is-focused:not(:active) {\n          box-shadow: 0 0 0 0.125em rgba(255, 13, 104, 0.25); }\n      .button.is-link-dark:active, .button.is-link-dark.is-active {\n        background-color: #f3005b;\n        border-color: transparent;\n        color: #fff; }\n      .button.is-link-dark[disabled], fieldset[disabled] .button.is-link-dark {\n        background-color: #ff0d68;\n        border-color: transparent;\n        box-shadow: none; }\n      .button.is-link-dark.is-inverted {\n        background-color: #fff;\n        color: #ff0d68; }\n        .button.is-link-dark.is-inverted:hover {\n          background-color: #f2f2f2; }\n        .button.is-link-dark.is-inverted[disabled], fieldset[disabled] .button.is-link-dark.is-inverted {\n          background-color: #fff;\n          border-color: transparent;\n          box-shadow: none;\n          color: #ff0d68; }\n      .button.is-link-dark.is-loading::after {\n        border-color: transparent transparent #fff #fff !important; }\n      .button.is-link-dark.is-outlined {\n        background-color: transparent;\n        border-color: #ff0d68;\n        color: #ff0d68; }\n        .button.is-link-dark.is-outlined:hover, .button.is-link-dark.is-outlined:focus {\n          background-color: #ff0d68;\n          border-color: #ff0d68;\n          color: #fff; }\n        .button.is-link-dark.is-outlined.is-loading::after {\n          border-color: transparent transparent #ff0d68 #ff0d68 !important; }\n        .button.is-link-dark.is-outlined[disabled], fieldset[disabled] .button.is-link-dark.is-outlined {\n          background-color: transparent;\n          border-color: #ff0d68;\n          box-shadow: none;\n          color: #ff0d68; }\n      .button.is-link-dark.is-inverted.is-outlined {\n        background-color: transparent;\n        border-color: #fff;\n        color: #fff; }\n        .button.is-link-dark.is-inverted.is-outlined:hover, .button.is-link-dark.is-inverted.is-outlined:focus {\n          background-color: #fff;\n          color: #ff0d68; }\n        .button.is-link-dark.is-inverted.is-outlined[disabled], fieldset[disabled] .button.is-link-dark.is-inverted.is-outlined {\n          background-color: transparent;\n          border-color: #fff;\n          box-shadow: none;\n          color: #fff; }\n    .button.is-info-dark {\n      background-color: #0092FF;\n      border-color: transparent;\n      color: #fff; }\n      .button.is-info-dark:hover, .button.is-info-dark.is-hovered {\n        background-color: #008bf2;\n        border-color: transparent;\n        color: #fff; }\n      .button.is-info-dark:focus, .button.is-info-dark.is-focused {\n        border-color: transparent;\n        color: #fff; }\n        .button.is-info-dark:focus:not(:active), .button.is-info-dark.is-focused:not(:active) {\n          box-shadow: 0 0 0 0.125em rgba(0, 146, 255, 0.25); }\n      .button.is-info-dark:active, .button.is-info-dark.is-active {\n        background-color: #0083e6;\n        border-color: transparent;\n        color: #fff; }\n      .button.is-info-dark[disabled], fieldset[disabled] .button.is-info-dark {\n        background-color: #0092FF;\n        border-color: transparent;\n        box-shadow: none; }\n      .button.is-info-dark.is-inverted {\n        background-color: #fff;\n        color: #0092FF; }\n        .button.is-info-dark.is-inverted:hover {\n          background-color: #f2f2f2; }\n        .button.is-info-dark.is-inverted[disabled], fieldset[disabled] .button.is-info-dark.is-inverted {\n          background-color: #fff;\n          border-color: transparent;\n          box-shadow: none;\n          color: #0092FF; }\n      .button.is-info-dark.is-loading::after {\n        border-color: transparent transparent #fff #fff !important; }\n      .button.is-info-dark.is-outlined {\n        background-color: transparent;\n        border-color: #0092FF;\n        color: #0092FF; }\n        .button.is-info-dark.is-outlined:hover, .button.is-info-dark.is-outlined:focus {\n          background-color: #0092FF;\n          border-color: #0092FF;\n          color: #fff; }\n        .button.is-info-dark.is-outlined.is-loading::after {\n          border-color: transparent transparent #0092FF #0092FF !important; }\n        .button.is-info-dark.is-outlined[disabled], fieldset[disabled] .button.is-info-dark.is-outlined {\n          background-color: transparent;\n          border-color: #0092FF;\n          box-shadow: none;\n          color: #0092FF; }\n      .button.is-info-dark.is-inverted.is-outlined {\n        background-color: transparent;\n        border-color: #fff;\n        color: #fff; }\n        .button.is-info-dark.is-inverted.is-outlined:hover, .button.is-info-dark.is-inverted.is-outlined:focus {\n          background-color: #fff;\n          color: #0092FF; }\n        .button.is-info-dark.is-inverted.is-outlined[disabled], fieldset[disabled] .button.is-info-dark.is-inverted.is-outlined {\n          background-color: transparent;\n          border-color: #fff;\n          box-shadow: none;\n          color: #fff; }\n    .button.is-success-dark {\n      background-color: #16DB93;\n      border-color: transparent;\n      color: rgba(0, 0, 0, 0.7); }\n      .button.is-success-dark:hover, .button.is-success-dark.is-hovered {\n        background-color: #15cf8b;\n        border-color: transparent;\n        color: rgba(0, 0, 0, 0.7); }\n      .button.is-success-dark:focus, .button.is-success-dark.is-focused {\n        border-color: transparent;\n        color: rgba(0, 0, 0, 0.7); }\n        .button.is-success-dark:focus:not(:active), .button.is-success-dark.is-focused:not(:active) {\n          box-shadow: 0 0 0 0.125em rgba(22, 219, 147, 0.25); }\n      .button.is-success-dark:active, .button.is-success-dark.is-active {\n        background-color: #14c483;\n        border-color: transparent;\n        color: rgba(0, 0, 0, 0.7); }\n      .button.is-success-dark[disabled], fieldset[disabled] .button.is-success-dark {\n        background-color: #16DB93;\n        border-color: transparent;\n        box-shadow: none; }\n      .button.is-success-dark.is-inverted {\n        background-color: rgba(0, 0, 0, 0.7);\n        color: #16DB93; }\n        .button.is-success-dark.is-inverted:hover {\n          background-color: rgba(0, 0, 0, 0.7); }\n        .button.is-success-dark.is-inverted[disabled], fieldset[disabled] .button.is-success-dark.is-inverted {\n          background-color: rgba(0, 0, 0, 0.7);\n          border-color: transparent;\n          box-shadow: none;\n          color: #16DB93; }\n      .button.is-success-dark.is-loading::after {\n        border-color: transparent transparent rgba(0, 0, 0, 0.7) rgba(0, 0, 0, 0.7) !important; }\n      .button.is-success-dark.is-outlined {\n        background-color: transparent;\n        border-color: #16DB93;\n        color: #16DB93; }\n        .button.is-success-dark.is-outlined:hover, .button.is-success-dark.is-outlined:focus {\n          background-color: #16DB93;\n          border-color: #16DB93;\n          color: rgba(0, 0, 0, 0.7); }\n        .button.is-success-dark.is-outlined.is-loading::after {\n          border-color: transparent transparent #16DB93 #16DB93 !important; }\n        .button.is-success-dark.is-outlined[disabled], fieldset[disabled] .button.is-success-dark.is-outlined {\n          background-color: transparent;\n          border-color: #16DB93;\n          box-shadow: none;\n          color: #16DB93; }\n      .button.is-success-dark.is-inverted.is-outlined {\n        background-color: transparent;\n        border-color: rgba(0, 0, 0, 0.7);\n        color: rgba(0, 0, 0, 0.7); }\n        .button.is-success-dark.is-inverted.is-outlined:hover, .button.is-success-dark.is-inverted.is-outlined:focus {\n          background-color: rgba(0, 0, 0, 0.7);\n          color: #16DB93; }\n        .button.is-success-dark.is-inverted.is-outlined[disabled], fieldset[disabled] .button.is-success-dark.is-inverted.is-outlined {\n          background-color: transparent;\n          border-color: rgba(0, 0, 0, 0.7);\n          box-shadow: none;\n          color: rgba(0, 0, 0, 0.7); }\n    .button.is-warning-dark {\n      background-color: #FFE900;\n      border-color: transparent;\n      color: rgba(0, 0, 0, 0.7); }\n      .button.is-warning-dark:hover, .button.is-warning-dark.is-hovered {\n        background-color: #f2dd00;\n        border-color: transparent;\n        color: rgba(0, 0, 0, 0.7); }\n      .button.is-warning-dark:focus, .button.is-warning-dark.is-focused {\n        border-color: transparent;\n        color: rgba(0, 0, 0, 0.7); }\n        .button.is-warning-dark:focus:not(:active), .button.is-warning-dark.is-focused:not(:active) {\n          box-shadow: 0 0 0 0.125em rgba(255, 233, 0, 0.25); }\n      .button.is-warning-dark:active, .button.is-warning-dark.is-active {\n        background-color: #e6d200;\n        border-color: transparent;\n        color: rgba(0, 0, 0, 0.7); }\n      .button.is-warning-dark[disabled], fieldset[disabled] .button.is-warning-dark {\n        background-color: #FFE900;\n        border-color: transparent;\n        box-shadow: none; }\n      .button.is-warning-dark.is-inverted {\n        background-color: rgba(0, 0, 0, 0.7);\n        color: #FFE900; }\n        .button.is-warning-dark.is-inverted:hover {\n          background-color: rgba(0, 0, 0, 0.7); }\n        .button.is-warning-dark.is-inverted[disabled], fieldset[disabled] .button.is-warning-dark.is-inverted {\n          background-color: rgba(0, 0, 0, 0.7);\n          border-color: transparent;\n          box-shadow: none;\n          color: #FFE900; }\n      .button.is-warning-dark.is-loading::after {\n        border-color: transparent transparent rgba(0, 0, 0, 0.7) rgba(0, 0, 0, 0.7) !important; }\n      .button.is-warning-dark.is-outlined {\n        background-color: transparent;\n        border-color: #FFE900;\n        color: #FFE900; }\n        .button.is-warning-dark.is-outlined:hover, .button.is-warning-dark.is-outlined:focus {\n          background-color: #FFE900;\n          border-color: #FFE900;\n          color: rgba(0, 0, 0, 0.7); }\n        .button.is-warning-dark.is-outlined.is-loading::after {\n          border-color: transparent transparent #FFE900 #FFE900 !important; }\n        .button.is-warning-dark.is-outlined[disabled], fieldset[disabled] .button.is-warning-dark.is-outlined {\n          background-color: transparent;\n          border-color: #FFE900;\n          box-shadow: none;\n          color: #FFE900; }\n      .button.is-warning-dark.is-inverted.is-outlined {\n        background-color: transparent;\n        border-color: rgba(0, 0, 0, 0.7);\n        color: rgba(0, 0, 0, 0.7); }\n        .button.is-warning-dark.is-inverted.is-outlined:hover, .button.is-warning-dark.is-inverted.is-outlined:focus {\n          background-color: rgba(0, 0, 0, 0.7);\n          color: #FFE900; }\n        .button.is-warning-dark.is-inverted.is-outlined[disabled], fieldset[disabled] .button.is-warning-dark.is-inverted.is-outlined {\n          background-color: transparent;\n          border-color: rgba(0, 0, 0, 0.7);\n          box-shadow: none;\n          color: rgba(0, 0, 0, 0.7); }\n    .button.is-danger-dark {\n      background-color: #f14668;\n      border-color: transparent;\n      color: #fff; }\n      .button.is-danger-dark:hover, .button.is-danger-dark.is-hovered {\n        background-color: #f03a5f;\n        border-color: transparent;\n        color: #fff; }\n      .button.is-danger-dark:focus, .button.is-danger-dark.is-focused {\n        border-color: transparent;\n        color: #fff; }\n        .button.is-danger-dark:focus:not(:active), .button.is-danger-dark.is-focused:not(:active) {\n          box-shadow: 0 0 0 0.125em rgba(241, 70, 104, 0.25); }\n      .button.is-danger-dark:active, .button.is-danger-dark.is-active {\n        background-color: #ef2e55;\n        border-color: transparent;\n        color: #fff; }\n      .button.is-danger-dark[disabled], fieldset[disabled] .button.is-danger-dark {\n        background-color: #f14668;\n        border-color: transparent;\n        box-shadow: none; }\n      .button.is-danger-dark.is-inverted {\n        background-color: #fff;\n        color: #f14668; }\n        .button.is-danger-dark.is-inverted:hover {\n          background-color: #f2f2f2; }\n        .button.is-danger-dark.is-inverted[disabled], fieldset[disabled] .button.is-danger-dark.is-inverted {\n          background-color: #fff;\n          border-color: transparent;\n          box-shadow: none;\n          color: #f14668; }\n      .button.is-danger-dark.is-loading::after {\n        border-color: transparent transparent #fff #fff !important; }\n      .button.is-danger-dark.is-outlined {\n        background-color: transparent;\n        border-color: #f14668;\n        color: #f14668; }\n        .button.is-danger-dark.is-outlined:hover, .button.is-danger-dark.is-outlined:focus {\n          background-color: #f14668;\n          border-color: #f14668;\n          color: #fff; }\n        .button.is-danger-dark.is-outlined.is-loading::after {\n          border-color: transparent transparent #f14668 #f14668 !important; }\n        .button.is-danger-dark.is-outlined[disabled], fieldset[disabled] .button.is-danger-dark.is-outlined {\n          background-color: transparent;\n          border-color: #f14668;\n          box-shadow: none;\n          color: #f14668; }\n      .button.is-danger-dark.is-inverted.is-outlined {\n        background-color: transparent;\n        border-color: #fff;\n        color: #fff; }\n        .button.is-danger-dark.is-inverted.is-outlined:hover, .button.is-danger-dark.is-inverted.is-outlined:focus {\n          background-color: #fff;\n          color: #f14668; }\n        .button.is-danger-dark.is-inverted.is-outlined[disabled], fieldset[disabled] .button.is-danger-dark.is-inverted.is-outlined {\n          background-color: transparent;\n          border-color: #fff;\n          box-shadow: none;\n          color: #fff; }\n    .button[disabled], fieldset[disabled] .button {\n      background-color: #0a0a0a;\n      border-color: #363636; }\n    .button.is-static {\n      background-color: whitesmoke;\n      border-color: #363636;\n      color: #7a7a7a; }\n  .content h1,\n  .content h2,\n  .content h3,\n  .content h4,\n  .content h5,\n  .content h6 {\n    color: #dbdbdb; }\n  .content blockquote {\n    background-color: #242424;\n    border-left: 5px solid #363636; }\n  .content table td,\n  .content table th {\n    border: 1px solid #363636; }\n  .content table th {\n    color: #dbdbdb; }\n  .content table thead td,\n  .content table thead th {\n    color: #dbdbdb; }\n  .content table tfoot td,\n  .content table tfoot th {\n    color: #dbdbdb; }\n  .input,\n  .textarea {\n    background-color: #0a0a0a;\n    border-color: #363636;\n    color: #dbdbdb;\n    box-shadow: inset 0 1px 2px rgba(255, 255, 255, 0.1); }\n    .input::-moz-placeholder,\n    .textarea::-moz-placeholder {\n      color: rgba(219, 219, 219, 0.3); }\n    .input::-webkit-input-placeholder,\n    .textarea::-webkit-input-placeholder {\n      color: rgba(219, 219, 219, 0.3); }\n    .input:-moz-placeholder,\n    .textarea:-moz-placeholder {\n      color: rgba(219, 219, 219, 0.3); }\n    .input:-ms-input-placeholder,\n    .textarea:-ms-input-placeholder {\n      color: rgba(219, 219, 219, 0.3); }\n    .input:hover, .input.is-hovered,\n    .textarea:hover,\n    .textarea.is-hovered {\n      border-color: #4a4a4a; }\n    .input:focus, .input.is-focused, .input:active, .input.is-active,\n    .textarea:focus,\n    .textarea.is-focused,\n    .textarea:active,\n    .textarea.is-active {\n      border-color: #e60056;\n      box-shadow: 0 0 0 0.125em rgba(230, 0, 86, 0.25); }\n    .input[disabled], fieldset[disabled] .input,\n    .textarea[disabled], fieldset[disabled] .textarea {\n      background-color: #242424;\n      border-color: #242424;\n      color: #b5b5b5; }\n      .input[disabled]::-moz-placeholder, fieldset[disabled] .input::-moz-placeholder,\n      .textarea[disabled]::-moz-placeholder, fieldset[disabled] .textarea::-moz-placeholder {\n        color: rgba(181, 181, 181, 0.3); }\n      .input[disabled]::-webkit-input-placeholder, fieldset[disabled] .input::-webkit-input-placeholder,\n      .textarea[disabled]::-webkit-input-placeholder, fieldset[disabled] .textarea::-webkit-input-placeholder {\n        color: rgba(181, 181, 181, 0.3); }\n      .input[disabled]:-moz-placeholder, fieldset[disabled] .input:-moz-placeholder,\n      .textarea[disabled]:-moz-placeholder, fieldset[disabled] .textarea:-moz-placeholder {\n        color: rgba(181, 181, 181, 0.3); }\n      .input[disabled]:-ms-input-placeholder, fieldset[disabled] .input:-ms-input-placeholder,\n      .textarea[disabled]:-ms-input-placeholder, fieldset[disabled] .textarea:-ms-input-placeholder {\n        color: rgba(181, 181, 181, 0.3); }\n    .input.is-white,\n    .textarea.is-white {\n      border-color: #e6e6e6; }\n      .input.is-white:focus, .input.is-white.is-focused, .input.is-white:active, .input.is-white.is-active,\n      .textarea.is-white:focus,\n      .textarea.is-white.is-focused,\n      .textarea.is-white:active,\n      .textarea.is-white.is-active {\n        box-shadow: 0 0 0 0.125em rgba(230, 230, 230, 0.25); }\n    .input.is-black,\n    .textarea.is-black {\n      border-color: black; }\n      .input.is-black:focus, .input.is-black.is-focused, .input.is-black:active, .input.is-black.is-active,\n      .textarea.is-black:focus,\n      .textarea.is-black.is-focused,\n      .textarea.is-black:active,\n      .textarea.is-black.is-active {\n        box-shadow: 0 0 0 0.125em rgba(0, 0, 0, 0.25); }\n    .input.is-light,\n    .textarea.is-light {\n      border-color: #dbdbdb; }\n      .input.is-light:focus, .input.is-light.is-focused, .input.is-light:active, .input.is-light.is-active,\n      .textarea.is-light:focus,\n      .textarea.is-light.is-focused,\n      .textarea.is-light:active,\n      .textarea.is-light.is-active {\n        box-shadow: 0 0 0 0.125em rgba(219, 219, 219, 0.25); }\n    .input.is-dark,\n    .textarea.is-dark {\n      border-color: #1c1c1c; }\n      .input.is-dark:focus, .input.is-dark.is-focused, .input.is-dark:active, .input.is-dark.is-active,\n      .textarea.is-dark:focus,\n      .textarea.is-dark.is-focused,\n      .textarea.is-dark:active,\n      .textarea.is-dark.is-active {\n        box-shadow: 0 0 0 0.125em rgba(28, 28, 28, 0.25); }\n    .input.is-primary,\n    .textarea.is-primary {\n      border-color: #d90052; }\n      .input.is-primary:focus, .input.is-primary.is-focused, .input.is-primary:active, .input.is-primary.is-active,\n      .textarea.is-primary:focus,\n      .textarea.is-primary.is-focused,\n      .textarea.is-primary:active,\n      .textarea.is-primary.is-active {\n        box-shadow: 0 0 0 0.125em rgba(217, 0, 82, 0.25); }\n    .input.is-link,\n    .textarea.is-link {\n      border-color: #d90052; }\n      .input.is-link:focus, .input.is-link.is-focused, .input.is-link:active, .input.is-link.is-active,\n      .textarea.is-link:focus,\n      .textarea.is-link.is-focused,\n      .textarea.is-link:active,\n      .textarea.is-link.is-active {\n        box-shadow: 0 0 0 0.125em rgba(217, 0, 82, 0.25); }\n    .input.is-info,\n    .textarea.is-info {\n      border-color: #0075cc; }\n      .input.is-info:focus, .input.is-info.is-focused, .input.is-info:active, .input.is-info.is-active,\n      .textarea.is-info:focus,\n      .textarea.is-info.is-focused,\n      .textarea.is-info:active,\n      .textarea.is-info.is-active {\n        box-shadow: 0 0 0 0.125em rgba(0, 117, 204, 0.25); }\n    .input.is-success,\n    .textarea.is-success {\n      border-color: #11ad74; }\n      .input.is-success:focus, .input.is-success.is-focused, .input.is-success:active, .input.is-success.is-active,\n      .textarea.is-success:focus,\n      .textarea.is-success.is-focused,\n      .textarea.is-success:active,\n      .textarea.is-success.is-active {\n        box-shadow: 0 0 0 0.125em rgba(17, 173, 116, 0.25); }\n    .input.is-warning,\n    .textarea.is-warning {\n      border-color: #ccba00; }\n      .input.is-warning:focus, .input.is-warning.is-focused, .input.is-warning:active, .input.is-warning.is-active,\n      .textarea.is-warning:focus,\n      .textarea.is-warning.is-focused,\n      .textarea.is-warning:active,\n      .textarea.is-warning.is-active {\n        box-shadow: 0 0 0 0.125em rgba(204, 186, 0, 0.25); }\n    .input.is-danger,\n    .textarea.is-danger {\n      border-color: #ee1742; }\n      .input.is-danger:focus, .input.is-danger.is-focused, .input.is-danger:active, .input.is-danger.is-active,\n      .textarea.is-danger:focus,\n      .textarea.is-danger.is-focused,\n      .textarea.is-danger:active,\n      .textarea.is-danger.is-active {\n        box-shadow: 0 0 0 0.125em rgba(238, 23, 66, 0.25); }\n    .input.is-white-dark,\n    .textarea.is-white-dark {\n      border-color: white; }\n      .input.is-white-dark:focus, .input.is-white-dark.is-focused, .input.is-white-dark:active, .input.is-white-dark.is-active,\n      .textarea.is-white-dark:focus,\n      .textarea.is-white-dark.is-focused,\n      .textarea.is-white-dark:active,\n      .textarea.is-white-dark.is-active {\n        box-shadow: 0 0 0 0.125em rgba(255, 255, 255, 0.25); }\n    .input.is-black-dark,\n    .textarea.is-black-dark {\n      border-color: #0a0a0a; }\n      .input.is-black-dark:focus, .input.is-black-dark.is-focused, .input.is-black-dark:active, .input.is-black-dark.is-active,\n      .textarea.is-black-dark:focus,\n      .textarea.is-black-dark.is-focused,\n      .textarea.is-black-dark:active,\n      .textarea.is-black-dark.is-active {\n        box-shadow: 0 0 0 0.125em rgba(10, 10, 10, 0.25); }\n    .input.is-light-dark,\n    .textarea.is-light-dark {\n      border-color: whitesmoke; }\n      .input.is-light-dark:focus, .input.is-light-dark.is-focused, .input.is-light-dark:active, .input.is-light-dark.is-active,\n      .textarea.is-light-dark:focus,\n      .textarea.is-light-dark.is-focused,\n      .textarea.is-light-dark:active,\n      .textarea.is-light-dark.is-active {\n        box-shadow: 0 0 0 0.125em rgba(245, 245, 245, 0.25); }\n    .input.is-dark-dark,\n    .textarea.is-dark-dark {\n      border-color: #363636; }\n      .input.is-dark-dark:focus, .input.is-dark-dark.is-focused, .input.is-dark-dark:active, .input.is-dark-dark.is-active,\n      .textarea.is-dark-dark:focus,\n      .textarea.is-dark-dark.is-focused,\n      .textarea.is-dark-dark:active,\n      .textarea.is-dark-dark.is-active {\n        box-shadow: 0 0 0 0.125em rgba(54, 54, 54, 0.25); }\n    .input.is-primary-dark,\n    .textarea.is-primary-dark {\n      border-color: #ff0d68; }\n      .input.is-primary-dark:focus, .input.is-primary-dark.is-focused, .input.is-primary-dark:active, .input.is-primary-dark.is-active,\n      .textarea.is-primary-dark:focus,\n      .textarea.is-primary-dark.is-focused,\n      .textarea.is-primary-dark:active,\n      .textarea.is-primary-dark.is-active {\n        box-shadow: 0 0 0 0.125em rgba(255, 13, 104, 0.25); }\n    .input.is-link-dark,\n    .textarea.is-link-dark {\n      border-color: #ff0d68; }\n      .input.is-link-dark:focus, .input.is-link-dark.is-focused, .input.is-link-dark:active, .input.is-link-dark.is-active,\n      .textarea.is-link-dark:focus,\n      .textarea.is-link-dark.is-focused,\n      .textarea.is-link-dark:active,\n      .textarea.is-link-dark.is-active {\n        box-shadow: 0 0 0 0.125em rgba(255, 13, 104, 0.25); }\n    .input.is-info-dark,\n    .textarea.is-info-dark {\n      border-color: #0092FF; }\n      .input.is-info-dark:focus, .input.is-info-dark.is-focused, .input.is-info-dark:active, .input.is-info-dark.is-active,\n      .textarea.is-info-dark:focus,\n      .textarea.is-info-dark.is-focused,\n      .textarea.is-info-dark:active,\n      .textarea.is-info-dark.is-active {\n        box-shadow: 0 0 0 0.125em rgba(0, 146, 255, 0.25); }\n    .input.is-success-dark,\n    .textarea.is-success-dark {\n      border-color: #16DB93; }\n      .input.is-success-dark:focus, .input.is-success-dark.is-focused, .input.is-success-dark:active, .input.is-success-dark.is-active,\n      .textarea.is-success-dark:focus,\n      .textarea.is-success-dark.is-focused,\n      .textarea.is-success-dark:active,\n      .textarea.is-success-dark.is-active {\n        box-shadow: 0 0 0 0.125em rgba(22, 219, 147, 0.25); }\n    .input.is-warning-dark,\n    .textarea.is-warning-dark {\n      border-color: #FFE900; }\n      .input.is-warning-dark:focus, .input.is-warning-dark.is-focused, .input.is-warning-dark:active, .input.is-warning-dark.is-active,\n      .textarea.is-warning-dark:focus,\n      .textarea.is-warning-dark.is-focused,\n      .textarea.is-warning-dark:active,\n      .textarea.is-warning-dark.is-active {\n        box-shadow: 0 0 0 0.125em rgba(255, 233, 0, 0.25); }\n    .input.is-danger-dark,\n    .textarea.is-danger-dark {\n      border-color: #f14668; }\n      .input.is-danger-dark:focus, .input.is-danger-dark.is-focused, .input.is-danger-dark:active, .input.is-danger-dark.is-active,\n      .textarea.is-danger-dark:focus,\n      .textarea.is-danger-dark.is-focused,\n      .textarea.is-danger-dark:active,\n      .textarea.is-danger-dark.is-active {\n        box-shadow: 0 0 0 0.125em rgba(241, 70, 104, 0.25); }\n  .checkbox:hover,\n  .radio:hover {\n    color: #dbdbdb; }\n  .checkbox[disabled], fieldset[disabled] .checkbox,\n  .radio[disabled], fieldset[disabled] .radio {\n    color: #b5b5b5; }\n  .select:not(.is-multiple):not(.is-loading)::after {\n    border-color: #e60056; }\n  .select select {\n    background-color: #0a0a0a;\n    border-color: #363636;\n    color: #dbdbdb; }\n    .select select::-moz-placeholder {\n      color: rgba(219, 219, 219, 0.3); }\n    .select select::-webkit-input-placeholder {\n      color: rgba(219, 219, 219, 0.3); }\n    .select select:-moz-placeholder {\n      color: rgba(219, 219, 219, 0.3); }\n    .select select:-ms-input-placeholder {\n      color: rgba(219, 219, 219, 0.3); }\n    .select select:hover, .select select.is-hovered {\n      border-color: #4a4a4a; }\n    .select select:focus, .select select.is-focused, .select select:active, .select select.is-active {\n      border-color: #e60056;\n      box-shadow: 0 0 0 0.125em rgba(230, 0, 86, 0.25); }\n    .select select[disabled], fieldset[disabled] .select select {\n      background-color: #242424;\n      border-color: #242424;\n      color: #b5b5b5; }\n      .select select[disabled]::-moz-placeholder, fieldset[disabled] .select select::-moz-placeholder {\n        color: rgba(181, 181, 181, 0.3); }\n      .select select[disabled]::-webkit-input-placeholder, fieldset[disabled] .select select::-webkit-input-placeholder {\n        color: rgba(181, 181, 181, 0.3); }\n      .select select[disabled]:-moz-placeholder, fieldset[disabled] .select select:-moz-placeholder {\n        color: rgba(181, 181, 181, 0.3); }\n      .select select[disabled]:-ms-input-placeholder, fieldset[disabled] .select select:-ms-input-placeholder {\n        color: rgba(181, 181, 181, 0.3); }\n    .select select[disabled]:hover, fieldset[disabled] .select select:hover {\n      border-color: #242424; }\n    .select select option {\n      color: #dbdbdb; }\n  .select:not(.is-multiple):not(.is-loading):hover::after {\n    border-color: #dbdbdb; }\n  .select.is-white:not(:hover)::after {\n    border-color: #e6e6e6; }\n  .select.is-white select {\n    border-color: #e6e6e6; }\n    .select.is-white select:hover, .select.is-white select.is-hovered {\n      border-color: #d9d9d9; }\n    .select.is-white select:focus, .select.is-white select.is-focused, .select.is-white select:active, .select.is-white select.is-active {\n      box-shadow: 0 0 0 0.125em rgba(230, 230, 230, 0.25); }\n  .select.is-black:not(:hover)::after {\n    border-color: black; }\n  .select.is-black select {\n    border-color: black; }\n    .select.is-black select:hover, .select.is-black select.is-hovered {\n      border-color: black; }\n    .select.is-black select:focus, .select.is-black select.is-focused, .select.is-black select:active, .select.is-black select.is-active {\n      box-shadow: 0 0 0 0.125em rgba(0, 0, 0, 0.25); }\n  .select.is-light:not(:hover)::after {\n    border-color: #dbdbdb; }\n  .select.is-light select {\n    border-color: #dbdbdb; }\n    .select.is-light select:hover, .select.is-light select.is-hovered {\n      border-color: #cfcfcf; }\n    .select.is-light select:focus, .select.is-light select.is-focused, .select.is-light select:active, .select.is-light select.is-active {\n      box-shadow: 0 0 0 0.125em rgba(219, 219, 219, 0.25); }\n  .select.is-dark:not(:hover)::after {\n    border-color: #1c1c1c; }\n  .select.is-dark select {\n    border-color: #1c1c1c; }\n    .select.is-dark select:hover, .select.is-dark select.is-hovered {\n      border-color: #0f0f0f; }\n    .select.is-dark select:focus, .select.is-dark select.is-focused, .select.is-dark select:active, .select.is-dark select.is-active {\n      box-shadow: 0 0 0 0.125em rgba(28, 28, 28, 0.25); }\n  .select.is-primary:not(:hover)::after {\n    border-color: #d90052; }\n  .select.is-primary select {\n    border-color: #d90052; }\n    .select.is-primary select:hover, .select.is-primary select.is-hovered {\n      border-color: #c00048; }\n    .select.is-primary select:focus, .select.is-primary select.is-focused, .select.is-primary select:active, .select.is-primary select.is-active {\n      box-shadow: 0 0 0 0.125em rgba(217, 0, 82, 0.25); }\n  .select.is-link:not(:hover)::after {\n    border-color: #d90052; }\n  .select.is-link select {\n    border-color: #d90052; }\n    .select.is-link select:hover, .select.is-link select.is-hovered {\n      border-color: #c00048; }\n    .select.is-link select:focus, .select.is-link select.is-focused, .select.is-link select:active, .select.is-link select.is-active {\n      box-shadow: 0 0 0 0.125em rgba(217, 0, 82, 0.25); }\n  .select.is-info:not(:hover)::after {\n    border-color: #0075cc; }\n  .select.is-info select {\n    border-color: #0075cc; }\n    .select.is-info select:hover, .select.is-info select.is-hovered {\n      border-color: #0066b3; }\n    .select.is-info select:focus, .select.is-info select.is-focused, .select.is-info select:active, .select.is-info select.is-active {\n      box-shadow: 0 0 0 0.125em rgba(0, 117, 204, 0.25); }\n  .select.is-success:not(:hover)::after {\n    border-color: #11ad74; }\n  .select.is-success select {\n    border-color: #11ad74; }\n    .select.is-success select:hover, .select.is-success select.is-hovered {\n      border-color: #0f9564; }\n    .select.is-success select:focus, .select.is-success select.is-focused, .select.is-success select:active, .select.is-success select.is-active {\n      box-shadow: 0 0 0 0.125em rgba(17, 173, 116, 0.25); }\n  .select.is-warning:not(:hover)::after {\n    border-color: #ccba00; }\n  .select.is-warning select {\n    border-color: #ccba00; }\n    .select.is-warning select:hover, .select.is-warning select.is-hovered {\n      border-color: #b3a300; }\n    .select.is-warning select:focus, .select.is-warning select.is-focused, .select.is-warning select:active, .select.is-warning select.is-active {\n      box-shadow: 0 0 0 0.125em rgba(204, 186, 0, 0.25); }\n  .select.is-danger:not(:hover)::after {\n    border-color: #ee1742; }\n  .select.is-danger select {\n    border-color: #ee1742; }\n    .select.is-danger select:hover, .select.is-danger select.is-hovered {\n      border-color: #da1039; }\n    .select.is-danger select:focus, .select.is-danger select.is-focused, .select.is-danger select:active, .select.is-danger select.is-active {\n      box-shadow: 0 0 0 0.125em rgba(238, 23, 66, 0.25); }\n  .select.is-white-dark:not(:hover)::after {\n    border-color: white; }\n  .select.is-white-dark select {\n    border-color: white; }\n    .select.is-white-dark select:hover, .select.is-white-dark select.is-hovered {\n      border-color: #f2f2f2; }\n    .select.is-white-dark select:focus, .select.is-white-dark select.is-focused, .select.is-white-dark select:active, .select.is-white-dark select.is-active {\n      box-shadow: 0 0 0 0.125em rgba(255, 255, 255, 0.25); }\n  .select.is-black-dark:not(:hover)::after {\n    border-color: #0a0a0a; }\n  .select.is-black-dark select {\n    border-color: #0a0a0a; }\n    .select.is-black-dark select:hover, .select.is-black-dark select.is-hovered {\n      border-color: black; }\n    .select.is-black-dark select:focus, .select.is-black-dark select.is-focused, .select.is-black-dark select:active, .select.is-black-dark select.is-active {\n      box-shadow: 0 0 0 0.125em rgba(10, 10, 10, 0.25); }\n  .select.is-light-dark:not(:hover)::after {\n    border-color: whitesmoke; }\n  .select.is-light-dark select {\n    border-color: whitesmoke; }\n    .select.is-light-dark select:hover, .select.is-light-dark select.is-hovered {\n      border-color: #e8e8e8; }\n    .select.is-light-dark select:focus, .select.is-light-dark select.is-focused, .select.is-light-dark select:active, .select.is-light-dark select.is-active {\n      box-shadow: 0 0 0 0.125em rgba(245, 245, 245, 0.25); }\n  .select.is-dark-dark:not(:hover)::after {\n    border-color: #363636; }\n  .select.is-dark-dark select {\n    border-color: #363636; }\n    .select.is-dark-dark select:hover, .select.is-dark-dark select.is-hovered {\n      border-color: #292929; }\n    .select.is-dark-dark select:focus, .select.is-dark-dark select.is-focused, .select.is-dark-dark select:active, .select.is-dark-dark select.is-active {\n      box-shadow: 0 0 0 0.125em rgba(54, 54, 54, 0.25); }\n  .select.is-primary-dark:not(:hover)::after {\n    border-color: #ff0d68; }\n  .select.is-primary-dark select {\n    border-color: #ff0d68; }\n    .select.is-primary-dark select:hover, .select.is-primary-dark select.is-hovered {\n      border-color: #f3005b; }\n    .select.is-primary-dark select:focus, .select.is-primary-dark select.is-focused, .select.is-primary-dark select:active, .select.is-primary-dark select.is-active {\n      box-shadow: 0 0 0 0.125em rgba(255, 13, 104, 0.25); }\n  .select.is-link-dark:not(:hover)::after {\n    border-color: #ff0d68; }\n  .select.is-link-dark select {\n    border-color: #ff0d68; }\n    .select.is-link-dark select:hover, .select.is-link-dark select.is-hovered {\n      border-color: #f3005b; }\n    .select.is-link-dark select:focus, .select.is-link-dark select.is-focused, .select.is-link-dark select:active, .select.is-link-dark select.is-active {\n      box-shadow: 0 0 0 0.125em rgba(255, 13, 104, 0.25); }\n  .select.is-info-dark:not(:hover)::after {\n    border-color: #0092FF; }\n  .select.is-info-dark select {\n    border-color: #0092FF; }\n    .select.is-info-dark select:hover, .select.is-info-dark select.is-hovered {\n      border-color: #0083e6; }\n    .select.is-info-dark select:focus, .select.is-info-dark select.is-focused, .select.is-info-dark select:active, .select.is-info-dark select.is-active {\n      box-shadow: 0 0 0 0.125em rgba(0, 146, 255, 0.25); }\n  .select.is-success-dark:not(:hover)::after {\n    border-color: #16DB93; }\n  .select.is-success-dark select {\n    border-color: #16DB93; }\n    .select.is-success-dark select:hover, .select.is-success-dark select.is-hovered {\n      border-color: #14c483; }\n    .select.is-success-dark select:focus, .select.is-success-dark select.is-focused, .select.is-success-dark select:active, .select.is-success-dark select.is-active {\n      box-shadow: 0 0 0 0.125em rgba(22, 219, 147, 0.25); }\n  .select.is-warning-dark:not(:hover)::after {\n    border-color: #FFE900; }\n  .select.is-warning-dark select {\n    border-color: #FFE900; }\n    .select.is-warning-dark select:hover, .select.is-warning-dark select.is-hovered {\n      border-color: #e6d200; }\n    .select.is-warning-dark select:focus, .select.is-warning-dark select.is-focused, .select.is-warning-dark select:active, .select.is-warning-dark select.is-active {\n      box-shadow: 0 0 0 0.125em rgba(255, 233, 0, 0.25); }\n  .select.is-danger-dark:not(:hover)::after {\n    border-color: #f14668; }\n  .select.is-danger-dark select {\n    border-color: #f14668; }\n    .select.is-danger-dark select:hover, .select.is-danger-dark select.is-hovered {\n      border-color: #ef2e55; }\n    .select.is-danger-dark select:focus, .select.is-danger-dark select.is-focused, .select.is-danger-dark select:active, .select.is-danger-dark select.is-active {\n      box-shadow: 0 0 0 0.125em rgba(241, 70, 104, 0.25); }\n  .select.is-disabled::after {\n    border-color: #b5b5b5; }\n  .file.is-white .file-cta {\n    background-color: #e6e6e6;\n    color: #0a0a0a; }\n  .file.is-white:hover .file-cta, .file.is-white.is-hovered .file-cta {\n    background-color: #dfdfdf;\n    color: #0a0a0a; }\n  .file.is-white:focus .file-cta, .file.is-white.is-focused .file-cta {\n    box-shadow: 0 0 0.5em rgba(230, 230, 230, 0.25);\n    color: #0a0a0a; }\n  .file.is-white:active .file-cta, .file.is-white.is-active .file-cta {\n    background-color: #d9d9d9;\n    color: #0a0a0a; }\n  .file.is-black .file-cta {\n    background-color: black;\n    color: white; }\n  .file.is-black:hover .file-cta, .file.is-black.is-hovered .file-cta {\n    background-color: black;\n    color: white; }\n  .file.is-black:focus .file-cta, .file.is-black.is-focused .file-cta {\n    box-shadow: 0 0 0.5em rgba(0, 0, 0, 0.25);\n    color: white; }\n  .file.is-black:active .file-cta, .file.is-black.is-active .file-cta {\n    background-color: black;\n    color: white; }\n  .file.is-light .file-cta {\n    background-color: #dbdbdb;\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-light:hover .file-cta, .file.is-light.is-hovered .file-cta {\n    background-color: #d5d5d5;\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-light:focus .file-cta, .file.is-light.is-focused .file-cta {\n    box-shadow: 0 0 0.5em rgba(219, 219, 219, 0.25);\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-light:active .file-cta, .file.is-light.is-active .file-cta {\n    background-color: #cfcfcf;\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-dark .file-cta {\n    background-color: #1c1c1c;\n    color: #fff; }\n  .file.is-dark:hover .file-cta, .file.is-dark.is-hovered .file-cta {\n    background-color: #161616;\n    color: #fff; }\n  .file.is-dark:focus .file-cta, .file.is-dark.is-focused .file-cta {\n    box-shadow: 0 0 0.5em rgba(28, 28, 28, 0.25);\n    color: #fff; }\n  .file.is-dark:active .file-cta, .file.is-dark.is-active .file-cta {\n    background-color: #0f0f0f;\n    color: #fff; }\n  .file.is-primary .file-cta {\n    background-color: #d90052;\n    color: #fff; }\n  .file.is-primary:hover .file-cta, .file.is-primary.is-hovered .file-cta {\n    background-color: #cc004d;\n    color: #fff; }\n  .file.is-primary:focus .file-cta, .file.is-primary.is-focused .file-cta {\n    box-shadow: 0 0 0.5em rgba(217, 0, 82, 0.25);\n    color: #fff; }\n  .file.is-primary:active .file-cta, .file.is-primary.is-active .file-cta {\n    background-color: #c00048;\n    color: #fff; }\n  .file.is-link .file-cta {\n    background-color: #d90052;\n    color: #fff; }\n  .file.is-link:hover .file-cta, .file.is-link.is-hovered .file-cta {\n    background-color: #cc004d;\n    color: #fff; }\n  .file.is-link:focus .file-cta, .file.is-link.is-focused .file-cta {\n    box-shadow: 0 0 0.5em rgba(217, 0, 82, 0.25);\n    color: #fff; }\n  .file.is-link:active .file-cta, .file.is-link.is-active .file-cta {\n    background-color: #c00048;\n    color: #fff; }\n  .file.is-info .file-cta {\n    background-color: #0075cc;\n    color: #fff; }\n  .file.is-info:hover .file-cta, .file.is-info.is-hovered .file-cta {\n    background-color: #006ebf;\n    color: #fff; }\n  .file.is-info:focus .file-cta, .file.is-info.is-focused .file-cta {\n    box-shadow: 0 0 0.5em rgba(0, 117, 204, 0.25);\n    color: #fff; }\n  .file.is-info:active .file-cta, .file.is-info.is-active .file-cta {\n    background-color: #0066b3;\n    color: #fff; }\n  .file.is-success .file-cta {\n    background-color: #11ad74;\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-success:hover .file-cta, .file.is-success.is-hovered .file-cta {\n    background-color: #10a16c;\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-success:focus .file-cta, .file.is-success.is-focused .file-cta {\n    box-shadow: 0 0 0.5em rgba(17, 173, 116, 0.25);\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-success:active .file-cta, .file.is-success.is-active .file-cta {\n    background-color: #0f9564;\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-warning .file-cta {\n    background-color: #ccba00;\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-warning:hover .file-cta, .file.is-warning.is-hovered .file-cta {\n    background-color: #bfaf00;\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-warning:focus .file-cta, .file.is-warning.is-focused .file-cta {\n    box-shadow: 0 0 0.5em rgba(204, 186, 0, 0.25);\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-warning:active .file-cta, .file.is-warning.is-active .file-cta {\n    background-color: #b3a300;\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-danger .file-cta {\n    background-color: #ee1742;\n    color: #fff; }\n  .file.is-danger:hover .file-cta, .file.is-danger.is-hovered .file-cta {\n    background-color: #e6113c;\n    color: #fff; }\n  .file.is-danger:focus .file-cta, .file.is-danger.is-focused .file-cta {\n    box-shadow: 0 0 0.5em rgba(238, 23, 66, 0.25);\n    color: #fff; }\n  .file.is-danger:active .file-cta, .file.is-danger.is-active .file-cta {\n    background-color: #da1039;\n    color: #fff; }\n  .file.is-white-dark .file-cta {\n    background-color: white;\n    color: #0a0a0a; }\n  .file.is-white-dark:hover .file-cta, .file.is-white-dark.is-hovered .file-cta {\n    background-color: #f9f9f9;\n    color: #0a0a0a; }\n  .file.is-white-dark:focus .file-cta, .file.is-white-dark.is-focused .file-cta {\n    box-shadow: 0 0 0.5em rgba(255, 255, 255, 0.25);\n    color: #0a0a0a; }\n  .file.is-white-dark:active .file-cta, .file.is-white-dark.is-active .file-cta {\n    background-color: #f2f2f2;\n    color: #0a0a0a; }\n  .file.is-black-dark .file-cta {\n    background-color: #0a0a0a;\n    color: white; }\n  .file.is-black-dark:hover .file-cta, .file.is-black-dark.is-hovered .file-cta {\n    background-color: #040404;\n    color: white; }\n  .file.is-black-dark:focus .file-cta, .file.is-black-dark.is-focused .file-cta {\n    box-shadow: 0 0 0.5em rgba(10, 10, 10, 0.25);\n    color: white; }\n  .file.is-black-dark:active .file-cta, .file.is-black-dark.is-active .file-cta {\n    background-color: black;\n    color: white; }\n  .file.is-light-dark .file-cta {\n    background-color: whitesmoke;\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-light-dark:hover .file-cta, .file.is-light-dark.is-hovered .file-cta {\n    background-color: #eeeeee;\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-light-dark:focus .file-cta, .file.is-light-dark.is-focused .file-cta {\n    box-shadow: 0 0 0.5em rgba(245, 245, 245, 0.25);\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-light-dark:active .file-cta, .file.is-light-dark.is-active .file-cta {\n    background-color: #e8e8e8;\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-dark-dark .file-cta {\n    background-color: #363636;\n    color: #fff; }\n  .file.is-dark-dark:hover .file-cta, .file.is-dark-dark.is-hovered .file-cta {\n    background-color: #2f2f2f;\n    color: #fff; }\n  .file.is-dark-dark:focus .file-cta, .file.is-dark-dark.is-focused .file-cta {\n    box-shadow: 0 0 0.5em rgba(54, 54, 54, 0.25);\n    color: #fff; }\n  .file.is-dark-dark:active .file-cta, .file.is-dark-dark.is-active .file-cta {\n    background-color: #292929;\n    color: #fff; }\n  .file.is-primary-dark .file-cta {\n    background-color: #ff0d68;\n    color: #fff; }\n  .file.is-primary-dark:hover .file-cta, .file.is-primary-dark.is-hovered .file-cta {\n    background-color: #ff0060;\n    color: #fff; }\n  .file.is-primary-dark:focus .file-cta, .file.is-primary-dark.is-focused .file-cta {\n    box-shadow: 0 0 0.5em rgba(255, 13, 104, 0.25);\n    color: #fff; }\n  .file.is-primary-dark:active .file-cta, .file.is-primary-dark.is-active .file-cta {\n    background-color: #f3005b;\n    color: #fff; }\n  .file.is-link-dark .file-cta {\n    background-color: #ff0d68;\n    color: #fff; }\n  .file.is-link-dark:hover .file-cta, .file.is-link-dark.is-hovered .file-cta {\n    background-color: #ff0060;\n    color: #fff; }\n  .file.is-link-dark:focus .file-cta, .file.is-link-dark.is-focused .file-cta {\n    box-shadow: 0 0 0.5em rgba(255, 13, 104, 0.25);\n    color: #fff; }\n  .file.is-link-dark:active .file-cta, .file.is-link-dark.is-active .file-cta {\n    background-color: #f3005b;\n    color: #fff; }\n  .file.is-info-dark .file-cta {\n    background-color: #0092FF;\n    color: #fff; }\n  .file.is-info-dark:hover .file-cta, .file.is-info-dark.is-hovered .file-cta {\n    background-color: #008bf2;\n    color: #fff; }\n  .file.is-info-dark:focus .file-cta, .file.is-info-dark.is-focused .file-cta {\n    box-shadow: 0 0 0.5em rgba(0, 146, 255, 0.25);\n    color: #fff; }\n  .file.is-info-dark:active .file-cta, .file.is-info-dark.is-active .file-cta {\n    background-color: #0083e6;\n    color: #fff; }\n  .file.is-success-dark .file-cta {\n    background-color: #16DB93;\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-success-dark:hover .file-cta, .file.is-success-dark.is-hovered .file-cta {\n    background-color: #15cf8b;\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-success-dark:focus .file-cta, .file.is-success-dark.is-focused .file-cta {\n    box-shadow: 0 0 0.5em rgba(22, 219, 147, 0.25);\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-success-dark:active .file-cta, .file.is-success-dark.is-active .file-cta {\n    background-color: #14c483;\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-warning-dark .file-cta {\n    background-color: #FFE900;\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-warning-dark:hover .file-cta, .file.is-warning-dark.is-hovered .file-cta {\n    background-color: #f2dd00;\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-warning-dark:focus .file-cta, .file.is-warning-dark.is-focused .file-cta {\n    box-shadow: 0 0 0.5em rgba(255, 233, 0, 0.25);\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-warning-dark:active .file-cta, .file.is-warning-dark.is-active .file-cta {\n    background-color: #e6d200;\n    color: rgba(0, 0, 0, 0.7); }\n  .file.is-danger-dark .file-cta {\n    background-color: #f14668;\n    color: #fff; }\n  .file.is-danger-dark:hover .file-cta, .file.is-danger-dark.is-hovered .file-cta {\n    background-color: #f03a5f;\n    color: #fff; }\n  .file.is-danger-dark:focus .file-cta, .file.is-danger-dark.is-focused .file-cta {\n    box-shadow: 0 0 0.5em rgba(241, 70, 104, 0.25);\n    color: #fff; }\n  .file.is-danger-dark:active .file-cta, .file.is-danger-dark.is-active .file-cta {\n    background-color: #ef2e55;\n    color: #fff; }\n  .file-label:hover .file-cta {\n    background-color: #1d1d1d;\n    color: #dbdbdb; }\n  .file-label:hover .file-name {\n    border-color: #2f2f2f; }\n  .file-label:active .file-cta {\n    background-color: #171717;\n    color: #dbdbdb; }\n  .file-label:active .file-name {\n    border-color: #292929; }\n  .file-cta,\n  .file-name {\n    border-color: #363636; }\n  .file-cta {\n    background-color: #242424;\n    color: #b5b5b5; }\n  .file-name {\n    border-color: #363636; }\n  .label {\n    color: #dbdbdb; }\n  .help.is-white {\n    color: #e6e6e6; }\n  .help.is-black {\n    color: black; }\n  .help.is-light {\n    color: #dbdbdb; }\n  .help.is-dark {\n    color: #1c1c1c; }\n  .help.is-primary {\n    color: #d90052; }\n  .help.is-link {\n    color: #d90052; }\n  .help.is-info {\n    color: #0075cc; }\n  .help.is-success {\n    color: #11ad74; }\n  .help.is-warning {\n    color: #ccba00; }\n  .help.is-danger {\n    color: #ee1742; }\n  .help.is-white-dark {\n    color: white; }\n  .help.is-black-dark {\n    color: #0a0a0a; }\n  .help.is-light-dark {\n    color: whitesmoke; }\n  .help.is-dark-dark {\n    color: #363636; }\n  .help.is-primary-dark {\n    color: #ff0d68; }\n  .help.is-link-dark {\n    color: #ff0d68; }\n  .help.is-info-dark {\n    color: #0092FF; }\n  .help.is-success-dark {\n    color: #16DB93; }\n  .help.is-warning-dark {\n    color: #FFE900; }\n  .help.is-danger-dark {\n    color: #f14668; }\n  .control.has-icons-left .icon, .control.has-icons-right .icon {\n    color: #363636; }\n  .notification {\n    background-color: #242424; }\n    .notification code,\n    .notification pre {\n      background: #0a0a0a; }\n    .notification.is-white {\n      background-color: #e6e6e6;\n      color: #0a0a0a; }\n    .notification.is-black {\n      background-color: black;\n      color: white; }\n    .notification.is-light {\n      background-color: #dbdbdb;\n      color: rgba(0, 0, 0, 0.7); }\n    .notification.is-dark {\n      background-color: #1c1c1c;\n      color: #fff; }\n    .notification.is-primary {\n      background-color: #d90052;\n      color: #fff; }\n    .notification.is-link {\n      background-color: #d90052;\n      color: #fff; }\n    .notification.is-info {\n      background-color: #0075cc;\n      color: #fff; }\n    .notification.is-success {\n      background-color: #11ad74;\n      color: rgba(0, 0, 0, 0.7); }\n    .notification.is-warning {\n      background-color: #ccba00;\n      color: rgba(0, 0, 0, 0.7); }\n    .notification.is-danger {\n      background-color: #ee1742;\n      color: #fff; }\n    .notification.is-white-dark {\n      background-color: white;\n      color: #0a0a0a; }\n    .notification.is-black-dark {\n      background-color: #0a0a0a;\n      color: white; }\n    .notification.is-light-dark {\n      background-color: whitesmoke;\n      color: rgba(0, 0, 0, 0.7); }\n    .notification.is-dark-dark {\n      background-color: #363636;\n      color: #fff; }\n    .notification.is-primary-dark {\n      background-color: #ff0d68;\n      color: #fff; }\n    .notification.is-link-dark {\n      background-color: #ff0d68;\n      color: #fff; }\n    .notification.is-info-dark {\n      background-color: #0092FF;\n      color: #fff; }\n    .notification.is-success-dark {\n      background-color: #16DB93;\n      color: rgba(0, 0, 0, 0.7); }\n    .notification.is-warning-dark {\n      background-color: #FFE900;\n      color: rgba(0, 0, 0, 0.7); }\n    .notification.is-danger-dark {\n      background-color: #f14668;\n      color: #fff; }\n  .progress::-webkit-progress-bar {\n    background-color: #363636; }\n  .progress::-webkit-progress-value {\n    background-color: #b5b5b5; }\n  .progress::-moz-progress-bar {\n    background-color: #b5b5b5; }\n  .progress::-ms-fill {\n    background-color: #b5b5b5; }\n  .progress:indeterminate {\n    background-color: #363636;\n    background-image: linear-gradient(to right, #4a4a4a 30%, #363636 30%); }\n  .progress.is-white::-webkit-progress-value {\n    background-color: #e6e6e6; }\n  .progress.is-white::-moz-progress-bar {\n    background-color: #e6e6e6; }\n  .progress.is-white::-ms-fill {\n    background-color: #e6e6e6; }\n  .progress.is-white:indeterminate {\n    background-image: linear-gradient(to right, #e6e6e6 30%, #363636 30%); }\n  .progress.is-black::-webkit-progress-value {\n    background-color: black; }\n  .progress.is-black::-moz-progress-bar {\n    background-color: black; }\n  .progress.is-black::-ms-fill {\n    background-color: black; }\n  .progress.is-black:indeterminate {\n    background-image: linear-gradient(to right, black 30%, #363636 30%); }\n  .progress.is-light::-webkit-progress-value {\n    background-color: #dbdbdb; }\n  .progress.is-light::-moz-progress-bar {\n    background-color: #dbdbdb; }\n  .progress.is-light::-ms-fill {\n    background-color: #dbdbdb; }\n  .progress.is-light:indeterminate {\n    background-image: linear-gradient(to right, #dbdbdb 30%, #363636 30%); }\n  .progress.is-dark::-webkit-progress-value {\n    background-color: #1c1c1c; }\n  .progress.is-dark::-moz-progress-bar {\n    background-color: #1c1c1c; }\n  .progress.is-dark::-ms-fill {\n    background-color: #1c1c1c; }\n  .progress.is-dark:indeterminate {\n    background-image: linear-gradient(to right, #1c1c1c 30%, #363636 30%); }\n  .progress.is-primary::-webkit-progress-value {\n    background-color: #d90052; }\n  .progress.is-primary::-moz-progress-bar {\n    background-color: #d90052; }\n  .progress.is-primary::-ms-fill {\n    background-color: #d90052; }\n  .progress.is-primary:indeterminate {\n    background-image: linear-gradient(to right, #d90052 30%, #363636 30%); }\n  .progress.is-link::-webkit-progress-value {\n    background-color: #d90052; }\n  .progress.is-link::-moz-progress-bar {\n    background-color: #d90052; }\n  .progress.is-link::-ms-fill {\n    background-color: #d90052; }\n  .progress.is-link:indeterminate {\n    background-image: linear-gradient(to right, #d90052 30%, #363636 30%); }\n  .progress.is-info::-webkit-progress-value {\n    background-color: #0075cc; }\n  .progress.is-info::-moz-progress-bar {\n    background-color: #0075cc; }\n  .progress.is-info::-ms-fill {\n    background-color: #0075cc; }\n  .progress.is-info:indeterminate {\n    background-image: linear-gradient(to right, #0075cc 30%, #363636 30%); }\n  .progress.is-success::-webkit-progress-value {\n    background-color: #11ad74; }\n  .progress.is-success::-moz-progress-bar {\n    background-color: #11ad74; }\n  .progress.is-success::-ms-fill {\n    background-color: #11ad74; }\n  .progress.is-success:indeterminate {\n    background-image: linear-gradient(to right, #11ad74 30%, #363636 30%); }\n  .progress.is-warning::-webkit-progress-value {\n    background-color: #ccba00; }\n  .progress.is-warning::-moz-progress-bar {\n    background-color: #ccba00; }\n  .progress.is-warning::-ms-fill {\n    background-color: #ccba00; }\n  .progress.is-warning:indeterminate {\n    background-image: linear-gradient(to right, #ccba00 30%, #363636 30%); }\n  .progress.is-danger::-webkit-progress-value {\n    background-color: #ee1742; }\n  .progress.is-danger::-moz-progress-bar {\n    background-color: #ee1742; }\n  .progress.is-danger::-ms-fill {\n    background-color: #ee1742; }\n  .progress.is-danger:indeterminate {\n    background-image: linear-gradient(to right, #ee1742 30%, #363636 30%); }\n  .progress.is-white-dark::-webkit-progress-value {\n    background-color: white; }\n  .progress.is-white-dark::-moz-progress-bar {\n    background-color: white; }\n  .progress.is-white-dark::-ms-fill {\n    background-color: white; }\n  .progress.is-white-dark:indeterminate {\n    background-image: linear-gradient(to right, white 30%, #363636 30%); }\n  .progress.is-black-dark::-webkit-progress-value {\n    background-color: #0a0a0a; }\n  .progress.is-black-dark::-moz-progress-bar {\n    background-color: #0a0a0a; }\n  .progress.is-black-dark::-ms-fill {\n    background-color: #0a0a0a; }\n  .progress.is-black-dark:indeterminate {\n    background-image: linear-gradient(to right, #0a0a0a 30%, #363636 30%); }\n  .progress.is-light-dark::-webkit-progress-value {\n    background-color: whitesmoke; }\n  .progress.is-light-dark::-moz-progress-bar {\n    background-color: whitesmoke; }\n  .progress.is-light-dark::-ms-fill {\n    background-color: whitesmoke; }\n  .progress.is-light-dark:indeterminate {\n    background-image: linear-gradient(to right, whitesmoke 30%, #363636 30%); }\n  .progress.is-dark-dark::-webkit-progress-value {\n    background-color: #363636; }\n  .progress.is-dark-dark::-moz-progress-bar {\n    background-color: #363636; }\n  .progress.is-dark-dark::-ms-fill {\n    background-color: #363636; }\n  .progress.is-dark-dark:indeterminate {\n    background-image: linear-gradient(to right, #363636 30%, #363636 30%); }\n  .progress.is-primary-dark::-webkit-progress-value {\n    background-color: #ff0d68; }\n  .progress.is-primary-dark::-moz-progress-bar {\n    background-color: #ff0d68; }\n  .progress.is-primary-dark::-ms-fill {\n    background-color: #ff0d68; }\n  .progress.is-primary-dark:indeterminate {\n    background-image: linear-gradient(to right, #ff0d68 30%, #363636 30%); }\n  .progress.is-link-dark::-webkit-progress-value {\n    background-color: #ff0d68; }\n  .progress.is-link-dark::-moz-progress-bar {\n    background-color: #ff0d68; }\n  .progress.is-link-dark::-ms-fill {\n    background-color: #ff0d68; }\n  .progress.is-link-dark:indeterminate {\n    background-image: linear-gradient(to right, #ff0d68 30%, #363636 30%); }\n  .progress.is-info-dark::-webkit-progress-value {\n    background-color: #0092FF; }\n  .progress.is-info-dark::-moz-progress-bar {\n    background-color: #0092FF; }\n  .progress.is-info-dark::-ms-fill {\n    background-color: #0092FF; }\n  .progress.is-info-dark:indeterminate {\n    background-image: linear-gradient(to right, #0092FF 30%, #363636 30%); }\n  .progress.is-success-dark::-webkit-progress-value {\n    background-color: #16DB93; }\n  .progress.is-success-dark::-moz-progress-bar {\n    background-color: #16DB93; }\n  .progress.is-success-dark::-ms-fill {\n    background-color: #16DB93; }\n  .progress.is-success-dark:indeterminate {\n    background-image: linear-gradient(to right, #16DB93 30%, #363636 30%); }\n  .progress.is-warning-dark::-webkit-progress-value {\n    background-color: #FFE900; }\n  .progress.is-warning-dark::-moz-progress-bar {\n    background-color: #FFE900; }\n  .progress.is-warning-dark::-ms-fill {\n    background-color: #FFE900; }\n  .progress.is-warning-dark:indeterminate {\n    background-image: linear-gradient(to right, #FFE900 30%, #363636 30%); }\n  .progress.is-danger-dark::-webkit-progress-value {\n    background-color: #f14668; }\n  .progress.is-danger-dark::-moz-progress-bar {\n    background-color: #f14668; }\n  .progress.is-danger-dark::-ms-fill {\n    background-color: #f14668; }\n  .progress.is-danger-dark:indeterminate {\n    background-image: linear-gradient(to right, #f14668 30%, #363636 30%); }\n  .table {\n    background-color: #0a0a0a;\n    color: #dbdbdb; }\n    .table td,\n    .table th {\n      border: 1px solid #363636; }\n      .table td.is-white,\n      .table th.is-white {\n        background-color: #e6e6e6;\n        border-color: #e6e6e6;\n        color: #0a0a0a; }\n      .table td.is-black,\n      .table th.is-black {\n        background-color: black;\n        border-color: black;\n        color: white; }\n      .table td.is-light,\n      .table th.is-light {\n        background-color: #dbdbdb;\n        border-color: #dbdbdb;\n        color: rgba(0, 0, 0, 0.7); }\n      .table td.is-dark,\n      .table th.is-dark {\n        background-color: #1c1c1c;\n        border-color: #1c1c1c;\n        color: #fff; }\n      .table td.is-primary,\n      .table th.is-primary {\n        background-color: #d90052;\n        border-color: #d90052;\n        color: #fff; }\n      .table td.is-link,\n      .table th.is-link {\n        background-color: #d90052;\n        border-color: #d90052;\n        color: #fff; }\n      .table td.is-info,\n      .table th.is-info {\n        background-color: #0075cc;\n        border-color: #0075cc;\n        color: #fff; }\n      .table td.is-success,\n      .table th.is-success {\n        background-color: #11ad74;\n        border-color: #11ad74;\n        color: rgba(0, 0, 0, 0.7); }\n      .table td.is-warning,\n      .table th.is-warning {\n        background-color: #ccba00;\n        border-color: #ccba00;\n        color: rgba(0, 0, 0, 0.7); }\n      .table td.is-danger,\n      .table th.is-danger {\n        background-color: #ee1742;\n        border-color: #ee1742;\n        color: #fff; }\n      .table td.is-white-dark,\n      .table th.is-white-dark {\n        background-color: white;\n        border-color: white;\n        color: #0a0a0a; }\n      .table td.is-black-dark,\n      .table th.is-black-dark {\n        background-color: #0a0a0a;\n        border-color: #0a0a0a;\n        color: white; }\n      .table td.is-light-dark,\n      .table th.is-light-dark {\n        background-color: whitesmoke;\n        border-color: whitesmoke;\n        color: rgba(0, 0, 0, 0.7); }\n      .table td.is-dark-dark,\n      .table th.is-dark-dark {\n        background-color: #363636;\n        border-color: #363636;\n        color: #fff; }\n      .table td.is-primary-dark,\n      .table th.is-primary-dark {\n        background-color: #ff0d68;\n        border-color: #ff0d68;\n        color: #fff; }\n      .table td.is-link-dark,\n      .table th.is-link-dark {\n        background-color: #ff0d68;\n        border-color: #ff0d68;\n        color: #fff; }\n      .table td.is-info-dark,\n      .table th.is-info-dark {\n        background-color: #0092FF;\n        border-color: #0092FF;\n        color: #fff; }\n      .table td.is-success-dark,\n      .table th.is-success-dark {\n        background-color: #16DB93;\n        border-color: #16DB93;\n        color: rgba(0, 0, 0, 0.7); }\n      .table td.is-warning-dark,\n      .table th.is-warning-dark {\n        background-color: #FFE900;\n        border-color: #FFE900;\n        color: rgba(0, 0, 0, 0.7); }\n      .table td.is-danger-dark,\n      .table th.is-danger-dark {\n        background-color: #f14668;\n        border-color: #f14668;\n        color: #fff; }\n      .table td.is-selected,\n      .table th.is-selected {\n        background-color: #e60056;\n        color: #e6e6e6; }\n    .table th {\n      color: #dbdbdb; }\n    .table tr.is-selected {\n      background-color: #e60056;\n      color: #e6e6e6; }\n      .table tr.is-selected td,\n      .table tr.is-selected th {\n        border-color: #e6e6e6; }\n    .table thead td,\n    .table thead th {\n      color: #dbdbdb; }\n    .table tfoot td,\n    .table tfoot th {\n      color: #dbdbdb; }\n    .table.is-hoverable tbody tr:not(.is-selected):hover {\n      background-color: #121212; }\n    .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover {\n      background-color: #121212; }\n      .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even) {\n        background-color: #242424; }\n    .table.is-striped tbody tr:not(.is-selected):nth-child(even) {\n      background-color: #121212; }\n  .tag:not(body) {\n    background-color: #242424;\n    color: #b5b5b5; }\n    .tag:not(body).is-white {\n      background-color: #e6e6e6;\n      color: #0a0a0a; }\n    .tag:not(body).is-black {\n      background-color: black;\n      color: white; }\n    .tag:not(body).is-light {\n      background-color: #dbdbdb;\n      color: rgba(0, 0, 0, 0.7); }\n    .tag:not(body).is-dark {\n      background-color: #1c1c1c;\n      color: #fff; }\n    .tag:not(body).is-primary {\n      background-color: #d90052;\n      color: #fff; }\n    .tag:not(body).is-link {\n      background-color: #d90052;\n      color: #fff; }\n    .tag:not(body).is-info {\n      background-color: #0075cc;\n      color: #fff; }\n    .tag:not(body).is-success {\n      background-color: #11ad74;\n      color: rgba(0, 0, 0, 0.7); }\n    .tag:not(body).is-warning {\n      background-color: #ccba00;\n      color: rgba(0, 0, 0, 0.7); }\n    .tag:not(body).is-danger {\n      background-color: #ee1742;\n      color: #fff; }\n    .tag:not(body).is-white-dark {\n      background-color: white;\n      color: #0a0a0a; }\n    .tag:not(body).is-black-dark {\n      background-color: #0a0a0a;\n      color: white; }\n    .tag:not(body).is-light-dark {\n      background-color: whitesmoke;\n      color: rgba(0, 0, 0, 0.7); }\n    .tag:not(body).is-dark-dark {\n      background-color: #363636;\n      color: #fff; }\n    .tag:not(body).is-primary-dark {\n      background-color: #ff0d68;\n      color: #fff; }\n    .tag:not(body).is-link-dark {\n      background-color: #ff0d68;\n      color: #fff; }\n    .tag:not(body).is-info-dark {\n      background-color: #0092FF;\n      color: #fff; }\n    .tag:not(body).is-success-dark {\n      background-color: #16DB93;\n      color: rgba(0, 0, 0, 0.7); }\n    .tag:not(body).is-warning-dark {\n      background-color: #FFE900;\n      color: rgba(0, 0, 0, 0.7); }\n    .tag:not(body).is-danger-dark {\n      background-color: #f14668;\n      color: #fff; }\n    .tag:not(body).is-delete:hover, .tag:not(body).is-delete:focus {\n      background-color: #171717; }\n    .tag:not(body).is-delete:active {\n      background-color: #0a0a0a; }\n  .title {\n    color: #dbdbdb; }\n  .subtitle {\n    color: #b5b5b5; }\n    .subtitle strong {\n      color: #dbdbdb; }\n  .number {\n    background-color: #242424; }\n  .breadcrumb a {\n    color: #e60056; }\n    .breadcrumb a:hover {\n      color: #dbdbdb; }\n  .breadcrumb li.is-active a {\n    color: #dbdbdb; }\n  .breadcrumb li + li::before {\n    color: #4a4a4a; }\n  .card {\n    background-color: #0a0a0a;\n    box-shadow: 0 2px 3px rgba(255, 255, 255, 0.1), 0 0 0 1px rgba(255, 255, 255, 0.1);\n    color: #b5b5b5; }\n  .card-header {\n    box-shadow: 0 1px 2px rgba(255, 255, 255, 0.1); }\n  .card-header-title {\n    color: #dbdbdb; }\n  .card-footer {\n    border-top: 1px solid #363636; }\n  .card-footer-item:not(:last-child) {\n    border-right: 1px solid #363636; }\n  .dropdown-content {\n    background-color: #0a0a0a;\n    box-shadow: 0 2px 3px rgba(255, 255, 255, 0.1), 0 0 0 1px rgba(255, 255, 255, 0.1); }\n  .dropdown-item {\n    color: #b5b5b5; }\n  a.dropdown-item:hover,\n  button.dropdown-item:hover {\n    background-color: #242424;\n    color: white; }\n  a.dropdown-item.is-active,\n  button.dropdown-item.is-active {\n    background-color: #e60056;\n    color: #fff; }\n  .dropdown-divider {\n    background-color: #363636; }\n  .list {\n    background-color: #0a0a0a;\n    box-shadow: 0 2px 3px rgba(255, 255, 255, 0.1), 0 0 0 1px rgba(255, 255, 255, 0.1); }\n  .list-item:not(a) {\n    color: #b5b5b5; }\n  .list-item:not(:last-child) {\n    border-bottom: 1px solid #363636; }\n  .list-item.is-active {\n    background-color: #e60056;\n    color: #fff; }\n  a.list-item {\n    background-color: #242424; }\n  .media .media {\n    border-top: 1px solid rgba(54, 54, 54, 0.5); }\n  .media + .media {\n    border-top: 1px solid rgba(54, 54, 54, 0.5); }\n  .menu-list a {\n    color: #b5b5b5; }\n    .menu-list a:hover {\n      background-color: #242424;\n      color: #dbdbdb; }\n    .menu-list a.is-active {\n      background-color: #e60056;\n      color: #fff; }\n  .menu-list li ul {\n    border-left: 1px solid #363636; }\n  .message {\n    background-color: #242424; }\n    .message.is-white {\n      background-color: #242424; }\n      .message.is-white .message-header {\n        background-color: white;\n        color: #0a0a0a; }\n      .message.is-white .message-body {\n        border-color: white;\n        color: #b5b5b5; }\n    .message.is-black {\n      background-color: #242424; }\n      .message.is-black .message-header {\n        background-color: #0a0a0a;\n        color: white; }\n      .message.is-black .message-body {\n        border-color: #0a0a0a;\n        color: #b5b5b5; }\n    .message.is-light {\n      background-color: #242424; }\n      .message.is-light .message-header {\n        background-color: whitesmoke;\n        color: rgba(0, 0, 0, 0.7); }\n      .message.is-light .message-body {\n        border-color: whitesmoke;\n        color: #b5b5b5; }\n    .message.is-dark {\n      background-color: #242424; }\n      .message.is-dark .message-header {\n        background-color: #363636;\n        color: #fff; }\n      .message.is-dark .message-body {\n        border-color: #363636;\n        color: #b5b5b5; }\n    .message.is-primary {\n      background-color: #242424; }\n      .message.is-primary .message-header {\n        background-color: #ff0d68;\n        color: #fff; }\n      .message.is-primary .message-body {\n        border-color: #ff0d68;\n        color: #b5b5b5; }\n    .message.is-link {\n      background-color: #242424; }\n      .message.is-link .message-header {\n        background-color: #ff0d68;\n        color: #fff; }\n      .message.is-link .message-body {\n        border-color: #ff0d68;\n        color: #b5b5b5; }\n    .message.is-info {\n      background-color: #242424; }\n      .message.is-info .message-header {\n        background-color: #0092FF;\n        color: #fff; }\n      .message.is-info .message-body {\n        border-color: #0092FF;\n        color: #b5b5b5; }\n    .message.is-success {\n      background-color: #242424; }\n      .message.is-success .message-header {\n        background-color: #16DB93;\n        color: rgba(0, 0, 0, 0.7); }\n      .message.is-success .message-body {\n        border-color: #16DB93;\n        color: #b5b5b5; }\n    .message.is-warning {\n      background-color: #242424; }\n      .message.is-warning .message-header {\n        background-color: #FFE900;\n        color: rgba(0, 0, 0, 0.7); }\n      .message.is-warning .message-body {\n        border-color: #FFE900;\n        color: #b5b5b5; }\n    .message.is-danger {\n      background-color: #242424; }\n      .message.is-danger .message-header {\n        background-color: #f14668;\n        color: #fff; }\n      .message.is-danger .message-body {\n        border-color: #f14668;\n        color: #b5b5b5; }\n    .message.is-white-dark {\n      background-color: #242424; }\n      .message.is-white-dark .message-header {\n        background-color: white;\n        color: #0a0a0a; }\n      .message.is-white-dark .message-body {\n        border-color: white;\n        color: #b5b5b5; }\n    .message.is-black-dark {\n      background-color: #242424; }\n      .message.is-black-dark .message-header {\n        background-color: #0a0a0a;\n        color: white; }\n      .message.is-black-dark .message-body {\n        border-color: #0a0a0a;\n        color: #b5b5b5; }\n    .message.is-light-dark {\n      background-color: #242424; }\n      .message.is-light-dark .message-header {\n        background-color: whitesmoke;\n        color: rgba(0, 0, 0, 0.7); }\n      .message.is-light-dark .message-body {\n        border-color: whitesmoke;\n        color: #b5b5b5; }\n    .message.is-dark-dark {\n      background-color: #242424; }\n      .message.is-dark-dark .message-header {\n        background-color: #363636;\n        color: #fff; }\n      .message.is-dark-dark .message-body {\n        border-color: #363636;\n        color: #b5b5b5; }\n    .message.is-primary-dark {\n      background-color: #242424; }\n      .message.is-primary-dark .message-header {\n        background-color: #ff0d68;\n        color: #fff; }\n      .message.is-primary-dark .message-body {\n        border-color: #ff0d68;\n        color: #b5b5b5; }\n    .message.is-link-dark {\n      background-color: #242424; }\n      .message.is-link-dark .message-header {\n        background-color: #ff0d68;\n        color: #fff; }\n      .message.is-link-dark .message-body {\n        border-color: #ff0d68;\n        color: #b5b5b5; }\n    .message.is-info-dark {\n      background-color: #242424; }\n      .message.is-info-dark .message-header {\n        background-color: #0092FF;\n        color: #fff; }\n      .message.is-info-dark .message-body {\n        border-color: #0092FF;\n        color: #b5b5b5; }\n    .message.is-success-dark {\n      background-color: #242424; }\n      .message.is-success-dark .message-header {\n        background-color: #16DB93;\n        color: rgba(0, 0, 0, 0.7); }\n      .message.is-success-dark .message-body {\n        border-color: #16DB93;\n        color: #b5b5b5; }\n    .message.is-warning-dark {\n      background-color: #242424; }\n      .message.is-warning-dark .message-header {\n        background-color: #FFE900;\n        color: rgba(0, 0, 0, 0.7); }\n      .message.is-warning-dark .message-body {\n        border-color: #FFE900;\n        color: #b5b5b5; }\n    .message.is-danger-dark {\n      background-color: #242424; }\n      .message.is-danger-dark .message-header {\n        background-color: #f14668;\n        color: #fff; }\n      .message.is-danger-dark .message-body {\n        border-color: #f14668;\n        color: #b5b5b5; }\n  .message-header {\n    background-color: #b5b5b5;\n    color: #fff; }\n  .message-body {\n    border-color: #363636;\n    color: #b5b5b5; }\n    .message-body code,\n    .message-body pre {\n      background-color: #0a0a0a; }\n  .modal-background {\n    background-color: rgba(255, 255, 255, 0.86); }\n  .modal-card-head,\n  .modal-card-foot {\n    background-color: #242424; }\n  .modal-card-head {\n    border-bottom: 1px solid #363636; }\n  .modal-card-title {\n    color: #dbdbdb; }\n  .modal-card-foot {\n    border-top: 1px solid #363636; }\n  .modal-card-body {\n    -webkit-overflow-scrolling: touch;\n    background-color: white; }\n  .navbar {\n    background-color: #17181c; }\n    .navbar.is-white {\n      background-color: #e6e6e6;\n      color: #0a0a0a; }\n      .navbar.is-white .navbar-brand > .navbar-item,\n      .navbar.is-white .navbar-brand .navbar-link {\n        color: #0a0a0a; }\n      .navbar.is-white .navbar-brand > a.navbar-item:hover, .navbar.is-white .navbar-brand > a.navbar-item.is-active,\n      .navbar.is-white .navbar-brand .navbar-link:hover,\n      .navbar.is-white .navbar-brand .navbar-link.is-active {\n        background-color: #d9d9d9;\n        color: #0a0a0a; }\n      .navbar.is-white .navbar-brand .navbar-link::after {\n        border-color: #0a0a0a; }\n      .navbar.is-white .navbar-burger {\n        color: #0a0a0a; } }\n    @media screen and (prefers-color-scheme: dark) and (min-width: 1024px) {\n      .navbar.is-white .navbar-start > .navbar-item,\n      .navbar.is-white .navbar-start .navbar-link,\n      .navbar.is-white .navbar-end > .navbar-item,\n      .navbar.is-white .navbar-end .navbar-link {\n        color: #0a0a0a; }\n      .navbar.is-white .navbar-start > a.navbar-item:hover, .navbar.is-white .navbar-start > a.navbar-item.is-active,\n      .navbar.is-white .navbar-start .navbar-link:hover,\n      .navbar.is-white .navbar-start .navbar-link.is-active,\n      .navbar.is-white .navbar-end > a.navbar-item:hover,\n      .navbar.is-white .navbar-end > a.navbar-item.is-active,\n      .navbar.is-white .navbar-end .navbar-link:hover,\n      .navbar.is-white .navbar-end .navbar-link.is-active {\n        background-color: #d9d9d9;\n        color: #0a0a0a; }\n      .navbar.is-white .navbar-start .navbar-link::after,\n      .navbar.is-white .navbar-end .navbar-link::after {\n        border-color: #0a0a0a; }\n      .navbar.is-white .navbar-item.has-dropdown:hover .navbar-link,\n      .navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link {\n        background-color: #d9d9d9;\n        color: #0a0a0a; }\n      .navbar.is-white .navbar-dropdown a.navbar-item.is-active {\n        background-color: #e6e6e6;\n        color: #0a0a0a; } }\n@media (prefers-color-scheme: dark) {\n    .navbar.is-black {\n      background-color: black;\n      color: white; }\n      .navbar.is-black .navbar-brand > .navbar-item,\n      .navbar.is-black .navbar-brand .navbar-link {\n        color: white; }\n      .navbar.is-black .navbar-brand > a.navbar-item:hover, .navbar.is-black .navbar-brand > a.navbar-item.is-active,\n      .navbar.is-black .navbar-brand .navbar-link:hover,\n      .navbar.is-black .navbar-brand .navbar-link.is-active {\n        background-color: black;\n        color: white; }\n      .navbar.is-black .navbar-brand .navbar-link::after {\n        border-color: white; }\n      .navbar.is-black .navbar-burger {\n        color: white; } }\n    @media screen and (prefers-color-scheme: dark) and (min-width: 1024px) {\n      .navbar.is-black .navbar-start > .navbar-item,\n      .navbar.is-black .navbar-start .navbar-link,\n      .navbar.is-black .navbar-end > .navbar-item,\n      .navbar.is-black .navbar-end .navbar-link {\n        color: white; }\n      .navbar.is-black .navbar-start > a.navbar-item:hover, .navbar.is-black .navbar-start > a.navbar-item.is-active,\n      .navbar.is-black .navbar-start .navbar-link:hover,\n      .navbar.is-black .navbar-start .navbar-link.is-active,\n      .navbar.is-black .navbar-end > a.navbar-item:hover,\n      .navbar.is-black .navbar-end > a.navbar-item.is-active,\n      .navbar.is-black .navbar-end .navbar-link:hover,\n      .navbar.is-black .navbar-end .navbar-link.is-active {\n        background-color: black;\n        color: white; }\n      .navbar.is-black .navbar-start .navbar-link::after,\n      .navbar.is-black .navbar-end .navbar-link::after {\n        border-color: white; }\n      .navbar.is-black .navbar-item.has-dropdown:hover .navbar-link,\n      .navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link {\n        background-color: black;\n        color: white; }\n      .navbar.is-black .navbar-dropdown a.navbar-item.is-active {\n        background-color: black;\n        color: white; } }\n@media (prefers-color-scheme: dark) {\n    .navbar.is-light {\n      background-color: #dbdbdb;\n      color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-light .navbar-brand > .navbar-item,\n      .navbar.is-light .navbar-brand .navbar-link {\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-light .navbar-brand > a.navbar-item:hover, .navbar.is-light .navbar-brand > a.navbar-item.is-active,\n      .navbar.is-light .navbar-brand .navbar-link:hover,\n      .navbar.is-light .navbar-brand .navbar-link.is-active {\n        background-color: #cfcfcf;\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-light .navbar-brand .navbar-link::after {\n        border-color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-light .navbar-burger {\n        color: rgba(0, 0, 0, 0.7); } }\n    @media screen and (prefers-color-scheme: dark) and (min-width: 1024px) {\n      .navbar.is-light .navbar-start > .navbar-item,\n      .navbar.is-light .navbar-start .navbar-link,\n      .navbar.is-light .navbar-end > .navbar-item,\n      .navbar.is-light .navbar-end .navbar-link {\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-light .navbar-start > a.navbar-item:hover, .navbar.is-light .navbar-start > a.navbar-item.is-active,\n      .navbar.is-light .navbar-start .navbar-link:hover,\n      .navbar.is-light .navbar-start .navbar-link.is-active,\n      .navbar.is-light .navbar-end > a.navbar-item:hover,\n      .navbar.is-light .navbar-end > a.navbar-item.is-active,\n      .navbar.is-light .navbar-end .navbar-link:hover,\n      .navbar.is-light .navbar-end .navbar-link.is-active {\n        background-color: #cfcfcf;\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-light .navbar-start .navbar-link::after,\n      .navbar.is-light .navbar-end .navbar-link::after {\n        border-color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-light .navbar-item.has-dropdown:hover .navbar-link,\n      .navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link {\n        background-color: #cfcfcf;\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-light .navbar-dropdown a.navbar-item.is-active {\n        background-color: #dbdbdb;\n        color: rgba(0, 0, 0, 0.7); } }\n@media (prefers-color-scheme: dark) {\n    .navbar.is-dark {\n      background-color: #1c1c1c;\n      color: #fff; }\n      .navbar.is-dark .navbar-brand > .navbar-item,\n      .navbar.is-dark .navbar-brand .navbar-link {\n        color: #fff; }\n      .navbar.is-dark .navbar-brand > a.navbar-item:hover, .navbar.is-dark .navbar-brand > a.navbar-item.is-active,\n      .navbar.is-dark .navbar-brand .navbar-link:hover,\n      .navbar.is-dark .navbar-brand .navbar-link.is-active {\n        background-color: #0f0f0f;\n        color: #fff; }\n      .navbar.is-dark .navbar-brand .navbar-link::after {\n        border-color: #fff; }\n      .navbar.is-dark .navbar-burger {\n        color: #fff; } }\n    @media screen and (prefers-color-scheme: dark) and (min-width: 1024px) {\n      .navbar.is-dark .navbar-start > .navbar-item,\n      .navbar.is-dark .navbar-start .navbar-link,\n      .navbar.is-dark .navbar-end > .navbar-item,\n      .navbar.is-dark .navbar-end .navbar-link {\n        color: #fff; }\n      .navbar.is-dark .navbar-start > a.navbar-item:hover, .navbar.is-dark .navbar-start > a.navbar-item.is-active,\n      .navbar.is-dark .navbar-start .navbar-link:hover,\n      .navbar.is-dark .navbar-start .navbar-link.is-active,\n      .navbar.is-dark .navbar-end > a.navbar-item:hover,\n      .navbar.is-dark .navbar-end > a.navbar-item.is-active,\n      .navbar.is-dark .navbar-end .navbar-link:hover,\n      .navbar.is-dark .navbar-end .navbar-link.is-active {\n        background-color: #0f0f0f;\n        color: #fff; }\n      .navbar.is-dark .navbar-start .navbar-link::after,\n      .navbar.is-dark .navbar-end .navbar-link::after {\n        border-color: #fff; }\n      .navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link,\n      .navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link {\n        background-color: #0f0f0f;\n        color: #fff; }\n      .navbar.is-dark .navbar-dropdown a.navbar-item.is-active {\n        background-color: #1c1c1c;\n        color: #fff; } }\n@media (prefers-color-scheme: dark) {\n    .navbar.is-primary {\n      background-color: #d90052;\n      color: #fff; }\n      .navbar.is-primary .navbar-brand > .navbar-item,\n      .navbar.is-primary .navbar-brand .navbar-link {\n        color: #fff; }\n      .navbar.is-primary .navbar-brand > a.navbar-item:hover, .navbar.is-primary .navbar-brand > a.navbar-item.is-active,\n      .navbar.is-primary .navbar-brand .navbar-link:hover,\n      .navbar.is-primary .navbar-brand .navbar-link.is-active {\n        background-color: #c00048;\n        color: #fff; }\n      .navbar.is-primary .navbar-brand .navbar-link::after {\n        border-color: #fff; }\n      .navbar.is-primary .navbar-burger {\n        color: #fff; } }\n    @media screen and (prefers-color-scheme: dark) and (min-width: 1024px) {\n      .navbar.is-primary .navbar-start > .navbar-item,\n      .navbar.is-primary .navbar-start .navbar-link,\n      .navbar.is-primary .navbar-end > .navbar-item,\n      .navbar.is-primary .navbar-end .navbar-link {\n        color: #fff; }\n      .navbar.is-primary .navbar-start > a.navbar-item:hover, .navbar.is-primary .navbar-start > a.navbar-item.is-active,\n      .navbar.is-primary .navbar-start .navbar-link:hover,\n      .navbar.is-primary .navbar-start .navbar-link.is-active,\n      .navbar.is-primary .navbar-end > a.navbar-item:hover,\n      .navbar.is-primary .navbar-end > a.navbar-item.is-active,\n      .navbar.is-primary .navbar-end .navbar-link:hover,\n      .navbar.is-primary .navbar-end .navbar-link.is-active {\n        background-color: #c00048;\n        color: #fff; }\n      .navbar.is-primary .navbar-start .navbar-link::after,\n      .navbar.is-primary .navbar-end .navbar-link::after {\n        border-color: #fff; }\n      .navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link,\n      .navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link {\n        background-color: #c00048;\n        color: #fff; }\n      .navbar.is-primary .navbar-dropdown a.navbar-item.is-active {\n        background-color: #d90052;\n        color: #fff; } }\n@media (prefers-color-scheme: dark) {\n    .navbar.is-link {\n      background-color: #d90052;\n      color: #fff; }\n      .navbar.is-link .navbar-brand > .navbar-item,\n      .navbar.is-link .navbar-brand .navbar-link {\n        color: #fff; }\n      .navbar.is-link .navbar-brand > a.navbar-item:hover, .navbar.is-link .navbar-brand > a.navbar-item.is-active,\n      .navbar.is-link .navbar-brand .navbar-link:hover,\n      .navbar.is-link .navbar-brand .navbar-link.is-active {\n        background-color: #c00048;\n        color: #fff; }\n      .navbar.is-link .navbar-brand .navbar-link::after {\n        border-color: #fff; }\n      .navbar.is-link .navbar-burger {\n        color: #fff; } }\n    @media screen and (prefers-color-scheme: dark) and (min-width: 1024px) {\n      .navbar.is-link .navbar-start > .navbar-item,\n      .navbar.is-link .navbar-start .navbar-link,\n      .navbar.is-link .navbar-end > .navbar-item,\n      .navbar.is-link .navbar-end .navbar-link {\n        color: #fff; }\n      .navbar.is-link .navbar-start > a.navbar-item:hover, .navbar.is-link .navbar-start > a.navbar-item.is-active,\n      .navbar.is-link .navbar-start .navbar-link:hover,\n      .navbar.is-link .navbar-start .navbar-link.is-active,\n      .navbar.is-link .navbar-end > a.navbar-item:hover,\n      .navbar.is-link .navbar-end > a.navbar-item.is-active,\n      .navbar.is-link .navbar-end .navbar-link:hover,\n      .navbar.is-link .navbar-end .navbar-link.is-active {\n        background-color: #c00048;\n        color: #fff; }\n      .navbar.is-link .navbar-start .navbar-link::after,\n      .navbar.is-link .navbar-end .navbar-link::after {\n        border-color: #fff; }\n      .navbar.is-link .navbar-item.has-dropdown:hover .navbar-link,\n      .navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link {\n        background-color: #c00048;\n        color: #fff; }\n      .navbar.is-link .navbar-dropdown a.navbar-item.is-active {\n        background-color: #d90052;\n        color: #fff; } }\n@media (prefers-color-scheme: dark) {\n    .navbar.is-info {\n      background-color: #0075cc;\n      color: #fff; }\n      .navbar.is-info .navbar-brand > .navbar-item,\n      .navbar.is-info .navbar-brand .navbar-link {\n        color: #fff; }\n      .navbar.is-info .navbar-brand > a.navbar-item:hover, .navbar.is-info .navbar-brand > a.navbar-item.is-active,\n      .navbar.is-info .navbar-brand .navbar-link:hover,\n      .navbar.is-info .navbar-brand .navbar-link.is-active {\n        background-color: #0066b3;\n        color: #fff; }\n      .navbar.is-info .navbar-brand .navbar-link::after {\n        border-color: #fff; }\n      .navbar.is-info .navbar-burger {\n        color: #fff; } }\n    @media screen and (prefers-color-scheme: dark) and (min-width: 1024px) {\n      .navbar.is-info .navbar-start > .navbar-item,\n      .navbar.is-info .navbar-start .navbar-link,\n      .navbar.is-info .navbar-end > .navbar-item,\n      .navbar.is-info .navbar-end .navbar-link {\n        color: #fff; }\n      .navbar.is-info .navbar-start > a.navbar-item:hover, .navbar.is-info .navbar-start > a.navbar-item.is-active,\n      .navbar.is-info .navbar-start .navbar-link:hover,\n      .navbar.is-info .navbar-start .navbar-link.is-active,\n      .navbar.is-info .navbar-end > a.navbar-item:hover,\n      .navbar.is-info .navbar-end > a.navbar-item.is-active,\n      .navbar.is-info .navbar-end .navbar-link:hover,\n      .navbar.is-info .navbar-end .navbar-link.is-active {\n        background-color: #0066b3;\n        color: #fff; }\n      .navbar.is-info .navbar-start .navbar-link::after,\n      .navbar.is-info .navbar-end .navbar-link::after {\n        border-color: #fff; }\n      .navbar.is-info .navbar-item.has-dropdown:hover .navbar-link,\n      .navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link {\n        background-color: #0066b3;\n        color: #fff; }\n      .navbar.is-info .navbar-dropdown a.navbar-item.is-active {\n        background-color: #0075cc;\n        color: #fff; } }\n@media (prefers-color-scheme: dark) {\n    .navbar.is-success {\n      background-color: #11ad74;\n      color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-success .navbar-brand > .navbar-item,\n      .navbar.is-success .navbar-brand .navbar-link {\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-success .navbar-brand > a.navbar-item:hover, .navbar.is-success .navbar-brand > a.navbar-item.is-active,\n      .navbar.is-success .navbar-brand .navbar-link:hover,\n      .navbar.is-success .navbar-brand .navbar-link.is-active {\n        background-color: #0f9564;\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-success .navbar-brand .navbar-link::after {\n        border-color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-success .navbar-burger {\n        color: rgba(0, 0, 0, 0.7); } }\n    @media screen and (prefers-color-scheme: dark) and (min-width: 1024px) {\n      .navbar.is-success .navbar-start > .navbar-item,\n      .navbar.is-success .navbar-start .navbar-link,\n      .navbar.is-success .navbar-end > .navbar-item,\n      .navbar.is-success .navbar-end .navbar-link {\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-success .navbar-start > a.navbar-item:hover, .navbar.is-success .navbar-start > a.navbar-item.is-active,\n      .navbar.is-success .navbar-start .navbar-link:hover,\n      .navbar.is-success .navbar-start .navbar-link.is-active,\n      .navbar.is-success .navbar-end > a.navbar-item:hover,\n      .navbar.is-success .navbar-end > a.navbar-item.is-active,\n      .navbar.is-success .navbar-end .navbar-link:hover,\n      .navbar.is-success .navbar-end .navbar-link.is-active {\n        background-color: #0f9564;\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-success .navbar-start .navbar-link::after,\n      .navbar.is-success .navbar-end .navbar-link::after {\n        border-color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-success .navbar-item.has-dropdown:hover .navbar-link,\n      .navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link {\n        background-color: #0f9564;\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-success .navbar-dropdown a.navbar-item.is-active {\n        background-color: #11ad74;\n        color: rgba(0, 0, 0, 0.7); } }\n@media (prefers-color-scheme: dark) {\n    .navbar.is-warning {\n      background-color: #ccba00;\n      color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-warning .navbar-brand > .navbar-item,\n      .navbar.is-warning .navbar-brand .navbar-link {\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-warning .navbar-brand > a.navbar-item:hover, .navbar.is-warning .navbar-brand > a.navbar-item.is-active,\n      .navbar.is-warning .navbar-brand .navbar-link:hover,\n      .navbar.is-warning .navbar-brand .navbar-link.is-active {\n        background-color: #b3a300;\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-warning .navbar-brand .navbar-link::after {\n        border-color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-warning .navbar-burger {\n        color: rgba(0, 0, 0, 0.7); } }\n    @media screen and (prefers-color-scheme: dark) and (min-width: 1024px) {\n      .navbar.is-warning .navbar-start > .navbar-item,\n      .navbar.is-warning .navbar-start .navbar-link,\n      .navbar.is-warning .navbar-end > .navbar-item,\n      .navbar.is-warning .navbar-end .navbar-link {\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-warning .navbar-start > a.navbar-item:hover, .navbar.is-warning .navbar-start > a.navbar-item.is-active,\n      .navbar.is-warning .navbar-start .navbar-link:hover,\n      .navbar.is-warning .navbar-start .navbar-link.is-active,\n      .navbar.is-warning .navbar-end > a.navbar-item:hover,\n      .navbar.is-warning .navbar-end > a.navbar-item.is-active,\n      .navbar.is-warning .navbar-end .navbar-link:hover,\n      .navbar.is-warning .navbar-end .navbar-link.is-active {\n        background-color: #b3a300;\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-warning .navbar-start .navbar-link::after,\n      .navbar.is-warning .navbar-end .navbar-link::after {\n        border-color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link,\n      .navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link {\n        background-color: #b3a300;\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-warning .navbar-dropdown a.navbar-item.is-active {\n        background-color: #ccba00;\n        color: rgba(0, 0, 0, 0.7); } }\n@media (prefers-color-scheme: dark) {\n    .navbar.is-danger {\n      background-color: #ee1742;\n      color: #fff; }\n      .navbar.is-danger .navbar-brand > .navbar-item,\n      .navbar.is-danger .navbar-brand .navbar-link {\n        color: #fff; }\n      .navbar.is-danger .navbar-brand > a.navbar-item:hover, .navbar.is-danger .navbar-brand > a.navbar-item.is-active,\n      .navbar.is-danger .navbar-brand .navbar-link:hover,\n      .navbar.is-danger .navbar-brand .navbar-link.is-active {\n        background-color: #da1039;\n        color: #fff; }\n      .navbar.is-danger .navbar-brand .navbar-link::after {\n        border-color: #fff; }\n      .navbar.is-danger .navbar-burger {\n        color: #fff; } }\n    @media screen and (prefers-color-scheme: dark) and (min-width: 1024px) {\n      .navbar.is-danger .navbar-start > .navbar-item,\n      .navbar.is-danger .navbar-start .navbar-link,\n      .navbar.is-danger .navbar-end > .navbar-item,\n      .navbar.is-danger .navbar-end .navbar-link {\n        color: #fff; }\n      .navbar.is-danger .navbar-start > a.navbar-item:hover, .navbar.is-danger .navbar-start > a.navbar-item.is-active,\n      .navbar.is-danger .navbar-start .navbar-link:hover,\n      .navbar.is-danger .navbar-start .navbar-link.is-active,\n      .navbar.is-danger .navbar-end > a.navbar-item:hover,\n      .navbar.is-danger .navbar-end > a.navbar-item.is-active,\n      .navbar.is-danger .navbar-end .navbar-link:hover,\n      .navbar.is-danger .navbar-end .navbar-link.is-active {\n        background-color: #da1039;\n        color: #fff; }\n      .navbar.is-danger .navbar-start .navbar-link::after,\n      .navbar.is-danger .navbar-end .navbar-link::after {\n        border-color: #fff; }\n      .navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link,\n      .navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link {\n        background-color: #da1039;\n        color: #fff; }\n      .navbar.is-danger .navbar-dropdown a.navbar-item.is-active {\n        background-color: #ee1742;\n        color: #fff; } }\n@media (prefers-color-scheme: dark) {\n    .navbar.is-white-dark {\n      background-color: white;\n      color: #0a0a0a; }\n      .navbar.is-white-dark .navbar-brand > .navbar-item,\n      .navbar.is-white-dark .navbar-brand .navbar-link {\n        color: #0a0a0a; }\n      .navbar.is-white-dark .navbar-brand > a.navbar-item:hover, .navbar.is-white-dark .navbar-brand > a.navbar-item.is-active,\n      .navbar.is-white-dark .navbar-brand .navbar-link:hover,\n      .navbar.is-white-dark .navbar-brand .navbar-link.is-active {\n        background-color: #f2f2f2;\n        color: #0a0a0a; }\n      .navbar.is-white-dark .navbar-brand .navbar-link::after {\n        border-color: #0a0a0a; }\n      .navbar.is-white-dark .navbar-burger {\n        color: #0a0a0a; } }\n    @media screen and (prefers-color-scheme: dark) and (min-width: 1024px) {\n      .navbar.is-white-dark .navbar-start > .navbar-item,\n      .navbar.is-white-dark .navbar-start .navbar-link,\n      .navbar.is-white-dark .navbar-end > .navbar-item,\n      .navbar.is-white-dark .navbar-end .navbar-link {\n        color: #0a0a0a; }\n      .navbar.is-white-dark .navbar-start > a.navbar-item:hover, .navbar.is-white-dark .navbar-start > a.navbar-item.is-active,\n      .navbar.is-white-dark .navbar-start .navbar-link:hover,\n      .navbar.is-white-dark .navbar-start .navbar-link.is-active,\n      .navbar.is-white-dark .navbar-end > a.navbar-item:hover,\n      .navbar.is-white-dark .navbar-end > a.navbar-item.is-active,\n      .navbar.is-white-dark .navbar-end .navbar-link:hover,\n      .navbar.is-white-dark .navbar-end .navbar-link.is-active {\n        background-color: #f2f2f2;\n        color: #0a0a0a; }\n      .navbar.is-white-dark .navbar-start .navbar-link::after,\n      .navbar.is-white-dark .navbar-end .navbar-link::after {\n        border-color: #0a0a0a; }\n      .navbar.is-white-dark .navbar-item.has-dropdown:hover .navbar-link,\n      .navbar.is-white-dark .navbar-item.has-dropdown.is-active .navbar-link {\n        background-color: #f2f2f2;\n        color: #0a0a0a; }\n      .navbar.is-white-dark .navbar-dropdown a.navbar-item.is-active {\n        background-color: white;\n        color: #0a0a0a; } }\n@media (prefers-color-scheme: dark) {\n    .navbar.is-black-dark {\n      background-color: #0a0a0a;\n      color: white; }\n      .navbar.is-black-dark .navbar-brand > .navbar-item,\n      .navbar.is-black-dark .navbar-brand .navbar-link {\n        color: white; }\n      .navbar.is-black-dark .navbar-brand > a.navbar-item:hover, .navbar.is-black-dark .navbar-brand > a.navbar-item.is-active,\n      .navbar.is-black-dark .navbar-brand .navbar-link:hover,\n      .navbar.is-black-dark .navbar-brand .navbar-link.is-active {\n        background-color: black;\n        color: white; }\n      .navbar.is-black-dark .navbar-brand .navbar-link::after {\n        border-color: white; }\n      .navbar.is-black-dark .navbar-burger {\n        color: white; } }\n    @media screen and (prefers-color-scheme: dark) and (min-width: 1024px) {\n      .navbar.is-black-dark .navbar-start > .navbar-item,\n      .navbar.is-black-dark .navbar-start .navbar-link,\n      .navbar.is-black-dark .navbar-end > .navbar-item,\n      .navbar.is-black-dark .navbar-end .navbar-link {\n        color: white; }\n      .navbar.is-black-dark .navbar-start > a.navbar-item:hover, .navbar.is-black-dark .navbar-start > a.navbar-item.is-active,\n      .navbar.is-black-dark .navbar-start .navbar-link:hover,\n      .navbar.is-black-dark .navbar-start .navbar-link.is-active,\n      .navbar.is-black-dark .navbar-end > a.navbar-item:hover,\n      .navbar.is-black-dark .navbar-end > a.navbar-item.is-active,\n      .navbar.is-black-dark .navbar-end .navbar-link:hover,\n      .navbar.is-black-dark .navbar-end .navbar-link.is-active {\n        background-color: black;\n        color: white; }\n      .navbar.is-black-dark .navbar-start .navbar-link::after,\n      .navbar.is-black-dark .navbar-end .navbar-link::after {\n        border-color: white; }\n      .navbar.is-black-dark .navbar-item.has-dropdown:hover .navbar-link,\n      .navbar.is-black-dark .navbar-item.has-dropdown.is-active .navbar-link {\n        background-color: black;\n        color: white; }\n      .navbar.is-black-dark .navbar-dropdown a.navbar-item.is-active {\n        background-color: #0a0a0a;\n        color: white; } }\n@media (prefers-color-scheme: dark) {\n    .navbar.is-light-dark {\n      background-color: whitesmoke;\n      color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-light-dark .navbar-brand > .navbar-item,\n      .navbar.is-light-dark .navbar-brand .navbar-link {\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-light-dark .navbar-brand > a.navbar-item:hover, .navbar.is-light-dark .navbar-brand > a.navbar-item.is-active,\n      .navbar.is-light-dark .navbar-brand .navbar-link:hover,\n      .navbar.is-light-dark .navbar-brand .navbar-link.is-active {\n        background-color: #e8e8e8;\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-light-dark .navbar-brand .navbar-link::after {\n        border-color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-light-dark .navbar-burger {\n        color: rgba(0, 0, 0, 0.7); } }\n    @media screen and (prefers-color-scheme: dark) and (min-width: 1024px) {\n      .navbar.is-light-dark .navbar-start > .navbar-item,\n      .navbar.is-light-dark .navbar-start .navbar-link,\n      .navbar.is-light-dark .navbar-end > .navbar-item,\n      .navbar.is-light-dark .navbar-end .navbar-link {\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-light-dark .navbar-start > a.navbar-item:hover, .navbar.is-light-dark .navbar-start > a.navbar-item.is-active,\n      .navbar.is-light-dark .navbar-start .navbar-link:hover,\n      .navbar.is-light-dark .navbar-start .navbar-link.is-active,\n      .navbar.is-light-dark .navbar-end > a.navbar-item:hover,\n      .navbar.is-light-dark .navbar-end > a.navbar-item.is-active,\n      .navbar.is-light-dark .navbar-end .navbar-link:hover,\n      .navbar.is-light-dark .navbar-end .navbar-link.is-active {\n        background-color: #e8e8e8;\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-light-dark .navbar-start .navbar-link::after,\n      .navbar.is-light-dark .navbar-end .navbar-link::after {\n        border-color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-light-dark .navbar-item.has-dropdown:hover .navbar-link,\n      .navbar.is-light-dark .navbar-item.has-dropdown.is-active .navbar-link {\n        background-color: #e8e8e8;\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-light-dark .navbar-dropdown a.navbar-item.is-active {\n        background-color: whitesmoke;\n        color: rgba(0, 0, 0, 0.7); } }\n@media (prefers-color-scheme: dark) {\n    .navbar.is-dark-dark {\n      background-color: #363636;\n      color: #fff; }\n      .navbar.is-dark-dark .navbar-brand > .navbar-item,\n      .navbar.is-dark-dark .navbar-brand .navbar-link {\n        color: #fff; }\n      .navbar.is-dark-dark .navbar-brand > a.navbar-item:hover, .navbar.is-dark-dark .navbar-brand > a.navbar-item.is-active,\n      .navbar.is-dark-dark .navbar-brand .navbar-link:hover,\n      .navbar.is-dark-dark .navbar-brand .navbar-link.is-active {\n        background-color: #292929;\n        color: #fff; }\n      .navbar.is-dark-dark .navbar-brand .navbar-link::after {\n        border-color: #fff; }\n      .navbar.is-dark-dark .navbar-burger {\n        color: #fff; } }\n    @media screen and (prefers-color-scheme: dark) and (min-width: 1024px) {\n      .navbar.is-dark-dark .navbar-start > .navbar-item,\n      .navbar.is-dark-dark .navbar-start .navbar-link,\n      .navbar.is-dark-dark .navbar-end > .navbar-item,\n      .navbar.is-dark-dark .navbar-end .navbar-link {\n        color: #fff; }\n      .navbar.is-dark-dark .navbar-start > a.navbar-item:hover, .navbar.is-dark-dark .navbar-start > a.navbar-item.is-active,\n      .navbar.is-dark-dark .navbar-start .navbar-link:hover,\n      .navbar.is-dark-dark .navbar-start .navbar-link.is-active,\n      .navbar.is-dark-dark .navbar-end > a.navbar-item:hover,\n      .navbar.is-dark-dark .navbar-end > a.navbar-item.is-active,\n      .navbar.is-dark-dark .navbar-end .navbar-link:hover,\n      .navbar.is-dark-dark .navbar-end .navbar-link.is-active {\n        background-color: #292929;\n        color: #fff; }\n      .navbar.is-dark-dark .navbar-start .navbar-link::after,\n      .navbar.is-dark-dark .navbar-end .navbar-link::after {\n        border-color: #fff; }\n      .navbar.is-dark-dark .navbar-item.has-dropdown:hover .navbar-link,\n      .navbar.is-dark-dark .navbar-item.has-dropdown.is-active .navbar-link {\n        background-color: #292929;\n        color: #fff; }\n      .navbar.is-dark-dark .navbar-dropdown a.navbar-item.is-active {\n        background-color: #363636;\n        color: #fff; } }\n@media (prefers-color-scheme: dark) {\n    .navbar.is-primary-dark {\n      background-color: #ff0d68;\n      color: #fff; }\n      .navbar.is-primary-dark .navbar-brand > .navbar-item,\n      .navbar.is-primary-dark .navbar-brand .navbar-link {\n        color: #fff; }\n      .navbar.is-primary-dark .navbar-brand > a.navbar-item:hover, .navbar.is-primary-dark .navbar-brand > a.navbar-item.is-active,\n      .navbar.is-primary-dark .navbar-brand .navbar-link:hover,\n      .navbar.is-primary-dark .navbar-brand .navbar-link.is-active {\n        background-color: #f3005b;\n        color: #fff; }\n      .navbar.is-primary-dark .navbar-brand .navbar-link::after {\n        border-color: #fff; }\n      .navbar.is-primary-dark .navbar-burger {\n        color: #fff; } }\n    @media screen and (prefers-color-scheme: dark) and (min-width: 1024px) {\n      .navbar.is-primary-dark .navbar-start > .navbar-item,\n      .navbar.is-primary-dark .navbar-start .navbar-link,\n      .navbar.is-primary-dark .navbar-end > .navbar-item,\n      .navbar.is-primary-dark .navbar-end .navbar-link {\n        color: #fff; }\n      .navbar.is-primary-dark .navbar-start > a.navbar-item:hover, .navbar.is-primary-dark .navbar-start > a.navbar-item.is-active,\n      .navbar.is-primary-dark .navbar-start .navbar-link:hover,\n      .navbar.is-primary-dark .navbar-start .navbar-link.is-active,\n      .navbar.is-primary-dark .navbar-end > a.navbar-item:hover,\n      .navbar.is-primary-dark .navbar-end > a.navbar-item.is-active,\n      .navbar.is-primary-dark .navbar-end .navbar-link:hover,\n      .navbar.is-primary-dark .navbar-end .navbar-link.is-active {\n        background-color: #f3005b;\n        color: #fff; }\n      .navbar.is-primary-dark .navbar-start .navbar-link::after,\n      .navbar.is-primary-dark .navbar-end .navbar-link::after {\n        border-color: #fff; }\n      .navbar.is-primary-dark .navbar-item.has-dropdown:hover .navbar-link,\n      .navbar.is-primary-dark .navbar-item.has-dropdown.is-active .navbar-link {\n        background-color: #f3005b;\n        color: #fff; }\n      .navbar.is-primary-dark .navbar-dropdown a.navbar-item.is-active {\n        background-color: #ff0d68;\n        color: #fff; } }\n@media (prefers-color-scheme: dark) {\n    .navbar.is-link-dark {\n      background-color: #ff0d68;\n      color: #fff; }\n      .navbar.is-link-dark .navbar-brand > .navbar-item,\n      .navbar.is-link-dark .navbar-brand .navbar-link {\n        color: #fff; }\n      .navbar.is-link-dark .navbar-brand > a.navbar-item:hover, .navbar.is-link-dark .navbar-brand > a.navbar-item.is-active,\n      .navbar.is-link-dark .navbar-brand .navbar-link:hover,\n      .navbar.is-link-dark .navbar-brand .navbar-link.is-active {\n        background-color: #f3005b;\n        color: #fff; }\n      .navbar.is-link-dark .navbar-brand .navbar-link::after {\n        border-color: #fff; }\n      .navbar.is-link-dark .navbar-burger {\n        color: #fff; } }\n    @media screen and (prefers-color-scheme: dark) and (min-width: 1024px) {\n      .navbar.is-link-dark .navbar-start > .navbar-item,\n      .navbar.is-link-dark .navbar-start .navbar-link,\n      .navbar.is-link-dark .navbar-end > .navbar-item,\n      .navbar.is-link-dark .navbar-end .navbar-link {\n        color: #fff; }\n      .navbar.is-link-dark .navbar-start > a.navbar-item:hover, .navbar.is-link-dark .navbar-start > a.navbar-item.is-active,\n      .navbar.is-link-dark .navbar-start .navbar-link:hover,\n      .navbar.is-link-dark .navbar-start .navbar-link.is-active,\n      .navbar.is-link-dark .navbar-end > a.navbar-item:hover,\n      .navbar.is-link-dark .navbar-end > a.navbar-item.is-active,\n      .navbar.is-link-dark .navbar-end .navbar-link:hover,\n      .navbar.is-link-dark .navbar-end .navbar-link.is-active {\n        background-color: #f3005b;\n        color: #fff; }\n      .navbar.is-link-dark .navbar-start .navbar-link::after,\n      .navbar.is-link-dark .navbar-end .navbar-link::after {\n        border-color: #fff; }\n      .navbar.is-link-dark .navbar-item.has-dropdown:hover .navbar-link,\n      .navbar.is-link-dark .navbar-item.has-dropdown.is-active .navbar-link {\n        background-color: #f3005b;\n        color: #fff; }\n      .navbar.is-link-dark .navbar-dropdown a.navbar-item.is-active {\n        background-color: #ff0d68;\n        color: #fff; } }\n@media (prefers-color-scheme: dark) {\n    .navbar.is-info-dark {\n      background-color: #0092FF;\n      color: #fff; }\n      .navbar.is-info-dark .navbar-brand > .navbar-item,\n      .navbar.is-info-dark .navbar-brand .navbar-link {\n        color: #fff; }\n      .navbar.is-info-dark .navbar-brand > a.navbar-item:hover, .navbar.is-info-dark .navbar-brand > a.navbar-item.is-active,\n      .navbar.is-info-dark .navbar-brand .navbar-link:hover,\n      .navbar.is-info-dark .navbar-brand .navbar-link.is-active {\n        background-color: #0083e6;\n        color: #fff; }\n      .navbar.is-info-dark .navbar-brand .navbar-link::after {\n        border-color: #fff; }\n      .navbar.is-info-dark .navbar-burger {\n        color: #fff; } }\n    @media screen and (prefers-color-scheme: dark) and (min-width: 1024px) {\n      .navbar.is-info-dark .navbar-start > .navbar-item,\n      .navbar.is-info-dark .navbar-start .navbar-link,\n      .navbar.is-info-dark .navbar-end > .navbar-item,\n      .navbar.is-info-dark .navbar-end .navbar-link {\n        color: #fff; }\n      .navbar.is-info-dark .navbar-start > a.navbar-item:hover, .navbar.is-info-dark .navbar-start > a.navbar-item.is-active,\n      .navbar.is-info-dark .navbar-start .navbar-link:hover,\n      .navbar.is-info-dark .navbar-start .navbar-link.is-active,\n      .navbar.is-info-dark .navbar-end > a.navbar-item:hover,\n      .navbar.is-info-dark .navbar-end > a.navbar-item.is-active,\n      .navbar.is-info-dark .navbar-end .navbar-link:hover,\n      .navbar.is-info-dark .navbar-end .navbar-link.is-active {\n        background-color: #0083e6;\n        color: #fff; }\n      .navbar.is-info-dark .navbar-start .navbar-link::after,\n      .navbar.is-info-dark .navbar-end .navbar-link::after {\n        border-color: #fff; }\n      .navbar.is-info-dark .navbar-item.has-dropdown:hover .navbar-link,\n      .navbar.is-info-dark .navbar-item.has-dropdown.is-active .navbar-link {\n        background-color: #0083e6;\n        color: #fff; }\n      .navbar.is-info-dark .navbar-dropdown a.navbar-item.is-active {\n        background-color: #0092FF;\n        color: #fff; } }\n@media (prefers-color-scheme: dark) {\n    .navbar.is-success-dark {\n      background-color: #16DB93;\n      color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-success-dark .navbar-brand > .navbar-item,\n      .navbar.is-success-dark .navbar-brand .navbar-link {\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-success-dark .navbar-brand > a.navbar-item:hover, .navbar.is-success-dark .navbar-brand > a.navbar-item.is-active,\n      .navbar.is-success-dark .navbar-brand .navbar-link:hover,\n      .navbar.is-success-dark .navbar-brand .navbar-link.is-active {\n        background-color: #14c483;\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-success-dark .navbar-brand .navbar-link::after {\n        border-color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-success-dark .navbar-burger {\n        color: rgba(0, 0, 0, 0.7); } }\n    @media screen and (prefers-color-scheme: dark) and (min-width: 1024px) {\n      .navbar.is-success-dark .navbar-start > .navbar-item,\n      .navbar.is-success-dark .navbar-start .navbar-link,\n      .navbar.is-success-dark .navbar-end > .navbar-item,\n      .navbar.is-success-dark .navbar-end .navbar-link {\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-success-dark .navbar-start > a.navbar-item:hover, .navbar.is-success-dark .navbar-start > a.navbar-item.is-active,\n      .navbar.is-success-dark .navbar-start .navbar-link:hover,\n      .navbar.is-success-dark .navbar-start .navbar-link.is-active,\n      .navbar.is-success-dark .navbar-end > a.navbar-item:hover,\n      .navbar.is-success-dark .navbar-end > a.navbar-item.is-active,\n      .navbar.is-success-dark .navbar-end .navbar-link:hover,\n      .navbar.is-success-dark .navbar-end .navbar-link.is-active {\n        background-color: #14c483;\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-success-dark .navbar-start .navbar-link::after,\n      .navbar.is-success-dark .navbar-end .navbar-link::after {\n        border-color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-success-dark .navbar-item.has-dropdown:hover .navbar-link,\n      .navbar.is-success-dark .navbar-item.has-dropdown.is-active .navbar-link {\n        background-color: #14c483;\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-success-dark .navbar-dropdown a.navbar-item.is-active {\n        background-color: #16DB93;\n        color: rgba(0, 0, 0, 0.7); } }\n@media (prefers-color-scheme: dark) {\n    .navbar.is-warning-dark {\n      background-color: #FFE900;\n      color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-warning-dark .navbar-brand > .navbar-item,\n      .navbar.is-warning-dark .navbar-brand .navbar-link {\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-warning-dark .navbar-brand > a.navbar-item:hover, .navbar.is-warning-dark .navbar-brand > a.navbar-item.is-active,\n      .navbar.is-warning-dark .navbar-brand .navbar-link:hover,\n      .navbar.is-warning-dark .navbar-brand .navbar-link.is-active {\n        background-color: #e6d200;\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-warning-dark .navbar-brand .navbar-link::after {\n        border-color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-warning-dark .navbar-burger {\n        color: rgba(0, 0, 0, 0.7); } }\n    @media screen and (prefers-color-scheme: dark) and (min-width: 1024px) {\n      .navbar.is-warning-dark .navbar-start > .navbar-item,\n      .navbar.is-warning-dark .navbar-start .navbar-link,\n      .navbar.is-warning-dark .navbar-end > .navbar-item,\n      .navbar.is-warning-dark .navbar-end .navbar-link {\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-warning-dark .navbar-start > a.navbar-item:hover, .navbar.is-warning-dark .navbar-start > a.navbar-item.is-active,\n      .navbar.is-warning-dark .navbar-start .navbar-link:hover,\n      .navbar.is-warning-dark .navbar-start .navbar-link.is-active,\n      .navbar.is-warning-dark .navbar-end > a.navbar-item:hover,\n      .navbar.is-warning-dark .navbar-end > a.navbar-item.is-active,\n      .navbar.is-warning-dark .navbar-end .navbar-link:hover,\n      .navbar.is-warning-dark .navbar-end .navbar-link.is-active {\n        background-color: #e6d200;\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-warning-dark .navbar-start .navbar-link::after,\n      .navbar.is-warning-dark .navbar-end .navbar-link::after {\n        border-color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-warning-dark .navbar-item.has-dropdown:hover .navbar-link,\n      .navbar.is-warning-dark .navbar-item.has-dropdown.is-active .navbar-link {\n        background-color: #e6d200;\n        color: rgba(0, 0, 0, 0.7); }\n      .navbar.is-warning-dark .navbar-dropdown a.navbar-item.is-active {\n        background-color: #FFE900;\n        color: rgba(0, 0, 0, 0.7); } }\n@media (prefers-color-scheme: dark) {\n    .navbar.is-danger-dark {\n      background-color: #f14668;\n      color: #fff; }\n      .navbar.is-danger-dark .navbar-brand > .navbar-item,\n      .navbar.is-danger-dark .navbar-brand .navbar-link {\n        color: #fff; }\n      .navbar.is-danger-dark .navbar-brand > a.navbar-item:hover, .navbar.is-danger-dark .navbar-brand > a.navbar-item.is-active,\n      .navbar.is-danger-dark .navbar-brand .navbar-link:hover,\n      .navbar.is-danger-dark .navbar-brand .navbar-link.is-active {\n        background-color: #ef2e55;\n        color: #fff; }\n      .navbar.is-danger-dark .navbar-brand .navbar-link::after {\n        border-color: #fff; }\n      .navbar.is-danger-dark .navbar-burger {\n        color: #fff; } }\n    @media screen and (prefers-color-scheme: dark) and (min-width: 1024px) {\n      .navbar.is-danger-dark .navbar-start > .navbar-item,\n      .navbar.is-danger-dark .navbar-start .navbar-link,\n      .navbar.is-danger-dark .navbar-end > .navbar-item,\n      .navbar.is-danger-dark .navbar-end .navbar-link {\n        color: #fff; }\n      .navbar.is-danger-dark .navbar-start > a.navbar-item:hover, .navbar.is-danger-dark .navbar-start > a.navbar-item.is-active,\n      .navbar.is-danger-dark .navbar-start .navbar-link:hover,\n      .navbar.is-danger-dark .navbar-start .navbar-link.is-active,\n      .navbar.is-danger-dark .navbar-end > a.navbar-item:hover,\n      .navbar.is-danger-dark .navbar-end > a.navbar-item.is-active,\n      .navbar.is-danger-dark .navbar-end .navbar-link:hover,\n      .navbar.is-danger-dark .navbar-end .navbar-link.is-active {\n        background-color: #ef2e55;\n        color: #fff; }\n      .navbar.is-danger-dark .navbar-start .navbar-link::after,\n      .navbar.is-danger-dark .navbar-end .navbar-link::after {\n        border-color: #fff; }\n      .navbar.is-danger-dark .navbar-item.has-dropdown:hover .navbar-link,\n      .navbar.is-danger-dark .navbar-item.has-dropdown.is-active .navbar-link {\n        background-color: #ef2e55;\n        color: #fff; }\n      .navbar.is-danger-dark .navbar-dropdown a.navbar-item.is-active {\n        background-color: #f14668;\n        color: #fff; } }\n@media (prefers-color-scheme: dark) {\n    .navbar.has-shadow {\n      box-shadow: 0 2px 0 0 #242424; }\n    .navbar.is-fixed-bottom.has-shadow {\n      box-shadow: 0 -2px 0 0 #242424; }\n  .navbar-burger {\n    color: #b5b5b5; }\n  .navbar-item,\n  .navbar-link {\n    color: #b5b5b5; }\n  a.navbar-item:hover, a.navbar-item.is-active,\n  .navbar-link:hover,\n  .navbar-link.is-active {\n    background-color: #121212;\n    color: #e60056; }\n  .navbar-item:hover {\n    border-bottom-color: #e60056; }\n  .navbar-item.is-active {\n    border-bottom-color: #e60056;\n    color: #e60056; }\n  .navbar-link:not(.is-arrowless)::after {\n    border-color: #e60056; }\n  .navbar-divider {\n    background-color: #242424; } }\n\n@media screen and (prefers-color-scheme: dark) and (max-width: 1023px) {\n  .navbar-menu {\n    background-color: #17181c;\n    box-shadow: 0 8px 16px rgba(255, 255, 255, 0.1); }\n  .navbar.is-fixed-bottom-touch.has-shadow {\n    box-shadow: 0 -2px 3px rgba(255, 255, 255, 0.1); } }\n\n@media screen and (prefers-color-scheme: dark) and (min-width: 1024px) {\n  .navbar.is-transparent .navbar-dropdown a.navbar-item:hover {\n    background-color: #242424;\n    color: white; }\n  .navbar.is-transparent .navbar-dropdown a.navbar-item.is-active {\n    background-color: #242424;\n    color: #e60056; }\n  .navbar-item.has-dropdown-up .navbar-dropdown {\n    border-bottom: 2px solid #363636;\n    box-shadow: 0 -8px 8px rgba(255, 255, 255, 0.1); }\n  .navbar-dropdown {\n    background-color: #0a0a0a;\n    border-top: 2px solid #363636;\n    box-shadow: 0 8px 8px rgba(255, 255, 255, 0.1); }\n    .navbar-dropdown a.navbar-item:hover {\n      background-color: #242424;\n      color: white; }\n    .navbar-dropdown a.navbar-item.is-active {\n      background-color: #242424;\n      color: #e60056; }\n    .navbar.is-spaced .navbar-dropdown, .navbar-dropdown.is-boxed {\n      box-shadow: 0 8px 8px rgba(255, 255, 255, 0.1), 0 0 0 1px rgba(255, 255, 255, 0.1); }\n  .navbar.is-fixed-bottom-desktop.has-shadow {\n    box-shadow: 0 -2px 3px rgba(255, 255, 255, 0.1); }\n  a.navbar-item.is-active,\n  .navbar-link.is-active {\n    color: white; }\n  .navbar-item.has-dropdown:hover .navbar-link, .navbar-item.has-dropdown.is-active .navbar-link {\n    background-color: #121212; } }\n\n@media (prefers-color-scheme: dark) {\n  .pagination-previous,\n  .pagination-next,\n  .pagination-link {\n    border-color: #363636;\n    color: #dbdbdb; }\n    .pagination-previous:hover,\n    .pagination-next:hover,\n    .pagination-link:hover {\n      border-color: #4a4a4a;\n      color: #dbdbdb; }\n    .pagination-previous:focus,\n    .pagination-next:focus,\n    .pagination-link:focus {\n      border-color: #5ea3e4; }\n    .pagination-previous:active,\n    .pagination-next:active,\n    .pagination-link:active {\n      box-shadow: inset 0 1px 2px rgba(255, 255, 255, 0.2); }\n    .pagination-previous[disabled],\n    .pagination-next[disabled],\n    .pagination-link[disabled] {\n      background-color: #363636;\n      border-color: #363636;\n      color: #7a7a7a; }\n  .pagination-link.is-current {\n    background-color: #e60056;\n    border-color: #e60056;\n    color: #fff; }\n  .pagination-ellipsis {\n    color: #4a4a4a; }\n  .panel-heading,\n  .panel-tabs,\n  .panel-block {\n    border-bottom: 1px solid #363636;\n    border-left: 1px solid #363636;\n    border-right: 1px solid #363636; }\n    .panel-heading:first-child,\n    .panel-tabs:first-child,\n    .panel-block:first-child {\n      border-top: 1px solid #363636; }\n  .panel-heading {\n    background-color: #242424;\n    color: #dbdbdb; }\n  .panel-tabs a {\n    border-bottom: 1px solid #363636; }\n    .panel-tabs a.is-active {\n      border-bottom-color: #b5b5b5;\n      color: #dbdbdb; }\n  .panel-list a {\n    color: #b5b5b5; }\n    .panel-list a:hover {\n      color: #e60056; }\n  .panel-block {\n    color: #dbdbdb; }\n    .panel-block.is-active {\n      border-left-color: #e60056;\n      color: #dbdbdb; }\n      .panel-block.is-active .panel-icon {\n        color: #e60056; }\n  a.panel-block:hover,\n  label.panel-block:hover {\n    background-color: #242424; }\n  .tabs a {\n    border-bottom-color: #363636;\n    color: #b5b5b5; }\n    .tabs a:hover {\n      border-bottom-color: #dbdbdb;\n      color: #dbdbdb; }\n  .tabs li.is-active a {\n    border-bottom-color: #e60056;\n    color: #e60056; }\n  .tabs ul {\n    border-bottom-color: #363636; }\n  .tabs.is-boxed a:hover {\n    background-color: #242424;\n    border-bottom-color: #363636; }\n  .tabs.is-boxed li.is-active a {\n    background-color: #0a0a0a;\n    border-color: #363636; }\n  .tabs.is-toggle a {\n    border-color: #363636; }\n    .tabs.is-toggle a:hover {\n      background-color: #242424;\n      border-color: #4a4a4a; }\n  .tabs.is-toggle li.is-active a {\n    background-color: #e60056;\n    border-color: #e60056;\n    color: #fff; }\n  .hero.is-white, .hero.is-white-dark {\n    background-color: #e6e6e6;\n    color: #0a0a0a; }\n    .hero.is-white a:not(.button):not(.dropdown-item):not(.tag),\n    .hero.is-white strong, .hero.is-white-dark a:not(.button):not(.dropdown-item):not(.tag),\n    .hero.is-white-dark strong {\n      color: inherit; }\n    .hero.is-white .title, .hero.is-white-dark .title {\n      color: #0a0a0a; }\n    .hero.is-white .subtitle, .hero.is-white-dark .subtitle {\n      color: rgba(10, 10, 10, 0.9); }\n      .hero.is-white .subtitle a:not(.button),\n      .hero.is-white .subtitle strong, .hero.is-white-dark .subtitle a:not(.button),\n      .hero.is-white-dark .subtitle strong {\n        color: #0a0a0a; } }\n  @media screen and (prefers-color-scheme: dark) and (max-width: 1023px) {\n    .hero.is-white .navbar-menu, .hero.is-white-dark .navbar-menu {\n      background-color: #e6e6e6; } }\n@media (prefers-color-scheme: dark) {\n    .hero.is-white .navbar-item,\n    .hero.is-white .navbar-link, .hero.is-white-dark .navbar-item,\n    .hero.is-white-dark .navbar-link {\n      color: rgba(10, 10, 10, 0.7); }\n    .hero.is-white a.navbar-item:hover, .hero.is-white a.navbar-item.is-active,\n    .hero.is-white .navbar-link:hover,\n    .hero.is-white .navbar-link.is-active, .hero.is-white-dark a.navbar-item:hover, .hero.is-white-dark a.navbar-item.is-active,\n    .hero.is-white-dark .navbar-link:hover,\n    .hero.is-white-dark .navbar-link.is-active {\n      background-color: #d9d9d9;\n      color: #0a0a0a; }\n    .hero.is-white .tabs a, .hero.is-white-dark .tabs a {\n      color: #0a0a0a;\n      opacity: 0.9; }\n      .hero.is-white .tabs a:hover, .hero.is-white-dark .tabs a:hover {\n        opacity: 1; }\n    .hero.is-white .tabs li.is-active a, .hero.is-white-dark .tabs li.is-active a {\n      opacity: 1; }\n    .hero.is-white .tabs.is-boxed a, .hero.is-white .tabs.is-toggle a, .hero.is-white-dark .tabs.is-boxed a, .hero.is-white-dark .tabs.is-toggle a {\n      color: #0a0a0a; }\n      .hero.is-white .tabs.is-boxed a:hover, .hero.is-white .tabs.is-toggle a:hover, .hero.is-white-dark .tabs.is-boxed a:hover, .hero.is-white-dark .tabs.is-toggle a:hover {\n        background-color: rgba(10, 10, 10, 0.1); }\n    .hero.is-white .tabs.is-boxed li.is-active a, .hero.is-white .tabs.is-boxed li.is-active a:hover, .hero.is-white .tabs.is-toggle li.is-active a, .hero.is-white .tabs.is-toggle li.is-active a:hover, .hero.is-white-dark .tabs.is-boxed li.is-active a, .hero.is-white-dark .tabs.is-boxed li.is-active a:hover, .hero.is-white-dark .tabs.is-toggle li.is-active a, .hero.is-white-dark .tabs.is-toggle li.is-active a:hover {\n      background-color: #0a0a0a;\n      border-color: #0a0a0a;\n      color: #e6e6e6; }\n    .hero.is-white.is-bold, .hero.is-white-dark.is-bold {\n      background-image: linear-gradient(141deg, #d1c7c9 0%, #e6e6e6 71%, #f3f2f2 100%); } }\n    @media screen and (prefers-color-scheme: dark) and (max-width: 768px) {\n      .hero.is-white.is-bold .navbar-menu, .hero.is-white-dark.is-bold .navbar-menu {\n        background-image: linear-gradient(141deg, #d1c7c9 0%, #e6e6e6 71%, #f3f2f2 100%); } }\n@media (prefers-color-scheme: dark) {\n  .hero.is-black, .hero.is-black-dark {\n    background-color: black;\n    color: white; }\n    .hero.is-black a:not(.button):not(.dropdown-item):not(.tag),\n    .hero.is-black strong, .hero.is-black-dark a:not(.button):not(.dropdown-item):not(.tag),\n    .hero.is-black-dark strong {\n      color: inherit; }\n    .hero.is-black .title, .hero.is-black-dark .title {\n      color: white; }\n    .hero.is-black .subtitle, .hero.is-black-dark .subtitle {\n      color: rgba(255, 255, 255, 0.9); }\n      .hero.is-black .subtitle a:not(.button),\n      .hero.is-black .subtitle strong, .hero.is-black-dark .subtitle a:not(.button),\n      .hero.is-black-dark .subtitle strong {\n        color: white; } }\n  @media screen and (prefers-color-scheme: dark) and (max-width: 1023px) {\n    .hero.is-black .navbar-menu, .hero.is-black-dark .navbar-menu {\n      background-color: black; } }\n@media (prefers-color-scheme: dark) {\n    .hero.is-black .navbar-item,\n    .hero.is-black .navbar-link, .hero.is-black-dark .navbar-item,\n    .hero.is-black-dark .navbar-link {\n      color: rgba(255, 255, 255, 0.7); }\n    .hero.is-black a.navbar-item:hover, .hero.is-black a.navbar-item.is-active,\n    .hero.is-black .navbar-link:hover,\n    .hero.is-black .navbar-link.is-active, .hero.is-black-dark a.navbar-item:hover, .hero.is-black-dark a.navbar-item.is-active,\n    .hero.is-black-dark .navbar-link:hover,\n    .hero.is-black-dark .navbar-link.is-active {\n      background-color: black;\n      color: white; }\n    .hero.is-black .tabs a, .hero.is-black-dark .tabs a {\n      color: white;\n      opacity: 0.9; }\n      .hero.is-black .tabs a:hover, .hero.is-black-dark .tabs a:hover {\n        opacity: 1; }\n    .hero.is-black .tabs li.is-active a, .hero.is-black-dark .tabs li.is-active a {\n      opacity: 1; }\n    .hero.is-black .tabs.is-boxed a, .hero.is-black .tabs.is-toggle a, .hero.is-black-dark .tabs.is-boxed a, .hero.is-black-dark .tabs.is-toggle a {\n      color: white; }\n      .hero.is-black .tabs.is-boxed a:hover, .hero.is-black .tabs.is-toggle a:hover, .hero.is-black-dark .tabs.is-boxed a:hover, .hero.is-black-dark .tabs.is-toggle a:hover {\n        background-color: rgba(10, 10, 10, 0.1); }\n    .hero.is-black .tabs.is-boxed li.is-active a, .hero.is-black .tabs.is-boxed li.is-active a:hover, .hero.is-black .tabs.is-toggle li.is-active a, .hero.is-black .tabs.is-toggle li.is-active a:hover, .hero.is-black-dark .tabs.is-boxed li.is-active a, .hero.is-black-dark .tabs.is-boxed li.is-active a:hover, .hero.is-black-dark .tabs.is-toggle li.is-active a, .hero.is-black-dark .tabs.is-toggle li.is-active a:hover {\n      background-color: white;\n      border-color: white;\n      color: black; }\n    .hero.is-black.is-bold, .hero.is-black-dark.is-bold {\n      background-image: linear-gradient(141deg, black 0%, black 71%, #0d0c0c 100%); } }\n    @media screen and (prefers-color-scheme: dark) and (max-width: 768px) {\n      .hero.is-black.is-bold .navbar-menu, .hero.is-black-dark.is-bold .navbar-menu {\n        background-image: linear-gradient(141deg, black 0%, black 71%, #0d0c0c 100%); } }\n@media (prefers-color-scheme: dark) {\n  .hero.is-light, .hero.is-light-dark {\n    background-color: #dbdbdb;\n    color: rgba(0, 0, 0, 0.7); }\n    .hero.is-light a:not(.button):not(.dropdown-item):not(.tag),\n    .hero.is-light strong, .hero.is-light-dark a:not(.button):not(.dropdown-item):not(.tag),\n    .hero.is-light-dark strong {\n      color: inherit; }\n    .hero.is-light .title, .hero.is-light-dark .title {\n      color: rgba(0, 0, 0, 0.7); }\n    .hero.is-light .subtitle, .hero.is-light-dark .subtitle {\n      color: rgba(0, 0, 0, 0.9); }\n      .hero.is-light .subtitle a:not(.button),\n      .hero.is-light .subtitle strong, .hero.is-light-dark .subtitle a:not(.button),\n      .hero.is-light-dark .subtitle strong {\n        color: rgba(0, 0, 0, 0.7); } }\n  @media screen and (prefers-color-scheme: dark) and (max-width: 1023px) {\n    .hero.is-light .navbar-menu, .hero.is-light-dark .navbar-menu {\n      background-color: #dbdbdb; } }\n@media (prefers-color-scheme: dark) {\n    .hero.is-light .navbar-item,\n    .hero.is-light .navbar-link, .hero.is-light-dark .navbar-item,\n    .hero.is-light-dark .navbar-link {\n      color: rgba(0, 0, 0, 0.7); }\n    .hero.is-light a.navbar-item:hover, .hero.is-light a.navbar-item.is-active,\n    .hero.is-light .navbar-link:hover,\n    .hero.is-light .navbar-link.is-active, .hero.is-light-dark a.navbar-item:hover, .hero.is-light-dark a.navbar-item.is-active,\n    .hero.is-light-dark .navbar-link:hover,\n    .hero.is-light-dark .navbar-link.is-active {\n      background-color: #cfcfcf;\n      color: rgba(0, 0, 0, 0.7); }\n    .hero.is-light .tabs a, .hero.is-light-dark .tabs a {\n      color: rgba(0, 0, 0, 0.7);\n      opacity: 0.9; }\n      .hero.is-light .tabs a:hover, .hero.is-light-dark .tabs a:hover {\n        opacity: 1; }\n    .hero.is-light .tabs li.is-active a, .hero.is-light-dark .tabs li.is-active a {\n      opacity: 1; }\n    .hero.is-light .tabs.is-boxed a, .hero.is-light .tabs.is-toggle a, .hero.is-light-dark .tabs.is-boxed a, .hero.is-light-dark .tabs.is-toggle a {\n      color: rgba(0, 0, 0, 0.7); }\n      .hero.is-light .tabs.is-boxed a:hover, .hero.is-light .tabs.is-toggle a:hover, .hero.is-light-dark .tabs.is-boxed a:hover, .hero.is-light-dark .tabs.is-toggle a:hover {\n        background-color: rgba(10, 10, 10, 0.1); }\n    .hero.is-light .tabs.is-boxed li.is-active a, .hero.is-light .tabs.is-boxed li.is-active a:hover, .hero.is-light .tabs.is-toggle li.is-active a, .hero.is-light .tabs.is-toggle li.is-active a:hover, .hero.is-light-dark .tabs.is-boxed li.is-active a, .hero.is-light-dark .tabs.is-boxed li.is-active a:hover, .hero.is-light-dark .tabs.is-toggle li.is-active a, .hero.is-light-dark .tabs.is-toggle li.is-active a:hover {\n      background-color: rgba(0, 0, 0, 0.7);\n      border-color: rgba(0, 0, 0, 0.7);\n      color: #dbdbdb; }\n    .hero.is-light.is-bold, .hero.is-light-dark.is-bold {\n      background-image: linear-gradient(141deg, #c8bcbe 0%, #dbdbdb 71%, #e9e7e7 100%); } }\n    @media screen and (prefers-color-scheme: dark) and (max-width: 768px) {\n      .hero.is-light.is-bold .navbar-menu, .hero.is-light-dark.is-bold .navbar-menu {\n        background-image: linear-gradient(141deg, #c8bcbe 0%, #dbdbdb 71%, #e9e7e7 100%); } }\n@media (prefers-color-scheme: dark) {\n  .hero.is-dark, .hero.is-dark-dark {\n    background-color: #1c1c1c;\n    color: #fff; }\n    .hero.is-dark a:not(.button):not(.dropdown-item):not(.tag),\n    .hero.is-dark strong, .hero.is-dark-dark a:not(.button):not(.dropdown-item):not(.tag),\n    .hero.is-dark-dark strong {\n      color: inherit; }\n    .hero.is-dark .title, .hero.is-dark-dark .title {\n      color: #fff; }\n    .hero.is-dark .subtitle, .hero.is-dark-dark .subtitle {\n      color: rgba(255, 255, 255, 0.9); }\n      .hero.is-dark .subtitle a:not(.button),\n      .hero.is-dark .subtitle strong, .hero.is-dark-dark .subtitle a:not(.button),\n      .hero.is-dark-dark .subtitle strong {\n        color: #fff; } }\n  @media screen and (prefers-color-scheme: dark) and (max-width: 1023px) {\n    .hero.is-dark .navbar-menu, .hero.is-dark-dark .navbar-menu {\n      background-color: #1c1c1c; } }\n@media (prefers-color-scheme: dark) {\n    .hero.is-dark .navbar-item,\n    .hero.is-dark .navbar-link, .hero.is-dark-dark .navbar-item,\n    .hero.is-dark-dark .navbar-link {\n      color: rgba(255, 255, 255, 0.7); }\n    .hero.is-dark a.navbar-item:hover, .hero.is-dark a.navbar-item.is-active,\n    .hero.is-dark .navbar-link:hover,\n    .hero.is-dark .navbar-link.is-active, .hero.is-dark-dark a.navbar-item:hover, .hero.is-dark-dark a.navbar-item.is-active,\n    .hero.is-dark-dark .navbar-link:hover,\n    .hero.is-dark-dark .navbar-link.is-active {\n      background-color: #0f0f0f;\n      color: #fff; }\n    .hero.is-dark .tabs a, .hero.is-dark-dark .tabs a {\n      color: #fff;\n      opacity: 0.9; }\n      .hero.is-dark .tabs a:hover, .hero.is-dark-dark .tabs a:hover {\n        opacity: 1; }\n    .hero.is-dark .tabs li.is-active a, .hero.is-dark-dark .tabs li.is-active a {\n      opacity: 1; }\n    .hero.is-dark .tabs.is-boxed a, .hero.is-dark .tabs.is-toggle a, .hero.is-dark-dark .tabs.is-boxed a, .hero.is-dark-dark .tabs.is-toggle a {\n      color: #fff; }\n      .hero.is-dark .tabs.is-boxed a:hover, .hero.is-dark .tabs.is-toggle a:hover, .hero.is-dark-dark .tabs.is-boxed a:hover, .hero.is-dark-dark .tabs.is-toggle a:hover {\n        background-color: rgba(10, 10, 10, 0.1); }\n    .hero.is-dark .tabs.is-boxed li.is-active a, .hero.is-dark .tabs.is-boxed li.is-active a:hover, .hero.is-dark .tabs.is-toggle li.is-active a, .hero.is-dark .tabs.is-toggle li.is-active a:hover, .hero.is-dark-dark .tabs.is-boxed li.is-active a, .hero.is-dark-dark .tabs.is-boxed li.is-active a:hover, .hero.is-dark-dark .tabs.is-toggle li.is-active a, .hero.is-dark-dark .tabs.is-toggle li.is-active a:hover {\n      background-color: #fff;\n      border-color: #fff;\n      color: #1c1c1c; }\n    .hero.is-dark.is-bold, .hero.is-dark-dark.is-bold {\n      background-image: linear-gradient(141deg, #030202 0%, #1c1c1c 71%, #2b2727 100%); } }\n    @media screen and (prefers-color-scheme: dark) and (max-width: 768px) {\n      .hero.is-dark.is-bold .navbar-menu, .hero.is-dark-dark.is-bold .navbar-menu {\n        background-image: linear-gradient(141deg, #030202 0%, #1c1c1c 71%, #2b2727 100%); } }\n@media (prefers-color-scheme: dark) {\n  .hero.is-primary, .hero.is-primary-dark {\n    background-color: #d90052;\n    color: #fff; }\n    .hero.is-primary a:not(.button):not(.dropdown-item):not(.tag),\n    .hero.is-primary strong, .hero.is-primary-dark a:not(.button):not(.dropdown-item):not(.tag),\n    .hero.is-primary-dark strong {\n      color: inherit; }\n    .hero.is-primary .title, .hero.is-primary-dark .title {\n      color: #fff; }\n    .hero.is-primary .subtitle, .hero.is-primary-dark .subtitle {\n      color: rgba(255, 255, 255, 0.9); }\n      .hero.is-primary .subtitle a:not(.button),\n      .hero.is-primary .subtitle strong, .hero.is-primary-dark .subtitle a:not(.button),\n      .hero.is-primary-dark .subtitle strong {\n        color: #fff; } }\n  @media screen and (prefers-color-scheme: dark) and (max-width: 1023px) {\n    .hero.is-primary .navbar-menu, .hero.is-primary-dark .navbar-menu {\n      background-color: #d90052; } }\n@media (prefers-color-scheme: dark) {\n    .hero.is-primary .navbar-item,\n    .hero.is-primary .navbar-link, .hero.is-primary-dark .navbar-item,\n    .hero.is-primary-dark .navbar-link {\n      color: rgba(255, 255, 255, 0.7); }\n    .hero.is-primary a.navbar-item:hover, .hero.is-primary a.navbar-item.is-active,\n    .hero.is-primary .navbar-link:hover,\n    .hero.is-primary .navbar-link.is-active, .hero.is-primary-dark a.navbar-item:hover, .hero.is-primary-dark a.navbar-item.is-active,\n    .hero.is-primary-dark .navbar-link:hover,\n    .hero.is-primary-dark .navbar-link.is-active {\n      background-color: #c00048;\n      color: #fff; }\n    .hero.is-primary .tabs a, .hero.is-primary-dark .tabs a {\n      color: #fff;\n      opacity: 0.9; }\n      .hero.is-primary .tabs a:hover, .hero.is-primary-dark .tabs a:hover {\n        opacity: 1; }\n    .hero.is-primary .tabs li.is-active a, .hero.is-primary-dark .tabs li.is-active a {\n      opacity: 1; }\n    .hero.is-primary .tabs.is-boxed a, .hero.is-primary .tabs.is-toggle a, .hero.is-primary-dark .tabs.is-boxed a, .hero.is-primary-dark .tabs.is-toggle a {\n      color: #fff; }\n      .hero.is-primary .tabs.is-boxed a:hover, .hero.is-primary .tabs.is-toggle a:hover, .hero.is-primary-dark .tabs.is-boxed a:hover, .hero.is-primary-dark .tabs.is-toggle a:hover {\n        background-color: rgba(10, 10, 10, 0.1); }\n    .hero.is-primary .tabs.is-boxed li.is-active a, .hero.is-primary .tabs.is-boxed li.is-active a:hover, .hero.is-primary .tabs.is-toggle li.is-active a, .hero.is-primary .tabs.is-toggle li.is-active a:hover, .hero.is-primary-dark .tabs.is-boxed li.is-active a, .hero.is-primary-dark .tabs.is-boxed li.is-active a:hover, .hero.is-primary-dark .tabs.is-toggle li.is-active a, .hero.is-primary-dark .tabs.is-toggle li.is-active a:hover {\n      background-color: #fff;\n      border-color: #fff;\n      color: #d90052; }\n    .hero.is-primary.is-bold, .hero.is-primary-dark.is-bold {\n      background-image: linear-gradient(141deg, #a6005a 0%, #d90052 71%, #f30033 100%); } }\n    @media screen and (prefers-color-scheme: dark) and (max-width: 768px) {\n      .hero.is-primary.is-bold .navbar-menu, .hero.is-primary-dark.is-bold .navbar-menu {\n        background-image: linear-gradient(141deg, #a6005a 0%, #d90052 71%, #f30033 100%); } }\n@media (prefers-color-scheme: dark) {\n  .hero.is-link, .hero.is-link-dark {\n    background-color: #d90052;\n    color: #fff; }\n    .hero.is-link a:not(.button):not(.dropdown-item):not(.tag),\n    .hero.is-link strong, .hero.is-link-dark a:not(.button):not(.dropdown-item):not(.tag),\n    .hero.is-link-dark strong {\n      color: inherit; }\n    .hero.is-link .title, .hero.is-link-dark .title {\n      color: #fff; }\n    .hero.is-link .subtitle, .hero.is-link-dark .subtitle {\n      color: rgba(255, 255, 255, 0.9); }\n      .hero.is-link .subtitle a:not(.button),\n      .hero.is-link .subtitle strong, .hero.is-link-dark .subtitle a:not(.button),\n      .hero.is-link-dark .subtitle strong {\n        color: #fff; } }\n  @media screen and (prefers-color-scheme: dark) and (max-width: 1023px) {\n    .hero.is-link .navbar-menu, .hero.is-link-dark .navbar-menu {\n      background-color: #d90052; } }\n@media (prefers-color-scheme: dark) {\n    .hero.is-link .navbar-item,\n    .hero.is-link .navbar-link, .hero.is-link-dark .navbar-item,\n    .hero.is-link-dark .navbar-link {\n      color: rgba(255, 255, 255, 0.7); }\n    .hero.is-link a.navbar-item:hover, .hero.is-link a.navbar-item.is-active,\n    .hero.is-link .navbar-link:hover,\n    .hero.is-link .navbar-link.is-active, .hero.is-link-dark a.navbar-item:hover, .hero.is-link-dark a.navbar-item.is-active,\n    .hero.is-link-dark .navbar-link:hover,\n    .hero.is-link-dark .navbar-link.is-active {\n      background-color: #c00048;\n      color: #fff; }\n    .hero.is-link .tabs a, .hero.is-link-dark .tabs a {\n      color: #fff;\n      opacity: 0.9; }\n      .hero.is-link .tabs a:hover, .hero.is-link-dark .tabs a:hover {\n        opacity: 1; }\n    .hero.is-link .tabs li.is-active a, .hero.is-link-dark .tabs li.is-active a {\n      opacity: 1; }\n    .hero.is-link .tabs.is-boxed a, .hero.is-link .tabs.is-toggle a, .hero.is-link-dark .tabs.is-boxed a, .hero.is-link-dark .tabs.is-toggle a {\n      color: #fff; }\n      .hero.is-link .tabs.is-boxed a:hover, .hero.is-link .tabs.is-toggle a:hover, .hero.is-link-dark .tabs.is-boxed a:hover, .hero.is-link-dark .tabs.is-toggle a:hover {\n        background-color: rgba(10, 10, 10, 0.1); }\n    .hero.is-link .tabs.is-boxed li.is-active a, .hero.is-link .tabs.is-boxed li.is-active a:hover, .hero.is-link .tabs.is-toggle li.is-active a, .hero.is-link .tabs.is-toggle li.is-active a:hover, .hero.is-link-dark .tabs.is-boxed li.is-active a, .hero.is-link-dark .tabs.is-boxed li.is-active a:hover, .hero.is-link-dark .tabs.is-toggle li.is-active a, .hero.is-link-dark .tabs.is-toggle li.is-active a:hover {\n      background-color: #fff;\n      border-color: #fff;\n      color: #d90052; }\n    .hero.is-link.is-bold, .hero.is-link-dark.is-bold {\n      background-image: linear-gradient(141deg, #a6005a 0%, #d90052 71%, #f30033 100%); } }\n    @media screen and (prefers-color-scheme: dark) and (max-width: 768px) {\n      .hero.is-link.is-bold .navbar-menu, .hero.is-link-dark.is-bold .navbar-menu {\n        background-image: linear-gradient(141deg, #a6005a 0%, #d90052 71%, #f30033 100%); } }\n@media (prefers-color-scheme: dark) {\n  .hero.is-info, .hero.is-info-dark {\n    background-color: #0075cc;\n    color: #fff; }\n    .hero.is-info a:not(.button):not(.dropdown-item):not(.tag),\n    .hero.is-info strong, .hero.is-info-dark a:not(.button):not(.dropdown-item):not(.tag),\n    .hero.is-info-dark strong {\n      color: inherit; }\n    .hero.is-info .title, .hero.is-info-dark .title {\n      color: #fff; }\n    .hero.is-info .subtitle, .hero.is-info-dark .subtitle {\n      color: rgba(255, 255, 255, 0.9); }\n      .hero.is-info .subtitle a:not(.button),\n      .hero.is-info .subtitle strong, .hero.is-info-dark .subtitle a:not(.button),\n      .hero.is-info-dark .subtitle strong {\n        color: #fff; } }\n  @media screen and (prefers-color-scheme: dark) and (max-width: 1023px) {\n    .hero.is-info .navbar-menu, .hero.is-info-dark .navbar-menu {\n      background-color: #0075cc; } }\n@media (prefers-color-scheme: dark) {\n    .hero.is-info .navbar-item,\n    .hero.is-info .navbar-link, .hero.is-info-dark .navbar-item,\n    .hero.is-info-dark .navbar-link {\n      color: rgba(255, 255, 255, 0.7); }\n    .hero.is-info a.navbar-item:hover, .hero.is-info a.navbar-item.is-active,\n    .hero.is-info .navbar-link:hover,\n    .hero.is-info .navbar-link.is-active, .hero.is-info-dark a.navbar-item:hover, .hero.is-info-dark a.navbar-item.is-active,\n    .hero.is-info-dark .navbar-link:hover,\n    .hero.is-info-dark .navbar-link.is-active {\n      background-color: #0066b3;\n      color: #fff; }\n    .hero.is-info .tabs a, .hero.is-info-dark .tabs a {\n      color: #fff;\n      opacity: 0.9; }\n      .hero.is-info .tabs a:hover, .hero.is-info-dark .tabs a:hover {\n        opacity: 1; }\n    .hero.is-info .tabs li.is-active a, .hero.is-info-dark .tabs li.is-active a {\n      opacity: 1; }\n    .hero.is-info .tabs.is-boxed a, .hero.is-info .tabs.is-toggle a, .hero.is-info-dark .tabs.is-boxed a, .hero.is-info-dark .tabs.is-toggle a {\n      color: #fff; }\n      .hero.is-info .tabs.is-boxed a:hover, .hero.is-info .tabs.is-toggle a:hover, .hero.is-info-dark .tabs.is-boxed a:hover, .hero.is-info-dark .tabs.is-toggle a:hover {\n        background-color: rgba(10, 10, 10, 0.1); }\n    .hero.is-info .tabs.is-boxed li.is-active a, .hero.is-info .tabs.is-boxed li.is-active a:hover, .hero.is-info .tabs.is-toggle li.is-active a, .hero.is-info .tabs.is-toggle li.is-active a:hover, .hero.is-info-dark .tabs.is-boxed li.is-active a, .hero.is-info-dark .tabs.is-boxed li.is-active a:hover, .hero.is-info-dark .tabs.is-toggle li.is-active a, .hero.is-info-dark .tabs.is-toggle li.is-active a:hover {\n      background-color: #fff;\n      border-color: #fff;\n      color: #0075cc; }\n    .hero.is-info.is-bold, .hero.is-info-dark.is-bold {\n      background-image: linear-gradient(141deg, #007199 0%, #0075cc 71%, #005de6 100%); } }\n    @media screen and (prefers-color-scheme: dark) and (max-width: 768px) {\n      .hero.is-info.is-bold .navbar-menu, .hero.is-info-dark.is-bold .navbar-menu {\n        background-image: linear-gradient(141deg, #007199 0%, #0075cc 71%, #005de6 100%); } }\n@media (prefers-color-scheme: dark) {\n  .hero.is-success, .hero.is-success-dark {\n    background-color: #11ad74;\n    color: rgba(0, 0, 0, 0.7); }\n    .hero.is-success a:not(.button):not(.dropdown-item):not(.tag),\n    .hero.is-success strong, .hero.is-success-dark a:not(.button):not(.dropdown-item):not(.tag),\n    .hero.is-success-dark strong {\n      color: inherit; }\n    .hero.is-success .title, .hero.is-success-dark .title {\n      color: rgba(0, 0, 0, 0.7); }\n    .hero.is-success .subtitle, .hero.is-success-dark .subtitle {\n      color: rgba(0, 0, 0, 0.9); }\n      .hero.is-success .subtitle a:not(.button),\n      .hero.is-success .subtitle strong, .hero.is-success-dark .subtitle a:not(.button),\n      .hero.is-success-dark .subtitle strong {\n        color: rgba(0, 0, 0, 0.7); } }\n  @media screen and (prefers-color-scheme: dark) and (max-width: 1023px) {\n    .hero.is-success .navbar-menu, .hero.is-success-dark .navbar-menu {\n      background-color: #11ad74; } }\n@media (prefers-color-scheme: dark) {\n    .hero.is-success .navbar-item,\n    .hero.is-success .navbar-link, .hero.is-success-dark .navbar-item,\n    .hero.is-success-dark .navbar-link {\n      color: rgba(0, 0, 0, 0.7); }\n    .hero.is-success a.navbar-item:hover, .hero.is-success a.navbar-item.is-active,\n    .hero.is-success .navbar-link:hover,\n    .hero.is-success .navbar-link.is-active, .hero.is-success-dark a.navbar-item:hover, .hero.is-success-dark a.navbar-item.is-active,\n    .hero.is-success-dark .navbar-link:hover,\n    .hero.is-success-dark .navbar-link.is-active {\n      background-color: #0f9564;\n      color: rgba(0, 0, 0, 0.7); }\n    .hero.is-success .tabs a, .hero.is-success-dark .tabs a {\n      color: rgba(0, 0, 0, 0.7);\n      opacity: 0.9; }\n      .hero.is-success .tabs a:hover, .hero.is-success-dark .tabs a:hover {\n        opacity: 1; }\n    .hero.is-success .tabs li.is-active a, .hero.is-success-dark .tabs li.is-active a {\n      opacity: 1; }\n    .hero.is-success .tabs.is-boxed a, .hero.is-success .tabs.is-toggle a, .hero.is-success-dark .tabs.is-boxed a, .hero.is-success-dark .tabs.is-toggle a {\n      color: rgba(0, 0, 0, 0.7); }\n      .hero.is-success .tabs.is-boxed a:hover, .hero.is-success .tabs.is-toggle a:hover, .hero.is-success-dark .tabs.is-boxed a:hover, .hero.is-success-dark .tabs.is-toggle a:hover {\n        background-color: rgba(10, 10, 10, 0.1); }\n    .hero.is-success .tabs.is-boxed li.is-active a, .hero.is-success .tabs.is-boxed li.is-active a:hover, .hero.is-success .tabs.is-toggle li.is-active a, .hero.is-success .tabs.is-toggle li.is-active a:hover, .hero.is-success-dark .tabs.is-boxed li.is-active a, .hero.is-success-dark .tabs.is-boxed li.is-active a:hover, .hero.is-success-dark .tabs.is-toggle li.is-active a, .hero.is-success-dark .tabs.is-toggle li.is-active a:hover {\n      background-color: rgba(0, 0, 0, 0.7);\n      border-color: rgba(0, 0, 0, 0.7);\n      color: #11ad74; }\n    .hero.is-success.is-bold, .hero.is-success-dark.is-bold {\n      background-image: linear-gradient(141deg, #068541 0%, #11ad74 71%, #0ec9a4 100%); } }\n    @media screen and (prefers-color-scheme: dark) and (max-width: 768px) {\n      .hero.is-success.is-bold .navbar-menu, .hero.is-success-dark.is-bold .navbar-menu {\n        background-image: linear-gradient(141deg, #068541 0%, #11ad74 71%, #0ec9a4 100%); } }\n@media (prefers-color-scheme: dark) {\n  .hero.is-warning, .hero.is-warning-dark {\n    background-color: #ccba00;\n    color: rgba(0, 0, 0, 0.7); }\n    .hero.is-warning a:not(.button):not(.dropdown-item):not(.tag),\n    .hero.is-warning strong, .hero.is-warning-dark a:not(.button):not(.dropdown-item):not(.tag),\n    .hero.is-warning-dark strong {\n      color: inherit; }\n    .hero.is-warning .title, .hero.is-warning-dark .title {\n      color: rgba(0, 0, 0, 0.7); }\n    .hero.is-warning .subtitle, .hero.is-warning-dark .subtitle {\n      color: rgba(0, 0, 0, 0.9); }\n      .hero.is-warning .subtitle a:not(.button),\n      .hero.is-warning .subtitle strong, .hero.is-warning-dark .subtitle a:not(.button),\n      .hero.is-warning-dark .subtitle strong {\n        color: rgba(0, 0, 0, 0.7); } }\n  @media screen and (prefers-color-scheme: dark) and (max-width: 1023px) {\n    .hero.is-warning .navbar-menu, .hero.is-warning-dark .navbar-menu {\n      background-color: #ccba00; } }\n@media (prefers-color-scheme: dark) {\n    .hero.is-warning .navbar-item,\n    .hero.is-warning .navbar-link, .hero.is-warning-dark .navbar-item,\n    .hero.is-warning-dark .navbar-link {\n      color: rgba(0, 0, 0, 0.7); }\n    .hero.is-warning a.navbar-item:hover, .hero.is-warning a.navbar-item.is-active,\n    .hero.is-warning .navbar-link:hover,\n    .hero.is-warning .navbar-link.is-active, .hero.is-warning-dark a.navbar-item:hover, .hero.is-warning-dark a.navbar-item.is-active,\n    .hero.is-warning-dark .navbar-link:hover,\n    .hero.is-warning-dark .navbar-link.is-active {\n      background-color: #b3a300;\n      color: rgba(0, 0, 0, 0.7); }\n    .hero.is-warning .tabs a, .hero.is-warning-dark .tabs a {\n      color: rgba(0, 0, 0, 0.7);\n      opacity: 0.9; }\n      .hero.is-warning .tabs a:hover, .hero.is-warning-dark .tabs a:hover {\n        opacity: 1; }\n    .hero.is-warning .tabs li.is-active a, .hero.is-warning-dark .tabs li.is-active a {\n      opacity: 1; }\n    .hero.is-warning .tabs.is-boxed a, .hero.is-warning .tabs.is-toggle a, .hero.is-warning-dark .tabs.is-boxed a, .hero.is-warning-dark .tabs.is-toggle a {\n      color: rgba(0, 0, 0, 0.7); }\n      .hero.is-warning .tabs.is-boxed a:hover, .hero.is-warning .tabs.is-toggle a:hover, .hero.is-warning-dark .tabs.is-boxed a:hover, .hero.is-warning-dark .tabs.is-toggle a:hover {\n        background-color: rgba(10, 10, 10, 0.1); }\n    .hero.is-warning .tabs.is-boxed li.is-active a, .hero.is-warning .tabs.is-boxed li.is-active a:hover, .hero.is-warning .tabs.is-toggle li.is-active a, .hero.is-warning .tabs.is-toggle li.is-active a:hover, .hero.is-warning-dark .tabs.is-boxed li.is-active a, .hero.is-warning-dark .tabs.is-boxed li.is-active a:hover, .hero.is-warning-dark .tabs.is-toggle li.is-active a, .hero.is-warning-dark .tabs.is-toggle li.is-active a:hover {\n      background-color: rgba(0, 0, 0, 0.7);\n      border-color: rgba(0, 0, 0, 0.7);\n      color: #ccba00; }\n    .hero.is-warning.is-bold, .hero.is-warning-dark.is-bold {\n      background-image: linear-gradient(141deg, #997200 0%, #ccba00 71%, #d3e600 100%); } }\n    @media screen and (prefers-color-scheme: dark) and (max-width: 768px) {\n      .hero.is-warning.is-bold .navbar-menu, .hero.is-warning-dark.is-bold .navbar-menu {\n        background-image: linear-gradient(141deg, #997200 0%, #ccba00 71%, #d3e600 100%); } }\n@media (prefers-color-scheme: dark) {\n  .hero.is-danger, .hero.is-danger-dark {\n    background-color: #ee1742;\n    color: #fff; }\n    .hero.is-danger a:not(.button):not(.dropdown-item):not(.tag),\n    .hero.is-danger strong, .hero.is-danger-dark a:not(.button):not(.dropdown-item):not(.tag),\n    .hero.is-danger-dark strong {\n      color: inherit; }\n    .hero.is-danger .title, .hero.is-danger-dark .title {\n      color: #fff; }\n    .hero.is-danger .subtitle, .hero.is-danger-dark .subtitle {\n      color: rgba(255, 255, 255, 0.9); }\n      .hero.is-danger .subtitle a:not(.button),\n      .hero.is-danger .subtitle strong, .hero.is-danger-dark .subtitle a:not(.button),\n      .hero.is-danger-dark .subtitle strong {\n        color: #fff; } }\n  @media screen and (prefers-color-scheme: dark) and (max-width: 1023px) {\n    .hero.is-danger .navbar-menu, .hero.is-danger-dark .navbar-menu {\n      background-color: #ee1742; } }\n@media (prefers-color-scheme: dark) {\n    .hero.is-danger .navbar-item,\n    .hero.is-danger .navbar-link, .hero.is-danger-dark .navbar-item,\n    .hero.is-danger-dark .navbar-link {\n      color: rgba(255, 255, 255, 0.7); }\n    .hero.is-danger a.navbar-item:hover, .hero.is-danger a.navbar-item.is-active,\n    .hero.is-danger .navbar-link:hover,\n    .hero.is-danger .navbar-link.is-active, .hero.is-danger-dark a.navbar-item:hover, .hero.is-danger-dark a.navbar-item.is-active,\n    .hero.is-danger-dark .navbar-link:hover,\n    .hero.is-danger-dark .navbar-link.is-active {\n      background-color: #da1039;\n      color: #fff; }\n    .hero.is-danger .tabs a, .hero.is-danger-dark .tabs a {\n      color: #fff;\n      opacity: 0.9; }\n      .hero.is-danger .tabs a:hover, .hero.is-danger-dark .tabs a:hover {\n        opacity: 1; }\n    .hero.is-danger .tabs li.is-active a, .hero.is-danger-dark .tabs li.is-active a {\n      opacity: 1; }\n    .hero.is-danger .tabs.is-boxed a, .hero.is-danger .tabs.is-toggle a, .hero.is-danger-dark .tabs.is-boxed a, .hero.is-danger-dark .tabs.is-toggle a {\n      color: #fff; }\n      .hero.is-danger .tabs.is-boxed a:hover, .hero.is-danger .tabs.is-toggle a:hover, .hero.is-danger-dark .tabs.is-boxed a:hover, .hero.is-danger-dark .tabs.is-toggle a:hover {\n        background-color: rgba(10, 10, 10, 0.1); }\n    .hero.is-danger .tabs.is-boxed li.is-active a, .hero.is-danger .tabs.is-boxed li.is-active a:hover, .hero.is-danger .tabs.is-toggle li.is-active a, .hero.is-danger .tabs.is-toggle li.is-active a:hover, .hero.is-danger-dark .tabs.is-boxed li.is-active a, .hero.is-danger-dark .tabs.is-boxed li.is-active a:hover, .hero.is-danger-dark .tabs.is-toggle li.is-active a, .hero.is-danger-dark .tabs.is-toggle li.is-active a:hover {\n      background-color: #fff;\n      border-color: #fff;\n      color: #ee1742; }\n    .hero.is-danger.is-bold, .hero.is-danger-dark.is-bold {\n      background-image: linear-gradient(141deg, #cd044e 0%, #ee1742 71%, #f52930 100%); } }\n    @media screen and (prefers-color-scheme: dark) and (max-width: 768px) {\n      .hero.is-danger.is-bold .navbar-menu, .hero.is-danger-dark.is-bold .navbar-menu {\n        background-image: linear-gradient(141deg, #cd044e 0%, #ee1742 71%, #f52930 100%); } }\n@media (prefers-color-scheme: dark) {\n  .hero.is-white-dark {\n    background-color: white;\n    color: #0a0a0a; }\n    .hero.is-white-dark a:not(.button):not(.dropdown-item):not(.tag),\n    .hero.is-white-dark strong {\n      color: inherit; }\n    .hero.is-white-dark .title {\n      color: #0a0a0a; }\n    .hero.is-white-dark .subtitle {\n      color: rgba(10, 10, 10, 0.9); }\n      .hero.is-white-dark .subtitle a:not(.button),\n      .hero.is-white-dark .subtitle strong {\n        color: #0a0a0a; } }\n  @media screen and (prefers-color-scheme: dark) and (max-width: 1023px) {\n    .hero.is-white-dark .navbar-menu {\n      background-color: white; } }\n@media (prefers-color-scheme: dark) {\n    .hero.is-white-dark .navbar-item,\n    .hero.is-white-dark .navbar-link {\n      color: rgba(10, 10, 10, 0.7); }\n    .hero.is-white-dark a.navbar-item:hover, .hero.is-white-dark a.navbar-item.is-active,\n    .hero.is-white-dark .navbar-link:hover,\n    .hero.is-white-dark .navbar-link.is-active {\n      background-color: #f2f2f2;\n      color: #0a0a0a; }\n    .hero.is-white-dark .tabs a {\n      color: #0a0a0a;\n      opacity: 0.9; }\n      .hero.is-white-dark .tabs a:hover {\n        opacity: 1; }\n    .hero.is-white-dark .tabs li.is-active a {\n      opacity: 1; }\n    .hero.is-white-dark .tabs.is-boxed a, .hero.is-white-dark .tabs.is-toggle a {\n      color: #0a0a0a; }\n      .hero.is-white-dark .tabs.is-boxed a:hover, .hero.is-white-dark .tabs.is-toggle a:hover {\n        background-color: rgba(10, 10, 10, 0.1); }\n    .hero.is-white-dark .tabs.is-boxed li.is-active a, .hero.is-white-dark .tabs.is-boxed li.is-active a:hover, .hero.is-white-dark .tabs.is-toggle li.is-active a, .hero.is-white-dark .tabs.is-toggle li.is-active a:hover {\n      background-color: #0a0a0a;\n      border-color: #0a0a0a;\n      color: white; }\n    .hero.is-white-dark.is-bold {\n      background-image: linear-gradient(141deg, #e8e3e4 0%, white 71%, white 100%); } }\n    @media screen and (prefers-color-scheme: dark) and (max-width: 768px) {\n      .hero.is-white-dark.is-bold .navbar-menu {\n        background-image: linear-gradient(141deg, #e8e3e4 0%, white 71%, white 100%); } }\n@media (prefers-color-scheme: dark) {\n  .hero.is-black-dark {\n    background-color: #0a0a0a;\n    color: white; }\n    .hero.is-black-dark a:not(.button):not(.dropdown-item):not(.tag),\n    .hero.is-black-dark strong {\n      color: inherit; }\n    .hero.is-black-dark .title {\n      color: white; }\n    .hero.is-black-dark .subtitle {\n      color: rgba(255, 255, 255, 0.9); }\n      .hero.is-black-dark .subtitle a:not(.button),\n      .hero.is-black-dark .subtitle strong {\n        color: white; } }\n  @media screen and (prefers-color-scheme: dark) and (max-width: 1023px) {\n    .hero.is-black-dark .navbar-menu {\n      background-color: #0a0a0a; } }\n@media (prefers-color-scheme: dark) {\n    .hero.is-black-dark .navbar-item,\n    .hero.is-black-dark .navbar-link {\n      color: rgba(255, 255, 255, 0.7); }\n    .hero.is-black-dark a.navbar-item:hover, .hero.is-black-dark a.navbar-item.is-active,\n    .hero.is-black-dark .navbar-link:hover,\n    .hero.is-black-dark .navbar-link.is-active {\n      background-color: black;\n      color: white; }\n    .hero.is-black-dark .tabs a {\n      color: white;\n      opacity: 0.9; }\n      .hero.is-black-dark .tabs a:hover {\n        opacity: 1; }\n    .hero.is-black-dark .tabs li.is-active a {\n      opacity: 1; }\n    .hero.is-black-dark .tabs.is-boxed a, .hero.is-black-dark .tabs.is-toggle a {\n      color: white; }\n      .hero.is-black-dark .tabs.is-boxed a:hover, .hero.is-black-dark .tabs.is-toggle a:hover {\n        background-color: rgba(10, 10, 10, 0.1); }\n    .hero.is-black-dark .tabs.is-boxed li.is-active a, .hero.is-black-dark .tabs.is-boxed li.is-active a:hover, .hero.is-black-dark .tabs.is-toggle li.is-active a, .hero.is-black-dark .tabs.is-toggle li.is-active a:hover {\n      background-color: white;\n      border-color: white;\n      color: #0a0a0a; }\n    .hero.is-black-dark.is-bold {\n      background-image: linear-gradient(141deg, black 0%, #0a0a0a 71%, #181616 100%); } }\n    @media screen and (prefers-color-scheme: dark) and (max-width: 768px) {\n      .hero.is-black-dark.is-bold .navbar-menu {\n        background-image: linear-gradient(141deg, black 0%, #0a0a0a 71%, #181616 100%); } }\n@media (prefers-color-scheme: dark) {\n  .hero.is-light-dark {\n    background-color: whitesmoke;\n    color: rgba(0, 0, 0, 0.7); }\n    .hero.is-light-dark a:not(.button):not(.dropdown-item):not(.tag),\n    .hero.is-light-dark strong {\n      color: inherit; }\n    .hero.is-light-dark .title {\n      color: rgba(0, 0, 0, 0.7); }\n    .hero.is-light-dark .subtitle {\n      color: rgba(0, 0, 0, 0.9); }\n      .hero.is-light-dark .subtitle a:not(.button),\n      .hero.is-light-dark .subtitle strong {\n        color: rgba(0, 0, 0, 0.7); } }\n  @media screen and (prefers-color-scheme: dark) and (max-width: 1023px) {\n    .hero.is-light-dark .navbar-menu {\n      background-color: whitesmoke; } }\n@media (prefers-color-scheme: dark) {\n    .hero.is-light-dark .navbar-item,\n    .hero.is-light-dark .navbar-link {\n      color: rgba(0, 0, 0, 0.7); }\n    .hero.is-light-dark a.navbar-item:hover, .hero.is-light-dark a.navbar-item.is-active,\n    .hero.is-light-dark .navbar-link:hover,\n    .hero.is-light-dark .navbar-link.is-active {\n      background-color: #e8e8e8;\n      color: rgba(0, 0, 0, 0.7); }\n    .hero.is-light-dark .tabs a {\n      color: rgba(0, 0, 0, 0.7);\n      opacity: 0.9; }\n      .hero.is-light-dark .tabs a:hover {\n        opacity: 1; }\n    .hero.is-light-dark .tabs li.is-active a {\n      opacity: 1; }\n    .hero.is-light-dark .tabs.is-boxed a, .hero.is-light-dark .tabs.is-toggle a {\n      color: rgba(0, 0, 0, 0.7); }\n      .hero.is-light-dark .tabs.is-boxed a:hover, .hero.is-light-dark .tabs.is-toggle a:hover {\n        background-color: rgba(10, 10, 10, 0.1); }\n    .hero.is-light-dark .tabs.is-boxed li.is-active a, .hero.is-light-dark .tabs.is-boxed li.is-active a:hover, .hero.is-light-dark .tabs.is-toggle li.is-active a, .hero.is-light-dark .tabs.is-toggle li.is-active a:hover {\n      background-color: rgba(0, 0, 0, 0.7);\n      border-color: rgba(0, 0, 0, 0.7);\n      color: whitesmoke; }\n    .hero.is-light-dark.is-bold {\n      background-image: linear-gradient(141deg, #dfd8d9 0%, whitesmoke 71%, white 100%); } }\n    @media screen and (prefers-color-scheme: dark) and (max-width: 768px) {\n      .hero.is-light-dark.is-bold .navbar-menu {\n        background-image: linear-gradient(141deg, #dfd8d9 0%, whitesmoke 71%, white 100%); } }\n@media (prefers-color-scheme: dark) {\n  .hero.is-dark-dark {\n    background-color: #363636;\n    color: #fff; }\n    .hero.is-dark-dark a:not(.button):not(.dropdown-item):not(.tag),\n    .hero.is-dark-dark strong {\n      color: inherit; }\n    .hero.is-dark-dark .title {\n      color: #fff; }\n    .hero.is-dark-dark .subtitle {\n      color: rgba(255, 255, 255, 0.9); }\n      .hero.is-dark-dark .subtitle a:not(.button),\n      .hero.is-dark-dark .subtitle strong {\n        color: #fff; } }\n  @media screen and (prefers-color-scheme: dark) and (max-width: 1023px) {\n    .hero.is-dark-dark .navbar-menu {\n      background-color: #363636; } }\n@media (prefers-color-scheme: dark) {\n    .hero.is-dark-dark .navbar-item,\n    .hero.is-dark-dark .navbar-link {\n      color: rgba(255, 255, 255, 0.7); }\n    .hero.is-dark-dark a.navbar-item:hover, .hero.is-dark-dark a.navbar-item.is-active,\n    .hero.is-dark-dark .navbar-link:hover,\n    .hero.is-dark-dark .navbar-link.is-active {\n      background-color: #292929;\n      color: #fff; }\n    .hero.is-dark-dark .tabs a {\n      color: #fff;\n      opacity: 0.9; }\n      .hero.is-dark-dark .tabs a:hover {\n        opacity: 1; }\n    .hero.is-dark-dark .tabs li.is-active a {\n      opacity: 1; }\n    .hero.is-dark-dark .tabs.is-boxed a, .hero.is-dark-dark .tabs.is-toggle a {\n      color: #fff; }\n      .hero.is-dark-dark .tabs.is-boxed a:hover, .hero.is-dark-dark .tabs.is-toggle a:hover {\n        background-color: rgba(10, 10, 10, 0.1); }\n    .hero.is-dark-dark .tabs.is-boxed li.is-active a, .hero.is-dark-dark .tabs.is-boxed li.is-active a:hover, .hero.is-dark-dark .tabs.is-toggle li.is-active a, .hero.is-dark-dark .tabs.is-toggle li.is-active a:hover {\n      background-color: #fff;\n      border-color: #fff;\n      color: #363636; }\n    .hero.is-dark-dark.is-bold {\n      background-image: linear-gradient(141deg, #1f191a 0%, #363636 71%, #46403f 100%); } }\n    @media screen and (prefers-color-scheme: dark) and (max-width: 768px) {\n      .hero.is-dark-dark.is-bold .navbar-menu {\n        background-image: linear-gradient(141deg, #1f191a 0%, #363636 71%, #46403f 100%); } }\n@media (prefers-color-scheme: dark) {\n  .hero.is-primary-dark {\n    background-color: #ff0d68;\n    color: #fff; }\n    .hero.is-primary-dark a:not(.button):not(.dropdown-item):not(.tag),\n    .hero.is-primary-dark strong {\n      color: inherit; }\n    .hero.is-primary-dark .title {\n      color: #fff; }\n    .hero.is-primary-dark .subtitle {\n      color: rgba(255, 255, 255, 0.9); }\n      .hero.is-primary-dark .subtitle a:not(.button),\n      .hero.is-primary-dark .subtitle strong {\n        color: #fff; } }\n  @media screen and (prefers-color-scheme: dark) and (max-width: 1023px) {\n    .hero.is-primary-dark .navbar-menu {\n      background-color: #ff0d68; } }\n@media (prefers-color-scheme: dark) {\n    .hero.is-primary-dark .navbar-item,\n    .hero.is-primary-dark .navbar-link {\n      color: rgba(255, 255, 255, 0.7); }\n    .hero.is-primary-dark a.navbar-item:hover, .hero.is-primary-dark a.navbar-item.is-active,\n    .hero.is-primary-dark .navbar-link:hover,\n    .hero.is-primary-dark .navbar-link.is-active {\n      background-color: #f3005b;\n      color: #fff; }\n    .hero.is-primary-dark .tabs a {\n      color: #fff;\n      opacity: 0.9; }\n      .hero.is-primary-dark .tabs a:hover {\n        opacity: 1; }\n    .hero.is-primary-dark .tabs li.is-active a {\n      opacity: 1; }\n    .hero.is-primary-dark .tabs.is-boxed a, .hero.is-primary-dark .tabs.is-toggle a {\n      color: #fff; }\n      .hero.is-primary-dark .tabs.is-boxed a:hover, .hero.is-primary-dark .tabs.is-toggle a:hover {\n        background-color: rgba(10, 10, 10, 0.1); }\n    .hero.is-primary-dark .tabs.is-boxed li.is-active a, .hero.is-primary-dark .tabs.is-boxed li.is-active a:hover, .hero.is-primary-dark .tabs.is-toggle li.is-active a, .hero.is-primary-dark .tabs.is-toggle li.is-active a:hover {\n      background-color: #fff;\n      border-color: #fff;\n      color: #ff0d68; }\n    .hero.is-primary-dark.is-bold {\n      background-image: linear-gradient(141deg, #d90076 0%, #ff0d68 71%, #ff2754 100%); } }\n    @media screen and (prefers-color-scheme: dark) and (max-width: 768px) {\n      .hero.is-primary-dark.is-bold .navbar-menu {\n        background-image: linear-gradient(141deg, #d90076 0%, #ff0d68 71%, #ff2754 100%); } }\n@media (prefers-color-scheme: dark) {\n  .hero.is-link-dark {\n    background-color: #ff0d68;\n    color: #fff; }\n    .hero.is-link-dark a:not(.button):not(.dropdown-item):not(.tag),\n    .hero.is-link-dark strong {\n      color: inherit; }\n    .hero.is-link-dark .title {\n      color: #fff; }\n    .hero.is-link-dark .subtitle {\n      color: rgba(255, 255, 255, 0.9); }\n      .hero.is-link-dark .subtitle a:not(.button),\n      .hero.is-link-dark .subtitle strong {\n        color: #fff; } }\n  @media screen and (prefers-color-scheme: dark) and (max-width: 1023px) {\n    .hero.is-link-dark .navbar-menu {\n      background-color: #ff0d68; } }\n@media (prefers-color-scheme: dark) {\n    .hero.is-link-dark .navbar-item,\n    .hero.is-link-dark .navbar-link {\n      color: rgba(255, 255, 255, 0.7); }\n    .hero.is-link-dark a.navbar-item:hover, .hero.is-link-dark a.navbar-item.is-active,\n    .hero.is-link-dark .navbar-link:hover,\n    .hero.is-link-dark .navbar-link.is-active {\n      background-color: #f3005b;\n      color: #fff; }\n    .hero.is-link-dark .tabs a {\n      color: #fff;\n      opacity: 0.9; }\n      .hero.is-link-dark .tabs a:hover {\n        opacity: 1; }\n    .hero.is-link-dark .tabs li.is-active a {\n      opacity: 1; }\n    .hero.is-link-dark .tabs.is-boxed a, .hero.is-link-dark .tabs.is-toggle a {\n      color: #fff; }\n      .hero.is-link-dark .tabs.is-boxed a:hover, .hero.is-link-dark .tabs.is-toggle a:hover {\n        background-color: rgba(10, 10, 10, 0.1); }\n    .hero.is-link-dark .tabs.is-boxed li.is-active a, .hero.is-link-dark .tabs.is-boxed li.is-active a:hover, .hero.is-link-dark .tabs.is-toggle li.is-active a, .hero.is-link-dark .tabs.is-toggle li.is-active a:hover {\n      background-color: #fff;\n      border-color: #fff;\n      color: #ff0d68; }\n    .hero.is-link-dark.is-bold {\n      background-image: linear-gradient(141deg, #d90076 0%, #ff0d68 71%, #ff2754 100%); } }\n    @media screen and (prefers-color-scheme: dark) and (max-width: 768px) {\n      .hero.is-link-dark.is-bold .navbar-menu {\n        background-image: linear-gradient(141deg, #d90076 0%, #ff0d68 71%, #ff2754 100%); } }\n@media (prefers-color-scheme: dark) {\n  .hero.is-info-dark {\n    background-color: #0092FF;\n    color: #fff; }\n    .hero.is-info-dark a:not(.button):not(.dropdown-item):not(.tag),\n    .hero.is-info-dark strong {\n      color: inherit; }\n    .hero.is-info-dark .title {\n      color: #fff; }\n    .hero.is-info-dark .subtitle {\n      color: rgba(255, 255, 255, 0.9); }\n      .hero.is-info-dark .subtitle a:not(.button),\n      .hero.is-info-dark .subtitle strong {\n        color: #fff; } }\n  @media screen and (prefers-color-scheme: dark) and (max-width: 1023px) {\n    .hero.is-info-dark .navbar-menu {\n      background-color: #0092FF; } }\n@media (prefers-color-scheme: dark) {\n    .hero.is-info-dark .navbar-item,\n    .hero.is-info-dark .navbar-link {\n      color: rgba(255, 255, 255, 0.7); }\n    .hero.is-info-dark a.navbar-item:hover, .hero.is-info-dark a.navbar-item.is-active,\n    .hero.is-info-dark .navbar-link:hover,\n    .hero.is-info-dark .navbar-link.is-active {\n      background-color: #0083e6;\n      color: #fff; }\n    .hero.is-info-dark .tabs a {\n      color: #fff;\n      opacity: 0.9; }\n      .hero.is-info-dark .tabs a:hover {\n        opacity: 1; }\n    .hero.is-info-dark .tabs li.is-active a {\n      opacity: 1; }\n    .hero.is-info-dark .tabs.is-boxed a, .hero.is-info-dark .tabs.is-toggle a {\n      color: #fff; }\n      .hero.is-info-dark .tabs.is-boxed a:hover, .hero.is-info-dark .tabs.is-toggle a:hover {\n        background-color: rgba(10, 10, 10, 0.1); }\n    .hero.is-info-dark .tabs.is-boxed li.is-active a, .hero.is-info-dark .tabs.is-boxed li.is-active a:hover, .hero.is-info-dark .tabs.is-toggle li.is-active a, .hero.is-info-dark .tabs.is-toggle li.is-active a:hover {\n      background-color: #fff;\n      border-color: #fff;\n      color: #0092FF; }\n    .hero.is-info-dark.is-bold {\n      background-image: linear-gradient(141deg, #0097cc 0%, #0092FF 71%, #1a77ff 100%); } }\n    @media screen and (prefers-color-scheme: dark) and (max-width: 768px) {\n      .hero.is-info-dark.is-bold .navbar-menu {\n        background-image: linear-gradient(141deg, #0097cc 0%, #0092FF 71%, #1a77ff 100%); } }\n@media (prefers-color-scheme: dark) {\n  .hero.is-success-dark {\n    background-color: #16DB93;\n    color: rgba(0, 0, 0, 0.7); }\n    .hero.is-success-dark a:not(.button):not(.dropdown-item):not(.tag),\n    .hero.is-success-dark strong {\n      color: inherit; }\n    .hero.is-success-dark .title {\n      color: rgba(0, 0, 0, 0.7); }\n    .hero.is-success-dark .subtitle {\n      color: rgba(0, 0, 0, 0.9); }\n      .hero.is-success-dark .subtitle a:not(.button),\n      .hero.is-success-dark .subtitle strong {\n        color: rgba(0, 0, 0, 0.7); } }\n  @media screen and (prefers-color-scheme: dark) and (max-width: 1023px) {\n    .hero.is-success-dark .navbar-menu {\n      background-color: #16DB93; } }\n@media (prefers-color-scheme: dark) {\n    .hero.is-success-dark .navbar-item,\n    .hero.is-success-dark .navbar-link {\n      color: rgba(0, 0, 0, 0.7); }\n    .hero.is-success-dark a.navbar-item:hover, .hero.is-success-dark a.navbar-item.is-active,\n    .hero.is-success-dark .navbar-link:hover,\n    .hero.is-success-dark .navbar-link.is-active {\n      background-color: #14c483;\n      color: rgba(0, 0, 0, 0.7); }\n    .hero.is-success-dark .tabs a {\n      color: rgba(0, 0, 0, 0.7);\n      opacity: 0.9; }\n      .hero.is-success-dark .tabs a:hover {\n        opacity: 1; }\n    .hero.is-success-dark .tabs li.is-active a {\n      opacity: 1; }\n    .hero.is-success-dark .tabs.is-boxed a, .hero.is-success-dark .tabs.is-toggle a {\n      color: rgba(0, 0, 0, 0.7); }\n      .hero.is-success-dark .tabs.is-boxed a:hover, .hero.is-success-dark .tabs.is-toggle a:hover {\n        background-color: rgba(10, 10, 10, 0.1); }\n    .hero.is-success-dark .tabs.is-boxed li.is-active a, .hero.is-success-dark .tabs.is-boxed li.is-active a:hover, .hero.is-success-dark .tabs.is-toggle li.is-active a, .hero.is-success-dark .tabs.is-toggle li.is-active a:hover {\n      background-color: rgba(0, 0, 0, 0.7);\n      border-color: rgba(0, 0, 0, 0.7);\n      color: #16DB93; }\n    .hero.is-success-dark.is-bold {\n      background-image: linear-gradient(141deg, #08b659 0%, #16DB93 71%, #1cefc5 100%); } }\n    @media screen and (prefers-color-scheme: dark) and (max-width: 768px) {\n      .hero.is-success-dark.is-bold .navbar-menu {\n        background-image: linear-gradient(141deg, #08b659 0%, #16DB93 71%, #1cefc5 100%); } }\n@media (prefers-color-scheme: dark) {\n  .hero.is-warning-dark {\n    background-color: #FFE900;\n    color: rgba(0, 0, 0, 0.7); }\n    .hero.is-warning-dark a:not(.button):not(.dropdown-item):not(.tag),\n    .hero.is-warning-dark strong {\n      color: inherit; }\n    .hero.is-warning-dark .title {\n      color: rgba(0, 0, 0, 0.7); }\n    .hero.is-warning-dark .subtitle {\n      color: rgba(0, 0, 0, 0.9); }\n      .hero.is-warning-dark .subtitle a:not(.button),\n      .hero.is-warning-dark .subtitle strong {\n        color: rgba(0, 0, 0, 0.7); } }\n  @media screen and (prefers-color-scheme: dark) and (max-width: 1023px) {\n    .hero.is-warning-dark .navbar-menu {\n      background-color: #FFE900; } }\n@media (prefers-color-scheme: dark) {\n    .hero.is-warning-dark .navbar-item,\n    .hero.is-warning-dark .navbar-link {\n      color: rgba(0, 0, 0, 0.7); }\n    .hero.is-warning-dark a.navbar-item:hover, .hero.is-warning-dark a.navbar-item.is-active,\n    .hero.is-warning-dark .navbar-link:hover,\n    .hero.is-warning-dark .navbar-link.is-active {\n      background-color: #e6d200;\n      color: rgba(0, 0, 0, 0.7); }\n    .hero.is-warning-dark .tabs a {\n      color: rgba(0, 0, 0, 0.7);\n      opacity: 0.9; }\n      .hero.is-warning-dark .tabs a:hover {\n        opacity: 1; }\n    .hero.is-warning-dark .tabs li.is-active a {\n      opacity: 1; }\n    .hero.is-warning-dark .tabs.is-boxed a, .hero.is-warning-dark .tabs.is-toggle a {\n      color: rgba(0, 0, 0, 0.7); }\n      .hero.is-warning-dark .tabs.is-boxed a:hover, .hero.is-warning-dark .tabs.is-toggle a:hover {\n        background-color: rgba(10, 10, 10, 0.1); }\n    .hero.is-warning-dark .tabs.is-boxed li.is-active a, .hero.is-warning-dark .tabs.is-boxed li.is-active a:hover, .hero.is-warning-dark .tabs.is-toggle li.is-active a, .hero.is-warning-dark .tabs.is-toggle li.is-active a:hover {\n      background-color: rgba(0, 0, 0, 0.7);\n      border-color: rgba(0, 0, 0, 0.7);\n      color: #FFE900; }\n    .hero.is-warning-dark.is-bold {\n      background-image: linear-gradient(141deg, #cc9800 0%, #FFE900 71%, #edff1a 100%); } }\n    @media screen and (prefers-color-scheme: dark) and (max-width: 768px) {\n      .hero.is-warning-dark.is-bold .navbar-menu {\n        background-image: linear-gradient(141deg, #cc9800 0%, #FFE900 71%, #edff1a 100%); } }\n@media (prefers-color-scheme: dark) {\n  .hero.is-danger-dark {\n    background-color: #f14668;\n    color: #fff; }\n    .hero.is-danger-dark a:not(.button):not(.dropdown-item):not(.tag),\n    .hero.is-danger-dark strong {\n      color: inherit; }\n    .hero.is-danger-dark .title {\n      color: #fff; }\n    .hero.is-danger-dark .subtitle {\n      color: rgba(255, 255, 255, 0.9); }\n      .hero.is-danger-dark .subtitle a:not(.button),\n      .hero.is-danger-dark .subtitle strong {\n        color: #fff; } }\n  @media screen and (prefers-color-scheme: dark) and (max-width: 1023px) {\n    .hero.is-danger-dark .navbar-menu {\n      background-color: #f14668; } }\n@media (prefers-color-scheme: dark) {\n    .hero.is-danger-dark .navbar-item,\n    .hero.is-danger-dark .navbar-link {\n      color: rgba(255, 255, 255, 0.7); }\n    .hero.is-danger-dark a.navbar-item:hover, .hero.is-danger-dark a.navbar-item.is-active,\n    .hero.is-danger-dark .navbar-link:hover,\n    .hero.is-danger-dark .navbar-link.is-active {\n      background-color: #ef2e55;\n      color: #fff; }\n    .hero.is-danger-dark .tabs a {\n      color: #fff;\n      opacity: 0.9; }\n      .hero.is-danger-dark .tabs a:hover {\n        opacity: 1; }\n    .hero.is-danger-dark .tabs li.is-active a {\n      opacity: 1; }\n    .hero.is-danger-dark .tabs.is-boxed a, .hero.is-danger-dark .tabs.is-toggle a {\n      color: #fff; }\n      .hero.is-danger-dark .tabs.is-boxed a:hover, .hero.is-danger-dark .tabs.is-toggle a:hover {\n        background-color: rgba(10, 10, 10, 0.1); }\n    .hero.is-danger-dark .tabs.is-boxed li.is-active a, .hero.is-danger-dark .tabs.is-boxed li.is-active a:hover, .hero.is-danger-dark .tabs.is-toggle li.is-active a, .hero.is-danger-dark .tabs.is-toggle li.is-active a:hover {\n      background-color: #fff;\n      border-color: #fff;\n      color: #f14668; }\n    .hero.is-danger-dark.is-bold {\n      background-image: linear-gradient(141deg, #fa0a62 0%, #f14668 71%, #f7595f 100%); } }\n    @media screen and (prefers-color-scheme: dark) and (max-width: 768px) {\n      .hero.is-danger-dark.is-bold .navbar-menu {\n        background-image: linear-gradient(141deg, #fa0a62 0%, #f14668 71%, #f7595f 100%); } }\n@media (prefers-color-scheme: dark) {\n  .footer {\n    background-color: #121212; } }\n\n.hero .title {\n  position: relative; }\n  .hero .title span {\n    display: none; }\n  .hero .title::after {\n    content: \"\";\n    position: absolute;\n    top: -108px;\n    left: 0;\n    right: 0;\n    margin: auto;\n    width: 500px;\n    height: 122px;\n    background: url(/assets/images/logo.svg) no-repeat center center; }\n\n.sanic-simple-logo {\n  background: url(/assets/images/logo.svg) no-repeat;\n  background-size: 270px;\n  height: 72px; }\n  .sanic-simple-logo img {\n    visibility: hidden; }\n\n:root {\n  --menu-background: #ededed;\n  --menu-divider: #dbdbdb;\n  --menu-contrast: #121212; }\n\n.c1 {\n  color: #7a7a7a; }\n\n@media (prefers-color-scheme: dark) {\n  :root {\n    --menu-background: #0a0a0a;\n    --menu-divider: #242424;\n    --menu-contrast: #7a7a7a; }\n  html,\n  .navbar {\n    background-color: #121212; }\n  .footer {\n    background-color: #0a0a0a; }\n  .sanic-simple-logo {\n    background: url(/assets/images/logo-white.svg) no-repeat;\n    background-size: 270px; }\n  .hero .title::after {\n    background: url(/assets/images/logo-white.svg) no-repeat center center; }\n  .list {\n    background-color: transparent;\n    box-shadow: none; }\n  .n, .na, .nb, .no, .nd, .ni, .ne, .nl,\n  .nn, .nx, .py, .nt, .nv, .bp, .vc, .vg,\n  .vi, .vm {\n    color: #b5b5b5; }\n  .s, .sa, .sb, .sc, .dl, .sd, .s2, .se,\n  .sh, .si, .sx, .sr, .s1, .ss {\n    background-color: transparent;\n    color: #16DB93; }\n  .nc {\n    color: #FFE900; }\n  .c1 {\n    color: #4a4a4a; }\n  .introduction-table .table tbody tr:last-child td {\n    border-bottom-width: 1px; }\n  ::-webkit-scrollbar {\n    width: 12px;\n    height: 12px; }\n  ::-webkit-scrollbar-track {\n    background: #242424; }\n  ::-webkit-scrollbar-thumb {\n    background: #121212;\n    border-radius: 6px;\n    border: 3px solid #242424; }\n  ::-webkit-scrollbar-thumb:hover {\n    background: #0a0a0a; } }\n\n.burger {\n  display: none;\n  position: fixed;\n  top: 1rem;\n  right: 1rem;\n  width: 2rem;\n  height: 2rem;\n  cursor: pointer;\n  z-index: 101; }\n  .burger span {\n    display: block;\n    position: absolute;\n    height: 2px;\n    width: 100%;\n    background: var(--menu-contrast);\n    border-radius: 2px;\n    opacity: 1;\n    left: 0;\n    transform: rotate(0deg);\n    transition: .25s ease-in-out; }\n    .burger span:nth-child(1) {\n      top: 0px; }\n    .burger span:nth-child(2), .burger span:nth-child(3) {\n      top: 8px; }\n    .burger span:nth-child(4) {\n      top: 16px; }\n  .burger.is-active span:nth-child(1) {\n    top: 18px;\n    width: 0%;\n    left: 50%; }\n  .burger.is-active span:nth-child(2) {\n    transform: rotate(45deg); }\n  .burger.is-active span:nth-child(3) {\n    transform: rotate(-45deg); }\n  .burger.is-active span:nth-child(4) {\n    top: 18px;\n    width: 0%;\n    left: 50%; }\n  .burger::after {\n    content: '';\n    display: block;\n    position: absolute;\n    top: -1rem;\n    left: -0.5rem;\n    width: 3rem;\n    height: 3rem;\n    background: var(--menu-background);\n    z-index: -1; }\n\n.menu {\n  background-color: var(--menu-background);\n  width: 360px;\n  padding: 2rem;\n  height: 100vh;\n  overflow: auto;\n  position: fixed;\n  top: 0;\n  left: 0;\n  z-index: 100; }\n  .menu hr {\n    background-color: var(--menu-divider); }\n  .menu .is-anchor {\n    font-size: 0.75em; }\n    .menu .is-anchor a::before {\n      content: '# ';\n      color: var(--menu-contrast); }\n  .menu .menu-label {\n    margin-bottom: 1rem; }\n  .menu li.is-group > a {\n    font-size: 0.85rem; }\n    .menu li.is-group > a::after {\n      content: '';\n      position: relative;\n      top: -2px;\n      left: 8px;\n      display: inline-block;\n      width: 0;\n      height: 0;\n      border-left: 8px solid var(--menu-contrast);\n      border-top: 5.33333px solid transparent;\n      border-bottom: 5.33333px solid transparent;\n      transform: rotate(90deg); }\n    .menu li.is-group > a ~ .menu-list {\n      transition: all .15s ease-in-out;\n      transform: scaleY(1);\n      transform-origin: top;\n      overflow: hidden;\n      opacity: 1;\n      margin: 0; }\n    .menu li.is-group > a:not(.is-open)::after {\n      transform: rotate(0deg); }\n    .menu li.is-group > a:not(.is-open) ~ .menu-list {\n      transform: scaleY(0);\n      opacity: 0;\n      font-size: 0; }\n  .menu ~ main {\n    margin-left: 360px; }\n  .menu .anchor-list {\n    display: none; }\n  .menu .menu-item .is-active + .anchor-list {\n    display: block; }\n\n@media screen and (max-width: 1024px) {\n  .menu {\n    left: -100vw;\n    width: 100vw;\n    transition: all .25s ease-in-out; }\n    .menu.is-active {\n      left: 0; }\n    .menu ~ main {\n      margin-left: 0; }\n  .burger {\n    display: block; } }\n\n.menu-list li ul {\n  margin: 0;\n  padding-left: 0; }\n\n.menu-list ul li:not(.is-anchor) {\n  margin-left: 0.75em; }\n\n.menu-list .menu-list li a {\n  font-size: 0.85em; }\n\ncode {\n  color: #4a4a4a; }\n\n.notification code {\n  background-color: rgba(237, 237, 237, 0.6); }\n\n@media (prefers-color-scheme: dark) {\n  code {\n    color: #dbdbdb; }\n  .notification code {\n    background-color: rgba(36, 36, 36, 0.6); } }\n\na[target=_blank]::after {\n  content: \"⇗\";\n  margin-left: 0.25em; }\n\na > code {\n  border-bottom: 1px solid #ff0d68; }\n  a > code:hover {\n    background-color: #ff0d68;\n    color: white; }\n\nh1 a.anchor,\nh2 a.anchor,\nh3 a.anchor,\nh4 a.anchor,\nh5 a.anchor,\nh6 a.anchor {\n  display: none;\n  padding-left: 0.375em; }\n\nh1:hover a.anchor,\nh2:hover a.anchor,\nh3:hover a.anchor,\nh4:hover a.anchor,\nh5:hover a.anchor,\nh6:hover a.anchor {\n  display: inline; }\n\nh1 {\n  margin-left: -2rem; }\n\nh2 {\n  margin-left: -1rem;\n  margin-top: 3rem; }\n\nh3:not(:first-child) {\n  margin-top: 2rem; }\n\narticle {\n  margin-left: 2rem; }\n\np + pre,\npre + p,\ndiv.highlight + p,\np + div.highlight,\ndiv.highlight + div.highlight,\np + h4,\np + .code-block,\n.code-block + p,\n.code-block + .code-block,\np + p {\n  margin-top: 1rem; }\n\n@media screen and (max-width: 769px) {\n  html {\n    font-size: 18px; }\n  h1 {\n    margin-left: 0; }\n  h2 {\n    margin-left: 0; }\n  article {\n    margin-left: 0; }\n  .section {\n    padding: 1rem 0rem;\n    max-width: 95vw; } }\n\n@media screen and (min-width: 1216px) {\n  .section {\n    padding: 3rem 0rem; } }\n\n@media screen and (min-width: 1216px) {\n  .section {\n    padding: 3rem 0rem; } }\n\n.footer {\n  margin-bottom: 4rem; }\n  .footer a[target=_blank]::after {\n    display: none; }\n\n@media screen and (max-width: 1024px) {\n  .footer .level {\n    align-items: baseline; }\n  .footer a {\n    font-size: 0.75em; } }\n\n.hero .subtitle {\n  font-size: 3rem;\n  font-weight: 700;\n  margin-bottom: 1rem; }\n\n.hero .tagline {\n  font-family: Fira Code,Source Code Pro,Menlo,Monaco,Consolas,Lucisa Console,monospace;\n  font-size: 2rem;\n  font-weight: 300;\n  margin-bottom: 2rem; }\n\n.language-sh code {\n  display: inline-block; }\n  .language-sh code::before {\n    content: '▶ '; }\n\n.notification {\n  margin-top: 2rem; }\n  .notification .notification-title {\n    font-size: 1.25rem;\n    font-weight: 700; }\n  .notification.is-note {\n    background-color: #ff0d68;\n    color: white; }\n  .notification.is-new {\n    background-color: #833FE3;\n    color: white; }\n    .notification.is-new::after {\n      content: '🌟';\n      position: absolute;\n      top: 1rem;\n      right: 1rem;\n      font-size: 2rem; }\n  .notification.is-tip::after {\n    content: '💡';\n    position: absolute;\n    top: 1rem;\n    right: 1rem;\n    font-size: 2rem; }\n\n.ol, .ul,\n.list {\n  margin: 1rem 0; }\n  .ol .li,\n  .ol .list-item, .ul .li,\n  .ul .list-item,\n  .list .li,\n  .list .list-item {\n    padding: 0.5rem 0; }\n\n.ul .li {\n  margin-left: 1.5rem;\n  list-style-type: disc; }\n\n.introduction-table {\n  margin: 2rem 0; }\n  .introduction-table a[target=_blank]::after {\n    display: none; }\n  .introduction-table th {\n    display: none; }\n\n.docobject h2 :last-child {\n  color: #363636; }\n\n.docobject .function-signature {\n  color: #1e2024; }\n  .docobject .function-signature .param-name {\n    color: #ff0d68;\n    font-weight: bold; }\n  .docobject .function-signature .param-default {\n    color: #833FE3;\n    font-style: italic; }\n  .docobject .function-signature .function-decorator {\n    font-style: italic;\n    color: #7a7a7a; }\n  .docobject .function-signature .param-annotation {\n    color: #0092FF; }\n  .docobject .function-signature .return-annotation {\n    color: #37ae6f; }\n\n.docobject dl {\n  display: flex;\n  flex-wrap: wrap;\n  margin: 0;\n  padding: 0; }\n\n.docobject dt {\n  flex: 0 0 25%;\n  padding: 5px 10px;\n  font-weight: bold;\n  color: #0092FF; }\n\n.docobject dd {\n  flex: 1;\n  padding: 5px 10px;\n  margin: 0; }\n\n.docobject div.highlight + p,\n.docobject p + div.highlight,\n.docobject div.highlight + div.highlight {\n  margin-top: 1rem; }\n\n.docobject .method {\n  padding-left: 1rem; }\n  .docobject .method h3 {\n    margin-left: -1rem; }\n\n.docobject .ol, .docobject .ul, .docobject .list {\n  margin: 1rem; }\n\n.mermaid {\n  margin-top: 2rem; }\n  .mermaid .actor {\n    stroke: #ff0d68 !important;\n    fill: #ffd9e7 !important; }\n  .mermaid .labelBox {\n    fill: #cce9ff !important;\n    stroke: #0092FF !important; }\n  .mermaid .note {\n    fill: #fffbcc !important;\n    stroke: #FFE900 !important; }\n\n@media (prefers-color-scheme: dark) {\n  .docobject h2 :last-child {\n    color: #fafafa; }\n  .docobject .function-signature {\n    color: #b5b5b5; }\n    .docobject .function-signature .param-default {\n      color: #FFE900; }\n  .mermaid text.messageText {\n    fill: #fafafa !important; }\n  .mermaid .actor {\n    fill: #ff0d68 !important; }\n  .mermaid .labelBox {\n    fill: #0092FF !important; }\n  .mermaid .labelText {\n    fill: #fafafa !important; }\n  .mermaid .note {\n    fill: #FFE900 !important; } }\n\nh1 + .code-block,\nh2 + .code-block,\nh3 + .code-block {\n  margin-top: 1rem; }\n\n.code-block {\n  position: relative; }\n  .code-block + .code-block {\n    margin-top: 1rem; }\n  .code-block:hover .code-block__copy {\n    opacity: 1; }\n  .code-block .code-block__copy {\n    position: absolute;\n    right: 10px;\n    bottom: 10px;\n    width: 36px;\n    height: 52px;\n    cursor: pointer;\n    opacity: 0;\n    transition: all 0.3s; }\n    .code-block .code-block__copy::before {\n      content: \"copied\";\n      position: absolute;\n      top: -17.33333px;\n      margin: auto;\n      opacity: 0;\n      right: 9px; }\n    .code-block .code-block__copy.clicked::before {\n      opacity: 1;\n      animation: all 0.3s ease-in-out; }\n  .code-block .code-block__rectangle {\n    position: absolute;\n    width: 18px;\n    height: 23.29412px;\n    transition: all 0.3s ease; }\n  .code-block .code-block__filled {\n    background-color: #ff0d68;\n    left: 5px;\n    top: 13px; }\n  .code-block .code-block__outlined {\n    border: 2px solid #ff0d68;\n    background-color: transparent;\n    left: -3px;\n    top: 5px; }\n  .code-block .code-block__copy.clicked .code-block__outlined {\n    left: -19px;\n    top: 13px;\n    background-color: #ff0d68; }\n\n.additional-attributes.details {\n  display: flex;\n  flex-direction: column;\n  width: 100%; }\n  .additional-attributes.details .code-block {\n    display: none;\n    width: 100%; }\n  .additional-attributes.details::before {\n    content: \"▼ \" attr(title);\n    display: block;\n    background-color: #b5b5b5;\n    padding: 10px;\n    cursor: pointer;\n    width: 100%;\n    box-sizing: border-box; }\n  .additional-attributes.details.is-active .code-block {\n    display: block; }\n  .additional-attributes.details.is-active::before {\n    content: \"▲ \" attr(title);\n    background-color: #dbdbdb; }\n\n@media (prefers-color-scheme: dark) {\n  .additional-attributes.details::before {\n    background-color: #363636; }\n  .additional-attributes.details.is-active::before {\n    background-color: #4a4a4a; } }\n\n.tabs .tab-content {\n  display: none; }\n\n.table-of-contents {\n  position: fixed;\n  right: 0;\n  bottom: 0;\n  z-index: 1000;\n  max-width: 500px;\n  padding: 1rem 2rem;\n  background-color: #fafafa;\n  box-shadow: 0 0 2px rgba(63, 63, 68, 0.5); }\n  @media (prefers-color-scheme: dark) {\n    .table-of-contents {\n      background-color: #0a0a0a;\n      box-shadow: 0 0 2px rgba(191, 191, 191, 0.5); } }\n  .table-of-contents .table-of-contents-item {\n    display: block;\n    margin-bottom: 0.5rem;\n    text-decoration: none; }\n    .table-of-contents .table-of-contents-item:hover {\n      text-decoration: underline;\n      color: #ff0d68; }\n      .table-of-contents .table-of-contents-item:hover strong, .table-of-contents .table-of-contents-item:hover small {\n        color: #ff0d68; }\n    .table-of-contents .table-of-contents-item strong {\n      color: #121212;\n      font-size: 1.15em;\n      display: block;\n      line-height: 1rem;\n      margin-top: 0.75rem; }\n    .table-of-contents .table-of-contents-item small {\n      color: #7a7a7a;\n      font-size: 0.85em; }\n    @media (prefers-color-scheme: dark) {\n      .table-of-contents .table-of-contents-item strong {\n        color: #dbdbdb; } }\n  @media (max-width: 768px) {\n    .table-of-contents {\n      position: static;\n      max-width: calc(100vw - 2rem); }\n      .table-of-contents .table-of-contents-item {\n        display: flex;\n        flex-direction: row-reverse;\n        justify-content: start; }\n        .table-of-contents .table-of-contents-item strong {\n          display: inline;\n          margin: 0 0 0 0.75rem; } }\n.loading-bar {\n  position: fixed;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 3px;\n  background-color: transparent;\n  z-index: 9999; }\n  .loading-bar.is-loading {\n    background-color: #ff0d68;\n    animation: pulsingLoading 1s linear infinite; }\n\n@keyframes pulsingLoading {\n  0%, 100% {\n    transform: scaleX(1);\n    opacity: 1; }\n  50% {\n    transform: scaleX(0.25);\n    opacity: 0.5; } }\n\n.changelog .ol .li, .changelog .ol .list-item, .changelog .ul .li, .changelog .ul .list-item, .changelog .list .li, .changelog .list .list-item {\n  padding: 0; }\n\n.sponsors {\n  display: flex;\n  flex-wrap: wrap;\n  justify-content: center;\n  text-align: center;\n  padding: 0.25rem 0; }\n  .sponsors .button {\n    margin-left: 1rem; }\n\n@media screen and (min-width: 769px) {\n  .hero.is-large .hero-body {\n    padding: 18rem 6rem 3rem; } }\n\n@media screen and (max-width: 769px) {\n  .hero {\n    height: 100vh;\n    margin-top: 15vh; }\n    .hero .hero-body {\n      padding: 3rem 1.5rem;\n      display: flex;\n      flex-direction: column;\n      justify-content: center; }\n    .hero .title::after {\n      width: calc(100vw - 1.5rem);\n      background: url(/assets/images/logo-white.svg) no-repeat center center;\n      background-size: 100% auto; }\n    .hero .subtitle {\n      margin-top: 1rem;\n      font-size: 1.4rem; }\n    .hero .tagline {\n      font-size: 1.1rem; } }\n\n.home .tab-container {\n  display: flex; }\n  .home .tab-container .tabs {\n    flex-shrink: 0;\n    width: 200px; }\n    .home .tab-container .tabs ul {\n      display: flex;\n      flex-direction: column;\n      border-bottom: none; }\n    .home .tab-container .tabs li {\n      display: block;\n      width: 100%; }\n      .home .tab-container .tabs li a {\n        display: block;\n        padding: 0.5em 1em;\n        text-align: left; }\n  .home .tab-container .tab-display {\n    flex-grow: 1;\n    min-width: 0;\n    padding-left: 20px; }\n  @media screen and (max-width: 768px) {\n    .home .tab-container {\n      flex-direction: column; }\n      .home .tab-container .tabs {\n        width: 100%; }\n        .home .tab-container .tabs ul {\n          display: block;\n          overflow-y: auto; }\n        .home .tab-container .tabs li {\n          display: block; }\n      .home .tab-container .tab-display {\n        padding-left: 0; } }\nfooter .level,\nfooter .level .level-right,\nfooter .level .level-left {\n  display: flex !important; }\n\n.tabs li a {\n  font-size: 0.7rem; }\n\n.box {\n  box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1); }\n\n.container {\n  padding: 0 0.5rem; }\n\n@media (prefers-color-scheme: dark) {\n  .box {\n    box-shadow: 0 2px 3px rgba(128, 128, 128, 0.1), 0 0 0 1px rgba(128, 128, 128, 0.1); } }\n"
  },
  {
    "path": "guide/public/index.html",
    "content": "<!-- Main HTML Index to redirect to /en/ -->\n<!DOCTYPE html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"UTF-8\" />\n        <title>Sanic Framework</title>\n        <!-- <meta http-equiv=\"refresh\" content=\"0; url=/en/\" /> -->\n    </head>\n    <body>\n        <p>Redirecting to <a href=\"/en/\">/en/</a></p>\n    </body>\n</html>\n"
  },
  {
    "path": "guide/public/web/browserconfig.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<browserconfig>\n    <msapplication>\n        <tile>\n            <square150x150logo src=\"/mstile-150x150.png\"/>\n            <TileColor>#da532c</TileColor>\n        </tile>\n    </msapplication>\n</browserconfig>\n"
  },
  {
    "path": "guide/public/web/robots.txt",
    "content": "User-agent: *\nAllow: /\nSitemap: https://sanic.dev/sitemap.xml\nHost: https://sanic.dev\n"
  },
  {
    "path": "guide/public/web/site.webmanifest",
    "content": "{\n    \"name\": \"Sanic\",\n    \"short_name\": \"Sanic\",\n    \"icons\": [\n        {\n            \"src\": \"/android-chrome-192x192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"/android-chrome-512x512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\"\n        }\n    ],\n    \"theme_color\": \"#ffffff\",\n    \"background_color\": \"#ffffff\",\n    \"display\": \"standalone\"\n}\n"
  },
  {
    "path": "guide/requirements.txt",
    "content": "sanic>=23.12\nsanic-ext>=23.12\nmsgspec\npython-frontmatter\npygments\ndocstring-parser\nlibsass\nmistune\n"
  },
  {
    "path": "guide/server.py",
    "content": "\"\"\"Sanic  User Guide\n\nhttps://sanic.dev\n\nBuilt using the SHH stack:\n- Sanic\n- html5tagger\n- HTMX\"\"\"\n\nfrom pathlib import Path\n\nfrom webapp.worker.factory import create_app\n\n\napp = create_app(Path(__file__).parent)\n"
  },
  {
    "path": "guide/style/bulma/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2022 Jeremy Thomas\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "guide/style/bulma/bulma.sass",
    "content": "@charset \"utf-8\"\n/*! bulma.io v0.9.4 | MIT License | github.com/jgthms/bulma */\n@import \"sass/utilities/_all\"\n@import \"sass/base/_all\"\n@import \"sass/elements/_all\"\n@import \"sass/form/_all\"\n@import \"sass/components/_all\"\n@import \"sass/grid/_all\"\n@import \"sass/helpers/_all\"\n@import \"sass/layout/_all\"\n"
  },
  {
    "path": "guide/style/bulma/sass/base/_all.sass",
    "content": "/* Bulma Base */\n@charset \"utf-8\"\n\n@import \"minireset\"\n@import \"generic\"\n@import \"animations\"\n"
  },
  {
    "path": "guide/style/bulma/sass/base/animations.sass",
    "content": "@keyframes spinAround\n  from\n    transform: rotate(0deg)\n  to\n    transform: rotate(359deg)\n"
  },
  {
    "path": "guide/style/bulma/sass/base/generic.sass",
    "content": "@import \"../utilities/mixins\"\n\n$body-background-color: $scheme-main !default\n$body-size: 16px !default\n$body-min-width: 300px !default\n$body-rendering: optimizeLegibility !default\n$body-family: $family-primary !default\n$body-overflow-x: hidden !default\n$body-overflow-y: scroll !default\n\n$body-color: $text !default\n$body-font-size: 1em !default\n$body-weight: $weight-normal !default\n$body-line-height: 1.5 !default\n\n$code-family: $family-code !default\n$code-padding: 0.25em 0.5em 0.25em !default\n$code-weight: normal !default\n$code-size: 0.875em !default\n\n$small-font-size: 0.875em !default\n\n$hr-background-color: $background !default\n$hr-height: 2px !default\n$hr-margin: 1.5rem 0 !default\n\n$strong-color: $text-strong !default\n$strong-weight: $weight-bold !default\n\n$pre-font-size: 0.875em !default\n$pre-padding: 1.25rem 1.5rem !default\n$pre-code-font-size: 1em !default\n\nhtml\n  background-color: $body-background-color\n  font-size: $body-size\n  -moz-osx-font-smoothing: grayscale\n  -webkit-font-smoothing: antialiased\n  min-width: $body-min-width\n  overflow-x: $body-overflow-x\n  overflow-y: $body-overflow-y\n  text-rendering: $body-rendering\n  text-size-adjust: 100%\n\narticle,\naside,\nfigure,\nfooter,\nheader,\nhgroup,\nsection\n  display: block\n\nbody,\nbutton,\ninput,\noptgroup,\nselect,\ntextarea\n  font-family: $body-family\n\ncode,\npre\n  -moz-osx-font-smoothing: auto\n  -webkit-font-smoothing: auto\n  font-family: $code-family\n\nbody\n  color: $body-color\n  font-size: $body-font-size\n  font-weight: $body-weight\n  line-height: $body-line-height\n\n// Inline\n\na\n  color: $link\n  cursor: pointer\n  text-decoration: none\n  strong\n    color: currentColor\n  &:hover\n    color: $link-hover\n\ncode\n  background-color: $code-background\n  color: $code\n  font-size: $code-size\n  font-weight: $code-weight\n  padding: $code-padding\n\nhr\n  background-color: $hr-background-color\n  border: none\n  display: block\n  height: $hr-height\n  margin: $hr-margin\n\nimg\n  height: auto\n  max-width: 100%\n\ninput[type=\"checkbox\"],\ninput[type=\"radio\"]\n  vertical-align: baseline\n\nsmall\n  font-size: $small-font-size\n\nspan\n  font-style: inherit\n  font-weight: inherit\n\nstrong\n  color: $strong-color\n  font-weight: $strong-weight\n\n// Block\n\nfieldset\n  border: none\n\npre\n  +overflow-touch\n  background-color: $pre-background\n  color: $pre\n  font-size: $pre-font-size\n  overflow-x: auto\n  padding: $pre-padding\n  white-space: pre\n  word-wrap: normal\n  code\n    background-color: transparent\n    color: currentColor\n    font-size: $pre-code-font-size\n    padding: 0\n\ntable\n  td,\n  th\n    vertical-align: top\n    &:not([align])\n      text-align: inherit\n  th\n    color: $text-strong\n"
  },
  {
    "path": "guide/style/bulma/sass/base/helpers.sass",
    "content": "@warn \"The helpers.sass file is DEPRECATED. It has moved into its own /helpers folder. Please import sass/helpers/_all instead.\"\n"
  },
  {
    "path": "guide/style/bulma/sass/base/minireset.sass",
    "content": "/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */\n// Blocks\nhtml,\nbody,\np,\nol,\nul,\nli,\ndl,\ndt,\ndd,\nblockquote,\nfigure,\nfieldset,\nlegend,\ntextarea,\npre,\niframe,\nhr,\nh1,\nh2,\nh3,\nh4,\nh5,\nh6\n  margin: 0\n  padding: 0\n\n// Headings\nh1,\nh2,\nh3,\nh4,\nh5,\nh6\n  font-size: 100%\n  font-weight: normal\n\n// List\nul\n  list-style: none\n\n// Form\nbutton,\ninput,\nselect,\ntextarea\n  margin: 0\n\n// Box sizing\nhtml\n  box-sizing: border-box\n\n*\n  &,\n  &::before,\n  &::after\n    box-sizing: inherit\n\n// Media\nimg,\nvideo\n  height: auto\n  max-width: 100%\n\n// Iframe\niframe\n  border: 0\n\n// Table\ntable\n  border-collapse: collapse\n  border-spacing: 0\n\ntd,\nth\n  padding: 0\n  &:not([align])\n    text-align: inherit\n"
  },
  {
    "path": "guide/style/bulma/sass/components/_all.sass",
    "content": "/* Bulma Components */\n@charset \"utf-8\"\n\n@import \"breadcrumb\"\n@import \"card\"\n@import \"dropdown\"\n@import \"level\"\n@import \"media\"\n@import \"menu\"\n@import \"message\"\n@import \"modal\"\n@import \"navbar\"\n@import \"pagination\"\n@import \"panel\"\n@import \"tabs\"\n"
  },
  {
    "path": "guide/style/bulma/sass/components/breadcrumb.sass",
    "content": "@import \"../utilities/mixins\"\n\n$breadcrumb-item-color: $link !default\n$breadcrumb-item-hover-color: $link-hover !default\n$breadcrumb-item-active-color: $text-strong !default\n\n$breadcrumb-item-padding-vertical: 0 !default\n$breadcrumb-item-padding-horizontal: 0.75em !default\n\n$breadcrumb-item-separator-color: $border-hover !default\n\n.breadcrumb\n  @extend %block\n  @extend %unselectable\n  font-size: $size-normal\n  white-space: nowrap\n  a\n    align-items: center\n    color: $breadcrumb-item-color\n    display: flex\n    justify-content: center\n    padding: $breadcrumb-item-padding-vertical $breadcrumb-item-padding-horizontal\n    &:hover\n      color: $breadcrumb-item-hover-color\n  li\n    align-items: center\n    display: flex\n    &:first-child a\n      +ltr-property(\"padding\", 0, false)\n    &.is-active\n      a\n        color: $breadcrumb-item-active-color\n        cursor: default\n        pointer-events: none\n    & + li::before\n      color: $breadcrumb-item-separator-color\n      content: \"\\0002f\"\n  ul,\n  ol\n    align-items: flex-start\n    display: flex\n    flex-wrap: wrap\n    justify-content: flex-start\n  .icon\n    &:first-child\n      +ltr-property(\"margin\", 0.5em)\n    &:last-child\n      +ltr-property(\"margin\", 0.5em, false)\n  // Alignment\n  &.is-centered\n    ol,\n    ul\n      justify-content: center\n  &.is-right\n    ol,\n    ul\n      justify-content: flex-end\n  // Sizes\n  &.is-small\n    font-size: $size-small\n  &.is-medium\n    font-size: $size-medium\n  &.is-large\n    font-size: $size-large\n  // Styles\n  &.has-arrow-separator\n    li + li::before\n      content: \"\\02192\"\n  &.has-bullet-separator\n    li + li::before\n      content: \"\\02022\"\n  &.has-dot-separator\n    li + li::before\n      content: \"\\000b7\"\n  &.has-succeeds-separator\n    li + li::before\n      content: \"\\0227B\"\n"
  },
  {
    "path": "guide/style/bulma/sass/components/card.sass",
    "content": "@import \"../utilities/mixins\"\n\n$card-color: $text !default\n$card-background-color: $scheme-main !default\n$card-shadow: $shadow !default\n$card-radius: 0.25rem !default\n\n$card-header-background-color: transparent !default\n$card-header-color: $text-strong !default\n$card-header-padding: 0.75rem 1rem !default\n$card-header-shadow: 0 0.125em 0.25em rgba($scheme-invert, 0.1) !default\n$card-header-weight: $weight-bold !default\n\n$card-content-background-color: transparent !default\n$card-content-padding: 1.5rem !default\n\n$card-footer-background-color: transparent !default\n$card-footer-border-top: 1px solid $border-light !default\n$card-footer-padding: 0.75rem !default\n\n$card-media-margin: $block-spacing !default\n\n.card\n  background-color: $card-background-color\n  border-radius: $card-radius\n  box-shadow: $card-shadow\n  color: $card-color\n  max-width: 100%\n  position: relative\n\n%card-item\n  &:first-child\n    border-top-left-radius: $card-radius\n    border-top-right-radius: $card-radius\n  &:last-child\n    border-bottom-left-radius: $card-radius\n    border-bottom-right-radius: $card-radius\n\n.card-header\n  @extend %card-item\n  background-color: $card-header-background-color\n  align-items: stretch\n  box-shadow: $card-header-shadow\n  display: flex\n\n.card-header-title\n  align-items: center\n  color: $card-header-color\n  display: flex\n  flex-grow: 1\n  font-weight: $card-header-weight\n  padding: $card-header-padding\n  &.is-centered\n    justify-content: center\n\n.card-header-icon\n  +reset\n  align-items: center\n  cursor: pointer\n  display: flex\n  justify-content: center\n  padding: $card-header-padding\n\n.card-image\n  display: block\n  position: relative\n  &:first-child\n    img\n      border-top-left-radius: $card-radius\n      border-top-right-radius: $card-radius\n  &:last-child\n    img\n      border-bottom-left-radius: $card-radius\n      border-bottom-right-radius: $card-radius\n\n.card-content\n  @extend %card-item\n  background-color: $card-content-background-color\n  padding: $card-content-padding\n\n.card-footer\n  @extend %card-item\n  background-color: $card-footer-background-color\n  border-top: $card-footer-border-top\n  align-items: stretch\n  display: flex\n\n.card-footer-item\n  align-items: center\n  display: flex\n  flex-basis: 0\n  flex-grow: 1\n  flex-shrink: 0\n  justify-content: center\n  padding: $card-footer-padding\n  &:not(:last-child)\n    +ltr-property(\"border\", $card-footer-border-top)\n\n// Combinations\n\n.card\n  .media:not(:last-child)\n    margin-bottom: $card-media-margin\n"
  },
  {
    "path": "guide/style/bulma/sass/components/dropdown.sass",
    "content": "@import \"../utilities/mixins\"\n\n$dropdown-menu-min-width: 12rem !default\n\n$dropdown-content-background-color: $scheme-main !default\n$dropdown-content-arrow: $link !default\n$dropdown-content-offset: 4px !default\n$dropdown-content-padding-bottom: 0.5rem !default\n$dropdown-content-padding-top: 0.5rem !default\n$dropdown-content-radius: $radius !default\n$dropdown-content-shadow: $shadow !default\n$dropdown-content-z: 20 !default\n\n$dropdown-item-color: $text !default\n$dropdown-item-hover-color: $scheme-invert !default\n$dropdown-item-hover-background-color: $background !default\n$dropdown-item-active-color: $link-invert !default\n$dropdown-item-active-background-color: $link !default\n\n$dropdown-divider-background-color: $border-light !default\n\n.dropdown\n  display: inline-flex\n  position: relative\n  vertical-align: top\n  &.is-active,\n  &.is-hoverable:hover\n    .dropdown-menu\n      display: block\n  &.is-right\n    .dropdown-menu\n      left: auto\n      right: 0\n  &.is-up\n    .dropdown-menu\n      bottom: 100%\n      padding-bottom: $dropdown-content-offset\n      padding-top: initial\n      top: auto\n\n.dropdown-menu\n  display: none\n  +ltr-position(0, false)\n  min-width: $dropdown-menu-min-width\n  padding-top: $dropdown-content-offset\n  position: absolute\n  top: 100%\n  z-index: $dropdown-content-z\n\n.dropdown-content\n  background-color: $dropdown-content-background-color\n  border-radius: $dropdown-content-radius\n  box-shadow: $dropdown-content-shadow\n  padding-bottom: $dropdown-content-padding-bottom\n  padding-top: $dropdown-content-padding-top\n\n.dropdown-item\n  color: $dropdown-item-color\n  display: block\n  font-size: 0.875rem\n  line-height: 1.5\n  padding: 0.375rem 1rem\n  position: relative\n\na.dropdown-item,\nbutton.dropdown-item\n  +ltr-property(\"padding\", 3rem)\n  text-align: inherit\n  white-space: nowrap\n  width: 100%\n  &:hover\n    background-color: $dropdown-item-hover-background-color\n    color: $dropdown-item-hover-color\n  &.is-active\n    background-color: $dropdown-item-active-background-color\n    color: $dropdown-item-active-color\n\n.dropdown-divider\n  background-color: $dropdown-divider-background-color\n  border: none\n  display: block\n  height: 1px\n  margin: 0.5rem 0\n"
  },
  {
    "path": "guide/style/bulma/sass/components/level.sass",
    "content": "@import \"../utilities/mixins\"\n\n$level-item-spacing: ($block-spacing * 0.5) !default\n\n.level\n  @extend %block\n  align-items: center\n  justify-content: space-between\n  code\n    border-radius: $radius\n  img\n    display: inline-block\n    vertical-align: top\n  // Modifiers\n  &.is-mobile\n    display: flex\n    .level-left,\n    .level-right\n      display: flex\n    .level-left + .level-right\n      margin-top: 0\n    .level-item\n      &:not(:last-child)\n        margin-bottom: 0\n        +ltr-property(\"margin\", $level-item-spacing)\n      &:not(.is-narrow)\n        flex-grow: 1\n  // Responsiveness\n  +tablet\n    display: flex\n    & > .level-item\n      &:not(.is-narrow)\n        flex-grow: 1\n\n.level-item\n  align-items: center\n  display: flex\n  flex-basis: auto\n  flex-grow: 0\n  flex-shrink: 0\n  justify-content: center\n  .title,\n  .subtitle\n    margin-bottom: 0\n  // Responsiveness\n  +mobile\n    &:not(:last-child)\n      margin-bottom: $level-item-spacing\n\n.level-left,\n.level-right\n  flex-basis: auto\n  flex-grow: 0\n  flex-shrink: 0\n  .level-item\n    // Modifiers\n    &.is-flexible\n      flex-grow: 1\n    // Responsiveness\n    +tablet\n      &:not(:last-child)\n        +ltr-property(\"margin\", $level-item-spacing)\n\n.level-left\n  align-items: center\n  justify-content: flex-start\n  // Responsiveness\n  +mobile\n    & + .level-right\n      margin-top: 1.5rem\n  +tablet\n    display: flex\n\n.level-right\n  align-items: center\n  justify-content: flex-end\n  // Responsiveness\n  +tablet\n    display: flex\n"
  },
  {
    "path": "guide/style/bulma/sass/components/media.sass",
    "content": "@import \"../utilities/mixins\"\n\n$media-border-color: bulmaRgba($border, 0.5) !default\n$media-border-size: 1px !default\n$media-spacing: 1rem !default\n$media-spacing-large: 1.5rem !default\n$media-content-spacing: 0.75rem !default\n$media-level-1-spacing: 0.75rem !default\n$media-level-1-content-spacing: 0.5rem !default\n$media-level-2-spacing: 0.5rem !default\n\n.media\n  align-items: flex-start\n  display: flex\n  text-align: inherit\n  .content:not(:last-child)\n    margin-bottom: $media-content-spacing\n  .media\n    border-top: $media-border-size solid $media-border-color\n    display: flex\n    padding-top: $media-level-1-spacing\n    .content:not(:last-child),\n    .control:not(:last-child)\n      margin-bottom: $media-level-1-content-spacing\n    .media\n      padding-top: $media-level-2-spacing\n      & + .media\n        margin-top: $media-level-2-spacing\n  & + .media\n    border-top: $media-border-size solid $media-border-color\n    margin-top: $media-spacing\n    padding-top: $media-spacing\n  // Sizes\n  &.is-large\n    & + .media\n      margin-top: $media-spacing-large\n      padding-top: $media-spacing-large\n\n.media-left,\n.media-right\n  flex-basis: auto\n  flex-grow: 0\n  flex-shrink: 0\n\n.media-left\n  +ltr-property(\"margin\", $media-spacing)\n\n.media-right\n  +ltr-property(\"margin\", $media-spacing, false)\n\n.media-content\n  flex-basis: auto\n  flex-grow: 1\n  flex-shrink: 1\n  text-align: inherit\n\n+mobile\n  .media-content\n    overflow-x: auto\n"
  },
  {
    "path": "guide/style/bulma/sass/components/menu.sass",
    "content": "@import \"../utilities/mixins\"\n\n$menu-item-color: $text !default\n$menu-item-radius: $radius-small !default\n$menu-item-hover-color: $text-strong !default\n$menu-item-hover-background-color: $background !default\n$menu-item-active-color: $link-invert !default\n$menu-item-active-background-color: $link !default\n\n$menu-list-border-left: 1px solid $border !default\n$menu-list-line-height: 1.25 !default\n$menu-list-link-padding: 0.5em 0.75em !default\n$menu-nested-list-margin: 0.75em !default\n$menu-nested-list-padding-left: 0.75em !default\n\n$menu-label-color: $text-light !default\n$menu-label-font-size: 0.75em !default\n$menu-label-letter-spacing: 0.1em !default\n$menu-label-spacing: 1em !default\n\n.menu\n  font-size: $size-normal\n  // Sizes\n  &.is-small\n    font-size: $size-small\n  &.is-medium\n    font-size: $size-medium\n  &.is-large\n    font-size: $size-large\n\n.menu-list\n  line-height: $menu-list-line-height\n  a\n    border-radius: $menu-item-radius\n    color: $menu-item-color\n    display: block\n    padding: $menu-list-link-padding\n    &:hover\n      background-color: $menu-item-hover-background-color\n      color: $menu-item-hover-color\n    // Modifiers\n    &.is-active\n      background-color: $menu-item-active-background-color\n      color: $menu-item-active-color\n  li\n    ul\n      +ltr-property(\"border\", $menu-list-border-left, false)\n      margin: $menu-nested-list-margin\n      +ltr-property(\"padding\", $menu-nested-list-padding-left, false)\n\n.menu-label\n  color: $menu-label-color\n  font-size: $menu-label-font-size\n  letter-spacing: $menu-label-letter-spacing\n  text-transform: uppercase\n  &:not(:first-child)\n    margin-top: $menu-label-spacing\n  &:not(:last-child)\n    margin-bottom: $menu-label-spacing\n"
  },
  {
    "path": "guide/style/bulma/sass/components/message.sass",
    "content": "@import \"../utilities/mixins\"\n\n$message-background-color: $background !default\n$message-radius: $radius !default\n\n$message-header-background-color: $text !default\n$message-header-color: $text-invert !default\n$message-header-weight: $weight-bold !default\n$message-header-padding: 0.75em 1em !default\n$message-header-radius: $radius !default\n\n$message-body-border-color: $border !default\n$message-body-border-width: 0 0 0 4px !default\n$message-body-color: $text !default\n$message-body-padding: 1.25em 1.5em !default\n$message-body-radius: $radius !default\n\n$message-body-pre-background-color: $scheme-main !default\n$message-body-pre-code-background-color: transparent !default\n\n$message-header-body-border-width: 0 !default\n$message-colors: $colors !default\n\n.message\n  @extend %block\n  background-color: $message-background-color\n  border-radius: $message-radius\n  font-size: $size-normal\n  strong\n    color: currentColor\n  a:not(.button):not(.tag):not(.dropdown-item)\n    color: currentColor\n    text-decoration: underline\n  // Sizes\n  &.is-small\n    font-size: $size-small\n  &.is-medium\n    font-size: $size-medium\n  &.is-large\n    font-size: $size-large\n  // Colors\n  @each $name, $components in $message-colors\n    $color: nth($components, 1)\n    $color-invert: nth($components, 2)\n    $color-light: null\n    $color-dark: null\n\n    @if length($components) >= 3\n      $color-light: nth($components, 3)\n      @if length($components) >= 4\n        $color-dark: nth($components, 4)\n      @else\n        $color-luminance: colorLuminance($color)\n        $darken-percentage: $color-luminance * 70%\n        $desaturate-percentage: $color-luminance * 30%\n        $color-dark: desaturate(darken($color, $darken-percentage), $desaturate-percentage)\n    @else\n      $color-lightning: max((100% - lightness($color)) - 2%, 0%)\n      $color-light: lighten($color, $color-lightning)\n\n    &.is-#{$name}\n      background-color: $color-light\n      .message-header\n        background-color: $color\n        color: $color-invert\n      .message-body\n        border-color: $color\n        color: $color-dark\n\n.message-header\n  align-items: center\n  background-color: $message-header-background-color\n  border-radius: $message-header-radius $message-header-radius 0 0\n  color: $message-header-color\n  display: flex\n  font-weight: $message-header-weight\n  justify-content: space-between\n  line-height: 1.25\n  padding: $message-header-padding\n  position: relative\n  .delete\n    flex-grow: 0\n    flex-shrink: 0\n    +ltr-property(\"margin\", 0.75em, false)\n  & + .message-body\n    border-width: $message-header-body-border-width\n    border-top-left-radius: 0\n    border-top-right-radius: 0\n\n.message-body\n  border-color: $message-body-border-color\n  border-radius: $message-body-radius\n  border-style: solid\n  border-width: $message-body-border-width\n  color: $message-body-color\n  padding: $message-body-padding\n  code,\n  pre\n    background-color: $message-body-pre-background-color\n  pre code\n    background-color: $message-body-pre-code-background-color\n"
  },
  {
    "path": "guide/style/bulma/sass/components/modal.sass",
    "content": "@import \"../utilities/mixins\"\n\n$modal-z: 40 !default\n\n$modal-background-background-color: bulmaRgba($scheme-invert, 0.86) !default\n\n$modal-content-width: 640px !default\n$modal-content-margin-mobile: 20px !default\n$modal-content-spacing-mobile: 160px !default\n$modal-content-spacing-tablet: 40px !default\n\n$modal-close-dimensions: 40px !default\n$modal-close-right: 20px !default\n$modal-close-top: 20px !default\n\n$modal-card-spacing: 40px !default\n\n$modal-card-head-background-color: $background !default\n$modal-card-head-border-bottom: 1px solid $border !default\n$modal-card-head-padding: 20px !default\n$modal-card-head-radius: $radius-large !default\n\n$modal-card-title-color: $text-strong !default\n$modal-card-title-line-height: 1 !default\n$modal-card-title-size: $size-4 !default\n\n$modal-card-foot-radius: $radius-large !default\n$modal-card-foot-border-top: 1px solid $border !default\n\n$modal-card-body-background-color: $scheme-main !default\n$modal-card-body-padding: 20px !default\n\n$modal-breakpoint: $tablet !default\n\n.modal\n  @extend %overlay\n  align-items: center\n  display: none\n  flex-direction: column\n  justify-content: center\n  overflow: hidden\n  position: fixed\n  z-index: $modal-z\n  // Modifiers\n  &.is-active\n    display: flex\n\n.modal-background\n  @extend %overlay\n  background-color: $modal-background-background-color\n\n.modal-content,\n.modal-card\n  margin: 0 $modal-content-margin-mobile\n  max-height: calc(100vh - #{$modal-content-spacing-mobile})\n  overflow: auto\n  position: relative\n  width: 100%\n  // Responsiveness\n  +from($modal-breakpoint)\n    margin: 0 auto\n    max-height: calc(100vh - #{$modal-content-spacing-tablet})\n    width: $modal-content-width\n\n.modal-close\n  @extend %delete\n  background: none\n  height: $modal-close-dimensions\n  position: fixed\n  +ltr-position($modal-close-right)\n  top: $modal-close-top\n  width: $modal-close-dimensions\n\n.modal-card\n  display: flex\n  flex-direction: column\n  max-height: calc(100vh - #{$modal-card-spacing})\n  overflow: hidden\n  -ms-overflow-y: visible\n\n.modal-card-head,\n.modal-card-foot\n  align-items: center\n  background-color: $modal-card-head-background-color\n  display: flex\n  flex-shrink: 0\n  justify-content: flex-start\n  padding: $modal-card-head-padding\n  position: relative\n\n.modal-card-head\n  border-bottom: $modal-card-head-border-bottom\n  border-top-left-radius: $modal-card-head-radius\n  border-top-right-radius: $modal-card-head-radius\n\n.modal-card-title\n  color: $modal-card-title-color\n  flex-grow: 1\n  flex-shrink: 0\n  font-size: $modal-card-title-size\n  line-height: $modal-card-title-line-height\n\n.modal-card-foot\n  border-bottom-left-radius: $modal-card-foot-radius\n  border-bottom-right-radius: $modal-card-foot-radius\n  border-top: $modal-card-foot-border-top\n  .button\n    &:not(:last-child)\n      +ltr-property(\"margin\", 0.5em)\n\n.modal-card-body\n  +overflow-touch\n  background-color: $modal-card-body-background-color\n  flex-grow: 1\n  flex-shrink: 1\n  overflow: auto\n  padding: $modal-card-body-padding\n"
  },
  {
    "path": "guide/style/bulma/sass/components/navbar.sass",
    "content": "@import \"../utilities/mixins\"\n\n$navbar-background-color: $scheme-main !default\n$navbar-box-shadow-size: 0 2px 0 0 !default\n$navbar-box-shadow-color: $background !default\n$navbar-height: 3.25rem !default\n$navbar-padding-vertical: 1rem !default\n$navbar-padding-horizontal: 2rem !default\n$navbar-z: 30 !default\n$navbar-fixed-z: 30 !default\n\n$navbar-item-color: $text !default\n$navbar-item-hover-color: $link !default\n$navbar-item-hover-background-color: $scheme-main-bis !default\n$navbar-item-active-color: $scheme-invert !default\n$navbar-item-active-background-color: transparent !default\n$navbar-item-img-max-height: 1.75rem !default\n\n$navbar-burger-color: $navbar-item-color !default\n\n$navbar-tab-hover-background-color: transparent !default\n$navbar-tab-hover-border-bottom-color: $link !default\n$navbar-tab-active-color: $link !default\n$navbar-tab-active-background-color: transparent !default\n$navbar-tab-active-border-bottom-color: $link !default\n$navbar-tab-active-border-bottom-style: solid !default\n$navbar-tab-active-border-bottom-width: 3px !default\n\n$navbar-dropdown-background-color: $scheme-main !default\n$navbar-dropdown-border-top: 2px solid $border !default\n$navbar-dropdown-offset: -4px !default\n$navbar-dropdown-arrow: $link !default\n$navbar-dropdown-radius: $radius-large !default\n$navbar-dropdown-z: 20 !default\n\n$navbar-dropdown-boxed-radius: $radius-large !default\n$navbar-dropdown-boxed-shadow: 0 8px 8px bulmaRgba($scheme-invert, 0.1), 0 0 0 1px bulmaRgba($scheme-invert, 0.1) !default\n\n$navbar-dropdown-item-hover-color: $scheme-invert !default\n$navbar-dropdown-item-hover-background-color: $background !default\n$navbar-dropdown-item-active-color: $link !default\n$navbar-dropdown-item-active-background-color: $background !default\n\n$navbar-divider-background-color: $background !default\n$navbar-divider-height: 2px !default\n\n$navbar-bottom-box-shadow-size: 0 -2px 0 0 !default\n\n$navbar-breakpoint: $desktop !default\n\n$navbar-colors: $colors !default\n\n=navbar-fixed\n  left: 0\n  position: fixed\n  right: 0\n  z-index: $navbar-fixed-z\n\n.navbar\n  background-color: $navbar-background-color\n  min-height: $navbar-height\n  position: relative\n  z-index: $navbar-z\n  @each $name, $pair in $navbar-colors\n    $color: nth($pair, 1)\n    $color-invert: nth($pair, 2)\n    &.is-#{$name}\n      background-color: $color\n      color: $color-invert\n      .navbar-brand\n        & > .navbar-item,\n        .navbar-link\n          color: $color-invert\n        & > a.navbar-item,\n        .navbar-link\n          &:focus,\n          &:hover,\n          &.is-active\n            background-color: bulmaDarken($color, 5%)\n            color: $color-invert\n        .navbar-link\n          &::after\n            border-color: $color-invert\n      .navbar-burger\n        color: $color-invert\n      +from($navbar-breakpoint)\n        .navbar-start,\n        .navbar-end\n          & > .navbar-item,\n          .navbar-link\n            color: $color-invert\n          & > a.navbar-item,\n          .navbar-link\n            &:focus,\n            &:hover,\n            &.is-active\n              background-color: bulmaDarken($color, 5%)\n              color: $color-invert\n          .navbar-link\n            &::after\n              border-color: $color-invert\n        .navbar-item.has-dropdown:focus .navbar-link,\n        .navbar-item.has-dropdown:hover .navbar-link,\n        .navbar-item.has-dropdown.is-active .navbar-link\n          background-color: bulmaDarken($color, 5%)\n          color: $color-invert\n        .navbar-dropdown\n          a.navbar-item\n            &.is-active\n              background-color: $color\n              color: $color-invert\n  & > .container\n    align-items: stretch\n    display: flex\n    min-height: $navbar-height\n    width: 100%\n  &.has-shadow\n    box-shadow: $navbar-box-shadow-size $navbar-box-shadow-color\n  &.is-fixed-bottom,\n  &.is-fixed-top\n    +navbar-fixed\n  &.is-fixed-bottom\n    bottom: 0\n    &.has-shadow\n      box-shadow: $navbar-bottom-box-shadow-size $navbar-box-shadow-color\n  &.is-fixed-top\n    top: 0\n\nhtml,\nbody\n  &.has-navbar-fixed-top\n    padding-top: $navbar-height\n  &.has-navbar-fixed-bottom\n    padding-bottom: $navbar-height\n\n.navbar-brand,\n.navbar-tabs\n  align-items: stretch\n  display: flex\n  flex-shrink: 0\n  min-height: $navbar-height\n\n.navbar-brand\n  a.navbar-item\n    &:focus,\n    &:hover\n      background-color: transparent\n\n.navbar-tabs\n  +overflow-touch\n  max-width: 100vw\n  overflow-x: auto\n  overflow-y: hidden\n\n.navbar-burger\n  @extend %reset\n  color: $navbar-burger-color\n  +hamburger($navbar-height)\n  +ltr-property(\"margin\", auto, false)\n\n.navbar-menu\n  display: none\n\n.navbar-item,\n.navbar-link\n  color: $navbar-item-color\n  display: block\n  line-height: 1.5\n  padding: 0.5rem 0.75rem\n  position: relative\n  .icon\n    &:only-child\n      margin-left: -0.25rem\n      margin-right: -0.25rem\n\na.navbar-item,\n.navbar-link\n  cursor: pointer\n  &:focus,\n  &:focus-within,\n  &:hover,\n  &.is-active\n    background-color: $navbar-item-hover-background-color\n    color: $navbar-item-hover-color\n\n.navbar-item\n  flex-grow: 0\n  flex-shrink: 0\n  img\n    max-height: $navbar-item-img-max-height\n  &.has-dropdown\n    padding: 0\n  &.is-expanded\n    flex-grow: 1\n    flex-shrink: 1\n  &.is-tab\n    border-bottom: 1px solid transparent\n    min-height: $navbar-height\n    padding-bottom: calc(0.5rem - 1px)\n    &:focus,\n    &:hover\n      background-color: $navbar-tab-hover-background-color\n      border-bottom-color: $navbar-tab-hover-border-bottom-color\n    &.is-active\n      background-color: $navbar-tab-active-background-color\n      border-bottom-color: $navbar-tab-active-border-bottom-color\n      border-bottom-style: $navbar-tab-active-border-bottom-style\n      border-bottom-width: $navbar-tab-active-border-bottom-width\n      color: $navbar-tab-active-color\n      padding-bottom: calc(0.5rem - #{$navbar-tab-active-border-bottom-width})\n\n.navbar-content\n  flex-grow: 1\n  flex-shrink: 1\n\n.navbar-link:not(.is-arrowless)\n  +ltr-property(\"padding\", 2.5em)\n  &::after\n    @extend %arrow\n    border-color: $navbar-dropdown-arrow\n    margin-top: -0.375em\n    +ltr-position(1.125em)\n\n.navbar-dropdown\n  font-size: 0.875rem\n  padding-bottom: 0.5rem\n  padding-top: 0.5rem\n  .navbar-item\n    padding-left: 1.5rem\n    padding-right: 1.5rem\n\n.navbar-divider\n  background-color: $navbar-divider-background-color\n  border: none\n  display: none\n  height: $navbar-divider-height\n  margin: 0.5rem 0\n\n+until($navbar-breakpoint)\n  .navbar > .container\n    display: block\n  .navbar-brand,\n  .navbar-tabs\n    .navbar-item\n      align-items: center\n      display: flex\n  .navbar-link\n    &::after\n      display: none\n  .navbar-menu\n    background-color: $navbar-background-color\n    box-shadow: 0 8px 16px bulmaRgba($scheme-invert, 0.1)\n    padding: 0.5rem 0\n    &.is-active\n      display: block\n  // Fixed navbar\n  .navbar\n    &.is-fixed-bottom-touch,\n    &.is-fixed-top-touch\n      +navbar-fixed\n    &.is-fixed-bottom-touch\n      bottom: 0\n      &.has-shadow\n        box-shadow: 0 -2px 3px bulmaRgba($scheme-invert, 0.1)\n    &.is-fixed-top-touch\n      top: 0\n    &.is-fixed-top,\n    &.is-fixed-top-touch\n      .navbar-menu\n        +overflow-touch\n        max-height: calc(100vh - #{$navbar-height})\n        overflow: auto\n  html,\n  body\n    &.has-navbar-fixed-top-touch\n      padding-top: $navbar-height\n    &.has-navbar-fixed-bottom-touch\n      padding-bottom: $navbar-height\n\n+from($navbar-breakpoint)\n  .navbar,\n  .navbar-menu,\n  .navbar-start,\n  .navbar-end\n    align-items: stretch\n    display: flex\n  .navbar\n    min-height: $navbar-height\n    &.is-spaced\n      padding: $navbar-padding-vertical $navbar-padding-horizontal\n      .navbar-start,\n      .navbar-end\n        align-items: center\n      a.navbar-item,\n      .navbar-link\n        border-radius: $radius\n    &.is-transparent\n      a.navbar-item,\n      .navbar-link\n        &:focus,\n        &:hover,\n        &.is-active\n          background-color: transparent !important\n      .navbar-item.has-dropdown\n        &.is-active,\n        &.is-hoverable:focus,\n        &.is-hoverable:focus-within,\n        &.is-hoverable:hover\n          .navbar-link\n            background-color: transparent !important\n      .navbar-dropdown\n        a.navbar-item\n          &:focus,\n          &:hover\n            background-color: $navbar-dropdown-item-hover-background-color\n            color: $navbar-dropdown-item-hover-color\n          &.is-active\n            background-color: $navbar-dropdown-item-active-background-color\n            color: $navbar-dropdown-item-active-color\n  .navbar-burger\n    display: none\n  .navbar-item,\n  .navbar-link\n    align-items: center\n    display: flex\n  .navbar-item\n    &.has-dropdown\n      align-items: stretch\n    &.has-dropdown-up\n      .navbar-link::after\n        transform: rotate(135deg) translate(0.25em, -0.25em)\n      .navbar-dropdown\n        border-bottom: $navbar-dropdown-border-top\n        border-radius: $navbar-dropdown-radius $navbar-dropdown-radius 0 0\n        border-top: none\n        bottom: 100%\n        box-shadow: 0 -8px 8px bulmaRgba($scheme-invert, 0.1)\n        top: auto\n    &.is-active,\n    &.is-hoverable:focus,\n    &.is-hoverable:focus-within,\n    &.is-hoverable:hover\n      .navbar-dropdown\n        display: block\n        .navbar.is-spaced &,\n        &.is-boxed\n          opacity: 1\n          pointer-events: auto\n          transform: translateY(0)\n  .navbar-menu\n    flex-grow: 1\n    flex-shrink: 0\n  .navbar-start\n    justify-content: flex-start\n    +ltr-property(\"margin\", auto)\n  .navbar-end\n    justify-content: flex-end\n    +ltr-property(\"margin\", auto, false)\n  .navbar-dropdown\n    background-color: $navbar-dropdown-background-color\n    border-bottom-left-radius: $navbar-dropdown-radius\n    border-bottom-right-radius: $navbar-dropdown-radius\n    border-top: $navbar-dropdown-border-top\n    box-shadow: 0 8px 8px bulmaRgba($scheme-invert, 0.1)\n    display: none\n    font-size: 0.875rem\n    +ltr-position(0, false)\n    min-width: 100%\n    position: absolute\n    top: 100%\n    z-index: $navbar-dropdown-z\n    .navbar-item\n      padding: 0.375rem 1rem\n      white-space: nowrap\n    a.navbar-item\n      +ltr-property(\"padding\", 3rem)\n      &:focus,\n      &:hover\n        background-color: $navbar-dropdown-item-hover-background-color\n        color: $navbar-dropdown-item-hover-color\n      &.is-active\n        background-color: $navbar-dropdown-item-active-background-color\n        color: $navbar-dropdown-item-active-color\n    .navbar.is-spaced &,\n    &.is-boxed\n      border-radius: $navbar-dropdown-boxed-radius\n      border-top: none\n      box-shadow: $navbar-dropdown-boxed-shadow\n      display: block\n      opacity: 0\n      pointer-events: none\n      top: calc(100% + (#{$navbar-dropdown-offset}))\n      transform: translateY(-5px)\n      transition-duration: $speed\n      transition-property: opacity, transform\n    &.is-right\n      left: auto\n      right: 0\n  .navbar-divider\n    display: block\n  .navbar > .container,\n  .container > .navbar\n    .navbar-brand\n      +ltr-property(\"margin\", -.75rem, false)\n    .navbar-menu\n      +ltr-property(\"margin\", -.75rem)\n  // Fixed navbar\n  .navbar\n    &.is-fixed-bottom-desktop,\n    &.is-fixed-top-desktop\n      +navbar-fixed\n    &.is-fixed-bottom-desktop\n      bottom: 0\n      &.has-shadow\n        box-shadow: 0 -2px 3px bulmaRgba($scheme-invert, 0.1)\n    &.is-fixed-top-desktop\n      top: 0\n  html,\n  body\n    &.has-navbar-fixed-top-desktop\n      padding-top: $navbar-height\n    &.has-navbar-fixed-bottom-desktop\n      padding-bottom: $navbar-height\n    &.has-spaced-navbar-fixed-top\n      padding-top: $navbar-height + ($navbar-padding-vertical * 2)\n    &.has-spaced-navbar-fixed-bottom\n      padding-bottom: $navbar-height + ($navbar-padding-vertical * 2)\n  // Hover/Active states\n  a.navbar-item,\n  .navbar-link\n    &.is-active\n      color: $navbar-item-active-color\n    &.is-active:not(:focus):not(:hover)\n      background-color: $navbar-item-active-background-color\n  .navbar-item.has-dropdown\n    &:focus,\n    &:hover,\n    &.is-active\n      .navbar-link\n        background-color: $navbar-item-hover-background-color\n\n// Combination\n\n.hero\n  &.is-fullheight-with-navbar\n    min-height: calc(100vh - #{$navbar-height})\n"
  },
  {
    "path": "guide/style/bulma/sass/components/pagination.sass",
    "content": "@import \"../utilities/controls\"\n@import \"../utilities/mixins\"\n\n$pagination-color: $text-strong !default\n$pagination-border-color: $border !default\n$pagination-margin: -0.25rem !default\n$pagination-min-width: $control-height !default\n\n$pagination-item-font-size: 1em !default\n$pagination-item-margin: 0.25rem !default\n$pagination-item-padding-left: 0.5em !default\n$pagination-item-padding-right: 0.5em !default\n\n$pagination-nav-padding-left: 0.75em !default\n$pagination-nav-padding-right: 0.75em !default\n\n$pagination-hover-color: $link-hover !default\n$pagination-hover-border-color: $link-hover-border !default\n\n$pagination-focus-color: $link-focus !default\n$pagination-focus-border-color: $link-focus-border !default\n\n$pagination-active-color: $link-active !default\n$pagination-active-border-color: $link-active-border !default\n\n$pagination-disabled-color: $text-light !default\n$pagination-disabled-background-color: $border !default\n$pagination-disabled-border-color: $border !default\n\n$pagination-current-color: $link-invert !default\n$pagination-current-background-color: $link !default\n$pagination-current-border-color: $link !default\n\n$pagination-ellipsis-color: $grey-light !default\n\n$pagination-shadow-inset: inset 0 1px 2px rgba($scheme-invert, 0.2) !default\n\n.pagination\n  @extend %block\n  font-size: $size-normal\n  margin: $pagination-margin\n  // Sizes\n  &.is-small\n    font-size: $size-small\n  &.is-medium\n    font-size: $size-medium\n  &.is-large\n    font-size: $size-large\n  &.is-rounded\n    .pagination-previous,\n    .pagination-next\n      padding-left: 1em\n      padding-right: 1em\n      border-radius: $radius-rounded\n    .pagination-link\n      border-radius: $radius-rounded\n\n.pagination,\n.pagination-list\n  align-items: center\n  display: flex\n  justify-content: center\n  text-align: center\n\n.pagination-previous,\n.pagination-next,\n.pagination-link,\n.pagination-ellipsis\n  @extend %control\n  @extend %unselectable\n  font-size: $pagination-item-font-size\n  justify-content: center\n  margin: $pagination-item-margin\n  padding-left: $pagination-item-padding-left\n  padding-right: $pagination-item-padding-right\n  text-align: center\n\n.pagination-previous,\n.pagination-next,\n.pagination-link\n  border-color: $pagination-border-color\n  color: $pagination-color\n  min-width: $pagination-min-width\n  &:hover\n    border-color: $pagination-hover-border-color\n    color: $pagination-hover-color\n  &:focus\n    border-color: $pagination-focus-border-color\n  &:active\n    box-shadow: $pagination-shadow-inset\n  &[disabled],\n  &.is-disabled\n    background-color: $pagination-disabled-background-color\n    border-color: $pagination-disabled-border-color\n    box-shadow: none\n    color: $pagination-disabled-color\n    opacity: 0.5\n\n.pagination-previous,\n.pagination-next\n  padding-left: $pagination-nav-padding-left\n  padding-right: $pagination-nav-padding-right\n  white-space: nowrap\n\n.pagination-link\n  &.is-current\n    background-color: $pagination-current-background-color\n    border-color: $pagination-current-border-color\n    color: $pagination-current-color\n\n.pagination-ellipsis\n  color: $pagination-ellipsis-color\n  pointer-events: none\n\n.pagination-list\n  flex-wrap: wrap\n  li\n    list-style: none\n\n+mobile\n  .pagination\n    flex-wrap: wrap\n  .pagination-previous,\n  .pagination-next\n    flex-grow: 1\n    flex-shrink: 1\n  .pagination-list\n    li\n      flex-grow: 1\n      flex-shrink: 1\n\n+tablet\n  .pagination-list\n    flex-grow: 1\n    flex-shrink: 1\n    justify-content: flex-start\n    order: 1\n  .pagination-previous,\n  .pagination-next,\n  .pagination-link,\n  .pagination-ellipsis\n    margin-bottom: 0\n    margin-top: 0\n  .pagination-previous\n    order: 2\n  .pagination-next\n    order: 3\n  .pagination\n    justify-content: space-between\n    margin-bottom: 0\n    margin-top: 0\n    &.is-centered\n      .pagination-previous\n        order: 1\n      .pagination-list\n        justify-content: center\n        order: 2\n      .pagination-next\n        order: 3\n    &.is-right\n      .pagination-previous\n        order: 1\n      .pagination-next\n        order: 2\n      .pagination-list\n        justify-content: flex-end\n        order: 3\n"
  },
  {
    "path": "guide/style/bulma/sass/components/panel.sass",
    "content": "@import \"../utilities/mixins\"\n\n$panel-margin: $block-spacing !default\n$panel-item-border: 1px solid $border-light !default\n$panel-radius: $radius-large !default\n$panel-shadow: $shadow !default\n\n$panel-heading-background-color: $border-light !default\n$panel-heading-color: $text-strong !default\n$panel-heading-line-height: 1.25 !default\n$panel-heading-padding: 0.75em 1em !default\n$panel-heading-radius: $radius !default\n$panel-heading-size: 1.25em !default\n$panel-heading-weight: $weight-bold !default\n\n$panel-tabs-font-size: 0.875em !default\n$panel-tab-border-bottom: 1px solid $border !default\n$panel-tab-active-border-bottom-color: $link-active-border !default\n$panel-tab-active-color: $link-active !default\n\n$panel-list-item-color: $text !default\n$panel-list-item-hover-color: $link !default\n\n$panel-block-color: $text-strong !default\n$panel-block-hover-background-color: $background !default\n$panel-block-active-border-left-color: $link !default\n$panel-block-active-color: $link-active !default\n$panel-block-active-icon-color: $link !default\n\n$panel-icon-color: $text-light !default\n$panel-colors: $colors !default\n\n.panel\n  border-radius: $panel-radius\n  box-shadow: $panel-shadow\n  font-size: $size-normal\n  &:not(:last-child)\n    margin-bottom: $panel-margin\n  // Colors\n  @each $name, $components in $panel-colors\n    $color: nth($components, 1)\n    $color-invert: nth($components, 2)\n    &.is-#{$name}\n      .panel-heading\n        background-color: $color\n        color: $color-invert\n      .panel-tabs a.is-active\n        border-bottom-color: $color\n      .panel-block.is-active .panel-icon\n        color: $color\n\n.panel-tabs,\n.panel-block\n  &:not(:last-child)\n    border-bottom: $panel-item-border\n\n.panel-heading\n  background-color: $panel-heading-background-color\n  border-radius: $panel-radius $panel-radius 0 0\n  color: $panel-heading-color\n  font-size: $panel-heading-size\n  font-weight: $panel-heading-weight\n  line-height: $panel-heading-line-height\n  padding: $panel-heading-padding\n\n.panel-tabs\n  align-items: flex-end\n  display: flex\n  font-size: $panel-tabs-font-size\n  justify-content: center\n  a\n    border-bottom: $panel-tab-border-bottom\n    margin-bottom: -1px\n    padding: 0.5em\n    // Modifiers\n    &.is-active\n      border-bottom-color: $panel-tab-active-border-bottom-color\n      color: $panel-tab-active-color\n\n.panel-list\n  a\n    color: $panel-list-item-color\n    &:hover\n      color: $panel-list-item-hover-color\n\n.panel-block\n  align-items: center\n  color: $panel-block-color\n  display: flex\n  justify-content: flex-start\n  padding: 0.5em 0.75em\n  input[type=\"checkbox\"]\n    +ltr-property(\"margin\", 0.75em)\n  & > .control\n    flex-grow: 1\n    flex-shrink: 1\n    width: 100%\n  &.is-wrapped\n    flex-wrap: wrap\n  &.is-active\n    border-left-color: $panel-block-active-border-left-color\n    color: $panel-block-active-color\n    .panel-icon\n      color: $panel-block-active-icon-color\n  &:last-child\n    border-bottom-left-radius: $panel-radius\n    border-bottom-right-radius: $panel-radius\n\na.panel-block,\nlabel.panel-block\n  cursor: pointer\n  &:hover\n    background-color: $panel-block-hover-background-color\n\n.panel-icon\n  +fa(14px, 1em)\n  color: $panel-icon-color\n  +ltr-property(\"margin\", 0.75em)\n  .fa\n    font-size: inherit\n    line-height: inherit\n"
  },
  {
    "path": "guide/style/bulma/sass/components/tabs.sass",
    "content": "@import \"../utilities/mixins\"\n\n$tabs-border-bottom-color: $border !default\n$tabs-border-bottom-style: solid !default\n$tabs-border-bottom-width: 1px !default\n$tabs-link-color: $text !default\n$tabs-link-hover-border-bottom-color: $text-strong !default\n$tabs-link-hover-color: $text-strong !default\n$tabs-link-active-border-bottom-color: $link !default\n$tabs-link-active-color: $link !default\n$tabs-link-padding: 0.5em 1em !default\n\n$tabs-boxed-link-radius: $radius !default\n$tabs-boxed-link-hover-background-color: $background !default\n$tabs-boxed-link-hover-border-bottom-color: $border !default\n\n$tabs-boxed-link-active-background-color: $scheme-main !default\n$tabs-boxed-link-active-border-color: $border !default\n$tabs-boxed-link-active-border-bottom-color: transparent !default\n\n$tabs-toggle-link-border-color: $border !default\n$tabs-toggle-link-border-style: solid !default\n$tabs-toggle-link-border-width: 1px !default\n$tabs-toggle-link-hover-background-color: $background !default\n$tabs-toggle-link-hover-border-color: $border-hover !default\n$tabs-toggle-link-radius: $radius !default\n$tabs-toggle-link-active-background-color: $link !default\n$tabs-toggle-link-active-border-color: $link !default\n$tabs-toggle-link-active-color: $link-invert !default\n\n.tabs\n  @extend %block\n  +overflow-touch\n  @extend %unselectable\n  align-items: stretch\n  display: flex\n  font-size: $size-normal\n  justify-content: space-between\n  overflow: hidden\n  overflow-x: auto\n  white-space: nowrap\n  a\n    align-items: center\n    border-bottom-color: $tabs-border-bottom-color\n    border-bottom-style: $tabs-border-bottom-style\n    border-bottom-width: $tabs-border-bottom-width\n    color: $tabs-link-color\n    display: flex\n    justify-content: center\n    margin-bottom: -#{$tabs-border-bottom-width}\n    padding: $tabs-link-padding\n    vertical-align: top\n    &:hover\n      border-bottom-color: $tabs-link-hover-border-bottom-color\n      color: $tabs-link-hover-color\n  li\n    display: block\n    &.is-active\n      a\n        border-bottom-color: $tabs-link-active-border-bottom-color\n        color: $tabs-link-active-color\n  ul\n    align-items: center\n    border-bottom-color: $tabs-border-bottom-color\n    border-bottom-style: $tabs-border-bottom-style\n    border-bottom-width: $tabs-border-bottom-width\n    display: flex\n    flex-grow: 1\n    flex-shrink: 0\n    justify-content: flex-start\n    &.is-left\n      padding-right: 0.75em\n    &.is-center\n      flex: none\n      justify-content: center\n      padding-left: 0.75em\n      padding-right: 0.75em\n    &.is-right\n      justify-content: flex-end\n      padding-left: 0.75em\n  .icon\n    &:first-child\n      +ltr-property(\"margin\", 0.5em)\n    &:last-child\n      +ltr-property(\"margin\", 0.5em, false)\n  // Alignment\n  &.is-centered\n    ul\n      justify-content: center\n  &.is-right\n    ul\n      justify-content: flex-end\n  // Styles\n  &.is-boxed\n    a\n      border: 1px solid transparent\n      +ltr\n        border-radius: $tabs-boxed-link-radius $tabs-boxed-link-radius 0 0\n      +rtl\n        border-radius: 0 0 $tabs-boxed-link-radius $tabs-boxed-link-radius\n      &:hover\n        background-color: $tabs-boxed-link-hover-background-color\n        border-bottom-color: $tabs-boxed-link-hover-border-bottom-color\n    li\n      &.is-active\n        a\n          background-color: $tabs-boxed-link-active-background-color\n          border-color: $tabs-boxed-link-active-border-color\n          border-bottom-color: $tabs-boxed-link-active-border-bottom-color !important\n  &.is-fullwidth\n    li\n      flex-grow: 1\n      flex-shrink: 0\n  &.is-toggle\n    a\n      border-color: $tabs-toggle-link-border-color\n      border-style: $tabs-toggle-link-border-style\n      border-width: $tabs-toggle-link-border-width\n      margin-bottom: 0\n      position: relative\n      &:hover\n        background-color: $tabs-toggle-link-hover-background-color\n        border-color: $tabs-toggle-link-hover-border-color\n        z-index: 2\n    li\n      & + li\n        +ltr-property(\"margin\", -#{$tabs-toggle-link-border-width}, false)\n      &:first-child a\n        +ltr\n          border-top-left-radius: $tabs-toggle-link-radius\n          border-bottom-left-radius: $tabs-toggle-link-radius\n        +rtl\n          border-top-right-radius: $tabs-toggle-link-radius\n          border-bottom-right-radius: $tabs-toggle-link-radius\n      &:last-child a\n        +ltr\n          border-top-right-radius: $tabs-toggle-link-radius\n          border-bottom-right-radius: $tabs-toggle-link-radius\n        +rtl\n          border-top-left-radius: $tabs-toggle-link-radius\n          border-bottom-left-radius: $tabs-toggle-link-radius\n      &.is-active\n        a\n          background-color: $tabs-toggle-link-active-background-color\n          border-color: $tabs-toggle-link-active-border-color\n          color: $tabs-toggle-link-active-color\n          z-index: 1\n    ul\n      border-bottom: none\n    &.is-toggle-rounded\n      li\n        &:first-child a\n          +ltr\n            border-bottom-left-radius: $radius-rounded\n            border-top-left-radius: $radius-rounded\n            padding-left: 1.25em\n          +rtl\n            border-bottom-right-radius: $radius-rounded\n            border-top-right-radius: $radius-rounded\n            padding-right: 1.25em\n        &:last-child a\n          +ltr\n            border-bottom-right-radius: $radius-rounded\n            border-top-right-radius: $radius-rounded\n            padding-right: 1.25em\n          +rtl\n            border-bottom-left-radius: $radius-rounded\n            border-top-left-radius: $radius-rounded\n            padding-left: 1.25em\n  // Sizes\n  &.is-small\n    font-size: $size-small\n  &.is-medium\n    font-size: $size-medium\n  &.is-large\n    font-size: $size-large\n"
  },
  {
    "path": "guide/style/bulma/sass/elements/_all.sass",
    "content": "/* Bulma Elements */\n@charset \"utf-8\"\n\n@import \"box\"\n@import \"button\"\n@import \"container\"\n@import \"content\"\n@import \"icon\"\n@import \"image\"\n@import \"notification\"\n@import \"progress\"\n@import \"table\"\n@import \"tag\"\n@import \"title\"\n\n@import \"other\"\n"
  },
  {
    "path": "guide/style/bulma/sass/elements/box.sass",
    "content": "@import \"../utilities/mixins\"\n\n$box-color: $text !default\n$box-background-color: $scheme-main !default\n$box-radius: $radius-large !default\n$box-shadow: $shadow !default\n$box-padding: 1.25rem !default\n\n$box-link-hover-shadow: 0 0.5em 1em -0.125em rgba($scheme-invert, 0.1), 0 0 0 1px $link !default\n$box-link-active-shadow: inset 0 1px 2px rgba($scheme-invert, 0.2), 0 0 0 1px $link !default\n\n.box\n  @extend %block\n  background-color: $box-background-color\n  border-radius: $box-radius\n  box-shadow: $box-shadow\n  color: $box-color\n  display: block\n  padding: $box-padding\n\na.box\n  &:hover,\n  &:focus\n    box-shadow: $box-link-hover-shadow\n  &:active\n    box-shadow: $box-link-active-shadow\n"
  },
  {
    "path": "guide/style/bulma/sass/elements/button.sass",
    "content": "@import \"../utilities/controls\"\n@import \"../utilities/mixins\"\n\n$button-color: $text-strong !default\n$button-background-color: $scheme-main !default\n$button-family: false !default\n\n$button-border-color: $border !default\n$button-border-width: $control-border-width !default\n\n$button-padding-vertical: calc(0.5em - #{$button-border-width}) !default\n$button-padding-horizontal: 1em !default\n\n$button-hover-color: $link-hover !default\n$button-hover-border-color: $link-hover-border !default\n\n$button-focus-color: $link-focus !default\n$button-focus-border-color: $link-focus-border !default\n$button-focus-box-shadow-size: 0 0 0 0.125em !default\n$button-focus-box-shadow-color: bulmaRgba($link, 0.25) !default\n\n$button-active-color: $link-active !default\n$button-active-border-color: $link-active-border !default\n\n$button-text-color: $text !default\n$button-text-decoration: underline !default\n$button-text-hover-background-color: $background !default\n$button-text-hover-color: $text-strong !default\n\n$button-ghost-background: none !default\n$button-ghost-border-color: transparent !default\n$button-ghost-color: $link !default\n$button-ghost-decoration: none !default\n$button-ghost-hover-color: $link !default\n$button-ghost-hover-decoration: underline !default\n\n$button-disabled-background-color: $scheme-main !default\n$button-disabled-border-color: $border !default\n$button-disabled-shadow: none !default\n$button-disabled-opacity: 0.5 !default\n\n$button-static-color: $text-light !default\n$button-static-background-color: $scheme-main-ter !default\n$button-static-border-color: $border !default\n\n$button-colors: $colors !default\n$button-responsive-sizes: (\"mobile\": (\"small\": ($size-small * 0.75), \"normal\": ($size-small * 0.875), \"medium\": $size-small, \"large\": $size-normal), \"tablet-only\": (\"small\": ($size-small * 0.875), \"normal\": ($size-small), \"medium\": $size-normal, \"large\": $size-medium)) !default\n\n// The button sizes use mixins so they can be used at different breakpoints\n=button-small\n  &:not(.is-rounded)\n    border-radius: $radius-small\n  font-size: $size-small\n=button-normal\n  font-size: $size-normal\n=button-medium\n  font-size: $size-medium\n=button-large\n  font-size: $size-large\n\n.button\n  @extend %control\n  @extend %unselectable\n  background-color: $button-background-color\n  border-color: $button-border-color\n  border-width: $button-border-width\n  color: $button-color\n  cursor: pointer\n  @if $button-family\n    font-family: $button-family\n  justify-content: center\n  padding-bottom: $button-padding-vertical\n  padding-left: $button-padding-horizontal\n  padding-right: $button-padding-horizontal\n  padding-top: $button-padding-vertical\n  text-align: center\n  white-space: nowrap\n  strong\n    color: inherit\n  .icon\n    &,\n    &.is-small,\n    &.is-medium,\n    &.is-large\n      height: 1.5em\n      width: 1.5em\n    &:first-child:not(:last-child)\n      +ltr-property(\"margin\", calc(#{-0.5 * $button-padding-horizontal} - #{$button-border-width}), false)\n      +ltr-property(\"margin\", $button-padding-horizontal * 0.25)\n    &:last-child:not(:first-child)\n      +ltr-property(\"margin\", $button-padding-horizontal * 0.25, false)\n      +ltr-property(\"margin\", calc(#{-0.5 * $button-padding-horizontal} - #{$button-border-width}))\n    &:first-child:last-child\n      margin-left: calc(#{-0.5 * $button-padding-horizontal} - #{$button-border-width})\n      margin-right: calc(#{-0.5 * $button-padding-horizontal} - #{$button-border-width})\n  // States\n  &:hover,\n  &.is-hovered\n    border-color: $button-hover-border-color\n    color: $button-hover-color\n  &:focus,\n  &.is-focused\n    border-color: $button-focus-border-color\n    color: $button-focus-color\n    &:not(:active)\n      box-shadow: $button-focus-box-shadow-size $button-focus-box-shadow-color\n  &:active,\n  &.is-active\n    border-color: $button-active-border-color\n    color: $button-active-color\n  // Colors\n  &.is-text\n    background-color: transparent\n    border-color: transparent\n    color: $button-text-color\n    text-decoration: $button-text-decoration\n    &:hover,\n    &.is-hovered,\n    &:focus,\n    &.is-focused\n      background-color: $button-text-hover-background-color\n      color: $button-text-hover-color\n    &:active,\n    &.is-active\n      background-color: bulmaDarken($button-text-hover-background-color, 5%)\n      color: $button-text-hover-color\n    &[disabled],\n    fieldset[disabled] &\n      background-color: transparent\n      border-color: transparent\n      box-shadow: none\n  &.is-ghost\n    background: $button-ghost-background\n    border-color: $button-ghost-border-color\n    color: $button-ghost-color\n    text-decoration: $button-ghost-decoration\n    &:hover,\n    &.is-hovered\n      color: $button-ghost-hover-color\n      text-decoration: $button-ghost-hover-decoration\n  @each $name, $pair in $button-colors\n    $color: nth($pair, 1)\n    $color-invert: nth($pair, 2)\n    &.is-#{$name}\n      background-color: $color\n      border-color: transparent\n      color: $color-invert\n      &:hover,\n      &.is-hovered\n        background-color: bulmaDarken($color, 2.5%)\n        border-color: transparent\n        color: $color-invert\n      &:focus,\n      &.is-focused\n        border-color: transparent\n        color: $color-invert\n        &:not(:active)\n          box-shadow: $button-focus-box-shadow-size bulmaRgba($color, 0.25)\n      &:active,\n      &.is-active\n        background-color: bulmaDarken($color, 5%)\n        border-color: transparent\n        color: $color-invert\n      &[disabled],\n      fieldset[disabled] &\n        background-color: $color\n        border-color: $color\n        box-shadow: none\n      &.is-inverted\n        background-color: $color-invert\n        color: $color\n        &:hover,\n        &.is-hovered\n          background-color: bulmaDarken($color-invert, 5%)\n        &[disabled],\n        fieldset[disabled] &\n          background-color: $color-invert\n          border-color: transparent\n          box-shadow: none\n          color: $color\n      &.is-loading\n        &::after\n          border-color: transparent transparent $color-invert $color-invert !important\n      &.is-outlined\n        background-color: transparent\n        border-color: $color\n        color: $color\n        &:hover,\n        &.is-hovered,\n        &:focus,\n        &.is-focused\n          background-color: $color\n          border-color: $color\n          color: $color-invert\n        &.is-loading\n          &::after\n            border-color: transparent transparent $color $color !important\n          &:hover,\n          &.is-hovered,\n          &:focus,\n          &.is-focused\n            &::after\n              border-color: transparent transparent $color-invert $color-invert !important\n        &[disabled],\n        fieldset[disabled] &\n          background-color: transparent\n          border-color: $color\n          box-shadow: none\n          color: $color\n      &.is-inverted.is-outlined\n        background-color: transparent\n        border-color: $color-invert\n        color: $color-invert\n        &:hover,\n        &.is-hovered,\n        &:focus,\n        &.is-focused\n          background-color: $color-invert\n          color: $color\n        &.is-loading\n          &:hover,\n          &.is-hovered,\n          &:focus,\n          &.is-focused\n            &::after\n              border-color: transparent transparent $color $color !important\n        &[disabled],\n        fieldset[disabled] &\n          background-color: transparent\n          border-color: $color-invert\n          box-shadow: none\n          color: $color-invert\n      // If light and dark colors are provided\n      @if length($pair) >= 4\n        $color-light: nth($pair, 3)\n        $color-dark: nth($pair, 4)\n        &.is-light\n          background-color: $color-light\n          color: $color-dark\n          &:hover,\n          &.is-hovered\n            background-color: bulmaDarken($color-light, 2.5%)\n            border-color: transparent\n            color: $color-dark\n          &:active,\n          &.is-active\n            background-color: bulmaDarken($color-light, 5%)\n            border-color: transparent\n            color: $color-dark\n  // Sizes\n  &.is-small\n    +button-small\n  &.is-normal\n    +button-normal\n  &.is-medium\n    +button-medium\n  &.is-large\n    +button-large\n  // Modifiers\n  &[disabled],\n  fieldset[disabled] &\n    background-color: $button-disabled-background-color\n    border-color: $button-disabled-border-color\n    box-shadow: $button-disabled-shadow\n    opacity: $button-disabled-opacity\n  &.is-fullwidth\n    display: flex\n    width: 100%\n  &.is-loading\n    color: transparent !important\n    pointer-events: none\n    &::after\n      @extend %loader\n      +center(1em)\n      position: absolute !important\n  &.is-static\n    background-color: $button-static-background-color\n    border-color: $button-static-border-color\n    color: $button-static-color\n    box-shadow: none\n    pointer-events: none\n  &.is-rounded\n    border-radius: $radius-rounded\n    padding-left: calc(#{$button-padding-horizontal} + 0.25em)\n    padding-right: calc(#{$button-padding-horizontal} + 0.25em)\n\n.buttons\n  align-items: center\n  display: flex\n  flex-wrap: wrap\n  justify-content: flex-start\n  .button\n    margin-bottom: 0.5rem\n    &:not(:last-child):not(.is-fullwidth)\n      +ltr-property(\"margin\", 0.5rem)\n  &:last-child\n    margin-bottom: -0.5rem\n  &:not(:last-child)\n    margin-bottom: 1rem\n  // Sizes\n  &.are-small\n    .button:not(.is-normal):not(.is-medium):not(.is-large)\n      +button-small\n  &.are-medium\n    .button:not(.is-small):not(.is-normal):not(.is-large)\n      +button-medium\n  &.are-large\n    .button:not(.is-small):not(.is-normal):not(.is-medium)\n      +button-large\n  &.has-addons\n    .button\n      &:not(:first-child)\n        border-bottom-left-radius: 0\n        border-top-left-radius: 0\n      &:not(:last-child)\n        border-bottom-right-radius: 0\n        border-top-right-radius: 0\n        +ltr-property(\"margin\", -1px)\n      &:last-child\n        +ltr-property(\"margin\", 0)\n      &:hover,\n      &.is-hovered\n        z-index: 2\n      &:focus,\n      &.is-focused,\n      &:active,\n      &.is-active,\n      &.is-selected\n        z-index: 3\n        &:hover\n          z-index: 4\n      &.is-expanded\n        flex-grow: 1\n        flex-shrink: 1\n  &.is-centered\n    justify-content: center\n    &:not(.has-addons)\n      .button:not(.is-fullwidth)\n        margin-left: 0.25rem\n        margin-right: 0.25rem\n  &.is-right\n    justify-content: flex-end\n    &:not(.has-addons)\n      .button:not(.is-fullwidth)\n        margin-left: 0.25rem\n        margin-right: 0.25rem\n\n@each $bp-name, $bp-sizes in $button-responsive-sizes\n  +breakpoint($bp-name)\n    @each $size, $value in $bp-sizes\n      @if $size != \"normal\"\n        .button.is-responsive.is-#{$size}\n          font-size: $value\n      @else\n        .button.is-responsive,\n        .button.is-responsive.is-normal\n          font-size: $value\n"
  },
  {
    "path": "guide/style/bulma/sass/elements/container.sass",
    "content": "@import \"../utilities/mixins\"\n\n$container-offset: (2 * $gap) !default\n$container-max-width: $fullhd !default\n\n.container\n  flex-grow: 1\n  margin: 0 auto\n  position: relative\n  width: auto\n  &.is-fluid\n    max-width: none !important\n    padding-left: $gap\n    padding-right: $gap\n    width: 100%\n  +desktop\n    max-width: $desktop - $container-offset\n  +until-widescreen\n    &.is-widescreen:not(.is-max-desktop)\n      max-width: min($widescreen, $container-max-width) - $container-offset\n  +until-fullhd\n    &.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen)\n      max-width: min($fullhd, $container-max-width) - $container-offset\n  +widescreen\n    &:not(.is-max-desktop)\n      max-width: min($widescreen, $container-max-width) - $container-offset\n  +fullhd\n    &:not(.is-max-desktop):not(.is-max-widescreen)\n      max-width: min($fullhd, $container-max-width) - $container-offset\n"
  },
  {
    "path": "guide/style/bulma/sass/elements/content.sass",
    "content": "@import \"../utilities/mixins\"\n\n$content-heading-color: $text-strong !default\n$content-heading-weight: $weight-semibold !default\n$content-heading-line-height: 1.125 !default\n\n$content-block-margin-bottom: 1em !default\n\n$content-blockquote-background-color: $background !default\n$content-blockquote-border-left: 5px solid $border !default\n$content-blockquote-padding: 1.25em 1.5em !default\n\n$content-pre-padding: 1.25em 1.5em !default\n\n$content-table-cell-border: 1px solid $border !default\n$content-table-cell-border-width: 0 0 1px !default\n$content-table-cell-padding: 0.5em 0.75em !default\n$content-table-cell-heading-color: $text-strong !default\n$content-table-head-cell-border-width: 0 0 2px !default\n$content-table-head-cell-color: $text-strong !default\n$content-table-body-last-row-cell-border-bottom-width: 0 !default\n$content-table-foot-cell-border-width: 2px 0 0 !default\n$content-table-foot-cell-color: $text-strong !default\n\n.content\n  @extend %block\n  // Inline\n  li + li\n    margin-top: 0.25em\n  // Block\n  p,\n  dl,\n  ol,\n  ul,\n  blockquote,\n  pre,\n  table\n    &:not(:last-child)\n      margin-bottom: $content-block-margin-bottom\n  h1,\n  h2,\n  h3,\n  h4,\n  h5,\n  h6\n    color: $content-heading-color\n    font-weight: $content-heading-weight\n    line-height: $content-heading-line-height\n  h1\n    font-size: 2em\n    margin-bottom: 0.5em\n    &:not(:first-child)\n      margin-top: 1em\n  h2\n    font-size: 1.75em\n    margin-bottom: 0.5714em\n    &:not(:first-child)\n      margin-top: 1.1428em\n  h3\n    font-size: 1.5em\n    margin-bottom: 0.6666em\n    &:not(:first-child)\n      margin-top: 1.3333em\n  h4\n    font-size: 1.25em\n    margin-bottom: 0.8em\n  h5\n    font-size: 1.125em\n    margin-bottom: 0.8888em\n  h6\n    font-size: 1em\n    margin-bottom: 1em\n  blockquote\n    background-color: $content-blockquote-background-color\n    +ltr-property(\"border\", $content-blockquote-border-left, false)\n    padding: $content-blockquote-padding\n  ol\n    list-style-position: outside\n    +ltr-property(\"margin\", 2em, false)\n    margin-top: 1em\n    &:not([type])\n      list-style-type: decimal\n      &.is-lower-alpha\n        list-style-type: lower-alpha\n      &.is-lower-roman\n        list-style-type: lower-roman\n      &.is-upper-alpha\n        list-style-type: upper-alpha\n      &.is-upper-roman\n        list-style-type: upper-roman\n  ul\n    list-style: disc outside\n    +ltr-property(\"margin\", 2em, false)\n    margin-top: 1em\n    ul\n      list-style-type: circle\n      margin-top: 0.5em\n      ul\n        list-style-type: square\n  dd\n    +ltr-property(\"margin\", 2em, false)\n  figure\n    margin-left: 2em\n    margin-right: 2em\n    text-align: center\n    &:not(:first-child)\n      margin-top: 2em\n    &:not(:last-child)\n      margin-bottom: 2em\n    img\n      display: inline-block\n    figcaption\n      font-style: italic\n  pre\n    +overflow-touch\n    overflow-x: auto\n    padding: $content-pre-padding\n    white-space: pre\n    word-wrap: normal\n  sup,\n  sub\n    font-size: 75%\n  table\n    width: 100%\n    td,\n    th\n      border: $content-table-cell-border\n      border-width: $content-table-cell-border-width\n      padding: $content-table-cell-padding\n      vertical-align: top\n    th\n      color: $content-table-cell-heading-color\n      &:not([align])\n        text-align: inherit\n    thead\n      td,\n      th\n        border-width: $content-table-head-cell-border-width\n        color: $content-table-head-cell-color\n    tfoot\n      td,\n      th\n        border-width: $content-table-foot-cell-border-width\n        color: $content-table-foot-cell-color\n    tbody\n      tr\n        &:last-child\n          td,\n          th\n            border-bottom-width: $content-table-body-last-row-cell-border-bottom-width\n  .tabs\n    li + li\n      margin-top: 0\n  // Sizes\n  &.is-small\n    font-size: $size-small\n  &.is-normal\n    font-size: $size-normal\n  &.is-medium\n    font-size: $size-medium\n  &.is-large\n    font-size: $size-large\n"
  },
  {
    "path": "guide/style/bulma/sass/elements/form.sass",
    "content": "@warn \"The form.sass file is DEPRECATED. It has moved into its own /form folder. Please import sass/form/_all instead.\"\n"
  },
  {
    "path": "guide/style/bulma/sass/elements/icon.sass",
    "content": "$icon-dimensions: 1.5rem !default\n$icon-dimensions-small: 1rem !default\n$icon-dimensions-medium: 2rem !default\n$icon-dimensions-large: 3rem !default\n$icon-text-spacing: 0.25em !default\n\n.icon\n  align-items: center\n  display: inline-flex\n  justify-content: center\n  height: $icon-dimensions\n  width: $icon-dimensions\n  // Sizes\n  &.is-small\n    height: $icon-dimensions-small\n    width: $icon-dimensions-small\n  &.is-medium\n    height: $icon-dimensions-medium\n    width: $icon-dimensions-medium\n  &.is-large\n    height: $icon-dimensions-large\n    width: $icon-dimensions-large\n\n.icon-text\n  align-items: flex-start\n  color: inherit\n  display: inline-flex\n  flex-wrap: wrap\n  line-height: $icon-dimensions\n  vertical-align: top\n  .icon\n    flex-grow: 0\n    flex-shrink: 0\n    &:not(:last-child)\n      +ltr\n        margin-right: $icon-text-spacing\n      +rtl\n        margin-left: $icon-text-spacing\n    &:not(:first-child)\n      +ltr\n        margin-left: $icon-text-spacing\n      +rtl\n        margin-right: $icon-text-spacing\n\ndiv.icon-text\n  display: flex\n"
  },
  {
    "path": "guide/style/bulma/sass/elements/image.sass",
    "content": "@import \"../utilities/mixins\"\n\n$dimensions: 16 24 32 48 64 96 128 !default\n\n.image\n  display: block\n  position: relative\n  img\n    display: block\n    height: auto\n    width: 100%\n    &.is-rounded\n      border-radius: $radius-rounded\n  &.is-fullwidth\n    width: 100%\n  // Ratio\n  &.is-square,\n  &.is-1by1,\n  &.is-5by4,\n  &.is-4by3,\n  &.is-3by2,\n  &.is-5by3,\n  &.is-16by9,\n  &.is-2by1,\n  &.is-3by1,\n  &.is-4by5,\n  &.is-3by4,\n  &.is-2by3,\n  &.is-3by5,\n  &.is-9by16,\n  &.is-1by2,\n  &.is-1by3\n    img,\n    .has-ratio\n      @extend %overlay\n      height: 100%\n      width: 100%\n  &.is-square,\n  &.is-1by1\n    padding-top: 100%\n  &.is-5by4\n    padding-top: 80%\n  &.is-4by3\n    padding-top: 75%\n  &.is-3by2\n    padding-top: 66.6666%\n  &.is-5by3\n    padding-top: 60%\n  &.is-16by9\n    padding-top: 56.25%\n  &.is-2by1\n    padding-top: 50%\n  &.is-3by1\n    padding-top: 33.3333%\n  &.is-4by5\n    padding-top: 125%\n  &.is-3by4\n    padding-top: 133.3333%\n  &.is-2by3\n    padding-top: 150%\n  &.is-3by5\n    padding-top: 166.6666%\n  &.is-9by16\n    padding-top: 177.7777%\n  &.is-1by2\n    padding-top: 200%\n  &.is-1by3\n    padding-top: 300%\n  // Sizes\n  @each $dimension in $dimensions\n    &.is-#{$dimension}x#{$dimension}\n      height: $dimension * 1px\n      width: $dimension * 1px\n"
  },
  {
    "path": "guide/style/bulma/sass/elements/notification.sass",
    "content": "@import \"../utilities/mixins\"\n\n$notification-background-color: $background !default\n$notification-code-background-color: $scheme-main !default\n$notification-radius: $radius !default\n$notification-padding: 1.25rem 2.5rem 1.25rem 1.5rem !default\n$notification-padding-ltr: 1.25rem 2.5rem 1.25rem 1.5rem !default\n$notification-padding-rtl: 1.25rem 1.5rem 1.25rem 2.5rem !default\n\n$notification-colors: $colors !default\n\n.notification\n  @extend %block\n  background-color: $notification-background-color\n  border-radius: $notification-radius\n  position: relative\n  +ltr\n    padding: $notification-padding-ltr\n  +rtl\n    padding: $notification-padding-rtl\n  a:not(.button):not(.dropdown-item)\n    color: currentColor\n    text-decoration: underline\n  strong\n    color: currentColor\n  code,\n  pre\n    background: $notification-code-background-color\n  pre code\n    background: transparent\n  & > .delete\n    +ltr-position(0.5rem)\n    position: absolute\n    top: 0.5rem\n  .title,\n  .subtitle,\n  .content\n    color: currentColor\n  // Colors\n  @each $name, $pair in $notification-colors\n    $color: nth($pair, 1)\n    $color-invert: nth($pair, 2)\n    &.is-#{$name}\n      background-color: $color\n      color: $color-invert\n      // If light and dark colors are provided\n      @if length($pair) >= 4\n        $color-light: nth($pair, 3)\n        $color-dark: nth($pair, 4)\n        &.is-light\n          background-color: $color-light\n          color: $color-dark\n"
  },
  {
    "path": "guide/style/bulma/sass/elements/other.sass",
    "content": "@import \"../utilities/mixins\"\n\n.block\n  @extend %block\n\n.delete\n  @extend %delete\n\n.heading\n  display: block\n  font-size: 11px\n  letter-spacing: 1px\n  margin-bottom: 5px\n  text-transform: uppercase\n\n.loader\n  @extend %loader\n\n.number\n  align-items: center\n  background-color: $background\n  border-radius: $radius-rounded\n  display: inline-flex\n  font-size: $size-medium\n  height: 2em\n  justify-content: center\n  margin-right: 1.5rem\n  min-width: 2.5em\n  padding: 0.25rem 0.5rem\n  text-align: center\n  vertical-align: top\n"
  },
  {
    "path": "guide/style/bulma/sass/elements/progress.sass",
    "content": "@import \"../utilities/mixins\"\n\n$progress-bar-background-color: $border-light !default\n$progress-value-background-color: $text !default\n$progress-border-radius: $radius-rounded !default\n\n$progress-indeterminate-duration: 1.5s !default\n\n$progress-colors: $colors !default\n\n.progress\n  @extend %block\n  -moz-appearance: none\n  -webkit-appearance: none\n  border: none\n  border-radius: $progress-border-radius\n  display: block\n  height: $size-normal\n  overflow: hidden\n  padding: 0\n  width: 100%\n  &::-webkit-progress-bar\n    background-color: $progress-bar-background-color\n  &::-webkit-progress-value\n    background-color: $progress-value-background-color\n  &::-moz-progress-bar\n    background-color: $progress-value-background-color\n  &::-ms-fill\n    background-color: $progress-value-background-color\n    border: none\n  // Colors\n  @each $name, $pair in $progress-colors\n    $color: nth($pair, 1)\n    &.is-#{$name}\n      &::-webkit-progress-value\n        background-color: $color\n      &::-moz-progress-bar\n        background-color: $color\n      &::-ms-fill\n        background-color: $color\n      &:indeterminate\n        background-image: linear-gradient(to right, $color 30%, $progress-bar-background-color 30%)\n\n  &:indeterminate\n    animation-duration: $progress-indeterminate-duration\n    animation-iteration-count: infinite\n    animation-name: moveIndeterminate\n    animation-timing-function: linear\n    background-color: $progress-bar-background-color\n    background-image: linear-gradient(to right, $text 30%, $progress-bar-background-color 30%)\n    background-position: top left\n    background-repeat: no-repeat\n    background-size: 150% 150%\n    &::-webkit-progress-bar\n      background-color: transparent\n    &::-moz-progress-bar\n      background-color: transparent\n    &::-ms-fill\n      animation-name: none\n\n  // Sizes\n  &.is-small\n    height: $size-small\n  &.is-medium\n    height: $size-medium\n  &.is-large\n    height: $size-large\n\n@keyframes moveIndeterminate\n  from\n    background-position: 200% 0\n  to\n    background-position: -200% 0\n"
  },
  {
    "path": "guide/style/bulma/sass/elements/table.sass",
    "content": "@import \"../utilities/mixins\"\n\n$table-color: $text-strong !default\n$table-background-color: $scheme-main !default\n\n$table-cell-border: 1px solid $border !default\n$table-cell-border-width: 0 0 1px !default\n$table-cell-padding: 0.5em 0.75em !default\n$table-cell-heading-color: $text-strong !default\n$table-cell-text-align: left !default\n\n$table-head-cell-border-width: 0 0 2px !default\n$table-head-cell-color: $text-strong !default\n$table-foot-cell-border-width: 2px 0 0 !default\n$table-foot-cell-color: $text-strong !default\n\n$table-head-background-color: transparent !default\n$table-body-background-color: transparent !default\n$table-foot-background-color: transparent !default\n\n$table-row-hover-background-color: $scheme-main-bis !default\n\n$table-row-active-background-color: $primary !default\n$table-row-active-color: $primary-invert !default\n\n$table-striped-row-even-background-color: $scheme-main-bis !default\n$table-striped-row-even-hover-background-color: $scheme-main-ter !default\n\n$table-colors: $colors !default\n\n.table\n  @extend %block\n  background-color: $table-background-color\n  color: $table-color\n  td,\n  th\n    border: $table-cell-border\n    border-width: $table-cell-border-width\n    padding: $table-cell-padding\n    vertical-align: top\n    // Colors\n    @each $name, $pair in $table-colors\n      $color: nth($pair, 1)\n      $color-invert: nth($pair, 2)\n      &.is-#{$name}\n        background-color: $color\n        border-color: $color\n        color: $color-invert\n    // Modifiers\n    &.is-narrow\n      white-space: nowrap\n      width: 1%\n    &.is-selected\n      background-color: $table-row-active-background-color\n      color: $table-row-active-color\n      a,\n      strong\n        color: currentColor\n    &.is-vcentered\n      vertical-align: middle\n  th\n    color: $table-cell-heading-color\n    &:not([align])\n      text-align: $table-cell-text-align\n  tr\n    &.is-selected\n      background-color: $table-row-active-background-color\n      color: $table-row-active-color\n      a,\n      strong\n        color: currentColor\n      td,\n      th\n        border-color: $table-row-active-color\n        color: currentColor\n  thead\n    background-color: $table-head-background-color\n    td,\n    th\n      border-width: $table-head-cell-border-width\n      color: $table-head-cell-color\n  tfoot\n    background-color: $table-foot-background-color\n    td,\n    th\n      border-width: $table-foot-cell-border-width\n      color: $table-foot-cell-color\n  tbody\n    background-color: $table-body-background-color\n    tr\n      &:last-child\n        td,\n        th\n          border-bottom-width: 0\n  // Modifiers\n  &.is-bordered\n    td,\n    th\n      border-width: 1px\n    tr\n      &:last-child\n        td,\n        th\n          border-bottom-width: 1px\n  &.is-fullwidth\n    width: 100%\n  &.is-hoverable\n    tbody\n      tr:not(.is-selected)\n        &:hover\n          background-color: $table-row-hover-background-color\n    &.is-striped\n      tbody\n        tr:not(.is-selected)\n          &:hover\n            background-color: $table-row-hover-background-color\n            &:nth-child(even)\n              background-color: $table-striped-row-even-hover-background-color\n  &.is-narrow\n    td,\n    th\n      padding: 0.25em 0.5em\n  &.is-striped\n    tbody\n      tr:not(.is-selected)\n        &:nth-child(even)\n          background-color: $table-striped-row-even-background-color\n\n.table-container\n  @extend %block\n  +overflow-touch\n  overflow: auto\n  overflow-y: hidden\n  max-width: 100%\n"
  },
  {
    "path": "guide/style/bulma/sass/elements/tag.sass",
    "content": "@import \"../utilities/mixins\"\n\n$tag-background-color: $background !default\n$tag-color: $text !default\n$tag-radius: $radius !default\n$tag-delete-margin: 1px !default\n\n$tag-colors: $colors !default\n\n.tags\n  align-items: center\n  display: flex\n  flex-wrap: wrap\n  justify-content: flex-start\n  .tag\n    margin-bottom: 0.5rem\n    &:not(:last-child)\n      +ltr-property(\"margin\", 0.5rem)\n  &:last-child\n    margin-bottom: -0.5rem\n  &:not(:last-child)\n    margin-bottom: 1rem\n  // Sizes\n  &.are-medium\n    .tag:not(.is-normal):not(.is-large)\n      font-size: $size-normal\n  &.are-large\n    .tag:not(.is-normal):not(.is-medium)\n      font-size: $size-medium\n  &.is-centered\n    justify-content: center\n    .tag\n      margin-right: 0.25rem\n      margin-left: 0.25rem\n  &.is-right\n    justify-content: flex-end\n    .tag\n      &:not(:first-child)\n        margin-left: 0.5rem\n      &:not(:last-child)\n        margin-right: 0\n  &.has-addons\n    .tag\n      +ltr-property(\"margin\", 0)\n      &:not(:first-child)\n        +ltr-property(\"margin\", 0, false)\n        +ltr\n          border-top-left-radius: 0\n          border-bottom-left-radius: 0\n        +rtl\n          border-top-right-radius: 0\n          border-bottom-right-radius: 0\n      &:not(:last-child)\n        +ltr\n          border-top-right-radius: 0\n          border-bottom-right-radius: 0\n        +rtl\n          border-top-left-radius: 0\n          border-bottom-left-radius: 0\n\n.tag:not(body)\n  align-items: center\n  background-color: $tag-background-color\n  border-radius: $tag-radius\n  color: $tag-color\n  display: inline-flex\n  font-size: $size-small\n  height: 2em\n  justify-content: center\n  line-height: 1.5\n  padding-left: 0.75em\n  padding-right: 0.75em\n  white-space: nowrap\n  .delete\n    +ltr-property(\"margin\", 0.25rem, false)\n    +ltr-property(\"margin\", -0.375rem)\n  // Colors\n  @each $name, $pair in $tag-colors\n    $color: nth($pair, 1)\n    $color-invert: nth($pair, 2)\n    &.is-#{$name}\n      background-color: $color\n      color: $color-invert\n      // If a light and dark colors are provided\n      @if length($pair) > 3\n        $color-light: nth($pair, 3)\n        $color-dark: nth($pair, 4)\n        &.is-light\n          background-color: $color-light\n          color: $color-dark\n  // Sizes\n  &.is-normal\n    font-size: $size-small\n  &.is-medium\n    font-size: $size-normal\n  &.is-large\n    font-size: $size-medium\n  .icon\n    &:first-child:not(:last-child)\n      +ltr-property(\"margin\", -0.375em, false)\n      +ltr-property(\"margin\", 0.1875em)\n    &:last-child:not(:first-child)\n      +ltr-property(\"margin\", 0.1875em, false)\n      +ltr-property(\"margin\", -0.375em)\n    &:first-child:last-child\n      +ltr-property(\"margin\", -0.375em, false)\n      +ltr-property(\"margin\", -0.375em)\n  // Modifiers\n  &.is-delete\n    +ltr-property(\"margin\", $tag-delete-margin, false)\n    padding: 0\n    position: relative\n    width: 2em\n    &::before,\n    &::after\n      background-color: currentColor\n      content: \"\"\n      display: block\n      left: 50%\n      position: absolute\n      top: 50%\n      transform: translateX(-50%) translateY(-50%) rotate(45deg)\n      transform-origin: center center\n    &::before\n      height: 1px\n      width: 50%\n    &::after\n      height: 50%\n      width: 1px\n    &:hover,\n    &:focus\n      background-color: darken($tag-background-color, 5%)\n    &:active\n      background-color: darken($tag-background-color, 10%)\n  &.is-rounded\n    border-radius: $radius-rounded\n\na.tag\n  &:hover\n    text-decoration: underline\n"
  },
  {
    "path": "guide/style/bulma/sass/elements/title.sass",
    "content": "@import \"../utilities/mixins\"\n\n$title-color: $text-strong !default\n$title-family: false !default\n$title-size: $size-3 !default\n$title-weight: $weight-semibold !default\n$title-line-height: 1.125 !default\n$title-strong-color: inherit !default\n$title-strong-weight: inherit !default\n$title-sub-size: 0.75em !default\n$title-sup-size: 0.75em !default\n\n$subtitle-color: $text !default\n$subtitle-family: false !default\n$subtitle-size: $size-5 !default\n$subtitle-weight: $weight-normal !default\n$subtitle-line-height: 1.25 !default\n$subtitle-strong-color: $text-strong !default\n$subtitle-strong-weight: $weight-semibold !default\n$subtitle-negative-margin: -1.25rem !default\n\n.title,\n.subtitle\n  @extend %block\n  word-break: break-word\n  em,\n  span\n    font-weight: inherit\n  sub\n    font-size: $title-sub-size\n  sup\n    font-size: $title-sup-size\n  .tag\n    vertical-align: middle\n\n.title\n  color: $title-color\n  @if $title-family\n    font-family: $title-family\n  font-size: $title-size\n  font-weight: $title-weight\n  line-height: $title-line-height\n  strong\n    color: $title-strong-color\n    font-weight: $title-strong-weight\n  &:not(.is-spaced) + .subtitle\n    margin-top: $subtitle-negative-margin\n  // Sizes\n  @each $size in $sizes\n    $i: index($sizes, $size)\n    &.is-#{$i}\n      font-size: $size\n\n.subtitle\n  color: $subtitle-color\n  @if $subtitle-family\n    font-family: $subtitle-family\n  font-size: $subtitle-size\n  font-weight: $subtitle-weight\n  line-height: $subtitle-line-height\n  strong\n    color: $subtitle-strong-color\n    font-weight: $subtitle-strong-weight\n  &:not(.is-spaced) + .title\n    margin-top: $subtitle-negative-margin\n  // Sizes\n  @each $size in $sizes\n    $i: index($sizes, $size)\n    &.is-#{$i}\n      font-size: $size\n"
  },
  {
    "path": "guide/style/bulma/sass/form/_all.sass",
    "content": "/* Bulma Form */\n@charset \"utf-8\"\n\n@import \"shared\"\n@import \"input-textarea\"\n@import \"checkbox-radio\"\n@import \"select\"\n@import \"file\"\n@import \"tools\"\n"
  },
  {
    "path": "guide/style/bulma/sass/form/checkbox-radio.sass",
    "content": "%checkbox-radio\n  cursor: pointer\n  display: inline-block\n  line-height: 1.25\n  position: relative\n  input\n    cursor: pointer\n  &:hover\n    color: $input-hover-color\n  &[disabled],\n  fieldset[disabled] &,\n  input[disabled]\n    color: $input-disabled-color\n    cursor: not-allowed\n\n.checkbox\n  @extend %checkbox-radio\n\n.radio\n  @extend %checkbox-radio\n  & + .radio\n    +ltr-property(\"margin\", 0.5em, false)\n"
  },
  {
    "path": "guide/style/bulma/sass/form/file.sass",
    "content": "$file-border-color: $border !default\n$file-radius: $radius !default\n\n$file-cta-background-color: $scheme-main-ter !default\n$file-cta-color: $text !default\n$file-cta-hover-color: $text-strong !default\n$file-cta-active-color: $text-strong !default\n\n$file-name-border-color: $border !default\n$file-name-border-style: solid !default\n$file-name-border-width: 1px 1px 1px 0 !default\n$file-name-max-width: 16em !default\n\n$file-colors: $form-colors !default\n\n.file\n  @extend %unselectable\n  align-items: stretch\n  display: flex\n  justify-content: flex-start\n  position: relative\n  // Colors\n  @each $name, $pair in $file-colors\n    $color: nth($pair, 1)\n    $color-invert: nth($pair, 2)\n    &.is-#{$name}\n      .file-cta\n        background-color: $color\n        border-color: transparent\n        color: $color-invert\n      &:hover,\n      &.is-hovered\n        .file-cta\n          background-color: bulmaDarken($color, 2.5%)\n          border-color: transparent\n          color: $color-invert\n      &:focus,\n      &.is-focused\n        .file-cta\n          border-color: transparent\n          box-shadow: 0 0 0.5em bulmaRgba($color, 0.25)\n          color: $color-invert\n      &:active,\n      &.is-active\n        .file-cta\n          background-color: bulmaDarken($color, 5%)\n          border-color: transparent\n          color: $color-invert\n  // Sizes\n  &.is-small\n    font-size: $size-small\n  &.is-normal\n    font-size: $size-normal\n  &.is-medium\n    font-size: $size-medium\n    .file-icon\n      .fa\n        font-size: 21px\n  &.is-large\n    font-size: $size-large\n    .file-icon\n      .fa\n        font-size: 28px\n  // Modifiers\n  &.has-name\n    .file-cta\n      border-bottom-right-radius: 0\n      border-top-right-radius: 0\n    .file-name\n      border-bottom-left-radius: 0\n      border-top-left-radius: 0\n    &.is-empty\n      .file-cta\n        border-radius: $file-radius\n      .file-name\n        display: none\n  &.is-boxed\n    .file-label\n      flex-direction: column\n    .file-cta\n      flex-direction: column\n      height: auto\n      padding: 1em 3em\n    .file-name\n      border-width: 0 1px 1px\n    .file-icon\n      height: 1.5em\n      width: 1.5em\n      .fa\n        font-size: 21px\n    &.is-small\n      .file-icon .fa\n        font-size: 14px\n    &.is-medium\n      .file-icon .fa\n        font-size: 28px\n    &.is-large\n      .file-icon .fa\n        font-size: 35px\n    &.has-name\n      .file-cta\n        border-radius: $file-radius $file-radius 0 0\n      .file-name\n        border-radius: 0 0 $file-radius $file-radius\n        border-width: 0 1px 1px\n  &.is-centered\n    justify-content: center\n  &.is-fullwidth\n    .file-label\n      width: 100%\n    .file-name\n      flex-grow: 1\n      max-width: none\n  &.is-right\n    justify-content: flex-end\n    .file-cta\n      border-radius: 0 $file-radius $file-radius 0\n    .file-name\n      border-radius: $file-radius 0 0 $file-radius\n      border-width: 1px 0 1px 1px\n      order: -1\n\n.file-label\n  align-items: stretch\n  display: flex\n  cursor: pointer\n  justify-content: flex-start\n  overflow: hidden\n  position: relative\n  &:hover\n    .file-cta\n      background-color: bulmaDarken($file-cta-background-color, 2.5%)\n      color: $file-cta-hover-color\n    .file-name\n      border-color: bulmaDarken($file-name-border-color, 2.5%)\n  &:active\n    .file-cta\n      background-color: bulmaDarken($file-cta-background-color, 5%)\n      color: $file-cta-active-color\n    .file-name\n      border-color: bulmaDarken($file-name-border-color, 5%)\n\n.file-input\n  height: 100%\n  left: 0\n  opacity: 0\n  outline: none\n  position: absolute\n  top: 0\n  width: 100%\n\n.file-cta,\n.file-name\n  @extend %control\n  border-color: $file-border-color\n  border-radius: $file-radius\n  font-size: 1em\n  padding-left: 1em\n  padding-right: 1em\n  white-space: nowrap\n\n.file-cta\n  background-color: $file-cta-background-color\n  color: $file-cta-color\n\n.file-name\n  border-color: $file-name-border-color\n  border-style: $file-name-border-style\n  border-width: $file-name-border-width\n  display: block\n  max-width: $file-name-max-width\n  overflow: hidden\n  text-align: inherit\n  text-overflow: ellipsis\n\n.file-icon\n  align-items: center\n  display: flex\n  height: 1em\n  justify-content: center\n  +ltr-property(\"margin\", 0.5em)\n  width: 1em\n  .fa\n    font-size: 14px\n"
  },
  {
    "path": "guide/style/bulma/sass/form/input-textarea.sass",
    "content": "$textarea-padding: $control-padding-horizontal !default\n$textarea-max-height: 40em !default\n$textarea-min-height: 8em !default\n\n$textarea-colors: $form-colors !default\n\n%input-textarea\n  @extend %input\n  box-shadow: $input-shadow\n  max-width: 100%\n  width: 100%\n  &[readonly]\n    box-shadow: none\n  // Colors\n  @each $name, $pair in $textarea-colors\n    $color: nth($pair, 1)\n    &.is-#{$name}\n      border-color: $color\n      &:focus,\n      &.is-focused,\n      &:active,\n      &.is-active\n        box-shadow: $input-focus-box-shadow-size bulmaRgba($color, 0.25)\n  // Sizes\n  &.is-small\n    +control-small\n  &.is-medium\n    +control-medium\n  &.is-large\n    +control-large\n  // Modifiers\n  &.is-fullwidth\n    display: block\n    width: 100%\n  &.is-inline\n    display: inline\n    width: auto\n\n.input\n  @extend %input-textarea\n  &.is-rounded\n    border-radius: $radius-rounded\n    padding-left: calc(#{$control-padding-horizontal} + 0.375em)\n    padding-right: calc(#{$control-padding-horizontal} + 0.375em)\n  &.is-static\n    background-color: transparent\n    border-color: transparent\n    box-shadow: none\n    padding-left: 0\n    padding-right: 0\n\n.textarea\n  @extend %input-textarea\n  display: block\n  max-width: 100%\n  min-width: 100%\n  padding: $textarea-padding\n  resize: vertical\n  &:not([rows])\n    max-height: $textarea-max-height\n    min-height: $textarea-min-height\n  &[rows]\n    height: initial\n  // Modifiers\n  &.has-fixed-size\n    resize: none\n"
  },
  {
    "path": "guide/style/bulma/sass/form/select.sass",
    "content": "$select-colors: $form-colors !default\n\n.select\n  display: inline-block\n  max-width: 100%\n  position: relative\n  vertical-align: top\n  &:not(.is-multiple)\n    height: $input-height\n  &:not(.is-multiple):not(.is-loading)\n    &::after\n      @extend %arrow\n      border-color: $input-arrow\n      +ltr-position(1.125em)\n      z-index: 4\n  &.is-rounded\n    select\n      border-radius: $radius-rounded\n      +ltr-property(\"padding\", 1em, false)\n  select\n    @extend %input\n    cursor: pointer\n    display: block\n    font-size: 1em\n    max-width: 100%\n    outline: none\n    &::-ms-expand\n      display: none\n    &[disabled]:hover,\n    fieldset[disabled] &:hover\n      border-color: $input-disabled-border-color\n    &:not([multiple])\n      +ltr-property(\"padding\", 2.5em)\n    &[multiple]\n      height: auto\n      padding: 0\n      option\n        padding: 0.5em 1em\n  // States\n  &:not(.is-multiple):not(.is-loading):hover\n    &::after\n      border-color: $input-hover-color\n  // Colors\n  @each $name, $pair in $select-colors\n    $color: nth($pair, 1)\n    &.is-#{$name}\n      &:not(:hover)::after\n        border-color: $color\n      select\n        border-color: $color\n        &:hover,\n        &.is-hovered\n          border-color: bulmaDarken($color, 5%)\n        &:focus,\n        &.is-focused,\n        &:active,\n        &.is-active\n          box-shadow: $input-focus-box-shadow-size bulmaRgba($color, 0.25)\n  // Sizes\n  &.is-small\n    +control-small\n  &.is-medium\n    +control-medium\n  &.is-large\n    +control-large\n  // Modifiers\n  &.is-disabled\n    &::after\n      border-color: $input-disabled-color !important\n      opacity: 0.5\n  &.is-fullwidth\n    width: 100%\n    select\n      width: 100%\n  &.is-loading\n    &::after\n      @extend %loader\n      margin-top: 0\n      position: absolute\n      +ltr-position(0.625em)\n      top: 0.625em\n      transform: none\n    &.is-small:after\n      font-size: $size-small\n    &.is-medium:after\n      font-size: $size-medium\n    &.is-large:after\n      font-size: $size-large\n"
  },
  {
    "path": "guide/style/bulma/sass/form/shared.sass",
    "content": "@import \"../utilities/controls\"\n@import \"../utilities/mixins\"\n\n$form-colors: $colors !default\n\n$input-color: $text-strong !default\n$input-background-color: $scheme-main !default\n$input-border-color: $border !default\n$input-height: $control-height !default\n$input-shadow: inset 0 0.0625em 0.125em rgba($scheme-invert, 0.05) !default\n$input-placeholder-color: bulmaRgba($input-color, 0.3) !default\n\n$input-hover-color: $text-strong !default\n$input-hover-border-color: $border-hover !default\n\n$input-focus-color: $text-strong !default\n$input-focus-border-color: $link !default\n$input-focus-box-shadow-size: 0 0 0 0.125em !default\n$input-focus-box-shadow-color: bulmaRgba($link, 0.25) !default\n\n$input-disabled-color: $text-light !default\n$input-disabled-background-color: $background !default\n$input-disabled-border-color: $background !default\n$input-disabled-placeholder-color: bulmaRgba($input-disabled-color, 0.3) !default\n\n$input-arrow: $link !default\n\n$input-icon-color: $border !default\n$input-icon-active-color: $text !default\n\n$input-radius: $radius !default\n\n=input\n  @extend %control\n  background-color: $input-background-color\n  border-color: $input-border-color\n  border-radius: $input-radius\n  color: $input-color\n  +placeholder\n    color: $input-placeholder-color\n  &:hover,\n  &.is-hovered\n    border-color: $input-hover-border-color\n  &:focus,\n  &.is-focused,\n  &:active,\n  &.is-active\n    border-color: $input-focus-border-color\n    box-shadow: $input-focus-box-shadow-size $input-focus-box-shadow-color\n  &[disabled],\n  fieldset[disabled] &\n    background-color: $input-disabled-background-color\n    border-color: $input-disabled-border-color\n    box-shadow: none\n    color: $input-disabled-color\n    +placeholder\n      color: $input-disabled-placeholder-color\n\n%input\n  +input\n"
  },
  {
    "path": "guide/style/bulma/sass/form/tools.sass",
    "content": "$label-color: $text-strong !default\n$label-weight: $weight-bold !default\n\n$help-size: $size-small !default\n\n$label-colors: $form-colors !default\n\n.label\n  color: $label-color\n  display: block\n  font-size: $size-normal\n  font-weight: $label-weight\n  &:not(:last-child)\n    margin-bottom: 0.5em\n  // Sizes\n  &.is-small\n    font-size: $size-small\n  &.is-medium\n    font-size: $size-medium\n  &.is-large\n    font-size: $size-large\n\n.help\n  display: block\n  font-size: $help-size\n  margin-top: 0.25rem\n  @each $name, $pair in $label-colors\n    $color: nth($pair, 1)\n    &.is-#{$name}\n      color: $color\n\n// Containers\n\n.field\n  &:not(:last-child)\n    margin-bottom: 0.75rem\n  // Modifiers\n  &.has-addons\n    display: flex\n    justify-content: flex-start\n    .control\n      &:not(:last-child)\n        +ltr-property(\"margin\", -1px)\n      &:not(:first-child):not(:last-child)\n        .button,\n        .input,\n        .select select\n          border-radius: 0\n      &:first-child:not(:only-child)\n        .button,\n        .input,\n        .select select\n          +ltr\n            border-bottom-right-radius: 0\n            border-top-right-radius: 0\n          +rtl\n            border-bottom-left-radius: 0\n            border-top-left-radius: 0\n      &:last-child:not(:only-child)\n        .button,\n        .input,\n        .select select\n          +ltr\n            border-bottom-left-radius: 0\n            border-top-left-radius: 0\n          +rtl\n            border-bottom-right-radius: 0\n            border-top-right-radius: 0\n      .button,\n      .input,\n      .select select\n        &:not([disabled])\n          &:hover,\n          &.is-hovered\n            z-index: 2\n          &:focus,\n          &.is-focused,\n          &:active,\n          &.is-active\n            z-index: 3\n            &:hover\n              z-index: 4\n      &.is-expanded\n        flex-grow: 1\n        flex-shrink: 1\n    &.has-addons-centered\n      justify-content: center\n    &.has-addons-right\n      justify-content: flex-end\n    &.has-addons-fullwidth\n      .control\n        flex-grow: 1\n        flex-shrink: 0\n  &.is-grouped\n    display: flex\n    justify-content: flex-start\n    & > .control\n      flex-shrink: 0\n      &:not(:last-child)\n        margin-bottom: 0\n        +ltr-property(\"margin\", 0.75rem)\n      &.is-expanded\n        flex-grow: 1\n        flex-shrink: 1\n    &.is-grouped-centered\n      justify-content: center\n    &.is-grouped-right\n      justify-content: flex-end\n    &.is-grouped-multiline\n      flex-wrap: wrap\n      & > .control\n        &:last-child,\n        &:not(:last-child)\n          margin-bottom: 0.75rem\n      &:last-child\n        margin-bottom: -0.75rem\n      &:not(:last-child)\n        margin-bottom: 0\n  &.is-horizontal\n    +tablet\n      display: flex\n\n.field-label\n  .label\n    font-size: inherit\n  +mobile\n    margin-bottom: 0.5rem\n  +tablet\n    flex-basis: 0\n    flex-grow: 1\n    flex-shrink: 0\n    +ltr-property(\"margin\", 1.5rem)\n    text-align: right\n    &.is-small\n      font-size: $size-small\n      padding-top: 0.375em\n    &.is-normal\n      padding-top: 0.375em\n    &.is-medium\n      font-size: $size-medium\n      padding-top: 0.375em\n    &.is-large\n      font-size: $size-large\n      padding-top: 0.375em\n\n.field-body\n  .field .field\n    margin-bottom: 0\n  +tablet\n    display: flex\n    flex-basis: 0\n    flex-grow: 5\n    flex-shrink: 1\n    .field\n      margin-bottom: 0\n    & > .field\n      flex-shrink: 1\n      &:not(.is-narrow)\n        flex-grow: 1\n      &:not(:last-child)\n        +ltr-property(\"margin\", 0.75rem)\n\n.control\n  box-sizing: border-box\n  clear: both\n  font-size: $size-normal\n  position: relative\n  text-align: inherit\n  // Modifiers\n  &.has-icons-left,\n  &.has-icons-right\n    .input,\n    .select\n      &:focus\n        & ~ .icon\n          color: $input-icon-active-color\n      &.is-small ~ .icon\n        font-size: $size-small\n      &.is-medium ~ .icon\n        font-size: $size-medium\n      &.is-large ~ .icon\n        font-size: $size-large\n    .icon\n      color: $input-icon-color\n      height: $input-height\n      pointer-events: none\n      position: absolute\n      top: 0\n      width: $input-height\n      z-index: 4\n  &.has-icons-left\n    .input,\n    .select select\n      padding-left: $input-height\n    .icon.is-left\n      left: 0\n  &.has-icons-right\n    .input,\n    .select select\n      padding-right: $input-height\n    .icon.is-right\n      right: 0\n  &.is-loading\n    &::after\n      @extend %loader\n      position: absolute !important\n      +ltr-position(0.625em)\n      top: 0.625em\n      z-index: 4\n    &.is-small:after\n      font-size: $size-small\n    &.is-medium:after\n      font-size: $size-medium\n    &.is-large:after\n      font-size: $size-large\n"
  },
  {
    "path": "guide/style/bulma/sass/grid/_all.sass",
    "content": "/* Bulma Grid */\n@charset \"utf-8\"\n\n@import \"columns\"\n@import \"tiles\"\n"
  },
  {
    "path": "guide/style/bulma/sass/grid/columns.sass",
    "content": "@import \"../utilities/mixins\"\n\n$column-gap: 0.75rem !default\n\n.column\n  display: block\n  flex-basis: 0\n  flex-grow: 1\n  flex-shrink: 1\n  padding: $column-gap\n  .columns.is-mobile > &.is-narrow\n    flex: none\n    width: unset\n  .columns.is-mobile > &.is-full\n    flex: none\n    width: 100%\n  .columns.is-mobile > &.is-three-quarters\n    flex: none\n    width: 75%\n  .columns.is-mobile > &.is-two-thirds\n    flex: none\n    width: 66.6666%\n  .columns.is-mobile > &.is-half\n    flex: none\n    width: 50%\n  .columns.is-mobile > &.is-one-third\n    flex: none\n    width: 33.3333%\n  .columns.is-mobile > &.is-one-quarter\n    flex: none\n    width: 25%\n  .columns.is-mobile > &.is-one-fifth\n    flex: none\n    width: 20%\n  .columns.is-mobile > &.is-two-fifths\n    flex: none\n    width: 40%\n  .columns.is-mobile > &.is-three-fifths\n    flex: none\n    width: 60%\n  .columns.is-mobile > &.is-four-fifths\n    flex: none\n    width: 80%\n  .columns.is-mobile > &.is-offset-three-quarters\n    +ltr-property(\"margin\", 75%, false)\n  .columns.is-mobile > &.is-offset-two-thirds\n    +ltr-property(\"margin\", 66.6666%, false)\n  .columns.is-mobile > &.is-offset-half\n    +ltr-property(\"margin\", 50%, false)\n  .columns.is-mobile > &.is-offset-one-third\n    +ltr-property(\"margin\", 33.3333%, false)\n  .columns.is-mobile > &.is-offset-one-quarter\n    +ltr-property(\"margin\", 25%, false)\n  .columns.is-mobile > &.is-offset-one-fifth\n    +ltr-property(\"margin\", 20%, false)\n  .columns.is-mobile > &.is-offset-two-fifths\n    +ltr-property(\"margin\", 40%, false)\n  .columns.is-mobile > &.is-offset-three-fifths\n    +ltr-property(\"margin\", 60%, false)\n  .columns.is-mobile > &.is-offset-four-fifths\n    +ltr-property(\"margin\", 80%, false)\n  @for $i from 0 through 12\n    .columns.is-mobile > &.is-#{$i}\n      flex: none\n      width: percentage(divide($i, 12))\n    .columns.is-mobile > &.is-offset-#{$i}\n      +ltr-property(\"margin\", percentage(divide($i, 12)), false)\n  +mobile\n    &.is-narrow-mobile\n      flex: none\n      width: unset\n    &.is-full-mobile\n      flex: none\n      width: 100%\n    &.is-three-quarters-mobile\n      flex: none\n      width: 75%\n    &.is-two-thirds-mobile\n      flex: none\n      width: 66.6666%\n    &.is-half-mobile\n      flex: none\n      width: 50%\n    &.is-one-third-mobile\n      flex: none\n      width: 33.3333%\n    &.is-one-quarter-mobile\n      flex: none\n      width: 25%\n    &.is-one-fifth-mobile\n      flex: none\n      width: 20%\n    &.is-two-fifths-mobile\n      flex: none\n      width: 40%\n    &.is-three-fifths-mobile\n      flex: none\n      width: 60%\n    &.is-four-fifths-mobile\n      flex: none\n      width: 80%\n    &.is-offset-three-quarters-mobile\n      +ltr-property(\"margin\", 75%, false)\n    &.is-offset-two-thirds-mobile\n      +ltr-property(\"margin\", 66.6666%, false)\n    &.is-offset-half-mobile\n      +ltr-property(\"margin\", 50%, false)\n    &.is-offset-one-third-mobile\n      +ltr-property(\"margin\", 33.3333%, false)\n    &.is-offset-one-quarter-mobile\n      +ltr-property(\"margin\", 25%, false)\n    &.is-offset-one-fifth-mobile\n      +ltr-property(\"margin\", 20%, false)\n    &.is-offset-two-fifths-mobile\n      +ltr-property(\"margin\", 40%, false)\n    &.is-offset-three-fifths-mobile\n      +ltr-property(\"margin\", 60%, false)\n    &.is-offset-four-fifths-mobile\n      +ltr-property(\"margin\", 80%, false)\n    @for $i from 0 through 12\n      &.is-#{$i}-mobile\n        flex: none\n        width: percentage(divide($i, 12))\n      &.is-offset-#{$i}-mobile\n        +ltr-property(\"margin\", percentage(divide($i, 12)), false)\n  +tablet\n    &.is-narrow,\n    &.is-narrow-tablet\n      flex: none\n      width: unset\n    &.is-full,\n    &.is-full-tablet\n      flex: none\n      width: 100%\n    &.is-three-quarters,\n    &.is-three-quarters-tablet\n      flex: none\n      width: 75%\n    &.is-two-thirds,\n    &.is-two-thirds-tablet\n      flex: none\n      width: 66.6666%\n    &.is-half,\n    &.is-half-tablet\n      flex: none\n      width: 50%\n    &.is-one-third,\n    &.is-one-third-tablet\n      flex: none\n      width: 33.3333%\n    &.is-one-quarter,\n    &.is-one-quarter-tablet\n      flex: none\n      width: 25%\n    &.is-one-fifth,\n    &.is-one-fifth-tablet\n      flex: none\n      width: 20%\n    &.is-two-fifths,\n    &.is-two-fifths-tablet\n      flex: none\n      width: 40%\n    &.is-three-fifths,\n    &.is-three-fifths-tablet\n      flex: none\n      width: 60%\n    &.is-four-fifths,\n    &.is-four-fifths-tablet\n      flex: none\n      width: 80%\n    &.is-offset-three-quarters,\n    &.is-offset-three-quarters-tablet\n      +ltr-property(\"margin\", 75%, false)\n    &.is-offset-two-thirds,\n    &.is-offset-two-thirds-tablet\n      +ltr-property(\"margin\", 66.6666%, false)\n    &.is-offset-half,\n    &.is-offset-half-tablet\n      +ltr-property(\"margin\", 50%, false)\n    &.is-offset-one-third,\n    &.is-offset-one-third-tablet\n      +ltr-property(\"margin\", 33.3333%, false)\n    &.is-offset-one-quarter,\n    &.is-offset-one-quarter-tablet\n      +ltr-property(\"margin\", 25%, false)\n    &.is-offset-one-fifth,\n    &.is-offset-one-fifth-tablet\n      +ltr-property(\"margin\", 20%, false)\n    &.is-offset-two-fifths,\n    &.is-offset-two-fifths-tablet\n      +ltr-property(\"margin\", 40%, false)\n    &.is-offset-three-fifths,\n    &.is-offset-three-fifths-tablet\n      +ltr-property(\"margin\", 60%, false)\n    &.is-offset-four-fifths,\n    &.is-offset-four-fifths-tablet\n      +ltr-property(\"margin\", 80%, false)\n    @for $i from 0 through 12\n      &.is-#{$i},\n      &.is-#{$i}-tablet\n        flex: none\n        width: percentage(divide($i, 12))\n      &.is-offset-#{$i},\n      &.is-offset-#{$i}-tablet\n        +ltr-property(\"margin\", percentage(divide($i, 12)), false)\n  +touch\n    &.is-narrow-touch\n      flex: none\n      width: unset\n    &.is-full-touch\n      flex: none\n      width: 100%\n    &.is-three-quarters-touch\n      flex: none\n      width: 75%\n    &.is-two-thirds-touch\n      flex: none\n      width: 66.6666%\n    &.is-half-touch\n      flex: none\n      width: 50%\n    &.is-one-third-touch\n      flex: none\n      width: 33.3333%\n    &.is-one-quarter-touch\n      flex: none\n      width: 25%\n    &.is-one-fifth-touch\n      flex: none\n      width: 20%\n    &.is-two-fifths-touch\n      flex: none\n      width: 40%\n    &.is-three-fifths-touch\n      flex: none\n      width: 60%\n    &.is-four-fifths-touch\n      flex: none\n      width: 80%\n    &.is-offset-three-quarters-touch\n      +ltr-property(\"margin\", 75%, false)\n    &.is-offset-two-thirds-touch\n      +ltr-property(\"margin\", 66.6666%, false)\n    &.is-offset-half-touch\n      +ltr-property(\"margin\", 50%, false)\n    &.is-offset-one-third-touch\n      +ltr-property(\"margin\", 33.3333%, false)\n    &.is-offset-one-quarter-touch\n      +ltr-property(\"margin\", 25%, false)\n    &.is-offset-one-fifth-touch\n      +ltr-property(\"margin\", 20%, false)\n    &.is-offset-two-fifths-touch\n      +ltr-property(\"margin\", 40%, false)\n    &.is-offset-three-fifths-touch\n      +ltr-property(\"margin\", 60%, false)\n    &.is-offset-four-fifths-touch\n      +ltr-property(\"margin\", 80%, false)\n    @for $i from 0 through 12\n      &.is-#{$i}-touch\n        flex: none\n        width: percentage(divide($i, 12))\n      &.is-offset-#{$i}-touch\n        +ltr-property(\"margin\", percentage(divide($i, 12)), false)\n  +desktop\n    &.is-narrow-desktop\n      flex: none\n      width: unset\n    &.is-full-desktop\n      flex: none\n      width: 100%\n    &.is-three-quarters-desktop\n      flex: none\n      width: 75%\n    &.is-two-thirds-desktop\n      flex: none\n      width: 66.6666%\n    &.is-half-desktop\n      flex: none\n      width: 50%\n    &.is-one-third-desktop\n      flex: none\n      width: 33.3333%\n    &.is-one-quarter-desktop\n      flex: none\n      width: 25%\n    &.is-one-fifth-desktop\n      flex: none\n      width: 20%\n    &.is-two-fifths-desktop\n      flex: none\n      width: 40%\n    &.is-three-fifths-desktop\n      flex: none\n      width: 60%\n    &.is-four-fifths-desktop\n      flex: none\n      width: 80%\n    &.is-offset-three-quarters-desktop\n      +ltr-property(\"margin\", 75%, false)\n    &.is-offset-two-thirds-desktop\n      +ltr-property(\"margin\", 66.6666%, false)\n    &.is-offset-half-desktop\n      +ltr-property(\"margin\", 50%, false)\n    &.is-offset-one-third-desktop\n      +ltr-property(\"margin\", 33.3333%, false)\n    &.is-offset-one-quarter-desktop\n      +ltr-property(\"margin\", 25%, false)\n    &.is-offset-one-fifth-desktop\n      +ltr-property(\"margin\", 20%, false)\n    &.is-offset-two-fifths-desktop\n      +ltr-property(\"margin\", 40%, false)\n    &.is-offset-three-fifths-desktop\n      +ltr-property(\"margin\", 60%, false)\n    &.is-offset-four-fifths-desktop\n      +ltr-property(\"margin\", 80%, false)\n    @for $i from 0 through 12\n      &.is-#{$i}-desktop\n        flex: none\n        width: percentage(divide($i, 12))\n      &.is-offset-#{$i}-desktop\n        +ltr-property(\"margin\", percentage(divide($i, 12)), false)\n  +widescreen\n    &.is-narrow-widescreen\n      flex: none\n      width: unset\n    &.is-full-widescreen\n      flex: none\n      width: 100%\n    &.is-three-quarters-widescreen\n      flex: none\n      width: 75%\n    &.is-two-thirds-widescreen\n      flex: none\n      width: 66.6666%\n    &.is-half-widescreen\n      flex: none\n      width: 50%\n    &.is-one-third-widescreen\n      flex: none\n      width: 33.3333%\n    &.is-one-quarter-widescreen\n      flex: none\n      width: 25%\n    &.is-one-fifth-widescreen\n      flex: none\n      width: 20%\n    &.is-two-fifths-widescreen\n      flex: none\n      width: 40%\n    &.is-three-fifths-widescreen\n      flex: none\n      width: 60%\n    &.is-four-fifths-widescreen\n      flex: none\n      width: 80%\n    &.is-offset-three-quarters-widescreen\n      +ltr-property(\"margin\", 75%, false)\n    &.is-offset-two-thirds-widescreen\n      +ltr-property(\"margin\", 66.6666%, false)\n    &.is-offset-half-widescreen\n      +ltr-property(\"margin\", 50%, false)\n    &.is-offset-one-third-widescreen\n      +ltr-property(\"margin\", 33.3333%, false)\n    &.is-offset-one-quarter-widescreen\n      +ltr-property(\"margin\", 25%, false)\n    &.is-offset-one-fifth-widescreen\n      +ltr-property(\"margin\", 20%, false)\n    &.is-offset-two-fifths-widescreen\n      +ltr-property(\"margin\", 40%, false)\n    &.is-offset-three-fifths-widescreen\n      +ltr-property(\"margin\", 60%, false)\n    &.is-offset-four-fifths-widescreen\n      +ltr-property(\"margin\", 80%, false)\n    @for $i from 0 through 12\n      &.is-#{$i}-widescreen\n        flex: none\n        width: percentage(divide($i, 12))\n      &.is-offset-#{$i}-widescreen\n        +ltr-property(\"margin\", percentage(divide($i, 12)), false)\n  +fullhd\n    &.is-narrow-fullhd\n      flex: none\n      width: unset\n    &.is-full-fullhd\n      flex: none\n      width: 100%\n    &.is-three-quarters-fullhd\n      flex: none\n      width: 75%\n    &.is-two-thirds-fullhd\n      flex: none\n      width: 66.6666%\n    &.is-half-fullhd\n      flex: none\n      width: 50%\n    &.is-one-third-fullhd\n      flex: none\n      width: 33.3333%\n    &.is-one-quarter-fullhd\n      flex: none\n      width: 25%\n    &.is-one-fifth-fullhd\n      flex: none\n      width: 20%\n    &.is-two-fifths-fullhd\n      flex: none\n      width: 40%\n    &.is-three-fifths-fullhd\n      flex: none\n      width: 60%\n    &.is-four-fifths-fullhd\n      flex: none\n      width: 80%\n    &.is-offset-three-quarters-fullhd\n      +ltr-property(\"margin\", 75%, false)\n    &.is-offset-two-thirds-fullhd\n      +ltr-property(\"margin\", 66.6666%, false)\n    &.is-offset-half-fullhd\n      +ltr-property(\"margin\", 50%, false)\n    &.is-offset-one-third-fullhd\n      +ltr-property(\"margin\", 33.3333%, false)\n    &.is-offset-one-quarter-fullhd\n      +ltr-property(\"margin\", 25%, false)\n    &.is-offset-one-fifth-fullhd\n      +ltr-property(\"margin\", 20%, false)\n    &.is-offset-two-fifths-fullhd\n      +ltr-property(\"margin\", 40%, false)\n    &.is-offset-three-fifths-fullhd\n      +ltr-property(\"margin\", 60%, false)\n    &.is-offset-four-fifths-fullhd\n      +ltr-property(\"margin\", 80%, false)\n    @for $i from 0 through 12\n      &.is-#{$i}-fullhd\n        flex: none\n        width: percentage(divide($i, 12))\n      &.is-offset-#{$i}-fullhd\n        +ltr-property(\"margin\", percentage(divide($i, 12)), false)\n\n.columns\n  +ltr-property(\"margin\", (-$column-gap), false)\n  +ltr-property(\"margin\", (-$column-gap))\n  margin-top: (-$column-gap)\n  &:last-child\n    margin-bottom: (-$column-gap)\n  &:not(:last-child)\n    margin-bottom: calc(1.5rem - #{$column-gap})\n  // Modifiers\n  &.is-centered\n    justify-content: center\n  &.is-gapless\n    +ltr-property(\"margin\", 0, false)\n    +ltr-property(\"margin\", 0)\n    margin-top: 0\n    & > .column\n      margin: 0\n      padding: 0 !important\n    &:not(:last-child)\n      margin-bottom: 1.5rem\n    &:last-child\n      margin-bottom: 0\n  &.is-mobile\n    display: flex\n  &.is-multiline\n    flex-wrap: wrap\n  &.is-vcentered\n    align-items: center\n  // Responsiveness\n  +tablet\n    &:not(.is-desktop)\n      display: flex\n  +desktop\n    // Modifiers\n    &.is-desktop\n      display: flex\n\n@if $variable-columns\n  .columns.is-variable\n    --columnGap: 0.75rem\n    +ltr-property(\"margin\", calc(-1 * var(--columnGap)), false)\n    +ltr-property(\"margin\", calc(-1 * var(--columnGap)))\n    > .column\n      padding-left: var(--columnGap)\n      padding-right: var(--columnGap)\n    @for $i from 0 through 8\n      &.is-#{$i}\n        --columnGap: #{$i * 0.25rem}\n      +mobile\n        &.is-#{$i}-mobile\n          --columnGap: #{$i * 0.25rem}\n      +tablet\n        &.is-#{$i}-tablet\n          --columnGap: #{$i * 0.25rem}\n      +tablet-only\n        &.is-#{$i}-tablet-only\n          --columnGap: #{$i * 0.25rem}\n      +touch\n        &.is-#{$i}-touch\n          --columnGap: #{$i * 0.25rem}\n      +desktop\n        &.is-#{$i}-desktop\n          --columnGap: #{$i * 0.25rem}\n      +desktop-only\n        &.is-#{$i}-desktop-only\n          --columnGap: #{$i * 0.25rem}\n      +widescreen\n        &.is-#{$i}-widescreen\n          --columnGap: #{$i * 0.25rem}\n      +widescreen-only\n        &.is-#{$i}-widescreen-only\n          --columnGap: #{$i * 0.25rem}\n      +fullhd\n        &.is-#{$i}-fullhd\n          --columnGap: #{$i * 0.25rem}\n"
  },
  {
    "path": "guide/style/bulma/sass/grid/tiles.sass",
    "content": "@import \"../utilities/mixins\"\n\n$tile-spacing: 0.75rem !default\n\n.tile\n  align-items: stretch\n  display: block\n  flex-basis: 0\n  flex-grow: 1\n  flex-shrink: 1\n  min-height: min-content\n  // Modifiers\n  &.is-ancestor\n    margin-left: $tile-spacing * -1\n    margin-right: $tile-spacing * -1\n    margin-top: $tile-spacing * -1\n    &:last-child\n      margin-bottom: $tile-spacing * -1\n    &:not(:last-child)\n      margin-bottom: $tile-spacing\n  &.is-child\n    margin: 0 !important\n  &.is-parent\n    padding: $tile-spacing\n  &.is-vertical\n    flex-direction: column\n    & > .tile.is-child:not(:last-child)\n      margin-bottom: 1.5rem !important\n  // Responsiveness\n  +tablet\n    &:not(.is-child)\n      display: flex\n    @for $i from 1 through 12\n      &.is-#{$i}\n        flex: none\n        width: (divide($i, 12)) * 100%\n"
  },
  {
    "path": "guide/style/bulma/sass/helpers/_all.sass",
    "content": "/* Bulma Helpers */\n@charset \"utf-8\"\n\n@import \"color\"\n@import \"flexbox\"\n@import \"float\"\n@import \"other\"\n@import \"overflow\"\n@import \"position\"\n@import \"spacing\"\n@import \"typography\"\n@import \"visibility\"\n"
  },
  {
    "path": "guide/style/bulma/sass/helpers/color.sass",
    "content": "@import \"../utilities/derived-variables\"\n\n@each $name, $pair in $colors\n  $color: nth($pair, 1)\n  .has-text-#{$name}\n    color: $color !important\n  a.has-text-#{$name}\n    &:hover,\n    &:focus\n      color: bulmaDarken($color, 10%) !important\n  .has-background-#{$name}\n    background-color: $color !important\n  @if length($pair) >= 4\n    $color-light: nth($pair, 3)\n    $color-dark: nth($pair, 4)\n    // Light\n    .has-text-#{$name}-light\n      color: $color-light !important\n    a.has-text-#{$name}-light\n      &:hover,\n      &:focus\n        color: bulmaDarken($color-light, 10%) !important\n    .has-background-#{$name}-light\n      background-color: $color-light !important\n    // Dark\n    .has-text-#{$name}-dark\n      color: $color-dark !important\n    a.has-text-#{$name}-dark\n      &:hover,\n      &:focus\n        color: bulmaLighten($color-dark, 10%) !important\n    .has-background-#{$name}-dark\n      background-color: $color-dark !important\n\n@each $name, $shade in $shades\n  .has-text-#{$name}\n    color: $shade !important\n  .has-background-#{$name}\n    background-color: $shade !important\n"
  },
  {
    "path": "guide/style/bulma/sass/helpers/flexbox.sass",
    "content": "$flex-direction-values: row, row-reverse, column, column-reverse\n@each $value in $flex-direction-values\n  .is-flex-direction-#{$value}\n    flex-direction: $value !important\n\n$flex-wrap-values: nowrap, wrap, wrap-reverse\n@each $value in $flex-wrap-values\n  .is-flex-wrap-#{$value}\n    flex-wrap: $value !important\n\n$justify-content-values: flex-start, flex-end, center, space-between, space-around, space-evenly, start, end, left, right\n@each $value in $justify-content-values\n  .is-justify-content-#{$value}\n    justify-content: $value !important\n\n$align-content-values: flex-start, flex-end, center, space-between, space-around, space-evenly, stretch, start, end, baseline\n@each $value in $align-content-values\n  .is-align-content-#{$value}\n    align-content: $value !important\n\n$align-items-values: stretch, flex-start, flex-end, center, baseline, start, end, self-start, self-end\n@each $value in $align-items-values\n  .is-align-items-#{$value}\n    align-items: $value !important\n\n$align-self-values: auto, flex-start, flex-end, center, baseline, stretch\n@each $value in $align-self-values\n  .is-align-self-#{$value}\n    align-self: $value !important\n\n$flex-operators: grow, shrink\n@each $operator in $flex-operators\n  @for $i from 0 through 5\n    .is-flex-#{$operator}-#{$i}\n      flex-#{$operator}: $i !important\n"
  },
  {
    "path": "guide/style/bulma/sass/helpers/float.sass",
    "content": "@import \"../utilities/mixins\"\n\n.is-clearfix\n  +clearfix\n\n.is-pulled-left\n  float: left !important\n\n.is-pulled-right\n  float: right !important\n"
  },
  {
    "path": "guide/style/bulma/sass/helpers/other.sass",
    "content": "@import \"../utilities/mixins\"\n\n.is-radiusless\n  border-radius: 0 !important\n\n.is-shadowless\n  box-shadow: none !important\n\n.is-clickable\n  cursor: pointer !important\n  pointer-events: all !important\n\n.is-unselectable\n  @extend %unselectable\n"
  },
  {
    "path": "guide/style/bulma/sass/helpers/overflow.sass",
    "content": ".is-clipped\n  overflow: hidden !important\n"
  },
  {
    "path": "guide/style/bulma/sass/helpers/position.sass",
    "content": "@import \"../utilities/mixins\"\n\n.is-overlay\n  @extend %overlay\n\n.is-relative\n  position: relative !important\n"
  },
  {
    "path": "guide/style/bulma/sass/helpers/spacing.sass",
    "content": ".is-marginless\n  margin: 0 !important\n\n.is-paddingless\n  padding: 0 !important\n\n$spacing-shortcuts: (\"margin\": \"m\", \"padding\": \"p\") !default\n$spacing-directions: (\"top\": \"t\", \"right\": \"r\", \"bottom\": \"b\", \"left\": \"l\") !default\n$spacing-horizontal: \"x\" !default\n$spacing-vertical: \"y\" !default\n$spacing-values: (\"0\": 0, \"1\": 0.25rem, \"2\": 0.5rem, \"3\": 0.75rem, \"4\": 1rem, \"5\": 1.5rem, \"6\": 3rem, \"auto\": auto) !default\n\n@each $property, $shortcut in $spacing-shortcuts\n  @each $name, $value in $spacing-values\n    // All directions\n    .#{$shortcut}-#{$name}\n      #{$property}: $value !important\n    // Cardinal directions\n    @each $direction, $suffix in $spacing-directions\n      .#{$shortcut}#{$suffix}-#{$name}\n        #{$property}-#{$direction}: $value !important\n    // Horizontal axis\n    @if $spacing-horizontal != null\n      .#{$shortcut}#{$spacing-horizontal}-#{$name}\n        #{$property}-left: $value !important\n        #{$property}-right: $value !important\n    // Vertical axis\n    @if $spacing-vertical != null\n      .#{$shortcut}#{$spacing-vertical}-#{$name}\n        #{$property}-top: $value !important\n        #{$property}-bottom: $value !important\n"
  },
  {
    "path": "guide/style/bulma/sass/helpers/typography.sass",
    "content": "@import \"../utilities/mixins\"\n\n=typography-size($target:'')\n  @each $size in $sizes\n    $i: index($sizes, $size)\n    .is-size-#{$i}#{if($target == '', '', '-' + $target)}\n      font-size: $size !important\n\n+typography-size()\n\n+mobile\n  +typography-size('mobile')\n\n+tablet\n  +typography-size('tablet')\n\n+touch\n  +typography-size('touch')\n\n+desktop\n  +typography-size('desktop')\n\n+widescreen\n  +typography-size('widescreen')\n\n+fullhd\n  +typography-size('fullhd')\n\n$alignments: ('centered': 'center', 'justified': 'justify', 'left': 'left', 'right': 'right')\n\n@each $alignment, $text-align in $alignments\n  .has-text-#{$alignment}\n    text-align: #{$text-align} !important\n\n@each $alignment, $text-align in $alignments\n  +mobile\n    .has-text-#{$alignment}-mobile\n      text-align: #{$text-align} !important\n  +tablet\n    .has-text-#{$alignment}-tablet\n      text-align: #{$text-align} !important\n  +tablet-only\n    .has-text-#{$alignment}-tablet-only\n      text-align: #{$text-align} !important\n  +touch\n    .has-text-#{$alignment}-touch\n      text-align: #{$text-align} !important\n  +desktop\n    .has-text-#{$alignment}-desktop\n      text-align: #{$text-align} !important\n  +desktop-only\n    .has-text-#{$alignment}-desktop-only\n      text-align: #{$text-align} !important\n  +widescreen\n    .has-text-#{$alignment}-widescreen\n      text-align: #{$text-align} !important\n  +widescreen-only\n    .has-text-#{$alignment}-widescreen-only\n      text-align: #{$text-align} !important\n  +fullhd\n    .has-text-#{$alignment}-fullhd\n      text-align: #{$text-align} !important\n\n.is-capitalized\n  text-transform: capitalize !important\n\n.is-lowercase\n  text-transform: lowercase !important\n\n.is-uppercase\n  text-transform: uppercase !important\n\n.is-italic\n  font-style: italic !important\n  \n.is-underlined\n  text-decoration: underline !important\n\n.has-text-weight-light\n  font-weight: $weight-light !important\n.has-text-weight-normal\n  font-weight: $weight-normal !important\n.has-text-weight-medium\n  font-weight: $weight-medium !important\n.has-text-weight-semibold\n  font-weight: $weight-semibold !important\n.has-text-weight-bold\n  font-weight: $weight-bold !important\n\n.is-family-primary\n  font-family: $family-primary !important\n\n.is-family-secondary\n  font-family: $family-secondary !important\n\n.is-family-sans-serif\n  font-family: $family-sans-serif !important\n\n.is-family-monospace\n  font-family: $family-monospace !important\n\n.is-family-code\n  font-family: $family-code !important\n"
  },
  {
    "path": "guide/style/bulma/sass/helpers/visibility.sass",
    "content": "@import \"../utilities/mixins\"\n\n$displays: 'block' 'flex' 'inline' 'inline-block' 'inline-flex'\n\n@each $display in $displays\n  .is-#{$display}\n    display: #{$display} !important\n  +mobile\n    .is-#{$display}-mobile\n      display: #{$display} !important\n  +tablet\n    .is-#{$display}-tablet\n      display: #{$display} !important\n  +tablet-only\n    .is-#{$display}-tablet-only\n      display: #{$display} !important\n  +touch\n    .is-#{$display}-touch\n      display: #{$display} !important\n  +desktop\n    .is-#{$display}-desktop\n      display: #{$display} !important\n  +desktop-only\n    .is-#{$display}-desktop-only\n      display: #{$display} !important\n  +widescreen\n    .is-#{$display}-widescreen\n      display: #{$display} !important\n  +widescreen-only\n    .is-#{$display}-widescreen-only\n      display: #{$display} !important\n  +fullhd\n    .is-#{$display}-fullhd\n      display: #{$display} !important\n\n.is-hidden\n  display: none !important\n\n.is-sr-only\n  border: none !important\n  clip: rect(0, 0, 0, 0) !important\n  height: 0.01em !important\n  overflow: hidden !important\n  padding: 0 !important\n  position: absolute !important\n  white-space: nowrap !important\n  width: 0.01em !important\n\n+mobile\n  .is-hidden-mobile\n    display: none !important\n\n+tablet\n  .is-hidden-tablet\n    display: none !important\n\n+tablet-only\n  .is-hidden-tablet-only\n    display: none !important\n\n+touch\n  .is-hidden-touch\n    display: none !important\n\n+desktop\n  .is-hidden-desktop\n    display: none !important\n\n+desktop-only\n  .is-hidden-desktop-only\n    display: none !important\n\n+widescreen\n  .is-hidden-widescreen\n    display: none !important\n\n+widescreen-only\n  .is-hidden-widescreen-only\n    display: none !important\n\n+fullhd\n  .is-hidden-fullhd\n    display: none !important\n\n.is-invisible\n  visibility: hidden !important\n\n+mobile\n  .is-invisible-mobile\n    visibility: hidden !important\n\n+tablet\n  .is-invisible-tablet\n    visibility: hidden !important\n\n+tablet-only\n  .is-invisible-tablet-only\n    visibility: hidden !important\n\n+touch\n  .is-invisible-touch\n    visibility: hidden !important\n\n+desktop\n  .is-invisible-desktop\n    visibility: hidden !important\n\n+desktop-only\n  .is-invisible-desktop-only\n    visibility: hidden !important\n\n+widescreen\n  .is-invisible-widescreen\n    visibility: hidden !important\n\n+widescreen-only\n  .is-invisible-widescreen-only\n    visibility: hidden !important\n\n+fullhd\n  .is-invisible-fullhd\n    visibility: hidden !important\n"
  },
  {
    "path": "guide/style/bulma/sass/layout/_all.sass",
    "content": "/* Bulma Layout */\n@charset \"utf-8\"\n\n@import \"hero\"\n@import \"section\"\n@import \"footer\"\n"
  },
  {
    "path": "guide/style/bulma/sass/layout/footer.sass",
    "content": "@import \"../utilities/derived-variables\"\n\n$footer-background-color: $scheme-main-bis !default\n$footer-color: false !default\n$footer-padding: 3rem 1.5rem 6rem !default\n\n.footer\n  background-color: $footer-background-color\n  padding: $footer-padding\n  @if $footer-color\n    color: $footer-color\n"
  },
  {
    "path": "guide/style/bulma/sass/layout/hero.sass",
    "content": "@import \"../utilities/mixins\"\n\n$hero-body-padding: 3rem 1.5rem !default\n$hero-body-padding-tablet: 3rem 3rem !default\n$hero-body-padding-small: 1.5rem !default\n$hero-body-padding-medium: 9rem 4.5rem !default\n$hero-body-padding-large: 18rem 6rem !default\n\n$hero-colors: $colors !default\n\n// Main container\n.hero\n  align-items: stretch\n  display: flex\n  flex-direction: column\n  justify-content: space-between\n  .navbar\n    background: none\n  .tabs\n    ul\n      border-bottom: none\n  // Colors\n  @each $name, $pair in $hero-colors\n    $color: nth($pair, 1)\n    $color-invert: nth($pair, 2)\n    &.is-#{$name}\n      background-color: $color\n      color: $color-invert\n      a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),\n      strong\n        color: inherit\n      .title\n        color: $color-invert\n      .subtitle\n        color: bulmaRgba($color-invert, 0.9)\n        a:not(.button),\n        strong\n          color: $color-invert\n      .navbar-menu\n        +touch\n          background-color: $color\n      .navbar-item,\n      .navbar-link\n        color: bulmaRgba($color-invert, 0.7)\n      a.navbar-item,\n      .navbar-link\n        &:hover,\n        &.is-active\n          background-color: bulmaDarken($color, 5%)\n          color: $color-invert\n      .tabs\n        a\n          color: $color-invert\n          opacity: 0.9\n          &:hover\n            opacity: 1\n        li\n          &.is-active a\n            color: $color !important\n            opacity: 1\n        &.is-boxed,\n        &.is-toggle\n          a\n            color: $color-invert\n            &:hover\n              background-color: bulmaRgba($scheme-invert, 0.1)\n          li.is-active a\n            &,\n            &:hover\n              background-color: $color-invert\n              border-color: $color-invert\n              color: $color\n      // Modifiers\n      @if type-of($color) == 'color'\n        &.is-bold\n          $gradient-top-left: darken(saturate(adjust-hue($color, -10deg), 10%), 10%)\n          $gradient-bottom-right: lighten(saturate(adjust-hue($color, 10deg), 5%), 5%)\n          background-image: linear-gradient(141deg, $gradient-top-left 0%, $color 71%, $gradient-bottom-right 100%)\n          +mobile\n            .navbar-menu\n              background-image: linear-gradient(141deg, $gradient-top-left 0%, $color 71%, $gradient-bottom-right 100%)\n  // Sizes\n  &.is-small\n    .hero-body\n      padding: $hero-body-padding-small\n  &.is-medium\n    +tablet\n      .hero-body\n        padding: $hero-body-padding-medium\n  &.is-large\n    +tablet\n      .hero-body\n        padding: $hero-body-padding-large\n  &.is-halfheight,\n  &.is-fullheight,\n  &.is-fullheight-with-navbar\n    .hero-body\n      align-items: center\n      display: flex\n      & > .container\n        flex-grow: 1\n        flex-shrink: 1\n  &.is-halfheight\n    min-height: 50vh\n  &.is-fullheight\n    min-height: 100vh\n\n// Components\n\n.hero-video\n  @extend %overlay\n  overflow: hidden\n  video\n    left: 50%\n    min-height: 100%\n    min-width: 100%\n    position: absolute\n    top: 50%\n    transform: translate3d(-50%, -50%, 0)\n  // Modifiers\n  &.is-transparent\n    opacity: 0.3\n  // Responsiveness\n  +mobile\n    display: none\n\n.hero-buttons\n  margin-top: 1.5rem\n  // Responsiveness\n  +mobile\n    .button\n      display: flex\n      &:not(:last-child)\n        margin-bottom: 0.75rem\n  +tablet\n    display: flex\n    justify-content: center\n    .button:not(:last-child)\n      +ltr-property(\"margin\", 1.5rem)\n\n// Containers\n\n.hero-head,\n.hero-foot\n  flex-grow: 0\n  flex-shrink: 0\n\n.hero-body\n  flex-grow: 1\n  flex-shrink: 0\n  padding: $hero-body-padding\n  +tablet\n    padding: $hero-body-padding-tablet\n"
  },
  {
    "path": "guide/style/bulma/sass/layout/section.sass",
    "content": "@import \"../utilities/mixins\"\n\n$section-padding: 3rem 1.5rem !default\n$section-padding-desktop: 3rem 3rem !default\n$section-padding-medium: 9rem 4.5rem !default\n$section-padding-large: 18rem 6rem !default\n\n.section\n  padding: $section-padding\n  // Responsiveness\n  +desktop\n    padding: $section-padding-desktop\n    // Sizes\n    &.is-medium\n      padding: $section-padding-medium\n    &.is-large\n      padding: $section-padding-large\n"
  },
  {
    "path": "guide/style/bulma/sass/utilities/_all.sass",
    "content": "/* Bulma Utilities */\n@charset \"utf-8\"\n\n@import \"initial-variables\"\n@import \"functions\"\n@import \"derived-variables\"\n@import \"mixins\"\n@import \"controls\"\n@import \"extends\"\n"
  },
  {
    "path": "guide/style/bulma/sass/utilities/animations.sass",
    "content": "@warn \"The animations.sass file has MOVED. It is now in the /base folder. Please import sass/base/animations instead.\"\n"
  },
  {
    "path": "guide/style/bulma/sass/utilities/controls.sass",
    "content": "@import \"derived-variables\"\n\n$control-radius: $radius !default\n$control-radius-small: $radius-small !default\n\n$control-border-width: 1px !default\n\n$control-height: 2.5em !default\n$control-line-height: 1.5 !default\n\n$control-padding-vertical: calc(0.5em - #{$control-border-width}) !default\n$control-padding-horizontal: calc(0.75em - #{$control-border-width}) !default\n\n=control\n  -moz-appearance: none\n  -webkit-appearance: none\n  align-items: center\n  border: $control-border-width solid transparent\n  border-radius: $control-radius\n  box-shadow: none\n  display: inline-flex\n  font-size: $size-normal\n  height: $control-height\n  justify-content: flex-start\n  line-height: $control-line-height\n  padding-bottom: $control-padding-vertical\n  padding-left: $control-padding-horizontal\n  padding-right: $control-padding-horizontal\n  padding-top: $control-padding-vertical\n  position: relative\n  vertical-align: top\n  // States\n  &:focus,\n  &.is-focused,\n  &:active,\n  &.is-active\n    outline: none\n  &[disabled],\n  fieldset[disabled] &\n    cursor: not-allowed\n\n// The controls sizes use mixins so they can be used at different breakpoints\n=control-small\n  border-radius: $control-radius-small\n  font-size: $size-small\n=control-medium\n  font-size: $size-medium\n=control-large\n  font-size: $size-large\n"
  },
  {
    "path": "guide/style/bulma/sass/utilities/derived-variables.sass",
    "content": "@import \"initial-variables\"\n@import \"functions\"\n\n$primary: $turquoise !default\n\n$info: $cyan !default\n$success: $green !default\n$warning: $yellow !default\n$danger: $red !default\n\n$light: $white-ter !default\n$dark: $grey-darker !default\n\n// Invert colors\n\n$orange-invert: findColorInvert($orange) !default\n$yellow-invert: findColorInvert($yellow) !default\n$green-invert: findColorInvert($green) !default\n$turquoise-invert: findColorInvert($turquoise) !default\n$cyan-invert: findColorInvert($cyan) !default\n$blue-invert: findColorInvert($blue) !default\n$purple-invert: findColorInvert($purple) !default\n$red-invert: findColorInvert($red) !default\n\n$primary-invert: findColorInvert($primary) !default\n$primary-light: findLightColor($primary) !default\n$primary-dark: findDarkColor($primary) !default\n$info-invert: findColorInvert($info) !default\n$info-light: findLightColor($info) !default\n$info-dark: findDarkColor($info) !default\n$success-invert: findColorInvert($success) !default\n$success-light: findLightColor($success) !default\n$success-dark: findDarkColor($success) !default\n$warning-invert: findColorInvert($warning) !default\n$warning-light: findLightColor($warning) !default\n$warning-dark: findDarkColor($warning) !default\n$danger-invert: findColorInvert($danger) !default\n$danger-light: findLightColor($danger) !default\n$danger-dark: findDarkColor($danger) !default\n$light-invert: findColorInvert($light) !default\n$dark-invert: findColorInvert($dark) !default\n\n// General colors\n\n$scheme-main: $white !default\n$scheme-main-bis: $white-bis !default\n$scheme-main-ter: $white-ter !default\n$scheme-invert: $black !default\n$scheme-invert-bis: $black-bis !default\n$scheme-invert-ter: $black-ter !default\n\n$background: $white-ter !default\n\n$border: $grey-lighter !default\n$border-hover: $grey-light !default\n$border-light: $grey-lightest !default\n$border-light-hover: $grey-light !default\n\n// Text colors\n\n$text: $grey-dark !default\n$text-invert: findColorInvert($text) !default\n$text-light: $grey !default\n$text-strong: $grey-darker !default\n\n// Code colors\n\n$code: darken($red, 15%) !default\n$code-background: $background !default\n\n$pre: $text !default\n$pre-background: $background !default\n\n// Link colors\n\n$link: $blue !default\n$link-invert: findColorInvert($link) !default\n$link-light: findLightColor($link) !default\n$link-dark: findDarkColor($link) !default\n$link-visited: $purple !default\n\n$link-hover: $grey-darker !default\n$link-hover-border: $grey-light !default\n\n$link-focus: $grey-darker !default\n$link-focus-border: $blue !default\n\n$link-active: $grey-darker !default\n$link-active-border: $grey-dark !default\n\n// Typography\n\n$family-primary: $family-sans-serif !default\n$family-secondary: $family-sans-serif !default\n$family-code: $family-monospace !default\n\n$size-small: $size-7 !default\n$size-normal: $size-6 !default\n$size-medium: $size-5 !default\n$size-large: $size-4 !default\n\n// Effects\n\n$shadow: 0 0.5em 1em -0.125em rgba($scheme-invert, 0.1), 0 0px 0 1px rgba($scheme-invert, 0.02) !default\n\n// Lists and maps\n$custom-colors: null !default\n$custom-shades: null !default\n\n$colors: mergeColorMaps((\"white\": ($white, $black), \"black\": ($black, $white), \"light\": ($light, $light-invert), \"dark\": ($dark, $dark-invert), \"primary\": ($primary, $primary-invert, $primary-light, $primary-dark), \"link\": ($link, $link-invert, $link-light, $link-dark), \"info\": ($info, $info-invert, $info-light, $info-dark), \"success\": ($success, $success-invert, $success-light, $success-dark), \"warning\": ($warning, $warning-invert, $warning-light, $warning-dark), \"danger\": ($danger, $danger-invert, $danger-light, $danger-dark)), $custom-colors) !default\n\n$shades: mergeColorMaps((\"black-bis\": $black-bis, \"black-ter\": $black-ter, \"grey-darker\": $grey-darker, \"grey-dark\": $grey-dark, \"grey\": $grey, \"grey-light\": $grey-light, \"grey-lighter\": $grey-lighter, \"white-ter\": $white-ter, \"white-bis\": $white-bis), $custom-shades) !default\n\n$sizes: $size-1 $size-2 $size-3 $size-4 $size-5 $size-6 $size-7 !default\n"
  },
  {
    "path": "guide/style/bulma/sass/utilities/extends.sass",
    "content": "@import \"mixins\"\n\n%control\n  +control\n\n%unselectable\n  +unselectable\n\n%arrow\n  +arrow\n\n%block\n  +block\n\n%delete\n  +delete\n\n%loader\n  +loader\n\n%overlay\n  +overlay\n\n%reset\n  +reset\n"
  },
  {
    "path": "guide/style/bulma/sass/utilities/functions.sass",
    "content": "@function mergeColorMaps($bulma-colors, $custom-colors)\n  // We return at least Bulma's hard-coded colors\n  $merged-colors: $bulma-colors\n\n  // We want a map as input\n  @if type-of($custom-colors) == 'map'\n    @each $name, $components in $custom-colors\n      // The color name should be a string\n      // and the components either a single color\n      // or a colors list with at least one element\n      @if type-of($name) == 'string' and (type-of($components) == 'list' or type-of($components) == 'color') and length($components) >= 1\n        $color-base: null\n        $color-invert: null\n        $color-light: null\n        $color-dark: null\n        $value: null\n\n        // The param can either be a single color\n        // or a list of 2 colors\n        @if type-of($components) == 'color'\n          $color-base: $components\n          $color-invert: findColorInvert($color-base)\n          $color-light: findLightColor($color-base)\n          $color-dark: findDarkColor($color-base)\n        @else if type-of($components) == 'list'\n          $color-base: nth($components, 1)\n          // If Invert, Light and Dark are provided\n          @if length($components) > 3\n            $color-invert: nth($components, 2)\n            $color-light: nth($components, 3)\n            $color-dark: nth($components, 4)\n            // If only Invert and Light are provided\n          @else if length($components) > 2\n            $color-invert: nth($components, 2)\n            $color-light: nth($components, 3)\n            $color-dark: findDarkColor($color-base)\n            // If only Invert is provided\n          @else\n            $color-invert: nth($components, 2)\n            $color-light: findLightColor($color-base)\n            $color-dark: findDarkColor($color-base)\n\n        $value: ($color-base, $color-invert, $color-light, $color-dark)\n\n        // We only want to merge the map if the color base is an actual color\n        @if type-of($color-base) == 'color'\n          // We merge this colors elements as map with Bulma's colors map\n          // (we can override them this way, no multiple definition for the same name)\n          // $merged-colors: map_merge($merged-colors, ($name: ($color-base, $color-invert, $color-light, $color-dark)))\n          $merged-colors: map_merge($merged-colors, ($name: $value))\n\n  @return $merged-colors\n\n@function powerNumber($number, $exp)\n  $value: 1\n  @if $exp > 0\n    @for $i from 1 through $exp\n      $value: $value * $number\n  @else if $exp < 0\n    @for $i from 1 through -$exp\n      $value: divide($value, $number)\n  @return $value\n\n@function colorLuminance($color)\n  @if type-of($color) != 'color'\n    @return 0.55\n  $color-rgb: ('red': red($color),'green': green($color),'blue': blue($color))\n  @each $name, $value in $color-rgb\n    $adjusted: 0\n    $value: divide($value, 255)\n    @if $value < 0.03928\n      $value: divide($value, 12.92)\n    @else\n      $value: divide(($value + .055), 1.055)\n      $value: powerNumber($value, 2)\n    $color-rgb: map-merge($color-rgb, ($name: $value))\n  @return (map-get($color-rgb, 'red') * .2126) + (map-get($color-rgb, 'green') * .7152) + (map-get($color-rgb, 'blue') * .0722)\n\n@function findColorInvert($color)\n  @if (colorLuminance($color) > 0.55)\n    @return rgba(#000, 0.7)\n  @else\n    @return #fff\n\n@function findLightColor($color, $l: 96%)\n  @if type-of($color) == 'color'\n    $l: 96%\n    @if lightness($color) > 96%\n      $l: lightness($color)\n    @return change-color($color, $lightness: $l)\n  @return $background\n\n@function findDarkColor($color, $base-l: 29%)\n  @if type-of($color) == 'color'\n    $luminance: colorLuminance($color)\n    $luminance-delta: (0.53 - $luminance)\n    $target-l: round($base-l + ($luminance-delta * 53))\n    @return change-color($color, $lightness: max($base-l, $target-l))\n  @return $text-strong\n\n@function bulmaRgba($color, $alpha)\n  @if type-of($color) != 'color'\n    @return $color\n  @return rgba($color, $alpha)\n\n@function bulmaDarken($color, $amount)\n  @if type-of($color) != 'color'\n    @return $color\n  @return darken($color, $amount)\n\n@function bulmaLighten($color, $amount)\n  @if type-of($color) != 'color'\n    @return $color\n  @return lighten($color, $amount)\n\n// Custom divide function by @mdo from https://github.com/twbs/bootstrap/pull/34245\n// Replaces old slash division deprecated in Dart Sass\n@function divide($dividend, $divisor, $precision: 10)\n  $sign: if($dividend > 0 and $divisor > 0, 1, -1)\n  $dividend: abs($dividend)\n  $divisor: abs($divisor)\n  $quotient: 0\n  $remainder: $dividend\n  @if $dividend == 0\n    @return 0\n  @if $divisor == 0\n    @error \"Cannot divide by 0\"\n  @if $divisor == 1\n    @return $dividend\n  @while $remainder >= $divisor\n    $quotient: $quotient + 1\n    $remainder: $remainder - $divisor\n  @if $remainder > 0 and $precision > 0\n    $remainder: divide($remainder * 10, $divisor, $precision - 1) * .1\n  @return ($quotient + $remainder) * $sign\n"
  },
  {
    "path": "guide/style/bulma/sass/utilities/initial-variables.sass",
    "content": "// Colors\n\n$black:        hsl(0, 0%, 4%) !default\n$black-bis:    hsl(0, 0%, 7%) !default\n$black-ter:    hsl(0, 0%, 14%) !default\n\n$grey-darker:  hsl(0, 0%, 21%) !default\n$grey-dark:    hsl(0, 0%, 29%) !default\n$grey:         hsl(0, 0%, 48%) !default\n$grey-light:   hsl(0, 0%, 71%) !default\n$grey-lighter: hsl(0, 0%, 86%) !default\n$grey-lightest: hsl(0, 0%, 93%) !default\n\n$white-ter:    hsl(0, 0%, 96%) !default\n$white-bis:    hsl(0, 0%, 98%) !default\n$white:        hsl(0, 0%, 100%) !default\n\n$orange:       hsl(14,  100%, 53%) !default\n$yellow:       hsl(44,  100%, 77%) !default\n$green:        hsl(153, 53%,  53%) !default\n$turquoise:    hsl(171, 100%, 41%) !default\n$cyan:         hsl(207, 61%,  53%) !default\n$blue:         hsl(229, 53%,  53%) !default\n$purple:       hsl(271, 100%, 71%) !default\n$red:          hsl(348, 86%, 61%) !default\n\n// Typography\n\n$family-sans-serif: BlinkMacSystemFont, -apple-system, \"Segoe UI\", \"Roboto\", \"Oxygen\", \"Ubuntu\", \"Cantarell\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\", \"Helvetica\", \"Arial\", sans-serif !default\n$family-monospace: monospace !default\n$render-mode: optimizeLegibility !default\n\n$size-1: 3rem !default\n$size-2: 2.5rem !default\n$size-3: 2rem !default\n$size-4: 1.5rem !default\n$size-5: 1.25rem !default\n$size-6: 1rem !default\n$size-7: 0.75rem !default\n\n$weight-light: 300 !default\n$weight-normal: 400 !default\n$weight-medium: 500 !default\n$weight-semibold: 600 !default\n$weight-bold: 700 !default\n\n// Spacing\n\n$block-spacing: 1.5rem !default\n\n// Responsiveness\n\n// The container horizontal gap, which acts as the offset for breakpoints\n$gap: 32px !default\n// 960, 1152, and 1344 have been chosen because they are divisible by both 12 and 16\n$tablet: 769px !default\n// 960px container + 4rem\n$desktop: 960px + (2 * $gap) !default\n// 1152px container + 4rem\n$widescreen: 1152px + (2 * $gap) !default\n$widescreen-enabled: true !default\n// 1344px container + 4rem\n$fullhd: 1344px + (2 * $gap) !default\n$fullhd-enabled: true !default\n$breakpoints: (\"mobile\": (\"until\": $tablet), \"tablet\": (\"from\": $tablet), \"tablet-only\": (\"from\": $tablet, \"until\": $desktop), \"touch\": (\"from\": $desktop), \"desktop\": (\"from\": $desktop), \"desktop-only\": (\"from\": $desktop, \"until\": $widescreen), \"until-widescreen\": (\"until\": $widescreen), \"widescreen\": (\"from\": $widescreen), \"widescreen-only\": (\"from\": $widescreen, \"until\": $fullhd), \"until-fullhd\": (\"until\": $fullhd), \"fullhd\": (\"from\": $fullhd)) !default\n\n// Miscellaneous\n\n$easing: ease-out !default\n$radius-small: 2px !default\n$radius: 4px !default\n$radius-large: 6px !default\n$radius-rounded: 9999px !default\n$speed: 86ms !default\n\n// Flags\n\n$variable-columns: true !default\n$rtl: false !default\n"
  },
  {
    "path": "guide/style/bulma/sass/utilities/mixins.sass",
    "content": "@import \"derived-variables\"\n\n=clearfix\n  &::after\n    clear: both\n    content: \" \"\n    display: table\n\n=center($width, $height: 0)\n  position: absolute\n  @if $height != 0\n    left: calc(50% - (#{$width} * 0.5))\n    top: calc(50% - (#{$height} * 0.5))\n  @else\n    left: calc(50% - (#{$width} * 0.5))\n    top: calc(50% - (#{$width} * 0.5))\n\n=fa($size, $dimensions)\n  display: inline-block\n  font-size: $size\n  height: $dimensions\n  line-height: $dimensions\n  text-align: center\n  vertical-align: top\n  width: $dimensions\n\n=hamburger($dimensions)\n  -moz-appearance: none\n  -webkit-appearance: none\n  appearance: none\n  background: none\n  border: none\n  cursor: pointer\n  display: block\n  height: $dimensions\n  position: relative\n  width: $dimensions\n  span\n    background-color: currentColor\n    display: block\n    height: 1px\n    left: calc(50% - 8px)\n    position: absolute\n    transform-origin: center\n    transition-duration: $speed\n    transition-property: background-color, opacity, transform\n    transition-timing-function: $easing\n    width: 16px\n    &:nth-child(1)\n      top: calc(50% - 6px)\n    &:nth-child(2)\n      top: calc(50% - 1px)\n    &:nth-child(3)\n      top: calc(50% + 4px)\n  &:hover\n    background-color: bulmaRgba(black, 0.05)\n  // Modifers\n  &.is-active\n    span\n      &:nth-child(1)\n        transform: translateY(5px) rotate(45deg)\n      &:nth-child(2)\n        opacity: 0\n      &:nth-child(3)\n        transform: translateY(-5px) rotate(-45deg)\n\n=overflow-touch\n  -webkit-overflow-scrolling: touch\n\n=placeholder\n  $placeholders: ':-moz' ':-webkit-input' '-moz' '-ms-input'\n  @each $placeholder in $placeholders\n    &:#{$placeholder}-placeholder\n      @content\n\n=reset\n  -moz-appearance: none\n  -webkit-appearance: none\n  appearance: none\n  background: none\n  border: none\n  color: currentColor\n  font-family: inherit\n  font-size: 1em\n  margin: 0\n  padding: 0\n\n// Responsiveness\n\n=from($device)\n  @media screen and (min-width: $device)\n    @content\n\n=until($device)\n  @media screen and (max-width: $device - 1px)\n    @content\n\n=between($from, $until)\n  @media screen and (min-width: $from) and (max-width: $until - 1px)\n    @content\n\n=mobile\n  @media screen and (max-width: $tablet - 1px)\n    @content\n\n=tablet\n  @media screen and (min-width: $tablet), print\n    @content\n\n=tablet-only\n  @media screen and (min-width: $tablet) and (max-width: $desktop - 1px)\n    @content\n\n=touch\n  @media screen and (max-width: $desktop - 1px)\n    @content\n\n=desktop\n  @media screen and (min-width: $desktop)\n    @content\n\n=desktop-only\n  @if $widescreen-enabled\n    @media screen and (min-width: $desktop) and (max-width: $widescreen - 1px)\n      @content\n\n=until-widescreen\n  @if $widescreen-enabled\n    @media screen and (max-width: $widescreen - 1px)\n      @content\n\n=widescreen\n  @if $widescreen-enabled\n    @media screen and (min-width: $widescreen)\n      @content\n\n=widescreen-only\n  @if $widescreen-enabled and $fullhd-enabled\n    @media screen and (min-width: $widescreen) and (max-width: $fullhd - 1px)\n      @content\n\n=until-fullhd\n  @if $fullhd-enabled\n    @media screen and (max-width: $fullhd - 1px)\n      @content\n\n=fullhd\n  @if $fullhd-enabled\n    @media screen and (min-width: $fullhd)\n      @content\n\n=breakpoint($name)\n  $breakpoint: map-get($breakpoints, $name)\n  @if $breakpoint\n    $from: map-get($breakpoint, \"from\")\n    $until: map-get($breakpoint, \"until\")\n    @if $from and $until\n      +between($from, $until)\n        @content\n    @else if $from\n      +from($from)\n        @content\n    @else if $until\n      +until($until)\n        @content\n\n=ltr\n  @if not $rtl\n    @content\n\n=rtl\n  @if $rtl\n    @content\n\n=ltr-property($property, $spacing, $right: true)\n  $normal: if($right, \"right\", \"left\")\n  $opposite: if($right, \"left\", \"right\")\n  @if $rtl\n    #{$property}-#{$opposite}: $spacing\n  @else\n    #{$property}-#{$normal}: $spacing\n\n=ltr-position($spacing, $right: true)\n  $normal: if($right, \"right\", \"left\")\n  $opposite: if($right, \"left\", \"right\")\n  @if $rtl\n    #{$opposite}: $spacing\n  @else\n    #{$normal}: $spacing\n\n// Placeholders\n\n=unselectable\n  -webkit-touch-callout: none\n  -webkit-user-select: none\n  -moz-user-select: none\n  -ms-user-select: none\n  user-select: none\n\n=arrow($color: transparent)\n  border: 3px solid $color\n  border-radius: 2px\n  border-right: 0\n  border-top: 0\n  content: \" \"\n  display: block\n  height: 0.625em\n  margin-top: -0.4375em\n  pointer-events: none\n  position: absolute\n  top: 50%\n  transform: rotate(-45deg)\n  transform-origin: center\n  width: 0.625em\n\n=block($spacing: $block-spacing)\n  &:not(:last-child)\n    margin-bottom: $spacing\n\n=delete\n  +unselectable\n  -moz-appearance: none\n  -webkit-appearance: none\n  background-color: bulmaRgba($scheme-invert, 0.2)\n  border: none\n  border-radius: $radius-rounded\n  cursor: pointer\n  pointer-events: auto\n  display: inline-block\n  flex-grow: 0\n  flex-shrink: 0\n  font-size: 0\n  height: 20px\n  max-height: 20px\n  max-width: 20px\n  min-height: 20px\n  min-width: 20px\n  outline: none\n  position: relative\n  vertical-align: top\n  width: 20px\n  &::before,\n  &::after\n    background-color: $scheme-main\n    content: \"\"\n    display: block\n    left: 50%\n    position: absolute\n    top: 50%\n    transform: translateX(-50%) translateY(-50%) rotate(45deg)\n    transform-origin: center center\n  &::before\n    height: 2px\n    width: 50%\n  &::after\n    height: 50%\n    width: 2px\n  &:hover,\n  &:focus\n    background-color: bulmaRgba($scheme-invert, 0.3)\n  &:active\n    background-color: bulmaRgba($scheme-invert, 0.4)\n  // Sizes\n  &.is-small\n    height: 16px\n    max-height: 16px\n    max-width: 16px\n    min-height: 16px\n    min-width: 16px\n    width: 16px\n  &.is-medium\n    height: 24px\n    max-height: 24px\n    max-width: 24px\n    min-height: 24px\n    min-width: 24px\n    width: 24px\n  &.is-large\n    height: 32px\n    max-height: 32px\n    max-width: 32px\n    min-height: 32px\n    min-width: 32px\n    width: 32px\n\n=loader\n  animation: spinAround 500ms infinite linear\n  border: 2px solid $grey-lighter\n  border-radius: $radius-rounded\n  border-right-color: transparent\n  border-top-color: transparent\n  content: \"\"\n  display: block\n  height: 1em\n  position: relative\n  width: 1em\n\n=overlay($offset: 0)\n  bottom: $offset\n  left: $offset\n  position: absolute\n  right: $offset\n  top: $offset\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2019 James Loh\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/bulma-prefers-dark.sass",
    "content": "@charset \"utf-8\"\n/*! Bulma Prefers Dark  | MIT License | github.com/jloh/bulma-prefers-dark */\n\n@import \"sass/utilities/_all\"\n+prefers-scheme(dark)\n  @import \"sass/base/_all\"\n  @import \"sass/elements/_all\"\n  @import \"sass/components/_all\"\n  @import \"sass/layout/_all\"\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/base/_all.sass",
    "content": "@charset \"utf-8\"\n\n@import \"generic.sass\"\n@import \"helpers.sass\"\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/base/generic.sass",
    "content": "$body-background-color-dark: $body-background-dark !default\n$body-color-dark: $text-dark !default\n\n$hr-background-color-dark: $background-dark !default\n\n$strong-color-dark: $text-strong-dark !default\n\nhtml\n  background-color: $body-background-color-dark\n\nbody\n  color: $body-color-dark\n\n// Inline\n\na\n  color: $link-dark\n  &:hover\n    color: $link-hover-dark\n\ncode\n  background-color: $code-background-dark\n  color: $code-dark\n\nhr\n  background-color: $hr-background-color-dark\n\nstrong\n  color: $strong-color-dark\n\n// Block\n\npre\n  background-color: $pre-background-dark\n  color: $pre-dark\n\ntable\n  th\n    color: $text-strong-dark\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/base/helpers.sass",
    "content": "@each $name, $pair in $colors\n  $color: nth($pair, 1)\n  .has-text-#{$name}-dark\n    color: $color !important\n  a.has-text-#{$name}-dark\n    &:hover,\n    &:focus\n      color: lighten($color, 10%) !important\n  .has-background-#{$name}-dark\n    background-color: $color !important\n\n@each $name, $shade in $shades\n  .has-text-#{$name}-dark\n    color: $shade !important\n  .has-background-#{$name}-dark\n    background-color: $shade !important\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/components/_all.sass",
    "content": "@charset \"utf-8\"\n\n@import \"breadcrumb.sass\"\n@import \"card.sass\"\n@import \"dropdown.sass\"\n@import \"list.sass\"\n@import \"media.sass\"\n@import \"menu.sass\"\n@import \"message.sass\"\n@import \"modal.sass\"\n@import \"navbar.sass\"\n@import \"pagination.sass\"\n@import \"panel.sass\"\n@import \"tabs.sass\"\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/components/breadcrumb.sass",
    "content": "$breadcrumb-item-color-dark: $link-dark !default\n$breadcrumb-item-hover-color-dark: $link-hover-dark !default\n$breadcrumb-item-active-color-dark: $text-strong-dark !default\n\n$breadcrumb-item-separator-color-dark: $grey-dark !default\n\n.breadcrumb\n  a\n    color: $breadcrumb-item-color-dark\n    &:hover\n      color: $breadcrumb-item-hover-color-dark\n  li\n    &.is-active\n      a\n        color: $breadcrumb-item-active-color-dark\n    & + li::before\n      color: $breadcrumb-item-separator-color-dark\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/components/card.sass",
    "content": "$card-color-dark: $text-dark !default\n$card-background-color-dark: $black !default\n$card-shadow-dark: 0 2px 3px rgba($white, 0.1), 0 0 0 1px rgba($white, 0.1) !default\n\n$card-header-color-dark: $text-strong-dark !default\n$card-header-shadow-dark: 0 1px 2px rgba($white, 0.1) !default\n\n$card-footer-border-top-dark: 1px solid $border-dark !default\n\n.card\n  background-color: $card-background-color-dark\n  box-shadow: $card-shadow-dark\n  color: $card-color-dark\n\n.card-header\n  box-shadow: $card-header-shadow-dark\n\n.card-header-title\n  color: $card-header-color-dark\n\n.card-footer\n  border-top: $card-footer-border-top-dark\n\n.card-footer-item\n  &:not(:last-child)\n    border-right: $card-footer-border-top-dark\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/components/dropdown.sass",
    "content": "$dropdown-content-background-color-dark: $black !default\n$dropdown-content-arrow: $link-dark !default\n$dropdown-content-shadow-dark: 0 2px 3px rgba($white, 0.1), 0 0 0 1px rgba($white, 0.1) !default\n\n$dropdown-item-color-dark: $grey-light !default\n$dropdown-item-hover-color-dark: $white !default\n$dropdown-item-hover-background-color-dark: $background-dark !default\n$dropdown-item-active-color-dark: $link-invert-dark !default\n$dropdown-item-active-background-color-dark: $link-dark !default\n\n$dropdown-divider-background-color-dark: $border-dark !default\n\n.dropdown-content\n  background-color: $dropdown-content-background-color-dark\n  box-shadow: $dropdown-content-shadow-dark\n\n.dropdown-item\n  color: $dropdown-item-color-dark\n\na.dropdown-item,\nbutton.dropdown-item\n  &:hover\n    background-color: $dropdown-item-hover-background-color-dark\n    color: $dropdown-item-hover-color-dark\n  &.is-active\n    background-color: $dropdown-item-active-background-color-dark\n    color: $dropdown-item-active-color-dark\n\n.dropdown-divider\n  background-color: $dropdown-divider-background-color-dark\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/components/list.sass",
    "content": "$list-background-color-dark: $black !default\n$list-shadow-dark: 0 2px 3px rgba($white, 0.1), 0 0 0 1px rgba($white, 0.1) !default\n\n$list-item-border-dark: 1px solid $border-dark !default\n$list-item-color-dark: $text-dark !default\n$list-item-active-background-color-dark: $link-dark !default\n$list-item-active-color-dark: $link-invert-dark !default\n$list-item-hover-background-color-dark: $background-dark !default\n\n.list\n  background-color: $list-background-color-dark\n  box-shadow: $list-shadow-dark\n  // &.is-hoverable > .list-item:hover:not(.is-active)\n  //   background-color: $list-item-hover-background-color-dark\n  //   cursor: pointer\n\n.list-item\n  &:not(a)\n    color: $list-item-color-dark\n  &:not(:last-child)\n    border-bottom: $list-item-border-dark\n  &.is-active\n    background-color: $list-item-active-background-color-dark\n    color: $list-item-active-color-dark\n\na.list-item\n  background-color: $list-item-hover-background-color-dark\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/components/media.sass",
    "content": ".media\n  .media\n    border-top: 1px solid rgba($border-dark, 0.5)\n  & + .media\n    border-top: 1px solid rgba($border-dark, 0.5)\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/components/menu.sass",
    "content": "$menu-item-color-dark: $text-dark !default\n$menu-item-hover-color-dark: $text-strong-dark !default\n$menu-item-hover-background-color-dark: $background-dark !default\n$menu-item-active-color-dark: $link-invert-dark !default\n$menu-item-active-background-color-dark: $link-dark !default\n\n$menu-list-border-left-dark: 1px solid $border-dark !default\n\n.menu-list\n  a\n    color: $menu-item-color-dark\n    &:hover\n      background-color: $menu-item-hover-background-color-dark\n      color: $menu-item-hover-color-dark\n    // Modifiers\n    &.is-active\n      background-color: $menu-item-active-background-color-dark\n      color: $menu-item-active-color-dark\n  li\n    ul\n      border-left: $menu-list-border-left-dark\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/components/message.sass",
    "content": "$message-background-color-dark: $background-dark !default\n\n$message-header-background-color-dark: $text-dark !default\n$message-header-color-dark: $text-invert-dark !default\n\n$message-body-border-color-dark: $border-dark !default\n$message-body-color-dark: $text-dark !default\n\n$message-body-pre-background-color-dark: $black !default\n\n.message\n  background-color: $message-background-color-dark\n  // Colors\n  @each $name, $pair in $colors\n    $color: nth($pair, 1)\n    $color-invert: nth($pair, 2)\n    &.is-#{$name}\n      background-color: $background-dark\n      .message-header\n        background-color: $color\n        color: $color-invert\n      .message-body\n        border-color: $color\n        color: $text-dark\n\n  // Dark Colors\n  @each $name, $pair in $colors\n    $color: nth($pair, 1)\n    $color-invert: nth($pair, 2)\n    &.is-#{$name}-dark\n      background-color: $background-dark\n      .message-header\n        background-color: $color\n        color: $color-invert\n      .message-body\n        border-color: $color\n        color: $text-dark\n\n.message-header\n  background-color: $message-header-background-color-dark\n  color: $message-header-color-dark\n\n.message-body\n  border-color: $message-body-border-color-dark\n  color: $message-body-color-dark\n  code,\n  pre\n    background-color: $message-body-pre-background-color-dark\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/components/modal.sass",
    "content": "$modal-background-background-color-dark: rgba($white, 0.86) !default\n\n$modal-card-head-background-color-dark: $background-dark !default\n$modal-card-head-border-bottom-dark: 1px solid $border-dark !default\n\n$modal-card-title-color-dark: $text-strong-dark !default\n\n$modal-card-foot-border-top-dark: 1px solid $border-dark !default\n\n$modal-card-body-background-color-dark: $white !default\n\n.modal-background\n  background-color: $modal-background-background-color-dark\n\n.modal-card-head,\n.modal-card-foot\n  background-color: $modal-card-head-background-color-dark\n\n.modal-card-head\n  border-bottom: $modal-card-head-border-bottom-dark\n\n.modal-card-title\n  color: $modal-card-title-color-dark\n\n.modal-card-foot\n  border-top: $modal-card-foot-border-top-dark\n\n.modal-card-body\n  +overflow-touch\n  background-color: $modal-card-body-background-color-dark\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/components/navbar.sass",
    "content": "$navbar-background-color-dark: $body-background-dark !default\n$navbar-box-shadow-size: 0 2px 0 0 !default\n$navbar-box-shadow-color-dark: $background-dark !default\n\n$navbar-item-color-dark: $grey-light !default\n$navbar-item-hover-color-dark: $link-dark !default\n$navbar-item-hover-background-color-dark: $black-bis !default\n$navbar-item-active-color-dark: $white !default\n\n$navbar-burger-color-dark: $navbar-item-color-dark !default\n\n$navbar-tab-hover-border-bottom-color-dark: $link-dark !default\n$navbar-tab-active-color-dark: $link-dark !default\n$navbar-tab-active-border-bottom-color-dark: $link-dark !default\n\n$navbar-dropdown-background-color-dark: $black !default\n$navbar-dropdown-border-top-dark: 2px solid $border-dark !default\n$navbar-dropdown-arrow-dark: $link-dark !default\n\n$navbar-dropdown-boxed-shadow-dark: 0 8px 8px rgba($white, 0.1), 0 0 0 1px rgba($white, 0.1) !default\n\n$navbar-dropdown-item-hover-color-dark: $white !default\n$navbar-dropdown-item-hover-background-color-dark: $background-dark !default\n$navbar-dropdown-item-active-color-dark: $link-dark !default\n$navbar-dropdown-item-active-background-color-dark: $background-dark !default\n\n$navbar-divider-background-color-dark: $background-dark !default\n\n$navbar-bottom-box-shadow-size: 0 -2px 0 0 !default\n\n$navbar-breakpoint: $desktop !default\n\n.navbar\n  background-color: $navbar-background-color-dark\n  @each $name, $pair in $colors\n    $color: darken(nth($pair, 1), 10%)\n    $color-invert: nth($pair, 2)\n    &.is-#{$name}\n      background-color: $color\n      color: $color-invert\n      .navbar-brand\n        & > .navbar-item,\n        .navbar-link\n          color: $color-invert\n        & > a.navbar-item,\n        .navbar-link\n          &:hover,\n          &.is-active\n            background-color: darken($color, 5%)\n            color: $color-invert\n        .navbar-link\n          &::after\n            border-color: $color-invert\n      .navbar-burger\n        color: $color-invert\n      +from($navbar-breakpoint)\n        .navbar-start,\n        .navbar-end\n          & > .navbar-item,\n          .navbar-link\n            color: $color-invert\n          & > a.navbar-item,\n          .navbar-link\n            &:hover,\n            &.is-active\n              background-color: darken($color, 5%)\n              color: $color-invert\n          .navbar-link\n            &::after\n              border-color: $color-invert\n        .navbar-item.has-dropdown:hover .navbar-link,\n        .navbar-item.has-dropdown.is-active .navbar-link\n          background-color: darken($color, 5%)\n          color: $color-invert\n        .navbar-dropdown\n          a.navbar-item\n            &.is-active\n              background-color: $color\n              color: $color-invert\n  // Colors Dark\n  @each $name, $pair in $colors\n    $color: nth($pair, 1)\n    $color-invert: nth($pair, 2)\n    &.is-#{$name}-dark\n      background-color: $color\n      color: $color-invert\n      .navbar-brand\n        & > .navbar-item,\n        .navbar-link\n          color: $color-invert\n        & > a.navbar-item,\n        .navbar-link\n          &:hover,\n          &.is-active\n            background-color: darken($color, 5%)\n            color: $color-invert\n        .navbar-link\n          &::after\n            border-color: $color-invert\n      .navbar-burger\n        color: $color-invert\n      +from($navbar-breakpoint)\n        .navbar-start,\n        .navbar-end\n          & > .navbar-item,\n          .navbar-link\n            color: $color-invert\n          & > a.navbar-item,\n          .navbar-link\n            &:hover,\n            &.is-active\n              background-color: darken($color, 5%)\n              color: $color-invert\n          .navbar-link\n            &::after\n              border-color: $color-invert\n        .navbar-item.has-dropdown:hover .navbar-link,\n        .navbar-item.has-dropdown.is-active .navbar-link\n          background-color: darken($color, 5%)\n          color: $color-invert\n        .navbar-dropdown\n          a.navbar-item\n            &.is-active\n              background-color: $color\n              color: $color-invert\n  &.has-shadow\n    box-shadow: $navbar-box-shadow-size $navbar-box-shadow-color-dark\n  &.is-fixed-bottom\n    &.has-shadow\n      box-shadow: $navbar-bottom-box-shadow-size $navbar-box-shadow-color-dark\n\n.navbar-burger\n  color: $navbar-burger-color-dark\n\n.navbar-item,\n.navbar-link\n  color: $navbar-item-color-dark\n\na.navbar-item,\n.navbar-link\n  &:hover,\n  &.is-active\n    background-color: $navbar-item-hover-background-color-dark\n    color: $navbar-item-hover-color-dark\n\n.navbar-item\n    &:hover\n      border-bottom-color: $navbar-tab-hover-border-bottom-color-dark\n    &.is-active\n      border-bottom-color: $navbar-tab-active-border-bottom-color-dark\n      color: $navbar-tab-active-color-dark\n\n.navbar-link:not(.is-arrowless)\n  &::after\n    border-color: $navbar-dropdown-arrow-dark\n\n.navbar-divider\n  background-color: $navbar-divider-background-color-dark\n\n+until($navbar-breakpoint)\n  .navbar-menu\n    background-color: $navbar-background-color-dark\n    box-shadow: 0 8px 16px rgba($white, 0.1)\n  // Fixed navbar\n  .navbar\n    &.is-fixed-bottom-touch\n      &.has-shadow\n        box-shadow: 0 -2px 3px rgba($white, 0.1)\n\n+from($navbar-breakpoint)\n  .navbar\n    &.is-transparent\n      .navbar-dropdown\n        a.navbar-item\n          &:hover\n            background-color: $navbar-dropdown-item-hover-background-color-dark\n            color: $navbar-dropdown-item-hover-color-dark\n          &.is-active\n            background-color: $navbar-dropdown-item-active-background-color-dark\n            color: $navbar-dropdown-item-active-color-dark\n  .navbar-item\n    &.has-dropdown-up\n      .navbar-dropdown\n        border-bottom: $navbar-dropdown-border-top-dark\n        box-shadow: 0 -8px 8px rgba($white, 0.1)\n  .navbar-dropdown\n    background-color: $navbar-dropdown-background-color-dark\n    border-top: $navbar-dropdown-border-top-dark\n    box-shadow: 0 8px 8px rgba($white, 0.1)\n    a.navbar-item\n      &:hover\n        background-color: $navbar-dropdown-item-hover-background-color-dark\n        color: $navbar-dropdown-item-hover-color-dark\n      &.is-active\n        background-color: $navbar-dropdown-item-active-background-color-dark\n        color: $navbar-dropdown-item-active-color-dark\n    .navbar.is-spaced &,\n    &.is-boxed\n      box-shadow: $navbar-dropdown-boxed-shadow-dark\n  // Fixed navbar\n  .navbar\n    &.is-fixed-bottom-desktop\n      &.has-shadow\n        box-shadow: 0 -2px 3px rgba($white, 0.1)\n  // Hover/Active states\n  a.navbar-item,\n  .navbar-link\n    &.is-active\n      color: $navbar-item-active-color-dark\n  .navbar-item.has-dropdown\n    &:hover,\n    &.is-active\n      .navbar-link\n        background-color: $navbar-item-hover-background-color-dark\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/components/pagination.sass",
    "content": "$pagination-color-dark: $grey-lighter !default\n$pagination-border-color-dark: $grey-darker !default\n\n$pagination-hover-color-dark: $link-hover-dark !default\n$pagination-hover-border-color-dark: $link-hover-border-dark !default\n\n$pagination-focus-color-dark: $link-focus-dark !default\n$pagination-focus-border-color-dark: $link-focus-border-dark !default\n\n$pagination-active-color-dark: $link-active-dark !default\n$pagination-active-border-color-dark: $link-active-border-dark !default\n\n$pagination-disabled-color-dark: $grey !default\n$pagination-disabled-background-color-dark: $grey-darker !default\n$pagination-disabled-border-color-dark: $grey-darker !default\n\n$pagination-current-color-dark: $link-invert-dark !default\n$pagination-current-background-color-dark: $link-dark !default\n$pagination-current-border-color-dark: $link-dark !default\n\n$pagination-ellipsis-color-dark: $grey-dark !default\n\n$pagination-shadow-inset-dark: inset 0 1px 2px rgba($white, 0.2)\n\n.pagination-previous,\n.pagination-next,\n.pagination-link\n  border-color: $pagination-border-color-dark\n  color: $pagination-color-dark\n  &:hover\n    border-color: $pagination-hover-border-color-dark\n    color: $pagination-hover-color-dark\n  &:focus\n    border-color: $pagination-focus-border-color-dark\n  &:active\n    box-shadow: $pagination-shadow-inset-dark\n  &[disabled]\n    background-color: $pagination-disabled-background-color-dark\n    border-color: $pagination-disabled-border-color-dark\n    color: $pagination-disabled-color-dark\n\n.pagination-link\n  &.is-current\n    background-color: $pagination-current-background-color-dark\n    border-color: $pagination-current-border-color-dark\n    color: $pagination-current-color-dark\n\n.pagination-ellipsis\n  color: $pagination-ellipsis-color-dark\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/components/panel.sass",
    "content": "$panel-item-border-dark: 1px solid $border-dark !default\n\n$panel-heading-background-color-dark: $background-dark !default\n$panel-heading-color-dark: $text-strong-dark !default\n\n$panel-tab-border-bottom-dark: 1px solid $border-dark !default\n$panel-tab-active-border-bottom-color-dark: $link-active-border-dark !default\n$panel-tab-active-color-dark: $link-active-dark !default\n\n$panel-list-item-color-dark: $text-dark !default\n$panel-list-item-hover-color-dark: $link-dark !default\n\n$panel-block-color-dark: $text-strong-dark !default\n$panel-block-hover-background-color-dark: $background-dark !default\n$panel-block-active-border-left-color-dark: $link-dark !default\n$panel-block-active-color-dark: $link-active-dark !default\n$panel-block-active-icon-color-dark: $link-dark !default\n\n.panel-heading,\n.panel-tabs,\n.panel-block\n  border-bottom: $panel-item-border-dark\n  border-left: $panel-item-border-dark\n  border-right: $panel-item-border-dark\n  &:first-child\n    border-top: $panel-item-border-dark\n\n.panel-heading\n  background-color: $panel-heading-background-color-dark\n  color: $panel-heading-color-dark\n\n.panel-tabs\n  a\n    border-bottom: $panel-tab-border-bottom-dark\n    // Modifiers\n    &.is-active\n      border-bottom-color: $panel-tab-active-border-bottom-color-dark\n      color: $panel-tab-active-color-dark\n\n.panel-list\n  a\n    color: $panel-list-item-color-dark\n    &:hover\n      color: $panel-list-item-hover-color-dark\n\n.panel-block\n  color: $panel-block-color-dark\n  &.is-active\n    border-left-color: $panel-block-active-border-left-color-dark\n    color: $panel-block-active-color-dark\n    .panel-icon\n      color: $panel-block-active-icon-color-dark\n\na.panel-block,\nlabel.panel-block\n  &:hover\n    background-color: $panel-block-hover-background-color-dark\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/components/tabs.sass",
    "content": "$tabs-border-bottom-color-dark: $border-dark !default\n$tabs-link-color-dark: $text-dark !default\n$tabs-link-hover-border-bottom-color-dark: $text-strong-dark !default\n$tabs-link-hover-color-dark: $text-strong-dark !default\n$tabs-link-active-border-bottom-color-dark: $link-dark !default\n$tabs-link-active-color-dark: $link-dark !default\n\n$tabs-boxed-link-hover-background-color-dark: $background-dark !default\n$tabs-boxed-link-hover-border-bottom-color-dark: $border-dark !default\n\n$tabs-boxed-link-active-background-color-dark: $black !default\n$tabs-boxed-link-active-border-color-dark: $border-dark !default\n\n$tabs-toggle-link-border-color-dark: $border-dark !default\n$tabs-toggle-link-hover-background-color-dark: $background-dark !default\n$tabs-toggle-link-hover-border-color-dark: $border-hover-dark !default\n$tabs-toggle-link-active-background-color-dark: $link-dark !default\n$tabs-toggle-link-active-border-color-dark: $link-dark !default\n$tabs-toggle-link-active-color-dark: $link-invert-dark !default\n\n.tabs\n  a\n    border-bottom-color: $tabs-border-bottom-color-dark\n    color: $tabs-link-color-dark\n    &:hover\n      border-bottom-color: $tabs-link-hover-border-bottom-color-dark\n      color: $tabs-link-hover-color-dark\n  li\n    &.is-active\n      a\n        border-bottom-color: $tabs-link-active-border-bottom-color-dark\n        color: $tabs-link-active-color-dark\n  ul\n    border-bottom-color: $tabs-border-bottom-color-dark\n  // Styles\n  &.is-boxed\n    a\n      &:hover\n        background-color: $tabs-boxed-link-hover-background-color-dark\n        border-bottom-color: $tabs-boxed-link-hover-border-bottom-color-dark\n    li\n      &.is-active\n        a\n          background-color: $tabs-boxed-link-active-background-color-dark\n          border-color: $tabs-boxed-link-active-border-color-dark\n  &.is-toggle\n    a\n      border-color: $tabs-toggle-link-border-color-dark\n      &:hover\n        background-color: $tabs-toggle-link-hover-background-color-dark\n        border-color: $tabs-toggle-link-hover-border-color-dark\n    li\n      &.is-active\n        a\n          background-color: $tabs-toggle-link-active-background-color-dark\n          border-color: $tabs-toggle-link-active-border-color-dark\n          color: $tabs-toggle-link-active-color-dark\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/elements/_all.sass",
    "content": "@charset \"utf-8\"\n\n@import \"box.sass\"\n@import \"button.sass\"\n@import \"content.sass\"\n@import \"form.sass\"\n@import \"notification.sass\"\n@import \"progress.sass\"\n@import \"table.sass\"\n@import \"tag.sass\"\n@import \"title.sass\"\n\n@import \"other.sass\"\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/elements/box.sass",
    "content": "$box-color-dark: $text-dark !default\n$box-background-color-dark: $black !default\n$box-shadow-dark: 0 2px 3px rgba($white, 0.1), 0 0 0 1px rgba($white, 0.1) !default\n\n$box-link-hover-shadow-dark: 0 2px 3px rgba($white, 0.1), 0 0 0 1px $link-dark !default\n$box-link-active-shadow-dark: inset 0 1px 2px rgba($white, 0.2), 0 0 0 1px $link-dark !default\n\n.box\n  background-color: $box-background-color-dark\n  box-shadow: $box-shadow-dark\n  color: $box-color-dark\n\na.box\n  &:hover,\n  &:focus\n    box-shadow: $box-link-hover-shadow-dark\n  &:active\n    box-shadow: $box-link-active-shadow-dark\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/elements/button.sass",
    "content": "$button-color-dark: $grey-lighter !default\n$button-background-color-dark: $black !default\n\n$button-border-color-dark: $grey-darker !default\n\n$button-hover-color-dark: $link-hover-dark !default\n$button-hover-border-color-dark: $link-hover-border-dark !default\n\n$button-focus-color-dark: $link-focus-dark !default\n$button-focus-border-color-dark: $link-focus-border-dark !default\n$button-focus-box-shadow-size: 0 0 0 0.125em !default\n$button-focus-box-shadow-color-dark: rgba($link-dark, 0.25) !default\n\n$button-active-color-dark: $link-active-dark !default\n$button-active-border-color-dark: $link-active-border-dark !default\n\n$button-text-color-dark: $text-dark !default\n$button-text-hover-background-color-dark: $background-dark !default\n$button-text-hover-color-dark: $text-strong-dark !default\n\n$button-disabled-background-color-dark: $black !default\n$button-disabled-border-color-dark: $grey-darker !default\n\n$button-static-color-dark: $grey !default\n$button-static-background-color-dark: $white-ter !default\n$button-static-border-color-dark: $grey-darker !default\n\n.button\n  background-color: $button-background-color-dark\n  border-color: $button-border-color-dark\n  color: $button-color-dark\n  // States\n  &:hover,\n  &.is-hovered\n    border-color: $button-hover-border-color-dark\n    color: $button-hover-color-dark\n  &:focus,\n  &.is-focused\n    border-color: $button-focus-border-color-dark\n    color: $button-focus-color-dark\n    &:not(:active)\n      box-shadow: $button-focus-box-shadow-size $button-focus-box-shadow-color-dark\n  &:active,\n  &.is-active\n    border-color: $button-active-border-color-dark\n    color: $button-active-color-dark\n  // Colors\n  &.is-text\n    color: $button-text-color-dark\n    &:hover,\n    &.is-hovered,\n    &:focus,\n    &.is-focused\n      background-color: $button-text-hover-background-color-dark\n      color: $button-text-hover-color-dark\n    &:active,\n    &.is-active\n      background-color: darken($button-text-hover-background-color-dark, 5%)\n      color: $button-text-hover-color-dark\n  // Colors\n  @each $name, $pair in $colors\n    $color: darken(nth($pair, 1), 10%)\n    $color-invert: nth($pair, 2)\n    &.is-#{$name}\n      background-color: $color\n      border-color: transparent\n      color: $color-invert\n      &:hover,\n      &.is-hovered\n        background-color: darken($color, 2.5%)\n        border-color: transparent\n        color: $color-invert\n      &:focus,\n      &.is-focused\n        border-color: transparent\n        color: $color-invert\n        &:not(:active)\n          box-shadow: $button-focus-box-shadow-size rgba($color, 0.25)\n      &:active,\n      &.is-active\n        background-color: darken($color, 5%)\n        border-color: transparent\n        color: $color-invert\n      &[disabled],\n      fieldset[disabled] &\n        background-color: $color\n        border-color: transparent\n        box-shadow: none\n      &.is-inverted\n        background-color: $color-invert\n        color: $color\n        &:hover\n          background-color: darken($color-invert, 5%)\n        &[disabled],\n        fieldset[disabled] &\n          background-color: $color-invert\n          border-color: transparent\n          box-shadow: none\n          color: $color\n      &.is-loading\n        &::after\n          border-color: transparent transparent $color-invert $color-invert !important\n      &.is-outlined\n        background-color: transparent\n        border-color: $color\n        color: $color\n        &:hover,\n        &:focus\n          background-color: $color\n          border-color: $color\n          color: $color-invert\n        &.is-loading\n          &::after\n            border-color: transparent transparent $color $color !important\n        &[disabled],\n        fieldset[disabled] &\n          background-color: transparent\n          border-color: $color\n          box-shadow: none\n          color: $color\n      &.is-inverted.is-outlined\n        background-color: transparent\n        border-color: $color-invert\n        color: $color-invert\n        &:hover,\n        &:focus\n          background-color: $color-invert\n          color: $color\n        &[disabled],\n        fieldset[disabled] &\n          background-color: transparent\n          border-color: $color-invert\n          box-shadow: none\n          color: $color-invert\n  // Colors Dark\n  @each $name, $pair in $colors\n    $color: nth($pair, 1)\n    $color-invert: nth($pair, 2)\n    &.is-#{$name}-dark\n      background-color: $color\n      border-color: transparent\n      color: $color-invert\n      &:hover,\n      &.is-hovered\n        background-color: darken($color, 2.5%)\n        border-color: transparent\n        color: $color-invert\n      &:focus,\n      &.is-focused\n        border-color: transparent\n        color: $color-invert\n        &:not(:active)\n          box-shadow: $button-focus-box-shadow-size rgba($color, 0.25)\n      &:active,\n      &.is-active\n        background-color: darken($color, 5%)\n        border-color: transparent\n        color: $color-invert\n      &[disabled],\n      fieldset[disabled] &\n        background-color: $color\n        border-color: transparent\n        box-shadow: none\n      &.is-inverted\n        background-color: $color-invert\n        color: $color\n        &:hover\n          background-color: darken($color-invert, 5%)\n        &[disabled],\n        fieldset[disabled] &\n          background-color: $color-invert\n          border-color: transparent\n          box-shadow: none\n          color: $color\n      &.is-loading\n        &::after\n          border-color: transparent transparent $color-invert $color-invert !important\n      &.is-outlined\n        background-color: transparent\n        border-color: $color\n        color: $color\n        &:hover,\n        &:focus\n          background-color: $color\n          border-color: $color\n          color: $color-invert\n        &.is-loading\n          &::after\n            border-color: transparent transparent $color $color !important\n        &[disabled],\n        fieldset[disabled] &\n          background-color: transparent\n          border-color: $color\n          box-shadow: none\n          color: $color\n      &.is-inverted.is-outlined\n        background-color: transparent\n        border-color: $color-invert\n        color: $color-invert\n        &:hover,\n        &:focus\n          background-color: $color-invert\n          color: $color\n        &[disabled],\n        fieldset[disabled] &\n          background-color: transparent\n          border-color: $color-invert\n          box-shadow: none\n          color: $color-invert\n  // Modifiers\n  &[disabled],\n  fieldset[disabled] &\n    background-color: $button-disabled-background-color-dark\n    border-color: $button-disabled-border-color-dark\n  &.is-static\n    background-color: $button-static-background-color-dark\n    border-color: $button-static-border-color-dark\n    color: $button-static-color-dark\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/elements/content.sass",
    "content": "$content-heading-color-dark: $text-strong-dark !default\n\n$content-blockquote-background-color-dark: $background-dark !default\n$content-blockquote-border-left-dark: 5px solid $border-dark !default\n\n$content-table-cell-border-dark: 1px solid $border-dark !default\n$content-table-cell-heading-color-dark: $text-strong-dark !default\n$content-table-head-cell-color-dark: $text-strong-dark !default\n$content-table-foot-cell-color-dark: $text-strong-dark !default\n\n.content\n  h1,\n  h2,\n  h3,\n  h4,\n  h5,\n  h6\n    color: $content-heading-color-dark\n  blockquote\n    background-color: $content-blockquote-background-color-dark\n    border-left: $content-blockquote-border-left-dark\n  table\n    td,\n    th\n      border: $content-table-cell-border-dark\n    th\n      color: $content-table-cell-heading-color-dark\n    thead\n      td,\n      th\n        color: $content-table-head-cell-color-dark\n    tfoot\n      td,\n      th\n        color: $content-table-foot-cell-color-dark\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/elements/form.sass",
    "content": "$input-color-dark: $grey-lighter !default\n$input-background-color-dark: $black !default\n$input-border-color-dark: $grey-darker !default\n$input-shadow-dark: inset 0 1px 2px rgba($white, 0.1) !default\n$input-placeholder-color-dark: rgba($input-color-dark, 0.3) !default\n\n$input-hover-color-dark: $grey-lighter !default\n$input-hover-border-color-dark: $grey-dark !default\n\n$input-focus-color-dark: $grey-lighter !default\n$input-focus-border-color-dark: $link-dark !default\n$input-focus-box-shadow-size: 0 0 0 0.125em !default\n$input-focus-box-shadow-color-dark: rgba($link-dark, 0.25) !default\n\n$input-disabled-color-dark: $text-dark !default\n$input-disabled-background-color-dark: $background-dark !default\n$input-disabled-border-color-dark: $background-dark !default\n$input-disabled-placeholder-color-dark: rgba($input-disabled-color-dark, 0.3) !default\n\n$input-arrow-dark: $link-dark !default\n\n$input-icon-color-dark: $grey-darker !default\n\n$file-border-color-dark: $border-dark !default\n\n$file-cta-background-color-dark: $black-ter !default\n$file-cta-color-dark: $grey-light !default\n$file-cta-hover-color-dark: $grey-lighter !default\n$file-cta-active-color-dark: $grey-lighter !default\n\n$file-name-border-color-dark: $border-dark !default\n\n$label-color-dark: $grey-lighter !default\n\n=input\n  background-color: $input-background-color-dark\n  border-color: $input-border-color-dark\n  color: $input-color-dark\n  +placeholder\n    color: $input-placeholder-color-dark\n  &:hover,\n  &.is-hovered\n    border-color: $input-hover-border-color-dark\n  &:focus,\n  &.is-focused,\n  &:active,\n  &.is-active\n    border-color: $input-focus-border-color-dark\n    box-shadow: $input-focus-box-shadow-size $input-focus-box-shadow-color-dark\n  &[disabled],\n  fieldset[disabled] &\n    background-color: $input-disabled-background-color-dark\n    border-color: $input-disabled-border-color-dark\n    color: $input-disabled-color-dark\n    +placeholder\n      color: $input-disabled-placeholder-color-dark\n\n.input,\n.textarea\n  +input\n  box-shadow: $input-shadow-dark\n  // Colors\n  @each $name, $pair in $colors\n    $color: darken(nth($pair, 1), 10%)\n    &.is-#{$name}\n      border-color: $color\n      &:focus,\n      &.is-focused,\n      &:active,\n      &.is-active\n        box-shadow: $input-focus-box-shadow-size rgba($color, 0.25)\n  // Colors Dark\n  @each $name, $pair in $colors\n    $color: nth($pair, 1)\n    &.is-#{$name}-dark\n      border-color: $color\n      &:focus,\n      &.is-focused,\n      &:active,\n      &.is-active\n        box-shadow: $input-focus-box-shadow-size rgba($color, 0.25)\n\n.checkbox,\n.radio\n  &:hover\n    color: $input-hover-color-dark\n  &[disabled],\n  fieldset[disabled] &\n    color: $input-disabled-color-dark\n\n.select\n  &:not(.is-multiple):not(.is-loading)\n    &::after\n      border-color: $input-arrow-dark\n  select\n    +input\n    &[disabled]:hover,\n    fieldset[disabled] &:hover\n      border-color: $input-disabled-border-color-dark\n    option\n      color: $input-color-dark\n  // States\n  &:not(.is-multiple):not(.is-loading):hover\n    &::after\n      border-color: $input-hover-color-dark\n  // Colors\n  @each $name, $pair in $colors\n    $color: darken(nth($pair, 1), 10%)\n    &.is-#{$name}\n      &:not(:hover)::after\n        border-color: $color\n      select\n        border-color: $color\n        &:hover,\n        &.is-hovered\n          border-color: darken($color, 5%)\n        &:focus,\n        &.is-focused,\n        &:active,\n        &.is-active\n          box-shadow: $input-focus-box-shadow-size rgba($color, 0.25)\n  // Colors Dark\n  @each $name, $pair in $colors\n    $color: nth($pair, 1)\n    &.is-#{$name}-dark\n      &:not(:hover)::after\n        border-color: $color\n      select\n        border-color: $color\n        &:hover,\n        &.is-hovered\n          border-color: darken($color, 5%)\n        &:focus,\n        &.is-focused,\n        &:active,\n        &.is-active\n          box-shadow: $input-focus-box-shadow-size rgba($color, 0.25)\n  // Modifiers\n  &.is-disabled\n    &::after\n      border-color: $input-disabled-color-dark\n\n.file\n  // Colors\n  @each $name, $pair in $colors\n    $color: darken(nth($pair, 1), 10%)\n    $color-invert: nth($pair, 2)\n    &.is-#{$name}\n      .file-cta\n        background-color: $color\n        color: $color-invert\n      &:hover,\n      &.is-hovered\n        .file-cta\n          background-color: darken($color, 2.5%)\n          color: $color-invert\n      &:focus,\n      &.is-focused\n        .file-cta\n          box-shadow: 0 0 0.5em rgba($color, 0.25)\n          color: $color-invert\n      &:active,\n      &.is-active\n        .file-cta\n          background-color: darken($color, 5%)\n          color: $color-invert\n  // Colors Dark\n  @each $name, $pair in $colors\n    $color: nth($pair, 1)\n    $color-invert: nth($pair, 2)\n    &.is-#{$name}-dark\n      .file-cta\n        background-color: $color\n        color: $color-invert\n      &:hover,\n      &.is-hovered\n        .file-cta\n          background-color: darken($color, 2.5%)\n          color: $color-invert\n      &:focus,\n      &.is-focused\n        .file-cta\n          box-shadow: 0 0 0.5em rgba($color, 0.25)\n          color: $color-invert\n      &:active,\n      &.is-active\n        .file-cta\n          background-color: darken($color, 5%)\n          color: $color-invert\n\n.file-label\n  &:hover\n    .file-cta\n      background-color: darken($file-cta-background-color-dark, 2.5%)\n      color: $file-cta-hover-color-dark\n    .file-name\n      border-color: darken($file-name-border-color-dark, 2.5%)\n  &:active\n    .file-cta\n      background-color: darken($file-cta-background-color-dark, 5%)\n      color: $file-cta-active-color-dark\n    .file-name\n      border-color: darken($file-name-border-color-dark, 5%)\n\n.file-cta,\n.file-name\n  border-color: $file-border-color-dark\n\n.file-cta\n  background-color: $file-cta-background-color-dark\n  color: $file-cta-color-dark\n\n.file-name\n  border-color: $file-name-border-color-dark\n\n.label\n  color: $label-color-dark\n\n.help\n  @each $name, $pair in $colors\n    $color: darken(nth($pair, 1), 10%)\n    &.is-#{$name}\n      color: $color\n  @each $name, $pair in $colors\n    $color: nth($pair, 1)\n    &.is-#{$name}-dark\n      color: $color\n\n// Containers\n\n.control\n  // Modifiers\n  &.has-icons-left,\n  &.has-icons-right\n    .icon\n      color: $input-icon-color-dark\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/elements/notification.sass",
    "content": "$notification-background-color-dark: $background-dark !default\n\n.notification\n  background-color: $notification-background-color-dark\n  code,\n  pre\n    background: $black\n  // Colors\n  @each $name, $pair in $colors\n    $color: darken(nth($pair, 1), 10%)\n    $color-invert: nth($pair, 2)\n    &.is-#{$name}\n      background-color: $color\n      color: $color-invert\n  // Colors Dark\n  @each $name, $pair in $colors\n    $color: nth($pair, 1)\n    $color-invert: nth($pair, 2)\n    &.is-#{$name}-dark\n      background-color: $color\n      color: $color-invert\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/elements/other.sass",
    "content": ".number\n  background-color: $background-dark\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/elements/progress.sass",
    "content": "$progress-bar-background-color-dark: $border-dark !default\n$progress-value-background-color-dark: $text-dark !default\n\n.progress\n  &::-webkit-progress-bar\n    background-color: $progress-bar-background-color-dark\n  &::-webkit-progress-value\n    background-color: $progress-value-background-color-dark\n  &::-moz-progress-bar\n    background-color: $progress-value-background-color-dark\n  &::-ms-fill\n    background-color: $progress-value-background-color-dark\n  &:indeterminate\n    background-color: $progress-bar-background-color-dark\n    background-image: linear-gradient(to right, $text 30%, $progress-bar-background-color-dark 30%)\n  // Colors\n  @each $name, $pair in $colors\n    $color: darken(nth($pair, 1), 10%)\n    &.is-#{$name}\n      &::-webkit-progress-value\n        background-color: $color\n      &::-moz-progress-bar\n        background-color: $color\n      &::-ms-fill\n        background-color: $color\n      &:indeterminate\n        background-image: linear-gradient(to right, $color 30%, $progress-bar-background-color-dark 30%)\n  // Colors Dark\n  @each $name, $pair in $colors\n    $color: nth($pair, 1)\n    &.is-#{$name}-dark\n      &::-webkit-progress-value\n        background-color: $color\n      &::-moz-progress-bar\n        background-color: $color\n      &::-ms-fill\n        background-color: $color\n      &:indeterminate\n        background-image: linear-gradient(to right, $color 30%, $progress-bar-background-color-dark 30%)\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/elements/table.sass",
    "content": "$table-color-dark: $grey-lighter !default\n$table-background-color-dark: $black !default\n\n$table-cell-border-dark: 1px solid $grey-darker !default\n$table-cell-heading-color-dark: $text-strong-dark !default\n\n$table-head-cell-color-dark: $text-strong-dark !default\n$table-foot-cell-color-dark: $text-strong-dark !default\n\n$table-row-hover-background-color-dark: $black-bis !default\n\n$table-row-active-background-color-dark: $primary-dark !default\n$table-row-active-color-dark: $primary-invert-dark !default\n\n$table-striped-row-even-background-color-dark: $black-bis !default\n$table-striped-row-even-hover-background-color-dark: $black-ter !default\n\n.table\n  background-color: $table-background-color-dark\n  color: $table-color-dark\n  td,\n  th\n    border: $table-cell-border-dark\n    // Colors\n    @each $name, $pair in $colors\n      $color: darken(nth($pair, 1), 10%)\n      $color-invert: nth($pair, 2)\n      &.is-#{$name}\n        background-color: $color\n        border-color: $color\n        color: $color-invert\n    // Colors Dark\n    @each $name, $pair in $colors\n      $color: nth($pair, 1)\n      $color-invert: nth($pair, 2)\n      &.is-#{$name}-dark\n        background-color: $color\n        border-color: $color\n        color: $color-invert\n    // Modifiers\n    &.is-selected\n      background-color: $table-row-active-background-color-dark\n      color: $table-row-active-color-dark\n  th\n    color: $table-cell-heading-color-dark\n  tr\n    &.is-selected\n      background-color: $table-row-active-background-color-dark\n      color: $table-row-active-color-dark\n      td,\n      th\n        border-color: $table-row-active-color-dark\n  thead\n    td,\n    th\n      color: $table-head-cell-color-dark\n  tfoot\n    td,\n    th\n      color: $table-foot-cell-color-dark\n  // Modifiers\n  &.is-hoverable\n    tbody\n      tr:not(.is-selected)\n        &:hover\n          background-color: $table-row-hover-background-color-dark\n    &.is-striped\n      tbody\n        tr:not(.is-selected)\n          &:hover\n            background-color: $table-row-hover-background-color-dark\n            &:nth-child(even)\n              background-color: $table-striped-row-even-hover-background-color-dark\n  &.is-striped\n    tbody\n      tr:not(.is-selected)\n        &:nth-child(even)\n          background-color: $table-striped-row-even-background-color-dark\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/elements/tag.sass",
    "content": "$tag-background-color-dark: $background-dark !default\n$tag-color-dark: $text-dark !default\n\n.tag:not(body)\n  background-color: $tag-background-color-dark\n  color: $tag-color-dark\n  // Colors\n  @each $name, $pair in $colors\n    $color: darken(nth($pair, 1), 10%)\n    $color-invert: nth($pair, 2)\n    &.is-#{$name}\n      background-color: $color\n      color: $color-invert\n  // Colors Dark\n  @each $name, $pair in $colors\n    $color: nth($pair, 1)\n    $color-invert: nth($pair, 2)\n    &.is-#{$name}-dark\n      background-color: $color\n      color: $color-invert\n  // Modifiers\n  &.is-delete\n    &:hover,\n    &:focus\n      background-color: darken($tag-background-color-dark, 5%)\n    &:active\n      background-color: darken($tag-background-color-dark, 10%)\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/elements/title.sass",
    "content": "$title-color-dark: $grey-lighter !default\n\n$subtitle-color-dark: $grey-light !default\n$subtitle-strong-color-dark: $grey-lighter !default\n\n.title\n  color: $title-color-dark\n\n.subtitle\n  color: $subtitle-color-dark\n  strong\n    color: $subtitle-strong-color-dark\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/layout/_all.sass",
    "content": "@charset \"utf-8\"\n\n@import \"hero.sass\"\n@import \"footer.sass\"\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/layout/footer.sass",
    "content": "$footer-background-color-dark: $black-bis !default\n\n.footer\n  background-color: $footer-background-color-dark\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/layout/hero.sass",
    "content": "// Main container\n\n.hero\n  // Colors\n  @each $name, $pair in $colors\n    $color: darken(nth($pair, 1), 10%)\n    $color-invert: nth($pair, 2)\n    &.is-#{$name}, &.is-#{$name}-dark\n      background-color: $color\n      color: $color-invert\n      a:not(.button):not(.dropdown-item):not(.tag),\n      strong\n        color: inherit\n      .title\n        color: $color-invert\n      .subtitle\n        color: rgba($color-invert, 0.9)\n        a:not(.button),\n        strong\n          color: $color-invert\n      .navbar-menu\n        +touch\n          background-color: $color\n      .navbar-item,\n      .navbar-link\n        color: rgba($color-invert, 0.7)\n      a.navbar-item,\n      .navbar-link\n        &:hover,\n        &.is-active\n          background-color: darken($color, 5%)\n          color: $color-invert\n      .tabs\n        a\n          color: $color-invert\n          opacity: 0.9\n          &:hover\n            opacity: 1\n        li\n          &.is-active a\n            opacity: 1\n        &.is-boxed,\n        &.is-toggle\n          a\n            color: $color-invert\n            &:hover\n              background-color: rgba($black, 0.1)\n          li.is-active a\n            &,\n            &:hover\n              background-color: $color-invert\n              border-color: $color-invert\n              color: $color\n      // Modifiers\n      &.is-bold\n        $gradient-top-left: darken(saturate(adjust-hue($color, -10deg), 10%), 10%)\n        $gradient-bottom-right: lighten(saturate(adjust-hue($color, 10deg), 5%), 5%)\n        background-image: linear-gradient(141deg, $gradient-top-left 0%, $color 71%, $gradient-bottom-right 100%)\n        +mobile\n          .navbar-menu\n            background-image: linear-gradient(141deg, $gradient-top-left 0%, $color 71%, $gradient-bottom-right 100%)\n      // Responsiveness\n      // +mobile\n      //   .nav-toggle\n      //     span\n      //       background-color: $color-invert\n      //     &:hover\n      //       background-color: rgba($black, 0.1)\n      //     &.is-active\n      //       span\n      //         background-color: $color-invert\n      //   .nav-menu\n      //     .nav-item\n      //       border-top-color: rgba($color-invert, 0.2)\n\n  // Colors dark\n  @each $name, $pair in $colors\n    $color: nth($pair, 1)\n    $color-invert: nth($pair, 2)\n    &.is-#{$name}-dark\n      background-color: $color\n      color: $color-invert\n      a:not(.button):not(.dropdown-item):not(.tag),\n      strong\n        color: inherit\n      .title\n        color: $color-invert\n      .subtitle\n        color: rgba($color-invert, 0.9)\n        a:not(.button),\n        strong\n          color: $color-invert\n      .navbar-menu\n        +touch\n          background-color: $color\n      .navbar-item,\n      .navbar-link\n        color: rgba($color-invert, 0.7)\n      a.navbar-item,\n      .navbar-link\n        &:hover,\n        &.is-active\n          background-color: darken($color, 5%)\n          color: $color-invert\n      .tabs\n        a\n          color: $color-invert\n          opacity: 0.9\n          &:hover\n            opacity: 1\n        li\n          &.is-active a\n            opacity: 1\n        &.is-boxed,\n        &.is-toggle\n          a\n            color: $color-invert\n            &:hover\n              background-color: rgba($black, 0.1)\n          li.is-active a\n            &,\n            &:hover\n              background-color: $color-invert\n              border-color: $color-invert\n              color: $color\n      // Modifiers\n      &.is-bold\n        $gradient-top-left: darken(saturate(adjust-hue($color, -10deg), 10%), 10%)\n        $gradient-bottom-right: lighten(saturate(adjust-hue($color, 10deg), 5%), 5%)\n        background-image: linear-gradient(141deg, $gradient-top-left 0%, $color 71%, $gradient-bottom-right 100%)\n        +mobile\n          .navbar-menu\n            background-image: linear-gradient(141deg, $gradient-top-left 0%, $color 71%, $gradient-bottom-right 100%)\n      // Responsiveness\n      // +mobile\n      //   .nav-toggle\n      //     span\n      //       background-color: $color-invert\n      //     &:hover\n      //       background-color: rgba($black, 0.1)\n      //     &.is-active\n      //       span\n      //         background-color: $color-invert\n      //   .nav-menu\n      //     .nav-item\n      //       border-top-color: rgba($color-invert, 0.2)\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/utilities/_all.sass",
    "content": "@charset \"utf-8\"\n\n@import \"initial-variables.sass\"\n@import \"derived-variables.sass\"\n@import \"mixins.sass\"\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/utilities/derived-variables.sass",
    "content": "$primary-dark: darken($turquoise, 10%) !default\n\n$info: darken($cyan, 10%) !default\n$success: darken($green, 10%) !default\n$warning: darken($yellow, 10%) !default\n$danger: darken($red, 10%) !default\n\n$light: $white-ter !default\n$dark: $grey-darker !default\n\n// Invert colors\n\n$orange-invert: findColorInvert($orange) !default\n$yellow-invert: findColorInvert($yellow) !default\n$green-invert: findColorInvert($green) !default\n$turquoise-invert: findColorInvert($turquoise) !default\n$cyan-invert: findColorInvert($cyan) !default\n$blue-invert: findColorInvert($blue) !default\n$purple-invert: findColorInvert($purple) !default\n$red-invert: findColorInvert($red) !default\n\n$primary-invert: $turquoise-invert !default\n$primary-invert-dark: darken($turquoise-invert, 10%) !default\n$info-invert: $cyan-invert !default\n$success-invert: $green-invert !default\n$warning-invert: $yellow-invert !default\n$danger-invert: $red-invert !default\n$light-invert: $dark !default\n$dark-invert: $light !default\n\n// General colors\n\n$background: $white-ter !default\n$background-dark: $black-ter !default\n\n$border-dark: $grey-darker !default\n$border-hover-dark: $grey-dark !default\n\n// Text colors\n\n$text-dark: $grey-light !default\n$text-invert: findColorInvert($text) !default\n$text-invert-dark: findColorInvert($text-dark) !default\n$text-light: $grey !default\n$text-strong-dark: $grey-lighter !default\n\n// Code colors\n\n$code-dark: darken($red, 15%) !default\n$code-background-dark: $background-dark !default\n\n$pre-dark: $text-dark !default\n$pre-background-dark: $background-dark !default\n\n// Link colors\n\n$link-dark: $blue-light !default\n$link-invert-dark: $blue-invert !default\n\n$link-hover-dark: $grey-lighter !default\n$link-hover-border-dark: $grey-dark !default\n\n$link-focus-dark: $grey-lighter !default\n$link-focus-border-dark: $blue-light !default\n\n$link-active-dark: $grey-lighter !default\n$link-active-border-dark: $grey-light !default\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/utilities/initial-variables.sass",
    "content": "// Colors\n\n$blue-light:   hsl(209, 71%,  63%) !default\n\n// Background\n\n$body-background-dark: #17181c !default\n"
  },
  {
    "path": "guide/style/bulma-prefers-dark/sass/utilities/mixins.sass",
    "content": "// Color schemes\n\n=prefers-scheme($scheme)\n  @media (prefers-color-scheme: $scheme)\n    @content\n"
  },
  {
    "path": "guide/style/colors.scss",
    "content": "$sanic: #ff0d68;\n$primary: $sanic;\n$link: $sanic;\n$blue: #0092FF;\n$cyan: $blue;\n$green: #16DB93;\n$yellow: #FFE900;\n\n$menu-item-active-background-color: $sanic;\n\n\n$green_1: #37ae6f;\n$green_2: #16DB93;\n$purple: #833FE3;\n$dark_1: #1e2024;\n$orange: #d98404;\n\n\n.has-text-purple { color: $purple; }\n"
  },
  {
    "path": "guide/style/elements.scss",
    "content": ".footer {\n    a[target=_blank]::after { display: none; }\n    margin-bottom: 4rem;\n}\n@media screen and (max-width: $desktop) {\n    .footer .level { align-items: baseline; }\n    .footer a { font-size: 0.75em; }\n}\n.hero {\n    .subtitle {\n        font-size: 3rem;\n        font-weight: 700;\n        margin-bottom: 1rem;\n    }\n    .tagline {\n        font-family: Fira Code,Source Code Pro,Menlo,Monaco,Consolas,Lucisa Console,monospace;\n        font-size: 2rem;\n        font-weight: 300;\n        margin-bottom: 2rem\n    }\n}\n\n.language-sh {\n    code {\n        display: inline-block;\n        &::before {\n            content: '▶ '\n        }\n\n    }\n}\n\n.notification {\n    margin-top: 2rem;\n    .notification-title {\n        font-size: 1.25rem;\n        font-weight: 700;\n    }\n    &.is-note {\n        background-color: $primary;\n        color: $white;\n    }\n    &.is-new {\n        background-color: $purple;\n        color: $white;\n        &::after {\n            content: '🌟';\n            position: absolute;\n            top: 1rem;\n            right: 1rem;\n            font-size: 2rem;\n        }\n    }\n    &.is-tip {\n        &::after {\n            content: '💡';\n            position: absolute;\n            top: 1rem;\n            right: 1rem;\n            font-size: 2rem;\n        }\n    }\n}\n\n.ol, .ul,\n.list {\n    margin: 1rem 0;\n    .li,\n    .list-item {\n        padding: 0.5rem 0;\n    }\n}\n.ul .li {\n    margin-left: 1.5rem;\n    list-style-type: disc;\n}\n.introduction-table {\n    margin: 2rem 0;\n    a[target=_blank]::after { display: none; }\n    th { display: none; }\n}\n\n.docobject {\n    h2 :last-child { color: $dark; }\n    .function-signature {\n        color: $dark_1;\n\n        .param-name {\n            color: $primary;\n            font-weight: bold;\n        }\n        .param-default {\n            color: $purple;\n            font-style: italic;\n        }\n        .function-decorator {\n            font-style: italic;\n            color: $grey\n        }\n        .param-annotation { color: $blue; }\n        .return-annotation { color: $green_1; }\n\n    }\n\n    dl {\n        display: flex;\n        flex-wrap: wrap;\n        margin: 0;\n        padding: 0;\n    }\n    dt {\n        flex: 0 0 25%;\n        padding: 5px 10px;\n        font-weight: bold;\n        color: $blue;\n    }\n    dd {\n        flex: 1;\n        padding: 5px 10px;\n        margin: 0;\n    }\n\n    div.highlight + p,\n    p + div.highlight,\n    div.highlight + div.highlight { margin-top: 1rem; }\n\n    .method {\n        padding-left: 1rem;\n\n        h3 { margin-left: -1rem; }\n    }\n\n    .ol, .ul, .list { margin: 1rem; }\n}\n\n.mermaid {\n    margin-top: 2rem;\n    .actor {\n        stroke: $primary !important;\n        fill: lighten($primary, 40%) !important;\n    }\n    .labelBox {\n        fill: lighten($blue, 40%) !important;\n        stroke: $blue !important;\n    }\n    // .labelText {\n    //     fill: $white-bis !important;\n    // }\n    .note {\n        fill: lighten($yellow, 40%) !important;\n        stroke: $yellow !important;\n    }\n}\n\n@media (prefers-color-scheme: dark) {\n    .docobject {\n        h2 :last-child { color: $white-bis; }\n        .function-signature {\n            color: $grey-light;\n            .param-default { color: $yellow; }\n        }\n    }\n    .mermaid {\n        text.messageText { fill: $white-bis !important; }\n        .actor { fill: $primary !important; }\n        .labelBox { fill: $blue !important; }\n        .labelText { fill: $white-bis !important; }\n        .note { fill: $yellow !important; }\n    }\n}\n\n$button-width: 36px;\n$button-height: 52px; // 36 x 52 for portrait paper ratio\n$rectangle-width: $button-width / 2;\n$rectangle-height: $rectangle-width / 8.5 * 11;\n$left-offset-filled: 5px;\n$top-offset-filled: 13px;\n$left-offset-outlined: -3px;\n$top-offset-outlined: 5px;\n$animation-slide: -$button-width / 1.5; // space for sliding and the gap\n\nh1 + .code-block,\nh2 + .code-block,\nh3 + .code-block { margin-top: 1rem; }\n.code-block {\n  position: relative;\n  & + .code-block { margin-top: 1rem; }\n\n  &:hover {\n    .code-block__copy {\n      opacity: 1;\n    }\n  }\n\n  .code-block__copy {\n    position: absolute;\n    right: 10px;\n    bottom: 10px;\n    width: $button-width;\n    height: $button-height;\n    cursor: pointer;\n    opacity: 0;\n    transition: all 0.3s;\n\n    &::before {\n        content: \"copied\";\n        position: absolute;\n        top: -$button-height / 3;\n        margin: auto;\n        opacity: 0;\n        right: $button-width / 4;\n    }\n    &.clicked::before {\n        opacity: 1;\n        animation: all 0.3s ease-in-out;\n    }\n  }\n\n  .code-block__rectangle {\n    position: absolute;\n    width: $rectangle-width;\n    height: $rectangle-height;\n    transition: all 0.3s ease;\n  }\n\n  .code-block__filled {\n    background-color: $primary;\n    left: $left-offset-filled;\n    top: $top-offset-filled;\n  }\n\n  .code-block__outlined {\n    border: 2px solid $primary;\n    background-color: transparent;\n    left: $left-offset-outlined;\n    top: $top-offset-outlined;\n  }\n\n  .code-block__copy.clicked {\n    .code-block__outlined {\n      left: $left-offset-filled + $animation-slide;\n      top: $top-offset-filled;\n      background-color: $primary;\n    }\n  }\n}\n\n.additional-attributes.details {\n  display: flex;\n  flex-direction: column;\n  width: 100%;\n\n  .code-block {\n    display: none;\n    width: 100%;\n  }\n\n  &::before {\n    content: \"▼ \" attr(title);\n    display: block;\n    background-color: $grey-light;\n    padding: 10px;\n    cursor: pointer;\n    width: 100%;\n    box-sizing: border-box;\n  }\n\n  &.is-active {\n    .code-block {\n      display: block;\n    }\n\n    &::before {\n      content: \"▲ \" attr(title);\n      background-color: $grey-lighter;\n    }\n  }\n}\n\n// dark mode\n@media (prefers-color-scheme: dark) {\n    .additional-attributes.details{\n        &::before { background-color: $grey-darker; }\n        &.is-active {\n            &::before { background-color: $grey-dark; }\n        }\n    }\n}\n\n.tabs {\n    .tab-content { display: none; }\n}\n\n.table-of-contents {\n  position: fixed;\n  right: 0;\n  bottom: 0;\n  z-index: 1000;\n  max-width: 500px;\n  padding: 1rem 2rem;\n  background-color: $white-bis;\n  box-shadow: 0 0 2px rgba(63, 63, 68, 0.5);\n\n  @media (prefers-color-scheme: dark) {\n      background-color: $black;\n      box-shadow: 0 0 2px rgba(191, 191, 191, 0.5);\n      \n  }\n\n  .table-of-contents-item {\n    display: block;\n    margin-bottom: 0.5rem;\n    text-decoration: none;\n\n    &:hover {\n\ttext-decoration: underline;\n\tcolor: $primary;\n\n\tstrong, small {\n\t    color: $primary;\n\t}\n    }\n\n    strong {\n\tcolor: $black-bis;\n\tfont-size: 1.15em;\n\tdisplay: block;\n\tline-height: 1rem;\n\tmargin-top: 0.75rem;\n    }\n\n    small {\n\tcolor: $grey;\n\tfont-size: 0.85em;\n    }\n\n    @media (prefers-color-scheme: dark) {\n\tstrong { color: $grey-lighter; }\n    }\n  }\n\n  @media (max-width: 768px) {\n    position: static;\n    max-width: calc(100vw - 2rem);\n\n    .table-of-contents-item {\n\tdisplay: flex;\n\tflex-direction: row-reverse;\n\tjustify-content: start;\n\t\n\tstrong {\n\t    display: inline;\n\t    margin: 0 0 0 0.75rem;\n\t}\n    }\n  }\n}\n\n.loading-bar {\n  position: fixed;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 3px;\n  background-color: transparent;\n  z-index: 9999;\n\n  &.is-loading {\n    background-color: $primary;\n    animation: pulsingLoading 1s linear infinite;\n  }\n}\n\n@keyframes pulsingLoading {\n  0%, 100% {\n    transform: scaleX(1);\n    opacity: 1;\n  }\n  50% {\n    transform: scaleX(0.25);\n    opacity: 0.5;\n  }\n}\n.changelog {\n    .ol, .ul, .list {\n\t.li, .list-item { padding: 0; }\n    }\n}\n\n.sponsors {\n    display: flex;\n    flex-wrap: wrap;\n    justify-content: center;\n    text-align: center;\n    padding: 0.25rem 0;\n    .button { margin-left: 1rem; }\n}\n"
  },
  {
    "path": "guide/style/general.scss",
    "content": "code { color: #{$grey-dark}; }\n.notification code { background-color: #{rgba($grey-lightest, 0.6)}; }\n@media (prefers-color-scheme: dark) {\n    code { color: #{$grey-lighter}; }\n    .notification code {\n        background-color: #{rgba($black-ter, 0.6)};\n    }\n}\n\na{\n    &[target=_blank]::after {\n        content: \"⇗\";\n        margin-left: 0.25em;\n    }\n    & > code {\n        border-bottom: 1px solid $primary;\n        &:hover {\n            background-color: $primary;\n            color: $white;\n        }\n    }\n}\nh1 a.anchor,\nh2 a.anchor,\nh3 a.anchor,\nh4 a.anchor,\nh5 a.anchor,\nh6 a.anchor {\n    display: none;\n    padding-left: 0.375em;\n}\nh1:hover a.anchor,\nh2:hover a.anchor,\nh3:hover a.anchor,\nh4:hover a.anchor,\nh5:hover a.anchor,\nh6:hover a.anchor {\n    display: inline;\n}\n\nh1 { margin-left: -2rem; }\nh2 { margin-left: -1rem; margin-top: 3rem; }\nh3:not(:first-child) { margin-top: 2rem; }\narticle { margin-left: 2rem; }\np + pre,\npre + p,\ndiv.highlight + p,\np + div.highlight,\ndiv.highlight + div.highlight,\np + h4,\np + .code-block,\n.code-block + p,\n.code-block + .code-block,\np + p { margin-top: 1rem; }\n\n@media screen and (max-width: $tablet) {\n    html { font-size: 18px; }\n    h1 { margin-left: 0; }\n    h2 { margin-left: 0; }\n    article { margin-left: 0; }\n    .section {\n\tpadding: 1rem 0rem;\n\tmax-width: 95vw;\n    }\n}\n@media screen and (min-width: $widescreen) {\n    .section {\n\tpadding: 3rem 0rem;\n    }\n}\n@media screen and (min-width: $widescreen) {\n    .section {\n\tpadding: 3rem 0rem;\n    }\n}\n"
  },
  {
    "path": "guide/style/home.scss",
    "content": "@media screen and (min-width: 769px) {\n    .hero.is-large .hero-body {\n\tpadding: 18rem 6rem 3rem;\n    }\n}\n@media screen and (max-width: 769px) {\n    .hero {\n        height: 100vh;\n\tmargin-top: 15vh;\n\n        .hero-body {\n            padding: 3rem 1.5rem;\n            display: flex;\n            flex-direction: column;\n            justify-content: center;\n        }\n        & .title::after {\n            width: calc(100vw - 1.5rem);\n            background: url(/assets/images/logo-white.svg) no-repeat center center;\n            background-size: 100% auto;\n        }\n\t.subtitle {\n\t    margin-top: 1rem;\n\t    font-size: 1.4rem;\n\t}\n\t.tagline {\n\t    font-size: 1.1rem;\n\t}\n    }\n}\n.home .tab-container {\n  display: flex;\n\n  .tabs {\n    flex-shrink: 0; // Prevent the tabs from shrinking\n    width: 200px; // Fixed width for the tabs\n    ul {\n      display: flex;\n      flex-direction: column;\n      border-bottom: none; // Remove the border between tabs\n    }\n    li {\n\tdisplay: block;\n\twidth: 100%;\n      a {\n        display: block;\n        padding: 0.5em 1em; // Adjust padding as needed\n        text-align: left; // Align text to the left\n      }\n    }\n  }\n\n  .tab-display {\n    flex-grow: 1; // Allow the content to grow as needed\n    min-width: 0; // Allows the flex item to shrink below its content size\n    padding-left: 20px; // Adjust the spacing between tabs and content as needed\n  }\n\n  @media screen and (max-width: 768px) {\n    flex-direction: column;\n\n    .tabs {\n      width: 100%; // Tabs take full width for mobile\n      ul {\n        display: block; // Vertical tabs\n        overflow-y: auto; // For scrollable tabs vertically\n      }\n      li {\n        display: block; // Keep tabs vertical\n      }\n    }\n\n    .tab-display {\n      padding-left: 0; // Reset padding for mobile view\n    }\n  }\n}\n"
  },
  {
    "path": "guide/style/index.scss",
    "content": "$body-size: 20px;\n\n@import \"./colors.scss\";\n@import \"./bulma/bulma.sass\";\n@import \"./bulma-prefers-dark/bulma-prefers-dark.sass\";\n@import \"./theme.scss\";\n@import \"./menu.scss\";\n@import \"./general.scss\";\n@import \"./elements.scss\";\n@import \"./home.scss\";\n@import \"./overrides.scss\";\n"
  },
  {
    "path": "guide/style/menu.scss",
    "content": "$arrow-size: 8px;\n$burger-size: 2rem;\n$menu-width: 360px;\n.burger {\n    display: none;\n    position: fixed;\n    top: 1rem;\n    right: $burger-size / 2;\n    width: $burger-size;\n    height: $burger-size;\n    cursor: pointer;\n    z-index: 101;\n    span {\n        display: block;\n        position: absolute;\n        height: 2px;\n        width: 100%;\n        background: var(--menu-contrast);\n        border-radius: 2px;\n        opacity: 1;\n        left: 0;\n        transform: rotate(0deg);\n        transition: .25s ease-in-out;\n        &:nth-child(1) { top: 0px; }\n        &:nth-child(2), &:nth-child(3) { top: 8px; }\n        &:nth-child(4) { top: 16px; }\n    }\n    &.is-active {\n        span {\n            &:nth-child(1) {\n                top: 18px;\n                width: 0%;\n                left: 50%;\n            }\n            &:nth-child(2) {\n                transform: rotate(45deg);\n            }\n            &:nth-child(3) {\n                transform: rotate(-45deg);\n            }\n            &:nth-child(4) {\n                top: 18px;\n                width: 0%;\n                left: 50%;\n            }\n        }\n    }\n    &::after {\n\tcontent: '';\n\tdisplay: block;\n\tposition: absolute;\n\ttop: -$burger-size / 2;\n\tleft: -$burger-size / 4;\n\twidth: $burger-size * 1.5;\n\theight: $burger-size * 1.5;\n\tbackground: var(--menu-background);\n\tz-index: -1;\n    }\n}\n.menu {\n    background-color: var(--menu-background);\n    width: $menu-width;\n    padding: 2rem;\n    height: 100vh;\n    overflow: auto;\n    position: fixed;\n    top: 0;\n    left: 0;\n    z-index: 100;\n\n    hr { background-color: var(--menu-divider); }\n\n    .is-anchor {\n        font-size: 0.75em;\n        & a::before {\n            content: '# ';\n            color: var(--menu-contrast);\n        }\n    }\n    .menu-label { margin-bottom: 1rem; }\n    li.is-group > a {\n\tfont-size: 0.85rem;\n        &::after {\n            content: '';\n            position: relative;\n            top: -$arrow-size / 4;\n            left: $arrow-size;\n            display: inline-block;\n            width: 0;\n            height: 0;\n            border-left: $arrow-size solid var(--menu-contrast);\n            border-top: $arrow-size * 2/3 solid transparent;\n            border-bottom: $arrow-size * 2/3 solid transparent;\n            transform: rotate(90deg);\n        }\n        & ~ .menu-list {\n            transition: all .15s ease-in-out;\n            transform: scaleY(1);\n            transform-origin: top;\n            overflow: hidden;\n            opacity: 1;\n            margin: 0;\n        }\n        &:not(.is-open) {\n            &::after { transform: rotate(0deg); }\n            & ~ .menu-list {\n                transform: scaleY(0);\n                opacity: 0;\n                font-size: 0;\n            }\n        }\n    }\n\n    & ~ main { margin-left: $menu-width; }\n    .anchor-list {\n        display: none;\n    }\n    .menu-item .is-active + .anchor-list {\n        display: block;\n    }\n}\n\n// On mobile breakpoints, we want to hide the menu and show the burger\n@media screen and (max-width: $desktop) {\n    .menu {\n        left: -100vw;\n        width: 100vw;\n        transition: all .25s ease-in-out;\n        &.is-active { left: 0; }\n        & ~ main { margin-left: 0; }\n    }\n    .burger { display: block; }\n}\n\n.menu-list li ul {\n    margin: 0;\n    padding-left: 0;\n}\n.menu-list ul li:not(.is-anchor) {\n    margin-left: 0.75em;\n}\n.menu-list .menu-list li a {\n    font-size: 0.85em;\n}\n"
  },
  {
    "path": "guide/style/overrides.scss",
    "content": "footer .level,\nfooter .level .level-right,\nfooter .level .level-left {\n    display: flex !important;\n}\n.tabs li a { font-size: 0.7rem; }\n.box {\n    box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1);\n}\n.container { padding: 0 0.5rem; }\n@media (prefers-color-scheme: dark) {\n    .box {\n        box-shadow: 0 2px 3px rgba(128, 128, 128, 0.1), 0 0 0 1px rgba(128, 128, 128, 0.1);\n    }\n}\n"
  },
  {
    "path": "guide/style/theme.scss",
    "content": "$background-image-size: 270px;\n.hero .title {\n    position: relative;\n        span { display: none; }\n        &::after {\n            content: \"\";\n            position: absolute;\n            top: -108px;\n            left: 0;\n            right: 0;\n            margin: auto;\n            width: 500px;\n            height: 122px;\n            background: url(/assets/images/logo.svg) no-repeat center center;\n        }\n\n}\n\n.sanic-simple-logo {\n    background: url(/assets/images/logo.svg) no-repeat;\n    background-size: $background-image-size;\n    height: 72px;\n\n    & img { visibility: hidden; }\n}\n:root {\n    --menu-background: #{$grey-lightest};\n    --menu-divider: #{$grey-lighter};\n    --menu-contrast: #{$black-bis};\n}\n\n.c1 { color: #{$grey}; }\n\n@media (prefers-color-scheme: dark) {\n    :root {\n        --menu-background: #{$black};\n        --menu-divider: #{$black-ter};\n        --menu-contrast: #{$grey};\n    }\n    html,\n    .navbar { background-color: #{$black-bis}; }\n    .footer { background-color: #{$black}; }\n    .sanic-simple-logo {\n        background: url(/assets/images/logo-white.svg) no-repeat;\n        background-size: $background-image-size;\n    }\n    .hero .title::after {\n        background: url(/assets/images/logo-white.svg) no-repeat center center;\n    }\n    .list {\n        background-color: transparent;\n        box-shadow: none;\n    }\n    .n,.na,.nb,.no,.nd,.ni,.ne,.nl,\n    .nn,.nx,.py,.nt,.nv,.bp,.vc,.vg,\n    .vi,.vm { color: #{$grey-light}; }\n    .s,.sa,.sb,.sc,.dl,.sd,.s2,.se,\n    .sh,.si,.sx,.sr,.s1,.ss{\n        background-color: transparent;\n        color: #{$green};\n    }\n    .nc { color: #{$yellow}; }\n    .c1 { color: #{$grey-dark}; }\n    .introduction-table .table tbody tr:last-child td { border-bottom-width: 1px; }\n\n    $scrollbar-width: 12px;\n    ::-webkit-scrollbar { width: $scrollbar-width; height: $scrollbar-width; }\n    ::-webkit-scrollbar-track { background: #{$black-ter}; }\n    ::-webkit-scrollbar-thumb {\n\tbackground: #{$black-bis};\n\tborder-radius: 6px;\n\tborder: 3px solid #{$black-ter};\n    }\n    ::-webkit-scrollbar-thumb:hover { background: #{$black}; }\n}\n"
  },
  {
    "path": "guide/webapp/__init__.py",
    "content": ""
  },
  {
    "path": "guide/webapp/display/__init__.py",
    "content": ""
  },
  {
    "path": "guide/webapp/display/base.py",
    "content": "from __future__ import annotations\n\nfrom os import environ\n\nfrom html5tagger import Builder, Document, E  # type: ignore\n\n\nclass BaseRenderer:\n    def __init__(self, base_title: str):\n        self.base_title = base_title\n\n    def get_builder(self, full: bool, language: str) -> Builder:\n        if full:\n            urls = [\n                \"/assets/code.css\",\n                \"/assets/style.css\",\n                \"/assets/docs.js\",\n                \"https://unpkg.com/htmx.org@1.9.2/dist/htmx.min.js\",\n                \"https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js\",\n            ]\n            builder = Document(\n                self.title(), lang=language, _urls=urls, _viewport=True\n            )\n            builder(*self._head())\n            builder.full = True\n        else:\n            builder = Builder(name=\"Partial\")\n            builder.full = False\n        return builder\n\n    def title(self) -> str:\n        return self.base_title\n\n    def _head(self) -> list[Builder]:\n        head = [\n            E.meta(name=\"theme-color\", content=\"#ff0d68\"),\n            E.meta(name=\"title\", content=self.title()),\n            E.meta(\n                name=\"description\",\n                content=(\n                    \"Sanic is a Python 3.10+ web server and \"\n                    \"web framework that's written to go fast.\"\n                ),\n            ),\n            E.link(rel=\"icon\", href=\"/favicon.ico\", sizes=\"any\"),\n            E.link(rel=\"icon\", href=\"/favicon-32x32.png\", type=\"image/png\"),\n            E.link(rel=\"icon\", href=\"/favicon-16x16.png\", type=\"image/png\"),\n            E.link(\n                rel=\"apple-touch-icon\",\n                sizes=\"180x180\",\n                href=\"/apple-touch-icon.png\",\n            ),\n            E.link(rel=\"manifest\", href=\"/site.webmanifest\"),\n            E.link(\n                rel=\"android-chrome\",\n                sizes=\"192x192\",\n                href=\"/android-chrome-192x192.png\",\n            ),\n            E.link(\n                rel=\"android-chrome\",\n                sizes=\"512x512\",\n                href=\"/android-chrome-512x512.png\",\n            ),\n            E.meta(name=\"msapplication-config\", content=\"/browserconfig.xml\"),\n            E.meta(name=\"msapplication-TileColor\", content=\"#ffffff\"),\n            E.meta(\n                name=\"msapplication-TileImage\", content=\"/mstile-144x144.png\"\n            ),\n            E.meta(name=\"theme-color\", content=\"#ff0d68\"),\n            E.link(\n                rel=\"mask-icon\", href=\"/safari-pinned-tab.svg\", color=\"#ff0d68\"\n            ),\n        ]\n        umami = E.script(\n            None,\n            async_=True,\n            defer=True,\n            data_website_id=\"0131e426-4d6d-476b-a84b-34a45e0be6de\",\n            src=\"https://analytics.sanicframework.org/umami.js\",\n        )\n        if environ.get(\"UMAMI\"):\n            head.append(umami)\n        return head\n"
  },
  {
    "path": "guide/webapp/display/code_style.py",
    "content": "from pygments.style import Style\nfrom pygments.token import (  # Error,; Generic,; Number,; Operator,\n    Comment,\n    Keyword,\n    Name,\n    String,\n    Token,\n)\n\n\nclass SanicCodeStyle(Style):\n    styles = {\n        Token: \"#777\",\n        Comment: \"italic #a2a2a2\",\n        Keyword: \"#ff0d68\",\n        Name: \"#333\",\n        Name.Class: \"bold #37ae6f\",\n        Name.Function: \"#0092FF\",\n        String: \"bg:#eee #833FE3\",\n    }\n"
  },
  {
    "path": "guide/webapp/display/layouts/__init__.py",
    "content": ""
  },
  {
    "path": "guide/webapp/display/layouts/base.py",
    "content": "from __future__ import annotations\n\nfrom collections.abc import Generator\nfrom contextlib import contextmanager\n\nfrom html5tagger import Builder\n\nfrom sanic import Request\n\n\nclass BaseLayout:\n    def __init__(self, builder: Builder):\n        self.builder = builder\n\n    @contextmanager\n    def __call__(\n        self, request: Request, full: bool = True\n    ) -> Generator[BaseLayout, None, None]:\n        with self.layout(request, full=full):\n            yield self\n\n    @contextmanager\n    def layout(\n        self, request: Request, full: bool = True\n    ) -> Generator[None, None, None]:\n        yield\n"
  },
  {
    "path": "guide/webapp/display/layouts/elements/__init__.py",
    "content": ""
  },
  {
    "path": "guide/webapp/display/layouts/elements/footer.py",
    "content": "from collections import deque\nfrom datetime import datetime\n\nfrom html5tagger import Builder, E  # type: ignore\n\nfrom sanic import Request\n\n\ndef do_footer(\n    builder: Builder,\n    request: Request,\n    extra_classes: str = \"\",\n    with_pagination: bool = True,\n) -> None:\n    content = deque([_content()])\n    if with_pagination:\n        content.appendleft(_pagination(request))\n    css_classes = \"footer\"\n    if extra_classes:\n        css_classes += f\" {extra_classes}\"\n    builder.footer(\n        *content,\n        class_=css_classes,\n    )\n\n\ndef _pagination(request: Request) -> Builder:\n    return E.div(\n        _pagination_left(request), _pagination_right(request), class_=\"level\"\n    )\n\n\ndef _pagination_left(request: Request) -> Builder:\n    item = E.div(class_=\"level-item\")\n    if not hasattr(request.ctx, \"previous_page\"):\n        return E.div(item, class_=\"level-left\")\n    with item:\n        if p := request.ctx.previous_page:\n            path = p.relative_path.with_suffix(\".html\")\n            item.a(\n                f\"← {p.meta.title}\",\n                href=f\"/{path}\",\n                hx_get=f\"/{path}\",\n                hx_target=\"#content\",\n                hx_swap=\"innerHTML\",\n                hx_push_url=\"true\",\n                class_=\"button pagination\",\n            )\n    return E.div(item, class_=\"level-left\")\n\n\ndef _pagination_right(request: Request) -> Builder:\n    item = E.div(class_=\"level-item\")\n    if not hasattr(request.ctx, \"next_page\"):\n        return E.div(item, class_=\"level-right\")\n    with item:\n        if p := request.ctx.next_page:\n            path = p.relative_path.with_suffix(\".html\")\n            item.a(\n                f\"{p.meta.title} →\",\n                href=f\"/{path}\",\n                hx_get=f\"/{path}\",\n                hx_target=\"#content\",\n                hx_swap=\"innerHTML\",\n                hx_push_url=\"true\",\n                class_=\"button pagination\",\n            )\n    return E.div(item, class_=\"level-right\")\n\n\ndef _content() -> Builder:\n    year = datetime.now().year\n    legal = E.p(\n        E.a(\n            \"MIT Licensed\",\n            href=\"https://github.com/sanic-org/sanic/blob/master/LICENSE\",\n            target=\"_blank\",\n            rel=\"nofollow noopener noreferrer\",\n        ).br()(\n            E.small(f\"Copyright © 2018-{year} Sanic Community Organization\")\n        ),\n    )\n    powered = E.p(\n        E.a(\"This site is powered\", href=\"/en/built-with-sanic.html\"),\n        E.img(\n            src=\"/assets/images/sanic-framework-logo-circle-32x32.png\",\n            alt=\"Sanic Logo\",\n            style=\"vertical-align: middle;\",\n            class_=\"ml-1\",\n        ),\n    )\n    with_love = E.p(\"~ Made with ❤️ and ☕️ ~\")\n    return E.div(\n        legal,\n        powered,\n        with_love,\n        class_=\"content has-text-centered\",\n    )\n"
  },
  {
    "path": "guide/webapp/display/layouts/elements/navbar.py",
    "content": "from html5tagger import Builder, E  # type: ignore\n\nfrom sanic import Request\nfrom webapp.display.layouts.models import MenuItem\n\n\ndef do_navbar(builder: Builder, request: Request) -> None:\n    navbar_items = [\n        _render_navbar_item(item, request)\n        for item in request.app.config.NAVBAR\n    ]\n    container = E.div(\n        _search_form(request), *navbar_items, class_=\"navbar-end\"\n    )\n\n    builder.nav(\n        E.div(container, class_=\"navbar-menu\"),\n        class_=\"navbar is-hidden-touch\",\n    )\n\n\ndef _search_form(request: Request) -> Builder:\n    return E.div(\n        E.div(\n            E.input(\n                id_=\"search\",\n                type_=\"text\",\n                placeholder=\"Search\",\n                class_=\"input\",\n                value=request.args.get(\"q\", \"\"),\n                hx_target=\"#content\",\n                hx_swap=\"innerHTML\",\n                hx_push_url=\"true\",\n                hx_trigger=\"keyup changed delay:500ms\",\n                hx_get=f\"/{request.ctx.language}/search\",\n                hx_params=\"*\",\n            ),\n            class_=\"control\",\n        ),\n        class_=\"navbar-item\",\n    )\n\n\ndef _render_navbar_item(item: MenuItem, request: Request) -> Builder:\n    if item.items:\n        return E.div(\n            E.a(item.label, class_=\"navbar-link\"),\n            E.div(\n                *(\n                    _render_navbar_item(subitem, request)\n                    for subitem in item.items\n                ),\n                class_=\"navbar-dropdown\",\n            ),\n            class_=\"navbar-item has-dropdown is-hoverable\",\n        )\n\n    kwargs = {\n        \"class_\": \"navbar-item\",\n    }\n    if item.href:\n        kwargs[\"href\"] = item.href\n        kwargs[\"target\"] = \"_blank\"\n        kwargs[\"rel\"] = \"nofollow noopener noreferrer\"\n    elif item.path:\n        kwargs[\"href\"] = f\"/{request.ctx.language}/{item.path}\"\n    internal = [item.label]\n    return E.a(*internal, **kwargs)\n"
  },
  {
    "path": "guide/webapp/display/layouts/elements/sidebar.py",
    "content": "from html5tagger import Builder, E  # type: ignore\n\nfrom sanic import Request\nfrom webapp.display.layouts.models import MenuItem\nfrom webapp.display.text import slugify\n\n\ndef do_sidebar(builder: Builder, request: Request) -> None:\n    builder.a(class_=\"burger\")(E.span().span().span().span())\n    builder.aside(*_menu_items(request), class_=\"menu\")\n\n\ndef _menu_items(request: Request) -> list[Builder]:\n    return [\n        _sanic_logo(request),\n        *_sidebar_items(request),\n        E.hr(),\n        E.p(\"Current with version \").strong(\n            request.app.config.GENERAL.current_version\n        ),\n        E.hr(),\n        E.ul.li(\"Need \").a(\"help\", href=f\"/{request.ctx.language}/help.html\")(\n            \"?\"\n        ),\n        E.li(\"How we \").a(\n            \"built this site w/ Sanic\",\n            href=f\"/{request.ctx.language}/built-with-sanic.html\",\n        ),\n        E.li(\"The \").a(\n            \"Awesome Sanic\",\n            href=\"https://github.com/mekicha/awesome-sanic\",\n            target=\"_blank\",\n        )(\" list\"),\n        E.hr(),\n        E.p(\"Want more? \")\n        .a(\"sanicbook.com\", href=\"https://sanicbook.com\", target=\"_blank\")\n        .br.img(src=\"https://sanicbook.com/images/SanicCoverFinal.png\"),\n        E.br.small(\"Book proceeds fund our journey\"),\n        E.hr(),\n        E.p(\"Secure, auto-document, and monetize your Sanic API with:\").a(\n            E.img(\n                src=\"/assets/images/zuplo.svg\",\n                alt=(\n                    \"Zuplo - Secure, auto-document, \"\n                    \"and monetize your Sanic API\"\n                ),\n                style=\"width: 90%;\",\n            ),\n            href=\"https://zuplo.com\",\n            target=\"_blank\",\n            rel=\"nofollow noopener noreferrer\",\n        ),\n    ]\n\n\ndef _sanic_logo(request: Request) -> Builder:\n    return E.a(\n        class_=\"navbar-item sanic-simple-logo my-3\",\n        href=f\"https://sanic.dev/{request.ctx.language}/\",\n    )(\n        E.img(\n            src=\"/assets/images/sanic-framework-logo-simple-400x97.png\",  # noqa: E501\n            alt=\"Sanic Framework\",\n        )\n    )\n\n\ndef _sidebar_items(request: Request) -> list[Builder]:\n    return [\n        builder\n        for item in request.app.config.SIDEBAR\n        for builder in _render_sidebar_item(item, request, True)\n    ]\n\n\ndef _render_sidebar_item(\n    item: MenuItem, request: Request, root: bool = False\n) -> list[Builder]:\n    builders: list[Builder] = []\n    if root:\n        builders.append(E.p(class_=\"menu-label\")(item.label))\n    else:\n        builders.append(_single_sidebar_item(item, request))\n\n    if item.items:\n        ul = E.ul(class_=\"menu-list\")\n        with ul:\n            for subitem in item.items:\n                sub_builders = _render_sidebar_item(subitem, request)\n                ul(*sub_builders)\n        builders.append(ul)\n\n    return builders\n\n\ndef _single_sidebar_item(item: MenuItem, request: Request) -> Builder:\n    if item.path and item.path.startswith(\"/\"):\n        path = item.path\n    else:\n        path = f\"/{request.ctx.language}/{item.path}\" if item.path else \"\"\n    kwargs = {}\n    classes: list[str] = []\n    li_classes = \"menu-item\"\n    _, page, _ = request.app.ctx.get_page(\n        request.ctx.language, item.path or \"\"\n    )\n    if request.path == path:\n        classes.append(\"is-active\")\n    if item.href:\n        kwargs[\"href\"] = item.href\n        kwargs[\"target\"] = \"_blank\"\n        kwargs[\"rel\"] = \"nofollow noopener noreferrer\"\n    elif not path:\n        li_classes += \" is-group\"\n        if _is_open_item(item, request.ctx.language, request.path):\n            classes.append(\"is-open\")\n    else:\n        kwargs.update(\n            {\n                \"href\": path,\n                \"hx-get\": path,\n                \"hx-target\": \"#content\",\n                \"hx-swap\": \"innerHTML\",\n                \"hx-push-url\": \"true\",\n            }\n        )\n    kwargs[\"class_\"] = \" \".join(classes)\n    inner = E().a(item.label, **kwargs)\n    if page and page.anchors:\n        with inner.ul(class_=\"anchor-list\"):\n            for anchor in page.anchors:\n                inner.li(\n                    E.a(anchor.strip(\"`\"), href=f\"{path}#{slugify(anchor)}\"),\n                    class_=\"is-anchor\",\n                )\n    return E.li(inner, class_=li_classes)\n\n\ndef _is_open_item(item: MenuItem, language: str, current_path: str) -> bool:\n    path = f\"/{language}/{item.path}\" if item.path else \"\"\n    if current_path == path:\n        return True\n    for subitem in item.items:\n        if _is_open_item(subitem, language, current_path):\n            return True\n    return False\n"
  },
  {
    "path": "guide/webapp/display/layouts/home.py",
    "content": "from __future__ import annotations\n\nfrom collections.abc import Generator\nfrom contextlib import contextmanager\n\nfrom html5tagger import Builder, E\n\nfrom sanic import Request\nfrom webapp.display.layouts.elements.footer import do_footer\n\nfrom .base import BaseLayout\n\n\nclass HomeLayout(BaseLayout):\n    @contextmanager\n    def layout(\n        self, request: Request, full: bool = True\n    ) -> Generator[None, None, None]:\n        self._sponsors()\n        self._hero(request.ctx.language)\n        with self.builder.div(class_=\"home container\"):\n            yield\n        self._footer(request)\n\n    def _hero(self, language: str) -> None:\n        with self.builder.section(class_=\"hero is-large has-text-centered\"):\n            self.builder.div(\n                E.h1(E.span(\"Sanic\"), class_=\"title\"),\n                E.h2(class_=\"subtitle\")(\"Build fast. Run fast.\"),\n                E.h3(class_=\"tagline\")(\"Accelerate your web app development\"),\n                self._do_buttons(language),\n                class_=\"hero-body\",\n            )\n\n    def _do_buttons(self, language: str) -> Builder:\n        builder = E.div(class_=\"buttons is-centered\")\n        with builder:\n            builder.a(\n                \"Get Started\",\n                class_=\"button is-primary\",\n                href=f\"/{language}/guide/getting-started.html\",\n            )\n            builder.a(\n                \"Help\",\n                class_=\"button is-outlined\",\n                href=f\"/{language}/help.html\",\n            )\n            builder.a(\n                \"GitHub\",\n                class_=\"button is-outlined\",\n                href=\"https://github.com/sanic-org/sanic\",\n                target=\"_blank\",\n            )\n        return builder\n\n    def _sponsors(self) -> None:\n        with self.builder.section(class_=\"sponsors\"):\n            self.builder(\n                \"Secure, auto-document, and monetize \"\n                \"your Sanic API with Zuplo\",\n                E.a(\n                    \"Start free\",\n                    href=\"https://zuplo.com\",\n                    target=\"_blank\",\n                    class_=\"button is-primary is-small\",\n                ),\n            )\n\n    def _footer(self, request: Request) -> None:\n        do_footer(\n            self.builder,\n            request,\n            extra_classes=\"mb-0 mt-6\",\n            with_pagination=False,\n        )\n"
  },
  {
    "path": "guide/webapp/display/layouts/main.py",
    "content": "from collections.abc import Generator\nfrom contextlib import contextmanager\n\nfrom sanic import Request\nfrom webapp.display.layouts.elements.footer import do_footer\nfrom webapp.display.layouts.elements.navbar import do_navbar\nfrom webapp.display.layouts.elements.sidebar import do_sidebar\n\nfrom .base import BaseLayout\n\n\nclass MainLayout(BaseLayout):\n    @contextmanager\n    def layout(\n        self, request: Request, full: bool = True\n    ) -> Generator[None, None, None]:\n        if full:\n            self.builder.div(class_=\"loading-bar\")\n            with self.builder.div(class_=\"is-flex\"):\n                self._sidebar(request)\n                with self.builder.main(class_=\"is-flex-grow-1\"):\n                    self._navbar(request)\n                    with self.builder.div(class_=\"container\", id=\"content\"):\n                        with self._content_wrapper(request):\n                            yield\n                        self._footer(request)\n        else:\n            with self._content_wrapper(request):\n                yield\n            self._footer(request)\n\n    @contextmanager\n    def _content_wrapper(\n        self, request: Request\n    ) -> Generator[None, None, None]:\n        current_page = (\n            request.ctx.current_page\n            if hasattr(request.ctx, \"current_page\")\n            else None\n        )\n        section_class = \"section\"\n        if current_page and current_page.meta.content_class:\n            section_class += f\" {current_page.meta.content_class}\"\n        with self.builder.section(class_=section_class):\n            with self.builder.article():\n                yield\n\n    def _navbar(self, request: Request) -> None:\n        do_navbar(self.builder, request)\n\n    def _sidebar(self, request: Request) -> None:\n        do_sidebar(self.builder, request)\n\n    def _footer(self, request: Request) -> None:\n        do_footer(self.builder, request)\n"
  },
  {
    "path": "guide/webapp/display/layouts/models.py",
    "content": "from __future__ import annotations\n\nfrom msgspec import Struct, field\n\n\nclass MenuItem(Struct, kw_only=False, omit_defaults=True):\n    label: str\n    path: str | None = None\n    href: str | None = None\n    items: list[MenuItem] = field(default_factory=list)\n\n\nclass GeneralConfig(Struct, kw_only=False):\n    current_version: str\n"
  },
  {
    "path": "guide/webapp/display/markdown.py",
    "content": "import re\n\nfrom textwrap import dedent\n\nfrom html5tagger import HTML, Builder, E  # type: ignore\nfrom mistune import HTMLRenderer, create_markdown, escape\nfrom mistune.directives import RSTDirective, TableOfContents\nfrom mistune.util import safe_entity\nfrom pygments import highlight\nfrom pygments.formatters import html\nfrom pygments.lexers import get_lexer_by_name\n\nfrom .code_style import SanicCodeStyle\nfrom .plugins.attrs import Attributes\nfrom .plugins.columns import Column\nfrom .plugins.hook import Hook\nfrom .plugins.inline_directive import inline_directive\nfrom .plugins.mermaid import Mermaid\nfrom .plugins.notification import Notification\nfrom .plugins.span import span\nfrom .plugins.tabs import Tabs\nfrom .text import slugify\n\n\nclass DocsRenderer(HTMLRenderer):\n    def block_code(self, code: str, info: str | None = None):\n        builder = Builder(\"Block\")\n        with builder.div(class_=\"code-block\"):\n            if info:\n                lexer = get_lexer_by_name(info, stripall=False)\n                formatter = html.HtmlFormatter(\n                    style=SanicCodeStyle,\n                    wrapcode=True,\n                    cssclass=f\"highlight language-{info}\",\n                )\n                builder(HTML(highlight(code, lexer, formatter)))\n                with builder.div(\n                    class_=\"code-block__copy\",\n                    onclick=\"copyCode(this)\",\n                ):\n                    builder.div(\n                        class_=\"code-block__rectangle code-block__filled\"\n                    ).div(class_=\"code-block__rectangle code-block__outlined\")\n            else:\n                builder.pre(E.code(escape(code)))\n        return str(builder)\n\n    def heading(self, text: str, level: int, **attrs) -> str:\n        ident = slugify(text)\n        if level > 1:\n            text += self._make_tag(\n                \"a\", {\"href\": f\"#{ident}\", \"class\": \"anchor\"}, \"#\"\n            )\n        return self._make_tag(\n            f\"h{level}\",\n            {\n                \"id\": ident,\n                \"class\": (\n                    f\"is-size-{level}-desktop is-size-{level + 2}-touch\"\n                ),\n            },\n            text,\n        )\n\n    def link(self, text: str, url: str, title: str | None = None) -> str:\n        url = self.safe_url(url).replace(\".md\", \".html\")\n        url, anchor = url.split(\"#\", 1) if \"#\" in url else (url, None)\n        if (\n            not url.endswith(\"/\")\n            and not url.endswith(\".html\")\n            and not url.startswith(\"http\")\n        ):\n            url += \".html\"\n        if anchor:\n            url += f\"#{anchor}\"\n        attributes: dict[str, str] = {\"href\": url}\n        if title:\n            attributes[\"title\"] = safe_entity(title)\n        if url.startswith(\"http\"):\n            attributes[\"target\"] = \"_blank\"\n            attributes[\"rel\"] = \"nofollow noreferrer\"\n        else:\n            attributes[\"hx-get\"] = url\n            attributes[\"hx-target\"] = \"#content\"\n            attributes[\"hx-swap\"] = \"innerHTML\"\n            attributes[\"hx-push-url\"] = \"true\"\n        return self._make_tag(\"a\", attributes, text)\n\n    def span(self, text, classes, **attrs) -> str:\n        if classes:\n            attrs[\"class\"] = classes\n        return self._make_tag(\"span\", attrs, text)\n\n    def list(self, text: str, ordered: bool, **attrs) -> str:\n        tag = \"ol\" if ordered else \"ul\"\n        attrs[\"class\"] = tag\n        return self._make_tag(tag, attrs, text)\n\n    def list_item(self, text: str, **attrs) -> str:\n        attrs[\"class\"] = \"li\"\n        return self._make_tag(\"li\", attrs, text)\n\n    def table(self, text: str, **attrs) -> str:\n        attrs[\"class\"] = \"table is-fullwidth is-bordered\"\n        return self._make_tag(\"table\", attrs, text)\n\n    def inline_directive(self, text: str, **attrs) -> str:\n        num_dots = text.count(\".\")\n        display = self.codespan(text)\n\n        if num_dots <= 1:\n            return display\n\n        module, *_ = text.rsplit(\".\", num_dots - 1)\n        href = f\"/api/{module}.html\"\n        return self._make_tag(\n            \"a\",\n            {\"href\": href, \"class\": \"inline-directive\"},\n            display,\n        )\n\n    def _make_tag(\n        self, tag: str, attributes: dict[str, str], text: str | None = None\n    ) -> str:\n        attrs = \" \".join(\n            f'{key}=\"{value}\"' for key, value in attributes.items()\n        )\n        if text is None:\n            return f\"<{tag} {attrs} />\"\n        return f\"<{tag} {attrs}>{text}</{tag}>\"\n\n\nclass SanicTableOfContents(TableOfContents):\n    def generate_heading_id(self, token, index):\n        return slugify(token[\"text\"])\n\n\nRST_CODE_BLOCK_PATTERN = re.compile(\n    r\"\\.\\.\\scode-block::\\s(\\w+)\\n\\n((?:\\n|(?:\\s\\s\\s\\s[^\\n]*))+)\"\n)\n\n_render_markdown = create_markdown(\n    renderer=DocsRenderer(),\n    plugins=[\n        RSTDirective(\n            [\n                # Admonition(),\n                Attributes(),\n                Notification(),\n                SanicTableOfContents(),\n                Column(),\n                Mermaid(),\n                Tabs(),\n                Hook(),\n            ]\n        ),\n        \"abbr\",\n        \"def_list\",\n        \"footnotes\",\n        \"mark\",\n        \"table\",\n        span,\n        inline_directive,\n    ],\n)\n\n\ndef render_markdown(text: str) -> str:\n    def replacer(match):\n        language = match.group(1)\n        code_block = dedent(match.group(2)).strip()\n        return f\"```{language}\\n{code_block}\\n```\\n\\n\"\n\n    text = RST_CODE_BLOCK_PATTERN.sub(replacer, text)\n    return _render_markdown(text)\n"
  },
  {
    "path": "guide/webapp/display/page/__init__.py",
    "content": "from .page import Page\nfrom .renderer import PageRenderer\n\n\n__all__ = [\"Page\", \"PageRenderer\"]\n"
  },
  {
    "path": "guide/webapp/display/page/docobject.py",
    "content": "from __future__ import annotations\n\nimport importlib\nimport inspect\nimport pkgutil\n\nfrom collections import defaultdict\nfrom dataclasses import dataclass, field\nfrom html import escape\n\nfrom docstring_parser import Docstring, DocstringParam, DocstringRaises\nfrom docstring_parser import parse as parse_docstring\nfrom docstring_parser.common import DocstringExample\nfrom html5tagger import HTML, Builder, E  # type: ignore\n\nfrom ..markdown import render_markdown, slugify\n\n\n@dataclass\nclass DocObject:\n    name: str\n    module_name: str\n    full_name: str\n    signature: inspect.Signature | None\n    docstring: Docstring\n    object_type: str = \"\"\n    methods: list[DocObject] = field(default_factory=list)\n    decorators: list[str] = field(default_factory=list)\n\n\ndef _extract_classes_methods(obj, full_name, docstrings):\n    methods = []\n    for method_name, method in inspect.getmembers(obj, _is_public_member):\n        try:\n            signature = _get_method_signature(method)\n            docstring = inspect.getdoc(method)\n            decorators = _detect_decorators(obj, method)\n            methods.append(\n                DocObject(\n                    name=method_name,\n                    module_name=\"\",\n                    full_name=f\"{full_name}.{method_name}\",\n                    signature=signature,\n                    docstring=parse_docstring(docstring or \"\"),\n                    decorators=decorators,\n                    object_type=_get_object_type(method),\n                )\n            )\n        except ValueError:\n            pass\n\n    docstrings[full_name].methods = methods\n\n\ndef _get_method_signature(method):\n    try:\n        return inspect.signature(method)\n    except TypeError:\n        signature = None\n        if func := getattr(method, \"fget\", None):\n            signature = inspect.signature(func)\n    return signature\n\n\ndef _is_public_member(obj: object) -> bool:\n    obj_name = getattr(obj, \"__name__\", \"\")\n    if func := getattr(obj, \"fget\", None):\n        obj_name = getattr(func, \"__name__\", \"\")\n    return (\n        not obj_name.startswith(\"_\")\n        and not obj_name.isupper()\n        and (\n            inspect.ismethod(obj)\n            or inspect.isfunction(obj)\n            or isinstance(obj, property)\n            or isinstance(obj, property)\n        )\n    )\n\n\ndef _detect_decorators(cls, method):\n    decorators = []\n    method_name = getattr(method, \"__name__\", None)\n    if isinstance(cls.__dict__.get(method_name), classmethod):\n        decorators.append(\"classmethod\")\n    if isinstance(cls.__dict__.get(method_name), staticmethod):\n        decorators.append(\"staticmethod\")\n    if isinstance(method, property):\n        decorators.append(\"property\")\n    return decorators\n\n\ndef _get_object_type(obj) -> str:\n    if inspect.isclass(obj):\n        return \"class\"\n\n    # If the object is a method, get the underlying function\n    if inspect.ismethod(obj):\n        obj = obj.__func__\n\n    # If the object is a coroutine or a coroutine function\n    if inspect.iscoroutine(obj) or inspect.iscoroutinefunction(obj):\n        return \"async def\"\n\n    return \"def\"\n\n\ndef organize_docobjects(package_name: str) -> dict[str, str]:\n    page_content: defaultdict[str, str] = defaultdict(str)\n    docobjects = _extract_docobjects(package_name)\n    page_registry: defaultdict[str, list[str]] = defaultdict(list)\n    for module, docobject in docobjects.items():\n        builder = Builder(name=\"Partial\")\n        _docobject_to_html(docobject, builder)\n        ref = module.rsplit(\".\", module.count(\".\") - 1)[0]\n        page_registry[ref].append(module)\n        page_content[f\"/api/{ref}.md\"] += str(builder)\n    for ref, objects in page_registry.items():\n        page_content[f\"/api/{ref}.md\"] = (\n            _table_of_contents(objects) + page_content[f\"/api/{ref}.md\"]\n        )\n    return page_content\n\n\ndef _table_of_contents(objects: list[str]) -> str:\n    builder = Builder(name=\"Partial\")\n    with builder.div(class_=\"table-of-contents\"):\n        builder.h3(\"Table of Contents\", class_=\"is-size-4\")\n        for obj in objects:\n            module, name = obj.rsplit(\".\", 1)\n            builder.a(\n                E.strong(name),\n                E.small(module),\n                href=f\"#{slugify(obj.replace('.', '-'))}\",\n                class_=\"table-of-contents-item\",\n            )\n    return str(builder)\n\n\ndef _extract_docobjects(package_name: str) -> dict[str, DocObject]:\n    docstrings = {}\n    package = importlib.import_module(package_name)\n\n    for _, name, _ in pkgutil.walk_packages(\n        package.__path__, package_name + \".\"\n    ):\n        module = importlib.import_module(name)\n        for obj_name, obj in inspect.getmembers(module):\n            if (\n                obj_name.startswith(\"_\")\n                or inspect.getmodule(obj) != module\n                or not callable(obj)\n            ):\n                continue\n            try:\n                signature = inspect.signature(obj)\n            except ValueError:\n                signature = None\n            docstring = inspect.getdoc(obj)\n            full_name = f\"{name}.{obj_name}\"\n            docstrings[full_name] = DocObject(\n                name=obj_name,\n                full_name=full_name,\n                module_name=name,\n                signature=signature,\n                docstring=parse_docstring(docstring or \"\"),\n                object_type=_get_object_type(obj),\n            )\n            if inspect.isclass(obj):\n                _extract_classes_methods(obj, full_name, docstrings)\n\n    return docstrings\n\n\ndef _docobject_to_html(\n    docobject: DocObject, builder: Builder, as_method: bool = False\n) -> None:\n    anchor_id = slugify(docobject.full_name.replace(\".\", \"-\"))\n    anchor = E.a(\"#\", class_=\"anchor\", href=f\"#{anchor_id}\")\n    class_name, heading = _define_heading_and_class(\n        docobject, anchor, as_method\n    )\n\n    with builder.div(class_=class_name):\n        builder(heading)\n\n        if docobject.docstring.short_description:\n            builder.div(\n                HTML(render_markdown(docobject.docstring.short_description)),\n                class_=\"short-description mt-3 is-size-5\",\n            )\n\n        if docobject.object_type == \"class\":\n            mro = [\n                item\n                for idx, item in enumerate(\n                    inspect.getmro(\n                        getattr(\n                            importlib.import_module(docobject.module_name),\n                            docobject.name,\n                        )\n                    )\n                )\n                if idx > 0 and item not in (object, type)\n            ]\n            if mro:\n                builder.div(\n                    E.span(\"Inherits from: \", class_=\"is-italic\"),\n                    E.span(\n                        \", \".join([cls.__name__ for cls in mro]),\n                        class_=\"has-text-weight-bold\",\n                    ),\n                    class_=\"short-description mt-3 is-size-5\",\n                )\n\n        builder.p(\n            HTML(\n                _signature_to_html(\n                    docobject.name,\n                    docobject.object_type,\n                    docobject.signature,\n                    docobject.decorators,\n                )\n            ),\n            class_=\"signature notification is-family-monospace\",\n        )\n\n        if docobject.docstring.long_description:\n            builder.div(\n                HTML(render_markdown(docobject.docstring.long_description)),\n                class_=\"long-description mt-3\",\n            )\n\n        if docobject.docstring.params:\n            with builder.div(class_=\"box mt-5\"):\n                builder.h5(\n                    \"Parameters\", class_=\"is-size-5 has-text-weight-bold\"\n                )\n                _render_params(builder, docobject.docstring.params)\n\n        if docobject.docstring.returns:\n            _render_returns(builder, docobject)\n\n        if docobject.docstring.raises:\n            _render_raises(builder, docobject.docstring.raises)\n\n        if docobject.docstring.examples:\n            _render_examples(builder, docobject.docstring.examples)\n\n        for method in docobject.methods:\n            _docobject_to_html(method, builder, as_method=True)\n\n\ndef _signature_to_html(\n    name: str,\n    object_type: str,\n    signature: inspect.Signature | None,\n    decorators: list[str],\n) -> str:\n    parts = []\n    parts.append(\"<span class='function-signature'>\")\n    for decorator in decorators:\n        parts.append(\n            f\"<span class='function-decorator'>@{decorator}</span><br>\"\n        )\n    parts.append(\n        f\"<span class='is-italic'>{object_type}</span> \"\n        f\"<span class='has-text-weight-bold'>{name}</span>(\"\n    )\n    if not signature:\n        parts.append(\"<span class='param-name'>self</span>)\")\n        parts.append(\"</span>\")\n        return \"\".join(parts)\n    for i, param in enumerate(signature.parameters.values()):\n        parts.append(f\"<span class='param-name'>{escape(param.name)}</span>\")\n        annotation = \"\"\n        if param.annotation != inspect.Parameter.empty:\n            annotation = escape(str(param.annotation))\n            parts.append(\n                f\": <span class='param-annotation'>{annotation}</span>\"\n            )\n        if param.default != inspect.Parameter.empty:\n            default = escape(str(param.default))\n            if annotation == \"str\":\n                default = f'\"{default}\"'\n            parts.append(f\" = <span class='param-default'>{default}</span>\")\n        if i < len(signature.parameters) - 1:\n            parts.append(\", \")\n    parts.append(\")\")\n    if signature.return_annotation != inspect.Signature.empty:\n        return_annotation = escape(str(signature.return_annotation))\n        parts.append(\n            f\": -> <span class='return-annotation'>{return_annotation}</span>\"\n        )\n    parts.append(\"</span>\")\n    return \"\".join(parts)\n\n\ndef _define_heading_and_class(\n    docobject: DocObject, anchor: Builder, as_method: bool\n) -> tuple[str, Builder]:\n    anchor_id = slugify(docobject.full_name.replace(\".\", \"-\"))\n    anchor = E.a(\"#\", class_=\"anchor\", href=f\"#{anchor_id}\")\n    if as_method:\n        class_name = \"method\"\n        heading = E.h3(\n            docobject.name,\n            anchor,\n            class_=\"is-size-4 has-text-weight-bold mt-6\",\n            id_=anchor_id,\n        )\n    else:\n        class_name = \"docobject\"\n        heading = E.h2(\n            E.span(docobject.module_name, class_=\"has-text-weight-light\"),\n            \".\",\n            E.span(docobject.name, class_=\"has-text-weight-bold is-size-1\"),\n            anchor,\n            class_=\"is-size-2\",\n            id_=anchor_id,\n        )\n    return class_name, heading\n\n\ndef _render_params(builder: Builder, params: list[DocstringParam]) -> None:\n    for param in params:\n        with builder.dl(class_=\"mt-2\"):\n            dt_args = [param.arg_name]\n            if param.type_name:\n                parts = [\n                    E.br(),\n                    E.span(\n                        param.type_name,\n                        class_=(\n                            \"has-text-weight-normal has-text-purple \"\n                            \"is-size-7 ml-2\"\n                        ),\n                    ),\n                ]\n                dt_args.extend(parts)\n            builder.dt(*dt_args, class_=\"is-family-monospace\")\n            builder.dd(\n                HTML(\n                    render_markdown(\n                        param.description\n                        or param.arg_name\n                        or param.type_name\n                        or \"\"\n                    )\n                )\n            )\n\n\ndef _render_raises(builder: Builder, raises: list[DocstringRaises]) -> None:\n    with builder.div(class_=\"box mt-5\"):\n        builder.h5(\"Raises\", class_=\"is-size-5 has-text-weight-bold\")\n        for raise_ in raises:\n            with builder.dl(class_=\"mt-2\"):\n                builder.dt(raise_.type_name, class_=\"is-family-monospace\")\n                builder.dd(\n                    HTML(\n                        render_markdown(\n                            raise_.description or raise_.type_name or \"\"\n                        )\n                    )\n                )\n\n\ndef _render_returns(builder: Builder, docobject: DocObject) -> None:\n    assert docobject.docstring.returns\n    return_type = docobject.docstring.returns.type_name\n    if not return_type or return_type == \"None\":\n        return\n    with builder.div(class_=\"box mt-5\"):\n        if not return_type and docobject.signature:\n            return_type = docobject.signature.return_annotation\n\n        if not return_type or return_type == inspect.Signature.empty:\n            return_type = \"N/A\"\n\n        term = (\n            \"Return\"\n            if not docobject.docstring.returns.is_generator\n            else \"Yields\"\n        )\n        builder.h5(term, class_=\"is-size-5 has-text-weight-bold\")\n        with builder.dl(class_=\"mt-2\"):\n            builder.dt(return_type, class_=\"is-family-monospace\")\n            builder.dd(\n                HTML(\n                    render_markdown(\n                        docobject.docstring.returns.description\n                        or docobject.docstring.returns.type_name\n                        or \"\"\n                    )\n                )\n            )\n\n\ndef _render_examples(\n    builder: Builder, examples: list[DocstringExample]\n) -> None:\n    with builder.div(class_=\"box mt-5\"):\n        builder.h5(\"Examples\", class_=\"is-size-5 has-text-weight-bold\")\n        for example in examples:\n            with builder.div(class_=\"mt-2\"):\n                builder(\n                    HTML(\n                        render_markdown(\n                            example.description or example.snippet or \"\"\n                        )\n                    )\n                )\n"
  },
  {
    "path": "guide/webapp/display/page/page.py",
    "content": "from __future__ import annotations\n\nfrom dataclasses import dataclass, field\nfrom pathlib import Path\n\nfrom frontmatter import parse\n\nfrom ..layouts.base import BaseLayout\nfrom ..layouts.home import HomeLayout\nfrom ..layouts.main import MainLayout\nfrom ..markdown import render_markdown\nfrom .docobject import organize_docobjects\n\n\n_PAGE_CACHE: dict[\n    str, dict[str, tuple[Page | None, Page | None, Page | None]]\n] = {}\n_LAYOUTS_CACHE: dict[str, type[BaseLayout]] = {\n    \"home\": HomeLayout,\n    \"main\": MainLayout,\n}\n_DEFAULT = \"en\"\n\n\n@dataclass\nclass PageMeta:\n    language: str = _DEFAULT\n    title: str = \"\"\n    description: str = \"\"\n    layout: str = \"main\"\n    features: list[dict[str, str]] = field(default_factory=list)\n    content_class: str = \"\"\n\n\n@dataclass\nclass Page:\n    path: Path\n    content: str\n    meta: PageMeta = field(default_factory=PageMeta)\n    _relative_path: Path | None = None\n    next_page: Page | None = None\n    previous_page: Page | None = None\n    anchors: list[str] = field(default_factory=list)\n\n    DEFAULT_LANGUAGE = _DEFAULT\n\n    def get_layout(self) -> type[BaseLayout]:\n        return _LAYOUTS_CACHE[self.meta.layout]\n\n    @property\n    def relative_path(self) -> Path:\n        if self._relative_path is None:\n            raise RuntimeError(\"Page not initialized\")\n        return self._relative_path\n\n    @classmethod\n    def get(\n        cls, language: str, path: str\n    ) -> tuple[Page | None, Page | None, Page | None]:\n        if path.endswith(\"/\") or not path:\n            path += \"index.html\"\n        if not path.endswith(\".md\"):\n            path = path.removesuffix(\".html\") + \".md\"\n        if language == \"api\":\n            path = f\"/api/{path}\"\n        return _PAGE_CACHE.get(language, {}).get(path, (None, None, None))\n\n    @classmethod\n    def load_pages(cls, base_path: Path, page_order: list[str]) -> list[Page]:\n        output: list[Page] = []\n        for path in base_path.glob(\"**/*.md\"):\n            relative = path.relative_to(base_path)\n            language = relative.parts[0]\n            name = \"/\".join(relative.parts[1:])\n            page = cls._load_page(path)\n            output.append(page)\n            page._relative_path = relative\n            _PAGE_CACHE.setdefault(language, {})[name] = (\n                None,\n                page,\n                None,\n            )\n            _PAGE_CACHE[\"api\"] = {}\n        for language, pages in _PAGE_CACHE.items():\n            for name, (_, current, _) in pages.items():\n                previous_page = None\n                next_page = None\n                try:\n                    index = page_order.index(name)\n                except ValueError:\n                    continue\n                try:\n                    if index > 0:\n                        previous_page = pages[page_order[index - 1]][1]\n                except KeyError:\n                    pass\n                try:\n                    if index < len(page_order) - 1:\n                        next_page = pages[page_order[index + 1]][1]\n                except KeyError:\n                    pass\n                pages[name] = (previous_page, current, next_page)\n            previous_page = None\n            next_page = None\n\n        api_pages = cls._load_api_pages()\n        filtered_order = [ref for ref in page_order if ref in api_pages]\n        for idx, ref in enumerate(filtered_order):\n            current_page = api_pages[ref]\n            previous_page = None\n            next_page = None\n            try:\n                if idx > 0:\n                    previous_page = api_pages[filtered_order[idx - 1]]\n            except KeyError:\n                pass\n            try:\n                if idx < len(filtered_order) - 1:\n                    next_page = api_pages[filtered_order[idx + 1]]\n            except KeyError:\n                pass\n            _PAGE_CACHE[\"api\"][ref] = (previous_page, current_page, next_page)\n\n        return output\n\n    @staticmethod\n    def _load_page(path: Path) -> Page:\n        raw = path.read_text()\n        metadata, raw_content = parse(raw)\n        content = render_markdown(raw_content)\n        page = Page(\n            path=path,\n            content=content,\n            meta=PageMeta(**metadata),\n        )\n        if not page.meta.title:\n            page.meta.title = page.path.stem.replace(\"-\", \" \").title()\n\n        for line in raw.splitlines():\n            if line.startswith(\"##\") and not line.startswith(\"###\"):\n                line = line.lstrip(\"#\").strip()\n                page.anchors.append(line)\n\n        return page\n\n    @staticmethod\n    def _load_api_pages() -> dict[str, Page]:\n        docstring_content = organize_docobjects(\"sanic\")\n        output: dict[str, Page] = {}\n\n        for module, content in docstring_content.items():\n            path = Path(module)\n            page = Page(\n                path=path,\n                content=content,\n                meta=PageMeta(\n                    title=path.stem,\n                    description=\"\",\n                    layout=\"main\",\n                ),\n            )\n            page._relative_path = Path(f\"./{module}\")\n            output[module] = page\n\n        return output\n"
  },
  {
    "path": "guide/webapp/display/page/renderer.py",
    "content": "from __future__ import annotations\n\nfrom contextlib import contextmanager\n\nfrom html5tagger import HTML, Builder  # type: ignore\n\nfrom sanic import Request\nfrom webapp.display.base import BaseRenderer\n\nfrom ..layouts.base import BaseLayout\nfrom .page import Page\n\n\nclass PageRenderer(BaseRenderer):\n    def render(self, request: Request, language: str, path: str) -> Builder:\n        self._setup_request(request, language, path)\n        builder = self.get_builder(\n            full=request.headers.get(\"HX-Request\") is None,\n            language=language,\n        )\n        self._body(request, builder, language, path)\n        return builder\n\n    def title(self) -> str:\n        request = Request.get_current()\n        title: str | None = None\n        if request and (\n            current_page := getattr(request.ctx, \"current_page\", None)\n        ):\n            title = f\"{self.base_title} - {current_page.meta.title}\"\n        return title or self.base_title\n\n    def _setup_request(self, request: Request, language: str, path: str):\n        prev_page, current_page, next_page = Page.get(language, path)\n        request.ctx.language = (\n            Page.DEFAULT_LANGUAGE if language == \"api\" else language\n        )\n        request.ctx.current_page = current_page\n        request.ctx.previous_page = prev_page\n        request.ctx.next_page = next_page\n\n    def _body(\n        self, request: Request, builder: Builder, language: str, path: str\n    ):\n        current_page = request.ctx.current_page\n        with self._base(request, builder, current_page):\n            if current_page is None:\n                builder.h1(\"Not found\")\n                return\n            builder(HTML(current_page.content))\n\n    @contextmanager\n    def _base(self, request: Request, builder: Builder, page: Page | None):\n        layout_type: type[BaseLayout] = (\n            page.get_layout() if page else BaseLayout\n        )\n        layout = layout_type(builder)\n        with layout(request, builder.full):\n            yield\n"
  },
  {
    "path": "guide/webapp/display/plugins/__init__.py",
    "content": ""
  },
  {
    "path": "guide/webapp/display/plugins/attrs.py",
    "content": "from re import Match\nfrom textwrap import dedent\nfrom typing import Any\n\nfrom html5tagger import HTML, E\nfrom mistune.block_parser import BlockParser\nfrom mistune.core import BlockState\nfrom mistune.directives import DirectivePlugin\n\n\nclass Attributes(DirectivePlugin):\n    def __call__(self, directive, md):\n        directive.register(\"attrs\", self.parse)\n\n        if md.renderer.NAME == \"html\":\n            md.renderer.register(\"attrs\", self._render)\n\n    def parse(\n        self, block: BlockParser, m: Match, state: BlockState\n    ) -> dict[str, Any]:\n        info = m.groupdict()\n        options = dict(self.parse_options(m))\n        new_state = block.state_cls()\n        new_state.process(dedent(info[\"text\"]))\n        block.parse(new_state)\n        options.setdefault(\"class_\", \"additional-attributes\")\n        classes = options.pop(\"class\", \"\")\n        if classes:\n            options[\"class_\"] += f\" {classes}\"\n\n        return {\n            \"type\": \"attrs\",\n            \"text\": info[\"text\"],\n            \"children\": new_state.tokens,\n            \"attrs\": options,\n        }\n\n    def _render(self, _, text: str, **attrs) -> str:\n        return str(E.div(HTML(text), **attrs))\n"
  },
  {
    "path": "guide/webapp/display/plugins/columns.py",
    "content": "from re import Match\nfrom textwrap import dedent\nfrom typing import Any\n\nfrom mistune import HTMLRenderer\nfrom mistune.block_parser import BlockParser\nfrom mistune.core import BlockState\nfrom mistune.directives import DirectivePlugin, RSTDirective\nfrom mistune.markdown import Markdown\n\n\nclass Column(DirectivePlugin):\n    def parse(\n        self, block: BlockParser, m: Match, state: BlockState\n    ) -> dict[str, Any]:\n        info = m.groupdict()\n\n        new_state = block.state_cls()\n        new_state.process(dedent(info[\"text\"]))\n        block.parse(new_state)\n\n        return {\n            \"type\": \"column\",\n            \"text\": info[\"text\"],\n            \"children\": new_state.tokens,\n            \"attrs\": {},\n        }\n\n    def __call__(  # type: ignore\n        self, directive: RSTDirective, md: Markdown\n    ) -> None:\n        directive.register(\"column\", self.parse)\n\n        if md.renderer.NAME == \"html\":\n            md.renderer.register(\"column\", self._render_column)\n\n    def _render_column(self, renderer: HTMLRenderer, text: str, **attrs):\n        start = (\n            '<div class=\"columns mt-3 is-multiline\">\\n'\n            if attrs.get(\"first\")\n            else \"\"\n        )\n        end = \"</div>\\n\" if attrs.get(\"last\") else \"\"\n        col = f'<div class=\"column is-half\">{text}</div>\\n'\n        return start + (col) + end\n"
  },
  {
    "path": "guide/webapp/display/plugins/hook.py",
    "content": "from mistune.core import BlockState\nfrom mistune.directives import DirectivePlugin, RSTDirective\nfrom mistune.markdown import Markdown\n\n\nclass Hook(DirectivePlugin):\n    def __call__(  # type: ignore\n        self, directive: RSTDirective, md: Markdown\n    ) -> None:\n        if md.renderer.NAME == \"html\":\n            md.before_render_hooks.append(self._hook)\n\n    def _hook(self, md: Markdown, state: BlockState) -> None:\n        prev = None\n        for idx, token in enumerate(state.tokens):\n            for type_ in (\"column\", \"tab\"):\n                if token[\"type\"] == type_:\n                    maybe_next = (\n                        state.tokens[idx + 1]\n                        if idx + 1 < len(state.tokens)\n                        else None\n                    )\n                    token.setdefault(\"attrs\", {})\n                    if prev and prev[\"type\"] != type_:\n                        token[\"attrs\"][\"first\"] = True\n                    if (\n                        maybe_next and maybe_next[\"type\"] != type_\n                    ) or not maybe_next:\n                        token[\"attrs\"][\"last\"] = True\n\n            prev = token\n"
  },
  {
    "path": "guide/webapp/display/plugins/inline_directive.py",
    "content": "import re\n\nfrom mistune.markdown import Markdown\n\n\nDIRECTIVE_PATTERN = r\":(?:class|func|meth|attr|exc|mod|data|const|obj|keyword|option|cmdoption|envvar):`(?P<ref>sanic\\.[^`]+)`\"  # noqa: E501\n\n\ndef _parse_inline_directive(inline, m: re.Match, state):\n    state.append_token(\n        {\n            \"type\": \"inline_directive\",\n            \"attrs\": {},\n            \"raw\": m.group(\"ref\"),\n        }\n    )\n    return m.end()\n\n\ndef inline_directive(md: Markdown):\n    md.inline.register(\n        \"inline_directive\",\n        DIRECTIVE_PATTERN,\n        _parse_inline_directive,\n        before=\"escape\",\n    )\n"
  },
  {
    "path": "guide/webapp/display/plugins/mermaid.py",
    "content": "from html import unescape\nfrom re import Match\nfrom textwrap import dedent\nfrom typing import Any\n\nfrom html5tagger import HTML, E\nfrom mistune import HTMLRenderer\nfrom mistune.block_parser import BlockParser\nfrom mistune.core import BlockState\nfrom mistune.directives import DirectivePlugin, RSTDirective\nfrom mistune.markdown import Markdown\n\n\nclass Mermaid(DirectivePlugin):\n    def parse(\n        self, block: BlockParser, m: Match, state: BlockState\n    ) -> dict[str, Any]:\n        info = m.groupdict()\n\n        new_state = block.state_cls()\n        new_state.process(dedent(info[\"text\"]))\n        block.parse(new_state)\n\n        text = HTML(info[\"text\"].strip())\n\n        return {\n            \"type\": \"mermaid\",\n            \"text\": text,\n            \"children\": [{\"type\": \"text\", \"text\": text}],\n            \"attrs\": {},\n        }\n\n    def __call__(self, directive: RSTDirective, md: Markdown) -> None:  # type: ignore\n        directive.register(\"mermaid\", self.parse)\n\n        if md.renderer.NAME == \"html\":\n            md.renderer.register(\"mermaid\", self._render_mermaid)\n\n    def _render_mermaid(self, renderer: HTMLRenderer, text: str, **attrs):\n        return str(E.div(class_=\"mermaid\")(HTML(unescape(text))))\n"
  },
  {
    "path": "guide/webapp/display/plugins/notification.py",
    "content": "from html5tagger import HTML, E\nfrom mistune.directives import Admonition\n\n\nclass Notification(Admonition):\n    SUPPORTED_NAMES = {\n        \"success\",\n        \"info\",\n        \"warning\",\n        \"danger\",\n        \"tip\",\n        \"new\",\n        \"note\",\n    }\n\n    def __call__(self, directive, md):\n        for name in self.SUPPORTED_NAMES:\n            directive.register(name, self.parse)\n\n        if md.renderer.NAME == \"html\":\n            md.renderer.register(\"admonition\", self._render_admonition)\n            md.renderer.register(\n                \"admonition_title\", self._render_admonition_title\n            )\n            md.renderer.register(\n                \"admonition_content\", self._render_admonition_content\n            )\n\n    def _render_admonition(self, _, text, name, **attrs) -> str:\n        return str(\n            E.div(\n                HTML(text),\n                class_=f\"notification is-{name}\",\n            )\n        )\n\n    def _render_admonition_title(self, _, text) -> str:\n        return str(\n            E.p(\n                text,\n                class_=\"notification-title\",\n            )\n        )\n\n    def _render_admonition_content(self, _, text) -> str:\n        return text\n"
  },
  {
    "path": "guide/webapp/display/plugins/span.py",
    "content": "import re\n\nfrom mistune.markdown import Markdown\n\n\ndef parse_inline_span(inline, m: re.Match, state):\n    state.append_token(\n        {\n            \"type\": \"span\",\n            \"attrs\": {\"classes\": m.group(\"classes\")},\n            \"raw\": m.group(\"content\"),\n        }\n    )\n    return m.end()\n\n\nSPAN_PATTERN = r\"{span:(?:(?P<classes>[^\\:]+?):)?(?P<content>.*?)}\"\n\n\ndef span(md: Markdown) -> None:\n    md.inline.register(\n        \"span\",\n        SPAN_PATTERN,\n        parse_inline_span,\n        before=\"link\",\n    )\n"
  },
  {
    "path": "guide/webapp/display/plugins/tabs.py",
    "content": "from re import Match\nfrom textwrap import dedent\nfrom typing import Any\n\nfrom mistune import HTMLRenderer\nfrom mistune.block_parser import BlockParser\nfrom mistune.core import BlockState\nfrom mistune.directives import DirectivePlugin, RSTDirective\nfrom mistune.markdown import Markdown\n\n\nclass Tabs(DirectivePlugin):\n    def parse(\n        self, block: BlockParser, m: Match, state: BlockState\n    ) -> dict[str, Any]:\n        info = m.groupdict()\n\n        new_state = block.state_cls()\n        new_state.process(dedent(info[\"text\"]))\n        block.parse(new_state)\n\n        return {\n            \"type\": \"tab\",\n            \"text\": info[\"text\"],\n            \"children\": new_state.tokens,\n            \"attrs\": {\n                \"title\": info[\"title\"],\n            },\n        }\n\n    def __call__(  # type: ignore\n        self,\n        directive: RSTDirective,\n        md: Markdown,\n    ) -> None:\n        directive.register(\"tab\", self.parse)\n\n        if md.renderer.NAME == \"html\":\n            md.renderer.register(\"tab\", self._render_tab)\n\n    def _render_tab(self, renderer: HTMLRenderer, text: str, **attrs):\n        start = (\n            '<div class=\"tab-container mt-6\"><div class=\"tabs\"><ul>\\n'\n            if attrs.get(\"first\")\n            else \"\"\n        )\n        end = (\n            '</ul></div><div class=\"tab-display\"></div></div>\\n'\n            if attrs.get(\"last\")\n            else \"\"\n        )\n        content = f'<div class=\"tab-content\">{text}</div>\\n'\n        tab = f\"<li><a>{attrs['title']}</a>{content}</li>\\n\"\n        return start + tab + end\n"
  },
  {
    "path": "guide/webapp/display/search/__init__.py",
    "content": ""
  },
  {
    "path": "guide/webapp/display/search/renderer.py",
    "content": "from contextlib import contextmanager\nfrom urllib.parse import unquote\n\nfrom html5tagger import Builder, E  # type: ignore\n\nfrom sanic import Request\nfrom webapp.display.search.search import Searcher\n\nfrom ..base import BaseRenderer\nfrom ..layouts.main import MainLayout\n\n\nclass SearchRenderer(BaseRenderer):\n    def render(\n        self, request: Request, language: str, searcher: Searcher, full: bool\n    ) -> Builder:\n        builder = self.get_builder(\n            full=request.headers.get(\"HX-Request\") is None,\n            language=language,\n        )\n        self._body(request, builder, language, searcher, full)\n        return builder\n\n    def _body(\n        self,\n        request: Request,\n        builder: Builder,\n        language: str,\n        searcher: Searcher,\n        full: bool,\n    ):\n        with self._base(request, builder, full):\n            builder.h1(\"Search\")\n            self._results(request, builder, searcher, language)\n\n    @contextmanager\n    def _base(self, request: Request, builder: Builder, full: bool):\n        layout = MainLayout(builder)\n        with layout(request, full):\n            yield\n\n    def _results(\n        self,\n        request: Request,\n        builder: Builder,\n        searcher: Searcher,\n        language: str,\n    ):\n        query = unquote(request.args.get(\"q\", \"\"))\n        results = searcher.search(query, language)\n        if not query or not results:\n            builder.p(\"No results found\")\n            return\n\n        with builder.div(class_=\"container\"):\n            with builder.ul():\n                for _, doc in results:\n                    builder.li(\n                        E.a(\n                            doc.title,\n                            href=f\"/{doc.page.relative_path}\",\n                            hx_get=f\"/{doc.page.relative_path}\",\n                            hx_target=\"#content\",\n                            hx_swap=\"innerHTML\",\n                            hx_push_url=\"true\",\n                        ),\n                        f\" - {doc.page.relative_path}\",\n                    )\n"
  },
  {
    "path": "guide/webapp/display/search/search.py",
    "content": "from __future__ import annotations\n\nfrom collections import Counter\nfrom pathlib import Path\nfrom typing import ClassVar\n\nfrom msgspec import Struct\n\nfrom webapp.display.page import Page\n\n\nclass Stemmer:\n    STOP_WORDS: ClassVar[set[str]] = set(\n        \"a about above after again against all am an and any are aren't as at be because been before being below between both but by can't cannot could couldn't did didn't do does doesn't doing don't down during each few for from further had hadn't has hasn't have haven't having he he'd he'll he's her here here's hers herself him himself his how how's i i'd i'll i'm i've if in into is isn't it it's its itself let's me more most mustn't my myself no nor not of off on once only or other ought our ours ourselves out over own same shan't she she'd she'll she's should shouldn't so some such than that that's the their theirs them themselves then there there's these they they'd they'll they're they've this those through to too under until up very was wasn't we we'd we'll we're we've were weren't what what's when when's where where's which while who who's whom why why's with won't would wouldn't you you'd you'll you're you've your yours yourself yourselves\".split()  # noqa: E501\n    )\n    PREFIXES = set(\"auto be fore over re un under\".split())\n    SUFFIXES = set(\n        \"able al ance ant ate ed en er ful hood ing ion ish ity ive ize less ly ment ness ous ship sion tion y\".split()  # noqa: E501\n    )\n    VOWELS = set(\"aeiou\")\n    PLURALIZATION = set(\"s es ies\".split())\n\n    def stem(self, word: str) -> str:\n        if word in self.STOP_WORDS:\n            return word\n        if word in self.PREFIXES:\n            return word\n        for suffix in self.SUFFIXES | self.PLURALIZATION:\n            if word.endswith(suffix):\n                return self._stem(word[: -len(suffix)])\n        return word\n\n    def _stem(self, word: str) -> str:\n        if word.endswith(\"e\"):\n            return word[:-1]\n        if word.endswith(\"y\") and word[-2] not in self.VOWELS:\n            return word[:-1]\n        return word\n\n    def __call__(self, word: str) -> str:\n        return self.stem(word)\n\n\nclass Document(Struct, kw_only=True):\n    TITLE_WEIGHT: ClassVar[int] = 3\n    BODY_WEIGHT: ClassVar[int] = 1\n\n    page: Page\n    language: str\n    term_frequency: dict[str, float] = {}\n\n    @property\n    def title(self) -> str:\n        return self.page.meta.title\n\n    @property\n    def text(self) -> str:\n        return self.page.content\n\n    @property\n    def weighted_text(self) -> str:\n        \"\"\"Return the text with the title repeated.\"\"\"\n        return \" \".join(\n            [self.title] * self.TITLE_WEIGHT + [self.text] * self.BODY_WEIGHT\n        )\n\n    def _term_frequency(self, stemmer: Stemmer) -> None:\n        \"\"\"Count the number of times each word appears in the document.\"\"\"\n        words = [\n            stemmer(word)\n            for word in self.weighted_text.lower().split()\n            if word not in Stemmer.STOP_WORDS\n        ]\n        num_words = len(words)\n        word_count = Counter(words)\n        self.term_frequency = {\n            word: count / num_words for word, count in word_count.items()\n        }\n\n    def process(self, stemmer: Stemmer) -> Document:\n        \"\"\"Process the document.\"\"\"\n        self._term_frequency(stemmer)\n        return self\n\n\ndef _inverse_document_frequency(docs: list[Document]) -> dict[str, float]:\n    \"\"\"Count the number of documents each word appears in.\"\"\"\n    num_docs = len(docs)\n    word_count: Counter[str] = Counter()\n    for doc in docs:\n        word_count.update(doc.term_frequency.keys())\n    return {word: num_docs / count for word, count in word_count.items()}\n\n\ndef _tf_idf_vector(\n    document: Document, idf: dict[str, float]\n) -> dict[str, float]:\n    \"\"\"Calculate the TF-IDF vector for a document.\"\"\"\n    return {\n        word: tf * idf[word]\n        for word, tf in document.term_frequency.items()\n        if word in idf\n    }\n\n\ndef _cosine_similarity(\n    vec1: dict[str, float], vec2: dict[str, float]\n) -> float:\n    \"\"\"Calculate the cosine similarity between two vectors.\"\"\"\n    if not vec1 or not vec2:\n        return 0.0\n    dot_product = sum(vec1.get(word, 0) * vec2.get(word, 0) for word in vec1)\n    magnitude1 = sum(value**2 for value in vec1.values()) ** 0.5\n    magnitude2 = sum(value**2 for value in vec2.values()) ** 0.5\n    return dot_product / (magnitude1 * magnitude2)\n\n\ndef _search(\n    query: str,\n    language: str,\n    vectors: list[dict[str, float]],\n    idf: dict[str, float],\n    documents: list[Document],\n    stemmer: Stemmer,\n) -> list[tuple[float, Document]]:\n    dummy_page = Page(Path(), query)\n    tf_idf_query = _tf_idf_vector(\n        Document(page=dummy_page, language=language).process(stemmer), idf\n    )\n    similarities = [\n        _cosine_similarity(tf_idf_query, vector) for vector in vectors\n    ]\n    return [\n        (similarity, document)\n        for similarity, document in sorted(\n            zip(similarities, documents),\n            reverse=True,\n            key=lambda pair: pair[0],\n        )[:10]\n        if similarity > 0\n    ]\n\n\nclass Searcher:\n    def __init__(\n        self,\n        stemmer: Stemmer,\n        documents: list[Document],\n    ):\n        self._documents: dict[str, list[Document]] = {}\n        for document in documents:\n            self._documents.setdefault(document.language, []).append(document)\n        self._idf = {\n            language: _inverse_document_frequency(documents)\n            for language, documents in self._documents.items()\n        }\n        self._vectors = {\n            language: [\n                _tf_idf_vector(document, self._idf[language])\n                for document in documents\n            ]\n            for language, documents in self._documents.items()\n        }\n        self._stemmer = stemmer\n\n    def search(\n        self, query: str, language: str\n    ) -> list[tuple[float, Document]]:\n        return _search(\n            query,\n            language,\n            self._vectors[language],\n            self._idf[language],\n            self._documents[language],\n            self._stemmer,\n        )\n"
  },
  {
    "path": "guide/webapp/display/text.py",
    "content": "import re\n\n\nSLUGIFY_PATTERN = re.compile(r\"[^a-zA-Z0-9-]\")\n\n\ndef slugify(text: str) -> str:\n    return SLUGIFY_PATTERN.sub(\"\", text.lower().replace(\" \", \"-\"))\n"
  },
  {
    "path": "guide/webapp/endpoint/__init__.py",
    "content": ""
  },
  {
    "path": "guide/webapp/endpoint/search.py",
    "content": "# from urllib.parse import unquote\n\nfrom sanic import Blueprint, Request, Sanic, html\nfrom webapp.display.page import Page\nfrom webapp.display.search.renderer import SearchRenderer\nfrom webapp.display.search.search import Document, Searcher, Stemmer\n\n\nbp = Blueprint(\"search\", url_prefix=\"/<language>/search\")\n\n\n@bp.get(\"\")\nasync def _search(request: Request, language: str, searcher: Searcher):\n    full = not bool(request.headers.get(\"HX-Request\"))\n    renderer = SearchRenderer(\"Sanic Documentation Search\")\n    builder = renderer.render(request, language, searcher, full)\n\n    return html(str(builder))\n\n\n@bp.before_server_start\nasync def setup_search(app: Sanic):\n    stemmer = Stemmer()\n    pages: list[Page] = app.ctx.pages\n    documents = [\n        Document(page=page, language=page.meta.language).process(stemmer)\n        for page in pages\n    ]\n    app.ext.dependency(Searcher(stemmer, documents))\n"
  },
  {
    "path": "guide/webapp/endpoint/sitemap.py",
    "content": "from sanic import Request, Sanic, raw\n\n\ndef setup_sitemap(app: Sanic) -> None:\n    app.get(\"/sitemap.xml\")(_sitemap)\n    app.before_server_start(_compile_sitemap, priority=0)\n\n\nasync def _compile_sitemap(app: Sanic):\n    pages: list[str] = [\n        app.url_for(\n            \"page\",\n            language=\"en\",\n            path=page.relative_path.with_suffix(\".html\"),\n            _external=True,\n            _server=\"sanic.dev\",\n            _scheme=\"https\",\n        )\n        for page in app.ctx.pages\n        if page.relative_path\n    ]\n    sitemap_parts: list[str] = [\n        '<?xml version=\"1.0\" encoding=\"UTF-8\"?>',\n        '<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">',\n        *[f\"<url><loc>{page}</loc></url>\" for page in pages],\n        \"</urlset>\",\n    ]\n    app.ctx.sitemap = \"\\n\".join(sitemap_parts)\n\n\nasync def _sitemap(request: Request):\n    return raw(request.app.ctx.sitemap, content_type=\"application/xml\")\n"
  },
  {
    "path": "guide/webapp/endpoint/view.py",
    "content": "from sanic import Blueprint\n\nfrom .search import bp as search_bp\n\n\nbp = Blueprint.group(search_bp)\n"
  },
  {
    "path": "guide/webapp/worker/__init__.py",
    "content": ""
  },
  {
    "path": "guide/webapp/worker/config.py",
    "content": "from pathlib import Path\n\nfrom msgspec import yaml\n\nfrom webapp.display.layouts.models import GeneralConfig, MenuItem\n\n\ndef load_menu(path: Path) -> list[MenuItem]:\n    loaded = yaml.decode(path.read_bytes(), type=dict[str, list[MenuItem]])\n    return loaded[\"root\"]\n\n\ndef load_config(path: Path) -> GeneralConfig:\n    loaded = yaml.decode(path.read_bytes(), type=GeneralConfig)\n    return loaded\n"
  },
  {
    "path": "guide/webapp/worker/factory.py",
    "content": "from pathlib import Path\n\nfrom sanic import Request, Sanic, html, redirect\nfrom webapp.display.layouts.models import MenuItem\nfrom webapp.display.page import Page, PageRenderer\nfrom webapp.endpoint.sitemap import setup_sitemap\nfrom webapp.endpoint.view import bp\nfrom webapp.worker.config import load_config, load_menu\nfrom webapp.worker.reload import setup_livereload\nfrom webapp.worker.style import setup_style\n\n\nKNOWN_REDIRECTS = {\n    \"guide/deployment/configuration.html\": \"guide/running/configuration.html\",\n    \"guide/deployment/development.html\": \"guide/running/development.html\",\n    \"guide/deployment/running.html\": \"guide/running/running.html\",\n    \"guide/deployment/manager.html\": \"guide/running/manager.html\",\n    \"guide/deployment/app-loader.html\": \"guide/running/app-loader.html\",\n    \"guide/deployment/inspector.html\": \"guide/running/inspector.html\",\n    \"org/policies.html\": \"organization/policies.html\",\n    \"org/scope.html\": \"organization/scope.html\",\n    \"org/feature_requests.html\": \"\",\n}\n\n\ndef _compile_sidebar_order(items: list[MenuItem]) -> list[str]:\n    order = []\n    for item in items:\n        if item.path:\n            order.append(item.path.removesuffix(\".html\") + \".md\")\n        if item.items:\n            order.extend(_compile_sidebar_order(item.items))\n    return order\n\n\ndef create_app(root: Path) -> Sanic:\n    app = Sanic(\"Documentation\")\n    app.config.PUBLIC_DIR = root / \"public\"\n    app.config.CONTENT_DIR = root / \"content\"\n    app.config.CONFIG_DIR = root / \"config\"\n    app.config.STYLE_DIR = root / \"style\"\n    app.config.NODE_MODULES_DIR = root / \"node_modules\"\n    app.config.LANGUAGES = [\"en\"]\n    app.config.SIDEBAR = load_menu(\n        app.config.CONFIG_DIR / \"en\" / \"sidebar.yaml\"\n    )\n    app.config.NAVBAR = load_menu(app.config.CONFIG_DIR / \"en\" / \"navbar.yaml\")\n    app.config.GENERAL = load_config(\n        app.config.CONFIG_DIR / \"en\" / \"general.yaml\"\n    )\n\n    setup_livereload(app)\n    setup_style(app)\n    setup_sitemap(app)\n    app.blueprint(bp)\n\n    app.static(\"/assets/\", app.config.PUBLIC_DIR / \"assets\", name=\"assets\")\n\n    for path in (app.config.PUBLIC_DIR / \"web\").glob(\"*\"):\n        app.static(f\"/{path.name}\", path, name=path.name)\n\n    @app.before_server_start(priority=1)\n    async def setup(app: Sanic):\n        app.ext.dependency(PageRenderer(base_title=\"Sanic User Guide\"))\n        page_order = _compile_sidebar_order(app.config.SIDEBAR)\n        app.ctx.pages = Page.load_pages(app.config.CONTENT_DIR, page_order)\n        app.ctx.get_page = Page.get\n\n    @app.get(\"/\", name=\"root\")\n    @app.get(\"/index.html\", name=\"index\")\n    async def index(request: Request):\n        return redirect(request.app.url_for(\"page\", language=\"en\", path=\"\"))\n\n    @app.get(\"/<language:str>\", name=\"page-without-path\")\n    @app.get(\"/<language:str>/<path:path>\")\n    async def page(\n        request: Request,\n        page_renderer: PageRenderer,\n        language: str,\n        path: str = \"\",\n    ):\n        # TODO: Add more language support\n        if language != \"api\" and language not in app.config.LANGUAGES:\n            return redirect(\n                request.app.url_for(\"page\", language=\"en\", path=path)\n            )\n        if path in KNOWN_REDIRECTS:\n            return redirect(\n                request.app.url_for(\n                    \"page\", language=language, path=KNOWN_REDIRECTS[path]\n                ),\n                status=301,\n            )\n        builder = page_renderer.render(request, language, path)\n        title_text = page_renderer.title()\n        return html(\n            str(builder),\n            headers={\n                \"vary\": \"hx-request\",\n                \"x-title\": title_text,\n            },\n        )\n\n    @app.on_request\n    async def set_language(request: Request):\n        request.ctx.language = request.match_info.get(\n            \"language\", Page.DEFAULT_LANGUAGE\n        )\n\n    return app\n"
  },
  {
    "path": "guide/webapp/worker/livereload.js",
    "content": "(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c=\"function\"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error(\"Cannot find module '\"+i+\"'\");throw a.code=\"MODULE_NOT_FOUND\",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u=\"function\"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){\nmodule.exports = function (it) {\n  if (typeof it != 'function') throw TypeError(it + ' is not a function!');\n  return it;\n};\n\n},{}],2:[function(require,module,exports){\n// 22.1.3.31 Array.prototype[@@unscopables]\nvar UNSCOPABLES = require('./_wks')('unscopables');\nvar ArrayProto = Array.prototype;\nif (ArrayProto[UNSCOPABLES] == undefined) require('./_hide')(ArrayProto, UNSCOPABLES, {});\nmodule.exports = function (key) {\n  ArrayProto[UNSCOPABLES][key] = true;\n};\n\n},{\"./_hide\":27,\"./_wks\":81}],3:[function(require,module,exports){\n'use strict';\nvar at = require('./_string-at')(true);\n\n // `AdvanceStringIndex` abstract operation\n// https://tc39.github.io/ecma262/#sec-advancestringindex\nmodule.exports = function (S, index, unicode) {\n  return index + (unicode ? at(S, index).length : 1);\n};\n\n},{\"./_string-at\":68}],4:[function(require,module,exports){\nvar isObject = require('./_is-object');\nmodule.exports = function (it) {\n  if (!isObject(it)) throw TypeError(it + ' is not an object!');\n  return it;\n};\n\n},{\"./_is-object\":34}],5:[function(require,module,exports){\n// false -> Array#indexOf\n// true  -> Array#includes\nvar toIObject = require('./_to-iobject');\nvar toLength = require('./_to-length');\nvar toAbsoluteIndex = require('./_to-absolute-index');\nmodule.exports = function (IS_INCLUDES) {\n  return function ($this, el, fromIndex) {\n    var O = toIObject($this);\n    var length = toLength(O.length);\n    var index = toAbsoluteIndex(fromIndex, length);\n    var value;\n    // Array#includes uses SameValueZero equality algorithm\n    // eslint-disable-next-line no-self-compare\n    if (IS_INCLUDES && el != el) while (length > index) {\n      value = O[index++];\n      // eslint-disable-next-line no-self-compare\n      if (value != value) return true;\n    // Array#indexOf ignores holes, Array#includes - not\n    } else for (;length > index; index++) if (IS_INCLUDES || index in O) {\n      if (O[index] === el) return IS_INCLUDES || index || 0;\n    } return !IS_INCLUDES && -1;\n  };\n};\n\n},{\"./_to-absolute-index\":72,\"./_to-iobject\":74,\"./_to-length\":75}],6:[function(require,module,exports){\n// 0 -> Array#forEach\n// 1 -> Array#map\n// 2 -> Array#filter\n// 3 -> Array#some\n// 4 -> Array#every\n// 5 -> Array#find\n// 6 -> Array#findIndex\nvar ctx = require('./_ctx');\nvar IObject = require('./_iobject');\nvar toObject = require('./_to-object');\nvar toLength = require('./_to-length');\nvar asc = require('./_array-species-create');\nmodule.exports = function (TYPE, $create) {\n  var IS_MAP = TYPE == 1;\n  var IS_FILTER = TYPE == 2;\n  var IS_SOME = TYPE == 3;\n  var IS_EVERY = TYPE == 4;\n  var IS_FIND_INDEX = TYPE == 6;\n  var NO_HOLES = TYPE == 5 || IS_FIND_INDEX;\n  var create = $create || asc;\n  return function ($this, callbackfn, that) {\n    var O = toObject($this);\n    var self = IObject(O);\n    var f = ctx(callbackfn, that, 3);\n    var length = toLength(self.length);\n    var index = 0;\n    var result = IS_MAP ? create($this, length) : IS_FILTER ? create($this, 0) : undefined;\n    var val, res;\n    for (;length > index; index++) if (NO_HOLES || index in self) {\n      val = self[index];\n      res = f(val, index, O);\n      if (TYPE) {\n        if (IS_MAP) result[index] = res;   // map\n        else if (res) switch (TYPE) {\n          case 3: return true;             // some\n          case 5: return val;              // find\n          case 6: return index;            // findIndex\n          case 2: result.push(val);        // filter\n        } else if (IS_EVERY) return false; // every\n      }\n    }\n    return IS_FIND_INDEX ? -1 : IS_SOME || IS_EVERY ? IS_EVERY : result;\n  };\n};\n\n},{\"./_array-species-create\":8,\"./_ctx\":13,\"./_iobject\":31,\"./_to-length\":75,\"./_to-object\":76}],7:[function(require,module,exports){\nvar isObject = require('./_is-object');\nvar isArray = require('./_is-array');\nvar SPECIES = require('./_wks')('species');\n\nmodule.exports = function (original) {\n  var C;\n  if (isArray(original)) {\n    C = original.constructor;\n    // cross-realm fallback\n    if (typeof C == 'function' && (C === Array || isArray(C.prototype))) C = undefined;\n    if (isObject(C)) {\n      C = C[SPECIES];\n      if (C === null) C = undefined;\n    }\n  } return C === undefined ? Array : C;\n};\n\n},{\"./_is-array\":33,\"./_is-object\":34,\"./_wks\":81}],8:[function(require,module,exports){\n// 9.4.2.3 ArraySpeciesCreate(originalArray, length)\nvar speciesConstructor = require('./_array-species-constructor');\n\nmodule.exports = function (original, length) {\n  return new (speciesConstructor(original))(length);\n};\n\n},{\"./_array-species-constructor\":7}],9:[function(require,module,exports){\n// getting tag from 19.1.3.6 Object.prototype.toString()\nvar cof = require('./_cof');\nvar TAG = require('./_wks')('toStringTag');\n// ES3 wrong here\nvar ARG = cof(function () { return arguments; }()) == 'Arguments';\n\n// fallback for IE11 Script Access Denied error\nvar tryGet = function (it, key) {\n  try {\n    return it[key];\n  } catch (e) { /* empty */ }\n};\n\nmodule.exports = function (it) {\n  var O, T, B;\n  return it === undefined ? 'Undefined' : it === null ? 'Null'\n    // @@toStringTag case\n    : typeof (T = tryGet(O = Object(it), TAG)) == 'string' ? T\n    // builtinTag case\n    : ARG ? cof(O)\n    // ES3 arguments fallback\n    : (B = cof(O)) == 'Object' && typeof O.callee == 'function' ? 'Arguments' : B;\n};\n\n},{\"./_cof\":10,\"./_wks\":81}],10:[function(require,module,exports){\nvar toString = {}.toString;\n\nmodule.exports = function (it) {\n  return toString.call(it).slice(8, -1);\n};\n\n},{}],11:[function(require,module,exports){\nvar core = module.exports = { version: '2.6.12' };\nif (typeof __e == 'number') __e = core; // eslint-disable-line no-undef\n\n},{}],12:[function(require,module,exports){\n'use strict';\nvar $defineProperty = require('./_object-dp');\nvar createDesc = require('./_property-desc');\n\nmodule.exports = function (object, index, value) {\n  if (index in object) $defineProperty.f(object, index, createDesc(0, value));\n  else object[index] = value;\n};\n\n},{\"./_object-dp\":45,\"./_property-desc\":57}],13:[function(require,module,exports){\n// optional / simple context binding\nvar aFunction = require('./_a-function');\nmodule.exports = function (fn, that, length) {\n  aFunction(fn);\n  if (that === undefined) return fn;\n  switch (length) {\n    case 1: return function (a) {\n      return fn.call(that, a);\n    };\n    case 2: return function (a, b) {\n      return fn.call(that, a, b);\n    };\n    case 3: return function (a, b, c) {\n      return fn.call(that, a, b, c);\n    };\n  }\n  return function (/* ...args */) {\n    return fn.apply(that, arguments);\n  };\n};\n\n},{\"./_a-function\":1}],14:[function(require,module,exports){\n// 7.2.1 RequireObjectCoercible(argument)\nmodule.exports = function (it) {\n  if (it == undefined) throw TypeError(\"Can't call method on  \" + it);\n  return it;\n};\n\n},{}],15:[function(require,module,exports){\n// Thank's IE8 for his funny defineProperty\nmodule.exports = !require('./_fails')(function () {\n  return Object.defineProperty({}, 'a', { get: function () { return 7; } }).a != 7;\n});\n\n},{\"./_fails\":21}],16:[function(require,module,exports){\nvar isObject = require('./_is-object');\nvar document = require('./_global').document;\n// typeof document.createElement is 'object' in old IE\nvar is = isObject(document) && isObject(document.createElement);\nmodule.exports = function (it) {\n  return is ? document.createElement(it) : {};\n};\n\n},{\"./_global\":25,\"./_is-object\":34}],17:[function(require,module,exports){\n// IE 8- don't enum bug keys\nmodule.exports = (\n  'constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf'\n).split(',');\n\n},{}],18:[function(require,module,exports){\n// all enumerable object keys, includes symbols\nvar getKeys = require('./_object-keys');\nvar gOPS = require('./_object-gops');\nvar pIE = require('./_object-pie');\nmodule.exports = function (it) {\n  var result = getKeys(it);\n  var getSymbols = gOPS.f;\n  if (getSymbols) {\n    var symbols = getSymbols(it);\n    var isEnum = pIE.f;\n    var i = 0;\n    var key;\n    while (symbols.length > i) if (isEnum.call(it, key = symbols[i++])) result.push(key);\n  } return result;\n};\n\n},{\"./_object-gops\":50,\"./_object-keys\":53,\"./_object-pie\":54}],19:[function(require,module,exports){\nvar global = require('./_global');\nvar core = require('./_core');\nvar hide = require('./_hide');\nvar redefine = require('./_redefine');\nvar ctx = require('./_ctx');\nvar PROTOTYPE = 'prototype';\n\nvar $export = function (type, name, source) {\n  var IS_FORCED = type & $export.F;\n  var IS_GLOBAL = type & $export.G;\n  var IS_STATIC = type & $export.S;\n  var IS_PROTO = type & $export.P;\n  var IS_BIND = type & $export.B;\n  var target = IS_GLOBAL ? global : IS_STATIC ? global[name] || (global[name] = {}) : (global[name] || {})[PROTOTYPE];\n  var exports = IS_GLOBAL ? core : core[name] || (core[name] = {});\n  var expProto = exports[PROTOTYPE] || (exports[PROTOTYPE] = {});\n  var key, own, out, exp;\n  if (IS_GLOBAL) source = name;\n  for (key in source) {\n    // contains in native\n    own = !IS_FORCED && target && target[key] !== undefined;\n    // export native or passed\n    out = (own ? target : source)[key];\n    // bind timers to global for call from export context\n    exp = IS_BIND && own ? ctx(out, global) : IS_PROTO && typeof out == 'function' ? ctx(Function.call, out) : out;\n    // extend global\n    if (target) redefine(target, key, out, type & $export.U);\n    // export\n    if (exports[key] != out) hide(exports, key, exp);\n    if (IS_PROTO && expProto[key] != out) expProto[key] = out;\n  }\n};\nglobal.core = core;\n// type bitmap\n$export.F = 1;   // forced\n$export.G = 2;   // global\n$export.S = 4;   // static\n$export.P = 8;   // proto\n$export.B = 16;  // bind\n$export.W = 32;  // wrap\n$export.U = 64;  // safe\n$export.R = 128; // real proto method for `library`\nmodule.exports = $export;\n\n},{\"./_core\":11,\"./_ctx\":13,\"./_global\":25,\"./_hide\":27,\"./_redefine\":58}],20:[function(require,module,exports){\nvar MATCH = require('./_wks')('match');\nmodule.exports = function (KEY) {\n  var re = /./;\n  try {\n    '/./'[KEY](re);\n  } catch (e) {\n    try {\n      re[MATCH] = false;\n      return !'/./'[KEY](re);\n    } catch (f) { /* empty */ }\n  } return true;\n};\n\n},{\"./_wks\":81}],21:[function(require,module,exports){\nmodule.exports = function (exec) {\n  try {\n    return !!exec();\n  } catch (e) {\n    return true;\n  }\n};\n\n},{}],22:[function(require,module,exports){\n'use strict';\nrequire('./es6.regexp.exec');\nvar redefine = require('./_redefine');\nvar hide = require('./_hide');\nvar fails = require('./_fails');\nvar defined = require('./_defined');\nvar wks = require('./_wks');\nvar regexpExec = require('./_regexp-exec');\n\nvar SPECIES = wks('species');\n\nvar REPLACE_SUPPORTS_NAMED_GROUPS = !fails(function () {\n  // #replace needs built-in support for named groups.\n  // #match works fine because it just return the exec results, even if it has\n  // a \"grops\" property.\n  var re = /./;\n  re.exec = function () {\n    var result = [];\n    result.groups = { a: '7' };\n    return result;\n  };\n  return ''.replace(re, '$<a>') !== '7';\n});\n\nvar SPLIT_WORKS_WITH_OVERWRITTEN_EXEC = (function () {\n  // Chrome 51 has a buggy \"split\" implementation when RegExp#exec !== nativeExec\n  var re = /(?:)/;\n  var originalExec = re.exec;\n  re.exec = function () { return originalExec.apply(this, arguments); };\n  var result = 'ab'.split(re);\n  return result.length === 2 && result[0] === 'a' && result[1] === 'b';\n})();\n\nmodule.exports = function (KEY, length, exec) {\n  var SYMBOL = wks(KEY);\n\n  var DELEGATES_TO_SYMBOL = !fails(function () {\n    // String methods call symbol-named RegEp methods\n    var O = {};\n    O[SYMBOL] = function () { return 7; };\n    return ''[KEY](O) != 7;\n  });\n\n  var DELEGATES_TO_EXEC = DELEGATES_TO_SYMBOL ? !fails(function () {\n    // Symbol-named RegExp methods call .exec\n    var execCalled = false;\n    var re = /a/;\n    re.exec = function () { execCalled = true; return null; };\n    if (KEY === 'split') {\n      // RegExp[@@split] doesn't call the regex's exec method, but first creates\n      // a new one. We need to return the patched regex when creating the new one.\n      re.constructor = {};\n      re.constructor[SPECIES] = function () { return re; };\n    }\n    re[SYMBOL]('');\n    return !execCalled;\n  }) : undefined;\n\n  if (\n    !DELEGATES_TO_SYMBOL ||\n    !DELEGATES_TO_EXEC ||\n    (KEY === 'replace' && !REPLACE_SUPPORTS_NAMED_GROUPS) ||\n    (KEY === 'split' && !SPLIT_WORKS_WITH_OVERWRITTEN_EXEC)\n  ) {\n    var nativeRegExpMethod = /./[SYMBOL];\n    var fns = exec(\n      defined,\n      SYMBOL,\n      ''[KEY],\n      function maybeCallNative(nativeMethod, regexp, str, arg2, forceStringMethod) {\n        if (regexp.exec === regexpExec) {\n          if (DELEGATES_TO_SYMBOL && !forceStringMethod) {\n            // The native String method already delegates to @@method (this\n            // polyfilled function), leasing to infinite recursion.\n            // We avoid it by directly calling the native @@method method.\n            return { done: true, value: nativeRegExpMethod.call(regexp, str, arg2) };\n          }\n          return { done: true, value: nativeMethod.call(str, regexp, arg2) };\n        }\n        return { done: false };\n      }\n    );\n    var strfn = fns[0];\n    var rxfn = fns[1];\n\n    redefine(String.prototype, KEY, strfn);\n    hide(RegExp.prototype, SYMBOL, length == 2\n      // 21.2.5.8 RegExp.prototype[@@replace](string, replaceValue)\n      // 21.2.5.11 RegExp.prototype[@@split](string, limit)\n      ? function (string, arg) { return rxfn.call(string, this, arg); }\n      // 21.2.5.6 RegExp.prototype[@@match](string)\n      // 21.2.5.9 RegExp.prototype[@@search](string)\n      : function (string) { return rxfn.call(string, this); }\n    );\n  }\n};\n\n},{\"./_defined\":14,\"./_fails\":21,\"./_hide\":27,\"./_redefine\":58,\"./_regexp-exec\":60,\"./_wks\":81,\"./es6.regexp.exec\":93}],23:[function(require,module,exports){\n'use strict';\n// 21.2.5.3 get RegExp.prototype.flags\nvar anObject = require('./_an-object');\nmodule.exports = function () {\n  var that = anObject(this);\n  var result = '';\n  if (that.global) result += 'g';\n  if (that.ignoreCase) result += 'i';\n  if (that.multiline) result += 'm';\n  if (that.unicode) result += 'u';\n  if (that.sticky) result += 'y';\n  return result;\n};\n\n},{\"./_an-object\":4}],24:[function(require,module,exports){\nmodule.exports = require('./_shared')('native-function-to-string', Function.toString);\n\n},{\"./_shared\":65}],25:[function(require,module,exports){\n// https://github.com/zloirock/core-js/issues/86#issuecomment-115759028\nvar global = module.exports = typeof window != 'undefined' && window.Math == Math\n  ? window : typeof self != 'undefined' && self.Math == Math ? self\n  // eslint-disable-next-line no-new-func\n  : Function('return this')();\nif (typeof __g == 'number') __g = global; // eslint-disable-line no-undef\n\n},{}],26:[function(require,module,exports){\nvar hasOwnProperty = {}.hasOwnProperty;\nmodule.exports = function (it, key) {\n  return hasOwnProperty.call(it, key);\n};\n\n},{}],27:[function(require,module,exports){\nvar dP = require('./_object-dp');\nvar createDesc = require('./_property-desc');\nmodule.exports = require('./_descriptors') ? function (object, key, value) {\n  return dP.f(object, key, createDesc(1, value));\n} : function (object, key, value) {\n  object[key] = value;\n  return object;\n};\n\n},{\"./_descriptors\":15,\"./_object-dp\":45,\"./_property-desc\":57}],28:[function(require,module,exports){\nvar document = require('./_global').document;\nmodule.exports = document && document.documentElement;\n\n},{\"./_global\":25}],29:[function(require,module,exports){\nmodule.exports = !require('./_descriptors') && !require('./_fails')(function () {\n  return Object.defineProperty(require('./_dom-create')('div'), 'a', { get: function () { return 7; } }).a != 7;\n});\n\n},{\"./_descriptors\":15,\"./_dom-create\":16,\"./_fails\":21}],30:[function(require,module,exports){\nvar isObject = require('./_is-object');\nvar setPrototypeOf = require('./_set-proto').set;\nmodule.exports = function (that, target, C) {\n  var S = target.constructor;\n  var P;\n  if (S !== C && typeof S == 'function' && (P = S.prototype) !== C.prototype && isObject(P) && setPrototypeOf) {\n    setPrototypeOf(that, P);\n  } return that;\n};\n\n},{\"./_is-object\":34,\"./_set-proto\":61}],31:[function(require,module,exports){\n// fallback for non-array-like ES3 and non-enumerable old V8 strings\nvar cof = require('./_cof');\n// eslint-disable-next-line no-prototype-builtins\nmodule.exports = Object('z').propertyIsEnumerable(0) ? Object : function (it) {\n  return cof(it) == 'String' ? it.split('') : Object(it);\n};\n\n},{\"./_cof\":10}],32:[function(require,module,exports){\n// check on default Array iterator\nvar Iterators = require('./_iterators');\nvar ITERATOR = require('./_wks')('iterator');\nvar ArrayProto = Array.prototype;\n\nmodule.exports = function (it) {\n  return it !== undefined && (Iterators.Array === it || ArrayProto[ITERATOR] === it);\n};\n\n},{\"./_iterators\":41,\"./_wks\":81}],33:[function(require,module,exports){\n// 7.2.2 IsArray(argument)\nvar cof = require('./_cof');\nmodule.exports = Array.isArray || function isArray(arg) {\n  return cof(arg) == 'Array';\n};\n\n},{\"./_cof\":10}],34:[function(require,module,exports){\nmodule.exports = function (it) {\n  return typeof it === 'object' ? it !== null : typeof it === 'function';\n};\n\n},{}],35:[function(require,module,exports){\n// 7.2.8 IsRegExp(argument)\nvar isObject = require('./_is-object');\nvar cof = require('./_cof');\nvar MATCH = require('./_wks')('match');\nmodule.exports = function (it) {\n  var isRegExp;\n  return isObject(it) && ((isRegExp = it[MATCH]) !== undefined ? !!isRegExp : cof(it) == 'RegExp');\n};\n\n},{\"./_cof\":10,\"./_is-object\":34,\"./_wks\":81}],36:[function(require,module,exports){\n// call something on iterator step with safe closing on error\nvar anObject = require('./_an-object');\nmodule.exports = function (iterator, fn, value, entries) {\n  try {\n    return entries ? fn(anObject(value)[0], value[1]) : fn(value);\n  // 7.4.6 IteratorClose(iterator, completion)\n  } catch (e) {\n    var ret = iterator['return'];\n    if (ret !== undefined) anObject(ret.call(iterator));\n    throw e;\n  }\n};\n\n},{\"./_an-object\":4}],37:[function(require,module,exports){\n'use strict';\nvar create = require('./_object-create');\nvar descriptor = require('./_property-desc');\nvar setToStringTag = require('./_set-to-string-tag');\nvar IteratorPrototype = {};\n\n// 25.1.2.1.1 %IteratorPrototype%[@@iterator]()\nrequire('./_hide')(IteratorPrototype, require('./_wks')('iterator'), function () { return this; });\n\nmodule.exports = function (Constructor, NAME, next) {\n  Constructor.prototype = create(IteratorPrototype, { next: descriptor(1, next) });\n  setToStringTag(Constructor, NAME + ' Iterator');\n};\n\n},{\"./_hide\":27,\"./_object-create\":44,\"./_property-desc\":57,\"./_set-to-string-tag\":63,\"./_wks\":81}],38:[function(require,module,exports){\n'use strict';\nvar LIBRARY = require('./_library');\nvar $export = require('./_export');\nvar redefine = require('./_redefine');\nvar hide = require('./_hide');\nvar Iterators = require('./_iterators');\nvar $iterCreate = require('./_iter-create');\nvar setToStringTag = require('./_set-to-string-tag');\nvar getPrototypeOf = require('./_object-gpo');\nvar ITERATOR = require('./_wks')('iterator');\nvar BUGGY = !([].keys && 'next' in [].keys()); // Safari has buggy iterators w/o `next`\nvar FF_ITERATOR = '@@iterator';\nvar KEYS = 'keys';\nvar VALUES = 'values';\n\nvar returnThis = function () { return this; };\n\nmodule.exports = function (Base, NAME, Constructor, next, DEFAULT, IS_SET, FORCED) {\n  $iterCreate(Constructor, NAME, next);\n  var getMethod = function (kind) {\n    if (!BUGGY && kind in proto) return proto[kind];\n    switch (kind) {\n      case KEYS: return function keys() { return new Constructor(this, kind); };\n      case VALUES: return function values() { return new Constructor(this, kind); };\n    } return function entries() { return new Constructor(this, kind); };\n  };\n  var TAG = NAME + ' Iterator';\n  var DEF_VALUES = DEFAULT == VALUES;\n  var VALUES_BUG = false;\n  var proto = Base.prototype;\n  var $native = proto[ITERATOR] || proto[FF_ITERATOR] || DEFAULT && proto[DEFAULT];\n  var $default = $native || getMethod(DEFAULT);\n  var $entries = DEFAULT ? !DEF_VALUES ? $default : getMethod('entries') : undefined;\n  var $anyNative = NAME == 'Array' ? proto.entries || $native : $native;\n  var methods, key, IteratorPrototype;\n  // Fix native\n  if ($anyNative) {\n    IteratorPrototype = getPrototypeOf($anyNative.call(new Base()));\n    if (IteratorPrototype !== Object.prototype && IteratorPrototype.next) {\n      // Set @@toStringTag to native iterators\n      setToStringTag(IteratorPrototype, TAG, true);\n      // fix for some old engines\n      if (!LIBRARY && typeof IteratorPrototype[ITERATOR] != 'function') hide(IteratorPrototype, ITERATOR, returnThis);\n    }\n  }\n  // fix Array#{values, @@iterator}.name in V8 / FF\n  if (DEF_VALUES && $native && $native.name !== VALUES) {\n    VALUES_BUG = true;\n    $default = function values() { return $native.call(this); };\n  }\n  // Define iterator\n  if ((!LIBRARY || FORCED) && (BUGGY || VALUES_BUG || !proto[ITERATOR])) {\n    hide(proto, ITERATOR, $default);\n  }\n  // Plug for library\n  Iterators[NAME] = $default;\n  Iterators[TAG] = returnThis;\n  if (DEFAULT) {\n    methods = {\n      values: DEF_VALUES ? $default : getMethod(VALUES),\n      keys: IS_SET ? $default : getMethod(KEYS),\n      entries: $entries\n    };\n    if (FORCED) for (key in methods) {\n      if (!(key in proto)) redefine(proto, key, methods[key]);\n    } else $export($export.P + $export.F * (BUGGY || VALUES_BUG), NAME, methods);\n  }\n  return methods;\n};\n\n},{\"./_export\":19,\"./_hide\":27,\"./_iter-create\":37,\"./_iterators\":41,\"./_library\":42,\"./_object-gpo\":51,\"./_redefine\":58,\"./_set-to-string-tag\":63,\"./_wks\":81}],39:[function(require,module,exports){\nvar ITERATOR = require('./_wks')('iterator');\nvar SAFE_CLOSING = false;\n\ntry {\n  var riter = [7][ITERATOR]();\n  riter['return'] = function () { SAFE_CLOSING = true; };\n  // eslint-disable-next-line no-throw-literal\n  Array.from(riter, function () { throw 2; });\n} catch (e) { /* empty */ }\n\nmodule.exports = function (exec, skipClosing) {\n  if (!skipClosing && !SAFE_CLOSING) return false;\n  var safe = false;\n  try {\n    var arr = [7];\n    var iter = arr[ITERATOR]();\n    iter.next = function () { return { done: safe = true }; };\n    arr[ITERATOR] = function () { return iter; };\n    exec(arr);\n  } catch (e) { /* empty */ }\n  return safe;\n};\n\n},{\"./_wks\":81}],40:[function(require,module,exports){\nmodule.exports = function (done, value) {\n  return { value: value, done: !!done };\n};\n\n},{}],41:[function(require,module,exports){\nmodule.exports = {};\n\n},{}],42:[function(require,module,exports){\nmodule.exports = false;\n\n},{}],43:[function(require,module,exports){\nvar META = require('./_uid')('meta');\nvar isObject = require('./_is-object');\nvar has = require('./_has');\nvar setDesc = require('./_object-dp').f;\nvar id = 0;\nvar isExtensible = Object.isExtensible || function () {\n  return true;\n};\nvar FREEZE = !require('./_fails')(function () {\n  return isExtensible(Object.preventExtensions({}));\n});\nvar setMeta = function (it) {\n  setDesc(it, META, { value: {\n    i: 'O' + ++id, // object ID\n    w: {}          // weak collections IDs\n  } });\n};\nvar fastKey = function (it, create) {\n  // return primitive with prefix\n  if (!isObject(it)) return typeof it == 'symbol' ? it : (typeof it == 'string' ? 'S' : 'P') + it;\n  if (!has(it, META)) {\n    // can't set metadata to uncaught frozen object\n    if (!isExtensible(it)) return 'F';\n    // not necessary to add metadata\n    if (!create) return 'E';\n    // add missing metadata\n    setMeta(it);\n  // return object ID\n  } return it[META].i;\n};\nvar getWeak = function (it, create) {\n  if (!has(it, META)) {\n    // can't set metadata to uncaught frozen object\n    if (!isExtensible(it)) return true;\n    // not necessary to add metadata\n    if (!create) return false;\n    // add missing metadata\n    setMeta(it);\n  // return hash weak collections IDs\n  } return it[META].w;\n};\n// add metadata on freeze-family methods calling\nvar onFreeze = function (it) {\n  if (FREEZE && meta.NEED && isExtensible(it) && !has(it, META)) setMeta(it);\n  return it;\n};\nvar meta = module.exports = {\n  KEY: META,\n  NEED: false,\n  fastKey: fastKey,\n  getWeak: getWeak,\n  onFreeze: onFreeze\n};\n\n},{\"./_fails\":21,\"./_has\":26,\"./_is-object\":34,\"./_object-dp\":45,\"./_uid\":78}],44:[function(require,module,exports){\n// 19.1.2.2 / 15.2.3.5 Object.create(O [, Properties])\nvar anObject = require('./_an-object');\nvar dPs = require('./_object-dps');\nvar enumBugKeys = require('./_enum-bug-keys');\nvar IE_PROTO = require('./_shared-key')('IE_PROTO');\nvar Empty = function () { /* empty */ };\nvar PROTOTYPE = 'prototype';\n\n// Create object with fake `null` prototype: use iframe Object with cleared prototype\nvar createDict = function () {\n  // Thrash, waste and sodomy: IE GC bug\n  var iframe = require('./_dom-create')('iframe');\n  var i = enumBugKeys.length;\n  var lt = '<';\n  var gt = '>';\n  var iframeDocument;\n  iframe.style.display = 'none';\n  require('./_html').appendChild(iframe);\n  iframe.src = 'javascript:'; // eslint-disable-line no-script-url\n  // createDict = iframe.contentWindow.Object;\n  // html.removeChild(iframe);\n  iframeDocument = iframe.contentWindow.document;\n  iframeDocument.open();\n  iframeDocument.write(lt + 'script' + gt + 'document.F=Object' + lt + '/script' + gt);\n  iframeDocument.close();\n  createDict = iframeDocument.F;\n  while (i--) delete createDict[PROTOTYPE][enumBugKeys[i]];\n  return createDict();\n};\n\nmodule.exports = Object.create || function create(O, Properties) {\n  var result;\n  if (O !== null) {\n    Empty[PROTOTYPE] = anObject(O);\n    result = new Empty();\n    Empty[PROTOTYPE] = null;\n    // add \"__proto__\" for Object.getPrototypeOf polyfill\n    result[IE_PROTO] = O;\n  } else result = createDict();\n  return Properties === undefined ? result : dPs(result, Properties);\n};\n\n},{\"./_an-object\":4,\"./_dom-create\":16,\"./_enum-bug-keys\":17,\"./_html\":28,\"./_object-dps\":46,\"./_shared-key\":64}],45:[function(require,module,exports){\nvar anObject = require('./_an-object');\nvar IE8_DOM_DEFINE = require('./_ie8-dom-define');\nvar toPrimitive = require('./_to-primitive');\nvar dP = Object.defineProperty;\n\nexports.f = require('./_descriptors') ? Object.defineProperty : function defineProperty(O, P, Attributes) {\n  anObject(O);\n  P = toPrimitive(P, true);\n  anObject(Attributes);\n  if (IE8_DOM_DEFINE) try {\n    return dP(O, P, Attributes);\n  } catch (e) { /* empty */ }\n  if ('get' in Attributes || 'set' in Attributes) throw TypeError('Accessors not supported!');\n  if ('value' in Attributes) O[P] = Attributes.value;\n  return O;\n};\n\n},{\"./_an-object\":4,\"./_descriptors\":15,\"./_ie8-dom-define\":29,\"./_to-primitive\":77}],46:[function(require,module,exports){\nvar dP = require('./_object-dp');\nvar anObject = require('./_an-object');\nvar getKeys = require('./_object-keys');\n\nmodule.exports = require('./_descriptors') ? Object.defineProperties : function defineProperties(O, Properties) {\n  anObject(O);\n  var keys = getKeys(Properties);\n  var length = keys.length;\n  var i = 0;\n  var P;\n  while (length > i) dP.f(O, P = keys[i++], Properties[P]);\n  return O;\n};\n\n},{\"./_an-object\":4,\"./_descriptors\":15,\"./_object-dp\":45,\"./_object-keys\":53}],47:[function(require,module,exports){\nvar pIE = require('./_object-pie');\nvar createDesc = require('./_property-desc');\nvar toIObject = require('./_to-iobject');\nvar toPrimitive = require('./_to-primitive');\nvar has = require('./_has');\nvar IE8_DOM_DEFINE = require('./_ie8-dom-define');\nvar gOPD = Object.getOwnPropertyDescriptor;\n\nexports.f = require('./_descriptors') ? gOPD : function getOwnPropertyDescriptor(O, P) {\n  O = toIObject(O);\n  P = toPrimitive(P, true);\n  if (IE8_DOM_DEFINE) try {\n    return gOPD(O, P);\n  } catch (e) { /* empty */ }\n  if (has(O, P)) return createDesc(!pIE.f.call(O, P), O[P]);\n};\n\n},{\"./_descriptors\":15,\"./_has\":26,\"./_ie8-dom-define\":29,\"./_object-pie\":54,\"./_property-desc\":57,\"./_to-iobject\":74,\"./_to-primitive\":77}],48:[function(require,module,exports){\n// fallback for IE11 buggy Object.getOwnPropertyNames with iframe and window\nvar toIObject = require('./_to-iobject');\nvar gOPN = require('./_object-gopn').f;\nvar toString = {}.toString;\n\nvar windowNames = typeof window == 'object' && window && Object.getOwnPropertyNames\n  ? Object.getOwnPropertyNames(window) : [];\n\nvar getWindowNames = function (it) {\n  try {\n    return gOPN(it);\n  } catch (e) {\n    return windowNames.slice();\n  }\n};\n\nmodule.exports.f = function getOwnPropertyNames(it) {\n  return windowNames && toString.call(it) == '[object Window]' ? getWindowNames(it) : gOPN(toIObject(it));\n};\n\n},{\"./_object-gopn\":49,\"./_to-iobject\":74}],49:[function(require,module,exports){\n// 19.1.2.7 / 15.2.3.4 Object.getOwnPropertyNames(O)\nvar $keys = require('./_object-keys-internal');\nvar hiddenKeys = require('./_enum-bug-keys').concat('length', 'prototype');\n\nexports.f = Object.getOwnPropertyNames || function getOwnPropertyNames(O) {\n  return $keys(O, hiddenKeys);\n};\n\n},{\"./_enum-bug-keys\":17,\"./_object-keys-internal\":52}],50:[function(require,module,exports){\nexports.f = Object.getOwnPropertySymbols;\n\n},{}],51:[function(require,module,exports){\n// 19.1.2.9 / 15.2.3.2 Object.getPrototypeOf(O)\nvar has = require('./_has');\nvar toObject = require('./_to-object');\nvar IE_PROTO = require('./_shared-key')('IE_PROTO');\nvar ObjectProto = Object.prototype;\n\nmodule.exports = Object.getPrototypeOf || function (O) {\n  O = toObject(O);\n  if (has(O, IE_PROTO)) return O[IE_PROTO];\n  if (typeof O.constructor == 'function' && O instanceof O.constructor) {\n    return O.constructor.prototype;\n  } return O instanceof Object ? ObjectProto : null;\n};\n\n},{\"./_has\":26,\"./_shared-key\":64,\"./_to-object\":76}],52:[function(require,module,exports){\nvar has = require('./_has');\nvar toIObject = require('./_to-iobject');\nvar arrayIndexOf = require('./_array-includes')(false);\nvar IE_PROTO = require('./_shared-key')('IE_PROTO');\n\nmodule.exports = function (object, names) {\n  var O = toIObject(object);\n  var i = 0;\n  var result = [];\n  var key;\n  for (key in O) if (key != IE_PROTO) has(O, key) && result.push(key);\n  // Don't enum bug & hidden keys\n  while (names.length > i) if (has(O, key = names[i++])) {\n    ~arrayIndexOf(result, key) || result.push(key);\n  }\n  return result;\n};\n\n},{\"./_array-includes\":5,\"./_has\":26,\"./_shared-key\":64,\"./_to-iobject\":74}],53:[function(require,module,exports){\n// 19.1.2.14 / 15.2.3.14 Object.keys(O)\nvar $keys = require('./_object-keys-internal');\nvar enumBugKeys = require('./_enum-bug-keys');\n\nmodule.exports = Object.keys || function keys(O) {\n  return $keys(O, enumBugKeys);\n};\n\n},{\"./_enum-bug-keys\":17,\"./_object-keys-internal\":52}],54:[function(require,module,exports){\nexports.f = {}.propertyIsEnumerable;\n\n},{}],55:[function(require,module,exports){\n// most Object methods by ES6 should accept primitives\nvar $export = require('./_export');\nvar core = require('./_core');\nvar fails = require('./_fails');\nmodule.exports = function (KEY, exec) {\n  var fn = (core.Object || {})[KEY] || Object[KEY];\n  var exp = {};\n  exp[KEY] = exec(fn);\n  $export($export.S + $export.F * fails(function () { fn(1); }), 'Object', exp);\n};\n\n},{\"./_core\":11,\"./_export\":19,\"./_fails\":21}],56:[function(require,module,exports){\n// all object keys, includes non-enumerable and symbols\nvar gOPN = require('./_object-gopn');\nvar gOPS = require('./_object-gops');\nvar anObject = require('./_an-object');\nvar Reflect = require('./_global').Reflect;\nmodule.exports = Reflect && Reflect.ownKeys || function ownKeys(it) {\n  var keys = gOPN.f(anObject(it));\n  var getSymbols = gOPS.f;\n  return getSymbols ? keys.concat(getSymbols(it)) : keys;\n};\n\n},{\"./_an-object\":4,\"./_global\":25,\"./_object-gopn\":49,\"./_object-gops\":50}],57:[function(require,module,exports){\nmodule.exports = function (bitmap, value) {\n  return {\n    enumerable: !(bitmap & 1),\n    configurable: !(bitmap & 2),\n    writable: !(bitmap & 4),\n    value: value\n  };\n};\n\n},{}],58:[function(require,module,exports){\nvar global = require('./_global');\nvar hide = require('./_hide');\nvar has = require('./_has');\nvar SRC = require('./_uid')('src');\nvar $toString = require('./_function-to-string');\nvar TO_STRING = 'toString';\nvar TPL = ('' + $toString).split(TO_STRING);\n\nrequire('./_core').inspectSource = function (it) {\n  return $toString.call(it);\n};\n\n(module.exports = function (O, key, val, safe) {\n  var isFunction = typeof val == 'function';\n  if (isFunction) has(val, 'name') || hide(val, 'name', key);\n  if (O[key] === val) return;\n  if (isFunction) has(val, SRC) || hide(val, SRC, O[key] ? '' + O[key] : TPL.join(String(key)));\n  if (O === global) {\n    O[key] = val;\n  } else if (!safe) {\n    delete O[key];\n    hide(O, key, val);\n  } else if (O[key]) {\n    O[key] = val;\n  } else {\n    hide(O, key, val);\n  }\n// add fake Function#toString for correct work wrapped methods / constructors with methods like LoDash isNative\n})(Function.prototype, TO_STRING, function toString() {\n  return typeof this == 'function' && this[SRC] || $toString.call(this);\n});\n\n},{\"./_core\":11,\"./_function-to-string\":24,\"./_global\":25,\"./_has\":26,\"./_hide\":27,\"./_uid\":78}],59:[function(require,module,exports){\n'use strict';\n\nvar classof = require('./_classof');\nvar builtinExec = RegExp.prototype.exec;\n\n // `RegExpExec` abstract operation\n// https://tc39.github.io/ecma262/#sec-regexpexec\nmodule.exports = function (R, S) {\n  var exec = R.exec;\n  if (typeof exec === 'function') {\n    var result = exec.call(R, S);\n    if (typeof result !== 'object') {\n      throw new TypeError('RegExp exec method returned something other than an Object or null');\n    }\n    return result;\n  }\n  if (classof(R) !== 'RegExp') {\n    throw new TypeError('RegExp#exec called on incompatible receiver');\n  }\n  return builtinExec.call(R, S);\n};\n\n},{\"./_classof\":9}],60:[function(require,module,exports){\n'use strict';\n\nvar regexpFlags = require('./_flags');\n\nvar nativeExec = RegExp.prototype.exec;\n// This always refers to the native implementation, because the\n// String#replace polyfill uses ./fix-regexp-well-known-symbol-logic.js,\n// which loads this file before patching the method.\nvar nativeReplace = String.prototype.replace;\n\nvar patchedExec = nativeExec;\n\nvar LAST_INDEX = 'lastIndex';\n\nvar UPDATES_LAST_INDEX_WRONG = (function () {\n  var re1 = /a/,\n      re2 = /b*/g;\n  nativeExec.call(re1, 'a');\n  nativeExec.call(re2, 'a');\n  return re1[LAST_INDEX] !== 0 || re2[LAST_INDEX] !== 0;\n})();\n\n// nonparticipating capturing group, copied from es5-shim's String#split patch.\nvar NPCG_INCLUDED = /()??/.exec('')[1] !== undefined;\n\nvar PATCH = UPDATES_LAST_INDEX_WRONG || NPCG_INCLUDED;\n\nif (PATCH) {\n  patchedExec = function exec(str) {\n    var re = this;\n    var lastIndex, reCopy, match, i;\n\n    if (NPCG_INCLUDED) {\n      reCopy = new RegExp('^' + re.source + '$(?!\\\\s)', regexpFlags.call(re));\n    }\n    if (UPDATES_LAST_INDEX_WRONG) lastIndex = re[LAST_INDEX];\n\n    match = nativeExec.call(re, str);\n\n    if (UPDATES_LAST_INDEX_WRONG && match) {\n      re[LAST_INDEX] = re.global ? match.index + match[0].length : lastIndex;\n    }\n    if (NPCG_INCLUDED && match && match.length > 1) {\n      // Fix browsers whose `exec` methods don't consistently return `undefined`\n      // for NPCG, like IE8. NOTE: This doesn' work for /(.?)?/\n      // eslint-disable-next-line no-loop-func\n      nativeReplace.call(match[0], reCopy, function () {\n        for (i = 1; i < arguments.length - 2; i++) {\n          if (arguments[i] === undefined) match[i] = undefined;\n        }\n      });\n    }\n\n    return match;\n  };\n}\n\nmodule.exports = patchedExec;\n\n},{\"./_flags\":23}],61:[function(require,module,exports){\n// Works with __proto__ only. Old v8 can't work with null proto objects.\n/* eslint-disable no-proto */\nvar isObject = require('./_is-object');\nvar anObject = require('./_an-object');\nvar check = function (O, proto) {\n  anObject(O);\n  if (!isObject(proto) && proto !== null) throw TypeError(proto + \": can't set as prototype!\");\n};\nmodule.exports = {\n  set: Object.setPrototypeOf || ('__proto__' in {} ? // eslint-disable-line\n    function (test, buggy, set) {\n      try {\n        set = require('./_ctx')(Function.call, require('./_object-gopd').f(Object.prototype, '__proto__').set, 2);\n        set(test, []);\n        buggy = !(test instanceof Array);\n      } catch (e) { buggy = true; }\n      return function setPrototypeOf(O, proto) {\n        check(O, proto);\n        if (buggy) O.__proto__ = proto;\n        else set(O, proto);\n        return O;\n      };\n    }({}, false) : undefined),\n  check: check\n};\n\n},{\"./_an-object\":4,\"./_ctx\":13,\"./_is-object\":34,\"./_object-gopd\":47}],62:[function(require,module,exports){\n'use strict';\nvar global = require('./_global');\nvar dP = require('./_object-dp');\nvar DESCRIPTORS = require('./_descriptors');\nvar SPECIES = require('./_wks')('species');\n\nmodule.exports = function (KEY) {\n  var C = global[KEY];\n  if (DESCRIPTORS && C && !C[SPECIES]) dP.f(C, SPECIES, {\n    configurable: true,\n    get: function () { return this; }\n  });\n};\n\n},{\"./_descriptors\":15,\"./_global\":25,\"./_object-dp\":45,\"./_wks\":81}],63:[function(require,module,exports){\nvar def = require('./_object-dp').f;\nvar has = require('./_has');\nvar TAG = require('./_wks')('toStringTag');\n\nmodule.exports = function (it, tag, stat) {\n  if (it && !has(it = stat ? it : it.prototype, TAG)) def(it, TAG, { configurable: true, value: tag });\n};\n\n},{\"./_has\":26,\"./_object-dp\":45,\"./_wks\":81}],64:[function(require,module,exports){\nvar shared = require('./_shared')('keys');\nvar uid = require('./_uid');\nmodule.exports = function (key) {\n  return shared[key] || (shared[key] = uid(key));\n};\n\n},{\"./_shared\":65,\"./_uid\":78}],65:[function(require,module,exports){\nvar core = require('./_core');\nvar global = require('./_global');\nvar SHARED = '__core-js_shared__';\nvar store = global[SHARED] || (global[SHARED] = {});\n\n(module.exports = function (key, value) {\n  return store[key] || (store[key] = value !== undefined ? value : {});\n})('versions', []).push({\n  version: core.version,\n  mode: require('./_library') ? 'pure' : 'global',\n  copyright: '© 2020 Denis Pushkarev (zloirock.ru)'\n});\n\n},{\"./_core\":11,\"./_global\":25,\"./_library\":42}],66:[function(require,module,exports){\n// 7.3.20 SpeciesConstructor(O, defaultConstructor)\nvar anObject = require('./_an-object');\nvar aFunction = require('./_a-function');\nvar SPECIES = require('./_wks')('species');\nmodule.exports = function (O, D) {\n  var C = anObject(O).constructor;\n  var S;\n  return C === undefined || (S = anObject(C)[SPECIES]) == undefined ? D : aFunction(S);\n};\n\n},{\"./_a-function\":1,\"./_an-object\":4,\"./_wks\":81}],67:[function(require,module,exports){\n'use strict';\nvar fails = require('./_fails');\n\nmodule.exports = function (method, arg) {\n  return !!method && fails(function () {\n    // eslint-disable-next-line no-useless-call\n    arg ? method.call(null, function () { /* empty */ }, 1) : method.call(null);\n  });\n};\n\n},{\"./_fails\":21}],68:[function(require,module,exports){\nvar toInteger = require('./_to-integer');\nvar defined = require('./_defined');\n// true  -> String#at\n// false -> String#codePointAt\nmodule.exports = function (TO_STRING) {\n  return function (that, pos) {\n    var s = String(defined(that));\n    var i = toInteger(pos);\n    var l = s.length;\n    var a, b;\n    if (i < 0 || i >= l) return TO_STRING ? '' : undefined;\n    a = s.charCodeAt(i);\n    return a < 0xd800 || a > 0xdbff || i + 1 === l || (b = s.charCodeAt(i + 1)) < 0xdc00 || b > 0xdfff\n      ? TO_STRING ? s.charAt(i) : a\n      : TO_STRING ? s.slice(i, i + 2) : (a - 0xd800 << 10) + (b - 0xdc00) + 0x10000;\n  };\n};\n\n},{\"./_defined\":14,\"./_to-integer\":73}],69:[function(require,module,exports){\n// helper for String#{startsWith, endsWith, includes}\nvar isRegExp = require('./_is-regexp');\nvar defined = require('./_defined');\n\nmodule.exports = function (that, searchString, NAME) {\n  if (isRegExp(searchString)) throw TypeError('String#' + NAME + \" doesn't accept regex!\");\n  return String(defined(that));\n};\n\n},{\"./_defined\":14,\"./_is-regexp\":35}],70:[function(require,module,exports){\nvar $export = require('./_export');\nvar defined = require('./_defined');\nvar fails = require('./_fails');\nvar spaces = require('./_string-ws');\nvar space = '[' + spaces + ']';\nvar non = '\\u200b\\u0085';\nvar ltrim = RegExp('^' + space + space + '*');\nvar rtrim = RegExp(space + space + '*$');\n\nvar exporter = function (KEY, exec, ALIAS) {\n  var exp = {};\n  var FORCE = fails(function () {\n    return !!spaces[KEY]() || non[KEY]() != non;\n  });\n  var fn = exp[KEY] = FORCE ? exec(trim) : spaces[KEY];\n  if (ALIAS) exp[ALIAS] = fn;\n  $export($export.P + $export.F * FORCE, 'String', exp);\n};\n\n// 1 -> String#trimLeft\n// 2 -> String#trimRight\n// 3 -> String#trim\nvar trim = exporter.trim = function (string, TYPE) {\n  string = String(defined(string));\n  if (TYPE & 1) string = string.replace(ltrim, '');\n  if (TYPE & 2) string = string.replace(rtrim, '');\n  return string;\n};\n\nmodule.exports = exporter;\n\n},{\"./_defined\":14,\"./_export\":19,\"./_fails\":21,\"./_string-ws\":71}],71:[function(require,module,exports){\nmodule.exports = '\\x09\\x0A\\x0B\\x0C\\x0D\\x20\\xA0\\u1680\\u180E\\u2000\\u2001\\u2002\\u2003' +\n  '\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200A\\u202F\\u205F\\u3000\\u2028\\u2029\\uFEFF';\n\n},{}],72:[function(require,module,exports){\nvar toInteger = require('./_to-integer');\nvar max = Math.max;\nvar min = Math.min;\nmodule.exports = function (index, length) {\n  index = toInteger(index);\n  return index < 0 ? max(index + length, 0) : min(index, length);\n};\n\n},{\"./_to-integer\":73}],73:[function(require,module,exports){\n// 7.1.4 ToInteger\nvar ceil = Math.ceil;\nvar floor = Math.floor;\nmodule.exports = function (it) {\n  return isNaN(it = +it) ? 0 : (it > 0 ? floor : ceil)(it);\n};\n\n},{}],74:[function(require,module,exports){\n// to indexed object, toObject with fallback for non-array-like ES3 strings\nvar IObject = require('./_iobject');\nvar defined = require('./_defined');\nmodule.exports = function (it) {\n  return IObject(defined(it));\n};\n\n},{\"./_defined\":14,\"./_iobject\":31}],75:[function(require,module,exports){\n// 7.1.15 ToLength\nvar toInteger = require('./_to-integer');\nvar min = Math.min;\nmodule.exports = function (it) {\n  return it > 0 ? min(toInteger(it), 0x1fffffffffffff) : 0; // pow(2, 53) - 1 == 9007199254740991\n};\n\n},{\"./_to-integer\":73}],76:[function(require,module,exports){\n// 7.1.13 ToObject(argument)\nvar defined = require('./_defined');\nmodule.exports = function (it) {\n  return Object(defined(it));\n};\n\n},{\"./_defined\":14}],77:[function(require,module,exports){\n// 7.1.1 ToPrimitive(input [, PreferredType])\nvar isObject = require('./_is-object');\n// instead of the ES6 spec version, we didn't implement @@toPrimitive case\n// and the second argument - flag - preferred type is a string\nmodule.exports = function (it, S) {\n  if (!isObject(it)) return it;\n  var fn, val;\n  if (S && typeof (fn = it.toString) == 'function' && !isObject(val = fn.call(it))) return val;\n  if (typeof (fn = it.valueOf) == 'function' && !isObject(val = fn.call(it))) return val;\n  if (!S && typeof (fn = it.toString) == 'function' && !isObject(val = fn.call(it))) return val;\n  throw TypeError(\"Can't convert object to primitive value\");\n};\n\n},{\"./_is-object\":34}],78:[function(require,module,exports){\nvar id = 0;\nvar px = Math.random();\nmodule.exports = function (key) {\n  return 'Symbol('.concat(key === undefined ? '' : key, ')_', (++id + px).toString(36));\n};\n\n},{}],79:[function(require,module,exports){\nvar global = require('./_global');\nvar core = require('./_core');\nvar LIBRARY = require('./_library');\nvar wksExt = require('./_wks-ext');\nvar defineProperty = require('./_object-dp').f;\nmodule.exports = function (name) {\n  var $Symbol = core.Symbol || (core.Symbol = LIBRARY ? {} : global.Symbol || {});\n  if (name.charAt(0) != '_' && !(name in $Symbol)) defineProperty($Symbol, name, { value: wksExt.f(name) });\n};\n\n},{\"./_core\":11,\"./_global\":25,\"./_library\":42,\"./_object-dp\":45,\"./_wks-ext\":80}],80:[function(require,module,exports){\nexports.f = require('./_wks');\n\n},{\"./_wks\":81}],81:[function(require,module,exports){\nvar store = require('./_shared')('wks');\nvar uid = require('./_uid');\nvar Symbol = require('./_global').Symbol;\nvar USE_SYMBOL = typeof Symbol == 'function';\n\nvar $exports = module.exports = function (name) {\n  return store[name] || (store[name] =\n    USE_SYMBOL && Symbol[name] || (USE_SYMBOL ? Symbol : uid)('Symbol.' + name));\n};\n\n$exports.store = store;\n\n},{\"./_global\":25,\"./_shared\":65,\"./_uid\":78}],82:[function(require,module,exports){\nvar classof = require('./_classof');\nvar ITERATOR = require('./_wks')('iterator');\nvar Iterators = require('./_iterators');\nmodule.exports = require('./_core').getIteratorMethod = function (it) {\n  if (it != undefined) return it[ITERATOR]\n    || it['@@iterator']\n    || Iterators[classof(it)];\n};\n\n},{\"./_classof\":9,\"./_core\":11,\"./_iterators\":41,\"./_wks\":81}],83:[function(require,module,exports){\n'use strict';\nvar $export = require('./_export');\nvar $filter = require('./_array-methods')(2);\n\n$export($export.P + $export.F * !require('./_strict-method')([].filter, true), 'Array', {\n  // 22.1.3.7 / 15.4.4.20 Array.prototype.filter(callbackfn [, thisArg])\n  filter: function filter(callbackfn /* , thisArg */) {\n    return $filter(this, callbackfn, arguments[1]);\n  }\n});\n\n},{\"./_array-methods\":6,\"./_export\":19,\"./_strict-method\":67}],84:[function(require,module,exports){\n'use strict';\nvar ctx = require('./_ctx');\nvar $export = require('./_export');\nvar toObject = require('./_to-object');\nvar call = require('./_iter-call');\nvar isArrayIter = require('./_is-array-iter');\nvar toLength = require('./_to-length');\nvar createProperty = require('./_create-property');\nvar getIterFn = require('./core.get-iterator-method');\n\n$export($export.S + $export.F * !require('./_iter-detect')(function (iter) { Array.from(iter); }), 'Array', {\n  // 22.1.2.1 Array.from(arrayLike, mapfn = undefined, thisArg = undefined)\n  from: function from(arrayLike /* , mapfn = undefined, thisArg = undefined */) {\n    var O = toObject(arrayLike);\n    var C = typeof this == 'function' ? this : Array;\n    var aLen = arguments.length;\n    var mapfn = aLen > 1 ? arguments[1] : undefined;\n    var mapping = mapfn !== undefined;\n    var index = 0;\n    var iterFn = getIterFn(O);\n    var length, result, step, iterator;\n    if (mapping) mapfn = ctx(mapfn, aLen > 2 ? arguments[2] : undefined, 2);\n    // if object isn't iterable or it's array with default iterator - use simple case\n    if (iterFn != undefined && !(C == Array && isArrayIter(iterFn))) {\n      for (iterator = iterFn.call(O), result = new C(); !(step = iterator.next()).done; index++) {\n        createProperty(result, index, mapping ? call(iterator, mapfn, [step.value, index], true) : step.value);\n      }\n    } else {\n      length = toLength(O.length);\n      for (result = new C(length); length > index; index++) {\n        createProperty(result, index, mapping ? mapfn(O[index], index) : O[index]);\n      }\n    }\n    result.length = index;\n    return result;\n  }\n});\n\n},{\"./_create-property\":12,\"./_ctx\":13,\"./_export\":19,\"./_is-array-iter\":32,\"./_iter-call\":36,\"./_iter-detect\":39,\"./_to-length\":75,\"./_to-object\":76,\"./core.get-iterator-method\":82}],85:[function(require,module,exports){\n'use strict';\nvar addToUnscopables = require('./_add-to-unscopables');\nvar step = require('./_iter-step');\nvar Iterators = require('./_iterators');\nvar toIObject = require('./_to-iobject');\n\n// 22.1.3.4 Array.prototype.entries()\n// 22.1.3.13 Array.prototype.keys()\n// 22.1.3.29 Array.prototype.values()\n// 22.1.3.30 Array.prototype[@@iterator]()\nmodule.exports = require('./_iter-define')(Array, 'Array', function (iterated, kind) {\n  this._t = toIObject(iterated); // target\n  this._i = 0;                   // next index\n  this._k = kind;                // kind\n// 22.1.5.2.1 %ArrayIteratorPrototype%.next()\n}, function () {\n  var O = this._t;\n  var kind = this._k;\n  var index = this._i++;\n  if (!O || index >= O.length) {\n    this._t = undefined;\n    return step(1);\n  }\n  if (kind == 'keys') return step(0, index);\n  if (kind == 'values') return step(0, O[index]);\n  return step(0, [index, O[index]]);\n}, 'values');\n\n// argumentsList[@@iterator] is %ArrayProto_values% (9.4.4.6, 9.4.4.7)\nIterators.Arguments = Iterators.Array;\n\naddToUnscopables('keys');\naddToUnscopables('values');\naddToUnscopables('entries');\n\n},{\"./_add-to-unscopables\":2,\"./_iter-define\":38,\"./_iter-step\":40,\"./_iterators\":41,\"./_to-iobject\":74}],86:[function(require,module,exports){\n'use strict';\nvar $export = require('./_export');\nvar $map = require('./_array-methods')(1);\n\n$export($export.P + $export.F * !require('./_strict-method')([].map, true), 'Array', {\n  // 22.1.3.15 / 15.4.4.19 Array.prototype.map(callbackfn [, thisArg])\n  map: function map(callbackfn /* , thisArg */) {\n    return $map(this, callbackfn, arguments[1]);\n  }\n});\n\n},{\"./_array-methods\":6,\"./_export\":19,\"./_strict-method\":67}],87:[function(require,module,exports){\n'use strict';\nvar $export = require('./_export');\nvar html = require('./_html');\nvar cof = require('./_cof');\nvar toAbsoluteIndex = require('./_to-absolute-index');\nvar toLength = require('./_to-length');\nvar arraySlice = [].slice;\n\n// fallback for not array-like ES3 strings and DOM objects\n$export($export.P + $export.F * require('./_fails')(function () {\n  if (html) arraySlice.call(html);\n}), 'Array', {\n  slice: function slice(begin, end) {\n    var len = toLength(this.length);\n    var klass = cof(this);\n    end = end === undefined ? len : end;\n    if (klass == 'Array') return arraySlice.call(this, begin, end);\n    var start = toAbsoluteIndex(begin, len);\n    var upTo = toAbsoluteIndex(end, len);\n    var size = toLength(upTo - start);\n    var cloned = new Array(size);\n    var i = 0;\n    for (; i < size; i++) cloned[i] = klass == 'String'\n      ? this.charAt(start + i)\n      : this[start + i];\n    return cloned;\n  }\n});\n\n},{\"./_cof\":10,\"./_export\":19,\"./_fails\":21,\"./_html\":28,\"./_to-absolute-index\":72,\"./_to-length\":75}],88:[function(require,module,exports){\n'use strict';\nvar global = require('./_global');\nvar has = require('./_has');\nvar cof = require('./_cof');\nvar inheritIfRequired = require('./_inherit-if-required');\nvar toPrimitive = require('./_to-primitive');\nvar fails = require('./_fails');\nvar gOPN = require('./_object-gopn').f;\nvar gOPD = require('./_object-gopd').f;\nvar dP = require('./_object-dp').f;\nvar $trim = require('./_string-trim').trim;\nvar NUMBER = 'Number';\nvar $Number = global[NUMBER];\nvar Base = $Number;\nvar proto = $Number.prototype;\n// Opera ~12 has broken Object#toString\nvar BROKEN_COF = cof(require('./_object-create')(proto)) == NUMBER;\nvar TRIM = 'trim' in String.prototype;\n\n// 7.1.3 ToNumber(argument)\nvar toNumber = function (argument) {\n  var it = toPrimitive(argument, false);\n  if (typeof it == 'string' && it.length > 2) {\n    it = TRIM ? it.trim() : $trim(it, 3);\n    var first = it.charCodeAt(0);\n    var third, radix, maxCode;\n    if (first === 43 || first === 45) {\n      third = it.charCodeAt(2);\n      if (third === 88 || third === 120) return NaN; // Number('+0x1') should be NaN, old V8 fix\n    } else if (first === 48) {\n      switch (it.charCodeAt(1)) {\n        case 66: case 98: radix = 2; maxCode = 49; break; // fast equal /^0b[01]+$/i\n        case 79: case 111: radix = 8; maxCode = 55; break; // fast equal /^0o[0-7]+$/i\n        default: return +it;\n      }\n      for (var digits = it.slice(2), i = 0, l = digits.length, code; i < l; i++) {\n        code = digits.charCodeAt(i);\n        // parseInt parses a string to a first unavailable symbol\n        // but ToNumber should return NaN if a string contains unavailable symbols\n        if (code < 48 || code > maxCode) return NaN;\n      } return parseInt(digits, radix);\n    }\n  } return +it;\n};\n\nif (!$Number(' 0o1') || !$Number('0b1') || $Number('+0x1')) {\n  $Number = function Number(value) {\n    var it = arguments.length < 1 ? 0 : value;\n    var that = this;\n    return that instanceof $Number\n      // check on 1..constructor(foo) case\n      && (BROKEN_COF ? fails(function () { proto.valueOf.call(that); }) : cof(that) != NUMBER)\n        ? inheritIfRequired(new Base(toNumber(it)), that, $Number) : toNumber(it);\n  };\n  for (var keys = require('./_descriptors') ? gOPN(Base) : (\n    // ES3:\n    'MAX_VALUE,MIN_VALUE,NaN,NEGATIVE_INFINITY,POSITIVE_INFINITY,' +\n    // ES6 (in case, if modules with ES6 Number statics required before):\n    'EPSILON,isFinite,isInteger,isNaN,isSafeInteger,MAX_SAFE_INTEGER,' +\n    'MIN_SAFE_INTEGER,parseFloat,parseInt,isInteger'\n  ).split(','), j = 0, key; keys.length > j; j++) {\n    if (has(Base, key = keys[j]) && !has($Number, key)) {\n      dP($Number, key, gOPD(Base, key));\n    }\n  }\n  $Number.prototype = proto;\n  proto.constructor = $Number;\n  require('./_redefine')(global, NUMBER, $Number);\n}\n\n},{\"./_cof\":10,\"./_descriptors\":15,\"./_fails\":21,\"./_global\":25,\"./_has\":26,\"./_inherit-if-required\":30,\"./_object-create\":44,\"./_object-dp\":45,\"./_object-gopd\":47,\"./_object-gopn\":49,\"./_redefine\":58,\"./_string-trim\":70,\"./_to-primitive\":77}],89:[function(require,module,exports){\n// 19.1.2.6 Object.getOwnPropertyDescriptor(O, P)\nvar toIObject = require('./_to-iobject');\nvar $getOwnPropertyDescriptor = require('./_object-gopd').f;\n\nrequire('./_object-sap')('getOwnPropertyDescriptor', function () {\n  return function getOwnPropertyDescriptor(it, key) {\n    return $getOwnPropertyDescriptor(toIObject(it), key);\n  };\n});\n\n},{\"./_object-gopd\":47,\"./_object-sap\":55,\"./_to-iobject\":74}],90:[function(require,module,exports){\n// 19.1.2.14 Object.keys(O)\nvar toObject = require('./_to-object');\nvar $keys = require('./_object-keys');\n\nrequire('./_object-sap')('keys', function () {\n  return function keys(it) {\n    return $keys(toObject(it));\n  };\n});\n\n},{\"./_object-keys\":53,\"./_object-sap\":55,\"./_to-object\":76}],91:[function(require,module,exports){\n'use strict';\n// 19.1.3.6 Object.prototype.toString()\nvar classof = require('./_classof');\nvar test = {};\ntest[require('./_wks')('toStringTag')] = 'z';\nif (test + '' != '[object z]') {\n  require('./_redefine')(Object.prototype, 'toString', function toString() {\n    return '[object ' + classof(this) + ']';\n  }, true);\n}\n\n},{\"./_classof\":9,\"./_redefine\":58,\"./_wks\":81}],92:[function(require,module,exports){\nvar global = require('./_global');\nvar inheritIfRequired = require('./_inherit-if-required');\nvar dP = require('./_object-dp').f;\nvar gOPN = require('./_object-gopn').f;\nvar isRegExp = require('./_is-regexp');\nvar $flags = require('./_flags');\nvar $RegExp = global.RegExp;\nvar Base = $RegExp;\nvar proto = $RegExp.prototype;\nvar re1 = /a/g;\nvar re2 = /a/g;\n// \"new\" creates a new object, old webkit buggy here\nvar CORRECT_NEW = new $RegExp(re1) !== re1;\n\nif (require('./_descriptors') && (!CORRECT_NEW || require('./_fails')(function () {\n  re2[require('./_wks')('match')] = false;\n  // RegExp constructor can alter flags and IsRegExp works correct with @@match\n  return $RegExp(re1) != re1 || $RegExp(re2) == re2 || $RegExp(re1, 'i') != '/a/i';\n}))) {\n  $RegExp = function RegExp(p, f) {\n    var tiRE = this instanceof $RegExp;\n    var piRE = isRegExp(p);\n    var fiU = f === undefined;\n    return !tiRE && piRE && p.constructor === $RegExp && fiU ? p\n      : inheritIfRequired(CORRECT_NEW\n        ? new Base(piRE && !fiU ? p.source : p, f)\n        : Base((piRE = p instanceof $RegExp) ? p.source : p, piRE && fiU ? $flags.call(p) : f)\n      , tiRE ? this : proto, $RegExp);\n  };\n  var proxy = function (key) {\n    key in $RegExp || dP($RegExp, key, {\n      configurable: true,\n      get: function () { return Base[key]; },\n      set: function (it) { Base[key] = it; }\n    });\n  };\n  for (var keys = gOPN(Base), i = 0; keys.length > i;) proxy(keys[i++]);\n  proto.constructor = $RegExp;\n  $RegExp.prototype = proto;\n  require('./_redefine')(global, 'RegExp', $RegExp);\n}\n\nrequire('./_set-species')('RegExp');\n\n},{\"./_descriptors\":15,\"./_fails\":21,\"./_flags\":23,\"./_global\":25,\"./_inherit-if-required\":30,\"./_is-regexp\":35,\"./_object-dp\":45,\"./_object-gopn\":49,\"./_redefine\":58,\"./_set-species\":62,\"./_wks\":81}],93:[function(require,module,exports){\n'use strict';\nvar regexpExec = require('./_regexp-exec');\nrequire('./_export')({\n  target: 'RegExp',\n  proto: true,\n  forced: regexpExec !== /./.exec\n}, {\n  exec: regexpExec\n});\n\n},{\"./_export\":19,\"./_regexp-exec\":60}],94:[function(require,module,exports){\n'use strict';\n\nvar anObject = require('./_an-object');\nvar toLength = require('./_to-length');\nvar advanceStringIndex = require('./_advance-string-index');\nvar regExpExec = require('./_regexp-exec-abstract');\n\n// @@match logic\nrequire('./_fix-re-wks')('match', 1, function (defined, MATCH, $match, maybeCallNative) {\n  return [\n    // `String.prototype.match` method\n    // https://tc39.github.io/ecma262/#sec-string.prototype.match\n    function match(regexp) {\n      var O = defined(this);\n      var fn = regexp == undefined ? undefined : regexp[MATCH];\n      return fn !== undefined ? fn.call(regexp, O) : new RegExp(regexp)[MATCH](String(O));\n    },\n    // `RegExp.prototype[@@match]` method\n    // https://tc39.github.io/ecma262/#sec-regexp.prototype-@@match\n    function (regexp) {\n      var res = maybeCallNative($match, regexp, this);\n      if (res.done) return res.value;\n      var rx = anObject(regexp);\n      var S = String(this);\n      if (!rx.global) return regExpExec(rx, S);\n      var fullUnicode = rx.unicode;\n      rx.lastIndex = 0;\n      var A = [];\n      var n = 0;\n      var result;\n      while ((result = regExpExec(rx, S)) !== null) {\n        var matchStr = String(result[0]);\n        A[n] = matchStr;\n        if (matchStr === '') rx.lastIndex = advanceStringIndex(S, toLength(rx.lastIndex), fullUnicode);\n        n++;\n      }\n      return n === 0 ? null : A;\n    }\n  ];\n});\n\n},{\"./_advance-string-index\":3,\"./_an-object\":4,\"./_fix-re-wks\":22,\"./_regexp-exec-abstract\":59,\"./_to-length\":75}],95:[function(require,module,exports){\n'use strict';\n\nvar anObject = require('./_an-object');\nvar toObject = require('./_to-object');\nvar toLength = require('./_to-length');\nvar toInteger = require('./_to-integer');\nvar advanceStringIndex = require('./_advance-string-index');\nvar regExpExec = require('./_regexp-exec-abstract');\nvar max = Math.max;\nvar min = Math.min;\nvar floor = Math.floor;\nvar SUBSTITUTION_SYMBOLS = /\\$([$&`']|\\d\\d?|<[^>]*>)/g;\nvar SUBSTITUTION_SYMBOLS_NO_NAMED = /\\$([$&`']|\\d\\d?)/g;\n\nvar maybeToString = function (it) {\n  return it === undefined ? it : String(it);\n};\n\n// @@replace logic\nrequire('./_fix-re-wks')('replace', 2, function (defined, REPLACE, $replace, maybeCallNative) {\n  return [\n    // `String.prototype.replace` method\n    // https://tc39.github.io/ecma262/#sec-string.prototype.replace\n    function replace(searchValue, replaceValue) {\n      var O = defined(this);\n      var fn = searchValue == undefined ? undefined : searchValue[REPLACE];\n      return fn !== undefined\n        ? fn.call(searchValue, O, replaceValue)\n        : $replace.call(String(O), searchValue, replaceValue);\n    },\n    // `RegExp.prototype[@@replace]` method\n    // https://tc39.github.io/ecma262/#sec-regexp.prototype-@@replace\n    function (regexp, replaceValue) {\n      var res = maybeCallNative($replace, regexp, this, replaceValue);\n      if (res.done) return res.value;\n\n      var rx = anObject(regexp);\n      var S = String(this);\n      var functionalReplace = typeof replaceValue === 'function';\n      if (!functionalReplace) replaceValue = String(replaceValue);\n      var global = rx.global;\n      if (global) {\n        var fullUnicode = rx.unicode;\n        rx.lastIndex = 0;\n      }\n      var results = [];\n      while (true) {\n        var result = regExpExec(rx, S);\n        if (result === null) break;\n        results.push(result);\n        if (!global) break;\n        var matchStr = String(result[0]);\n        if (matchStr === '') rx.lastIndex = advanceStringIndex(S, toLength(rx.lastIndex), fullUnicode);\n      }\n      var accumulatedResult = '';\n      var nextSourcePosition = 0;\n      for (var i = 0; i < results.length; i++) {\n        result = results[i];\n        var matched = String(result[0]);\n        var position = max(min(toInteger(result.index), S.length), 0);\n        var captures = [];\n        // NOTE: This is equivalent to\n        //   captures = result.slice(1).map(maybeToString)\n        // but for some reason `nativeSlice.call(result, 1, result.length)` (called in\n        // the slice polyfill when slicing native arrays) \"doesn't work\" in safari 9 and\n        // causes a crash (https://pastebin.com/N21QzeQA) when trying to debug it.\n        for (var j = 1; j < result.length; j++) captures.push(maybeToString(result[j]));\n        var namedCaptures = result.groups;\n        if (functionalReplace) {\n          var replacerArgs = [matched].concat(captures, position, S);\n          if (namedCaptures !== undefined) replacerArgs.push(namedCaptures);\n          var replacement = String(replaceValue.apply(undefined, replacerArgs));\n        } else {\n          replacement = getSubstitution(matched, S, position, captures, namedCaptures, replaceValue);\n        }\n        if (position >= nextSourcePosition) {\n          accumulatedResult += S.slice(nextSourcePosition, position) + replacement;\n          nextSourcePosition = position + matched.length;\n        }\n      }\n      return accumulatedResult + S.slice(nextSourcePosition);\n    }\n  ];\n\n    // https://tc39.github.io/ecma262/#sec-getsubstitution\n  function getSubstitution(matched, str, position, captures, namedCaptures, replacement) {\n    var tailPos = position + matched.length;\n    var m = captures.length;\n    var symbols = SUBSTITUTION_SYMBOLS_NO_NAMED;\n    if (namedCaptures !== undefined) {\n      namedCaptures = toObject(namedCaptures);\n      symbols = SUBSTITUTION_SYMBOLS;\n    }\n    return $replace.call(replacement, symbols, function (match, ch) {\n      var capture;\n      switch (ch.charAt(0)) {\n        case '$': return '$';\n        case '&': return matched;\n        case '`': return str.slice(0, position);\n        case \"'\": return str.slice(tailPos);\n        case '<':\n          capture = namedCaptures[ch.slice(1, -1)];\n          break;\n        default: // \\d\\d?\n          var n = +ch;\n          if (n === 0) return match;\n          if (n > m) {\n            var f = floor(n / 10);\n            if (f === 0) return match;\n            if (f <= m) return captures[f - 1] === undefined ? ch.charAt(1) : captures[f - 1] + ch.charAt(1);\n            return match;\n          }\n          capture = captures[n - 1];\n      }\n      return capture === undefined ? '' : capture;\n    });\n  }\n});\n\n},{\"./_advance-string-index\":3,\"./_an-object\":4,\"./_fix-re-wks\":22,\"./_regexp-exec-abstract\":59,\"./_to-integer\":73,\"./_to-length\":75,\"./_to-object\":76}],96:[function(require,module,exports){\n'use strict';\n\nvar isRegExp = require('./_is-regexp');\nvar anObject = require('./_an-object');\nvar speciesConstructor = require('./_species-constructor');\nvar advanceStringIndex = require('./_advance-string-index');\nvar toLength = require('./_to-length');\nvar callRegExpExec = require('./_regexp-exec-abstract');\nvar regexpExec = require('./_regexp-exec');\nvar fails = require('./_fails');\nvar $min = Math.min;\nvar $push = [].push;\nvar $SPLIT = 'split';\nvar LENGTH = 'length';\nvar LAST_INDEX = 'lastIndex';\nvar MAX_UINT32 = 0xffffffff;\n\n// babel-minify transpiles RegExp('x', 'y') -> /x/y and it causes SyntaxError\nvar SUPPORTS_Y = !fails(function () { RegExp(MAX_UINT32, 'y'); });\n\n// @@split logic\nrequire('./_fix-re-wks')('split', 2, function (defined, SPLIT, $split, maybeCallNative) {\n  var internalSplit;\n  if (\n    'abbc'[$SPLIT](/(b)*/)[1] == 'c' ||\n    'test'[$SPLIT](/(?:)/, -1)[LENGTH] != 4 ||\n    'ab'[$SPLIT](/(?:ab)*/)[LENGTH] != 2 ||\n    '.'[$SPLIT](/(.?)(.?)/)[LENGTH] != 4 ||\n    '.'[$SPLIT](/()()/)[LENGTH] > 1 ||\n    ''[$SPLIT](/.?/)[LENGTH]\n  ) {\n    // based on es5-shim implementation, need to rework it\n    internalSplit = function (separator, limit) {\n      var string = String(this);\n      if (separator === undefined && limit === 0) return [];\n      // If `separator` is not a regex, use native split\n      if (!isRegExp(separator)) return $split.call(string, separator, limit);\n      var output = [];\n      var flags = (separator.ignoreCase ? 'i' : '') +\n                  (separator.multiline ? 'm' : '') +\n                  (separator.unicode ? 'u' : '') +\n                  (separator.sticky ? 'y' : '');\n      var lastLastIndex = 0;\n      var splitLimit = limit === undefined ? MAX_UINT32 : limit >>> 0;\n      // Make `global` and avoid `lastIndex` issues by working with a copy\n      var separatorCopy = new RegExp(separator.source, flags + 'g');\n      var match, lastIndex, lastLength;\n      while (match = regexpExec.call(separatorCopy, string)) {\n        lastIndex = separatorCopy[LAST_INDEX];\n        if (lastIndex > lastLastIndex) {\n          output.push(string.slice(lastLastIndex, match.index));\n          if (match[LENGTH] > 1 && match.index < string[LENGTH]) $push.apply(output, match.slice(1));\n          lastLength = match[0][LENGTH];\n          lastLastIndex = lastIndex;\n          if (output[LENGTH] >= splitLimit) break;\n        }\n        if (separatorCopy[LAST_INDEX] === match.index) separatorCopy[LAST_INDEX]++; // Avoid an infinite loop\n      }\n      if (lastLastIndex === string[LENGTH]) {\n        if (lastLength || !separatorCopy.test('')) output.push('');\n      } else output.push(string.slice(lastLastIndex));\n      return output[LENGTH] > splitLimit ? output.slice(0, splitLimit) : output;\n    };\n  // Chakra, V8\n  } else if ('0'[$SPLIT](undefined, 0)[LENGTH]) {\n    internalSplit = function (separator, limit) {\n      return separator === undefined && limit === 0 ? [] : $split.call(this, separator, limit);\n    };\n  } else {\n    internalSplit = $split;\n  }\n\n  return [\n    // `String.prototype.split` method\n    // https://tc39.github.io/ecma262/#sec-string.prototype.split\n    function split(separator, limit) {\n      var O = defined(this);\n      var splitter = separator == undefined ? undefined : separator[SPLIT];\n      return splitter !== undefined\n        ? splitter.call(separator, O, limit)\n        : internalSplit.call(String(O), separator, limit);\n    },\n    // `RegExp.prototype[@@split]` method\n    // https://tc39.github.io/ecma262/#sec-regexp.prototype-@@split\n    //\n    // NOTE: This cannot be properly polyfilled in engines that don't support\n    // the 'y' flag.\n    function (regexp, limit) {\n      var res = maybeCallNative(internalSplit, regexp, this, limit, internalSplit !== $split);\n      if (res.done) return res.value;\n\n      var rx = anObject(regexp);\n      var S = String(this);\n      var C = speciesConstructor(rx, RegExp);\n\n      var unicodeMatching = rx.unicode;\n      var flags = (rx.ignoreCase ? 'i' : '') +\n                  (rx.multiline ? 'm' : '') +\n                  (rx.unicode ? 'u' : '') +\n                  (SUPPORTS_Y ? 'y' : 'g');\n\n      // ^(? + rx + ) is needed, in combination with some S slicing, to\n      // simulate the 'y' flag.\n      var splitter = new C(SUPPORTS_Y ? rx : '^(?:' + rx.source + ')', flags);\n      var lim = limit === undefined ? MAX_UINT32 : limit >>> 0;\n      if (lim === 0) return [];\n      if (S.length === 0) return callRegExpExec(splitter, S) === null ? [S] : [];\n      var p = 0;\n      var q = 0;\n      var A = [];\n      while (q < S.length) {\n        splitter.lastIndex = SUPPORTS_Y ? q : 0;\n        var z = callRegExpExec(splitter, SUPPORTS_Y ? S : S.slice(q));\n        var e;\n        if (\n          z === null ||\n          (e = $min(toLength(splitter.lastIndex + (SUPPORTS_Y ? 0 : q)), S.length)) === p\n        ) {\n          q = advanceStringIndex(S, q, unicodeMatching);\n        } else {\n          A.push(S.slice(p, q));\n          if (A.length === lim) return A;\n          for (var i = 1; i <= z.length - 1; i++) {\n            A.push(z[i]);\n            if (A.length === lim) return A;\n          }\n          q = p = e;\n        }\n      }\n      A.push(S.slice(p));\n      return A;\n    }\n  ];\n});\n\n},{\"./_advance-string-index\":3,\"./_an-object\":4,\"./_fails\":21,\"./_fix-re-wks\":22,\"./_is-regexp\":35,\"./_regexp-exec\":60,\"./_regexp-exec-abstract\":59,\"./_species-constructor\":66,\"./_to-length\":75}],97:[function(require,module,exports){\n// 21.1.3.7 String.prototype.includes(searchString, position = 0)\n'use strict';\nvar $export = require('./_export');\nvar context = require('./_string-context');\nvar INCLUDES = 'includes';\n\n$export($export.P + $export.F * require('./_fails-is-regexp')(INCLUDES), 'String', {\n  includes: function includes(searchString /* , position = 0 */) {\n    return !!~context(this, searchString, INCLUDES)\n      .indexOf(searchString, arguments.length > 1 ? arguments[1] : undefined);\n  }\n});\n\n},{\"./_export\":19,\"./_fails-is-regexp\":20,\"./_string-context\":69}],98:[function(require,module,exports){\n'use strict';\nvar $at = require('./_string-at')(true);\n\n// 21.1.3.27 String.prototype[@@iterator]()\nrequire('./_iter-define')(String, 'String', function (iterated) {\n  this._t = String(iterated); // target\n  this._i = 0;                // next index\n// 21.1.5.2.1 %StringIteratorPrototype%.next()\n}, function () {\n  var O = this._t;\n  var index = this._i;\n  var point;\n  if (index >= O.length) return { value: undefined, done: true };\n  point = $at(O, index);\n  this._i += point.length;\n  return { value: point, done: false };\n});\n\n},{\"./_iter-define\":38,\"./_string-at\":68}],99:[function(require,module,exports){\n'use strict';\n// ECMAScript 6 symbols shim\nvar global = require('./_global');\nvar has = require('./_has');\nvar DESCRIPTORS = require('./_descriptors');\nvar $export = require('./_export');\nvar redefine = require('./_redefine');\nvar META = require('./_meta').KEY;\nvar $fails = require('./_fails');\nvar shared = require('./_shared');\nvar setToStringTag = require('./_set-to-string-tag');\nvar uid = require('./_uid');\nvar wks = require('./_wks');\nvar wksExt = require('./_wks-ext');\nvar wksDefine = require('./_wks-define');\nvar enumKeys = require('./_enum-keys');\nvar isArray = require('./_is-array');\nvar anObject = require('./_an-object');\nvar isObject = require('./_is-object');\nvar toObject = require('./_to-object');\nvar toIObject = require('./_to-iobject');\nvar toPrimitive = require('./_to-primitive');\nvar createDesc = require('./_property-desc');\nvar _create = require('./_object-create');\nvar gOPNExt = require('./_object-gopn-ext');\nvar $GOPD = require('./_object-gopd');\nvar $GOPS = require('./_object-gops');\nvar $DP = require('./_object-dp');\nvar $keys = require('./_object-keys');\nvar gOPD = $GOPD.f;\nvar dP = $DP.f;\nvar gOPN = gOPNExt.f;\nvar $Symbol = global.Symbol;\nvar $JSON = global.JSON;\nvar _stringify = $JSON && $JSON.stringify;\nvar PROTOTYPE = 'prototype';\nvar HIDDEN = wks('_hidden');\nvar TO_PRIMITIVE = wks('toPrimitive');\nvar isEnum = {}.propertyIsEnumerable;\nvar SymbolRegistry = shared('symbol-registry');\nvar AllSymbols = shared('symbols');\nvar OPSymbols = shared('op-symbols');\nvar ObjectProto = Object[PROTOTYPE];\nvar USE_NATIVE = typeof $Symbol == 'function' && !!$GOPS.f;\nvar QObject = global.QObject;\n// Don't use setters in Qt Script, https://github.com/zloirock/core-js/issues/173\nvar setter = !QObject || !QObject[PROTOTYPE] || !QObject[PROTOTYPE].findChild;\n\n// fallback for old Android, https://code.google.com/p/v8/issues/detail?id=687\nvar setSymbolDesc = DESCRIPTORS && $fails(function () {\n  return _create(dP({}, 'a', {\n    get: function () { return dP(this, 'a', { value: 7 }).a; }\n  })).a != 7;\n}) ? function (it, key, D) {\n  var protoDesc = gOPD(ObjectProto, key);\n  if (protoDesc) delete ObjectProto[key];\n  dP(it, key, D);\n  if (protoDesc && it !== ObjectProto) dP(ObjectProto, key, protoDesc);\n} : dP;\n\nvar wrap = function (tag) {\n  var sym = AllSymbols[tag] = _create($Symbol[PROTOTYPE]);\n  sym._k = tag;\n  return sym;\n};\n\nvar isSymbol = USE_NATIVE && typeof $Symbol.iterator == 'symbol' ? function (it) {\n  return typeof it == 'symbol';\n} : function (it) {\n  return it instanceof $Symbol;\n};\n\nvar $defineProperty = function defineProperty(it, key, D) {\n  if (it === ObjectProto) $defineProperty(OPSymbols, key, D);\n  anObject(it);\n  key = toPrimitive(key, true);\n  anObject(D);\n  if (has(AllSymbols, key)) {\n    if (!D.enumerable) {\n      if (!has(it, HIDDEN)) dP(it, HIDDEN, createDesc(1, {}));\n      it[HIDDEN][key] = true;\n    } else {\n      if (has(it, HIDDEN) && it[HIDDEN][key]) it[HIDDEN][key] = false;\n      D = _create(D, { enumerable: createDesc(0, false) });\n    } return setSymbolDesc(it, key, D);\n  } return dP(it, key, D);\n};\nvar $defineProperties = function defineProperties(it, P) {\n  anObject(it);\n  var keys = enumKeys(P = toIObject(P));\n  var i = 0;\n  var l = keys.length;\n  var key;\n  while (l > i) $defineProperty(it, key = keys[i++], P[key]);\n  return it;\n};\nvar $create = function create(it, P) {\n  return P === undefined ? _create(it) : $defineProperties(_create(it), P);\n};\nvar $propertyIsEnumerable = function propertyIsEnumerable(key) {\n  var E = isEnum.call(this, key = toPrimitive(key, true));\n  if (this === ObjectProto && has(AllSymbols, key) && !has(OPSymbols, key)) return false;\n  return E || !has(this, key) || !has(AllSymbols, key) || has(this, HIDDEN) && this[HIDDEN][key] ? E : true;\n};\nvar $getOwnPropertyDescriptor = function getOwnPropertyDescriptor(it, key) {\n  it = toIObject(it);\n  key = toPrimitive(key, true);\n  if (it === ObjectProto && has(AllSymbols, key) && !has(OPSymbols, key)) return;\n  var D = gOPD(it, key);\n  if (D && has(AllSymbols, key) && !(has(it, HIDDEN) && it[HIDDEN][key])) D.enumerable = true;\n  return D;\n};\nvar $getOwnPropertyNames = function getOwnPropertyNames(it) {\n  var names = gOPN(toIObject(it));\n  var result = [];\n  var i = 0;\n  var key;\n  while (names.length > i) {\n    if (!has(AllSymbols, key = names[i++]) && key != HIDDEN && key != META) result.push(key);\n  } return result;\n};\nvar $getOwnPropertySymbols = function getOwnPropertySymbols(it) {\n  var IS_OP = it === ObjectProto;\n  var names = gOPN(IS_OP ? OPSymbols : toIObject(it));\n  var result = [];\n  var i = 0;\n  var key;\n  while (names.length > i) {\n    if (has(AllSymbols, key = names[i++]) && (IS_OP ? has(ObjectProto, key) : true)) result.push(AllSymbols[key]);\n  } return result;\n};\n\n// 19.4.1.1 Symbol([description])\nif (!USE_NATIVE) {\n  $Symbol = function Symbol() {\n    if (this instanceof $Symbol) throw TypeError('Symbol is not a constructor!');\n    var tag = uid(arguments.length > 0 ? arguments[0] : undefined);\n    var $set = function (value) {\n      if (this === ObjectProto) $set.call(OPSymbols, value);\n      if (has(this, HIDDEN) && has(this[HIDDEN], tag)) this[HIDDEN][tag] = false;\n      setSymbolDesc(this, tag, createDesc(1, value));\n    };\n    if (DESCRIPTORS && setter) setSymbolDesc(ObjectProto, tag, { configurable: true, set: $set });\n    return wrap(tag);\n  };\n  redefine($Symbol[PROTOTYPE], 'toString', function toString() {\n    return this._k;\n  });\n\n  $GOPD.f = $getOwnPropertyDescriptor;\n  $DP.f = $defineProperty;\n  require('./_object-gopn').f = gOPNExt.f = $getOwnPropertyNames;\n  require('./_object-pie').f = $propertyIsEnumerable;\n  $GOPS.f = $getOwnPropertySymbols;\n\n  if (DESCRIPTORS && !require('./_library')) {\n    redefine(ObjectProto, 'propertyIsEnumerable', $propertyIsEnumerable, true);\n  }\n\n  wksExt.f = function (name) {\n    return wrap(wks(name));\n  };\n}\n\n$export($export.G + $export.W + $export.F * !USE_NATIVE, { Symbol: $Symbol });\n\nfor (var es6Symbols = (\n  // 19.4.2.2, 19.4.2.3, 19.4.2.4, 19.4.2.6, 19.4.2.8, 19.4.2.9, 19.4.2.10, 19.4.2.11, 19.4.2.12, 19.4.2.13, 19.4.2.14\n  'hasInstance,isConcatSpreadable,iterator,match,replace,search,species,split,toPrimitive,toStringTag,unscopables'\n).split(','), j = 0; es6Symbols.length > j;)wks(es6Symbols[j++]);\n\nfor (var wellKnownSymbols = $keys(wks.store), k = 0; wellKnownSymbols.length > k;) wksDefine(wellKnownSymbols[k++]);\n\n$export($export.S + $export.F * !USE_NATIVE, 'Symbol', {\n  // 19.4.2.1 Symbol.for(key)\n  'for': function (key) {\n    return has(SymbolRegistry, key += '')\n      ? SymbolRegistry[key]\n      : SymbolRegistry[key] = $Symbol(key);\n  },\n  // 19.4.2.5 Symbol.keyFor(sym)\n  keyFor: function keyFor(sym) {\n    if (!isSymbol(sym)) throw TypeError(sym + ' is not a symbol!');\n    for (var key in SymbolRegistry) if (SymbolRegistry[key] === sym) return key;\n  },\n  useSetter: function () { setter = true; },\n  useSimple: function () { setter = false; }\n});\n\n$export($export.S + $export.F * !USE_NATIVE, 'Object', {\n  // 19.1.2.2 Object.create(O [, Properties])\n  create: $create,\n  // 19.1.2.4 Object.defineProperty(O, P, Attributes)\n  defineProperty: $defineProperty,\n  // 19.1.2.3 Object.defineProperties(O, Properties)\n  defineProperties: $defineProperties,\n  // 19.1.2.6 Object.getOwnPropertyDescriptor(O, P)\n  getOwnPropertyDescriptor: $getOwnPropertyDescriptor,\n  // 19.1.2.7 Object.getOwnPropertyNames(O)\n  getOwnPropertyNames: $getOwnPropertyNames,\n  // 19.1.2.8 Object.getOwnPropertySymbols(O)\n  getOwnPropertySymbols: $getOwnPropertySymbols\n});\n\n// Chrome 38 and 39 `Object.getOwnPropertySymbols` fails on primitives\n// https://bugs.chromium.org/p/v8/issues/detail?id=3443\nvar FAILS_ON_PRIMITIVES = $fails(function () { $GOPS.f(1); });\n\n$export($export.S + $export.F * FAILS_ON_PRIMITIVES, 'Object', {\n  getOwnPropertySymbols: function getOwnPropertySymbols(it) {\n    return $GOPS.f(toObject(it));\n  }\n});\n\n// 24.3.2 JSON.stringify(value [, replacer [, space]])\n$JSON && $export($export.S + $export.F * (!USE_NATIVE || $fails(function () {\n  var S = $Symbol();\n  // MS Edge converts symbol values to JSON as {}\n  // WebKit converts symbol values to JSON as null\n  // V8 throws on boxed symbols\n  return _stringify([S]) != '[null]' || _stringify({ a: S }) != '{}' || _stringify(Object(S)) != '{}';\n})), 'JSON', {\n  stringify: function stringify(it) {\n    var args = [it];\n    var i = 1;\n    var replacer, $replacer;\n    while (arguments.length > i) args.push(arguments[i++]);\n    $replacer = replacer = args[1];\n    if (!isObject(replacer) && it === undefined || isSymbol(it)) return; // IE8 returns string on undefined\n    if (!isArray(replacer)) replacer = function (key, value) {\n      if (typeof $replacer == 'function') value = $replacer.call(this, key, value);\n      if (!isSymbol(value)) return value;\n    };\n    args[1] = replacer;\n    return _stringify.apply($JSON, args);\n  }\n});\n\n// 19.4.3.4 Symbol.prototype[@@toPrimitive](hint)\n$Symbol[PROTOTYPE][TO_PRIMITIVE] || require('./_hide')($Symbol[PROTOTYPE], TO_PRIMITIVE, $Symbol[PROTOTYPE].valueOf);\n// 19.4.3.5 Symbol.prototype[@@toStringTag]\nsetToStringTag($Symbol, 'Symbol');\n// 20.2.1.9 Math[@@toStringTag]\nsetToStringTag(Math, 'Math', true);\n// 24.3.3 JSON[@@toStringTag]\nsetToStringTag(global.JSON, 'JSON', true);\n\n},{\"./_an-object\":4,\"./_descriptors\":15,\"./_enum-keys\":18,\"./_export\":19,\"./_fails\":21,\"./_global\":25,\"./_has\":26,\"./_hide\":27,\"./_is-array\":33,\"./_is-object\":34,\"./_library\":42,\"./_meta\":43,\"./_object-create\":44,\"./_object-dp\":45,\"./_object-gopd\":47,\"./_object-gopn\":49,\"./_object-gopn-ext\":48,\"./_object-gops\":50,\"./_object-keys\":53,\"./_object-pie\":54,\"./_property-desc\":57,\"./_redefine\":58,\"./_set-to-string-tag\":63,\"./_shared\":65,\"./_to-iobject\":74,\"./_to-object\":76,\"./_to-primitive\":77,\"./_uid\":78,\"./_wks\":81,\"./_wks-define\":79,\"./_wks-ext\":80}],100:[function(require,module,exports){\n'use strict';\n// https://github.com/tc39/Array.prototype.includes\nvar $export = require('./_export');\nvar $includes = require('./_array-includes')(true);\n\n$export($export.P, 'Array', {\n  includes: function includes(el /* , fromIndex = 0 */) {\n    return $includes(this, el, arguments.length > 1 ? arguments[1] : undefined);\n  }\n});\n\nrequire('./_add-to-unscopables')('includes');\n\n},{\"./_add-to-unscopables\":2,\"./_array-includes\":5,\"./_export\":19}],101:[function(require,module,exports){\n// https://github.com/tc39/proposal-object-getownpropertydescriptors\nvar $export = require('./_export');\nvar ownKeys = require('./_own-keys');\nvar toIObject = require('./_to-iobject');\nvar gOPD = require('./_object-gopd');\nvar createProperty = require('./_create-property');\n\n$export($export.S, 'Object', {\n  getOwnPropertyDescriptors: function getOwnPropertyDescriptors(object) {\n    var O = toIObject(object);\n    var getDesc = gOPD.f;\n    var keys = ownKeys(O);\n    var result = {};\n    var i = 0;\n    var key, desc;\n    while (keys.length > i) {\n      desc = getDesc(O, key = keys[i++]);\n      if (desc !== undefined) createProperty(result, key, desc);\n    }\n    return result;\n  }\n});\n\n},{\"./_create-property\":12,\"./_export\":19,\"./_object-gopd\":47,\"./_own-keys\":56,\"./_to-iobject\":74}],102:[function(require,module,exports){\nvar $iterators = require('./es6.array.iterator');\nvar getKeys = require('./_object-keys');\nvar redefine = require('./_redefine');\nvar global = require('./_global');\nvar hide = require('./_hide');\nvar Iterators = require('./_iterators');\nvar wks = require('./_wks');\nvar ITERATOR = wks('iterator');\nvar TO_STRING_TAG = wks('toStringTag');\nvar ArrayValues = Iterators.Array;\n\nvar DOMIterables = {\n  CSSRuleList: true, // TODO: Not spec compliant, should be false.\n  CSSStyleDeclaration: false,\n  CSSValueList: false,\n  ClientRectList: false,\n  DOMRectList: false,\n  DOMStringList: false,\n  DOMTokenList: true,\n  DataTransferItemList: false,\n  FileList: false,\n  HTMLAllCollection: false,\n  HTMLCollection: false,\n  HTMLFormElement: false,\n  HTMLSelectElement: false,\n  MediaList: true, // TODO: Not spec compliant, should be false.\n  MimeTypeArray: false,\n  NamedNodeMap: false,\n  NodeList: true,\n  PaintRequestList: false,\n  Plugin: false,\n  PluginArray: false,\n  SVGLengthList: false,\n  SVGNumberList: false,\n  SVGPathSegList: false,\n  SVGPointList: false,\n  SVGStringList: false,\n  SVGTransformList: false,\n  SourceBufferList: false,\n  StyleSheetList: true, // TODO: Not spec compliant, should be false.\n  TextTrackCueList: false,\n  TextTrackList: false,\n  TouchList: false\n};\n\nfor (var collections = getKeys(DOMIterables), i = 0; i < collections.length; i++) {\n  var NAME = collections[i];\n  var explicit = DOMIterables[NAME];\n  var Collection = global[NAME];\n  var proto = Collection && Collection.prototype;\n  var key;\n  if (proto) {\n    if (!proto[ITERATOR]) hide(proto, ITERATOR, ArrayValues);\n    if (!proto[TO_STRING_TAG]) hide(proto, TO_STRING_TAG, NAME);\n    Iterators[NAME] = ArrayValues;\n    if (explicit) for (key in $iterators) if (!proto[key]) redefine(proto, key, $iterators[key], true);\n  }\n}\n\n},{\"./_global\":25,\"./_hide\":27,\"./_iterators\":41,\"./_object-keys\":53,\"./_redefine\":58,\"./_wks\":81,\"./es6.array.iterator\":85}],103:[function(require,module,exports){\n\"use strict\";\n\nrequire(\"core-js/modules/es6.symbol.js\");\nrequire(\"core-js/modules/es6.number.constructor.js\");\nrequire(\"core-js/modules/es6.string.iterator.js\");\nrequire(\"core-js/modules/es6.object.to-string.js\");\nrequire(\"core-js/modules/es6.array.iterator.js\");\nrequire(\"core-js/modules/web.dom.iterable.js\");\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && \"function\" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }, _typeof(obj); }\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, \"prototype\", { writable: false }); return Constructor; }\nfunction _toPropertyKey(arg) { var key = _toPrimitive(arg, \"string\"); return _typeof(key) === \"symbol\" ? key : String(key); }\nfunction _toPrimitive(input, hint) { if (_typeof(input) !== \"object\" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || \"default\"); if (_typeof(res) !== \"object\") return res; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (hint === \"string\" ? String : Number)(input); }\nvar _require = require('./protocol'),\n  Parser = _require.Parser,\n  PROTOCOL_6 = _require.PROTOCOL_6,\n  PROTOCOL_7 = _require.PROTOCOL_7;\nvar VERSION = \"4.0.1\";\nvar Connector = /*#__PURE__*/function () {\n  function Connector(options, WebSocket, Timer, handlers) {\n    var _this = this;\n    _classCallCheck(this, Connector);\n    this.options = options;\n    this.WebSocket = WebSocket;\n    this.Timer = Timer;\n    this.handlers = handlers;\n    var path = this.options.path ? \"\".concat(this.options.path) : 'livereload';\n    var port = this.options.port ? \":\".concat(this.options.port) : '';\n    this._uri = \"ws\".concat(this.options.https ? 's' : '', \"://\").concat(this.options.host).concat(port, \"/\").concat(path);\n    this._nextDelay = this.options.mindelay;\n    this._connectionDesired = false;\n    this.protocol = 0;\n    this.protocolParser = new Parser({\n      connected: function connected(protocol) {\n        _this.protocol = protocol;\n        _this._handshakeTimeout.stop();\n        _this._nextDelay = _this.options.mindelay;\n        _this._disconnectionReason = 'broken';\n        return _this.handlers.connected(_this.protocol);\n      },\n      error: function error(e) {\n        _this.handlers.error(e);\n        return _this._closeOnError();\n      },\n      message: function message(_message) {\n        return _this.handlers.message(_message);\n      }\n    });\n    this._handshakeTimeout = new this.Timer(function () {\n      if (!_this._isSocketConnected()) {\n        return;\n      }\n      _this._disconnectionReason = 'handshake-timeout';\n      return _this.socket.close();\n    });\n    this._reconnectTimer = new this.Timer(function () {\n      if (!_this._connectionDesired) {\n        // shouldn't hit this, but just in case\n        return;\n      }\n      return _this.connect();\n    });\n    this.connect();\n  }\n  _createClass(Connector, [{\n    key: \"_isSocketConnected\",\n    value: function _isSocketConnected() {\n      return this.socket && this.socket.readyState === this.WebSocket.OPEN;\n    }\n  }, {\n    key: \"connect\",\n    value: function connect() {\n      var _this2 = this;\n      this._connectionDesired = true;\n      if (this._isSocketConnected()) {\n        return;\n      }\n\n      // prepare for a new connection\n      this._reconnectTimer.stop();\n      this._disconnectionReason = 'cannot-connect';\n      this.protocolParser.reset();\n      this.handlers.connecting();\n      this.socket = new this.WebSocket(this._uri);\n      this.socket.onopen = function (e) {\n        return _this2._onopen(e);\n      };\n      this.socket.onclose = function (e) {\n        return _this2._onclose(e);\n      };\n      this.socket.onmessage = function (e) {\n        return _this2._onmessage(e);\n      };\n      this.socket.onerror = function (e) {\n        return _this2._onerror(e);\n      };\n    }\n  }, {\n    key: \"disconnect\",\n    value: function disconnect() {\n      this._connectionDesired = false;\n      this._reconnectTimer.stop(); // in case it was running\n\n      if (!this._isSocketConnected()) {\n        return;\n      }\n      this._disconnectionReason = 'manual';\n      return this.socket.close();\n    }\n  }, {\n    key: \"_scheduleReconnection\",\n    value: function _scheduleReconnection() {\n      if (!this._connectionDesired) {\n        // don't reconnect after manual disconnection\n        return;\n      }\n      if (!this._reconnectTimer.running) {\n        this._reconnectTimer.start(this._nextDelay);\n        this._nextDelay = Math.min(this.options.maxdelay, this._nextDelay * 2);\n      }\n    }\n  }, {\n    key: \"sendCommand\",\n    value: function sendCommand(command) {\n      if (!this.protocol) {\n        return;\n      }\n      return this._sendCommand(command);\n    }\n  }, {\n    key: \"_sendCommand\",\n    value: function _sendCommand(command) {\n      return this.socket.send(JSON.stringify(command));\n    }\n  }, {\n    key: \"_closeOnError\",\n    value: function _closeOnError() {\n      this._handshakeTimeout.stop();\n      this._disconnectionReason = 'error';\n      return this.socket.close();\n    }\n  }, {\n    key: \"_onopen\",\n    value: function _onopen(e) {\n      this.handlers.socketConnected();\n      this._disconnectionReason = 'handshake-failed';\n\n      // start handshake\n      var hello = {\n        command: 'hello',\n        protocols: [PROTOCOL_6, PROTOCOL_7]\n      };\n      hello.ver = VERSION;\n      if (this.options.ext) {\n        hello.ext = this.options.ext;\n      }\n      if (this.options.extver) {\n        hello.extver = this.options.extver;\n      }\n      if (this.options.snipver) {\n        hello.snipver = this.options.snipver;\n      }\n      this._sendCommand(hello);\n      return this._handshakeTimeout.start(this.options.handshake_timeout);\n    }\n  }, {\n    key: \"_onclose\",\n    value: function _onclose(e) {\n      this.protocol = 0;\n      this.handlers.disconnected(this._disconnectionReason, this._nextDelay);\n      return this._scheduleReconnection();\n    }\n  }, {\n    key: \"_onerror\",\n    value: function _onerror(e) {}\n  }, {\n    key: \"_onmessage\",\n    value: function _onmessage(e) {\n      return this.protocolParser.process(e.data);\n    }\n  }]);\n  return Connector;\n}();\n;\nexports.Connector = Connector;\n\n},{\"./protocol\":108,\"core-js/modules/es6.array.iterator.js\":85,\"core-js/modules/es6.number.constructor.js\":88,\"core-js/modules/es6.object.to-string.js\":91,\"core-js/modules/es6.string.iterator.js\":98,\"core-js/modules/es6.symbol.js\":99,\"core-js/modules/web.dom.iterable.js\":102}],104:[function(require,module,exports){\n\"use strict\";\n\nvar CustomEvents = {\n  bind: function bind(element, eventName, handler) {\n    if (element.addEventListener) {\n      return element.addEventListener(eventName, handler, false);\n    }\n    if (element.attachEvent) {\n      element[eventName] = 1;\n      return element.attachEvent('onpropertychange', function (event) {\n        if (event.propertyName === eventName) {\n          return handler();\n        }\n      });\n    }\n    throw new Error(\"Attempt to attach custom event \".concat(eventName, \" to something which isn't a DOMElement\"));\n  },\n  fire: function fire(element, eventName) {\n    if (element.addEventListener) {\n      var event = document.createEvent('HTMLEvents');\n      event.initEvent(eventName, true, true);\n      return document.dispatchEvent(event);\n    } else if (element.attachEvent) {\n      if (element[eventName]) {\n        return element[eventName]++;\n      }\n    } else {\n      throw new Error(\"Attempt to fire custom event \".concat(eventName, \" on something which isn't a DOMElement\"));\n    }\n  }\n};\nexports.bind = CustomEvents.bind;\nexports.fire = CustomEvents.fire;\n\n},{}],105:[function(require,module,exports){\n\"use strict\";\n\nrequire(\"core-js/modules/es6.regexp.match.js\");\nrequire(\"core-js/modules/es6.symbol.js\");\nrequire(\"core-js/modules/es6.array.from.js\");\nrequire(\"core-js/modules/es6.string.iterator.js\");\nrequire(\"core-js/modules/es6.object.to-string.js\");\nrequire(\"core-js/modules/es6.array.iterator.js\");\nrequire(\"core-js/modules/web.dom.iterable.js\");\nrequire(\"core-js/modules/es6.number.constructor.js\");\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && \"function\" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }, _typeof(obj); }\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, \"prototype\", { writable: false }); return Constructor; }\nfunction _toPropertyKey(arg) { var key = _toPrimitive(arg, \"string\"); return _typeof(key) === \"symbol\" ? key : String(key); }\nfunction _toPrimitive(input, hint) { if (_typeof(input) !== \"object\" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || \"default\"); if (_typeof(res) !== \"object\") return res; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (hint === \"string\" ? String : Number)(input); }\nvar LessPlugin = /*#__PURE__*/function () {\n  function LessPlugin(window, host) {\n    _classCallCheck(this, LessPlugin);\n    this.window = window;\n    this.host = host;\n  }\n  _createClass(LessPlugin, [{\n    key: \"reload\",\n    value: function reload(path, options) {\n      if (this.window.less && this.window.less.refresh) {\n        if (path.match(/\\.less$/i)) {\n          return this.reloadLess(path);\n        }\n        if (options.originalPath.match(/\\.less$/i)) {\n          return this.reloadLess(options.originalPath);\n        }\n      }\n      return false;\n    }\n  }, {\n    key: \"reloadLess\",\n    value: function reloadLess(path) {\n      var link;\n      var links = function () {\n        var result = [];\n        for (var _i = 0, _Array$from = Array.from(document.getElementsByTagName('link')); _i < _Array$from.length; _i++) {\n          link = _Array$from[_i];\n          if (link.href && link.rel.match(/^stylesheet\\/less$/i) || link.rel.match(/stylesheet/i) && link.type.match(/^text\\/(x-)?less$/i)) {\n            result.push(link);\n          }\n        }\n        return result;\n      }();\n      if (links.length === 0) {\n        return false;\n      }\n      for (var _i2 = 0, _Array$from2 = Array.from(links); _i2 < _Array$from2.length; _i2++) {\n        link = _Array$from2[_i2];\n        link.href = this.host.generateCacheBustUrl(link.href);\n      }\n      this.host.console.log('LiveReload is asking LESS to recompile all stylesheets');\n      this.window.less.refresh(true);\n      return true;\n    }\n  }, {\n    key: \"analyze\",\n    value: function analyze() {\n      return {\n        disable: !!(this.window.less && this.window.less.refresh)\n      };\n    }\n  }]);\n  return LessPlugin;\n}();\n;\nLessPlugin.identifier = 'less';\nLessPlugin.version = '1.0';\nmodule.exports = LessPlugin;\n\n},{\"core-js/modules/es6.array.from.js\":84,\"core-js/modules/es6.array.iterator.js\":85,\"core-js/modules/es6.number.constructor.js\":88,\"core-js/modules/es6.object.to-string.js\":91,\"core-js/modules/es6.regexp.match.js\":94,\"core-js/modules/es6.string.iterator.js\":98,\"core-js/modules/es6.symbol.js\":99,\"core-js/modules/web.dom.iterable.js\":102}],106:[function(require,module,exports){\n\"use strict\";\n\nrequire(\"core-js/modules/es6.symbol.js\");\nrequire(\"core-js/modules/es6.number.constructor.js\");\nrequire(\"core-js/modules/es6.array.slice.js\");\nrequire(\"core-js/modules/es6.object.to-string.js\");\nrequire(\"core-js/modules/es6.array.from.js\");\nrequire(\"core-js/modules/es6.string.iterator.js\");\nrequire(\"core-js/modules/es6.array.iterator.js\");\nrequire(\"core-js/modules/web.dom.iterable.js\");\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && \"function\" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }, _typeof(obj); }\nrequire(\"core-js/modules/es6.regexp.match.js\");\nrequire(\"core-js/modules/es6.object.keys.js\");\nfunction _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== \"undefined\" && o[Symbol.iterator] || o[\"@@iterator\"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === \"number\") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError(\"Invalid attempt to iterate non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }\nfunction _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === \"string\") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === \"Object\" && o.constructor) n = o.constructor.name; if (n === \"Map\" || n === \"Set\") return Array.from(o); if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }\nfunction _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, \"prototype\", { writable: false }); return Constructor; }\nfunction _toPropertyKey(arg) { var key = _toPrimitive(arg, \"string\"); return _typeof(key) === \"symbol\" ? key : String(key); }\nfunction _toPrimitive(input, hint) { if (_typeof(input) !== \"object\" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || \"default\"); if (_typeof(res) !== \"object\") return res; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (hint === \"string\" ? String : Number)(input); }\n/* global alert */\nvar _require = require('./connector'),\n  Connector = _require.Connector;\nvar _require2 = require('./timer'),\n  Timer = _require2.Timer;\nvar _require3 = require('./options'),\n  Options = _require3.Options;\nvar _require4 = require('./reloader'),\n  Reloader = _require4.Reloader;\nvar _require5 = require('./protocol'),\n  ProtocolError = _require5.ProtocolError;\nvar LiveReload = /*#__PURE__*/function () {\n  function LiveReload(window) {\n    var _this = this;\n    _classCallCheck(this, LiveReload);\n    this.window = window;\n    this.listeners = {};\n    this.plugins = [];\n    this.pluginIdentifiers = {};\n\n    // i can haz console?\n    this.console = this.window.console && this.window.console.log && this.window.console.error ? this.window.location.href.match(/LR-verbose/) ? this.window.console : {\n      log: function log() {},\n      error: this.window.console.error.bind(this.window.console)\n    } : {\n      log: function log() {},\n      error: function error() {}\n    };\n\n    // i can haz sockets?\n    if (!(this.WebSocket = this.window.WebSocket || this.window.MozWebSocket)) {\n      this.console.error('LiveReload disabled because the browser does not seem to support web sockets');\n      return;\n    }\n\n    // i can haz options?\n    if ('LiveReloadOptions' in window) {\n      this.options = new Options();\n      for (var _i = 0, _Object$keys = Object.keys(window.LiveReloadOptions || {}); _i < _Object$keys.length; _i++) {\n        var k = _Object$keys[_i];\n        var v = window.LiveReloadOptions[k];\n        this.options.set(k, v);\n      }\n    } else {\n      this.options = Options.extract(this.window.document);\n      if (!this.options) {\n        this.console.error('LiveReload disabled because it could not find its own <SCRIPT> tag');\n        return;\n      }\n    }\n\n    // i can haz reloader?\n    this.reloader = new Reloader(this.window, this.console, Timer);\n\n    // i can haz connection?\n    this.connector = new Connector(this.options, this.WebSocket, Timer, {\n      connecting: function connecting() {},\n      socketConnected: function socketConnected() {},\n      connected: function connected(protocol) {\n        if (typeof _this.listeners.connect === 'function') {\n          _this.listeners.connect();\n        }\n        var host = _this.options.host;\n        var port = _this.options.port ? \":\".concat(_this.options.port) : '';\n        _this.log(\"LiveReload is connected to \".concat(host).concat(port, \" (protocol v\").concat(protocol, \").\"));\n        return _this.analyze();\n      },\n      error: function error(e) {\n        if (e instanceof ProtocolError) {\n          if (typeof console !== 'undefined' && console !== null) {\n            return console.log(\"\".concat(e.message, \".\"));\n          }\n        } else {\n          if (typeof console !== 'undefined' && console !== null) {\n            return console.log(\"LiveReload internal error: \".concat(e.message));\n          }\n        }\n      },\n      disconnected: function disconnected(reason, nextDelay) {\n        if (typeof _this.listeners.disconnect === 'function') {\n          _this.listeners.disconnect();\n        }\n        var host = _this.options.host;\n        var port = _this.options.port ? \":\".concat(_this.options.port) : '';\n        switch (reason) {\n          case 'cannot-connect':\n            return _this.log(\"LiveReload cannot connect to \".concat(host).concat(port, \", will retry in \").concat(nextDelay, \" sec.\"));\n          case 'broken':\n            return _this.log(\"LiveReload disconnected from \".concat(host).concat(port, \", reconnecting in \").concat(nextDelay, \" sec.\"));\n          case 'handshake-timeout':\n            return _this.log(\"LiveReload cannot connect to \".concat(host).concat(port, \" (handshake timeout), will retry in \").concat(nextDelay, \" sec.\"));\n          case 'handshake-failed':\n            return _this.log(\"LiveReload cannot connect to \".concat(host).concat(port, \" (handshake failed), will retry in \").concat(nextDelay, \" sec.\"));\n          case 'manual': // nop\n          case 'error': // nop\n          default:\n            return _this.log(\"LiveReload disconnected from \".concat(host).concat(port, \" (\").concat(reason, \"), reconnecting in \").concat(nextDelay, \" sec.\"));\n        }\n      },\n      message: function message(_message) {\n        switch (_message.command) {\n          case 'reload':\n            return _this.performReload(_message);\n          case 'alert':\n            return _this.performAlert(_message);\n        }\n      }\n    });\n    this.initialized = true;\n  }\n  _createClass(LiveReload, [{\n    key: \"on\",\n    value: function on(eventName, handler) {\n      this.listeners[eventName] = handler;\n    }\n  }, {\n    key: \"log\",\n    value: function log(message) {\n      return this.console.log(\"\".concat(message));\n    }\n  }, {\n    key: \"performReload\",\n    value: function performReload(message) {\n      this.log(\"LiveReload received reload request: \".concat(JSON.stringify(message, null, 2)));\n      var _this$options = this.options,\n        host = _this$options.host,\n        port = _this$options.port,\n        pluginOrder = _this$options.pluginOrder;\n      return this.reloader.reload(message.path, {\n        liveCSS: message.liveCSS != null ? message.liveCSS : true,\n        liveImg: message.liveImg != null ? message.liveImg : true,\n        reloadMissingCSS: message.reloadMissingCSS != null ? message.reloadMissingCSS : true,\n        originalPath: message.originalPath || '',\n        overrideURL: message.overrideURL || '',\n        serverURL: \"http://\".concat(host).concat(port && \":\".concat(port)),\n        pluginOrder: pluginOrder\n      });\n    }\n  }, {\n    key: \"performAlert\",\n    value: function performAlert(message) {\n      return alert(message.message);\n    }\n  }, {\n    key: \"shutDown\",\n    value: function shutDown() {\n      if (!this.initialized) {\n        return;\n      }\n      this.connector.disconnect();\n      this.log('LiveReload disconnected.');\n      return typeof this.listeners.shutdown === 'function' ? this.listeners.shutdown() : undefined;\n    }\n  }, {\n    key: \"hasPlugin\",\n    value: function hasPlugin(identifier) {\n      return !!this.pluginIdentifiers[identifier];\n    }\n  }, {\n    key: \"addPlugin\",\n    value: function addPlugin(PluginClass) {\n      var _this2 = this;\n      if (!this.initialized) {\n        return;\n      }\n      if (this.hasPlugin(PluginClass.identifier)) {\n        return;\n      }\n      this.pluginIdentifiers[PluginClass.identifier] = true;\n      var plugin = new PluginClass(this.window, {\n        // expose internal objects for those who know what they're doing\n        // (note that these are private APIs and subject to change at any time!)\n        _livereload: this,\n        _reloader: this.reloader,\n        _connector: this.connector,\n        // official API\n        console: this.console,\n        Timer: Timer,\n        generateCacheBustUrl: function generateCacheBustUrl(url) {\n          return _this2.reloader.generateCacheBustUrl(url);\n        }\n      });\n\n      // API that PluginClass can/must provide:\n      //\n      // string PluginClass.identifier\n      //   -- required, globally-unique name of this plugin\n      //\n      // string PluginClass.version\n      //   -- required, plugin version number (format %d.%d or %d.%d.%d)\n      //\n      // plugin = new PluginClass(window, officialLiveReloadAPI)\n      //   -- required, plugin constructor\n      //\n      // bool plugin.reload(string path, { bool liveCSS, bool liveImg })\n      //   -- optional, attemp to reload the given path, return true if handled\n      //\n      // object plugin.analyze()\n      //   -- optional, returns plugin-specific information about the current document (to send to the connected server)\n      //      (LiveReload 2 server currently only defines 'disable' key in this object; return {disable:true} to disable server-side\n      //       compilation of a matching plugin's files)\n\n      this.plugins.push(plugin);\n      this.reloader.addPlugin(plugin);\n    }\n  }, {\n    key: \"analyze\",\n    value: function analyze() {\n      if (!this.initialized) {\n        return;\n      }\n      if (!(this.connector.protocol >= 7)) {\n        return;\n      }\n      var pluginsData = {};\n      var _iterator = _createForOfIteratorHelper(this.plugins),\n        _step;\n      try {\n        for (_iterator.s(); !(_step = _iterator.n()).done;) {\n          var plugin = _step.value;\n          var pluginData = (typeof plugin.analyze === 'function' ? plugin.analyze() : undefined) || {};\n          pluginsData[plugin.constructor.identifier] = pluginData;\n          pluginData.version = plugin.constructor.version;\n        }\n      } catch (err) {\n        _iterator.e(err);\n      } finally {\n        _iterator.f();\n      }\n      this.connector.sendCommand({\n        command: 'info',\n        plugins: pluginsData,\n        url: this.window.location.href\n      });\n    }\n  }]);\n  return LiveReload;\n}();\n;\nexports.LiveReload = LiveReload;\n\n},{\"./connector\":103,\"./options\":107,\"./protocol\":108,\"./reloader\":109,\"./timer\":111,\"core-js/modules/es6.array.from.js\":84,\"core-js/modules/es6.array.iterator.js\":85,\"core-js/modules/es6.array.slice.js\":87,\"core-js/modules/es6.number.constructor.js\":88,\"core-js/modules/es6.object.keys.js\":90,\"core-js/modules/es6.object.to-string.js\":91,\"core-js/modules/es6.regexp.match.js\":94,\"core-js/modules/es6.string.iterator.js\":98,\"core-js/modules/es6.symbol.js\":99,\"core-js/modules/web.dom.iterable.js\":102}],107:[function(require,module,exports){\n\"use strict\";\n\nrequire(\"core-js/modules/es6.regexp.split.js\");\nrequire(\"core-js/modules/es6.symbol.js\");\nrequire(\"core-js/modules/es6.number.constructor.js\");\nrequire(\"core-js/modules/es6.array.from.js\");\nrequire(\"core-js/modules/es6.string.iterator.js\");\nrequire(\"core-js/modules/es6.object.to-string.js\");\nrequire(\"core-js/modules/es6.array.iterator.js\");\nrequire(\"core-js/modules/web.dom.iterable.js\");\nrequire(\"core-js/modules/es6.regexp.match.js\");\nrequire(\"core-js/modules/es6.regexp.replace.js\");\nrequire(\"core-js/modules/es6.array.slice.js\");\nfunction _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== \"undefined\" && o[Symbol.iterator] || o[\"@@iterator\"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === \"number\") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e2) { throw _e2; }, f: F }; } throw new TypeError(\"Invalid attempt to iterate non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e3) { didErr = true; err = _e3; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }\nfunction _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }\nfunction _nonIterableRest() { throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); }\nfunction _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === \"string\") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === \"Object\" && o.constructor) n = o.constructor.name; if (n === \"Map\" || n === \"Set\") return Array.from(o); if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }\nfunction _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }\nfunction _iterableToArrayLimit(arr, i) { var _i = null == arr ? null : \"undefined\" != typeof Symbol && arr[Symbol.iterator] || arr[\"@@iterator\"]; if (null != _i) { var _s, _e, _x, _r, _arr = [], _n = !0, _d = !1; try { if (_x = (_i = _i.call(arr)).next, 0 === i) { if (Object(_i) !== _i) return; _n = !1; } else for (; !(_n = (_s = _x.call(_i)).done) && (_arr.push(_s.value), _arr.length !== i); _n = !0); } catch (err) { _d = !0, _e = err; } finally { try { if (!_n && null != _i.return && (_r = _i.return(), Object(_r) !== _r)) return; } finally { if (_d) throw _e; } } return _arr; } }\nfunction _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && \"function\" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }, _typeof(obj); }\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, \"prototype\", { writable: false }); return Constructor; }\nfunction _toPropertyKey(arg) { var key = _toPrimitive(arg, \"string\"); return _typeof(key) === \"symbol\" ? key : String(key); }\nfunction _toPrimitive(input, hint) { if (_typeof(input) !== \"object\" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || \"default\"); if (_typeof(res) !== \"object\") return res; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (hint === \"string\" ? String : Number)(input); }\nvar Options = /*#__PURE__*/function () {\n  function Options() {\n    _classCallCheck(this, Options);\n    this.https = false;\n    this.host = null;\n    var port = 35729; // backing variable for port property closure\n\n    // we allow port to be overridden with a falsy value to indicate\n    // that we should not add a port specification to the backend url;\n    // port is now either a number, or a non-numeric string\n    Object.defineProperty(this, 'port', {\n      get: function get() {\n        return port;\n      },\n      set: function set(v) {\n        port = v ? isNaN(v) ? v : +v : '';\n      }\n    });\n    this.snipver = null;\n    this.ext = null;\n    this.extver = null;\n    this.mindelay = 1000;\n    this.maxdelay = 60000;\n    this.handshake_timeout = 5000;\n    var pluginOrder = [];\n    Object.defineProperty(this, 'pluginOrder', {\n      get: function get() {\n        return pluginOrder;\n      },\n      set: function set(v) {\n        pluginOrder.push.apply(pluginOrder, v.split(/[,;]/));\n      }\n    });\n  }\n  _createClass(Options, [{\n    key: \"set\",\n    value: function set(name, value) {\n      if (typeof value === 'undefined') {\n        return;\n      }\n      if (!isNaN(+value)) {\n        value = +value;\n      }\n      this[name] = value;\n    }\n  }]);\n  return Options;\n}();\nOptions.extract = function (document) {\n  for (var _i = 0, _Array$from = Array.from(document.getElementsByTagName('script')); _i < _Array$from.length; _i++) {\n    var element = _Array$from[_i];\n    // eslint-disable-next-line no-var\n    var m;\n    // eslint-disable-next-line no-var\n    var mm;\n    var src = element.src;\n    var srcAttr = element.getAttribute('src');\n    var lrUrlRegexp = /^([^:]+:\\/\\/([^/:]+|\\[[0-9a-f:]+\\])(?::(\\d+))?\\/|\\/\\/|\\/)?([^/].*\\/)?z?livereload\\.js(?:\\?(.*))?$/;\n    //                   ^proto:// ^host                      ^port     ^//  ^/   ^folder\n    var lrUrlRegexpAttr = /^(?:(?:([^:/]+)?:?)\\/{0,2})([^:]+|\\[[0-9a-f:]+\\])(?::(\\d+))?/;\n    //                              ^proto             ^host/folder             ^port\n\n    if ((m = src.match(lrUrlRegexp)) && (mm = srcAttr.match(lrUrlRegexpAttr))) {\n      var _m = m,\n        _m2 = _slicedToArray(_m, 6),\n        host = _m2[2],\n        port = _m2[3],\n        params = _m2[5];\n      var _mm = mm,\n        _mm2 = _slicedToArray(_mm, 4),\n        portFromAttr = _mm2[3];\n      var options = new Options();\n      options.https = element.src.indexOf('https') === 0;\n      options.host = host;\n\n      // use port number that the script is loaded from as default\n      // for explicitly blank value; enables livereload through proxy\n      var ourPort = parseInt(port || portFromAttr, 10) || '';\n\n      // if port is specified in script use that as default instead\n      options.port = ourPort || options.port;\n      if (params) {\n        var _iterator = _createForOfIteratorHelper(params.split('&')),\n          _step;\n        try {\n          for (_iterator.s(); !(_step = _iterator.n()).done;) {\n            var pair = _step.value;\n            // eslint-disable-next-line no-var\n            var keyAndValue;\n            if ((keyAndValue = pair.split('=')).length > 1) {\n              options.set(keyAndValue[0].replace(/-/g, '_'), keyAndValue.slice(1).join('='));\n            }\n          }\n        } catch (err) {\n          _iterator.e(err);\n        } finally {\n          _iterator.f();\n        }\n      }\n\n      // if port was overwritten by empty value, then revert to using the same\n      // port as the script is running from again (note that it shouldn't be\n      // coerced to a numeric value, since that will be 0 for the empty string)\n      options.port = options.port || ourPort;\n      return options;\n    }\n  }\n  return null;\n};\nexports.Options = Options;\n\n},{\"core-js/modules/es6.array.from.js\":84,\"core-js/modules/es6.array.iterator.js\":85,\"core-js/modules/es6.array.slice.js\":87,\"core-js/modules/es6.number.constructor.js\":88,\"core-js/modules/es6.object.to-string.js\":91,\"core-js/modules/es6.regexp.match.js\":94,\"core-js/modules/es6.regexp.replace.js\":95,\"core-js/modules/es6.regexp.split.js\":96,\"core-js/modules/es6.string.iterator.js\":98,\"core-js/modules/es6.symbol.js\":99,\"core-js/modules/web.dom.iterable.js\":102}],108:[function(require,module,exports){\n\"use strict\";\n\nrequire(\"core-js/modules/es6.number.constructor.js\");\nrequire(\"core-js/modules/es6.array.slice.js\");\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && \"function\" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }, _typeof(obj); }\nrequire(\"core-js/modules/es6.regexp.match.js\");\nrequire(\"core-js/modules/es6.regexp.constructor.js\");\nrequire(\"core-js/modules/es6.string.includes.js\");\nrequire(\"core-js/modules/es7.array.includes.js\");\nrequire(\"core-js/modules/es6.symbol.js\");\nrequire(\"core-js/modules/es6.array.from.js\");\nrequire(\"core-js/modules/es6.string.iterator.js\");\nrequire(\"core-js/modules/es6.object.to-string.js\");\nrequire(\"core-js/modules/es6.array.iterator.js\");\nrequire(\"core-js/modules/web.dom.iterable.js\");\nfunction _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }\nfunction _nonIterableRest() { throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); }\nfunction _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === \"string\") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === \"Object\" && o.constructor) n = o.constructor.name; if (n === \"Map\" || n === \"Set\") return Array.from(o); if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }\nfunction _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }\nfunction _iterableToArrayLimit(arr, i) { var _i = null == arr ? null : \"undefined\" != typeof Symbol && arr[Symbol.iterator] || arr[\"@@iterator\"]; if (null != _i) { var _s, _e, _x, _r, _arr = [], _n = !0, _d = !1; try { if (_x = (_i = _i.call(arr)).next, 0 === i) { if (Object(_i) !== _i) return; _n = !1; } else for (; !(_n = (_s = _x.call(_i)).done) && (_arr.push(_s.value), _arr.length !== i); _n = !0); } catch (err) { _d = !0, _e = err; } finally { try { if (!_n && null != _i.return && (_r = _i.return(), Object(_r) !== _r)) return; } finally { if (_d) throw _e; } } return _arr; } }\nfunction _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, \"prototype\", { writable: false }); return Constructor; }\nfunction _toPropertyKey(arg) { var key = _toPrimitive(arg, \"string\"); return _typeof(key) === \"symbol\" ? key : String(key); }\nfunction _toPrimitive(input, hint) { if (_typeof(input) !== \"object\" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || \"default\"); if (_typeof(res) !== \"object\") return res; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (hint === \"string\" ? String : Number)(input); }\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\nvar PROTOCOL_6, PROTOCOL_7;\nexports.PROTOCOL_6 = PROTOCOL_6 = 'http://livereload.com/protocols/official-6';\nexports.PROTOCOL_7 = PROTOCOL_7 = 'http://livereload.com/protocols/official-7';\nvar ProtocolError = /*#__PURE__*/_createClass(function ProtocolError(reason, data) {\n  _classCallCheck(this, ProtocolError);\n  this.message = \"LiveReload protocol error (\".concat(reason, \") after receiving data: \\\"\").concat(data, \"\\\".\");\n});\n;\nvar Parser = /*#__PURE__*/function () {\n  function Parser(handlers) {\n    _classCallCheck(this, Parser);\n    this.handlers = handlers;\n    this.reset();\n  }\n  _createClass(Parser, [{\n    key: \"reset\",\n    value: function reset() {\n      this.protocol = null;\n    }\n  }, {\n    key: \"process\",\n    value: function process(data) {\n      try {\n        var message;\n        if (!this.protocol) {\n          // eslint-disable-next-line prefer-regex-literals\n          if (data.match(new RegExp('^!!ver:([\\\\d.]+)$'))) {\n            this.protocol = 6;\n          } else if (message = this._parseMessage(data, ['hello'])) {\n            if (!message.protocols.length) {\n              throw new ProtocolError('no protocols specified in handshake message');\n            } else if (Array.from(message.protocols).includes(PROTOCOL_7)) {\n              this.protocol = 7;\n            } else if (Array.from(message.protocols).includes(PROTOCOL_6)) {\n              this.protocol = 6;\n            } else {\n              throw new ProtocolError('no supported protocols found');\n            }\n          }\n          return this.handlers.connected(this.protocol);\n        }\n        if (this.protocol === 6) {\n          message = JSON.parse(data);\n          if (!message.length) {\n            throw new ProtocolError('protocol 6 messages must be arrays');\n          }\n          var _Array$from = Array.from(message),\n            _Array$from2 = _slicedToArray(_Array$from, 2),\n            command = _Array$from2[0],\n            options = _Array$from2[1];\n          if (command !== 'refresh') {\n            throw new ProtocolError('unknown protocol 6 command');\n          }\n          return this.handlers.message({\n            command: 'reload',\n            path: options.path,\n            liveCSS: options.apply_css_live != null ? options.apply_css_live : true\n          });\n        }\n        message = this._parseMessage(data, ['reload', 'alert']);\n        return this.handlers.message(message);\n      } catch (e) {\n        if (e instanceof ProtocolError) {\n          return this.handlers.error(e);\n        }\n        throw e;\n      }\n    }\n  }, {\n    key: \"_parseMessage\",\n    value: function _parseMessage(data, validCommands) {\n      var message;\n      try {\n        message = JSON.parse(data);\n      } catch (e) {\n        throw new ProtocolError('unparsable JSON', data);\n      }\n      if (!message.command) {\n        throw new ProtocolError('missing \"command\" key', data);\n      }\n      if (!validCommands.includes(message.command)) {\n        throw new ProtocolError(\"invalid command '\".concat(message.command, \"', only valid commands are: \").concat(validCommands.join(', '), \")\"), data);\n      }\n      return message;\n    }\n  }]);\n  return Parser;\n}();\n;\nexports.ProtocolError = ProtocolError;\nexports.Parser = Parser;\n\n},{\"core-js/modules/es6.array.from.js\":84,\"core-js/modules/es6.array.iterator.js\":85,\"core-js/modules/es6.array.slice.js\":87,\"core-js/modules/es6.number.constructor.js\":88,\"core-js/modules/es6.object.to-string.js\":91,\"core-js/modules/es6.regexp.constructor.js\":92,\"core-js/modules/es6.regexp.match.js\":94,\"core-js/modules/es6.string.includes.js\":97,\"core-js/modules/es6.string.iterator.js\":98,\"core-js/modules/es6.symbol.js\":99,\"core-js/modules/es7.array.includes.js\":100,\"core-js/modules/web.dom.iterable.js\":102}],109:[function(require,module,exports){\n\"use strict\";\n\nrequire(\"core-js/modules/es6.number.constructor.js\");\nrequire(\"core-js/modules/es6.object.keys.js\");\nrequire(\"core-js/modules/es6.object.get-own-property-descriptor.js\");\nrequire(\"core-js/modules/es7.object.get-own-property-descriptors.js\");\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && \"function\" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }, _typeof(obj); }\nrequire(\"core-js/modules/es6.array.slice.js\");\nrequire(\"core-js/modules/es6.regexp.replace.js\");\nrequire(\"core-js/modules/es6.regexp.constructor.js\");\nrequire(\"core-js/modules/es6.regexp.split.js\");\nrequire(\"core-js/modules/es6.symbol.js\");\nrequire(\"core-js/modules/es6.array.from.js\");\nrequire(\"core-js/modules/es6.string.iterator.js\");\nrequire(\"core-js/modules/es6.object.to-string.js\");\nrequire(\"core-js/modules/es6.array.iterator.js\");\nrequire(\"core-js/modules/web.dom.iterable.js\");\nrequire(\"core-js/modules/es6.regexp.match.js\");\nrequire(\"core-js/modules/es6.array.filter.js\");\nrequire(\"core-js/modules/es6.array.map.js\");\nfunction ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }\nfunction _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }\nfunction _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, \"prototype\", { writable: false }); return Constructor; }\nfunction _toPropertyKey(arg) { var key = _toPrimitive(arg, \"string\"); return _typeof(key) === \"symbol\" ? key : String(key); }\nfunction _toPrimitive(input, hint) { if (_typeof(input) !== \"object\" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || \"default\"); if (_typeof(res) !== \"object\") return res; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (hint === \"string\" ? String : Number)(input); }\nfunction _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== \"undefined\" && o[Symbol.iterator] || o[\"@@iterator\"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === \"number\") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError(\"Invalid attempt to iterate non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }\nfunction _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === \"string\") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === \"Object\" && o.constructor) n = o.constructor.name; if (n === \"Map\" || n === \"Set\") return Array.from(o); if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }\nfunction _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }\n/* global CSSRule */\n\n/**\n * Split URL\n * @param  {string} url\n * @return {object}\n */\nfunction splitUrl(url) {\n  var hash = '';\n  var params = '';\n  var index = url.indexOf('#');\n  if (index >= 0) {\n    hash = url.slice(index);\n    url = url.slice(0, index);\n  }\n\n  // http://your.domain.com/path/to/combo/??file1.css,file2,css\n  var comboSign = url.indexOf('??');\n  if (comboSign >= 0) {\n    if (comboSign + 1 !== url.lastIndexOf('?')) {\n      index = url.lastIndexOf('?');\n    }\n  } else {\n    index = url.indexOf('?');\n  }\n  if (index >= 0) {\n    params = url.slice(index);\n    url = url.slice(0, index);\n  }\n  return {\n    url: url,\n    params: params,\n    hash: hash\n  };\n}\n;\n\n/**\n * Get path from URL (remove protocol, host, port)\n * @param  {string} url\n * @return {string}\n */\nfunction pathFromUrl(url) {\n  if (!url) {\n    return '';\n  }\n  var path;\n  var _splitUrl = splitUrl(url);\n  url = _splitUrl.url;\n  if (url.indexOf('file://') === 0) {\n    // eslint-disable-next-line prefer-regex-literals\n    path = url.replace(new RegExp('^file://(localhost)?'), '');\n  } else {\n    //                        http  :   // hostname  :8080  /\n    // eslint-disable-next-line prefer-regex-literals\n    path = url.replace(new RegExp('^([^:]+:)?//([^:/]+)(:\\\\d*)?/'), '/');\n  }\n\n  // decodeURI has special handling of stuff like semicolons, so use decodeURIComponent\n  return decodeURIComponent(path);\n}\n\n/**\n * Get number of matching path segments\n * @param  {string} left\n * @param  {string} right\n * @return {int}\n */\nfunction numberOfMatchingSegments(left, right) {\n  // get rid of leading slashes and normalize to lower case\n  left = left.replace(/^\\/+/, '').toLowerCase();\n  right = right.replace(/^\\/+/, '').toLowerCase();\n  if (left === right) {\n    return 10000;\n  }\n  var comps1 = left.split(/\\/|\\\\/).reverse();\n  var comps2 = right.split(/\\/|\\\\/).reverse();\n  var len = Math.min(comps1.length, comps2.length);\n  var eqCount = 0;\n  while (eqCount < len && comps1[eqCount] === comps2[eqCount]) {\n    ++eqCount;\n  }\n  return eqCount;\n}\n\n/**\n * Pick best matching path from a collection\n * @param  {string} path         Path to match\n * @param  {array} objects       Collection of paths\n * @param  {function} [pathFunc] Transform applied to each item in collection\n * @return {object}\n */\nfunction pickBestMatch(path, objects) {\n  var pathFunc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function (s) {\n    return s;\n  };\n  var score;\n  var bestMatch = {\n    score: 0\n  };\n  var _iterator = _createForOfIteratorHelper(objects),\n    _step;\n  try {\n    for (_iterator.s(); !(_step = _iterator.n()).done;) {\n      var object = _step.value;\n      score = numberOfMatchingSegments(path, pathFunc(object));\n      if (score > bestMatch.score) {\n        bestMatch = {\n          object: object,\n          score: score\n        };\n      }\n    }\n  } catch (err) {\n    _iterator.e(err);\n  } finally {\n    _iterator.f();\n  }\n  if (bestMatch.score === 0) {\n    return null;\n  }\n  return bestMatch;\n}\n\n/**\n * Test if paths match\n * @param  {string} left\n * @param  {string} right\n * @return {bool}\n */\nfunction pathsMatch(left, right) {\n  return numberOfMatchingSegments(left, right) > 0;\n}\nvar IMAGE_STYLES = [{\n  selector: 'background',\n  styleNames: ['backgroundImage']\n}, {\n  selector: 'border',\n  styleNames: ['borderImage', 'webkitBorderImage', 'MozBorderImage']\n}];\nvar DEFAULT_OPTIONS = {\n  stylesheetReloadTimeout: 15000\n};\nvar IMAGES_REGEX = /\\.(jpe?g|png|gif|svg)$/i;\nvar Reloader = /*#__PURE__*/function () {\n  function Reloader(window, console, Timer) {\n    _classCallCheck(this, Reloader);\n    this.window = window;\n    this.console = console;\n    this.Timer = Timer;\n    this.document = this.window.document;\n    this.importCacheWaitPeriod = 200;\n    this.plugins = [];\n  }\n  _createClass(Reloader, [{\n    key: \"addPlugin\",\n    value: function addPlugin(plugin) {\n      return this.plugins.push(plugin);\n    }\n  }, {\n    key: \"analyze\",\n    value: function analyze(callback) {}\n  }, {\n    key: \"reload\",\n    value: function reload(path) {\n      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n      this.options = _objectSpread(_objectSpread({}, DEFAULT_OPTIONS), options); // avoid passing it through all the funcs\n\n      if (this.options.pluginOrder && this.options.pluginOrder.length) {\n        this.runPluginsByOrder(path, options);\n        return;\n      }\n      for (var _i = 0, _Array$from = Array.from(this.plugins); _i < _Array$from.length; _i++) {\n        var plugin = _Array$from[_i];\n        if (plugin.reload && plugin.reload(path, options)) {\n          return;\n        }\n      }\n      if (options.liveCSS && path.match(/\\.css(?:\\.map)?$/i)) {\n        if (this.reloadStylesheet(path)) {\n          return;\n        }\n      }\n      if (options.liveImg && path.match(IMAGES_REGEX)) {\n        this.reloadImages(path);\n        return;\n      }\n      if (options.isChromeExtension) {\n        this.reloadChromeExtension();\n        return;\n      }\n      return this.reloadPage();\n    }\n  }, {\n    key: \"runPluginsByOrder\",\n    value: function runPluginsByOrder(path, options) {\n      var _this = this;\n      options.pluginOrder.some(function (pluginId) {\n        if (pluginId === 'css') {\n          if (options.liveCSS && path.match(/\\.css(?:\\.map)?$/i)) {\n            if (_this.reloadStylesheet(path)) {\n              return true;\n            }\n          }\n        }\n        if (pluginId === 'img') {\n          if (options.liveImg && path.match(IMAGES_REGEX)) {\n            _this.reloadImages(path);\n            return true;\n          }\n        }\n        if (pluginId === 'extension') {\n          if (options.isChromeExtension) {\n            _this.reloadChromeExtension();\n            return true;\n          }\n        }\n        if (pluginId === 'others') {\n          _this.reloadPage();\n          return true;\n        }\n        if (pluginId === 'external') {\n          return _this.plugins.some(function (plugin) {\n            return plugin.reload && plugin.reload(path, options);\n          });\n        }\n        return _this.plugins.filter(function (plugin) {\n          return plugin.constructor.identifier === pluginId;\n        }).some(function (plugin) {\n          return plugin.reload && plugin.reload(path, options);\n        });\n      });\n    }\n  }, {\n    key: \"reloadPage\",\n    value: function reloadPage() {\n      return this.window.document.location.reload();\n    }\n  }, {\n    key: \"reloadChromeExtension\",\n    value: function reloadChromeExtension() {\n      return this.window.chrome.runtime.reload();\n    }\n  }, {\n    key: \"reloadImages\",\n    value: function reloadImages(path) {\n      var _this2 = this;\n      var img;\n      var expando = this.generateUniqueString();\n      for (var _i2 = 0, _Array$from2 = Array.from(this.document.images); _i2 < _Array$from2.length; _i2++) {\n        img = _Array$from2[_i2];\n        if (pathsMatch(path, pathFromUrl(img.src))) {\n          img.src = this.generateCacheBustUrl(img.src, expando);\n        }\n      }\n      if (this.document.querySelectorAll) {\n        for (var _i3 = 0, _IMAGE_STYLES = IMAGE_STYLES; _i3 < _IMAGE_STYLES.length; _i3++) {\n          var _IMAGE_STYLES$_i = _IMAGE_STYLES[_i3],\n            selector = _IMAGE_STYLES$_i.selector,\n            styleNames = _IMAGE_STYLES$_i.styleNames;\n          for (var _i4 = 0, _Array$from3 = Array.from(this.document.querySelectorAll(\"[style*=\".concat(selector, \"]\"))); _i4 < _Array$from3.length; _i4++) {\n            img = _Array$from3[_i4];\n            this.reloadStyleImages(img.style, styleNames, path, expando);\n          }\n        }\n      }\n      if (this.document.styleSheets) {\n        return Array.from(this.document.styleSheets).map(function (styleSheet) {\n          return _this2.reloadStylesheetImages(styleSheet, path, expando);\n        });\n      }\n    }\n  }, {\n    key: \"reloadStylesheetImages\",\n    value: function reloadStylesheetImages(styleSheet, path, expando) {\n      var rules;\n      try {\n        rules = (styleSheet || {}).cssRules;\n      } catch (e) {}\n      if (!rules) {\n        return;\n      }\n      for (var _i5 = 0, _Array$from4 = Array.from(rules); _i5 < _Array$from4.length; _i5++) {\n        var rule = _Array$from4[_i5];\n        switch (rule.type) {\n          case CSSRule.IMPORT_RULE:\n            this.reloadStylesheetImages(rule.styleSheet, path, expando);\n            break;\n          case CSSRule.STYLE_RULE:\n            for (var _i6 = 0, _IMAGE_STYLES2 = IMAGE_STYLES; _i6 < _IMAGE_STYLES2.length; _i6++) {\n              var styleNames = _IMAGE_STYLES2[_i6].styleNames;\n              this.reloadStyleImages(rule.style, styleNames, path, expando);\n            }\n            break;\n          case CSSRule.MEDIA_RULE:\n            this.reloadStylesheetImages(rule, path, expando);\n            break;\n        }\n      }\n    }\n  }, {\n    key: \"reloadStyleImages\",\n    value: function reloadStyleImages(style, styleNames, path, expando) {\n      var _this3 = this;\n      var _iterator2 = _createForOfIteratorHelper(styleNames),\n        _step2;\n      try {\n        for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {\n          var styleName = _step2.value;\n          var value = style[styleName];\n          if (typeof value === 'string') {\n            // eslint-disable-next-line prefer-regex-literals\n            var newValue = value.replace(new RegExp('\\\\burl\\\\s*\\\\(([^)]*)\\\\)'), function (match, src) {\n              if (pathsMatch(path, pathFromUrl(src))) {\n                return \"url(\".concat(_this3.generateCacheBustUrl(src, expando), \")\");\n              }\n              return match;\n            });\n            if (newValue !== value) {\n              style[styleName] = newValue;\n            }\n          }\n        }\n      } catch (err) {\n        _iterator2.e(err);\n      } finally {\n        _iterator2.f();\n      }\n    }\n  }, {\n    key: \"reloadStylesheet\",\n    value: function reloadStylesheet(path) {\n      var _this4 = this;\n      var options = this.options || DEFAULT_OPTIONS;\n\n      // has to be a real array, because DOMNodeList will be modified\n      var style;\n      var link;\n      var links = function () {\n        var result = [];\n        for (var _i7 = 0, _Array$from5 = Array.from(_this4.document.getElementsByTagName('link')); _i7 < _Array$from5.length; _i7++) {\n          link = _Array$from5[_i7];\n          if (link.rel.match(/^stylesheet$/i) && !link.__LiveReload_pendingRemoval) {\n            result.push(link);\n          }\n        }\n        return result;\n      }();\n\n      // find all imported stylesheets\n      var imported = [];\n      for (var _i8 = 0, _Array$from6 = Array.from(this.document.getElementsByTagName('style')); _i8 < _Array$from6.length; _i8++) {\n        style = _Array$from6[_i8];\n        if (style.sheet) {\n          this.collectImportedStylesheets(style, style.sheet, imported);\n        }\n      }\n      for (var _i9 = 0, _Array$from7 = Array.from(links); _i9 < _Array$from7.length; _i9++) {\n        link = _Array$from7[_i9];\n        this.collectImportedStylesheets(link, link.sheet, imported);\n      }\n\n      // handle prefixfree\n      if (this.window.StyleFix && this.document.querySelectorAll) {\n        for (var _i10 = 0, _Array$from8 = Array.from(this.document.querySelectorAll('style[data-href]')); _i10 < _Array$from8.length; _i10++) {\n          style = _Array$from8[_i10];\n          links.push(style);\n        }\n      }\n      this.console.log(\"LiveReload found \".concat(links.length, \" LINKed stylesheets, \").concat(imported.length, \" @imported stylesheets\"));\n      var match = pickBestMatch(path, links.concat(imported), function (link) {\n        return pathFromUrl(_this4.linkHref(link));\n      });\n      if (match) {\n        if (match.object.rule) {\n          this.console.log(\"LiveReload is reloading imported stylesheet: \".concat(match.object.href));\n          this.reattachImportedRule(match.object);\n        } else {\n          this.console.log(\"LiveReload is reloading stylesheet: \".concat(this.linkHref(match.object)));\n          this.reattachStylesheetLink(match.object);\n        }\n      } else {\n        if (options.reloadMissingCSS) {\n          this.console.log(\"LiveReload will reload all stylesheets because path '\".concat(path, \"' did not match any specific one. To disable this behavior, set 'options.reloadMissingCSS' to 'false'.\"));\n          for (var _i11 = 0, _Array$from9 = Array.from(links); _i11 < _Array$from9.length; _i11++) {\n            link = _Array$from9[_i11];\n            this.reattachStylesheetLink(link);\n          }\n        } else {\n          this.console.log(\"LiveReload will not reload path '\".concat(path, \"' because the stylesheet was not found on the page and 'options.reloadMissingCSS' was set to 'false'.\"));\n        }\n      }\n      return true;\n    }\n  }, {\n    key: \"collectImportedStylesheets\",\n    value: function collectImportedStylesheets(link, styleSheet, result) {\n      // in WebKit, styleSheet.cssRules is null for inaccessible stylesheets;\n      // Firefox/Opera may throw exceptions\n      var rules;\n      try {\n        rules = (styleSheet || {}).cssRules;\n      } catch (e) {}\n      if (rules && rules.length) {\n        for (var index = 0; index < rules.length; index++) {\n          var rule = rules[index];\n          switch (rule.type) {\n            case CSSRule.CHARSET_RULE:\n              continue;\n            // do nothing\n            case CSSRule.IMPORT_RULE:\n              result.push({\n                link: link,\n                rule: rule,\n                index: index,\n                href: rule.href\n              });\n              this.collectImportedStylesheets(link, rule.styleSheet, result);\n              break;\n            default:\n              break;\n            // import rules can only be preceded by charset rules\n          }\n        }\n      }\n    }\n  }, {\n    key: \"waitUntilCssLoads\",\n    value: function waitUntilCssLoads(clone, func) {\n      var _this5 = this;\n      var options = this.options || DEFAULT_OPTIONS;\n      var callbackExecuted = false;\n      var executeCallback = function executeCallback() {\n        if (callbackExecuted) {\n          return;\n        }\n        callbackExecuted = true;\n        return func();\n      };\n\n      // supported by Chrome 19+, Safari 5.2+, Firefox 9+, Opera 9+, IE6+\n      // http://www.zachleat.com/web/load-css-dynamically/\n      // http://pieisgood.org/test/script-link-events/\n      clone.onload = function () {\n        _this5.console.log('LiveReload: the new stylesheet has finished loading');\n        _this5.knownToSupportCssOnLoad = true;\n        return executeCallback();\n      };\n      if (!this.knownToSupportCssOnLoad) {\n        // polling\n        var _poll;\n        (_poll = function poll() {\n          if (clone.sheet) {\n            _this5.console.log('LiveReload is polling until the new CSS finishes loading...');\n            return executeCallback();\n          }\n          return _this5.Timer.start(50, _poll);\n        })();\n      }\n\n      // fail safe\n      return this.Timer.start(options.stylesheetReloadTimeout, executeCallback);\n    }\n  }, {\n    key: \"linkHref\",\n    value: function linkHref(link) {\n      // prefixfree uses data-href when it turns LINK into STYLE\n      return link.href || link.getAttribute && link.getAttribute('data-href');\n    }\n  }, {\n    key: \"reattachStylesheetLink\",\n    value: function reattachStylesheetLink(link) {\n      var _this6 = this;\n      // ignore LINKs that will be removed by LR soon\n      var clone;\n      if (link.__LiveReload_pendingRemoval) {\n        return;\n      }\n      link.__LiveReload_pendingRemoval = true;\n      if (link.tagName === 'STYLE') {\n        // prefixfree\n        clone = this.document.createElement('link');\n        clone.rel = 'stylesheet';\n        clone.media = link.media;\n        clone.disabled = link.disabled;\n      } else {\n        clone = link.cloneNode(false);\n      }\n      clone.href = this.generateCacheBustUrl(this.linkHref(link));\n\n      // insert the new LINK before the old one\n      var parent = link.parentNode;\n      if (parent.lastChild === link) {\n        parent.appendChild(clone);\n      } else {\n        parent.insertBefore(clone, link.nextSibling);\n      }\n      return this.waitUntilCssLoads(clone, function () {\n        var additionalWaitingTime;\n        if (/AppleWebKit/.test(_this6.window.navigator.userAgent)) {\n          additionalWaitingTime = 5;\n        } else {\n          additionalWaitingTime = 200;\n        }\n        return _this6.Timer.start(additionalWaitingTime, function () {\n          if (!link.parentNode) {\n            return;\n          }\n          link.parentNode.removeChild(link);\n          clone.onreadystatechange = null;\n          return _this6.window.StyleFix ? _this6.window.StyleFix.link(clone) : undefined;\n        });\n      }); // prefixfree\n    }\n  }, {\n    key: \"reattachImportedRule\",\n    value: function reattachImportedRule(_ref) {\n      var _this7 = this;\n      var rule = _ref.rule,\n        index = _ref.index,\n        link = _ref.link;\n      var parent = rule.parentStyleSheet;\n      var href = this.generateCacheBustUrl(rule.href);\n      var media = rule.media.length ? [].join.call(rule.media, ', ') : '';\n      var newRule = \"@import url(\\\"\".concat(href, \"\\\") \").concat(media, \";\");\n\n      // used to detect if reattachImportedRule has been called again on the same rule\n      rule.__LiveReload_newHref = href;\n\n      // WORKAROUND FOR WEBKIT BUG: WebKit resets all styles if we add @import'ed\n      // stylesheet that hasn't been cached yet. Workaround is to pre-cache the\n      // stylesheet by temporarily adding it as a LINK tag.\n      var tempLink = this.document.createElement('link');\n      tempLink.rel = 'stylesheet';\n      tempLink.href = href;\n      tempLink.__LiveReload_pendingRemoval = true; // exclude from path matching\n\n      if (link.parentNode) {\n        link.parentNode.insertBefore(tempLink, link);\n      }\n\n      // wait for it to load\n      return this.Timer.start(this.importCacheWaitPeriod, function () {\n        if (tempLink.parentNode) {\n          tempLink.parentNode.removeChild(tempLink);\n        }\n\n        // if another reattachImportedRule call is in progress, abandon this one\n        if (rule.__LiveReload_newHref !== href) {\n          return;\n        }\n        parent.insertRule(newRule, index);\n        parent.deleteRule(index + 1);\n\n        // save the new rule, so that we can detect another reattachImportedRule call\n        rule = parent.cssRules[index];\n        rule.__LiveReload_newHref = href;\n\n        // repeat again for good measure\n        return _this7.Timer.start(_this7.importCacheWaitPeriod, function () {\n          // if another reattachImportedRule call is in progress, abandon this one\n          if (rule.__LiveReload_newHref !== href) {\n            return;\n          }\n          parent.insertRule(newRule, index);\n          return parent.deleteRule(index + 1);\n        });\n      });\n    }\n  }, {\n    key: \"generateUniqueString\",\n    value: function generateUniqueString() {\n      return \"livereload=\".concat(Date.now());\n    }\n  }, {\n    key: \"generateCacheBustUrl\",\n    value: function generateCacheBustUrl(url, expando) {\n      var options = this.options || DEFAULT_OPTIONS;\n      var hash, oldParams;\n      if (!expando) {\n        expando = this.generateUniqueString();\n      }\n      var _splitUrl2 = splitUrl(url);\n      url = _splitUrl2.url;\n      hash = _splitUrl2.hash;\n      oldParams = _splitUrl2.params;\n      if (options.overrideURL) {\n        if (url.indexOf(options.serverURL) < 0) {\n          var originalUrl = url;\n          url = options.serverURL + options.overrideURL + '?url=' + encodeURIComponent(url);\n          this.console.log(\"LiveReload is overriding source URL \".concat(originalUrl, \" with \").concat(url));\n        }\n      }\n      var params = oldParams.replace(/(\\?|&)livereload=(\\d+)/, function (match, sep) {\n        return \"\".concat(sep).concat(expando);\n      });\n      if (params === oldParams) {\n        if (oldParams.length === 0) {\n          params = \"?\".concat(expando);\n        } else {\n          params = \"\".concat(oldParams, \"&\").concat(expando);\n        }\n      }\n      return url + params + hash;\n    }\n  }]);\n  return Reloader;\n}();\n;\nexports.splitUrl = splitUrl;\nexports.pathFromUrl = pathFromUrl;\nexports.numberOfMatchingSegments = numberOfMatchingSegments;\nexports.pickBestMatch = pickBestMatch;\nexports.pathsMatch = pathsMatch;\nexports.Reloader = Reloader;\n\n},{\"core-js/modules/es6.array.filter.js\":83,\"core-js/modules/es6.array.from.js\":84,\"core-js/modules/es6.array.iterator.js\":85,\"core-js/modules/es6.array.map.js\":86,\"core-js/modules/es6.array.slice.js\":87,\"core-js/modules/es6.number.constructor.js\":88,\"core-js/modules/es6.object.get-own-property-descriptor.js\":89,\"core-js/modules/es6.object.keys.js\":90,\"core-js/modules/es6.object.to-string.js\":91,\"core-js/modules/es6.regexp.constructor.js\":92,\"core-js/modules/es6.regexp.match.js\":94,\"core-js/modules/es6.regexp.replace.js\":95,\"core-js/modules/es6.regexp.split.js\":96,\"core-js/modules/es6.string.iterator.js\":98,\"core-js/modules/es6.symbol.js\":99,\"core-js/modules/es7.object.get-own-property-descriptors.js\":101,\"core-js/modules/web.dom.iterable.js\":102}],110:[function(require,module,exports){\n\"use strict\";\n\nrequire(\"core-js/modules/es6.regexp.match.js\");\nvar CustomEvents = require('./customevents');\nvar LiveReload = window.LiveReload = new (require('./livereload').LiveReload)(window);\nfor (var k in window) {\n  if (k.match(/^LiveReloadPlugin/)) {\n    LiveReload.addPlugin(window[k]);\n  }\n}\nLiveReload.addPlugin(require('./less'));\nLiveReload.on('shutdown', function () {\n  return delete window.LiveReload;\n});\nLiveReload.on('connect', function () {\n  return CustomEvents.fire(document, 'LiveReloadConnect');\n});\nLiveReload.on('disconnect', function () {\n  return CustomEvents.fire(document, 'LiveReloadDisconnect');\n});\nCustomEvents.bind(document, 'LiveReloadShutDown', function () {\n  return LiveReload.shutDown();\n});\n\n},{\"./customevents\":104,\"./less\":105,\"./livereload\":106,\"core-js/modules/es6.regexp.match.js\":94}],111:[function(require,module,exports){\n\"use strict\";\n\nrequire(\"core-js/modules/es6.string.iterator.js\");\nrequire(\"core-js/modules/es6.object.to-string.js\");\nrequire(\"core-js/modules/es6.array.iterator.js\");\nrequire(\"core-js/modules/web.dom.iterable.js\");\nrequire(\"core-js/modules/es6.symbol.js\");\nrequire(\"core-js/modules/es6.number.constructor.js\");\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && \"function\" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }, _typeof(obj); }\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, \"prototype\", { writable: false }); return Constructor; }\nfunction _toPropertyKey(arg) { var key = _toPrimitive(arg, \"string\"); return _typeof(key) === \"symbol\" ? key : String(key); }\nfunction _toPrimitive(input, hint) { if (_typeof(input) !== \"object\" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || \"default\"); if (_typeof(res) !== \"object\") return res; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (hint === \"string\" ? String : Number)(input); }\nvar Timer = /*#__PURE__*/function () {\n  function Timer(func) {\n    var _this = this;\n    _classCallCheck(this, Timer);\n    this.func = func;\n    this.running = false;\n    this.id = null;\n    this._handler = function () {\n      _this.running = false;\n      _this.id = null;\n      return _this.func();\n    };\n  }\n  _createClass(Timer, [{\n    key: \"start\",\n    value: function start(timeout) {\n      if (this.running) {\n        clearTimeout(this.id);\n      }\n      this.id = setTimeout(this._handler, timeout);\n      this.running = true;\n    }\n  }, {\n    key: \"stop\",\n    value: function stop() {\n      if (this.running) {\n        clearTimeout(this.id);\n        this.running = false;\n        this.id = null;\n      }\n    }\n  }]);\n  return Timer;\n}();\n;\nTimer.start = function (timeout, func) {\n  return setTimeout(func, timeout);\n};\nexports.Timer = Timer;\n\n},{\"core-js/modules/es6.array.iterator.js\":85,\"core-js/modules/es6.number.constructor.js\":88,\"core-js/modules/es6.object.to-string.js\":91,\"core-js/modules/es6.string.iterator.js\":98,\"core-js/modules/es6.symbol.js\":99,\"core-js/modules/web.dom.iterable.js\":102}]},{},[110]);\n"
  },
  {
    "path": "guide/webapp/worker/reload.py",
    "content": "from asyncio import sleep\nfrom multiprocessing import Manager\nfrom pathlib import Path\nfrom queue import Empty, Queue\nfrom typing import Any\n\nimport ujson\n\nfrom sanic import Request, Sanic, Websocket\n\n\ndef setup_livereload(app: Sanic) -> None:\n    @app.main_process_start\n    async def main_process_start(app: Sanic):\n        app.ctx.manager = Manager()\n        app.shared_ctx.reload_queue = app.ctx.manager.Queue()\n\n    @app.main_process_ready\n    async def main_process_ready(app: Sanic):\n        app.manager.manage(\n            \"Livereload\",\n            _run_reload_server,\n            {\n                \"reload_queue\": app.shared_ctx.reload_queue,\n                \"debug\": app.state.is_debug,\n                \"state\": app.manager.worker_state,\n            },\n        )\n\n    @app.main_process_stop\n    async def main_process_stop(app: Sanic):\n        app.ctx.manager.shutdown()\n\n    @app.before_server_start\n    async def before_server_start(app: Sanic):\n        app.shared_ctx.reload_queue.put(\"reload\")\n\n    @app.after_server_start\n    async def after_server_start(app: Sanic):\n        app.m.state[\"ready\"] = True\n\n    @app.before_server_stop\n    async def before_server_stop(app: Sanic):\n        app.m.state[\"ready\"] = False\n\n\nclass Livereload:\n    SERVER_NAME = \"Reloader\"\n    HELLO = {\n        \"command\": \"hello\",\n        \"protocols\": [\n            \"http://livereload.com/protocols/official-7\",\n        ],\n        \"serverName\": SERVER_NAME,\n    }\n\n    def __init__(\n        self, reload_queue: Queue, debug: bool, state: dict[str, Any]\n    ):\n        self.reload_queue = reload_queue\n        self.app = Sanic(self.SERVER_NAME)\n        self.debug = debug\n        self.state = state\n        self.app.static(\n            \"/livereload.js\", Path(__file__).parent / \"livereload.js\"\n        )\n        self.app.add_websocket_route(\n            self.livereload_handler, \"/livereload\", name=\"livereload\"\n        )\n        self.app.add_task(self._listen_to_queue())\n        self.app.config.EVENT_AUTOREGISTER = True\n\n    def run(self):\n        kwargs = {\n            \"debug\": self.debug,\n            \"access_log\": False,\n            \"single_process\": True,\n            \"port\": 35729,\n        }\n        self.app.run(**kwargs)\n\n    async def _listen_to_queue(self):\n        while True:\n            try:\n                self.reload_queue.get_nowait()\n            except Empty:\n                await sleep(0.5)\n                continue\n            await self.app.dispatch(\"livereload.file.reload\")\n\n    async def livereload_handler(self, request: Request, ws: Websocket):\n        await ws.recv()\n        await ws.send(ujson.dumps(self.HELLO))\n\n        while True:\n            await request.app.event(\"livereload.file.reload\")\n            await self._wait_for_state()\n            await ws.send(ujson.dumps({\"command\": \"reload\", \"path\": \"...\"}))\n\n    async def _wait_for_state(self):\n        while True:\n            states = [\n                state.get(\"ready\")\n                for state in self.state.values()\n                if state.get(\"server\")\n            ]\n            if all(states):\n                await sleep(0.5)\n                break\n\n\ndef _run_reload_server(\n    reload_queue: Queue, debug: bool, state: dict[str, Any]\n):\n    Livereload(reload_queue, debug, state).run()\n"
  },
  {
    "path": "guide/webapp/worker/style.py",
    "content": "# from scss.compiler import compile_string\n\nfrom pygments.formatters import html\nfrom sass import compile as compile_scss\n\nfrom sanic import Sanic\nfrom webapp.display.code_style import SanicCodeStyle\n\n\ndef setup_style(app: Sanic) -> None:\n    index = app.config.STYLE_DIR / \"index.scss\"\n    style_output = app.config.PUBLIC_DIR / \"assets\" / \"style.css\"\n    code_output = app.config.PUBLIC_DIR / \"assets\" / \"code.css\"\n\n    @app.before_server_start\n    async def setup(app: Sanic):\n        scss = compile_scss(\n            string=index.read_text(),\n            include_paths=[\n                str(app.config.NODE_MODULES_DIR),\n                str(app.config.STYLE_DIR),\n            ],\n        )\n        style_output.write_text(scss)\n        formatter = html.HtmlFormatter(\n            style=SanicCodeStyle, full=True, cssfile=code_output\n        )\n        code_output.write_text(formatter.get_style_defs())\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\"setuptools\", \"wheel\"]\nbuild-backend = \"setuptools.build_meta\"\n\n\n[tool.ruff]\ntarget-version = \"py310\"\nline-length = 79\n\n[tool.ruff.lint]\nselect = [\n    \"E\",    # pycodestyle\n    \"F\",    # pyflakes\n    \"I\",    # isort\n    \"W\",    # pycodestyle warnings\n]\nignore = [\n    \"E203\",\n]\n\n[tool.ruff.lint.pydocstyle]\nconvention = \"google\"\n\n[tool.ruff.format]\nquote-style = \"double\"\nindent-style = \"space\"\nskip-magic-trailing-comma = false\nline-ending = \"auto\"\n\n[tool.ruff.lint.isort]\nknown-first-party = [\"sanic\"]\nknown-third-party = [\"pytest\"]\nlines-after-imports = 2\nlines-between-types = 1\n\n[tool.ruff.per-file-ignores]\n\"sanic/__main__.py\" = [\"E402\", \"I001\"]\n\n[[tool.mypy.overrides]]\nmodule = [\n    \"httptools.*\",\n    \"trustme.*\",\n    \"sanic_routing.*\",\n    \"aioquic.*\",\n    \"html5tagger.*\",\n    \"tracerite.*\",\n]\nignore_missing_imports = true\n"
  },
  {
    "path": "readthedocs.yml",
    "content": "version: 2\npython:\n   install:\n      - method: pip\n        path: .\n        extra_requirements:\n            - docs\n\nbuild:\n  os: \"ubuntu-22.04\"\n  tools:\n    python: \"3.9\"\n"
  },
  {
    "path": "sanic/__init__.py",
    "content": "from types import SimpleNamespace\n\nfrom typing_extensions import TypeAlias\n\nfrom sanic.__version__ import __version__\nfrom sanic.app import Sanic\nfrom sanic.blueprints import Blueprint\nfrom sanic.config import Config\nfrom sanic.constants import HTTPMethod\nfrom sanic.exceptions import (\n    BadRequest,\n    ExpectationFailed,\n    FileNotFound,\n    Forbidden,\n    HeaderNotFound,\n    InternalServerError,\n    InvalidHeader,\n    MethodNotAllowed,\n    NotFound,\n    RangeNotSatisfiable,\n    SanicException,\n    ServerError,\n    ServiceUnavailable,\n    Unauthorized,\n)\nfrom sanic.request import Request\nfrom sanic.response import (\n    HTTPResponse,\n    empty,\n    file,\n    html,\n    json,\n    raw,\n    redirect,\n    text,\n)\nfrom sanic.server.websockets.impl import WebsocketImplProtocol as Websocket\n\n\nDefaultSanic: TypeAlias = \"Sanic[Config, SimpleNamespace]\"\n\"\"\"\nA type alias for a Sanic app with a default config and namespace.\n\"\"\"\n\nDefaultRequest: TypeAlias = Request[DefaultSanic, SimpleNamespace]\n\"\"\"\nA type alias for a request with a default Sanic app and namespace.\n\"\"\"\n\n__all__ = (\n    \"__version__\",\n    # Common objects\n    \"Sanic\",\n    \"Config\",\n    \"Blueprint\",\n    \"HTTPMethod\",\n    \"HTTPResponse\",\n    \"Request\",\n    \"Websocket\",\n    # Common types\n    \"DefaultSanic\",\n    \"DefaultRequest\",\n    # Common exceptions\n    \"BadRequest\",\n    \"ExpectationFailed\",\n    \"FileNotFound\",\n    \"Forbidden\",\n    \"HeaderNotFound\",\n    \"InternalServerError\",\n    \"InvalidHeader\",\n    \"MethodNotAllowed\",\n    \"NotFound\",\n    \"RangeNotSatisfiable\",\n    \"SanicException\",\n    \"ServerError\",\n    \"ServiceUnavailable\",\n    \"Unauthorized\",\n    # Common response methods\n    \"empty\",\n    \"file\",\n    \"html\",\n    \"json\",\n    \"raw\",\n    \"redirect\",\n    \"text\",\n)\n"
  },
  {
    "path": "sanic/__main__.py",
    "content": "import tracerite\n\ntry:\n    tracerite.load()\nexcept Exception as exc:\n    raise RuntimeError(\n        \"Failed to initialize tracerite. Please verify that tracerite is \"\n        \"correctly installed and compatible with this environment.\"\n    ) from exc\n\nfrom sanic.cli.app import SanicCLI\nfrom sanic.compat import OS_IS_WINDOWS, enable_windows_color_support\nfrom sanic.startup.errors import maybe_handle_startup_error\n\nif OS_IS_WINDOWS:\n    enable_windows_color_support()\n\n\ndef main(args=None):\n    try:\n        cli = SanicCLI()\n        cli.attach()\n        cli.run(args)\n    except Exception as e:\n        maybe_handle_startup_error(e)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "sanic/__version__.py",
    "content": "__version__ = \"25.12.0\"\n"
  },
  {
    "path": "sanic/app.py",
    "content": "from __future__ import annotations\n\nimport asyncio\nimport logging\nimport logging.config\nimport re\nimport sys\n\nfrom asyncio import (\n    AbstractEventLoop,\n    CancelledError,\n    Task,\n    ensure_future,\n    get_running_loop,\n    wait_for,\n)\nfrom asyncio.futures import Future\nfrom collections import defaultdict, deque\nfrom collections.abc import Awaitable, Coroutine, Iterable, Iterator\nfrom contextlib import contextmanager, suppress\nfrom enum import Enum\nfrom functools import partial, wraps\nfrom inspect import isawaitable\nfrom os import environ\nfrom pathlib import Path\nfrom socket import socket\nfrom traceback import format_exc\nfrom types import SimpleNamespace\nfrom typing import (\n    TYPE_CHECKING,\n    Any,\n    AnyStr,\n    Callable,\n    ClassVar,\n    Generic,\n    Literal,\n    TypeVar,\n    cast,\n    overload,\n)\nfrom urllib.parse import urlencode, urlunparse\n\nfrom sanic_routing.exceptions import FinalizationError, NotFound\nfrom sanic_routing.route import Route\n\nfrom sanic.application.ext import setup_ext\nfrom sanic.application.state import ApplicationState, ServerStage\nfrom sanic.asgi import ASGIApp, Lifespan\nfrom sanic.base.root import BaseSanic\nfrom sanic.blueprint_group import BlueprintGroup\nfrom sanic.blueprints import Blueprint\nfrom sanic.compat import OS_IS_WINDOWS, enable_windows_color_support\nfrom sanic.config import SANIC_PREFIX, Config\nfrom sanic.exceptions import (\n    BadRequest,\n    SanicException,\n    ServerError,\n    URLBuildError,\n)\nfrom sanic.handlers import ErrorHandler\nfrom sanic.helpers import Default, _default\nfrom sanic.http import Stage\nfrom sanic.log import LOGGING_CONFIG_DEFAULTS, error_logger, logger\nfrom sanic.logging.deprecation import deprecation\nfrom sanic.logging.setup import setup_logging\nfrom sanic.middleware import Middleware, MiddlewareLocation\nfrom sanic.mixins.commands import CommandMixin\nfrom sanic.mixins.listeners import ListenerEvent\nfrom sanic.mixins.startup import StartupMixin\nfrom sanic.mixins.static import StaticHandleMixin\nfrom sanic.models.ctx_types import REPLContext\nfrom sanic.models.futures import (\n    FutureException,\n    FutureListener,\n    FutureMiddleware,\n    FutureRegistry,\n    FutureRoute,\n    FutureSignal,\n)\nfrom sanic.models.handler_types import ListenerType, MiddlewareType\nfrom sanic.models.handler_types import Sanic as SanicVar\nfrom sanic.request import Request\nfrom sanic.response import BaseHTTPResponse, HTTPResponse, ResponseStream\nfrom sanic.router import Router\nfrom sanic.server.websockets.impl import ConnectionClosed\nfrom sanic.signals import Event, Signal, SignalRouter\nfrom sanic.touchup import TouchUp, TouchUpMeta\nfrom sanic.types.shared_ctx import SharedContext\nfrom sanic.worker.inspector import Inspector\nfrom sanic.worker.loader import CertLoader\nfrom sanic.worker.manager import WorkerManager\n\n\nif TYPE_CHECKING:\n    try:\n        from sanic_ext import Extend  # type: ignore\n        from sanic_ext.extensions.base import Extension  # type: ignore\n    except ImportError:\n        Extend = TypeVar(\"Extend\", type)  # type: ignore\n\n\nif OS_IS_WINDOWS:  # no cov\n    enable_windows_color_support()\n\nctx_type = TypeVar(\"ctx_type\")\nconfig_type = TypeVar(\"config_type\", bound=Config)\n\n\nclass Sanic(\n    Generic[config_type, ctx_type],\n    StaticHandleMixin,\n    BaseSanic,\n    StartupMixin,\n    CommandMixin,\n    metaclass=TouchUpMeta,\n):\n    \"\"\"The main application instance\n\n    You will create an instance of this class and use it to register\n    routes, listeners, middleware, blueprints, error handlers, etc.\n\n    By convention, it is often called `app`. It must be named using\n    the `name` parameter and is roughly constrained to the same\n    restrictions as a Python module name, however, it can contain\n    hyphens (`-`).\n\n    ```python\n    # will cause an error because it contains spaces\n    Sanic(\"This is not legal\")\n    ```\n\n    ```python\n    # this is legal\n    Sanic(\"Hyphens-are-legal_or_also_underscores\")\n    ```\n\n    Args:\n        name (str): The name of the application. Must be a valid\n            Python module name (including hyphens).\n        config (Optional[config_type]): The configuration to use for\n            the application. Defaults to `None`.\n        ctx (Optional[ctx_type]): The context to use for the\n            application. Defaults to `None`.\n        router (Optional[Router]): The router to use for the\n            application. Defaults to `None`.\n        signal_router (Optional[SignalRouter]): The signal router to\n            use for the application. Defaults to `None`.\n        error_handler (Optional[ErrorHandler]): The error handler to\n            use for the application. Defaults to `None`.\n        env_prefix (Optional[str]): The prefix to use for environment\n            variables. Defaults to `SANIC_`.\n        request_class (Optional[Type[Request]]): The request class to\n            use for the application. Defaults to `Request`.\n        strict_slashes (bool): Whether to enforce strict slashes.\n            Defaults to `False`.\n        log_config (Optional[Dict[str, Any]]): The logging configuration\n            to use for the application. Defaults to `None`.\n        configure_logging (bool): Whether to configure logging.\n            Defaults to `True`.\n        dumps (Optional[Callable[..., AnyStr]]): The function to use\n            for serializing JSON. Defaults to `None`.\n        loads (Optional[Callable[..., Any]]): The function to use\n            for deserializing JSON. Defaults to `None`.\n        inspector (bool): Whether to enable the inspector. Defaults\n            to `False`.\n        inspector_class (Optional[Type[Inspector]]): The inspector\n            class to use for the application. Defaults to `None`.\n        certloader_class (Optional[Type[CertLoader]]): The certloader\n            class to use for the application. Defaults to `None`.\n    \"\"\"\n\n    __touchup__ = (\n        \"handle_request\",\n        \"handle_exception\",\n        \"_run_response_middleware\",\n        \"_run_request_middleware\",\n    )\n    __slots__ = (\n        \"_asgi_app\",\n        \"_asgi_lifespan\",\n        \"_asgi_client\",\n        \"_blueprint_order\",\n        \"_delayed_tasks\",\n        \"_ext\",\n        \"_future_commands\",\n        \"_future_exceptions\",\n        \"_future_listeners\",\n        \"_future_middleware\",\n        \"_future_registry\",\n        \"_future_routes\",\n        \"_future_signals\",\n        \"_future_statics\",\n        \"_inspector\",\n        \"_manager\",\n        \"_state\",\n        \"_task_registry\",\n        \"_test_client\",\n        \"_test_manager\",\n        \"blueprints\",\n        \"certloader_class\",\n        \"config\",\n        \"configure_logging\",\n        \"ctx\",\n        \"error_handler\",\n        \"inspector_class\",\n        \"go_fast\",\n        \"listeners\",\n        \"multiplexer\",\n        \"named_request_middleware\",\n        \"named_response_middleware\",\n        \"repl_ctx\",\n        \"request_class\",\n        \"request_middleware\",\n        \"response_middleware\",\n        \"router\",\n        \"shared_ctx\",\n        \"signal_router\",\n        \"sock\",\n        \"strict_slashes\",\n        \"websocket_enabled\",\n        \"websocket_tasks\",\n    )\n\n    _app_registry: ClassVar[dict[str, Sanic]] = {}\n    test_mode: ClassVar[bool] = False\n\n    @overload\n    def __init__(\n        self: Sanic[Config, SimpleNamespace],\n        name: str,\n        config: None = None,\n        ctx: None = None,\n        router: Router | None = None,\n        signal_router: SignalRouter | None = None,\n        error_handler: ErrorHandler | None = None,\n        env_prefix: str | None = SANIC_PREFIX,\n        request_class: type[Request] | None = None,\n        strict_slashes: bool = False,\n        log_config: dict[str, Any] | None = None,\n        configure_logging: bool = True,\n        dumps: Callable[..., AnyStr] | None = None,\n        loads: Callable[..., Any] | None = None,\n        inspector: bool = False,\n        inspector_class: type[Inspector] | None = None,\n        certloader_class: type[CertLoader] | None = None,\n    ) -> None: ...\n\n    @overload\n    def __init__(\n        self: Sanic[config_type, SimpleNamespace],\n        name: str,\n        config: config_type | None = None,\n        ctx: None = None,\n        router: Router | None = None,\n        signal_router: SignalRouter | None = None,\n        error_handler: ErrorHandler | None = None,\n        env_prefix: str | None = SANIC_PREFIX,\n        request_class: type[Request] | None = None,\n        strict_slashes: bool = False,\n        log_config: dict[str, Any] | None = None,\n        configure_logging: bool = True,\n        dumps: Callable[..., AnyStr] | None = None,\n        loads: Callable[..., Any] | None = None,\n        inspector: bool = False,\n        inspector_class: type[Inspector] | None = None,\n        certloader_class: type[CertLoader] | None = None,\n    ) -> None: ...\n\n    @overload\n    def __init__(\n        self: Sanic[Config, ctx_type],\n        name: str,\n        config: None = None,\n        ctx: ctx_type | None = None,\n        router: Router | None = None,\n        signal_router: SignalRouter | None = None,\n        error_handler: ErrorHandler | None = None,\n        env_prefix: str | None = SANIC_PREFIX,\n        request_class: type[Request] | None = None,\n        strict_slashes: bool = False,\n        log_config: dict[str, Any] | None = None,\n        configure_logging: bool = True,\n        dumps: Callable[..., AnyStr] | None = None,\n        loads: Callable[..., Any] | None = None,\n        inspector: bool = False,\n        inspector_class: type[Inspector] | None = None,\n        certloader_class: type[CertLoader] | None = None,\n    ) -> None: ...\n\n    @overload\n    def __init__(\n        self: Sanic[config_type, ctx_type],\n        name: str,\n        config: config_type | None = None,\n        ctx: ctx_type | None = None,\n        router: Router | None = None,\n        signal_router: SignalRouter | None = None,\n        error_handler: ErrorHandler | None = None,\n        env_prefix: str | None = SANIC_PREFIX,\n        request_class: type[Request] | None = None,\n        strict_slashes: bool = False,\n        log_config: dict[str, Any] | None = None,\n        configure_logging: bool = True,\n        dumps: Callable[..., AnyStr] | None = None,\n        loads: Callable[..., Any] | None = None,\n        inspector: bool = False,\n        inspector_class: type[Inspector] | None = None,\n        certloader_class: type[CertLoader] | None = None,\n    ) -> None: ...\n\n    def __init__(\n        self,\n        name: str,\n        config: config_type | None = None,\n        ctx: ctx_type | None = None,\n        router: Router | None = None,\n        signal_router: SignalRouter | None = None,\n        error_handler: ErrorHandler | None = None,\n        env_prefix: str | None = SANIC_PREFIX,\n        request_class: type[Request] | None = None,\n        strict_slashes: bool = False,\n        log_config: dict[str, Any] | None = None,\n        configure_logging: bool = True,\n        dumps: Callable[..., AnyStr] | None = None,\n        loads: Callable[..., Any] | None = None,\n        inspector: bool = False,\n        inspector_class: type[Inspector] | None = None,\n        certloader_class: type[CertLoader] | None = None,\n    ) -> None:\n        super().__init__(name=name)\n        # logging\n        if configure_logging:\n            dict_config = log_config or LOGGING_CONFIG_DEFAULTS\n            logging.config.dictConfig(dict_config)  # type: ignore\n\n        if config and env_prefix != SANIC_PREFIX:\n            raise SanicException(\n                \"When instantiating Sanic with config, you cannot also pass \"\n                \"env_prefix\"\n            )\n\n        # First setup config\n        self.config: config_type = cast(\n            config_type, config or Config(env_prefix=env_prefix)\n        )\n        if inspector:\n            self.config.INSPECTOR = inspector\n\n        # Then we can do the rest\n        self._asgi_app: ASGIApp | None = None\n        self._asgi_lifespan: Lifespan | None = None\n        self._asgi_client: Any = None\n        self._blueprint_order: list[Blueprint] = []\n        self._delayed_tasks: list[str] = []\n        self._future_registry: FutureRegistry = FutureRegistry()\n        self._inspector: Inspector | None = None\n        self._manager: WorkerManager | None = None\n        self._state: ApplicationState = ApplicationState(app=self)\n        self._task_registry: dict[str, Task | None] = {}\n        self._test_client: Any = None\n        self._test_manager: Any = None\n        self.asgi = False\n        self.auto_reload = False\n        self.blueprints: dict[str, Blueprint] = {}\n        self.certloader_class: type[CertLoader] = (\n            certloader_class or CertLoader\n        )\n        self.configure_logging: bool = configure_logging\n        self.ctx: ctx_type = cast(ctx_type, ctx or SimpleNamespace())\n        self.error_handler: ErrorHandler = error_handler or ErrorHandler()\n        self.inspector_class: type[Inspector] = inspector_class or Inspector\n        self.listeners: dict[str, list[ListenerType[Any]]] = defaultdict(list)\n        self.named_request_middleware: dict[str, deque[Middleware]] = {}\n        self.named_response_middleware: dict[str, deque[Middleware]] = {}\n        self.repl_ctx: REPLContext = REPLContext()\n        self.request_class = request_class or Request\n        self.request_middleware: deque[Middleware] = deque()\n        self.response_middleware: deque[Middleware] = deque()\n        self.router: Router = router or Router()\n        self.shared_ctx: SharedContext = SharedContext()\n        self.signal_router: SignalRouter = signal_router or SignalRouter()\n        self.sock: socket | None = None\n        self.strict_slashes: bool = strict_slashes\n        self.websocket_enabled: bool = False\n        self.websocket_tasks: set[Future[Any]] = set()\n\n        # Register alternative method names\n        self.go_fast = self.run\n        self.router.ctx.app = self\n        self.signal_router.ctx.app = self\n        self.__class__.register_app(self)\n\n        if dumps:\n            BaseHTTPResponse._dumps = dumps  # type: ignore\n        if loads:\n            Request._loads = loads  # type: ignore\n\n    @property\n    def loop(self) -> AbstractEventLoop:\n        \"\"\"Synonymous with asyncio.get_event_loop().\n\n        .. note::\n            Only supported when using the `app.run` method.\n\n        Returns:\n            AbstractEventLoop: The event loop for the application.\n\n        Raises:\n            SanicException: If the application is not running.\n        \"\"\"\n        if self.state.stage is ServerStage.STOPPED and self.asgi is False:\n            raise SanicException(\n                \"Loop can only be retrieved after the app has started \"\n                \"running. Not supported with `create_server` function\"\n            )\n        try:\n            return get_running_loop()\n        except RuntimeError:  # no cov\n            return asyncio.get_event_loop_policy().get_event_loop()\n\n    # -------------------------------------------------------------------- #\n    # Registration\n    # -------------------------------------------------------------------- #\n\n    def register_listener(\n        self,\n        listener: ListenerType[SanicVar],\n        event: str,\n        *,\n        priority: int = 0,\n    ) -> ListenerType[SanicVar]:\n        \"\"\"Register the listener for a given event.\n\n        Args:\n            listener (Callable): The listener to register.\n            event (str): The event to listen for.\n\n        Returns:\n            Callable: The listener that was registered.\n        \"\"\"\n\n        try:\n            _event = ListenerEvent[event.upper()]\n        except (ValueError, AttributeError):\n            valid = \", \".join(\n                map(lambda x: x.lower(), ListenerEvent.__members__.keys())\n            )\n            raise BadRequest(f\"Invalid event: {event}. Use one of: {valid}\")\n\n        if \".\" in _event:\n            self.signal(_event.value, priority=priority)(\n                partial(self._listener, listener=listener)\n            )\n        else:\n            if priority:\n                error_logger.warning(\n                    f\"Priority is not supported for {_event.value}\"\n                )\n            self.listeners[_event.value].append(listener)\n\n        return listener\n\n    def register_middleware(\n        self,\n        middleware: MiddlewareType | Middleware,\n        attach_to: str = \"request\",\n        *,\n        priority: Default | int = _default,\n    ) -> MiddlewareType | Middleware:\n        \"\"\"Register a middleware to be called before a request is handled.\n\n        Args:\n            middleware (Callable): A callable that takes in a request.\n            attach_to (str): Whether to attach to request or response.\n                Defaults to `'request'`.\n            priority (int): The priority level of the middleware.\n                Lower numbers are executed first. Defaults to `0`.\n\n        Returns:\n            Union[Callable, Callable[[Callable], Callable]]: The decorated\n                middleware function or a partial function depending on how\n                the method was called.\n        \"\"\"\n        retval = middleware\n        location = MiddlewareLocation[attach_to.upper()]\n\n        if not isinstance(middleware, Middleware):\n            middleware = Middleware(\n                middleware,\n                location=location,\n                priority=priority if isinstance(priority, int) else 0,\n            )\n        elif middleware.priority != priority and isinstance(priority, int):\n            middleware = Middleware(\n                middleware.func,\n                location=middleware.location,\n                priority=priority,\n            )\n\n        if location is MiddlewareLocation.REQUEST:\n            if middleware not in self.request_middleware:\n                self.request_middleware.append(middleware)\n        if location is MiddlewareLocation.RESPONSE:\n            if middleware not in self.response_middleware:\n                self.response_middleware.appendleft(middleware)\n        return retval\n\n    def register_named_middleware(\n        self,\n        middleware: MiddlewareType,\n        route_names: Iterable[str],\n        attach_to: str = \"request\",\n        *,\n        priority: Default | int = _default,\n    ):\n        \"\"\"Used to register named middleqare (middleware typically on blueprints)\n\n        Args:\n            middleware (Callable): A callable that takes in a request.\n            route_names (Iterable[str]): The route names to attach the\n                middleware to.\n            attach_to (str): Whether to attach to request or response.\n                Defaults to `'request'`.\n            priority (int): The priority level of the middleware.\n                Lower numbers are executed first. Defaults to `0`.\n\n        Returns:\n            Union[Callable, Callable[[Callable], Callable]]: The decorated\n                middleware function or a partial function depending on how\n                the method was called.\n        \"\"\"  # noqa: E501\n        retval = middleware\n        location = MiddlewareLocation[attach_to.upper()]\n\n        if not isinstance(middleware, Middleware):\n            middleware = Middleware(\n                middleware,\n                location=location,\n                priority=priority if isinstance(priority, int) else 0,\n            )\n        elif middleware.priority != priority and isinstance(priority, int):\n            middleware = Middleware(\n                middleware.func,\n                location=middleware.location,\n                priority=priority,\n            )\n\n        if location is MiddlewareLocation.REQUEST:\n            for _rn in route_names:\n                if _rn not in self.named_request_middleware:\n                    self.named_request_middleware[_rn] = deque()\n                if middleware not in self.named_request_middleware[_rn]:\n                    self.named_request_middleware[_rn].append(middleware)\n        if location is MiddlewareLocation.RESPONSE:\n            for _rn in route_names:\n                if _rn not in self.named_response_middleware:\n                    self.named_response_middleware[_rn] = deque()\n                if middleware not in self.named_response_middleware[_rn]:\n                    self.named_response_middleware[_rn].appendleft(middleware)\n        return retval\n\n    def _apply_exception_handler(\n        self,\n        handler: FutureException,\n        route_names: list[str] | None = None,\n    ):\n        \"\"\"Decorate a function to be registered as a handler for exceptions\n\n        :param exceptions: exceptions\n        :return: decorated function\n        \"\"\"\n\n        for exception in handler.exceptions:\n            if isinstance(exception, (tuple, list)):\n                for e in exception:\n                    self.error_handler.add(e, handler.handler, route_names)\n            else:\n                self.error_handler.add(exception, handler.handler, route_names)\n        return handler.handler\n\n    def _apply_listener(self, listener: FutureListener):\n        return self.register_listener(\n            listener.listener, listener.event, priority=listener.priority\n        )\n\n    def _apply_route(\n        self, route: FutureRoute, overwrite: bool = False\n    ) -> list[Route]:\n        params = route._asdict()\n        params[\"overwrite\"] = overwrite\n        websocket = params.pop(\"websocket\", False)\n        subprotocols = params.pop(\"subprotocols\", None)\n\n        if websocket:\n            self.enable_websocket()\n            websocket_handler = partial(\n                self._websocket_handler,\n                route.handler,\n                subprotocols=subprotocols,\n            )\n            websocket_handler.__name__ = route.handler.__name__  # type: ignore\n            websocket_handler.is_websocket = True  # type: ignore\n            params[\"handler\"] = websocket_handler\n\n        ctx = params.pop(\"route_context\")\n\n        with self.amend():\n            routes = self.router.add(**params)\n            if isinstance(routes, Route):\n                routes = [routes]\n\n            for r in routes:\n                r.extra.websocket = websocket\n                r.extra.static = params.get(\"static\", False)\n                r.ctx.__dict__.update(ctx)\n\n        return routes\n\n    def _apply_middleware(\n        self,\n        middleware: FutureMiddleware,\n        route_names: list[str] | None = None,\n    ):\n        with self.amend():\n            if route_names:\n                return self.register_named_middleware(\n                    middleware.middleware, route_names, middleware.attach_to\n                )\n            else:\n                return self.register_middleware(\n                    middleware.middleware, middleware.attach_to\n                )\n\n    def _apply_signal(self, signal: FutureSignal) -> Signal:\n        with self.amend():\n            return self.signal_router.add(\n                handler=signal.handler,\n                event=signal.event,\n                condition=signal.condition,\n                exclusive=signal.exclusive,\n                priority=signal.priority,\n            )\n\n    @overload\n    def dispatch(\n        self,\n        event: str,\n        *,\n        condition: dict[str, str] | None = None,\n        context: dict[str, Any] | None = None,\n        fail_not_found: bool = True,\n        inline: Literal[True],\n        reverse: bool = False,\n    ) -> Coroutine[Any, Any, Awaitable[Any]]: ...\n\n    @overload\n    def dispatch(\n        self,\n        event: str,\n        *,\n        condition: dict[str, str] | None = None,\n        context: dict[str, Any] | None = None,\n        fail_not_found: bool = True,\n        inline: Literal[False] = False,\n        reverse: bool = False,\n    ) -> Coroutine[Any, Any, Awaitable[Task]]: ...\n\n    def dispatch(\n        self,\n        event: str,\n        *,\n        condition: dict[str, str] | None = None,\n        context: dict[str, Any] | None = None,\n        fail_not_found: bool = True,\n        inline: bool = False,\n        reverse: bool = False,\n    ) -> Coroutine[Any, Any, Awaitable[Task | Any]]:\n        \"\"\"Dispatches an event to the signal router.\n\n        Args:\n            event (str): Name of the event to dispatch.\n            condition (Optional[Dict[str, str]]): Condition for the\n                event dispatch.\n            context (Optional[Dict[str, Any]]): Context for the event dispatch.\n            fail_not_found (bool): Whether to fail if the event is not found.\n                Default is `True`.\n            inline (bool): If `True`, returns the result directly. If `False`,\n                returns a `Task`. Default is `False`.\n            reverse (bool): Whether to reverse the dispatch order.\n                Default is `False`.\n\n        Returns:\n            Coroutine[Any, Any, Awaitable[Union[Task, Any]]]: An awaitable\n                that returns the result directly if `inline=True`, or a `Task`\n                if `inline=False`.\n\n        Examples:\n            ```python\n            @app.signal(\"user.registration.created\")\n            async def send_registration_email(**context):\n                await send_email(context[\"email\"], template=\"registration\")\n\n            @app.post(\"/register\")\n            async def handle_registration(request):\n                await do_registration(request)\n                await request.app.dispatch(\n                    \"user.registration.created\",\n                    context={\"email\": request.json.email}\n                })\n            ```\n        \"\"\"\n        return self.signal_router.dispatch(\n            event,\n            context=context,\n            condition=condition,\n            inline=inline,\n            reverse=reverse,\n            fail_not_found=fail_not_found,\n        )\n\n    async def event(\n        self,\n        event: str | Enum,\n        timeout: int | float | None = None,\n        *,\n        condition: dict[str, Any] | None = None,\n        exclusive: bool = True,\n    ) -> None:\n        \"\"\"Wait for a specific event to be triggered.\n\n        This method waits for a named event to be triggered and can be used\n        in conjunction with the signal system to wait for specific signals.\n        If the event is not found and auto-registration of events is enabled,\n        the event will be registered and then waited on. If the event is not\n        found and auto-registration is not enabled, a `NotFound` exception\n        is raised.\n\n        Auto-registration can be handled by setting the `EVENT_AUTOREGISTER`\n        config value to `True`.\n\n        ```python\n        app.config.EVENT_AUTOREGISTER = True\n        ```\n\n        Args:\n            event (str): The name of the event to wait for.\n            timeout (Optional[Union[int, float]]): An optional timeout value\n                in seconds. If provided, the wait will be terminated if the\n                timeout is reached. Defaults to `None`, meaning no timeout.\n            condition: If provided, method will only return when the signal\n                is dispatched with the given condition.\n            exclusive: When true (default), the signal can only be dispatched\n                when the condition has been met. When ``False``, the signal can\n                be dispatched either with or without it.\n\n        Raises:\n            NotFound: If the event is not found and auto-registration of\n                events is not enabled.\n\n        Returns:\n            The context dict of the dispatched signal.\n\n        Examples:\n            ```python\n            async def wait_for_event(app):\n                while True:\n                    print(\"> waiting\")\n                    await app.event(\"foo.bar.baz\")\n                    print(\"> event found\")\n\n            @app.after_server_start\n            async def after_server_start(app, loop):\n                app.add_task(wait_for_event(app))\n            ```\n        \"\"\"\n\n        waiter = self.signal_router.get_waiter(event, condition, exclusive)\n\n        if not waiter and self.config.EVENT_AUTOREGISTER:\n            self.signal_router.reset()\n            self.add_signal(None, event)\n            waiter = self.signal_router.get_waiter(event, condition, exclusive)\n            self.signal_router.finalize()\n\n        if not waiter:\n            raise NotFound(f\"Could not find signal {event}\")\n\n        return await wait_for(waiter.wait(), timeout=timeout)\n\n    def report_exception(\n        self, handler: Callable[[Sanic, Exception], Coroutine[Any, Any, None]]\n    ) -> Callable[[Exception], Coroutine[Any, Any, None]]:\n        \"\"\"Register a handler to report exceptions.\n\n        A convenience method to register a handler for the signal that\n        is emitted when an exception occurs. It is typically used to\n        report exceptions to an external service.\n\n        It is equivalent to:\n\n        ```python\n        @app.signal(Event.SERVER_EXCEPTION_REPORT)\n        async def report(exception):\n            await do_something_with_error(exception)\n        ```\n\n        Args:\n            handler (Callable[[Sanic, Exception], Coroutine[Any, Any, None]]):\n                The handler to register.\n\n        Returns:\n            Callable[[Sanic, Exception], Coroutine[Any, Any, None]]: The\n                handler that was registered.\n        \"\"\"\n\n        @wraps(handler)\n        async def report(exception: Exception) -> None:\n            await handler(self, exception)\n\n        self.add_signal(\n            handler=report, event=Event.SERVER_EXCEPTION_REPORT.value\n        )\n\n        return report\n\n    def enable_websocket(self, enable: bool = True) -> None:\n        \"\"\"Enable or disable the support for websocket.\n\n        Websocket is enabled automatically if websocket routes are\n        added to the application. This typically will not need to be\n        called manually.\n\n        Args:\n            enable (bool, optional): If set to `True`, enables websocket\n                support. If set to `False`, disables websocket support.\n                Defaults to `True`.\n\n        Returns:\n            None\n        \"\"\"\n\n        if not self.websocket_enabled:\n            # if the server is stopped, we want to cancel any ongoing\n            # websocket tasks, to allow the server to exit promptly\n            self.listener(\"before_server_stop\")(self._cancel_websocket_tasks)\n\n        self.websocket_enabled = enable\n\n    def blueprint(\n        self,\n        blueprint: Blueprint | Iterable[Blueprint] | BlueprintGroup,\n        *,\n        url_prefix: str | None = None,\n        version: int | float | str | None = None,\n        strict_slashes: bool | None = None,\n        version_prefix: str | None = None,\n        name_prefix: str | None = None,\n    ) -> None:\n        \"\"\"Register a blueprint on the application.\n\n        See [Blueprints](/en/guide/best-practices/blueprints) for more information.\n\n        Args:\n            blueprint (Union[Blueprint, Iterable[Blueprint], BlueprintGroup]): Blueprint object or (list, tuple) thereof.\n            url_prefix (Optional[str]): Prefix for all URLs bound to the blueprint. Defaults to `None`.\n            version (Optional[Union[int, float, str]]): Version prefix for URLs. Defaults to `None`.\n            strict_slashes (Optional[bool]): Enforce the trailing slashes. Defaults to `None`.\n            version_prefix (Optional[str]): Prefix for version. Defaults to `None`.\n            name_prefix (Optional[str]): Prefix for the blueprint name. Defaults to `None`.\n\n        Example:\n            ```python\n            app = Sanic(\"TestApp\")\n            bp = Blueprint('TestBP')\n\n            @bp.route('/route')\n            def handler(request):\n                return text('Hello, Blueprint!')\n\n            app.blueprint(bp, url_prefix='/blueprint')\n            ```\n        \"\"\"  # noqa: E501\n        options: dict[str, Any] = {}\n        if url_prefix is not None:\n            options[\"url_prefix\"] = url_prefix\n        if version is not None:\n            options[\"version\"] = version\n        if strict_slashes is not None:\n            options[\"strict_slashes\"] = strict_slashes\n        if version_prefix is not None:\n            options[\"version_prefix\"] = version_prefix\n        if name_prefix is not None:\n            options[\"name_prefix\"] = name_prefix\n        if isinstance(blueprint, (Iterable, BlueprintGroup)):\n            for item in blueprint:\n                params: dict[str, Any] = {**options}\n                if isinstance(blueprint, BlueprintGroup):\n                    merge_from = [\n                        options.get(\"url_prefix\", \"\"),\n                        blueprint.url_prefix or \"\",\n                    ]\n                    if not isinstance(item, BlueprintGroup):\n                        merge_from.append(item.url_prefix or \"\")\n                    merged_prefix = \"/\".join(\n                        str(u).strip(\"/\") for u in merge_from if u\n                    ).rstrip(\"/\")\n                    params[\"url_prefix\"] = f\"/{merged_prefix}\"\n\n                    for _attr in [\"version\", \"strict_slashes\"]:\n                        if getattr(item, _attr) is None:\n                            params[_attr] = getattr(\n                                blueprint, _attr\n                            ) or options.get(_attr)\n                    if item.version_prefix == \"/v\":\n                        if blueprint.version_prefix == \"/v\":\n                            params[\"version_prefix\"] = options.get(\n                                \"version_prefix\"\n                            )\n                        else:\n                            params[\"version_prefix\"] = blueprint.version_prefix\n                    name_prefix = getattr(blueprint, \"name_prefix\", None)\n                    if name_prefix and \"name_prefix\" not in params:\n                        params[\"name_prefix\"] = name_prefix\n                self.blueprint(item, **params)\n            return\n        if blueprint.name in self.blueprints:\n            assert self.blueprints[blueprint.name] is blueprint, (\n                'A blueprint with the name \"%s\" is already registered.  '\n                \"Blueprint names must be unique.\" % (blueprint.name,)\n            )\n        else:\n            self.blueprints[blueprint.name] = blueprint\n            self._blueprint_order.append(blueprint)\n\n        if (\n            self.strict_slashes is not None\n            and blueprint.strict_slashes is None\n        ):\n            blueprint.strict_slashes = self.strict_slashes\n        blueprint.register(self, options)\n\n    def url_for(self, view_name: str, **kwargs):\n        \"\"\"Build a URL based on a view name and the values provided.\n\n        This method constructs URLs for a given view name, taking into account\n        various special keyword arguments that can be used to modify the resulting\n        URL. It can handle internal routing as well as external URLs with different\n        schemes.\n\n        There are several special keyword arguments that can be used to modify\n        the URL that is built. They each begin with an underscore. They are:\n\n        - `_anchor`\n        - `_external`\n        - `_host`\n        - `_server`\n        - `_scheme`\n\n        Args:\n            view_name (str): String referencing the view name.\n            _anchor (str): Adds an \"#anchor\" to the end.\n            _scheme (str): Should be either \"http\" or \"https\", default is \"http\".\n            _external (bool): Whether to return the path or a full URL with scheme and host.\n            _host (str): Used when one or more hosts are defined for a route to tell Sanic which to use.\n            _server (str): If not using \"_host\", this will be used for defining the hostname of the URL.\n            **kwargs: Keys and values that are used to build request parameters and\n                    query string arguments.\n\n        Raises:\n            URLBuildError: If there are issues with constructing the URL.\n\n        Returns:\n            str: The built URL.\n\n        Examples:\n            Building a URL for a specific view with parameters:\n            ```python\n            url_for('view_name', param1='value1', param2='value2')\n            # /view-name?param1=value1&param2=value2\n            ```\n\n            Creating an external URL with a specific scheme and anchor:\n            ```python\n            url_for('view_name', _scheme='https', _external=True, _anchor='section1')\n            # https://example.com/view-name#section1\n            ```\n\n            Creating a URL with a specific host:\n            ```python\n            url_for('view_name', _host='subdomain.example.com')\n            # http://subdomain.example.com/view-name\n        \"\"\"  # noqa: E501\n        # find the route by the supplied view name\n        kw: dict[str, str] = {}\n        # special static files url_for\n\n        if \".\" not in view_name:\n            view_name = f\"{self.name}.{view_name}\"\n\n        if view_name.endswith(\".static\"):\n            name = kwargs.pop(\"name\", None)\n            if name:\n                view_name = view_name.replace(\"static\", name)\n            kw.update(name=view_name)\n\n        route = self.router.find_route_by_view_name(view_name, **kw)\n        if not route:\n            raise URLBuildError(\n                f\"Endpoint with name `{view_name}` was not found\"\n            )\n\n        uri = route.path\n\n        if getattr(route.extra, \"static\", None):\n            filename = kwargs.pop(\"filename\", \"\")\n            # it's static folder\n            if \"__file_uri__\" in uri:\n                folder_ = uri.split(\"<__file_uri__:\", 1)[0]\n                if folder_.endswith(\"/\"):\n                    folder_ = folder_[:-1]\n\n                if filename.startswith(\"/\"):\n                    filename = filename[1:]\n\n                kwargs[\"__file_uri__\"] = filename\n\n        if (\n            uri != \"/\"\n            and uri.endswith(\"/\")\n            and not route.strict\n            and not route.raw_path[:-1]\n        ):\n            uri = uri[:-1]\n\n        if not uri.startswith(\"/\"):\n            uri = f\"/{uri}\"\n\n        out = uri\n\n        # _method is only a placeholder now, don't know how to support it\n        kwargs.pop(\"_method\", None)\n        anchor = kwargs.pop(\"_anchor\", \"\")\n        # _external need SERVER_NAME in config or pass _server arg\n        host = kwargs.pop(\"_host\", None)\n        external = kwargs.pop(\"_external\", False) or bool(host)\n        scheme = kwargs.pop(\"_scheme\", \"\")\n        if route.extra.hosts and external:\n            if not host and len(route.extra.hosts) > 1:\n                raise ValueError(\n                    f\"Host is ambiguous: {', '.join(route.extra.hosts)}\"\n                )\n            elif host and host not in route.extra.hosts:\n                raise ValueError(\n                    f\"Requested host ({host}) is not available for this \"\n                    f\"route: {route.extra.hosts}\"\n                )\n            elif not host:\n                host = list(route.extra.hosts)[0]\n\n        if scheme and not external:\n            raise ValueError(\"When specifying _scheme, _external must be True\")\n\n        netloc = kwargs.pop(\"_server\", None)\n        if netloc is None and external:\n            netloc = host or self.config.get(\"SERVER_NAME\", \"\")\n\n        if external:\n            if not scheme:\n                if \":\" in netloc[:8]:\n                    scheme = netloc[:8].split(\":\", 1)[0]\n                else:\n                    scheme = \"http\"\n                # Replace http/https with ws/wss for WebSocket handlers\n                if route.extra.websocket:\n                    scheme = scheme.replace(\"http\", \"ws\")\n\n            if \"://\" in netloc[:8]:\n                netloc = netloc.split(\"://\", 1)[-1]\n\n        # find all the parameters we will need to build in the URL\n        # matched_params = re.findall(self.router.parameter_pattern, uri)\n        route.finalize()\n        for param_info in route.params.values():\n            # name, _type, pattern = self.router.parse_parameter_string(match)\n            # we only want to match against each individual parameter\n\n            try:\n                supplied_param = str(kwargs.pop(param_info.name))\n            except KeyError:\n                raise URLBuildError(\n                    f\"Required parameter `{param_info.name}` was not \"\n                    \"passed to url_for\"\n                )\n\n            # determine if the parameter supplied by the caller\n            # passes the test in the URL\n            if param_info.pattern:\n                pattern = (\n                    param_info.pattern[1]\n                    if isinstance(param_info.pattern, tuple)\n                    else param_info.pattern\n                )\n                passes_pattern = pattern.match(supplied_param)\n                if not passes_pattern:\n                    if param_info.cast is not str:\n                        msg = (\n                            f'Value \"{supplied_param}\" '\n                            f\"for parameter `{param_info.name}` does \"\n                            \"not match pattern for type \"\n                            f\"`{param_info.cast.__name__}`: \"\n                            f\"{pattern.pattern}\"\n                        )\n                    else:\n                        msg = (\n                            f'Value \"{supplied_param}\" for parameter '\n                            f\"`{param_info.name}` does not satisfy \"\n                            f\"pattern {pattern.pattern}\"\n                        )\n                    raise URLBuildError(msg)\n\n            # replace the parameter in the URL with the supplied value\n            replacement_regex = f\"(<{param_info.name}.*?>)\"\n            out = re.sub(replacement_regex, supplied_param, out)\n\n        # parse the remainder of the keyword arguments into a querystring\n        query_string = urlencode(kwargs, doseq=True) if kwargs else \"\"\n        # scheme://netloc/path;parameters?query#fragment\n        out = urlunparse((scheme, netloc, out, \"\", query_string, anchor))\n\n        return out\n\n    # -------------------------------------------------------------------- #\n    # Request Handling\n    # -------------------------------------------------------------------- #\n\n    async def handle_exception(\n        self,\n        request: Request,\n        exception: BaseException,\n        run_middleware: bool = True,\n    ) -> None:  # no cov\n        \"\"\"A handler that catches specific exceptions and outputs a response.\n\n        .. note::\n            This method is typically used internally, and you should not need\n            to call it directly.\n\n        Args:\n            request (Request): The current request object.\n            exception (BaseException): The exception that was raised.\n            run_middleware (bool): Whether to run middleware. Defaults\n                to `True`.\n\n        Raises:\n            ServerError: response 500.\n        \"\"\"\n        response = None\n        if not getattr(exception, \"__dispatched__\", False):\n            ...  # DO NOT REMOVE THIS LINE. IT IS NEEDED FOR TOUCHUP.\n            await self.dispatch(\n                \"server.exception.report\",\n                context={\"exception\": exception},\n            )\n        await self.dispatch(\n            \"http.lifecycle.exception\",\n            inline=True,\n            context={\"request\": request, \"exception\": exception},\n        )\n\n        if (\n            request.stream is not None\n            and request.stream.stage is not Stage.HANDLER\n        ):\n            error_logger.exception(exception, exc_info=True)\n            logger.error(\n                \"The error response will not be sent to the client for \"\n                f'the following exception:\"{exception}\". A previous response '\n                \"has at least partially been sent.\"\n            )\n\n            handler = self.error_handler._lookup(\n                exception, request.name if request else None\n            )\n            if handler:\n                logger.warning(\n                    \"An error occurred while handling the request after at \"\n                    \"least some part of the response was sent to the client. \"\n                    \"The response from your custom exception handler \"\n                    f\"{handler.__name__} will not be sent to the client.\"\n                    \"Exception handlers should only be used to generate the \"\n                    \"exception responses. If you would like to perform any \"\n                    \"other action on a raised exception, consider using a \"\n                    \"signal handler like \"\n                    '`@app.signal(\"http.lifecycle.exception\")`\\n'\n                    \"For further information, please see the docs: \"\n                    \"https://sanicframework.org/en/guide/advanced/\"\n                    \"signals.html\",\n                )\n            return\n\n        # -------------------------------------------- #\n        # Request Middleware\n        # -------------------------------------------- #\n        if run_middleware:\n            try:\n                middleware = (\n                    request.route and request.route.extra.request_middleware\n                ) or self.request_middleware\n                response = await self._run_request_middleware(\n                    request, middleware\n                )\n            except Exception as e:\n                return await self.handle_exception(request, e, False)\n        # No middleware results\n        if not response:\n            try:\n                response = self.error_handler.response(request, exception)\n                if isawaitable(response):\n                    response = await response\n            except Exception as e:\n                if isinstance(e, SanicException):\n                    response = self.error_handler.default(request, e)\n                elif self.debug:\n                    response = HTTPResponse(\n                        (\n                            f\"Error while handling error: {e}\\n\"\n                            f\"Stack: {format_exc()}\"\n                        ),\n                        status=500,\n                    )\n                else:\n                    response = HTTPResponse(\n                        \"An error occurred while handling an error\", status=500\n                    )\n        if response is not None:\n            try:\n                request.reset_response()\n                response = await request.respond(response)\n            except BaseException:\n                # Skip response middleware\n                if request.stream:\n                    request.stream.respond(response)\n                await response.send(end_stream=True)\n                raise\n        else:\n            if request.stream:\n                response = request.stream.response\n\n        # Marked for cleanup and DRY with handle_request/handle_exception\n        # when ResponseStream is no longer supporder\n        if isinstance(response, BaseHTTPResponse):\n            await self.dispatch(\n                \"http.lifecycle.response\",\n                inline=True,\n                context={\n                    \"request\": request,\n                    \"response\": response,\n                },\n            )\n            await response.send(end_stream=True)\n        elif isinstance(response, ResponseStream):\n            resp = await response(request)\n            await self.dispatch(\n                \"http.lifecycle.response\",\n                inline=True,\n                context={\n                    \"request\": request,\n                    \"response\": resp,\n                },\n            )\n            await response.eof()\n        else:\n            raise ServerError(\n                f\"Invalid response type {response!r} (need HTTPResponse)\"\n            )\n\n    async def handle_request(self, request: Request) -> None:  # no cov\n        \"\"\"Handles a request by dispatching it to the appropriate handler.\n\n        .. note::\n            This method is typically used internally, and you should not need\n            to call it directly.\n\n        Args:\n            request (Request): The current request object.\n\n        Raises:\n            ServerError: response 500.\n        \"\"\"\n        __tracebackhide__ = True\n\n        await self.dispatch(\n            \"http.lifecycle.handle\",\n            inline=True,\n            context={\"request\": request},\n        )\n\n        # Define `response` var here to remove warnings about\n        # allocation before assignment below.\n        response: (\n            BaseHTTPResponse\n            | Coroutine[Any, Any, BaseHTTPResponse | None]\n            | ResponseStream\n            | None\n        ) = None\n        run_middleware = True\n        try:\n            await self.dispatch(\n                \"http.routing.before\",\n                inline=True,\n                context={\"request\": request},\n            )\n            # Fetch handler from router\n            route, handler, kwargs = self.router.get(\n                request.path,\n                request.method,\n                request.headers.getone(\"host\", None),\n            )\n\n            request._match_info = {**kwargs}\n            request.route = route\n\n            await self.dispatch(\n                \"http.routing.after\",\n                inline=True,\n                context={\n                    \"request\": request,\n                    \"route\": route,\n                    \"kwargs\": kwargs,\n                    \"handler\": handler,\n                },\n            )\n\n            if (\n                request.stream\n                and request.stream.request_body\n                and not route.extra.ignore_body\n            ):\n                if hasattr(handler, \"is_stream\"):\n                    # Streaming handler: lift the size limit\n                    request.stream.request_max_size = float(\"inf\")\n                else:\n                    # Non-streaming handler: preload body\n                    await request.receive_body()\n\n            # -------------------------------------------- #\n            # Request Middleware\n            # -------------------------------------------- #\n            run_middleware = False\n            if request.route.extra.request_middleware:\n                response = await self._run_request_middleware(\n                    request, request.route.extra.request_middleware\n                )\n\n            # No middleware results\n            if not response:\n                # -------------------------------------------- #\n                # Execute Handler\n                # -------------------------------------------- #\n\n                if handler is None:\n                    raise ServerError(\n                        \"'None' was returned while requesting a \"\n                        \"handler from the router\"\n                    )\n\n                # Run response handler\n                await self.dispatch(\n                    \"http.handler.before\",\n                    inline=True,\n                    context={\"request\": request},\n                )\n                response = handler(request, **request.match_info)\n                if isawaitable(response):\n                    response = await response\n                await self.dispatch(\n                    \"http.handler.after\",\n                    inline=True,\n                    context={\"request\": request},\n                )\n\n            if request.responded:\n                if response is not None:\n                    error_logger.error(\n                        \"The response object returned by the route handler \"\n                        \"will not be sent to client. The request has already \"\n                        \"been responded to.\"\n                    )\n                if request.stream is not None:\n                    response = request.stream.response\n            elif response is not None:\n                response = await request.respond(response)  # type: ignore\n            elif not hasattr(handler, \"is_websocket\"):\n                response = request.stream.response  # type: ignore\n\n            # Marked for cleanup and DRY with handle_request/handle_exception\n            # when ResponseStream is no longer supporder\n            if isinstance(response, BaseHTTPResponse):\n                await self.dispatch(\n                    \"http.lifecycle.response\",\n                    inline=True,\n                    context={\n                        \"request\": request,\n                        \"response\": response,\n                    },\n                )\n                ...\n                await response.send(end_stream=True)\n            elif isinstance(response, ResponseStream):\n                resp = await response(request)\n                await self.dispatch(\n                    \"http.lifecycle.response\",\n                    inline=True,\n                    context={\n                        \"request\": request,\n                        \"response\": resp,\n                    },\n                )\n                await response.eof()\n            else:\n                if not hasattr(handler, \"is_websocket\"):\n                    raise ServerError(\n                        f\"Invalid response type {response!r} \"\n                        \"(need HTTPResponse)\"\n                    )\n\n        except CancelledError:  # type: ignore\n            raise\n        except Exception as e:\n            # Response Generation Failed\n            await self.handle_exception(\n                request, e, run_middleware=run_middleware\n            )\n\n    async def _websocket_handler(\n        self, handler, request, *args, subprotocols=None, **kwargs\n    ):\n        if self.asgi:\n            ws = request.transport.get_websocket_connection()\n            await ws.accept(subprotocols)\n        else:\n            protocol = request.transport.get_protocol()\n            ws = await protocol.websocket_handshake(request, subprotocols)\n\n        await self.dispatch(\n            \"websocket.handler.before\",\n            inline=True,\n            context={\"request\": request, \"websocket\": ws},\n            fail_not_found=False,\n        )\n        # schedule the application handler\n        # its future is kept in self.websocket_tasks in case it\n        # needs to be cancelled due to the server being stopped\n        fut = ensure_future(handler(request, ws, *args, **kwargs))\n        self.websocket_tasks.add(fut)\n        cancelled = False\n        try:\n            await fut\n            await self.dispatch(\n                \"websocket.handler.after\",\n                inline=True,\n                context={\"request\": request, \"websocket\": ws},\n                reverse=True,\n                fail_not_found=False,\n            )\n        except (CancelledError, ConnectionClosed):  # type: ignore\n            cancelled = True\n        except Exception as e:\n            self.error_handler.log(request, e)\n            await self.dispatch(\n                \"websocket.handler.exception\",\n                inline=True,\n                context={\"request\": request, \"websocket\": ws, \"exception\": e},\n                reverse=True,\n                fail_not_found=False,\n            )\n        finally:\n            self.websocket_tasks.remove(fut)\n            if cancelled:\n                ws.end_connection(1000)\n            else:\n                await ws.close()\n\n    # -------------------------------------------------------------------- #\n    # Testing\n    # -------------------------------------------------------------------- #\n\n    @property\n    def test_client(self) -> SanicTestClient:  # type: ignore # noqa\n        \"\"\"A testing client that uses httpx and a live running server to reach into the application to execute handlers.\n\n        This property is available if the `sanic-testing` package is installed.\n\n        See [Test Clients](/en/plugins/sanic-testing/clients#wsgi-client-sanictestclient) for details.\n\n        Returns:\n            SanicTestClient: A testing client from the `sanic-testing` package.\n        \"\"\"  # noqa: E501\n        if self._test_client:\n            return self._test_client\n        elif self._test_manager:\n            return self._test_manager.test_client\n        from sanic_testing.testing import SanicTestClient  # type: ignore\n\n        self._test_client = SanicTestClient(self)\n        return self._test_client\n\n    @property\n    def asgi_client(self) -> SanicASGITestClient:  # type: ignore # noqa\n        \"\"\"A testing client that uses ASGI to reach into the application to execute handlers.\n\n        This property is available if the `sanic-testing` package is installed.\n\n        See [Test Clients](/en/plugins/sanic-testing/clients#asgi-async-client-sanicasgitestclient) for details.\n\n        Returns:\n            SanicASGITestClient: A testing client from the `sanic-testing` package.\n        \"\"\"  # noqa: E501\n        if self._asgi_client:\n            return self._asgi_client\n        elif self._test_manager:\n            return self._test_manager.asgi_client\n        from sanic_testing.testing import SanicASGITestClient  # type: ignore\n\n        self._asgi_client = SanicASGITestClient(self)\n        return self._asgi_client\n\n    # -------------------------------------------------------------------- #\n    # Execution\n    # -------------------------------------------------------------------- #\n\n    async def _run_request_middleware(\n        self, request, middleware_collection\n    ):  # no cov\n        request._request_middleware_started = True\n\n        for middleware in middleware_collection:\n            await self.dispatch(\n                \"http.middleware.before\",\n                inline=True,\n                context={\n                    \"request\": request,\n                    \"response\": None,\n                },\n                condition={\"attach_to\": \"request\"},\n            )\n\n            response = middleware(request)\n            if isawaitable(response):\n                response = await response\n\n            await self.dispatch(\n                \"http.middleware.after\",\n                inline=True,\n                context={\n                    \"request\": request,\n                    \"response\": None,\n                },\n                condition={\"attach_to\": \"request\"},\n            )\n\n            if response:\n                return response\n        return None\n\n    async def _run_response_middleware(\n        self, request, response, middleware_collection\n    ):  # no cov\n        for middleware in middleware_collection:\n            await self.dispatch(\n                \"http.middleware.before\",\n                inline=True,\n                context={\n                    \"request\": request,\n                    \"response\": response,\n                },\n                condition={\"attach_to\": \"response\"},\n            )\n\n            _response = middleware(request, response)\n            if isawaitable(_response):\n                _response = await _response\n\n            await self.dispatch(\n                \"http.middleware.after\",\n                inline=True,\n                context={\n                    \"request\": request,\n                    \"response\": _response if _response else response,\n                },\n                condition={\"attach_to\": \"response\"},\n            )\n\n            if _response:\n                response = _response\n                if isinstance(response, BaseHTTPResponse):\n                    response = request.stream.respond(response)\n                break\n        return response\n\n    def _build_endpoint_name(self, *parts):\n        parts = [self.name, *parts]\n        return \".\".join(parts)\n\n    @classmethod\n    def _cancel_websocket_tasks(cls, app):\n        for task in app.websocket_tasks:\n            task.cancel()\n\n    @staticmethod\n    async def _listener(\n        app: Sanic, loop: AbstractEventLoop, listener: ListenerType\n    ):\n        try:\n            maybe_coro = listener(app)  # type: ignore\n        except TypeError:\n            name = getattr(\n                listener,\n                \"__qualname__\",\n                getattr(getattr(listener, \"func\", None), \"__qualname__\", None),\n            )\n            deprecation(\n                f\"Passing the loop argument to listeners is deprecated. \"\n                f\"Your listener{f' {name!r}' if name else ''} should only \"\n                \"accept the app argument.\",\n                26.6,\n            )\n            maybe_coro = listener(app, loop)  # type: ignore\n        if maybe_coro and isawaitable(maybe_coro):\n            await maybe_coro\n\n    # -------------------------------------------------------------------- #\n    # Task management\n    # -------------------------------------------------------------------- #\n\n    @classmethod\n    def _prep_task(\n        cls,\n        task,\n        app,\n        loop,\n    ):\n        async def do(task):\n            try:\n                if callable(task):\n                    try:\n                        task = task(app)\n                    except TypeError:\n                        task = task()\n                if isawaitable(task):\n                    return await task\n            except CancelledError:\n                error_logger.warning(\n                    f\"Task {task} was cancelled before it completed.\"\n                )\n                raise\n            except Exception as e:\n                await app.dispatch(\n                    \"server.exception.report\",\n                    context={\"exception\": e},\n                )\n                raise\n\n        return do(task)\n\n    @classmethod\n    def _loop_add_task(\n        cls,\n        task,\n        app,\n        loop,\n        *,\n        name: str | None = None,\n        register: bool = True,\n    ) -> Task:\n        tsk: Task = task\n        if not isinstance(task, Future):\n            prepped = cls._prep_task(task, app, loop)\n            tsk = loop.create_task(prepped, name=name)\n\n        if name and register:\n            app._task_registry[name] = tsk\n\n        return tsk\n\n    @staticmethod\n    async def dispatch_delayed_tasks(app: Sanic) -> None:\n        \"\"\"Signal handler for dispatching delayed tasks.\n\n        This is used to dispatch tasks that were added before the loop was\n        started, and will be called after the loop has started. It is\n        not typically used directly.\n\n        Args:\n            app (Sanic): The Sanic application instance.\n\n        Returns:\n            None\n        \"\"\"\n        loop = asyncio.get_running_loop()\n        for name in app._delayed_tasks:\n            await app.dispatch(name, context={\"app\": app, \"loop\": loop})\n        app._delayed_tasks.clear()\n\n    @staticmethod\n    async def run_delayed_task(\n        app: Sanic,\n        loop: AbstractEventLoop,\n        task: Future[Any] | Task[Any] | Awaitable[Any],\n    ) -> None:\n        \"\"\"Executes a delayed task within the context of a given app and loop.\n\n        This method prepares a given task by invoking the app's private\n        `_prep_task` method and then awaits the execution of the prepared task.\n\n        Args:\n            app (Any): The application instance on which the task will\n                be executed.\n            loop (AbstractEventLoop): The event loop where the task will\n                be scheduled.\n            task (Task[Any]): The task function that will be prepared\n                and executed.\n\n        Returns:\n            None\n        \"\"\"\n        prepped = app._prep_task(task, app, loop)\n        await prepped\n\n    def add_task(\n        self,\n        task: Future[Any] | Coroutine[Any, Any, Any] | Awaitable[Any],\n        *,\n        name: str | None = None,\n        register: bool = True,\n    ) -> Task[Any] | None:\n        \"\"\"Schedule a task to run later, after the loop has started.\n\n        While this is somewhat similar to `asyncio.create_task`, it can be\n        used before the loop has started (in which case it will run after the\n        loop has started in the `before_server_start` listener).\n\n        Naming tasks is a good practice as it allows you to cancel them later,\n        and allows Sanic to manage them when the server is stopped, if needed.\n\n        [See user guide re: background tasks](/en/guide/basics/tasks#background-tasks)\n\n        Args:\n            task (Union[Future[Any], Coroutine[Any, Any, Any], Awaitable[Any]]):\n                The future, coroutine, or awaitable to schedule.\n            name (Optional[str], optional): The name of the task, if needed for\n                later reference. Defaults to `None`.\n            register (bool, optional): Whether to register the task. Defaults\n                to `True`.\n\n        Returns:\n            Optional[Task[Any]]: The task that was scheduled, if applicable.\n        \"\"\"  # noqa: E501\n        try:\n            loop = self.loop  # Will raise SanicError if loop is not started\n            return self._loop_add_task(\n                task, self, loop, name=name, register=register\n            )\n        except SanicException:\n            task_name = f\"sanic.delayed_task.{hash(task)}\"\n            if not self._delayed_tasks:\n                self.after_server_start(partial(self.dispatch_delayed_tasks))\n\n            if name:\n                raise RuntimeError(\n                    \"Cannot name task outside of a running application\"\n                )\n\n            self.signal(task_name)(partial(self.run_delayed_task, task=task))\n            self._delayed_tasks.append(task_name)\n            return None\n\n    @overload\n    def get_task(\n        self, name: str, *, raise_exception: Literal[True]\n    ) -> Task: ...\n\n    @overload\n    def get_task(\n        self, name: str, *, raise_exception: Literal[False]\n    ) -> Task | None: ...\n\n    @overload\n    def get_task(self, name: str, *, raise_exception: bool) -> Task | None: ...\n\n    def get_task(\n        self, name: str, *, raise_exception: bool = True\n    ) -> Task | None:\n        \"\"\"Get a named task.\n\n        This method is used to get a task by its name. Optionally, you can\n        control whether an exception should be raised if the task is not found.\n\n        Args:\n            name (str): The name of the task to be retrieved.\n            raise_exception (bool): If `True`, an exception will be raised if\n                the task is not found. Defaults to `True`.\n\n        Returns:\n            Optional[Task]: The task, if found.\n        \"\"\"\n        try:\n            return self._task_registry[name]\n        except KeyError:\n            if raise_exception:\n                raise SanicException(\n                    f'Registered task named \"{name}\" not found.'\n                )\n            return None\n\n    async def cancel_task(\n        self,\n        name: str,\n        msg: str | None = None,\n        *,\n        raise_exception: bool = True,\n    ) -> None:\n        \"\"\"Cancel a named task.\n\n        This method is used to cancel a task by its name. Optionally, you can\n        provide a message that describes why the task was canceled, and control\n        whether an exception should be raised if the task is not found.\n\n        Args:\n            name (str): The name of the task to be canceled.\n            msg (Optional[str]): Optional message describing why the task was canceled. Defaults to None.\n            raise_exception (bool): If True, an exception will be raised if the task is not found. Defaults to True.\n\n        Example:\n            ```python\n            async def my_task():\n                try:\n                    await asyncio.sleep(10)\n                except asyncio.CancelledError as e:\n                    current_task = asyncio.current_task()\n                    print(f\"Task {current_task.get_name()} was cancelled. {e}\")\n                    # Task sleepy_task was cancelled. No more sleeping!\n\n\n            @app.before_server_start\n            async def before_start(app):\n                app.add_task(my_task, name=\"sleepy_task\")\n                await asyncio.sleep(1)\n                await app.cancel_task(\"sleepy_task\", msg=\"No more sleeping!\")\n            ```\n        \"\"\"  # noqa: E501\n        task = self.get_task(name, raise_exception=raise_exception)\n        if task and not task.cancelled():\n            if msg and sys.version_info < (3, 14):\n                task.cancel(msg)\n            else:\n                task.cancel()\n            try:\n                await task\n            except CancelledError:\n                ...\n\n    def purge_tasks(self) -> None:\n        \"\"\"Purges completed and cancelled tasks from the task registry.\n\n        This method iterates through the task registry, identifying any tasks\n        that are either done or cancelled, and then removes those tasks,\n        leaving only the pending tasks in the registry.\n        \"\"\"\n        for key, task in self._task_registry.items():\n            if task is None:\n                continue\n            if task.done() or task.cancelled():\n                self._task_registry[key] = None\n\n        self._task_registry = {\n            k: v for k, v in self._task_registry.items() if v is not None\n        }\n\n    def shutdown_tasks(\n        self, timeout: float | None = None, increment: float = 0.1\n    ) -> None:\n        \"\"\"Cancel all tasks except the server task.\n\n        This method is used to cancel all tasks except the server task. It\n        iterates through the task registry, cancelling all tasks except the\n        server task, and then waits for the tasks to complete. Optionally, you\n        can provide a timeout and an increment to control how long the method\n        will wait for the tasks to complete.\n\n        Args:\n            timeout (Optional[float]): The amount of time to wait for the tasks\n                to complete. Defaults to `None`.\n            increment (float): The amount of time to wait between checks for\n                whether the tasks have completed. Defaults to `0.1`.\n        \"\"\"\n        for task in self.tasks:\n            if task.get_name() != \"RunServer\":\n                task.cancel()\n\n        if timeout is None:\n            timeout = self.config.GRACEFUL_SHUTDOWN_TIMEOUT\n\n        while len(self._task_registry) and timeout:\n            with suppress(RuntimeError):\n                running_loop = get_running_loop()\n                running_loop.run_until_complete(asyncio.sleep(increment))\n            self.purge_tasks()\n            timeout -= increment\n\n    @property\n    def tasks(self) -> Iterable[Task[Any]]:\n        \"\"\"The tasks that are currently registered with the application.\n\n        Returns:\n            Iterable[Task[Any]]: The tasks that are currently registered with\n                the application.\n        \"\"\"\n        return (\n            task\n            for task in iter(self._task_registry.values())\n            if task is not None\n        )\n\n    # -------------------------------------------------------------------- #\n    # ASGI\n    # -------------------------------------------------------------------- #\n\n    async def __call__(self, scope, receive, send):\n        \"\"\"\n        To be ASGI compliant, our instance must be a callable that accepts\n        three arguments: scope, receive, send. See the ASGI reference for more\n        details: https://asgi.readthedocs.io/en/latest\n        \"\"\"\n        if scope[\"type\"] == \"lifespan\":\n            setup_logging(\n                self.state.is_debug,\n                self.config.NO_COLOR,\n                self.config.LOG_EXTRA,\n            )\n            self.asgi = True\n            self.motd(\"\")\n            self._asgi_lifespan = Lifespan(self, scope, receive, send)\n            await self._asgi_lifespan()\n        else:\n            self._asgi_app = await ASGIApp.create(self, scope, receive, send)\n            await self._asgi_app()\n\n    _asgi_single_callable = True  # We conform to ASGI 3.0 single-callable\n\n    # -------------------------------------------------------------------- #\n    # Configuration\n    # -------------------------------------------------------------------- #\n\n    def update_config(self, config: bytes | str | dict | Any) -> None:\n        \"\"\"Update the application configuration.\n\n        This method is used to update the application configuration. It can\n        accept a configuration object, a dictionary, or a path to a file that\n        contains a configuration object or dictionary.\n\n        See [Configuration](/en/guide/deployment/configuration) for details.\n\n        Args:\n            config (Union[bytes, str, dict, Any]): The configuration object,\n                dictionary, or path to a configuration file.\n        \"\"\"\n\n        self.config.update_config(config)\n\n    @property\n    def asgi(self) -> bool:\n        \"\"\"Whether the app is running in ASGI mode.\"\"\"\n        return self.state.asgi\n\n    @asgi.setter\n    def asgi(self, value: bool):\n        self.state.asgi = value\n\n    @property\n    def debug(self) -> bool:\n        \"\"\"Whether the app is running in debug mode.\"\"\"\n        return self.state.is_debug\n\n    @property\n    def auto_reload(self) -> bool:\n        \"\"\"Whether the app is running in auto-reload mode.\"\"\"\n        return self.config.AUTO_RELOAD\n\n    @auto_reload.setter\n    def auto_reload(self, value: bool):\n        self.config.AUTO_RELOAD = value\n        self.state.auto_reload = value\n\n    @property\n    def state(self) -> ApplicationState:  # type: ignore\n        \"\"\"The application state.\n\n        Returns:\n            ApplicationState: The current state of the application.\n        \"\"\"\n        return self._state\n\n    @property\n    def reload_dirs(self) -> set[Path]:\n        \"\"\"The directories that are monitored for auto-reload.\n\n        Returns:\n            Set[str]: The set of directories that are monitored for\n                auto-reload.\n        \"\"\"\n        return self.state.reload_dirs\n\n    # -------------------------------------------------------------------- #\n    # Sanic Extensions\n    # -------------------------------------------------------------------- #\n\n    @property\n    def ext(self) -> Extend:\n        \"\"\"Convenience property for accessing Sanic Extensions.\n\n        This property is available if the `sanic-ext` package is installed.\n\n        See [Sanic Extensions](/en/plugins/sanic-ext/getting-started)\n            for details.\n\n        Returns:\n            Extend: The Sanic Extensions instance.\n\n        Examples:\n            A typical use case might be for registering a dependency injection.\n            ```python\n            app.ext.dependency(SomeObject())\n            ```\n        \"\"\"\n        if not hasattr(self, \"_ext\"):\n            setup_ext(self, fail=True)\n\n        if not hasattr(self, \"_ext\"):\n            raise RuntimeError(\n                \"Sanic Extensions is not installed. You can add it to your \"\n                \"environment using:\\n$ pip install sanic[ext]\\nor\\n$ pip \"\n                \"install sanic-ext\"\n            )\n        return self._ext  # type: ignore\n\n    def extend(\n        self,\n        *,\n        extensions: list[type[Extension]] | None = None,\n        built_in_extensions: bool = True,\n        config: Config | dict[str, Any] | None = None,\n        **kwargs,\n    ) -> Extend:\n        \"\"\"Extend Sanic with additional functionality using Sanic Extensions.\n\n        This method enables you to add one or more Sanic Extensions to the\n        current Sanic instance. It allows for more control over the Extend\n        object, such as enabling or disabling built-in extensions or providing\n        custom configuration.\n\n        See [Sanic Extensions](/en/plugins/sanic-ext/getting-started)\n            for details.\n\n        Args:\n            extensions (Optional[List[Type[Extension]]], optional): A list of\n                extensions to add. Defaults to `None`, meaning only built-in\n                extensions are added.\n            built_in_extensions (bool, optional): Whether to enable built-in\n                extensions. Defaults to `True`.\n            config (Optional[Union[Config, Dict[str, Any]]], optional):\n                Optional custom configuration for the extensions. Defaults\n                to `None`.\n            **kwargs: Additional keyword arguments that might be needed by\n                specific extensions.\n\n        Returns:\n            Extend: The Sanic Extensions instance.\n\n        Raises:\n            RuntimeError: If an attempt is made to extend Sanic after Sanic\n                Extensions has already been set up.\n\n        Examples:\n            A typical use case might be to add a custom extension along with\n                built-in ones.\n            ```python\n            app.extend(\n                extensions=[MyCustomExtension],\n                built_in_extensions=True\n            )\n            ```\n        \"\"\"\n        if hasattr(self, \"_ext\"):\n            raise RuntimeError(\n                \"Cannot extend Sanic after Sanic Extensions has been setup.\"\n            )\n        setup_ext(\n            self,\n            extensions=extensions,\n            built_in_extensions=built_in_extensions,\n            config=config,\n            fail=True,\n            **kwargs,\n        )\n        return self.ext\n\n    # -------------------------------------------------------------------- #\n    # Class methods\n    # -------------------------------------------------------------------- #\n\n    @classmethod\n    def register_app(cls, app: Sanic) -> None:\n        \"\"\"Register a Sanic instance with the class registry.\n\n        This method adds a Sanic application instance to the class registry,\n        which is used for tracking all instances of the application. It is\n        usually used internally, but can be used to register an application\n        that may have otherwise been created outside of the class registry.\n\n        Args:\n            app (Sanic): The Sanic instance to be registered.\n\n        Raises:\n            SanicException: If the app is not an instance of Sanic or if the\n                name of the app is already in use (unless in test mode).\n\n        Examples:\n            ```python\n            Sanic.register_app(my_app)\n            ```\n        \"\"\"\n        if not isinstance(app, cls):\n            raise SanicException(\"Registered app must be an instance of Sanic\")\n\n        name = app.name\n        if name in cls._app_registry and not cls.test_mode:\n            raise SanicException(f'Sanic app name \"{name}\" already in use.')\n\n        cls._app_registry[name] = app\n\n    @classmethod\n    def unregister_app(cls, app: Sanic) -> None:\n        \"\"\"Unregister a Sanic instance from the class registry.\n\n        This method removes a previously registered Sanic application instance\n        from the class registry. This can be useful for cleanup purposes,\n        especially in testing or when an app instance is no longer needed. But,\n        it is typically used internally and should not be needed in most cases.\n\n        Args:\n            app (Sanic): The Sanic instance to be unregistered.\n\n        Raises:\n            SanicException: If the app is not an instance of Sanic.\n\n        Examples:\n            ```python\n            Sanic.unregister_app(my_app)\n            ```\n        \"\"\"\n        if not isinstance(app, cls):\n            raise SanicException(\"Registered app must be an instance of Sanic\")\n\n        name = app.name\n        if name in cls._app_registry:\n            del cls._app_registry[name]\n\n    @classmethod\n    def get_app(\n        cls, name: str | None = None, *, force_create: bool = False\n    ) -> Sanic:\n        \"\"\"Retrieve an instantiated Sanic instance by name.\n\n        This method is best used when needing to get access to an already\n        defined application instance in another part of an app.\n\n        .. warning::\n            Be careful when using this method in the global scope as it is\n            possible that the import path running will cause it to error if\n            the imported global scope runs before the application instance\n            is created.\n\n            It is typically best used in a function or method that is called\n            after the application instance has been created.\n\n            ```python\n            def setup_routes():\n                app = Sanic.get_app()\n                app.add_route(handler_1, '/route1')\n                app.add_route(handler_2, '/route2')\n            ```\n\n        Args:\n            name (Optional[str], optional): Name of the application instance\n                to retrieve. When not specified, it will return the only\n                application instance if there is only one. If not specified\n                and there are multiple application instances, it will raise\n                an exception. Defaults to `None`.\n            force_create (bool, optional): If `True` and the named app does\n                not exist, a new instance will be created. Defaults to `False`.\n\n        Returns:\n            Sanic: The requested Sanic app instance.\n\n        Raises:\n            SanicException: If there are multiple or no Sanic apps found, or\n                if the specified name is not found.\n\n\n        Example:\n            ```python\n            app1 = Sanic(\"app1\")\n            app2 = Sanic.get_app(\"app1\")  # app2 is the same instance as app1\n            ```\n        \"\"\"\n        if name is None:\n            if len(cls._app_registry) > 1:\n                raise SanicException(\n                    'Multiple Sanic apps found, use Sanic.get_app(\"app_name\")'\n                )\n            elif len(cls._app_registry) == 0:\n                raise SanicException(\"No Sanic apps have been registered.\")\n            else:\n                return list(cls._app_registry.values())[0]\n        try:\n            return cls._app_registry[name]\n        except KeyError:\n            if name == \"__main__\":\n                return cls.get_app(\"__mp_main__\", force_create=force_create)\n            if force_create:\n                return cls(name)\n            raise SanicException(\n                f\"Sanic app name '{name}' not found.\\n\"\n                \"App instantiation must occur outside \"\n                \"if __name__ == '__main__' \"\n                \"block or by using an AppLoader.\\nSee \"\n                \"https://sanic.dev/en/guide/deployment/app-loader.html\"\n                \" for more details.\"\n            )\n\n    @classmethod\n    def _check_uvloop_conflict(cls) -> None:\n        values = {app.config.USE_UVLOOP for app in cls._app_registry.values()}\n        if len(values) > 1:\n            error_logger.warning(\n                \"It looks like you're running several apps with different \"\n                \"uvloop settings. This is not supported and may lead to \"\n                \"unintended behaviour.\"\n            )\n\n    # -------------------------------------------------------------------- #\n    # Lifecycle\n    # -------------------------------------------------------------------- #\n\n    @contextmanager\n    def amend(self) -> Iterator[None]:\n        \"\"\"Context manager to allow changes to the app after it has started.\n\n        Typically, once an application has started and is running, you cannot\n        make certain changes, like adding routes, middleware, or signals. This\n        context manager allows you to make those changes, and then finalizes\n        the app again when the context manager exits.\n\n        Yields:\n            None\n\n        Example:\n            ```python\n            with app.amend():\n                app.add_route(handler, '/new_route')\n            ```\n        \"\"\"\n        if not self.state.is_started:\n            yield\n        else:\n            do_router = self.router.finalized\n            do_signal_router = self.signal_router.finalized\n            if do_router:\n                self.router.reset()\n            if do_signal_router:\n                self.signal_router.reset()\n            yield\n            if do_signal_router:\n                self.signalize(cast(bool, self.config.TOUCHUP))\n            if do_router:\n                self.finalize()\n\n    def finalize(self) -> None:\n        \"\"\"Finalize the routing configuration for the Sanic application.\n\n        This method completes the routing setup by calling the router's\n        finalize method, and it also finalizes any middleware that has been\n        added to the application. If the application is not in test mode,\n        any finalization errors will be raised.\n\n        Finalization consists of identifying defined routes and optimizing\n        Sanic's performance to meet the application's specific needs. If\n        you are manually adding routes, after Sanic has started, you will\n        typically want to use the  `amend` context manager rather than\n        calling this method directly.\n\n        .. note::\n            This method is usually called internally during the server setup\n            process and does not typically need to be invoked manually.\n\n        Raises:\n            FinalizationError: If there is an error during the finalization\n                process, and the application is not in test mode.\n\n        Example:\n            ```python\n            app.finalize()\n            ```\n        \"\"\"\n        try:\n            self.router.finalize()\n        except FinalizationError as e:\n            if not Sanic.test_mode:\n                raise e\n        self.finalize_middleware()\n\n    def signalize(self, allow_fail_builtin: bool = True) -> None:\n        \"\"\"Finalize the signal handling configuration for the Sanic application.\n\n        This method completes the signal handling setup by calling the signal\n        router's finalize method. If the application is not in test mode,\n        any finalization errors will be raised.\n\n        Finalization consists of identifying defined signaliz and optimizing\n        Sanic's performance to meet the application's specific needs. If\n        you are manually adding signals, after Sanic has started, you will\n        typically want to use the  `amend` context manager rather than\n        calling this method directly.\n\n        .. note::\n            This method is usually called internally during the server setup\n            process and does not typically need to be invoked manually.\n\n        Args:\n            allow_fail_builtin (bool, optional): If set to `True`, will allow\n                built-in signals to fail during the finalization process.\n                Defaults to `True`.\n\n        Raises:\n            FinalizationError: If there is an error during the signal\n                finalization process, and the application is not in test mode.\n\n        Example:\n            ```python\n            app.signalize(allow_fail_builtin=False)\n            ```\n        \"\"\"  # noqa: E501\n        self.signal_router.allow_fail_builtin = allow_fail_builtin\n        try:\n            self.signal_router.finalize()\n        except FinalizationError as e:\n            if not Sanic.test_mode:\n                raise e\n\n    async def _startup(self):\n        self._future_registry.clear()\n\n        if not hasattr(self, \"_ext\"):\n            setup_ext(self)\n        if hasattr(self, \"_ext\"):\n            self.ext._display()\n\n        if self.state.is_debug and self.config.TOUCHUP is not True:\n            self.config.TOUCHUP = False\n        elif isinstance(self.config.TOUCHUP, Default):\n            self.config.TOUCHUP = True\n\n        # Setup routers\n        self.signalize(self.config.TOUCHUP)\n        self.finalize()\n\n        route_names = [route.extra.ident for route in self.router.routes]\n        duplicates = {\n            name for name in route_names if route_names.count(name) > 1\n        }\n        if duplicates:\n            names = \", \".join(duplicates)\n            message = (\n                f\"Duplicate route names detected: {names}. You should rename \"\n                \"one or more of them explicitly by using the `name` param, \"\n                \"or changing the implicit name derived from the class and \"\n                \"function name. For more details, please see \"\n                \"https://sanic.dev/en/guide/release-notes/v23.3.html#duplicated-route-names-are-no-longer-allowed\"  # noqa\n            )\n            raise ServerError(message)\n\n        Sanic._check_uvloop_conflict()\n\n        # Startup time optimizations\n        if self.state.primary:\n            # TODO:\n            # - Raise warning if secondary apps have error handler config\n            if self.config.TOUCHUP:\n                TouchUp.run(self)\n\n        self.state.is_started = True\n\n    def ack(self) -> None:\n        \"\"\"Shorthand to send an ack message to the Server Manager.\n\n        In general, this should usually not need to be called manually.\n        It is used to tell the Manager that a process is operational and\n        ready to begin operation.\n        \"\"\"\n        if hasattr(self, \"multiplexer\"):\n            self.multiplexer.ack()\n\n    def set_serving(self, serving: bool) -> None:\n        \"\"\"Set the serving state of the application.\n\n        This method is used to set the serving state of the application.\n        It is used internally by Sanic and should not typically be called\n        manually.\n\n        Args:\n            serving (bool): Whether the application is serving.\n        \"\"\"\n        self.state.is_running = serving\n        if hasattr(self, \"multiplexer\"):\n            self.multiplexer.set_serving(serving)\n\n    async def _server_event(\n        self,\n        concern: str,\n        action: str,\n        loop: AbstractEventLoop | None = None,\n    ) -> None:\n        event = f\"server.{concern}.{action}\"\n        if action not in (\"before\", \"after\") or concern not in (\n            \"init\",\n            \"shutdown\",\n        ):\n            raise SanicException(f\"Invalid server event: {event}\")\n        logger.debug(\n            f\"Triggering server events: {event}\", extra={\"verbosity\": 1}\n        )\n        reverse = concern == \"shutdown\"\n        if loop is None:\n            loop = self.loop\n        await self.dispatch(\n            event,\n            fail_not_found=False,\n            reverse=reverse,\n            inline=True,\n            context={\n                \"app\": self,\n                \"loop\": loop,\n            },\n        )\n\n    # -------------------------------------------------------------------- #\n    # Process Management\n    # -------------------------------------------------------------------- #\n\n    def refresh(\n        self,\n        passthru: dict[str, Any] | None = None,\n    ) -> Sanic:\n        \"\"\"Refresh the application instance. **This is used internally by Sanic**.\n\n        .. warning::\n            This method is intended for internal use only and should not be\n            called directly.\n\n        Args:\n            passthru (Optional[Dict[str, Any]], optional): Optional dictionary\n                of attributes to pass through to the new instance. Defaults to\n                `None`.\n\n        Returns:\n            Sanic: The refreshed application instance.\n        \"\"\"  # noqa: E501\n        registered = self.__class__.get_app(self.name)\n        if self is not registered:\n            if not registered.state.server_info:\n                registered.state.server_info = self.state.server_info\n            self = registered\n        if passthru:\n            for attr, info in passthru.items():\n                if isinstance(info, dict):\n                    for key, value in info.items():\n                        setattr(getattr(self, attr), key, value)\n                else:\n                    setattr(self, attr, info)\n        if hasattr(self, \"multiplexer\"):\n            self.shared_ctx.lock()\n        return self\n\n    @property\n    def inspector(self) -> Inspector:\n        \"\"\"An instance of Inspector for accessing the application's state.\n\n        This can only be accessed from a worker process, and only if the\n        inspector has been enabled.\n\n        See [Inspector](/en/guide/deployment/inspector) for details.\n\n        Returns:\n            Inspector: An instance of Inspector.\n        \"\"\"\n        if environ.get(\"SANIC_WORKER_PROCESS\") or not self._inspector:\n            raise SanicException(\n                \"Can only access the inspector from the main process \"\n                \"after main_process_start has run. For example, you most \"\n                \"likely want to use it inside the @app.main_process_ready \"\n                \"event listener.\"\n            )\n        return self._inspector\n\n    @property\n    def manager(self) -> WorkerManager:\n        \"\"\"\n        Property to access the WorkerManager instance.\n\n        This property provides access to the WorkerManager object controlling\n        the worker processes. It can only be accessed from the main process.\n\n        .. note::\n            Make sure to only access this property from the main process,\n            as attempting to do so from a worker process will result\n            in an exception.\n\n        See [WorkerManager](/en/guide/deployment/manager) for details.\n\n        Returns:\n            WorkerManager: The manager responsible for managing\n                worker processes.\n\n        Raises:\n            SanicException: If an attempt is made to access the manager\n                from a worker process or if the manager is not initialized.\n\n        Example:\n            ```python\n            app.manager.manage(...)\n            ```\n        \"\"\"\n\n        if environ.get(\"SANIC_WORKER_PROCESS\") or not self._manager:\n            raise SanicException(\n                \"Can only access the manager from the main process \"\n                \"after main_process_start has run. For example, you most \"\n                \"likely want to use it inside the @app.main_process_ready \"\n                \"event listener.\"\n            )\n        return self._manager\n"
  },
  {
    "path": "sanic/application/__init__.py",
    "content": ""
  },
  {
    "path": "sanic/application/constants.py",
    "content": "from enum import Enum, IntEnum, auto\n\n\nclass StrEnum(str, Enum):  # no cov\n    def _generate_next_value_(name: str, *args) -> str:  # type: ignore\n        return name.lower()\n\n    def __eq__(self, value: object) -> bool:\n        value = str(value).upper()\n        return super().__eq__(value)\n\n    def __hash__(self) -> int:\n        return hash(self.value)\n\n    def __str__(self) -> str:\n        return self.value\n\n\nclass Server(StrEnum):\n    \"\"\"Server types.\"\"\"\n\n    SANIC = auto()\n    ASGI = auto()\n\n\nclass Mode(StrEnum):\n    \"\"\"Server modes.\"\"\"\n\n    PRODUCTION = auto()\n    DEBUG = auto()\n\n\nclass ServerStage(IntEnum):\n    \"\"\"Server stages.\"\"\"\n\n    STOPPED = auto()\n    PARTIAL = auto()\n    SERVING = auto()\n"
  },
  {
    "path": "sanic/application/ext.py",
    "content": "from __future__ import annotations\n\nfrom contextlib import suppress\nfrom importlib import import_module\nfrom typing import TYPE_CHECKING\n\n\nif TYPE_CHECKING:\n    from sanic import Sanic\n\n\ndef setup_ext(app: Sanic, *, fail: bool = False, **kwargs):\n    \"\"\"Setup Sanic Extensions.\n\n    Requires Sanic Extensions to be installed.\n\n    Args:\n        app (Sanic): Sanic application.\n        fail (bool, optional): Raise an error if Sanic Extensions is not\n            installed. Defaults to `False`.\n        **kwargs: Keyword arguments to pass to `sanic_ext.Extend`.\n\n    Returns:\n        sanic_ext.Extend: Sanic Extensions instance.\n    \"\"\"\n\n    if not app.config.AUTO_EXTEND:\n        return\n\n    sanic_ext = None\n    with suppress(ModuleNotFoundError):\n        sanic_ext = import_module(\"sanic_ext\")\n\n    if not sanic_ext:  # no cov\n        if fail:\n            raise RuntimeError(\n                \"Sanic Extensions is not installed. You can add it to your \"\n                \"environment using:\\n$ pip install sanic[ext]\\nor\\n$ pip \"\n                \"install sanic-ext\"\n            )\n\n        return\n\n    if not getattr(app, \"_ext\", None):\n        Ext = getattr(sanic_ext, \"Extend\")\n        app._ext = Ext(app, **kwargs)\n\n        return app.ext\n"
  },
  {
    "path": "sanic/application/logo.py",
    "content": "import re\nimport sys\n\nfrom os import environ\n\nfrom sanic.helpers import is_atty\n\n\nBASE_LOGO = \"\"\"\n\n                 Sanic\n         Build Fast. Run Fast.\n\n\"\"\"\nCOFFEE_LOGO = \"\"\"\\033[48;2;255;13;104m                     \\033[0m\n\\033[38;2;255;255;255;48;2;255;13;104m     ▄████████▄      \\033[0m\n\\033[38;2;255;255;255;48;2;255;13;104m    ██       ██▀▀▄   \\033[0m\n\\033[38;2;255;255;255;48;2;255;13;104m    ███████████  █   \\033[0m\n\\033[38;2;255;255;255;48;2;255;13;104m    ███████████▄▄▀   \\033[0m\n\\033[38;2;255;255;255;48;2;255;13;104m     ▀███████▀       \\033[0m\n\\033[48;2;255;13;104m                     \\033[0m\nDark roast. No sugar.\"\"\"\n\nCOLOR_LOGO = \"\"\"\\033[48;2;255;13;104m                     \\033[0m\n\\033[38;2;255;255;255;48;2;255;13;104m    ▄███ █████ ██    \\033[0m\n\\033[38;2;255;255;255;48;2;255;13;104m   ██                \\033[0m\n\\033[38;2;255;255;255;48;2;255;13;104m    ▀███████ ███▄    \\033[0m\n\\033[38;2;255;255;255;48;2;255;13;104m                ██   \\033[0m\n\\033[38;2;255;255;255;48;2;255;13;104m   ████ ████████▀    \\033[0m\n\\033[48;2;255;13;104m                     \\033[0m\nBuild Fast. Run Fast.\"\"\"\n\nFULL_COLOR_LOGO = \"\"\"\n\n\\033[38;2;255;13;104m  ▄███ █████ ██ \\033[0m     ▄█▄      ██       █   █   ▄██████████\n\\033[38;2;255;13;104m ██             \\033[0m    █   █     █ ██     █   █  ██\n\\033[38;2;255;13;104m  ▀███████ ███▄ \\033[0m   ▀     █    █   ██   ▄   █  ██\n\\033[38;2;255;13;104m              ██\\033[0m  █████████   █     ██ █   █  ▄▄\n\\033[38;2;255;13;104m ████ ████████▀ \\033[0m █         █  █       ██   █   ▀██ ███████\n\n\"\"\"  # noqa\n\nSVG_LOGO_SIMPLE = \"\"\"<svg id=logo-simple viewBox=\"0 0 964 279\"><desc>Sanic</desc><path d=\"M107 222c9-2 10-20 1-22s-20-2-30-2-17 7-16 14 6 10 15 10h30zm115-1c16-2 30-11 35-23s6-24 2-33-6-14-15-20-24-11-38-10c-7 3-10 13-5 19s17-1 24 4 15 14 13 24-5 15-14 18-50 0-74 0h-17c-6 4-10 15-4 20s16 2 23 3zM251 83q9-1 9-7 0-15-10-16h-13c-10 6-10 20 0 22zM147 60c-4 0-10 3-11 11s5 13 10 12 42 0 67 0c5-3 7-10 6-15s-4-8-9-8zm-33 1c-8 0-16 0-24 3s-20 10-25 20-6 24-4 36 15 22 26 27 78 8 94 3c4-4 4-12 0-18s-69 8-93-10c-8-7-9-23 0-30s12-10 20-10 12 2 16-3 1-15-5-18z\" fill=\"#ff0d68\"/><path d=\"M676 74c0-14-18-9-20 0s0 30 0 39 20 9 20 2zm-297-10c-12 2-15 12-23 23l-41 58H340l22-30c8-12 23-13 30-4s20 24 24 38-10 10-17 10l-68 2q-17 1-48 30c-7 6-10 20 0 24s15-8 20-13 20 -20 58-21h50 c20 2 33 9 52 30 8 10 24-4 16-13L384 65q-3-2-5-1zm131 0c-10 1-12 12-11 20v96c1 10-3 23 5 32s20-5 17-15c0-23-3-46 2-67 5-12 22-14 32-5l103 87c7 5 19 1 18-9v-64c-3-10-20-9-21 2s-20 22-30 13l-97-80c-5-4-10-10-18-10zM701 76v128c2 10 15 12 20 4s0-102 0-124s-20-18-20-7z M850 63c-35 0-69-2-86 15s-20 60-13 66 13 8 16 0 1-10 1-27 12-26 20-32 66-5 85-5 31 4 31-10-18-7-54-7M764 159c-6-2-15-2-16 12s19 37 33 43 23 8 25-4-4-11-11-14q-9-3-22-18c-4-7-3-16-10-19zM828 196c-4 0-8 1-10 5s-4 12 0 15 8 2 12 2h60c5 0 10-2 12-6 3-7-1-16-8-16z\" fill=\"#1f1f1f\"/></svg>\"\"\"  # noqa\n\nansi_pattern = re.compile(r\"\\x1B(?:[@-Z\\\\-_]|\\[[0-?]*[ -/]*[@-~])\")\n\n\ndef get_logo(full: bool = False, coffee: bool = False) -> str:\n    \"\"\"Get the Sanic logo.\n\n    Will return the full color logo if the terminal supports it.\n\n    Args:\n        full (bool, optional): Use the full color logo. Defaults to `False`.\n        coffee (bool, optional): Use the coffee logo. Defaults to `False`.\n\n    Returns:\n        str: Sanic logo.\n    \"\"\"\n    logo = (\n        (FULL_COLOR_LOGO if full else (COFFEE_LOGO if coffee else COLOR_LOGO))\n        if is_atty()\n        else BASE_LOGO\n    )\n\n    if (\n        sys.platform == \"darwin\"\n        and environ.get(\"TERM_PROGRAM\") == \"Apple_Terminal\"\n    ):\n        logo = ansi_pattern.sub(\"\", logo)\n\n    return logo\n"
  },
  {
    "path": "sanic/application/motd.py",
    "content": "from abc import ABC, abstractmethod\nfrom shutil import get_terminal_size\nfrom textwrap import indent, wrap\n\nfrom sanic import __version__\nfrom sanic.helpers import is_atty\nfrom sanic.log import logger\n\n\nclass MOTD(ABC):\n    \"\"\"Base class for the Message of the Day (MOTD) display.\"\"\"\n\n    def __init__(\n        self,\n        logo: str | None,\n        serve_location: str,\n        data: dict[str, str],\n        extra: dict[str, str],\n    ) -> None:\n        self.logo = logo\n        self.serve_location = serve_location\n        self.data = data\n        self.extra = extra\n        self.key_width = 0\n        self.value_width = 0\n\n    @abstractmethod\n    def display(self):\n        \"\"\"Display the MOTD.\"\"\"\n\n    @classmethod\n    def output(\n        cls,\n        logo: str | None,\n        serve_location: str,\n        data: dict[str, str],\n        extra: dict[str, str],\n    ) -> None:\n        \"\"\"Output the MOTD.\n\n        Args:\n            logo (Optional[str]): Logo to display.\n            serve_location (str): Location to serve.\n            data (Dict[str, str]): Data to display.\n            extra (Dict[str, str]): Extra data to display.\n        \"\"\"\n        motd_class = MOTDTTY if is_atty() else MOTDBasic\n        motd_class(logo, serve_location, data, extra).display()\n\n\nclass MOTDBasic(MOTD):\n    \"\"\"A basic MOTD display.\n\n    This is used when the terminal does not support ANSI escape codes.\n    \"\"\"\n\n    def display(self):\n        if self.logo:\n            logger.debug(self.logo)\n        lines = [f\"Sanic v{__version__}\"]\n        if self.serve_location:\n            lines.append(f\"Goin' Fast @ {self.serve_location}\")\n        lines += [\n            *(f\"{key}: {value}\" for key, value in self.data.items()),\n            *(f\"{key}: {value}\" for key, value in self.extra.items()),\n        ]\n        for line in lines:\n            logger.info(line)\n\n\nclass MOTDTTY(MOTD):\n    \"\"\"A MOTD display for terminals that support ANSI escape codes.\"\"\"\n\n    def __init__(self, *args, **kwargs) -> None:\n        super().__init__(*args, **kwargs)\n        self.set_variables()\n\n    def set_variables(self):  # no  cov\n        \"\"\"Set the variables used for display.\"\"\"\n        fallback = (108, 24)\n        terminal_width = max(\n            get_terminal_size(fallback=fallback).columns, fallback[0]\n        )\n        self.max_value_width = terminal_width - fallback[0] + 36\n\n        self.key_width = 4\n        self.value_width = self.max_value_width\n        if self.data:\n            self.key_width = max(map(len, self.data.keys()))\n            self.value_width = min(\n                max(map(len, self.data.values())), self.max_value_width\n            )\n        if self.extra:\n            self.key_width = max(\n                self.key_width, max(map(len, self.extra.keys()))\n            )\n            self.value_width = min(\n                max((*map(len, self.extra.values()), self.value_width)),\n                self.max_value_width,\n            )\n        self.logo_lines = self.logo.split(\"\\n\") if self.logo else []\n        self.logo_line_length = 24\n        self.centering_length = (\n            self.key_width + self.value_width + 2 + self.logo_line_length\n        )\n        self.display_length = self.key_width + self.value_width + 2\n\n    def display(self, version=True, action=\"Goin' Fast\", out=None):\n        \"\"\"Display the MOTD.\n\n        Args:\n            version (bool, optional): Display the version. Defaults to `True`.\n            action (str, optional): Action to display. Defaults to\n                `\"Goin' Fast\"`.\n            out (Optional[Callable], optional): Output function. Defaults to\n                `None`.\n        \"\"\"\n        if not out:\n            out = logger.info\n        header = \"Sanic\"\n        if version:\n            header += f\" v{__version__}\"\n        header = header.center(self.centering_length)\n        running = (\n            f\"{action} @ {self.serve_location}\" if self.serve_location else \"\"\n        ).center(self.centering_length)\n        length = len(header) + 2 - self.logo_line_length\n        first_filler = \"─\" * (self.logo_line_length - 1)\n        second_filler = \"─\" * length\n        display_filler = \"─\" * (self.display_length + 2)\n        lines = [\n            f\"\\n┌{first_filler}─{second_filler}┐\",\n            f\"│ {header} │\",\n            f\"│ {running} │\",\n            f\"├{first_filler}┬{second_filler}┤\",\n        ]\n\n        self._render_data(lines, self.data, 0)\n        if self.extra:\n            logo_part = self._get_logo_part(len(lines) - 4)\n            lines.append(f\"│ {logo_part} ├{display_filler}┤\")\n            self._render_data(lines, self.extra, len(lines) - 4)\n\n        self._render_fill(lines)\n\n        lines.append(f\"└{first_filler}┴{second_filler}┘\\n\")\n        out(indent(\"\\n\".join(lines), \"  \"))\n\n    def _render_data(self, lines, data, start):\n        offset = 0\n        for idx, (key, value) in enumerate(data.items(), start=start):\n            key = key.rjust(self.key_width)\n\n            wrapped = wrap(value, self.max_value_width, break_on_hyphens=False)\n            for wrap_index, part in enumerate(wrapped):\n                part = part.ljust(self.value_width)\n                logo_part = self._get_logo_part(idx + offset + wrap_index)\n                display = (\n                    f\"{key}: {part}\"\n                    if wrap_index == 0\n                    else (\" \" * len(key) + f\"  {part}\")\n                )\n                lines.append(f\"│ {logo_part} │ {display} │\")\n                if wrap_index:\n                    offset += 1\n\n    def _render_fill(self, lines):\n        filler = \" \" * self.display_length\n        idx = len(lines) - 5\n        for i in range(1, len(self.logo_lines) - idx):\n            logo_part = self.logo_lines[idx + i]\n            lines.append(f\"│ {logo_part} │ {filler} │\")\n\n    def _get_logo_part(self, idx):\n        try:\n            logo_part = self.logo_lines[idx]\n        except IndexError:\n            logo_part = \" \" * (self.logo_line_length - 3)\n        return logo_part\n"
  },
  {
    "path": "sanic/application/spinner.py",
    "content": "import os\nimport sys\nimport time\n\nfrom contextlib import contextmanager\nfrom queue import Queue\nfrom threading import Thread\n\n\nif os.name == \"nt\":  # noqa\n    import ctypes  # noqa\n\n    class _CursorInfo(ctypes.Structure):\n        _fields_ = [(\"size\", ctypes.c_int), (\"visible\", ctypes.c_byte)]\n\n\nclass Spinner:  # noqa\n    \"\"\"Spinner class to show a loading spinner in the terminal.\n\n    Used internally by the `loading` context manager.\n    \"\"\"\n\n    def __init__(self, message: str) -> None:\n        self.message = message\n        self.queue: Queue[int] = Queue()\n        self.spinner = self.cursor()\n        self.thread = Thread(target=self.run)\n\n    def start(self):\n        self.queue.put(1)\n        self.thread.start()\n        self.hide()\n\n    def run(self):\n        while self.queue.get():\n            output = f\"\\r{self.message} [{next(self.spinner)}]\"\n            sys.stdout.write(output)\n            sys.stdout.flush()\n            time.sleep(0.1)\n            self.queue.put(1)\n\n    def stop(self):\n        self.queue.put(0)\n        self.thread.join()\n        self.show()\n\n    @staticmethod\n    def cursor():\n        while True:\n            yield from \"|/-\\\\\"\n\n    @staticmethod\n    def hide():\n        if os.name == \"nt\":\n            ci = _CursorInfo()\n            handle = ctypes.windll.kernel32.GetStdHandle(-11)\n            ctypes.windll.kernel32.GetConsoleCursorInfo(\n                handle, ctypes.byref(ci)\n            )\n            ci.visible = False\n            ctypes.windll.kernel32.SetConsoleCursorInfo(\n                handle, ctypes.byref(ci)\n            )\n        elif os.name == \"posix\":\n            sys.stdout.write(\"\\033[?25l\")\n            sys.stdout.flush()\n\n    @staticmethod\n    def show():\n        if os.name == \"nt\":\n            ci = _CursorInfo()\n            handle = ctypes.windll.kernel32.GetStdHandle(-11)\n            ctypes.windll.kernel32.GetConsoleCursorInfo(\n                handle, ctypes.byref(ci)\n            )\n            ci.visible = True\n            ctypes.windll.kernel32.SetConsoleCursorInfo(\n                handle, ctypes.byref(ci)\n            )\n        elif os.name == \"posix\":\n            sys.stdout.write(\"\\033[?25h\")\n            sys.stdout.flush()\n\n\n@contextmanager\ndef loading(message: str = \"Loading\"):  # noqa\n    spinner = Spinner(message)\n    spinner.start()\n    yield\n    spinner.stop()\n"
  },
  {
    "path": "sanic/application/state.py",
    "content": "from __future__ import annotations\n\nimport logging\n\nfrom dataclasses import dataclass, field\nfrom pathlib import Path\nfrom socket import socket\nfrom ssl import SSLContext\nfrom typing import TYPE_CHECKING, Any\n\nfrom sanic.application.constants import Mode, Server, ServerStage\nfrom sanic.log import VerbosityFilter, logger\nfrom sanic.server.async_server import AsyncioServer\n\n\nif TYPE_CHECKING:\n    from sanic import Sanic\n\n\n@dataclass\nclass ApplicationServerInfo:\n    \"\"\"Information about a server instance.\"\"\"\n\n    settings: dict[str, Any]\n    stage: ServerStage = field(default=ServerStage.STOPPED)\n    server: AsyncioServer | None = field(default=None)\n\n\n@dataclass\nclass ApplicationState:\n    \"\"\"Application state.\n\n    This class is used to store the state of the application. It is\n    instantiated by the application and is available as `app.state`.\n    \"\"\"\n\n    app: Sanic\n    asgi: bool = field(default=False)\n    coffee: bool = field(default=False)\n    fast: bool = field(default=False)\n    host: str = field(default=\"\")\n    port: int = field(default=0)\n    ssl: SSLContext | None = field(default=None)\n    sock: socket | None = field(default=None)\n    unix: str | None = field(default=None)\n    mode: Mode = field(default=Mode.PRODUCTION)\n    reload_dirs: set[Path] = field(default_factory=set)\n    auto_reload: bool = field(default=False)\n    server: Server = field(default=Server.SANIC)\n    is_running: bool = field(default=False)\n    is_started: bool = field(default=False)\n    is_stopping: bool = field(default=False)\n    verbosity: int = field(default=0)\n    workers: int = field(default=0)\n    primary: bool = field(default=True)\n    server_info: list[ApplicationServerInfo] = field(default_factory=list)\n\n    # This property relates to the ApplicationState instance and should\n    # not be changed except in the __post_init__ method\n    _init: bool = field(default=False)\n\n    def __post_init__(self) -> None:\n        self._init = True\n\n    def __setattr__(self, name: str, value: Any) -> None:\n        if self._init and name == \"_init\":\n            raise RuntimeError(\n                \"Cannot change the value of _init after instantiation\"\n            )\n        super().__setattr__(name, value)\n        if self._init and hasattr(self, f\"set_{name}\"):\n            getattr(self, f\"set_{name}\")(value)\n\n    def set_mode(self, value: str | Mode):\n        if hasattr(self.app, \"error_handler\"):\n            self.app.error_handler.debug = self.app.debug\n        if getattr(self.app, \"configure_logging\", False) and self.app.debug:\n            logger.setLevel(logging.DEBUG)\n\n    def set_verbosity(self, value: int) -> None:\n        \"\"\"Set the verbosity level.\n\n        Args:\n            value (int): Verbosity level.\n        \"\"\"\n        VerbosityFilter.verbosity = value\n\n    @property\n    def is_debug(self) -> bool:\n        \"\"\"Check if the application is in debug mode.\n\n        Returns:\n            bool: `True` if the application is in debug mode, `False`\n                otherwise.\n        \"\"\"\n        return self.mode is Mode.DEBUG\n\n    @property\n    def stage(self) -> ServerStage:\n        \"\"\"Get the server stage.\n\n        Returns:\n            ServerStage: Server stage.\n        \"\"\"\n        if not self.server_info:\n            return ServerStage.STOPPED\n\n        if all(info.stage is ServerStage.SERVING for info in self.server_info):\n            return ServerStage.SERVING\n        elif any(\n            info.stage is ServerStage.SERVING for info in self.server_info\n        ):\n            return ServerStage.PARTIAL\n\n        return ServerStage.STOPPED\n"
  },
  {
    "path": "sanic/asgi.py",
    "content": "from __future__ import annotations\n\nimport warnings\n\nfrom typing import TYPE_CHECKING\n\nfrom sanic.compat import Header\nfrom sanic.exceptions import BadRequest, ServerError\nfrom sanic.helpers import Default\nfrom sanic.http import Stage\nfrom sanic.log import error_logger, logger\nfrom sanic.models.asgi import ASGIReceive, ASGIScope, ASGISend, MockTransport\nfrom sanic.request import Request\nfrom sanic.response import BaseHTTPResponse\nfrom sanic.server import ConnInfo\nfrom sanic.server.websockets.connection import WebSocketConnection\n\n\nif TYPE_CHECKING:\n    from sanic import Sanic\n\n\nclass Lifespan:\n    def __init__(\n        self, sanic_app, scope: ASGIScope, receive: ASGIReceive, send: ASGISend\n    ) -> None:\n        self.sanic_app = sanic_app\n        self.scope = scope\n        self.receive = receive\n        self.send = send\n\n        if \"server.init.before\" in self.sanic_app.signal_router.name_index:\n            logger.debug(\n                'You have set a listener for \"before_server_start\" '\n                \"in ASGI mode. \"\n                \"It will be executed as early as possible, but not before \"\n                \"the ASGI server is started.\",\n                extra={\"verbosity\": 1},\n            )\n        if \"server.shutdown.after\" in self.sanic_app.signal_router.name_index:\n            logger.debug(\n                'You have set a listener for \"after_server_stop\" '\n                \"in ASGI mode. \"\n                \"It will be executed as late as possible, but not after \"\n                \"the ASGI server is stopped.\",\n                extra={\"verbosity\": 1},\n            )\n\n    async def startup(self) -> None:\n        \"\"\"\n        Gather the listeners to fire on server start.\n        Because we are using a third-party server and not Sanic server, we do\n        not have access to fire anything BEFORE the server starts.\n        Therefore, we fire before_server_start and after_server_start\n        in sequence since the ASGI lifespan protocol only supports a single\n        startup event.\n        \"\"\"\n        await self.sanic_app._startup()\n        await self.sanic_app._server_event(\"init\", \"before\")\n        await self.sanic_app._server_event(\"init\", \"after\")\n\n        if not isinstance(self.sanic_app.config.USE_UVLOOP, Default):\n            warnings.warn(\n                \"You have set the USE_UVLOOP configuration option, but Sanic \"\n                \"cannot control the event loop when running in ASGI mode.\"\n                \"This option will be ignored.\"\n            )\n\n    async def shutdown(self) -> None:\n        \"\"\"\n        Gather the listeners to fire on server stop.\n        Because we are using a third-party server and not Sanic server, we do\n        not have access to fire anything AFTER the server stops.\n        Therefore, we fire before_server_stop and after_server_stop\n        in sequence since the ASGI lifespan protocol only supports a single\n        shutdown event.\n        \"\"\"\n        await self.sanic_app._server_event(\"shutdown\", \"before\")\n        await self.sanic_app._server_event(\"shutdown\", \"after\")\n\n    async def __call__(self) -> None:\n        while True:\n            message = await self.receive()\n            if message[\"type\"] == \"lifespan.startup\":\n                try:\n                    await self.startup()\n                except Exception as e:\n                    error_logger.exception(e)\n                    await self.send(\n                        {\"type\": \"lifespan.startup.failed\", \"message\": str(e)}\n                    )\n                else:\n                    await self.send({\"type\": \"lifespan.startup.complete\"})\n            elif message[\"type\"] == \"lifespan.shutdown\":\n                try:\n                    await self.shutdown()\n                except Exception as e:\n                    error_logger.exception(e)\n                    await self.send(\n                        {\"type\": \"lifespan.shutdown.failed\", \"message\": str(e)}\n                    )\n                else:\n                    await self.send({\"type\": \"lifespan.shutdown.complete\"})\n                return\n\n\nclass ASGIApp:\n    sanic_app: Sanic\n    request: Request\n    transport: MockTransport\n    lifespan: Lifespan\n    ws: WebSocketConnection | None\n    stage: Stage\n    response: BaseHTTPResponse | None\n\n    @classmethod\n    async def create(\n        cls,\n        sanic_app: Sanic,\n        scope: ASGIScope,\n        receive: ASGIReceive,\n        send: ASGISend,\n    ) -> ASGIApp:\n        instance = cls()\n        instance.ws = None\n        instance.sanic_app = sanic_app\n        instance.transport = MockTransport(scope, receive, send)\n        instance.transport.loop = sanic_app.loop\n        instance.stage = Stage.IDLE\n        instance.response = None\n        instance.sanic_app.state.is_started = True\n        setattr(instance.transport, \"add_task\", sanic_app.loop.create_task)\n\n        try:\n            headers = Header(\n                [\n                    (\n                        key.decode(\"ASCII\"),\n                        value.decode(errors=\"surrogateescape\"),\n                    )\n                    for key, value in scope.get(\"headers\", [])\n                ]\n            )\n        except UnicodeDecodeError:\n            raise BadRequest(\n                \"Header names can only contain US-ASCII characters\"\n            )\n\n        if scope[\"type\"] == \"http\":\n            version = scope[\"http_version\"]\n            method = scope[\"method\"]\n        elif scope[\"type\"] == \"websocket\":\n            version = \"1.1\"\n            method = \"GET\"\n\n            instance.ws = instance.transport.create_websocket_connection(\n                send, receive\n            )\n        else:\n            raise ServerError(\"Received unknown ASGI scope\")\n\n        url_bytes, query = scope[\"raw_path\"], scope[\"query_string\"]\n        if query:\n            # httpx ASGI client sends query string as part of raw_path\n            url_bytes = url_bytes.split(b\"?\", 1)[0]\n            # All servers send them separately\n            url_bytes = b\"%b?%b\" % (url_bytes, query)\n\n        request_class = sanic_app.request_class or Request  # type: ignore\n        instance.request = request_class(\n            url_bytes,\n            headers,\n            version,\n            method,\n            instance.transport,\n            sanic_app,\n        )\n        request_class._current.set(instance.request)\n        instance.request.stream = instance  # type: ignore\n        instance.request_body = True\n        instance.request.conn_info = ConnInfo(instance.transport)\n\n        await instance.sanic_app.dispatch(\n            \"http.lifecycle.request\",\n            inline=True,\n            context={\"request\": instance.request},\n            fail_not_found=False,\n        )\n\n        return instance\n\n    async def read(self) -> bytes | None:\n        \"\"\"\n        Read and stream the body in chunks from an incoming ASGI message.\n        \"\"\"\n        if self.stage is Stage.IDLE:\n            self.stage = Stage.REQUEST\n        message = await self.transport.receive()\n        body = message.get(\"body\", b\"\")\n        if not message.get(\"more_body\", False):\n            self.request_body = False\n            if not body:\n                return None\n        return body\n\n    async def __aiter__(self):\n        while self.request_body:\n            data = await self.read()\n            if data:\n                yield data\n\n    def respond(self, response: BaseHTTPResponse):\n        if self.stage is not Stage.HANDLER:\n            self.stage = Stage.FAILED\n            raise RuntimeError(\"Response already started\")\n        if self.response is not None:\n            self.response.stream = None\n        response.stream, self.response = self, response\n        return response\n\n    async def send(self, data, end_stream):\n        if self.stage is Stage.IDLE:\n            if not end_stream or data:\n                raise RuntimeError(\n                    \"There is no request to respond to, either the \"\n                    \"response has already been sent or the \"\n                    \"request has not been received yet.\"\n                )\n            return\n        if self.response and self.stage is Stage.HANDLER:\n            await self.transport.send(\n                {\n                    \"type\": \"http.response.start\",\n                    \"status\": self.response.status,\n                    \"headers\": self.response.processed_headers,\n                }\n            )\n            response_body = getattr(self.response, \"body\", None)\n            if response_body:\n                data = response_body + data if data else response_body\n        self.stage = Stage.IDLE if end_stream else Stage.RESPONSE\n        await self.transport.send(\n            {\n                \"type\": \"http.response.body\",\n                \"body\": data.encode() if hasattr(data, \"encode\") else data,\n                \"more_body\": not end_stream,\n            }\n        )\n\n    _asgi_single_callable = True  # We conform to ASGI 3.0 single-callable\n\n    async def __call__(self) -> None:\n        \"\"\"\n        Handle the incoming request.\n        \"\"\"\n        try:\n            self.stage = Stage.HANDLER\n            await self.sanic_app.handle_request(self.request)\n        except Exception as e:\n            try:\n                await self.sanic_app.handle_exception(self.request, e)\n            except Exception as exc:\n                await self.sanic_app.handle_exception(self.request, exc, False)\n"
  },
  {
    "path": "sanic/base/__init__.py",
    "content": ""
  },
  {
    "path": "sanic/base/meta.py",
    "content": "class SanicMeta(type):\n    @classmethod\n    def __prepare__(metaclass, name, bases, **kwds):\n        cls = super().__prepare__(metaclass, name, bases, **kwds)\n        cls[\"__slots__\"] = ()\n        return cls\n"
  },
  {
    "path": "sanic/base/root.py",
    "content": "import re\n\nfrom typing import Any\n\nfrom sanic.base.meta import SanicMeta\nfrom sanic.exceptions import SanicException\nfrom sanic.mixins.commands import CommandMixin\nfrom sanic.mixins.exceptions import ExceptionMixin\nfrom sanic.mixins.listeners import ListenerMixin\nfrom sanic.mixins.middleware import MiddlewareMixin\nfrom sanic.mixins.routes import RouteMixin\nfrom sanic.mixins.signals import SignalMixin\nfrom sanic.mixins.static import StaticMixin\n\n\nVALID_NAME = re.compile(r\"^[a-zA-Z_][a-zA-Z0-9_\\-]*$\")\n\n\nclass BaseSanic(\n    RouteMixin,\n    StaticMixin,\n    MiddlewareMixin,\n    ListenerMixin,\n    ExceptionMixin,\n    SignalMixin,\n    CommandMixin,\n    metaclass=SanicMeta,\n):\n    __slots__ = (\"name\",)\n\n    def __init__(\n        self, name: str | None = None, *args: Any, **kwargs: Any\n    ) -> None:\n        class_name = self.__class__.__name__\n\n        if name is None:\n            raise SanicException(\n                f\"{class_name} instance cannot be unnamed. \"\n                \"Please use Sanic(name='your_application_name') instead.\",\n            )\n\n        if not VALID_NAME.match(name):\n            raise SanicException(\n                f\"{class_name} instance named '{name}' uses an invalid \"\n                \"format. Names must begin with a character and may only \"\n                \"contain alphanumeric characters, _, or -.\"\n            )\n\n        self.name = name\n\n        for base in BaseSanic.__bases__:\n            base.__init__(self, *args, **kwargs)  # type: ignore\n\n    def __str__(self) -> str:\n        return f\"<{self.__class__.__name__} {self.name}>\"\n\n    def __repr__(self) -> str:\n        return f'{self.__class__.__name__}(name=\"{self.name}\")'\n\n    def __setattr__(self, name: str, value: Any) -> None:\n        try:\n            super().__setattr__(name, value)\n        except AttributeError as e:\n            raise AttributeError(\n                f\"Setting variables on {self.__class__.__name__} instances is \"\n                \"not allowed. You should change your \"\n                f\"{self.__class__.__name__} instance to use \"\n                f\"instance.ctx.{name} instead.\",\n            ) from e\n"
  },
  {
    "path": "sanic/blueprint_group.py",
    "content": "from .blueprints import BlueprintGroup\n\n\n__all__ = [\"BlueprintGroup\"]  # noqa: F405\n"
  },
  {
    "path": "sanic/blueprints.py",
    "content": "from __future__ import annotations\n\nimport asyncio\n\nfrom collections import defaultdict\nfrom collections.abc import Iterable, Iterator, MutableSequence, Sequence\nfrom copy import deepcopy\nfrom functools import partial, wraps\nfrom inspect import isfunction\nfrom itertools import chain\nfrom types import SimpleNamespace\nfrom typing import (\n    TYPE_CHECKING,\n    Any,\n    Callable,\n    overload,\n)\n\nfrom sanic_routing.exceptions import NotFound\nfrom sanic_routing.route import Route\n\nfrom sanic.base.root import BaseSanic\nfrom sanic.exceptions import SanicException\nfrom sanic.helpers import Default, _default\nfrom sanic.models.futures import FutureRoute, FutureSignal, FutureStatic\nfrom sanic.models.handler_types import (\n    ListenerType,\n    MiddlewareType,\n    RouteHandler,\n)\n\n\nif TYPE_CHECKING:\n    from sanic import Sanic\n\n\ndef lazy(func, as_decorator=True):\n    \"\"\"Decorator to register a function to be called later.\n\n    Args:\n        func (Callable): Function to be called later.\n        as_decorator (bool): Whether the function should be called\n            immediately or not.\n    \"\"\"\n\n    @wraps(func)\n    def decorator(bp, *args, **kwargs):\n        nonlocal as_decorator\n        kwargs[\"apply\"] = False\n        pass_handler = None\n\n        if args and isfunction(args[0]):\n            as_decorator = False\n\n        def wrapper(handler):\n            future = func(bp, *args, **kwargs)\n            if as_decorator:\n                future = future(handler)\n\n            if bp.registered:\n                for app in bp.apps:\n                    bp.register(app, {})\n\n            return future\n\n        return wrapper if as_decorator else wrapper(pass_handler)\n\n    return decorator\n\n\nclass Blueprint(BaseSanic):\n    \"\"\"A logical collection of URLs that consist of a similar logical domain.\n\n    A Blueprint object is the main tool for grouping functionality and similar endpoints. It allows the developer to\n    organize routes, exception handlers, middleware, and other web functionalities into separate, modular groups.\n\n    See [Blueprints](/en/guide/best-practices/blueprints) for more information.\n\n    Args:\n        name (str): The name of the blueprint.\n        url_prefix (Optional[str]): The URL prefix for all routes defined on this blueprint.\n        host (Optional[Union[List[str], str]]): Host or list of hosts that this blueprint should respond to.\n        version (Optional[Union[int, str, float]]): Version number of the API implemented by this blueprint.\n        strict_slashes (Optional[bool]): Whether or not the URL should end with a slash.\n        version_prefix (str): Prefix for the version. Default is \"/v\".\n    \"\"\"  # noqa: E501\n\n    __slots__ = (\n        \"_apps\",\n        \"_future_commands\",\n        \"_future_routes\",\n        \"_future_statics\",\n        \"_future_middleware\",\n        \"_future_listeners\",\n        \"_future_exceptions\",\n        \"_future_signals\",\n        \"_allow_route_overwrite\",\n        \"copied_from\",\n        \"ctx\",\n        \"exceptions\",\n        \"host\",\n        \"listeners\",\n        \"middlewares\",\n        \"routes\",\n        \"statics\",\n        \"strict_slashes\",\n        \"url_prefix\",\n        \"version\",\n        \"version_prefix\",\n        \"websocket_routes\",\n    )\n\n    def __init__(\n        self,\n        name: str,\n        url_prefix: str | None = None,\n        host: list[str] | str | None = None,\n        version: int | str | float | None = None,\n        strict_slashes: bool | None = None,\n        version_prefix: str = \"/v\",\n    ):\n        super().__init__(name=name)\n        self.reset()\n        self._allow_route_overwrite = False\n        self.copied_from = \"\"\n        self.ctx = SimpleNamespace()\n        self.host = host\n        self.strict_slashes = strict_slashes\n        self.url_prefix = (\n            url_prefix[:-1]\n            if url_prefix and url_prefix.endswith(\"/\")\n            else url_prefix\n        )\n        self.version = version\n        self.version_prefix = version_prefix\n\n    def __repr__(self) -> str:\n        args = \", \".join(\n            [\n                f'{attr}=\"{getattr(self, attr)}\"'\n                if isinstance(getattr(self, attr), str)\n                else f\"{attr}={getattr(self, attr)}\"\n                for attr in (\n                    \"name\",\n                    \"url_prefix\",\n                    \"host\",\n                    \"version\",\n                    \"strict_slashes\",\n                )\n            ]\n        )\n        return f\"Blueprint({args})\"\n\n    @property\n    def apps(self) -> set[Sanic]:\n        \"\"\"Get the set of apps that this blueprint is registered to.\n\n        Returns:\n            Set[Sanic]: Set of apps that this blueprint is registered to.\n\n        Raises:\n            SanicException: If the blueprint has not yet been registered to\n                an app.\n        \"\"\"\n        if not self._apps:\n            raise SanicException(\n                f\"{self} has not yet been registered to an app\"\n            )\n        return self._apps\n\n    @property\n    def registered(self) -> bool:\n        \"\"\"Check if the blueprint has been registered to an app.\n\n        Returns:\n            bool: `True` if the blueprint has been registered to an app,\n                `False` otherwise.\n        \"\"\"\n        return bool(self._apps)\n\n    exception = lazy(BaseSanic.exception)\n    listener = lazy(BaseSanic.listener)\n    middleware = lazy(BaseSanic.middleware)\n    route = lazy(BaseSanic.route)\n    signal = lazy(BaseSanic.signal)\n    static = lazy(BaseSanic.static, as_decorator=False)\n\n    def reset(self) -> None:\n        \"\"\"Reset the blueprint to its initial state.\"\"\"\n        self._apps: set[Sanic] = set()\n        self._allow_route_overwrite = False\n        self.exceptions: list[RouteHandler] = []\n        self.listeners: dict[str, list[ListenerType[Any]]] = {}\n        self.middlewares: list[MiddlewareType] = []\n        self.routes: list[Route] = []\n        self.statics: list[RouteHandler] = []\n        self.websocket_routes: list[Route] = []\n\n    def copy(\n        self,\n        name: str,\n        url_prefix: str | Default | None = _default,\n        version: int | str | float | Default | None = _default,\n        version_prefix: str | Default = _default,\n        allow_route_overwrite: bool | Default = _default,\n        strict_slashes: bool | Default | None = _default,\n        with_registration: bool = True,\n        with_ctx: bool = False,\n    ):\n        \"\"\"Copy a blueprint instance with some optional parameters to override the values of attributes in the old instance.\n\n        Args:\n            name (str): Unique name of the blueprint.\n            url_prefix (Optional[Union[str, Default]]): URL to be prefixed before all route URLs.\n            version (Optional[Union[int, str, float, Default]]): Blueprint version.\n            version_prefix (Union[str, Default]): The prefix of the version number shown in the URL.\n            allow_route_overwrite (Union[bool, Default]): Whether to allow route overwrite or not.\n            strict_slashes (Optional[Union[bool, Default]]): Enforce the API URLs are requested with a trailing \"/*\".\n            with_registration (bool): Whether to register the new blueprint instance with Sanic apps that were registered with the old instance or not. Default is `True`.\n            with_ctx (bool): Whether the ``ctx`` will be copied or not. Default is `False`.\n\n        Returns:\n            Blueprint: A new Blueprint instance with the specified attributes.\n        \"\"\"  # noqa: E501\n\n        attrs_backup = {\n            \"_apps\": self._apps,\n            \"routes\": self.routes,\n            \"websocket_routes\": self.websocket_routes,\n            \"middlewares\": self.middlewares,\n            \"exceptions\": self.exceptions,\n            \"listeners\": self.listeners,\n            \"statics\": self.statics,\n        }\n\n        self.reset()\n        new_bp = deepcopy(self)\n        new_bp.name = name\n        new_bp.copied_from = self.name\n\n        if not isinstance(url_prefix, Default):\n            new_bp.url_prefix = url_prefix\n        if not isinstance(version, Default):\n            new_bp.version = version\n        if not isinstance(strict_slashes, Default):\n            new_bp.strict_slashes = strict_slashes\n        if not isinstance(version_prefix, Default):\n            new_bp.version_prefix = version_prefix\n        if not isinstance(allow_route_overwrite, Default):\n            new_bp._allow_route_overwrite = allow_route_overwrite\n\n        for key, value in attrs_backup.items():\n            setattr(self, key, value)\n\n        if with_registration and self._apps:\n            if new_bp._future_statics:\n                raise SanicException(\n                    \"Static routes registered with the old blueprint instance,\"\n                    \" cannot be registered again.\"\n                )\n            for app in self._apps:\n                app.blueprint(new_bp)\n\n        if not with_ctx:\n            new_bp.ctx = SimpleNamespace()\n\n        return new_bp\n\n    @staticmethod\n    def group(\n        *blueprints: Blueprint | BlueprintGroup,\n        url_prefix: str | None = None,\n        version: int | str | float | None = None,\n        strict_slashes: bool | None = None,\n        version_prefix: str = \"/v\",\n        name_prefix: str | None = \"\",\n    ) -> BlueprintGroup:\n        \"\"\"Group multiple blueprints (or other blueprint groups) together.\n\n        Gropuping blueprings is a method for modularizing and organizing\n        your application's code. This can be a powerful tool for creating\n        reusable components, logically structuring your application code,\n        and easily maintaining route definitions in bulk.\n\n        This is the preferred way to group multiple blueprints together.\n\n        Args:\n            blueprints (Union[Blueprint, BlueprintGroup]): Blueprints to be\n                registered as a group.\n            url_prefix (Optional[str]): URL route to be prepended to all\n                sub-prefixes. Default is `None`.\n            version (Optional[Union[int, str, float]]): API Version to be\n                used for Blueprint group. Default is `None`.\n            strict_slashes (Optional[bool]): Indicate strict slash\n                termination behavior for URL. Default is `None`.\n            version_prefix (str): Prefix to be used for the version in the\n                URL. Default is \"/v\".\n            name_prefix (Optional[str]): Prefix to be used for the name of\n                the blueprints in the group. Default is an empty string.\n\n        Returns:\n            BlueprintGroup: A group of blueprints.\n\n        Example:\n            The resulting group will have the URL prefixes\n            `'/v2/bp1'` and `'/v2/bp2'` for bp1 and bp2, respectively.\n            ```python\n            bp1 = Blueprint('bp1', url_prefix='/bp1')\n            bp2 = Blueprint('bp2', url_prefix='/bp2')\n            group = group(bp1, bp2, version=2)\n            ```\n        \"\"\"\n\n        def chain(nested) -> Iterable[Blueprint]:\n            \"\"\"Iterate through nested blueprints\"\"\"\n            for i in nested:\n                if isinstance(i, (list, tuple)):\n                    yield from chain(i)\n                else:\n                    yield i\n\n        bps = BlueprintGroup(\n            url_prefix=url_prefix,\n            version=version,\n            strict_slashes=strict_slashes,\n            version_prefix=version_prefix,\n            name_prefix=name_prefix,\n        )\n        for bp in chain(blueprints):\n            bps.append(bp)\n        return bps\n\n    def register(self, app, options):\n        \"\"\"Register the blueprint to the sanic app.\n\n        Args:\n            app (Sanic): Sanic app to register the blueprint to.\n            options (dict): Options to be passed to the blueprint.\n        \"\"\"\n\n        self._apps.add(app)\n        url_prefix = options.get(\"url_prefix\", self.url_prefix)\n        opt_version = options.get(\"version\", None)\n        opt_strict_slashes = options.get(\"strict_slashes\", None)\n        opt_version_prefix = options.get(\"version_prefix\", self.version_prefix)\n        opt_name_prefix = options.get(\"name_prefix\", None)\n        error_format = options.get(\n            \"error_format\", app.config.FALLBACK_ERROR_FORMAT\n        )\n\n        routes = []\n        middleware = []\n        exception_handlers = []\n        listeners = defaultdict(list)\n        registered = set()\n\n        # Routes\n        for future in self._future_routes:\n            # Prepend the blueprint URI prefix if available\n            uri = self._setup_uri(future.uri, url_prefix)\n\n            route_error_format = (\n                future.error_format if future.error_format else error_format\n            )\n\n            version_prefix = self.version_prefix\n            for prefix in (\n                future.version_prefix,\n                opt_version_prefix,\n            ):\n                if prefix and prefix != \"/v\":\n                    version_prefix = prefix\n                    break\n\n            version = self._extract_value(\n                future.version, opt_version, self.version\n            )\n            strict_slashes = self._extract_value(\n                future.strict_slashes, opt_strict_slashes, self.strict_slashes\n            )\n\n            name = future.name\n            if opt_name_prefix:\n                name = f\"{opt_name_prefix}_{future.name}\"\n            name = app.generate_name(name)\n            host = future.host or self.host\n            if isinstance(host, list):\n                host = tuple(host)\n\n            apply_route = FutureRoute(\n                future.handler,\n                uri,\n                future.methods,\n                host,\n                strict_slashes,\n                future.stream,\n                version,\n                name,\n                future.ignore_body,\n                future.websocket,\n                future.subprotocols,\n                future.unquote,\n                future.static,\n                version_prefix,\n                route_error_format,\n                future.route_context,\n            )\n\n            if (self, apply_route) in app._future_registry:\n                continue\n\n            registered.add(apply_route)\n            route = app._apply_route(\n                apply_route, overwrite=self._allow_route_overwrite\n            )\n\n            # If it is a copied BP, then make sure all of the names of routes\n            # matchup with the new BP name\n            if self.copied_from:\n                for r in route:\n                    r.name = r.name.replace(self.copied_from, self.name)\n                    r.extra.ident = r.extra.ident.replace(\n                        self.copied_from, self.name\n                    )\n\n            operation = (\n                routes.extend if isinstance(route, list) else routes.append\n            )\n            operation(route)\n\n        # Static Files\n        for future in self._future_statics:\n            # Prepend the blueprint URI prefix if available\n            uri = self._setup_uri(future.uri, url_prefix)\n            apply_route = FutureStatic(uri, *future[1:])\n\n            if (self, apply_route) in app._future_registry:\n                continue\n\n            registered.add(apply_route)\n            route = app._apply_static(apply_route)\n            routes.append(route)\n\n        route_names = [route.name for route in routes if route]\n\n        if route_names:\n            # Middleware\n            for future in self._future_middleware:\n                if (self, future) in app._future_registry:\n                    continue\n                middleware.append(app._apply_middleware(future, route_names))\n\n            # Exceptions\n            for future in self._future_exceptions:\n                if (self, future) in app._future_registry:\n                    continue\n                exception_handlers.append(\n                    app._apply_exception_handler(future, route_names)\n                )\n\n        # Event listeners\n        for future in self._future_listeners:\n            if (self, future) in app._future_registry:\n                continue\n            listeners[future.event].append(app._apply_listener(future))\n\n        # Signals\n        for future in self._future_signals:\n            if (self, future) in app._future_registry:\n                continue\n            future.condition.update({\"__blueprint__\": self.name})\n            # Force exclusive to be False\n            app._apply_signal(\n                FutureSignal(\n                    future.handler,\n                    future.event,\n                    future.condition,\n                    False,\n                    future.priority,\n                )\n            )\n\n        self.routes += [route for route in routes if isinstance(route, Route)]\n        self.websocket_routes += [\n            route for route in self.routes if route.extra.websocket\n        ]\n        self.middlewares += middleware\n        self.exceptions += exception_handlers\n        self.listeners.update(dict(listeners))\n\n        if self.registered:\n            self.register_futures(\n                self.apps,\n                self,\n                chain(\n                    registered,\n                    self._future_middleware,\n                    self._future_exceptions,\n                    self._future_listeners,\n                    self._future_signals,\n                ),\n            )\n\n        if self._future_commands:\n            raise SanicException(\n                \"Registering commands with blueprints is not supported.\"\n            )\n\n    async def dispatch(self, *args, **kwargs):\n        \"\"\"Dispatch a signal event\n\n        Args:\n            *args: Arguments to be passed to the signal event.\n            **kwargs: Keyword arguments to be passed to the signal event.\n        \"\"\"\n        condition = kwargs.pop(\"condition\", {})\n        condition.update({\"__blueprint__\": self.name})\n        kwargs[\"condition\"] = condition\n        return await asyncio.gather(\n            *[app.dispatch(*args, **kwargs) for app in self.apps]\n        )\n\n    def event(\n        self,\n        event: str,\n        timeout: int | float | None = None,\n        *,\n        condition: dict[str, Any] | None = None,\n    ):\n        \"\"\"Wait for a signal event to be dispatched.\n\n        Args:\n            event (str): Name of the signal event.\n            timeout (Optional[Union[int, float]]): Timeout for the event to be\n                dispatched.\n            condition: If provided, method will only return when the signal\n                is dispatched with the given condition.\n\n        Returns:\n            Awaitable: Awaitable for the event to be dispatched.\n        \"\"\"\n        if condition is None:\n            condition = {}\n        condition.update({\"__blueprint__\": self.name})\n\n        waiters = []\n        for app in self.apps:\n            waiter = app.signal_router.get_waiter(\n                event, condition, exclusive=False\n            )\n            if not waiter:\n                raise NotFound(\"Could not find signal %s\" % event)\n            waiters.append(waiter)\n\n        return self._event(waiters, timeout)\n\n    async def _event(self, waiters, timeout):\n        done, pending = await asyncio.wait(\n            [asyncio.create_task(waiter.wait()) for waiter in waiters],\n            return_when=asyncio.FIRST_COMPLETED,\n            timeout=timeout,\n        )\n        for task in pending:\n            task.cancel()\n        if not done:\n            raise TimeoutError()\n        (finished_task,) = done\n        return finished_task.result()\n\n    @staticmethod\n    def _extract_value(*values):\n        value = values[-1]\n        for v in values:\n            if v is not None:\n                value = v\n                break\n        return value\n\n    @staticmethod\n    def _setup_uri(base: str, prefix: str | None):\n        uri = base\n        if prefix:\n            uri = prefix\n            if base.startswith(\"/\") and prefix.endswith(\"/\"):\n                uri += base[1:]\n            else:\n                uri += base\n\n        return uri[1:] if uri.startswith(\"//\") else uri\n\n    @staticmethod\n    def register_futures(\n        apps: set[Sanic], bp: Blueprint, futures: Sequence[tuple[Any, ...]]\n    ):\n        \"\"\"Register futures to the apps.\n\n        Args:\n            apps (Set[Sanic]): Set of apps to register the futures to.\n            bp (Blueprint): Blueprint that the futures belong to.\n            futures (Sequence[Tuple[Any, ...]]): Sequence of futures to be\n                registered.\n        \"\"\"\n\n        for app in apps:\n            app._future_registry.update({(bp, item) for item in futures})\n\n\nbpg_base = MutableSequence[Blueprint]\n\n\nclass BlueprintGroup(bpg_base):\n    \"\"\"This class provides a mechanism to implement a Blueprint Group.\n\n    The `BlueprintGroup` class allows grouping blueprints under a common\n    URL prefix, version, and other shared attributes. It integrates with\n    Sanic's Blueprint system, offering a custom iterator to treat an\n    object of this class as a list/tuple.\n\n    Although possible to instantiate a group directly, it is recommended\n    to use the `Blueprint.group` method to create a group of blueprints.\n\n    Args:\n        url_prefix (Optional[str]): URL to be prefixed before all the\n            Blueprint Prefixes. Default is `None`.\n        version (Optional[Union[int, str, float]]): API Version for the\n            blueprint group, inherited by each Blueprint. Default is `None`.\n        strict_slashes (Optional[bool]): URL Strict slash behavior\n            indicator. Default is `None`.\n        version_prefix (str): Prefix for the version in the URL.\n            Default is `\"/v\"`.\n        name_prefix (Optional[str]): Prefix for the name of the blueprints\n            in the group. Default is an empty string.\n\n    Examples:\n        ```python\n        bp1 = Blueprint(\"bp1\", url_prefix=\"/bp1\")\n        bp2 = Blueprint(\"bp2\", url_prefix=\"/bp2\")\n\n        bp3 = Blueprint(\"bp3\", url_prefix=\"/bp4\")\n        bp4 = Blueprint(\"bp3\", url_prefix=\"/bp4\")\n\n\n        group1 = Blueprint.group(bp1, bp2)\n        group2 = Blueprint.group(bp3, bp4, version_prefix=\"/api/v\", version=\"1\")\n\n\n        @bp1.on_request\n        async def bp1_only_middleware(request):\n            print(\"applied on Blueprint : bp1 Only\")\n\n\n        @bp1.route(\"/\")\n        async def bp1_route(request):\n            return text(\"bp1\")\n\n\n        @bp2.route(\"/<param>\")\n        async def bp2_route(request, param):\n            return text(param)\n\n\n        @bp3.route(\"/\")\n        async def bp3_route(request):\n            return text(\"bp3\")\n\n\n        @bp4.route(\"/<param>\")\n        async def bp4_route(request, param):\n            return text(param)\n\n\n        @group1.on_request\n        async def group_middleware(request):\n            print(\"common middleware applied for both bp1 and bp2\")\n\n\n        # Register Blueprint group under the app\n        app.blueprint(group1)\n        app.blueprint(group2)\n        ```\n    \"\"\"  # noqa: E501\n\n    __slots__ = (\n        \"_blueprints\",\n        \"_url_prefix\",\n        \"_version\",\n        \"_strict_slashes\",\n        \"_version_prefix\",\n        \"_name_prefix\",\n    )\n\n    def __init__(\n        self,\n        url_prefix: str | None = None,\n        version: int | str | float | None = None,\n        strict_slashes: bool | None = None,\n        version_prefix: str = \"/v\",\n        name_prefix: str | None = \"\",\n    ):\n        self._blueprints: list[Blueprint] = []\n        self._url_prefix = url_prefix\n        self._version = version\n        self._version_prefix = version_prefix\n        self._strict_slashes = strict_slashes\n        self._name_prefix = name_prefix\n\n    @property\n    def url_prefix(self) -> int | str | float | None:\n        \"\"\"The URL prefix for the Blueprint Group.\n\n        Returns:\n            Optional[Union[int, str, float]]: URL prefix for the Blueprint\n                Group.\n        \"\"\"\n        return self._url_prefix\n\n    @property\n    def blueprints(self) -> list[Blueprint]:\n        \"\"\"A list of all the available blueprints under this group.\n\n        Returns:\n            List[Blueprint]: List of all the available blueprints under\n                this group.\n        \"\"\"\n        return self._blueprints\n\n    @property\n    def version(self) -> str | int | float | None:\n        \"\"\"API Version for the Blueprint Group, if any.\n\n        Returns:\n            Optional[Union[str, int, float]]: API Version for the Blueprint\n        \"\"\"\n        return self._version\n\n    @property\n    def strict_slashes(self) -> bool | None:\n        \"\"\"Whether to enforce strict slashes for the Blueprint Group.\n\n        Returns:\n            Optional[bool]: Whether to enforce strict slashes for the\n        \"\"\"\n        return self._strict_slashes\n\n    @property\n    def version_prefix(self) -> str:\n        \"\"\"Version prefix for the Blueprint Group.\n\n        Returns:\n            str: Version prefix for the Blueprint Group.\n        \"\"\"\n        return self._version_prefix\n\n    @property\n    def name_prefix(self) -> str | None:\n        \"\"\"Name prefix for the Blueprint Group.\n\n        This is mainly needed when blueprints are copied in order to\n        avoid name conflicts.\n\n        Returns:\n            Optional[str]: Name prefix for the Blueprint Group.\n        \"\"\"\n        return self._name_prefix\n\n    def __iter__(self) -> Iterator[Blueprint]:\n        \"\"\"Iterate over the list of blueprints in the group.\n\n        Returns:\n            Iterator[Blueprint]: Iterator for the list of blueprints in\n        \"\"\"\n        return iter(self._blueprints)\n\n    @overload\n    def __getitem__(self, item: int) -> Blueprint: ...\n\n    @overload\n    def __getitem__(self, item: slice) -> MutableSequence[Blueprint]: ...\n\n    def __getitem__(\n        self, item: int | slice\n    ) -> Blueprint | MutableSequence[Blueprint]:\n        \"\"\"Get the Blueprint object at the specified index.\n\n        This method returns a blueprint inside the group specified by\n        an index value. This will enable indexing, splice and slicing\n        of the blueprint group like we can do with regular list/tuple.\n\n        This method is provided to ensure backward compatibility with\n        any of the pre-existing usage that might break.\n\n        Returns:\n            Blueprint: Blueprint object at the specified index.\n\n        Raises:\n            IndexError: If the index is out of range.\n        \"\"\"\n        return self._blueprints[item]\n\n    @overload\n    def __setitem__(self, index: int, item: Blueprint) -> None: ...\n\n    @overload\n    def __setitem__(self, index: slice, item: Iterable[Blueprint]) -> None: ...\n\n    def __setitem__(\n        self,\n        index: int | slice,\n        item: Blueprint | Iterable[Blueprint],\n    ) -> None:\n        \"\"\"Set the Blueprint object at the specified index.\n\n        Abstract method implemented to turn the `BlueprintGroup` class\n        into a list like object to support all the existing behavior.\n\n        This method is used to perform the list's indexed setter operation.\n\n        Args:\n            index (int): Index to use for removing a new Blueprint item\n            item (Blueprint): New `Blueprint` object.\n\n        Returns:\n            None\n\n        Raises:\n            IndexError: If the index is out of range.\n        \"\"\"\n        if isinstance(index, int):\n            if not isinstance(item, Blueprint):\n                raise TypeError(\"Expected a Blueprint instance\")\n            self._blueprints[index] = item\n        elif isinstance(index, slice):\n            if not isinstance(item, Iterable):\n                raise TypeError(\"Expected an iterable of Blueprint instances\")\n            self._blueprints[index] = list(item)\n        else:\n            raise TypeError(\"Index must be int or slice\")\n\n    @overload\n    def __delitem__(self, index: int) -> None: ...\n\n    @overload\n    def __delitem__(self, index: slice) -> None: ...\n\n    def __delitem__(self, index: int | slice) -> None:\n        \"\"\"Delete the Blueprint object at the specified index.\n\n        Abstract method implemented to turn the `BlueprintGroup` class\n        into a list like object to support all the existing behavior.\n\n        This method is used to delete an item from the list of blueprint\n        groups like it can be done on a regular list with index.\n\n        Args:\n            index (int): Index to use for removing a new Blueprint item\n\n        Returns:\n            None\n\n        Raises:\n            IndexError: If the index is out of range.\n        \"\"\"\n        del self._blueprints[index]\n\n    def __len__(self) -> int:\n        \"\"\"Get the Length of the blueprint group object.\n\n        Returns:\n            int: Length of the blueprint group object.\n        \"\"\"\n        return len(self._blueprints)\n\n    def append(self, value: Blueprint) -> None:\n        \"\"\"Add a new Blueprint object to the group.\n\n        The Abstract class `MutableSequence` leverages this append method to\n        perform the `BlueprintGroup.append` operation.\n\n        Args:\n            value (Blueprint): New `Blueprint` object.\n\n        Returns:\n            None\n        \"\"\"\n        self._blueprints.append(value)\n\n    def exception(self, *exceptions: Exception, **kwargs) -> Callable:\n        \"\"\"Decorate a function to handle exceptions for all blueprints in the group.\n\n        In case of nested Blueprint Groups, the same handler is applied\n        across each of the Blueprints recursively.\n\n        Args:\n            *exceptions (Exception): Exceptions to handle\n            **kwargs (dict): Optional Keyword arg to use with Middleware\n\n        Returns:\n            Partial function to apply the middleware\n\n        Examples:\n            ```python\n            bp1 = Blueprint(\"bp1\", url_prefix=\"/bp1\")\n            bp2 = Blueprint(\"bp2\", url_prefix=\"/bp2\")\n            group1 = Blueprint.group(bp1, bp2)\n\n            @group1.exception(Exception)\n            def handler(request, exception):\n                return text(\"Exception caught\")\n            ```\n        \"\"\"  # noqa: E501\n\n        def register_exception_handler_for_blueprints(fn):\n            for blueprint in self.blueprints:\n                blueprint.exception(*exceptions, **kwargs)(fn)\n\n        return register_exception_handler_for_blueprints\n\n    def insert(self, index: int, item: Blueprint) -> None:\n        \"\"\"Insert a new Blueprint object to the group at the specified index.\n\n        The Abstract class `MutableSequence` leverages this insert method to\n        perform the `BlueprintGroup.append` operation.\n\n        Args:\n            index (int): Index to use for removing a new Blueprint item\n            item (Blueprint): New `Blueprint` object.\n\n        Returns:\n            None\n        \"\"\"\n        self._blueprints.insert(index, item)\n\n    def middleware(self, *args, **kwargs):\n        \"\"\"A decorator that can be used to implement a Middleware for all blueprints in the group.\n\n        In case of nested Blueprint Groups, the same middleware is applied\n        across each of the Blueprints recursively.\n\n        Args:\n            *args (Optional): Optional positional Parameters to be use middleware\n            **kwargs (Optional): Optional Keyword arg to use with Middleware\n\n        Returns:\n            Partial function to apply the middleware\n        \"\"\"  # noqa: E501\n\n        def register_middleware_for_blueprints(fn):\n            for blueprint in self.blueprints:\n                blueprint.middleware(fn, *args, **kwargs)\n\n        if args and callable(args[0]):\n            fn = args[0]\n            args = list(args)[1:]\n            return register_middleware_for_blueprints(fn)\n        return register_middleware_for_blueprints\n\n    def on_request(self, middleware=None):\n        \"\"\"Convenience method to register a request middleware for all blueprints in the group.\n\n        Args:\n            middleware (Optional): Optional positional Parameters to be use middleware\n\n        Returns:\n            Partial function to apply the middleware\n        \"\"\"  # noqa: E501\n        if callable(middleware):\n            return self.middleware(middleware, \"request\")\n        else:\n            return partial(self.middleware, attach_to=\"request\")\n\n    def on_response(self, middleware=None):\n        \"\"\"Convenience method to register a response middleware for all blueprints in the group.\n\n        Args:\n            middleware (Optional): Optional positional Parameters to be use middleware\n\n        Returns:\n            Partial function to apply the middleware\n        \"\"\"  # noqa: E501\n        if callable(middleware):\n            return self.middleware(middleware, \"response\")\n        else:\n            return partial(self.middleware, attach_to=\"response\")\n"
  },
  {
    "path": "sanic/cli/__init__.py",
    "content": ""
  },
  {
    "path": "sanic/cli/app.py",
    "content": "from __future__ import annotations\n\nimport os\nimport shutil\nimport sys\n\nfrom argparse import Namespace\nfrom functools import partial\nfrom textwrap import indent\n\nfrom sanic.app import Sanic\nfrom sanic.application.logo import get_logo\nfrom sanic.cli.arguments import Group\nfrom sanic.cli.base import SanicArgumentParser, SanicHelpFormatter\nfrom sanic.cli.console import SanicREPL\nfrom sanic.cli.daemon import (\n    kill_daemon,\n    make_kill_parser,\n    make_restart_parser,\n    make_status_parser,\n    resolve_target,\n    restart_daemon,\n    status_daemon,\n    stop_daemon,\n)\nfrom sanic.cli.executor import Executor, make_executor_parser\nfrom sanic.cli.inspector import make_inspector_parser\nfrom sanic.cli.inspector_client import InspectorClient\nfrom sanic.compat import OS_IS_WINDOWS\nfrom sanic.helpers import _default, is_atty\nfrom sanic.log import error_logger\nfrom sanic.worker.daemon import Daemon, DaemonError\nfrom sanic.worker.loader import AppLoader\n\n\nclass SanicCLI:\n    DESCRIPTION_SHORT = indent(\n        f\"\"\"\n{get_logo(True)}\n\nUsage:\n    sanic <target> [options]       Run a Sanic application\n    sanic <target> exec <cmd>      Execute a command in app context\n    sanic inspect [options]        Inspect a running instance\n    sanic help [--full]            Show help (--full for all options)\n\nExamples:\n    sanic path.to.server:app       Run app\n    sanic path.to.server --dev     Run in development mode\n    sanic ./static --simple        Serve static files\n\"\"\",\n        prefix=\" \",\n    )\n\n    DESCRIPTION_SHORT_FOOTER = \"\"\"\n(additional options available)\n\nFor complete options and documentation:\n    sanic help --full\n\"\"\"\n\n    DESCRIPTION_FULL = indent(\n        f\"\"\"\n{get_logo(True)}\n\nTo start running a Sanic application, provide a path to the module, where\napp is a Sanic() instance in the global scope:\n\n    $ sanic path.to.server:app\n\nIf the Sanic instance variable is called 'app', you can leave off the last\npart, and only provide a path to the module where the instance is:\n\n    $ sanic path.to.server\n\nOr, a path to a callable that returns a Sanic() instance:\n\n    $ sanic path.to.factory:create_app\n\nOr, a path to a directory to run as a simple HTTP server:\n\n    $ sanic ./path/to/static\n\nAdditional commands:\n\n    $ sanic inspect ...           Inspect a running Sanic instance\n    $ sanic path.to.app exec ...  Run app commands\n    $ sanic path.to.app status    Check if app daemon is running\n    $ sanic path.to.app restart   Restart app daemon (future use)\n    $ sanic path.to.app stop      Stop app daemon\n\nAdvanced daemon management:\n\n    $ sanic kill (--pid PID | --pidfile PATH)      Force kill (SIGKILL)\n    $ sanic status (--pid PID | --pidfile PATH)    Check status\n    $ sanic restart (--pid PID | --pidfile PATH)   Restart (future use)\n\"\"\",\n        prefix=\" \",\n    )\n\n    def __init__(self) -> None:\n        width = shutil.get_terminal_size().columns\n        self.parser = SanicArgumentParser(\n            prog=\"sanic\",\n            description=self.DESCRIPTION_SHORT,\n            formatter_class=lambda prog: SanicHelpFormatter(\n                prog,\n                max_help_position=36 if width > 96 else 24,\n                indent_increment=4,\n                width=None,\n            ),\n        )\n        self.parser._positionals.title = \"Required\\n========\\n  Positional\"\n        self.parser._optionals.title = \"Optional\\n========\\n  General\"\n        self.main_process = (\n            os.environ.get(\"SANIC_RELOADER_PROCESS\", \"\") != \"true\"\n        )\n        self.args: Namespace = Namespace()\n        self.groups: list[Group] = []\n        self.run_mode = \"serve\"\n\n    def attach(self):\n        if len(sys.argv) > 1 and sys.argv[1] == \"help\":\n            self.run_mode = \"help\"\n            return\n\n        if len(sys.argv) == 2 and sys.argv[1] in (\"-h\", \"--help\"):\n            self.run_mode = \"help\"\n            return\n\n        if len(sys.argv) > 1 and sys.argv[1] == \"inspect\":\n            self.run_mode = \"inspect\"\n            self.parser.description = get_logo(True)\n            make_inspector_parser(self.parser)\n            return\n\n        if not OS_IS_WINDOWS and len(sys.argv) > 1 and sys.argv[1] == \"kill\":\n            self.run_mode = \"kill\"\n            self.parser.description = get_logo(True)\n            make_kill_parser(self.parser)\n            return\n\n        if not OS_IS_WINDOWS and len(sys.argv) > 1 and sys.argv[1] == \"status\":\n            self.run_mode = \"status\"\n            self.parser.description = get_logo(True)\n            make_status_parser(self.parser)\n            return\n\n        if (\n            not OS_IS_WINDOWS\n            and len(sys.argv) > 1\n            and sys.argv[1] == \"restart\"\n        ):\n            self.run_mode = \"restart\"\n            self.parser.description = get_logo(True)\n            make_restart_parser(self.parser)\n            return\n\n        # Check for app-based daemon commands: sanic <app> status|restart|stop\n        if (\n            not OS_IS_WINDOWS\n            and len(sys.argv) > 2\n            and sys.argv[2]\n            in (\n                \"status\",\n                \"restart\",\n                \"stop\",\n            )\n        ):\n            self.run_mode = f\"app_{sys.argv[2]}\"\n\n        for group in Group._registry:\n            instance = group.create(self.parser)\n            instance.attach()\n            self.groups.append(instance)\n\n        if len(sys.argv) > 2 and sys.argv[2] == \"exec\":\n            self.run_mode = \"exec\"\n            self.parser.description = get_logo(True)\n            make_executor_parser(self.parser)\n\n    def run(self, parse_args=None):\n        if self.run_mode == \"inspect\":\n            self._inspector()\n            return\n\n        if self.run_mode == \"kill\":\n            self._kill()\n            return\n\n        if self.run_mode == \"status\":\n            self._status()\n            return\n\n        if self.run_mode == \"restart\":\n            self._restart()\n            return\n\n        if self.run_mode == \"help\":\n            self._help()\n            return\n\n        if self.run_mode.startswith(\"app_\"):\n            self._app_daemon_command()\n            return\n\n        legacy_version = False\n        if not parse_args:\n            # This is to provide backwards compat -v to display version\n            legacy_version = len(sys.argv) == 2 and sys.argv[-1] == \"-v\"\n            parse_args = [\"--version\"] if legacy_version else None\n        elif parse_args == [\"-v\"]:\n            parse_args = [\"--version\"]\n\n        if not legacy_version:\n            if self.run_mode == \"exec\":\n                parse_args = [\n                    a\n                    for a in (parse_args or sys.argv[1:])\n                    if a not in \"-h --help\".split()\n                ]\n            parsed, unknown = self.parser.parse_known_args(args=parse_args)\n            if unknown and parsed.factory:\n                for arg in unknown:\n                    if arg.startswith(\"--\"):\n                        self.parser.add_argument(arg.split(\"=\")[0])\n\n        if self.run_mode == \"exec\":\n            self.args, _ = self.parser.parse_known_args(args=parse_args)\n        else:\n            self.args = self.parser.parse_args(args=parse_args)\n        self._precheck()\n        app_loader = AppLoader(\n            self.args.target, self.args.factory, self.args.simple, self.args\n        )\n\n        try:\n            app = self._get_app(app_loader)\n            kwargs = self._build_run_kwargs()\n        except ValueError as e:\n            error_logger.exception(f\"Failed to run app: {e}\")\n        else:\n            if self.run_mode == \"exec\":\n                self._executor(app, kwargs)\n                return\n            elif self.run_mode != \"serve\":\n                raise ValueError(f\"Unknown run mode: {self.run_mode}\")\n\n            daemon = None\n            if getattr(self.args, \"daemon\", False):\n                daemon = self._setup_daemon(app.name)\n                if daemon:\n                    lines = [\"Starting Sanic in daemon mode...\"]\n                    if daemon.pidfile:\n                        lines.append(f\"  PID file: {daemon.pidfile}\")\n                    if daemon.logfile:\n                        lines.append(f\"  Logs: {daemon.logfile}\")\n                    print(\"\\n\".join(lines), flush=True)\n                    daemon.daemonize()\n\n            if self.args.repl:\n                self._repl(app)\n            for http_version in self.args.http:\n                app.prepare(**kwargs, version=http_version)\n\n            if daemon:\n                daemon.drop_privileges()\n\n            if self.args.single:\n                serve = Sanic.serve_single\n            else:\n                serve = partial(Sanic.serve, app_loader=app_loader)\n            serve(app)\n\n    def _inspector(self):\n        args = sys.argv[2:]\n        self.args, unknown = self.parser.parse_known_args(args=args)\n        if unknown:\n            for arg in unknown:\n                if arg.startswith(\"--\"):\n                    try:\n                        key, value = arg.split(\"=\")\n                        key = key.lstrip(\"-\")\n                    except ValueError:\n                        value = False if arg.startswith(\"--no-\") else True\n                        key = (\n                            arg.replace(\"--no-\", \"\")\n                            .lstrip(\"-\")\n                            .replace(\"-\", \"_\")\n                        )\n                    setattr(self.args, key, value)\n\n        kwargs = {**self.args.__dict__}\n        host = kwargs.pop(\"host\")\n        port = kwargs.pop(\"port\")\n        secure = kwargs.pop(\"secure\")\n        raw = kwargs.pop(\"raw\")\n        action = kwargs.pop(\"action\") or \"info\"\n        api_key = kwargs.pop(\"api_key\")\n        positional = kwargs.pop(\"positional\", None)\n        if action == \"<custom>\" and positional:\n            action = positional[0]\n            if len(positional) > 1:\n                kwargs[\"args\"] = positional[1:]\n        InspectorClient(host, port, secure, raw, api_key).do(action, **kwargs)\n\n    def _kill(self):\n        self.args = self.parser.parse_args(args=sys.argv[2:])\n        pid, pidfile = resolve_target(self.args.pid, self.args.pidfile)\n        kill_daemon(pid, pidfile)\n\n    def _status(self):\n        self.args = self.parser.parse_args(args=sys.argv[2:])\n        pid, pidfile = resolve_target(self.args.pid, self.args.pidfile)\n        status_daemon(pid, pidfile)\n\n    def _restart(self):\n        self.args = self.parser.parse_args(args=sys.argv[2:])\n        pid, _ = resolve_target(self.args.pid, self.args.pidfile)\n        restart_daemon(pid)\n\n    def _help(self):\n        full = \"--full\" in sys.argv\n        if full:\n            self.parser.description = self.DESCRIPTION_FULL\n        for group in Group._registry:\n            instance = group.create(self.parser)\n            instance.attach(short=not full)\n            self.groups.append(instance)\n        self.parser.print_help()\n        if not full:\n            print(self.DESCRIPTION_SHORT_FOOTER)\n\n    def _app_daemon_command(self):\n        \"\"\"Handle app-based daemon commands: sanic <app> status|restart|stop\"\"\"\n        command = self.run_mode.replace(\"app_\", \"\")\n        # Parse just the app target (first arg)\n        self.args = self.parser.parse_args(args=[sys.argv[1]])\n\n        app_loader = AppLoader(\n            self.args.target, self.args.factory, self.args.simple, self.args\n        )\n\n        try:\n            app = self._get_app(app_loader)\n        except (ImportError, ValueError) as e:\n            error_logger.error(f\"Failed to load app: {e}\")\n            sys.exit(1)\n\n        pidfile = Daemon.get_pidfile_path(app.name)\n        pid, pidfile = resolve_target(None, str(pidfile))\n\n        if command == \"status\":\n            status_daemon(pid, pidfile)\n        elif command == \"restart\":\n            restart_daemon(pid)\n        elif command == \"stop\":\n            force = \"-f\" in sys.argv or \"--force\" in sys.argv\n            stop_daemon(pid, pidfile, force)\n\n    def _executor(self, app: Sanic, kwargs: dict):\n        args = sys.argv[3:]\n        Executor(app, kwargs).run(self.args.command, args)\n\n    def _repl(self, app: Sanic):\n        if is_atty():\n\n            @app.main_process_ready\n            async def start_repl(app):\n                SanicREPL(app, self.args.repl).run()\n                await app._startup()\n\n        elif self.args.repl is True:\n            error_logger.error(\n                \"Can't start REPL in non-interactive mode. \"\n                \"You can only run with --repl in a TTY.\"\n            )\n\n    def _setup_daemon(self, app_name: str):\n        if OS_IS_WINDOWS:\n            error_logger.error(\n                \"Daemon mode is not supported on Windows. \"\n                \"Consider using a Windows service or nssm instead.\"\n            )\n            sys.exit(1)\n\n        if getattr(self.args, \"dev\", False) or getattr(\n            self.args, \"auto_reload\", False\n        ):\n            error_logger.error(\n                \"Daemon mode is not compatible with --dev or --auto-reload\"\n            )\n            sys.exit(1)\n\n        if getattr(self.args, \"repl\", False):\n            error_logger.error(\"Daemon mode is not compatible with --repl\")\n            sys.exit(1)\n\n        try:\n            daemon = Daemon(\n                pidfile=getattr(self.args, \"pidfile\", None) or \"auto\",\n                logfile=getattr(self.args, \"logfile\", None),\n                user=getattr(self.args, \"daemon_user\", None),\n                group=getattr(self.args, \"daemon_group\", None),\n                name=app_name,\n            )\n            return daemon\n        except DaemonError as e:\n            error_logger.error(f\"Daemon configuration error: {e}\")\n            sys.exit(1)\n\n    def _precheck(self):\n        # Custom TLS mismatch handling for better diagnostics\n        if self.main_process and (\n            # one of cert/key missing\n            bool(self.args.cert) != bool(self.args.key)\n            # new and old style self.args used together\n            or self.args.tls\n            and self.args.cert\n            # strict host checking without certs would always fail\n            or self.args.tlshost\n            and not self.args.tls\n            and not self.args.cert\n        ):\n            self.parser.print_usage(sys.stderr)\n            message = (\n                \"TLS certificates must be specified by either of:\\n\"\n                \"  --cert certdir/fullchain.pem --key certdir/privkey.pem\\n\"\n                \"  --tls certdir  (equivalent to the above)\"\n            )\n            error_logger.error(message)\n            sys.exit(1)\n\n    def _get_app(self, app_loader: AppLoader):\n        try:\n            app = app_loader.load()\n        except ImportError as e:\n            if app_loader.module_name.startswith(e.name):  # type: ignore\n                error_logger.error(\n                    f\"No module named {e.name} found.\\n\"\n                    \"  Example File: project/sanic_server.py -> app\\n\"\n                    \"  Example Module: project.sanic_server.app\"\n                )\n                error_logger.error(\n                    \"\\nThe error below might have caused the above one:\\n\"\n                    f\"{e.msg}\"\n                )\n                sys.exit(1)\n            else:\n                raise e\n        return app\n\n    def _build_run_kwargs(self):\n        for group in self.groups:\n            group.prepare(self.args)\n        ssl: None | dict | str | list = []\n        if self.args.tlshost:\n            ssl.append(None)\n        if self.args.cert is not None or self.args.key is not None:\n            ssl.append(dict(cert=self.args.cert, key=self.args.key))\n        if self.args.tls:\n            ssl += self.args.tls\n        if not ssl:\n            ssl = None\n        elif len(ssl) == 1 and ssl[0] is not None:\n            # Use only one cert, no TLSSelector.\n            ssl = ssl[0]\n\n        kwargs = {\n            \"access_log\": self.args.access_log,\n            \"coffee\": self.args.coffee,\n            \"debug\": self.args.debug,\n            \"fast\": self.args.fast,\n            \"host\": self.args.host,\n            \"motd\": self.args.motd,\n            \"noisy_exceptions\": self.args.noisy_exceptions,\n            \"port\": self.args.port,\n            \"ssl\": ssl,\n            \"unix\": self.args.unix,\n            \"verbosity\": self.args.verbosity or 0,\n            \"workers\": self.args.workers,\n            \"auto_tls\": self.args.auto_tls,\n            \"single_process\": self.args.single,\n        }\n\n        for maybe_arg in (\"auto_reload\", \"dev\"):\n            if getattr(self.args, maybe_arg, False):\n                kwargs[maybe_arg] = True\n\n        if self.args.dev and all(\n            arg not in sys.argv for arg in (\"--repl\", \"--no-repl\")\n        ):\n            self.args.repl = _default\n\n        if self.args.path:\n            kwargs[\"auto_reload\"] = True\n            kwargs[\"reload_dir\"] = self.args.path\n\n        return kwargs\n"
  },
  {
    "path": "sanic/cli/arguments.py",
    "content": "from __future__ import annotations\n\nfrom argparse import ArgumentParser, _ArgumentGroup\n\nfrom sanic_routing import __version__ as __routing_version__\n\nfrom sanic import __version__\nfrom sanic.compat import OS_IS_WINDOWS\nfrom sanic.http.constants import HTTP\n\n\nclass Group:\n    name: str | None\n    container: ArgumentParser | _ArgumentGroup\n    _registry: list[type[Group]] = []\n\n    def __init_subclass__(cls) -> None:\n        Group._registry.append(cls)\n\n    def __init__(self, parser: ArgumentParser, title: str | None):\n        self.parser = parser\n\n        if title:\n            self.container = self.parser.add_argument_group(title=f\"  {title}\")\n        else:\n            self.container = self.parser\n\n    @classmethod\n    def create(cls, parser: ArgumentParser):\n        instance = cls(parser, cls.name)\n        return instance\n\n    def add_bool_arguments(\n        self,\n        *args,\n        nullable=False,\n        help: str,\n        negative_help: str | None = None,\n        **kwargs,\n    ):\n        group = self.container.add_mutually_exclusive_group()\n\n        pos_help = help[0].upper() + help[1:]\n        neg_help = (\n            negative_help if negative_help else f\"Disable {help.lower()}\"\n        )\n\n        group.add_argument(\n            *args,\n            action=\"store_true\",\n            help=pos_help,\n            **kwargs,\n        )\n\n        group.add_argument(\n            \"--no-\" + args[0][2:],\n            *args[1:],\n            action=\"store_false\",\n            help=neg_help[0].upper() + neg_help[1:],\n            **kwargs,\n        )\n\n        if nullable:\n            group.set_defaults(**{args[0][2:].replace(\"-\", \"_\"): None})\n\n    def prepare(self, args) -> None: ...\n\n    def attach(self, short: bool = False) -> None: ...\n\n\nclass GeneralGroup(Group):\n    name = None\n\n    def attach(self, short: bool = False):\n        if short:\n            return\n\n        self.container.add_argument(\n            \"--version\",\n            action=\"version\",\n            version=f\"Sanic {__version__}; Routing {__routing_version__}\",\n        )\n\n        self.container.add_argument(\n            \"target\",\n            help=(\n                \"Path to your Sanic app instance.\\n\"\n                \"\\tExample: path.to.server:app\\n\"\n                \"If running a Simple Server, path to directory to serve.\\n\"\n                \"\\tExample: ./\\n\"\n                \"Additionally, this can be a path to a factory function\\n\"\n                \"that returns a Sanic app instance.\\n\"\n                \"\\tExample: path.to.server:create_app\\n\"\n            ),\n        )\n\n        choices = [\"serve\", \"exec\"]\n        help_text = (\n            \"Action to perform.\\n\"\n            \"\\tserve: Run the Sanic app [default]\\n\"\n            \"\\texec: Execute a command in the Sanic app context\\n\"\n        )\n        if not OS_IS_WINDOWS:\n            choices.extend([\"status\", \"restart\", \"stop\"])\n            help_text += (\n                \"\\tstatus: Check if daemon is running\\n\"\n                \"\\trestart: Restart daemon (future use)\\n\"\n                \"\\tstop: Stop daemon gracefully\\n\"\n            )\n        self.container.add_argument(\n            \"action\",\n            nargs=\"?\",\n            default=\"serve\",\n            choices=choices,\n            help=help_text,\n        )\n\n\nclass ApplicationGroup(Group):\n    name = \"Application\"\n\n    def attach(self, short: bool = False):\n        if short:\n            return\n\n        group = self.container.add_mutually_exclusive_group()\n        group.add_argument(\n            \"--factory\",\n            action=\"store_true\",\n            help=(\n                \"Treat app as an application factory, \"\n                \"i.e. a () -> <Sanic app> callable\"\n            ),\n        )\n        group.add_argument(\n            \"-s\",\n            \"--simple\",\n            dest=\"simple\",\n            action=\"store_true\",\n            help=(\n                \"Run Sanic as a Simple Server, and serve the contents of \"\n                \"a directory\\n(module arg should be a path)\"\n            ),\n        )\n        self.add_bool_arguments(\n            \"--repl\",\n            help=\"Run with an interactive shell session\",\n            negative_help=\"Disable interactive shell session\",\n        )\n\n\nclass SocketGroup(Group):\n    name = \"Socket binding\"\n\n    def attach(self, short: bool = False):\n        self.container.add_argument(\n            \"-H\",\n            \"--host\",\n            dest=\"host\",\n            type=str,\n            help=\"Host address [default 127.0.0.1]\",\n        )\n        self.container.add_argument(\n            \"-p\",\n            \"--port\",\n            dest=\"port\",\n            type=int,\n            help=\"Port to serve on [default 8000]\",\n        )\n        if not OS_IS_WINDOWS and not short:\n            self.container.add_argument(\n                \"-u\",\n                \"--unix\",\n                dest=\"unix\",\n                type=str,\n                default=\"\",\n                help=\"location of UNIX socket\",\n            )\n\n\nclass HTTPVersionGroup(Group):\n    name = \"HTTP version\"\n\n    def attach(self, short: bool = False):\n        if short:\n            return\n\n        http_values = [http.value for http in HTTP.__members__.values()]\n\n        self.container.add_argument(\n            \"--http\",\n            dest=\"http\",\n            action=\"append\",\n            choices=http_values,\n            type=int,\n            help=(\n                \"Which HTTP version to use: HTTP/1.1 or HTTP/3. Value should\\n\"\n                \"be either 1, or 3. [default 1]\"\n            ),\n        )\n        self.container.add_argument(\n            \"-1\",\n            dest=\"http\",\n            action=\"append_const\",\n            const=1,\n            help=(\"Run Sanic server using HTTP/1.1\"),\n        )\n        self.container.add_argument(\n            \"-3\",\n            dest=\"http\",\n            action=\"append_const\",\n            const=3,\n            help=(\"Run Sanic server using HTTP/3\"),\n        )\n\n    def prepare(self, args):\n        if not args.http:\n            args.http = [1]\n        args.http = tuple(sorted(set(map(HTTP, args.http)), reverse=True))\n\n\nclass TLSGroup(Group):\n    name = \"TLS certificate\"\n\n    def attach(self, short: bool = False):\n        if short:\n            return\n\n        self.container.add_argument(\n            \"--cert\",\n            dest=\"cert\",\n            type=str,\n            help=\"Location of fullchain.pem, bundle.crt or equivalent\",\n        )\n        self.container.add_argument(\n            \"--key\",\n            dest=\"key\",\n            type=str,\n            help=\"Location of privkey.pem or equivalent .key file\",\n        )\n        self.container.add_argument(\n            \"--tls\",\n            metavar=\"DIR\",\n            type=str,\n            action=\"append\",\n            help=(\n                \"TLS certificate folder with fullchain.pem and privkey.pem\\n\"\n                \"May be specified multiple times to choose multiple \"\n                \"certificates\"\n            ),\n        )\n        self.container.add_argument(\n            \"--tls-strict-host\",\n            dest=\"tlshost\",\n            action=\"store_true\",\n            help=\"Only allow clients that send an SNI matching server certs\",\n        )\n\n\nclass DevelopmentGroup(Group):\n    name = \"Development\"\n\n    def attach(self, short: bool = False):\n        if not short:\n            self.container.add_argument(\n                \"--debug\",\n                dest=\"debug\",\n                action=\"store_true\",\n                help=\"Run the server in debug mode\",\n            )\n            self.container.add_argument(\n                \"-r\",\n                \"--reload\",\n                \"--auto-reload\",\n                dest=\"auto_reload\",\n                action=\"store_true\",\n                help=\"Auto-reload on source changes\",\n            )\n            self.container.add_argument(\n                \"-R\",\n                \"--reload-dir\",\n                dest=\"path\",\n                action=\"append\",\n                help=\"Additional directories to watch for changes\",\n            )\n        self.container.add_argument(\n            \"-d\",\n            \"--dev\",\n            dest=\"dev\",\n            action=\"store_true\",\n            help=\"Run in development mode (debug + auto-reload)\",\n        )\n        if not short:\n            self.container.add_argument(\n                \"--auto-tls\",\n                dest=\"auto_tls\",\n                action=\"store_true\",\n                help=(\n                    \"Create a temporary TLS certificate for local development \"\n                    \"(requires mkcert or trustme)\"\n                ),\n            )\n\n\nclass WorkerGroup(Group):\n    name = \"Worker\"\n\n    def attach(self, short: bool = False):\n        if short:\n            return\n\n        group = self.container.add_mutually_exclusive_group()\n        group.add_argument(\n            \"-w\",\n            \"--workers\",\n            dest=\"workers\",\n            type=int,\n            default=1,\n            help=\"Number of worker processes [default 1]\",\n        )\n        group.add_argument(\n            \"--fast\",\n            dest=\"fast\",\n            action=\"store_true\",\n            help=\"Set the number of workers to max allowed\",\n        )\n        group.add_argument(\n            \"--single-process\",\n            dest=\"single\",\n            action=\"store_true\",\n            help=\"Do not use multiprocessing, run server in a single process\",\n        )\n        self.add_bool_arguments(\n            \"--access-logs\",\n            dest=\"access_log\",\n            help=\"display access logs\",\n            default=None,\n        )\n\n\nclass OutputGroup(Group):\n    name = \"Output\"\n\n    def attach(self, short: bool = False):\n        if short:\n            return\n\n        self.add_bool_arguments(\n            \"--coffee\",\n            dest=\"coffee\",\n            default=False,\n            help=\"Uhm, coffee?\",\n            negative_help=\"No coffee? Is that a typo?\",\n        )\n        self.add_bool_arguments(\n            \"--motd\",\n            dest=\"motd\",\n            default=True,\n            help=\"Show the startup display\",\n            negative_help=\"Disable the startup display\",\n        )\n        self.container.add_argument(\n            \"-v\",\n            \"--verbosity\",\n            action=\"count\",\n            help=\"Control logging noise, eg. -vv or --verbosity=2 [default 0]\",\n        )\n        self.add_bool_arguments(\n            \"--noisy-exceptions\",\n            dest=\"noisy_exceptions\",\n            help=\"Output stack traces for all exceptions\",\n            default=None,\n        )\n\n\nclass DaemonGroup(Group):\n    name = \"Daemon\"\n\n    def attach(self, short: bool = False):\n        if OS_IS_WINDOWS:\n            return\n\n        self.container.add_argument(\n            \"-D\",\n            \"--daemon\",\n            dest=\"daemon\",\n            action=\"store_true\",\n            help=\"Run server in background (auto-generated PID file)\",\n        )\n\n        if short:\n            return\n\n        self.container.add_argument(\n            \"--pidfile\",\n            dest=\"pidfile\",\n            type=str,\n            default=None,\n            help=\"Override auto-generated PID file path (requires --daemon)\",\n        )\n        self.container.add_argument(\n            \"--logfile\",\n            dest=\"logfile\",\n            type=str,\n            default=None,\n            help=\"Path to log file for daemon output (requires --daemon)\",\n        )\n        self.container.add_argument(\n            \"--user\",\n            dest=\"daemon_user\",\n            type=str,\n            default=None,\n            help=\"User to run daemon as (requires root)\",\n        )\n        self.container.add_argument(\n            \"--group\",\n            dest=\"daemon_group\",\n            type=str,\n            default=None,\n            help=\"Group to run daemon as (requires root)\",\n        )\n\n    def prepare(self, args):\n        if OS_IS_WINDOWS:\n            return\n\n        has_daemon_opts = getattr(args, \"pidfile\", None) or getattr(\n            args, \"logfile\", None\n        )\n        if has_daemon_opts and not getattr(args, \"daemon\", False):\n            raise SystemExit(\"Error: --pidfile and --logfile require --daemon\")\n"
  },
  {
    "path": "sanic/cli/base.py",
    "content": "from argparse import (\n    SUPPRESS,\n    Action,\n    ArgumentParser,\n    RawTextHelpFormatter,\n    _SubParsersAction,\n)\nfrom typing import Any\n\n\nclass SanicArgumentParser(ArgumentParser):\n    def _check_value(self, action: Action, value: Any) -> None:\n        if isinstance(action, SanicSubParsersAction):\n            return\n        super()._check_value(action, value)\n\n\nclass SanicHelpFormatter(RawTextHelpFormatter):\n    def add_usage(self, usage, actions, groups, prefix=None):\n        if not usage:\n            usage = SUPPRESS\n            # Add one linebreak, but not two\n            self.add_text(\"\\x1b[1A\")\n        super().add_usage(usage, actions, groups, prefix)\n\n\nclass SanicSubParsersAction(_SubParsersAction):\n    def __call__(self, parser, namespace, values, option_string=None):\n        self._name_parser_map\n        parser_name = values[0]\n        if parser_name not in self._name_parser_map:\n            self._name_parser_map[parser_name] = parser\n            values = [\"<custom>\", *values]\n\n        super().__call__(parser, namespace, values, option_string)\n"
  },
  {
    "path": "sanic/cli/console.py",
    "content": "from __future__ import annotations\n\nimport atexit\nimport concurrent.futures\nimport sys\nimport threading\nimport time\nimport traceback\n\n\ntry:\n    import termios\n\n    TERMIOS_AVAILABLE = True\nexcept ImportError:\n    TERMIOS_AVAILABLE = False\n    termios = None  # type: ignore\n\nfrom ast import PyCF_ALLOW_TOP_LEVEL_AWAIT\nfrom asyncio import iscoroutine, new_event_loop\nfrom code import InteractiveConsole\nfrom collections.abc import Sequence\nfrom contextlib import suppress\nfrom types import FunctionType\nfrom typing import Any, NamedTuple\n\nimport sanic\n\nfrom sanic import Request, Sanic\nfrom sanic.compat import Header\nfrom sanic.helpers import Default\nfrom sanic.http.constants import Stage\nfrom sanic.log import Colors\nfrom sanic.models.ctx_types import REPLContext\nfrom sanic.models.protocol_types import TransportProtocol\nfrom sanic.response.types import HTTPResponse\n\n\ntry:\n    from httpx import Client\n\n    HTTPX_AVAILABLE = True\n\n    class SanicClient(Client):\n        def __init__(self, app: Sanic):\n            base_url = app.get_server_location(\n                app.state.server_info[0].settings\n            )\n            super().__init__(base_url=base_url)\n\nexcept ImportError:\n    HTTPX_AVAILABLE = False\n\ntry:\n    import readline  # noqa\nexcept ImportError:\n    print(\n        \"Module 'readline' not available. History navigation will be limited.\",\n        file=sys.stderr,\n    )\n\nrepl_app: Sanic | None = None\nrepl_response: HTTPResponse | None = None\n\n\nclass REPLProtocol(TransportProtocol):\n    def __init__(self):\n        self.stage = Stage.IDLE\n        self.request_body = True\n\n    def respond(self, response):\n        global repl_response\n        repl_response = response\n        response.stream = self\n        return response\n\n    async def send(self, data, end_stream): ...\n\n\nclass Result(NamedTuple):\n    request: Request\n    response: HTTPResponse\n\n\ndef make_request(\n    url: str = \"/\",\n    headers: dict[str, Any] | Sequence[tuple[str, str]] | None = None,\n    method: str = \"GET\",\n    body: str | None = None,\n):\n    assert repl_app, \"No Sanic app has been registered.\"\n    headers = headers or {}\n    protocol = REPLProtocol()\n    request = Request(  # type: ignore\n        url.encode(),\n        Header(headers),\n        \"1.1\",\n        method,\n        protocol,\n        repl_app,\n    )\n    if body is not None:\n        request.body = body.encode()\n    request.stream = protocol  # type: ignore\n    request.conn_info = None\n    return request\n\n\nasync def respond(request) -> HTTPResponse:\n    assert repl_app, \"No Sanic app has been registered.\"\n    await repl_app.handle_request(request)\n    assert repl_response\n    return repl_response\n\n\nasync def do(\n    url: str = \"/\",\n    headers: dict[str, Any] | Sequence[tuple[str, str]] | None = None,\n    method: str = \"GET\",\n    body: str | None = None,\n) -> Result:\n    request = make_request(url, headers, method, body)\n    response = await respond(request)\n    return Result(request, response)\n\n\ndef _variable_description(name: str, desc: str, type_desc: str) -> str:\n    return (\n        f\"  - {Colors.BOLD + Colors.SANIC}{name}{Colors.END}: {desc} - \"\n        f\"{Colors.BOLD + Colors.BLUE}{type_desc}{Colors.END}\"\n    )\n\n\nclass SanicREPL(InteractiveConsole):\n    def __init__(self, app: Sanic, start: Default | None = None):\n        global repl_app\n        repl_app = app\n        locals_available = {\n            \"app\": app,\n            \"sanic\": sanic,\n            \"do\": do,\n        }\n\n        user_locals = {\n            user_local.name: user_local.var for user_local in app.repl_ctx\n        }\n\n        client_availability = \"\"\n        variable_descriptions = [\n            _variable_description(\n                \"app\", REPLContext.BUILTINS[\"app\"], str(app)\n            ),\n            _variable_description(\n                \"sanic\", REPLContext.BUILTINS[\"sanic\"], \"import sanic\"\n            ),\n            _variable_description(\n                \"do\", REPLContext.BUILTINS[\"do\"], \"Result(request, response)\"\n            ),\n        ]\n\n        user_locals_descriptions = [\n            _variable_description(\n                user_local.name, user_local.desc, str(type(user_local.var))\n            )\n            for user_local in app.repl_ctx\n        ]\n\n        if HTTPX_AVAILABLE:\n            locals_available[\"client\"] = SanicClient(app)\n            variable_descriptions.append(\n                _variable_description(\n                    \"client\",\n                    REPLContext.BUILTINS[\"client\"],\n                    \"from httpx import Client\",\n                ),\n            )\n        else:\n            client_availability = (\n                f\"\\n{Colors.YELLOW}The HTTP client has been disabled. \"\n                \"To enable it, install httpx:\\n\\t\"\n                f\"pip install httpx{Colors.END}\\n\"\n            )\n        super().__init__(locals={**locals_available, **user_locals})\n        self.compile.compiler.flags |= PyCF_ALLOW_TOP_LEVEL_AWAIT\n        self.loop = new_event_loop()\n        self._start = start\n        self._pause_event = threading.Event()\n        self._started_event = threading.Event()\n        self._interact_thread = threading.Thread(\n            target=self._console,\n            daemon=True,\n        )\n        self._monitor_thread = threading.Thread(\n            target=self._monitor,\n            daemon=True,\n        )\n        self._async_thread = threading.Thread(\n            target=self.loop.run_forever,\n            daemon=True,\n        )\n        self.app = app\n        self.resume()\n        self.exit_message = \"Closing the REPL.\"\n        self.banner_message = \"\\n\".join(\n            [\n                f\"\\n{Colors.BOLD}Welcome to the Sanic interactive console{Colors.END}\",  # noqa: E501\n                client_availability,\n                \"The following objects are available for your convenience:\",  # noqa: E501\n                *variable_descriptions,\n            ]\n            + (\n                [\n                    \"\\nREPL Context:\",\n                    *user_locals_descriptions,\n                ]\n                if user_locals_descriptions\n                else []\n            )\n            + [\n                \"\\nThe async/await keywords are available for use here.\",  # noqa: E501\n                f\"To exit, press {Colors.BOLD}CTRL+C{Colors.END}, \"\n                f\"{Colors.BOLD}CTRL+D{Colors.END}, or type {Colors.BOLD}exit(){Colors.END}.\\n\",  # noqa: E501\n            ]\n        )\n\n    def pause(self):\n        if self.is_paused():\n            return\n        self._pause_event.clear()\n\n    def resume(self):\n        self._pause_event.set()\n\n    def runsource(self, source, filename=\"<input>\", symbol=\"single\"):\n        if source.strip() == \"exit()\":\n            self._shutdown()\n            return False\n\n        if self.is_paused():\n            print(\"Console is paused. Please wait for it to be resumed.\")\n            return False\n\n        return super().runsource(source, filename, symbol)\n\n    def runcode(self, code):\n        future = concurrent.futures.Future()\n\n        async def callback():\n            func = FunctionType(code, self.locals)\n            try:\n                result = func()\n                if iscoroutine(result):\n                    result = await result\n            except BaseException:\n                traceback.print_exc()\n                result = False\n            future.set_result(result)\n\n        self.loop.call_soon_threadsafe(self.loop.create_task, callback())\n        return future.result()\n\n    def is_paused(self):\n        return not self._pause_event.is_set()\n\n    def _console(self):\n        self._started_event.set()\n        self.interact(banner=self.banner_message, exitmsg=self.exit_message)\n        self._shutdown()\n\n    def _setup_terminal(self):\n        assert termios is not None\n        with suppress(termios.error, AttributeError):\n            fd = sys.stdin.fileno()\n            old_attrs = termios.tcgetattr(fd)\n            atexit.register(\n                termios.tcsetattr, fd, termios.TCSADRAIN, old_attrs\n            )\n\n    def _monitor(self):\n        if isinstance(self._start, Default):\n            if TERMIOS_AVAILABLE and sys.stdin.isatty():\n                self._setup_terminal()\n            enter = f\"{Colors.BOLD + Colors.SANIC}ENTER{Colors.END}\"\n            start = input(f\"\\nPress {enter} at anytime to start the REPL.\\n\\n\")\n            if start:\n                return\n        try:\n            while True:\n                if not self._started_event.is_set():\n                    self.app.manager.wait_for_ack()\n                    self._interact_thread.start()\n                elif self.app.manager._all_workers_ack() and self.is_paused():\n                    self.resume()\n                    print(sys.ps1, end=\"\", flush=True)\n                elif (\n                    not self.app.manager._all_workers_ack()\n                    and not self.is_paused()\n                ):\n                    self.pause()\n                time.sleep(0.1)\n        except (ConnectionResetError, BrokenPipeError):\n            pass\n\n    def _shutdown(self):\n        self.app.manager.monitor_publisher.send(\"__TERMINATE__\")\n\n    def run(self):\n        self._monitor_thread.start()\n        self._async_thread.start()\n"
  },
  {
    "path": "sanic/cli/daemon.py",
    "content": "from __future__ import annotations\n\nimport os\nimport signal\nimport sys\n\nfrom argparse import ArgumentParser\nfrom contextlib import suppress\nfrom pathlib import Path\n\nfrom sanic.worker.daemon import Daemon\n\n\ndef _add_target_args(parser: ArgumentParser) -> None:\n    group = parser.add_mutually_exclusive_group(required=True)\n    group.add_argument(\n        \"--pid\",\n        type=int,\n        metavar=\"PID\",\n        help=\"Process ID of the daemon\",\n    )\n    group.add_argument(\n        \"--pidfile\",\n        type=str,\n        metavar=\"PATH\",\n        help=\"Path to PID file of the daemon\",\n    )\n\n\ndef make_kill_parser(parser: ArgumentParser) -> None:\n    \"\"\"Kill command always sends SIGKILL.\"\"\"\n    _add_target_args(parser)\n\n\ndef make_status_parser(parser: ArgumentParser) -> None:\n    _add_target_args(parser)\n\n\ndef make_restart_parser(parser: ArgumentParser) -> None:\n    _add_target_args(parser)\n\n\ndef resolve_target(\n    pid: int | None, pidfile: str | None\n) -> tuple[int, Path | None]:\n    \"\"\"\n    Resolve a PID from either a direct PID or a pidfile path.\n\n    Returns:\n        Tuple of (pid, pidfile_path or None)\n\n    Raises:\n        SystemExit: If pidfile not found or invalid\n    \"\"\"\n    if pid:\n        return pid, None\n\n    pidfile_path = Path(pidfile)  # type: ignore[arg-type]\n    if not pidfile_path.exists():\n        print(f\"PID file not found: {pidfile_path}\", file=sys.stderr)\n        sys.exit(1)\n\n    resolved_pid = Daemon.read_pidfile(pidfile_path)\n    if resolved_pid is None:\n        print(f\"Invalid PID file: {pidfile_path}\", file=sys.stderr)\n        sys.exit(1)\n\n    return resolved_pid, pidfile_path\n\n\ndef _terminate_process(\n    pid: int, sig: signal.Signals, pidfile: Path | None = None\n) -> None:\n    \"\"\"Send a signal to terminate a process and clean up pidfile.\"\"\"\n    sig_name = sig.name\n\n    try:\n        os.kill(pid, sig)\n        print(f\"Sent {sig_name} to process {pid}\")\n    except ProcessLookupError:\n        print(f\"Process {pid} not found\", file=sys.stderr)\n        sys.exit(1)\n    except PermissionError:\n        print(\n            f\"Permission denied to signal process {pid}. \"\n            \"Are you running as the correct user?\",\n            file=sys.stderr,\n        )\n        sys.exit(1)\n    except OSError as e:\n        print(f\"Failed to signal process {pid}: {e}\", file=sys.stderr)\n        sys.exit(1)\n\n    if pidfile:\n        if pidfile.exists():\n            try:\n                pidfile.unlink()\n                print(f\"Removed PID file: {pidfile}\")\n            except OSError as e:\n                print(\n                    f\"Warning: Could not remove PID file {pidfile}: {e}\",\n                    file=sys.stderr,\n                )\n        lockfile = pidfile.with_suffix(\".lock\")\n        if lockfile.exists():\n            try:\n                lockfile.unlink()\n            except OSError:\n                pass\n\n\ndef kill_daemon(pid: int, pidfile: Path | None = None) -> None:\n    \"\"\"Force kill a daemon process with SIGKILL.\"\"\"\n    _terminate_process(pid, signal.SIGKILL, pidfile)\n\n\ndef stop_daemon(\n    pid: int, pidfile: Path | None = None, force: bool = False\n) -> None:\n    \"\"\"Stop a daemon process gracefully (SIGTERM) or forcefully (SIGKILL).\"\"\"\n    sig = signal.SIGKILL if force else signal.SIGTERM\n    _terminate_process(pid, sig, pidfile)\n\n\ndef status_daemon(pid: int, pidfile: Path | None = None) -> bool:\n    \"\"\"\n    Check if a daemon process is running.\n\n    Args:\n        pid: Process ID to check\n        pidfile: Optional pidfile path to clean up if stale\n\n    Returns:\n        True if running, False otherwise (exits with code 1 if not)\n    \"\"\"\n    try:\n        os.kill(pid, 0)\n        running = True\n    except ProcessLookupError:\n        running = False\n    except PermissionError:\n        running = True\n\n    if running:\n        print(f\"Process {pid} is running\")\n        return True\n\n    print(f\"Process {pid} is NOT running\")\n    if pidfile and pidfile.exists():\n        with suppress(OSError):\n            pidfile.unlink()\n            print(f\"Removed stale PID file: {pidfile}\")\n    sys.exit(1)\n\n\ndef restart_daemon(pid: int) -> None:\n    \"\"\"\n    Restart a daemon process.\n\n    Args:\n        pid: Process ID to restart (unused, for future use)\n    \"\"\"\n    print(\"Restart is not yet implemented. Coming in a future release.\")\n    sys.exit(0)\n"
  },
  {
    "path": "sanic/cli/executor.py",
    "content": "import shutil\n\nfrom argparse import ArgumentParser\nfrom asyncio import run\nfrom inspect import signature\nfrom typing import Callable\n\nfrom sanic import Sanic\nfrom sanic.application.logo import get_logo\nfrom sanic.cli.base import (\n    SanicArgumentParser,\n    SanicHelpFormatter,\n)\n\n\ndef make_executor_parser(parser: ArgumentParser) -> None:\n    parser.add_argument(\n        \"command\",\n        help=\"Command to execute\",\n    )\n\n\nclass ExecutorSubParser(ArgumentParser):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        if not self.description:\n            self.description = \"\"\n        self.description = get_logo(True) + self.description\n\n\nclass Executor:\n    def __init__(self, app: Sanic, kwargs: dict) -> None:\n        self.app = app\n        self.kwargs = kwargs\n        self.commands = self._make_commands()\n        self.parser = self._make_parser()\n\n    def run(self, command: str, args: list[str]) -> None:\n        if command == \"exec\":\n            args = [\"--help\"]\n        parsed_args = self.parser.parse_args(args)\n        if command not in self.commands:\n            raise ValueError(f\"Unknown command: {command}\")\n        parsed_kwargs = vars(parsed_args)\n        parsed_kwargs.pop(\"command\")\n        run(self.commands[command](**parsed_kwargs))\n\n    def _make_commands(self) -> dict[str, Callable]:\n        commands = {c.name: c.func for c in self.app._future_commands}\n        return commands\n\n    def _make_parser(self) -> SanicArgumentParser:\n        width = shutil.get_terminal_size().columns\n        parser = SanicArgumentParser(\n            prog=\"sanic\",\n            description=get_logo(True),\n            formatter_class=lambda prog: SanicHelpFormatter(\n                prog,\n                max_help_position=36 if width > 96 else 24,\n                indent_increment=4,\n                width=None,\n            ),\n        )\n\n        subparsers = parser.add_subparsers(\n            dest=\"command\",\n            title=\"  Commands\",\n            parser_class=ExecutorSubParser,\n        )\n        for command in self.app._future_commands:\n            sub = subparsers.add_parser(\n                command.name,\n                help=command.func.__doc__ or f\"Execute {command.name}\",\n                formatter_class=SanicHelpFormatter,\n            )\n            self._add_arguments(sub, command.func)\n\n        return parser\n\n    def _add_arguments(self, parser: ArgumentParser, func: Callable) -> None:\n        sig = signature(func)\n        for param in sig.parameters.values():\n            kwargs = {}\n            if param.default is not param.empty:\n                kwargs[\"default\"] = param.default\n            # In Python 3.14+, argparse validates help strings and rejects\n            # non-string types. Convert annotations to string representation.\n            help_text = None\n            if param.annotation is not param.empty:\n                if isinstance(param.annotation, str):\n                    help_text = param.annotation\n                else:\n                    help_text = getattr(\n                        param.annotation, \"__name__\", str(param.annotation)\n                    )\n            parser.add_argument(\n                f\"--{param.name}\",\n                help=help_text,\n                **kwargs,\n            )\n"
  },
  {
    "path": "sanic/cli/inspector.py",
    "content": "from argparse import ArgumentParser\n\nfrom sanic.application.logo import get_logo\nfrom sanic.cli.base import SanicHelpFormatter, SanicSubParsersAction\n\n\ndef _add_shared(parser: ArgumentParser) -> None:\n    parser.add_argument(\n        \"--host\",\n        \"-H\",\n        default=\"localhost\",\n        help=\"Inspector host address [default 127.0.0.1]\",\n    )\n    parser.add_argument(\n        \"--port\",\n        \"-p\",\n        default=6457,\n        type=int,\n        help=\"Inspector port [default 6457]\",\n    )\n    parser.add_argument(\n        \"--secure\",\n        \"-s\",\n        action=\"store_true\",\n        help=\"Whether to access the Inspector via TLS encryption\",\n    )\n    parser.add_argument(\"--api-key\", \"-k\", help=\"Inspector authentication key\")\n    parser.add_argument(\n        \"--raw\",\n        action=\"store_true\",\n        help=\"Whether to output the raw response information\",\n    )\n\n\nclass InspectorSubParser(ArgumentParser):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        _add_shared(self)\n        if not self.description:\n            self.description = \"\"\n        self.description = get_logo(True) + self.description\n\n\ndef make_inspector_parser(parser: ArgumentParser) -> None:\n    _add_shared(parser)\n    subparsers = parser.add_subparsers(\n        action=SanicSubParsersAction,\n        dest=\"action\",\n        description=(\n            \"Run one or none of the below subcommands. Using inspect without \"\n            \"a subcommand will fetch general information about the state \"\n            \"of the application instance.\\n\\n\"\n            \"Or, you can optionally follow inspect with a subcommand. \"\n            \"If you have created a custom \"\n            \"Inspector instance, then you can run custom commands. See \"\n            \"https://sanic.dev/en/guide/deployment/inspector.html \"\n            \"for more details.\"\n        ),\n        title=\"  Subcommands\",\n        parser_class=InspectorSubParser,\n    )\n    reloader = subparsers.add_parser(\n        \"reload\",\n        help=\"Trigger a reload of the server workers\",\n        formatter_class=SanicHelpFormatter,\n    )\n    reloader.add_argument(\n        \"--zero-downtime\",\n        action=\"store_true\",\n        help=(\n            \"Whether to wait for the new process to be online before \"\n            \"terminating the old\"\n        ),\n    )\n    subparsers.add_parser(\n        \"shutdown\",\n        help=\"Shutdown the application and all processes\",\n        formatter_class=SanicHelpFormatter,\n    )\n    scale = subparsers.add_parser(\n        \"scale\",\n        help=\"Scale the number of workers\",\n        formatter_class=SanicHelpFormatter,\n    )\n    scale.add_argument(\n        \"replicas\",\n        type=int,\n        help=\"Number of workers requested\",\n    )\n\n    custom = subparsers.add_parser(\n        \"<custom>\",\n        help=\"Run a custom command\",\n        description=(\n            \"keyword arguments:\\n  When running a custom command, you can \"\n            \"add keyword arguments by appending them to your command\\n\\n\"\n            \"\\tsanic inspect foo --one=1 --two=2\"\n        ),\n        formatter_class=SanicHelpFormatter,\n    )\n    custom.add_argument(\n        \"positional\",\n        nargs=\"*\",\n        help=\"Add one or more non-keyword args to your custom command\",\n    )\n"
  },
  {
    "path": "sanic/cli/inspector_client.py",
    "content": "from __future__ import annotations\n\nimport sys\n\nfrom http.client import RemoteDisconnected\nfrom textwrap import indent\nfrom typing import Any\nfrom urllib.error import URLError\nfrom urllib.request import Request as URequest\nfrom urllib.request import urlopen\n\nfrom sanic.application.logo import get_logo\nfrom sanic.application.motd import MOTDTTY\nfrom sanic.log import Colors\n\n\ntry:  # no cov\n    from ujson import dumps, loads\nexcept ModuleNotFoundError:  # no cov\n    from json import dumps, loads  # type: ignore\n\n\nclass InspectorClient:\n    def __init__(\n        self,\n        host: str,\n        port: int,\n        secure: bool,\n        raw: bool,\n        api_key: str | None,\n    ) -> None:\n        self.scheme = \"https\" if secure else \"http\"\n        self.host = host\n        self.port = port\n        self.raw = raw\n        self.api_key = api_key\n\n        for scheme in (\"http\", \"https\"):\n            full = f\"{scheme}://\"\n            if self.host.startswith(full):\n                self.scheme = scheme\n                self.host = self.host[len(full) :]  # noqa E203\n\n    def do(self, action: str, **kwargs: Any) -> None:\n        if action == \"info\":\n            self.info()\n            return\n        result = self.request(action, **kwargs).get(\"result\")\n        if result:\n            out = (\n                dumps(result)\n                if isinstance(result, (list, dict))\n                else str(result)\n            )\n            sys.stdout.write(out + \"\\n\")\n\n    def info(self) -> None:\n        out = sys.stdout.write\n        response = self.request(\"\", \"GET\")\n        if self.raw or not response:\n            return\n        data = response[\"result\"]\n        display = data.pop(\"info\")\n        extra = display.pop(\"extra\", {})\n        display[\"packages\"] = \", \".join(display[\"packages\"])\n        MOTDTTY(get_logo(), self.base_url, display, extra).display(\n            version=False,\n            action=\"Inspecting\",\n            out=out,\n        )\n        for name, info in data[\"workers\"].items():\n            info = \"\\n\".join(\n                f\"\\t{key}: {Colors.BLUE}{value}{Colors.END}\"\n                for key, value in info.items()\n            )\n            out(\n                \"\\n\"\n                + indent(\n                    \"\\n\".join(\n                        [\n                            f\"{Colors.BOLD}{Colors.SANIC}{name}{Colors.END}\",\n                            info,\n                        ]\n                    ),\n                    \"  \",\n                )\n                + \"\\n\"\n            )\n\n    def request(self, action: str, method: str = \"POST\", **kwargs: Any) -> Any:\n        url = f\"{self.base_url}/{action}\"\n        params: dict[str, Any] = {\"method\": method, \"headers\": {}}\n        if kwargs:\n            params[\"data\"] = dumps(kwargs).encode()\n            params[\"headers\"][\"content-type\"] = \"application/json\"\n        if self.api_key:\n            params[\"headers\"][\"authorization\"] = f\"Bearer {self.api_key}\"\n        request = URequest(url, **params)\n\n        try:\n            with urlopen(request) as response:  # nosec B310\n                raw = response.read()\n                loaded = loads(raw)\n                if self.raw:\n                    sys.stdout.write(dumps(loaded.get(\"result\")) + \"\\n\")\n                    return {}\n                return loaded\n        except (URLError, RemoteDisconnected) as e:\n            sys.stderr.write(\n                f\"{Colors.RED}Could not connect to inspector at: \"\n                f\"{Colors.YELLOW}{self.base_url}{Colors.END}\\n\"\n                \"Either the application is not running, or it did not start \"\n                f\"an inspector instance.\\n{e}\\n\"\n            )\n            sys.exit(1)\n\n    @property\n    def base_url(self):\n        return f\"{self.scheme}://{self.host}:{self.port}\"\n"
  },
  {
    "path": "sanic/compat.py",
    "content": "import asyncio\nimport os\nimport platform\nimport signal\nimport sys\n\nfrom collections.abc import Awaitable\nfrom contextlib import contextmanager\nfrom enum import Enum\nfrom typing import Literal\n\nfrom multidict import CIMultiDict  # type: ignore\n\nfrom sanic.helpers import Default\nfrom sanic.log import error_logger\n\n\nStartMethod = (\n    Default | Literal[\"fork\"] | Literal[\"forkserver\"] | Literal[\"spawn\"]\n)\n\nOS_IS_WINDOWS = os.name == \"nt\"\nPYPY_IMPLEMENTATION = platform.python_implementation() == \"PyPy\"\nUVLOOP_INSTALLED = False\nPYTHON_314_OR_LATER = sys.version_info >= (3, 14)\n\ntry:\n    import uvloop  # type: ignore # noqa\n\n    UVLOOP_INSTALLED = True\nexcept ImportError:\n    pass\n\n# Python 3.11 changed the way Enum formatting works for mixed-in types.\nif sys.version_info < (3, 11, 0):\n\n    class StrEnum(str, Enum):\n        pass\n\nelse:\n    from enum import StrEnum  # type: ignore # noqa\n\n\nclass UpperStrEnum(StrEnum):\n    \"\"\"Base class for string enums that are case insensitive.\"\"\"\n\n    def _generate_next_value_(name, start, count, last_values):\n        return name.upper()\n\n    def __eq__(self, value: object) -> bool:\n        value = str(value).upper()\n        return super().__eq__(value)\n\n    def __hash__(self) -> int:\n        return hash(self.value)\n\n    def __str__(self) -> str:\n        return self.value\n\n\n@contextmanager\ndef use_context(method: StartMethod):\n    from sanic import Sanic\n\n    orig = Sanic.start_method\n    Sanic.start_method = method\n    yield\n    Sanic.start_method = orig\n\n\ndef enable_windows_color_support():\n    import ctypes\n\n    kernel = ctypes.windll.kernel32\n    kernel.SetConsoleMode(kernel.GetStdHandle(-11), 7)\n\n\ndef pypy_os_module_patch() -> None:\n    \"\"\"\n    The PyPy os module is missing the 'readlink' function, which causes issues\n    withaiofiles. This workaround replaces the missing 'readlink' function\n    with 'os.path.realpath', which serves the same purpose.\n    \"\"\"\n    if hasattr(os, \"readlink\"):\n        error_logger.debug(\n            \"PyPy: Skipping patching of the os module as it appears the \"\n            \"'readlink' function has been added.\"\n        )\n        return\n\n    module = sys.modules[\"os\"]\n    module.readlink = os.path.realpath  # type: ignore\n\n\ndef pypy_windows_set_console_cp_patch() -> None:\n    \"\"\"\n    A patch function for PyPy on Windows that sets the console code page to\n    UTF-8 encodingto allow for proper handling of non-ASCII characters. This\n    function uses ctypes to call the Windows API functions SetConsoleCP and\n    SetConsoleOutputCP to set the code page.\n    \"\"\"\n    from ctypes import windll  # type: ignore\n\n    code: int = windll.kernel32.GetConsoleOutputCP()\n    if code != 65001:\n        windll.kernel32.SetConsoleCP(65001)\n        windll.kernel32.SetConsoleOutputCP(65001)\n\n\nclass Header(CIMultiDict):\n    \"\"\"Container used for both request and response headers.\n    It is a subclass of  [CIMultiDict](https://multidict.readthedocs.io/en/stable/multidict.html#cimultidictproxy)\n\n    It allows for multiple values for a single key in keeping with the HTTP\n    spec. Also, all keys are *case in-sensitive*.\n\n    Please checkout [the MultiDict documentation](https://multidict.readthedocs.io/en/stable/multidict.html#multidict)\n    for more details about how to use the object. In general, it should work\n    very similar to a regular dictionary.\n    \"\"\"  # noqa: E501\n\n    def __getattr__(self, key: str) -> str:\n        if key.startswith(\"_\"):\n            return self.__getattribute__(key)\n        key = key.rstrip(\"_\").replace(\"_\", \"-\")\n        return \",\".join(self.getall(key, []))\n\n    def get_all(self, key: str):\n        \"\"\"Convenience method mapped to ``getall()``.\"\"\"\n        return self.getall(key, [])\n\n\nuse_trio = sys.argv[0].endswith(\"hypercorn\") and \"trio\" in sys.argv\n\nif use_trio:  # pragma: no cover\n    import trio  # type: ignore\n\n    def stat_async(path) -> Awaitable[os.stat_result]:\n        return trio.Path(path).stat()\n\n    open_async = trio.open_file\n    CancelledErrors = tuple([asyncio.CancelledError, trio.Cancelled])\nelse:\n    if PYPY_IMPLEMENTATION:\n        pypy_os_module_patch()\n\n        if OS_IS_WINDOWS:\n            pypy_windows_set_console_cp_patch()\n\n    from aiofiles import open as aio_open  # type: ignore\n    from aiofiles.os import stat as stat_async  # type: ignore  # noqa: F401\n\n    async def open_async(file, mode=\"r\", **kwargs):\n        return aio_open(file, mode, **kwargs)\n\n    CancelledErrors = tuple([asyncio.CancelledError])\n\n\ndef ctrlc_workaround_for_windows(app):\n    async def stay_active(app):\n        \"\"\"Asyncio wakeups to allow receiving SIGINT in Python\"\"\"\n        while not die:\n            # If someone else stopped the app, just exit\n            if app.state.is_stopping:\n                return\n            # Windows Python blocks signal handlers while the event loop is\n            # waiting for I/O. Frequent wakeups keep interrupts flowing.\n            await asyncio.sleep(0.1)\n        # Can't be called from signal handler, so call it from here\n        app.stop()\n\n    def ctrlc_handler(sig, frame):\n        nonlocal die\n        if die:\n            raise KeyboardInterrupt(\"Non-graceful Ctrl+C\")\n        die = True\n\n    die = False\n    signal.signal(signal.SIGINT, ctrlc_handler)\n    app.add_task(stay_active)\n\n\ndef clear_function_annotate(*funcs):\n    \"\"\"Clear __annotate__ on functions for Python 3.14+ pickle compatibility.\n\n    In Python 3.14, PEP 649 adds __annotate__ to functions with annotations.\n    When methods are used in functools.partial and pickled, the __annotate__\n    function can cause PicklingError because pickle cannot locate it by name.\n\n    This function sets __annotate__ to None on the given functions to avoid\n    pickle issues.\n    \"\"\"\n    if PYTHON_314_OR_LATER:\n        for func in funcs:\n            if hasattr(func, \"__annotate__\") and func.__annotate__ is not None:\n                func.__annotate__ = None\n"
  },
  {
    "path": "sanic/config.py",
    "content": "from __future__ import annotations\n\nfrom abc import ABC, ABCMeta, abstractmethod\nfrom collections.abc import Sequence\nfrom inspect import getmembers, isclass, isdatadescriptor\nfrom os import environ\nfrom pathlib import Path\nfrom typing import Any, Callable, Literal\nfrom warnings import filterwarnings\n\nfrom sanic.constants import LocalCertCreator\nfrom sanic.errorpages import DEFAULT_FORMAT, check_error_format\nfrom sanic.helpers import Default, _default\nfrom sanic.http import Http\nfrom sanic.log import error_logger\nfrom sanic.utils import load_module_from_file_location, str_to_bool\n\n\nFilterWarningType = (\n    Literal[\"default\"]\n    | Literal[\"error\"]\n    | Literal[\"ignore\"]\n    | Literal[\"always\"]\n    | Literal[\"module\"]\n    | Literal[\"once\"]\n)\n\nSANIC_PREFIX = \"SANIC_\"\n\n\nDEFAULT_CONFIG = {\n    \"_FALLBACK_ERROR_FORMAT\": _default,\n    \"ACCESS_LOG\": False,\n    \"AUTO_EXTEND\": True,\n    \"AUTO_RELOAD\": False,\n    \"EVENT_AUTOREGISTER\": False,\n    \"DEPRECATION_FILTER\": \"once\",\n    \"FORWARDED_FOR_HEADER\": \"X-Forwarded-For\",\n    \"FORWARDED_SECRET\": None,\n    \"GRACEFUL_SHUTDOWN_TIMEOUT\": 15.0,\n    \"GRACEFUL_TCP_CLOSE_TIMEOUT\": 5.0,\n    \"INSPECTOR\": False,\n    \"INSPECTOR_HOST\": \"localhost\",\n    \"INSPECTOR_PORT\": 6457,\n    \"INSPECTOR_TLS_KEY\": _default,\n    \"INSPECTOR_TLS_CERT\": _default,\n    \"INSPECTOR_API_KEY\": \"\",\n    \"KEEP_ALIVE_TIMEOUT\": 120,\n    \"KEEP_ALIVE\": True,\n    \"LOCAL_CERT_CREATOR\": LocalCertCreator.AUTO,\n    \"LOCAL_TLS_KEY\": _default,\n    \"LOCAL_TLS_CERT\": _default,\n    \"LOCALHOST\": \"localhost\",\n    \"LOG_EXTRA\": _default,\n    \"MOTD\": True,\n    \"MOTD_DISPLAY\": {},\n    \"NO_COLOR\": False,\n    \"NOISY_EXCEPTIONS\": False,\n    \"PROXIES_COUNT\": None,\n    \"REAL_IP_HEADER\": None,\n    \"REQUEST_BUFFER_SIZE\": 65536,\n    \"REQUEST_MAX_HEADER_SIZE\": 8192,  # Cannot exceed 16384\n    \"REQUEST_ID_HEADER\": \"X-Request-ID\",\n    \"REQUEST_MAX_SIZE\": 100_000_000,\n    \"REQUEST_TIMEOUT\": 60,\n    \"RESPONSE_TIMEOUT\": 60,\n    \"TLS_CERT_PASSWORD\": \"\",\n    \"TOUCHUP\": _default,\n    \"USE_UVLOOP\": _default,\n    \"WEBSOCKET_MAX_SIZE\": 2**20,  # 1 MiB\n    \"WEBSOCKET_PING_INTERVAL\": 20,\n    \"WEBSOCKET_PING_TIMEOUT\": 20,\n}\n\n\nclass DescriptorMeta(ABCMeta):\n    \"\"\"Metaclass for Config.\"\"\"\n\n    def __init__(cls, *_):\n        cls.__setters__ = {name for name, _ in getmembers(cls, cls._is_setter)}\n\n    @staticmethod\n    def _is_setter(member: object):\n        return isdatadescriptor(member) and hasattr(member, \"setter\")\n\n\nclass DetailedConverter(ABC):\n    \"\"\"Base class for detailed converters that need additional context.\n\n    DetailedConverter provides access to the full environment variable key,\n    the raw value, and the current config defaults. This allows for more\n    sophisticated conversion logic that can take into account the variable\n    name pattern, perform validation, or use default values for fallback.\n\n    Examples:\n        ```python\n        # Example of a converter that casts values to the type of the default\n        class DefaultsCastConverter(DetailedConverter):\n            def __call__(self, full_key: str, config_key: str, value: str,\n                                                        defaults: dict) -> Any:\n                try:\n                    if config_key in defaults:\n                        return type(defaults[config_key])(value)\n                except (ValueError, TypeError):\n                    raise ValueError\n        ```\n    \"\"\"\n\n    @abstractmethod\n    def __call__(\n        self, full_key: str, config_key: str, value: str, defaults: dict\n    ) -> Any:\n        \"\"\"Convert an environment variable to a Python value.\n\n        Args:\n            full_key: The full environment variable name (with prefix)\n                            (e.g., \"SANIC_DATABASE_URL\")\n            config_key: The environment variable name (without prefix)\n                            (e.g., \"DATABASE_URL\")\n            value: The raw string value from the environment\n            defaults: The current default configuration values\n\n        Returns:\n            The converted Python value\n\n        Raises:\n            ValueError: If the value cannot be converted by this converter\n        \"\"\"\n\n\nclass Config(dict, metaclass=DescriptorMeta):\n    \"\"\"Configuration object for Sanic.\n\n    You can use this object to both: (1) configure how Sanic will operate, and\n    (2) manage your application's custom configuration values.\n    \"\"\"\n\n    ACCESS_LOG: bool\n    AUTO_EXTEND: bool\n    AUTO_RELOAD: bool\n    EVENT_AUTOREGISTER: bool\n    DEPRECATION_FILTER: FilterWarningType\n    FORWARDED_FOR_HEADER: str\n    FORWARDED_SECRET: str | None\n    GRACEFUL_SHUTDOWN_TIMEOUT: float\n    GRACEFUL_TCP_CLOSE_TIMEOUT: float\n    INSPECTOR: bool\n    INSPECTOR_HOST: str\n    INSPECTOR_PORT: int\n    INSPECTOR_TLS_KEY: Path | str | Default\n    INSPECTOR_TLS_CERT: Path | str | Default\n    INSPECTOR_API_KEY: str\n    KEEP_ALIVE_TIMEOUT: int\n    KEEP_ALIVE: bool\n    LOCAL_CERT_CREATOR: str | LocalCertCreator\n    LOCAL_TLS_KEY: Path | str | Default\n    LOCAL_TLS_CERT: Path | str | Default\n    LOCALHOST: str\n    LOG_EXTRA: Default | bool\n    MOTD: bool\n    MOTD_DISPLAY: dict[str, str]\n    NO_COLOR: bool\n    NOISY_EXCEPTIONS: bool\n    PROXIES_COUNT: int | None\n    REAL_IP_HEADER: str | None\n    REQUEST_BUFFER_SIZE: int\n    REQUEST_MAX_HEADER_SIZE: int\n    REQUEST_ID_HEADER: str\n    REQUEST_MAX_SIZE: int\n    REQUEST_TIMEOUT: int\n    RESPONSE_TIMEOUT: int\n    SERVER_NAME: str\n    TLS_CERT_PASSWORD: str\n    TOUCHUP: Default | bool\n    USE_UVLOOP: Default | bool\n    WEBSOCKET_MAX_SIZE: int\n    WEBSOCKET_PING_INTERVAL: int\n    WEBSOCKET_PING_TIMEOUT: int\n\n    def __init__(\n        self,\n        defaults: dict[str, str | bool | int | float | None] | None = None,\n        env_prefix: str | None = SANIC_PREFIX,\n        keep_alive: bool | None = None,\n        *,\n        converters: Sequence[Callable[[str], Any]] | None = None,\n    ):\n        defaults = defaults or {}\n        self.defaults = {**DEFAULT_CONFIG, **defaults}\n        super().__init__(self.defaults)\n        self._configure_warnings()\n\n        self._converters = [str, str_to_bool, float, int]\n\n        if converters:\n            for converter in converters:\n                self.register_type(converter)\n\n        if keep_alive is not None:\n            self.KEEP_ALIVE = keep_alive\n\n        if env_prefix != SANIC_PREFIX:\n            if env_prefix:\n                self.load_environment_vars(env_prefix)\n        else:\n            self.load_environment_vars(SANIC_PREFIX)\n\n        self._configure_header_size()\n        self._check_error_format()\n        self._init = True\n\n    def __getattr__(self, attr: Any):\n        try:\n            return self[attr]\n        except KeyError as ke:\n            raise AttributeError(f\"Config has no '{ke.args[0]}'\")\n\n    def __setattr__(self, attr: str, value: Any) -> None:\n        self.update({attr: value})\n\n    def __setitem__(self, attr: str, value: Any) -> None:\n        self.update({attr: value})\n\n    def update(self, *other: Any, **kwargs: Any) -> None:\n        \"\"\"Update the config with new values.\n\n        This method will update the config with the values from the provided\n        `other` objects, and then update the config with the provided\n        `kwargs`. The `other` objects can be any object that can be converted\n        to a dictionary, such as a `dict`, `Config` object, or `str` path to a\n        Python file. The `kwargs` must be a dictionary of key-value pairs.\n\n        .. note::\n            Only upper case settings are considered\n\n        Args:\n            *other: Any number of objects that can be converted to a\n                dictionary.\n            **kwargs: Any number of key-value pairs.\n\n        Raises:\n            AttributeError: If a key is not in the config.\n\n        Examples:\n            ```python\n            config.update(\n                {\"A\": 1, \"B\": 2},\n                {\"C\": 3, \"D\": 4},\n                E=5,\n                F=6,\n            )\n            ```\n        \"\"\"\n        kwargs.update({k: v for item in other for k, v in dict(item).items()})\n        setters: dict[str, Any] = {\n            k: kwargs.pop(k)\n            for k in {**kwargs}.keys()\n            if k in self.__class__.__setters__\n        }\n\n        for key, value in setters.items():\n            try:\n                super().__setattr__(key, value)\n            except AttributeError:\n                ...\n\n        super().update(**kwargs)\n        for attr, value in {**setters, **kwargs}.items():\n            self._post_set(attr, value)\n\n    def _post_set(self, attr, value) -> None:\n        if self.get(\"_init\"):\n            if attr in (\n                \"REQUEST_MAX_HEADER_SIZE\",\n                \"REQUEST_BUFFER_SIZE\",\n                \"REQUEST_MAX_SIZE\",\n            ):\n                self._configure_header_size()\n\n        if attr == \"LOCAL_CERT_CREATOR\" and not isinstance(\n            self.LOCAL_CERT_CREATOR, LocalCertCreator\n        ):\n            self.LOCAL_CERT_CREATOR = LocalCertCreator[\n                self.LOCAL_CERT_CREATOR.upper()\n            ]\n        elif attr == \"DEPRECATION_FILTER\":\n            self._configure_warnings()\n\n    @property\n    def FALLBACK_ERROR_FORMAT(self) -> str:\n        if isinstance(self._FALLBACK_ERROR_FORMAT, Default):\n            return DEFAULT_FORMAT\n        return self._FALLBACK_ERROR_FORMAT\n\n    @FALLBACK_ERROR_FORMAT.setter\n    def FALLBACK_ERROR_FORMAT(self, value):\n        self._check_error_format(value)\n        if (\n            not isinstance(self._FALLBACK_ERROR_FORMAT, Default)\n            and value != self._FALLBACK_ERROR_FORMAT\n        ):\n            error_logger.warning(\n                \"Setting config.FALLBACK_ERROR_FORMAT on an already \"\n                \"configured value may have unintended consequences.\"\n            )\n        self._FALLBACK_ERROR_FORMAT = value\n\n    def _configure_header_size(self):\n        Http.set_header_max_size(\n            self.REQUEST_MAX_HEADER_SIZE,\n            self.REQUEST_BUFFER_SIZE - 4096,\n            self.REQUEST_MAX_SIZE,\n        )\n\n    def _configure_warnings(self):\n        filterwarnings(\n            self.DEPRECATION_FILTER,\n            category=DeprecationWarning,\n            module=r\"sanic.*\",\n        )\n\n    def _check_error_format(self, format: str | None = None):\n        check_error_format(format or self.FALLBACK_ERROR_FORMAT)\n\n    def load_environment_vars(self, prefix=SANIC_PREFIX):\n        \"\"\"Load environment variables into the config.\n\n        Looks for prefixed environment variables and applies them to the\n        configuration if present. This is called automatically when Sanic\n        starts up to load environment variables into config. Environment\n        variables should start with the defined prefix and should only\n        contain uppercase letters.\n\n        It will automatically hydrate the following types:\n\n        - ``int``\n        - ``float``\n        - ``bool``\n\n        Anything else will be imported as a ``str``. If you would like to add\n        additional types to this list, you can use\n        :meth:`sanic.config.Config.register_type`. Just make sure that they\n        are registered before you instantiate your application.\n\n        You likely won't need to call this method directly.\n\n        See [Configuration](/en/guide/deployment/configuration) for more details.\n\n        Args:\n            prefix (str): The prefix to use when looking for environment\n                variables. Defaults to `SANIC_`.\n\n\n        Examples:\n            ```python\n            # Environment variables\n            # SANIC_SERVER_NAME=example.com\n            # SANIC_SERVER_PORT=9999\n            # SANIC_SERVER_AUTORELOAD=true\n\n            # Python\n            app.config.load_environment_vars()\n            ```\n        \"\"\"  # noqa: E501\n        for key, value in environ.items():\n            if not key.startswith(prefix) or not key.isupper():\n                continue\n\n            _, config_key = key.split(prefix, 1)\n\n            for converter in reversed(self._converters):\n                try:\n                    if isinstance(converter, DetailedConverter):\n                        self[config_key] = converter(\n                            key, config_key, value, self.defaults\n                        )\n                    else:\n                        self[config_key] = converter(value)\n                    break\n                except ValueError:\n                    pass\n\n    def update_config(self, config: bytes | str | dict[str, Any] | Any):\n        \"\"\"Update app.config.\n\n        .. note::\n\n            Only upper case settings are considered\n\n        See [Configuration](/en/guide/deployment/configuration) for more details.\n\n        Args:\n            config (Union[bytes, str, dict, Any]): Path to py file holding\n                settings, dict holding settings, or any object holding\n                settings.\n\n        Examples:\n            You can upload app config by providing path to py file\n            holding settings.\n\n            ```python\n            # /some/py/file\n            A = 1\n            B = 2\n            ```\n\n            ```python\n            config.update_config(\"${some}/py/file\")\n            ```\n\n            Yes you can put environment variable here, but they must be provided\n            in format: ``${some_env_var}``, and mark that ``$some_env_var`` is\n            treated as plain string.\n\n            You can upload app config by providing dict holding settings.\n\n            ```python\n            d = {\"A\": 1, \"B\": 2}\n            config.update_config(d)\n            ```\n\n            You can upload app config by providing any object holding settings,\n            but in such case config.__dict__ will be used as dict holding settings.\n\n            ```python\n            class C:\n                A = 1\n                B = 2\n\n            config.update_config(C)\n            ```\n        \"\"\"  # noqa: E501\n        if isinstance(config, (bytes, str, Path)):\n            config = load_module_from_file_location(location=config)\n\n        if not isinstance(config, dict):\n            cfg = {}\n            if not isclass(config):\n                cfg.update(\n                    {\n                        key: getattr(config, key)\n                        for key in config.__class__.__dict__.keys()\n                    }\n                )\n\n            config = dict(config.__dict__)\n            config.update(cfg)\n\n        config = dict(filter(lambda i: i[0].isupper(), config.items()))\n\n        self.update(config)\n\n    load = update_config\n\n    def register_type(self, converter: Callable[[str], Any]) -> None:\n        \"\"\"Register a custom type converter.\n\n        Allows for adding custom function to cast from a string value to any\n        other type. The function should raise ValueError if it is not the\n        correct type.\n\n        Args:\n            converter (Callable[[str], Any]): A function that takes a string\n            and returns a value of any type, or a DetailedConverter instance.\n\n        Examples:\n            ```python\n            def my_converter(value: str) -> Any:\n                # Do something to convert the value\n                return value\n\n            config.register_type(my_converter)\n\n            # Or use a DetailedConverter for more context\n            # Example of a converter that casts values to\n            # the type of the default\n            class DefaultsCastConverter(DetailedConverter):\n                def __call__(self, full_key: str, config_key: str, value: str,\n                                                    defaults: dict) -> Any:\n                    try:\n                        if config_key in defaults:\n                            return type(defaults[config_key])(value)\n                    except (ValueError, TypeError):\n                        raise ValueError\n\n            config.register_type(DefaultsCastConverter())\n            ```\n        \"\"\"\n        if converter in self._converters:\n            error_logger.warning(\n                f\"Configuration value converter '{converter.__name__}' has \"\n                \"already been registered\"\n            )\n            return\n        self._converters.append(converter)\n"
  },
  {
    "path": "sanic/constants.py",
    "content": "from enum import auto\n\nfrom sanic.compat import UpperStrEnum\n\n\nclass HTTPMethod(UpperStrEnum):\n    \"\"\"HTTP methods that are commonly used.\"\"\"\n\n    GET = auto()\n    POST = auto()\n    PUT = auto()\n    HEAD = auto()\n    OPTIONS = auto()\n    PATCH = auto()\n    DELETE = auto()\n\n\nclass LocalCertCreator(UpperStrEnum):\n    \"\"\"Local certificate creator.\"\"\"\n\n    AUTO = auto()\n    TRUSTME = auto()\n    MKCERT = auto()\n\n\nHTTP_METHODS = tuple(HTTPMethod.__members__.values())\nSAFE_HTTP_METHODS = (HTTPMethod.GET, HTTPMethod.HEAD, HTTPMethod.OPTIONS)\nIDEMPOTENT_HTTP_METHODS = (\n    HTTPMethod.GET,\n    HTTPMethod.HEAD,\n    HTTPMethod.PUT,\n    HTTPMethod.DELETE,\n    HTTPMethod.OPTIONS,\n)\nCACHEABLE_HTTP_METHODS = (HTTPMethod.GET, HTTPMethod.HEAD)\nDEFAULT_HTTP_CONTENT_TYPE = \"application/octet-stream\"\nDEFAULT_LOCAL_TLS_KEY = \"key.pem\"\nDEFAULT_LOCAL_TLS_CERT = \"cert.pem\"\n"
  },
  {
    "path": "sanic/cookies/__init__.py",
    "content": "from .response import Cookie, CookieJar\n\n\n__all__ = (\"Cookie\", \"CookieJar\")\n"
  },
  {
    "path": "sanic/cookies/request.py",
    "content": "import re\n\nfrom typing import Any\n\nfrom sanic.cookies.response import Cookie\nfrom sanic.request.parameters import RequestParameters\n\n\nCOOKIE_NAME_RESERVED_CHARS = re.compile(\n    '[\\x00-\\x1f\\x7f-\\xff()<>@,;:\\\\\\\\\"/[\\\\]?={} \\x09]'\n)\nOCTAL_PATTERN = re.compile(r\"\\\\[0-3][0-7][0-7]\")\nQUOTE_PATTERN = re.compile(r\"[\\\\].\")\n\n\ndef _unquote(str):  # no cov\n    if str is None or len(str) < 2:\n        return str\n    if str[0] != '\"' or str[-1] != '\"':\n        return str\n\n    str = str[1:-1]\n\n    i = 0\n    n = len(str)\n    res = []\n    while 0 <= i < n:\n        o_match = OCTAL_PATTERN.search(str, i)\n        q_match = QUOTE_PATTERN.search(str, i)\n        if not o_match and not q_match:\n            res.append(str[i:])\n            break\n        # else:\n        j = k = -1\n        if o_match:\n            j = o_match.start(0)\n        if q_match:\n            k = q_match.start(0)\n        if q_match and (not o_match or k < j):\n            res.append(str[i:k])\n            res.append(str[k + 1])\n            i = k + 2\n        else:\n            res.append(str[i:j])\n            res.append(chr(int(str[j + 1 : j + 4], 8)))  # noqa: E203\n            i = j + 4\n    return \"\".join(res)\n\n\ndef parse_cookie(raw: str) -> dict[str, list[str]]:\n    \"\"\"Parses a raw cookie string into a dictionary.\n\n    The function takes a raw cookie string (usually from HTTP headers) and\n    returns a dictionary where each key is a cookie name and the value is a\n    list of values for that cookie. The function handles quoted values and\n    skips invalid cookie names.\n\n    Args:\n        raw (str): The raw cookie string to be parsed.\n\n    Returns:\n        Dict[str, List[str]]: A dictionary containing the cookie names as keys\n        and a list of values for each cookie.\n\n    Example:\n        ```python\n        raw = 'name1=value1; name2=\"value2\"; name3=value3'\n        cookies = parse_cookie(raw)\n        # cookies will be {'name1': ['value1'], 'name2': ['value2'], 'name3': ['value3']}\n        ```\n    \"\"\"  # noqa: E501\n    cookies: dict[str, list[str]] = {}\n\n    for token in raw.split(\";\"):\n        name, sep, value = token.partition(\"=\")\n        name = name.strip()\n        value = value.strip()\n\n        # Support cookies =value or plain value with no name\n        # https://github.com/httpwg/http-extensions/issues/159\n        if not sep:\n            if not name:\n                # Empty value like ;; or a cookie header with no value\n                continue\n            name, value = \"\", name\n\n        if COOKIE_NAME_RESERVED_CHARS.search(name):  # no cov\n            continue\n\n        if len(value) > 2 and value[0] == '\"' and value[-1] == '\"':  # no cov\n            value = _unquote(value)\n\n        if name in cookies:\n            cookies[name].append(value)\n        else:\n            cookies[name] = [value]\n\n    return cookies\n\n\nclass CookieRequestParameters(RequestParameters):\n    \"\"\"A container for accessing single and multiple cookie values.\n\n    Because the HTTP standard allows for multiple cookies with the same name,\n    a standard dictionary cannot be used to access cookie values. This class\n    provides a way to access cookie values in a way that is similar to a\n    dictionary, but also allows for accessing multiple values for a single\n    cookie name when necessary.\n\n    Args:\n        cookies (Dict[str, List[str]]): A dictionary containing the cookie\n            names as keys and a list of values for each cookie.\n\n    Example:\n        ```python\n        raw = 'name1=value1; name2=\"value2\"; name3=value3'\n        cookies = parse_cookie(raw)\n        # cookies will be {'name1': ['value1'], 'name2': ['value2'], 'name3': ['value3']}\n\n        request_cookies = CookieRequestParameters(cookies)\n        request_cookies['name1']  # 'value1'\n        request_cookies.get('name1')  # 'value1'\n        request_cookies.getlist('name1')  # ['value1']\n        ```\n    \"\"\"  # noqa: E501\n\n    def __getitem__(self, key: str) -> str | None:\n        try:\n            value = self._get_prefixed_cookie(key)\n        except KeyError:\n            value = super().__getitem__(key)\n        return value\n\n    def __getattr__(self, key: str) -> str:\n        if key.startswith(\"_\"):\n            return self.__getattribute__(key)\n        key = key.rstrip(\"_\").replace(\"_\", \"-\")\n        return str(self.get(key, \"\"))\n\n    def get(self, name: str, default: Any | None = None) -> Any | None:\n        try:\n            return self._get_prefixed_cookie(name)[0]\n        except KeyError:\n            return super().get(name, default)\n\n    def getlist(\n        self, name: str, default: list[Any] | None = None\n    ) -> list[Any]:\n        try:\n            return self._get_prefixed_cookie(name)\n        except KeyError:\n            return super().getlist(name, default)\n\n    def _get_prefixed_cookie(self, name: str) -> Any:\n        getitem = super().__getitem__\n        try:\n            return getitem(f\"{Cookie.HOST_PREFIX}{name}\")\n        except KeyError:\n            return getitem(f\"{Cookie.SECURE_PREFIX}{name}\")\n"
  },
  {
    "path": "sanic/cookies/response.py",
    "content": "from __future__ import annotations\n\nimport re\nimport string\n\nfrom datetime import datetime\nfrom typing import TYPE_CHECKING, Literal, cast\n\nfrom sanic.exceptions import ServerError\n\n\nif TYPE_CHECKING:\n    from sanic.compat import Header\n\n\nSameSite = (\n    Literal[\"Strict\"]\n    | Literal[\"Lax\"]\n    | Literal[\"None\"]\n    | Literal[\"strict\"]\n    | Literal[\"lax\"]\n    | Literal[\"none\"]\n)\n\nDEFAULT_MAX_AGE = 0\nSAMESITE_VALUES = (\"strict\", \"lax\", \"none\")\n\nLEGAL_CHARS = string.ascii_letters + string.digits + \"!#$%&'*+-.^_`|~:\"\nUNESCAPED_CHARS = LEGAL_CHARS + \" ()/<=>?@[]{}\"\nTRANSLATOR = {ch: f\"\\\\{ch:03o}\" for ch in bytes(range(32)) + b'\";\\\\\\x7f'}\n\n\ndef _quote(str):  # no cov\n    r\"\"\"Quote a string for use in a cookie header.\n    If the string does not need to be double-quoted, then just return the\n    string.  Otherwise, surround the string in doublequotes and quote\n    (with a \\) special characters.\n    \"\"\"\n    if str is None or _is_legal_key(str):\n        return str\n    else:\n        return f'\"{str.translate(TRANSLATOR)}\"'\n\n\n_is_legal_key = re.compile(\"[%s]+\" % re.escape(LEGAL_CHARS)).fullmatch\n\n\nclass CookieJar:\n    \"\"\"A container to manipulate cookies.\n\n    CookieJar dynamically writes headers as cookies are added and removed\n    It gets around the limitation of one header per name by using the\n    MultiHeader class to provide a unique key that encodes to Set-Cookie.\n\n    Args:\n        headers (Header): The headers object to write cookies to.\n    \"\"\"\n\n    HEADER_KEY = \"Set-Cookie\"\n\n    def __init__(self, headers: Header):\n        self.headers = headers\n\n    def __len__(self):  # no cov\n        return len(self.cookies)\n\n    @property\n    def cookies(self) -> list[Cookie]:\n        \"\"\"A list of cookies in the CookieJar.\n\n        Returns:\n            List[Cookie]: A list of cookies in the CookieJar.\n        \"\"\"\n        return self.headers.getall(self.HEADER_KEY, [])\n\n    def get_cookie(\n        self,\n        key: str,\n        path: str = \"/\",\n        domain: str | None = None,\n        host_prefix: bool = False,\n        secure_prefix: bool = False,\n    ) -> Cookie | None:\n        \"\"\"Fetch a cookie from the CookieJar.\n\n        Args:\n            key (str): The key of the cookie to fetch.\n            path (str, optional): The path of the cookie. Defaults to `\"/\"`.\n            domain (Optional[str], optional): The domain of the cookie.\n                Defaults to `None`.\n            host_prefix (bool, optional): Whether to add __Host- as a prefix to the key.\n                This requires that path=\"/\", domain=None, and secure=True.\n                Defaults to `False`.\n            secure_prefix (bool, optional): Whether to add __Secure- as a prefix to the key.\n                This requires that secure=True. Defaults to `False`.\n\n        Returns:\n            Optional[Cookie]: The cookie if it exists, otherwise `None`.\n        \"\"\"  # noqa: E501\n        for cookie in self.cookies:\n            if (\n                cookie.key == Cookie.make_key(key, host_prefix, secure_prefix)\n                and cookie.path == path\n                and cookie.domain == domain\n            ):\n                return cookie\n        return None\n\n    def has_cookie(\n        self,\n        key: str,\n        path: str = \"/\",\n        domain: str | None = None,\n        host_prefix: bool = False,\n        secure_prefix: bool = False,\n    ) -> bool:\n        \"\"\"Check if a cookie exists in the CookieJar.\n\n        Args:\n            key (str): The key of the cookie to check.\n            path (str, optional): The path of the cookie. Defaults to `\"/\"`.\n            domain (Optional[str], optional): The domain of the cookie.\n                Defaults to `None`.\n            host_prefix (bool, optional): Whether to add __Host- as a prefix to the key.\n                This requires that path=\"/\", domain=None, and secure=True.\n                Defaults to `False`.\n            secure_prefix (bool, optional): Whether to add __Secure- as a prefix to the key.\n                This requires that secure=True. Defaults to `False`.\n\n        Returns:\n            bool: Whether the cookie exists.\n        \"\"\"  # noqa: E501\n        for cookie in self.cookies:\n            if (\n                cookie.key == Cookie.make_key(key, host_prefix, secure_prefix)\n                and cookie.path == path\n                and cookie.domain == domain\n            ):\n                return True\n        return False\n\n    def add_cookie(\n        self,\n        key: str,\n        value: str,\n        *,\n        path: str = \"/\",\n        domain: str | None = None,\n        secure: bool = True,\n        max_age: int | None = None,\n        expires: datetime | None = None,\n        httponly: bool = False,\n        samesite: SameSite | None = \"Lax\",\n        partitioned: bool = False,\n        comment: str | None = None,\n        host_prefix: bool = False,\n        secure_prefix: bool = False,\n    ) -> Cookie:\n        \"\"\"Add a cookie to the CookieJar.\n\n        Args:\n            key (str): Key of the cookie.\n            value (str): Value of the cookie.\n            path (str, optional): Path of the cookie. Defaults to \"/\".\n            domain (Optional[str], optional): Domain of the cookie. Defaults to None.\n            secure (bool, optional): Whether to set it as a secure cookie. Defaults to True.\n            max_age (Optional[int], optional): Max age of the cookie in seconds; if set to 0 a\n                browser should delete it. Defaults to None.\n            expires (Optional[datetime], optional): When the cookie expires; if set to None browsers\n                should set it as a session cookie. Defaults to None.\n            httponly (bool, optional): Whether to set it as HTTP only. Defaults to False.\n            samesite (Optional[SameSite], optional): How to set the samesite property, should be\n                strict, lax, or none (case insensitive). Defaults to \"Lax\".\n            partitioned (bool, optional): Whether to set it as partitioned. Defaults to False.\n            comment (Optional[str], optional): A cookie comment. Defaults to None.\n            host_prefix (bool, optional): Whether to add __Host- as a prefix to the key.\n                This requires that path=\"/\", domain=None, and secure=True. Defaults to False.\n            secure_prefix (bool, optional): Whether to add __Secure- as a prefix to the key.\n                This requires that secure=True. Defaults to False.\n\n        Returns:\n            Cookie: The instance of the created cookie.\n\n        Raises:\n            ServerError: If host_prefix is set without secure=True.\n            ServerError: If host_prefix is set without path=\"/\" and domain=None.\n            ServerError: If host_prefix is set with domain.\n            ServerError: If secure_prefix is set without secure=True.\n            ServerError: If partitioned is set without host_prefix=True.\n\n        Examples:\n            Basic usage\n            ```python\n            cookie = add_cookie('name', 'value')\n            ```\n\n            Adding a cookie with a custom path and domain\n            ```python\n            cookie = add_cookie('name', 'value', path='/custom', domain='example.com')\n            ```\n\n            Adding a secure, HTTP-only cookie with a comment\n            ```python\n            cookie = add_cookie('name', 'value', secure=True, httponly=True, comment='My Cookie')\n            ```\n\n            Adding a cookie with a max age of 60 seconds\n            ```python\n            cookie = add_cookie('name', 'value', max_age=60)\n            ```\n        \"\"\"  # noqa: E501\n        cookie = Cookie(\n            key,\n            value,\n            path=path,\n            expires=expires,\n            comment=comment,\n            domain=domain,\n            max_age=max_age,\n            secure=secure,\n            httponly=httponly,\n            samesite=samesite,\n            partitioned=partitioned,\n            host_prefix=host_prefix,\n            secure_prefix=secure_prefix,\n        )\n        self.headers.add(self.HEADER_KEY, cookie)\n\n        return cookie\n\n    def delete_cookie(\n        self,\n        key: str,\n        *,\n        path: str = \"/\",\n        domain: str | None = None,\n        secure: bool = True,\n        host_prefix: bool = False,\n        secure_prefix: bool = False,\n    ) -> None:\n        \"\"\"\n        Delete a cookie\n\n        This will effectively set it as Max-Age: 0, which a browser should\n        interpret it to mean: \"delete the cookie\".\n\n        Since it is a browser/client implementation, your results may vary\n        depending upon which client is being used.\n\n        :param key: The key to be deleted\n        :type key: str\n        :param path: Path of the cookie, defaults to None\n        :type path: Optional[str], optional\n        :param domain: Domain of the cookie, defaults to None\n        :type domain: Optional[str], optional\n        :param secure: Whether to delete a secure cookie. Defaults to True.\n        :param secure: bool\n        :param host_prefix: Whether to add __Host- as a prefix to the key.\n            This requires that path=\"/\", domain=None, and secure=True,\n            defaults to False\n        :type host_prefix: bool\n        :param secure_prefix: Whether to add __Secure- as a prefix to the key.\n            This requires that secure=True, defaults to False\n        :type secure_prefix: bool\n        \"\"\"\n        if host_prefix and not (secure and path == \"/\" and domain is None):\n            raise ServerError(\n                \"Cannot set host_prefix on a cookie without \"\n                \"path='/', domain=None, and secure=True\"\n            )\n        if secure_prefix and not secure:\n            raise ServerError(\n                \"Cannot set secure_prefix on a cookie without secure=True\"\n            )\n\n        cookies: list[Cookie] = self.headers.popall(self.HEADER_KEY, [])\n        existing_cookie = None\n        for cookie in cookies:\n            if (\n                cookie.key != Cookie.make_key(key, host_prefix, secure_prefix)\n                or cookie.path != path\n                or cookie.domain != domain\n            ):\n                self.headers.add(self.HEADER_KEY, cookie)\n            elif existing_cookie is None:\n                existing_cookie = cookie\n\n        if existing_cookie is not None:\n            # Use all the same values as the cookie to be deleted\n            # except value=\"\" and max_age=0\n            self.add_cookie(\n                key=key,\n                value=\"\",\n                path=existing_cookie.path,\n                domain=existing_cookie.domain,\n                secure=existing_cookie.secure,\n                max_age=0,\n                httponly=existing_cookie.httponly,\n                partitioned=existing_cookie.partitioned,\n                samesite=existing_cookie.samesite,\n                host_prefix=host_prefix,\n                secure_prefix=secure_prefix,\n            )\n        else:\n            self.add_cookie(\n                key=key,\n                value=\"\",\n                path=path,\n                domain=domain,\n                secure=secure,\n                max_age=0,\n                samesite=None,\n                host_prefix=host_prefix,\n                secure_prefix=secure_prefix,\n            )\n\n\nclass Cookie:\n    \"\"\"A representation of a HTTP cookie, providing an interface to manipulate cookie attributes intended for a response.\n\n    This class is a simplified representation of a cookie, similar to the Morsel SimpleCookie in Python's standard library.\n    It allows the manipulation of various cookie attributes including path, domain, security settings, and others.\n\n    Several \"smart defaults\" are provided to make it easier to create cookies that are secure by default. These include:\n\n    - Setting the `secure` flag to `True` by default\n    - Setting the `samesite` flag to `Lax` by default\n\n    Args:\n        key (str): The key (name) of the cookie.\n        value (str): The value of the cookie.\n        path (str, optional): The path for the cookie. Defaults to \"/\".\n        domain (Optional[str], optional): The domain for the cookie.\n            Defaults to `None`.\n        secure (bool, optional): Whether the cookie is secure.\n            Defaults to `True`.\n        max_age (Optional[int], optional): The maximum age of the cookie\n            in seconds. Defaults to `None`.\n        expires (Optional[datetime], optional): The expiration date of the\n            cookie. Defaults to `None`.\n        httponly (bool, optional): HttpOnly flag for the cookie.\n            Defaults to `False`.\n        samesite (Optional[SameSite], optional): The SameSite attribute for\n            the cookie. Defaults to `\"Lax\"`.\n        partitioned (bool, optional): Whether the cookie is partitioned.\n            Defaults to `False`.\n        comment (Optional[str], optional): A comment for the cookie.\n            Defaults to `None`.\n        host_prefix (bool, optional): Whether to use the host prefix.\n            Defaults to `False`.\n        secure_prefix (bool, optional): Whether to use the secure prefix.\n            Defaults to `False`.\n    \"\"\"  # noqa: E501\n\n    HOST_PREFIX = \"__Host-\"\n    SECURE_PREFIX = \"__Secure-\"\n\n    __slots__ = (\n        \"key\",\n        \"value\",\n        \"_path\",\n        \"_comment\",\n        \"_domain\",\n        \"_secure\",\n        \"_httponly\",\n        \"_partitioned\",\n        \"_expires\",\n        \"_max_age\",\n        \"_samesite\",\n    )\n\n    _keys = {\n        \"path\": \"Path\",\n        \"comment\": \"Comment\",\n        \"domain\": \"Domain\",\n        \"max-age\": \"Max-Age\",\n        \"expires\": \"expires\",\n        \"samesite\": \"SameSite\",\n        # \"version\": \"Version\",\n        \"secure\": \"Secure\",\n        \"httponly\": \"HttpOnly\",\n        \"partitioned\": \"Partitioned\",\n    }\n    _flags = {\"secure\", \"httponly\", \"partitioned\"}\n\n    def __init__(\n        self,\n        key: str,\n        value: str,\n        *,\n        path: str = \"/\",\n        domain: str | None = None,\n        secure: bool = True,\n        max_age: int | None = None,\n        expires: datetime | None = None,\n        httponly: bool = False,\n        samesite: SameSite | None = \"Lax\",\n        partitioned: bool = False,\n        comment: str | None = None,\n        host_prefix: bool = False,\n        secure_prefix: bool = False,\n    ):\n        if key in self._keys:\n            raise KeyError(\"Cookie name is a reserved word\")\n        if not _is_legal_key(key):\n            raise KeyError(\"Cookie key contains illegal characters\")\n        if host_prefix:\n            if not secure:\n                raise ServerError(\n                    \"Cannot set host_prefix on a cookie without secure=True\"\n                )\n            if path != \"/\":\n                raise ServerError(\n                    \"Cannot set host_prefix on a cookie unless path='/'\"\n                )\n            if domain:\n                raise ServerError(\n                    \"Cannot set host_prefix on a cookie with a defined domain\"\n                )\n        elif secure_prefix and not secure:\n            raise ServerError(\n                \"Cannot set secure_prefix on a cookie without secure=True\"\n            )\n        if partitioned and not host_prefix:\n            # This is technically possible, but it is not advisable so we will\n            # take a stand and say \"don't shoot yourself in the foot\"\n            raise ServerError(\n                \"Cannot create a partitioned cookie without \"\n                \"also setting host_prefix=True\"\n            )\n\n        self.key = self.make_key(key, host_prefix, secure_prefix)\n        self.value = value\n\n        self._path = path\n        self._comment = comment\n        self._domain = domain\n        self._secure = secure\n        self._httponly = httponly\n        self._partitioned = partitioned\n        self._expires: datetime | None = None\n        self._max_age: int | None = None\n        self._samesite: SameSite | None = None\n\n        if expires is not None:\n            self.expires = expires\n        if max_age is not None:\n            self.max_age = max_age\n        if samesite is not None:\n            self.samesite = samesite\n\n    def __str__(self):\n        \"\"\"Format as a Set-Cookie header value.\"\"\"\n        output = [\"{}={}\".format(self.key, _quote(self.value))]\n        ordered_keys = list(self._keys.keys())\n        for key in sorted(\n            self._keys.keys(), key=lambda k: ordered_keys.index(k)\n        ):\n            value = getattr(self, key.replace(\"-\", \"_\"))\n            if value is not None and value is not False:\n                if key == \"max-age\":\n                    try:\n                        output.append(\"%s=%d\" % (self._keys[key], value))\n                    except TypeError:\n                        output.append(\"{}={}\".format(self._keys[key], value))\n                elif key == \"expires\":\n                    output.append(\n                        \"%s=%s\"\n                        % (\n                            self._keys[key],\n                            value.strftime(\"%a, %d-%b-%Y %T GMT\"),\n                        )\n                    )\n                elif key in self._flags:\n                    output.append(self._keys[key])\n                else:\n                    output.append(\"{}={}\".format(self._keys[key], value))\n\n        return \"; \".join(output)\n\n    @property\n    def path(self) -> str:  # no cov\n        \"\"\"The path of the cookie. Defaults to `\"/\"`.\"\"\"\n        return self._path\n\n    @path.setter\n    def path(self, value: str) -> None:  # no cov\n        self._path = value\n\n    @property\n    def expires(self) -> datetime | None:  # no cov\n        \"\"\"The expiration date of the cookie. Defaults to `None`.\"\"\"\n        return self._expires\n\n    @expires.setter\n    def expires(self, value: datetime) -> None:  # no cov\n        if not isinstance(value, datetime):\n            raise TypeError(\"Cookie 'expires' property must be a datetime\")\n        self._expires = value\n\n    @property\n    def comment(self) -> str | None:  # no cov\n        \"\"\"A comment for the cookie. Defaults to `None`.\"\"\"\n        return self._comment\n\n    @comment.setter\n    def comment(self, value: str) -> None:  # no cov\n        self._comment = value\n\n    @property\n    def domain(self) -> str | None:  # no cov\n        \"\"\"The domain of the cookie. Defaults to `None`.\"\"\"\n        return self._domain\n\n    @domain.setter\n    def domain(self, value: str) -> None:  # no cov\n        self._domain = value\n\n    @property\n    def max_age(self) -> int | None:  # no cov\n        \"\"\"The maximum age of the cookie in seconds. Defaults to `None`.\"\"\"\n        return self._max_age\n\n    @max_age.setter\n    def max_age(self, value: int) -> None:  # no cov\n        if not str(value).isdigit():\n            raise ValueError(\"Cookie max-age must be an integer\")\n        self._max_age = value\n\n    @property\n    def secure(self) -> bool:  # no cov\n        \"\"\"Whether the cookie is secure. Defaults to `True`.\"\"\"\n        return self._secure\n\n    @secure.setter\n    def secure(self, value: bool) -> None:  # no cov\n        self._secure = value\n\n    @property\n    def httponly(self) -> bool:  # no cov\n        \"\"\"Whether the cookie is HTTP only. Defaults to `False`.\"\"\"\n        return self._httponly\n\n    @httponly.setter\n    def httponly(self, value: bool) -> None:  # no cov\n        self._httponly = value\n\n    @property\n    def samesite(self) -> SameSite | None:  # no cov\n        \"\"\"The SameSite attribute for the cookie. Defaults to `\"Lax\"`.\"\"\"\n        return self._samesite\n\n    @samesite.setter\n    def samesite(self, value: SameSite) -> None:  # no cov\n        if value.lower() not in SAMESITE_VALUES:\n            raise TypeError(\n                \"Cookie 'samesite' property must \"\n                f\"be one of: {','.join(SAMESITE_VALUES)}\"\n            )\n        self._samesite = cast(SameSite, value.title())\n\n    @property\n    def partitioned(self) -> bool:  # no cov\n        \"\"\"Whether the cookie is partitioned. Defaults to `False`.\"\"\"\n        return self._partitioned\n\n    @partitioned.setter\n    def partitioned(self, value: bool) -> None:  # no cov\n        self._partitioned = value\n\n    @classmethod\n    def make_key(\n        cls, key: str, host_prefix: bool = False, secure_prefix: bool = False\n    ) -> str:\n        \"\"\"Create a cookie key with the appropriate prefix.\n\n        Cookies can have one ow two prefixes. The first is `__Host-` which\n        requires that the cookie be set with `path=\"/\", domain=None, and\n        secure=True`. The second is `__Secure-` which requires that\n        `secure=True`.\n\n        They cannot be combined.\n\n        Args:\n            key (str): The key (name) of the cookie.\n            host_prefix (bool, optional): Whether to add __Host- as a prefix to the key.\n                This requires that path=\"/\", domain=None, and secure=True.\n                Defaults to `False`.\n            secure_prefix (bool, optional): Whether to add __Secure- as a prefix to the key.\n                This requires that secure=True. Defaults to `False`.\n\n        Raises:\n            ServerError: If both host_prefix and secure_prefix are set.\n\n        Returns:\n            str: The key with the appropriate prefix.\n        \"\"\"  # noqa: E501\n        if host_prefix and secure_prefix:\n            raise ServerError(\n                \"Both host_prefix and secure_prefix were requested. \"\n                \"A cookie should have only one prefix.\"\n            )\n        elif host_prefix:\n            key = cls.HOST_PREFIX + key\n        elif secure_prefix:\n            key = cls.SECURE_PREFIX + key\n        return key\n"
  },
  {
    "path": "sanic/errorpages.py",
    "content": "\"\"\"\nSanic `provides a pattern\n<https://sanicframework.org/guide/best-practices/exceptions.html#using-sanic-exceptions>`_\nfor providing a response when an exception occurs. However, if you do no handle\nan exception, it will provide a fallback. There are three fallback types:\n\n- HTML - *default*\n- Text\n- JSON\n\nSetting ``app.config.FALLBACK_ERROR_FORMAT = \"auto\"`` will enable a switch that\nwill attempt to provide an appropriate response format based upon the\nrequest type.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport sys\nimport typing as t\n\nfrom functools import partial\nfrom traceback import extract_tb\n\nfrom sanic.exceptions import BadRequest, SanicException\nfrom sanic.helpers import STATUS_CODES\nfrom sanic.log import deprecation, logger\nfrom sanic.pages.error import ErrorPage\nfrom sanic.response import html, json, text\n\n\ndumps: t.Callable[..., str]\ntry:\n    from ujson import dumps\n\n    dumps = partial(dumps, escape_forward_slashes=False)\nexcept ImportError:  # noqa\n    from json import dumps\n\nif t.TYPE_CHECKING:\n    from sanic import HTTPResponse, Request\n\nDEFAULT_FORMAT = \"auto\"\nFALLBACK_TEXT = \"\"\"\\\nThe application encountered an unexpected error and could not continue.\\\n\"\"\"\nFALLBACK_STATUS = 500\nJSON = \"application/json\"\n\n\nclass BaseRenderer:\n    \"\"\"Base class that all renderers must inherit from.\n\n    This class defines the structure for rendering objects, handling the core functionality that specific renderers may extend.\n\n    Attributes:\n        request (Request): The incoming request object that needs rendering.\n        exception (Exception): Any exception that occurred and needs to be rendered.\n        debug (bool): Flag indicating whether to render with debugging information.\n\n    Methods:\n        dumps: A static method that must be overridden by subclasses to define the specific rendering.\n\n    Args:\n        request (Request): The incoming request object that needs rendering.\n        exception (Exception): Any exception that occurred and needs to be rendered.\n        debug (bool): Flag indicating whether to render with debugging information.\n    \"\"\"  # noqa: E501\n\n    dumps = staticmethod(dumps)\n\n    def __init__(self, request: Request, exception: Exception, debug: bool):\n        self.request = request\n        self.exception = exception\n        self.debug = debug\n\n    @property\n    def headers(self) -> t.Dict[str, str]:\n        \"\"\"The headers to be used for the response.\"\"\"\n        if isinstance(self.exception, SanicException):\n            return getattr(self.exception, \"headers\", {})\n        return {}\n\n    @property\n    def status(self):\n        \"\"\"The status code to be used for the response.\"\"\"\n        if isinstance(self.exception, SanicException):\n            return getattr(self.exception, \"status_code\", FALLBACK_STATUS)\n        return FALLBACK_STATUS\n\n    @property\n    def text(self):\n        \"\"\"The text to be used for the response.\"\"\"\n        if self.debug or isinstance(self.exception, SanicException):\n            return str(self.exception)\n        return FALLBACK_TEXT\n\n    @property\n    def title(self):\n        \"\"\"The title to be used for the response.\"\"\"\n        status_text = STATUS_CODES.get(self.status, b\"Error Occurred\").decode()\n        return f\"{self.status} — {status_text}\"\n\n    def render(self) -> HTTPResponse:\n        \"\"\"Outputs the exception as a response.\n\n        Returns:\n            HTTPResponse: The response object.\n        \"\"\"\n        output = (\n            self.full\n            if self.debug and not getattr(self.exception, \"quiet\", False)\n            else self.minimal\n        )()\n        output.status = self.status\n        output.headers.update(self.headers)\n        return output\n\n    def minimal(self) -> HTTPResponse:  # noqa\n        \"\"\"Provide a formatted message that is meant to not show any sensitive data or details.\n\n        This is the default fallback for production environments.\n\n        Returns:\n            HTTPResponse: The response object.\n        \"\"\"  # noqa: E501\n        raise NotImplementedError\n\n    def full(self) -> HTTPResponse:  # noqa\n        \"\"\"Provide a formatted message that has all details and is mean to be used primarily for debugging and non-production environments.\n\n        Returns:\n            HTTPResponse: The response object.\n        \"\"\"  # noqa: E501\n        raise NotImplementedError\n\n\nclass HTMLRenderer(BaseRenderer):\n    \"\"\"Render an exception as HTML.\n\n    The default fallback type.\n    \"\"\"\n\n    def full(self) -> HTTPResponse:\n        page = ErrorPage(\n            debug=self.debug,\n            title=super().title,\n            text=super().text,\n            request=self.request,\n            exc=self.exception,\n        )\n        return html(page.render())\n\n    def minimal(self) -> HTTPResponse:\n        return self.full()\n\n\nclass TextRenderer(BaseRenderer):\n    \"\"\"Render an exception as plain text.\"\"\"\n\n    OUTPUT_TEXT = \"{title}\\n{bar}\\n{text}\\n\\n{body}\"\n    SPACER = \"  \"\n\n    def full(self) -> HTTPResponse:\n        return text(\n            self.OUTPUT_TEXT.format(\n                title=self.title,\n                text=self.text,\n                bar=(\"=\" * len(self.title)),\n                body=self._generate_body(full=True),\n            )\n        )\n\n    def minimal(self) -> HTTPResponse:\n        return text(\n            self.OUTPUT_TEXT.format(\n                title=self.title,\n                text=self.text,\n                bar=(\"=\" * len(self.title)),\n                body=self._generate_body(full=False),\n            )\n        )\n\n    @property\n    def title(self):\n        return f\"⚠️ {super().title}\"\n\n    def _generate_body(self, *, full):\n        lines = []\n        if full:\n            _, exc_value, __ = sys.exc_info()\n            exceptions = []\n\n            lines += [\n                f\"{self.exception.__class__.__name__}: {self.exception} while \"\n                f\"handling path {self.request.path}\",\n                f\"Traceback of {self.request.app.name} \"\n                \"(most recent call last):\\n\",\n            ]\n\n            while exc_value:\n                exceptions.append(self._format_exc(exc_value))\n                exc_value = exc_value.__cause__\n\n            lines += exceptions[::-1]\n\n        for attr, display in ((\"context\", True), (\"extra\", bool(full))):\n            info = getattr(self.exception, attr, None)\n            if info and display:\n                lines += self._generate_object_display_list(info, attr)\n\n        return \"\\n\".join(lines)\n\n    def _format_exc(self, exc):\n        frames = \"\\n\\n\".join(\n            [\n                f\"{self.SPACER * 2}File {frame.filename}, \"\n                f\"line {frame.lineno}, in \"\n                f\"{frame.name}\\n{self.SPACER * 2}{frame.line}\"\n                for frame in extract_tb(exc.__traceback__)\n            ]\n        )\n        return f\"{self.SPACER}{exc.__class__.__name__}: {exc}\\n{frames}\"\n\n    def _generate_object_display_list(self, obj, descriptor):\n        lines = [f\"\\n{descriptor.title()}\"]\n        for key, value in obj.items():\n            display = self.dumps(value)\n            lines.append(f\"{self.SPACER * 2}{key}: {display}\")\n        return lines\n\n\nclass JSONRenderer(BaseRenderer):\n    \"\"\"Render an exception as JSON.\"\"\"\n\n    def full(self) -> HTTPResponse:\n        output = self._generate_output(full=True)\n        return json(output, dumps=self.dumps)\n\n    def minimal(self) -> HTTPResponse:\n        output = self._generate_output(full=False)\n        return json(output, dumps=self.dumps)\n\n    def _generate_output(self, *, full):\n        output = {\n            \"description\": self.title,\n            \"status\": self.status,\n            \"message\": self.text,\n        }\n\n        for attr, display in ((\"context\", True), (\"extra\", bool(full))):\n            info = getattr(self.exception, attr, None)\n            if info and display:\n                output[attr] = info\n\n        if full:\n            _, exc_value, __ = sys.exc_info()\n            exceptions = []\n\n            while exc_value:\n                exceptions.append(\n                    {\n                        \"type\": exc_value.__class__.__name__,\n                        \"exception\": str(exc_value),\n                        \"frames\": [\n                            {\n                                \"file\": frame.filename,\n                                \"line\": frame.lineno,\n                                \"name\": frame.name,\n                                \"src\": frame.line,\n                            }\n                            for frame in extract_tb(exc_value.__traceback__)\n                        ],\n                    }\n                )\n                exc_value = exc_value.__cause__\n\n            output[\"path\"] = self.request.path\n            output[\"args\"] = self.request.args\n            output[\"exceptions\"] = exceptions[::-1]\n\n        return output\n\n    @property\n    def title(self):\n        return STATUS_CODES.get(self.status, b\"Error Occurred\").decode()\n\n\ndef escape(text):\n    \"\"\"Minimal HTML escaping, not for attribute values (unlike html.escape).\"\"\"\n    return f\"{text}\".replace(\"&\", \"&amp;\").replace(\"<\", \"&lt;\")\n\n\nMIME_BY_CONFIG = {\n    \"text\": \"text/plain\",\n    \"json\": \"application/json\",\n    \"html\": \"text/html\",\n}\nCONFIG_BY_MIME = {v: k for k, v in MIME_BY_CONFIG.items()}\nRENDERERS_BY_CONTENT_TYPE = {\n    \"text/plain\": TextRenderer,\n    \"application/json\": JSONRenderer,\n    \"multipart/form-data\": HTMLRenderer,\n    \"text/html\": HTMLRenderer,\n}\n\n# Handler source code is checked for which response types it returns with the\n# route error_format=\"auto\" (default) to determine which format to use.\nRESPONSE_MAPPING = {\n    \"json\": \"json\",\n    \"text\": \"text\",\n    \"html\": \"html\",\n    \"JSONResponse\": \"json\",\n    \"text/plain\": \"text\",\n    \"text/html\": \"html\",\n    \"application/json\": \"json\",\n}\n\n\ndef check_error_format(format):\n    \"\"\"Check that the format is known.\"\"\"\n    if format not in MIME_BY_CONFIG and format != \"auto\":\n        raise SanicException(f\"Unknown format: {format}\")\n\n\ndef exception_response(\n    request: Request,\n    exception: Exception,\n    debug: bool,\n    fallback: str,\n    base: t.Type[BaseRenderer],\n    renderer: t.Optional[t.Type[BaseRenderer]] = None,\n) -> HTTPResponse:\n    \"\"\"Render a response for the default FALLBACK exception handler.\"\"\"\n    if not renderer:\n        mt = guess_mime(request, fallback)\n        renderer = RENDERERS_BY_CONTENT_TYPE.get(mt, base)\n\n    renderer = t.cast(t.Type[BaseRenderer], renderer)\n    return renderer(request, exception, debug).render()\n\n\ndef guess_mime(req: Request, fallback: str) -> str:\n    \"\"\"Guess the MIME type for the response based upon the request.\"\"\"\n    # Attempt to find a suitable MIME format for the response.\n    # Insertion-ordered map of formats[\"html\"] = \"source of that suggestion\"\n    formats = {}\n    name = \"\"\n    # Route error_format (by magic from handler code if auto, the default)\n    if req.route:\n        name = req.route.name\n        f = req.route.extra.error_format\n        if f in MIME_BY_CONFIG:\n            formats[f] = name\n\n    if not formats and fallback in MIME_BY_CONFIG:\n        formats[fallback] = \"FALLBACK_ERROR_FORMAT\"\n\n    # If still not known, check for the request for clues of JSON\n    if not formats and fallback == \"auto\" and req.accept.match(JSON):\n        if JSON in req.accept:  # Literally, not wildcard\n            formats[\"json\"] = \"request.accept\"\n        elif JSON in req.headers.getone(\"content-type\", \"\"):\n            formats[\"json\"] = \"content-type\"\n        # DEPRECATION: Remove this block in 24.3\n        else:\n            c = None\n            try:\n                c = req.json\n            except BadRequest:\n                pass\n            if c:\n                formats[\"json\"] = \"request.json\"\n                deprecation(\n                    \"Response type was determined by the JSON content of \"\n                    \"the request. This behavior is deprecated and will be \"\n                    \"removed in v24.3. Please specify the format either by\\n\"\n                    f'  error_format=\"json\" on route {name}, by\\n'\n                    '  FALLBACK_ERROR_FORMAT = \"json\", or by adding header\\n'\n                    \"  accept: application/json to your requests.\",\n                    24.3,\n                )\n\n    # Any other supported formats\n    if fallback == \"auto\":\n        for k in MIME_BY_CONFIG:\n            if k not in formats:\n                formats[k] = \"any\"\n\n    mimes = [MIME_BY_CONFIG[k] for k in formats]\n    m = req.accept.match(*mimes)\n    if m:\n        format = CONFIG_BY_MIME[m.mime]\n        source = formats[format]\n        logger.debug(\n            \"Error Page: The client accepts %s, using '%s' from %s\",\n            m.header,\n            format,\n            source,\n        )\n    else:\n        logger.debug(\n            \"Error Page: No format found, the client accepts %s\",\n            repr(req.accept),\n        )\n    return m.mime\n"
  },
  {
    "path": "sanic/exceptions.py",
    "content": "from asyncio import CancelledError\nfrom collections.abc import Sequence\nfrom os import PathLike\nfrom typing import Any\n\nfrom sanic.helpers import STATUS_CODES\nfrom sanic.models.protocol_types import Range\n\n\nclass RequestCancelled(CancelledError):\n    quiet = True\n\n\nclass ServerKilled(Exception):\n    \"\"\"Exception Sanic server uses when killing a server process for something unexpected happening.\"\"\"  # noqa: E501\n\n    quiet = True\n\n\nclass SanicException(Exception):\n    \"\"\"Generic exception that will generate an HTTP response when raised in the context of a request lifecycle.\n\n    Usually, it is best practice to use one of the more specific exceptions\n    than this generic one. Even when trying to raise a 500, it is generally\n    preferable to use `ServerError`.\n\n    Args:\n        message (Optional[Union[str, bytes]], optional): The message to be sent to the client. If `None`,\n            then the appropriate HTTP response status message will be used instead. Defaults to `None`.\n        status_code (Optional[int], optional): The HTTP response code to send, if applicable. If `None`,\n            then it will be 500. Defaults to `None`.\n        quiet (Optional[bool], optional): When `True`, the error traceback will be suppressed from the logs.\n            Defaults to `None`.\n        context (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will be\n            sent to the client upon exception. Defaults to `None`.\n        extra (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will NOT be\n            sent to the client when in PRODUCTION mode. Defaults to `None`.\n        headers (Optional[Dict[str, Any]], optional): Additional headers that should be sent with the HTTP\n            response. Defaults to `None`.\n\n    Examples:\n        ```python\n        raise SanicException(\n            \"Something went wrong\",\n            status_code=999,\n            context={\n                \"info\": \"Some additional details to send to the client\",\n            },\n            headers={\n                \"X-Foo\": \"bar\"\n            }\n        )\n        ```\n    \"\"\"  # noqa: E501\n\n    status_code: int = 500\n    quiet: bool | None = False\n    headers: dict[str, str] = {}\n    message: str = \"\"\n\n    def __init__(\n        self,\n        message: str | bytes | None = None,\n        status_code: int | None = None,\n        *,\n        quiet: bool | None = None,\n        context: dict[str, Any] | None = None,\n        extra: dict[str, Any] | None = None,\n        headers: dict[str, str] | None = None,\n    ) -> None:\n        self.context = context\n        self.extra = extra\n        status_code = status_code or getattr(\n            self.__class__, \"status_code\", None\n        )\n        quiet = (\n            quiet\n            if quiet is not None\n            else getattr(self.__class__, \"quiet\", None)\n        )\n        headers = headers or getattr(self.__class__, \"headers\", {})\n        if message is None:\n            message = self.message\n            if not message and status_code:\n                msg = STATUS_CODES.get(status_code, b\"\")\n                message = msg.decode()\n        elif isinstance(message, bytes):\n            message = message.decode()\n\n        super().__init__(message)\n\n        self.status_code = status_code or self.status_code\n        self.quiet = quiet\n        self.headers = headers\n        try:\n            self.message = message\n        except AttributeError:\n            ...\n\n\nclass HTTPException(SanicException):\n    \"\"\"A base class for other exceptions and should not be called directly.\"\"\"\n\n    def __init__(\n        self,\n        message: str | bytes | None = None,\n        *,\n        quiet: bool | None = None,\n        context: dict[str, Any] | None = None,\n        extra: dict[str, Any] | None = None,\n        headers: dict[str, Any] | None = None,\n    ) -> None:\n        super().__init__(\n            message,\n            quiet=quiet,\n            context=context,\n            extra=extra,\n            headers=headers,\n        )\n\n\nclass NotFound(HTTPException):\n    \"\"\"A base class for other exceptions and should not be called directly.\n\n    Args:\n        message (Optional[Union[str, bytes]], optional): The message to be sent to the client. If `None`,\n            then the appropriate HTTP response status message will be used instead. Defaults to `None`.\n        quiet (Optional[bool], optional): When `True`, the error traceback will be suppressed from the logs.\n            Defaults to `None`.\n        context (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will be\n            sent to the client upon exception. Defaults to `None`.\n        extra (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will NOT be\n            sent to the client when in PRODUCTION mode. Defaults to `None`.\n        headers (Optional[Dict[str, Any]], optional): Additional headers that should be sent with the HTTP\n            response. Defaults to `None`.\n    \"\"\"  # noqa: E501\n\n    status_code = 404\n    quiet = True\n\n\nclass BadRequest(HTTPException):\n    \"\"\"400 Bad Request\n\n    Args:\n        message (Optional[Union[str, bytes]], optional): The message to be sent to the client. If `None`\n            then the HTTP status 'Bad Request' will be sent. Defaults to `None`.\n        quiet (Optional[bool], optional): When `True`, the error traceback will be suppressed\n            from the logs. Defaults to `None`.\n        context (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will be\n            sent to the client upon exception. Defaults to `None`.\n        extra (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will NOT be\n            sent to the client when in PRODUCTION mode. Defaults to `None`.\n        headers (Optional[Dict[str, Any]], optional): Additional headers that should be sent with the HTTP\n            response. Defaults to `None`.\n    \"\"\"  # noqa: E501\n\n    status_code = 400\n    quiet = True\n\n\nInvalidUsage = BadRequest\nBadURL = BadRequest\n\n\nclass MethodNotAllowed(HTTPException):\n    \"\"\"405 Method Not Allowed\n\n    Args:\n        message (Optional[Union[str, bytes]], optional): The message to be sent to the client. If `None`\n            then the HTTP status 'Method Not Allowed' will be sent. Defaults to `None`.\n        method (Optional[str], optional): The HTTP method that was used. Defaults to an empty string.\n        allowed_methods (Optional[Sequence[str]], optional): The HTTP methods that can be used instead of the\n            one that was attempted.\n        quiet (Optional[bool], optional): When `True`, the error traceback will be suppressed\n            from the logs. Defaults to `None`.\n        context (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will be\n            sent to the client upon exception. Defaults to `None`.\n        extra (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will NOT be\n            sent to the client when in PRODUCTION mode. Defaults to `None`.\n        headers (Optional[Dict[str, Any]], optional): Additional headers that should be sent with the HTTP\n            response. Defaults to `None`.\n    \"\"\"  # noqa: E501\n\n    status_code = 405\n    quiet = True\n\n    def __init__(\n        self,\n        message: str | bytes | None = None,\n        method: str = \"\",\n        allowed_methods: Sequence[str] | None = None,\n        *,\n        quiet: bool | None = None,\n        context: dict[str, Any] | None = None,\n        extra: dict[str, Any] | None = None,\n        headers: dict[str, Any] | None = None,\n    ):\n        super().__init__(\n            message,\n            quiet=quiet,\n            context=context,\n            extra=extra,\n            headers=headers,\n        )\n        if allowed_methods:\n            self.headers = {\n                **self.headers,\n                \"Allow\": \", \".join(allowed_methods),\n            }\n        self.method = method\n        self.allowed_methods = allowed_methods\n\n\nMethodNotSupported = MethodNotAllowed\n\n\nclass ServerError(HTTPException):\n    \"\"\"500 Internal Server Error\n\n    A general server-side error has occurred. If no other HTTP exception is\n    appropriate, then this should be used\n\n    Args:\n        message (Optional[Union[str, bytes]], optional): The message to be sent to the client. If `None`\n            then the HTTP status 'Bad Request' will be sent. Defaults to `None`.\n        quiet (Optional[bool], optional): When `True`, the error traceback will be suppressed\n            from the logs. Defaults to `None`.\n        context (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will be\n            sent to the client upon exception. Defaults to `None`.\n        extra (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will NOT be\n            sent to the client when in PRODUCTION mode. Defaults to `None`.\n        headers (Optional[Dict[str, Any]], optional): Additional headers that should be sent with the HTTP\n            response. Defaults to `None`.\n    \"\"\"  # noqa: E501\n\n    status_code = 500\n\n\nInternalServerError = ServerError\n\n\nclass ServiceUnavailable(HTTPException):\n    \"\"\"503 Service Unavailable\n\n    The server is currently unavailable (because it is overloaded or\n    down for maintenance). Generally, this is a temporary state.\n\n    Args:\n        message (Optional[Union[str, bytes]], optional): The message to be sent to the client. If `None`\n            then the HTTP status 'Bad Request' will be sent. Defaults to `None`.\n        quiet (Optional[bool], optional): When `True`, the error traceback will be suppressed\n            from the logs. Defaults to `None`.\n        context (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will be\n            sent to the client upon exception. Defaults to `None`.\n        extra (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will NOT be\n            sent to the client when in PRODUCTION mode. Defaults to `None`.\n        headers (Optional[Dict[str, Any]], optional): Additional headers that should be sent with the HTTP\n            response. Defaults to `None`.\n    \"\"\"  # noqa: E501\n\n    status_code = 503\n    quiet = True\n\n\nclass URLBuildError(HTTPException):\n    \"\"\"500 Internal Server Error\n\n    An exception used by Sanic internals when unable to build a URL.\n\n    Args:\n        message (Optional[Union[str, bytes]], optional): The message to be sent to the client. If `None`\n            then the HTTP status 'Bad Request' will be sent. Defaults to `None`.\n        quiet (Optional[bool], optional): When `True`, the error traceback will be suppressed\n            from the logs. Defaults to `None`.\n        context (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will be\n            sent to the client upon exception. Defaults to `None`.\n        extra (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will NOT be\n            sent to the client when in PRODUCTION mode. Defaults to `None`.\n        headers (Optional[Dict[str, Any]], optional): Additional headers that should be sent with the HTTP\n            response. Defaults to `None`.\n    \"\"\"  # noqa: E501\n\n    status_code = 500\n\n\nclass FileNotFound(NotFound):\n    \"\"\"404 Not Found\n\n    A specific form of :class:`.NotFound` that is specifically when looking\n    for a file on the file system at a known path.\n\n    Args:\n        message (Optional[Union[str, bytes]], optional): The message to be sent to the client. If `None`\n            then the HTTP status 'Not Found' will be sent. Defaults to `None`.\n        path (Optional[PathLike], optional): The path, if any, to the file that could not\n            be found. Defaults to `None`.\n        relative_url (Optional[str], optional): A relative URL of the file. Defaults to `None`.\n        quiet (Optional[bool], optional): When `True`, the error traceback will be suppressed\n            from the logs. Defaults to `None`.\n        context (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will be\n            sent to the client upon exception. Defaults to `None`.\n        extra (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will NOT be\n            sent to the client when in PRODUCTION mode. Defaults to `None`.\n        headers (Optional[Dict[str, Any]], optional): Additional headers that should be sent with the HTTP\n            response. Defaults to `None`.\n    \"\"\"  # noqa: E501\n\n    def __init__(\n        self,\n        message: str | bytes | None = None,\n        path: PathLike | None = None,\n        relative_url: str | None = None,\n        *,\n        quiet: bool | None = None,\n        context: dict[str, Any] | None = None,\n        extra: dict[str, Any] | None = None,\n        headers: dict[str, Any] | None = None,\n    ):\n        super().__init__(\n            message,\n            quiet=quiet,\n            context=context,\n            extra=extra,\n            headers=headers,\n        )\n        self.path = path\n        self.relative_url = relative_url\n\n\nclass RequestTimeout(HTTPException):\n    \"\"\"408 Request Timeout\n\n    The Web server (running the Web site) thinks that there has been too\n    long an interval of time between 1) the establishment of an IP\n    connection (socket) between the client and the server and\n    2) the receipt of any data on that socket, so the server has dropped\n    the connection. The socket connection has actually been lost - the Web\n    server has 'timed out' on that particular socket connection.\n\n    This is an internal exception thrown by Sanic and should not be used\n    directly.\n\n    Args:\n        message (Optional[Union[str, bytes]], optional): The message to be sent to the client. If `None`\n            then the HTTP status 'Bad Request' will be sent. Defaults to `None`.\n        quiet (Optional[bool], optional): When `True`, the error traceback will be suppressed\n            from the logs. Defaults to `None`.\n        context (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will be\n            sent to the client upon exception. Defaults to `None`.\n        extra (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will NOT be\n            sent to the client when in PRODUCTION mode. Defaults to `None`.\n        headers (Optional[Dict[str, Any]], optional): Additional headers that should be sent with the HTTP\n            response. Defaults to `None`.\n    \"\"\"  # noqa: E501\n\n    status_code = 408\n    quiet = True\n\n\nclass PayloadTooLarge(HTTPException):\n    \"\"\"413 Payload Too Large\n\n    This is an internal exception thrown by Sanic and should not be used\n    directly.\n\n    Args:\n        message (Optional[Union[str, bytes]], optional): The message to be sent to the client. If `None`\n            then the HTTP status 'Bad Request' will be sent. Defaults to `None`.\n        quiet (Optional[bool], optional): When `True`, the error traceback will be suppressed\n            from the logs. Defaults to `None`.\n        context (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will be\n            sent to the client upon exception. Defaults to `None`.\n        extra (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will NOT be\n            sent to the client when in PRODUCTION mode. Defaults to `None`.\n        headers (Optional[Dict[str, Any]], optional): Additional headers that should be sent with the HTTP\n            response. Defaults to `None`.\n    \"\"\"  # noqa: E501\n\n    status_code = 413\n    quiet = True\n\n\nclass HeaderNotFound(BadRequest):\n    \"\"\"400 Bad Request\n\n    Args:\n        message (Optional[Union[str, bytes]], optional): The message to be sent to the client. If `None`\n            then the HTTP status 'Bad Request' will be sent. Defaults to `None`.\n        quiet (Optional[bool], optional): When `True`, the error traceback will be suppressed\n            from the logs. Defaults to `None`.\n        context (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will be\n            sent to the client upon exception. Defaults to `None`.\n        extra (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will NOT be\n            sent to the client when in PRODUCTION mode. Defaults to `None`.\n        headers (Optional[Dict[str, Any]], optional): Additional headers that should be sent with the HTTP\n            response. Defaults to `None`.\n    \"\"\"  # noqa: E501\n\n\nclass InvalidHeader(BadRequest):\n    \"\"\"400 Bad Request\n\n    Args:\n        message (Optional[Union[str, bytes]], optional): The message to be sent to the client. If `None`\n            then the HTTP status 'Bad Request' will be sent. Defaults to `None`.\n        quiet (Optional[bool], optional): When `True`, the error traceback will be suppressed\n            from the logs. Defaults to `None`.\n        context (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will be\n            sent to the client upon exception. Defaults to `None`.\n        extra (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will NOT be\n            sent to the client when in PRODUCTION mode. Defaults to `None`.\n        headers (Optional[Dict[str, Any]], optional): Additional headers that should be sent with the HTTP\n            response. Defaults to `None`.\n    \"\"\"  # noqa: E501\n\n\nclass RangeNotSatisfiable(HTTPException):\n    \"\"\"416 Range Not Satisfiable\n\n    Args:\n        message (Optional[Union[str, bytes]], optional): The message to be sent to the client. If `None`\n            then the HTTP status 'Range Not Satisfiable' will be sent. Defaults to `None`.\n        content_range (Optional[ContentRange], optional): An object meeting the :class:`.ContentRange` protocol\n            that has a `total` property. Defaults to `None`.\n        quiet (Optional[bool], optional): When `True`, the error traceback will be suppressed\n            from the logs. Defaults to `None`.\n        context (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will be\n            sent to the client upon exception. Defaults to `None`.\n        extra (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will NOT be\n            sent to the client when in PRODUCTION mode. Defaults to `None`.\n        headers (Optional[Dict[str, Any]], optional): Additional headers that should be sent with the HTTP\n            response. Defaults to `None`.\n    \"\"\"  # noqa: E501\n\n    status_code = 416\n    quiet = True\n\n    def __init__(\n        self,\n        message: str | bytes | None = None,\n        content_range: Range | None = None,\n        *,\n        quiet: bool | None = None,\n        context: dict[str, Any] | None = None,\n        extra: dict[str, Any] | None = None,\n        headers: dict[str, Any] | None = None,\n    ):\n        super().__init__(\n            message,\n            quiet=quiet,\n            context=context,\n            extra=extra,\n            headers=headers,\n        )\n        if content_range is not None:\n            self.headers = {\n                **self.headers,\n                \"Content-Range\": f\"bytes */{content_range.total}\",\n            }\n\n\nContentRangeError = RangeNotSatisfiable\n\n\nclass ExpectationFailed(HTTPException):\n    \"\"\"417 Expectation Failed\n\n    Args:\n        message (Optional[Union[str, bytes]], optional): The message to be sent to the client. If `None`\n            then the HTTP status 'Bad Request' will be sent. Defaults to `None`.\n        quiet (Optional[bool], optional): When `True`, the error traceback will be suppressed\n            from the logs. Defaults to `None`.\n        context (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will be\n            sent to the client upon exception. Defaults to `None`.\n        extra (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will NOT be\n            sent to the client when in PRODUCTION mode. Defaults to `None`.\n        headers (Optional[Dict[str, Any]], optional): Additional headers that should be sent with the HTTP\n            response. Defaults to `None`.\n    \"\"\"  # noqa: E501\n\n    status_code = 417\n    quiet = True\n\n\nHeaderExpectationFailed = ExpectationFailed\n\n\nclass Forbidden(HTTPException):\n    \"\"\"403 Forbidden\n\n    Args:\n        message (Optional[Union[str, bytes]], optional): The message to be sent to the client. If `None`\n            then the HTTP status 'Bad Request' will be sent. Defaults to `None`.\n        quiet (Optional[bool], optional): When `True`, the error traceback will be suppressed\n            from the logs. Defaults to `None`.\n        context (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will be\n            sent to the client upon exception. Defaults to `None`.\n        extra (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will NOT be\n            sent to the client when in PRODUCTION mode. Defaults to `None`.\n        headers (Optional[Dict[str, Any]], optional): Additional headers that should be sent with the HTTP\n            response. Defaults to `None`.\n    \"\"\"  # noqa: E501\n\n    status_code = 403\n    quiet = True\n\n\nclass InvalidRangeType(RangeNotSatisfiable):\n    \"\"\"416 Range Not Satisfiable\n\n    Args:\n        message (Optional[Union[str, bytes]], optional): The message to be sent to the client. If `None`\n            then the HTTP status 'Bad Request' will be sent. Defaults to `None`.\n        quiet (Optional[bool], optional): When `True`, the error traceback will be suppressed\n            from the logs. Defaults to `None`.\n        context (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will be\n            sent to the client upon exception. Defaults to `None`.\n        extra (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will NOT be\n            sent to the client when in PRODUCTION mode. Defaults to `None`.\n        headers (Optional[Dict[str, Any]], optional): Additional headers that should be sent with the HTTP\n            response. Defaults to `None`.\n    \"\"\"  # noqa: E501\n\n    status_code = 416\n    quiet = True\n\n\nclass PyFileError(SanicException):\n    def __init__(\n        self,\n        file,\n        status_code: int | None = None,\n        *,\n        quiet: bool | None = None,\n        context: dict[str, Any] | None = None,\n        extra: dict[str, Any] | None = None,\n        headers: dict[str, Any] | None = None,\n    ):\n        super().__init__(\n            \"could not execute config file %s\" % file,\n            status_code=status_code,\n            quiet=quiet,\n            context=context,\n            extra=extra,\n            headers=headers,\n        )\n\n\nclass Unauthorized(HTTPException):\n    \"\"\"\n    **Status**: 401 Unauthorized\n\n    When present, additional keyword arguments may be used to complete\n    the WWW-Authentication header.\n\n    Args:\n        message (Optional[Union[str, bytes]], optional): The message to be sent to the client. If `None`\n            then the HTTP status 'Bad Request' will be sent. Defaults to `None`.\n        scheme (Optional[str], optional): Name of the authentication scheme to be used. Defaults to `None`.\n        quiet (Optional[bool], optional): When `True`, the error traceback will be suppressed\n            from the logs. Defaults to `None`.\n        context (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will be\n            sent to the client upon exception. Defaults to `None`.\n        extra (Optional[Dict[str, Any]], optional): Additional mapping of key/value data that will NOT be\n            sent to the client when in PRODUCTION mode. Defaults to `None`.\n        headers (Optional[Dict[str, Any]], optional): Additional headers that should be sent with the HTTP\n            response. Defaults to `None`.\n        **challenges (Dict[str, Any]): Additional keyword arguments that will be used to complete the\n            WWW-Authentication header. Defaults to `None`.\n\n    Examples:\n        With a Basic auth-scheme, realm MUST be present:\n        ```python\n        raise Unauthorized(\n            \"Auth required.\",\n            scheme=\"Basic\",\n            realm=\"Restricted Area\"\n        )\n        ```\n\n        With a Digest auth-scheme, things are a bit more complicated:\n        ```python\n        raise Unauthorized(\n            \"Auth required.\",\n            scheme=\"Digest\",\n            realm=\"Restricted Area\",\n            qop=\"auth, auth-int\",\n            algorithm=\"MD5\",\n            nonce=\"abcdef\",\n            opaque=\"zyxwvu\"\n        )\n        ```\n\n        With a Bearer auth-scheme, realm is optional so you can write:\n        ```python\n        raise Unauthorized(\"Auth required.\", scheme=\"Bearer\")\n        ```\n\n        or, if you want to specify the realm:\n        ```python\n        raise Unauthorized(\n            \"Auth required.\",\n            scheme=\"Bearer\",\n            realm=\"Restricted Area\"\n        )\n        ```\n    \"\"\"  # noqa: E501\n\n    status_code = 401\n    quiet = True\n\n    def __init__(\n        self,\n        message: str | bytes | None = None,\n        scheme: str | None = None,\n        *,\n        quiet: bool | None = None,\n        context: dict[str, Any] | None = None,\n        extra: dict[str, Any] | None = None,\n        headers: dict[str, Any] | None = None,\n        **challenges,\n    ):\n        super().__init__(\n            message,\n            quiet=quiet,\n            context=context,\n            extra=extra,\n            headers=headers,\n        )\n\n        # if auth-scheme is specified, set \"WWW-Authenticate\" header\n        if scheme is not None:\n            values = [f'{k!s}=\"{v!s}\"' for k, v in challenges.items()]\n            challenge = \", \".join(values)\n\n            self.headers = {\n                **self.headers,\n                \"WWW-Authenticate\": f\"{scheme} {challenge}\".rstrip(),\n            }\n\n\nclass LoadFileException(SanicException):\n    \"\"\"Exception raised when a file cannot be loaded.\"\"\"\n\n\nclass InvalidSignal(SanicException):\n    \"\"\"Exception raised when an invalid signal is sent.\"\"\"\n\n\nclass WebsocketClosed(SanicException):\n    \"\"\"Exception raised when a websocket is closed.\"\"\"\n\n    quiet = True\n    message = \"Client has closed the websocket connection\"\n"
  },
  {
    "path": "sanic/handlers/__init__.py",
    "content": "from .content_range import ContentRangeHandler\nfrom .directory import DirectoryHandler\nfrom .error import ErrorHandler\n\n\n__all__ = (\n    \"ContentRangeHandler\",\n    \"DirectoryHandler\",\n    \"ErrorHandler\",\n)\n"
  },
  {
    "path": "sanic/handlers/content_range.py",
    "content": "from __future__ import annotations\n\nimport os\n\nfrom typing import TYPE_CHECKING\n\nfrom sanic.exceptions import (\n    HeaderNotFound,\n    InvalidRangeType,\n    RangeNotSatisfiable,\n)\nfrom sanic.models.protocol_types import Range\n\n\nif TYPE_CHECKING:\n    from sanic import Request\n\n\nclass ContentRangeHandler(Range):\n    \"\"\"Parse and process the incoming request headers to extract the content range information.\n\n    Args:\n        request (Request): The incoming request object.\n        stats (os.stat_result): The stats of the file being served.\n    \"\"\"  # noqa: E501\n\n    __slots__ = (\"start\", \"end\", \"size\", \"total\", \"headers\")\n\n    def __init__(self, request: Request, stats: os.stat_result) -> None:\n        self.total = stats.st_size\n        _range = request.headers.getone(\"range\", None)\n        if _range is None:\n            raise HeaderNotFound(\"Range Header Not Found\")\n        unit, _, value = tuple(map(str.strip, _range.partition(\"=\")))\n        if unit != \"bytes\":\n            raise InvalidRangeType(\n                \"{} is not a valid Range Type\".format(unit), self\n            )\n        start_b, _, end_b = tuple(map(str.strip, value.partition(\"-\")))\n        try:\n            self.start = int(start_b) if start_b else None\n        except ValueError:\n            raise RangeNotSatisfiable(\n                \"'{}' is invalid for Content Range\".format(start_b), self\n            )\n        try:\n            self.end = int(end_b) if end_b else None\n        except ValueError:\n            raise RangeNotSatisfiable(\n                \"'{}' is invalid for Content Range\".format(end_b), self\n            )\n        if self.end is None:\n            if self.start is None:\n                raise RangeNotSatisfiable(\n                    \"Invalid for Content Range parameters\", self\n                )\n            else:\n                # this case represents `Content-Range: bytes 5-`\n                self.end = self.total - 1\n        else:\n            if self.start is None:\n                # this case represents `Content-Range: bytes -5`\n                self.start = self.total - self.end\n                self.end = self.total - 1\n        if self.start > self.end:\n            raise RangeNotSatisfiable(\n                \"Invalid for Content Range parameters\", self\n            )\n        self.size = self.end - self.start + 1\n        self.headers = {\n            \"Content-Range\": \"bytes %s-%s/%s\"\n            % (self.start, self.end, self.total)\n        }\n\n    def __bool__(self):\n        return hasattr(self, \"size\") and self.size > 0\n"
  },
  {
    "path": "sanic/handlers/directory.py",
    "content": "from __future__ import annotations\n\nfrom collections.abc import Iterable, Sequence\nfrom datetime import datetime\nfrom operator import itemgetter\nfrom pathlib import Path\nfrom stat import S_ISDIR\nfrom typing import cast\nfrom urllib.parse import unquote\n\nfrom sanic.exceptions import NotFound\nfrom sanic.pages.directory_page import DirectoryPage, FileInfo\nfrom sanic.request import Request\nfrom sanic.response import file, html, redirect\n\n\ndef _is_path_within_root(path: Path, root: Path) -> bool:\n    \"\"\"Check if a path (after resolution) is within the root directory.\n\n    Returns False for:\n    - Broken symlinks (cannot be resolved)\n    - Paths that resolve outside the root directory\n    - Any errors during resolution\n    \"\"\"\n    try:\n        resolved = path.resolve()\n        resolved.relative_to(root.resolve())\n    except (ValueError, OSError, RuntimeError):\n        return False\n    else:\n        return True\n\n\nclass DirectoryHandler:\n    \"\"\"Serve files from a directory.\n\n    Args:\n        uri (str): The URI to serve the files at.\n        directory (Path): The directory to serve files from.\n        directory_view (bool): Whether to show a directory listing or not.\n        index (str | Sequence[str] | None): The index file(s) to\n            serve if the directory is requested. Defaults to None.\n        root_path (Optional[Path]): The root path for security checks.\n            Symlinks resolving outside this path will be hidden from\n            directory listings. Defaults to directory if not specified.\n        follow_external_symlink_files (bool): Whether to show file symlinks\n            pointing outside root in directory listings. Defaults to False.\n        follow_external_symlink_dirs (bool): Whether to show directory symlinks\n            pointing outside root in directory listings. Defaults to False.\n    \"\"\"\n\n    def __init__(\n        self,\n        uri: str,\n        directory: Path,\n        directory_view: bool = False,\n        index: str | Sequence[str] | None = None,\n        root_path: Path | None = None,\n        follow_external_symlink_files: bool = False,\n        follow_external_symlink_dirs: bool = False,\n    ) -> None:\n        if isinstance(index, str):\n            index = [index]\n        elif index is None:\n            index = []\n        self.base = uri.strip(\"/\")\n        self.directory = directory\n        self.directory_view = directory_view\n        self.index = tuple(index)\n        self.root_path = root_path if root_path is not None else directory\n        self.follow_external_symlink_files = follow_external_symlink_files\n        self.follow_external_symlink_dirs = follow_external_symlink_dirs\n\n    async def handle(self, request: Request, path: str):\n        \"\"\"Handle the request.\n\n        Args:\n            request (Request): The incoming request object.\n            path (str): The path to the file to serve.\n\n        Raises:\n            NotFound: If the file is not found.\n            IsADirectoryError: If the path is a directory and directory_view is False.\n\n        Returns:\n            Response: The response object.\n        \"\"\"  # noqa: E501\n        current = unquote(path).strip(\"/\")[len(self.base) :].strip(\"/\")  # noqa: E203\n        for file_name in self.index:\n            index_file = self.directory / current / file_name\n            if index_file.is_file():\n                return await file(index_file)\n\n        if self.directory_view:\n            return self._index(\n                self.directory / current, path, request.app.debug\n            )\n\n        if self.index:\n            raise NotFound(\"File not found\")\n\n        raise IsADirectoryError(f\"{self.directory.as_posix()} is a directory\")\n\n    def _index(self, location: Path, path: str, debug: bool):\n        # Remove empty path elements, append slash\n        if \"//\" in path or not path.endswith(\"/\"):\n            return redirect(\n                \"/\" + \"\".join([f\"{p}/\" for p in path.split(\"/\") if p])\n            )\n\n        # Render file browser\n        page = DirectoryPage(self._iter_files(location), path, debug)\n        return html(page.render())\n\n    def _prepare_file(self, path: Path) -> dict[str, int | str] | None:\n        try:\n            stat = path.stat()\n        except OSError:\n            return None\n        modified = (\n            datetime.fromtimestamp(stat.st_mtime)\n            .isoformat()[:19]\n            .replace(\"T\", \" \")\n        )\n        is_dir = S_ISDIR(stat.st_mode)\n        icon = \"📁\" if is_dir else \"📄\"\n        file_name = path.name\n        if is_dir:\n            file_name += \"/\"\n        return {\n            \"priority\": is_dir * -1,\n            \"file_name\": file_name,\n            \"icon\": icon,\n            \"file_access\": modified,\n            \"file_size\": stat.st_size,\n        }\n\n    def _iter_files(self, location: Path) -> Iterable[FileInfo]:\n        prepared = []\n        for f in location.iterdir():\n            if f.is_symlink() and not _is_path_within_root(f, self.root_path):\n                # External symlink - check if allowed based on type\n                try:\n                    is_dir = f.resolve().is_dir()\n                except OSError:\n                    continue  # Broken symlink\n                if is_dir and not self.follow_external_symlink_dirs:\n                    continue\n                if not is_dir and not self.follow_external_symlink_files:\n                    continue\n            file_info = self._prepare_file(f)\n            if file_info is not None:\n                prepared.append(file_info)\n        for item in sorted(prepared, key=itemgetter(\"priority\", \"file_name\")):\n            del item[\"priority\"]\n            yield cast(FileInfo, item)\n"
  },
  {
    "path": "sanic/handlers/error.py",
    "content": "from __future__ import annotations\n\nfrom sanic.errorpages import BaseRenderer, TextRenderer, exception_response\nfrom sanic.exceptions import ServerError\nfrom sanic.log import error_logger\nfrom sanic.models.handler_types import RouteHandler\nfrom sanic.request.types import Request\nfrom sanic.response import text\nfrom sanic.response.types import HTTPResponse\n\n\nclass ErrorHandler:\n    \"\"\"Process and handle all uncaught exceptions.\n\n    This error handling framework is built into the core that can be extended\n    by the developers to perform a wide range of tasks from recording the error\n    stats to reporting them to an external service that can be used for\n    realtime alerting system.\n\n    Args:\n        base (BaseRenderer): The renderer to use for the error pages.\n    \"\"\"  # noqa: E501\n\n    def __init__(\n        self,\n        base: type[BaseRenderer] = TextRenderer,\n    ):\n        self.cached_handlers: dict[\n            tuple[type[BaseException], str | None], RouteHandler | None\n        ] = {}\n        self.debug = False\n        self.base = base\n\n    def _full_lookup(self, exception, route_name: str | None = None):\n        return self.lookup(exception, route_name)\n\n    def _add(\n        self,\n        key: tuple[type[BaseException], str | None],\n        handler: RouteHandler,\n    ) -> None:\n        if key in self.cached_handlers:\n            exc, name = key\n            if name is None:\n                name = \"__ALL_ROUTES__\"\n\n            message = (\n                f\"Duplicate exception handler definition on: route={name} \"\n                f\"and exception={exc}\"\n            )\n            raise ServerError(message)\n        self.cached_handlers[key] = handler\n\n    def add(self, exception, handler, route_names: list[str] | None = None):\n        \"\"\"Add a new exception handler to an already existing handler object.\n\n        Args:\n            exception (sanic.exceptions.SanicException or Exception): Type\n                of exception that needs to be handled.\n            handler (function): Reference to the function that will\n                handle the exception.\n\n        Returns:\n            None\n\n        \"\"\"  # noqa: E501\n        if route_names:\n            for route in route_names:\n                self._add((exception, route), handler)\n        else:\n            self._add((exception, None), handler)\n\n    def lookup(self, exception, route_name: str | None = None):\n        \"\"\"Lookup the existing instance of `ErrorHandler` and fetch the registered handler for a specific type of exception.\n\n        This method leverages a dict lookup to speedup the retrieval process.\n\n        Args:\n            exception (sanic.exceptions.SanicException or Exception): Type\n                of exception.\n\n        Returns:\n            Registered function if found, ``None`` otherwise.\n\n        \"\"\"  # noqa: E501\n        exception_class = type(exception)\n\n        for name in (route_name, None):\n            exception_key = (exception_class, name)\n            handler = self.cached_handlers.get(exception_key)\n            if handler:\n                return handler\n\n        for name in (route_name, None):\n            for ancestor in type.mro(exception_class):\n                exception_key = (ancestor, name)\n                if exception_key in self.cached_handlers:\n                    handler = self.cached_handlers[exception_key]\n                    self.cached_handlers[(exception_class, route_name)] = (\n                        handler\n                    )\n                    return handler\n\n                if ancestor is BaseException:\n                    break\n        self.cached_handlers[(exception_class, route_name)] = None\n        handler = None\n        return handler\n\n    _lookup = _full_lookup\n\n    def response(self, request, exception):\n        \"\"\"Fetch and executes an exception handler and returns a response object.\n\n        Args:\n            request (sanic.request.Request): Instance of the request.\n            exception (sanic.exceptions.SanicException or Exception): Exception to handle.\n\n        Returns:\n            Wrap the return value obtained from the `default` function or the registered handler for that type of exception.\n\n        \"\"\"  # noqa: E501\n        route_name = request.name if request else None\n        handler = self._lookup(exception, route_name)\n        response = None\n        try:\n            if handler:\n                response = handler(request, exception)\n            if response is None:\n                response = self.default(request, exception)\n        except Exception:\n            try:\n                url = repr(request.url)\n            except AttributeError:  # no cov\n                url = \"unknown\"\n            response_message = (\n                'Exception raised in exception handler \"%s\" for uri: %s'\n            )\n            error_logger.exception(response_message, handler.__name__, url)\n\n            if self.debug:\n                return text(response_message % (handler.__name__, url), 500)\n            else:\n                return text(\"An error occurred while handling an error\", 500)\n        return response\n\n    def default(self, request: Request, exception: Exception) -> HTTPResponse:\n        \"\"\"Provide a default behavior for the objects of ErrorHandler.\n\n        If a developer chooses to extend the ErrorHandler, they can\n        provide a custom implementation for this method to behave in a way\n        they see fit.\n\n        Args:\n            request (sanic.request.Request): Incoming request.\n            exception (sanic.exceptions.SanicException or Exception): Exception object.\n\n        Returns:\n            HTTPResponse: The response object.\n\n        Examples:\n            ```python\n            class CustomErrorHandler(ErrorHandler):\n                def default(self, request: Request, exception: Exception) -> HTTPResponse:\n                    # Custom logic for handling the exception and creating a response\n                    custom_response = my_custom_logic(request, exception)\n                    return custom_response\n\n            app = Sanic(\"MyApp\", error_handler=CustomErrorHandler())\n            ```\n        \"\"\"  # noqa: E501\n        self.log(request, exception)\n        fallback = request.app.config.FALLBACK_ERROR_FORMAT\n        return exception_response(\n            request,\n            exception,\n            debug=self.debug,\n            base=self.base,\n            fallback=fallback,\n        )\n\n    @staticmethod\n    def log(request: Request, exception: Exception) -> None:\n        \"\"\"Logs information about an incoming request and the associated exception.\n\n        Args:\n            request (Request): The incoming request to be logged.\n            exception (Exception): The exception that occurred during the handling of the request.\n\n        Returns:\n            None\n        \"\"\"  # noqa: E501\n        quiet = getattr(exception, \"quiet\", False)\n        noisy = getattr(request.app.config, \"NOISY_EXCEPTIONS\", False)\n        if quiet is False or noisy is True:\n            try:\n                url = repr(request.url)\n            except AttributeError:  # no cov\n                url = \"unknown\"\n\n            error_logger.exception(\n                \"Exception occurred while handling uri: %s\", url\n            )\n"
  },
  {
    "path": "sanic/headers.py",
    "content": "from __future__ import annotations\n\nimport re\n\nfrom collections.abc import Iterable\nfrom typing import Any\nfrom urllib.parse import unquote\n\nfrom sanic.exceptions import InvalidHeader\nfrom sanic.helpers import STATUS_CODES\n\n\n# TODO:\n# - the Options object should be a typed object to allow for less casting\n#   across the application (in request.py for example)\nHeaderIterable = Iterable[tuple[str, Any]]  # Values convertible to str\nHeaderBytesIterable = Iterable[tuple[bytes, bytes]]\nOptions = dict[str, int | str]  # key=value fields in various headers\nOptionsIterable = Iterable[tuple[str, str]]  # May contain duplicate keys\n\n_token, _quoted = r\"([\\w!#$%&'*+\\-.^_`|~]+)\", r'\"([^\"]*)\"'\n_param = re.compile(rf\";\\s*{_token}=(?:{_token}|{_quoted})\", re.ASCII)\n_ipv6 = \"(?:[0-9A-Fa-f]{0,4}:){2,7}[0-9A-Fa-f]{0,4}\"\n_ipv6_re = re.compile(_ipv6)\n_host_re = re.compile(\n    r\"((?:\\[\" + _ipv6 + r\"\\])|[a-zA-Z0-9.\\-]{1,253})(?::(\\d{1,5}))?\"\n)\n\n# RFC's quoted-pair escapes are mostly ignored by browsers. Chrome, Firefox and\n# curl all have different escaping, that we try to handle as well as possible,\n# even though no client escapes in a way that would allow perfect handling.\n\n# For more information, consult ../tests/test_requests.py\n\n\nclass MediaType:\n    \"\"\"A media type, as used in the Accept header.\n\n    This class is a representation of a media type, as used in the Accept\n    header. It encapsulates the type, subtype and any parameters, and\n    provides methods for matching against other media types.\n\n    Two separate methods are provided for searching the list:\n    - 'match' for finding the most preferred match (wildcards supported)\n    -  operator 'in' for checking explicit matches (wildcards as literals)\n\n    Args:\n        type_ (str): The type of the media type.\n        subtype (str): The subtype of the media type.\n        **params (str): Any parameters for the media type.\n    \"\"\"\n\n    def __init__(\n        self,\n        type_: str,\n        subtype: str,\n        **params: str,\n    ):\n        self.type = type_\n        self.subtype = subtype\n        self.q = float(params.get(\"q\", \"1.0\"))\n        self.params = params\n        self.mime = f\"{type_}/{subtype}\"\n        self.key = (\n            -1 * self.q,\n            -1 * len(self.params),\n            self.subtype == \"*\",\n            self.type == \"*\",\n        )\n\n    def __repr__(self):\n        return self.mime + \"\".join(f\";{k}={v}\" for k, v in self.params.items())\n\n    def __eq__(self, other):\n        \"\"\"Check for mime (str or MediaType) identical type/subtype.\n        Parameters such as q are not considered.\"\"\"\n        if isinstance(other, str):\n            # Give a friendly reminder if str contains parameters\n            if \";\" in other:\n                raise ValueError(\"Use match() to compare with parameters\")\n            return self.mime == other\n        if isinstance(other, MediaType):\n            # Ignore parameters silently with MediaType objects\n            return self.mime == other.mime\n        return NotImplemented\n\n    def match(\n        self,\n        mime_with_params: str | MediaType,\n    ) -> MediaType | None:\n        \"\"\"Match this media type against another media type.\n\n        Check if this media type matches the given mime type/subtype.\n        Wildcards are supported both ways on both type and subtype.\n        If mime contains a semicolon, optionally followed by parameters,\n        the parameters of the two media types must match exactly.\n\n        .. note::\n            Use the `==` operator instead to check for literal matches\n            without expanding wildcards.\n\n\n        Args:\n            media_type (str): A type/subtype string to match.\n\n        Returns:\n            MediaType: Returns `self` if the media types are compatible.\n            None: Returns `None` if the media types are not compatible.\n        \"\"\"\n        mt = (\n            MediaType._parse(mime_with_params)\n            if isinstance(mime_with_params, str)\n            else mime_with_params\n        )\n        return (\n            self\n            if (\n                mt\n                # All parameters given in the other media type must match\n                and all(self.params.get(k) == v for k, v in mt.params.items())\n                # Subtype match\n                and (\n                    self.subtype == mt.subtype\n                    or self.subtype == \"*\"\n                    or mt.subtype == \"*\"\n                )\n                # Type match\n                and (\n                    self.type == mt.type or self.type == \"*\" or mt.type == \"*\"\n                )\n            )\n            else None\n        )\n\n    @property\n    def has_wildcard(self) -> bool:\n        \"\"\"Return True if this media type has a wildcard in it.\n\n        Returns:\n            bool: True if this media type has a wildcard in it.\n        \"\"\"\n        return any(part == \"*\" for part in (self.subtype, self.type))\n\n    @classmethod\n    def _parse(cls, mime_with_params: str) -> MediaType | None:\n        mtype = mime_with_params.strip()\n        if \"/\" not in mime_with_params:\n            return None\n\n        mime, *raw_params = mtype.split(\";\")\n        type_, subtype = mime.split(\"/\", 1)\n        if not type_ or not subtype:\n            raise ValueError(f\"Invalid media type: {mtype}\")\n\n        params = {\n            key.strip(): value.strip()\n            for key, value in (param.split(\"=\", 1) for param in raw_params)\n        }\n\n        return cls(type_.lstrip(), subtype.rstrip(), **params)\n\n\nclass Matched:\n    \"\"\"A matching result of a MIME string against a header.\n\n    This class is a representation of a matching result of a MIME string\n    against a header. It encapsulates the MIME string, the header, and\n    provides methods for matching against other MIME strings.\n\n    Args:\n        mime (str): The MIME string to match.\n        header (MediaType): The header to match against, if any.\n    \"\"\"\n\n    def __init__(self, mime: str, header: MediaType | None):\n        self.mime = mime\n        self.header = header\n\n    def __repr__(self):\n        return f\"<{self} matched {self.header}>\" if self else \"<no match>\"\n\n    def __str__(self):\n        return self.mime\n\n    def __bool__(self):\n        return self.header is not None\n\n    def __eq__(self, other: Any) -> bool:\n        try:\n            comp, other_accept = self._compare(other)\n        except TypeError:\n            return False\n\n        return bool(\n            comp\n            and (\n                (self.header and other_accept.header)\n                or (not self.header and not other_accept.header)\n            )\n        )\n\n    def _compare(self, other) -> tuple[bool, Matched]:\n        if isinstance(other, str):\n            parsed = Matched.parse(other)\n            if self.mime == other:\n                return True, parsed\n            other = parsed\n\n        if isinstance(other, Matched):\n            return self.header == other.header, other\n\n        raise TypeError(\n            \"Comparison not supported between unequal \"\n            f\"mime types of '{self.mime}' and '{other}'\"\n        )\n\n    def match(self, other: str | Matched) -> Matched | None:\n        \"\"\"Match this MIME string against another MIME string.\n\n        Check if this MIME string matches the given MIME string. Wildcards are supported both ways on both type and subtype.\n\n        Args:\n            other (str): A MIME string to match.\n\n        Returns:\n            Matched: Returns `self` if the MIME strings are compatible.\n            None: Returns `None` if the MIME strings are not compatible.\n        \"\"\"  # noqa: E501\n        accept = Matched.parse(other) if isinstance(other, str) else other\n        if not self.header or not accept.header:\n            return None\n        if self.header.match(accept.header):\n            return accept\n        return None\n\n    @classmethod\n    def parse(cls, raw: str) -> Matched:\n        media_type = MediaType._parse(raw)\n        return cls(raw, media_type)\n\n\nclass AcceptList(list):\n    \"\"\"A list of media types, as used in the Accept header.\n\n    The Accept header entries are listed in order of preference, starting\n    with the most preferred. This class is a list of `MediaType` objects,\n    that encapsulate also the q value or any other parameters.\n\n    Two separate methods are provided for searching the list:\n    - 'match' for finding the most preferred match (wildcards supported)\n    -  operator 'in' for checking explicit matches (wildcards as literals)\n\n    Args:\n        *args (MediaType): Any number of MediaType objects.\n    \"\"\"\n\n    def match(self, *mimes: str, accept_wildcards=True) -> Matched:\n        \"\"\"Find a media type accepted by the client.\n\n        This method can be used to find which of the media types requested by\n        the client is most preferred against the ones given as arguments.\n\n        The ordering of preference is set by:\n        1. The order set by RFC 7231, s. 5.3.2, giving a higher priority\n            to q values and more specific type definitions,\n        2. The order of the arguments (first is most preferred), and\n        3. The first matching entry on the Accept header.\n\n        Wildcards are matched both ways. A match is usually found, as the\n        Accept headers typically include `*/*`, in particular if the header\n        is missing, is not manually set, or if the client is a browser.\n\n        Note: the returned object behaves as a string of the mime argument\n        that matched, and is empty/falsy if no match was found. The matched\n        header entry `MediaType` or `None` is available as the `m` attribute.\n\n        Args:\n            mimes (List[str]): Any MIME types to search for in order of preference.\n            accept_wildcards (bool): Match Accept entries with wildcards in them.\n\n        Returns:\n            Match: A match object with the mime string and the MediaType object.\n        \"\"\"  # noqa: E501\n        a = sorted(\n            (-acc.q, i, j, mime, acc)\n            for j, acc in enumerate(self)\n            if accept_wildcards or not acc.has_wildcard\n            for i, mime in enumerate(mimes)\n            if acc.match(mime)\n        )\n        return Matched(*(a[0][-2:] if a else (\"\", None)))\n\n    def __str__(self):\n        \"\"\"Format as Accept header value (parsed, not original).\"\"\"\n        return \", \".join(str(m) for m in self)\n\n\ndef parse_accept(accept: str | None) -> AcceptList:\n    \"\"\"Parse an Accept header and order the acceptable media types according to RFC 7231, s. 5.3.2\n\n    https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2\n\n    Args:\n        accept (str): The Accept header value to parse.\n\n    Returns:\n        AcceptList: A list of MediaType objects, ordered by preference.\n\n    Raises:\n        InvalidHeader: If the header value is invalid.\n    \"\"\"  # noqa: E501\n    if not accept:\n        if accept == \"\":\n            return AcceptList()  # Empty header, accept nothing\n        accept = \"*/*\"  # No header means that all types are accepted\n    try:\n        a = [\n            mt\n            for mt in [MediaType._parse(mtype) for mtype in accept.split(\",\")]\n            if mt\n        ]\n        if not a:\n            raise ValueError\n        return AcceptList(sorted(a, key=lambda x: x.key))\n    except ValueError:\n        raise InvalidHeader(f\"Invalid header value in Accept: {accept}\")\n\n\ndef parse_content_header(value: str) -> tuple[str, Options]:\n    \"\"\"Parse content-type and content-disposition header values.\n\n    E.g. `form-data; name=upload; filename=\"file.txt\"` to\n    ('form-data', {'name': 'upload', 'filename': 'file.txt'})\n\n    Mostly identical to cgi.parse_header and werkzeug.parse_options_header\n    but runs faster and handles special characters better.\n\n    Unescapes %22 to `\"` and %0D%0A to `\\n` in field values.\n\n    Args:\n        value (str): The header value to parse.\n\n    Returns:\n        Tuple[str, Options]: The header value and a dict of options.\n    \"\"\"\n    pos = value.find(\";\")\n    if pos == -1:\n        options: dict[str, int | str] = {}\n    else:\n        options = {\n            m.group(1).lower(): (m.group(2) or m.group(3))\n            .replace(\"%22\", '\"')\n            .replace(\"%0D%0A\", \"\\n\")\n            for m in _param.finditer(value[pos:])\n        }\n        value = value[:pos]\n    return value.strip().lower(), options\n\n\n# https://tools.ietf.org/html/rfc7230#section-3.2.6 and\n# https://tools.ietf.org/html/rfc7239#section-4\n# This regex is for *reversed* strings because that works much faster for\n# right-to-left matching than the other way around. Be wary that all things are\n# a bit backwards! _rparam matches forwarded pairs alike \";key=value\"\n_rparam = re.compile(f\"(?:{_token}|{_quoted})={_token}\\\\s*($|[;,])\", re.ASCII)\n\n\ndef parse_forwarded(headers, config) -> Options | None:\n    \"\"\"Parse RFC 7239 Forwarded headers.\n    The value of `by` or `secret` must match `config.FORWARDED_SECRET`\n    :return: dict with keys and values, or None if nothing matched\n    \"\"\"\n    header = headers.getall(\"forwarded\", None)\n    secret = config.FORWARDED_SECRET\n    if header is None or not secret:\n        return None\n    header = \",\".join(header)  # Join multiple header lines\n    if secret not in header:\n        return None\n    # Loop over <separator><key>=<value> elements from right to left\n    sep = pos = None\n    options_list: list[tuple[str, str]] = []\n    found = False\n    for m in _rparam.finditer(header[::-1]):\n        # Start of new element? (on parser skips and non-semicolon right sep)\n        if m.start() != pos or sep != \";\":\n            # Was the previous element (from right) what we wanted?\n            if found:\n                break\n            # Clear values and parse as new element\n            del options_list[:]\n        pos = m.end()\n        val_token, val_quoted, key, sep = m.groups()\n        key = key.lower()[::-1]\n        val = (val_token or val_quoted.replace('\"\\\\', '\"'))[::-1]\n        options_list.append((key, val))\n        if key in (\"secret\", \"by\") and val == secret:\n            found = True\n        # Check if we would return on next round, to avoid useless parse\n        if found and sep != \";\":\n            break\n    # If secret was found, return the matching options in left-to-right order\n    return fwd_normalize(reversed(options_list)) if found else None\n\n\ndef parse_xforwarded(headers, config) -> Options | None:\n    \"\"\"Parse traditional proxy headers.\"\"\"\n    real_ip_header = config.REAL_IP_HEADER\n    proxies_count = config.PROXIES_COUNT\n    addr = real_ip_header and headers.getone(real_ip_header, None)\n    if not addr and proxies_count:\n        assert proxies_count > 0\n        try:\n            # Combine, split and filter multiple headers' entries\n            forwarded_for = headers.getall(config.FORWARDED_FOR_HEADER)\n            proxies = [\n                p\n                for p in (\n                    p.strip() for h in forwarded_for for p in h.split(\",\")\n                )\n                if p\n            ]\n            addr = proxies[-proxies_count]\n        except (KeyError, IndexError):\n            pass\n    # No processing of other headers if no address is found\n    if not addr:\n        return None\n\n    def options():\n        yield \"for\", addr\n        for key, header in (\n            (\"proto\", \"x-scheme\"),\n            (\"proto\", \"x-forwarded-proto\"),  # Overrides X-Scheme if present\n            (\"host\", \"x-forwarded-host\"),\n            (\"port\", \"x-forwarded-port\"),\n            (\"path\", \"x-forwarded-path\"),\n        ):\n            yield key, headers.getone(header, None)\n\n    return fwd_normalize(options())\n\n\ndef fwd_normalize(fwd: OptionsIterable) -> Options:\n    \"\"\"Normalize and convert values extracted from forwarded headers.\n\n    Args:\n        fwd (OptionsIterable): An iterable of key-value pairs.\n\n    Returns:\n        Options: A dict of normalized key-value pairs.\n    \"\"\"\n    ret: dict[str, int | str] = {}\n    for key, val in fwd:\n        if val is not None:\n            try:\n                if key in (\"by\", \"for\"):\n                    ret[key] = fwd_normalize_address(val)\n                elif key in (\"host\", \"proto\"):\n                    ret[key] = val.lower()\n                elif key == \"port\":\n                    ret[key] = int(val)\n                elif key == \"path\":\n                    ret[key] = unquote(val)\n                else:\n                    ret[key] = val\n            except ValueError:\n                pass\n    return ret\n\n\ndef fwd_normalize_address(addr: str) -> str:\n    \"\"\"Normalize address fields of proxy headers.\n\n    Args:\n        addr (str): An address string.\n\n    Returns:\n        str: A normalized address string.\n    \"\"\"\n    if addr == \"unknown\":\n        raise ValueError()  # omit unknown value identifiers\n    if addr.startswith(\"_\"):\n        return addr  # do not lower-case obfuscated strings\n    if _ipv6_re.fullmatch(addr):\n        addr = f\"[{addr}]\"  # bracket IPv6\n    return addr.lower()\n\n\ndef parse_host(host: str) -> tuple[str | None, int | None]:\n    \"\"\"Split host:port into hostname and port.\n\n    Args:\n        host (str): A host string.\n\n    Returns:\n        Tuple[Optional[str], Optional[int]]: A tuple of hostname and port.\n    \"\"\"\n    m = _host_re.fullmatch(host)\n    if not m:\n        return None, None\n    host, port = m.groups()\n    return host.lower(), int(port) if port is not None else None\n\n\n_HTTP1_STATUSLINES = [\n    b\"HTTP/1.1 %d %b\\r\\n\" % (status, STATUS_CODES.get(status, b\"UNKNOWN\"))\n    for status in range(1000)\n]\n\n\ndef format_http1_response(status: int, headers: HeaderBytesIterable) -> bytes:\n    \"\"\"Format a HTTP/1.1 response header.\n\n    Args:\n        status (int): The HTTP status code.\n        headers (HeaderBytesIterable): An iterable of header tuples.\n\n    Returns:\n        bytes: The formatted response header.\n    \"\"\"\n    # Note: benchmarks show that here bytes concat is faster than bytearray,\n    # b\"\".join() or %-formatting. %timeit any changes you make.\n    ret = _HTTP1_STATUSLINES[status]\n    for h in headers:\n        ret += b\"%b: %b\\r\\n\" % h\n    ret += b\"\\r\\n\"\n    return ret\n\n\ndef parse_credentials(\n    header: str | None,\n    prefixes: list | tuple | set | None = None,\n) -> tuple[str | None, str | None]:\n    \"\"\"Parses any header with the aim to retrieve any credentials from it.\n\n    Args:\n        header (Optional[str]): The header to parse.\n        prefixes (Optional[Union[List, Tuple, Set]], optional): The prefixes to look for. Defaults to None.\n\n    Returns:\n        Tuple[Optional[str], Optional[str]]: The prefix and the credentials.\n    \"\"\"  # noqa: E501\n    if not prefixes or not isinstance(prefixes, (list, tuple, set)):\n        prefixes = (\"Basic\", \"Bearer\", \"Token\")\n    if header is not None:\n        for prefix in prefixes:\n            if prefix in header:\n                return prefix, header.partition(prefix)[-1].strip()\n    return None, header\n"
  },
  {
    "path": "sanic/helpers.py",
    "content": "\"\"\"Defines basics of HTTP standard.\"\"\"\n\nimport sys\n\nfrom functools import partial\nfrom importlib import import_module\nfrom inspect import ismodule\n\n\ntry:\n    from ujson import dumps as ujson_dumps\n\n    json_dumps = partial(ujson_dumps, escape_forward_slashes=False)\nexcept ImportError:\n    # This is done in order to ensure that the JSON response is\n    # kept consistent across both ujson and inbuilt json usage.\n    from json import dumps\n\n    json_dumps = partial(dumps, separators=(\",\", \":\"))\n\nSTATUS_CODES: dict[int, bytes] = {\n    100: b\"Continue\",\n    101: b\"Switching Protocols\",\n    102: b\"Processing\",\n    103: b\"Early Hints\",\n    200: b\"OK\",\n    201: b\"Created\",\n    202: b\"Accepted\",\n    203: b\"Non-Authoritative Information\",\n    204: b\"No Content\",\n    205: b\"Reset Content\",\n    206: b\"Partial Content\",\n    207: b\"Multi-Status\",\n    208: b\"Already Reported\",\n    226: b\"IM Used\",\n    300: b\"Multiple Choices\",\n    301: b\"Moved Permanently\",\n    302: b\"Found\",\n    303: b\"See Other\",\n    304: b\"Not Modified\",\n    305: b\"Use Proxy\",\n    307: b\"Temporary Redirect\",\n    308: b\"Permanent Redirect\",\n    400: b\"Bad Request\",\n    401: b\"Unauthorized\",\n    402: b\"Payment Required\",\n    403: b\"Forbidden\",\n    404: b\"Not Found\",\n    405: b\"Method Not Allowed\",\n    406: b\"Not Acceptable\",\n    407: b\"Proxy Authentication Required\",\n    408: b\"Request Timeout\",\n    409: b\"Conflict\",\n    410: b\"Gone\",\n    411: b\"Length Required\",\n    412: b\"Precondition Failed\",\n    413: b\"Request Entity Too Large\",\n    414: b\"Request-URI Too Long\",\n    415: b\"Unsupported Media Type\",\n    416: b\"Requested Range Not Satisfiable\",\n    417: b\"Expectation Failed\",\n    418: b\"I'm a teapot\",\n    422: b\"Unprocessable Entity\",\n    423: b\"Locked\",\n    424: b\"Failed Dependency\",\n    426: b\"Upgrade Required\",\n    428: b\"Precondition Required\",\n    429: b\"Too Many Requests\",\n    431: b\"Request Header Fields Too Large\",\n    451: b\"Unavailable For Legal Reasons\",\n    500: b\"Internal Server Error\",\n    501: b\"Not Implemented\",\n    502: b\"Bad Gateway\",\n    503: b\"Service Unavailable\",\n    504: b\"Gateway Timeout\",\n    505: b\"HTTP Version Not Supported\",\n    506: b\"Variant Also Negotiates\",\n    507: b\"Insufficient Storage\",\n    508: b\"Loop Detected\",\n    510: b\"Not Extended\",\n    511: b\"Network Authentication Required\",\n}\n\n# According to https://tools.ietf.org/html/rfc2616#section-7.1\n_ENTITY_HEADERS = frozenset(\n    [\n        \"allow\",\n        \"content-encoding\",\n        \"content-language\",\n        \"content-length\",\n        \"content-location\",\n        \"content-md5\",\n        \"content-range\",\n        \"content-type\",\n        \"expires\",\n        \"last-modified\",\n        \"extension-header\",\n    ]\n)\n\n# According to https://tools.ietf.org/html/rfc2616#section-13.5.1\n_HOP_BY_HOP_HEADERS = frozenset(\n    [\n        \"connection\",\n        \"keep-alive\",\n        \"proxy-authenticate\",\n        \"proxy-authorization\",\n        \"te\",\n        \"trailers\",\n        \"transfer-encoding\",\n        \"upgrade\",\n    ]\n)\n\n\ndef has_message_body(status):\n    \"\"\"\n    According to the following RFC message body and length SHOULD NOT\n    be included in responses status 1XX, 204 and 304.\n    https://tools.ietf.org/html/rfc2616#section-4.4\n    https://tools.ietf.org/html/rfc2616#section-4.3\n    \"\"\"\n    return status not in (204, 304) and not (100 <= status < 200)\n\n\ndef is_entity_header(header):\n    \"\"\"Checks if the given header is an Entity Header\"\"\"\n    return header.lower() in _ENTITY_HEADERS\n\n\ndef is_hop_by_hop_header(header):\n    \"\"\"Checks if the given header is a Hop By Hop header\"\"\"\n    return header.lower() in _HOP_BY_HOP_HEADERS\n\n\ndef import_string(module_name, package=None):\n    \"\"\"\n    import a module or class by string path.\n\n    :module_name: str with path of module or path to import and\n    instantiate a class\n    :returns: a module object or one instance from class if\n    module_name is a valid path to class\n\n    \"\"\"\n    module, klass = module_name.rsplit(\".\", 1)\n    module = import_module(module, package=package)\n    obj = getattr(module, klass)\n    if ismodule(obj):\n        return obj\n    return obj()\n\n\ndef is_atty() -> bool:\n    return bool(sys.stdout and sys.stdout.isatty())\n\n\nclass Default:\n    \"\"\"\n    It is used to replace `None` or `object()` as a sentinel\n    that represents a default value. Sometimes we want to set\n    a value to `None` so we cannot use `None` to represent the\n    default value, and `object()` is hard to be typed.\n    \"\"\"\n\n    def __repr__(self):\n        return \"<Default>\"\n\n    def __str__(self) -> str:\n        return self.__repr__()\n\n\n_default = Default()\n"
  },
  {
    "path": "sanic/http/__init__.py",
    "content": "from .constants import Stage\nfrom .http1 import Http\nfrom .http3 import Http3\n\n\n__all__ = (\"Http\", \"Stage\", \"Http3\")\n"
  },
  {
    "path": "sanic/http/constants.py",
    "content": "from enum import Enum, IntEnum\n\n\nclass Stage(Enum):\n    \"\"\"Enum for representing the stage of the request/response cycle\n\n    | ``IDLE``  Waiting for request\n    | ``REQUEST``  Request headers being received\n    | ``HANDLER``  Headers done, handler running\n    | ``RESPONSE``  Response headers sent, body in progress\n    | ``FAILED``  Unrecoverable state (error while sending response)\n    |\n    \"\"\"\n\n    IDLE = 0  # Waiting for request\n    REQUEST = 1  # Request headers being received\n    HANDLER = 3  # Headers done, handler running\n    RESPONSE = 4  # Response headers sent, body in progress\n    FAILED = 100  # Unrecoverable state (error while sending response)\n\n\nclass HTTP(IntEnum):\n    \"\"\"Enum for representing HTTP versions\"\"\"\n\n    VERSION_1 = 1\n    VERSION_3 = 3\n\n    def display(self) -> str:\n        value = 1.1 if self.value == 1 else self.value\n        return f\"HTTP/{value}\"\n"
  },
  {
    "path": "sanic/http/http1.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\n\nif TYPE_CHECKING:\n    from sanic.request import Request\n    from sanic.response import BaseHTTPResponse\n\nfrom asyncio import CancelledError, sleep\nfrom time import perf_counter\n\nfrom sanic.compat import Header\nfrom sanic.exceptions import (\n    BadRequest,\n    ExpectationFailed,\n    PayloadTooLarge,\n    RequestCancelled,\n    ServerError,\n    ServiceUnavailable,\n)\nfrom sanic.headers import format_http1_response\nfrom sanic.helpers import has_message_body\nfrom sanic.http.constants import Stage\nfrom sanic.http.stream import Stream\nfrom sanic.log import access_logger, error_logger, logger\nfrom sanic.touchup import TouchUpMeta\n\n\nHTTP_CONTINUE = b\"HTTP/1.1 100 Continue\\r\\n\\r\\n\"\n\n\nclass Http(Stream, metaclass=TouchUpMeta):\n    \"\"\" \"Internal helper for managing the HTTP/1.1 request/response cycle.\n\n    Raises:\n        BadRequest: If the request body is malformed.\n        Exception: If the request is malformed.\n        ExpectationFailed: If the request is malformed.\n        PayloadTooLarge: If the request body exceeds the size limit.\n        RuntimeError: If the response status is invalid.\n        ServerError: If the handler does not produce a response.\n        ServerError: If the response is bigger than the content-length.\n    \"\"\"\n\n    HEADER_CEILING = 16_384\n    HEADER_MAX_SIZE = 0\n    __touchup__ = (\n        \"http1_request_header\",\n        \"http1_response_header\",\n        \"read\",\n    )\n    __slots__ = [\n        \"_send\",\n        \"_receive_more\",\n        \"dispatch\",\n        \"recv_buffer\",\n        \"protocol\",\n        \"expecting_continue\",\n        \"stage\",\n        \"keep_alive\",\n        \"head_only\",\n        \"request\",\n        \"exception\",\n        \"url\",\n        \"request_body\",\n        \"request_bytes\",\n        \"request_bytes_left\",\n        \"response\",\n        \"response_func\",\n        \"response_size\",\n        \"response_bytes_left\",\n        \"upgrade_websocket\",\n        \"perft0\",\n    ]\n\n    def __init__(self, protocol):\n        self._send = protocol.send\n        self._receive_more = protocol.receive_more\n        self.recv_buffer = protocol.recv_buffer\n        self.protocol = protocol\n        self.keep_alive = True\n        self.stage: Stage = Stage.IDLE\n        self.dispatch = self.protocol.app.dispatch\n\n    def init_for_request(self):\n        \"\"\"Init/reset all per-request variables.\"\"\"\n        self.exception = None\n        self.expecting_continue: bool = False\n        self.head_only = None\n        self.request_body = None\n        self.request_bytes = None\n        self.request_bytes_left = None\n        self.request_max_size = self.protocol.request_max_size\n        self.request: Request = None\n        self.response: BaseHTTPResponse = None\n        self.upgrade_websocket = False\n        self.url = None\n        self.perft0 = None\n\n    def __bool__(self):\n        \"\"\"Test if request handling is in progress\"\"\"\n        return self.stage in (Stage.HANDLER, Stage.RESPONSE)\n\n    async def http1(self):\n        \"\"\"HTTP 1.1 connection handler\"\"\"\n        # Handle requests while the connection stays reusable\n        while self.keep_alive and self.stage is Stage.IDLE:\n            self.init_for_request()\n            # Wait for incoming bytes (in IDLE stage)\n            if not self.recv_buffer:\n                await self._receive_more()\n            self.stage = Stage.REQUEST\n            try:\n                # Receive and handle a request\n                self.response_func = self.http1_response_header\n\n                await self.http1_request_header()\n\n                self.stage = Stage.HANDLER\n                self.perft0 = perf_counter()\n                self.request.conn_info = self.protocol.conn_info\n                await self.protocol.request_handler(self.request)\n\n                # Handler finished, response should've been sent\n                if self.stage is Stage.HANDLER and not self.upgrade_websocket:\n                    raise ServerError(\"Handler produced no response\")\n\n                if self.stage is Stage.RESPONSE:\n                    await self.response.send(end_stream=True)\n            except CancelledError as exc:\n                # Write an appropriate response before exiting\n                if not self.protocol.transport:\n                    logger.info(\n                        f\"Request: {self.request.method} {self.request.url} \"\n                        \"stopped. Transport is closed.\"\n                    )\n                    return\n                e = (\n                    RequestCancelled()\n                    if self.protocol.conn_info.lost\n                    else (self.exception or exc)\n                )\n                self.exception = None\n                self.keep_alive = False\n                await self.error_response(e)\n            except Exception as e:\n                # Write an error response\n                await self.error_response(e)\n\n            # Try to consume any remaining request body\n            if self.request_body:\n                if self.response and 200 <= self.response.status < 300:\n                    error_logger.error(f\"{self.request} body not consumed.\")\n                # Limit the size because the handler may have set it infinite\n                self.request_max_size = min(\n                    self.request_max_size, self.protocol.request_max_size\n                )\n                try:\n                    async for _ in self:\n                        pass\n                except PayloadTooLarge:\n                    # We won't read the body and that may cause httpx and\n                    # tests to fail. This little delay allows clients to push\n                    # a small request into network buffers before we close the\n                    # socket, so that they are then able to read the response.\n                    await sleep(0.001)\n                    self.keep_alive = False\n\n            # Clean up to free memory and for the next request\n            if self.request:\n                self.request.stream = None\n                if self.response:\n                    self.response.stream = None\n\n    async def http1_request_header(self):  # no cov\n        \"\"\"Receive and parse request header into self.request.\"\"\"\n        # Receive until full header is in buffer\n        buf = self.recv_buffer\n        pos = 0\n\n        while True:\n            pos = buf.find(b\"\\r\\n\\r\\n\", pos)\n            if pos != -1:\n                break\n\n            pos = max(0, len(buf) - 3)\n            if pos >= self.HEADER_MAX_SIZE:\n                break\n\n            await self._receive_more()\n\n        if pos >= self.HEADER_MAX_SIZE:\n            raise PayloadTooLarge(\"Request header exceeds the size limit\")\n\n        # Parse header content\n        try:\n            head = buf[:pos]\n            raw_headers = head.decode(errors=\"surrogateescape\")\n            reqline, *split_headers = raw_headers.split(\"\\r\\n\")\n            method, self.url, protocol = reqline.split(\" \")\n\n            await self.dispatch(\n                \"http.lifecycle.read_head\",\n                inline=True,\n                context={\"head\": bytes(head)},\n            )\n\n            if protocol == \"HTTP/1.1\":\n                self.keep_alive = True\n            elif protocol == \"HTTP/1.0\":\n                self.keep_alive = False\n            else:\n                raise Exception  # Raise a Bad Request on try-except\n\n            self.head_only = method.upper() == \"HEAD\"\n            request_body = False\n            headers = []\n\n            for name, value in (h.split(\":\", 1) for h in split_headers):\n                name, value = h = name.lower(), value.lstrip()\n\n                if name in (\"content-length\", \"transfer-encoding\"):\n                    if request_body:\n                        raise ValueError(\n                            \"Duplicate Content-Length or Transfer-Encoding\"\n                        )\n                    request_body = True\n                elif name == \"connection\":\n                    self.keep_alive = value.lower() == \"keep-alive\"\n\n                headers.append(h)\n        except Exception:\n            raise BadRequest(\"Bad Request\")\n\n        if not self.protocol.app.config.KEEP_ALIVE:\n            self.keep_alive = False\n\n        headers_instance = Header(headers)\n        self.upgrade_websocket = (\n            headers_instance.getone(\"upgrade\", \"\").lower() == \"websocket\"\n        )\n\n        try:\n            url_bytes = self.url.encode(\"ASCII\")\n        except UnicodeEncodeError:\n            raise BadRequest(\"URL may only contain US-ASCII characters.\")\n\n        # Prepare a Request object\n        request = self.protocol.request_class(\n            url_bytes=url_bytes,\n            headers=headers_instance,\n            head=bytes(head),\n            version=protocol[5:],\n            method=method,\n            transport=self.protocol.transport,\n            app=self.protocol.app,\n        )\n        self.protocol.request_class._current.set(request)\n        await self.dispatch(\n            \"http.lifecycle.request\",\n            inline=True,\n            context={\"request\": request},\n        )\n\n        # Prepare for request body\n        self.request_bytes_left = self.request_bytes = 0\n        if request_body:\n            headers = request.headers\n            expect = headers.getone(\"expect\", None)\n\n            if expect is not None:\n                if expect.lower() == \"100-continue\":\n                    self.expecting_continue = True\n                else:\n                    raise ExpectationFailed(f\"Unknown Expect: {expect}\")\n\n            if headers.getone(\"transfer-encoding\", None) == \"chunked\":\n                self.request_body = \"chunked\"\n                pos -= 2  # One CRLF stays in buffer\n            else:\n                self.request_body = True\n                try:\n                    self.request_bytes_left = self.request_bytes = (\n                        self._safe_int(headers[\"content-length\"])\n                    )\n                except Exception:\n                    raise BadRequest(\"Bad content-length\")\n\n        # Remove header and its trailing CRLF\n        del buf[: pos + 4]\n        self.request, request.stream = request, self\n        self.protocol.state[\"requests_count\"] += 1\n\n    async def http1_response_header(\n        self, data: bytes, end_stream: bool\n    ) -> None:  # no cov\n        \"\"\"Format response header and send it.\"\"\"\n        res = self.response\n\n        # Compatibility with simple response body\n        if not data and getattr(res, \"body\", None):\n            data, end_stream = res.body, True  # type: ignore\n\n        size = len(data)\n        headers = res.headers\n        status = res.status\n        self.response_size = size\n\n        if not isinstance(status, int) or status < 200:\n            raise RuntimeError(f\"Invalid response status {status!r}\")\n\n        if not has_message_body(status):\n            # Header-only response status\n            self.response_func = None\n            if (\n                data\n                or not end_stream\n                or \"content-length\" in headers\n                or \"transfer-encoding\" in headers\n            ):\n                data, size, end_stream = b\"\", 0, True\n                headers.pop(\"content-length\", None)\n                headers.pop(\"transfer-encoding\", None)\n                logger.warning(\n                    f\"Message body set in response on {self.request.path}. \"\n                    f\"A {status} response may only have headers, no body.\"\n                )\n        elif self.head_only and \"content-length\" in headers:\n            self.response_func = None\n        elif end_stream:\n            # Non-streaming response (all in one block)\n            headers[\"content-length\"] = size\n            self.response_func = None\n        elif \"content-length\" in headers:\n            # Streaming response with size known in advance\n            self.response_bytes_left = int(headers[\"content-length\"]) - size\n            self.response_func = self.http1_response_normal\n        else:\n            # Length not known, use chunked encoding\n            headers[\"transfer-encoding\"] = \"chunked\"\n            data = b\"%x\\r\\n%b\\r\\n\" % (size, data) if size else b\"\"\n            self.response_func = self.http1_response_chunked\n\n        if self.head_only:\n            # Head request: don't send body\n            data = b\"\"\n            self.response_func = self.head_response_ignored\n\n        headers[\"connection\"] = \"keep-alive\" if self.keep_alive else \"close\"\n\n        # This header may be removed or modified by the AltSvcCheck Touchup\n        # service. At server start, we either remove this header from ever\n        # being assigned, or we change the value as required.\n        headers[\"alt-svc\"] = \"\"\n\n        ret = format_http1_response(status, res.processed_headers)\n        if data:\n            ret += data\n\n        # Send a 100-continue if expected and not Expectation Failed\n        if self.expecting_continue:\n            self.expecting_continue = False\n            if status != 417:\n                ret = HTTP_CONTINUE + ret\n\n        # Send response\n        if self.protocol.access_log:\n            self.log_response()\n\n        await self._send(ret)\n        self.stage = Stage.IDLE if end_stream else Stage.RESPONSE\n\n    def head_response_ignored(self, data: bytes, end_stream: bool) -> None:\n        \"\"\"HEAD response: body data silently ignored.\"\"\"\n        if end_stream:\n            self.response_func = None\n            self.stage = Stage.IDLE\n\n    async def http1_response_chunked(\n        self, data: bytes, end_stream: bool\n    ) -> None:\n        \"\"\"Format a part of response body in chunked encoding.\"\"\"\n        # Chunked encoding\n        size = len(data)\n        if end_stream:\n            await self._send(\n                b\"%x\\r\\n%b\\r\\n0\\r\\n\\r\\n\" % (size, data)\n                if size\n                else b\"0\\r\\n\\r\\n\"\n            )\n            self.response_func = None\n            self.stage = Stage.IDLE\n        elif size:\n            await self._send(b\"%x\\r\\n%b\\r\\n\" % (size, data))\n\n    async def http1_response_normal(\n        self, data: bytes, end_stream: bool\n    ) -> None:\n        \"\"\"Format / keep track of non-chunked response.\"\"\"\n        bytes_left = self.response_bytes_left - len(data)\n        if bytes_left <= 0:\n            if bytes_left < 0:\n                raise ServerError(\"Response was bigger than content-length\")\n\n            await self._send(data)\n            self.response_func = None\n            self.stage = Stage.IDLE\n        else:\n            if end_stream:\n                raise ServerError(\"Response was smaller than content-length\")\n\n            await self._send(data)\n        self.response_bytes_left = bytes_left\n\n    async def error_response(self, exception: Exception) -> None:\n        \"\"\"Handle response when exception encountered\"\"\"\n        # Disconnect after an error if in any other state than handler\n        if self.stage is not Stage.HANDLER:\n            self.keep_alive = False\n\n        # Request failure? Respond but then disconnect\n        if self.stage is Stage.REQUEST:\n            self.stage = Stage.HANDLER\n\n        # From request and handler states we can respond, otherwise be silent\n        if self.stage is Stage.HANDLER:\n            app = self.protocol.app\n\n            if self.request is None:\n                self.create_empty_request()\n\n            request_middleware = not isinstance(\n                exception, (ServiceUnavailable, RequestCancelled)\n            )\n            try:\n                await app.handle_exception(\n                    self.request, exception, request_middleware\n                )\n            except Exception as e:\n                await app.handle_exception(self.request, e, False)\n\n    def create_empty_request(self) -> None:\n        \"\"\"Create an empty request object for error handling use.\n\n        Current error handling code needs a request object that won't exist\n        if an error occurred during before a request was received. Create a\n        bogus response for error handling use.\n        \"\"\"\n\n        # Reformat any URL already received with \\xHH escapes for better logs\n        url_bytes = (\n            self.url.encode(errors=\"surrogateescape\")\n            .decode(\"ASCII\", errors=\"backslashreplace\")\n            .encode(\"ASCII\")\n            if self.url\n            else b\"*\"\n        )\n\n        # FIXME: Avoid this by refactoring error handling and response code\n        self.request = self.protocol.request_class(\n            url_bytes=url_bytes,\n            headers=Header({}),\n            version=\"1.1\",\n            method=\"NONE\",\n            transport=self.protocol.transport,\n            app=self.protocol.app,\n        )\n        self.request.stream = self\n\n    def log_response(self) -> None:\n        \"\"\"Helper method provided to enable the logging of responses in case if the `HttpProtocol.access_log` is enabled.\"\"\"  # noqa: E501\n        req, res = self.request, self.response\n        extra = {\n            \"status\": getattr(res, \"status\", 0),\n            \"byte\": res.headers.get(\"content-length\", 0)\n            if res.headers.get(\"transfer-encoding\") != \"chunked\"\n            else \"chunked\",\n            \"host\": f\"{id(self.protocol.transport):X}\"[-5:-1] + \"unx\",\n            \"request\": \"nil\",\n            \"duration\": (\n                f\" {1000 * (perf_counter() - self.perft0):.1f}ms\"\n                if self.perft0 is not None\n                else \"\"\n            ),\n        }\n        if ip := req.client_ip:\n            extra[\"host\"] = f\"{ip}:{req.port}\"\n        extra[\"request\"] = f\"{req.method} {req.url}\"\n        access_logger.info(\"\", extra=extra)\n\n    # Request methods\n\n    async def __aiter__(self):\n        \"\"\"Async iterate over request body.\"\"\"\n        while self.request_body:\n            data = await self.read()\n\n            if data:\n                yield data\n\n    async def read(self) -> bytes | None:  # no cov\n        \"\"\"Read some bytes of request body.\"\"\"\n\n        # Send a 100-continue if needed\n        if self.expecting_continue:\n            self.expecting_continue = False\n            await self._send(HTTP_CONTINUE)\n\n        # Receive request body chunk\n        buf = self.recv_buffer\n        if self.request_bytes_left == 0 and self.request_body == \"chunked\":\n            # Process a chunk header: \\r\\n<size>[;<chunk extensions>]\\r\\n\n            while True:\n                pos = buf.find(b\"\\r\\n\", 3)\n\n                if pos != -1:\n                    break\n\n                if len(buf) > 64:\n                    self.keep_alive = False\n                    raise BadRequest(\"Bad chunked encoding\")\n\n                await self._receive_more()\n\n            try:\n                raw = buf[2:pos].split(b\";\", 1)[0].decode()\n                size = self._safe_int(raw, 16)\n            except Exception:\n                self.keep_alive = False\n                raise BadRequest(\"Bad chunked encoding\")\n\n            if size <= 0:\n                self.request_body = None\n\n                if size < 0:\n                    self.keep_alive = False\n                    raise BadRequest(\"Bad chunked encoding\")\n\n                # Consume CRLF, chunk size 0 and the two CRLF that follow\n                pos += 4\n                # Might need to wait for the final CRLF\n                while len(buf) < pos:\n                    await self._receive_more()\n                del buf[:pos]\n                return None\n\n            # Remove CRLF, chunk size and the CRLF that follows\n            del buf[: pos + 2]\n\n            self.request_bytes_left = size\n            self.request_bytes += size\n\n        # Request size limit\n        if self.request_bytes > self.request_max_size:\n            self.keep_alive = False\n            raise PayloadTooLarge(\"Request body exceeds the size limit\")\n\n        # End of request body?\n        if not self.request_bytes_left:\n            self.request_body = None\n            return None\n\n        # At this point we are good to read/return up to request_bytes_left\n        if not buf:\n            await self._receive_more()\n\n        data = bytes(buf[: self.request_bytes_left])\n        size = len(data)\n\n        del buf[:size]\n\n        self.request_bytes_left -= size\n\n        await self.dispatch(\n            \"http.lifecycle.read_body\",\n            inline=True,\n            context={\"body\": data},\n        )\n\n        return data\n\n    # Response methods\n\n    def respond(self, response: BaseHTTPResponse) -> BaseHTTPResponse:\n        \"\"\"Initiate new streaming response.\n\n        Nothing is sent until the first send() call on the returned object, and\n        calling this function multiple times will just alter the response to be\n        given.\n        \"\"\"\n        if self.stage is not Stage.HANDLER:\n            self.stage = Stage.FAILED\n            raise RuntimeError(\"Response already started\")\n\n        # Disconnect any earlier but unused response object\n        if self.response is not None:\n            self.response.stream = None\n\n        # Connect and return the response\n        self.response, response.stream = response, self\n        return response\n\n    @property\n    def send(self):\n        return self.response_func\n\n    @classmethod\n    def set_header_max_size(cls, *sizes: int):\n        cls.HEADER_MAX_SIZE = min(\n            *sizes,\n            cls.HEADER_CEILING,\n        )\n\n    @staticmethod\n    def _safe_int(value: str, base: int = 10) -> int:\n        if \"-\" in value or \"+\" in value or \"_\" in value:\n            raise ValueError\n        return int(value, base)\n"
  },
  {
    "path": "sanic/http/http3.py",
    "content": "from __future__ import annotations\n\nimport asyncio\n\nfrom abc import ABC, abstractmethod\nfrom ssl import SSLContext\nfrom typing import TYPE_CHECKING, Any, Callable, cast\n\nfrom sanic.compat import Header\nfrom sanic.constants import LocalCertCreator\nfrom sanic.exceptions import (\n    BadRequest,\n    PayloadTooLarge,\n    SanicException,\n    ServerError,\n)\nfrom sanic.helpers import has_message_body\nfrom sanic.http.constants import Stage\nfrom sanic.http.stream import Stream\nfrom sanic.http.tls.context import CertSelector, SanicSSLContext\nfrom sanic.log import Colors, logger\nfrom sanic.models.protocol_types import TransportProtocol\nfrom sanic.models.server_types import ConnInfo\n\n\ntry:\n    from aioquic.h0.connection import H0_ALPN, H0Connection\n    from aioquic.h3.connection import H3_ALPN, H3Connection\n    from aioquic.h3.events import (\n        DatagramReceived,\n        DataReceived,\n        H3Event,\n        HeadersReceived,\n        WebTransportStreamDataReceived,\n    )\n    from aioquic.quic.configuration import QuicConfiguration\n    from aioquic.tls import SessionTicket\n\n    HTTP3_AVAILABLE = True\nexcept ModuleNotFoundError:  # no cov\n    HTTP3_AVAILABLE = False\n\nif TYPE_CHECKING:\n    from sanic import Sanic\n    from sanic.request import Request\n    from sanic.response import BaseHTTPResponse\n    from sanic.server.protocols.http_protocol import Http3Protocol\n\n    HttpConnection = H0Connection | H3Connection\n\n\nclass HTTP3Transport(TransportProtocol):\n    \"\"\"HTTP/3 transport implementation.\"\"\"\n\n    __slots__ = (\"_protocol\",)\n\n    def __init__(self, protocol: Http3Protocol):\n        self._protocol = protocol\n\n    def get_protocol(self) -> Http3Protocol:\n        return self._protocol\n\n    def get_extra_info(self, info: str, default: Any = None) -> Any:\n        if (\n            info in (\"socket\", \"sockname\", \"peername\")\n            and self._protocol._transport\n        ):\n            return self._protocol._transport.get_extra_info(info, default)\n        elif info == \"network_paths\":\n            return self._protocol._quic._network_paths\n        elif info == \"ssl_context\":\n            return self._protocol.app.state.ssl\n        return default\n\n\nclass Receiver(ABC):\n    \"\"\"HTTP/3 receiver base class.\"\"\"\n\n    future: asyncio.Future\n\n    def __init__(self, transmit, protocol, request: Request) -> None:\n        self.transmit = transmit\n        self.protocol = protocol\n        self.request = request\n\n    @abstractmethod\n    async def run(self):  # no cov\n        ...\n\n\nclass HTTPReceiver(Receiver, Stream):\n    \"\"\"HTTP/3 receiver implementation.\"\"\"\n\n    stage: Stage\n    request: Request\n\n    def __init__(self, *args, **kwargs) -> None:\n        super().__init__(*args, **kwargs)\n        self.request_body = None\n        self.stage = Stage.IDLE\n        self.headers_sent = False\n        self.response: BaseHTTPResponse | None = None\n        self.request_max_size = self.protocol.request_max_size\n        self.request_bytes = 0\n\n    async def run(self, exception: Exception | None = None):\n        \"\"\"Handle the request and response cycle.\"\"\"\n        self.stage = Stage.HANDLER\n        self.head_only = self.request.method.upper() == \"HEAD\"\n\n        if exception:\n            logger.info(  # no cov\n                f\"{Colors.BLUE}[exception]: \"\n                f\"{Colors.RED}{exception}{Colors.END}\",\n                exc_info=True,\n                extra={\"verbosity\": 1},\n            )\n            await self.error_response(exception)\n        else:\n            try:\n                logger.info(  # no cov\n                    f\"{Colors.BLUE}[request]:{Colors.END} {self.request}\",\n                    extra={\"verbosity\": 1},\n                )\n                await self.protocol.request_handler(self.request)\n            except Exception as e:  # no cov\n                # This should largely be handled within the request handler.\n                # But, just in case...\n                await self.run(e)\n        self.stage = Stage.IDLE\n\n    async def error_response(self, exception: Exception) -> None:\n        \"\"\"Handle response when exception encountered\"\"\"\n        # From request and handler states we can respond, otherwise be silent\n        app = self.protocol.app\n\n        await app.handle_exception(self.request, exception)\n\n    def _prepare_headers(\n        self, response: BaseHTTPResponse\n    ) -> list[tuple[bytes, bytes]]:\n        size = len(response.body) if response.body else 0\n        headers = response.headers\n        status = response.status\n\n        if not has_message_body(status) and (\n            size\n            or \"content-length\" in headers\n            or \"transfer-encoding\" in headers\n        ):\n            headers.pop(\"content-length\", None)\n            headers.pop(\"transfer-encoding\", None)\n            logger.warning(  # no cov\n                f\"Message body set in response on {self.request.path}. \"\n                f\"A {status} response may only have headers, no body.\"\n            )\n        elif \"content-length\" not in headers:\n            if size:\n                headers[\"content-length\"] = size\n            else:\n                headers[\"transfer-encoding\"] = \"chunked\"\n\n        headers = [\n            (b\":status\", str(response.status).encode()),\n            *response.processed_headers,\n        ]\n        return headers\n\n    def send_headers(self) -> None:\n        \"\"\"Send response headers to client\"\"\"\n        logger.debug(  # no cov\n            f\"{Colors.BLUE}[send]: {Colors.GREEN}HEADERS{Colors.END}\",\n            extra={\"verbosity\": 2},\n        )\n        if not self.response:\n            raise RuntimeError(\"no response\")\n\n        response = self.response\n        headers = self._prepare_headers(response)\n\n        self.protocol.connection.send_headers(\n            stream_id=self.request.stream_id,\n            headers=headers,\n        )\n        self.headers_sent = True\n        self.stage = Stage.RESPONSE\n\n        if self.response.body and not self.head_only:\n            self._send(self.response.body, False)\n        elif self.head_only:\n            self.future.cancel()\n\n    def respond(self, response: BaseHTTPResponse) -> BaseHTTPResponse:\n        \"\"\"Prepare response to client\"\"\"\n        logger.debug(  # no cov\n            f\"{Colors.BLUE}[respond]:{Colors.END} {response}\",\n            extra={\"verbosity\": 2},\n        )\n\n        if self.stage is not Stage.HANDLER:\n            self.stage = Stage.FAILED\n            raise RuntimeError(\"Response already started\")\n\n        # Disconnect any earlier but unused response object\n        if self.response is not None:\n            self.response.stream = None\n\n        self.response, response.stream = response, self\n\n        return response\n\n    def receive_body(self, data: bytes) -> None:\n        \"\"\"Receive request body from client\"\"\"\n        self.request_bytes += len(data)\n        if self.request_bytes > self.request_max_size:\n            raise PayloadTooLarge(\"Request body exceeds the size limit\")\n\n        self.request.body += data\n\n    async def send(self, data: bytes, end_stream: bool) -> None:\n        \"\"\"Send data to client\"\"\"\n        logger.debug(  # no cov\n            f\"{Colors.BLUE}[send]: {Colors.GREEN}data={data.decode()} \"\n            f\"end_stream={end_stream}{Colors.END}\",\n            extra={\"verbosity\": 2},\n        )\n        self._send(data, end_stream)\n\n    def _send(self, data: bytes, end_stream: bool) -> None:\n        if not self.headers_sent:\n            self.send_headers()\n        if self.stage is not Stage.RESPONSE:\n            raise ServerError(f\"not ready to send: {self.stage}\")\n\n        # Chunked\n        if (\n            self.response\n            and self.response.headers.get(\"transfer-encoding\") == \"chunked\"\n        ):\n            size = len(data)\n            if end_stream:\n                data = (\n                    b\"%x\\r\\n%b\\r\\n0\\r\\n\\r\\n\" % (size, data)\n                    if size\n                    else b\"0\\r\\n\\r\\n\"\n                )\n            elif size:\n                data = b\"%x\\r\\n%b\\r\\n\" % (size, data)\n\n        logger.debug(  # no cov\n            f\"{Colors.BLUE}[transmitting]{Colors.END}\",\n            extra={\"verbosity\": 2},\n        )\n        self.protocol.connection.send_data(\n            stream_id=self.request.stream_id,\n            data=data,\n            end_stream=end_stream,\n        )\n        self.transmit()\n\n        if end_stream:\n            self.stage = Stage.IDLE\n\n\nclass WebsocketReceiver(Receiver):  # noqa\n    \"\"\"Websocket receiver implementation.\"\"\"\n\n    async def run(self): ...\n\n\nclass WebTransportReceiver(Receiver):  # noqa\n    \"\"\"WebTransport receiver implementation.\"\"\"\n\n    async def run(self): ...\n\n\nclass Http3:\n    \"\"\"Internal helper for managing the HTTP/3 request/response cycle\"\"\"\n\n    if HTTP3_AVAILABLE:\n        HANDLER_PROPERTY_MAPPING = {\n            DataReceived: \"stream_id\",\n            HeadersReceived: \"stream_id\",\n            DatagramReceived: \"flow_id\",\n            WebTransportStreamDataReceived: \"session_id\",\n        }\n\n    def __init__(\n        self,\n        protocol: Http3Protocol,\n        transmit: Callable[[], None],\n    ) -> None:\n        self.protocol = protocol\n        self.transmit = transmit\n        self.receivers: dict[int, Receiver] = {}\n\n    def http_event_received(self, event: H3Event) -> None:\n        logger.debug(  # no cov\n            f\"{Colors.BLUE}[http_event_received]: \"\n            f\"{Colors.YELLOW}{event}{Colors.END}\",\n            extra={\"verbosity\": 2},\n        )\n        receiver, created_new = self.get_or_make_receiver(event)\n        receiver = cast(HTTPReceiver, receiver)\n\n        if isinstance(event, HeadersReceived) and created_new:\n            receiver.future = asyncio.ensure_future(receiver.run())\n        elif isinstance(event, DataReceived):\n            try:\n                receiver.receive_body(event.data)\n            except Exception as e:\n                receiver.future.cancel()\n                receiver.future = asyncio.ensure_future(receiver.run(e))\n        else:\n            ...  # Intentionally here to help out Touchup\n            logger.debug(  # no cov\n                f\"{Colors.RED}DOING NOTHING{Colors.END}\",\n                extra={\"verbosity\": 2},\n            )\n\n    def get_or_make_receiver(self, event: H3Event) -> tuple[Receiver, bool]:\n        if (\n            isinstance(event, HeadersReceived)\n            and event.stream_id not in self.receivers\n        ):\n            request = self._make_request(event)\n            receiver = HTTPReceiver(self.transmit, self.protocol, request)\n            request.stream = receiver\n\n            self.receivers[event.stream_id] = receiver\n            return receiver, True\n        else:\n            ident = getattr(event, self.HANDLER_PROPERTY_MAPPING[type(event)])\n            return self.receivers[ident], False\n\n    def get_receiver_by_stream_id(self, stream_id: int) -> Receiver:\n        return self.receivers[stream_id]\n\n    def _make_request(self, event: HeadersReceived) -> Request:\n        try:\n            headers = Header(\n                (\n                    (k.decode(\"ASCII\"), v.decode(errors=\"surrogateescape\"))\n                    for k, v in event.headers\n                )\n            )\n        except UnicodeDecodeError:\n            raise BadRequest(\n                \"Header names may only contain US-ASCII characters.\"\n            )\n        method = headers[\":method\"]\n        path = headers[\":path\"]\n        scheme = headers.pop(\":scheme\", \"\")\n        authority = headers.pop(\":authority\", \"\")\n\n        if authority:\n            headers[\"host\"] = authority\n\n        try:\n            url_bytes = path.encode(\"ASCII\")\n        except UnicodeEncodeError:\n            raise BadRequest(\"URL may only contain US-ASCII characters.\")\n\n        transport = HTTP3Transport(self.protocol)\n        request = self.protocol.request_class(\n            url_bytes,\n            headers,\n            \"3\",\n            method,\n            transport,\n            self.protocol.app,\n            b\"\",\n        )\n        request.conn_info = ConnInfo(transport)\n        request._stream_id = event.stream_id\n        request._scheme = scheme\n\n        return request\n\n\nclass SessionTicketStore:\n    \"\"\"\n    Simple in-memory store for session tickets.\n    \"\"\"\n\n    def __init__(self) -> None:\n        self.tickets: dict[bytes, SessionTicket] = {}\n\n    def add(self, ticket: SessionTicket) -> None:\n        self.tickets[ticket.ticket] = ticket\n\n    def pop(self, label: bytes) -> SessionTicket | None:\n        return self.tickets.pop(label, None)\n\n\ndef get_config(app: Sanic, ssl: SanicSSLContext | CertSelector | SSLContext):\n    # TODO:\n    # - proper selection needed if service with multiple certs insted of\n    #   just taking the first\n    if isinstance(ssl, CertSelector):\n        ssl = cast(SanicSSLContext, ssl.sanic_select[0])\n    if app.config.LOCAL_CERT_CREATOR is LocalCertCreator.TRUSTME:\n        raise SanicException(\n            \"Sorry, you cannot currently use trustme as a local certificate \"\n            \"generator for an HTTP/3 server. This is not yet supported. You \"\n            \"should be able to use mkcert instead. For more information, see: \"\n            \"https://github.com/aiortc/aioquic/issues/295.\"\n        )\n    if not isinstance(ssl, SanicSSLContext):\n        raise SanicException(\"SSLContext is not SanicSSLContext\")\n\n    config = QuicConfiguration(\n        alpn_protocols=H3_ALPN + H0_ALPN + [\"siduck\"],\n        is_client=False,\n        max_datagram_frame_size=65536,\n    )\n    password = app.config.TLS_CERT_PASSWORD or None\n\n    config.load_cert_chain(\n        ssl.sanic[\"cert\"], ssl.sanic[\"key\"], password=password\n    )\n\n    return config\n"
  },
  {
    "path": "sanic/http/stream.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom sanic.http.constants import Stage\n\n\nif TYPE_CHECKING:\n    from sanic.response import BaseHTTPResponse\n    from sanic.server.protocols.http_protocol import HttpProtocol\n\n\nclass Stream:\n    stage: Stage\n    response: BaseHTTPResponse | None\n    protocol: HttpProtocol\n    url: str | None\n    request_body: bytes | None\n    request_max_size: int | float\n\n    __touchup__: tuple[str, ...] = tuple()\n    __slots__ = (\"request_max_size\",)\n\n    def respond(\n        self, response: BaseHTTPResponse\n    ) -> BaseHTTPResponse:  # no cov\n        raise NotImplementedError(\"Not implemented\")\n"
  },
  {
    "path": "sanic/http/tls/__init__.py",
    "content": "from .context import process_to_context\nfrom .creators import get_ssl_context\n\n\n__all__ = (\"get_ssl_context\", \"process_to_context\")\n"
  },
  {
    "path": "sanic/http/tls/context.py",
    "content": "from __future__ import annotations\n\nimport os\nimport ssl\n\nfrom collections.abc import Iterable\nfrom typing import Any\n\nfrom sanic.log import logger\n\n\n# Only allow secure ciphers, notably leaving out AES-CBC mode\n# OpenSSL chooses ECDSA or RSA depending on the cert in use\nCIPHERS_TLS12 = [\n    \"ECDHE-ECDSA-CHACHA20-POLY1305\",\n    \"ECDHE-ECDSA-AES256-GCM-SHA384\",\n    \"ECDHE-ECDSA-AES128-GCM-SHA256\",\n    \"ECDHE-RSA-CHACHA20-POLY1305\",\n    \"ECDHE-RSA-AES256-GCM-SHA384\",\n    \"ECDHE-RSA-AES128-GCM-SHA256\",\n]\n\n\ndef create_context(\n    certfile: str | None = None,\n    keyfile: str | None = None,\n    password: str | None = None,\n    purpose: ssl.Purpose = ssl.Purpose.CLIENT_AUTH,\n) -> ssl.SSLContext:\n    \"\"\"Create a context with secure crypto and HTTP/1.1 in protocols.\"\"\"\n    context = ssl.create_default_context(purpose=purpose)\n    context.minimum_version = ssl.TLSVersion.TLSv1_2\n    context.set_ciphers(\":\".join(CIPHERS_TLS12))\n    context.set_alpn_protocols([\"http/1.1\"])\n    if purpose is ssl.Purpose.CLIENT_AUTH:\n        context.sni_callback = server_name_callback\n    if certfile and keyfile:\n        context.load_cert_chain(certfile, keyfile, password)\n    return context\n\n\ndef shorthand_to_ctx(\n    ctxdef: None | ssl.SSLContext | dict | str,\n) -> ssl.SSLContext | None:\n    \"\"\"Convert an ssl argument shorthand to an SSLContext object.\"\"\"\n    if ctxdef is None or isinstance(ctxdef, ssl.SSLContext):\n        return ctxdef\n    if isinstance(ctxdef, str):\n        return load_cert_dir(ctxdef)\n    if isinstance(ctxdef, dict):\n        return CertSimple(**ctxdef)\n    raise ValueError(\n        f\"Invalid ssl argument {type(ctxdef)}.\"\n        \" Expecting a list of certdirs, a dict or an SSLContext.\"\n    )\n\n\ndef process_to_context(\n    ssldef: None | ssl.SSLContext | dict | str | list | tuple,\n) -> ssl.SSLContext | None:\n    \"\"\"Process app.run ssl argument from easy formats to full SSLContext.\"\"\"\n    return (\n        CertSelector(map(shorthand_to_ctx, ssldef))\n        if isinstance(ssldef, (list, tuple))\n        else shorthand_to_ctx(ssldef)\n    )\n\n\ndef load_cert_dir(p: str) -> ssl.SSLContext:\n    if os.path.isfile(p):\n        raise ValueError(f\"Certificate folder expected but {p} is a file.\")\n    keyfile = os.path.join(p, \"privkey.pem\")\n    certfile = os.path.join(p, \"fullchain.pem\")\n    if not os.access(keyfile, os.R_OK):\n        raise ValueError(\n            f\"Certificate not found or permission denied {keyfile}\"\n        )\n    if not os.access(certfile, os.R_OK):\n        raise ValueError(\n            f\"Certificate not found or permission denied {certfile}\"\n        )\n    return CertSimple(certfile, keyfile)\n\n\ndef find_cert(self: CertSelector, server_name: str):\n    \"\"\"Find the first certificate that matches the given SNI.\n\n    :raises ssl.CertificateError: No matching certificate found.\n    :return: A matching ssl.SSLContext object if found.\"\"\"\n    if not server_name:\n        if self.sanic_fallback:\n            return self.sanic_fallback\n        raise ValueError(\n            \"The client provided no SNI to match for certificate.\"\n        )\n    for ctx in self.sanic_select:\n        if match_hostname(ctx, server_name):\n            return ctx\n    if self.sanic_fallback:\n        return self.sanic_fallback\n    raise ValueError(f\"No certificate found matching hostname {server_name!r}\")\n\n\ndef match_hostname(ctx: ssl.SSLContext | CertSelector, hostname: str) -> bool:\n    \"\"\"Match names from CertSelector against a received hostname.\"\"\"\n    # Local certs are considered trusted, so this can be less pedantic\n    # and thus faster than the deprecated ssl.match_hostname function is.\n    names = dict(getattr(ctx, \"sanic\", {})).get(\"names\", [])\n    hostname = hostname.lower()\n    for name in names:\n        if name.startswith(\"*.\"):\n            if hostname.split(\".\", 1)[-1] == name[2:]:\n                return True\n        elif name == hostname:\n            return True\n    return False\n\n\ndef selector_sni_callback(\n    sslobj: ssl.SSLObject, server_name: str, ctx: CertSelector\n) -> int | None:\n    \"\"\"Select a certificate matching the SNI.\"\"\"\n    # Call server_name_callback to store the SNI on sslobj\n    server_name_callback(sslobj, server_name, ctx)\n    # Find a new context matching the hostname\n    try:\n        sslobj.context = find_cert(ctx, server_name)\n    except ValueError as e:\n        logger.warning(f\"Rejecting TLS connection: {e}\")\n        # This would show ERR_SSL_UNRECOGNIZED_NAME_ALERT on client side if\n        # asyncio/uvloop did proper SSL shutdown. They don't.\n        return ssl.ALERT_DESCRIPTION_UNRECOGNIZED_NAME\n    return None  # mypy complains without explicit return\n\n\ndef server_name_callback(\n    sslobj: ssl.SSLObject, server_name: str, ctx: ssl.SSLContext\n) -> None:\n    \"\"\"Store the received SNI as sslobj.sanic_server_name.\"\"\"\n    sslobj.sanic_server_name = server_name  # type: ignore\n\n\nclass SanicSSLContext(ssl.SSLContext):\n    sanic: dict[str, os.PathLike]\n\n    @classmethod\n    def create_from_ssl_context(cls, context: ssl.SSLContext):\n        context.__class__ = cls\n        return context\n\n\nclass CertSimple(SanicSSLContext):\n    \"\"\"A wrapper for creating SSLContext with a sanic attribute.\"\"\"\n\n    sanic: dict[str, Any]\n\n    def __new__(cls, cert, key, **kw):\n        # try common aliases, rename to cert/key\n        certfile = kw[\"cert\"] = kw.pop(\"certificate\", None) or cert\n        keyfile = kw[\"key\"] = kw.pop(\"keyfile\", None) or key\n        password = kw.get(\"password\", None)\n        if not certfile or not keyfile:\n            raise ValueError(\"SSL dict needs filenames for cert and key.\")\n        subject = {}\n        if \"names\" not in kw:\n            cert = ssl._ssl._test_decode_cert(certfile)  # type: ignore\n            kw[\"names\"] = [\n                name\n                for t, name in cert[\"subjectAltName\"]\n                if t in [\"DNS\", \"IP Address\"]\n            ]\n            subject = {k: v for item in cert[\"subject\"] for k, v in item}\n        self = create_context(certfile, keyfile, password)\n        self.__class__ = cls\n        self.sanic = {**subject, **kw}\n        return self\n\n    def __init__(self, cert, key, **kw):\n        pass  # Do not call super().__init__ because it is already initialized\n\n\nclass CertSelector(ssl.SSLContext):\n    \"\"\"Automatically select SSL certificate based on the hostname that the\n    client is trying to access, via SSL SNI. Paths to certificate folders\n    with privkey.pem and fullchain.pem in them should be provided, and\n    will be matched in the order given whenever there is a new connection.\n    \"\"\"\n\n    def __new__(cls, ctxs):\n        return super().__new__(cls)\n\n    def __init__(self, ctxs: Iterable[ssl.SSLContext | None]):\n        super().__init__()\n        self.sni_callback = selector_sni_callback  # type: ignore\n        self.sanic_select = []\n        self.sanic_fallback = None\n        all_names = []\n        for i, ctx in enumerate(ctxs):\n            if not ctx:\n                continue\n            names = dict(getattr(ctx, \"sanic\", {})).get(\"names\", [])\n            all_names += names\n            self.sanic_select.append(ctx)\n            if i == 0:\n                self.sanic_fallback = ctx\n        if not all_names:\n            raise ValueError(\n                \"No certificates with SubjectAlternativeNames found.\"\n            )\n        logger.info(f\"Certificate vhosts: {', '.join(all_names)}\")\n"
  },
  {
    "path": "sanic/http/tls/creators.py",
    "content": "from __future__ import annotations\n\nimport ssl\nimport subprocess\nimport sys\n\nfrom abc import ABC, abstractmethod\nfrom contextlib import suppress\nfrom pathlib import Path\nfrom tempfile import mkdtemp\nfrom types import ModuleType\nfrom typing import TYPE_CHECKING, cast\n\nfrom sanic.application.constants import Mode\nfrom sanic.application.spinner import loading\nfrom sanic.constants import (\n    DEFAULT_LOCAL_TLS_CERT,\n    DEFAULT_LOCAL_TLS_KEY,\n    LocalCertCreator,\n)\nfrom sanic.exceptions import SanicException\nfrom sanic.helpers import Default\nfrom sanic.http.tls.context import CertSimple, SanicSSLContext\n\n\ntry:\n    import trustme\n\n    TRUSTME_INSTALLED = True\nexcept (ImportError, ModuleNotFoundError):\n    trustme = ModuleType(\"trustme\")\n    TRUSTME_INSTALLED = False\n\nif TYPE_CHECKING:\n    from sanic import Sanic\n\n\n# Only allow secure ciphers, notably leaving out AES-CBC mode\n# OpenSSL chooses ECDSA or RSA depending on the cert in use\nCIPHERS_TLS12 = [\n    \"ECDHE-ECDSA-CHACHA20-POLY1305\",\n    \"ECDHE-ECDSA-AES256-GCM-SHA384\",\n    \"ECDHE-ECDSA-AES128-GCM-SHA256\",\n    \"ECDHE-RSA-CHACHA20-POLY1305\",\n    \"ECDHE-RSA-AES256-GCM-SHA384\",\n    \"ECDHE-RSA-AES128-GCM-SHA256\",\n]\n\n\ndef _make_path(maybe_path: Path | str, tmpdir: Path | None) -> Path:\n    if isinstance(maybe_path, Path):\n        return maybe_path\n    else:\n        path = Path(maybe_path)\n        if not path.exists():\n            if not tmpdir:\n                raise RuntimeError(\"Reached an unknown state. No tmpdir.\")\n            return tmpdir / maybe_path\n\n    return path\n\n\ndef get_ssl_context(app: Sanic, ssl: ssl.SSLContext | None) -> ssl.SSLContext:\n    if ssl:\n        return ssl\n\n    if app.state.mode is Mode.PRODUCTION:\n        raise SanicException(\n            \"Cannot run Sanic as an HTTPS server in PRODUCTION mode \"\n            \"without passing a TLS certificate. If you are developing \"\n            \"locally, please enable DEVELOPMENT mode and Sanic will \"\n            \"generate a localhost TLS certificate. For more information \"\n            \"please see: https://sanic.dev/en/guide/deployment/development.\"\n            \"html#automatic-tls-certificate.\"\n        )\n\n    creator = CertCreator.select(\n        app,\n        cast(LocalCertCreator, app.config.LOCAL_CERT_CREATOR),\n        app.config.LOCAL_TLS_KEY,\n        app.config.LOCAL_TLS_CERT,\n    )\n    context = creator.generate_cert(app.config.LOCALHOST)\n    return context\n\n\nclass CertCreator(ABC):\n    def __init__(self, app, key, cert) -> None:\n        self.app = app\n        self.key = key\n        self.cert = cert\n        self.tmpdir = None\n\n        if isinstance(self.key, Default) or isinstance(self.cert, Default):\n            self.tmpdir = Path(mkdtemp())\n\n        key = (\n            DEFAULT_LOCAL_TLS_KEY\n            if isinstance(self.key, Default)\n            else self.key\n        )\n        cert = (\n            DEFAULT_LOCAL_TLS_CERT\n            if isinstance(self.cert, Default)\n            else self.cert\n        )\n\n        self.key_path = _make_path(key, self.tmpdir)\n        self.cert_path = _make_path(cert, self.tmpdir)\n\n    @abstractmethod\n    def check_supported(self) -> None:  # no cov\n        ...\n\n    @abstractmethod\n    def generate_cert(self, localhost: str) -> ssl.SSLContext:  # no cov\n        ...\n\n    @classmethod\n    def select(\n        cls,\n        app: Sanic,\n        cert_creator: LocalCertCreator,\n        local_tls_key,\n        local_tls_cert,\n    ) -> CertCreator:\n        creator: CertCreator | None = None\n\n        cert_creator_options: tuple[\n            tuple[type[CertCreator], LocalCertCreator], ...\n        ] = (\n            (MkcertCreator, LocalCertCreator.MKCERT),\n            (TrustmeCreator, LocalCertCreator.TRUSTME),\n        )\n        for creator_class, local_creator in cert_creator_options:\n            creator = cls._try_select(\n                app,\n                creator,\n                creator_class,\n                local_creator,\n                cert_creator,\n                local_tls_key,\n                local_tls_cert,\n            )\n            if creator:\n                break\n\n        if not creator:\n            raise SanicException(\n                \"Sanic could not find package to create a TLS certificate. \"\n                \"You must have either mkcert or trustme installed. See \"\n                \"https://sanic.dev/en/guide/deployment/development.html\"\n                \"#automatic-tls-certificate for more details.\"\n            )\n\n        return creator\n\n    @staticmethod\n    def _try_select(\n        app: Sanic,\n        creator: CertCreator | None,\n        creator_class: type[CertCreator],\n        creator_requirement: LocalCertCreator,\n        creator_requested: LocalCertCreator,\n        local_tls_key,\n        local_tls_cert,\n    ):\n        if creator or (\n            creator_requested is not LocalCertCreator.AUTO\n            and creator_requested is not creator_requirement\n        ):\n            return creator\n\n        instance = creator_class(app, local_tls_key, local_tls_cert)\n        try:\n            instance.check_supported()\n        except SanicException:\n            if creator_requested is creator_requirement:\n                raise\n            else:\n                return None\n\n        return instance\n\n\nclass MkcertCreator(CertCreator):\n    def check_supported(self) -> None:\n        try:\n            subprocess.run(  # nosec B603 B607\n                [\"mkcert\", \"-help\"],\n                check=True,\n                stderr=subprocess.DEVNULL,\n                stdout=subprocess.DEVNULL,\n            )\n        except Exception as e:\n            raise SanicException(\n                \"Sanic is attempting to use mkcert to generate local TLS \"\n                \"certificates since you did not supply a certificate, but \"\n                \"one is required. Sanic cannot proceed since mkcert does not \"\n                \"appear to be installed. Alternatively, you can use trustme. \"\n                \"Please install mkcert, trustme, or supply TLS certificates \"\n                \"to proceed. Installation instructions can be found here: \"\n                \"https://github.com/FiloSottile/mkcert.\\n\"\n                \"Find out more information about your options here: \"\n                \"https://sanic.dev/en/guide/deployment/development.html#\"\n                \"automatic-tls-certificate\"\n            ) from e\n\n    def generate_cert(self, localhost: str) -> ssl.SSLContext:\n        try:\n            if not self.cert_path.exists():\n                message = \"Generating TLS certificate\"\n                # TODO: Validate input for security\n                with loading(message):\n                    cmd = [\n                        \"mkcert\",\n                        \"-key-file\",\n                        str(self.key_path),\n                        \"-cert-file\",\n                        str(self.cert_path),\n                        localhost,\n                    ]\n                    resp = subprocess.run(  # nosec B603\n                        cmd,\n                        check=True,\n                        stdout=subprocess.PIPE,\n                        stderr=subprocess.STDOUT,\n                        text=True,\n                    )\n                sys.stdout.write(\"\\r\" + \" \" * (len(message) + 4))\n                sys.stdout.flush()\n                sys.stdout.write(resp.stdout)\n        finally:\n\n            @self.app.main_process_stop\n            async def cleanup(*_):  # no cov\n                if self.tmpdir:\n                    with suppress(FileNotFoundError):\n                        self.key_path.unlink()\n                        self.cert_path.unlink()\n                    self.tmpdir.rmdir()\n\n        context = CertSimple(self.cert_path, self.key_path)\n        context.sanic[\"creator\"] = \"mkcert\"\n        context.sanic[\"localhost\"] = localhost\n        SanicSSLContext.create_from_ssl_context(context)\n\n        return context\n\n\nclass TrustmeCreator(CertCreator):\n    def check_supported(self) -> None:\n        if not TRUSTME_INSTALLED:\n            raise SanicException(\n                \"Sanic is attempting to use trustme to generate local TLS \"\n                \"certificates since you did not supply a certificate, but \"\n                \"one is required. Sanic cannot proceed since trustme does not \"\n                \"appear to be installed. Alternatively, you can use mkcert. \"\n                \"Please install mkcert, trustme, or supply TLS certificates \"\n                \"to proceed. Installation instructions can be found here: \"\n                \"https://github.com/python-trio/trustme.\\n\"\n                \"Find out more information about your options here: \"\n                \"https://sanic.dev/en/guide/deployment/development.html#\"\n                \"automatic-tls-certificate\"\n            )\n\n    def generate_cert(self, localhost: str) -> ssl.SSLContext:\n        context = SanicSSLContext.create_from_ssl_context(\n            ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)\n        )\n        context.sanic = {\n            \"cert\": self.cert_path.absolute(),\n            \"key\": self.key_path.absolute(),\n        }\n        ca = trustme.CA()\n        server_cert = ca.issue_cert(localhost)\n        server_cert.configure_cert(context)\n        ca.configure_trust(context)\n\n        ca.cert_pem.write_to_path(str(self.cert_path.absolute()))\n        server_cert.private_key_and_cert_chain_pem.write_to_path(\n            str(self.key_path.absolute())\n        )\n        context.sanic[\"creator\"] = \"trustme\"\n        context.sanic[\"localhost\"] = localhost\n\n        return context\n"
  },
  {
    "path": "sanic/log.py",
    "content": "from sanic.logging.color import Colors\nfrom sanic.logging.default import LOGGING_CONFIG_DEFAULTS\nfrom sanic.logging.deprecation import deprecation\nfrom sanic.logging.filter import VerbosityFilter\nfrom sanic.logging.loggers import (\n    access_logger,\n    error_logger,\n    logger,\n    server_logger,\n    websockets_logger,\n)\n\n\n__all__ = (\n    \"deprecation\",\n    \"logger\",\n    \"access_logger\",\n    \"error_logger\",\n    \"server_logger\",\n    \"websockets_logger\",\n    \"VerbosityFilter\",\n    \"Colors\",\n    \"LOGGING_CONFIG_DEFAULTS\",\n)\n"
  },
  {
    "path": "sanic/logging/__init__.py",
    "content": ""
  },
  {
    "path": "sanic/logging/color.py",
    "content": "import logging\nimport os\nimport sys\n\nfrom enum import Enum\nfrom typing import TYPE_CHECKING\n\nfrom sanic.helpers import is_atty\n\n\n# Python 3.11 changed the way Enum formatting works for mixed-in types.\nif sys.version_info < (3, 11, 0):\n\n    class StrEnum(str, Enum):\n        pass\n\nelse:\n    if not TYPE_CHECKING:\n        from enum import StrEnum\n\n\nCOLORIZE = is_atty() and not os.environ.get(\"SANIC_NO_COLOR\")\n\n\nclass Colors(StrEnum):  # no cov\n    \"\"\"\n    Colors for log messages. If the output is not a TTY, the colors will be\n    disabled.\n\n    Can be used like this:\n\n    .. code-block:: python\n\n        from sanic.log import logger, Colors\n\n        logger.info(f\"{Colors.GREEN}This is a green message{Colors.END}\")\n\n\n    Attributes:\n        END: Reset the color\n        BOLD: Bold text\n        BLUE: Blue text\n        GREEN: Green text\n        PURPLE: Purple text\n        RED: Red text\n        SANIC: Sanic pink\n        YELLOW: Yellow text\n        GREY: Grey text\n    \"\"\"\n\n    END = \"\\033[0m\" if COLORIZE else \"\"\n    BOLD = \"\\033[1m\" if COLORIZE else \"\"\n    BLUE = \"\\033[34m\" if COLORIZE else \"\"\n    GREEN = \"\\033[32m\" if COLORIZE else \"\"\n    PURPLE = \"\\033[35m\" if COLORIZE else \"\"\n    CYAN = \"\\033[36m\" if COLORIZE else \"\"\n    RED = \"\\033[31m\" if COLORIZE else \"\"\n    YELLOW = \"\\033[33m\" if COLORIZE else \"\"\n    GREY = \"\\033[38;5;240m\" if COLORIZE else \"\"\n    SANIC = \"\\033[38;2;255;13;104m\" if COLORIZE else \"\"\n\n\nLEVEL_COLORS = {\n    logging.DEBUG: Colors.BLUE,\n    logging.WARNING: Colors.YELLOW,\n    logging.ERROR: Colors.RED,\n    logging.CRITICAL: Colors.RED + Colors.BOLD,\n}\n"
  },
  {
    "path": "sanic/logging/default.py",
    "content": "import sys\n\nfrom typing import Any\n\n\nLOGGING_CONFIG_DEFAULTS: dict[str, Any] = dict(  # no cov\n    version=1,\n    disable_existing_loggers=False,\n    loggers={\n        \"sanic.root\": {\"level\": \"INFO\", \"handlers\": [\"console\"]},\n        \"sanic.error\": {\n            \"level\": \"INFO\",\n            \"handlers\": [\"error_console\"],\n            \"propagate\": True,\n            \"qualname\": \"sanic.error\",\n        },\n        \"sanic.access\": {\n            \"level\": \"INFO\",\n            \"handlers\": [\"access_console\"],\n            \"propagate\": True,\n            \"qualname\": \"sanic.access\",\n        },\n        \"sanic.server\": {\n            \"level\": \"INFO\",\n            \"handlers\": [\"console\"],\n            \"propagate\": True,\n            \"qualname\": \"sanic.server\",\n        },\n        \"sanic.websockets\": {\n            \"level\": \"INFO\",\n            \"handlers\": [\"console\"],\n            \"propagate\": True,\n            \"qualname\": \"sanic.websockets\",\n        },\n    },\n    handlers={\n        \"console\": {\n            \"class\": \"logging.StreamHandler\",\n            \"formatter\": \"generic\",\n            \"stream\": sys.stdout,\n        },\n        \"error_console\": {\n            \"class\": \"logging.StreamHandler\",\n            \"formatter\": \"generic\",\n            \"stream\": sys.stderr,\n        },\n        \"access_console\": {\n            \"class\": \"logging.StreamHandler\",\n            \"formatter\": \"access\",\n            \"stream\": sys.stdout,\n        },\n    },\n    formatters={\n        \"generic\": {\"class\": \"sanic.logging.formatter.AutoFormatter\"},\n        \"access\": {\"class\": \"sanic.logging.formatter.AutoAccessFormatter\"},\n    },\n)\n\"\"\"\nDefault logging configuration\n\"\"\"\n"
  },
  {
    "path": "sanic/logging/deprecation.py",
    "content": "from warnings import warn\n\nfrom sanic.helpers import is_atty\nfrom sanic.logging.color import Colors\n\n\ndef deprecation(message: str, version: float):  # no cov\n    \"\"\"\n    Add a deprecation notice\n\n    Example when a feature is being removed. In this case, version\n    should be AT LEAST next version + 2\n\n    .. code-block:: python\n\n        deprecation(\"Helpful message\", 99.9)\n\n    Example when a feature is deprecated but not being removed:\n\n    .. code-block:: python\n\n        deprecation(\"Helpful message\", 0)\n\n    Args:\n        message (str): Deprecation message\n        version (float): Version when the feature will be removed\n    \"\"\"\n    version_display = f\" v{version}\" if version else \"\"\n    version_info = f\"[DEPRECATION{version_display}] \"\n    if is_atty():\n        version_info = f\"{Colors.RED}{version_info}\"\n        message = f\"{Colors.YELLOW}{message}{Colors.END}\"\n    warn(version_info + message, DeprecationWarning)\n"
  },
  {
    "path": "sanic/logging/filter.py",
    "content": "import logging\n\n\nclass VerbosityFilter(logging.Filter):\n    \"\"\"\n    Filter log records based on verbosity level.\n    \"\"\"\n\n    verbosity: int = 0\n\n    def filter(self, record: logging.LogRecord) -> bool:\n        verbosity = getattr(record, \"verbosity\", 0)\n        return verbosity <= self.verbosity\n"
  },
  {
    "path": "sanic/logging/formatter.py",
    "content": "from __future__ import annotations\n\nimport logging\nimport os\nimport re\n\nfrom sanic.helpers import is_atty, json_dumps\nfrom sanic.logging.color import LEVEL_COLORS\nfrom sanic.logging.color import Colors as c\n\n\nCONTROL_RE = re.compile(r\"\\033\\[[0-9;]*\\w\")\nCONTROL_LIMIT_IDENT = \"\\033[1000D\\033[{limit}C\"\nCONTROL_LIMIT_START = \"\\033[1000D\\033[{start}C\\033[K\"\nCONTROL_LIMIT_END = \"\\033[1000C\\033[{right}D\\033[K\"\nEXCEPTION_LINE_RE = re.compile(r\"^(?P<exc>.*?): (?P<message>.*)$\")\nFILE_LINE_RE = re.compile(\n    r\"File \\\"(?P<path>.*?)\\\", line (?P<line_num>\\d+), in (?P<location>.*)\"\n)\nDEFAULT_FIELDS = set(\n    logging.LogRecord(\"\", 0, \"\", 0, \"\", (), None).__dict__.keys()\n) | {\n    \"ident\",\n    \"message\",\n    \"asctime\",\n    \"right\",\n}\n\n\nclass AutoFormatter(logging.Formatter):\n    \"\"\"\n    Automatically sets up the formatter based on the environment.\n\n    It will switch between the Debug and Production formatters based upon\n    how the environment is set up. Additionally, it will automatically\n    detect if the output is a TTY and colorize the output accordingly.\n    \"\"\"\n\n    SETUP = False\n    ATTY = is_atty()\n    NO_COLOR = os.environ.get(\"SANIC_NO_COLOR\", \"false\").lower() == \"true\"\n    LOG_EXTRA = os.environ.get(\"SANIC_LOG_EXTRA\", \"true\").lower() == \"true\"\n    IDENT = os.environ.get(\"SANIC_WORKER_IDENTIFIER\", \"Main \") or \"Main \"\n    DATE_FORMAT = \"%Y-%m-%d %H:%M:%S %z\"\n    IDENT_LIMIT = 5\n    MESSAGE_START = 42\n    PREFIX_FORMAT = (\n        f\"{c.GREY}%(ident)s{{limit}} %(asctime)s {c.END}\"\n        \"%(levelname)s: {start}\"\n    )\n    MESSAGE_FORMAT = \"%(message)s\"\n\n    def __init__(self, *args) -> None:\n        args_list = list(args)\n        if not args:\n            args_list.append(self._make_format())\n        elif args and not args[0]:\n            args_list[0] = self._make_format()\n        if len(args_list) < 2:\n            args_list.append(self.DATE_FORMAT)\n        elif not args[1]:\n            args_list[1] = self.DATE_FORMAT\n\n        super().__init__(*args_list)\n\n    def format(self, record: logging.LogRecord) -> str:\n        record.ident = self.IDENT\n        self._set_levelname(record)\n        output = super().format(record)\n        if self.LOG_EXTRA:\n            output += self._log_extra(record)\n        return output\n\n    def _set_levelname(self, record: logging.LogRecord) -> None:\n        if (\n            self.ATTY\n            and not self.NO_COLOR\n            and (color := LEVEL_COLORS.get(record.levelno))\n        ):\n            record.levelname = f\"{color}{record.levelname}{c.END}\"\n\n    def _make_format(self) -> str:\n        limit = CONTROL_LIMIT_IDENT.format(limit=self.IDENT_LIMIT)\n        start = CONTROL_LIMIT_START.format(start=self.MESSAGE_START)\n        base_format = self.PREFIX_FORMAT + self.MESSAGE_FORMAT\n        fmt = base_format.format(limit=limit, start=start)\n        if not self.ATTY or self.NO_COLOR:\n            return CONTROL_RE.sub(\"\", fmt)\n        return fmt\n\n    def _log_extra(self, record: logging.LogRecord, indent: int = 0) -> str:\n        extra_lines = [\"\"]\n\n        for key, value in record.__dict__.items():\n            if key not in DEFAULT_FIELDS:\n                extra_lines.append(self._format_key_value(key, value, indent))\n\n        return \"\\n\".join(extra_lines)\n\n    def _format_key_value(self, key, value, indent):\n        indentation = \" \" * indent\n        template = (\n            f\"{indentation}  {{c.YELLOW}}{{key}}{{c.END}}={{value}}\"\n            if self.ATTY and not self.NO_COLOR\n            else f\"{indentation}{{key}}={{value}}\"\n        )\n        if isinstance(value, dict):\n            nested_lines = [template.format(c=c, key=key, value=\"\")]\n            for nested_key, nested_value in value.items():\n                nested_lines.append(\n                    self._format_key_value(\n                        nested_key, nested_value, indent + 2\n                    )\n                )\n            return \"\\n\".join(nested_lines)\n        else:\n            return template.format(c=c, key=key, value=value)\n\n\nclass DebugFormatter(AutoFormatter):\n    \"\"\"\n    The DebugFormatter is used for development and debugging purposes.\n\n    It can be used directly, or it will be automatically selected if the\n    environment is set up for development and is using the AutoFormatter.\n    \"\"\"\n\n    IDENT_LIMIT = 5\n    MESSAGE_START = 23\n    DATE_FORMAT = \"%H:%M:%S\"\n\n    def _set_levelname(self, record: logging.LogRecord) -> None:\n        if len(record.levelname) > 5:\n            record.levelname = record.levelname[:4]\n        super()._set_levelname(record)\n\n    def formatException(self, ei):  # no cov\n        orig = super().formatException(ei)\n        if not self.ATTY or self.NO_COLOR:\n            return orig\n        colored_traceback = []\n        lines = orig.splitlines()\n        for idx, line in enumerate(lines):\n            if line.startswith(\"  File\"):\n                line = self._color_file_line(line)\n            elif line.startswith(\"    \"):\n                line = self._color_code_line(line)\n            elif (\n                \"Error\" in line or \"Exception\" in line or len(lines) - 1 == idx\n            ):\n                line = self._color_exception_line(line)\n            colored_traceback.append(line)\n        return \"\\n\".join(colored_traceback)\n\n    def _color_exception_line(self, line: str) -> str:  # no cov\n        match = EXCEPTION_LINE_RE.match(line)\n        if not match:\n            return line\n        exc = match.group(\"exc\")\n        message = match.group(\"message\")\n        return f\"{c.SANIC}{c.BOLD}{exc}{c.END}: {c.BOLD}{message}{c.END}\"\n\n    def _color_file_line(self, line: str) -> str:  # no cov\n        match = FILE_LINE_RE.search(line)\n        if not match:\n            return line\n        path = match.group(\"path\")\n        line_num = match.group(\"line_num\")\n        location = match.group(\"location\")\n        return (\n            f'  File \"{path}\", line {c.CYAN}{c.BOLD}{line_num}{c.END}, '\n            f\"in {c.BLUE}{c.BOLD}{location}{c.END}\"\n        )\n\n    def _color_code_line(self, line: str) -> str:  # no cov\n        return f\"{c.YELLOW}{line}{c.END}\"\n\n\nclass ProdFormatter(AutoFormatter):\n    \"\"\"\n    The ProdFormatter is used for production environments.\n\n    It can be used directly, or it will be automatically selected if the\n    environment is set up for production and is using the AutoFormatter.\n    \"\"\"\n\n\nclass LegacyFormatter(AutoFormatter):\n    \"\"\"\n    The LegacyFormatter is used if you want to use the old style of logging.\n\n    You can use it as follows, typically in conjunction with the\n    LegacyAccessFormatter:\n\n    .. code-block:: python\n\n        from sanic.log import LOGGING_CONFIG_DEFAULTS\n\n        LOGGING_CONFIG_DEFAULTS[\"formatters\"] = {\n            \"generic\": {\n                \"class\": \"sanic.logging.formatter.LegacyFormatter\"\n            },\n            \"access\": {\n                \"class\": \"sanic.logging.formatter.LegacyAccessFormatter\"\n            },\n        }\n    \"\"\"\n\n    PREFIX_FORMAT = \"%(asctime)s [%(process)s] [%(levelname)s] \"\n    DATE_FORMAT = \"[%Y-%m-%d %H:%M:%S %z]\"\n\n\nclass AutoAccessFormatter(AutoFormatter):\n    MESSAGE_FORMAT = (\n        f\"{c.PURPLE}%(host)s \"\n        f\"{c.BLUE + c.BOLD}%(request)s{c.END} \"\n        f\"%(right)s%(status)s %(byte)s {c.GREY}%(duration)s{c.END}\"\n    )\n\n    def format(self, record: logging.LogRecord) -> str:\n        status = len(str(getattr(record, \"status\", \"\")))\n        byte = len(str(getattr(record, \"byte\", \"\")))\n        duration = len(str(getattr(record, \"duration\", \"\")))\n        record.right = (\n            CONTROL_LIMIT_END.format(right=status + byte + duration + 1)\n            if self.ATTY\n            else \"\"\n        )\n        return super().format(record)\n\n    def _set_levelname(self, record: logging.LogRecord) -> None:\n        if self.ATTY and record.levelno == logging.INFO:\n            record.levelname = f\"{c.SANIC}ACCESS{c.END}\"\n\n\nclass LegacyAccessFormatter(AutoAccessFormatter):\n    \"\"\"\n    The LegacyFormatter is used if you want to use the old style of logging.\n\n    You can use it as follows, typically in conjunction with the\n    LegacyFormatter:\n\n    .. code-block:: python\n\n        from sanic.log import LOGGING_CONFIG_DEFAULTS\n\n        LOGGING_CONFIG_DEFAULTS[\"formatters\"] = {\n            \"generic\": {\n                \"class\": \"sanic.logging.formatter.LegacyFormatter\"\n            },\n            \"access\": {\n                \"class\": \"sanic.logging.formatter.LegacyAccessFormatter\"\n            },\n        }\n    \"\"\"\n\n    PREFIX_FORMAT = \"%(asctime)s - (%(name)s)[%(levelname)s][%(host)s]: \"\n    MESSAGE_FORMAT = \"%(request)s %(message)s %(status)s %(byte)s\"\n\n\nclass DebugAccessFormatter(AutoAccessFormatter):\n    IDENT_LIMIT = 5\n    MESSAGE_START = 23\n    DATE_FORMAT = \"%H:%M:%S\"\n    LOG_EXTRA = False\n\n\nclass ProdAccessFormatter(AutoAccessFormatter):\n    IDENT_LIMIT = 5\n    MESSAGE_START = 42\n    PREFIX_FORMAT = (\n        f\"{c.GREY}%(ident)s{{limit}}|%(asctime)s{c.END} \"\n        f\"%(levelname)s: {{start}}\"\n    )\n    MESSAGE_FORMAT = (\n        f\"{c.PURPLE}%(host)s {c.BLUE + c.BOLD}\"\n        f\"%(request)s{c.END} \"\n        f\"%(right)s%(status)s %(byte)s {c.GREY}%(duration)s{c.END}\"\n    )\n    LOG_EXTRA = False\n\n\nclass JSONFormatter(AutoFormatter):\n    \"\"\"\n    The JSONFormatter is used to output logs in JSON format.\n\n    This is useful for logging to a file or to a log aggregator that\n    understands JSON. It will output all the fields from the LogRecord\n    as well as the extra fields that are passed in.\n\n    You can use it as follows:\n\n    .. code-block:: python\n\n        from sanic.log import LOGGING_CONFIG_DEFAULTS\n\n        LOGGING_CONFIG_DEFAULTS[\"formatters\"] = {\n            \"generic\": {\n                \"class\": \"sanic.logging.formatter.JSONFormatter\"\n            },\n            \"access\": {\n                \"class\": \"sanic.logging.formatter.JSONFormatter\"\n            },\n        }\n    \"\"\"\n\n    ATTY = False\n    NO_COLOR = True\n    FIELDS = [\n        \"name\",\n        \"levelno\",\n        \"pathname\",\n        \"module\",\n        \"filename\",\n        \"lineno\",\n    ]\n\n    dumps = json_dumps\n\n    def format(self, record: logging.LogRecord) -> str:\n        return self.format_dict(self.to_dict(record))\n\n    def to_dict(self, record: logging.LogRecord) -> dict:\n        base = {field: getattr(record, field, None) for field in self.FIELDS}\n        extra = {\n            key: value\n            for key, value in record.__dict__.items()\n            if key not in DEFAULT_FIELDS\n        }\n        info = {}\n        if record.exc_info:\n            info[\"exc_info\"] = self.formatException(record.exc_info)\n        if record.stack_info:\n            info[\"stack_info\"] = self.formatStack(record.stack_info)\n        return {\n            \"timestamp\": self.formatTime(record, self.datefmt),\n            \"level\": record.levelname,\n            \"message\": record.getMessage(),\n            **base,\n            **info,\n            **extra,\n        }\n\n    def format_dict(self, record: dict) -> str:\n        return self.dumps(record)\n\n\nclass JSONAccessFormatter(JSONFormatter):\n    \"\"\"\n    The JSONAccessFormatter is used to output access logs in JSON format.\n\n    This is useful for logging to a file or to a log aggregator that\n    understands JSON. It will output all the fields from the LogRecord\n    as well as the extra fields that are passed in.\n\n    You can use it as follows:\n\n    .. code-block:: python\n\n        from sanic.log import LOGGING_CONFIG_DEFAULTS\n\n        LOGGING_CONFIG_DEFAULTS[\"formatters\"] = {\n            \"generic\": {\n                \"class\": \"sanic.logging.formatter.JSONFormatter\"\n            },\n            \"access\": {\n                \"class\": \"sanic.logging.formatter.JSONAccessFormatter\"\n            },\n        }\n    \"\"\"\n\n    FIELDS = [\n        \"host\",\n        \"request\",\n        \"status\",\n        \"byte\",\n        \"duration\",\n    ]\n\n    def to_dict(self, record: logging.LogRecord) -> dict:\n        base = {field: getattr(record, field, None) for field in self.FIELDS}\n        return {\n            \"timestamp\": self.formatTime(record, self.datefmt),\n            \"level\": record.levelname,\n            \"message\": record.getMessage(),\n            **base,\n        }\n"
  },
  {
    "path": "sanic/logging/loggers.py",
    "content": "import logging\n\nfrom sanic.logging.filter import VerbosityFilter\n\n\n_verbosity_filter = VerbosityFilter()\n\nlogger = logging.getLogger(\"sanic.root\")  # no cov\n\"\"\"\nGeneral Sanic logger\n\"\"\"\nlogger.addFilter(_verbosity_filter)\n\nerror_logger = logging.getLogger(\"sanic.error\")  # no cov\n\"\"\"\nLogger used by Sanic for error logging\n\"\"\"\nerror_logger.addFilter(_verbosity_filter)\n\naccess_logger = logging.getLogger(\"sanic.access\")  # no cov\n\"\"\"\nLogger used by Sanic for access logging\n\"\"\"\naccess_logger.addFilter(_verbosity_filter)\n\nserver_logger = logging.getLogger(\"sanic.server\")  # no cov\n\"\"\"\nLogger used by Sanic for server related messages\n\"\"\"\nserver_logger.addFilter(_verbosity_filter)\n\nwebsockets_logger = logging.getLogger(\"sanic.websockets\")  # no cov\n\"\"\"\nLogger used by Sanic for websockets module and protocol related messages\n\"\"\"\nwebsockets_logger.addFilter(_verbosity_filter)\nwebsockets_logger.setLevel(logging.WARNING)  # Too noisy on debug/info\n"
  },
  {
    "path": "sanic/logging/setup.py",
    "content": "import logging\nimport os\n\nfrom sanic.helpers import Default, _default\nfrom sanic.log import (\n    access_logger,\n    error_logger,\n    logger,\n    server_logger,\n    websockets_logger,\n)\nfrom sanic.logging.formatter import (\n    AutoAccessFormatter,\n    AutoFormatter,\n    DebugAccessFormatter,\n    DebugFormatter,\n    ProdAccessFormatter,\n    ProdFormatter,\n)\n\n\ndef setup_logging(\n    debug: bool,\n    no_color: bool = False,\n    log_extra: bool | Default = _default,\n) -> None:\n    if AutoFormatter.SETUP:\n        return\n\n    if isinstance(log_extra, Default):\n        log_extra = debug\n        os.environ[\"SANIC_LOG_EXTRA\"] = str(log_extra)\n    AutoFormatter.LOG_EXTRA = log_extra\n\n    if no_color:\n        os.environ[\"SANIC_NO_COLOR\"] = str(no_color)\n        AutoFormatter.NO_COLOR = no_color\n\n    AutoFormatter.SETUP = True\n\n    for lggr in (logger, server_logger, error_logger, websockets_logger):\n        _auto_format(\n            lggr,\n            AutoFormatter,\n            DebugFormatter if debug else ProdFormatter,\n        )\n    _auto_format(\n        access_logger,\n        AutoAccessFormatter,\n        DebugAccessFormatter if debug else ProdAccessFormatter,\n    )\n\n\ndef _auto_format(\n    logger: logging.Logger,\n    auto_class: type[AutoFormatter],\n    formatter_class: type[AutoFormatter],\n) -> None:\n    for handler in logger.handlers:\n        if type(handler.formatter) is auto_class:\n            handler.setFormatter(formatter_class())\n"
  },
  {
    "path": "sanic/middleware.py",
    "content": "from __future__ import annotations\n\nfrom collections import deque\nfrom collections.abc import Sequence\nfrom enum import IntEnum, auto\nfrom itertools import count\n\nfrom sanic.models.handler_types import MiddlewareType\n\n\nclass MiddlewareLocation(IntEnum):\n    REQUEST = auto()\n    RESPONSE = auto()\n\n\nclass Middleware:\n    \"\"\"Middleware object that is used to encapsulate middleware functions.\n\n    This should generally not be instantiated directly, but rather through\n    the `sanic.Sanic.middleware` decorator and its variants.\n\n    Args:\n        func (MiddlewareType): The middleware function to be called.\n        location (MiddlewareLocation): The location of the middleware.\n        priority (int): The priority of the middleware.\n    \"\"\"\n\n    _counter = count()\n    count: int\n\n    __slots__ = (\"func\", \"priority\", \"location\", \"definition\")\n\n    def __init__(\n        self,\n        func: MiddlewareType,\n        location: MiddlewareLocation,\n        priority: int = 0,\n    ) -> None:\n        self.func = func\n        self.priority = priority\n        self.location = location\n        self.definition = next(Middleware._counter)\n\n    def __call__(self, *args, **kwargs):\n        return self.func(*args, **kwargs)\n\n    def __hash__(self) -> int:\n        return hash(self.func)\n\n    def __repr__(self) -> str:\n        return (\n            f\"{self.__class__.__name__}(\"\n            f\"func=<function {self.func.__name__}>, \"\n            f\"priority={self.priority}, \"\n            f\"location={self.location.name})\"\n        )\n\n    @property\n    def order(self) -> tuple[int, int]:\n        \"\"\"Return a tuple of the priority and definition order.\n\n        This is used to sort the middleware.\n\n        Returns:\n            tuple[int, int]: The priority and definition order.\n        \"\"\"\n        return (self.priority, -self.definition)\n\n    @classmethod\n    def convert(\n        cls,\n        *middleware_collections: Sequence[Middleware | MiddlewareType],\n        location: MiddlewareLocation,\n    ) -> deque[Middleware]:\n        \"\"\"Convert middleware collections to a deque of Middleware objects.\n\n        Args:\n            *middleware_collections (Sequence[Union[Middleware, MiddlewareType]]):\n                The middleware collections to convert.\n            location (MiddlewareLocation): The location of the middleware.\n\n        Returns:\n            Deque[Middleware]: The converted middleware.\n        \"\"\"  # noqa: E501\n        return deque(\n            [\n                middleware\n                if isinstance(middleware, Middleware)\n                else Middleware(middleware, location)\n                for collection in middleware_collections\n                for middleware in collection\n            ]\n        )\n\n    @classmethod\n    def reset_count(cls) -> None:\n        \"\"\"Reset the counter for the middleware definition order.\n\n        This is used for testing.\n\n        Returns:\n            None\n        \"\"\"\n        cls._counter = count()\n        cls.count = next(cls._counter)\n"
  },
  {
    "path": "sanic/mixins/__init__.py",
    "content": ""
  },
  {
    "path": "sanic/mixins/base.py",
    "content": "from __future__ import annotations\n\nfrom typing import Protocol\n\nfrom sanic.base.meta import SanicMeta\n\n\nclass NameProtocol(Protocol):\n    name: str\n\n\nclass DunderNameProtocol(Protocol):\n    __name__: str\n\n\nclass BaseMixin(metaclass=SanicMeta):\n    \"\"\"Base class for various mixins.\"\"\"\n\n    name: str\n    strict_slashes: bool | None\n\n    def _generate_name(\n        self, *objects: NameProtocol | DunderNameProtocol | str\n    ) -> str:\n        name: str | None = None\n        for obj in objects:\n            if not obj:\n                continue\n            if isinstance(obj, str):\n                name = obj\n            else:\n                name = getattr(obj, \"name\", getattr(obj, \"__name__\", None))\n\n            if name:\n                break\n        if not name or not isinstance(name, str):\n            raise ValueError(\"Could not generate a name for handler\")\n\n        if not name.startswith(f\"{self.name}.\"):\n            name = f\"{self.name}.{name}\"\n\n        return name\n\n    def generate_name(self, *objects) -> str:\n        return self._generate_name(*objects)\n"
  },
  {
    "path": "sanic/mixins/commands.py",
    "content": "from __future__ import annotations\n\nfrom functools import wraps\nfrom inspect import isawaitable\nfrom typing import Callable\n\nfrom sanic.base.meta import SanicMeta\nfrom sanic.models.futures import FutureCommand\n\n\nclass CommandMixin(metaclass=SanicMeta):\n    def __init__(self, *args, **kwargs) -> None:\n        self._future_commands: set[FutureCommand] = set()\n\n    def command(\n        self, maybe_func: Callable | None = None, *, name: str = \"\"\n    ) -> Callable | Callable[[Callable], Callable]:\n        def decorator(f):\n            @wraps(f)\n            async def decorated_function(*args, **kwargs):\n                response = f(*args, **kwargs)\n                if isawaitable(response):\n                    response = await response\n                return response\n\n            self._future_commands.add(\n                FutureCommand(name or f.__name__, decorated_function)\n            )\n            return decorated_function\n\n        return decorator(maybe_func) if maybe_func else decorator\n"
  },
  {
    "path": "sanic/mixins/exceptions.py",
    "content": "from __future__ import annotations\n\nfrom typing import Any, Callable\n\nfrom sanic.base.meta import SanicMeta\nfrom sanic.models.futures import FutureException\n\n\nclass ExceptionMixin(metaclass=SanicMeta):\n    def __init__(self, *args, **kwargs) -> None:\n        self._future_exceptions: set[FutureException] = set()\n\n    def _apply_exception_handler(self, handler: FutureException):\n        raise NotImplementedError  # noqa\n\n    def exception(\n        self,\n        *exceptions: type[Exception] | list[type[Exception]],\n        apply: bool = True,\n    ) -> Callable:\n        \"\"\"Decorator used to register an exception handler for the current application or blueprint instance.\n\n        This method allows you to define a handler for specific exceptions that\n        may be raised within the routes of this blueprint. You can specify one\n        or more exception types to catch, and the handler will be applied to\n        those exceptions.\n\n        When used on a Blueprint, the handler will only be applied to routes\n        registered under that blueprint. That means they only apply to\n        requests that have been matched, and the exception is raised within\n        the handler function (or middleware) for that route.\n\n        A general exception like `NotFound` should only be registered on the\n        application instance, not on a blueprint.\n\n        See [Exceptions](/en/guide/best-practices/exceptions.html) for more information.\n\n        Args:\n            exceptions (Union[Type[Exception], List[Type[Exception]]]): List of\n                Python exceptions to be caught by the handler.\n            apply (bool, optional): Whether the exception handler should be\n                applied. Defaults to True.\n\n        Returns:\n            Callable: A decorated method to handle global exceptions for any route\n                registered under this blueprint.\n\n        Example:\n            ```python\n            from sanic import Blueprint, text\n\n            bp = Blueprint('my_blueprint')\n\n            @bp.exception(Exception)\n            def handle_exception(request, exception):\n                return text(\"Oops, something went wrong!\", status=500)\n            ```\n\n            ```python\n            from sanic import Sanic, NotFound, text\n\n            app = Sanic('MyApp')\n\n            @app.exception(NotFound)\n            def ignore_404s(request, exception):\n                return text(f\"Yep, I totally found the page: {request.url}\")\n        \"\"\"  # noqa: E501\n\n        def decorator(handler):\n            nonlocal apply\n            nonlocal exceptions\n\n            if isinstance(exceptions[0], list):\n                exceptions = tuple(*exceptions)\n\n            future_exception = FutureException(handler, exceptions)\n            self._future_exceptions.add(future_exception)\n            if apply:\n                self._apply_exception_handler(future_exception)\n            return handler\n\n        return decorator\n\n    def all_exceptions(\n        self, handler: Callable[..., Any]\n    ) -> Callable[..., Any]:\n        \"\"\"Enables the process of creating a global exception handler as a convenience.\n\n        This following two examples are equivalent:\n\n        ```python\n        @app.exception(Exception)\n        async def handler(request: Request, exception: Exception) -> HTTPResponse:\n            return text(f\"Exception raised: {exception}\")\n        ```\n\n        ```python\n        @app.all_exceptions\n        async def handler(request: Request, exception: Exception) -> HTTPResponse:\n            return text(f\"Exception raised: {exception}\")\n        ```\n\n        Args:\n            handler (Callable[..., Any]): A coroutine function to handle exceptions.\n\n        Returns:\n            Callable[..., Any]: A decorated method to handle global exceptions for\n                any route registered under this blueprint.\n        \"\"\"  # noqa: E501\n        return self.exception(Exception)(handler)\n"
  },
  {
    "path": "sanic/mixins/listeners.py",
    "content": "from __future__ import annotations\n\nfrom enum import Enum, auto\nfrom functools import partial\nfrom typing import Callable, cast, overload\n\nfrom sanic.base.meta import SanicMeta\nfrom sanic.exceptions import BadRequest\nfrom sanic.models.futures import FutureListener\nfrom sanic.models.handler_types import ListenerType, Sanic\n\n\nclass ListenerEvent(str, Enum):\n    def _generate_next_value_(name: str, *args) -> str:  # type: ignore\n        return name.lower()\n\n    BEFORE_SERVER_START = \"server.init.before\"\n    AFTER_SERVER_START = \"server.init.after\"\n    BEFORE_SERVER_STOP = \"server.shutdown.before\"\n    AFTER_SERVER_STOP = \"server.shutdown.after\"\n    MAIN_PROCESS_START = auto()\n    MAIN_PROCESS_READY = auto()\n    MAIN_PROCESS_STOP = auto()\n    RELOAD_PROCESS_START = auto()\n    RELOAD_PROCESS_STOP = auto()\n    BEFORE_RELOAD_TRIGGER = auto()\n    AFTER_RELOAD_TRIGGER = auto()\n\n\nclass ListenerMixin(metaclass=SanicMeta):\n    def __init__(self, *args, **kwargs) -> None:\n        self._future_listeners: list[FutureListener] = []\n\n    def _apply_listener(self, listener: FutureListener):\n        raise NotImplementedError  # noqa\n\n    @overload\n    def listener(\n        self,\n        listener_or_event: ListenerType[Sanic],\n        event_or_none: str,\n        apply: bool = ...,\n        *,\n        priority: int = 0,\n    ) -> ListenerType[Sanic]: ...\n\n    @overload\n    def listener(\n        self,\n        listener_or_event: str,\n        event_or_none: None = ...,\n        apply: bool = ...,\n        *,\n        priority: int = 0,\n    ) -> Callable[[ListenerType[Sanic]], ListenerType[Sanic]]: ...\n\n    def listener(\n        self,\n        listener_or_event: ListenerType[Sanic] | str,\n        event_or_none: str | None = None,\n        apply: bool = True,\n        *,\n        priority: int = 0,\n    ) -> (\n        ListenerType[Sanic]\n        | Callable[[ListenerType[Sanic]], ListenerType[Sanic]]\n    ):\n        \"\"\"Create a listener for a specific event in the application's lifecycle.\n\n        See [Listeners](/en/guide/basics/listeners) for more details.\n\n        .. note::\n            Overloaded signatures allow for different ways of calling this method, depending on the types of the arguments.\n\n            Usually, it is prederred to use one of the convenience methods such as `before_server_start` or `after_server_stop` instead of calling this method directly.\n\n            ```python\n            @app.before_server_start\n            async def prefered_method(_):\n                ...\n\n            @app.listener(\"before_server_start\")\n            async def not_prefered_method(_):\n                ...\n\n        Args:\n            listener_or_event (Union[ListenerType[Sanic], str]): A listener function or an event name.\n            event_or_none (Optional[str]): The event name to listen for if `listener_or_event` is a function. Defaults to `None`.\n            apply (bool): Whether to apply the listener immediately. Defaults to `True`.\n            priority (int): The priority of the listener. Defaults to `0`.\n\n        Returns:\n            Union[ListenerType[Sanic], Callable[[ListenerType[Sanic]], ListenerType[Sanic]]]: The listener or a callable that takes a listener.\n\n        Example:\n            The following code snippet shows how you can use this method as a decorator:\n\n            ```python\n            @bp.listener(\"before_server_start\")\n            async def before_server_start(app, loop):\n                ...\n            ```\n        \"\"\"  # noqa: E501\n\n        def register_listener(\n            listener: ListenerType[Sanic], event: str, priority: int = 0\n        ) -> ListenerType[Sanic]:\n            \"\"\"A helper function to register a listener for an event.\n\n            Typically will not be called directly.\n\n            Args:\n                listener (ListenerType[Sanic]): The listener function to\n                    register.\n                event (str): The event name to listen for.\n\n            Returns:\n                ListenerType[Sanic]: The listener function that was registered.\n            \"\"\"\n            nonlocal apply\n\n            future_listener = FutureListener(listener, event, priority)\n            self._future_listeners.append(future_listener)\n            if apply:\n                self._apply_listener(future_listener)\n            return listener\n\n        if callable(listener_or_event):\n            if event_or_none is None:\n                raise BadRequest(\n                    \"Invalid event registration: Missing event name.\"\n                )\n            return register_listener(\n                listener_or_event, event_or_none, priority\n            )\n        else:\n            return partial(\n                register_listener, event=listener_or_event, priority=priority\n            )\n\n    def _setup_listener(\n        self,\n        listener: ListenerType[Sanic] | None,\n        event: str,\n        priority: int,\n    ) -> ListenerType[Sanic]:\n        if listener is not None:\n            return self.listener(listener, event, priority=priority)\n        return cast(\n            ListenerType[Sanic],\n            partial(self.listener, event_or_none=event, priority=priority),\n        )\n\n    def main_process_start(\n        self, listener: ListenerType[Sanic] | None, *, priority: int = 0\n    ) -> ListenerType[Sanic]:\n        \"\"\"Decorator for registering a listener for the main_process_start event.\n\n        This event is fired only on the main process and **NOT** on any\n        worker processes. You should typically use this event to initialize\n        resources that are shared across workers, or to initialize resources\n        that are not safe to be initialized in a worker process.\n\n        See [Listeners](/en/guide/basics/listeners) for more details.\n\n        Args:\n            listener (ListenerType[Sanic]): The listener handler to attach.\n\n        Examples:\n            ```python\n            @app.main_process_start\n            async def on_main_process_start(app: Sanic):\n                print(\"Main process started\")\n            ```\n        \"\"\"  # noqa: E501\n        return self._setup_listener(listener, \"main_process_start\", priority)\n\n    def main_process_ready(\n        self, listener: ListenerType[Sanic] | None, *, priority: int = 0\n    ) -> ListenerType[Sanic]:\n        \"\"\"Decorator for registering a listener for the main_process_ready event.\n\n        This event is fired only on the main process and **NOT** on any\n        worker processes. It is fired after the main process has started and\n        the Worker Manager has been initialized (ie, you will have access to\n        `app.manager` instance). The typical use case for this event is to\n        add a managed process to the Worker Manager.\n\n        See [Running custom processes](/en/guide/deployment/manager.html#running-custom-processes) and [Listeners](/en/guide/basics/listeners.html) for more details.\n\n        Args:\n            listener (ListenerType[Sanic]): The listener handler to attach.\n\n        Examples:\n            ```python\n            @app.main_process_ready\n            async def on_main_process_ready(app: Sanic):\n                print(\"Main process ready\")\n            ```\n        \"\"\"  # noqa: E501\n        return self._setup_listener(listener, \"main_process_ready\", priority)\n\n    def main_process_stop(\n        self, listener: ListenerType[Sanic] | None, *, priority: int = 0\n    ) -> ListenerType[Sanic]:\n        \"\"\"Decorator for registering a listener for the main_process_stop event.\n\n        This event is fired only on the main process and **NOT** on any\n        worker processes. You should typically use this event to clean up\n        resources that were initialized in the main_process_start event.\n\n        See [Listeners](/en/guide/basics/listeners) for more details.\n\n        Args:\n            listener (ListenerType[Sanic]): The listener handler to attach.\n\n        Examples:\n            ```python\n            @app.main_process_stop\n            async def on_main_process_stop(app: Sanic):\n                print(\"Main process stopped\")\n            ```\n        \"\"\"  # noqa: E501\n        return self._setup_listener(listener, \"main_process_stop\", priority)\n\n    def reload_process_start(\n        self, listener: ListenerType[Sanic] | None, *, priority: int = 0\n    ) -> ListenerType[Sanic]:\n        \"\"\"Decorator for registering a listener for the reload_process_start event.\n\n        This event is fired only on the reload process and **NOT** on any\n        worker processes. This is similar to the main_process_start event,\n        except that it is fired only when the reload process is started.\n\n        See [Listeners](/en/guide/basics/listeners) for more details.\n\n        Args:\n            listener (ListenerType[Sanic]): The listener handler to attach.\n\n        Examples:\n            ```python\n            @app.reload_process_start\n            async def on_reload_process_start(app: Sanic):\n                print(\"Reload process started\")\n            ```\n        \"\"\"  # noqa: E501\n        return self._setup_listener(listener, \"reload_process_start\", priority)\n\n    def reload_process_stop(\n        self, listener: ListenerType[Sanic] | None, *, priority: int = 0\n    ) -> ListenerType[Sanic]:\n        \"\"\"Decorator for registering a listener for the reload_process_stop event.\n\n        This event is fired only on the reload process and **NOT** on any\n        worker processes. This is similar to the main_process_stop event,\n        except that it is fired only when the reload process is stopped.\n\n        See [Listeners](/en/guide/basics/listeners) for more details.\n\n        Args:\n            listener (ListenerType[Sanic]): The listener handler to attach.\n\n        Examples:\n            ```python\n            @app.reload_process_stop\n            async def on_reload_process_stop(app: Sanic):\n                print(\"Reload process stopped\")\n            ```\n        \"\"\"  # noqa: E501\n        return self._setup_listener(listener, \"reload_process_stop\", priority)\n\n    def before_reload_trigger(\n        self, listener: ListenerType[Sanic] | None, *, priority: int = 0\n    ) -> ListenerType[Sanic]:\n        \"\"\"Decorator for registering a listener for the before_reload_trigger event.\n\n        This event is fired only on the reload process and **NOT** on any\n        worker processes. This event is fired before the reload process\n        triggers the reload. A change event has been detected and the reload\n        process is about to be triggered.\n\n        See [Listeners](/en/guide/basics/listeners) for more details.\n\n        Args:\n            listener (ListenerType[Sanic]): The listener handler to attach.\n\n        Examples:\n            ```python\n            @app.before_reload_trigger\n            async def on_before_reload_trigger(app: Sanic):\n                print(\"Before reload trigger\")\n            ```\n        \"\"\"  # noqa: E501\n        return self._setup_listener(\n            listener, \"before_reload_trigger\", priority\n        )\n\n    def after_reload_trigger(\n        self, listener: ListenerType[Sanic] | None, *, priority: int = 0\n    ) -> ListenerType[Sanic]:\n        \"\"\"Decorator for registering a listener for the after_reload_trigger event.\n\n        This event is fired only on the reload process and **NOT** on any\n        worker processes. This event is fired after the reload process\n        triggers the reload. A change event has been detected and the reload\n        process has been triggered.\n\n        See [Listeners](/en/guide/basics/listeners) for more details.\n\n        Args:\n            listener (ListenerType[Sanic]): The listener handler to attach.\n\n        Examples:\n            ```python\n            @app.after_reload_trigger\n            async def on_after_reload_trigger(app: Sanic, changed: set[str]):\n                print(\"After reload trigger, changed files: \", changed)\n            ```\n        \"\"\"  # noqa: E501\n        return self._setup_listener(listener, \"after_reload_trigger\", priority)\n\n    def before_server_start(\n        self,\n        listener: ListenerType[Sanic] | None = None,\n        *,\n        priority: int = 0,\n    ) -> ListenerType[Sanic]:\n        \"\"\"Decorator for registering a listener for the before_server_start event.\n\n        This event is fired on all worker processes. You should typically\n        use this event to initialize resources that are global in nature, or\n        will be shared across requests and various parts of the application.\n\n        A common use case for this event is to initialize a database connection\n        pool, or to initialize a cache client.\n\n        See [Listeners](/en/guide/basics/listeners) for more details.\n\n        Args:\n            listener (ListenerType[Sanic]): The listener handler to attach.\n\n        Examples:\n            ```python\n            @app.before_server_start\n            async def on_before_server_start(app: Sanic):\n                print(\"Before server start\")\n            ```\n        \"\"\"  # noqa: E501\n        return self._setup_listener(listener, \"before_server_start\", priority)\n\n    def after_server_start(\n        self, listener: ListenerType[Sanic] | None, *, priority: int = 0\n    ) -> ListenerType[Sanic]:\n        \"\"\"Decorator for registering a listener for the after_server_start event.\n\n        This event is fired on all worker processes. You should typically\n        use this event to run background tasks, or perform other actions that\n        are not directly related to handling requests. In theory, it is\n        possible that some requests may be handled before this event is fired,\n        so you should not use this event to initialize resources that are\n        required for handling requests.\n\n        A common use case for this event is to start a background task that\n        periodically performs some action, such as clearing a cache or\n        performing a health check.\n\n        See [Listeners](/en/guide/basics/listeners) for more details.\n\n        Args:\n            listener (ListenerType[Sanic]): The listener handler to attach.\n\n        Examples:\n            ```python\n            @app.after_server_start\n            async def on_after_server_start(app: Sanic):\n                print(\"After server start\")\n            ```\n        \"\"\"  # noqa: E501\n        return self._setup_listener(listener, \"after_server_start\", priority)\n\n    def before_server_stop(\n        self, listener: ListenerType[Sanic] | None, *, priority: int = 0\n    ) -> ListenerType[Sanic]:\n        \"\"\"Decorator for registering a listener for the before_server_stop event.\n\n        This event is fired on all worker processes. This event is fired\n        before the server starts shutting down. You should not use this event\n        to perform any actions that are required for handling requests, as\n        some requests may continue to be handled after this event is fired.\n\n        A common use case for this event is to stop a background task that\n        was started in the after_server_start event.\n\n        See [Listeners](/en/guide/basics/listeners) for more details.\n\n        Args:\n            listener (ListenerType[Sanic]): The listener handler to attach.\n\n        Examples:\n            ```python\n            @app.before_server_stop\n            async def on_before_server_stop(app: Sanic):\n                print(\"Before server stop\")\n            ```\n        \"\"\"  # noqa: E501\n        return self._setup_listener(listener, \"before_server_stop\", priority)\n\n    def after_server_stop(\n        self, listener: ListenerType[Sanic] | None, *, priority: int = 0\n    ) -> ListenerType[Sanic]:\n        \"\"\"Decorator for registering a listener for the after_server_stop event.\n\n        This event is fired on all worker processes. This event is fired\n        after the server has stopped shutting down, and all requests have\n        been handled. You should typically use this event to clean up\n        resources that were initialized in the before_server_start event.\n\n        A common use case for this event is to close a database connection\n        pool, or to close a cache client.\n\n        See [Listeners](/en/guide/basics/listeners) for more details.\n\n        Args:\n            listener (ListenerType[Sanic]): The listener handler to attach.\n\n        Examples:\n            ```python\n            @app.after_server_stop\n            async def on_after_server_stop(app: Sanic):\n                print(\"After server stop\")\n            ```\n        \"\"\"  # noqa: E501\n        return self._setup_listener(listener, \"after_server_stop\", priority)\n"
  },
  {
    "path": "sanic/mixins/middleware.py",
    "content": "from __future__ import annotations\n\nfrom collections import deque\nfrom functools import partial\nfrom operator import attrgetter\nfrom typing import Callable, overload\n\nfrom sanic.base.meta import SanicMeta\nfrom sanic.middleware import Middleware, MiddlewareLocation\nfrom sanic.models.futures import FutureMiddleware, MiddlewareType\nfrom sanic.router import Router\n\n\nclass MiddlewareMixin(metaclass=SanicMeta):\n    router: Router\n\n    def __init__(self, *args, **kwargs) -> None:\n        self._future_middleware: list[FutureMiddleware] = []\n\n    def _apply_middleware(self, middleware: FutureMiddleware):\n        raise NotImplementedError  # noqa\n\n    @overload\n    def middleware(\n        self,\n        middleware_or_request: MiddlewareType,\n        attach_to: str = \"request\",\n        apply: bool = True,\n        *,\n        priority: int = 0,\n    ) -> MiddlewareType: ...\n\n    @overload\n    def middleware(\n        self,\n        middleware_or_request: str,\n        attach_to: str = \"request\",\n        apply: bool = True,\n        *,\n        priority: int = 0,\n    ) -> Callable[[MiddlewareType], MiddlewareType]: ...\n\n    def middleware(\n        self,\n        middleware_or_request: MiddlewareType | str,\n        attach_to: str = \"request\",\n        apply: bool = True,\n        *,\n        priority: int = 0,\n    ) -> MiddlewareType | Callable[[MiddlewareType], MiddlewareType]:\n        \"\"\"Decorator for registering middleware.\n\n        Decorate and register middleware to be called before a request is\n        handled or after a response is created. Can either be called as\n        *@app.middleware* or *@app.middleware('request')*. Although, it is\n        recommended to use *@app.on_request* or *@app.on_response* instead\n        for clarity and convenience.\n\n        See [Middleware](/guide/basics/middleware) for more information.\n\n        Args:\n            middleware_or_request (Union[Callable, str]): Middleware function\n                or the keyword 'request' or 'response'.\n            attach_to (str, optional): When to apply the middleware;\n                either 'request' (before the request is handled) or 'response'\n                (after the response is created). Defaults to `'request'`.\n            apply (bool, optional): Whether the middleware should be applied.\n                Defaults to `True`.\n            priority (int, optional): The priority level of the middleware.\n                Lower numbers are executed first. Defaults to `0`.\n\n        Returns:\n            Union[Callable, Callable[[Callable], Callable]]: The decorated\n                middleware function or a partial function depending on how\n                the method was called.\n\n        Example:\n            ```python\n            @app.middleware('request')\n            async def custom_middleware(request):\n                ...\n            ```\n        \"\"\"\n\n        def register_middleware(middleware, attach_to=\"request\"):\n            nonlocal apply\n\n            location = (\n                MiddlewareLocation.REQUEST\n                if attach_to == \"request\"\n                else MiddlewareLocation.RESPONSE\n            )\n            middleware = Middleware(middleware, location, priority=priority)\n            future_middleware = FutureMiddleware(middleware, attach_to)\n            self._future_middleware.append(future_middleware)\n            if apply:\n                self._apply_middleware(future_middleware)\n            return middleware\n\n        # Detect which way this was called, @middleware or @middleware('AT')\n        if callable(middleware_or_request):\n            return register_middleware(\n                middleware_or_request, attach_to=attach_to\n            )\n        else:\n            return partial(\n                register_middleware, attach_to=middleware_or_request\n            )\n\n    def on_request(self, middleware=None, *, priority=0) -> MiddlewareType:\n        \"\"\"Register a middleware to be called before a request is handled.\n\n        This is the same as *@app.middleware('request')*.\n\n        Args:\n            middleware (Callable, optional): A callable that takes in a\n                request. Defaults to `None`.\n\n        Returns:\n            Callable: The decorated middleware function or a partial function\n                depending on how the method was called.\n\n        Examples:\n            ```python\n            @app.on_request\n            async def custom_middleware(request):\n                request.ctx.custom = 'value'\n            ```\n        \"\"\"\n        if callable(middleware):\n            return self.middleware(middleware, \"request\", priority=priority)\n        else:\n            return partial(  # type: ignore\n                self.middleware, attach_to=\"request\", priority=priority\n            )\n\n    def on_response(self, middleware=None, *, priority=0):\n        \"\"\"Register a middleware to be called after a response is created.\n\n        This is the same as *@app.middleware('response')*.\n\n        Args:\n            middleware (Callable, optional): A callable that takes in a\n                request and response. Defaults to `None`.\n\n        Returns:\n            Callable: The decorated middleware function or a partial function\n                depending on how the method was called.\n\n        Examples:\n            ```python\n            @app.on_response\n            async def custom_middleware(request, response):\n                response.headers['X-Server'] = 'Sanic'\n            ```\n        \"\"\"\n        if callable(middleware):\n            return self.middleware(middleware, \"response\", priority=priority)\n        else:\n            return partial(\n                self.middleware, attach_to=\"response\", priority=priority\n            )\n\n    def finalize_middleware(self) -> None:\n        \"\"\"Finalize the middleware configuration for the Sanic application.\n\n        This method completes the middleware setup for the application.\n        Middleware in Sanic is used to process requests globally before they\n        reach individual routes or after routes have been processed.\n\n        Finalization consists of identifying defined routes and optimizing\n        Sanic's performance to meet the application's specific needs. If\n        you are manually adding routes, after Sanic has started, you will\n        typically want to use the `amend` context manager rather than\n        calling this method directly.\n\n        .. note::\n            This method is usually called internally during the server setup\n            process and does not typically need to be invoked manually.\n\n        Example:\n            ```python\n            app.finalize_middleware()\n            ```\n        \"\"\"\n        for route in self.router.routes:\n            request_middleware = Middleware.convert(\n                self.request_middleware,  # type: ignore\n                self.named_request_middleware.get(route.name, deque()),  # type: ignore  # noqa: E501\n                location=MiddlewareLocation.REQUEST,\n            )\n            response_middleware = Middleware.convert(\n                self.response_middleware,  # type: ignore\n                self.named_response_middleware.get(route.name, deque()),  # type: ignore  # noqa: E501\n                location=MiddlewareLocation.RESPONSE,\n            )\n            route.extra.request_middleware = deque(\n                sorted(\n                    request_middleware,\n                    key=attrgetter(\"order\"),\n                    reverse=True,\n                )\n            )\n            route.extra.response_middleware = deque(\n                sorted(\n                    response_middleware,\n                    key=attrgetter(\"order\"),\n                    reverse=True,\n                )[::-1]\n            )\n        request_middleware = Middleware.convert(\n            self.request_middleware,  # type: ignore\n            location=MiddlewareLocation.REQUEST,\n        )\n        response_middleware = Middleware.convert(\n            self.response_middleware,  # type: ignore\n            location=MiddlewareLocation.RESPONSE,\n        )\n        self.request_middleware = deque(\n            sorted(\n                request_middleware,\n                key=attrgetter(\"order\"),\n                reverse=True,\n            )\n        )\n        self.response_middleware = deque(\n            sorted(\n                response_middleware,\n                key=attrgetter(\"order\"),\n                reverse=True,\n            )[::-1]\n        )\n"
  },
  {
    "path": "sanic/mixins/routes.py",
    "content": "from __future__ import annotations\n\nfrom ast import NodeVisitor, Return, parse\nfrom collections.abc import Iterable\nfrom contextlib import suppress\nfrom inspect import getsource, signature\nfrom textwrap import dedent\nfrom typing import (\n    Any,\n    Callable,\n    cast,\n)\n\nfrom sanic_routing.route import Route\n\nfrom sanic.base.meta import SanicMeta\nfrom sanic.constants import HTTP_METHODS\nfrom sanic.errorpages import RESPONSE_MAPPING\nfrom sanic.mixins.base import BaseMixin\nfrom sanic.models.futures import FutureRoute, FutureStatic\nfrom sanic.models.handler_types import RouteHandler\nfrom sanic.types import HashableDict\n\n\nRouteWrapper = Callable[\n    [RouteHandler], RouteHandler | tuple[Route, RouteHandler]\n]\n\n\nclass RouteMixin(BaseMixin, metaclass=SanicMeta):\n    def __init__(self, *args, **kwargs) -> None:\n        self._future_routes: set[FutureRoute] = set()\n        self._future_statics: set[FutureStatic] = set()\n\n    def _apply_route(self, route: FutureRoute) -> list[Route]:\n        raise NotImplementedError  # noqa\n\n    def route(\n        self,\n        uri: str,\n        methods: Iterable[str] | None = None,\n        host: str | list[str] | None = None,\n        strict_slashes: bool | None = None,\n        stream: bool = False,\n        version: int | str | float | None = None,\n        name: str | None = None,\n        ignore_body: bool = False,\n        apply: bool = True,\n        subprotocols: list[str] | None = None,\n        websocket: bool = False,\n        unquote: bool = False,\n        static: bool = False,\n        version_prefix: str = \"/v\",\n        error_format: str | None = None,\n        **ctx_kwargs: Any,\n    ) -> RouteWrapper:\n        \"\"\"Decorate a function to be registered as a route.\n\n        Args:\n            uri (str): Path of the URL.\n            methods (Optional[Iterable[str]]): List or tuple of\n                methods allowed.\n            host (Optional[Union[str, List[str]]]): The host, if required.\n            strict_slashes (Optional[bool]): Whether to apply strict slashes\n                to the route.\n            stream (bool): Whether to allow the request to stream its body.\n            version (Optional[Union[int, str, float]]): Route specific\n                versioning.\n            name (Optional[str]): User-defined route name for url_for.\n            ignore_body (bool): Whether the handler should ignore request\n                body (e.g. `GET` requests).\n            apply (bool): Apply middleware to the route.\n            subprotocols (Optional[List[str]]): List of subprotocols.\n            websocket (bool): Enable WebSocket support.\n            unquote (bool): Unquote special characters in the URL path.\n            static (bool): Enable static route.\n            version_prefix (str): URL path that should be before the version\n                 value; default: `\"/v\"`.\n            error_format (Optional[str]): Error format for the route.\n            ctx_kwargs (Any): Keyword arguments that begin with a `ctx_*`\n                prefix will be appended to the route context (`route.ctx`).\n\n        Returns:\n            RouteWrapper: Tuple of routes, decorated function.\n\n        Examples:\n            Using the method to define a GET endpoint:\n\n            ```python\n            @app.route(\"/hello\")\n            async def hello(request: Request):\n                return text(\"Hello, World!\")\n            ```\n\n            Adding context kwargs to the route:\n\n            ```python\n            @app.route(\"/greet\", ctx_name=\"World\")\n            async def greet(request: Request):\n                name = request.route.ctx.name\n                return text(f\"Hello, {name}!\")\n            ```\n        \"\"\"\n\n        # Fix case where the user did not prefix the URL with a /\n        # and will probably get confused as to why it's not working\n        if not uri.startswith(\"/\") and (uri or hasattr(self, \"router\")):\n            uri = \"/\" + uri\n\n        if strict_slashes is None:\n            strict_slashes = self.strict_slashes\n\n        if not methods and not websocket:\n            methods = frozenset({\"GET\"})\n\n        route_context = self._build_route_context(ctx_kwargs)\n\n        def decorator(handler):\n            nonlocal uri\n            nonlocal methods\n            nonlocal host\n            nonlocal strict_slashes\n            nonlocal stream\n            nonlocal version\n            nonlocal name\n            nonlocal ignore_body\n            nonlocal subprotocols\n            nonlocal websocket\n            nonlocal static\n            nonlocal version_prefix\n            nonlocal error_format\n\n            if isinstance(handler, tuple):\n                # if a handler fn is already wrapped in a route, the handler\n                # variable will be a tuple of (existing routes, handler fn)\n                _, handler = handler\n\n            name = self.generate_name(name, handler)\n\n            if isinstance(host, str):\n                host = frozenset([host])\n            elif host and not isinstance(host, frozenset):\n                try:\n                    host = frozenset(host)\n                except TypeError:\n                    raise ValueError(\n                        \"Expected either string or Iterable of host strings, \"\n                        \"not %s\" % host\n                    )\n            if isinstance(subprotocols, list):\n                # Ordered subprotocols, maintain order\n                subprotocols = tuple(subprotocols)\n            elif isinstance(subprotocols, set):\n                # subprotocol is unordered, keep it unordered\n                subprotocols = frozenset(subprotocols)\n\n            if not error_format or error_format == \"auto\":\n                error_format = self._determine_error_format(handler)\n\n            route = FutureRoute(\n                handler,\n                uri,\n                None if websocket else frozenset([x.upper() for x in methods]),\n                host,\n                strict_slashes,\n                stream,\n                version,\n                name,\n                ignore_body,\n                websocket,\n                subprotocols,\n                unquote,\n                static,\n                version_prefix,\n                error_format,\n                route_context,\n            )\n            overwrite = getattr(self, \"_allow_route_overwrite\", False)\n            if overwrite:\n                self._future_routes = set(\n                    filter(lambda x: x.uri != uri, self._future_routes)\n                )\n            self._future_routes.add(route)\n\n            args = list(signature(handler).parameters.keys())\n            if websocket and len(args) < 2:\n                handler_name = handler.__name__\n\n                raise ValueError(\n                    f\"Required parameter `request` and/or `ws` missing \"\n                    f\"in the {handler_name}() route?\"\n                )\n            elif not args:\n                handler_name = handler.__name__\n\n                raise ValueError(\n                    f\"Required parameter `request` missing \"\n                    f\"in the {handler_name}() route?\"\n                )\n\n            if not websocket and stream:\n                handler.is_stream = stream\n\n            if apply:\n                self._apply_route(route, overwrite=overwrite)\n\n            if static:\n                return route, handler\n            return handler\n\n        return decorator\n\n    def add_route(\n        self,\n        handler: RouteHandler,\n        uri: str,\n        methods: Iterable[str] = frozenset({\"GET\"}),\n        host: str | list[str] | None = None,\n        strict_slashes: bool | None = None,\n        version: int | str | float | None = None,\n        name: str | None = None,\n        stream: bool = False,\n        version_prefix: str = \"/v\",\n        error_format: str | None = None,\n        unquote: bool = False,\n        **ctx_kwargs: Any,\n    ) -> RouteHandler:\n        \"\"\"A helper method to register class-based view or functions as a handler to the application url routes.\n\n        Args:\n            handler (RouteHandler): Function or class-based view used as a route handler.\n            uri (str): Path of the URL.\n            methods (Iterable[str]): List or tuple of methods allowed; these are overridden if using an HTTPMethodView.\n            host (Optional[Union[str, List[str]]]): Hostname or hostnames to match for this route.\n            strict_slashes (Optional[bool]): If set, a route's slashes will be strict. E.g. `/foo` will not match `/foo/`.\n            version (Optional[Union[int, str, float]]): Version of the API for this route.\n            name (Optional[str]): User-defined route name for `url_for`.\n            stream (bool): Boolean specifying if the handler is a stream handler.\n            version_prefix (str): URL path that should be before the version value; default: ``/v``.\n            error_format (Optional[str]): Custom error format string.\n            unquote (bool): Boolean specifying if the handler requires unquoting.\n            ctx_kwargs (Any): Keyword arguments that begin with a `ctx_*` prefix will be appended to the route context (``route.ctx``). See below for examples.\n\n        Returns:\n            RouteHandler: The route handler.\n\n        Examples:\n            ```python\n            from sanic import Sanic, text\n\n            app = Sanic(\"test\")\n\n            async def handler(request):\n                return text(\"OK\")\n\n            app.add_route(handler, \"/test\", methods=[\"GET\", \"POST\"])\n            ```\n\n            You can use `ctx_kwargs` to add custom context to the route. This\n            can often be useful when wanting to add metadata to a route that\n            can be used by other parts of the application (like middleware).\n\n            ```python\n            from sanic import Sanic, text\n\n            app = Sanic(\"test\")\n\n            async def handler(request):\n                return text(\"OK\")\n\n            async def custom_middleware(request):\n                if request.route.ctx.monitor:\n                    do_some_monitoring()\n\n            app.add_route(handler, \"/test\", methods=[\"GET\", \"POST\"], ctx_monitor=True)\n            app.register_middleware(custom_middleware)\n        \"\"\"  # noqa: E501\n        # Handle HTTPMethodView differently\n        if hasattr(handler, \"view_class\"):\n            methods = set()\n\n            for method in HTTP_METHODS:\n                view_class = getattr(handler, \"view_class\")\n                _handler = getattr(view_class, method.lower(), None)\n                if _handler:\n                    methods.add(method)\n                    if hasattr(_handler, \"is_stream\"):\n                        stream = True\n\n        if strict_slashes is None:\n            strict_slashes = self.strict_slashes\n\n        self.route(\n            uri=uri,\n            methods=methods,\n            host=host,\n            strict_slashes=strict_slashes,\n            stream=stream,\n            version=version,\n            name=name,\n            version_prefix=version_prefix,\n            error_format=error_format,\n            unquote=unquote,\n            **ctx_kwargs,\n        )(handler)\n        return handler\n\n    # Shorthand method decorators\n    def get(\n        self,\n        uri: str,\n        host: str | list[str] | None = None,\n        strict_slashes: bool | None = None,\n        version: int | str | float | None = None,\n        name: str | None = None,\n        ignore_body: bool = True,\n        version_prefix: str = \"/v\",\n        error_format: str | None = None,\n        **ctx_kwargs: Any,\n    ) -> RouteHandler:\n        \"\"\"Decorate a function handler to create a route definition using the **GET** HTTP method.\n\n        Args:\n            uri (str): URL to be tagged to GET method of HTTP.\n            host (Optional[Union[str, List[str]]]): Host IP or FQDN for\n                the service to use.\n            strict_slashes (Optional[bool]): Instruct Sanic to check if the\n                request URLs need to terminate with a `/`.\n            version (Optional[Union[int, str, float]]): API Version.\n            name (Optional[str]): Unique name that can be used to identify\n                the route.\n            ignore_body (bool): Whether the handler should ignore request\n                body. This means the body of the request, if sent, will not\n                be consumed. In that instance, you will see a warning in\n                the logs. Defaults to `True`, meaning do not consume the body.\n            version_prefix (str): URL path that should be before the version\n                value. Defaults to `\"/v\"`.\n            error_format (Optional[str]): Custom error format string.\n            **ctx_kwargs (Any): Keyword arguments that begin with a\n                `ctx_* prefix` will be appended to the route\n                context (`route.ctx`).\n\n        Returns:\n            RouteHandler: Object decorated with route method.\n        \"\"\"  # noqa: E501\n        return cast(\n            RouteHandler,\n            self.route(\n                uri,\n                methods=frozenset({\"GET\"}),\n                host=host,\n                strict_slashes=strict_slashes,\n                version=version,\n                name=name,\n                ignore_body=ignore_body,\n                version_prefix=version_prefix,\n                error_format=error_format,\n                **ctx_kwargs,\n            ),\n        )\n\n    def post(\n        self,\n        uri: str,\n        host: str | list[str] | None = None,\n        strict_slashes: bool | None = None,\n        stream: bool = False,\n        version: int | str | float | None = None,\n        name: str | None = None,\n        version_prefix: str = \"/v\",\n        error_format: str | None = None,\n        **ctx_kwargs: Any,\n    ) -> RouteHandler:\n        \"\"\"Decorate a function handler to create a route definition using the **POST** HTTP method.\n\n        Args:\n            uri (str): URL to be tagged to POST method of HTTP.\n            host (Optional[Union[str, List[str]]]): Host IP or FQDN for\n                the service to use.\n            strict_slashes (Optional[bool]): Instruct Sanic to check if the\n                request URLs need to terminate with a `/`.\n            stream (bool): Whether or not to stream the request body.\n                Defaults to `False`.\n            version (Optional[Union[int, str, float]]): API Version.\n            name (Optional[str]): Unique name that can be used to identify\n                the route.\n            version_prefix (str): URL path that should be before the version\n                value. Defaults to `\"/v\"`.\n            error_format (Optional[str]): Custom error format string.\n            **ctx_kwargs (Any): Keyword arguments that begin with a\n                `ctx_*` prefix will be appended to the route\n                context (`route.ctx`).\n\n        Returns:\n            RouteHandler: Object decorated with route method.\n        \"\"\"  # noqa: E501\n        return cast(\n            RouteHandler,\n            self.route(\n                uri,\n                methods=frozenset({\"POST\"}),\n                host=host,\n                strict_slashes=strict_slashes,\n                stream=stream,\n                version=version,\n                name=name,\n                version_prefix=version_prefix,\n                error_format=error_format,\n                **ctx_kwargs,\n            ),\n        )\n\n    def put(\n        self,\n        uri: str,\n        host: str | list[str] | None = None,\n        strict_slashes: bool | None = None,\n        stream: bool = False,\n        version: int | str | float | None = None,\n        name: str | None = None,\n        version_prefix: str = \"/v\",\n        error_format: str | None = None,\n        **ctx_kwargs: Any,\n    ) -> RouteHandler:\n        \"\"\"Decorate a function handler to create a route definition using the **PUT** HTTP method.\n\n        Args:\n            uri (str): URL to be tagged to PUT method of HTTP.\n            host (Optional[Union[str, List[str]]]): Host IP or FQDN for\n                the service to use.\n            strict_slashes (Optional[bool]): Instruct Sanic to check if the\n                request URLs need to terminate with a `/`.\n            stream (bool): Whether or not to stream the request body.\n                Defaults to `False`.\n            version (Optional[Union[int, str, float]]): API Version.\n            name (Optional[str]): Unique name that can be used to identify\n                the route.\n            version_prefix (str): URL path that should be before the version\n                value. Defaults to `\"/v\"`.\n            error_format (Optional[str]): Custom error format string.\n            **ctx_kwargs (Any): Keyword arguments that begin with a\n                `ctx_*` prefix will be appended to the route\n                context (`route.ctx`).\n\n        Returns:\n            RouteHandler: Object decorated with route method.\n        \"\"\"  # noqa: E501\n        return cast(\n            RouteHandler,\n            self.route(\n                uri,\n                methods=frozenset({\"PUT\"}),\n                host=host,\n                strict_slashes=strict_slashes,\n                stream=stream,\n                version=version,\n                name=name,\n                version_prefix=version_prefix,\n                error_format=error_format,\n                **ctx_kwargs,\n            ),\n        )\n\n    def head(\n        self,\n        uri: str,\n        host: str | list[str] | None = None,\n        strict_slashes: bool | None = None,\n        version: int | str | float | None = None,\n        name: str | None = None,\n        ignore_body: bool = True,\n        version_prefix: str = \"/v\",\n        error_format: str | None = None,\n        **ctx_kwargs: Any,\n    ) -> RouteHandler:\n        \"\"\"Decorate a function handler to create a route definition using the **HEAD** HTTP method.\n\n        Args:\n            uri (str): URL to be tagged to HEAD method of HTTP.\n            host (Optional[Union[str, List[str]]]): Host IP or FQDN for\n                the service to use.\n            strict_slashes (Optional[bool]): Instruct Sanic to check if the\n                request URLs need to terminate with a `/`.\n            version (Optional[Union[int, str, float]]): API Version.\n            name (Optional[str]): Unique name that can be used to identify\n                the route.\n            ignore_body (bool): Whether the handler should ignore request\n                body. This means the body of the request, if sent, will not\n                be consumed. In that instance, you will see a warning in\n                the logs. Defaults to `True`, meaning do not consume the body.\n            version_prefix (str): URL path that should be before the version\n                value. Defaults to `\"/v\"`.\n            error_format (Optional[str]): Custom error format string.\n            **ctx_kwargs (Any): Keyword arguments that begin with a\n                `ctx_*` prefix will be appended to the route\n                context (`route.ctx`).\n\n        Returns:\n            RouteHandler: Object decorated with route method.\n        \"\"\"  # noqa: E501\n        return cast(\n            RouteHandler,\n            self.route(\n                uri,\n                methods=frozenset({\"HEAD\"}),\n                host=host,\n                strict_slashes=strict_slashes,\n                version=version,\n                name=name,\n                ignore_body=ignore_body,\n                version_prefix=version_prefix,\n                error_format=error_format,\n                **ctx_kwargs,\n            ),\n        )\n\n    def options(\n        self,\n        uri: str,\n        host: str | list[str] | None = None,\n        strict_slashes: bool | None = None,\n        version: int | str | float | None = None,\n        name: str | None = None,\n        ignore_body: bool = True,\n        version_prefix: str = \"/v\",\n        error_format: str | None = None,\n        **ctx_kwargs: Any,\n    ) -> RouteHandler:\n        \"\"\"Decorate a function handler to create a route definition using the **OPTIONS** HTTP method.\n\n        Args:\n            uri (str): URL to be tagged to OPTIONS method of HTTP.\n            host (Optional[Union[str, List[str]]]): Host IP or FQDN for\n                the service to use.\n            strict_slashes (Optional[bool]): Instruct Sanic to check if the\n                request URLs need to terminate with a `/`.\n            version (Optional[Union[int, str, float]]): API Version.\n            name (Optional[str]): Unique name that can be used to identify\n                the route.\n            ignore_body (bool): Whether the handler should ignore request\n                body. This means the body of the request, if sent, will not\n                be consumed. In that instance, you will see a warning in\n                the logs. Defaults to `True`, meaning do not consume the body.\n            version_prefix (str): URL path that should be before the version\n                value. Defaults to `\"/v\"`.\n            error_format (Optional[str]): Custom error format string.\n            **ctx_kwargs (Any): Keyword arguments that begin with a\n                `ctx_*` prefix will be appended to the route\n                context (`route.ctx`).\n\n        Returns:\n            RouteHandler: Object decorated with route method.\n        \"\"\"  # noqa: E501\n        return cast(\n            RouteHandler,\n            self.route(\n                uri,\n                methods=frozenset({\"OPTIONS\"}),\n                host=host,\n                strict_slashes=strict_slashes,\n                version=version,\n                name=name,\n                ignore_body=ignore_body,\n                version_prefix=version_prefix,\n                error_format=error_format,\n                **ctx_kwargs,\n            ),\n        )\n\n    def patch(\n        self,\n        uri: str,\n        host: str | list[str] | None = None,\n        strict_slashes: bool | None = None,\n        stream=False,\n        version: int | str | float | None = None,\n        name: str | None = None,\n        version_prefix: str = \"/v\",\n        error_format: str | None = None,\n        **ctx_kwargs: Any,\n    ) -> RouteHandler:\n        \"\"\"Decorate a function handler to create a route definition using the **PATCH** HTTP method.\n\n        Args:\n            uri (str): URL to be tagged to PATCH method of HTTP.\n            host (Optional[Union[str, List[str]]]): Host IP or FQDN for\n                the service to use.\n            strict_slashes (Optional[bool]): Instruct Sanic to check if the\n                request URLs need to terminate with a `/`.\n            stream (bool): Set to `True` if full request streaming is needed,\n                `False` otherwise. Defaults to `False`.\n            version (Optional[Union[int, str, float]]): API Version.\n            name (Optional[str]): Unique name that can be used to identify\n                the route.\n            version_prefix (str): URL path that should be before the version\n                value. Defaults to `\"/v\"`.\n            error_format (Optional[str]): Custom error format string.\n            **ctx_kwargs (Any): Keyword arguments that begin with a\n                `ctx_*` prefix will be appended to the route\n                context (`route.ctx`).\n\n        Returns:\n            RouteHandler: Object decorated with route method.\n        \"\"\"  # noqa: E501\n        return cast(\n            RouteHandler,\n            self.route(\n                uri,\n                methods=frozenset({\"PATCH\"}),\n                host=host,\n                strict_slashes=strict_slashes,\n                stream=stream,\n                version=version,\n                name=name,\n                version_prefix=version_prefix,\n                error_format=error_format,\n                **ctx_kwargs,\n            ),\n        )\n\n    def delete(\n        self,\n        uri: str,\n        host: str | list[str] | None = None,\n        strict_slashes: bool | None = None,\n        version: int | str | float | None = None,\n        name: str | None = None,\n        ignore_body: bool = False,\n        version_prefix: str = \"/v\",\n        error_format: str | None = None,\n        **ctx_kwargs: Any,\n    ) -> RouteHandler:\n        \"\"\"Decorate a function handler to create a route definition using the **DELETE** HTTP method.\n\n        Args:\n            uri (str): URL to be tagged to the DELETE method of HTTP.\n            host (Optional[Union[str, List[str]]]): Host IP or FQDN for the\n                service to use.\n            strict_slashes (Optional[bool]): Instruct Sanic to check if the\n                request URLs need to terminate with a */*.\n            version (Optional[Union[int, str, float]]): API Version.\n            name (Optional[str]): Unique name that can be used to identify\n                the Route.\n            ignore_body (bool): Whether or not to ignore the body in the\n                request. Defaults to `False`.\n            version_prefix (str): URL path that should be before the version\n                value. Defaults to `\"/v\"`.\n            error_format (Optional[str]): Custom error format string.\n            **ctx_kwargs (Any): Keyword arguments that begin with a `ctx_*`\n                prefix will be appended to the route context (`route.ctx`).\n\n        Returns:\n            RouteHandler: Object decorated with route method.\n        \"\"\"  # noqa: E501\n        return cast(\n            RouteHandler,\n            self.route(\n                uri,\n                methods=frozenset({\"DELETE\"}),\n                host=host,\n                strict_slashes=strict_slashes,\n                version=version,\n                name=name,\n                ignore_body=ignore_body,\n                version_prefix=version_prefix,\n                error_format=error_format,\n                **ctx_kwargs,\n            ),\n        )\n\n    def websocket(\n        self,\n        uri: str,\n        host: str | list[str] | None = None,\n        strict_slashes: bool | None = None,\n        subprotocols: list[str] | None = None,\n        version: int | str | float | None = None,\n        name: str | None = None,\n        apply: bool = True,\n        version_prefix: str = \"/v\",\n        error_format: str | None = None,\n        **ctx_kwargs: Any,\n    ):\n        \"\"\"Decorate a function to be registered as a websocket route.\n\n        Args:\n            uri (str): Path of the URL.\n            host (Optional[Union[str, List[str]]]): Host IP or FQDN details.\n            strict_slashes (Optional[bool]): If the API endpoint needs to\n                terminate with a `\"/\"` or not.\n            subprotocols (Optional[List[str]]): Optional list of str with\n                supported subprotocols.\n            version (Optional[Union[int, str, float]]): WebSocket\n                protocol version.\n            name (Optional[str]): A unique name assigned to the URL so that\n                it can be used with url_for.\n            apply (bool): If set to False, it doesn't apply the route to the\n                app. Default is `True`.\n            version_prefix (str): URL path that should be before the version\n                value. Defaults to `\"/v\"`.\n            error_format (Optional[str]): Custom error format string.\n            **ctx_kwargs (Any): Keyword arguments that begin with\n                a `ctx_* prefix` will be appended to the route\n                context (`route.ctx`).\n\n        Returns:\n            tuple: Tuple of routes, decorated function.\n        \"\"\"\n        return self.route(\n            uri=uri,\n            host=host,\n            methods=None,\n            strict_slashes=strict_slashes,\n            version=version,\n            name=name,\n            apply=apply,\n            subprotocols=subprotocols,\n            websocket=True,\n            version_prefix=version_prefix,\n            error_format=error_format,\n            **ctx_kwargs,\n        )\n\n    def add_websocket_route(\n        self,\n        handler,\n        uri: str,\n        host: str | list[str] | None = None,\n        strict_slashes: bool | None = None,\n        subprotocols=None,\n        version: int | str | float | None = None,\n        name: str | None = None,\n        version_prefix: str = \"/v\",\n        error_format: str | None = None,\n        **ctx_kwargs: Any,\n    ):\n        \"\"\"A helper method to register a function as a websocket route.\n\n        Args:\n            handler (Callable): A callable function or instance of a class\n                that can handle the websocket request.\n            uri (str): URL path that will be mapped to the websocket handler.\n            host (Optional[Union[str, List[str]]]): Host IP or FQDN details.\n            strict_slashes (Optional[bool]): If the API endpoint needs to\n                terminate with a `\"/\"` or not.\n            subprotocols (Optional[List[str]]): Subprotocols to be used with\n                websocket handshake.\n            version (Optional[Union[int, str, float]]): Versioning information.\n            name (Optional[str]): A unique name assigned to the URL.\n            version_prefix (str): URL path before the version value.\n                Defaults to `\"/v\"`.\n            error_format (Optional[str]): Format for error handling.\n            **ctx_kwargs (Any): Keyword arguments beginning with `ctx_*`\n                prefix will be appended to the route context (`route.ctx`).\n\n        Returns:\n            Callable: Object passed as the handler.\n        \"\"\"\n        return self.websocket(\n            uri=uri,\n            host=host,\n            strict_slashes=strict_slashes,\n            subprotocols=subprotocols,\n            version=version,\n            name=name,\n            version_prefix=version_prefix,\n            error_format=error_format,\n            **ctx_kwargs,\n        )(handler)\n\n    def _determine_error_format(self, handler) -> str:\n        with suppress(OSError, TypeError):\n            src = dedent(getsource(handler))\n            tree = parse(src)\n            http_response_types = self._get_response_types(tree)\n\n            if len(http_response_types) == 1:\n                return next(iter(http_response_types))\n\n        return \"\"\n\n    def _get_response_types(self, node):\n        types = set()\n\n        class HttpResponseVisitor(NodeVisitor):\n            def visit_Return(self, node: Return) -> Any:\n                nonlocal types\n\n                with suppress(AttributeError):\n                    checks = [node.value.func.id]  # type: ignore\n                    if node.value.keywords:  # type: ignore\n                        checks += [\n                            k.value\n                            for k in node.value.keywords  # type: ignore\n                            if k.arg == \"content_type\"\n                        ]\n\n                    for check in checks:\n                        if check in RESPONSE_MAPPING:\n                            types.add(RESPONSE_MAPPING[check])\n\n        HttpResponseVisitor().visit(node)\n\n        return types\n\n    def _build_route_context(self, raw: dict[str, Any]) -> HashableDict:\n        ctx_kwargs = {\n            key.replace(\"ctx_\", \"\"): raw.pop(key)\n            for key in {**raw}.keys()\n            if key.startswith(\"ctx_\")\n        }\n        if raw:\n            unexpected_arguments = \", \".join(raw.keys())\n            raise TypeError(\n                f\"Unexpected keyword arguments: {unexpected_arguments}\"\n            )\n        return HashableDict(ctx_kwargs)\n"
  },
  {
    "path": "sanic/mixins/signals.py",
    "content": "from __future__ import annotations\n\nfrom collections.abc import Coroutine\nfrom enum import Enum\nfrom typing import Any, Callable\n\nfrom sanic.base.meta import SanicMeta\nfrom sanic.models.futures import FutureSignal\nfrom sanic.models.handler_types import SignalHandler\nfrom sanic.signals import Event, Signal\nfrom sanic.types import HashableDict\n\n\nclass SignalMixin(metaclass=SanicMeta):\n    def __init__(self, *args, **kwargs) -> None:\n        self._future_signals: set[FutureSignal] = set()\n\n    def _apply_signal(self, signal: FutureSignal) -> Signal:\n        raise NotImplementedError  # noqa\n\n    def signal(\n        self,\n        event: str | Enum,\n        *,\n        apply: bool = True,\n        condition: dict[str, Any] | None = None,\n        exclusive: bool = True,\n        priority: int = 0,\n    ) -> Callable[[SignalHandler], SignalHandler]:\n        \"\"\"\n        For creating a signal handler, used similar to a route handler:\n\n        .. code-block:: python\n\n            @app.signal(\"foo.bar.<thing>\")\n            async def signal_handler(thing, **kwargs):\n                print(f\"[signal_handler] {thing=}\", kwargs)\n\n        :param event: Representation of the event in ``one.two.three`` form\n        :type event: str\n        :param apply: For lazy evaluation, defaults to ``True``\n        :type apply: bool, optional\n        :param condition: For use with the ``condition`` argument in dispatch\n            filtering, defaults to ``None``\n        :param exclusive: When ``True``, the signal can only be dispatched\n            when the condition has been met. When ``False``, the signal can\n            be dispatched either with or without it. *THIS IS INAPPLICABLE TO\n            BLUEPRINT SIGNALS. THEY ARE ALWAYS NON-EXCLUSIVE*, defaults\n            to ``True``\n        :type condition: Dict[str, Any], optional\n        \"\"\"\n        event_value = str(event.value) if isinstance(event, Enum) else event\n\n        def decorator(handler: SignalHandler):\n            future_signal = FutureSignal(\n                handler,\n                event_value,\n                HashableDict(condition or {}),\n                exclusive,\n                priority,\n            )\n            self._future_signals.add(future_signal)\n\n            if apply:\n                self._apply_signal(future_signal)\n\n            return handler\n\n        return decorator\n\n    def add_signal(\n        self,\n        handler: Callable[..., Any] | None,\n        event: str | Enum,\n        condition: dict[str, Any] | None = None,\n        exclusive: bool = True,\n    ) -> Callable[..., Any]:\n        \"\"\"Registers a signal handler for a specific event.\n\n        Args:\n            handler (Optional[Callable[..., Any]]): The function to be called\n                when the event occurs. Defaults to a noop if not provided.\n            event (str): The name of the event to listen for.\n            condition (Optional[Dict[str, Any]]): Optional condition to filter\n                the event triggering. Defaults to `None`.\n            exclusive (bool): Whether or not the handler is exclusive. When\n                `True`, the signal can only be dispatched when the\n                `condition` has been met. *This is inapplicable to blueprint\n                signals, which are **ALWAYS** non-exclusive.* Defaults\n                to `True`.\n\n        Returns:\n            Callable[..., Any]: The handler that was registered.\n        \"\"\"\n        if not handler:\n\n            async def noop(**context): ...\n\n            handler = noop\n        self.signal(event=event, condition=condition, exclusive=exclusive)(\n            handler\n        )\n        return handler\n\n    def event(self, event: str):\n        raise NotImplementedError\n\n    def catch_exception(\n        self,\n        handler: Callable[[SignalMixin, Exception], Coroutine[Any, Any, None]],\n    ) -> None:\n        \"\"\"Register an exception handler for logging or processing.\n\n        This method allows the registration of a custom exception handler to\n        catch and process exceptions that occur in the application. Unlike a\n        typical exception handler that might modify the response to the client,\n        this is intended to capture exceptions for logging or other internal\n        processing, such as sending them to an error reporting utility.\n\n        Args:\n            handler (Callable): A coroutine function that takes the application\n                instance and the exception as arguments. It will be called when\n                an exception occurs within the application's lifecycle.\n\n        Example:\n            ```python\n            app = Sanic(\"TestApp\")\n\n            @app.catch_exception\n            async def report_exception(app: Sanic, exception: Exception):\n                logging.error(f\"An exception occurred: {exception}\")\n\n                # Send to an error reporting service\n                await error_service.report(exception)\n\n            # Any unhandled exceptions within the application will now be\n            # logged and reported to the error service.\n            ```\n        \"\"\"  # noqa: E501\n\n        async def signal_handler(exception: Exception):\n            await handler(self, exception)\n\n        self.signal(Event.SERVER_EXCEPTION_REPORT)(signal_handler)\n"
  },
  {
    "path": "sanic/mixins/startup.py",
    "content": "from __future__ import annotations\n\nimport os\nimport platform\n\nfrom asyncio import (\n    AbstractEventLoop,\n    CancelledError,\n    Protocol,\n    all_tasks,\n    get_event_loop,\n    get_running_loop,\n    new_event_loop,\n)\nfrom collections.abc import Mapping\nfrom contextlib import suppress\nfrom functools import partial\nfrom importlib import import_module\nfrom multiprocessing import (\n    Manager,\n    Pipe,\n    get_context,\n    get_start_method,\n    set_start_method,\n)\nfrom multiprocessing.context import BaseContext\nfrom pathlib import Path\nfrom socket import SHUT_RDWR, socket\nfrom ssl import SSLContext\nfrom time import sleep\nfrom typing import (\n    TYPE_CHECKING,\n    Any,\n    Callable,\n    ClassVar,\n    Literal,\n    cast,\n)\n\nfrom sanic.application.ext import setup_ext\nfrom sanic.application.logo import get_logo\nfrom sanic.application.motd import MOTD\nfrom sanic.application.state import ApplicationServerInfo, Mode, ServerStage\nfrom sanic.base.meta import SanicMeta\nfrom sanic.compat import OS_IS_WINDOWS, StartMethod\nfrom sanic.exceptions import ServerError, ServerKilled\nfrom sanic.helpers import Default, _default, is_atty\nfrom sanic.http.constants import HTTP\nfrom sanic.http.tls import get_ssl_context, process_to_context\nfrom sanic.http.tls.context import SanicSSLContext\nfrom sanic.log import Colors, deprecation, error_logger, logger\nfrom sanic.logging.setup import setup_logging\nfrom sanic.models.handler_types import ListenerType\nfrom sanic.server import Signal as ServerSignal\nfrom sanic.server import try_use_uvloop\nfrom sanic.server.async_server import AsyncioServer\nfrom sanic.server.events import trigger_events\nfrom sanic.server.goodbye import get_goodbye\nfrom sanic.server.loop import try_windows_loop\nfrom sanic.server.protocols.http_protocol import HttpProtocol\nfrom sanic.server.protocols.websocket_protocol import WebSocketProtocol\nfrom sanic.server.runners import serve\nfrom sanic.server.socket import configure_socket, remove_unix_socket\nfrom sanic.worker.loader import AppLoader\nfrom sanic.worker.manager import WorkerManager\nfrom sanic.worker.multiplexer import WorkerMultiplexer\nfrom sanic.worker.reloader import Reloader\nfrom sanic.worker.serve import worker_serve\n\n\nif TYPE_CHECKING:\n    from sanic import Sanic\n    from sanic.application.state import ApplicationState\n    from sanic.config import Config\n\nSANIC_PACKAGES = (\"sanic-routing\", \"sanic-testing\", \"sanic-ext\")\n\n\nHTTPVersion = HTTP | Literal[1] | Literal[3]\n\n\nclass StartupMixin(metaclass=SanicMeta):\n    _app_registry: ClassVar[dict[str, Sanic]]\n    name: str\n    asgi: bool\n    config: Config\n    listeners: dict[str, list[ListenerType[Any]]]\n    state: ApplicationState\n    websocket_enabled: bool\n    multiplexer: WorkerMultiplexer\n\n    test_mode: ClassVar[bool]\n    start_method: ClassVar[StartMethod] = _default\n    START_METHOD_SET: ClassVar[bool] = False\n\n    def setup_loop(self) -> None:\n        \"\"\"Set up the event loop.\n\n        An internal method that sets up the event loop to uvloop if\n        possible, or a Windows selector loop if on Windows.\n\n        Returns:\n            None\n        \"\"\"\n        if not self.asgi:\n            if self.config.USE_UVLOOP is True or (\n                isinstance(self.config.USE_UVLOOP, Default)\n                and not OS_IS_WINDOWS\n            ):\n                try_use_uvloop()\n            elif OS_IS_WINDOWS:\n                try_windows_loop()\n\n    @property\n    def m(self) -> WorkerMultiplexer:\n        \"\"\"Interface for interacting with the worker processes\n\n        This is a shortcut for `app.multiplexer`. It is available only in a\n        worker process using the Sanic server. It allows you to interact with\n        the worker processes, such as sending messages and commands.\n\n        See [Access to the multiplexer](/en/guide/deployment/manager#access-to-the-multiplexer) for more information.\n\n        Returns:\n            WorkerMultiplexer: The worker multiplexer instance\n\n        Examples:\n            ```python\n            app.m.restart()    # restarts the worker\n            app.m.terminate()  # terminates the worker\n            app.m.scale(4)     # scales the number of workers to 4\n        ```\n        \"\"\"  # noqa: E501\n        return self.multiplexer\n\n    def make_coffee(self, *args, **kwargs):\n        \"\"\"\n        Try for yourself! `sanic server:app --coffee`\n\n         ```\n         ▄████████▄\n        ██       ██▀▀▄\n        ███████████  █\n        ███████████▄▄▀\n         ▀███████▀\n\n         ```\n        \"\"\"\n        self.state.coffee = True\n        self.run(*args, **kwargs)\n\n    def run(\n        self,\n        host: str | None = None,\n        port: int | None = None,\n        *,\n        dev: bool = False,\n        debug: bool = False,\n        auto_reload: bool | None = None,\n        version: HTTPVersion = HTTP.VERSION_1,\n        ssl: None | SSLContext | dict | str | list | tuple = None,\n        sock: socket | None = None,\n        workers: int = 1,\n        protocol: type[Protocol] | None = None,\n        backlog: int = 100,\n        register_sys_signals: bool = True,\n        access_log: bool | None = None,\n        unix: str | None = None,\n        loop: AbstractEventLoop | None = None,\n        reload_dir: list[str] | str | None = None,\n        noisy_exceptions: bool | None = None,\n        motd: bool = True,\n        fast: bool = False,\n        verbosity: int = 0,\n        motd_display: dict[str, str] | None = None,\n        auto_tls: bool = False,\n        single_process: bool = False,\n    ) -> None:\n        \"\"\"Run the HTTP Server and listen until keyboard interrupt or term signal. On termination, drain connections before closing.\n\n        .. note::\n            When you need control over running the Sanic instance, this is the method to use.\n            However, in most cases the preferred method is to use the CLI command:\n\n            ```sh\n            sanic server:app`\n            ```\n\n        If you are using this method to run Sanic, make sure you do the following:\n\n        1. Use `if __name__ == \"__main__\"` to guard the code.\n        2. Do **NOT** define the app instance inside the `if` block.\n\n        See [Dynamic Applications](/en/guide/deployment/app-loader) for more information about the second point.\n\n        Args:\n            host (Optional[str]): Address to host on.\n            port (Optional[int]): Port to host on.\n            dev (bool): Run the server in development mode.\n            debug (bool): Enables debug output (slows server).\n            auto_reload (Optional[bool]): Reload app whenever its source code is changed.\n                Enabled by default in debug mode.\n            version (HTTPVersion): HTTP Version.\n            ssl (Union[None, SSLContext, dict, str, list, tuple]): SSLContext, or location of certificate and key\n                for SSL encryption of worker(s).\n            sock (Optional[socket]): Socket for the server to accept connections from.\n            workers (int): Number of processes received before it is respected.\n            protocol (Optional[Type[Protocol]]): Subclass of asyncio Protocol class.\n            backlog (int): A number of unaccepted connections that the system will allow\n                before refusing new connections.\n            register_sys_signals (bool): Register SIG* events.\n            access_log (Optional[bool]): Enables writing access logs (slows server).\n            unix (Optional[str]): Unix socket to listen on instead of TCP port.\n            loop (Optional[AbstractEventLoop]): AsyncIO event loop.\n            reload_dir (Optional[Union[List[str], str]]): Directory to watch for code changes, if auto_reload is True.\n            noisy_exceptions (Optional[bool]): Log exceptions that are normally considered to be quiet/silent.\n            motd (bool): Display Message of the Day.\n            fast (bool): Enable fast mode.\n            verbosity (int): Verbosity level.\n            motd_display (Optional[Dict[str, str]]): Customize Message of the Day display.\n            auto_tls (bool): Enable automatic TLS certificate handling.\n            single_process (bool): Enable single process mode.\n\n        Returns:\n            None\n\n        Raises:\n            RuntimeError: Raised when attempting to serve HTTP/3 as a secondary server.\n            RuntimeError: Raised when attempting to use both `fast` and `workers`.\n            RuntimeError: Raised when attempting to use `single_process` with `fast`, `workers`, or `auto_reload`.\n            TypeError: Raised when attempting to use `loop` with `create_server`.\n            ValueError: Raised when `PROXIES_COUNT` is negative.\n\n        Examples:\n            ```python\n            from sanic import Sanic, Request, json\n\n            app = Sanic(\"TestApp\")\n\n\n            @app.get(\"/\")\n            async def handler(request: Request):\n                return json({\"foo\": \"bar\"})\n\n\n            if __name__ == \"__main__\":\n                app.run(port=9999, dev=True)\n            ```\n        \"\"\"  # noqa: E501\n        self.prepare(\n            host=host,\n            port=port,\n            dev=dev,\n            debug=debug,\n            auto_reload=auto_reload,\n            version=version,\n            ssl=ssl,\n            sock=sock,\n            workers=workers,\n            protocol=protocol,\n            backlog=backlog,\n            register_sys_signals=register_sys_signals,\n            access_log=access_log,\n            unix=unix,\n            loop=loop,\n            reload_dir=reload_dir,\n            noisy_exceptions=noisy_exceptions,\n            motd=motd,\n            fast=fast,\n            verbosity=verbosity,\n            motd_display=motd_display,\n            auto_tls=auto_tls,\n            single_process=single_process,\n        )\n\n        if single_process:\n            serve = self.__class__.serve_single\n        else:\n            serve = self.__class__.serve\n        serve(primary=self)  # type: ignore\n\n    def prepare(\n        self,\n        host: str | None = None,\n        port: int | None = None,\n        *,\n        dev: bool = False,\n        debug: bool = False,\n        auto_reload: bool | None = None,\n        version: HTTPVersion = HTTP.VERSION_1,\n        ssl: None | SSLContext | dict | str | list | tuple = None,\n        sock: socket | None = None,\n        workers: int = 1,\n        protocol: type[Protocol] | None = None,\n        backlog: int = 100,\n        register_sys_signals: bool = True,\n        access_log: bool | None = None,\n        unix: str | None = None,\n        loop: AbstractEventLoop | None = None,\n        reload_dir: list[str] | str | None = None,\n        noisy_exceptions: bool | None = None,\n        motd: bool = True,\n        fast: bool = False,\n        verbosity: int = 0,\n        motd_display: dict[str, str] | None = None,\n        coffee: bool = False,\n        auto_tls: bool = False,\n        single_process: bool = False,\n    ) -> None:\n        \"\"\"Prepares one or more Sanic applications to be served simultaneously.\n\n        This low-level API is typically used when you need to run multiple Sanic applications at the same time. Once prepared, `Sanic.serve()` should be called in the `if __name__ == \"__main__\"` block.\n\n        .. note::\n            \"Preparing\" and \"serving\" with this function is equivalent to using `app.run` for a single instance. This should only be used when running multiple applications at the same time.\n\n        Args:\n            host (Optional[str], optional): Hostname to listen on. Defaults to `None`.\n            port (Optional[int], optional): Port to listen on. Defaults to `None`.\n            dev (bool, optional): Development mode. Defaults to `False`.\n            debug (bool, optional): Debug mode. Defaults to `False`.\n            auto_reload (Optional[bool], optional): Auto reload feature. Defaults to `None`.\n            version (HTTPVersion, optional): HTTP version to use. Defaults to `HTTP.VERSION_1`.\n            ssl (Union[None, SSLContext, dict, str, list, tuple], optional): SSL configuration. Defaults to `None`.\n            sock (Optional[socket], optional): Socket to bind to. Defaults to `None`.\n            workers (int, optional): Number of worker processes. Defaults to `1`.\n            protocol (Optional[Type[Protocol]], optional): Custom protocol class. Defaults to `None`.\n            backlog (int, optional): Maximum number of pending connections. Defaults to `100`.\n            register_sys_signals (bool, optional): Register system signals. Defaults to `True`.\n            access_log (Optional[bool], optional): Access log. Defaults to `None`.\n            unix (Optional[str], optional): Unix socket. Defaults to `None`.\n            loop (Optional[AbstractEventLoop], optional): Event loop. Defaults to `None`.\n            reload_dir (Optional[Union[List[str], str]], optional): Reload directory. Defaults to `None`.\n            noisy_exceptions (Optional[bool], optional): Display exceptions. Defaults to `None`.\n            motd (bool, optional): Display message of the day. Defaults to `True`.\n            fast (bool, optional): Fast mode. Defaults to `False`.\n            verbosity (int, optional): Verbosity level. Defaults to `0`.\n            motd_display (Optional[Dict[str, str]], optional): Custom MOTD display. Defaults to `None`.\n            coffee (bool, optional): Coffee mode. Defaults to `False`.\n            auto_tls (bool, optional): Auto TLS. Defaults to `False`.\n            single_process (bool, optional): Single process mode. Defaults to `False`.\n\n        Raises:\n            RuntimeError: Raised when attempting to serve HTTP/3 as a secondary server.\n            RuntimeError: Raised when attempting to use both `fast` and `workers`.\n            RuntimeError: Raised when attempting to use `single_process` with `fast`, `workers`, or `auto_reload`.\n            TypeError: Raised when attempting to use `loop` with `create_server`.\n            ValueError: Raised when `PROXIES_COUNT` is negative.\n\n        Examples:\n            ```python\n            if __name__ == \"__main__\":\n                app.prepare()\n                app.serve()\n            ```\n        \"\"\"  # noqa: E501\n        if version == 3 and self.state.server_info:\n            raise RuntimeError(\n                \"Serving HTTP/3 instances as a secondary server is \"\n                \"not supported. There can only be a single HTTP/3 worker \"\n                \"and it must be the first instance prepared.\"\n            )\n\n        if dev:\n            debug = True\n            auto_reload = True\n\n        if debug and access_log is None:\n            access_log = True\n\n        self.state.verbosity = verbosity\n        if not self.state.auto_reload:\n            self.state.auto_reload = bool(auto_reload)\n\n        if fast and workers != 1:\n            raise RuntimeError(\"You cannot use both fast=True and workers=X\")\n\n        if single_process and (fast or (workers > 1) or auto_reload):\n            raise RuntimeError(\n                \"Single process cannot be run with multiple workers \"\n                \"or auto-reload\"\n            )\n\n        if register_sys_signals is False and not single_process:\n            raise RuntimeError(\n                \"Cannot run Sanic.serve with register_sys_signals=False. \"\n                \"Use Sanic.serve_single.\"\n            )\n\n        if motd_display:\n            self.config.MOTD_DISPLAY.update(motd_display)\n\n        if reload_dir:\n            if isinstance(reload_dir, str):\n                reload_dir = [reload_dir]\n\n            for directory in reload_dir:\n                direc = Path(directory)\n                if not direc.is_dir():\n                    logger.warning(\n                        f\"Directory {directory} could not be located\"\n                    )\n                self.state.reload_dirs.add(Path(directory))\n\n        if loop is not None:\n            raise TypeError(\n                \"loop is not a valid argument. To use an existing loop, \"\n                \"change to create_server().\\nSee more: \"\n                \"https://sanic.readthedocs.io/en/latest/sanic/deploying.html\"\n                \"#asynchronous-support\"\n            )\n\n        if sock is None:\n            host, port = self.get_address(host, port, version, auto_tls)\n\n        if protocol is None:\n            protocol = (\n                WebSocketProtocol if self.websocket_enabled else HttpProtocol\n            )\n\n        # Set explicitly passed configuration values\n        for attribute, value in {\n            \"ACCESS_LOG\": access_log,\n            \"AUTO_RELOAD\": auto_reload,\n            \"MOTD\": motd,\n            \"NOISY_EXCEPTIONS\": noisy_exceptions,\n        }.items():\n            if value is not None:\n                setattr(self.config, attribute, value)\n\n        if fast:\n            self.state.fast = True\n            try:\n                workers = len(os.sched_getaffinity(0))\n            except AttributeError:  # no cov\n                workers = os.cpu_count() or 1\n\n        if coffee:\n            self.state.coffee = True\n\n        server_settings = self._helper(\n            host=host,\n            port=port,\n            debug=debug,\n            version=version,\n            ssl=ssl,\n            sock=sock,\n            unix=unix,\n            workers=workers,\n            protocol=protocol,\n            backlog=backlog,\n            register_sys_signals=register_sys_signals,\n            auto_tls=auto_tls,\n        )\n        self.state.server_info.append(\n            ApplicationServerInfo(settings=server_settings)\n        )\n\n        # if self.config.USE_UVLOOP is True or (\n        #     self.config.USE_UVLOOP is _default and not OS_IS_WINDOWS\n        # ):\n        #     try_use_uvloop()\n\n    async def create_server(\n        self,\n        host: str | None = None,\n        port: int | None = None,\n        *,\n        debug: bool = False,\n        ssl: None | SSLContext | dict | str | list | tuple = None,\n        sock: socket | None = None,\n        protocol: type[Protocol] | None = None,\n        backlog: int = 100,\n        access_log: bool | None = None,\n        unix: str | None = None,\n        return_asyncio_server: bool = True,\n        asyncio_server_kwargs: dict[str, Any] | None = None,\n        noisy_exceptions: bool | None = None,\n    ) -> AsyncioServer | None:\n        \"\"\"\n        Low level API for creating a Sanic Server instance.\n\n        This method will create a Sanic Server instance, but will not start\n        it. This is useful for integrating Sanic into other systems. But, you\n        should take caution when using it as it is a low level API and does\n        not perform any of the lifecycle events.\n\n        .. note::\n            This does not support multiprocessing and is not the preferred\n            way to run a Sanic application. Proceed with caution.\n\n        You will need to start the server yourself as shown in the example\n        below. You are responsible for the lifecycle of the server, including\n        app startup using `await app.startup()`. No events will be triggered\n        for you, so you will need to trigger them yourself if wanted.\n\n        Args:\n            host (Optional[str]): Address to host on.\n            port (Optional[int]): Port to host on.\n            debug (bool): Enables debug output (slows server).\n            ssl (Union[None, SSLContext, dict, str, list, tuple]): SSLContext,\n                or location of certificate and key for SSL encryption\n                of worker(s).\n            sock (Optional[socket]): Socket for the server to accept\n                connections from.\n            protocol (Optional[Type[Protocol]]): Subclass of\n                `asyncio.Protocol` class.\n            backlog (int): Number of unaccepted connections that the system\n                will allow before refusing new connections.\n            access_log (Optional[bool]): Enables writing access logs\n                (slows server).\n            return_asyncio_server (bool): _DEPRECATED_\n            asyncio_server_kwargs (Optional[Dict[str, Any]]): Key-value\n                arguments for asyncio/uvloop `create_server` method.\n            noisy_exceptions (Optional[bool]): Log exceptions that are normally\n                considered to be quiet/silent.\n\n        Returns:\n            Optional[AsyncioServer]: AsyncioServer if `return_asyncio_server`\n                is `True` else `None`.\n\n        Examples:\n            ```python\n            import asyncio\n            import uvloop\n            from sanic import Sanic, response\n\n\n            app = Sanic(\"Example\")\n\n\n            @app.route(\"/\")\n            async def test(request):\n                return response.json({\"answer\": \"42\"})\n\n\n            async def main():\n                server = await app.create_server()\n                await server.startup()\n                await server.serve_forever()\n\n\n            if __name__ == \"__main__\":\n                asyncio.set_event_loop(uvloop.new_event_loop())\n                asyncio.run(main())\n            ```\n        \"\"\"\n\n        if sock is None:\n            host, port = host, port = self.get_address(host, port)\n\n        if protocol is None:\n            protocol = (\n                WebSocketProtocol if self.websocket_enabled else HttpProtocol\n            )\n\n        # Set explicitly passed configuration values\n        for attribute, value in {\n            \"ACCESS_LOG\": access_log,\n            \"NOISY_EXCEPTIONS\": noisy_exceptions,\n        }.items():\n            if value is not None:\n                setattr(self.config, attribute, value)\n\n        if not return_asyncio_server:\n            return_asyncio_server = True\n            deprecation(\n                \"The `return_asyncio_server` argument is deprecated and \"\n                \"ignored. It will be removed in v24.3.\",\n                24.3,\n            )\n\n        server_settings = self._helper(\n            host=host,\n            port=port,\n            debug=debug,\n            ssl=ssl,\n            sock=sock,\n            unix=unix,\n            loop=get_event_loop(),\n            protocol=protocol,\n            backlog=backlog,\n            run_async=return_asyncio_server,\n        )\n\n        if not isinstance(self.config.USE_UVLOOP, Default):\n            error_logger.warning(\n                \"You are trying to change the uvloop configuration, but \"\n                \"this is only effective when using the run(...) method. \"\n                \"When using the create_server(...) method Sanic will use \"\n                \"the already existing loop.\"\n            )\n\n        main_start = server_settings.pop(\"main_start\", None)\n        main_stop = server_settings.pop(\"main_stop\", None)\n        if main_start or main_stop:\n            logger.warning(\n                \"Listener events for the main process are not available \"\n                \"with create_server()\"\n            )\n\n        return await serve(\n            asyncio_server_kwargs=asyncio_server_kwargs, **server_settings\n        )\n\n    def stop(self, terminate: bool = True, unregister: bool = False) -> None:\n        \"\"\"This kills the Sanic server, cleaning up after itself.\n\n        Args:\n            terminate (bool): Force kill all requests immediately without\n                allowing them to finish processing.\n            unregister (bool): Unregister the app from the global registry.\n\n        Returns:\n            None\n        \"\"\"\n        if terminate and hasattr(self, \"multiplexer\"):\n            self.multiplexer.terminate()\n        if self.state.stage is not ServerStage.STOPPED:\n            self.shutdown_tasks(timeout=0)  # type: ignore\n            for task in all_tasks():\n                with suppress(AttributeError):\n                    if task.get_name() == \"RunServer\":\n                        task.cancel()\n            get_event_loop().stop()\n\n        if unregister:\n            self.__class__.unregister_app(self)  # type: ignore\n\n    def _helper(\n        self,\n        host: str | None = None,\n        port: int | None = None,\n        debug: bool = False,\n        version: HTTPVersion = HTTP.VERSION_1,\n        ssl: None | SSLContext | dict | str | list | tuple = None,\n        sock: socket | None = None,\n        unix: str | None = None,\n        workers: int = 1,\n        loop: AbstractEventLoop | None = None,\n        protocol: type[Protocol] = HttpProtocol,\n        backlog: int = 100,\n        register_sys_signals: bool = True,\n        run_async: bool = False,\n        auto_tls: bool = False,\n    ) -> dict[str, Any]:\n        \"\"\"Helper function used by `run` and `create_server`.\"\"\"\n        if self.config.PROXIES_COUNT and self.config.PROXIES_COUNT < 0:\n            raise ValueError(\n                \"PROXIES_COUNT cannot be negative. \"\n                \"https://sanic.readthedocs.io/en/latest/sanic/config.html\"\n                \"#proxy-configuration\"\n            )\n\n        if not self.state.is_debug:\n            self.state.mode = Mode.DEBUG if debug else Mode.PRODUCTION\n\n        setup_logging(\n            self.state.is_debug, self.config.NO_COLOR, self.config.LOG_EXTRA\n        )\n\n        if isinstance(version, int):\n            version = HTTP(version)\n\n        ssl = process_to_context(ssl)\n        if version is HTTP.VERSION_3 or auto_tls:\n            if TYPE_CHECKING:\n                self = cast(Sanic, self)\n            ssl = get_ssl_context(self, ssl)\n\n        self.state.host = host or \"\"\n        self.state.port = port or 0\n        self.state.workers = workers\n        self.state.ssl = ssl\n        self.state.unix = unix\n        self.state.sock = sock\n\n        server_settings = {\n            \"protocol\": protocol,\n            \"host\": host,\n            \"port\": port,\n            \"version\": version,\n            \"sock\": sock,\n            \"unix\": unix,\n            \"ssl\": ssl,\n            \"app\": self,\n            \"signal\": ServerSignal(),\n            \"loop\": loop,\n            \"register_sys_signals\": register_sys_signals,\n            \"backlog\": backlog,\n        }\n\n        self.motd(server_settings=server_settings)\n\n        if (\n            is_atty()\n            and not self.state.is_debug\n            and not os.environ.get(\"SANIC_IGNORE_PRODUCTION_WARNING\")\n        ):\n            error_logger.warning(\n                f\"{Colors.YELLOW}Sanic is running in PRODUCTION mode. \"\n                \"Consider using '--debug' or '--dev' while actively \"\n                f\"developing your application.{Colors.END}\"\n            )\n\n        # Register start/stop events\n        for event_name, settings_name, reverse in (\n            (\"main_process_start\", \"main_start\", False),\n            (\"main_process_stop\", \"main_stop\", True),\n        ):\n            listeners = self.listeners[event_name].copy()\n            if reverse:\n                listeners.reverse()\n            # Prepend sanic to the arguments when listeners are triggered\n            listeners = [partial(listener, self) for listener in listeners]\n            server_settings[settings_name] = listeners  # type: ignore\n\n        if run_async:\n            server_settings[\"run_async\"] = True\n\n        return server_settings\n\n    def motd(\n        self,\n        server_settings: dict[str, Any] | None = None,\n    ) -> None:\n        \"\"\"Outputs the message of the day (MOTD).\n\n        It generally can only be called once per process, and is usually\n        called by the `run` method in the main process.\n\n        Args:\n            server_settings (Optional[Dict[str, Any]], optional): Settings for\n                the server. Defaults to `None`.\n\n        Returns:\n            None\n        \"\"\"\n        if (\n            os.environ.get(\"SANIC_WORKER_NAME\")\n            or os.environ.get(\"SANIC_MOTD_OUTPUT\")\n            or os.environ.get(\"SANIC_WORKER_PROCESS\")\n            or os.environ.get(\"SANIC_SERVER_RUNNING\")\n        ):\n            return\n        serve_location = self.get_server_location(server_settings)\n        if self.config.MOTD:\n            logo = get_logo(coffee=self.state.coffee)\n            display, extra = self.get_motd_data(server_settings)\n\n            MOTD.output(logo, serve_location, display, extra)\n\n    def get_motd_data(\n        self, server_settings: dict[str, Any] | None = None\n    ) -> tuple[dict[str, Any], dict[str, Any]]:\n        \"\"\"Retrieves the message of the day (MOTD) data.\n\n        Args:\n            server_settings (Optional[Dict[str, Any]], optional): Settings for\n                the server. Defaults to `None`.\n\n        Returns:\n            Tuple[Dict[str, Any], Dict[str, Any]]: A tuple containing two\n                dictionaries with the relevant MOTD data.\n        \"\"\"\n\n        mode = [f\"{self.state.mode},\"]\n        if self.state.fast:\n            mode.append(\"goin' fast\")\n        if self.state.asgi:\n            mode.append(\"ASGI\")\n        else:\n            if self.state.workers == 1:\n                mode.append(\"single worker\")\n            else:\n                mode.append(f\"w/ {self.state.workers} workers\")\n\n        if server_settings:\n            server = \", \".join(\n                (\n                    self.state.server,\n                    server_settings[\"version\"].display(),  # type: ignore\n                )\n            )\n        else:\n            server = \"ASGI\" if self.asgi else \"unknown\"  # type: ignore\n\n        display = {\n            \"app\": self.name,\n            \"mode\": \" \".join(mode),\n            \"server\": server,\n            \"python\": platform.python_version(),\n            \"platform\": platform.platform(),\n        }\n        extra = {}\n        if self.config.AUTO_RELOAD:\n            reload_display = \"enabled\"\n            if self.state.reload_dirs:\n                reload_display += \", \".join(\n                    [\n                        \"\",\n                        *(\n                            str(path.absolute())\n                            for path in self.state.reload_dirs\n                        ),\n                    ]\n                )\n            display[\"auto-reload\"] = reload_display\n\n        packages = []\n        for package_name in SANIC_PACKAGES:\n            module_name = package_name.replace(\"-\", \"_\")\n            try:\n                module = import_module(module_name)\n                packages.append(f\"{package_name}=={module.__version__}\")  # type: ignore\n            except ImportError:  # no cov\n                ...\n\n        if packages:\n            display[\"packages\"] = \", \".join(packages)\n\n        if self.config.MOTD_DISPLAY:\n            extra.update(self.config.MOTD_DISPLAY)\n\n        return display, extra\n\n    @property\n    def serve_location(self) -> str:\n        \"\"\"Retrieve the server location.\n\n        Returns:\n            str: The server location.\n        \"\"\"\n        try:\n            server_settings = self.state.server_info[0].settings\n            return self.get_server_location(server_settings)\n        except IndexError:\n            location = \"ASGI\" if self.asgi else \"unknown\"  # type: ignore\n            return f\"http://<{location}>\"\n\n    @staticmethod\n    def get_server_location(\n        server_settings: dict[str, Any] | None = None,\n    ) -> str:\n        \"\"\"Using the server settings, retrieve the server location.\n\n        Args:\n            server_settings (Optional[Dict[str, Any]], optional): Settings for\n                the server. Defaults to `None`.\n\n        Returns:\n            str: The server location.\n        \"\"\"\n        serve_location = \"\"\n        proto = \"http\"\n        if not server_settings:\n            return serve_location\n\n        host = server_settings[\"host\"]\n        port = server_settings[\"port\"]\n\n        if server_settings.get(\"ssl\") is not None:\n            proto = \"https\"\n        if server_settings.get(\"unix\"):\n            serve_location = f\"{server_settings['unix']} {proto}://...\"\n        elif server_settings.get(\"sock\"):\n            host, port, *_ = server_settings[\"sock\"].getsockname()\n\n        if not serve_location and host and port:\n            # colon(:) is legal for a host only in an ipv6 address\n            display_host = f\"[{host}]\" if \":\" in host else host\n            serve_location = f\"{proto}://{display_host}:{port}\"\n\n        return serve_location\n\n    @staticmethod\n    def get_address(\n        host: str | None,\n        port: int | None,\n        version: HTTPVersion = HTTP.VERSION_1,\n        auto_tls: bool = False,\n    ) -> tuple[str, int]:\n        \"\"\"Retrieve the host address and port, with default values based on the given parameters.\n\n        Args:\n            host (Optional[str]): Host IP or FQDN for the service to use. Defaults to `\"127.0.0.1\"`.\n            port (Optional[int]): Port number. Defaults to `8443` if version is 3 or `auto_tls=True`, else `8000`\n            version (HTTPVersion, optional): HTTP Version. Defaults to `HTTP.VERSION_1` (HTTP/1.1).\n            auto_tls (bool, optional): Automatic TLS flag. Defaults to `False`.\n\n        Returns:\n            Tuple[str, int]: Tuple containing the host and port\n        \"\"\"  # noqa: E501\n        host = host or \"127.0.0.1\"\n        port = port or (8443 if (version == 3 or auto_tls) else 8000)\n        return host, port\n\n    @classmethod\n    def should_auto_reload(cls) -> bool:\n        \"\"\"Check if any applications have auto-reload enabled.\n\n        Returns:\n            bool: `True` if any applications have auto-reload enabled, else\n                `False`.\n        \"\"\"\n        return any(app.state.auto_reload for app in cls._app_registry.values())\n\n    @classmethod\n    def _get_startup_method(cls) -> str:\n        return (\n            cls.start_method\n            if not isinstance(cls.start_method, Default)\n            else \"spawn\"\n        )\n\n    @classmethod\n    def _set_startup_method(cls) -> None:\n        if cls.START_METHOD_SET and not cls.test_mode:\n            return\n\n        method = cls._get_startup_method()\n        try:\n            set_start_method(method, force=cls.test_mode)\n        except RuntimeError:\n            ctx = get_context()\n            actual = ctx.get_start_method()\n            if actual != method:\n                raise RuntimeError(\n                    f\"Start method '{method}' was requested, but '{actual}' \"\n                    \"was already set.\\nFor more information, see: \"\n                    \"https://sanic.dev/en/guide/running/manager.html#overcoming-a-coderuntimeerrorcode\"\n                ) from None\n            else:\n                raise\n        cls.START_METHOD_SET = True\n\n    @classmethod\n    def _get_context(cls) -> BaseContext:\n        method = cls._get_startup_method()\n        logger.debug(\"Creating multiprocessing context using '%s'\", method)\n        actual = get_start_method()\n        if method != actual:\n            raise RuntimeError(\n                f\"Start method '{method}' was requested, but '{actual}' \"\n                \"was already set.\\nFor more information, see: \"\n                \"https://sanic.dev/en/guide/running/manager.html#overcoming-a-coderuntimeerrorcode\"\n            ) from None\n        return get_context()\n\n    @classmethod\n    def serve(\n        cls,\n        primary: Sanic | None = None,\n        *,\n        app_loader: AppLoader | None = None,\n        factory: Callable[[], Sanic] | None = None,\n    ) -> None:\n        \"\"\"Serve one or more Sanic applications.\n\n        This is the main entry point for running Sanic applications. It\n        should be called in the `if __name__ == \"__main__\"` block.\n\n        Args:\n            primary (Optional[Sanic], optional): The primary Sanic application\n                to serve. Defaults to `None`.\n            app_loader (Optional[AppLoader], optional): An AppLoader instance\n                to use for loading applications. Defaults to `None`.\n            factory (Optional[Callable[[], Sanic]], optional): A factory\n                function to use for loading applications. Defaults to `None`.\n\n        Raises:\n            RuntimeError: Raised when no applications are found.\n            RuntimeError: Raised when no server information is found for the\n                primary application.\n            RuntimeError: Raised when attempting to use `loop` with\n                `create_server`.\n            RuntimeError: Raised when attempting to use `single_process` with\n                `fast`, `workers`, or `auto_reload`.\n            RuntimeError: Raised when attempting to serve HTTP/3 as a\n                secondary server.\n            RuntimeError: Raised when attempting to use both `fast` and\n                `workers`.\n            TypeError: Raised when attempting to use `loop` with\n                `create_server`.\n            ValueError: Raised when `PROXIES_COUNT` is negative.\n\n        Examples:\n            ```python\n            if __name__ == \"__main__\":\n                app.prepare()\n                Sanic.serve()\n            ```\n        \"\"\"\n        cls._set_startup_method()\n        os.environ[\"SANIC_MOTD_OUTPUT\"] = \"true\"\n        apps = list(cls._app_registry.values())\n        if factory:\n            primary = factory()\n        else:\n            if not primary:\n                if app_loader:\n                    primary = app_loader.load()\n                if not primary:\n                    try:\n                        primary = apps[0]\n                    except IndexError:\n                        raise RuntimeError(\n                            \"Did not find any applications.\"\n                        ) from None\n\n            # This exists primarily for unit testing\n            if not primary.state.server_info:  # no cov\n                for app in apps:\n                    app.state.server_info.clear()\n                return\n\n        try:\n            primary_server_info = primary.state.server_info[0]\n        except IndexError:\n            raise RuntimeError(\n                f\"No server information found for {primary.name}. Perhaps you \"\n                \"need to run app.prepare(...)?\"\n            ) from None\n\n        socks = []\n        try:\n            sync_manager = Manager()\n        except EOFError:\n            message = (\n                \"Sanic server could not start: worker process failed.\\n\\n\"\n                \"This may have happened if you are running Sanic in the \"\n                \"global scope and not inside of a \"\n                '`if __name__ == \"__main__\"` block.\\n\\nSee more information: '\n                \"https://sanic.dev/en/guide/deployment/manager.html#\"\n                \"how-sanic-server-starts-processes\\n\"\n            )\n            if not OS_IS_WINDOWS:\n                message += (\n                    \"\\nAlternatively, you can set \"\n                    '`Sanic.start_method = \"fork\"` at the start of your '\n                    \"application to avoid this issue (Unix only).\\n\"\n                )\n            raise ServerError(message, quiet=True) from None\n        worker_state: Mapping[str, Any] = {\"state\": \"NONE\"}\n        setup_ext(primary)\n        exit_code = 0\n        workers_started = False\n        try:\n            primary_server_info.settings.pop(\"main_start\", None)\n            primary_server_info.settings.pop(\"main_stop\", None)\n            main_start = primary.listeners.get(\"main_process_start\")\n            main_stop = primary.listeners.get(\"main_process_stop\")\n            app = primary_server_info.settings.pop(\"app\")\n            app.setup_loop()\n            loop = new_event_loop()\n            trigger_events(main_start, loop, primary)\n\n            socks = [\n                sock\n                for sock in [\n                    configure_socket(server_info.settings)\n                    for app in apps\n                    for server_info in app.state.server_info\n                ]\n                if sock\n            ]\n            primary_server_info.settings[\"run_multiple\"] = True\n            monitor_sub, monitor_pub = Pipe(True)\n            worker_state = sync_manager.dict()\n            kwargs: dict[str, Any] = {\n                **primary_server_info.settings,\n                \"monitor_publisher\": monitor_pub,\n                \"worker_state\": worker_state,\n            }\n\n            if not app_loader:\n                if factory:\n                    app_loader = AppLoader(factory=factory)\n                else:\n                    app_loader = AppLoader(\n                        factory=partial(cls.get_app, app.name)  # type: ignore\n                    )\n            kwargs[\"app_name\"] = app.name\n            kwargs[\"app_loader\"] = app_loader\n            kwargs[\"server_info\"] = {}\n            kwargs[\"passthru\"] = {\n                \"auto_reload\": app.auto_reload,\n                \"state\": {\n                    \"verbosity\": app.state.verbosity,\n                    \"mode\": app.state.mode,\n                },\n                \"config\": {\n                    \"ACCESS_LOG\": app.config.ACCESS_LOG,\n                    \"NOISY_EXCEPTIONS\": app.config.NOISY_EXCEPTIONS,\n                },\n                \"shared_ctx\": app.shared_ctx.__dict__,\n            }\n            for app in apps:\n                kwargs[\"server_info\"][app.name] = []\n                for server_info in app.state.server_info:\n                    server_info.settings = {\n                        k: v\n                        for k, v in server_info.settings.items()\n                        if k not in (\"main_start\", \"main_stop\", \"app\", \"ssl\")\n                    }\n                    kwargs[\"server_info\"][app.name].append(server_info)\n\n            ssl = kwargs.get(\"ssl\")\n\n            if isinstance(ssl, SanicSSLContext):\n                kwargs[\"ssl\"] = ssl.sanic\n\n            manager = WorkerManager(\n                primary.state.workers,\n                worker_serve,\n                kwargs,\n                cls._get_context(),\n                (monitor_pub, monitor_sub),\n                worker_state,\n            )\n            if cls.should_auto_reload():\n                reload_dirs: set[Path] = primary.state.reload_dirs.union(\n                    *(app.state.reload_dirs for app in apps)\n                )\n                reloader = Reloader(monitor_pub, 0, reload_dirs, app_loader)\n                manager.manage(\"Reloader\", reloader, {}, transient=False)\n\n            inspector = None\n            if primary.config.INSPECTOR:\n                display, extra = primary.get_motd_data()\n                packages = [\n                    pkg.strip() for pkg in display[\"packages\"].split(\",\")\n                ]\n                module = import_module(\"sanic\")\n                sanic_version = f\"sanic=={module.__version__}\"  # type: ignore\n                app_info = {\n                    **display,\n                    \"packages\": [sanic_version, *packages],\n                    \"extra\": extra,\n                }\n                inspector = primary.inspector_class(\n                    monitor_pub,\n                    app_info,\n                    worker_state,\n                    primary.config.INSPECTOR_HOST,\n                    primary.config.INSPECTOR_PORT,\n                    primary.config.INSPECTOR_API_KEY,\n                    primary.config.INSPECTOR_TLS_KEY,\n                    primary.config.INSPECTOR_TLS_CERT,\n                )\n                manager.manage(\"Inspector\", inspector, {}, transient=False)\n\n            primary._inspector = inspector\n            primary._manager = manager\n\n            ready = primary.listeners[\"main_process_ready\"]\n            trigger_events(ready, loop, primary)\n\n            workers_started = True\n            manager.run()\n        except ServerKilled:\n            exit_code = 1\n        except BaseException:\n            kwargs = primary_server_info.settings\n            error_logger.error(\"Experienced exception while trying to serve\")\n            raise\n        finally:\n            logger.info(\"Server Stopped\")\n            for app in apps:\n                app.state.server_info.clear()\n                app.router.reset()\n                app.signal_router.reset()\n\n            for sock in socks:\n                try:\n                    sock.shutdown(SHUT_RDWR)\n                except OSError:\n                    ...\n                sock.close()\n            socks = []\n\n            trigger_events(main_stop, loop, primary)\n\n            loop.close()\n            cls._cleanup_env_vars()\n            cls._cleanup_apps()\n\n            if workers_started:\n                limit = 100\n                while cls._get_process_states(worker_state):\n                    sleep(0.1)\n                    limit -= 1\n                    if limit <= 0:\n                        error_logger.warning(\n                            \"Worker shutdown timed out. \"\n                            \"Some processes may still be running.\"\n                        )\n                        break\n            sync_manager.shutdown()\n            unix = kwargs.get(\"unix\")\n            if unix:\n                remove_unix_socket(unix)\n            logger.debug(get_goodbye())\n        if exit_code:\n            os._exit(exit_code)\n\n    @staticmethod\n    def _get_process_states(worker_state) -> list[str]:\n        try:\n            return [\n                state\n                for s in worker_state.values()\n                if (\n                    (state := s.get(\"state\"))\n                    and state\n                    not in (\"TERMINATED\", \"FAILED\", \"COMPLETED\", \"NONE\")\n                )\n            ]\n        except (BrokenPipeError, ConnectionResetError, EOFError):\n            return []\n\n    @classmethod\n    def serve_single(cls, primary: Sanic | None = None) -> None:\n        \"\"\"Serve a single process of a Sanic application.\n\n        Similar to `serve`, but only serves a single process. When used,\n        certain features are disabled, such as `fast`, `workers`,\n        `multiplexer`, `auto_reload`, and the Inspector. It is almost\n        never needed to use this method directly. Instead, you should\n        use the CLI:\n\n        ```sh\n        sanic app.sanic:app --single-process\n        ```\n\n        Or, if you need to do it programmatically, you should use the\n        `single_process` argument of `run`:\n\n        ```python\n        app.run(single_process=True)\n        ```\n\n        Args:\n            primary (Optional[Sanic], optional): The primary Sanic application\n                to serve. Defaults to `None`.\n\n        Raises:\n            RuntimeError: Raised when no applications are found.\n            RuntimeError: Raised when no server information is found for the\n                primary application.\n            RuntimeError: Raised when attempting to serve HTTP/3 as a\n                secondary server.\n            RuntimeError: Raised when attempting to use both `fast` and\n                `workers`.\n            ValueError: Raised when `PROXIES_COUNT` is negative.\n        \"\"\"\n        os.environ[\"SANIC_MOTD_OUTPUT\"] = \"true\"\n        apps = list(cls._app_registry.values())\n\n        if not primary:\n            try:\n                primary = apps[0]\n            except IndexError:\n                raise RuntimeError(\"Did not find any applications.\")\n\n        # This exists primarily for unit testing\n        if not primary.state.server_info:  # no cov\n            for app in apps:\n                app.state.server_info.clear()\n            return\n\n        primary_server_info = primary.state.server_info[0]\n        primary.before_server_start(partial(primary._start_servers, apps=apps))\n        kwargs = {\n            k: v\n            for k, v in primary_server_info.settings.items()\n            if k\n            not in (\n                \"main_start\",\n                \"main_stop\",\n                \"app\",\n            )\n        }\n        kwargs[\"app_name\"] = primary.name\n        kwargs[\"app_loader\"] = None\n        sock = configure_socket(kwargs)\n\n        kwargs[\"server_info\"] = {}\n        kwargs[\"server_info\"][primary.name] = []\n        for server_info in primary.state.server_info:\n            server_info.settings = {\n                k: v\n                for k, v in server_info.settings.items()\n                if k not in (\"main_start\", \"main_stop\", \"app\")\n            }\n            kwargs[\"server_info\"][primary.name].append(server_info)\n\n        try:\n            worker_serve(monitor_publisher=None, **kwargs)\n        except BaseException:\n            error_logger.exception(\n                \"Experienced exception while trying to serve\"\n            )\n            raise\n        finally:\n            logger.info(\"Server Stopped\")\n            for app in apps:\n                app.state.server_info.clear()\n                app.router.reset()\n                app.signal_router.reset()\n\n            if sock:\n                sock.close()\n\n            cls._cleanup_env_vars()\n            cls._cleanup_apps()\n\n    async def _start_servers(\n        self,\n        primary: Sanic,\n        apps: list[Sanic],\n    ) -> None:\n        for app in apps:\n            if (\n                app.name is not primary.name\n                and app.state.workers != primary.state.workers\n                and app.state.server_info\n            ):\n                message = (\n                    f\"The primary application {repr(primary)} is running \"\n                    f\"with {primary.state.workers} worker(s). All \"\n                    \"application instances will run with the same number. \"\n                    f\"You requested {repr(app)} to run with \"\n                    f\"{app.state.workers} worker(s), which will be ignored \"\n                    \"in favor of the primary application.\"\n                )\n                if is_atty():\n                    message = \"\".join(\n                        [\n                            Colors.YELLOW,\n                            message,\n                            Colors.END,\n                        ]\n                    )\n                error_logger.warning(message, exc_info=True)\n            for server_info in app.state.server_info:\n                if server_info.stage is not ServerStage.SERVING:\n                    app.state.primary = False\n                    handlers = [\n                        *server_info.settings.pop(\"main_start\", []),\n                        *server_info.settings.pop(\"main_stop\", []),\n                    ]\n                    if handlers:  # no cov\n                        error_logger.warning(\n                            f\"Sanic found {len(handlers)} listener(s) on \"\n                            \"secondary applications attached to the main \"\n                            \"process. These will be ignored since main \"\n                            \"process listeners can only be attached to your \"\n                            \"primary application: \"\n                            f\"{repr(primary)}\"\n                        )\n\n                    if not server_info.settings[\"loop\"]:\n                        server_info.settings[\"loop\"] = get_running_loop()\n\n                    serve_args: dict[str, Any] = {\n                        **server_info.settings,\n                        \"run_async\": True,\n                        \"reuse_port\": bool(primary.state.workers - 1),\n                    }\n                    if \"app\" not in serve_args:\n                        serve_args[\"app\"] = app\n                    try:\n                        server_info.server = await serve(**serve_args)\n                    except OSError as e:  # no cov\n                        first_message = (\n                            \"An OSError was detected on startup. \"\n                            \"The encountered error was: \"\n                        )\n                        second_message = str(e)\n                        if is_atty():\n                            message_parts = [\n                                Colors.YELLOW,\n                                first_message,\n                                Colors.RED,\n                                second_message,\n                                Colors.END,\n                            ]\n                        else:\n                            message_parts = [first_message, second_message]\n                        message = \"\".join(message_parts)\n                        error_logger.warning(message, exc_info=True)\n                        continue\n                    primary.add_task(\n                        self._run_server(app, server_info), name=\"RunServer\"\n                    )\n\n    async def _run_server(\n        self,\n        app: StartupMixin,\n        server_info: ApplicationServerInfo,\n    ) -> None:  # no cov\n        try:\n            # We should never get to this point without a server\n            # This is primarily to keep mypy happy\n            if not server_info.server:  # no cov\n                raise RuntimeError(\"Could not locate AsyncioServer\")\n            if app.state.stage is ServerStage.STOPPED:\n                server_info.stage = ServerStage.SERVING\n                await server_info.server.startup()\n                await server_info.server.before_start()\n                await server_info.server.after_start()\n            await server_info.server.serve_forever()\n        except CancelledError:\n            # We should never get to this point without a server\n            # This is primarily to keep mypy happy\n            if not server_info.server:  # no cov\n                raise RuntimeError(\"Could not locate AsyncioServer\")\n            await server_info.server.before_stop()\n            await server_info.server.close()\n            await server_info.server.after_stop()\n        finally:\n            server_info.stage = ServerStage.STOPPED\n            server_info.server = None\n\n    @staticmethod\n    def _cleanup_env_vars():\n        variables = (\n            \"SANIC_RELOADER_PROCESS\",\n            \"SANIC_IGNORE_PRODUCTION_WARNING\",\n            \"SANIC_WORKER_NAME\",\n            \"SANIC_MOTD_OUTPUT\",\n            \"SANIC_WORKER_PROCESS\",\n            \"SANIC_SERVER_RUNNING\",\n        )\n        for var in variables:\n            try:\n                del os.environ[var]\n            except KeyError:\n                ...\n\n    @classmethod\n    def _cleanup_apps(cls):\n        for app in cls._app_registry.values():\n            app.state.server_info.clear()\n            app.router.reset()\n            app.signal_router.reset()\n"
  },
  {
    "path": "sanic/mixins/static.py",
    "content": "from __future__ import annotations\n\nfrom collections.abc import Sequence\nfrom email.utils import formatdate\nfrom functools import partial, wraps\nfrom os import PathLike, path\nfrom pathlib import Path, PurePath\nfrom urllib.parse import unquote\n\nfrom sanic_routing.route import Route\n\nfrom sanic.base.meta import SanicMeta\nfrom sanic.compat import clear_function_annotate, stat_async\nfrom sanic.exceptions import FileNotFound, HeaderNotFound, RangeNotSatisfiable\nfrom sanic.handlers import ContentRangeHandler\nfrom sanic.handlers.directory import DirectoryHandler\nfrom sanic.log import error_logger\nfrom sanic.mixins.base import BaseMixin\nfrom sanic.models.futures import FutureStatic\nfrom sanic.request import Request\nfrom sanic.response import HTTPResponse, file, file_stream, validate_file\nfrom sanic.response.convenience import guess_content_type\n\n\nclass StaticMixin(BaseMixin, metaclass=SanicMeta):\n    def __init__(self, *args, **kwargs) -> None:\n        self._future_statics: set[FutureStatic] = set()\n\n    def _apply_static(self, static: FutureStatic) -> Route:\n        raise NotImplementedError  # noqa\n\n    def static(\n        self,\n        uri: str,\n        file_or_directory: PathLike | str,\n        pattern: str = r\"/?.+\",\n        use_modified_since: bool = True,\n        use_content_range: bool = False,\n        stream_large_files: bool | int = False,\n        name: str = \"static\",\n        host: str | None = None,\n        strict_slashes: bool | None = None,\n        content_type: str | None = None,\n        apply: bool = True,\n        resource_type: str | None = None,\n        index: str | Sequence[str] | None = None,\n        directory_view: bool = False,\n        directory_handler: DirectoryHandler | None = None,\n        follow_external_symlink_files: bool = False,\n        follow_external_symlink_dirs: bool = False,\n    ):\n        \"\"\"Register a root to serve files from. The input can either be a file or a directory.\n\n        This method provides an easy and simple way to set up the route necessary to serve static files.\n\n        Args:\n            uri (str): URL path to be used for serving static content.\n            file_or_directory (Union[PathLike, str]): Path to the static file\n                or directory with static files.\n            pattern (str, optional): Regex pattern identifying the valid\n                static files. Defaults to `r\"/?.+\"`.\n            use_modified_since (bool, optional): If true, send file modified\n                time, and return not modified if the browser's matches the\n                server's. Defaults to `True`.\n            use_content_range (bool, optional): If true, process header for\n                range requests and sends  the file part that is requested.\n                Defaults to `False`.\n            stream_large_files (Union[bool, int], optional): If `True`, use\n                the `StreamingHTTPResponse.file_stream` handler rather than\n                the `HTTPResponse.file handler` to send the file. If this\n                is an integer, it represents the threshold size to switch\n                to `StreamingHTTPResponse.file_stream`. Defaults to `False`,\n                which means that the response will not be streamed.\n            name (str, optional): User-defined name used for url_for.\n                Defaults to `\"static\"`.\n            host (Optional[str], optional): Host IP or FQDN for the\n                service to use.\n            strict_slashes (Optional[bool], optional): Instruct Sanic to\n                check if the request URLs need to terminate with a slash.\n            content_type (Optional[str], optional): User-defined content type\n                for header.\n            apply (bool, optional): If true, will register the route\n                immediately. Defaults to `True`.\n            resource_type (Optional[str], optional): Explicitly declare a\n                resource to be a `\"file\"` or a `\"dir\"`.\n            index (Optional[Union[str, Sequence[str]]], optional): When\n                exposing against a directory, index is  the name that will\n                be served as the default file. When multiple file names are\n                passed, then they will be tried in order.\n            directory_view (bool, optional): Whether to fallback to showing\n                the directory viewer when exposing a directory. Defaults\n                to `False`.\n            directory_handler (Optional[DirectoryHandler], optional): An\n                instance of DirectoryHandler that can be used for explicitly\n                controlling and subclassing the behavior of the default\n                directory handler.\n            follow_external_symlink_files (bool, optional): Whether to serve\n                files that are symlinks pointing outside the static root.\n                Defaults to `False` for security.\n            follow_external_symlink_dirs (bool, optional): Whether to serve\n                files from directories that are symlinks pointing outside\n                the static root. Defaults to `False` for security.\n\n        Returns:\n            List[sanic.router.Route]: Routes registered on the router.\n\n        Examples:\n            Serving a single file:\n            ```python\n            app.static('/foo', 'path/to/static/file.txt')\n            ```\n\n            Serving all files from a directory:\n            ```python\n            app.static('/static', 'path/to/static/directory')\n            ```\n\n            Serving large files with a specific threshold:\n            ```python\n            app.static('/static', 'path/to/large/files', stream_large_files=1000000)\n            ```\n        \"\"\"  # noqa: E501\n\n        name = self.generate_name(name)\n\n        if strict_slashes is None and self.strict_slashes is not None:\n            strict_slashes = self.strict_slashes\n\n        if not isinstance(file_or_directory, (str, bytes, PurePath)):\n            raise ValueError(\n                f\"Static route must be a valid path, not {file_or_directory}\"\n            )\n\n        try:\n            file_or_directory = Path(file_or_directory).resolve()\n        except TypeError:\n            raise TypeError(\n                \"Static file or directory must be a path-like object or string\"\n            )\n\n        if directory_handler and (directory_view or index):\n            raise ValueError(\n                \"When explicitly setting directory_handler, you cannot \"\n                \"set either directory_view or index. Instead, pass \"\n                \"these arguments to your DirectoryHandler instance.\"\n            )\n\n        if not directory_handler:\n            directory_handler = DirectoryHandler(\n                uri=uri,\n                directory=file_or_directory,\n                directory_view=directory_view,\n                index=index,\n                root_path=file_or_directory,\n                follow_external_symlink_files=follow_external_symlink_files,\n                follow_external_symlink_dirs=follow_external_symlink_dirs,\n            )\n\n        static = FutureStatic(\n            uri,\n            file_or_directory,\n            pattern,\n            use_modified_since,\n            use_content_range,\n            stream_large_files,\n            name,\n            host,\n            strict_slashes,\n            content_type,\n            resource_type,\n            directory_handler,\n            follow_external_symlink_files,\n            follow_external_symlink_dirs,\n        )\n        self._future_statics.add(static)\n\n        if apply:\n            self._apply_static(static)\n\n\nclass StaticHandleMixin(metaclass=SanicMeta):\n    def _apply_static(self, static: FutureStatic) -> Route:\n        return self._register_static(static)\n\n    def _register_static(\n        self,\n        static: FutureStatic,\n    ):\n        # TODO: Though sanic is not a file server, I feel like we should\n        # at least make a good effort here.  Modified-since is nice, but\n        # we could also look into etags, expires, and caching\n        \"\"\"\n        Register a static directory handler with Sanic by adding a route to the\n        router and registering a handler.\n        \"\"\"\n        file_or_directory: PathLike\n\n        if isinstance(static.file_or_directory, bytes):\n            file_or_directory = Path(static.file_or_directory.decode(\"utf-8\"))\n        elif isinstance(static.file_or_directory, PurePath):\n            file_or_directory = static.file_or_directory\n        elif isinstance(static.file_or_directory, str):\n            file_or_directory = Path(static.file_or_directory)\n        else:\n            raise ValueError(\"Invalid file path string.\")\n\n        uri = static.uri\n        name = static.name\n        # If we're not trying to match a file directly,\n        # serve from the folder\n        if not static.resource_type:\n            if not path.isfile(file_or_directory):\n                uri = uri.rstrip(\"/\")\n                uri += \"/<__file_uri__:path>\"\n        elif static.resource_type == \"dir\":\n            if path.isfile(file_or_directory):\n                raise TypeError(\n                    \"Resource type improperly identified as directory. \"\n                    f\"'{file_or_directory}'\"\n                )\n            uri = uri.rstrip(\"/\")\n            uri += \"/<__file_uri__:path>\"\n        elif static.resource_type == \"file\" and not path.isfile(\n            file_or_directory\n        ):\n            raise TypeError(\n                \"Resource type improperly identified as file. \"\n                f\"'{file_or_directory}'\"\n            )\n        elif static.resource_type != \"file\":\n            raise ValueError(\n                \"The resource_type should be set to 'file' or 'dir'\"\n            )\n\n        # special prefix for static files\n        # if not static.name.startswith(\"_static_\"):\n        #     name = f\"_static_{static.name}\"\n\n        _handler = wraps(self._static_request_handler)(\n            partial(\n                self._static_request_handler,\n                file_or_directory=str(file_or_directory),\n                use_modified_since=static.use_modified_since,\n                use_content_range=static.use_content_range,\n                stream_large_files=static.stream_large_files,\n                content_type=static.content_type,\n                directory_handler=static.directory_handler,\n                follow_external_symlink_files=static.follow_external_symlink_files,\n                follow_external_symlink_dirs=static.follow_external_symlink_dirs,\n            )\n        )\n\n        route, _ = self.route(  # type: ignore\n            uri=uri,\n            methods=[\"GET\", \"HEAD\"],\n            name=name,\n            host=static.host,\n            strict_slashes=static.strict_slashes,\n            static=True,\n        )(_handler)\n\n        return route\n\n    async def _static_request_handler(\n        self,\n        request: Request,\n        *,\n        file_or_directory: str,\n        use_modified_since: bool,\n        use_content_range: bool,\n        stream_large_files: bool | int,\n        directory_handler: DirectoryHandler,\n        follow_external_symlink_files: bool,\n        follow_external_symlink_dirs: bool,\n        content_type: str | None = None,\n        __file_uri__: str | None = None,\n    ):\n        not_found = FileNotFound(\n            \"File not found\",\n            path=Path(file_or_directory),\n            relative_url=__file_uri__,\n        )\n\n        # Merge served directory and requested file if provided\n        file_path = await self._get_file_path(\n            file_or_directory,\n            __file_uri__,\n            not_found,\n            follow_external_symlink_files,\n            follow_external_symlink_dirs,\n        )\n\n        try:\n            headers = {}\n            # Check if the client has been sent this file before\n            # and it has not been modified since\n            stats = None\n            if use_modified_since:\n                stats = await stat_async(file_path)\n                modified_since = stats.st_mtime\n                response = await validate_file(request.headers, modified_since)\n                if response:\n                    return response\n                headers[\"Last-Modified\"] = formatdate(\n                    modified_since, usegmt=True\n                )\n            _range = None\n            if use_content_range:\n                _range = None\n                if not stats:\n                    stats = await stat_async(file_path)\n                headers[\"Accept-Ranges\"] = \"bytes\"\n                headers[\"Content-Length\"] = str(stats.st_size)\n                if request.method != \"HEAD\":\n                    try:\n                        _range = ContentRangeHandler(request, stats)\n                    except HeaderNotFound:\n                        pass\n                    else:\n                        del headers[\"Content-Length\"]\n                        headers.update(_range.headers)\n\n            if \"content-type\" not in headers:\n                content_type = content_type or guess_content_type(file_path)\n\n                if \"charset=\" not in content_type and (\n                    content_type.startswith(\"text/\")\n                    or content_type == \"application/javascript\"\n                ):\n                    content_type += \"; charset=utf-8\"\n\n                headers[\"Content-Type\"] = content_type\n\n            if request.method == \"HEAD\":\n                return HTTPResponse(headers=headers)\n            else:\n                if stream_large_files:\n                    if isinstance(stream_large_files, bool):\n                        threshold = 1024 * 1024\n                    else:\n                        threshold = stream_large_files\n\n                    if not stats:\n                        stats = await stat_async(file_path)\n                    if stats.st_size >= threshold:\n                        return await file_stream(\n                            file_path, headers=headers, _range=_range\n                        )\n                return await file(file_path, headers=headers, _range=_range)\n        except (IsADirectoryError, PermissionError):\n            return await directory_handler.handle(request, request.path)\n        except RangeNotSatisfiable:\n            raise\n        except FileNotFoundError:\n            raise not_found\n        except Exception:\n            error_logger.exception(\n                \"Exception in static request handler: \"\n                f\"path={file_or_directory}, \"\n                f\"relative_url={__file_uri__}\"\n            )\n            raise\n\n    async def _get_file_path(\n        self,\n        file_or_directory,\n        __file_uri__,\n        not_found,\n        follow_external_symlink_files: bool,\n        follow_external_symlink_dirs: bool,\n    ):\n        \"\"\"\n        Resolve a filesystem path safely.\n\n        Security goals:\n        - Prevent path traversal via `..`\n        - Prevent escaping the root via symlinks unless explicitly allowed\n        - Treat file URIs as relative paths even if they look absolute\n        \"\"\"\n\n        def reject():\n            error_logger.exception(\n                f\"File not found: path={file_or_directory}, \"\n                f\"relative_url={__file_uri__}\"\n            )\n            raise not_found\n\n        root_raw = Path(unquote(file_or_directory))\n        root_path = root_raw.resolve()\n        file_path_raw = root_raw\n\n        if __file_uri__:\n            # URLs may start with `/`, Path() interprets as absolute\n            rel_uri = unquote(__file_uri__).lstrip(\"/\")\n            file_path_raw = Path(root_raw, rel_uri)\n\n            if \"..\" in file_path_raw.parts:\n                reject()\n\n        file_path = file_path_raw.resolve()\n\n        try:\n            file_path.relative_to(root_path)\n        except ValueError:\n            # Check if it's a symlink and determine its type\n            is_file_symlink = (\n                file_path_raw.is_symlink() and not file_path.is_dir()\n            )\n            if is_file_symlink:\n                allowed = follow_external_symlink_files\n            else:\n                allowed = follow_external_symlink_dirs\n            if not allowed:\n                reject()\n\n        return file_path\n\n\n# Clear __annotate__ on methods that may be pickled via functools.partial\n# to avoid PicklingError in Python 3.14+ (PEP 649)\nclear_function_annotate(\n    StaticHandleMixin._static_request_handler,\n    StaticHandleMixin._get_file_path,\n    StaticHandleMixin._register_static,\n)\n"
  },
  {
    "path": "sanic/models/__init__.py",
    "content": ""
  },
  {
    "path": "sanic/models/asgi.py",
    "content": "import asyncio\n\nfrom collections.abc import Awaitable, MutableMapping\nfrom typing import Any, Callable\n\nfrom sanic.exceptions import BadRequest\nfrom sanic.models.protocol_types import TransportProtocol\nfrom sanic.server.websockets.connection import WebSocketConnection\n\n\nASGIScope = MutableMapping[str, Any]\nASGIMessage = MutableMapping[str, Any]\nASGISend = Callable[[ASGIMessage], Awaitable[None]]\nASGIReceive = Callable[[], Awaitable[ASGIMessage]]\n\n\nclass MockProtocol:  # no cov\n    def __init__(self, transport: \"MockTransport\", loop):\n        self.transport = transport\n        self._not_paused = asyncio.Event()\n        self._not_paused.set()\n        self._complete = asyncio.Event()\n\n    def pause_writing(self) -> None:\n        self._not_paused.clear()\n\n    def resume_writing(self) -> None:\n        self._not_paused.set()\n\n    async def complete(self) -> None:\n        self._not_paused.set()\n        await self.transport.send(\n            {\"type\": \"http.response.body\", \"body\": b\"\", \"more_body\": False}\n        )\n\n    @property\n    def is_complete(self) -> bool:\n        return self._complete.is_set()\n\n    async def push_data(self, data: bytes) -> None:\n        if not self.is_complete:\n            await self.transport.send(\n                {\"type\": \"http.response.body\", \"body\": data, \"more_body\": True}\n            )\n\n    async def drain(self) -> None:\n        await self._not_paused.wait()\n\n\nclass MockTransport(TransportProtocol):  # no cov\n    _protocol: MockProtocol | None\n\n    def __init__(\n        self, scope: ASGIScope, receive: ASGIReceive, send: ASGISend\n    ) -> None:\n        self.scope = scope\n        self._receive = receive\n        self._send = send\n        self._protocol = None\n        self.loop: asyncio.AbstractEventLoop | None = None\n\n    def get_protocol(self) -> MockProtocol:  # type: ignore\n        if not self._protocol:\n            self._protocol = MockProtocol(self, self.loop)\n        return self._protocol\n\n    def get_extra_info(self, info: str, default=None) -> str | bool | None:\n        if info == \"peername\":\n            return self.scope.get(\"client\")\n        elif info == \"sslcontext\":\n            return self.scope.get(\"scheme\") in [\"https\", \"wss\"]\n        return default\n\n    def get_websocket_connection(self) -> WebSocketConnection:\n        try:\n            return self._websocket_connection\n        except AttributeError:\n            raise BadRequest(\"Improper websocket connection.\")\n\n    def create_websocket_connection(\n        self, send: ASGISend, receive: ASGIReceive\n    ) -> WebSocketConnection:\n        self._websocket_connection = WebSocketConnection(\n            send, receive, self.scope.get(\"subprotocols\", [])\n        )\n        return self._websocket_connection\n\n    def add_task(self) -> None:\n        raise NotImplementedError\n\n    async def send(self, data) -> None:\n        # TODO:\n        # - Validation on data and that it is formatted properly and is valid\n        await self._send(data)\n\n    async def receive(self) -> ASGIMessage:\n        return await self._receive()\n"
  },
  {
    "path": "sanic/models/ctx_types.py",
    "content": "from typing import Any, NamedTuple\n\n\nclass REPLLocal(NamedTuple):\n    var: Any\n    name: str\n    desc: str\n\n\nclass REPLContext:\n    BUILTINS = {\n        \"app\": \"The Sanic application instance\",\n        \"sanic\": \"The Sanic module\",\n        \"do\": \"An async function to fake a request to the application\",\n        \"client\": \"A client to access the Sanic app instance using httpx\",\n    }\n\n    def __init__(self):\n        self._locals: set[REPLLocal] = set()\n\n    def add(\n        self,\n        var: Any,\n        name: str | None = None,\n        desc: str | None = None,\n    ):\n        \"\"\"Add a local variable to be available in REPL context.\n\n        Args:\n            var (Any): A module, class, object or a class.\n            name (Optional[str], optional): An alias for the local. Defaults to None.\n            desc (Optional[str], optional): A brief description for the local. Defaults to None.\n        \"\"\"  # noqa: E501\n        if name is None:\n            try:\n                name = var.__name__\n            except AttributeError:\n                name = var.__class__.__name__\n\n        if desc is None:\n            try:\n                desc = var.__doc__ or \"\"\n            except AttributeError:\n                desc = str(type(var))\n\n        assert isinstance(desc, str) and isinstance(\n            name, str\n        )  # Just to make mypy happy\n\n        if name in self.BUILTINS:\n            raise ValueError(f\"Cannot override built-in variable: {name}\")\n\n        desc = self._truncate(desc)\n\n        self._locals.add(REPLLocal(var, name, desc))\n\n    def __setattr__(self, name: str, value: Any):\n        if name.startswith(\"_\"):\n            super().__setattr__(name, value)\n        else:\n            self.add(value, name)\n\n    def __iter__(self):\n        return iter(self._locals)\n\n    @staticmethod\n    def _truncate(s: str, limit: int = 40) -> str:\n        s = s.replace(\"\\n\", \" \")\n        return s[:limit] + \"...\" if len(s) > limit else s\n"
  },
  {
    "path": "sanic/models/futures.py",
    "content": "from collections.abc import Iterable\nfrom pathlib import Path\nfrom typing import Callable, NamedTuple\n\nfrom sanic.handlers.directory import DirectoryHandler\nfrom sanic.models.handler_types import (\n    ErrorMiddlewareType,\n    ListenerType,\n    MiddlewareType,\n    SignalHandler,\n)\nfrom sanic.types import HashableDict\n\n\nclass FutureRoute(NamedTuple):\n    handler: str\n    uri: str\n    methods: Iterable[str] | None\n    host: str | list[str]\n    strict_slashes: bool\n    stream: bool\n    version: int | None\n    name: str\n    ignore_body: bool\n    websocket: bool\n    subprotocols: list[str] | None\n    unquote: bool\n    static: bool\n    version_prefix: str\n    error_format: str | None\n    route_context: HashableDict\n\n\nclass FutureListener(NamedTuple):\n    listener: ListenerType\n    event: str\n    priority: int\n\n\nclass FutureMiddleware(NamedTuple):\n    middleware: MiddlewareType\n    attach_to: str\n\n\nclass FutureException(NamedTuple):\n    handler: ErrorMiddlewareType\n    exceptions: list[BaseException]\n\n\nclass FutureStatic(NamedTuple):\n    uri: str\n    file_or_directory: Path\n    pattern: str\n    use_modified_since: bool\n    use_content_range: bool\n    stream_large_files: bool | int\n    name: str\n    host: str | None\n    strict_slashes: bool | None\n    content_type: str | None\n    resource_type: str | None\n    directory_handler: DirectoryHandler\n    follow_external_symlink_files: bool\n    follow_external_symlink_dirs: bool\n\n\nclass FutureSignal(NamedTuple):\n    handler: SignalHandler\n    event: str\n    condition: dict[str, str] | None\n    exclusive: bool\n    priority: int\n\n\nclass FutureRegistry(set): ...\n\n\nclass FutureCommand(NamedTuple):\n    name: str\n    func: Callable\n"
  },
  {
    "path": "sanic/models/handler_types.py",
    "content": "from asyncio.events import AbstractEventLoop\nfrom collections.abc import Coroutine\nfrom typing import Any, Callable, TypeVar\n\nimport sanic\n\nfrom sanic import request\nfrom sanic.response import BaseHTTPResponse, HTTPResponse\n\n\nSanic = TypeVar(\"Sanic\", bound=\"sanic.Sanic\")\nRequest = TypeVar(\"Request\", bound=\"request.Request\")\n\nMiddlewareResponse = (\n    HTTPResponse | None | Coroutine[Any, Any, HTTPResponse | None]\n)\nRequestMiddlewareType = Callable[[Request], MiddlewareResponse]\nResponseMiddlewareType = Callable[\n    [Request, BaseHTTPResponse], MiddlewareResponse\n]\nErrorMiddlewareType = Callable[\n    [Request, BaseException], Coroutine[Any, Any, None] | None\n]\nMiddlewareType = RequestMiddlewareType | ResponseMiddlewareType\nListenerType = (\n    Callable[[Sanic], Coroutine[Any, Any, None] | None]\n    | Callable[[Sanic, AbstractEventLoop], Coroutine[Any, Any, None] | None]\n)\nRouteHandler = Callable[..., Coroutine[Any, Any, HTTPResponse | None]]\nSignalHandler = Callable[..., Coroutine[Any, Any, None]]\n"
  },
  {
    "path": "sanic/models/http_types.py",
    "content": "from __future__ import annotations\n\nfrom base64 import b64decode\nfrom dataclasses import dataclass, field\n\n\n@dataclass()\nclass Credentials:\n    auth_type: str | None\n    token: str | None\n    _username: str | None = field(default=None)\n    _password: str | None = field(default=None)\n\n    def __post_init__(self):\n        if self._auth_is_basic:\n            self._username, self._password = (\n                b64decode(self.token.encode(\"utf-8\")).decode().split(\":\")\n            )\n\n    @property\n    def username(self):\n        if not self._auth_is_basic:\n            raise AttributeError(\"Username is available for Basic Auth only\")\n        return self._username\n\n    @property\n    def password(self):\n        if not self._auth_is_basic:\n            raise AttributeError(\"Password is available for Basic Auth only\")\n        return self._password\n\n    @property\n    def _auth_is_basic(self) -> bool:\n        return self.auth_type == \"Basic\"\n"
  },
  {
    "path": "sanic/models/protocol_types.py",
    "content": "from __future__ import annotations\n\nfrom asyncio import BaseTransport\nfrom typing import TYPE_CHECKING, Protocol\n\n\nif TYPE_CHECKING:\n    from sanic.http.constants import HTTP\n    from sanic.models.asgi import ASGIScope\n\n\nclass HTMLProtocol(Protocol):\n    def __html__(self) -> str | bytes: ...\n\n    def _repr_html_(self) -> str | bytes: ...\n\n\nclass Range(Protocol):\n    start: int | None\n    end: int | None\n    size: int | None\n    total: int | None\n    __slots__ = ()\n\n\nclass TransportProtocol(BaseTransport):\n    scope: ASGIScope\n    version: HTTP\n    __slots__ = ()\n"
  },
  {
    "path": "sanic/models/server_types.py",
    "content": "from __future__ import annotations\n\nfrom ssl import SSLContext, SSLObject\nfrom types import SimpleNamespace\nfrom typing import Any\n\nfrom sanic.models.protocol_types import TransportProtocol\n\n\nclass Signal:\n    stopped = False\n\n\nclass ConnInfo:\n    \"\"\"\n    Local and remote addresses and SSL status info.\n    \"\"\"\n\n    __slots__ = (\n        \"client_port\",\n        \"client\",\n        \"client_ip\",\n        \"ctx\",\n        \"lost\",\n        \"peername\",\n        \"server_port\",\n        \"server\",\n        \"server_name\",\n        \"sockname\",\n        \"ssl\",\n        \"cert\",\n        \"network_paths\",\n    )\n\n    def __init__(self, transport: TransportProtocol, unix=None):\n        self.ctx = SimpleNamespace()\n        self.lost = False\n        self.peername: tuple[str, int] | None = None\n        self.server = self.client = \"\"\n        self.server_port = self.client_port = 0\n        self.client_ip = \"\"\n        self.sockname = addr = transport.get_extra_info(\"sockname\")\n        self.ssl = False\n        self.server_name = \"\"\n        self.cert: dict[str, Any] = {}\n        self.network_paths: list[Any] = []\n        sslobj: SSLObject | None = transport.get_extra_info(\"ssl_object\")  # type: ignore\n        sslctx: SSLContext | None = transport.get_extra_info(\"ssl_context\")  # type: ignore\n        if sslobj:\n            self.ssl = True\n            self.server_name = getattr(sslobj, \"sanic_server_name\", None) or \"\"\n            self.cert = dict(getattr(sslobj.context, \"sanic\", {}))\n        if sslctx and not self.cert:\n            self.cert = dict(getattr(sslctx, \"sanic\", {}))\n        if isinstance(addr, str):  # UNIX socket\n            self.server = unix or addr\n            return\n        # IPv4 (ip, port) or IPv6 (ip, port, flowinfo, scopeid)\n        if isinstance(addr, tuple):\n            self.server = addr[0] if len(addr) == 2 else f\"[{addr[0]}]\"\n            self.server_port = addr[1]\n            # self.server gets non-standard port appended\n            if addr[1] != (443 if self.ssl else 80):\n                self.server = f\"{self.server}:{addr[1]}\"\n        self.peername = addr = transport.get_extra_info(\"peername\")\n        self.network_paths = transport.get_extra_info(\"network_paths\")  # type: ignore\n\n        if isinstance(addr, tuple):\n            self.client = addr[0] if len(addr) == 2 else f\"[{addr[0]}]\"\n            self.client_ip = addr[0]\n            self.client_port = addr[1]\n"
  },
  {
    "path": "sanic/pages/__init__.py",
    "content": ""
  },
  {
    "path": "sanic/pages/base.py",
    "content": "from abc import ABC, abstractmethod\n\nfrom html5tagger import HTML, Builder, Document\n\nfrom sanic import __version__ as VERSION\nfrom sanic.application.logo import SVG_LOGO_SIMPLE\nfrom sanic.pages.css import CSS\n\n\nclass BasePage(ABC, metaclass=CSS):  # no cov\n    \"\"\"Base page for Sanic pages.\"\"\"\n\n    TITLE = \"Sanic\"\n    HEADING = None\n    CSS: str\n    doc: Builder\n\n    def __init__(self, debug: bool = True) -> None:\n        self.debug = debug\n\n    @property\n    def style(self) -> str:\n        \"\"\"Returns the CSS for the page.\n\n        Returns:\n            str: The CSS for the page.\n        \"\"\"\n        return self.CSS\n\n    def render(self) -> str:\n        \"\"\"Renders the page.\n\n        Returns:\n            str: The rendered page.\n        \"\"\"\n        self.doc = Document(self.TITLE, lang=\"en\", id=\"sanic\")\n        self._head()\n        self._body()\n        self._foot()\n        return str(self.doc)\n\n    def _head(self) -> None:\n        self.doc.style(HTML(self.style))\n        with self.doc.header:\n            self.doc.div(self.HEADING or self.TITLE)\n\n    def _foot(self) -> None:\n        with self.doc.footer:\n            self.doc.div(\"powered by\")\n            with self.doc.div:\n                self._sanic_logo()\n            if self.debug:\n                self.doc.div(f\"Version {VERSION}\")\n                with self.doc.div:\n                    for idx, (title, href) in enumerate(\n                        (\n                            (\"Docs\", \"https://sanic.dev\"),\n                            (\"Help\", \"https://sanic.dev/en/help.html\"),\n                            (\"GitHub\", \"https://github.com/sanic-org/sanic\"),\n                        )\n                    ):\n                        if idx > 0:\n                            self.doc(\" | \")\n                        self.doc.a(\n                            title,\n                            href=href,\n                            target=\"_blank\",\n                            referrerpolicy=\"no-referrer\",\n                        )\n                self.doc.div(\"DEBUG mode\")\n\n    @abstractmethod\n    def _body(self) -> None: ...\n\n    def _sanic_logo(self) -> None:\n        self.doc.a(\n            HTML(SVG_LOGO_SIMPLE),\n            href=\"https://sanic.dev\",\n            target=\"_blank\",\n            referrerpolicy=\"no-referrer\",\n        )\n"
  },
  {
    "path": "sanic/pages/css.py",
    "content": "from abc import ABCMeta\nfrom pathlib import Path\n\n\nCURRENT_DIR = Path(__file__).parent\n\n\ndef _extract_style(maybe_style: str | None, name: str) -> str:\n    if maybe_style is not None:\n        maybe_path = Path(maybe_style)\n        if maybe_path.exists():\n            return maybe_path.read_text(encoding=\"UTF-8\")\n        return maybe_style\n    maybe_path = CURRENT_DIR / \"styles\" / f\"{name}.css\"\n    if maybe_path.exists():\n        return maybe_path.read_text(encoding=\"UTF-8\")\n    return \"\"\n\n\nclass CSS(ABCMeta):\n    \"\"\"Cascade stylesheets, i.e. combine all ancestor styles\"\"\"\n\n    def __new__(cls, name, bases, attrs):\n        Page = super().__new__(cls, name, bases, attrs)\n        # Use a locally defined STYLE or the one from styles directory\n        Page.STYLE = _extract_style(attrs.get(\"STYLE_FILE\"), name)\n        Page.STYLE += attrs.get(\"STYLE_APPEND\", \"\")\n        # Combine with all ancestor styles\n        Page.CSS = \"\".join(\n            Class.STYLE\n            for Class in reversed(Page.__mro__)\n            if type(Class) is CSS\n        )\n        return Page\n"
  },
  {
    "path": "sanic/pages/directory_page.py",
    "content": "from collections.abc import Iterable\nfrom typing import TypedDict\n\nfrom html5tagger import E\n\nfrom .base import BasePage\n\n\nclass FileInfo(TypedDict):\n    \"\"\"Type for file info.\"\"\"\n\n    icon: str\n    file_name: str\n    file_access: str\n    file_size: str\n\n\nclass DirectoryPage(BasePage):  # no cov\n    \"\"\"Page for viewing a directory.\"\"\"\n\n    TITLE = \"Directory Viewer\"\n\n    def __init__(\n        self, files: Iterable[FileInfo], url: str, debug: bool\n    ) -> None:\n        super().__init__(debug)\n        self.files = files\n        self.url = url\n\n    def _body(self) -> None:\n        with self.doc.main:\n            self._headline()\n            files = list(self.files)\n            if files:\n                self._file_table(files)\n            else:\n                self.doc.p(\"The folder is empty.\")\n\n    def _headline(self):\n        \"\"\"Implement a heading with the current path, combined with\n        breadcrumb links\"\"\"\n        with self.doc.h1(id=\"breadcrumbs\"):\n            p = self.url.split(\"/\")[:-1]\n\n            for i, part in enumerate(p):\n                path = \"/\".join(p[: i + 1]) + \"/\"\n                with self.doc.a(href=path):\n                    self.doc.span(part, class_=\"dir\").span(\"/\", class_=\"sep\")\n\n    def _file_table(self, files: Iterable[FileInfo]):\n        with self.doc.table(class_=\"autoindex container\"):\n            for f in files:\n                self._file_row(**f)\n\n    def _file_row(\n        self,\n        icon: str,\n        file_name: str,\n        file_access: str,\n        file_size: str,\n    ):\n        first = E.span(icon, class_=\"icon\").a(file_name, href=file_name)\n        self.doc.tr.td(first).td(file_size).td(file_access)\n"
  },
  {
    "path": "sanic/pages/error.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any\n\nimport tracerite.html\n\nfrom html5tagger import E\nfrom tracerite import html_traceback, inspector\n\nfrom sanic.request import Request\n\nfrom .base import BasePage\n\n\n# Avoid showing the request in the traceback variable inspectors\ninspector.blacklist_types += (Request,)\n\nENDUSER_TEXT = \"\"\"\\\nWe're sorry, but it looks like something went wrong. Please try refreshing \\\nthe page or navigating back to the homepage. If the issue persists, our \\\ntechnical team is working to resolve it as soon as possible. We apologize \\\nfor the inconvenience and appreciate your patience.\\\n\"\"\"\n\n\nclass ErrorPage(BasePage):\n    \"\"\"Page for displaying an error.\"\"\"\n\n    STYLE_APPEND = tracerite.html.style\n\n    def __init__(\n        self,\n        debug: bool,\n        title: str,\n        text: str,\n        request: Request,\n        exc: Exception,\n    ) -> None:\n        super().__init__(debug)\n        name = request.app.name.replace(\"_\", \" \").strip()\n        if name.islower():\n            name = name.title()\n        self.TITLE = f\"Application {name} cannot handle your request\"\n        self.HEADING = E(\"Application \").strong(name)(\n            \" cannot handle your request\"\n        )\n        self.title = title\n        self.text = text\n        self.request = request\n        self.exc = exc\n        self.details_open = not getattr(exc, \"quiet\", False)\n\n    def _head(self) -> None:\n        self.doc._script(tracerite.html.javascript)\n        super()._head()\n\n    def _body(self) -> None:\n        debug = self.request.app.debug\n        route_name = self.request.name or \"[route not found]\"\n        with self.doc.main:\n            self.doc.h1(f\"⚠️ {self.title}\").p(self.text)\n            # Show context details if available on the exception\n            context = getattr(self.exc, \"context\", None)\n            if context:\n                self._key_value_table(\n                    \"Issue context\", \"exception-context\", context\n                )\n\n            if not debug:\n                with self.doc.div(id=\"enduser\"):\n                    self.doc.p(ENDUSER_TEXT).p.a(\"Front Page\", href=\"/\")\n                return\n            # Show additional details in debug mode,\n            # open by default for 500 errors\n            with self.doc.details(open=self.details_open, class_=\"smalltext\"):\n                # Show extra details if available on the exception\n                extra = getattr(self.exc, \"extra\", None)\n                if extra:\n                    self._key_value_table(\n                        \"Issue extra data\", \"exception-extra\", extra\n                    )\n\n                self.doc.summary(\n                    \"Details for developers (Sanic debug mode only)\"\n                )\n                if self.exc:\n                    with self.doc.div(class_=\"exception-wrapper\"):\n                        self.doc.h2(f\"Exception in {route_name}:\")\n                        self.doc(\n                            html_traceback(self.exc, include_js_css=False)\n                        )\n\n                self._key_value_table(\n                    f\"{self.request.method} {self.request.path}\",\n                    \"request-headers\",\n                    self.request.headers,\n                )\n\n    def _key_value_table(\n        self, title: str, table_id: str, data: Mapping[str, Any]\n    ) -> None:\n        with self.doc.div(class_=\"key-value-display\"):\n            self.doc.h2(title)\n            with self.doc.dl(id=table_id, class_=\"key-value-table smalltext\"):\n                for key, value in data.items():\n                    # Reading values may cause a new exception, so suppress it\n                    try:\n                        value = str(value)\n                    except Exception:\n                        value = E.em(\"Unable to display value\")\n                    self.doc.dt.span(key, class_=\"nobr key\").span(\": \").dd(\n                        value\n                    )\n"
  },
  {
    "path": "sanic/pages/styles/BasePage.css",
    "content": "/** BasePage **/\n\n:root {\n    --sanic: #ff0d68;\n    --sanic-yellow: #FFE900;\n    --sanic-background: #efeced;\n    --sanic-text: #121010;\n    --sanic-text-lighter: #756169;\n    --sanic-link: #ff0d68;\n    --sanic-block-background: #f7f4f6;\n    --sanic-block-text: #000;\n    --sanic-block-alt-text: #6b6468;\n    --sanic-header-background: #272325;\n    --sanic-header-border: #fff;\n    --sanic-header-text: #fff;\n    --sanic-highlight-background: var(--sanic-yellow);\n    --sanic-highlight-text: var(--sanic-text);\n    --sanic-tab-background: #f7f4f6;\n    --sanic-tab-shadow: #f7f6f6;\n    --sanic-tab-text: #222021;\n    --sanic-tracerite-var: var(--sanic-text);\n    --sanic-tracerite-val: #ff0d68;\n    --sanic-tracerite-type: #6d6a6b;\n}\n\n\n@media (prefers-color-scheme: dark) {\n    :root {\n        --sanic-text: #f7f4f6;\n        --sanic-background: #121010;\n        --sanic-block-background: #0f0d0e;\n        --sanic-block-text: #f7f4f6;\n        --sanic-header-background: #030203;\n        --sanic-header-border: #000;\n        --sanic-highlight-text: var(--sanic-background);\n        --sanic-tab-background: #292728;\n        --sanic-tab-shadow: #0f0d0e;\n        --sanic-tab-text: #aea7ab;\n    }\n}\n\nhtml {\n    font: 16px sans-serif;\n    background: var(--sanic-background);\n    color: var(--sanic-text);\n    scrollbar-gutter: stable;\n    overflow: hidden auto;\n}\n\nbody {\n    margin: 0;\n    font-size: 1.25rem;\n    line-height: 125%;\n}\n\nbody>* {\n    padding: 1rem 2vw;\n}\n\n@media (max-width: 1000px) {\n    body>* {\n        padding: 0.5rem 1.5vw;\n    }\n\n    html {\n        /* Scale everything by rem of 6px-16px by viewport width */\n        font-size: calc(6px + 10 * 100vw / 1000);\n    }\n}\n\nmain {\n    /* Make sure the footer is closer to bottom */\n    min-height: 70vh;\n    /* Generous padding for readability */\n    padding: 1rem 2.5rem;\n}\n\n.smalltext {\n    font-size: 1.0rem;\n}\n\n.container {\n    min-width: 600px;\n    max-width: 1600px;\n}\n\nheader {\n    background: var(--sanic-header-background);\n    color: var(--sanic-header-text);\n    border-bottom: 1px solid var(--sanic-header-border);\n    text-align: center;\n}\n\nfooter {\n    text-align: center;\n    display: flex;\n    flex-direction: column;\n    font-size: 0.8rem;\n    margin: 2rem;\n    line-height: 1.5em;\n}\n\nh1 {\n    text-align: left;\n}\n\na {\n    text-decoration: none;\n    color: var(--sanic-link);\n}\n\na:hover,\na:focus {\n    text-decoration: underline;\n    outline: none;\n}\n\n\nspan.icon {\n    margin-right: 1rem;\n}\n\n#logo-simple {\n    height: 1.75rem;\n    padding: 0 0.25rem;\n}\n\n\n@media (prefers-color-scheme: dark) {\n    #logo-simple path:last-child {\n        fill: #e1e1e1;\n    }\n}\n\n#sanic pre,\n#sanic code {\n    font-family: \"Fira Code\",\n        \"Source Code Pro\",\n        Menlo,\n        Meslo,\n        Monaco,\n        Consolas,\n        Lucida Console,\n        monospace;\n    font-size: 0.8rem;\n}\n"
  },
  {
    "path": "sanic/pages/styles/DirectoryPage.css",
    "content": "/** DirectoryPage **/\n#breadcrumbs>a:hover {\n    text-decoration: none;\n}\n\n#breadcrumbs>a .dir {\n    padding: 0 0.25em;\n}\n\n#breadcrumbs>a:first-child:hover::before,\n#breadcrumbs>a .dir:hover {\n    text-decoration: underline;\n}\n\n#breadcrumbs>a:first-child::before {\n    content: \"🏠\";\n}\n\n#breadcrumbs>a:last-child {\n    color: #ff0d68;\n}\n\nmain a {\n    color: inherit;\n    font-weight: bold;\n}\n\ntable.autoindex {\n    width: 100%;\n    font-family: monospace;\n    font-size: 1.25rem;\n}\n\ntable.autoindex tr {\n    display: flex;\n}\n\ntable.autoindex tr:hover {\n    background-color: #ddd;\n}\n\ntable.autoindex td {\n    margin: 0 0.5rem;\n}\n\ntable.autoindex td:first-child {\n    flex: 1;\n}\n\ntable.autoindex td:nth-child(2) {\n    text-align: right;\n}\n\ntable.autoindex td:last-child {\n    text-align: right;\n}\n\n\n@media (prefers-color-scheme: dark) {\n    table.autoindex tr:hover {\n        background-color: #222;\n    }\n}\n"
  },
  {
    "path": "sanic/pages/styles/ErrorPage.css",
    "content": "/** ErrorPage **/\n#enduser {\n    max-width: 30em;\n    margin: 5em auto 5em auto;\n    text-align: justify;\n    /*text-justify: both;*/\n}\n\n#enduser a {\n    color: var(--sanic-blue);\n}\n\n#enduser p:last-child {\n    text-align: right;\n}\n\nsummary {\n    margin-top: 3em;\n    color: var(--sanic-text-lighter);\n    cursor: pointer;\n}\n\n.tracerite {\n    --tracerite-var: var(--sanic-tracerite-var);\n    --tracerite-val: var(--sanic-tracerite-val);\n    --tracerite-type: var(--sanic-tracerite-type);\n    --tracerite-exception: var(--sanic);\n    --tracerite-highlight: var(--sanic-yellow);\n    --tracerite-tab: var(--sanic-tab-background);\n    --tracerite-tab-text: var(--sanic-tab-text);\n}\n\n.tracerite>h3 {\n    margin: 0.5rem 0 !important;\n}\n\n#sanic .tracerite .traceback-labels button {\n    font-size: 0.8rem;\n    line-height: 120%;\n    background: var(--tracerite-tab);\n    color: var(--tracerite-tab-text);\n    transition: 0.3s;\n    cursor: pointer;\n}\n\n.tracerite .traceback-labels {\n    padding-top: 5px;\n}\n\n.tracerite .traceback-labels button:hover {\n    filter: contrast(150%) brightness(120%) drop-shadow(0 -0 2px var(--sanic-tab-shadow));\n}\n\n#sanic .tracerite .tracerite-tooltip::before {\n    bottom: 1.75em;\n}\n\n#sanic .tracerite .traceback-details mark span {\n    background: var(--sanic-highlight-background);\n    color: var(--sanic-highlight-text);\n}\n\nheader {\n    background: var(--sanic-header-background);\n}\n\nh2 {\n    font-size: 1.3rem;\n    color: var(--sanic-text);\n}\n\n.key-value-display,\n.exception-wrapper {\n    padding: 0.5rem;\n    margin-top: 1rem;\n}\n\n.key-value-display {\n    background-color: var(--sanic-block-background);\n    color: var(--sanic-block-text);\n}\n\n.key-value-display h2 {\n    margin-bottom: 0.2em;\n}\n\ndl.key-value-table {\n    width: 100%;\n    margin: 0;\n    display: grid;\n    grid-template-columns: 1fr 5fr;\n    grid-gap: .3em;\n    white-space: pre-wrap;\n}\n\ndl.key-value-table * {\n    margin: 0;\n}\n\ndl.key-value-table dt {\n    color: var(--sanic-block-alt-text);\n    word-break: break-word;\n}\n\ndl.key-value-table dd {\n    /* Better breaking for cookies header and such */\n    word-break: break-all;\n}\n"
  },
  {
    "path": "sanic/py.typed",
    "content": ""
  },
  {
    "path": "sanic/request/__init__.py",
    "content": "from .form import File, parse_multipart_form\nfrom .parameters import RequestParameters\nfrom .types import Request\n\n\n__all__ = (\n    \"File\",\n    \"parse_multipart_form\",\n    \"Request\",\n    \"RequestParameters\",\n)\n"
  },
  {
    "path": "sanic/request/form.py",
    "content": "from __future__ import annotations\n\nimport email.utils\nimport unicodedata\n\nfrom typing import NamedTuple\nfrom urllib.parse import unquote\n\nfrom sanic.headers import parse_content_header\nfrom sanic.log import logger\n\nfrom .parameters import RequestParameters\n\n\nclass File(NamedTuple):\n    \"\"\"Model for defining a file.\n\n    It is a `namedtuple`, therefore you can iterate over the object, or\n    access the parameters by name.\n\n    Args:\n        type (str, optional): The mimetype, defaults to \"text/plain\".\n        body (bytes): Bytes of the file.\n        name (str): The filename.\n    \"\"\"\n\n    type: str\n    body: bytes\n    name: str\n\n\ndef parse_multipart_form(body, boundary):\n    \"\"\"Parse a request body and returns fields and files\n\n    Args:\n        body (bytes): Bytes request body.\n        boundary (bytes): Bytes multipart boundary.\n\n    Returns:\n        tuple[RequestParameters, RequestParameters]: A tuple containing fields and files as `RequestParameters`.\n    \"\"\"  # noqa: E501\n    files = {}\n    fields = {}\n\n    form_parts = body.split(boundary)\n    for form_part in form_parts[1:-1]:\n        file_name = None\n        content_type = \"text/plain\"\n        content_charset = \"utf-8\"\n        field_name = None\n        line_index = 2\n        line_end_index = 0\n        while not line_end_index == -1:\n            line_end_index = form_part.find(b\"\\r\\n\", line_index)\n            form_line = form_part[line_index:line_end_index].decode(\"utf-8\")\n            line_index = line_end_index + 2\n\n            if not form_line:\n                break\n\n            colon_index = form_line.index(\":\")\n            idx = colon_index + 2\n            form_header_field = form_line[0:colon_index].lower()\n            form_header_value, form_parameters = parse_content_header(\n                form_line[idx:]\n            )\n\n            if form_header_field == \"content-disposition\":\n                field_name = form_parameters.get(\"name\")\n                file_name = form_parameters.get(\"filename\")\n\n                # non-ASCII filenames in RFC2231, \"filename*\" format\n                if file_name is None and form_parameters.get(\"filename*\"):\n                    encoding, _, value = email.utils.decode_rfc2231(\n                        form_parameters[\"filename*\"]\n                    )\n                    file_name = unquote(value, encoding=encoding)\n\n                # Normalize to NFC (Apple MacOS/iOS send NFD)\n                # Notes:\n                # - No effect for Windows, Linux or Android clients which\n                #   already send NFC\n                # - Python open() is tricky (creates files in NFC no matter\n                #   which form you use)\n                if file_name is not None:\n                    file_name = unicodedata.normalize(\"NFC\", file_name)\n\n            elif form_header_field == \"content-type\":\n                content_type = form_header_value\n                content_charset = form_parameters.get(\"charset\", \"utf-8\")\n\n        if field_name:\n            post_data = form_part[line_index:-4]\n            if file_name is None:\n                value = post_data.decode(content_charset)\n                if field_name in fields:\n                    fields[field_name].append(value)\n                else:\n                    fields[field_name] = [value]\n            else:\n                form_file = File(\n                    type=content_type, name=file_name, body=post_data\n                )\n                if field_name in files:\n                    files[field_name].append(form_file)\n                else:\n                    files[field_name] = [form_file]\n        else:\n            logger.debug(\n                \"Form-data field does not have a 'name' parameter \"\n                \"in the Content-Disposition header\"\n            )\n\n    return RequestParameters(fields), RequestParameters(files)\n"
  },
  {
    "path": "sanic/request/parameters.py",
    "content": "from __future__ import annotations\n\nfrom typing import Any\n\n\nclass RequestParameters(dict):\n    \"\"\"Hosts a dict with lists as values where get returns the first value of the list and getlist returns the whole shebang\"\"\"  # noqa: E501\n\n    def get(self, name: str, default: Any | None = None) -> Any | None:\n        \"\"\"Return the first value, either the default or actual\n\n        Args:\n            name (str): The name of the parameter\n            default (Any | None, optional): The default value. Defaults to None.\n\n        Returns:\n            Any | None: The first value of the list\n        \"\"\"  # noqa: E501\n        return super().get(name, [default])[0]\n\n    def getlist(\n        self, name: str, default: list[Any] | None = None\n    ) -> list[Any]:\n        \"\"\"Return the entire list\n\n        Args:\n            name (str): The name of the parameter\n            default (list[Any] | None, optional): The default value. Defaults to None.\n\n        Returns:\n            list[Any]: The entire list of values or [] if not found\n        \"\"\"  # noqa: E501\n        return super().get(name, default) or []\n"
  },
  {
    "path": "sanic/request/types.py",
    "content": "from __future__ import annotations\n\nfrom asyncio import BaseProtocol\nfrom collections import defaultdict\nfrom contextvars import ContextVar\nfrom inspect import isawaitable\nfrom types import SimpleNamespace\nfrom typing import (\n    TYPE_CHECKING,\n    Any,\n    Generic,\n    cast,\n)\n\nfrom sanic_routing.route import Route\nfrom typing_extensions import TypeVar\n\nfrom sanic.http.constants import HTTP  # type: ignore\nfrom sanic.http.stream import Stream\nfrom sanic.models.asgi import ASGIScope\nfrom sanic.models.http_types import Credentials\n\n\nif TYPE_CHECKING:\n    from sanic.app import Sanic\n    from sanic.config import Config\n    from sanic.server import ConnInfo\n\nimport uuid\n\nfrom urllib.parse import parse_qs, parse_qsl, urlunparse\n\nfrom httptools import parse_url\nfrom httptools.parser.errors import HttpParserInvalidURLError\n\nfrom sanic.compat import CancelledErrors, Header\nfrom sanic.constants import (\n    CACHEABLE_HTTP_METHODS,\n    DEFAULT_HTTP_CONTENT_TYPE,\n    IDEMPOTENT_HTTP_METHODS,\n    SAFE_HTTP_METHODS,\n)\nfrom sanic.cookies.request import CookieRequestParameters, parse_cookie\nfrom sanic.exceptions import BadRequest, BadURL, ServerError\nfrom sanic.headers import (\n    AcceptList,\n    Options,\n    parse_accept,\n    parse_content_header,\n    parse_credentials,\n    parse_forwarded,\n    parse_host,\n    parse_xforwarded,\n)\nfrom sanic.http import Stage\nfrom sanic.log import error_logger\nfrom sanic.models.protocol_types import TransportProtocol\nfrom sanic.response import BaseHTTPResponse, HTTPResponse\n\nfrom .form import parse_multipart_form\nfrom .parameters import RequestParameters\n\n\ntry:\n    from ujson import loads as json_loads  # type: ignore\nexcept ImportError:\n    from json import loads as json_loads  # type: ignore\n\nif TYPE_CHECKING:\n    # The default argument of TypeVar is proposed to be added in Python 3.13\n    # by PEP 696 (https://www.python.org/dev/peps/pep-0696/).\n    # Therefore, we use typing_extensions.TypeVar for compatibility.\n    # For more information, see:\n    # https://discuss.python.org/t/pep-696-type-defaults-for-typevarlikes\n    sanic_type = TypeVar(\n        \"sanic_type\", bound=Sanic, default=Sanic[Config, SimpleNamespace]\n    )\n    ctx_type = TypeVar(\n        \"ctx_type\", bound=SimpleNamespace, default=SimpleNamespace\n    )\nelse:\n    sanic_type = TypeVar(\"sanic_type\")\n    ctx_type = TypeVar(\"ctx_type\")\n\n\nclass Request(Generic[sanic_type, ctx_type]):\n    \"\"\"State of HTTP request.\n\n    Args:\n        url_bytes (bytes): Raw URL bytes.\n        headers (Header): Request headers.\n        version (str): HTTP version.\n        method (str): HTTP method.\n        transport (TransportProtocol): Transport protocol.\n        app (Sanic): Sanic instance.\n        head (bytes, optional): Request head. Defaults to `b\"\"`.\n        stream_id (int, optional): HTTP/3 stream ID. Defaults to `0`.\n    \"\"\"\n\n    _current: ContextVar[Request] = ContextVar(\"request\")\n    _loads = json_loads\n\n    __slots__ = (\n        \"__weakref__\",\n        \"_cookies\",\n        \"_ctx\",\n        \"_id\",\n        \"_ip\",\n        \"_parsed_url\",\n        \"_port\",\n        \"_protocol\",\n        \"_remote_addr\",\n        \"_request_middleware_started\",\n        \"_response_middleware_started\",\n        \"_scheme\",\n        \"_socket\",\n        \"_stream_id\",\n        \"_match_info\",\n        \"_name\",\n        \"app\",\n        \"body\",\n        \"conn_info\",\n        \"head\",\n        \"headers\",\n        \"method\",\n        \"parsed_accept\",\n        \"parsed_args\",\n        \"parsed_cookies\",\n        \"parsed_credentials\",\n        \"parsed_files\",\n        \"parsed_form\",\n        \"parsed_forwarded\",\n        \"parsed_json\",\n        \"parsed_not_grouped_args\",\n        \"parsed_token\",\n        \"raw_url\",\n        \"responded\",\n        \"route\",\n        \"stream\",\n        \"transport\",\n        \"version\",\n    )\n\n    def __init__(\n        self,\n        url_bytes: bytes,\n        headers: Header,\n        version: str,\n        method: str,\n        transport: TransportProtocol,\n        app: sanic_type,\n        head: bytes = b\"\",\n        stream_id: int = 0,\n    ):\n        self.raw_url = url_bytes\n        try:\n            self._parsed_url = parse_url(url_bytes)\n        except HttpParserInvalidURLError:\n            url = url_bytes.decode(errors=\"backslashreplace\")\n            raise BadURL(f\"Bad URL: {url}\")\n        self._id: uuid.UUID | str | int | None = None\n        self._name: str | None = None\n        self._stream_id = stream_id\n        self.app = app\n\n        self.headers = Header(headers)\n        self.version = version\n        self.method = method\n        self.transport = transport\n        self.head = head\n\n        # Init but do not inhale\n        self.body = b\"\"\n        self.conn_info: ConnInfo | None = None\n        self._ctx: ctx_type | None = None\n        self.parsed_accept: AcceptList | None = None\n        self.parsed_args: defaultdict[\n            tuple[bool, bool, str, str], RequestParameters\n        ] = defaultdict(RequestParameters)\n        self.parsed_cookies: RequestParameters | None = None\n        self.parsed_credentials: Credentials | None = None\n        self.parsed_files: RequestParameters | None = None\n        self.parsed_form: RequestParameters | None = None\n        self.parsed_forwarded: Options | None = None\n        self.parsed_json = None\n        self.parsed_not_grouped_args: defaultdict[\n            tuple[bool, bool, str, str], list[tuple[str, str]]\n        ] = defaultdict(list)\n        self.parsed_token: str | None = None\n        self._request_middleware_started = False\n        self._response_middleware_started = False\n        self.responded: bool = False\n        self.route: Route | None = None\n        self.stream: Stream | None = None\n        self._match_info: dict[str, Any] = {}\n        self._protocol: BaseProtocol | None = None\n\n    def __repr__(self):\n        class_name = self.__class__.__name__\n        return f\"<{class_name}: {self.method} {self.path}>\"\n\n    @staticmethod\n    def make_context() -> ctx_type:\n        \"\"\"Create a new context object.\n\n        This method is called when a new request context is pushed. It is\n        a great candidate for overriding in a subclass if you want to\n        control the type of context object that is created.\n\n        By default, it returns a `types.SimpleNamespace` instance.\n\n        Returns:\n            ctx_type: A new context object.\n        \"\"\"\n        return cast(ctx_type, SimpleNamespace())\n\n    @classmethod\n    def get_current(cls) -> Request:\n        \"\"\"Retrieve the current request object\n\n        This implements [Context Variables](https://docs.python.org/3/library/contextvars.html)\n        to allow for accessing the current request from anywhere.\n\n        A typical usecase is when you want to access the current request\n        from a function that is not a handler, such as a logging function:\n\n        ```python\n        import logging\n\n        class LoggingFormater(logging.Formatter):\n            def format(self, record):\n                request = Request.get_current()\n                record.url = request.url\n                record.ip = request.ip\n                return super().format(record)\n        ```\n\n        Returns:\n            Request: The current request object\n\n        Raises:\n            sanic.exceptions.ServerError: If it is outside of a request\n                lifecycle.\n        \"\"\"  # noqa: E501\n        request = cls._current.get(None)\n        if not request:\n            raise ServerError(\"No current request\")\n        return request\n\n    @classmethod\n    def generate_id(*_) -> uuid.UUID | str | int:\n        \"\"\"Generate a unique ID for the request.\n\n        This method is called to generate a unique ID for each request.\n        By default, it returns a `uuid.UUID` instance.\n\n        Returns:\n            uuid.UUID | str | int: A unique ID for the request.\n        \"\"\"\n        return uuid.uuid4()\n\n    @property\n    def ctx(self) -> ctx_type:\n        \"\"\"The current request context.\n\n        This is a context object for the current request. It is created\n        by `Request.make_context` and is a great place to store data\n        that you want to be accessible during the request lifecycle.\n\n        Returns:\n            ctx_type: The current request context.\n        \"\"\"\n        if not self._ctx:\n            self._ctx = self.make_context()\n        return self._ctx\n\n    @property\n    def stream_id(self) -> int:\n        \"\"\"Access the HTTP/3 stream ID.\n\n        Raises:\n            sanic.exceptions.ServerError: If the request is not HTTP/3.\n\n        Returns:\n            int: The HTTP/3 stream ID.\n        \"\"\"\n        if self.protocol.version is not HTTP.VERSION_3:\n            raise ServerError(\n                \"Stream ID is only a property of a HTTP/3 request\"\n            )\n        return self._stream_id\n\n    def reset_response(self) -> None:\n        \"\"\"Reset the response object.\n\n        This clears much of the state of the object. It should\n        generally not be called directly, but is called automatically as\n        part of the request lifecycle.\n\n        Raises:\n            sanic.exceptions.ServerError: If the response has already been\n                sent.\n        \"\"\"\n        try:\n            if (\n                self.stream is not None\n                and self.stream.stage is not Stage.HANDLER\n            ):\n                raise ServerError(\n                    \"Cannot reset response because previous response was sent.\"\n                )\n            self.stream.response.stream = None  # type: ignore\n            self.stream.response = None  # type: ignore\n            self.responded = False\n        except AttributeError:\n            pass\n\n    async def respond(\n        self,\n        response: BaseHTTPResponse | None = None,\n        *,\n        status: int = 200,\n        headers: Header | dict[str, str] | None = None,\n        content_type: str | None = None,\n    ):\n        \"\"\"Respond to the request without returning.\n\n        This method can only be called once, as you can only respond once.\n        If no ``response`` argument is passed, one will be created from the\n        ``status``, ``headers`` and ``content_type`` arguments.\n\n        **The first typical usecase** is if you wish to respond to the\n        request without returning from the handler:\n\n        ```python\n        @app.get(\"/\")\n        async def handler(request: Request):\n            data = ...  # Process something\n\n            json_response = json({\"data\": data})\n            await request.respond(json_response)\n\n        @app.on_response\n        async def add_header(_, response: HTTPResponse):\n            # Middlewares still get executed as expected\n            response.headers[\"one\"] = \"two\"\n        ```\n\n        **The second possible usecase** is for when you want to directly\n        respond to the request:\n\n        ```python\n        response = await request.respond(content_type=\"text/csv\")\n        await response.send(\"foo,\")\n        await response.send(\"bar\")\n\n        # You can control the completion of the response by calling\n        # the 'eof()' method:\n        await response.eof()\n        ```\n\n        Args:\n            response (ResponseType): Response instance to send.\n            status (int): Status code to return in the response.\n            headers (dict[str, str] | None): Headers to return in the response, defaults to None.\n            content_type (str | None): Content-Type header of the response, defaults to None.\n\n        Returns:\n            FinalResponseType: Final response being sent (may be different from the\n                \"response\" parameter because of middlewares), which can be\n                used to manually send data.\n        \"\"\"  # noqa: E501\n        try:\n            if self.stream is not None and self.stream.response:\n                raise ServerError(\"Second respond call is not allowed.\")\n        except AttributeError:\n            pass\n        # This logic of determining which response to use is subject to change\n        if response is None:\n            response = HTTPResponse(\n                status=status,\n                headers=headers,\n                content_type=content_type,\n            )\n\n        # Connect the response\n        if isinstance(response, BaseHTTPResponse) and self.stream:\n            response = self.stream.respond(response)\n\n            if isawaitable(response):\n                response = await response  # type: ignore\n        # Run response middleware\n        try:\n            middleware = (\n                self.route and self.route.extra.response_middleware\n            ) or self.app.response_middleware\n            if middleware and not self._response_middleware_started:\n                self._response_middleware_started = True\n                response = await self.app._run_response_middleware(\n                    self, response, middleware\n                )\n        except CancelledErrors:\n            raise\n        except Exception:\n            error_logger.exception(\n                \"Exception occurred in one of response middleware handlers\"\n            )\n        self.responded = True\n        return response\n\n    async def receive_body(self):\n        \"\"\"Receive request.body, if not already received.\n\n        Streaming handlers may call this to receive the full body. Sanic calls\n        this function before running any handlers of non-streaming routes.\n\n        Custom request classes can override this for custom handling of both\n        streaming and non-streaming routes.\n        \"\"\"\n        if not self.body:\n            self.body = b\"\".join([data async for data in self.stream])\n\n    @property\n    def name(self) -> str | None:\n        \"\"\"The route name\n\n        In the following pattern:\n\n        ```\n        <AppName>.[<BlueprintName>.]<HandlerName>\n        ```\n\n        Returns:\n            str | None: The route name\n        \"\"\"\n        if self._name:\n            return self._name\n        elif self.route:\n            return self.route.name\n        return None\n\n    @property\n    def endpoint(self) -> str | None:\n        \"\"\"Alias of `sanic.request.Request.name`\n\n        Returns:\n            str | None: The route name\n        \"\"\"\n        return self.name\n\n    @property\n    def uri_template(self) -> str | None:\n        \"\"\"The defined URI template\n\n        Returns:\n            str | None: The defined URI template\n        \"\"\"\n        if self.route:\n            return f\"/{self.route.path}\"\n        return None\n\n    @property\n    def protocol(self) -> TransportProtocol:\n        \"\"\"The HTTP protocol instance\n\n        Returns:\n            Protocol: The HTTP protocol instance\n        \"\"\"\n        if not self._protocol:\n            self._protocol = self.transport.get_protocol()\n        return self._protocol  # type: ignore\n\n    @property\n    def raw_headers(self) -> bytes:\n        \"\"\"The unparsed HTTP headers\n\n        Returns:\n            bytes: The unparsed HTTP headers\n        \"\"\"\n        _, headers = self.head.split(b\"\\r\\n\", 1)\n        return bytes(headers)\n\n    @property\n    def request_line(self) -> bytes:\n        \"\"\"The first line of a HTTP request\n\n        Returns:\n            bytes: The first line of a HTTP request\n        \"\"\"\n        reqline, _ = self.head.split(b\"\\r\\n\", 1)\n        return bytes(reqline)\n\n    @property\n    def id(self) -> uuid.UUID | str | int | None:\n        \"\"\"A request ID passed from the client, or generated from the backend.\n\n        By default, this will look in a request header defined at:\n        `self.app.config.REQUEST_ID_HEADER`. It defaults to\n        `X-Request-ID`. Sanic will try to cast the ID into a `UUID` or an\n        `int`.\n\n        If there is not a UUID from the client, then Sanic will try\n        to generate an ID by calling `Request.generate_id()`. The default\n        behavior is to generate a `UUID`. You can customize this behavior\n        by subclassing `Request` and overwriting that method.\n\n        ```python\n        from sanic import Request, Sanic\n        from itertools import count\n\n        class IntRequest(Request):\n            counter = count()\n\n            def generate_id(self):\n                return next(self.counter)\n\n        app = Sanic(\"MyApp\", request_class=IntRequest)\n        ```\n\n        Returns:\n            uuid.UUID | str | int | None: A request ID passed from the\n                client, or generated from the backend.\n        \"\"\"\n        if not self._id:\n            self._id = self.headers.getone(\n                self.app.config.REQUEST_ID_HEADER,\n                self.__class__.generate_id(self),  # type: ignore\n            )\n\n            # Try casting to a UUID or an integer\n            if isinstance(self._id, str):\n                try:\n                    self._id = uuid.UUID(self._id)\n                except ValueError:\n                    try:\n                        self._id = int(self._id)  # type: ignore\n                    except ValueError:\n                        ...\n\n        return self._id  # type: ignore\n\n    @property\n    def json(self) -> Any:\n        \"\"\"The request body parsed as JSON\n\n        Returns:\n            Any: The request body parsed as JSON\n        \"\"\"\n        if self.parsed_json is None:\n            self.load_json()\n\n        return self.parsed_json\n\n    def load_json(self, loads=None) -> Any:\n        \"\"\"Load the request body as JSON\n\n        Args:\n            loads (Callable, optional): A custom JSON loader. Defaults to None.\n\n        Raises:\n            BadRequest: If the request body cannot be parsed as JSON\n\n        Returns:\n            Any: The request body parsed as JSON\n        \"\"\"\n        try:\n            if not loads:\n                loads = self.__class__._loads\n\n            self.parsed_json = loads(self.body)\n        except Exception:\n            if not self.body:\n                return None\n            raise BadRequest(\"Failed when parsing body as json\")\n\n        return self.parsed_json\n\n    @property\n    def accept(self) -> AcceptList:\n        \"\"\"Accepted response content types.\n\n        A convenience handler for easier RFC-compliant matching of MIME types,\n        parsed as a list that can match wildcards and includes */* by default.\n\n        Returns:\n            AcceptList: Accepted response content types\n        \"\"\"\n        if self.parsed_accept is None:\n            self.parsed_accept = parse_accept(self.headers.get(\"accept\"))\n        return self.parsed_accept\n\n    @property\n    def token(self) -> str | None:\n        \"\"\"Attempt to return the auth header token.\n\n        Returns:\n            str | None: The auth header token\n        \"\"\"\n        if self.parsed_token is None:\n            prefixes = (\"Bearer\", \"Token\")\n            _, token = parse_credentials(\n                self.headers.getone(\"authorization\", None), prefixes\n            )\n            self.parsed_token = token\n        return self.parsed_token\n\n    @property\n    def credentials(self) -> Credentials | None:\n        \"\"\"Attempt to return the auth header value.\n\n        Covers NoAuth, Basic Auth, Bearer Token, Api Token authentication\n        schemas.\n\n        Returns:\n            Credentials | None: A Credentials object with token, or username\n                and password related to the request\n        \"\"\"\n        if self.parsed_credentials is None:\n            try:\n                prefix, credentials = parse_credentials(\n                    self.headers.getone(\"authorization\", None)\n                )\n                if credentials:\n                    self.parsed_credentials = Credentials(\n                        auth_type=prefix, token=credentials\n                    )\n            except ValueError:\n                pass\n        return self.parsed_credentials\n\n    def get_form(\n        self, keep_blank_values: bool = False\n    ) -> RequestParameters | None:\n        \"\"\"Method to extract and parse the form data from a request.\n\n        Args:\n            keep_blank_values (bool): Whether to discard blank values from the form data.\n\n        Returns:\n            RequestParameters | None: The parsed form data.\n        \"\"\"  # noqa: E501\n        self.parsed_form = RequestParameters()\n        self.parsed_files = RequestParameters()\n        content_type = self.headers.getone(\n            \"content-type\", DEFAULT_HTTP_CONTENT_TYPE\n        )\n        content_type, parameters = parse_content_header(content_type)\n        try:\n            if content_type == \"application/x-www-form-urlencoded\":\n                self.parsed_form = RequestParameters(\n                    parse_qs(\n                        self.body.decode(\"utf-8\"),\n                        keep_blank_values=keep_blank_values,\n                    )\n                )\n            elif content_type == \"multipart/form-data\":\n                # TODO: Stream this instead of reading to/from memory\n                boundary = parameters[\"boundary\"].encode(  # type: ignore\n                    \"utf-8\"\n                )  # type: ignore\n                self.parsed_form, self.parsed_files = parse_multipart_form(\n                    self.body, boundary\n                )\n        except Exception:\n            error_logger.exception(\"Failed when parsing form\")\n\n        return self.parsed_form\n\n    @property\n    def form(self) -> RequestParameters | None:\n        \"\"\"The request body parsed as form data\n\n        Returns:\n            RequestParameters | None: The request body parsed as form data\n        \"\"\"\n        if self.parsed_form is None:\n            self.get_form()\n\n        return self.parsed_form\n\n    @property\n    def files(self) -> RequestParameters | None:\n        \"\"\"The request body parsed as uploaded files\n\n        Returns:\n            RequestParameters | None: The request body parsed as uploaded files\n        \"\"\"  # noqa: E501\n        if self.parsed_files is None:\n            self.form  # compute form to get files\n\n        return self.parsed_files\n\n    def get_args(\n        self,\n        keep_blank_values: bool = False,\n        strict_parsing: bool = False,\n        encoding: str = \"utf-8\",\n        errors: str = \"replace\",\n    ) -> RequestParameters:\n        \"\"\"Parse `query_string` using `urllib.parse.parse_qs`.\n\n        This methods is used by the `args` property, but it also\n        can be used directly if you need to change default parameters.\n\n        Args:\n            keep_blank_values (bool): Flag indicating whether blank values in\n                percent-encoded queries should be treated as blank strings.\n                A `True` value indicates that blanks should be retained as\n                blank strings. The default `False` value indicates that\n                blank values are to be ignored and treated as if they were\n                not included.\n            strict_parsing (bool): Flag indicating what to do with parsing\n                errors. If `False` (the default), errors are silently ignored.\n                If `True`, errors raise a `ValueError` exception.\n            encoding (str): Specify how to decode percent-encoded sequences\n                into Unicode characters, as accepted by the\n                `bytes.decode()` method.\n            errors (str): Specify how to decode percent-encoded sequences\n                into Unicode characters, as accepted by the\n                `bytes.decode()` method.\n\n        Returns:\n            RequestParameters: A dictionary containing the parsed arguments.\n        \"\"\"\n        if (\n            keep_blank_values,\n            strict_parsing,\n            encoding,\n            errors,\n        ) not in self.parsed_args:\n            if self.query_string:\n                self.parsed_args[\n                    (keep_blank_values, strict_parsing, encoding, errors)\n                ] = RequestParameters(\n                    parse_qs(\n                        qs=self.query_string,\n                        keep_blank_values=keep_blank_values,\n                        strict_parsing=strict_parsing,\n                        encoding=encoding,\n                        errors=errors,\n                    )\n                )\n\n        return self.parsed_args[\n            (keep_blank_values, strict_parsing, encoding, errors)\n        ]\n\n    args = property(get_args)\n    \"\"\"Convenience property to access `Request.get_args` with default values.\n    \"\"\"\n\n    def get_query_args(\n        self,\n        keep_blank_values: bool = False,\n        strict_parsing: bool = False,\n        encoding: str = \"utf-8\",\n        errors: str = \"replace\",\n    ) -> list:\n        \"\"\"Parse `query_string` using `urllib.parse.parse_qsl`.\n\n        This methods is used by `query_args` propertyn but can be used\n        directly if you need to change default parameters.\n\n        Args:\n            keep_blank_values (bool): Flag indicating whether blank values in\n                percent-encoded queries should be treated as blank strings.\n                A `True` value indicates that blanks should be retained as\n                blank strings. The default `False` value indicates that\n                blank values are to be ignored and treated as if they were\n                not included.\n            strict_parsing (bool): Flag indicating what to do with\n                parsing errors. If `False` (the default), errors are\n                silently ignored. If `True`, errors raise a\n                `ValueError` exception.\n            encoding (str): Specify how to decode percent-encoded sequences\n                into Unicode characters, as accepted by the\n                `bytes.decode()` method.\n            errors (str): Specify how to decode percent-encoded sequences\n                into Unicode characters, as accepted by the\n                `bytes.decode()` method.\n\n        Returns:\n            list: A list of tuples containing the parsed arguments.\n        \"\"\"\n        if (\n            keep_blank_values,\n            strict_parsing,\n            encoding,\n            errors,\n        ) not in self.parsed_not_grouped_args:\n            if self.query_string:\n                self.parsed_not_grouped_args[\n                    (keep_blank_values, strict_parsing, encoding, errors)\n                ] = parse_qsl(\n                    qs=self.query_string,\n                    keep_blank_values=keep_blank_values,\n                    strict_parsing=strict_parsing,\n                    encoding=encoding,\n                    errors=errors,\n                )\n        return self.parsed_not_grouped_args[\n            (keep_blank_values, strict_parsing, encoding, errors)\n        ]\n\n    query_args = property(get_query_args)\n    \"\"\"Convenience property to access `Request.get_query_args` with default values.\n    \"\"\"  # noqa: E501\n\n    def get_cookies(self) -> RequestParameters:\n        cookie = self.headers.getone(\"cookie\", \"\")\n        self.parsed_cookies = CookieRequestParameters(parse_cookie(cookie))\n        return self.parsed_cookies\n\n    @property\n    def cookies(self) -> RequestParameters:\n        \"\"\"Incoming cookies on the request\n\n        Returns:\n            RequestParameters: Incoming cookies on the request\n        \"\"\"\n\n        if self.parsed_cookies is None:\n            self.get_cookies()\n        return cast(CookieRequestParameters, self.parsed_cookies)\n\n    @property\n    def content_type(self) -> str:\n        \"\"\"Content-Type header form the request\n\n        Returns:\n            str: Content-Type header form the request\n        \"\"\"\n        return self.headers.getone(\"content-type\", DEFAULT_HTTP_CONTENT_TYPE)\n\n    @property\n    def match_info(self) -> dict[str, Any]:\n        \"\"\"Matched path parameters after resolving route\n\n        Returns:\n            dict[str, Any]: Matched path parameters after resolving route\n        \"\"\"\n        return self._match_info\n\n    @match_info.setter\n    def match_info(self, value):\n        self._match_info = value\n\n    @property\n    def ip(self) -> str:\n        \"\"\"Peer ip of the socket\n\n        Returns:\n            str: Peer ip of the socket\n        \"\"\"\n        return self.conn_info.client_ip if self.conn_info else \"\"\n\n    @property\n    def port(self) -> int:\n        \"\"\"Peer port of the socket\n\n        Returns:\n            int: Peer port of the socket\n        \"\"\"\n        return self.conn_info.client_port if self.conn_info else 0\n\n    @property\n    def socket(self) -> tuple[str, int] | tuple[None, None]:\n        \"\"\"Information about the connected socket if available\n\n        Returns:\n            tuple[str, int] | tuple[None, None]: Information about the\n                connected socket if available, in the form of a tuple of\n                (ip, port)\n        \"\"\"\n        return (\n            self.conn_info.peername\n            if self.conn_info and self.conn_info.peername\n            else (None, None)\n        )\n\n    @property\n    def path(self) -> str:\n        \"\"\"Path of the local HTTP request\n\n        Returns:\n            str: Path of the local HTTP request\n        \"\"\"\n        return self._parsed_url.path.decode(\"utf-8\")\n\n    @property\n    def network_paths(self) -> list[Any] | None:\n        \"\"\"Access the network paths if available\n\n        Returns:\n            list[Any] | None: Access the network paths if available\n        \"\"\"\n        if self.conn_info is None:\n            return None\n        return self.conn_info.network_paths\n\n    # Proxy properties (using SERVER_NAME/forwarded/request/transport info)\n\n    @property\n    def forwarded(self) -> Options:\n        \"\"\"Active proxy information obtained from request headers, as specified in Sanic configuration.\n\n        Field names by, for, proto, host, port and path are normalized.\n        - for and by IPv6 addresses are bracketed\n        - port (int) is only set by port headers, not from host.\n        - path is url-unencoded\n\n        Additional values may be available from new style Forwarded headers.\n\n        Returns:\n            Options: proxy information from request headers\n        \"\"\"  # noqa: E501\n        if self.parsed_forwarded is None:\n            self.parsed_forwarded = (\n                parse_forwarded(self.headers, self.app.config)\n                or parse_xforwarded(self.headers, self.app.config)\n                or {}\n            )\n        return self.parsed_forwarded\n\n    @property\n    def remote_addr(self) -> str:\n        \"\"\"Client IP address, if available from proxy.\n\n        Returns:\n            str: IPv4, bracketed IPv6, UNIX socket name or arbitrary string\n        \"\"\"\n        if not hasattr(self, \"_remote_addr\"):\n            self._remote_addr = str(self.forwarded.get(\"for\", \"\"))\n        return self._remote_addr\n\n    @property\n    def client_ip(self) -> str:\n        \"\"\"\n        Client IP address.\n        1. proxied remote address `self.forwarded['for']`\n        2. local peer address `self.ip`\n\n        New in Sanic 23.6. Prefer this over `remote_addr` for determining the\n        client address regardless of whether the service runs behind a proxy\n        or not (proxy deployment needs separate configuration).\n\n        Returns:\n            str: IPv4, bracketed IPv6, UNIX socket name or arbitrary string\n        \"\"\"\n        return self.remote_addr or self.ip\n\n    @property\n    def scheme(self) -> str:\n        \"\"\"Determine request scheme.\n\n        1. `config.SERVER_NAME` if in full URL format\n        2. proxied proto/scheme\n        3. local connection protocol\n\n        Returns:\n            str: http|https|ws|wss or arbitrary value given by the headers.\n        \"\"\"\n        if not hasattr(self, \"_scheme\"):\n            if (\n                self.app.websocket_enabled\n                and self.headers.upgrade.lower() == \"websocket\"\n            ):\n                scheme = \"ws\"\n            else:\n                scheme = \"http\"\n            proto = None\n            sp = self.app.config.get(\"SERVER_NAME\", \"\").split(\"://\", 1)\n            if len(sp) == 2:\n                proto = sp[0]\n            elif \"proto\" in self.forwarded:\n                proto = str(self.forwarded[\"proto\"])\n            if proto:\n                # Give ws/wss if websocket, otherwise keep the same\n                scheme = proto.replace(\"http\", scheme)\n            elif self.conn_info and self.conn_info.ssl:\n                scheme += \"s\"\n            self._scheme = scheme\n\n        return self._scheme\n\n    @property\n    def host(self) -> str:\n        \"\"\"The currently effective server 'host' (hostname or hostname:port).\n\n        1. `config.SERVER_NAME` overrides any client headers\n        2. proxied host of original request\n        3. request host header\n        hostname and port may be separated by\n        `sanic.headers.parse_host(request.host)`.\n\n        Returns:\n            str: the first matching host found, or empty string\n        \"\"\"\n        server_name = self.app.config.get(\"SERVER_NAME\")\n        if server_name:\n            return server_name.split(\"//\", 1)[-1].split(\"/\", 1)[0]\n        return str(\n            self.forwarded.get(\"host\") or self.headers.getone(\"host\", \"\")\n        )\n\n    @property\n    def server_name(self) -> str:\n        \"\"\"hostname the client connected to, by `request.host`\n\n        Returns:\n            str: hostname the client connected to, by `request.host`\n        \"\"\"\n        return parse_host(self.host)[0] or \"\"\n\n    @property\n    def server_port(self) -> int:\n        \"\"\"The port the client connected to, by forwarded `port` or `request.host`.\n\n        Default port is returned as 80 and 443 based on `request.scheme`.\n\n        Returns:\n            int: The port the client connected to, by forwarded `port` or `request.host`.\n        \"\"\"  # noqa: E501\n        port = self.forwarded.get(\"port\") or parse_host(self.host)[1]\n        return int(port or (80 if self.scheme in (\"http\", \"ws\") else 443))\n\n    @property\n    def server_path(self) -> str:\n        \"\"\"Full path of current URL; uses proxied or local path\n\n        Returns:\n            str: Full path of current URL; uses proxied or local path\n        \"\"\"\n        return str(self.forwarded.get(\"path\") or self.path)\n\n    @property\n    def query_string(self) -> str:\n        \"\"\"Representation of the requested query\n\n        Returns:\n            str: Representation of the requested query\n        \"\"\"\n        if self._parsed_url.query:\n            return self._parsed_url.query.decode(\"utf-8\")\n        else:\n            return \"\"\n\n    @property\n    def url(self) -> str:\n        \"\"\"The URL\n\n        Returns:\n            str: The URL\n        \"\"\"\n        return urlunparse(\n            (self.scheme, self.host, self.path, None, self.query_string, None)\n        )\n\n    def url_for(self, view_name: str, **kwargs) -> str:\n        \"\"\"Retrieve a URL for a given view name.\n\n        Same as `sanic.Sanic.url_for`, but automatically determine `scheme`\n        and `netloc` base on the request. Since this method is aiming\n        to generate correct schema & netloc, `_external` is implied.\n\n        Args:\n            view_name (str): The view name to generate URL for.\n            **kwargs: Arbitrary keyword arguments to build URL query string.\n\n        Returns:\n            str: The generated URL.\n        \"\"\"\n        # Full URL SERVER_NAME can only be handled in app.url_for\n        try:\n            sp = self.app.config.get(\"SERVER_NAME\", \"\").split(\"://\", 1)\n            if len(sp) == 2:\n                return self.app.url_for(view_name, _external=True, **kwargs)\n        except AttributeError:\n            pass\n\n        scheme = self.scheme\n        host = self.server_name\n        port = self.server_port\n\n        if (scheme.lower() in (\"http\", \"ws\") and port == 80) or (\n            scheme.lower() in (\"https\", \"wss\") and port == 443\n        ):\n            netloc = host\n        else:\n            netloc = f\"{host}:{port}\"\n\n        return self.app.url_for(\n            view_name, _external=True, _scheme=scheme, _server=netloc, **kwargs\n        )\n\n    @property\n    def scope(self) -> ASGIScope:\n        \"\"\"The ASGI scope of the request.\n\n        Returns:\n            ASGIScope: The ASGI scope of the request.\n\n        Raises:\n            NotImplementedError: If the app isn't an ASGI app.\n        \"\"\"\n        if not self.app.asgi:\n            raise NotImplementedError(\n                \"App isn't running in ASGI mode. \"\n                \"Scope is only available for ASGI apps.\"\n            )\n\n        return self.transport.scope\n\n    @property\n    def is_safe(self) -> bool:\n        \"\"\"Whether the HTTP method is safe.\n\n        See https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.1\n\n        Returns:\n            bool: Whether the HTTP method is safe.\n        \"\"\"\n        return self.method in SAFE_HTTP_METHODS\n\n    @property\n    def is_idempotent(self) -> bool:\n        \"\"\"Whether the HTTP method is iempotent.\n\n        See https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.2\n\n        Returns:\n            bool: Whether the HTTP method is iempotent.\n        \"\"\"\n        return self.method in IDEMPOTENT_HTTP_METHODS\n\n    @property\n    def is_cacheable(self) -> bool:\n        \"\"\"Whether the HTTP method is cacheable.\n\n        See https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.3\n\n        Returns:\n            bool: Whether the HTTP method is cacheable.\n        \"\"\"\n        return self.method in CACHEABLE_HTTP_METHODS\n"
  },
  {
    "path": "sanic/response/__init__.py",
    "content": "from .convenience import (\n    empty,\n    file,\n    file_stream,\n    html,\n    json,\n    raw,\n    redirect,\n    text,\n    validate_file,\n)\nfrom .types import (\n    BaseHTTPResponse,\n    HTTPResponse,\n    JSONResponse,\n    ResponseStream,\n    json_dumps,\n)\n\n\n__all__ = (\n    \"BaseHTTPResponse\",\n    \"HTTPResponse\",\n    \"JSONResponse\",\n    \"ResponseStream\",\n    \"empty\",\n    \"json\",\n    \"text\",\n    \"raw\",\n    \"html\",\n    \"validate_file\",\n    \"file\",\n    \"redirect\",\n    \"file_stream\",\n    \"json_dumps\",\n)\n"
  },
  {
    "path": "sanic/response/convenience.py",
    "content": "from __future__ import annotations\n\nfrom datetime import datetime, timezone\nfrom email.utils import formatdate, parsedate_to_datetime\nfrom mimetypes import guess_type\nfrom os import path\nfrom pathlib import PurePath\nfrom time import time\nfrom typing import Any, AnyStr, Callable\nfrom urllib.parse import quote_plus\n\nfrom sanic.compat import Header, open_async, stat_async\nfrom sanic.constants import DEFAULT_HTTP_CONTENT_TYPE\nfrom sanic.helpers import Default, _default\nfrom sanic.log import logger\nfrom sanic.models.protocol_types import HTMLProtocol, Range\n\nfrom .types import HTTPResponse, JSONResponse, ResponseStream\n\n\ndef empty(\n    status: int = 204, headers: dict[str, str] | None = None\n) -> HTTPResponse:\n    \"\"\"Returns an empty response to the client.\n\n    Args:\n        status (int, optional): HTTP response code. Defaults to `204`.\n        headers ([type], optional): Custom HTTP headers. Defaults to `None`.\n\n    Returns:\n        HTTPResponse: An empty response to the client.\n    \"\"\"\n    return HTTPResponse(body=b\"\", status=status, headers=headers)\n\n\ndef json(\n    body: Any,\n    status: int = 200,\n    headers: dict[str, str] | None = None,\n    content_type: str = \"application/json\",\n    dumps: Callable[..., AnyStr] | None = None,\n    **kwargs: Any,\n) -> JSONResponse:\n    \"\"\"Returns response object with body in json format.\n\n    Args:\n        body (Any): Response data to be serialized.\n        status (int, optional): HTTP response code. Defaults to `200`.\n        headers (Dict[str, str], optional): Custom HTTP headers. Defaults to `None`.\n        content_type (str, optional): The content type (string) of the response. Defaults to `\"application/json\"`.\n        dumps (Callable[..., AnyStr], optional): A custom json dumps function. Defaults to `None`.\n        **kwargs (Any): Remaining arguments that are passed to the json encoder.\n\n    Returns:\n        JSONResponse: A response object with body in json format.\n    \"\"\"  # noqa: E501\n    return JSONResponse(\n        body,\n        status=status,\n        headers=headers,\n        content_type=content_type,\n        dumps=dumps,\n        **kwargs,\n    )\n\n\ndef text(\n    body: str,\n    status: int = 200,\n    headers: dict[str, str] | None = None,\n    content_type: str = \"text/plain; charset=utf-8\",\n) -> HTTPResponse:\n    \"\"\"Returns response object with body in text format.\n\n    Args:\n        body (str): Response data.\n        status (int, optional): HTTP response code. Defaults to `200`.\n        headers (Dict[str, str], optional): Custom HTTP headers. Defaults to `None`.\n        content_type (str, optional): The content type (string) of the response. Defaults to `\"text/plain; charset=utf-8\"`.\n\n    Returns:\n        HTTPResponse: A response object with body in text format.\n\n    Raises:\n        TypeError: If the body is not a string.\n    \"\"\"  # noqa: E501\n    if not isinstance(body, str):\n        raise TypeError(\n            f\"Bad body type. Expected str, got {type(body).__name__})\"\n        )\n\n    return HTTPResponse(\n        body, status=status, headers=headers, content_type=content_type\n    )\n\n\ndef raw(\n    body: AnyStr | None,\n    status: int = 200,\n    headers: dict[str, str] | None = None,\n    content_type: str = DEFAULT_HTTP_CONTENT_TYPE,\n) -> HTTPResponse:\n    \"\"\"Returns response object without encoding the body.\n\n    Args:\n        body (Optional[AnyStr]): Response data.\n        status (int, optional): HTTP response code. Defaults to `200`.\n        headers (Dict[str, str], optional): Custom HTTP headers. Defaults to `None`.\n        content_type (str, optional): The content type (string) of the response. Defaults to `\"application/octet-stream\"`.\n\n    Returns:\n        HTTPResponse: A response object without encoding the body.\n    \"\"\"  # noqa: E501\n    return HTTPResponse(\n        body=body,\n        status=status,\n        headers=headers,\n        content_type=content_type,\n    )\n\n\ndef html(\n    body: str | bytes | HTMLProtocol,\n    status: int = 200,\n    headers: dict[str, str] | None = None,\n) -> HTTPResponse:\n    \"\"\"Returns response object with body in html format.\n\n    Body should be a `str` or `bytes` like object, or an object with `__html__` or `_repr_html_`.\n\n    Args:\n        body (Union[str, bytes, HTMLProtocol]): Response data.\n        status (int, optional): HTTP response code. Defaults to `200`.\n        headers (Dict[str, str], optional): Custom HTTP headers. Defaults to `None`.\n\n    Returns:\n        HTTPResponse: A response object with body in html format.\n    \"\"\"  # noqa: E501\n    if not isinstance(body, (str, bytes)):\n        if hasattr(body, \"__html__\"):\n            body = body.__html__()\n        elif hasattr(body, \"_repr_html_\"):\n            body = body._repr_html_()\n\n    return HTTPResponse(\n        body,\n        status=status,\n        headers=headers,\n        content_type=\"text/html; charset=utf-8\",\n    )\n\n\nasync def validate_file(\n    request_headers: Header, last_modified: datetime | float | int\n) -> HTTPResponse | None:\n    \"\"\"Validate file based on request headers.\n\n    Args:\n        request_headers (Header): The request headers.\n        last_modified (Union[datetime, float, int]): The last modified date and time of the file.\n\n    Returns:\n        Optional[HTTPResponse]: A response object with status 304 if the file is not modified.\n    \"\"\"  # noqa: E501\n    try:\n        if_modified_since = request_headers.getone(\"If-Modified-Since\")\n    except KeyError:\n        return None\n    try:\n        if_modified_since = parsedate_to_datetime(if_modified_since)\n    except (TypeError, ValueError):\n        logger.warning(\n            \"Ignorning invalid If-Modified-Since header received: '%s'\",\n            if_modified_since,\n        )\n        return None\n    if not isinstance(last_modified, datetime):\n        last_modified = datetime.fromtimestamp(\n            float(last_modified), tz=timezone.utc\n        ).replace(microsecond=0)\n\n    if (\n        last_modified.utcoffset() is None\n        and if_modified_since.utcoffset() is not None\n    ):\n        logger.warning(\n            \"Cannot compare tz-aware and tz-naive datetimes. To avoid \"\n            \"this conflict Sanic is converting last_modified to UTC.\"\n        )\n        last_modified.replace(tzinfo=timezone.utc)\n    elif (\n        last_modified.utcoffset() is not None\n        and if_modified_since.utcoffset() is None\n    ):\n        logger.warning(\n            \"Cannot compare tz-aware and tz-naive datetimes. To avoid \"\n            \"this conflict Sanic is converting if_modified_since to UTC.\"\n        )\n        if_modified_since.replace(tzinfo=timezone.utc)\n    if last_modified.timestamp() <= if_modified_since.timestamp():\n        return HTTPResponse(status=304)\n\n    return None\n\n\nasync def file(\n    location: str | PurePath,\n    status: int = 200,\n    request_headers: Header | None = None,\n    validate_when_requested: bool = True,\n    mime_type: str | None = None,\n    headers: dict[str, str] | None = None,\n    filename: str | None = None,\n    last_modified: datetime | float | int | Default | None = _default,\n    max_age: float | int | None = None,\n    no_store: bool | None = None,\n    _range: Range | None = None,\n) -> HTTPResponse:\n    \"\"\"Return a response object with file data.\n\n    Args:\n        location (Union[str, PurePath]): Location of file on system.\n        status (int, optional): HTTP response code. Won't enforce the passed in status if only a part of the content will be sent (206) or file is being validated (304). Defaults to 200.\n        request_headers (Optional[Header], optional): The request headers.\n        validate_when_requested (bool, optional): If `True`, will validate the file when requested. Defaults to True.\n        mime_type (Optional[str], optional): Specific mime_type.\n        headers (Optional[Dict[str, str]], optional): Custom Headers.\n        filename (Optional[str], optional): Override filename.\n        last_modified (Optional[Union[datetime, float, int, Default]], optional): The last modified date and time of the file.\n        max_age (Optional[Union[float, int]], optional): Max age for cache control.\n        no_store (Optional[bool], optional): Any cache should not store this response. Defaults to None.\n        _range (Optional[Range], optional):\n\n    Returns:\n        HTTPResponse: The response object with the file data.\n    \"\"\"  # noqa: E501\n\n    if isinstance(last_modified, datetime):\n        last_modified = last_modified.replace(microsecond=0).timestamp()\n    elif isinstance(last_modified, Default):\n        stat = await stat_async(location)\n        last_modified = stat.st_mtime\n\n    if (\n        validate_when_requested\n        and request_headers is not None\n        and last_modified\n    ):\n        response = await validate_file(request_headers, last_modified)\n        if response:\n            return response\n\n    headers = headers or {}\n    if last_modified:\n        headers.setdefault(\n            \"Last-Modified\", formatdate(last_modified, usegmt=True)\n        )\n\n    if filename:\n        headers.setdefault(\n            \"Content-Disposition\", f'attachment; filename=\"{filename}\"'\n        )\n\n    if no_store:\n        cache_control = \"no-store\"\n    elif max_age:\n        cache_control = f\"public, max-age={max_age}\"\n        headers.setdefault(\n            \"expires\",\n            formatdate(\n                time() + max_age,\n                usegmt=True,\n            ),\n        )\n    else:\n        cache_control = \"no-cache\"\n\n    headers.setdefault(\"cache-control\", cache_control)\n\n    filename = filename or path.split(location)[-1]\n\n    async with await open_async(location, mode=\"rb\") as f:\n        if _range:\n            await f.seek(_range.start)\n            out_stream = await f.read(_range.size)\n            headers[\"Content-Range\"] = (\n                f\"bytes {_range.start}-{_range.end}/{_range.total}\"\n            )\n            status = 206\n        else:\n            out_stream = await f.read()\n\n    content_type = mime_type or guess_content_type(\n        filename, fallback=\"text/plain; charset=utf-8\"\n    )\n    return HTTPResponse(\n        body=out_stream,\n        status=status,\n        headers=headers,\n        content_type=content_type,\n    )\n\n\ndef redirect(\n    to: str,\n    headers: dict[str, str] | None = None,\n    status: int = 302,\n    content_type: str = \"text/html; charset=utf-8\",\n) -> HTTPResponse:\n    \"\"\"Cause a HTTP redirect (302 by default) by setting a Location header.\n\n    Args:\n        to (str): path or fully qualified URL to redirect to\n        headers (Optional[Dict[str, str]], optional): optional dict of headers to include in the new request. Defaults to None.\n        status (int, optional): status code (int) of the new request, defaults to 302. Defaults to 302.\n        content_type (str, optional): the content type (string) of the response. Defaults to \"text/html; charset=utf-8\".\n\n    Returns:\n        HTTPResponse: A response object with the redirect.\n    \"\"\"  # noqa: E501\n    headers = headers or {}\n\n    # URL Quote the URL before redirecting\n    safe_to = quote_plus(to, safe=\":/%#?&=@[]!$&'()*+,;\")\n\n    # According to RFC 7231, a relative URI is now permitted.\n    headers[\"Location\"] = safe_to\n\n    return HTTPResponse(\n        status=status, headers=headers, content_type=content_type\n    )\n\n\nasync def file_stream(\n    location: str | PurePath,\n    status: int = 200,\n    chunk_size: int = 4096,\n    mime_type: str | None = None,\n    headers: dict[str, str] | None = None,\n    filename: str | None = None,\n    _range: Range | None = None,\n) -> ResponseStream:\n    \"\"\"Return a streaming response object with file data.\n\n    :param location: Location of file on system.\n    :param chunk_size: The size of each chunk in the stream (in bytes)\n    :param mime_type: Specific mime_type.\n    :param headers: Custom Headers.\n    :param filename: Override filename.\n    :param _range:\n\n    Args:\n        location (Union[str, PurePath]): Location of file on system.\n        status (int, optional): HTTP response code. Won't enforce the passed in status if only a part of the content will be sent (206) or file is being validated (304). Defaults to `200`.\n        chunk_size (int, optional): The size of each chunk in the stream (in bytes). Defaults to `4096`.\n        mime_type (Optional[str], optional): Specific mime_type.\n        headers (Optional[Dict[str, str]], optional): Custom HTTP headers.\n        filename (Optional[str], optional): Override filename.\n        _range (Optional[Range], optional): The range of bytes to send.\n    \"\"\"  # noqa: E501\n    headers = headers or {}\n    if filename:\n        headers.setdefault(\n            \"Content-Disposition\", f'attachment; filename=\"{filename}\"'\n        )\n    filename = filename or path.split(location)[-1]\n    mime_type = mime_type or guess_type(filename)[0] or \"text/plain\"\n    if _range:\n        start = _range.start\n        end = _range.end\n        total = _range.total\n\n        headers[\"Content-Range\"] = f\"bytes {start}-{end}/{total}\"\n        status = 206\n\n    async def _streaming_fn(response):\n        async with await open_async(location, mode=\"rb\") as f:\n            if _range:\n                await f.seek(_range.start)\n                to_send = _range.size\n                while to_send > 0:\n                    content = await f.read(min((_range.size, chunk_size)))\n                    if len(content) < 1:\n                        break\n                    to_send -= len(content)\n                    await response.write(content)\n            else:\n                while True:\n                    content = await f.read(chunk_size)\n                    if len(content) < 1:\n                        break\n                    await response.write(content)\n\n    return ResponseStream(\n        streaming_fn=_streaming_fn,\n        status=status,\n        headers=headers,\n        content_type=mime_type,\n    )\n\n\ndef guess_content_type(\n    file_path: str | PurePath,\n    fallback: str = DEFAULT_HTTP_CONTENT_TYPE,\n) -> str:\n    \"\"\"Guess the content type (rather than MIME only) by the file extension.\"\"\"\n    mediatype = guess_type(file_path)[0]\n    if mediatype is None:\n        return fallback\n    if mediatype.startswith(\"text/\"):\n        return f\"{mediatype}; charset=utf-8\"\n    return mediatype\n"
  },
  {
    "path": "sanic/response/types.py",
    "content": "from __future__ import annotations\n\nfrom collections.abc import Coroutine, Iterator\nfrom datetime import datetime\nfrom typing import (\n    TYPE_CHECKING,\n    Any,\n    AnyStr,\n    Callable,\n    TypeVar,\n)\n\nfrom sanic.compat import Header\nfrom sanic.cookies import CookieJar\nfrom sanic.cookies.response import Cookie, SameSite\nfrom sanic.exceptions import SanicException, ServerError\nfrom sanic.helpers import Default, _default, has_message_body, json_dumps\nfrom sanic.http import Http\n\n\nif TYPE_CHECKING:\n    from sanic.asgi import ASGIApp\n    from sanic.http.http3 import HTTPReceiver\n    from sanic.request import Request\nelse:\n    Request = TypeVar(\"Request\")\n\n\nclass BaseHTTPResponse:\n    \"\"\"The base class for all HTTP Responses\"\"\"\n\n    __slots__ = (\n        \"asgi\",\n        \"body\",\n        \"content_type\",\n        \"stream\",\n        \"status\",\n        \"headers\",\n        \"_cookies\",\n    )\n\n    _dumps = json_dumps\n\n    def __init__(self):\n        self.asgi: bool = False\n        self.body: bytes | None = None\n        self.content_type: str | None = None\n        self.stream: Http | ASGIApp | HTTPReceiver | None = None\n        self.status: int = None\n        self.headers = Header({})\n        self._cookies: CookieJar | None = None\n\n    def __repr__(self):\n        class_name = self.__class__.__name__\n        return f\"<{class_name}: {self.status} {self.content_type}>\"\n\n    def _encode_body(self, data: str | bytes | None):\n        if data is None:\n            return b\"\"\n        return data.encode() if hasattr(data, \"encode\") else data  # type: ignore\n\n    @property\n    def cookies(self) -> CookieJar:\n        \"\"\"The response cookies.\n\n        See [Cookies](/en/guide/basics/cookies.html)\n\n        Returns:\n            CookieJar: The response cookies\n        \"\"\"\n        if self._cookies is None:\n            self._cookies = CookieJar(self.headers)\n        return self._cookies\n\n    @property\n    def processed_headers(self) -> Iterator[tuple[bytes, bytes]]:\n        \"\"\"Obtain a list of header tuples encoded in bytes for sending.\n\n        Add and remove headers based on status and content_type.\n\n        Returns:\n            Iterator[Tuple[bytes, bytes]]: A list of header tuples encoded in bytes for sending\n        \"\"\"  # noqa: E501\n        if has_message_body(self.status):\n            self.headers.setdefault(\"content-type\", self.content_type)\n        # Encode headers into bytes\n        return (\n            (name.encode(\"ascii\"), f\"{value}\".encode(errors=\"surrogateescape\"))\n            for name, value in self.headers.items()\n        )\n\n    async def send(\n        self,\n        data: AnyStr | None = None,\n        end_stream: bool | None = None,\n    ) -> None:\n        \"\"\"Send any pending response headers and the given data as body.\n\n        Args:\n            data (Optional[AnyStr], optional): str or bytes to be written. Defaults to `None`.\n            end_stream (Optional[bool], optional): whether to close the stream after this block. Defaults to `None`.\n        \"\"\"  # noqa: E501\n        if data is None and end_stream is None:\n            end_stream = True\n        if self.stream is None:\n            raise SanicException(\n                \"No stream is connected to the response object instance.\"\n            )\n        if self.stream.send is None:\n            if end_stream and not data:\n                return\n            raise ServerError(\n                \"Response stream was ended, no more response data is \"\n                \"allowed to be sent.\"\n            )\n        data = data.encode() if hasattr(data, \"encode\") else data or b\"\"  # type: ignore\n        await self.stream.send(\n            data,  # type: ignore\n            end_stream=end_stream or False,\n        )\n\n    def add_cookie(\n        self,\n        key: str,\n        value: str,\n        *,\n        path: str = \"/\",\n        domain: str | None = None,\n        secure: bool = True,\n        max_age: int | None = None,\n        expires: datetime | None = None,\n        httponly: bool = False,\n        samesite: SameSite | None = \"Lax\",\n        partitioned: bool = False,\n        comment: str | None = None,\n        host_prefix: bool = False,\n        secure_prefix: bool = False,\n    ) -> Cookie:\n        \"\"\"Add a cookie to the CookieJar\n\n        See [Cookies](/en/guide/basics/cookies.html)\n\n        Args:\n            key (str): The key to be added\n            value (str): The value to be added\n            path (str, optional): Path of the cookie. Defaults to `\"/\"`.\n            domain (Optional[str], optional): Domain of the cookie. Defaults to `None`.\n            secure (bool, optional): Whether the cookie is secure. Defaults to `True`.\n            max_age (Optional[int], optional): Max age of the cookie. Defaults to `None`.\n            expires (Optional[datetime], optional): Expiry date of the cookie. Defaults to `None`.\n            httponly (bool, optional): Whether the cookie is http only. Defaults to `False`.\n            samesite (Optional[SameSite], optional): SameSite policy of the cookie. Defaults to `\"Lax\"`.\n            partitioned (bool, optional): Whether the cookie is partitioned. Defaults to `False`.\n            comment (Optional[str], optional): Comment of the cookie. Defaults to `None`.\n            host_prefix (bool, optional): Whether to add __Host- as a prefix to the key. This requires that path=\"/\", domain=None, and secure=True. Defaults to `False`.\n            secure_prefix (bool, optional): Whether to add __Secure- as a prefix to the key. This requires that secure=True. Defaults to `False`.\n\n        Returns:\n            Cookie: The cookie that was added\n        \"\"\"  # noqa: E501\n        return self.cookies.add_cookie(\n            key=key,\n            value=value,\n            path=path,\n            domain=domain,\n            secure=secure,\n            max_age=max_age,\n            expires=expires,\n            httponly=httponly,\n            samesite=samesite,\n            partitioned=partitioned,\n            comment=comment,\n            host_prefix=host_prefix,\n            secure_prefix=secure_prefix,\n        )\n\n    def delete_cookie(\n        self,\n        key: str,\n        *,\n        path: str = \"/\",\n        domain: str | None = None,\n        host_prefix: bool = False,\n        secure_prefix: bool = False,\n    ) -> None:\n        \"\"\"Delete a cookie\n\n        This will effectively set it as Max-Age: 0, which a browser should\n        interpret it to mean: \"delete the cookie\".\n\n        Since it is a browser/client implementation, your results may vary\n        depending upon which client is being used.\n\n        See [Cookies](/en/guide/basics/cookies.html)\n\n        Args:\n            key (str): The key to be deleted\n            path (str, optional): Path of the cookie. Defaults to `\"/\"`.\n            domain (Optional[str], optional): Domain of the cookie. Defaults to `None`.\n            host_prefix (bool, optional): Whether to add __Host- as a prefix to the key. This requires that path=\"/\", domain=None, and secure=True. Defaults to `False`.\n            secure_prefix (bool, optional): Whether to add __Secure- as a prefix to the key. This requires that secure=True. Defaults to `False`.\n        \"\"\"  # noqa: E501\n        self.cookies.delete_cookie(\n            key=key,\n            path=path,\n            domain=domain,\n            host_prefix=host_prefix,\n            secure_prefix=secure_prefix,\n        )\n\n\nclass HTTPResponse(BaseHTTPResponse):\n    \"\"\"HTTP response to be sent back to the client.\n\n    Args:\n        body (Optional[Any], optional): The body content to be returned. Defaults to `None`.\n        status (int, optional): HTTP response number. Defaults to `200`.\n        headers (Optional[Union[Header, Dict[str, str]]], optional): Headers to be returned. Defaults to `None`.\n        content_type (Optional[str], optional): Content type to be returned (as a header). Defaults to `None`.\n    \"\"\"  # noqa: E501\n\n    __slots__ = ()\n\n    def __init__(\n        self,\n        body: Any = None,\n        status: int = 200,\n        headers: Header | dict[str, str] | None = None,\n        content_type: str | None = None,\n    ):\n        super().__init__()\n\n        self.content_type: str | None = content_type\n        self.body = self._encode_body(body)\n        self.status = status\n        self.headers = Header(headers or {})\n        self._cookies = None\n\n    async def eof(self):\n        \"\"\"Send a EOF (End of File) message to the client.\"\"\"\n        await self.send(\"\", True)\n\n    async def __aenter__(self):\n        return self.send\n\n    async def __aexit__(self, *_):\n        await self.eof()\n\n\nclass JSONResponse(HTTPResponse):\n    \"\"\"Convenience class for JSON responses\n\n    HTTP response to be sent back to the client, when the response\n    is of json type. Offers several utilities to manipulate common\n    json data types.\n\n    Args:\n        body (Optional[Any], optional): The body content to be returned. Defaults to `None`.\n        status (int, optional): HTTP response number. Defaults to `200`.\n        headers (Optional[Union[Header, Dict[str, str]]], optional): Headers to be returned. Defaults to `None`.\n        content_type (str, optional): Content type to be returned (as a header). Defaults to `\"application/json\"`.\n        dumps (Optional[Callable[..., AnyStr]], optional): The function to use for json encoding. Defaults to `None`.\n        **kwargs (Any, optional): The kwargs to pass to the json encoding function. Defaults to `{}`.\n    \"\"\"  # noqa: E501\n\n    __slots__ = (\n        \"_body\",\n        \"_body_manually_set\",\n        \"_initialized\",\n        \"_raw_body\",\n        \"_use_dumps\",\n        \"_use_dumps_kwargs\",\n    )\n\n    def __init__(\n        self,\n        body: Any = None,\n        status: int = 200,\n        headers: Header | dict[str, str] | None = None,\n        content_type: str = \"application/json\",\n        dumps: Callable[..., AnyStr] | None = None,\n        **kwargs: Any,\n    ):\n        self._initialized = False\n        self._body_manually_set = False\n\n        self._use_dumps: Callable[..., str | bytes] = (\n            dumps or BaseHTTPResponse._dumps\n        )\n        self._use_dumps_kwargs = kwargs\n\n        self._raw_body = body\n\n        super().__init__(\n            self._encode_body(self._use_dumps(body, **self._use_dumps_kwargs)),\n            headers=headers,\n            status=status,\n            content_type=content_type,\n        )\n\n        self._initialized = True\n\n    def _check_body_not_manually_set(self):\n        if self._body_manually_set:\n            raise SanicException(\n                \"Cannot use raw_body after body has been manually set.\"\n            )\n\n    @property\n    def raw_body(self) -> Any:\n        \"\"\"Returns the raw body, as long as body has not been manually set previously.\n\n        NOTE: This object should not be mutated, as it will not be\n        reflected in the response body. If you need to mutate the\n        response body, consider using one of the provided methods in\n        this class or alternatively call set_body() with the mutated\n        object afterwards or set the raw_body property to it.\n\n        Returns:\n            Optional[Any]: The raw body\n        \"\"\"  # noqa: E501\n        self._check_body_not_manually_set()\n        return self._raw_body\n\n    @raw_body.setter\n    def raw_body(self, value: Any):\n        self._body_manually_set = False\n        self._body = self._encode_body(\n            self._use_dumps(value, **self._use_dumps_kwargs)\n        )\n        self._raw_body = value\n\n    @property  # type: ignore\n    def body(self) -> bytes | None:  # type: ignore\n        \"\"\"Returns the response body.\n\n        Returns:\n            Optional[bytes]: The response body\n        \"\"\"\n        return self._body\n\n    @body.setter\n    def body(self, value: bytes | None):\n        self._body = value\n        if not self._initialized:\n            return\n        self._body_manually_set = True\n\n    def set_body(\n        self,\n        body: Any,\n        dumps: Callable[..., AnyStr] | None = None,\n        **dumps_kwargs: Any,\n    ) -> None:\n        \"\"\"Set the response body to the given value, using the given dumps function\n\n        Sets a new response body using the given dumps function\n        and kwargs, or falling back to the defaults given when\n        creating the object if none are specified.\n\n        Args:\n            body (Any): The body to set\n            dumps (Optional[Callable[..., AnyStr]], optional): The function to use for json encoding. Defaults to `None`.\n            **dumps_kwargs (Any, optional): The kwargs to pass to the json encoding function. Defaults to `{}`.\n\n        Examples:\n            ```python\n            response = JSONResponse({\"foo\": \"bar\"})\n            response.set_body({\"bar\": \"baz\"})\n            assert response.body == b'{\"bar\": \"baz\"}'\n            ```\n        \"\"\"  # noqa: E501\n        self._body_manually_set = False\n        self._raw_body = body\n\n        use_dumps = dumps or self._use_dumps\n        use_dumps_kwargs = dumps_kwargs if dumps else self._use_dumps_kwargs\n\n        self._body = self._encode_body(use_dumps(body, **use_dumps_kwargs))\n\n    def append(self, value: Any) -> None:\n        \"\"\"Appends a value to the response raw_body, ensuring that body is kept up to date.\n\n        This can only be used if raw_body is a list.\n\n        Args:\n            value (Any): The value to append\n\n        Raises:\n            SanicException: If the body is not a list\n        \"\"\"  # noqa: E501\n\n        self._check_body_not_manually_set()\n\n        if not isinstance(self._raw_body, list):\n            raise SanicException(\"Cannot append to a non-list object.\")\n\n        self._raw_body.append(value)\n        self.raw_body = self._raw_body\n\n    def extend(self, value: Any) -> None:\n        \"\"\"Extends the response's raw_body with the given values, ensuring that body is kept up to date.\n\n        This can only be used if raw_body is a list.\n\n        Args:\n            value (Any): The values to extend with\n\n        Raises:\n            SanicException: If the body is not a list\n        \"\"\"  # noqa: E501\n\n        self._check_body_not_manually_set()\n\n        if not isinstance(self._raw_body, list):\n            raise SanicException(\"Cannot extend a non-list object.\")\n\n        self._raw_body.extend(value)\n        self.raw_body = self._raw_body\n\n    def update(self, *args, **kwargs) -> None:\n        \"\"\"Updates the response's raw_body with the given values, ensuring that body is kept up to date.\n\n        This can only be used if raw_body is a dict.\n\n        Args:\n            *args: The args to update with\n            **kwargs: The kwargs to update with\n\n        Raises:\n            SanicException: If the body is not a dict\n        \"\"\"  # noqa: E501\n\n        self._check_body_not_manually_set()\n\n        if not isinstance(self._raw_body, dict):\n            raise SanicException(\"Cannot update a non-dict object.\")\n\n        self._raw_body.update(*args, **kwargs)\n        self.raw_body = self._raw_body\n\n    def pop(self, key: Any, default: Any = _default) -> Any:\n        \"\"\"Pops a key from the response's raw_body, ensuring that body is kept up to date.\n\n        This can only be used if raw_body is a dict or a list.\n\n        Args:\n            key (Any): The key to pop\n            default (Any, optional): The default value to return if the key is not found. Defaults to `_default`.\n\n        Raises:\n            SanicException: If the body is not a dict or a list\n            TypeError: If the body is a list and a default value is provided\n\n        Returns:\n            Any: The value that was popped\n        \"\"\"  # noqa: E501\n\n        self._check_body_not_manually_set()\n\n        if not isinstance(self._raw_body, (list, dict)):\n            raise SanicException(\n                \"Cannot pop from a non-list and non-dict object.\"\n            )\n\n        if isinstance(default, Default):\n            value = self._raw_body.pop(key)\n        elif isinstance(self._raw_body, list):\n            raise TypeError(\"pop doesn't accept a default argument for lists\")\n        else:\n            value = self._raw_body.pop(key, default)\n\n        self.raw_body = self._raw_body\n\n        return value\n\n\nclass ResponseStream:\n    \"\"\"A compat layer to bridge the gap after the deprecation of StreamingHTTPResponse.\n\n    It will be removed when:\n    - file_stream is moved to new style streaming\n    - file and file_stream are combined into a single API\n    \"\"\"  # noqa: E501\n\n    __slots__ = (\n        \"_cookies\",\n        \"content_type\",\n        \"headers\",\n        \"request\",\n        \"response\",\n        \"status\",\n        \"streaming_fn\",\n    )\n\n    def __init__(\n        self,\n        streaming_fn: Callable[\n            [BaseHTTPResponse | ResponseStream],\n            Coroutine[Any, Any, None],\n        ],\n        status: int = 200,\n        headers: Header | dict[str, str] | None = None,\n        content_type: str | None = None,\n    ):\n        if headers is None:\n            headers = Header()\n        elif not isinstance(headers, Header):\n            headers = Header(headers)\n        self.streaming_fn = streaming_fn\n        self.status = status\n        self.headers = headers or Header()\n        self.content_type = content_type\n        self.request: Request | None = None\n        self._cookies: CookieJar | None = None\n\n    async def write(self, message: str):\n        await self.response.send(message)\n\n    async def stream(self) -> HTTPResponse:\n        if not self.request:\n            raise ServerError(\"Attempted response to unknown request\")\n        self.response = await self.request.respond(\n            headers=self.headers,\n            status=self.status,\n            content_type=self.content_type,\n        )\n        await self.streaming_fn(self)\n        return self.response\n\n    async def eof(self) -> None:\n        await self.response.eof()\n\n    @property\n    def cookies(self) -> CookieJar:\n        if self._cookies is None:\n            self._cookies = CookieJar(self.headers)\n        return self._cookies\n\n    @property\n    def processed_headers(self):\n        return self.response.processed_headers\n\n    @property\n    def body(self):\n        return self.response.body\n\n    def __call__(self, request: Request) -> ResponseStream:\n        self.request = request\n        return self\n\n    def __await__(self):\n        return self.stream().__await__()\n"
  },
  {
    "path": "sanic/router.py",
    "content": "from __future__ import annotations\n\nfrom collections.abc import Iterable\nfrom functools import lru_cache\nfrom inspect import signature\nfrom typing import Any\nfrom uuid import UUID\n\nfrom sanic_routing import BaseRouter\nfrom sanic_routing.exceptions import NoMethod\nfrom sanic_routing.exceptions import NotFound as RoutingNotFound\nfrom sanic_routing.group import RouteGroup\nfrom sanic_routing.route import Route\n\nfrom sanic.constants import HTTP_METHODS\nfrom sanic.errorpages import check_error_format\nfrom sanic.exceptions import MethodNotAllowed, NotFound, SanicException\nfrom sanic.models.handler_types import RouteHandler\n\n\nROUTER_CACHE_SIZE = 1024\nALLOWED_LABELS = (\"__file_uri__\",)\n\n\nclass Router(BaseRouter):\n    \"\"\"The router implementation responsible for routing a `Request` object to the appropriate handler.\"\"\"  # noqa: E501\n\n    DEFAULT_METHOD = \"GET\"\n    ALLOWED_METHODS = HTTP_METHODS\n\n    def _get(\n        self, path: str, method: str, host: str | None\n    ) -> tuple[Route, RouteHandler, dict[str, Any]]:\n        try:\n            return self.resolve(\n                path=path,\n                method=method,\n                extra={\"host\": host} if host else None,\n            )\n        except RoutingNotFound as e:\n            raise NotFound(f\"Requested URL {e.path} not found\") from None\n        except NoMethod as e:\n            raise MethodNotAllowed(\n                f\"Method {method} not allowed for URL {path}\",\n                method=method,\n                allowed_methods=tuple(e.allowed_methods)\n                if e.allowed_methods\n                else None,\n            ) from None\n\n    @lru_cache(maxsize=ROUTER_CACHE_SIZE)\n    def get(  # type: ignore\n        self, path: str, method: str, host: str | None\n    ) -> tuple[Route, RouteHandler, dict[str, Any]]:\n        \"\"\"Retrieve a `Route` object containing the details about how to handle a response for a given request\n\n        :param request: the incoming request object\n        :type request: Request\n        :return: details needed for handling the request and returning the\n            correct response\n        :rtype: Tuple[ Route, RouteHandler, Dict[str, Any]]\n\n        Args:\n            path (str): the path of the route\n            method (str): the HTTP method of the route\n            host (Optional[str]): the host of the route\n\n        Raises:\n            NotFound: if the route is not found\n            MethodNotAllowed: if the method is not allowed for the route\n\n        Returns:\n            Tuple[Route, RouteHandler, Dict[str, Any]]: the route, handler, and match info\n        \"\"\"  # noqa: E501\n        __tracebackhide__ = True\n        return self._get(path, method, host)\n\n    def add(  # type: ignore\n        self,\n        uri: str,\n        methods: Iterable[str],\n        handler: RouteHandler,\n        host: str | Iterable[str] | None = None,\n        strict_slashes: bool = False,\n        stream: bool = False,\n        ignore_body: bool = False,\n        version: str | float | int | None = None,\n        name: str | None = None,\n        unquote: bool = False,\n        static: bool = False,\n        version_prefix: str = \"/v\",\n        overwrite: bool = False,\n        error_format: str | None = None,\n    ) -> Route | list[Route]:\n        \"\"\"Add a handler to the router\n\n        Args:\n            uri (str): The path of the route.\n            methods (Iterable[str]): The types of HTTP methods that should be attached,\n                example: [\"GET\", \"POST\", \"OPTIONS\"].\n            handler (RouteHandler): The sync or async function to be executed.\n            host (Optional[str], optional): Host that the route should be on. Defaults to None.\n            strict_slashes (bool, optional): Whether to apply strict slashes. Defaults to False.\n            stream (bool, optional): Whether to stream the response. Defaults to False.\n            ignore_body (bool, optional): Whether the incoming request body should be read.\n                Defaults to False.\n            version (Union[str, float, int], optional): A version modifier for the uri. Defaults to None.\n            name (Optional[str], optional): An identifying name of the route. Defaults to None.\n\n        Returns:\n            Route: The route object.\n        \"\"\"  # noqa: E501\n\n        if version is not None:\n            version = str(version).strip(\"/\").lstrip(\"v\")\n            uri = \"/\".join([f\"{version_prefix}{version}\", uri.lstrip(\"/\")])\n\n        uri = self._normalize(uri, handler)\n\n        params = dict(\n            path=uri,\n            handler=handler,\n            methods=frozenset(map(str, methods)) if methods else None,\n            name=name,\n            strict=strict_slashes,\n            unquote=unquote,\n            overwrite=overwrite,\n        )\n\n        if isinstance(host, str):\n            hosts = [host]\n        else:\n            hosts = host or [None]  # type: ignore\n\n        routes = []\n\n        for host in hosts:\n            if host:\n                params.update({\"requirements\": {\"host\": host}})\n\n            ident = name\n            if len(hosts) > 1:\n                ident = (\n                    f\"{name}_{host.replace('.', '_')}\"\n                    if name\n                    else \"__unnamed__\"\n                )\n\n            route = super().add(**params)  # type: ignore\n            route.extra.ident = ident\n            route.extra.ignore_body = ignore_body\n            route.extra.stream = stream\n            route.extra.hosts = hosts\n            route.extra.static = static\n            route.extra.error_format = error_format\n\n            if error_format:\n                check_error_format(route.extra.error_format)\n\n            routes.append(route)\n\n        if len(routes) == 1:\n            return routes[0]\n        return routes\n\n    @lru_cache(maxsize=ROUTER_CACHE_SIZE)\n    def find_route_by_view_name(\n        self, view_name: str, name: str | None = None\n    ) -> Route | None:\n        \"\"\"Find a route in the router based on the specified view name.\n\n        Args:\n            view_name (str): the name of the view to search for\n            name (Optional[str], optional): the name of the route. Defaults to `None`.\n\n        Returns:\n            Optional[Route]: the route object\n        \"\"\"  # noqa: E501\n        if not view_name:\n            return None\n\n        route = self.name_index.get(view_name)\n        if not route:\n            full_name = self.ctx.app.generate_name(view_name)\n            route = self.name_index.get(full_name)\n\n        if not route:\n            return None\n\n        return route\n\n    @property\n    def routes_all(self) -> dict[tuple[str, ...], Route]:\n        \"\"\"Return all routes in the router.\n\n        Returns:\n            Dict[Tuple[str, ...], Route]: a dictionary of routes\n        \"\"\"\n        return {route.parts: route for route in self.routes}\n\n    @property\n    def routes_static(self) -> dict[tuple[str, ...], RouteGroup]:\n        \"\"\"Return all static routes in the router.\n\n        _In this context \"static\" routes do not refer to the `app.static()`\n        method. Instead, they refer to routes that do not contain\n        any path parameters._\n\n        Returns:\n            Dict[Tuple[str, ...], Route]: a dictionary of routes\n        \"\"\"\n        return self.static_routes\n\n    @property\n    def routes_dynamic(self) -> dict[tuple[str, ...], RouteGroup]:\n        \"\"\"Return all dynamic routes in the router.\n\n        _Dynamic routes are routes that contain path parameters._\n\n        Returns:\n            Dict[Tuple[str, ...], Route]: a dictionary of routes\n        \"\"\"\n        return self.dynamic_routes\n\n    @property\n    def routes_regex(self) -> dict[tuple[str, ...], RouteGroup]:\n        \"\"\"Return all regex routes in the router.\n\n        _Regex routes are routes that contain path parameters with regex\n        expressions, or otherwise need regex to resolve._\n\n        Returns:\n            Dict[Tuple[str, ...], Route]: a dictionary of routes\n        \"\"\"\n        return self.regex_routes\n\n    def finalize(self, *args, **kwargs) -> None:\n        \"\"\"Finalize the router.\n\n        Raises:\n            SanicException: if a route contains a parameter name that starts with \"__\" and is not in ALLOWED_LABELS\n        \"\"\"  # noqa: E501\n        super().finalize(*args, **kwargs)\n\n        for route in self.dynamic_routes.values():\n            if any(\n                label.startswith(\"__\") and label not in ALLOWED_LABELS\n                for label in route.labels\n            ):\n                raise SanicException(\n                    f\"Invalid route: {route}. Parameter names cannot use '__'.\"\n                )\n\n    def _normalize(self, uri: str, handler: RouteHandler) -> str:\n        if \"<\" not in uri:\n            return uri\n\n        sig = signature(handler)\n        mapping = {\n            param.name: param.annotation.__name__.lower()\n            for param in sig.parameters.values()\n            if param.annotation in (str, int, float, UUID)\n        }\n\n        reconstruction = []\n        for part in uri.split(\"/\"):\n            if part.startswith(\"<\") and \":\" not in part:\n                name = part[1:-1]\n                annotation = mapping.get(name)\n                if annotation:\n                    part = f\"<{name}:{annotation}>\"\n            reconstruction.append(part)\n        return \"/\".join(reconstruction)\n"
  },
  {
    "path": "sanic/server/__init__.py",
    "content": "from sanic.models.server_types import ConnInfo, Signal\nfrom sanic.server.async_server import AsyncioServer\nfrom sanic.server.loop import try_use_uvloop\nfrom sanic.server.protocols.http_protocol import HttpProtocol\nfrom sanic.server.runners import serve\n\n\n__all__ = (\n    \"AsyncioServer\",\n    \"ConnInfo\",\n    \"HttpProtocol\",\n    \"Signal\",\n    \"serve\",\n    \"try_use_uvloop\",\n)\n"
  },
  {
    "path": "sanic/server/async_server.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom sanic.exceptions import SanicException\n\n\nif TYPE_CHECKING:\n    from sanic import Sanic\n\n\nclass AsyncioServer:\n    \"\"\"Wraps an asyncio server with functionality that might be useful to a user who needs to manage the server lifecycle manually.\"\"\"  # noqa: E501\n\n    __slots__ = (\"app\", \"connections\", \"loop\", \"serve_coro\", \"server\")\n\n    def __init__(\n        self,\n        app: Sanic,\n        loop,\n        serve_coro,\n        connections,\n    ):\n        # Note, Sanic already called \"before_server_start\" events\n        # before this helper was even created. So we don't need it here.\n        self.app = app\n        self.connections = connections\n        self.loop = loop\n        self.serve_coro = serve_coro\n        self.server = None\n\n    def startup(self):\n        \"\"\"Trigger \"startup\" operations on the app\"\"\"\n        return self.app._startup()\n\n    def before_start(self):\n        \"\"\"Trigger \"before_server_start\" events\"\"\"\n        return self._server_event(\"init\", \"before\")\n\n    def after_start(self):\n        \"\"\"Trigger \"after_server_start\" events\"\"\"\n        return self._server_event(\"init\", \"after\")\n\n    def before_stop(self):\n        \"\"\"Trigger \"before_server_stop\" events\"\"\"\n        return self._server_event(\"shutdown\", \"before\")\n\n    def after_stop(self):\n        \"\"\"Trigger \"after_server_stop\" events\"\"\"\n        return self._server_event(\"shutdown\", \"after\")\n\n    def is_serving(self) -> bool:\n        \"\"\"Returns True if the server is running, False otherwise\"\"\"\n        if self.server:\n            return self.server.is_serving()\n        return False\n\n    def wait_closed(self):\n        \"\"\"Wait until the server is closed\"\"\"\n        if self.server:\n            return self.server.wait_closed()\n\n    def close(self):\n        \"\"\"Close the server\"\"\"\n        if self.server:\n            self.server.close()\n            coro = self.wait_closed()\n            task = self.loop.create_task(coro)\n            return task\n\n    def start_serving(self):\n        \"\"\"Start serving requests\"\"\"\n        return self._serve(self.server.start_serving)\n\n    def serve_forever(self):\n        \"\"\"Serve requests until the server is stopped\"\"\"\n        return self._serve(self.server.serve_forever)\n\n    def _serve(self, serve_func):\n        if self.server:\n            if not self.app.state.is_started:\n                raise SanicException(\n                    \"Cannot run Sanic server without first running \"\n                    \"await server.startup()\"\n                )\n\n            try:\n                return serve_func()\n            except AttributeError:\n                name = serve_func.__name__\n                raise NotImplementedError(\n                    f\"server.{name} not available in this version \"\n                    \"of asyncio or uvloop.\"\n                )\n\n    def _server_event(self, concern: str, action: str):\n        if not self.app.state.is_started:\n            raise SanicException(\n                \"Cannot dispatch server event without \"\n                \"first running await server.startup()\"\n            )\n        return self.app._server_event(concern, action, loop=self.loop)\n\n    def __await__(self):\n        \"\"\"\n        Starts the asyncio server, returns AsyncServerCoro\n        \"\"\"\n        task = self.loop.create_task(self.serve_coro)\n        while not task.done():\n            yield\n        self.server = task.result()\n        return self\n"
  },
  {
    "path": "sanic/server/events.py",
    "content": "from __future__ import annotations\n\nfrom collections.abc import Iterable\nfrom inspect import isawaitable\nfrom typing import TYPE_CHECKING, Any, Callable\n\n\nif TYPE_CHECKING:\n    from sanic import Sanic\n\n\ndef trigger_events(\n    events: Iterable[Callable[..., Any]] | None,\n    loop,\n    app: Sanic | None = None,\n    **kwargs,\n):\n    \"\"\"Trigger event callbacks (functions or async)\n\n    Args:\n        events (Optional[Iterable[Callable[..., Any]]]): [description]\n        loop ([type]): [description]\n        app (Optional[Sanic], optional): [description]. Defaults to None.\n    \"\"\"\n    if events:\n        for event in events:\n            try:\n                result = event(**kwargs) if not app else event(app, **kwargs)\n            except TypeError:\n                result = (\n                    event(loop, **kwargs)\n                    if not app\n                    else event(app, loop, **kwargs)\n                )\n            if isawaitable(result):\n                loop.run_until_complete(result)\n"
  },
  {
    "path": "sanic/server/goodbye.py",
    "content": "# flake8: noqa: E501\n\nimport random\nimport sys\n\n\n# fmt: off\nascii_phrases = {\n    'Farewell', 'Bye', 'See you later', 'Take care', 'So long', 'Adieu', 'Cheerio',\n    'Goodbye', 'Adios', 'Au revoir', 'Arrivederci', 'Sayonara', 'Auf Wiedersehen',\n    'Do svidaniya', 'Annyeong', 'Tot ziens', 'Ha det', 'Selamat tinggal',\n    'Hasta luego', 'Nos vemos', 'Salut', 'Ciao', 'A presto',\n    'Dag', 'Tot later', 'Vi ses', 'Sampai jumpa',\n}\n\nnon_ascii_phrases = {\n    'Tschüss', 'Zài jiàn', 'Bāi bāi', 'Míngtiān jiàn', 'Adeus', 'Tchau', 'Até logo',\n    'Hejdå', 'À bientôt', 'Bis später', 'Adjø',\n    'じゃね', 'またね', '안녕히 계세요', '잘 가', 'שלום',\n    'להתראות', 'مع السلامة', 'إلى اللقاء', 'وداعاً', 'अलविदा',\n    'फिर मिलेंगे',\n}\n\nall_phrases = ascii_phrases | non_ascii_phrases\n# fmt: on\n\n\ndef get_goodbye() -> str:  # pragma: no cover\n    is_utf8 = sys.stdout.encoding.lower() == \"utf-8\"\n    phrases = all_phrases if is_utf8 else ascii_phrases\n    return random.choice(list(phrases))  # nosec: B311\n"
  },
  {
    "path": "sanic/server/loop.py",
    "content": "import asyncio\n\nfrom os import getenv\n\nfrom sanic.compat import OS_IS_WINDOWS\nfrom sanic.log import error_logger\nfrom sanic.utils import str_to_bool\n\n\ndef try_use_uvloop() -> None:\n    \"\"\"Use uvloop instead of the default asyncio loop.\"\"\"\n    if OS_IS_WINDOWS:\n        error_logger.warning(\n            \"You are trying to use uvloop, but uvloop is not compatible \"\n            \"with your system. You can disable uvloop completely by setting \"\n            \"the 'USE_UVLOOP' configuration value to false, or simply not \"\n            \"defining it and letting Sanic handle it for you. Sanic will now \"\n            \"continue to run using the default event loop.\"\n        )\n        return\n\n    try:\n        import uvloop  # type: ignore\n    except ImportError:\n        error_logger.warning(\n            \"You are trying to use uvloop, but uvloop is not \"\n            \"installed in your system. In order to use uvloop \"\n            \"you must first install it. Otherwise, you can disable \"\n            \"uvloop completely by setting the 'USE_UVLOOP' \"\n            \"configuration value to false. Sanic will now continue \"\n            \"to run with the default event loop.\"\n        )\n        return\n\n    uvloop_install_removed = str_to_bool(getenv(\"SANIC_NO_UVLOOP\", \"no\"))\n    if uvloop_install_removed:\n        error_logger.info(\n            \"You are requesting to run Sanic using uvloop, but the \"\n            \"install-time 'SANIC_NO_UVLOOP' environment variable (used to \"\n            \"opt-out of installing uvloop with Sanic) is set to true. If \"\n            \"you want to prevent Sanic from overriding the event loop policy \"\n            \"during runtime, set the 'USE_UVLOOP' configuration value to \"\n            \"false.\"\n        )\n\n    if not isinstance(asyncio.get_event_loop_policy(), uvloop.EventLoopPolicy):\n        asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())\n\n\ndef try_windows_loop():\n    \"\"\"Try to use the WindowsSelectorEventLoopPolicy instead of the default\"\"\"\n    if not OS_IS_WINDOWS:\n        error_logger.warning(\n            \"You are trying to use an event loop policy that is not \"\n            \"compatible with your system. You can simply let Sanic handle \"\n            \"selecting the best loop for you. Sanic will now continue to run \"\n            \"using the default event loop.\"\n        )\n        return\n\n    if not isinstance(\n        asyncio.get_event_loop_policy(), asyncio.WindowsSelectorEventLoopPolicy\n    ):\n        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())\n"
  },
  {
    "path": "sanic/server/protocols/__init__.py",
    "content": ""
  },
  {
    "path": "sanic/server/protocols/base_protocol.py",
    "content": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom sanic.exceptions import RequestCancelled\n\n\nif TYPE_CHECKING:\n    from sanic.app import Sanic\n\nimport asyncio\n\nfrom asyncio.transports import Transport\nfrom time import monotonic as current_time\n\nfrom sanic.log import error_logger\nfrom sanic.models.server_types import ConnInfo, Signal\n\n\nclass SanicProtocol(asyncio.Protocol):\n    __slots__ = (\n        \"app\",\n        # event loop, connection\n        \"loop\",\n        \"transport\",\n        \"connections\",\n        \"conn_info\",\n        \"signal\",\n        \"_can_write\",\n        \"_time\",\n        \"_task\",\n        \"_unix\",\n        \"_data_received\",\n    )\n\n    def __init__(\n        self,\n        *,\n        loop,\n        app: Sanic,\n        signal=None,\n        connections=None,\n        unix=None,\n        **kwargs,\n    ):\n        asyncio.set_event_loop(loop)\n        self.loop = loop\n        self.app: Sanic = app\n        self.signal = signal or Signal()\n        self.transport: Transport | None = None\n        self.connections = connections if connections is not None else set()\n        self.conn_info: ConnInfo | None = None\n        self._can_write = asyncio.Event()\n        self._can_write.set()\n        self._unix = unix\n        self._time = 0.0  # type: float\n        self._task: asyncio.Task | None = None\n        self._data_received = asyncio.Event()\n\n    @property\n    def ctx(self):\n        if self.conn_info is not None:\n            return self.conn_info.ctx\n        else:\n            return None\n\n    async def send(self, data):\n        \"\"\"\n        Generic data write implementation with backpressure control.\n        \"\"\"\n        await self._can_write.wait()\n        if self.transport.is_closing():\n            raise RequestCancelled\n        self.transport.write(data)\n        self._time = current_time()\n\n    async def receive_more(self):\n        \"\"\"\n        Wait until more data is received into the Server protocol's buffer\n        \"\"\"\n        self.transport.resume_reading()\n        self._data_received.clear()\n        await self._data_received.wait()\n\n    def close(self, timeout: float | None = None):\n        \"\"\"\n        Attempt close the connection.\n        \"\"\"\n        if self.transport is None or self.transport.is_closing():\n            # do not attempt to close again, already aborted or closing\n            return\n\n        # Check if write is already paused _before_ close() is called.\n        write_was_paused = not self._can_write.is_set()\n        # Trigger the UVLoop Stream Transport Close routine\n        # Causes a call to connection_lost where further cleanup occurs\n        # Close may fully close the connection now, but if there is still\n        # data in the libuv buffer, then close becomes an async operation\n        self.transport.close()\n        try:\n            # Check write-buffer data left _after_ close is called.\n            # in UVLoop, get the data in the libuv transport write-buffer\n            data_left = self.transport.get_write_buffer_size()\n        # Some asyncio implementations don't support get_write_buffer_size\n        except (AttributeError, NotImplementedError):\n            data_left = 0\n        if write_was_paused or data_left > 0:\n            # don't call resume_writing here, it gets called by the transport\n            # to unpause the protocol when it is ready for more data\n\n            # Schedule the async close checker, to close the connection\n            # after the transport is done, and clean everything up.\n            if timeout is None:\n                # This close timeout needs to be less than the graceful\n                # shutdown timeout. The graceful shutdown _could_ be waiting\n                # for this transport to close before shutting down the app.\n                timeout = self.app.config.GRACEFUL_TCP_CLOSE_TIMEOUT\n                # This is 5s by default.\n        else:\n            # Schedule the async close checker but with no timeout,\n            # this will ensure abort() is called if required.\n            if timeout is None:\n                timeout = 0\n        self.loop.call_soon(\n            _async_protocol_transport_close,\n            self,\n            self.loop,\n            timeout,\n        )\n\n    def abort(self):\n        \"\"\"\n        Force close the connection.\n        \"\"\"\n        # Cause a call to connection_lost where further cleanup occurs\n        if self.transport:\n            self.transport.abort()\n            self.transport = None\n\n    # asyncio.Protocol API Callbacks #\n    # ------------------------------ #\n    def connection_made(self, transport):\n        \"\"\"\n        Generic connection-made, with no connection_task, and no recv_buffer.\n        Override this for protocol-specific connection implementations.\n        \"\"\"\n        try:\n            transport.set_write_buffer_limits(low=16384, high=65536)\n            self.connections.add(self)\n            self.transport = transport\n            self.conn_info = ConnInfo(self.transport, unix=self._unix)\n        except Exception:\n            error_logger.exception(\"protocol.connect_made\")\n\n    def connection_lost(self, exc):\n        \"\"\"\n        This is a callback handler that is called from the asyncio\n        transport layer implementation (eg, UVLoop's UVStreamTransport).\n        It is scheduled to be called async after the transport has closed.\n        When data is still in the send buffer, this call to connection_lost\n        will be delayed until _after_ the buffer is finished being sent.\n\n        So we can use this callback as a confirmation callback\n        that the async write-buffer transfer is finished.\n        \"\"\"\n        try:\n            self.connections.discard(self)\n            # unblock the send queue if it is paused,\n            # this allows the route handler to see\n            # the CancelledError exception\n            self.resume_writing()\n            self.conn_info.lost = True\n            if self._task:\n                self._task.cancel()\n        except BaseException:\n            error_logger.exception(\"protocol.connection_lost\")\n\n    def pause_writing(self):\n        self._can_write.clear()\n\n    def resume_writing(self):\n        self._can_write.set()\n\n    def data_received(self, data: bytes):\n        try:\n            self._time = current_time()\n            if not data:\n                return self.close()\n\n            if self._data_received:\n                self._data_received.set()\n        except BaseException:\n            error_logger.exception(\"protocol.data_received\")\n\n\ndef _async_protocol_transport_close(\n    protocol: SanicProtocol,\n    loop: asyncio.AbstractEventLoop,\n    timeout: float,\n):\n    \"\"\"\n    This function is scheduled to be called after close() is called.\n    It checks that the transport has shut down properly, or waits\n    for any remaining data to be sent, and aborts after a timeout.\n    This is required if the transport is closed while there is an async\n    large async transport write operation in progress.\n    This is observed when NGINX reverse-proxy is the client.\n    \"\"\"\n    if protocol.transport is None:\n        # abort() is the only method that can make\n        # protocol.transport be None, so abort was already called\n        return\n    # protocol.connection_lost does not set protocol.transport to None\n    # so to detect it a different way with conninfo.lost\n    elif protocol.conn_info is not None and protocol.conn_info.lost:\n        # Terminus. Most connections finish the protocol here!\n        # Connection_lost callback was executed already,\n        # so transport did complete and close itself properly.\n        # No need to call abort().\n\n        # This is the last part of cleanup to do\n        # that is not done by connection_lost handler.\n        # Ensure transport is cleaned up by GC.\n        protocol.transport = None\n        return\n    elif not protocol.transport.is_closing():\n        raise RuntimeError(\n            \"You must call transport.close() before \"\n            \"protocol._async_transport_close() runs.\"\n        )\n\n    write_is_paused = not protocol._can_write.is_set()\n    try:\n        # in UVLoop, get the data in the libuv write-buffer\n        data_left = protocol.transport.get_write_buffer_size()\n    # Some asyncio implementations don't support get_write_buffer_size\n    except (AttributeError, NotImplementedError):\n        data_left = 0\n    if write_is_paused or data_left > 0:\n        # don't need to call resume_writing here to unpause\n        if timeout <= 0:\n            # timeout is 0 or less, so we can simply abort now\n            loop.call_soon(SanicProtocol.abort, protocol)\n        else:\n            next_check_interval = min(timeout, 0.1)\n            next_check_timeout = timeout - next_check_interval\n            loop.call_later(\n                # Recurse back in after the timeout, to check again\n                next_check_interval,\n                # this next time with reduced timeout.\n                _async_protocol_transport_close,\n                protocol,\n                loop,\n                next_check_timeout,\n            )\n    else:\n        # Not paused, and no data left in the buffer, but transport\n        # is still open, connection_lost has not been called yet.\n        # We can call abort() to fix that.\n        loop.call_soon(SanicProtocol.abort, protocol)\n"
  },
  {
    "path": "sanic/server/protocols/http_protocol.py",
    "content": "from __future__ import annotations\n\nimport sys\n\nfrom typing import TYPE_CHECKING\n\nfrom sanic.http.constants import HTTP\nfrom sanic.http.http3 import Http3\nfrom sanic.touchup.meta import TouchUpMeta\n\n\nif TYPE_CHECKING:\n    from sanic.app import Sanic\n\n\nfrom asyncio import CancelledError\nfrom time import monotonic as current_time\n\nfrom sanic.exceptions import (\n    RequestCancelled,\n    RequestTimeout,\n    ServiceUnavailable,\n)\nfrom sanic.http import Http, Stage\nfrom sanic.log import (\n    Colors,\n    access_logger,\n    error_logger,\n    logger,\n    websockets_logger,\n)\nfrom sanic.models.server_types import ConnInfo\nfrom sanic.request import Request\nfrom sanic.server.protocols.base_protocol import SanicProtocol\n\n\nConnectionProtocol = type(\"ConnectionProtocol\", (), {})\ntry:\n    from aioquic.asyncio import QuicConnectionProtocol\n    from aioquic.h3.connection import H3_ALPN, H3Connection\n    from aioquic.quic.events import (\n        DatagramFrameReceived,\n        ProtocolNegotiated,\n        QuicEvent,\n    )\n\n    ConnectionProtocol = QuicConnectionProtocol\nexcept ModuleNotFoundError:  # no cov\n    ...\n\n\nclass HttpProtocolMixin:\n    __slots__ = ()\n    __version__: HTTP\n\n    def _setup_connection(self, *args, **kwargs):\n        self._http = self.HTTP_CLASS(self, *args, **kwargs)\n        self._time = current_time()\n        try:\n            self.check_timeouts()\n        except AttributeError:\n            ...\n\n    def _setup(self):\n        self.request: Request | None = None\n        self.access_log = self.app.config.ACCESS_LOG\n        self.request_handler = self.app.handle_request\n        self.error_handler = self.app.error_handler\n        self.request_timeout = self.app.config.REQUEST_TIMEOUT\n        self.response_timeout = self.app.config.RESPONSE_TIMEOUT\n        self.keep_alive_timeout = self.app.config.KEEP_ALIVE_TIMEOUT\n        self.request_max_size = self.app.config.REQUEST_MAX_SIZE\n        self.request_class = self.app.request_class or Request\n\n    @property\n    def http(self):\n        if not hasattr(self, \"_http\"):\n            return None\n        return self._http\n\n    @property\n    def version(self):\n        return self.__class__.__version__\n\n\nclass HttpProtocol(HttpProtocolMixin, SanicProtocol, metaclass=TouchUpMeta):\n    \"\"\"\n    This class provides implements the HTTP 1.1 protocol on top of our\n    Sanic Server transport\n    \"\"\"\n\n    HTTP_CLASS = Http\n\n    __touchup__ = (\n        \"send\",\n        \"connection_task\",\n    )\n    __version__ = HTTP.VERSION_1\n    __slots__ = (\n        # request params\n        \"request\",\n        # request config\n        \"request_handler\",\n        \"request_timeout\",\n        \"response_timeout\",\n        \"keep_alive_timeout\",\n        \"request_max_size\",\n        \"request_class\",\n        \"error_handler\",\n        # enable or disable access log purpose\n        \"access_log\",\n        # connection management\n        \"state\",\n        \"url\",\n        \"_handler_task\",\n        \"_http\",\n        \"_exception\",\n        \"recv_buffer\",\n        \"_callback_check_timeouts\",\n    )\n\n    def __init__(\n        self,\n        *,\n        loop,\n        app: Sanic,\n        signal=None,\n        connections=None,\n        state=None,\n        unix=None,\n        **kwargs,\n    ):\n        super().__init__(\n            loop=loop,\n            app=app,\n            signal=signal,\n            connections=connections,\n            unix=unix,\n        )\n        self.url = None\n        self.state = state if state else {}\n        self._setup()\n        if \"requests_count\" not in self.state:\n            self.state[\"requests_count\"] = 0\n        self._exception = None\n        self._callback_check_timeouts = None\n\n    async def connection_task(self):  # no cov\n        \"\"\"\n        Run a HTTP connection.\n\n        Timeouts and some additional error handling occur here, while most of\n        everything else happens in class Http or in code called from there.\n        \"\"\"\n        try:\n            self._setup_connection()\n            await self.app.dispatch(\n                \"http.lifecycle.begin\",\n                inline=True,\n                context={\"conn_info\": self.conn_info},\n            )\n            await self._http.http1()\n        except CancelledError:\n            pass\n        except Exception:\n            error_logger.exception(\"protocol.connection_task uncaught\")\n        finally:\n            if (\n                self.access_log\n                and self._http\n                and self.transport\n                and not self._http.upgrade_websocket\n            ):\n                self.log_disconnect()\n            self._http = None\n            self._task = None\n            try:\n                self.close()\n            except BaseException:\n                error_logger.exception(\"Closing failed\")\n            finally:\n                await self.app.dispatch(\n                    \"http.lifecycle.complete\",\n                    inline=True,\n                    context={\"conn_info\": self.conn_info},\n                )\n                # Important to keep this Ellipsis here for the TouchUp module\n                ...\n\n    def log_disconnect(self):\n        ip = self.transport.get_extra_info(\"peername\")\n\n        req = self._http.request\n        res = self._http.response\n        extra = {\n            \"status\": res.status if res else str(self._http.stage),\n            \"byte\": \"DISCONNECTED\",\n            \"host\": f\"{id(self):X}\"[-5:-1] + \"unx\",\n            \"request\": \"nil\",\n            \"duration\": \"\",\n        }\n        if req is not None:\n            if ip := req.client_ip:\n                extra[\"host\"] = f\"{ip}:{req.port}\"\n            extra[\"request\"] = f\"{req.method} {req.url}\"\n        access_logger.info(\"\", extra=extra)\n\n    def check_timeouts(self):\n        \"\"\"\n        Runs itself periodically to enforce any expired timeouts.\n        \"\"\"\n        try:\n            if not self._task:\n                return\n            duration = current_time() - self._time\n            stage = self._http.stage\n            if stage is Stage.IDLE and duration > self.keep_alive_timeout:\n                logger.debug(\"KeepAlive Timeout. Closing connection.\")\n            elif stage is Stage.REQUEST and duration > self.request_timeout:\n                logger.debug(\"Request Timeout. Closing connection.\")\n                self._http.exception = RequestTimeout(\"Request Timeout\")\n            elif stage is Stage.HANDLER and self._http.upgrade_websocket:\n                websockets_logger.debug(\n                    \"Handling websocket. Timeouts disabled.\"\n                )\n                return\n            elif (\n                stage in (Stage.HANDLER, Stage.RESPONSE, Stage.FAILED)\n                and duration > self.response_timeout\n            ):\n                logger.debug(\"Response Timeout. Closing connection.\")\n                self._http.exception = ServiceUnavailable(\"Response Timeout\")\n            else:\n                interval = (\n                    min(\n                        self.keep_alive_timeout,\n                        self.request_timeout,\n                        self.response_timeout,\n                    )\n                    / 2\n                )\n                _interval = max(0.1, interval)\n                self._callback_check_timeouts = self.loop.call_later(\n                    _interval, self.check_timeouts\n                )\n                return\n            if sys.version_info < (3, 14):\n                self._task.cancel(\"Cancel connection task with a timeout\")\n            else:\n                self._task.cancel()\n        except Exception:\n            error_logger.exception(\"protocol.check_timeouts\")\n\n    def close(self, timeout: float | None = None):\n        \"\"\"\n        Requires to prevent checking timeouts for closed connections\n        \"\"\"\n\n        if self._callback_check_timeouts:\n            self._callback_check_timeouts.cancel()\n        return super().close(timeout=timeout)\n\n    async def send(self, data):  # no cov\n        \"\"\"\n        Writes HTTP data with backpressure control.\n        \"\"\"\n        await self._can_write.wait()\n        if self.transport.is_closing():\n            raise RequestCancelled\n        await self.app.dispatch(\n            \"http.lifecycle.send\",\n            inline=True,\n            context={\"data\": data},\n        )\n        self.transport.write(data)\n        self._time = current_time()\n\n    def close_if_idle(self) -> bool:\n        \"\"\"\n        Close the connection if a request is not being sent or received\n\n        :return: boolean - True if closed, false if staying open\n        \"\"\"\n        if self.http is None or self.http.stage is Stage.IDLE:\n            self.close()\n            return True\n        return False\n\n    # -------------------------------------------- #\n    # Only asyncio.Protocol callbacks below this\n    # -------------------------------------------- #\n\n    def connection_made(self, transport):\n        \"\"\"\n        HTTP-protocol-specific new connection handler\n        \"\"\"\n        try:\n            # TODO: Benchmark to find suitable write buffer limits\n            transport.set_write_buffer_limits(low=16384, high=65536)\n            self.connections.add(self)\n            self.transport = transport\n            self._task = self.loop.create_task(self.connection_task())\n            self.recv_buffer = bytearray()\n            self.conn_info = ConnInfo(self.transport, unix=self._unix)\n        except Exception:\n            error_logger.exception(\"protocol.connect_made\")\n\n    def data_received(self, data: bytes):\n        try:\n            self._time = current_time()\n            if not data:\n                return self.close()\n            self.recv_buffer += data\n\n            if (\n                len(self.recv_buffer) >= self.app.config.REQUEST_BUFFER_SIZE\n                and self.transport\n            ):\n                self.transport.pause_reading()\n\n            if self._data_received:\n                self._data_received.set()\n        except Exception:\n            error_logger.exception(\"protocol.data_received\")\n\n\nclass Http3Protocol(HttpProtocolMixin, ConnectionProtocol):  # type: ignore\n    HTTP_CLASS = Http3\n    __version__ = HTTP.VERSION_3\n\n    def __init__(self, *args, app: Sanic, **kwargs) -> None:\n        self.app = app\n        super().__init__(*args, **kwargs)\n        self._setup()\n        self._connection: H3Connection | None = None\n\n    def quic_event_received(self, event: QuicEvent) -> None:\n        logger.debug(\n            f\"{Colors.BLUE}[quic_event_received]: \"\n            f\"{Colors.PURPLE}{event}{Colors.END}\",\n            extra={\"verbosity\": 2},\n        )\n        if isinstance(event, ProtocolNegotiated):\n            self._setup_connection(transmit=self.transmit)\n            if event.alpn_protocol in H3_ALPN:\n                self._connection = H3Connection(\n                    self._quic, enable_webtransport=True\n                )\n        elif isinstance(event, DatagramFrameReceived):\n            if event.data == b\"quack\":\n                self._quic.send_datagram_frame(b\"quack-ack\")\n\n        #  pass event to the HTTP layer\n        if self._connection is not None:\n            for http_event in self._connection.handle_event(event):\n                self._http.http_event_received(http_event)\n\n    @property\n    def connection(self) -> H3Connection | None:\n        return self._connection\n"
  },
  {
    "path": "sanic/server/protocols/websocket_protocol.py",
    "content": "from collections.abc import Sequence\nfrom typing import cast\n\n\ntry:  # websockets >= 11.0\n    from websockets.protocol import State  # type: ignore\n    from websockets.server import ServerProtocol  # type: ignore\nexcept ImportError:  # websockets < 11.0\n    from websockets.connection import State\n    from websockets.server import ServerConnection as ServerProtocol\n\nfrom websockets import http11\nfrom websockets.datastructures import Headers as WSHeaders\nfrom websockets.typing import Subprotocol\n\nfrom sanic.exceptions import SanicException\nfrom sanic.log import access_logger, websockets_logger\nfrom sanic.request import Request\nfrom sanic.server import HttpProtocol\n\nfrom ..websockets.impl import WebsocketImplProtocol\n\n\nOPEN = State.OPEN\nCLOSING = State.CLOSING\nCLOSED = State.CLOSED\n\n\nclass WebSocketProtocol(HttpProtocol):\n    __slots__ = (\n        \"websocket\",\n        \"websocket_timeout\",\n        \"websocket_max_size\",\n        \"websocket_ping_interval\",\n        \"websocket_ping_timeout\",\n        \"websocket_url\",\n        \"websocket_peer\",\n    )\n\n    def __init__(\n        self,\n        *args,\n        websocket_timeout: float = 10.0,\n        websocket_max_size: int | None = None,\n        websocket_ping_interval: float | None = 20.0,\n        websocket_ping_timeout: float | None = 20.0,\n        **kwargs,\n    ):\n        super().__init__(*args, **kwargs)\n        self.websocket: WebsocketImplProtocol | None = None\n        self.websocket_timeout = websocket_timeout\n        self.websocket_max_size = websocket_max_size\n        self.websocket_ping_interval = websocket_ping_interval\n        self.websocket_ping_timeout = websocket_ping_timeout\n        self.websocket_url: str | None = None\n        self.websocket_peer: str | None = None\n\n    def connection_lost(self, exc):\n        if self.websocket is not None:\n            self.websocket.connection_lost(exc)\n        super().connection_lost(exc)\n        self.log_websocket(\"CLOSE\")\n        self.websocket_url = None\n        self.websocket_peer = None\n\n    def data_received(self, data):\n        if self.websocket is not None:\n            self.websocket.data_received(data)\n        else:\n            # Pass it to HttpProtocol handler first\n            # That will (hopefully) upgrade it to a websocket.\n            super().data_received(data)\n\n    def eof_received(self) -> bool | None:\n        if self.websocket is not None:\n            return self.websocket.eof_received()\n        else:\n            return False\n\n    def close(self, timeout: float | None = None):\n        # Called by HttpProtocol at the end of connection_task\n        # If we've upgraded to websocket, we do our own closing\n        if self.websocket is not None:\n            # Note, we don't want to use websocket.close()\n            # That is used for user's application code to send a\n            # websocket close packet. This is different.\n            self.websocket.end_connection(1001)\n        else:\n            super().close()\n\n    def close_if_idle(self):\n        # Called by Sanic Server when shutting down\n        # If we've upgraded to websocket, shut it down\n        if self.websocket is not None:\n            if self.websocket.ws_proto.state in (CLOSING, CLOSED):\n                return True\n            elif self.websocket.loop is not None:\n                self.websocket.loop.create_task(self.websocket.close(1001))\n            else:\n                self.websocket.end_connection(1001)\n        else:\n            return super().close_if_idle()\n\n    @staticmethod\n    def sanic_request_to_ws_request(request: Request):\n        return http11.Request(\n            path=request.path,\n            headers=WSHeaders(request.headers),\n        )\n\n    async def websocket_handshake(\n        self, request, subprotocols: Sequence[str] | None = None\n    ):\n        # let the websockets package do the handshake with the client\n        try:\n            if subprotocols is not None:\n                # subprotocols can be a set or frozenset,\n                # but ServerProtocol needs a list\n                subprotocols = cast(\n                    Sequence[Subprotocol] | None,\n                    list(\n                        [\n                            Subprotocol(subprotocol)\n                            for subprotocol in subprotocols\n                        ]\n                    ),\n                )\n            ws_proto = ServerProtocol(\n                max_size=self.websocket_max_size,\n                subprotocols=subprotocols,\n                state=OPEN,\n                logger=websockets_logger,\n            )\n            resp = ws_proto.accept(self.sanic_request_to_ws_request(request))\n        except Exception:\n            msg = (\n                \"Failed to open a WebSocket connection.\\n\"\n                \"See server log for more information.\\n\"\n            )\n            raise SanicException(msg, status_code=500)\n        if 100 <= resp.status_code <= 299:\n            first_line = (\n                f\"HTTP/1.1 {resp.status_code} {resp.reason_phrase}\\r\\n\"\n            ).encode()\n            rbody = bytearray(first_line)\n            rbody += (\n                \"\".join([f\"{k}: {v}\\r\\n\" for k, v in resp.headers.items()])\n            ).encode()\n            rbody += b\"\\r\\n\"\n            if resp.body:\n                rbody += resp.body\n                rbody += b\"\\r\\n\\r\\n\"\n            await super().send(rbody)\n        else:\n            raise SanicException(resp.body, resp.status_code)\n        self.websocket = WebsocketImplProtocol(\n            ws_proto,\n            ping_interval=self.websocket_ping_interval,\n            ping_timeout=self.websocket_ping_timeout,\n            close_timeout=self.websocket_timeout,\n        )\n        loop = (\n            request.transport.loop\n            if hasattr(request, \"transport\")\n            and hasattr(request.transport, \"loop\")\n            else None\n        )\n        await self.websocket.connection_made(self, loop=loop)\n        self.websocket_url = self._http.request.url\n        self.websocket_peer = f\"{id(self):X}\"[-5:-1] + \"unx\"\n        if ip := self._http.request.client_ip:\n            self.websocket_peer = f\"{ip}:{self._http.request.port}\"\n        self.log_websocket(\"OPEN\")\n        return self.websocket\n\n    def log_websocket(self, message):\n        if not self.access_log or not self.websocket_url:\n            return\n        status = \"\"\n        close = \"\"\n        try:\n            # Can we get some useful statistics?\n            p = self.websocket.ws_proto\n            state = p.state\n            if state == CLOSED:\n                codes = {\n                    1000: \"NORMAL\",\n                    1001: \"GOING AWAY\",\n                    1005: \"NO STATUS\",\n                    1006: \"ABNORMAL\",\n                    1011: \"SERVER ERR\",\n                }\n                if p.close_code == 1006:\n                    message = \"CLOSE_ABN\"\n                scode = rcode = 1006  # Abnormal closure (disconnection)\n                sdesc = rdesc = \"\"\n                if p.close_sent:\n                    scode = p.close_sent.code\n                    sdesc = p.close_sent.reason\n                if p.close_rcvd:\n                    rcode = p.close_rcvd.code\n                    rdesc = p.close_rcvd.reason\n                # Use repr() to escape any control characters\n                sdesc = repr(sdesc[:256]) if sdesc else codes.get(scode, \"\")\n                rdesc = repr(rdesc[:256]) if rdesc else codes.get(rcode, \"\")\n                if p.close_rcvd_then_sent or scode == 1006:\n                    status = rcode\n                    close = (\n                        f\"{rdesc} from client\"\n                        if scode in (rcode, 1006)\n                        else f\"{rdesc} ▼▲ {scode} {sdesc}\"\n                    )\n                else:\n                    status = scode\n                    close = (\n                        f\"{sdesc} from server\"\n                        if rcode in (scode, 1006)\n                        else f\"{sdesc} ▲▼ {rcode} {rdesc}\"\n                    )\n\n        except AttributeError:\n            ...\n        extra = {\n            \"status\": status,\n            \"byte\": close,\n            \"host\": self.websocket_peer,\n            \"request\": f\" 🔌 {self.websocket_url}\",\n            \"duration\": \"\",\n        }\n        access_logger.info(message, extra=extra)\n"
  },
  {
    "path": "sanic/server/runners.py",
    "content": "from __future__ import annotations\n\nfrom ssl import SSLContext\nfrom typing import TYPE_CHECKING\n\nfrom sanic.config import Config\nfrom sanic.exceptions import ServerError\nfrom sanic.http.constants import HTTP\nfrom sanic.http.tls import get_ssl_context\n\n\nif TYPE_CHECKING:\n    from sanic.app import Sanic\n\nimport asyncio\nimport os\nimport socket\n\nfrom functools import partial\nfrom signal import SIG_IGN, SIGINT, SIGTERM\nfrom signal import signal as signal_func\n\nfrom sanic.application.ext import setup_ext\nfrom sanic.compat import OS_IS_WINDOWS, ctrlc_workaround_for_windows\nfrom sanic.http.http3 import SessionTicketStore, get_config\nfrom sanic.log import error_logger, server_logger\nfrom sanic.logging.setup import setup_logging\nfrom sanic.models.server_types import Signal\nfrom sanic.server.async_server import AsyncioServer\nfrom sanic.server.protocols.http_protocol import Http3Protocol, HttpProtocol\nfrom sanic.server.socket import bind_unix_socket, remove_unix_socket\n\n\ntry:\n    from aioquic.asyncio import serve as quic_serve\n\n    HTTP3_AVAILABLE = True\nexcept ModuleNotFoundError:  # no cov\n    HTTP3_AVAILABLE = False\n\n\ndef serve(\n    host,\n    port,\n    app: Sanic,\n    ssl: SSLContext | None = None,\n    sock: socket.socket | None = None,\n    unix: str | None = None,\n    reuse_port: bool = False,\n    loop=None,\n    protocol: type[asyncio.Protocol] = HttpProtocol,\n    backlog: int = 100,\n    register_sys_signals: bool = True,\n    run_multiple: bool = False,\n    run_async: bool = False,\n    connections=None,\n    signal=Signal(),\n    state=None,\n    asyncio_server_kwargs=None,\n    version=HTTP.VERSION_1,\n):\n    \"\"\"Start asynchronous HTTP Server on an individual process.\n\n    :param host: Address to host on\n    :param port: Port to host on\n    :param before_start: function to be executed before the server starts\n                         listening. Takes arguments `app` instance and `loop`\n    :param after_start: function to be executed after the server starts\n                        listening. Takes  arguments `app` instance and `loop`\n    :param before_stop: function to be executed when a stop signal is\n                        received before it is respected. Takes arguments\n                        `app` instance and `loop`\n    :param after_stop: function to be executed when a stop signal is\n                       received after it is respected. Takes arguments\n                       `app` instance and `loop`\n    :param ssl: SSLContext\n    :param sock: Socket for the server to accept connections from\n    :param unix: Unix socket to listen on instead of TCP port\n    :param reuse_port: `True` for multiple workers\n    :param loop: asyncio compatible event loop\n    :param run_async: bool: Do not create a new event loop for the server,\n                      and return an AsyncServer object rather than running it\n    :param asyncio_server_kwargs: key-value args for asyncio/uvloop\n                                  create_server method\n    :return: Nothing\n\n    Args:\n        host (str): Address to host on\n        port (int): Port to host on\n        app (Sanic): Sanic app instance\n        ssl (Optional[SSLContext], optional): SSLContext. Defaults to `None`.\n        sock (Optional[socket.socket], optional): Socket for the server to\n            accept connections from. Defaults to `None`.\n        unix (Optional[str], optional): Unix socket to listen on instead of\n            TCP port. Defaults to `None`.\n        reuse_port (bool, optional): `True` for multiple workers. Defaults\n            to `False`.\n        loop: asyncio compatible event loop. Defaults\n            to `None`.\n        protocol (Type[asyncio.Protocol], optional): Protocol to use. Defaults\n            to `HttpProtocol`.\n        backlog (int, optional): The maximum number of queued connections\n            passed to socket.listen(). Defaults to `100`.\n        register_sys_signals (bool, optional): Register SIGINT and SIGTERM.\n            Defaults to `True`.\n        run_multiple (bool, optional): Run multiple workers. Defaults\n            to `False`.\n        run_async (bool, optional): Return an AsyncServer object.\n            Defaults to `False`.\n        connections: Connections. Defaults to `None`.\n        signal (Signal, optional): Signal. Defaults to `Signal()`.\n        state: State. Defaults to `None`.\n        asyncio_server_kwargs (Optional[Dict[str, Union[int, float]]], optional):\n            key-value args for asyncio/uvloop create_server method. Defaults\n            to `None`.\n        version (str, optional): HTTP version. Defaults to `HTTP.VERSION_1`.\n\n    Raises:\n        ServerError: Cannot run HTTP/3 server without aioquic installed.\n\n    Returns:\n        AsyncioServer: AsyncioServer object if `run_async` is `True`.\n    \"\"\"  # noqa: E501\n    if not run_async and not loop:\n        # create new event_loop after fork\n        loop = asyncio.new_event_loop()\n        asyncio.set_event_loop(loop)\n\n    setup_logging(app.debug, app.config.NO_COLOR, app.config.LOG_EXTRA)\n\n    if app.debug:\n        loop.set_debug(app.debug)\n\n    app.asgi = False\n\n    if version is HTTP.VERSION_3:\n        return _serve_http_3(host, port, app, loop, ssl)\n    return _serve_http_1(\n        host,\n        port,\n        app,\n        ssl,\n        sock,\n        unix,\n        reuse_port,\n        loop,\n        protocol,\n        backlog,\n        register_sys_signals,\n        run_multiple,\n        run_async,\n        connections,\n        signal,\n        state,\n        asyncio_server_kwargs,\n    )\n\n\ndef _setup_system_signals(\n    app: Sanic,\n    run_multiple: bool,\n    register_sys_signals: bool,\n    loop: asyncio.AbstractEventLoop,\n) -> None:  # no cov\n    signal_func(SIGINT, SIG_IGN)\n    signal_func(SIGTERM, SIG_IGN)\n    os.environ[\"SANIC_WORKER_PROCESS\"] = \"true\"\n    # Register signals for graceful termination\n    if register_sys_signals:\n        if OS_IS_WINDOWS:\n            ctrlc_workaround_for_windows(app)\n        else:\n            for _signal in [SIGINT, SIGTERM]:\n                loop.add_signal_handler(\n                    _signal, partial(app.stop, terminate=False)\n                )\n\n\ndef _run_shutdown_coro(loop, coro):\n    \"\"\"Run a shutdown coroutine, handling the case where the loop was stopped.\n\n    When loop.stop() is called during run_forever(), asyncio sets an internal\n    flag that causes the first run_until_complete() to fail. For standard\n    asyncio, we clear the _stopping flag. For uvloop (which doesn't expose\n    this flag), the first attempt may fail but subsequent attempts succeed.\n    \"\"\"\n    # Clear asyncio's stopped state if accessible\n    if hasattr(loop, \"_stopping\"):\n        loop._stopping = False\n\n    try:\n        loop.run_until_complete(coro())\n    except (RuntimeError, KeyboardInterrupt):\n        # RuntimeError: loop was stopped (uvloop behavior)\n        # KeyboardInterrupt: signal arrived during select (asyncio behavior)\n        # Try once more - this handles uvloop's behavior where the first\n        # run_until_complete after stop() fails but subsequent calls succeed.\n        if hasattr(loop, \"_stopping\"):\n            loop._stopping = False\n        try:\n            loop.run_until_complete(coro())\n        except (RuntimeError, KeyboardInterrupt):\n            # If it still fails, the loop is truly unusable\n            pass\n\n\ndef _run_server_forever(loop, before_stop, after_stop, cleanup, unix, pid):\n    try:\n        server_logger.info(\"Worker ready [%s]\", pid)\n        loop.run_forever()\n    finally:\n        server_logger.info(\"Stopping worker [%s]\", pid)\n\n        for _signal in [SIGINT, SIGTERM]:\n            try:\n                loop.remove_signal_handler(_signal)\n            except (NotImplementedError, OSError):\n                pass\n\n        _run_shutdown_coro(loop, before_stop)\n\n        if cleanup:\n            cleanup()\n\n        _run_shutdown_coro(loop, after_stop)\n\n        remove_unix_socket(unix)\n        loop.close()\n        server_logger.info(\"Worker complete [%s]\", pid)\n\n\ndef _serve_http_1(\n    host,\n    port,\n    app,\n    ssl,\n    sock,\n    unix,\n    reuse_port,\n    loop,\n    protocol,\n    backlog,\n    register_sys_signals,\n    run_multiple,\n    run_async,\n    connections,\n    signal,\n    state,\n    asyncio_server_kwargs,\n):\n    connections = connections if connections is not None else set()\n    protocol_kwargs = _build_protocol_kwargs(protocol, app.config)\n    server = partial(\n        protocol,\n        loop=loop,\n        connections=connections,\n        signal=signal,\n        app=app,\n        state=state,\n        unix=unix,\n        **protocol_kwargs,\n    )\n    asyncio_server_kwargs = (\n        asyncio_server_kwargs if asyncio_server_kwargs else {}\n    )\n    if OS_IS_WINDOWS and sock:\n        pid = os.getpid()\n        sock = sock.share(pid)\n        sock = socket.fromshare(sock)\n    # UNIX sockets are always bound by us (to preserve semantics between modes)\n    elif unix:\n        sock = bind_unix_socket(unix, backlog=backlog)\n    server_coroutine = loop.create_server(\n        server,\n        None if sock else host,\n        None if sock else port,\n        ssl=ssl,\n        reuse_port=reuse_port,\n        sock=sock,\n        backlog=backlog,\n        **asyncio_server_kwargs,\n    )\n\n    setup_ext(app)\n    if run_async:\n        return AsyncioServer(\n            app=app,\n            loop=loop,\n            serve_coro=server_coroutine,\n            connections=connections,\n        )\n\n    pid = os.getpid()\n    server_logger.info(\"Starting worker [%s]\", pid)\n    loop.run_until_complete(app._startup())\n    loop.run_until_complete(app._server_event(\"init\", \"before\"))\n    app.ack()\n\n    try:\n        http_server = loop.run_until_complete(server_coroutine)\n    except BaseException:\n        error_logger.exception(\"Unable to start server\", exc_info=True)\n        return\n\n    def _cleanup():\n        # Wait for event loop to finish and all connections to drain\n        http_server.close()\n        loop.run_until_complete(http_server.wait_closed())\n\n        # Complete all tasks on the loop\n        signal.stopped = True\n        for connection in connections:\n            connection.close_if_idle()\n\n        # Gracefully shutdown timeout.\n        # We should provide graceful_shutdown_timeout,\n        # instead of letting connection hangs forever.\n        # Let's roughly calcucate time.\n        graceful = app.config.GRACEFUL_SHUTDOWN_TIMEOUT\n        start_shutdown: float = 0\n        while connections and (start_shutdown < graceful):\n            loop.run_until_complete(asyncio.sleep(0.1))\n            start_shutdown = start_shutdown + 0.1\n\n        app.shutdown_tasks(graceful - start_shutdown)\n\n        # Force close non-idle connection after waiting for\n        # graceful_shutdown_timeout\n        for conn in connections:\n            if hasattr(conn, \"websocket\") and conn.websocket:\n                conn.websocket.fail_connection(code=1001)\n            else:\n                conn.abort()\n\n        try:\n            app.set_serving(False)\n        except (BrokenPipeError, ConnectionResetError, EOFError):\n            pass\n\n    _setup_system_signals(app, run_multiple, register_sys_signals, loop)\n    loop.run_until_complete(app._server_event(\"init\", \"after\"))\n    app.set_serving(True)\n    _run_server_forever(\n        loop,\n        partial(app._server_event, \"shutdown\", \"before\"),\n        partial(app._server_event, \"shutdown\", \"after\"),\n        _cleanup,\n        unix,\n        pid,\n    )\n\n\ndef _serve_http_3(\n    host,\n    port,\n    app,\n    loop,\n    ssl,\n    register_sys_signals: bool = True,\n    run_multiple: bool = False,\n):\n    if not HTTP3_AVAILABLE:\n        raise ServerError(\n            \"Cannot run HTTP/3 server without aioquic installed. \"\n        )\n    pid = os.getpid()\n    server_logger.info(\"Starting worker [%s]\", pid)\n    protocol = partial(Http3Protocol, app=app)\n    ticket_store = SessionTicketStore()\n    ssl_context = get_ssl_context(app, ssl)\n    config = get_config(app, ssl_context)\n    coro = quic_serve(\n        host,\n        port,\n        configuration=config,\n        create_protocol=protocol,\n        session_ticket_fetcher=ticket_store.pop,\n        session_ticket_handler=ticket_store.add,\n    )\n    server = AsyncioServer(app, loop, coro, [])\n    loop.run_until_complete(server.startup())\n    loop.run_until_complete(server.before_start())\n    app.ack()\n    loop.run_until_complete(server)\n    _setup_system_signals(app, run_multiple, register_sys_signals, loop)\n    loop.run_until_complete(server.after_start())\n\n    # TODO: Create connection cleanup and graceful shutdown\n    cleanup = None\n    _run_server_forever(\n        loop, server.before_stop, server.after_stop, cleanup, None, pid\n    )\n\n\ndef _build_protocol_kwargs(\n    protocol: type[asyncio.Protocol], config: Config\n) -> dict[str, int | float]:\n    if hasattr(protocol, \"websocket_handshake\"):\n        return {\n            \"websocket_max_size\": config.WEBSOCKET_MAX_SIZE,\n            \"websocket_ping_timeout\": config.WEBSOCKET_PING_TIMEOUT,\n            \"websocket_ping_interval\": config.WEBSOCKET_PING_INTERVAL,\n        }\n    return {}\n"
  },
  {
    "path": "sanic/server/socket.py",
    "content": "from __future__ import annotations\n\nimport secrets\nimport socket\nimport stat\n\nfrom ipaddress import ip_address\nfrom pathlib import Path\nfrom typing import Any\n\nfrom sanic.http.constants import HTTP\n\n\ndef bind_socket(host: str, port: int, *, backlog=100) -> socket.socket:\n    \"\"\"Create TCP server socket.\n    :param host: IPv4, IPv6 or hostname may be specified\n    :param port: TCP port number\n    :param backlog: Maximum number of connections to queue\n    :return: socket.socket object\n    \"\"\"\n    location = (host, port)\n    # socket.share, socket.fromshare\n    try:  # IP address: family must be specified for IPv6 at least\n        ip = ip_address(host)\n        host = str(ip)\n        sock = socket.socket(\n            socket.AF_INET6 if ip.version == 6 else socket.AF_INET\n        )\n    except ValueError:  # Hostname, may become AF_INET or AF_INET6\n        sock = socket.socket()\n    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n    sock.bind(location)\n    sock.listen(backlog)\n    sock.set_inheritable(True)\n    return sock\n\n\ndef bind_unix_socket(\n    path: Path | str, *, mode=0o666, backlog=100\n) -> socket.socket:\n    \"\"\"Create unix socket.\n    :param path: filesystem path\n    :param backlog: Maximum number of connections to queue\n    :return: socket.socket object\n    \"\"\"\n\n    # Sanitise and pre-verify socket path\n    path = Path(path)\n    folder = path.parent\n    if not folder.is_dir():\n        raise FileNotFoundError(f\"Socket folder does not exist: {folder}\")\n    try:\n        if not stat.S_ISSOCK(path.lstat().st_mode):\n            raise FileExistsError(f\"Existing file is not a socket: {path}\")\n    except FileNotFoundError:\n        pass\n    # Create new socket with a random temporary name\n    tmp_path = path.with_name(f\"{path.name}.{secrets.token_urlsafe()}\")\n    sock = socket.socket(socket.AF_UNIX)\n    try:\n        # Critical section begins (filename races)\n        sock.bind(tmp_path.as_posix())\n        try:\n            tmp_path.chmod(mode)\n            # Start listening before rename to avoid connection failures\n            sock.listen(backlog)\n            tmp_path.rename(path)\n        except:  # noqa: E722\n            try:\n                tmp_path.unlink()\n            finally:\n                raise\n    except:  # noqa: E722\n        try:\n            sock.close()\n        finally:\n            raise\n    return sock\n\n\ndef remove_unix_socket(path: Path | str | None) -> None:\n    \"\"\"Remove dead unix socket during server exit.\"\"\"\n    if not path:\n        return\n    try:\n        path = Path(path)\n        if stat.S_ISSOCK(path.lstat().st_mode):\n            # Is it actually dead (doesn't belong to a new server instance)?\n            with socket.socket(socket.AF_UNIX) as testsock:\n                try:\n                    testsock.connect(path.as_posix())\n                except ConnectionRefusedError:\n                    path.unlink()\n    except FileNotFoundError:\n        pass\n\n\ndef configure_socket(\n    server_settings: dict[str, Any],\n) -> socket.SocketType | None:\n    # Create a listening socket or use the one in settings\n    if server_settings.get(\"version\") is HTTP.VERSION_3:\n        return None\n    sock = server_settings.get(\"sock\")\n    unix = server_settings[\"unix\"]\n    backlog = server_settings[\"backlog\"]\n    if unix:\n        unix = Path(unix).absolute()\n        sock = bind_unix_socket(unix, backlog=backlog)\n        server_settings[\"unix\"] = unix\n    if sock is None:\n        sock = bind_socket(\n            server_settings[\"host\"],\n            server_settings[\"port\"],\n            backlog=backlog,\n        )\n        sock.set_inheritable(True)\n        server_settings[\"sock\"] = sock\n        server_settings[\"host\"] = None\n        server_settings[\"port\"] = None\n    return sock\n"
  },
  {
    "path": "sanic/server/websockets/__init__.py",
    "content": ""
  },
  {
    "path": "sanic/server/websockets/connection.py",
    "content": "from collections.abc import Awaitable, MutableMapping\nfrom typing import Any, Callable\n\nfrom sanic.exceptions import InvalidUsage\n\n\nASGIMessage = MutableMapping[str, Any]\n\n\nclass WebSocketConnection:\n    \"\"\"\n    This is for ASGI Connections.\n    It provides an interface similar to WebsocketProtocol, but\n    sends/receives over an ASGI connection.\n    \"\"\"\n\n    # TODO\n    # - Implement ping/pong\n\n    def __init__(\n        self,\n        send: Callable[[ASGIMessage], Awaitable[None]],\n        receive: Callable[[], Awaitable[ASGIMessage]],\n        subprotocols: list[str] | None = None,\n    ) -> None:\n        self._send = send\n        self._receive = receive\n        self._subprotocols = subprotocols or []\n\n    async def send(self, data: str | bytes, *args, **kwargs) -> None:\n        message: dict[str, str | bytes] = {\"type\": \"websocket.send\"}\n\n        if isinstance(data, bytes):\n            message.update({\"bytes\": data})\n        else:\n            message.update({\"text\": str(data)})\n\n        await self._send(message)\n\n    async def recv(self, *args, **kwargs) -> str | bytes | None:\n        message = await self._receive()\n\n        if message[\"type\"] == \"websocket.receive\":\n            try:\n                return message[\"text\"]\n            except KeyError:\n                try:\n                    return message[\"bytes\"]\n                except KeyError:\n                    raise InvalidUsage(\"Bad ASGI message received\")\n        elif message[\"type\"] == \"websocket.disconnect\":\n            pass\n\n        return None\n\n    receive = recv\n\n    async def accept(self, subprotocols: list[str] | None = None) -> None:\n        subprotocol = None\n        if subprotocols:\n            for subp in subprotocols:\n                if subp in self.subprotocols:\n                    subprotocol = subp\n                    break\n\n        await self._send(\n            {\n                \"type\": \"websocket.accept\",\n                \"subprotocol\": subprotocol,\n            }\n        )\n\n    async def close(self, code: int = 1000, reason: str = \"\") -> None:\n        pass\n\n    @property\n    def subprotocols(self):\n        return self._subprotocols\n\n    @subprotocols.setter\n    def subprotocols(self, subprotocols: list[str] | None = None):\n        self._subprotocols = subprotocols or []\n"
  },
  {
    "path": "sanic/server/websockets/frame.py",
    "content": "import asyncio\nimport codecs\n\nfrom collections.abc import AsyncIterator\nfrom typing import TYPE_CHECKING\n\nfrom websockets.frames import Frame, Opcode\nfrom websockets.typing import Data\n\nfrom sanic.exceptions import ServerError\n\n\nif TYPE_CHECKING:\n    from .impl import WebsocketImplProtocol\n\nUTF8Decoder = codecs.getincrementaldecoder(\"utf-8\")\n\n\nclass WebsocketFrameAssembler:\n    \"\"\"\n    Assemble a message from frames.\n    Code borrowed from aaugustin/websockets project:\n    https://github.com/aaugustin/websockets/blob/6eb98dd8fa5b2c896b9f6be7e8d117708da82a39/src/websockets/sync/messages.py\n    \"\"\"\n\n    __slots__ = (\n        \"protocol\",\n        \"read_mutex\",\n        \"write_mutex\",\n        \"message_complete\",\n        \"message_fetched\",\n        \"get_in_progress\",\n        \"decoder\",\n        \"completed_queue\",\n        \"chunks\",\n        \"chunks_queue\",\n        \"paused\",\n        \"get_id\",\n        \"put_id\",\n    )\n    if TYPE_CHECKING:\n        protocol: \"WebsocketImplProtocol\"\n        read_mutex: asyncio.Lock\n        write_mutex: asyncio.Lock\n        message_complete: asyncio.Event\n        message_fetched: asyncio.Event\n        completed_queue: asyncio.Queue\n        get_in_progress: bool\n        decoder: codecs.IncrementalDecoder | None\n        # For streaming chunks rather than messages:\n        chunks: list[Data]\n        chunks_queue: asyncio.Queue[Data | None] | None\n        paused: bool\n\n    def __init__(self, protocol) -> None:\n        self.protocol = protocol\n\n        self.read_mutex = asyncio.Lock()\n        self.write_mutex = asyncio.Lock()\n\n        self.completed_queue = asyncio.Queue(maxsize=1)  # type: asyncio.Queue[Data]\n\n        # put() sets this event to tell get() that a message can be fetched.\n        self.message_complete = asyncio.Event()\n        # get() sets this event to let put()\n        self.message_fetched = asyncio.Event()\n\n        # This flag prevents concurrent calls to get() by user code.\n        self.get_in_progress = False\n\n        # Decoder for text frames, None for binary frames.\n        self.decoder = None\n\n        # Buffer data from frames belonging to the same message.\n        self.chunks = []\n\n        # When switching from \"buffering\" to \"streaming\", we use a thread-safe\n        # queue for transferring frames from the writing thread (library code)\n        # to the reading thread (user code). We're buffering when chunks_queue\n        # is None and streaming when it's a Queue. None is a sentinel\n        # value marking the end of the stream, superseding message_complete.\n\n        # Stream data from frames belonging to the same message.\n        self.chunks_queue = None\n\n        # Flag to indicate we've paused the protocol\n        self.paused = False\n\n    async def get(self, timeout: float | None = None) -> Data | None:\n        \"\"\"\n        Read the next message.\n        :meth:`get` returns a single :class:`str` or :class:`bytes`.\n        If the :message was fragmented, :meth:`get` waits until the last frame\n        is received, then it reassembles the message.\n        If ``timeout`` is set and elapses before a complete message is\n        received, :meth:`get` returns ``None``.\n        \"\"\"\n        completed: bool\n        async with self.read_mutex:\n            if timeout is not None and timeout <= 0:\n                if not self.message_complete.is_set():\n                    return None\n            if self.get_in_progress:\n                # This should be guarded against with the read_mutex,\n                # exception is only here as a failsafe\n                raise ServerError(\n                    \"Called get() on Websocket frame assembler \"\n                    \"while asynchronous get is already in progress.\"\n                )\n            self.get_in_progress = True\n\n            # If the message_complete event isn't set yet, release the lock to\n            # allow put() to run and eventually set it.\n            # Locking with get_in_progress ensures only one task can get here.\n            if timeout is None:\n                completed = await self.message_complete.wait()\n            elif timeout <= 0:\n                completed = self.message_complete.is_set()\n            else:\n                try:\n                    await asyncio.wait_for(\n                        self.message_complete.wait(), timeout=timeout\n                    )\n                except asyncio.TimeoutError:\n                    ...\n                finally:\n                    completed = self.message_complete.is_set()\n\n            # Unpause the transport, if its paused\n            if self.paused:\n                self.protocol.resume_frames()\n                self.paused = False\n            if not self.get_in_progress:  # no cov\n                # This should be guarded against with the read_mutex,\n                # exception is here as a failsafe\n                raise ServerError(\n                    \"State of Websocket frame assembler was modified while an \"\n                    \"asynchronous get was in progress.\"\n                )\n            self.get_in_progress = False\n\n            # Waiting for a complete message timed out.\n            if not completed:\n                return None\n            if not self.message_complete.is_set():\n                return None\n\n            self.message_complete.clear()\n\n            joiner: Data = b\"\" if self.decoder is None else \"\"\n            # mypy cannot figure out that chunks have the proper type.\n            message: Data = joiner.join(self.chunks)  # type: ignore\n            if self.message_fetched.is_set():\n                # This should be guarded against with the read_mutex,\n                # and get_in_progress check, this exception is here\n                # as a failsafe\n                raise ServerError(\n                    \"Websocket get() found a message when \"\n                    \"state was already fetched.\"\n                )\n            self.message_fetched.set()\n            self.chunks = []\n            # this should already be None, but set it here for safety\n            self.chunks_queue = None\n            return message\n\n    async def get_iter(self) -> AsyncIterator[Data]:\n        \"\"\"\n        Stream the next message.\n        Iterating the return value of :meth:`get_iter` yields a :class:`str`\n        or :class:`bytes` for each frame in the message.\n        \"\"\"\n        async with self.read_mutex:\n            if self.get_in_progress:\n                # This should be guarded against with the read_mutex,\n                # exception is only here as a failsafe\n                raise ServerError(\n                    \"Called get_iter on Websocket frame assembler \"\n                    \"while asynchronous get is already in progress.\"\n                )\n            self.get_in_progress = True\n\n            chunks = self.chunks\n            self.chunks = []\n            self.chunks_queue = asyncio.Queue()\n\n            # Sending None in chunk_queue supersedes setting message_complete\n            # when switching to \"streaming\". If message is already complete\n            # when the switch happens, put() didn't send None, so we have to.\n            if self.message_complete.is_set():\n                await self.chunks_queue.put(None)\n\n            # Locking with get_in_progress ensures only one task can get here\n            for c in chunks:\n                yield c\n            while True:\n                chunk = await self.chunks_queue.get()\n                if chunk is None:\n                    break\n                yield chunk\n\n            # Unpause the transport, if its paused\n            if self.paused:\n                self.protocol.resume_frames()\n                self.paused = False\n            if not self.get_in_progress:  # no cov\n                # This should be guarded against with the read_mutex,\n                # exception is here as a failsafe\n                raise ServerError(\n                    \"State of Websocket frame assembler was modified while an \"\n                    \"asynchronous get was in progress.\"\n                )\n            self.get_in_progress = False\n            if not self.message_complete.is_set():  # no cov\n                # This should be guarded against with the read_mutex,\n                # exception is here as a failsafe\n                raise ServerError(\n                    \"Websocket frame assembler chunks queue ended before \"\n                    \"message was complete.\"\n                )\n            self.message_complete.clear()\n            if self.message_fetched.is_set():  # no cov\n                # This should be guarded against with the read_mutex,\n                # and get_in_progress check, this exception is\n                # here as a failsafe\n                raise ServerError(\n                    \"Websocket get_iter() found a message when state was \"\n                    \"already fetched.\"\n                )\n\n            self.message_fetched.set()\n            # this should already be empty, but set it here for safety\n            self.chunks = []\n            self.chunks_queue = None\n\n    async def put(self, frame: Frame) -> None:\n        \"\"\"\n        Add ``frame`` to the next message.\n        When ``frame`` is the final frame in a message, :meth:`put` waits\n        until the message is fetched, either by calling :meth:`get` or by\n        iterating the return value of :meth:`get_iter`.\n        :meth:`put` assumes that the stream of frames respects the protocol.\n        If it doesn't, the behavior is undefined.\n        \"\"\"\n\n        async with self.write_mutex:\n            if frame.opcode is Opcode.TEXT:\n                self.decoder = UTF8Decoder(errors=\"strict\")\n            elif frame.opcode is Opcode.BINARY:\n                self.decoder = None\n            elif frame.opcode is Opcode.CONT:\n                pass\n            else:\n                # Ignore control frames.\n                return\n            data: Data\n            if self.decoder is not None:\n                data = self.decoder.decode(frame.data, frame.fin)\n            else:\n                data = frame.data\n            if self.chunks_queue is None:\n                self.chunks.append(data)\n            else:\n                await self.chunks_queue.put(data)\n\n            if not frame.fin:\n                return\n            if not self.get_in_progress:\n                # nobody is waiting for this frame, so try to pause subsequent\n                # frames at the protocol level\n                self.paused = self.protocol.pause_frames()\n            # Message is complete. Wait until it's fetched to return.\n\n            if self.chunks_queue is not None:\n                await self.chunks_queue.put(None)\n            if self.message_complete.is_set():\n                # This should be guarded against with the write_mutex\n                raise ServerError(\n                    \"Websocket put() got a new message when a message was \"\n                    \"already in its chamber.\"\n                )\n            self.message_complete.set()  # Signal to get() it can serve the\n            if self.message_fetched.is_set():\n                # This should be guarded against with the write_mutex\n                raise ServerError(\n                    \"Websocket put() got a new message when the previous \"\n                    \"message was not yet fetched.\"\n                )\n\n            # Allow get() to run and eventually set the event.\n            await self.message_fetched.wait()\n            self.message_fetched.clear()\n            self.decoder = None\n"
  },
  {
    "path": "sanic/server/websockets/impl.py",
    "content": "import asyncio\nimport secrets\n\nfrom collections.abc import AsyncIterator, Iterable, Mapping, Sequence\n\nfrom websockets.exceptions import (\n    ConnectionClosed,\n    ConnectionClosedError,\n    ConnectionClosedOK,\n)\nfrom websockets.frames import Frame, Opcode\n\n\ntry:  # websockets >= 11.0\n    from websockets.protocol import Event, State  # type: ignore\n    from websockets.server import ServerProtocol  # type: ignore\nexcept ImportError:  # websockets < 11.0\n    from websockets.connection import Event, State  # type: ignore\n    from websockets.server import ServerConnection as ServerProtocol\n\nfrom websockets.typing import Data\n\nfrom sanic.log import websockets_logger\nfrom sanic.server.protocols.base_protocol import SanicProtocol\n\nfrom ...exceptions import ServerError, WebsocketClosed\nfrom .frame import WebsocketFrameAssembler\n\n\nOPEN = State.OPEN\nCLOSING = State.CLOSING\nCLOSED = State.CLOSED\n\n\nclass WebsocketImplProtocol:\n    ws_proto: ServerProtocol\n    io_proto: SanicProtocol | None\n    loop: asyncio.AbstractEventLoop | None\n    max_queue: int\n    close_timeout: float\n    ping_interval: float | None\n    ping_timeout: float | None\n    assembler: WebsocketFrameAssembler\n    # dict[bytes, asyncio.Future[None]]\n    pings: dict[bytes, asyncio.Future]\n    conn_mutex: asyncio.Lock\n    recv_lock: asyncio.Lock\n    recv_cancel: asyncio.Future | None\n    process_event_mutex: asyncio.Lock\n    can_pause: bool\n    # asyncio.Future[None] | None\n    data_finished_fut: asyncio.Future | None\n    # asyncio.Future[None] | None\n    pause_frame_fut: asyncio.Future | None\n    # asyncio.Future[None] | None\n    connection_lost_waiter: asyncio.Future | None\n    keepalive_ping_task: asyncio.Task | None\n    auto_closer_task: asyncio.Task | None\n\n    def __init__(\n        self,\n        ws_proto,\n        max_queue=None,\n        ping_interval: float | None = 20,\n        ping_timeout: float | None = 20,\n        close_timeout: float = 10,\n        loop=None,\n    ):\n        self.ws_proto = ws_proto\n        self.io_proto = None\n        self.loop = None\n        self.max_queue = max_queue\n        self.close_timeout = close_timeout\n        self.ping_interval = ping_interval\n        self.ping_timeout = ping_timeout\n        self.assembler = WebsocketFrameAssembler(self)\n        self.pings = {}\n        self.conn_mutex = asyncio.Lock()\n        self.recv_lock = asyncio.Lock()\n        self.recv_cancel = None\n        self.process_event_mutex = asyncio.Lock()\n        self.data_finished_fut = None\n        self.can_pause = True\n        self.pause_frame_fut = None\n        self.keepalive_ping_task = None\n        self.auto_closer_task = None\n        self.connection_lost_waiter = None\n\n    @property\n    def subprotocol(self):\n        return self.ws_proto.subprotocol\n\n    def pause_frames(self):\n        if not self.can_pause:\n            return False\n        if self.pause_frame_fut:\n            websockets_logger.debug(\"Websocket connection already paused.\")\n            return False\n        if (not self.loop) or (not self.io_proto):\n            return False\n        if self.io_proto.transport:\n            self.io_proto.transport.pause_reading()\n        self.pause_frame_fut = self.loop.create_future()\n        websockets_logger.debug(\"Websocket connection paused.\")\n        return True\n\n    def resume_frames(self):\n        if not self.pause_frame_fut:\n            websockets_logger.debug(\"Websocket connection not paused.\")\n            return False\n        if (not self.loop) or (not self.io_proto):\n            websockets_logger.debug(\n                \"Websocket attempting to resume reading frames, \"\n                \"but connection is gone.\"\n            )\n            return False\n        if self.io_proto.transport:\n            self.io_proto.transport.resume_reading()\n        self.pause_frame_fut.set_result(None)\n        self.pause_frame_fut = None\n        websockets_logger.debug(\"Websocket connection unpaused.\")\n        return True\n\n    async def connection_made(\n        self,\n        io_proto: SanicProtocol,\n        loop: asyncio.AbstractEventLoop | None = None,\n    ):\n        if not loop:\n            try:\n                loop = getattr(io_proto, \"loop\")\n            except AttributeError:\n                loop = asyncio.get_event_loop()\n        if not loop:\n            # This catch is for mypy type checker\n            # to assert loop is not None here.\n            raise ServerError(\"Connection received with no asyncio loop.\")\n        if self.auto_closer_task:\n            raise ServerError(\n                \"Cannot call connection_made more than once \"\n                \"on a websocket connection.\"\n            )\n        self.loop = loop\n        self.io_proto = io_proto\n        self.connection_lost_waiter = self.loop.create_future()\n        self.data_finished_fut = asyncio.shield(self.loop.create_future())\n\n        if self.ping_interval:\n            self.keepalive_ping_task = asyncio.create_task(\n                self.keepalive_ping()\n            )\n        self.auto_closer_task = asyncio.create_task(\n            self.auto_close_connection()\n        )\n\n    async def wait_for_connection_lost(self, timeout=None) -> bool:\n        \"\"\"\n        Wait until the TCP connection is closed or ``timeout`` elapses.\n        If timeout is None, wait forever.\n        Recommend you should pass in self.close_timeout as timeout\n\n        Return ``True`` if the connection is closed and ``False`` otherwise.\n\n        \"\"\"\n        if not self.connection_lost_waiter:\n            return False\n        if self.connection_lost_waiter.done():\n            return True\n        else:\n            try:\n                await asyncio.wait_for(\n                    asyncio.shield(self.connection_lost_waiter), timeout\n                )\n                return True\n            except asyncio.TimeoutError:\n                # Re-check self.connection_lost_waiter.done() synchronously\n                # because connection_lost() could run between the moment the\n                # timeout occurs and the moment this coroutine resumes running\n                return self.connection_lost_waiter.done()\n\n    async def process_events(self, events: Sequence[Event]) -> None:\n        \"\"\"\n        Process a list of incoming events.\n        \"\"\"\n        # Wrapped in a mutex lock, to prevent other incoming events\n        # from processing at the same time\n        async with self.process_event_mutex:\n            for event in events:\n                if not isinstance(event, Frame):\n                    # Event is not a frame. Ignore it.\n                    continue\n                if event.opcode == Opcode.PONG:\n                    await self.process_pong(event)\n                elif event.opcode == Opcode.CLOSE:\n                    if self.recv_cancel:\n                        self.recv_cancel.cancel()\n                else:\n                    await self.assembler.put(event)\n\n    async def process_pong(self, frame: Frame) -> None:\n        if frame.data in self.pings:\n            # Acknowledge all pings up to the one matching this pong.\n            ping_ids = []\n            for ping_id, ping in self.pings.items():\n                ping_ids.append(ping_id)\n                if not ping.done():\n                    ping.set_result(None)\n                if ping_id == frame.data:\n                    break\n            else:  # noqa\n                raise ServerError(\"ping_id is not in self.pings\")\n            # Remove acknowledged pings from self.pings.\n            for ping_id in ping_ids:\n                del self.pings[ping_id]\n\n    async def keepalive_ping(self) -> None:\n        \"\"\"\n        Send a Ping frame and wait for a Pong frame at regular intervals.\n        This coroutine exits when the connection terminates and one of the\n        following happens:\n        - :meth:`ping` raises :exc:`ConnectionClosed`, or\n        - :meth:`auto_close_connection` cancels :attr:`keepalive_ping_task`.\n        \"\"\"\n        if self.ping_interval is None:\n            return\n\n        try:\n            while True:\n                await asyncio.sleep(self.ping_interval)\n\n                # ping() raises CancelledError if the connection is closed,\n                # when auto_close_connection() cancels keepalive_ping_task.\n\n                # ping() raises ConnectionClosed if the connection is lost,\n                # when connection_lost() calls abort_pings().\n\n                ping_waiter = await self.ping()\n\n                if self.ping_timeout is not None:\n                    try:\n                        await asyncio.wait_for(ping_waiter, self.ping_timeout)\n                    except asyncio.TimeoutError:\n                        websockets_logger.warning(\n                            \"Websocket timed out waiting for pong\"\n                        )\n                        self.fail_connection(1011)\n                        break\n        except asyncio.CancelledError:\n            # It is expected for this task to be cancelled during during\n            # normal operation, when the connection is closed.\n            websockets_logger.debug(\n                \"Websocket keepalive ping task was cancelled.\"\n            )\n        except (ConnectionClosed, WebsocketClosed):\n            websockets_logger.debug(\n                \"Websocket closed. Keepalive ping task exiting.\"\n            )\n        except Exception as e:\n            websockets_logger.warning(\n                \"Unexpected exception in websocket keepalive ping task.\"\n            )\n            websockets_logger.debug(str(e))\n\n    def _force_disconnect(self) -> bool:\n        \"\"\"\n        Internal method used by end_connection and fail_connection\n        only when the graceful auto-closer cannot be used\n        \"\"\"\n        if self.auto_closer_task and not self.auto_closer_task.done():\n            self.auto_closer_task.cancel()\n        if self.data_finished_fut and not self.data_finished_fut.done():\n            self.data_finished_fut.cancel()\n            self.data_finished_fut = None\n        if self.keepalive_ping_task and not self.keepalive_ping_task.done():\n            self.keepalive_ping_task.cancel()\n            self.keepalive_ping_task = None\n        if self.loop and self.io_proto and self.io_proto.transport:\n            self.io_proto.transport.close()\n            self.loop.call_later(\n                self.close_timeout, self.io_proto.transport.abort\n            )\n        # We were never open, or already closed\n        return True\n\n    def fail_connection(self, code: int = 1006, reason: str = \"\") -> bool:\n        \"\"\"\n        Fail the WebSocket Connection\n        This requires:\n        1. Stopping all processing of incoming data, which means cancelling\n           pausing the underlying io protocol. The close code will be 1006\n           unless a close frame was received earlier.\n        2. Sending a close frame with an appropriate code if the opening\n           handshake succeeded and the other side is likely to process it.\n        3. Closing the connection. :meth:`auto_close_connection` takes care\n           of this.\n        (The specification describes these steps in the opposite order.)\n        \"\"\"\n        if self.io_proto and self.io_proto.transport:\n            # Stop new data coming in\n            # In Python Version 3.7: pause_reading is idempotent\n            # ut can be called when the transport is already paused or closed\n            self.io_proto.transport.pause_reading()\n\n            # Keeping fail_connection() synchronous guarantees it can't\n            # get stuck and simplifies the implementation of the callers.\n            # Not draining the write buffer is acceptable in this context.\n\n            # clear the send buffer\n            _ = self.ws_proto.data_to_send()\n            # If we're not already CLOSED or CLOSING, then send the close.\n            if self.ws_proto.state is OPEN:\n                if code in (1000, 1001):\n                    self.ws_proto.send_close(code, reason)\n                else:\n                    self.ws_proto.fail(code, reason)\n                try:\n                    data_to_send = self.ws_proto.data_to_send()\n                    while (\n                        len(data_to_send)\n                        and self.io_proto\n                        and self.io_proto.transport\n                    ):\n                        frame_data = data_to_send.pop(0)\n                        self.io_proto.transport.write(frame_data)\n                except Exception:\n                    # sending close frames may fail if the\n                    # transport closes during this period\n                    ...\n        if code == 1006:\n            # Special case: 1006 consider the transport already closed\n            self.ws_proto.state = CLOSED\n        if self.data_finished_fut and not self.data_finished_fut.done():\n            # We have a graceful auto-closer. Use it to close the connection.\n            self.data_finished_fut.cancel()\n            self.data_finished_fut = None\n        if (not self.auto_closer_task) or self.auto_closer_task.done():\n            return self._force_disconnect()\n        return False\n\n    def end_connection(self, code=1000, reason=\"\"):\n        # This is like slightly more graceful form of fail_connection\n        # Use this instead of close() when you need an immediate\n        # close and cannot await websocket.close() handshake.\n\n        if code == 1006 or not self.io_proto or not self.io_proto.transport:\n            return self.fail_connection(code, reason)\n\n        # Stop new data coming in\n        # In Python Version 3.7: pause_reading is idempotent\n        # i.e. it can be called when the transport is already paused or closed.\n        self.io_proto.transport.pause_reading()\n        if self.ws_proto.state == OPEN:\n            data_to_send = self.ws_proto.data_to_send()\n            self.ws_proto.send_close(code, reason)\n            data_to_send.extend(self.ws_proto.data_to_send())\n            try:\n                while (\n                    len(data_to_send)\n                    and self.io_proto\n                    and self.io_proto.transport\n                ):\n                    frame_data = data_to_send.pop(0)\n                    self.io_proto.transport.write(frame_data)\n            except Exception:\n                # sending close frames may fail if the\n                # transport closes during this period\n                # But that doesn't matter at this point\n                ...\n        if self.data_finished_fut and not self.data_finished_fut.done():\n            # We have the ability to signal the auto-closer\n            # try to trigger it to auto-close the connection\n            self.data_finished_fut.cancel()\n            self.data_finished_fut = None\n        if (not self.auto_closer_task) or self.auto_closer_task.done():\n            # Auto-closer is not running, do force disconnect\n            return self._force_disconnect()\n        return False\n\n    async def auto_close_connection(self) -> None:\n        \"\"\"\n        Close the WebSocket Connection\n        When the opening handshake succeeds, :meth:`connection_open` starts\n        this coroutine in a task. It waits for the data transfer phase to\n        complete then it closes the TCP connection cleanly.\n        When the opening handshake fails, :meth:`fail_connection` does the\n        same. There's no data transfer phase in that case.\n        \"\"\"\n        try:\n            # Wait for the data transfer phase to complete.\n            if self.data_finished_fut:\n                try:\n                    await self.data_finished_fut\n                    websockets_logger.debug(\n                        \"Websocket task finished. Closing the connection.\"\n                    )\n                except asyncio.CancelledError:\n                    # Cancelled error is called when data phase is cancelled\n                    # if an error occurred or the client closed the connection\n                    websockets_logger.debug(\n                        \"Websocket handler cancelled. Closing the connection.\"\n                    )\n\n            # Cancel the keepalive ping task.\n            if self.keepalive_ping_task:\n                self.keepalive_ping_task.cancel()\n                self.keepalive_ping_task = None\n\n            # Half-close the TCP connection if possible (when there's no TLS).\n            if (\n                self.io_proto\n                and self.io_proto.transport\n                and self.io_proto.transport.can_write_eof()\n            ):\n                websockets_logger.debug(\n                    \"Websocket half-closing TCP connection\"\n                )\n                try:\n                    self.io_proto.transport.write_eof()\n                except RuntimeError:\n                    ...\n                if self.connection_lost_waiter:\n                    if await self.wait_for_connection_lost(timeout=0):\n                        return\n        except asyncio.CancelledError:\n            ...\n        except BaseException:\n            websockets_logger.exception(\"Error closing websocket connection\")\n        finally:\n            # Does this still exist?\n            if self.keepalive_ping_task:\n                self.keepalive_ping_task.cancel()\n                self.keepalive_ping_task = None\n            # The try/finally ensures that the transport never remains open,\n            # even if this coroutine is cancelled (for example).\n            if (not self.io_proto) or (not self.io_proto.transport):\n                # we were never open, or done. Can't do any finalization.\n                return\n            elif (\n                self.connection_lost_waiter\n                and self.connection_lost_waiter.done()\n            ):\n                # connection confirmed closed already, proceed to abort waiter\n                ...\n            elif self.io_proto.transport.is_closing():\n                # Connection is already closing (due to half-close above)\n                # proceed to abort waiter\n                ...\n            else:\n                self.io_proto.transport.close()\n            if not self.connection_lost_waiter:\n                # Our connection monitor task isn't running.\n                try:\n                    await asyncio.sleep(self.close_timeout)\n                except asyncio.CancelledError:\n                    ...\n                if self.io_proto and self.io_proto.transport:\n                    self.io_proto.transport.abort()\n            else:\n                if await self.wait_for_connection_lost(\n                    timeout=self.close_timeout\n                ):\n                    # Connection aborted before the timeout expired.\n                    return\n                websockets_logger.warning(\n                    \"Timeout waiting for TCP connection to close. Aborting\"\n                )\n                if self.io_proto and self.io_proto.transport:\n                    self.io_proto.transport.abort()\n\n    def abort_pings(self) -> None:\n        \"\"\"\n        Raise ConnectionClosed in pending keepalive pings.\n        They'll never receive a pong once the connection is closed.\n        \"\"\"\n        if self.ws_proto.state is not CLOSED:\n            raise ServerError(\n                \"Webscoket about_pings should only be called \"\n                \"after connection state is changed to CLOSED\"\n            )\n\n        for ping in self.pings.values():\n            ping.set_exception(ConnectionClosedError(None, None))\n            # If the exception is never retrieved, it will be logged when ping\n            # is garbage-collected. This is confusing for users.\n            # Given that ping is done (with an exception), canceling it does\n            # nothing, but it prevents logging the exception.\n            ping.cancel()\n\n    async def close(self, code: int = 1000, reason: str = \"\") -> None:\n        \"\"\"\n        Perform the closing handshake.\n        This is a websocket-protocol level close.\n        :meth:`close` waits for the other end to complete the handshake and\n        for the TCP connection to terminate.\n        :meth:`close` is idempotent: it doesn't do anything once the\n        connection is closed.\n        :param code: WebSocket close code\n        :param reason: WebSocket close reason\n        \"\"\"\n        if code == 1006:\n            self.fail_connection(code, reason)\n            return\n        async with self.conn_mutex:\n            if self.ws_proto.state is OPEN:\n                self.ws_proto.send_close(code, reason)\n                data_to_send = self.ws_proto.data_to_send()\n                await self.send_data(data_to_send)\n\n    async def recv(self, timeout: float | None = None) -> Data | None:\n        \"\"\"\n        Receive the next message.\n        Return a :class:`str` for a text frame and :class:`bytes` for a binary\n        frame.\n        When the end of the message stream is reached, :meth:`recv` raises\n        :exc:`~websockets.exceptions.ConnectionClosed`. Specifically, it\n        raises :exc:`~websockets.exceptions.ConnectionClosedOK` after a normal\n        connection closure and\n        :exc:`~websockets.exceptions.ConnectionClosedError` after a protocol\n        error or a network failure.\n        If ``timeout`` is ``None``, block until a message is received. Else,\n        if no message is received within ``timeout`` seconds, return ``None``.\n        Set ``timeout`` to ``0`` to check if a message was already received.\n        :raises ~websockets.exceptions.ConnectionClosed: when the\n            connection is closed\n        :raises asyncio.CancelledError: if the websocket closes while waiting\n        :raises ServerError: if two tasks call :meth:`recv` or\n            :meth:`recv_streaming` concurrently\n        \"\"\"\n\n        if self.recv_lock.locked():\n            raise ServerError(\n                \"cannot call recv while another task is \"\n                \"already waiting for the next message\"\n            )\n        await self.recv_lock.acquire()\n        if self.ws_proto.state is CLOSED:\n            self.recv_lock.release()\n            raise WebsocketClosed(\n                \"Cannot receive from websocket interface after it is closed.\"\n            )\n        assembler_get: asyncio.Task | None = None\n        try:\n            self.recv_cancel = asyncio.Future()\n            assembler_get = asyncio.create_task(self.assembler.get(timeout))\n            tasks = (self.recv_cancel, assembler_get)\n            done, pending = await asyncio.wait(\n                tasks,\n                return_when=asyncio.FIRST_COMPLETED,\n            )\n            done_task = next(iter(done))\n            if done_task is self.recv_cancel:\n                # recv was cancelled\n                for p in pending:\n                    p.cancel()\n                raise asyncio.CancelledError()\n            else:\n                self.recv_cancel.cancel()\n                return done_task.result()\n        except asyncio.CancelledError:\n            # recv was cancelled\n            if assembler_get:\n                assembler_get.cancel()\n            raise\n        finally:\n            self.recv_cancel = None\n            self.recv_lock.release()\n\n    async def recv_burst(self, max_recv=256) -> Sequence[Data]:\n        \"\"\"\n        Receive the messages which have arrived since last checking.\n        Return a :class:`list` containing :class:`str` for a text frame\n        and :class:`bytes` for a binary frame.\n        When the end of the message stream is reached, :meth:`recv_burst`\n        raises :exc:`~websockets.exceptions.ConnectionClosed`. Specifically,\n        it raises :exc:`~websockets.exceptions.ConnectionClosedOK` after a\n        normal connection closure and\n        :exc:`~websockets.exceptions.ConnectionClosedError` after a protocol\n        error or a network failure.\n        :raises ~websockets.exceptions.ConnectionClosed: when the\n            connection is closed\n        :raises ServerError: if two tasks call :meth:`recv_burst` or\n            :meth:`recv_streaming` concurrently\n        \"\"\"\n\n        if self.recv_lock.locked():\n            raise ServerError(\n                \"cannot call recv_burst while another task is already waiting \"\n                \"for the next message\"\n            )\n        await self.recv_lock.acquire()\n        if self.ws_proto.state is CLOSED:\n            self.recv_lock.release()\n            raise WebsocketClosed(\n                \"Cannot receive from websocket interface after it is closed.\"\n            )\n        messages = []\n        assembler_get: asyncio.Task | None = None\n        try:\n            # Prevent pausing the transport when we're\n            # receiving a burst of messages\n            self.can_pause = False\n            self.recv_cancel = asyncio.Future()\n            while True:\n                assembler_get = asyncio.create_task(self.assembler.get(0))\n                tasks = (self.recv_cancel, assembler_get)\n                done, pending = await asyncio.wait(\n                    tasks,\n                    return_when=asyncio.FIRST_COMPLETED,\n                )\n                done_task = next(iter(done))\n                if done_task is self.recv_cancel:\n                    # recv_burst was cancelled\n                    for p in pending:\n                        p.cancel()\n                    raise asyncio.CancelledError()\n                m = done_task.result()\n                if m is None:\n                    # None left in the burst. This is good!\n                    break\n                messages.append(m)\n                if len(messages) >= max_recv:\n                    # Too much data in the pipe. Hit our burst limit.\n                    break\n                # Allow an eventloop iteration for the\n                # next message to pass into the Assembler\n                await asyncio.sleep(0)\n            self.recv_cancel.cancel()\n        except asyncio.CancelledError:\n            # recv_burst was cancelled\n            if assembler_get:\n                assembler_get.cancel()\n            raise\n        finally:\n            self.recv_cancel = None\n            self.can_pause = True\n            self.recv_lock.release()\n        return messages\n\n    async def recv_streaming(self) -> AsyncIterator[Data]:\n        \"\"\"\n        Receive the next message frame by frame.\n        Return an iterator of :class:`str` for a text frame and :class:`bytes`\n        for a binary frame. The iterator should be exhausted, or else the\n        connection will become unusable.\n        With the exception of the return value, :meth:`recv_streaming` behaves\n        like :meth:`recv`.\n        \"\"\"\n        if self.recv_lock.locked():\n            raise ServerError(\n                \"Cannot call recv_streaming while another task \"\n                \"is already waiting for the next message\"\n            )\n        await self.recv_lock.acquire()\n        if self.ws_proto.state is CLOSED:\n            self.recv_lock.release()\n            raise WebsocketClosed(\n                \"Cannot receive from websocket interface after it is closed.\"\n            )\n        try:\n            cancelled = False\n            self.recv_cancel = asyncio.Future()\n            self.can_pause = False\n            async for m in self.assembler.get_iter():\n                if self.recv_cancel.done():\n                    cancelled = True\n                    break\n                yield m\n            if cancelled:\n                raise asyncio.CancelledError()\n        finally:\n            self.can_pause = True\n            self.recv_cancel = None\n            self.recv_lock.release()\n\n    async def send(self, message: Data | Iterable[Data]) -> None:\n        \"\"\"\n        Send a message.\n        A string (:class:`str`) is sent as a `Text frame`_. A bytestring or\n        bytes-like object (:class:`bytes`, :class:`bytearray`, or\n        :class:`memoryview`) is sent as a `Binary frame`_.\n        .. _Text frame: https://tools.ietf.org/html/rfc6455#section-5.6\n        .. _Binary frame: https://tools.ietf.org/html/rfc6455#section-5.6\n        :meth:`send` also accepts an iterable of strings, bytestrings, or\n        bytes-like objects. In that case the message is fragmented. Each item\n        is treated as a message fragment and sent in its own frame. All items\n        must be of the same type, or else :meth:`send` will raise a\n        :exc:`TypeError` and the connection will be closed.\n        :meth:`send` rejects dict-like objects because this is often an error.\n        If you wish to send the keys of a dict-like object as fragments, call\n        its :meth:`~dict.keys` method and pass the result to :meth:`send`.\n        :raises TypeError: for unsupported inputs\n        \"\"\"\n        async with self.conn_mutex:\n            if self.ws_proto.state in (CLOSED, CLOSING):\n                raise WebsocketClosed(\n                    \"Cannot write to websocket interface after it is closed.\"\n                )\n            if (not self.data_finished_fut) or self.data_finished_fut.done():\n                raise ServerError(\n                    \"Cannot write to websocket interface after it is finished.\"\n                )\n\n            # Unfragmented message -- this case must be handled first because\n            # strings and bytes-like objects are iterable.\n\n            if isinstance(message, str):\n                self.ws_proto.send_text(message.encode(\"utf-8\"))\n                await self.send_data(self.ws_proto.data_to_send())\n\n            elif isinstance(message, (bytes, bytearray, memoryview)):\n                self.ws_proto.send_binary(message)\n                await self.send_data(self.ws_proto.data_to_send())\n\n            elif isinstance(message, Mapping):\n                # Catch a common mistake -- passing a dict to send().\n                raise TypeError(\"data is a dict-like object\")\n\n            elif isinstance(message, Iterable):\n                # Fragmented message -- regular iterator.\n                raise NotImplementedError(\n                    \"Fragmented websocket messages are not supported.\"\n                )\n            else:\n                raise TypeError(\"Websocket data must be bytes, str.\")\n\n    async def ping(self, data: Data | None = None) -> asyncio.Future:\n        \"\"\"\n        Send a ping.\n        Return an :class:`~asyncio.Future` that will be resolved when the\n        corresponding pong is received. You can ignore it if you don't intend\n        to wait.\n        A ping may serve as a keepalive or as a check that the remote endpoint\n        received all messages up to this point::\n            await pong_event = ws.ping()\n            await pong_event # only if you want to wait for the pong\n        By default, the ping contains four random bytes. This payload may be\n        overridden with the optional ``data`` argument which must be a string\n        (which will be encoded to UTF-8) or a bytes-like object.\n        \"\"\"\n        async with self.conn_mutex:\n            if self.ws_proto.state in (CLOSED, CLOSING):\n                raise WebsocketClosed(\n                    \"Cannot send a ping when the websocket interface \"\n                    \"is closed.\"\n                )\n            if (not self.io_proto) or (not self.io_proto.loop):\n                raise ServerError(\n                    \"Cannot send a ping when the websocket has no I/O \"\n                    \"protocol attached.\"\n                )\n            if data is not None:\n                if isinstance(data, str):\n                    data = data.encode(\"utf-8\")\n                elif isinstance(data, (bytearray, memoryview)):\n                    data = bytes(data)\n\n            # Protect against duplicates if a payload is explicitly set.\n            if data in self.pings:\n                raise ValueError(\n                    \"already waiting for a pong with the same data\"\n                )\n\n            # Generate a unique random payload otherwise.\n            while data is None or data in self.pings:\n                data = secrets.token_bytes(4)\n\n            self.pings[data] = self.io_proto.loop.create_future()\n\n            self.ws_proto.send_ping(data)\n            await self.send_data(self.ws_proto.data_to_send())\n\n            return asyncio.shield(self.pings[data])\n\n    async def pong(self, data: Data = b\"\") -> None:\n        \"\"\"\n        Send a pong.\n        An unsolicited pong may serve as a unidirectional heartbeat.\n        The payload may be set with the optional ``data`` argument which must\n        be a string (which will be encoded to UTF-8) or a bytes-like object.\n        \"\"\"\n        async with self.conn_mutex:\n            if self.ws_proto.state in (CLOSED, CLOSING):\n                # Cannot send pong after transport is shutting down\n                return\n            if isinstance(data, str):\n                data = data.encode(\"utf-8\")\n            elif isinstance(data, (bytearray, memoryview)):\n                data = bytes(data)\n            self.ws_proto.send_pong(data)\n            await self.send_data(self.ws_proto.data_to_send())\n\n    async def send_data(self, data_to_send):\n        for data in data_to_send:\n            if data:\n                await self.io_proto.send(data)\n            else:\n                # Send an EOF - We don't actually send it,\n                # just trigger to autoclose the connection\n                if (\n                    self.auto_closer_task\n                    and not self.auto_closer_task.done()\n                    and self.data_finished_fut\n                    and not self.data_finished_fut.done()\n                ):\n                    # Auto-close the connection\n                    self.data_finished_fut.set_result(None)\n                else:\n                    # This will fail the connection appropriately\n                    SanicProtocol.close(self.io_proto, timeout=1.0)\n\n    async def async_data_received(self, data_to_send, events_to_process):\n        if self.ws_proto.state in (OPEN, CLOSING) and len(data_to_send) > 0:\n            # receiving data can generate data to send (eg, pong for a ping)\n            # send connection.data_to_send()\n            await self.send_data(data_to_send)\n        if len(events_to_process) > 0:\n            await self.process_events(events_to_process)\n\n    def data_received(self, data):\n        self.ws_proto.receive_data(data)\n        data_to_send = self.ws_proto.data_to_send()\n        events_to_process = self.ws_proto.events_received()\n        if len(data_to_send) > 0 or len(events_to_process) > 0:\n            asyncio.create_task(\n                self.async_data_received(data_to_send, events_to_process)\n            )\n\n    async def async_eof_received(self, data_to_send, events_to_process):\n        # receiving EOF can generate data to send\n        # send connection.data_to_send()\n        if self.ws_proto.state in (OPEN, CLOSING) and len(data_to_send) > 0:\n            await self.send_data(data_to_send)\n        if len(events_to_process) > 0:\n            await self.process_events(events_to_process)\n        if self.recv_cancel:\n            self.recv_cancel.cancel()\n        if (\n            self.auto_closer_task\n            and not self.auto_closer_task.done()\n            and self.data_finished_fut\n            and not self.data_finished_fut.done()\n        ):\n            # Auto-close the connection\n            self.data_finished_fut.set_result(None)\n            # Cancel the running handler if its waiting\n        else:\n            # This will fail the connection appropriately\n            SanicProtocol.close(self.io_proto, timeout=1.0)\n\n    def eof_received(self) -> bool | None:\n        self.ws_proto.receive_eof()\n        data_to_send = self.ws_proto.data_to_send()\n        events_to_process = self.ws_proto.events_received()\n        asyncio.create_task(\n            self.async_eof_received(data_to_send, events_to_process)\n        )\n        return False\n\n    def connection_lost(self, exc):\n        \"\"\"\n        The WebSocket Connection is Closed.\n        \"\"\"\n        if not self.ws_proto.state == CLOSED:\n            # signal to the websocket connection handler\n            # we've lost the connection\n            self.ws_proto.fail(code=1006)\n            self.ws_proto.state = CLOSED\n\n        self.abort_pings()\n        if self.connection_lost_waiter:\n            self.connection_lost_waiter.set_result(None)\n\n    async def __aiter__(self):\n        try:\n            while True:\n                yield await self.recv()\n        except ConnectionClosedOK:\n            return\n"
  },
  {
    "path": "sanic/signals.py",
    "content": "from __future__ import annotations\n\nimport asyncio\n\nfrom collections import deque\nfrom dataclasses import dataclass\nfrom enum import Enum\nfrom inspect import isawaitable\nfrom typing import Any, cast\n\nfrom sanic_routing import BaseRouter, Route, RouteGroup\nfrom sanic_routing.exceptions import NotFound\nfrom sanic_routing.utils import path_to_parts\n\nfrom sanic.exceptions import InvalidSignal\nfrom sanic.log import error_logger, logger\nfrom sanic.models.handler_types import SignalHandler\n\n\nclass Event(Enum):\n    \"\"\"Event names for the SignalRouter\"\"\"\n\n    SERVER_EXCEPTION_REPORT = \"server.exception.report\"\n    SERVER_INIT_AFTER = \"server.init.after\"\n    SERVER_INIT_BEFORE = \"server.init.before\"\n    SERVER_SHUTDOWN_AFTER = \"server.shutdown.after\"\n    SERVER_SHUTDOWN_BEFORE = \"server.shutdown.before\"\n    HTTP_LIFECYCLE_BEGIN = \"http.lifecycle.begin\"\n    HTTP_LIFECYCLE_COMPLETE = \"http.lifecycle.complete\"\n    HTTP_LIFECYCLE_EXCEPTION = \"http.lifecycle.exception\"\n    HTTP_LIFECYCLE_HANDLE = \"http.lifecycle.handle\"\n    HTTP_LIFECYCLE_READ_BODY = \"http.lifecycle.read_body\"\n    HTTP_LIFECYCLE_READ_HEAD = \"http.lifecycle.read_head\"\n    HTTP_LIFECYCLE_REQUEST = \"http.lifecycle.request\"\n    HTTP_LIFECYCLE_RESPONSE = \"http.lifecycle.response\"\n    HTTP_ROUTING_AFTER = \"http.routing.after\"\n    HTTP_ROUTING_BEFORE = \"http.routing.before\"\n    HTTP_HANDLER_AFTER = \"http.handler.after\"\n    HTTP_HANDLER_BEFORE = \"http.handler.before\"\n    HTTP_LIFECYCLE_SEND = \"http.lifecycle.send\"\n    HTTP_MIDDLEWARE_AFTER = \"http.middleware.after\"\n    HTTP_MIDDLEWARE_BEFORE = \"http.middleware.before\"\n    WEBSOCKET_HANDLER_AFTER = \"websocket.handler.after\"\n    WEBSOCKET_HANDLER_BEFORE = \"websocket.handler.before\"\n    WEBSOCKET_HANDLER_EXCEPTION = \"websocket.handler.exception\"\n\n\nRESERVED_NAMESPACES = {\n    \"server\": (\n        Event.SERVER_EXCEPTION_REPORT.value,\n        Event.SERVER_INIT_AFTER.value,\n        Event.SERVER_INIT_BEFORE.value,\n        Event.SERVER_SHUTDOWN_AFTER.value,\n        Event.SERVER_SHUTDOWN_BEFORE.value,\n    ),\n    \"http\": (\n        Event.HTTP_LIFECYCLE_BEGIN.value,\n        Event.HTTP_LIFECYCLE_COMPLETE.value,\n        Event.HTTP_LIFECYCLE_EXCEPTION.value,\n        Event.HTTP_LIFECYCLE_HANDLE.value,\n        Event.HTTP_LIFECYCLE_READ_BODY.value,\n        Event.HTTP_LIFECYCLE_READ_HEAD.value,\n        Event.HTTP_LIFECYCLE_REQUEST.value,\n        Event.HTTP_LIFECYCLE_RESPONSE.value,\n        Event.HTTP_ROUTING_AFTER.value,\n        Event.HTTP_ROUTING_BEFORE.value,\n        Event.HTTP_HANDLER_AFTER.value,\n        Event.HTTP_HANDLER_BEFORE.value,\n        Event.HTTP_LIFECYCLE_SEND.value,\n        Event.HTTP_MIDDLEWARE_AFTER.value,\n        Event.HTTP_MIDDLEWARE_BEFORE.value,\n    ),\n    \"websocket\": {\n        Event.WEBSOCKET_HANDLER_AFTER.value,\n        Event.WEBSOCKET_HANDLER_BEFORE.value,\n        Event.WEBSOCKET_HANDLER_EXCEPTION.value,\n    },\n}\n\nGENERIC_SIGNAL_FORMAT = \"__generic__.__signal__.%s\"\n\n\ndef _blank(): ...\n\n\nclass Signal(Route):\n    \"\"\"A `Route` that is used to dispatch signals to handlers\"\"\"\n\n\n@dataclass\nclass SignalWaiter:\n    \"\"\"A record representing a future waiting for a signal\"\"\"\n\n    signal: Signal\n    event_definition: str\n    trigger: str = \"\"\n    requirements: dict[str, str] | None = None\n    exclusive: bool = True\n\n    future: asyncio.Future | None = None\n\n    async def wait(self):\n        \"\"\"Block until the signal is next dispatched.\n\n        Return the context of the signal dispatch, if any.\n        \"\"\"\n        loop = asyncio.get_running_loop()\n        self.future = loop.create_future()\n        self.signal.ctx.waiters.append(self)\n        try:\n            return await self.future\n        finally:\n            self.signal.ctx.waiters.remove(self)\n\n    def matches(self, event, condition):\n        return (\n            (condition is None and not self.exclusive)\n            or (condition is None and not self.requirements)\n            or condition == self.requirements\n        ) and (self.trigger or event == self.event_definition)\n\n\nclass SignalGroup(RouteGroup):\n    \"\"\"A `RouteGroup` that is used to dispatch signals to handlers\"\"\"\n\n\nclass SignalRouter(BaseRouter):\n    \"\"\"A `BaseRouter` that is used to dispatch signals to handlers\"\"\"\n\n    def __init__(self) -> None:\n        super().__init__(\n            delimiter=\".\",\n            route_class=Signal,\n            group_class=SignalGroup,\n            stacking=True,\n        )\n        self.allow_fail_builtin = True\n        self.ctx.loop = None\n\n    @staticmethod\n    def format_event(event: str | Enum) -> str:\n        \"\"\"Ensure event strings in proper format\n\n        Args:\n            event (str): event string\n\n        Returns:\n            str: formatted event string\n        \"\"\"\n        if isinstance(event, Enum):\n            event = str(event.value)\n        if \".\" not in event:\n            event = GENERIC_SIGNAL_FORMAT % event\n        return event\n\n    def get(  # type: ignore\n        self,\n        event: str | Enum,\n        condition: dict[str, str] | None = None,\n    ):\n        \"\"\"Get the handlers for a signal\n\n        Args:\n            event (str): The event to get the handlers for\n            condition (Optional[Dict[str, str]], optional): A dictionary of conditions to match against the handlers. Defaults to `None`.\n\n        Returns:\n            Tuple[SignalGroup, List[SignalHandler], Dict[str, Any]]: A tuple of the `SignalGroup` that matched, a list of the handlers that matched, and a dictionary of the params that matched\n\n        Raises:\n            NotFound: If no handlers are found\n        \"\"\"  # noqa: E501\n        event = self.format_event(event)\n        extra = condition or {}\n        try:\n            group, param_basket = self.find_route(\n                f\".{event}\",\n                self.DEFAULT_METHOD,\n                self,\n                {\"__params__\": {}, \"__matches__\": {}},\n                extra=extra,\n            )\n        except NotFound:\n            message = \"Could not find signal %s\"\n            terms: list[str | dict[str, str] | None] = [event]\n            if extra:\n                message += \" with %s\"\n                terms.append(extra)\n            raise NotFound(message % tuple(terms))\n\n        # Regex routes evaluate and can extract params directly. They are set\n        # on param_basket[\"__params__\"]\n        params = param_basket[\"__params__\"]\n        if not params:\n            # If param_basket[\"__params__\"] does not exist, we might have\n            # param_basket[\"__matches__\"], which are indexed based matches\n            # on path segments. They should already be cast types.\n            params = {\n                param.name: param_basket[\"__matches__\"][idx]\n                for idx, param in group.params.items()\n            }\n\n        return group, [route.handler for route in group], params\n\n    async def _dispatch(\n        self,\n        event: str,\n        context: dict[str, Any] | None = None,\n        condition: dict[str, str] | None = None,\n        fail_not_found: bool = True,\n        reverse: bool = False,\n    ) -> Any:\n        event = self.format_event(event)\n        try:\n            group, handlers, params = self.get(event, condition=condition)\n        except NotFound as e:\n            is_reserved = event.split(\".\", 1)[0] in RESERVED_NAMESPACES\n            if fail_not_found and (not is_reserved or self.allow_fail_builtin):\n                raise e\n            else:\n                if self.ctx.app.debug and self.ctx.app.state.verbosity >= 1:\n                    error_logger.warning(str(e))\n                return None\n\n        if context:\n            params.update(context)\n        params.pop(\"__trigger__\", None)\n\n        signals = group.routes\n        if not reverse:\n            signals = signals[::-1]\n        try:\n            for signal in signals:\n                for waiter in signal.ctx.waiters:\n                    if waiter.matches(event, condition):\n                        waiter.future.set_result(dict(params))\n\n            for signal in signals:\n                requirements = signal.extra.requirements\n                if (\n                    (condition is None and signal.ctx.exclusive is False)\n                    or (condition is None and not requirements)\n                    or (condition == requirements)\n                ) and (signal.ctx.trigger or event == signal.ctx.definition):\n                    maybe_coroutine = signal.handler(**params)\n                    if isawaitable(maybe_coroutine):\n                        retval = await maybe_coroutine\n                        if retval:\n                            return retval\n                    elif maybe_coroutine:\n                        return maybe_coroutine\n            return None\n        except Exception as e:\n            if self.ctx.app.debug and self.ctx.app.state.verbosity >= 1:\n                error_logger.exception(e)\n\n            if event != Event.SERVER_EXCEPTION_REPORT.value:\n                await self.dispatch(\n                    Event.SERVER_EXCEPTION_REPORT.value,\n                    context={\"exception\": e},\n                )\n                setattr(e, \"__dispatched__\", True)\n            raise e\n\n    async def dispatch(\n        self,\n        event: str | Enum,\n        *,\n        context: dict[str, Any] | None = None,\n        condition: dict[str, str] | None = None,\n        fail_not_found: bool = True,\n        inline: bool = False,\n        reverse: bool = False,\n    ) -> asyncio.Task | Any:\n        \"\"\"Dispatch a signal to all handlers that match the event\n\n        Args:\n            event (str): The event to dispatch\n            context (Optional[Dict[str, Any]], optional): A dictionary of context to pass to the handlers. Defaults to `None`.\n            condition (Optional[Dict[str, str]], optional): A dictionary of conditions to match against the handlers. Defaults to `None`.\n            fail_not_found (bool, optional): Whether to raise an exception if no handlers are found. Defaults to `True`.\n            inline (bool, optional): Whether to run the handlers inline. An inline run means it will return the value of the signal handler. When `False` (which is the default) the signal handler will run in a background task. Defaults to `False`.\n            reverse (bool, optional): Whether to run the handlers in reverse order. Defaults to `False`.\n\n        Returns:\n            Union[asyncio.Task, Any]: If `inline` is `True` then the return value of the signal handler will be returned. If `inline` is `False` then an `asyncio.Task` will be returned.\n\n        Raises:\n            RuntimeError: If the signal is dispatched outside of an event loop\n        \"\"\"  # noqa: E501\n\n        event = self.format_event(event)\n        dispatch = self._dispatch(\n            event,\n            context=context,\n            condition=condition,\n            fail_not_found=fail_not_found and inline,\n            reverse=reverse,\n        )\n        logger.debug(f\"Dispatching signal: {event}\", extra={\"verbosity\": 1})\n\n        if inline:\n            return await dispatch\n\n        task = asyncio.get_running_loop().create_task(dispatch)\n        await asyncio.sleep(0)\n        return task\n\n    def get_waiter(\n        self,\n        event: str | Enum,\n        condition: dict[str, Any] | None = None,\n        exclusive: bool = True,\n    ) -> SignalWaiter | None:\n        event_definition = self.format_event(event)\n        name, trigger, _ = self._get_event_parts(event_definition)\n        signal = cast(Signal, self.name_index.get(name))\n        if not signal:\n            return None\n\n        if event_definition.endswith(\".*\") and not trigger:\n            trigger = \"*\"\n        return SignalWaiter(\n            signal=signal,\n            event_definition=event_definition,\n            trigger=trigger,\n            requirements=condition,\n            exclusive=bool(exclusive),\n        )\n\n    def _get_event_parts(self, event: str) -> tuple[str, str, str]:\n        parts = self._build_event_parts(event)\n        if parts[2].startswith(\"<\"):\n            name = \".\".join([*parts[:-1], \"*\"])\n            trigger = self._clean_trigger(parts[2])\n        else:\n            name = event\n            trigger = \"\"\n\n        if not trigger:\n            event = \".\".join([*parts[:2], \"<__trigger__>\"])\n\n        return name, trigger, event\n\n    def add(  # type: ignore\n        self,\n        handler: SignalHandler,\n        event: str | Enum,\n        condition: dict[str, Any] | None = None,\n        exclusive: bool = True,\n        *,\n        priority: int = 0,\n    ) -> Signal:\n        event_definition = self.format_event(event)\n        name, trigger, event_string = self._get_event_parts(event_definition)\n\n        signal = super().add(\n            event_string,\n            handler,\n            name=name,\n            append=True,\n            priority=priority,\n        )  # type: ignore\n\n        signal.ctx.exclusive = exclusive\n        signal.ctx.trigger = trigger\n        signal.ctx.definition = event_definition\n        signal.extra.requirements = condition\n\n        return cast(Signal, signal)\n\n    def finalize(self, do_compile: bool = True, do_optimize: bool = False):\n        \"\"\"Finalize the router and compile the routes\n\n        Args:\n            do_compile (bool, optional): Whether to compile the routes. Defaults to `True`.\n            do_optimize (bool, optional): Whether to optimize the routes. Defaults to `False`.\n\n        Returns:\n            SignalRouter: The router\n\n        Raises:\n            RuntimeError: If the router is finalized outside of an event loop\n        \"\"\"  # noqa: E501\n        self.add(_blank, \"sanic.__signal__.__init__\")\n\n        try:\n            self.ctx.loop = asyncio.get_running_loop()\n        except RuntimeError:\n            raise RuntimeError(\"Cannot finalize signals outside of event loop\")\n\n        for signal in self.routes:\n            signal.ctx.waiters = deque()\n\n        return super().finalize(do_compile=do_compile, do_optimize=do_optimize)\n\n    def _build_event_parts(self, event: str) -> tuple[str, str, str]:\n        parts = path_to_parts(event, self.delimiter)\n        if (\n            len(parts) != 3\n            or parts[0].startswith(\"<\")\n            or parts[1].startswith(\"<\")\n        ):\n            raise InvalidSignal(\"Invalid signal event: %s\" % event)\n\n        if (\n            parts[0] in RESERVED_NAMESPACES\n            and event not in RESERVED_NAMESPACES[parts[0]]\n            and not (parts[2].startswith(\"<\") and parts[2].endswith(\">\"))\n        ):\n            raise InvalidSignal(\n                \"Cannot declare reserved signal event: %s\" % event\n            )\n        return parts\n\n    def _clean_trigger(self, trigger: str) -> str:\n        trigger = trigger[1:-1]\n        if \":\" in trigger:\n            trigger, _ = trigger.split(\":\")\n        return trigger\n"
  },
  {
    "path": "sanic/simple.py",
    "content": "from pathlib import Path\n\nfrom sanic import Sanic\nfrom sanic.exceptions import SanicException\n\n\ndef create_simple_server(directory: Path):\n    if not directory.is_dir():\n        raise SanicException(\n            \"Cannot setup Sanic Simple Server without a path to a directory\"\n        )\n\n    app = Sanic(\"SimpleServer\")\n    app.static(\n        \"/\", directory, name=\"main\", directory_view=True, index=\"index.html\"\n    )\n\n    return app\n"
  },
  {
    "path": "sanic/startup/__init__.py",
    "content": ""
  },
  {
    "path": "sanic/startup/errors.py",
    "content": "import errno\nimport sys\n\nfrom typing import Callable\n\nfrom sanic.exceptions import ServerError\nfrom sanic.log import error_logger\nfrom sanic.worker.daemon import DaemonError\n\n\nExceptionHandler = Callable[[Exception], bool]\n\n\ndef maybe_handle_startup_error(exc: Exception) -> None:\n    for handler in EXCEPTION_HANDLERS:\n        if handler(exc):\n            sys.exit(1)\n    raise exc\n\n\ndef _handle_os_error(exc: Exception) -> bool:\n    if not isinstance(exc, OSError):\n        return False\n\n    if exc.errno == errno.EADDRINUSE:\n        error_logger.error(\n            \"Startup failed: Address already in use. \\n\\n\"\n            \"Ensure no other process is using the same address and port, \"\n            \"or configure the server to use a different port.\"\n        )\n    else:\n        error_logger.error(\n            \"Startup failed due to OS error: %s (errno %s)\",\n            exc.strerror,\n            exc.errno,\n        )\n    return True\n\n\ndef _handle_server_error(exc: Exception) -> bool:\n    if not isinstance(exc, ServerError):\n        return False\n\n    error_logger.error(f\"Startup failed due to server error. {exc}\")\n    return True\n\n\ndef _handle_daemon_error(exc: Exception) -> bool:\n    if not isinstance(exc, DaemonError):\n        return False\n\n    error_logger.error(f\"Daemon error: {exc}\")\n    return True\n\n\nEXCEPTION_HANDLERS: tuple[ExceptionHandler, ...] = (\n    _handle_os_error,\n    _handle_server_error,\n    _handle_daemon_error,\n)\n"
  },
  {
    "path": "sanic/touchup/__init__.py",
    "content": "from .meta import TouchUpMeta\nfrom .service import TouchUp\n\n\n__all__ = (\n    \"TouchUp\",\n    \"TouchUpMeta\",\n)\n"
  },
  {
    "path": "sanic/touchup/meta.py",
    "content": "from sanic.base.meta import SanicMeta\nfrom sanic.exceptions import SanicException\n\nfrom .service import TouchUp\n\n\nclass TouchUpMeta(SanicMeta):\n    def __new__(cls, name, bases, attrs, **kwargs):\n        gen_class = super().__new__(cls, name, bases, attrs, **kwargs)\n\n        methods = attrs.get(\"__touchup__\")\n        attrs[\"__touched__\"] = False\n        if methods:\n            for method in methods:\n                if method not in attrs:\n                    raise SanicException(\n                        \"Cannot perform touchup on non-existent method: \"\n                        f\"{name}.{method}\"\n                    )\n                TouchUp.register(gen_class, method)\n\n        return gen_class\n"
  },
  {
    "path": "sanic/touchup/schemes/__init__.py",
    "content": "from .altsvc import AltSvcCheck  # noqa\nfrom .base import BaseScheme\nfrom .ode import OptionalDispatchEvent  # noqa\n\n\n__all__ = (\"BaseScheme\",)\n"
  },
  {
    "path": "sanic/touchup/schemes/altsvc.py",
    "content": "from __future__ import annotations\n\nfrom ast import Assign, Constant, NodeTransformer, Subscript\nfrom typing import TYPE_CHECKING, Any\n\nfrom sanic.http.constants import HTTP\n\nfrom .base import BaseScheme\n\n\nif TYPE_CHECKING:\n    from sanic import Sanic\n\n\nclass AltSvcCheck(BaseScheme):\n    ident = \"ALTSVC\"\n\n    def visitors(self) -> list[NodeTransformer]:\n        return [RemoveAltSvc(self.app, self.app.state.verbosity)]\n\n\nclass RemoveAltSvc(NodeTransformer):\n    def __init__(self, app: Sanic, verbosity: int = 0) -> None:\n        self._app = app\n        self._verbosity = verbosity\n        self._versions = {\n            info.settings[\"version\"] for info in app.state.server_info\n        }\n\n    def visit_Assign(self, node: Assign) -> Any:\n        if any(self._matches(target) for target in node.targets):\n            if self._should_remove():\n                return None\n            assert isinstance(node.value, Constant)\n            node.value.value = self.value()\n        return node\n\n    def _should_remove(self) -> bool:\n        return len(self._versions) == 1\n\n    @staticmethod\n    def _matches(node) -> bool:\n        return (\n            isinstance(node, Subscript)\n            and isinstance(node.slice, Constant)\n            and node.slice.value == \"alt-svc\"\n        )\n\n    def value(self):\n        values = []\n        for info in self._app.state.server_info:\n            port = info.settings[\"port\"]\n            version = info.settings[\"version\"]\n            if version is HTTP.VERSION_3:\n                values.append(f'h3=\":{port}\"')\n        return \", \".join(values)\n"
  },
  {
    "path": "sanic/touchup/schemes/base.py",
    "content": "from abc import ABC, abstractmethod\nfrom ast import NodeTransformer, parse\nfrom inspect import getsource\nfrom textwrap import dedent\nfrom typing import Any\n\n\nclass BaseScheme(ABC):\n    ident: str\n    _registry: set[type] = set()\n\n    def __init__(self, app) -> None:\n        self.app = app\n\n    @abstractmethod\n    def visitors(self) -> list[NodeTransformer]: ...\n\n    def __init_subclass__(cls):\n        BaseScheme._registry.add(cls)\n\n    def __call__(self):\n        return self.visitors()\n\n    @classmethod\n    def build(cls, method, module_globals, app):\n        raw_source = getsource(method)\n        src = dedent(raw_source)\n        node = parse(src)\n\n        for scheme in cls._registry:\n            for visitor in scheme(app)():\n                node = visitor.visit(node)\n\n        compiled_src = compile(node, method.__name__, \"exec\")\n        exec_locals: dict[str, Any] = {}\n        exec(compiled_src, module_globals, exec_locals)  # nosec\n        return exec_locals[method.__name__]\n"
  },
  {
    "path": "sanic/touchup/schemes/ode.py",
    "content": "from ast import Attribute, Await, Expr, NodeTransformer\nfrom typing import Any\n\nfrom sanic.log import logger\n\nfrom .base import BaseScheme\n\n\nclass OptionalDispatchEvent(BaseScheme):\n    ident = \"ODE\"\n    SYNC_SIGNAL_NAMESPACES = \"http.\"\n\n    def __init__(self, app) -> None:\n        super().__init__(app)\n\n        self._sync_events()\n        self._registered_events = [\n            signal.name for signal in app.signal_router.routes\n        ]\n\n    def visitors(self) -> list[NodeTransformer]:\n        return [RemoveDispatch(self._registered_events)]\n\n    def _sync_events(self):\n        all_events = set()\n        app_events = {}\n        for app in self.app.__class__._app_registry.values():\n            if app.state.server_info:\n                app_events[app] = {\n                    signal.name for signal in app.signal_router.routes\n                }\n                all_events.update(app_events[app])\n\n        for app, events in app_events.items():\n            missing = {\n                x\n                for x in all_events.difference(events)\n                if any(x.startswith(y) for y in self.SYNC_SIGNAL_NAMESPACES)\n            }\n            if missing:\n                was_finalized = app.signal_router.finalized\n                if was_finalized:  # no cov\n                    app.signal_router.reset()\n                for event in missing:\n                    app.signal(event)(self.noop)\n                if was_finalized:  # no cov\n                    app.signal_router.finalize()\n\n    @staticmethod\n    async def noop(**_):  # no cov\n        ...\n\n\nclass RemoveDispatch(NodeTransformer):\n    def __init__(self, registered_events) -> None:\n        self._registered_events = registered_events\n\n    def visit_Expr(self, node: Expr) -> Any:\n        call = node.value\n        if isinstance(call, Await):\n            call = call.value\n\n        func = getattr(call, \"func\", None)\n        args = getattr(call, \"args\", None)\n        if not func or not args:\n            return node\n\n        if isinstance(func, Attribute) and func.attr == \"dispatch\":\n            event = args[0]\n            if event_name := getattr(event, \"value\", None):\n                if self._not_registered(event_name):\n                    logger.debug(\n                        f\"Disabling event: {event_name}\",\n                        extra={\"verbosity\": 2},\n                    )\n                    return None\n        return node\n\n    def _not_registered(self, event_name):\n        dynamic = []\n        for event in self._registered_events:\n            if event.endswith(\">\"):\n                namespace_concern, _ = event.rsplit(\".\", 1)\n                dynamic.append(namespace_concern)\n\n        namespace_concern, _ = event_name.rsplit(\".\", 1)\n        return (\n            event_name not in self._registered_events\n            and namespace_concern not in dynamic\n        )\n"
  },
  {
    "path": "sanic/touchup/service.py",
    "content": "from inspect import getmembers, getmodule\n\nfrom .schemes import BaseScheme\n\n\nclass TouchUp:\n    _registry: set[tuple[type, str]] = set()\n\n    @classmethod\n    def run(cls, app):\n        for target, method_name in cls._registry:\n            method = getattr(target, method_name)\n\n            if app.test_mode:\n                placeholder = f\"_{method_name}\"\n                if hasattr(target, placeholder):\n                    method = getattr(target, placeholder)\n                else:\n                    setattr(target, placeholder, method)\n\n            module = getmodule(target)\n            module_globals = dict(getmembers(module))\n            modified = BaseScheme.build(method, module_globals, app)\n            setattr(target, method_name, modified)\n\n            target.__touched__ = True\n\n    @classmethod\n    def register(cls, target, method_name):\n        cls._registry.add((target, method_name))\n"
  },
  {
    "path": "sanic/types/__init__.py",
    "content": "from .hashable_dict import HashableDict\n\n\n__all__ = (\"HashableDict\",)\n"
  },
  {
    "path": "sanic/types/hashable_dict.py",
    "content": "class HashableDict(dict):\n    def __hash__(self):\n        return hash(tuple(sorted(self.items())))\n"
  },
  {
    "path": "sanic/types/shared_ctx.py",
    "content": "import os\n\nfrom collections.abc import Iterable\nfrom types import SimpleNamespace\nfrom typing import Any\n\nfrom sanic.log import Colors, error_logger\n\n\nclass SharedContext(SimpleNamespace):\n    SAFE = (\"_lock\",)\n\n    def __init__(self, **kwargs: Any) -> None:\n        super().__init__(**kwargs)\n        self._lock = False\n\n    def __setattr__(self, name: str, value: Any) -> None:\n        if self.is_locked:\n            raise RuntimeError(\n                f\"Cannot set {name} on locked SharedContext object\"\n            )\n        if not os.environ.get(\"SANIC_WORKER_NAME\"):\n            to_check: Iterable[Any]\n            if not isinstance(value, (tuple, frozenset)):\n                to_check = [value]\n            else:\n                to_check = value\n            for item in to_check:\n                self._check(name, item)\n        super().__setattr__(name, value)\n\n    def _check(self, name: str, value: Any) -> None:\n        if name in self.SAFE:\n            return\n        try:\n            module = value.__module__\n        except AttributeError:\n            module = \"\"\n        if not any(\n            module.startswith(prefix)\n            for prefix in (\"multiprocessing\", \"ctypes\")\n        ):\n            error_logger.warning(\n                f\"{Colors.YELLOW}Unsafe object {Colors.PURPLE}{name} \"\n                f\"{Colors.YELLOW}with type {Colors.PURPLE}{type(value)} \"\n                f\"{Colors.YELLOW}was added to shared_ctx. It may not \"\n                \"not function as intended. Consider using the regular \"\n                f\"ctx.\\nFor more information, please see https://sanic.dev/en\"\n                \"/guide/deployment/manager.html#using-shared-context-between-\"\n                f\"worker-processes.{Colors.END}\"\n            )\n\n    @property\n    def is_locked(self) -> bool:\n        return getattr(self, \"_lock\", False)\n\n    def lock(self) -> None:\n        self._lock = True\n"
  },
  {
    "path": "sanic/utils.py",
    "content": "import types\n\nfrom importlib.util import module_from_spec, spec_from_file_location\nfrom os import environ as os_environ\nfrom pathlib import Path\nfrom re import findall as re_findall\n\nfrom sanic.exceptions import LoadFileException, PyFileError\nfrom sanic.helpers import import_string\n\n\ndef str_to_bool(val: str) -> bool:\n    \"\"\"Takes string and tries to turn it into bool as human would do.\n\n    If val is in case insensitive (\n        \"y\", \"yes\", \"yep\", \"yup\", \"t\",\n        \"true\", \"on\", \"enable\", \"enabled\", \"1\"\n    ) returns True.\n    If val is in case insensitive (\n        \"n\", \"no\", \"f\", \"nope\", \"false\", \"off\", \"disable\", \"disabled\", \"0\"\n    ) returns False.\n    Else Raise ValueError.\"\"\"\n\n    val = val.lower()\n    if val in {\n        \"y\",\n        \"yes\",\n        \"yep\",\n        \"yup\",\n        \"t\",\n        \"true\",\n        \"on\",\n        \"enable\",\n        \"enabled\",\n        \"1\",\n    }:\n        return True\n    elif val in {\n        \"n\",\n        \"no\",\n        \"f\",\n        \"nope\",\n        \"false\",\n        \"off\",\n        \"disable\",\n        \"disabled\",\n        \"0\",\n    }:\n        return False\n    else:\n        raise ValueError(f\"Invalid truth value {val}\")\n\n\ndef load_module_from_file_location(\n    location: bytes | str | Path, encoding: str = \"utf8\", *args, **kwargs\n):  # noqa\n    \"\"\"Returns loaded module provided as a file path.\n\n    :param args:\n        Corresponds to importlib.util.spec_from_file_location location\n        parameters,but with this differences:\n        - It has to be of a string or bytes type.\n        - You can also use here environment variables\n          in format ${some_env_var}.\n          Mark that $some_env_var will not be resolved as environment variable.\n    :encoding:\n        If location parameter is of a bytes type, then use this encoding\n        to decode it into string.\n    :param args:\n        Corresponds to the rest of importlib.util.spec_from_file_location\n        parameters.\n    :param kwargs:\n        Corresponds to the rest of importlib.util.spec_from_file_location\n        parameters.\n\n    For example You can:\n\n        some_module = load_module_from_file_location(\n            \"some_module_name\",\n            \"/some/path/${some_env_var}\"\n        )\n    \"\"\"\n    if isinstance(location, bytes):\n        location = location.decode(encoding)\n\n    if isinstance(location, Path) or \"/\" in location or \"$\" in location:\n        if not isinstance(location, Path):\n            # A) Check if location contains any environment variables\n            #    in format ${some_env_var}.\n            env_vars_in_location = set(re_findall(r\"\\${(.+?)}\", location))\n\n            # B) Check these variables exists in environment.\n            not_defined_env_vars = env_vars_in_location.difference(\n                os_environ.keys()\n            )\n            if not_defined_env_vars:\n                raise LoadFileException(\n                    \"The following environment variables are not set: \"\n                    f\"{', '.join(not_defined_env_vars)}\"\n                )\n\n            # C) Substitute them in location.\n            for env_var in env_vars_in_location:\n                location = location.replace(\n                    \"${\" + env_var + \"}\", os_environ[env_var]\n                )\n\n        location = str(location)\n        if \".py\" in location:\n            name = location.split(\"/\")[-1].split(\".\")[\n                0\n            ]  # get just the file name without path and .py extension\n            _mod_spec = spec_from_file_location(\n                name, location, *args, **kwargs\n            )\n            assert _mod_spec is not None  # type assertion for mypy\n            module = module_from_spec(_mod_spec)\n            _mod_spec.loader.exec_module(module)  # type: ignore\n\n        else:\n            module = types.ModuleType(\"config\")\n            module.__file__ = str(location)\n            try:\n                with open(location) as config_file:\n                    exec(  # nosec\n                        compile(config_file.read(), location, \"exec\"),\n                        module.__dict__,\n                    )\n            except OSError as e:\n                e.strerror = \"Unable to load configuration file (e.strerror)\"\n                raise\n            except Exception as e:\n                raise PyFileError(location) from e\n\n        return module\n    else:\n        try:\n            return import_string(location)\n        except ValueError:\n            raise OSError(\"Unable to load configuration %s\" % str(location))\n"
  },
  {
    "path": "sanic/views.py",
    "content": "from __future__ import annotations\n\nfrom collections.abc import Iterable\nfrom typing import (\n    TYPE_CHECKING,\n    Any,\n    Callable,\n)\n\nfrom sanic.models.handler_types import RouteHandler\nfrom sanic.request.types import Request\n\n\nif TYPE_CHECKING:\n    from sanic import Sanic\n    from sanic.blueprints import Blueprint\n\n\nclass HTTPMethodView:\n    \"\"\"Class based implementation for creating and grouping handlers\n\n    Class-based views (CBVs) are an alternative to function-based views. They\n    allow you to reuse common logic, and group related views, while keeping\n    the flexibility of function-based views.\n\n\n    To use a class-based view, subclass the method handler, and implement\n    methods (`get`, `post`, `put`, `patch`, `delete`) for the class\n    to correspond to each HTTP method you want to support.\n\n    For example:\n\n    ```python\n    class DummyView(HTTPMethodView):\n        def get(self, request: Request):\n            return text('I am get method')\n\n        def put(self, request: Request):\n            return text('I am put method')\n    ```\n\n    If someone tries to use a non-implemented method, they will reveive a\n    405 response.\n\n    If you need any url params just include them in method signature, like\n    you would for function-based views.\n\n    ```python\n    class DummyView(HTTPMethodView):\n        def get(self, request: Request, my_param_here: str):\n            return text(f\"I am get method with {my_param_here}\")\n    ```\n\n    Next, you need to attach the view to the app or blueprint. You can do this\n    in the exact same way as you would for a function-based view, except you\n    should you use `MyView.as_view()` instead of `my_view_handler`.\n\n    ```python\n    app.add_route(DummyView.as_view(), \"/<my_param_here>\")\n    ```\n\n    Alternatively, you can use the `attach` method:\n\n    ```python\n    DummyView.attach(app, \"/<my_param_here>\")\n    ```\n\n    Or, at the time of subclassing:\n\n    ```python\n    class DummyView(HTTPMethodView, attach=app, uri=\"/<my_param_here>\"):\n        ...\n    ```\n\n    To add a decorator, you can either:\n\n    1. Add it to the `decorators` list on the class, which will apply it to\n         all methods on the class; or\n    2. Add it to the method directly, which will only apply it to that method.\n\n    ```python\n    class DummyView(HTTPMethodView):\n        decorators = [my_decorator]\n        ...\n\n    # or\n\n    class DummyView(HTTPMethodView):\n        @my_decorator\n        def get(self, request: Request):\n            ...\n    ```\n\n    One catch is that you need to be mindful that the call inside the decorator\n    may need to account for the `self` argument, which is passed to the method\n    as the first argument. Alternatively, you may want to also mark your method\n    as `staticmethod` to avoid this.\n\n    Available attributes at the time of subclassing:\n    - **attach** (Optional[Union[Sanic, Blueprint]]): The app or blueprint to\n        attach the view to.\n    - **uri** (str): The uri to attach the view to.\n    - **methods** (Iterable[str]): The HTTP methods to attach the view to.\n        Defaults to `{\"GET\"}`.\n    - **host** (Optional[str]): The host to attach the view to.\n    - **strict_slashes** (Optional[bool]): Whether to add a redirect rule for\n        the uri with a trailing slash.\n    - **version** (Optional[int]): The version to attach the view to.\n    - **name** (Optional[str]): The name to attach the view to.\n    - **stream** (bool): Whether the view is a stream handler.\n    - **version_prefix** (str): The prefix to use for the version. Defaults\n        to `\"/v\"`.\n    \"\"\"\n\n    decorators: list[Callable[[Callable[..., Any]], Callable[..., Any]]] = []\n\n    def __init_subclass__(\n        cls,\n        attach: Sanic | Blueprint | None = None,\n        uri: str = \"\",\n        methods: Iterable[str] = frozenset({\"GET\"}),\n        host: str | None = None,\n        strict_slashes: bool | None = None,\n        version: int | None = None,\n        name: str | None = None,\n        stream: bool = False,\n        version_prefix: str = \"/v\",\n        **kwargs: Any,\n    ) -> None:\n        super().__init_subclass__(**kwargs)\n        if attach:\n            cls.attach(\n                attach,\n                uri=uri,\n                methods=methods,\n                host=host,\n                strict_slashes=strict_slashes,\n                version=version,\n                name=name,\n                stream=stream,\n                version_prefix=version_prefix,\n            )\n\n    def dispatch_request(self, request: Request, *args, **kwargs):\n        \"\"\"Dispatch request to appropriate handler method.\"\"\"\n        method = request.method.lower()\n        handler = getattr(self, method, None)\n\n        if not handler and method == \"head\":\n            handler = getattr(self, \"get\")\n        if not handler:\n            # The router will never allow us to get here, but this is\n            # included as a fallback and for completeness.\n            raise NotImplementedError(\n                f\"{request.method} is not supported for this endpoint.\"\n            )\n        return handler(request, *args, **kwargs)\n\n    @classmethod\n    def as_view(cls, *class_args: Any, **class_kwargs: Any) -> RouteHandler:\n        \"\"\"Return view function for use with the routing system, that dispatches request to appropriate handler method.\n\n        If you need to pass arguments to the class's constructor, you can\n        pass the arguments to `as_view` and they will be passed to the class\n        `__init__` method.\n\n        Args:\n            *class_args: Variable length argument list for the class instantiation.\n            **class_kwargs: Arbitrary keyword arguments for the class instantiation.\n\n        Returns:\n            RouteHandler: The view function.\n\n        Examples:\n            ```python\n            class DummyView(HTTPMethodView):\n                def __init__(self, foo: MyFoo):\n                    self.foo = foo\n\n                async def get(self, request: Request):\n                    return text(self.foo.bar)\n\n            app.add_route(DummyView.as_view(foo=MyFoo()), \"/\")\n            ```\n        \"\"\"  # noqa: E501\n\n        def view(*args, **kwargs):\n            self = view.view_class(*class_args, **class_kwargs)\n            return self.dispatch_request(*args, **kwargs)\n\n        if cls.decorators:\n            view.__module__ = cls.__module__\n            for decorator in cls.decorators:\n                view = decorator(view)\n\n        view.view_class = cls  # type: ignore\n        view.__doc__ = cls.__doc__\n        view.__module__ = cls.__module__\n        view.__name__ = cls.__name__\n        return view\n\n    @classmethod\n    def attach(\n        cls,\n        to: Sanic | Blueprint,\n        uri: str,\n        methods: Iterable[str] = frozenset({\"GET\"}),\n        host: str | None = None,\n        strict_slashes: bool | None = None,\n        version: int | None = None,\n        name: str | None = None,\n        stream: bool = False,\n        version_prefix: str = \"/v\",\n    ) -> None:\n        \"\"\"Attaches the view to a Sanic app or Blueprint at the specified URI.\n\n        Args:\n            cls: The class that this method is part of.\n            to (Union[Sanic, Blueprint]): The Sanic application or Blueprint to attach to.\n            uri (str): The URI to bind the view to.\n            methods (Iterable[str], optional): A collection of HTTP methods that the view should respond to. Defaults to `frozenset({\"GET\"})`.\n            host (Optional[str], optional): A specific host or hosts to bind the view to. Defaults to `None`.\n            strict_slashes (Optional[bool], optional): Enforce or not the trailing slash. Defaults to `None`.\n            version (Optional[int], optional): Version of the API if versioning is used. Defaults to `None`.\n            name (Optional[str], optional): Unique name for the route. Defaults to `None`.\n            stream (bool, optional): Enable or disable streaming for the view. Defaults to `False`.\n            version_prefix (str, optional): The prefix for the version, if versioning is used. Defaults to `\"/v\"`.\n        \"\"\"  # noqa: E501\n        to.add_route(\n            cls.as_view(),\n            uri=uri,\n            methods=methods,\n            host=host,\n            strict_slashes=strict_slashes,\n            version=version,\n            name=name,\n            stream=stream,\n            version_prefix=version_prefix,\n        )\n\n\ndef stream(func):\n    \"\"\"Decorator to mark a function as a stream handler.\"\"\"\n    func.is_stream = True\n    return func\n"
  },
  {
    "path": "sanic/worker/__init__.py",
    "content": ""
  },
  {
    "path": "sanic/worker/constants.py",
    "content": "from enum import IntEnum, auto\n\nfrom sanic.compat import UpperStrEnum\n\n\nclass RestartOrder(UpperStrEnum):\n    \"\"\"Available restart orders.\"\"\"\n\n    SHUTDOWN_FIRST = auto()\n    STARTUP_FIRST = auto()\n\n\nclass ProcessState(IntEnum):\n    \"\"\"Process states.\"\"\"\n\n    NONE = auto()\n    IDLE = auto()\n    RESTARTING = auto()\n    STARTING = auto()\n    STARTED = auto()\n    ACKED = auto()\n    JOINED = auto()\n    TERMINATED = auto()\n    FAILED = auto()\n    COMPLETED = auto()\n"
  },
  {
    "path": "sanic/worker/daemon.py",
    "content": "from __future__ import annotations\n\nimport atexit\nimport grp\nimport os\nimport pwd\nimport signal\nimport sys\nimport time\nimport uuid\n\nfrom dataclasses import dataclass\nfrom pathlib import Path\n\nfrom sanic.compat import OS_IS_WINDOWS\nfrom sanic.log import logger\n\n\ntry:\n    import fcntl  # type: ignore\nexcept (ImportError, ModuleNotFoundError):  # pragma: no cover\n    fcntl = None  # type: ignore\n\n\ndef _get_default_runtime_dir() -> Path:\n    \"\"\"\n    Default directory for auto-generated runtime artifacts (pid/lock/log).\n\n    Priority:\n      1) XDG_RUNTIME_DIR/sanic  (preferred, runtime-only)\n      2) ~/.local/state/sanic   (persistent state, modern default)\n      3) ~/.cache/sanic         (fallback)\n      4) cwd                    (last resort)\n    \"\"\"\n    xdg_runtime = os.environ.get(\"XDG_RUNTIME_DIR\")\n    if xdg_runtime:\n        path = Path(xdg_runtime) / \"sanic\"\n        try:\n            path.mkdir(mode=0o700, parents=True, exist_ok=True)\n            return path\n        except OSError:\n            pass\n\n    state_dir = Path.home() / \".local\" / \"state\" / \"sanic\"\n    try:\n        state_dir.mkdir(mode=0o700, parents=True, exist_ok=True)\n        return state_dir\n    except OSError:\n        pass\n\n    cache_dir = Path.home() / \".cache\" / \"sanic\"\n    try:\n        cache_dir.mkdir(mode=0o700, parents=True, exist_ok=True)\n        return cache_dir\n    except OSError:\n        pass\n\n    return Path.cwd()\n\n\ndef _sanitize_name(name: str) -> str:\n    return (\n        \"\".join(\n            c if c.isalnum() or c in (\"-\", \"_\", \".\") else \"_\" for c in name\n        ).strip(\"._\")\n        or \"sanic\"\n    )\n\n\ndef _process_exists(pid: int) -> bool:\n    try:\n        os.kill(pid, 0)\n    except OSError:\n        return False\n    return True\n\n\ndef _is_sanic_process(pid: int) -> bool:\n    \"\"\"\n    Best-effort Sanic process identification.\n\n    On Linux, checks /proc/<pid>/cmdline for 'sanic'. On other platforms,\n    falls back to True if process exists (no strong identification).\n    \"\"\"\n    proc_cmdline = Path(f\"/proc/{pid}/cmdline\")\n    if proc_cmdline.exists():\n        try:\n            data = proc_cmdline.read_bytes()\n            return b\"sanic\" in data\n        except OSError:\n            return False\n    return True\n\n\n@dataclass(frozen=True)\nclass PidfileInfo:\n    pid: int\n    started: int | None = None\n    name: str | None = None\n\n\nclass DaemonError(Exception):\n    pass\n\n\nclass Daemon:\n    \"\"\"\n    Daemonization helper (Unix only).\n\n    Supports:\n      - Double-fork daemonization\n      - Optional PID file (with Sanic marker + metadata)\n      - Optional lock file (prevents double start / stale PID reuse issues)\n      - Optional logfile redirection\n      - Optional privilege drop (user/group)\n      - SIGHUP handler that preserves pidfile identity\n    \"\"\"\n\n    def __init__(\n        self,\n        pidfile: str | None = None,\n        logfile: str | None = None,\n        user: str | None = None,\n        group: str | None = None,\n        name: str | None = None,\n        lockfile: str | None = None,\n    ):\n        if OS_IS_WINDOWS:\n            raise DaemonError(\n                \"Daemon mode is not supported on Windows. \"\n                \"Consider using a Windows service or nssm instead.\"\n            )\n\n        self.name = _sanitize_name(name) if name else None\n\n        self._auto_pidfile = pidfile == \"auto\" or pidfile == \"\"\n        self.pidfile: Path | None\n        if self._auto_pidfile:\n            base = _get_default_runtime_dir()\n            # Prefer deterministic name if provided\n            if self.name:\n                self.pidfile = base / f\"{self.name}.pid\"\n            else:\n                self.pidfile = base / f\"sanic-{uuid.uuid4().hex}.pid\"\n        elif pidfile:\n            self.pidfile = Path(pidfile)\n        else:\n            self.pidfile = None\n\n        self.logfile = Path(logfile) if logfile else None\n        self.user = user\n        self.group = group\n\n        self._uid: int | None = None\n        self._gid: int | None = None\n        self.pid: int | None = None\n\n        self._lockfile_path: Path | None = Path(lockfile) if lockfile else None\n        self._lock_fd: int | None = None\n\n    @staticmethod\n    def get_pidfile_path(name: str) -> Path:\n        \"\"\"\n        Get the predictable pidfile path for a given name.\n\n        Args:\n            name: The application/daemon name\n\n        Returns:\n            Path to the pidfile in the default runtime directory\n        \"\"\"\n        sanitized = _sanitize_name(name)\n        return _get_default_runtime_dir() / f\"{sanitized}.pid\"\n\n    def validate(self) -> None:\n        self._validate_user_group()\n        self._validate_paths()\n        self._validate_runtime_state()\n\n    def daemonize(self) -> None:\n        \"\"\"\n        Double-fork daemonization.\n\n        Important: anything meant for the invoking terminal must be\n        printed/logged before calling this method, since stdout/stderr\n        are redirected after detaching.\n        \"\"\"\n        self.validate()\n\n        # Acquire lock before forking to prevent race condition\n        # The lock is inherited by child processes and remains held\n        self._acquire_lockfile()\n\n        # First fork\n        try:\n            pid = os.fork()\n            if pid > 0:\n                os._exit(0)  # pragma: no cover\n        except OSError as e:\n            raise DaemonError(f\"First fork failed: {e}\") from e\n\n        os.chdir(\"/\")\n        os.setsid()\n        os.umask(0o022)\n\n        # Second fork\n        try:\n            pid = os.fork()\n            if pid > 0:\n                os._exit(0)  # pragma: no cover\n        except OSError as e:\n            raise DaemonError(f\"Second fork failed: {e}\") from e\n\n        self.pid = os.getpid()\n\n        self._redirect_streams()\n        self._write_pidfile()\n        self._setup_signal_handlers()\n\n        logger.info(\n            \"Sanic daemon started\",\n            extra={\n                \"daemon_pid\": self.pid,\n                \"daemon_pidfile\": str(self.pidfile) if self.pidfile else None,\n                \"daemon_logfile\": str(self.logfile) if self.logfile else None,\n                \"daemon_name\": self.name,\n            },\n        )\n\n    def drop_privileges(self) -> None:\n        \"\"\"\n        Drop privileges to configured user/group.\n\n        Call after binding privileged ports but before serving requests.\n        \"\"\"\n        if self._uid is None and self._gid is None:\n            return\n\n        if os.getuid() != 0:\n            logger.warning(\n                \"Privilege drop requested but not running as root\",\n                extra={\"user\": self.user, \"group\": self.group},\n            )\n            return\n\n        if self._gid is not None:\n            try:\n                os.setgid(self._gid)\n                if self.user:\n                    os.initgroups(self.user, self._gid)\n            except PermissionError as e:\n                grp = self.group or self._gid\n                raise DaemonError(\n                    f\"Cannot change group to '{grp}'. Are you root?\"\n                ) from e\n\n        if self._uid is not None:\n            try:\n                os.setuid(self._uid)\n            except PermissionError as e:\n                usr = self.user or self._uid\n                raise DaemonError(\n                    f\"Cannot change user to '{usr}'. Are you root?\"\n                ) from e\n\n        logger.info(\n            \"Dropped privileges\",\n            extra={\n                \"user\": self.user or \"unchanged\",\n                \"group\": self.group or \"unchanged\",\n            },\n        )\n\n    def _validate_user_group(self) -> None:\n        if not self.user and not self.group:\n            return\n\n        if self.user:\n            try:\n                pw = pwd.getpwnam(self.user)\n            except KeyError as e:\n                raise DaemonError(f\"User '{self.user}' does not exist\") from e\n            self._uid = pw.pw_uid\n            if not self.group:\n                self._gid = pw.pw_gid\n\n        if self.group:\n            try:\n                gr = grp.getgrnam(self.group)\n            except KeyError as e:\n                raise DaemonError(\n                    f\"Group '{self.group}' does not exist\"\n                ) from e\n            self._gid = gr.gr_gid\n\n    def _validate_paths(self) -> None:\n        if self.pidfile:\n            self._validate_writable_dir(self.pidfile, \"PID file\")\n\n        if self.logfile:\n            self._validate_writable_dir(self.logfile, \"Log file\")\n\n        # Default lockfile beside pidfile if not explicitly set\n        if not self._lockfile_path and self.pidfile:\n            self._lockfile_path = self.pidfile.with_suffix(\".lock\")\n\n        if self._lockfile_path:\n            self._validate_writable_dir(self._lockfile_path, \"Lock file\")\n\n    def _validate_runtime_state(self) -> None:\n        # PID file check (stale vs running)\n        if not self.pidfile or not self.pidfile.exists():\n            return\n\n        info = self.read_pidfile_info(self.pidfile)\n        if not info:\n            return\n\n        if _process_exists(info.pid) and _is_sanic_process(info.pid):\n            raise DaemonError(f\"Daemon already running with PID {info.pid}\")\n\n    def _validate_writable_dir(self, path: Path, label: str) -> None:\n        directory = path.parent\n        if not directory.exists():\n            raise DaemonError(f\"{label} directory does not exist: {directory}\")\n        if not os.access(directory, os.W_OK):\n            raise DaemonError(\n                f\"Cannot write to {label} directory: {directory}\"\n            )\n\n    def _write_pidfile(self) -> None:\n        if not self.pidfile:\n            return\n\n        pid = os.getpid()\n        started = int(time.time())\n        name = self.name or \"\"\n\n        # Format:\n        #   sanic\n        #   pid=123\n        #   started=...\n        #   name=...\n        lines = [\n            \"sanic\",\n            f\"pid={pid}\",\n            f\"started={started}\",\n        ]\n        if name:\n            lines.append(f\"name={name}\")\n\n        try:\n            self.pidfile.write_text(\"\\n\".join(lines) + \"\\n\")\n        except OSError as e:\n            raise DaemonError(\n                f\"Failed to write PID file: {self.pidfile}\"\n            ) from e\n\n        atexit.register(self._remove_pidfile)\n\n    def _remove_pidfile(self) -> None:\n        if not self.pidfile:\n            return\n        try:\n            self.pidfile.unlink(missing_ok=True)\n        except OSError as e:\n            # Best-effort cleanup: failure to remove the PID file is non-fatal.\n            logger.debug(\"Failed to remove PID file %s: %s\", self.pidfile, e)\n\n    @staticmethod\n    def read_pidfile(pidfile: str | Path) -> int | None:\n        info = Daemon.read_pidfile_info(pidfile)\n        return info.pid if info else None\n\n    @staticmethod\n    def read_pidfile_info(pidfile: str | Path) -> PidfileInfo | None:\n        path = Path(pidfile)\n        if not path.exists():\n            return None\n\n        try:\n            raw = path.read_text().splitlines()\n        except OSError:\n            return None\n\n        if not raw or raw[0].strip() != \"sanic\":\n            return None\n\n        data: dict[str, str] = {}\n        for line in raw[1:]:\n            if \"=\" in line:\n                k, v = line.split(\"=\", 1)\n                data[k.strip()] = v.strip()\n\n        try:\n            pid = int(data.get(\"pid\", \"\"))\n        except ValueError:\n            return None\n\n        started = None\n        if \"started\" in data:\n            try:\n                started = int(data[\"started\"])\n            except ValueError:\n                started = None\n\n        name = data.get(\"name\") or None\n        return PidfileInfo(pid=pid, started=started, name=name)\n\n    def _acquire_lockfile(self) -> None:\n        if not self._lockfile_path:\n            return\n        if fcntl is None:\n            return\n\n        try:\n            fd = os.open(\n                str(self._lockfile_path), os.O_RDWR | os.O_CREAT, 0o644\n            )\n        except OSError as e:\n            raise DaemonError(\n                f\"Failed to open lock file: {self._lockfile_path}\"\n            ) from e\n\n        try:\n            fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)\n        except OSError as e:\n            try:\n                os.close(fd)\n            except OSError:\n                pass\n            raise DaemonError(\n                f\"Daemon already running (lock held): {self._lockfile_path}\"\n            ) from e\n\n        self._lock_fd = fd\n        atexit.register(self._release_lockfile)\n\n    def _release_lockfile(self) -> None:\n        if self._lock_fd is None:\n            return\n        try:\n            if fcntl is not None:\n                fcntl.flock(self._lock_fd, fcntl.LOCK_UN)\n        except OSError as e:\n            # Best-effort cleanup: failure to unlock is non-fatal.\n            logger.debug(\n                \"Failed to unlock file descriptor %s: %s\", self._lock_fd, e\n            )\n        try:\n            os.close(self._lock_fd)\n        except OSError as e:\n            # Best-effort cleanup: failure to close is non-fatal.\n            logger.debug(\n                \"Failed to close file descriptor %s: %s\", self._lock_fd, e\n            )\n        self._lock_fd = None\n        if self._lockfile_path:\n            try:\n                self._lockfile_path.unlink(missing_ok=True)\n            except OSError as e:\n                # Best-effort cleanup: failure to remove lock file\n                # is non-fatal.\n                logger.debug(\n                    \"Failed to remove lock file %s: %s\", self._lockfile_path, e\n                )\n\n    def _redirect_streams(self) -> None:\n        sys.stdout.flush()\n        sys.stderr.flush()\n\n        stdin_fd = os.open(os.devnull, os.O_RDONLY)\n        os.dup2(stdin_fd, sys.stdin.fileno())\n\n        if self.logfile:\n            log_fd = os.open(\n                str(self.logfile),\n                os.O_WRONLY | os.O_CREAT | os.O_APPEND,\n                0o644,\n            )\n            os.dup2(log_fd, sys.stdout.fileno())\n            os.dup2(log_fd, sys.stderr.fileno())\n            if log_fd > 2:\n                os.close(log_fd)\n        else:\n            devnull_fd = os.open(os.devnull, os.O_RDWR)\n            os.dup2(devnull_fd, sys.stdout.fileno())\n            os.dup2(devnull_fd, sys.stderr.fileno())\n            if devnull_fd > 2:\n                os.close(devnull_fd)\n\n        if stdin_fd > 2:\n            os.close(stdin_fd)\n\n    def _setup_signal_handlers(self) -> None:\n        if not self.pidfile:\n            return\n\n        original = signal.getsignal(signal.SIGHUP)\n\n        def _handle_sighup(signum, frame):\n            # Preserve pidfile identity; ensure it exists.\n            self._write_pidfile()\n            if callable(original):\n                original(signum, frame)\n\n        signal.signal(signal.SIGHUP, _handle_sighup)\n"
  },
  {
    "path": "sanic/worker/inspector.py",
    "content": "from __future__ import annotations\n\nfrom collections.abc import Mapping\nfrom datetime import datetime\nfrom inspect import isawaitable\nfrom multiprocessing.connection import Connection\nfrom os import environ\nfrom pathlib import Path\nfrom typing import Any\n\nfrom sanic.exceptions import Unauthorized\nfrom sanic.helpers import Default\nfrom sanic.log import logger\nfrom sanic.request import Request\nfrom sanic.response import json\n\n\nclass Inspector:\n    \"\"\"Inspector for Sanic workers.\n\n    This class is used to create an inspector for Sanic workers. It is\n    instantiated by the worker class and is used to create a Sanic app\n    that can be used to inspect and control the workers and the server.\n\n    It is not intended to be used directly by the user.\n\n    See [Inspector](/en/guide/deployment/inspector) for more information.\n\n    Args:\n        publisher (Connection): The connection to the worker.\n        app_info (Dict[str, Any]): Information about the app.\n        worker_state (Mapping[str, Any]): The state of the worker.\n        host (str): The host to bind the inspector to.\n        port (int): The port to bind the inspector to.\n        api_key (str): The API key to use for authentication.\n        tls_key (Union[Path, str, Default]): The path to the TLS key file.\n        tls_cert (Union[Path, str, Default]): The path to the TLS cert file.\n    \"\"\"\n\n    def __init__(\n        self,\n        publisher: Connection,\n        app_info: dict[str, Any],\n        worker_state: Mapping[str, Any],\n        host: str,\n        port: int,\n        api_key: str,\n        tls_key: Path | str | Default,\n        tls_cert: Path | str | Default,\n    ):\n        self._publisher = publisher\n        self.app_info = app_info\n        self.worker_state = worker_state\n        self.host = host\n        self.port = port\n        self.api_key = api_key\n        self.tls_key = tls_key\n        self.tls_cert = tls_cert\n\n    def __call__(self, run=True, **_) -> Inspector:\n        from sanic import Sanic\n\n        self.app = Sanic(\"Inspector\")\n        self._setup()\n        if run:\n            self.app.run(\n                host=self.host,\n                port=self.port,\n                single_process=True,\n                ssl={\"key\": self.tls_key, \"cert\": self.tls_cert}\n                if not isinstance(self.tls_key, Default)\n                and not isinstance(self.tls_cert, Default)\n                else None,\n            )\n        return self\n\n    def _setup(self):\n        self.app.get(\"/\")(self._info)\n        self.app.post(\"/<action:str>\")(self._action)\n        if self.api_key:\n            self.app.on_request(self._authentication)\n        environ[\"SANIC_IGNORE_PRODUCTION_WARNING\"] = \"true\"\n\n    def _authentication(self, request: Request) -> None:\n        if request.token != self.api_key:\n            raise Unauthorized(\"Bad API key\")\n\n    async def _action(self, request: Request, action: str):\n        logger.info(\"Incoming inspector action: %s\", action)\n        output: Any = None\n        method = getattr(self, action, None)\n        if method:\n            kwargs = {}\n            if request.body:\n                kwargs = request.json\n            args = kwargs.pop(\"args\", ())\n            output = method(*args, **kwargs)\n            if isawaitable(output):\n                output = await output\n\n        return await self._respond(request, output)\n\n    async def _info(self, request: Request):\n        return await self._respond(request, self._state_to_json())\n\n    async def _respond(self, request: Request, output: Any):\n        name = request.match_info.get(\"action\", \"info\")\n        return json({\"meta\": {\"action\": name}, \"result\": output})\n\n    def _state_to_json(self) -> dict[str, Any]:\n        output = {\"info\": self.app_info}\n        output[\"workers\"] = self._make_safe(dict(self.worker_state))\n        return output\n\n    @staticmethod\n    def _make_safe(obj: dict[str, Any]) -> dict[str, Any]:\n        for key, value in obj.items():\n            if isinstance(value, dict):\n                obj[key] = Inspector._make_safe(value)\n            elif isinstance(value, datetime):\n                obj[key] = value.isoformat()\n        return obj\n\n    def reload(self, zero_downtime: bool = False) -> None:\n        \"\"\"Reload the workers\n\n        Args:\n            zero_downtime (bool, optional): Whether to use zero downtime\n                reload. Defaults to `False`.\n        \"\"\"\n        message = \"__ALL_PROCESSES__:\"\n        if zero_downtime:\n            message += \":STARTUP_FIRST\"\n        self._publisher.send(message)\n\n    def scale(self, replicas: str | int) -> str:\n        \"\"\"Scale the number of workers\n\n        Args:\n            replicas (Union[str, int]): The number of workers to scale to.\n\n        Returns:\n            str: A log message.\n        \"\"\"\n        num_workers = 1\n        if replicas:\n            num_workers = int(replicas)\n        log_msg = f\"Scaling to {num_workers}\"\n        logger.info(log_msg)\n        message = f\"__SCALE__:{num_workers}\"\n        self._publisher.send(message)\n        return log_msg\n\n    def shutdown(self) -> None:\n        \"\"\"Shutdown the workers\"\"\"\n        message = \"__TERMINATE__\"\n        self._publisher.send(message)\n"
  },
  {
    "path": "sanic/worker/loader.py",
    "content": "from __future__ import annotations\n\nimport os\nimport sys\n\nfrom contextlib import suppress\nfrom importlib import import_module\nfrom inspect import isfunction\nfrom pathlib import Path\nfrom ssl import SSLContext\nfrom typing import TYPE_CHECKING, Any, Callable, cast\n\nfrom sanic.http.tls.context import process_to_context\nfrom sanic.http.tls.creators import MkcertCreator, TrustmeCreator\n\n\nif TYPE_CHECKING:\n    from sanic import Sanic as SanicApp\n\nDEFAULT_APP_NAME = \"app\"\n\n\nclass AppLoader:\n    \"\"\"A helper to load application instances.\n\n    Generally used by the worker to load the application instance.\n\n    See [Dynamic Applications](/en/guide/deployment/app-loader) for information on when you may need to use this.\n\n    Args:\n        module_input (str): The module to load the application from.\n        as_factory (bool): Whether the application is a factory.\n        as_simple (bool): Whether the application is a simple server.\n        args (Any): Arguments to pass to the application factory.\n        factory (Callable[[], SanicApp]): A callable that returns a Sanic application instance.\n    \"\"\"  # noqa: E501\n\n    def __init__(\n        self,\n        module_input: str = \"\",\n        as_factory: bool = False,\n        as_simple: bool = False,\n        args: Any = None,\n        factory: Callable[[], SanicApp] | None = None,\n    ) -> None:\n        self.module_input = module_input\n        self.module_name = \"\"\n        self.app_name = \"\"\n        self.as_factory = as_factory\n        self.as_simple = as_simple\n        self.args = args\n        self.factory = factory\n        self.cwd = os.getcwd()\n\n        if module_input:\n            delimiter = \":\" if \":\" in module_input else \".\"\n            if (\n                delimiter in module_input\n                and \"\\\\\" not in module_input\n                and \"/\" not in module_input\n            ):\n                module_name, app_name = module_input.rsplit(delimiter, 1)\n                self.module_name = module_name\n                self.app_name = app_name\n                if self.app_name.endswith(\"()\"):\n                    self.as_factory = True\n                    self.app_name = self.app_name[:-2]\n\n    def load(self) -> SanicApp:\n        module_path = os.path.abspath(self.cwd)\n        if module_path not in sys.path:\n            sys.path.append(module_path)\n\n        if self.factory:\n            return self.factory()\n        else:\n            from sanic.app import Sanic\n            from sanic.simple import create_simple_server\n\n            maybe_path = Path(self.module_input)\n            if self.as_simple or (\n                maybe_path.is_dir()\n                and (\"\\\\\" in self.module_input or \"/\" in self.module_input)\n            ):\n                app = create_simple_server(maybe_path)\n            else:\n                implied_app_name = False\n                if not self.module_name and not self.app_name:\n                    self.module_name = self.module_input\n                    self.app_name = DEFAULT_APP_NAME\n                    implied_app_name = True\n                module = import_module(self.module_name)\n                app = getattr(module, self.app_name, None)\n                if not app and implied_app_name:\n                    raise ValueError(\n                        \"Looks like you only supplied a module name. Sanic \"\n                        \"tried to locate an application instance named \"\n                        f\"{self.module_name}:app, but was unable to locate \"\n                        \"an application instance. Please provide a path \"\n                        \"to a global instance of Sanic(), or a callable that \"\n                        \"will return a Sanic() application instance.\"\n                    )\n                if self.as_factory or isfunction(app):\n                    if not callable(app):\n                        type_name = type(app).__name__\n                        raise ValueError(\n                            f\"Expected a callable, but got {type_name}.\"\n                        )\n                    try:\n                        app = app(self.args)\n                    except TypeError:\n                        app = app()\n\n                app_type_name = type(app).__name__\n\n                if (\n                    not isinstance(app, Sanic)\n                    and self.args\n                    and hasattr(self.args, \"target\")\n                ):\n                    with suppress(ModuleNotFoundError):\n                        maybe_module = import_module(self.module_input)\n                        app = getattr(maybe_module, \"app\", None)\n                    if not app:\n                        message = (\n                            \"Module is not a Sanic app, \"\n                            f\"it is a {app_type_name}\\n\"\n                            f\"  Perhaps you meant {self.args.target}:app?\"\n                        )\n                        raise ValueError(message)\n        return app\n\n\nclass CertLoader:\n    _creators = {\n        \"mkcert\": MkcertCreator,\n        \"trustme\": TrustmeCreator,\n    }\n\n    def __init__(\n        self,\n        ssl_data: SSLContext | dict[str, str | os.PathLike] | None,\n    ):\n        self._ssl_data = ssl_data\n        self._creator_class = None\n        if not ssl_data or not isinstance(ssl_data, dict):\n            return\n\n        creator_name = cast(str, ssl_data.get(\"creator\"))\n\n        self._creator_class = self._creators.get(creator_name)\n        if not creator_name:\n            return\n\n        if not self._creator_class:\n            raise RuntimeError(f\"Unknown certificate creator: {creator_name}\")\n\n        self._key = ssl_data[\"key\"]\n        self._cert = ssl_data[\"cert\"]\n        self._localhost = cast(str, ssl_data[\"localhost\"])\n\n    def load(self, app: SanicApp):\n        if not self._creator_class:\n            return process_to_context(self._ssl_data)\n\n        creator = self._creator_class(app, self._key, self._cert)\n        return creator.generate_cert(self._localhost)\n"
  },
  {
    "path": "sanic/worker/manager.py",
    "content": "from __future__ import annotations\n\nimport os\nimport signal\n\nfrom collections.abc import Iterable, MutableMapping\nfrom contextlib import suppress\nfrom enum import IntEnum, auto\nfrom itertools import chain, count\nfrom multiprocessing.connection import Connection\nfrom multiprocessing.context import BaseContext\nfrom random import choice\nfrom signal import SIGINT, SIGTERM, Signals\nfrom signal import signal as signal_func\nfrom typing import Any, Callable\n\nfrom sanic.compat import OS_IS_WINDOWS\nfrom sanic.exceptions import ServerKilled\nfrom sanic.log import error_logger, logger\nfrom sanic.worker.constants import RestartOrder\nfrom sanic.worker.process import ProcessState, Worker, WorkerProcess\nfrom sanic.worker.restarter import Restarter\n\n\nSIGKILL: int\nif not OS_IS_WINDOWS:\n    from signal import SIGKILL\nelse:\n    SIGKILL = SIGINT\n\n\nclass MonitorCycle(IntEnum):\n    BREAK = auto()\n    CONTINUE = auto()\n\n\nclass WorkerManager:\n    \"\"\"Manage all of the processes.\n\n    This class is used to manage all of the processes. It is instantiated\n    by Sanic when in multiprocess mode (which is OOTB default) and is used\n    to start, stop, and restart the worker processes.\n\n    You can access it to interact with it **ONLY** when on the main process.\n\n    Therefore, you should really only access it from within the\n    `main_process_ready` event listener.\n\n    ```python\n    from sanic import Sanic\n\n    app = Sanic(\"MyApp\")\n\n    @app.main_process_ready\n    async def ready(app: Sanic, _):\n        app.manager.manage(\"MyProcess\", my_process, {\"foo\": \"bar\"})\n    ```\n\n    See [Worker Manager](/en/guide/deployment/manager) for more information.\n    \"\"\"\n\n    THRESHOLD = WorkerProcess.THRESHOLD\n    MAIN_NAME = \"Sanic-Main\"\n\n    def __init__(\n        self,\n        number: int,\n        serve: Callable[..., Any],\n        server_settings: dict[str, Any],\n        context: BaseContext,\n        monitor_pubsub: tuple[Connection[Any, Any], Connection[Any, Any]],\n        worker_state: MutableMapping[str, Any],\n    ):\n        self.num_server = number\n        self.context = context\n        self.transient: dict[str, Worker] = {}\n        self.durable: dict[str, Worker] = {}\n        self.restarter = Restarter()\n        self.monitor_publisher, self.monitor_subscriber = monitor_pubsub\n        self.worker_state = worker_state\n        self.worker_state[self.MAIN_NAME] = {\"pid\": self.pid}\n        self._shutting_down = False\n        self._serve = serve\n        self._server_settings = server_settings\n        self._server_count = count()\n\n        if number == 0:\n            raise RuntimeError(\"Cannot serve with no workers\")\n\n        for _ in range(number):\n            self.create_server()\n\n        signal_func(SIGINT, self.shutdown_signal)\n        signal_func(SIGTERM, self.shutdown_signal)\n\n    def manage(\n        self,\n        name: str,\n        func: Callable[..., Any],\n        kwargs: dict[str, Any],\n        transient: bool = False,\n        restartable: bool | None = None,\n        tracked: bool = True,\n        auto_start: bool = True,\n        workers: int = 1,\n        ident: str = \"\",\n    ) -> Worker:\n        \"\"\"Instruct Sanic to manage a custom process.\n\n        Args:\n            name (str): A name for the worker process\n            func (Callable[..., Any]): The function to call in the background process\n            kwargs (Dict[str, Any]): Arguments to pass to the function\n            transient (bool, optional): Whether to mark the process as transient. If `True`\n                then the Worker Manager will restart the process along\n                with any global restart (ex: auto-reload), defaults to `False`\n            restartable (Optional[bool], optional): Whether to mark the process as restartable. If\n                `True` then the Worker Manager will be able to restart the process\n                if prompted. If `transient=True`, this property will be implied\n                to be `True`, defaults to `None`\n            tracked (bool, optional): Whether to track the process after completion,\n                defaults to `True`\n            auto_start (bool, optional): Whether to start the process immediately, defaults to `True`\n            workers (int, optional): The number of worker processes to run. Defaults to `1`.\n            ident (str, optional): The identifier for the worker. If not provided, the name\n                passed will be used. Defaults to `\"\"`.\n\n        Returns:\n            Worker: The Worker instance\n        \"\"\"  # noqa: E501\n        if name in self.transient or name in self.durable:\n            raise ValueError(f\"Worker {name} already exists\")\n        restartable = restartable if restartable is not None else transient\n        if transient and not restartable:\n            raise ValueError(\n                \"Cannot create a transient worker that is not restartable\"\n            )\n        container = self.transient if transient else self.durable\n        worker = Worker(\n            ident or name,\n            name,\n            func,\n            kwargs,\n            self.context,\n            self.worker_state,\n            workers,\n            restartable,\n            tracked,\n            auto_start,\n        )\n        container[worker.name] = worker\n        return worker\n\n    def create_server(self) -> Worker:\n        \"\"\"Create a new server process.\n\n        Returns:\n            Worker: The Worker instance\n        \"\"\"\n        server_number = next(self._server_count)\n        return self.manage(\n            f\"{WorkerProcess.SERVER_LABEL}-{server_number}\",\n            self._serve,\n            self._server_settings,\n            transient=True,\n            restartable=True,\n            ident=f\"{WorkerProcess.SERVER_IDENTIFIER}{server_number:2}\",\n        )\n\n    def shutdown_server(self, name: str | None = None) -> None:\n        \"\"\"Shutdown a server process.\n\n        Args:\n            name (Optional[str], optional): The name of the server process to shutdown.\n                If `None` then a random server will be chosen. Defaults to `None`.\n        \"\"\"  # noqa: E501\n        if not name:\n            servers = [\n                worker\n                for worker in self.transient.values()\n                if worker.name.startswith(WorkerProcess.SERVER_LABEL)\n            ]\n            if not servers:\n                error_logger.error(\n                    \"Server shutdown failed because a server was not found.\"\n                )\n                return\n            worker = choice(servers)  # nosec B311\n        else:\n            worker = self.transient[name]\n\n        for process in worker.processes:\n            process.terminate()\n\n        del self.transient[worker.name]\n\n    def run(self):\n        \"\"\"Run the worker manager.\"\"\"\n        self.start()\n        self.monitor()\n        self.join()\n        self.terminate()\n        self.cleanup()\n\n    def start(self):\n        \"\"\"Start the worker processes.\"\"\"\n        for worker in self.workers:\n            for process in worker.processes:\n                if not worker.auto_start:\n                    process.set_state(ProcessState.NONE, True)\n                    continue\n                process.start()\n\n    def join(self):\n        \"\"\"Join the worker processes.\"\"\"\n        logger.debug(\"Joining processes\", extra={\"verbosity\": 1})\n        joined = set()\n        for process in self.processes:\n            logger.debug(\n                f\"Found {process.pid} - {process.state.name}\",\n                extra={\"verbosity\": 1},\n            )\n            if process.state < ProcessState.JOINED:\n                logger.debug(f\"Joining {process.pid}\", extra={\"verbosity\": 1})\n                joined.add(process.pid)\n                process.join()\n        if joined:\n            self.join()\n\n    def terminate(self):\n        \"\"\"Terminate the worker processes.\"\"\"\n        if not self._shutting_down:\n            for process in self.processes:\n                process.terminate()\n\n    def cleanup(self):\n        \"\"\"Cleanup the worker processes.\"\"\"\n        for process in self.processes:\n            process.exit()\n\n    def restart(\n        self,\n        process_names: list[str] | None = None,\n        restart_order=RestartOrder.SHUTDOWN_FIRST,\n        **kwargs,\n    ):\n        \"\"\"Restart the worker processes.\n\n        Args:\n            process_names (Optional[List[str]], optional): The names of the processes to restart.\n                If `None` then all processes will be restarted. Defaults to `None`.\n            restart_order (RestartOrder, optional): The order in which to restart the processes.\n                Defaults to `RestartOrder.SHUTDOWN_FIRST`.\n        \"\"\"  # noqa: E501\n        self.restarter.restart(\n            transient_processes=list(self.transient_processes),\n            durable_processes=list(self.durable_processes),\n            process_names=process_names,\n            restart_order=restart_order,\n            **kwargs,\n        )\n\n    def scale(self, num_worker: int):\n        if num_worker <= 0:\n            raise ValueError(\"Cannot scale to 0 workers.\")\n\n        change = num_worker - self.num_server\n        if change == 0:\n            logger.info(\n                f\"No change needed. There are already {num_worker} workers.\"\n            )\n            return\n\n        logger.info(f\"Scaling from {self.num_server} to {num_worker} workers\")\n        if change > 0:\n            for _ in range(change):\n                worker = self.create_server()\n                for process in worker.processes:\n                    process.start()\n        else:\n            for _ in range(abs(change)):\n                self.shutdown_server()\n        self.num_server = num_worker\n\n    def monitor(self):\n        \"\"\"Monitor the worker processes.\n\n        First, wait for all of the workers to acknowledge that they are ready.\n        Then, wait for messages from the workers. If a message is received\n        then it is processed and the state of the worker is updated.\n\n        Also used to restart, shutdown, and scale the workers.\n\n        Raises:\n            ServerKilled: Raised when a worker fails to come online.\n        \"\"\"\n        self.wait_for_ack()\n        while True:\n            try:\n                cycle = self._poll_monitor()\n                if cycle is MonitorCycle.BREAK:\n                    break\n                elif cycle is MonitorCycle.CONTINUE:\n                    continue\n                self._sync_states()\n                self._cleanup_non_tracked_workers()\n            except InterruptedError:\n                if not OS_IS_WINDOWS:\n                    raise\n                break\n\n    def wait_for_ack(self):  # no cov\n        \"\"\"Wait for all of the workers to acknowledge that they are ready.\"\"\"\n        misses = 0\n        message = (\n            \"It seems that one or more of your workers failed to come \"\n            \"online in the allowed time. Sanic is shutting down to avoid a \"\n            f\"deadlock. The current threshold is {self.THRESHOLD / 10}s. \"\n            \"If this problem persists, please check out the documentation \"\n            \"https://sanic.dev/en/guide/deployment/manager.html#worker-ack.\"\n        )\n        while not self._all_workers_ack():\n            if self.monitor_subscriber.poll(0.1):\n                monitor_msg = self.monitor_subscriber.recv()\n                if monitor_msg != \"__TERMINATE_EARLY__\":\n                    self.monitor_publisher.send(monitor_msg)\n                    continue\n                misses = self.THRESHOLD\n                message = (\n                    \"One of your worker processes terminated before startup \"\n                    \"was completed. Please solve any errors experienced \"\n                    \"during startup. If you do not see an exception traceback \"\n                    \"in your error logs, try running Sanic in a single \"\n                    \"process using --single-process or single_process=True. \"\n                    \"Once you are confident that the server is able to start \"\n                    \"without errors you can switch back to multiprocess mode.\"\n                )\n            misses += 1\n            if misses > self.THRESHOLD:\n                error_logger.error(\n                    \"Not all workers acknowledged a successful startup. \"\n                    \"Shutting down.\\n\\n\" + message\n                )\n                self.kill()\n\n    @property\n    def workers(self) -> list[Worker]:\n        \"\"\"Get all of the workers.\"\"\"\n        return list(self.transient.values()) + list(self.durable.values())\n\n    @property\n    def all_workers(self) -> Iterable[tuple[str, Worker]]:\n        return chain(self.transient.items(), self.durable.items())\n\n    @property\n    def processes(self):\n        \"\"\"Get all of the processes.\"\"\"\n        for worker in self.workers:\n            for process in worker.processes:\n                if not process.pid:\n                    continue\n                yield process\n\n    @property\n    def transient_processes(self):\n        \"\"\"Get all of the transient processes.\"\"\"\n        for worker in self.transient.values():\n            yield from worker.processes\n\n    @property\n    def durable_processes(self):\n        for worker in self.durable.values():\n            yield from worker.processes\n\n    def kill(self):\n        \"\"\"Kill all of the processes.\"\"\"\n        for process in self.processes:\n            logger.info(\"Killing %s [%s]\", process.name, process.pid)\n            with suppress(ProcessLookupError):\n                if os.name == \"nt\":\n                    # Windows has no os.killpg and SIGKILL doesn't seem to work\n                    os.kill(process.pid, signal.CTRL_C_EVENT)\n                else:\n                    try:\n                        os.killpg(os.getpgid(process.pid), SIGKILL)\n                    except OSError:\n                        os.kill(process.pid, SIGKILL)\n        raise ServerKilled\n\n    def shutdown_signal(self, signal, frame):\n        \"\"\"Handle the shutdown signal.\"\"\"\n        if self._shutting_down:\n            logger.info(\"Shutdown interrupted. Killing.\")\n            with suppress(ServerKilled):\n                self.kill()\n            return\n\n        logger.info(\"Received signal %s. Shutting down.\", Signals(signal).name)\n        self.monitor_publisher.send(None)\n        self.shutdown()\n\n    def shutdown(self):\n        \"\"\"Shutdown the worker manager.\"\"\"\n        for process in self.processes:\n            if process.is_alive():\n                process.terminate()\n        self._shutting_down = True\n\n    def remove_worker(self, worker: Worker) -> None:\n        if worker.tracked:\n            error_logger.error(\n                f\"Worker {worker.name} is tracked and cannot be removed.\"\n            )\n            return\n        if worker.has_alive_processes():\n            error_logger.error(\n                f\"Worker {worker.name} has alive processes and cannot be \"\n                \"removed.\"\n            )\n            return\n        self.transient.pop(worker.name, None)\n        self.durable.pop(worker.name, None)\n        for process in worker.processes:\n            self.worker_state.pop(process.name, None)\n        logger.info(\"Removed worker %s\", worker.name)\n        del worker\n\n    @property\n    def pid(self):\n        \"\"\"Get the process ID of the main process.\"\"\"\n        return os.getpid()\n\n    def _all_workers_ack(self):\n        acked = [\n            worker_state.get(\"state\") == ProcessState.ACKED.name\n            for worker_state in self.worker_state.values()\n            if worker_state.get(\"server\")\n        ]\n        return all(acked) and len(acked) == self.num_server\n\n    def _sync_states(self):\n        for process in self.processes:\n            try:\n                state = self.worker_state[process.name].get(\"state\")\n            except KeyError:\n                process.set_state(ProcessState.TERMINATED, True)\n                continue\n            # Skip state sync if process is restarting to avoid race condition\n            if process.state == ProcessState.RESTARTING:\n                continue\n            if not process.is_alive():\n                state = \"FAILED\" if process.exitcode else \"COMPLETED\"\n            if state and process.state.name != state:\n                process.set_state(ProcessState[state], True)\n\n    def _cleanup_non_tracked_workers(self) -> None:\n        to_remove = [\n            worker\n            for worker in self.workers\n            if not worker.tracked and not worker.has_alive_processes()\n        ]\n\n        for worker in to_remove:\n            self.remove_worker(worker)\n\n    def _poll_monitor(self) -> MonitorCycle | None:\n        if self.monitor_subscriber.poll(0.1):\n            message = self.monitor_subscriber.recv()\n            logger.debug(f\"Monitor message: {message}\", extra={\"verbosity\": 2})\n            if not message:\n                return MonitorCycle.BREAK\n            elif message == \"__TERMINATE__\":\n                self._handle_terminate()\n                return MonitorCycle.BREAK\n            elif isinstance(message, tuple) and (\n                len(message) == 7 or len(message) == 8\n            ):\n                self._handle_manage(*message)  # type: ignore\n                return MonitorCycle.CONTINUE\n            elif not isinstance(message, str):\n                error_logger.error(\n                    \"Monitor received an invalid message: %s\", message\n                )\n                return MonitorCycle.CONTINUE\n            return self._handle_message(message)\n        return None\n\n    def _handle_terminate(self) -> None:\n        self.shutdown()\n\n    def _handle_message(self, message: str) -> MonitorCycle | None:\n        logger.debug(\n            \"Incoming monitor message: %s\",\n            message,\n            extra={\"verbosity\": 1},\n        )\n        split_message = message.split(\":\", 2)\n        if message.startswith(\"__SCALE__\"):\n            self.scale(int(split_message[-1]))\n            return MonitorCycle.CONTINUE\n\n        processes = split_message[0]\n        reloaded_files = split_message[1] if len(split_message) > 1 else None\n        process_names: list[str] | None = [\n            name.strip() for name in processes.split(\",\")\n        ]\n        if process_names and \"__ALL_PROCESSES__\" in process_names:\n            process_names = None\n        order = (\n            RestartOrder.STARTUP_FIRST\n            if \"STARTUP_FIRST\" in split_message\n            else RestartOrder.SHUTDOWN_FIRST\n        )\n        self.restart(\n            process_names=process_names,\n            reloaded_files=reloaded_files,\n            restart_order=order,\n        )\n\n        return None\n\n    def _handle_manage(\n        self,\n        name: str,\n        func: Callable[..., Any],\n        kwargs: dict[str, Any],\n        transient: bool,\n        restartable: bool | None,\n        tracked: bool,\n        auto_start: bool,\n        workers: int,\n    ) -> None:\n        try:\n            worker = self.manage(\n                name,\n                func,\n                kwargs,\n                transient=transient,\n                restartable=restartable,\n                tracked=tracked,\n                auto_start=auto_start,\n                workers=workers,\n            )\n        except Exception:\n            error_logger.exception(\"Failed to manage worker %s\", name)\n        else:\n            if not auto_start:\n                return\n            for process in worker.processes:\n                process.start()\n"
  },
  {
    "path": "sanic/worker/multiplexer.py",
    "content": "from __future__ import annotations\n\nfrom multiprocessing.connection import Connection\nfrom os import environ, getpid\nfrom typing import Any, Callable\n\nfrom sanic.log import Colors, logger\nfrom sanic.worker.process import ProcessState\nfrom sanic.worker.state import WorkerState\n\n\nclass WorkerMultiplexer:\n    \"\"\"Multiplexer for Sanic workers.\n\n    This is instantiated inside of worker porocesses only. It is used to\n    communicate with the monitor process.\n\n    Args:\n        monitor_publisher (Connection): The connection to the monitor.\n        worker_state (Dict[str, Any]): The state of the worker.\n    \"\"\"\n\n    def __init__(\n        self,\n        monitor_publisher: Connection,\n        worker_state: dict[str, Any],\n    ):\n        self._monitor_publisher = monitor_publisher\n        self._state = WorkerState(worker_state, self.name)\n\n    def ack(self):\n        \"\"\"Acknowledge the worker is ready.\"\"\"\n        logger.debug(\n            f\"{Colors.BLUE}Process ack: {Colors.BOLD}{Colors.SANIC}\"\n            f\"%s {Colors.BLUE}[%s]{Colors.END}\",\n            self.name,\n            self.pid,\n        )\n        self._state._state[self.name] = {\n            **self._state._state[self.name],\n            \"state\": ProcessState.ACKED.name,\n        }\n\n    def manage(\n        self,\n        ident: str,\n        func: Callable[..., Any],\n        kwargs: dict[str, Any],\n        transient: bool = False,\n        restartable: bool | None = None,\n        tracked: bool = False,\n        auto_start: bool = True,\n        workers: int = 1,\n    ) -> None:\n        \"\"\"Manages the initiation and monitoring of a worker process.\n\n        Args:\n            ident (str): A unique identifier for the worker process.\n            func (Callable[..., Any]): The function to be executed in the worker process.\n            kwargs (Dict[str, Any]): A dictionary of arguments to be passed to `func`.\n            transient (bool, optional): Flag to mark the process as transient. If `True`,\n                the Worker Manager will restart the process with any global restart\n                (e.g., auto-reload). Defaults to `False`.\n            restartable (Optional[bool], optional): Flag to mark the process as restartable. If `True`,\n                the Worker Manager can restart the process if prompted. Defaults to `None`.\n            tracked (bool, optional): Flag to indicate whether the process should be tracked\n                after its completion. Defaults to `False`.\n            auto_start (bool, optional): Flag to indicate whether the process should be started\n            workers (int, optional): The number of worker processes to run. Defaults to 1.\n\n        This method packages the provided arguments into a bundle and sends them back to the\n        main process to be managed by the Worker Manager.\n        \"\"\"  # noqa: E501\n        bundle = (\n            ident,\n            func,\n            kwargs,\n            transient,\n            restartable,\n            tracked,\n            auto_start,\n            workers,\n        )\n        self._monitor_publisher.send(bundle)\n\n    def set_serving(self, serving: bool) -> None:\n        \"\"\"Set the worker to serving.\n\n        Args:\n            serving (bool): Whether the worker is serving.\n        \"\"\"\n        self._state._state[self.name] = {\n            **self._state._state[self.name],\n            \"serving\": serving,\n        }\n\n    def exit(self):\n        \"\"\"Run cleanup at worker exit.\"\"\"\n        try:\n            del self._state._state[self.name]\n        except ConnectionRefusedError:\n            logger.debug(\"Monitor process has already exited.\")\n\n    def restart(\n        self,\n        name: str = \"\",\n        all_workers: bool = False,\n        zero_downtime: bool = False,\n    ):\n        \"\"\"Restart the worker.\n\n        Args:\n            name (str): The name of the process to restart.\n            all_workers (bool): Whether to restart all workers.\n            zero_downtime (bool): Whether to restart with zero downtime.\n        \"\"\"\n        if name and all_workers:\n            raise ValueError(\n                \"Ambiguous restart with both a named process and\"\n                \" all_workers=True\"\n            )\n        if not name:\n            name = \"__ALL_PROCESSES__:\" if all_workers else self.name\n        if not name.endswith(\":\"):\n            name += \":\"\n        if zero_downtime:\n            name += \":STARTUP_FIRST\"\n        self._monitor_publisher.send(name)\n\n    reload = restart  # no cov\n    \"\"\"Alias for restart.\"\"\"\n\n    def scale(self, num_workers: int):\n        \"\"\"Scale the number of workers.\n\n        Args:\n            num_workers (int): The number of workers to scale to.\n        \"\"\"\n        message = f\"__SCALE__:{num_workers}\"\n        self._monitor_publisher.send(message)\n\n    def terminate(self, early: bool = False):\n        \"\"\"Terminate the worker.\n\n        Args:\n            early (bool): Whether to terminate early.\n        \"\"\"\n        message = \"__TERMINATE_EARLY__\" if early else \"__TERMINATE__\"\n        self._monitor_publisher.send(message)\n\n    @property\n    def pid(self) -> int:\n        \"\"\"The process ID of the worker.\"\"\"\n        return getpid()\n\n    @property\n    def name(self) -> str:\n        \"\"\"The name of the worker.\"\"\"\n        return environ.get(\"SANIC_WORKER_NAME\", \"\")\n\n    @property\n    def state(self):\n        \"\"\"The state of the worker.\"\"\"\n        return self._state\n\n    @property\n    def workers(self) -> dict[str, Any]:\n        \"\"\"The state of all workers.\"\"\"\n        return self.state.full()\n"
  },
  {
    "path": "sanic/worker/process.py",
    "content": "from __future__ import annotations\n\nimport os\n\nfrom collections.abc import MutableMapping\nfrom datetime import datetime, timezone\nfrom inspect import signature\nfrom multiprocessing.context import BaseContext\nfrom signal import SIGINT\nfrom threading import Thread\nfrom time import sleep\nfrom typing import Any\n\nfrom sanic.log import Colors, logger\nfrom sanic.worker.constants import ProcessState, RestartOrder\n\n\ndef get_now():\n    now = datetime.now(tz=timezone.utc)\n    return now\n\n\nclass WorkerProcess:\n    \"\"\"A worker process.\"\"\"\n\n    THRESHOLD = 300  # == 30 seconds\n    SERVER_LABEL = \"Server\"\n    SERVER_IDENTIFIER = \"Srv\"\n\n    def __init__(\n        self,\n        factory,\n        name,\n        ident,\n        target,\n        kwargs,\n        worker_state,\n        restartable: bool = False,\n    ):\n        self.state = ProcessState.IDLE\n        self.factory = factory\n        self.name = name\n        self.ident = ident\n        self.target = target\n        self.kwargs = kwargs\n        self.worker_state = worker_state\n        self.restartable = restartable\n        if self.name not in self.worker_state:\n            self.worker_state[self.name] = {\n                \"server\": self.SERVER_LABEL in self.name\n            }\n        self.spawn()\n\n    def set_state(self, state: ProcessState, force=False):\n        if not force and state < self.state:\n            raise Exception(\"...\")\n        self.state = state\n        self.worker_state[self.name] = {\n            **self.worker_state[self.name],\n            \"state\": self.state.name,\n        }\n\n    def start(self):\n        os.environ[\"SANIC_WORKER_NAME\"] = self.name\n        os.environ[\"SANIC_WORKER_IDENTIFIER\"] = self.ident\n        logger.debug(\n            f\"{Colors.BLUE}Starting a process: {Colors.BOLD}\"\n            f\"{Colors.SANIC}%s{Colors.END}\",\n            self.name,\n        )\n        self.set_state(ProcessState.STARTING)\n        self._current_process.start()\n        self.set_state(ProcessState.STARTED)\n        if not self.worker_state[self.name].get(\"starts\"):\n            self.worker_state[self.name] = {\n                **self.worker_state[self.name],\n                \"pid\": self.pid,\n                \"start_at\": get_now(),\n                \"starts\": 1,\n            }\n        del os.environ[\"SANIC_WORKER_NAME\"]\n        del os.environ[\"SANIC_WORKER_IDENTIFIER\"]\n\n    def join(self):\n        self.set_state(ProcessState.JOINED)\n        self._current_process.join()\n\n    def exit(self):\n        limit = 100\n        while self.is_alive() and limit > 0:\n            sleep(0.1)\n            limit -= 1\n\n        if not self.is_alive():\n            try:\n                del self.worker_state[self.name]\n            except (\n                BrokenPipeError,\n                ConnectionRefusedError,\n                ConnectionResetError,\n                EOFError,\n            ):\n                logger.debug(\"Monitor process has already exited.\")\n            except KeyError:\n                logger.debug(\"Could not find worker state to delete.\")\n\n    def terminate(self):\n        if self.state is not ProcessState.TERMINATED:\n            logger.debug(\n                f\"{Colors.BLUE}Terminating a process: \"\n                f\"{Colors.BOLD}{Colors.SANIC}\"\n                f\"%s {Colors.BLUE}[%s]{Colors.END}\",\n                self.name,\n                self.pid,\n            )\n            try:\n                self.set_state(ProcessState.TERMINATED, force=True)\n            except (BrokenPipeError, ConnectionResetError, EOFError):\n                pass\n            try:\n                os.kill(self.pid, SIGINT)\n            except (KeyError, AttributeError, ProcessLookupError):\n                ...\n\n    def restart(self, restart_order=RestartOrder.SHUTDOWN_FIRST, **kwargs):\n        logger.debug(\n            f\"{Colors.BLUE}Restarting a process: {Colors.BOLD}{Colors.SANIC}\"\n            f\"%s {Colors.BLUE}[%s]{Colors.END}\",\n            self.name,\n            self.pid,\n        )\n        self.set_state(ProcessState.RESTARTING, force=True)\n        if restart_order is RestartOrder.SHUTDOWN_FIRST:\n            self._terminate_now()\n        else:\n            self._old_process = self._current_process\n        if self._add_config():\n            self.kwargs.update(\n                {\"config\": {k.upper(): v for k, v in kwargs.items()}}\n            )\n        try:\n            self.spawn()\n            self.start()\n        except AttributeError:\n            raise RuntimeError(\"Restart failed\")\n\n        if restart_order is RestartOrder.STARTUP_FIRST:\n            self._terminate_soon()\n\n        self.worker_state[self.name] = {\n            **self.worker_state[self.name],\n            \"pid\": self.pid,\n            \"starts\": self.worker_state[self.name][\"starts\"] + 1,\n            \"restart_at\": get_now(),\n        }\n\n    def is_alive(self):\n        try:\n            return self._current_process.is_alive()\n        except AssertionError:\n            return False\n\n    def spawn(self):\n        if self.state not in (ProcessState.IDLE, ProcessState.RESTARTING):\n            raise Exception(\"Cannot spawn a worker process until it is idle.\")\n        self._current_process = self.factory(\n            name=self.name,\n            target=self.target,\n            kwargs=self.kwargs,\n            daemon=True,\n        )\n\n    @property\n    def pid(self):\n        return self._current_process.pid\n\n    @property\n    def exitcode(self):\n        return self._current_process.exitcode\n\n    def _terminate_now(self):\n        if not self._current_process.is_alive():\n            return\n        logger.debug(\n            f\"{Colors.BLUE}Begin restart termination: \"\n            f\"{Colors.BOLD}{Colors.SANIC}\"\n            f\"%s {Colors.BLUE}[%s]{Colors.END}\",\n            self.name,\n            self._current_process.pid,\n        )\n        self._current_process.terminate()\n\n    def _terminate_soon(self):\n        logger.debug(\n            f\"{Colors.BLUE}Begin restart termination: \"\n            f\"{Colors.BOLD}{Colors.SANIC}\"\n            f\"%s {Colors.BLUE}[%s]{Colors.END}\",\n            self.name,\n            self._current_process.pid,\n        )\n        termination_thread = Thread(target=self._wait_to_terminate)\n        termination_thread.start()\n\n    def _wait_to_terminate(self):\n        logger.debug(\n            f\"{Colors.BLUE}Waiting for process to be acked: \"\n            f\"{Colors.BOLD}{Colors.SANIC}\"\n            f\"%s {Colors.BLUE}[%s]{Colors.END}\",\n            self.name,\n            self._old_process.pid,\n        )\n        misses = 0\n        while self.state is not ProcessState.ACKED:\n            sleep(0.1)\n            misses += 1\n            if misses > self.THRESHOLD:\n                raise TimeoutError(\n                    f\"Worker {self.name} failed to come ack within \"\n                    f\"{self.THRESHOLD / 10} seconds\"\n                )\n        else:\n            logger.debug(\n                f\"{Colors.BLUE}Process acked. Terminating: \"\n                f\"{Colors.BOLD}{Colors.SANIC}\"\n                f\"%s {Colors.BLUE}[%s]{Colors.END}\",\n                self.name,\n                self._old_process.pid,\n            )\n            self._old_process.terminate()\n        delattr(self, \"_old_process\")\n\n    def _add_config(self) -> bool:\n        sig = signature(self.target)\n        if \"config\" in sig.parameters or any(\n            param.kind == param.VAR_KEYWORD\n            for param in sig.parameters.values()\n        ):\n            return True\n        return False\n\n\nclass Worker:\n    WORKER_PREFIX = \"Sanic\"\n\n    def __init__(\n        self,\n        ident: str,\n        name: str,\n        serve,\n        server_settings,\n        context: BaseContext,\n        worker_state: MutableMapping[str, Any],\n        num: int = 1,\n        restartable: bool = False,\n        tracked: bool = True,\n        auto_start: bool = True,\n    ):\n        self.ident = ident\n        self.name = name\n        self.num = num\n        self.context = context\n        self.serve = serve\n        self.server_settings = server_settings\n        self.worker_state = worker_state\n        self.processes: set[WorkerProcess] = set()\n        self.restartable = restartable\n        self.tracked = tracked\n        self.auto_start = auto_start\n        for _ in range(num):\n            self.create_process()\n\n    def create_process(self) -> WorkerProcess:\n        process = WorkerProcess(\n            # Need to ignore this typing error - The problem is the\n            # BaseContext itself has no Process. But, all of its\n            # implementations do. We can safely ignore as it is a typing\n            # issue in the standard lib.\n            factory=self.context.Process,  # type: ignore\n            name=\"-\".join(\n                [self.WORKER_PREFIX, self.name, str(len(self.processes))]\n            ),\n            ident=self.ident,\n            target=self.serve,\n            kwargs={**self.server_settings},\n            worker_state=self.worker_state,\n            restartable=self.restartable,\n        )\n        self.processes.add(process)\n        return process\n\n    def has_alive_processes(self) -> bool:\n        return any(process.is_alive() for process in self.processes)\n"
  },
  {
    "path": "sanic/worker/reloader.py",
    "content": "from __future__ import annotations\n\nimport os\nimport sys\n\nfrom asyncio import new_event_loop\nfrom itertools import chain\nfrom multiprocessing.connection import Connection\nfrom pathlib import Path\nfrom signal import SIGINT, SIGTERM\nfrom signal import signal as signal_func\nfrom time import sleep\n\nfrom sanic.server.events import trigger_events\nfrom sanic.worker.loader import AppLoader\n\n\nclass Reloader:\n    INTERVAL = 1.0  # seconds\n\n    def __init__(\n        self,\n        publisher: Connection,\n        interval: float,\n        reload_dirs: set[Path],\n        app_loader: AppLoader,\n    ):\n        self._publisher = publisher\n        self.interval = interval or self.INTERVAL\n        self.reload_dirs = reload_dirs\n        self.run = True\n        self.app_loader = app_loader\n\n    def __call__(self) -> None:\n        app = self.app_loader.load()\n        signal_func(SIGINT, self.stop)\n        signal_func(SIGTERM, self.stop)\n        mtimes: dict[str, float] = {}\n\n        reloader_start = app.listeners.get(\"reload_process_start\")\n        reloader_stop = app.listeners.get(\"reload_process_stop\")\n        before_trigger = app.listeners.get(\"before_reload_trigger\")\n        after_trigger = app.listeners.get(\"after_reload_trigger\")\n        loop = new_event_loop()\n        if reloader_start:\n            trigger_events(reloader_start, loop, app)\n\n        while self.run:\n            changed = set()\n            for filename in self.files():\n                try:\n                    if self.check_file(filename, mtimes):\n                        path = (\n                            filename\n                            if isinstance(filename, str)\n                            else filename.resolve()\n                        )\n                        changed.add(str(path))\n                except OSError:\n                    continue\n            if changed:\n                if before_trigger:\n                    trigger_events(before_trigger, loop, app)\n                self.reload(\",\".join(changed) if changed else \"unknown\")\n                if after_trigger:\n                    trigger_events(after_trigger, loop, app, changed=changed)\n            sleep(self.interval)\n        else:\n            if reloader_stop:\n                trigger_events(reloader_stop, loop, app)\n\n    def stop(self, *_):\n        self.run = False\n\n    def reload(self, reloaded_files):\n        message = f\"__ALL_PROCESSES__:{reloaded_files}\"\n        self._publisher.send(message)\n\n    def files(self):\n        return chain(\n            self.python_files(),\n            *(d.glob(\"**/*\") for d in self.reload_dirs),\n        )\n\n    def python_files(self):  # no cov\n        \"\"\"This iterates over all relevant Python files.\n\n        It goes through all\n        loaded files from modules, all files in folders of already loaded\n        modules as well as all files reachable through a package.\n        \"\"\"\n        # The list call is necessary on Python 3 in case the module\n        # dictionary modifies during iteration.\n        for module in list(sys.modules.values()):\n            if module is None:\n                continue\n            filename = getattr(module, \"__file__\", None)\n            if filename:\n                old = None\n                while not os.path.isfile(filename):\n                    old = filename\n                    filename = os.path.dirname(filename)\n                    if filename == old:\n                        break\n                else:\n                    if filename[-4:] in (\".pyc\", \".pyo\"):\n                        filename = filename[:-1]\n                    yield filename\n\n    @staticmethod\n    def check_file(filename, mtimes) -> bool:\n        need_reload = False\n\n        mtime = os.stat(filename).st_mtime\n        old_time = mtimes.get(filename)\n        if old_time is None:\n            mtimes[filename] = mtime\n        elif mtime > old_time:\n            mtimes[filename] = mtime\n            need_reload = True\n\n        return need_reload\n"
  },
  {
    "path": "sanic/worker/restarter.py",
    "content": "from __future__ import annotations\n\nfrom sanic.log import error_logger\nfrom sanic.worker.constants import RestartOrder\nfrom sanic.worker.process import ProcessState, WorkerProcess\n\n\nclass Restarter:\n    def restart(\n        self,\n        transient_processes: list[WorkerProcess],\n        durable_processes: list[WorkerProcess],\n        process_names: list[str] | None = None,\n        restart_order=RestartOrder.SHUTDOWN_FIRST,\n        **kwargs,\n    ) -> None:\n        \"\"\"Restart the worker processes.\n\n        Args:\n            process_names (Optional[List[str]], optional): The names of the processes to restart.\n                If `None`, then all processes will be restarted. Defaults to `None`.\n            restart_order (RestartOrder, optional): The order in which to restart the processes.\n                Defaults to `RestartOrder.SHUTDOWN_FIRST`.\n        \"\"\"  # noqa: E501\n        restarted = self._restart_transient(\n            transient_processes,\n            process_names or [],\n            restart_order,\n            **kwargs,\n        )\n        restarted |= self._restart_durable(\n            durable_processes,\n            process_names or [],\n            restart_order,\n            **kwargs,\n        )\n\n        if process_names and not restarted:\n            error_logger.error(\n                f\"Failed to restart processes: {', '.join(process_names)}\"\n            )\n\n    def _restart_transient(\n        self,\n        processes: list[WorkerProcess],\n        process_names: list[str],\n        restart_order: RestartOrder,\n        **kwargs,\n    ) -> set[str]:\n        restarted: set[str] = set()\n        for process in processes:\n            if not process.restartable or (\n                process_names and process.name not in process_names\n            ):\n                continue\n            self._restart_process(process, restart_order, **kwargs)\n            restarted.add(process.name)\n        return restarted\n\n    def _restart_durable(\n        self,\n        processes: list[WorkerProcess],\n        process_names: list[str],\n        restart_order: RestartOrder,\n        **kwargs,\n    ) -> set[str]:\n        restarted: set[str] = set()\n        if not process_names:\n            return restarted\n        for process in processes:\n            if not process.restartable or process.name not in process_names:\n                continue\n            if process.state not in (\n                ProcessState.COMPLETED,\n                ProcessState.FAILED,\n                ProcessState.NONE,\n            ):\n                error_logger.error(\n                    f\"Cannot restart process {process.name} because \"\n                    \"it is not in a final state. Current state is: \"\n                    f\"{process.state.name}.\"\n                )\n                continue\n            self._restart_process(process, restart_order, **kwargs)\n            restarted.add(process.name)\n\n        return restarted\n\n    def _restart_process(self, process, restart_order, **kwargs):\n        process.restart(restart_order=restart_order, **kwargs)\n"
  },
  {
    "path": "sanic/worker/serve.py",
    "content": "from __future__ import annotations\n\nimport asyncio\nimport os\nimport socket\nimport warnings\n\nfrom functools import partial\nfrom multiprocessing.connection import Connection\nfrom ssl import SSLContext\nfrom typing import Any\n\nfrom sanic.application.constants import ServerStage\nfrom sanic.application.state import ApplicationServerInfo\nfrom sanic.http.constants import HTTP\nfrom sanic.log import error_logger\nfrom sanic.logging.setup import setup_logging\nfrom sanic.models.server_types import Signal\nfrom sanic.server.protocols.http_protocol import HttpProtocol\nfrom sanic.server.runners import _serve_http_1, _serve_http_3\nfrom sanic.worker.loader import AppLoader, CertLoader\nfrom sanic.worker.multiplexer import WorkerMultiplexer\nfrom sanic.worker.process import Worker, WorkerProcess\n\n\ndef worker_serve(\n    host,\n    port,\n    app_name: str,\n    monitor_publisher: Connection | None,\n    app_loader: AppLoader,\n    worker_state: dict[str, Any] | None = None,\n    server_info: dict[str, list[ApplicationServerInfo]] | None = None,\n    ssl: SSLContext | dict[str, str | os.PathLike[str]] | None = None,\n    sock: socket.socket | None = None,\n    unix: str | None = None,\n    reuse_port: bool = False,\n    loop=None,\n    protocol: type[asyncio.Protocol] = HttpProtocol,\n    backlog: int = 100,\n    register_sys_signals: bool = True,\n    run_multiple: bool = False,\n    run_async: bool = False,\n    connections=None,\n    signal=Signal(),\n    state=None,\n    asyncio_server_kwargs=None,\n    version=HTTP.VERSION_1,\n    config: bytes | str | dict[str, Any] | Any | None = None,\n    passthru: dict[str, Any] | None = None,\n):\n    try:\n        from sanic import Sanic\n\n        if app_loader:\n            app = app_loader.load()\n        else:\n            app = Sanic.get_app(app_name)\n\n        app.refresh(passthru)\n        app.setup_loop()\n        setup_logging(\n            app.state.is_debug, app.config.NO_COLOR, app.config.LOG_EXTRA\n        )\n\n        loop = asyncio.new_event_loop()\n        asyncio.set_event_loop(loop)\n\n        # Hydrate server info if needed\n        if server_info:\n            for app_name, server_info_objects in server_info.items():\n                a = Sanic.get_app(app_name)\n                if not a.state.server_info:\n                    a.state.server_info = []\n                    for info in server_info_objects:\n                        if not info.settings.get(\"app\"):\n                            info.settings[\"app\"] = a\n                        a.state.server_info.append(info)\n\n        if isinstance(ssl, dict) or app.certloader_class is not CertLoader:\n            cert_loader = app.certloader_class(ssl or {})\n            ssl = cert_loader.load(app)\n            for info in app.state.server_info:\n                info.settings[\"ssl\"] = ssl\n\n        # When in a worker process, do some init\n        worker_name = os.environ.get(\"SANIC_WORKER_NAME\")\n        if worker_name and worker_name.startswith(\n            \"-\".join([Worker.WORKER_PREFIX, WorkerProcess.SERVER_LABEL])\n        ):\n            # Hydrate apps with any passed server info\n\n            if monitor_publisher is None:\n                raise RuntimeError(\n                    \"No restart publisher found in worker process\"\n                )\n            if worker_state is None:\n                raise RuntimeError(\"No worker state found in worker process\")\n\n            # Run secondary servers\n            apps = list(Sanic._app_registry.values())\n            app.before_server_start(partial(app._start_servers, apps=apps))\n            for a in apps:\n                a.multiplexer = WorkerMultiplexer(\n                    monitor_publisher, worker_state\n                )\n\n        if app.debug:\n            loop.set_debug(app.debug)\n\n        app.asgi = False\n\n        if app.state.server_info:\n            primary_server_info = app.state.server_info[0]\n            primary_server_info.stage = ServerStage.SERVING\n        if config:\n            app.update_config(config)\n\n        if version is HTTP.VERSION_3:\n            return _serve_http_3(host, port, app, loop, ssl)\n        return _serve_http_1(\n            host,\n            port,\n            app,\n            ssl,\n            sock,\n            unix,\n            reuse_port,\n            loop,\n            protocol,\n            backlog,\n            register_sys_signals,\n            run_multiple,\n            run_async,\n            connections,\n            signal,\n            state,\n            asyncio_server_kwargs,\n        )\n    except Exception as e:\n        warnings.simplefilter(\"ignore\", category=RuntimeWarning)\n        if monitor_publisher:\n            error_logger.exception(e)\n            multiplexer = WorkerMultiplexer(monitor_publisher, {})\n            multiplexer.terminate(True)\n        else:\n            raise e\n"
  },
  {
    "path": "sanic/worker/state.py",
    "content": "from __future__ import annotations\n\nfrom collections.abc import ItemsView, Iterator, KeysView, Mapping, ValuesView\nfrom collections.abc import Mapping as MappingType\nfrom typing import Any\n\n\nclass WorkerState(Mapping):\n    RESTRICTED = (\n        \"health\",\n        \"pid\",\n        \"requests\",\n        \"restart_at\",\n        \"server\",\n        \"start_at\",\n        \"starts\",\n        \"state\",\n    )\n\n    def __init__(self, state: dict[str, Any], current: str) -> None:\n        self._name = current\n        self._state = state\n\n    def __getitem__(self, key: str) -> Any:\n        return self._state[self._name][key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        if key in self.RESTRICTED:\n            self._write_error([key])\n        self._state[self._name] = {\n            **self._state[self._name],\n            key: value,\n        }\n\n    def __delitem__(self, key: str) -> None:\n        if key in self.RESTRICTED:\n            self._write_error([key])\n        self._state[self._name] = {\n            k: v for k, v in self._state[self._name].items() if k != key\n        }\n\n    def __iter__(self) -> Iterator[Any]:\n        return iter(self._state[self._name])\n\n    def __len__(self) -> int:\n        return len(self._state[self._name])\n\n    def __repr__(self) -> str:\n        return repr(self._state[self._name])\n\n    def __eq__(self, other: object) -> bool:\n        return self._state[self._name] == other\n\n    def keys(self) -> KeysView[str]:\n        return self._state[self._name].keys()\n\n    def values(self) -> ValuesView[Any]:\n        return self._state[self._name].values()\n\n    def items(self) -> ItemsView[str, Any]:\n        return self._state[self._name].items()\n\n    def update(self, mapping: MappingType[str, Any]) -> None:\n        if any(k in self.RESTRICTED for k in mapping.keys()):\n            self._write_error(\n                [k for k in mapping.keys() if k in self.RESTRICTED]\n            )\n        self._state[self._name] = {\n            **self._state[self._name],\n            **mapping,\n        }\n\n    def pop(self) -> None:\n        raise NotImplementedError\n\n    def full(self) -> dict[str, Any]:\n        return dict(self._state)\n\n    def _write_error(self, keys: list[str]) -> None:\n        raise LookupError(\n            f\"Cannot set restricted key{'s' if len(keys) > 1 else ''} on \"\n            f\"WorkerState: {', '.join(keys)}\"\n        )\n"
  },
  {
    "path": "scripts/changelog.py",
    "content": "#!/usr/bin/env python\n\nimport sys\n\nfrom os import path\n\n\nif __name__ == \"__main__\":\n    try:\n        import click\n        import towncrier\n    except ImportError:\n        print(\n            \"Please make sure you have installed towncrier and \"\n            \"click before using this tool\"\n        )\n        sys.exit(1)\n\n    @click.command()\n    @click.option(\n        \"--draft\",\n        \"draft\",\n        default=False,\n        flag_value=True,\n        help=\"Render the news fragments, don't write to files, \"\n        \"don't check versions.\",\n    )\n    @click.option(\n        \"--dir\", \"directory\", default=path.dirname(path.abspath(__file__))\n    )\n    @click.option(\"--name\", \"project_name\", default=None)\n    @click.option(\n        \"--version\",\n        \"project_version\",\n        default=None,\n        help=\"Render the news fragments using given version.\",\n    )\n    @click.option(\"--date\", \"project_date\", default=None)\n    @click.option(\n        \"--yes\",\n        \"answer_yes\",\n        default=False,\n        flag_value=True,\n        help=\"Do not ask for confirmation to remove news fragments.\",\n    )\n    def _main(\n        draft,\n        directory,\n        project_name,\n        project_version,\n        project_date,\n        answer_yes,\n    ):\n        return towncrier.__main(\n            draft,\n            directory,\n            project_name,\n            project_version,\n            project_date,\n            answer_yes,\n        )\n\n    _main()\n"
  },
  {
    "path": "scripts/pyproject.toml",
    "content": "[tool.towncrier]\npackage = \"sanic\"\npackage_dir = \"..\"\nfilename = \"../CHANGELOG.rst\"\ndirectory = \"./changelogs\"\nunderlines = [\"=\", \"*\", \"~\"]\nissue_format = \"`#{issue} <https://github.com/huge-success/sanic/issues/{issue}>`__\"\ntitle_format = \"Version {version}\"\n\n[[tool.towncrier.type]]\ndirectory = \"feature\"\nname = \"Features\"\nshowcontent = true\n\n[[tool.towncrier.type]]\ndirectory = \"bugfix\"\nname = \"Bugfixes\"\nshowcontent = true\n\n[[tool.towncrier.type]]\ndirectory = \"doc\"\nname = \"Improved Documentation\"\nshowcontent = true\n\n[[tool.towncrier.type]]\ndirectory = \"removal\"\nname = \"Deprecations and Removals\"\nshowcontent = true\n\n[[tool.towncrier.type]]\ndirectory = \"misc\"\nname = \"Miscellaneous internal changes\"\nshowcontent = true\n"
  },
  {
    "path": "scripts/release.py",
    "content": "#!/usr/bin/env python\n\nimport sys\n\nfrom argparse import ArgumentParser, Namespace\nfrom collections import OrderedDict\nfrom configparser import RawConfigParser\nfrom datetime import datetime\nfrom json import dumps\nfrom os import chdir, path\nfrom subprocess import PIPE, Popen\n\nimport towncrier\n\nfrom jinja2 import BaseLoader, Environment\nfrom requests import patch\n\n\nGIT_COMMANDS = {\n    \"get_tag\": [\"git describe --tags --abbrev=0\"],\n    \"commit_version_change\": [\n        \"git add . && git commit -m 'Bumping up version from \"\n        \"{current_version} to {new_version}'\"\n    ],\n    \"create_new_tag\": [\n        \"git tag -a {new_version} -m 'Bumping up version from \"\n        \"{current_version} to {new_version}'\"\n    ],\n    \"push_tag\": [\"git push origin {new_version}\"],\n    \"get_change_log\": [\n        'git log --no-merges --pretty=format:\"%h::: %cn::: %s\" '\n        \"{current_version}..\"\n    ],\n}\n\n\nRELEASE_NOTE_TEMPLATE = \"\"\"\n# {{ release_name }} - {% now 'utc', '%Y-%m-%d' %}\n\nTo see the exhaustive list of pull requests included in this release see:\nhttps://github.com/huge-success/sanic/milestone/{{milestone}}?closed=1\n\n# Changelog\n{% for row in changelogs %}\n* {{ row -}}\n{% endfor %}\n\n# Credits\n{% for author in authors %}\n* {{ author -}}\n{% endfor %}\n\"\"\"\n\nJINJA_RELEASE_NOTE_TEMPLATE = Environment(\n    loader=BaseLoader, extensions=[\"jinja2_time.TimeExtension\"]\n).from_string(RELEASE_NOTE_TEMPLATE)\n\nRELEASE_NOTE_UPDATE_URL = (\n    \"https://api.github.com/repos/huge-success/sanic/releases/tags/\"\n    \"{new_version}?access_token={token}\"\n)\n\n\nclass Directory:\n    def __init__(self):\n        self._old_path = path.dirname(path.abspath(__file__))\n        self._new_path = path.dirname(self._old_path)\n\n    def __enter__(self):\n        chdir(self._new_path)\n\n    def __exit__(self, exc_type, exc_val, exc_tb):\n        chdir(self._old_path)\n\n\ndef _run_shell_command(command: list):\n    try:\n        process = Popen(\n            command, stderr=PIPE, stdout=PIPE, stdin=PIPE, shell=True\n        )\n        output, error = process.communicate()\n        return_code = process.returncode\n        return output.decode(\"utf-8\"), error, return_code\n    except Exception:\n        return None, None, -1\n\n\ndef _fetch_default_calendar_release_version():\n    return datetime.now().strftime(\"%y.%m.0\")\n\n\ndef _fetch_current_version(config_file: str) -> str:\n    if path.isfile(config_file):\n        config_parser = RawConfigParser()\n        with open(config_file) as cfg:\n            config_parser.read_file(cfg)\n            return (\n                config_parser.get(\"version\", \"current_version\")\n                or _fetch_default_calendar_release_version()\n            )\n    else:\n        return _fetch_default_calendar_release_version()\n\n\ndef _change_micro_version(current_version: str):\n    version_string = current_version.split(\".\")\n    version_string[-1] = str((int(version_string[-1]) + 1))\n    return \".\".join(version_string)\n\n\ndef _get_new_version(\n    config_file: str = \"./setup.cfg\",\n    current_version: str = None,\n    micro_release: bool = False,\n):\n    if micro_release:\n        if current_version:\n            return _change_micro_version(current_version)\n        elif config_file:\n            return _change_micro_version(_fetch_current_version(config_file))\n        else:\n            return _fetch_default_calendar_release_version()\n    else:\n        return _fetch_default_calendar_release_version()\n\n\ndef _get_current_tag(git_command_name=\"get_tag\"):\n    global GIT_COMMANDS\n    command = GIT_COMMANDS.get(git_command_name)\n    out, err, ret = _run_shell_command(command)\n    if str(out):\n        return str(out).split(\"\\n\")[0]\n    else:\n        return None\n\n\ndef _update_release_version_for_sanic(\n    current_version, new_version, config_file, generate_changelog\n):\n    config_parser = RawConfigParser()\n    with open(config_file) as cfg:\n        config_parser.read_file(cfg)\n    config_parser.set(\"version\", \"current_version\", new_version)\n\n    version_files = config_parser.get(\"version\", \"files\")\n    current_version_line = config_parser.get(\n        \"version\", \"current_version_pattern\"\n    ).format(current_version=current_version)\n    new_version_line = config_parser.get(\n        \"version\", \"new_version_pattern\"\n    ).format(new_version=new_version)\n\n    for version_file in version_files.split(\",\"):\n        with open(version_file) as init_file:\n            data = init_file.read()\n\n        new_data = data.replace(current_version_line, new_version_line)\n        with open(version_file, \"w\") as init_file:\n            init_file.write(new_data)\n\n    with open(config_file, \"w\") as config:\n        config_parser.write(config)\n\n    if generate_changelog:\n        towncrier.__main(\n            draft=False,\n            directory=path.dirname(path.abspath(__file__)),\n            project_name=None,\n            project_version=new_version,\n            project_date=None,\n            answer_yes=True,\n        )\n\n    command = GIT_COMMANDS.get(\"commit_version_change\")\n    command[0] = command[0].format(\n        new_version=new_version, current_version=current_version\n    )\n    _, err, ret = _run_shell_command(command)\n    if int(ret) != 0:\n        print(\n            \"Failed to Commit Version upgrade changes to Sanic: {}\".format(\n                err.decode(\"utf-8\")\n            )\n        )\n        sys.exit(1)\n\n\ndef _generate_change_log(current_version: str = None):\n    global GIT_COMMANDS\n    command = GIT_COMMANDS.get(\"get_change_log\")\n    command[0] = command[0].format(current_version=current_version)\n    output, error, ret = _run_shell_command(command=command)\n    if not str(output):\n        print(\"Unable to Fetch Change log details to update the Release Note\")\n        sys.exit(1)\n\n    commit_details = OrderedDict()\n    commit_details[\"authors\"] = {}\n    commit_details[\"commits\"] = []\n\n    for line in str(output).split(\"\\n\"):\n        commit, author, description = line.split(\":::\")\n        if \"GitHub\" not in author:\n            commit_details[\"authors\"][author] = 1\n        commit_details[\"commits\"].append(\" - \".join([commit, description]))\n\n    return commit_details\n\n\ndef _generate_markdown_document(\n    milestone, release_name, current_version, release_version\n):\n    global JINJA_RELEASE_NOTE_TEMPLATE\n    release_name = release_name or release_version\n    change_log = _generate_change_log(current_version=current_version)\n    return JINJA_RELEASE_NOTE_TEMPLATE.render(\n        release_name=release_name,\n        milestone=milestone,\n        changelogs=change_log[\"commits\"],\n        authors=change_log[\"authors\"].keys(),\n    )\n\n\ndef _tag_release(new_version, current_version, milestone, release_name, token):\n    global GIT_COMMANDS\n    global RELEASE_NOTE_UPDATE_URL\n    for command_name in [\"create_new_tag\", \"push_tag\"]:\n        command = GIT_COMMANDS.get(command_name)\n        command[0] = command[0].format(\n            new_version=new_version, current_version=current_version\n        )\n        out, error, ret = _run_shell_command(command=command)\n        if int(ret) != 0:\n            print(\"Failed to execute the command: {}\".format(command[0]))\n            sys.exit(1)\n\n    change_log = _generate_markdown_document(\n        milestone, release_name, current_version, new_version\n    )\n\n    body = {\"name\": release_name or new_version, \"body\": change_log}\n\n    headers = {\"content-type\": \"application/json\"}\n\n    response = patch(\n        RELEASE_NOTE_UPDATE_URL.format(new_version=new_version, token=token),\n        data=dumps(body),\n        headers=headers,\n    )\n    response.raise_for_status()\n\n\ndef release(args: Namespace):\n    current_tag = _get_current_tag()\n    current_version = _fetch_current_version(args.config)\n    if current_tag and current_version not in current_tag:\n        print(\n            \"Tag mismatch between what's in git and what was provided by \"\n            \"--current-version. Existing: {}, Give: {}\".format(\n                current_tag, current_version\n            )\n        )\n        sys.exit(1)\n    new_version = args.release_version or _get_new_version(\n        args.config, current_version, args.micro_release\n    )\n    _update_release_version_for_sanic(\n        current_version=current_version,\n        new_version=new_version,\n        config_file=args.config,\n        generate_changelog=args.generate_changelog,\n    )\n    if args.tag_release:\n        _tag_release(\n            current_version=current_version,\n            new_version=new_version,\n            milestone=args.milestone,\n            release_name=args.release_name,\n            token=args.token,\n        )\n\n\nif __name__ == \"__main__\":\n    cli = ArgumentParser(description=\"Sanic Release Manager\")\n    cli.add_argument(\n        \"--release-version\",\n        \"-r\",\n        help=\"New Version to use for Release\",\n        default=_fetch_default_calendar_release_version(),\n        required=False,\n    )\n    cli.add_argument(\n        \"--current-version\",\n        \"-cv\",\n        help=\"Current Version to default in case if you don't want to \"\n        \"use the version configuration files\",\n        default=None,\n        required=False,\n    )\n    cli.add_argument(\n        \"--config\",\n        \"-c\",\n        help=\"Configuration file used for release\",\n        default=\"./setup.cfg\",\n        required=False,\n    )\n    cli.add_argument(\n        \"--token\",\n        \"-t\",\n        help=\"Git access token with necessary access to Huge Sanic Org\",\n        required=False,\n    )\n    cli.add_argument(\n        \"--milestone\",\n        \"-ms\",\n        help=\"Git Release milestone information to include in release note\",\n        required=False,\n    )\n    cli.add_argument(\n        \"--release-name\",\n        \"-n\",\n        help=\"Release Name to use if any\",\n        required=False,\n    )\n    cli.add_argument(\n        \"--micro-release\",\n        \"-m\",\n        help=\"Micro Release with patches only\",\n        default=False,\n        action=\"store_true\",\n        required=False,\n    )\n    cli.add_argument(\n        \"--tag-release\",\n        help=\"Tag a new release for Sanic\",\n        default=False,\n        action=\"store_true\",\n        required=False,\n    )\n    cli.add_argument(\n        \"--generate-changelog\",\n        help=\"Generate changelog for Sanic as part of release\",\n        default=False,\n        action=\"store_true\",\n        required=False,\n    )\n    args = cli.parse_args()\n    if args.tag_release:\n        for key, value in {\n            \"--token/-t\": args.token,\n            \"--milestone/-m\": args.milestone,\n        }.items():\n            if not value:\n                print(f\"{key} is mandatory while using --tag-release\")\n                sys.exit(1)\n    with Directory():\n        release(args)\n"
  },
  {
    "path": "setup.py",
    "content": "\"\"\"\nSanic\n\"\"\"\n\nimport codecs\nimport os\nimport re\nimport sys\n\nfrom setuptools import find_packages, setup\nfrom setuptools.command.test import test as TestCommand\n\n\nclass PyTest(TestCommand):\n    \"\"\"\n    Provide a Test runner to be used from setup.py to run unit tests\n    \"\"\"\n\n    user_options = [(\"pytest-args=\", \"a\", \"Arguments to pass to pytest\")]\n\n    def initialize_options(self):\n        TestCommand.initialize_options(self)\n        self.pytest_args = \"\"\n\n    def run_tests(self):\n        import shlex\n\n        import pytest\n\n        errno = pytest.main(shlex.split(self.pytest_args))\n        sys.exit(errno)\n\n\ndef open_local(paths, mode=\"r\", encoding=\"utf8\"):\n    path = os.path.join(os.path.abspath(os.path.dirname(__file__)), *paths)\n\n    return codecs.open(path, mode, encoding)\n\n\ndef str_to_bool(val: str) -> bool:\n    val = val.lower()\n    if val in {\n        \"y\",\n        \"yes\",\n        \"yep\",\n        \"yup\",\n        \"t\",\n        \"true\",\n        \"on\",\n        \"enable\",\n        \"enabled\",\n        \"1\",\n    }:\n        return True\n    elif val in {\n        \"n\",\n        \"no\",\n        \"f\",\n        \"nope\",\n        \"false\",\n        \"off\",\n        \"disable\",\n        \"disabled\",\n        \"0\",\n    }:\n        return False\n    else:\n        raise ValueError(f\"Invalid truth value {val}\")\n\n\nwith open_local([\"sanic\", \"__version__.py\"], encoding=\"latin1\") as fp:\n    try:\n        version = re.findall(\n            r\"^__version__ = \\\"([^']+)\\\"\\r?$\", fp.read(), re.M\n        )[0]\n    except IndexError:\n        raise RuntimeError(\"Unable to determine version.\")\n\nwith open_local([\"README.rst\"]) as rm:\n    long_description = rm.read()\n\nsetup_kwargs = {\n    \"name\": \"sanic\",\n    \"version\": version,\n    \"url\": \"http://github.com/sanic-org/sanic/\",\n    \"license\": \"MIT\",\n    \"author\": \"Sanic Community\",\n    \"author_email\": \"admhpkns@gmail.com\",\n    \"description\": (\n        \"A web server and web framework that's written to go fast. \"\n        \"Build fast. Run fast.\"\n    ),\n    \"long_description\": long_description,\n    \"packages\": find_packages(exclude=(\"tests\", \"tests.*\")),\n    \"package_data\": {\"sanic\": [\"py.typed\", \"pages/styles/*\"]},\n    \"platforms\": \"any\",\n    \"python_requires\": \">=3.10\",\n    \"classifiers\": [\n        \"Development Status :: 4 - Beta\",\n        \"Environment :: Web Environment\",\n        \"License :: OSI Approved :: MIT License\",\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    ],\n    \"entry_points\": {\"console_scripts\": [\"sanic = sanic.__main__:main\"]},\n}\n\nenv_dependency = (\n    '; sys_platform != \"win32\" ' 'and implementation_name == \"cpython\"'\n)\nujson = \"ujson>=1.35\" + env_dependency\nuvloop = \"uvloop>=0.15.0\" + env_dependency\ntypes_ujson = \"types-ujson\" + env_dependency\nrequirements = [\n    \"sanic-routing>=23.12.0\",\n    \"httptools>=0.0.10\",\n    uvloop,\n    ujson,\n    \"aiofiles>=0.6.0\",\n    \"websockets>=10.0\",\n    \"multidict>=5.0,<7.0\",\n    \"html5tagger>=1.2.1\",\n    \"tracerite>=2.2.0\",\n    \"typing-extensions>=4.4.0\",\n    \"setuptools>=70.1.0\",\n]\n\ntests_require = [\n    \"sanic-testing>=23.6.0\",\n    \"pytest>=8.2.2\",\n    \"pytest-xdist>=3.5.0\",\n    \"pytest-cov>=4.0.0\",\n    \"coverage\",\n    \"beautifulsoup4\",\n    \"pytest-sanic\",\n    \"pytest-benchmark\",\n    \"chardet==3.*\",\n    \"ruff\",\n    \"bandit\",\n    \"mypy\",\n    \"docutils\",\n    \"pygments\",\n    \"uvicorn\",\n    \"slotscheck>=0.8.0,<1\",\n    types_ujson,\n]\n\ndocs_require = [\n    \"sphinx>=2.1.2\",\n    \"sphinx_rtd_theme>=0.4.3\",\n    \"docutils\",\n    \"pygments\",\n    \"m2r2\",\n    \"enum-tools[sphinx]\",\n    \"mistune>=2.0.0\",\n    \"autodocsumm>=0.2.11\",\n    \"msgspec\",\n    \"python-frontmatter\",\n    \"docstring-parser\",\n    \"libsass\",\n]\n\ndev_require = tests_require + [\n    \"cryptography\",\n    \"tox\",\n    \"towncrier\",\n]\n\nall_require = list(set(dev_require + docs_require))\n\nif str_to_bool(os.environ.get(\"SANIC_NO_UJSON\", \"no\")):\n    print(\"Installing without uJSON\")\n    requirements.remove(ujson)\n    tests_require.remove(types_ujson)\n\n# 'nt' means windows OS\nif str_to_bool(os.environ.get(\"SANIC_NO_UVLOOP\", \"no\")):\n    print(\"Installing without uvLoop\")\n    requirements.remove(uvloop)\n\nextras_require = {\n    \"test\": tests_require,\n    \"dev\": dev_require,\n    \"docs\": docs_require,\n    \"all\": all_require,\n    \"ext\": [\"sanic-ext\"],\n    \"http3\": [\"aioquic\"],\n}\n\nsetup_kwargs[\"install_requires\"] = requirements\nsetup_kwargs[\"tests_require\"] = tests_require\nsetup_kwargs[\"extras_require\"] = extras_require\nsetup_kwargs[\"cmdclass\"] = {\"test\": PyTest}\nsetup(**setup_kwargs)\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/asyncmock.py",
    "content": "\"\"\"\nFor 3.7 compat\n\n\"\"\"\n\nfrom unittest.mock import Mock\n\n\nclass AsyncMock(Mock):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.await_count = 0\n\n    def __call__(self, *args, **kwargs):\n        self.call_count += 1\n        parent = super()\n\n        async def dummy():\n            self.await_count += 1\n            return parent.__call__(*args, **kwargs)\n\n        return dummy()\n\n    def __await__(self):\n        return self().__await__()\n\n    def reset_mock(self, *args, **kwargs):\n        super().reset_mock(*args, **kwargs)\n        self.await_count = 0\n\n    def assert_awaited_once(self):\n        if not self.await_count == 1:\n            msg = (\n                f\"Expected to have been awaited once.\"\n                f\" Awaited {self.await_count} times.\"\n            )\n            raise AssertionError(msg)\n\n    def assert_awaited_once_with(self, *args, **kwargs):\n        if not self.await_count == 1:\n            msg = (\n                f\"Expected to have been awaited once.\"\n                f\" Awaited {self.await_count} times.\"\n            )\n            raise AssertionError(msg)\n        self.assert_awaited_once()\n        return self.assert_called_with(*args, **kwargs)\n"
  },
  {
    "path": "tests/benchmark/test_route_resolution_benchmark.py",
    "content": "from random import choice, seed\n\nfrom pytest import mark\n\nimport sanic.router\n\nfrom sanic.request import Request\n\n\nseed(\"Pack my box with five dozen liquor jugs.\")\n\n# Disable Caching for testing purpose\nsanic.router.ROUTER_CACHE_SIZE = 0\n\n\nclass TestSanicRouteResolution:\n    @mark.asyncio\n    async def test_resolve_route_no_arg_string_path(\n        self, sanic_router, route_generator, benchmark\n    ):\n        simple_routes = route_generator.generate_random_direct_route(\n            max_route_depth=4\n        )\n        router, simple_routes = sanic_router(route_details=simple_routes)\n        route_to_call = choice(simple_routes)\n        request = Request(\n            f\"/{route_to_call[-1]}\".encode(),\n            {\"host\": \"localhost\"},\n            \"v1\",\n            route_to_call[0],\n            None,\n            None,\n        )\n\n        result = benchmark.pedantic(\n            router.get,\n            (\n                request.path,\n                request.method,\n                request.headers.get(\"host\"),\n            ),\n            iterations=1000,\n            rounds=1000,\n        )\n        assert await result[1](None) == 1\n\n    @mark.asyncio\n    async def test_resolve_route_with_typed_args(\n        self, sanic_router, route_generator, benchmark\n    ):\n        typed_routes = route_generator.add_typed_parameters(\n            route_generator.generate_random_direct_route(max_route_depth=4),\n            max_route_depth=8,\n        )\n        router, typed_routes = sanic_router(route_details=typed_routes)\n        route_to_call = choice(typed_routes)\n        url = route_generator.generate_url_for_template(\n            template=route_to_call[-1]\n        )\n\n        print(f\"{route_to_call[-1]} -> {url}\")\n        request = Request(\n            f\"/{url}\".encode(),\n            {\"host\": \"localhost\"},\n            \"v1\",\n            route_to_call[0],\n            None,\n            None,\n        )\n\n        result = benchmark.pedantic(\n            router.get,\n            (\n                request.path,\n                request.method,\n                request.headers.get(\"host\"),\n            ),\n            iterations=1000,\n            rounds=1000,\n        )\n        assert await result[1](None) == 1\n"
  },
  {
    "path": "tests/certs/createcerts.py",
    "content": "from datetime import datetime, timedelta\nfrom ipaddress import ip_address\nfrom os import path\n\nfrom cryptography.hazmat.primitives import hashes, serialization\nfrom cryptography.hazmat.primitives.asymmetric import ec, rsa\nfrom cryptography.x509 import (\n    BasicConstraints,\n    CertificateBuilder,\n    DNSName,\n    ExtendedKeyUsage,\n    IPAddress,\n    KeyUsage,\n    Name,\n    NameAttribute,\n    SubjectAlternativeName,\n    random_serial_number,\n)\nfrom cryptography.x509.oid import ExtendedKeyUsageOID, NameOID\n\n\ndef writefiles(key, cert):\n    cn = cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value\n    folder = path.join(path.dirname(__file__), cn)\n    with open(path.join(folder, \"fullchain.pem\"), \"wb\") as f:\n        f.write(cert.public_bytes(serialization.Encoding.PEM))\n\n    with open(path.join(folder, \"privkey.pem\"), \"wb\") as f:\n        f.write(\n            key.private_bytes(\n                serialization.Encoding.PEM,\n                serialization.PrivateFormat.TraditionalOpenSSL,\n                serialization.NoEncryption(),\n            )\n        )\n\n\ndef selfsigned(key, common_name, san):\n    subject = issuer = Name(\n        [\n            NameAttribute(NameOID.COMMON_NAME, common_name),\n            NameAttribute(NameOID.ORGANIZATION_NAME, \"Sanic Org\"),\n        ]\n    )\n    cert = (\n        CertificateBuilder()\n        .subject_name(subject)\n        .issuer_name(issuer)\n        .public_key(key.public_key())\n        .serial_number(random_serial_number())\n        .not_valid_before(datetime.utcnow())\n        .not_valid_after(datetime.utcnow() + timedelta(days=365.25 * 8))\n        .add_extension(\n            KeyUsage(\n                True, False, False, False, False, False, False, False, False\n            ),\n            critical=True,\n        )\n        .add_extension(\n            ExtendedKeyUsage(\n                [\n                    ExtendedKeyUsageOID.SERVER_AUTH,\n                    ExtendedKeyUsageOID.CLIENT_AUTH,\n                ]\n            ),\n            critical=False,\n        )\n        .add_extension(\n            BasicConstraints(ca=True, path_length=None),\n            critical=True,\n        )\n        .add_extension(\n            SubjectAlternativeName(\n                [\n                    IPAddress(ip_address(n))\n                    if n[0].isdigit() or \":\" in n\n                    else DNSName(n)\n                    for n in san\n                ]\n            ),\n            critical=False,\n        )\n        .sign(key, hashes.SHA256())\n    )\n    return cert\n\n\n# Sanic example/test self-signed cert RSA\nkey = rsa.generate_private_key(public_exponent=65537, key_size=2048)\ncert = selfsigned(\n    key,\n    \"sanic.example\",\n    [\n        \"sanic.example\",\n        \"www.sanic.example\",\n        \"*.sanic.test\",\n        \"2001:db8::541c\",\n    ],\n)\nwritefiles(key, cert)\n\n# Sanic localhost self-signed cert ECDSA\nkey = ec.generate_private_key(ec.SECP256R1)\ncert = selfsigned(\n    key,\n    \"localhost\",\n    [\n        \"localhost\",\n        \"127.0.0.1\",\n        \"::1\",\n    ],\n)\nwritefiles(key, cert)\n"
  },
  {
    "path": "tests/certs/invalid.certmissing/privkey.pem",
    "content": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIFP3fCUob41U1wvVOvei4dGsXrZeSiBUCX/xVu9215bvoAoGCCqGSM49\nAwEHoUQDQgAEvBHo/RatEnPRBeiLURXX2sQDBbr9XRb73Fvm8jIOrPyJg8PcvNXH\nD1jQah5K60THdjmdkLsY/hamZfqLb24EFQ==\n-----END EC PRIVATE KEY-----\n"
  },
  {
    "path": "tests/certs/localhost/fullchain.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIBwjCCAWigAwIBAgIUQOCJIPRMiZsOMmvH0uiofxEDFn8wCgYIKoZIzj0EAwIw\nKDESMBAGA1UEAwwJbG9jYWxob3N0MRIwEAYDVQQKDAlTYW5pYyBPcmcwHhcNMjEx\nMDE5MTcwMTE3WhcNMjkxMDE5MTcwMTE3WjAoMRIwEAYDVQQDDAlsb2NhbGhvc3Qx\nEjAQBgNVBAoMCVNhbmljIE9yZzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHf0\nSrvRtGF9KIXEtk4+6vsqleNaleuYVvf4d6TD3pX1CbOV/NsZdW6+EhkA1U2pEBnJ\ntxXqAGVJT4ans8ud3K6jcDBuMA4GA1UdDwEB/wQEAwIHgDAdBgNVHSUEFjAUBggr\nBgEFBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAsBgNVHREEJTAjggls\nb2NhbGhvc3SHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwCgYIKoZIzj0EAwIDSAAw\nRQIhAJhwopVuiW0S4MKEDCl+Vxwyei5AYobrALcP0pwGpFzIAiAWkxMPeAOMWIjq\nLD4t2UZ9h6ma2fS2Jf9pzTon6438Ng==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "tests/certs/localhost/privkey.pem",
    "content": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIDKTs1c2Qo7KMQ8DJrmIuNb29z2fNi4O+TNkJWjvclvsoAoGCCqGSM49\nAwEHoUQDQgAEd/RKu9G0YX0ohcS2Tj7q+yqV41qV65hW9/h3pMPelfUJs5X82xl1\nbr4SGQDVTakQGcm3FeoAZUlPhqezy53crg==\n-----END EC PRIVATE KEY-----\n"
  },
  {
    "path": "tests/certs/password/fullchain.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDCTCCAfGgAwIBAgIUa7OOlAGQfXOgUgRENJ9GbUgO7kwwDQYJKoZIhvcNAQEL\nBQAwFDESMBAGA1UEAwwJMTI3LjAuMC4xMB4XDTIzMDMyMDA3MzE1M1oXDTIzMDQx\nOTA3MzE1M1owFDESMBAGA1UEAwwJMTI3LjAuMC4xMIIBIjANBgkqhkiG9w0BAQEF\nAAOCAQ8AMIIBCgKCAQEAn2/RqVpzO7GFrgVGiowR5CzcFzf1tSFti1K/WIGr/jsu\nNP+1R3sim17pgg6SCOFnUMRS0KnDihkzoeP6z+0tFsrbCH4V1+fq0iud8WgYQrgD\n3ttUcHrz04p7wsMoeqndUQoLbyJzP8MpA2XJsoacdIVkuLv2AESGXLhJym/e9HGN\ng8bqdz25X0hVTczZW1FN9AZyWWVf9Go6jqC7LCaOnYXAnOkEy2/JHdkeNXYFZHB3\n71UemfkCjfp0vlRV8pVpkBGMhRNFphBTfxdqeWiGQwVqrhaJO4M7DJlQHCAPY16P\no9ywnhLDhFHD7KIfTih9XxrdgTowqcwyGX3e3aJpTwIDAQABo1MwUTAdBgNVHQ4E\nFgQU5NogMq6mRBeGl4i6hIuUlcR2bVEwHwYDVR0jBBgwFoAU5NogMq6mRBeGl4i6\nhIuUlcR2bVEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAYW34\nJY1kd0UO5HE41oxJD4PioQboXXX0al4RgKaUUsPykeHQbK0q0TSYAZLwRjooTVUO\nWvna5bU2mzyULqA2r/Cr/w4zb9xybO3SiHFHcU1RacouauHXROHwRm98i8A73xnH\nvHws5BADr2ggnVcPNh4VOQ9ZvBlC7jhgpvMjqOEu5ZPCovhfZYfSsvBDHcD74ZYm\nDi9DvqsJmrb23Dv3SUykm3W+Ql2q+JyjFj30rhD89CFwJ9iSlFwTYEwZLHA+mV6p\nUKy3I3Fiht1Oc+nIivX5uhRSMbDVvDTVHbjjPujxxFjkiHXMjtwvwfg4Sb6du61q\nAjBRFyXbNu4hZkkHOA==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "tests/certs/password/privkey.pem",
    "content": "-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQI94UBqjaZlG4CAggA\nMAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBCvJhEy+3/+0Ec0gpd5dkP6BIIE\n0E7rLplTe9rxK3sR9V0cx8Xn6V+uFhG3p7dzeMDCCKpGo9MEaacF5m+paGnBkMlH\nPz3rRoLA5jqzwXl4US/C5E1Or//2YBgF1XXKi3BPF/bVx/g6vR+xeobf9kQGbqQk\nFNPYtP7mpg2dekp5BUsKSosIt8BkknWFvhBeNuGZT/zlMUuq1WpMe4KIh/W9IdNr\nHolcuZJWBhQAwGPciWIZRyq48wKa++W7Jdg/aG8FviJQnjaAUv4CyZJHUJnaNwUx\niHOETpzIC+bhF2K+s4g5w68VCj6Jtz78sIBEZKzo7LI5QHdRHqYB5SJ/dGiV+h09\nR/rQ/M+24mwHDlRSCxxq0yuDwUuGBlHyATeDCFeE3L5OX8yTLuqYJ6vUa6UbzMYA\n8H4l5zfu9RrAhKYa9tD+4ONxMmHziIgmn5zvSXeBwJKfeUbnN4IKWLsSoSVspBRh\nzLl51DMAnem4NEjLfIW8WYjhsvSYwd9BYqxXaAiv4Wjx9ZV1yLqFICC7tejpVdRT\nafI0qMOfWu4ma6xVBg1ezLgF1wHIPrq6euTvWdnifYQopVICALlltEo5oxQ2i/OM\nNY8RyovWujiGNsa3pId9HmZXiLyLXjKPstGWRK4liMyc2EiP099gTdBvrb+VQp+I\nEyPavmh3WNhgZGOh3qah39X8HrBprc0PPfSPlxpaWdNMIIMSbcIWWdJEA/e4tcy/\nuBaV4H3sNCtBApgrb6B9YUbS9CXNUburJo19T1sk2uCaO12qYfdu2IDEnFf8JiF3\ni7nyftotRuoKq2D+V8d0PeMi/vJSo6+eZIn7VNe6ejYf+w0s7sxlpiKVzkslyOhq\nn0T4M3ZkSwGIETzgkRRuTY1OK7slhglMgXlQ2FuIUUo6CRg9WjRJvI5rujLzLWfB\nhkgP8STirjTV0DUWPFGtUcenvEcZPkYIQcoPHxOJGNW3ZPXNpt4RjbvPLeVzDm0O\nWJiay/qhag/bXGqKraO3b6Y7FOzJa8kG4G0XrcFY1s2oCXRqRqYJAtwaEeVCjCSJ\nQy0OZkqcJEU7pv98pLMpG9OWz4Gle77g4KoQUJjQGtmg0MUMoPd0iPRmvkxsYg8E\nQ9uZS3m6PpWmmYDY0Ik1w/4avs3skl2mW3dqcZGLEepkjiQSnFABsuvxKd+uIEQy\nlyf9FrynXVcUI87LUkuniLRKwZZzFALVuc+BwtO3SA5mvEK22ZEq9QOysbwlpN54\nG5xXJKJEeexUSjEUIij4J89RLsXldibhp7YYZ7rFviR6chIqC0V7G6VqAM9TOCrV\nPWZXr3ZY5/pCZYs5DYKFJBFMSQ2UT/++VYxdZCeBH75vaxugbS8RdUM+iVDevWpQ\n/AnP1FolNAgkVhi3Rw4L16SibkqpEzIi1svPWKMwXdvewA32UidLElhuTWWjI2Wm\nveXhmEqwk/7ML4JMI7wHcDQdvSKen0mCL2J9tB7A/pewYyDE0ffIUmjxglOtw30f\nZOlQKhMaKJGXp00U2zsHA2NJRI/hThbJncsnZyvuLei0P42RrF+r64b/0gUH6IZ5\nwPUttT815KSNoy+XXXum9YGDYYFoAL+6WVEkl6dgo+X0hcH7DDf5Nkewiq8UcJGh\n/69vFIfp+JlpicXzZ+R42LO3T3luC907aFBywF3pmi//\n-----END ENCRYPTED PRIVATE KEY-----\n"
  },
  {
    "path": "tests/certs/sanic.example/fullchain.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDdzCCAl+gAwIBAgIUF1H0To9k3mUiMT8mjF6g45A9KgcwDQYJKoZIhvcNAQEL\nBQAwLDEWMBQGA1UEAwwNc2FuaWMuZXhhbXBsZTESMBAGA1UECgwJU2FuaWMgT3Jn\nMB4XDTIxMTAxOTE3MDExN1oXDTI5MTAxOTE3MDExN1owLDEWMBQGA1UEAwwNc2Fu\naWMuZXhhbXBsZTESMBAGA1UECgwJU2FuaWMgT3JnMIIBIjANBgkqhkiG9w0BAQEF\nAAOCAQ8AMIIBCgKCAQEAzNeC95zB5LRybz9Wl16+Q4kbOLgXlyUQVKhg9OZD1ChN\n3T4Ya/KvChQmPWOWdF814NgkkNS1yHKXlORU2Ljbpqzr+WoOAwGVixbRTknjmI46\nglUhCOJlGqxl16RfuYA2BWv0+At9jKBhT1tnrGVhfqldnxsb4FDh0JsFnrZN4/DB\nz6x8PY1z0eQMgsyeKAfSTTnGXhkZzAQz6afuQbGZhe8vQIUTvwmnZiU9OdUZ6nLc\nb7lSbIQ1edT6/xXUkbn5ixGsEQTf6JWLqEDLkqpo9sbkYmvMMQpj2pCgtaEjx7An\n+hQe8Itv+i6h0KD3ARVeCBgdWEgXZTs7zKrmU77xfwIDAQABo4GQMIGNMA4GA1Ud\nDwEB/wQEAwIHgDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDwYDVR0T\nAQH/BAUwAwEB/zBLBgNVHREERDBCgg1zYW5pYy5leGFtcGxlghF3d3cuc2FuaWMu\nZXhhbXBsZYIMKi5zYW5pYy50ZXN0hxAgAQ24AAAAAAAAAAAAAFQcMA0GCSqGSIb3\nDQEBCwUAA4IBAQBLV7xSEI7308Qmm3SyV+ro9jQ/i2ydwUIUyRMtf04EFRS8fHK/\nLln5Yweaba9XP5k3DLSC63Qg1tE50fVqQypbWVA4SMkMW21cK8vEhHEYeGYkHsuC\nxCFdwJYhmofqWaQ/j/ErLBrQbaHBdSJ/Nou5RPRtM4HrSU7F2azLGmLczYk6PcZa\nwSBvoXdjiEUrRl7XB0iB2ktTga6amuYz4bSJzUvaA8SodJzC4OKhRsduUD83LdDi\n2As4KiTcSO/SOCaK2KmbPNBlTKMF4cpqysGMvmnGVWhECOG1PZItJkWNbbBV4XRR\nqGmrey2JwDDeTYHFDHaND385/PSJKfSSGLNk\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "tests/certs/sanic.example/privkey.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAzNeC95zB5LRybz9Wl16+Q4kbOLgXlyUQVKhg9OZD1ChN3T4Y\na/KvChQmPWOWdF814NgkkNS1yHKXlORU2Ljbpqzr+WoOAwGVixbRTknjmI46glUh\nCOJlGqxl16RfuYA2BWv0+At9jKBhT1tnrGVhfqldnxsb4FDh0JsFnrZN4/DBz6x8\nPY1z0eQMgsyeKAfSTTnGXhkZzAQz6afuQbGZhe8vQIUTvwmnZiU9OdUZ6nLcb7lS\nbIQ1edT6/xXUkbn5ixGsEQTf6JWLqEDLkqpo9sbkYmvMMQpj2pCgtaEjx7An+hQe\n8Itv+i6h0KD3ARVeCBgdWEgXZTs7zKrmU77xfwIDAQABAoIBABWKpG89wPY4M8CX\nPJf2krOve3lfgruWXj1I58lZXdC13Fpj6VWQ0++PZuYVzwC18oiOsmm4tNU7l81E\npdeUuSSyEq7MBGU0iXFzGNfO1Wx5qJWENlEk3dUMRDmFQ7vSS9wOGljrfGyJgTJD\nPofWsYYMcZgF1cylNNonM1QZf990hfd0JDfO6CHCloRe/pKIdVzIxQp+3Ju/3OPk\nGw5V+YnVrG4wdZbhOCW2hPp/TLdgFy/xHvrxkEkGx+2ZHGCw9uFj2LRZJwwuaO9p\nLDzbyfbFlPWIHdPamdBvenZ6RNTf28+YsbiqwoOk5C286QYb/VDnT8UnG42hXS1I\np3m//qECgYEA7zXmMSBy1tkMQsuaAakOFfl2HfVL2rrW6/CH6BwcCHUD6Wr8wv6a\nkPNhI6pqqnP6Xg8XqJXfyIVZOJYPQMQr69zni2y7b3jPOemVGTBSqN7UE71NZkHF\n+HZov55bPuX/KD6qc/WAXCyEcISy9TmcA7cEN7ivmyXmbuSXEoiAjlsCgYEA2zgU\nmzL6ObJ2555UOqzGCMx6o2KQqOgA1SGmYLBRX77I3fuvGj+DLo6/iuM0FcVV7alG\nU/U6qqrSymtdRgeZXHziSVhLZKY/qobgKG2iO1F3DzqyZ94EK/v0XRS4UyiJma3f\nlwVG/BcVnv+FKCYUo2JKGln0R8Wcm6D9Nxp0mq0CgYEAn0Dj+oreyZiAqCuCYV6a\nSRjmgTVghcNj+HoPEQE9zIeSziBzHKKCZsQRRLxc/RPveBVWK99zt7zHVHvatcSk\ndQeBg3olIyZr1+NhZv6b2V9YE7gwwkZBtZOnUwLrPmnCwJlPw5mLFlJw7bP6rHXp\nHzQF887Z4lGOIv++cBE+fQcCgYEArF26BhXdHcSvLYsWW1RCGeT9gL4dVFGnZe2h\nbmD0er3+Hlyo35CUyuS+wqvG5l9VIxt4CsfFKzBJsZMdsdSDx28CVf0wuqDlamXG\nlsMtTkrNvJHAeV7eFN900kNaczhqiQVnys0BdXGJNI1g26Klk5nS/klAg7ZjXxME\nRnFswbkCgYBG5OToLXM8pg3yTM9MHMSXFhnnd2MbBK2AySFah2P1V4xv1rJdklU0\n9QRTd/hQmYGHioPIF9deU8YSWlj+FBimyoNfJ51YzFyp2maOSJq4Wxe1nv2DflRK\ngh5pkl8FizoDnu8BHu1AjOfRQJ3/tCIi2XZJgBuCxyTjd1b6hVUhyg==\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "tests/client.py",
    "content": "import asyncio\n\nfrom textwrap import dedent\nfrom typing import AnyStr\n\n\nclass RawClient:\n    CRLF = b\"\\r\\n\"\n\n    def __init__(self, host: str, port: int):\n        self.reader = None\n        self.writer = None\n        self.host = host\n        self.port = port\n\n    async def connect(self):\n        self.reader, self.writer = await asyncio.open_connection(\n            self.host, self.port\n        )\n\n    async def close(self):\n        self.writer.close()\n        await self.writer.wait_closed()\n\n    async def send(self, message: AnyStr):\n        if isinstance(message, str):\n            msg = self._clean(message).encode(\"utf-8\")\n        else:\n            msg = message\n        await self._send(msg)\n\n    async def _send(self, message: bytes):\n        if not self.writer:\n            raise Exception(\"No open write stream\")\n        self.writer.write(message)\n\n    async def recv(self, nbytes: int = -1) -> bytes:\n        if not self.reader:\n            raise Exception(\"No open read stream\")\n        return await self.reader.read(nbytes)\n\n    def _clean(self, message: str) -> str:\n        return (\n            dedent(message)\n            .lstrip(\"\\n\")\n            .replace(\"\\n\", self.CRLF.decode(\"utf-8\"))\n        )\n"
  },
  {
    "path": "tests/conftest.py",
    "content": "import asyncio\nimport inspect\nimport logging\nimport os\nimport random\nimport re\nimport socket\nimport string\nimport sys\nimport uuid\n\nfrom contextlib import suppress\nfrom logging import LogRecord\nfrom typing import Any\nfrom unittest.mock import MagicMock, Mock, patch\n\nimport pytest\n\nfrom sanic_routing.exceptions import RouteExists\nfrom sanic_testing.testing import PORT\n\nfrom sanic import Sanic\nfrom sanic.constants import HTTP_METHODS\nfrom sanic.logging.formatter import AutoFormatter\nfrom sanic.router import Router\nfrom sanic.touchup.service import TouchUp\n\n\nslugify = re.compile(r\"[^a-zA-Z0-9_\\-]\")\nrandom.seed(\"Pack my box with five dozen liquor jugs.\")\nSanic.test_mode = True\n\nif sys.platform in [\"win32\", \"cygwin\"]:\n    collect_ignore = [\"test_worker.py\"]\n\n\ndef get_port():\n    sock = socket.socket()\n    sock.bind(\n        (\"\", 0)\n    )  # Bind to 0 port, so os will pick available port for us.\n    return sock.getsockname()[1]\n\n\n@pytest.fixture(scope=\"function\")\ndef port():\n    yield get_port()\n\n\nasync def _handler(request):\n    \"\"\"\n    Dummy placeholder method used for route resolver when creating a new\n    route into the sanic router. This router is not actually called by the\n    sanic app. So do not worry about the arguments to this method.\n\n    If you change the return value of this method, make sure to propagate the\n    change to any test case that leverages RouteStringGenerator.\n    \"\"\"\n    return 1\n\n\nTYPE_TO_GENERATOR_MAP = {\n    \"str\": lambda: \"\".join(\n        [random.choice(string.ascii_lowercase) for _ in range(4)]\n    ),\n    \"int\": lambda: random.choice(range(1000000)),\n    \"float\": lambda: random.random(),\n    \"alpha\": lambda: \"\".join(\n        [random.choice(string.ascii_lowercase) for _ in range(4)]\n    ),\n    \"uuid\": lambda: str(uuid.uuid1()),\n}\n\nCACHE: dict[str, Any] = {}\n\n\nclass RouteStringGenerator:\n    ROUTE_COUNT_PER_DEPTH = 100\n    HTTP_METHODS = HTTP_METHODS\n    ROUTE_PARAM_TYPES = [\"str\", \"int\", \"float\", \"alpha\", \"uuid\"]\n\n    def generate_random_direct_route(self, max_route_depth=4):\n        routes = []\n        for depth in range(1, max_route_depth + 1):\n            for _ in range(self.ROUTE_COUNT_PER_DEPTH):\n                route = \"/\".join(\n                    [TYPE_TO_GENERATOR_MAP.get(\"str\")() for _ in range(depth)]\n                )\n                route = route.replace(\".\", \"\", -1)\n                route_detail = (random.choice(self.HTTP_METHODS), route)\n\n                if route_detail not in routes:\n                    routes.append(route_detail)\n        return routes\n\n    def add_typed_parameters(self, current_routes, max_route_depth=8):\n        routes = []\n        for method, route in current_routes:\n            current_length = len(route.split(\"/\"))\n            new_route_part = \"/\".join(\n                [\n                    \"<{}:{}>\".format(\n                        TYPE_TO_GENERATOR_MAP.get(\"str\")(),\n                        random.choice(self.ROUTE_PARAM_TYPES),\n                    )\n                    for _ in range(max_route_depth - current_length)\n                ]\n            )\n            route = \"/\".join([route, new_route_part])\n            route = route.replace(\".\", \"\", -1)\n            routes.append((method, route))\n        return routes\n\n    @staticmethod\n    def generate_url_for_template(template):\n        url = template\n        for pattern, param_type in re.findall(\n            re.compile(r\"((?:<\\w+:(str|int|float|alpha|uuid)>)+)\"),\n            template,\n        ):\n            value = TYPE_TO_GENERATOR_MAP.get(param_type)()\n            url = url.replace(pattern, str(value), -1)\n        return url\n\n\n@pytest.fixture(scope=\"function\")\ndef sanic_router(app):\n    # noinspection PyProtectedMember\n    def _setup(route_details: tuple) -> tuple[Router, tuple]:\n        router = Router()\n        router.ctx.app = app\n        added_router = []\n        for method, route in route_details:\n            try:\n                router.add(\n                    uri=f\"/{route}\",\n                    methods=frozenset({method}),\n                    host=\"localhost\",\n                    handler=_handler,\n                )\n                added_router.append((method, route))\n            except RouteExists:\n                pass\n        router.finalize()\n        return router, tuple(added_router)\n\n    return _setup\n\n\n@pytest.fixture(scope=\"function\")\ndef route_generator() -> RouteStringGenerator:\n    return RouteStringGenerator()\n\n\n@pytest.fixture(scope=\"function\")\ndef url_param_generator():\n    return TYPE_TO_GENERATOR_MAP\n\n\n@pytest.fixture(scope=\"function\")\ndef app(request):\n    if not CACHE:\n        for target, method_name in TouchUp._registry:\n            CACHE[method_name] = getattr(target, method_name)\n    app = Sanic(slugify.sub(\"-\", request.node.name))\n\n    yield app\n    for target, method_name in TouchUp._registry:\n        setattr(target, method_name, CACHE[method_name])\n    Sanic._app_registry.clear()\n    AutoFormatter.SETUP = False\n    AutoFormatter.LOG_EXTRA = False\n    os.environ.pop(\"SANIC_LOG_EXTRA\", None)\n    os.environ.pop(\"SANIC_NO_COLOR\", None)\n\n\n@pytest.fixture(scope=\"function\")\ndef run_startup(caplog):\n    def run(app):\n        nonlocal caplog\n        loop = asyncio.new_event_loop()\n        asyncio.set_event_loop(loop)\n        with caplog.at_level(logging.DEBUG):\n            server = app.create_server(\n                debug=True, return_asyncio_server=True, port=PORT\n            )\n            loop._stopping = False\n\n            _server = loop.run_until_complete(server)\n\n            _server.close()\n            loop.run_until_complete(_server.wait_closed())\n            app.stop()\n\n        return caplog.record_tuples\n\n    return run\n\n\n@pytest.fixture\ndef run_multi(caplog):\n    def run(app, level=logging.DEBUG):\n        @app.after_server_start\n        async def stop(app, _):\n            app.stop()\n\n        with caplog.at_level(level):\n            Sanic.serve()\n\n        return caplog.record_tuples\n\n    return run\n\n\n@pytest.fixture(scope=\"function\")\ndef message_in_records():\n    def msg_in_log(records: list[LogRecord], msg: str):\n        error_captured = False\n        for record in records:\n            if msg in record.message:\n                error_captured = True\n                break\n        return error_captured\n\n    return msg_in_log\n\n\n@pytest.fixture\ndef ext_instance():\n    ext_instance = MagicMock()\n    ext_instance.injection = MagicMock()\n    return ext_instance\n\n\n@pytest.fixture(autouse=True)  # type: ignore\ndef mock_sanic_ext(ext_instance):  # noqa\n    mock_sanic_ext = MagicMock(__version__=\"1.2.3\")\n    mock_sanic_ext.Extend = MagicMock()\n    mock_sanic_ext.Extend.return_value = ext_instance\n    sys.modules[\"sanic_ext\"] = mock_sanic_ext\n    yield mock_sanic_ext\n    with suppress(KeyError):\n        del sys.modules[\"sanic_ext\"]\n\n\n@pytest.fixture\ndef urlopen():\n    urlopen = Mock()\n    urlopen.return_value = urlopen\n    urlopen.__enter__ = Mock(return_value=urlopen)\n    urlopen.__exit__ = Mock()\n    urlopen.read = Mock()\n    with patch(\"sanic.cli.inspector_client.urlopen\", urlopen):\n        yield urlopen\n\n\n@pytest.fixture(scope=\"module\")\ndef static_file_directory():\n    \"\"\"The static directory to serve\"\"\"\n    current_file = inspect.getfile(inspect.currentframe())\n    current_directory = os.path.dirname(os.path.abspath(current_file))\n    static_directory = os.path.join(current_directory, \"static\")\n    return static_directory\n"
  },
  {
    "path": "tests/fake/server.py",
    "content": "import json\n\nfrom sanic import Sanic, text\nfrom sanic.application.constants import Mode\nfrom sanic.config import Config\nfrom sanic.log import LOGGING_CONFIG_DEFAULTS, logger\n\n\nLOGGING_CONFIG = {**LOGGING_CONFIG_DEFAULTS}\nLOGGING_CONFIG[\"formatters\"][\"generic\"][\"format\"] = \"%(message)s\"\nLOGGING_CONFIG[\"loggers\"][\"sanic.root\"][\"level\"] = \"DEBUG\"\n\napp = Sanic(\"FakeServer\", log_config=LOGGING_CONFIG)\n\n\n@app.get(\"/\")\nasync def handler(request):\n    return text(request.ip)\n\n\n@app.main_process_start\nasync def app_info_dump(app: Sanic, _):\n    app_data = {\n        \"access_log\": app.config.ACCESS_LOG,\n        \"auto_reload\": app.auto_reload,\n        \"debug\": app.debug,\n        \"noisy_exceptions\": app.config.NOISY_EXCEPTIONS,\n    }\n    logger.info(json.dumps(app_data))\n\n\n@app.main_process_stop\nasync def app_cleanup(app: Sanic, _):\n    app.state.auto_reload = False\n    app.state.mode = Mode.PRODUCTION\n    app.config = Config()\n\n\n@app.after_server_start\nasync def shutdown(app: Sanic, _):\n    app.stop()\n\n\ndef create_app():\n    return app\n\n\ndef create_app_with_args(args):\n    try:\n        logger.info(f\"foo={args.foo}\")\n    except AttributeError:\n        logger.info(f\"target={args.target}\")\n\n    return app\n\n\n@app.command\nasync def foo(one, two: str, three: str = \"...\"):\n    logger.info(f\"FOO {one=} {two=} {three=}\")\n\n\n@app.command\ndef bar():\n    logger.info(\"BAR\")\n\n\n@app.command(name=\"qqq\")\nasync def baz():\n    logger.info(\"BAZ\")\n"
  },
  {
    "path": "tests/http3/__init__.py",
    "content": ""
  },
  {
    "path": "tests/http3/test_http_receiver.py",
    "content": "from unittest.mock import Mock\n\nimport pytest\n\nfrom aioquic.h3.connection import H3Connection\nfrom aioquic.h3.events import DataReceived, HeadersReceived\nfrom aioquic.quic.configuration import QuicConfiguration\nfrom aioquic.quic.connection import QuicConnection\nfrom aioquic.quic.events import ProtocolNegotiated\n\nfrom sanic import Request, Sanic\nfrom sanic.compat import Header\nfrom sanic.config import DEFAULT_CONFIG\nfrom sanic.exceptions import BadRequest, PayloadTooLarge\nfrom sanic.http.constants import Stage\nfrom sanic.http.http3 import Http3, HTTPReceiver\nfrom sanic.models.server_types import ConnInfo\nfrom sanic.response import empty, json\nfrom sanic.server.protocols.http_protocol import Http3Protocol\n\n\ntry:\n    from unittest.mock import AsyncMock\nexcept ImportError:\n    from tests.asyncmock import AsyncMock  # type: ignore\n\npytestmark = pytest.mark.asyncio\n\n\n@pytest.fixture(autouse=True)\nasync def setup(app: Sanic):\n    @app.get(\"/\")\n    async def handler(*_):\n        return empty()\n\n    app.router.finalize()\n    app.signal_router.finalize()\n    app.signal_router.allow_fail_builtin = False\n\n\n@pytest.fixture\ndef http_request(app):\n    return Request(b\"/\", Header({}), \"3\", \"GET\", Mock(), app)\n\n\ndef generate_protocol(app):\n    connection = QuicConnection(configuration=QuicConfiguration())\n    connection._ack_delay = 0\n    connection._loss = Mock()\n    connection._loss.spaces = []\n    connection._loss.get_loss_detection_time = lambda: None\n    connection.datagrams_to_send = Mock(return_value=[])  # type: ignore\n    return Http3Protocol(\n        connection,\n        app=app,\n        stream_handler=None,\n    )\n\n\ndef generate_http_receiver(app, http_request) -> HTTPReceiver:\n    protocol = generate_protocol(app)\n    receiver = HTTPReceiver(\n        protocol.transmit,\n        protocol,\n        http_request,\n    )\n    http_request.stream = receiver\n    return receiver\n\n\nasync def test_http_receiver_init(app: Sanic, http_request: Request):\n    receiver = generate_http_receiver(app, http_request)\n    assert receiver.request_body is None\n    assert receiver.stage is Stage.IDLE\n    assert receiver.headers_sent is False\n    assert receiver.response is None\n    assert receiver.request_max_size == DEFAULT_CONFIG[\"REQUEST_MAX_SIZE\"]\n    assert receiver.request_bytes == 0\n\n\nasync def test_http_receiver_run_request(app: Sanic, http_request: Request):\n    handler = AsyncMock()\n\n    class mock_handle(Sanic):\n        handle_request = handler\n\n    app.__class__ = mock_handle\n    receiver = generate_http_receiver(app, http_request)\n    receiver.protocol.quic_event_received(\n        ProtocolNegotiated(alpn_protocol=\"h3\")\n    )\n    await receiver.run()\n    handler.assert_awaited_once_with(receiver.request)\n\n\nasync def test_http_receiver_run_exception(app: Sanic, http_request: Request):\n    handler = AsyncMock()\n\n    class mock_handle(Sanic):\n        handle_exception = handler\n\n    app.__class__ = mock_handle\n    receiver = generate_http_receiver(app, http_request)\n    receiver.protocol.quic_event_received(\n        ProtocolNegotiated(alpn_protocol=\"h3\")\n    )\n    exception = Exception(\"Oof\")\n    await receiver.run(exception)\n    handler.assert_awaited_once_with(receiver.request, exception)\n\n    handler.reset_mock()\n    receiver.stage = Stage.REQUEST\n    await receiver.run(exception)\n    handler.assert_awaited_once_with(receiver.request, exception)\n\n\nasync def test_http_receiver_respond(app: Sanic, http_request: Request):\n    receiver = generate_http_receiver(app, http_request)\n    response = empty()\n\n    receiver.stage = Stage.RESPONSE\n    with pytest.raises(RuntimeError, match=\"Response already started\"):\n        receiver.respond(response)\n\n    receiver.stage = Stage.HANDLER\n    receiver.response = Mock()\n    resp = receiver.respond(response)\n\n    assert receiver.response is resp\n    assert resp is response\n    assert response.stream is receiver\n\n\nasync def test_http_receiver_receive_body(app: Sanic, http_request: Request):\n    receiver = generate_http_receiver(app, http_request)\n    receiver.request_max_size = 4\n\n    receiver.receive_body(b\"..\")\n    assert receiver.request.body == b\"..\"\n\n    receiver.receive_body(b\"..\")\n    assert receiver.request.body == b\"....\"\n\n    with pytest.raises(\n        PayloadTooLarge, match=\"Request body exceeds the size limit\"\n    ):\n        receiver.receive_body(b\"..\")\n\n\nasync def test_http3_events(app):\n    protocol = generate_protocol(app)\n    http3 = Http3(protocol, protocol.transmit)\n    http3.http_event_received(\n        HeadersReceived(\n            [\n                (b\":method\", b\"GET\"),\n                (b\":path\", b\"/location\"),\n                (b\":scheme\", b\"https\"),\n                (b\":authority\", b\"localhost:8443\"),\n                (b\"foo\", b\"bar\"),\n            ],\n            1,\n            False,\n        )\n    )\n    http3.http_event_received(DataReceived(b\"foobar\", 1, False))\n    receiver = http3.receivers[1]\n\n    assert len(http3.receivers) == 1\n    assert receiver.request.stream_id == 1\n    assert receiver.request.path == \"/location\"\n    assert receiver.request.method == \"GET\"\n    assert receiver.request.headers[\"foo\"] == \"bar\"\n    assert receiver.request.body == b\"foobar\"\n\n\nasync def test_send_headers(app: Sanic, http_request: Request):\n    send_headers_mock = Mock()\n    existing_send_headers = H3Connection.send_headers\n    receiver = generate_http_receiver(app, http_request)\n    receiver.protocol.quic_event_received(\n        ProtocolNegotiated(alpn_protocol=\"h3\")\n    )\n\n    http_request._protocol = receiver.protocol\n\n    def send_headers(*args, **kwargs):\n        send_headers_mock(*args, **kwargs)\n        return existing_send_headers(\n            receiver.protocol.connection, *args, **kwargs\n        )\n\n    receiver.protocol.connection.send_headers = send_headers\n    receiver.head_only = False\n    response = json({}, status=201, headers={\"foo\": \"bar\"})\n\n    with pytest.raises(RuntimeError, match=\"no response\"):\n        receiver.send_headers()\n\n    receiver.response = response\n    receiver.send_headers()\n\n    assert receiver.headers_sent\n    assert receiver.stage is Stage.RESPONSE\n    send_headers_mock.assert_called_once_with(\n        stream_id=0,\n        headers=[\n            (b\":status\", b\"201\"),\n            (b\"foo\", b\"bar\"),\n            (b\"content-length\", b\"2\"),\n            (b\"content-type\", b\"application/json\"),\n        ],\n    )\n\n\nasync def test_multiple_streams(app):\n    protocol = generate_protocol(app)\n    http3 = Http3(protocol, protocol.transmit)\n    http3.http_event_received(\n        HeadersReceived(\n            [\n                (b\":method\", b\"GET\"),\n                (b\":path\", b\"/location\"),\n                (b\":scheme\", b\"https\"),\n                (b\":authority\", b\"localhost:8443\"),\n                (b\"foo\", b\"bar\"),\n            ],\n            1,\n            False,\n        )\n    )\n    http3.http_event_received(\n        HeadersReceived(\n            [\n                (b\":method\", b\"GET\"),\n                (b\":path\", b\"/location\"),\n                (b\":scheme\", b\"https\"),\n                (b\":authority\", b\"localhost:8443\"),\n                (b\"foo\", b\"bar\"),\n            ],\n            2,\n            False,\n        )\n    )\n\n    receiver1 = http3.get_receiver_by_stream_id(1)\n    receiver2 = http3.get_receiver_by_stream_id(2)\n    assert len(http3.receivers) == 2\n    assert isinstance(receiver1, HTTPReceiver)\n    assert isinstance(receiver2, HTTPReceiver)\n    assert receiver1 is not receiver2\n\n\nasync def test_request_stream_id(app):\n    protocol = generate_protocol(app)\n    http3 = Http3(protocol, protocol.transmit)\n    http3.http_event_received(\n        HeadersReceived(\n            [\n                (b\":method\", b\"GET\"),\n                (b\":path\", b\"/location\"),\n                (b\":scheme\", b\"https\"),\n                (b\":authority\", b\"localhost:8443\"),\n                (b\"foo\", b\"bar\"),\n            ],\n            1,\n            False,\n        )\n    )\n    receiver = http3.get_receiver_by_stream_id(1)\n\n    assert isinstance(receiver.request, Request)\n    assert receiver.request.stream_id == 1\n\n\nasync def test_request_conn_info(app):\n    protocol = generate_protocol(app)\n    http3 = Http3(protocol, protocol.transmit)\n    http3.http_event_received(\n        HeadersReceived(\n            [\n                (b\":method\", b\"GET\"),\n                (b\":path\", b\"/location\"),\n                (b\":scheme\", b\"https\"),\n                (b\":authority\", b\"localhost:8443\"),\n                (b\"foo\", b\"bar\"),\n            ],\n            1,\n            False,\n        )\n    )\n    receiver = http3.get_receiver_by_stream_id(1)\n\n    assert isinstance(receiver.request.conn_info, ConnInfo)\n\n\nasync def test_request_header_encoding(app):\n    protocol = generate_protocol(app)\n    http3 = Http3(protocol, protocol.transmit)\n    with pytest.raises(BadRequest) as exc_info:\n        http3.http_event_received(\n            HeadersReceived(\n                [\n                    (b\":method\", b\"GET\"),\n                    (b\":path\", b\"/location\"),\n                    (b\":scheme\", b\"https\"),\n                    (b\":authority\", b\"localhost:8443\"),\n                    (\"foo\\u00a0\".encode(), b\"bar\"),\n                ],\n                1,\n                False,\n            )\n        )\n    assert exc_info.value.status_code == 400\n    assert (\n        str(exc_info.value)\n        == \"Header names may only contain US-ASCII characters.\"\n    )\n\n\nasync def test_request_url_encoding(app):\n    protocol = generate_protocol(app)\n    http3 = Http3(protocol, protocol.transmit)\n    with pytest.raises(BadRequest) as exc_info:\n        http3.http_event_received(\n            HeadersReceived(\n                [\n                    (b\":method\", b\"GET\"),\n                    (b\":path\", b\"/location\\xa0\"),\n                    (b\":scheme\", b\"https\"),\n                    (b\":authority\", b\"localhost:8443\"),\n                    (b\"foo\", b\"bar\"),\n                ],\n                1,\n                False,\n            )\n        )\n    assert exc_info.value.status_code == 400\n    assert str(exc_info.value) == \"URL may only contain US-ASCII characters.\"\n"
  },
  {
    "path": "tests/http3/test_server.py",
    "content": "import logging\n\nfrom asyncio import Event\nfrom pathlib import Path\n\nimport pytest\n\nfrom sanic import Sanic\nfrom sanic.http.constants import HTTP\n\n\nparent_dir = Path(__file__).parent.parent\nlocalhost_dir = parent_dir / \"certs/localhost\"\n\n\n@pytest.mark.parametrize(\"version\", (3, HTTP.VERSION_3))\ndef test_server_starts_http3(app: Sanic, version, caplog):\n    ev = Event()\n\n    @app.after_server_start\n    def shutdown(*_):\n        ev.set()\n        app.stop()\n\n    with caplog.at_level(logging.INFO):\n        app.run(\n            version=version,\n            ssl={\n                \"cert\": localhost_dir / \"fullchain.pem\",\n                \"key\": localhost_dir / \"privkey.pem\",\n            },\n            single_process=True,\n        )\n\n    assert ev.is_set()\n    assert (\n        \"sanic.root\",\n        logging.INFO,\n        \"server: sanic, HTTP/3\",\n    ) in caplog.record_tuples\n\n\ndef test_server_starts_http1_and_http3(app: Sanic, caplog):\n    @app.after_server_start\n    def shutdown(*_):\n        app.stop()\n\n    app.prepare(\n        version=3,\n        ssl={\n            \"cert\": localhost_dir / \"fullchain.pem\",\n            \"key\": localhost_dir / \"privkey.pem\",\n        },\n    )\n    app.prepare(\n        version=1,\n        ssl={\n            \"cert\": localhost_dir / \"fullchain.pem\",\n            \"key\": localhost_dir / \"privkey.pem\",\n        },\n    )\n    with caplog.at_level(logging.INFO):\n        Sanic.serve_single()\n\n    assert (\n        \"sanic.root\",\n        logging.INFO,\n        \"server: sanic, HTTP/1.1\",\n    ) in caplog.record_tuples\n    assert (\n        \"sanic.root\",\n        logging.INFO,\n        \"server: sanic, HTTP/3\",\n    ) in caplog.record_tuples\n\n\ndef test_server_starts_http1_and_http3_bad_order(app: Sanic, caplog):\n    @app.after_server_start\n    def shutdown(*_):\n        app.stop()\n\n    app.prepare(\n        version=1,\n        ssl={\n            \"cert\": localhost_dir / \"fullchain.pem\",\n            \"key\": localhost_dir / \"privkey.pem\",\n        },\n    )\n    message = (\n        \"Serving HTTP/3 instances as a secondary server is not supported. \"\n        \"There can only be a single HTTP/3 worker and it must be the first \"\n        \"instance prepared.\"\n    )\n    with pytest.raises(RuntimeError, match=message):\n        app.prepare(\n            version=3,\n            ssl={\n                \"cert\": localhost_dir / \"fullchain.pem\",\n                \"key\": localhost_dir / \"privkey.pem\",\n            },\n        )\n"
  },
  {
    "path": "tests/http3/test_session_ticket_store.py",
    "content": "from datetime import datetime\n\nfrom aioquic.tls import CipherSuite, SessionTicket\n\nfrom sanic.http.http3 import SessionTicketStore\n\n\ndef _generate_ticket(label):\n    return SessionTicket(\n        1,\n        CipherSuite.AES_128_GCM_SHA256,\n        datetime.now(),\n        datetime.now(),\n        label,\n        label.decode(),\n        label,\n        None,\n        [],\n    )\n\n\ndef test_session_ticket_store():\n    store = SessionTicketStore()\n\n    assert len(store.tickets) == 0\n\n    ticket1 = _generate_ticket(b\"foo\")\n    store.add(ticket1)\n\n    assert len(store.tickets) == 1\n\n    ticket2 = _generate_ticket(b\"bar\")\n    store.add(ticket2)\n\n    assert len(store.tickets) == 2\n    assert len(store.tickets) == 2\n\n    popped2 = store.pop(ticket2.ticket)\n\n    assert len(store.tickets) == 1\n    assert popped2 is ticket2\n\n    popped1 = store.pop(ticket1.ticket)\n\n    assert len(store.tickets) == 0\n    assert popped1 is ticket1\n"
  },
  {
    "path": "tests/performance/sanic/http_response.py",
    "content": "import inspect\nimport os\nimport sys\nimport timeit\n\nfrom sanic.response import json\n\n\ncurrentdir = os.path.dirname(\n    os.path.abspath(inspect.getfile(inspect.currentframe()))\n)\nsys.path.insert(0, currentdir + \"/../../../\")\n\n\nprint(json({\"test\": True}).output())\n\nprint(\"Running New 100,000 times\")\ntimes = 0\ntotal_time = 0\nfor n in range(6):\n    time = timeit.timeit(\n        'json({ \"test\":True }).output()',\n        setup=\"from sanic.response import json\",\n        number=100000,\n    )\n    print(f\"Took {time} seconds\")\n    total_time += time\n    times += 1\nprint(f\"Average: {total_time / times}\")\n\nprint(\"Running Old 100,000 times\")\ntimes = 0\ntotal_time = 0\nfor n in range(6):\n    time = timeit.timeit(\n        'json({ \"test\":True }).output_old()',\n        setup=\"from sanic.response import json\",\n        number=100000,\n    )\n    print(f\"Took {time} seconds\")\n    total_time += time\n    times += 1\nprint(f\"Average: {total_time / times}\")\n"
  },
  {
    "path": "tests/performance/sanic/simple_server.py",
    "content": "import inspect\nimport os\nimport sys\n\nfrom sanic import Sanic\nfrom sanic.response import json\n\n\ncurrentdir = os.path.dirname(\n    os.path.abspath(inspect.getfile(inspect.currentframe()))\n)\nsys.path.insert(0, currentdir + \"/../../../\")\n\n\napp = Sanic(\"test\")\n\n\n@app.route(\"/\")\nasync def test(request):\n    return json({\"test\": True})\n\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", port=sys.argv[1])\n"
  },
  {
    "path": "tests/performance/sanic/varied_server.py",
    "content": "import inspect\nimport os\nimport sys\n\nfrom sanic import Sanic\nfrom sanic.exceptions import ServerError\nfrom sanic.response import json, text\n\n\ncurrentdir = os.path.dirname(\n    os.path.abspath(inspect.getfile(inspect.currentframe()))\n)\nsys.path.insert(0, currentdir + \"/../../../\")\n\n\napp = Sanic(\"test\")\n\n\n@app.route(\"/\")\nasync def test(request):\n    return json({\"test\": True})\n\n\n@app.route(\"/sync\", methods=[\"GET\", \"POST\"])\ndef test_json_response(request):\n    return json({\"test\": True})\n\n\n@app.route(\"/text/<name>/<butt:int>\")\ndef rtext(request, name, butt):\n    return text(f\"yeehaww {name} {butt}\")\n\n\n@app.route(\"/exception\")\ndef exception(request):\n    raise ServerError(\"yep\")\n\n\n@app.route(\"/exception/async\")\nasync def test_server_error(request):\n    raise ServerError(\"asunk\")\n\n\n@app.route(\"/post_json\")\ndef post_json(request):\n    return json({\"received\": True, \"message\": request.json})\n\n\n@app.route(\"/query_string\")\ndef query_string(request):\n    return json(\n        {\n            \"parsed\": True,\n            \"args\": request.args,\n            \"url\": request.url,\n            \"query_string\": request.query_string,\n        }\n    )\n\n\napp.run(host=\"0.0.0.0\", port=sys.argv[1])\n\n\n# import asyncio_redis\n# import asyncpg\n# async def setup(sanic, loop):\n#     sanic.conn = []\n#     sanic.redis = []\n#     for x in range(10):\n#         sanic.conn.append(await asyncpg.connect(\n#           user='postgres',\n#           password='zomgdev',\n#           database='postgres',\n#           host='192.168.99.100'\n#         ))\n#     for n in range(30):\n#         connection = await asyncio_redis.Connection.create(\n#           host='192.168.99.100', port=6379\n#         )\n#         sanic.redis.append(connection)\n\n\n# c=0\n# @app.route(\"/postgres\")\n# async def postgres(request):\n#     global c\n#     values = await app.conn[c].fetch('''SELECT * FROM players''')\n#     c += 1\n#     if c == 10:\n#         c = 0\n#     return text(\"yep\")\n\n# r=0\n# @app.route(\"/redis\")\n# async def redis(request):\n#     global r\n#     try:\n#         values = await app.redis[r].get('my_key')\n#     except asyncio_redis.exceptions.ConnectionLostError:\n#         app.redis[r] = await asyncio_redis.Connection.create(\n#           host='127.0.0.1', port=6379\n#         )\n#         values = await app.redis[r].get('my_key')\n\n#     r += 1\n#     if r == 30:\n#         r = 0\n#     return text(values)\n"
  },
  {
    "path": "tests/skip_test_custom_protocol.py",
    "content": "from sanic.response import text\nfrom sanic.server import HttpProtocol\n\n\nclass CustomHttpProtocol(HttpProtocol):\n    def write_response(self, response):\n        if isinstance(response, str):\n            response = text(response)\n        self.transport.write(response.output(self.request.version))\n        self.transport.close()\n\n\ndef test_use_custom_protocol(app):\n    @app.route(\"/1\")\n    async def handler_1(request):\n        return \"OK\"\n\n    server_kwargs = {\"protocol\": CustomHttpProtocol}\n    request, response = app.test_client.get(\"/1\", server_kwargs=server_kwargs)\n    assert response.status == 200\n    assert response.text == \"OK\"\n"
  },
  {
    "path": "tests/static/app_test_config.py",
    "content": "TEST_SETTING_VALUE = 1\n"
  },
  {
    "path": "tests/static/bp/decode me.txt",
    "content": "I am just a regular static file that needs to have its uri decoded\n"
  },
  {
    "path": "tests/static/bp/test.file",
    "content": "I am just a regular static file\n"
  },
  {
    "path": "tests/static/decode me.txt",
    "content": "I am just a regular static file that needs to have its uri decoded\n"
  },
  {
    "path": "tests/static/nested/dir/foo.txt",
    "content": "foo\n"
  },
  {
    "path": "tests/static/test.file",
    "content": "I am just a regular static file\n"
  },
  {
    "path": "tests/static/test.html",
    "content": "<html>\n<body>\n<pre>\n                 ▄▄▄▄▄\n        ▀▀▀██████▄▄▄       _______________\n      ▄▄▄▄▄  █████████▄  /                 \\\n     ▀▀▀▀█████▌ ▀▐▄ ▀▐█ |   Gotta go fast!  |\n   ▀▀█████▄▄ ▀██████▄██ | _________________/\n   ▀▄▄▄▄▄  ▀▀█▄▀█════█▀ |/\n        ▀▀▀▄  ▀▀███ ▀       ▄▄\n     ▄███▀▀██▄████████▄ ▄▀▀▀▀▀▀█▌\n   ██▀▄▄▄██▀▄███▀ ▀▀████      ▄██\n▄▀▀▀▄██▄▀▀▌████▒▒▒▒▒▒███     ▌▄▄▀\n▌    ▐▀████▐███▒▒▒▒▒▐██▌\n▀▄▄▄▄▀   ▀▀████▒▒▒▒▄██▀\n          ▀▀█████████▀\n        ▄▄██▀██████▀█\n      ▄██▀     ▀▀▀  █\n     ▄█             ▐▌\n ▄▄▄▄█▌              ▀█▄▄▄▄▀▀▄\n▌     ▐                ▀▀▄▄▄▀\n ▀▀▄▄▀\n\n</pre>\n</body>\n</html>\n"
  },
  {
    "path": "tests/test_app.py",
    "content": "import asyncio\nimport logging\nimport re\n\nfrom collections import Counter\nfrom inspect import isawaitable\nfrom os import environ\nfrom unittest.mock import Mock, patch\n\nimport pytest\n\nimport sanic\n\nfrom sanic import Sanic\nfrom sanic.compat import OS_IS_WINDOWS\nfrom sanic.config import Config\nfrom sanic.exceptions import SanicException\nfrom sanic.helpers import Default\nfrom sanic.log import LOGGING_CONFIG_DEFAULTS\nfrom sanic.response import text\nfrom sanic.router import Route\n\nfrom .conftest import get_port\n\n\n@pytest.fixture(autouse=True)\ndef clear_app_registry():\n    Sanic._app_registry = {}\n\n\ndef test_app_loop_running(app: Sanic):\n    @app.get(\"/test\")\n    async def handler(request):\n        assert isinstance(app.loop, asyncio.AbstractEventLoop)\n        return text(\"pass\")\n\n    request, response = app.test_client.get(\"/test\")\n    assert response.text == \"pass\"\n\n\n@pytest.mark.asyncio\ndef test_create_asyncio_server(app: Sanic, port: int):\n    loop = asyncio.get_event_loop()\n    asyncio_srv_coro = app.create_server(return_asyncio_server=True, port=port)\n    assert isawaitable(asyncio_srv_coro)\n    srv = loop.run_until_complete(asyncio_srv_coro)\n    assert srv.is_serving() is True\n\n\n@pytest.mark.asyncio\ndef test_asyncio_server_no_start_serving(app: Sanic, port):\n    loop = asyncio.get_event_loop()\n    asyncio_srv_coro = app.create_server(\n        port=port,\n        return_asyncio_server=True,\n        asyncio_server_kwargs=dict(start_serving=False),\n    )\n    srv = loop.run_until_complete(asyncio_srv_coro)\n    assert srv.is_serving() is False\n\n\n@pytest.mark.asyncio\ndef test_asyncio_server_start_serving(app: Sanic, port):\n    loop = asyncio.get_event_loop()\n    asyncio_srv_coro = app.create_server(\n        port=port,\n        return_asyncio_server=True,\n        asyncio_server_kwargs=dict(start_serving=False),\n    )\n    srv = loop.run_until_complete(asyncio_srv_coro)\n    assert srv.is_serving() is False\n    loop.run_until_complete(srv.startup())\n    loop.run_until_complete(srv.start_serving())\n    assert srv.is_serving() is True\n    wait_close = srv.close()\n    loop.run_until_complete(wait_close)\n    # Looks like we can't easily test `serve_forever()`\n\n\n@pytest.mark.asyncio\ndef test_create_server_main(app: Sanic, caplog, port):\n    app.listener(\"main_process_start\")(lambda *_: ...)\n    loop = asyncio.get_event_loop()\n    with caplog.at_level(logging.INFO):\n        asyncio_srv_coro = app.create_server(\n            return_asyncio_server=True, port=port\n        )\n        loop.run_until_complete(asyncio_srv_coro)\n    assert (\n        \"sanic.root\",\n        30,\n        \"Listener events for the main process are not available with \"\n        \"create_server()\",\n    ) in caplog.record_tuples\n\n\n@pytest.mark.asyncio\ndef test_create_server_no_startup(app: Sanic, port):\n    loop = asyncio.get_event_loop()\n    asyncio_srv_coro = app.create_server(\n        port=port,\n        return_asyncio_server=True,\n        asyncio_server_kwargs=dict(start_serving=False),\n    )\n    srv = loop.run_until_complete(asyncio_srv_coro)\n    message = (\n        \"Cannot run Sanic server without first running await server.startup()\"\n    )\n    with pytest.raises(SanicException, match=message):\n        loop.run_until_complete(srv.start_serving())\n\n\n@pytest.mark.asyncio\ndef test_create_server_main_convenience(app: Sanic, caplog, port):\n    app.main_process_start(lambda *_: ...)\n    loop = asyncio.get_event_loop()\n    with caplog.at_level(logging.INFO):\n        asyncio_srv_coro = app.create_server(\n            return_asyncio_server=True, port=port\n        )\n        loop.run_until_complete(asyncio_srv_coro)\n    assert (\n        \"sanic.root\",\n        30,\n        \"Listener events for the main process are not available with \"\n        \"create_server()\",\n    ) in caplog.record_tuples\n\n\ndef test_app_loop_not_running(app: Sanic):\n    with pytest.raises(SanicException) as excinfo:\n        app.loop\n\n    assert str(excinfo.value) == (\n        \"Loop can only be retrieved after the app has started \"\n        \"running. Not supported with `create_server` function\"\n    )\n\n\ndef test_app_run_raise_type_error(app: Sanic, port):\n    with pytest.raises(TypeError) as excinfo:\n        app.run(loop=\"loop\", port=port)\n\n    assert str(excinfo.value) == (\n        \"loop is not a valid argument. To use an existing loop, \"\n        \"change to create_server().\\nSee more: \"\n        \"https://sanic.readthedocs.io/en/latest/sanic/deploying.html\"\n        \"#asynchronous-support\"\n    )\n\n\ndef test_app_route_raise_value_error(app: Sanic):\n    with pytest.raises(ValueError) as excinfo:\n\n        @app.route(\"/test\")\n        async def handler():\n            return text(\"test\")\n\n    assert (\n        str(excinfo.value)\n        == \"Required parameter `request` missing in the handler() route?\"\n    )\n\n\ndef test_app_handle_request_handler_is_none(app: Sanic, monkeypatch):\n    app.config.TOUCHUP = False\n    route = Mock(spec=Route)\n    route.extra.request_middleware = []\n    route.extra.response_middleware = []\n\n    def mockreturn(*args, **kwargs):\n        return route, None, {}\n\n    monkeypatch.setattr(app.router, \"get\", mockreturn)\n\n    @app.get(\"/test\")\n    def handler(request):\n        return text(\"test\")\n\n    _, response = app.test_client.get(\"/test\")\n\n    assert (\n        \"'None' was returned while requesting a handler from the router\"\n        in response.text\n    )\n\n\n@pytest.mark.parametrize(\"websocket_enabled\", [True, False])\n@pytest.mark.parametrize(\"enable\", [True, False])\ndef test_app_enable_websocket(app: Sanic, websocket_enabled, enable):\n    app.websocket_enabled = websocket_enabled\n    app.enable_websocket(enable=enable)\n\n    assert app.websocket_enabled == enable\n\n    @app.websocket(\"/ws\")\n    async def handler(request, ws):\n        await ws.send(\"test\")\n\n    assert app.websocket_enabled is True\n\n\n@patch(\"sanic.mixins.startup.WebSocketProtocol\")\ndef test_app_websocket_parameters(websocket_protocol_mock, app: Sanic):\n    app.config.WEBSOCKET_MAX_SIZE = 44\n    app.config.WEBSOCKET_PING_TIMEOUT = 48\n    app.config.WEBSOCKET_PING_INTERVAL = 50\n\n    @app.websocket(\"/ws\")\n    async def handler(request, ws):\n        await ws.send(\"test\")\n\n    try:\n        # This will fail because WebSocketProtocol is mocked and only the\n        # call kwargs matter\n        app.test_client.get(\"/ws\")\n    except Exception:\n        pass\n\n    websocket_protocol_call_args = websocket_protocol_mock.call_args\n    ws_kwargs = websocket_protocol_call_args[1]\n    assert ws_kwargs[\"websocket_max_size\"] == app.config.WEBSOCKET_MAX_SIZE\n    assert (\n        ws_kwargs[\"websocket_ping_timeout\"]\n        == app.config.WEBSOCKET_PING_TIMEOUT\n    )\n    assert (\n        ws_kwargs[\"websocket_ping_interval\"]\n        == app.config.WEBSOCKET_PING_INTERVAL\n    )\n\n\ndef test_handle_request_with_nested_exception(app: Sanic, monkeypatch):\n    err_msg = \"Mock Exception\"\n\n    def mock_error_handler_response(*args, **kwargs):\n        raise Exception(err_msg)\n\n    monkeypatch.setattr(\n        app.error_handler, \"response\", mock_error_handler_response\n    )\n\n    @app.get(\"/\")\n    def handler(request):\n        raise Exception\n\n    request, response = app.test_client.get(\"/\")\n    assert response.status == 500\n    assert response.text == \"An error occurred while handling an error\"\n\n\ndef test_handle_request_with_nested_exception_debug(app: Sanic, monkeypatch):\n    err_msg = \"Mock Exception\"\n\n    def mock_error_handler_response(*args, **kwargs):\n        raise Exception(err_msg)\n\n    monkeypatch.setattr(\n        app.error_handler, \"response\", mock_error_handler_response\n    )\n\n    @app.get(\"/\")\n    def handler(request):\n        raise Exception\n\n    request, response = app.test_client.get(\"/\", debug=True)\n    assert response.status == 500\n    assert response.text.startswith(\n        f\"Error while handling error: {err_msg}\\n\"\n        \"Stack: Traceback (most recent call last):\\n\"\n    )\n\n\ndef test_handle_request_with_nested_sanic_exception(\n    app: Sanic, monkeypatch, caplog\n):\n    def mock_error_handler_response(*args, **kwargs):\n        raise SanicException(\"Mock SanicException\")\n\n    monkeypatch.setattr(\n        app.error_handler, \"response\", mock_error_handler_response\n    )\n\n    @app.get(\"/\")\n    def handler(request):\n        raise Exception\n\n    with caplog.at_level(logging.ERROR):\n        request, response = app.test_client.get(\"/\")\n    port = request.server_port\n    assert port > 0\n    assert response.status == 500\n    assert \"Mock SanicException\" in response.text\n    assert (\n        \"sanic.error\",\n        logging.ERROR,\n        f\"Exception occurred while handling uri: 'http://127.0.0.1:{port}/'\",\n    ) in caplog.record_tuples\n\n\ndef test_app_name_required():\n    with pytest.raises(TypeError):\n        Sanic()\n\n\ndef test_app_has_test_mode_sync():\n    app = Sanic(\"test\")\n\n    @app.get(\"/\")\n    def handler(request):\n        assert request.app.test_mode\n        return text(\"test\")\n\n    _, response = app.test_client.get(\"/\")\n    assert response.status == 200\n\n\ndef test_app_registry():\n    assert len(Sanic._app_registry) == 0\n    instance = Sanic(\"test\")\n    assert len(Sanic._app_registry) == 1\n    assert Sanic._app_registry[\"test\"] is instance\n    Sanic.unregister_app(instance)\n    assert len(Sanic._app_registry) == 0\n\n\ndef test_app_registry_wrong_type():\n    with pytest.raises(\n        SanicException, match=\"Registered app must be an instance of Sanic\"\n    ):\n        Sanic.register_app(1)\n\n\ndef test_app_registry_name_reuse():\n    Sanic(\"test\")\n    Sanic.test_mode = False\n    with pytest.raises(\n        SanicException, match='Sanic app name \"test\" already in use.'\n    ):\n        Sanic(\"test\")\n    Sanic.test_mode = True\n    Sanic(\"test\")\n\n\ndef test_app_registry_retrieval():\n    instance = Sanic(\"test\")\n    assert Sanic.get_app(\"test\") is instance\n\n\ndef test_app_registry_retrieval_from_multiple():\n    instance = Sanic(\"test\")\n    Sanic(\"something_else\")\n    assert Sanic.get_app(\"test\") is instance\n\n\ndef test_get_app_does_not_exist():\n    with pytest.raises(\n        SanicException,\n        match=(\n            \"Sanic app name 'does-not-exist' not found.\\n\"\n            \"App instantiation must occur outside \"\n            \"if __name__ == '__main__' \"\n            \"block or by using an AppLoader.\\nSee \"\n            \"https://sanic.dev/en/guide/deployment/app-loader.html\"\n            \" for more details.\"\n        ),\n    ):\n        Sanic.get_app(\"does-not-exist\")\n\n\ndef test_get_app_does_not_exist_force_create():\n    assert isinstance(\n        Sanic.get_app(\"does-not-exist\", force_create=True), Sanic\n    )\n\n\ndef test_get_app_default():\n    instance = Sanic(\"test\")\n    assert Sanic.get_app() is instance\n\n\ndef test_get_app_no_default():\n    with pytest.raises(\n        SanicException, match=\"No Sanic apps have been registered.\"\n    ):\n        Sanic.get_app()\n\n\ndef test_get_app_default_ambiguous():\n    Sanic(\"test1\")\n    Sanic(\"test2\")\n    with pytest.raises(\n        SanicException,\n        match=re.escape(\n            'Multiple Sanic apps found, use Sanic.get_app(\"app_name\")'\n        ),\n    ):\n        Sanic.get_app()\n\n\ndef test_app_set_attribute_warning(app: Sanic):\n    message = (\n        \"Setting variables on Sanic instances is not allowed. You should \"\n        \"change your Sanic instance to use instance.ctx.foo instead.\"\n    )\n    with pytest.raises(AttributeError, match=message):\n        app.foo = 1\n\n\ndef test_app_set_context(app: Sanic):\n    app.ctx.foo = 1\n\n    retrieved = Sanic.get_app(app.name)\n    assert retrieved.ctx.foo == 1\n\n\ndef test_subclass_initialisation():\n    class CustomSanic(Sanic):\n        pass\n\n    CustomSanic(\"test_subclass_initialisation\")\n\n\ndef test_bad_custom_config():\n    with pytest.raises(\n        SanicException,\n        match=(\n            \"When instantiating Sanic with config, you cannot also pass \"\n            \"env_prefix\"\n        ),\n    ):\n        Sanic(\"test\", config=1, env_prefix=1)\n\n\ndef test_custom_config():\n    class CustomConfig(Config): ...\n\n    config = CustomConfig()\n    app = Sanic(\"custom\", config=config)\n\n    assert app.config == config\n\n\ndef test_custom_context():\n    class CustomContext: ...\n\n    ctx = CustomContext()\n    app = Sanic(\"custom\", ctx=ctx)\n\n    assert app.ctx == ctx\n\n\n@pytest.mark.parametrize(\"use\", (False, True))\ndef test_uvloop_config(app: Sanic, monkeypatch, use):\n    @app.get(\"/test\", name=\"test\")\n    def handler(request):\n        return text(\"ok\")\n\n    try_use_uvloop = Mock()\n    monkeypatch.setattr(sanic.mixins.startup, \"try_use_uvloop\", try_use_uvloop)\n\n    # Default config\n    app.test_client.get(\"/test\")\n    if OS_IS_WINDOWS:\n        try_use_uvloop.assert_not_called()\n    else:\n        try_use_uvloop.assert_called_once()\n\n    try_use_uvloop.reset_mock()\n    app.config[\"USE_UVLOOP\"] = use\n    app.test_client.get(\"/test\")\n\n    if use:\n        try_use_uvloop.assert_called_once()\n    else:\n        try_use_uvloop.assert_not_called()\n\n\n@pytest.mark.asyncio\ndef test_uvloop_cannot_never_called_with_create_server(caplog, monkeypatch):\n    apps = (Sanic(\"default-uvloop\"), Sanic(\"no-uvloop\"), Sanic(\"yes-uvloop\"))\n\n    apps[1].config.USE_UVLOOP = False\n    apps[2].config.USE_UVLOOP = True\n\n    try_use_uvloop = Mock()\n    monkeypatch.setattr(sanic.mixins.startup, \"try_use_uvloop\", try_use_uvloop)\n\n    loop = asyncio.get_event_loop()\n\n    with caplog.at_level(logging.WARNING):\n        for app in apps:\n            srv_coro = app.create_server(\n                return_asyncio_server=True,\n                asyncio_server_kwargs=dict(start_serving=False),\n                port=get_port(),\n            )\n            loop.run_until_complete(srv_coro)\n\n    try_use_uvloop.assert_not_called()  # Check it didn't try to change policy\n\n    message = (\n        \"You are trying to change the uvloop configuration, but \"\n        \"this is only effective when using the run(...) method. \"\n        \"When using the create_server(...) method Sanic will use \"\n        \"the already existing loop.\"\n    )\n\n    counter = Counter([(r[1], r[2]) for r in caplog.record_tuples])\n    modified = sum(\n        1 for app in apps if not isinstance(app.config.USE_UVLOOP, Default)\n    )\n\n    assert counter[(logging.WARNING, message)] == modified\n\n\n@pytest.mark.asyncio\ndef test_multiple_uvloop_configs_display_warning(caplog):\n    Sanic._uvloop_setting = None  # Reset the setting (changed in prev tests)\n\n    default_uvloop = Sanic(\"default-uvloop\")\n    no_uvloop = Sanic(\"no-uvloop\")\n    yes_uvloop = Sanic(\"yes-uvloop\")\n\n    no_uvloop.config.USE_UVLOOP = False\n    yes_uvloop.config.USE_UVLOOP = True\n\n    loop = asyncio.get_event_loop()\n\n    with caplog.at_level(logging.WARNING):\n        for app in (default_uvloop, no_uvloop, yes_uvloop):\n            srv_coro = app.create_server(\n                return_asyncio_server=True,\n                asyncio_server_kwargs=dict(start_serving=False),\n                port=get_port(),\n            )\n            srv = loop.run_until_complete(srv_coro)\n            loop.run_until_complete(srv.startup())\n\n    message = (\n        \"It looks like you're running several apps with different \"\n        \"uvloop settings. This is not supported and may lead to \"\n        \"unintended behaviour.\"\n    )\n\n    counter = Counter([(r[1], r[2]) for r in caplog.record_tuples])\n\n    assert counter[(logging.WARNING, message)] == 3\n\n\ndef test_cannot_run_fast_and_workers(app: Sanic, port):\n    message = \"You cannot use both fast=True and workers=X\"\n    with pytest.raises(RuntimeError, match=message):\n        app.run(fast=True, workers=4, port=port)\n\n\ndef test_no_workers(app: Sanic, port):\n    with pytest.raises(RuntimeError, match=\"Cannot serve with no workers\"):\n        app.run(workers=0, port=port)\n\n\n@pytest.mark.parametrize(\n    \"extra\",\n    (\n        {\"fast\": True},\n        {\"workers\": 2},\n        {\"auto_reload\": True},\n    ),\n)\ndef test_cannot_run_single_process_and_workers_or_auto_reload(\n    app: Sanic, extra, port\n):\n    message = (\n        \"Single process cannot be run with multiple workers or auto-reload\"\n    )\n    with pytest.raises(RuntimeError, match=message):\n        app.run(single_process=True, port=port, **extra)\n\n\ndef test_default_configure_logging():\n    with patch(\"sanic.app.logging\") as mock:\n        Sanic(\"Test\")\n\n    mock.config.dictConfig.assert_called_with(LOGGING_CONFIG_DEFAULTS)\n\n\ndef test_custom_configure_logging():\n    with patch(\"sanic.app.logging\") as mock:\n        Sanic(\"Test\", log_config={\"foo\": \"bar\"})\n\n    mock.config.dictConfig.assert_called_with({\"foo\": \"bar\"})\n\n\ndef test_disable_configure_logging():\n    with patch(\"sanic.app.logging\") as mock:\n        Sanic(\"Test\", configure_logging=False)\n\n    mock.config.dictConfig.assert_not_called()\n\n\n@pytest.mark.parametrize(\"inspector\", (True, False))\ndef test_inspector(inspector):\n    app = Sanic(\"Test\", inspector=inspector)\n    assert app.config.INSPECTOR is inspector\n\n\ndef test_build_endpoint_name():\n    app = Sanic(\"Test\")\n    name = app._build_endpoint_name(\"foo\", \"bar\")\n    assert name == \"Test.foo.bar\"\n\n\ndef test_manager_in_main_process_only(app: Sanic):\n    message = \"Can only access the manager from the main process\"\n\n    with pytest.raises(SanicException, match=message):\n        app.manager\n\n    app._manager = 1\n    environ[\"SANIC_WORKER_PROCESS\"] = \"ok\"\n\n    with pytest.raises(SanicException, match=message):\n        app.manager\n\n    del environ[\"SANIC_WORKER_PROCESS\"]\n\n    assert app.manager == 1\n\n\ndef test_inspector_in_main_process_only(app: Sanic):\n    message = \"Can only access the inspector from the main process\"\n\n    with pytest.raises(SanicException, match=message):\n        app.inspector\n\n    app._inspector = 1\n    environ[\"SANIC_WORKER_PROCESS\"] = \"ok\"\n\n    with pytest.raises(SanicException, match=message):\n        app.inspector\n\n    del environ[\"SANIC_WORKER_PROCESS\"]\n\n    assert app.inspector == 1\n\n\ndef test_stop_trigger_terminate(app: Sanic):\n    app.multiplexer = Mock()\n\n    app.stop()\n\n    app.multiplexer.terminate.assert_called_once()\n    app.multiplexer.reset_mock()\n    assert len(Sanic._app_registry) == 1\n    Sanic._app_registry.clear()\n\n    app.stop(terminate=True)\n\n    app.multiplexer.terminate.assert_called_once()\n    app.multiplexer.reset_mock()\n    assert len(Sanic._app_registry) == 0\n    Sanic._app_registry.clear()\n\n    app.stop(unregister=False)\n    app.multiplexer.terminate.assert_called_once()\n\n\ndef test_refresh_pass_passthru_data_to_new_instance(app: Sanic):\n    # arrange\n    passthru = {\"_inspector\": 2, \"config\": {\"TOUCHUP\": 23}}\n    app = app.refresh(passthru)\n\n    assert app.inspector == 2\n    assert app.config.TOUCHUP == 23\n"
  },
  {
    "path": "tests/test_asgi.py",
    "content": "import asyncio\nimport logging\n\nfrom collections import deque, namedtuple\nfrom unittest.mock import call\n\nimport pytest\nimport uvicorn\n\nfrom httpx import Headers\nfrom pytest import MonkeyPatch\n\nfrom sanic import Sanic\nfrom sanic.application.state import Mode\nfrom sanic.asgi import Lifespan, MockTransport\nfrom sanic.exceptions import BadRequest, Forbidden, ServiceUnavailable\nfrom sanic.request import Request\nfrom sanic.response import json, text\nfrom sanic.server.websockets.connection import WebSocketConnection\nfrom sanic.signals import RESERVED_NAMESPACES\n\nfrom .conftest import get_port\n\n\ntry:\n    from unittest.mock import AsyncMock\nexcept ImportError:\n    from tests.asyncmock import AsyncMock  # type: ignore\n\n\n@pytest.fixture\ndef message_stack():\n    return deque()\n\n\n@pytest.fixture\ndef receive(message_stack):\n    async def _receive():\n        return message_stack.popleft()\n\n    return _receive\n\n\n@pytest.fixture\ndef send(message_stack):\n    async def _send(message):\n        message_stack.append(message)\n\n    return _send\n\n\n@pytest.fixture\ndef transport(message_stack, receive, send):\n    return MockTransport({}, receive, send)\n\n\n@pytest.fixture\ndef protocol(transport):\n    loop = asyncio.new_event_loop()\n    asyncio.set_event_loop(loop)\n    transport.loop = loop\n    return transport.get_protocol()\n\n\ndef test_listeners_triggered(caplog):\n    app = Sanic(\"app\")\n    before_server_start = False\n    after_server_start = False\n    before_server_stop = False\n    after_server_stop = False\n\n    @app.listener(\"before_server_start\")\n    def do_before_server_start(*args, **kwargs):\n        nonlocal before_server_start\n        before_server_start = True\n\n    @app.listener(\"after_server_start\")\n    def do_after_server_start(*args, **kwargs):\n        nonlocal after_server_start\n        after_server_start = True\n\n    @app.listener(\"before_server_stop\")\n    def do_before_server_stop(*args, **kwargs):\n        nonlocal before_server_stop\n        before_server_stop = True\n\n    @app.listener(\"after_server_stop\")\n    def do_after_server_stop(*args, **kwargs):\n        nonlocal after_server_stop\n        after_server_stop = True\n\n    @app.route(\"/\")\n    def handler(request):\n        return text(\"...\")\n\n    class CustomServer(uvicorn.Server):\n        def install_signal_handlers(self):\n            pass\n\n    config = uvicorn.Config(\n        app=app, loop=\"asyncio\", limit_max_requests=0, port=get_port()\n    )\n    server = CustomServer(config=config)\n\n    start_message = (\n        'You have set a listener for \"before_server_start\" in ASGI mode. '\n        \"It will be executed as early as possible, but not before the ASGI \"\n        \"server is started.\"\n    )\n    stop_message = (\n        'You have set a listener for \"after_server_stop\" in ASGI mode. '\n        \"It will be executed as late as possible, but not after the ASGI \"\n        \"server is stopped.\"\n    )\n\n    with caplog.at_level(logging.DEBUG):\n        server.run()\n\n    assert (\n        \"sanic.root\",\n        logging.DEBUG,\n        start_message,\n    ) not in caplog.record_tuples\n    assert (\n        \"sanic.root\",\n        logging.DEBUG,\n        stop_message,\n    ) not in caplog.record_tuples\n\n    assert before_server_start\n    assert after_server_start\n    assert before_server_stop\n    assert after_server_stop\n\n    app.state.mode = Mode.DEBUG\n    with caplog.at_level(logging.DEBUG):\n        server.run()\n\n    assert (\n        \"sanic.root\",\n        logging.DEBUG,\n        start_message,\n    ) not in caplog.record_tuples\n    assert (\n        \"sanic.root\",\n        logging.DEBUG,\n        stop_message,\n    ) not in caplog.record_tuples\n\n    app.state.verbosity = 2\n    with caplog.at_level(logging.DEBUG):\n        server.run()\n\n    assert (\n        \"sanic.root\",\n        logging.DEBUG,\n        start_message,\n    ) in caplog.record_tuples\n    assert (\n        \"sanic.root\",\n        logging.DEBUG,\n        stop_message,\n    ) in caplog.record_tuples\n\n\ndef test_listeners_triggered_async(app, caplog):\n    before_server_start = False\n    after_server_start = False\n    before_server_stop = False\n    after_server_stop = False\n\n    @app.listener(\"before_server_start\")\n    async def do_before_server_start(*args, **kwargs):\n        nonlocal before_server_start\n        before_server_start = True\n\n    @app.listener(\"after_server_start\")\n    async def do_after_server_start(*args, **kwargs):\n        nonlocal after_server_start\n        after_server_start = True\n\n    @app.listener(\"before_server_stop\")\n    async def do_before_server_stop(*args, **kwargs):\n        nonlocal before_server_stop\n        before_server_stop = True\n\n    @app.listener(\"after_server_stop\")\n    async def do_after_server_stop(*args, **kwargs):\n        nonlocal after_server_stop\n        after_server_stop = True\n\n    @app.route(\"/\")\n    def handler(request):\n        return text(\"...\")\n\n    class CustomServer(uvicorn.Server):\n        def install_signal_handlers(self):\n            pass\n\n    config = uvicorn.Config(\n        app=app, loop=\"asyncio\", limit_max_requests=0, port=get_port()\n    )\n    server = CustomServer(config=config)\n\n    start_message = (\n        'You have set a listener for \"before_server_start\" in ASGI mode. '\n        \"It will be executed as early as possible, but not before the ASGI \"\n        \"server is started.\"\n    )\n    stop_message = (\n        'You have set a listener for \"after_server_stop\" in ASGI mode. '\n        \"It will be executed as late as possible, but not after the ASGI \"\n        \"server is stopped.\"\n    )\n\n    with caplog.at_level(logging.DEBUG):\n        server.run()\n\n    assert (\n        \"sanic.root\",\n        logging.DEBUG,\n        start_message,\n    ) not in caplog.record_tuples\n    assert (\n        \"sanic.root\",\n        logging.DEBUG,\n        stop_message,\n    ) not in caplog.record_tuples\n\n    assert before_server_start\n    assert after_server_start\n    assert before_server_stop\n    assert after_server_stop\n\n    app.state.mode = Mode.DEBUG\n    app.state.verbosity = 0\n    with caplog.at_level(logging.DEBUG):\n        server.run()\n\n    assert (\n        \"sanic.root\",\n        logging.DEBUG,\n        start_message,\n    ) not in caplog.record_tuples\n    assert (\n        \"sanic.root\",\n        logging.DEBUG,\n        stop_message,\n    ) not in caplog.record_tuples\n\n    app.state.verbosity = 2\n    with caplog.at_level(logging.DEBUG):\n        server.run()\n\n    assert (\n        \"sanic.root\",\n        logging.DEBUG,\n        start_message,\n    ) in caplog.record_tuples\n    assert (\n        \"sanic.root\",\n        logging.DEBUG,\n        stop_message,\n    ) in caplog.record_tuples\n\n\ndef test_non_default_uvloop_config_raises_warning(app):\n    app.config.USE_UVLOOP = True\n\n    class CustomServer(uvicorn.Server):\n        def install_signal_handlers(self):\n            pass\n\n    config = uvicorn.Config(\n        app=app, loop=\"asyncio\", limit_max_requests=0, port=get_port()\n    )\n    server = CustomServer(config=config)\n\n    with pytest.warns(UserWarning) as records:\n        server.run()\n\n    msg = \"\"\n    for record in records:\n        _msg = str(record.message)\n        if _msg.startswith(\"You have set the USE_UVLOOP configuration\"):\n            msg = _msg\n            break\n\n    assert msg == (\n        \"You have set the USE_UVLOOP configuration option, but Sanic \"\n        \"cannot control the event loop when running in ASGI mode.\"\n        \"This option will be ignored.\"\n    )\n\n\n@pytest.mark.asyncio\nasync def test_mockprotocol_events(protocol):\n    assert protocol._not_paused.is_set()\n    protocol.pause_writing()\n    assert not protocol._not_paused.is_set()\n    protocol.resume_writing()\n    assert protocol._not_paused.is_set()\n\n\n@pytest.mark.asyncio\nasync def test_protocol_push_data(protocol, message_stack):\n    text = b\"hello\"\n\n    await protocol.push_data(text)\n    await protocol.complete()\n\n    assert len(message_stack) == 2\n\n    message = message_stack.popleft()\n    assert message[\"type\"] == \"http.response.body\"\n    assert message[\"more_body\"]\n    assert message[\"body\"] == text\n\n    message = message_stack.popleft()\n    assert message[\"type\"] == \"http.response.body\"\n    assert not message[\"more_body\"]\n    assert message[\"body\"] == b\"\"\n\n\n@pytest.mark.asyncio\nasync def test_websocket_send(send, receive, message_stack):\n    text_string = \"hello\"\n    text_bytes = b\"hello\"\n\n    ws = WebSocketConnection(send, receive)\n    await ws.send(text_string)\n    await ws.send(text_bytes)\n\n    assert len(message_stack) == 2\n\n    message = message_stack.popleft()\n    assert message[\"type\"] == \"websocket.send\"\n    assert message[\"text\"] == text_string\n    assert \"bytes\" not in message\n\n    message = message_stack.popleft()\n    assert message[\"type\"] == \"websocket.send\"\n    assert message[\"bytes\"] == text_bytes\n    assert \"text\" not in message\n\n\n@pytest.mark.asyncio\nasync def test_websocket_text_receive(send, receive, message_stack):\n    msg = {\"text\": \"hello\", \"type\": \"websocket.receive\"}\n    message_stack.append(msg)\n\n    ws = WebSocketConnection(send, receive)\n    text = await ws.receive()\n\n    assert text == msg[\"text\"]\n\n\n@pytest.mark.asyncio\nasync def test_websocket_bytes_receive(send, receive, message_stack):\n    msg = {\"bytes\": b\"hello\", \"type\": \"websocket.receive\"}\n    message_stack.append(msg)\n\n    ws = WebSocketConnection(send, receive)\n    data = await ws.receive()\n\n    assert data == msg[\"bytes\"]\n\n\n@pytest.mark.asyncio\nasync def test_websocket_accept_with_no_subprotocols(\n    send, receive, message_stack\n):\n    ws = WebSocketConnection(send, receive)\n    await ws.accept()\n\n    assert len(message_stack) == 1\n\n    message = message_stack.popleft()\n    assert message[\"type\"] == \"websocket.accept\"\n    assert message[\"subprotocol\"] is None\n    assert \"bytes\" not in message\n\n\n@pytest.mark.asyncio\nasync def test_websocket_accept_with_subprotocol(send, receive, message_stack):\n    subprotocols = [\"graphql-ws\"]\n\n    ws = WebSocketConnection(send, receive, subprotocols)\n    await ws.accept(subprotocols)\n\n    assert len(message_stack) == 1\n\n    message = message_stack.popleft()\n    assert message[\"type\"] == \"websocket.accept\"\n    assert message[\"subprotocol\"] == \"graphql-ws\"\n    assert \"bytes\" not in message\n\n\n@pytest.mark.asyncio\nasync def test_websocket_accept_with_multiple_subprotocols(\n    send, receive, message_stack\n):\n    subprotocols = [\"graphql-ws\", \"hello\", \"world\"]\n\n    ws = WebSocketConnection(send, receive, subprotocols)\n    await ws.accept([\"hello\", \"world\"])\n\n    assert len(message_stack) == 1\n\n    message = message_stack.popleft()\n    assert message[\"type\"] == \"websocket.accept\"\n    assert message[\"subprotocol\"] == \"hello\"\n    assert \"bytes\" not in message\n\n\ndef test_improper_websocket_connection(transport, send, receive):\n    with pytest.raises(BadRequest):\n        transport.get_websocket_connection()\n\n    transport.create_websocket_connection(send, receive)\n    connection = transport.get_websocket_connection()\n    assert isinstance(connection, WebSocketConnection)\n\n\n@pytest.mark.asyncio\nasync def test_request_class_regular(app):\n    @app.get(\"/regular\")\n    def regular_request(request):\n        return text(request.__class__.__name__)\n\n    _, response = await app.asgi_client.get(\"/regular\")\n    assert response.body == b\"Request\"\n\n\n@pytest.mark.asyncio\nasync def test_request_class_custom():\n    class MyCustomRequest(Request):\n        pass\n\n    app = Sanic(name=\"Test\", request_class=MyCustomRequest)\n\n    @app.get(\"/custom\")\n    def custom_request(request):\n        return text(request.__class__.__name__)\n\n    _, response = await app.asgi_client.get(\"/custom\")\n    assert response.body == b\"MyCustomRequest\"\n\n\n@pytest.mark.asyncio\nasync def test_cookie_customization(app):\n    @app.get(\"/cookie\")\n    def get_cookie(request):\n        response = text(\"There's a cookie up in this response\")\n        response.add_cookie(\"test\", \"Cookie1\", httponly=True)\n        response.add_cookie(\"c2\", \"Cookie2\", httponly=False)\n\n        return response\n\n    _, response = await app.asgi_client.get(\"/cookie\")\n\n    CookieDef = namedtuple(\"CookieDef\", (\"value\", \"httponly\"))\n    Cookie = namedtuple(\"Cookie\", (\"domain\", \"path\", \"value\", \"httponly\"))\n    cookie_map = {\n        \"test\": CookieDef(\"Cookie1\", True),\n        \"c2\": CookieDef(\"Cookie2\", False),\n    }\n\n    cookies = {\n        c.name: Cookie(c.domain, c.path, c.value, \"HttpOnly\" in c._rest.keys())\n        for c in response.cookies.jar\n    }\n\n    for name, definition in cookie_map.items():\n        cookie = cookies.get(name)\n        assert cookie\n        assert cookie.value == definition.value\n        assert cookie.domain == \"mockserver.local\"\n        assert cookie.path == \"/\"\n        assert cookie.httponly == definition.httponly\n\n\n@pytest.mark.asyncio\nasync def test_content_type(app):\n    @app.get(\"/json\")\n    def send_json(request):\n        return json({\"foo\": \"bar\"})\n\n    @app.get(\"/text\")\n    def send_text(request):\n        return text(\"foobar\")\n\n    @app.get(\"/custom\")\n    def send_custom(request):\n        return text(\"foobar\", content_type=\"somethingelse\")\n\n    _, response = await app.asgi_client.get(\"/json\")\n    assert response.headers.get(\"content-type\") == \"application/json\"\n\n    _, response = await app.asgi_client.get(\"/text\")\n    assert response.headers.get(\"content-type\") == \"text/plain; charset=utf-8\"\n\n    _, response = await app.asgi_client.get(\"/custom\")\n    assert response.headers.get(\"content-type\") == \"somethingelse\"\n\n\n@pytest.mark.asyncio\nasync def test_request_handle_exception(app):\n    @app.get(\"/error-prone\")\n    def _request(request):\n        raise ServiceUnavailable(message=\"Service unavailable\")\n\n    _, response = await app.asgi_client.get(\"/wrong-path\")\n    assert response.status_code == 404\n\n    _, response = await app.asgi_client.get(\"/error-prone\")\n    assert response.status_code == 503\n\n\n@pytest.mark.asyncio\nasync def test_request_exception_suppressed_by_middleware(app):\n    @app.get(\"/error-prone\")\n    def _request(request):\n        raise ServiceUnavailable(message=\"Service unavailable\")\n\n    @app.on_request\n    def forbidden(request):\n        raise Forbidden(message=\"forbidden\")\n\n    _, response = await app.asgi_client.get(\"/wrong-path\")\n    assert response.status_code == 403\n\n    _, response = await app.asgi_client.get(\"/error-prone\")\n    assert response.status_code == 403\n\n\n@pytest.mark.asyncio\nasync def test_signals_triggered(app):\n    @app.get(\"/test_signals_triggered\")\n    async def _request(request):\n        return text(\"test_signals_triggered\")\n\n    signals_triggered = []\n    signals_expected = [\n        # \"http.lifecycle.begin\",\n        # \"http.lifecycle.read_head\",\n        \"http.lifecycle.request\",\n        \"http.lifecycle.handle\",\n        \"http.routing.before\",\n        \"http.routing.after\",\n        \"http.handler.before\",\n        \"http.handler.after\",\n        \"http.lifecycle.response\",\n        # \"http.lifecycle.send\",\n        # \"http.lifecycle.complete\",\n    ]\n\n    def signal_handler(signal):\n        return lambda *a, **kw: signals_triggered.append(signal)\n\n    for signal in RESERVED_NAMESPACES[\"http\"]:\n        app.signal(signal)(signal_handler(signal))\n\n    _, response = await app.asgi_client.get(\"/test_signals_triggered\")\n    assert response.status_code == 200\n    assert response.text == \"test_signals_triggered\"\n    assert signals_triggered == signals_expected\n\n\n@pytest.mark.asyncio\nasync def test_asgi_serve_location(app):\n    @app.get(\"/\")\n    def _request(request: Request):\n        return text(request.app.serve_location)\n\n    _, response = await app.asgi_client.get(\"/\")\n    assert response.text == \"http://<ASGI>\"\n\n\n@pytest.mark.asyncio\nasync def test_error_on_lifespan_exception_start(app, caplog):\n    @app.before_server_start\n    async def before_server_start(_):\n        1 / 0\n\n    recv = AsyncMock(\n        side_effect=[\n            {\"type\": \"lifespan.startup\"},\n            {\"type\": \"lifespan.shutdown\"},\n        ]\n    )\n    send = AsyncMock()\n    app.asgi = True\n\n    lifespan = Lifespan(app, {\"type\": \"lifespan\"}, recv, send)\n    with caplog.at_level(logging.ERROR):\n        await lifespan()\n\n    send.assert_has_calls(\n        [\n            call(\n                {\n                    \"type\": \"lifespan.startup.failed\",\n                    \"message\": \"division by zero\",\n                }\n            )\n        ]\n    )\n\n\n@pytest.mark.asyncio\nasync def test_error_on_lifespan_exception_stop(app: Sanic):\n    @app.before_server_stop\n    async def before_server_stop(_):\n        1 / 0\n\n    recv = AsyncMock(\n        side_effect=[\n            {\"type\": \"lifespan.startup\"},\n            {\"type\": \"lifespan.shutdown\"},\n        ]\n    )\n    send = AsyncMock()\n    app.asgi = True\n    await app._startup()\n\n    lifespan = Lifespan(app, {\"type\": \"lifespan\"}, recv, send)\n    await lifespan()\n\n    send.assert_has_calls(\n        [\n            call(\n                {\n                    \"type\": \"lifespan.shutdown.failed\",\n                    \"message\": \"division by zero\",\n                }\n            )\n        ]\n    )\n\n\n@pytest.mark.asyncio\nasync def test_asgi_headers_decoding(app: Sanic, monkeypatch: MonkeyPatch):\n    @app.get(\"/\")\n    def handler(request: Request):\n        return text(\"\")\n\n    headers_init = Headers.__init__\n\n    def mocked_headers_init(self, *args, **kwargs):\n        if \"encoding\" in kwargs:\n            kwargs.pop(\"encoding\")\n        headers_init(self, encoding=\"utf-8\", *args, **kwargs)\n\n    monkeypatch.setattr(Headers, \"__init__\", mocked_headers_init)\n\n    message = \"Header names can only contain US-ASCII characters\"\n    with pytest.raises(BadRequest, match=message):\n        _, response = await app.asgi_client.get(\"/\", headers={\"😂\": \"😅\"})\n\n    _, response = await app.asgi_client.get(\"/\", headers={\"Test-Header\": \"😅\"})\n    assert response.status_code == 200\n\n\n@pytest.mark.asyncio\nasync def test_asgi_url_decoding(app):\n    @app.get(\"/dir/<name>\", unquote=True)\n    def _request(request: Request, name):\n        return text(name)\n\n    # 2F should not become a path separator (unquoted later)\n    _, response = await app.asgi_client.get(\"/dir/some%2Fpath\")\n    assert response.text == \"some/path\"\n\n    _, response = await app.asgi_client.get(\"/dir/some%F0%9F%98%80path\")\n    assert response.text == \"some😀path\"\n"
  },
  {
    "path": "tests/test_bad_request.py",
    "content": "import asyncio\n\nfrom sanic import Sanic\n\n\ndef test_bad_request_response(app: Sanic):\n    lines = []\n\n    app.get(\"/\")(lambda x: ...)\n\n    @app.listener(\"after_server_start\")\n    async def _request(sanic, loop):\n        nonlocal lines\n        connect = asyncio.open_connection(\"127.0.0.1\", 42101)\n        reader, writer = await connect\n        writer.write(b\"not http\\r\\n\\r\\n\")\n        while True:\n            line = await reader.readline()\n            if not line:\n                break\n            lines.append(line)\n        app.stop()\n\n    app.run(host=\"127.0.0.1\", port=42101, debug=False, single_process=True)\n    assert lines[0] == b\"HTTP/1.1 400 Bad Request\\r\\n\"\n    assert b\"Bad Request\" in lines[-2]\n"
  },
  {
    "path": "tests/test_base.py",
    "content": "import pytest\n\nfrom sanic import Blueprint, Sanic\nfrom sanic.exceptions import SanicException\n\n\n@pytest.fixture\ndef app():\n    return Sanic(\"my_app\")\n\n\n@pytest.fixture\ndef bp(app):\n    return Blueprint(\"my_bp\")\n\n\ndef test_app_str(app):\n    assert str(app) == \"<Sanic my_app>\"\n\n\ndef test_app_repr(app):\n    assert repr(app) == 'Sanic(name=\"my_app\")'\n\n\ndef test_bp_str(bp):\n    assert str(bp) == \"<Blueprint my_bp>\"\n\n\ndef test_bp_repr(bp):\n    assert repr(bp) == (\n        'Blueprint(name=\"my_bp\", url_prefix=None, host=None, '\n        \"version=None, strict_slashes=None)\"\n    )\n\n\ndef test_bp_repr_with_values(bp):\n    bp.host = \"example.com\"\n    bp.url_prefix = \"/foo\"\n    bp.version = 3\n    bp.strict_slashes = True\n    assert repr(bp) == (\n        'Blueprint(name=\"my_bp\", url_prefix=\"/foo\", host=\"example.com\", '\n        \"version=3, strict_slashes=True)\"\n    )\n\n\n@pytest.mark.parametrize(\n    \"name\",\n    (\n        \"something\",\n        \"some-thing\",\n        \"some_thing\",\n        \"Something\",\n        \"SomeThing\",\n        \"Some-Thing\",\n        \"Some_Thing\",\n        \"SomeThing123\",\n        \"something123\",\n        \"some-thing123\",\n        \"some_thing123\",\n        \"some-Thing123\",\n        \"some_Thing123\",\n    ),\n)\ndef test_names_okay(name):\n    app = Sanic(name)\n    bp = Blueprint(name)\n\n    assert app.name == name\n    assert bp.name == name\n\n\n@pytest.mark.parametrize(\n    \"name\",\n    (\n        \"123something\",\n        \"some thing\",\n        \"something!\",\n    ),\n)\ndef test_names_not_okay(name):\n    app_message = (\n        f\"Sanic instance named '{name}' uses an invalid format. Names must \"\n        \"begin with a character and may only contain alphanumeric \"\n        \"characters, _, or -.\"\n    )\n    bp_message = (\n        f\"Blueprint instance named '{name}' uses an invalid format. Names \"\n        \"must begin with a character and may only contain alphanumeric \"\n        \"characters, _, or -.\"\n    )\n\n    with pytest.raises(SanicException, match=app_message):\n        Sanic(name)\n\n    with pytest.raises(SanicException, match=bp_message):\n        Blueprint(name)\n"
  },
  {
    "path": "tests/test_blueprint_copy.py",
    "content": "import pytest\n\nfrom sanic_routing.exceptions import RouteExists\n\nfrom sanic import Blueprint, Request, Sanic\nfrom sanic.response import text\n\n\ndef test_bp_copy(app: Sanic):\n    bp1 = Blueprint(\"test_bp1\", version=1)\n    bp1.ctx.test = 1\n    assert hasattr(bp1.ctx, \"test\")\n\n    @bp1.route(\"/page\")\n    def handle_request(request):\n        return text(\"Hello world!\")\n\n    bp2 = bp1.copy(name=\"test_bp2\", version=2)\n    assert id(bp1) != id(bp2)\n    assert bp1._apps == bp2._apps == set()\n    assert not hasattr(bp2.ctx, \"test\")\n    assert len(bp2._future_exceptions) == len(bp1._future_exceptions)\n    assert len(bp2._future_listeners) == len(bp1._future_listeners)\n    assert len(bp2._future_middleware) == len(bp1._future_middleware)\n    assert len(bp2._future_routes) == len(bp1._future_routes)\n    assert len(bp2._future_signals) == len(bp1._future_signals)\n\n    app.blueprint(bp1)\n    app.blueprint(bp2)\n\n    bp3 = bp1.copy(name=\"test_bp3\", version=3, with_registration=True)\n    assert id(bp1) != id(bp3)\n    assert bp1._apps == bp3._apps and bp3._apps\n    assert not hasattr(bp3.ctx, \"test\")\n\n    bp4 = bp1.copy(name=\"test_bp4\", version=4, with_ctx=True)\n    assert id(bp1) != id(bp4)\n    assert bp4.ctx.test == 1\n\n    bp5 = bp1.copy(name=\"test_bp5\", version=5, with_registration=False)\n    assert id(bp1) != id(bp5)\n    assert not bp5._apps\n    assert bp1._apps != set()\n\n    app.blueprint(bp5)\n\n    bp6 = bp1.copy(\n        name=\"test_bp6\",\n        version=6,\n        with_registration=True,\n        version_prefix=\"/version\",\n    )\n    assert bp6._apps\n    assert bp6.version_prefix == \"/version\"\n\n    _, response = app.test_client.get(\"/v1/page\")\n    assert \"Hello world!\" in response.text\n\n    _, response = app.test_client.get(\"/v2/page\")\n    assert \"Hello world!\" in response.text\n\n    _, response = app.test_client.get(\"/v3/page\")\n    assert \"Hello world!\" in response.text\n\n    _, response = app.test_client.get(\"/v4/page\")\n    assert \"Hello world!\" in response.text\n\n    _, response = app.test_client.get(\"/v5/page\")\n    assert \"Hello world!\" in response.text\n\n    _, response = app.test_client.get(\"/version6/page\")\n    assert \"Hello world!\" in response.text\n\n    route_names = [route.name for route in app.router.routes]\n    assert \"test_bp_copy.test_bp1.handle_request\" in route_names\n    assert \"test_bp_copy.test_bp2.handle_request\" in route_names\n    assert \"test_bp_copy.test_bp3.handle_request\" in route_names\n    assert \"test_bp_copy.test_bp4.handle_request\" in route_names\n    assert \"test_bp_copy.test_bp5.handle_request\" in route_names\n    assert \"test_bp_copy.test_bp6.handle_request\" in route_names\n\n\ndef test_bp_copy_without_route_overwriting(app: Sanic):\n    bpv1 = Blueprint(\"bp_v1\", version=1, url_prefix=\"my_api\")\n\n    @bpv1.route(\"/\")\n    async def handler_v1(request: Request):\n        return text(\"v1\")\n\n    app.blueprint(bpv1)\n\n    bpv2 = bpv1.copy(\"bp_v2\", version=2, allow_route_overwrite=False)\n    bpv3 = bpv1.copy(\n        \"bp_v3\",\n        version=3,\n        allow_route_overwrite=False,\n        with_registration=False,\n    )\n\n    with pytest.raises(RouteExists, match=\"Route already registered*\"):\n\n        @bpv2.route(\"/\")\n        async def handler_v2(request: Request):\n            return text(\"v2\")\n\n        app.blueprint(bpv2)\n\n    with pytest.raises(RouteExists, match=\"Route already registered*\"):\n\n        @bpv3.route(\"/\")\n        async def handler_v3(request: Request):\n            return text(\"v3\")\n\n        app.blueprint(bpv3)\n\n\ndef test_bp_copy_with_route_overwriting(app: Sanic):\n    bpv1 = Blueprint(\"bp_v1\", version=1, url_prefix=\"my_api\")\n\n    @bpv1.route(\"/\")\n    async def handler_v1(request: Request):\n        return text(\"v1\")\n\n    app.blueprint(bpv1)\n\n    bpv2 = bpv1.copy(\"bp_v2\", version=2, allow_route_overwrite=True)\n    bpv3 = bpv1.copy(\n        \"bp_v3\", version=3, allow_route_overwrite=True, with_registration=False\n    )\n\n    @bpv2.route(\"/\")\n    async def handler_v2(request: Request):\n        return text(\"v2\")\n\n    app.blueprint(bpv2)\n\n    @bpv3.route(\"/\")\n    async def handler_v3(request: Request):\n        return text(\"v3\")\n\n    app.blueprint(bpv3)\n\n    _, response = app.test_client.get(\"/v1/my_api\")\n    assert response.status == 200\n    assert response.text == \"v1\"\n\n    _, response = app.test_client.get(\"/v2/my_api\")\n    assert response.status == 200\n    assert response.text == \"v2\"\n\n    _, response = app.test_client.get(\"/v3/my_api\")\n    assert response.status == 200\n    assert response.text == \"v3\"\n"
  },
  {
    "path": "tests/test_blueprint_group.py",
    "content": "import pytest\n\nfrom pytest import raises\n\nfrom sanic.app import Sanic\nfrom sanic.blueprint_group import BlueprintGroup\nfrom sanic.blueprints import Blueprint\nfrom sanic.exceptions import BadRequest, Forbidden, SanicException, ServerError\nfrom sanic.request import Request\nfrom sanic.response import HTTPResponse, text\n\n\nMIDDLEWARE_INVOKE_COUNTER = {\"request\": 0, \"response\": 0}\n\nAUTH = \"dGVzdDp0ZXN0Cg==\"\n\n\ndef test_bp_group_indexing(app: Sanic):\n    blueprint_1 = Blueprint(\"blueprint_1\", url_prefix=\"/bp1\")\n    blueprint_2 = Blueprint(\"blueprint_2\", url_prefix=\"/bp2\")\n\n    group = Blueprint.group(blueprint_1, blueprint_2)\n    assert group[0] == blueprint_1\n\n    with raises(expected_exception=IndexError):\n        _ = group[3]\n\n\ndef test_bp_group_set_item_by_index(app: Sanic):\n    blueprint_1 = Blueprint(\"blueprint_1\", url_prefix=\"/bp1\")\n    blueprint_2 = Blueprint(\"blueprint_2\", url_prefix=\"/bp2\")\n\n    group = Blueprint.group(blueprint_1, blueprint_2)\n    group[0] = blueprint_2\n\n    assert group[0] == blueprint_2\n\n\ndef test_bp_group_with_additional_route_params(app: Sanic):\n    blueprint_1 = Blueprint(\"blueprint_1\", url_prefix=\"/bp1\")\n    blueprint_2 = Blueprint(\"blueprint_2\", url_prefix=\"/bp2\")\n\n    @blueprint_1.route(\n        \"/request_path\", methods=frozenset({\"PUT\", \"POST\"}), version=2\n    )\n    def blueprint_1_v2_method_with_put_and_post(request: Request):\n        if request.method == \"PUT\":\n            return text(\"PUT_OK\")\n        elif request.method == \"POST\":\n            return text(\"POST_OK\")\n\n    @blueprint_2.route(\n        \"/route/<param>\", methods=frozenset({\"DELETE\", \"PATCH\"}), name=\"test\"\n    )\n    def blueprint_2_named_method(request: Request, param):\n        if request.method == \"DELETE\":\n            return text(f\"DELETE_{param}\")\n        elif request.method == \"PATCH\":\n            return text(f\"PATCH_{param}\")\n\n    blueprint_group = Blueprint.group(\n        blueprint_1, blueprint_2, url_prefix=\"/api\"\n    )\n\n    @blueprint_group.middleware(\"request\")\n    def authenticate_request(request: Request):\n        global AUTH\n        auth = request.headers.get(\"authorization\")\n        if auth:\n            # Dummy auth check. We can have anything here and it's fine.\n            if AUTH not in auth:\n                return text(\"Unauthorized\", status=401)\n        else:\n            return text(\"Unauthorized\", status=401)\n\n    @blueprint_group.middleware(\"response\")\n    def enhance_response_middleware(request: Request, response: HTTPResponse):\n        response.headers.add(\"x-test-middleware\", \"value\")\n\n    app.blueprint(blueprint_group)\n\n    header = {\"authorization\": \" \".join([\"Basic\", AUTH])}\n    _, response = app.test_client.put(\n        \"/v2/api/bp1/request_path\", headers=header\n    )\n    assert response.text == \"PUT_OK\"\n    assert response.headers.get(\"x-test-middleware\") == \"value\"\n\n    _, response = app.test_client.post(\n        \"/v2/api/bp1/request_path\", headers=header\n    )\n    assert response.text == \"POST_OK\"\n\n    _, response = app.test_client.delete(\"/api/bp2/route/bp2\", headers=header)\n    assert response.text == \"DELETE_bp2\"\n\n    _, response = app.test_client.patch(\"/api/bp2/route/bp2\", headers=header)\n    assert response.text == \"PATCH_bp2\"\n\n    _, response = app.test_client.put(\"/v2/api/bp1/request_path\")\n    assert response.status == 401\n\n\ndef test_bp_group(app: Sanic):\n    blueprint_1 = Blueprint(\"blueprint_1\", url_prefix=\"/bp1\")\n    blueprint_2 = Blueprint(\"blueprint_2\", url_prefix=\"/bp2\")\n\n    @blueprint_1.route(\"/\")\n    def blueprint_1_default_route(request):\n        return text(\"BP1_OK\")\n\n    @blueprint_1.route(\"/invalid\")\n    def blueprint_1_error(request: Request):\n        raise BadRequest(\"Invalid\")\n\n    @blueprint_2.route(\"/\")\n    def blueprint_2_default_route(request):\n        return text(\"BP2_OK\")\n\n    @blueprint_2.route(\"/error\")\n    def blueprint_2_error(request: Request):\n        raise ServerError(\"Error\")\n\n    blueprint_group_1 = Blueprint.group(\n        blueprint_1, blueprint_2, url_prefix=\"/bp\"\n    )\n\n    blueprint_3 = Blueprint(\"blueprint_3\", url_prefix=\"/bp3\")\n\n    @blueprint_group_1.exception(BadRequest)\n    def handle_group_exception(request, exception):\n        return text(\"BP1_ERR_OK\")\n\n    @blueprint_group_1.middleware(\"request\")\n    def blueprint_group_1_middleware(request):\n        global MIDDLEWARE_INVOKE_COUNTER\n        MIDDLEWARE_INVOKE_COUNTER[\"request\"] += 1\n\n    @blueprint_group_1.middleware\n    def blueprint_group_1_middleware_not_called(request):\n        global MIDDLEWARE_INVOKE_COUNTER\n        MIDDLEWARE_INVOKE_COUNTER[\"request\"] += 1\n\n    @blueprint_group_1.on_request\n    def blueprint_group_1_convenience_1(request):\n        global MIDDLEWARE_INVOKE_COUNTER\n        MIDDLEWARE_INVOKE_COUNTER[\"request\"] += 1\n\n    @blueprint_group_1.on_request()\n    def blueprint_group_1_convenience_2(request):\n        global MIDDLEWARE_INVOKE_COUNTER\n        MIDDLEWARE_INVOKE_COUNTER[\"request\"] += 1\n\n    @blueprint_3.route(\"/\")\n    def blueprint_3_default_route(request):\n        return text(\"BP3_OK\")\n\n    @blueprint_3.route(\"/forbidden\")\n    def blueprint_3_forbidden(request: Request):\n        raise Forbidden(\"Forbidden\")\n\n    blueprint_group_2 = Blueprint.group(\n        blueprint_group_1, blueprint_3, url_prefix=\"/api\"\n    )\n\n    @blueprint_group_2.exception(SanicException)\n    def handle_non_handled_exception(request, exception):\n        return text(\"BP2_ERR_OK\")\n\n    @blueprint_group_2.middleware(\"response\")\n    def blueprint_group_2_middleware(request, response):\n        global MIDDLEWARE_INVOKE_COUNTER\n        MIDDLEWARE_INVOKE_COUNTER[\"response\"] += 1\n\n    @blueprint_group_2.on_response\n    def blueprint_group_2_middleware_convenience_1(request, response):\n        global MIDDLEWARE_INVOKE_COUNTER\n        MIDDLEWARE_INVOKE_COUNTER[\"response\"] += 1\n\n    @blueprint_group_2.on_response()\n    def blueprint_group_2_middleware_convenience_2(request, response):\n        global MIDDLEWARE_INVOKE_COUNTER\n        MIDDLEWARE_INVOKE_COUNTER[\"response\"] += 1\n\n    app.blueprint(blueprint_group_2)\n\n    @app.route(\"/\")\n    def app_default_route(request):\n        return text(\"APP_OK\")\n\n    _, response = app.test_client.get(\"/\")\n    assert response.text == \"APP_OK\"\n\n    _, response = app.test_client.get(\"/api/bp/bp1\")\n    assert response.text == \"BP1_OK\"\n\n    _, response = app.test_client.get(\"/api/bp/bp1/invalid\")\n    assert response.text == \"BP1_ERR_OK\"\n\n    _, response = app.test_client.get(\"/api/bp/bp2\")\n    assert response.text == \"BP2_OK\"\n\n    _, response = app.test_client.get(\"/api/bp/bp2/error\")\n    assert response.text == \"BP2_ERR_OK\"\n\n    _, response = app.test_client.get(\"/api/bp3\")\n    assert response.text == \"BP3_OK\"\n\n    _, response = app.test_client.get(\"/api/bp3/forbidden\")\n    assert response.text == \"BP2_ERR_OK\"\n\n    assert MIDDLEWARE_INVOKE_COUNTER[\"response\"] == 18\n    assert MIDDLEWARE_INVOKE_COUNTER[\"request\"] == 16\n\n\ndef test_bp_group_list_operations(app: Sanic):\n    blueprint_1 = Blueprint(\"blueprint_1\", url_prefix=\"/bp1\")\n    blueprint_2 = Blueprint(\"blueprint_2\", url_prefix=\"/bp2\")\n\n    @blueprint_1.route(\"/\")\n    def blueprint_1_default_route(request):\n        return text(\"BP1_OK\")\n\n    @blueprint_2.route(\"/\")\n    def blueprint_2_default_route(request):\n        return text(\"BP2_OK\")\n\n    blueprint_group_1 = Blueprint.group(\n        blueprint_1, blueprint_2, url_prefix=\"/bp\"\n    )\n\n    blueprint_3 = Blueprint(\"blueprint_2\", url_prefix=\"/bp3\")\n\n    @blueprint_3.route(\"/second\")\n    def blueprint_3_second_route(request):\n        return text(\"BP3_OK\")\n\n    assert len(blueprint_group_1) == 2\n\n    blueprint_group_1.append(blueprint_3)\n    assert len(blueprint_group_1) == 3\n\n    del blueprint_group_1[2]\n    assert len(blueprint_group_1) == 2\n\n    blueprint_group_1[1] = blueprint_3\n    assert len(blueprint_group_1) == 2\n\n    assert blueprint_group_1.url_prefix == \"/bp\"\n\n\ndef test_bp_group_as_list():\n    blueprint_1 = Blueprint(\"blueprint_1\", url_prefix=\"/bp1\")\n    blueprint_2 = Blueprint(\"blueprint_2\", url_prefix=\"/bp2\")\n    blueprint_group_1 = Blueprint.group([blueprint_1, blueprint_2])\n    assert len(blueprint_group_1) == 2\n\n\ndef test_bp_group_as_nested_group():\n    blueprint_1 = Blueprint(\"blueprint_1\", url_prefix=\"/bp1\")\n    blueprint_2 = Blueprint(\"blueprint_2\", url_prefix=\"/bp2\")\n    blueprint_group_1 = Blueprint.group(\n        Blueprint.group(blueprint_1, blueprint_2)\n    )\n    assert len(blueprint_group_1) == 1\n\n\ndef test_blueprint_group_insert():\n    blueprint_1 = Blueprint(\n        \"blueprint_1\", url_prefix=\"/bp1\", strict_slashes=True, version=1\n    )\n    blueprint_2 = Blueprint(\"blueprint_2\", url_prefix=\"/bp2\")\n    blueprint_3 = Blueprint(\"blueprint_3\", url_prefix=None)\n    group = BlueprintGroup(\n        url_prefix=\"/test\", version=1.3, strict_slashes=False\n    )\n    group.insert(0, blueprint_1)\n    group.insert(0, blueprint_2)\n    group.insert(0, blueprint_3)\n\n    @blueprint_1.route(\"/\")\n    def blueprint_1_default_route(request):\n        return text(\"BP1_OK\")\n\n    @blueprint_2.route(\"/\")\n    def blueprint_2_default_route(request):\n        return text(\"BP2_OK\")\n\n    @blueprint_3.route(\"/\")\n    def blueprint_3_default_route(request):\n        return text(\"BP3_OK\")\n\n    app = Sanic(\"PropTest\")\n    app.blueprint(group)\n    app.router.finalize()\n\n    routes = [(route.path, route.strict) for route in app.router.routes]\n\n    assert len(routes) == 3\n    assert (\"v1/test/bp1/\", True) in routes\n    assert (\"v1.3/test/bp2\", False) in routes\n    assert (\"v1.3/test\", False) in routes\n\n\ndef test_bp_group_properties():\n    blueprint_1 = Blueprint(\"blueprint_1\", url_prefix=\"/bp1\")\n    blueprint_2 = Blueprint(\"blueprint_2\", url_prefix=\"/bp2\")\n    group = Blueprint.group(\n        blueprint_1,\n        blueprint_2,\n        version=1,\n        version_prefix=\"/api/v\",\n        url_prefix=\"/grouped\",\n        strict_slashes=True,\n    )\n    primary = Blueprint.group(group, url_prefix=\"/primary\")\n\n    @blueprint_1.route(\"/\")\n    def blueprint_1_default_route(request):\n        return text(\"BP1_OK\")\n\n    @blueprint_2.route(\"/\")\n    def blueprint_2_default_route(request):\n        return text(\"BP2_OK\")\n\n    app = Sanic(\"PropTest\")\n    app.blueprint(group)\n    app.blueprint(primary)\n    app.router.finalize()\n\n    routes = [route.path for route in app.router.routes]\n\n    assert len(routes) == 4\n    assert \"api/v1/grouped/bp1/\" in routes\n    assert \"api/v1/grouped/bp2/\" in routes\n    assert \"api/v1/primary/grouped/bp1\" in routes\n    assert \"api/v1/primary/grouped/bp2\" in routes\n\n\ndef test_nested_bp_group_properties():\n    one = Blueprint(\"one\", url_prefix=\"/one\")\n    two = Blueprint.group(one)\n    three = Blueprint.group(two, url_prefix=\"/three\")\n\n    @one.route(\"/four\")\n    def handler(request):\n        return text(\"pi\")\n\n    app = Sanic(\"PropTest\")\n    app.blueprint(three)\n    app.router.finalize()\n\n    routes = [route.path for route in app.router.routes]\n    assert routes == [\"three/one/four\"]\n\n\n@pytest.mark.asyncio\nasync def test_multiple_nested_bp_group():\n    bp1 = Blueprint(\"bp1\", url_prefix=\"/bp1\")\n    bp2 = Blueprint(\"bp2\", url_prefix=\"/bp2\")\n\n    bp1.add_route(lambda _: ..., \"/\", name=\"route1\")\n    bp2.add_route(lambda _: ..., \"/\", name=\"route2\")\n\n    group_a = Blueprint.group(\n        bp1, bp2, url_prefix=\"/group-a\", name_prefix=\"group-a\"\n    )\n    group_b = Blueprint.group(\n        bp1, bp2, url_prefix=\"/group-b\", name_prefix=\"group-b\"\n    )\n\n    app = Sanic(\"PropTest\")\n    app.blueprint(group_a)\n    app.blueprint(group_b)\n\n    await app._startup()\n\n    routes = [route.path for route in app.router.routes]\n    assert routes == [\n        \"group-a/bp1\",\n        \"group-a/bp2\",\n        \"group-b/bp1\",\n        \"group-b/bp2\",\n    ]\n    names = [route.name for route in app.router.routes]\n    assert names == [\n        \"PropTest.group-a_bp1.route1\",\n        \"PropTest.group-a_bp2.route2\",\n        \"PropTest.group-b_bp1.route1\",\n        \"PropTest.group-b_bp2.route2\",\n    ]\n"
  },
  {
    "path": "tests/test_blueprints.py",
    "content": "import asyncio\nimport inspect\nimport os\n\nimport pytest\n\nfrom sanic.app import Sanic\nfrom sanic.blueprints import Blueprint\nfrom sanic.constants import HTTP_METHODS\nfrom sanic.exceptions import BadRequest, NotFound, SanicException, ServerError\nfrom sanic.request import Request\nfrom sanic.response import json, text\n\n\n# ------------------------------------------------------------ #\n#  GET\n# ------------------------------------------------------------ #\n\n\ndef test_bp(app: Sanic):\n    bp = Blueprint(\"test_text\")\n\n    @bp.route(\"/\")\n    def handler(request):\n        return text(\"Hello\")\n\n    app.blueprint(bp)\n    request, response = app.test_client.get(\"/\")\n\n    assert response.text == \"Hello\"\n\n\ndef test_bp_app_access(app: Sanic):\n    bp = Blueprint(\"test\")\n\n    with pytest.raises(\n        SanicException,\n        match=\"<Blueprint test> has not yet been registered to an app\",\n    ):\n        bp.apps\n\n    app.blueprint(bp)\n\n    assert app in bp.apps\n\n\n@pytest.fixture(scope=\"module\")\ndef static_file_directory():\n    \"\"\"The static directory to serve\"\"\"\n    current_file = inspect.getfile(inspect.currentframe())\n    current_directory = os.path.dirname(os.path.abspath(current_file))\n    static_directory = os.path.join(current_directory, \"static\")\n    return static_directory\n\n\ndef get_file_path(static_file_directory, file_name):\n    return os.path.join(static_file_directory, file_name)\n\n\ndef get_file_content(static_file_directory, file_name):\n    \"\"\"The content of the static file to check\"\"\"\n    with open(get_file_path(static_file_directory, file_name), \"rb\") as file:\n        return file.read()\n\n\n@pytest.mark.parametrize(\"method\", HTTP_METHODS)\ndef test_versioned_routes_get(app, method):\n    bp = Blueprint(\"test_text\")\n\n    method = method.lower()\n\n    func = getattr(bp, method)\n    if callable(func):\n\n        @func(f\"/{method}\", version=1)\n        def handler(request):\n            return text(\"OK\")\n\n    else:\n        raise Exception(f\"{func} is not callable\")\n\n    app.blueprint(bp)\n\n    client_method = getattr(app.test_client, method)\n\n    request, response = client_method(f\"/v1/{method}\")\n    assert response.status == 200\n\n\ndef test_bp_strict_slash(app: Sanic):\n    bp = Blueprint(\"test_text\")\n\n    @bp.get(\"/get\", strict_slashes=True)\n    def get_handler(request):\n        return text(\"OK\")\n\n    @bp.post(\"/post/\", strict_slashes=True)\n    def post_handler(request):\n        return text(\"OK\")\n\n    app.blueprint(bp)\n\n    request, response = app.test_client.get(\"/get\")\n    assert response.text == \"OK\"\n    assert response.json is None\n\n    request, response = app.test_client.get(\"/get/\")\n    assert response.status == 404\n\n    request, response = app.test_client.post(\"/post/\")\n    assert response.text == \"OK\"\n\n    request, response = app.test_client.post(\"/post\")\n    assert response.status == 404\n\n\ndef test_bp_strict_slash_default_value(app: Sanic):\n    bp = Blueprint(\"test_text\", strict_slashes=True)\n\n    @bp.get(\"/get\")\n    def get_handler(request):\n        return text(\"OK\")\n\n    @bp.post(\"/post/\")\n    def post_handler(request):\n        return text(\"OK\")\n\n    app.blueprint(bp)\n\n    request, response = app.test_client.get(\"/get/\")\n    assert response.status == 404\n\n    request, response = app.test_client.post(\"/post\")\n    assert response.status == 404\n\n\ndef test_bp_strict_slash_without_passing_default_value(app: Sanic):\n    bp = Blueprint(\"test_text\")\n\n    @bp.get(\"/get\")\n    def get_handler(request):\n        return text(\"OK\")\n\n    @bp.post(\"/post/\")\n    def post_handler(request):\n        return text(\"OK\")\n\n    app.blueprint(bp)\n\n    request, response = app.test_client.get(\"/get/\")\n    assert response.text == \"OK\"\n\n    request, response = app.test_client.post(\"/post\")\n    assert response.text == \"OK\"\n\n\ndef test_bp_strict_slash_default_value_can_be_overwritten(app: Sanic):\n    bp = Blueprint(\"test_text\", strict_slashes=True)\n\n    @bp.get(\"/get\", strict_slashes=False)\n    def get_handler(request):\n        return text(\"OK\")\n\n    @bp.post(\"/post/\", strict_slashes=False)\n    def post_handler(request):\n        return text(\"OK\")\n\n    app.blueprint(bp)\n\n    request, response = app.test_client.get(\"/get/\")\n    assert response.text == \"OK\"\n\n    request, response = app.test_client.post(\"/post\")\n    assert response.text == \"OK\"\n\n\ndef test_bp_with_url_prefix(app: Sanic):\n    bp = Blueprint(\"test_text\", url_prefix=\"/test1\")\n\n    @bp.route(\"/\")\n    def handler(request):\n        return text(\"Hello\")\n\n    app.blueprint(bp)\n    request, response = app.test_client.get(\"/test1/\")\n\n    assert response.text == \"Hello\"\n\n\ndef test_several_bp_with_url_prefix(app: Sanic):\n    bp = Blueprint(\"test_text\", url_prefix=\"/test1\")\n    bp2 = Blueprint(\"test_text2\", url_prefix=\"/test2\")\n\n    @bp.route(\"/\")\n    def handler(request):\n        return text(\"Hello\")\n\n    @bp2.route(\"/\")\n    def handler2(request):\n        return text(\"Hello2\")\n\n    app.blueprint(bp)\n    app.blueprint(bp2)\n    request, response = app.test_client.get(\"/test1/\")\n    assert response.text == \"Hello\"\n\n    request, response = app.test_client.get(\"/test2/\")\n    assert response.text == \"Hello2\"\n\n\ndef test_bp_with_host(app: Sanic):\n    bp = Blueprint(\"test_bp_host\", url_prefix=\"/test1\", host=\"example.com\")\n\n    @bp.route(\"/\")\n    def handler1(request):\n        return text(\"Hello\")\n\n    @bp.route(\"/\", host=\"sub.example.com\")\n    def handler2(request):\n        return text(\"Hello subdomain!\")\n\n    app.blueprint(bp)\n    headers = {\"Host\": \"example.com\"}\n\n    request, response = app.test_client.get(\"/test1/\", headers=headers)\n    assert response.text == \"Hello\"\n\n    headers = {\"Host\": \"sub.example.com\"}\n    request, response = app.test_client.get(\"/test1/\", headers=headers)\n    assert response.body == b\"Hello subdomain!\"\n\n\ndef test_several_bp_with_host(app: Sanic):\n    bp = Blueprint(\n        \"test_text\",\n        url_prefix=\"/test\",\n        host=\"example.com\",\n        strict_slashes=True,\n    )\n    bp2 = Blueprint(\n        \"test_text2\",\n        url_prefix=\"/test\",\n        host=\"sub.example.com\",\n        strict_slashes=True,\n    )\n\n    @bp.route(\"/\")\n    def handler(request):\n        return text(\"Hello\")\n\n    @bp2.route(\"/\")\n    def handler1(request):\n        return text(\"Hello2\")\n\n    @bp2.route(\"/other/\")\n    def handler2(request):\n        return text(\"Hello3\")\n\n    app.blueprint(bp)\n    app.blueprint(bp2)\n\n    assert bp.host == \"example.com\"\n    headers = {\"Host\": \"example.com\"}\n    request, response = app.test_client.get(\"/test/\", headers=headers)\n\n    assert response.text == \"Hello\"\n\n    assert bp2.host == \"sub.example.com\"\n    headers = {\"Host\": \"sub.example.com\"}\n    request, response = app.test_client.get(\"/test/\", headers=headers)\n\n    assert response.text == \"Hello2\"\n    request, response = app.test_client.get(\"/test/other/\", headers=headers)\n    assert response.text == \"Hello3\"\n\n\ndef test_bp_with_host_list(app: Sanic):\n    bp = Blueprint(\n        \"test_bp_host\",\n        url_prefix=\"/test1\",\n        host=[\"example.com\", \"sub.example.com\"],\n    )\n\n    @bp.route(\"/\")\n    def handler1(request):\n        return text(\"Hello\")\n\n    @bp.route(\"/\", host=[\"sub1.example.com\"])\n    def handler2(request):\n        return text(\"Hello subdomain!\")\n\n    app.blueprint(bp)\n    headers = {\"Host\": \"example.com\"}\n    request, response = app.test_client.get(\"/test1/\", headers=headers)\n    assert response.text == \"Hello\"\n\n    headers = {\"Host\": \"sub.example.com\"}\n    request, response = app.test_client.get(\"/test1/\", headers=headers)\n    assert response.text == \"Hello\"\n\n    headers = {\"Host\": \"sub1.example.com\"}\n    request, response = app.test_client.get(\"/test1/\", headers=headers)\n\n    assert response.text == \"Hello subdomain!\"\n\n    route_names = [r.name for r in app.router.routes]\n    assert \"test_bp_with_host_list.test_bp_host.handler1\" in route_names\n    assert \"test_bp_with_host_list.test_bp_host.handler2\" in route_names\n\n\ndef test_several_bp_with_host_list(app: Sanic):\n    bp = Blueprint(\n        \"test_text\",\n        url_prefix=\"/test\",\n        host=[\"example.com\", \"sub.example.com\"],\n    )\n    bp2 = Blueprint(\n        \"test_text2\",\n        url_prefix=\"/test\",\n        host=[\"sub1.example.com\", \"sub2.example.com\"],\n    )\n\n    @bp.route(\"/\")\n    def handler(request):\n        return text(\"Hello\")\n\n    @bp2.route(\"/\")\n    def handler1(request):\n        return text(\"Hello2\")\n\n    @bp2.route(\"/other/\")\n    def handler2(request):\n        return text(\"Hello3\")\n\n    app.blueprint(bp)\n    app.blueprint(bp2)\n\n    assert bp.host == [\"example.com\", \"sub.example.com\"]\n    headers = {\"Host\": \"example.com\"}\n    request, response = app.test_client.get(\"/test/\", headers=headers)\n    assert response.text == \"Hello\"\n\n    assert bp.host == [\"example.com\", \"sub.example.com\"]\n    headers = {\"Host\": \"sub.example.com\"}\n    request, response = app.test_client.get(\"/test/\", headers=headers)\n    assert response.text == \"Hello\"\n\n    assert bp2.host == [\"sub1.example.com\", \"sub2.example.com\"]\n    headers = {\"Host\": \"sub1.example.com\"}\n    request, response = app.test_client.get(\"/test/\", headers=headers)\n    assert response.text == \"Hello2\"\n    request, response = app.test_client.get(\"/test/other/\", headers=headers)\n    assert response.text == \"Hello3\"\n\n    assert bp2.host == [\"sub1.example.com\", \"sub2.example.com\"]\n    headers = {\"Host\": \"sub2.example.com\"}\n    request, response = app.test_client.get(\"/test/\", headers=headers)\n    assert response.text == \"Hello2\"\n    request, response = app.test_client.get(\"/test/other/\", headers=headers)\n    assert response.text == \"Hello3\"\n\n\ndef test_bp_middleware(app: Sanic):\n    blueprint = Blueprint(\"test_bp_middleware\")\n\n    @blueprint.middleware(\"response\")\n    async def process_response(request, response):\n        return text(\"OK\")\n\n    @app.route(\"/\")\n    async def handler(request):\n        return text(\"FAIL\")\n\n    app.blueprint(blueprint)\n\n    request, response = app.test_client.get(\"/\")\n\n    assert response.status == 200\n    assert response.text == \"FAIL\"\n\n\ndef test_bp_middleware_with_route(app: Sanic):\n    blueprint = Blueprint(\"test_bp_middleware\")\n\n    @blueprint.middleware(\"response\")\n    async def process_response(request, response):\n        return text(\"OK\")\n\n    @app.route(\"/\")\n    async def handler(request):\n        return text(\"FAIL\")\n\n    @blueprint.route(\"/bp\")\n    async def bp_handler(request):\n        return text(\"FAIL\")\n\n    app.blueprint(blueprint)\n\n    request, response = app.test_client.get(\"/bp\")\n\n    assert response.status == 200\n    assert response.text == \"OK\"\n\n\ndef test_bp_middleware_order(app: Sanic):\n    blueprint = Blueprint(\"test_bp_middleware_order\")\n    order = []\n\n    @blueprint.middleware(\"request\")\n    def mw_1(request):\n        order.append(1)\n\n    @blueprint.middleware(\"request\")\n    def mw_2(request):\n        order.append(2)\n\n    @blueprint.middleware(\"request\")\n    def mw_3(request):\n        order.append(3)\n\n    @blueprint.middleware(\"response\")\n    def mw_4(request, response):\n        order.append(6)\n\n    @blueprint.middleware(\"response\")\n    def mw_5(request, response):\n        order.append(5)\n\n    @blueprint.middleware(\"response\")\n    def mw_6(request, response):\n        order.append(4)\n\n    @blueprint.route(\"/\")\n    def process_response(request):\n        return text(\"OK\")\n\n    app.blueprint(blueprint)\n    order.clear()\n    request, response = app.test_client.get(\"/\")\n\n    assert response.status == 200\n    assert order == [1, 2, 3, 4, 5, 6]\n\n\ndef test_bp_exception_handler(app: Sanic):\n    blueprint = Blueprint(\"test_middleware\")\n\n    @blueprint.route(\"/1\")\n    def handler_1(request):\n        raise BadRequest(\"OK\")\n\n    @blueprint.route(\"/2\")\n    def handler_2(request):\n        raise ServerError(\"OK\")\n\n    @blueprint.route(\"/3\")\n    def handler_3(request):\n        raise NotFound(\"OK\")\n\n    @blueprint.exception(NotFound, ServerError)\n    def handler_exception(request, exception):\n        return text(\"OK\")\n\n    app.blueprint(blueprint)\n\n    request, response = app.test_client.get(\"/1\")\n    assert response.status == 400\n\n    request, response = app.test_client.get(\"/2\")\n    assert response.status == 200\n    assert response.text == \"OK\"\n\n    request, response = app.test_client.get(\"/3\")\n    assert response.status == 200\n\n\ndef test_bp_exception_handler_applied(app: Sanic):\n    class Error(Exception):\n        pass\n\n    handled = Blueprint(\"handled\")\n    nothandled = Blueprint(\"nothandled\")\n\n    @handled.exception(Error)\n    def handle_error(req, e):\n        return text(f\"handled {e}\")\n\n    @handled.route(\"/ok\")\n    def ok(request):\n        raise Error(\"uh oh\")\n\n    @nothandled.route(\"/notok\")\n    def notok(request):\n        raise Error(\"uh oh\")\n\n    app.blueprint(handled)\n    app.blueprint(nothandled)\n\n    _, response = app.test_client.get(\"/ok\")\n    assert response.status == 200\n    assert response.text == \"handled uh oh\"\n\n    _, response = app.test_client.get(\"/notok\")\n    assert response.status == 500\n\n\ndef test_bp_exception_handler_not_applied(app: Sanic):\n    class Error(Exception):\n        pass\n\n    handled = Blueprint(\"handled\")\n    nothandled = Blueprint(\"nothandled\")\n\n    @handled.exception(Error)\n    def handle_error(req, e):\n        return text(f\"handled {e}\")\n\n    @nothandled.route(\"/notok\")\n    def notok(request):\n        raise Error(\"uh oh\")\n\n    app.blueprint(handled)\n    app.blueprint(nothandled)\n\n    _, response = app.test_client.get(\"/notok\")\n    assert response.status == 500\n\n\ndef test_bp_listeners(app: Sanic):\n    app.route(\"/\")(lambda x: x)\n    blueprint = Blueprint(\"test_middleware\")\n\n    order = []\n\n    @blueprint.listener(\"before_server_start\")\n    def handler_1(sanic, loop):\n        order.append(1)\n\n    @blueprint.listener(\"after_server_start\")\n    def handler_2(sanic, loop):\n        order.append(2)\n\n    @blueprint.listener(\"after_server_start\")\n    def handler_3(sanic, loop):\n        order.append(3)\n\n    @blueprint.listener(\"before_server_stop\")\n    def handler_4(sanic, loop):\n        order.append(5)\n\n    @blueprint.listener(\"before_server_stop\")\n    def handler_5(sanic, loop):\n        order.append(4)\n\n    @blueprint.listener(\"after_server_stop\")\n    def handler_6(sanic, loop):\n        order.append(6)\n\n    app.blueprint(blueprint)\n\n    request, response = app.test_client.get(\"/\")\n\n    assert order == [1, 2, 3, 4, 5, 6]\n\n\ndef test_bp_static(app: Sanic):\n    current_file = inspect.getfile(inspect.currentframe())\n    with open(current_file, \"rb\") as file:\n        current_file_contents = file.read()\n\n    blueprint = Blueprint(\"test_static\")\n\n    blueprint.static(\"/testing.file\", current_file)\n\n    app.blueprint(blueprint)\n\n    request, response = app.test_client.get(\"/testing.file\")\n    assert response.status == 200\n    assert response.body == current_file_contents\n\n\n@pytest.mark.parametrize(\"file_name\", [\"test.html\"])\ndef test_bp_static_content_type(app, file_name):\n    # This is done here, since no other test loads a file here\n    current_file = inspect.getfile(inspect.currentframe())\n    current_directory = os.path.dirname(os.path.abspath(current_file))\n    static_directory = os.path.join(current_directory, \"static\")\n\n    blueprint = Blueprint(\"test_static\")\n    blueprint.static(\n        \"/testing.file\",\n        get_file_path(static_directory, file_name),\n        content_type=\"text/html; charset=utf-8\",\n    )\n\n    app.blueprint(blueprint)\n\n    request, response = app.test_client.get(\"/testing.file\")\n    assert response.status == 200\n    assert response.body == get_file_content(static_directory, file_name)\n    assert response.headers[\"Content-Type\"] == \"text/html; charset=utf-8\"\n\n\ndef test_bp_shorthand(app: Sanic):\n    blueprint = Blueprint(\"test_shorhand_routes\")\n    ev = asyncio.Event()\n\n    @blueprint.get(\"/get\")\n    def handler(request):\n        return text(\"OK\")\n\n    @blueprint.put(\"/put\")\n    def put_handler(request):\n        return text(\"OK\")\n\n    @blueprint.post(\"/post\")\n    def post_handler(request):\n        return text(\"OK\")\n\n    @blueprint.head(\"/head\")\n    def head_handler(request):\n        return text(\"OK\")\n\n    @blueprint.options(\"/options\")\n    def options_handler(request):\n        return text(\"OK\")\n\n    @blueprint.patch(\"/patch\")\n    def patch_handler(request):\n        return text(\"OK\")\n\n    @blueprint.delete(\"/delete\")\n    def delete_handler(request):\n        return text(\"OK\")\n\n    @blueprint.websocket(\"/ws/\", strict_slashes=True)\n    async def websocket_handler(request, ws):\n        ev.set()\n\n    app.blueprint(blueprint)\n\n    request, response = app.test_client.get(\"/get\")\n    assert response.body == b\"OK\"\n\n    request, response = app.test_client.post(\"/get\")\n    assert response.status == 405\n\n    request, response = app.test_client.put(\"/put\")\n    assert response.body == b\"OK\"\n\n    request, response = app.test_client.get(\"/post\")\n    assert response.status == 405\n\n    request, response = app.test_client.post(\"/post\")\n    assert response.body == b\"OK\"\n\n    request, response = app.test_client.get(\"/post\")\n    assert response.status == 405\n\n    request, response = app.test_client.head(\"/head\")\n    assert response.status == 200\n\n    request, response = app.test_client.get(\"/head\")\n    assert response.status == 405\n\n    request, response = app.test_client.options(\"/options\")\n    assert response.body == b\"OK\"\n\n    request, response = app.test_client.get(\"/options\")\n    assert response.status == 405\n\n    request, response = app.test_client.patch(\"/patch\")\n    assert response.body == b\"OK\"\n\n    request, response = app.test_client.get(\"/patch\")\n    assert response.status == 405\n\n    request, response = app.test_client.delete(\"/delete\")\n    assert response.body == b\"OK\"\n\n    request, response = app.test_client.get(\"/delete\")\n    assert response.status == 405\n\n    request, response = app.test_client.websocket(\"/ws/\")\n    assert response.opened is True\n    assert ev.is_set()\n\n\ndef test_bp_group(app: Sanic):\n    deep_0 = Blueprint(\"deep_0\", url_prefix=\"/deep\")\n    deep_1 = Blueprint(\"deep_1\", url_prefix=\"/deep1\")\n\n    @deep_0.route(\"/\")\n    def handler(request):\n        return text(\"D0_OK\")\n\n    @deep_1.route(\"/bottom\")\n    def bottom_handler(request):\n        return text(\"D1B_OK\")\n\n    mid_0 = Blueprint.group(deep_0, deep_1, url_prefix=\"/mid\")\n    mid_1 = Blueprint(\"mid_tier\", url_prefix=\"/mid1\")\n\n    @mid_1.route(\"/\")\n    def handler1(request):\n        return text(\"M1_OK\")\n\n    top = Blueprint.group(mid_0, mid_1)\n\n    app.blueprint(top)\n\n    @app.route(\"/\")\n    def handler2(request):\n        return text(\"TOP_OK\")\n\n    request, response = app.test_client.get(\"/\")\n    assert response.text == \"TOP_OK\"\n\n    request, response = app.test_client.get(\"/mid1\")\n    assert response.text == \"M1_OK\"\n\n    request, response = app.test_client.get(\"/mid/deep\")\n    assert response.text == \"D0_OK\"\n\n    request, response = app.test_client.get(\"/mid/deep1/bottom\")\n    assert response.text == \"D1B_OK\"\n\n\ndef test_bp_group_with_default_url_prefix(app: Sanic):\n    from sanic.response import json\n\n    bp_resources = Blueprint(\"bp_resources\")\n\n    @bp_resources.get(\"/\")\n    def list_resources_handler(request):\n        resource = {}\n        return json([resource])\n\n    bp_resource = Blueprint(\"bp_resource\", url_prefix=\"/<resource_id>\")\n\n    @bp_resource.get(\"/\")\n    def get_resource_hander(request, resource_id):\n        resource = {\"resource_id\": resource_id}\n        return json(resource)\n\n    bp_resources_group = Blueprint.group(\n        bp_resources, bp_resource, url_prefix=\"/resources\"\n    )\n    bp_api_v1 = Blueprint(\"bp_api_v1\")\n\n    @bp_api_v1.get(\"/info\")\n    def api_v1_info(request):\n        return text(\"api_version: v1\")\n\n    bp_api_v1_group = Blueprint.group(\n        bp_api_v1, bp_resources_group, url_prefix=\"/v1\"\n    )\n    bp_api_group = Blueprint.group(bp_api_v1_group, url_prefix=\"/api\")\n    app.blueprint(bp_api_group)\n\n    request, response = app.test_client.get(\"/api/v1/info\")\n    assert response.text == \"api_version: v1\"\n\n    request, response = app.test_client.get(\"/api/v1/resources\")\n    assert response.json == [{}]\n\n    from uuid import uuid4\n\n    resource_id = str(uuid4())\n    request, response = app.test_client.get(f\"/api/v1/resources/{resource_id}\")\n    assert response.json == {\"resource_id\": resource_id}\n\n\ndef test_blueprint_middleware_with_args(app: Sanic):\n    bp = Blueprint(name=\"with_args_bp\", url_prefix=\"/wa\")\n\n    @bp.middleware\n    def middleware_with_no_tag(request: Request):\n        if request.headers.get(\"content-type\") == \"application/json\":\n            request.headers[\"accepts\"] = \"plain/text\"\n        else:\n            request.headers[\"accepts\"] = \"application/json\"\n\n    @bp.route(\"/\")\n    def default_route(request):\n        if request.headers.get(\"accepts\") == \"application/json\":\n            return json({\"test\": \"value\"})\n        else:\n            return text(\"value\")\n\n    app.blueprint(bp)\n\n    _, response = app.test_client.get(\n        \"/wa\", headers={\"content-type\": \"application/json\"}\n    )\n    assert response.text == \"value\"\n\n    _, response = app.test_client.get(\n        \"/wa\", headers={\"content-type\": \"plain/text\"}\n    )\n    assert response.json.get(\"test\") == \"value\"\n\n\n@pytest.mark.parametrize(\"file_name\", [\"test.file\"])\ndef test_static_blueprint_name(static_file_directory, file_name):\n    app = Sanic(\"app\")\n    current_file = inspect.getfile(inspect.currentframe())\n    with open(current_file, \"rb\") as file:\n        file.read()\n\n    bp = Blueprint(name=\"static\", url_prefix=\"/static\", strict_slashes=False)\n\n    bp.static(\n        \"/test.file/\",\n        get_file_path(static_file_directory, file_name),\n        name=\"static.testing\",\n        strict_slashes=True,\n    )\n\n    app.blueprint(bp)\n\n    uri = app.url_for(\"static\", name=\"static.testing\")\n    assert uri == \"/static/test.file/\"\n\n    _, response = app.test_client.get(\"/static/test.file\")\n    assert response.status == 404\n\n    _, response = app.test_client.get(\"/static/test.file/\")\n    assert response.status == 200\n\n\n@pytest.mark.parametrize(\"file_name\", [\"test.file\"])\ndef test_static_blueprintp_mw(app: Sanic, static_file_directory, file_name):\n    current_file = inspect.getfile(inspect.currentframe())  # type: ignore\n    with open(current_file, \"rb\") as file:\n        file.read()\n\n    triggered = False\n\n    bp = Blueprint(name=\"test_mw\", url_prefix=\"\")\n\n    @bp.middleware(\"request\")\n    def bp_mw1(request):\n        nonlocal triggered\n        triggered = True\n\n    bp.static(\n        \"/test.file\",\n        get_file_path(static_file_directory, file_name),\n        strict_slashes=True,\n        name=\"static\",\n    )\n\n    app.blueprint(bp)\n\n    uri = app.url_for(\"test_mw.static\")\n    assert uri == \"/test.file\"\n\n    _, response = app.test_client.get(\"/test.file\")\n    assert triggered is True\n\n\ndef test_websocket_route(app: Sanic):\n    event = asyncio.Event()\n\n    async def websocket_handler(request, ws):\n        assert ws.subprotocol is None\n        event.set()\n\n    bp = Blueprint(name=\"handler\", url_prefix=\"/ws\")\n    bp.add_websocket_route(websocket_handler, \"/test\", name=\"test\")\n\n    app.blueprint(bp)\n\n    _, response = app.test_client.websocket(\"/ws/test\")\n    assert response.opened is True\n    assert event.is_set()\n\n\ndef test_duplicate_blueprint(app: Sanic):\n    bp_name = \"bp\"\n    bp = Blueprint(bp_name)\n    bp1 = Blueprint(bp_name)\n\n    app.blueprint(bp)\n\n    with pytest.raises(AssertionError) as excinfo:\n        app.blueprint(bp1)\n\n    assert str(excinfo.value) == (\n        f'A blueprint with the name \"{bp_name}\" is already registered.  '\n        \"Blueprint names must be unique.\"\n    )\n\n\ndef test_strict_slashes_behavior_adoption():\n    app = Sanic(\"app\")\n    app.strict_slashes = True\n    bp = Blueprint(\"bp\")\n    bp2 = Blueprint(\"bp2\", strict_slashes=False)\n\n    @app.get(\"/test\")\n    def handler_test(request):\n        return text(\"Test\")\n\n    @app.get(\"/f1\", strict_slashes=False)\n    def f1(request):\n        return text(\"f1\")\n\n    @bp.get(\"/one\", strict_slashes=False)\n    def one(request):\n        return text(\"one\")\n\n    @bp.get(\"/second\")\n    def second(request):\n        return text(\"second\")\n\n    @bp2.get(\"/third\")\n    def third(request):\n        return text(\"third\")\n\n    app.blueprint(bp)\n    app.blueprint(bp2)\n\n    assert app.test_client.get(\"/test\")[1].status == 200\n    assert app.test_client.get(\"/test/\")[1].status == 404\n\n    assert app.test_client.get(\"/one\")[1].status == 200\n    assert app.test_client.get(\"/one/\")[1].status == 200\n\n    assert app.test_client.get(\"/second\")[1].status == 200\n    assert app.test_client.get(\"/second/\")[1].status == 404\n\n    assert app.test_client.get(\"/third\")[1].status == 200\n    assert app.test_client.get(\"/third/\")[1].status == 200\n\n    assert app.test_client.get(\"/f1\")[1].status == 200\n    assert app.test_client.get(\"/f1/\")[1].status == 200\n\n\ndef test_blueprint_group_versioning():\n    app = Sanic(name=\"blueprint-group-test\")\n\n    bp1 = Blueprint(name=\"bp1\", url_prefix=\"/bp1\")\n    bp2 = Blueprint(name=\"bp2\", url_prefix=\"/bp2\", version=2)\n\n    bp3 = Blueprint(name=\"bp3\", url_prefix=\"/bp3\")\n\n    @bp3.get(\"/r1\")\n    async def bp3_r1(request):\n        return json({\"from\": \"bp3/r1\"})\n\n    @bp1.get(\"/pre-group\")\n    async def pre_group(request):\n        return json({\"from\": \"bp1/pre-group\"})\n\n    group = Blueprint.group([bp1, bp2], url_prefix=\"/group1\", version=1)\n\n    group2 = Blueprint.group([bp3])\n\n    @bp1.get(\"/r1\")\n    async def r1(request):\n        return json({\"from\": \"bp1/r1\"})\n\n    @bp2.get(\"/r2\")\n    async def r2(request):\n        return json({\"from\": \"bp2/r2\"})\n\n    @bp2.get(\"/r3\", version=3)\n    async def r3(request):\n        return json({\"from\": \"bp2/r3\"})\n\n    app.blueprint([group, group2])\n\n    assert app.test_client.get(\"/v1/group1/bp1/r1/\")[1].status == 200\n    assert app.test_client.get(\"/v2/group1/bp2/r2\")[1].status == 200\n    assert app.test_client.get(\"/v1/group1/bp1/pre-group\")[1].status == 200\n    assert app.test_client.get(\"/v3/group1/bp2/r3\")[1].status == 200\n    assert app.test_client.get(\"/bp3/r1\")[1].status == 200\n\n    assert group.version == 1\n    assert group2.strict_slashes is None\n\n\ndef test_blueprint_group_strict_slashes():\n    app = Sanic(name=\"blueprint-group-test\")\n    bp1 = Blueprint(name=\"bp1\", url_prefix=None, strict_slashes=False)\n\n    bp2 = Blueprint(\n        name=\"bp2\", version=3, url_prefix=\"/bp2\", strict_slashes=None\n    )\n\n    bp3 = Blueprint(\n        name=\"bp3\", version=None, url_prefix=\"/bp3/\", strict_slashes=None\n    )\n\n    @bp1.get(\"/r1\")\n    async def bp1_r1(request):\n        return json({\"from\": \"bp1/r1\"})\n\n    @bp2.get(\"/r1\")\n    async def bp2_r1(request):\n        return json({\"from\": \"bp2/r1\"})\n\n    @bp2.get(\"/r2/\")\n    async def bp2_r2(request):\n        return json({\"from\": \"bp2/r2\"})\n\n    @bp3.get(\"/r1\")\n    async def bp3_r1(request):\n        return json({\"from\": \"bp3/r1\"})\n\n    group = Blueprint.group(\n        [bp1, bp2],\n        url_prefix=\"/slash-check/\",\n        version=1.3,\n        strict_slashes=True,\n    )\n\n    group2 = Blueprint.group(\n        [bp3], url_prefix=\"/other-prefix/\", version=\"v2\", strict_slashes=False\n    )\n\n    app.blueprint(group)\n    app.blueprint(group2)\n\n    assert app.test_client.get(\"/v1.3/slash-check/r1\")[1].status == 200\n    assert app.test_client.get(\"/v1.3/slash-check/r1/\")[1].status == 200\n    assert app.test_client.get(\"/v3/slash-check/bp2/r1\")[1].status == 200\n    assert app.test_client.get(\"/v3/slash-check/bp2/r1/\")[1].status == 404\n    assert app.test_client.get(\"/v3/slash-check/bp2/r2\")[1].status == 404\n    assert app.test_client.get(\"/v3/slash-check/bp2/r2/\")[1].status == 200\n    assert app.test_client.get(\"/v2/other-prefix/bp3/r1\")[1].status == 200\n\n\ndef test_blueprint_registered_multiple_apps():\n    app1 = Sanic(\"app1\")\n    app2 = Sanic(\"app2\")\n    bp = Blueprint(\"bp\")\n\n    @bp.get(\"/\")\n    async def handler(request):\n        return text(request.route.name)\n\n    app1.blueprint(bp)\n    app2.blueprint(bp)\n\n    for app in (app1, app2):\n        _, response = app.test_client.get(\"/\")\n        assert response.text == f\"{app.name}.bp.handler\"\n\n\ndef test_bp_set_attribute_warning():\n    bp = Blueprint(\"bp\")\n    message = (\n        \"Setting variables on Blueprint instances is not allowed. You should \"\n        \"change your Blueprint instance to use instance.ctx.foo instead.\"\n    )\n    with pytest.raises(AttributeError, match=message):\n        bp.foo = 1\n\n\ndef test_early_registration(app: Sanic):\n    assert len(app.router.routes) == 0\n\n    bp = Blueprint(\"bp\")\n\n    @bp.get(\"/one\")\n    async def one(_):\n        return text(\"one\")\n\n    app.blueprint(bp)\n\n    assert len(app.router.routes) == 1\n\n    @bp.get(\"/two\")\n    async def two(_):\n        return text(\"two\")\n\n    @bp.get(\"/three\")\n    async def three(_):\n        return text(\"three\")\n\n    assert len(app.router.routes) == 3\n\n    for path in (\"one\", \"two\", \"three\"):\n        _, response = app.test_client.get(f\"/{path}\")\n        assert response.text == path\n\n\ndef test_remove_double_slashes_defined_on_bp(app: Sanic):\n    bp = Blueprint(\"bp\", url_prefix=\"/foo/\", strict_slashes=True)\n\n    @bp.get(\"/\")\n    async def handler(_): ...\n\n    app.blueprint(bp)\n    app.router.finalize()\n\n    assert app.router.routes[0].path == \"foo/\"\n\n\ndef test_remove_double_slashes_defined_on_register(app: Sanic):\n    bp = Blueprint(\"bp\")\n\n    @bp.get(\"/\")\n    async def index(_): ...\n\n    app.blueprint(bp, url_prefix=\"/foo/\", strict_slashes=True)\n    app.router.finalize()\n\n    assert app.router.routes[0].path == \"foo/\"\n\n\ndef test_blueprint_copy_returns_blueprint_with_the_name_of_original_blueprint(\n    app: Sanic,\n):\n    # arrange\n    bp = Blueprint(\"bp\")\n\n    # act\n    actual = bp.copy(\"new_bp_name\")\n\n    # assert\n    assert bp.name == actual.copied_from\n\n\ndef test_blueprint_copy_returns_blueprint_with_overwritten_properties(\n    app: Sanic,\n):\n    # arrange\n    bp = Blueprint(\"bp\")\n    to_override_attrs = expected = dict(\n        url_prefix=\"v2\",\n        version=\"v2\",\n        version_prefix=\"v2\",\n        allow_route_overwrite=True,\n        strict_slashes=True,\n    )\n\n    # act\n    actual = bp.copy(\n        \"new_bp_name\",\n        **to_override_attrs,\n    )\n\n    # assert\n    assert all(\n        value == getattr(actual, key)\n        for key, value in expected.items()\n        if hasattr(actual, key)\n    )\n"
  },
  {
    "path": "tests/test_cancellederror.py",
    "content": "from asyncio import CancelledError\n\nfrom sanic import Request, Sanic, json\n\n\ndef test_can_raise_in_handler(app: Sanic):\n    @app.get(\"/\")\n    async def handler(request: Request):\n        raise CancelledError(\"STOP!!\")\n\n    @app.exception(CancelledError)\n    async def handle_cancel(request: Request, exc: CancelledError):\n        return json({\"message\": exc.args[0]}, status=418)\n\n    _, response = app.test_client.get(\"/\")\n    assert response.status == 418\n    assert response.json[\"message\"] == \"STOP!!\"\n"
  },
  {
    "path": "tests/test_cli.py",
    "content": "import json\nimport os\nimport sys\n\nfrom pathlib import Path\nfrom typing import Optional\nfrom unittest.mock import patch\n\nimport pytest\n\nfrom sanic_routing import __version__ as __routing_version__\n\nfrom sanic import __version__\nfrom sanic.__main__ import main\nfrom sanic.cli.inspector_client import InspectorClient\nfrom sanic.models.ctx_types import REPLLocal\n\nfrom .conftest import get_port\n\n\n@pytest.fixture(scope=\"module\", autouse=True)\ndef tty():\n    orig = sys.stdout.isatty\n    sys.stdout.isatty = lambda: False\n    yield\n    sys.stdout.isatty = orig\n\n\ndef capture(command: list[str], caplog=None, capsys=None):\n    if capsys:\n        capsys.readouterr()\n    if caplog:\n        caplog.clear()\n    os.chdir(Path(__file__).parent)\n    try:\n        main(command)\n    except SystemExit:\n        ...\n    if capsys:\n        captured_err = capsys.readouterr()\n        return captured_err\n    if caplog:\n        return [record.message for record in caplog.records]\n    return None\n\n\ndef read_app_info(lines: list[str]):\n    for line in lines:\n        if line.startswith(\"{\") and line.endswith(\"}\"):  # type: ignore\n            return json.loads(line)\n\n\n@pytest.mark.parametrize(\n    \"appname,extra\",\n    (\n        (\"fake.server.app\", None),\n        (\"fake.server\", None),\n        (\"fake.server:create_app\", \"--factory\"),\n        (\"fake.server.create_app()\", None),\n        (\"fake.server.create_app\", None),\n    ),\n)\ndef test_server_run(\n    appname: str, extra: Optional[str], caplog: pytest.LogCaptureFixture, port\n):\n    command = [appname, f\"-p={port}\"]\n    if extra:\n        command.append(extra)\n    lines = capture(command, caplog)\n\n    assert f\"Goin' Fast @ http://127.0.0.1:{port}\" in lines\n\n\n@pytest.mark.parametrize(\n    \"command\",\n    (\n        [\"fake.server.create_app_with_args\", \"--factory\"],\n        [\"fake.server.create_app_with_args\"],\n    ),\n)\ndef test_server_run_factory_with_args(caplog, command, port):\n    command.append(f\"-p={port}\")\n    lines = capture(command, caplog)\n\n    assert \"target=fake.server.create_app_with_args\" in lines\n\n\ndef test_server_run_factory_with_args_arbitrary(caplog, port):\n    command = [\n        \"fake.server.create_app_with_args\",\n        \"--factory\",\n        \"--foo=bar\",\n        f\"-p={port}\",\n    ]\n    lines = capture(command, caplog)\n\n    assert \"foo=bar\" in lines\n\n\n@pytest.mark.parametrize(\n    \"cmd\",\n    (\n        (\n            \"--cert=certs/sanic.example/fullchain.pem\",\n            \"--key=certs/sanic.example/privkey.pem\",\n        ),\n        (\n            \"--tls=certs/sanic.example/\",\n            \"--tls=certs/localhost/\",\n        ),\n        (\n            \"--tls=certs/sanic.example/\",\n            \"--tls=certs/localhost/\",\n            \"--tls-strict-host\",\n        ),\n    ),\n)\ndef test_tls_options(cmd: tuple[str, ...], caplog, port):\n    command = [\n        \"fake.server.app\",\n        *cmd,\n        f\"--port={port}\",\n        \"--debug\",\n        \"--single-process\",\n    ]\n    lines = capture(command, caplog)\n    assert f\"Goin' Fast @ https://127.0.0.1:{port}\" in lines\n\n\n@pytest.mark.parametrize(\n    \"cmd\",\n    (\n        (\"--cert=certs/sanic.example/fullchain.pem\",),\n        (\n            \"--cert=certs/sanic.example/fullchain.pem\",\n            \"--key=certs/sanic.example/privkey.pem\",\n            \"--tls=certs/localhost/\",\n        ),\n        (\"--tls-strict-host\",),\n    ),\n)\ndef test_tls_wrong_options(cmd: tuple[str, ...], caplog, port):\n    command = [\"fake.server.app\", *cmd, f\"-p={port}\", \"--debug\"]\n    lines = capture(command, caplog)\n\n    assert (\n        \"TLS certificates must be specified by either of:\\n  \"\n        \"--cert certdir/fullchain.pem --key certdir/privkey.pem\\n  \"\n        \"--tls certdir  (equivalent to the above)\"\n    ) in lines\n\n\n@pytest.mark.parametrize(\n    \"cmd\",\n    (\n        (\"--host=localhost\", \"--port={port}\"),\n        (\"-H\", \"localhost\", \"-p\", \"{port}\"),\n    ),\n)\ndef test_host_port_localhost(cmd: tuple[str, ...], caplog, port):\n    cmd = [c.format(port=str(port)) for c in cmd]\n    command = [\"fake.server.app\", *cmd]\n    lines = capture(command, caplog)\n    expected = f\"Goin' Fast @ http://localhost:{port}\"\n\n    assert expected in lines\n\n\n@pytest.mark.parametrize(\n    \"cmd,expected\",\n    (\n        (\n            (\"--host=localhost\", \"--port={port}\"),\n            \"Goin' Fast @ http://localhost:{port}\",\n        ),\n        (\n            (\"-H\", \"localhost\", \"-p\", \"{port}\"),\n            \"Goin' Fast @ http://localhost:{port}\",\n        ),\n        (\n            (\"--host=127.0.0.1\", \"--port={port}\"),\n            \"Goin' Fast @ http://127.0.0.1:{port}\",\n        ),\n        (\n            (\"-H\", \"127.0.0.1\", \"-p\", \"{port}\"),\n            \"Goin' Fast @ http://127.0.0.1:{port}\",\n        ),\n        ((\"--host=::\", \"--port={port}\"), \"Goin' Fast @ http://[::]:{port}\"),\n        ((\"-H\", \"::\", \"-p\", \"{port}\"), \"Goin' Fast @ http://[::]:{port}\"),\n        ((\"--host=::1\", \"--port={port}\"), \"Goin' Fast @ http://[::1]:{port}\"),\n        ((\"-H\", \"::1\", \"-p\", \"{port}\"), \"Goin' Fast @ http://[::1]:{port}\"),\n    ),\n)\ndef test_host_port(cmd: tuple[str, ...], expected: str, caplog, port):\n    cmd = [c.format(port=str(port)) for c in cmd]\n    expected = expected.format(port=str(port))\n    command = [\"fake.server.app\", *cmd]\n    lines = capture(command, caplog)\n\n    assert expected in lines\n\n\n@pytest.mark.parametrize(\n    \"num,cmd\",\n    (\n        (1, (f\"--workers={1}\",)),\n        (2, (f\"--workers={2}\",)),\n        (4, (f\"--workers={4}\",)),\n        (1, (\"-w\", \"1\")),\n        (2, (\"-w\", \"2\")),\n        (4, (\"-w\", \"4\")),\n    ),\n)\ndef test_num_workers(num: int, cmd: tuple[str, ...], caplog, port):\n    command = [\"fake.server.app\", *cmd, f\"-p={port}\"]\n    lines = capture(command, caplog)\n\n    if num == 1:\n        expected = \"mode: production, single worker\"\n    else:\n        expected = f\"mode: production, w/ {num} workers\"\n\n    assert expected in lines\n\n\n@pytest.mark.parametrize(\"cmd\", (\"--debug\",))\ndef test_debug(cmd: str, caplog, port):\n    command = [\"fake.server.app\", cmd, f\"-p={port}\"]\n    lines = capture(command, caplog)\n    info = read_app_info(lines)\n\n    assert info[\"debug\"] is True\n    assert info[\"auto_reload\"] is False\n\n\n@pytest.mark.parametrize(\"cmd\", (\"--dev\", \"-d\"))\ndef test_dev(cmd: str, caplog, port):\n    command = [\"fake.server.app\", cmd, f\"-p={port}\"]\n    lines = capture(command, caplog)\n    info = read_app_info(lines)\n\n    assert info[\"debug\"] is True\n    assert info[\"auto_reload\"] is True\n\n\n@pytest.mark.parametrize(\"cmd\", (\"--auto-reload\", \"-r\"))\ndef test_auto_reload(cmd: str, caplog, port):\n    command = [\"fake.server.app\", cmd, f\"-p={port}\"]\n    lines = capture(command, caplog)\n    info = read_app_info(lines)\n\n    assert info[\"debug\"] is False, f\"Unexpected value of debug {info}\"\n    assert info[\"auto_reload\"] is True, (\n        f\"Unexpected value of auto reload {info}\"\n    )\n\n\n@pytest.mark.parametrize(\n    \"cmd,expected\",\n    (\n        (\"\", False),\n        (\"--debug\", True),\n        (\"--access-log\", True),\n        (\"--no-access-log\", False),\n    ),\n)\ndef test_access_logs(cmd: str, expected: bool, caplog, port):\n    command = [\"fake.server.app\", f\"-p={port}\"]\n    if cmd:\n        command.append(cmd)\n    lines = capture(command, caplog)\n    print(lines)\n    info = read_app_info(lines)\n    if info[\"access_log\"] != expected:\n        print(lines)\n    assert info[\"access_log\"] is expected, (\n        f\"Expected: {expected}. Received: {info}. Lines: {lines}\"\n    )\n\n\n@pytest.mark.parametrize(\"cmd\", (\"--version\", \"-v\"))\ndef test_version(cmd: str, caplog, capsys):\n    command = [cmd]\n    capture(command, caplog)\n    version_string = f\"Sanic {__version__}; Routing {__routing_version__}\\n\"\n    out, _ = capsys.readouterr()\n    assert version_string == out\n\n\n@pytest.mark.parametrize(\n    \"cmd,expected\",\n    (\n        (\"--noisy-exceptions\", True),\n        (\"--no-noisy-exceptions\", False),\n    ),\n)\ndef test_noisy_exceptions(cmd: str, expected: bool, caplog, port):\n    command = [\"fake.server.app\", cmd, f\"-p={port}\"]\n    lines = capture(command, caplog)\n    info = read_app_info(lines)\n\n    assert info[\"noisy_exceptions\"] is expected\n\n\ndef test_inspector_inspect(urlopen, caplog, capsys):\n    urlopen.read.return_value = json.dumps(\n        {\n            \"result\": {\n                \"info\": {\n                    \"packages\": [\"foo\"],\n                },\n                \"extra\": {\n                    \"more\": \"data\",\n                },\n                \"workers\": {\"Worker-Name\": {\"some\": \"state\"}},\n            }\n        }\n    ).encode()\n    with patch(\"sys.argv\", [\"sanic\", \"inspect\"]):\n        capture([\"inspect\"], caplog)\n    captured = capsys.readouterr()\n    assert \"Inspecting @ http://localhost:6457\" in captured.out\n    assert \"Worker-Name\" in captured.out\n    assert captured.err == \"\"\n\n\n@pytest.mark.parametrize(\n    \"command,params\",\n    (\n        ([\"reload\"], {\"zero_downtime\": False}),\n        ([\"reload\", \"--zero-downtime\"], {\"zero_downtime\": True}),\n        ([\"shutdown\"], {}),\n        ([\"scale\", \"9\"], {\"replicas\": 9}),\n        ([\"foo\", \"--bar=something\"], {\"bar\": \"something\"}),\n        ([\"foo\", \"--bar\"], {\"bar\": True}),\n        ([\"foo\", \"--no-bar\"], {\"bar\": False}),\n        ([\"foo\", \"positional\"], {\"args\": [\"positional\"]}),\n        (\n            [\"foo\", \"positional\", \"--bar=something\"],\n            {\"args\": [\"positional\"], \"bar\": \"something\"},\n        ),\n    ),\n)\ndef test_inspector_command(command, params):\n    with patch.object(InspectorClient, \"request\") as client:\n        with patch(\"sys.argv\", [\"sanic\", \"inspect\", *command]):\n            main()\n\n    client.assert_called_once_with(command[0], **params)\n\n\ndef test_server_run_with_repl(caplog, capsys):\n    record = (\n        \"sanic.error\",\n        40,\n        \"Can't start REPL in non-interactive mode. \"\n        \"You can only run with --repl in a TTY.\",\n    )\n\n    def run():\n        command = [\"fake.server.app\", \"--repl\", f\"-p={get_port()}\"]\n        return capture(command, capsys=capsys)\n\n    with patch(\"sanic.cli.app.is_atty\", return_value=True):\n        result = run()\n\n    assert record not in caplog.record_tuples\n    assert \"Welcome to the Sanic interactive console\" in result.err\n    assert \">>> \" in result.out\n\n    run()\n    assert record in caplog.record_tuples\n\n\ndef test_command_no_args(caplog):\n    args = [\"fake.server.app\", \"exec\", \"foo\"]\n    with patch(\"sys.argv\", [\"sanic\", *args]):\n        lines = capture(args, caplog)\n    assert \"FOO one=None two=None three='...'\" in lines\n\n\ndef test_command_with_args(caplog):\n    args = [\n        \"fake.server.app\",\n        \"exec\",\n        \"foo\",\n        \"--one=1\",\n        \"--two=2\",\n        \"--three=3\",\n    ]\n    with patch(\"sys.argv\", [\"sanic\", *args]):\n        lines = capture(args, caplog)\n    assert \"FOO one='1' two='2' three='3'\" in lines\n\n\ndef test_command_with_sync_handler(caplog):\n    args = [\"fake.server.app\", \"exec\", \"bar\"]\n    with patch(\"sys.argv\", [\"sanic\", *args]):\n        lines = capture(args, caplog)\n    assert \"BAR\" in lines\n\n\ndef test_command_with_renamed_command(caplog):\n    args = [\"fake.server.app\", \"exec\", \"qqq\"]\n    with patch(\"sys.argv\", [\"sanic\", *args]):\n        lines = capture(args, caplog)\n    assert \"BAZ\" in lines\n\n\ndef test_add_local_method(app):\n    def foo(): ...\n    def bar():\n        \"\"\"bar method docstring.\"\"\"\n\n    class Luffy: ...\n\n    import os\n\n    app.repl_ctx.add(foo)\n    app.repl_ctx.add(bar)\n    app.repl_ctx.add(Luffy)\n    app.repl_ctx.add(os, desc=\"Standard os module.\")\n\n    assert REPLLocal(foo, \"foo\", \"\") in app.repl_ctx._locals\n    assert (\n        REPLLocal(bar, \"bar\", \"bar method docstring.\") in app.repl_ctx._locals\n    )\n    assert REPLLocal(Luffy, \"Luffy\", \"\") in app.repl_ctx._locals\n    assert REPLLocal(os, \"os\", \"Standard os module.\") in app.repl_ctx._locals\n\n\ndef test_add_local_attr(app):\n    def foo(): ...\n    def bar():\n        \"\"\"bar method docstring.\"\"\"\n\n    class Luffy: ...\n\n    import os\n\n    app.repl_ctx.foo = foo\n    app.repl_ctx.bar = bar\n    app.repl_ctx.Luffy = Luffy\n    app.repl_ctx.os = os\n\n    assert REPLLocal(foo, \"foo\", \"\") in app.repl_ctx._locals\n    assert (\n        REPLLocal(bar, \"bar\", \"bar method docstring.\") in app.repl_ctx._locals\n    )\n    assert REPLLocal(Luffy, \"Luffy\", \"\") in app.repl_ctx._locals\n    assert any(\n        isinstance(item, REPLLocal) and item.var == os and item.name == \"os\"\n        for item in app.repl_ctx._locals\n    )\n"
  },
  {
    "path": "tests/test_coffee.py",
    "content": "import logging\n\nfrom unittest.mock import patch\n\nimport pytest\n\nfrom sanic.application.logo import COFFEE_LOGO, get_logo\nfrom sanic.exceptions import SanicException\n\n\ndef has_sugar(value):\n    if value:\n        raise SanicException(\"I said no sugar please\")\n\n    return False\n\n\n@pytest.mark.parametrize(\"sugar\", (True, False))\ndef test_no_sugar(sugar):\n    if sugar:\n        with pytest.raises(SanicException):\n            assert has_sugar(sugar)\n    else:\n        assert not has_sugar(sugar)\n\n\ndef test_get_logo_returns_expected_logo():\n    with patch(\"sys.stdout.isatty\") as isatty:\n        isatty.return_value = True\n        logo = get_logo(coffee=True)\n    assert logo is COFFEE_LOGO\n\n\ndef test_logo_true(app, caplog):\n    @app.after_server_start\n    async def shutdown(*_):\n        app.stop()\n\n    with patch(\"sys.stdout.isatty\") as isatty:\n        isatty.return_value = True\n        with caplog.at_level(logging.DEBUG):\n            app.make_coffee(single_process=True)\n\n    # Only in the regular logo\n    assert \"    ▄███ █████ ██    \" not in caplog.text\n\n    # Only in the coffee logo\n    assert \"    ██       ██▀▀▄   \" in caplog.text\n"
  },
  {
    "path": "tests/test_config.py",
    "content": "import logging\nimport os\n\nfrom contextlib import contextmanager\nfrom os import environ\nfrom pathlib import Path\nfrom tempfile import TemporaryDirectory\nfrom textwrap import dedent\nfrom unittest.mock import Mock, call\n\nimport pytest\n\nfrom pytest import MonkeyPatch\n\nfrom sanic import Sanic\nfrom sanic.config import DEFAULT_CONFIG, Config, DetailedConverter\nfrom sanic.constants import LocalCertCreator\nfrom sanic.exceptions import PyFileError\n\nfrom .conftest import get_port\n\n\n@contextmanager\ndef temp_path():\n    \"\"\"a simple cross platform replacement for NamedTemporaryFile\"\"\"\n    with TemporaryDirectory() as td:\n        yield Path(td, \"file\")\n\n\nclass ConfigTest:\n    not_for_config = \"should not be used\"\n    CONFIG_VALUE = \"should be used\"\n\n    @property\n    def ANOTHER_VALUE(self):\n        return self.CONFIG_VALUE\n\n    @property\n    def another_not_for_config(self):\n        return self.not_for_config\n\n\nclass UltimateAnswer:\n    def __init__(self, answer):\n        self.answer = int(answer)\n\n\ndef test_load_from_object(app: Sanic):\n    app.config.load(ConfigTest)\n    assert \"CONFIG_VALUE\" in app.config\n    assert app.config.CONFIG_VALUE == \"should be used\"\n    assert \"not_for_config\" not in app.config\n\n\ndef test_load_from_object_string(app: Sanic):\n    app.config.load(\"tests.test_config.ConfigTest\")\n    assert \"CONFIG_VALUE\" in app.config\n    assert app.config.CONFIG_VALUE == \"should be used\"\n    assert \"not_for_config\" not in app.config\n\n\ndef test_load_from_instance(app: Sanic):\n    app.config.load(ConfigTest())\n    assert \"CONFIG_VALUE\" in app.config\n    assert app.config.CONFIG_VALUE == \"should be used\"\n    assert app.config.ANOTHER_VALUE == \"should be used\"\n    assert \"not_for_config\" not in app.config\n    assert \"another_not_for_config\" not in app.config\n\n\ndef test_load_from_object_string_exception(app: Sanic):\n    with pytest.raises(ImportError):\n        app.config.load(\"test_config.Config.test\")\n\n\ndef test_auto_env_prefix():\n    environ[\"SANIC_TEST_ANSWER\"] = \"42\"\n    app = Sanic(name=\"Test\")\n    assert app.config.TEST_ANSWER == 42\n    del environ[\"SANIC_TEST_ANSWER\"]\n\n\ndef test_auto_bool_env_prefix():\n    environ[\"SANIC_TEST_ANSWER\"] = \"True\"\n    app = Sanic(name=\"Test\")\n    assert app.config.TEST_ANSWER is True\n    del environ[\"SANIC_TEST_ANSWER\"]\n\n\n@pytest.mark.parametrize(\"env_prefix\", [None, \"\"])\ndef test_empty_load_env_prefix(env_prefix):\n    environ[\"SANIC_TEST_ANSWER\"] = \"42\"\n    app = Sanic(name=\"Test\", env_prefix=env_prefix)\n    assert getattr(app.config, \"TEST_ANSWER\", None) is None\n    del environ[\"SANIC_TEST_ANSWER\"]\n\n\ndef test_env_prefix():\n    environ[\"MYAPP_TEST_ANSWER\"] = \"42\"\n    app = Sanic(name=\"Test\", env_prefix=\"MYAPP_\")\n    assert app.config.TEST_ANSWER == 42\n    del environ[\"MYAPP_TEST_ANSWER\"]\n\n\ndef test_env_prefix_float_values():\n    environ[\"MYAPP_TEST_ROI\"] = \"2.3\"\n    app = Sanic(name=\"Test\", env_prefix=\"MYAPP_\")\n    assert app.config.TEST_ROI == 2.3\n    del environ[\"MYAPP_TEST_ROI\"]\n\n\ndef test_env_prefix_string_value():\n    environ[\"MYAPP_TEST_TOKEN\"] = \"somerandomtesttoken\"\n    app = Sanic(name=\"Test\", env_prefix=\"MYAPP_\")\n    assert app.config.TEST_TOKEN == \"somerandomtesttoken\"\n    del environ[\"MYAPP_TEST_TOKEN\"]\n\n\ndef test_env_w_custom_converter():\n    environ[\"SANIC_TEST_ANSWER\"] = \"42\"\n\n    config = Config(converters=[UltimateAnswer])\n    app = Sanic(name=\"Test\", config=config)\n    assert isinstance(app.config.TEST_ANSWER, UltimateAnswer)\n    assert app.config.TEST_ANSWER.answer == 42\n    del environ[\"SANIC_TEST_ANSWER\"]\n\n\ndef test_env_lowercase():\n    environ[\"SANIC_test_answer\"] = \"42\"\n    app = Sanic(name=\"Test\")\n    assert \"test_answer\" not in app.config\n    del environ[\"SANIC_test_answer\"]\n\n\ndef test_add_converter_multiple_times(caplog):\n    def converter(): ...\n\n    message = (\n        \"Configuration value converter 'converter' has already been registered\"\n    )\n    config = Config()\n    config.register_type(converter)\n    with caplog.at_level(logging.WARNING):\n        config.register_type(converter)\n\n    assert (\"sanic.error\", logging.WARNING, message) in caplog.record_tuples\n    assert len(config._converters) == 5\n\n\nclass MockDetailedConverter(DetailedConverter):\n    \"\"\"Mock converter that returns a dict with all passed parameters.\"\"\"\n\n    def __call__(\n        self, full_key: str, config_key: str, value: str, defaults: dict\n    ):\n        return {\n            \"full_key\": full_key,\n            \"config_key\": config_key,\n            \"value\": value,\n            \"has_default\": config_key in defaults,\n            \"default_value\": defaults.get(config_key, None),\n        }\n\n\ndef test_detailed_converter_basic_functionality():\n    \"\"\"Test that DetailedConverter receives all expected parameters.\"\"\"\n    environ[\"SANIC_TEST_DETAILED\"] = \"test_value\"\n\n    config = Config(converters=[MockDetailedConverter()])\n\n    result = config.TEST_DETAILED\n    assert isinstance(result, dict)\n    assert result[\"full_key\"] == \"SANIC_TEST_DETAILED\"\n    assert result[\"config_key\"] == \"TEST_DETAILED\"\n    assert result[\"value\"] == \"test_value\"\n    assert result[\"has_default\"] is False\n    assert result[\"default_value\"] is None\n\n    del environ[\"SANIC_TEST_DETAILED\"]\n\n\ndef test_detailed_converter_with_defaults():\n    \"\"\"Test DetailedConverter with custom defaults.\"\"\"\n    environ[\"SANIC_CUSTOM_VALUE\"] = \"42\"\n\n    defaults = {\"CUSTOM_VALUE\": 100}\n    config = Config(defaults=defaults, converters=[MockDetailedConverter()])\n\n    result = config.CUSTOM_VALUE\n    assert isinstance(result, dict)\n    assert result[\"full_key\"] == \"SANIC_CUSTOM_VALUE\"\n    assert result[\"config_key\"] == \"CUSTOM_VALUE\"\n    assert result[\"value\"] == \"42\"\n    assert result[\"has_default\"] is True\n    assert result[\"default_value\"] == 100\n\n    del environ[\"SANIC_CUSTOM_VALUE\"]\n\n\ndef test_detailed_converter_with_custom_prefix():\n    \"\"\"Test DetailedConverter with custom environment prefix.\"\"\"\n    environ[\"MYAPP_TEST_VALUE\"] = \"custom_test\"\n\n    config = Config(env_prefix=\"MYAPP_\", converters=[MockDetailedConverter()])\n\n    result = config.TEST_VALUE\n    assert isinstance(result, dict)\n    assert result[\"full_key\"] == \"MYAPP_TEST_VALUE\"\n    assert result[\"config_key\"] == \"TEST_VALUE\"\n    assert result[\"value\"] == \"custom_test\"\n\n    del environ[\"MYAPP_TEST_VALUE\"]\n\n\ndef test_load_from_file(app: Sanic):\n    config = dedent(\n        \"\"\"\n    VALUE = 'some value'\n    condition = 1 == 1\n    if condition:\n        CONDITIONAL = 'should be set'\n    \"\"\"\n    )\n    with temp_path() as config_path:\n        config_path.write_text(config)\n        app.config.load(str(config_path))\n        assert \"VALUE\" in app.config\n        assert app.config.VALUE == \"some value\"\n        assert \"CONDITIONAL\" in app.config\n        assert app.config.CONDITIONAL == \"should be set\"\n        assert \"condition\" not in app.config\n\n\ndef test_load_from_missing_file(app: Sanic):\n    with pytest.raises(IOError):\n        app.config.load(\"non-existent file\")\n\n\ndef test_load_from_envvar(app: Sanic):\n    config = \"VALUE = 'some value'\"\n    with temp_path() as config_path:\n        config_path.write_text(config)\n        environ[\"APP_CONFIG\"] = str(config_path)\n        app.config.load(\"${APP_CONFIG}\")\n        assert \"VALUE\" in app.config\n        assert app.config.VALUE == \"some value\"\n\n\ndef test_load_from_missing_envvar(app: Sanic):\n    with pytest.raises(IOError) as e:\n        app.config.load(\"non-existent variable\")\n        assert str(e.value) == (\n            \"The environment variable 'non-existent \"\n            \"variable' is not set and thus configuration \"\n            \"could not be loaded.\"\n        )\n\n\ndef test_load_config_from_file_invalid_syntax(app: Sanic):\n    config = \"VALUE = some value\"\n    with temp_path() as config_path:\n        config_path.write_text(config)\n\n        with pytest.raises(PyFileError):\n            app.config.load(config_path)\n\n\ndef test_overwrite_exisiting_config(app: Sanic):\n    app.config.DEFAULT = 1\n\n    class Config:\n        DEFAULT = 2\n\n    app.config.load(Config)\n    assert app.config.DEFAULT == 2\n\n\ndef test_overwrite_exisiting_config_ignore_lowercase(app: Sanic):\n    app.config.default = 1\n\n    class Config:\n        default = 2\n\n    app.config.load(Config)\n    assert app.config.default == 1\n\n\ndef test_missing_config(app: Sanic):\n    with pytest.raises(AttributeError, match=\"Config has no 'NON_EXISTENT'\"):\n        _ = app.config.NON_EXISTENT\n\n\ndef test_config_defaults():\n    \"\"\"\n    load DEFAULT_CONFIG\n    \"\"\"\n    conf = Config()\n    for key, value in DEFAULT_CONFIG.items():\n        assert getattr(conf, key) == value\n\n\ndef test_config_custom_defaults():\n    \"\"\"\n    we should have all the variables from defaults rewriting them with\n    custom defaults passed in\n    Config\n    \"\"\"\n    custom_defaults = {\n        \"REQUEST_MAX_SIZE\": 1,\n        \"KEEP_ALIVE\": False,\n        \"ACCESS_LOG\": False,\n    }\n    conf = Config(defaults=custom_defaults)\n    for key, value in DEFAULT_CONFIG.items():\n        if key in custom_defaults.keys():\n            value = custom_defaults[key]\n        assert getattr(conf, key) == value\n\n\ndef test_config_custom_defaults_with_env():\n    \"\"\"\n    test that environment variables has higher priority than DEFAULT_CONFIG\n    and passed defaults dict\n    \"\"\"\n    custom_defaults = {\n        \"REQUEST_MAX_SIZE123\": 1,\n        \"KEEP_ALIVE123\": False,\n        \"ACCESS_LOG123\": False,\n    }\n\n    environ_defaults = {\n        \"SANIC_REQUEST_MAX_SIZE123\": \"2\",\n        \"SANIC_KEEP_ALIVE123\": \"True\",\n        \"SANIC_ACCESS_LOG123\": \"False\",\n    }\n\n    for key, value in environ_defaults.items():\n        environ[key] = value\n\n    conf = Config(defaults=custom_defaults)\n    for key, value in DEFAULT_CONFIG.items():\n        if \"SANIC_\" + key in environ_defaults.keys():\n            value = environ_defaults[\"SANIC_\" + key]\n            try:\n                value = int(value)\n            except ValueError:\n                if value in [\"True\", \"False\"]:\n                    value = value == \"True\"\n\n        assert getattr(conf, key) == value\n\n    for key, value in environ_defaults.items():\n        del environ[key]\n\n\n@pytest.mark.parametrize(\"access_log\", (True, False))\ndef test_config_access_log_passing_in_run(app: Sanic, port, access_log):\n    assert app.config.ACCESS_LOG is False\n\n    @app.listener(\"after_server_start\")\n    async def _request(sanic, loop):\n        app.stop()\n\n    app.run(port=port, access_log=access_log, single_process=True)\n    assert app.config.ACCESS_LOG is access_log\n\n\n@pytest.mark.asyncio\nasync def test_config_access_log_passing_in_create_server(app: Sanic):\n    assert app.config.ACCESS_LOG is False\n\n    @app.listener(\"after_server_start\")\n    async def _request(sanic, loop):\n        app.stop()\n\n    await app.create_server(\n        port=get_port(), access_log=False, return_asyncio_server=True\n    )\n    assert app.config.ACCESS_LOG is False\n\n    await app.create_server(\n        port=get_port(), access_log=True, return_asyncio_server=True\n    )\n    assert app.config.ACCESS_LOG is True\n\n\ndef test_config_rewrite_keep_alive():\n    config = Config()\n    assert config.KEEP_ALIVE == DEFAULT_CONFIG[\"KEEP_ALIVE\"]\n    config = Config(keep_alive=True)\n    assert config.KEEP_ALIVE is True\n    config = Config(keep_alive=False)\n    assert config.KEEP_ALIVE is False\n\n    # use defaults\n    config = Config(defaults={\"KEEP_ALIVE\": False})\n    assert config.KEEP_ALIVE is False\n    config = Config(defaults={\"KEEP_ALIVE\": True})\n    assert config.KEEP_ALIVE is True\n\n\n_test_setting_as_dict = {\"TEST_SETTING_VALUE\": 1}\n_test_setting_as_class = type(\"C\", (), {\"TEST_SETTING_VALUE\": 1})\n_test_setting_as_module = str(\n    Path(__file__).parent / \"static/app_test_config.py\"\n)\n\n\n@pytest.mark.parametrize(\n    \"conf_object\",\n    [\n        _test_setting_as_dict,\n        _test_setting_as_class,\n        _test_setting_as_module,\n    ],\n    ids=[\"from_dict\", \"from_class\", \"from_file\"],\n)\ndef test_update(app: Sanic, conf_object):\n    app.update_config(conf_object)\n    assert app.config[\"TEST_SETTING_VALUE\"] == 1\n\n\ndef test_update_from_lowercase_key(app: Sanic):\n    d = {\"test_setting_value\": 1}\n    app.update_config(d)\n    assert \"test_setting_value\" not in app.config\n\n\ndef test_config_set_methods(app: Sanic, monkeypatch: MonkeyPatch):\n    post_set = Mock()\n    monkeypatch.setattr(Config, \"_post_set\", post_set)\n\n    app.config.FOO = 1\n    post_set.assert_called_once_with(\"FOO\", 1)\n    post_set.reset_mock()\n\n    app.config[\"FOO\"] = 2\n    post_set.assert_called_once_with(\"FOO\", 2)\n    post_set.reset_mock()\n\n    app.config.update({\"FOO\": 3})\n    post_set.assert_called_once_with(\"FOO\", 3)\n    post_set.reset_mock()\n\n    app.config.update([(\"FOO\", 4)])\n    post_set.assert_called_once_with(\"FOO\", 4)\n    post_set.reset_mock()\n\n    app.config.update(FOO=5)\n    post_set.assert_called_once_with(\"FOO\", 5)\n    post_set.reset_mock()\n\n    app.config.update({\"FOO\": 6}, {\"BAR\": 7})\n    post_set.assert_has_calls(\n        calls=[\n            call(\"FOO\", 6),\n            call(\"BAR\", 7),\n        ]\n    )\n    post_set.reset_mock()\n\n    app.config.update({\"FOO\": 8}, BAR=9)\n    post_set.assert_has_calls(\n        calls=[\n            call(\"FOO\", 8),\n            call(\"BAR\", 9),\n        ],\n        any_order=True,\n    )\n    post_set.reset_mock()\n\n    app.config.update_config({\"FOO\": 10})\n    post_set.assert_called_once_with(\"FOO\", 10)\n\n\ndef test_negative_proxy_count(app: Sanic):\n    app.config.PROXIES_COUNT = -1\n\n    message = (\n        \"PROXIES_COUNT cannot be negative. \"\n        \"https://sanic.readthedocs.io/en/latest/sanic/config.html\"\n        \"#proxy-configuration\"\n    )\n    with pytest.raises(ValueError, match=message):\n        app.prepare()\n\n\n@pytest.mark.parametrize(\n    \"passed,expected\",\n    (\n        (\"auto\", LocalCertCreator.AUTO),\n        (\"mkcert\", LocalCertCreator.MKCERT),\n        (\"trustme\", LocalCertCreator.TRUSTME),\n        (\"AUTO\", LocalCertCreator.AUTO),\n        (\"MKCERT\", LocalCertCreator.MKCERT),\n        (\"TRUSTME\", LocalCertCreator.TRUSTME),\n    ),\n)\ndef test_convert_local_cert_creator(passed, expected):\n    os.environ[\"SANIC_LOCAL_CERT_CREATOR\"] = passed\n    app = Sanic(\"Test\")\n    assert app.config.LOCAL_CERT_CREATOR is expected\n    del os.environ[\"SANIC_LOCAL_CERT_CREATOR\"]\n"
  },
  {
    "path": "tests/test_constants.py",
    "content": "import pytest\n\nfrom sanic import Sanic, text\nfrom sanic.application.constants import Mode, Server, ServerStage\nfrom sanic.constants import HTTP_METHODS, HTTPMethod\n\n\n@pytest.mark.parametrize(\"enum\", (HTTPMethod, Server, Mode))\ndef test_string_compat(enum):\n    for key in enum.__members__.keys():\n        assert key.upper() == getattr(enum, key).upper()\n        assert key.lower() == getattr(enum, key).lower()\n\n\ndef test_http_methods():\n    for value in HTTPMethod.__members__.values():\n        assert value in HTTP_METHODS\n\n\ndef test_server_stage():\n    assert ServerStage.SERVING > ServerStage.PARTIAL > ServerStage.STOPPED\n\n\ndef test_use_in_routes(app: Sanic):\n    @app.route(\"/\", methods=[HTTPMethod.GET, HTTPMethod.POST])\n    def handler(_):\n        return text(\"It works\")\n\n    _, response = app.test_client.get(\"/\")\n    assert response.status == 200\n    assert response.text == \"It works\"\n\n    _, response = app.test_client.post(\"/\")\n    assert response.status == 200\n    assert response.text == \"It works\"\n"
  },
  {
    "path": "tests/test_cookies.py",
    "content": "from datetime import datetime, timedelta\nfrom http.cookies import SimpleCookie\nfrom unittest.mock import Mock\n\nimport pytest\n\nfrom sanic import Request, Sanic\nfrom sanic.compat import Header\nfrom sanic.cookies import Cookie, CookieJar\nfrom sanic.cookies.request import CookieRequestParameters, parse_cookie\nfrom sanic.exceptions import ServerError\nfrom sanic.response import text\nfrom sanic.response.convenience import json\n\n\ndef test_request_cookies():\n    cdict = parse_cookie(\"foo=one; foo=two; abc = xyz;;bare;=bare2\")\n    assert cdict == {\n        \"foo\": [\"one\", \"two\"],\n        \"abc\": [\"xyz\"],\n        \"\": [\"bare\", \"bare2\"],\n    }\n    c = CookieRequestParameters(cdict)\n    assert c.getlist(\"foo\") == [\"one\", \"two\"]\n    assert c.getlist(\"abc\") == [\"xyz\"]\n    assert c.getlist(\"\") == [\"bare\", \"bare2\"]\n    assert c.getlist(\"bare\") == []\n\n\n# ------------------------------------------------------------ #\n#  GET\n# ------------------------------------------------------------ #\n\n\ndef test_cookies(app):\n    @app.route(\"/\")\n    def handler(request):\n        cookie_value = request.cookies[\"test\"]\n        response = text(f\"Cookies are: {cookie_value}\")\n        response.add_cookie(\"right_back\", \"at you\")\n        return response\n\n    request, response = app.test_client.get(\"/\", cookies={\"test\": \"working!\"})\n    response_cookies = SimpleCookie()\n    response_cookies.load(response.headers.get(\"Set-Cookie\", {}))\n\n    assert response.text == \"Cookies are: ['working!']\"\n    assert response_cookies[\"right_back\"].value == \"at you\"\n\n\n@pytest.mark.asyncio\nasync def test_cookies_asgi(app):\n    @app.route(\"/\")\n    def handler(request):\n        cookie_value = request.cookies[\"test\"]\n        response = text(f\"Cookies are: {cookie_value}\")\n        response.add_cookie(\"right_back\", \"at you\")\n        return response\n\n    request, response = await app.asgi_client.get(\n        \"/\", cookies={\"test\": \"working!\"}\n    )\n    response_cookies = SimpleCookie()\n    response_cookies.load(response.headers.get(\"set-cookie\", {}))\n\n    assert response.body == b\"Cookies are: ['working!']\"\n    assert response_cookies[\"right_back\"].value == \"at you\"\n\n\n@pytest.mark.parametrize(\n    \"httponly,expected\", [(False, \"False\"), (True, \"True\")]\n)\ndef test_false_cookies_encoded(app, httponly, expected):\n    @app.route(\"/\")\n    def handler(request):\n        response = text(\"hello cookies\")\n        response.add_cookie(\"hello\", \"world\", httponly=httponly)\n        return text(str(response.cookies.get_cookie(\"hello\").httponly))\n\n    request, response = app.test_client.get(\"/\")\n\n    assert response.text == expected\n\n\n@pytest.mark.parametrize(\"httponly,expected\", [(False, False), (True, True)])\ndef test_false_cookies(app, httponly, expected):\n    @app.route(\"/\")\n    def handler(request):\n        response = text(\"hello cookies\")\n        response.add_cookie(\"right_back\", \"at you\", httponly=httponly)\n        return response\n\n    request, response = app.test_client.get(\"/\")\n    response_cookies = SimpleCookie()\n    response_cookies.load(response.headers.get(\"Set-Cookie\", {}))\n\n    assert (\"HttpOnly\" in response_cookies[\"right_back\"].output()) == expected\n\n\ndef test_http2_cookies(app):\n    @app.route(\"/\")\n    async def handler(request):\n        cookie_value = request.cookies[\"test\"]\n        response = text(f\"Cookies are: {cookie_value}\")\n        return response\n\n    headers = {\"cookie\": \"test=working!\"}\n    request, response = app.test_client.get(\"/\", headers=headers)\n\n    assert response.text == \"Cookies are: ['working!']\"\n\n\ndef test_cookie_options(app):\n    @app.route(\"/\")\n    def handler(request):\n        response = text(\"OK\")\n        response.add_cookie(\n            \"test\",\n            \"at you\",\n            httponly=True,\n            expires=datetime.now() + timedelta(seconds=10),\n        )\n        return response\n\n    request, response = app.test_client.get(\"/\")\n    response_cookies = SimpleCookie()\n    response_cookies.load(response.headers.get(\"Set-Cookie\", {}))\n\n    assert response_cookies[\"test\"].value == \"at you\"\n    assert response_cookies[\"test\"][\"httponly\"] is True\n\n\ndef test_cookie_deletion(app):\n    cookie_jar = None\n\n    @app.route(\"/\")\n    def handler(request):\n        nonlocal cookie_jar\n        response = text(\"OK\")\n        response.delete_cookie(\"one\")\n        response.add_cookie(\"two\", \"testing\")\n        response.delete_cookie(\"two\")\n        cookie_jar = response.cookies\n        return response\n\n    _, response = app.test_client.get(\"/\")\n\n    assert cookie_jar.get_cookie(\"one\").max_age == 0\n    assert cookie_jar.get_cookie(\"two\").max_age == 0\n    assert len(response.cookies) == 0\n\n\ndef test_cookie_reserved_cookie():\n    with pytest.raises(expected_exception=KeyError) as e:\n        Cookie(\"domain\", \"testdomain.com\")\n        assert e.message == \"Cookie name is a reserved word\"\n\n\ndef test_cookie_illegal_key_format():\n    with pytest.raises(expected_exception=KeyError) as e:\n        Cookie(\"testå\", \"test\")\n        assert e.message == \"Cookie key contains illegal characters\"\n\n\ndef test_cookie_set_same_key(app):\n    cookies = {\"test\": \"wait\"}\n\n    @app.get(\"/\")\n    def handler(request):\n        response = text(\"pass\")\n        response.add_cookie(\"test\", \"modified\")\n        response.add_cookie(\"test\", \"pass\")\n        return response\n\n    request, response = app.test_client.get(\"/\", cookies=cookies)\n    assert response.status == 200\n    assert response.cookies[\"test\"] == \"pass\"\n\n\n@pytest.mark.parametrize(\"max_age\", [\"0\", 30, \"30\"])\ndef test_cookie_max_age(app, max_age):\n    cookies = {\"test\": \"wait\"}\n\n    @app.get(\"/\")\n    def handler(request):\n        response = text(\"pass\")\n        response.add_cookie(\"test\", \"pass\", max_age=max_age)\n        return response\n\n    request, response = app.test_client.get(\n        \"/\", cookies=cookies, raw_cookies=True\n    )\n    assert response.status == 200\n\n    cookie = response.cookies.get(\"test\")\n    if (\n        str(max_age).isdigit()\n        and int(max_age) == float(max_age)\n        and int(max_age) != 0\n    ):\n        cookie_expires = datetime.utcfromtimestamp(\n            response.raw_cookies[\"test\"].expires\n        ).replace(microsecond=0)\n\n        # Grabbing utcnow after the response may lead to it being off slightly.\n        # Therefore, we 0 out the microseconds, and accept the test if there\n        # is a 1 second difference.\n        expires = datetime.utcnow().replace(microsecond=0) + timedelta(\n            seconds=int(max_age)\n        )\n\n        assert cookie == \"pass\"\n        assert (\n            cookie_expires == expires\n            or cookie_expires == expires + timedelta(seconds=-1)\n        )\n    else:\n        assert cookie is None\n\n\n@pytest.mark.parametrize(\"max_age\", [30.0, 30.1, \"test\"])\ndef test_cookie_bad_max_age(app, max_age):\n    cookies = {\"test\": \"wait\"}\n\n    @app.get(\"/\")\n    def handler(request):\n        response = text(\"pass\")\n        response.add_cookie(\"test\", \"pass\", max_age=max_age)\n        return response\n\n    request, response = app.test_client.get(\n        \"/\", cookies=cookies, raw_cookies=True\n    )\n    assert response.status == 500\n\n\n@pytest.mark.parametrize(\"expires\", [timedelta(seconds=60)])\ndef test_cookie_expires(app: Sanic, expires: timedelta):\n    expires_time = datetime.utcnow().replace(microsecond=0) + expires\n    cookies = {\"test\": \"wait\"}\n\n    @app.get(\"/\")\n    def handler(request):\n        response = text(\"pass\")\n        response.add_cookie(\"test\", \"pass\", expires=expires_time)\n        return response\n\n    request, response = app.test_client.get(\n        \"/\", cookies=cookies, raw_cookies=True\n    )\n\n    cookie_expires = datetime.utcfromtimestamp(\n        response.raw_cookies[\"test\"].expires\n    ).replace(microsecond=0)\n\n    assert response.status == 200\n    assert response.cookies[\"test\"] == \"pass\"\n    assert cookie_expires == expires_time\n\n\n@pytest.mark.parametrize(\"expires\", [\"Fri, 21-Dec-2018 15:30:00 GMT\"])\ndef test_cookie_expires_illegal_instance_type(expires):\n    c = Cookie(\"test_cookie\", \"value\")\n    with pytest.raises(expected_exception=TypeError) as e:\n        c[\"expires\"] = expires\n        assert e.message == \"Cookie 'expires' property must be a datetime\"\n\n\n@pytest.mark.parametrize(\"value\", (\"foo=one; foo=two\", \"foo=one;foo=two\"))\ndef test_request_with_duplicate_cookie_key(value):\n    headers = Header({\"Cookie\": value})\n    request = Request(b\"/\", headers, \"1.1\", \"GET\", Mock(), Mock())\n\n    assert request.cookies[\"foo\"] == [\"one\", \"two\"]\n    assert request.cookies.get(\"foo\") == \"one\"\n    assert request.cookies.getlist(\"foo\") == [\"one\", \"two\"]\n    assert request.cookies.get(\"bar\") is None\n\n\ndef test_cookie_jar_cookies():\n    headers = Header()\n    jar = CookieJar(headers)\n    jar.add_cookie(\"foo\", \"one\")\n    jar.add_cookie(\"foo\", \"two\", domain=\"example.com\")\n\n    assert len(jar.cookies) == 2\n    assert len(headers) == 2\n\n\ndef test_cookie_jar_has_cookie():\n    headers = Header()\n    jar = CookieJar(headers)\n    jar.add_cookie(\"foo\", \"one\")\n    jar.add_cookie(\"foo\", \"two\", domain=\"example.com\")\n\n    assert jar.has_cookie(\"foo\")\n    assert jar.has_cookie(\"foo\", domain=\"example.com\")\n    assert not jar.has_cookie(\"foo\", path=\"/unknown\")\n    assert not jar.has_cookie(\"bar\")\n\n\ndef test_cookie_jar_get_cookie():\n    headers = Header()\n    jar = CookieJar(headers)\n    cookie1 = jar.add_cookie(\"foo\", \"one\")\n    cookie2 = jar.add_cookie(\"foo\", \"two\", domain=\"example.com\")\n\n    assert jar.get_cookie(\"foo\") is cookie1\n    assert jar.get_cookie(\"foo\", domain=\"example.com\") is cookie2\n    assert jar.get_cookie(\"foo\", path=\"/unknown\") is None\n    assert jar.get_cookie(\"bar\") is None\n\n\ndef test_cookie_jar_add_cookie_encode():\n    headers = Header()\n    jar = CookieJar(headers)\n    jar.add_cookie(\"foo\", \"one\")\n    jar.add_cookie(\n        \"foo\",\n        \"two\",\n        domain=\"example.com\",\n        path=\"/something\",\n        secure=True,\n        max_age=999,\n        httponly=True,\n        samesite=\"strict\",\n    )\n    jar.add_cookie(\"foo\", \"three\", secure_prefix=True)\n    jar.add_cookie(\"foo\", \"four\", host_prefix=True)\n    jar.add_cookie(\"foo\", \"five\", host_prefix=True, partitioned=True)\n\n    encoded = [str(cookie) for cookie in jar.cookies]\n    assert encoded == [\n        \"foo=one; Path=/; SameSite=Lax; Secure\",\n        \"foo=two; Path=/something; Domain=example.com; Max-Age=999; SameSite=Strict; Secure; HttpOnly\",  # noqa\n        \"__Secure-foo=three; Path=/; SameSite=Lax; Secure\",\n        \"__Host-foo=four; Path=/; SameSite=Lax; Secure\",\n        \"__Host-foo=five; Path=/; SameSite=Lax; Secure; Partitioned\",\n    ]\n\n\ndef test_cookie_jar_old_school_cookie_encode():\n    headers = Header()\n    jar = CookieJar(headers)\n    jar.add_cookie(\"foo\", \"one\")\n    jar.add_cookie(\n        \"bar\",\n        \"two\",\n        domain=\"example.com\",\n        path=\"/something\",\n        secure=True,\n        max_age=999,\n        httponly=True,\n        samesite=\"strict\",\n    )\n\n    assert [str(cookie) for cookie in jar.cookies] == [\n        \"foo=one; Path=/; SameSite=Lax; Secure\",\n        \"bar=two; Path=/something; Domain=example.com; Max-Age=999; SameSite=Strict; Secure; HttpOnly\",  # noqa\n    ]\n\n\ndef test_cookie_jar_delete_cookie_encode():\n    headers = Header()\n    jar = CookieJar(headers)\n    jar.delete_cookie(\"foo\")\n    jar.delete_cookie(\"foo\", domain=\"example.com\")\n\n    encoded = [str(cookie) for cookie in jar.cookies]\n    assert encoded == [\n        'foo=\"\"; Path=/; Max-Age=0; Secure',\n        'foo=\"\"; Path=/; Domain=example.com; Max-Age=0; Secure',\n    ]\n\n\ndef test_cookie_jar_delete_nonsecure_cookie():\n    headers = Header()\n    jar = CookieJar(headers)\n    jar.delete_cookie(\"foo\", domain=\"example.com\", secure=False)\n\n    encoded = [str(cookie) for cookie in jar.cookies]\n    assert encoded == [\n        'foo=\"\"; Path=/; Domain=example.com; Max-Age=0',\n    ]\n\n\ndef test_cookie_jar_delete_existing_cookie():\n    headers = Header()\n    jar = CookieJar(headers)\n    jar.add_cookie(\n        \"foo\", \"test\", secure=True, domain=\"example.com\", samesite=\"Strict\"\n    )\n    jar.delete_cookie(\"foo\", domain=\"example.com\", secure=True)\n\n    encoded = [str(cookie) for cookie in jar.cookies]\n    # deletion cookie contains samesite=Strict as was in original cookie\n    assert encoded == [\n        'foo=\"\"; Path=/; Domain=example.com; '\n        \"Max-Age=0; SameSite=Strict; Secure\"\n    ]\n\n\ndef test_cookie_jar_delete_existing_nonsecure_cookie():\n    headers = Header()\n    jar = CookieJar(headers)\n    jar.add_cookie(\n        \"foo\", \"test\", secure=False, domain=\"example.com\", samesite=\"Strict\"\n    )\n    jar.delete_cookie(\"foo\", domain=\"example.com\", secure=False)\n\n    encoded = [str(cookie) for cookie in jar.cookies]\n    # deletion cookie contains samesite=Strict as was in original cookie\n    assert encoded == [\n        'foo=\"\"; Path=/; Domain=example.com; Max-Age=0; SameSite=Strict',\n    ]\n\n\ndef test_cookie_jar_delete_existing_nonsecure_cookie_bad_prefix():\n    headers = Header()\n    jar = CookieJar(headers)\n    jar.add_cookie(\n        \"foo\", \"test\", secure=False, domain=\"example.com\", samesite=\"Strict\"\n    )\n    message = (\n        \"Cannot set host_prefix on a cookie without \"\n        \"path='/', domain=None, and secure=True\"\n    )\n    with pytest.raises(ServerError, match=message):\n        jar.delete_cookie(\n            \"foo\",\n            domain=\"example.com\",\n            secure=False,\n            secure_prefix=True,\n            host_prefix=True,\n        )\n\n\ndef test_cookie_jar_old_school_delete_encode():\n    headers = Header()\n    jar = CookieJar(headers)\n    jar.delete_cookie(\"foo\")\n\n    encoded = [str(cookie) for cookie in jar.cookies]\n    assert encoded == [\n        'foo=\"\"; Path=/; Max-Age=0; Secure',\n    ]\n\n\ndef test_bad_cookie_prarms():\n    headers = Header()\n    jar = CookieJar(headers)\n\n    with pytest.raises(\n        ServerError,\n        match=(\n            \"Both host_prefix and secure_prefix were requested. \"\n            \"A cookie should have only one prefix.\"\n        ),\n    ):\n        jar.add_cookie(\"foo\", \"bar\", host_prefix=True, secure_prefix=True)\n\n    with pytest.raises(\n        ServerError,\n        match=\"Cannot set host_prefix on a cookie without secure=True\",\n    ):\n        jar.add_cookie(\"foo\", \"bar\", host_prefix=True, secure=False)\n\n    with pytest.raises(\n        ServerError,\n        match=\"Cannot set host_prefix on a cookie unless path='/'\",\n    ):\n        jar.add_cookie(\n            \"foo\", \"bar\", host_prefix=True, secure=True, path=\"/foo\"\n        )\n\n    with pytest.raises(\n        ServerError,\n        match=\"Cannot set host_prefix on a cookie with a defined domain\",\n    ):\n        jar.add_cookie(\n            \"foo\", \"bar\", host_prefix=True, secure=True, domain=\"foo.bar\"\n        )\n\n    with pytest.raises(\n        ServerError,\n        match=\"Cannot set secure_prefix on a cookie without secure=True\",\n    ):\n        jar.add_cookie(\"foo\", \"bar\", secure_prefix=True, secure=False)\n\n    with pytest.raises(\n        ServerError,\n        match=(\n            \"Cannot create a partitioned cookie without \"\n            \"also setting host_prefix=True\"\n        ),\n    ):\n        jar.add_cookie(\"foo\", \"bar\", partitioned=True)\n\n\ndef test_cookie_accessors(app: Sanic):\n    @app.get(\"/\")\n    async def handler(request: Request):\n        return json(\n            {\n                \"getitem\": {\n                    \"one\": request.cookies[\"one\"],\n                    \"two\": request.cookies[\"two\"],\n                    \"three\": request.cookies[\"three\"],\n                },\n                \"get\": {\n                    \"one\": request.cookies.get(\"one\", \"fallback\"),\n                    \"two\": request.cookies.get(\"two\", \"fallback\"),\n                    \"three\": request.cookies.get(\"three\", \"fallback\"),\n                    \"four\": request.cookies.get(\"four\", \"fallback\"),\n                },\n                \"getlist\": {\n                    \"one\": request.cookies.getlist(\"one\"),\n                    \"two\": request.cookies.getlist(\"two\"),\n                    \"three\": request.cookies.getlist(\"three\"),\n                    \"four\": request.cookies.getlist(\"four\"),\n                    \"five\": request.cookies.getlist(\"five\", [\"fallback\"]),\n                },\n                \"getattr\": {\n                    \"one\": request.cookies.one,\n                    \"two\": request.cookies.two,\n                    \"three\": request.cookies.three,\n                    \"four\": request.cookies.four,\n                },\n            }\n        )\n\n    _, response = app.test_client.get(\n        \"/\",\n        cookies={\n            \"__Host-one\": \"1\",\n            \"__Secure-two\": \"2\",\n            \"three\": \"3\",\n        },\n    )\n\n    assert response.json == {\n        \"getitem\": {\n            \"one\": [\"1\"],\n            \"two\": [\"2\"],\n            \"three\": [\"3\"],\n        },\n        \"get\": {\n            \"one\": \"1\",\n            \"two\": \"2\",\n            \"three\": \"3\",\n            \"four\": \"fallback\",\n        },\n        \"getlist\": {\n            \"one\": [\"1\"],\n            \"two\": [\"2\"],\n            \"three\": [\"3\"],\n            \"four\": [],\n            \"five\": [\"fallback\"],\n        },\n        \"getattr\": {\n            \"one\": \"1\",\n            \"two\": \"2\",\n            \"three\": \"3\",\n            \"four\": \"\",\n        },\n    }\n\n\ndef test_cookie_accessor_hyphens():\n    cookies = CookieRequestParameters({\"session-token\": [\"abc123\"]})\n\n    assert cookies.get(\"session-token\") == cookies.session_token\n\n\ndef test_cookie_passthru(app):\n    cookie_jar = None\n\n    @app.route(\"/\")\n    def handler(request):\n        nonlocal cookie_jar\n        response = text(\"OK\")\n        response.add_cookie(\"one\", \"1\", host_prefix=True)\n        response.delete_cookie(\"two\", secure_prefix=True)\n        cookie_jar = response.cookies\n        return response\n\n    _, response = app.test_client.get(\"/\")\n\n    assert cookie_jar.get_cookie(\"two\", secure_prefix=True).max_age == 0\n    assert len(response.cookies) == 1\n    assert response.cookies[\"__Host-one\"] == \"1\"\n"
  },
  {
    "path": "tests/test_create_task.py",
    "content": "import asyncio\n\nfrom threading import Event\n\nimport pytest\n\nfrom sanic.exceptions import SanicException\nfrom sanic.response import text\n\n\ndef test_create_task(app):\n    e = Event()\n\n    async def coro():\n        await asyncio.sleep(0.05)\n        e.set()\n\n    @app.route(\"/early\")\n    def not_set(request):\n        return text(str(e.is_set()))\n\n    @app.route(\"/late\")\n    async def set(request):\n        await asyncio.sleep(0.1)\n        return text(str(e.is_set()))\n\n    app.add_task(coro)\n\n    request, response = app.test_client.get(\"/early\")\n    assert response.body == b\"False\"\n\n    app.signal_router.reset()\n    app.add_task(coro)\n    request, response = app.test_client.get(\"/late\")\n    assert response.body == b\"True\"\n\n\ndef test_create_task_with_app_arg(app):\n    @app.after_server_start\n    async def setup_q(app, _):\n        app.ctx.q = asyncio.Queue()\n\n    @app.route(\"/\")\n    async def not_set(request):\n        return text(await request.app.ctx.q.get())\n\n    async def coro(app):\n        await app.ctx.q.put(app.name)\n\n    app.add_task(coro)\n\n    _, response = app.test_client.get(\"/\")\n    assert response.text == \"test_create_task_with_app_arg\"\n\n\ndef test_create_named_task(app, port):\n    async def dummy(): ...\n\n    @app.before_server_start\n    async def setup(app, _):\n        app.add_task(dummy, name=\"dummy_task\")\n\n    @app.after_server_start\n    async def stop(app, _):\n        task = app.get_task(\"dummy_task\")\n\n        assert app._task_registry\n        assert isinstance(task, asyncio.Task)\n\n        assert task.get_name() == \"dummy_task\"\n\n        app.stop()\n\n    app.run(single_process=True, port=port)\n\n\ndef test_named_task_called(app):\n    e = Event()\n\n    async def coro():\n        e.set()\n\n    @app.route(\"/\")\n    async def isset(request):\n        await asyncio.sleep(0.05)\n        return text(str(e.is_set()))\n\n    @app.before_server_start\n    async def setup(app, _):\n        app.add_task(coro, name=\"dummy_task\")\n\n    request, response = app.test_client.get(\"/\")\n    assert response.body == b\"True\"\n\n\ndef test_create_named_task_fails_outside_app(app):\n    async def dummy(): ...\n\n    message = \"Cannot name task outside of a running application\"\n    with pytest.raises(RuntimeError, match=message):\n        app.add_task(dummy, name=\"dummy_task\")\n    assert not app._task_registry\n\n    message = 'Registered task named \"dummy_task\" not found.'\n    with pytest.raises(SanicException, match=message):\n        app.get_task(\"dummy_task\")\n"
  },
  {
    "path": "tests/test_custom_request.py",
    "content": "from io import BytesIO\n\nfrom sanic import Sanic\nfrom sanic.request import Request\nfrom sanic.response import json_dumps, text\n\n\nclass CustomRequest(Request):\n    \"\"\"Alternative implementation for loading body (non-streaming handlers)\"\"\"\n\n    async def receive_body(self):\n        buffer = BytesIO()\n        async for data in self.stream:\n            buffer.write(data)\n        self.body = buffer.getvalue().upper()\n        buffer.close()\n\n\ndef test_custom_request():\n    app = Sanic(name=\"Test\", request_class=CustomRequest)\n\n    @app.route(\"/post\", methods=[\"POST\"])\n    async def post_handler(request):\n        return text(\"OK\")\n\n    @app.route(\"/get\")\n    async def get_handler(request):\n        return text(\"OK\")\n\n    payload = {\"test\": \"OK\"}\n    headers = {\"content-type\": \"application/json\"}\n\n    request, response = app.test_client.post(\n        \"/post\", data=json_dumps(payload), headers=headers\n    )\n\n    assert request.body == b'{\"TEST\":\"OK\"}'\n    assert request.json.get(\"TEST\") == \"OK\"\n    assert response.text == \"OK\"\n    assert response.status == 200\n\n    request, response = app.test_client.get(\"/get\")\n\n    assert request.body == b\"\"\n    assert response.text == \"OK\"\n    assert response.status == 200\n"
  },
  {
    "path": "tests/test_daemon.py",
    "content": "\"\"\"Tests for daemon mode functionality.\"\"\"\n\nimport os\nimport signal\nimport sys\n\nfrom pathlib import Path\nfrom unittest.mock import MagicMock, patch\n\nimport pytest\n\nfrom sanic.cli.app import SanicCLI\nfrom sanic.compat import OS_IS_WINDOWS\nfrom sanic.worker.daemon import (\n    Daemon,\n    DaemonError,\n    PidfileInfo,\n    _get_default_runtime_dir,\n    _is_sanic_process,\n    _process_exists,\n    _sanitize_name,\n)\n\n\npytestmark = pytest.mark.skipif(\n    OS_IS_WINDOWS,\n    reason=\"Daemon mode is not supported on Windows\",\n)\n\n\n@pytest.fixture\ndef temp_pidfile(tmp_path):\n    \"\"\"Provide a temporary PID file path.\"\"\"\n    return tmp_path / \"sanic.pid\"\n\n\n@pytest.fixture\ndef temp_logfile(tmp_path):\n    \"\"\"Provide a temporary log file path.\"\"\"\n    return tmp_path / \"sanic.log\"\n\n\n@pytest.fixture\ndef temp_lockfile(tmp_path):\n    \"\"\"Provide a temporary lock file path.\"\"\"\n    return tmp_path / \"sanic.lock\"\n\n\n# Helper Function Tests: _get_default_runtime_dir\n\n\ndef test_get_default_runtime_dir_uses_xdg(tmp_path):\n    xdg_dir = tmp_path / \"xdg_runtime\"\n    xdg_dir.mkdir()\n\n    with patch.dict(os.environ, {\"XDG_RUNTIME_DIR\": str(xdg_dir)}):\n        result = _get_default_runtime_dir()\n\n    assert result == xdg_dir / \"sanic\"\n    assert result.exists()\n\n\ndef test_get_default_runtime_dir_falls_back_to_state_dir(tmp_path):\n    mock_home = tmp_path / \"home\"\n    mock_home.mkdir()\n\n    with patch.dict(os.environ, {\"XDG_RUNTIME_DIR\": \"\"}, clear=False):\n        with patch.object(Path, \"home\", return_value=mock_home):\n            result = _get_default_runtime_dir()\n\n    assert result == mock_home / \".local\" / \"state\" / \"sanic\"\n    assert result.exists()\n\n\ndef test_get_default_runtime_dir_xdg_mkdir_oserror(tmp_path):\n    mock_home = tmp_path / \"home\"\n    mock_home.mkdir()\n\n    xdg_dir = tmp_path / \"xdg_runtime\"\n    xdg_dir.mkdir()\n    xdg_dir.chmod(0o444)\n\n    try:\n        with patch.dict(os.environ, {\"XDG_RUNTIME_DIR\": str(xdg_dir)}):\n            with patch.object(Path, \"home\", return_value=mock_home):\n                result = _get_default_runtime_dir()\n        assert \"sanic\" in str(result)\n    finally:\n        xdg_dir.chmod(0o755)\n\n\ndef test_get_default_runtime_dir_falls_back_to_cache_dir(tmp_path):\n    mock_home = tmp_path / \"home\"\n    mock_home.mkdir()\n    cache_dir = mock_home / \".cache\" / \"sanic\"\n\n    call_count = {\"count\": 0}\n    original_mkdir = Path.mkdir\n\n    def selective_failing_mkdir(self, *args, **kwargs):\n        call_count[\"count\"] += 1\n        # Fail for state_dir (first call after XDG fails), succeed for cache\n        if \".local\" in str(self) and \"state\" in str(self):\n            raise OSError(\"Cannot create state directory\")\n        return original_mkdir(self, *args, **kwargs)\n\n    with patch.dict(os.environ, {\"XDG_RUNTIME_DIR\": \"\"}, clear=False):\n        with patch.object(Path, \"home\", return_value=mock_home):\n            with patch.object(Path, \"mkdir\", selective_failing_mkdir):\n                result = _get_default_runtime_dir()\n\n    assert result == cache_dir\n\n\ndef test_get_default_runtime_dir_falls_back_to_cwd(tmp_path, monkeypatch):\n    monkeypatch.chdir(tmp_path)\n\n    def failing_mkdir(self, *args, **kwargs):\n        raise OSError(\"Cannot create directory\")\n\n    with patch.dict(os.environ, {\"XDG_RUNTIME_DIR\": \"\"}, clear=False):\n        with patch.object(Path, \"mkdir\", failing_mkdir):\n            result = _get_default_runtime_dir()\n\n    assert result == Path.cwd()\n\n\n# Helper Function Tests: _sanitize_name\n\n\ndef test_sanitize_name_alphanumeric():\n    assert _sanitize_name(\"myapp\") == \"myapp\"\n    assert _sanitize_name(\"MyApp123\") == \"MyApp123\"\n\n\ndef test_sanitize_name_allowed_special_chars():\n    assert _sanitize_name(\"my-app\") == \"my-app\"\n    assert _sanitize_name(\"my_app\") == \"my_app\"\n    assert _sanitize_name(\"my.app\") == \"my.app\"\n\n\ndef test_sanitize_name_replaces_invalid_chars():\n    assert _sanitize_name(\"my app\") == \"my_app\"\n    assert _sanitize_name(\"my/app\") == \"my_app\"\n    assert _sanitize_name(\"my:app\") == \"my_app\"\n\n\ndef test_sanitize_name_strips_dots_underscores():\n    assert _sanitize_name(\"...myapp\") == \"myapp\"\n    assert _sanitize_name(\"myapp___\") == \"myapp\"\n    assert _sanitize_name(\"._myapp_.\") == \"myapp\"\n\n\ndef test_sanitize_name_empty_returns_sanic():\n    assert _sanitize_name(\"\") == \"sanic\"\n    assert _sanitize_name(\"...\") == \"sanic\"\n    assert _sanitize_name(\"___\") == \"sanic\"\n\n\n# Helper Function Tests: _process_exists\n\n\ndef test_process_exists_returns_true_for_current():\n    assert _process_exists(os.getpid()) is True\n\n\ndef test_process_exists_returns_false_for_nonexistent():\n    assert _process_exists(999999999) is False\n\n\ndef test_process_exists_returns_false_on_oserror():\n    with patch(\"os.kill\", side_effect=OSError(\"No such process\")):\n        assert _process_exists(12345) is False\n\n\n# Helper Function Tests: _is_sanic_process\n\n\ndef test_is_sanic_process_returns_false_on_oserror():\n    with patch(\"sanic.worker.daemon.Path\") as mock_path:\n        mock_cmdline = MagicMock()\n        mock_cmdline.exists.return_value = True\n        mock_cmdline.read_bytes.side_effect = OSError(\"Permission denied\")\n        mock_path.return_value = mock_cmdline\n\n        result = _is_sanic_process(12345)\n        assert result is False\n\n\ndef test_is_sanic_process_returns_true_when_proc_unavailable():\n    with patch(\"sanic.worker.daemon.Path\") as mock_path:\n        mock_cmdline = MagicMock()\n        mock_cmdline.exists.return_value = False\n        mock_path.return_value = mock_cmdline\n\n        result = _is_sanic_process(12345)\n        assert result is True\n\n\ndef test_is_sanic_process_returns_true_when_sanic_found():\n    with patch(\"sanic.worker.daemon.Path\") as mock_path:\n        mock_cmdline = MagicMock()\n        mock_cmdline.exists.return_value = True\n        mock_cmdline.read_bytes.return_value = b\"python\\x00-m\\x00sanic\\x00app\"\n        mock_path.return_value = mock_cmdline\n\n        result = _is_sanic_process(12345)\n        assert result is True\n\n\ndef test_is_sanic_process_returns_false_when_not_sanic():\n    with patch(\"sanic.worker.daemon.Path\") as mock_path:\n        mock_cmdline = MagicMock()\n        mock_cmdline.exists.return_value = True\n        mock_cmdline.read_bytes.return_value = b\"python\\x00-m\\x00flask\\x00app\"\n        mock_path.return_value = mock_cmdline\n\n        result = _is_sanic_process(12345)\n        assert result is False\n\n\n# PidfileInfo Tests\n\n\ndef test_pidfile_info_all_fields():\n    info = PidfileInfo(pid=123, started=1234567890, name=\"testapp\")\n    assert info.pid == 123\n    assert info.started == 1234567890\n    assert info.name == \"testapp\"\n\n\ndef test_pidfile_info_defaults():\n    info = PidfileInfo(pid=123)\n    assert info.pid == 123\n    assert info.started is None\n    assert info.name is None\n\n\ndef test_pidfile_info_frozen():\n    info = PidfileInfo(pid=123)\n    with pytest.raises(Exception):\n        info.pid = 456\n\n\n# Daemon Initialization Tests\n\n\ndef test_daemon_init_with_explicit_pidfile(tmp_path):\n    pidfile = tmp_path / \"explicit.pid\"\n    daemon = Daemon(pidfile=str(pidfile))\n    assert daemon.pidfile == pidfile\n\n\ndef test_daemon_init_with_auto_pidfile_and_name():\n    daemon = Daemon(name=\"TestApp\", pidfile=\"auto\")\n    assert daemon.pidfile is not None\n    assert daemon.pidfile.name == \"TestApp.pid\"\n\n\ndef test_daemon_init_with_empty_pidfile_string():\n    daemon = Daemon(name=\"TestApp\", pidfile=\"\")\n    assert daemon.pidfile is not None\n    assert daemon.pidfile.name == \"TestApp.pid\"\n\n\ndef test_daemon_init_with_auto_pidfile_no_name():\n    daemon = Daemon(pidfile=\"auto\")\n    assert daemon.pidfile is not None\n    # Should have a uuid-based name\n    assert daemon.pidfile.name.startswith(\"sanic-\")\n    assert daemon.pidfile.name.endswith(\".pid\")\n\n\ndef test_daemon_init_with_explicit_lockfile(tmp_path):\n    lockfile = tmp_path / \"explicit.lock\"\n    daemon = Daemon(lockfile=str(lockfile))\n    assert daemon._lockfile_path == lockfile\n\n\ndef test_daemon_init_no_pidfile():\n    daemon = Daemon()\n    assert daemon.pidfile is None\n\n\ndef test_daemon_init_with_logfile(tmp_path):\n    logfile = tmp_path / \"test.log\"\n    daemon = Daemon(logfile=str(logfile))\n    assert daemon.logfile == logfile\n\n\ndef test_daemon_init_with_user_and_group():\n    daemon = Daemon(user=\"testuser\", group=\"testgroup\")\n    assert daemon.user == \"testuser\"\n    assert daemon.group == \"testgroup\"\n\n\n# Daemon Validation Tests\n\n\ndef test_validates_nonexistent_user(temp_pidfile):\n    daemon = Daemon(pidfile=str(temp_pidfile), user=\"nonexistent_user_12345\")\n\n    with pytest.raises(DaemonError, match=\"does not exist\"):\n        daemon.validate()\n\n\ndef test_validates_nonexistent_group(temp_pidfile):\n    daemon = Daemon(pidfile=str(temp_pidfile), group=\"nonexistent_group_12345\")\n\n    with pytest.raises(DaemonError, match=\"does not exist\"):\n        daemon.validate()\n\n\ndef test_validates_pidfile_directory():\n    daemon = Daemon(pidfile=\"/nonexistent/path/sanic.pid\")\n\n    with pytest.raises(DaemonError, match=\"does not exist\"):\n        daemon.validate()\n\n\ndef test_validates_logfile_directory():\n    daemon = Daemon(logfile=\"/nonexistent/path/sanic.log\")\n\n    with pytest.raises(DaemonError, match=\"does not exist\"):\n        daemon.validate()\n\n\ndef test_validates_lockfile_directory():\n    daemon = Daemon(lockfile=\"/nonexistent/path/sanic.lock\")\n\n    with pytest.raises(DaemonError, match=\"does not exist\"):\n        daemon.validate()\n\n\ndef test_detects_already_running(temp_pidfile):\n    temp_pidfile.write_text(f\"sanic\\npid={os.getpid()}\\n\")\n\n    daemon = Daemon(pidfile=str(temp_pidfile))\n\n    with patch(\"sanic.worker.daemon._is_sanic_process\", return_value=True):\n        with pytest.raises(DaemonError, match=\"already running\"):\n            daemon.validate()\n\n\ndef test_ignores_stale_pidfile(temp_pidfile):\n    temp_pidfile.write_text(\"sanic\\npid=999999999\\n\")\n\n    daemon = Daemon(pidfile=str(temp_pidfile))\n    daemon.validate()\n\n\ndef test_validates_without_pidfile(temp_logfile):\n    daemon = Daemon(logfile=str(temp_logfile))\n    daemon.validate()\n\n\ndef test_validate_writable_dir_not_writable(tmp_path):\n    readonly_dir = tmp_path / \"readonly\"\n    readonly_dir.mkdir()\n    readonly_dir.chmod(0o444)\n\n    try:\n        daemon = Daemon(pidfile=str(readonly_dir / \"test.pid\"))\n        with pytest.raises(DaemonError, match=\"Cannot write\"):\n            daemon.validate()\n    finally:\n        readonly_dir.chmod(0o755)\n\n\ndef test_validate_user_sets_uid_and_gid(temp_pidfile):\n    daemon = Daemon(pidfile=str(temp_pidfile), user=\"root\")\n    daemon._validate_user_group()\n    assert daemon._uid == 0\n    assert daemon._gid is not None\n\n\ndef test_validate_group_sets_gid(temp_pidfile):\n    daemon = Daemon(pidfile=str(temp_pidfile), group=\"root\")\n    daemon._validate_user_group()\n    assert daemon._gid == 0\n\n\ndef test_validate_creates_default_lockfile(temp_pidfile):\n    daemon = Daemon(pidfile=str(temp_pidfile))\n    daemon._validate_paths()\n    assert daemon._lockfile_path == temp_pidfile.with_suffix(\".lock\")\n\n\n# PID File Tests\n\n\ndef test_writes_pidfile_with_marker(temp_pidfile):\n    daemon = Daemon(pidfile=str(temp_pidfile))\n    daemon._write_pidfile()\n\n    assert temp_pidfile.exists()\n    content = temp_pidfile.read_text()\n    lines = content.strip().split(\"\\n\")\n    assert lines[0] == \"sanic\"\n    assert lines[1].startswith(\"pid=\")\n    pid = int(lines[1].split(\"=\")[1])\n    assert pid == os.getpid()\n\n\ndef test_writes_pidfile_with_name(temp_pidfile):\n    daemon = Daemon(pidfile=str(temp_pidfile), name=\"MyApp\")\n    daemon._write_pidfile()\n\n    content = temp_pidfile.read_text()\n    assert \"name=MyApp\" in content\n\n\ndef test_writes_pidfile_with_started_timestamp(temp_pidfile):\n    daemon = Daemon(pidfile=str(temp_pidfile))\n    daemon._write_pidfile()\n\n    content = temp_pidfile.read_text()\n    assert \"started=\" in content\n\n\ndef test_write_pidfile_oserror(temp_pidfile):\n    daemon = Daemon(pidfile=str(temp_pidfile))\n\n    with patch.object(Path, \"write_text\", side_effect=OSError(\"Disk full\")):\n        with pytest.raises(DaemonError, match=\"Failed to write PID file\"):\n            daemon._write_pidfile()\n\n\ndef test_write_pidfile_skips_if_no_pidfile():\n    daemon = Daemon()\n    daemon._write_pidfile()  # Should not raise\n\n\ndef test_reads_pidfile_with_marker(temp_pidfile):\n    temp_pidfile.write_text(f\"sanic\\npid={os.getpid()}\\n\")\n\n    pid = Daemon.read_pidfile(temp_pidfile)\n    assert pid == os.getpid()\n\n\ndef test_rejects_pidfile_without_marker(temp_pidfile):\n    temp_pidfile.write_text(f\"{os.getpid()}\\n\")\n\n    pid = Daemon.read_pidfile(temp_pidfile)\n    assert pid is None\n\n\ndef test_no_pidfile_when_not_requested():\n    daemon = Daemon()\n    assert daemon.pidfile is None\n\n\ndef test_name_pidfile_generation():\n    daemon = Daemon(name=\"TestApp\", pidfile=\"auto\")\n    assert daemon.pidfile is not None\n    assert daemon.pidfile.name == \"TestApp.pid\"\n\n\ndef test_get_pidfile_path():\n    path = Daemon.get_pidfile_path(\"MyApp\")\n    assert path.name == \"MyApp.pid\"\n\n\ndef test_removes_pidfile_on_cleanup(temp_pidfile):\n    daemon = Daemon(pidfile=str(temp_pidfile))\n    daemon._write_pidfile()\n\n    assert temp_pidfile.exists()\n    daemon._remove_pidfile()\n    assert not temp_pidfile.exists()\n\n\ndef test_remove_pidfile_handles_missing_file(temp_pidfile):\n    daemon = Daemon(pidfile=str(temp_pidfile))\n    daemon._remove_pidfile()\n\n\ndef test_remove_pidfile_handles_oserror(temp_pidfile):\n    daemon = Daemon(pidfile=str(temp_pidfile))\n    temp_pidfile.write_text(\"test\")\n\n    with patch.object(\n        Path, \"unlink\", side_effect=OSError(\"Permission denied\")\n    ):\n        # Should not raise, just log\n        daemon._remove_pidfile()\n\n\ndef test_remove_pidfile_skips_if_no_pidfile():\n    daemon = Daemon()\n    daemon._remove_pidfile()  # Should not raise\n\n\n# read_pidfile_info Tests\n\n\ndef test_read_pidfile_info_returns_none_for_missing():\n    result = Daemon.read_pidfile_info(\"/nonexistent/path.pid\")\n    assert result is None\n\n\ndef test_read_pidfile_info_returns_none_on_read_error(temp_pidfile):\n    temp_pidfile.write_text(\"sanic\\npid=123\\n\")\n\n    with patch.object(Path, \"read_text\", side_effect=OSError(\"Read error\")):\n        result = Daemon.read_pidfile_info(temp_pidfile)\n        assert result is None\n\n\ndef test_read_pidfile_info_returns_none_for_empty_file(temp_pidfile):\n    temp_pidfile.write_text(\"\")\n    result = Daemon.read_pidfile_info(temp_pidfile)\n    assert result is None\n\n\ndef test_read_pidfile_info_returns_none_without_sanic_marker(temp_pidfile):\n    temp_pidfile.write_text(\"not_sanic\\npid=123\\n\")\n    result = Daemon.read_pidfile_info(temp_pidfile)\n    assert result is None\n\n\ndef test_read_pidfile_info_returns_none_for_invalid_pid(temp_pidfile):\n    temp_pidfile.write_text(\"sanic\\npid=notanumber\\n\")\n    result = Daemon.read_pidfile_info(temp_pidfile)\n    assert result is None\n\n\ndef test_read_pidfile_info_returns_none_for_missing_pid(temp_pidfile):\n    temp_pidfile.write_text(\"sanic\\nstarted=123\\n\")\n    result = Daemon.read_pidfile_info(temp_pidfile)\n    assert result is None\n\n\ndef test_read_pidfile_info_handles_invalid_started(temp_pidfile):\n    temp_pidfile.write_text(\"sanic\\npid=123\\nstarted=notanumber\\n\")\n    result = Daemon.read_pidfile_info(temp_pidfile)\n    assert result is not None\n    assert result.pid == 123\n    assert result.started is None\n\n\ndef test_read_pidfile_info_parses_all_fields(temp_pidfile):\n    temp_pidfile.write_text(\n        \"sanic\\npid=123\\nstarted=1234567890\\nname=testapp\\n\"\n    )\n    result = Daemon.read_pidfile_info(temp_pidfile)\n    assert result is not None\n    assert result.pid == 123\n    assert result.started == 1234567890\n    assert result.name == \"testapp\"\n\n\ndef test_read_pidfile_info_handles_empty_name(temp_pidfile):\n    temp_pidfile.write_text(\"sanic\\npid=123\\nname=\\n\")\n    result = Daemon.read_pidfile_info(temp_pidfile)\n    assert result is not None\n    assert result.name is None\n\n\n# Lockfile Tests\n\n\ndef test_acquire_lockfile_success(temp_lockfile):\n    daemon = Daemon(lockfile=str(temp_lockfile))\n    daemon._acquire_lockfile()\n\n    assert daemon._lock_fd is not None\n    assert temp_lockfile.exists()\n\n    # Cleanup\n    daemon._release_lockfile()\n\n\ndef test_acquire_lockfile_skips_if_no_lockfile():\n    daemon = Daemon()\n    daemon._lockfile_path = None\n    daemon._acquire_lockfile()  # Should not raise\n    assert daemon._lock_fd is None\n\n\ndef test_acquire_lockfile_fails_when_locked(temp_lockfile):\n    import fcntl\n\n    # Acquire lock manually\n    fd = os.open(str(temp_lockfile), os.O_RDWR | os.O_CREAT, 0o644)\n    fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)\n\n    try:\n        daemon = Daemon(lockfile=str(temp_lockfile))\n        with pytest.raises(DaemonError, match=\"already running.*lock held\"):\n            daemon._acquire_lockfile()\n    finally:\n        fcntl.flock(fd, fcntl.LOCK_UN)\n        os.close(fd)\n\n\ndef test_acquire_lockfile_oserror_on_open(tmp_path):\n    daemon = Daemon()\n    daemon._lockfile_path = tmp_path / \"nonexistent_dir\" / \"test.lock\"\n\n    with pytest.raises(DaemonError, match=\"Failed to open lock file\"):\n        daemon._acquire_lockfile()\n\n\ndef test_release_lockfile_skips_if_no_fd():\n    daemon = Daemon()\n    daemon._lock_fd = None\n    daemon._release_lockfile()  # Should not raise\n\n\ndef test_release_lockfile_handles_unlock_oserror(temp_lockfile):\n    import fcntl\n\n    daemon = Daemon(lockfile=str(temp_lockfile))\n    daemon._acquire_lockfile()\n\n    with patch.object(fcntl, \"flock\", side_effect=OSError(\"Unlock failed\")):\n        # Should not raise, just log\n        daemon._release_lockfile()\n\n\ndef test_release_lockfile_handles_close_oserror(temp_lockfile):\n    daemon = Daemon(lockfile=str(temp_lockfile))\n    daemon._acquire_lockfile()\n\n    with patch(\"os.close\", side_effect=OSError(\"Close failed\")):\n        # Should not raise, just log\n        daemon._release_lockfile()\n\n\ndef test_release_lockfile_handles_unlink_oserror(temp_lockfile):\n    daemon = Daemon(lockfile=str(temp_lockfile))\n    daemon._acquire_lockfile()\n\n    with patch.object(Path, \"unlink\", side_effect=OSError(\"Unlink failed\")):\n        # Should not raise, just log\n        daemon._release_lockfile()\n\n\ndef test_release_lockfile_cleans_up(temp_lockfile):\n    daemon = Daemon(lockfile=str(temp_lockfile))\n    daemon._acquire_lockfile()\n\n    assert daemon._lock_fd is not None\n    daemon._release_lockfile()\n\n    assert daemon._lock_fd is None\n\n\n# Stream Redirection Tests\n\n\ndef test_redirect_streams_to_devnull(tmp_path, temp_pidfile):\n    daemon = Daemon(pidfile=str(temp_pidfile))\n\n    # Mock stdin/stdout/stderr fileno() and the os functions\n    mock_stdin = MagicMock()\n    mock_stdin.fileno.return_value = 0\n    mock_stdin.flush = MagicMock()\n\n    mock_stdout = MagicMock()\n    mock_stdout.fileno.return_value = 1\n    mock_stdout.flush = MagicMock()\n\n    mock_stderr = MagicMock()\n    mock_stderr.fileno.return_value = 2\n\n    with patch(\"os.open\") as mock_open:\n        with patch(\"os.dup2\"):\n            with patch(\"os.close\"):\n                with patch.object(sys, \"stdin\", mock_stdin):\n                    with patch.object(sys, \"stdout\", mock_stdout):\n                        with patch.object(sys, \"stderr\", mock_stderr):\n                            mock_open.return_value = 10\n                            daemon._redirect_streams()\n\n    # Should have opened /dev/null twice (for stdin and stdout/stderr)\n    assert mock_open.call_count >= 2\n\n\ndef test_redirect_streams_to_logfile(temp_pidfile, temp_logfile):\n    daemon = Daemon(pidfile=str(temp_pidfile), logfile=str(temp_logfile))\n\n    mock_stdin = MagicMock()\n    mock_stdin.fileno.return_value = 0\n    mock_stdin.flush = MagicMock()\n\n    mock_stdout = MagicMock()\n    mock_stdout.fileno.return_value = 1\n    mock_stdout.flush = MagicMock()\n\n    mock_stderr = MagicMock()\n    mock_stderr.fileno.return_value = 2\n\n    with patch(\"os.open\") as mock_open:\n        with patch(\"os.dup2\"):\n            with patch(\"os.close\"):\n                with patch.object(sys, \"stdin\", mock_stdin):\n                    with patch.object(sys, \"stdout\", mock_stdout):\n                        with patch.object(sys, \"stderr\", mock_stderr):\n                            mock_open.return_value = 10\n                            daemon._redirect_streams()\n\n    # Should have opened logfile\n    calls = [str(c) for c in mock_open.call_args_list]\n    assert any(str(temp_logfile) in c for c in calls)\n\n\n# Signal Handler Tests\n\n\ndef test_setup_signal_handlers_skips_if_no_pidfile():\n    daemon = Daemon()\n    daemon._setup_signal_handlers()  # Should not raise\n\n\ndef test_setup_signal_handlers_installs_sighup(temp_pidfile):\n    daemon = Daemon(pidfile=str(temp_pidfile))\n\n    original_handler = signal.getsignal(signal.SIGHUP)\n    try:\n        daemon._setup_signal_handlers()\n\n        # Check that a handler was installed\n        new_handler = signal.getsignal(signal.SIGHUP)\n        assert new_handler != original_handler\n    finally:\n        signal.signal(signal.SIGHUP, original_handler)\n\n\ndef test_sighup_handler_rewrites_pidfile(temp_pidfile):\n    daemon = Daemon(pidfile=str(temp_pidfile))\n    daemon._write_pidfile()\n\n    original_handler = signal.getsignal(signal.SIGHUP)\n    try:\n        daemon._setup_signal_handlers()\n\n        # Trigger SIGHUP\n        handler = signal.getsignal(signal.SIGHUP)\n        handler(signal.SIGHUP, None)\n\n        # PID file should still exist with same pid\n        assert temp_pidfile.exists()\n        new_content = temp_pidfile.read_text()\n        assert f\"pid={os.getpid()}\" in new_content\n    finally:\n        signal.signal(signal.SIGHUP, original_handler)\n\n\ndef test_sighup_handler_calls_original_handler(temp_pidfile):\n    daemon = Daemon(pidfile=str(temp_pidfile))\n    daemon._write_pidfile()\n\n    handler_called = {\"called\": False, \"signum\": None}\n\n    def custom_handler(signum, frame):\n        handler_called[\"called\"] = True\n        handler_called[\"signum\"] = signum\n\n    original_handler = signal.getsignal(signal.SIGHUP)\n    try:\n        # Install a custom handler first\n        signal.signal(signal.SIGHUP, custom_handler)\n\n        # Now install daemon's handler which should chain to our custom one\n        daemon._setup_signal_handlers()\n\n        # Trigger SIGHUP\n        handler = signal.getsignal(signal.SIGHUP)\n        handler(signal.SIGHUP, None)\n\n        # Both handlers should have run\n        assert temp_pidfile.exists()\n        assert handler_called[\"called\"] is True\n        assert handler_called[\"signum\"] == signal.SIGHUP\n    finally:\n        signal.signal(signal.SIGHUP, original_handler)\n\n\n# Daemon Process Tests\n\n\ndef test_daemonize_forks_twice(temp_pidfile, temp_logfile):\n    fork_count = {\"count\": 0}\n\n    def mock_fork():\n        fork_count[\"count\"] += 1\n        return 0\n\n    daemon = Daemon(pidfile=str(temp_pidfile), logfile=str(temp_logfile))\n\n    with patch(\"os.fork\", side_effect=mock_fork):\n        with patch(\"os.setsid\"):\n            with patch(\"os.chdir\"):\n                with patch(\"os.umask\"):\n                    with patch.object(daemon, \"_redirect_streams\"):\n                        with patch.object(daemon, \"_write_pidfile\"):\n                            with patch.object(\n                                daemon, \"_setup_signal_handlers\"\n                            ):\n                                daemon.validate = MagicMock()\n                                daemon.daemonize()\n\n    assert fork_count[\"count\"] == 2\n\n\ndef test_daemonize_calls_validate(temp_pidfile):\n    daemon = Daemon(pidfile=str(temp_pidfile))\n    daemon.validate = MagicMock()\n\n    with patch(\"os.fork\", return_value=0):\n        with patch(\"os.setsid\"):\n            with patch(\"os.chdir\"):\n                with patch(\"os.umask\"):\n                    with patch.object(daemon, \"_redirect_streams\"):\n                        with patch.object(daemon, \"_write_pidfile\"):\n                            with patch.object(\n                                daemon, \"_setup_signal_handlers\"\n                            ):\n                                daemon.daemonize()\n\n    daemon.validate.assert_called_once()\n\n\ndef test_daemonize_first_fork_error(temp_pidfile):\n    daemon = Daemon(pidfile=str(temp_pidfile))\n    daemon.validate = MagicMock()\n\n    with patch(\"os.fork\", side_effect=OSError(\"Fork failed\")):\n        with pytest.raises(DaemonError, match=\"First fork failed\"):\n            daemon.daemonize()\n\n\ndef test_daemonize_second_fork_error(temp_pidfile):\n    daemon = Daemon(pidfile=str(temp_pidfile))\n    daemon.validate = MagicMock()\n\n    call_count = {\"count\": 0}\n\n    def mock_fork():\n        call_count[\"count\"] += 1\n        if call_count[\"count\"] == 1:\n            return 0\n        raise OSError(\"Second fork failed\")\n\n    with patch(\"os.fork\", side_effect=mock_fork):\n        with patch(\"os.setsid\"):\n            with patch(\"os.chdir\"):\n                with patch(\"os.umask\"):\n                    with pytest.raises(\n                        DaemonError, match=\"Second fork failed\"\n                    ):\n                        daemon.daemonize()\n\n\ndef test_daemonize_acquires_lockfile(temp_pidfile, temp_lockfile):\n    daemon = Daemon(pidfile=str(temp_pidfile), lockfile=str(temp_lockfile))\n    daemon.validate = MagicMock()\n\n    with patch(\"os.fork\", return_value=0):\n        with patch(\"os.setsid\"):\n            with patch(\"os.chdir\"):\n                with patch(\"os.umask\"):\n                    with patch.object(daemon, \"_redirect_streams\"):\n                        with patch.object(daemon, \"_write_pidfile\"):\n                            with patch.object(\n                                daemon, \"_setup_signal_handlers\"\n                            ):\n                                with patch.object(\n                                    daemon, \"_acquire_lockfile\"\n                                ) as mock_acquire:\n                                    daemon.daemonize()\n\n    mock_acquire.assert_called_once()\n\n\n# Privilege Tests\n\n\ndef test_drop_privileges_does_nothing_without_user_group():\n    daemon = Daemon()\n    daemon.drop_privileges()\n\n\ndef test_drop_privileges_warns_if_not_root(temp_pidfile):\n    daemon = Daemon(pidfile=str(temp_pidfile), user=\"root\")\n    daemon._uid = 0\n    daemon._gid = 0\n\n    with patch(\"os.getuid\", return_value=1000):  # Non-root\n        # Should log warning but not raise\n        daemon.drop_privileges()\n\n\ndef test_drop_privileges_requires_root(temp_pidfile):\n    daemon = Daemon(pidfile=str(temp_pidfile), user=\"root\")\n    daemon._uid = 0\n    daemon._gid = 0\n\n    with patch(\"os.getuid\", return_value=0):\n        with patch(\"os.setgid\", side_effect=PermissionError):\n            with pytest.raises(DaemonError, match=\"Are you root\"):\n                daemon.drop_privileges()\n\n\ndef test_drop_privileges_setuid_error(temp_pidfile):\n    daemon = Daemon(pidfile=str(temp_pidfile), user=\"root\")\n    daemon._uid = 0\n    daemon._gid = None\n\n    with patch(\"os.getuid\", return_value=0):\n        with patch(\"os.setuid\", side_effect=PermissionError):\n            with pytest.raises(DaemonError, match=\"Are you root\"):\n                daemon.drop_privileges()\n\n\ndef test_drop_privileges_sets_gid_and_initgroups(temp_pidfile):\n    daemon = Daemon(pidfile=str(temp_pidfile), user=\"root\", group=\"root\")\n    daemon._uid = 0\n    daemon._gid = 0\n\n    with patch(\"os.getuid\", return_value=0):\n        with patch(\"os.setgid\") as mock_setgid:\n            with patch(\"os.initgroups\") as mock_initgroups:\n                with patch(\"os.setuid\") as mock_setuid:\n                    daemon.drop_privileges()\n\n    mock_setgid.assert_called_once_with(0)\n    mock_initgroups.assert_called_once_with(\"root\", 0)\n    mock_setuid.assert_called_once_with(0)\n\n\ndef test_drop_privileges_skips_initgroups_without_user(temp_pidfile):\n    daemon = Daemon(pidfile=str(temp_pidfile), group=\"root\")\n    daemon._uid = None\n    daemon._gid = 0\n    daemon.user = None\n\n    with patch(\"os.getuid\", return_value=0):\n        with patch(\"os.setgid\") as mock_setgid:\n            with patch(\"os.initgroups\") as mock_initgroups:\n                daemon.drop_privileges()\n\n    mock_setgid.assert_called_once_with(0)\n    mock_initgroups.assert_not_called()\n\n\n# Windows Check Tests\n\n\ndef test_raises_on_windows():\n    with patch(\"sanic.worker.daemon.OS_IS_WINDOWS\", True):\n        from sanic.worker import daemon\n\n        with patch.object(daemon, \"OS_IS_WINDOWS\", True):\n            with pytest.raises(DaemonError, match=\"not supported on Windows\"):\n                daemon.Daemon()\n\n\n# CLI Integration Tests\n\n\ndef test_daemon_flag_parsed():\n    cli = SanicCLI()\n    cli.attach()\n    args = cli.parser.parse_args(\n        [\"fake.app:app\", \"--daemon\", \"--pidfile=/tmp/test.pid\"]\n    )\n\n    assert args.daemon is True\n    assert args.pidfile == \"/tmp/test.pid\"\n\n\ndef test_pidfile_without_daemon_fails():\n    cli = SanicCLI()\n    cli.attach()\n    args = cli.parser.parse_args([\"fake.app:app\", \"--pidfile=/tmp/test.pid\"])\n\n    with pytest.raises(SystemExit) as exc_info:\n        for group in cli.groups:\n            group.prepare(args)\n\n    assert \"require --daemon\" in str(exc_info.value)\n\n\ndef test_logfile_without_daemon_fails():\n    cli = SanicCLI()\n    cli.attach()\n    args = cli.parser.parse_args([\"fake.app:app\", \"--logfile=/tmp/test.log\"])\n\n    with pytest.raises(SystemExit) as exc_info:\n        for group in cli.groups:\n            group.prepare(args)\n\n    assert \"require --daemon\" in str(exc_info.value)\n\n\ndef test_daemon_with_dev_is_incompatible():\n    cli = SanicCLI()\n    cli.attach()\n    args = cli.parser.parse_args([\"fake.app:app\", \"--daemon\", \"--dev\"])\n    cli.args = args\n\n    with pytest.raises(SystemExit):\n        cli._setup_daemon(\"TestApp\")\n\n\ndef test_daemon_with_auto_reload_is_incompatible():\n    cli = SanicCLI()\n    cli.attach()\n    args = cli.parser.parse_args([\"fake.app:app\", \"--daemon\", \"--auto-reload\"])\n    cli.args = args\n\n    with pytest.raises(SystemExit):\n        cli._setup_daemon(\"TestApp\")\n\n\ndef test_daemon_with_repl_is_incompatible():\n    cli = SanicCLI()\n    cli.attach()\n    args = cli.parser.parse_args([\"fake.app:app\", \"--daemon\", \"--repl\"])\n    cli.args = args\n\n    with pytest.raises(SystemExit):\n        cli._setup_daemon(\"TestApp\")\n\n\ndef test_user_group_options_parsed():\n    cli = SanicCLI()\n    cli.attach()\n    args = cli.parser.parse_args(\n        [\"fake.app:app\", \"--daemon\", \"--user=nobody\", \"--group=nogroup\"]\n    )\n\n    assert args.daemon_user == \"nobody\"\n    assert args.daemon_group == \"nogroup\"\n\n\n# Kill Command Tests (always SIGKILL)\n\n\ndef test_kill_command_parses_pid():\n    with patch(\"sys.argv\", [\"sanic\", \"kill\", \"--pid\", \"12345\"]):\n        cli = SanicCLI()\n        cli.attach()\n        args = cli.parser.parse_args([\"--pid\", \"12345\"])\n\n    assert args.pid == 12345\n    assert args.pidfile is None\n\n\ndef test_kill_command_parses_pidfile():\n    with patch(\n        \"sys.argv\", [\"sanic\", \"kill\", \"--pidfile\", \"/var/run/sanic.pid\"]\n    ):\n        cli = SanicCLI()\n        cli.attach()\n        args = cli.parser.parse_args([\"--pidfile\", \"/var/run/sanic.pid\"])\n\n    assert args.pidfile == \"/var/run/sanic.pid\"\n    assert args.pid is None\n\n\ndef test_kill_command_requires_target():\n    with patch(\"sys.argv\", [\"sanic\", \"kill\"]):\n        cli = SanicCLI()\n        cli.attach()\n        with pytest.raises(SystemExit):\n            cli.parser.parse_args([])\n\n\ndef test_kill_always_sends_sigkill(temp_pidfile):\n    pid = os.getpid()\n    with open(temp_pidfile, \"w\") as f:\n        f.write(f\"sanic\\npid={pid}\\n\")\n\n    with patch(\"sys.argv\", [\"sanic\", \"kill\", \"--pidfile\", str(temp_pidfile)]):\n        cli = SanicCLI()\n        cli.attach()\n        with patch(\"os.kill\") as mock_kill:\n            cli._kill()\n            mock_kill.assert_called_once_with(pid, signal.SIGKILL)\n\n\ndef test_kill_removes_pidfile_after_success(temp_pidfile):\n    with open(temp_pidfile, \"w\") as f:\n        f.write(\"sanic\\npid=12345\\n\")\n\n    with patch(\"sys.argv\", [\"sanic\", \"kill\", \"--pidfile\", str(temp_pidfile)]):\n        cli = SanicCLI()\n        cli.attach()\n        with patch(\"os.kill\"):\n            cli._kill()\n\n    assert not temp_pidfile.exists()\n\n\ndef test_kill_handles_missing_pidfile(tmp_path):\n    missing_pidfile = tmp_path / \"nonexistent.pid\"\n\n    with patch(\n        \"sys.argv\", [\"sanic\", \"kill\", \"--pidfile\", str(missing_pidfile)]\n    ):\n        cli = SanicCLI()\n        cli.attach()\n        with pytest.raises(SystemExit):\n            cli._kill()\n\n\ndef test_kill_handles_process_not_found():\n    with patch(\"sys.argv\", [\"sanic\", \"kill\", \"--pid\", \"999999999\"]):\n        cli = SanicCLI()\n        cli.attach()\n        with pytest.raises(SystemExit):\n            cli._kill()\n\n\ndef test_kill_direct_pid():\n    with patch(\"sys.argv\", [\"sanic\", \"kill\", \"--pid\", \"12345\"]):\n        cli = SanicCLI()\n        cli.attach()\n        with patch(\"os.kill\") as mock_kill:\n            cli._kill()\n            mock_kill.assert_called_once_with(12345, signal.SIGKILL)\n\n\ndef test_kill_invalid_pidfile_fails(temp_pidfile):\n    temp_pidfile.write_text(\"12345\")\n\n    with patch(\"sys.argv\", [\"sanic\", \"kill\", \"--pidfile\", str(temp_pidfile)]):\n        cli = SanicCLI()\n        cli.attach()\n        with pytest.raises(SystemExit):\n            cli._kill()\n\n\n# Status Command Tests\n\n\ndef test_status_command_parses_pid():\n    with patch(\"sys.argv\", [\"sanic\", \"status\", \"--pid\", \"12345\"]):\n        cli = SanicCLI()\n        cli.attach()\n        args = cli.parser.parse_args([\"--pid\", \"12345\"])\n\n    assert args.pid == 12345\n    assert args.pidfile is None\n\n\ndef test_status_command_parses_pidfile():\n    with patch(\n        \"sys.argv\", [\"sanic\", \"status\", \"--pidfile\", \"/var/run/sanic.pid\"]\n    ):\n        cli = SanicCLI()\n        cli.attach()\n        args = cli.parser.parse_args([\"--pidfile\", \"/var/run/sanic.pid\"])\n\n    assert args.pidfile == \"/var/run/sanic.pid\"\n    assert args.pid is None\n\n\ndef test_status_command_requires_target():\n    with patch(\"sys.argv\", [\"sanic\", \"status\"]):\n        cli = SanicCLI()\n        cli.attach()\n        with pytest.raises(SystemExit):\n            cli.parser.parse_args([])\n\n\ndef test_status_running_process(temp_pidfile):\n    pid = os.getpid()\n    with open(temp_pidfile, \"w\") as f:\n        f.write(f\"sanic\\npid={pid}\\n\")\n\n    with patch(\n        \"sys.argv\", [\"sanic\", \"status\", \"--pidfile\", str(temp_pidfile)]\n    ):\n        cli = SanicCLI()\n        cli.attach()\n        cli._status()\n\n\ndef test_status_not_running_process(temp_pidfile):\n    with open(temp_pidfile, \"w\") as f:\n        f.write(\"sanic\\npid=999999999\\n\")\n\n    with patch(\n        \"sys.argv\", [\"sanic\", \"status\", \"--pidfile\", str(temp_pidfile)]\n    ):\n        cli = SanicCLI()\n        cli.attach()\n        with pytest.raises(SystemExit):\n            cli._status()\n\n\ndef test_status_direct_pid_running():\n    pid = os.getpid()\n\n    with patch(\"sys.argv\", [\"sanic\", \"status\", \"--pid\", str(pid)]):\n        cli = SanicCLI()\n        cli.attach()\n        cli._status()\n\n\ndef test_status_direct_pid_not_running():\n    with patch(\"sys.argv\", [\"sanic\", \"status\", \"--pid\", \"999999999\"]):\n        cli = SanicCLI()\n        cli.attach()\n        with pytest.raises(SystemExit):\n            cli._status()\n\n\n# Restart Command Tests (future use - not yet implemented)\n\n\ndef test_restart_command_parses_pid():\n    with patch(\"sys.argv\", [\"sanic\", \"restart\", \"--pid\", \"12345\"]):\n        cli = SanicCLI()\n        cli.attach()\n        args = cli.parser.parse_args([\"--pid\", \"12345\"])\n\n    assert args.pid == 12345\n    assert args.pidfile is None\n\n\ndef test_restart_command_parses_pidfile():\n    with patch(\n        \"sys.argv\", [\"sanic\", \"restart\", \"--pidfile\", \"/var/run/sanic.pid\"]\n    ):\n        cli = SanicCLI()\n        cli.attach()\n        args = cli.parser.parse_args([\"--pidfile\", \"/var/run/sanic.pid\"])\n\n    assert args.pidfile == \"/var/run/sanic.pid\"\n    assert args.pid is None\n\n\ndef test_restart_command_requires_target():\n    with patch(\"sys.argv\", [\"sanic\", \"restart\"]):\n        cli = SanicCLI()\n        cli.attach()\n        with pytest.raises(SystemExit):\n            cli.parser.parse_args([])\n\n\ndef test_restart_not_implemented(temp_pidfile, capsys):\n    pid = os.getpid()\n    with open(temp_pidfile, \"w\") as f:\n        f.write(f\"sanic\\npid={pid}\\n\")\n\n    with patch(\n        \"sys.argv\", [\"sanic\", \"restart\", \"--pidfile\", str(temp_pidfile)]\n    ):\n        cli = SanicCLI()\n        cli.attach()\n        with pytest.raises(SystemExit) as exc_info:\n            cli._restart()\n        assert exc_info.value.code == 0\n\n    captured = capsys.readouterr()\n    assert \"not yet implemented\" in captured.out\n"
  },
  {
    "path": "tests/test_deprecation.py",
    "content": "import pytest\n\nfrom sanic import Sanic\nfrom sanic.log import deprecation\n\n\ndef test_deprecation():\n    message = r\"\\[DEPRECATION v9\\.9\\] hello\"\n    with pytest.warns(DeprecationWarning, match=message):\n        deprecation(\"hello\", 9.9)\n\n\n@pytest.mark.parametrize(\n    \"filter,expected\",\n    ((\"default\", 1), (\"once\", 1), (\"ignore\", 0)),\n)\ndef test_deprecation_filter(app: Sanic, filter, expected, recwarn):\n    app.config.DEPRECATION_FILTER = filter\n    # Clear any warnings captured during test setup (e.g., from pytest_sanic)\n    recwarn.clear()\n    deprecation(\"hello\", 9.9)\n    assert len(recwarn) == expected\n"
  },
  {
    "path": "tests/test_dynamic_routes.py",
    "content": "import pytest\n\nfrom sanic_routing.exceptions import RouteExists\n\nfrom sanic.response import text\n\n\n@pytest.mark.parametrize(\n    \"method,attr, expected\",\n    [\n        (\"get\", \"text\", \"OK1 test\"),\n        (\"post\", \"text\", \"OK2 test\"),\n        (\"put\", \"text\", \"OK2 test\"),\n    ],\n)\ndef test_overload_dynamic_routes(app, method, attr, expected):\n    @app.route(\"/overload/<param>\", methods=[\"GET\"])\n    async def handler1(request, param):\n        return text(\"OK1 \" + param)\n\n    @app.route(\"/overload/<param>\", methods=[\"POST\", \"PUT\"])\n    async def handler2(request, param):\n        return text(\"OK2 \" + param)\n\n    request, response = getattr(app.test_client, method)(\"/overload/test\")\n    assert getattr(response, attr) == expected\n\n\ndef test_overload_dynamic_routes_exist(app):\n    @app.route(\"/overload/<param>\", methods=[\"GET\"])\n    async def handler1(request, param):\n        return text(\"OK1 \" + param)\n\n    @app.route(\"/overload/<param>\", methods=[\"POST\", \"PUT\"])\n    async def handler2(request, param):\n        return text(\"OK2 \" + param)\n\n    # if this doesn't raise an error, than at least the below should happen:\n    # assert response.text == 'Duplicated'\n    with pytest.raises(RouteExists):\n\n        @app.route(\"/overload/<param>\", methods=[\"PUT\", \"DELETE\"])\n        async def handler3(request, param):\n            return text(\"Duplicated\")\n"
  },
  {
    "path": "tests/test_errorpages.py",
    "content": "import logging\n\nimport pytest\n\nimport sanic\n\nfrom sanic import Sanic\nfrom sanic.config import Config\nfrom sanic.errorpages import TextRenderer, exception_response, guess_mime\nfrom sanic.exceptions import NotFound, SanicException\nfrom sanic.handlers import ErrorHandler\nfrom sanic.request import Request\nfrom sanic.response import HTTPResponse, empty, html, json, text\n\n\n@pytest.fixture\ndef app():\n    app = Sanic(\"error_page_testing\")\n\n    @app.route(\"/error\", methods=[\"GET\", \"POST\"])\n    def err(request):\n        raise Exception(\"something went wrong\")\n\n    @app.get(\"/forced_json/<fail>\", error_format=\"json\")\n    def manual_fail(request, fail):\n        if fail == \"fail\":\n            raise Exception\n        return html(\"\")  # Should be ignored\n\n    @app.get(\"/empty/<fail>\")\n    def empty_fail(request, fail):\n        if fail == \"fail\":\n            raise Exception\n        return empty()\n\n    @app.get(\"/json/<fail>\")\n    def json_fail(request, fail):\n        if fail == \"fail\":\n            raise Exception\n        # After 23.3 route format should become json, older versions think it\n        # is mixed due to empty mapping to html, and don't find any format.\n        return json({\"foo\": \"bar\"}) if fail == \"json\" else empty()\n\n    @app.get(\"/html/<fail>\")\n    def html_fail(request, fail):\n        if fail == \"fail\":\n            raise Exception\n        return html(\"<h1>foo</h1>\")\n\n    @app.get(\"/text/<fail>\")\n    def text_fail(request, fail):\n        if fail == \"fail\":\n            raise Exception\n        return text(\"foo\")\n\n    @app.get(\"/mixed/<param>\")\n    def mixed_fail(request, param):\n        if param not in (\"json\", \"html\"):\n            raise Exception\n        return json({}) if param == \"json\" else html(\"\")\n\n    return app\n\n\n@pytest.fixture\ndef fake_request(app):\n    return Request(b\"/foobar\", {\"accept\": \"*/*\"}, \"1.1\", \"GET\", None, app)\n\n\n@pytest.mark.parametrize(\n    \"fallback,content_type, exception, status\",\n    (\n        (None, \"text/plain; charset=utf-8\", Exception, 500),\n        (\"html\", \"text/html; charset=utf-8\", Exception, 500),\n        (\"auto\", \"text/plain; charset=utf-8\", Exception, 500),\n        (\"text\", \"text/plain; charset=utf-8\", Exception, 500),\n        (\"json\", \"application/json\", Exception, 500),\n        (None, \"text/plain; charset=utf-8\", NotFound, 404),\n        (\"html\", \"text/html; charset=utf-8\", NotFound, 404),\n        (\"auto\", \"text/plain; charset=utf-8\", NotFound, 404),\n        (\"text\", \"text/plain; charset=utf-8\", NotFound, 404),\n        (\"json\", \"application/json\", NotFound, 404),\n    ),\n)\ndef test_should_return_html_valid_setting(\n    fake_request, fallback, content_type, exception, status\n):\n    # Note: if fallback is None or \"auto\", prior to PR #2668 base was returned\n    # and after that a text response is given because it matches */*. Changed\n    # base to TextRenderer in this test, like it is in Sanic itself, so the\n    # test passes with either version but still covers everything that it did.\n    if fallback:\n        fake_request.app.config.FALLBACK_ERROR_FORMAT = fallback\n\n    try:\n        raise exception(\"bad stuff\")\n    except Exception as e:\n        response = exception_response(\n            fake_request,\n            e,\n            True,\n            base=TextRenderer,\n            fallback=fake_request.app.config.FALLBACK_ERROR_FORMAT,\n        )\n\n    assert isinstance(response, HTTPResponse)\n    assert response.status == status\n    assert response.content_type == content_type\n\n\ndef test_auto_fallback_with_data(app):\n    app.config.FALLBACK_ERROR_FORMAT = \"auto\"\n\n    _, response = app.test_client.get(\"/error\")\n    assert response.status == 500\n    assert response.content_type == \"text/plain; charset=utf-8\"\n\n    _, response = app.test_client.post(\"/error\", json={\"foo\": \"bar\"})\n    assert response.status == 500\n    assert response.content_type == \"application/json\"\n\n    _, response = app.test_client.post(\"/error\", data={\"foo\": \"bar\"})\n    assert response.status == 500\n    assert response.content_type == \"text/plain; charset=utf-8\"\n\n\ndef test_auto_fallback_with_content_type(app):\n    app.config.FALLBACK_ERROR_FORMAT = \"auto\"\n\n    _, response = app.test_client.get(\n        \"/error\", headers={\"content-type\": \"application/json\", \"accept\": \"*/*\"}\n    )\n    assert response.status == 500\n    assert response.content_type == \"application/json\"\n\n    _, response = app.test_client.get(\n        \"/error\", headers={\"content-type\": \"foo/bar\", \"accept\": \"*/*\"}\n    )\n    assert response.status == 500\n    assert response.content_type == \"text/plain; charset=utf-8\"\n\n\ndef test_route_error_format_set_on_auto(app):\n    @app.get(\"/text\")\n    def text_response(request):\n        return text(request.route.extra.error_format)\n\n    @app.get(\"/json\")\n    def json_response(request):\n        return json({\"format\": request.route.extra.error_format})\n\n    @app.get(\"/html\")\n    def html_response(request):\n        return html(request.route.extra.error_format)\n\n    _, response = app.test_client.get(\"/text\")\n    assert response.text == \"text\"\n\n    _, response = app.test_client.get(\"/json\")\n    assert response.json[\"format\"] == \"json\"\n\n    _, response = app.test_client.get(\"/html\")\n    assert response.text == \"html\"\n\n\ndef test_route_error_response_from_auto_route(app):\n    @app.get(\"/text\")\n    def text_response(request):\n        raise Exception(\"oops\")\n        return text(\"Never gonna see this\")\n\n    @app.get(\"/json\")\n    def json_response(request):\n        raise Exception(\"oops\")\n        return json({\"message\": \"Never gonna see this\"})\n\n    @app.get(\"/html\")\n    def html_response(request):\n        raise Exception(\"oops\")\n        return html(\"<h1>Never gonna see this</h1>\")\n\n    _, response = app.test_client.get(\"/text\")\n    assert response.content_type == \"text/plain; charset=utf-8\"\n\n    _, response = app.test_client.get(\"/json\")\n    assert response.content_type == \"application/json\"\n\n    _, response = app.test_client.get(\"/html\")\n    assert response.content_type == \"text/html; charset=utf-8\"\n\n\ndef test_route_error_response_from_explicit_format(app):\n    @app.get(\"/text\", error_format=\"json\")\n    def text_response(request):\n        raise Exception(\"oops\")\n        return text(\"Never gonna see this\")\n\n    @app.get(\"/json\", error_format=\"text\")\n    def json_response(request):\n        raise Exception(\"oops\")\n        return json({\"message\": \"Never gonna see this\"})\n\n    _, response = app.test_client.get(\"/text\")\n    assert response.content_type == \"application/json\"\n\n    _, response = app.test_client.get(\"/json\")\n    assert response.content_type == \"text/plain; charset=utf-8\"\n\n\ndef test_blueprint_error_response_from_explicit_format(app):\n    bp = sanic.Blueprint(\"MyBlueprint\")\n\n    @bp.get(\"/text\", error_format=\"json\")\n    def text_response(request):\n        raise Exception(\"oops\")\n        return text(\"Never gonna see this\")\n\n    @bp.get(\"/json\", error_format=\"text\")\n    def json_response(request):\n        raise Exception(\"oops\")\n        return json({\"message\": \"Never gonna see this\"})\n\n    app.blueprint(bp)\n    _, response = app.test_client.get(\"/text\")\n    assert response.content_type == \"application/json\"\n\n    _, response = app.test_client.get(\"/json\")\n    assert response.content_type == \"text/plain; charset=utf-8\"\n\n\ndef test_unknown_fallback_format(app):\n    with pytest.raises(SanicException, match=\"Unknown format: bad\"):\n        app.config.FALLBACK_ERROR_FORMAT = \"bad\"\n\n\ndef test_route_error_format_unknown(app):\n    with pytest.raises(SanicException, match=\"Unknown format: bad\"):\n\n        @app.get(\"/text\", error_format=\"bad\")\n        def handler(request): ...\n\n\ndef test_fallback_with_content_type_html(app):\n    app.config.FALLBACK_ERROR_FORMAT = \"auto\"\n\n    _, response = app.test_client.get(\n        \"/error\",\n        headers={\"content-type\": \"application/json\", \"accept\": \"text/html\"},\n    )\n    assert response.status == 500\n    assert response.content_type == \"text/html; charset=utf-8\"\n\n\ndef test_fallback_with_content_type_mismatch_accept(app):\n    app.config.FALLBACK_ERROR_FORMAT = \"auto\"\n\n    _, response = app.test_client.get(\n        \"/error\",\n        headers={\"content-type\": \"application/json\", \"accept\": \"text/plain\"},\n    )\n    assert response.status == 500\n    assert response.content_type == \"text/plain; charset=utf-8\"\n\n    _, response = app.test_client.get(\n        \"/error\",\n        headers={\"content-type\": \"text/html\", \"accept\": \"foo/bar\"},\n    )\n    assert response.status == 500\n    assert response.content_type == \"text/plain; charset=utf-8\"\n\n    app.router.reset()\n\n    @app.route(\"/alt1\", name=\"alt1\")\n    @app.route(\"/alt2\", error_format=\"text\", name=\"alt2\")\n    @app.route(\"/alt3\", error_format=\"html\", name=\"alt3\")\n    def handler(_):\n        raise Exception(\"problem here\")\n        # Yes, we know this return value is unreachable. This is on purpose.\n        return json({})\n\n    app.router.finalize()\n\n    _, response = app.test_client.get(\n        \"/alt1\",\n        headers={\"accept\": \"foo/bar\"},\n    )\n    assert response.status == 500\n    assert response.content_type == \"text/plain; charset=utf-8\"\n    _, response = app.test_client.get(\n        \"/alt1\",\n        headers={\"accept\": \"foo/bar,*/*\"},\n    )\n    assert response.status == 500\n    assert response.content_type == \"application/json\"\n\n    _, response = app.test_client.get(\n        \"/alt2\",\n        headers={\"accept\": \"foo/bar\"},\n    )\n    assert response.status == 500\n    assert response.content_type == \"text/plain; charset=utf-8\"\n    _, response = app.test_client.get(\n        \"/alt2\",\n        headers={\"accept\": \"foo/bar,*/*\"},\n    )\n    assert response.status == 500\n    assert response.content_type == \"text/plain; charset=utf-8\"\n\n    _, response = app.test_client.get(\n        \"/alt3\",\n        headers={\"accept\": \"foo/bar\"},\n    )\n    assert response.status == 500\n    assert response.content_type == \"text/plain; charset=utf-8\"\n\n    _, response = app.test_client.get(\n        \"/alt3\",\n        headers={\"accept\": \"foo/bar,text/html\"},\n    )\n    assert response.status == 500\n    assert response.content_type == \"text/html; charset=utf-8\"\n\n\n@pytest.mark.parametrize(\n    \"accept,content_type,expected\",\n    (\n        (None, None, \"text/plain; charset=utf-8\"),\n        (\"foo/bar\", None, \"text/plain; charset=utf-8\"),\n        (\"application/json\", None, \"application/json\"),\n        (\"application/json,text/plain\", None, \"application/json\"),\n        (\"text/plain,application/json\", None, \"application/json\"),\n        (\"text/plain,foo/bar\", None, \"text/plain; charset=utf-8\"),\n        (\"text/plain,text/html\", None, \"text/plain; charset=utf-8\"),\n        (\"*/*\", \"foo/bar\", \"text/plain; charset=utf-8\"),\n        (\"*/*\", \"application/json\", \"application/json\"),\n        # App wants text/plain but accept has equal entries for it\n        (\"text/*,*/plain\", None, \"text/plain; charset=utf-8\"),\n    ),\n)\ndef test_combinations_for_auto(fake_request, accept, content_type, expected):\n    if accept:\n        fake_request.headers[\"accept\"] = accept\n    else:\n        del fake_request.headers[\"accept\"]\n\n    if content_type:\n        fake_request.headers[\"content-type\"] = content_type\n\n    try:\n        raise Exception(\"bad stuff\")\n    except Exception as e:\n        response = exception_response(\n            fake_request,\n            e,\n            True,\n            base=TextRenderer,\n            fallback=\"auto\",\n        )\n\n    assert response.content_type == expected\n\n\ndef test_allow_fallback_error_format_set_main_process_start(app):\n    @app.main_process_start\n    async def start(app, _):\n        app.config.FALLBACK_ERROR_FORMAT = \"text\"\n\n    _, response = app.test_client.get(\"/error\")\n    assert response.status == 500\n    assert response.content_type == \"text/plain; charset=utf-8\"\n\n\ndef test_setting_fallback_on_config_changes_as_expected(app):\n    app.error_handler = ErrorHandler()\n\n    _, response = app.test_client.get(\"/error\")\n    assert response.content_type == \"text/plain; charset=utf-8\"\n\n    app.config.FALLBACK_ERROR_FORMAT = \"html\"\n    _, response = app.test_client.get(\"/error\")\n    assert response.content_type == \"text/html; charset=utf-8\"\n\n    app.config.FALLBACK_ERROR_FORMAT = \"text\"\n    _, response = app.test_client.get(\"/error\")\n    assert response.content_type == \"text/plain; charset=utf-8\"\n\n\ndef test_allow_fallback_error_format_in_config_injection():\n    class MyConfig(Config):\n        FALLBACK_ERROR_FORMAT = \"text\"\n\n    app = Sanic(\"test\", config=MyConfig())\n\n    @app.route(\"/error\", methods=[\"GET\", \"POST\"])\n    def err(request):\n        raise Exception(\"something went wrong\")\n\n    request, response = app.test_client.get(\"/error\")\n    assert response.status == 500\n    assert response.content_type == \"text/plain; charset=utf-8\"\n\n\ndef test_allow_fallback_error_format_in_config_replacement(app):\n    class MyConfig(Config):\n        FALLBACK_ERROR_FORMAT = \"text\"\n\n    app.config = MyConfig()\n\n    request, response = app.test_client.get(\"/error\")\n    assert response.status == 500\n    assert response.content_type == \"text/plain; charset=utf-8\"\n\n\ndef test_config_fallback_before_and_after_startup(app):\n    app.config.FALLBACK_ERROR_FORMAT = \"json\"\n\n    @app.main_process_start\n    async def start(app, _):\n        app.config.FALLBACK_ERROR_FORMAT = \"text\"\n\n    _, response = app.test_client.get(\"/error\")\n    assert response.status == 500\n    assert response.content_type == \"application/json\"\n\n\ndef test_config_fallback_using_update_dict(app):\n    app.config.update({\"FALLBACK_ERROR_FORMAT\": \"text\"})\n\n    _, response = app.test_client.get(\"/error\")\n    assert response.status == 500\n    assert response.content_type == \"text/plain; charset=utf-8\"\n\n\ndef test_config_fallback_using_update_kwarg(app):\n    app.config.update(FALLBACK_ERROR_FORMAT=\"text\")\n\n    _, response = app.test_client.get(\"/error\")\n    assert response.status == 500\n    assert response.content_type == \"text/plain; charset=utf-8\"\n\n\ndef test_config_fallback_bad_value(app):\n    message = \"Unknown format: fake\"\n    with pytest.raises(SanicException, match=message):\n        app.config.FALLBACK_ERROR_FORMAT = \"fake\"\n\n\n@pytest.mark.parametrize(\n    \"route_format,fallback,accept,expected\",\n    (\n        (\n            \"json\",\n            \"html\",\n            \"*/*\",\n            \"The client accepts */*, using 'json' from fakeroute\",\n        ),\n        (\n            \"json\",\n            \"auto\",\n            \"text/html,*/*;q=0.8\",\n            \"The client accepts text/html, using 'html' from any\",\n        ),\n        (\n            \"json\",\n            \"json\",\n            \"text/html,*/*;q=0.8\",\n            \"The client accepts */*;q=0.8, using 'json' from fakeroute\",\n        ),\n        (\n            \"\",\n            \"html\",\n            \"text/*,*/plain\",\n            \"The client accepts text/*, using 'html' from\"\n            \" FALLBACK_ERROR_FORMAT\",\n        ),\n        (\n            \"\",\n            \"json\",\n            \"text/*,*/*\",\n            \"The client accepts */*, using 'json' from FALLBACK_ERROR_FORMAT\",\n        ),\n        (\n            \"\",\n            \"auto\",\n            \"*/*,application/json;q=0.5\",\n            \"The client accepts */*, using 'json' from request.accept\",\n        ),\n        (\n            \"\",\n            \"auto\",\n            \"*/*\",\n            \"The client accepts */*, using 'json' from content-type\",\n        ),\n        (\n            \"\",\n            \"auto\",\n            \"text/html,text/plain\",\n            \"The client accepts text/plain, using 'text' from any\",\n        ),\n        (\n            \"\",\n            \"auto\",\n            \"text/html,text/plain;q=0.9\",\n            \"The client accepts text/html, using 'html' from any\",\n        ),\n        (\n            \"html\",\n            \"json\",\n            \"application/xml\",\n            \"No format found, the client accepts [application/xml]\",\n        ),\n        (\"\", \"auto\", \"*/*\", \"The client accepts */*, using 'text' from any\"),\n        (\"\", \"\", \"*/*\", \"No format found, the client accepts [*/*]\"),\n        # DEPRECATED: remove in 24.3\n        (\n            \"\",\n            \"auto\",\n            \"*/*\",\n            \"The client accepts */*, using 'json' from request.json\",\n        ),\n    ),\n)\ndef test_guess_mime_logging(\n    caplog, fake_request, route_format, fallback, accept, expected\n):\n    class FakeObject:\n        pass\n\n    fake_request.route = FakeObject()\n    fake_request.route.name = \"fakeroute\"\n    fake_request.route.extra = FakeObject()\n    fake_request.route.extra.error_format = route_format\n    if accept is None:\n        del fake_request.headers[\"accept\"]\n    else:\n        fake_request.headers[\"accept\"] = accept\n\n    if \"content-type\" in expected:\n        fake_request.headers[\"content-type\"] = \"application/json\"\n\n    # Fake JSON content (DEPRECATED: remove in 24.3)\n    if \"request.json\" in expected:\n        fake_request.parsed_json = {\"foo\": \"bar\"}\n\n    with caplog.at_level(logging.DEBUG, logger=\"sanic.root\"):\n        guess_mime(fake_request, fallback)\n\n    (logmsg,) = (\n        r.message for r in caplog.records if r.funcName == \"guess_mime\"\n    )\n\n    assert logmsg == f\"Error Page: {expected}\"\n\n\n@pytest.mark.parametrize(\n    \"format,expected\",\n    (\n        (\"html\", \"text/html; charset=utf-8\"),\n        (\"text\", \"text/plain; charset=utf-8\"),\n        (\"json\", \"application/json\"),\n    ),\n)\ndef test_exception_header_on_renderers(app: Sanic, format, expected):\n    app.config.FALLBACK_ERROR_FORMAT = format\n\n    @app.get(\"/test\")\n    def test(request):\n        raise SanicException(\n            \"test\", status_code=400, headers={\"exception\": \"test\"}\n        )\n\n    _, response = app.test_client.get(\"/test\")\n    assert response.status == 400\n    assert response.headers.get(\"exception\") == \"test\"\n    assert response.content_type == expected\n"
  },
  {
    "path": "tests/test_exceptions.py",
    "content": "import logging\n\nfrom itertools import count\n\nimport pytest\n\nfrom bs4 import BeautifulSoup\n\nfrom sanic import Sanic\nfrom sanic.exceptions import (\n    BadRequest,\n    ContentRangeError,\n    ExpectationFailed,\n    Forbidden,\n    HeaderExpectationFailed,\n    InvalidUsage,\n    MethodNotAllowed,\n    MethodNotSupported,\n    NotFound,\n    RangeNotSatisfiable,\n    SanicException,\n    ServerError,\n    Unauthorized,\n)\nfrom sanic.response import text\n\n\ndef dl_to_dict(soup, dl_id):\n    keys, values = [], []\n    for dl in soup.find_all(\"dl\", {\"id\": dl_id}):\n        for dt in dl.find_all(\"dt\"):\n            keys.append(dt.text.split(\":\", 1)[0])\n        for dd in dl.find_all(\"dd\"):\n            values.append(dd.text.strip())\n    return dict(zip(keys, values))\n\n\nclass SanicExceptionTestException(Exception):\n    pass\n\n\n@pytest.fixture(scope=\"module\")\ndef exception_app():\n    app = Sanic(\"test_exceptions\")\n    app.config.FALLBACK_ERROR_FORMAT = \"html\"\n\n    @app.route(\"/\")\n    def handler(request):\n        return text(\"OK\")\n\n    @app.route(\"/error\")\n    def handler_error(request):\n        raise ServerError(\"OK\")\n\n    @app.route(\"/404\")\n    def handler_404(request):\n        raise NotFound(\"OK\")\n\n    @app.route(\"/403\")\n    def handler_403(request):\n        raise Forbidden(\"Forbidden\")\n\n    @app.route(\"/401\")\n    def handler_401(request):\n        raise Unauthorized(\"Unauthorized\")\n\n    @app.route(\"/401/basic\")\n    def handler_401_basic(request):\n        raise Unauthorized(\"Unauthorized\", scheme=\"Basic\", realm=\"Sanic\")\n\n    @app.route(\"/401/digest\")\n    def handler_401_digest(request):\n        raise Unauthorized(\n            \"Unauthorized\",\n            scheme=\"Digest\",\n            realm=\"Sanic\",\n            qop=\"auth, auth-int\",\n            algorithm=\"MD5\",\n            nonce=\"abcdef\",\n            opaque=\"zyxwvu\",\n        )\n\n    @app.route(\"/401/bearer\")\n    def handler_401_bearer(request):\n        raise Unauthorized(\"Unauthorized\", scheme=\"Bearer\")\n\n    @app.route(\"/invalid\")\n    def handler_invalid(request):\n        raise BadRequest(\"OK\")\n\n    @app.route(\"/abort/401\")\n    def handler_401_error(request):\n        raise SanicException(status_code=401)\n\n    @app.route(\"/abort\")\n    def handler_500_error(request):\n        raise SanicException(status_code=500)\n\n    @app.route(\"/abort/message\")\n    def handler_abort_message(request):\n        raise SanicException(message=\"Custom Message\", status_code=500)\n\n    @app.route(\"/divide_by_zero\")\n    def handle_unhandled_exception(request):\n        _ = 1 / 0\n\n    @app.route(\"/error_in_error_handler_handler\")\n    def custom_error_handler(request):\n        raise SanicExceptionTestException(\"Dummy message!\")\n\n    @app.exception(SanicExceptionTestException)\n    def error_in_error_handler_handler(request, exception):\n        _ = 1 / 0\n\n    return app\n\n\ndef test_catch_exception_list(app):\n    @app.exception([SanicExceptionTestException, NotFound])\n    def exception_list(request, exception):\n        return text(\"ok\")\n\n    @app.route(\"/\")\n    def exception(request):\n        raise SanicExceptionTestException(\"You won't see me\")\n\n    request, response = app.test_client.get(\"/random\")\n    assert response.text == \"ok\"\n\n    request, response = app.test_client.get(\"/\")\n    assert response.text == \"ok\"\n\n\ndef test_no_exception(exception_app):\n    \"\"\"Test that a route works without an exception\"\"\"\n    request, response = exception_app.test_client.get(\"/\")\n    assert response.status == 200\n    assert response.text == \"OK\"\n\n\ndef test_server_error_exception(exception_app):\n    \"\"\"Test the built-in ServerError exception works\"\"\"\n    request, response = exception_app.test_client.get(\"/error\")\n    assert response.status == 500\n\n\ndef test_invalid_usage_exception(exception_app):\n    \"\"\"Test the built-in BadRequest exception works\"\"\"\n    request, response = exception_app.test_client.get(\"/invalid\")\n    assert response.status == 400\n\n\ndef test_not_found_exception(exception_app):\n    \"\"\"Test the built-in NotFound exception works\"\"\"\n    request, response = exception_app.test_client.get(\"/404\")\n    assert response.status == 404\n\n\ndef test_forbidden_exception(exception_app):\n    \"\"\"Test the built-in Forbidden exception\"\"\"\n    request, response = exception_app.test_client.get(\"/403\")\n    assert response.status == 403\n\n\ndef test_unauthorized_exception(exception_app):\n    \"\"\"Test the built-in Unauthorized exception\"\"\"\n    request, response = exception_app.test_client.get(\"/401\")\n    assert response.status == 401\n\n    request, response = exception_app.test_client.get(\"/401/basic\")\n    assert response.status == 401\n    assert response.headers.get(\"WWW-Authenticate\") is not None\n    assert response.headers.get(\"WWW-Authenticate\") == 'Basic realm=\"Sanic\"'\n\n    request, response = exception_app.test_client.get(\"/401/digest\")\n    assert response.status == 401\n\n    auth_header = response.headers.get(\"WWW-Authenticate\")\n    assert auth_header is not None\n    assert auth_header.startswith(\"Digest\")\n    assert 'qop=\"auth, auth-int\"' in auth_header\n    assert 'algorithm=\"MD5\"' in auth_header\n    assert 'nonce=\"abcdef\"' in auth_header\n    assert 'opaque=\"zyxwvu\"' in auth_header\n\n    request, response = exception_app.test_client.get(\"/401/bearer\")\n    assert response.status == 401\n    assert response.headers.get(\"WWW-Authenticate\") == \"Bearer\"\n\n\ndef test_handled_unhandled_exception(exception_app):\n    \"\"\"Test that an exception not built into sanic is handled\"\"\"\n    request, response = exception_app.test_client.get(\"/divide_by_zero\")\n    assert response.status == 500\n    soup = BeautifulSoup(response.body, \"html.parser\")\n    assert \"Internal Server Error\" in soup.h1.text\n\n    message = \" \".join(soup.p.text.split())\n    assert \"The application encountered an unexpected error\" in message\n\n\ndef test_exception_in_exception_handler(exception_app):\n    \"\"\"Test that an exception thrown in an error handler is handled\"\"\"\n    request, response = exception_app.test_client.get(\n        \"/error_in_error_handler_handler\"\n    )\n    assert response.status == 500\n    assert response.body == b\"An error occurred while handling an error\"\n\n\ndef test_exception_in_exception_handler_debug_off(exception_app):\n    \"\"\"Test that an exception thrown in an error handler is handled\"\"\"\n    request, response = exception_app.test_client.get(\n        \"/error_in_error_handler_handler\", debug=False\n    )\n    assert response.status == 500\n    assert response.body == b\"An error occurred while handling an error\"\n\n\ndef test_exception_in_exception_handler_debug_on(exception_app):\n    \"\"\"Test that an exception thrown in an error handler is handled\"\"\"\n    request, response = exception_app.test_client.get(\n        \"/error_in_error_handler_handler\", debug=True\n    )\n    assert response.status == 500\n    assert response.body.startswith(b\"Exception raised in exception \")\n\n\ndef test_sanic_exception(exception_app):\n    \"\"\"Test sanic exceptions are handled\"\"\"\n    request, response = exception_app.test_client.get(\"/abort/401\")\n    assert response.status == 401\n\n    request, response = exception_app.test_client.get(\"/abort\")\n    assert response.status == 500\n    # check fallback message\n    assert \"Internal Server Error\" in response.text\n\n    request, response = exception_app.test_client.get(\"/abort/message\")\n    assert response.status == 500\n    assert \"Custom Message\" in response.text\n\n\ndef test_custom_exception_default_message(exception_app):\n    class TeaError(SanicException):\n        message = \"Tempest in a teapot\"\n        status_code = 418\n\n    exception_app.router.reset()\n\n    @exception_app.get(\"/tempest\")\n    def tempest(_):\n        raise TeaError\n\n    _, response = exception_app.test_client.get(\"/tempest\", debug=True)\n    assert response.status == 418\n    assert b\"Tempest in a teapot\" in response.body\n\n\ndef test_exception_in_ws_logged(caplog):\n    app = Sanic(\"Test\")\n\n    @app.websocket(\"/feed\")\n    async def feed(request, ws):\n        raise Exception(\"...\")\n\n    with caplog.at_level(logging.INFO):\n        app.test_client.websocket(\"/feed\")\n\n    for record in caplog.record_tuples:\n        if record[2].startswith(\"Exception occurred\"):\n            break\n\n    assert record[0] == \"sanic.error\"\n    assert record[1] == logging.ERROR\n    assert \"Exception occurred while handling uri:\" in record[2]\n\n\n@pytest.mark.parametrize(\"debug\", (True, False))\ndef test_contextual_exception_context(debug):\n    app = Sanic(\"Test\")\n\n    class TeapotError(SanicException):\n        status_code = 418\n        message = \"Sorry, I cannot brew coffee\"\n\n    def fail():\n        raise TeapotError(context={\"foo\": \"bar\"})\n\n    app.post(\"/coffee/json\", error_format=\"json\", name=\"json\")(\n        lambda _: fail()\n    )\n    app.post(\"/coffee/html\", error_format=\"html\", name=\"html\")(\n        lambda _: fail()\n    )\n    app.post(\"/coffee/text\", error_format=\"text\", name=\"text\")(\n        lambda _: fail()\n    )\n\n    _, response = app.test_client.post(\"/coffee/json\", debug=debug)\n    assert response.status == 418\n    assert response.json[\"message\"] == \"Sorry, I cannot brew coffee\"\n    assert response.json[\"context\"] == {\"foo\": \"bar\"}\n\n    _, response = app.test_client.post(\"/coffee/html\", debug=debug)\n    soup = BeautifulSoup(response.body, \"html.parser\")\n    dl = dl_to_dict(soup, \"exception-context\")\n    assert response.status == 418\n    assert \"Sorry, I cannot brew coffee\" in soup.find(\"p\").text\n    assert dl == {\"foo\": \"bar\"}\n\n    _, response = app.test_client.post(\"/coffee/text\", debug=debug)\n    lines = list(map(lambda x: x.decode(), response.body.split(b\"\\n\")))\n    idx = lines.index(\"Context\") + 1\n    assert response.status == 418\n    assert lines[2] == \"Sorry, I cannot brew coffee\"\n    assert lines[idx] == '    foo: \"bar\"'\n\n\n@pytest.mark.parametrize(\"debug\", (True, False))\ndef test_contextual_exception_extra(debug):\n    app = Sanic(\"Test\")\n\n    class TeapotError(SanicException):\n        status_code = 418\n\n        @property\n        def message(self):\n            return f\"Found {self.extra['foo']}\"\n\n    def fail():\n        raise TeapotError(extra={\"foo\": \"bar\"})\n\n    app.post(\"/coffee/json\", error_format=\"json\", name=\"json\")(\n        lambda _: fail()\n    )\n    app.post(\"/coffee/html\", error_format=\"html\", name=\"html\")(\n        lambda _: fail()\n    )\n    app.post(\"/coffee/text\", error_format=\"text\", name=\"text\")(\n        lambda _: fail()\n    )\n\n    _, response = app.test_client.post(\"/coffee/json\", debug=debug)\n    assert response.status == 418\n    assert response.json[\"message\"] == \"Found bar\"\n    if debug:\n        assert response.json[\"extra\"] == {\"foo\": \"bar\"}\n    else:\n        assert \"extra\" not in response.json\n\n    _, response = app.test_client.post(\"/coffee/html\", debug=debug)\n    soup = BeautifulSoup(response.body, \"html.parser\")\n    dl = dl_to_dict(soup, \"exception-extra\")\n    assert response.status == 418\n    assert \"Found bar\" in soup.find(\"p\").text\n    if debug:\n        assert dl == {\"foo\": \"bar\"}\n    else:\n        assert not dl\n\n    _, response = app.test_client.post(\"/coffee/text\", debug=debug)\n    lines = list(map(lambda x: x.decode(), response.body.split(b\"\\n\")))\n    assert response.status == 418\n    assert lines[2] == \"Found bar\"\n    if debug:\n        idx = lines.index(\"Extra\") + 1\n        assert lines[idx] == '    foo: \"bar\"'\n    else:\n        assert \"Extra\" not in lines\n\n\n@pytest.mark.parametrize(\"override\", (True, False))\ndef test_contextual_exception_functional_message(override):\n    app = Sanic(\"Test\")\n\n    class TeapotError(SanicException):\n        status_code = 418\n\n        @property\n        def message(self):\n            return f\"Received foo={self.context['foo']}\"\n\n    @app.post(\"/coffee\", error_format=\"json\")\n    async def make_coffee(_):\n        error_args = {\"context\": {\"foo\": \"bar\"}}\n        if override:\n            error_args[\"message\"] = \"override\"\n        raise TeapotError(**error_args)\n\n    _, response = app.test_client.post(\"/coffee\", debug=True)\n    error_message = \"override\" if override else \"Received foo=bar\"\n    assert response.status == 418\n    assert response.json[\"message\"] == error_message\n    assert response.json[\"context\"] == {\"foo\": \"bar\"}\n\n\ndef test_exception_aliases():\n    assert InvalidUsage is BadRequest\n    assert MethodNotSupported is MethodNotAllowed\n    assert ContentRangeError is RangeNotSatisfiable\n    assert HeaderExpectationFailed is ExpectationFailed\n\n\ndef test_exception_message_attribute():\n    assert ServerError(\"it failed\").message == \"it failed\"\n    assert ServerError(b\"it failed\").message == \"it failed\"\n    assert (\n        ServerError().message == str(ServerError()) == \"Internal Server Error\"\n    )\n\n    class CustomError(SanicException):\n        message = \"Something bad happened\"\n\n    assert CustomError().message == CustomError.message == str(CustomError())\n    assert SanicException().message != \"\"\n    assert SanicException(\"\").message == \"\"\n\n\ndef test_exception_quiet_attribute():\n    class SilentException(SanicException):\n        quiet = True\n\n    class NoisyException(SanicException):\n        quiet = False\n\n    assert SilentException().quiet\n    assert not NoisyException().quiet\n    assert SilentException(quiet=True).quiet\n    assert NoisyException(quiet=True).quiet\n    assert not SilentException(quiet=False).quiet\n    assert not NoisyException(quiet=False).quiet\n\n\ndef test_request_middleware_exception_on_404(app: Sanic):\n    \"\"\"See https://github.com/sanic-org/sanic/issues/2950\"\"\"\n    counter = count()\n\n    @app.on_request\n    def request_middleware(request):\n        next(counter)\n        raise Exception\n\n    @app.route(\"/\")\n    async def handler(request): ...\n\n    _, response = app.test_client.get(\"/not-found\")\n\n    assert response.status == 500\n    assert next(counter) == 1\n"
  },
  {
    "path": "tests/test_exceptions_handler.py",
    "content": "import asyncio\nimport logging\n\nfrom typing import Callable\nfrom unittest.mock import Mock\n\nimport pytest\n\nfrom bs4 import BeautifulSoup\nfrom pytest import LogCaptureFixture, MonkeyPatch\n\nfrom sanic import Sanic, handlers\nfrom sanic.exceptions import BadRequest, Forbidden, NotFound, ServerError\nfrom sanic.handlers import ErrorHandler\nfrom sanic.request import Request\nfrom sanic.response import text\n\n\nclass ErrorWithRequestCtx(ServerError):\n    pass\n\n\n@pytest.fixture\ndef exception_handler_app():\n    exception_handler_app = Sanic(\"test_exception_handler\")\n\n    @exception_handler_app.route(\"/1\", error_format=\"html\")\n    def handler_1(request):\n        raise BadRequest(\"OK\")\n\n    @exception_handler_app.route(\"/2\", error_format=\"html\")\n    def handler_2(request):\n        raise ServerError(\"OK\")\n\n    @exception_handler_app.route(\"/3\", error_format=\"html\")\n    def handler_3(request):\n        raise NotFound(\"OK\")\n\n    @exception_handler_app.route(\"/4\", error_format=\"html\")\n    def handler_4(request):\n        foo = bar  # noqa -- F821\n        return text(foo)\n\n    @exception_handler_app.route(\"/5\", error_format=\"html\")\n    def handler_5(request):\n        class CustomServerError(ServerError):\n            pass\n\n        raise CustomServerError(\"Custom server error\")\n\n    @exception_handler_app.route(\"/6/<arg:int>\", error_format=\"html\")\n    def handler_6(request, arg):\n        try:\n            foo = 1 / arg\n        except Exception as e:\n            raise e from ValueError(f\"{arg}\")\n        return text(foo)\n\n    @exception_handler_app.route(\"/7\", error_format=\"html\")\n    def handler_7(request):\n        raise Forbidden(\"go away!\")\n\n    @exception_handler_app.route(\"/8\", error_format=\"html\")\n    def handler_8(request):\n        raise ErrorWithRequestCtx(\"OK\")\n\n    @exception_handler_app.exception(ErrorWithRequestCtx, NotFound)\n    def handler_exception_with_ctx(request, exception):\n        return text(request.ctx.middleware_ran)\n\n    @exception_handler_app.exception(ServerError)\n    def handler_exception(request, exception):\n        return text(\"OK\")\n\n    @exception_handler_app.exception(Forbidden)\n    async def async_handler_exception(request, exception):\n        response = await request.respond(content_type=\"text/csv\")\n        await response.send(\"foo,\")\n        await asyncio.sleep(0.001)\n        await response.send(\"bar\")\n\n    @exception_handler_app.middleware\n    async def some_request_middleware(request):\n        request.ctx.middleware_ran = \"Done.\"\n\n    return exception_handler_app\n\n\ndef test_invalid_usage_exception_handler(exception_handler_app: Sanic):\n    request, response = exception_handler_app.test_client.get(\"/1\")\n    assert response.status == 400\n\n\ndef test_server_error_exception_handler(exception_handler_app: Sanic):\n    request, response = exception_handler_app.test_client.get(\"/2\")\n    assert response.status == 200\n    assert response.text == \"OK\"\n\n\ndef test_not_found_exception_handler(exception_handler_app: Sanic):\n    request, response = exception_handler_app.test_client.get(\"/3\")\n    assert response.status == 200\n\n\ndef test_text_exception__handler(exception_handler_app: Sanic):\n    request, response = exception_handler_app.test_client.get(\"/random\")\n    assert response.status == 200\n    assert response.text == \"Done.\"\n\n\ndef test_async_exception_handler(exception_handler_app: Sanic):\n    request, response = exception_handler_app.test_client.get(\"/7\")\n    assert response.status == 200\n    assert response.text == \"foo,bar\"\n\n\ndef test_html_traceback_output_in_debug_mode(exception_handler_app: Sanic):\n    request, response = exception_handler_app.test_client.get(\"/4\", debug=True)\n    assert response.status == 500\n    soup = BeautifulSoup(response.body, \"html.parser\")\n    html = str(soup)\n    text_content = soup.get_text()\n\n    assert \"handler_4\" in html\n    assert \"foo = bar\" in text_content\n\n    summary_text = soup.select(\"h3\")[0].text\n    assert \"NameError: name 'bar' is not defined\" == summary_text\n    request_text = soup.select(\"h2\")[-1].text\n    assert \"GET /4\" == request_text\n\n\ndef test_inherited_exception_handler(exception_handler_app: Sanic):\n    request, response = exception_handler_app.test_client.get(\"/5\")\n    assert response.status == 200\n\n\ndef test_chained_exception_handler(exception_handler_app: Sanic):\n    request, response = exception_handler_app.test_client.get(\n        \"/6/0\", debug=True\n    )\n    assert response.status == 500\n\n    soup = BeautifulSoup(response.body, \"html.parser\")\n    html = str(soup)\n    text_content = soup.get_text()\n\n    assert \"handler_6\" in html\n    assert \"foo = 1 / arg\" in text_content\n    assert \"ValueError\" in html\n    assert \"GET /6\" in html\n\n    # Both exceptions should be present in the traceback headers\n    header_texts = [h.text for h in soup.select(\"h2, h3\")]\n    assert any(\"ZeroDivisionError\" in text for text in header_texts)\n    assert any(\"ValueError\" in text for text in header_texts)\n\n\ndef test_exception_handler_lookup(exception_handler_app: Sanic):\n    class CustomError(Exception):\n        pass\n\n    class CustomServerError(ServerError):\n        pass\n\n    def custom_error_handler():\n        pass\n\n    def server_error_handler():\n        pass\n\n    def import_error_handler():\n        pass\n\n    try:\n        ModuleNotFoundError  # noqa: F823\n    except Exception:\n\n        class ModuleNotFoundError(ImportError):\n            pass\n\n    handler = ErrorHandler()\n    handler.add(ImportError, import_error_handler)\n    handler.add(CustomError, custom_error_handler)\n    handler.add(ServerError, server_error_handler)\n\n    assert handler.lookup(ImportError(), None) == import_error_handler\n    assert handler.lookup(ModuleNotFoundError(), None) == import_error_handler\n    assert handler.lookup(CustomError(), None) == custom_error_handler\n    assert handler.lookup(ServerError(\"Error\"), None) == server_error_handler\n    assert (\n        handler.lookup(CustomServerError(\"Error\"), None)\n        == server_error_handler\n    )\n\n    # once again to ensure there is no caching bug\n    assert handler.lookup(ImportError(), None) == import_error_handler\n    assert handler.lookup(ModuleNotFoundError(), None) == import_error_handler\n    assert handler.lookup(CustomError(), None) == custom_error_handler\n    assert handler.lookup(ServerError(\"Error\"), None) == server_error_handler\n    assert (\n        handler.lookup(CustomServerError(\"Error\"), None)\n        == server_error_handler\n    )\n\n\ndef test_exception_handler_processed_request_middleware(\n    exception_handler_app: Sanic,\n):\n    request, response = exception_handler_app.test_client.get(\"/8\")\n    assert response.status == 200\n    assert response.text == \"Done.\"\n\n\ndef test_error_handler_noisy_log(\n    exception_handler_app: Sanic, monkeypatch: MonkeyPatch\n):\n    err_logger = Mock()\n    monkeypatch.setattr(handlers.error, \"error_logger\", err_logger)\n\n    exception_handler_app.config[\"NOISY_EXCEPTIONS\"] = False\n    exception_handler_app.test_client.get(\"/1\")\n    err_logger.exception.assert_not_called()\n\n    exception_handler_app.config[\"NOISY_EXCEPTIONS\"] = True\n    request, _ = exception_handler_app.test_client.get(\"/1\")\n    err_logger.exception.assert_called_with(\n        \"Exception occurred while handling uri: %s\", repr(request.url)\n    )\n\n\ndef test_exception_handler_response_was_sent(\n    app: Sanic,\n    caplog: LogCaptureFixture,\n    message_in_records: Callable[[list[logging.LogRecord], str], bool],\n):\n    exception_handler_ran = False\n\n    @app.exception(ServerError)\n    async def exception_handler(request, exception):\n        nonlocal exception_handler_ran\n        exception_handler_ran = True\n        return text(\"Error\")\n\n    @app.route(\"/1\")\n    async def handler1(request: Request):\n        response = await request.respond()\n        await response.send(\"some text\")\n        raise ServerError(\"Exception\")\n\n    @app.route(\"/2\")\n    async def handler2(request: Request):\n        await request.respond()\n        raise ServerError(\"Exception\")\n\n    with caplog.at_level(logging.WARNING):\n        _, response = app.test_client.get(\"/1\")\n        assert \"some text\" in response.text\n\n    message_in_records(\n        caplog.records,\n        (\n            \"An error occurred while handling the request after at \"\n            \"least some part of the response was sent to the client. \"\n            \"Therefore, the response from your custom exception \"\n        ),\n    )\n\n    _, response = app.test_client.get(\"/2\")\n    assert \"Error\" in response.text\n\n\ndef test_errir_on_duplicate(app: Sanic):\n    @app.exception(ServerError)\n    async def exception_handler_1(request, exception): ...\n\n    message = (\n        \"Duplicate exception handler definition on: route=__ALL_ROUTES__ and \"\n        \"exception=<class 'sanic.exceptions.ServerError'>\"\n    )\n    with pytest.raises(ServerError, match=message):\n\n        @app.exception(ServerError)\n        async def exception_handler_2(request, exception): ...\n"
  },
  {
    "path": "tests/test_ext_integration.py",
    "content": "import sys\n\nimport pytest\n\nfrom sanic import Sanic\n\n\ntry:\n    import sanic_ext  # noqa: F401\n\n    SANIC_EXT_IN_ENV = True\nexcept ImportError:\n    SANIC_EXT_IN_ENV = False\n\n\n@pytest.fixture\ndef stoppable_app(app):\n    @app.before_server_start\n    async def stop(*_):\n        app.stop()\n\n    return app\n\n\ndef test_ext_is_loaded(stoppable_app: Sanic, mock_sanic_ext, port):\n    stoppable_app.run(single_process=True, port=port)\n    mock_sanic_ext.Extend.assert_called_once_with(stoppable_app)\n\n\ndef test_ext_is_not_loaded(stoppable_app: Sanic, mock_sanic_ext, port):\n    stoppable_app.config.AUTO_EXTEND = False\n    stoppable_app.run(single_process=True, port=port)\n    mock_sanic_ext.Extend.assert_not_called()\n\n\ndef test_extend_with_args(stoppable_app: Sanic, mock_sanic_ext, port):\n    stoppable_app.extend(built_in_extensions=False)\n    stoppable_app.run(single_process=True, port=port)\n    mock_sanic_ext.Extend.assert_called_once_with(\n        stoppable_app, built_in_extensions=False, config=None, extensions=None\n    )\n\n\ndef test_access_object_sets_up_extension(app: Sanic, mock_sanic_ext):\n    app.ext\n    mock_sanic_ext.Extend.assert_called_once_with(app)\n\n\ndef test_extend_cannot_be_called_multiple_times(app: Sanic, mock_sanic_ext):\n    app.extend()\n\n    message = \"Cannot extend Sanic after Sanic Extensions has been setup.\"\n    with pytest.raises(RuntimeError, match=message):\n        app.extend()\n    mock_sanic_ext.Extend.assert_called_once_with(\n        app, extensions=None, built_in_extensions=True, config=None\n    )\n\n\n@pytest.mark.skipif(\n    SANIC_EXT_IN_ENV,\n    reason=\"Running tests with sanic_ext already in the environment\",\n)\ndef test_fail_if_not_loaded(app: Sanic):\n    del sys.modules[\"sanic_ext\"]\n    with pytest.raises(\n        RuntimeError, match=\"Sanic Extensions is not installed.*\"\n    ):\n        app.extend(built_in_extensions=False)\n\n\ndef test_can_access_app_ext_while_running(\n    app: Sanic, mock_sanic_ext, ext_instance, port\n):\n    class IceCream:\n        flavor: str\n\n    @app.before_server_start\n    async def injections(*_):\n        app.ext.injection(IceCream)\n        app.stop()\n\n    app.run(single_process=True, port=port)\n    ext_instance.injection.assert_called_with(IceCream)\n"
  },
  {
    "path": "tests/test_graceful_shutdown.py",
    "content": "import asyncio\nimport logging\n\nimport pytest\n\nfrom pytest import LogCaptureFixture\n\nfrom sanic.response import empty\n\n\n@pytest.mark.xfail(reason=\"This test runs fine locally, but fails on CI\")\ndef test_no_exceptions_when_cancel_pending_request(\n    app, caplog: LogCaptureFixture, port\n):\n    app.config.GRACEFUL_SHUTDOWN_TIMEOUT = 1\n\n    @app.get(\"/\")\n    async def handler(request):\n        await asyncio.sleep(5)\n\n    @app.listener(\"after_server_start\")\n    async def _request(sanic, loop):\n        connect = asyncio.open_connection(\"127.0.0.1\", port)\n        _, writer = await connect\n        writer.write(b\"GET / HTTP/1.1\\r\\n\\r\\n\")\n        app.stop()\n\n    with caplog.at_level(logging.INFO):\n        app.run(single_process=True, access_log=True, port=port)\n\n    assert \"Request: GET http:/// stopped. Transport is closed.\" in caplog.text\n\n\ndef test_completes_request(app, caplog: LogCaptureFixture, port):\n    app.config.GRACEFUL_SHUTDOWN_TIMEOUT = 1\n\n    @app.get(\"/\")\n    async def handler(request):\n        await asyncio.sleep(0.5)\n        return empty()\n\n    @app.listener(\"after_server_start\")\n    async def _request(sanic, loop):\n        connect = asyncio.open_connection(\"127.0.0.1\", port)\n        _, writer = await connect\n        writer.write(b\"GET / HTTP/1.1\\r\\n\\r\\n\")\n        app.stop()\n\n    with caplog.at_level(logging.INFO):\n        app.run(single_process=True, access_log=True, port=port)\n\n    assert (\"sanic.access\", 20, \"\") in caplog.record_tuples\n\n    # Make sure that the server starts shutdown process before access log\n    index_stopping = 0\n    for idx, record in enumerate(caplog.records):\n        if record.message.startswith(\"Stopping worker\"):\n            index_stopping = idx\n            break\n    index_request = caplog.record_tuples.index((\"sanic.access\", 20, \"\"))\n    assert index_request > index_stopping > 0\n"
  },
  {
    "path": "tests/test_handler.py",
    "content": "from sanic.app import Sanic\nfrom sanic.response import empty\nfrom sanic.signals import Event\n\n\ndef test_handler_operation_order(app: Sanic):\n    operations = []\n\n    @app.on_request\n    async def on_request(_):\n        nonlocal operations\n        operations.append(1)\n\n    @app.on_response\n    async def on_response(*_):\n        nonlocal operations\n        operations.append(5)\n\n    @app.get(\"/\")\n    async def handler(_):\n        nonlocal operations\n        operations.append(3)\n        return empty()\n\n    @app.signal(Event.HTTP_HANDLER_BEFORE)\n    async def handler_before(**_):\n        nonlocal operations\n        operations.append(2)\n\n    @app.signal(Event.HTTP_HANDLER_AFTER)\n    async def handler_after(**_):\n        nonlocal operations\n        operations.append(4)\n\n    app.test_client.get(\"/\")\n    assert operations == [1, 2, 3, 4, 5]\n"
  },
  {
    "path": "tests/test_handler_annotations.py",
    "content": "from uuid import UUID\n\nimport pytest\n\nfrom sanic import json\n\n\n@pytest.mark.parametrize(\n    \"idx,path,expectation\",\n    (\n        (0, \"/abc\", \"str\"),\n        (1, \"/123\", \"int\"),\n        (2, \"/123.5\", \"float\"),\n        (3, \"/8af729fe-2b94-4a95-a168-c07068568429\", \"UUID\"),\n    ),\n)\ndef test_annotated_handlers(app, idx, path, expectation):\n    def build_response(num, foo):\n        return json({\"num\": num, \"type\": type(foo).__name__})\n\n    @app.get(\"/<foo>\")\n    def handler0(_, foo: str):\n        return build_response(0, foo)\n\n    @app.get(\"/<foo>\")\n    def handler1(_, foo: int):\n        return build_response(1, foo)\n\n    @app.get(\"/<foo>\")\n    def handler2(_, foo: float):\n        return build_response(2, foo)\n\n    @app.get(\"/<foo>\")\n    def handler3(_, foo: UUID):\n        return build_response(3, foo)\n\n    _, response = app.test_client.get(path)\n    assert response.json[\"num\"] == idx\n    assert response.json[\"type\"] == expectation\n"
  },
  {
    "path": "tests/test_headers.py",
    "content": "from unittest.mock import Mock\n\nimport pytest\n\nfrom sanic import Sanic, headers, json, text\nfrom sanic.exceptions import InvalidHeader, PayloadTooLarge\nfrom sanic.http import Http\nfrom sanic.request import Request\n\n\ndef make_request(headers) -> Request:\n    return Request(b\"/\", headers, \"1.1\", \"GET\", None, None)\n\n\n@pytest.fixture\ndef raised_ceiling():\n    Http.HEADER_CEILING = 32_768\n    yield\n    Http.HEADER_CEILING = 16_384\n\n\n@pytest.mark.parametrize(\n    \"input, expected\",\n    [\n        (\"text/plain\", (\"text/plain\", {})),\n        (\"text/vnd.just.made.this.up ; \", (\"text/vnd.just.made.this.up\", {})),\n        (\n            \"text/plain;charset=us-ascii\",\n            (\"text/plain\", {\"charset\": \"us-ascii\"}),\n        ),\n        (\n            'text/plain ; charset=\"us-ascii\"',\n            (\"text/plain\", {\"charset\": \"us-ascii\"}),\n        ),\n        (\n            'text/plain ; charset=\"us-ascii\"; another=opt',\n            (\"text/plain\", {\"charset\": \"us-ascii\", \"another\": \"opt\"}),\n        ),\n        (\n            'attachment; filename=\"silly.txt\"',\n            (\"attachment\", {\"filename\": \"silly.txt\"}),\n        ),\n        (\n            'attachment; filename=\"strange;name\"',\n            (\"attachment\", {\"filename\": \"strange;name\"}),\n        ),\n        (\n            'attachment; filename=\"strange;name\";size=123;',\n            (\"attachment\", {\"filename\": \"strange;name\", \"size\": \"123\"}),\n        ),\n        (\n            'form-data; name=\"foo\"; value=\"%22\\\\%0D%0A\"',\n            (\"form-data\", {\"name\": \"foo\", \"value\": '\"\\\\\\n'}),\n        ),\n        # <input type=file name=\"foo&quot;;bar\\\"> with Unicode filename!\n        (\n            # Chrome, Firefox:\n            # Content-Disposition: form-data; name=\"foo%22;bar\\\"; filename=\"😀\"\n            'form-data; name=\"foo%22;bar\\\\\"; filename=\"😀\"',\n            (\"form-data\", {\"name\": 'foo\";bar\\\\', \"filename\": \"😀\"}),\n            # cgi: ('form-data', {'name': 'foo%22;bar\"; filename=\"😀'})\n            # werkzeug (pre 2.3.0): (\n            #   'form-data', {'name': 'foo%22;bar\"; filename='}\n            # )\n        ),\n    ],\n)\ndef test_parse_headers(input, expected):\n    assert headers.parse_content_header(input) == expected\n\n\n@pytest.mark.asyncio\nasync def test_header_size_exceeded():\n    recv_buffer = bytearray()\n\n    async def _receive_more():\n        nonlocal recv_buffer\n        recv_buffer += b\"123\"\n\n    protocol = Mock()\n    Http.set_header_max_size(1)\n    http = Http(protocol)\n    http._receive_more = _receive_more\n    http.recv_buffer = recv_buffer\n\n    with pytest.raises(PayloadTooLarge):\n        await http.http1_request_header()\n\n\n@pytest.mark.asyncio\nasync def test_header_size_increased_okay():\n    recv_buffer = bytearray()\n\n    async def _receive_more():\n        nonlocal recv_buffer\n        recv_buffer += b\"123\"\n\n    protocol = Mock()\n    Http.set_header_max_size(12_288)\n    http = Http(protocol)\n    http._receive_more = _receive_more\n    http.recv_buffer = recv_buffer\n\n    with pytest.raises(PayloadTooLarge):\n        await http.http1_request_header()\n\n    assert len(recv_buffer) == 12_291\n\n\n@pytest.mark.asyncio\nasync def test_header_size_exceeded_maxed_out():\n    recv_buffer = bytearray()\n\n    async def _receive_more():\n        nonlocal recv_buffer\n        recv_buffer += b\"123\"\n\n    protocol = Mock()\n    Http.set_header_max_size(18_432)\n    http = Http(protocol)\n    http._receive_more = _receive_more\n    http.recv_buffer = recv_buffer\n\n    with pytest.raises(PayloadTooLarge):\n        await http.http1_request_header()\n\n    assert len(recv_buffer) == 16_389\n\n\n@pytest.mark.asyncio\nasync def test_header_size_exceeded_raised_ceiling(raised_ceiling):\n    recv_buffer = bytearray()\n\n    async def _receive_more():\n        nonlocal recv_buffer\n        recv_buffer += b\"123\"\n\n    protocol = Mock()\n    http = Http(protocol)\n    Http.set_header_max_size(65_536)\n    http._receive_more = _receive_more\n    http.recv_buffer = recv_buffer\n\n    with pytest.raises(PayloadTooLarge):\n        await http.http1_request_header()\n\n    assert len(recv_buffer) == 32_772\n\n\ndef test_raw_headers(app):\n    app.route(\"/\")(lambda _: text(\"\"))\n    request, _ = app.test_client.get(\n        \"/\",\n        headers={\n            \"FOO\": \"bar\",\n            \"Host\": \"example.com\",\n            \"User-Agent\": \"Sanic-Testing\",\n        },\n    )\n\n    assert b\"Host: example.com\" in request.raw_headers\n    assert b\"Accept: */*\" in request.raw_headers\n    assert b\"Accept-Encoding: gzip, deflate\" in request.raw_headers\n    assert b\"Connection: keep-alive\" in request.raw_headers\n    assert b\"User-Agent: Sanic-Testing\" in request.raw_headers\n    assert b\"FOO: bar\" in request.raw_headers\n\n\ndef test_request_line(app):\n    app.route(\"/\")(lambda _: text(\"\"))\n    request, _ = app.test_client.get(\n        \"/\",\n        headers={\n            \"FOO\": \"bar\",\n            \"Host\": \"example.com\",\n            \"User-Agent\": \"Sanic-Testing\",\n        },\n    )\n\n    assert request.request_line == b\"GET / HTTP/1.1\"\n\n\n@pytest.mark.parametrize(\n    \"raw,expected_subtype\",\n    (\n        (\"show/first, show/second\", \"first\"),\n        (\"show/*, show/first\", \"first\"),\n        (\"*/*, show/first\", \"first\"),\n        (\"*/*, show/*\", \"*\"),\n        (\"other/*; q=0.1, show/*; q=0.2\", \"*\"),\n        (\"show/first; q=0.5, show/second; q=0.5\", \"first\"),\n        (\"show/first; foo=bar, show/second; foo=bar\", \"first\"),\n        (\"show/second, show/first; foo=bar\", \"first\"),\n        (\"show/second; q=0.5, show/first; foo=bar; q=0.5\", \"first\"),\n        (\"show/second; q=0.5, show/first; q=1.0\", \"first\"),\n        (\"show/first, show/second; q=1.0\", \"second\"),\n    ),\n)\ndef test_parse_accept_ordered_okay(raw, expected_subtype):\n    ordered = headers.parse_accept(raw)\n    assert ordered[0].type == \"show\"\n    assert ordered[0].subtype == expected_subtype\n\n\n@pytest.mark.parametrize(\n    \"raw\",\n    (\n        \"missing\",\n        \"missing/\",\n        \"/missing\",\n        \"/\",\n    ),\n)\ndef test_bad_accept(raw):\n    with pytest.raises(InvalidHeader):\n        headers.parse_accept(raw)\n\n\ndef test_empty_accept():\n    a = headers.parse_accept(\"\")\n    assert a == []\n    assert not a.match(\"*/*\")\n\n\ndef test_wildcard_accept_set_ok():\n    accept = headers.parse_accept(\"*/*\")[0]\n    assert accept.type == \"*\"\n    assert accept.subtype == \"*\"\n    assert accept.has_wildcard\n\n    accept = headers.parse_accept(\"foo/*\")[0]\n    assert accept.type == \"foo\"\n    assert accept.subtype == \"*\"\n    assert accept.has_wildcard\n\n    accept = headers.parse_accept(\"foo/bar\")[0]\n    assert accept.type == \"foo\"\n    assert accept.subtype == \"bar\"\n    assert not accept.has_wildcard\n\n\ndef test_accept_parsed_against_str():\n    accept = headers.Matched.parse(\"foo/bar\")\n    assert accept == \"foo/bar; q=0.1\"\n\n\ndef test_media_type_matching():\n    assert headers.MediaType(\"foo\", \"bar\").match(\n        headers.MediaType(\"foo\", \"bar\")\n    )\n    assert headers.MediaType(\"foo\", \"bar\").match(\"foo/bar\")\n\n\n@pytest.mark.parametrize(\n    \"value,other,outcome\",\n    (\n        # ALLOW BOTH\n        (\"foo/bar\", \"foo/bar\", True),\n        (\"foo/bar\", headers.Matched.parse(\"foo/bar\"), True),\n        (\"foo/bar\", \"foo/*\", True),\n        (\"foo/bar\", headers.Matched.parse(\"foo/*\"), True),\n        (\"foo/bar\", \"*/*\", True),\n        (\"foo/bar\", headers.Matched.parse(\"*/*\"), True),\n        (\"foo/*\", \"foo/bar\", True),\n        (\"foo/*\", headers.Matched.parse(\"foo/bar\"), True),\n        (\"foo/*\", \"foo/*\", True),\n        (\"foo/*\", headers.Matched.parse(\"foo/*\"), True),\n        (\"foo/*\", \"*/*\", True),\n        (\"foo/*\", headers.Matched.parse(\"*/*\"), True),\n        (\"*/*\", \"foo/bar\", True),\n        (\"*/*\", headers.Matched.parse(\"foo/bar\"), True),\n        (\"*/*\", \"foo/*\", True),\n        (\"*/*\", headers.Matched.parse(\"foo/*\"), True),\n        (\"*/*\", \"*/*\", True),\n        (\"*/*\", headers.Matched.parse(\"*/*\"), True),\n    ),\n)\ndef test_accept_matching(value, other, outcome):\n    assert bool(headers.Matched.parse(value).match(other)) is outcome\n\n\n@pytest.mark.parametrize(\"value\", (\"foo/bar\", \"foo/*\", \"*/*\"))\ndef test_value_in_accept(value):\n    acceptable = headers.parse_accept(value)\n    assert acceptable.match(\"foo/bar\")\n    assert acceptable.match(\"foo/*\")\n    assert acceptable.match(\"*/*\")\n\n\n@pytest.mark.parametrize(\"value\", (\"foo/bar\", \"foo/*\"))\ndef test_value_not_in_accept(value):\n    acceptable = headers.parse_accept(value)\n    assert not acceptable.match(\"no/match\")\n    assert not acceptable.match(\"no/*\")\n    assert \"*/*\" not in acceptable\n    assert \"*/bar\" not in acceptable\n\n\n@pytest.mark.parametrize(\n    \"header,expected\",\n    (\n        (\n            \"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8\",  # noqa: E501\n            [\n                \"text/html\",\n                \"application/xhtml+xml\",\n                \"image/avif\",\n                \"image/webp\",\n                \"application/xml;q=0.9\",\n                \"*/*;q=0.8\",\n            ],\n        ),\n    ),\n)\ndef test_browser_headers_general(header, expected):\n    request = Request(b\"/\", {\"accept\": header}, \"1.1\", \"GET\", None, None)\n    assert [str(item) for item in request.accept] == expected\n\n\n@pytest.mark.parametrize(\n    \"header,expected\",\n    (\n        (\n            \"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8\",  # noqa: E501\n            [\n                (\"text/html\", 1.0),\n                (\"application/xhtml+xml\", 1.0),\n                (\"image/avif\", 1.0),\n                (\"image/webp\", 1.0),\n                (\"application/xml\", 0.9),\n                (\"*/*\", 0.8),\n            ],\n        ),\n    ),\n)\ndef test_browser_headers_specific(header, expected):\n    mimes = [e[0] for e in expected]\n    qs = [e[1] for e in expected]\n    request = Request(b\"/\", {\"accept\": header}, \"1.1\", \"GET\", None, None)\n    assert request.accept == mimes\n    for a, m, q in zip(request.accept, mimes, qs):\n        assert a == m\n        assert a.mime == m\n        assert a.q == q\n\n\n@pytest.mark.parametrize(\n    \"raw\",\n    (\n        \"text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8\",\n        \"application/xml;q=0.9, */*;q=0.8, text/html, application/xhtml+xml\",\n        (\n            \"foo/bar;q=0.9, */*;q=0.8, text/html=0.8, \"\n            \"text/plain, application/xhtml+xml\"\n        ),\n    ),\n)\ndef test_accept_ordering(raw):\n    \"\"\"Should sort by q but also be stable.\"\"\"\n    accept = headers.parse_accept(raw)\n    assert accept[0].type == \"text\"\n    raw1 = \", \".join(str(a) for a in accept)\n    accept = headers.parse_accept(raw1)\n    raw2 = \", \".join(str(a) for a in accept)\n    assert raw1 == raw2\n\n\ndef test_not_accept_wildcard():\n    accept = headers.parse_accept(\"*/*, foo/*, */bar, foo/bar;q=0.1\")\n    assert not accept.match(\n        \"text/html\", \"foo/foo\", \"bar/bar\", accept_wildcards=False\n    )\n    # Should ignore wildcards in accept but still matches them from mimes\n    m = accept.match(\"text/plain\", \"*/*\", accept_wildcards=False)\n    assert m.mime == \"*/*\"\n    assert m.match(\"*/*\")\n    assert m.header == \"foo/bar\"\n    assert not accept.match(\n        \"text/html\", \"foo/foo\", \"bar/bar\", accept_wildcards=False\n    )\n\n\ndef test_accept_misc():\n    header = (\n        \"foo/bar;q=0.0, */plain;param=123, text/plain, text/*, foo/bar;q=0.5\"\n    )\n    a = headers.parse_accept(header)\n    assert repr(a) == (\n        \"[*/plain;param=123, text/plain, text/*, foo/bar;q=0.5, foo/bar;q=0.0]\"\n    )  # noqa: E501\n    assert str(a) == (\n        \"*/plain;param=123, text/plain, text/*, foo/bar;q=0.5, foo/bar;q=0.0\"\n    )  # noqa: E501\n    # q=1 types don't match foo/bar but match the two others,\n    # text/* comes first and matches */plain because it\n    # comes first in the header\n    m = a.match(\"foo/bar\", \"text/*\", \"text/plain\")\n    assert repr(m) == \"<text/* matched */plain;param=123>\"\n    assert m == \"text/*\"\n    assert m.mime == \"text/*\"\n    assert m.header.mime == \"*/plain\"\n    assert m.header.type == \"*\"\n    assert m.header.subtype == \"plain\"\n    assert m.header.q == 1.0\n    assert m.header.params == dict(param=\"123\")\n    # Matches object against another Matched object (by mime and header)\n    assert m == a.match(\"text/*\")\n    # Against unsupported type falls back to object id matching\n    assert m != 123\n    # Matches the highest q value\n    m = a.match(\"foo/bar\")\n    assert repr(m) == \"<foo/bar matched foo/bar;q=0.5>\"\n    assert m == \"foo/bar\"\n    assert m == \"foo/bar;q=0.5\"\n    # Matching nothing special case\n    m = a.match()\n    assert m == \"\"\n    assert m.header is None\n    # No header means anything\n    a = headers.parse_accept(None)\n    assert a == [\"*/*\"]\n    assert a.match(\"foo/bar\")\n    # Empty header means nothing\n    a = headers.parse_accept(\"\")\n    assert a == []\n    assert not a.match(\"foo/bar\")\n\n\n@pytest.mark.parametrize(\n    \"headers,expected\",\n    (\n        ({\"foo\": \"bar\"}, \"bar\"),\n        (((\"foo\", \"bar\"), (\"foo\", \"baz\")), \"bar,baz\"),\n        ({}, \"\"),\n    ),\n)\ndef test_field_simple_accessor(headers, expected):\n    request = make_request(headers)\n    assert request.headers.foo == request.headers.foo_ == expected\n\n\n@pytest.mark.parametrize(\n    \"headers,expected\",\n    (\n        ({\"foo-bar\": \"bar\"}, \"bar\"),\n        (((\"foo-bar\", \"bar\"), (\"foo-bar\", \"baz\")), \"bar,baz\"),\n    ),\n)\ndef test_field_hyphenated_accessor(headers, expected):\n    request = make_request(headers)\n    assert request.headers.foo_bar == request.headers.foo_bar_ == expected\n\n\ndef test_bad_accessor():\n    request = make_request({})\n    msg = \"'Header' object has no attribute '_foo'\"\n    with pytest.raises(AttributeError, match=msg):\n        request.headers._foo\n\n\ndef test_multiple_fields_accessor(app: Sanic):\n    @app.get(\"\")\n    async def handler(request: Request):\n        return json({\"field\": request.headers.example_field})\n\n    _, response = app.test_client.get(\n        \"/\", headers=((\"Example-Field\", \"Foo, Bar\"), (\"Example-Field\", \"Baz\"))\n    )\n    assert response.json[\"field\"] == \"Foo, Bar,Baz\"\n"
  },
  {
    "path": "tests/test_helpers.py",
    "content": "import inspect\n\nimport pytest\n\nfrom sanic import helpers\nfrom sanic.config import Config\n\n\ndef test_has_message_body():\n    tests = (\n        (100, False),\n        (102, False),\n        (204, False),\n        (200, True),\n        (304, False),\n        (400, True),\n    )\n    for status_code, expected in tests:\n        assert helpers.has_message_body(status_code) is expected\n\n\ndef test_is_entity_header():\n    tests = (\n        (\"allow\", True),\n        (\"extension-header\", True),\n        (\"\", False),\n        (\"test\", False),\n    )\n    for header, expected in tests:\n        assert helpers.is_entity_header(header) is expected\n\n\ndef test_is_hop_by_hop_header():\n    tests = (\n        (\"connection\", True),\n        (\"upgrade\", True),\n        (\"\", False),\n        (\"test\", False),\n    )\n    for header, expected in tests:\n        assert helpers.is_hop_by_hop_header(header) is expected\n\n\ndef test_import_string_class():\n    obj = helpers.import_string(\"sanic.config.Config\")\n    assert isinstance(obj, Config)\n\n\ndef test_import_string_module():\n    module = helpers.import_string(\"sanic.config\")\n    assert inspect.ismodule(module)\n\n\ndef test_import_string_exception():\n    with pytest.raises(ImportError):\n        helpers.import_string(\"test.test.test\")\n"
  },
  {
    "path": "tests/test_http.py",
    "content": "import json as stdjson\n\nfrom collections import namedtuple\nfrom pathlib import Path\nfrom sys import version_info\n\nimport pytest\n\nfrom sanic_testing.reusable import ReusableClient\n\nfrom sanic import json, text\nfrom sanic.app import Sanic\nfrom tests.client import RawClient\n\n\nparent_dir = Path(__file__).parent\nlocalhost_dir = parent_dir / \"certs/localhost\"\n\n\n@pytest.fixture\ndef test_app(app: Sanic):\n    app.config.KEEP_ALIVE_TIMEOUT = 1\n\n    @app.get(\"/\")\n    async def base_handler(request):\n        return text(\"111122223333444455556666777788889999\")\n\n    @app.post(\"/upload\", stream=True)\n    async def upload_handler(request):\n        data = [part.decode(\"utf-8\") async for part in request.stream]\n        return json(data)\n\n    return app\n\n\n@pytest.fixture\ndef runner(test_app: Sanic, port):\n    client = ReusableClient(test_app, port=port)\n    client.run()\n    yield client\n    client.stop()\n\n\n@pytest.fixture\ndef client(runner: ReusableClient):\n    client = namedtuple(\"Client\", (\"raw\", \"send\", \"recv\"))\n\n    raw = RawClient(runner.host, runner.port)\n    runner._run(raw.connect())\n\n    def send(msg):\n        nonlocal runner\n        nonlocal raw\n        runner._run(raw.send(msg))\n\n    def recv(**kwargs):\n        nonlocal runner\n        nonlocal raw\n        method = raw.recv_until if \"until\" in kwargs else raw.recv\n        return runner._run(method(**kwargs))\n\n    yield client(raw, send, recv)\n\n    runner._run(raw.close())\n\n\ndef test_full_message(client):\n    client.send(\n        \"\"\"\n        GET / HTTP/1.1\n        host: localhost:7777\n\n        \"\"\"\n    )\n    response = client.recv()\n\n    # AltSvcCheck touchup removes the Alt-Svc header from the\n    # response in the Python 3.9+ in this case\n    assert len(response) == (151 if version_info < (3, 9) else 140)\n    assert b\"200 OK\" in response\n\n\ndef test_transfer_chunked(client):\n    client.send(\n        \"\"\"\n        POST /upload HTTP/1.1\n        transfer-encoding: chunked\n\n        \"\"\"\n    )\n    client.send(b\"3\\r\\nfoo\\r\\n\")\n    client.send(b\"3\\r\\nbar\\r\\n\")\n    client.send(b\"0\\r\\n\\r\\n\")\n    response = client.recv()\n    _, body = response.rsplit(b\"\\r\\n\\r\\n\", 1)\n    data = stdjson.loads(body)\n\n    assert data == [\"foo\", \"bar\"]\n\n\ndef test_url_encoding(client):\n    client.send(\n        \"\"\"\n        GET /invalid\\xa0url HTTP/1.1\n\n        \"\"\"\n    )\n    response = client.recv()\n    headers, body = response.rsplit(b\"\\r\\n\\r\\n\", 1)\n\n    assert b\"400 Bad Request\" in headers\n    assert b\"URL may only contain US-ASCII characters.\" in body\n\n\n@pytest.mark.parametrize(\n    \"content_length\",\n    (\n        b\"-50\",\n        b\"+50\",\n        b\"5_0\",\n        b\"50.5\",\n    ),\n)\ndef test_invalid_content_length(content_length, client):\n    body = b\"Hello\" * 10\n    client.send(\n        b\"POST /upload HTTP/1.1\\r\\n\"\n        + b\"content-length: \"\n        + content_length\n        + b\"\\r\\n\\r\\n\"\n        + body\n        + b\"\\r\\n\\r\\n\"\n    )\n\n    response = client.recv()\n    headers, body = response.rsplit(b\"\\r\\n\\r\\n\", 1)\n\n    assert b\"400 Bad Request\" in headers\n    assert b\"Bad content-length\" in body\n\n\n@pytest.mark.parametrize(\n    \"chunk_length\",\n    (\n        b\"-50\",\n        b\"+50\",\n        b\"5_0\",\n        b\"50.5\",\n    ),\n)\ndef test_invalid_chunk_length(chunk_length, client):\n    body = b\"Hello\" * 10\n    client.send(\n        b\"POST /upload HTTP/1.1\\r\\n\"\n        + b\"transfer-encoding: chunked\\r\\n\\r\\n\"\n        + chunk_length\n        + b\"\\r\\n\"\n        + body\n        + b\"\\r\\n\"\n        + b\"0\\r\\n\\r\\n\"\n    )\n\n    response = client.recv()\n    headers, body = response.rsplit(b\"\\r\\n\\r\\n\", 1)\n\n    assert b\"400 Bad Request\" in headers\n    assert b\"Bad chunked encoding\" in body\n\n\ndef test_smuggle(client):\n    client.send(\n        \"\"\"\n        POST /upload HTTP/1.1\n        Content-Length: 5\n        Transfer-Encoding: chunked\n        Transfer-Encoding: xchunked\n\n        5\n        hello\n        0\n\n        GET / HTTP/1.1\n        \n        \"\"\"  # noqa\n    )\n\n    response = client.recv()\n    num_responses = response.count(b\"HTTP/1.1\")\n    assert num_responses == 1\n\n    headers, body = response.rsplit(b\"\\r\\n\\r\\n\", 1)\n    assert b\"400 Bad Request\" in headers\n    assert b\"Bad Request\" in body\n"
  },
  {
    "path": "tests/test_http_alt_svc.py",
    "content": "import sys\n\nfrom pathlib import Path\n\nimport pytest\n\nfrom sanic.app import Sanic\nfrom sanic.response import empty\nfrom tests.client import RawClient\n\n\nparent_dir = Path(__file__).parent\nlocalhost_dir = parent_dir / \"certs/localhost\"\n\n\n@pytest.mark.skipif(sys.version_info < (3, 9), reason=\"Not supported in 3.7\")\ndef test_http1_response_has_alt_svc(port):\n    Sanic._app_registry.clear()\n    app = Sanic(\"TestAltSvc\")\n    app.config.TOUCHUP = True\n    response = b\"\"\n\n    @app.get(\"/\")\n    async def handler(*_):\n        return empty()\n\n    @app.after_server_start\n    async def do_request(*_):\n        nonlocal response\n\n        app.router.reset()\n        app.router.finalize()\n\n        client = RawClient(app.state.host, app.state.port)\n        await client.connect()\n        await client.send(\n            \"\"\"\n            GET / HTTP/1.1\n            host: localhost:7777\n\n            \"\"\"\n        )\n        response = await client.recv(1024)\n        await client.close()\n\n    @app.after_server_start\n    def shutdown(*_):\n        app.stop()\n\n    app.prepare(\n        version=3,\n        ssl={\n            \"cert\": localhost_dir / \"fullchain.pem\",\n            \"key\": localhost_dir / \"privkey.pem\",\n        },\n        port=port,\n    )\n    app.prepare(\n        version=1,\n        port=port,\n    )\n    Sanic.serve_single(app)\n\n    assert f'alt-svc: h3=\":{port}\"\\r\\n'.encode() in response\n"
  },
  {
    "path": "tests/test_init.py",
    "content": "from importlib import import_module\n\nimport pytest\n\n\n@pytest.mark.parametrize(\n    \"item\",\n    (\n        \"__version__\",\n        \"Sanic\",\n        \"Blueprint\",\n        \"HTTPMethod\",\n        \"HTTPResponse\",\n        \"Request\",\n        \"Websocket\",\n        \"empty\",\n        \"file\",\n        \"html\",\n        \"json\",\n        \"redirect\",\n        \"text\",\n    ),\n)\ndef test_imports(item):\n    import_module(\"sanic\", item)\n"
  },
  {
    "path": "tests/test_json_decoding.py",
    "content": "from json import loads as sloads\n\nimport pytest\n\n\ntry:\n    from ujson import loads as uloads\n\n    NO_UJSON = False\n    DEFAULT_LOADS = uloads\nexcept ModuleNotFoundError:\n    NO_UJSON = True\n    DEFAULT_LOADS = sloads\n\nfrom sanic import Request, Sanic, json\n\n\n@pytest.fixture(autouse=True)\ndef default_back_to_ujson():\n    yield\n    Request._loads = DEFAULT_LOADS\n\n\ndef test_change_decoder():\n    Sanic(\"Test\", loads=sloads)\n    assert Request._loads == sloads\n\n\ndef test_change_decoder_to_some_custom():\n    def my_custom_decoder(some_str: str):\n        dict = sloads(some_str)\n        dict[\"some_key\"] = \"new_value\"\n        return dict\n\n    app = Sanic(\"Test\", loads=my_custom_decoder)\n    assert Request._loads == my_custom_decoder\n\n    req_body = {\"some_key\": \"some_value\"}\n\n    @app.post(\"/test\")\n    def handler(request):\n        new_json = request.json\n        return json(new_json)\n\n    req, res = app.test_client.post(\"/test\", json=req_body)\n    assert sloads(res.body) == {\"some_key\": \"new_value\"}\n\n\n@pytest.mark.skipif(NO_UJSON is True, reason=\"ujson not installed\")\ndef test_default_decoder():\n    Sanic(\"Test\")\n    assert Request._loads == uloads\n"
  },
  {
    "path": "tests/test_json_encoding.py",
    "content": "import sys\n\nfrom dataclasses import asdict, dataclass\nfrom functools import partial\nfrom json import dumps as sdumps\nfrom string import ascii_lowercase\n\nimport pytest\n\n\ntry:\n    import ujson\n\n    from ujson import dumps as udumps\n\n    ujson_version = tuple(\n        map(int, ujson.__version__.strip(ascii_lowercase).split(\".\"))\n    )\n\n    NO_UJSON = False\n    DEFAULT_DUMPS = udumps\nexcept ModuleNotFoundError:\n    NO_UJSON = True\n    DEFAULT_DUMPS = partial(sdumps, separators=(\",\", \":\"))\n    ujson_version = None\n\nfrom sanic import Sanic\nfrom sanic.response import BaseHTTPResponse, json\n\n\n@dataclass\nclass Foo:\n    bar: str\n\n    def __json__(self):\n        return udumps(asdict(self))\n\n\n@pytest.fixture\ndef foo():\n    return Foo(bar=\"bar\")\n\n\n@pytest.fixture\ndef payload(foo: Foo):\n    return {\"foo\": foo}\n\n\n@pytest.fixture(autouse=True)\ndef default_back_to_ujson():\n    yield\n    BaseHTTPResponse._dumps = DEFAULT_DUMPS\n\n\ndef test_change_encoder():\n    Sanic(\"Test\", dumps=sdumps)\n    assert BaseHTTPResponse._dumps == sdumps\n\n\ndef test_change_encoder_to_some_custom():\n    def my_custom_encoder():\n        return \"foo\"\n\n    Sanic(\"Test\", dumps=my_custom_encoder)\n    assert BaseHTTPResponse._dumps == my_custom_encoder\n\n\n@pytest.mark.skipif(NO_UJSON is True, reason=\"ujson not installed\")\ndef test_json_response_ujson(payload: dict[str, Foo]):\n    \"\"\"ujson will look at __json__\"\"\"\n    response = json(payload)\n    assert response.body == b'{\"foo\":{\"bar\":\"bar\"}}'\n\n    with pytest.raises(\n        TypeError, match=\"Object of type Foo is not JSON serializable\"\n    ):\n        json(payload, dumps=sdumps)\n\n    Sanic(\"Test\", dumps=sdumps)\n    with pytest.raises(\n        TypeError, match=\"Object of type Foo is not JSON serializable\"\n    ):\n        json(payload)\n\n\n@pytest.mark.skipif(\n    NO_UJSON is True or ujson_version >= (5, 4, 0),\n    reason=(\n        \"ujson not installed or version is 5.4.0 or newer, \"\n        \"which can handle arbitrary size integers\"\n    ),\n)\ndef test_json_response_json():\n    \"\"\"One of the easiest ways to tell the difference is that ujson cannot\n    serialize over 64 bits\"\"\"\n    too_big_for_ujson = 111111111111111111111\n\n    with pytest.raises(OverflowError, match=\"int too big to convert\"):\n        json(too_big_for_ujson)\n\n    response = json(too_big_for_ujson, dumps=sdumps)\n    assert sys.getsizeof(response.body) == 54\n\n    Sanic(\"Test\", dumps=sdumps)\n    response = json(too_big_for_ujson)\n    assert sys.getsizeof(response.body) == 54\n"
  },
  {
    "path": "tests/test_keep_alive_timeout.py",
    "content": "import asyncio\nimport platform\n\nfrom asyncio import sleep as aio_sleep\nfrom itertools import count\nfrom os import environ\n\nimport pytest\n\nfrom sanic_testing.reusable import ReusableClient\n\nfrom sanic import Sanic\nfrom sanic.compat import OS_IS_WINDOWS\nfrom sanic.response import text\n\n\npytestmark = pytest.mark.xdist_group(name=\"keep_alive_timeout\")\n\nCONFIG_FOR_TESTS = {\"KEEP_ALIVE_TIMEOUT\": 2, \"KEEP_ALIVE\": True}\nCONFIG_KEEP_ALIVE_FALSE = {\"KEEP_ALIVE_TIMEOUT\": 2, \"KEEP_ALIVE\": False}\n\nMAX_LOOPS = 15\nport_counter = count()\n\n\nkeep_alive_timeout_app_reuse = Sanic(\"test_ka_timeout_reuse\")\nkeep_alive_app_client_timeout = Sanic(\"test_ka_client_timeout\")\nkeep_alive_app_server_timeout = Sanic(\"test_ka_server_timeout\")\nkeep_alive_app_context = Sanic(\"keep_alive_app_context\")\nkeep_alive_app_disabled = Sanic(\"test_ka_disabled\")\n\nkeep_alive_timeout_app_reuse.config.update(CONFIG_FOR_TESTS)\nkeep_alive_app_client_timeout.config.update(CONFIG_FOR_TESTS)\nkeep_alive_app_server_timeout.config.update(CONFIG_FOR_TESTS)\nkeep_alive_app_context.config.update(CONFIG_FOR_TESTS)\nkeep_alive_app_disabled.config.update(CONFIG_KEEP_ALIVE_FALSE)\n\n\n@keep_alive_timeout_app_reuse.route(\"/1\")\nasync def handler1(request):\n    return text(\"OK\")\n\n\n@keep_alive_app_client_timeout.route(\"/1\")\nasync def handler2(request):\n    return text(\"OK\")\n\n\n@keep_alive_app_server_timeout.route(\"/1\")\nasync def handler3(request):\n    return text(\"OK\")\n\n\n@keep_alive_app_context.post(\"/ctx\")\ndef set_ctx(request):\n    request.conn_info.ctx.foo = \"hello\"\n    return text(\"OK\")\n\n\n@keep_alive_app_context.get(\"/ctx\")\ndef get_ctx(request):\n    return text(request.conn_info.ctx.foo)\n\n\n@keep_alive_app_disabled.route(\"/1\")\nasync def handler_disabled(request):\n    return text(\"OK\")\n\n\n@pytest.mark.skipif(\n    bool(environ.get(\"SANIC_NO_UVLOOP\")) or OS_IS_WINDOWS,\n    reason=\"Not testable with current client\",\n)\ndef test_keep_alive_timeout_reuse(port):\n    \"\"\"If the server keep-alive timeout and client keep-alive timeout are\n    both longer than the delay, the client _and_ server will successfully\n    reuse the existing connection.\"\"\"\n    loops = 0\n    while True:\n        loop = asyncio.new_event_loop()\n        asyncio.set_event_loop(loop)\n        client = ReusableClient(\n            keep_alive_timeout_app_reuse, loop=loop, port=port\n        )\n        try:\n            with client:\n                headers = {\"Connection\": \"keep-alive\"}\n                request, response = client.get(\"/1\", headers=headers)\n                assert response.status == 200\n                assert response.text == \"OK\"\n                assert request.protocol.state[\"requests_count\"] == 1\n\n                loop.run_until_complete(aio_sleep(1))\n\n                request, response = client.get(\"/1\")\n                assert response.status == 200\n                assert response.text == \"OK\"\n                assert request.protocol.state[\"requests_count\"] == 2\n        except OSError:\n            loops += 1\n            if loops > MAX_LOOPS:\n                raise\n            continue\n        else:\n            break\n\n\n@pytest.mark.skipif(\n    bool(environ.get(\"SANIC_NO_UVLOOP\"))\n    or OS_IS_WINDOWS\n    or platform.system() != \"Linux\",\n    reason=\"Not testable with current client\",\n)\ndef test_keep_alive_client_timeout(port):\n    \"\"\"If the server keep-alive timeout is longer than the client\n    keep-alive timeout, client will try to create a new connection here.\"\"\"\n    loops = 0\n    while True:\n        try:\n            loop = asyncio.new_event_loop()\n            asyncio.set_event_loop(loop)\n            client = ReusableClient(\n                keep_alive_app_client_timeout, loop=loop, port=port\n            )\n            with client:\n                headers = {\"Connection\": \"keep-alive\"}\n                request, response = client.get(\n                    \"/1\", headers=headers, timeout=1\n                )\n\n                assert response.status == 200\n                assert response.text == \"OK\"\n                assert request.protocol.state[\"requests_count\"] == 1\n\n                loop.run_until_complete(aio_sleep(2))\n                request, response = client.get(\"/1\", timeout=1)\n                assert request.protocol.state[\"requests_count\"] == 1\n        except OSError:\n            loops += 1\n            if loops > MAX_LOOPS:\n                raise\n            continue\n        else:\n            break\n\n\n@pytest.mark.skipif(\n    bool(environ.get(\"SANIC_NO_UVLOOP\")) or OS_IS_WINDOWS,\n    reason=\"Not testable with current client\",\n)\ndef test_keep_alive_server_timeout(port):\n    \"\"\"If the client keep-alive timeout is longer than the server\n    keep-alive timeout, the client will either a 'Connection reset' error\n    _or_ a new connection. Depending on how the event-loop handles the\n    broken server connection.\"\"\"\n    loops = 0\n    while True:\n        try:\n            loop = asyncio.new_event_loop()\n            asyncio.set_event_loop(loop)\n            client = ReusableClient(\n                keep_alive_app_server_timeout, loop=loop, port=port\n            )\n            with client:\n                headers = {\"Connection\": \"keep-alive\"}\n                request, response = client.get(\n                    \"/1\", headers=headers, timeout=60\n                )\n\n                assert response.status == 200\n                assert response.text == \"OK\"\n                assert request.protocol.state[\"requests_count\"] == 1\n\n                loop.run_until_complete(aio_sleep(3))\n                request, response = client.get(\"/1\", timeout=60)\n\n                assert request.protocol.state[\"requests_count\"] == 1\n        except OSError:\n            loops += 1\n            if loops > MAX_LOOPS:\n                raise\n            continue\n        else:\n            break\n\n\n@pytest.mark.skipif(\n    bool(environ.get(\"SANIC_NO_UVLOOP\")) or OS_IS_WINDOWS,\n    reason=\"Not testable with current client\",\n)\ndef test_keep_alive_connection_context(port):\n    loops = 0\n    while True:\n        try:\n            loop = asyncio.new_event_loop()\n            asyncio.set_event_loop(loop)\n            client = ReusableClient(\n                keep_alive_app_context, loop=loop, port=port\n            )\n            with client:\n                headers = {\"Connection\": \"keep-alive\"}\n                request1, _ = client.post(\"/ctx\", headers=headers)\n\n                loop.run_until_complete(aio_sleep(1))\n                request2, response = client.get(\"/ctx\")\n\n                assert response.text == \"hello\"\n                assert id(request1.conn_info.ctx) == id(request2.conn_info.ctx)\n                assert (\n                    request1.conn_info.ctx.foo\n                    == request2.conn_info.ctx.foo\n                    == \"hello\"\n                )\n                assert request2.protocol.state[\"requests_count\"] == 2\n        except OSError:\n            loops += 1\n            if loops > MAX_LOOPS:\n                raise\n            continue\n        else:\n            break\n\n\n@pytest.mark.skipif(\n    bool(environ.get(\"SANIC_NO_UVLOOP\")) or OS_IS_WINDOWS,\n    reason=\"Not testable with current client\",\n)\ndef test_keep_alive_config_false(port):\n    \"\"\"When KEEP_ALIVE=False, server should respond with Connection: close\n    and not reuse the connection, even if client requests keep-alive.\n\n    See: https://github.com/sanic-org/sanic/issues/2984\n    \"\"\"\n    loops = 0\n    while True:\n        loop = asyncio.new_event_loop()\n        asyncio.set_event_loop(loop)\n        client = ReusableClient(keep_alive_app_disabled, loop=loop, port=port)\n        try:\n            with client:\n                headers = {\"Connection\": \"keep-alive\"}\n                request1, response1 = client.get(\"/1\", headers=headers)\n                assert response1.status == 200\n                assert response1.text == \"OK\"\n                assert response1.headers.get(\"connection\") == \"close\"\n                assert request1.protocol is None\n\n                loop.run_until_complete(aio_sleep(1))\n\n                request2, response2 = client.get(\"/1\")\n                assert response2.status == 200\n                assert request1.conn_info is not request2.conn_info\n        except OSError:\n            loops += 1\n            if loops > MAX_LOOPS:\n                raise\n            continue\n        else:\n            break\n"
  },
  {
    "path": "tests/test_late_adds.py",
    "content": "import pytest\n\nfrom sanic import Sanic, text\n\n\npytestmark = pytest.mark.xdist_group(name=\"process_spawning\")\n\n\n@pytest.fixture\ndef late_app(app: Sanic):\n    app.config.TOUCHUP = False\n    app.get(\"/\")(lambda _: text(\"\"))\n    return app\n\n\ndef test_late_route(late_app: Sanic):\n    @late_app.before_server_start\n    async def late(app: Sanic):\n        @app.get(\"/late\")\n        def handler(_):\n            return text(\"late\")\n\n    _, response = late_app.test_client.get(\"/late\")\n    assert response.status_code == 200\n    assert response.text == \"late\"\n\n\ndef test_late_middleware(late_app: Sanic):\n    @late_app.get(\"/late\")\n    def handler(request):\n        return text(request.ctx.late)\n\n    @late_app.before_server_start\n    async def late(app: Sanic):\n        @app.on_request\n        def handler(request):\n            request.ctx.late = \"late\"\n\n    _, response = late_app.test_client.get(\"/late\")\n    assert response.status_code == 200\n    assert response.text == \"late\"\n\n\ndef test_late_signal(late_app: Sanic):\n    @late_app.get(\"/late\")\n    def handler(request):\n        return text(request.ctx.late)\n\n    @late_app.before_server_start\n    async def late(app: Sanic):\n        @app.signal(\"http.lifecycle.request\")\n        def handler(request):\n            request.ctx.late = \"late\"\n\n    _, response = late_app.test_client.get(\"/late\")\n    assert response.status_code == 200\n    assert response.text == \"late\"\n"
  },
  {
    "path": "tests/test_logging.py",
    "content": "import logging\nimport sys\nimport uuid\n\nfrom importlib import reload\nfrom io import StringIO\nfrom unittest.mock import ANY, Mock\n\nimport pytest\n\nimport sanic\n\nfrom sanic import Sanic\nfrom sanic.log import LOGGING_CONFIG_DEFAULTS, Colors, logger\nfrom sanic.logging.formatter import (\n    AutoFormatter,\n    DebugAccessFormatter,\n    DebugFormatter,\n    ProdAccessFormatter,\n    ProdFormatter,\n)\nfrom sanic.logging.setup import setup_logging\nfrom sanic.response import text\n\n\nlogging_format = \"\"\"module: %(module)s; \\\nfunction: %(funcName)s(); \\\nmessage: %(message)s\"\"\"\n\n\ndef reset_logging():\n    logging.shutdown()\n    reload(logging)\n\n\ndef test_log(app):\n    log_stream = StringIO()\n    for handler in logging.root.handlers[:]:\n        logging.root.removeHandler(handler)\n    logging.basicConfig(\n        format=logging_format, level=logging.DEBUG, stream=log_stream\n    )\n    logging.getLogger(\"asyncio\").setLevel(logging.WARNING)\n    log = logging.getLogger()\n    rand_string = str(uuid.uuid4())\n\n    @app.route(\"/\")\n    def handler(request):\n        log.info(rand_string)\n        return text(\"hello\")\n\n    request, response = app.test_client.get(\"/\")\n    log_text = log_stream.getvalue()\n    assert rand_string in log_text\n\n\n@pytest.mark.parametrize(\"debug\", (True, False))\ndef test_logging_defaults(debug):\n    AutoFormatter.ATTY = False\n    AutoFormatter.SETUP = False\n    Sanic(\"test_logging\")\n    setup_logging(debug)\n    std_formatter = (DebugFormatter if debug else ProdFormatter)()\n    access_formatter = (\n        DebugAccessFormatter if debug else ProdAccessFormatter\n    )()\n\n    for logger_name, formatter in [\n        (\"sanic.root\", std_formatter),\n        (\"sanic.error\", std_formatter),\n        (\"sanic.access\", access_formatter),\n        (\"sanic.server\", std_formatter),\n        (\"sanic.websockets\", std_formatter),\n    ]:\n        print(\"....\", logger_name)\n        for fmt in [\n            h.formatter for h in logging.getLogger(logger_name).handlers\n        ]:\n            print(f\"{logger_name} logger_formatter: \", fmt._fmt)\n            print(f\"{logger_name}        formatter: \", formatter._fmt)\n            assert fmt._fmt == formatter._fmt\n\n\ndef test_logging_pass_customer_logconfig():\n    # reset_logging()\n\n    modified_config = LOGGING_CONFIG_DEFAULTS\n    modified_config[\"formatters\"][\"generic\"][\"format\"] = (\n        \"%(asctime)s - (%(name)s)[%(levelname)s]: %(message)s\"\n    )\n    modified_config[\"formatters\"][\"access\"][\"format\"] = (\n        \"%(asctime)s - (%(name)s)[%(levelname)s]: %(message)s\"\n    )\n\n    Sanic(\"test_logging\", log_config=modified_config)\n\n    for fmt in [h.formatter for h in logging.getLogger(\"sanic.root\").handlers]:\n        assert fmt._fmt == modified_config[\"formatters\"][\"generic\"][\"format\"]\n\n    for fmt in [\n        h.formatter for h in logging.getLogger(\"sanic.error\").handlers\n    ]:\n        assert fmt._fmt == modified_config[\"formatters\"][\"generic\"][\"format\"]\n\n    for fmt in [\n        h.formatter for h in logging.getLogger(\"sanic.access\").handlers\n    ]:\n        assert fmt._fmt == modified_config[\"formatters\"][\"access\"][\"format\"]\n\n\n@pytest.mark.asyncio\nasync def test_logger(caplog):\n    rand_string = str(uuid.uuid4())\n\n    app = Sanic(name=\"Test\")\n\n    @app.get(\"/\")\n    def log_info(request):\n        logger.info(rand_string)\n        return text(\"hello\")\n\n    with caplog.at_level(logging.INFO):\n        _ = await app.asgi_client.get(\"/\")\n\n    record = (\"sanic.root\", logging.INFO, rand_string)\n    assert record in caplog.record_tuples\n\n\ndef test_logging_modified_root_logger_config():\n    # reset_logging()\n\n    modified_config = LOGGING_CONFIG_DEFAULTS\n    modified_config[\"loggers\"][\"sanic.root\"][\"level\"] = \"DEBUG\"\n\n    Sanic(\"test_logging\", log_config=modified_config)\n\n    assert logging.getLogger(\"sanic.root\").getEffectiveLevel() == logging.DEBUG\n\n\ndef test_access_log_client_ip_remote_addr(monkeypatch):\n    access = Mock()\n    monkeypatch.setattr(sanic.http.http1, \"access_logger\", access)\n\n    app = Sanic(\"test_logging\")\n    app.config.ACCESS_LOG = True\n    app.config.PROXIES_COUNT = 2\n\n    @app.route(\"/\")\n    async def handler(request):\n        return text(request.remote_addr)\n\n    headers = {\"X-Forwarded-For\": \"1.1.1.1, 2.2.2.2\"}\n\n    request, response = app.test_client.get(\"/\", headers=headers)\n\n    assert request.remote_addr == \"1.1.1.1\"\n    access.info.assert_called_with(\n        \"\",\n        extra={\n            \"status\": 200,\n            \"byte\": len(response.content),\n            \"host\": f\"{request.remote_addr}:{request.port}\",\n            \"request\": f\"GET {request.scheme}://{request.host}/\",\n            \"duration\": ANY,\n        },\n    )\n\n\ndef test_access_log_client_ip_reqip(monkeypatch):\n    access = Mock()\n    monkeypatch.setattr(sanic.http.http1, \"access_logger\", access)\n\n    app = Sanic(\"test_logging\")\n    app.config.ACCESS_LOG = True\n\n    @app.route(\"/\")\n    async def handler(request):\n        return text(request.ip)\n\n    request, response = app.test_client.get(\"/\")\n\n    access.info.assert_called_with(\n        \"\",\n        extra={\n            \"status\": 200,\n            \"byte\": len(response.content),\n            \"host\": f\"{request.ip}:{request.port}\",\n            \"request\": f\"GET {request.scheme}://{request.host}/\",\n            \"duration\": ANY,\n        },\n    )\n\n\n@pytest.mark.parametrize(\n    \"app_verbosity,log_verbosity,exists\",\n    (\n        (0, 0, True),\n        (0, 1, False),\n        (0, 2, False),\n        (1, 0, True),\n        (1, 1, True),\n        (1, 2, False),\n        (2, 0, True),\n        (2, 1, True),\n        (2, 2, True),\n    ),\n)\ndef test_verbosity(app, caplog, app_verbosity, log_verbosity, exists):\n    rand_string = str(uuid.uuid4())\n\n    @app.get(\"/\")\n    def log_info(request):\n        logger.info(\"DEFAULT\")\n        logger.info(rand_string, extra={\"verbosity\": log_verbosity})\n        return text(\"hello\")\n\n    with caplog.at_level(logging.INFO):\n        _ = app.test_client.get(\n            \"/\", server_kwargs={\"verbosity\": app_verbosity}\n        )\n\n    record = (\"sanic.root\", logging.INFO, rand_string)\n\n    if exists:\n        assert record in caplog.record_tuples\n    else:\n        assert record not in caplog.record_tuples\n\n    if app_verbosity == 0:\n        assert (\"sanic.root\", logging.INFO, \"DEFAULT\") in caplog.record_tuples\n\n\ndef test_colors_enum_format():\n    assert f\"{Colors.END}\" == Colors.END.value\n    assert f\"{Colors.BOLD}\" == Colors.BOLD.value\n    assert f\"{Colors.BLUE}\" == Colors.BLUE.value\n    assert f\"{Colors.GREEN}\" == Colors.GREEN.value\n    assert f\"{Colors.PURPLE}\" == Colors.PURPLE.value\n    assert f\"{Colors.RED}\" == Colors.RED.value\n    assert f\"{Colors.SANIC}\" == Colors.SANIC.value\n    assert f\"{Colors.YELLOW}\" == Colors.YELLOW.value\n\n\n@pytest.mark.parametrize(\n    \"atty,no_color,expected\",\n    [\n        (True, False, True),\n        (False, False, False),\n        (True, True, False),\n        (False, True, False),\n    ],\n)\n@pytest.mark.xfail(reason=\"Runs on local but fails on CI, not highly critical\")\ndef test_debug_formatter_formatException(atty, no_color, expected):\n    formatter = DebugFormatter()\n    formatter.ATTY = atty\n    formatter.NO_COLOR = no_color\n\n    try:\n        1 / 0\n    except Exception as e:\n        exc_info = (type(e), e, e.__traceback__)\n\n    output = formatter.formatException(exc_info)\n    lines = output.splitlines()\n\n    assert len(lines) == 5 if sys.version_info >= (3, 11) else 4\n    assert (\"\\033\" in output) is expected\n    assert (\"\\033[36m\\033[1m\" in lines[1]) is expected\n    assert (\n        lines[1].endswith(\n            \"\\033[34m\\033[1mtest_debug_formatter_formatException\\033[0m\"\n        )\n        is expected\n    )\n    assert (\n        lines[1].endswith(\"test_debug_formatter_formatException\")\n        is not expected\n    )\n    assert (lines[2] == \"\\033[33m    1 / 0\\033[0m\") is expected\n    assert (lines[2] == \"    1 / 0\") is not expected\n    assert (\n        lines[-1] == \"\\033[38;2;255;13;104m\\033[1mZeroDivisionError\\033[0m: \"\n        \"\\033[1mdivision by zero\\033[0m\"\n    ) is expected\n    assert (lines[-1] == \"ZeroDivisionError: division by zero\") is not expected\n\n\ndef test_log_extra_config_respected():\n    AutoFormatter.SETUP = False\n    AutoFormatter.LOG_EXTRA = True\n\n    app = Sanic(name=\"TestLogExtra\")\n    app.config.LOG_EXTRA = False\n\n    @app.get(\"/\")\n    def handler(request):\n        return text(\"ok\")\n\n    app.test_client.get(\"/\", debug=True)\n\n    assert AutoFormatter.LOG_EXTRA is False\n"
  },
  {
    "path": "tests/test_logo.py",
    "content": "import os\nimport sys\n\nfrom unittest.mock import patch\n\nimport pytest\n\nfrom sanic.application.logo import (\n    BASE_LOGO,\n    COLOR_LOGO,\n    FULL_COLOR_LOGO,\n    get_logo,\n)\n\n\n@pytest.mark.parametrize(\n    \"tty,full,expected\",\n    (\n        (True, False, COLOR_LOGO),\n        (True, True, FULL_COLOR_LOGO),\n        (False, False, BASE_LOGO),\n        (False, True, BASE_LOGO),\n    ),\n)\ndef test_get_logo_returns_expected_logo(tty, full, expected):\n    with patch(\"sys.stdout.isatty\") as isatty:\n        isatty.return_value = tty\n        logo = get_logo(full=full)\n    assert logo is expected\n\n\ndef test_get_logo_returns_no_colors_on_apple_terminal():\n    platform = sys.platform\n    sys.platform = \"darwin\"\n    os.environ[\"TERM_PROGRAM\"] = \"Apple_Terminal\"\n    with patch(\"sys.stdout.isatty\") as isatty:\n        isatty.return_value = False\n        logo = get_logo()\n    assert \"\\033\" not in logo\n    sys.platform = platform\n    del os.environ[\"TERM_PROGRAM\"]\n"
  },
  {
    "path": "tests/test_middleware.py",
    "content": "import logging\n\nfrom asyncio import CancelledError, sleep\nfrom itertools import count\n\nfrom sanic.exceptions import NotFound\nfrom sanic.request import Request\nfrom sanic.response import HTTPResponse, json, text\n\n\n# ------------------------------------------------------------ #\n#  GET\n# ------------------------------------------------------------ #\n\n\ndef test_middleware_request(app):\n    results = []\n\n    @app.middleware\n    async def handler1(request):\n        results.append(request)\n\n    @app.route(\"/\")\n    async def handler2(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\"/\")\n\n    assert response.text == \"OK\"\n    assert type(results[0]) is Request\n\n\ndef test_middleware_request_as_convenience(app):\n    results = []\n\n    @app.on_request\n    async def handler1(request):\n        results.append(request)\n\n    @app.on_request()\n    async def handler2(request):\n        results.append(request)\n\n    @app.route(\"/\")\n    async def handler3(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\"/\")\n\n    assert response.text == \"OK\"\n    assert type(results[0]) is Request\n    assert type(results[1]) is Request\n\n\ndef test_middleware_response(app):\n    results = []\n\n    @app.middleware(\"request\")\n    async def process_request(request):\n        results.append(request)\n\n    @app.middleware(\"response\")\n    async def process_response(request, response):\n        results.append(request)\n        results.append(response)\n\n    @app.route(\"/\")\n    async def handler(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\"/\")\n\n    assert response.text == \"OK\"\n    assert type(results[0]) is Request\n    assert type(results[1]) is Request\n    assert isinstance(results[2], HTTPResponse)\n\n\ndef test_middleware_response_as_convenience(app):\n    results = []\n\n    @app.on_request\n    async def process_request(request):\n        results.append(request)\n\n    @app.on_response\n    async def process_response_1(request, response):\n        results.append(request)\n        results.append(response)\n\n    @app.on_response()\n    async def process_response_2(request, response):\n        results.append(request)\n        results.append(response)\n\n    @app.route(\"/\")\n    async def handler(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\"/\")\n\n    assert response.text == \"OK\"\n    assert type(results[0]) is Request\n    assert type(results[1]) is Request\n    assert isinstance(results[2], HTTPResponse)\n    assert type(results[3]) is Request\n    assert isinstance(results[4], HTTPResponse)\n\n\ndef test_middleware_response_as_convenience_called(app):\n    results = []\n\n    @app.on_request()\n    async def process_request(request):\n        results.append(request)\n\n    @app.on_response()\n    async def process_response(request, response):\n        results.append(request)\n        results.append(response)\n\n    @app.route(\"/\")\n    async def handler(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\"/\")\n\n    assert response.text == \"OK\"\n    assert type(results[0]) is Request\n    assert type(results[1]) is Request\n    assert isinstance(results[2], HTTPResponse)\n\n\ndef test_middleware_response_exception(app):\n    result = {\"status_code\": \"middleware not run\"}\n\n    @app.middleware(\"response\")\n    async def process_response(request, response):\n        result[\"status_code\"] = response.status\n        return response\n\n    @app.exception(NotFound)\n    async def error_handler(request, exception):\n        return text(\"OK\", exception.status_code)\n\n    @app.route(\"/\")\n    async def handler(request):\n        return text(\"FAIL\")\n\n    request, response = app.test_client.get(\"/page_not_found\")\n    assert response.text == \"OK\"\n    assert result[\"status_code\"] == 404\n\n\ndef test_middleware_response_raise_cancelled_error(app, caplog):\n    app.config.RESPONSE_TIMEOUT = 1\n\n    @app.middleware(\"response\")\n    async def process_response(request, response):\n        raise CancelledError(\"CancelledError at response middleware\")\n\n    @app.get(\"/\")\n    def handler(request):\n        return text(\"OK\")\n\n    with caplog.at_level(logging.ERROR):\n        reqrequest, response = app.test_client.get(\"/\")\n\n        assert response.status == 500\n        assert (\n            \"sanic.error\",\n            logging.ERROR,\n            \"Exception occurred while handling uri: 'http://127.0.0.1:42101/'\",\n        ) not in caplog.record_tuples\n\n\ndef test_middleware_response_raise_exception(app, caplog):\n    @app.middleware(\"response\")\n    async def process_response(request, response):\n        raise Exception(\"Exception at response middleware\")\n\n    app.route(\"/\")(lambda x: x)\n    with caplog.at_level(logging.ERROR):\n        reqrequest, response = app.test_client.get(\"/fail\")\n\n    assert response.status == 404\n    # 404 errors are not logged\n    assert (\n        \"sanic.error\",\n        logging.ERROR,\n        \"Exception occurred while handling uri: 'http://127.0.0.1:42101/'\",\n    ) not in caplog.record_tuples\n    # Middleware exception ignored but logged\n    assert (\n        \"sanic.error\",\n        logging.ERROR,\n        \"Exception occurred in one of response middleware handlers\",\n    ) in caplog.record_tuples\n\n\ndef test_middleware_override_request(app):\n    @app.middleware\n    async def halt_request(request):\n        return text(\"OK\")\n\n    @app.route(\"/\")\n    async def handler(request):\n        return text(\"FAIL\")\n\n    _, response = app.test_client.get(\"/\", gather_request=False)\n\n    assert response.status == 200\n    assert response.text == \"OK\"\n\n\ndef test_middleware_override_response(app):\n    @app.middleware(\"response\")\n    async def process_response(request, response):\n        return text(\"OK\")\n\n    @app.route(\"/\")\n    async def handler(request):\n        return text(\"FAIL\")\n\n    request, response = app.test_client.get(\"/\")\n\n    assert response.status == 200\n    assert response.text == \"OK\"\n\n\ndef test_middleware_order(app):\n    order = []\n\n    @app.middleware(\"request\")\n    async def request1(request):\n        order.append(1)\n\n    @app.middleware(\"request\")\n    async def request2(request):\n        order.append(2)\n\n    @app.middleware(\"request\")\n    async def request3(request):\n        order.append(3)\n\n    @app.middleware(\"response\")\n    async def response1(request, response):\n        order.append(6)\n\n    @app.middleware(\"response\")\n    async def response2(request, response):\n        order.append(5)\n\n    @app.middleware(\"response\")\n    async def response3(request, response):\n        order.append(4)\n\n    @app.route(\"/\")\n    async def handler(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\"/\")\n\n    assert response.status == 200\n    assert order == [1, 2, 3, 4, 5, 6]\n\n\ndef test_request_middleware_executes_once(app):\n    i = count()\n\n    @app.middleware(\"request\")\n    async def inc(request):\n        nonlocal i\n        next(i)\n\n    @app.route(\"/\")\n    async def handler(request):\n        await request.app._run_request_middleware(request)\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\"/\")\n    assert next(i) == 1\n\n    request, response = app.test_client.get(\"/\")\n    assert next(i) == 3\n\n\ndef test_middleware_added_response(app):\n    @app.on_response\n    def display(_, response):\n        response[\"foo\"] = \"bar\"\n        return json(response)\n\n    @app.get(\"/\")\n    async def handler(request):\n        return {}\n\n    _, response = app.test_client.get(\"/\")\n    assert response.json[\"foo\"] == \"bar\"\n\n\ndef test_middleware_return_response(app):\n    response_middleware_run_count = 0\n    request_middleware_run_count = 0\n\n    @app.on_response\n    def response(_, response):\n        nonlocal response_middleware_run_count\n        response_middleware_run_count += 1\n\n    @app.on_request\n    def request(_):\n        nonlocal request_middleware_run_count\n        request_middleware_run_count += 1\n\n    @app.get(\"/\")\n    async def handler(request):\n        resp1 = await request.respond()\n        return resp1\n\n    app.test_client.get(\"/\")\n    assert response_middleware_run_count == 1\n    assert request_middleware_run_count == 1\n\n\ndef test_middleware_run_on_timeout(app):\n    app.config.RESPONSE_TIMEOUT = 0.1\n    response_middleware_run_count = 0\n    request_middleware_run_count = 0\n\n    @app.on_response\n    def response(_, response):\n        nonlocal response_middleware_run_count\n        response_middleware_run_count += 1\n\n    @app.on_request\n    def request(_):\n        nonlocal request_middleware_run_count\n        request_middleware_run_count += 1\n\n    @app.get(\"/\")\n    async def handler(request):\n        resp1 = await request.respond()\n        await sleep(1)\n        return resp1\n\n    app.test_client.get(\"/\")\n    assert request_middleware_run_count == 1\n    assert response_middleware_run_count == 1\n"
  },
  {
    "path": "tests/test_middleware_priority.py",
    "content": "from functools import partial\n\nimport pytest\n\nfrom sanic import Sanic\nfrom sanic.middleware import Middleware, MiddlewareLocation\nfrom sanic.response import json\n\n\nPRIORITY_TEST_CASES = (\n    ([0, 1, 2], [1, 1, 1]),\n    ([0, 1, 2], [1, 1, None]),\n    ([0, 1, 2], [1, None, None]),\n    ([0, 1, 2], [2, 1, None]),\n    ([0, 1, 2], [2, 2, None]),\n    ([0, 1, 2], [3, 2, 1]),\n    ([0, 1, 2], [None, None, None]),\n    ([0, 2, 1], [1, None, 1]),\n    ([0, 2, 1], [2, None, 1]),\n    ([0, 2, 1], [2, None, 2]),\n    ([0, 2, 1], [3, 1, 2]),\n    ([1, 0, 2], [1, 2, None]),\n    ([1, 0, 2], [2, 3, 1]),\n    ([1, 0, 2], [None, 1, None]),\n    ([1, 2, 0], [1, 3, 2]),\n    ([1, 2, 0], [None, 1, 1]),\n    ([1, 2, 0], [None, 2, 1]),\n    ([1, 2, 0], [None, 2, 2]),\n    ([2, 0, 1], [1, None, 2]),\n    ([2, 0, 1], [2, 1, 3]),\n    ([2, 0, 1], [None, None, 1]),\n    ([2, 1, 0], [1, 2, 3]),\n    ([2, 1, 0], [None, 1, 2]),\n)\n\n\n@pytest.fixture(autouse=True)\ndef reset_middleware():\n    yield\n    Middleware.reset_count()\n\n\ndef test_add_register_priority(app: Sanic):\n    def foo(*_): ...\n\n    app.register_middleware(foo, priority=999)\n    assert len(app.request_middleware) == 1\n    assert len(app.response_middleware) == 0\n    assert app.request_middleware[0].priority == 999  # type: ignore\n    app.register_middleware(foo, attach_to=\"response\", priority=999)\n    assert len(app.request_middleware) == 1\n    assert len(app.response_middleware) == 1\n    assert app.response_middleware[0].priority == 999  # type: ignore\n\n\ndef test_add_register_named_priority(app: Sanic):\n    def foo(*_): ...\n\n    app.register_named_middleware(foo, route_names=[\"foo\"], priority=999)\n    assert len(app.named_request_middleware) == 1\n    assert len(app.named_response_middleware) == 0\n    assert app.named_request_middleware[\"foo\"][0].priority == 999  # type: ignore\n    app.register_named_middleware(\n        foo, attach_to=\"response\", route_names=[\"foo\"], priority=999\n    )\n    assert len(app.named_request_middleware) == 1\n    assert len(app.named_response_middleware) == 1\n    assert app.named_response_middleware[\"foo\"][0].priority == 999  # type: ignore\n\n\ndef test_add_decorator_priority(app: Sanic):\n    def foo(*_): ...\n\n    app.middleware(foo, priority=999)\n    assert len(app.request_middleware) == 1\n    assert len(app.response_middleware) == 0\n    assert app.request_middleware[0].priority == 999  # type: ignore\n    app.middleware(foo, attach_to=\"response\", priority=999)\n    assert len(app.request_middleware) == 1\n    assert len(app.response_middleware) == 1\n    assert app.response_middleware[0].priority == 999  # type: ignore\n\n\ndef test_add_convenience_priority(app: Sanic):\n    def foo(*_): ...\n\n    app.on_request(foo, priority=999)\n    assert len(app.request_middleware) == 1\n    assert len(app.response_middleware) == 0\n    assert app.request_middleware[0].priority == 999  # type: ignore\n    app.on_response(foo, priority=999)\n    assert len(app.request_middleware) == 1\n    assert len(app.response_middleware) == 1\n    assert app.response_middleware[0].priority == 999  # type: ignore\n\n\ndef test_add_conflicting_priority(app: Sanic):\n    def foo(*_): ...\n\n    middleware = Middleware(foo, MiddlewareLocation.REQUEST, priority=998)\n    app.register_middleware(middleware=middleware, priority=999)\n    assert app.request_middleware[0].priority == 999  # type: ignore\n    middleware.priority == 998\n\n\ndef test_add_conflicting_priority_named(app: Sanic):\n    def foo(*_): ...\n\n    middleware = Middleware(foo, MiddlewareLocation.REQUEST, priority=998)\n    app.register_named_middleware(\n        middleware=middleware, route_names=[\"foo\"], priority=999\n    )\n    assert app.named_request_middleware[\"foo\"][0].priority == 999  # type: ignore\n    middleware.priority == 998\n\n\n@pytest.mark.parametrize(\n    \"expected,priorities\",\n    PRIORITY_TEST_CASES,\n)\ndef test_request_middleware_order_priority(app: Sanic, expected, priorities):\n    order = []\n\n    def add_ident(request, ident):\n        order.append(ident)\n\n    @app.get(\"/\")\n    def handler(request):\n        return json(None)\n\n    for ident, priority in enumerate(priorities):\n        kwargs = {}\n        if priority is not None:\n            kwargs[\"priority\"] = priority\n        app.on_request(partial(add_ident, ident=ident), **kwargs)\n\n    app.test_client.get(\"/\")\n\n    assert order == expected\n\n\n@pytest.mark.parametrize(\n    \"expected,priorities\",\n    PRIORITY_TEST_CASES,\n)\ndef test_response_middleware_order_priority(app: Sanic, expected, priorities):\n    order = []\n\n    def add_ident(request, response, ident):\n        order.append(ident)\n\n    @app.get(\"/\")\n    def handler(request):\n        return json(None)\n\n    for ident, priority in enumerate(priorities):\n        kwargs = {}\n        if priority is not None:\n            kwargs[\"priority\"] = priority\n        app.on_response(partial(add_ident, ident=ident), **kwargs)\n\n    app.test_client.get(\"/\")\n\n    assert order[::-1] == expected\n"
  },
  {
    "path": "tests/test_motd.py",
    "content": "import logging\nimport os\nimport platform\n\nfrom unittest.mock import Mock, patch\n\nimport pytest\n\nfrom sanic import __version__\nfrom sanic.application.logo import BASE_LOGO\nfrom sanic.application.motd import MOTD, MOTDTTY\n\n\n@pytest.fixture(autouse=True)\ndef reset():\n    try:\n        del os.environ[\"SANIC_MOTD_OUTPUT\"]\n    except KeyError:\n        ...\n\n\ndef test_logo_base(app, run_startup):\n    logs = run_startup(app)\n\n    assert logs[0][1] == logging.DEBUG\n    assert logs[0][2] == BASE_LOGO\n\n\ndef test_motd_with_expected_info(app, run_startup):\n    logs = run_startup(app)\n\n    assert logs[1][2] == f\"Sanic v{__version__}\"\n    assert logs[3][2] == \"app: test_motd_with_expected_info\"\n    assert logs[4][2] == \"mode: debug, single worker\"\n    assert logs[5][2] == \"server: sanic, HTTP/1.1\"\n    assert logs[6][2] == f\"python: {platform.python_version()}\"\n    assert logs[7][2] == f\"platform: {platform.platform()}\"\n\n\ndef test_motd_init():\n    _orig = MOTDTTY.set_variables\n    MOTDTTY.set_variables = Mock()\n    motd = MOTDTTY(None, \"\", {}, {})\n\n    motd.set_variables.assert_called_once()\n    MOTDTTY.set_variables = _orig\n\n\ndef test_motd_display(caplog):\n    motd = MOTDTTY(\"       foobar        \", \"\", {\"one\": \"1\"}, {\"two\": \"2\"})\n\n    with caplog.at_level(logging.INFO):\n        motd.display()\n\n    version_line = f\"Sanic v{__version__}\".center(motd.centering_length)\n    assert (\n        \"\".join(caplog.messages)\n        == f\"\"\"\n  ┌────────────────────────────────┐\n  │ {version_line} │\n  │                                │\n  ├───────────────────────┬────────┤\n  │        foobar         │ one: 1 │\n  │                       ├────────┤\n  │                       │ two: 2 │\n  └───────────────────────┴────────┘\n\"\"\"\n    )\n\n\ndef test_reload_dirs(app):\n    app.config.LOGO = None\n    app.config.MOTD = True\n    app.config.AUTO_RELOAD = True\n\n    with patch.object(MOTD, \"output\") as mock:\n        app.prepare(\n            reload_dir=\"./\", auto_reload=True, motd_display={\"foo\": \"bar\"}\n        )\n    mock.assert_called()\n    assert mock.call_args.args[2][\"auto-reload\"] == f\"enabled, {os.getcwd()}\"\n    assert mock.call_args.args[3] == {\"foo\": \"bar\"}\n"
  },
  {
    "path": "tests/test_multi_serve.py",
    "content": "# import logging\n\n# from unittest.mock import Mock\n\n# import pytest\n\n# from sanic import Sanic\n# from sanic.response import text\n# from sanic.server.async_server import AsyncioServer\n# from sanic.signals import Event\n# from sanic.touchup.schemes.ode import OptionalDispatchEvent\n\n\n# try:\n#     from unittest.mock import AsyncMock\n# except ImportError:\n#     from tests.asyncmock import AsyncMock  # type: ignore\n\n\n# @pytest.fixture\n# def app_one():\n#     app = Sanic(\"One\")\n\n#     @app.get(\"/one\")\n#     async def one(request):\n#         return text(\"one\")\n\n#     return app\n\n\n# @pytest.fixture\n# def app_two():\n#     app = Sanic(\"Two\")\n\n#     @app.get(\"/two\")\n#     async def two(request):\n#         return text(\"two\")\n\n#     return app\n\n\n# @pytest.fixture(autouse=True)\n# def clean():\n#     Sanic._app_registry = {}\n#     yield\n\n\n# def test_serve_same_app_multiple_tuples(app_one, run_multi):\n#     app_one.prepare(port=23456)\n#     app_one.prepare(port=23457)\n\n#     logs = run_multi(app_one)\n#     assert (\n#         \"sanic.root\",\n#         logging.INFO,\n#         \"Goin' Fast @ http://127.0.0.1:23456\",\n#     ) in logs\n#     assert (\n#         \"sanic.root\",\n#         logging.INFO,\n#         \"Goin' Fast @ http://127.0.0.1:23457\",\n#     ) in logs\n\n\n# def test_serve_multiple_apps(app_one, app_two, run_multi):\n#     app_one.prepare(port=23456)\n#     app_two.prepare(port=23457)\n\n#     logs = run_multi(app_one)\n#     assert (\n#         \"sanic.root\",\n#         logging.INFO,\n#         \"Goin' Fast @ http://127.0.0.1:23456\",\n#     ) in logs\n#     assert (\n#         \"sanic.root\",\n#         logging.INFO,\n#         \"Goin' Fast @ http://127.0.0.1:23457\",\n#     ) in logs\n\n\n# def test_listeners_on_secondary_app(app_one, app_two, run_multi):\n#     app_one.prepare(port=23456)\n#     app_two.prepare(port=23457)\n\n#     before_start = AsyncMock()\n#     after_start = AsyncMock()\n#     before_stop = AsyncMock()\n#     after_stop = AsyncMock()\n\n#     app_two.before_server_start(before_start)\n#     app_two.after_server_start(after_start)\n#     app_two.before_server_stop(before_stop)\n#     app_two.after_server_stop(after_stop)\n\n#     run_multi(app_one)\n\n#     before_start.assert_awaited_once()\n#     after_start.assert_awaited_once()\n#     before_stop.assert_awaited_once()\n#     after_stop.assert_awaited_once()\n\n\n# @pytest.mark.parametrize(\n#     \"events\",\n#     (\n#         (Event.HTTP_LIFECYCLE_BEGIN,),\n#         (Event.HTTP_LIFECYCLE_BEGIN, Event.HTTP_LIFECYCLE_COMPLETE),\n#         (\n#             Event.HTTP_LIFECYCLE_BEGIN,\n#             Event.HTTP_LIFECYCLE_COMPLETE,\n#             Event.HTTP_LIFECYCLE_REQUEST,\n#         ),\n#     ),\n# )\n# def test_signal_synchronization(app_one, app_two, run_multi, events):\n#     app_one.prepare(port=23456)\n#     app_two.prepare(port=23457)\n\n#     for event in events:\n#         app_one.signal(event)(AsyncMock())\n\n#     run_multi(app_one)\n\n#     assert len(app_two.signal_router.routes) == len(events) + 1\n\n#     signal_handlers = {\n#         signal.handler\n#         for signal in app_two.signal_router.routes\n#         if signal.name.startswith(\"http\")\n#     }\n\n#     assert len(signal_handlers) == 1\n#     assert list(signal_handlers)[0] is OptionalDispatchEvent.noop\n\n\n# def test_warning_main_process_listeners_on_secondary(\n#     app_one, app_two, run_multi\n# ):\n#     app_two.main_process_start(AsyncMock())\n#     app_two.main_process_stop(AsyncMock())\n#     app_one.prepare(port=23456)\n#     app_two.prepare(port=23457)\n\n#     log = run_multi(app_one)\n\n#     message = (\n#         f\"Sanic found 2 listener(s) on \"\n#         \"secondary applications attached to the main \"\n#         \"process. These will be ignored since main \"\n#         \"process listeners can only be attached to your \"\n#         \"primary application: \"\n#         f\"{repr(app_one)}\"\n#     )\n\n#     assert (\"sanic.error\", logging.WARNING, message) in log\n\n\n# def test_no_applications():\n#     Sanic._app_registry = {}\n#     message = \"Did not find any applications.\"\n#     with pytest.raises(RuntimeError, match=message):\n#         Sanic.serve()\n\n\n# def test_oserror_warning(app_one, app_two, run_multi, capfd):\n#     orig = AsyncioServer.__await__\n#     AsyncioServer.__await__ = Mock(side_effect=OSError(\"foo\"))\n#     app_one.prepare(port=23456, workers=2)\n#     app_two.prepare(port=23457, workers=2)\n\n#     run_multi(app_one)\n\n#     captured = capfd.readouterr()\n#     assert (\n#         \"An OSError was detected on startup. The encountered error was: foo\"\n#     ) in captured.err\n\n#     AsyncioServer.__await__ = orig\n\n\n# def test_running_multiple_offset_warning(app_one, app_two, run_multi, capfd):\n#     app_one.prepare(port=23456, workers=2)\n#     app_two.prepare(port=23457)\n\n#     run_multi(app_one)\n\n#     captured = capfd.readouterr()\n#     assert (\n#         f\"The primary application {repr(app_one)} is running \"\n#         \"with 2 worker(s). All \"\n#         \"application instances will run with the same number. \"\n#         f\"You requested {repr(app_two)} to run with \"\n#         \"1 worker(s), which will be ignored \"\n#         \"in favor of the primary application.\"\n#     ) in captured.err\n\n\n# def test_running_multiple_secondary(app_one, app_two, run_multi, capfd):\n#     app_one.prepare(port=23456, workers=2)\n#     app_two.prepare(port=23457)\n\n#     before_start = AsyncMock()\n#     app_two.before_server_start(before_start)\n#     run_multi(app_one)\n\n#     before_start.await_count == 2\n"
  },
  {
    "path": "tests/test_multiprocessing.py",
    "content": "import logging\nimport multiprocessing\nimport pickle\nimport random\nimport signal\nimport sys\n\nfrom asyncio import sleep\n\nimport pytest\n\nfrom sanic_testing.testing import HOST\n\nfrom sanic import Blueprint, text\nfrom sanic.compat import use_context\nfrom sanic.log import logger\n\n\npytestmark = pytest.mark.xdist_group(name=\"process_spawning\")\n\n\n@pytest.mark.skipif(\n    not hasattr(signal, \"SIGALRM\"),\n    reason=\"SIGALRM is not implemented for this platform, we have to come \"\n    \"up with another timeout strategy to test these\",\n)\n@pytest.mark.skipif(\n    sys.platform not in (\"linux\", \"darwin\"),\n    reason=\"This test requires fork context\",\n)\ndef test_multiprocessing(app, port):\n    \"\"\"Tests that the number of children we produce is correct\"\"\"\n    # Selects a number at random so we can spot check\n    num_workers = random.choice(range(2, multiprocessing.cpu_count() * 2 + 1))\n    process_list = set()\n\n    @app.after_server_start\n    async def shutdown(app):\n        await sleep(2.1)\n        app.stop()\n\n    def stop_on_alarm(*args):\n        for process in multiprocessing.active_children():\n            process_list.add(process.pid)\n\n    signal.signal(signal.SIGALRM, stop_on_alarm)\n    signal.alarm(2)\n    with use_context(\"fork\"):\n        app.run(HOST, port, workers=num_workers, debug=True)\n\n    assert len(process_list) == num_workers + 1\n\n\n@pytest.mark.skipif(\n    not hasattr(signal, \"SIGALRM\"),\n    reason=\"SIGALRM is not implemented for this platform\",\n)\n@pytest.mark.skipif(\n    sys.platform not in (\"linux\", \"darwin\"),\n    reason=\"This test requires fork context\",\n)\ndef test_multiprocessing_with_blueprint(app: object, port) -> object:\n    # Selects a number at random so we can spot check\n    num_workers = random.choice(range(2, multiprocessing.cpu_count() * 2 + 1))\n    process_list = set()\n\n    @app.after_server_start\n    async def shutdown(app):\n        await sleep(2.1)\n        app.stop()\n\n    def stop_on_alarm(*args):\n        for process in multiprocessing.active_children():\n            process_list.add(process.pid)\n\n    signal.signal(signal.SIGALRM, stop_on_alarm)\n    signal.alarm(2)\n\n    bp = Blueprint(\"test_text\")\n    app.blueprint(bp)\n    with use_context(\"fork\"):\n        app.run(HOST, port, workers=num_workers, debug=True)\n\n    assert len(process_list) == num_workers + 1\n\n\n# this function must be outside a test function so that it can be\n# able to be pickled (local functions cannot be pickled).\ndef handler(request):\n    return text(\"Hello\")\n\n\ndef stop(app):\n    app.stop()\n\n\n# Multiprocessing on Windows requires app to be able to be pickled\n@pytest.mark.parametrize(\"protocol\", [3, 4])\ndef test_pickle_app(app, protocol, port):\n    app.route(\"/\")(handler)\n    app.after_server_start(stop)\n    app.router.reset()\n    app.signal_router.reset()\n    p_app = pickle.dumps(app, protocol=protocol)\n    del app\n    up_p_app = pickle.loads(p_app)\n    assert up_p_app\n    up_p_app.run(single_process=True, port=port)\n\n\n@pytest.mark.parametrize(\"protocol\", [3, 4])\ndef test_pickle_app_with_bp(app, protocol, port):\n    bp = Blueprint(\"test_text\")\n    bp.route(\"/\")(handler)\n    bp.after_server_start(stop)\n    app.blueprint(bp)\n    app.router.reset()\n    app.signal_router.reset()\n    p_app = pickle.dumps(app, protocol=protocol)\n    del app\n    up_p_app = pickle.loads(p_app)\n    assert up_p_app\n    up_p_app.run(single_process=True, port=port)\n\n\n@pytest.mark.parametrize(\"protocol\", [3, 4])\ndef test_pickle_app_with_static(app, protocol):\n    app.route(\"/\")(handler)\n    app.after_server_start(stop)\n    app.static(\"/static\", \"/tmp/static\")\n    app.router.reset()\n    app.signal_router.reset()\n    p_app = pickle.dumps(app, protocol=protocol)\n    del app\n    up_p_app = pickle.loads(p_app)\n    assert up_p_app\n    up_p_app.run(single_process=True)\n\n\n@pytest.mark.skipif(\n    sys.platform not in (\"linux\", \"darwin\"),\n    reason=\"This test requires fork context\",\n)\ndef test_main_process_event(app, caplog, port):\n    # Selects a number at random so we can spot check\n    num_workers = random.choice(range(2, multiprocessing.cpu_count() * 2 + 1))\n\n    app.after_server_start(stop)\n\n    @app.listener(\"main_process_start\")\n    def main_process_start(app, loop):\n        logger.info(\"main_process_start\")\n\n    @app.listener(\"main_process_stop\")\n    def main_process_stop(app, loop):\n        logger.info(\"main_process_stop\")\n\n    @app.main_process_start\n    def main_process_start2(app, loop):\n        logger.info(\"main_process_start\")\n\n    @app.main_process_stop\n    def main_process_stop2(app, loop):\n        logger.info(\"main_process_stop\")\n\n    with use_context(\"fork\"):\n        with caplog.at_level(logging.INFO):\n            app.run(HOST, port, workers=num_workers)\n\n    assert (\n        caplog.record_tuples.count((\"sanic.root\", 20, \"main_process_start\"))\n        == 2\n    )\n    assert (\n        caplog.record_tuples.count((\"sanic.root\", 20, \"main_process_stop\"))\n        == 2\n    )\n"
  },
  {
    "path": "tests/test_named_routes.py",
    "content": "#!/usr/bin/env python3\n\nimport asyncio\n\nimport pytest\n\nfrom sanic import Sanic\nfrom sanic.blueprints import Blueprint\nfrom sanic.constants import HTTP_METHODS\nfrom sanic.exceptions import URLBuildError\nfrom sanic.response import text\n\n\n# ------------------------------------------------------------ #\n#  UTF-8\n# ------------------------------------------------------------ #\n\n\n@pytest.mark.parametrize(\"method\", HTTP_METHODS)\ndef test_versioned_named_routes_get(method):\n    app = Sanic(\"app\")\n\n    bp = Blueprint(\"test_bp\", url_prefix=\"/bp\")\n\n    method = method.lower()\n    route_name = f\"route_{method}\"\n    route_name2 = f\"route2_{method}\"\n\n    func = getattr(app, method)\n    if callable(func):\n\n        @func(f\"/{method}\", version=1, name=route_name)\n        def handler(request):\n            return text(\"OK\")\n\n    else:\n        raise\n\n    func = getattr(bp, method)\n    if callable(func):\n\n        @func(f\"/{method}\", version=1, name=route_name2)\n        def handler2(request):\n            return text(\"OK\")\n\n    else:\n        raise\n\n    app.blueprint(bp)\n\n    assert (\n        app.router.routes_all[\n            (\n                \"v1\",\n                method,\n            )\n        ].name\n        == f\"app.{route_name}\"\n    )\n\n    route = app.router.routes_all[\n        (\n            \"v1\",\n            \"bp\",\n            method,\n        )\n    ]\n    assert route.name == f\"app.test_bp.{route_name2}\"\n\n    assert app.url_for(route_name) == f\"/v1/{method}\"\n    url = app.url_for(f\"test_bp.{route_name2}\")\n    assert url == f\"/v1/bp/{method}\"\n    with pytest.raises(URLBuildError):\n        app.url_for(\"handler\")\n\n\ndef test_shorthand_default_routes_get():\n    app = Sanic(\"app\")\n\n    @app.get(\"/get\")\n    def handler(request):\n        return text(\"OK\")\n\n    assert app.router.routes_all[(\"get\",)].name == \"app.handler\"\n    assert app.url_for(\"handler\") == \"/get\"\n\n\ndef test_shorthand_named_routes_get():\n    app = Sanic(\"app\")\n    bp = Blueprint(\"test_bp\", url_prefix=\"/bp\")\n\n    @app.get(\"/get\", name=\"route_get\")\n    def handler(request):\n        return text(\"OK\")\n\n    @bp.get(\"/get\", name=\"route_bp\")\n    def handler2(request):\n        return text(\"Blueprint\")\n\n    app.blueprint(bp)\n\n    assert app.router.routes_all[(\"get\",)].name == \"app.route_get\"\n    assert app.url_for(\"route_get\") == \"/get\"\n    with pytest.raises(URLBuildError):\n        app.url_for(\"handler\")\n\n    assert (\n        app.router.routes_all[\n            (\n                \"bp\",\n                \"get\",\n            )\n        ].name\n        == \"app.test_bp.route_bp\"\n    )\n    assert app.url_for(\"test_bp.route_bp\") == \"/bp/get\"\n    with pytest.raises(URLBuildError):\n        app.url_for(\"test_bp.handler2\")\n\n\ndef test_shorthand_named_routes_post():\n    app = Sanic(\"app\")\n\n    @app.post(\"/post\", name=\"route_name\")\n    def handler(request):\n        return text(\"OK\")\n\n    assert app.router.routes_all[(\"post\",)].name == \"app.route_name\"\n    assert app.url_for(\"route_name\") == \"/post\"\n    with pytest.raises(URLBuildError):\n        app.url_for(\"handler\")\n\n\ndef test_shorthand_named_routes_put():\n    app = Sanic(\"app\")\n\n    @app.put(\"/put\", name=\"route_put\")\n    def handler(request):\n        return text(\"OK\")\n\n    assert app.router.routes_all[(\"put\",)].name == \"app.route_put\"\n    assert app.url_for(\"route_put\") == \"/put\"\n    with pytest.raises(URLBuildError):\n        app.url_for(\"handler\")\n\n\ndef test_shorthand_named_routes_delete():\n    app = Sanic(\"app\")\n\n    @app.delete(\"/delete\", name=\"route_delete\")\n    def handler(request):\n        return text(\"OK\")\n\n    assert app.router.routes_all[(\"delete\",)].name == \"app.route_delete\"\n    assert app.url_for(\"route_delete\") == \"/delete\"\n    with pytest.raises(URLBuildError):\n        app.url_for(\"handler\")\n\n\ndef test_shorthand_named_routes_patch():\n    app = Sanic(\"app\")\n\n    @app.patch(\"/patch\", name=\"route_patch\")\n    def handler(request):\n        return text(\"OK\")\n\n    assert app.router.routes_all[(\"patch\",)].name == \"app.route_patch\"\n    assert app.url_for(\"route_patch\") == \"/patch\"\n    with pytest.raises(URLBuildError):\n        app.url_for(\"handler\")\n\n\ndef test_shorthand_named_routes_head():\n    app = Sanic(\"app\")\n\n    @app.head(\"/head\", name=\"route_head\")\n    def handler(request):\n        return text(\"OK\")\n\n    assert app.router.routes_all[(\"head\",)].name == \"app.route_head\"\n    assert app.url_for(\"route_head\") == \"/head\"\n    with pytest.raises(URLBuildError):\n        app.url_for(\"handler\")\n\n\ndef test_shorthand_named_routes_options():\n    app = Sanic(\"app\")\n\n    @app.options(\"/options\", name=\"route_options\")\n    def handler(request):\n        return text(\"OK\")\n\n    assert app.router.routes_all[(\"options\",)].name == \"app.route_options\"\n    assert app.url_for(\"route_options\") == \"/options\"\n    with pytest.raises(URLBuildError):\n        app.url_for(\"handler\")\n\n\ndef test_named_static_routes():\n    app = Sanic(\"app\")\n\n    @app.route(\"/test\", name=\"route_test\")\n    async def handler1(request):\n        return text(\"OK1\")\n\n    @app.route(\"/pizazz\", name=\"route_pizazz\")\n    async def handler2(request):\n        return text(\"OK2\")\n\n    assert app.router.routes_all[(\"test\",)].name == \"app.route_test\"\n    assert app.router.routes_static[(\"test\",)][0].name == \"app.route_test\"\n    assert app.url_for(\"route_test\") == \"/test\"\n    with pytest.raises(URLBuildError):\n        app.url_for(\"handler1\")\n\n    assert app.router.routes_all[(\"pizazz\",)].name == \"app.route_pizazz\"\n    assert app.router.routes_static[(\"pizazz\",)][0].name == \"app.route_pizazz\"\n    assert app.url_for(\"route_pizazz\") == \"/pizazz\"\n    with pytest.raises(URLBuildError):\n        app.url_for(\"handler2\")\n\n\ndef test_named_dynamic_route():\n    app = Sanic(\"app\")\n    results = []\n\n    @app.route(\"/folder/<name>\", name=\"route_dynamic\")\n    async def handler(request, name):\n        results.append(name)\n        return text(\"OK\")\n\n    assert (\n        app.router.routes_all[\n            (\n                \"folder\",\n                \"<name:str>\",\n            )\n        ].name\n        == \"app.route_dynamic\"\n    )\n    assert app.url_for(\"route_dynamic\", name=\"test\") == \"/folder/test\"\n    with pytest.raises(URLBuildError):\n        app.url_for(\"handler\")\n\n\ndef test_dynamic_named_route_regex():\n    app = Sanic(\"app\")\n\n    @app.route(\"/folder/<folder_id:[A-Za-z0-9]{0,4}>\", name=\"route_re\")\n    async def handler(request, folder_id):\n        return text(\"OK\")\n\n    route = app.router.routes_all[\n        (\n            \"folder\",\n            \"<folder_id:[A-Za-z0-9]{0,4}>\",\n        )\n    ]\n    assert route.name == \"app.route_re\"\n    assert app.url_for(\"route_re\", folder_id=\"test\") == \"/folder/test\"\n    with pytest.raises(URLBuildError):\n        app.url_for(\"handler\")\n\n\ndef test_dynamic_named_route_path():\n    app = Sanic(\"app\")\n\n    @app.route(\"/<path:path>/info\", name=\"route_dynamic_path\")\n    async def handler(request, path):\n        return text(\"OK\")\n\n    route = app.router.routes_all[\n        (\n            \"<path:path>\",\n            \"info\",\n        )\n    ]\n    assert route.name == \"app.route_dynamic_path\"\n    assert app.url_for(\"route_dynamic_path\", path=\"path/1\") == \"/path/1/info\"\n    with pytest.raises(URLBuildError):\n        app.url_for(\"handler\")\n\n\ndef test_dynamic_named_route_unhashable():\n    app = Sanic(\"app\")\n\n    @app.route(\n        \"/folder/<unhashable:[A-Za-z0-9/]+>/end/\", name=\"route_unhashable\"\n    )\n    async def handler(request, unhashable):\n        return text(\"OK\")\n\n    route = app.router.routes_all[\n        (\n            \"folder\",\n            \"<unhashable:[A-Za-z0-9/]+>\",\n            \"end\",\n        )\n    ]\n    assert route.name == \"app.route_unhashable\"\n    url = app.url_for(\"route_unhashable\", unhashable=\"test/asdf\")\n    assert url == \"/folder/test/asdf/end\"\n    with pytest.raises(URLBuildError):\n        app.url_for(\"handler\")\n\n\ndef test_websocket_named_route():\n    app = Sanic(\"app\")\n    ev = asyncio.Event()\n\n    @app.websocket(\"/ws\", name=\"route_ws\")\n    async def handler(request, ws):\n        assert ws.subprotocol is None\n        ev.set()\n\n    assert app.router.routes_all[(\"ws\",)].name == \"app.route_ws\"\n    assert app.url_for(\"route_ws\") == \"/ws\"\n    with pytest.raises(URLBuildError):\n        app.url_for(\"handler\")\n\n\ndef test_websocket_named_route_with_subprotocols():\n    app = Sanic(\"app\")\n    results = []\n\n    @app.websocket(\"/ws\", subprotocols=[\"foo\", \"bar\"], name=\"route_ws\")\n    async def handler(request, ws):\n        results.append(ws.subprotocol)\n\n    assert app.router.routes_all[(\"ws\",)].name == \"app.route_ws\"\n    assert app.url_for(\"route_ws\") == \"/ws\"\n    with pytest.raises(URLBuildError):\n        app.url_for(\"handler\")\n\n\ndef test_static_add_named_route():\n    app = Sanic(\"app\")\n\n    async def handler1(request):\n        return text(\"OK1\")\n\n    async def handler2(request):\n        return text(\"OK2\")\n\n    app.add_route(handler1, \"/test\", name=\"route_test\")\n    app.add_route(handler2, \"/test2\", name=\"route_test2\")\n\n    assert app.router.routes_all[(\"test\",)].name == \"app.route_test\"\n    assert app.router.routes_static[(\"test\",)][0].name == \"app.route_test\"\n    assert app.url_for(\"route_test\") == \"/test\"\n    with pytest.raises(URLBuildError):\n        app.url_for(\"handler1\")\n\n    assert app.router.routes_all[(\"test2\",)].name == \"app.route_test2\"\n    assert app.router.routes_static[(\"test2\",)][0].name == \"app.route_test2\"\n    assert app.url_for(\"route_test2\") == \"/test2\"\n    with pytest.raises(URLBuildError):\n        app.url_for(\"handler2\")\n\n\ndef test_dynamic_add_named_route():\n    app = Sanic(\"app\")\n    results = []\n\n    async def handler(request, name):\n        results.append(name)\n        return text(\"OK\")\n\n    app.add_route(handler, \"/folder/<name>\", name=\"route_dynamic\")\n    assert (\n        app.router.routes_all[(\"folder\", \"<name:str>\")].name\n        == \"app.route_dynamic\"\n    )\n    assert app.url_for(\"route_dynamic\", name=\"test\") == \"/folder/test\"\n    with pytest.raises(URLBuildError):\n        app.url_for(\"handler\")\n\n\ndef test_dynamic_add_named_route_unhashable():\n    app = Sanic(\"app\")\n\n    async def handler(request, unhashable):\n        return text(\"OK\")\n\n    app.add_route(\n        handler,\n        \"/folder/<unhashable:[A-Za-z0-9/]+>/end/\",\n        name=\"route_unhashable\",\n    )\n    route = app.router.routes_all[\n        (\n            \"folder\",\n            \"<unhashable:[A-Za-z0-9/]+>\",\n            \"end\",\n        )\n    ]\n    assert route.name == \"app.route_unhashable\"\n    url = app.url_for(\"route_unhashable\", unhashable=\"folder1\")\n    assert url == \"/folder/folder1/end\"\n    with pytest.raises(URLBuildError):\n        app.url_for(\"handler\")\n\n\ndef test_overload_routes():\n    app = Sanic(\"app\")\n\n    @app.route(\"/overload\", methods=[\"GET\"], name=\"route_first\")\n    async def handler1(request):\n        return text(\"OK1\")\n\n    @app.route(\"/overload\", methods=[\"POST\", \"PUT\"], name=\"route_second\")\n    async def handler2(request):\n        return text(\"OK2\")\n\n    request, response = app.test_client.get(app.url_for(\"route_first\"))\n    assert response.text == \"OK1\"\n\n    request, response = app.test_client.post(app.url_for(\"route_first\"))\n    assert response.text == \"OK2\"\n\n    request, response = app.test_client.put(app.url_for(\"route_first\"))\n    assert response.text == \"OK2\"\n\n    request, response = app.test_client.get(app.url_for(\"route_second\"))\n    assert response.text == \"OK1\"\n\n    request, response = app.test_client.post(app.url_for(\"route_second\"))\n    assert response.text == \"OK2\"\n\n    request, response = app.test_client.put(app.url_for(\"route_second\"))\n    assert response.text == \"OK2\"\n\n    assert app.router.routes_all[(\"overload\",)].name == \"app.route_first\"\n    with pytest.raises(URLBuildError):\n        app.url_for(\"handler1\")\n\n    assert app.url_for(\"route_first\") == \"/overload\"\n    assert app.url_for(\"route_second\") == app.url_for(\"route_first\")\n"
  },
  {
    "path": "tests/test_naming.py",
    "content": "from sanic import Blueprint, Sanic, text\n\n\ndef factory(sanic_cls: type[Sanic], blueprint_cls: type[Blueprint]):\n    app = sanic_cls(\"Foo\")\n    bp = blueprint_cls(\"Bar\", url_prefix=\"/bar\")\n\n    @app.get(\"/\")\n    async def handler(request):\n        return text(request.name)\n\n    @bp.get(\"/\")\n    async def handler(request):  # noqa: F811  // Intentionally reused handler name\n        return text(request.name)\n\n    app.blueprint(bp)\n\n    return app\n\n\ndef test_vanilla_sanic():\n    app = factory(Sanic, Blueprint)\n    _, foo_response = app.test_client.get(\"/\")\n    _, bar_response = app.test_client.get(\"/bar/\")\n\n    assert foo_response.text == \"Foo.handler\"\n    assert bar_response.text == \"Foo.Bar.handler\"\n\n\ndef test_custom_app():\n    class Custom(Sanic):\n        def generate_name(self, *objects):\n            existing = self._generate_name(*objects)\n            return existing.replace(\"Foo\", \"CHANGED_APP\")\n\n    app = factory(Custom, Blueprint)\n    _, foo_response = app.test_client.get(\"/\")\n    _, bar_response = app.test_client.get(\"/bar/\")\n\n    assert foo_response.text == \"CHANGED_APP.handler\"\n    assert bar_response.text == \"CHANGED_APP.Bar.handler\"\n\n\ndef test_custom_blueprint():\n    class Custom(Blueprint):\n        def generate_name(self, *objects):\n            existing = self._generate_name(*objects)\n            return existing.replace(\"Bar\", \"CHANGED_BP\")\n\n    app = factory(Sanic, Custom)\n    _, foo_response = app.test_client.get(\"/\")\n    _, bar_response = app.test_client.get(\"/bar/\")\n\n    assert foo_response.text == \"Foo.handler\"\n    assert bar_response.text == \"Foo.CHANGED_BP.handler\"\n"
  },
  {
    "path": "tests/test_payload_too_large.py",
    "content": "from sanic.exceptions import PayloadTooLarge\nfrom sanic.response import text\n\n\ndef test_payload_too_large_from_error_handler(app):\n    app.config.REQUEST_MAX_SIZE = 1\n\n    @app.route(\"/1\")\n    async def handler1(request):\n        return text(\"OK\")\n\n    @app.exception(PayloadTooLarge)\n    def handler_exception(request, exception):\n        return text(\"Payload Too Large from error_handler.\", 413)\n\n    _, response = app.test_client.get(\"/1\", gather_request=False)\n    assert response.status == 413\n    assert response.text == \"Payload Too Large from error_handler.\"\n\n\ndef test_payload_too_large_at_data_received_default(app):\n    app.config.REQUEST_MAX_SIZE = 1\n\n    @app.route(\"/1\")\n    async def handler2(request):\n        return text(\"OK\")\n\n    _, response = app.test_client.get(\"/1\", gather_request=False)\n    assert response.status == 413\n    assert \"Request header\" in response.text\n\n\ndef test_payload_too_large_at_on_header_default(app):\n    app.config.REQUEST_MAX_SIZE = 500\n\n    @app.post(\"/1\")\n    async def handler3(request):\n        return text(\"OK\")\n\n    data = \"a\" * 1000\n    _, response = app.test_client.post(\"/1\", gather_request=False, data=data)\n    assert response.status == 413\n    assert \"Request body\" in response.text\n"
  },
  {
    "path": "tests/test_pipelining.py",
    "content": "from sanic_testing.reusable import ReusableClient\n\nfrom sanic.response import json, text\n\n\ndef test_no_body_requests(app, port):\n    @app.get(\"/\")\n    async def handler(request):\n        return json(\n            {\n                \"request_id\": str(request.id),\n                \"connection_id\": id(request.conn_info),\n            }\n        )\n\n    client = ReusableClient(app, port=port)\n\n    with client:\n        _, response1 = client.get(\"/\")\n        _, response2 = client.get(\"/\")\n\n    assert response1.status == response2.status == 200\n    assert response1.json[\"request_id\"] != response2.json[\"request_id\"]\n    assert response1.json[\"connection_id\"] == response2.json[\"connection_id\"]\n\n\ndef test_json_body_requests(app, port):\n    @app.post(\"/\")\n    async def handler(request):\n        return json(\n            {\n                \"request_id\": str(request.id),\n                \"connection_id\": id(request.conn_info),\n                \"foo\": request.json.get(\"foo\"),\n            }\n        )\n\n    client = ReusableClient(app, port=port)\n\n    with client:\n        _, response1 = client.post(\"/\", json={\"foo\": True})\n        _, response2 = client.post(\"/\", json={\"foo\": True})\n\n    assert response1.status == response2.status == 200\n    assert response1.json[\"foo\"] is response2.json[\"foo\"] is True\n    assert response1.json[\"request_id\"] != response2.json[\"request_id\"]\n    assert response1.json[\"connection_id\"] == response2.json[\"connection_id\"]\n\n\ndef test_streaming_body_requests(app, port):\n    @app.post(\"/\", stream=True)\n    async def handler(request):\n        data = [part.decode(\"utf-8\") async for part in request.stream]\n        return json(\n            {\n                \"request_id\": str(request.id),\n                \"connection_id\": id(request.conn_info),\n                \"data\": data,\n            }\n        )\n\n    data = [\"hello\", \"world\"]\n\n    client = ReusableClient(app, port=port)\n\n    async def stream(data):\n        for value in data:\n            yield value.encode(\"utf-8\")\n\n    with client:\n        _, response1 = client.post(\"/\", data=stream(data))\n        _, response2 = client.post(\"/\", data=stream(data))\n\n    assert response1.status == response2.status == 200\n    assert response1.json[\"data\"] == response2.json[\"data\"] == data\n    assert response1.json[\"request_id\"] != response2.json[\"request_id\"]\n    assert response1.json[\"connection_id\"] == response2.json[\"connection_id\"]\n\n\ndef test_bad_headers(app, port):\n    @app.get(\"/\")\n    async def handler(request):\n        return text(\"\")\n\n    @app.on_response\n    async def reqid(request, response):\n        response.headers[\"x-request-id\"] = request.id\n\n    client = ReusableClient(app, port=port)\n    bad_headers = {\"bad\": \"bad\" * 5_000}\n\n    with client:\n        _, response1 = client.get(\"/\")\n        _, response2 = client.get(\"/\", headers=bad_headers)\n\n    assert response1.status == 200\n    assert response2.status == 413\n    assert (\n        response1.headers[\"x-request-id\"] != response2.headers[\"x-request-id\"]\n    )\n"
  },
  {
    "path": "tests/test_prepare.py",
    "content": "import logging\nimport os\n\nfrom pathlib import Path\nfrom unittest.mock import Mock\n\nimport pytest\n\nfrom sanic import Sanic\nfrom sanic.application.state import ApplicationServerInfo\n\n\n@pytest.fixture(autouse=True)\ndef no_skip():\n    should_auto_reload = Sanic.should_auto_reload\n    Sanic.should_auto_reload = Mock(return_value=False)\n    yield\n    Sanic._app_registry = {}\n    Sanic.should_auto_reload = should_auto_reload\n    try:\n        del os.environ[\"SANIC_MOTD_OUTPUT\"]\n    except KeyError:\n        ...\n\n\ndef get_primary(app: Sanic) -> ApplicationServerInfo:\n    return app.state.server_info[0]\n\n\ndef test_dev(app: Sanic):\n    app.prepare(dev=True)\n\n    assert app.state.is_debug\n    assert app.state.auto_reload\n\n\ndef test_motd_display(app: Sanic):\n    app.prepare(motd_display={\"foo\": \"bar\"})\n\n    assert app.config.MOTD_DISPLAY[\"foo\"] == \"bar\"\n    del app.config.MOTD_DISPLAY[\"foo\"]\n\n\n@pytest.mark.parametrize(\"dirs\", (\"./foo\", (\"./foo\", \"./bar\")))\ndef test_reload_dir(app: Sanic, dirs, caplog):\n    messages = []\n    with caplog.at_level(logging.WARNING):\n        app.prepare(reload_dir=dirs)\n\n    if isinstance(dirs, str):\n        dirs = (dirs,)\n        for d in dirs:\n            assert Path(d) in app.state.reload_dirs\n            messages.append(\n                f\"Directory {d} could not be located\",\n            )\n\n    for message in messages:\n        assert (\"sanic.root\", logging.WARNING, message) in caplog.record_tuples\n\n\ndef test_fast(app: Sanic, caplog):\n    @app.after_server_start\n    async def stop(app, _):\n        app.stop()\n\n    try:\n        workers = len(os.sched_getaffinity(0))\n    except AttributeError:\n        workers = os.cpu_count() or 1\n\n    with caplog.at_level(logging.INFO):\n        app.prepare(fast=True)\n\n    assert app.state.fast\n    assert app.state.workers == workers\n\n    messages = [m[2] for m in caplog.record_tuples]\n\n    if workers == 1:\n        worker_fragment = \"single worker\"\n    else:\n        worker_fragment = f\"w/ {workers} workers\"\n\n    assert f\"mode: production, goin' fast {worker_fragment}\" in messages\n"
  },
  {
    "path": "tests/test_redirect.py",
    "content": "from urllib.parse import quote\n\nimport pytest\n\nfrom sanic.response import redirect, text\n\n\n@pytest.fixture\ndef redirect_app(app):\n    @app.route(\"/redirect_init\")\n    async def redirect_init(request):\n        return redirect(\"/redirect_target\")\n\n    @app.route(\"/redirect_init_with_301\")\n    async def redirect_init_with_301(request):\n        return redirect(\"/redirect_target\", status=301)\n\n    @app.route(\"/redirect_target\")\n    async def redirect_target(request):\n        return text(\"OK\")\n\n    @app.route(\"/1\")\n    def handler1(request):\n        return redirect(\"/2\")\n\n    @app.route(\"/2\")\n    def handler2(request):\n        return redirect(\"/3\")\n\n    @app.route(\"/3\")\n    def handler3(request):\n        return text(\"OK\")\n\n    @app.route(\"/redirect_with_header_injection\")\n    async def redirect_with_header_injection(request):\n        return redirect(\"/unsafe\\ntest-header: test-value\\n\\ntest-body\")\n\n    return app\n\n\ndef test_redirect_default_302(redirect_app):\n    \"\"\"\n    We expect a 302 default status code and the headers to be set.\n    \"\"\"\n    request, response = redirect_app.test_client.get(\n        \"/redirect_init\", allow_redirects=False\n    )\n\n    assert response.status == 302\n    assert response.headers[\"Location\"] == \"/redirect_target\"\n    assert response.headers[\"Content-Type\"] == \"text/html; charset=utf-8\"\n\n\ndef test_redirect_headers_none(redirect_app):\n    request, response = redirect_app.test_client.get(\n        uri=\"/redirect_init\", headers=None, allow_redirects=False\n    )\n\n    assert response.status == 302\n    assert response.headers[\"Location\"] == \"/redirect_target\"\n\n\ndef test_redirect_with_301(redirect_app):\n    \"\"\"\n    Test redirection with a different status code.\n    \"\"\"\n    request, response = redirect_app.test_client.get(\n        \"/redirect_init_with_301\", allow_redirects=False\n    )\n\n    assert response.status == 301\n    assert response.headers[\"Location\"] == \"/redirect_target\"\n\n\ndef test_get_then_redirect_follow_redirect(redirect_app):\n    \"\"\"\n    With `allow_redirects` we expect a 200.\n    \"\"\"\n    request, response = redirect_app.test_client.get(\n        \"/redirect_init\", allow_redirects=True\n    )\n\n    assert response.status == 200\n    assert response.text == \"OK\"\n\n\ndef test_chained_redirect(redirect_app):\n    \"\"\"Test test_client is working for redirection\"\"\"\n    request, response = redirect_app.test_client.get(\"/1\")\n    assert request.url.endswith(\"/1\")\n    assert response.status == 200\n    assert response.text == \"OK\"\n    try:\n        assert response.url.endswith(\"/3\")\n    except AttributeError:\n        assert response.url.path.endswith(\"/3\")\n\n\ndef test_redirect_with_header_injection(redirect_app):\n    \"\"\"\n    Test redirection to a URL with header and body injections.\n    \"\"\"\n    request, response = redirect_app.test_client.get(\n        \"/redirect_with_header_injection\", allow_redirects=False\n    )\n\n    assert response.status == 302\n    assert \"test-header\" not in response.headers\n    assert not response.text.startswith(\"test-body\")\n\n\n@pytest.mark.parametrize(\n    \"test_str\",\n    [\n        \"sanic-test\",\n        \"sanictest\",\n        \"sanic test\",\n    ],\n)\ndef test_redirect_with_params(app, test_str):\n    use_in_uri = quote(test_str)\n\n    @app.route(\"/api/v1/test/<test>/\")\n    async def init_handler(request, test):\n        return redirect(f\"/api/v2/test/{use_in_uri}/\")\n\n    @app.route(\"/api/v2/test/<test>/\", unquote=True)\n    async def target_handler(request, test):\n        assert test == quote(test_str)\n        return text(\"OK\")\n\n    _, response = app.test_client.get(f\"/api/v1/test/{use_in_uri}/\")\n    assert response.status == 200\n\n    assert response.body == b\"OK\"\n"
  },
  {
    "path": "tests/test_reloader.py",
    "content": "# 2024-12-22 AMH - Reloader tests have not been working for a while.\n# We need to re-implement them.\n\n# import os\n# import secrets\n# import sys\n\n# from contextlib import suppress\n# from subprocess import PIPE, Popen, TimeoutExpired\n# from tempfile import TemporaryDirectory\n# from textwrap import dedent\n# from threading import Timer\n# from time import sleep\n\n# import pytest\n\n\n# # We need to interrupt the autoreloader without killing it,\n# # so that the server gets terminated\n# # https://stefan.sofa-rockers.org/2013/08/15/handling-sub-process-hierarchies-python-linux-os-x/\n\n# try:\n#     from signal import CTRL_BREAK_EVENT\n#     from subprocess import CREATE_NEW_PROCESS_GROUP\n\n#     flags = CREATE_NEW_PROCESS_GROUP\n# except ImportError:\n#     flags = 0\n\n# TIMER_DELAY = 2\n\n\n# def terminate(proc):\n#     if flags:\n#         proc.send_signal(CTRL_BREAK_EVENT)\n#     else:\n#         proc.terminate()\n\n\n# def write_app(filename, **runargs):\n#     text = secrets.token_urlsafe()\n#     with open(filename, \"w\") as f:\n#         f.write(\n#             dedent(\n#                 f\"\"\"\\\n#             import os\n#             from sanic import Sanic\n\n#             app = Sanic(__name__)\n\n#             app.route(\"/\")(lambda x: x)\n\n#             @app.listener(\"after_server_start\")\n#             def complete(*args):\n#                 print(\"complete\", os.getpid(), {text!r})\n\n#             if __name__ == \"__main__\":\n#                 app.run(**{runargs!r})\n#             \"\"\"\n#             )\n#         )\n#     return text\n\n\n# def write_listener_app(filename, **runargs):\n#     start_text = secrets.token_urlsafe()\n#     stop_text = secrets.token_urlsafe()\n#     with open(filename, \"w\") as f:\n#         f.write(\n#             dedent(\n#                 f\"\"\"\\\n#             import os\n#             from sanic import Sanic\n\n#             app = Sanic(__name__)\n\n#             app.route(\"/\")(lambda x: x)\n\n#             @app.reload_process_start\n#             async def reload_start(*_):\n#                 print(\"reload_start\", os.getpid(), {start_text!r})\n\n#             @app.reload_process_stop\n#             async def reload_stop(*_):\n#                 print(\"reload_stop\", os.getpid(), {stop_text!r})\n\n#             if __name__ == \"__main__\":\n#                 app.run(**{runargs!r})\n#             \"\"\"\n#             )\n#         )\n#     return start_text, stop_text\n\n\n# def write_json_config_app(filename, jsonfile, **runargs):\n#     with open(filename, \"w\") as f:\n#         f.write(\n#             dedent(\n#                 f\"\"\"\\\n#             import os\n#             from sanic import Sanic\n#             import json\n\n#             app = Sanic(__name__)\n#             with open(\"{jsonfile}\", \"r\") as f:\n#                 config = json.load(f)\n#             app.config.update_config(config)\n\n#             app.route(\"/\")(lambda x: x)\n\n#             @app.listener(\"after_server_start\")\n#             def complete(*args):\n#                 print(\"complete\", os.getpid(), app.config.FOO)\n\n#             if __name__ == \"__main__\":\n#                 app.run(**{runargs!r})\n#             \"\"\"\n#             )\n#         )\n\n\n# def write_file(filename):\n#     text = secrets.token_urlsafe()\n#     with open(filename, \"w\") as f:\n#         f.write(f\"\"\"{{\"FOO\": \"{text}\"}}\"\"\")\n#     return text\n\n\n# def scanner(proc, trigger=\"complete\"):\n#     for line in proc.stdout:\n#         line = line.decode().strip()\n#         if line.startswith(trigger):\n#             yield line\n\n\n# argv = dict(\n#     script=[sys.executable, \"reloader.py\"],\n#     module=[sys.executable, \"-m\", \"reloader\"],\n#     sanic=[\n#         sys.executable,\n#         \"-m\",\n#         \"sanic\",\n#         \"--port\",\n#         \"42204\",\n#         \"--auto-reload\",\n#         \"reloader.app\",\n#     ],\n# )\n\n\n# @pytest.mark.parametrize(\n#     \"runargs, mode\",\n#     [\n#         (dict(port=42202, auto_reload=True), \"script\"),\n#         (dict(port=42203, auto_reload=True), \"module\"),\n#         ({}, \"sanic\"),\n#     ],\n# )\n# @pytest.mark.xfail\n# async def test_reloader_live(runargs, mode):\n#     with TemporaryDirectory() as tmpdir:\n#         filename = os.path.join(tmpdir, \"reloader.py\")\n#         text = write_app(filename, **runargs)\n#         command = argv[mode]\n#         proc = Popen(command, cwd=tmpdir, stdout=PIPE, creationflags=flags)\n#         try:\n#             timeout = Timer(TIMER_DELAY, terminate, [proc])\n#             timeout.start()\n#             # Python apparently keeps using the old source sometimes if\n#             # we don't sleep before rewrite (pycache timestamp problem?)\n#             sleep(1)\n#             line = scanner(proc)\n#             assert text in next(line)\n#             # Edit source code and try again\n#             text = write_app(filename, **runargs)\n#             assert text in next(line)\n#         finally:\n#             timeout.cancel()\n#             terminate(proc)\n#             with suppress(TimeoutExpired):\n#                 proc.wait(timeout=3)\n\n\n# @pytest.mark.parametrize(\n#     \"runargs, mode\",\n#     [\n#         (dict(port=42302, auto_reload=True), \"script\"),\n#         (dict(port=42303, auto_reload=True), \"module\"),\n#         ({}, \"sanic\"),\n#     ],\n# )\n# @pytest.mark.xfail\n# async def test_reloader_live_with_dir(runargs, mode):\n#     with TemporaryDirectory() as tmpdir:\n#         filename = os.path.join(tmpdir, \"reloader.py\")\n#         config_file = os.path.join(tmpdir, \"config.json\")\n#         runargs[\"reload_dir\"] = tmpdir\n#         write_json_config_app(filename, config_file, **runargs)\n#         text = write_file(config_file)\n#         command = argv[mode]\n#         if mode == \"sanic\":\n#             command += [\"--reload-dir\", tmpdir]\n#         proc = Popen(command, cwd=tmpdir, stdout=PIPE, creationflags=flags)\n#         try:\n#             timeout = Timer(TIMER_DELAY, terminate, [proc])\n#             timeout.start()\n#             # Python apparently keeps using the old source sometimes if\n#             # we don't sleep before rewrite (pycache timestamp problem?)\n#             sleep(1)\n#             line = scanner(proc)\n#             assert text in next(line)\n#             # Edit source code and try again\n#             text = write_file(config_file)\n#             assert text in next(line)\n#         finally:\n#             timeout.cancel()\n#             terminate(proc)\n#             with suppress(TimeoutExpired):\n#                 proc.wait(timeout=3)\n\n\n# def test_reload_listeners():\n#     with TemporaryDirectory() as tmpdir:\n#         filename = os.path.join(tmpdir, \"reloader.py\")\n#         start_text, stop_text = write_listener_app(\n#             filename, port=42305, auto_reload=True\n#         )\n\n#         proc = Popen(\n#             argv[\"script\"], cwd=tmpdir, stdout=PIPE, creationflags=flags\n#         )\n#         try:\n#             timeout = Timer(TIMER_DELAY, terminate, [proc])\n#             timeout.start()\n#             # Python apparently keeps using the old source sometimes if\n#             # we don't sleep before rewrite (pycache timestamp problem?)\n#             sleep(1)\n#             line = scanner(proc, \"reload_start\")\n#             assert start_text in next(line)\n#             line = scanner(proc, \"reload_stop\")\n#             assert stop_text in next(line)\n#         finally:\n#             timeout.cancel()\n#             terminate(proc)\n#             with suppress(TimeoutExpired):\n#                 proc.wait(timeout=3)\n"
  },
  {
    "path": "tests/test_request.py",
    "content": "import uuid\n\nfrom unittest.mock import Mock\nfrom uuid import UUID, uuid4\n\nimport pytest\n\nfrom sanic import Sanic, response\nfrom sanic.exceptions import BadURL, SanicException\nfrom sanic.request import Request\nfrom sanic.server import HttpProtocol\n\n\ndef test_no_request_id_not_called(monkeypatch):\n    monkeypatch.setattr(uuid, \"uuid4\", Mock())\n    request = Request(b\"/\", {}, None, \"GET\", None, None)\n\n    assert request._id is None\n    uuid.uuid4.assert_not_called()\n\n\ndef test_request_id_generates_from_request(monkeypatch):\n    monkeypatch.setattr(Request, \"generate_id\", Mock())\n    Request.generate_id.return_value = 1\n    request = Request(b\"/\", {}, None, \"GET\", None, Mock())\n    request.app.config.REQUEST_ID_HEADER = \"foo\"\n\n    for _ in range(10):\n        request.id\n    Request.generate_id.assert_called_once_with(request)\n\n\ndef test_request_id_defaults_uuid():\n    request = Request(b\"/\", {}, None, \"GET\", None, Mock())\n    request.app.config.REQUEST_ID_HEADER = \"foo\"\n\n    assert isinstance(request.id, UUID)\n\n    # Makes sure that it has been cached and not called multiple times\n    assert request.id == request.id == request._id\n\n\ndef test_name_none():\n    request = Request(b\"/\", {}, None, \"GET\", None, None)\n\n    assert request.name is None\n\n\ndef test_name_from_route():\n    request = Request(b\"/\", {}, None, \"GET\", None, None)\n    route = Mock()\n    request.route = route\n\n    assert request.name == route.name\n\n\ndef test_name_from_set():\n    request = Request(b\"/\", {}, None, \"GET\", None, None)\n    request._name = \"foo\"\n\n    assert request.name == \"foo\"\n\n\n@pytest.mark.parametrize(\n    \"request_id,expected_type\",\n    (\n        (99, int),\n        (uuid4(), UUID),\n        (\"foo\", str),\n    ),\n)\ndef test_request_id(request_id, expected_type):\n    app = Sanic(\"req-generator\")\n\n    @app.get(\"/\")\n    async def get(request):\n        return response.empty()\n\n    request, _ = app.test_client.get(\n        \"/\", headers={\"X-REQUEST-ID\": f\"{request_id}\"}\n    )\n    assert request.id == request_id\n    assert type(request.id) is expected_type\n\n\ndef test_custom_generator():\n    REQUEST_ID = 99\n\n    class FooRequest(Request):\n        @classmethod\n        def generate_id(cls, request):\n            return int(request.headers[\"some-other-request-id\"]) * 2\n\n    app = Sanic(\"req-generator\", request_class=FooRequest)\n\n    @app.get(\"/\")\n    async def get(request):\n        return response.empty()\n\n    request, _ = app.test_client.get(\n        \"/\", headers={\"SOME-OTHER-REQUEST-ID\": f\"{REQUEST_ID}\"}\n    )\n    assert request.id == REQUEST_ID * 2\n\n\ndef test_route_assigned_to_request(app):\n    @app.get(\"/\")\n    async def get(request):\n        return response.empty()\n\n    request, _ = app.test_client.get(\"/\")\n    assert request.route is list(app.router.routes)[0]\n\n\ndef test_protocol_attribute(app):\n    retrieved = None\n\n    @app.get(\"/\")\n    async def get(request):\n        nonlocal retrieved\n        retrieved = request.protocol\n        return response.empty()\n\n    headers = {\"Connection\": \"keep-alive\"}\n    _ = app.test_client.get(\"/\", headers=headers)\n\n    assert isinstance(retrieved, HttpProtocol)\n\n\ndef test_ipv6_address_is_not_wrapped(app):\n    @app.get(\"/\")\n    async def get(request):\n        return response.json(\n            {\n                \"client_ip\": request.conn_info.client_ip,\n                \"client\": request.conn_info.client,\n            }\n        )\n\n    request, resp = app.test_client.get(\"/\", host=\"::1\")\n\n    assert request.route is list(app.router.routes)[0]\n    assert resp.json[\"client\"] == \"[::1]\"\n    assert resp.json[\"client_ip\"] == \"::1\"\n    assert request.ip == \"::1\"\n\n\ndef test_request_accept():\n    app = Sanic(\"req-generator\")\n\n    @app.get(\"/\")\n    async def get(request):\n        return response.empty()\n\n    header_value = \"text/plain;format=flowed, text/plain, text/*, */*\"\n    request, _ = app.test_client.get(\n        \"/\",\n        headers={\"Accept\": header_value},\n    )\n    assert str(request.accept) == header_value\n    match = request.accept.match(\n        \"*/*;format=flowed\",\n        \"text/plain;format=flowed\",\n        \"text/plain\",\n        \"text/*\",\n        \"*/*\",\n    )\n    assert match == \"*/*;format=flowed\"\n    assert match.header.mime == \"text/plain\"\n    assert match.header.params == {\"format\": \"flowed\"}\n\n    header_value = (\n        \"text/plain; q=0.5,   text/html, text/x-dvi; q=0.8, text/x-c\"\n    )\n    request, _ = app.test_client.get(\n        \"/\",\n        headers={\"Accept\": header_value},\n    )\n    assert [str(i) for i in request.accept] == [\n        \"text/html\",\n        \"text/x-c\",\n        \"text/x-dvi;q=0.8\",\n        \"text/plain;q=0.5\",\n    ]\n    match = request.accept.match(\n        \"application/json\",\n        \"text/plain\",  # Has lower q in accept header\n        \"text/html;format=flowed\",  # Params mismatch\n        \"text/*\",  # Matches\n        \"*/*\",\n    )\n    assert match == \"text/*\"\n    assert match.header.mime == \"text/html\"\n    assert match.header.q == 1.0\n    assert not match.header.params\n\n\ndef test_bad_url_parse():\n    message = \"Bad URL: my.redacted-domain.com:443\"\n    with pytest.raises(BadURL, match=message):\n        Request(\n            b\"my.redacted-domain.com:443\",\n            Mock(),\n            Mock(),\n            Mock(),\n            Mock(),\n            Mock(),\n            Mock(),\n        )\n\n\ndef test_request_scope_raises_exception_when_no_asgi():\n    app = Sanic(\"no_asgi\")\n\n    @app.get(\"/\")\n    async def get(request):\n        return request.scope\n\n    request, response = app.test_client.get(\"/\")\n    assert response.status == 500\n    with pytest.raises(NotImplementedError):\n        _ = request.scope\n\n\n@pytest.mark.asyncio\nasync def test_request_scope_is_not_none_when_running_in_asgi(app):\n    @app.get(\"/\")\n    async def get(request):\n        return response.empty()\n\n    request, _ = await app.asgi_client.get(\"/\")\n\n    assert request.scope is not None\n    assert request.scope[\"method\"].lower() == \"get\"\n    assert request.scope[\"path\"].lower() == \"/\"\n\n\ndef test_cannot_get_request_outside_of_cycle():\n    with pytest.raises(SanicException, match=\"No current request\"):\n        Request.get_current()\n\n\ndef test_get_current_request(app):\n    @app.get(\"/\")\n    async def get(request):\n        return response.json({\"same\": request is Request.get_current()})\n\n    _, resp = app.test_client.get(\"/\")\n    assert resp.json[\"same\"]\n\n\ndef test_request_stream_id(app):\n    @app.get(\"/\")\n    async def get(request):\n        try:\n            request.stream_id\n        except Exception as e:\n            return response.text(str(e))\n\n    _, resp = app.test_client.get(\"/\")\n    assert resp.text == \"Stream ID is only a property of a HTTP/3 request\"\n\n\n@pytest.mark.parametrize(\n    \"method,safe\",\n    (\n        (\"DELETE\", False),\n        (\"GET\", True),\n        (\"HEAD\", True),\n        (\"OPTIONS\", True),\n        (\"PATCH\", False),\n        (\"POST\", False),\n        (\"PUT\", False),\n    ),\n)\ndef test_request_safe(method, safe):\n    request = Request(b\"/\", {}, None, method, None, None)\n    assert request.is_safe is safe\n\n\n@pytest.mark.parametrize(\n    \"method,idempotent\",\n    (\n        (\"DELETE\", True),\n        (\"GET\", True),\n        (\"HEAD\", True),\n        (\"OPTIONS\", True),\n        (\"PATCH\", False),\n        (\"POST\", False),\n        (\"PUT\", True),\n    ),\n)\ndef test_request_idempotent(method, idempotent):\n    request = Request(b\"/\", {}, None, method, None, None)\n    assert request.is_idempotent is idempotent\n\n\n@pytest.mark.parametrize(\n    \"method,cacheable\",\n    (\n        (\"DELETE\", False),\n        (\"GET\", True),\n        (\"HEAD\", True),\n        (\"OPTIONS\", False),\n        (\"PATCH\", False),\n        (\"POST\", False),\n        (\"PUT\", False),\n    ),\n)\ndef test_request_cacheable(method, cacheable):\n    request = Request(b\"/\", {}, None, method, None, None)\n    assert request.is_cacheable is cacheable\n\n\ndef test_custom_ctx():\n    class CustomContext:\n        FOO = \"foo\"\n\n    class CustomRequest(Request[Sanic, CustomContext]):\n        @staticmethod\n        def make_context() -> CustomContext:\n            return CustomContext()\n\n    app = Sanic(\"Test\", request_class=CustomRequest)\n\n    @app.get(\"/\")\n    async def handler(request: CustomRequest):\n        return response.json(\n            [\n                isinstance(request, CustomRequest),\n                isinstance(request.ctx, CustomContext),\n                request.ctx.FOO,\n            ]\n        )\n\n    _, resp = app.test_client.get(\"/\")\n\n    assert resp.json == [True, True, \"foo\"]\n"
  },
  {
    "path": "tests/test_request_cancel.py",
    "content": "import asyncio\nimport contextlib\n\nimport pytest\n\nfrom sanic.response import text\n\n\n@pytest.mark.asyncio\nasync def test_request_cancel_when_connection_lost(app):\n    app.ctx.still_serving_cancelled_request = False\n\n    @app.get(\"/\")\n    async def handler(request):\n        await asyncio.sleep(1.0)\n        # at this point client is already disconnected\n        app.ctx.still_serving_cancelled_request = True\n        return text(\"OK\")\n\n    # schedule client call\n    loop = asyncio.get_event_loop()\n    task = loop.create_task(app.asgi_client.get(\"/\"))\n    loop.call_later(0.01, task)\n    await asyncio.sleep(0.5)\n\n    # cancelling request and closing connection after 0.5 sec\n    task.cancel()\n\n    with contextlib.suppress(asyncio.CancelledError):\n        await task\n\n    # Wait for server and check if it's still serving the cancelled request\n    await asyncio.sleep(1.0)\n\n    assert app.ctx.still_serving_cancelled_request is False\n\n\n@pytest.mark.asyncio\nasync def test_stream_request_cancel_when_conn_lost(app):\n    app.ctx.still_serving_cancelled_request = False\n\n    @app.post(\"/post/<id>\", stream=True)\n    async def post(request, id):\n        assert isinstance(request.stream, asyncio.Queue)\n\n        response = await request.respond()\n\n        await asyncio.sleep(1.0)\n        # at this point client is already disconnected\n        app.ctx.still_serving_cancelled_request = True\n        while True:\n            body = await request.stream.get()\n            if body is None:\n                break\n            await response.send(body.decode(\"utf-8\"))\n\n    # schedule client call\n    loop = asyncio.get_event_loop()\n    task = loop.create_task(app.asgi_client.post(\"/post/1\"))\n    loop.call_later(0.01, task)\n    await asyncio.sleep(0.5)\n\n    # cancelling request and closing connection after 0.5 sec\n    task.cancel()\n\n    with contextlib.suppress(asyncio.CancelledError):\n        await task\n\n    # Wait for server and check if it's still serving the cancelled request\n    await asyncio.sleep(1.0)\n\n    assert app.ctx.still_serving_cancelled_request is False\n"
  },
  {
    "path": "tests/test_request_data.py",
    "content": "import random\n\nfrom sanic.response import json\n\n\ntry:\n    from ujson import loads\nexcept ImportError:\n    from json import loads\n\n\ndef test_custom_context(app):\n    @app.middleware(\"request\")\n    def store(request):\n        request.ctx.user = \"sanic\"\n        request.ctx.session = None\n\n    @app.route(\"/\")\n    def handler(request):\n        # Accessing non-existent key should fail with AttributeError\n        try:\n            invalid = request.ctx.missing\n        except AttributeError as e:\n            invalid = str(e)\n        return json(\n            {\n                \"user\": request.ctx.user,\n                \"session\": request.ctx.session,\n                \"has_user\": hasattr(request.ctx, \"user\"),\n                \"has_session\": hasattr(request.ctx, \"session\"),\n                \"has_missing\": hasattr(request.ctx, \"missing\"),\n                \"invalid\": invalid,\n            }\n        )\n\n    @app.middleware(\"response\")\n    def modify(request, response):\n        # Using response-middleware to access request ctx\n        try:\n            user = request.ctx.user\n        except AttributeError as e:\n            user = str(e)\n        try:\n            invalid = request.ctx.missing\n        except AttributeError as e:\n            invalid = str(e)\n\n        j = loads(response.body)\n        j[\"response_mw_valid\"] = user\n        j[\"response_mw_invalid\"] = invalid\n        return json(j)\n\n    request, response = app.test_client.get(\"/\")\n    assert response.json == {\n        \"user\": \"sanic\",\n        \"session\": None,\n        \"has_user\": True,\n        \"has_session\": True,\n        \"has_missing\": False,\n        \"invalid\": \"'types.SimpleNamespace' object has no attribute 'missing'\",\n        \"response_mw_valid\": \"sanic\",\n        \"response_mw_invalid\": \"'types.SimpleNamespace' object has no\"\n        \" attribute 'missing'\",\n    }\n\n\ndef test_app_injection(app):\n    expected = random.choice(range(0, 100))\n\n    @app.listener(\"after_server_start\")\n    async def inject_data(app, loop):\n        app.ctx.injected = expected\n\n    @app.get(\"/\")\n    async def handler(request):\n        return json({\"injected\": request.app.ctx.injected})\n\n    request, response = app.test_client.get(\"/\")\n\n    response_json = loads(response.text)\n    assert response_json[\"injected\"] == expected\n"
  },
  {
    "path": "tests/test_request_stream.py",
    "content": "import asyncio\n\nimport pytest\n\nfrom sanic import Sanic\nfrom sanic.blueprints import Blueprint\nfrom sanic.response import json, text\nfrom sanic.views import HTTPMethodView\nfrom sanic.views import stream as stream_decorator\n\n\ndata = \"abc\" * 1_000_000\n\n\ndef test_request_stream_method_view(app):\n    class SimpleView(HTTPMethodView):\n        def get(self, request):\n            return text(\"OK\")\n\n        @stream_decorator\n        async def post(self, request):\n            result = b\"\"\n            while True:\n                body = await request.stream.read()\n                if body is None:\n                    break\n                result += body\n            return text(result.decode())\n\n    app.add_route(SimpleView.as_view(), \"/method_view\")\n\n    request, response = app.test_client.get(\"/method_view\")\n    assert response.status == 200\n    assert response.text == \"OK\"\n\n    request, response = app.test_client.post(\"/method_view\", data=data)\n    assert response.status == 200\n    assert response.text == data\n\n\n@pytest.mark.parametrize(\n    \"headers, expect_raise_exception\",\n    [\n        ({\"EXPECT\": \"100-continue\"}, False),\n        # The below test SHOULD work, and it does produce a 417\n        # However, httpx now intercepts this and raises an exception,\n        # so we will need a new method for testing this\n        # ({\"EXPECT\": \"100-continue-extra\"}, True),\n    ],\n)\ndef test_request_stream_100_continue(app, headers, expect_raise_exception):\n    class SimpleView(HTTPMethodView):\n        @stream_decorator\n        async def post(self, request):\n            result = \"\"\n            while True:\n                body = await request.stream.read()\n                if body is None:\n                    break\n                result += body.decode(\"utf-8\")\n            return text(result)\n\n    app.add_route(SimpleView.as_view(), \"/method_view\")\n\n    if not expect_raise_exception:\n        request, response = app.test_client.post(\n            \"/method_view\", data=data, headers=headers\n        )\n        assert response.status == 200\n        assert response.text == data\n    else:\n        request, response = app.test_client.post(\n            \"/method_view\", data=data, headers=headers\n        )\n        assert response.status == 417\n\n\ndef test_request_stream_app(app):\n    @app.get(\"/get\")\n    async def get(request):\n        return text(\"GET\")\n\n    @app.head(\"/head\")\n    async def head(request):\n        return text(\"HEAD\")\n\n    @app.delete(\"/delete\")\n    async def delete(request):\n        return text(\"DELETE\")\n\n    @app.options(\"/options\")\n    async def options(request):\n        return text(\"OPTIONS\")\n\n    @app.post(\"/_post/<id>\")\n    async def _post(request, id):\n        return text(\"_POST\")\n\n    @app.post(\"/post/<id>\", stream=True)\n    async def post(request, id):\n        result = \"\"\n        while True:\n            body = await request.stream.read()\n            if body is None:\n                break\n            result += body.decode(\"utf-8\")\n        return text(result)\n\n    @app.put(\"/_put\")\n    async def _put(request):\n        return text(\"_PUT\")\n\n    @app.put(\"/put\", stream=True)\n    async def put(request):\n        result = \"\"\n        while True:\n            body = await request.stream.read()\n            if body is None:\n                break\n            result += body.decode(\"utf-8\")\n        return text(result)\n\n    @app.patch(\"/_patch\")\n    async def _patch(request):\n        return text(\"_PATCH\")\n\n    @app.patch(\"/patch\", stream=True)\n    async def patch(request):\n        result = \"\"\n        while True:\n            body = await request.stream.read()\n            if body is None:\n                break\n            result += body.decode(\"utf-8\")\n        return text(result)\n\n    request, response = app.test_client.get(\"/get\")\n    assert response.status == 200\n    assert response.text == \"GET\"\n\n    request, response = app.test_client.head(\"/head\")\n    assert response.status == 200\n    assert response.text == \"\"\n\n    request, response = app.test_client.delete(\"/delete\")\n    assert response.status == 200\n    assert response.text == \"DELETE\"\n\n    request, response = app.test_client.options(\"/options\")\n    assert response.status == 200\n    assert response.text == \"OPTIONS\"\n\n    request, response = app.test_client.post(\"/_post/1\", data=data)\n    assert response.status == 200\n    assert response.text == \"_POST\"\n\n    request, response = app.test_client.post(\"/post/1\", data=data)\n    assert response.status == 200\n    assert response.text == data\n\n    request, response = app.test_client.put(\"/_put\", data=data)\n    assert response.status == 200\n    assert response.text == \"_PUT\"\n\n    request, response = app.test_client.put(\"/put\", data=data)\n    assert response.status == 200\n    assert response.text == data\n\n    request, response = app.test_client.patch(\"/_patch\", data=data)\n    assert response.status == 200\n    assert response.text == \"_PATCH\"\n\n    request, response = app.test_client.patch(\"/patch\", data=data)\n    assert response.status == 200\n    assert response.text == data\n\n\n@pytest.mark.asyncio\nasync def test_request_stream_app_asgi(app):\n    @app.get(\"/get\")\n    async def get(request):\n        return text(\"GET\")\n\n    @app.head(\"/head\")\n    async def head(request):\n        return text(\"HEAD\")\n\n    @app.delete(\"/delete\")\n    async def delete(request):\n        return text(\"DELETE\")\n\n    @app.options(\"/options\")\n    async def options(request):\n        return text(\"OPTIONS\")\n\n    @app.post(\"/_post/<id>\")\n    async def _post(request, id):\n        return text(\"_POST\")\n\n    @app.post(\"/post/<id>\", stream=True)\n    async def post(request, id):\n        result = \"\"\n        while True:\n            body = await request.stream.read()\n            if body is None:\n                break\n            result += body.decode(\"utf-8\")\n        return text(result)\n\n    @app.put(\"/_put\")\n    async def _put(request):\n        return text(\"_PUT\")\n\n    @app.put(\"/put\", stream=True)\n    async def put(request):\n        result = \"\"\n        while True:\n            body = await request.stream.read()\n            if body is None:\n                break\n            result += body.decode(\"utf-8\")\n        return text(result)\n\n    @app.patch(\"/_patch\")\n    async def _patch(request):\n        return text(\"_PATCH\")\n\n    @app.patch(\"/patch\", stream=True)\n    async def patch(request):\n        result = \"\"\n        while True:\n            body = await request.stream.read()\n            if body is None:\n                break\n            result += body.decode(\"utf-8\")\n        return text(result)\n\n    request, response = await app.asgi_client.get(\"/get\")\n    assert response.status == 200\n    assert response.text == \"GET\"\n\n    request, response = await app.asgi_client.head(\"/head\")\n    assert response.status == 200\n    assert response.text == \"\"\n\n    request, response = await app.asgi_client.delete(\"/delete\")\n    assert response.status == 200\n    assert response.text == \"DELETE\"\n\n    request, response = await app.asgi_client.options(\"/options\")\n    assert response.status == 200\n    assert response.text == \"OPTIONS\"\n\n    request, response = await app.asgi_client.post(\"/_post/1\", data=data)\n    assert response.status == 200\n    assert response.text == \"_POST\"\n\n    request, response = await app.asgi_client.post(\"/post/1\", data=data)\n    assert response.status == 200\n    assert response.text == data\n\n    request, response = await app.asgi_client.put(\"/_put\", data=data)\n    assert response.status == 200\n    assert response.text == \"_PUT\"\n\n    request, response = await app.asgi_client.put(\"/put\", data=data)\n    assert response.status == 200\n    assert response.text == data\n\n    request, response = await app.asgi_client.patch(\"/_patch\", data=data)\n    assert response.status == 200\n    assert response.text == \"_PATCH\"\n\n    request, response = await app.asgi_client.patch(\"/patch\", data=data)\n    assert response.status == 200\n    assert response.text == data\n\n\ndef test_request_stream_handle_exception(app):\n    \"\"\"for handling exceptions properly\"\"\"\n\n    @app.post(\"/post/<id>\", stream=True)\n    async def post(request, id):\n        result = b\"\"\n        while True:\n            body = await request.stream.read()\n            if body is None:\n                break\n            result += body\n        return text(result.decode())\n\n    # 404\n    request, response = app.test_client.post(\"/in_valid_post\", data=data)\n    assert response.status == 404\n    assert \"Requested URL /in_valid_post not found\" in response.text\n\n    # 405\n    request, response = app.test_client.get(\"/post/random_id\")\n    assert response.status == 405\n    assert \"Method GET not allowed for URL /post/random_id\" in response.text\n\n\ndef test_request_stream_blueprint(app):\n    bp = Blueprint(\"test_blueprint_request_stream_blueprint\")\n\n    @app.get(\"/get\")\n    async def get(request):\n        return text(\"GET\")\n\n    @bp.head(\"/head\")\n    async def head(request):\n        return text(\"HEAD\")\n\n    @bp.delete(\"/delete\")\n    async def delete(request):\n        return text(\"DELETE\")\n\n    @bp.options(\"/options\")\n    async def options(request):\n        return text(\"OPTIONS\")\n\n    @bp.post(\"/_post/<id>\")\n    async def _post(request, id):\n        return text(\"_POST\")\n\n    @bp.post(\"/post/<id>\", stream=True)\n    async def post(request, id):\n        result = \"\"\n        while True:\n            body = await request.stream.read()\n            if body is None:\n                break\n            result += body.decode(\"utf-8\")\n        return text(result)\n\n    @bp.put(\"/_put\")\n    async def _put(request):\n        return text(\"_PUT\")\n\n    @bp.put(\"/put\", stream=True)\n    async def put(request):\n        result = \"\"\n        while True:\n            body = await request.stream.read()\n            if body is None:\n                break\n            result += body.decode(\"utf-8\")\n        return text(result)\n\n    @bp.patch(\"/_patch\")\n    async def _patch(request):\n        return text(\"_PATCH\")\n\n    @bp.patch(\"/patch\", stream=True)\n    async def patch(request):\n        result = \"\"\n        while True:\n            body = await request.stream.read()\n            if body is None:\n                break\n            result += body.decode(\"utf-8\")\n        return text(result)\n\n    async def post_add_route(request):\n        result = \"\"\n        while True:\n            body = await request.stream.read()\n            if body is None:\n                break\n            result += body.decode(\"utf-8\")\n        return text(result)\n\n    bp.add_route(\n        post_add_route, \"/post/add_route\", methods=[\"POST\"], stream=True\n    )\n    app.blueprint(bp)\n\n    request, response = app.test_client.get(\"/get\")\n    assert response.status == 200\n    assert response.text == \"GET\"\n\n    request, response = app.test_client.head(\"/head\")\n    assert response.status == 200\n    assert response.text == \"\"\n\n    request, response = app.test_client.delete(\"/delete\")\n    assert response.status == 200\n    assert response.text == \"DELETE\"\n\n    request, response = app.test_client.options(\"/options\")\n    assert response.status == 200\n    assert response.text == \"OPTIONS\"\n\n    request, response = app.test_client.post(\"/_post/1\", data=data)\n    assert response.status == 200\n    assert response.text == \"_POST\"\n\n    request, response = app.test_client.post(\"/post/1\", data=data)\n    assert response.status == 200\n    assert response.text == data\n\n    request, response = app.test_client.put(\"/_put\", data=data)\n    assert response.status == 200\n    assert response.text == \"_PUT\"\n\n    request, response = app.test_client.put(\"/put\", data=data)\n    assert response.status == 200\n    assert response.text == data\n\n    request, response = app.test_client.patch(\"/_patch\", data=data)\n    assert response.status == 200\n    assert response.text == \"_PATCH\"\n\n    request, response = app.test_client.patch(\"/patch\", data=data)\n    assert response.status == 200\n    assert response.text == data\n\n    request, response = app.test_client.post(\"/post/add_route\", data=data)\n    assert response.status == 200\n    assert response.text == data\n\n\ndef test_request_stream(app):\n    \"\"\"test for complex application\"\"\"\n    bp = Blueprint(\"test_blueprint_request_stream\")\n\n    class SimpleView(HTTPMethodView):\n        def get(self, request):\n            return text(\"OK\")\n\n        @stream_decorator\n        async def post(self, request):\n            result = \"\"\n            while True:\n                body = await request.stream.read()\n                if body is None:\n                    break\n                result += body.decode(\"utf-8\")\n            return text(result)\n\n    @app.post(\"/stream\", stream=True)\n    async def handler(request):\n        result = \"\"\n        while True:\n            body = await request.stream.read()\n            if body is None:\n                break\n            result += body.decode(\"utf-8\")\n        return text(result)\n\n    @app.get(\"/get\")\n    async def get(request):\n        return text(\"OK\")\n\n    @bp.post(\"/bp_stream\", stream=True)\n    async def bp_stream(request):\n        result = \"\"\n        while True:\n            body = await request.stream.read()\n            if body is None:\n                break\n            result += body.decode(\"utf-8\")\n        return text(result)\n\n    @bp.get(\"/bp_get\")\n    async def bp_get(request):\n        return text(\"OK\")\n\n    def get_handler(request):\n        return text(\"OK\")\n\n    async def post_handler(request):\n        result = \"\"\n        while True:\n            body = await request.stream.read()\n            if body is None:\n                break\n            result += body.decode(\"utf-8\")\n        return text(result)\n\n    app.add_route(SimpleView.as_view(), \"/method_view\")\n\n    app.blueprint(bp)\n\n    request, response = app.test_client.get(\"/method_view\")\n    assert response.status == 200\n    assert response.text == \"OK\"\n\n    request, response = app.test_client.post(\"/method_view\", data=data)\n    assert response.status == 200\n    assert response.text == data\n\n    request, response = app.test_client.get(\"/get\")\n    assert response.status == 200\n    assert response.text == \"OK\"\n\n    request, response = app.test_client.post(\"/stream\", data=data)\n    assert response.status == 200\n    assert response.text == data\n\n    request, response = app.test_client.get(\"/bp_get\")\n    assert response.status == 200\n    assert response.text == \"OK\"\n\n    request, response = app.test_client.post(\"/bp_stream\", data=data)\n    assert response.status == 200\n    assert response.text == data\n\n\ndef test_streaming_new_api(app):\n    @app.post(\"/non-stream\")\n    async def handler1(request):\n        assert request.body == b\"x\"\n        await request.receive_body()  # This should do nothing\n        assert request.body == b\"x\"\n        return text(\"OK\")\n\n    @app.post(\"/1\", stream=True)\n    async def handler2(request):\n        assert request.stream\n        assert not request.body\n        await request.receive_body()\n        return text(request.body.decode().upper())\n\n    @app.post(\"/2\", stream=True)\n    async def handler(request):\n        ret = []\n        async for data in request.stream:\n            # We should have no b\"\" or None, just proper chunks\n            assert data\n            assert isinstance(data, bytes)\n            ret.append(data.decode(\"ASCII\"))\n        return json(ret)\n\n    request, response = app.test_client.post(\"/non-stream\", data=\"x\")\n    assert response.status == 200\n\n    request, response = app.test_client.post(\"/1\", data=\"TEST data\")\n    assert request.body == b\"TEST data\"\n    assert response.status == 200\n    assert response.text == \"TEST DATA\"\n\n    request, response = app.test_client.post(\"/2\", data=data)\n    assert response.status == 200\n    res = response.json\n    assert isinstance(res, list)\n    assert \"\".join(res) == data\n\n\ndef test_streaming_echo():\n    \"\"\"2-way streaming chat between server and client.\"\"\"\n    app = Sanic(name=\"Test\")\n\n    @app.post(\"/echo\", stream=True)\n    async def handler(request):\n        res = await request.respond(content_type=\"text/plain; charset=utf-8\")\n        # Send headers\n        await res.send(end_stream=False)\n        # Echo back data (case swapped)\n        async for data in request.stream:\n            await res.send(data.swapcase())\n        # Add EOF marker after successful operation\n        await res.send(b\"-\", end_stream=True)\n\n    @app.listener(\"after_server_start\")\n    async def client_task(app, loop):\n        try:\n            reader, writer = await asyncio.open_connection(\"localhost\", 8000)\n            await client(app, reader, writer)\n        finally:\n            writer.close()\n            app.stop()\n\n    async def client(app, reader, writer):\n        # httpx doesn't support 2-way streaming,so do it by hand.\n        host = b\"host: localhost:8000\\r\\n\"\n        writer.write(\n            b\"POST /echo HTTP/1.1\\r\\n\" + host + b\"content-length: 2\\r\\n\"\n            b\"content-type: text/plain; charset=utf-8\\r\\n\"\n            b\"\\r\\n\"\n        )\n        # Read response\n        res = b\"\"\n        while b\"\\r\\n\\r\\n\" not in res:\n            res += await reader.read(4096)\n        assert res.startswith(b\"HTTP/1.1 200 OK\\r\\n\")\n        assert res.endswith(b\"\\r\\n\\r\\n\")\n        buffer = b\"\"\n\n        async def read_chunk():\n            nonlocal buffer\n            while b\"\\r\\n\" not in buffer:\n                data = await reader.read(4096)\n                assert data\n                buffer += data\n            size, buffer = buffer.split(b\"\\r\\n\", 1)\n            size = int(size, 16)\n            if size == 0:\n                return None\n            while len(buffer) < size + 2:\n                data = await reader.read(4096)\n                assert data\n                buffer += data\n            assert buffer[size : size + 2] == b\"\\r\\n\"\n            ret, buffer = buffer[:size], buffer[size + 2 :]\n            return ret\n\n        # Chat with server\n        writer.write(b\"a\")\n        res = await read_chunk()\n        assert res == b\"A\"\n\n        writer.write(b\"b\")\n        res = await read_chunk()\n        assert res == b\"B\"\n\n        res = await read_chunk()\n        assert res == b\"-\"\n\n        res = await read_chunk()\n        assert res is None\n\n    app.run(access_log=False, single_process=True)\n"
  },
  {
    "path": "tests/test_requests.py",
    "content": "import base64\nimport logging\n\nfrom json import dumps as json_dumps\nfrom json import loads as json_loads\nfrom urllib.parse import urlparse\n\nimport pytest\n\nfrom sanic_testing.testing import (\n    ASGI_BASE_URL,\n    ASGI_PORT,\n    HOST,\n    PORT,\n    SanicTestClient,\n)\n\nfrom sanic import Blueprint, Sanic\nfrom sanic.constants import DEFAULT_HTTP_CONTENT_TYPE\nfrom sanic.exceptions import ServerError\nfrom sanic.request import RequestParameters\nfrom sanic.response import BaseHTTPResponse, html, json, text\n\n\ndef encode_basic_auth_credentials(username, password):\n    return base64.b64encode(f\"{username}:{password}\".encode()).decode(\"ascii\")\n\n\n# ------------------------------------------------------------ #\n#  GET\n# ------------------------------------------------------------ #\n\n\ndef test_sync(app):\n    @app.route(\"/\")\n    def handler(request):\n        return text(\"Hello\")\n\n    request, response = app.test_client.get(\"/\")\n\n    assert response.body == b\"Hello\"\n\n\n@pytest.mark.asyncio\nasync def test_sync_asgi(app):\n    @app.route(\"/\")\n    def handler(request):\n        return text(\"Hello\")\n\n    request, response = await app.asgi_client.get(\"/\")\n\n    assert response.body == b\"Hello\"\n\n\ndef test_ip(app):\n    @app.route(\"/\")\n    def handler(request):\n        return text(f\"{request.ip}\")\n\n    request, response = app.test_client.get(\"/\")\n\n    assert response.body == b\"127.0.0.1\"\n\n\n@pytest.mark.asyncio\nasync def test_url_asgi(app):\n    @app.route(\"/\")\n    def handler(request):\n        return text(f\"{request.url}\")\n\n    request, response = await app.asgi_client.get(\"/\")\n\n    if response.body.decode().endswith(\"/\") and not ASGI_BASE_URL.endswith(\n        \"/\"\n    ):\n        response.body[:-1] == ASGI_BASE_URL.encode()\n    else:\n        assert response.body == ASGI_BASE_URL.encode()\n\n\ndef test_text(app):\n    @app.route(\"/\")\n    async def handler(request):\n        return text(\"Hello\")\n\n    request, response = app.test_client.get(\"/\")\n\n    assert response.body == b\"Hello\"\n\n\ndef test_html(app):\n    class Foo:\n        def __html__(self):\n            return \"<h1>Foo</h1>\"\n\n        def _repr_html_(self):\n            return \"<h1>Foo object repr</h1>\"\n\n    class Bar:\n        def _repr_html_(self):\n            return \"<h1>Bar object repr</h1>\"\n\n    @app.route(\"/\")\n    async def handler(request):\n        return html(\"<h1>Hello</h1>\")\n\n    @app.route(\"/foo\")\n    async def handler_foo(request):\n        return html(Foo())\n\n    @app.route(\"/bar\")\n    async def handler_bar(request):\n        return html(Bar())\n\n    request, response = app.test_client.get(\"/\")\n    assert response.content_type == \"text/html; charset=utf-8\"\n    assert response.body == b\"<h1>Hello</h1>\"\n\n    request, response = app.test_client.get(\"/foo\")\n    assert response.body == b\"<h1>Foo</h1>\"\n\n    request, response = app.test_client.get(\"/bar\")\n    assert response.body == b\"<h1>Bar object repr</h1>\"\n\n\n@pytest.mark.asyncio\nasync def test_text_asgi(app):\n    @app.route(\"/\")\n    async def handler(request):\n        return text(\"Hello\")\n\n    request, response = await app.asgi_client.get(\"/\")\n\n    assert response.body == b\"Hello\"\n\n\ndef test_headers(app):\n    @app.route(\"/\")\n    async def handler(request):\n        headers = {\"spam\": \"great\"}\n        return text(\"Hello\", headers=headers)\n\n    request, response = app.test_client.get(\"/\")\n\n    assert response.headers.get(\"spam\") == \"great\"\n\n\n@pytest.mark.asyncio\nasync def test_headers_asgi(app):\n    @app.route(\"/\")\n    async def handler(request):\n        headers = {\"spam\": \"great\"}\n        return text(\"Hello\", headers=headers)\n\n    request, response = await app.asgi_client.get(\"/\")\n\n    assert response.headers.get(\"spam\") == \"great\"\n\n\ndef test_non_str_headers(app):\n    @app.route(\"/\")\n    async def handler(request):\n        headers = {\"answer\": 42}\n        return text(\"Hello\", headers=headers)\n\n    request, response = app.test_client.get(\"/\")\n\n    assert response.headers.get(\"answer\") == \"42\"\n\n\n@pytest.mark.asyncio\nasync def test_non_str_headers_asgi(app):\n    @app.route(\"/\")\n    async def handler(request):\n        headers = {\"answer\": 42}\n        return text(\"Hello\", headers=headers)\n\n    request, response = await app.asgi_client.get(\"/\")\n\n    assert response.headers.get(\"answer\") == \"42\"\n\n\ndef test_invalid_response(app):\n    @app.exception(ServerError)\n    def handler_exception(request, exception):\n        return text(\"Internal Server Error.\", 500)\n\n    @app.route(\"/\")\n    async def handler(request):\n        return \"This should fail\"\n\n    request, response = app.test_client.get(\"/\")\n    assert response.status == 500\n    assert response.body == b\"Internal Server Error.\"\n\n\n@pytest.mark.asyncio\nasync def test_invalid_response_asgi(app):\n    @app.exception(ServerError)\n    def handler_exception(request, exception):\n        return text(\"Internal Server Error.\", 500)\n\n    @app.route(\"/\")\n    async def handler(request):\n        return \"This should fail\"\n\n    request, response = await app.asgi_client.get(\"/\")\n    assert response.status == 500\n    assert response.body == b\"Internal Server Error.\"\n\n\ndef test_json(app):\n    @app.route(\"/\")\n    async def handler(request):\n        return json({\"test\": True})\n\n    request, response = app.test_client.get(\"/\")\n\n    results = json_loads(response.text)\n\n    assert results.get(\"test\") is True\n\n\n@pytest.mark.asyncio\nasync def test_json_asgi(app):\n    @app.route(\"/\")\n    async def handler(request):\n        return json({\"test\": True})\n\n    request, response = await app.asgi_client.get(\"/\")\n\n    results = json_loads(response.body)\n\n    assert results.get(\"test\") is True\n\n\ndef test_empty_json(app):\n    @app.route(\"/\")\n    async def handler(request):\n        assert request.json is None\n        return json(request.json)\n\n    request, response = app.test_client.get(\"/\")\n    assert response.status == 200\n    assert response.body == b\"null\"\n\n\n@pytest.mark.asyncio\nasync def test_empty_json_asgi(app):\n    @app.route(\"/\")\n    async def handler(request):\n        assert request.json is None\n        return json(request.json)\n\n    request, response = await app.asgi_client.get(\"/\")\n    assert response.status == 200\n    assert response.body == b\"null\"\n\n\ndef test_echo_json(app):\n    @app.post(\"/\")\n    async def handler(request):\n        return json(request.json)\n\n    data = {\"foo\": \"bar\"}\n    request, response = app.test_client.post(\"/\", json=data)\n\n    assert response.status == 200\n    assert response.json == data\n\n\n@pytest.mark.asyncio\nasync def test_echo_json_asgi(app):\n    @app.post(\"/\")\n    async def handler(request):\n        return json(request.json)\n\n    data = {\"foo\": \"bar\"}\n    request, response = await app.asgi_client.post(\"/\", json=data)\n\n    assert response.status == 200\n    assert response.json == data\n\n\ndef test_invalid_json(app):\n    @app.post(\"/\")\n    async def handler(request):\n        return json(request.json)\n\n    data = \"I am not json\"\n    request, response = app.test_client.post(\"/\", data=data)\n\n    assert response.status == 400\n\n\n@pytest.mark.asyncio\nasync def test_invalid_json_asgi(app):\n    @app.post(\"/\")\n    async def handler(request):\n        return json(request.json)\n\n    data = \"I am not json\"\n    request, response = await app.asgi_client.post(\"/\", data=data)\n\n    assert response.status == 400\n\n\ndef test_query_string(app):\n    @app.route(\"/\")\n    async def handler(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\n        \"/\", params=[(\"test1\", \"1\"), (\"test2\", \"false\"), (\"test2\", \"true\")]\n    )\n\n    assert request.args.get(\"test1\") == \"1\"\n    assert request.args.get(\"test2\") == \"false\"\n    assert request.args.getlist(\"test2\") == [\"false\", \"true\"]\n    assert request.args.getlist(\"test1\") == [\"1\"]\n    assert request.args.get(\"test3\", default=\"My value\") == \"My value\"\n\n\ndef test_popped_stays_popped(app):\n    @app.route(\"/\")\n    async def handler(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\"/\", params=[(\"test1\", \"1\")])\n\n    assert request.args.pop(\"test1\") == [\"1\"]\n    assert \"test1\" not in request.args\n\n\n@pytest.mark.asyncio\nasync def test_query_string_asgi(app):\n    @app.route(\"/\")\n    async def handler(request):\n        return text(\"OK\")\n\n    request, response = await app.asgi_client.get(\n        \"/\", params=[(\"test1\", \"1\"), (\"test2\", \"false\"), (\"test2\", \"true\")]\n    )\n\n    assert request.args.get(\"test1\") == \"1\"\n    assert request.args.get(\"test2\") == \"false\"\n    assert request.args.getlist(\"test2\") == [\"false\", \"true\"]\n    assert request.args.getlist(\"test1\") == [\"1\"]\n    assert request.args.get(\"test3\", default=\"My value\") == \"My value\"\n\n\ndef test_uri_template(app):\n    @app.route(\"/foo/<id:int>/bar/<name:[A-z]+>\")\n    async def handler(request, id, name):\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\"/foo/123/bar/baz\")\n    assert request.uri_template == \"/foo/<id:int>/bar/<name:[A-z]+>\"\n\n\n@pytest.mark.asyncio\nasync def test_uri_template_asgi(app):\n    @app.route(\"/foo/<id:int>/bar/<name:[A-z]+>\")\n    async def handler(request, id, name):\n        return text(\"OK\")\n\n    request, response = await app.asgi_client.get(\"/foo/123/bar/baz\")\n    assert request.uri_template == \"/foo/<id:int>/bar/<name:[A-z]+>\"\n\n\n@pytest.mark.parametrize(\n    (\"auth_type\", \"token\"),\n    [\n        # uuid4 generated token set in \"Authorization\" header\n        (None, \"a1d895e0-553a-421a-8e22-5ff8ecb48cbf\"),\n        # uuid4 generated token with API Token authorization\n        (\"Token\", \"a1d895e0-553a-421a-8e22-5ff8ecb48cbf\"),\n        # uuid4 generated token with Bearer Token authorization\n        (\"Bearer\", \"a1d895e0-553a-421a-8e22-5ff8ecb48cbf\"),\n        # no Authorization header\n        (None, None),\n    ],\n)\ndef test_token(app, auth_type, token):\n    @app.route(\"/\")\n    async def handler(request):\n        return text(\"OK\")\n\n    if token:\n        headers = {\n            \"content-type\": \"application/json\",\n            \"Authorization\": f\"{auth_type} {token}\"\n            if auth_type\n            else f\"{token}\",\n        }\n    else:\n        headers = {\"content-type\": \"application/json\"}\n\n    request, response = app.test_client.get(\"/\", headers=headers)\n    assert request.token == token\n\n\n@pytest.mark.parametrize(\n    (\"auth_type\", \"token\", \"username\", \"password\"),\n    [\n        # uuid4 generated token set in \"Authorization\" header\n        (None, \"a1d895e0-553a-421a-8e22-5ff8ecb48cbf\", None, None),\n        # uuid4 generated token with API Token authorization\n        (\"Token\", \"a1d895e0-553a-421a-8e22-5ff8ecb48cbf\", None, None),\n        # uuid4 generated token with Bearer Token authorization\n        (\"Bearer\", \"a1d895e0-553a-421a-8e22-5ff8ecb48cbf\", None, None),\n        # username and password with Basic Auth authorization\n        (\n            \"Basic\",\n            encode_basic_auth_credentials(\"some_username\", \"some_pass\"),\n            \"some_username\",\n            \"some_pass\",\n        ),\n        # no Authorization header\n        (None, None, None, None),\n    ],\n)\ndef test_credentials(app, capfd, auth_type, token, username, password):\n    @app.route(\"/\")\n    async def handler(request):\n        return text(\"OK\")\n\n    if token:\n        headers = {\n            \"content-type\": \"application/json\",\n            \"Authorization\": f\"{auth_type} {token}\"\n            if auth_type\n            else f\"{token}\",\n        }\n    else:\n        headers = {\"content-type\": \"application/json\"}\n\n    request, response = app.test_client.get(\"/\", headers=headers)\n\n    if auth_type == \"Basic\":\n        assert request.credentials.username == username\n        assert request.credentials.password == password\n    else:\n        _, err = capfd.readouterr()\n        with pytest.raises(AttributeError):\n            request.credentials.password\n            assert \"Password is available for Basic Auth only\" in err\n            request.credentials.username\n            assert \"Username is available for Basic Auth only\" in err\n\n    if token:\n        assert request.credentials.token == token\n        assert request.credentials.auth_type == auth_type\n    else:\n        assert request.credentials is None\n        assert not hasattr(request.credentials, \"token\")\n        assert not hasattr(request.credentials, \"auth_type\")\n        assert not hasattr(request.credentials, \"_username\")\n        assert not hasattr(request.credentials, \"_password\")\n\n\ndef test_content_type(app):\n    @app.route(\"/\")\n    async def handler(request):\n        return text(request.content_type)\n\n    request, response = app.test_client.get(\"/\")\n    assert request.content_type == DEFAULT_HTTP_CONTENT_TYPE\n    assert response.body.decode() == DEFAULT_HTTP_CONTENT_TYPE\n\n    headers = {\"content-type\": \"application/json\"}\n    request, response = app.test_client.get(\"/\", headers=headers)\n    assert request.content_type == \"application/json\"\n    assert response.body == b\"application/json\"\n\n\n@pytest.mark.asyncio\nasync def test_content_type_asgi(app):\n    @app.route(\"/\")\n    async def handler(request):\n        return text(request.content_type)\n\n    request, response = await app.asgi_client.get(\"/\")\n    assert request.content_type == DEFAULT_HTTP_CONTENT_TYPE\n    assert response.body.decode() == DEFAULT_HTTP_CONTENT_TYPE\n\n    headers = {\"content-type\": \"application/json\"}\n    request, response = await app.asgi_client.get(\"/\", headers=headers)\n    assert request.content_type == \"application/json\"\n    assert response.body == b\"application/json\"\n\n\ndef test_standard_forwarded(app):\n    @app.route(\"/\")\n    async def handler(request):\n        return json(request.forwarded)\n\n    # Without configured FORWARDED_SECRET, x-headers should be respected\n    app.config.PROXIES_COUNT = 1\n    app.config.REAL_IP_HEADER = \"x-real-ip\"\n    headers = {\n        \"Forwarded\": (\n            'for=1.1.1.1, for=injected;host=\"'\n            ', for=\"[::2]\";proto=https;host=me.tld;'\n            'path=\"/app/\";secret=mySecret'\n            \",for=broken;;secret=b0rked\"\n            \", for=127.0.0.3;scheme=http;port=1234\"\n        ),\n        \"X-Real-IP\": \"127.0.0.2\",\n        \"X-Forwarded-For\": \"127.0.1.1\",\n        \"X-Scheme\": \"ws\",\n        \"Host\": \"local.site\",\n    }\n    request, response = app.test_client.get(\"/\", headers=headers)\n    assert response.json == {\"for\": \"127.0.0.2\", \"proto\": \"ws\"}\n    assert request.remote_addr == \"127.0.0.2\"\n    assert request.client_ip == \"127.0.0.2\"\n    assert request.scheme == \"ws\"\n    assert request.server_name == \"local.site\"\n    assert request.server_port == 80\n\n    app.config.FORWARDED_SECRET = \"mySecret\"\n    request, response = app.test_client.get(\"/\", headers=headers)\n    assert response.json == {\n        \"for\": \"[::2]\",\n        \"proto\": \"https\",\n        \"host\": \"me.tld\",\n        \"path\": \"/app/\",\n        \"secret\": \"mySecret\",\n    }\n    assert request.remote_addr == \"[::2]\"\n    assert request.server_name == \"me.tld\"\n    assert request.scheme == \"https\"\n    assert request.server_port == 443\n\n    # Empty Forwarded header -> use X-headers\n    headers[\"Forwarded\"] = \"\"\n    request, response = app.test_client.get(\"/\", headers=headers)\n    assert response.json == {\"for\": \"127.0.0.2\", \"proto\": \"ws\"}\n\n    # Header present but not matching anything\n    request, response = app.test_client.get(\"/\", headers={\"Forwarded\": \".\"})\n    assert response.json == {}\n\n    # Forwarded header present but no matching secret -> use X-headers\n    headers = {\n        \"Forwarded\": \"for=1.1.1.1;secret=x, for=127.0.0.1\",\n        \"X-Real-IP\": \"127.0.0.2\",\n    }\n    request, response = app.test_client.get(\"/\", headers=headers)\n    assert response.json == {\"for\": \"127.0.0.2\"}\n    assert request.remote_addr == \"127.0.0.2\"\n\n    # Different formatting and hitting both ends of the header\n    headers = {\"Forwarded\": 'Secret=\"mySecret\";For=127.0.0.4;Port=1234'}\n    request, response = app.test_client.get(\"/\", headers=headers)\n    assert response.json == {\n        \"for\": \"127.0.0.4\",\n        \"port\": 1234,\n        \"secret\": \"mySecret\",\n    }\n\n    # Test escapes (modify this if you see anyone implementing quoted-pairs)\n    headers = {\"Forwarded\": 'for=test;quoted=\"\\\\,x=x;y=\\\\\";secret=mySecret'}\n    request, response = app.test_client.get(\"/\", headers=headers)\n    assert response.json == {\n        \"for\": \"test\",\n        \"quoted\": \"\\\\,x=x;y=\\\\\",\n        \"secret\": \"mySecret\",\n    }\n\n    # Secret insulated by malformed field #1\n    headers = {\"Forwarded\": \"for=test;secret=mySecret;b0rked;proto=wss;\"}\n    request, response = app.test_client.get(\"/\", headers=headers)\n    assert response.json == {\"for\": \"test\", \"secret\": \"mySecret\"}\n\n    # Secret insulated by malformed field #2\n    headers = {\"Forwarded\": \"for=test;b0rked;secret=mySecret;proto=wss\"}\n    request, response = app.test_client.get(\"/\", headers=headers)\n    assert response.json == {\"proto\": \"wss\", \"secret\": \"mySecret\"}\n\n    # Unexpected termination should not lose existing acceptable values\n    headers = {\"Forwarded\": \"b0rked;secret=mySecret;proto=wss\"}\n    request, response = app.test_client.get(\"/\", headers=headers)\n    assert response.json == {\"proto\": \"wss\", \"secret\": \"mySecret\"}\n\n    # Field normalization\n    headers = {\n        \"Forwarded\": 'PROTO=WSS;BY=\"CAFE::8000\";FOR=unknown;PORT=X;HOST=\"A:2\";'\n        'PATH=\"/With%20Spaces%22Quoted%22/sanicApp?key=val\";SECRET=mySecret'\n    }\n    request, response = app.test_client.get(\"/\", headers=headers)\n    assert response.json == {\n        \"proto\": \"wss\",\n        \"by\": \"[cafe::8000]\",\n        \"host\": \"a:2\",\n        \"path\": '/With Spaces\"Quoted\"/sanicApp?key=val',\n        \"secret\": \"mySecret\",\n    }\n\n    # Using \"by\" field as secret\n    app.config.FORWARDED_SECRET = \"_proxySecret\"\n    headers = {\"Forwarded\": \"for=1.2.3.4; by=_proxySecret\"}\n    request, response = app.test_client.get(\"/\", headers=headers)\n    assert response.json == {\"for\": \"1.2.3.4\", \"by\": \"_proxySecret\"}\n\n\n@pytest.mark.asyncio\nasync def test_standard_forwarded_asgi(app):\n    @app.route(\"/\")\n    async def handler(request):\n        return json(request.forwarded)\n\n    # Without configured FORWARDED_SECRET, x-headers should be respected\n    app.config.PROXIES_COUNT = 1\n    app.config.REAL_IP_HEADER = \"x-real-ip\"\n    headers = {\n        \"Forwarded\": (\n            'for=1.1.1.1, for=injected;host=\"'\n            ', for=\"[::2]\";proto=https;host=me.tld;'\n            'path=\"/app/\";secret=mySecret'\n            \",for=broken;;secret=b0rked\"\n            \", for=127.0.0.3;scheme=http;port=1234\"\n        ),\n        \"X-Real-IP\": \"127.0.0.2\",\n        \"X-Forwarded-For\": \"127.0.1.1\",\n        \"X-Scheme\": \"ws\",\n    }\n    request, response = await app.asgi_client.get(\"/\", headers=headers)\n\n    assert response.json == {\"for\": \"127.0.0.2\", \"proto\": \"ws\"}\n    assert request.remote_addr == \"127.0.0.2\"\n    assert request.scheme == \"ws\"\n    assert request.server_port == ASGI_PORT\n\n    app.config.FORWARDED_SECRET = \"mySecret\"\n    request, response = await app.asgi_client.get(\"/\", headers=headers)\n    assert response.json == {\n        \"for\": \"[::2]\",\n        \"proto\": \"https\",\n        \"host\": \"me.tld\",\n        \"path\": \"/app/\",\n        \"secret\": \"mySecret\",\n    }\n    assert request.remote_addr == \"[::2]\"\n    assert request.server_name == \"me.tld\"\n    assert request.scheme == \"https\"\n    assert request.server_port == 443\n\n    # Empty Forwarded header -> use X-headers\n    headers[\"Forwarded\"] = \"\"\n    request, response = await app.asgi_client.get(\"/\", headers=headers)\n    assert response.json == {\"for\": \"127.0.0.2\", \"proto\": \"ws\"}\n\n    # Header present but not matching anything\n    request, response = await app.asgi_client.get(\n        \"/\", headers={\"Forwarded\": \".\"}\n    )\n    assert response.json == {}\n\n    # Forwarded header present but no matching secret -> use X-headers\n    headers = {\n        \"Forwarded\": \"for=1.1.1.1;secret=x, for=127.0.0.1\",\n        \"X-Real-IP\": \"127.0.0.2\",\n    }\n    request, response = await app.asgi_client.get(\"/\", headers=headers)\n    assert response.json == {\"for\": \"127.0.0.2\"}\n    assert request.remote_addr == \"127.0.0.2\"\n\n    # Different formatting and hitting both ends of the header\n    headers = {\"Forwarded\": 'Secret=\"mySecret\";For=127.0.0.4;Port=1234'}\n    request, response = await app.asgi_client.get(\"/\", headers=headers)\n    assert response.json == {\n        \"for\": \"127.0.0.4\",\n        \"port\": 1234,\n        \"secret\": \"mySecret\",\n    }\n\n    # Test escapes (modify this if you see anyone implementing quoted-pairs)\n    headers = {\"Forwarded\": 'for=test;quoted=\"\\\\,x=x;y=\\\\\";secret=mySecret'}\n    request, response = await app.asgi_client.get(\"/\", headers=headers)\n    assert response.json == {\n        \"for\": \"test\",\n        \"quoted\": \"\\\\,x=x;y=\\\\\",\n        \"secret\": \"mySecret\",\n    }\n\n    # Secret insulated by malformed field #1\n    headers = {\"Forwarded\": \"for=test;secret=mySecret;b0rked;proto=wss;\"}\n    request, response = await app.asgi_client.get(\"/\", headers=headers)\n    assert response.json == {\"for\": \"test\", \"secret\": \"mySecret\"}\n\n    # Secret insulated by malformed field #2\n    headers = {\"Forwarded\": \"for=test;b0rked;secret=mySecret;proto=wss\"}\n    request, response = await app.asgi_client.get(\"/\", headers=headers)\n    assert response.json == {\"proto\": \"wss\", \"secret\": \"mySecret\"}\n\n    # Unexpected termination should not lose existing acceptable values\n    headers = {\"Forwarded\": \"b0rked;secret=mySecret;proto=wss\"}\n    request, response = await app.asgi_client.get(\"/\", headers=headers)\n    assert response.json == {\"proto\": \"wss\", \"secret\": \"mySecret\"}\n\n    # Field normalization\n    headers = {\n        \"Forwarded\": 'PROTO=WSS;BY=\"CAFE::8000\";FOR=unknown;PORT=X;HOST=\"A:2\";'\n        'PATH=\"/With%20Spaces%22Quoted%22/sanicApp?key=val\";SECRET=mySecret'\n    }\n    request, response = await app.asgi_client.get(\"/\", headers=headers)\n    assert response.json == {\n        \"proto\": \"wss\",\n        \"by\": \"[cafe::8000]\",\n        \"host\": \"a:2\",\n        \"path\": '/With Spaces\"Quoted\"/sanicApp?key=val',\n        \"secret\": \"mySecret\",\n    }\n\n    # Using \"by\" field as secret\n    app.config.FORWARDED_SECRET = \"_proxySecret\"\n    headers = {\"Forwarded\": \"for=1.2.3.4; by=_proxySecret\"}\n    request, response = await app.asgi_client.get(\"/\", headers=headers)\n    assert response.json == {\n        \"for\": \"1.2.3.4\",\n        \"by\": \"_proxySecret\",\n    }\n\n\ndef test_remote_addr_with_two_proxies(app):\n    app.config.PROXIES_COUNT = 2\n    app.config.REAL_IP_HEADER = \"x-real-ip\"\n\n    @app.route(\"/\")\n    async def handler(request):\n        return text(request.remote_addr)\n\n    headers = {\"X-Real-IP\": \"127.0.0.2\", \"X-Forwarded-For\": \"127.0.1.1\"}\n    request, response = app.test_client.get(\"/\", headers=headers)\n    assert request.remote_addr == \"127.0.0.2\"\n    assert response.body == b\"127.0.0.2\"\n\n    headers = {\"X-Forwarded-For\": \"127.0.1.1\"}\n    request, response = app.test_client.get(\"/\", headers=headers)\n    assert request.remote_addr == \"\"\n    assert request.client_ip == \"127.0.0.1\"\n    assert response.body == b\"\"\n\n    headers = {\"X-Forwarded-For\": \"127.0.0.1, 127.0.1.2\"}\n    request, response = app.test_client.get(\"/\", headers=headers)\n    assert request.remote_addr == \"127.0.0.1\"\n    assert response.body == b\"127.0.0.1\"\n\n    request, response = app.test_client.get(\"/\")\n    assert request.remote_addr == \"\"\n    assert response.body == b\"\"\n\n    headers = {\"X-Forwarded-For\": \"127.0.0.1, ,   ,,127.0.1.2\"}\n    request, response = app.test_client.get(\"/\", headers=headers)\n    assert request.remote_addr == \"127.0.0.1\"\n    assert response.body == b\"127.0.0.1\"\n\n    headers = {\n        \"X-Forwarded-For\": \", 127.0.2.2, ,  ,127.0.0.1, ,   ,,127.0.1.2\"\n    }\n    request, response = app.test_client.get(\"/\", headers=headers)\n    assert request.remote_addr == \"127.0.0.1\"\n    assert response.body == b\"127.0.0.1\"\n\n\n@pytest.mark.asyncio\nasync def test_remote_addr_with_two_proxies_asgi(app):\n    app.config.PROXIES_COUNT = 2\n    app.config.REAL_IP_HEADER = \"x-real-ip\"\n\n    @app.route(\"/\")\n    async def handler(request):\n        return text(request.remote_addr)\n\n    headers = {\"X-Real-IP\": \"127.0.0.2\", \"X-Forwarded-For\": \"127.0.1.1\"}\n    request, response = await app.asgi_client.get(\"/\", headers=headers)\n    assert request.remote_addr == \"127.0.0.2\"\n    assert response.body == b\"127.0.0.2\"\n\n    headers = {\"X-Forwarded-For\": \"127.0.1.1\"}\n    request, response = await app.asgi_client.get(\"/\", headers=headers)\n    assert request.remote_addr == \"\"\n    assert response.body == b\"\"\n\n    headers = {\"X-Forwarded-For\": \"127.0.0.1, 127.0.1.2\"}\n    request, response = await app.asgi_client.get(\"/\", headers=headers)\n    assert request.remote_addr == \"127.0.0.1\"\n    assert response.body == b\"127.0.0.1\"\n\n    request, response = await app.asgi_client.get(\"/\")\n    assert request.remote_addr == \"\"\n    assert response.body == b\"\"\n\n    headers = {\"X-Forwarded-For\": \"127.0.0.1, ,   ,,127.0.1.2\"}\n    request, response = await app.asgi_client.get(\"/\", headers=headers)\n    assert request.remote_addr == \"127.0.0.1\"\n    assert response.body == b\"127.0.0.1\"\n\n    headers = {\n        \"X-Forwarded-For\": \", 127.0.2.2, ,  ,127.0.0.1, ,   ,,127.0.1.2\"\n    }\n    request, response = await app.asgi_client.get(\"/\", headers=headers)\n    assert request.remote_addr == \"127.0.0.1\"\n    assert response.body == b\"127.0.0.1\"\n\n\ndef test_remote_addr_without_proxy(app):\n    app.config.PROXIES_COUNT = 0\n\n    @app.route(\"/\")\n    async def handler(request):\n        return text(request.remote_addr)\n\n    headers = {\"X-Real-IP\": \"127.0.0.2\", \"X-Forwarded-For\": \"127.0.1.1\"}\n    request, response = app.test_client.get(\"/\", headers=headers)\n    assert request.remote_addr == \"\"\n    assert response.body == b\"\"\n\n    headers = {\"X-Forwarded-For\": \"127.0.1.1\"}\n    request, response = app.test_client.get(\"/\", headers=headers)\n    assert request.remote_addr == \"\"\n    assert response.body == b\"\"\n\n    headers = {\"X-Forwarded-For\": \"127.0.0.1, 127.0.1.2\"}\n    request, response = app.test_client.get(\"/\", headers=headers)\n    assert request.remote_addr == \"\"\n    assert response.body == b\"\"\n\n\n@pytest.mark.asyncio\nasync def test_remote_addr_without_proxy_asgi(app):\n    app.config.PROXIES_COUNT = 0\n\n    @app.route(\"/\")\n    async def handler(request):\n        return text(request.remote_addr)\n\n    headers = {\"X-Real-IP\": \"127.0.0.2\", \"X-Forwarded-For\": \"127.0.1.1\"}\n    request, response = await app.asgi_client.get(\"/\", headers=headers)\n    assert request.remote_addr == \"\"\n    assert response.body == b\"\"\n\n    headers = {\"X-Forwarded-For\": \"127.0.1.1\"}\n    request, response = await app.asgi_client.get(\"/\", headers=headers)\n    assert request.remote_addr == \"\"\n    assert response.body == b\"\"\n\n    headers = {\"X-Forwarded-For\": \"127.0.0.1, 127.0.1.2\"}\n    request, response = await app.asgi_client.get(\"/\", headers=headers)\n    assert request.remote_addr == \"\"\n    assert response.body == b\"\"\n\n\ndef test_remote_addr_custom_headers(app):\n    app.config.PROXIES_COUNT = 1\n    app.config.REAL_IP_HEADER = \"Client-IP\"\n    app.config.FORWARDED_FOR_HEADER = \"Forwarded\"\n\n    @app.route(\"/\")\n    async def handler(request):\n        return text(request.remote_addr)\n\n    headers = {\"X-Real-IP\": \"127.0.0.2\", \"Forwarded\": \"127.0.1.1\"}\n    request, response = app.test_client.get(\"/\", headers=headers)\n    assert request.remote_addr == \"127.0.1.1\"\n    assert response.body == b\"127.0.1.1\"\n\n    headers = {\"X-Forwarded-For\": \"127.0.1.1\"}\n    request, response = app.test_client.get(\"/\", headers=headers)\n    assert request.remote_addr == \"\"\n    assert response.body == b\"\"\n\n    headers = {\"Client-IP\": \"127.0.0.2\", \"Forwarded\": \"127.0.1.1\"}\n    request, response = app.test_client.get(\"/\", headers=headers)\n    assert request.remote_addr == \"127.0.0.2\"\n    assert response.body == b\"127.0.0.2\"\n\n\n@pytest.mark.asyncio\nasync def test_remote_addr_custom_headers_asgi(app):\n    app.config.PROXIES_COUNT = 1\n    app.config.REAL_IP_HEADER = \"Client-IP\"\n    app.config.FORWARDED_FOR_HEADER = \"Forwarded\"\n\n    @app.route(\"/\")\n    async def handler(request):\n        return text(request.remote_addr)\n\n    headers = {\"X-Real-IP\": \"127.0.0.2\", \"Forwarded\": \"127.0.1.1\"}\n    request, response = await app.asgi_client.get(\"/\", headers=headers)\n    assert request.remote_addr == \"127.0.1.1\"\n    assert response.body == b\"127.0.1.1\"\n\n    headers = {\"X-Forwarded-For\": \"127.0.1.1\"}\n    request, response = await app.asgi_client.get(\"/\", headers=headers)\n    assert request.remote_addr == \"\"\n    assert response.body == b\"\"\n\n    headers = {\"Client-IP\": \"127.0.0.2\", \"Forwarded\": \"127.0.1.1\"}\n    request, response = await app.asgi_client.get(\"/\", headers=headers)\n    assert request.remote_addr == \"127.0.0.2\"\n    assert response.body == b\"127.0.0.2\"\n\n\ndef test_forwarded_scheme(app):\n    @app.route(\"/\")\n    async def handler(request):\n        return text(request.remote_addr)\n\n    app.config.PROXIES_COUNT = 1\n    request, response = app.test_client.get(\"/\")\n    assert request.scheme == \"http\"\n\n    request, response = app.test_client.get(\n        \"/\",\n        headers={\"X-Forwarded-For\": \"127.1.2.3\", \"X-Forwarded-Proto\": \"https\"},\n    )\n    assert request.scheme == \"https\"\n\n    request, response = app.test_client.get(\n        \"/\", headers={\"X-Forwarded-For\": \"127.1.2.3\", \"X-Scheme\": \"https\"}\n    )\n    assert request.scheme == \"https\"\n\n\ndef test_match_info(app):\n    @app.route(\"/api/v1/user/<user_id>/\")\n    async def handler(request, user_id):\n        return json(request.match_info)\n\n    request, response = app.test_client.get(\"/api/v1/user/sanic_user/\")\n\n    assert request.match_info == {\"user_id\": \"sanic_user\"}\n    assert json_loads(response.text) == {\"user_id\": \"sanic_user\"}\n\n\n@pytest.mark.asyncio\nasync def test_match_info_asgi(app):\n    @app.route(\"/api/v1/user/<user_id>/\")\n    async def handler(request, user_id):\n        return json(request.match_info)\n\n    request, response = await app.asgi_client.get(\"/api/v1/user/sanic_user/\")\n\n    assert request.match_info == {\"user_id\": \"sanic_user\"}\n    assert json_loads(response.body) == {\"user_id\": \"sanic_user\"}\n\n\n# ------------------------------------------------------------ #\n#  POST\n# ------------------------------------------------------------ #\n\n\ndef test_post_json(app):\n    @app.route(\"/\", methods=[\"POST\"])\n    async def handler(request):\n        return text(\"OK\")\n\n    payload = {\"test\": \"OK\"}\n    headers = {\"content-type\": \"application/json\"}\n\n    request, response = app.test_client.post(\n        \"/\", data=json_dumps(payload), headers=headers\n    )\n\n    assert request.json.get(\"test\") == \"OK\"\n    assert request.json.get(\"test\") == \"OK\"  # for request.parsed_json\n    assert response.body == b\"OK\"\n\n\n@pytest.mark.asyncio\nasync def test_post_json_asgi(app):\n    @app.route(\"/\", methods=[\"POST\"])\n    async def handler(request):\n        return text(\"OK\")\n\n    payload = {\"test\": \"OK\"}\n    headers = {\"content-type\": \"application/json\"}\n\n    request, response = await app.asgi_client.post(\n        \"/\", data=json_dumps(payload), headers=headers\n    )\n\n    assert request.json.get(\"test\") == \"OK\"\n    assert request.json.get(\"test\") == \"OK\"  # for request.parsed_json\n    assert response.body == b\"OK\"\n\n\ndef test_post_form_urlencoded(app):\n    @app.route(\"/\", methods=[\"POST\"])\n    async def handler(request):\n        return text(\"OK\")\n\n    payload = \"test=OK\"\n    headers = {\"content-type\": \"application/x-www-form-urlencoded\"}\n\n    request, response = app.test_client.post(\n        \"/\", data=payload, headers=headers\n    )\n\n    assert request.form.get(\"test\") == \"OK\"\n    assert request.form.get(\"test\") == \"OK\"  # For request.parsed_form\n\n\n@pytest.mark.asyncio\nasync def test_post_form_urlencoded_asgi(app):\n    @app.route(\"/\", methods=[\"POST\"])\n    async def handler(request):\n        return text(\"OK\")\n\n    payload = \"test=OK\"\n    headers = {\"content-type\": \"application/x-www-form-urlencoded\"}\n\n    request, response = await app.asgi_client.post(\n        \"/\", data=payload, headers=headers\n    )\n\n    assert request.form.get(\"test\") == \"OK\"\n    assert request.form.get(\"test\") == \"OK\"  # For request.parsed_form\n\n\ndef test_post_form_urlencoded_keep_blanks(app):\n    @app.route(\"/\", methods=[\"POST\"])\n    async def handler(request):\n        request.get_form(keep_blank_values=True)\n        return text(\"OK\")\n\n    payload = \"test=\"\n    headers = {\"content-type\": \"application/x-www-form-urlencoded\"}\n\n    request, response = app.test_client.post(\n        \"/\", data=payload, headers=headers\n    )\n\n    assert request.form.get(\"test\") == \"\"\n    assert request.form.get(\"test\") == \"\"  # For request.parsed_form\n\n\n@pytest.mark.asyncio\nasync def test_post_form_urlencoded_keep_blanks_asgi(app):\n    @app.route(\"/\", methods=[\"POST\"])\n    async def handler(request):\n        request.get_form(keep_blank_values=True)\n        return text(\"OK\")\n\n    payload = \"test=\"\n    headers = {\"content-type\": \"application/x-www-form-urlencoded\"}\n\n    request, response = await app.asgi_client.post(\n        \"/\", data=payload, headers=headers\n    )\n\n    assert request.form.get(\"test\") == \"\"\n    assert request.form.get(\"test\") == \"\"  # For request.parsed_form\n\n\ndef test_post_form_urlencoded_drop_blanks(app):\n    @app.route(\"/\", methods=[\"POST\"])\n    async def handler(request):\n        return text(\"OK\")\n\n    payload = \"test=\"\n    headers = {\"content-type\": \"application/x-www-form-urlencoded\"}\n\n    request, response = app.test_client.post(\n        \"/\", data=payload, headers=headers\n    )\n\n    assert \"test\" not in request.form.keys()\n\n\n@pytest.mark.asyncio\nasync def test_post_form_urlencoded_drop_blanks_asgi(app):\n    @app.route(\"/\", methods=[\"POST\"])\n    async def handler(request):\n        return text(\"OK\")\n\n    payload = \"test=\"\n    headers = {\"content-type\": \"application/x-www-form-urlencoded\"}\n\n    request, response = await app.asgi_client.post(\n        \"/\", data=payload, headers=headers\n    )\n\n    assert \"test\" not in request.form.keys()\n\n\n@pytest.mark.parametrize(\n    \"payload\",\n    [\n        \"------sanic\\r\\n\"\n        'Content-Disposition: form-data; name=\"test\"\\r\\n'\n        \"\\r\\n\"\n        \"OK\\r\\n\"\n        \"------sanic--\\r\\n\",\n        \"------sanic\\r\\n\"\n        'content-disposition: form-data; name=\"test\"\\r\\n'\n        \"\\r\\n\"\n        \"OK\\r\\n\"\n        \"------sanic--\\r\\n\",\n    ],\n)\ndef test_post_form_multipart_form_data(app, payload):\n    @app.route(\"/\", methods=[\"POST\"])\n    async def handler(request):\n        return text(\"OK\")\n\n    headers = {\"content-type\": \"multipart/form-data; boundary=----sanic\"}\n\n    request, response = app.test_client.post(data=payload, headers=headers)\n\n    assert request.form.get(\"test\") == \"OK\"\n\n\n@pytest.mark.parametrize(\n    \"payload\",\n    [\n        \"------sanic\\r\\n\"\n        'Content-Disposition: form-data; name=\"test\"\\r\\n'\n        \"\\r\\n\"\n        \"OK\\r\\n\"\n        \"------sanic--\\r\\n\",\n        \"------sanic\\r\\n\"\n        'content-disposition: form-data; name=\"test\"\\r\\n'\n        \"\\r\\n\"\n        \"OK\\r\\n\"\n        \"------sanic--\\r\\n\",\n    ],\n)\n@pytest.mark.asyncio\nasync def test_post_form_multipart_form_data_asgi(app, payload):\n    @app.route(\"/\", methods=[\"POST\"])\n    async def handler(request):\n        return text(\"OK\")\n\n    headers = {\"content-type\": \"multipart/form-data; boundary=----sanic\"}\n\n    request, response = await app.asgi_client.post(\n        \"/\", data=payload, headers=headers\n    )\n\n    assert request.form.get(\"test\") == \"OK\"\n\n\n@pytest.mark.parametrize(\n    \"path,query,expected_url\",\n    [\n        (\"/foo\", \"\", \"http://{}:{}/foo\"),\n        (\"/bar/baz\", \"\", \"http://{}:{}/bar/baz\"),\n        (\"/moo/boo\", \"arg1=val1\", \"http://{}:{}/moo/boo?arg1=val1\"),\n    ],\n)\ndef test_url_attributes_no_ssl(app, path, query, expected_url):\n    async def handler(request):\n        return text(\"OK\")\n\n    app.add_route(handler, path)\n\n    request, response = app.test_client.get(path + f\"?{query}\")\n    assert request.url == expected_url.format(HOST, request.server_port)\n\n    parsed = urlparse(request.url)\n\n    assert parsed.scheme == request.scheme\n    assert parsed.path == request.path\n    assert parsed.query == request.query_string\n    assert parsed.netloc == request.host\n\n\n@pytest.mark.parametrize(\n    \"path,query,expected_url\",\n    [\n        (\"/foo\", \"\", \"{}/foo\"),\n        (\"/bar/baz\", \"\", \"{}/bar/baz\"),\n        (\"/moo/boo\", \"arg1=val1\", \"{}/moo/boo?arg1=val1\"),\n    ],\n)\n@pytest.mark.asyncio\nasync def test_url_attributes_no_ssl_asgi(app, path, query, expected_url):\n    async def handler(request):\n        return text(\"OK\")\n\n    app.add_route(handler, path)\n\n    request, response = await app.asgi_client.get(path + f\"?{query}\")\n    assert request.url == expected_url.format(ASGI_BASE_URL)\n\n    parsed = urlparse(request.url)\n\n    assert parsed.scheme == request.scheme\n    assert parsed.path == request.path\n    assert parsed.query == request.query_string\n    assert parsed.netloc == request.host\n\n\ndef test_form_with_multiple_values(app):\n    @app.route(\"/\", methods=[\"POST\"])\n    async def handler(request):\n        return text(\"OK\")\n\n    payload = \"selectedItems=v1&selectedItems=v2&selectedItems=v3\"\n\n    headers = {\"content-type\": \"application/x-www-form-urlencoded\"}\n\n    request, response = app.test_client.post(\n        \"/\", data=payload, headers=headers\n    )\n\n    assert request.form.getlist(\"selectedItems\") == [\"v1\", \"v2\", \"v3\"]\n\n\n@pytest.mark.asyncio\nasync def test_form_with_multiple_values_asgi(app):\n    @app.route(\"/\", methods=[\"POST\"])\n    async def handler(request):\n        return text(\"OK\")\n\n    payload = \"selectedItems=v1&selectedItems=v2&selectedItems=v3\"\n\n    headers = {\"content-type\": \"application/x-www-form-urlencoded\"}\n\n    request, response = await app.asgi_client.post(\n        \"/\", data=payload, headers=headers\n    )\n\n    assert request.form.getlist(\"selectedItems\") == [\"v1\", \"v2\", \"v3\"]\n\n\ndef test_request_string_representation(app):\n    @app.route(\"/\", methods=[\"GET\"])\n    async def get(request):\n        return text(\"OK\")\n\n    request, _ = app.test_client.get(\"/\")\n    assert repr(request) == \"<Request: GET />\"\n\n\n@pytest.mark.asyncio\nasync def test_request_string_representation_asgi(app):\n    @app.route(\"/\", methods=[\"GET\"])\n    async def get(request):\n        return text(\"OK\")\n\n    request, _ = await app.asgi_client.get(\"/\")\n    assert repr(request) == \"<Request: GET />\"\n\n\n@pytest.mark.parametrize(\n    \"payload,filename\",\n    [\n        (\n            \"------sanic\\r\\n\"\n            'Content-Disposition: form-data; filename=\"filename\"'\n            '; name=\"test\"\\r\\n'\n            \"\\r\\n\"\n            \"OK\\r\\n\"\n            \"------sanic--\\r\\n\",\n            \"filename\",\n        ),\n        (\n            \"------sanic\\r\\n\"\n            'content-disposition: form-data; filename=\"filename\"'\n            '; name=\"test\"\\r\\n'\n            \"\\r\\n\"\n            'content-type: application/json; {\"field\": \"value\"}\\r\\n'\n            \"------sanic--\\r\\n\",\n            \"filename\",\n        ),\n        (\n            \"------sanic\\r\\n\"\n            'Content-Disposition: form-data; filename=\"\"; name=\"test\"\\r\\n'\n            \"\\r\\n\"\n            \"OK\\r\\n\"\n            \"------sanic--\\r\\n\",\n            \"\",\n        ),\n        (\n            \"------sanic\\r\\n\"\n            'content-disposition: form-data; filename=\"\"; name=\"test\"\\r\\n'\n            \"\\r\\n\"\n            'content-type: application/json; {\"field\": \"value\"}\\r\\n'\n            \"------sanic--\\r\\n\",\n            \"\",\n        ),\n        (\n            \"------sanic\\r\\n\"\n            \"Content-Disposition: form-data; filename*=\"\n            '\"utf-8\\'\\'filename_%C2%A0_test\"; name=\"test\"\\r\\n'\n            \"\\r\\n\"\n            \"OK\\r\\n\"\n            \"------sanic--\\r\\n\",\n            \"filename_\\u00a0_test\",\n        ),\n        (\n            \"------sanic\\r\\n\"\n            \"content-disposition: form-data; filename*=\"\n            '\"utf-8\\'\\'filename_%C2%A0_test\"; name=\"test\"\\r\\n'\n            \"\\r\\n\"\n            'content-type: application/json; {\"field\": \"value\"}\\r\\n'\n            \"------sanic--\\r\\n\",\n            \"filename_\\u00a0_test\",\n        ),\n        # Umlaut using NFC normalization (Windows, Linux, Android)\n        (\n            \"------sanic\\r\\n\"\n            \"content-disposition: form-data; filename*=\"\n            '\"utf-8\\'\\'filename_%C3%A4_test\"; name=\"test\"\\r\\n'\n            \"\\r\\n\"\n            \"OK\\r\\n\"\n            \"------sanic--\\r\\n\",\n            \"filename_\\u00e4_test\",\n        ),\n        # Umlaut using NFD normalization (MacOS client)\n        (\n            \"------sanic\\r\\n\"\n            \"content-disposition: form-data; filename*=\"\n            '\"utf-8\\'\\'filename_a%CC%88_test\"; name=\"test\"\\r\\n'\n            \"\\r\\n\"\n            \"OK\\r\\n\"\n            \"------sanic--\\r\\n\",\n            \"filename_\\u00e4_test\",  # Sanic should normalize to NFC\n        ),\n    ],\n)\ndef test_request_multipart_files(app, payload, filename):\n    @app.route(\"/\", methods=[\"POST\"])\n    async def post(request):\n        return text(\"OK\")\n\n    headers = {\"content-type\": \"multipart/form-data; boundary=----sanic\"}\n\n    request, _ = app.test_client.post(data=payload, headers=headers)\n    assert request.files.get(\"test\").name == filename\n\n\n@pytest.mark.parametrize(\n    \"payload,filename\",\n    [\n        (\n            \"------sanic\\r\\n\"\n            'Content-Disposition: form-data; filename=\"filename\";'\n            ' name=\"test\"\\r\\n'\n            \"\\r\\n\"\n            \"OK\\r\\n\"\n            \"------sanic--\\r\\n\",\n            \"filename\",\n        ),\n        (\n            \"------sanic\\r\\n\"\n            'content-disposition: form-data; filename=\"filename\";'\n            ' name=\"test\"\\r\\n'\n            \"\\r\\n\"\n            'content-type: application/json; {\"field\": \"value\"}\\r\\n'\n            \"------sanic--\\r\\n\",\n            \"filename\",\n        ),\n        (\n            \"------sanic\\r\\n\"\n            'Content-Disposition: form-data; filename=\"\"; name=\"test\"\\r\\n'\n            \"\\r\\n\"\n            \"OK\\r\\n\"\n            \"------sanic--\\r\\n\",\n            \"\",\n        ),\n        (\n            \"------sanic\\r\\n\"\n            'content-disposition: form-data; filename=\"\"; name=\"test\"\\r\\n'\n            \"\\r\\n\"\n            'content-type: application/json; {\"field\": \"value\"}\\r\\n'\n            \"------sanic--\\r\\n\",\n            \"\",\n        ),\n        (\n            \"------sanic\\r\\n\"\n            \"Content-Disposition: form-data; filename*=\"\n            '\"utf-8\\'\\'filename_%C2%A0_test\"; name=\"test\"\\r\\n'\n            \"\\r\\n\"\n            \"OK\\r\\n\"\n            \"------sanic--\\r\\n\",\n            \"filename_\\u00a0_test\",\n        ),\n        (\n            \"------sanic\\r\\n\"\n            \"content-disposition: form-data; filename*=\"\n            '\"utf-8\\'\\'filename_%C2%A0_test\"; name=\"test\"\\r\\n'\n            \"\\r\\n\"\n            'content-type: application/json; {\"field\": \"value\"}\\r\\n'\n            \"------sanic--\\r\\n\",\n            \"filename_\\u00a0_test\",\n        ),\n    ],\n)\n@pytest.mark.asyncio\nasync def test_request_multipart_files_asgi(app, payload, filename):\n    @app.route(\"/\", methods=[\"POST\"])\n    async def post(request):\n        return text(\"OK\")\n\n    headers = {\"content-type\": \"multipart/form-data; boundary=----sanic\"}\n\n    request, _ = await app.asgi_client.post(\"/\", data=payload, headers=headers)\n    assert request.files.get(\"test\").name == filename\n\n\ndef test_request_multipart_file_with_json_content_type(app):\n    @app.route(\"/\", methods=[\"POST\"])\n    async def post(request):\n        return text(\"OK\")\n\n    payload = (\n        \"------sanic\\r\\n\"\n        'Content-Disposition: form-data; name=\"file\";'\n        ' filename=\"test.json\"\\r\\n'\n        \"Content-Type: application/json\\r\\n\"\n        \"Content-Length: 0\"\n        \"\\r\\n\"\n        \"\\r\\n\"\n        \"------sanic--\"\n    )\n\n    headers = {\"content-type\": \"multipart/form-data; boundary=------sanic\"}\n\n    request, _ = app.test_client.post(data=payload, headers=headers)\n    assert request.files.get(\"file\").type == \"application/json\"\n\n\n@pytest.mark.asyncio\nasync def test_request_multipart_file_with_json_content_type_asgi(app):\n    @app.route(\"/\", methods=[\"POST\"])\n    async def post(request):\n        return text(\"OK\")\n\n    payload = (\n        \"------sanic\\r\\n\"\n        'Content-Disposition: form-data; name=\"file\";'\n        ' filename=\"test.json\"\\r\\n'\n        \"Content-Type: application/json\\r\\n\"\n        \"Content-Length: 0\"\n        \"\\r\\n\"\n        \"\\r\\n\"\n        \"------sanic--\"\n    )\n\n    headers = {\"content-type\": \"multipart/form-data; boundary=------sanic\"}\n\n    request, _ = await app.asgi_client.post(\"/\", data=payload, headers=headers)\n    assert request.files.get(\"file\").type == \"application/json\"\n\n\ndef test_request_multipart_file_without_field_name(app, caplog):\n    @app.route(\"/\", methods=[\"POST\"])\n    async def post(request):\n        return text(\"OK\")\n\n    payload = (\n        '------sanic\\r\\nContent-Disposition: form-data; filename=\"test.json\"'\n        \"\\r\\nContent-Type: application/json\\r\\n\\r\\n\\r\\n------sanic--\"\n    )\n\n    headers = {\"content-type\": \"multipart/form-data; boundary=------sanic\"}\n\n    request, _ = app.test_client.post(\n        data=payload, headers=headers, debug=True\n    )\n    with caplog.at_level(logging.DEBUG):\n        request.form\n\n    assert caplog.record_tuples[-1] == (\n        \"sanic.root\",\n        logging.DEBUG,\n        \"Form-data field does not have a 'name' parameter \"\n        \"in the Content-Disposition header\",\n    )\n\n\ndef test_request_multipart_file_duplicate_filed_name(app):\n    @app.route(\"/\", methods=[\"POST\"])\n    async def post(request):\n        return text(\"OK\")\n\n    payload = (\n        \"--e73ffaa8b1b2472b8ec848de833cb05b\\r\\n\"\n        'Content-Disposition: form-data; name=\"file\"\\r\\n'\n        \"Content-Type: application/octet-stream\\r\\n\"\n        \"Content-Length: 15\\r\\n\"\n        \"\\r\\n\"\n        '{\"test\":\"json\"}\\r\\n'\n        \"--e73ffaa8b1b2472b8ec848de833cb05b\\r\\n\"\n        'Content-Disposition: form-data; name=\"file\"\\r\\n'\n        \"Content-Type: application/octet-stream\\r\\n\"\n        \"Content-Length: 15\\r\\n\"\n        \"\\r\\n\"\n        '{\"test\":\"json2\"}\\r\\n'\n        \"--e73ffaa8b1b2472b8ec848de833cb05b--\\r\\n\"\n    )\n\n    headers = {\n        \"Content-Type\": \"multipart/form-data;\"\n        \" boundary=e73ffaa8b1b2472b8ec848de833cb05b\"\n    }\n\n    request, _ = app.test_client.post(\n        data=payload, headers=headers, debug=True\n    )\n    assert request.form.getlist(\"file\") == [\n        '{\"test\":\"json\"}',\n        '{\"test\":\"json2\"}',\n    ]\n\n\n@pytest.mark.asyncio\nasync def test_request_multipart_file_duplicate_filed_name_asgi(app):\n    @app.route(\"/\", methods=[\"POST\"])\n    async def post(request):\n        return text(\"OK\")\n\n    payload = (\n        \"--e73ffaa8b1b2472b8ec848de833cb05b\\r\\n\"\n        'Content-Disposition: form-data; name=\"file\"\\r\\n'\n        \"Content-Type: application/octet-stream\\r\\n\"\n        \"Content-Length: 15\\r\\n\"\n        \"\\r\\n\"\n        '{\"test\":\"json\"}\\r\\n'\n        \"--e73ffaa8b1b2472b8ec848de833cb05b\\r\\n\"\n        'Content-Disposition: form-data; name=\"file\"\\r\\n'\n        \"Content-Type: application/octet-stream\\r\\n\"\n        \"Content-Length: 15\\r\\n\"\n        \"\\r\\n\"\n        '{\"test\":\"json2\"}\\r\\n'\n        \"--e73ffaa8b1b2472b8ec848de833cb05b--\\r\\n\"\n    )\n\n    headers = {\n        \"Content-Type\": \"multipart/form-data;\"\n        \" boundary=e73ffaa8b1b2472b8ec848de833cb05b\"\n    }\n\n    request, _ = await app.asgi_client.post(\"/\", data=payload, headers=headers)\n    assert request.form.getlist(\"file\") == [\n        '{\"test\":\"json\"}',\n        '{\"test\":\"json2\"}',\n    ]\n\n\ndef test_request_multipart_with_multiple_files_and_type(app):\n    @app.route(\"/\", methods=[\"POST\"])\n    async def post(request):\n        return text(\"OK\")\n\n    payload = (\n        '------sanic\\r\\nContent-Disposition: form-data; name=\"file\";'\n        ' filename=\"test.json\"'\n        \"\\r\\nContent-Type: application/json\\r\\n\\r\\n\\r\\n\"\n        '------sanic\\r\\nContent-Disposition: form-data; name=\"file\";'\n        ' filename=\"some_file.pdf\"\\r\\n'\n        \"Content-Type: application/pdf\\r\\n\\r\\n\\r\\n------sanic--\"\n    )\n    headers = {\"content-type\": \"multipart/form-data; boundary=------sanic\"}\n\n    request, _ = app.test_client.post(data=payload, headers=headers)\n    assert len(request.files.getlist(\"file\")) == 2\n    assert request.files.getlist(\"file\")[0].type == \"application/json\"\n    assert request.files.getlist(\"file\")[1].type == \"application/pdf\"\n\n\n@pytest.mark.asyncio\nasync def test_request_multipart_with_multiple_files_and_type_asgi(app):\n    @app.route(\"/\", methods=[\"POST\"])\n    async def post(request):\n        return text(\"OK\")\n\n    payload = (\n        '------sanic\\r\\nContent-Disposition: form-data; name=\"file\";'\n        ' filename=\"test.json\"'\n        \"\\r\\nContent-Type: application/json\\r\\n\\r\\n\\r\\n\"\n        '------sanic\\r\\nContent-Disposition: form-data; name=\"file\";'\n        ' filename=\"some_file.pdf\"\\r\\n'\n        \"Content-Type: application/pdf\\r\\n\\r\\n\\r\\n------sanic--\"\n    )\n    headers = {\"content-type\": \"multipart/form-data; boundary=------sanic\"}\n\n    request, _ = await app.asgi_client.post(\"/\", data=payload, headers=headers)\n    assert len(request.files.getlist(\"file\")) == 2\n    assert request.files.getlist(\"file\")[0].type == \"application/json\"\n    assert request.files.getlist(\"file\")[1].type == \"application/pdf\"\n\n\ndef test_request_repr(app):\n    @app.get(\"/\")\n    def handler(request):\n        return text(\"pass\")\n\n    request, response = app.test_client.get(\"/\")\n    assert repr(request) == \"<Request: GET />\"\n\n    request.method = None\n    assert repr(request) == \"<Request: None />\"\n\n\n@pytest.mark.asyncio\nasync def test_request_repr_asgi(app):\n    @app.get(\"/\")\n    def handler(request):\n        return text(\"pass\")\n\n    request, response = await app.asgi_client.get(\"/\")\n    assert repr(request) == \"<Request: GET />\"\n\n    request.method = None\n    assert repr(request) == \"<Request: None />\"\n\n\ndef test_request_bool(app):\n    @app.get(\"/\")\n    def handler(request):\n        return text(\"pass\")\n\n    request, response = app.test_client.get(\"/\")\n    assert bool(request)\n\n\ndef test_request_parsing_form_failed(app, caplog):\n    @app.route(\"/\", methods=[\"POST\"])\n    async def handler(request):\n        return text(\"OK\")\n\n    payload = \"test=OK\"\n    headers = {\"content-type\": \"multipart/form-data\"}\n\n    request, response = app.test_client.post(\n        \"/\", data=payload, headers=headers\n    )\n\n    with caplog.at_level(logging.ERROR):\n        request.form\n\n    assert caplog.record_tuples[-1] == (\n        \"sanic.error\",\n        logging.ERROR,\n        \"Failed when parsing form\",\n    )\n\n\n@pytest.mark.asyncio\nasync def test_request_parsing_form_failed_asgi(app, caplog):\n    @app.route(\"/\", methods=[\"POST\"])\n    async def handler(request):\n        return text(\"OK\")\n\n    payload = \"test=OK\"\n    headers = {\"content-type\": \"multipart/form-data\"}\n\n    request, response = await app.asgi_client.post(\n        \"/\", data=payload, headers=headers\n    )\n\n    with caplog.at_level(logging.ERROR):\n        request.form\n\n    assert caplog.record_tuples[-1] == (\n        \"sanic.error\",\n        logging.ERROR,\n        \"Failed when parsing form\",\n    )\n\n\ndef test_request_args_no_query_string(app):\n    @app.get(\"/\")\n    def handler(request):\n        return text(\"pass\")\n\n    request, response = app.test_client.get(\"/\")\n\n    assert request.args == {}\n\n\n@pytest.mark.asyncio\nasync def test_request_args_no_query_string_await(app):\n    @app.get(\"/\")\n    def handler(request):\n        return text(\"pass\")\n\n    request, response = await app.asgi_client.get(\"/\")\n\n    assert request.args == {}\n\n\ndef test_request_query_args(app):\n    # test multiple params with the same key\n    params = [(\"test\", \"value1\"), (\"test\", \"value2\")]\n\n    @app.get(\"/\")\n    def handler(request):\n        return text(\"pass\")\n\n    request, response = app.test_client.get(\"/\", params=params)\n\n    assert request.query_args == params\n\n    # test cached value\n    assert (\n        request.parsed_not_grouped_args[(False, False, \"utf-8\", \"replace\")]\n        == request.query_args\n    )\n\n    # test params directly in the url\n    request, response = app.test_client.get(\"/?test=value1&test=value2\")\n\n    assert request.query_args == params\n\n    # test unique params\n    params = [(\"test1\", \"value1\"), (\"test2\", \"value2\")]\n\n    request, response = app.test_client.get(\"/\", params=params)\n\n    assert request.query_args == params\n\n    # test no params\n    request, response = app.test_client.get(\"/\")\n\n    assert not request.query_args\n\n\n@pytest.mark.asyncio\nasync def test_request_query_args_asgi(app):\n    # test multiple params with the same key\n    params = [(\"test\", \"value1\"), (\"test\", \"value2\")]\n\n    @app.get(\"/\")\n    def handler(request):\n        return text(\"pass\")\n\n    request, response = await app.asgi_client.get(\"/\", params=params)\n\n    assert request.query_args == params\n\n    # test cached value\n    assert (\n        request.parsed_not_grouped_args[(False, False, \"utf-8\", \"replace\")]\n        == request.query_args\n    )\n\n    # test params directly in the url\n    request, response = await app.asgi_client.get(\"/?test=value1&test=value2\")\n\n    assert request.query_args == params\n\n    # test unique params\n    params = [(\"test1\", \"value1\"), (\"test2\", \"value2\")]\n\n    request, response = await app.asgi_client.get(\"/\", params=params)\n\n    assert request.query_args == params\n\n    # test no params\n    request, response = await app.asgi_client.get(\"/\")\n\n    assert not request.query_args\n\n\ndef test_request_query_args_custom_parsing(app):\n    @app.get(\"/\")\n    def handler(request):\n        return text(\"pass\")\n\n    request, response = app.test_client.get(\n        \"/?test1=value1&test2=&test3=value3\"\n    )\n\n    assert request.get_query_args(keep_blank_values=True) == [\n        (\"test1\", \"value1\"),\n        (\"test2\", \"\"),\n        (\"test3\", \"value3\"),\n    ]\n    assert request.query_args == [(\"test1\", \"value1\"), (\"test3\", \"value3\")]\n    assert request.get_query_args(keep_blank_values=False) == [\n        (\"test1\", \"value1\"),\n        (\"test3\", \"value3\"),\n    ]\n\n    assert request.get_args(keep_blank_values=True) == RequestParameters(\n        {\"test1\": [\"value1\"], \"test2\": [\"\"], \"test3\": [\"value3\"]}\n    )\n\n    assert request.args == RequestParameters(\n        {\"test1\": [\"value1\"], \"test3\": [\"value3\"]}\n    )\n\n    assert request.get_args(keep_blank_values=False) == RequestParameters(\n        {\"test1\": [\"value1\"], \"test3\": [\"value3\"]}\n    )\n\n\n@pytest.mark.asyncio\nasync def test_request_query_args_custom_parsing_asgi(app):\n    @app.get(\"/\")\n    def handler(request):\n        return text(\"pass\")\n\n    request, response = await app.asgi_client.get(\n        \"/?test1=value1&test2=&test3=value3\"\n    )\n\n    assert request.get_query_args(keep_blank_values=True) == [\n        (\"test1\", \"value1\"),\n        (\"test2\", \"\"),\n        (\"test3\", \"value3\"),\n    ]\n    assert request.query_args == [(\"test1\", \"value1\"), (\"test3\", \"value3\")]\n    assert request.get_query_args(keep_blank_values=False) == [\n        (\"test1\", \"value1\"),\n        (\"test3\", \"value3\"),\n    ]\n\n    assert request.get_args(keep_blank_values=True) == RequestParameters(\n        {\"test1\": [\"value1\"], \"test2\": [\"\"], \"test3\": [\"value3\"]}\n    )\n\n    assert request.args == RequestParameters(\n        {\"test1\": [\"value1\"], \"test3\": [\"value3\"]}\n    )\n\n    assert request.get_args(keep_blank_values=False) == RequestParameters(\n        {\"test1\": [\"value1\"], \"test3\": [\"value3\"]}\n    )\n\n\ndef test_request_cookies(app):\n    cookies = {\"test\": \"OK\"}\n\n    @app.get(\"/\")\n    def handler(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\"/\", cookies=cookies)\n\n    assert len(request.cookies) == len(cookies)\n    assert request.cookies[\"test\"] == [cookies[\"test\"]]\n\n\n@pytest.mark.asyncio\nasync def test_request_cookies_asgi(app):\n    cookies = {\"test\": \"OK\"}\n\n    @app.get(\"/\")\n    def handler(request):\n        return text(\"OK\")\n\n    request, response = await app.asgi_client.get(\"/\", cookies=cookies)\n\n    assert len(request.cookies) == len(cookies)\n    assert request.cookies[\"test\"] == [cookies[\"test\"]]\n\n\ndef test_request_cookies_without_cookies(app):\n    @app.get(\"/\")\n    def handler(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\"/\")\n\n    assert request.cookies == {}\n\n\n@pytest.mark.asyncio\nasync def test_request_cookies_without_cookies_asgi(app):\n    @app.get(\"/\")\n    def handler(request):\n        return text(\"OK\")\n\n    request, response = await app.asgi_client.get(\"/\")\n\n    assert request.cookies == {}\n\n\ndef test_request_port(app):\n    @app.get(\"/\")\n    def handler(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\"/\")\n\n    port = request.port\n    assert isinstance(port, int)\n\n\n@pytest.mark.asyncio\nasync def test_request_port_asgi(app):\n    @app.get(\"/\")\n    def handler(request):\n        return text(\"OK\")\n\n    request, response = await app.asgi_client.get(\"/\")\n\n    port = request.port\n    assert isinstance(port, int)\n\n\ndef test_request_socket(app):\n    @app.get(\"/\")\n    def handler(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\"/\")\n\n    socket = request.socket\n    assert isinstance(socket, tuple)\n\n    ip = socket[0]\n    port = socket[1]\n\n    assert ip == request.ip\n    assert port == request.port\n\n\ndef test_request_server_name(app):\n    @app.get(\"/\")\n    def handler(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\"/\")\n    assert request.server_name == \"127.0.0.1\"\n\n\ndef test_request_server_name_in_host_header(app):\n    @app.get(\"/\")\n    def handler(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\n        \"/\", headers={\"Host\": \"my-server:5555\"}\n    )\n    assert request.server_name == \"my-server\"\n\n    request, response = app.test_client.get(\n        \"/\", headers={\"Host\": \"[2a00:1450:400f:80c::200e]:5555\"}\n    )\n    assert request.server_name == \"[2a00:1450:400f:80c::200e]\"\n\n    request, response = app.test_client.get(\n        \"/\", headers={\"Host\": \"mal_formed\"}\n    )\n    assert request.server_name == \"\"\n\n\ndef test_request_server_name_forwarded(app):\n    @app.get(\"/\")\n    def handler(request):\n        return text(\"OK\")\n\n    app.config.PROXIES_COUNT = 1\n    request, response = app.test_client.get(\n        \"/\",\n        headers={\n            \"Host\": \"my-server:5555\",\n            \"X-Forwarded-For\": \"127.1.2.3\",\n            \"X-Forwarded-Host\": \"your-server\",\n        },\n    )\n    assert request.server_name == \"your-server\"\n\n\ndef test_request_server_port(app):\n    @app.get(\"/\")\n    def handler(request):\n        return text(\"OK\")\n\n    test_client = SanicTestClient(app)\n    request, response = test_client.get(\"/\", headers={\"Host\": \"my-server\"})\n    assert request.server_port == 80\n\n\ndef test_request_server_port_in_host_header(app):\n    @app.get(\"/\")\n    def handler(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\n        \"/\", headers={\"Host\": \"my-server:5555\"}\n    )\n    assert request.server_port == 5555\n\n    request, response = app.test_client.get(\n        \"/\", headers={\"Host\": \"[2a00:1450:400f:80c::200e]:5555\"}\n    )\n    assert request.server_port == 5555\n\n    request, response = app.test_client.get(\n        \"/\", headers={\"Host\": \"mal_formed:5555\"}\n    )\n    if PORT is None:\n        assert request.server_port != 5555\n    else:\n        assert request.server_port == app.test_client.port\n\n\ndef test_request_server_port_forwarded(app):\n    @app.get(\"/\")\n    def handler(request):\n        return text(\"OK\")\n\n    app.config.PROXIES_COUNT = 1\n    request, response = app.test_client.get(\n        \"/\",\n        headers={\n            \"Host\": \"my-server:5555\",\n            \"X-Forwarded-For\": \"127.1.2.3\",\n            \"X-Forwarded-Port\": \"4444\",\n        },\n    )\n    assert request.server_port == 4444\n\n\ndef test_request_form_invalid_content_type(app):\n    @app.route(\"/\", methods=[\"POST\"])\n    async def post(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.post(\"/\", json={\"test\": \"OK\"})\n\n    assert request.form == {}\n\n\ndef test_server_name_and_url_for(app):\n    @app.get(\"/foo\")\n    def handler(request):\n        return text(\"ok\")\n\n    app.config.SERVER_NAME = \"my-server\"  # This means default port\n    assert app.url_for(\"handler\", _external=True) == \"http://my-server/foo\"\n    request, response = app.test_client.get(\"/foo\")\n    assert request.url_for(\"handler\") == \"http://my-server/foo\"\n\n    app.config.SERVER_NAME = \"https://my-server/path\"\n    request, response = app.test_client.get(\"/foo\")\n    url = \"https://my-server/path/foo\"\n    assert app.url_for(\"handler\", _external=True) == url\n    assert request.url_for(\"handler\") == url\n\n\ndef test_url_for_with_forwarded_request(app):\n    @app.get(\"/\")\n    def handler(request):\n        return text(\"OK\")\n\n    @app.get(\"/another_view/\")\n    def view_name(request):\n        return text(\"OK\")\n\n    app.config.SERVER_NAME = \"my-server\"\n    app.config.PROXIES_COUNT = 1\n    request, response = app.test_client.get(\n        \"/\",\n        headers={\n            \"X-Forwarded-For\": \"127.1.2.3\",\n            \"X-Forwarded-Proto\": \"https\",\n            \"X-Forwarded-Port\": \"6789\",\n        },\n    )\n    assert app.url_for(\"view_name\") == \"/another_view\"\n    assert (\n        app.url_for(\"view_name\", _external=True)\n        == \"http://my-server/another_view\"\n    )\n    assert (\n        request.url_for(\"view_name\") == \"https://my-server:6789/another_view\"\n    )\n\n    request, response = app.test_client.get(\n        \"/\",\n        headers={\n            \"X-Forwarded-For\": \"127.1.2.3\",\n            \"X-Forwarded-Proto\": \"https\",\n            \"X-Forwarded-Port\": \"443\",\n        },\n    )\n    assert request.url_for(\"view_name\") == \"https://my-server/another_view\"\n\n\n@pytest.mark.asyncio\nasync def test_request_form_invalid_content_type_asgi(app):\n    @app.route(\"/\", methods=[\"POST\"])\n    async def post(request):\n        return text(\"OK\")\n\n    request, response = await app.asgi_client.post(\"/\", json={\"test\": \"OK\"})\n\n    assert request.form == {}\n\n\ndef test_endpoint_basic():\n    app = Sanic(name=\"Test\")\n\n    @app.route(\"/\")\n    def my_unique_handler(request):\n        return text(\"Hello\")\n\n    request, response = app.test_client.get(\"/\")\n\n    assert request.endpoint == \"Test.my_unique_handler\"\n\n\n@pytest.mark.asyncio\nasync def test_endpoint_basic_asgi():\n    app = Sanic(name=\"Test\")\n\n    @app.route(\"/\")\n    def my_unique_handler(request):\n        return text(\"Hello\")\n\n    request, response = await app.asgi_client.get(\"/\")\n\n    assert request.endpoint == \"Test.my_unique_handler\"\n\n\ndef test_endpoint_named_app():\n    app = Sanic(\"named\")\n\n    @app.route(\"/\")\n    def my_unique_handler(request):\n        return text(\"Hello\")\n\n    request, response = app.test_client.get(\"/\")\n\n    assert request.endpoint == \"named.my_unique_handler\"\n\n\n@pytest.mark.asyncio\nasync def test_endpoint_named_app_asgi():\n    app = Sanic(\"named\")\n\n    @app.route(\"/\")\n    def my_unique_handler(request):\n        return text(\"Hello\")\n\n    request, response = await app.asgi_client.get(\"/\")\n\n    assert request.endpoint == \"named.my_unique_handler\"\n\n\ndef test_endpoint_blueprint():\n    bp = Blueprint(\"my_blueprint\", url_prefix=\"/bp\")\n\n    @bp.route(\"/\")\n    async def bp_root(request):\n        return text(\"Hello\")\n\n    app = Sanic(\"named\")\n    app.blueprint(bp)\n\n    request, response = app.test_client.get(\"/bp\")\n\n    assert request.endpoint == \"named.my_blueprint.bp_root\"\n\n\n@pytest.mark.asyncio\nasync def test_endpoint_blueprint_asgi():\n    bp = Blueprint(\"my_blueprint\", url_prefix=\"/bp\")\n\n    @bp.route(\"/\")\n    async def bp_root(request):\n        return text(\"Hello\")\n\n    app = Sanic(\"named\")\n    app.blueprint(bp)\n\n    request, response = await app.asgi_client.get(\"/bp\")\n\n    assert request.endpoint == \"named.my_blueprint.bp_root\"\n\n\ndef test_url_for_without_server_name(app):\n    @app.route(\"/sample\")\n    def sample(request):\n        return json({\"url\": request.url_for(\"url_for\")})\n\n    @app.route(\"/url-for\")\n    def url_for(request):\n        return text(\"url-for\")\n\n    request, response = app.test_client.get(\"/sample\")\n    assert (\n        response.json[\"url\"]\n        == f\"http://127.0.0.1:{request.server_port}/url-for\"\n    )\n\n\ndef test_safe_method_with_body_ignored(app):\n    @app.get(\"/\")\n    async def handler(request):\n        return text(\"OK\")\n\n    payload = {\"test\": \"OK\"}\n    headers = {\"content-type\": \"application/json\"}\n\n    request, response = app.test_client.request(\n        \"/\", http_method=\"get\", data=json_dumps(payload), headers=headers\n    )\n\n    assert request.body == b\"\"\n    assert request.json is None\n    assert response.body == b\"OK\"\n\n\ndef test_safe_method_with_body(app):\n    @app.get(\"/\", ignore_body=False)\n    async def handler(request):\n        return text(\"OK\")\n\n    payload = {\"test\": \"OK\"}\n    headers = {\"content-type\": \"application/json\"}\n    data = json_dumps(payload)\n    request, response = app.test_client.request(\n        \"/\", http_method=\"get\", data=data, headers=headers\n    )\n\n    assert request.body == data.encode(\"utf-8\")\n    assert request.json.get(\"test\") == \"OK\"\n    assert response.body == b\"OK\"\n\n\n@pytest.mark.asyncio\nasync def test_conflicting_body_methods_overload_error(app: Sanic):\n    @app.put(\"/\")\n    @app.put(\"/p/\")\n    @app.put(\"/p/<foo>\")\n    async def put(request, foo=None): ...\n\n    with pytest.raises(\n        ServerError,\n        match=(\n            r\"Duplicate route names detected:\"\n            r\" test_conflicting_body_methods_overload_error\\.put.*\"\n        ),\n    ):\n        await app._startup()\n\n\ndef test_conflicting_body_methods_overload(app: Sanic):\n    @app.put(\"/\", name=\"one\")\n    @app.put(\"/p/\", name=\"two\")\n    @app.put(\"/p/<foo>\", name=\"three\")\n    async def put(request, foo=None):\n        return json(\n            {\n                \"name\": request.route.name,\n                \"body\": str(request.body).replace(\" \", \"\"),\n                \"foo\": foo,\n            }\n        )\n\n    @app.delete(\"/p/<foo>\")\n    async def delete(request, foo):\n        return json(\n            {\n                \"name\": request.route.name,\n                \"body\": str(request.body).replace(\" \", \"\"),\n                \"foo\": foo,\n            }\n        )\n\n    dumps = BaseHTTPResponse._dumps\n    payload = {\"test\": \"OK\"}\n    data = str(dumps(payload).encode()).replace(\" \", \"\")\n\n    _, response = app.test_client.put(\"/\", json=payload)\n    assert response.status == 200\n    assert response.json == {\n        \"name\": \"test_conflicting_body_methods_overload.one\",\n        \"foo\": None,\n        \"body\": data,\n    }\n    _, response = app.test_client.put(\"/p\", json=payload)\n    assert response.status == 200\n    assert response.json == {\n        \"name\": \"test_conflicting_body_methods_overload.two\",\n        \"foo\": None,\n        \"body\": data,\n    }\n    _, response = app.test_client.put(\"/p/test\", json=payload)\n    assert response.status == 200\n    assert response.json == {\n        \"name\": \"test_conflicting_body_methods_overload.three\",\n        \"foo\": \"test\",\n        \"body\": data,\n    }\n    _, response = app.test_client.delete(\"/p/test\")\n    assert response.status == 200\n    assert response.json == {\n        \"name\": \"test_conflicting_body_methods_overload.delete\",\n        \"foo\": \"test\",\n        \"body\": str(b\"\"),\n    }\n\n\n@pytest.mark.asyncio\nasync def test_handler_overload_error(app: Sanic):\n    @app.get(\"/long/sub/route/param_a/<param_a:str>/param_b/<param_b:str>\")\n    @app.post(\"/long/sub/route/\")\n    def handler(request, **kwargs): ...\n\n    with pytest.raises(\n        ServerError,\n        match=(\n            r\"Duplicate route names detected:\"\n            r\" test_handler_overload_error\\.handler.*\"\n        ),\n    ):\n        await app._startup()\n\n\ndef test_handler_overload(app: Sanic):\n    @app.get(\n        \"/long/sub/route/param_a/<param_a:str>/param_b/<param_b:str>\",\n        name=\"one\",\n    )\n    @app.post(\"/long/sub/route/\", name=\"two\")\n    def handler(request, **kwargs):\n        return json(kwargs)\n\n    _, response = app.test_client.get(\n        \"/long/sub/route/param_a/foo/param_b/bar\"\n    )\n    assert response.status == 200\n    assert response.json == {\n        \"param_a\": \"foo\",\n        \"param_b\": \"bar\",\n    }\n    _, response = app.test_client.post(\"/long/sub/route\")\n    assert response.status == 200\n    assert response.json == {}\n"
  },
  {
    "path": "tests/test_response.py",
    "content": "import asyncio\nimport inspect\nimport os\nimport time\n\nfrom collections import namedtuple\nfrom datetime import datetime, timedelta\nfrom email.utils import formatdate, parsedate_to_datetime\nfrom logging import ERROR, LogRecord\nfrom mimetypes import guess_type\nfrom pathlib import Path\nfrom random import choice\nfrom typing import Callable, Union\nfrom urllib.parse import unquote\n\nimport pytest\n\nfrom aiofiles import os as async_os\nfrom pytest import LogCaptureFixture\n\nfrom sanic import Request, Sanic\nfrom sanic.compat import Header\nfrom sanic.constants import DEFAULT_HTTP_CONTENT_TYPE\nfrom sanic.cookies import CookieJar\nfrom sanic.response import (\n    HTTPResponse,\n    ResponseStream,\n    empty,\n    file,\n    file_stream,\n    json,\n    raw,\n    text,\n)\n\n\nJSON_DATA = {\"ok\": True}\n\n\n@pytest.mark.filterwarnings(\"ignore:Types other than str will be\")\ndef test_response_body_not_a_string(app):\n    \"\"\"Test when a response body sent from the application is not a string\"\"\"\n    random_num = choice(range(1000))\n\n    @app.route(\"/hello\")\n    async def hello_route(request: Request):\n        return text(random_num)\n\n    request, response = app.test_client.get(\"/hello\")\n    assert response.status == 500\n    assert b\"Internal Server Error\" in response.body\n\n\nasync def sample_streaming_fn(request, response=None):\n    if not response:\n        response = await request.respond(content_type=\"text/csv\")\n    await response.send(\"foo,\")\n    await asyncio.sleep(0.001)\n    await response.send(\"bar\")\n    await response.eof()\n\n\ndef test_method_not_allowed():\n    app = Sanic(\"app\")\n\n    @app.get(\"/\")\n    async def test_get(request: Request):\n        return response.json({\"hello\": \"world\"})\n\n    request, response = app.test_client.head(\"/\")\n    assert set(response.headers[\"Allow\"].split(\", \")) == {\n        \"GET\",\n    }\n\n    request, response = app.test_client.post(\"/\")\n    assert set(response.headers[\"Allow\"].split(\", \")) == {\n        \"GET\",\n    }\n\n    app.router.reset()\n\n    @app.post(\"/\")\n    async def test_post(request: Request):\n        return response.json({\"hello\": \"world\"})\n\n    request, response = app.test_client.head(\"/\")\n    assert response.status == 405\n    assert set(response.headers[\"Allow\"].split(\", \")) == {\n        \"GET\",\n        \"POST\",\n    }\n    assert response.headers[\"Content-Length\"] == \"0\"\n\n    request, response = app.test_client.patch(\"/\")\n    assert response.status == 405\n    assert set(response.headers[\"Allow\"].split(\", \")) == {\n        \"GET\",\n        \"POST\",\n    }\n    assert response.headers[\"Content-Length\"] == \"0\"\n\n\ndef test_response_header(app):\n    @app.get(\"/\")\n    async def test(request: Request):\n        return json({\"ok\": True}, headers={\"CONTENT-TYPE\": \"application/json\"})\n\n    request, response = app.test_client.get(\"/\")\n    for key, value in {\n        \"connection\": \"keep-alive\",\n        \"content-length\": \"11\",\n        \"content-type\": \"application/json\",\n    }.items():\n        assert response.headers[key] == value\n\n\ndef test_response_content_length(app):\n    @app.get(\"/response_with_space\")\n    async def response_with_space(request: Request):\n        return json(\n            {\"message\": \"Data\", \"details\": \"Some Details\"},\n            headers={\"CONTENT-TYPE\": \"application/json\"},\n        )\n\n    @app.get(\"/response_without_space\")\n    async def response_without_space(request: Request):\n        return json(\n            {\"message\": \"Data\", \"details\": \"Some Details\"},\n            headers={\"CONTENT-TYPE\": \"application/json\"},\n        )\n\n    _, response = app.test_client.get(\"/response_with_space\")\n    content_length_for_response_with_space = response.headers.get(\n        \"Content-Length\"\n    )\n\n    _, response = app.test_client.get(\"/response_without_space\")\n    content_length_for_response_without_space = response.headers.get(\n        \"Content-Length\"\n    )\n\n    assert (\n        content_length_for_response_with_space\n        == content_length_for_response_without_space\n    )\n\n    assert content_length_for_response_with_space == \"43\"\n\n\ndef test_response_content_length_with_different_data_types(app):\n    @app.get(\"/\")\n    async def get_data_with_different_types(request: Request):\n        # Indentation issues in the Response is intentional. Please do not fix\n        return json(\n            {\"bool\": True, \"none\": None, \"string\": \"string\", \"number\": -1},\n            headers={\"CONTENT-TYPE\": \"application/json\"},\n        )\n\n    _, response = app.test_client.get(\"/\")\n    assert response.headers.get(\"Content-Length\") == \"55\"\n\n\n@pytest.fixture\ndef json_app(app):\n    @app.route(\"/\")\n    async def test(request: Request):\n        return json(JSON_DATA)\n\n    @app.get(\"/no-content\")\n    async def no_content_handler(request: Request):\n        return json(JSON_DATA, status=204)\n\n    @app.get(\"/no-content/unmodified\")\n    async def no_content_unmodified_handler(request: Request):\n        return json(None, status=304)\n\n    @app.get(\"/unmodified\")\n    async def unmodified_handler(request: Request):\n        return json(JSON_DATA, status=304)\n\n    @app.get(\"/precondition\")\n    async def precondition_handler(request: Request):\n        return json(JSON_DATA, status=412)\n\n    @app.delete(\"/\")\n    async def delete_handler(request: Request):\n        return json(None, status=204)\n\n    return app\n\n\ndef test_json_response(json_app):\n    from sanic.response import json_dumps\n\n    request, response = json_app.test_client.get(\"/\")\n    assert response.status == 200\n    assert response.text == json_dumps(JSON_DATA)\n    assert response.json == JSON_DATA\n\n    request, response = json_app.test_client.get(\"/precondition\")\n    assert response.status == 412\n    assert response.json == JSON_DATA\n\n\ndef test_no_content(json_app):\n    request, response = json_app.test_client.get(\"/no-content\")\n    assert response.status == 204\n    assert response.text == \"\"\n    assert \"Content-Length\" not in response.headers\n\n    request, response = json_app.test_client.get(\"/no-content/unmodified\")\n    assert response.status == 304\n    assert response.text == \"\"\n    assert \"Content-Length\" not in response.headers\n    assert \"Content-Type\" not in response.headers\n\n    request, response = json_app.test_client.get(\"/unmodified\")\n    assert response.status == 304\n    assert response.text == \"\"\n    assert \"Content-Length\" not in response.headers\n    assert \"Content-Type\" not in response.headers\n\n    request, response = json_app.test_client.delete(\"/\")\n    assert response.status == 204\n    assert response.text == \"\"\n    assert \"Content-Length\" not in response.headers\n\n\n@pytest.fixture\ndef streaming_app(app):\n    @app.route(\"/\")\n    async def test(request: Request):\n        await sample_streaming_fn(request)\n\n    return app\n\n\n@pytest.fixture\ndef non_chunked_streaming_app(app):\n    @app.route(\"/\")\n    async def test(request: Request):\n        response = await request.respond(\n            headers={\"Content-Length\": \"7\"},\n            content_type=\"text/csv\",\n        )\n        await sample_streaming_fn(request, response)\n\n    return app\n\n\ndef test_chunked_streaming_adds_correct_headers(streaming_app):\n    request, response = streaming_app.test_client.get(\"/\")\n    assert response.headers[\"Transfer-Encoding\"] == \"chunked\"\n    assert response.headers[\"Content-Type\"] == \"text/csv\"\n    # Content-Length is not allowed by HTTP/1.1 specification\n    # when \"Transfer-Encoding: chunked\" is used\n    assert \"Content-Length\" not in response.headers\n\n\ndef test_chunked_streaming_returns_correct_content(streaming_app):\n    request, response = streaming_app.test_client.get(\"/\")\n    assert response.text == \"foo,bar\"\n\n\n@pytest.mark.asyncio\nasync def test_chunked_streaming_returns_correct_content_asgi(streaming_app):\n    request, response = await streaming_app.asgi_client.get(\"/\")\n    assert response.body == b\"foo,bar\"\n\n\ndef test_non_chunked_streaming_adds_correct_headers(non_chunked_streaming_app):\n    request, response = non_chunked_streaming_app.test_client.get(\"/\")\n\n    assert \"Transfer-Encoding\" not in response.headers\n    assert response.headers[\"Content-Type\"] == \"text/csv\"\n    assert response.headers[\"Content-Length\"] == \"7\"\n\n\n@pytest.mark.asyncio\nasync def test_non_chunked_streaming_adds_correct_headers_asgi(\n    non_chunked_streaming_app,\n):\n    request, response = await non_chunked_streaming_app.asgi_client.get(\"/\")\n    assert \"Transfer-Encoding\" not in response.headers\n    assert response.headers[\"Content-Type\"] == \"text/csv\"\n    assert response.headers[\"Content-Length\"] == \"7\"\n\n\ndef test_non_chunked_streaming_returns_correct_content(\n    non_chunked_streaming_app,\n):\n    request, response = non_chunked_streaming_app.test_client.get(\"/\")\n    assert response.text == \"foo,bar\"\n\n\ndef test_stream_response_with_cookies(app):\n    @app.route(\"/\")\n    async def test(request: Request):\n        headers = Header()\n        cookies = CookieJar(headers)\n        cookies.add_cookie(\"test\", \"modified\")\n        cookies.add_cookie(\"test\", \"pass\")\n        response = await request.respond(\n            content_type=\"text/csv\", headers=headers\n        )\n\n        await response.send(\"foo,\")\n        await asyncio.sleep(0.001)\n        await response.send(\"bar\")\n\n    request, response = app.test_client.get(\"/\")\n    assert response.cookies[\"test\"] == \"pass\"\n\n\ndef test_stream_response_without_cookies(app):\n    @app.route(\"/\")\n    async def test(request: Request):\n        await sample_streaming_fn(request)\n\n    request, response = app.test_client.get(\"/\")\n    assert response.cookies == {}\n\n\n@pytest.fixture\ndef static_file_directory():\n    \"\"\"The static directory to serve\"\"\"\n    current_file = inspect.getfile(inspect.currentframe())\n    current_directory = os.path.dirname(os.path.abspath(current_file))\n    static_directory = os.path.join(current_directory, \"static\")\n    return static_directory\n\n\ndef path_str_to_path_obj(static_file_directory: Union[Path, str]):\n    if isinstance(static_file_directory, str):\n        static_file_directory = Path(static_file_directory)\n    return static_file_directory\n\n\ndef get_file_content(static_file_directory: Union[Path, str], file_name: str):\n    \"\"\"The content of the static file to check\"\"\"\n    static_file_directory = path_str_to_path_obj(static_file_directory)\n    with open(static_file_directory / file_name, \"rb\") as file:\n        return file.read()\n\n\ndef get_file_last_modified_timestamp(\n    static_file_directory: Union[Path, str], file_name: str\n):\n    \"\"\"The content of the static file to check\"\"\"\n    static_file_directory = path_str_to_path_obj(static_file_directory)\n    return (static_file_directory / file_name).stat().st_mtime\n\n\n@pytest.mark.parametrize(\n    \"file_name\", [\"test.file\", \"decode me.txt\", \"python.png\"]\n)\n@pytest.mark.parametrize(\"status\", [200, 401])\ndef test_file_response(app: Sanic, file_name, static_file_directory, status):\n    @app.route(\"/files/<filename>\", methods=[\"GET\"])\n    def file_route(request, filename):\n        file_path = os.path.join(static_file_directory, filename)\n        file_path = os.path.abspath(unquote(file_path))\n        return file(\n            file_path,\n            status=status,\n            mime_type=guess_type(file_path)[0] or \"text/plain\",\n        )\n\n    request, response = app.test_client.get(f\"/files/{file_name}\")\n    assert response.status == status\n    assert response.body == get_file_content(static_file_directory, file_name)\n    assert \"Content-Disposition\" not in response.headers\n\n\n@pytest.mark.parametrize(\n    \"source,dest\",\n    [\n        (\"test.file\", \"my_file.txt\"),\n        (\"decode me.txt\", \"readme.md\"),\n        (\"python.png\", \"logo.png\"),\n    ],\n)\ndef test_file_response_custom_filename(\n    app: Sanic, source, dest, static_file_directory\n):\n    @app.route(\"/files/<filename>\", methods=[\"GET\"])\n    def file_route(request, filename):\n        file_path = os.path.join(static_file_directory, filename)\n        file_path = os.path.abspath(unquote(file_path))\n        return file(file_path, filename=dest)\n\n    request, response = app.test_client.get(f\"/files/{source}\")\n    assert response.status == 200\n    assert response.body == get_file_content(static_file_directory, source)\n    assert (\n        response.headers[\"Content-Disposition\"]\n        == f'attachment; filename=\"{dest}\"'\n    )\n\n\n@pytest.mark.parametrize(\"file_name\", [\"test.file\", \"decode me.txt\"])\ndef test_file_head_response(app: Sanic, file_name, static_file_directory):\n    @app.route(\"/files/<filename>\", methods=[\"GET\", \"HEAD\"])\n    async def file_route(request, filename):\n        file_path = os.path.join(static_file_directory, filename)\n        file_path = os.path.abspath(unquote(file_path))\n        stats = await async_os.stat(file_path)\n        headers = {}\n        headers[\"Accept-Ranges\"] = \"bytes\"\n        headers[\"Content-Length\"] = str(stats.st_size)\n        if request.method == \"HEAD\":\n            return HTTPResponse(\n                headers=headers,\n                content_type=guess_type(file_path)[0] or \"text/plain\",\n            )\n        else:\n            return file(\n                file_path,\n                headers=headers,\n                mime_type=guess_type(file_path)[0] or \"text/plain\",\n            )\n\n    request, response = app.test_client.head(f\"/files/{file_name}\")\n    assert response.status == 200\n    assert \"Accept-Ranges\" in response.headers\n    assert \"Content-Length\" in response.headers\n    assert int(response.headers[\"Content-Length\"]) == len(\n        get_file_content(static_file_directory, file_name)\n    )\n\n\n@pytest.mark.parametrize(\n    \"file_name\", [\"test.file\", \"decode me.txt\", \"python.png\"]\n)\ndef test_file_stream_response(app: Sanic, file_name, static_file_directory):\n    @app.route(\"/files/<filename>\", methods=[\"GET\"])\n    def file_route(request, filename):\n        file_path = os.path.join(static_file_directory, filename)\n        file_path = os.path.abspath(unquote(file_path))\n        return file_stream(\n            file_path,\n            chunk_size=32,\n            mime_type=guess_type(file_path)[0] or \"text/plain\",\n        )\n\n    request, response = app.test_client.get(f\"/files/{file_name}\")\n    assert response.status == 200\n    assert response.body == get_file_content(static_file_directory, file_name)\n    assert \"Content-Disposition\" not in response.headers\n\n\n@pytest.mark.parametrize(\n    \"source,dest\",\n    [\n        (\"test.file\", \"my_file.txt\"),\n        (\"decode me.txt\", \"readme.md\"),\n        (\"python.png\", \"logo.png\"),\n    ],\n)\ndef test_file_stream_response_custom_filename(\n    app: Sanic, source, dest, static_file_directory\n):\n    @app.route(\"/files/<filename>\", methods=[\"GET\"])\n    def file_route(request, filename):\n        file_path = os.path.join(static_file_directory, filename)\n        file_path = os.path.abspath(unquote(file_path))\n        return file_stream(file_path, chunk_size=32, filename=dest)\n\n    request, response = app.test_client.get(f\"/files/{source}\")\n    assert response.status == 200\n    assert response.body == get_file_content(static_file_directory, source)\n    assert (\n        response.headers[\"Content-Disposition\"]\n        == f'attachment; filename=\"{dest}\"'\n    )\n\n\n@pytest.mark.parametrize(\"file_name\", [\"test.file\", \"decode me.txt\"])\ndef test_file_stream_head_response(\n    app: Sanic, file_name, static_file_directory\n):\n    @app.route(\"/files/<filename>\", methods=[\"GET\", \"HEAD\"])\n    async def file_route(request, filename):\n        file_path = os.path.join(static_file_directory, filename)\n        file_path = os.path.abspath(unquote(file_path))\n        headers = {}\n        headers[\"Accept-Ranges\"] = \"bytes\"\n        if request.method == \"HEAD\":\n            # Return a normal HTTPResponse, not a\n            # StreamingHTTPResponse for a HEAD request\n            stats = await async_os.stat(file_path)\n            headers[\"Content-Length\"] = str(stats.st_size)\n            return HTTPResponse(\n                headers=headers,\n                content_type=guess_type(file_path)[0] or \"text/plain\",\n            )\n        else:\n            return file_stream(\n                file_path,\n                chunk_size=32,\n                headers=headers,\n                mime_type=guess_type(file_path)[0] or \"text/plain\",\n            )\n\n    request, response = app.test_client.head(f\"/files/{file_name}\")\n    assert response.status == 200\n    # A HEAD request should never be streamed/chunked.\n    if \"Transfer-Encoding\" in response.headers:\n        assert response.headers[\"Transfer-Encoding\"] != \"chunked\"\n    assert \"Accept-Ranges\" in response.headers\n    # A HEAD request should get the Content-Length too\n    assert \"Content-Length\" in response.headers\n    assert int(response.headers[\"Content-Length\"]) == len(\n        get_file_content(static_file_directory, file_name)\n    )\n\n\n@pytest.mark.parametrize(\n    \"file_name\", [\"test.file\", \"decode me.txt\", \"python.png\"]\n)\n@pytest.mark.parametrize(\n    \"size,start,end\", [(1024, 0, 1024), (4096, 1024, 8192)]\n)\ndef test_file_stream_response_range(\n    app: Sanic, file_name, static_file_directory, size, start, end\n):\n    Range = namedtuple(\"Range\", [\"size\", \"start\", \"end\", \"total\"])\n    total = len(get_file_content(static_file_directory, file_name))\n    range = Range(size=size, start=start, end=end, total=total)\n\n    @app.route(\"/files/<filename>\", methods=[\"GET\"])\n    def file_route(request, filename):\n        file_path = os.path.join(static_file_directory, filename)\n        file_path = os.path.abspath(unquote(file_path))\n        return file_stream(\n            file_path,\n            chunk_size=32,\n            mime_type=guess_type(file_path)[0] or \"text/plain\",\n            _range=range,\n        )\n\n    request, response = app.test_client.get(f\"/files/{file_name}\")\n    assert response.status == 206\n    assert \"Content-Range\" in response.headers\n    assert (\n        response.headers[\"Content-Range\"]\n        == f\"bytes {range.start}-{range.end}/{range.total}\"\n    )\n\n\ndef test_raw_response(app):\n    @app.get(\"/test\")\n    def handler(request: Request):\n        return raw(b\"raw_response\")\n\n    request, response = app.test_client.get(\"/test\")\n    assert response.content_type == DEFAULT_HTTP_CONTENT_TYPE\n    assert response.body == b\"raw_response\"\n\n\ndef test_empty_response(app):\n    @app.get(\"/test\")\n    def handler(request: Request):\n        return empty()\n\n    request, response = app.test_client.get(\"/test\")\n    assert response.content_type is None\n    assert response.body == b\"\"\n\n\ndef test_direct_response_stream(app: Sanic):\n    @app.route(\"/\")\n    async def test(request: Request):\n        response = await request.respond(content_type=\"text/csv\")\n        await response.send(\"foo,\")\n        await response.send(\"bar\")\n        await response.eof()\n\n    _, response = app.test_client.get(\"/\")\n    assert response.text == \"foo,bar\"\n    assert response.headers[\"Transfer-Encoding\"] == \"chunked\"\n    assert response.headers[\"Content-Type\"] == \"text/csv\"\n    assert \"Content-Length\" not in response.headers\n\n\n@pytest.mark.asyncio\nasync def test_direct_response_stream_asgi(app: Sanic):\n    @app.route(\"/\")\n    async def test(request: Request):\n        response = await request.respond(content_type=\"text/csv\")\n        await response.send(\"foo,\")\n        await response.send(\"bar\")\n        await response.eof()\n\n    _, response = await app.asgi_client.get(\"/\")\n    assert response.text == \"foo,bar\"\n    assert response.headers[\"Content-Type\"] == \"text/csv\"\n    assert \"Content-Length\" not in response.headers\n\n\ndef test_multiple_responses(\n    app: Sanic,\n    caplog: LogCaptureFixture,\n    message_in_records: Callable[[list[LogRecord], str], bool],\n):\n    @app.route(\"/1\")\n    async def handler1(request: Request):\n        response = await request.respond()\n        await response.send(\"foo\")\n        response = await request.respond()\n\n    @app.route(\"/2\")\n    async def handler2(request: Request):\n        response = await request.respond()\n        response = await request.respond()\n        await response.send(\"foo\")\n\n    @app.get(\"/3\")\n    async def handler3(request: Request):\n        response = await request.respond()\n        await response.send(\"foo,\")\n        response = await request.respond()\n        await response.send(\"bar\")\n\n    @app.get(\"/4\")\n    async def handler4(request: Request):\n        await request.respond(headers={\"one\": \"one\"})\n        return json({\"foo\": \"bar\"}, headers={\"one\": \"two\"})\n\n    @app.get(\"/5\")\n    async def handler5(request: Request):\n        response = await request.respond(headers={\"one\": \"one\"})\n        await response.send(\"foo\")\n        return json({\"foo\": \"bar\"}, headers={\"one\": \"two\"})\n\n    @app.get(\"/6\")\n    async def handler6(request: Request):\n        response = await request.respond(headers={\"one\": \"one\"})\n        await response.send(\"foo, \")\n        json_response = json({\"foo\": \"bar\"}, headers={\"one\": \"two\"})\n        await response.send(\"bar\")\n        return json_response\n\n    error_msg0 = \"Second respond call is not allowed.\"\n\n    error_msg1 = (\n        \"The error response will not be sent to the client for the following \"\n        'exception:\"Second respond call is not allowed.\". A previous '\n        \"response has at least partially been sent.\"\n    )\n\n    error_msg2 = (\n        \"The response object returned by the route handler \"\n        \"will not be sent to client. The request has already \"\n        \"been responded to.\"\n    )\n\n    with caplog.at_level(ERROR):\n        _, response = app.test_client.get(\"/1\")\n        assert response.status == 200\n        assert message_in_records(caplog.records, error_msg0)\n        assert message_in_records(caplog.records, error_msg1)\n\n    with caplog.at_level(ERROR):\n        _, response = app.test_client.get(\"/2\")\n        assert response.status == 500\n        assert \"500 — Internal Server Error\" in response.text\n\n    with caplog.at_level(ERROR):\n        _, response = app.test_client.get(\"/3\")\n        assert response.status == 200\n        assert \"foo,\" in response.text\n        assert message_in_records(caplog.records, error_msg0)\n        assert message_in_records(caplog.records, error_msg1)\n\n    with caplog.at_level(ERROR):\n        _, response = app.test_client.get(\"/4\")\n        assert response.status == 200\n        assert \"foo\" not in response.text\n        assert \"one\" in response.headers\n        assert response.headers[\"one\"] == \"one\"\n\n        assert message_in_records(caplog.records, error_msg2)\n\n    with caplog.at_level(ERROR):\n        _, response = app.test_client.get(\"/5\")\n        assert response.status == 200\n        assert \"foo\" in response.text\n        assert \"one\" in response.headers\n        assert response.headers[\"one\"] == \"one\"\n        assert message_in_records(caplog.records, error_msg2)\n\n    with caplog.at_level(ERROR):\n        _, response = app.test_client.get(\"/6\")\n        assert \"foo, bar\" in response.text\n        assert \"one\" in response.headers\n        assert response.headers[\"one\"] == \"one\"\n        assert message_in_records(caplog.records, error_msg2)\n\n\ndef test_send_response_after_eof_should_fail(\n    app: Sanic,\n    caplog: LogCaptureFixture,\n    message_in_records: Callable[[list[LogRecord], str], bool],\n):\n    @app.get(\"/\")\n    async def handler(request: Request):\n        response = await request.respond()\n        await response.send(\"foo, \")\n        await response.eof()\n        await response.send(\"bar\")\n\n    error_msg1 = (\n        \"The error response will not be sent to the client for the following \"\n        'exception:\"Response stream was ended, no more response '\n        'data is allowed to be sent.\". A previous '\n        \"response has at least partially been sent.\"\n    )\n\n    error_msg2 = (\n        \"Response stream was ended, no more response \"\n        \"data is allowed to be sent.\"\n    )\n\n    with caplog.at_level(ERROR):\n        _, response = app.test_client.get(\"/\")\n        assert \"foo, \" in response.text\n        assert message_in_records(caplog.records, error_msg1)\n        assert message_in_records(caplog.records, error_msg2)\n\n\n@pytest.mark.asyncio\nasync def test_send_response_after_eof_should_fail_asgi(\n    app: Sanic,\n    caplog: LogCaptureFixture,\n    message_in_records: Callable[[list[LogRecord], str], bool],\n):\n    @app.get(\"/\")\n    async def handler(request: Request):\n        response = await request.respond()\n        await response.send(\"foo, \")\n        await response.eof()\n        await response.send(\"bar\")\n\n    error_msg1 = (\n        \"The error response will not be sent to the client for the \"\n        'following exception:\"There is no request to respond to, '\n        \"either the response has already been sent or the request \"\n        'has not been received yet.\". A previous response has '\n        \"at least partially been sent.\"\n    )\n\n    error_msg2 = (\n        \"There is no request to respond to, either the response has \"\n        \"already been sent or the request has not been received yet.\"\n    )\n\n    with caplog.at_level(ERROR):\n        _, response = await app.asgi_client.get(\"/\")\n        assert \"foo, \" in response.text\n        assert message_in_records(caplog.records, error_msg1)\n        assert message_in_records(caplog.records, error_msg2)\n\n\n@pytest.mark.parametrize(\n    \"file_name\", [\"test.file\", \"decode me.txt\", \"python.png\"]\n)\ndef test_file_response_headers(\n    app: Sanic, file_name: str, static_file_directory: str\n):\n    test_last_modified = datetime.now()\n    test_max_age = 10\n    test_expires = test_last_modified.timestamp() + test_max_age\n\n    @app.route(\"/files/cached/<filename>\", methods=[\"GET\"])\n    def file_route_cache(request: Request, filename: str):\n        file_path = (\n            Path(static_file_directory) / unquote(filename)\n        ).absolute()\n        return file(\n            file_path, max_age=test_max_age, last_modified=test_last_modified\n        )\n\n    @app.route(\n        \"/files/cached_default_last_modified/<filename>\", methods=[\"GET\"]\n    )\n    def file_route_cache_default_last_modified(\n        request: Request, filename: str\n    ):\n        file_path = (\n            Path(static_file_directory) / unquote(filename)\n        ).absolute()\n        return file(file_path, max_age=test_max_age)\n\n    @app.route(\"/files/no_cache/<filename>\", methods=[\"GET\"])\n    def file_route_no_cache(request: Request, filename: str):\n        file_path = (\n            Path(static_file_directory) / unquote(filename)\n        ).absolute()\n        return file(file_path)\n\n    @app.route(\"/files/no_store/<filename>\", methods=[\"GET\"])\n    def file_route_no_store(request: Request, filename: str):\n        file_path = (\n            Path(static_file_directory) / unquote(filename)\n        ).absolute()\n        return file(file_path, no_store=True)\n\n    _, response = app.test_client.get(f\"/files/cached/{file_name}\")\n    assert response.body == get_file_content(static_file_directory, file_name)\n    headers = response.headers\n    assert (\n        \"cache-control\" in headers\n        and f\"max-age={test_max_age}\" in headers.get(\"cache-control\")\n        and \"public\" in headers.get(\"cache-control\")\n    )\n    assert (\n        \"expires\" in headers\n        and headers.get(\"expires\")[:-6]\n        == formatdate(test_expires, usegmt=True)[:-6]\n        # [:-6] to allow at most 1 min difference\n        # It's minimal for cases like:\n        # Thu, 26 May 2022 05:36:59 GMT\n        # AND\n        # Thu, 26 May 2022 05:37:00 GMT\n    )\n    assert response.status == 200\n    assert \"last-modified\" in headers and headers.get(\n        \"last-modified\"\n    ) == formatdate(test_last_modified.timestamp(), usegmt=True)\n\n    _, response = app.test_client.get(\n        f\"/files/cached_default_last_modified/{file_name}\"\n    )\n    file_last_modified = get_file_last_modified_timestamp(\n        static_file_directory, file_name\n    )\n    headers = response.headers\n    assert \"last-modified\" in headers and headers.get(\n        \"last-modified\"\n    ) == formatdate(file_last_modified, usegmt=True)\n    assert response.status == 200\n\n    _, response = app.test_client.get(f\"/files/no_cache/{file_name}\")\n    headers = response.headers\n    assert \"cache-control\" in headers and \"no-cache\" == headers.get(\n        \"cache-control\"\n    )\n    assert response.status == 200\n\n    _, response = app.test_client.get(f\"/files/no_store/{file_name}\")\n    headers = response.headers\n    assert \"cache-control\" in headers and \"no-store\" == headers.get(\n        \"cache-control\"\n    )\n    assert response.status == 200\n\n\ndef test_file_validate(app: Sanic, static_file_directory: str):\n    file_name = \"test_validate.txt\"\n    static_file_directory = Path(static_file_directory)\n    file_path = static_file_directory / file_name\n    file_path = file_path.absolute()\n    test_max_age = 10\n\n    with open(file_path, \"w+\") as f:\n        f.write(\"foo\\n\")\n\n    @app.route(\"/validate\", methods=[\"GET\"])\n    def file_route_cache(request: Request):\n        return file(\n            file_path,\n            request_headers=request.headers,\n            max_age=test_max_age,\n            validate_when_requested=True,\n        )\n\n    _, response = app.test_client.get(\"/validate\")\n    assert response.status == 200\n    assert response.body == b\"foo\\n\"\n    last_modified = response.headers[\"Last-Modified\"]\n\n    time.sleep(1)\n    with open(file_path, \"a\") as f:\n        f.write(\"bar\\n\")\n    _, response = app.test_client.get(\n        \"/validate\", headers={\"If-Modified-Since\": last_modified}\n    )\n\n    assert response.status == 200\n    assert response.body == b\"foo\\nbar\\n\"\n\n    last_modified = response.headers[\"Last-Modified\"]\n    _, response = app.test_client.get(\n        \"/validate\", headers={\"if-modified-since\": last_modified}\n    )\n    assert response.status == 304\n    assert response.body == b\"\"\n\n    file_path.unlink()\n\n\n@pytest.mark.parametrize(\n    \"file_name\", [\"test.file\", \"decode me.txt\", \"python.png\"]\n)\ndef test_file_validating_invalid_header(\n    app: Sanic, file_name: str, static_file_directory: str\n):\n    @app.route(\"/files/<filename>\", methods=[\"GET\"])\n    def file_route(request: Request, filename: str):\n        handler_file_path = (\n            Path(static_file_directory) / unquote(filename)\n        ).absolute()\n\n        return file(\n            handler_file_path,\n            request_headers=request.headers,\n            validate_when_requested=True,\n        )\n\n    _, response = app.test_client.get(f\"/files/{file_name}\")\n    assert response.status == 200\n    assert response.body == get_file_content(static_file_directory, file_name)\n\n    _, response = app.test_client.get(\n        f\"/files/{file_name}\", headers={\"if-modified-since\": \"invalid-value\"}\n    )\n    assert response.status == 200\n    assert response.body == get_file_content(static_file_directory, file_name)\n\n    _, response = app.test_client.get(\n        f\"/files/{file_name}\", headers={\"if-modified-since\": \"\"}\n    )\n    assert response.status == 200\n    assert response.body == get_file_content(static_file_directory, file_name)\n\n\n@pytest.mark.parametrize(\n    \"file_name\", [\"test.file\", \"decode me.txt\", \"python.png\"]\n)\ndef test_file_validating_304_response_file_route(\n    app: Sanic, file_name: str, static_file_directory: str\n):\n    @app.route(\"/files/<filename>\", methods=[\"GET\"])\n    def file_route(request: Request, filename: str):\n        handler_file_path = (\n            Path(static_file_directory) / unquote(filename)\n        ).absolute()\n\n        return file(\n            handler_file_path,\n            request_headers=request.headers,\n            validate_when_requested=True,\n        )\n\n    _, response = app.test_client.get(f\"/files/{file_name}\")\n    assert response.status == 200\n    assert response.body == get_file_content(static_file_directory, file_name)\n\n    _, response = app.test_client.get(\n        f\"/files/{file_name}\",\n        headers={\"if-modified-since\": response.headers[\"Last-Modified\"]},\n    )\n    assert response.status == 304\n    assert response.body == b\"\"\n\n\n@pytest.mark.parametrize(\n    \"file_name\", [\"test.file\", \"decode me.txt\", \"python.png\"]\n)\ndef test_file_validating_304_response(\n    app: Sanic, file_name: str, static_file_directory: str\n):\n    app.static(\"static\", Path(static_file_directory) / file_name)\n\n    _, response = app.test_client.get(\"/static\")\n    assert response.status == 200\n    assert response.body == get_file_content(static_file_directory, file_name)\n    last_modified = parsedate_to_datetime(response.headers[\"Last-Modified\"])\n    last_modified += timedelta(seconds=1)\n    _, response = app.test_client.get(\n        \"/static\",\n        headers={\n            \"if-modified-since\": formatdate(\n                last_modified.timestamp(), usegmt=True\n            )\n        },\n    )\n    assert response.status == 304\n    assert response.body == b\"\"\n\n\ndef test_stream_response_with_default_headers(app: Sanic):\n    async def sample_streaming_fn(response_):\n        await response_.write(\"foo\")\n\n    @app.route(\"/\")\n    async def test(request: Request):\n        return ResponseStream(sample_streaming_fn, content_type=\"text/csv\")\n\n    _, response = app.test_client.get(\"/\")\n    assert response.text == \"foo\"\n    assert response.headers[\"Transfer-Encoding\"] == \"chunked\"\n    assert response.headers[\"Content-Type\"] == \"text/csv\"\n"
  },
  {
    "path": "tests/test_response_file.py",
    "content": "from datetime import datetime, timezone\nfrom logging import INFO\n\nimport pytest\n\nfrom sanic.compat import Header\nfrom sanic.response.convenience import guess_content_type, validate_file\n\n\n@pytest.mark.parametrize(\n    \"ifmod,lastmod,expected\",\n    (\n        (\"Sat, 01 Apr 2023 00:00:00 GMT\", 1672524000, None),\n        (\n            \"Sat, 01 Apr 2023 00:00:00\",\n            1672524000,\n            \"converting if_modified_since\",\n        ),\n        (\n            \"Sat, 01 Apr 2023 00:00:00 GMT\",\n            datetime(2023, 1, 1, 0, 0, 0),\n            \"converting last_modified\",\n        ),\n        (\n            \"Sat, 01 Apr 2023 00:00:00\",\n            datetime(2023, 1, 1, 0, 0, 0),\n            None,\n        ),\n        (\n            \"Sat, 01 Apr 2023 00:00:00 GMT\",\n            datetime(2023, 1, 1, 0, 0, 0).replace(tzinfo=timezone.utc),\n            None,\n        ),\n        (\n            \"Sat, 01 Apr 2023 00:00:00\",\n            datetime(2023, 1, 1, 0, 0, 0).replace(tzinfo=timezone.utc),\n            \"converting if_modified_since\",\n        ),\n    ),\n)\n@pytest.mark.asyncio\nasync def test_file_timestamp_validation(\n    lastmod, ifmod, expected, caplog: pytest.LogCaptureFixture\n):\n    headers = Header([[\"If-Modified-Since\", ifmod]])\n\n    with caplog.at_level(INFO):\n        response = await validate_file(headers, lastmod)\n    assert response.status == 304\n    records = caplog.records\n    if not expected:\n        assert len(records) == 0\n    else:\n        record = records[0]\n        assert expected in record.message\n\n\n@pytest.mark.parametrize(\n    \"file_path,expected\",\n    (\n        (\"test.html\", \"text/html; charset=utf-8\"),\n        (\"test.txt\", \"text/plain; charset=utf-8\"),\n        (\"test.css\", \"text/css; charset=utf-8\"),\n        (\"test.js\", \"text/javascript; charset=utf-8\"),\n        (\"test.csv\", \"text/csv; charset=utf-8\"),\n        (\"test.xml\", \"application/xml\"),\n        # Fallback for unknown types\n        (\"test.file\", \"application/octet-stream\"),\n    ),\n)\ndef test_guess_content_type(file_path, expected):\n    \"\"\"Test that guess_content_type correctly adds charset for text types.\"\"\"\n    result = guess_content_type(file_path)\n    assert result == expected\n\n\ndef test_guess_content_type_with_custom_fallback():\n    \"\"\"Test that guess_content_type uses custom fallback for unknown types.\"\"\"\n    result = guess_content_type(\"no_extension\", fallback=\"custom/type\")\n    assert result == \"custom/type\"\n\n\ndef test_guess_content_type_with_pathlib():\n    \"\"\"Test that guess_content_type works with pathlib Path objects.\"\"\"\n    from pathlib import Path\n\n    result = guess_content_type(Path(\"test.html\"))\n    assert result == \"text/html; charset=utf-8\"\n"
  },
  {
    "path": "tests/test_response_json.py",
    "content": "import json\n\nfrom functools import partial\nfrom unittest.mock import Mock\n\nimport pytest\n\nfrom sanic import Request, Sanic\nfrom sanic.exceptions import SanicException\nfrom sanic.response import json as json_response\nfrom sanic.response.types import JSONResponse\n\n\nJSON_BODY = {\"ok\": True}\njson_dumps = partial(json.dumps, separators=(\",\", \":\"))\n\n\n@pytest.fixture\ndef json_app(app: Sanic):\n    @app.get(\"/json\")\n    async def handle(request: Request):\n        return json_response(JSON_BODY)\n\n    return app\n\n\ndef test_body_can_be_retrieved(json_app: Sanic):\n    _, resp = json_app.test_client.get(\"/json\")\n    assert resp.body == json_dumps(JSON_BODY).encode()\n\n\ndef test_body_can_be_set(json_app: Sanic):\n    new_body = b'{\"hello\":\"world\"}'\n\n    @json_app.on_response\n    def set_body(request: Request, response: JSONResponse):\n        response.body = new_body\n\n    _, resp = json_app.test_client.get(\"/json\")\n    assert resp.body == new_body\n\n\ndef test_raw_body_can_be_retrieved(json_app: Sanic):\n    @json_app.on_response\n    def check_body(request: Request, response: JSONResponse):\n        assert response.raw_body == JSON_BODY\n\n    json_app.test_client.get(\"/json\")\n\n\ndef test_raw_body_can_be_set(json_app: Sanic):\n    new_body = {\"hello\": \"world\"}\n\n    @json_app.on_response\n    def set_body(request: Request, response: JSONResponse):\n        response.raw_body = new_body\n        assert response.raw_body == new_body\n        assert response.body == json_dumps(new_body).encode()\n\n    json_app.test_client.get(\"/json\")\n\n\ndef test_raw_body_cant_be_retrieved_after_body_set(json_app: Sanic):\n    new_body = b'{\"hello\":\"world\"}'\n\n    @json_app.on_response\n    def check_raw_body(request: Request, response: JSONResponse):\n        response.body = new_body\n        with pytest.raises(SanicException):\n            response.raw_body\n\n    json_app.test_client.get(\"/json\")\n\n\ndef test_raw_body_can_be_reset_after_body_set(json_app: Sanic):\n    new_body = b'{\"hello\":\"world\"}'\n    new_new_body = {\"lorem\": \"ipsum\"}\n\n    @json_app.on_response\n    def set_bodies(request: Request, response: JSONResponse):\n        response.body = new_body\n        response.raw_body = new_new_body\n\n    _, resp = json_app.test_client.get(\"/json\")\n    assert resp.body == json_dumps(new_new_body).encode()\n\n\ndef test_set_body_method(json_app: Sanic):\n    new_body = {\"lorem\": \"ipsum\"}\n\n    @json_app.on_response\n    def set_body(request: Request, response: JSONResponse):\n        response.set_body(new_body)\n\n    _, resp = json_app.test_client.get(\"/json\")\n    assert resp.body == json_dumps(new_body).encode()\n\n\ndef test_set_body_method_after_body_set(json_app: Sanic):\n    new_body = b'{\"hello\":\"world\"}'\n    new_new_body = {\"lorem\": \"ipsum\"}\n\n    @json_app.on_response\n    def set_body(request: Request, response: JSONResponse):\n        response.body = new_body\n        response.set_body(new_new_body)\n\n    _, resp = json_app.test_client.get(\"/json\")\n    assert resp.body == json_dumps(new_new_body).encode()\n\n\ndef test_custom_dumps_and_kwargs(json_app: Sanic):\n    custom_dumps = Mock(return_value=\"custom\")\n\n    @json_app.get(\"/json-custom\")\n    async def handle_custom(request: Request):\n        return json_response(JSON_BODY, dumps=custom_dumps, prry=\"platypus\")\n\n    _, resp = json_app.test_client.get(\"/json-custom\")\n    assert resp.body == b\"custom\"\n    custom_dumps.assert_called_once_with(JSON_BODY, prry=\"platypus\")\n\n\ndef test_override_dumps_and_kwargs(json_app: Sanic):\n    custom_dumps_1 = Mock(return_value=\"custom1\")\n    custom_dumps_2 = Mock(return_value=\"custom2\")\n\n    @json_app.get(\"/json-custom\")\n    async def handle_custom(request: Request):\n        return json_response(JSON_BODY, dumps=custom_dumps_1, prry=\"platypus\")\n\n    @json_app.on_response\n    def set_body(request: Request, response: JSONResponse):\n        response.set_body(JSON_BODY, dumps=custom_dumps_2, platypus=\"prry\")\n\n    _, resp = json_app.test_client.get(\"/json-custom\")\n\n    assert resp.body == b\"custom2\"\n    custom_dumps_1.assert_called_once_with(JSON_BODY, prry=\"platypus\")\n    custom_dumps_2.assert_called_once_with(JSON_BODY, platypus=\"prry\")\n\n\ndef test_append(json_app: Sanic):\n    @json_app.get(\"/json-append\")\n    async def handler_append(request: Request):\n        return json_response([\"a\", \"b\"], status=200)\n\n    @json_app.on_response\n    def do_append(request: Request, response: JSONResponse):\n        response.append(\"c\")\n\n    _, resp = json_app.test_client.get(\"/json-append\")\n    assert resp.body == json_dumps([\"a\", \"b\", \"c\"]).encode()\n\n\ndef test_extend(json_app: Sanic):\n    @json_app.get(\"/json-extend\")\n    async def handler_extend(request: Request):\n        return json_response([\"a\", \"b\"], status=200)\n\n    @json_app.on_response\n    def do_extend(request: Request, response: JSONResponse):\n        response.extend([\"c\", \"d\"])\n\n    _, resp = json_app.test_client.get(\"/json-extend\")\n    assert resp.body == json_dumps([\"a\", \"b\", \"c\", \"d\"]).encode()\n\n\ndef test_update(json_app: Sanic):\n    @json_app.get(\"/json-update\")\n    async def handler_update(request: Request):\n        return json_response({\"a\": \"b\"}, status=200)\n\n    @json_app.on_response\n    def do_update(request: Request, response: JSONResponse):\n        response.update({\"c\": \"d\"}, e=\"f\")\n\n    _, resp = json_app.test_client.get(\"/json-update\")\n    assert resp.body == json_dumps({\"a\": \"b\", \"c\": \"d\", \"e\": \"f\"}).encode()\n\n\ndef test_pop_dict(json_app: Sanic):\n    @json_app.get(\"/json-pop\")\n    async def handler_pop(request: Request):\n        return json_response({\"a\": \"b\", \"c\": \"d\"}, status=200)\n\n    @json_app.on_response\n    def do_pop(request: Request, response: JSONResponse):\n        val = response.pop(\"c\")\n        assert val == \"d\"\n\n        val_default = response.pop(\"e\", \"f\")\n        assert val_default == \"f\"\n\n    _, resp = json_app.test_client.get(\"/json-pop\")\n    assert resp.body == json_dumps({\"a\": \"b\"}).encode()\n\n\ndef test_pop_list(json_app: Sanic):\n    @json_app.get(\"/json-pop\")\n    async def handler_pop(request: Request):\n        return json_response([\"a\", \"b\"], status=200)\n\n    @json_app.on_response\n    def do_pop(request: Request, response: JSONResponse):\n        val = response.pop(0)\n        assert val == \"a\"\n\n        with pytest.raises(\n            TypeError, match=\"pop doesn't accept a default argument for lists\"\n        ):\n            response.pop(21, \"nah nah\")\n\n    _, resp = json_app.test_client.get(\"/json-pop\")\n    assert resp.body == json_dumps([\"b\"]).encode()\n\n\ndef test_json_response_class_sets_proper_content_type(json_app: Sanic):\n    @json_app.get(\"/json-class\")\n    async def handler(request: Request):\n        return JSONResponse(JSON_BODY)\n\n    _, resp = json_app.test_client.get(\"/json-class\")\n    assert resp.headers[\"content-type\"] == \"application/json\"\n"
  },
  {
    "path": "tests/test_response_timeout.py",
    "content": "import asyncio\nimport logging\n\nfrom time import sleep\n\nimport pytest\n\nfrom sanic import Sanic\nfrom sanic.exceptions import ServiceUnavailable\nfrom sanic.log import LOGGING_CONFIG_DEFAULTS\nfrom sanic.response import text\n\n\n@pytest.fixture\ndef response_timeout_app():\n    app = Sanic(\"test_response_timeout\")\n    app.config.RESPONSE_TIMEOUT = 1\n\n    @app.route(\"/1\")\n    async def handler_1(request):\n        await asyncio.sleep(2)\n        return text(\"OK\")\n\n    @app.exception(ServiceUnavailable)\n    def handler_exception(request, exception):\n        return text(\"Response Timeout from error_handler.\", 503)\n\n    return app\n\n\n@pytest.fixture\ndef response_timeout_default_app():\n    app = Sanic(\"test_response_timeout_default\")\n    app.config.RESPONSE_TIMEOUT = 1\n\n    @app.route(\"/1\")\n    async def handler_2(request):\n        await asyncio.sleep(2)\n        return text(\"OK\")\n\n    return app\n\n\n@pytest.fixture\ndef response_handler_cancelled_app():\n    app = Sanic(\"test_response_handler_cancelled\")\n    app.config.RESPONSE_TIMEOUT = 1\n    app.ctx.flag = False\n\n    @app.exception(asyncio.CancelledError)\n    def handler_cancelled(request, exception):\n        # If we get a CancelledError, it means sanic has already sent a\n        # response, we should not ever have to handle a CancelledError.\n        response_handler_cancelled_app.ctx.flag = True\n        return text(\"App received CancelledError!\", 500)\n        # The client will never receive this response, because the socket\n        # is already closed when we get a CancelledError.\n\n    @app.route(\"/1\")\n    async def handler_3(request):\n        await asyncio.sleep(2)\n        return text(\"OK\")\n\n    return app\n\n\ndef test_server_error_response_timeout(response_timeout_app):\n    request, response = response_timeout_app.test_client.get(\"/1\")\n    assert response.status == 503\n    assert response.text == \"Response Timeout from error_handler.\"\n\n\ndef test_default_server_error_response_timeout(response_timeout_default_app):\n    request, response = response_timeout_default_app.test_client.get(\"/1\")\n    assert response.status == 503\n    assert \"Response Timeout\" in response.text\n\n\ndef test_response_handler_cancelled(response_handler_cancelled_app):\n    request, response = response_handler_cancelled_app.test_client.get(\"/1\")\n    assert response.status == 503\n    assert \"Response Timeout\" in response.text\n    assert response_handler_cancelled_app.ctx.flag is False\n\n\ndef test_response_timeout_not_applied(caplog):\n    modified_config = LOGGING_CONFIG_DEFAULTS\n    modified_config[\"loggers\"][\"sanic.websockets\"][\"level\"] = \"DEBUG\"\n\n    app = Sanic(\"test_logging\", log_config=modified_config)\n    app.config.RESPONSE_TIMEOUT = 1\n    app.ctx.event = asyncio.Event()\n\n    @app.websocket(\"/ws\")\n    async def ws_handler(request, ws):\n        sleep(2)\n        await asyncio.sleep(0)\n        request.app.ctx.event.set()\n\n    with caplog.at_level(logging.DEBUG):\n        _ = app.test_client.websocket(\"/ws\")\n    assert app.ctx.event.is_set()\n    assert (\n        \"sanic.websockets\",\n        10,\n        \"Handling websocket. Timeouts disabled.\",\n    ) in caplog.record_tuples\n"
  },
  {
    "path": "tests/test_routes.py",
    "content": "import asyncio\nimport re\n\nimport pytest\n\nfrom sanic_routing.exceptions import (\n    InvalidUsage,\n    ParameterNameConflicts,\n    RouteExists,\n)\nfrom sanic_testing.testing import SanicTestClient\n\nfrom sanic import Blueprint, Sanic\nfrom sanic.constants import HTTP_METHODS\nfrom sanic.exceptions import NotFound, SanicException, ServerError\nfrom sanic.request import Request\nfrom sanic.response import empty, json, text\n\n\n@pytest.mark.parametrize(\n    \"path,headers,expected\",\n    (\n        # app base\n        (b\"/\", {}, 200),\n        (b\"/\", {\"host\": \"maybe.com\"}, 200),\n        (b\"/host\", {\"host\": \"matching.com\"}, 200),\n        (b\"/host\", {\"host\": \"wrong.com\"}, 404),\n        # app strict_slashes default\n        (b\"/without\", {}, 200),\n        (b\"/without/\", {}, 200),\n        (b\"/with\", {}, 200),\n        (b\"/with/\", {}, 200),\n        # app strict_slashes off - expressly\n        (b\"/expwithout\", {}, 200),\n        (b\"/expwithout/\", {}, 200),\n        (b\"/expwith\", {}, 200),\n        (b\"/expwith/\", {}, 200),\n        # app strict_slashes on\n        (b\"/without/strict\", {}, 200),\n        (b\"/without/strict/\", {}, 404),\n        (b\"/with/strict\", {}, 404),\n        (b\"/with/strict/\", {}, 200),\n        # bp1 base\n        (b\"/bp1\", {}, 200),\n        (b\"/bp1\", {\"host\": \"maybe.com\"}, 200),\n        (b\"/bp1/host\", {\"host\": \"matching.com\"}, 200),  # BROKEN ON MASTER\n        (b\"/bp1/host\", {\"host\": \"wrong.com\"}, 404),\n        # bp1 strict_slashes default\n        (b\"/bp1/without\", {}, 200),\n        (b\"/bp1/without/\", {}, 200),\n        (b\"/bp1/with\", {}, 200),\n        (b\"/bp1/with/\", {}, 200),\n        # bp1 strict_slashes off - expressly\n        (b\"/bp1/expwithout\", {}, 200),\n        (b\"/bp1/expwithout/\", {}, 200),\n        (b\"/bp1/expwith\", {}, 200),\n        (b\"/bp1/expwith/\", {}, 200),\n        # bp1 strict_slashes on\n        (b\"/bp1/without/strict\", {}, 200),\n        (b\"/bp1/without/strict/\", {}, 404),\n        (b\"/bp1/with/strict\", {}, 404),\n        (b\"/bp1/with/strict/\", {}, 200),\n        # bp2 base\n        (b\"/bp2/\", {}, 200),\n        (b\"/bp2/\", {\"host\": \"maybe.com\"}, 200),\n        (b\"/bp2/host\", {\"host\": \"matching.com\"}, 200),  # BROKEN ON MASTER\n        (b\"/bp2/host\", {\"host\": \"wrong.com\"}, 404),\n        # bp2 strict_slashes default\n        (b\"/bp2/without\", {}, 200),\n        (b\"/bp2/without/\", {}, 404),\n        (b\"/bp2/with\", {}, 404),\n        (b\"/bp2/with/\", {}, 200),\n        # # bp2 strict_slashes off - expressly\n        (b\"/bp2/expwithout\", {}, 200),\n        (b\"/bp2/expwithout/\", {}, 200),\n        (b\"/bp2/expwith\", {}, 200),\n        (b\"/bp2/expwith/\", {}, 200),\n        # # bp2 strict_slashes on\n        (b\"/bp2/without/strict\", {}, 200),\n        (b\"/bp2/without/strict/\", {}, 404),\n        (b\"/bp2/with/strict\", {}, 404),\n        (b\"/bp2/with/strict/\", {}, 200),\n        # bp3 base\n        (b\"/bp3\", {}, 200),\n        (b\"/bp3\", {\"host\": \"maybe.com\"}, 200),\n        (b\"/bp3/host\", {\"host\": \"matching.com\"}, 200),  # BROKEN ON MASTER\n        (b\"/bp3/host\", {\"host\": \"wrong.com\"}, 404),\n        # bp3 strict_slashes default\n        (b\"/bp3/without\", {}, 200),\n        (b\"/bp3/without/\", {}, 200),\n        (b\"/bp3/with\", {}, 200),\n        (b\"/bp3/with/\", {}, 200),\n        # bp3 strict_slashes off - expressly\n        (b\"/bp3/expwithout\", {}, 200),\n        (b\"/bp3/expwithout/\", {}, 200),\n        (b\"/bp3/expwith\", {}, 200),\n        (b\"/bp3/expwith/\", {}, 200),\n        # bp3 strict_slashes on\n        (b\"/bp3/without/strict\", {}, 200),\n        (b\"/bp3/without/strict/\", {}, 404),\n        (b\"/bp3/with/strict\", {}, 404),\n        (b\"/bp3/with/strict/\", {}, 200),\n        # bp4 base\n        (b\"/bp4\", {}, 404),\n        (b\"/bp4\", {\"host\": \"maybe.com\"}, 200),\n        (b\"/bp4/host\", {\"host\": \"matching.com\"}, 200),  # BROKEN ON MASTER\n        (b\"/bp4/host\", {\"host\": \"wrong.com\"}, 404),\n        # bp4 strict_slashes default\n        (b\"/bp4/without\", {}, 404),\n        (b\"/bp4/without/\", {}, 404),\n        (b\"/bp4/with\", {}, 404),\n        (b\"/bp4/with/\", {}, 404),\n        # bp4 strict_slashes off - expressly\n        (b\"/bp4/expwithout\", {}, 404),\n        (b\"/bp4/expwithout/\", {}, 404),\n        (b\"/bp4/expwith\", {}, 404),\n        (b\"/bp4/expwith/\", {}, 404),\n        # bp4 strict_slashes on\n        (b\"/bp4/without/strict\", {}, 404),\n        (b\"/bp4/without/strict/\", {}, 404),\n        (b\"/bp4/with/strict\", {}, 404),\n        (b\"/bp4/with/strict/\", {}, 404),\n    ),\n)\ndef test_matching(path, headers, expected):\n    app = Sanic(\"dev\")\n    bp1 = Blueprint(\"bp1\", url_prefix=\"/bp1\")\n    bp2 = Blueprint(\"bp2\", url_prefix=\"/bp2\", strict_slashes=True)\n    bp3 = Blueprint(\"bp3\", url_prefix=\"/bp3\", strict_slashes=False)\n    bp4 = Blueprint(\"bp4\", url_prefix=\"/bp4\", host=\"maybe.com\")\n\n    def handler(request):\n        return text(\"Hello!\")\n\n    defs = (\n        (\"/\", None, None),\n        (\"/host\", None, \"matching.com\"),\n        (\"/without\", None, None),\n        (\"/with/\", None, None),\n        (\"/expwithout\", False, None),\n        (\"/expwith/\", False, None),\n        (\"/without/strict\", True, None),\n        (\"/with/strict/\", True, None),\n    )\n    for uri, strict_slashes, host in defs:\n        params = {\"uri\": uri}\n        if strict_slashes is not None:\n            params[\"strict_slashes\"] = strict_slashes\n        if host is not None:\n            params[\"host\"] = host\n        app.route(**params)(handler)\n        bp1.route(**params)(handler)\n        bp2.route(**params)(handler)\n        bp3.route(**params)(handler)\n        bp4.route(**params)(handler)\n\n    app.blueprint(bp1)\n    app.blueprint(bp2)\n    app.blueprint(bp3)\n    app.blueprint(bp4)\n\n    app.router.finalize()\n\n    request = Request(path, headers, None, \"GET\", None, app)\n\n    try:\n        app.router.get(\n            request.path, request.method, request.headers.get(\"host\")\n        )\n    except NotFound:\n        response = 404\n    except Exception:\n        response = 500\n    else:\n        response = 200\n\n    assert response == expected\n\n\n# # ------------------------------------------------------------ #\n# #  UTF-8\n# # ------------------------------------------------------------ #\n\n\n@pytest.mark.parametrize(\"method\", HTTP_METHODS)\ndef test_versioned_routes_get(app, method):\n    method = method.lower()\n\n    func = getattr(app, method)\n    if callable(func):\n\n        @func(f\"/{method}\", version=1)\n        def handler(request):\n            return text(\"OK\")\n\n    else:\n        raise Exception(f\"Method: {method} is not callable\")\n\n    client_method = getattr(app.test_client, method)\n\n    request, response = client_method(f\"/v1/{method}\")\n    assert response.status == 200\n\n\ndef test_shorthand_routes_get(app):\n    @app.get(\"/get\")\n    def handler(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\"/get\")\n    assert response.text == \"OK\"\n\n    request, response = app.test_client.post(\"/get\")\n    assert response.status == 405\n\n\ndef test_shorthand_routes_multiple(app):\n    @app.get(\"/get\")\n    def get_handler(request):\n        return text(\"OK\")\n\n    @app.options(\"/get\")\n    def options_handler(request):\n        return text(\"\")\n\n    request, response = app.test_client.get(\"/get/\")\n    assert response.status == 200\n    assert response.text == \"OK\"\n\n    request, response = app.test_client.options(\"/get/\")\n    assert response.status == 200\n\n\ndef test_route_strict_slash(app):\n    @app.get(\"/get\", strict_slashes=True)\n    def handler1(request):\n        return text(\"OK\")\n\n    @app.post(\"/post/\", strict_slashes=True)\n    def handler2(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\"/get\")\n    assert response.text == \"OK\"\n\n    request, response = app.test_client.get(\"/get/\")\n    assert response.status == 404\n\n    request, response = app.test_client.post(\"/post/\")\n    assert response.text == \"OK\"\n\n    request, response = app.test_client.post(\"/post\")\n    assert response.status == 404\n\n\ndef test_route_invalid_parameter_syntax(app):\n    with pytest.raises(InvalidUsage):\n\n        @app.get(\"/get/<:str>\", strict_slashes=True)\n        def handler(request):\n            return text(\"OK\")\n\n        request, response = app.test_client.get(\"/get\")\n\n\ndef test_route_strict_slash_default_value():\n    app = Sanic(\"test_route_strict_slash\", strict_slashes=True)\n\n    @app.get(\"/get\")\n    def handler(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\"/get/\")\n    assert response.status == 404\n\n\ndef test_route_strict_slash_without_passing_default_value(app):\n    @app.get(\"/get\")\n    def handler(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\"/get/\")\n    assert response.text == \"OK\"\n\n\ndef test_route_strict_slash_default_value_can_be_overwritten():\n    app = Sanic(\"test_route_strict_slash\", strict_slashes=True)\n\n    @app.get(\"/get\", strict_slashes=False)\n    def handler(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\"/get/\")\n    assert response.text == \"OK\"\n\n\ndef test_route_slashes_overload(app):\n    @app.get(\"/hello/\")\n    def handler_get(request):\n        return text(\"OK\")\n\n    @app.post(\"/hello/\")\n    def handler_post(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\"/hello\")\n    assert response.text == \"OK\"\n\n    request, response = app.test_client.get(\"/hello/\")\n    assert response.text == \"OK\"\n\n    request, response = app.test_client.post(\"/hello\")\n    assert response.text == \"OK\"\n\n    request, response = app.test_client.post(\"/hello/\")\n    assert response.text == \"OK\"\n\n\ndef test_route_optional_slash(app):\n    @app.get(\"/get\")\n    def handler(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\"/get\")\n    assert response.text == \"OK\"\n\n    request, response = app.test_client.get(\"/get/\")\n    assert response.text == \"OK\"\n\n\ndef test_route_strict_slashes_set_to_false_and_host_is_a_list(app):\n    # Part of regression test for issue #1120\n    test_client = SanicTestClient(app, port=42111)\n    site1 = f\"127.0.0.1:{test_client.port}\"\n\n    # before fix, this raises a RouteExists error\n    @app.get(\"/get\", host=[site1, \"site2.com\"], strict_slashes=False)\n    def get_handler(request):\n        return text(\"OK\")\n\n    request, response = test_client.get(\"http://\" + site1 + \"/get\")\n    assert response.text == \"OK\"\n\n    app.router.finalized = False\n\n    @app.post(\"/post\", host=[site1, \"site2.com\"], strict_slashes=False)\n    def post_handler(request):\n        return text(\"OK\")\n\n    request, response = test_client.post(\"http://\" + site1 + \"/post\")\n    assert response.text == \"OK\"\n\n    app.router.finalized = False\n\n    @app.put(\"/put\", host=[site1, \"site2.com\"], strict_slashes=False)\n    def put_handler(request):\n        return text(\"OK\")\n\n    request, response = test_client.put(\"http://\" + site1 + \"/put\")\n    assert response.text == \"OK\"\n\n    app.router.finalized = False\n\n    @app.delete(\"/delete\", host=[site1, \"site2.com\"], strict_slashes=False)\n    def delete_handler(request):\n        return text(\"OK\")\n\n    request, response = test_client.delete(\"http://\" + site1 + \"/delete\")\n    assert response.text == \"OK\"\n\n\ndef test_shorthand_routes_post(app):\n    @app.post(\"/post\")\n    def handler(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.post(\"/post\")\n    assert response.text == \"OK\"\n\n    request, response = app.test_client.get(\"/post\")\n    assert response.status == 405\n\n\ndef test_shorthand_routes_put(app):\n    @app.put(\"/put\")\n    def handler(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.put(\"/put\")\n    assert response.text == \"OK\"\n\n    request, response = app.test_client.get(\"/put\")\n    assert response.status == 405\n\n\ndef test_shorthand_routes_delete(app):\n    @app.delete(\"/delete\")\n    def handler(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.delete(\"/delete\")\n    assert response.text == \"OK\"\n\n    request, response = app.test_client.get(\"/delete\")\n    assert response.status == 405\n\n\ndef test_shorthand_routes_patch(app):\n    @app.patch(\"/patch\")\n    def handler(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.patch(\"/patch\")\n    assert response.text == \"OK\"\n\n    request, response = app.test_client.get(\"/patch\")\n    assert response.status == 405\n\n\ndef test_shorthand_routes_head(app):\n    @app.head(\"/head\")\n    def handler(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.head(\"/head\")\n    assert response.status == 200\n\n    request, response = app.test_client.get(\"/head\")\n    assert response.status == 405\n\n\ndef test_shorthand_routes_options(app):\n    @app.options(\"/options\")\n    def handler(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.options(\"/options\")\n    assert response.status == 200\n\n    request, response = app.test_client.get(\"/options\")\n    assert response.status == 405\n\n\ndef test_static_routes(app):\n    @app.route(\"/test\")\n    async def handler1(request):\n        return text(\"OK1\")\n\n    @app.route(\"/pizazz\")\n    async def handler2(request):\n        return text(\"OK2\")\n\n    request, response = app.test_client.get(\"/test\")\n    assert response.text == \"OK1\"\n\n    request, response = app.test_client.get(\"/pizazz\")\n    assert response.text == \"OK2\"\n\n\ndef test_dynamic_route(app):\n    results = []\n\n    @app.route(\"/folder/<name>\")\n    async def handler(request, name):\n        results.append(name)\n        return text(\"OK\")\n\n    app.router.finalize(False)\n\n    request, response = app.test_client.get(\"/folder/test123\")\n\n    assert response.text == \"OK\"\n    assert results[0] == \"test123\"\n\n\ndef test_dynamic_route_string(app):\n    results = []\n\n    @app.route(\"/folder/<name:str>\")\n    async def handler(request, name):\n        results.append(name)\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\"/folder/test123\")\n\n    assert response.text == \"OK\"\n    assert results[0] == \"test123\"\n\n    request, response = app.test_client.get(\"/folder/favicon.ico\")\n\n    assert response.text == \"OK\"\n    assert results[1] == \"favicon.ico\"\n\n\ndef test_dynamic_route_int(app):\n    results = []\n\n    @app.route(\"/folder/<folder_id:int>\")\n    async def handler(request, folder_id):\n        results.append(folder_id)\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\"/folder/12345\")\n    assert response.text == \"OK\"\n    assert isinstance(results[0], int)\n\n    request, response = app.test_client.get(\"/folder/asdf\")\n    assert response.status == 404\n\n\ndef test_dynamic_route_number(app):\n    results = []\n\n    @app.route(\"/weight/<weight:float>\")\n    async def handler(request, weight):\n        results.append(weight)\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\"/weight/12345\")\n    assert response.text == \"OK\"\n    assert isinstance(results[0], float)\n\n    request, response = app.test_client.get(\"/weight/1234.56\")\n    assert response.status == 200\n\n    request, response = app.test_client.get(\"/weight/.12\")\n    assert response.status == 200\n\n    request, response = app.test_client.get(\"/weight/12.\")\n    assert response.status == 200\n\n    request, response = app.test_client.get(\"/weight/1234-56\")\n    assert response.status == 404\n\n    request, response = app.test_client.get(\"/weight/12.34.56\")\n    assert response.status == 404\n\n\ndef test_dynamic_route_regex(app):\n    @app.route(\"/folder/<folder_id:[A-Za-z0-9]{0,4}>\")\n    async def handler(request, folder_id):\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\"/folder/test\")\n    assert response.status == 200\n\n    request, response = app.test_client.get(\"/folder/test1\")\n    assert response.status == 404\n\n    request, response = app.test_client.get(\"/folder/test-123\")\n    assert response.status == 404\n\n    request, response = app.test_client.get(\"/folder/\")\n    assert response.status == 200\n\n\ndef test_dynamic_route_uuid(app):\n    import uuid\n\n    results = []\n\n    @app.route(\"/quirky/<unique_id:uuid>\")\n    async def handler(request, unique_id):\n        results.append(unique_id)\n        return text(\"OK\")\n\n    url = \"/quirky/123e4567-e89b-12d3-a456-426655440000\"\n    request, response = app.test_client.get(url)\n    assert response.text == \"OK\"\n    assert isinstance(results[0], uuid.UUID)\n\n    generated_uuid = uuid.uuid4()\n    request, response = app.test_client.get(f\"/quirky/{generated_uuid}\")\n    assert response.status == 200\n\n    request, response = app.test_client.get(\"/quirky/non-existing\")\n    assert response.status == 404\n\n\ndef test_dynamic_route_path(app):\n    @app.route(\"/<path:path>/info\")\n    async def handler(request, path):\n        return text(\"OK\")\n\n    app.router.finalize()\n\n    request, response = app.test_client.get(\"/path/1/info\")\n    assert response.status == 200\n\n    request, response = app.test_client.get(\"/info\")\n    assert response.status == 404\n\n    app.router.reset()\n\n    @app.route(\"/<path:path>\")\n    async def handler1(request, path):\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\"/info\")\n    assert response.status == 200\n\n    request, response = app.test_client.get(\"/whatever/you/set\")\n    assert response.status == 200\n\n\ndef test_dynamic_route_unhashable(app):\n    @app.route(\"/folder/<unhashable:[A-Za-z0-9/]+>/end/\")\n    async def handler(request, unhashable):\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\"/folder/test/asdf/end/\")\n    assert response.status == 200\n\n    request, response = app.test_client.get(\"/folder/test///////end/\")\n    assert response.status == 200\n\n    request, response = app.test_client.get(\"/folder/test/end/\")\n    assert response.status == 200\n\n    request, response = app.test_client.get(\"/folder/test/nope/\")\n    assert response.status == 404\n\n\n@pytest.mark.parametrize(\"url\", [\"/ws\", \"ws\"])\ndef test_websocket_route(app, url):\n    ev = asyncio.Event()\n\n    @app.websocket(url)\n    async def handler(request, ws):\n        assert request.scheme == \"ws\"\n        assert ws.subprotocol is None\n        ev.set()\n\n    request, response = app.test_client.websocket(url)\n    assert response.opened is True\n    assert ev.is_set()\n\n\ndef test_websocket_route_invalid_handler(app):\n    with pytest.raises(ValueError) as e:\n\n        @app.websocket(\"/\")\n        async def handler(): ...\n\n    assert e.match(\n        r\"Required parameter `request` and/or `ws` missing in the \"\n        r\"handler\\(\\) route\\?\"\n    )\n\n\n@pytest.mark.asyncio\n@pytest.mark.parametrize(\"url\", [\"/ws\", \"/ws/\"])\nasync def test_websocket_route_asgi(app, url):\n    app.ctx.ev = asyncio.Event()\n\n    @app.websocket(url)\n    async def handler(request, ws):\n        request.app.ctx.ev.set()\n\n    @app.get(\"/ev\")\n    async def check(request):\n        return json({\"set\": request.app.ctx.ev.is_set()})\n\n    _, response = await app.asgi_client.websocket(url)\n    _, response = await app.asgi_client.get(\"/ev\")\n    assert response.json[\"set\"]\n\n\n@pytest.mark.parametrize(\n    \"subprotocols,expected\",\n    (\n        ([\"one\"], \"one\"),\n        ([\"three\", \"one\"], \"one\"),\n        ([\"tree\"], None),\n        (None, None),\n    ),\n)\ndef test_websocket_route_with_subprotocols(app, subprotocols, expected):\n    results = []\n\n    @app.websocket(\"/ws\", subprotocols=[\"zero\", \"one\", \"two\", \"three\"])\n    async def handler(request, ws):\n        nonlocal results\n        results = ws.subprotocol\n        assert ws.subprotocol is not None\n\n    _, response = SanicTestClient(app).websocket(\n        \"/ws\", subprotocols=subprotocols\n    )\n    assert response.opened is True\n    assert results == expected\n\n\n@pytest.mark.parametrize(\"strict_slashes\", [True, False, None])\ndef test_add_webscoket_route(app, strict_slashes):\n    ev = asyncio.Event()\n\n    async def handler(request, ws):\n        assert ws.subprotocol is None\n        ev.set()\n\n    app.add_websocket_route(handler, \"/ws\", strict_slashes=strict_slashes)\n    request, response = app.test_client.websocket(\"/ws\")\n    assert response.opened is True\n    assert ev.is_set()\n\n\ndef test_add_webscoket_route_with_version(app):\n    ev = asyncio.Event()\n\n    async def handler(request, ws):\n        assert ws.subprotocol is None\n        ev.set()\n\n    app.add_websocket_route(handler, \"/ws\", version=1)\n    request, response = app.test_client.websocket(\"/v1/ws\")\n    assert response.opened is True\n    assert ev.is_set()\n\n\ndef test_route_duplicate(app):\n    with pytest.raises(RouteExists):\n\n        @app.route(\"/test\")\n        async def handler1(request):\n            pass\n\n        @app.route(\"/test\")\n        async def handler2(request):\n            pass\n\n    with pytest.raises(RouteExists):\n\n        @app.route(\"/test/<dynamic>/\")\n        async def handler3(request, dynamic):\n            pass\n\n        @app.route(\"/test/<dynamic>/\")\n        async def handler4(request, dynamic):\n            pass\n\n\ndef test_double_stack_route(app):\n    @app.route(\"/test/1\", name=\"test1\")\n    @app.route(\"/test/2\", name=\"test2\")\n    async def handler1(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\"/test/1\")\n    assert response.status == 200\n    request, response = app.test_client.get(\"/test/2\")\n    assert response.status == 200\n\n\n@pytest.mark.asyncio\nasync def test_websocket_route_asgi_when_first_and_second_set(app):\n    ev = asyncio.Event()\n\n    @app.websocket(\"/test/1\", name=\"test1\")\n    @app.websocket(\"/test/2\", name=\"test2\")\n    async def handler(request, ws):\n        ev.set()\n\n    request, response = await app.asgi_client.websocket(\"/test/1\")\n    first_set = ev.is_set()\n    ev.clear()\n    request, response = await app.asgi_client.websocket(\"/test/1\")\n    second_set = ev.is_set()\n    assert first_set and second_set\n\n\ndef test_method_not_allowed(app):\n    @app.route(\"/test\", methods=[\"GET\"])\n    async def handler(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\"/test\")\n    assert response.status == 200\n\n    request, response = app.test_client.post(\"/test\")\n    assert response.status == 405\n\n\n@pytest.mark.parametrize(\"strict_slashes\", [True, False, None])\ndef test_static_add_route(app, strict_slashes):\n    async def handler1(request):\n        return text(\"OK1\")\n\n    async def handler2(request):\n        return text(\"OK2\")\n\n    app.add_route(handler1, \"/test\", strict_slashes=strict_slashes)\n    app.add_route(handler2, \"/test2\", strict_slashes=strict_slashes)\n\n    request, response = app.test_client.get(\"/test\")\n    assert response.text == \"OK1\"\n\n    request, response = app.test_client.get(\"/test2\")\n    assert response.text == \"OK2\"\n\n\n@pytest.mark.parametrize(\"unquote\", [True, False, None])\ndef test_unquote_add_route(app, unquote):\n    async def handler1(_, foo):\n        return text(foo)\n\n    app.add_route(handler1, \"/<foo>\", unquote=unquote)\n    value = \"啊\" if unquote else r\"%E5%95%8A\"\n\n    _, response = app.test_client.get(\"/啊\")\n    assert response.text == value\n\n    _, response = app.test_client.get(r\"/%E5%95%8A\")\n    assert response.text == value\n\n\ndef test_dynamic_add_route(app):\n    results = []\n\n    async def handler(request, name):\n        results.append(name)\n        return text(\"OK\")\n\n    app.add_route(handler, \"/folder/<name>\")\n    request, response = app.test_client.get(\"/folder/test123\")\n\n    assert response.text == \"OK\"\n    assert results[0] == \"test123\"\n\n\ndef test_dynamic_add_route_string(app):\n    results = []\n\n    async def handler(request, name):\n        results.append(name)\n        return text(\"OK\")\n\n    app.add_route(handler, \"/folder/<name:str>\")\n    request, response = app.test_client.get(\"/folder/test123\")\n\n    assert response.text == \"OK\"\n    assert results[0] == \"test123\"\n\n    request, response = app.test_client.get(\"/folder/favicon.ico\")\n\n    assert response.text == \"OK\"\n    assert results[1] == \"favicon.ico\"\n\n\ndef test_dynamic_add_route_int(app):\n    results = []\n\n    async def handler(request, folder_id):\n        results.append(folder_id)\n        return text(\"OK\")\n\n    app.add_route(handler, \"/folder/<folder_id:int>\")\n\n    request, response = app.test_client.get(\"/folder/12345\")\n    assert response.text == \"OK\"\n    assert isinstance(results[0], int)\n\n    request, response = app.test_client.get(\"/folder/asdf\")\n    assert response.status == 404\n\n\ndef test_dynamic_add_route_number(app):\n    results = []\n\n    async def handler(request, weight):\n        results.append(weight)\n        return text(\"OK\")\n\n    app.add_route(handler, \"/weight/<weight:float>\")\n\n    request, response = app.test_client.get(\"/weight/12345\")\n    assert response.text == \"OK\"\n    assert isinstance(results[0], float)\n\n    request, response = app.test_client.get(\"/weight/1234.56\")\n    assert response.status == 200\n\n    request, response = app.test_client.get(\"/weight/.12\")\n    assert response.status == 200\n\n    request, response = app.test_client.get(\"/weight/12.\")\n    assert response.status == 200\n\n    request, response = app.test_client.get(\"/weight/1234-56\")\n    assert response.status == 404\n\n    request, response = app.test_client.get(\"/weight/12.34.56\")\n    assert response.status == 404\n\n\ndef test_dynamic_add_route_regex(app):\n    async def handler(request, folder_id):\n        return text(\"OK\")\n\n    app.add_route(handler, \"/folder/<folder_id:[A-Za-z0-9]{0,4}>\")\n\n    request, response = app.test_client.get(\"/folder/test\")\n    assert response.status == 200\n\n    request, response = app.test_client.get(\"/folder/test1\")\n    assert response.status == 404\n\n    request, response = app.test_client.get(\"/folder/test-123\")\n    assert response.status == 404\n\n    request, response = app.test_client.get(\"/folder/\")\n    assert response.status == 200\n\n\ndef test_dynamic_add_route_unhashable(app):\n    async def handler(request, unhashable):\n        return text(\"OK\")\n\n    app.add_route(handler, \"/folder/<unhashable:[A-Za-z0-9/]+>/end/\")\n\n    request, response = app.test_client.get(\"/folder/test/asdf/end/\")\n    assert response.status == 200\n\n    request, response = app.test_client.get(\"/folder/test///////end/\")\n    assert response.status == 200\n\n    request, response = app.test_client.get(\"/folder/test/end/\")\n    assert response.status == 200\n\n    request, response = app.test_client.get(\"/folder/test/nope/\")\n    assert response.status == 404\n\n\ndef test_add_route_duplicate(app):\n    with pytest.raises(RouteExists):\n\n        async def handler1(request):\n            pass\n\n        async def handler2(request):\n            pass\n\n        app.add_route(handler1, \"/test\")\n        app.add_route(handler2, \"/test\")\n\n    with pytest.raises(RouteExists):\n\n        async def handler1(request, dynamic):\n            pass\n\n        async def handler2(request, dynamic):\n            pass\n\n        app.add_route(handler1, \"/test/<dynamic>/\")\n        app.add_route(handler2, \"/test/<dynamic>/\")\n\n\ndef test_add_route_method_not_allowed(app):\n    async def handler(request):\n        return text(\"OK\")\n\n    app.add_route(handler, \"/test\", methods=[\"GET\"])\n\n    request, response = app.test_client.get(\"/test\")\n    assert response.status == 200\n\n    request, response = app.test_client.post(\"/test\")\n    assert response.status == 405\n\n\ndef test_removing_slash(app):\n    @app.get(\"/rest/<resource>\")\n    def get(_):\n        pass\n\n    @app.post(\"/rest/<resource>\")\n    def post(_):\n        pass\n\n    assert len(app.router.routes_all.keys()) == 1\n\n\ndef test_overload_routes(app):\n    @app.route(\"/overload\", methods=[\"GET\"])\n    async def handler1(request):\n        return text(\"OK1\")\n\n    @app.route(\"/overload\", methods=[\"POST\", \"PUT\"])\n    async def handler2(request):\n        return text(\"OK2\")\n\n    request, response = app.test_client.get(\"/overload\")\n    assert response.text == \"OK1\"\n\n    request, response = app.test_client.post(\"/overload\")\n    assert response.text == \"OK2\"\n\n    request, response = app.test_client.put(\"/overload\")\n    assert response.text == \"OK2\"\n\n    request, response = app.test_client.delete(\"/overload\")\n    assert response.status == 405\n\n    app.router.reset()\n    with pytest.raises(RouteExists):\n\n        @app.route(\"/overload\", methods=[\"PUT\", \"DELETE\"])\n        async def handler3(request):\n            return text(\"Duplicated\")\n\n\ndef test_unmergeable_overload_routes(app):\n    @app.route(\"/overload_whole\", methods=None)\n    async def handler1(request):\n        return text(\"OK1\")\n\n    @app.route(\"/overload_whole\", methods=[\"POST\", \"PUT\"])\n    async def handler2(request):\n        return text(\"OK1\")\n\n    assert len(app.router.static_routes) == 1\n    assert len(app.router.static_routes[(\"overload_whole\",)].methods) == 3\n\n    request, response = app.test_client.get(\"/overload_whole\")\n    assert response.text == \"OK1\"\n\n    request, response = app.test_client.post(\"/overload_whole\")\n    assert response.text == \"OK1\"\n\n    request, response = app.test_client.put(\"/overload_whole\")\n    assert response.text == \"OK1\"\n\n    app.router.reset()\n\n    @app.route(\"/overload_part\", methods=[\"GET\"])\n    async def handler3(request):\n        return text(\"OK1\")\n\n    with pytest.raises(RouteExists):\n\n        @app.route(\"/overload_part\")\n        async def handler4(request):\n            return text(\"Duplicated\")\n\n    request, response = app.test_client.get(\"/overload_part\")\n    assert response.text == \"OK1\"\n\n    request, response = app.test_client.post(\"/overload_part\")\n    assert response.status == 405\n\n\ndef test_unicode_routes(app):\n    @app.get(\"/你好\")\n    def handler1(request):\n        return text(\"OK1\")\n\n    request, response = app.test_client.get(\"/你好\")\n    assert response.text == \"OK1\"\n\n    app.router.reset()\n\n    @app.route(\"/overload/<param>\", methods=[\"GET\"], unquote=True)\n    async def handler2(request, param):\n        return text(\"OK2 \" + param)\n\n    request, response = app.test_client.get(\"/overload/你好\")\n    assert response.text == \"OK2 你好\"\n\n\ndef test_uri_with_different_method_and_different_params(app):\n    @app.route(\"/ads/<ad_id>\", methods=[\"GET\"])\n    async def ad_get(request, ad_id):\n        return json({\"ad_id\": ad_id})\n\n    @app.route(\"/ads/<action>\", methods=[\"POST\"])\n    async def ad_post(request, action):\n        return json({\"action\": action})\n\n    request, response = app.test_client.get(\"/ads/1234\")\n    assert response.status == 200\n    assert response.json == {\"ad_id\": \"1234\"}\n\n    request, response = app.test_client.post(\"/ads/post\")\n    assert response.status == 200\n    assert response.json == {\"action\": \"post\"}\n\n\ndef test_uri_with_different_method_and_same_params(app):\n    @app.route(\"/ads/<ad_id>\", methods=[\"GET\"])\n    async def ad_get(request, ad_id):\n        return json({\"ad_id\": ad_id})\n\n    @app.route(\"/ads/<ad_id>\", methods=[\"POST\"])\n    async def ad_post(request, ad_id):\n        return json({\"ad_id\": ad_id})\n\n    request, response = app.test_client.get(\"/ads/1234\")\n    assert response.status == 200\n    assert response.json == {\"ad_id\": \"1234\"}\n\n    request, response = app.test_client.post(\"/ads/post\")\n    assert response.status == 200\n    assert response.json == {\"ad_id\": \"post\"}\n\n\ndef test_route_raise_ParameterNameConflicts(app):\n    @app.get(\"/api/v1/<user>/<user>/\")\n    def handler(request, user):\n        return text(\"OK\")\n\n    with pytest.raises(ParameterNameConflicts):\n        app.router.finalize()\n\n\ndef test_route_invalid_host(app):\n    host = 321\n    with pytest.raises(ValueError) as excinfo:\n\n        @app.get(\"/test\", host=host)\n        def handler(request):\n            return text(\"pass\")\n\n    assert str(excinfo.value) == (\n        \"Expected either string or Iterable of host strings, not {!r}\"\n    ).format(host)\n\n\ndef test_route_with_regex_group(app):\n    @app.route(r\"/path/to/<ext:file\\.(txt)>\")\n    async def handler(request, ext):\n        return text(ext)\n\n    _, response = app.test_client.get(\"/path/to/file.txt\")\n    assert response.text == \"txt\"\n\n\ndef test_route_with_regex_named_group(app):\n    @app.route(r\"/path/to/<ext:file\\.(?P<ext>txt)>\")\n    async def handler(request, ext):\n        return text(ext)\n\n    _, response = app.test_client.get(\"/path/to/file.txt\")\n    assert response.text == \"txt\"\n\n\ndef test_route_with_regex_named_group_invalid(app):\n    @app.route(r\"/path/to/<ext:file\\.(?P<wrong>txt)>\")\n    async def handler(request, ext):\n        return text(ext)\n\n    with pytest.raises(InvalidUsage) as e:\n        app.router.finalize()\n\n    assert e.match(\n        re.escape(\"Named group (wrong) must match your named parameter (ext)\")\n    )\n\n\ndef test_route_with_regex_group_ambiguous(app):\n    @app.route(r\"/path/to/<ext:file(?:\\.)(txt)>\")\n    async def handler(request, ext):\n        return text(ext)\n\n    with pytest.raises(InvalidUsage) as e:\n        app.router.finalize()\n\n    assert e.match(\n        re.escape(\n            r\"Could not compile pattern file(?:\\.)(txt). Try using a named \"\n            \"group instead: '(?P<ext>your_matching_group)'\"\n        )\n    )\n\n\ndef test_route_with_bad_named_param(app):\n    @app.route(\"/foo/<__bar__>\")\n    async def handler(request):\n        return text(\"...\")\n\n    with pytest.raises(SanicException):\n        app.router.finalize()\n\n\ndef test_routes_with_and_without_slash_definitions(app):\n    bar = Blueprint(\"bar\", url_prefix=\"bar\")\n    baz = Blueprint(\"baz\", url_prefix=\"/baz\")\n    fizz = Blueprint(\"fizz\", url_prefix=\"fizz/\")\n    buzz = Blueprint(\"buzz\", url_prefix=\"/buzz/\")\n\n    instances = (\n        (app, \"foo\"),\n        (bar, \"bar\"),\n        (baz, \"baz\"),\n        (fizz, \"fizz\"),\n        (buzz, \"buzz\"),\n    )\n\n    for instance, term in instances:\n        route = f\"/{term}\" if isinstance(instance, Sanic) else \"\"\n\n        @instance.get(route, strict_slashes=True)\n        def get_without(request):\n            return text(f\"{term}_without\")\n\n        @instance.get(f\"{route}/\", strict_slashes=True)\n        def get_with(request):\n            return text(f\"{term}_with\")\n\n        @instance.post(route, strict_slashes=True)\n        def post_without(request):\n            return text(f\"{term}_without\")\n\n        @instance.post(f\"{route}/\", strict_slashes=True)\n        def post_with(request):\n            return text(f\"{term}_with\")\n\n    app.blueprint(bar)\n    app.blueprint(baz)\n    app.blueprint(fizz)\n    app.blueprint(buzz)\n\n    for _, term in instances:\n        _, response = app.test_client.get(f\"/{term}\")\n        assert response.status == 200\n        assert response.text == f\"{term}_without\"\n\n        _, response = app.test_client.get(f\"/{term}/\")\n        assert response.status == 200\n        assert response.text == f\"{term}_with\"\n\n        _, response = app.test_client.post(f\"/{term}\")\n        assert response.status == 200\n        assert response.text == f\"{term}_without\"\n\n        _, response = app.test_client.post(f\"/{term}/\")\n        assert response.status == 200\n        assert response.text == f\"{term}_with\"\n\n\ndef test_added_route_ctx_kwargs(app):\n    @app.route(\"/\", ctx_foo=\"foo\", ctx_bar=99)\n    async def handler(request: Request):\n        return empty()\n\n    request, _ = app.test_client.get(\"/\")\n\n    assert request.route.ctx.foo == \"foo\"\n    assert request.route.ctx.bar == 99\n\n\ndef test_added_bad_route_kwargs(app):\n    message = \"Unexpected keyword arguments: foo, bar\"\n    with pytest.raises(TypeError, match=message):\n\n        @app.route(\"/\", foo=\"foo\", bar=99)\n        async def handler(request: Request): ...\n\n\n@pytest.mark.asyncio\nasync def test_added_callable_route_ctx_kwargs(app):\n    def foo(*args, **kwargs):\n        return \"foo\"\n\n    async def bar(*args, **kwargs):\n        return 99\n\n    @app.route(\"/\", ctx_foo=foo, ctx_bar=bar)\n    async def handler(request: Request):\n        return empty()\n\n    request, _ = await app.asgi_client.get(\"/\")\n\n    assert request.route.ctx.foo() == \"foo\"\n    assert await request.route.ctx.bar() == 99\n\n\n@pytest.mark.asyncio\nasync def test_duplicate_route_error(app):\n    @app.route(\"/foo\", name=\"duped\")\n    async def handler_foo(request):\n        return text(\"...\")\n\n    @app.route(\"/bar\", name=\"duped\")\n    async def handler_bar(request):\n        return text(\"...\")\n\n    message = (\n        \"Duplicate route names detected: test_duplicate_route_error.duped.\"\n    )\n    with pytest.raises(ServerError, match=message):\n        await app._startup()\n"
  },
  {
    "path": "tests/test_server_events.py",
    "content": "import asyncio\nimport signal\n\nfrom contextlib import closing\nfrom socket import socket\n\nimport pytest\n\nfrom sanic_testing.testing import HOST\n\nfrom sanic import Blueprint\nfrom sanic.exceptions import BadRequest, SanicException\n\nfrom .conftest import get_port\n\n\nAVAILABLE_LISTENERS = [\n    \"before_server_start\",\n    \"after_server_start\",\n    \"before_server_stop\",\n    \"after_server_stop\",\n]\n\n\ndef create_listener(listener_name, in_list):\n    async def _listener(app, loop):\n        print(f\"DEBUG MESSAGE FOR PYTEST for {listener_name}\")\n        in_list.insert(0, app.name + listener_name)\n\n    return _listener\n\n\ndef create_listener_no_loop(listener_name, in_list):\n    async def _listener(app):\n        print(f\"DEBUG MESSAGE FOR PYTEST for {listener_name}\")\n        in_list.insert(0, app.name + listener_name)\n\n    return _listener\n\n\ndef start_stop_app(random_name_app, **run_kwargs):\n    @random_name_app.after_server_start\n    async def shutdown(app):\n        await asyncio.sleep(1.1)\n        app.stop()\n\n    try:\n        random_name_app.run(\n            HOST, get_port(), single_process=True, **run_kwargs\n        )\n    except KeyboardInterrupt:\n        pass\n\n\n@pytest.mark.parametrize(\"listener_name\", AVAILABLE_LISTENERS)\ndef test_single_listener(app, listener_name):\n    \"\"\"Test that listeners on their own work\"\"\"\n    output = []\n    # Register listener\n    app.listener(listener_name)(create_listener(listener_name, output))\n    start_stop_app(app)\n    assert app.name + listener_name == output.pop()\n\n\n@pytest.mark.parametrize(\"listener_name\", AVAILABLE_LISTENERS)\ndef test_single_listener_no_loop(app, listener_name):\n    \"\"\"Test that listeners on their own work\"\"\"\n    output = []\n    # Register listener\n    app.listener(listener_name)(create_listener_no_loop(listener_name, output))\n    start_stop_app(app)\n    assert app.name + listener_name == output.pop()\n\n\n@pytest.mark.parametrize(\"listener_name\", AVAILABLE_LISTENERS)\ndef test_register_listener(app, listener_name):\n    \"\"\"\n    Test that listeners on their own work with\n    app.register_listener method\n    \"\"\"\n    output = []\n    # Register listener\n    listener = create_listener(listener_name, output)\n    app.register_listener(listener, event=listener_name)\n    start_stop_app(app)\n    assert app.name + listener_name == output.pop()\n\n\ndef test_all_listeners(app):\n    output = []\n    for listener_name in AVAILABLE_LISTENERS:\n        listener = create_listener(listener_name, output)\n        app.listener(listener_name)(listener)\n    start_stop_app(app)\n    for listener_name in AVAILABLE_LISTENERS:\n        assert app.name + listener_name == output.pop()\n\n\ndef test_all_listeners_as_convenience(app):\n    output = []\n    for listener_name in AVAILABLE_LISTENERS:\n        listener = create_listener(listener_name, output)\n        method = getattr(app, listener_name)\n        method(listener)\n    start_stop_app(app)\n    for listener_name in AVAILABLE_LISTENERS:\n        assert app.name + listener_name == output.pop()\n\n\n@pytest.mark.asyncio\nasync def test_trigger_before_events_create_server(app, port):\n    class MySanicDb:\n        pass\n\n    @app.listener(\"before_server_start\")\n    async def init_db(app, loop):\n        app.ctx.db = MySanicDb()\n\n    srv = await app.create_server(\n        debug=True, return_asyncio_server=True, port=port\n    )\n    await srv.startup()\n    await srv.before_start()\n\n    assert hasattr(app.ctx, \"db\")\n    assert isinstance(app.ctx.db, MySanicDb)\n\n\n@pytest.mark.asyncio\nasync def test_trigger_before_events_create_server_missing_event(app):\n    class MySanicDb:\n        pass\n\n    with pytest.raises(BadRequest):\n\n        @app.listener\n        async def init_db(app, loop):\n            app.ctx.db = MySanicDb()\n\n    assert not hasattr(app.ctx, \"db\")\n\n\ndef test_create_server_trigger_events(app):\n    \"\"\"Test if create_server can trigger server events\"\"\"\n\n    def stop_on_alarm(signum, frame):\n        raise KeyboardInterrupt(\"...\")\n\n    flag1 = False\n    flag2 = False\n    flag3 = False\n\n    async def stop(app, loop):\n        nonlocal flag1\n        flag1 = True\n\n    async def before_stop(app, loop):\n        nonlocal flag2\n        flag2 = True\n\n    async def after_stop(app, loop):\n        nonlocal flag3\n        flag3 = True\n\n    app.listener(\"after_server_start\")(stop)\n    app.listener(\"before_server_stop\")(before_stop)\n    app.listener(\"after_server_stop\")(after_stop)\n\n    loop = asyncio.get_event_loop()\n\n    # Use random port for tests\n\n    signal.signal(signal.SIGALRM, stop_on_alarm)\n    signal.alarm(1)\n    with closing(socket()) as sock:\n        sock.bind((\"127.0.0.1\", 0))\n\n        serv_coro = app.create_server(\n            return_asyncio_server=True, sock=sock, debug=True\n        )\n        serv_task = asyncio.ensure_future(serv_coro, loop=loop)\n        server = loop.run_until_complete(serv_task)\n        loop.run_until_complete(server.startup())\n        loop.run_until_complete(server.after_start())\n        try:\n            loop.run_forever()\n        except KeyboardInterrupt:\n            loop.stop()\n        finally:\n            # Run the on_stop function if provided\n            loop.run_until_complete(server.before_stop())\n\n            # Wait for server to close\n            close_task = server.close()\n            loop.run_until_complete(close_task)\n\n            # Complete all tasks on the loop\n            for connection in server.connections:\n                connection.close_if_idle()\n            loop.run_until_complete(server.after_stop())\n        assert flag1 and flag2 and flag3\n\n\n@pytest.mark.asyncio\nasync def test_missing_startup_raises_exception(app, port):\n    @app.listener(\"before_server_start\")\n    async def init_db(app, loop): ...\n\n    srv = await app.create_server(\n        debug=True, return_asyncio_server=True, port=port\n    )\n\n    with pytest.raises(SanicException):\n        await srv.before_start()\n\n\ndef test_reload_listeners_attached(app):\n    async def dummy(*_): ...\n\n    app.reload_process_start(dummy)\n    app.reload_process_stop(dummy)\n    app.listener(\"reload_process_start\")(dummy)\n    app.listener(\"reload_process_stop\")(dummy)\n\n    assert len(app.listeners.get(\"reload_process_start\")) == 2\n    assert len(app.listeners.get(\"reload_process_stop\")) == 2\n\n\ndef test_priority_ordering(app):\n    output = []\n    bp = Blueprint(\"bp\")\n\n    @app.before_server_start\n    async def first(app):\n        output.append(\"first\")\n\n    @app.listener(\"before_server_start\", priority=2)\n    async def second(app):\n        output.append(\"second\")\n\n    @app.before_server_start(priority=3)\n    async def third(app):\n        output.append(\"third\")\n\n    @bp.before_server_start\n    async def bp_first(app):\n        output.append(\"bp_first\")\n\n    @bp.listener(\"before_server_start\", priority=2)\n    async def bp_second(app):\n        output.append(\"bp_second\")\n\n    @bp.before_server_start(priority=3)\n    async def bp_third(app):\n        output.append(\"bp_third\")\n\n    @app.before_server_start\n    async def fourth(app):\n        output.append(\"fourth\")\n\n    app.blueprint(bp)\n    start_stop_app(app)\n\n    # The order of the listeners is:\n    # priority descending, app before bp, definition order ascending\n    assert output == [\n        \"third\",\n        \"bp_third\",\n        \"second\",\n        \"bp_second\",\n        \"first\",\n        \"fourth\",\n        \"bp_first\",\n    ]\n"
  },
  {
    "path": "tests/test_server_loop.py",
    "content": "import logging\n\nfrom unittest.mock import Mock, patch\n\nimport pytest\n\nfrom sanic.compat import OS_IS_WINDOWS, UVLOOP_INSTALLED\nfrom sanic.server import loop\n\n\n@pytest.mark.skipif(\n    not OS_IS_WINDOWS,\n    reason=\"Not testable with current client\",\n)\ndef test_raises_warning_if_os_is_windows(caplog):\n    with caplog.at_level(logging.WARNING):\n        loop.try_use_uvloop()\n\n    for record in caplog.records:\n        if record.message.startswith(\"You are trying to use\"):\n            break\n\n    assert record.message == (\n        \"You are trying to use uvloop, but uvloop is not compatible \"\n        \"with your system. You can disable uvloop completely by setting \"\n        \"the 'USE_UVLOOP' configuration value to false, or simply not \"\n        \"defining it and letting Sanic handle it for you. Sanic will now \"\n        \"continue to run using the default event loop.\"\n    )\n\n\n@pytest.mark.skipif(\n    OS_IS_WINDOWS or UVLOOP_INSTALLED,\n    reason=\"Not testable with current client\",\n)\ndef test_raises_warning_if_uvloop_not_installed(caplog):\n    with caplog.at_level(logging.WARNING):\n        loop.try_use_uvloop()\n\n    for record in caplog.records:\n        if record.message.startswith(\"You are trying to use\"):\n            break\n\n    assert record.message == (\n        \"You are trying to use uvloop, but uvloop is not \"\n        \"installed in your system. In order to use uvloop \"\n        \"you must first install it. Otherwise, you can disable \"\n        \"uvloop completely by setting the 'USE_UVLOOP' \"\n        \"configuration value to false. Sanic will now continue \"\n        \"to run with the default event loop.\"\n    )\n\n\n@pytest.mark.skipif(\n    OS_IS_WINDOWS or not UVLOOP_INSTALLED,\n    reason=\"Not testable with current client\",\n)\ndef test_logs_when_install_and_runtime_config_mismatch(caplog, monkeypatch):\n    getenv = Mock(return_value=\"no\")\n    monkeypatch.setattr(loop, \"getenv\", getenv)\n\n    with caplog.at_level(logging.INFO):\n        loop.try_use_uvloop()\n\n    getenv.assert_called_once_with(\"SANIC_NO_UVLOOP\", \"no\")\n    assert caplog.record_tuples == []\n\n    getenv = Mock(return_value=\"yes\")\n    monkeypatch.setattr(loop, \"getenv\", getenv)\n    with caplog.at_level(logging.INFO):\n        loop.try_use_uvloop()\n\n    getenv.assert_called_once_with(\"SANIC_NO_UVLOOP\", \"no\")\n    for record in caplog.records:\n        if record.message.startswith(\"You are requesting to run\"):\n            break\n\n    assert record.message == (\n        \"You are requesting to run Sanic using uvloop, but the \"\n        \"install-time 'SANIC_NO_UVLOOP' environment variable (used to \"\n        \"opt-out of installing uvloop with Sanic) is set to true. If \"\n        \"you want to prevent Sanic from overriding the event loop policy \"\n        \"during runtime, set the 'USE_UVLOOP' configuration value to \"\n        \"false.\"\n    )\n\n\n@pytest.mark.skipif(\n    OS_IS_WINDOWS or not UVLOOP_INSTALLED,\n    reason=\"Not testable with current client\",\n)\ndef test_sets_loop_policy_only_when_not_already_set(monkeypatch):\n    import uvloop  # type: ignore\n\n    # Existing policy is not uvloop.EventLoopPolicy\n    get_event_loop_policy = Mock(return_value=None)\n    monkeypatch.setattr(\n        loop.asyncio, \"get_event_loop_policy\", get_event_loop_policy\n    )\n\n    with patch(\"asyncio.set_event_loop_policy\") as set_event_loop_policy:\n        loop.try_use_uvloop()\n        set_event_loop_policy.assert_called_once()\n        args, _ = set_event_loop_policy.call_args\n        policy = args[0]\n        assert isinstance(policy, uvloop.EventLoopPolicy)\n\n    # Existing policy is uvloop.EventLoopPolicy\n    get_event_loop_policy = Mock(return_value=policy)\n    monkeypatch.setattr(\n        loop.asyncio, \"get_event_loop_policy\", get_event_loop_policy\n    )\n\n    with patch(\"asyncio.set_event_loop_policy\") as set_event_loop_policy:\n        loop.try_use_uvloop()\n        set_event_loop_policy.assert_not_called()\n"
  },
  {
    "path": "tests/test_signal_handlers.py",
    "content": "import asyncio\nimport os\nimport signal\n\nfrom queue import Queue\nfrom types import SimpleNamespace\nfrom typing import Optional\nfrom unittest.mock import MagicMock\n\nimport pytest\n\nfrom sanic_testing.testing import HOST, PORT\n\nfrom sanic import Sanic\nfrom sanic.compat import ctrlc_workaround_for_windows\nfrom sanic.exceptions import BadRequest, ServerError\nfrom sanic.response import HTTPResponse\nfrom sanic.signals import Event\n\n\npytestmark = pytest.mark.xdist_group(name=\"process_spawning\")\n\n\nasync def stop(app, loop):\n    await asyncio.sleep(0.1)\n    app.stop()\n\n\ncalledq = Queue()\n\n\ndef set_loop(app, loop):\n    global mock\n    mock = MagicMock()\n    if os.name == \"nt\":\n        signal.signal = mock\n    else:\n        loop.add_signal_handler = mock\n\n\ndef after(app, loop):\n    print(\"...\")\n    calledq.put(mock.called)\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"May hang CI on py38/windows\")\ndef test_register_system_signals(app):\n    \"\"\"Test if sanic register system signals\"\"\"\n\n    @app.route(\"/hello\")\n    async def hello_route(request):\n        return HTTPResponse()\n\n    app.listener(\"after_server_start\")(stop)\n    app.listener(\"before_server_start\")(set_loop)\n    app.listener(\"after_server_stop\")(after)\n\n    app.run(HOST, PORT, single_process=True)\n    assert calledq.get() is True\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"May hang CI on py38/windows\")\ndef test_no_register_system_signals_fails(app):\n    \"\"\"Test if sanic don't register system signals\"\"\"\n\n    @app.route(\"/hello\")\n    async def hello_route(request):\n        return HTTPResponse()\n\n    app.listener(\"after_server_start\")(stop)\n    app.listener(\"before_server_start\")(set_loop)\n    app.listener(\"after_server_stop\")(after)\n\n    message = (\n        r\"Cannot run Sanic\\.serve with register_sys_signals=False\\. Use \"\n        r\"Sanic.serve_single\\.\"\n    )\n    with pytest.raises(RuntimeError, match=message):\n        app.prepare(HOST, PORT, register_sys_signals=False)\n    assert calledq.empty()\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"May hang CI on py38/windows\")\ndef test_dont_register_system_signals(app):\n    \"\"\"Test if sanic don't register system signals\"\"\"\n\n    @app.route(\"/hello\")\n    async def hello_route(request):\n        return HTTPResponse()\n\n    app.listener(\"after_server_start\")(stop)\n    app.listener(\"before_server_start\")(set_loop)\n    app.listener(\"after_server_stop\")(after)\n\n    app.run(HOST, PORT, register_sys_signals=False, single_process=True)\n    assert calledq.get() is False\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"windows cannot SIGINT processes\")\ndef test_windows_workaround():\n    \"\"\"Test Windows workaround (on any other OS)\"\"\"\n\n    # At least some code coverage, even though this test doesn't work on\n    # Windows...\n    class MockApp:\n        def __init__(self):\n            self.state = SimpleNamespace()\n            self.state.is_stopping = False\n\n        def stop(self):\n            assert not self.state.is_stopping\n            self.state.is_stopping = True\n\n        def add_task(self, func):\n            loop = asyncio.get_event_loop()\n            self.stay_active_task = loop.create_task(func(self))\n\n    async def atest(stop_first):\n        app = MockApp()\n        ctrlc_workaround_for_windows(app)\n        await asyncio.sleep(0.05)\n        if stop_first:\n            app.stop()\n            await asyncio.sleep(0.2)\n        assert app.state.is_stopping == stop_first\n        # First Ctrl+C: should call app.stop() within 0.1 seconds\n        os.kill(os.getpid(), signal.SIGINT)\n        await asyncio.sleep(0.2)\n        assert app.state.is_stopping\n        assert app.stay_active_task.result() is None\n        # Second Ctrl+C should raise\n        with pytest.raises(KeyboardInterrupt):\n            os.kill(os.getpid(), signal.SIGINT)\n        return \"OK\"\n\n    # Run in our private loop\n    loop = asyncio.new_event_loop()\n    asyncio.set_event_loop(loop)\n    res = loop.run_until_complete(atest(False))\n    assert res == \"OK\"\n    res = loop.run_until_complete(atest(True))\n    assert res == \"OK\"\n\n\n@pytest.mark.skipif(os.name == \"nt\", reason=\"May hang CI on py38/windows\")\ndef test_signals_with_invalid_invocation(app):\n    \"\"\"Test if sanic register fails with invalid invocation\"\"\"\n\n    @app.route(\"/hello\")\n    async def hello_route(request):\n        return HTTPResponse()\n\n    with pytest.raises(\n        BadRequest, match=\"Invalid event registration: Missing event name\"\n    ):\n        app.listener(stop)\n\n\ndef test_signal_server_lifecycle_exception(app: Sanic):\n    trigger: Optional[Exception] = None\n\n    @app.route(\"/hello\")\n    async def hello_route(request):\n        return HTTPResponse()\n\n    @app.signal(Event.SERVER_EXCEPTION_REPORT)\n    async def test_signal(exception: Exception):\n        nonlocal trigger\n        trigger = exception\n\n    @app.before_server_start\n    async def test_before_server_start(app):\n        raise ServerError(\"test_before_server_start\")\n\n    with pytest.raises(ServerError, match=\"test_before_server_start\"):\n        app.run(single_process=True)\n\n    assert isinstance(trigger, ServerError)\n    assert str(trigger) == \"test_before_server_start\"\n"
  },
  {
    "path": "tests/test_signals.py",
    "content": "import asyncio\n\nfrom enum import Enum\nfrom itertools import count\n\nimport pytest\n\nfrom sanic_routing.exceptions import NotFound\n\nfrom sanic import Blueprint, Sanic, empty\nfrom sanic.exceptions import InvalidSignal, SanicException\nfrom sanic.signals import Event\n\n\ndef test_add_signal(app):\n    def sync_signal(*_): ...\n\n    app.add_signal(sync_signal, \"foo.bar.baz\")\n\n    assert len(app.signal_router.routes) == 1\n\n\ndef test_add_signal_method_handler(app):\n    counter = 0\n\n    class TestSanic(Sanic):\n        def __init__(self, *args, **kwargs):\n            super().__init__(*args, **kwargs)\n            self.add_signal(\n                self.after_routing_signal_handler, \"http.routing.after\"\n            )\n\n        def after_routing_signal_handler(self, *args, **kwargs):\n            nonlocal counter\n            counter += 1\n\n    app = TestSanic(\"Test\")\n    assert len(app.signal_router.routes) == 1\n\n    @app.route(\"/\")\n    async def handler(_):\n        return empty()\n\n    app.test_client.get(\"/\")\n    assert counter == 1\n\n\ndef test_add_signal_decorator(app):\n    @app.signal(\"foo.bar.baz\")\n    def sync_signal(*_): ...\n\n    @app.signal(\"foo.bar.baz\")\n    async def async_signal(*_): ...\n\n    assert len(app.signal_router.routes) == 2\n    assert len(app.signal_router.dynamic_routes) == 1\n\n\n@pytest.mark.parametrize(\n    \"signal\",\n    (\n        \"<foo>.bar.bax\",\n        \"foo.<bar>.baz\",\n        \"foo.bar\",\n        \"foo.bar.baz.qux\",\n    ),\n)\ndef test_invalid_signal(app, signal):\n    with pytest.raises(InvalidSignal, match=f\"Invalid signal event: {signal}\"):\n\n        @app.signal(signal)\n        def handler(): ...\n\n\n@pytest.mark.asyncio\nasync def test_dispatch_signal_triggers_event(app):\n    @app.signal(\"foo.bar.baz\")\n    def sync_signal(*args):\n        pass\n\n    app.signal_router.finalize()\n\n    event_task = asyncio.create_task(app.event(\"foo.bar.baz\"))\n    await app.dispatch(\"foo.bar.baz\")\n    await asyncio.sleep(0)\n\n    assert event_task.done()\n    event_task.result()  # Will raise if there was an exception\n\n\n@pytest.mark.asyncio\nasync def test_dispatch_signal_triggers_correct_event(app):\n    # Check for https://github.com/sanic-org/sanic/issues/2826\n\n    @app.signal(\"foo.bar.baz\")\n    def sync_signal_baz(*args):\n        pass\n\n    @app.signal(\"foo.bar.spam\")\n    def sync_signal_spam(*args):\n        pass\n\n    app.signal_router.finalize()\n\n    baz_task = asyncio.create_task(app.event(\"foo.bar.baz\"))\n    spam_task = asyncio.create_task(app.event(\"foo.bar.spam\"))\n\n    await app.dispatch(\"foo.bar.baz\")\n    await asyncio.sleep(0)\n\n    assert baz_task.done()\n    assert not spam_task.done()\n    baz_task.result()\n    spam_task.cancel()\n\n\n@pytest.mark.asyncio\nasync def test_dispatch_signal_with_enum_event(app):\n    counter = 0\n\n    class FooEnum(Enum):\n        FOO_BAR_BAZ = \"foo.bar.baz\"\n\n    @app.signal(FooEnum.FOO_BAR_BAZ)\n    def sync_signal(*_):\n        nonlocal counter\n\n        counter += 1\n\n    app.signal_router.finalize()\n\n    await app.dispatch(\"foo.bar.baz\")\n    assert counter == 1\n\n\n@pytest.mark.asyncio\nasync def test_dispatch_signal_with_enum_event_to_event(app):\n    class FooEnum(Enum):\n        FOO_BAR_BAZ = \"foo.bar.baz\"\n\n    @app.signal(FooEnum.FOO_BAR_BAZ)\n    def sync_signal(*args):\n        pass\n\n    app.signal_router.finalize()\n\n    event_task = asyncio.create_task(app.event(FooEnum.FOO_BAR_BAZ))\n    await app.dispatch(\"foo.bar.baz\")\n    await asyncio.sleep(0)\n\n    assert event_task.done()\n    event_task.result()  # Will raise if there was an exception\n\n\n@pytest.mark.asyncio\nasync def test_dispatch_signal_triggers_multiple_handlers(app):\n    counter = 0\n\n    @app.signal(\"foo.bar.baz\")\n    def sync_signal(*_):\n        nonlocal counter\n\n        counter += 1\n\n    @app.signal(\"foo.bar.baz\")\n    async def async_signal(*_):\n        nonlocal counter\n\n        counter += 1\n\n    app.signal_router.finalize()\n\n    assert len(app.signal_router.routes) == 3\n    await app.dispatch(\"foo.bar.baz\")\n    assert counter == 2\n\n\n@pytest.mark.asyncio\nasync def test_dispatch_signal_triggers_multiple_events(app):\n    @app.signal(\"foo.bar.baz\")\n    def sync_signal(*_):\n        pass\n\n    app.signal_router.finalize()\n\n    event_task1 = asyncio.create_task(app.event(\"foo.bar.baz\"))\n    event_task2 = asyncio.create_task(app.event(\"foo.bar.baz\"))\n\n    await app.dispatch(\"foo.bar.baz\")\n    await asyncio.sleep(0)\n\n    assert event_task1.done()\n    assert event_task2.done()\n    event_task1.result()  # Will raise if there was an exception\n    event_task2.result()  # Will raise if there was an exception\n\n\n@pytest.mark.asyncio\nasync def test_dispatch_signal_with_multiple_handlers_triggers_event_once(app):\n    @app.signal(\"foo.bar.baz\")\n    def sync_signal(*_):\n        pass\n\n    @app.signal(\"foo.bar.baz\")\n    async def async_signal(*_):\n        pass\n\n    app.signal_router.finalize()\n\n    event_task = asyncio.create_task(app.event(\"foo.bar.baz\"))\n    await app.dispatch(\"foo.bar.baz\")\n    await asyncio.sleep(0)\n\n    assert event_task.done()\n    event_task.result()  # Will raise if there was an exception\n\n\n@pytest.mark.asyncio\nasync def test_dispatch_signal_triggers_dynamic_route(app):\n    counter = 0\n\n    @app.signal(\"foo.bar.<baz:int>\")\n    def sync_signal(baz):\n        nonlocal counter\n\n        counter += baz\n\n    app.signal_router.finalize()\n\n    await app.dispatch(\"foo.bar.9\")\n    assert counter == 9\n\n\n@pytest.mark.asyncio\nasync def test_dispatch_signal_triggers_parameterized_dynamic_route_event(app):\n    @app.signal(\"foo.bar.<baz:int>\")\n    def sync_signal(baz):\n        pass\n\n    app.signal_router.finalize()\n\n    event_task = asyncio.create_task(app.event(\"foo.bar.<baz:int>\"))\n    await app.dispatch(\"foo.bar.9\")\n    await asyncio.sleep(0)\n\n    assert event_task.done()\n    event_task.result()  # Will raise if there was an exception\n\n\n@pytest.mark.asyncio\nasync def test_dispatch_signal_triggers_starred_dynamic_route_event(app):\n    @app.signal(\"foo.bar.<baz:int>\")\n    def sync_signal(baz):\n        pass\n\n    app.signal_router.finalize()\n\n    event_task = asyncio.create_task(app.event(\"foo.bar.*\"))\n    await app.dispatch(\"foo.bar.9\")\n    await asyncio.sleep(0)\n\n    assert event_task.done()\n    event_task.result()  # Will raise if there was an exception\n\n\n@pytest.mark.asyncio\nasync def test_dispatch_signal_triggers_with_requirements(app):\n    counter = 0\n\n    @app.signal(\"foo.bar.baz\", condition={\"one\": \"two\"})\n    def sync_signal(*_):\n        nonlocal counter\n        counter += 1\n\n    app.signal_router.finalize()\n\n    await app.dispatch(\"foo.bar.baz\")\n    assert counter == 0\n    await app.dispatch(\"foo.bar.baz\", condition={\"one\": \"two\"})\n    assert counter == 1\n\n\n@pytest.mark.asyncio\nasync def test_dispatch_signal_to_event_with_requirements(app):\n    @app.signal(\"foo.bar.baz\")\n    def sync_signal(*_):\n        pass\n\n    app.signal_router.finalize()\n\n    event_task = asyncio.create_task(\n        app.event(\"foo.bar.baz\", condition={\"one\": \"two\"})\n    )\n    await app.dispatch(\"foo.bar.baz\")\n    await asyncio.sleep(0)\n    assert not event_task.done()\n\n    await app.dispatch(\"foo.bar.baz\", condition={\"one\": \"two\"})\n    await asyncio.sleep(0)\n    assert event_task.done()\n    event_task.result()  # Will raise if there was an exception\n\n\n@pytest.mark.asyncio\nasync def test_dispatch_signal_triggers_with_requirements_exclusive(app):\n    counter = 0\n\n    @app.signal(\"foo.bar.baz\", condition={\"one\": \"two\"}, exclusive=False)\n    def sync_signal(*_):\n        nonlocal counter\n        counter += 1\n\n    app.signal_router.finalize()\n\n    await app.dispatch(\"foo.bar.baz\")\n    assert counter == 1\n    await app.dispatch(\"foo.bar.baz\", condition={\"one\": \"two\"})\n    assert counter == 2\n\n\n@pytest.mark.asyncio\nasync def test_dispatch_signal_to_event_with_requirements_exclusive(app):\n    @app.signal(\"foo.bar.baz\")\n    def sync_signal(*_):\n        pass\n\n    app.signal_router.finalize()\n\n    event_task = asyncio.create_task(\n        app.event(\"foo.bar.baz\", condition={\"one\": \"two\"}, exclusive=False)\n    )\n    await app.dispatch(\"foo.bar.baz\")\n    await asyncio.sleep(0)\n    assert event_task.done()\n    event_task.result()  # Will raise if there was an exception\n\n    event_task = asyncio.create_task(\n        app.event(\"foo.bar.baz\", condition={\"one\": \"two\"}, exclusive=False)\n    )\n    await app.dispatch(\"foo.bar.baz\", condition={\"one\": \"two\"})\n    await asyncio.sleep(0)\n    assert event_task.done()\n    event_task.result()  # Will raise if there was an exception\n\n\n@pytest.mark.asyncio\nasync def test_dispatch_signal_triggers_with_context(app):\n    counter = 0\n\n    @app.signal(\"foo.bar.baz\")\n    def sync_signal(amount):\n        nonlocal counter\n        counter += amount\n\n    app.signal_router.finalize()\n\n    await app.dispatch(\"foo.bar.baz\", context={\"amount\": 9})\n    assert counter == 9\n\n\n@pytest.mark.asyncio\nasync def test_dispatch_signal_to_event_with_context(app):\n    @app.signal(\"foo.bar.baz\")\n    def sync_signal(**context):\n        pass\n\n    app.signal_router.finalize()\n\n    event_task = asyncio.create_task(app.event(\"foo.bar.baz\"))\n    await app.dispatch(\"foo.bar.baz\", context={\"amount\": 9})\n    await asyncio.sleep(0)\n    assert event_task.done()\n    assert event_task.result()[\"amount\"] == 9\n\n\n@pytest.mark.asyncio\nasync def test_dispatch_signal_triggers_with_context_fail(app):\n    counter = 0\n\n    @app.signal(\"foo.bar.baz\")\n    def sync_signal(amount):\n        nonlocal counter\n        counter += amount\n\n    app.signal_router.finalize()\n\n    with pytest.raises(TypeError):\n        await app.dispatch(\"foo.bar.baz\", {\"amount\": 9})\n\n\n@pytest.mark.asyncio\nasync def test_dispatch_signal_to_dynamic_route_event(app):\n    @app.signal(\"foo.bar.<something>\")\n    def sync_signal(**context):\n        pass\n\n    app.signal_router.finalize()\n\n    event_task = asyncio.create_task(app.event(\"foo.bar.<something>\"))\n    await app.dispatch(\"foo.bar.baz\")\n    await asyncio.sleep(0)\n    assert event_task.done()\n    assert event_task.result()[\"something\"] == \"baz\"\n\n\n@pytest.mark.asyncio\nasync def test_dispatch_signal_triggers_on_bp(app):\n    bp = Blueprint(\"bp\")\n\n    app_counter = 0\n    bp_counter = 0\n\n    @app.signal(\"foo.bar.baz\")\n    def app_signal():\n        nonlocal app_counter\n        app_counter += 1\n\n    @bp.signal(\"foo.bar.baz\")\n    def bp_signal():\n        nonlocal bp_counter\n        bp_counter += 1\n\n    app.blueprint(bp)\n    app.signal_router.finalize()\n\n    await app.dispatch(\"foo.bar.baz\")\n    assert app_counter == 1\n    assert bp_counter == 1\n\n    await bp.dispatch(\"foo.bar.baz\")\n    assert app_counter == 1\n    assert bp_counter == 2\n\n\n@pytest.mark.asyncio\nasync def test_dispatch_signal_triggers_on_bp_alone(app):\n    bp = Blueprint(\"bp\")\n\n    bp_counter = 0\n\n    @bp.signal(\"foo.bar.baz\")\n    def bp_signal():\n        nonlocal bp_counter\n        bp_counter += 1\n\n    app.blueprint(bp)\n    app.signal_router.finalize()\n    await app.dispatch(\"foo.bar.baz\")\n    await bp.dispatch(\"foo.bar.baz\")\n    assert bp_counter == 2\n\n\n@pytest.mark.asyncio\nasync def test_dispatch_signal_triggers_event_on_bp(app):\n    bp = Blueprint(\"bp\")\n\n    @app.signal(\"foo.bar.baz\")\n    def app_signal(): ...\n\n    @bp.signal(\"foo.bar.baz\")\n    def bp_signal(): ...\n\n    app.blueprint(bp)\n    app.signal_router.finalize()\n\n    app_task = asyncio.create_task(app.event(\"foo.bar.baz\"))\n    bp_task = asyncio.create_task(bp.event(\"foo.bar.baz\"))\n    await asyncio.sleep(0)\n    await app.dispatch(\"foo.bar.baz\")\n\n    # Allow a few event loop iterations for tasks to finish\n    for _ in range(5):\n        await asyncio.sleep(0)\n\n    assert app_task.done()\n    assert bp_task.done()\n    app_task.result()\n    bp_task.result()\n\n    app_task = asyncio.create_task(app.event(\"foo.bar.baz\"))\n    bp_task = asyncio.create_task(bp.event(\"foo.bar.baz\"))\n    await asyncio.sleep(0)\n    await bp.dispatch(\"foo.bar.baz\")\n\n    # Allow a few event loop iterations for tasks to finish\n    for _ in range(5):\n        await asyncio.sleep(0)\n\n    assert bp_task.done()\n    assert not app_task.done()\n    bp_task.result()\n    app_task.cancel()\n\n\n@pytest.mark.asyncio\nasync def test_dispatch_simple_signal_triggers(app):\n    counter = 0\n\n    @app.signal(\"foo\")\n    def sync_signal():\n        nonlocal counter\n\n        counter += 1\n\n    app.signal_router.finalize()\n\n    await app.dispatch(\"foo\")\n    assert counter == 1\n\n\n@pytest.mark.asyncio\nasync def test_dispatch_simple_signal_triggers_dynamic_foo(app):\n    counter = 0\n\n    @app.signal(\"<foo:int>\")\n    def sync_signal(foo):\n        nonlocal counter\n\n        counter += foo\n\n    app.signal_router.finalize()\n\n    await app.dispatch(\"9\")\n    assert counter == 9\n\n\n@pytest.mark.asyncio\nasync def test_dispatch_simple_signal_triggers_foo_bar(app):\n    counter = 0\n\n    @app.signal(\"foo.bar.<baz:int>\")\n    def sync_signal(baz):\n        nonlocal counter\n\n        counter += baz\n\n    app.signal_router.finalize()\n\n    await app.dispatch(\"foo.bar.9\")\n    assert counter == 9\n\n\n@pytest.mark.asyncio\nasync def test_dispatch_signal_triggers_event_on_bp_with_context(app):\n    bp = Blueprint(\"bp\")\n\n    @bp.signal(\"foo.bar.baz\")\n    def bp_signal(): ...\n\n    app.blueprint(bp)\n    app.signal_router.finalize()\n\n    event_task = asyncio.create_task(bp.event(\"foo.bar.baz\"))\n    await asyncio.sleep(0)\n    await app.dispatch(\"foo.bar.baz\", context={\"amount\": 9})\n    for _ in range(5):\n        await asyncio.sleep(0)\n    assert event_task.done()\n    assert event_task.result()[\"amount\"] == 9\n\n\ndef test_bad_finalize(app):\n    counter = 0\n\n    @app.signal(\"foo.bar.baz\")\n    def sync_signal(amount):\n        nonlocal counter\n        counter += amount\n\n    with pytest.raises(\n        RuntimeError, match=\"Cannot finalize signals outside of event loop\"\n    ):\n        app.signal_router.finalize()\n\n    assert counter == 0\n\n\n@pytest.mark.asyncio\nasync def test_event_not_exist(app):\n    with pytest.raises(NotFound, match=\"Could not find signal does.not.exist\"):\n        await app.event(\"does.not.exist\")\n\n\n@pytest.mark.asyncio\nasync def test_event_not_exist_on_bp(app):\n    bp = Blueprint(\"bp\")\n    app.blueprint(bp)\n\n    with pytest.raises(NotFound, match=\"Could not find signal does.not.exist\"):\n        await bp.event(\"does.not.exist\")\n\n\n@pytest.mark.asyncio\nasync def test_event_not_exist_with_autoregister(app):\n    app.config.EVENT_AUTOREGISTER = True\n    try:\n        await app.event(\"does.not.exist\", timeout=0.1)\n    except asyncio.TimeoutError:\n        ...\n\n\n@pytest.mark.asyncio\nasync def test_dispatch_signal_triggers_non_exist_event_with_autoregister(app):\n    @app.signal(\"some.stand.in\")\n    async def signal_handler(): ...\n\n    app.config.EVENT_AUTOREGISTER = True\n    app_counter = 0\n    app.signal_router.finalize()\n\n    async def do_wait():\n        nonlocal app_counter\n        await app.event(\"foo.bar.baz\")\n        app_counter += 1\n\n    fut = asyncio.ensure_future(do_wait())\n    await app.dispatch(\"foo.bar.baz\")\n    await fut\n\n    assert app_counter == 1\n\n\n@pytest.mark.asyncio\nasync def test_dispatch_not_exist(app):\n    @app.signal(\"do.something.start\")\n    async def signal_handler(): ...\n\n    app.signal_router.finalize()\n    await app.dispatch(\"does.not.exist\")\n\n\ndef test_event_on_bp_not_registered():\n    bp = Blueprint(\"bp\")\n\n    @bp.signal(\"foo.bar.baz\")\n    def bp_signal(): ...\n\n    with pytest.raises(\n        SanicException,\n        match=\"<Blueprint bp> has not yet been registered to an app\",\n    ):\n        bp.event(\"foo.bar.baz\")\n\n\n@pytest.mark.parametrize(\n    \"event,expected\",\n    (\n        (\"foo.bar.baz\", True),\n        (\"server.init.before\", True),\n        (\"server.init.somethingelse\", False),\n        (\"http.request.start\", False),\n        (\"sanic.notice.anything\", True),\n    ),\n)\ndef test_signal_reservation(app, event, expected):\n    if not expected:\n        with pytest.raises(\n            InvalidSignal,\n            match=f\"Cannot declare reserved signal event: {event}\",\n        ):\n            app.signal(event)(lambda: ...)\n    else:\n        app.signal(event)(lambda: ...)\n\n\n@pytest.mark.asyncio\nasync def test_report_exception(app: Sanic):\n    @app.report_exception\n    async def catch_any_exception(app: Sanic, exception: Exception): ...\n\n    @app.route(\"/\")\n    async def handler(request):\n        1 / 0\n\n    app.signal_router.finalize()\n\n    registered_signal_handlers = [\n        handler\n        for handler, *_ in app.signal_router.get(\n            Event.SERVER_EXCEPTION_REPORT.value\n        )\n    ]\n\n    assert catch_any_exception in registered_signal_handlers\n\n\ndef test_report_exception_runs(app: Sanic):\n    event = asyncio.Event()\n\n    @app.report_exception\n    async def catch_any_exception(app: Sanic, exception: Exception):\n        event.set()\n\n    @app.route(\"/\")\n    async def handler(request):\n        1 / 0\n\n    app.test_client.get(\"/\")\n\n    assert event.is_set()\n\n\ndef test_report_exception_runs_once_inline(app: Sanic):\n    event = asyncio.Event()\n    c = count()\n\n    @app.report_exception\n    async def catch_any_exception(app: Sanic, exception: Exception):\n        event.set()\n        next(c)\n\n    @app.route(\"/\")\n    async def handler(request): ...\n\n    @app.signal(Event.HTTP_ROUTING_AFTER.value)\n    async def after_routing(**_):\n        1 / 0\n\n    app.test_client.get(\"/\")\n\n    assert event.is_set()\n    assert next(c) == 1\n\n\ndef test_report_exception_runs_once_custom(app: Sanic):\n    event = asyncio.Event()\n    c = count()\n\n    @app.report_exception\n    async def catch_any_exception(app: Sanic, exception: Exception):\n        event.set()\n        next(c)\n\n    @app.route(\"/\")\n    async def handler(request):\n        await app.dispatch(\"one.two.three\")\n        return empty()\n\n    @app.signal(\"one.two.three\")\n    async def one_two_three(**_):\n        1 / 0\n\n    app.test_client.get(\"/\")\n\n    assert event.is_set()\n    assert next(c) == 1\n\n\ndef test_report_exception_runs_task(app: Sanic):\n    c = count()\n\n    async def task_1():\n        next(c)\n\n    async def task_2(app):\n        next(c)\n\n    @app.report_exception\n    async def catch_any_exception(app: Sanic, exception: Exception):\n        next(c)\n\n    @app.route(\"/\")\n    async def handler(request):\n        app.add_task(task_1)\n        app.add_task(task_1())\n        app.add_task(task_2)\n        app.add_task(task_2(app))\n        return empty()\n\n    app.test_client.get(\"/\")\n\n    assert next(c) == 4\n"
  },
  {
    "path": "tests/test_startup_errors.py",
    "content": "import errno\n\nimport pytest\n\nfrom sanic.exceptions import ServerError\nfrom sanic.startup.errors import (\n    _handle_os_error,\n    _handle_server_error,\n    maybe_handle_startup_error,\n)\n\n\ndef test_handle_os_error_address_in_use(caplog):\n    exc = OSError(errno.EADDRINUSE, \"Address already in use\")\n    result = _handle_os_error(exc)\n\n    assert result is True\n    assert \"address already in use\" in caplog.text.lower()\n    assert \"Ensure no other process\" in caplog.text\n\n\ndef test_handle_os_error_other_errno(caplog):\n    exc = OSError(13, \"Permission denied\")\n    exc.strerror = \"Permission denied\"\n    result = _handle_os_error(exc)\n\n    assert result is True\n    assert \"Permission denied\" in caplog.text\n    assert \"errno 13\" in caplog.text\n\n\ndef test_handle_os_error_non_os_error():\n    exc = ValueError(\"not an OS error\")\n    result = _handle_os_error(exc)\n\n    assert result is False\n\n\ndef test_handle_server_error(caplog):\n    exc = ServerError(\"Something went wrong\")\n    result = _handle_server_error(exc)\n\n    assert result is True\n    assert \"Startup failed due to server error\" in caplog.text\n    assert \"Something went wrong\" in caplog.text\n\n\ndef test_handle_server_error_non_server_error():\n    exc = ValueError(\"not a server error\")\n    result = _handle_server_error(exc)\n\n    assert result is False\n\n\ndef test_maybe_handle_startup_error_exits_on_os_error():\n    exc = OSError(errno.EADDRINUSE, \"Address already in use\")\n    with pytest.raises(SystemExit) as exc_info:\n        maybe_handle_startup_error(exc)\n\n    assert exc_info.value.code == 1\n\n\ndef test_maybe_handle_startup_error_exits_on_server_error():\n    exc = ServerError(\"test error\")\n    with pytest.raises(SystemExit) as exc_info:\n        maybe_handle_startup_error(exc)\n\n    assert exc_info.value.code == 1\n\n\ndef test_maybe_handle_startup_error_raises_unhandled():\n    exc = ValueError(\"unhandled error\")\n    with pytest.raises(ValueError, match=\"unhandled error\"):\n        maybe_handle_startup_error(exc)\n\n\ndef test_maybe_handle_startup_error_raises_runtime_error():\n    exc = RuntimeError(\"some runtime error\")\n    with pytest.raises(RuntimeError, match=\"some runtime error\"):\n        maybe_handle_startup_error(exc)\n"
  },
  {
    "path": "tests/test_static.py",
    "content": "import logging\nimport os\nimport sys\nfrom collections import Counter\nfrom pathlib import Path\nfrom time import gmtime, strftime\nfrom urllib.parse import unquote\n\nimport pytest\nfrom sanic import Sanic, text\nfrom sanic.exceptions import FileNotFound, ServerError\nfrom sanic.response import file\n\npytestmark = pytest.mark.xdist_group(name=\"static_files\")\n\n\n@pytest.fixture(scope=\"module\")\ndef double_dotted_directory_file(static_file_directory: str):\n    \"\"\"Generate double dotted directory and its files\"\"\"\n    if sys.platform == \"win32\":\n        raise Exception(\"Windows doesn't support double dotted directories\")\n\n    file_path = Path(static_file_directory) / \"dotted..\" / \"dot.txt\"\n    double_dotted_dir = file_path.parent\n    Path.mkdir(double_dotted_dir, exist_ok=True)\n    with open(file_path, \"w\") as f:\n        f.write(\"DOT\\n\")\n    yield file_path\n    Path.unlink(file_path)\n    Path.rmdir(double_dotted_dir)\n\n\ndef get_file_path(static_file_directory, file_name):\n    return os.path.join(static_file_directory, file_name)\n\n\ndef get_file_content(static_file_directory, file_name):\n    \"\"\"The content of the static file to check\"\"\"\n    with open(get_file_path(static_file_directory, file_name), \"rb\") as file:\n        return file.read()\n\n\n@pytest.fixture(scope=\"module\")\ndef large_file(static_file_directory):\n    large_file_path = os.path.join(static_file_directory, \"large.file\")\n\n    size = 2 * 1024 * 1024\n    with open(large_file_path, \"w\") as f:\n        f.write(\"a\" * size)\n\n    yield large_file_path\n\n    os.remove(large_file_path)\n\n\n@pytest.fixture(autouse=True, scope=\"module\")\ndef symlink(static_file_directory):\n    src = os.path.abspath(\n        os.path.join(os.path.dirname(static_file_directory), \"conftest.py\")\n    )\n    symlink = \"symlink\"\n    dist = os.path.join(static_file_directory, symlink)\n    if os.path.islink(dist):\n        os.remove(dist)\n    os.symlink(src, dist)\n    yield symlink\n    if os.path.exists(dist):\n        os.remove(dist)\n\n\n@pytest.fixture(autouse=True, scope=\"module\")\ndef hard_link(static_file_directory):\n    src = os.path.abspath(\n        os.path.join(os.path.dirname(static_file_directory), \"conftest.py\")\n    )\n    hard_link = \"hard_link\"\n    dist = os.path.join(static_file_directory, hard_link)\n    if os.path.exists(dist):\n        os.remove(dist)\n    os.link(src, dist)\n    yield hard_link\n    if os.path.exists(dist):\n        os.remove(dist)\n\n\n@pytest.mark.parametrize(\n    \"file_name\",\n    [\"test.file\", \"decode me.txt\", \"python.png\", \"symlink\", \"hard_link\"],\n)\ndef test_static_file(app, static_file_directory, file_name):\n    app.static(\n        \"/testing.file\", get_file_path(static_file_directory, file_name)\n    )\n\n    request, response = app.test_client.get(\"/testing.file\")\n    assert response.status == 200\n    assert response.body == get_file_content(static_file_directory, file_name)\n\n\n@pytest.mark.parametrize(\n    \"file_name\",\n    [\"test.file\", \"decode me.txt\", \"python.png\", \"symlink\", \"hard_link\"],\n)\ndef test_static_file_pathlib(app, static_file_directory, file_name):\n    file_path = Path(get_file_path(static_file_directory, file_name))\n    app.static(\"/testing.file\", file_path)\n    request, response = app.test_client.get(\"/testing.file\")\n    assert response.status == 200\n    assert response.body == get_file_content(static_file_directory, file_name)\n\n\n@pytest.mark.parametrize(\n    \"file_name\",\n    [\n        \"test.file\",\n        \"decode me.txt\",\n        \"python.png\",\n        \"symlink\",\n        \"hard_link\",\n    ],\n)\ndef test_static_file_pathlib_relative_path_traversal(\n    app, static_file_directory, file_name\n):\n    \"\"\"Get the current working directory and check if it ends with \"sanic\" \"\"\"\n    cwd = Path.cwd()\n    if not str(cwd).endswith(\"sanic\"):\n        pytest.skip(\"Current working directory does not end with 'sanic'\")\n\n    file_path = \"./tests/static/../static/\"\n    app.static(\"/\", file_path, follow_external_symlink_files=True)\n    _, response = app.test_client.get(f\"/{file_name}\")\n    assert response.status == 200\n    assert response.body == get_file_content(static_file_directory, file_name)\n\n\n@pytest.mark.parametrize(\n    \"file_name\",\n    [b\"test.file\", b\"decode me.txt\", b\"python.png\"],\n)\ndef test_static_file_bytes(app, static_file_directory, file_name):\n    bsep = os.path.sep.encode(\"utf-8\")\n    file_path = static_file_directory.encode(\"utf-8\") + bsep + file_name\n    message = \"Static file or directory must be a path-like object or string\"\n    with pytest.raises(TypeError, match=message):\n        app.static(\"/testing.file\", file_path)\n\n\n@pytest.mark.parametrize(\n    \"file_name\",\n    [{}, [], object()],\n)\ndef test_static_file_invalid_path(app, static_file_directory, file_name):\n    app.route(\"/\")(lambda x: x)\n    with pytest.raises(ValueError):\n        app.static(\"/testing.file\", file_name)\n    request, response = app.test_client.get(\"/testing.file\")\n    assert response.status == 404\n\n\n@pytest.mark.parametrize(\n    \"file_name,expected_content_type\",\n    [\n        (\"decode me.txt\", \"text/plain; charset=utf-8\"),\n        (\"test.html\", \"text/html; charset=utf-8\"),\n        (\"python.png\", \"image/png\"),\n        # Note: file() default for unknown types differs from app.static\n        (\"test.file\", \"text/plain; charset=utf-8\"),\n    ],\n)\ndef test_file_response_content_type(\n    app: Sanic, file_name, expected_content_type, static_file_directory\n):\n    \"\"\"Responses by file() rather than app.static.\"\"\"\n\n    @app.get(\"/files/<filename>\")\n    def file_route(request, filename):\n        file_path = os.path.join(static_file_directory, filename)\n        file_path = os.path.abspath(unquote(file_path))\n        return file(file_path)\n\n    request, response = app.test_client.get(f\"/files/{file_name}\")\n    assert response.status == 200\n    assert response.headers[\"Content-Type\"] == expected_content_type\n\n\n@pytest.mark.parametrize(\n    \"file_name,expected_content_type\",\n    [\n        (\"decode me.txt\", \"text/plain; charset=utf-8\"),\n        (\"test.html\", \"text/html; charset=utf-8\"),\n        (\"python.png\", \"image/png\"),\n        (\"test.file\", \"application/octet-stream\"),\n    ],\n)\ndef test_static_file_content_type(\n    app: Sanic, file_name, expected_content_type, static_file_directory\n):\n    \"\"\"Test that file responses automatically include charset for text.\"\"\"\n\n    app.static(\"/\", static_file_directory)\n    request, response = app.test_client.get(file_name)\n    assert response.status == 200\n    assert response.headers[\"Content-Type\"] == expected_content_type\n\n\ndef test_static_file_content_type_forced(app, static_file_directory):\n    app.static(\n        \"/testing.file\",\n        get_file_path(static_file_directory, \"decode me.txt\"),\n        content_type=\"text/plain;charset=ISO-8859-1\",\n    )\n\n    request, response = app.test_client.get(\"/testing.file\")\n    assert response.status == 200\n    assert response.headers[\"Content-Type\"] == \"text/plain;charset=ISO-8859-1\"\n\n\n@pytest.mark.parametrize(\n    \"file_name\", [\"test.file\", \"decode me.txt\", \"symlink\", \"hard_link\"]\n)\n@pytest.mark.parametrize(\"base_uri\", [\"/static\", \"\", \"/dir\"])\ndef test_static_directory(app, file_name, base_uri, static_file_directory):\n    app.static(\n        base_uri,\n        static_file_directory,\n        follow_external_symlink_files=(file_name == \"symlink\"),\n    )\n\n    request, response = app.test_client.get(uri=f\"{base_uri}/{file_name}\")\n    assert response.status == 200\n    assert response.body == get_file_content(static_file_directory, file_name)\n\n\n@pytest.mark.parametrize(\"file_name\", [\"test.file\", \"decode me.txt\"])\ndef test_static_head_request(app, file_name, static_file_directory):\n    app.static(\n        \"/testing.file\",\n        get_file_path(static_file_directory, file_name),\n        use_content_range=True,\n    )\n\n    request, response = app.test_client.head(\"/testing.file\")\n    assert response.status == 200\n    assert \"Accept-Ranges\" in response.headers\n    assert \"Content-Length\" in response.headers\n    assert int(response.headers[\"Content-Length\"]) == len(\n        get_file_content(static_file_directory, file_name)\n    )\n\n\n@pytest.mark.parametrize(\"file_name\", [\"test.file\", \"decode me.txt\"])\ndef test_static_content_range_correct(app, file_name, static_file_directory):\n    app.static(\n        \"/testing.file\",\n        get_file_path(static_file_directory, file_name),\n        use_content_range=True,\n    )\n\n    headers = {\"Range\": \"bytes=12-19\"}\n    request, response = app.test_client.get(\"/testing.file\", headers=headers)\n    assert response.status == 206\n    assert \"Content-Length\" in response.headers\n    assert \"Content-Range\" in response.headers\n    static_content = bytes(get_file_content(static_file_directory, file_name))[\n        12:20\n    ]\n    assert int(response.headers[\"Content-Length\"]) == len(static_content)\n    assert response.body == static_content\n\n\n@pytest.mark.parametrize(\"file_name\", [\"test.file\", \"decode me.txt\"])\ndef test_static_content_range_front(app, file_name, static_file_directory):\n    app.static(\n        \"/testing.file\",\n        get_file_path(static_file_directory, file_name),\n        use_content_range=True,\n    )\n\n    headers = {\"Range\": \"bytes=12-\"}\n    request, response = app.test_client.get(\"/testing.file\", headers=headers)\n    assert response.status == 206\n    assert \"Content-Length\" in response.headers\n    assert \"Content-Range\" in response.headers\n    static_content = bytes(get_file_content(static_file_directory, file_name))[\n        12:\n    ]\n    assert int(response.headers[\"Content-Length\"]) == len(static_content)\n    assert response.body == static_content\n\n\n@pytest.mark.parametrize(\"file_name\", [\"test.file\", \"decode me.txt\"])\ndef test_static_content_range_back(app, file_name, static_file_directory):\n    app.static(\n        \"/testing.file\",\n        get_file_path(static_file_directory, file_name),\n        use_content_range=True,\n    )\n\n    headers = {\"Range\": \"bytes=-12\"}\n    request, response = app.test_client.get(\"/testing.file\", headers=headers)\n    assert response.status == 206\n    assert \"Content-Length\" in response.headers\n    assert \"Content-Range\" in response.headers\n    static_content = bytes(get_file_content(static_file_directory, file_name))[\n        -12:\n    ]\n    assert int(response.headers[\"Content-Length\"]) == len(static_content)\n    assert response.body == static_content\n\n\n@pytest.mark.parametrize(\"use_modified_since\", [True, False])\n@pytest.mark.parametrize(\"file_name\", [\"test.file\", \"decode me.txt\"])\ndef test_static_content_range_empty(\n    app, file_name, static_file_directory, use_modified_since\n):\n    app.static(\n        \"/testing.file\",\n        get_file_path(static_file_directory, file_name),\n        use_content_range=True,\n        use_modified_since=use_modified_since,\n    )\n\n    request, response = app.test_client.get(\"/testing.file\")\n    assert response.status == 200\n    assert \"Content-Length\" in response.headers\n    assert \"Content-Range\" not in response.headers\n    assert int(response.headers[\"Content-Length\"]) == len(\n        get_file_content(static_file_directory, file_name)\n    )\n    assert response.body == bytes(\n        get_file_content(static_file_directory, file_name)\n    )\n\n\n@pytest.mark.parametrize(\"file_name\", [\"test.file\", \"decode me.txt\"])\ndef test_static_content_range_error(app, file_name, static_file_directory):\n    app.static(\n        \"/testing.file\",\n        get_file_path(static_file_directory, file_name),\n        use_content_range=True,\n    )\n\n    headers = {\"Range\": \"bytes=1-0\"}\n    request, response = app.test_client.get(\"/testing.file\", headers=headers)\n    assert response.status == 416\n    assert \"Content-Length\" in response.headers\n    assert \"Content-Range\" in response.headers\n    assert response.headers[\"Content-Range\"] == \"bytes */{}\".format(\n        len(get_file_content(static_file_directory, file_name)),\n    )\n\n\n@pytest.mark.parametrize(\"file_name\", [\"test.file\", \"decode me.txt\"])\ndef test_static_content_range_invalid_unit(\n    app, file_name, static_file_directory\n):\n    app.static(\n        \"/testing.file\",\n        get_file_path(static_file_directory, file_name),\n        use_content_range=True,\n    )\n\n    unit = \"bit\"\n    headers = {\"Range\": f\"{unit}=1-0\"}\n    request, response = app.test_client.get(\"/testing.file\", headers=headers)\n\n    assert response.status == 416\n    assert f\"{unit} is not a valid Range Type\" in response.text\n\n\n@pytest.mark.parametrize(\"file_name\", [\"test.file\", \"decode me.txt\"])\ndef test_static_content_range_invalid_start(\n    app, file_name, static_file_directory\n):\n    app.static(\n        \"/testing.file\",\n        get_file_path(static_file_directory, file_name),\n        use_content_range=True,\n    )\n\n    start = \"start\"\n    headers = {\"Range\": f\"bytes={start}-0\"}\n    request, response = app.test_client.get(\"/testing.file\", headers=headers)\n\n    assert response.status == 416\n    assert f\"'{start}' is invalid for Content Range\" in response.text\n\n\n@pytest.mark.parametrize(\"file_name\", [\"test.file\", \"decode me.txt\"])\ndef test_static_content_range_invalid_end(\n    app, file_name, static_file_directory\n):\n    app.static(\n        \"/testing.file\",\n        get_file_path(static_file_directory, file_name),\n        use_content_range=True,\n    )\n\n    end = \"end\"\n    headers = {\"Range\": f\"bytes=1-{end}\"}\n    request, response = app.test_client.get(\"/testing.file\", headers=headers)\n\n    assert response.status == 416\n    assert f\"'{end}' is invalid for Content Range\" in response.text\n\n\n@pytest.mark.parametrize(\"file_name\", [\"test.file\", \"decode me.txt\"])\ndef test_static_content_range_invalid_parameters(\n    app, file_name, static_file_directory\n):\n    app.static(\n        \"/testing.file\",\n        get_file_path(static_file_directory, file_name),\n        use_content_range=True,\n    )\n\n    headers = {\"Range\": \"bytes=-\"}\n    request, response = app.test_client.get(\"/testing.file\", headers=headers)\n\n    assert response.status == 416\n    assert \"Invalid for Content Range parameters\" in response.text\n\n\n@pytest.mark.parametrize(\n    \"file_name\", [\"test.file\", \"decode me.txt\", \"python.png\"]\n)\ndef test_static_file_specified_host(app, static_file_directory, file_name):\n    app.static(\n        \"/testing.file\",\n        get_file_path(static_file_directory, file_name),\n        host=\"www.example.com\",\n    )\n\n    headers = {\"Host\": \"www.example.com\"}\n    request, response = app.test_client.get(\"/testing.file\", headers=headers)\n    assert response.status == 200\n    assert response.body == get_file_content(static_file_directory, file_name)\n    request, response = app.test_client.get(\"/testing.file\")\n    assert response.status == 404\n\n\n@pytest.mark.parametrize(\"use_modified_since\", [True, False])\n@pytest.mark.parametrize(\"stream_large_files\", [True, 1024])\n@pytest.mark.parametrize(\"file_name\", [\"test.file\", \"large.file\"])\ndef test_static_stream_large_file(\n    app,\n    static_file_directory,\n    file_name,\n    use_modified_since,\n    stream_large_files,\n    large_file,\n):\n    app.static(\n        \"/testing.file\",\n        get_file_path(static_file_directory, file_name),\n        use_modified_since=use_modified_since,\n        stream_large_files=stream_large_files,\n    )\n\n    request, response = app.test_client.get(\"/testing.file\")\n\n    assert response.status == 200\n    assert response.body == get_file_content(static_file_directory, file_name)\n\n\n@pytest.mark.parametrize(\n    \"file_name\", [\"test.file\", \"decode me.txt\", \"python.png\"]\n)\ndef test_use_modified_since(app, static_file_directory, file_name):\n    file_stat = os.stat(get_file_path(static_file_directory, file_name))\n    modified_since = strftime(\n        \"%a, %d %b %Y %H:%M:%S GMT\", gmtime(file_stat.st_mtime)\n    )\n\n    app.static(\n        \"/testing.file\",\n        get_file_path(static_file_directory, file_name),\n        use_modified_since=True,\n    )\n\n    request, response = app.test_client.get(\n        \"/testing.file\", headers={\"If-Modified-Since\": modified_since}\n    )\n\n    assert response.status == 304\n\n\ndef test_file_not_found(app, static_file_directory):\n    app.static(\"/static\", static_file_directory)\n\n    request, response = app.test_client.get(\"/static/not_found\")\n\n    assert response.status == 404\n    assert \"File not found\" in response.text\n\n\n@pytest.mark.parametrize(\"static_name\", [\"_static_name\", \"static\"])\n@pytest.mark.parametrize(\"file_name\", [\"test.html\"])\ndef test_static_name(app, static_file_directory, static_name, file_name):\n    app.static(\"/static\", static_file_directory, name=static_name)\n\n    request, response = app.test_client.get(f\"/static/{file_name}\")\n\n    assert response.status == 200\n\n\ndef test_nested_dir(app, static_file_directory):\n    app.static(\"/static\", static_file_directory)\n\n    request, response = app.test_client.get(\"/static/nested/dir/foo.txt\")\n\n    assert response.status == 200\n    assert response.text == \"foo\\n\"\n\n\ndef test_handle_is_a_directory_error(app, static_file_directory):\n    error_text = \"Is a directory. Access denied\"\n    app.static(\"/static\", static_file_directory)\n\n    @app.exception(Exception)\n    async def handleStaticDirError(request, exception):\n        if isinstance(exception, IsADirectoryError):\n            return text(error_text, status=403)\n        raise exception\n\n    request, response = app.test_client.get(\"/static/\")\n\n    assert response.status == 403\n    assert response.text == error_text\n\n\ndef test_stack_trace_on_not_found(app, static_file_directory, caplog):\n    app.static(\"/static\", static_file_directory)\n\n    with caplog.at_level(logging.INFO):\n        _, response = app.test_client.get(\"/static/non_existing_file.file\")\n\n    counter = Counter([(r[0], r[1]) for r in caplog.record_tuples])\n\n    assert response.status == 404\n    assert counter[(\"sanic.root\", logging.INFO)] == 10\n    assert counter[(\"sanic.root\", logging.ERROR)] == 0\n    assert counter[(\"sanic.error\", logging.ERROR)] == 0\n    assert counter[(\"sanic.server\", logging.INFO)] == 4\n\n\ndef test_no_stack_trace_on_not_found(app, static_file_directory, caplog):\n    app.static(\"/static\", static_file_directory)\n\n    @app.exception(FileNotFound)\n    async def file_not_found(request, exception):\n        return text(f\"No file: {request.path}\", status=404)\n\n    with caplog.at_level(logging.INFO):\n        _, response = app.test_client.get(\"/static/non_existing_file.file\")\n\n    counter = Counter([(r[0], r[1]) for r in caplog.record_tuples])\n\n    assert response.status == 404\n    assert counter[(\"sanic.root\", logging.INFO)] == 10\n    assert counter[(\"sanic.root\", logging.ERROR)] == 0\n    assert counter[(\"sanic.error\", logging.ERROR)] == 0\n    assert counter[(\"sanic.server\", logging.INFO)] == 4\n    assert response.text == \"No file: /static/non_existing_file.file\"\n\n\n@pytest.mark.asyncio\nasync def test_multiple_statics_error(app, static_file_directory):\n    app.static(\"/file\", get_file_path(static_file_directory, \"test.file\"))\n    app.static(\"/png\", get_file_path(static_file_directory, \"python.png\"))\n\n    message = (\n        r\"Duplicate route names detected: test_multiple_statics_error\\.static\"\n    )\n    with pytest.raises(ServerError, match=message):\n        await app._startup()\n\n\ndef test_multiple_statics(app, static_file_directory):\n    app.static(\n        \"/file\", get_file_path(static_file_directory, \"test.file\"), name=\"file\"\n    )\n    app.static(\n        \"/png\", get_file_path(static_file_directory, \"python.png\"), name=\"png\"\n    )\n\n    _, response = app.test_client.get(\"/file\")\n    assert response.status == 200\n    assert response.body == get_file_content(\n        static_file_directory, \"test.file\"\n    )\n\n    _, response = app.test_client.get(\"/png\")\n    assert response.status == 200\n    assert response.body == get_file_content(\n        static_file_directory, \"python.png\"\n    )\n\n\n@pytest.mark.asyncio\nasync def test_resource_type_default_error(app, static_file_directory):\n    app.static(\"/static\", static_file_directory)\n    app.static(\"/file\", get_file_path(static_file_directory, \"test.file\"))\n\n    message = (\n        r\"Duplicate route names \"\n        r\"detected: test_resource_type_default_error\\.static\"\n    )\n    with pytest.raises(ServerError, match=message):\n        await app._startup()\n\n\ndef test_resource_type_default(app, static_file_directory):\n    app.static(\"/static\", static_file_directory, name=\"static\")\n    app.static(\n        \"/file\", get_file_path(static_file_directory, \"test.file\"), name=\"file\"\n    )\n\n    _, response = app.test_client.get(\"/static\")\n    assert response.status == 404\n\n    _, response = app.test_client.get(\"/file\")\n    assert response.status == 200\n    assert response.body == get_file_content(\n        static_file_directory, \"test.file\"\n    )\n\n\ndef test_resource_type_file(app, static_file_directory):\n    app.static(\n        \"/file\",\n        get_file_path(static_file_directory, \"test.file\"),\n        resource_type=\"file\",\n    )\n\n    _, response = app.test_client.get(\"/file\")\n    assert response.status == 200\n    assert response.body == get_file_content(\n        static_file_directory, \"test.file\"\n    )\n\n    with pytest.raises(TypeError):\n        app.static(\"/static\", static_file_directory, resource_type=\"file\")\n\n\ndef test_resource_type_dir(app, static_file_directory):\n    app.static(\"/static\", static_file_directory, resource_type=\"dir\")\n\n    _, response = app.test_client.get(\"/static/test.file\")\n    assert response.status == 200\n    assert response.body == get_file_content(\n        static_file_directory, \"test.file\"\n    )\n\n    with pytest.raises(TypeError):\n        app.static(\n            \"/file\",\n            get_file_path(static_file_directory, \"test.file\"),\n            resource_type=\"dir\",\n        )\n\n\ndef test_resource_type_unknown(app, static_file_directory, caplog):\n    with pytest.raises(ValueError):\n        app.static(\"/static\", static_file_directory, resource_type=\"unknown\")\n\n\n@pytest.mark.skipif(\n    sys.platform == \"win32\",\n    reason=\"Windows does not support double dotted directories\",\n)\ndef test_dotted_dir_ok(\n    app: Sanic, static_file_directory: str, double_dotted_directory_file: Path\n):\n    app.static(\"/foo\", static_file_directory)\n    dot_relative_path = str(\n        double_dotted_directory_file.relative_to(static_file_directory)\n    )\n    _, response = app.test_client.get(\"/foo/\" + dot_relative_path)\n    assert response.status == 200\n    assert response.body == b\"DOT\\n\"\n\n\ndef test_breakout(app: Sanic, static_file_directory: str):\n    app.static(\"/foo\", static_file_directory)\n\n    _, response = app.test_client.get(\"/foo/..%2Ffake/server.py\")\n    assert response.status == 404\n\n    _, response = app.test_client.get(\"/foo/..%2Fstatic/test.file\")\n    assert response.status == 404\n\n\n@pytest.mark.skipif(\n    sys.platform != \"win32\", reason=\"Block backslash on Windows only\"\n)\ndef test_double_backslash_prohibited_on_win32(\n    app: Sanic, static_file_directory: str\n):\n    app.static(\"/foo\", static_file_directory)\n\n    _, response = app.test_client.get(\"/foo/static/..\\\\static/test.file\")\n    assert response.status == 404\n    _, response = app.test_client.get(\"/foo/static\\\\../static/test.file\")\n    assert response.status == 404\n"
  },
  {
    "path": "tests/test_static_directory.py",
    "content": "import os\nimport tempfile\n\nfrom pathlib import Path\n\nimport pytest\n\nfrom sanic import Sanic\nfrom sanic.handlers.directory import DirectoryHandler\n\n\npytestmark = pytest.mark.xdist_group(name=\"static_files\")\n\n\ndef get_file_path(static_file_directory, file_name):\n    return os.path.join(static_file_directory, file_name)\n\n\ndef get_file_content(static_file_directory, file_name):\n    \"\"\"The content of the static file to check\"\"\"\n    with open(get_file_path(static_file_directory, file_name), \"rb\") as file:\n        return file.read()\n\n\ndef test_static_directory_view(app: Sanic, static_file_directory: str):\n    app.static(\"/static\", static_file_directory, directory_view=True)\n\n    _, response = app.test_client.get(\"/static/\")\n    assert response.status == 200\n    assert response.content_type == \"text/html; charset=utf-8\"\n    assert \"<title>Directory Viewer</title>\" in response.text\n\n\ndef test_static_index_single(app: Sanic, static_file_directory: str):\n    app.static(\"/static\", static_file_directory, index=\"test.html\")\n\n    _, response = app.test_client.get(\"/static/\")\n    assert response.status == 200\n    assert response.body == get_file_content(\n        static_file_directory, \"test.html\"\n    )\n    assert response.headers[\"Content-Type\"] == \"text/html; charset=utf-8\"\n\n\ndef test_static_index_single_not_found(app: Sanic, static_file_directory: str):\n    app.static(\"/static\", static_file_directory, index=\"index.html\")\n\n    _, response = app.test_client.get(\"/static/\")\n    assert response.status == 404\n\n\ndef test_static_index_multiple(app: Sanic, static_file_directory: str):\n    app.static(\n        \"/static\",\n        static_file_directory,\n        index=[\"index.html\", \"test.html\"],\n    )\n\n    _, response = app.test_client.get(\"/static/\")\n    assert response.status == 200\n    assert response.body == get_file_content(\n        static_file_directory, \"test.html\"\n    )\n    assert response.headers[\"Content-Type\"] == \"text/html; charset=utf-8\"\n\n\ndef test_static_directory_view_and_index(\n    app: Sanic, static_file_directory: str\n):\n    app.static(\n        \"/static\",\n        static_file_directory,\n        directory_view=True,\n        index=\"foo.txt\",\n    )\n\n    _, response = app.test_client.get(\"/static/nested/\")\n    assert response.status == 200\n    assert response.content_type == \"text/html; charset=utf-8\"\n    assert \"<title>Directory Viewer</title>\" in response.text\n\n    _, response = app.test_client.get(\"/static/nested/dir/\")\n    assert response.status == 200\n    assert response.body == get_file_content(\n        f\"{static_file_directory}/nested/dir\", \"foo.txt\"\n    )\n    assert response.content_type == \"text/plain; charset=utf-8\"\n\n\ndef test_static_directory_handler(app: Sanic, static_file_directory: str):\n    dh = DirectoryHandler(\n        \"/static\",\n        Path(static_file_directory),\n        directory_view=True,\n        index=\"foo.txt\",\n    )\n    app.static(\"/static\", static_file_directory, directory_handler=dh)\n\n    _, response = app.test_client.get(\"/static/nested/\")\n    assert response.status == 200\n    assert response.content_type == \"text/html; charset=utf-8\"\n    assert \"<title>Directory Viewer</title>\" in response.text\n\n    _, response = app.test_client.get(\"/static/nested/dir/\")\n    assert response.status == 200\n    assert response.body == get_file_content(\n        f\"{static_file_directory}/nested/dir\", \"foo.txt\"\n    )\n    assert response.content_type == \"text/plain; charset=utf-8\"\n\n\ndef test_static_directory_handler_fails(app: Sanic):\n    dh = DirectoryHandler(\n        \"/static\",\n        Path(\"\"),\n        directory_view=True,\n        index=\"foo.txt\",\n    )\n    message = (\n        \"When explicitly setting directory_handler, you cannot \"\n        \"set either directory_view or index. Instead, pass \"\n        \"these arguments to your DirectoryHandler instance.\"\n    )\n    with pytest.raises(ValueError, match=message):\n        app.static(\"/static\", \"\", directory_handler=dh, directory_view=True)\n    with pytest.raises(ValueError, match=message):\n        app.static(\"/static\", \"\", directory_handler=dh, index=\"index.html\")\n\n\n@pytest.fixture\ndef symlink_test_directory(tmp_path):\n    static_root = tmp_path / \"static\"\n    static_root.mkdir()\n\n    (static_root / \"normal_file.txt\").write_text(\"normal content\")\n\n    subdir = static_root / \"subdir\"\n    subdir.mkdir()\n    (subdir / \"sub_file.txt\").write_text(\"sub content\")\n\n    outside_dir = tmp_path / \"outside\"\n    outside_dir.mkdir()\n    secret_file = outside_dir / \"secret.txt\"\n    secret_file.write_text(\"secret content\")\n\n    symlink_to_outside_file = static_root / \"link_to_secret\"\n    symlink_to_outside_file.symlink_to(secret_file)\n\n    symlink_to_outside_dir = static_root / \"link_to_outside_dir\"\n    symlink_to_outside_dir.symlink_to(outside_dir)\n\n    symlink_to_inside = static_root / \"link_to_subdir\"\n    symlink_to_inside.symlink_to(subdir)\n\n    broken_symlink = static_root / \"broken_link\"\n    broken_symlink.symlink_to(tmp_path / \"nonexistent\")\n\n    return static_root\n\n\ndef test_directory_view_hides_symlinks_outside_root(\n    app: Sanic, symlink_test_directory: Path\n):\n    app.static(\"/static\", symlink_test_directory, directory_view=True)\n\n    _, response = app.test_client.get(\"/static/\")\n    assert response.status == 200\n\n    assert \"normal_file.txt\" in response.text\n    assert \"link_to_subdir\" in response.text\n    assert \"link_to_secret\" not in response.text\n    assert \"link_to_outside_dir\" not in response.text\n    assert \"broken_link\" not in response.text\n\n\ndef test_directory_view_broken_symlink_no_crash(\n    app: Sanic, symlink_test_directory: Path\n):\n    app.static(\"/static\", symlink_test_directory, directory_view=True)\n\n    _, response = app.test_client.get(\"/static/\")\n    assert response.status == 200\n\n\ndef test_symlink_inside_root_visible(app: Sanic, symlink_test_directory: Path):\n    app.static(\"/static\", symlink_test_directory, directory_view=True)\n\n    _, response = app.test_client.get(\"/static/\")\n    assert response.status == 200\n    assert \"link_to_subdir\" in response.text\n\n    _, response = app.test_client.get(\"/static/link_to_subdir/\")\n    assert response.status == 200\n    assert \"sub_file.txt\" in response.text\n\n\ndef test_symlink_to_outside_file_returns_404(\n    app: Sanic, symlink_test_directory: Path\n):\n    app.static(\"/static\", symlink_test_directory)\n\n    _, response = app.test_client.get(\"/static/link_to_secret\")\n    assert response.status == 404\n\n\ndef test_symlink_to_outside_dir_returns_404(\n    app: Sanic, symlink_test_directory: Path\n):\n    app.static(\"/static\", symlink_test_directory)\n\n    _, response = app.test_client.get(\"/static/link_to_outside_dir/secret.txt\")\n    assert response.status == 404\n\n\n@pytest.mark.parametrize(\n    \"follow_files,follow_dirs,path,expected_status\",\n    [\n        # Normal file - always accessible\n        (False, False, \"/static/normal_file.txt\", 200),\n        (True, False, \"/static/normal_file.txt\", 200),\n        (False, True, \"/static/normal_file.txt\", 200),\n        (True, True, \"/static/normal_file.txt\", 200),\n        # Symlink to file inside root - always accessible\n        (False, False, \"/static/link_to_subdir/sub_file.txt\", 200),\n        (True, False, \"/static/link_to_subdir/sub_file.txt\", 200),\n        (False, True, \"/static/link_to_subdir/sub_file.txt\", 200),\n        (True, True, \"/static/link_to_subdir/sub_file.txt\", 200),\n        # Symlink to file outside root - only with follow_files=True\n        (False, False, \"/static/link_to_secret\", 404),\n        (True, False, \"/static/link_to_secret\", 200),\n        (False, True, \"/static/link_to_secret\", 404),\n        (True, True, \"/static/link_to_secret\", 200),\n        # File in symlinked dir outside root - only with follow_dirs=True\n        (False, False, \"/static/link_to_outside_dir/secret.txt\", 404),\n        (True, False, \"/static/link_to_outside_dir/secret.txt\", 404),\n        (False, True, \"/static/link_to_outside_dir/secret.txt\", 200),\n        (True, True, \"/static/link_to_outside_dir/secret.txt\", 200),\n        # Broken symlink - always 404\n        (False, False, \"/static/broken_link\", 404),\n        (True, False, \"/static/broken_link\", 404),\n        (False, True, \"/static/broken_link\", 404),\n        (True, True, \"/static/broken_link\", 404),\n    ],\n)\ndef test_symlink_serving_permutations(\n    app: Sanic,\n    symlink_test_directory: Path,\n    follow_files: bool,\n    follow_dirs: bool,\n    path: str,\n    expected_status: int,\n):\n    app.static(\n        \"/static\",\n        symlink_test_directory,\n        follow_external_symlink_files=follow_files,\n        follow_external_symlink_dirs=follow_dirs,\n    )\n\n    _, response = app.test_client.get(path)\n    assert response.status == expected_status\n\n\n@pytest.mark.parametrize(\n    \"follow_files,follow_dirs,item,expected_visible\",\n    [\n        # Normal file - always visible\n        (False, False, \"normal_file.txt\", True),\n        (True, False, \"normal_file.txt\", True),\n        (False, True, \"normal_file.txt\", True),\n        (True, True, \"normal_file.txt\", True),\n        # Subdir - always visible\n        (False, False, \"subdir\", True),\n        (True, False, \"subdir\", True),\n        (False, True, \"subdir\", True),\n        (True, True, \"subdir\", True),\n        # Symlink to dir inside root - always visible\n        (False, False, \"link_to_subdir\", True),\n        (True, False, \"link_to_subdir\", True),\n        (False, True, \"link_to_subdir\", True),\n        (True, True, \"link_to_subdir\", True),\n        # Symlink to file outside root - only with follow_files=True\n        (False, False, \"link_to_secret\", False),\n        (True, False, \"link_to_secret\", True),\n        (False, True, \"link_to_secret\", False),\n        (True, True, \"link_to_secret\", True),\n        # Symlink to dir outside root - only with follow_dirs=True\n        (False, False, \"link_to_outside_dir\", False),\n        (True, False, \"link_to_outside_dir\", False),\n        (False, True, \"link_to_outside_dir\", True),\n        (True, True, \"link_to_outside_dir\", True),\n        # Broken symlink - never visible\n        (False, False, \"broken_link\", False),\n        (True, False, \"broken_link\", False),\n        (False, True, \"broken_link\", False),\n        (True, True, \"broken_link\", False),\n    ],\n)\ndef test_directory_view_visibility_permutations(\n    app: Sanic,\n    symlink_test_directory: Path,\n    follow_files: bool,\n    follow_dirs: bool,\n    item: str,\n    expected_visible: bool,\n):\n    app.static(\n        \"/static\",\n        symlink_test_directory,\n        directory_view=True,\n        follow_external_symlink_files=follow_files,\n        follow_external_symlink_dirs=follow_dirs,\n    )\n\n    _, response = app.test_client.get(\"/static/\")\n    assert response.status == 200\n\n    if expected_visible:\n        assert item in response.text\n    else:\n        assert item not in response.text\n\n\n@pytest.mark.parametrize(\n    \"dir_name\",\n    [\n        \"你好\",  # Chinese\n        \"こんにちは\",  # Japanese\n        \"안녕하세요\",  # Korean\n        \"hello 世界\",  # Mixed ASCII and CJK\n    ],\n)\ndef test_static_index_with_cjk_directory_name(app: Sanic, dir_name: str):\n    \"\"\"Test static file serving with CJK characters in directory names.\n\n    See: https://github.com/sanic-org/sanic/issues/3008\n    \"\"\"\n    with tempfile.TemporaryDirectory() as tmpdir:\n        # Create directory with CJK name containing an index file\n        cjk_dir = Path(tmpdir) / dir_name\n        cjk_dir.mkdir()\n        index_file = cjk_dir / \"index.html\"\n        index_content = b\"<html>Hello from CJK directory</html>\"\n        index_file.write_bytes(index_content)\n\n        app.static(\"/static\", tmpdir, index=\"index.html\")\n\n        # Access the CJK directory - should serve index.html\n        _, response = app.test_client.get(f\"/static/{dir_name}/\")\n        assert response.status == 200\n        assert response.body == index_content\n"
  },
  {
    "path": "tests/test_tasks.py",
    "content": "import asyncio\n\nfrom asyncio.tasks import Task\nfrom unittest.mock import Mock, call\n\nimport pytest\n\nfrom sanic.app import Sanic\nfrom sanic.application.state import ApplicationServerInfo, ServerStage\nfrom sanic.response import empty\n\n\ntry:\n    from unittest.mock import AsyncMock\nexcept ImportError:\n    from tests.asyncmock import AsyncMock  # type: ignore\n\npytestmark = pytest.mark.asyncio\n\n\nasync def dummy(n=0):\n    for _ in range(n):\n        await asyncio.sleep(1)\n    return True\n\n\n@pytest.fixture(autouse=True)\ndef mark_app_running(app: Sanic):\n    app.state.server_info.append(\n        ApplicationServerInfo(\n            stage=ServerStage.SERVING, settings={}, server=AsyncMock()\n        )\n    )\n\n\nasync def test_add_task_returns_task(app: Sanic):\n    task = app.add_task(dummy())\n\n    assert isinstance(task, Task)\n    assert len(app._task_registry) == 0\n\n\nasync def test_add_task_with_name(app: Sanic):\n    task = app.add_task(dummy(), name=\"dummy\")\n\n    assert isinstance(task, Task)\n    assert len(app._task_registry) == 1\n    assert task is app.get_task(\"dummy\")\n\n    for task in app.tasks:\n        assert task in app._task_registry.values()\n\n\nasync def test_cancel_task(app: Sanic):\n    task = app.add_task(dummy(3), name=\"dummy\")\n\n    assert task\n    assert not task.done()\n    assert not task.cancelled()\n\n    await asyncio.sleep(0.1)\n\n    assert not task.done()\n    assert not task.cancelled()\n\n    await app.cancel_task(\"dummy\")\n\n    assert task.cancelled()\n\n\nasync def test_purge_tasks(app: Sanic):\n    app.add_task(dummy(3), name=\"dummy\")\n\n    await app.cancel_task(\"dummy\")\n\n    assert len(app._task_registry) == 1\n\n    app.purge_tasks()\n\n    assert len(app._task_registry) == 0\n\n\nasync def test_purge_tasks_with_create_task(app: Sanic):\n    app.add_task(asyncio.create_task(dummy(3)), name=\"dummy\")\n\n    await app.cancel_task(\"dummy\")\n\n    assert len(app._task_registry) == 1\n\n    app.purge_tasks()\n\n    assert len(app._task_registry) == 0\n\n\ndef test_shutdown_tasks_on_app_stop():\n    class TestSanic(Sanic):\n        shutdown_tasks = Mock()\n\n    app = TestSanic(\"Test\")\n\n    @app.route(\"/\")\n    async def handler(_):\n        return empty()\n\n    app.test_client.get(\"/\")\n\n    app.shutdown_tasks.call_args == [\n        call(timeout=0),\n        call(15.0),\n    ]\n\n\nasync def test_task_result_is_preserved(app: Sanic):\n    async def return_value(x):\n        await asyncio.sleep(0)\n        return x\n\n    task = app.add_task(return_value(42), name=\"return_42\")\n\n    result = await task\n\n    assert result == 42\n    assert task.result() == 42\n\n\nasync def test_task_result_with_callable(app: Sanic):\n    async def coro_with_app(app):\n        await asyncio.sleep(0)\n        return app.name\n\n    task = app.add_task(coro_with_app, name=\"return_app_name\")\n\n    result = await task\n\n    assert result == app.name\n    assert task.result() == app.name\n"
  },
  {
    "path": "tests/test_test_client_port.py",
    "content": "from sanic_testing.testing import PORT, SanicTestClient\n\nfrom sanic.response import json, text\n\n\n# ------------------------------------------------------------ #\n#  UTF-8\n# ------------------------------------------------------------ #\n\n\ndef test_test_client_port_none(app):\n    @app.get(\"/get\")\n    def handler(request):\n        return text(\"OK\")\n\n    test_client = SanicTestClient(app, port=None)\n\n    request, response = test_client.get(\"/get\")\n    assert response.text == \"OK\"\n\n    request, response = test_client.post(\"/get\")\n    assert response.status == 405\n\n\ndef test_test_client_port_default(app):\n    @app.get(\"/get\")\n    def handler(request):\n        return json(request.transport.get_extra_info(\"sockname\")[1])\n\n    test_client = SanicTestClient(app)\n    assert test_client.port == PORT  # Can be None before request\n\n    request, response = test_client.get(\"/get\")\n    assert test_client.port > 0\n    assert response.json == test_client.port\n"
  },
  {
    "path": "tests/test_timeout_logic.py",
    "content": "import asyncio\n\nfrom unittest.mock import Mock\n\nimport pytest\n\nfrom sanic import Sanic\nfrom sanic.exceptions import RequestTimeout, ServiceUnavailable\nfrom sanic.http import Stage\nfrom sanic.server import HttpProtocol\n\n\n@pytest.fixture\ndef app():\n    return Sanic(\"test\")\n\n\n@pytest.fixture\ndef mock_transport():\n    return Mock()\n\n\n@pytest.fixture\ndef protocol(app, mock_transport):\n    loop = asyncio.new_event_loop()\n    protocol = HttpProtocol(loop=loop, app=app)\n    protocol.connection_made(mock_transport)\n    protocol._setup_connection()\n    protocol._http.init_for_request()\n    protocol._task = Mock(spec=asyncio.Task)\n    protocol._task.cancel = Mock()\n    return protocol\n\n\ndef test_setup(protocol: HttpProtocol):\n    assert protocol._task is not None\n    assert protocol._http is not None\n    assert protocol._time is not None\n\n\ndef test_check_timeouts_no_timeout(protocol: HttpProtocol):\n    protocol.keep_alive_timeout = 1\n    protocol.loop.call_later = Mock()\n    protocol.check_timeouts()\n    protocol._task.cancel.assert_not_called()\n    assert protocol._http.stage is Stage.IDLE\n    assert protocol._http.exception is None\n    protocol.loop.call_later.assert_called_with(\n        protocol.keep_alive_timeout / 2, protocol.check_timeouts\n    )\n\n\ndef test_check_timeouts_keep_alive_timeout(protocol: HttpProtocol):\n    protocol._http.stage = Stage.IDLE\n    protocol._time = 0\n    protocol.check_timeouts()\n    protocol._task.cancel.assert_called_once()\n    assert protocol._http.exception is None\n\n\ndef test_check_timeouts_request_timeout(protocol: HttpProtocol):\n    protocol._http.stage = Stage.REQUEST\n    protocol._time = 0\n    protocol.check_timeouts()\n    protocol._task.cancel.assert_called_once()\n    assert isinstance(protocol._http.exception, RequestTimeout)\n\n\ndef test_check_timeouts_response_timeout(protocol: HttpProtocol):\n    protocol._http.stage = Stage.RESPONSE\n    protocol._time = 0\n    protocol.check_timeouts()\n    protocol._task.cancel.assert_called_once()\n    assert isinstance(protocol._http.exception, ServiceUnavailable)\n"
  },
  {
    "path": "tests/test_tls.py",
    "content": "import logging\nimport os\nimport ssl\nimport subprocess\nimport sys\n\nfrom contextlib import contextmanager\nfrom multiprocessing import Event\nfrom pathlib import Path\nfrom unittest.mock import Mock, patch\nfrom urllib.parse import urlparse\n\nimport pytest\n\nfrom sanic_testing.testing import HOST, PORT, SanicTestClient\n\nimport sanic.http.tls.creators\n\nfrom sanic import Sanic\nfrom sanic.application.constants import Mode\nfrom sanic.compat import use_context\nfrom sanic.constants import LocalCertCreator\nfrom sanic.exceptions import SanicException\nfrom sanic.helpers import _default\nfrom sanic.http.tls.context import SanicSSLContext\nfrom sanic.http.tls.creators import (\n    MkcertCreator,\n    TrustmeCreator,\n    get_ssl_context,\n)\nfrom sanic.response import text\nfrom sanic.worker.loader import CertLoader\n\n\ncurrent_dir = os.path.dirname(os.path.realpath(__file__))\nlocalhost_dir = os.path.join(current_dir, \"certs/localhost\")\npassword_dir = os.path.join(current_dir, \"certs/password\")\nsanic_dir = os.path.join(current_dir, \"certs/sanic.example\")\ninvalid_dir = os.path.join(current_dir, \"certs/invalid.nonexist\")\nlocalhost_cert = os.path.join(localhost_dir, \"fullchain.pem\")\nlocalhost_key = os.path.join(localhost_dir, \"privkey.pem\")\nsanic_cert = os.path.join(sanic_dir, \"fullchain.pem\")\nsanic_key = os.path.join(sanic_dir, \"privkey.pem\")\npassword_dict = {\n    \"cert\": os.path.join(password_dir, \"fullchain.pem\"),\n    \"key\": os.path.join(password_dir, \"privkey.pem\"),\n    \"password\": \"password\",\n    \"names\": [\"localhost\"],\n}\n\n\n@pytest.fixture\ndef server_cert():\n    return Mock()\n\n\n@pytest.fixture\ndef issue_cert(server_cert):\n    mock = Mock(return_value=server_cert)\n    return mock\n\n\n@pytest.fixture\ndef ca(issue_cert):\n    ca = Mock()\n    ca.issue_cert = issue_cert\n    return ca\n\n\n@pytest.fixture\ndef trustme(ca):\n    module = Mock()\n    module.CA = Mock(return_value=ca)\n    return module\n\n\n@pytest.fixture\ndef MockMkcertCreator():\n    class Creator(MkcertCreator):\n        SUPPORTED = True\n\n        def check_supported(self):\n            if not self.SUPPORTED:\n                raise SanicException(\"Nope\")\n\n        generate_cert = Mock()\n\n    return Creator\n\n\n@pytest.fixture\ndef MockTrustmeCreator():\n    class Creator(TrustmeCreator):\n        SUPPORTED = True\n\n        def check_supported(self):\n            if not self.SUPPORTED:\n                raise SanicException(\"Nope\")\n\n        generate_cert = Mock()\n\n    return Creator\n\n\n@contextmanager\ndef replace_server_name(hostname):\n    \"\"\"Temporarily replace the server name sent with all TLS requests with\n    a fake hostname.\"\"\"\n\n    def hack_wrap_bio(\n        self,\n        incoming,\n        outgoing,\n        server_side=False,\n        server_hostname=None,\n        session=None,\n    ):\n        return orig_wrap_bio(\n            self, incoming, outgoing, server_side, hostname, session\n        )\n\n    orig_wrap_bio, ssl.SSLContext.wrap_bio = (\n        ssl.SSLContext.wrap_bio,\n        hack_wrap_bio,\n    )\n    try:\n        yield\n    finally:\n        ssl.SSLContext.wrap_bio = orig_wrap_bio\n\n\n@pytest.mark.parametrize(\n    \"path,query,expected_url\",\n    [\n        (\"/foo\", \"\", \"https://{}:{}/foo\"),\n        (\"/bar/baz\", \"\", \"https://{}:{}/bar/baz\"),\n        (\"/moo/boo\", \"arg1=val1\", \"https://{}:{}/moo/boo?arg1=val1\"),\n    ],\n)\ndef test_url_attributes_with_ssl_context(app, path, query, expected_url):\n    context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH)\n    context.load_cert_chain(localhost_cert, localhost_key)\n\n    async def handler(request):\n        return text(\"OK\")\n\n    app.add_route(handler, path)\n\n    request, _ = app.test_client.get(\n        f\"https://{HOST}:{PORT}\" + path + f\"?{query}\",\n        server_kwargs={\"ssl\": context},\n    )\n    assert request.url == expected_url.format(HOST, request.server_port)\n\n    parsed = urlparse(request.url)\n\n    assert parsed.scheme == request.scheme\n    assert parsed.path == request.path\n    assert parsed.query == request.query_string\n    assert parsed.netloc == request.host\n\n\n@pytest.mark.parametrize(\n    \"path,query,expected_url\",\n    [\n        (\"/foo\", \"\", \"https://{}:{}/foo\"),\n        (\"/bar/baz\", \"\", \"https://{}:{}/bar/baz\"),\n        (\"/moo/boo\", \"arg1=val1\", \"https://{}:{}/moo/boo?arg1=val1\"),\n    ],\n)\ndef test_url_attributes_with_ssl_dict(app, path, query, expected_url):\n    ssl_dict = {\"cert\": localhost_cert, \"key\": localhost_key}\n\n    async def handler(request):\n        return text(\"OK\")\n\n    app.add_route(handler, path)\n\n    request, _ = app.test_client.get(\n        f\"https://{HOST}:{PORT}\" + path + f\"?{query}\",\n        server_kwargs={\"ssl\": ssl_dict},\n    )\n    assert request.url == expected_url.format(HOST, request.server_port)\n\n    parsed = urlparse(request.url)\n\n    assert parsed.scheme == request.scheme\n    assert parsed.path == request.path\n    assert parsed.query == request.query_string\n    assert parsed.netloc == request.host\n\n\ndef test_cert_sni_single(app):\n    @app.get(\"/sni\")\n    async def handler1(request):\n        return text(request.conn_info.server_name)\n\n    @app.get(\"/commonname\")\n    async def handler2(request):\n        return text(request.conn_info.cert.get(\"commonName\"))\n\n    port = app.test_client.port\n    _, response = app.test_client.get(\n        f\"https://localhost:{port}/sni\",\n        server_kwargs={\"ssl\": localhost_dir},\n    )\n    assert response.status == 200\n    assert response.text == \"localhost\"\n\n    _, response = app.test_client.get(\n        f\"https://localhost:{port}/commonname\",\n        server_kwargs={\"ssl\": localhost_dir},\n    )\n    assert response.status == 200\n    assert response.text == \"localhost\"\n\n\ndef test_cert_sni_list(app):\n    ssl_list = [sanic_dir, localhost_dir]\n\n    @app.get(\"/sni\")\n    async def handler1(request):\n        return text(request.conn_info.server_name)\n\n    @app.get(\"/commonname\")\n    async def handler2(request):\n        return text(request.conn_info.cert.get(\"commonName\"))\n\n    # This test should match the localhost cert\n    port = app.test_client.port\n    _, response = app.test_client.get(\n        f\"https://localhost:{port}/sni\",\n        server_kwargs={\"ssl\": ssl_list},\n    )\n    assert response.status == 200\n    assert response.text == \"localhost\"\n\n    request, response = app.test_client.get(\n        f\"https://localhost:{port}/commonname\",\n        server_kwargs={\"ssl\": ssl_list},\n    )\n    assert response.status == 200\n    assert response.text == \"localhost\"\n\n    # This part should use the sanic.example cert because it matches\n    with replace_server_name(\"www.sanic.example\"):\n        _, response = app.test_client.get(\n            f\"https://127.0.0.1:{port}/sni\",\n            server_kwargs={\"ssl\": ssl_list},\n        )\n        assert response.status == 200\n        assert response.text == \"www.sanic.example\"\n\n        _, response = app.test_client.get(\n            f\"https://127.0.0.1:{port}/commonname\",\n            server_kwargs={\"ssl\": ssl_list},\n        )\n        assert response.status == 200\n        assert response.text == \"sanic.example\"\n\n    # This part should use the sanic.example cert, that being the first listed\n    with replace_server_name(\"invalid.test\"):\n        _, response = app.test_client.get(\n            f\"https://127.0.0.1:{port}/sni\",\n            server_kwargs={\"ssl\": ssl_list},\n        )\n        assert response.status == 200\n        assert response.text == \"invalid.test\"\n\n        _, response = app.test_client.get(\n            f\"https://127.0.0.1:{port}/commonname\",\n            server_kwargs={\"ssl\": ssl_list},\n        )\n        assert response.status == 200\n        assert response.text == \"sanic.example\"\n\n\n@pytest.mark.xfail\ndef test_missing_sni(app):\n    \"\"\"The sanic cert does not list 127.0.0.1 and httpx does not send\n    IP as SNI anyway.\"\"\"\n    ssl_list = [None, sanic_dir]\n\n    @app.get(\"/sni\")\n    async def handler(request):\n        return text(request.conn_info.server_name)\n\n    port = app.test_client.port\n    with pytest.raises(Exception) as exc:\n        app.test_client.get(\n            f\"https://127.0.0.1:{port}/sni\",\n            server_kwargs={\"ssl\": ssl_list},\n        )\n    assert \"Request and response object expected\" in str(exc.value)\n\n\n@pytest.mark.xfail\ndef test_no_matching_cert(app):\n    \"\"\"The sanic cert does not list 127.0.0.1 and httpx does not send\n    IP as SNI anyway.\"\"\"\n    ssl_list = [None, sanic_dir]\n\n    @app.get(\"/sni\")\n    async def handler(request):\n        return text(request.conn_info.server_name)\n\n    port = app.test_client.port\n    with replace_server_name(\"invalid.test\"):\n        with pytest.raises(Exception) as exc:\n            app.test_client.get(\n                f\"https://127.0.0.1:{port}/sni\",\n                server_kwargs={\"ssl\": ssl_list},\n            )\n    assert \"Request and response object expected\" in str(exc.value)\n\n\n@pytest.mark.xfail\ndef test_wildcards(app):\n    ssl_list = [None, localhost_dir, sanic_dir]\n\n    @app.get(\"/sni\")\n    async def handler(request):\n        return text(request.conn_info.server_name)\n\n    port = app.test_client.port\n\n    with replace_server_name(\"foo.sanic.test\"):\n        _, response = app.test_client.get(\n            f\"https://127.0.0.1:{port}/sni\",\n            server_kwargs={\"ssl\": ssl_list},\n        )\n        assert response.status == 200\n        assert response.text == \"foo.sanic.test\"\n\n    with replace_server_name(\"sanic.test\"):\n        with pytest.raises(Exception) as exc:\n            _, response = app.test_client.get(\n                f\"https://127.0.0.1:{port}/sni\",\n                server_kwargs={\"ssl\": ssl_list},\n            )\n        assert \"Request and response object expected\" in str(exc.value)\n    with replace_server_name(\"sub.foo.sanic.test\"):\n        with pytest.raises(Exception) as exc:\n            _, response = app.test_client.get(\n                f\"https://127.0.0.1:{port}/sni\",\n                server_kwargs={\"ssl\": ssl_list},\n            )\n        assert \"Request and response object expected\" in str(exc.value)\n\n\ndef test_invalid_ssl_dict(app):\n    @app.get(\"/test\")\n    async def handler(request):\n        return text(\"ssl test\")\n\n    ssl_dict = {\"cert\": None, \"key\": None}\n\n    with pytest.raises(ValueError) as excinfo:\n        app.test_client.get(\"/test\", server_kwargs={\"ssl\": ssl_dict})\n\n    assert str(excinfo.value) == \"SSL dict needs filenames for cert and key.\"\n\n\ndef test_invalid_ssl_type(app):\n    @app.get(\"/test\")\n    async def handler(request):\n        return text(\"ssl test\")\n\n    with pytest.raises(ValueError) as excinfo:\n        app.test_client.get(\"/test\", server_kwargs={\"ssl\": False})\n\n    assert \"Invalid ssl argument\" in str(excinfo.value)\n\n\ndef test_cert_file_on_pathlist(app):\n    @app.get(\"/test\")\n    async def handler(request):\n        return text(\"ssl test\")\n\n    ssl_list = [sanic_cert]\n\n    with pytest.raises(ValueError) as excinfo:\n        app.test_client.get(\"/test\", server_kwargs={\"ssl\": ssl_list})\n\n    assert \"folder expected\" in str(excinfo.value)\n    assert sanic_cert in str(excinfo.value)\n\n\ndef test_missing_cert_path(app):\n    @app.get(\"/test\")\n    async def handler(request):\n        return text(\"ssl test\")\n\n    ssl_list = [invalid_dir]\n\n    with pytest.raises(ValueError) as excinfo:\n        app.test_client.get(\"/test\", server_kwargs={\"ssl\": ssl_list})\n\n    assert \"not found\" in str(excinfo.value)\n    assert invalid_dir + \"/privkey.pem\" in str(excinfo.value)\n\n\ndef test_missing_cert_file(app):\n    @app.get(\"/test\")\n    async def handler(request):\n        return text(\"ssl test\")\n\n    invalid2 = invalid_dir.replace(\"nonexist\", \"certmissing\")\n    ssl_list = [invalid2]\n\n    with pytest.raises(ValueError) as excinfo:\n        app.test_client.get(\"/test\", server_kwargs={\"ssl\": ssl_list})\n\n    assert \"not found\" in str(excinfo.value)\n    assert invalid2 + \"/fullchain.pem\" in str(excinfo.value)\n\n\ndef test_no_certs_on_list(app):\n    @app.get(\"/test\")\n    async def handler(request):\n        return text(\"ssl test\")\n\n    ssl_list = [None]\n\n    with pytest.raises(ValueError) as excinfo:\n        app.test_client.get(\"/test\", server_kwargs={\"ssl\": ssl_list})\n\n    assert \"No certificates\" in str(excinfo.value)\n\n\ndef test_custom_cert_loader(port):\n    class MyCertLoader(CertLoader):\n        def load(self, app: Sanic):\n            self._ssl_data = {\n                \"key\": localhost_key,\n                \"cert\": localhost_cert,\n            }\n            return super().load(app)\n\n    app = Sanic(\"custom\", certloader_class=MyCertLoader)\n\n    @app.get(\"/test\")\n    async def handler(request):\n        return text(\"ssl test\")\n\n    client = SanicTestClient(app, port=port)\n\n    request, response = client.get(f\"https://localhost:{port}/test\")\n    assert request.scheme == \"https\"\n    assert response.status_code == 200\n    assert response.text == \"ssl test\"\n\n\ndef test_logger_vhosts(caplog, port):\n    app = Sanic(name=\"test_logger_vhosts\")\n\n    @app.after_server_start\n    def stop(*args):\n        app.stop()\n\n    with caplog.at_level(logging.INFO):\n        app.run(\n            host=\"127.0.0.1\",\n            port=port,\n            ssl=[localhost_dir, sanic_dir],\n            single_process=True,\n        )\n\n    logmsg = [\n        msg\n        for name, levelno, msg in caplog.record_tuples\n        if (msg.startswith(\"Certificate\"))\n    ][0]\n\n    assert logmsg == (\n        \"Certificate vhosts: localhost, 127.0.0.1, 0:0:0:0:0:0:0:1, \"\n        \"sanic.example, www.sanic.example, *.sanic.test, \"\n        \"2001:DB8:0:0:0:0:0:541C\"\n    )\n\n\ndef test_mk_cert_creator_default(app: Sanic):\n    cert_creator = MkcertCreator(app, _default, _default)\n    assert isinstance(cert_creator.tmpdir, Path)\n    assert cert_creator.tmpdir.exists()\n\n\ndef test_mk_cert_creator_is_supported(app):\n    cert_creator = MkcertCreator(app, _default, _default)\n    with patch(\"subprocess.run\") as run:\n        cert_creator.check_supported()\n        run.assert_called_once_with(\n            [\"mkcert\", \"-help\"],\n            check=True,\n            stderr=subprocess.DEVNULL,\n            stdout=subprocess.DEVNULL,\n        )\n\n\ndef test_mk_cert_creator_is_not_supported(app):\n    cert_creator = MkcertCreator(app, _default, _default)\n    with patch(\"subprocess.run\") as run:\n        run.side_effect = Exception(\"\")\n        with pytest.raises(\n            SanicException, match=\"Sanic is attempting to use mkcert\"\n        ):\n            cert_creator.check_supported()\n\n\ndef test_mk_cert_creator_generate_cert_default(app):\n    cert_creator = MkcertCreator(app, _default, _default)\n    with patch(\"subprocess.run\") as run:\n        with patch(\"sanic.http.tls.creators.CertSimple\"):\n            retval = Mock()\n            retval.stdout = \"foo\"\n            run.return_value = retval\n            cert_creator.generate_cert(\"localhost\")\n            run.assert_called_once()\n\n\ndef test_mk_cert_creator_generate_cert_localhost(app):\n    cert_creator = MkcertCreator(app, localhost_key, localhost_cert)\n    with patch(\"subprocess.run\") as run:\n        with patch(\"sanic.http.tls.creators.CertSimple\"):\n            cert_creator.generate_cert(\"localhost\")\n            run.assert_not_called()\n\n\ndef test_trustme_creator_default(app: Sanic):\n    cert_creator = TrustmeCreator(app, _default, _default)\n    assert isinstance(cert_creator.tmpdir, Path)\n    assert cert_creator.tmpdir.exists()\n\n\ndef test_trustme_creator_is_supported(app, monkeypatch):\n    monkeypatch.setattr(sanic.http.tls.creators, \"TRUSTME_INSTALLED\", True)\n    cert_creator = TrustmeCreator(app, _default, _default)\n    cert_creator.check_supported()\n\n\ndef test_trustme_creator_is_not_supported(app, monkeypatch):\n    monkeypatch.setattr(sanic.http.tls.creators, \"TRUSTME_INSTALLED\", False)\n    cert_creator = TrustmeCreator(app, _default, _default)\n    with pytest.raises(\n        SanicException, match=\"Sanic is attempting to use trustme\"\n    ):\n        cert_creator.check_supported()\n\n\ndef test_trustme_creator_generate_cert_default(\n    app, monkeypatch, trustme, issue_cert, server_cert, ca\n):\n    monkeypatch.setattr(sanic.http.tls.creators, \"trustme\", trustme)\n    cert_creator = TrustmeCreator(app, _default, _default)\n    cert = cert_creator.generate_cert(\"localhost\")\n\n    assert isinstance(cert, SanicSSLContext)\n    trustme.CA.assert_called_once_with()\n    issue_cert.assert_called_once_with(\"localhost\")\n    server_cert.configure_cert.assert_called_once()\n    ca.configure_trust.assert_called_once()\n    ca.cert_pem.write_to_path.assert_called_once_with(str(cert.sanic[\"cert\"]))\n    write_to_path = server_cert.private_key_and_cert_chain_pem.write_to_path\n    write_to_path.assert_called_once_with(str(cert.sanic[\"key\"]))\n\n\ndef test_trustme_creator_generate_cert_localhost(\n    app, monkeypatch, trustme, server_cert, ca\n):\n    monkeypatch.setattr(sanic.http.tls.creators, \"trustme\", trustme)\n    cert_creator = TrustmeCreator(app, localhost_key, localhost_cert)\n    cert_creator.generate_cert(\"localhost\")\n\n    ca.cert_pem.write_to_path.assert_called_once_with(localhost_cert)\n    write_to_path = server_cert.private_key_and_cert_chain_pem.write_to_path\n    write_to_path.assert_called_once_with(localhost_key)\n\n\ndef test_get_ssl_context_with_ssl_context(app):\n    mock_context = Mock()\n    context = get_ssl_context(app, mock_context)\n    assert context is mock_context\n\n\ndef test_get_ssl_context_in_production(app):\n    app.state.mode = Mode.PRODUCTION\n    with pytest.raises(\n        SanicException,\n        match=\"Cannot run Sanic as an HTTPS server in PRODUCTION mode\",\n    ):\n        get_ssl_context(app, None)\n\n\n@pytest.mark.parametrize(\n    \"requirement,mk_supported,trustme_supported,mk_called,trustme_called,err\",\n    (\n        (LocalCertCreator.AUTO, True, False, True, False, None),\n        (LocalCertCreator.AUTO, True, True, True, False, None),\n        (LocalCertCreator.AUTO, False, True, False, True, None),\n        (\n            LocalCertCreator.AUTO,\n            False,\n            False,\n            False,\n            False,\n            \"Sanic could not find package to create a TLS certificate\",\n        ),\n        (LocalCertCreator.MKCERT, True, False, True, False, None),\n        (LocalCertCreator.MKCERT, True, True, True, False, None),\n        (LocalCertCreator.MKCERT, False, True, False, False, \"Nope\"),\n        (LocalCertCreator.MKCERT, False, False, False, False, \"Nope\"),\n        (LocalCertCreator.TRUSTME, True, False, False, False, \"Nope\"),\n        (LocalCertCreator.TRUSTME, True, True, False, True, None),\n        (LocalCertCreator.TRUSTME, False, True, False, True, None),\n        (LocalCertCreator.TRUSTME, False, False, False, False, \"Nope\"),\n    ),\n)\ndef test_get_ssl_context_only_mkcert(\n    app,\n    monkeypatch,\n    MockMkcertCreator,\n    MockTrustmeCreator,\n    requirement,\n    mk_supported,\n    trustme_supported,\n    mk_called,\n    trustme_called,\n    err,\n):\n    app.state.mode = Mode.DEBUG\n    app.config.LOCAL_CERT_CREATOR = requirement\n    monkeypatch.setattr(\n        sanic.http.tls.creators, \"MkcertCreator\", MockMkcertCreator\n    )\n    monkeypatch.setattr(\n        sanic.http.tls.creators, \"TrustmeCreator\", MockTrustmeCreator\n    )\n    MockMkcertCreator.SUPPORTED = mk_supported\n    MockTrustmeCreator.SUPPORTED = trustme_supported\n\n    if err:\n        with pytest.raises(SanicException, match=err):\n            get_ssl_context(app, None)\n    else:\n        get_ssl_context(app, None)\n\n    if mk_called:\n        MockMkcertCreator.generate_cert.assert_called_once_with(\"localhost\")\n    else:\n        MockMkcertCreator.generate_cert.assert_not_called()\n    if trustme_called:\n        MockTrustmeCreator.generate_cert.assert_called_once_with(\"localhost\")\n    else:\n        MockTrustmeCreator.generate_cert.assert_not_called()\n\n\n# def test_no_http3_with_trustme(\n#     app,\n#     monkeypatch,\n#     MockTrustmeCreator,\n# ):\n#     monkeypatch.setattr(\n#         sanic.http.tls.creators, \"TrustmeCreator\", MockTrustmeCreator\n#     )\n#     MockTrustmeCreator.SUPPORTED = True\n#     app.config.LOCAL_CERT_CREATOR = \"TRUSTME\"\n#     with pytest.raises(\n#         SanicException,\n#         match=(\n#             \"Sorry, you cannot currently use trustme as a local certificate \"\n#             \"generator for an HTTP/3 server\"\n#         ),\n#     ):\n#         app.run(version=3, debug=True)\n\n\ndef test_sanic_ssl_context_create():\n    context = ssl.SSLContext()\n    sanic_context = SanicSSLContext.create_from_ssl_context(context)\n\n    assert sanic_context is context\n    assert isinstance(sanic_context, SanicSSLContext)\n\n\n@pytest.mark.xdist_group(name=\"process_spawning\")\n@pytest.mark.skipif(\n    sys.platform not in (\"linux\", \"darwin\"),\n    reason=\"This test requires fork context\",\n)\ndef test_ssl_in_multiprocess_mode(app: Sanic, caplog):\n    ssl_dict = {\"cert\": localhost_cert, \"key\": localhost_key}\n    event = Event()\n\n    @app.main_process_start\n    async def main_start(app: Sanic):\n        app.shared_ctx.event = event\n\n    @app.after_server_start\n    async def shutdown(app):\n        app.shared_ctx.event.set()\n        app.stop()\n\n    assert not event.is_set()\n    with use_context(\"fork\"):\n        with caplog.at_level(logging.INFO):\n            app.run(ssl=ssl_dict)\n    assert event.is_set()\n\n    assert (\n        \"sanic.root\",\n        logging.INFO,\n        \"Goin' Fast @ https://127.0.0.1:8000\",\n    ) in caplog.record_tuples\n\n\n@pytest.mark.xdist_group(name=\"process_spawning\")\n@pytest.mark.skipif(\n    sys.platform not in (\"linux\", \"darwin\"),\n    reason=\"This test requires fork context\",\n)\ndef test_ssl_in_multiprocess_mode_password(\n    app: Sanic, caplog: pytest.LogCaptureFixture\n):\n    event = Event()\n\n    @app.main_process_start\n    async def main_start(app: Sanic):\n        app.shared_ctx.event = event\n\n    @app.after_server_start\n    async def shutdown(app):\n        app.shared_ctx.event.set()\n        app.stop()\n\n    assert not event.is_set()\n    with use_context(\"fork\"):\n        with caplog.at_level(logging.INFO):\n            app.run(ssl=password_dict)\n    assert event.is_set()\n\n    assert (\n        \"sanic.root\",\n        logging.INFO,\n        \"Goin' Fast @ https://127.0.0.1:8000\",\n    ) in caplog.record_tuples\n"
  },
  {
    "path": "tests/test_touchup.py",
    "content": "import logging\n\nimport pytest\n\nfrom sanic_routing.exceptions import NotFound\n\nfrom sanic.signals import RESERVED_NAMESPACES\nfrom sanic.touchup import TouchUp\n\n\ndef test_touchup_methods(app):\n    assert len(TouchUp._registry) == 9\n\n\n@pytest.mark.parametrize(\n    \"verbosity,result\", ((0, False), (1, False), (2, True), (3, True))\n)\nasync def test_ode_removes_dispatch_events(app, caplog, verbosity, result):\n    with caplog.at_level(logging.DEBUG, logger=\"sanic.root\"):\n        app.state.verbosity = verbosity\n        await app._startup()\n    logs = caplog.record_tuples\n\n    for signal in RESERVED_NAMESPACES[\"http\"]:\n        assert (\n            (\n                \"sanic.root\",\n                logging.DEBUG,\n                f\"Disabling event: {signal}\",\n            )\n            in logs\n        ) is result\n\n\n@pytest.mark.parametrize(\"skip_it,result\", ((False, True), (True, False)))\nasync def test_skip_touchup(app, caplog, skip_it, result):\n    app.config.TOUCHUP = not skip_it\n    with caplog.at_level(logging.DEBUG, logger=\"sanic.root\"):\n        app.state.verbosity = 2\n        await app._startup()\n    assert app.signal_router.allow_fail_builtin is (not skip_it)\n    logs = caplog.record_tuples\n\n    for signal in RESERVED_NAMESPACES[\"http\"]:\n        assert (\n            (\n                \"sanic.root\",\n                logging.DEBUG,\n                f\"Disabling event: {signal}\",\n            )\n            in logs\n        ) is result\n    not_found_exceptions = 0\n    # Skip-touchup disables NotFound exceptions on the dispatcher\n    for signal in RESERVED_NAMESPACES[\"http\"]:\n        try:\n            await app.dispatch(event=signal, inline=True)\n        except NotFound:\n            not_found_exceptions += 1\n    assert (not_found_exceptions > 0) is result\n\n\n@pytest.mark.parametrize(\"skip_it,result\", ((False, True), (True, True)))\nasync def test_skip_touchup_non_reserved(app, caplog, skip_it, result):\n    app.config.TOUCHUP = not skip_it\n\n    @app.signal(\"foo.bar.one\")\n    def sync_signal(*_): ...\n\n    await app._startup()\n    assert app.signal_router.allow_fail_builtin is (not skip_it)\n    not_found_exception = False\n    # Skip-touchup doesn't disable NotFound exceptions for user-defined signals\n    try:\n        await app.dispatch(event=\"foo.baz.two\", inline=True)\n    except NotFound:\n        not_found_exception = True\n    assert not_found_exception is result\n"
  },
  {
    "path": "tests/test_unix_socket.py",
    "content": "# import asyncio\nimport logging\nimport os\nimport sys\n\nfrom asyncio import AbstractEventLoop, sleep\nfrom pathlib import Path\nfrom string import ascii_lowercase\n\nimport httpcore\nimport httpx\nimport pytest\n\nfrom pytest import LogCaptureFixture\n\nfrom sanic import Sanic\nfrom sanic.compat import use_context\nfrom sanic.request import Request\nfrom sanic.response import text\n\n\n# import platform\n# import subprocess\n# import sys\n\n\npytestmark = [\n    pytest.mark.skipif(os.name != \"posix\", reason=\"UNIX only\"),\n    pytest.mark.xdist_group(name=\"unix_socket\"),\n]\nSOCKPATH = Path(\"/tmp/sanictest.sock\")\nSOCKPATH2 = \"/tmp/sanictest2.sock\"\nhttpx_version = tuple(\n    map(int, httpx.__version__.strip(ascii_lowercase).split(\".\"))\n)\n\n\n@pytest.fixture(autouse=True)\ndef socket_cleanup():\n    try:\n        os.unlink(SOCKPATH)\n    except FileNotFoundError:\n        pass\n    try:\n        os.unlink(SOCKPATH2)\n    except FileNotFoundError:\n        pass\n    # Run test function\n    yield\n    try:\n        os.unlink(SOCKPATH2)\n    except FileNotFoundError:\n        pass\n    try:\n        os.unlink(SOCKPATH)\n    except FileNotFoundError:\n        pass\n\n\n@pytest.mark.xfail(\n    reason=\"Flaky Test on Non Linux Infra\",\n)\ndef test_unix_socket_creation(caplog: LogCaptureFixture):\n    from socket import AF_UNIX, socket\n\n    with socket(AF_UNIX) as sock:\n        sock.bind(SOCKPATH)\n    assert os.path.exists(SOCKPATH)\n    ino = os.stat(SOCKPATH).st_ino\n\n    app = Sanic(name=\"test\")\n\n    @app.after_server_start\n    def running(app: Sanic):\n        assert os.path.exists(SOCKPATH)\n        assert ino != os.stat(SOCKPATH).st_ino\n        app.stop()\n\n    with caplog.at_level(logging.INFO):\n        app.run(unix=SOCKPATH, single_process=True)\n\n    assert (\n        \"sanic.root\",\n        logging.INFO,\n        f\"Goin' Fast @ {SOCKPATH} http://...\",\n    ) in caplog.record_tuples\n    assert not os.path.exists(SOCKPATH)\n\n\n@pytest.mark.parametrize(\"path\", (\".\", \"no-such-directory/sanictest.sock\"))\ndef test_invalid_paths(path: str):\n    app = Sanic(name=\"test\")\n    #\n    with pytest.raises((FileExistsError, FileNotFoundError)):\n        app.run(unix=path, single_process=True)\n\n\ndef test_dont_replace_file():\n    SOCKPATH.write_text(\"File, not socket\")\n\n    app = Sanic(name=\"test\")\n\n    @app.after_server_start\n    def stop(app: Sanic):\n        app.stop()\n\n    with pytest.raises(FileExistsError):\n        app.run(unix=SOCKPATH, single_process=True)\n\n\ndef test_dont_follow_symlink():\n    from socket import AF_UNIX, socket\n\n    with socket(AF_UNIX) as sock:\n        sock.bind(SOCKPATH2)\n    os.symlink(SOCKPATH2, SOCKPATH)\n\n    app = Sanic(name=\"test\")\n\n    @app.after_server_start\n    def stop(app: Sanic):\n        app.stop()\n\n    with pytest.raises(FileExistsError):\n        app.run(unix=SOCKPATH, single_process=True)\n\n\ndef test_socket_deleted_while_running():\n    app = Sanic(name=\"test\")\n\n    @app.after_server_start\n    async def hack(app: Sanic):\n        os.unlink(SOCKPATH)\n        app.stop()\n\n    app.run(host=\"myhost.invalid\", unix=SOCKPATH, single_process=True)\n\n\ndef test_socket_replaced_with_file():\n    app = Sanic(name=\"test\")\n\n    @app.after_server_start\n    async def hack(app: Sanic):\n        os.unlink(SOCKPATH)\n        with open(SOCKPATH, \"w\") as f:\n            f.write(\"Not a socket\")\n        app.stop()\n\n    app.run(host=\"myhost.invalid\", unix=SOCKPATH, single_process=True)\n\n\ndef test_unix_connection():\n    app = Sanic(name=\"test\")\n\n    @app.get(\"/\")\n    def handler(request: Request):\n        return text(f\"{request.conn_info.server}\")\n\n    @app.after_server_start\n    async def client(app: Sanic):\n        if httpx_version >= (0, 20):\n            transport = httpx.AsyncHTTPTransport(uds=SOCKPATH)\n        else:\n            transport = httpcore.AsyncConnectionPool(uds=SOCKPATH)\n        try:\n            async with httpx.AsyncClient(transport=transport) as client:\n                r = await client.get(\"http://myhost.invalid/\")\n                assert r.status_code == 200\n                assert r.text == os.path.abspath(SOCKPATH)\n        finally:\n            app.stop()\n\n    app.run(host=\"myhost.invalid\", unix=SOCKPATH, single_process=True)\n\n\ndef handler(request: Request):\n    return text(f\"{request.conn_info.server}\")\n\n\nasync def client(app: Sanic, loop: AbstractEventLoop):\n    try:\n        transport = httpx.AsyncHTTPTransport(uds=SOCKPATH)\n        async with httpx.AsyncClient(transport=transport) as client:\n            r = await client.get(\"http://myhost.invalid/\")\n            assert r.status_code == 200\n            assert r.text == os.path.abspath(SOCKPATH)\n    finally:\n        await sleep(0.2)\n        app.stop()\n\n\n@pytest.mark.skipif(\n    sys.platform not in (\"linux\", \"darwin\"),\n    reason=\"This test requires fork context\",\n)\ndef test_unix_connection_multiple_workers():\n    with use_context(\"fork\"):\n        app_multi = Sanic(name=\"test\")\n        app_multi.get(\"/\")(handler)\n        app_multi.listener(\"after_server_start\")(client)\n        app_multi.run(host=\"myhost.invalid\", unix=SOCKPATH, workers=2)\n\n\n# @pytest.mark.xfail(\n#     condition=platform.system() != \"Linux\",\n#     reason=\"Flaky Test on Non Linux Infra\",\n# )\n# async def test_zero_downtime():\n#     \"\"\"Graceful server termination and socket replacement on restarts\"\"\"\n#     from signal import SIGINT\n#     from time import monotonic as current_time\n\n#     async def client():\n#         if httpx_version >= (0, 20):\n#             transport = httpx.AsyncHTTPTransport(uds=SOCKPATH)\n#         else:\n#             transport = httpcore.AsyncConnectionPool(uds=SOCKPATH)\n#         for _ in range(40):\n#             async with httpx.AsyncClient(transport=transport) as client:\n#                 r = await client.get(\"http://localhost/sleep/0.1\")\n#                 assert r.status_code == 200, r.text\n#                 assert r.text == \"Slept 0.1 seconds.\\n\"\n\n#     def spawn():\n#         command = [\n#             sys.executable,\n#             \"-m\",\n#             \"sanic\",\n#             \"--debug\",\n#             \"--unix\",\n#             SOCKPATH,\n#             \"examples.delayed_response.app\",\n#         ]\n#         DN = subprocess.DEVNULL\n#         return subprocess.Popen(\n#             command, stdin=DN, stdout=DN, stderr=subprocess.PIPE\n#         )\n\n#     try:\n#         processes = [spawn()]\n#         while not os.path.exists(SOCKPATH):\n#             if processes[0].poll() is not None:\n#                 raise Exception(\n#                     \"Worker did not start properly. \"\n#                     f\"stderr: {processes[0].stderr.read()}\"\n#                 )\n#             await asyncio.sleep(0.0001)\n#         ino = os.stat(SOCKPATH).st_ino\n#         task = asyncio.get_event_loop().create_task(client())\n#         start_time = current_time()\n#         while current_time() < start_time + 6:\n#             # Start a new one and wait until the socket is replaced\n#             processes.append(spawn())\n#             while ino == os.stat(SOCKPATH).st_ino:\n#                 await asyncio.sleep(0.001)\n#             ino = os.stat(SOCKPATH).st_ino\n#             # Graceful termination of the previous one\n#             processes[-2].send_signal(SIGINT)\n#         # Wait until client has completed all requests\n#         await task\n#         processes[-1].send_signal(SIGINT)\n#         for worker in processes:\n#             try:\n#                 worker.wait(1.0)\n#             except subprocess.TimeoutExpired:\n#                 raise Exception(\n#                     f\"Worker would not terminate:\\n{worker.stderr}\"\n#                 )\n#     finally:\n#         for worker in processes:\n#             worker.kill()\n#     # Test for clean run and termination\n#     return_codes = [worker.poll() for worker in processes]\n\n#     # Removing last process which seems to be flappy\n#     return_codes.pop()\n#     assert len(processes) > 5\n#     assert all(code == 0 for code in return_codes)\n\n#     # Removing this check that seems to be flappy\n#     # assert not os.path.exists(SOCKPATH)\n"
  },
  {
    "path": "tests/test_url_building.py",
    "content": "import string\n\nfrom urllib.parse import parse_qsl, urlsplit\n\nimport pytest\n\nfrom sanic_testing.testing import HOST as test_host\nfrom sanic_testing.testing import PORT as test_port\n\nfrom sanic import Sanic\nfrom sanic.blueprints import Blueprint\nfrom sanic.exceptions import URLBuildError\nfrom sanic.response import text\nfrom sanic.views import HTTPMethodView\n\n\nURL_FOR_ARGS1 = dict(arg1=[\"v1\", \"v2\"])\nURL_FOR_VALUE1 = \"/myurl?arg1=v1&arg1=v2\"\nURL_FOR_ARGS2 = dict(arg1=[\"v1\", \"v2\"], _anchor=\"anchor\")\nURL_FOR_VALUE2 = \"/myurl?arg1=v1&arg1=v2#anchor\"\nURL_FOR_ARGS3 = dict(\n    arg1=\"v1\",\n    _anchor=\"anchor\",\n    _scheme=\"http\",\n    _server=f\"{test_host}:{test_port}\",\n    _external=True,\n)\nURL_FOR_VALUE3 = f\"http://{test_host}:{test_port}/myurl?arg1=v1#anchor\"\nURL_FOR_ARGS4 = dict(\n    arg1=\"v1\",\n    _anchor=\"anchor\",\n    _external=True,\n    _server=f\"http://{test_host}:{test_port}\",\n)\nURL_FOR_VALUE4 = f\"http://{test_host}:{test_port}/myurl?arg1=v1#anchor\"\n\n\ndef _generate_handlers_from_names(app, names):\n    for name in names:\n        # this is the easiest way to generate functions with dynamic names\n        exec(\n            f'@app.route(name)\\ndef {name}(request):\\n\\treturn text(\"{name}\")'\n        )\n\n\n@pytest.fixture\ndef simple_app(app):\n    handler_names = list(string.ascii_letters)\n\n    _generate_handlers_from_names(app, handler_names)\n\n    return app\n\n\ndef test_simple_url_for_getting(simple_app):\n    for letter in string.ascii_letters:\n        url = simple_app.url_for(letter)\n\n        assert url == f\"/{letter}\"\n        request, response = simple_app.test_client.get(url)\n        assert response.status == 200\n        assert response.text == letter\n\n\n@pytest.mark.parametrize(\n    \"args,url\",\n    [\n        (URL_FOR_ARGS1, URL_FOR_VALUE1),\n        (URL_FOR_ARGS2, URL_FOR_VALUE2),\n        (URL_FOR_ARGS3, URL_FOR_VALUE3),\n        (URL_FOR_ARGS4, URL_FOR_VALUE4),\n    ],\n)\ndef test_simple_url_for_getting_with_more_params(app, args, url):\n    @app.route(\"/myurl\")\n    def passes(request):\n        return text(\"this should pass\")\n\n    assert url == app.url_for(\"passes\", **args)\n    request, response = app.test_client.get(url)\n    assert response.status == 200\n    assert response.text == \"this should pass\"\n\n\ndef test_url_for_with_server_name(app):\n    server_name = f\"{test_host}:{test_port}\"\n    app.config.update({\"SERVER_NAME\": server_name})\n    path = \"/myurl\"\n\n    @app.route(path)\n    def passes(request):\n        return text(\"this should pass\")\n\n    url = f\"http://{server_name}{path}\"\n    assert url == app.url_for(\"passes\", _server=None, _external=True)\n    request, response = app.test_client.get(url)\n    assert response.status == 200\n    assert response.text == \"this should pass\"\n\n\ndef test_fails_if_endpoint_not_found():\n    app = Sanic(\"app\")\n\n    @app.route(\"/fail\")\n    def fail(request):\n        return text(\"this should fail\")\n\n    with pytest.raises(URLBuildError) as e:\n        app.url_for(\"passes\")\n        e.match(\"Endpoint with name `app.passes` was not found\")\n\n\ndef test_fails_url_build_if_param_not_passed(app):\n    url = \"/\"\n\n    for letter in string.ascii_lowercase:\n        url += f\"<{letter}>/\"\n\n    @app.route(url)\n    def fail(request):\n        return text(\"this should fail\")\n\n    fail_args = list(string.ascii_lowercase)\n    fail_args.pop()\n\n    fail_kwargs = {fail_arg: fail_arg for fail_arg in fail_args}\n\n    with pytest.raises(URLBuildError) as e:\n        app.url_for(\"fail\", **fail_kwargs)\n        assert e.match(\"Required parameter `z` was not passed to url_for\")\n\n\ndef test_fails_url_build_if_params_not_passed(app):\n    @app.route(\"/fail\")\n    def fail(request):\n        return text(\"this should fail\")\n\n    with pytest.raises(ValueError) as e:\n        app.url_for(\"fail\", _scheme=\"http\")\n        assert e.match(\"When specifying _scheme, _external must be True\")\n\n\nCOMPLEX_PARAM_URL = (\n    \"/<foo:int>/<four_letter_string:[A-z]{4}>/\"\n    \"<two_letter_string:[A-z]{2}>/<normal_string>/<some_number:float>\"\n)\nPASSING_KWARGS = {\n    \"foo\": 4,\n    \"four_letter_string\": \"woof\",\n    \"two_letter_string\": \"ba\",\n    \"normal_string\": \"normal\",\n    \"some_number\": \"1.001\",\n}\nEXPECTED_BUILT_URL = \"/4/woof/ba/normal/1.001\"\n\n\ndef test_fails_with_int_message(app):\n    @app.route(COMPLEX_PARAM_URL)\n    def fail(request):\n        return text(\"this should fail\")\n\n    failing_kwargs = dict(PASSING_KWARGS)\n    failing_kwargs[\"foo\"] = \"not_int\"\n\n    with pytest.raises(URLBuildError) as e:\n        app.url_for(\"fail\", **failing_kwargs)\n\n    expected_error = (\n        r'Value \"not_int\" for parameter `foo` '\n        r\"does not match pattern for type `int`: ^-?\\d+$\"\n    )\n    assert str(e.value) == expected_error\n\n\ndef test_passes_with_negative_int_message(app):\n    @app.route(\"path/<possibly_neg:int>/another-word\")\n    def good(request, possibly_neg):\n        assert isinstance(possibly_neg, int)\n        return text(f\"this should pass with `{possibly_neg}`\")\n\n    u_plus_3 = app.url_for(\"good\", possibly_neg=3)\n    assert u_plus_3 == \"/path/3/another-word\", u_plus_3\n    request, response = app.test_client.get(u_plus_3)\n    assert response.text == \"this should pass with `3`\"\n    u_neg_3 = app.url_for(\"good\", possibly_neg=-3)\n    assert u_neg_3 == \"/path/-3/another-word\", u_neg_3\n    request, response = app.test_client.get(u_neg_3)\n    assert response.text == \"this should pass with `-3`\"\n\n\ndef test_fails_with_two_letter_string_message(app):\n    @app.route(COMPLEX_PARAM_URL)\n    def fail(request):\n        return text(\"this should fail\")\n\n    failing_kwargs = dict(PASSING_KWARGS)\n    failing_kwargs[\"two_letter_string\"] = \"foobar\"\n\n    with pytest.raises(URLBuildError) as e:\n        app.url_for(\"fail\", **failing_kwargs)\n        e.match(\n            'Value \"foobar\" for parameter `two_letter_string` '\n            \"does not satisfy pattern ^[A-z]{2}$\"\n        )\n\n\ndef test_fails_with_number_message(app):\n    @app.route(COMPLEX_PARAM_URL)\n    def fail(request):\n        return text(\"this should fail\")\n\n    failing_kwargs = dict(PASSING_KWARGS)\n    failing_kwargs[\"some_number\"] = \"foo\"\n\n    with pytest.raises(URLBuildError) as e:\n        app.url_for(\"fail\", **failing_kwargs)\n        e.match(\n            'Value \"foo\" for parameter `some_number` '\n            r\"does not match pattern for type \"\n            r\"`float`: ^-?(?:\\d+(?:\\.\\d*)?|\\.\\d+)$\"\n        )\n\n\n@pytest.mark.parametrize(\"number\", [3, -3, 13.123, -13.123])\ndef test_passes_with_negative_number_message(app, number):\n    @app.route(\"path/<possibly_neg:float>/another-word\")\n    def good(request, possibly_neg):\n        assert isinstance(possibly_neg, (int, float))\n        return text(f\"this should pass with `{possibly_neg}`\")\n\n    u = app.url_for(\"good\", possibly_neg=number)\n    assert u == f\"/path/{number}/another-word\", u\n    request, response = app.test_client.get(u)\n    # For ``number``,it has been cast to a float - so a ``3`` becomes a ``3.0``\n    assert response.text == f\"this should pass with `{float(number)}`\"\n\n\ndef test_adds_other_supplied_values_as_query_string(app):\n    @app.route(COMPLEX_PARAM_URL)\n    def passes(request):\n        return text(\"this should pass\")\n\n    new_kwargs = dict(PASSING_KWARGS)\n    new_kwargs[\"added_value_one\"] = \"one\"\n    new_kwargs[\"added_value_two\"] = \"two\"\n\n    url = app.url_for(\"passes\", **new_kwargs)\n\n    query = dict(parse_qsl(urlsplit(url).query))\n\n    assert query[\"added_value_one\"] == \"one\"\n    assert query[\"added_value_two\"] == \"two\"\n\n\n@pytest.fixture\ndef blueprint_app():\n    app = Sanic(\"app\")\n\n    first_print = Blueprint(\"first\", url_prefix=\"/first\")\n    second_print = Blueprint(\"second\", url_prefix=\"/second\")\n\n    @first_print.route(\"/foo\")\n    def foo(request):\n        return text(\"foo from first\")\n\n    @first_print.route(\"/foo/<param>\")\n    def foo_with_param(request, param):\n        return text(f\"foo from first : {param}\")\n\n    @second_print.route(\"/foo\")  # noqa\n    def bar(request):\n        return text(\"foo from second\")\n\n    @second_print.route(\"/foo/<param>\")  # noqa\n    def bar_with_param(request, param):\n        return text(f\"foo from second : {param}\")\n\n    app.blueprint(first_print)\n    app.blueprint(second_print)\n\n    return app\n\n\ndef test_blueprints_are_named_correctly(blueprint_app):\n    first_url = blueprint_app.url_for(\"first.foo\")\n    assert first_url == \"/first/foo\"\n\n    second_url = blueprint_app.url_for(\"second.bar\")\n    assert second_url == \"/second/foo\"\n\n\ndef test_blueprints_work_with_params(blueprint_app):\n    first_url = blueprint_app.url_for(\"first.foo_with_param\", param=\"bar\")\n    assert first_url == \"/first/foo/bar\"\n\n    second_url = blueprint_app.url_for(\"second.bar_with_param\", param=\"bar\")\n    assert second_url == \"/second/foo/bar\"\n\n\n@pytest.fixture\ndef methodview_app(app):\n    class ViewOne(HTTPMethodView):\n        def get(self, request):\n            return text(\"I am get method\")\n\n        def post(self, request):\n            return text(\"I am post method\")\n\n        def put(self, request):\n            return text(\"I am put method\")\n\n        def patch(self, request):\n            return text(\"I am patch method\")\n\n        def delete(self, request):\n            return text(\"I am delete method\")\n\n    app.add_route(ViewOne.as_view(\"view_one\"), \"/view_one\")\n\n    class ViewTwo(HTTPMethodView):\n        def get(self, request):\n            return text(\"I am get method\")\n\n        def post(self, request):\n            return text(\"I am post method\")\n\n        def put(self, request):\n            return text(\"I am put method\")\n\n        def patch(self, request):\n            return text(\"I am patch method\")\n\n        def delete(self, request):\n            return text(\"I am delete method\")\n\n    app.add_route(ViewTwo.as_view(), \"/view_two\")\n\n    return app\n\n\ndef test_methodview_naming(methodview_app):\n    viewone_url = methodview_app.url_for(\"ViewOne\")\n    viewtwo_url = methodview_app.url_for(\"ViewTwo\")\n\n    assert viewone_url == \"/view_one\"\n    assert viewtwo_url == \"/view_two\"\n\n\n@pytest.mark.parametrize(\n    \"path,version,expected\",\n    (\n        (\"/foo\", 1, \"/v1/foo\"),\n        (\"/foo\", 1.1, \"/v1.1/foo\"),\n        (\"/foo\", \"1\", \"/v1/foo\"),\n        (\"/foo\", \"1.1\", \"/v1.1/foo\"),\n        (\"/foo\", \"1.0.1\", \"/v1.0.1/foo\"),\n        (\"/foo\", \"v1.0.1\", \"/v1.0.1/foo\"),\n    ),\n)\ndef test_versioning(app, path, version, expected):\n    @app.route(path, version=version)\n    def handler(*_): ...\n\n    url = app.url_for(\"handler\")\n    assert url == expected\n"
  },
  {
    "path": "tests/test_url_for.py",
    "content": "import asyncio\n\nimport pytest\n\nfrom sanic_testing.testing import SanicTestClient\n\nfrom sanic.blueprints import Blueprint\n\n\ndef test_routes_with_host(app):\n    @app.route(\"/\", name=\"hostindex\", host=\"example.com\")\n    @app.route(\"/path\", name=\"hostpath\", host=\"path.example.com\")\n    def index(request):\n        pass\n\n    assert app.url_for(\"hostindex\") == \"/\"\n    assert app.url_for(\"hostpath\") == \"/path\"\n    assert app.url_for(\"hostindex\", _external=True) == \"http://example.com/\"\n    assert (\n        app.url_for(\"hostpath\", _external=True)\n        == \"http://path.example.com/path\"\n    )\n\n\ndef test_routes_with_multiple_hosts(app):\n    @app.route(\"/\", name=\"hostindex\", host=[\"example.com\", \"path.example.com\"])\n    def index(request):\n        pass\n\n    assert app.url_for(\"hostindex\") == \"/\"\n    assert (\n        app.url_for(\"hostindex\", _host=\"example.com\") == \"http://example.com/\"\n    )\n\n    with pytest.raises(ValueError) as e:\n        assert app.url_for(\"hostindex\", _external=True)\n    assert str(e.value).startswith(\"Host is ambiguous\")\n\n    with pytest.raises(ValueError) as e:\n        assert app.url_for(\"hostindex\", _host=\"unknown.com\")\n    assert str(e.value).startswith(\n        \"Requested host (unknown.com) is not available for this route\"\n    )\n\n\n@pytest.mark.parametrize(\n    \"name,expected\",\n    (\n        (\"test_route\", \"/bp/route\"),\n        (\"test_route2\", \"/bp/route2\"),\n        (\"foobar_3\", \"/bp/route3\"),\n    ),\n)\ndef test_websocket_bp_route_name(app, name, expected):\n    \"\"\"Tests that blueprint websocket route is named.\"\"\"\n    event = asyncio.Event()\n    bp = Blueprint(\"test_bp\", url_prefix=\"/bp\")\n\n    @bp.get(\"/main\")\n    async def main(request): ...\n\n    @bp.websocket(\"/route\")\n    async def test_route(request, ws):\n        event.set()\n\n    @bp.websocket(\"/route2\")\n    async def test_route2(request, ws):\n        event.set()\n\n    @bp.websocket(\"/route3\", name=\"foobar_3\")\n    async def test_route3(request, ws):\n        event.set()\n\n    app.blueprint(bp)\n\n    uri = app.url_for(\"test_bp.main\")\n    assert uri == \"/bp/main\"\n\n    uri = app.url_for(f\"test_bp.{name}\")\n    assert uri == expected\n    request, response = SanicTestClient(app).websocket(uri)\n    assert response.opened is True\n    assert event.is_set()\n\n\n# TODO: add test with a route with multiple hosts\n# TODO: add test with a route with _host in url_for\n@pytest.mark.parametrize(\n    \"path,strict,expected\",\n    (\n        (\"/foo\", False, \"/foo\"),\n        (\"/foo/\", False, \"/foo\"),\n        (\"/foo\", True, \"/foo\"),\n        (\"/foo/\", True, \"/foo/\"),\n    ),\n)\ndef test_trailing_slash_url_for(app, path, strict, expected):\n    @app.route(path, strict_slashes=strict)\n    def handler(*_): ...\n\n    url = app.url_for(\"handler\")\n    assert url == expected\n"
  },
  {
    "path": "tests/test_url_for_static.py",
    "content": "import inspect\nimport os\n\nimport pytest\n\nfrom sanic import Sanic\nfrom sanic.blueprints import Blueprint\n\n\npytestmark = pytest.mark.xdist_group(name=\"static_files\")\n\n\n@pytest.fixture(scope=\"module\")\ndef static_file_directory():\n    \"\"\"The static directory to serve\"\"\"\n    current_file = inspect.getfile(inspect.currentframe())\n    current_directory = os.path.dirname(os.path.abspath(current_file))\n    static_directory = os.path.join(current_directory, \"static\")\n    return static_directory\n\n\ndef get_file_path(static_file_directory, file_name):\n    return os.path.join(static_file_directory, file_name)\n\n\ndef get_file_content(static_file_directory, file_name):\n    \"\"\"The content of the static file to check\"\"\"\n    with open(get_file_path(static_file_directory, file_name), \"rb\") as file:\n        return file.read()\n\n\n@pytest.mark.parametrize(\n    \"file_name\",\n    [\n        \"test.file\",\n        \"decode me.txt\",\n        \"python.png\",\n    ],\n)\ndef test_static_file(static_file_directory, file_name):\n    app = Sanic(\"qq\")\n    app.static(\n        \"/testing.file\", get_file_path(static_file_directory, file_name)\n    )\n    app.static(\n        \"/testing2.file\",\n        get_file_path(static_file_directory, file_name),\n        name=\"testing_file\",\n    )\n\n    app.router.finalize()\n\n    uri = app.url_for(\"static\")\n    uri2 = app.url_for(\"static\", filename=\"any\")\n    uri3 = app.url_for(\"static\", name=\"static\", filename=\"any\")\n\n    assert uri == \"/testing.file\"\n    assert uri == uri2\n    assert uri2 == uri3\n\n    app.router.reset()\n\n    request, response = app.test_client.get(uri)\n    assert response.status == 200\n    assert response.body == get_file_content(static_file_directory, file_name)\n\n    app.router.reset()\n\n    bp = Blueprint(\"test_bp_static\", url_prefix=\"/bp\")\n\n    bp.static(\"/testing.file\", get_file_path(static_file_directory, file_name))\n    bp.static(\n        \"/testing2.file\",\n        get_file_path(static_file_directory, file_name),\n        name=\"testing_file\",\n    )\n\n    app.blueprint(bp)\n\n    uris = [\n        app.url_for(\"static\", name=\"test_bp_static.static\"),\n        app.url_for(\"static\", name=\"test_bp_static.static\", filename=\"any\"),\n        app.url_for(\"test_bp_static.static\"),\n        app.url_for(\"test_bp_static.static\", filename=\"any\"),\n    ]\n\n    assert all(uri == \"/bp/testing.file\" for uri in uris)\n\n    request, response = app.test_client.get(uri)\n    assert response.status == 200\n    assert response.body == get_file_content(static_file_directory, file_name)\n\n    # test for other parameters\n    uri = app.url_for(\"static\", _external=True, _server=\"http://localhost\")\n    assert uri == \"http://localhost/testing.file\"\n\n    uri = app.url_for(\n        \"static\",\n        name=\"test_bp_static.static\",\n        _external=True,\n        _server=\"http://localhost\",\n    )\n    assert uri == \"http://localhost/bp/testing.file\"\n\n    # test for defined name\n    uri = app.url_for(\"static\", name=\"testing_file\")\n    assert uri == \"/testing2.file\"\n\n    request, response = app.test_client.get(uri)\n    assert response.status == 200\n    assert response.body == get_file_content(static_file_directory, file_name)\n\n    uri = app.url_for(\"static\", name=\"test_bp_static.testing_file\")\n    assert uri == \"/bp/testing2.file\"\n    assert uri == app.url_for(\n        \"static\", name=\"test_bp_static.testing_file\", filename=\"any\"\n    )\n\n    request, response = app.test_client.get(uri)\n    assert response.status == 200\n    assert response.body == get_file_content(static_file_directory, file_name)\n\n\n@pytest.mark.parametrize(\"file_name\", [\"test.file\", \"decode me.txt\"])\n@pytest.mark.parametrize(\"base_uri\", [\"/static\", \"\", \"/dir\"])\ndef test_static_directory(file_name, base_uri, static_file_directory):\n    app = Sanic(\"base\")\n\n    app.static(base_uri, static_file_directory)\n    base_uri2 = base_uri + \"/2\"\n    app.static(base_uri2, static_file_directory, name=\"uploads\")\n\n    uri = app.url_for(\"static\", name=\"static\", filename=file_name)\n    assert uri == f\"{base_uri}/{file_name}\"\n\n    request, response = app.test_client.get(uri)\n    assert response.status == 200\n    assert response.body == get_file_content(static_file_directory, file_name)\n\n    uri2 = app.url_for(\"static\", name=\"static\", filename=\"/\" + file_name)\n    uri3 = app.url_for(\"static\", filename=file_name)\n    uri4 = app.url_for(\"static\", filename=\"/\" + file_name)\n    uri5 = app.url_for(\"static\", name=\"uploads\", filename=file_name)\n    uri6 = app.url_for(\"static\", name=\"uploads\", filename=\"/\" + file_name)\n\n    assert uri == uri2\n    assert uri2 == uri3\n    assert uri3 == uri4\n\n    assert uri5 == f\"{base_uri2}/{file_name}\"\n    assert uri5 == uri6\n\n    bp = Blueprint(\"test_bp_static\", url_prefix=\"/bp\")\n\n    bp.static(base_uri, static_file_directory)\n    bp.static(base_uri2, static_file_directory, name=\"uploads\")\n\n    app.router.reset()\n    app.blueprint(bp)\n\n    uri = app.url_for(\n        \"static\", name=\"test_bp_static.static\", filename=file_name\n    )\n    uri2 = app.url_for(\n        \"static\", name=\"test_bp_static.static\", filename=\"/\" + file_name\n    )\n\n    uri4 = app.url_for(\n        \"static\", name=\"test_bp_static.uploads\", filename=file_name\n    )\n    uri5 = app.url_for(\n        \"static\", name=\"test_bp_static.uploads\", filename=\"/\" + file_name\n    )\n\n    assert uri == f\"/bp{base_uri}/{file_name}\"\n    assert uri == uri2\n\n    assert uri4 == f\"/bp{base_uri2}/{file_name}\"\n    assert uri4 == uri5\n\n    request, response = app.test_client.get(uri)\n    assert response.status == 200\n    assert response.body == get_file_content(static_file_directory, file_name)\n\n\n@pytest.mark.parametrize(\"file_name\", [\"test.file\", \"decode me.txt\"])\ndef test_static_head_request(file_name, static_file_directory):\n    app = Sanic(\"base\")\n    app.static(\n        \"/testing.file\",\n        get_file_path(static_file_directory, file_name),\n        use_content_range=True,\n    )\n\n    bp = Blueprint(\"test_bp_static\", url_prefix=\"/bp\")\n    bp.static(\n        \"/testing.file\",\n        get_file_path(static_file_directory, file_name),\n        use_content_range=True,\n    )\n    app.blueprint(bp)\n\n    uri = app.url_for(\"static\")\n    assert uri == \"/testing.file\"\n    assert uri == app.url_for(\"static\", name=\"static\")\n    assert uri == app.url_for(\"static\", name=\"static\", filename=\"any\")\n\n    request, response = app.test_client.head(uri)\n    assert response.status == 200\n    assert \"Accept-Ranges\" in response.headers\n    assert \"Content-Length\" in response.headers\n    assert int(response.headers[\"Content-Length\"]) == len(\n        get_file_content(static_file_directory, file_name)\n    )\n\n    # blueprint\n    uri = app.url_for(\"static\", name=\"test_bp_static.static\")\n    assert uri == \"/bp/testing.file\"\n    assert uri == app.url_for(\n        \"static\", name=\"test_bp_static.static\", filename=\"any\"\n    )\n\n    request, response = app.test_client.head(uri)\n    assert response.status == 200\n    assert \"Accept-Ranges\" in response.headers\n    assert \"Content-Length\" in response.headers\n    assert int(response.headers[\"Content-Length\"]) == len(\n        get_file_content(static_file_directory, file_name)\n    )\n\n\n@pytest.mark.parametrize(\"file_name\", [\"test.file\", \"decode me.txt\"])\ndef test_static_content_range_correct(file_name, static_file_directory):\n    app = Sanic(\"base\")\n    app.static(\n        \"/testing.file\",\n        get_file_path(static_file_directory, file_name),\n        use_content_range=True,\n    )\n\n    bp = Blueprint(\"test_bp_static\", url_prefix=\"/bp\")\n    bp.static(\n        \"/testing.file\",\n        get_file_path(static_file_directory, file_name),\n        use_content_range=True,\n    )\n    app.blueprint(bp)\n\n    headers = {\"Range\": \"bytes=12-19\"}\n    uri = app.url_for(\"static\")\n    assert uri == \"/testing.file\"\n    assert uri == app.url_for(\"static\", name=\"static\")\n    assert uri == app.url_for(\"static\", name=\"static\", filename=\"any\")\n\n    request, response = app.test_client.get(uri, headers=headers)\n    assert response.status == 206\n    assert \"Content-Length\" in response.headers\n    assert \"Content-Range\" in response.headers\n    static_content = bytes(get_file_content(static_file_directory, file_name))[\n        12:20\n    ]\n    assert int(response.headers[\"Content-Length\"]) == len(static_content)\n    assert response.body == static_content\n\n    # blueprint\n    uri = app.url_for(\"static\", name=\"test_bp_static.static\")\n    assert uri == \"/bp/testing.file\"\n    assert uri == app.url_for(\n        \"static\", name=\"test_bp_static.static\", filename=\"any\"\n    )\n    assert uri == app.url_for(\"test_bp_static.static\")\n\n    request, response = app.test_client.get(uri, headers=headers)\n    assert response.status == 206\n    assert \"Content-Length\" in response.headers\n    assert \"Content-Range\" in response.headers\n    static_content = bytes(get_file_content(static_file_directory, file_name))[\n        12:20\n    ]\n    assert int(response.headers[\"Content-Length\"]) == len(static_content)\n    assert response.body == static_content\n\n\n@pytest.mark.parametrize(\"file_name\", [\"test.file\", \"decode me.txt\"])\ndef test_static_content_range_front(file_name, static_file_directory):\n    app = Sanic(\"base\")\n    app.static(\n        \"/testing.file\",\n        get_file_path(static_file_directory, file_name),\n        use_content_range=True,\n    )\n\n    bp = Blueprint(\"test_bp_static\", url_prefix=\"/bp\")\n    bp.static(\n        \"/testing.file\",\n        get_file_path(static_file_directory, file_name),\n        use_content_range=True,\n    )\n    app.blueprint(bp)\n\n    headers = {\"Range\": \"bytes=12-\"}\n    uri = app.url_for(\"static\")\n    assert uri == \"/testing.file\"\n    assert uri == app.url_for(\"static\", name=\"static\")\n    assert uri == app.url_for(\"static\", name=\"static\", filename=\"any\")\n\n    request, response = app.test_client.get(uri, headers=headers)\n    assert response.status == 206\n    assert \"Content-Length\" in response.headers\n    assert \"Content-Range\" in response.headers\n    static_content = bytes(get_file_content(static_file_directory, file_name))[\n        12:\n    ]\n    assert int(response.headers[\"Content-Length\"]) == len(static_content)\n    assert response.body == static_content\n\n    # blueprint\n    uri = app.url_for(\"static\", name=\"test_bp_static.static\")\n    assert uri == \"/bp/testing.file\"\n    assert uri == app.url_for(\n        \"static\", name=\"test_bp_static.static\", filename=\"any\"\n    )\n    assert uri == app.url_for(\"test_bp_static.static\")\n    assert uri == app.url_for(\"test_bp_static.static\", filename=\"any\")\n\n    request, response = app.test_client.get(uri, headers=headers)\n    assert response.status == 206\n    assert \"Content-Length\" in response.headers\n    assert \"Content-Range\" in response.headers\n    static_content = bytes(get_file_content(static_file_directory, file_name))[\n        12:\n    ]\n    assert int(response.headers[\"Content-Length\"]) == len(static_content)\n    assert response.body == static_content\n\n\n@pytest.mark.parametrize(\"file_name\", [\"test.file\", \"decode me.txt\"])\ndef test_static_content_range_back(file_name, static_file_directory):\n    app = Sanic(\"base\")\n    app.static(\n        \"/testing.file\",\n        get_file_path(static_file_directory, file_name),\n        use_content_range=True,\n    )\n\n    bp = Blueprint(\"test_bp_static\", url_prefix=\"/bp\")\n    bp.static(\n        \"/testing.file\",\n        get_file_path(static_file_directory, file_name),\n        use_content_range=True,\n    )\n    app.blueprint(bp)\n\n    headers = {\"Range\": \"bytes=-12\"}\n    uri = app.url_for(\"static\")\n    assert uri == \"/testing.file\"\n    assert uri == app.url_for(\"static\", name=\"static\")\n    assert uri == app.url_for(\"static\", name=\"static\", filename=\"any\")\n\n    request, response = app.test_client.get(uri, headers=headers)\n    assert response.status == 206\n    assert \"Content-Length\" in response.headers\n    assert \"Content-Range\" in response.headers\n    static_content = bytes(get_file_content(static_file_directory, file_name))[\n        -12:\n    ]\n    assert int(response.headers[\"Content-Length\"]) == len(static_content)\n    assert response.body == static_content\n\n    # blueprint\n    uri = app.url_for(\"static\", name=\"test_bp_static.static\")\n    assert uri == \"/bp/testing.file\"\n    assert uri == app.url_for(\n        \"static\", name=\"test_bp_static.static\", filename=\"any\"\n    )\n    assert uri == app.url_for(\"test_bp_static.static\")\n    assert uri == app.url_for(\"test_bp_static.static\", filename=\"any\")\n\n    request, response = app.test_client.get(uri, headers=headers)\n    assert response.status == 206\n    assert \"Content-Length\" in response.headers\n    assert \"Content-Range\" in response.headers\n    static_content = bytes(get_file_content(static_file_directory, file_name))[\n        -12:\n    ]\n    assert int(response.headers[\"Content-Length\"]) == len(static_content)\n    assert response.body == static_content\n\n\n@pytest.mark.parametrize(\"file_name\", [\"test.file\", \"decode me.txt\"])\ndef test_static_content_range_empty(file_name, static_file_directory):\n    app = Sanic(\"base\")\n    app.static(\n        \"/testing.file\",\n        get_file_path(static_file_directory, file_name),\n        use_content_range=True,\n    )\n\n    bp = Blueprint(\"test_bp_static\", url_prefix=\"/bp\")\n    bp.static(\n        \"/testing.file\",\n        get_file_path(static_file_directory, file_name),\n        use_content_range=True,\n    )\n    app.blueprint(bp)\n\n    uri = app.url_for(\"static\")\n    assert uri == \"/testing.file\"\n    assert uri == app.url_for(\"static\", name=\"static\")\n    assert uri == app.url_for(\"static\", name=\"static\", filename=\"any\")\n\n    request, response = app.test_client.get(uri)\n    assert response.status == 200\n    assert \"Content-Length\" in response.headers\n    assert \"Content-Range\" not in response.headers\n    assert int(response.headers[\"Content-Length\"]) == len(\n        get_file_content(static_file_directory, file_name)\n    )\n    assert response.body == bytes(\n        get_file_content(static_file_directory, file_name)\n    )\n\n    # blueprint\n    uri = app.url_for(\"static\", name=\"test_bp_static.static\")\n    assert uri == \"/bp/testing.file\"\n    assert uri == app.url_for(\n        \"static\", name=\"test_bp_static.static\", filename=\"any\"\n    )\n    assert uri == app.url_for(\"test_bp_static.static\")\n    assert uri == app.url_for(\"test_bp_static.static\", filename=\"any\")\n\n    request, response = app.test_client.get(uri)\n    assert response.status == 200\n    assert \"Content-Length\" in response.headers\n    assert \"Content-Range\" not in response.headers\n    assert int(response.headers[\"Content-Length\"]) == len(\n        get_file_content(static_file_directory, file_name)\n    )\n    assert response.body == bytes(\n        get_file_content(static_file_directory, file_name)\n    )\n\n\n@pytest.mark.parametrize(\"file_name\", [\"test.file\", \"decode me.txt\"])\ndef test_static_content_range_error(app, file_name, static_file_directory):\n    app = Sanic(\"base\")\n    app.static(\n        \"/testing.file\",\n        get_file_path(static_file_directory, file_name),\n        use_content_range=True,\n    )\n\n    bp = Blueprint(\"test_bp_static\", url_prefix=\"/bp\")\n    bp.static(\n        \"/testing.file\",\n        get_file_path(static_file_directory, file_name),\n        use_content_range=True,\n    )\n    app.blueprint(bp)\n\n    headers = {\"Range\": \"bytes=1-0\"}\n    uri = app.url_for(\"static\")\n    assert uri == \"/testing.file\"\n    assert uri == app.url_for(\"static\", name=\"static\")\n    assert uri == app.url_for(\"static\", name=\"static\", filename=\"any\")\n\n    request, response = app.test_client.get(uri, headers=headers)\n    assert response.status == 416\n    assert \"Content-Length\" in response.headers\n    assert \"Content-Range\" in response.headers\n    assert response.headers[\"Content-Range\"] == \"bytes */{}\".format(\n        len(get_file_content(static_file_directory, file_name)),\n    )\n\n    # blueprint\n    uri = app.url_for(\"static\", name=\"test_bp_static.static\")\n    assert uri == \"/bp/testing.file\"\n    assert uri == app.url_for(\n        \"static\", name=\"test_bp_static.static\", filename=\"any\"\n    )\n    assert uri == app.url_for(\"test_bp_static.static\")\n    assert uri == app.url_for(\"test_bp_static.static\", filename=\"any\")\n\n    request, response = app.test_client.get(uri, headers=headers)\n    assert response.status == 416\n    assert \"Content-Length\" in response.headers\n    assert \"Content-Range\" in response.headers\n    assert response.headers[\"Content-Range\"] == \"bytes */{}\".format(\n        len(get_file_content(static_file_directory, file_name)),\n    )\n"
  },
  {
    "path": "tests/test_utf8.py",
    "content": "from json import dumps as json_dumps\n\nfrom sanic.response import text\n\n\n# ------------------------------------------------------------ #\n#  UTF-8\n# ------------------------------------------------------------ #\n\n\ndef test_utf8_query_string(app):\n    @app.route(\"/\")\n    async def handler(request):\n        return text(\"OK\")\n\n    request, response = app.test_client.get(\"/\", params=[(\"utf8\", \"✓\")])\n    assert request.args.get(\"utf8\") == \"✓\"\n\n\ndef test_utf8_response(app):\n    @app.route(\"/\")\n    async def handler(request):\n        return text(\"✓\")\n\n    request, response = app.test_client.get(\"/\")\n    assert response.text == \"✓\"\n\n\ndef skip_test_utf8_route(app):\n    @app.route(\"/\")\n    async def handler(request):\n        return text(\"OK\")\n\n    # UTF-8 Paths are not supported\n    request, response = app.test_client.get(\"/✓\")\n    assert response.text == \"OK\"\n\n\ndef test_utf8_post_json(app):\n    @app.post(\"/\")\n    async def handler(request):\n        return text(\"OK\")\n\n    payload = {\"test\": \"✓\"}\n    headers = {\"content-type\": \"application/json\"}\n\n    request, response = app.test_client.post(\n        \"/\", data=json_dumps(payload), headers=headers\n    )\n\n    assert request.json.get(\"test\") == \"✓\"\n    assert response.text == \"OK\"\n"
  },
  {
    "path": "tests/test_utils.py",
    "content": "from os import environ\nfrom pathlib import Path\nfrom types import ModuleType\n\nimport pytest\n\nfrom sanic.exceptions import LoadFileException\nfrom sanic.utils import load_module_from_file_location\n\n\n@pytest.mark.parametrize(\n    \"location\",\n    (\n        Path(__file__).parent / \"static\" / \"app_test_config.py\",\n        str(Path(__file__).parent / \"static\" / \"app_test_config.py\"),\n        str(Path(__file__).parent / \"static\" / \"app_test_config.py\").encode(),\n    ),\n)\ndef test_load_module_from_file_location(location):\n    module = load_module_from_file_location(location)\n\n    assert isinstance(module, ModuleType)\n\n\ndef test_loaded_module_from_file_location_name():\n    module = load_module_from_file_location(\n        str(Path(__file__).parent / \"static\" / \"app_test_config.py\")\n    )\n\n    name = module.__name__\n    if \"C:\\\\\" in name:\n        name = name.split(\"\\\\\")[-1]\n    assert name == \"app_test_config\"\n\n\ndef test_load_module_from_file_location_with_non_existing_env_variable():\n    with pytest.raises(\n        LoadFileException,\n        match=\"The following environment variables are not set: MuuMilk\",\n    ):\n        load_module_from_file_location(\"${MuuMilk}\")\n\n\ndef test_load_module_from_file_location_using_env():\n    environ[\"APP_TEST_CONFIG\"] = \"static/app_test_config.py\"\n    location = str(Path(__file__).parent / \"${APP_TEST_CONFIG}\")\n    module = load_module_from_file_location(location)\n\n    assert isinstance(module, ModuleType)\n"
  },
  {
    "path": "tests/test_versioning.py",
    "content": "import pytest\n\nfrom sanic import Blueprint, text\n\n\n@pytest.fixture\ndef handler():\n    def handler(_):\n        return text(\"Done.\")\n\n    return handler\n\n\ndef test_route(app, handler):\n    app.route(\"/\", version=1)(handler)\n\n    _, response = app.test_client.get(\"/v1\")\n    assert response.status == 200\n\n\ndef test_bp(app, handler):\n    bp = Blueprint(\"Test\", version=1)\n    bp.route(\"/\")(handler)\n    app.blueprint(bp)\n\n    _, response = app.test_client.get(\"/v1\")\n    assert response.status == 200\n\n\ndef test_bp_use_route(app, handler):\n    bp = Blueprint(\"Test\", version=1)\n    bp.route(\"/\", version=1.1)(handler)\n    app.blueprint(bp)\n\n    _, response = app.test_client.get(\"/v1.1\")\n    assert response.status == 200\n\n\ndef test_bp_group(app, handler):\n    bp = Blueprint(\"Test\")\n    bp.route(\"/\")(handler)\n    group = Blueprint.group(bp, version=1)\n    app.blueprint(group)\n\n    _, response = app.test_client.get(\"/v1\")\n    assert response.status == 200\n\n\ndef test_bp_group_use_bp(app, handler):\n    bp = Blueprint(\"Test\", version=1.1)\n    bp.route(\"/\")(handler)\n    group = Blueprint.group(bp, version=1)\n    app.blueprint(group)\n\n    _, response = app.test_client.get(\"/v1.1\")\n    assert response.status == 200\n\n\ndef test_bp_group_use_registration(app, handler):\n    bp = Blueprint(\"Test\", version=1.1)\n    bp.route(\"/\")(handler)\n    group = Blueprint.group(bp, version=1)\n    app.blueprint(group, version=1.2)\n\n    _, response = app.test_client.get(\"/v1.2\")\n    assert response.status == 200\n\n\ndef test_bp_group_use_route(app, handler):\n    bp = Blueprint(\"Test\", version=1.1)\n    bp.route(\"/\", version=1.3)(handler)\n    group = Blueprint.group(bp, version=1)\n    app.blueprint(group, version=1.2)\n\n    _, response = app.test_client.get(\"/v1.3\")\n    assert response.status == 200\n\n\ndef test_version_prefix_route(app, handler):\n    app.route(\"/\", version=1, version_prefix=\"/api/v\")(handler)\n\n    _, response = app.test_client.get(\"/api/v1\")\n    assert response.status == 200\n\n\ndef test_version_prefix_bp(app, handler):\n    bp = Blueprint(\"Test\", version=1, version_prefix=\"/api/v\")\n    bp.route(\"/\")(handler)\n    app.blueprint(bp)\n\n    _, response = app.test_client.get(\"/api/v1\")\n    assert response.status == 200\n\n\ndef test_version_prefix_bp_use_route(app, handler):\n    bp = Blueprint(\"Test\", version=1, version_prefix=\"/ignore/v\")\n    bp.route(\"/\", version=1.1, version_prefix=\"/api/v\")(handler)\n    app.blueprint(bp)\n\n    _, response = app.test_client.get(\"/api/v1.1\")\n    assert response.status == 200\n\n\ndef test_version_prefix_bp_group(app, handler):\n    bp = Blueprint(\"Test\")\n    bp.route(\"/\")(handler)\n    group = Blueprint.group(bp, version=1, version_prefix=\"/api/v\")\n    app.blueprint(group)\n\n    _, response = app.test_client.get(\"/api/v1\")\n    assert response.status == 200\n\n\ndef test_version_prefix_bp_group_use_bp(app, handler):\n    bp = Blueprint(\"Test\", version=1.1, version_prefix=\"/api/v\")\n    bp.route(\"/\")(handler)\n    group = Blueprint.group(bp, version=1, version_prefix=\"/ignore/v\")\n    app.blueprint(group)\n\n    _, response = app.test_client.get(\"/api/v1.1\")\n    assert response.status == 200\n\n\ndef test_version_prefix_bp_group_use_registration(app, handler):\n    bp = Blueprint(\"Test\", version=1.1, version_prefix=\"/alsoignore/v\")\n    bp.route(\"/\")(handler)\n    group = Blueprint.group(bp, version=1, version_prefix=\"/ignore/v\")\n    app.blueprint(group, version=1.2, version_prefix=\"/api/v\")\n\n    _, response = app.test_client.get(\"/api/v1.2\")\n    assert response.status == 200\n\n\ndef test_version_prefix_bp_group_use_route(app, handler):\n    bp = Blueprint(\"Test\", version=1.1, version_prefix=\"/alsoignore/v\")\n    bp.route(\"/\", version=1.3, version_prefix=\"/api/v\")(handler)\n    group = Blueprint.group(bp, version=1, version_prefix=\"/ignore/v\")\n    app.blueprint(group, version=1.2, version_prefix=\"/stillignoring/v\")\n\n    _, response = app.test_client.get(\"/api/v1.3\")\n    assert response.status == 200\n"
  },
  {
    "path": "tests/test_vhosts.py",
    "content": "import pytest\n\nfrom sanic_routing.exceptions import RouteExists\n\nfrom sanic import Sanic\nfrom sanic.response import text\n\n\ndef test_vhosts():\n    app = Sanic(\"app\")\n\n    @app.route(\"/\", host=\"example.com\")\n    async def handler1(request):\n        return text(\"You're at example.com!\")\n\n    @app.route(\"/\", host=\"subdomain.example.com\")\n    async def handler2(request):\n        return text(\"You're at subdomain.example.com!\")\n\n    headers = {\"Host\": \"example.com\"}\n    request, response = app.test_client.get(\"/\", headers=headers)\n    assert response.text == \"You're at example.com!\"\n\n    headers = {\"Host\": \"subdomain.example.com\"}\n    request, response = app.test_client.get(\"/\", headers=headers)\n    assert response.text == \"You're at subdomain.example.com!\"\n\n\ndef test_vhosts_with_list(app):\n    @app.route(\"/\", host=[\"hello.com\", \"world.com\"])\n    async def handler(request):\n        return text(\"Hello, world!\")\n\n    headers = {\"Host\": \"hello.com\"}\n    request, response = app.test_client.get(\"/\", headers=headers)\n    assert response.text == \"Hello, world!\"\n\n    headers = {\"Host\": \"world.com\"}\n    request, response = app.test_client.get(\"/\", headers=headers)\n    assert response.text == \"Hello, world!\"\n\n\ndef test_vhosts_with_defaults(app):\n    @app.route(\"/\", host=\"hello.com\")\n    async def handler1(request):\n        return text(\"Hello, world!\")\n\n    with pytest.raises(RouteExists):\n\n        @app.route(\"/\")\n        async def handler2(request):\n            return text(\"default\")\n\n    headers = {\"Host\": \"hello.com\"}\n    request, response = app.test_client.get(\"/\", headers=headers)\n    assert response.text == \"Hello, world!\"\n"
  },
  {
    "path": "tests/test_views.py",
    "content": "import pytest\n\nfrom sanic.blueprints import Blueprint\nfrom sanic.constants import HTTP_METHODS\nfrom sanic.request import Request\nfrom sanic.response import HTTPResponse, text\nfrom sanic.views import HTTPMethodView\n\n\n@pytest.mark.parametrize(\"method\", HTTP_METHODS)\ndef test_methods(app, method):\n    class DummyView(HTTPMethodView):\n        async def get(self, request):\n            return text(\"\", headers={\"method\": \"GET\"})\n\n        def post(self, request):\n            return text(\"\", headers={\"method\": \"POST\"})\n\n        async def put(self, request):\n            return text(\"\", headers={\"method\": \"PUT\"})\n\n        def head(self, request):\n            return text(\"\", headers={\"method\": \"HEAD\"})\n\n        def options(self, request):\n            return text(\"\", headers={\"method\": \"OPTIONS\"})\n\n        async def patch(self, request):\n            return text(\"\", headers={\"method\": \"PATCH\"})\n\n        def delete(self, request):\n            return text(\"\", headers={\"method\": \"DELETE\"})\n\n    app.add_route(DummyView.as_view(), \"/\")\n\n    request, response = getattr(app.test_client, method.lower())(\"/\")\n    assert response.headers[\"method\"] == method\n\n\ndef test_unexisting_methods(app):\n    class DummyView(HTTPMethodView):\n        def get(self, request):\n            return text(\"I am get method\")\n\n    app.add_route(DummyView.as_view(), \"/\")\n    request, response = app.test_client.get(\"/\")\n    assert response.body == b\"I am get method\"\n    request, response = app.test_client.post(\"/\")\n    assert b\"Method POST not allowed for URL /\" in response.body\n\n\ndef test_argument_methods(app):\n    class DummyView(HTTPMethodView):\n        def get(self, request, my_param_here):\n            return text(\"I am get method with %s\" % my_param_here)\n\n    app.add_route(DummyView.as_view(), \"/<my_param_here>\")\n\n    request, response = app.test_client.get(\"/test123\")\n\n    assert response.text == \"I am get method with test123\"\n\n\ndef test_with_bp(app):\n    bp = Blueprint(\"test_text\")\n\n    class DummyView(HTTPMethodView):\n        def get(self, request):\n            return text(\"I am get method\")\n\n    bp.add_route(DummyView.as_view(), \"/\")\n\n    app.blueprint(bp)\n    request, response = app.test_client.get(\"/\")\n\n    assert response.text == \"I am get method\"\n\n\ndef test_with_attach(app):\n    class DummyView(HTTPMethodView):\n        def get(self, request):\n            return text(\"I am get method\")\n\n    DummyView.attach(app, \"/\")\n\n    request, response = app.test_client.get(\"/\")\n\n    assert response.text == \"I am get method\"\n\n\ndef test_with_sub_init(app):\n    class DummyView(HTTPMethodView, attach=app, uri=\"/\"):\n        def get(self, request):\n            return text(\"I am get method\")\n\n    request, response = app.test_client.get(\"/\")\n\n    assert response.text == \"I am get method\"\n\n\ndef test_with_attach_and_bp(app):\n    bp = Blueprint(\"test_text\")\n\n    class DummyView(HTTPMethodView):\n        def get(self, request):\n            return text(\"I am get method\")\n\n    DummyView.attach(bp, \"/\")\n\n    app.blueprint(bp)\n    request, response = app.test_client.get(\"/\")\n\n    assert response.text == \"I am get method\"\n\n\ndef test_with_sub_init_and_bp(app):\n    bp = Blueprint(\"test_text\")\n\n    class DummyView(HTTPMethodView, attach=bp, uri=\"/\"):\n        def get(self, request):\n            return text(\"I am get method\")\n\n    app.blueprint(bp)\n    request, response = app.test_client.get(\"/\")\n\n    assert response.text == \"I am get method\"\n\n\ndef test_with_bp_with_url_prefix(app):\n    bp = Blueprint(\"test_text\", url_prefix=\"/test1\")\n\n    class DummyView(HTTPMethodView):\n        def get(self, request):\n            return text(\"I am get method\")\n\n    bp.add_route(DummyView.as_view(), \"/\")\n\n    app.blueprint(bp)\n    request, response = app.test_client.get(\"/test1/\")\n\n    assert response.text == \"I am get method\"\n\n\ndef test_with_middleware(app):\n    class DummyView(HTTPMethodView):\n        def get(self, request):\n            return text(\"I am get method\")\n\n    app.add_route(DummyView.as_view(), \"/\")\n\n    results = []\n\n    @app.middleware\n    async def handler(request):\n        results.append(request)\n\n    request, response = app.test_client.get(\"/\")\n\n    assert response.text == \"I am get method\"\n    assert type(results[0]) is Request\n\n\ndef test_with_middleware_response(app):\n    results = []\n\n    @app.middleware(\"request\")\n    async def process_request(request):\n        results.append(request)\n\n    @app.middleware(\"response\")\n    async def process_response(request, response):\n        results.append(request)\n        results.append(response)\n\n    class DummyView(HTTPMethodView):\n        def get(self, request):\n            return text(\"I am get method\")\n\n    app.add_route(DummyView.as_view(), \"/\")\n\n    request, response = app.test_client.get(\"/\")\n\n    assert response.text == \"I am get method\"\n    assert type(results[0]) is Request\n    assert type(results[1]) is Request\n    assert isinstance(results[2], HTTPResponse)\n\n\ndef test_with_custom_class_methods(app):\n    class DummyView(HTTPMethodView):\n        global_var = 0\n\n        def _iternal_method(self):\n            self.global_var += 10\n\n        def get(self, request):\n            self._iternal_method()\n            return text(f\"I am get method and global var is {self.global_var}\")\n\n    app.add_route(DummyView.as_view(), \"/\")\n    request, response = app.test_client.get(\"/\")\n    assert response.text == \"I am get method and global var is 10\"\n\n\ndef test_with_decorator(app):\n    results = []\n\n    def stupid_decorator(view):\n        def decorator(*args, **kwargs):\n            results.append(1)\n            return view(*args, **kwargs)\n\n        return decorator\n\n    class DummyView(HTTPMethodView):\n        decorators = [stupid_decorator]\n\n        def get(self, request):\n            return text(\"I am get method\")\n\n    app.add_route(DummyView.as_view(), \"/\")\n    request, response = app.test_client.get(\"/\")\n    assert response.text == \"I am get method\"\n    assert results[0] == 1\n"
  },
  {
    "path": "tests/test_websockets.py",
    "content": "import re\n\nfrom asyncio import Event, Queue, TimeoutError\nfrom unittest.mock import Mock, call\n\nimport pytest\n\nfrom websockets.frames import CTRL_OPCODES, DATA_OPCODES, OP_TEXT, Frame\n\nfrom sanic.exceptions import ServerError\nfrom sanic.server.websockets.frame import WebsocketFrameAssembler\n\n\ntry:\n    from unittest.mock import AsyncMock\nexcept ImportError:\n    from tests.asyncmock import AsyncMock  # type: ignore\n\n\n@pytest.mark.asyncio\nasync def test_ws_frame_get_message_incomplete_timeout_0():\n    assembler = WebsocketFrameAssembler(Mock())\n    assembler.message_complete = AsyncMock(spec=Event)\n    assembler.message_complete.is_set = Mock(return_value=False)\n    data = await assembler.get(0)\n\n    assert data is None\n    assembler.message_complete.is_set.assert_called_once()\n\n\n@pytest.mark.asyncio\nasync def test_ws_frame_get_message_in_progress():\n    assembler = WebsocketFrameAssembler(Mock())\n    assembler.get_in_progress = True\n\n    message = re.escape(\n        \"Called get() on Websocket frame assembler \"\n        \"while asynchronous get is already in progress.\"\n    )\n\n    with pytest.raises(ServerError, match=message):\n        await assembler.get()\n\n\n@pytest.mark.asyncio\nasync def test_ws_frame_get_message_incomplete():\n    assembler = WebsocketFrameAssembler(Mock())\n    assembler.message_complete.wait = AsyncMock(return_value=True)\n    assembler.message_complete.is_set = Mock(return_value=False)\n    data = await assembler.get()\n\n    assert data is None\n    assembler.message_complete.wait.assert_awaited_once()\n\n\n@pytest.mark.asyncio\nasync def test_ws_frame_get_message():\n    assembler = WebsocketFrameAssembler(Mock())\n    assembler.message_complete.wait = AsyncMock(return_value=True)\n    assembler.message_complete.is_set = Mock(return_value=True)\n    data = await assembler.get()\n\n    assert data == b\"\"\n    assembler.message_complete.wait.assert_awaited_once()\n\n\n@pytest.mark.asyncio\nasync def test_ws_frame_get_message_with_timeout():\n    assembler = WebsocketFrameAssembler(Mock())\n    assembler.message_complete.wait = AsyncMock(return_value=True)\n    assembler.message_complete.is_set = Mock(return_value=True)\n    data = await assembler.get(0.1)\n\n    assert data == b\"\"\n    assembler.message_complete.wait.assert_awaited_once()\n    assert assembler.message_complete.is_set.call_count == 2\n\n\n@pytest.mark.asyncio\nasync def test_ws_frame_get_message_with_timeouterror():\n    assembler = WebsocketFrameAssembler(Mock())\n    assembler.message_complete.wait = AsyncMock(return_value=True)\n    assembler.message_complete.is_set = Mock(return_value=True)\n    assembler.message_complete.wait.side_effect = TimeoutError(\"...\")\n    data = await assembler.get(0.1)\n\n    assert data == b\"\"\n    assembler.message_complete.wait.assert_awaited_once()\n    assert assembler.message_complete.is_set.call_count == 2\n\n\n@pytest.mark.asyncio\nasync def test_ws_frame_get_not_completed():\n    assembler = WebsocketFrameAssembler(Mock())\n    assembler.message_complete = AsyncMock(spec=Event)\n    assembler.message_complete.is_set = Mock(return_value=False)\n    data = await assembler.get()\n\n    assert data is None\n\n\n@pytest.mark.asyncio\nasync def test_ws_frame_get_not_completed_start():\n    assembler = WebsocketFrameAssembler(Mock())\n    assembler.message_complete = AsyncMock(spec=Event)\n    assembler.message_complete.is_set = Mock(side_effect=[False, True])\n    data = await assembler.get(0.1)\n\n    assert data is None\n\n\n@pytest.mark.asyncio\nasync def test_ws_frame_get_paused():\n    assembler = WebsocketFrameAssembler(Mock())\n    assembler.message_complete = AsyncMock(spec=Event)\n    assembler.message_complete.is_set = Mock(side_effect=[False, True])\n    assembler.paused = True\n    data = await assembler.get()\n\n    assert data is None\n    assembler.protocol.resume_frames.assert_called_once()\n\n\n@pytest.mark.asyncio\nasync def test_ws_frame_get_data():\n    assembler = WebsocketFrameAssembler(Mock())\n    assembler.message_complete = AsyncMock(spec=Event)\n    assembler.message_complete.is_set = Mock(return_value=True)\n    assembler.chunks = [b\"foo\", b\"bar\"]\n    data = await assembler.get()\n\n    assert data == b\"foobar\"\n\n\n@pytest.mark.asyncio\nasync def test_ws_frame_get_iter_in_progress():\n    assembler = WebsocketFrameAssembler(Mock())\n    assembler.get_in_progress = True\n\n    message = re.escape(\n        \"Called get_iter on Websocket frame assembler \"\n        \"while asynchronous get is already in progress.\"\n    )\n\n    with pytest.raises(ServerError, match=message):\n        [x async for x in assembler.get_iter()]\n\n\n@pytest.mark.asyncio\nasync def test_ws_frame_get_iter_none_in_queue():\n    assembler = WebsocketFrameAssembler(Mock())\n    assembler.message_complete.set()\n    assembler.chunks = [b\"foo\", b\"bar\"]\n\n    chunks = [x async for x in assembler.get_iter()]\n\n    assert chunks == [b\"foo\", b\"bar\"]\n\n\n@pytest.mark.asyncio\nasync def test_ws_frame_get_iter_paused():\n    assembler = WebsocketFrameAssembler(Mock())\n    assembler.message_complete.set()\n    assembler.paused = True\n\n    [x async for x in assembler.get_iter()]\n    assembler.protocol.resume_frames.assert_called_once()\n\n\n@pytest.mark.asyncio\n@pytest.mark.parametrize(\"opcode\", DATA_OPCODES)\nasync def test_ws_frame_put_not_fetched(opcode):\n    assembler = WebsocketFrameAssembler(Mock())\n    assembler.message_fetched.set()\n\n    message = re.escape(\n        \"Websocket put() got a new message when the previous message was \"\n        \"not yet fetched.\"\n    )\n    with pytest.raises(ServerError, match=message):\n        await assembler.put(Frame(opcode, b\"\"))\n\n\n@pytest.mark.asyncio\n@pytest.mark.parametrize(\"opcode\", DATA_OPCODES)\nasync def test_ws_frame_put_fetched(opcode):\n    assembler = WebsocketFrameAssembler(Mock())\n    assembler.message_fetched = AsyncMock()\n    assembler.message_fetched.is_set = Mock(return_value=False)\n\n    await assembler.put(Frame(opcode, b\"\"))\n    assembler.message_fetched.wait.assert_awaited_once()\n    assembler.message_fetched.clear.assert_called_once()\n\n\n@pytest.mark.asyncio\n@pytest.mark.parametrize(\"opcode\", DATA_OPCODES)\nasync def test_ws_frame_put_message_complete(opcode):\n    assembler = WebsocketFrameAssembler(Mock())\n    assembler.message_complete.set()\n\n    message = re.escape(\n        \"Websocket put() got a new message when a message was \"\n        \"already in its chamber.\"\n    )\n    with pytest.raises(ServerError, match=message):\n        await assembler.put(Frame(opcode, b\"\"))\n\n\n@pytest.mark.asyncio\n@pytest.mark.parametrize(\"opcode\", DATA_OPCODES)\nasync def test_ws_frame_put_message_into_queue(opcode):\n    foo = \"foo\" if (opcode == OP_TEXT) else b\"foo\"\n    assembler = WebsocketFrameAssembler(Mock())\n    assembler.chunks_queue = AsyncMock(spec=Queue)\n    assembler.message_fetched = AsyncMock()\n    assembler.message_fetched.is_set = Mock(return_value=False)\n    await assembler.put(Frame(opcode, b\"foo\"))\n\n    assert assembler.chunks_queue.put.call_args_list == [call(foo), call(None)]\n\n\n@pytest.mark.asyncio\n@pytest.mark.parametrize(\"opcode\", DATA_OPCODES)\nasync def test_ws_frame_put_not_fin(opcode):\n    assembler = WebsocketFrameAssembler(Mock())\n\n    retval = await assembler.put(Frame(opcode, b\"foo\", fin=False))\n\n    assert retval is None\n\n\n@pytest.mark.asyncio\n@pytest.mark.parametrize(\"opcode\", CTRL_OPCODES)\nasync def test_ws_frame_put_skip_ctrl(opcode):\n    assembler = WebsocketFrameAssembler(Mock())\n\n    retval = await assembler.put(Frame(opcode, b\"\"))\n\n    assert retval is None\n"
  },
  {
    "path": "tests/test_ws_handlers.py",
    "content": "import base64\nimport secrets\n\nfrom collections.abc import Coroutine\nfrom typing import Any, Callable\n\nimport pytest\n\nfrom websockets.client import WebSocketClientProtocol\n\nfrom sanic import Request, Sanic, Websocket\n\n\nMimicClientType = Callable[\n    [WebSocketClientProtocol], Coroutine[None, None, Any]\n]\n\n\n@pytest.fixture\ndef simple_ws_mimic_client():\n    async def client_mimic(ws: WebSocketClientProtocol):\n        await ws.send(\"test 1\")\n        await ws.recv()\n        await ws.send(\"test 2\")\n        await ws.recv()\n\n    return client_mimic\n\n\ndef signalapp(app):\n    @app.signal(\"websocket.handler.before\")\n    async def ws_before(request: Request, websocket: Websocket):\n        app.ctx.seq.append(\"before\")\n        print(\"before\")\n        await websocket.send(\"before: \" + await websocket.recv())\n        print(\"before2\")\n\n    @app.signal(\"websocket.handler.after\")\n    async def ws_after(request: Request, websocket: Websocket):\n        app.ctx.seq.append(\"after\")\n        await websocket.send(\"after: \" + await websocket.recv())\n        await websocket.recv()\n\n    @app.signal(\"websocket.handler.exception\")\n    async def ws_exception(\n        request: Request, websocket: Websocket, exception: Exception\n    ):\n        app.ctx.seq.append(\"exception\")\n        await websocket.send(f\"exception: {exception}\")\n        await websocket.recv()\n\n    @app.websocket(\"/ws\")\n    async def ws_handler(request: Request, ws: Websocket):\n        app.ctx.seq.append(\"ws\")\n\n    @app.websocket(\"/wserror\")\n    async def ws_error(request: Request, ws: Websocket):\n        print(\"wserr\")\n        app.ctx.seq.append(\"wserror\")\n        raise Exception(await ws.recv())\n        print(\"wserr2\")\n\n\ndef test_ws_handler(\n    app: Sanic,\n    simple_ws_mimic_client: MimicClientType,\n):\n    @app.websocket(\"/ws\")\n    async def ws_echo_handler(request: Request, ws: Websocket):\n        while True:\n            msg = await ws.recv()\n            await ws.send(msg)\n\n    _, ws_proxy = app.test_client.websocket(\n        \"/ws\", mimic=simple_ws_mimic_client\n    )\n    assert ws_proxy.client_sent == [\"test 1\", \"test 2\", \"\"]\n    assert ws_proxy.client_received == [\"test 1\", \"test 2\"]\n\n\ndef test_ws_handler_invalid_upgrade(app: Sanic):\n    @app.websocket(\"/ws\")\n    async def ws_echo_handler(request: Request, ws: Websocket):\n        async for msg in ws:\n            await ws.send(msg)\n\n    ws_key = base64.b64encode(secrets.token_bytes(16)).decode(\"utf-8\")\n    invalid_upgrade_headers = {\n        \"Upgrade\": \"websocket\",\n        # \"Connection\": \"Upgrade\",\n        \"Sec-WebSocket-Key\": ws_key,\n        \"Sec-WebSocket-Version\": \"13\",\n    }\n    _, response = app.test_client.get(\"/ws\", headers=invalid_upgrade_headers)\n    assert response.status == 426\n\n\ndef test_ws_handler_async_for(\n    app: Sanic,\n    simple_ws_mimic_client: MimicClientType,\n):\n    @app.websocket(\"/ws\")\n    async def ws_echo_handler(request: Request, ws: Websocket):\n        async for msg in ws:\n            await ws.send(msg)\n\n    _, ws_proxy = app.test_client.websocket(\n        \"/ws\", mimic=simple_ws_mimic_client\n    )\n    assert ws_proxy.client_sent == [\"test 1\", \"test 2\", \"\"]\n    assert ws_proxy.client_received == [\"test 1\", \"test 2\"]\n\n\n@pytest.mark.parametrize(\"proxy\", [\"\", \"proxy\", \"servername\"])\ndef test_request_url(\n    app: Sanic,\n    simple_ws_mimic_client: MimicClientType,\n    proxy: str,\n):\n    @app.websocket(\"/ws\")\n    async def ws_url_handler(request: Request, ws: Websocket):\n        request.headers[\"forwarded\"] = (\n            \"for=[2001:db8::1];proto=https;host=example.com;by=proxy\"\n        )\n\n        await ws.recv()\n        await ws.send(request.url)\n        await ws.recv()\n        await ws.send(request.url_for(\"ws_url_handler\"))\n        await ws.recv()\n\n    app.config.FORWARDED_SECRET = proxy\n    app.config.SERVER_NAME = (\n        \"https://example.com\" if proxy == \"servername\" else \"\"\n    )\n    _, ws_proxy = app.test_client.websocket(\n        \"/ws\",\n        mimic=simple_ws_mimic_client,\n    )\n    assert ws_proxy.client_sent == [\"test 1\", \"test 2\", \"\"]\n    assert ws_proxy.client_received[0] == ws_proxy.client_received[1]\n    if proxy == \"servername\":\n        assert ws_proxy.client_received[0] == \"wss://example.com/ws\"\n        assert ws_proxy.client_received[1] == \"wss://example.com/ws\"\n    else:\n        assert ws_proxy.client_received[0].startswith(\"ws://127.0.0.1\")\n        assert ws_proxy.client_received[1].startswith(\"ws://127.0.0.1\")\n\n\ndef test_ws_signals(\n    app: Sanic,\n    simple_ws_mimic_client: MimicClientType,\n):\n    signalapp(app)\n\n    app.ctx.seq = []\n    _, ws_proxy = app.test_client.websocket(\n        \"/ws\", mimic=simple_ws_mimic_client\n    )\n    assert ws_proxy.client_received == [\"before: test 1\", \"after: test 2\"]\n    assert app.ctx.seq == [\"before\", \"ws\", \"after\"]\n\n\ndef test_ws_signals_exception(\n    app: Sanic,\n    simple_ws_mimic_client: MimicClientType,\n):\n    signalapp(app)\n\n    app.ctx.seq = []\n    _, ws_proxy = app.test_client.websocket(\n        \"/wserror\", mimic=simple_ws_mimic_client\n    )\n    assert ws_proxy.client_received == [\"before: test 1\", \"exception: test 2\"]\n    assert app.ctx.seq == [\"before\", \"wserror\", \"exception\"]\n"
  },
  {
    "path": "tests/typing/samples/app_custom_config.py",
    "content": "from sanic import Sanic\nfrom sanic.config import Config\n\n\nclass CustomConfig(Config):\n    pass\n\n\napp = Sanic(\"test\", config=CustomConfig())\nreveal_type(app)  # noqa\n"
  },
  {
    "path": "tests/typing/samples/app_custom_ctx.py",
    "content": "from sanic import Sanic\n\n\nclass Foo:\n    pass\n\n\napp = Sanic(\"test\", ctx=Foo())\nreveal_type(app)  # noqa\n"
  },
  {
    "path": "tests/typing/samples/app_default.py",
    "content": "from sanic import Sanic\n\n\napp = Sanic(\"test\")\nreveal_type(app)  # noqa\n"
  },
  {
    "path": "tests/typing/samples/app_fully_custom.py",
    "content": "from sanic import Sanic\nfrom sanic.config import Config\n\n\nclass CustomConfig(Config):\n    pass\n\n\nclass Foo:\n    pass\n\n\napp = Sanic(\"test\", config=CustomConfig(), ctx=Foo())\nreveal_type(app)  # noqa\n"
  },
  {
    "path": "tests/typing/samples/request_custom_ctx.py",
    "content": "from types import SimpleNamespace\n\nfrom sanic import Request, Sanic\nfrom sanic.config import Config\n\n\nclass Foo:\n    pass\n\n\napp = Sanic(\"test\")\n\n\n@app.get(\"/\")\nasync def handler(request: Request[Sanic[Config, SimpleNamespace], Foo]):\n    reveal_type(request.ctx)  # noqa\n    reveal_type(request.app)  # noqa\n"
  },
  {
    "path": "tests/typing/samples/request_custom_sanic.py",
    "content": "from types import SimpleNamespace\n\nfrom sanic import Request, Sanic\nfrom sanic.config import Config\n\n\nclass CustomConfig(Config):\n    pass\n\n\napp = Sanic(\"test\", config=CustomConfig())\n\n\n@app.get(\"/\")\nasync def handler(\n    request: Request[Sanic[CustomConfig, SimpleNamespace], SimpleNamespace],\n):\n    reveal_type(request.ctx)  # noqa\n    reveal_type(request.app)  # noqa\n"
  },
  {
    "path": "tests/typing/samples/request_fully_custom.py",
    "content": "from sanic import Request, Sanic\nfrom sanic.config import Config\n\n\nclass CustomConfig(Config):\n    pass\n\n\nclass Foo:\n    pass\n\n\nclass RequestContext:\n    foo: Foo\n\n\nclass CustomRequest(Request[Sanic[CustomConfig, Foo], RequestContext]):\n    @staticmethod\n    def make_context() -> RequestContext:\n        ctx = RequestContext()\n        ctx.foo = Foo()\n        return ctx\n\n\napp = Sanic(\n    \"test\", config=CustomConfig(), ctx=Foo(), request_class=CustomRequest\n)\n\n\n@app.get(\"/\")\nasync def handler(request: CustomRequest):\n    reveal_type(request)  # noqa\n    reveal_type(request.ctx)  # noqa\n    reveal_type(request.app)  # noqa\n"
  },
  {
    "path": "tests/typing/test_typing.py",
    "content": "# flake8: noqa: E501\n\nimport subprocess\n\nfrom pathlib import Path\n\nimport pytest\n\n\nCURRENT_DIR = Path(__file__).parent\n\n\ndef run_check(path_location: str) -> str:\n    \"\"\"Use mypy to check the given path location and return the output.\"\"\"\n\n    mypy_path = \"mypy\"\n    path = CURRENT_DIR / path_location\n    command = [mypy_path, path.resolve().as_posix()]\n\n    process = subprocess.run(\n        command,\n        capture_output=True,\n        text=True,\n    )\n    output = process.stdout + process.stderr\n    return output\n\n\n@pytest.mark.parametrize(\n    \"path_location,expected\",\n    (\n        (\n            \"app_default.py\",\n            [\n                (\n                    \"sanic.app.Sanic[sanic.config.Config, types.SimpleNamespace]\",\n                    5,\n                )\n            ],\n        ),\n        (\n            \"app_custom_config.py\",\n            [\n                (\n                    \"sanic.app.Sanic[app_custom_config.CustomConfig, types.SimpleNamespace]\",\n                    10,\n                )\n            ],\n        ),\n        (\n            \"app_custom_ctx.py\",\n            [(\"sanic.app.Sanic[sanic.config.Config, app_custom_ctx.Foo]\", 9)],\n        ),\n        (\n            \"app_fully_custom.py\",\n            [\n                (\n                    \"sanic.app.Sanic[app_fully_custom.CustomConfig, app_fully_custom.Foo]\",\n                    14,\n                )\n            ],\n        ),\n        (\n            \"request_custom_sanic.py\",\n            [\n                (\"types.SimpleNamespace\", 18),\n                (\n                    \"sanic.app.Sanic[request_custom_sanic.CustomConfig, types.SimpleNamespace]\",\n                    19,\n                ),\n            ],\n        ),\n        (\n            \"request_custom_ctx.py\",\n            [\n                (\"request_custom_ctx.Foo\", 16),\n                (\n                    \"sanic.app.Sanic[sanic.config.Config, types.SimpleNamespace]\",\n                    17,\n                ),\n            ],\n        ),\n        (\n            \"request_fully_custom.py\",\n            [\n                (\"request_fully_custom.CustomRequest\", 32),\n                (\"request_fully_custom.RequestContext\", 33),\n                (\n                    \"sanic.app.Sanic[request_fully_custom.CustomConfig, request_fully_custom.Foo]\",\n                    34,\n                ),\n            ],\n        ),\n    ),\n)\ndef test_check_app_default(\n    path_location: str, expected: list[tuple[str, int]]\n) -> None:\n    output = run_check(f\"samples/{path_location}\")\n\n    for text, number in expected:\n        current = CURRENT_DIR / f\"samples/{path_location}\"\n        path = current.relative_to(CURRENT_DIR.parent)\n\n        target = Path.cwd()\n        while True:\n            note = _text_from_path(current, path, target, number, text)\n            try:\n                assert note in output, output\n            except AssertionError:\n                target = target.parent\n                if not target.exists() or target == target.parent:\n                    raise\n            else:\n                break\n\n\ndef _text_from_path(\n    base: Path, path: Path, target: Path, number: int, text: str\n) -> str:\n    relative_to_cwd = base.relative_to(target)\n    prefix = \".\".join(relative_to_cwd.parts[:-1])\n    text = text.replace(path.stem, f\"{prefix}.{path.stem}\")\n    return f'{path}:{number}: note: Revealed type is \"{text}\"'\n"
  },
  {
    "path": "tests/worker/conftest.py",
    "content": "import pytest\n\n\ndef pytest_collection_modifyitems(items):\n    \"\"\"Group all worker tests to run on same xdist worker.\n\n    These tests spawn processes and can conflict with parallel execution.\n    \"\"\"\n    for item in items:\n        if \"/worker/\" in str(item.fspath):\n            item.add_marker(pytest.mark.xdist_group(name=\"process_spawning\"))\n"
  },
  {
    "path": "tests/worker/test_inspector.py",
    "content": "try:  # no cov\n    from ujson import dumps\nexcept ModuleNotFoundError:  # no cov\n    from json import dumps  # type: ignore\n\nfrom datetime import datetime\nfrom unittest.mock import Mock, patch\nfrom urllib.error import URLError\n\nimport pytest\n\nfrom sanic_testing import TestManager\n\nfrom sanic.cli.inspector_client import InspectorClient\nfrom sanic.helpers import Default\nfrom sanic.log import Colors\nfrom sanic.worker.inspector import Inspector\n\n\nDATA = {\n    \"info\": {\n        \"packages\": [\"foo\"],\n    },\n    \"extra\": {\n        \"more\": \"data\",\n    },\n    \"workers\": {\"Worker-Name\": {\"some\": \"state\"}},\n}\nFULL_SERIALIZED = dumps({\"result\": DATA})\nOUT_SERIALIZED = dumps(DATA)\n\n\nclass FooInspector(Inspector):\n    async def foo(self, bar):\n        return f\"bar is {bar}\"\n\n\n@pytest.fixture\ndef publisher():\n    publisher = Mock()\n    return publisher\n\n\n@pytest.fixture\ndef inspector(publisher):\n    inspector = FooInspector(\n        publisher, {}, {}, \"localhost\", 9999, \"\", Default(), Default()\n    )\n    inspector(False)\n    return inspector\n\n\n@pytest.fixture\ndef http_client(inspector):\n    manager = TestManager(inspector.app)\n    return manager.test_client\n\n\n@pytest.mark.parametrize(\"command\", (\"info\",))\n@patch(\"sanic.cli.inspector_client.sys.stdout.write\")\ndef test_send_inspect(write, urlopen, command: str):\n    urlopen.read.return_value = FULL_SERIALIZED.encode()\n    InspectorClient(\"localhost\", 9999, False, False, None).do(command)\n    write.assert_called()\n    write.reset_mock()\n    InspectorClient(\"localhost\", 9999, False, True, None).do(command)\n    write.assert_called_with(OUT_SERIALIZED + \"\\n\")\n\n\n@patch(\"sanic.cli.inspector_client.sys\")\ndef test_send_inspect_conn_refused(sys: Mock, urlopen):\n    urlopen.side_effect = URLError(\"\")\n    InspectorClient(\"localhost\", 9999, False, False, None).do(\"info\")\n\n    message = (\n        f\"{Colors.RED}Could not connect to inspector at: \"\n        f\"{Colors.YELLOW}http://localhost:9999{Colors.END}\\n\"\n        \"Either the application is not running, or it did not start \"\n        \"an inspector instance.\\n<urlopen error >\\n\"\n    )\n    sys.exit.assert_called_once_with(1)\n    sys.stderr.write.assert_called_once_with(message)\n\n\ndef test_run_inspector_reload(publisher, http_client):\n    _, response = http_client.post(\"/reload\")\n    assert response.status == 200\n    publisher.send.assert_called_once_with(\"__ALL_PROCESSES__:\")\n\n\ndef test_run_inspector_reload_zero_downtime(publisher, http_client):\n    _, response = http_client.post(\"/reload\", json={\"zero_downtime\": True})\n    assert response.status == 200\n    publisher.send.assert_called_once_with(\"__ALL_PROCESSES__::STARTUP_FIRST\")\n\n\ndef test_run_inspector_shutdown(publisher, http_client):\n    _, response = http_client.post(\"/shutdown\")\n    assert response.status == 200\n    publisher.send.assert_called_once_with(\"__TERMINATE__\")\n\n\ndef test_run_inspector_scale(publisher, http_client):\n    _, response = http_client.post(\"/scale\", json={\"replicas\": 4})\n    assert response.status == 200\n    publisher.send.assert_called_once_with(\"__SCALE__:4\")\n\n\ndef test_run_inspector_arbitrary(http_client):\n    _, response = http_client.post(\"/foo\", json={\"bar\": 99})\n    assert response.status == 200\n    assert response.json == {\"meta\": {\"action\": \"foo\"}, \"result\": \"bar is 99\"}\n\n\ndef test_state_to_json():\n    now = datetime.now()\n    now_iso = now.isoformat()\n    app_info = {\"app\": \"hello\"}\n    worker_state = {\"Test\": {\"now\": now, \"nested\": {\"foo\": now}}}\n    inspector = Inspector(\n        Mock(), app_info, worker_state, \"\", 0, \"\", Default(), Default()\n    )\n    state = inspector._state_to_json()\n\n    assert state == {\n        \"info\": app_info,\n        \"workers\": {\"Test\": {\"now\": now_iso, \"nested\": {\"foo\": now_iso}}},\n    }\n\n\ndef test_run_inspector_authentication():\n    inspector = Inspector(\n        Mock(), {}, {}, \"\", 0, \"super-secret\", Default(), Default()\n    )(False)\n    manager = TestManager(inspector.app)\n    _, response = manager.test_client.get(\"/\")\n    assert response.status == 401\n    _, response = manager.test_client.get(\n        \"/\", headers={\"Authorization\": \"Bearer super-secret\"}\n    )\n    assert response.status == 200\n"
  },
  {
    "path": "tests/worker/test_loader.py",
    "content": "import sys\n\nfrom os import getcwd\nfrom pathlib import Path\nfrom types import SimpleNamespace\nfrom unittest.mock import Mock, patch\n\nimport pytest\n\nfrom sanic.app import Sanic\nfrom sanic.worker.loader import AppLoader, CertLoader\n\n\nSTATIC = Path.cwd() / \"tests\" / \"static\"\n\n\n@pytest.mark.parametrize(\n    \"module_input\", (\"tests.fake.server:app\", \"tests.fake.server.app\")\n)\ndef test_load_app_instance(module_input):\n    loader = AppLoader(module_input)\n    app = loader.load()\n    assert isinstance(app, Sanic)\n\n\n@pytest.mark.parametrize(\n    \"module_input\",\n    (\"tests.fake.server:create_app\", \"tests.fake.server:create_app()\"),\n)\ndef test_load_app_factory(module_input):\n    loader = AppLoader(module_input, as_factory=True)\n    app = loader.load()\n    assert isinstance(app, Sanic)\n\n\ndef test_load_app_simple():\n    loader = AppLoader(str(STATIC), as_simple=True)\n    app = loader.load()\n    assert isinstance(app, Sanic)\n\n\ndef test_create_with_factory():\n    loader = AppLoader(factory=lambda: Sanic(\"Test\"))\n    app = loader.load()\n    assert isinstance(app, Sanic)\n\n\ndef test_cwd_in_path():\n    AppLoader(\"tests.fake.server:app\").load()\n    assert getcwd() in sys.path\n\n\ndef test_input_is_dir():\n    loader = AppLoader(str(STATIC))\n    app = loader.load()\n    assert isinstance(app, Sanic)\n\n\ndef test_input_is_factory():\n    ns = SimpleNamespace(target=\"foo\")\n    loader = AppLoader(\"tests.fake.server:create_app\", args=ns)\n    app = loader.load()\n    assert isinstance(app, Sanic)\n\n\ndef test_input_is_module():\n    ns = SimpleNamespace(target=\"foo\")\n    loader = AppLoader(\"tests.fake.server\", args=ns)\n\n    app = loader.load()\n    assert isinstance(app, Sanic)\n\n\n@pytest.mark.parametrize(\"creator\", (\"mkcert\", \"trustme\"))\n@patch(\"sanic.worker.loader.TrustmeCreator\")\n@patch(\"sanic.worker.loader.MkcertCreator\")\ndef test_cert_loader(MkcertCreator: Mock, TrustmeCreator: Mock, creator: str):\n    CertLoader._creators = {\n        \"mkcert\": MkcertCreator,\n        \"trustme\": TrustmeCreator,\n    }\n    MkcertCreator.return_value = MkcertCreator\n    TrustmeCreator.return_value = TrustmeCreator\n    data = {\n        \"creator\": creator,\n        \"key\": Path.cwd() / \"tests\" / \"certs\" / \"localhost\" / \"privkey.pem\",\n        \"cert\": Path.cwd() / \"tests\" / \"certs\" / \"localhost\" / \"fullchain.pem\",\n        \"localhost\": \"localhost\",\n    }\n    app = Sanic(\"Test\")\n    loader = CertLoader(data)  # type: ignore\n    loader.load(app)\n    creator_class = MkcertCreator if creator == \"mkcert\" else TrustmeCreator\n    creator_class.assert_called_once_with(app, data[\"key\"], data[\"cert\"])\n    creator_class.generate_cert.assert_called_once_with(\"localhost\")\n"
  },
  {
    "path": "tests/worker/test_manager.py",
    "content": "from logging import ERROR, INFO\nfrom signal import SIGINT\nfrom unittest.mock import Mock, call, patch\n\nimport pytest\n\nfrom sanic.compat import OS_IS_WINDOWS\nfrom sanic.exceptions import ServerKilled\nfrom sanic.worker.constants import RestartOrder\nfrom sanic.worker.manager import WorkerManager\nfrom sanic.worker.process import Worker\n\n\nif not OS_IS_WINDOWS:\n    from signal import SIGKILL\nelse:\n    SIGKILL = SIGINT\n\n\ndef fake_serve(): ...\n\n\n@pytest.fixture\ndef manager() -> WorkerManager:\n    p1 = Mock()\n    p1.pid = 1234\n    context = Mock()\n    context.Process.return_value = p1\n    pub = Mock()\n    manager = WorkerManager(1, fake_serve, {}, context, (pub, Mock()), {})\n    return manager\n\n\ndef test_manager_no_workers():\n    message = \"Cannot serve with no workers\"\n    with pytest.raises(RuntimeError, match=message):\n        WorkerManager(0, fake_serve, {}, Mock(), (Mock(), Mock()), {})\n\n\n@patch(\"sanic.worker.process.os\")\ndef test_terminate(os_mock: Mock):\n    process = Mock()\n    process.pid = 1234\n    context = Mock()\n    context.Process.return_value = process\n    manager = WorkerManager(1, fake_serve, {}, context, (Mock(), Mock()), {})\n    manager.terminate()\n    os_mock.kill.assert_called_once_with(1234, SIGINT)\n\n\n@patch(\"sanic.worker.process.os\")\ndef test_shutown(os_mock: Mock):\n    process = Mock()\n    process.pid = 1234\n    process.is_alive.return_value = True\n    context = Mock()\n    context.Process.return_value = process\n    manager = WorkerManager(1, fake_serve, {}, context, (Mock(), Mock()), {})\n    manager.shutdown()\n    os_mock.kill.assert_called_once_with(1234, SIGINT)\n\n\n@patch(\"sanic.worker.manager.os\")\ndef test_kill(os_mock: Mock):\n    process = Mock()\n    process.pid = 1234\n    context = Mock()\n    context.Process.return_value = process\n    os_mock.getpgid.return_value = 5678\n    manager = WorkerManager(1, fake_serve, {}, context, (Mock(), Mock()), {})\n    with pytest.raises(ServerKilled):\n        manager.kill()\n    os_mock.getpgid.assert_called_once_with(1234)\n    os_mock.killpg.assert_called_once_with(5678, SIGKILL)\n\n\n@patch(\"sanic.worker.process.os\")\n@patch(\"sanic.worker.manager.os\")\ndef test_shutdown_signal_send_kill(\n    manager_os_mock: Mock, process_os_mock: Mock\n):\n    process = Mock()\n    process.pid = 1234\n    context = Mock()\n    context.Process.return_value = process\n    manager_os_mock.getpgid.return_value = 5678\n    manager = WorkerManager(1, fake_serve, {}, context, (Mock(), Mock()), {})\n    assert manager._shutting_down is False\n    manager.shutdown_signal(SIGINT, None)\n    assert manager._shutting_down is True\n    process_os_mock.kill.assert_called_once_with(1234, SIGINT)\n    manager.shutdown_signal(SIGINT, None)\n    manager_os_mock.getpgid.assert_called_once_with(1234)\n    manager_os_mock.killpg.assert_called_once_with(5678, SIGKILL)\n\n\ndef test_restart_all():\n    p1 = Mock()\n    p2 = Mock()\n    context = Mock()\n    context.Process.side_effect = [p1, p2, p1, p2]\n    manager = WorkerManager(2, fake_serve, {}, context, (Mock(), Mock()), {})\n    assert len(list(manager.transient_processes))\n    manager.restart()\n    p1.terminate.assert_called_once()\n    p2.terminate.assert_called_once()\n    context.Process.assert_has_calls(\n        [\n            call(\n                name=\"Sanic-Server-0-0\",\n                target=fake_serve,\n                kwargs={},\n                daemon=True,\n            ),\n            call(\n                name=\"Sanic-Server-1-0\",\n                target=fake_serve,\n                kwargs={},\n                daemon=True,\n            ),\n            call(\n                name=\"Sanic-Server-0-0\",\n                target=fake_serve,\n                kwargs={},\n                daemon=True,\n            ),\n            call(\n                name=\"Sanic-Server-1-0\",\n                target=fake_serve,\n                kwargs={},\n                daemon=True,\n            ),\n        ]\n    )\n\n\n@pytest.mark.parametrize(\"zero_downtime\", (False, True))\ndef test_monitor_all(zero_downtime):\n    p1 = Mock()\n    p2 = Mock()\n    sub = Mock()\n    incoming = (\n        \"__ALL_PROCESSES__::STARTUP_FIRST\"\n        if zero_downtime\n        else \"__ALL_PROCESSES__:\"\n    )\n    sub.recv.side_effect = [incoming, \"\"]\n    context = Mock()\n    context.Process.side_effect = [p1, p2]\n    manager = WorkerManager(2, fake_serve, {}, context, (Mock(), sub), {})\n    manager.restart = Mock()  # type: ignore\n    manager.wait_for_ack = Mock()  # type: ignore\n    manager.monitor()\n\n    restart_order = (\n        RestartOrder.STARTUP_FIRST\n        if zero_downtime\n        else RestartOrder.SHUTDOWN_FIRST\n    )\n    manager.restart.assert_called_once_with(\n        process_names=None,\n        reloaded_files=\"\",\n        restart_order=restart_order,\n    )\n\n\n@pytest.mark.parametrize(\"zero_downtime\", (False, True))\ndef test_monitor_all_with_files(zero_downtime):\n    p1 = Mock()\n    p2 = Mock()\n    sub = Mock()\n    incoming = (\n        \"__ALL_PROCESSES__:foo,bar:STARTUP_FIRST\"\n        if zero_downtime\n        else \"__ALL_PROCESSES__:foo,bar\"\n    )\n    sub.recv.side_effect = [incoming, \"\"]\n    context = Mock()\n    context.Process.side_effect = [p1, p2]\n    manager = WorkerManager(2, fake_serve, {}, context, (Mock(), sub), {})\n    manager.restart = Mock()  # type: ignore\n    manager.wait_for_ack = Mock()  # type: ignore\n    manager.monitor()\n\n    restart_order = (\n        RestartOrder.STARTUP_FIRST\n        if zero_downtime\n        else RestartOrder.SHUTDOWN_FIRST\n    )\n    manager.restart.assert_called_once_with(\n        process_names=None,\n        reloaded_files=\"foo,bar\",\n        restart_order=restart_order,\n    )\n\n\n@pytest.mark.parametrize(\"zero_downtime\", (False, True))\ndef test_monitor_one_process(zero_downtime):\n    p1 = Mock()\n    p1.name = \"Testing\"\n    p2 = Mock()\n    sub = Mock()\n    incoming = (\n        f\"{p1.name}:foo,bar:STARTUP_FIRST\"\n        if zero_downtime\n        else f\"{p1.name}:foo,bar\"\n    )\n    sub.recv.side_effect = [incoming, \"\"]\n    context = Mock()\n    context.Process.side_effect = [p1, p2]\n    manager = WorkerManager(2, fake_serve, {}, context, (Mock(), sub), {})\n    manager.restart = Mock()  # type: ignore\n    manager.wait_for_ack = Mock()  # type: ignore\n    manager.monitor()\n\n    restart_order = (\n        RestartOrder.STARTUP_FIRST\n        if zero_downtime\n        else RestartOrder.SHUTDOWN_FIRST\n    )\n    manager.restart.assert_called_once_with(\n        process_names=[p1.name],\n        reloaded_files=\"foo,bar\",\n        restart_order=restart_order,\n    )\n\n\ndef test_shutdown_signal():\n    pub = Mock()\n    manager = WorkerManager(1, fake_serve, {}, Mock(), (pub, Mock()), {})\n    manager.shutdown = Mock()  # type: ignore\n\n    manager.shutdown_signal(SIGINT, None)\n    pub.send.assert_called_with(None)\n    manager.shutdown.assert_called_once_with()\n\n\ndef test_shutdown_servers(caplog):\n    p1 = Mock()\n    p1.pid = 1234\n    context = Mock()\n    context.Process.side_effect = [p1]\n    pub = Mock()\n    manager = WorkerManager(1, fake_serve, {}, context, (pub, Mock()), {})\n\n    with patch(\"os.kill\") as kill:\n        with caplog.at_level(ERROR):\n            caplog.clear()\n            manager.shutdown_server()\n\n            kill.assert_called_once_with(1234, SIGINT)\n            kill.reset_mock()\n\n            sanic_errors = [\n                r for r in caplog.record_tuples if r[0].startswith(\"sanic\")\n            ]\n            assert not sanic_errors\n\n            manager.shutdown_server()\n\n            kill.assert_not_called()\n\n            assert (\n                \"sanic.error\",\n                ERROR,\n                \"Server shutdown failed because a server was not found.\",\n            ) in caplog.record_tuples\n\n\ndef test_shutdown_servers_named():\n    p1 = Mock()\n    p1.pid = 1234\n    p2 = Mock()\n    p2.pid = 6543\n    context = Mock()\n    context.Process.side_effect = [p1, p2]\n    pub = Mock()\n    manager = WorkerManager(2, fake_serve, {}, context, (pub, Mock()), {})\n\n    with patch(\"os.kill\") as kill:\n        with pytest.raises(KeyError):\n            manager.shutdown_server(\"foo\")\n        manager.shutdown_server(\"Server-1\")\n\n        kill.assert_called_once_with(6543, SIGINT)\n\n\ndef test_scale(caplog):\n    p1 = Mock()\n    p1.pid = 1234\n    p2 = Mock()\n    p2.pid = 3456\n    p3 = Mock()\n    p3.pid = 5678\n    context = Mock()\n    context.Process.side_effect = [p1, p2, p3]\n    pub = Mock()\n    manager = WorkerManager(1, fake_serve, {}, context, (pub, Mock()), {})\n\n    assert len(manager.transient) == 1\n\n    manager.scale(3)\n    assert len(manager.transient) == 3\n\n    with patch(\"os.kill\") as kill:\n        manager.scale(2)\n        assert len(manager.transient) == 2\n\n        manager.scale(1)\n        assert len(manager.transient) == 1\n\n        kill.call_count == 2\n\n    with caplog.at_level(INFO):\n        manager.scale(1)\n\n    assert (\n        \"sanic.root\",\n        INFO,\n        \"No change needed. There are already 1 workers.\",\n    ) in caplog.record_tuples\n\n    with pytest.raises(ValueError, match=r\"Cannot scale to 0 workers\\.\"):\n        manager.scale(0)\n\n\ndef test_manage_basic(manager: WorkerManager):\n    assert len(manager.transient) == 1\n    assert len(manager.durable) == 0\n    manager.manage(\"TEST\", fake_serve, kwargs={\"foo\": \"bar\"})\n    assert len(manager.transient) == 1\n    assert len(manager.durable) == 1\n\n    worker_process = manager.durable[\"TEST\"]\n\n    assert isinstance(worker_process, Worker)\n    assert worker_process.server_settings == {\"foo\": \"bar\"}\n    assert worker_process.restartable is False\n    assert worker_process.tracked is True\n    assert worker_process.auto_start is True\n    assert worker_process.num == 1\n\n\ndef test_manage_transient(manager: WorkerManager):\n    manager.manage(\n        \"TEST\", fake_serve, kwargs={\"foo\": \"bar\"}, workers=3, transient=True\n    )\n    assert len(manager.transient) == 2\n    assert len(manager.durable) == 0\n\n    worker_process = manager.transient[\"TEST\"]\n\n    assert isinstance(worker_process, Worker)\n    assert worker_process.restartable is True\n    assert worker_process.tracked is True\n    assert worker_process.auto_start is True\n    assert worker_process.num == 3\n\n\ndef test_manage_restartable(manager: WorkerManager):\n    manager.manage(\n        \"TEST\",\n        fake_serve,\n        kwargs={\"foo\": \"bar\"},\n        restartable=True,\n        auto_start=False,\n    )\n    assert len(manager.transient) == 1\n    assert len(manager.durable) == 1\n\n    worker_process = manager.durable[\"TEST\"]\n\n    assert isinstance(worker_process, Worker)\n    assert worker_process.restartable is True\n    assert worker_process.tracked is True\n    assert worker_process.auto_start is False\n\n\ndef test_manage_untracked(manager: WorkerManager):\n    manager.manage(\"TEST\", fake_serve, kwargs={\"foo\": \"bar\"}, tracked=False)\n    assert len(manager.transient) == 1\n    assert len(manager.durable) == 1\n\n    worker_process = manager.durable[\"TEST\"]\n\n    assert isinstance(worker_process, Worker)\n    assert worker_process.restartable is False\n    assert worker_process.tracked is False\n    assert worker_process.auto_start is True\n\n\ndef test_manage_duplicate_ident(manager: WorkerManager):\n    manager.manage(\"TEST\", fake_serve, kwargs={\"foo\": \"bar\"})\n    message = \"Worker TEST already exists\"\n    with pytest.raises(ValueError, match=message):\n        manager.manage(\"TEST\", fake_serve, kwargs={\"foo\": \"bar\"})\n\n\ndef test_transient_not_restartable(manager: WorkerManager):\n    message = \"Cannot create a transient worker that is not restartable\"\n    with pytest.raises(ValueError, match=message):\n        manager.manage(\n            \"TEST\",\n            fake_serve,\n            kwargs={\"foo\": \"bar\"},\n            transient=True,\n            restartable=False,\n        )\n\n\ndef test_remove_worker(manager: WorkerManager, caplog):\n    worker = manager.manage(\"TEST\", fake_serve, kwargs={})\n\n    assert \"Sanic-TEST-0\" in worker.worker_state\n    assert len(manager.transient) == 1\n    assert len(manager.durable) == 1\n\n    manager.remove_worker(worker)\n    message = \"Worker TEST is tracked and cannot be removed.\"\n\n    assert \"Sanic-TEST-0\" in worker.worker_state\n    assert len(manager.transient) == 1\n    assert len(manager.durable) == 1\n    assert (\"sanic.error\", 40, message) in caplog.record_tuples\n\n\ndef test_remove_untracked_worker(manager: WorkerManager, caplog):\n    caplog.set_level(20)\n    worker = manager.manage(\"TEST\", fake_serve, kwargs={}, tracked=False)\n    worker.has_alive_processes = Mock(return_value=True)\n\n    assert \"Sanic-TEST-0\" in worker.worker_state\n    assert len(manager.transient) == 1\n    assert len(manager.durable) == 1\n\n    manager.remove_worker(worker)\n    message = \"Worker TEST has alive processes and cannot be removed.\"\n\n    assert \"Sanic-TEST-0\" in worker.worker_state\n    assert len(manager.transient) == 1\n    assert len(manager.durable) == 1\n    assert (\"sanic.error\", 40, message) in caplog.record_tuples\n\n    worker.has_alive_processes = Mock(return_value=False)\n    manager.remove_worker(worker)\n    message = \"Removed worker TEST\"\n\n    assert \"Sanic-TEST-0\" not in worker.worker_state\n    assert len(manager.transient) == 1\n    assert len(manager.durable) == 0\n    assert (\"sanic.root\", 20, message) in caplog.record_tuples\n"
  },
  {
    "path": "tests/worker/test_multiplexer.py",
    "content": "import sys\n\nfrom multiprocessing import Event\nfrom os import environ, getpid\nfrom typing import Any, Union\nfrom unittest.mock import Mock\n\nimport pytest\n\nfrom sanic import Sanic\nfrom sanic.compat import use_context\nfrom sanic.worker.multiplexer import WorkerMultiplexer\nfrom sanic.worker.state import WorkerState\n\n\ndef noop(*args, **kwargs):\n    pass\n\n\n@pytest.fixture\ndef monitor_publisher():\n    return Mock()\n\n\n@pytest.fixture\ndef worker_state():\n    return {}\n\n\n@pytest.fixture\ndef m(monitor_publisher, worker_state):\n    environ[\"SANIC_WORKER_NAME\"] = \"Test\"\n    worker_state[\"Test\"] = {}\n    yield WorkerMultiplexer(monitor_publisher, worker_state)\n    del environ[\"SANIC_WORKER_NAME\"]\n\n\n@pytest.mark.skipif(\n    sys.platform not in (\"linux\", \"darwin\"),\n    reason=\"This test requires fork context\",\n)\ndef test_has_multiplexer_default(app: Sanic):\n    event = Event()\n\n    @app.main_process_start\n    async def setup(app, _):\n        app.shared_ctx.event = event\n\n    @app.after_server_start\n    def stop(app):\n        if hasattr(app, \"m\") and isinstance(app.m, WorkerMultiplexer):\n            app.shared_ctx.event.set()\n        app.stop()\n\n    with use_context(\"fork\"):\n        app.run()\n\n    assert event.is_set()\n\n\ndef test_not_have_multiplexer_single(app: Sanic):\n    event = Event()\n\n    @app.main_process_start\n    async def setup(app, _):\n        app.shared_ctx.event = event\n\n    @app.after_server_start\n    def stop(app):\n        if hasattr(app, \"m\") and isinstance(app.m, WorkerMultiplexer):\n            app.shared_ctx.event.set()\n        app.stop()\n\n    app.run(single_process=True)\n\n    assert not event.is_set()\n\n\ndef test_ack(worker_state: dict[str, Any], m: WorkerMultiplexer):\n    worker_state[\"Test\"] = {\"foo\": \"bar\"}\n    m.ack()\n    assert worker_state[\"Test\"] == {\"foo\": \"bar\", \"state\": \"ACKED\"}\n\n\ndef test_restart_self(monitor_publisher: Mock, m: WorkerMultiplexer):\n    m.restart()\n    monitor_publisher.send.assert_called_once_with(\"Test:\")\n\n\ndef test_restart_foo(monitor_publisher: Mock, m: WorkerMultiplexer):\n    m.restart(\"foo\")\n    monitor_publisher.send.assert_called_once_with(\"foo:\")\n\n\ndef test_reload_alias(monitor_publisher: Mock, m: WorkerMultiplexer):\n    m.reload()\n    monitor_publisher.send.assert_called_once_with(\"Test:\")\n\n\ndef test_terminate(monitor_publisher: Mock, m: WorkerMultiplexer):\n    m.terminate()\n    monitor_publisher.send.assert_called_once_with(\"__TERMINATE__\")\n\n\ndef test_scale(monitor_publisher: Mock, m: WorkerMultiplexer):\n    m.scale(99)\n    monitor_publisher.send.assert_called_once_with(\"__SCALE__:99\")\n\n\ndef test_manage(monitor_publisher: Mock, m: WorkerMultiplexer):\n    m.manage(\"NEW\", noop, auto_start=False, kwargs={\"foo\": 99})\n    monitor_publisher.send.assert_called_once_with(\n        (\"NEW\", noop, {\"foo\": 99}, False, None, False, False, 1)\n    )\n\n\ndef test_properties(\n    monitor_publisher: Mock, worker_state: dict[str, Any], m: WorkerMultiplexer\n):\n    assert m.reload == m.restart\n    assert m.pid == getpid()\n    assert m.name == \"Test\"\n    assert m.workers == worker_state\n    assert m.state == worker_state[\"Test\"]\n    assert isinstance(m.state, WorkerState)\n\n\n@pytest.mark.parametrize(\n    \"params,expected\",\n    (\n        ({}, \"Test:\"),\n        ({\"name\": \"foo\"}, \"foo:\"),\n        ({\"all_workers\": True}, \"__ALL_PROCESSES__:\"),\n        ({\"zero_downtime\": True}, \"Test::STARTUP_FIRST\"),\n        ({\"name\": \"foo\", \"all_workers\": True}, ValueError),\n        ({\"name\": \"foo\", \"zero_downtime\": True}, \"foo::STARTUP_FIRST\"),\n        (\n            {\"all_workers\": True, \"zero_downtime\": True},\n            \"__ALL_PROCESSES__::STARTUP_FIRST\",\n        ),\n        (\n            {\"name\": \"foo\", \"all_workers\": True, \"zero_downtime\": True},\n            ValueError,\n        ),\n    ),\n)\ndef test_restart_params(\n    monitor_publisher: Mock,\n    m: WorkerMultiplexer,\n    params: dict[str, Any],\n    expected: Union[str, type[Exception]],\n):\n    if isinstance(expected, str):\n        m.restart(**params)\n        monitor_publisher.send.assert_called_once_with(expected)\n    else:\n        with pytest.raises(expected):\n            m.restart(**params)\n"
  },
  {
    "path": "tests/worker/test_reloader.py",
    "content": "import re\nimport signal\nimport threading\n\nfrom asyncio import Event\nfrom logging import DEBUG\nfrom pathlib import Path\nfrom time import sleep\nfrom unittest.mock import Mock\n\nimport pytest\n\nfrom sanic.app import Sanic\nfrom sanic.worker.constants import ProcessState, RestartOrder\nfrom sanic.worker.loader import AppLoader\nfrom sanic.worker.process import WorkerProcess\nfrom sanic.worker.reloader import Reloader\n\n\n@pytest.fixture\ndef reloader(): ...\n\n\n@pytest.fixture\ndef app():\n    app = Sanic(\"Test\")\n\n    @app.route(\"/\")\n    def handler(_): ...\n\n    return app\n\n\n@pytest.fixture\ndef app_loader(app):\n    return AppLoader(factory=lambda: app)\n\n\ndef run_reloader(reloader):\n    def stop(*_):\n        reloader.stop()\n\n    signal.signal(signal.SIGALRM, stop)\n    signal.alarm(1)\n    reloader()\n\n\ndef is_python_file(filename):\n    return (isinstance(filename, Path) and (filename.suffix == \"py\")) or (\n        isinstance(filename, str) and filename.endswith(\".py\")\n    )\n\n\ndef test_reload_send():\n    publisher = Mock()\n    reloader = Reloader(publisher, 0.1, set(), Mock())\n    reloader.reload(\"foobar\")\n    publisher.send.assert_called_once_with(\"__ALL_PROCESSES__:foobar\")\n\n\ndef test_iter_files():\n    reloader = Reloader(Mock(), 0.1, set(), Mock())\n    len_python_files = len(list(reloader.files()))\n    assert len_python_files > 0\n\n    static_dir = Path(__file__).parent.parent / \"static\"\n    len_static_files = len(list(static_dir.glob(\"**/*\")))\n    reloader = Reloader(Mock(), 0.1, set({static_dir}), Mock())\n    len_total_files = len(list(reloader.files()))\n    assert len_static_files > 0\n    assert len_total_files == len_python_files + len_static_files\n\n\n@pytest.mark.parametrize(\n    \"order,expected\",\n    (\n        (\n            RestartOrder.SHUTDOWN_FIRST,\n            [\n                \"Restarting a process\",\n                \"Begin restart termination\",\n                \"Starting a process\",\n            ],\n        ),\n        (\n            RestartOrder.STARTUP_FIRST,\n            [\n                \"Restarting a process\",\n                \"Starting a process\",\n                \"Begin restart termination\",\n                \"Waiting for process to be acked\",\n                \"Process acked. Terminating\",\n            ],\n        ),\n    ),\n)\ndef test_default_reload_shutdown_order(monkeypatch, caplog, order, expected):\n    current_process = Mock()\n    worker_process = WorkerProcess(\n        lambda **_: current_process,\n        \"Test\",\n        \"TST\",\n        lambda **_: ...,\n        {},\n        {},\n    )\n\n    def start(self):\n        worker_process.set_state(ProcessState.ACKED)\n        self._target()\n\n    orig = threading.Thread.start\n    monkeypatch.setattr(threading.Thread, \"start\", start)\n\n    with caplog.at_level(DEBUG):\n        worker_process.restart(restart_order=order)\n\n    ansi = re.compile(r\"\\x1B(?:[@-Z\\\\-_]|\\[[0-?]*[ -/]*[@-~])\")\n\n    def clean(msg: str):\n        msg, _ = ansi.sub(\"\", msg).split(\":\", 1)\n        return msg\n\n    debug = [clean(record[2]) for record in caplog.record_tuples]\n    assert debug == expected\n    current_process.start.assert_called_once()\n    current_process.terminate.assert_called_once()\n    monkeypatch.setattr(threading.Thread, \"start\", orig)\n\n\ndef test_reload_delayed(monkeypatch):\n    WorkerProcess.THRESHOLD = 1\n\n    current_process = Mock()\n    worker_process = WorkerProcess(\n        lambda **_: current_process,\n        \"Test\",\n        \"TST\",\n        lambda **_: ...,\n        {},\n        {},\n    )\n\n    def start(self):\n        sleep(0.2)\n        self._target()\n\n    orig = threading.Thread.start\n    monkeypatch.setattr(threading.Thread, \"start\", start)\n\n    message = \"Worker Test failed to come ack within 0.1 seconds\"\n    with pytest.raises(TimeoutError, match=message):\n        worker_process.restart(restart_order=RestartOrder.STARTUP_FIRST)\n\n    monkeypatch.setattr(threading.Thread, \"start\", orig)\n\n\ndef test_reloader_triggers_start_stop_listeners(\n    app: Sanic, app_loader: AppLoader\n):\n    results = []\n\n    @app.reload_process_start\n    def reload_process_start(_):\n        results.append(\"reload_process_start\")\n\n    @app.reload_process_stop\n    def reload_process_stop(_):\n        results.append(\"reload_process_stop\")\n\n    reloader = Reloader(Mock(), 0.1, set(), app_loader)\n    run_reloader(reloader)\n\n    assert results == [\"reload_process_start\", \"reload_process_stop\"]\n\n\ndef test_not_triggered(app_loader):\n    reload_dir = Path(__file__).parent.parent / \"fake\"\n    publisher = Mock()\n    reloader = Reloader(publisher, 0.1, {reload_dir}, app_loader)\n    run_reloader(reloader)\n\n    publisher.send.assert_not_called()\n\n\ndef test_triggered(app_loader):\n    paths = set()\n\n    def check_file(filename, mtimes):\n        if (isinstance(filename, Path) and (filename.name == \"server.py\")) or (\n            isinstance(filename, str) and \"sanic/app.py\" in filename\n        ):\n            paths.add(str(filename))\n            return True\n        return False\n\n    reload_dir = Path(__file__).parent.parent / \"fake\"\n    publisher = Mock()\n    reloader = Reloader(publisher, 0.1, {reload_dir}, app_loader)\n    reloader.check_file = check_file  # type: ignore\n    run_reloader(reloader)\n\n    assert len(paths) == 2\n\n    publisher.send.assert_called()\n    call_arg = publisher.send.call_args_list[0][0][0]\n    assert call_arg.startswith(\"__ALL_PROCESSES__:\")\n    assert call_arg.count(\",\") == 1\n    for path in paths:\n        assert str(path) in call_arg\n\n\ndef test_reloader_triggers_reload_listeners(app: Sanic, app_loader: AppLoader):\n    before = Event()\n    after = Event()\n    changed_files = set()\n\n    def check_file(filename, mtimes):\n        return not after.is_set()\n\n    @app.before_reload_trigger\n    async def before_reload_trigger(_):\n        before.set()\n\n    @app.after_reload_trigger\n    async def after_reload_trigger(_, changed):\n        after.set()\n        changed_files.update(changed)\n\n    reloader = Reloader(Mock(), 0.1, set(), app_loader)\n    reloader.check_file = check_file  # type: ignore\n    run_reloader(reloader)\n\n    assert before.is_set()\n    assert after.is_set()\n    assert len(changed_files) > 0\n    assert changed_files == set(reloader.files())\n\n\ndef test_check_file(tmp_path):\n    current = tmp_path / \"testing.txt\"\n    current.touch()\n    mtimes = {}\n    assert Reloader.check_file(current, mtimes) is False\n    assert len(mtimes) == 1\n    assert Reloader.check_file(current, mtimes) is False\n    mtimes[current] = mtimes[current] - 1\n    assert Reloader.check_file(current, mtimes) is True\n"
  },
  {
    "path": "tests/worker/test_restarter.py",
    "content": "from unittest.mock import Mock\n\nimport pytest\n\nfrom sanic.worker.constants import ProcessState, RestartOrder\nfrom sanic.worker.process import WorkerProcess\nfrom sanic.worker.restarter import Restarter\n\n\ndef noop(*args, **kwargs):\n    pass\n\n\ndef make_worker_process(\n    name: str, state: ProcessState = ProcessState.STARTED\n) -> WorkerProcess:\n    worker_process = Mock()\n    worker_process.restart = Mock()\n    worker_process.name = name\n    worker_process.state = state\n    return worker_process\n\n\ndef test_restart_transient():\n    transient = make_worker_process(\"Transient\")\n    durable = make_worker_process(\"Durable\")\n    restarter = Restarter()\n\n    restarter.restart([transient], [durable])\n    transient.restart.assert_called_once_with(\n        restart_order=RestartOrder.SHUTDOWN_FIRST\n    )\n    durable.restart.assert_not_called()\n    transient.restart.reset_mock()\n    restarter.restart(\n        [transient], [durable], restart_order=RestartOrder.STARTUP_FIRST\n    )\n    transient.restart.assert_called_once_with(\n        restart_order=RestartOrder.STARTUP_FIRST\n    )\n\n\n@pytest.mark.parametrize(\n    \"state,called\",\n    (\n        (ProcessState.IDLE, False),\n        (ProcessState.RESTARTING, False),\n        (ProcessState.STARTING, False),\n        (ProcessState.STARTED, False),\n        (ProcessState.ACKED, False),\n        (ProcessState.JOINED, False),\n        (ProcessState.TERMINATED, False),\n        (ProcessState.FAILED, True),\n        (ProcessState.COMPLETED, True),\n        (ProcessState.NONE, True),\n    ),\n)\ndef test_restart_durable(caplog, state, called):\n    transient = make_worker_process(\"Transient\")\n    durable = make_worker_process(\"Durable\")\n    restarter = Restarter()\n\n    restarter.restart([transient], [durable], process_names=[\"Durable\"])\n\n    transient.restart.assert_not_called()\n    durable.restart.assert_not_called()\n\n    assert (\n        \"sanic.error\",\n        40,\n        \"Cannot restart process Durable because it is not in a \"\n        \"final state. Current state is: STARTED.\",\n    ) in caplog.record_tuples\n    assert (\n        \"sanic.error\",\n        40,\n        \"Failed to restart processes: Durable\",\n    ) in caplog.record_tuples\n\n    durable.state = state\n    restarter.restart([transient], [durable], process_names=[\"Durable\"])\n\n    transient.restart.assert_not_called()\n    if called:\n        durable.restart.assert_called_once_with(\n            restart_order=RestartOrder.SHUTDOWN_FIRST\n        )\n    else:\n        durable.restart.assert_not_called()\n"
  },
  {
    "path": "tests/worker/test_runner.py",
    "content": "from unittest.mock import Mock, call, patch\n\nimport pytest\nfrom sanic.app import Sanic\nfrom sanic.http.constants import HTTP\nfrom sanic.server.runners import _run_server_forever, serve\n\n\n@patch(\"sanic.server.runners._serve_http_1\")\n@patch(\"sanic.server.runners._serve_http_3\")\ndef test_run_http_1(_serve_http_3: Mock, _serve_http_1: Mock, app: Sanic):\n    serve(\"\", 0, app)\n    _serve_http_3.assert_not_called()\n    _serve_http_1.assert_called_once()\n\n\n@patch(\"sanic.server.runners._serve_http_1\")\n@patch(\"sanic.server.runners._serve_http_3\")\ndef test_run_http_3(_serve_http_3: Mock, _serve_http_1: Mock, app: Sanic):\n    serve(\"\", 0, app, version=HTTP.VERSION_3)\n    _serve_http_1.assert_not_called()\n    _serve_http_3.assert_called_once()\n\n\n@patch(\"sanic.server.runners.remove_unix_socket\")\n@pytest.mark.parametrize(\"do_cleanup\", (True, False))\ndef test_run_server_forever(remove_unix_socket: Mock, do_cleanup: bool):\n    loop = Mock()\n    cleanup = Mock()\n    loop.run_forever = Mock(side_effect=KeyboardInterrupt())\n    before_stop = Mock()\n    before_stop.return_value = Mock()\n    after_stop = Mock()\n    after_stop.return_value = Mock()\n    unix = Mock()\n\n    with pytest.raises(KeyboardInterrupt):\n        _run_server_forever(\n            loop,\n            before_stop,\n            after_stop,\n            cleanup if do_cleanup else None,\n            unix,\n            12345,\n        )\n\n    loop.run_forever.assert_called_once_with()\n    loop.run_until_complete.assert_has_calls(\n        [call(before_stop.return_value), call(after_stop.return_value)]\n    )\n\n    if do_cleanup:\n        cleanup.assert_called_once_with()\n    else:\n        cleanup.assert_not_called()\n\n    remove_unix_socket.assert_called_once_with(unix)\n    loop.close.assert_called_once_with()\n"
  },
  {
    "path": "tests/worker/test_shared_ctx.py",
    "content": "# 18\n# 21-29\n# 26\n# 36-37\n# 42\n# 55\n# 38->\n\nimport logging\n\nfrom ctypes import c_int32\nfrom multiprocessing import Pipe, Queue, Value\nfrom os import environ\nfrom typing import Any\n\nimport pytest\n\nfrom sanic.types.shared_ctx import SharedContext\n\n\n@pytest.mark.parametrize(\n    \"item,okay\",\n    (\n        (Pipe(), True),\n        (Value(\"i\", 0), True),\n        (Queue(), True),\n        (c_int32(1), True),\n        (1, False),\n        (\"thing\", False),\n        (object(), False),\n    ),\n)\ndef test_set_items(item: Any, okay: bool, caplog):\n    ctx = SharedContext()\n\n    with caplog.at_level(logging.INFO):\n        ctx.item = item\n\n    assert ctx.is_locked is False\n    assert len(caplog.record_tuples) == 0 if okay else 1\n    if not okay:\n        assert caplog.record_tuples[0][0] == \"sanic.error\"\n        assert caplog.record_tuples[0][1] == logging.WARNING\n        assert \"Unsafe object\" in caplog.record_tuples[0][2]\n\n\n@pytest.mark.parametrize(\n    \"item\",\n    (\n        Pipe(),\n        Value(\"i\", 0),\n        Queue(),\n        c_int32(1),\n        1,\n        \"thing\",\n        object(),\n    ),\n)\ndef test_set_items_in_worker(item: Any, caplog):\n    ctx = SharedContext()\n\n    environ[\"SANIC_WORKER_NAME\"] = \"foo\"\n    with caplog.at_level(logging.INFO):\n        ctx.item = item\n    del environ[\"SANIC_WORKER_NAME\"]\n\n    assert ctx.is_locked is False\n    assert len(caplog.record_tuples) == 0\n\n\ndef test_lock():\n    ctx = SharedContext()\n\n    assert ctx.is_locked is False\n\n    ctx.lock()\n\n    assert ctx.is_locked is True\n\n    message = \"Cannot set item on locked SharedContext object\"\n    with pytest.raises(RuntimeError, match=message):\n        ctx.item = 1\n"
  },
  {
    "path": "tests/worker/test_socket.py",
    "content": "from pathlib import Path\n\nfrom sanic.server.socket import (\n    bind_unix_socket,\n    configure_socket,\n    remove_unix_socket,\n)\n\n\ndef test_setup_and_teardown_unix():\n    socket_address = \"./test.sock\"\n    path = Path.cwd() / socket_address\n    assert not path.exists()\n    bind_unix_socket(socket_address)\n    assert path.exists()\n    remove_unix_socket(socket_address)\n    assert not path.exists()\n\n\ndef test_configure_socket():\n    socket_address = \"./test.sock\"\n    path = Path.cwd() / socket_address\n    assert not path.exists()\n    configure_socket({\"unix\": socket_address, \"backlog\": 100})\n    assert path.exists()\n    remove_unix_socket(socket_address)\n    assert not path.exists()\n"
  },
  {
    "path": "tests/worker/test_startup.py",
    "content": "import sys\n\nfrom multiprocessing import set_start_method\nfrom unittest.mock import patch\n\nimport pytest\n\nfrom sanic import Sanic\n\n\n@pytest.mark.parametrize(\n    \"start_method,platform,expected\",\n    (\n        (None, \"linux\", \"spawn\"),\n        (None, \"other\", \"spawn\"),\n        (\"fork\", \"linux\", \"fork\"),\n        (\"fork\", \"other\", \"fork\"),\n        (\"forkserver\", \"linux\", \"forkserver\"),\n        (\"forkserver\", \"other\", \"forkserver\"),\n        (\"spawn\", \"linux\", \"spawn\"),\n        (\"spawn\", \"other\", \"spawn\"),\n    ),\n)\ndef test_get_context(start_method, platform, expected):\n    if start_method:\n        Sanic.start_method = start_method\n    with patch(\"sys.platform\", platform):\n        assert Sanic._get_startup_method() == expected\n\n\n@pytest.mark.skipif(\n    not sys.platform.startswith(\"linux\"), reason=\"Only test on Linux\"\n)\ndef test_set_startup_catch():\n    Sanic.START_METHOD_SET = False\n    set_start_method(\"fork\", force=True)\n    Sanic.test_mode = False\n    message = (\n        \"Start method 'spawn' was requested, but 'fork' was already set.\\n\"\n        \"For more information, see: https://sanic.dev/en/guide/running/manager.html#overcoming-a-coderuntimeerrorcode\"\n    )\n    with pytest.raises(RuntimeError, match=message):\n        Sanic._set_startup_method()\n    Sanic.test_mode = True\n"
  },
  {
    "path": "tests/worker/test_state.py",
    "content": "import pytest\n\nfrom sanic.worker.state import WorkerState\n\n\ndef gen_state(**kwargs):\n    return WorkerState({\"foo\": kwargs}, \"foo\")\n\n\ndef test_set_get_state():\n    state = gen_state()\n    state[\"additional\"] = 123\n    assert state[\"additional\"] == 123\n    assert state.get(\"additional\") == 123\n    assert state._state == {\"foo\": {\"additional\": 123}}\n\n\ndef test_del_state():\n    state = gen_state(one=1)\n    assert state[\"one\"] == 1\n    del state[\"one\"]\n    assert state._state == {\"foo\": {}}\n\n\ndef test_iter_state():\n    result = [item for item in gen_state(one=1, two=2)]\n    assert result == [\"one\", \"two\"]\n\n\ndef test_state_len():\n    result = [item for item in gen_state(one=1, two=2)]\n    assert len(result) == 2\n\n\ndef test_state_repr():\n    assert repr(gen_state(one=1, two=2)) == repr({\"one\": 1, \"two\": 2})\n\n\ndef test_state_eq():\n    state = gen_state(one=1, two=2)\n    assert state == {\"one\": 1, \"two\": 2}\n    assert state != {\"one\": 1}\n\n\ndef test_state_keys():\n    assert list(gen_state(one=1, two=2).keys()) == list(\n        {\"one\": 1, \"two\": 2}.keys()\n    )\n\n\ndef test_state_values():\n    assert list(gen_state(one=1, two=2).values()) == list(\n        {\"one\": 1, \"two\": 2}.values()\n    )\n\n\ndef test_state_items():\n    assert list(gen_state(one=1, two=2).items()) == list(\n        {\"one\": 1, \"two\": 2}.items()\n    )\n\n\ndef test_state_update():\n    state = gen_state()\n    assert len(state) == 0\n    state.update({\"nine\": 9})\n    assert len(state) == 1\n    assert state[\"nine\"] == 9\n\n\ndef test_state_pop():\n    state = gen_state(one=1)\n    with pytest.raises(NotImplementedError):\n        state.pop()\n\n\ndef test_state_full():\n    state = gen_state(one=1)\n    assert state.full() == {\"foo\": {\"one\": 1}}\n\n\n@pytest.mark.parametrize(\"key\", WorkerState.RESTRICTED)\ndef test_state_restricted_operation(key):\n    state = gen_state()\n    message = f\"Cannot set restricted key on WorkerState: {key}\"\n    with pytest.raises(LookupError, match=message):\n        state[key] = \"Nope\"\n        del state[key]\n\n    with pytest.raises(LookupError, match=message):\n        state.update({\"okay\": True, key: \"bad\"})\n"
  },
  {
    "path": "tests/worker/test_worker_serve.py",
    "content": "import logging\n\nfrom os import environ\nfrom unittest.mock import Mock, patch\n\nimport pytest\n\nfrom sanic.app import Sanic\nfrom sanic.worker.loader import AppLoader\nfrom sanic.worker.multiplexer import WorkerMultiplexer\nfrom sanic.worker.process import Worker, WorkerProcess\nfrom sanic.worker.serve import worker_serve\n\n\n@pytest.fixture\ndef mock_app():\n    app = Mock()\n    server_info = Mock()\n    server_info.settings = {\"app\": app}\n    app.state.workers = 1\n    app.listeners = {\"main_process_ready\": []}\n    app.get_motd_data.return_value = ({\"packages\": \"\"}, {})\n    app.state.server_info = [server_info]\n    return app\n\n\ndef args(app, **kwargs):\n    params = {**kwargs}\n    params.setdefault(\"host\", \"127.0.0.1\")\n    params.setdefault(\"port\", 9999)\n    params.setdefault(\"app_name\", \"test_config_app\")\n    params.setdefault(\"monitor_publisher\", None)\n    params.setdefault(\"app_loader\", AppLoader(factory=lambda: app))\n    return params\n\n\ndef test_config_app(mock_app: Mock):\n    with patch(\"sanic.worker.serve._serve_http_1\"):\n        worker_serve(**args(mock_app, config={\"FOO\": \"BAR\"}))\n    mock_app.update_config.assert_called_once_with({\"FOO\": \"BAR\"})\n\n\ndef test_bad_process(mock_app: Mock, caplog):\n    environ[\"SANIC_WORKER_NAME\"] = (\n        f\"{Worker.WORKER_PREFIX}-{WorkerProcess.SERVER_LABEL}-FOO\"\n    )\n\n    message = \"No restart publisher found in worker process\"\n    with pytest.raises(RuntimeError, match=message):\n        worker_serve(**args(mock_app))\n\n    message = \"No worker state found in worker process\"\n    publisher = Mock()\n    with caplog.at_level(logging.ERROR):\n        worker_serve(**args(mock_app, monitor_publisher=publisher))\n\n    assert (\"sanic.error\", logging.ERROR, message) in caplog.record_tuples\n    publisher.send.assert_called_once_with(\"__TERMINATE_EARLY__\")\n\n    del environ[\"SANIC_WORKER_NAME\"]\n\n\ndef test_has_multiplexer(app: Sanic):\n    environ[\"SANIC_WORKER_NAME\"] = (\n        f\"{Worker.WORKER_PREFIX}-{WorkerProcess.SERVER_LABEL}-FOO\"\n    )\n\n    Sanic.register_app(app)\n    with patch(\"sanic.worker.serve._serve_http_1\"):\n        worker_serve(\n            **args(app, monitor_publisher=Mock(), worker_state=Mock())\n        )\n    assert isinstance(app.multiplexer, WorkerMultiplexer)\n\n    del environ[\"SANIC_WORKER_NAME\"]\n\n\n@patch(\"sanic.mixins.startup.WorkerManager\")\ndef test_serve_app_implicit(wm: Mock, app):\n    app.prepare()\n    Sanic.serve()\n    wm.call_args[0] == app.state.workers\n\n\n@patch(\"sanic.mixins.startup.WorkerManager\")\ndef test_serve_app_explicit(wm: Mock, mock_app):\n    Sanic.serve(mock_app)\n    wm.call_args[0] == mock_app.state.workers\n\n\n@patch(\"sanic.mixins.startup.WorkerManager\")\ndef test_serve_app_loader(wm: Mock, mock_app):\n    Sanic.serve(app_loader=AppLoader(factory=lambda: mock_app))\n    wm.call_args[0] == mock_app.state.workers\n    # Sanic.serve(factory=lambda: mock_app)\n\n\n@patch(\"sanic.mixins.startup.WorkerManager\")\ndef test_serve_app_factory(wm: Mock, mock_app):\n    Sanic.serve(factory=lambda: mock_app)\n    wm.call_args[0] == mock_app.state.workers\n\n\n@patch(\"sanic.mixins.startup.WorkerManager\")\n@pytest.mark.parametrize(\"config\", (True, False))\ndef test_serve_with_inspector(\n    WorkerManager: Mock, mock_app: Mock, config: bool\n):\n    Inspector = Mock()\n    mock_app.config.INSPECTOR = config\n    mock_app.inspector_class = Inspector\n    inspector = Mock()\n    Inspector.return_value = inspector\n    WorkerManager.return_value = WorkerManager\n\n    Sanic.serve(mock_app)\n\n    if config:\n        Inspector.assert_called_once()\n        WorkerManager.manage.assert_called_once_with(\n            \"Inspector\", inspector, {}, transient=False\n        )\n    else:\n        Inspector.assert_not_called()\n        WorkerManager.manage.assert_not_called()\n"
  },
  {
    "path": "tox.ini",
    "content": "[tox]\nenvlist = py310, py311, py312, py313, py314, pyNightly, pypy310, {py310,py311,py312,py313,py314,pyNightly,pypy310}-no-ext, lint, check, security, docs, type-checking\n\n[testenv]\nusedevelop = true\nsetenv =\n    {py310,py311,py312,py313,py314,pyNightly}-no-ext: SANIC_NO_UJSON=1\n    {py310,py311,py312,py313,py314,pyNightly}-no-ext: SANIC_NO_UVLOOP=1\nextras = test, http3\ndeps =\n    httpx>=0.23\n    setuptools\nallowlist_externals =\n    pytest\n    coverage\ncommands =\n    pytest -n 3 --dist loadgroup {posargs:tests}\n\n[testenv:py310]\ncommands =\n    pytest {posargs:tests}\n\n[testenv:lint]\ncommands =\n    ruff check sanic\n    ruff format sanic --check\n    slotscheck --verbose -m sanic\n\n[testenv:type-checking]\ncommands =\n    mypy sanic\n\n[testenv:check]\ncommands =\n    python setup.py check -r -s\n\n[pytest]\nfilterwarnings =\n    ignore:.*async with lock.* instead:DeprecationWarning\n    ignore::pytest.PytestUnhandledThreadExceptionWarning\naddopts = --strict-markers\nmarkers =\n    asyncio\n    xdist_group(name): mark test to run on same worker as others with same group name\n\n[testenv:security]\n\ncommands =\n    bandit --recursive sanic -b ./bandit.baseline\n\n[testenv:docs]\nplatform = linux|linux2|darwin\nallowlist_externals = make\nextras = docs, http3\ncommands =\n    make docs-test\n\n[testenv:coverage]\ncommands =\n    coverage run --source ./sanic -m pytest {posargs:tests}\n    - coverage combine --append\n    coverage report -m -i\n    coverage xml -i\n"
  }
]