Compare commits
	
		
			3 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 9e889fc20b | ||
|   | bae2d4cb57 | ||
|   | 492d6fd19d | 
| @@ -1,12 +0,0 @@ | |||||||
| exclude_patterns: |  | ||||||
|   - "sanic/__main__.py" |  | ||||||
|   - "sanic/reloader_helpers.py" |  | ||||||
|   - "sanic/simple.py" |  | ||||||
|   - "sanic/utils.py" |  | ||||||
|   - ".github/" |  | ||||||
|   - "changelogs/" |  | ||||||
|   - "docker/" |  | ||||||
|   - "docs/" |  | ||||||
|   - "examples/" |  | ||||||
|   - "hack/" |  | ||||||
|   - "scripts/" |  | ||||||
| @@ -1,12 +1,7 @@ | |||||||
| [run] | [run] | ||||||
| branch = True | branch = True | ||||||
| source = sanic | source = sanic | ||||||
| omit = | omit = site-packages, sanic/utils.py, sanic/__main__.py | ||||||
|     site-packages |  | ||||||
|     sanic/__main__.py |  | ||||||
|     sanic/reloader_helpers.py |  | ||||||
|     sanic/simple.py |  | ||||||
|     sanic/utils.py |  | ||||||
|  |  | ||||||
| [html] | [html] | ||||||
| directory = coverage | directory = coverage | ||||||
|   | |||||||
							
								
								
									
										37
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										37
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,10 +1,22 @@ | |||||||
|  | # For most projects, this workflow file will not need changing; you simply need | ||||||
|  | # to commit it to your repository. | ||||||
|  | # | ||||||
|  | # You may wish to alter this file to override the set of languages analyzed, | ||||||
|  | # or to provide custom queries or build logic. | ||||||
|  | # | ||||||
|  | # ******** NOTE ******** | ||||||
|  | # We have attempted to detect the languages in your repository. Please check | ||||||
|  | # the `language` matrix defined below to confirm you have the correct set of | ||||||
|  | # supported CodeQL languages. | ||||||
|  | # | ||||||
| name: "CodeQL" | name: "CodeQL" | ||||||
|  |  | ||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
|     branches: [ main ] |     branches: [ master ] | ||||||
|   pull_request: |   pull_request: | ||||||
|     branches: [ main ] |     # The branches below must be a subset of the branches above | ||||||
|  |     branches: [ master ] | ||||||
|   schedule: |   schedule: | ||||||
|     - cron: '25 16 * * 0' |     - cron: '25 16 * * 0' | ||||||
|  |  | ||||||
| @@ -17,18 +29,39 @@ jobs: | |||||||
|       fail-fast: false |       fail-fast: false | ||||||
|       matrix: |       matrix: | ||||||
|         language: [ 'python' ] |         language: [ 'python' ] | ||||||
|  |         # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] | ||||||
|  |         # Learn more: | ||||||
|  |         # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed | ||||||
|  |  | ||||||
|     steps: |     steps: | ||||||
|     - name: Checkout repository |     - name: Checkout repository | ||||||
|       uses: actions/checkout@v2 |       uses: actions/checkout@v2 | ||||||
|  |  | ||||||
|  |     # Initializes the CodeQL tools for scanning. | ||||||
|     - name: Initialize CodeQL |     - name: Initialize CodeQL | ||||||
|       uses: github/codeql-action/init@v1 |       uses: github/codeql-action/init@v1 | ||||||
|       with: |       with: | ||||||
|         languages: ${{ matrix.language }} |         languages: ${{ matrix.language }} | ||||||
|  |         # If you wish to specify custom queries, you can do so here or in a config file. | ||||||
|  |         # By default, queries listed here will override any specified in a config file. | ||||||
|  |         # Prefix the list here with "+" to use these queries and those in the config file. | ||||||
|  |         # queries: ./path/to/local/query, your-org/your-repo/queries@main | ||||||
|  |  | ||||||
|  |     # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java). | ||||||
|  |     # If this step fails, then you should remove it and run the build manually (see below) | ||||||
|     - name: Autobuild |     - name: Autobuild | ||||||
|       uses: github/codeql-action/autobuild@v1 |       uses: github/codeql-action/autobuild@v1 | ||||||
|  |  | ||||||
|  |     # ℹ️ Command-line programs to run using the OS shell. | ||||||
|  |     # 📚 https://git.io/JvXDl | ||||||
|  |  | ||||||
|  |     # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines | ||||||
|  |     #    and modify them (or add more) to build your code if your project | ||||||
|  |     #    uses a compiled language | ||||||
|  |  | ||||||
|  |     #- run: | | ||||||
|  |     #   make bootstrap | ||||||
|  |     #   make release | ||||||
|  |  | ||||||
|     - name: Perform CodeQL Analysis |     - name: Perform CodeQL Analysis | ||||||
|       uses: github/codeql-action/analyze@v1 |       uses: github/codeql-action/analyze@v1 | ||||||
|   | |||||||
							
								
								
									
										40
									
								
								.github/workflows/coverage.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										40
									
								
								.github/workflows/coverage.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,40 +0,0 @@ | |||||||
| name: Coverage check |  | ||||||
| # on: |  | ||||||
| #   push: |  | ||||||
| #     branches: |  | ||||||
| #       - main |  | ||||||
| #     tags: |  | ||||||
| #       - "!*" # Do not execute on tags |  | ||||||
| #     paths: |  | ||||||
| #       - sanic/* |  | ||||||
| #       - tests/* |  | ||||||
| #   pull_request: |  | ||||||
| #     paths: |  | ||||||
| #       - "!*.MD" |  | ||||||
| on: [push, pull_request] |  | ||||||
| jobs: |  | ||||||
|   test: |  | ||||||
|     runs-on: ${{ matrix.os }} |  | ||||||
|     strategy: |  | ||||||
|       matrix: |  | ||||||
|         python-version: [3.9] |  | ||||||
|         os: [ubuntu-latest] |  | ||||||
|       fail-fast: false |  | ||||||
|  |  | ||||||
|     steps: |  | ||||||
|       - uses: actions/checkout@v2 |  | ||||||
|  |  | ||||||
|       - uses: actions/setup-python@v1 |  | ||||||
|         with: |  | ||||||
|           python-version: ${{ matrix.python-version }} |  | ||||||
|  |  | ||||||
|       - name: Install dependencies 🔨 |  | ||||||
|         run: | |  | ||||||
|           python -m pip install --upgrade pip |  | ||||||
|           pip install tox |  | ||||||
|       - uses: paambaati/codeclimate-action@v2.5.3 |  | ||||||
|         if: always() |  | ||||||
|         env: |  | ||||||
|           CC_TEST_REPORTER_ID: ${{ secrets.CODECLIMATE }} |  | ||||||
|         with: |  | ||||||
|           coverageCommand: tox -e coverage |  | ||||||
							
								
								
									
										39
									
								
								.github/workflows/on-demand.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										39
									
								
								.github/workflows/on-demand.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,39 +0,0 @@ | |||||||
| name: On Demand Task |  | ||||||
| on: |  | ||||||
|   workflow_dispatch: |  | ||||||
|     inputs: |  | ||||||
|       python-version: |  | ||||||
|         description: 'Version of Python to use for running Test' |  | ||||||
|         required: false |  | ||||||
|         default: "3.8" |  | ||||||
|       tox-env: |  | ||||||
|         description: 'Test Environment to Run' |  | ||||||
|         required: true |  | ||||||
|         default: '' |  | ||||||
|       os: |  | ||||||
|         description: 'Operating System to Run Test on' |  | ||||||
|         required: false |  | ||||||
|         default: ubuntu-latest |  | ||||||
| jobs: |  | ||||||
|   onDemand: |  | ||||||
|     name: tox-${{ matrix.config.tox-env }}-on-${{ matrix.os }} |  | ||||||
|     runs-on: ${{ matrix.os }} |  | ||||||
|     strategy: |  | ||||||
|       fail-fast: false |  | ||||||
|       matrix: |  | ||||||
|         os: ["${{ github.event.inputs.os}}"] |  | ||||||
|         config: |  | ||||||
|           - { tox-env: "${{ github.event.inputs.tox-env }}", py-version: "${{ github.event.inputs.python-version }}"} |  | ||||||
|     steps: |  | ||||||
|       - name: Checkout Repository |  | ||||||
|         uses: actions/checkout@v2 |  | ||||||
|  |  | ||||||
|       - name: Run tests |  | ||||||
|         uses: harshanarayana/custom-actions@main |  | ||||||
|         with: |  | ||||||
|           python-version: ${{ matrix.config.py-version }} |  | ||||||
|           test-infra-tool: tox |  | ||||||
|           test-infra-version: latest |  | ||||||
|           action: tests |  | ||||||
|           test-additional-args: "-e=${{ matrix.config.tox-env }}" |  | ||||||
|           experimental-ignore-error: "yes" |  | ||||||
							
								
								
									
										32
									
								
								.github/workflows/pr-bandit.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										32
									
								
								.github/workflows/pr-bandit.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,32 +0,0 @@ | |||||||
| name: Security Analysis |  | ||||||
| on: |  | ||||||
|   pull_request: |  | ||||||
|     branches: |  | ||||||
|       - main |  | ||||||
|  |  | ||||||
| jobs: |  | ||||||
|   bandit: |  | ||||||
|     name: type-check-${{ matrix.config.python-version }} |  | ||||||
|     runs-on: ${{ matrix.os }} |  | ||||||
|     strategy: |  | ||||||
|       fail-fast: false |  | ||||||
|       matrix: |  | ||||||
|         os: [ubuntu-latest] |  | ||||||
|         config: |  | ||||||
|           - { python-version: 3.7, tox-env: security} |  | ||||||
|           - { python-version: 3.8, tox-env: security} |  | ||||||
|           - { python-version: 3.9, tox-env: security} |  | ||||||
|     steps: |  | ||||||
|       - name: Checkout the repository |  | ||||||
|         uses: actions/checkout@v2 |  | ||||||
|         id: checkout-branch |  | ||||||
|  |  | ||||||
|       - name: Run Linter Checks |  | ||||||
|         id: linter-check |  | ||||||
|         uses: harshanarayana/custom-actions@main |  | ||||||
|         with: |  | ||||||
|           python-version: ${{ matrix.config.python-version }} |  | ||||||
|           test-infra-tool: tox |  | ||||||
|           test-infra-version: latest |  | ||||||
|           action: tests |  | ||||||
|           test-additional-args: "-e=${{ matrix.config.tox-env }}" |  | ||||||
							
								
								
									
										29
									
								
								.github/workflows/pr-docs.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										29
									
								
								.github/workflows/pr-docs.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,29 +0,0 @@ | |||||||
| name: Document Linter |  | ||||||
| on: |  | ||||||
|   pull_request: |  | ||||||
|     branches: |  | ||||||
|       - main |  | ||||||
|  |  | ||||||
| jobs: |  | ||||||
|   docsLinter: |  | ||||||
|     name: Lint Documentation |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|     strategy: |  | ||||||
|       matrix: |  | ||||||
|         config: |  | ||||||
|           - {python-version: "3.8", tox-env: "docs"} |  | ||||||
|       fail-fast: false |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     steps: |  | ||||||
|       - name: Checkout repository |  | ||||||
|         uses: actions/checkout@v2 |  | ||||||
|  |  | ||||||
|       - name: Run Document Linter |  | ||||||
|         uses: harshanarayana/custom-actions@main |  | ||||||
|         with: |  | ||||||
|           python-version: ${{ matrix.config.python-version }} |  | ||||||
|           test-infra-tool: tox |  | ||||||
|           test-infra-version: latest |  | ||||||
|           action: tests |  | ||||||
|           test-additional-args: "-e=${{ matrix.config.tox-env }}" |  | ||||||
							
								
								
									
										30
									
								
								.github/workflows/pr-linter.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										30
									
								
								.github/workflows/pr-linter.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,30 +0,0 @@ | |||||||
| name: Linter Checks |  | ||||||
| on: |  | ||||||
|   pull_request: |  | ||||||
|     branches: |  | ||||||
|       - main |  | ||||||
|  |  | ||||||
| jobs: |  | ||||||
|   linter: |  | ||||||
|     name: lint |  | ||||||
|     runs-on: ${{ matrix.os }} |  | ||||||
|     strategy: |  | ||||||
|       fail-fast: false |  | ||||||
|       matrix: |  | ||||||
|         os: [ubuntu-latest] |  | ||||||
|         config: |  | ||||||
|           - { python-version: 3.8, tox-env: lint} |  | ||||||
|     steps: |  | ||||||
|       - name: Checkout the repository |  | ||||||
|         uses: actions/checkout@v2 |  | ||||||
|         id: checkout-branch |  | ||||||
|  |  | ||||||
|       - name: Run Linter Checks |  | ||||||
|         id: linter-check |  | ||||||
|         uses: harshanarayana/custom-actions@main |  | ||||||
|         with: |  | ||||||
|           python-version: ${{ matrix.config.python-version }} |  | ||||||
|           test-infra-tool: tox |  | ||||||
|           test-infra-version: latest |  | ||||||
|           action: tests |  | ||||||
|           test-additional-args: "-e=${{ matrix.config.tox-env }}" |  | ||||||
							
								
								
									
										41
									
								
								.github/workflows/pr-python-pypy.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										41
									
								
								.github/workflows/pr-python-pypy.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,41 +0,0 @@ | |||||||
| name: Python PyPy Tests |  | ||||||
| on: |  | ||||||
|   workflow_dispatch: |  | ||||||
|     inputs: |  | ||||||
|       tox-env: |  | ||||||
|         description: "Tox Env to run on the PyPy Infra" |  | ||||||
|         required: false |  | ||||||
|         default: "pypy37" |  | ||||||
|       pypy-version: |  | ||||||
|         description: "Version of PyPy to use" |  | ||||||
|         required: false |  | ||||||
|         default: "pypy-3.7" |  | ||||||
| jobs: |  | ||||||
|   testPyPy: |  | ||||||
|     name: ut-${{ matrix.config.tox-env }}-${{ matrix.os }} |  | ||||||
|     runs-on: ${{ matrix.os }} |  | ||||||
|     strategy: |  | ||||||
|       fail-fast: false |  | ||||||
|       matrix: |  | ||||||
|         # os: [ubuntu-latest, macos-latest] |  | ||||||
|         os: [ubuntu-latest] |  | ||||||
|         config: |  | ||||||
|           - { |  | ||||||
|               python-version: "${{ github.event.inputs.pypy-version }}", |  | ||||||
|               tox-env: "${{ github.event.inputs.tox-env }}", |  | ||||||
|             } |  | ||||||
|     steps: |  | ||||||
|       - name: Checkout the Repository |  | ||||||
|         uses: actions/checkout@v2 |  | ||||||
|         id: checkout-branch |  | ||||||
|  |  | ||||||
|       - name: Run Unit Tests |  | ||||||
|         uses: harshanarayana/custom-actions@main |  | ||||||
|         with: |  | ||||||
|           python-version: ${{ matrix.config.python-version }} |  | ||||||
|           test-infra-tool: tox |  | ||||||
|           test-infra-version: latest |  | ||||||
|           action: tests |  | ||||||
|           test-additional-args: "-e=${{ matrix.config.tox-env }}" |  | ||||||
|           experimental-ignore-error: "true" |  | ||||||
|           command-timeout: "600000" |  | ||||||
							
								
								
									
										38
									
								
								.github/workflows/pr-python37.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										38
									
								
								.github/workflows/pr-python37.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,38 +0,0 @@ | |||||||
