Compare commits
	
		
			75 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 05002d7ee4 | ||
|   | b4360d4a20 | ||
|   | 3b85b3bbad | ||
|   | 6e55e73da1 | ||
|   | 89d942451f | ||
|   | 4d6205e6fe | ||
|   | 1684b0b986 | ||
|   | 4f5faa4a3c | ||
|   | cbb77b536a | ||
|   | 35c76253bf | ||
|   | 8d86c3c598 | ||
|   | 97635111af | ||
|   | 7f3fe40cd4 | ||
|   | ea34bcd849 | ||
|   | 05f758583b | ||
|   | 760c74a293 | ||
|   | 9def46beb8 | ||
|   | 04be8e95a5 | ||
|   | 78ced20fc7 | ||
|   | c3003413d3 | ||
|   | fe3fdc5d83 | ||
|   | b66fb6f9e8 | ||
|   | bf6175fb20 | ||
|   | 7475897a03 | ||
|   | 58ca887be4 | ||
|   | 449bc417a3 | ||
|   | 262f89f2b6 | ||
|   | 38337446cf | ||
|   | ac1331ea4c | ||
|   | 2b947e831f | ||
|   | 112715eb80 | ||
|   | ea9cf365bc | ||
|   | b9b3b4051a | ||
|   | ecb6db29e6 | ||
|   | 6515dde64b | ||
|   | 01d2a2aa3c | ||
|   | 39e12accb8 | ||
|   | 39fe6ea5b1 | ||
|   | fc4b7df088 | ||
|   | 35f28f7a64 | ||
|   | 614be40438 | ||
|   | bde0428d0c | ||
|   | 63567c2ae4 | ||
|   | ec10f337b6 | ||
|   | d0f0e73e96 | ||
|   | b4fe2c8a6b | ||
|   | 33da0771d1 | ||
|   | 75994cd915 | ||
|   | c0839afdde | ||
|   | 5961da3f57 | ||
|   | 41f1809351 | ||
|   | 5fbdcb62e4 | ||
|   | 677b83e9f8 | ||
|   | 6a5c8becac | ||
|   | fd23b99d60 | ||
|   | 634b586df3 | ||
|   | 4ca3e98082 | ||
|   | d18a776964 | ||
|   | d6b4d7d265 | ||
|   | 33ee4c21b3 | ||
|   | a026cd7195 | ||
|   | 7b1bce8d90 | ||
|   | 217a7c5161 | ||
|   | 2949e3422d | ||
|   | 16ea99b0c0 | ||
|   | 19b84ce9f0 | ||
|   | e5aed4c067 | ||
|   | 9e048bc0c3 | ||
|   | 5d7b0735ce | ||
|   | 12521cd5b4 | ||
|   | 7dbd3eb5e8 | ||
|   | 96364aacc0 | ||
|   | fc18f86964 | ||
|   | fb3d368a78 | ||
|   | f41435fae3 | 
| @@ -17,6 +17,12 @@ environment: | ||||
|       PYTHON_VERSION: "3.8.x" | ||||
|       PYTHON_ARCH: "64" | ||||
|  | ||||
|     # - TOXENV: py39-no-ext | ||||
|     #   PYTHON: "C:\\Python39-x64\\python" | ||||
|     #   PYTHONPATH: "C:\\Python39-x64" | ||||
|     #   PYTHON_VERSION: "3.9.x" | ||||
|     #   PYTHON_ARCH: "64" | ||||
|  | ||||
| init: SET "PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" | ||||
|  | ||||
| install: | ||||
|   | ||||
							
								
								
									
										12
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| # These are supported funding model platforms | ||||
|  | ||||
| github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] | ||||
| patreon: # Replace with a single Patreon username | ||||
| open_collective: sanic-org # Replace with a single Open Collective username | ||||
| ko_fi: # Replace with a single Ko-fi username | ||||
| tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel | ||||
| community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry | ||||
| liberapay: # Replace with a single Liberapay username | ||||
| issuehunt: # Replace with a single IssueHunt username | ||||
| otechie: # Replace with a single Otechie username | ||||
| custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] | ||||
							
								
								
									
										40
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| name: "CodeQL" | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     branches: | ||||
|       - main | ||||
|       - "*LTS" | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - main | ||||
|       - "*LTS" | ||||
|     types: [opened, synchronize, reopened, ready_for_review] | ||||
|   schedule: | ||||
|     - cron: '25 16 * * 0' | ||||
|  | ||||
| jobs: | ||||
|   analyze: | ||||
|     if: github.event.pull_request.draft == false | ||||
|     name: Analyze | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       matrix: | ||||
|         language: [ 'python' ] | ||||
|  | ||||
|     steps: | ||||
|     - name: Checkout repository | ||||
|       uses: actions/checkout@v2 | ||||
|  | ||||
|     - name: Initialize CodeQL | ||||
|       uses: github/codeql-action/init@v1 | ||||
|       with: | ||||
|         languages: ${{ matrix.language }} | ||||
|  | ||||
|     - name: Autobuild | ||||
|       uses: github/codeql-action/autobuild@v1 | ||||
|  | ||||
|     - name: Perform CodeQL Analysis | ||||
|       uses: github/codeql-action/analyze@v1 | ||||
							
								
								
									
										37
									
								
								.github/workflows/coverage.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								.github/workflows/coverage.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| name: Coverage check | ||||
| on: | ||||
|   push: | ||||
|     branches: | ||||
|       - main | ||||
|       - "*LTS" | ||||
|     tags: | ||||
|       - "!*" # Do not execute on tags | ||||
|   pull_request: | ||||
|     types: [opened, synchronize, reopened, ready_for_review] | ||||
| jobs: | ||||
|   test: | ||||
|     if: github.event.pull_request.draft == false | ||||
|     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
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								.github/workflows/on-demand.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| 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" | ||||
							
								
								
									
										36
									
								
								.github/workflows/pr-bandit.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								.github/workflows/pr-bandit.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| name: Security Analysis | ||||
| on: | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - main | ||||
|       - "*LTS" | ||||
|     types: [opened, synchronize, reopened, ready_for_review] | ||||
|  | ||||
| jobs: | ||||
|   bandit: | ||||
|     if: github.event.pull_request.draft == false | ||||
|     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} | ||||
|           - { python-version: "3.10", 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 }}" | ||||
							
								
								
									
										32
									
								
								.github/workflows/pr-docs.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								.github/workflows/pr-docs.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| name: Document Linter | ||||
| on: | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - main | ||||
|       - "*LTS" | ||||
|     types: [opened, synchronize, reopened, ready_for_review] | ||||
|  | ||||
| jobs: | ||||
|   docsLinter: | ||||
|     if: github.event.pull_request.draft == false | ||||
|     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 }}" | ||||
							
								
								
									
										33
									
								
								.github/workflows/pr-linter.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								.github/workflows/pr-linter.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| name: Linter Checks | ||||
| on: | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - main | ||||
|       - "*LTS" | ||||
|     types: [opened, synchronize, reopened, ready_for_review] | ||||
|  | ||||
| jobs: | ||||
|   linter: | ||||
|     if: github.event.pull_request.draft == false | ||||
|     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
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								.github/workflows/pr-python-pypy.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| 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" | ||||
							
								
								
									
										35
									
								
								.github/workflows/pr-python37.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								.github/workflows/pr-python37.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| name: Python 3.7 Tests | ||||
| on: | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - main | ||||
|       - "*LTS" | ||||
|     types: [opened, synchronize, reopened, ready_for_review] | ||||
|  | ||||
| jobs: | ||||
|   testPy37: | ||||
|     if: github.event.pull_request.draft == false | ||||
|     name: ut-${{ matrix.config.tox-env }}-${{ matrix.os }} | ||||
|     runs-on: ${{ matrix.os }} | ||||
|     strategy: | ||||
|       fail-fast: true | ||||
|       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" | ||||
							
								
								
									
										35
									
								
								.github/workflows/pr-python38.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								.github/workflows/pr-python38.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| name: Python 3.8 Tests | ||||
| on: | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - main | ||||
|       - "*LTS" | ||||
|     types: [opened, synchronize, reopened, ready_for_review] | ||||
|  | ||||
| jobs: | ||||
|   testPy38: | ||||
|     if: github.event.pull_request.draft == false | ||||
|     name: ut-${{ matrix.config.tox-env }}-${{ matrix.os }} | ||||
|     runs-on: ${{ matrix.os }} | ||||
|     strategy: | ||||
|       fail-fast: true | ||||
|       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" | ||||
							
								
								
									
										47
									
								
								.github/workflows/pr-python39.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								.github/workflows/pr-python39.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| name: Python 3.9 Tests | ||||
| on: | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - main | ||||
|       - "*LTS" | ||||
|     types: [opened, synchronize, reopened, ready_for_review] | ||||
|  | ||||
| jobs: | ||||
|   testPy39: | ||||
|     if: github.event.pull_request.draft == false | ||||
|     name: ut-${{ matrix.config.tox-env }}-${{ matrix.os }} | ||||
|     runs-on: ${{ matrix.os }} | ||||
|     strategy: | ||||
|       fail-fast: true | ||||
|       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" | ||||
							
								
								
									
										35
									
								
								.github/workflows/pr-type-check.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								.github/workflows/pr-type-check.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| name: Typing Checks | ||||
| on: | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - main | ||||
|       - "*LTS" | ||||
|     types: [opened, synchronize, reopened, ready_for_review] | ||||
|  | ||||
| jobs: | ||||
|   typeChecking: | ||||
|     if: github.event.pull_request.draft == false | ||||
|     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 }}" | ||||
							
								
								
									
										37
									
								
								.github/workflows/pr-windows.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								.github/workflows/pr-windows.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| name: Run Unit Tests on Windows | ||||
| on: | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - main | ||||
|       - "*LTS" | ||||
|     types: [opened, synchronize, reopened, ready_for_review] | ||||
|  | ||||
| jobs: | ||||
|   testsOnWindows: | ||||
|     if: github.event.pull_request.draft == false | ||||
|     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
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								.github/workflows/publish-images.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| 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
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								.github/workflows/publish-package.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| 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" | ||||
							
								
								
									
										27
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								.travis.yml
									
									
									
									
									
								
							| @@ -31,6 +31,16 @@ matrix: | ||||