| name: Python 3.7 Tests |  | ||||||
| on: |  | ||||||
|   pull_request: |  | ||||||
|     branches: |  | ||||||
|       - main |  | ||||||
|   push: |  | ||||||
|     branches: |  | ||||||
|       - main |  | ||||||
|     paths: |  | ||||||
|       - sanic/* |  | ||||||
|       - tests/* |  | ||||||
|  |  | ||||||
| jobs: |  | ||||||
|   testPy37: |  | ||||||
|     name: ut-${{ matrix.config.tox-env }}-${{ matrix.os }} |  | ||||||
|     runs-on: ${{ matrix.os }} |  | ||||||
|     strategy: |  | ||||||
|       fail-fast: false |  | ||||||
|       matrix: |  | ||||||
|         #         os: [ubuntu-latest, macos-latest] |  | ||||||
|         os: [ubuntu-latest] |  | ||||||
|         config: |  | ||||||
|           - { python-version: 3.7, tox-env: py37 } |  | ||||||
|           - { python-version: 3.7, tox-env: py37-no-ext } |  | ||||||
|     steps: |  | ||||||
|       - name: Checkout the Repository |  | ||||||
|         uses: actions/checkout@v2 |  | ||||||
|         id: checkout-branch |  | ||||||
|  |  | ||||||
|       - name: Run Unit Tests |  | ||||||
|         uses: harshanarayana/custom-actions@main |  | ||||||
|         with: |  | ||||||
|           python-version: ${{ matrix.config.python-version }} |  | ||||||
|           test-infra-tool: tox |  | ||||||
|           test-infra-version: latest |  | ||||||
|           action: tests |  | ||||||
|           test-additional-args: "-e=${{ matrix.config.tox-env }}" |  | ||||||
|           test-failure-retry: "3" |  | ||||||
							
								
								
									
										38
									
								
								.github/workflows/pr-python38.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										38
									
								
								.github/workflows/pr-python38.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,38 +0,0 @@ | |||||||
| name: Python 3.8 Tests |  | ||||||
| on: |  | ||||||
|   pull_request: |  | ||||||
|     branches: |  | ||||||
|       - main |  | ||||||
|   push: |  | ||||||
|     branches: |  | ||||||
|       - main |  | ||||||
|     paths: |  | ||||||
|       - sanic/* |  | ||||||
|       - tests/* |  | ||||||
|  |  | ||||||
| jobs: |  | ||||||
|   testPy38: |  | ||||||
|     name: ut-${{ matrix.config.tox-env }}-${{ matrix.os }} |  | ||||||
|     runs-on: ${{ matrix.os }} |  | ||||||
|     strategy: |  | ||||||
|       fail-fast: false |  | ||||||
|       matrix: |  | ||||||
|         # os: [ubuntu-latest, macos-latest] |  | ||||||
|         os: [ubuntu-latest] |  | ||||||
|         config: |  | ||||||
|           - { python-version: 3.8, tox-env: py38 } |  | ||||||
|           - { python-version: 3.8, tox-env: py38-no-ext } |  | ||||||
|     steps: |  | ||||||
|       - name: Checkout the Repository |  | ||||||
|         uses: actions/checkout@v2 |  | ||||||
|         id: checkout-branch |  | ||||||
|  |  | ||||||
|       - name: Run Unit Tests |  | ||||||
|         uses: harshanarayana/custom-actions@main |  | ||||||
|         with: |  | ||||||
|           python-version: ${{ matrix.config.python-version }} |  | ||||||
|           test-infra-tool: tox |  | ||||||
|           test-infra-version: latest |  | ||||||
|           action: tests |  | ||||||
|           test-additional-args: "-e=${{ matrix.config.tox-env }}" |  | ||||||
|           test-failure-retry: "3" |  | ||||||
							
								
								
									
										50
									
								
								.github/workflows/pr-python39.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										50
									
								
								.github/workflows/pr-python39.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,50 +0,0 @@ | |||||||
| name: Python 3.9 Tests |  | ||||||
| on: |  | ||||||
|   pull_request: |  | ||||||
|     branches: |  | ||||||
|       - main |  | ||||||
|   push: |  | ||||||
|     branches: |  | ||||||
|       - main |  | ||||||
|     paths: |  | ||||||
|       - sanic/* |  | ||||||
|       - tests/* |  | ||||||
|  |  | ||||||
| jobs: |  | ||||||
|   testPy39: |  | ||||||
|     name: ut-${{ matrix.config.tox-env }}-${{ matrix.os }} |  | ||||||
|     runs-on: ${{ matrix.os }} |  | ||||||
|     strategy: |  | ||||||
|       fail-fast: false |  | ||||||
|       matrix: |  | ||||||
|         # os: [ubuntu-latest, macos-latest] |  | ||||||
|         os: [ubuntu-latest] |  | ||||||
|         config: |  | ||||||
|           - { |  | ||||||
|               python-version: 3.9, |  | ||||||
|               tox-env: py39, |  | ||||||
|               ignore-error-flake: "false", |  | ||||||
|               command-timeout: "0", |  | ||||||
|             } |  | ||||||
|           - { |  | ||||||
|               python-version: 3.9, |  | ||||||
|               tox-env: py39-no-ext, |  | ||||||
|               ignore-error-flake: "true", |  | ||||||
|               command-timeout: "600000", |  | ||||||
|             } |  | ||||||
|     steps: |  | ||||||
|       - name: Checkout the Repository |  | ||||||
|         uses: actions/checkout@v2 |  | ||||||
|         id: checkout-branch |  | ||||||
|  |  | ||||||
|       - name: Run Unit Tests |  | ||||||
|         uses: harshanarayana/custom-actions@main |  | ||||||
|         with: |  | ||||||
|           python-version: ${{ matrix.config.python-version }} |  | ||||||
|           test-infra-tool: tox |  | ||||||
|           test-infra-version: latest |  | ||||||
|           action: tests |  | ||||||
|           test-additional-args: "-e=${{ matrix.config.tox-env }},-vv=''" |  | ||||||
|           experimental-ignore-error: "${{ matrix.config.ignore-error-flake }}" |  | ||||||
|           command-timeout: "${{ matrix.config.command-timeout }}" |  | ||||||
|           test-failure-retry: "3" |  | ||||||
							
								
								
									
										32
									
								
								.github/workflows/pr-type-check.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										32
									
								
								.github/workflows/pr-type-check.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,32 +0,0 @@ | |||||||
| name: Typing Checks |  | ||||||
| on: |  | ||||||
|   pull_request: |  | ||||||
|     branches: |  | ||||||
|       - main |  | ||||||
|  |  | ||||||
| jobs: |  | ||||||
|   typeChecking: |  | ||||||
|     name: type-check-${{ matrix.config.python-version }} |  | ||||||
|     runs-on: ${{ matrix.os }} |  | ||||||
|     strategy: |  | ||||||
|       fail-fast: false |  | ||||||
|       matrix: |  | ||||||
|         os: [ubuntu-latest] |  | ||||||
|         config: |  | ||||||
|           - { python-version: 3.7, tox-env: type-checking} |  | ||||||
|           - { python-version: 3.8, tox-env: type-checking} |  | ||||||
|           - { python-version: 3.9, tox-env: type-checking} |  | ||||||
|     steps: |  | ||||||
|       - name: Checkout the repository |  | ||||||
|         uses: actions/checkout@v2 |  | ||||||
|         id: checkout-branch |  | ||||||
|  |  | ||||||
|       - name: Run Linter Checks |  | ||||||
|         id: linter-check |  | ||||||
|         uses: harshanarayana/custom-actions@main |  | ||||||
|         with: |  | ||||||
|           python-version: ${{ matrix.config.python-version }} |  | ||||||
|           test-infra-tool: tox |  | ||||||
|           test-infra-version: latest |  | ||||||
|           action: tests |  | ||||||
|           test-additional-args: "-e=${{ matrix.config.tox-env }}" |  | ||||||
							
								
								
									
										34
									
								
								.github/workflows/pr-windows.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										34
									
								
								.github/workflows/pr-windows.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,34 +0,0 @@ | |||||||
| # name: Run Unit Tests on Windows |  | ||||||
| # on: |  | ||||||
| #   pull_request: |  | ||||||
| #     branches: |  | ||||||
| #       - main |  | ||||||
|  |  | ||||||
| # jobs: |  | ||||||
| #   testsOnWindows: |  | ||||||
| #     name: ut-${{ matrix.config.tox-env }} |  | ||||||
| #     runs-on: windows-latest |  | ||||||
| #     strategy: |  | ||||||
| #       fail-fast: false |  | ||||||
| #       matrix: |  | ||||||
| #         config: |  | ||||||
| #           - { python-version: 3.7, tox-env: py37-no-ext } |  | ||||||
| #           - { python-version: 3.8, tox-env: py38-no-ext } |  | ||||||
| #           - { python-version: 3.9, tox-env: py39-no-ext } |  | ||||||
| #           - { python-version: pypy-3.7, tox-env: pypy37-no-ext } |  | ||||||
|  |  | ||||||
| #     steps: |  | ||||||
| #       - name: Checkout Repository |  | ||||||
| #         uses: actions/checkout@v2 |  | ||||||
|  |  | ||||||
| #       - name: Run Unit Tests |  | ||||||
| #         uses: ahopkins/custom-actions@pip-extra-args |  | ||||||
| #         with: |  | ||||||
| #           python-version: ${{ matrix.config.python-version }} |  | ||||||
| #           test-infra-tool: tox |  | ||||||
| #           test-infra-version: latest |  | ||||||
| #           action: tests |  | ||||||
| #           test-additional-args: "-e=${{ matrix.config.tox-env }}" |  | ||||||
| #           experimental-ignore-error: "true" |  | ||||||
| #           command-timeout: "600000" |  | ||||||
| #           pip-extra-args: "--user" |  | ||||||
							
								
								
									
										48
									
								
								.github/workflows/publish-images.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										48
									
								
								.github/workflows/publish-images.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,48 +0,0 @@ | |||||||
| name: Publish Docker Images |  | ||||||
| on: |  | ||||||
|   workflow_run: |  | ||||||
|     workflows: |  | ||||||
|       - 'Publish Artifacts' |  | ||||||
|     types: |  | ||||||
|       - completed |  | ||||||
|  |  | ||||||
| jobs: |  | ||||||
|   publishDockerImages: |  | ||||||
|     name: Docker Image Build [${{ matrix.python-version }}] |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|  |  | ||||||
|     strategy: |  | ||||||
|       fail-fast: true |  | ||||||
|       matrix: |  | ||||||
|         python-version: ["3.7", "3.8", "3.9"] |  | ||||||
|  |  | ||||||
|     steps: |  | ||||||
|       - name: Checkout repository |  | ||||||
|         uses: actions/checkout@v2 |  | ||||||
|  |  | ||||||
|       - name: Build Latest Base images for ${{ matrix.python-version }} |  | ||||||
|         uses: harshanarayana/custom-actions@main |  | ||||||
|         with: |  | ||||||
|           docker-image-base-name: sanicframework/sanic-build |  | ||||||
|           ignore-python-setup: 'true' |  | ||||||
|           dockerfile-base-dir: './docker' |  | ||||||
|           action: 'image-publish' |  | ||||||
|           docker-image-tag: "${{ matrix.python-version }}" |  | ||||||
|           docker-file-suffix: "base" |  | ||||||
|           docker-build-args: "PYTHON_VERSION=${{ matrix.python-version }}" |  | ||||||
|           registry-auth-user: ${{ secrets.DOCKER_ACCESS_USER }} |  | ||||||
|           registry-auth-password: ${{ secrets.DOCKER_ACCESS_TOKEN }} |  | ||||||
|           push-images: 'true' |  | ||||||
|  |  | ||||||
|       - name: Publish Sanic Docker Image for ${{ matrix.python-version }} |  | ||||||
|         uses: harshanarayana/custom-actions@main |  | ||||||
|         with: |  | ||||||
|           docker-image-base-name: sanicframework/sanic |  | ||||||
|           ignore-python-setup: 'true' |  | ||||||
|           dockerfile-base-dir: './docker' |  | ||||||
|           action: 'image-publish' |  | ||||||
|           docker-build-args: "BASE_IMAGE_TAG=${{ matrix.python-version }}" |  | ||||||
|           docker-image-prefix: "${{ matrix.python-version }}" |  | ||||||
|           registry-auth-user: ${{ secrets.DOCKER_ACCESS_USER }} |  | ||||||
|           registry-auth-password: ${{ secrets.DOCKER_ACCESS_TOKEN }} |  | ||||||
|           push-images: 'true' |  | ||||||
							
								
								
									
										28
									
								
								.github/workflows/publish-package.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										28
									
								
								.github/workflows/publish-package.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,28 +0,0 @@ | |||||||
| name: Publish Artifacts |  | ||||||
| on: |  | ||||||
|   release: |  | ||||||
|     types: [created] |  | ||||||
|  |  | ||||||
| jobs: |  | ||||||
|   publishPythonPackage: |  | ||||||
|     name: Publishing Sanic Release Artifacts |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|  |  | ||||||
|     strategy: |  | ||||||
|       fail-fast: true |  | ||||||
|       matrix: |  | ||||||
|         python-version: ["3.8"] |  | ||||||
|  |  | ||||||
|     steps: |  | ||||||
|       - name: Checkout Repository |  | ||||||
|         uses: actions/checkout@v2 |  | ||||||
|  |  | ||||||
|       - name: Publish Python Package |  | ||||||
|         uses: harshanarayana/custom-actions@main |  | ||||||
|         with: |  | ||||||
|           python-version: ${{ matrix.python-version }} |  | ||||||
|           package-infra-name: "twine" |  | ||||||
|           pypi-user: __token__ |  | ||||||
|           pypi-access-token: ${{ secrets.PYPI_ACCESS_TOKEN }} |  | ||||||
|           action: "package-publish" |  | ||||||
|           pypi-verify-metadata: "true" |  | ||||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -6,7 +6,6 @@ | |||||||
| .coverage | .coverage | ||||||
| .coverage.* | .coverage.* | ||||||
| coverage | coverage | ||||||
| coverage.xml |  | ||||||
| .tox | .tox | ||||||
| settings.py | settings.py | ||||||
| .idea/* | .idea/* | ||||||
| @@ -19,6 +18,3 @@ build/* | |||||||
| .DS_Store | .DS_Store | ||||||
| dist/* | dist/* | ||||||
| pip-wheel-metadata/ | pip-wheel-metadata/ | ||||||
| .pytest_cache/* |  | ||||||
| .venv/* |  | ||||||
| .vscode/* |  | ||||||
|   | |||||||
							
								
								
									
										94
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | |||||||
|  | sudo: false | ||||||
|  | language: python | ||||||
|  | cache: | ||||||
|  |   directories: | ||||||
|  |     - $HOME/.cache/pip | ||||||
|  | matrix: | ||||||
|  |   include: | ||||||
|  |     - env: TOX_ENV=py37 | ||||||
|  |       python: 3.7 | ||||||
|  |       dist: xenial | ||||||
|  |       sudo: true | ||||||
|  |       name: "Python 3.7 with Extensions" | ||||||
|  |     - env: TOX_ENV=py37-no-ext | ||||||
|  |       python: 3.7 | ||||||
|  |       dist: xenial | ||||||
|  |       sudo: true | ||||||
|  |       name: "Python 3.7 without Extensions" | ||||||
|  |     - env: TOX_ENV=py38 | ||||||
|  |       python: 3.8 | ||||||
|  |       dist: xenial | ||||||
|  |       sudo: true | ||||||
|  |       name: "Python 3.8 with Extensions" | ||||||
|  |     - env: TOX_ENV=py38-no-ext | ||||||
|  |       python: 3.8 | ||||||
|  |       dist: xenial | ||||||
|  |       sudo: true | ||||||
|  |       name: "Python 3.8 without Extensions" | ||||||
|  |     - env: TOX_ENV=py39 | ||||||
|  |       python: 3.9 | ||||||
|  |       dist: bionic | ||||||
|  |       sudo: true | ||||||
|  |       name: "Python 3.9 with Extensions" | ||||||
|  |     - env: TOX_ENV=py39-no-ext | ||||||
|  |       python: 3.9 | ||||||
|  |       dist: bionic | ||||||
|  |       sudo: true | ||||||
|  |       name: "Python 3.9 without Extensions" | ||||||
|  |     - env: TOX_ENV=type-checking | ||||||
|  |       python: 3.7 | ||||||
|  |       name: "Python 3.7 Type checks" | ||||||
|  |     - env: TOX_ENV=type-checking | ||||||
|  |       python: 3.8 | ||||||
|  |       name: "Python 3.8 Type checks" | ||||||
|  |     - env: TOX_ENV=type-checking | ||||||
|  |       python: 3.9 | ||||||
|  |       dist: bionic | ||||||
|  |       name: "Python 3.9 Type checks" | ||||||
|  |     - env: TOX_ENV=security | ||||||
|  |       python: 3.7 | ||||||
|  |       dist: xenial | ||||||
|  |       sudo: true | ||||||
|  |       name: "Python 3.7 Bandit security scan" | ||||||
|  |     - env: TOX_ENV=security | ||||||
|  |       python: 3.8 | ||||||
|  |       dist: xenial | ||||||
|  |       sudo: true | ||||||
|  |       name: "Python 3.8 Bandit security scan" | ||||||
|  |     - env: TOX_ENV=security | ||||||
|  |       python: 3.9 | ||||||
|  |       dist: bionic | ||||||
|  |       sudo: true | ||||||
|  |       name: "Python 3.9 Bandit security scan" | ||||||
|  |     - env: TOX_ENV=docs | ||||||
|  |       python: 3.7 | ||||||
|  |       dist: xenial | ||||||
|  |       sudo: true | ||||||
|  |       name: "Python 3.7 Documentation tests" | ||||||
|  |     - env: TOX_ENV=pyNightly | ||||||
|  |       python: "nightly" | ||||||
|  |       name: "Python nightly with Extensions" | ||||||
|  |     - env: TOX_ENV=pyNightly-no-ext | ||||||
|  |       python: "nightly" | ||||||
|  |       name: "Python nightly without Extensions" | ||||||
|  |   allow_failures: | ||||||
|  |     - env: TOX_ENV=pyNightly | ||||||
|  |       python: "nightly" | ||||||
|  |       name: "Python nightly with Extensions" | ||||||
|  |     - env: TOX_ENV=pyNightly-no-ext | ||||||
|  |       python: "nightly" | ||||||
|  |       name: "Python nightly without Extensions" | ||||||
|  | install: | ||||||
|  |   - pip install -U tox | ||||||
|  |   - pip install codecov | ||||||
|  | script: travis_retry tox -e $TOX_ENV | ||||||
|  | after_success: | ||||||
|  |   - codecov | ||||||
|  | deploy: | ||||||
|  |   provider: pypi | ||||||
|  |   user: brewmaster | ||||||
|  |   password: | ||||||
|  |     secure: "GoawLwmbtJOgKB6AJ0ZSYUUnNwIoonseHBxaAUH3zu79TS/Afrq+yB3lsVaMSG0CbyDgN4FrfD1phT1NzbvZ1VcLIOTDtCrmpQ1kLDw+zwgF40ab8sp8fPkKVHHHfCCs1mjltHIpxQa5lZTJcAs6Bpi/lbUWWwYxFzSV8pHw4W4hY09EHUd2o+evLTSVxaploetSt725DJUYKICUr2eAtCC11IDnIW4CzBJEx6krVV3uhzfTJW0Ls17x0c6sdZ9icMnV/G9xO/eQH6RIHe4xcrWJ6cmLDNKoGAkJp+BKr1CeVVg7Jw/MzPjvZKL2/ki6Beue1y6GUIy7lOS7jPVaOEhJ23b0zQwFcLMZw+Tt+E3v6QfHk+B/WBBBnM3zUZed9UI+QyW8+lqLLt39sQX0FO0P3eaDh8qTXtUuon2jTyFMMAMTFRTNpJmpAzuBH9yeMmDeALPTh0HphI+BkoUl5q1QbWFYjjnZMH2CatApxpLybt9A7rwm//PbOG0TSI93GEKNQ4w5DYryKTfwHzRBptNSephJSuxZYEfJsmUtas5es1D7Fe0PkyjxNNSU+eO+8wsTlitLUsJO4k0jAgy+cEKdU7YJ3J0GZVXocSkrNnUfd2hQPcJ3UtEJx3hLqqr8EM7EZBAasc1yGHh36NFetclzFY24YPih0G1+XurhTys=" | ||||||
|  |   on: | ||||||
|  |     tags: true | ||||||
|  |   distributions: "sdist bdist_wheel" | ||||||
| @@ -1,95 +1,3 @@ | |||||||
| Version 21.6.0 |  | ||||||
| -------------- |  | ||||||
|  |  | ||||||
| Features |  | ||||||
| ******** |  | ||||||
|  |  | ||||||
|   * `#2094 <https://github.com/sanic-org/sanic/pull/2094>`_ |  | ||||||
|     Add ``response.eof()`` method for closing a stream in a handler |  | ||||||
|   * `#2097 <https://github.com/sanic-org/sanic/pull/2097>`_ |  | ||||||
|     Allow case-insensitive HTTP Upgrade header |  | ||||||
|   * `#2104 <https://github.com/sanic-org/sanic/pull/2104>`_ |  | ||||||
|     Explicit usage of CIMultiDict getters |  | ||||||
|   * `#2109 <https://github.com/sanic-org/sanic/pull/2109>`_ |  | ||||||
|     Consistent use of error loggers |  | ||||||
|   * `#2114 <https://github.com/sanic-org/sanic/pull/2114>`_ |  | ||||||
|     New ``client_ip`` access of connection info instance |  | ||||||
|   * `#2119 <https://github.com/sanic-org/sanic/pull/2119>`_ |  | ||||||
|     Alternatate classes on instantiation for ``Config`` and ``Sanic.ctx`` |  | ||||||
|   * `#2133 <https://github.com/sanic-org/sanic/pull/2133>`_ |  | ||||||
|     Implement new version of AST router |  | ||||||
|  |  | ||||||
|       * Proper differentiation between ``alpha`` and ``string`` param types |  | ||||||
|       * Adds a ``slug`` param type, example: ``<foo:slug>`` |  | ||||||
|       * Deprecates ``<foo:string>`` in favor of ``<foo:str>`` |  | ||||||
|       * Deprecates ``<foo:number>`` in favor of ``<foo:float>`` |  | ||||||
|       * Adds a ``route.uri`` accessor |  | ||||||
|   * `#2136 <https://github.com/sanic-org/sanic/pull/2136>`_ |  | ||||||
|     CLI improvements with new optional params |  | ||||||
|   * `#2137 <https://github.com/sanic-org/sanic/pull/2137>`_ |  | ||||||
|     Add ``version_prefix`` to URL builders |  | ||||||
|   * `#2140 <https://github.com/sanic-org/sanic/pull/2140>`_ |  | ||||||
|     Event autoregistration with ``EVENT_AUTOREGISTER`` |  | ||||||
|   * `#2146 <https://github.com/sanic-org/sanic/pull/2146>`_, `#2147 <https://github.com/sanic-org/sanic/pull/2147>`_ |  | ||||||
|     Require stricter names on  ``Sanic()`` and ``Blueprint()`` |  | ||||||
|   * `#2150 <https://github.com/sanic-org/sanic/pull/2150>`_ |  | ||||||
|     Infinitely reusable and nestable ``Blueprint`` and ``BlueprintGroup`` |  | ||||||
|   * `#2154 <https://github.com/sanic-org/sanic/pull/2154>`_ |  | ||||||
|     Upgrade ``websockets`` dependency to min version |  | ||||||
|   * `#2155 <https://github.com/sanic-org/sanic/pull/2155>`_ |  | ||||||
|     Allow for maximum header sizes to be increased: ``REQUEST_MAX_HEADER_SIZE`` |  | ||||||
|   * `#2157 <https://github.com/sanic-org/sanic/pull/2157>`_ |  | ||||||
|     Allow app factory pattern in CLI |  | ||||||
|   * `#2165 <https://github.com/sanic-org/sanic/pull/2165>`_ |  | ||||||
|     Change HTTP methods to enums |  | ||||||
|   * `#2167 <https://github.com/sanic-org/sanic/pull/2167>`_ |  | ||||||
|     Allow auto-reloading on additional directories |  | ||||||
|   * `#2168 <https://github.com/sanic-org/sanic/pull/2168>`_ |  | ||||||
|     Add simple HTTP server to CLI |  | ||||||
|   * `#2170 <https://github.com/sanic-org/sanic/pull/2170>`_ |  | ||||||
|     Additional methods for attaching ``HTTPMethodView`` |  | ||||||
|  |  | ||||||
| Bugfixes |  | ||||||
| ******** |  | ||||||
|  |  | ||||||
|   * `#2091 <https://github.com/sanic-org/sanic/pull/2091>`_ |  | ||||||
|     Fix ``UserWarning`` in ASGI mode for missing ``__slots__`` |  | ||||||
|   * `#2099 <https://github.com/sanic-org/sanic/pull/2099>`_ |  | ||||||
|     Fix static request handler logging exception on 404 |  | ||||||
|   * `#2110 <https://github.com/sanic-org/sanic/pull/2110>`_ |  | ||||||
|     Fix request.args.pop removes parameters inconsistently |  | ||||||
|   * `#2107 <https://github.com/sanic-org/sanic/pull/2107>`_ |  | ||||||
|     Fix type hinting for load_env |  | ||||||
|   * `#2127 <https://github.com/sanic-org/sanic/pull/2127>`_ |  | ||||||
|     Make sure ASGI ws subprotocols is a list |  | ||||||
|   * `#2128 <https://github.com/sanic-org/sanic/pull/2128>`_ |  | ||||||
|     Fix issue where Blueprint exception handlers do not consistently route to proper handler |  | ||||||
|  |  | ||||||
|  |  | ||||||
| Deprecations and Removals |  | ||||||
| ************************* |  | ||||||
|  |  | ||||||
|   * `#2156 <https://github.com/sanic-org/sanic/pull/2156>`_ |  | ||||||
|     Remove config value ``REQUEST_BUFFER_QUEUE_SIZE`` |  | ||||||
|   * `#2170 <https://github.com/sanic-org/sanic/pull/2170>`_ |  | ||||||
|     ``CompositionView`` deprecated and marked for removal in 21.12 |  | ||||||
|   * `#2172 <https://github.com/sanic-org/sanic/pull/2170>`_ |  | ||||||
|     Deprecate StreamingHTTPResponse |  | ||||||
|  |  | ||||||
| Developer infrastructure |  | ||||||
| ************************ |  | ||||||
|  |  | ||||||
|   * `#2149 <https://github.com/sanic-org/sanic/pull/2149>`_ |  | ||||||
|     Remove Travis CI in favor of GitHub Actions |  | ||||||
|  |  | ||||||
| Improved Documentation |  | ||||||
| ********************** |  | ||||||
|  |  | ||||||
|   * `#2164 <https://github.com/sanic-org/sanic/pull/2164>`_ |  | ||||||
|     Fix typo in documentation |  | ||||||
|   * `#2100 <https://github.com/sanic-org/sanic/pull/2100>`_ |  | ||||||
|     Remove documentation for non-existent arguments |  | ||||||
|  |  | ||||||
| Version 21.3.2 | Version 21.3.2 | ||||||
| -------------- | -------------- | ||||||
|  |  | ||||||
|   | |||||||
| @@ -87,7 +87,7 @@ Permform ``flake8``\ , ``black`` and ``isort`` checks. | |||||||
|    tox -e lint |    tox -e lint | ||||||
|  |  | ||||||
| Run type annotation checks | Run type annotation checks | ||||||
| -------------------------- | --------------- | ||||||
|  |  | ||||||
| ``tox`` environment -> ``[testenv:type-checking]`` | ``tox`` environment -> ``[testenv:type-checking]`` | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								Makefile
									
									
									
									
									
								
							| @@ -49,9 +49,6 @@ test: clean | |||||||
| test-coverage: clean | test-coverage: clean | ||||||
| 	python setup.py test --pytest-args="--cov sanic --cov-report term --cov-append " | 	python setup.py test --pytest-args="--cov sanic --cov-report term --cov-append " | ||||||
|  |  | ||||||
| view-coverage: |  | ||||||
| 	sanic ./coverage --simple |  | ||||||
|  |  | ||||||
| install: | install: | ||||||
| 	python setup.py install | 	python setup.py install | ||||||
|  |  | ||||||
| @@ -88,7 +85,8 @@ docs-test: docs-clean | |||||||
| 	cd docs && make dummy | 	cd docs && make dummy | ||||||
|  |  | ||||||
| docs-serve: | docs-serve: | ||||||
| 	sphinx-autobuild docs docs/_build/html --port 9999 --watch ./ | 	# python -m http.server --directory=./docs/_build/html 9999 | ||||||
|  | 	sphinx-autobuild docs docs/_build/html --port 9999 --watch ./sanic | ||||||
|  |  | ||||||
| changelog: | changelog: | ||||||
| 	python scripts/changelog.py | 	python scripts/changelog.py | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								README.rst
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								README.rst
									
									
									
									
									
								
							| @@ -11,7 +11,7 @@ Sanic | Build fast. Run fast. | |||||||
|     :stub-columns: 1 |     :stub-columns: 1 | ||||||
|  |  | ||||||
|     * - Build |     * - Build | ||||||
|       - | |Py39Test| |Py38Test| |Py37Test| |Codecov| |       - | |Build Status| |AppVeyor Build Status| |Codecov| | ||||||
|     * - Docs |     * - Docs | ||||||
|       - | |UserGuide| |Documentation| |       - | |UserGuide| |Documentation| | ||||||
|     * - Package |     * - Package | ||||||
| @@ -29,12 +29,10 @@ Sanic | Build fast. Run fast. | |||||||
|    :target: https://discord.gg/FARQzAEMAA |    :target: https://discord.gg/FARQzAEMAA | ||||||
| .. |Codecov| image:: https://codecov.io/gh/sanic-org/sanic/branch/master/graph/badge.svg | .. |Codecov| image:: https://codecov.io/gh/sanic-org/sanic/branch/master/graph/badge.svg | ||||||
|     :target: https://codecov.io/gh/sanic-org/sanic |     :target: https://codecov.io/gh/sanic-org/sanic | ||||||
| .. |Py39Test| image:: https://github.com/sanic-org/sanic/actions/workflows/pr-python39.yml/badge.svg?branch=main | .. |Build Status| image:: https://travis-ci.com/sanic-org/sanic.svg?branch=master | ||||||
|    :target: https://github.com/sanic-org/sanic/actions/workflows/pr-python39.yml |    :target: https://travis-ci.com/sanic-org/sanic | ||||||
| .. |Py38Test| image:: https://github.com/sanic-org/sanic/actions/workflows/pr-python38.yml/badge.svg?branch=main | .. |AppVeyor Build Status| image:: https://ci.appveyor.com/api/projects/status/d8pt3ids0ynexi8c/branch/master?svg=true | ||||||
|    :target: https://github.com/sanic-org/sanic/actions/workflows/pr-python38.yml |    :target: https://ci.appveyor.com/project/sanic-org/sanic | ||||||
| .. |Py37Test| image:: https://github.com/sanic-org/sanic/actions/workflows/pr-python37.yml/badge.svg?branch=main |  | ||||||
|    :target: https://github.com/sanic-org/sanic/actions/workflows/pr-python37.yml |  | ||||||
| .. |Documentation| image:: https://readthedocs.org/projects/sanic/badge/?version=latest | .. |Documentation| image:: https://readthedocs.org/projects/sanic/badge/?version=latest | ||||||
|    :target: http://sanic.readthedocs.io/en/latest/?badge=latest |    :target: http://sanic.readthedocs.io/en/latest/?badge=latest | ||||||
| .. |PyPI| image:: https://img.shields.io/pypi/v/sanic.svg | .. |PyPI| image:: https://img.shields.io/pypi/v/sanic.svg | ||||||
|   | |||||||
							
								
								
									
										14
									
								
								codecov.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								codecov.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | codecov: | ||||||
|  |   require_ci_to_pass: no | ||||||
|  | coverage: | ||||||
|  |   precision: 3 | ||||||
|  |   round: nearest | ||||||
|  |   status: | ||||||
|  |     project: | ||||||
|  |       default: | ||||||
|  |         target: auto | ||||||
|  |         threshold: 0.5% | ||||||
|  |     patch: | ||||||
|  |       default: | ||||||
|  |         target: auto | ||||||
|  |         threshold: 0.75% | ||||||
| @@ -1,9 +1,28 @@ | |||||||
| ARG BASE_IMAGE_TAG | FROM alpine:3.7 | ||||||
|  |  | ||||||
| FROM sanicframework/sanic-build:${BASE_IMAGE_TAG} | RUN apk add --no-cache --update \ | ||||||
|  |         curl \ | ||||||
|  |         bash \ | ||||||
|  |         build-base \ | ||||||
|  |         ca-certificates \ | ||||||
|  |         git \ | ||||||
|  |         bzip2-dev \ | ||||||
|  |         linux-headers \ | ||||||
|  |         ncurses-dev \ | ||||||
|  |         openssl \ | ||||||
|  |         openssl-dev \ | ||||||
|  |         readline-dev \ | ||||||
|  |         sqlite-dev | ||||||
|  |  | ||||||
| RUN apk update |  | ||||||
| RUN update-ca-certificates | RUN update-ca-certificates | ||||||
|  | RUN rm -rf /var/cache/apk/* | ||||||
|  |  | ||||||
| RUN pip install sanic | ENV PYENV_ROOT="/root/.pyenv" | ||||||
| RUN apk del build-base | ENV PATH="$PYENV_ROOT/bin:$PATH" | ||||||
|  |  | ||||||
|  | ADD . /app | ||||||
|  | WORKDIR /app | ||||||
|  |  | ||||||
|  | RUN /app/docker/bin/install_python.sh 3.5.4 3.6.4 | ||||||
|  |  | ||||||
|  | ENTRYPOINT ["./docker/bin/entrypoint.sh"] | ||||||
|   | |||||||
| @@ -1,9 +0,0 @@ | |||||||
| ARG PYTHON_VERSION |  | ||||||
|  |  | ||||||
| FROM python:${PYTHON_VERSION}-alpine |  | ||||||
| RUN apk update |  | ||||||
| RUN apk add --no-cache --update build-base \ |  | ||||||
|         ca-certificates \ |  | ||||||
|         openssl |  | ||||||
| RUN update-ca-certificates |  | ||||||
| RUN rm -rf /var/cache/apk/* |  | ||||||
							
								
								
									
										11
									
								
								docker/bin/entrypoint.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										11
									
								
								docker/bin/entrypoint.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | set -e | ||||||
|  |  | ||||||
|  | eval "$(pyenv init -)" | ||||||
|  | eval "$(pyenv virtualenv-init -)" | ||||||
|  | source /root/.pyenv/completions/pyenv.bash | ||||||
|  |  | ||||||
|  | pip install tox | ||||||
|  |  | ||||||
|  | exec $@ | ||||||
|  |  | ||||||
							
								
								
									
										17
									
								
								docker/bin/install_python.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										17
									
								
								docker/bin/install_python.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | set -e | ||||||
|  |  | ||||||
|  | export CFLAGS='-O2' | ||||||
|  | export EXTRA_CFLAGS="-DTHREAD_STACK_SIZE=0x100000" | ||||||
|  |  | ||||||
|  | curl -L https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer | bash | ||||||
|  | eval "$(pyenv init -)" | ||||||
|  |  | ||||||
|  | for ver in $@ | ||||||
|  | do | ||||||
|  |     pyenv install $ver | ||||||
|  | done | ||||||
|  |  | ||||||
|  | pyenv global $@ | ||||||
|  | pip install --upgrade pip | ||||||
|  | pyenv rehash | ||||||
| @@ -1,17 +0,0 @@ | |||||||
| Application |  | ||||||
| =========== |  | ||||||
|  |  | ||||||
| sanic.app |  | ||||||
| --------- |  | ||||||
|  |  | ||||||
| .. automodule:: sanic.app |  | ||||||
|     :members: |  | ||||||
|     :show-inheritance: |  | ||||||
|     :inherited-members: |  | ||||||
|  |  | ||||||
| sanic.config |  | ||||||
| ------------ |  | ||||||
|  |  | ||||||
| .. automodule:: sanic.config |  | ||||||
|     :members: |  | ||||||
|     :show-inheritance: |  | ||||||
| @@ -1,17 +0,0 @@ | |||||||
| Blueprints |  | ||||||
| ========== |  | ||||||
|  |  | ||||||
| sanic.blueprints |  | ||||||
| ---------------- |  | ||||||
|  |  | ||||||
| .. automodule:: sanic.blueprints |  | ||||||
|     :members: |  | ||||||
|     :show-inheritance: |  | ||||||
|     :inherited-members: |  | ||||||
|  |  | ||||||
| sanic.blueprint_group |  | ||||||
| --------------------- |  | ||||||
|  |  | ||||||
| .. automodule:: sanic.blueprint_group |  | ||||||
|     :members: |  | ||||||
|     :special-members: |  | ||||||
| @@ -1,47 +0,0 @@ | |||||||
| Core |  | ||||||
| ==== |  | ||||||
|  |  | ||||||
| sanic.cookies |  | ||||||
| ------------- |  | ||||||
|  |  | ||||||
| .. automodule:: sanic.cookies |  | ||||||
|     :members: |  | ||||||
|     :show-inheritance: |  | ||||||
|  |  | ||||||
|  |  | ||||||
| sanic.handlers |  | ||||||
| -------------- |  | ||||||
|  |  | ||||||
| .. automodule:: sanic.handlers |  | ||||||
|     :members: |  | ||||||
|     :show-inheritance: |  | ||||||
|  |  | ||||||
|  |  | ||||||
| sanic.request |  | ||||||
| ------------- |  | ||||||
|  |  | ||||||
| .. automodule:: sanic.request |  | ||||||
|     :members: |  | ||||||
|     :show-inheritance: |  | ||||||
|  |  | ||||||
| sanic.response |  | ||||||
| -------------- |  | ||||||
|  |  | ||||||
| .. automodule:: sanic.response |  | ||||||
|     :members: |  | ||||||
|     :show-inheritance: |  | ||||||
|  |  | ||||||
|  |  | ||||||
| sanic.views |  | ||||||
| ----------- |  | ||||||
|  |  | ||||||
| .. automodule:: sanic.views |  | ||||||
|     :members: |  | ||||||
|     :show-inheritance: |  | ||||||
|  |  | ||||||
| sanic.websocket |  | ||||||
| --------------- |  | ||||||
|  |  | ||||||
| .. automodule:: sanic.websocket |  | ||||||
|     :members: |  | ||||||
|     :show-inheritance: |  | ||||||
| @@ -1,16 +0,0 @@ | |||||||
| Exceptions |  | ||||||
| ========== |  | ||||||
|  |  | ||||||
| sanic.errorpages |  | ||||||
| ---------------- |  | ||||||
|  |  | ||||||
| .. automodule:: sanic.errorpages |  | ||||||
|     :members: |  | ||||||
|     :show-inheritance: |  | ||||||
|  |  | ||||||
| sanic.exceptions |  | ||||||
| ---------------- |  | ||||||
|  |  | ||||||
| .. automodule:: sanic.exceptions |  | ||||||
|     :members: |  | ||||||
|     :show-inheritance: |  | ||||||
| @@ -1,18 +0,0 @@ | |||||||
| Routing |  | ||||||
| ======= |  | ||||||
|  |  | ||||||
| sanic_routing models |  | ||||||
| -------------------- |  | ||||||
|  |  | ||||||
| .. autoclass:: sanic_routing.route::Route |  | ||||||
|     :members: |  | ||||||
|  |  | ||||||
| .. autoclass:: sanic_routing.group::RouteGroup |  | ||||||
|     :members: |  | ||||||
|  |  | ||||||
| sanic.router |  | ||||||
| ------------ |  | ||||||
|  |  | ||||||
| .. automodule:: sanic.router |  | ||||||
|     :members: |  | ||||||
|     :show-inheritance: |  | ||||||
| @@ -1,25 +0,0 @@ | |||||||
| Sanic Server |  | ||||||
| ============ |  | ||||||
|  |  | ||||||
| sanic.http |  | ||||||
| ---------- |  | ||||||
|  |  | ||||||
| .. automodule:: sanic.http |  | ||||||
|     :members: |  | ||||||
|     :show-inheritance: |  | ||||||
|  |  | ||||||
|  |  | ||||||
| sanic.server |  | ||||||
| ------------ |  | ||||||
|  |  | ||||||
| .. automodule:: sanic.server |  | ||||||
|     :members: |  | ||||||
|     :show-inheritance: |  | ||||||
|  |  | ||||||
|  |  | ||||||
| sanic.worker |  | ||||||
| ------------ |  | ||||||
|  |  | ||||||
| .. automodule:: sanic.worker |  | ||||||
|     :members: |  | ||||||
|     :show-inheritance: |  | ||||||
| @@ -1,16 +0,0 @@ | |||||||
| Utility |  | ||||||
| ======= |  | ||||||
|  |  | ||||||
| sanic.compat |  | ||||||
| ------------ |  | ||||||
|  |  | ||||||
| .. automodule:: sanic.compat |  | ||||||
|     :members: |  | ||||||
|     :show-inheritance: |  | ||||||
|  |  | ||||||
| sanic.log |  | ||||||
| --------- |  | ||||||
|  |  | ||||||
| .. automodule:: sanic.log |  | ||||||
|     :members: |  | ||||||
|     :show-inheritance: |  | ||||||
| @@ -1,13 +1,132 @@ | |||||||
| 📑 API Reference | 📑 API Reference | ||||||
| ================ | ================ | ||||||
|  |  | ||||||
| .. toctree:: | sanic.app | ||||||
|    :maxdepth: 2 | --------- | ||||||
|  |  | ||||||
|    api/app | .. automodule:: sanic.app | ||||||
|    api/blueprints |     :members: | ||||||
|    api/core |     :show-inheritance: | ||||||
|    api/exceptions |     :inherited-members: | ||||||
|    api/router |  | ||||||
|    api/server | sanic.blueprints | ||||||
|    api/utility | ---------------- | ||||||
|  |  | ||||||
|  | .. automodule:: sanic.blueprints | ||||||
|  |     :members: | ||||||
|  |     :show-inheritance: | ||||||
|  |     :inherited-members: | ||||||
|  |  | ||||||
|  | sanic.blueprint_group | ||||||
|  | --------------------- | ||||||
|  |  | ||||||
|  | .. automodule:: sanic.blueprint_group | ||||||
|  |     :members: | ||||||
|  |     :special-members: | ||||||
|  |  | ||||||
|  |  | ||||||
|  | sanic.compat | ||||||
|  | ------------ | ||||||
|  |  | ||||||
|  | .. automodule:: sanic.compat | ||||||
|  |     :members: | ||||||
|  |     :show-inheritance: | ||||||
|  |  | ||||||
|  | sanic.config | ||||||
|  | ------------ | ||||||
|  |  | ||||||
|  | .. automodule:: sanic.config | ||||||
|  |     :members: | ||||||
|  |     :show-inheritance: | ||||||
|  |  | ||||||
|  | sanic.cookies | ||||||
|  | ------------- | ||||||
|  |  | ||||||
|  | .. automodule:: sanic.cookies | ||||||
|  |     :members: | ||||||
|  |     :show-inheritance: | ||||||
|  |  | ||||||
|  | sanic.errorpages | ||||||
|  | ---------------- | ||||||
|  |  | ||||||
|  | .. automodule:: sanic.errorpages | ||||||
|  |     :members: | ||||||
|  |     :show-inheritance: | ||||||
|  |  | ||||||
|  | sanic.exceptions | ||||||
|  | ---------------- | ||||||
|  |  | ||||||
|  | .. automodule:: sanic.exceptions | ||||||
|  |     :members: | ||||||
|  |     :show-inheritance: | ||||||
|  |  | ||||||
|  | sanic.handlers | ||||||
|  | -------------- | ||||||
|  |  | ||||||
|  | .. automodule:: sanic.handlers | ||||||
|  |     :members: | ||||||
|  |     :show-inheritance: | ||||||
|  |  | ||||||
|  | sanic.http | ||||||
|  | ---------- | ||||||
|  |  | ||||||
|  | .. automodule:: sanic.http | ||||||
|  |     :members: | ||||||
|  |     :show-inheritance: | ||||||
|  |  | ||||||
|  | sanic.log | ||||||
|  | --------- | ||||||
|  |  | ||||||
|  | .. automodule:: sanic.log | ||||||
|  |     :members: | ||||||
|  |     :show-inheritance: | ||||||
|  |  | ||||||
|  | sanic.request | ||||||
|  | ------------- | ||||||
|  |  | ||||||
|  | .. automodule:: sanic.request | ||||||
|  |     :members: | ||||||
|  |     :show-inheritance: | ||||||
|  |  | ||||||
|  | sanic.response | ||||||
|  | -------------- | ||||||
|  |  | ||||||
|  | .. automodule:: sanic.response | ||||||
|  |     :members: | ||||||
|  |     :show-inheritance: | ||||||
|  |  | ||||||
|  | sanic.router | ||||||
|  | ------------ | ||||||
|  |  | ||||||
|  | .. automodule:: sanic.router | ||||||
|  |     :members: | ||||||
|  |     :show-inheritance: | ||||||
|  |  | ||||||
|  | sanic.server | ||||||
|  | ------------ | ||||||
|  |  | ||||||
|  | .. automodule:: sanic.server | ||||||
|  |     :members: | ||||||
|  |     :show-inheritance: | ||||||
|  |  | ||||||
|  |  | ||||||
|  | sanic.views | ||||||
|  | ----------- | ||||||
|  |  | ||||||
|  | .. automodule:: sanic.views | ||||||
|  |     :members: | ||||||
|  |     :show-inheritance: | ||||||
|  |  | ||||||
|  | sanic.websocket | ||||||
|  | --------------- | ||||||
|  |  | ||||||
|  | .. automodule:: sanic.websocket | ||||||
|  |     :members: | ||||||
|  |     :show-inheritance: | ||||||
|  |  | ||||||
|  | sanic.worker | ||||||
|  | ------------ | ||||||
|  |  | ||||||
|  | .. automodule:: sanic.worker | ||||||
|  |     :members: | ||||||
|  |     :show-inheritance: | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| ♥️ Contributing | ♥️ Contributing | ||||||
| ============== | =============== | ||||||
|  |  | ||||||
| .. include:: ../../CONTRIBUTING.rst | .. include:: ../../CONTRIBUTING.rst | ||||||
|   | |||||||
| @@ -1,6 +0,0 @@ | |||||||
| FROM catthehacker/ubuntu:act-latest |  | ||||||
| SHELL [ "/bin/bash", "-c" ] |  | ||||||
| ENTRYPOINT [] |  | ||||||
| RUN apt-get update |  | ||||||
| RUN apt-get install gcc -y |  | ||||||
| RUN apt-get install -y --no-install-recommends g++ |  | ||||||
| @@ -1,7 +1,6 @@ | |||||||
| from sanic.__version__ import __version__ | from sanic.__version__ import __version__ | ||||||
| from sanic.app import Sanic | from sanic.app import Sanic | ||||||
| from sanic.blueprints import Blueprint | from sanic.blueprints import Blueprint | ||||||
| from sanic.constants import HTTPMethod |  | ||||||
| from sanic.request import Request | from sanic.request import Request | ||||||
| from sanic.response import HTTPResponse, html, json, text | from sanic.response import HTTPResponse, html, json, text | ||||||
|  |  | ||||||
| @@ -10,7 +9,6 @@ __all__ = ( | |||||||
|     "__version__", |     "__version__", | ||||||
|     "Sanic", |     "Sanic", | ||||||
|     "Blueprint", |     "Blueprint", | ||||||
|     "HTTPMethod", |  | ||||||
|     "HTTPResponse", |     "HTTPResponse", | ||||||
|     "Request", |     "Request", | ||||||
|     "html", |     "html", | ||||||
|   | |||||||
| @@ -1,25 +1,21 @@ | |||||||
| import os | import os | ||||||
| import sys | import sys | ||||||
|  |  | ||||||
| from argparse import ArgumentParser, RawTextHelpFormatter | from argparse import ArgumentParser, RawDescriptionHelpFormatter | ||||||
| from importlib import import_module | from importlib import import_module | ||||||
| from pathlib import Path |  | ||||||
| from typing import Any, Dict, Optional | from typing import Any, Dict, Optional | ||||||
|  |  | ||||||
| from sanic_routing import __version__ as __routing_version__  # type: ignore |  | ||||||
|  |  | ||||||
| from sanic import __version__ | from sanic import __version__ | ||||||
| from sanic.app import Sanic | from sanic.app import Sanic | ||||||
| from sanic.config import BASE_LOGO | from sanic.config import BASE_LOGO | ||||||
| from sanic.log import error_logger | from sanic.log import logger | ||||||
| from sanic.simple import create_simple_server |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class SanicArgumentParser(ArgumentParser): | class SanicArgumentParser(ArgumentParser): | ||||||
|     def add_bool_arguments(self, *args, **kwargs): |     def add_bool_arguments(self, *args, **kwargs): | ||||||
|         group = self.add_mutually_exclusive_group() |         group = self.add_mutually_exclusive_group() | ||||||
|         group.add_argument(*args, action="store_true", **kwargs) |         group.add_argument(*args, action="store_true", **kwargs) | ||||||
|         kwargs["help"] = f"no {kwargs['help']}\n " |         kwargs["help"] = "no " + kwargs["help"] | ||||||
|         group.add_argument( |         group.add_argument( | ||||||
|             "--no-" + args[0][2:], *args[1:], action="store_false", **kwargs |             "--no-" + args[0][2:], *args[1:], action="store_false", **kwargs | ||||||
|         ) |         ) | ||||||
| @@ -29,30 +25,7 @@ def main(): | |||||||
|     parser = SanicArgumentParser( |     parser = SanicArgumentParser( | ||||||
|         prog="sanic", |         prog="sanic", | ||||||
|         description=BASE_LOGO, |         description=BASE_LOGO, | ||||||
|         formatter_class=lambda prog: RawTextHelpFormatter( |         formatter_class=RawDescriptionHelpFormatter, | ||||||
|             prog, max_help_position=33 |  | ||||||
|         ), |  | ||||||
|     ) |  | ||||||
|     parser.add_argument( |  | ||||||
|         "-v", |  | ||||||
|         "--version", |  | ||||||
|         action="version", |  | ||||||
|         version=f"Sanic {__version__}; Routing {__routing_version__}", |  | ||||||
|     ) |  | ||||||
|     parser.add_argument( |  | ||||||
|         "--factory", |  | ||||||
|         action="store_true", |  | ||||||
|         help=( |  | ||||||
|             "Treat app as an application factory, " |  | ||||||
|             "i.e. a () -> <Sanic app> callable" |  | ||||||
|         ), |  | ||||||
|     ) |  | ||||||
|     parser.add_argument( |  | ||||||
|         "-s", |  | ||||||
|         "--simple", |  | ||||||
|         dest="simple", |  | ||||||
|         action="store_true", |  | ||||||
|         help="Run Sanic as a Simple Server (module arg should be a path)\n ", |  | ||||||
|     ) |     ) | ||||||
|     parser.add_argument( |     parser.add_argument( | ||||||
|         "-H", |         "-H", | ||||||
| @@ -60,7 +33,7 @@ def main(): | |||||||
|         dest="host", |         dest="host", | ||||||
|         type=str, |         type=str, | ||||||
|         default="127.0.0.1", |         default="127.0.0.1", | ||||||
|         help="Host address [default 127.0.0.1]", |         help="host address [default 127.0.0.1]", | ||||||
|     ) |     ) | ||||||
|     parser.add_argument( |     parser.add_argument( | ||||||
|         "-p", |         "-p", | ||||||
| @@ -68,7 +41,7 @@ def main(): | |||||||
|         dest="port", |         dest="port", | ||||||
|         type=int, |         type=int, | ||||||
|         default=8000, |         default=8000, | ||||||
|         help="Port to serve on [default 8000]", |         help="port to serve on [default 8000]", | ||||||
|     ) |     ) | ||||||
|     parser.add_argument( |     parser.add_argument( | ||||||
|         "-u", |         "-u", | ||||||
| @@ -76,16 +49,13 @@ def main(): | |||||||
|         dest="unix", |         dest="unix", | ||||||
|         type=str, |         type=str, | ||||||
|         default="", |         default="", | ||||||
|         help="location of unix socket\n ", |         help="location of unix socket", | ||||||
|     ) |     ) | ||||||
|     parser.add_argument( |     parser.add_argument( | ||||||
|         "--cert", dest="cert", type=str, help="Location of certificate for SSL" |         "--cert", dest="cert", type=str, help="location of certificate for SSL" | ||||||
|     ) |     ) | ||||||
|     parser.add_argument( |     parser.add_argument( | ||||||
|         "--key", dest="key", type=str, help="location of keyfile for SSL\n " |         "--key", dest="key", type=str, help="location of keyfile for SSL." | ||||||
|     ) |  | ||||||
|     parser.add_bool_arguments( |  | ||||||
|         "--access-logs", dest="access_log", help="display access logs" |  | ||||||
|     ) |     ) | ||||||
|     parser.add_argument( |     parser.add_argument( | ||||||
|         "-w", |         "-w", | ||||||
| @@ -93,31 +63,20 @@ def main(): | |||||||
|         dest="workers", |         dest="workers", | ||||||
|         type=int, |         type=int, | ||||||
|         default=1, |         default=1, | ||||||
|         help="number of worker processes [default 1]\n ", |         help="number of worker processes [default 1]", | ||||||
|     ) |     ) | ||||||
|     parser.add_argument("-d", "--debug", dest="debug", action="store_true") |     parser.add_argument("--debug", dest="debug", action="store_true") | ||||||
|     parser.add_argument( |     parser.add_bool_arguments( | ||||||
|         "-r", |         "--access-logs", dest="access_log", help="display access logs" | ||||||
|         "--reload", |  | ||||||
|         "--auto-reload", |  | ||||||
|         dest="auto_reload", |  | ||||||
|         action="store_true", |  | ||||||
|         help="Watch source directory for file changes and reload on changes", |  | ||||||
|     ) |     ) | ||||||
|     parser.add_argument( |     parser.add_argument( | ||||||
|         "-R", |         "-v", | ||||||
|         "--reload-dir", |         "--version", | ||||||
|         dest="path", |         action="version", | ||||||
|         action="append", |         version=f"Sanic {__version__}", | ||||||
|         help="Extra directories to watch and reload on changes\n ", |  | ||||||
|     ) |     ) | ||||||
|     parser.add_argument( |     parser.add_argument( | ||||||
|         "module", |         "module", help="path to your Sanic app. Example: path.to.server:app" | ||||||
|         help=( |  | ||||||
|             "Path to your Sanic app. Example: path.to.server:app\n" |  | ||||||
|             "If running a Simple Server, path to directory to serve. " |  | ||||||
|             "Example: ./\n" |  | ||||||
|         ), |  | ||||||
|     ) |     ) | ||||||
|     args = parser.parse_args() |     args = parser.parse_args() | ||||||
|  |  | ||||||
| @@ -126,71 +85,47 @@ def main(): | |||||||
|         if module_path not in sys.path: |         if module_path not in sys.path: | ||||||
|             sys.path.append(module_path) |             sys.path.append(module_path) | ||||||
|  |  | ||||||
|         if args.simple: |         if ":" in args.module: | ||||||
|             path = Path(args.module) |             module_name, app_name = args.module.rsplit(":", 1) | ||||||
|             app = create_simple_server(path) |  | ||||||
|         else: |         else: | ||||||
|             delimiter = ":" if ":" in args.module else "." |             module_parts = args.module.split(".") | ||||||
|             module_name, app_name = args.module.rsplit(delimiter, 1) |             module_name = ".".join(module_parts[:-1]) | ||||||
|  |             app_name = module_parts[-1] | ||||||
|  |  | ||||||
|             if app_name.endswith("()"): |         module = import_module(module_name) | ||||||
|                 args.factory = True |         app = getattr(module, app_name, None) | ||||||
|                 app_name = app_name[:-2] |         app_name = type(app).__name__ | ||||||
|  |  | ||||||
|             module = import_module(module_name) |         if not isinstance(app, Sanic): | ||||||
|             app = getattr(module, app_name, None) |             raise ValueError( | ||||||
|             if args.factory: |                 f"Module is not a Sanic app, it is a {app_name}.  " | ||||||
|                 app = app() |                 f"Perhaps you meant {args.module}.app?" | ||||||
|  |             ) | ||||||
|             app_type_name = type(app).__name__ |  | ||||||
|  |  | ||||||
|             if not isinstance(app, Sanic): |  | ||||||
|                 raise ValueError( |  | ||||||
|                     f"Module is not a Sanic app, it is a {app_type_name}.  " |  | ||||||
|                     f"Perhaps you meant {args.module}.app?" |  | ||||||
|                 ) |  | ||||||
|         if args.cert is not None or args.key is not None: |         if args.cert is not None or args.key is not None: | ||||||
|             ssl: Optional[Dict[str, Any]] = { |             ssl = { | ||||||
|                 "cert": args.cert, |                 "cert": args.cert, | ||||||
|                 "key": args.key, |                 "key": args.key, | ||||||
|             } |             }  # type: Optional[Dict[str, Any]] | ||||||
|         else: |         else: | ||||||
|             ssl = None |             ssl = None | ||||||
|  |  | ||||||
|         kwargs = { |         app.run( | ||||||
|             "host": args.host, |             host=args.host, | ||||||
|             "port": args.port, |             port=args.port, | ||||||
|             "unix": args.unix, |             unix=args.unix, | ||||||
|             "workers": args.workers, |             workers=args.workers, | ||||||
|             "debug": args.debug, |             debug=args.debug, | ||||||
|             "access_log": args.access_log, |             access_log=args.access_log, | ||||||
|             "ssl": ssl, |             ssl=ssl, | ||||||
|         } |         ) | ||||||
|         if args.auto_reload: |  | ||||||
|             kwargs["auto_reload"] = True |  | ||||||
|  |  | ||||||
|         if args.path: |  | ||||||
|             if args.auto_reload or args.debug: |  | ||||||
|                 kwargs["reload_dir"] = args.path |  | ||||||
|             else: |  | ||||||
|                 error_logger.warning( |  | ||||||
|                     "Ignoring '--reload-dir' since auto reloading was not " |  | ||||||
|                     "enabled. If you would like to watch directories for " |  | ||||||
|                     "changes, consider using --debug or --auto-reload." |  | ||||||
|                 ) |  | ||||||
|  |  | ||||||
|         app.run(**kwargs) |  | ||||||
|     except ImportError as e: |     except ImportError as e: | ||||||
|         if module_name.startswith(e.name): |         logger.error( | ||||||
|             error_logger.error( |             f"No module named {e.name} found.\n" | ||||||
|                 f"No module named {e.name} found.\n" |             f"  Example File: project/sanic_server.py -> app\n" | ||||||
|                 "  Example File: project/sanic_server.py -> app\n" |             f"  Example Module: project.sanic_server.app" | ||||||
|                 "  Example Module: project.sanic_server.app" |         ) | ||||||
|             ) |  | ||||||
|         else: |  | ||||||
|             raise e |  | ||||||
|     except ValueError: |     except ValueError: | ||||||
|         error_logger.exception("Failed to run app") |         logger.exception("Failed to run app") | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == "__main__": | if __name__ == "__main__": | ||||||
|   | |||||||
| @@ -1 +1 @@ | |||||||
| __version__ = "21.6.2" | __version__ = "21.3.2" | ||||||
|   | |||||||
							
								
								
									
										126
									
								
								sanic/app.py
									
									
									
									
									
								
							
							
						
						
									
										126
									
								
								sanic/app.py
									
									
									
									
									
								
							| @@ -14,7 +14,6 @@ from asyncio.futures import Future | |||||||
| from collections import defaultdict, deque | from collections import defaultdict, deque | ||||||
| from functools import partial | from functools import partial | ||||||
| from inspect import isawaitable | from inspect import isawaitable | ||||||
| from pathlib import Path |  | ||||||
| from socket import socket | from socket import socket | ||||||
| from ssl import Purpose, SSLContext, create_default_context | from ssl import Purpose, SSLContext, create_default_context | ||||||
| from traceback import format_exc | from traceback import format_exc | ||||||
| @@ -44,7 +43,7 @@ from sanic.asgi import ASGIApp | |||||||
| from sanic.base import BaseSanic | from sanic.base import BaseSanic | ||||||
| from sanic.blueprint_group import BlueprintGroup | from sanic.blueprint_group import BlueprintGroup | ||||||
| from sanic.blueprints import Blueprint | from sanic.blueprints import Blueprint | ||||||
| from sanic.config import BASE_LOGO, SANIC_PREFIX, Config | from sanic.config import BASE_LOGO, Config | ||||||
| from sanic.exceptions import ( | from sanic.exceptions import ( | ||||||
|     InvalidUsage, |     InvalidUsage, | ||||||
|     SanicException, |     SanicException, | ||||||
| @@ -79,7 +78,6 @@ class Sanic(BaseSanic): | |||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     __fake_slots__ = ( |     __fake_slots__ = ( | ||||||
|         "_asgi_app", |  | ||||||
|         "_app_registry", |         "_app_registry", | ||||||
|         "_asgi_client", |         "_asgi_client", | ||||||
|         "_blueprint_order", |         "_blueprint_order", | ||||||
| @@ -91,7 +89,6 @@ class Sanic(BaseSanic): | |||||||
|         "_future_signals", |         "_future_signals", | ||||||
|         "_test_client", |         "_test_client", | ||||||
|         "_test_manager", |         "_test_manager", | ||||||
|         "auto_reload", |  | ||||||
|         "asgi", |         "asgi", | ||||||
|         "blueprints", |         "blueprints", | ||||||
|         "config", |         "config", | ||||||
| @@ -106,7 +103,6 @@ class Sanic(BaseSanic): | |||||||
|         "name", |         "name", | ||||||
|         "named_request_middleware", |         "named_request_middleware", | ||||||
|         "named_response_middleware", |         "named_response_middleware", | ||||||
|         "reload_dirs", |  | ||||||
|         "request_class", |         "request_class", | ||||||
|         "request_middleware", |         "request_middleware", | ||||||
|         "response_middleware", |         "response_middleware", | ||||||
| @@ -125,13 +121,10 @@ class Sanic(BaseSanic): | |||||||
|     def __init__( |     def __init__( | ||||||
|         self, |         self, | ||||||
|         name: str = None, |         name: str = None, | ||||||
|         config: Optional[Config] = None, |  | ||||||
|         ctx: Optional[Any] = None, |  | ||||||
|         router: Optional[Router] = None, |         router: Optional[Router] = None, | ||||||
|         signal_router: Optional[SignalRouter] = None, |         signal_router: Optional[SignalRouter] = None, | ||||||
|         error_handler: Optional[ErrorHandler] = None, |         error_handler: Optional[ErrorHandler] = None, | ||||||
|         load_env: Union[bool, str] = True, |         load_env: bool = True, | ||||||
|         env_prefix: Optional[str] = SANIC_PREFIX, |  | ||||||
|         request_class: Optional[Type[Request]] = None, |         request_class: Optional[Type[Request]] = None, | ||||||
|         strict_slashes: bool = False, |         strict_slashes: bool = False, | ||||||
|         log_config: Optional[Dict[str, Any]] = None, |         log_config: Optional[Dict[str, Any]] = None, | ||||||
| @@ -139,38 +132,34 @@ class Sanic(BaseSanic): | |||||||
|         register: Optional[bool] = None, |         register: Optional[bool] = None, | ||||||
|         dumps: Optional[Callable[..., str]] = None, |         dumps: Optional[Callable[..., str]] = None, | ||||||
|     ) -> None: |     ) -> None: | ||||||
|         super().__init__(name=name) |         super().__init__() | ||||||
|  |  | ||||||
|  |         if name is None: | ||||||
|  |             raise SanicException( | ||||||
|  |                 "Sanic instance cannot be unnamed. " | ||||||
|  |                 "Please use Sanic(name='your_application_name') instead.", | ||||||
|  |             ) | ||||||
|         # logging |         # logging | ||||||
|         if configure_logging: |         if configure_logging: | ||||||
|             logging.config.dictConfig(log_config or LOGGING_CONFIG_DEFAULTS) |             logging.config.dictConfig(log_config or LOGGING_CONFIG_DEFAULTS) | ||||||
|  |  | ||||||
|         if config and (load_env is not True or env_prefix != SANIC_PREFIX): |  | ||||||
|             raise SanicException( |  | ||||||
|                 "When instantiating Sanic with config, you cannot also pass " |  | ||||||
|                 "load_env or env_prefix" |  | ||||||
|             ) |  | ||||||
|  |  | ||||||
|         self._asgi_client = None |         self._asgi_client = None | ||||||
|         self._blueprint_order: List[Blueprint] = [] |         self._blueprint_order: List[Blueprint] = [] | ||||||
|         self._test_client = None |         self._test_client = None | ||||||
|         self._test_manager = None |         self._test_manager = None | ||||||
|         self.asgi = False |         self.asgi = False | ||||||
|         self.auto_reload = False |  | ||||||
|         self.blueprints: Dict[str, Blueprint] = {} |         self.blueprints: Dict[str, Blueprint] = {} | ||||||
|         self.config = config or Config( |         self.config = Config(load_env=load_env) | ||||||
|             load_env=load_env, env_prefix=env_prefix |  | ||||||
|         ) |  | ||||||
|         self.configure_logging = configure_logging |         self.configure_logging = configure_logging | ||||||
|         self.ctx = ctx or SimpleNamespace() |         self.ctx = SimpleNamespace() | ||||||
|         self.debug = None |         self.debug = None | ||||||
|         self.error_handler = error_handler or ErrorHandler() |         self.error_handler = error_handler or ErrorHandler() | ||||||
|         self.is_running = False |         self.is_running = False | ||||||
|         self.is_stopping = False |         self.is_stopping = False | ||||||
|         self.listeners: Dict[str, List[ListenerType]] = defaultdict(list) |         self.listeners: Dict[str, List[ListenerType]] = defaultdict(list) | ||||||
|  |         self.name = name | ||||||
|         self.named_request_middleware: Dict[str, Deque[MiddlewareType]] = {} |         self.named_request_middleware: Dict[str, Deque[MiddlewareType]] = {} | ||||||
|         self.named_response_middleware: Dict[str, Deque[MiddlewareType]] = {} |         self.named_response_middleware: Dict[str, Deque[MiddlewareType]] = {} | ||||||
|         self.reload_dirs: Set[Path] = set() |  | ||||||
|         self.request_class = request_class |         self.request_class = request_class | ||||||
|         self.request_middleware: Deque[MiddlewareType] = deque() |         self.request_middleware: Deque[MiddlewareType] = deque() | ||||||
|         self.response_middleware: Deque[MiddlewareType] = deque() |         self.response_middleware: Deque[MiddlewareType] = deque() | ||||||
| @@ -186,6 +175,7 @@ class Sanic(BaseSanic): | |||||||
|  |  | ||||||
|         if register is not None: |         if register is not None: | ||||||
|             self.config.REGISTER = register |             self.config.REGISTER = register | ||||||
|  |  | ||||||
|         if self.config.REGISTER: |         if self.config.REGISTER: | ||||||
|             self.__class__.register_app(self) |             self.__class__.register_app(self) | ||||||
|  |  | ||||||
| @@ -384,19 +374,11 @@ class Sanic(BaseSanic): | |||||||
|             condition=condition, |             condition=condition, | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     async def event( |     def event(self, event: str, timeout: Optional[Union[int, float]] = None): | ||||||
|         self, event: str, timeout: Optional[Union[int, float]] = None |  | ||||||
|     ): |  | ||||||
|         signal = self.signal_router.name_index.get(event) |         signal = self.signal_router.name_index.get(event) | ||||||
|         if not signal: |         if not signal: | ||||||
|             if self.config.EVENT_AUTOREGISTER: |             raise NotFound("Could not find signal %s" % event) | ||||||
|                 self.signal_router.reset() |         return wait_for(signal.ctx.event.wait(), timeout=timeout) | ||||||
|                 self.add_signal(None, event) |  | ||||||
|                 signal = self.signal_router.name_index[event] |  | ||||||
|                 self.signal_router.finalize() |  | ||||||
|             else: |  | ||||||
|                 raise NotFound("Could not find signal %s" % event) |  | ||||||
|         return await wait_for(signal.ctx.event.wait(), timeout=timeout) |  | ||||||
|  |  | ||||||
|     def enable_websocket(self, enable=True): |     def enable_websocket(self, enable=True): | ||||||
|         """Enable or disable the support for websocket. |         """Enable or disable the support for websocket. | ||||||
| @@ -420,33 +402,7 @@ class Sanic(BaseSanic): | |||||||
|         """ |         """ | ||||||
|         if isinstance(blueprint, (list, tuple, BlueprintGroup)): |         if isinstance(blueprint, (list, tuple, BlueprintGroup)): | ||||||
|             for item in blueprint: |             for item in blueprint: | ||||||
|                 params = {**options} |                 self.blueprint(item, **options) | ||||||
|                 if isinstance(blueprint, BlueprintGroup): |  | ||||||
|                     if blueprint.url_prefix: |  | ||||||
|                         merge_from = [ |  | ||||||
|                             options.get("url_prefix", ""), |  | ||||||
|                             blueprint.url_prefix, |  | ||||||
|                         ] |  | ||||||
|                         if not isinstance(item, BlueprintGroup): |  | ||||||
|                             merge_from.append(item.url_prefix or "") |  | ||||||
|                         merged_prefix = "/".join( |  | ||||||
|                             u.strip("/") for u in merge_from |  | ||||||
|                         ).rstrip("/") |  | ||||||
|                         params["url_prefix"] = f"/{merged_prefix}" |  | ||||||
|  |  | ||||||
|                     for _attr in ["version", "strict_slashes"]: |  | ||||||
|                         if getattr(item, _attr) is None: |  | ||||||
|                             params[_attr] = getattr( |  | ||||||
|                                 blueprint, _attr |  | ||||||
|                             ) or options.get(_attr) |  | ||||||
|                     if item.version_prefix == "/v": |  | ||||||
|                         if blueprint.version_prefix == "/v": |  | ||||||
|                             params["version_prefix"] = options.get( |  | ||||||
|                                 "version_prefix" |  | ||||||
|                             ) |  | ||||||
|                         else: |  | ||||||
|                             params["version_prefix"] = blueprint.version_prefix |  | ||||||
|                 self.blueprint(item, **params) |  | ||||||
|             return |             return | ||||||
|         if blueprint.name in self.blueprints: |         if blueprint.name in self.blueprints: | ||||||
|             assert self.blueprints[blueprint.name] is blueprint, ( |             assert self.blueprints[blueprint.name] is blueprint, ( | ||||||
| @@ -611,12 +567,7 @@ class Sanic(BaseSanic): | |||||||
|             # determine if the parameter supplied by the caller |             # determine if the parameter supplied by the caller | ||||||
|             # passes the test in the URL |             # passes the test in the URL | ||||||
|             if param_info.pattern: |             if param_info.pattern: | ||||||
|                 pattern = ( |                 passes_pattern = param_info.pattern.match(supplied_param) | ||||||
|                     param_info.pattern[1] |  | ||||||
|                     if isinstance(param_info.pattern, tuple) |  | ||||||
|                     else param_info.pattern |  | ||||||
|                 ) |  | ||||||
|                 passes_pattern = pattern.match(supplied_param) |  | ||||||
|                 if not passes_pattern: |                 if not passes_pattern: | ||||||
|                     if param_info.cast != str: |                     if param_info.cast != str: | ||||||
|                         msg = ( |                         msg = ( | ||||||
| @@ -624,13 +575,13 @@ class Sanic(BaseSanic): | |||||||
|                             f"for parameter `{param_info.name}` does " |                             f"for parameter `{param_info.name}` does " | ||||||
|                             "not match pattern for type " |                             "not match pattern for type " | ||||||
|                             f"`{param_info.cast.__name__}`: " |                             f"`{param_info.cast.__name__}`: " | ||||||
|                             f"{pattern.pattern}" |                             f"{param_info.pattern.pattern}" | ||||||
|                         ) |                         ) | ||||||
|                     else: |                     else: | ||||||
|                         msg = ( |                         msg = ( | ||||||
|                             f'Value "{supplied_param}" for parameter ' |                             f'Value "{supplied_param}" for parameter ' | ||||||
|                             f"`{param_info.name}` does not satisfy " |                             f"`{param_info.name}` does not satisfy " | ||||||
|                             f"pattern {pattern.pattern}" |                             f"pattern {param_info.pattern.pattern}" | ||||||
|                         ) |                         ) | ||||||
|                     raise URLBuildError(msg) |                     raise URLBuildError(msg) | ||||||
|  |  | ||||||
| @@ -713,6 +664,11 @@ class Sanic(BaseSanic): | |||||||
|         exception handling must be done here |         exception handling must be done here | ||||||
|  |  | ||||||
|         :param request: HTTP Request object |         :param request: HTTP Request object | ||||||
|  |         :param write_callback: Synchronous response function to be | ||||||
|  |             called with the response as the only argument | ||||||
|  |         :param stream_callback: Coroutine that handles streaming a | ||||||
|  |             StreamingHTTPResponse if produced by the handler. | ||||||
|  |  | ||||||
|         :return: Nothing |         :return: Nothing | ||||||
|         """ |         """ | ||||||
|         # Define `response` var here to remove warnings about |         # Define `response` var here to remove warnings about | ||||||
| @@ -721,9 +677,7 @@ class Sanic(BaseSanic): | |||||||
|         try: |         try: | ||||||
|             # Fetch handler from router |             # Fetch handler from router | ||||||
|             route, handler, kwargs = self.router.get( |             route, handler, kwargs = self.router.get( | ||||||
|                 request.path, |                 request.path, request.method, request.headers.get("host") | ||||||
|                 request.method, |  | ||||||
|                 request.headers.getone("host", None), |  | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|             request._match_info = kwargs |             request._match_info = kwargs | ||||||
| @@ -771,14 +725,17 @@ class Sanic(BaseSanic): | |||||||
|  |  | ||||||
|             if response: |             if response: | ||||||
|                 response = await request.respond(response) |                 response = await request.respond(response) | ||||||
|             elif not hasattr(handler, "is_websocket"): |             else: | ||||||
|                 response = request.stream.response  # type: ignore |                 response = request.stream.response  # type: ignore | ||||||
|  |  | ||||||
|             # Make sure that response is finished / run StreamingHTTP callback |             # Make sure that response is finished / run StreamingHTTP callback | ||||||
|  |  | ||||||
|             if isinstance(response, BaseHTTPResponse): |             if isinstance(response, BaseHTTPResponse): | ||||||
|                 await response.send(end_stream=True) |                 await response.send(end_stream=True) | ||||||
|             else: |             else: | ||||||
|                 if not hasattr(handler, "is_websocket"): |                 try: | ||||||
|  |                     # Fastest method for checking if the property exists | ||||||
|  |                     handler.is_websocket  # type: ignore | ||||||
|  |                 except AttributeError: | ||||||
|                     raise ServerError( |                     raise ServerError( | ||||||
|                         f"Invalid response type {response!r} " |                         f"Invalid response type {response!r} " | ||||||
|                         "(need HTTPResponse)" |                         "(need HTTPResponse)" | ||||||
| @@ -805,7 +762,6 @@ class Sanic(BaseSanic): | |||||||
|  |  | ||||||
|         if self.asgi: |         if self.asgi: | ||||||
|             ws = request.transport.get_websocket_connection() |             ws = request.transport.get_websocket_connection() | ||||||
|             await ws.accept(subprotocols) |  | ||||||
|         else: |         else: | ||||||
|             protocol = request.transport.get_protocol() |             protocol = request.transport.get_protocol() | ||||||
|             protocol.app = self |             protocol.app = self | ||||||
| @@ -878,7 +834,6 @@ class Sanic(BaseSanic): | |||||||
|         access_log: Optional[bool] = None, |         access_log: Optional[bool] = None, | ||||||
|         unix: Optional[str] = None, |         unix: Optional[str] = None, | ||||||
|         loop: None = None, |         loop: None = None, | ||||||
|         reload_dir: Optional[Union[List[str], str]] = None, |  | ||||||
|     ) -> None: |     ) -> None: | ||||||
|         """ |         """ | ||||||
|         Run the HTTP Server and listen until keyboard interrupt or term |         Run the HTTP Server and listen until keyboard interrupt or term | ||||||
| @@ -913,18 +868,6 @@ class Sanic(BaseSanic): | |||||||
|         :type unix: str |         :type unix: str | ||||||
|         :return: Nothing |         :return: Nothing | ||||||
|         """ |         """ | ||||||
|         if reload_dir: |  | ||||||
|             if isinstance(reload_dir, str): |  | ||||||
|                 reload_dir = [reload_dir] |  | ||||||
|  |  | ||||||
|             for directory in reload_dir: |  | ||||||
|                 direc = Path(directory) |  | ||||||
|                 if not direc.is_dir(): |  | ||||||
|                     logger.warning( |  | ||||||
|                         f"Directory {directory} could not be located" |  | ||||||
|                     ) |  | ||||||
|                 self.reload_dirs.add(Path(directory)) |  | ||||||
|  |  | ||||||
|         if loop is not None: |         if loop is not None: | ||||||
|             raise TypeError( |             raise TypeError( | ||||||
|                 "loop is not a valid argument. To use an existing loop, " |                 "loop is not a valid argument. To use an existing loop, " | ||||||
| @@ -934,9 +877,8 @@ class Sanic(BaseSanic): | |||||||
|             ) |             ) | ||||||
|  |  | ||||||
|         if auto_reload or auto_reload is None and debug: |         if auto_reload or auto_reload is None and debug: | ||||||
|             self.auto_reload = True |  | ||||||
|             if os.environ.get("SANIC_SERVER_RUNNING") != "true": |             if os.environ.get("SANIC_SERVER_RUNNING") != "true": | ||||||
|                 return reloader_helpers.watchdog(1.0, self) |                 return reloader_helpers.watchdog(1.0) | ||||||
|  |  | ||||||
|         if sock is None: |         if sock is None: | ||||||
|             host, port = host or "127.0.0.1", port or 8000 |             host, port = host or "127.0.0.1", port or 8000 | ||||||
| @@ -1235,10 +1177,6 @@ class Sanic(BaseSanic): | |||||||
|             else: |             else: | ||||||
|                 logger.info(f"Goin' Fast @ {proto}://{host}:{port}") |                 logger.info(f"Goin' Fast @ {proto}://{host}:{port}") | ||||||
|  |  | ||||||
|         debug_mode = "enabled" if self.debug else "disabled" |  | ||||||
|         logger.debug("Sanic auto-reload: enabled") |  | ||||||
|         logger.debug(f"Sanic debug mode: {debug_mode}") |  | ||||||
|  |  | ||||||
|         return server_settings |         return server_settings | ||||||
|  |  | ||||||
|     def _build_endpoint_name(self, *parts): |     def _build_endpoint_name(self, *parts): | ||||||
|   | |||||||
| @@ -140,6 +140,7 @@ class ASGIApp: | |||||||
|                 instance.ws = instance.transport.create_websocket_connection( |                 instance.ws = instance.transport.create_websocket_connection( | ||||||
|                     send, receive |                     send, receive | ||||||
|                 ) |                 ) | ||||||
|  |                 await instance.ws.accept() | ||||||
|             else: |             else: | ||||||
|                 raise ServerError("Received unknown ASGI scope") |                 raise ServerError("Received unknown ASGI scope") | ||||||
|  |  | ||||||
| @@ -207,7 +208,4 @@ class ASGIApp: | |||||||
|         """ |         """ | ||||||
|         Handle the incoming request. |         Handle the incoming request. | ||||||
|         """ |         """ | ||||||
|         try: |         await self.sanic_app.handle_request(self.request) | ||||||
|             await self.sanic_app.handle_request(self.request) |  | ||||||
|         except Exception as e: |  | ||||||
|             await self.sanic_app.handle_exception(self.request, e) |  | ||||||
|   | |||||||
| @@ -1,9 +1,6 @@ | |||||||
| import re |  | ||||||
|  |  | ||||||
| from typing import Any, Tuple | from typing import Any, Tuple | ||||||
| from warnings import warn | from warnings import warn | ||||||
|  |  | ||||||
| from sanic.exceptions import SanicException |  | ||||||
| from sanic.mixins.exceptions import ExceptionMixin | from sanic.mixins.exceptions import ExceptionMixin | ||||||
| from sanic.mixins.listeners import ListenerMixin | from sanic.mixins.listeners import ListenerMixin | ||||||
| from sanic.mixins.middleware import MiddlewareMixin | from sanic.mixins.middleware import MiddlewareMixin | ||||||
| @@ -11,9 +8,6 @@ from sanic.mixins.routes import RouteMixin | |||||||
| from sanic.mixins.signals import SignalMixin | from sanic.mixins.signals import SignalMixin | ||||||
|  |  | ||||||
|  |  | ||||||
| VALID_NAME = re.compile(r"^[a-zA-Z][a-zA-Z0-9_\-]*$") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class BaseSanic( | class BaseSanic( | ||||||
|     RouteMixin, |     RouteMixin, | ||||||
|     MiddlewareMixin, |     MiddlewareMixin, | ||||||
| @@ -23,25 +17,7 @@ class BaseSanic( | |||||||
| ): | ): | ||||||
|     __fake_slots__: Tuple[str, ...] |     __fake_slots__: Tuple[str, ...] | ||||||
|  |  | ||||||
|     def __init__(self, name: str = None, *args, **kwargs) -> None: |     def __init__(self, *args, **kwargs) -> None: | ||||||
|         class_name = self.__class__.__name__ |  | ||||||
|  |  | ||||||
|         if name is None: |  | ||||||
|             raise SanicException( |  | ||||||
|                 f"{class_name} instance cannot be unnamed. " |  | ||||||
|                 "Please use Sanic(name='your_application_name') instead.", |  | ||||||
|             ) |  | ||||||
|  |  | ||||||
|         if not VALID_NAME.match(name): |  | ||||||
|             warn( |  | ||||||
|                 f"{class_name} instance named '{name}' uses a format that is" |  | ||||||
|                 f"deprecated. Starting in version 21.12, {class_name} objects " |  | ||||||
|                 "must be named only using alphanumeric characters, _, or -.", |  | ||||||
|                 DeprecationWarning, |  | ||||||
|             ) |  | ||||||
|  |  | ||||||
|         self.name = name |  | ||||||
|  |  | ||||||
|         for base in BaseSanic.__bases__: |         for base in BaseSanic.__bases__: | ||||||
|             base.__init__(self, *args, **kwargs)  # type: ignore |             base.__init__(self, *args, **kwargs)  # type: ignore | ||||||
|  |  | ||||||
| @@ -60,7 +36,6 @@ class BaseSanic( | |||||||
|                 f"Setting variables on {self.__class__.__name__} instances is " |                 f"Setting variables on {self.__class__.__name__} instances is " | ||||||
|                 "deprecated and will be removed in version 21.9. You should " |                 "deprecated and will be removed in version 21.9. You should " | ||||||
|                 f"change your {self.__class__.__name__} instance to use " |                 f"change your {self.__class__.__name__} instance to use " | ||||||
|                 f"instance.ctx.{name} instead.", |                 f"instance.ctx.{name} instead." | ||||||
|                 DeprecationWarning, |  | ||||||
|             ) |             ) | ||||||
|         super().__setattr__(name, value) |         super().__setattr__(name, value) | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| from __future__ import annotations |  | ||||||
|  |  | ||||||
| from collections.abc import MutableSequence | from collections.abc import MutableSequence | ||||||
| from typing import TYPE_CHECKING, List, Optional, Union | from typing import TYPE_CHECKING, List, Optional, Union | ||||||
|  |  | ||||||
|  | import sanic | ||||||
|  |  | ||||||
|  |  | ||||||
| if TYPE_CHECKING: | if TYPE_CHECKING: | ||||||
|     from sanic.blueprints import Blueprint |     from sanic.blueprints import Blueprint | ||||||
| @@ -58,20 +58,13 @@ class BlueprintGroup(MutableSequence): | |||||||
|         app.blueprint(bpg) |         app.blueprint(bpg) | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     __slots__ = ( |     __slots__ = ("_blueprints", "_url_prefix", "_version", "_strict_slashes") | ||||||
|         "_blueprints", |  | ||||||
|         "_url_prefix", |  | ||||||
|         "_version", |  | ||||||
|         "_strict_slashes", |  | ||||||
|         "_version_prefix", |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     def __init__( |     def __init__( | ||||||
|         self, |         self, | ||||||
|         url_prefix: Optional[str] = None, |         url_prefix: Optional[str] = None, | ||||||
|         version: Optional[Union[int, str, float]] = None, |         version: Optional[Union[int, str, float]] = None, | ||||||
|         strict_slashes: Optional[bool] = None, |         strict_slashes: Optional[bool] = None, | ||||||
|         version_prefix: str = "/v", |  | ||||||
|     ): |     ): | ||||||
|         """ |         """ | ||||||
|         Create a new Blueprint Group |         Create a new Blueprint Group | ||||||
| @@ -84,7 +77,6 @@ class BlueprintGroup(MutableSequence): | |||||||
|         self._blueprints: List[Blueprint] = [] |         self._blueprints: List[Blueprint] = [] | ||||||
|         self._url_prefix = url_prefix |         self._url_prefix = url_prefix | ||||||
|         self._version = version |         self._version = version | ||||||
|         self._version_prefix = version_prefix |  | ||||||
|         self._strict_slashes = strict_slashes |         self._strict_slashes = strict_slashes | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
| @@ -97,7 +89,7 @@ class BlueprintGroup(MutableSequence): | |||||||
|         return self._url_prefix |         return self._url_prefix | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def blueprints(self) -> List[Blueprint]: |     def blueprints(self) -> List["sanic.Blueprint"]: | ||||||
|         """ |         """ | ||||||
|         Retrieve a list of all the available blueprints under this group. |         Retrieve a list of all the available blueprints under this group. | ||||||
|  |  | ||||||
| @@ -124,15 +116,6 @@ class BlueprintGroup(MutableSequence): | |||||||
|         """ |         """ | ||||||
|         return self._strict_slashes |         return self._strict_slashes | ||||||
|  |  | ||||||
|     @property |  | ||||||
|     def version_prefix(self) -> str: |  | ||||||
|         """ |  | ||||||
|         Version prefix; defaults to ``/v`` |  | ||||||
|  |  | ||||||
|         :return: str |  | ||||||
|         """ |  | ||||||
|         return self._version_prefix |  | ||||||
|  |  | ||||||
|     def __iter__(self): |     def __iter__(self): | ||||||
|         """ |         """ | ||||||
|         Tun the class Blueprint Group into an Iterable item |         Tun the class Blueprint Group into an Iterable item | ||||||
| @@ -187,16 +170,34 @@ class BlueprintGroup(MutableSequence): | |||||||
|         """ |         """ | ||||||
|         return len(self._blueprints) |         return len(self._blueprints) | ||||||
|  |  | ||||||
|     def append(self, value: Blueprint) -> None: |     def _sanitize_blueprint(self, bp: "sanic.Blueprint") -> "sanic.Blueprint": | ||||||
|  |         """ | ||||||
|  |         Sanitize the Blueprint Entity to override the Version and strict slash | ||||||
|  |         behaviors as required. | ||||||
|  |  | ||||||
|  |         :param bp: Sanic Blueprint entity Object | ||||||
|  |         :return: Modified Blueprint | ||||||
|  |         """ | ||||||
|  |         if self._url_prefix: | ||||||
|  |             merged_prefix = "/".join( | ||||||
|  |                 u.strip("/") for u in [self._url_prefix, bp.url_prefix or ""] | ||||||
|  |             ).rstrip("/") | ||||||
|  |             bp.url_prefix = f"/{merged_prefix}" | ||||||
|  |         for _attr in ["version", "strict_slashes"]: | ||||||
|  |             if getattr(bp, _attr) is None: | ||||||
|  |                 setattr(bp, _attr, getattr(self, _attr)) | ||||||
|  |         return bp | ||||||
|  |  | ||||||
|  |     def append(self, value: "sanic.Blueprint") -> None: | ||||||
|         """ |         """ | ||||||
|         The Abstract class `MutableSequence` leverages this append method to |         The Abstract class `MutableSequence` leverages this append method to | ||||||
|         perform the `BlueprintGroup.append` operation. |         perform the `BlueprintGroup.append` operation. | ||||||
|         :param value: New `Blueprint` object. |         :param value: New `Blueprint` object. | ||||||
|         :return: None |         :return: None | ||||||
|         """ |         """ | ||||||
|         self._blueprints.append(value) |         self._blueprints.append(self._sanitize_blueprint(bp=value)) | ||||||
|  |  | ||||||
|     def insert(self, index: int, item: Blueprint) -> None: |     def insert(self, index: int, item: "sanic.Blueprint") -> None: | ||||||
|         """ |         """ | ||||||
|         The Abstract class `MutableSequence` leverages this insert method to |         The Abstract class `MutableSequence` leverages this insert method to | ||||||
|         perform the `BlueprintGroup.append` operation. |         perform the `BlueprintGroup.append` operation. | ||||||
| @@ -205,7 +206,7 @@ class BlueprintGroup(MutableSequence): | |||||||
|         :param item: New `Blueprint` object. |         :param item: New `Blueprint` object. | ||||||
|         :return: None |         :return: None | ||||||
|         """ |         """ | ||||||
|         self._blueprints.insert(index, item) |         self._blueprints.insert(index, self._sanitize_blueprint(item)) | ||||||
|  |  | ||||||
|     def middleware(self, *args, **kwargs): |     def middleware(self, *args, **kwargs): | ||||||
|         """ |         """ | ||||||
|   | |||||||
| @@ -62,20 +62,18 @@ class Blueprint(BaseSanic): | |||||||
|         "strict_slashes", |         "strict_slashes", | ||||||
|         "url_prefix", |         "url_prefix", | ||||||
|         "version", |         "version", | ||||||
|         "version_prefix", |  | ||||||
|         "websocket_routes", |         "websocket_routes", | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     def __init__( |     def __init__( | ||||||
|         self, |         self, | ||||||
|         name: str = None, |         name: str, | ||||||
|         url_prefix: Optional[str] = None, |         url_prefix: Optional[str] = None, | ||||||
|         host: Optional[str] = None, |         host: Optional[str] = None, | ||||||
|         version: Optional[Union[int, str, float]] = None, |         version: Optional[Union[int, str, float]] = None, | ||||||
|         strict_slashes: Optional[bool] = None, |         strict_slashes: Optional[bool] = None, | ||||||
|         version_prefix: str = "/v", |  | ||||||
|     ): |     ): | ||||||
|         super().__init__(name=name) |         super().__init__() | ||||||
|  |  | ||||||
|         self._apps: Set[Sanic] = set() |         self._apps: Set[Sanic] = set() | ||||||
|         self.ctx = SimpleNamespace() |         self.ctx = SimpleNamespace() | ||||||
| @@ -83,6 +81,7 @@ class Blueprint(BaseSanic): | |||||||
|         self.host = host |         self.host = host | ||||||
|         self.listeners: Dict[str, List[ListenerType]] = {} |         self.listeners: Dict[str, List[ListenerType]] = {} | ||||||
|         self.middlewares: List[MiddlewareType] = [] |         self.middlewares: List[MiddlewareType] = [] | ||||||
|  |         self.name = name | ||||||
|         self.routes: List[Route] = [] |         self.routes: List[Route] = [] | ||||||
|         self.statics: List[RouteHandler] = [] |         self.statics: List[RouteHandler] = [] | ||||||
|         self.strict_slashes = strict_slashes |         self.strict_slashes = strict_slashes | ||||||
| @@ -92,7 +91,6 @@ class Blueprint(BaseSanic): | |||||||
|             else url_prefix |             else url_prefix | ||||||
|         ) |         ) | ||||||
|         self.version = version |         self.version = version | ||||||
|         self.version_prefix = version_prefix |  | ||||||
|         self.websocket_routes: List[Route] = [] |         self.websocket_routes: List[Route] = [] | ||||||
|  |  | ||||||
|     def __repr__(self) -> str: |     def __repr__(self) -> str: | ||||||
| @@ -145,13 +143,7 @@ class Blueprint(BaseSanic): | |||||||
|         return super().signal(event, *args, **kwargs) |         return super().signal(event, *args, **kwargs) | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def group( |     def group(*blueprints, url_prefix="", version=None, strict_slashes=None): | ||||||
|         *blueprints, |  | ||||||
|         url_prefix="", |  | ||||||
|         version=None, |  | ||||||
|         strict_slashes=None, |  | ||||||
|         version_prefix: str = "/v", |  | ||||||
|     ): |  | ||||||
|         """ |         """ | ||||||
|         Create a list of blueprints, optionally grouping them under a |         Create a list of blueprints, optionally grouping them under a | ||||||
|         general URL prefix. |         general URL prefix. | ||||||
| @@ -168,6 +160,8 @@ class Blueprint(BaseSanic): | |||||||
|             for i in nested: |             for i in nested: | ||||||
|                 if isinstance(i, (list, tuple)): |                 if isinstance(i, (list, tuple)): | ||||||
|                     yield from chain(i) |                     yield from chain(i) | ||||||
|  |                 elif isinstance(i, BlueprintGroup): | ||||||
|  |                     yield from i.blueprints | ||||||
|                 else: |                 else: | ||||||
|                     yield i |                     yield i | ||||||
|  |  | ||||||
| @@ -175,7 +169,6 @@ class Blueprint(BaseSanic): | |||||||
|             url_prefix=url_prefix, |             url_prefix=url_prefix, | ||||||
|             version=version, |             version=version, | ||||||
|             strict_slashes=strict_slashes, |             strict_slashes=strict_slashes, | ||||||
|             version_prefix=version_prefix, |  | ||||||
|         ) |         ) | ||||||
|         for bp in chain(blueprints): |         for bp in chain(blueprints): | ||||||
|             bps.append(bp) |             bps.append(bp) | ||||||
| @@ -193,9 +186,6 @@ class Blueprint(BaseSanic): | |||||||
|  |  | ||||||
|         self._apps.add(app) |         self._apps.add(app) | ||||||
|         url_prefix = options.get("url_prefix", self.url_prefix) |         url_prefix = options.get("url_prefix", self.url_prefix) | ||||||
|         opt_version = options.get("version", None) |  | ||||||
|         opt_strict_slashes = options.get("strict_slashes", None) |  | ||||||
|         opt_version_prefix = options.get("version_prefix", self.version_prefix) |  | ||||||
|  |  | ||||||
|         routes = [] |         routes = [] | ||||||
|         middleware = [] |         middleware = [] | ||||||
| @@ -210,22 +200,12 @@ class Blueprint(BaseSanic): | |||||||
|             # Prepend the blueprint URI prefix if available |             # Prepend the blueprint URI prefix if available | ||||||
|             uri = url_prefix + future.uri if url_prefix else future.uri |             uri = url_prefix + future.uri if url_prefix else future.uri | ||||||
|  |  | ||||||
|             version_prefix = self.version_prefix |             strict_slashes = ( | ||||||
|             for prefix in ( |                 self.strict_slashes | ||||||
|                 future.version_prefix, |                 if future.strict_slashes is None | ||||||
|                 opt_version_prefix, |                 and self.strict_slashes is not None | ||||||
|             ): |                 else future.strict_slashes | ||||||
|                 if prefix and prefix != "/v": |  | ||||||
|                     version_prefix = prefix |  | ||||||
|                     break |  | ||||||
|  |  | ||||||
|             version = self._extract_value( |  | ||||||
|                 future.version, opt_version, self.version |  | ||||||
|             ) |             ) | ||||||
|             strict_slashes = self._extract_value( |  | ||||||
|                 future.strict_slashes, opt_strict_slashes, self.strict_slashes |  | ||||||
|             ) |  | ||||||
|  |  | ||||||
|             name = app._generate_name(future.name) |             name = app._generate_name(future.name) | ||||||
|  |  | ||||||
|             apply_route = FutureRoute( |             apply_route = FutureRoute( | ||||||
| @@ -235,14 +215,13 @@ class Blueprint(BaseSanic): | |||||||
|                 future.host or self.host, |                 future.host or self.host, | ||||||
|                 strict_slashes, |                 strict_slashes, | ||||||
|                 future.stream, |                 future.stream, | ||||||
|                 version, |                 future.version or self.version, | ||||||
|                 name, |                 name, | ||||||
|                 future.ignore_body, |                 future.ignore_body, | ||||||
|                 future.websocket, |                 future.websocket, | ||||||
|                 future.subprotocols, |                 future.subprotocols, | ||||||
|                 future.unquote, |                 future.unquote, | ||||||
|                 future.static, |                 future.static, | ||||||
|                 version_prefix, |  | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|             route = app._apply_route(apply_route) |             route = app._apply_route(apply_route) | ||||||
| @@ -279,6 +258,8 @@ class Blueprint(BaseSanic): | |||||||
|             app._apply_signal(signal) |             app._apply_signal(signal) | ||||||
|  |  | ||||||
|         self.routes = [route for route in routes if isinstance(route, Route)] |         self.routes = [route for route in routes if isinstance(route, Route)] | ||||||
|  |  | ||||||
|  |         # Deprecate these in 21.6 | ||||||
|         self.websocket_routes = [ |         self.websocket_routes = [ | ||||||
|             route for route in self.routes if route.ctx.websocket |             route for route in self.routes if route.ctx.websocket | ||||||
|         ] |         ] | ||||||
| @@ -307,12 +288,3 @@ class Blueprint(BaseSanic): | |||||||
|             return_when=asyncio.FIRST_COMPLETED, |             return_when=asyncio.FIRST_COMPLETED, | ||||||
|             timeout=timeout, |             timeout=timeout, | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     @staticmethod |  | ||||||
|     def _extract_value(*values): |  | ||||||
|         value = values[-1] |  | ||||||
|         for v in values: |  | ||||||
|             if v is not None: |  | ||||||
|                 value = v |  | ||||||
|                 break |  | ||||||
|         return value |  | ||||||
|   | |||||||
							
								
								
									
										102
									
								
								sanic/config.py
									
									
									
									
									
								
							
							
						
						
									
										102
									
								
								sanic/config.py
									
									
									
									
									
								
							| @@ -1,10 +1,7 @@ | |||||||
| from inspect import isclass | from inspect import isclass | ||||||
| from os import environ | from os import environ | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
| from typing import Any, Dict, Optional, Union | from typing import Any, Union | ||||||
| from warnings import warn |  | ||||||
|  |  | ||||||
| from sanic.http import Http |  | ||||||
|  |  | ||||||
| from .utils import load_module_from_file_location, str_to_bool | from .utils import load_module_from_file_location, str_to_bool | ||||||
|  |  | ||||||
| @@ -18,64 +15,33 @@ BASE_LOGO = """ | |||||||
| """ | """ | ||||||
|  |  | ||||||
| DEFAULT_CONFIG = { | DEFAULT_CONFIG = { | ||||||
|     "ACCESS_LOG": True, |  | ||||||
|     "EVENT_AUTOREGISTER": False, |  | ||||||
|     "FALLBACK_ERROR_FORMAT": "html", |  | ||||||
|     "FORWARDED_FOR_HEADER": "X-Forwarded-For", |  | ||||||
|     "FORWARDED_SECRET": None, |  | ||||||
|     "GRACEFUL_SHUTDOWN_TIMEOUT": 15.0,  # 15 sec |  | ||||||
|     "KEEP_ALIVE_TIMEOUT": 5,  # 5 seconds |  | ||||||
|     "KEEP_ALIVE": True, |  | ||||||
|     "PROXIES_COUNT": None, |  | ||||||
|     "REAL_IP_HEADER": None, |  | ||||||
|     "REGISTER": True, |  | ||||||
|     "REQUEST_BUFFER_SIZE": 65536,  # 64 KiB |  | ||||||
|     "REQUEST_MAX_HEADER_SIZE": 8192,  # 8 KiB, but cannot exceed 16384 |  | ||||||
|     "REQUEST_ID_HEADER": "X-Request-ID", |  | ||||||
|     "REQUEST_MAX_SIZE": 100000000,  # 100 megabytes |     "REQUEST_MAX_SIZE": 100000000,  # 100 megabytes | ||||||
|  |     "REQUEST_BUFFER_QUEUE_SIZE": 100, | ||||||
|  |     "REQUEST_BUFFER_SIZE": 65536,  # 64 KiB | ||||||
|     "REQUEST_TIMEOUT": 60,  # 60 seconds |     "REQUEST_TIMEOUT": 60,  # 60 seconds | ||||||
|     "RESPONSE_TIMEOUT": 60,  # 60 seconds |     "RESPONSE_TIMEOUT": 60,  # 60 seconds | ||||||
|     "WEBSOCKET_MAX_QUEUE": 32, |     "KEEP_ALIVE": True, | ||||||
|  |     "KEEP_ALIVE_TIMEOUT": 5,  # 5 seconds | ||||||
|     "WEBSOCKET_MAX_SIZE": 2 ** 20,  # 1 megabyte |     "WEBSOCKET_MAX_SIZE": 2 ** 20,  # 1 megabyte | ||||||
|     "WEBSOCKET_PING_INTERVAL": 20, |     "WEBSOCKET_MAX_QUEUE": 32, | ||||||
|     "WEBSOCKET_PING_TIMEOUT": 20, |  | ||||||
|     "WEBSOCKET_READ_LIMIT": 2 ** 16, |     "WEBSOCKET_READ_LIMIT": 2 ** 16, | ||||||
|     "WEBSOCKET_WRITE_LIMIT": 2 ** 16, |     "WEBSOCKET_WRITE_LIMIT": 2 ** 16, | ||||||
|  |     "WEBSOCKET_PING_TIMEOUT": 20, | ||||||
|  |     "WEBSOCKET_PING_INTERVAL": 20, | ||||||
|  |     "GRACEFUL_SHUTDOWN_TIMEOUT": 15.0,  # 15 sec | ||||||
|  |     "ACCESS_LOG": True, | ||||||
|  |     "FORWARDED_SECRET": None, | ||||||
|  |     "REAL_IP_HEADER": None, | ||||||
|  |     "PROXIES_COUNT": None, | ||||||
|  |     "FORWARDED_FOR_HEADER": "X-Forwarded-For", | ||||||
|  |     "REQUEST_ID_HEADER": "X-Request-ID", | ||||||
|  |     "FALLBACK_ERROR_FORMAT": "html", | ||||||
|  |     "REGISTER": True, | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| class Config(dict): | class Config(dict): | ||||||
|     ACCESS_LOG: bool |     def __init__(self, defaults=None, load_env=True, keep_alive=None): | ||||||
|     EVENT_AUTOREGISTER: bool |  | ||||||
|     FALLBACK_ERROR_FORMAT: str |  | ||||||
|     FORWARDED_FOR_HEADER: str |  | ||||||
|     FORWARDED_SECRET: Optional[str] |  | ||||||
|     GRACEFUL_SHUTDOWN_TIMEOUT: float |  | ||||||
|     KEEP_ALIVE_TIMEOUT: int |  | ||||||
|     KEEP_ALIVE: bool |  | ||||||
|     PROXIES_COUNT: Optional[int] |  | ||||||
|     REAL_IP_HEADER: Optional[str] |  | ||||||
|     REGISTER: bool |  | ||||||
|     REQUEST_BUFFER_SIZE: int |  | ||||||
|     REQUEST_MAX_HEADER_SIZE: int |  | ||||||
|     REQUEST_ID_HEADER: str |  | ||||||
|     REQUEST_MAX_SIZE: int |  | ||||||
|     REQUEST_TIMEOUT: int |  | ||||||
|     RESPONSE_TIMEOUT: int |  | ||||||
|     WEBSOCKET_MAX_QUEUE: int |  | ||||||
|     WEBSOCKET_MAX_SIZE: int |  | ||||||
|     WEBSOCKET_PING_INTERVAL: int |  | ||||||
|     WEBSOCKET_PING_TIMEOUT: int |  | ||||||
|     WEBSOCKET_READ_LIMIT: int |  | ||||||
|     WEBSOCKET_WRITE_LIMIT: int |  | ||||||
|  |  | ||||||
|     def __init__( |  | ||||||
|         self, |  | ||||||
|         defaults: Dict[str, Union[str, bool, int, float, None]] = None, |  | ||||||
|         load_env: Optional[Union[bool, str]] = True, |  | ||||||
|         env_prefix: Optional[str] = SANIC_PREFIX, |  | ||||||
|         keep_alive: Optional[bool] = None, |  | ||||||
|     ): |  | ||||||
|         defaults = defaults or {} |         defaults = defaults or {} | ||||||
|         super().__init__({**DEFAULT_CONFIG, **defaults}) |         super().__init__({**DEFAULT_CONFIG, **defaults}) | ||||||
|  |  | ||||||
| @@ -84,22 +50,9 @@ class Config(dict): | |||||||
|         if keep_alive is not None: |         if keep_alive is not None: | ||||||
|             self.KEEP_ALIVE = keep_alive |             self.KEEP_ALIVE = keep_alive | ||||||
|  |  | ||||||
|         if env_prefix != SANIC_PREFIX: |         if load_env: | ||||||
|             if env_prefix: |             prefix = SANIC_PREFIX if load_env is True else load_env | ||||||
|                 self.load_environment_vars(env_prefix) |             self.load_environment_vars(prefix=prefix) | ||||||
|         elif load_env is not True: |  | ||||||
|             if load_env: |  | ||||||
|                 self.load_environment_vars(prefix=load_env) |  | ||||||
|             warn( |  | ||||||
|                 "Use of load_env is deprecated and will be removed in " |  | ||||||
|                 "21.12. Modify the configuration prefix by passing " |  | ||||||
|                 "env_prefix instead.", |  | ||||||
|                 DeprecationWarning, |  | ||||||
|             ) |  | ||||||
|         else: |  | ||||||
|             self.load_environment_vars(SANIC_PREFIX) |  | ||||||
|  |  | ||||||
|         self._configure_header_size() |  | ||||||
|  |  | ||||||
|     def __getattr__(self, attr): |     def __getattr__(self, attr): | ||||||
|         try: |         try: | ||||||
| @@ -109,19 +62,6 @@ class Config(dict): | |||||||
|  |  | ||||||
|     def __setattr__(self, attr, value): |     def __setattr__(self, attr, value): | ||||||
|         self[attr] = value |         self[attr] = value | ||||||
|         if attr in ( |  | ||||||
|             "REQUEST_MAX_HEADER_SIZE", |  | ||||||
|             "REQUEST_BUFFER_SIZE", |  | ||||||
|             "REQUEST_MAX_SIZE", |  | ||||||
|         ): |  | ||||||
|             self._configure_header_size() |  | ||||||
|  |  | ||||||
|     def _configure_header_size(self): |  | ||||||
|         Http.set_header_max_size( |  | ||||||
|             self.REQUEST_MAX_HEADER_SIZE, |  | ||||||
|             self.REQUEST_BUFFER_SIZE - 4096, |  | ||||||
|             self.REQUEST_MAX_SIZE, |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     def load_environment_vars(self, prefix=SANIC_PREFIX): |     def load_environment_vars(self, prefix=SANIC_PREFIX): | ||||||
|         """ |         """ | ||||||
|   | |||||||
| @@ -1,28 +1,2 @@ | |||||||
| from enum import Enum, auto | HTTP_METHODS = ("GET", "POST", "PUT", "HEAD", "OPTIONS", "PATCH", "DELETE") | ||||||
|  |  | ||||||
|  |  | ||||||
| class HTTPMethod(str, Enum): |  | ||||||
|     def _generate_next_value_(name, start, count, last_values): |  | ||||||
|         return name.upper() |  | ||||||
|  |  | ||||||
|     def __eq__(self, value: object) -> bool: |  | ||||||
|         value = str(value).upper() |  | ||||||
|         return super().__eq__(value) |  | ||||||
|  |  | ||||||
|     def __hash__(self) -> int: |  | ||||||
|         return hash(self.value) |  | ||||||
|  |  | ||||||
|     def __str__(self) -> str: |  | ||||||
|         return self.value |  | ||||||
|  |  | ||||||
|     GET = auto() |  | ||||||
|     POST = auto() |  | ||||||
|     PUT = auto() |  | ||||||
|     HEAD = auto() |  | ||||||
|     OPTIONS = auto() |  | ||||||
|     PATCH = auto() |  | ||||||
|     DELETE = auto() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| HTTP_METHODS = tuple(HTTPMethod.__members__.values()) |  | ||||||
| DEFAULT_HTTP_CONTENT_TYPE = "application/octet-stream" | DEFAULT_HTTP_CONTENT_TYPE = "application/octet-stream" | ||||||
|   | |||||||
| @@ -366,7 +366,7 @@ def exception_response( | |||||||
|                 except InvalidUsage: |                 except InvalidUsage: | ||||||
|                     renderer = HTMLRenderer |                     renderer = HTMLRenderer | ||||||
|  |  | ||||||
|                 content_type, *_ = request.headers.getone( |                 content_type, *_ = request.headers.get( | ||||||
|                     "content-type", "" |                     "content-type", "" | ||||||
|                 ).split(";") |                 ).split(";") | ||||||
|                 renderer = RENDERERS_BY_CONTENT_TYPE.get( |                 renderer = RENDERERS_BY_CONTENT_TYPE.get( | ||||||
|   | |||||||
| @@ -3,18 +3,26 @@ from typing import Optional, Union | |||||||
| from sanic.helpers import STATUS_CODES | from sanic.helpers import STATUS_CODES | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _sanic_exceptions = {} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def add_status_code(code, quiet=None): | ||||||
|  |     """ | ||||||
|  |     Decorator used for adding exceptions to :class:`SanicException`. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def class_decorator(cls): | ||||||
|  |         cls.status_code = code | ||||||
|  |         if quiet or quiet is None and code != 500: | ||||||
|  |             cls.quiet = True | ||||||
|  |         _sanic_exceptions[code] = cls | ||||||
|  |         return cls | ||||||
|  |  | ||||||
|  |     return class_decorator | ||||||
|  |  | ||||||
|  |  | ||||||
| class SanicException(Exception): | class SanicException(Exception): | ||||||
|     def __init__( |     def __init__(self, message, status_code=None, quiet=None): | ||||||
|         self, |  | ||||||
|         message: Optional[Union[str, bytes]] = None, |  | ||||||
|         status_code: Optional[int] = None, |  | ||||||
|         quiet: Optional[bool] = None, |  | ||||||
|     ) -> None: |  | ||||||
|  |  | ||||||
|         if message is None and status_code is not None: |  | ||||||
|             msg: bytes = STATUS_CODES.get(status_code, b"") |  | ||||||
|             message = msg.decode("utf8") |  | ||||||
|  |  | ||||||
|         super().__init__(message) |         super().__init__(message) | ||||||
|  |  | ||||||
|         if status_code is not None: |         if status_code is not None: | ||||||
| @@ -25,45 +33,45 @@ class SanicException(Exception): | |||||||
|             self.quiet = True |             self.quiet = True | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @add_status_code(404) | ||||||
| class NotFound(SanicException): | class NotFound(SanicException): | ||||||
|     """ |     """ | ||||||
|     **Status**: 404 Not Found |     **Status**: 404 Not Found | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     status_code = 404 |     pass | ||||||
|     quiet = True |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @add_status_code(400) | ||||||
| class InvalidUsage(SanicException): | class InvalidUsage(SanicException): | ||||||
|     """ |     """ | ||||||
|     **Status**: 400 Bad Request |     **Status**: 400 Bad Request | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     status_code = 400 |     pass | ||||||
|     quiet = True |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @add_status_code(405) | ||||||
| class MethodNotSupported(SanicException): | class MethodNotSupported(SanicException): | ||||||
|     """ |     """ | ||||||
|     **Status**: 405 Method Not Allowed |     **Status**: 405 Method Not Allowed | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     status_code = 405 |  | ||||||
|     quiet = True |  | ||||||
|  |  | ||||||
|     def __init__(self, message, method, allowed_methods): |     def __init__(self, message, method, allowed_methods): | ||||||
|         super().__init__(message) |         super().__init__(message) | ||||||
|         self.headers = {"Allow": ", ".join(allowed_methods)} |         self.headers = {"Allow": ", ".join(allowed_methods)} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @add_status_code(500) | ||||||
| class ServerError(SanicException): | class ServerError(SanicException): | ||||||
|     """ |     """ | ||||||
|     **Status**: 500 Internal Server Error |     **Status**: 500 Internal Server Error | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     status_code = 500 |     pass | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @add_status_code(503) | ||||||
| class ServiceUnavailable(SanicException): | class ServiceUnavailable(SanicException): | ||||||
|     """ |     """ | ||||||
|     **Status**: 503 Service Unavailable |     **Status**: 503 Service Unavailable | ||||||
| @@ -72,8 +80,7 @@ class ServiceUnavailable(SanicException): | |||||||
|     down for maintenance). Generally, this is a temporary state. |     down for maintenance). Generally, this is a temporary state. | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     status_code = 503 |     pass | ||||||
|     quiet = True |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class URLBuildError(ServerError): | class URLBuildError(ServerError): | ||||||
| @@ -81,7 +88,7 @@ class URLBuildError(ServerError): | |||||||
|     **Status**: 500 Internal Server Error |     **Status**: 500 Internal Server Error | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     status_code = 500 |     pass | ||||||
|  |  | ||||||
|  |  | ||||||
| class FileNotFound(NotFound): | class FileNotFound(NotFound): | ||||||
| @@ -95,6 +102,7 @@ class FileNotFound(NotFound): | |||||||
|         self.relative_url = relative_url |         self.relative_url = relative_url | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @add_status_code(408) | ||||||
| class RequestTimeout(SanicException): | class RequestTimeout(SanicException): | ||||||
|     """The Web server (running the Web site) thinks that there has been too |     """The Web server (running the Web site) thinks that there has been too | ||||||
|     long an interval of time between 1) the establishment of an IP |     long an interval of time between 1) the establishment of an IP | ||||||
| @@ -104,17 +112,16 @@ class RequestTimeout(SanicException): | |||||||
|     server has 'timed out' on that particular socket connection. |     server has 'timed out' on that particular socket connection. | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     status_code = 408 |     pass | ||||||
|     quiet = True |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @add_status_code(413) | ||||||
| class PayloadTooLarge(SanicException): | class PayloadTooLarge(SanicException): | ||||||
|     """ |     """ | ||||||
|     **Status**: 413 Payload Too Large |     **Status**: 413 Payload Too Large | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     status_code = 413 |     pass | ||||||
|     quiet = True |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class HeaderNotFound(InvalidUsage): | class HeaderNotFound(InvalidUsage): | ||||||
| @@ -122,39 +129,36 @@ class HeaderNotFound(InvalidUsage): | |||||||
|     **Status**: 400 Bad Request |     **Status**: 400 Bad Request | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     status_code = 400 |     pass | ||||||
|     quiet = True |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @add_status_code(416) | ||||||
| class ContentRangeError(SanicException): | class ContentRangeError(SanicException): | ||||||
|     """ |     """ | ||||||
|     **Status**: 416 Range Not Satisfiable |     **Status**: 416 Range Not Satisfiable | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     status_code = 416 |  | ||||||
|     quiet = True |  | ||||||
|  |  | ||||||
|     def __init__(self, message, content_range): |     def __init__(self, message, content_range): | ||||||
|         super().__init__(message) |         super().__init__(message) | ||||||
|         self.headers = {"Content-Range": f"bytes */{content_range.total}"} |         self.headers = {"Content-Range": f"bytes */{content_range.total}"} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @add_status_code(417) | ||||||
| class HeaderExpectationFailed(SanicException): | class HeaderExpectationFailed(SanicException): | ||||||
|     """ |     """ | ||||||
|     **Status**: 417 Expectation Failed |     **Status**: 417 Expectation Failed | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     status_code = 417 |     pass | ||||||
|     quiet = True |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @add_status_code(403) | ||||||
| class Forbidden(SanicException): | class Forbidden(SanicException): | ||||||
|     """ |     """ | ||||||
|     **Status**: 403 Forbidden |     **Status**: 403 Forbidden | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     status_code = 403 |     pass | ||||||
|     quiet = True |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class InvalidRangeType(ContentRangeError): | class InvalidRangeType(ContentRangeError): | ||||||
| @@ -162,8 +166,7 @@ class InvalidRangeType(ContentRangeError): | |||||||
|     **Status**: 416 Range Not Satisfiable |     **Status**: 416 Range Not Satisfiable | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     status_code = 416 |     pass | ||||||
|     quiet = True |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PyFileError(Exception): | class PyFileError(Exception): | ||||||
| @@ -171,6 +174,7 @@ class PyFileError(Exception): | |||||||
|         super().__init__("could not execute config file %s", file) |         super().__init__("could not execute config file %s", file) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @add_status_code(401) | ||||||
| class Unauthorized(SanicException): | class Unauthorized(SanicException): | ||||||
|     """ |     """ | ||||||
|     **Status**: 401 Unauthorized |     **Status**: 401 Unauthorized | ||||||
| @@ -206,9 +210,6 @@ class Unauthorized(SanicException): | |||||||
|                            realm="Restricted Area") |                            realm="Restricted Area") | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     status_code = 401 |  | ||||||
|     quiet = True |  | ||||||
|  |  | ||||||
|     def __init__(self, message, status_code=None, scheme=None, **kwargs): |     def __init__(self, message, status_code=None, scheme=None, **kwargs): | ||||||
|         super().__init__(message, status_code) |         super().__init__(message, status_code) | ||||||
|  |  | ||||||
| @@ -240,13 +241,9 @@ def abort(status_code: int, message: Optional[Union[str, bytes]] = None): | |||||||
|     :param status_code: The HTTP status code to return. |     :param status_code: The HTTP status code to return. | ||||||
|     :param message: The HTTP response body. Defaults to the messages in |     :param message: The HTTP response body. Defaults to the messages in | ||||||
|     """ |     """ | ||||||
|     import warnings |     if message is None: | ||||||
|  |         msg: bytes = STATUS_CODES[status_code] | ||||||
|     warnings.warn( |         # These are stored as bytes in the STATUS_CODES dict | ||||||
|         "sanic.exceptions.abort has been marked as deprecated, and will be " |         message = msg.decode("utf8") | ||||||
|         "removed in release 21.12.\n To migrate your code, simply replace " |     sanic_exception = _sanic_exceptions.get(status_code, SanicException) | ||||||
|         "abort(status_code, msg) with raise SanicException(msg, status_code), " |     raise sanic_exception(message=message, status_code=status_code) | ||||||
|         "or even better, raise an appropriate SanicException subclass." |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     raise SanicException(message=message, status_code=status_code) |  | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ from sanic.exceptions import ( | |||||||
|     HeaderNotFound, |     HeaderNotFound, | ||||||
|     InvalidRangeType, |     InvalidRangeType, | ||||||
| ) | ) | ||||||
| from sanic.log import error_logger | from sanic.log import logger | ||||||
| from sanic.response import text | from sanic.response import text | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -25,6 +25,7 @@ class ErrorHandler: | |||||||
|  |  | ||||||
|     handlers = None |     handlers = None | ||||||
|     cached_handlers = None |     cached_handlers = None | ||||||
|  |     _missing = object() | ||||||
|  |  | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         self.handlers = [] |         self.handlers = [] | ||||||
| @@ -44,9 +45,7 @@ class ErrorHandler: | |||||||
|  |  | ||||||
|         :return: None |         :return: None | ||||||
|         """ |         """ | ||||||
|         # self.handlers to be deprecated and removed in version 21.12 |  | ||||||
|         self.handlers.append((exception, handler)) |         self.handlers.append((exception, handler)) | ||||||
|         self.cached_handlers[exception] = handler |  | ||||||
|  |  | ||||||
|     def lookup(self, exception): |     def lookup(self, exception): | ||||||
|         """ |         """ | ||||||
| @@ -62,19 +61,14 @@ class ErrorHandler: | |||||||
|  |  | ||||||
|         :return: Registered function if found ``None`` otherwise |         :return: Registered function if found ``None`` otherwise | ||||||
|         """ |         """ | ||||||
|         exception_class = type(exception) |         handler = self.cached_handlers.get(type(exception), self._missing) | ||||||
|         if exception_class in self.cached_handlers: |         if handler is self._missing: | ||||||
|             return self.cached_handlers[exception_class] |             for exception_class, handler in self.handlers: | ||||||
|  |                 if isinstance(exception, exception_class): | ||||||
|         for ancestor in type.mro(exception_class): |                     self.cached_handlers[type(exception)] = handler | ||||||
|             if ancestor in self.cached_handlers: |                     return handler | ||||||
|                 handler = self.cached_handlers[ancestor] |             self.cached_handlers[type(exception)] = None | ||||||
|                 self.cached_handlers[exception_class] = handler |             handler = None | ||||||
|                 return handler |  | ||||||
|             if ancestor is BaseException: |  | ||||||
|                 break |  | ||||||
|         self.cached_handlers[exception_class] = None |  | ||||||
|         handler = None |  | ||||||
|         return handler |         return handler | ||||||
|  |  | ||||||
|     def response(self, request, exception): |     def response(self, request, exception): | ||||||
| @@ -107,7 +101,7 @@ class ErrorHandler: | |||||||
|             response_message = ( |             response_message = ( | ||||||
|                 "Exception raised in exception handler " '"%s" for uri: %s' |                 "Exception raised in exception handler " '"%s" for uri: %s' | ||||||
|             ) |             ) | ||||||
|             error_logger.exception(response_message, handler.__name__, url) |             logger.exception(response_message, handler.__name__, url) | ||||||
|  |  | ||||||
|             if self.debug: |             if self.debug: | ||||||
|                 return text(response_message % (handler.__name__, url), 500) |                 return text(response_message % (handler.__name__, url), 500) | ||||||
| @@ -143,9 +137,7 @@ class ErrorHandler: | |||||||
|                 url = "unknown" |                 url = "unknown" | ||||||
|  |  | ||||||
|             self.log(format_exc()) |             self.log(format_exc()) | ||||||
|             error_logger.exception( |             logger.exception("Exception occurred while handling uri: %s", url) | ||||||
|                 "Exception occurred while handling uri: %s", url |  | ||||||
|             ) |  | ||||||
|  |  | ||||||
|         return exception_response(request, exception, self.debug) |         return exception_response(request, exception, self.debug) | ||||||
|  |  | ||||||
| @@ -173,7 +165,7 @@ class ContentRangeHandler: | |||||||
|  |  | ||||||
|     def __init__(self, request, stats): |     def __init__(self, request, stats): | ||||||
|         self.total = stats.st_size |         self.total = stats.st_size | ||||||
|         _range = request.headers.getone("range", None) |         _range = request.headers.get("Range") | ||||||
|         if _range is None: |         if _range is None: | ||||||
|             raise HeaderNotFound("Range Header Not Found") |             raise HeaderNotFound("Range Header Not Found") | ||||||
|         unit, _, value = tuple(map(str.strip, _range.partition("="))) |         unit, _, value = tuple(map(str.strip, _range.partition("="))) | ||||||
|   | |||||||
| @@ -102,7 +102,7 @@ def parse_xforwarded(headers, config) -> Optional[Options]: | |||||||
|     """Parse traditional proxy headers.""" |     """Parse traditional proxy headers.""" | ||||||
|     real_ip_header = config.REAL_IP_HEADER |     real_ip_header = config.REAL_IP_HEADER | ||||||
|     proxies_count = config.PROXIES_COUNT |     proxies_count = config.PROXIES_COUNT | ||||||
|     addr = real_ip_header and headers.getone(real_ip_header, None) |     addr = real_ip_header and headers.get(real_ip_header) | ||||||
|     if not addr and proxies_count: |     if not addr and proxies_count: | ||||||
|         assert proxies_count > 0 |         assert proxies_count > 0 | ||||||
|         try: |         try: | ||||||
| @@ -131,7 +131,7 @@ def parse_xforwarded(headers, config) -> Optional[Options]: | |||||||
|             ("port", "x-forwarded-port"), |             ("port", "x-forwarded-port"), | ||||||
|             ("path", "x-forwarded-path"), |             ("path", "x-forwarded-path"), | ||||||
|         ): |         ): | ||||||
|             yield key, headers.getone(header, None) |             yield key, headers.get(header) | ||||||
|  |  | ||||||
|     return fwd_normalize(options()) |     return fwd_normalize(options()) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ from sanic.exceptions import ( | |||||||
| ) | ) | ||||||
| from sanic.headers import format_http1_response | from sanic.headers import format_http1_response | ||||||
| from sanic.helpers import has_message_body | from sanic.helpers import has_message_body | ||||||
| from sanic.log import access_logger, error_logger, logger | from sanic.log import access_logger, logger | ||||||
|  |  | ||||||
|  |  | ||||||
| class Stage(Enum): | class Stage(Enum): | ||||||
| @@ -64,9 +64,6 @@ class Http: | |||||||
|     :raises RuntimeError: |     :raises RuntimeError: | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     HEADER_CEILING = 16_384 |  | ||||||
|     HEADER_MAX_SIZE = 0 |  | ||||||
|  |  | ||||||
|     __slots__ = [ |     __slots__ = [ | ||||||
|         "_send", |         "_send", | ||||||
|         "_receive_more", |         "_receive_more", | ||||||
| @@ -95,23 +92,19 @@ class Http: | |||||||
|         self._receive_more = protocol.receive_more |         self._receive_more = protocol.receive_more | ||||||
|         self.recv_buffer = protocol.recv_buffer |         self.recv_buffer = protocol.recv_buffer | ||||||
|         self.protocol = protocol |         self.protocol = protocol | ||||||
|         self.keep_alive = True |  | ||||||
|         self.stage: Stage = Stage.IDLE |  | ||||||
|         self.init_for_request() |  | ||||||
|  |  | ||||||
|     def init_for_request(self): |  | ||||||
|         """Init/reset all per-request variables.""" |  | ||||||
|         self.exception = None |  | ||||||
|         self.expecting_continue: bool = False |         self.expecting_continue: bool = False | ||||||
|         self.head_only = None |         self.stage: Stage = Stage.IDLE | ||||||
|         self.request_body = None |         self.request_body = None | ||||||
|         self.request_bytes = None |         self.request_bytes = None | ||||||
|         self.request_bytes_left = None |         self.request_bytes_left = None | ||||||
|         self.request_max_size = self.protocol.request_max_size |         self.request_max_size = protocol.request_max_size | ||||||
|  |         self.keep_alive = True | ||||||
|  |         self.head_only = None | ||||||
|         self.request: Request = None |         self.request: Request = None | ||||||
|         self.response: BaseHTTPResponse = None |         self.response: BaseHTTPResponse = None | ||||||
|         self.upgrade_websocket = False |         self.exception = None | ||||||
|         self.url = None |         self.url = None | ||||||
|  |         self.upgrade_websocket = False | ||||||
|  |  | ||||||
|     def __bool__(self): |     def __bool__(self): | ||||||
|         """Test if request handling is in progress""" |         """Test if request handling is in progress""" | ||||||
| @@ -151,11 +144,8 @@ class Http: | |||||||
|             # Try to consume any remaining request body |             # Try to consume any remaining request body | ||||||
|             if self.request_body: |             if self.request_body: | ||||||
|                 if self.response and 200 <= self.response.status < 300: |                 if self.response and 200 <= self.response.status < 300: | ||||||
|                     error_logger.error(f"{self.request} body not consumed.") |                     logger.error(f"{self.request} body not consumed.") | ||||||
|                 # Limit the size because the handler may have set it infinite |  | ||||||
|                 self.request_max_size = min( |  | ||||||
|                     self.request_max_size, self.protocol.request_max_size |  | ||||||
|                 ) |  | ||||||
|                 try: |                 try: | ||||||
|                     async for _ in self: |                     async for _ in self: | ||||||
|                         pass |                         pass | ||||||
| @@ -167,19 +157,11 @@ class Http: | |||||||
|                     await sleep(0.001) |                     await sleep(0.001) | ||||||
|                     self.keep_alive = False |                     self.keep_alive = False | ||||||
|  |  | ||||||
|             # Clean up to free memory and for the next request |  | ||||||
|             if self.request: |  | ||||||
|                 self.request.stream = None |  | ||||||
|                 if self.response: |  | ||||||
|                     self.response.stream = None |  | ||||||
|  |  | ||||||
|             self.init_for_request() |  | ||||||
|  |  | ||||||
|             # Exit and disconnect if no more requests can be taken |             # Exit and disconnect if no more requests can be taken | ||||||
|             if self.stage is not Stage.IDLE or not self.keep_alive: |             if self.stage is not Stage.IDLE or not self.keep_alive: | ||||||
|                 break |                 break | ||||||
|  |  | ||||||
|             # Wait for the next request |             # Wait for next request | ||||||
|             if not self.recv_buffer: |             if not self.recv_buffer: | ||||||
|                 await self._receive_more() |                 await self._receive_more() | ||||||
|  |  | ||||||
| @@ -187,6 +169,7 @@ class Http: | |||||||
|         """ |         """ | ||||||
|         Receive and parse request header into self.request. |         Receive and parse request header into self.request. | ||||||
|         """ |         """ | ||||||
|  |         HEADER_MAX_SIZE = min(8192, self.request_max_size) | ||||||
|         # Receive until full header is in buffer |         # Receive until full header is in buffer | ||||||
|         buf = self.recv_buffer |         buf = self.recv_buffer | ||||||
|         pos = 0 |         pos = 0 | ||||||
| @@ -197,12 +180,12 @@ class Http: | |||||||
|                 break |                 break | ||||||
|  |  | ||||||
|             pos = max(0, len(buf) - 3) |             pos = max(0, len(buf) - 3) | ||||||
|             if pos >= self.HEADER_MAX_SIZE: |             if pos >= HEADER_MAX_SIZE: | ||||||
|                 break |                 break | ||||||
|  |  | ||||||
|             await self._receive_more() |             await self._receive_more() | ||||||
|  |  | ||||||
|         if pos >= self.HEADER_MAX_SIZE: |         if pos >= HEADER_MAX_SIZE: | ||||||
|             raise PayloadTooLarge("Request header exceeds the size limit") |             raise PayloadTooLarge("Request header exceeds the size limit") | ||||||
|  |  | ||||||
|         # Parse header content |         # Parse header content | ||||||
| @@ -236,9 +219,7 @@ class Http: | |||||||
|             raise InvalidUsage("Bad Request") |             raise InvalidUsage("Bad Request") | ||||||
|  |  | ||||||
|         headers_instance = Header(headers) |         headers_instance = Header(headers) | ||||||
|         self.upgrade_websocket = ( |         self.upgrade_websocket = headers_instance.get("upgrade") == "websocket" | ||||||
|             headers_instance.getone("upgrade", "").lower() == "websocket" |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         # Prepare a Request object |         # Prepare a Request object | ||||||
|         request = self.protocol.request_class( |         request = self.protocol.request_class( | ||||||
| @@ -255,7 +236,7 @@ class Http: | |||||||
|         self.request_bytes_left = self.request_bytes = 0 |         self.request_bytes_left = self.request_bytes = 0 | ||||||
|         if request_body: |         if request_body: | ||||||
|             headers = request.headers |             headers = request.headers | ||||||
|             expect = headers.getone("expect", None) |             expect = headers.get("expect") | ||||||
|  |  | ||||||
|             if expect is not None: |             if expect is not None: | ||||||
|                 if expect.lower() == "100-continue": |                 if expect.lower() == "100-continue": | ||||||
| @@ -263,7 +244,7 @@ class Http: | |||||||
|                 else: |                 else: | ||||||
|                     raise HeaderExpectationFailed(f"Unknown Expect: {expect}") |                     raise HeaderExpectationFailed(f"Unknown Expect: {expect}") | ||||||
|  |  | ||||||
|             if headers.getone("transfer-encoding", None) == "chunked": |             if headers.get("transfer-encoding") == "chunked": | ||||||
|                 self.request_body = "chunked" |                 self.request_body = "chunked" | ||||||
|                 pos -= 2  # One CRLF stays in buffer |                 pos -= 2  # One CRLF stays in buffer | ||||||
|             else: |             else: | ||||||
| @@ -501,6 +482,8 @@ class Http: | |||||||
|                 self.keep_alive = False |                 self.keep_alive = False | ||||||
|                 raise InvalidUsage("Bad chunked encoding") |                 raise InvalidUsage("Bad chunked encoding") | ||||||
|  |  | ||||||
|  |             del buf[: pos + 2] | ||||||
|  |  | ||||||
|             if size <= 0: |             if size <= 0: | ||||||
|                 self.request_body = None |                 self.request_body = None | ||||||
|  |  | ||||||
| @@ -508,17 +491,8 @@ class Http: | |||||||
|                     self.keep_alive = False |                     self.keep_alive = False | ||||||
|                     raise InvalidUsage("Bad chunked encoding") |                     raise InvalidUsage("Bad chunked encoding") | ||||||
|  |  | ||||||
|                 # Consume CRLF, chunk size 0 and the two CRLF that follow |  | ||||||
|                 pos += 4 |  | ||||||
|                 # Might need to wait for the final CRLF |  | ||||||
|                 while len(buf) < pos: |  | ||||||
|                     await self._receive_more() |  | ||||||
|                 del buf[:pos] |  | ||||||
|                 return None |                 return None | ||||||
|  |  | ||||||
|             # Remove CRLF, chunk size and the CRLF that follows |  | ||||||
|             del buf[: pos + 2] |  | ||||||
|  |  | ||||||
|             self.request_bytes_left = size |             self.request_bytes_left = size | ||||||
|             self.request_bytes += size |             self.request_bytes += size | ||||||
|  |  | ||||||
| @@ -565,10 +539,3 @@ class Http: | |||||||
|     @property |     @property | ||||||
|     def send(self): |     def send(self): | ||||||
|         return self.response_func |         return self.response_func | ||||||
|  |  | ||||||
|     @classmethod |  | ||||||
|     def set_header_max_size(cls, *sizes: int): |  | ||||||
|         cls.HEADER_MAX_SIZE = min( |  | ||||||
|             *sizes, |  | ||||||
|             cls.HEADER_CEILING, |  | ||||||
|         ) |  | ||||||
|   | |||||||
| @@ -35,7 +35,7 @@ class ListenerMixin: | |||||||
|         """ |         """ | ||||||
|         Create a listener from a decorated function. |         Create a listener from a decorated function. | ||||||
|  |  | ||||||
|         To be used as a decorator: |         To be used as a deocrator: | ||||||
|  |  | ||||||
|         .. code-block:: python |         .. code-block:: python | ||||||
|  |  | ||||||
|   | |||||||
| @@ -26,11 +26,10 @@ from sanic.views import CompositionView | |||||||
|  |  | ||||||
|  |  | ||||||
| class RouteMixin: | class RouteMixin: | ||||||
|     name: str |  | ||||||
|  |  | ||||||
|     def __init__(self, *args, **kwargs) -> None: |     def __init__(self, *args, **kwargs) -> None: | ||||||
|         self._future_routes: Set[FutureRoute] = set() |         self._future_routes: Set[FutureRoute] = set() | ||||||
|         self._future_statics: Set[FutureStatic] = set() |         self._future_statics: Set[FutureStatic] = set() | ||||||
|  |         self.name = "" | ||||||
|         self.strict_slashes: Optional[bool] = False |         self.strict_slashes: Optional[bool] = False | ||||||
|  |  | ||||||
|     def _apply_route(self, route: FutureRoute) -> List[Route]: |     def _apply_route(self, route: FutureRoute) -> List[Route]: | ||||||
| @@ -54,7 +53,6 @@ class RouteMixin: | |||||||
|         websocket: bool = False, |         websocket: bool = False, | ||||||
|         unquote: bool = False, |         unquote: bool = False, | ||||||
|         static: bool = False, |         static: bool = False, | ||||||
|         version_prefix: str = "/v", |  | ||||||
|     ): |     ): | ||||||
|         """ |         """ | ||||||
|         Decorate a function to be registered as a route |         Decorate a function to be registered as a route | ||||||
| @@ -68,8 +66,6 @@ class RouteMixin: | |||||||
|         :param name: user defined route name for url_for |         :param name: user defined route name for url_for | ||||||
|         :param ignore_body: whether the handler should ignore request |         :param ignore_body: whether the handler should ignore request | ||||||
|             body (eg. GET requests) |             body (eg. GET requests) | ||||||
|         :param version_prefix: URL path that should be before the version |  | ||||||
|             value; default: ``/v`` |  | ||||||
|         :return: tuple of routes, decorated function |         :return: tuple of routes, decorated function | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
| @@ -96,7 +92,6 @@ class RouteMixin: | |||||||
|             nonlocal subprotocols |             nonlocal subprotocols | ||||||
|             nonlocal websocket |             nonlocal websocket | ||||||
|             nonlocal static |             nonlocal static | ||||||
|             nonlocal version_prefix |  | ||||||
|  |  | ||||||
|             if isinstance(handler, tuple): |             if isinstance(handler, tuple): | ||||||
|                 # if a handler fn is already wrapped in a route, the handler |                 # if a handler fn is already wrapped in a route, the handler | ||||||
| @@ -133,7 +128,6 @@ class RouteMixin: | |||||||
|                 subprotocols, |                 subprotocols, | ||||||
|                 unquote, |                 unquote, | ||||||
|                 static, |                 static, | ||||||
|                 version_prefix, |  | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|             self._future_routes.add(route) |             self._future_routes.add(route) | ||||||
| @@ -160,9 +154,7 @@ class RouteMixin: | |||||||
|             if apply: |             if apply: | ||||||
|                 self._apply_route(route) |                 self._apply_route(route) | ||||||
|  |  | ||||||
|             if static: |             return route, handler | ||||||
|                 return route, handler |  | ||||||
|             return handler |  | ||||||
|  |  | ||||||
|         return decorator |         return decorator | ||||||
|  |  | ||||||
| @@ -176,7 +168,6 @@ class RouteMixin: | |||||||
|         version: Optional[int] = None, |         version: Optional[int] = None, | ||||||
|         name: Optional[str] = None, |         name: Optional[str] = None, | ||||||
|         stream: bool = False, |         stream: bool = False, | ||||||
|         version_prefix: str = "/v", |  | ||||||
|     ): |     ): | ||||||
|         """A helper method to register class instance or |         """A helper method to register class instance or | ||||||
|         functions as a handler to the application url |         functions as a handler to the application url | ||||||
| @@ -191,8 +182,6 @@ class RouteMixin: | |||||||
|         :param version: |         :param version: | ||||||
|         :param name: user defined route name for url_for |         :param name: user defined route name for url_for | ||||||
|         :param stream: boolean specifying if the handler is a stream handler |         :param stream: boolean specifying if the handler is a stream handler | ||||||
|         :param version_prefix: URL path that should be before the version |  | ||||||
|             value; default: ``/v`` |  | ||||||
|         :return: function or class instance |         :return: function or class instance | ||||||
|         """ |         """ | ||||||
|         # Handle HTTPMethodView differently |         # Handle HTTPMethodView differently | ||||||
| @@ -225,7 +214,6 @@ class RouteMixin: | |||||||
|             stream=stream, |             stream=stream, | ||||||
|             version=version, |             version=version, | ||||||
|             name=name, |             name=name, | ||||||
|             version_prefix=version_prefix, |  | ||||||
|         )(handler) |         )(handler) | ||||||
|         return handler |         return handler | ||||||
|  |  | ||||||
| @@ -238,7 +226,6 @@ class RouteMixin: | |||||||
|         version: Optional[int] = None, |         version: Optional[int] = None, | ||||||
|         name: Optional[str] = None, |         name: Optional[str] = None, | ||||||
|         ignore_body: bool = True, |         ignore_body: bool = True, | ||||||
|         version_prefix: str = "/v", |  | ||||||
|     ): |     ): | ||||||
|         """ |         """ | ||||||
|         Add an API URL under the **GET** *HTTP* method |         Add an API URL under the **GET** *HTTP* method | ||||||
| @@ -249,8 +236,6 @@ class RouteMixin: | |||||||
|             URLs need to terminate with a */* |             URLs need to terminate with a */* | ||||||
|         :param version: API Version |         :param version: API Version | ||||||
|         :param name: Unique name that can be used to identify the Route |         :param name: Unique name that can be used to identify the Route | ||||||
|         :param version_prefix: URL path that should be before the version |  | ||||||
|             value; default: ``/v`` |  | ||||||
|         :return: Object decorated with :func:`route` method |         :return: Object decorated with :func:`route` method | ||||||
|         """ |         """ | ||||||
|         return self.route( |         return self.route( | ||||||
| @@ -261,7 +246,6 @@ class RouteMixin: | |||||||
|             version=version, |             version=version, | ||||||
|             name=name, |             name=name, | ||||||
|             ignore_body=ignore_body, |             ignore_body=ignore_body, | ||||||
|             version_prefix=version_prefix, |  | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def post( |     def post( | ||||||
| @@ -272,7 +256,6 @@ class RouteMixin: | |||||||
|         stream: bool = False, |         stream: bool = False, | ||||||
|         version: Optional[int] = None, |         version: Optional[int] = None, | ||||||
|         name: Optional[str] = None, |         name: Optional[str] = None, | ||||||
|         version_prefix: str = "/v", |  | ||||||
|     ): |     ): | ||||||
|         """ |         """ | ||||||
|         Add an API URL under the **POST** *HTTP* method |         Add an API URL under the **POST** *HTTP* method | ||||||
| @@ -283,8 +266,6 @@ class RouteMixin: | |||||||
|             URLs need to terminate with a */* |             URLs need to terminate with a */* | ||||||
|         :param version: API Version |         :param version: API Version | ||||||
|         :param name: Unique name that can be used to identify the Route |         :param name: Unique name that can be used to identify the Route | ||||||
|         :param version_prefix: URL path that should be before the version |  | ||||||
|             value; default: ``/v`` |  | ||||||
|         :return: Object decorated with :func:`route` method |         :return: Object decorated with :func:`route` method | ||||||
|         """ |         """ | ||||||
|         return self.route( |         return self.route( | ||||||
| @@ -295,7 +276,6 @@ class RouteMixin: | |||||||
|             stream=stream, |             stream=stream, | ||||||
|             version=version, |             version=version, | ||||||
|             name=name, |             name=name, | ||||||
|             version_prefix=version_prefix, |  | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def put( |     def put( | ||||||
| @@ -306,7 +286,6 @@ class RouteMixin: | |||||||
|         stream: bool = False, |         stream: bool = False, | ||||||
|         version: Optional[int] = None, |         version: Optional[int] = None, | ||||||
|         name: Optional[str] = None, |         name: Optional[str] = None, | ||||||
|         version_prefix: str = "/v", |  | ||||||
|     ): |     ): | ||||||
|         """ |         """ | ||||||
|         Add an API URL under the **PUT** *HTTP* method |         Add an API URL under the **PUT** *HTTP* method | ||||||
| @@ -317,8 +296,6 @@ class RouteMixin: | |||||||
|             URLs need to terminate with a */* |             URLs need to terminate with a */* | ||||||
|         :param version: API Version |         :param version: API Version | ||||||
|         :param name: Unique name that can be used to identify the Route |         :param name: Unique name that can be used to identify the Route | ||||||
|         :param version_prefix: URL path that should be before the version |  | ||||||
|             value; default: ``/v`` |  | ||||||
|         :return: Object decorated with :func:`route` method |         :return: Object decorated with :func:`route` method | ||||||
|         """ |         """ | ||||||
|         return self.route( |         return self.route( | ||||||
| @@ -329,7 +306,6 @@ class RouteMixin: | |||||||
|             stream=stream, |             stream=stream, | ||||||
|             version=version, |             version=version, | ||||||
|             name=name, |             name=name, | ||||||
|             version_prefix=version_prefix, |  | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def head( |     def head( | ||||||
| @@ -340,7 +316,6 @@ class RouteMixin: | |||||||
|         version: Optional[int] = None, |         version: Optional[int] = None, | ||||||
|         name: Optional[str] = None, |         name: Optional[str] = None, | ||||||
|         ignore_body: bool = True, |         ignore_body: bool = True, | ||||||
|         version_prefix: str = "/v", |  | ||||||
|     ): |     ): | ||||||
|         """ |         """ | ||||||
|         Add an API URL under the **HEAD** *HTTP* method |         Add an API URL under the **HEAD** *HTTP* method | ||||||
| @@ -359,8 +334,6 @@ class RouteMixin: | |||||||
|         :param ignore_body: whether the handler should ignore request |         :param ignore_body: whether the handler should ignore request | ||||||
|             body (eg. GET requests), defaults to True |             body (eg. GET requests), defaults to True | ||||||
|         :type ignore_body: bool, optional |         :type ignore_body: bool, optional | ||||||
|         :param version_prefix: URL path that should be before the version |  | ||||||
|             value; default: ``/v`` |  | ||||||
|         :return: Object decorated with :func:`route` method |         :return: Object decorated with :func:`route` method | ||||||
|         """ |         """ | ||||||
|         return self.route( |         return self.route( | ||||||
| @@ -371,7 +344,6 @@ class RouteMixin: | |||||||
|             version=version, |             version=version, | ||||||
|             name=name, |             name=name, | ||||||
|             ignore_body=ignore_body, |             ignore_body=ignore_body, | ||||||
|             version_prefix=version_prefix, |  | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def options( |     def options( | ||||||
| @@ -382,7 +354,6 @@ class RouteMixin: | |||||||
|         version: Optional[int] = None, |         version: Optional[int] = None, | ||||||
|         name: Optional[str] = None, |         name: Optional[str] = None, | ||||||
|         ignore_body: bool = True, |         ignore_body: bool = True, | ||||||
|         version_prefix: str = "/v", |  | ||||||
|     ): |     ): | ||||||
|         """ |         """ | ||||||
|         Add an API URL under the **OPTIONS** *HTTP* method |         Add an API URL under the **OPTIONS** *HTTP* method | ||||||
| @@ -401,8 +372,6 @@ class RouteMixin: | |||||||
|         :param ignore_body: whether the handler should ignore request |         :param ignore_body: whether the handler should ignore request | ||||||
|             body (eg. GET requests), defaults to True |             body (eg. GET requests), defaults to True | ||||||
|         :type ignore_body: bool, optional |         :type ignore_body: bool, optional | ||||||
|         :param version_prefix: URL path that should be before the version |  | ||||||
|             value; default: ``/v`` |  | ||||||
|         :return: Object decorated with :func:`route` method |         :return: Object decorated with :func:`route` method | ||||||
|         """ |         """ | ||||||
|         return self.route( |         return self.route( | ||||||
| @@ -413,7 +382,6 @@ class RouteMixin: | |||||||
|             version=version, |             version=version, | ||||||
|             name=name, |             name=name, | ||||||
|             ignore_body=ignore_body, |             ignore_body=ignore_body, | ||||||
|             version_prefix=version_prefix, |  | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def patch( |     def patch( | ||||||
| @@ -424,7 +392,6 @@ class RouteMixin: | |||||||
|         stream=False, |         stream=False, | ||||||
|         version: Optional[int] = None, |         version: Optional[int] = None, | ||||||
|         name: Optional[str] = None, |         name: Optional[str] = None, | ||||||
|         version_prefix: str = "/v", |  | ||||||
|     ): |     ): | ||||||
|         """ |         """ | ||||||
|         Add an API URL under the **PATCH** *HTTP* method |         Add an API URL under the **PATCH** *HTTP* method | ||||||
| @@ -445,8 +412,6 @@ class RouteMixin: | |||||||
|         :param ignore_body: whether the handler should ignore request |         :param ignore_body: whether the handler should ignore request | ||||||
|             body (eg. GET requests), defaults to True |             body (eg. GET requests), defaults to True | ||||||
|         :type ignore_body: bool, optional |         :type ignore_body: bool, optional | ||||||
|         :param version_prefix: URL path that should be before the version |  | ||||||
|             value; default: ``/v`` |  | ||||||
|         :return: Object decorated with :func:`route` method |         :return: Object decorated with :func:`route` method | ||||||
|         """ |         """ | ||||||
|         return self.route( |         return self.route( | ||||||
| @@ -457,7 +422,6 @@ class RouteMixin: | |||||||
|             stream=stream, |             stream=stream, | ||||||
|             version=version, |             version=version, | ||||||
|             name=name, |             name=name, | ||||||
|             version_prefix=version_prefix, |  | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def delete( |     def delete( | ||||||
| @@ -468,7 +432,6 @@ class RouteMixin: | |||||||
|         version: Optional[int] = None, |         version: Optional[int] = None, | ||||||
|         name: Optional[str] = None, |         name: Optional[str] = None, | ||||||
|         ignore_body: bool = True, |         ignore_body: bool = True, | ||||||
|         version_prefix: str = "/v", |  | ||||||
|     ): |     ): | ||||||
|         """ |         """ | ||||||
|         Add an API URL under the **DELETE** *HTTP* method |         Add an API URL under the **DELETE** *HTTP* method | ||||||
| @@ -479,8 +442,6 @@ class RouteMixin: | |||||||
|             URLs need to terminate with a */* |             URLs need to terminate with a */* | ||||||
|         :param version: API Version |         :param version: API Version | ||||||
|         :param name: Unique name that can be used to identify the Route |         :param name: Unique name that can be used to identify the Route | ||||||
|         :param version_prefix: URL path that should be before the version |  | ||||||
|             value; default: ``/v`` |  | ||||||
|         :return: Object decorated with :func:`route` method |         :return: Object decorated with :func:`route` method | ||||||
|         """ |         """ | ||||||
|         return self.route( |         return self.route( | ||||||
| @@ -491,7 +452,6 @@ class RouteMixin: | |||||||
|             version=version, |             version=version, | ||||||
|             name=name, |             name=name, | ||||||
|             ignore_body=ignore_body, |             ignore_body=ignore_body, | ||||||
|             version_prefix=version_prefix, |  | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def websocket( |     def websocket( | ||||||
| @@ -503,7 +463,6 @@ class RouteMixin: | |||||||
|         version: Optional[int] = None, |         version: Optional[int] = None, | ||||||
|         name: Optional[str] = None, |         name: Optional[str] = None, | ||||||
|         apply: bool = True, |         apply: bool = True, | ||||||
|         version_prefix: str = "/v", |  | ||||||
|     ): |     ): | ||||||
|         """ |         """ | ||||||
|         Decorate a function to be registered as a websocket route |         Decorate a function to be registered as a websocket route | ||||||
| @@ -515,8 +474,6 @@ class RouteMixin: | |||||||
|         :param subprotocols: optional list of str with supported subprotocols |         :param subprotocols: optional list of str with supported subprotocols | ||||||
|         :param name: A unique name assigned to the URL so that it can |         :param name: A unique name assigned to the URL so that it can | ||||||
|                      be used with :func:`url_for` |                      be used with :func:`url_for` | ||||||
|         :param version_prefix: URL path that should be before the version |  | ||||||
|             value; default: ``/v`` |  | ||||||
|         :return: tuple of routes, decorated function |         :return: tuple of routes, decorated function | ||||||
|         """ |         """ | ||||||
|         return self.route( |         return self.route( | ||||||
| @@ -529,7 +486,6 @@ class RouteMixin: | |||||||
|             apply=apply, |             apply=apply, | ||||||
|             subprotocols=subprotocols, |             subprotocols=subprotocols, | ||||||
|             websocket=True, |             websocket=True, | ||||||
|             version_prefix=version_prefix, |  | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def add_websocket_route( |     def add_websocket_route( | ||||||
| @@ -541,7 +497,6 @@ class RouteMixin: | |||||||
|         subprotocols=None, |         subprotocols=None, | ||||||
|         version: Optional[int] = None, |         version: Optional[int] = None, | ||||||
|         name: Optional[str] = None, |         name: Optional[str] = None, | ||||||
|         version_prefix: str = "/v", |  | ||||||
|     ): |     ): | ||||||
|         """ |         """ | ||||||
|         A helper method to register a function as a websocket route. |         A helper method to register a function as a websocket route. | ||||||
| @@ -558,8 +513,6 @@ class RouteMixin: | |||||||
|                 handshake |                 handshake | ||||||
|         :param name: A unique name assigned to the URL so that it can |         :param name: A unique name assigned to the URL so that it can | ||||||
|                 be used with :func:`url_for` |                 be used with :func:`url_for` | ||||||
|         :param version_prefix: URL path that should be before the version |  | ||||||
|             value; default: ``/v`` |  | ||||||
|         :return: Objected decorated by :func:`websocket` |         :return: Objected decorated by :func:`websocket` | ||||||
|         """ |         """ | ||||||
|         return self.websocket( |         return self.websocket( | ||||||
| @@ -569,7 +522,6 @@ class RouteMixin: | |||||||
|             subprotocols=subprotocols, |             subprotocols=subprotocols, | ||||||
|             version=version, |             version=version, | ||||||
|             name=name, |             name=name, | ||||||
|             version_prefix=version_prefix, |  | ||||||
|         )(handler) |         )(handler) | ||||||
|  |  | ||||||
|     def static( |     def static( | ||||||
| @@ -713,10 +665,7 @@ class RouteMixin: | |||||||
|                 modified_since = strftime( |                 modified_since = strftime( | ||||||
|                     "%a, %d %b %Y %H:%M:%S GMT", gmtime(stats.st_mtime) |                     "%a, %d %b %Y %H:%M:%S GMT", gmtime(stats.st_mtime) | ||||||
|                 ) |                 ) | ||||||
|                 if ( |                 if request.headers.get("If-Modified-Since") == modified_since: | ||||||
|                     request.headers.getone("if-modified-since", None) |  | ||||||
|                     == modified_since |  | ||||||
|                 ): |  | ||||||
|                     return HTTPResponse(status=304) |                     return HTTPResponse(status=304) | ||||||
|                 headers["Last-Modified"] = modified_since |                 headers["Last-Modified"] = modified_since | ||||||
|             _range = None |             _range = None | ||||||
| @@ -769,18 +718,16 @@ class RouteMixin: | |||||||
|                 return await file(file_path, headers=headers, _range=_range) |                 return await file(file_path, headers=headers, _range=_range) | ||||||
|         except ContentRangeError: |         except ContentRangeError: | ||||||
|             raise |             raise | ||||||
|         except FileNotFoundError: |         except Exception: | ||||||
|  |             error_logger.exception( | ||||||
|  |                 f"File not found: path={file_or_directory}, " | ||||||
|  |                 f"relative_url={__file_uri__}" | ||||||
|  |             ) | ||||||
|             raise FileNotFound( |             raise FileNotFound( | ||||||
|                 "File not found", |                 "File not found", | ||||||
|                 path=file_or_directory, |                 path=file_or_directory, | ||||||
|                 relative_url=__file_uri__, |                 relative_url=__file_uri__, | ||||||
|             ) |             ) | ||||||
|         except Exception: |  | ||||||
|             error_logger.exception( |  | ||||||
|                 f"Exception in static request handler:\ |  | ||||||
|  path={file_or_directory}, " |  | ||||||
|                 f"relative_url={__file_uri__}" |  | ||||||
|             ) |  | ||||||
|  |  | ||||||
|     def _register_static( |     def _register_static( | ||||||
|         self, |         self, | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| from typing import Any, Callable, Dict, Optional, Set | from typing import Any, Callable, Dict, Set | ||||||
|  |  | ||||||
| from sanic.models.futures import FutureSignal | from sanic.models.futures import FutureSignal | ||||||
| from sanic.models.handler_types import SignalHandler | from sanic.models.handler_types import SignalHandler | ||||||
| @@ -60,16 +60,10 @@ class SignalMixin: | |||||||
|  |  | ||||||
|     def add_signal( |     def add_signal( | ||||||
|         self, |         self, | ||||||
|         handler: Optional[Callable[..., Any]], |         handler, | ||||||
|         event: str, |         event: str, | ||||||
|         condition: Dict[str, Any] = None, |         condition: Dict[str, Any] = None, | ||||||
|     ): |     ): | ||||||
|         if not handler: |  | ||||||
|  |  | ||||||
|             async def noop(): |  | ||||||
|                 ... |  | ||||||
|  |  | ||||||
|             handler = noop |  | ||||||
|         self.signal(event=event, condition=condition)(handler) |         self.signal(event=event, condition=condition)(handler) | ||||||
|         return handler |         return handler | ||||||
|  |  | ||||||
|   | |||||||
| @@ -23,7 +23,6 @@ class FutureRoute(NamedTuple): | |||||||
|     subprotocols: Optional[List[str]] |     subprotocols: Optional[List[str]] | ||||||
|     unquote: bool |     unquote: bool | ||||||
|     static: bool |     static: bool | ||||||
|     version_prefix: str |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class FutureListener(NamedTuple): | class FutureListener(NamedTuple): | ||||||
|   | |||||||
| @@ -1,4 +1,3 @@ | |||||||
| import itertools |  | ||||||
| import os | import os | ||||||
| import signal | import signal | ||||||
| import subprocess | import subprocess | ||||||
| @@ -6,9 +5,6 @@ import sys | |||||||
|  |  | ||||||
| from time import sleep | from time import sleep | ||||||
|  |  | ||||||
| from sanic.config import BASE_LOGO |  | ||||||
| from sanic.log import logger |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def _iter_module_files(): | def _iter_module_files(): | ||||||
|     """This iterates over all relevant Python files. |     """This iterates over all relevant Python files. | ||||||
| @@ -60,21 +56,7 @@ def restart_with_reloader(): | |||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def _check_file(filename, mtimes): | def watchdog(sleep_interval): | ||||||
|     need_reload = False |  | ||||||
|  |  | ||||||
|     mtime = os.stat(filename).st_mtime |  | ||||||
|     old_time = mtimes.get(filename) |  | ||||||
|     if old_time is None: |  | ||||||
|         mtimes[filename] = mtime |  | ||||||
|     elif mtime > old_time: |  | ||||||
|         mtimes[filename] = mtime |  | ||||||
|         need_reload = True |  | ||||||
|  |  | ||||||
|     return need_reload |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def watchdog(sleep_interval, app): |  | ||||||
|     """Watch project files, restart worker process if a change happened. |     """Watch project files, restart worker process if a change happened. | ||||||
|  |  | ||||||
|     :param sleep_interval: interval in second. |     :param sleep_interval: interval in second. | ||||||
| @@ -91,25 +73,21 @@ def watchdog(sleep_interval, app): | |||||||
|  |  | ||||||
|     worker_process = restart_with_reloader() |     worker_process = restart_with_reloader() | ||||||
|  |  | ||||||
|     if app.config.LOGO: |  | ||||||
|         logger.debug( |  | ||||||
|             app.config.LOGO if isinstance(app.config.LOGO, str) else BASE_LOGO |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     try: |     try: | ||||||
|         while True: |         while True: | ||||||
|             need_reload = False |             need_reload = False | ||||||
|  |  | ||||||
|             for filename in itertools.chain( |             for filename in _iter_module_files(): | ||||||
|                 _iter_module_files(), |  | ||||||
|                 *(d.glob("**/*") for d in app.reload_dirs), |  | ||||||
|             ): |  | ||||||
|                 try: |                 try: | ||||||
|                     check = _check_file(filename, mtimes) |                     mtime = os.stat(filename).st_mtime | ||||||
|                 except OSError: |                 except OSError: | ||||||
|                     continue |                     continue | ||||||
|  |  | ||||||
|                 if check: |                 old_time = mtimes.get(filename) | ||||||
|  |                 if old_time is None: | ||||||
|  |                     mtimes[filename] = mtime | ||||||
|  |                 elif mtime > old_time: | ||||||
|  |                     mtimes[filename] = mtime | ||||||
|                     need_reload = True |                     need_reload = True | ||||||
|  |  | ||||||
|             if need_reload: |             if need_reload: | ||||||
|   | |||||||
| @@ -125,7 +125,7 @@ class Request: | |||||||
|         self._name: Optional[str] = None |         self._name: Optional[str] = None | ||||||
|         self.app = app |         self.app = app | ||||||
|  |  | ||||||
|         self.headers = Header(headers) |         self.headers = headers | ||||||
|         self.version = version |         self.version = version | ||||||
|         self.method = method |         self.method = method | ||||||
|         self.transport = transport |         self.transport = transport | ||||||
| @@ -262,7 +262,7 @@ class Request: | |||||||
|             app = Sanic("MyApp", request_class=IntRequest) |             app = Sanic("MyApp", request_class=IntRequest) | ||||||
|         """ |         """ | ||||||
|         if not self._id: |         if not self._id: | ||||||
|             self._id = self.headers.getone( |             self._id = self.headers.get( | ||||||
|                 self.app.config.REQUEST_ID_HEADER, |                 self.app.config.REQUEST_ID_HEADER, | ||||||
|                 self.__class__.generate_id(self),  # type: ignore |                 self.__class__.generate_id(self),  # type: ignore | ||||||
|             ) |             ) | ||||||
| @@ -303,7 +303,7 @@ class Request: | |||||||
|         :return: token related to request |         :return: token related to request | ||||||
|         """ |         """ | ||||||
|         prefixes = ("Bearer", "Token") |         prefixes = ("Bearer", "Token") | ||||||
|         auth_header = self.headers.getone("authorization", None) |         auth_header = self.headers.get("Authorization") | ||||||
|  |  | ||||||
|         if auth_header is not None: |         if auth_header is not None: | ||||||
|             for prefix in prefixes: |             for prefix in prefixes: | ||||||
| @@ -317,8 +317,8 @@ class Request: | |||||||
|         if self.parsed_form is None: |         if self.parsed_form is None: | ||||||
|             self.parsed_form = RequestParameters() |             self.parsed_form = RequestParameters() | ||||||
|             self.parsed_files = RequestParameters() |             self.parsed_files = RequestParameters() | ||||||
|             content_type = self.headers.getone( |             content_type = self.headers.get( | ||||||
|                 "content-type", DEFAULT_HTTP_CONTENT_TYPE |                 "Content-Type", DEFAULT_HTTP_CONTENT_TYPE | ||||||
|             ) |             ) | ||||||
|             content_type, parameters = parse_content_header(content_type) |             content_type, parameters = parse_content_header(content_type) | ||||||
|             try: |             try: | ||||||
| @@ -378,12 +378,9 @@ class Request: | |||||||
|         :type errors: str |         :type errors: str | ||||||
|         :return: RequestParameters |         :return: RequestParameters | ||||||
|         """ |         """ | ||||||
|         if ( |         if not self.parsed_args[ | ||||||
|             keep_blank_values, |             (keep_blank_values, strict_parsing, encoding, errors) | ||||||
|             strict_parsing, |         ]: | ||||||
|             encoding, |  | ||||||
|             errors, |  | ||||||
|         ) not in self.parsed_args: |  | ||||||
|             if self.query_string: |             if self.query_string: | ||||||
|                 self.parsed_args[ |                 self.parsed_args[ | ||||||
|                     (keep_blank_values, strict_parsing, encoding, errors) |                     (keep_blank_values, strict_parsing, encoding, errors) | ||||||
| @@ -437,12 +434,9 @@ class Request: | |||||||
|         :type errors: str |         :type errors: str | ||||||
|         :return: list |         :return: list | ||||||
|         """ |         """ | ||||||
|         if ( |         if not self.parsed_not_grouped_args[ | ||||||
|             keep_blank_values, |             (keep_blank_values, strict_parsing, encoding, errors) | ||||||
|             strict_parsing, |         ]: | ||||||
|             encoding, |  | ||||||
|             errors, |  | ||||||
|         ) not in self.parsed_not_grouped_args: |  | ||||||
|             if self.query_string: |             if self.query_string: | ||||||
|                 self.parsed_not_grouped_args[ |                 self.parsed_not_grouped_args[ | ||||||
|                     (keep_blank_values, strict_parsing, encoding, errors) |                     (keep_blank_values, strict_parsing, encoding, errors) | ||||||
| @@ -471,7 +465,7 @@ class Request: | |||||||
|         """ |         """ | ||||||
|  |  | ||||||
|         if self._cookies is None: |         if self._cookies is None: | ||||||
|             cookie = self.headers.getone("cookie", None) |             cookie = self.headers.get("Cookie") | ||||||
|             if cookie is not None: |             if cookie is not None: | ||||||
|                 cookies: SimpleCookie = SimpleCookie() |                 cookies: SimpleCookie = SimpleCookie() | ||||||
|                 cookies.load(cookie) |                 cookies.load(cookie) | ||||||
| @@ -488,7 +482,7 @@ class Request: | |||||||
|         :return: Content-Type header form the request |         :return: Content-Type header form the request | ||||||
|         :rtype: str |         :rtype: str | ||||||
|         """ |         """ | ||||||
|         return self.headers.getone("content-type", DEFAULT_HTTP_CONTENT_TYPE) |         return self.headers.get("Content-Type", DEFAULT_HTTP_CONTENT_TYPE) | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def match_info(self): |     def match_info(self): | ||||||
| @@ -505,7 +499,7 @@ class Request: | |||||||
|         :return: peer ip of the socket |         :return: peer ip of the socket | ||||||
|         :rtype: str |         :rtype: str | ||||||
|         """ |         """ | ||||||
|         return self.conn_info.client_ip if self.conn_info else "" |         return self.conn_info.client if self.conn_info else "" | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def port(self) -> int: |     def port(self) -> int: | ||||||
| @@ -587,7 +581,7 @@ class Request: | |||||||
|  |  | ||||||
|         if ( |         if ( | ||||||
|             self.app.websocket_enabled |             self.app.websocket_enabled | ||||||
|             and self.headers.getone("upgrade", "").lower() == "websocket" |             and self.headers.get("upgrade") == "websocket" | ||||||
|         ): |         ): | ||||||
|             scheme = "ws" |             scheme = "ws" | ||||||
|         else: |         else: | ||||||
| @@ -614,9 +608,7 @@ class Request: | |||||||
|         server_name = self.app.config.get("SERVER_NAME") |         server_name = self.app.config.get("SERVER_NAME") | ||||||
|         if server_name: |         if server_name: | ||||||
|             return server_name.split("//", 1)[-1].split("/", 1)[0] |             return server_name.split("//", 1)[-1].split("/", 1)[0] | ||||||
|         return str( |         return str(self.forwarded.get("host") or self.headers.get("host", "")) | ||||||
|             self.forwarded.get("host") or self.headers.getone("host", "") |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def server_name(self) -> str: |     def server_name(self) -> str: | ||||||
|   | |||||||
| @@ -143,7 +143,7 @@ class StreamingHTTPResponse(BaseHTTPResponse): | |||||||
|  |  | ||||||
|     .. warning:: |     .. warning:: | ||||||
|  |  | ||||||
|         **Deprecated** and set for removal in v21.12. You can now achieve the |         **Deprecated** and set for removal in v21.6. You can now achieve the | ||||||
|         same functionality without a callback. |         same functionality without a callback. | ||||||
|  |  | ||||||
|         .. code-block:: python |         .. code-block:: python | ||||||
| @@ -174,16 +174,12 @@ class StreamingHTTPResponse(BaseHTTPResponse): | |||||||
|         status: int = 200, |         status: int = 200, | ||||||
|         headers: Optional[Union[Header, Dict[str, str]]] = None, |         headers: Optional[Union[Header, Dict[str, str]]] = None, | ||||||
|         content_type: str = "text/plain; charset=utf-8", |         content_type: str = "text/plain; charset=utf-8", | ||||||
|         ignore_deprecation_notice: bool = False, |         chunked="deprecated", | ||||||
|     ): |     ): | ||||||
|         if not ignore_deprecation_notice: |         if chunked != "deprecated": | ||||||
|             warn( |             warn( | ||||||
|                 "Use of the StreamingHTTPResponse is deprecated in v21.6, and " |                 "The chunked argument has been deprecated and will be " | ||||||
|                 "will be removed in v21.12. Please upgrade your streaming " |                 "removed in v21.6" | ||||||
|                 "response implementation. You can learn more here: " |  | ||||||
|                 "https://sanicframework.org/en/guide/advanced/streaming.html" |  | ||||||
|                 "#response-streaming. If you use the builtin stream() or " |  | ||||||
|                 "file_stream() methods, this upgrade will be be done for you." |  | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|         super().__init__() |         super().__init__() | ||||||
| @@ -207,9 +203,6 @@ class StreamingHTTPResponse(BaseHTTPResponse): | |||||||
|             self.streaming_fn = None |             self.streaming_fn = None | ||||||
|         await super().send(*args, **kwargs) |         await super().send(*args, **kwargs) | ||||||
|  |  | ||||||
|     async def eof(self): |  | ||||||
|         raise NotImplementedError |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class HTTPResponse(BaseHTTPResponse): | class HTTPResponse(BaseHTTPResponse): | ||||||
|     """ |     """ | ||||||
| @@ -242,15 +235,6 @@ class HTTPResponse(BaseHTTPResponse): | |||||||
|         self.headers = Header(headers or {}) |         self.headers = Header(headers or {}) | ||||||
|         self._cookies = None |         self._cookies = None | ||||||
|  |  | ||||||
|     async def eof(self): |  | ||||||
|         await self.send("", True) |  | ||||||
|  |  | ||||||
|     async def __aenter__(self): |  | ||||||
|         return self.send |  | ||||||
|  |  | ||||||
|     async def __aexit__(self, *_): |  | ||||||
|         await self.eof() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def empty( | def empty( | ||||||
|     status=204, headers: Optional[Dict[str, str]] = None |     status=204, headers: Optional[Dict[str, str]] = None | ||||||
| @@ -412,6 +396,7 @@ async def file_stream( | |||||||
|     mime_type: Optional[str] = None, |     mime_type: Optional[str] = None, | ||||||
|     headers: Optional[Dict[str, str]] = None, |     headers: Optional[Dict[str, str]] = None, | ||||||
|     filename: Optional[str] = None, |     filename: Optional[str] = None, | ||||||
|  |     chunked="deprecated", | ||||||
|     _range: Optional[Range] = None, |     _range: Optional[Range] = None, | ||||||
| ) -> StreamingHTTPResponse: | ) -> StreamingHTTPResponse: | ||||||
|     """Return a streaming response object with file data. |     """Return a streaming response object with file data. | ||||||
| @@ -424,6 +409,12 @@ async def file_stream( | |||||||
|     :param chunked: Deprecated |     :param chunked: Deprecated | ||||||
|     :param _range: |     :param _range: | ||||||
|     """ |     """ | ||||||
|  |     if chunked != "deprecated": | ||||||
|  |         warn( | ||||||
|  |             "The chunked argument has been deprecated and will be " | ||||||
|  |             "removed in v21.6" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     headers = headers or {} |     headers = headers or {} | ||||||
|     if filename: |     if filename: | ||||||
|         headers.setdefault( |         headers.setdefault( | ||||||
| @@ -462,7 +453,6 @@ async def file_stream( | |||||||
|         status=status, |         status=status, | ||||||
|         headers=headers, |         headers=headers, | ||||||
|         content_type=mime_type, |         content_type=mime_type, | ||||||
|         ignore_deprecation_notice=True, |  | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -471,6 +461,7 @@ def stream( | |||||||
|     status: int = 200, |     status: int = 200, | ||||||
|     headers: Optional[Dict[str, str]] = None, |     headers: Optional[Dict[str, str]] = None, | ||||||
|     content_type: str = "text/plain; charset=utf-8", |     content_type: str = "text/plain; charset=utf-8", | ||||||
|  |     chunked="deprecated", | ||||||
| ): | ): | ||||||
|     """Accepts an coroutine `streaming_fn` which can be used to |     """Accepts an coroutine `streaming_fn` which can be used to | ||||||
|     write chunks to a streaming response. Returns a `StreamingHTTPResponse`. |     write chunks to a streaming response. Returns a `StreamingHTTPResponse`. | ||||||
| @@ -491,12 +482,17 @@ def stream( | |||||||
|     :param headers: Custom Headers. |     :param headers: Custom Headers. | ||||||
|     :param chunked: Deprecated |     :param chunked: Deprecated | ||||||
|     """ |     """ | ||||||
|  |     if chunked != "deprecated": | ||||||
|  |         warn( | ||||||
|  |             "The chunked argument has been deprecated and will be " | ||||||
|  |             "removed in v21.6" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     return StreamingHTTPResponse( |     return StreamingHTTPResponse( | ||||||
|         streaming_fn, |         streaming_fn, | ||||||
|         headers=headers, |         headers=headers, | ||||||
|         content_type=content_type, |         content_type=content_type, | ||||||
|         status=status, |         status=status, | ||||||
|         ignore_deprecation_notice=True, |  | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -73,7 +73,6 @@ class Router(BaseRouter): | |||||||
|         name: Optional[str] = None, |         name: Optional[str] = None, | ||||||
|         unquote: bool = False, |         unquote: bool = False, | ||||||
|         static: bool = False, |         static: bool = False, | ||||||
|         version_prefix: str = "/v", |  | ||||||
|     ) -> Union[Route, List[Route]]: |     ) -> Union[Route, List[Route]]: | ||||||
|         """ |         """ | ||||||
|         Add a handler to the router |         Add a handler to the router | ||||||
| @@ -104,12 +103,12 @@ class Router(BaseRouter): | |||||||
|         """ |         """ | ||||||
|         if version is not None: |         if version is not None: | ||||||
|             version = str(version).strip("/").lstrip("v") |             version = str(version).strip("/").lstrip("v") | ||||||
|             uri = "/".join([f"{version_prefix}{version}", uri.lstrip("/")]) |             uri = "/".join([f"/v{version}", uri.lstrip("/")]) | ||||||
|  |  | ||||||
|         params = dict( |         params = dict( | ||||||
|             path=uri, |             path=uri, | ||||||
|             handler=handler, |             handler=handler, | ||||||
|             methods=frozenset(map(str, methods)) if methods else None, |             methods=methods, | ||||||
|             name=name, |             name=name, | ||||||
|             strict=strict_slashes, |             strict=strict_slashes, | ||||||
|             unquote=unquote, |             unquote=unquote, | ||||||
|   | |||||||
| @@ -39,7 +39,7 @@ from sanic.compat import OS_IS_WINDOWS, ctrlc_workaround_for_windows | |||||||
| from sanic.config import Config | from sanic.config import Config | ||||||
| from sanic.exceptions import RequestTimeout, ServiceUnavailable | from sanic.exceptions import RequestTimeout, ServiceUnavailable | ||||||
| from sanic.http import Http, Stage | from sanic.http import Http, Stage | ||||||
| from sanic.log import error_logger, logger | from sanic.log import logger | ||||||
| from sanic.models.protocol_types import TransportProtocol | from sanic.models.protocol_types import TransportProtocol | ||||||
| from sanic.request import Request | from sanic.request import Request | ||||||
|  |  | ||||||
| @@ -65,7 +65,6 @@ class ConnInfo: | |||||||
|     __slots__ = ( |     __slots__ = ( | ||||||
|         "client_port", |         "client_port", | ||||||
|         "client", |         "client", | ||||||
|         "client_ip", |  | ||||||
|         "ctx", |         "ctx", | ||||||
|         "peername", |         "peername", | ||||||
|         "server_port", |         "server_port", | ||||||
| @@ -79,7 +78,6 @@ class ConnInfo: | |||||||
|         self.peername = None |         self.peername = None | ||||||
|         self.server = self.client = "" |         self.server = self.client = "" | ||||||
|         self.server_port = self.client_port = 0 |         self.server_port = self.client_port = 0 | ||||||
|         self.client_ip = "" |  | ||||||
|         self.sockname = addr = transport.get_extra_info("sockname") |         self.sockname = addr = transport.get_extra_info("sockname") | ||||||
|         self.ssl: bool = bool(transport.get_extra_info("sslcontext")) |         self.ssl: bool = bool(transport.get_extra_info("sslcontext")) | ||||||
|  |  | ||||||
| @@ -98,7 +96,6 @@ class ConnInfo: | |||||||
|  |  | ||||||
|         if isinstance(addr, tuple): |         if isinstance(addr, tuple): | ||||||
|             self.client = addr[0] if len(addr) == 2 else f"[{addr[0]}]" |             self.client = addr[0] if len(addr) == 2 else f"[{addr[0]}]" | ||||||
|             self.client_ip = addr[0] |  | ||||||
|             self.client_port = addr[1] |             self.client_port = addr[1] | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -125,6 +122,7 @@ class HttpProtocol(asyncio.Protocol): | |||||||
|         "response_timeout", |         "response_timeout", | ||||||
|         "keep_alive_timeout", |         "keep_alive_timeout", | ||||||
|         "request_max_size", |         "request_max_size", | ||||||
|  |         "request_buffer_queue_size", | ||||||
|         "request_class", |         "request_class", | ||||||
|         "error_handler", |         "error_handler", | ||||||
|         # enable or disable access log purpose |         # enable or disable access log purpose | ||||||
| @@ -167,6 +165,9 @@ class HttpProtocol(asyncio.Protocol): | |||||||
|         self.request_handler = self.app.handle_request |         self.request_handler = self.app.handle_request | ||||||
|         self.error_handler = self.app.error_handler |         self.error_handler = self.app.error_handler | ||||||
|         self.request_timeout = self.app.config.REQUEST_TIMEOUT |         self.request_timeout = self.app.config.REQUEST_TIMEOUT | ||||||
|  |         self.request_buffer_queue_size = ( | ||||||
|  |             self.app.config.REQUEST_BUFFER_QUEUE_SIZE | ||||||
|  |         ) | ||||||
|         self.response_timeout = self.app.config.RESPONSE_TIMEOUT |         self.response_timeout = self.app.config.RESPONSE_TIMEOUT | ||||||
|         self.keep_alive_timeout = self.app.config.KEEP_ALIVE_TIMEOUT |         self.keep_alive_timeout = self.app.config.KEEP_ALIVE_TIMEOUT | ||||||
|         self.request_max_size = self.app.config.REQUEST_MAX_SIZE |         self.request_max_size = self.app.config.REQUEST_MAX_SIZE | ||||||
| @@ -198,11 +199,11 @@ class HttpProtocol(asyncio.Protocol): | |||||||
|         except CancelledError: |         except CancelledError: | ||||||
|             pass |             pass | ||||||
|         except Exception: |         except Exception: | ||||||
|             error_logger.exception("protocol.connection_task uncaught") |             logger.exception("protocol.connection_task uncaught") | ||||||
|         finally: |         finally: | ||||||
|             if self.app.debug and self._http: |             if self.app.debug and self._http: | ||||||
|                 ip = self.transport.get_extra_info("peername") |                 ip = self.transport.get_extra_info("peername") | ||||||
|                 error_logger.error( |                 logger.error( | ||||||
|                     "Connection lost before response written" |                     "Connection lost before response written" | ||||||
|                     f" @ {ip} {self._http.request}" |                     f" @ {ip} {self._http.request}" | ||||||
|                 ) |                 ) | ||||||
| @@ -211,7 +212,7 @@ class HttpProtocol(asyncio.Protocol): | |||||||
|             try: |             try: | ||||||
|                 self.close() |                 self.close() | ||||||
|             except BaseException: |             except BaseException: | ||||||
|                 error_logger.exception("Closing failed") |                 logger.exception("Closing failed") | ||||||
|  |  | ||||||
|     async def receive_more(self): |     async def receive_more(self): | ||||||
|         """ |         """ | ||||||
| @@ -257,7 +258,7 @@ class HttpProtocol(asyncio.Protocol): | |||||||
|                 return |                 return | ||||||
|             self._task.cancel() |             self._task.cancel() | ||||||
|         except Exception: |         except Exception: | ||||||
|             error_logger.exception("protocol.check_timeouts") |             logger.exception("protocol.check_timeouts") | ||||||
|  |  | ||||||
|     async def send(self, data): |     async def send(self, data): | ||||||
|         """ |         """ | ||||||
| @@ -303,7 +304,7 @@ class HttpProtocol(asyncio.Protocol): | |||||||
|             self.recv_buffer = bytearray() |             self.recv_buffer = bytearray() | ||||||
|             self.conn_info = ConnInfo(self.transport, unix=self._unix) |             self.conn_info = ConnInfo(self.transport, unix=self._unix) | ||||||
|         except Exception: |         except Exception: | ||||||
|             error_logger.exception("protocol.connect_made") |             logger.exception("protocol.connect_made") | ||||||
|  |  | ||||||
|     def connection_lost(self, exc): |     def connection_lost(self, exc): | ||||||
|         try: |         try: | ||||||
| @@ -312,7 +313,7 @@ class HttpProtocol(asyncio.Protocol): | |||||||
|             if self._task: |             if self._task: | ||||||
|                 self._task.cancel() |                 self._task.cancel() | ||||||
|         except Exception: |         except Exception: | ||||||
|             error_logger.exception("protocol.connection_lost") |             logger.exception("protocol.connection_lost") | ||||||
|  |  | ||||||
|     def pause_writing(self): |     def pause_writing(self): | ||||||
|         self._can_write.clear() |         self._can_write.clear() | ||||||
| @@ -336,7 +337,7 @@ class HttpProtocol(asyncio.Protocol): | |||||||
|             if self._data_received: |             if self._data_received: | ||||||
|                 self._data_received.set() |                 self._data_received.set() | ||||||
|         except Exception: |         except Exception: | ||||||
|             error_logger.exception("protocol.data_received") |             logger.exception("protocol.data_received") | ||||||
|  |  | ||||||
|  |  | ||||||
| def trigger_events(events: Optional[Iterable[Callable[..., Any]]], loop): | def trigger_events(events: Optional[Iterable[Callable[..., Any]]], loop): | ||||||
| @@ -555,7 +556,7 @@ def serve( | |||||||
|     try: |     try: | ||||||
|         http_server = loop.run_until_complete(server_coroutine) |         http_server = loop.run_until_complete(server_coroutine) | ||||||
|     except BaseException: |     except BaseException: | ||||||
|         error_logger.exception("Unable to start server") |         logger.exception("Unable to start server") | ||||||
|         return |         return | ||||||
|  |  | ||||||
|     trigger_events(after_start, loop) |     trigger_events(after_start, loop) | ||||||
|   | |||||||
| @@ -48,7 +48,7 @@ class SignalRouter(BaseRouter): | |||||||
|                 f".{event}", |                 f".{event}", | ||||||
|                 self.DEFAULT_METHOD, |                 self.DEFAULT_METHOD, | ||||||
|                 self, |                 self, | ||||||
|                 {"__params__": {}, "__matches__": {}}, |                 {"__params__": {}}, | ||||||
|                 extra=extra, |                 extra=extra, | ||||||
|             ) |             ) | ||||||
|         except NotFound: |         except NotFound: | ||||||
| @@ -59,13 +59,7 @@ class SignalRouter(BaseRouter): | |||||||
|                 terms.append(extra) |                 terms.append(extra) | ||||||
|             raise NotFound(message % tuple(terms)) |             raise NotFound(message % tuple(terms)) | ||||||
|  |  | ||||||
|         params = param_basket["__params__"] |         params = param_basket.pop("__params__") | ||||||
|         if not params: |  | ||||||
|             params = { |  | ||||||
|                 param.name: param_basket["__matches__"][idx] |  | ||||||
|                 for idx, param in group.params.items() |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|         return group, [route.handler for route in group], params |         return group, [route.handler for route in group], params | ||||||
|  |  | ||||||
|     async def _dispatch( |     async def _dispatch( | ||||||
|   | |||||||
| @@ -1,21 +0,0 @@ | |||||||
| from pathlib import Path |  | ||||||
|  |  | ||||||
| from sanic import Sanic |  | ||||||
| from sanic.exceptions import SanicException |  | ||||||
| from sanic.response import redirect |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def create_simple_server(directory: Path): |  | ||||||
|     if not directory.is_dir(): |  | ||||||
|         raise SanicException( |  | ||||||
|             "Cannot setup Sanic Simple Server without a path to a directory" |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     app = Sanic("SimpleServer") |  | ||||||
|     app.static("/", directory, name="main") |  | ||||||
|  |  | ||||||
|     @app.get("/") |  | ||||||
|     def index(_): |  | ||||||
|         return redirect(app.url_for("main", filename="index.html")) |  | ||||||
|  |  | ||||||
|     return app |  | ||||||
| @@ -105,7 +105,6 @@ def load_module_from_file_location( | |||||||
|             _mod_spec = spec_from_file_location( |             _mod_spec = spec_from_file_location( | ||||||
|                 name, location, *args, **kwargs |                 name, location, *args, **kwargs | ||||||
|             ) |             ) | ||||||
|             assert _mod_spec is not None  # type assertion for mypy |  | ||||||
|             module = module_from_spec(_mod_spec) |             module = module_from_spec(_mod_spec) | ||||||
|             _mod_spec.loader.exec_module(module)  # type: ignore |             _mod_spec.loader.exec_module(module)  # type: ignore | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,25 +1,9 @@ | |||||||
| from __future__ import annotations | from typing import Any, Callable, List | ||||||
|  |  | ||||||
| from typing import ( |  | ||||||
|     TYPE_CHECKING, |  | ||||||
|     Any, |  | ||||||
|     Callable, |  | ||||||
|     Iterable, |  | ||||||
|     List, |  | ||||||
|     Optional, |  | ||||||
|     Union, |  | ||||||
| ) |  | ||||||
| from warnings import warn |  | ||||||
|  |  | ||||||
| from sanic.constants import HTTP_METHODS | from sanic.constants import HTTP_METHODS | ||||||
| from sanic.exceptions import InvalidUsage | from sanic.exceptions import InvalidUsage | ||||||
|  |  | ||||||
|  |  | ||||||
| if TYPE_CHECKING: |  | ||||||
|     from sanic import Sanic |  | ||||||
|     from sanic.blueprints import Blueprint |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class HTTPMethodView: | class HTTPMethodView: | ||||||
|     """Simple class based implementation of view for the sanic. |     """Simple class based implementation of view for the sanic. | ||||||
|     You should implement methods (get, post, put, patch, delete) for the class |     You should implement methods (get, post, put, patch, delete) for the class | ||||||
| @@ -56,31 +40,6 @@ class HTTPMethodView: | |||||||
|  |  | ||||||
|     decorators: List[Callable[[Callable[..., Any]], Callable[..., Any]]] = [] |     decorators: List[Callable[[Callable[..., Any]], Callable[..., Any]]] = [] | ||||||
|  |  | ||||||
|     def __init_subclass__( |  | ||||||
|         cls, |  | ||||||
|         attach: Optional[Union[Sanic, Blueprint]] = None, |  | ||||||
|         uri: str = "", |  | ||||||
|         methods: Iterable[str] = frozenset({"GET"}), |  | ||||||
|         host: Optional[str] = None, |  | ||||||
|         strict_slashes: Optional[bool] = None, |  | ||||||
|         version: Optional[int] = None, |  | ||||||
|         name: Optional[str] = None, |  | ||||||
|         stream: bool = False, |  | ||||||
|         version_prefix: str = "/v", |  | ||||||
|     ) -> None: |  | ||||||
|         if attach: |  | ||||||
|             cls.attach( |  | ||||||
|                 attach, |  | ||||||
|                 uri=uri, |  | ||||||
|                 methods=methods, |  | ||||||
|                 host=host, |  | ||||||
|                 strict_slashes=strict_slashes, |  | ||||||
|                 version=version, |  | ||||||
|                 name=name, |  | ||||||
|                 stream=stream, |  | ||||||
|                 version_prefix=version_prefix, |  | ||||||
|             ) |  | ||||||
|  |  | ||||||
|     def dispatch_request(self, request, *args, **kwargs): |     def dispatch_request(self, request, *args, **kwargs): | ||||||
|         handler = getattr(self, request.method.lower(), None) |         handler = getattr(self, request.method.lower(), None) | ||||||
|         return handler(request, *args, **kwargs) |         return handler(request, *args, **kwargs) | ||||||
| @@ -106,31 +65,6 @@ class HTTPMethodView: | |||||||
|         view.__name__ = cls.__name__ |         view.__name__ = cls.__name__ | ||||||
|         return view |         return view | ||||||
|  |  | ||||||
|     @classmethod |  | ||||||
|     def attach( |  | ||||||
|         cls, |  | ||||||
|         to: Union[Sanic, Blueprint], |  | ||||||
|         uri: str, |  | ||||||
|         methods: Iterable[str] = frozenset({"GET"}), |  | ||||||
|         host: Optional[str] = None, |  | ||||||
|         strict_slashes: Optional[bool] = None, |  | ||||||
|         version: Optional[int] = None, |  | ||||||
|         name: Optional[str] = None, |  | ||||||
|         stream: bool = False, |  | ||||||
|         version_prefix: str = "/v", |  | ||||||
|     ) -> None: |  | ||||||
|         to.add_route( |  | ||||||
|             cls.as_view(), |  | ||||||
|             uri=uri, |  | ||||||
|             methods=methods, |  | ||||||
|             host=host, |  | ||||||
|             strict_slashes=strict_slashes, |  | ||||||
|             version=version, |  | ||||||
|             name=name, |  | ||||||
|             stream=stream, |  | ||||||
|             version_prefix=version_prefix, |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def stream(func): | def stream(func): | ||||||
|     func.is_stream = True |     func.is_stream = True | ||||||
| @@ -157,11 +91,6 @@ class CompositionView: | |||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         self.handlers = {} |         self.handlers = {} | ||||||
|         self.name = self.__class__.__name__ |         self.name = self.__class__.__name__ | ||||||
|         warn( |  | ||||||
|             "CompositionView has been deprecated and will be removed in " |  | ||||||
|             "v21.12. Please update your view to HTTPMethodView.", |  | ||||||
|             DeprecationWarning, |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     def __name__(self): |     def __name__(self): | ||||||
|         return self.name |         return self.name | ||||||
|   | |||||||
| @@ -14,13 +14,9 @@ from websockets import (  # type: ignore | |||||||
|     ConnectionClosed, |     ConnectionClosed, | ||||||
|     InvalidHandshake, |     InvalidHandshake, | ||||||
|     WebSocketCommonProtocol, |     WebSocketCommonProtocol, | ||||||
|  |     handshake, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| # Despite the "legacy" namespace, the primary maintainer of websockets |  | ||||||
| # committed to maintaining backwards-compatibility until 2026 and will |  | ||||||
| # consider extending it if sanic continues depending on this module. |  | ||||||
| from websockets.legacy import handshake |  | ||||||
|  |  | ||||||
| from sanic.exceptions import InvalidUsage | from sanic.exceptions import InvalidUsage | ||||||
| from sanic.server import HttpProtocol | from sanic.server import HttpProtocol | ||||||
|  |  | ||||||
| @@ -41,7 +37,7 @@ class WebSocketProtocol(HttpProtocol): | |||||||
|         websocket_write_limit=2 ** 16, |         websocket_write_limit=2 ** 16, | ||||||
|         websocket_ping_interval=20, |         websocket_ping_interval=20, | ||||||
|         websocket_ping_timeout=20, |         websocket_ping_timeout=20, | ||||||
|         **kwargs, |         **kwargs | ||||||
|     ): |     ): | ||||||
|         super().__init__(*args, **kwargs) |         super().__init__(*args, **kwargs) | ||||||
|         self.websocket = None |         self.websocket = None | ||||||
| @@ -130,9 +126,7 @@ class WebSocketProtocol(HttpProtocol): | |||||||
|             ping_interval=self.websocket_ping_interval, |             ping_interval=self.websocket_ping_interval, | ||||||
|             ping_timeout=self.websocket_ping_timeout, |             ping_timeout=self.websocket_ping_timeout, | ||||||
|         ) |         ) | ||||||
|         # we use WebSocketCommonProtocol because we don't want the handshake |         # Following two lines are required for websockets 8.x | ||||||
|         # logic from WebSocketServerProtocol; however, we must tell it that |  | ||||||
|         # we're running on the server side |  | ||||||
|         self.websocket.is_client = False |         self.websocket.is_client = False | ||||||
|         self.websocket.side = "server" |         self.websocket.side = "server" | ||||||
|         self.websocket.subprotocol = subprotocol |         self.websocket.subprotocol = subprotocol | ||||||
| @@ -154,7 +148,7 @@ class WebSocketConnection: | |||||||
|     ) -> None: |     ) -> None: | ||||||
|         self._send = send |         self._send = send | ||||||
|         self._receive = receive |         self._receive = receive | ||||||
|         self._subprotocols = subprotocols or [] |         self.subprotocols = subprotocols or [] | ||||||
|  |  | ||||||
|     async def send(self, data: Union[str, bytes], *args, **kwargs) -> None: |     async def send(self, data: Union[str, bytes], *args, **kwargs) -> None: | ||||||
|         message: Dict[str, Union[str, bytes]] = {"type": "websocket.send"} |         message: Dict[str, Union[str, bytes]] = {"type": "websocket.send"} | ||||||
| @@ -178,28 +172,13 @@ class WebSocketConnection: | |||||||
|  |  | ||||||
|     receive = recv |     receive = recv | ||||||
|  |  | ||||||
|     async def accept(self, subprotocols: Optional[List[str]] = None) -> None: |     async def accept(self) -> None: | ||||||
|         subprotocol = None |  | ||||||
|         if subprotocols: |  | ||||||
|             for subp in subprotocols: |  | ||||||
|                 if subp in self.subprotocols: |  | ||||||
|                     subprotocol = subp |  | ||||||
|                     break |  | ||||||
|  |  | ||||||
|         await self._send( |         await self._send( | ||||||
|             { |             { | ||||||
|                 "type": "websocket.accept", |                 "type": "websocket.accept", | ||||||
|                 "subprotocol": subprotocol, |                 "subprotocol": ",".join(list(self.subprotocols)), | ||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     async def close(self) -> None: |     async def close(self) -> None: | ||||||
|         pass |         pass | ||||||
|  |  | ||||||
|     @property |  | ||||||
|     def subprotocols(self): |  | ||||||
|         return self._subprotocols |  | ||||||
|  |  | ||||||
|     @subprotocols.setter |  | ||||||
|     def subprotocols(self, subprotocols: Optional[List[str]] = None): |  | ||||||
|         self._subprotocols = subprotocols or [] |  | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								setup.py
									
									
									
									
									
								
							| @@ -83,17 +83,17 @@ ujson = "ujson>=1.35" + env_dependency | |||||||
| uvloop = "uvloop>=0.5.3" + env_dependency | uvloop = "uvloop>=0.5.3" + env_dependency | ||||||
|  |  | ||||||
| requirements = [ | requirements = [ | ||||||
|     "sanic-routing~=0.7", |     "sanic-routing>=0.6.0", | ||||||
|     "httptools>=0.0.10", |     "httptools>=0.0.10", | ||||||
|     uvloop, |     uvloop, | ||||||
|     ujson, |     ujson, | ||||||
|     "aiofiles>=0.6.0", |     "aiofiles>=0.6.0", | ||||||
|     "websockets>=9.0", |     "websockets>=8.1,<9.0", | ||||||
|     "multidict>=5.0,<6.0", |     "multidict>=5.0,<6.0", | ||||||
| ] | ] | ||||||
|  |  | ||||||
| tests_require = [ | tests_require = [ | ||||||
|     "sanic-testing>=0.7.0b1", |     "sanic-testing", | ||||||
|     "pytest==5.2.1", |     "pytest==5.2.1", | ||||||
|     "multidict>=5.0,<6.0", |     "multidict>=5.0,<6.0", | ||||||
|     "gunicorn==20.0.4", |     "gunicorn==20.0.4", | ||||||
|   | |||||||
| @@ -15,7 +15,6 @@ from sanic.constants import HTTP_METHODS | |||||||
| from sanic.router import Router | from sanic.router import Router | ||||||
|  |  | ||||||
|  |  | ||||||
| slugify = re.compile(r"[^a-zA-Z0-9_\-]") |  | ||||||
| random.seed("Pack my box with five dozen liquor jugs.") | random.seed("Pack my box with five dozen liquor jugs.") | ||||||
| Sanic.test_mode = True | Sanic.test_mode = True | ||||||
|  |  | ||||||
| @@ -141,5 +140,5 @@ def url_param_generator(): | |||||||
|  |  | ||||||
| @pytest.fixture(scope="function") | @pytest.fixture(scope="function") | ||||||
| def app(request): | def app(request): | ||||||
|     app = Sanic(slugify.sub("-", request.node.name)) |     app = Sanic(request.node.name) | ||||||
|     return app |     return app | ||||||
|   | |||||||
| @@ -1,36 +0,0 @@ | |||||||
| import json |  | ||||||
| import logging |  | ||||||
|  |  | ||||||
| from sanic import Sanic, text |  | ||||||
| from sanic.log import LOGGING_CONFIG_DEFAULTS, logger |  | ||||||
|  |  | ||||||
|  |  | ||||||
| LOGGING_CONFIG = {**LOGGING_CONFIG_DEFAULTS} |  | ||||||
| LOGGING_CONFIG["formatters"]["generic"]["format"] = "%(message)s" |  | ||||||
| LOGGING_CONFIG["loggers"]["sanic.root"]["level"] = "DEBUG" |  | ||||||
|  |  | ||||||
| app = Sanic(__name__, log_config=LOGGING_CONFIG) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @app.get("/") |  | ||||||
| async def handler(request): |  | ||||||
|     return text(request.ip) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @app.before_server_start |  | ||||||
| async def app_info_dump(app: Sanic, _): |  | ||||||
|     app_data = { |  | ||||||
|         "access_log": app.config.ACCESS_LOG, |  | ||||||
|         "auto_reload": app.auto_reload, |  | ||||||
|         "debug": app.debug, |  | ||||||
|     } |  | ||||||
|     logger.info(json.dumps(app_data)) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @app.after_server_start |  | ||||||
| async def shutdown(app: Sanic, _): |  | ||||||
|     app.stop() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def create_app(): |  | ||||||
|     return app |  | ||||||
| @@ -9,7 +9,6 @@ from unittest.mock import Mock, patch | |||||||
| import pytest | import pytest | ||||||
|  |  | ||||||
| from sanic import Sanic | from sanic import Sanic | ||||||
| from sanic.config import Config |  | ||||||
| from sanic.exceptions import SanicException | from sanic.exceptions import SanicException | ||||||
| from sanic.response import text | from sanic.response import text | ||||||
|  |  | ||||||
| @@ -277,7 +276,7 @@ def test_handle_request_with_nested_sanic_exception(app, monkeypatch, caplog): | |||||||
|     assert response.status == 500 |     assert response.status == 500 | ||||||
|     assert "Mock SanicException" in response.text |     assert "Mock SanicException" in response.text | ||||||
|     assert ( |     assert ( | ||||||
|         "sanic.error", |         "sanic.root", | ||||||
|         logging.ERROR, |         logging.ERROR, | ||||||
|         f"Exception occurred while handling uri: 'http://127.0.0.1:{port}/'", |         f"Exception occurred while handling uri: 'http://127.0.0.1:{port}/'", | ||||||
|     ) in caplog.record_tuples |     ) in caplog.record_tuples | ||||||
| @@ -390,7 +389,7 @@ def test_app_no_registry_env(): | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_app_set_attribute_warning(app): | def test_app_set_attribute_warning(app): | ||||||
|     with pytest.warns(DeprecationWarning) as record: |     with pytest.warns(UserWarning) as record: | ||||||
|         app.foo = 1 |         app.foo = 1 | ||||||
|  |  | ||||||
|     assert len(record) == 1 |     assert len(record) == 1 | ||||||
| @@ -413,42 +412,3 @@ def test_subclass_initialisation(): | |||||||
|         pass |         pass | ||||||
|  |  | ||||||
|     CustomSanic("test_subclass_initialisation") |     CustomSanic("test_subclass_initialisation") | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_bad_custom_config(): |  | ||||||
|     with pytest.raises( |  | ||||||
|         SanicException, |  | ||||||
|         match=( |  | ||||||
|             "When instantiating Sanic with config, you cannot also pass " |  | ||||||
|             "load_env or env_prefix" |  | ||||||
|         ), |  | ||||||
|     ): |  | ||||||
|         Sanic("test", config=1, load_env=1) |  | ||||||
|     with pytest.raises( |  | ||||||
|         SanicException, |  | ||||||
|         match=( |  | ||||||
|             "When instantiating Sanic with config, you cannot also pass " |  | ||||||
|             "load_env or env_prefix" |  | ||||||
|         ), |  | ||||||
|     ): |  | ||||||
|         Sanic("test", config=1, env_prefix=1) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_custom_config(): |  | ||||||
|     class CustomConfig(Config): |  | ||||||
|         ... |  | ||||||
|  |  | ||||||
|     config = CustomConfig() |  | ||||||
|     app = Sanic("custom", config=config) |  | ||||||
|  |  | ||||||
|     assert app.config == config |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_custom_context(): |  | ||||||
|     class CustomContext: |  | ||||||
|         ... |  | ||||||
|  |  | ||||||
|     ctx = CustomContext() |  | ||||||
|     app = Sanic("custom", ctx=ctx) |  | ||||||
|  |  | ||||||
|     assert app.ctx == ctx |  | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| import asyncio | import asyncio | ||||||
|  | import sys | ||||||
|  |  | ||||||
| from collections import deque, namedtuple | from collections import deque, namedtuple | ||||||
|  |  | ||||||
| @@ -7,7 +8,7 @@ import uvicorn | |||||||
|  |  | ||||||
| from sanic import Sanic | from sanic import Sanic | ||||||
| from sanic.asgi import MockTransport | from sanic.asgi import MockTransport | ||||||
| from sanic.exceptions import Forbidden, InvalidUsage, ServiceUnavailable | from sanic.exceptions import InvalidUsage | ||||||
| from sanic.request import Request | from sanic.request import Request | ||||||
| from sanic.response import json, text | from sanic.response import json, text | ||||||
| from sanic.websocket import WebSocketConnection | from sanic.websocket import WebSocketConnection | ||||||
| @@ -218,7 +219,7 @@ async def test_websocket_accept_with_no_subprotocols( | |||||||
|  |  | ||||||
|     message = message_stack.popleft() |     message = message_stack.popleft() | ||||||
|     assert message["type"] == "websocket.accept" |     assert message["type"] == "websocket.accept" | ||||||
|     assert message["subprotocol"] is None |     assert message["subprotocol"] == "" | ||||||
|     assert "bytes" not in message |     assert "bytes" not in message | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -227,7 +228,7 @@ async def test_websocket_accept_with_subprotocol(send, receive, message_stack): | |||||||
|     subprotocols = ["graphql-ws"] |     subprotocols = ["graphql-ws"] | ||||||
|  |  | ||||||
|     ws = WebSocketConnection(send, receive, subprotocols) |     ws = WebSocketConnection(send, receive, subprotocols) | ||||||
|     await ws.accept(subprotocols) |     await ws.accept() | ||||||
|  |  | ||||||
|     assert len(message_stack) == 1 |     assert len(message_stack) == 1 | ||||||
|  |  | ||||||
| @@ -244,13 +245,13 @@ async def test_websocket_accept_with_multiple_subprotocols( | |||||||
|     subprotocols = ["graphql-ws", "hello", "world"] |     subprotocols = ["graphql-ws", "hello", "world"] | ||||||
|  |  | ||||||
|     ws = WebSocketConnection(send, receive, subprotocols) |     ws = WebSocketConnection(send, receive, subprotocols) | ||||||
|     await ws.accept(["hello", "world"]) |     await ws.accept() | ||||||
|  |  | ||||||
|     assert len(message_stack) == 1 |     assert len(message_stack) == 1 | ||||||
|  |  | ||||||
|     message = message_stack.popleft() |     message = message_stack.popleft() | ||||||
|     assert message["type"] == "websocket.accept" |     assert message["type"] == "websocket.accept" | ||||||
|     assert message["subprotocol"] == "hello" |     assert message["subprotocol"] == "graphql-ws,hello,world" | ||||||
|     assert "bytes" not in message |     assert "bytes" not in message | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -346,32 +347,3 @@ async def test_content_type(app): | |||||||
|  |  | ||||||
|     _, response = await app.asgi_client.get("/custom") |     _, response = await app.asgi_client.get("/custom") | ||||||
|     assert response.headers.get("content-type") == "somethingelse" |     assert response.headers.get("content-type") == "somethingelse" | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.asyncio |  | ||||||
| async def test_request_handle_exception(app): |  | ||||||
|     @app.get("/error-prone") |  | ||||||
|     def _request(request): |  | ||||||
|         raise ServiceUnavailable(message="Service unavailable") |  | ||||||
|  |  | ||||||
|     _, response = await app.asgi_client.get("/wrong-path") |  | ||||||
|     assert response.status_code == 404 |  | ||||||
|  |  | ||||||
|     _, response = await app.asgi_client.get("/error-prone") |  | ||||||
|     assert response.status_code == 503 |  | ||||||
|  |  | ||||||
| @pytest.mark.asyncio |  | ||||||
| async def test_request_exception_suppressed_by_middleware(app): |  | ||||||
|     @app.get("/error-prone") |  | ||||||
|     def _request(request): |  | ||||||
|         raise ServiceUnavailable(message="Service unavailable") |  | ||||||
|  |  | ||||||
|     @app.on_request |  | ||||||
|     def forbidden(request): |  | ||||||
|         raise Forbidden(message="forbidden") |  | ||||||
|  |  | ||||||
|     _, response = await app.asgi_client.get("/wrong-path") |  | ||||||
|     assert response.status_code == 403 |  | ||||||
|  |  | ||||||
|     _, response = await app.asgi_client.get("/error-prone") |  | ||||||
|     assert response.status_code == 403 |  | ||||||
| @@ -41,62 +41,3 @@ def test_bp_repr_with_values(bp): | |||||||
|         'Blueprint(name="my_bp", url_prefix="/foo", host="example.com", ' |         'Blueprint(name="my_bp", url_prefix="/foo", host="example.com", ' | ||||||
|         "version=3, strict_slashes=True)" |         "version=3, strict_slashes=True)" | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize( |  | ||||||
|     "name", |  | ||||||
|     ( |  | ||||||
|         "something", |  | ||||||
|         "some-thing", |  | ||||||
|         "some_thing", |  | ||||||
|         "Something", |  | ||||||
|         "SomeThing", |  | ||||||
|         "Some-Thing", |  | ||||||
|         "Some_Thing", |  | ||||||
|         "SomeThing123", |  | ||||||
|         "something123", |  | ||||||
|         "some-thing123", |  | ||||||
|         "some_thing123", |  | ||||||
|         "some-Thing123", |  | ||||||
|         "some_Thing123", |  | ||||||
|     ), |  | ||||||
| ) |  | ||||||
| def test_names_okay(name): |  | ||||||
|     app = Sanic(name) |  | ||||||
|     bp = Blueprint(name) |  | ||||||
|  |  | ||||||
|     assert app.name == name |  | ||||||
|     assert bp.name == name |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize( |  | ||||||
|     "name", |  | ||||||
|     ( |  | ||||||
|         "123something", |  | ||||||
|         "some thing", |  | ||||||
|         "something!", |  | ||||||
|     ), |  | ||||||
| ) |  | ||||||
| def test_names_not_okay(name): |  | ||||||
|     app_message = ( |  | ||||||
|         f"Sanic instance named '{name}' uses a format that isdeprecated. " |  | ||||||
|         "Starting in version 21.12, Sanic objects must be named only using " |  | ||||||
|         "alphanumeric characters, _, or -." |  | ||||||
|     ) |  | ||||||
|     bp_message = ( |  | ||||||
|         f"Blueprint instance named '{name}' uses a format that isdeprecated. " |  | ||||||
|         "Starting in version 21.12, Blueprint objects must be named only using " |  | ||||||
|         "alphanumeric characters, _, or -." |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     with pytest.warns(DeprecationWarning) as app_e: |  | ||||||
|         app = Sanic(name) |  | ||||||
|  |  | ||||||
|     with pytest.warns(DeprecationWarning) as bp_e: |  | ||||||
|         bp = Blueprint(name) |  | ||||||
|  |  | ||||||
|     assert app.name == name |  | ||||||
|     assert bp.name == name |  | ||||||
|  |  | ||||||
|     assert app_e[0].message.args[0] == app_message |  | ||||||
|     assert bp_e[0].message.args[0] == bp_message |  | ||||||
|   | |||||||
| @@ -200,7 +200,7 @@ def test_bp_group_as_nested_group(): | |||||||
|     blueprint_group_1 = Blueprint.group( |     blueprint_group_1 = Blueprint.group( | ||||||
|         Blueprint.group(blueprint_1, blueprint_2) |         Blueprint.group(blueprint_1, blueprint_2) | ||||||
|     ) |     ) | ||||||
|     assert len(blueprint_group_1) == 1 |     assert len(blueprint_group_1) == 2 | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_blueprint_group_insert(): | def test_blueprint_group_insert(): | ||||||
| @@ -215,61 +215,6 @@ def test_blueprint_group_insert(): | |||||||
|     group.insert(0, blueprint_1) |     group.insert(0, blueprint_1) | ||||||
|     group.insert(0, blueprint_2) |     group.insert(0, blueprint_2) | ||||||
|     group.insert(0, blueprint_3) |     group.insert(0, blueprint_3) | ||||||
|  |     assert group.blueprints[1].strict_slashes is False | ||||||
|     @blueprint_1.route("/") |     assert group.blueprints[2].strict_slashes is True | ||||||
|     def blueprint_1_default_route(request): |     assert group.blueprints[0].url_prefix == "/test" | ||||||
|         return text("BP1_OK") |  | ||||||
|  |  | ||||||
|     @blueprint_2.route("/") |  | ||||||
|     def blueprint_2_default_route(request): |  | ||||||
|         return text("BP2_OK") |  | ||||||
|  |  | ||||||
|     @blueprint_3.route("/") |  | ||||||
|     def blueprint_3_default_route(request): |  | ||||||
|         return text("BP3_OK") |  | ||||||
|  |  | ||||||
|     app = Sanic("PropTest") |  | ||||||
|     app.blueprint(group) |  | ||||||
|     app.router.finalize() |  | ||||||
|  |  | ||||||
|     routes = [(route.path, route.strict) for route in app.router.routes] |  | ||||||
|  |  | ||||||
|     assert len(routes) == 3 |  | ||||||
|     assert ("v1/test/bp1/", True) in routes |  | ||||||
|     assert ("v1.3/test/bp2", False) in routes |  | ||||||
|     assert ("v1.3/test", False) in routes |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_bp_group_properties(): |  | ||||||
|     blueprint_1 = Blueprint("blueprint_1", url_prefix="/bp1") |  | ||||||
|     blueprint_2 = Blueprint("blueprint_2", url_prefix="/bp2") |  | ||||||
|     group = Blueprint.group( |  | ||||||
|         blueprint_1, |  | ||||||
|         blueprint_2, |  | ||||||
|         version=1, |  | ||||||
|         version_prefix="/api/v", |  | ||||||
|         url_prefix="/grouped", |  | ||||||
|         strict_slashes=True, |  | ||||||
|     ) |  | ||||||
|     primary = Blueprint.group(group, url_prefix="/primary") |  | ||||||
|  |  | ||||||
|     @blueprint_1.route("/") |  | ||||||
|     def blueprint_1_default_route(request): |  | ||||||
|         return text("BP1_OK") |  | ||||||
|  |  | ||||||
|     @blueprint_2.route("/") |  | ||||||
|     def blueprint_2_default_route(request): |  | ||||||
|         return text("BP2_OK") |  | ||||||
|  |  | ||||||
|     app = Sanic("PropTest") |  | ||||||
|     app.blueprint(group) |  | ||||||
|     app.blueprint(primary) |  | ||||||
|     app.router.finalize() |  | ||||||
|  |  | ||||||
|     routes = [route.path for route in app.router.routes] |  | ||||||
|  |  | ||||||
|     assert len(routes) == 4 |  | ||||||
|     assert "api/v1/grouped/bp1/" in routes |  | ||||||
|     assert "api/v1/grouped/bp2/" in routes |  | ||||||
|     assert "api/v1/primary/grouped/bp1" in routes |  | ||||||
|     assert "api/v1/primary/grouped/bp2" in routes |  | ||||||
|   | |||||||
| @@ -1028,7 +1028,7 @@ def test_blueprint_registered_multiple_apps(): | |||||||
|  |  | ||||||
| def test_bp_set_attribute_warning(): | def test_bp_set_attribute_warning(): | ||||||
|     bp = Blueprint("bp") |     bp = Blueprint("bp") | ||||||
|     with pytest.warns(DeprecationWarning) as record: |     with pytest.warns(UserWarning) as record: | ||||||
|         bp.foo = 1 |         bp.foo = 1 | ||||||
|  |  | ||||||
|     assert len(record) == 1 |     assert len(record) == 1 | ||||||
|   | |||||||
| @@ -1,133 +0,0 @@ | |||||||
| import json |  | ||||||
| import subprocess |  | ||||||
|  |  | ||||||
| from pathlib import Path |  | ||||||
|  |  | ||||||
| import pytest |  | ||||||
|  |  | ||||||
| from sanic_routing import __version__ as __routing_version__ |  | ||||||
|  |  | ||||||
| from sanic import __version__ |  | ||||||
| from sanic.config import BASE_LOGO |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def capture(command): |  | ||||||
|     proc = subprocess.Popen( |  | ||||||
|         command, |  | ||||||
|         stdout=subprocess.PIPE, |  | ||||||
|         stderr=subprocess.PIPE, |  | ||||||
|         cwd=Path(__file__).parent, |  | ||||||
|     ) |  | ||||||
|     try: |  | ||||||
|         out, err = proc.communicate(timeout=0.5) |  | ||||||
|     except subprocess.TimeoutExpired: |  | ||||||
|         proc.kill() |  | ||||||
|         out, err = proc.communicate() |  | ||||||
|     return out, err, proc.returncode |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize( |  | ||||||
|     "appname", |  | ||||||
|     ( |  | ||||||
|         "fake.server.app", |  | ||||||
|         "fake.server:app", |  | ||||||
|         "fake.server:create_app()", |  | ||||||
|         "fake.server.create_app()", |  | ||||||
|     ), |  | ||||||
| ) |  | ||||||
| def test_server_run(appname): |  | ||||||
|     command = ["sanic", appname] |  | ||||||
|     out, err, exitcode = capture(command) |  | ||||||
|     lines = out.split(b"\n") |  | ||||||
|     firstline = lines[6] |  | ||||||
|  |  | ||||||
|     assert exitcode != 1 |  | ||||||
|     assert firstline == b"Goin' Fast @ http://127.0.0.1:8000" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize( |  | ||||||
|     "cmd", |  | ||||||
|     ( |  | ||||||
|         ("--host=localhost", "--port=9999"), |  | ||||||
|         ("-H", "localhost", "-p", "9999"), |  | ||||||
|     ), |  | ||||||
| ) |  | ||||||
| def test_host_port(cmd): |  | ||||||
|     command = ["sanic", "fake.server.app", *cmd] |  | ||||||
|     out, err, exitcode = capture(command) |  | ||||||
|     lines = out.split(b"\n") |  | ||||||
|     firstline = lines[6] |  | ||||||
|  |  | ||||||
|     assert exitcode != 1 |  | ||||||
|     assert firstline == b"Goin' Fast @ http://localhost:9999" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize( |  | ||||||
|     "num,cmd", |  | ||||||
|     ( |  | ||||||
|         (1, (f"--workers={1}",)), |  | ||||||
|         (2, (f"--workers={2}",)), |  | ||||||
|         (4, (f"--workers={4}",)), |  | ||||||
|         (1, ("-w", "1")), |  | ||||||
|         (2, ("-w", "2")), |  | ||||||
|         (4, ("-w", "4")), |  | ||||||
|     ), |  | ||||||
| ) |  | ||||||
| def test_num_workers(num, cmd): |  | ||||||
|     command = ["sanic", "fake.server.app", *cmd] |  | ||||||
|     out, err, exitcode = capture(command) |  | ||||||
|     lines = out.split(b"\n") |  | ||||||
|  |  | ||||||
|     worker_lines = [line for line in lines if b"worker" in line] |  | ||||||
|     assert exitcode != 1 |  | ||||||
|     assert len(worker_lines) == num * 2 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize("cmd", ("--debug", "-d")) |  | ||||||
| def test_debug(cmd): |  | ||||||
|     command = ["sanic", "fake.server.app", cmd] |  | ||||||
|     out, err, exitcode = capture(command) |  | ||||||
|     lines = out.split(b"\n") |  | ||||||
|  |  | ||||||
|     app_info = lines[9] |  | ||||||
|     info = json.loads(app_info) |  | ||||||
|  |  | ||||||
|     assert (b"\n".join(lines[:6])).decode("utf-8") == BASE_LOGO |  | ||||||
|     assert info["debug"] is True |  | ||||||
|     assert info["auto_reload"] is True |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize("cmd", ("--auto-reload", "-r")) |  | ||||||
| def test_auto_reload(cmd): |  | ||||||
|     command = ["sanic", "fake.server.app", cmd] |  | ||||||
|     out, err, exitcode = capture(command) |  | ||||||
|     lines = out.split(b"\n") |  | ||||||
|  |  | ||||||
|     app_info = lines[9] |  | ||||||
|     info = json.loads(app_info) |  | ||||||
|  |  | ||||||
|     assert info["debug"] is False |  | ||||||
|     assert info["auto_reload"] is True |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize( |  | ||||||
|     "cmd,expected", (("--access-log", True), ("--no-access-log", False)) |  | ||||||
| ) |  | ||||||
| def test_access_logs(cmd, expected): |  | ||||||
|     command = ["sanic", "fake.server.app", cmd] |  | ||||||
|     out, err, exitcode = capture(command) |  | ||||||
|     lines = out.split(b"\n") |  | ||||||
|  |  | ||||||
|     app_info = lines[9] |  | ||||||
|     info = json.loads(app_info) |  | ||||||
|  |  | ||||||
|     assert info["access_log"] is expected |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize("cmd", ("--version", "-v")) |  | ||||||
| def test_version(cmd): |  | ||||||
|     command = ["sanic", cmd] |  | ||||||
|     out, err, exitcode = capture(command) |  | ||||||
|     version_string = f"Sanic {__version__}; Routing {__routing_version__}\n" |  | ||||||
|  |  | ||||||
|     assert out == version_string.encode("utf-8") |  | ||||||
| @@ -59,14 +59,14 @@ def test_load_from_object_string_exception(app): | |||||||
|         app.config.load("test_config.Config.test") |         app.config.load("test_config.Config.test") | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_auto_env_prefix(): | def test_auto_load_env(): | ||||||
|     environ["SANIC_TEST_ANSWER"] = "42" |     environ["SANIC_TEST_ANSWER"] = "42" | ||||||
|     app = Sanic(name=__name__) |     app = Sanic(name=__name__) | ||||||
|     assert app.config.TEST_ANSWER == 42 |     assert app.config.TEST_ANSWER == 42 | ||||||
|     del environ["SANIC_TEST_ANSWER"] |     del environ["SANIC_TEST_ANSWER"] | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_auto_bool_env_prefix(): | def test_auto_load_bool_env(): | ||||||
|     environ["SANIC_TEST_ANSWER"] = "True" |     environ["SANIC_TEST_ANSWER"] = "True" | ||||||
|     app = Sanic(name=__name__) |     app = Sanic(name=__name__) | ||||||
|     assert app.config.TEST_ANSWER is True |     assert app.config.TEST_ANSWER is True | ||||||
| @@ -80,10 +80,10 @@ def test_dont_load_env(): | |||||||
|     del environ["SANIC_TEST_ANSWER"] |     del environ["SANIC_TEST_ANSWER"] | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize("load_env", [None, False, "", "MYAPP_"]) | # @pytest.mark.parametrize("load_env", [None, False, "", "MYAPP_"]) | ||||||
| def test_load_env_deprecation(load_env): | # def test_load_env_deprecation(load_env): | ||||||
|     with pytest.warns(DeprecationWarning, match=r"21\.12"): | #     with pytest.warns(DeprecationWarning, match=r"21\.12"): | ||||||
|         _ = Sanic(name=__name__, load_env=load_env) | #         _ = Sanic(name=__name__, load_env=load_env) | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_load_env_prefix(): | def test_load_env_prefix(): | ||||||
| @@ -93,12 +93,12 @@ def test_load_env_prefix(): | |||||||
|     del environ["MYAPP_TEST_ANSWER"] |     del environ["MYAPP_TEST_ANSWER"] | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize("env_prefix", [None, ""]) | # @pytest.mark.parametrize("env_prefix", [None, ""]) | ||||||
| def test_empty_load_env_prefix(env_prefix): | # def test_empty_load_env_prefix(env_prefix): | ||||||
|     environ["SANIC_TEST_ANSWER"] = "42" | #     environ["SANIC_TEST_ANSWER"] = "42" | ||||||
|     app = Sanic(name=__name__, env_prefix=env_prefix) | #     app = Sanic(name=__name__, env_prefix=env_prefix) | ||||||
|     assert getattr(app.config, "TEST_ANSWER", None) is None | #     assert getattr(app.config, "TEST_ANSWER", None) is None | ||||||
|     del environ["SANIC_TEST_ANSWER"] | #     del environ["SANIC_TEST_ANSWER"] | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_load_env_prefix_float_values(): | def test_load_env_prefix_float_values(): | ||||||
| @@ -115,27 +115,6 @@ def test_load_env_prefix_string_value(): | |||||||
|     del environ["MYAPP_TEST_TOKEN"] |     del environ["MYAPP_TEST_TOKEN"] | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_env_prefix(): |  | ||||||
|     environ["MYAPP_TEST_ANSWER"] = "42" |  | ||||||
|     app = Sanic(name=__name__, env_prefix="MYAPP_") |  | ||||||
|     assert app.config.TEST_ANSWER == 42 |  | ||||||
|     del environ["MYAPP_TEST_ANSWER"] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_env_prefix_float_values(): |  | ||||||
|     environ["MYAPP_TEST_ROI"] = "2.3" |  | ||||||
|     app = Sanic(name=__name__, env_prefix="MYAPP_") |  | ||||||
|     assert app.config.TEST_ROI == 2.3 |  | ||||||
|     del environ["MYAPP_TEST_ROI"] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_env_prefix_string_value(): |  | ||||||
|     environ["MYAPP_TEST_TOKEN"] = "somerandomtesttoken" |  | ||||||
|     app = Sanic(name=__name__, env_prefix="MYAPP_") |  | ||||||
|     assert app.config.TEST_TOKEN == "somerandomtesttoken" |  | ||||||
|     del environ["MYAPP_TEST_TOKEN"] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_load_from_file(app): | def test_load_from_file(app): | ||||||
|     config = dedent( |     config = dedent( | ||||||
|         """ |         """ | ||||||
|   | |||||||
| @@ -1,28 +0,0 @@ | |||||||
| from crypt import methods |  | ||||||
|  |  | ||||||
| from sanic import text |  | ||||||
| from sanic.constants import HTTP_METHODS, HTTPMethod |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_string_compat(): |  | ||||||
|     assert "GET" == HTTPMethod.GET |  | ||||||
|     assert "GET" in HTTP_METHODS |  | ||||||
|     assert "get" == HTTPMethod.GET |  | ||||||
|     assert "get" in HTTP_METHODS |  | ||||||
|  |  | ||||||
|     assert HTTPMethod.GET.lower() == "get" |  | ||||||
|     assert HTTPMethod.GET.upper() == "GET" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_use_in_routes(app): |  | ||||||
|     @app.route("/", methods=[HTTPMethod.GET, HTTPMethod.POST]) |  | ||||||
|     def handler(_): |  | ||||||
|         return text("It works") |  | ||||||
|  |  | ||||||
|     _, response = app.test_client.get("/") |  | ||||||
|     assert response.status == 200 |  | ||||||
|     assert response.text == "It works" |  | ||||||
|  |  | ||||||
|     _, response = app.test_client.post("/") |  | ||||||
|     assert response.status == 200 |  | ||||||
|     assert response.text == "It works" |  | ||||||
| @@ -1,5 +1,3 @@ | |||||||
| import warnings |  | ||||||
|  |  | ||||||
| import pytest | import pytest | ||||||
|  |  | ||||||
| from bs4 import BeautifulSoup | from bs4 import BeautifulSoup | ||||||
| @@ -9,7 +7,6 @@ from sanic.exceptions import ( | |||||||
|     Forbidden, |     Forbidden, | ||||||
|     InvalidUsage, |     InvalidUsage, | ||||||
|     NotFound, |     NotFound, | ||||||
|     SanicException, |  | ||||||
|     ServerError, |     ServerError, | ||||||
|     Unauthorized, |     Unauthorized, | ||||||
|     abort, |     abort, | ||||||
| @@ -71,19 +68,16 @@ def exception_app(): | |||||||
|  |  | ||||||
|     @app.route("/abort/401") |     @app.route("/abort/401") | ||||||
|     def handler_401_error(request): |     def handler_401_error(request): | ||||||
|         raise SanicException(status_code=401) |         abort(401) | ||||||
|  |  | ||||||
|     @app.route("/abort") |     @app.route("/abort") | ||||||
|     def handler_500_error(request): |     def handler_500_error(request): | ||||||
|         raise SanicException(status_code=500) |  | ||||||
|  |  | ||||||
|     @app.route("/old_abort") |  | ||||||
|     def handler_old_abort_error(request): |  | ||||||
|         abort(500) |         abort(500) | ||||||
|  |         return text("OK") | ||||||
|  |  | ||||||
|     @app.route("/abort/message") |     @app.route("/abort/message") | ||||||
|     def handler_abort_message(request): |     def handler_abort_message(request): | ||||||
|         raise SanicException(message="Custom Message", status_code=500) |         abort(500, message="Abort") | ||||||
|  |  | ||||||
|     @app.route("/divide_by_zero") |     @app.route("/divide_by_zero") | ||||||
|     def handle_unhandled_exception(request): |     def handle_unhandled_exception(request): | ||||||
| @@ -214,21 +208,14 @@ def test_exception_in_exception_handler_debug_on(exception_app): | |||||||
|     assert response.body.startswith(b"Exception raised in exception ") |     assert response.body.startswith(b"Exception raised in exception ") | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_sanic_exception(exception_app): | def test_abort(exception_app): | ||||||
|     """Test sanic exceptions are handled""" |     """Test the abort function""" | ||||||
|     request, response = exception_app.test_client.get("/abort/401") |     request, response = exception_app.test_client.get("/abort/401") | ||||||
|     assert response.status == 401 |     assert response.status == 401 | ||||||
|  |  | ||||||
|     request, response = exception_app.test_client.get("/abort") |     request, response = exception_app.test_client.get("/abort") | ||||||
|     assert response.status == 500 |     assert response.status == 500 | ||||||
|     # check fallback message |  | ||||||
|     assert "Internal Server Error" in response.text |  | ||||||
|  |  | ||||||
|     request, response = exception_app.test_client.get("/abort/message") |     request, response = exception_app.test_client.get("/abort/message") | ||||||
|     assert response.status == 500 |     assert response.status == 500 | ||||||
|     assert "Custom Message" in response.text |     assert "Abort" in response.text | ||||||
|  |  | ||||||
|     with warnings.catch_warnings(record=True) as w: |  | ||||||
|         request, response = exception_app.test_client.get("/old_abort") |  | ||||||
|     assert response.status == 500 |  | ||||||
|     assert len(w) == 1 and "deprecated" in w[0].message.args[0] |  | ||||||
|   | |||||||
| @@ -7,13 +7,6 @@ from sanic.exceptions import PayloadTooLarge | |||||||
| from sanic.http import Http | from sanic.http import Http | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.fixture |  | ||||||
| def raised_ceiling(): |  | ||||||
|     Http.HEADER_CEILING = 32_768 |  | ||||||
|     yield |  | ||||||
|     Http.HEADER_CEILING = 16_384 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize( | @pytest.mark.parametrize( | ||||||
|     "input, expected", |     "input, expected", | ||||||
|     [ |     [ | ||||||
| @@ -83,75 +76,15 @@ async def test_header_size_exceeded(): | |||||||
|         recv_buffer += b"123" |         recv_buffer += b"123" | ||||||
|  |  | ||||||
|     protocol = Mock() |     protocol = Mock() | ||||||
|     Http.set_header_max_size(1) |  | ||||||
|     http = Http(protocol) |     http = Http(protocol) | ||||||
|     http._receive_more = _receive_more |     http._receive_more = _receive_more | ||||||
|  |     http.request_max_size = 1 | ||||||
|     http.recv_buffer = recv_buffer |     http.recv_buffer = recv_buffer | ||||||
|  |  | ||||||
|     with pytest.raises(PayloadTooLarge): |     with pytest.raises(PayloadTooLarge): | ||||||
|         await http.http1_request_header() |         await http.http1_request_header() | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.asyncio |  | ||||||
| async def test_header_size_increased_okay(): |  | ||||||
|     recv_buffer = bytearray() |  | ||||||
|  |  | ||||||
|     async def _receive_more(): |  | ||||||
|         nonlocal recv_buffer |  | ||||||
|         recv_buffer += b"123" |  | ||||||
|  |  | ||||||
|     protocol = Mock() |  | ||||||
|     Http.set_header_max_size(12_288) |  | ||||||
|     http = Http(protocol) |  | ||||||
|     http._receive_more = _receive_more |  | ||||||
|     http.recv_buffer = recv_buffer |  | ||||||
|  |  | ||||||
|     with pytest.raises(PayloadTooLarge): |  | ||||||
|         await http.http1_request_header() |  | ||||||
|  |  | ||||||
|     assert len(recv_buffer) == 12_291 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.asyncio |  | ||||||
| async def test_header_size_exceeded_maxed_out(): |  | ||||||
|     recv_buffer = bytearray() |  | ||||||
|  |  | ||||||
|     async def _receive_more(): |  | ||||||
|         nonlocal recv_buffer |  | ||||||
|         recv_buffer += b"123" |  | ||||||
|  |  | ||||||
|     protocol = Mock() |  | ||||||
|     Http.set_header_max_size(18_432) |  | ||||||
|     http = Http(protocol) |  | ||||||
|     http._receive_more = _receive_more |  | ||||||
|     http.recv_buffer = recv_buffer |  | ||||||
|  |  | ||||||
|     with pytest.raises(PayloadTooLarge): |  | ||||||
|         await http.http1_request_header() |  | ||||||
|  |  | ||||||
|     assert len(recv_buffer) == 16_389 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.asyncio |  | ||||||
| async def test_header_size_exceeded_raised_ceiling(raised_ceiling): |  | ||||||
|     recv_buffer = bytearray() |  | ||||||
|  |  | ||||||
|     async def _receive_more(): |  | ||||||
|         nonlocal recv_buffer |  | ||||||
|         recv_buffer += b"123" |  | ||||||
|  |  | ||||||
|     protocol = Mock() |  | ||||||
|     http = Http(protocol) |  | ||||||
|     Http.set_header_max_size(65_536) |  | ||||||
|     http._receive_more = _receive_more |  | ||||||
|     http.recv_buffer = recv_buffer |  | ||||||
|  |  | ||||||
|     with pytest.raises(PayloadTooLarge): |  | ||||||
|         await http.http1_request_header() |  | ||||||
|  |  | ||||||
|     assert len(recv_buffer) == 32_772 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_raw_headers(app): | def test_raw_headers(app): | ||||||
|     app.route("/")(lambda _: text("")) |     app.route("/")(lambda _: text("")) | ||||||
|     request, _ = app.test_client.get( |     request, _ = app.test_client.get( | ||||||
|   | |||||||
| @@ -1,5 +1,4 @@ | |||||||
| import asyncio | import asyncio | ||||||
| import platform |  | ||||||
|  |  | ||||||
| from asyncio import sleep as aio_sleep | from asyncio import sleep as aio_sleep | ||||||
| from json import JSONDecodeError | from json import JSONDecodeError | ||||||
| @@ -242,9 +241,7 @@ def test_keep_alive_timeout_reuse(): | |||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.skipif( | @pytest.mark.skipif( | ||||||
|     bool(environ.get("SANIC_NO_UVLOOP")) |     bool(environ.get("SANIC_NO_UVLOOP")) or OS_IS_WINDOWS, | ||||||
|     or OS_IS_WINDOWS |  | ||||||
|     or platform.system() != "Linux", |  | ||||||
|     reason="Not testable with current client", |     reason="Not testable with current client", | ||||||
| ) | ) | ||||||
| def test_keep_alive_client_timeout(): | def test_keep_alive_client_timeout(): | ||||||
|   | |||||||
| @@ -113,9 +113,9 @@ def test_logging_pass_customer_logconfig(): | |||||||
| def test_log_connection_lost(app, debug, monkeypatch): | def test_log_connection_lost(app, debug, monkeypatch): | ||||||
|     """ Should not log Connection lost exception on non debug """ |     """ Should not log Connection lost exception on non debug """ | ||||||
|     stream = StringIO() |     stream = StringIO() | ||||||
|     error = logging.getLogger("sanic.error") |     root = logging.getLogger("sanic.root") | ||||||
|     error.addHandler(logging.StreamHandler(stream)) |     root.addHandler(logging.StreamHandler(stream)) | ||||||
|     monkeypatch.setattr(sanic.server, "error_logger", error) |     monkeypatch.setattr(sanic.server, "logger", root) | ||||||
|  |  | ||||||
|     @app.route("/conn_lost") |     @app.route("/conn_lost") | ||||||
|     async def conn_lost(request): |     async def conn_lost(request): | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ import logging | |||||||
| from asyncio import CancelledError | from asyncio import CancelledError | ||||||
| from itertools import count | from itertools import count | ||||||
|  |  | ||||||
| from sanic.exceptions import NotFound | from sanic.exceptions import NotFound, SanicException | ||||||
| from sanic.request import Request | from sanic.request import Request | ||||||
| from sanic.response import HTTPResponse, text | from sanic.response import HTTPResponse, text | ||||||
|  |  | ||||||
| @@ -156,7 +156,7 @@ def test_middleware_response_raise_cancelled_error(app, caplog): | |||||||
|  |  | ||||||
|         assert response.status == 503 |         assert response.status == 503 | ||||||
|         assert ( |         assert ( | ||||||
|             "sanic.error", |             "sanic.root", | ||||||
|             logging.ERROR, |             logging.ERROR, | ||||||
|             "Exception occurred while handling uri: 'http://127.0.0.1:42101/'", |             "Exception occurred while handling uri: 'http://127.0.0.1:42101/'", | ||||||
|         ) not in caplog.record_tuples |         ) not in caplog.record_tuples | ||||||
| @@ -174,7 +174,7 @@ def test_middleware_response_raise_exception(app, caplog): | |||||||
|     assert response.status == 404 |     assert response.status == 404 | ||||||
|     # 404 errors are not logged |     # 404 errors are not logged | ||||||
|     assert ( |     assert ( | ||||||
|         "sanic.error", |         "sanic.root", | ||||||
|         logging.ERROR, |         logging.ERROR, | ||||||
|         "Exception occurred while handling uri: 'http://127.0.0.1:42101/'", |         "Exception occurred while handling uri: 'http://127.0.0.1:42101/'", | ||||||
|     ) not in caplog.record_tuples |     ) not in caplog.record_tuples | ||||||
|   | |||||||
| @@ -234,7 +234,7 @@ def test_named_dynamic_route(): | |||||||
|         app.router.routes_all[ |         app.router.routes_all[ | ||||||
|             ( |             ( | ||||||
|                 "folder", |                 "folder", | ||||||
|                 "<name:str>", |                 "<name>", | ||||||
|             ) |             ) | ||||||
|         ].name |         ].name | ||||||
|         == "app.route_dynamic" |         == "app.route_dynamic" | ||||||
| @@ -369,8 +369,7 @@ def test_dynamic_add_named_route(): | |||||||
|  |  | ||||||
|     app.add_route(handler, "/folder/<name>", name="route_dynamic") |     app.add_route(handler, "/folder/<name>", name="route_dynamic") | ||||||
|     assert ( |     assert ( | ||||||
|         app.router.routes_all[("folder", "<name:str>")].name |         app.router.routes_all[("folder", "<name>")].name == "app.route_dynamic" | ||||||
|         == "app.route_dynamic" |  | ||||||
|     ) |     ) | ||||||
|     assert app.url_for("route_dynamic", name="test") == "/folder/test" |     assert app.url_for("route_dynamic", name="test") == "/folder/test" | ||||||
|     with pytest.raises(URLBuildError): |     with pytest.raises(URLBuildError): | ||||||
|   | |||||||
| @@ -1,105 +0,0 @@ | |||||||
| from httpx import AsyncByteStream |  | ||||||
| from sanic_testing.reusable import ReusableClient |  | ||||||
|  |  | ||||||
| from sanic.response import json, text |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_no_body_requests(app): |  | ||||||
|     @app.get("/") |  | ||||||
|     async def handler(request): |  | ||||||
|         return json( |  | ||||||
|             { |  | ||||||
|                 "request_id": str(request.id), |  | ||||||
|                 "connection_id": id(request.conn_info), |  | ||||||
|             } |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     client = ReusableClient(app, port=1234) |  | ||||||
|  |  | ||||||
|     with client: |  | ||||||
|         _, response1 = client.get("/") |  | ||||||
|         _, response2 = client.get("/") |  | ||||||
|  |  | ||||||
|     assert response1.status == response2.status == 200 |  | ||||||
|     assert response1.json["request_id"] != response2.json["request_id"] |  | ||||||
|     assert response1.json["connection_id"] == response2.json["connection_id"] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_json_body_requests(app): |  | ||||||
|     @app.post("/") |  | ||||||
|     async def handler(request): |  | ||||||
|         return json( |  | ||||||
|             { |  | ||||||
|                 "request_id": str(request.id), |  | ||||||
|                 "connection_id": id(request.conn_info), |  | ||||||
|                 "foo": request.json.get("foo"), |  | ||||||
|             } |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     client = ReusableClient(app, port=1234) |  | ||||||
|  |  | ||||||
|     with client: |  | ||||||
|         _, response1 = client.post("/", json={"foo": True}) |  | ||||||
|         _, response2 = client.post("/", json={"foo": True}) |  | ||||||
|  |  | ||||||
|     assert response1.status == response2.status == 200 |  | ||||||
|     assert response1.json["foo"] is response2.json["foo"] is True |  | ||||||
|     assert response1.json["request_id"] != response2.json["request_id"] |  | ||||||
|     assert response1.json["connection_id"] == response2.json["connection_id"] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_streaming_body_requests(app): |  | ||||||
|     @app.post("/", stream=True) |  | ||||||
|     async def handler(request): |  | ||||||
|         data = [part.decode("utf-8") async for part in request.stream] |  | ||||||
|         return json( |  | ||||||
|             { |  | ||||||
|                 "request_id": str(request.id), |  | ||||||
|                 "connection_id": id(request.conn_info), |  | ||||||
|                 "data": data, |  | ||||||
|             } |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     data = ["hello", "world"] |  | ||||||
|  |  | ||||||
|     class Data(AsyncByteStream): |  | ||||||
|         def __init__(self, data): |  | ||||||
|             self.data = data |  | ||||||
|  |  | ||||||
|         async def __aiter__(self): |  | ||||||
|             for value in self.data: |  | ||||||
|                 yield value.encode("utf-8") |  | ||||||
|  |  | ||||||
|     client = ReusableClient(app, port=1234) |  | ||||||
|  |  | ||||||
|     with client: |  | ||||||
|         _, response1 = client.post("/", data=Data(data)) |  | ||||||
|         _, response2 = client.post("/", data=Data(data)) |  | ||||||
|  |  | ||||||
|     assert response1.status == response2.status == 200 |  | ||||||
|     assert response1.json["data"] == response2.json["data"] == data |  | ||||||
|     assert response1.json["request_id"] != response2.json["request_id"] |  | ||||||
|     assert response1.json["connection_id"] == response2.json["connection_id"] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_bad_headers(app): |  | ||||||
|     @app.get("/") |  | ||||||
|     async def handler(request): |  | ||||||
|         return text("") |  | ||||||
|  |  | ||||||
|     @app.on_response |  | ||||||
|     async def reqid(request, response): |  | ||||||
|         response.headers["x-request-id"] = request.id |  | ||||||
|  |  | ||||||
|     client = ReusableClient(app, port=1234) |  | ||||||
|     bad_headers = {"bad": "bad" * 5_000} |  | ||||||
|  |  | ||||||
|     with client: |  | ||||||
|         _, response1 = client.get("/") |  | ||||||
|         _, response2 = client.get("/", headers=bad_headers) |  | ||||||
|  |  | ||||||
|     assert response1.status == 200 |  | ||||||
|     assert response2.status == 413 |  | ||||||
|     assert ( |  | ||||||
|         response1.headers["x-request-id"] != response2.headers["x-request-id"] |  | ||||||
|     ) |  | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| from urllib.parse import quote | from urllib.parse import quote, unquote | ||||||
|  |  | ||||||
| import pytest | import pytest | ||||||
|  |  | ||||||
|   | |||||||
| @@ -23,8 +23,6 @@ try: | |||||||
| except ImportError: | except ImportError: | ||||||
|     flags = 0 |     flags = 0 | ||||||
|  |  | ||||||
| TIMER_DELAY = 2 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def terminate(proc): | def terminate(proc): | ||||||
|     if flags: |     if flags: | ||||||
| @@ -58,40 +56,6 @@ def write_app(filename, **runargs): | |||||||
|     return text |     return text | ||||||
|  |  | ||||||
|  |  | ||||||
| def write_json_config_app(filename, jsonfile, **runargs): |  | ||||||
|     with open(filename, "w") as f: |  | ||||||
|         f.write( |  | ||||||
|             dedent( |  | ||||||
|                 f"""\ |  | ||||||
|             import os |  | ||||||
|             from sanic import Sanic |  | ||||||
|             import json |  | ||||||
|  |  | ||||||
|             app = Sanic(__name__) |  | ||||||
|             with open("{jsonfile}", "r") as f: |  | ||||||
|                 config = json.load(f) |  | ||||||
|             app.config.update_config(config) |  | ||||||
|  |  | ||||||
|             app.route("/")(lambda x: x) |  | ||||||
|  |  | ||||||
|             @app.listener("after_server_start") |  | ||||||
|             def complete(*args): |  | ||||||
|                 print("complete", os.getpid(), app.config.FOO) |  | ||||||
|  |  | ||||||
|             if __name__ == "__main__": |  | ||||||
|                 app.run(**{runargs!r}) |  | ||||||
|             """ |  | ||||||
|             ) |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def write_file(filename): |  | ||||||
|     text = secrets.token_urlsafe() |  | ||||||
|     with open(filename, "w") as f: |  | ||||||
|         f.write(f"""{{"FOO": "{text}"}}""") |  | ||||||
|     return text |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def scanner(proc): | def scanner(proc): | ||||||
|     for line in proc.stdout: |     for line in proc.stdout: | ||||||
|         line = line.decode().strip() |         line = line.decode().strip() | ||||||
| @@ -126,10 +90,9 @@ async def test_reloader_live(runargs, mode): | |||||||
|     with TemporaryDirectory() as tmpdir: |     with TemporaryDirectory() as tmpdir: | ||||||
|         filename = os.path.join(tmpdir, "reloader.py") |         filename = os.path.join(tmpdir, "reloader.py") | ||||||
|         text = write_app(filename, **runargs) |         text = write_app(filename, **runargs) | ||||||
|         command = argv[mode] |         proc = Popen(argv[mode], cwd=tmpdir, stdout=PIPE, creationflags=flags) | ||||||
|         proc = Popen(command, cwd=tmpdir, stdout=PIPE, creationflags=flags) |  | ||||||
|         try: |         try: | ||||||
|             timeout = Timer(TIMER_DELAY, terminate, [proc]) |             timeout = Timer(5, terminate, [proc]) | ||||||
|             timeout.start() |             timeout.start() | ||||||
|             # Python apparently keeps using the old source sometimes if |             # Python apparently keeps using the old source sometimes if | ||||||
|             # we don't sleep before rewrite (pycache timestamp problem?) |             # we don't sleep before rewrite (pycache timestamp problem?) | ||||||
| @@ -144,40 +107,3 @@ async def test_reloader_live(runargs, mode): | |||||||
|             terminate(proc) |             terminate(proc) | ||||||
|             with suppress(TimeoutExpired): |             with suppress(TimeoutExpired): | ||||||
|                 proc.wait(timeout=3) |                 proc.wait(timeout=3) | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize( |  | ||||||
|     "runargs, mode", |  | ||||||
|     [ |  | ||||||
|         (dict(port=42102, auto_reload=True), "script"), |  | ||||||
|         (dict(port=42103, debug=True), "module"), |  | ||||||
|         ({}, "sanic"), |  | ||||||
|     ], |  | ||||||
| ) |  | ||||||
| async def test_reloader_live_with_dir(runargs, mode): |  | ||||||
|     with TemporaryDirectory() as tmpdir: |  | ||||||
|         filename = os.path.join(tmpdir, "reloader.py") |  | ||||||
|         config_file = os.path.join(tmpdir, "config.json") |  | ||||||
|         runargs["reload_dir"] = tmpdir |  | ||||||
|         write_json_config_app(filename, config_file, **runargs) |  | ||||||
|         text = write_file(config_file) |  | ||||||
|         command = argv[mode] |  | ||||||
|         if mode == "sanic": |  | ||||||
|             command += ["--reload-dir", tmpdir] |  | ||||||
|         proc = Popen(command, cwd=tmpdir, stdout=PIPE, creationflags=flags) |  | ||||||
|         try: |  | ||||||
|             timeout = Timer(TIMER_DELAY, terminate, [proc]) |  | ||||||
|             timeout.start() |  | ||||||
|             # Python apparently keeps using the old source sometimes if |  | ||||||
|             # we don't sleep before rewrite (pycache timestamp problem?) |  | ||||||
|             sleep(1) |  | ||||||
|             line = scanner(proc) |  | ||||||
|             assert text in next(line) |  | ||||||
|             # Edit source code and try again |  | ||||||
|             text = write_file(config_file) |  | ||||||
|             assert text in next(line) |  | ||||||
|         finally: |  | ||||||
|             timeout.cancel() |  | ||||||
|             terminate(proc) |  | ||||||
|             with suppress(TimeoutExpired): |  | ||||||
|                 proc.wait(timeout=3) |  | ||||||
|   | |||||||
| @@ -20,7 +20,6 @@ def test_request_id_generates_from_request(monkeypatch): | |||||||
|     monkeypatch.setattr(Request, "generate_id", Mock()) |     monkeypatch.setattr(Request, "generate_id", Mock()) | ||||||
|     Request.generate_id.return_value = 1 |     Request.generate_id.return_value = 1 | ||||||
|     request = Request(b"/", {}, None, "GET", None, Mock()) |     request = Request(b"/", {}, None, "GET", None, Mock()) | ||||||
|     request.app.config.REQUEST_ID_HEADER = "foo" |  | ||||||
|  |  | ||||||
|     for _ in range(10): |     for _ in range(10): | ||||||
|         request.id |         request.id | ||||||
| @@ -29,7 +28,6 @@ def test_request_id_generates_from_request(monkeypatch): | |||||||
|  |  | ||||||
| def test_request_id_defaults_uuid(): | def test_request_id_defaults_uuid(): | ||||||
|     request = Request(b"/", {}, None, "GET", None, Mock()) |     request = Request(b"/", {}, None, "GET", None, Mock()) | ||||||
|     request.app.config.REQUEST_ID_HEADER = "foo" |  | ||||||
|  |  | ||||||
|     assert isinstance(request.id, UUID) |     assert isinstance(request.id, UUID) | ||||||
|  |  | ||||||
| @@ -122,21 +120,3 @@ def test_protocol_attribute(app): | |||||||
|     _ = app.test_client.get("/", headers=headers) |     _ = app.test_client.get("/", headers=headers) | ||||||
|  |  | ||||||
|     assert isinstance(retrieved, HttpProtocol) |     assert isinstance(retrieved, HttpProtocol) | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_ipv6_address_is_not_wrapped(app): |  | ||||||
|     @app.get("/") |  | ||||||
|     async def get(request): |  | ||||||
|         return response.json( |  | ||||||
|             { |  | ||||||
|                 "client_ip": request.conn_info.client_ip, |  | ||||||
|                 "client": request.conn_info.client, |  | ||||||
|             } |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     request, resp = app.test_client.get("/", host="::1") |  | ||||||
|  |  | ||||||
|     assert request.route is list(app.router.routes)[0] |  | ||||||
|     assert resp.json["client"] == "[::1]" |  | ||||||
|     assert resp.json["client_ip"] == "::1" |  | ||||||
|     assert request.ip == "::1" |  | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ import pytest | |||||||
| from sanic import Sanic | from sanic import Sanic | ||||||
| from sanic.blueprints import Blueprint | from sanic.blueprints import Blueprint | ||||||
| from sanic.response import json, text | from sanic.response import json, text | ||||||
|  | from sanic.server import HttpProtocol | ||||||
| from sanic.views import CompositionView, HTTPMethodView | from sanic.views import CompositionView, HTTPMethodView | ||||||
| from sanic.views import stream as stream_decorator | from sanic.views import stream as stream_decorator | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,8 +1,21 @@ | |||||||
| import asyncio | import asyncio | ||||||
|  |  | ||||||
|  | from typing import cast | ||||||
|  |  | ||||||
| import httpcore | import httpcore | ||||||
| import httpx | import httpx | ||||||
|  |  | ||||||
|  | from httpcore._async.base import ( | ||||||
|  |     AsyncByteStream, | ||||||
|  |     AsyncHTTPTransport, | ||||||
|  |     ConnectionState, | ||||||
|  |     NewConnectionRequired, | ||||||
|  | ) | ||||||
|  | from httpcore._async.connection import AsyncHTTPConnection | ||||||
|  | from httpcore._async.connection_pool import ResponseByteStream | ||||||
|  | from httpcore._exceptions import LocalProtocolError, UnsupportedProtocol | ||||||
|  | from httpcore._types import TimeoutDict | ||||||
|  | from httpcore._utils import url_to_origin | ||||||
| from sanic_testing.testing import SanicTestClient | from sanic_testing.testing import SanicTestClient | ||||||
|  |  | ||||||
| from sanic import Sanic | from sanic import Sanic | ||||||
|   | |||||||
| @@ -317,15 +317,15 @@ def test_query_string(app): | |||||||
|     assert request.args.get("test3", default="My value") == "My value" |     assert request.args.get("test3", default="My value") == "My value" | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_popped_stays_popped(app): | # def test_popped_stays_popped(app): | ||||||
|     @app.route("/") | #     @app.route("/") | ||||||
|     async def handler(request): | #     async def handler(request): | ||||||
|         return text("OK") | #         return text("OK") | ||||||
|  |  | ||||||
|     request, response = app.test_client.get("/", params=[("test1", "1")]) | #     request, response = app.test_client.get("/", params=[("test1", "1")]) | ||||||
|  |  | ||||||
|     assert request.args.pop("test1") == ["1"] | #     assert request.args.pop("test1") == ["1"] | ||||||
|     assert "test1" not in request.args | #     assert "test1" not in request.args | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.asyncio | @pytest.mark.asyncio | ||||||
| @@ -2246,7 +2246,9 @@ def test_conflicting_body_methods_overload(app): | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_handler_overload(app): | def test_handler_overload(app): | ||||||
|     @app.get("/long/sub/route/param_a/<param_a:str>/param_b/<param_b:str>") |     @app.get( | ||||||
|  |         "/long/sub/route/param_a/<param_a:string>/param_b/<param_b:string>" | ||||||
|  |     ) | ||||||
|     @app.post("/long/sub/route/") |     @app.post("/long/sub/route/") | ||||||
|     def handler(request, **kwargs): |     def handler(request, **kwargs): | ||||||
|         return json(kwargs) |         return json(kwargs) | ||||||
|   | |||||||
| @@ -1,19 +1,23 @@ | |||||||
| import asyncio | import asyncio | ||||||
| import inspect | import inspect | ||||||
| import os | import os | ||||||
|  | import warnings | ||||||
|  |  | ||||||
| from collections import namedtuple | from collections import namedtuple | ||||||
| from mimetypes import guess_type | from mimetypes import guess_type | ||||||
| from random import choice | from random import choice | ||||||
|  | from unittest.mock import MagicMock | ||||||
| from urllib.parse import unquote | from urllib.parse import unquote | ||||||
|  |  | ||||||
| import pytest | import pytest | ||||||
|  |  | ||||||
| from aiofiles import os as async_os | from aiofiles import os as async_os | ||||||
|  | from sanic_testing.testing import HOST, PORT | ||||||
|  |  | ||||||
| from sanic import Sanic | from sanic import Sanic | ||||||
| from sanic.response import ( | from sanic.response import ( | ||||||
|     HTTPResponse, |     HTTPResponse, | ||||||
|  |     StreamingHTTPResponse, | ||||||
|     empty, |     empty, | ||||||
|     file, |     file, | ||||||
|     file_stream, |     file_stream, | ||||||
| @@ -22,6 +26,7 @@ from sanic.response import ( | |||||||
|     stream, |     stream, | ||||||
|     text, |     text, | ||||||
| ) | ) | ||||||
|  | from sanic.server import HttpProtocol | ||||||
|  |  | ||||||
|  |  | ||||||
| JSON_DATA = {"ok": True} | JSON_DATA = {"ok": True} | ||||||
| @@ -224,6 +229,7 @@ def non_chunked_streaming_app(app): | |||||||
|             sample_streaming_fn, |             sample_streaming_fn, | ||||||
|             headers={"Content-Length": "7"}, |             headers={"Content-Length": "7"}, | ||||||
|             content_type="text/csv", |             content_type="text/csv", | ||||||
|  |             chunked=False, | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     return app |     return app | ||||||
| @@ -250,7 +256,11 @@ async def test_chunked_streaming_returns_correct_content_asgi(streaming_app): | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_non_chunked_streaming_adds_correct_headers(non_chunked_streaming_app): | def test_non_chunked_streaming_adds_correct_headers(non_chunked_streaming_app): | ||||||
|     request, response = non_chunked_streaming_app.test_client.get("/") |     with pytest.warns(UserWarning) as record: | ||||||
|  |         request, response = non_chunked_streaming_app.test_client.get("/") | ||||||
|  |  | ||||||
|  |     assert len(record) == 1 | ||||||
|  |     assert "removed in v21.6" in record[0].message.args[0] | ||||||
|  |  | ||||||
|     assert "Transfer-Encoding" not in response.headers |     assert "Transfer-Encoding" not in response.headers | ||||||
|     assert response.headers["Content-Type"] == "text/csv" |     assert response.headers["Content-Type"] == "text/csv" | ||||||
| @@ -524,19 +534,3 @@ def test_empty_response(app): | |||||||
|     request, response = app.test_client.get("/test") |     request, response = app.test_client.get("/test") | ||||||
|     assert response.content_type is None |     assert response.content_type is None | ||||||
|     assert response.body == b"" |     assert response.body == b"" | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_direct_response_stream(app): |  | ||||||
|     @app.route("/") |  | ||||||
|     async def test(request): |  | ||||||
|         response = await request.respond(content_type="text/csv") |  | ||||||
|         await response.send("foo,") |  | ||||||
|         await response.send("bar") |  | ||||||
|         await response.eof() |  | ||||||
|         return response |  | ||||||
|  |  | ||||||
|     _, response = app.test_client.get("/") |  | ||||||
|     assert response.text == "foo,bar" |  | ||||||
|     assert response.headers["Transfer-Encoding"] == "chunked" |  | ||||||
|     assert response.headers["Content-Type"] == "text/csv" |  | ||||||
|     assert "Content-Length" not in response.headers |  | ||||||
|   | |||||||
| @@ -258,7 +258,7 @@ def test_route_strict_slash(app): | |||||||
| def test_route_invalid_parameter_syntax(app): | def test_route_invalid_parameter_syntax(app): | ||||||
|     with pytest.raises(ValueError): |     with pytest.raises(ValueError): | ||||||
|  |  | ||||||
|         @app.get("/get/<:str>", strict_slashes=True) |         @app.get("/get/<:string>", strict_slashes=True) | ||||||
|         def handler(request): |         def handler(request): | ||||||
|             return text("OK") |             return text("OK") | ||||||
|  |  | ||||||
| @@ -478,7 +478,7 @@ def test_dynamic_route(app): | |||||||
| def test_dynamic_route_string(app): | def test_dynamic_route_string(app): | ||||||
|     results = [] |     results = [] | ||||||
|  |  | ||||||
|     @app.route("/folder/<name:str>") |     @app.route("/folder/<name:string>") | ||||||
|     async def handler(request, name): |     async def handler(request, name): | ||||||
|         results.append(name) |         results.append(name) | ||||||
|         return text("OK") |         return text("OK") | ||||||
| @@ -513,7 +513,7 @@ def test_dynamic_route_int(app): | |||||||
| def test_dynamic_route_number(app): | def test_dynamic_route_number(app): | ||||||
|     results = [] |     results = [] | ||||||
|  |  | ||||||
|     @app.route("/weight/<weight:float>") |     @app.route("/weight/<weight:number>") | ||||||
|     async def handler(request, weight): |     async def handler(request, weight): | ||||||
|         results.append(weight) |         results.append(weight) | ||||||
|         return text("OK") |         return text("OK") | ||||||
| @@ -585,6 +585,7 @@ def test_dynamic_route_path(app): | |||||||
|         return text("OK") |         return text("OK") | ||||||
|  |  | ||||||
|     app.router.finalize() |     app.router.finalize() | ||||||
|  |     print(app.router.find_route_src) | ||||||
|  |  | ||||||
|     request, response = app.test_client.get("/path/1/info") |     request, response = app.test_client.get("/path/1/info") | ||||||
|     assert response.status == 200 |     assert response.status == 200 | ||||||
| @@ -823,7 +824,7 @@ def test_dynamic_add_route_string(app): | |||||||
|         results.append(name) |         results.append(name) | ||||||
|         return text("OK") |         return text("OK") | ||||||
|  |  | ||||||
|     app.add_route(handler, "/folder/<name:str>") |     app.add_route(handler, "/folder/<name:string>") | ||||||
|     request, response = app.test_client.get("/folder/test123") |     request, response = app.test_client.get("/folder/test123") | ||||||
|  |  | ||||||
|     assert response.text == "OK" |     assert response.text == "OK" | ||||||
| @@ -859,7 +860,7 @@ def test_dynamic_add_route_number(app): | |||||||
|         results.append(weight) |         results.append(weight) | ||||||
|         return text("OK") |         return text("OK") | ||||||
|  |  | ||||||
|     app.add_route(handler, "/weight/<weight:float>") |     app.add_route(handler, "/weight/<weight:number>") | ||||||
|  |  | ||||||
|     request, response = app.test_client.get("/weight/12345") |     request, response = app.test_client.get("/weight/12345") | ||||||
|     assert response.text == "OK" |     assert response.text == "OK" | ||||||
| @@ -1066,8 +1067,7 @@ def test_uri_with_different_method_and_different_params(app): | |||||||
|         return json({"action": action}) |         return json({"action": action}) | ||||||
|  |  | ||||||
|     request, response = app.test_client.get("/ads/1234") |     request, response = app.test_client.get("/ads/1234") | ||||||
|     assert response.status == 200 |     assert response.status == 405 | ||||||
|     assert response.json == {"ad_id": "1234"} |  | ||||||
|  |  | ||||||
|     request, response = app.test_client.post("/ads/post") |     request, response = app.test_client.post("/ads/post") | ||||||
|     assert response.status == 200 |     assert response.status == 200 | ||||||
|   | |||||||
| @@ -257,60 +257,17 @@ def test_bad_finalize(app): | |||||||
|     assert counter == 0 |     assert counter == 0 | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.asyncio | def test_event_not_exist(app): | ||||||
| async def test_event_not_exist(app): |  | ||||||
|     with pytest.raises(NotFound, match="Could not find signal does.not.exist"): |     with pytest.raises(NotFound, match="Could not find signal does.not.exist"): | ||||||
|         await app.event("does.not.exist") |         app.event("does.not.exist") | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.asyncio | def test_event_not_exist_on_bp(app): | ||||||
| async def test_event_not_exist_on_bp(app): |  | ||||||
|     bp = Blueprint("bp") |     bp = Blueprint("bp") | ||||||
|     app.blueprint(bp) |     app.blueprint(bp) | ||||||
|  |  | ||||||
|     with pytest.raises(NotFound, match="Could not find signal does.not.exist"): |     with pytest.raises(NotFound, match="Could not find signal does.not.exist"): | ||||||
|         await bp.event("does.not.exist") |         bp.event("does.not.exist") | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.asyncio |  | ||||||
| async def test_event_not_exist_with_autoregister(app): |  | ||||||
|     app.config.EVENT_AUTOREGISTER = True |  | ||||||
|     try: |  | ||||||
|         await app.event("does.not.exist", timeout=0.1) |  | ||||||
|     except asyncio.TimeoutError: |  | ||||||
|         ... |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.asyncio |  | ||||||
| async def test_dispatch_signal_triggers_non_exist_event_with_autoregister(app): |  | ||||||
|     @app.signal("some.stand.in") |  | ||||||
|     async def signal_handler(): |  | ||||||
|         ... |  | ||||||
|  |  | ||||||
|     app.config.EVENT_AUTOREGISTER = True |  | ||||||
|     app_counter = 0 |  | ||||||
|     app.signal_router.finalize() |  | ||||||
|  |  | ||||||
|     async def do_wait(): |  | ||||||
|         nonlocal app_counter |  | ||||||
|         await app.event("foo.bar.baz") |  | ||||||
|         app_counter += 1 |  | ||||||
|  |  | ||||||
|     fut = asyncio.ensure_future(do_wait()) |  | ||||||
|     await app.dispatch("foo.bar.baz") |  | ||||||
|     await fut |  | ||||||
|  |  | ||||||
|     assert app_counter == 1 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.asyncio |  | ||||||
| async def test_dispatch_not_exist(app): |  | ||||||
|     @app.signal("do.something.start") |  | ||||||
|     async def signal_handler(): |  | ||||||
|         ... |  | ||||||
|  |  | ||||||
|     app.signal_router.finalize() |  | ||||||
|     await app.dispatch("does.not.exist") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_event_on_bp_not_registered(): | def test_event_on_bp_not_registered(): | ||||||
|   | |||||||
| @@ -471,25 +471,25 @@ def test_stack_trace_on_not_found(app, static_file_directory, caplog): | |||||||
|  |  | ||||||
|     assert response.status == 404 |     assert response.status == 404 | ||||||
|     assert counter[logging.INFO] == 5 |     assert counter[logging.INFO] == 5 | ||||||
|     assert counter[logging.ERROR] == 0 |     assert counter[logging.ERROR] == 1 | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_no_stack_trace_on_not_found(app, static_file_directory, caplog): | # def test_no_stack_trace_on_not_found(app, static_file_directory, caplog): | ||||||
|     app.static("/static", static_file_directory) | #     app.static("/static", static_file_directory) | ||||||
|  |  | ||||||
|     @app.exception(FileNotFound) | #     @app.exception(FileNotFound) | ||||||
|     async def file_not_found(request, exception): | #     async def file_not_found(request, exception): | ||||||
|         return text(f"No file: {request.path}", status=404) | #         return text(f"No file: {request.path}", status=404) | ||||||
|  |  | ||||||
|     with caplog.at_level(logging.INFO): | #     with caplog.at_level(logging.INFO): | ||||||
|         _, response = app.test_client.get("/static/non_existing_file.file") | #         _, response = app.test_client.get("/static/non_existing_file.file") | ||||||
|  |  | ||||||
|     counter = Counter([r[1] for r in caplog.record_tuples]) | #     counter = Counter([r[1] for r in caplog.record_tuples]) | ||||||
|  |  | ||||||
|     assert response.status == 404 | #     assert response.status == 404 | ||||||
|     assert counter[logging.INFO] == 5 | #     assert counter[logging.INFO] == 5 | ||||||
|     assert logging.ERROR not in counter | #     assert logging.ERROR not in counter | ||||||
|     assert response.text == "No file: /static/non_existing_file.file" | #     assert response.text == "No file: /static/non_existing_file.file" | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_multiple_statics(app, static_file_directory): | def test_multiple_statics(app, static_file_directory): | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import asyncio | import asyncio | ||||||
|  |  | ||||||
|  | from time import monotonic as current_time | ||||||
| from unittest.mock import Mock | from unittest.mock import Mock | ||||||
|  |  | ||||||
| import pytest | import pytest | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| import asyncio | import asyncio | ||||||
| import logging | import logging | ||||||
| import os | import os | ||||||
| import platform |  | ||||||
| import subprocess | import subprocess | ||||||
| import sys | import sys | ||||||
|  |  | ||||||
| @@ -176,10 +175,6 @@ def test_unix_connection_multiple_workers(): | |||||||
|     app_multi.run(host="myhost.invalid", unix=SOCKPATH, workers=2) |     app_multi.run(host="myhost.invalid", unix=SOCKPATH, workers=2) | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.xfail( |  | ||||||
|     condition=platform.system() != "Linux", |  | ||||||
|     reason="Flaky Test on Non Linux Infra", |  | ||||||
| ) |  | ||||||
| async def test_zero_downtime(): | async def test_zero_downtime(): | ||||||
|     """Graceful server termination and socket replacement on restarts""" |     """Graceful server termination and socket replacement on restarts""" | ||||||
|     from signal import SIGINT |     from signal import SIGINT | ||||||
|   | |||||||
| @@ -143,7 +143,7 @@ def test_fails_url_build_if_params_not_passed(app): | |||||||
|  |  | ||||||
| COMPLEX_PARAM_URL = ( | COMPLEX_PARAM_URL = ( | ||||||
|     "/<foo:int>/<four_letter_string:[A-z]{4}>/" |     "/<foo:int>/<four_letter_string:[A-z]{4}>/" | ||||||
|     "<two_letter_string:[A-z]{2}>/<normal_string>/<some_number:float>" |     "<two_letter_string:[A-z]{2}>/<normal_string>/<some_number:number>" | ||||||
| ) | ) | ||||||
| PASSING_KWARGS = { | PASSING_KWARGS = { | ||||||
|     "foo": 4, |     "foo": 4, | ||||||
| @@ -168,7 +168,7 @@ def test_fails_with_int_message(app): | |||||||
|  |  | ||||||
|     expected_error = ( |     expected_error = ( | ||||||
|         r'Value "not_int" for parameter `foo` ' |         r'Value "not_int" for parameter `foo` ' | ||||||
|         r"does not match pattern for type `int`: ^-?\d+$" |         r"does not match pattern for type `int`: ^-?\d+" | ||||||
|     ) |     ) | ||||||
|     assert str(e.value) == expected_error |     assert str(e.value) == expected_error | ||||||
|  |  | ||||||
| @@ -223,7 +223,7 @@ def test_fails_with_number_message(app): | |||||||
|  |  | ||||||
| @pytest.mark.parametrize("number", [3, -3, 13.123, -13.123]) | @pytest.mark.parametrize("number", [3, -3, 13.123, -13.123]) | ||||||
| def test_passes_with_negative_number_message(app, number): | def test_passes_with_negative_number_message(app, number): | ||||||
|     @app.route("path/<possibly_neg:float>/another-word") |     @app.route("path/<possibly_neg:number>/another-word") | ||||||
|     def good(request, possibly_neg): |     def good(request, possibly_neg): | ||||||
|         assert isinstance(possibly_neg, (int, float)) |         assert isinstance(possibly_neg, (int, float)) | ||||||
|         return text(f"this should pass with `{possibly_neg}`") |         return text(f"this should pass with `{possibly_neg}`") | ||||||
|   | |||||||
| @@ -1,141 +0,0 @@ | |||||||
| import pytest |  | ||||||
|  |  | ||||||
| from sanic import Blueprint, text |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.fixture |  | ||||||
| def handler(): |  | ||||||
|     def handler(_): |  | ||||||
|         return text("Done.") |  | ||||||
|  |  | ||||||
|     return handler |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_route(app, handler): |  | ||||||
|     app.route("/", version=1)(handler) |  | ||||||
|  |  | ||||||
|     _, response = app.test_client.get("/v1") |  | ||||||
|     assert response.status == 200 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_bp(app, handler): |  | ||||||
|     bp = Blueprint(__file__, version=1) |  | ||||||
|     bp.route("/")(handler) |  | ||||||
|     app.blueprint(bp) |  | ||||||
|  |  | ||||||
|     _, response = app.test_client.get("/v1") |  | ||||||
|     assert response.status == 200 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_bp_use_route(app, handler): |  | ||||||
|     bp = Blueprint(__file__, version=1) |  | ||||||
|     bp.route("/", version=1.1)(handler) |  | ||||||
|     app.blueprint(bp) |  | ||||||
|  |  | ||||||
|     _, response = app.test_client.get("/v1.1") |  | ||||||
|     assert response.status == 200 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_bp_group(app, handler): |  | ||||||
|     bp = Blueprint(__file__) |  | ||||||
|     bp.route("/")(handler) |  | ||||||
|     group = Blueprint.group(bp, version=1) |  | ||||||
|     app.blueprint(group) |  | ||||||
|  |  | ||||||
|     _, response = app.test_client.get("/v1") |  | ||||||
|     assert response.status == 200 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_bp_group_use_bp(app, handler): |  | ||||||
|     bp = Blueprint(__file__, version=1.1) |  | ||||||
|     bp.route("/")(handler) |  | ||||||
|     group = Blueprint.group(bp, version=1) |  | ||||||
|     app.blueprint(group) |  | ||||||
|  |  | ||||||
|     _, response = app.test_client.get("/v1.1") |  | ||||||
|     assert response.status == 200 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_bp_group_use_registration(app, handler): |  | ||||||
|     bp = Blueprint(__file__, version=1.1) |  | ||||||
|     bp.route("/")(handler) |  | ||||||
|     group = Blueprint.group(bp, version=1) |  | ||||||
|     app.blueprint(group, version=1.2) |  | ||||||
|  |  | ||||||
|     _, response = app.test_client.get("/v1.2") |  | ||||||
|     assert response.status == 200 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_bp_group_use_route(app, handler): |  | ||||||
|     bp = Blueprint(__file__, version=1.1) |  | ||||||
|     bp.route("/", version=1.3)(handler) |  | ||||||
|     group = Blueprint.group(bp, version=1) |  | ||||||
|     app.blueprint(group, version=1.2) |  | ||||||
|  |  | ||||||
|     _, response = app.test_client.get("/v1.3") |  | ||||||
|     assert response.status == 200 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_version_prefix_route(app, handler): |  | ||||||
|     app.route("/", version=1, version_prefix="/api/v")(handler) |  | ||||||
|  |  | ||||||
|     _, response = app.test_client.get("/api/v1") |  | ||||||
|     assert response.status == 200 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_version_prefix_bp(app, handler): |  | ||||||
|     bp = Blueprint(__file__, version=1, version_prefix="/api/v") |  | ||||||
|     bp.route("/")(handler) |  | ||||||
|     app.blueprint(bp) |  | ||||||
|  |  | ||||||
|     _, response = app.test_client.get("/api/v1") |  | ||||||
|     assert response.status == 200 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_version_prefix_bp_use_route(app, handler): |  | ||||||
|     bp = Blueprint(__file__, version=1, version_prefix="/ignore/v") |  | ||||||
|     bp.route("/", version=1.1, version_prefix="/api/v")(handler) |  | ||||||
|     app.blueprint(bp) |  | ||||||
|  |  | ||||||
|     _, response = app.test_client.get("/api/v1.1") |  | ||||||
|     assert response.status == 200 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_version_prefix_bp_group(app, handler): |  | ||||||
|     bp = Blueprint(__file__) |  | ||||||
|     bp.route("/")(handler) |  | ||||||
|     group = Blueprint.group(bp, version=1, version_prefix="/api/v") |  | ||||||
|     app.blueprint(group) |  | ||||||
|  |  | ||||||
|     _, response = app.test_client.get("/api/v1") |  | ||||||
|     assert response.status == 200 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_version_prefix_bp_group_use_bp(app, handler): |  | ||||||
|     bp = Blueprint(__file__, version=1.1, version_prefix="/api/v") |  | ||||||
|     bp.route("/")(handler) |  | ||||||
|     group = Blueprint.group(bp, version=1, version_prefix="/ignore/v") |  | ||||||
|     app.blueprint(group) |  | ||||||
|  |  | ||||||
|     _, response = app.test_client.get("/api/v1.1") |  | ||||||
|     assert response.status == 200 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_version_prefix_bp_group_use_registration(app, handler): |  | ||||||
|     bp = Blueprint(__file__, version=1.1, version_prefix="/alsoignore/v") |  | ||||||
|     bp.route("/")(handler) |  | ||||||
|     group = Blueprint.group(bp, version=1, version_prefix="/ignore/v") |  | ||||||
|     app.blueprint(group, version=1.2, version_prefix="/api/v") |  | ||||||
|  |  | ||||||
|     _, response = app.test_client.get("/api/v1.2") |  | ||||||
|     assert response.status == 200 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_version_prefix_bp_group_use_route(app, handler): |  | ||||||
|     bp = Blueprint(__file__, version=1.1, version_prefix="/alsoignore/v") |  | ||||||
|     bp.route("/", version=1.3, version_prefix="/api/v")(handler) |  | ||||||
|     group = Blueprint.group(bp, version=1, version_prefix="/ignore/v") |  | ||||||
|     app.blueprint(group, version=1.2, version_prefix="/stillignoring/v") |  | ||||||
|  |  | ||||||
|     _, response = app.test_client.get("/api/v1.3") |  | ||||||
|     assert response.status == 200 |  | ||||||
| @@ -77,56 +77,6 @@ def test_with_bp(app): | |||||||
|     assert response.text == "I am get method" |     assert response.text == "I am get method" | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_with_attach(app): |  | ||||||
|     class DummyView(HTTPMethodView): |  | ||||||
|         def get(self, request): |  | ||||||
|             return text("I am get method") |  | ||||||
|  |  | ||||||
|     DummyView.attach(app, "/") |  | ||||||
|  |  | ||||||
|     request, response = app.test_client.get("/") |  | ||||||
|  |  | ||||||
|     assert response.text == "I am get method" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_with_sub_init(app): |  | ||||||
|     class DummyView(HTTPMethodView, attach=app, uri="/"): |  | ||||||
|         def get(self, request): |  | ||||||
|             return text("I am get method") |  | ||||||
|  |  | ||||||
|     request, response = app.test_client.get("/") |  | ||||||
|  |  | ||||||
|     assert response.text == "I am get method" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_with_attach_and_bp(app): |  | ||||||
|     bp = Blueprint("test_text") |  | ||||||
|  |  | ||||||
|     class DummyView(HTTPMethodView): |  | ||||||
|         def get(self, request): |  | ||||||
|             return text("I am get method") |  | ||||||
|  |  | ||||||
|     DummyView.attach(bp, "/") |  | ||||||
|  |  | ||||||
|     app.blueprint(bp) |  | ||||||
|     request, response = app.test_client.get("/") |  | ||||||
|  |  | ||||||
|     assert response.text == "I am get method" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_with_sub_init_and_bp(app): |  | ||||||
|     bp = Blueprint("test_text") |  | ||||||
|  |  | ||||||
|     class DummyView(HTTPMethodView, attach=bp, uri="/"): |  | ||||||
|         def get(self, request): |  | ||||||
|             return text("I am get method") |  | ||||||
|  |  | ||||||
|     app.blueprint(bp) |  | ||||||
|     request, response = app.test_client.get("/") |  | ||||||
|  |  | ||||||
|     assert response.text == "I am get method" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_with_bp_with_url_prefix(app): | def test_with_bp_with_url_prefix(app): | ||||||
|     bp = Blueprint("test_text", url_prefix="/test1") |     bp = Blueprint("test_text", url_prefix="/test1") | ||||||
|  |  | ||||||
| @@ -268,15 +218,15 @@ def test_composition_view_runs_methods_as_expected(app, method): | |||||||
|         assert response.status == 200 |         assert response.status == 200 | ||||||
|         assert response.text == "first method" |         assert response.text == "first method" | ||||||
|  |  | ||||||
|         response = view(request) |         # response = view(request) | ||||||
|         assert response.body.decode() == "first method" |         # assert response.body.decode() == "first method" | ||||||
|  |  | ||||||
|     if method in ["DELETE", "PATCH"]: |     # if method in ["DELETE", "PATCH"]: | ||||||
|         request, response = getattr(app.test_client, method.lower())("/") |     #     request, response = getattr(app.test_client, method.lower())("/") | ||||||
|         assert response.text == "second method" |     #     assert response.text == "second method" | ||||||
|  |  | ||||||
|         response = view(request) |     #     response = view(request) | ||||||
|         assert response.body.decode() == "second method" |     #     assert response.body.decode() == "second method" | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize("method", HTTP_METHODS) | @pytest.mark.parametrize("method", HTTP_METHODS) | ||||||
| @@ -294,12 +244,3 @@ def test_composition_view_rejects_invalid_methods(app, method): | |||||||
|     if method in ["DELETE", "PATCH"]: |     if method in ["DELETE", "PATCH"]: | ||||||
|         request, response = getattr(app.test_client, method.lower())("/") |         request, response = getattr(app.test_client, method.lower())("/") | ||||||
|         assert response.status == 405 |         assert response.status == 405 | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_composition_view_deprecation(): |  | ||||||
|     message = ( |  | ||||||
|         "CompositionView has been deprecated and will be removed in v21.12. " |  | ||||||
|         "Please update your view to HTTPMethodView." |  | ||||||
|     ) |  | ||||||
|     with pytest.warns(DeprecationWarning, match=message): |  | ||||||
|         CompositionView() |  | ||||||
|   | |||||||
							
								
								
									
										28
									
								
								tox.ini
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								tox.ini
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | |||||||
| [tox] | [tox] | ||||||
| envlist = py37, py38, py39, pyNightly, pypy37, {py37,py38,py39,pyNightly,pypy37}-no-ext, lint, check, security, docs, type-checking | envlist = py37, py38, py39, pyNightly, {py37,py38,py39,pyNightly}-no-ext, lint, check, security, docs | ||||||
|  |  | ||||||
| [testenv] | [testenv] | ||||||
| usedevelop = True | usedevelop = True | ||||||
| @@ -7,7 +7,7 @@ setenv = | |||||||
|     {py37,py38,py39,pyNightly}-no-ext: SANIC_NO_UJSON=1 |     {py37,py38,py39,pyNightly}-no-ext: SANIC_NO_UJSON=1 | ||||||
|     {py37,py38,py39,pyNightly}-no-ext: SANIC_NO_UVLOOP=1 |     {py37,py38,py39,pyNightly}-no-ext: SANIC_NO_UVLOOP=1 | ||||||
| deps = | deps = | ||||||
|     sanic-testing>=0.6.0 |     sanic-testing | ||||||
|     coverage==5.3 |     coverage==5.3 | ||||||
|     pytest==5.2.1 |     pytest==5.2.1 | ||||||
|     pytest-cov |     pytest-cov | ||||||
| @@ -18,7 +18,7 @@ deps = | |||||||
|     beautifulsoup4 |     beautifulsoup4 | ||||||
|     gunicorn==20.0.4 |     gunicorn==20.0.4 | ||||||
|     uvicorn |     uvicorn | ||||||
|     websockets>=9.0 |     websockets>=8.1,<9.0 | ||||||
| commands = | commands = | ||||||
|     pytest {posargs:tests --cov sanic} |     pytest {posargs:tests --cov sanic} | ||||||
|     - coverage combine --append |     - coverage combine --append | ||||||
| @@ -39,8 +39,7 @@ commands = | |||||||
|  |  | ||||||
| [testenv:type-checking] | [testenv:type-checking] | ||||||
| deps = | deps = | ||||||
|     mypy>=0.901 |     mypy | ||||||
|     types-ujson |  | ||||||
|  |  | ||||||
| commands = | commands = | ||||||
|     mypy sanic |     mypy sanic | ||||||
| @@ -76,23 +75,6 @@ deps = | |||||||
|     docutils |     docutils | ||||||
|     pygments |     pygments | ||||||
|     gunicorn==20.0.4 |     gunicorn==20.0.4 | ||||||
|  |  | ||||||
| commands = | commands = | ||||||
|     make docs-test |     make docs-test | ||||||
|  |  | ||||||
| [testenv:coverage] |  | ||||||
| usedevelop = True |  | ||||||
| deps = |  | ||||||
|     sanic-testing>=0.6.0 |  | ||||||
|     coverage==5.3 |  | ||||||
|     pytest==5.2.1 |  | ||||||
|     pytest-cov |  | ||||||
|     pytest-sanic |  | ||||||
|     pytest-sugar |  | ||||||
|     pytest-benchmark |  | ||||||
|     chardet==3.* |  | ||||||
|     beautifulsoup4 |  | ||||||
|     gunicorn==20.0.4 |  | ||||||
|     uvicorn |  | ||||||
|     websockets>=9.0 |  | ||||||
| commands = |  | ||||||
|     pytest tests --cov=./sanic --cov-report=xml |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user