|       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.6 | ||||
|       name: "Python 3.6 Type checks" | ||||
| @@ -40,6 +50,10 @@ matrix: | ||||
|     - 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=lint | ||||
|       python: 3.6 | ||||
|       name: "Python 3.6 Linter checks" | ||||
| @@ -61,23 +75,28 @@ matrix: | ||||
|       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' | ||||
|       python: "nightly" | ||||
|       name: "Python nightly with Extensions" | ||||
|     - env: TOX_ENV=pyNightly-no-ext | ||||
|       python: 'nightly' | ||||
|       python: "nightly" | ||||
|       name: "Python nightly without Extensions" | ||||
|   allow_failures: | ||||
|     - env: TOX_ENV=pyNightly | ||||
|       python: 'nightly' | ||||
|       python: "nightly" | ||||
|       name: "Python nightly with Extensions" | ||||
|     - env: TOX_ENV=pyNightly-no-ext | ||||
|       python: 'nightly' | ||||
|       python: "nightly" | ||||
|       name: "Python nightly without Extensions" | ||||
| install: | ||||
|   - pip install -U tox | ||||
|   | ||||
							
								
								
									
										122
									
								
								CHANGELOG.rst
									
									
									
									
									
								
							
							
						
						
									
										122
									
								
								CHANGELOG.rst
									
									
									
									
									
								
							| @@ -1,3 +1,125 @@ | ||||
| Version 20.12.5 | ||||
| =============== | ||||
|  | ||||
| Bugfixes | ||||
| ******** | ||||
|  | ||||
|   * | ||||
|    `#2366 <https://github.com/sanic-org/sanic/pull/2366>`_ | ||||
|    websocket dependency for websockets 9.1 security fix | ||||
|  | ||||
| Version 20.12.0 | ||||
| =============== | ||||
|  | ||||
| Features | ||||
| ******** | ||||
|  | ||||
|   * | ||||
|     `#1945 <https://github.com/huge-success/sanic/pull/1945>`_ | ||||
|     Static route more verbose if file not found | ||||
|  | ||||
|   * | ||||
|     `#1954 <https://github.com/huge-success/sanic/pull/1954>`_ | ||||
|     Fix static routes registration on a blueprint | ||||
|  | ||||
|   * | ||||
|     `#1961 <https://github.com/huge-success/sanic/pull/1961>`_ | ||||
|     Add Python 3.9 support | ||||
|  | ||||
|   * | ||||
|     `#1962 <https://github.com/huge-success/sanic/pull/1962>`_ | ||||
|     Sanic CLI upgrade | ||||
|  | ||||
|   * | ||||
|     `#1967 <https://github.com/huge-success/sanic/pull/1967>`_ | ||||
|     Update aiofile version requirements | ||||
|  | ||||
|   * | ||||
|     `#1969 <https://github.com/huge-success/sanic/pull/1969>`_ | ||||
|     Update multidict version requirements | ||||
|  | ||||
|   * | ||||
|     `#1970 <https://github.com/huge-success/sanic/pull/1970>`_ | ||||
|     Add py.typed file | ||||
|  | ||||
|   * | ||||
|     `#1972 <https://github.com/huge-success/sanic/pull/1972>`_ | ||||
|     Speed optimization in request handler | ||||
|  | ||||
|   * | ||||
|     `#1979 <https://github.com/huge-success/sanic/pull/1979>`_ | ||||
|     Add app registry and Sanic class level app retrieval | ||||
|  | ||||
| Bugfixes | ||||
| ******** | ||||
|  | ||||
|   * | ||||
|     `#1965 <https://github.com/huge-success/sanic/pull/1965>`_ | ||||
|     Fix Chunked Transport-Encoding in ASGI streaming response | ||||
|  | ||||
| Deprecations and Removals | ||||
| ************************* | ||||
|  | ||||
|   * | ||||
|     `#1981 <https://github.com/huge-success/sanic/pull/1981>`_ | ||||
|     Cleanup and remove deprecated code | ||||
|  | ||||
| Developer infrastructure | ||||
| ************************ | ||||
|  | ||||
|   * | ||||
|     `#1956 <https://github.com/huge-success/sanic/pull/1956>`_ | ||||
|     Fix load module test | ||||
|  | ||||
|   * | ||||
|     `#1973 <https://github.com/huge-success/sanic/pull/1973>`_ | ||||
|     Transition Travis from .org to .com | ||||
|  | ||||
|   * | ||||
|     `#1986 <https://github.com/huge-success/sanic/pull/1986>`_ | ||||
|     Update tox requirements | ||||
|  | ||||
| Improved Documentation | ||||
| ********************** | ||||
|  | ||||
|   * | ||||
|     `#1951 <https://github.com/huge-success/sanic/pull/1951>`_ | ||||
|     Documentation improvements | ||||
|  | ||||
|   * | ||||
|     `#1983 <https://github.com/huge-success/sanic/pull/1983>`_ | ||||
|     Remove duplicate contents in testing.rst | ||||
|  | ||||
|   * | ||||
|     `#1984 <https://github.com/huge-success/sanic/pull/1984>`_ | ||||
|     Fix typo in routing.rst | ||||
|  | ||||
|  | ||||
| Version 20.9.1 | ||||
| =============== | ||||
|  | ||||
| Bugfixes | ||||
| ******** | ||||
|  | ||||
|   * | ||||
|     `#1954 <https://github.com/huge-success/sanic/pull/1954>`_ | ||||
|     Fix static route registration on blueprints | ||||
|   * | ||||
|     `#1957 <https://github.com/huge-success/sanic/pull/1957>`_ | ||||
|     Removes duplicate headers in ASGI streaming body | ||||
|  | ||||
|  | ||||
| Version 19.12.3 | ||||
| =============== | ||||
|  | ||||
| Bugfixes | ||||
| ******** | ||||
|  | ||||
|   * | ||||
|     `#1959 <https://github.com/huge-success/sanic/pull/1959>`_ | ||||
|     Removes duplicate headers in ASGI streaming body | ||||
|  | ||||
|  | ||||
| Version 20.9.0 | ||||
| =============== | ||||
|  | ||||
|   | ||||
| @@ -26,8 +26,8 @@ Sanic | Build fast. Run fast. | ||||
|    :target: https://gitter.im/sanic-python/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge | ||||
| .. |Codecov| image:: https://codecov.io/gh/huge-success/sanic/branch/master/graph/badge.svg | ||||
|     :target: https://codecov.io/gh/huge-success/sanic | ||||
| .. |Build Status| image:: https://travis-ci.org/huge-success/sanic.svg?branch=master | ||||
|    :target: https://travis-ci.org/huge-success/sanic | ||||
| .. |Build Status| image:: https://travis-ci.com/huge-success/sanic.svg?branch=master | ||||
|    :target: https://travis-ci.com/huge-success/sanic | ||||
| .. |AppVeyor Build Status| image:: https://ci.appveyor.com/api/projects/status/d8pt3ids0ynexi8c/branch/master?svg=true | ||||
|    :target: https://ci.appveyor.com/project/huge-success/sanic | ||||
| .. |Documentation| image:: https://readthedocs.org/projects/sanic/badge/?version=latest | ||||
|   | ||||
							
								
								
									
										1
									
								
								changelogs/1970.misc.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								changelogs/1970.misc.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| Adds py.typed file to expose type information to other packages. | ||||
| @@ -60,3 +60,26 @@ Open the address `http://0.0.0.0:8000 <http://0.0.0.0:8000>`_ in your web browse | ||||
| the message *Hello world!*. | ||||
|  | ||||
| You now have a working Sanic server! | ||||
|  | ||||
| 5. Application registry | ||||
| ----------------------- | ||||
|  | ||||
| When you instantiate a Sanic instance, that can be retrieved at a later time from the Sanic app registry. This can be useful, for example, if you need to access your Sanic instance from a location where it is not otherwise accessible. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     # ./path/to/server.py | ||||
|     from sanic import Sanic | ||||
|  | ||||
|     app = Sanic("my_awesome_server") | ||||
|  | ||||
|     # ./path/to/somewhere_else.py | ||||
|     from sanic import Sanic | ||||
|  | ||||
|     app = Sanic.get_app("my_awesome_server") | ||||
|  | ||||
| If you call ``Sanic.get_app("non-existing")`` on an app that does not exist, it will raise ``SanicException`` by default. You can, instead, force the method to return a new instance of ``Sanic`` with that name: | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     app = Sanic.get_app("my_awesome_server", force_create=True) | ||||
|   | ||||
| @@ -133,7 +133,7 @@ which allows the handler function to work with any of the HTTP methods in the li | ||||
|     async def get_handler(request): | ||||
|         return text('GET request - {}'.format(request.args)) | ||||
|  | ||||
| There is also an optional `host` argument (which can be a list or a string). This restricts a route to the host or hosts provided. If there is a also a route with no host, it will be the default. | ||||
| There is also an optional `host` argument (which can be a list or a string). This restricts a route to the host or hosts provided. If there is also a route with no host, it will be the default. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|   | ||||
| @@ -88,5 +88,5 @@ When `stream_large_files` is `True`, Sanic will use `file_stream()` instead of ` | ||||
|  | ||||
|     app = Sanic(__name__) | ||||
|  | ||||
|     chunk_size = 1024 * 1024 * 8 # Set chunk size to 8KB | ||||
|     chunk_size = 1024 * 1024 * 8 # Set chunk size to 8MiB | ||||
|     app.static('/large_video.mp4', '/home/ubuntu/large_video.mp4', stream_large_files=chunk_size) | ||||
|   | ||||
| @@ -58,10 +58,6 @@ More information about | ||||
| the available arguments to `httpx` can be found | ||||
| [in the documentation for `httpx <https://www.encode.io/httpx/>`_. | ||||
|  | ||||
| Additionally, Sanic has an asynchronous testing client. The difference is that the async client will not stand up an | ||||
| instance of your application, but will instead reach inside it using ASGI. All listeners and middleware are still | ||||
| executed. | ||||
|  | ||||
| .. code-block:: python | ||||
|     @pytest.mark.asyncio | ||||
|     async def test_index_returns_200(): | ||||
|   | ||||
| @@ -7,6 +7,7 @@ | ||||
| """ | ||||
|  | ||||
| from pathlib import Path | ||||
|  | ||||
| from sanic import Sanic, response | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,28 +1,83 @@ | ||||
| import os | ||||
| import sys | ||||
|  | ||||
| from argparse import ArgumentParser | ||||
| from argparse import ArgumentParser, RawDescriptionHelpFormatter | ||||
| from importlib import import_module | ||||
| from typing import Any, Dict, Optional | ||||
|  | ||||
| from sanic import __version__ | ||||
| from sanic.app import Sanic | ||||
| from sanic.config import BASE_LOGO | ||||
| from sanic.log import logger | ||||
|  | ||||
|  | ||||
| class SanicArgumentParser(ArgumentParser): | ||||
|     def add_bool_arguments(self, *args, **kwargs): | ||||
|         group = self.add_mutually_exclusive_group() | ||||
|         group.add_argument(*args, action="store_true", **kwargs) | ||||
|         kwargs["help"] = "no " + kwargs["help"] | ||||
|         group.add_argument( | ||||
|             "--no-" + args[0][2:], *args[1:], action="store_false", **kwargs | ||||
|         ) | ||||
|  | ||||
|  | ||||
| def main(): | ||||
|     parser = ArgumentParser(prog="sanic") | ||||
|     parser.add_argument("--host", dest="host", type=str, default="127.0.0.1") | ||||
|     parser.add_argument("--port", dest="port", type=int, default=8000) | ||||
|     parser.add_argument("--unix", dest="unix", type=str, default="") | ||||
|     parser = SanicArgumentParser( | ||||
|         prog="sanic", | ||||
|         description=BASE_LOGO, | ||||
|         formatter_class=RawDescriptionHelpFormatter, | ||||
|     ) | ||||
|     parser.add_argument( | ||||
|         "-H", | ||||
|         "--host", | ||||
|         dest="host", | ||||
|         type=str, | ||||
|         default="127.0.0.1", | ||||
|         help="host address [default 127.0.0.1]", | ||||
|     ) | ||||
|     parser.add_argument( | ||||
|         "-p", | ||||
|         "--port", | ||||
|         dest="port", | ||||
|         type=int, | ||||
|         default=8000, | ||||
|         help="port to serve on [default 8000]", | ||||
|     ) | ||||
|     parser.add_argument( | ||||
|         "-u", | ||||
|         "--unix", | ||||
|         dest="unix", | ||||
|         type=str, | ||||
|         default="", | ||||
|         help="location of unix socket", | ||||
|     ) | ||||
|     parser.add_argument( | ||||
|         "--cert", dest="cert", type=str, help="location of certificate for SSL" | ||||
|     ) | ||||
|     parser.add_argument( | ||||
|         "--key", dest="key", type=str, help="location of keyfile for SSL." | ||||
|     ) | ||||
|     parser.add_argument("--workers", dest="workers", type=int, default=1) | ||||
|     parser.add_argument( | ||||
|         "-w", | ||||
|         "--workers", | ||||
|         dest="workers", | ||||
|         type=int, | ||||
|         default=1, | ||||
|         help="number of worker processes [default 1]", | ||||
|     ) | ||||
|     parser.add_argument("--debug", dest="debug", action="store_true") | ||||
|     parser.add_argument("module") | ||||
|     parser.add_bool_arguments( | ||||
|         "--access-logs", dest="access_log", help="display access logs" | ||||
|     ) | ||||
|     parser.add_argument( | ||||
|         "-v", | ||||
|         "--version", | ||||
|         action="version", | ||||
|         version=f"Sanic {__version__}", | ||||
|     ) | ||||
|     parser.add_argument( | ||||
|         "module", help="path to your Sanic app. Example: path.to.server:app" | ||||
|     ) | ||||
|     args = parser.parse_args() | ||||
|  | ||||
|     try: | ||||
| @@ -30,6 +85,9 @@ def main(): | ||||
|         if module_path not in sys.path: | ||||
|             sys.path.append(module_path) | ||||
|  | ||||
|         if ":" in args.module: | ||||
|             module_name, app_name = args.module.rsplit(":", 1) | ||||
|         else: | ||||
|             module_parts = args.module.split(".") | ||||
|             module_name = ".".join(module_parts[:-1]) | ||||
|             app_name = module_parts[-1] | ||||
| @@ -57,6 +115,7 @@ def main(): | ||||
|             unix=args.unix, | ||||
|             workers=args.workers, | ||||
|             debug=args.debug, | ||||
|             access_log=args.access_log, | ||||
|             ssl=ssl, | ||||
|         ) | ||||
|     except ImportError as e: | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| __version__ = "20.9.0" | ||||
| __version__ = "20.12.7" | ||||
|   | ||||
							
								
								
									
										163
									
								
								sanic/app.py
									
									
									
									
									
								
							
							
						
						
									
										163
									
								
								sanic/app.py
									
									
									
									
									
								
							| @@ -2,17 +2,18 @@ import logging | ||||
| import logging.config | ||||
| import os | ||||
| import re | ||||
| import warnings | ||||
| import sys | ||||
|  | ||||
| from asyncio import CancelledError, Protocol, ensure_future, get_event_loop | ||||
| from collections import defaultdict, deque | ||||
| from functools import partial | ||||
| from inspect import getmodulename, isawaitable, signature, stack | ||||
| from inspect import isawaitable, signature | ||||
| from socket import socket | ||||
| from ssl import Purpose, SSLContext, create_default_context | ||||
| from traceback import format_exc | ||||
| from typing import Any, Dict, Optional, Type, Union | ||||
| from urllib.parse import urlencode, urlunparse | ||||
| from warnings import warn | ||||
|  | ||||
| from sanic import reloader_helpers | ||||
| from sanic.asgi import ASGIApp | ||||
| @@ -38,6 +39,9 @@ from sanic.websocket import ConnectionClosed, WebSocketProtocol | ||||
|  | ||||
|  | ||||
| class Sanic: | ||||
|     _app_registry: Dict[str, "Sanic"] = {} | ||||
|     test_mode = False | ||||
|  | ||||
|     def __init__( | ||||
|         self, | ||||
|         name=None, | ||||
| @@ -48,27 +52,35 @@ class Sanic: | ||||
|         strict_slashes=False, | ||||
|         log_config=None, | ||||
|         configure_logging=True, | ||||
|         register=None, | ||||
|     ): | ||||
|  | ||||
|         # Get name from previous stack frame | ||||
|         if name is None: | ||||
|             warnings.warn( | ||||
|                 "Sanic(name=None) is deprecated and None value support " | ||||
|                 "for `name` will be removed in the next release. " | ||||
|             raise SanicException( | ||||
|                 "Sanic instance cannot be unnamed. " | ||||
|                 "Please use Sanic(name='your_application_name') instead.", | ||||
|                 DeprecationWarning, | ||||
|                 stacklevel=2, | ||||
|             ) | ||||
|             frame_records = stack()[1] | ||||
|             name = getmodulename(frame_records[1]) | ||||
|  | ||||
|         # logging | ||||
|         if configure_logging: | ||||
|             logging.config.dictConfig(log_config or LOGGING_CONFIG_DEFAULTS) | ||||
|  | ||||
|         if sys.version_info >= (3, 10): | ||||
|             error_logger.error( | ||||
|                 "Unsupported version of Python has been detected.\n\nPython " | ||||
|                 f"version {sys.version} is not supported by this version of " | ||||
|                 "Sanic. There is a security advisory that has been issued for " | ||||
|                 "Sanic v20.12 while running Python 3.10+. You should either " | ||||
|                 "use a supported version of Python (v3.6 - v3.9) or upgrade " | ||||
|                 "Sanic to v21+.\n\nPlease see https://github.com/sanic-org/" | ||||
|                 "sanic/security/advisories/GHSA-7p79-6x2v-5h88 for " | ||||
|                 "more information.\n" | ||||
|             ) | ||||
|  | ||||
|         self.name = name | ||||
|         self.asgi = False | ||||
|         self.router = router or Router() | ||||
|         self.router = router or Router(self) | ||||
|         self.request_class = request_class | ||||
|         self.error_handler = error_handler or ErrorHandler() | ||||
|         self.config = Config(load_env=load_env) | ||||
| @@ -90,7 +102,12 @@ class Sanic: | ||||
|         self.named_response_middleware = {} | ||||
|         # Register alternative method names | ||||
|         self.go_fast = self.run | ||||
|         self.test_mode = False | ||||
|  | ||||
|         if register is not None: | ||||
|             self.config.REGISTER = register | ||||
|  | ||||
|         if self.config.REGISTER: | ||||
|             self.__class__.register_app(self) | ||||
|  | ||||
|     @property | ||||
|     def loop(self): | ||||
| @@ -491,9 +508,7 @@ class Sanic: | ||||
|             websocket_handler = partial( | ||||
|                 self._websocket_handler, handler, subprotocols=subprotocols | ||||
|             ) | ||||
|             websocket_handler.__name__ = ( | ||||
|                 "websocket_handler_" + handler.__name__ | ||||
|             ) | ||||
|             websocket_handler.__name__ = handler.__name__ | ||||
|             routes.extend( | ||||
|                 self.router.add( | ||||
|                     uri=uri, | ||||
| @@ -676,9 +691,10 @@ class Sanic: | ||||
|         :param strict_slashes: Instruct :class:`Sanic` to check if the request | ||||
|             URLs need to terminate with a */* | ||||
|         :param content_type: user defined content type for header | ||||
|         :return: None | ||||
|         :return: routes registered on the router | ||||
|         :rtype: List[sanic.router.Route] | ||||
|         """ | ||||
|         static_register( | ||||
|         return static_register( | ||||
|             self, | ||||
|             uri, | ||||
|             file_or_directory, | ||||
| @@ -713,28 +729,6 @@ class Sanic: | ||||
|             self._blueprint_order.append(blueprint) | ||||
|         blueprint.register(self, options) | ||||
|  | ||||
|     def register_blueprint(self, *args, **kwargs): | ||||
|         """ | ||||
|         Proxy method provided for invoking the :func:`blueprint` method | ||||
|  | ||||
|         .. note:: | ||||
|             To be deprecated in 1.0. Use :func:`blueprint` instead. | ||||
|  | ||||
|         :param args: Blueprint object or (list, tuple) thereof | ||||
|         :param kwargs: option dictionary with blueprint defaults | ||||
|         :return: None | ||||
|         """ | ||||
|  | ||||
|         if self.debug: | ||||
|             warnings.simplefilter("default") | ||||
|         warnings.warn( | ||||
|             "Use of register_blueprint will be deprecated in " | ||||
|             "version 1.0.  Please use the blueprint method" | ||||
|             " instead", | ||||
|             DeprecationWarning, | ||||
|         ) | ||||
|         return self.blueprint(*args, **kwargs) | ||||
|  | ||||
|     def url_for(self, view_name: str, **kwargs): | ||||
|         r"""Build a URL based on a view name and the values provided. | ||||
|  | ||||
| @@ -765,6 +759,24 @@ class Sanic: | ||||
|             kw.update(name=view_name) | ||||
|  | ||||
|         uri, route = self.router.find_route_by_view_name(view_name, **kw) | ||||
|  | ||||
|         # TODO(laggardkernel): this fix should be removed in v21.3. | ||||
|         # Try again without the unnecessary prefix "websocket_handler_", | ||||
|         # which was added by accident on non-blueprint handlers. GH-2021 | ||||
|         if not (uri and route) and view_name.startswith("websocket_handler_"): | ||||
|             view_name = view_name[18:] | ||||
|             uri, route = self.router.find_route_by_view_name(view_name, **kw) | ||||
|             if uri and route: | ||||
|                 warn( | ||||
|                     "The bug of adding unnecessary `websocket_handler_` " | ||||
|                     "prefix in param `view_name` for non-blueprint handlers " | ||||
|                     "is fixed. This backward support will be removed in " | ||||
|                     "v21.3. Please update `Sanic.url_for()` callings in your " | ||||
|                     "code soon.", | ||||
|                     DeprecationWarning, | ||||
|                     stacklevel=2, | ||||
|                 ) | ||||
|  | ||||
|         if not (uri and route): | ||||
|             raise URLBuildError( | ||||
|                 f"Endpoint with name `{view_name}` was not found" | ||||
| @@ -899,7 +911,9 @@ class Sanic: | ||||
|         name = None | ||||
|         try: | ||||
|             # Fetch handler from router | ||||
|             handler, args, kwargs, uri, name = self.router.get(request) | ||||
|             handler, args, kwargs, uri, name, endpoint = self.router.get( | ||||
|                 request | ||||
|             ) | ||||
|  | ||||
|             # -------------------------------------------- # | ||||
|             # Request Middleware | ||||
| @@ -921,16 +935,8 @@ class Sanic: | ||||
|                             "handler from the router" | ||||
|                         ) | ||||
|                     ) | ||||
|                 else: | ||||
|                     if not getattr(handler, "__blueprintname__", False): | ||||
|                         request.endpoint = self._build_endpoint_name( | ||||
|                             handler.__name__ | ||||
|                         ) | ||||
|                     else: | ||||
|                         request.endpoint = self._build_endpoint_name( | ||||
|                             getattr(handler, "__blueprintname__", ""), | ||||
|                             handler.__name__, | ||||
|                         ) | ||||
|  | ||||
|                 request.endpoint = endpoint | ||||
|  | ||||
|                 # Run response handler | ||||
|                 response = handler(request, *args, **kwargs) | ||||
| @@ -1031,7 +1037,6 @@ class Sanic: | ||||
|         workers: int = 1, | ||||
|         protocol: Optional[Type[Protocol]] = None, | ||||
|         backlog: int = 100, | ||||
|         stop_event: Any = None, | ||||
|         register_sys_signals: bool = True, | ||||
|         access_log: Optional[bool] = None, | ||||
|         unix: Optional[str] = None, | ||||
| @@ -1061,9 +1066,6 @@ class Sanic: | ||||
|         :param backlog: a number of unaccepted connections that the system | ||||
|                         will allow before refusing new connections | ||||
|         :type backlog: int | ||||
|         :param stop_event: event to be triggered | ||||
|                            before stopping the app - deprecated | ||||
|         :type stop_event: None | ||||
|         :param register_sys_signals: Register SIG* events | ||||
|         :type register_sys_signals: bool | ||||
|         :param access_log: Enables writing access logs (slows server) | ||||
| @@ -1091,13 +1093,6 @@ class Sanic: | ||||
|             protocol = ( | ||||
|                 WebSocketProtocol if self.websocket_enabled else HttpProtocol | ||||
|             ) | ||||
|         if stop_event is not None: | ||||
|             if debug: | ||||
|                 warnings.simplefilter("default") | ||||
|             warnings.warn( | ||||
|                 "stop_event will be removed from future versions.", | ||||
|                 DeprecationWarning, | ||||
|             ) | ||||
|         # if access_log is passed explicitly change config.ACCESS_LOG | ||||
|         if access_log is not None: | ||||
|             self.config.ACCESS_LOG = access_log | ||||
| @@ -1154,7 +1149,6 @@ class Sanic: | ||||
|         sock: Optional[socket] = None, | ||||
|         protocol: Type[Protocol] = None, | ||||
|         backlog: int = 100, | ||||
|         stop_event: Any = None, | ||||
|         access_log: Optional[bool] = None, | ||||
|         unix: Optional[str] = None, | ||||
|         return_asyncio_server=False, | ||||
| @@ -1187,9 +1181,6 @@ class Sanic: | ||||
|         :param backlog: a number of unaccepted connections that the system | ||||
|                         will allow before refusing new connections | ||||
|         :type backlog: int | ||||
|         :param stop_event: event to be triggered | ||||
|                            before stopping the app - deprecated | ||||
|         :type stop_event: None | ||||
|         :param access_log: Enables writing access logs (slows server) | ||||
|         :type access_log: bool | ||||
|         :param return_asyncio_server: flag that defines whether there's a need | ||||
| @@ -1209,13 +1200,6 @@ class Sanic: | ||||
|             protocol = ( | ||||
|                 WebSocketProtocol if self.websocket_enabled else HttpProtocol | ||||
|             ) | ||||
|         if stop_event is not None: | ||||
|             if debug: | ||||
|                 warnings.simplefilter("default") | ||||
|             warnings.warn( | ||||
|                 "stop_event will be removed from future versions.", | ||||
|                 DeprecationWarning, | ||||
|             ) | ||||
|         # if access_log is passed explicitly change config.ACCESS_LOG | ||||
|         if access_log is not None: | ||||
|             self.config.ACCESS_LOG = access_log | ||||
| @@ -1297,7 +1281,6 @@ class Sanic: | ||||
|         loop=None, | ||||
|         protocol=HttpProtocol, | ||||
|         backlog=100, | ||||
|         stop_event=None, | ||||
|         register_sys_signals=True, | ||||
|         run_async=False, | ||||
|         auto_reload=False, | ||||
| @@ -1312,13 +1295,6 @@ class Sanic: | ||||
|             context = create_default_context(purpose=Purpose.CLIENT_AUTH) | ||||
|             context.load_cert_chain(cert, keyfile=key) | ||||
|             ssl = context | ||||
|         if stop_event is not None: | ||||
|             if debug: | ||||
|                 warnings.simplefilter("default") | ||||
|             warnings.warn( | ||||
|                 "stop_event will be removed from future versions.", | ||||
|                 DeprecationWarning, | ||||
|             ) | ||||
|         if self.config.PROXIES_COUNT and self.config.PROXIES_COUNT < 0: | ||||
|             raise ValueError( | ||||
|                 "PROXIES_COUNT cannot be negative. " | ||||
| @@ -1453,12 +1429,41 @@ class Sanic: | ||||
|         asgi_app = await ASGIApp.create(self, scope, receive, send) | ||||
|         await asgi_app() | ||||
|  | ||||
|     _asgi_single_callable = True  # We conform to ASGI 3.0 single-callable | ||||
|  | ||||
|     # -------------------------------------------------------------------- # | ||||
|     # Configuration | ||||
|     # -------------------------------------------------------------------- # | ||||
|  | ||||
|     def update_config(self, config: Union[bytes, str, dict, Any]): | ||||
|         """Update app.config. | ||||
|  | ||||
|         Please refer to config.py::Config.update_config for documentation.""" | ||||
|  | ||||
|         self.config.update_config(config) | ||||
|  | ||||
|     # -------------------------------------------------------------------- # | ||||
|     # Class methods | ||||
|     # -------------------------------------------------------------------- # | ||||
|  | ||||
|     @classmethod | ||||
|     def register_app(cls, app: "Sanic") -> None: | ||||
|         """Register a Sanic instance""" | ||||
|         if not isinstance(app, cls): | ||||
|             raise SanicException("Registered app must be an instance of Sanic") | ||||
|  | ||||
|         name = app.name | ||||
|         if name in cls._app_registry and not cls.test_mode: | ||||
|             raise SanicException(f'Sanic app name "{name}" already in use.') | ||||
|  | ||||
|         cls._app_registry[name] = app | ||||
|  | ||||
|     @classmethod | ||||
|     def get_app(cls, name: str, *, force_create: bool = False) -> "Sanic": | ||||
|         """Retrieve an instantiated Sanic instance""" | ||||
|         try: | ||||
|             return cls._app_registry[name] | ||||
|         except KeyError: | ||||
|             if force_create: | ||||
|                 return cls(name) | ||||
|             raise SanicException(f'Sanic app name "{name}" not found.') | ||||
|   | ||||
| @@ -312,13 +312,19 @@ class ASGIApp: | ||||
|         callback = None if self.ws else self.stream_callback | ||||
|         await handler(self.request, None, callback) | ||||
|  | ||||
|     async def stream_callback(self, response: HTTPResponse) -> None: | ||||
|     _asgi_single_callable = True  # We conform to ASGI 3.0 single-callable | ||||
|  | ||||
|     async def stream_callback( | ||||
|         self, response: Union[HTTPResponse, StreamingHTTPResponse] | ||||
|     ) -> None: | ||||
|         """ | ||||
|         Write the response. | ||||
|         """ | ||||
|         headers: List[Tuple[bytes, bytes]] = [] | ||||
|         cookies: Dict[str, str] = {} | ||||
|         content_length: List[str] = [] | ||||
|         try: | ||||
|             content_length = response.headers.popall("content-length", []) | ||||
|             cookies = { | ||||
|                 v.key: v | ||||
|                 for _, v in list( | ||||
| @@ -350,11 +356,23 @@ class ASGIApp: | ||||
|                 if name not in (b"Set-Cookie",) | ||||
|             ] | ||||
|  | ||||
|         if "content-length" not in response.headers and not isinstance( | ||||
|             response, StreamingHTTPResponse | ||||
|         ): | ||||
|         response.asgi = True | ||||
|         is_streaming = isinstance(response, StreamingHTTPResponse) | ||||
|         if is_streaming and getattr(response, "chunked", False): | ||||
|             # disable sanic chunking, this is done at the ASGI-server level | ||||
|             setattr(response, "chunked", False) | ||||
|             # content-length header is removed to signal to the ASGI-server | ||||
|             # to use automatic-chunking if it supports it | ||||
|         elif len(content_length) > 0: | ||||
|             headers += [ | ||||
|                 (b"content-length", str(len(response.body)).encode("latin-1")) | ||||
|                 (b"content-length", str(content_length[0]).encode("latin-1")) | ||||
|             ] | ||||
|         elif not is_streaming: | ||||
|             headers += [ | ||||
|                 ( | ||||
|                     b"content-length", | ||||
|                     str(len(getattr(response, "body", b""))).encode("latin-1"), | ||||
|                 ) | ||||
|             ] | ||||
|  | ||||
|         if "content-type" not in response.headers: | ||||
|   | ||||
| @@ -143,7 +143,18 @@ class Blueprint: | ||||
|             if _routes: | ||||
|                 routes += _routes | ||||
|  | ||||
|         # Static Files | ||||
|         for future in self.statics: | ||||
|             # Prepend the blueprint URI prefix if available | ||||
|             uri = url_prefix + future.uri if url_prefix else future.uri | ||||
|             _routes = app.static( | ||||
|                 uri, future.file_or_directory, *future.args, **future.kwargs | ||||
|             ) | ||||
|             if _routes: | ||||
|                 routes += _routes | ||||
|  | ||||
|         route_names = [route.name for route in routes if route] | ||||
|  | ||||
|         # Middleware | ||||
|         for future in self.middlewares: | ||||
|             if future.args or future.kwargs: | ||||
| @@ -160,14 +171,6 @@ class Blueprint: | ||||
|         for future in self.exceptions: | ||||
|             app.exception(*future.args, **future.kwargs)(future.handler) | ||||
|  | ||||
|         # Static Files | ||||
|         for future in self.statics: | ||||
|             # Prepend the blueprint URI prefix if available | ||||
|             uri = url_prefix + future.uri if url_prefix else future.uri | ||||
|             app.static( | ||||
|                 uri, future.file_or_directory, *future.args, **future.kwargs | ||||
|             ) | ||||
|  | ||||
|         # Event listeners | ||||
|         for event, listeners in self.listeners.items(): | ||||
|             for listener in listeners: | ||||
|   | ||||
| @@ -20,7 +20,6 @@ if use_trio: | ||||
|     def stat_async(path): | ||||
|         return Path(path).stat() | ||||
|  | ||||
|  | ||||
| else: | ||||
|     from aiofiles import open as aio_open  # type: ignore | ||||
|     from aiofiles.os import stat as stat_async  # type: ignore  # noqa: F401 | ||||
|   | ||||
| @@ -40,6 +40,7 @@ DEFAULT_CONFIG = { | ||||
|     "PROXIES_COUNT": None, | ||||
|     "FORWARDED_FOR_HEADER": "X-Forwarded-For", | ||||
|     "FALLBACK_ERROR_FORMAT": "html", | ||||
|     "REGISTER": True, | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -179,8 +179,8 @@ def abort(status_code, message=None): | ||||
|     message appropriate for the given status code, unless provided. | ||||
|  | ||||
|     :param status_code: The HTTP status code to return. | ||||
|     :param message: The HTTP response body. Defaults to the messages | ||||
|                     in response.py for the given status code. | ||||
|     :param message: The HTTP response body. Defaults to the messages in | ||||
|     STATUS_CODES from sanic.helpers for the given status code. | ||||
|     """ | ||||
|     if message is None: | ||||
|         message = STATUS_CODES.get(status_code) | ||||
|   | ||||
							
								
								
									
										0
									
								
								sanic/py.typed
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								sanic/py.typed
									
									
									
									
									
										Normal file
									
								
							| @@ -136,15 +136,18 @@ class Request: | ||||
|         return f"<{class_name}: {self.method} {self.path}>" | ||||
|  | ||||
|     def body_init(self): | ||||
|         """.. deprecated:: 20.3""" | ||||
|         """.. deprecated:: 20.3 | ||||
|         To be removed in 21.3""" | ||||
|         self.body = [] | ||||
|  | ||||
|     def body_push(self, data): | ||||
|         """.. deprecated:: 20.3""" | ||||
|         """.. deprecated:: 20.3 | ||||
|         To be removed in 21.3""" | ||||
|         self.body.append(data) | ||||
|  | ||||
|     def body_finish(self): | ||||
|         """.. deprecated:: 20.3""" | ||||
|         """.. deprecated:: 20.3 | ||||
|         To be removed in 21.3""" | ||||
|         self.body = b"".join(self.body) | ||||
|  | ||||
|     async def receive_body(self): | ||||
| @@ -262,9 +265,12 @@ class Request: | ||||
|         :type errors: str | ||||
|         :return: RequestParameters | ||||
|         """ | ||||
|         if not self.parsed_args[ | ||||
|             (keep_blank_values, strict_parsing, encoding, errors) | ||||
|         ]: | ||||
|         if ( | ||||
|             keep_blank_values, | ||||
|             strict_parsing, | ||||
|             encoding, | ||||
|             errors, | ||||
|         ) not in self.parsed_args: | ||||
|             if self.query_string: | ||||
|                 self.parsed_args[ | ||||
|                     (keep_blank_values, strict_parsing, encoding, errors) | ||||
| @@ -318,9 +324,12 @@ class Request: | ||||
|         :type errors: str | ||||
|         :return: list | ||||
|         """ | ||||
|         if not self.parsed_not_grouped_args[ | ||||
|             (keep_blank_values, strict_parsing, encoding, errors) | ||||
|         ]: | ||||
|         if ( | ||||
|             keep_blank_values, | ||||
|             strict_parsing, | ||||
|             encoding, | ||||
|             errors, | ||||
|         ) not in self.parsed_not_grouped_args: | ||||
|             if self.query_string: | ||||
|                 self.parsed_not_grouped_args[ | ||||
|                     (keep_blank_values, strict_parsing, encoding, errors) | ||||
|   | ||||
| @@ -1,5 +1,3 @@ | ||||
| import warnings | ||||
|  | ||||
| from functools import partial | ||||
| from mimetypes import guess_type | ||||
| from os import path | ||||
| @@ -22,7 +20,12 @@ except ImportError: | ||||
|  | ||||
|  | ||||
| class BaseHTTPResponse: | ||||
|     def __init__(self): | ||||
|         self.asgi = False | ||||
|  | ||||
|     def _encode_body(self, data): | ||||
|         if data is None: | ||||
|             return b"" | ||||
|         return data.encode() if hasattr(data, "encode") else data | ||||
|  | ||||
|     def _parse_headers(self): | ||||
| @@ -42,7 +45,7 @@ class BaseHTTPResponse: | ||||
|         body=b"", | ||||
|     ): | ||||
|         """.. deprecated:: 20.3: | ||||
|         This function is not public API and will be removed.""" | ||||
|         This function is not public API and will be removed in 21.3.""" | ||||
|  | ||||
|         # self.headers get priority over content_type | ||||
|         if self.content_type and "Content-Type" not in self.headers: | ||||
| @@ -80,6 +83,8 @@ class StreamingHTTPResponse(BaseHTTPResponse): | ||||
|         content_type="text/plain; charset=utf-8", | ||||
|         chunked=True, | ||||
|     ): | ||||
|         super().__init__() | ||||
|  | ||||
|         self.content_type = content_type | ||||
|         self.streaming_fn = streaming_fn | ||||
|         self.status = status | ||||
| @@ -95,6 +100,8 @@ class StreamingHTTPResponse(BaseHTTPResponse): | ||||
|         """ | ||||
|         data = self._encode_body(data) | ||||
|  | ||||
|         # `chunked` will always be False in ASGI-mode, even if the underlying | ||||
|         # ASGI Transport implements Chunked transport. That does it itself. | ||||
|         if self.chunked: | ||||
|             await self.protocol.push_data(b"%x\r\n%b\r\n" % (len(data), data)) | ||||
|         else: | ||||
| @@ -109,6 +116,7 @@ class StreamingHTTPResponse(BaseHTTPResponse): | ||||
|         """ | ||||
|         if version != "1.1": | ||||
|             self.chunked = False | ||||
|         if not getattr(self, "asgi", False): | ||||
|             headers = self.get_headers( | ||||
|                 version, | ||||
|                 keep_alive=keep_alive, | ||||
| @@ -141,20 +149,15 @@ class HTTPResponse(BaseHTTPResponse): | ||||
|         status=200, | ||||
|         headers=None, | ||||
|         content_type=None, | ||||
|         body_bytes=b"", | ||||
|     ): | ||||
|         super().__init__() | ||||
|  | ||||
|         self.content_type = content_type | ||||
|         self.body = body_bytes if body is None else self._encode_body(body) | ||||
|         self.body = self._encode_body(body) | ||||
|         self.status = status | ||||
|         self.headers = Header(headers or {}) | ||||
|         self._cookies = None | ||||
|  | ||||
|         if body_bytes: | ||||
|             warnings.warn( | ||||
|                 "Parameter `body_bytes` is deprecated, use `body` instead", | ||||
|                 DeprecationWarning, | ||||
|             ) | ||||
|  | ||||
|     def output(self, version="1.1", keep_alive=False, keep_alive_timeout=None): | ||||
|         body = b"" | ||||
|         if has_message_body(self.status): | ||||
| @@ -218,20 +221,10 @@ def text( | ||||
|     :param content_type: the content type (string) of the response | ||||
|     """ | ||||
|     if not isinstance(body, str): | ||||
|         warnings.warn( | ||||
|             "Types other than str will be deprecated in future versions for" | ||||
|             f" response.text, got type {type(body).__name__})", | ||||
|             DeprecationWarning, | ||||
|         raise TypeError( | ||||
|             f"Bad body type. Expected str, got {type(body).__name__})" | ||||
|         ) | ||||
|     # Type conversions are deprecated and quite b0rked but still supported for | ||||
|     # text() until applications get fixed. This try-except should be removed. | ||||
|     try: | ||||
|         # Avoid repr(body).encode() b0rkage for body that is already encoded. | ||||
|         # memoryview used only to test bytes-ishness. | ||||
|         with memoryview(body): | ||||
|             pass | ||||
|     except TypeError: | ||||
|         body = f"{body}"  # no-op if body is already str | ||||
|  | ||||
|     return HTTPResponse( | ||||
|         body, status=status, headers=headers, content_type=content_type | ||||
|     ) | ||||
|   | ||||
| @@ -11,7 +11,16 @@ from sanic.views import CompositionView | ||||
|  | ||||
|  | ||||
| Route = namedtuple( | ||||
|     "Route", ["handler", "methods", "pattern", "parameters", "name", "uri"] | ||||
|     "Route", | ||||
|     [ | ||||
|         "handler", | ||||
|         "methods", | ||||
|         "pattern", | ||||
|         "parameters", | ||||
|         "name", | ||||
|         "uri", | ||||
|         "endpoint", | ||||
|     ], | ||||
| ) | ||||
| Parameter = namedtuple("Parameter", ["name", "cast"]) | ||||
|  | ||||
| @@ -79,7 +88,8 @@ class Router: | ||||
|     routes_always_check = None | ||||
|     parameter_pattern = re.compile(r"<(.+?)>") | ||||
|  | ||||
|     def __init__(self): | ||||
|     def __init__(self, app): | ||||
|         self.app = app | ||||
|         self.routes_all = {} | ||||
|         self.routes_names = {} | ||||
|         self.routes_static_files = {} | ||||
| @@ -299,11 +309,15 @@ class Router: | ||||
|  | ||||
|             handler_name = f"{bp_name}.{name or handler.__name__}" | ||||
|         else: | ||||
|             handler_name = name or getattr(handler, "__name__", None) | ||||
|             handler_name = name or getattr( | ||||
|                 handler, "__name__", handler.__class__.__name__ | ||||
|             ) | ||||
|  | ||||
|         if route: | ||||
|             route = merge_route(route, methods, handler) | ||||
|         else: | ||||
|             endpoint = self.app._build_endpoint_name(handler_name) | ||||
|  | ||||
|             route = Route( | ||||
|                 handler=handler, | ||||
|                 methods=methods, | ||||
| @@ -311,6 +325,7 @@ class Router: | ||||
|                 parameters=parameters, | ||||
|                 name=handler_name, | ||||
|                 uri=uri, | ||||
|                 endpoint=endpoint, | ||||
|             ) | ||||
|  | ||||
|         self.routes_all[uri] = route | ||||
| @@ -449,7 +464,8 @@ class Router: | ||||
|         route_handler = route.handler | ||||
|         if hasattr(route_handler, "handlers"): | ||||
|             route_handler = route_handler.handlers[method] | ||||
|         return route_handler, [], kwargs, route.uri, route.name | ||||
|  | ||||
|         return route_handler, [], kwargs, route.uri, route.name, route.endpoint | ||||
|  | ||||
|     def is_stream_handler(self, request): | ||||
|         """Handler for request is stream or not. | ||||
|   | ||||
| @@ -169,7 +169,11 @@ class HttpProtocol(asyncio.Protocol): | ||||
|         self.request_class = self.app.request_class or Request | ||||
|         self.is_request_stream = self.app.is_request_stream | ||||
|         self._is_stream_handler = False | ||||
|         self._not_paused = asyncio.Event(loop=deprecated_loop) | ||||
|         self._not_paused = ( | ||||
|             asyncio.Event() | ||||
|             if sys.version_info >= (3, 10) | ||||
|             else asyncio.Event(loop=deprecated_loop) | ||||
|         ) | ||||
|         self._total_request_size = 0 | ||||
|         self._request_timeout_handler = None | ||||
|         self._response_timeout_handler = None | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| from functools import partial, wraps | ||||
| from mimetypes import guess_type | ||||
| from os import path | ||||
| from re import sub | ||||
| from os import path, sep | ||||
| from pathlib import Path | ||||
| from time import gmtime, strftime | ||||
| from urllib.parse import unquote | ||||
|  | ||||
| @@ -13,6 +13,7 @@ from sanic.exceptions import ( | ||||
|     InvalidUsage, | ||||
| ) | ||||
| from sanic.handlers import ContentRangeHandler | ||||
| from sanic.log import error_logger | ||||
| from sanic.response import HTTPResponse, file, file_stream | ||||
|  | ||||
|  | ||||
| @@ -25,24 +26,41 @@ async def _static_request_handler( | ||||
|     content_type=None, | ||||
|     file_uri=None, | ||||
| ): | ||||
|     # Using this to determine if the URL is trying to break out of the path | ||||
|     # served.  os.path.realpath seems to be very slow | ||||
|     if file_uri and "../" in file_uri: | ||||
|         raise InvalidUsage("Invalid URL") | ||||
|     # Merge served directory and requested file if provided | ||||
|     # Strip all / that in the beginning of the URL to help prevent python | ||||
|     # from herping a derp and treating the uri as an absolute path | ||||
|     root_path = file_path = file_or_directory | ||||
|     if file_uri: | ||||
|         file_path = path.join(file_or_directory, sub("^[/]*", "", file_uri)) | ||||
|  | ||||
|     # URL decode the path sent by the browser otherwise we won't be able to | ||||
|     # match filenames which got encoded (filenames with spaces etc) | ||||
|     file_path = path.abspath(unquote(file_path)) | ||||
|     if not file_path.startswith(path.abspath(unquote(root_path))): | ||||
|         raise FileNotFound( | ||||
|             "File not found", path=file_or_directory, relative_url=file_uri | ||||
|     file_path_raw = Path(unquote(file_or_directory)) | ||||
|     root_path = file_path = file_path_raw.resolve() | ||||
|     not_found = FileNotFound( | ||||
|         "File not found", | ||||
|         path=file_or_directory, | ||||
|         relative_url=file_uri, | ||||
|     ) | ||||
|  | ||||
|     if file_uri: | ||||
|         # Strip all / that in the beginning of the URL to help prevent | ||||
|         # python from herping a derp and treating the uri as an | ||||
|         # absolute path | ||||
|         unquoted_file_uri = unquote(file_uri).lstrip("/") | ||||
|         file_path_raw = Path(file_or_directory, unquoted_file_uri) | ||||
|         file_path = file_path_raw.resolve() | ||||
|         if ( | ||||
|             file_path < root_path and not file_path_raw.is_symlink() | ||||
|         ) or ".." in file_path_raw.parts: | ||||
|             error_logger.exception( | ||||
|                 f"File not found: path={file_or_directory}, " | ||||
|                 f"relative_url={file_uri}" | ||||
|             ) | ||||
|             raise not_found | ||||
|  | ||||
|     try: | ||||
|         file_path.relative_to(root_path) | ||||
|     except ValueError: | ||||
|         if not file_path_raw.is_symlink(): | ||||
|             error_logger.exception( | ||||
|                 f"File not found: path={file_or_directory}, " | ||||
|                 f"relative_url={file_uri}" | ||||
|             ) | ||||
|             raise not_found | ||||
|  | ||||
|     try: | ||||
|         headers = {} | ||||
|         # Check if the client has been sent this file before | ||||
| @@ -94,6 +112,10 @@ async def _static_request_handler( | ||||
|     except ContentRangeError: | ||||
|         raise | ||||
|     except Exception: | ||||
|         error_logger.exception( | ||||
|             f"File not found: path={file_or_directory}, " | ||||
|             f"relative_url={file_uri}" | ||||
|         ) | ||||
|         raise FileNotFound( | ||||
|             "File not found", path=file_or_directory, relative_url=file_uri | ||||
|         ) | ||||
| @@ -134,6 +156,8 @@ def register( | ||||
|                               threshold size to switch to file_stream() | ||||
|     :param name: user defined name used for url_for | ||||
|     :param content_type: user defined content type for header | ||||
|     :return: registered static routes | ||||
|     :rtype: List[sanic.router.Route] | ||||
|     """ | ||||
|     # If we're not trying to match a file directly, | ||||
|     # serve from the folder | ||||
| @@ -155,10 +179,11 @@ def register( | ||||
|         ) | ||||
|     ) | ||||
|  | ||||
|     app.route( | ||||
|     _routes, _ = app.route( | ||||
|         uri, | ||||
|         methods=["GET", "HEAD"], | ||||
|         name=name, | ||||
|         host=host, | ||||
|         strict_slashes=strict_slashes, | ||||
|     )(_handler) | ||||
|     return _routes | ||||
|   | ||||
| @@ -90,6 +90,7 @@ class CompositionView: | ||||
|  | ||||
|     def __init__(self): | ||||
|         self.handlers = {} | ||||
|         self.name = self.__class__.__name__ | ||||
|  | ||||
|     def add(self, methods, handler, stream=False): | ||||
|         if stream: | ||||
|   | ||||
							
								
								
									
										28
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								setup.py
									
									
									
									
									
								
							| @@ -5,6 +5,7 @@ import codecs | ||||
| import os | ||||
| import re | ||||
| import sys | ||||
|  | ||||
| from distutils.util import strtobool | ||||
|  | ||||
| from setuptools import setup | ||||
| @@ -24,6 +25,7 @@ class PyTest(TestCommand): | ||||
|  | ||||
|     def run_tests(self): | ||||
|         import shlex | ||||
|  | ||||
|         import pytest | ||||
|  | ||||
|         errno = pytest.main(shlex.split(self.pytest_args)) | ||||
| @@ -38,7 +40,9 @@ def open_local(paths, mode="r", encoding="utf8"): | ||||
|  | ||||
| with open_local(["sanic", "__version__.py"], encoding="latin1") as fp: | ||||
|     try: | ||||
|         version = re.findall(r"^__version__ = \"([^']+)\"\r?$", fp.read(), re.M)[0] | ||||
|         version = re.findall( | ||||
|             r"^__version__ = \"([^']+)\"\r?$", fp.read(), re.M | ||||
|         )[0] | ||||
|     except IndexError: | ||||
|         raise RuntimeError("Unable to determine version.") | ||||
|  | ||||
| @@ -53,10 +57,12 @@ setup_kwargs = { | ||||
|     "author": "Sanic Community", | ||||
|     "author_email": "admhpkns@gmail.com", | ||||
|     "description": ( | ||||
|         "A web server and web framework that's written to go fast. Build fast. Run fast." | ||||
|         "A web server and web framework that's written to go fast. " | ||||
|         "Build fast. Run fast." | ||||
|     ), | ||||
|     "long_description": long_description, | ||||
|     "packages": ["sanic"], | ||||
|     "package_data": {"sanic": ["py.typed"]}, | ||||
|     "platforms": "any", | ||||
|     "python_requires": ">=3.6", | ||||
|     "classifiers": [ | ||||
| @@ -66,11 +72,14 @@ setup_kwargs = { | ||||
|         "Programming Language :: Python :: 3.6", | ||||
|         "Programming Language :: Python :: 3.7", | ||||
|         "Programming Language :: Python :: 3.8", | ||||
|         "Programming Language :: Python :: 3.9", | ||||
|     ], | ||||
|     "entry_points": {"console_scripts": ["sanic = sanic.__main__:main"]}, | ||||
| } | ||||
|  | ||||
| env_dependency = '; sys_platform != "win32" ' 'and implementation_name == "cpython"' | ||||
| env_dependency = ( | ||||
|     '; sys_platform != "win32" ' 'and implementation_name == "cpython"' | ||||
| ) | ||||
| ujson = "ujson>=1.35" + env_dependency | ||||
| uvloop = "uvloop>=0.5.3" + env_dependency | ||||
|  | ||||
| @@ -78,24 +87,25 @@ requirements = [ | ||||
|     "httptools>=0.0.10", | ||||
|     uvloop, | ||||
|     ujson, | ||||
|     "aiofiles>=0.3.0", | ||||
|     "websockets>=8.1,<9.0", | ||||
|     "multidict>=4.0,<5.0", | ||||
|     "aiofiles>=0.6.0", | ||||
|     "websockets>=8.1,<=9.1", | ||||
|     "multidict>=5.0,<6.0", | ||||
|     "httpx==0.15.4", | ||||
| ] | ||||
|  | ||||
| tests_require = [ | ||||
|     "pytest==5.2.1", | ||||
|     "multidict>=4.0,<5.0", | ||||
|     "gunicorn", | ||||
|     "multidict>=5.0,<6.0", | ||||
|     "gunicorn==20.0.4", | ||||
|     "pytest-cov", | ||||
|     "httpcore==0.3.0", | ||||
|     "httpcore==0.11.*", | ||||
|     "beautifulsoup4", | ||||
|     uvloop, | ||||
|     ujson, | ||||
|     "pytest-sanic", | ||||
|     "pytest-sugar", | ||||
|     "pytest-benchmark", | ||||
|     "pytest-dependency", | ||||
| ] | ||||
|  | ||||
| docs_require = [ | ||||
|   | ||||
| @@ -11,6 +11,7 @@ from sanic.router import RouteExists, Router | ||||
|  | ||||
|  | ||||
| random.seed("Pack my box with five dozen liquor jugs.") | ||||
| Sanic.test_mode = True | ||||
|  | ||||
| if sys.platform in ["win32", "cygwin"]: | ||||
|     collect_ignore = ["test_worker.py"] | ||||
| @@ -95,10 +96,10 @@ class RouteStringGenerator: | ||||
|  | ||||
|  | ||||
| @pytest.fixture(scope="function") | ||||
| def sanic_router(): | ||||
| def sanic_router(app): | ||||
|     # noinspection PyProtectedMember | ||||
|     def _setup(route_details: tuple) -> (Router, tuple): | ||||
|         router = Router() | ||||
|         router = Router(app) | ||||
|         added_router = [] | ||||
|         for method, route in route_details: | ||||
|             try: | ||||
|   | ||||
| @@ -3,6 +3,7 @@ import logging | ||||
| import sys | ||||
|  | ||||
| from inspect import isawaitable | ||||
| from os import environ | ||||
| from unittest.mock import patch | ||||
|  | ||||
| import pytest | ||||
| @@ -117,7 +118,7 @@ def test_app_route_raise_value_error(app): | ||||
|  | ||||
| def test_app_handle_request_handler_is_none(app, monkeypatch): | ||||
|     def mockreturn(*args, **kwargs): | ||||
|         return None, [], {}, "", "" | ||||
|         return None, [], {}, "", "", None | ||||
|  | ||||
|     # Not sure how to make app.router.get() return None, so use mock here. | ||||
|     monkeypatch.setattr(app.router, "get", mockreturn) | ||||
| @@ -258,7 +259,7 @@ def test_handle_request_with_nested_sanic_exception(app, monkeypatch, caplog): | ||||
|  | ||||
|  | ||||
| def test_app_name_required(): | ||||
|     with pytest.deprecated_call(): | ||||
|     with pytest.raises(SanicException): | ||||
|         Sanic() | ||||
|  | ||||
|  | ||||
| @@ -274,14 +275,50 @@ def test_app_has_test_mode_sync(): | ||||
|     assert response.status == 200 | ||||
|  | ||||
|  | ||||
| # @pytest.mark.asyncio | ||||
| # async def test_app_has_test_mode_async(): | ||||
| #     app = Sanic("test") | ||||
| def test_app_registry(): | ||||
|     instance = Sanic("test") | ||||
|     assert Sanic._app_registry["test"] is instance | ||||
|  | ||||
| #     @app.get("/") | ||||
| #     async def handler(request): | ||||
| #         assert request.app.test_mode | ||||
| #         return text("test") | ||||
|  | ||||
| #     _, response = await app.asgi_client.get("/") | ||||
| #     assert response.status == 200 | ||||
| def test_app_registry_wrong_type(): | ||||
|     with pytest.raises(SanicException): | ||||
|         Sanic.register_app(1) | ||||
|  | ||||
|  | ||||
| def test_app_registry_name_reuse(): | ||||
|     Sanic("test") | ||||
|     Sanic.test_mode = False | ||||
|     with pytest.raises(SanicException): | ||||
|         Sanic("test") | ||||
|     Sanic.test_mode = True | ||||
|     Sanic("test") | ||||
|  | ||||
|  | ||||
| def test_app_registry_retrieval(): | ||||
|     instance = Sanic("test") | ||||
|     assert Sanic.get_app("test") is instance | ||||
|  | ||||
|  | ||||
| def test_get_app_does_not_exist(): | ||||
|     with pytest.raises(SanicException): | ||||
|         Sanic.get_app("does-not-exist") | ||||
|  | ||||
|  | ||||
| def test_get_app_does_not_exist_force_create(): | ||||
|     assert isinstance( | ||||
|         Sanic.get_app("does-not-exist", force_create=True), Sanic | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def test_app_no_registry(): | ||||
|     Sanic("no-register", register=False) | ||||
|     with pytest.raises(SanicException): | ||||
|         Sanic.get_app("no-register") | ||||
|  | ||||
|  | ||||
| def test_app_no_registry_env(): | ||||
|     environ["SANIC_REGISTER"] = "False" | ||||
|     Sanic("no-register") | ||||
|     with pytest.raises(SanicException): | ||||
|         Sanic.get_app("no-register") | ||||
|     del environ["SANIC_REGISTER"] | ||||
|   | ||||
| @@ -1,6 +1,3 @@ | ||||
| import asyncio | ||||
| import sys | ||||
|  | ||||
| from collections import deque, namedtuple | ||||
|  | ||||
| import pytest | ||||
| @@ -82,14 +79,6 @@ def test_listeners_triggered(app): | ||||
|     with pytest.warns(UserWarning): | ||||
|         server.run() | ||||
|  | ||||
|     all_tasks = ( | ||||
|         asyncio.Task.all_tasks() | ||||
|         if sys.version_info < (3, 7) | ||||
|         else asyncio.all_tasks(asyncio.get_event_loop()) | ||||
|     ) | ||||
|     for task in all_tasks: | ||||
|         task.cancel() | ||||
|  | ||||
|     assert before_server_start | ||||
|     assert after_server_start | ||||
|     assert before_server_stop | ||||
| @@ -132,14 +121,6 @@ def test_listeners_triggered_async(app): | ||||
|     with pytest.warns(UserWarning): | ||||
|         server.run() | ||||
|  | ||||
|     all_tasks = ( | ||||
|         asyncio.Task.all_tasks() | ||||
|         if sys.version_info < (3, 7) | ||||
|         else asyncio.all_tasks(asyncio.get_event_loop()) | ||||
|     ) | ||||
|     for task in all_tasks: | ||||
|         task.cancel() | ||||
|  | ||||
|     assert before_server_start | ||||
|     assert after_server_start | ||||
|     assert before_server_stop | ||||
|   | ||||
| @@ -736,6 +736,37 @@ def test_static_blueprint_name(app: Sanic, static_file_directory, file_name): | ||||
|     assert response.status == 200 | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("file_name", ["test.file"]) | ||||
| def test_static_blueprintp_mw(app: Sanic, static_file_directory, file_name): | ||||
|     current_file = inspect.getfile(inspect.currentframe()) | ||||
|     with open(current_file, "rb") as file: | ||||
|         file.read() | ||||
|  | ||||
|     triggered = False | ||||
|  | ||||
|     bp = Blueprint(name="test_mw", url_prefix="") | ||||
|  | ||||
|     @bp.middleware("request") | ||||
|     def bp_mw1(request): | ||||
|         nonlocal triggered | ||||
|         triggered = True | ||||
|  | ||||
|     bp.static( | ||||
|         "/test.file", | ||||
|         get_file_path(static_file_directory, file_name), | ||||
|         strict_slashes=True, | ||||
|         name="static", | ||||
|     ) | ||||
|  | ||||
|     app.blueprint(bp) | ||||
|  | ||||
|     uri = app.url_for("test_mw.static") | ||||
|     assert uri == "/test.file" | ||||
|  | ||||
|     _, response = app.test_client.get("/test.file") | ||||
|     assert triggered is True | ||||
|  | ||||
|  | ||||
| def test_route_handler_add(app: Sanic): | ||||
|     view = CompositionView() | ||||
|  | ||||
| @@ -794,21 +825,6 @@ def test_duplicate_blueprint(app): | ||||
|     ) | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("debug", [True, False, None]) | ||||
| def test_register_blueprint(app, debug): | ||||
|     bp = Blueprint("bp") | ||||
|  | ||||
|     app.debug = debug | ||||
|     with pytest.warns(DeprecationWarning) as record: | ||||
|         app.register_blueprint(bp) | ||||
|  | ||||
|     assert record[0].message.args[0] == ( | ||||
|         "Use of register_blueprint will be deprecated in " | ||||
|         "version 1.0.  Please use the blueprint method" | ||||
|         " instead" | ||||
|     ) | ||||
|  | ||||
|  | ||||
| def test_strict_slashes_behavior_adoption(app): | ||||
|     app.strict_slashes = True | ||||
|  | ||||
|   | ||||
| @@ -10,7 +10,7 @@ from sanic.utils import load_module_from_file_location | ||||
| @pytest.fixture | ||||
| def loaded_module_from_file_location(): | ||||
|     return load_module_from_file_location( | ||||
|         str(Path(__file__).parent / "static/app_test_config.py") | ||||
|         str(Path(__file__).parent / "static" / "app_test_config.py") | ||||
|     ) | ||||
|  | ||||
|  | ||||
| @@ -23,7 +23,10 @@ def test_load_module_from_file_location(loaded_module_from_file_location): | ||||
| def test_loaded_module_from_file_location_name( | ||||
|     loaded_module_from_file_location, | ||||
| ): | ||||
|     assert loaded_module_from_file_location.__name__ == "app_test_config" | ||||
|     name = loaded_module_from_file_location.__name__ | ||||
|     if "C:\\" in name: | ||||
|         name = name.split("\\")[-1] | ||||
|     assert name == "app_test_config" | ||||
|  | ||||
|  | ||||
| def test_load_module_from_file_location_with_non_existing_env_variable(): | ||||
|   | ||||
| @@ -290,6 +290,17 @@ def test_query_string(app): | ||||
|     assert request.args.get("test3", default="My value") == "My value" | ||||
|  | ||||
|  | ||||
| def test_popped_stays_popped(app): | ||||
|     @app.route("/") | ||||
|     async def handler(request): | ||||
|         return text("OK") | ||||
|  | ||||
|     request, response = app.test_client.get("/", params=[("test1", "1")]) | ||||
|  | ||||
|     assert request.args.pop("test1") == ["1"] | ||||
|     assert "test1" not in request.args | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_query_string_asgi(app): | ||||
|     @app.route("/") | ||||
|   | ||||
| @@ -41,7 +41,8 @@ def test_response_body_not_a_string(app): | ||||
|         return text(random_num) | ||||
|  | ||||
|     request, response = app.test_client.get("/hello") | ||||
|     assert response.text == str(random_num) | ||||
|     assert response.status == 500 | ||||
|     assert b"Internal Server Error" in response.body | ||||
|  | ||||
|  | ||||
| async def sample_streaming_fn(response): | ||||
| @@ -235,6 +236,12 @@ def test_chunked_streaming_returns_correct_content(streaming_app): | ||||
|     assert response.text == "foo,bar" | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_chunked_streaming_returns_correct_content_asgi(streaming_app): | ||||
|     request, response = await streaming_app.asgi_client.get("/") | ||||
|     assert response.text == "foo,bar" | ||||
|  | ||||
|  | ||||
| def test_non_chunked_streaming_adds_correct_headers(non_chunked_streaming_app): | ||||
|     request, response = non_chunked_streaming_app.test_client.get("/") | ||||
|     assert "Transfer-Encoding" not in response.headers | ||||
| @@ -242,6 +249,16 @@ def test_non_chunked_streaming_adds_correct_headers(non_chunked_streaming_app): | ||||
|     assert response.headers["Content-Length"] == "7" | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_non_chunked_streaming_adds_correct_headers_asgi( | ||||
|     non_chunked_streaming_app, | ||||
| ): | ||||
|     request, response = await non_chunked_streaming_app.asgi_client.get("/") | ||||
|     assert "Transfer-Encoding" not in response.headers | ||||
|     assert response.headers["Content-Type"] == "text/csv" | ||||
|     assert response.headers["Content-Length"] == "7" | ||||
|  | ||||
|  | ||||
| def test_non_chunked_streaming_returns_correct_content( | ||||
|     non_chunked_streaming_app, | ||||
| ): | ||||
| @@ -608,17 +625,3 @@ def test_empty_response(app): | ||||
|     request, response = app.test_client.get("/test") | ||||
|     assert response.content_type is None | ||||
|     assert response.body == b"" | ||||
|  | ||||
|  | ||||
| def test_response_body_bytes_deprecated(app): | ||||
|     with warnings.catch_warnings(record=True) as w: | ||||
|         warnings.simplefilter("always") | ||||
|  | ||||
|         HTTPResponse(body_bytes=b"bytes") | ||||
|  | ||||
|         assert len(w) == 1 | ||||
|         assert issubclass(w[0].category, DeprecationWarning) | ||||
|         assert ( | ||||
|             "Parameter `body_bytes` is deprecated, use `body` instead" | ||||
|             in str(w[0].message) | ||||
|         ) | ||||
|   | ||||
| @@ -1,10 +1,14 @@ | ||||
| import inspect | ||||
| import os | ||||
| import sys | ||||
|  | ||||
| from pathlib import Path | ||||
| from time import gmtime, strftime | ||||
|  | ||||
| import pytest | ||||
|  | ||||
| from sanic.app import Sanic | ||||
|  | ||||
|  | ||||
| @pytest.fixture(scope="module") | ||||
| def static_file_directory(): | ||||
| @@ -15,6 +19,22 @@ def static_file_directory(): | ||||
|     return static_directory | ||||
|  | ||||
|  | ||||
| @pytest.fixture(scope="module") | ||||
| def double_dotted_directory_file(static_file_directory: str): | ||||
|     """Generate double dotted directory and its files""" | ||||
|     if sys.platform == "win32": | ||||
|         raise Exception("Windows doesn't support double dotted directories") | ||||
|  | ||||
|     file_path = Path(static_file_directory) / "dotted.." / "dot.txt" | ||||
|     double_dotted_dir = file_path.parent | ||||
|     Path.mkdir(double_dotted_dir, exist_ok=True) | ||||
|     with open(file_path, "w") as f: | ||||
|         f.write("DOT\n") | ||||
|     yield file_path | ||||
|     Path.unlink(file_path) | ||||
|     Path.rmdir(double_dotted_dir) | ||||
|  | ||||
|  | ||||
| def get_file_path(static_file_directory, file_name): | ||||
|     return os.path.join(static_file_directory, file_name) | ||||
|  | ||||
| @@ -374,3 +394,43 @@ def test_static_name(app, static_file_directory, static_name, file_name): | ||||
|     request, response = app.test_client.get(f"/static/{file_name}") | ||||
|  | ||||
|     assert response.status == 200 | ||||
|  | ||||
|  | ||||
| @pytest.mark.skipif( | ||||
|     sys.platform == "win32", | ||||
|     reason="Windows does not support double dotted directories", | ||||
| ) | ||||
| def test_dotted_dir_ok( | ||||
|     app: Sanic, static_file_directory: str, double_dotted_directory_file: Path | ||||
| ): | ||||
|     app.static("/foo", static_file_directory) | ||||
|     dot_relative_path = str( | ||||
|         double_dotted_directory_file.relative_to(static_file_directory) | ||||
|     ) | ||||
|     _, response = app.test_client.get("/foo/" + dot_relative_path) | ||||
|     assert response.status == 200 | ||||
|     assert response.body == b"DOT\n" | ||||
|  | ||||
|  | ||||
| def test_breakout(app: Sanic, static_file_directory: str): | ||||
|     app.static("/foo", static_file_directory) | ||||
|  | ||||
|     _, response = app.test_client.get("/foo/..%2Ffake/server.py") | ||||
|     assert response.status == 404 | ||||
|  | ||||
|     _, response = app.test_client.get("/foo/..%2Fstatic/test.file") | ||||
|     assert response.status == 404 | ||||
|  | ||||
|  | ||||
| @pytest.mark.skipif( | ||||
|     sys.platform != "win32", reason="Block backslash on Windows only" | ||||
| ) | ||||
| def test_double_backslash_prohibited_on_win32( | ||||
|     app: Sanic, static_file_directory: str | ||||
| ): | ||||
|     app.static("/foo", static_file_directory) | ||||
|  | ||||
|     _, response = app.test_client.get("/foo/static/..\\static/test.file") | ||||
|     assert response.status == 404 | ||||
|     _, response = app.test_client.get("/foo/static\\../static/test.file") | ||||
|     assert response.status == 404 | ||||
|   | ||||
| @@ -348,3 +348,13 @@ def test_methodview_naming(methodview_app): | ||||
|  | ||||
|     assert viewone_url == "/view_one" | ||||
|     assert viewtwo_url == "/view_two" | ||||
|  | ||||
|  | ||||
| def test_url_for_with_websocket_handlers(app): | ||||
|     # Test for a specific bugfix in GH-2021 | ||||
|     @app.websocket("/ws") | ||||
|     async def my_handler(request, ws): | ||||
|         pass | ||||
|  | ||||
|     assert app.url_for("my_handler") == "/ws" | ||||
|     assert app.url_for("websocket_handler_my_handler") == "/ws" | ||||
|   | ||||
							
								
								
									
										18
									
								
								tox.ini
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								tox.ini
									
									
									
									
									
								
							| @@ -1,26 +1,26 @@ | ||||
| [tox] | ||||
| envlist = py36, py37, py38, pyNightly, {py36,py37,py38,pyNightly}-no-ext, lint, check, security, docs | ||||
| envlist = py36, py37, py38, py39, pyNightly, {py36,py37,py38,py39,pyNightly}-no-ext, lint, check, security, docs | ||||
|  | ||||
| [testenv] | ||||
| usedevelop = True | ||||
| setenv = | ||||
|     {py36,py37,py38,pyNightly}-no-ext: SANIC_NO_UJSON=1 | ||||
|     {py36,py37,py38,pyNightly}-no-ext: SANIC_NO_UVLOOP=1 | ||||
|     {py36,py37,py38,py39,pyNightly}-no-ext: SANIC_NO_UJSON=1 | ||||
|     {py36,py37,py38,py39,pyNightly}-no-ext: SANIC_NO_UVLOOP=1 | ||||
| deps = | ||||
|     coverage | ||||
|     coverage==5.3 | ||||
|     pytest==5.2.1 | ||||
|     pytest-cov | ||||
|     pytest-sanic | ||||
|     pytest-sugar | ||||
|     pytest-benchmark | ||||
|     pytest-dependency | ||||
|     httpcore==0.3.0 | ||||
|     httpcore==0.11.* | ||||
|     httpx==0.15.4 | ||||
|     chardet<=2.3.0 | ||||
|     multidict>=5.0,<6.0 | ||||
|     beautifulsoup4 | ||||
|     gunicorn | ||||
|     gunicorn==20.0.4 | ||||
|     uvicorn | ||||
|     websockets>=8.1,<9.0 | ||||
|     websockets>=8.1,<=9.1 | ||||
| commands = | ||||
|     pytest {posargs:tests --cov sanic} | ||||
|     - coverage combine --append | ||||
| @@ -76,7 +76,7 @@ deps = | ||||
|     recommonmark>=0.5.0 | ||||
|     docutils | ||||
|     pygments | ||||
|     gunicorn | ||||
|     gunicorn==20.0.4 | ||||
|  | ||||
| commands = | ||||
|     make docs-test | ||||
|   | ||||
		Reference in New Issue
	
	Block a user