Merge pull request #17 from FasterApiWeb/stage #16
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| push: | |
| branches: | |
| - master | |
| - stage | |
| - dev | |
| tags: | |
| - "v*" | |
| permissions: | |
| contents: write | |
| id-token: write | |
| packages: write | |
| jobs: | |
| # ── Publish channel builds to TestPyPI on branch merges ─────────────── | |
| # Channel mapping: | |
| # - dev -> 0.0.0.devN | |
| # - stage -> 0.0.0aN | |
| # - master -> 0.0.0rcN | |
| # Stable PyPI releases remain tag-based (vX.Y.Z). | |
| publish-testpypi-dev: | |
| if: github.ref == 'refs/heads/dev' | |
| runs-on: ubuntu-latest | |
| environment: testpypi | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Verify TestPyPI token is configured | |
| run: | | |
| if [ -z "${{ secrets.TEST_PYPI_API_TOKEN }}" ]; then | |
| echo "::error::Missing secret TEST_PYPI_API_TOKEN (Settings → Secrets and variables → Actions)." | |
| exit 1 | |
| fi | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.13" | |
| - name: Install build tools | |
| run: pip install build | |
| - name: Build channel package (dev) | |
| env: | |
| SETUPTOOLS_SCM_PRETEND_VERSION: 0.0.0.dev${{ github.run_number }} | |
| run: python -m build | |
| - name: Upload package artifact (dev channel) | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: dist-dev | |
| path: dist/ | |
| - name: Publish to TestPyPI | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| with: | |
| repository-url: https://test.pypi.org/legacy/ | |
| user: __token__ | |
| password: ${{ secrets.TEST_PYPI_API_TOKEN }} | |
| skip-existing: true | |
| publish-testpypi-stage: | |
| if: github.ref == 'refs/heads/stage' | |
| runs-on: ubuntu-latest | |
| environment: testpypi | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Verify TestPyPI token is configured | |
| run: | | |
| if [ -z "${{ secrets.TEST_PYPI_API_TOKEN }}" ]; then | |
| echo "::error::Missing secret TEST_PYPI_API_TOKEN (Settings → Secrets and variables → Actions)." | |
| exit 1 | |
| fi | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.13" | |
| - name: Install build tools | |
| run: pip install build | |
| - name: Build channel package (stage) | |
| env: | |
| SETUPTOOLS_SCM_PRETEND_VERSION: 0.0.0a${{ github.run_number }} | |
| run: python -m build | |
| - name: Upload package artifact (stage channel) | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: dist-stage | |
| path: dist/ | |
| - name: Publish to TestPyPI | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| with: | |
| repository-url: https://test.pypi.org/legacy/ | |
| user: __token__ | |
| password: ${{ secrets.TEST_PYPI_API_TOKEN }} | |
| skip-existing: true | |
| publish-testpypi-master: | |
| if: github.ref == 'refs/heads/master' | |
| runs-on: ubuntu-latest | |
| environment: testpypi | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Verify TestPyPI token is configured | |
| run: | | |
| if [ -z "${{ secrets.TEST_PYPI_API_TOKEN }}" ]; then | |
| echo "::error::Missing secret TEST_PYPI_API_TOKEN (Settings → Secrets and variables → Actions)." | |
| echo "Create an API token on TestPyPI for the `faster-api-web` project and add it as TEST_PYPI_API_TOKEN." | |
| exit 1 | |
| fi | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.13" | |
| - name: Install build tools | |
| run: pip install build | |
| - name: Build channel package (master) | |
| env: | |
| SETUPTOOLS_SCM_PRETEND_VERSION: 0.0.0rc${{ github.run_number }} | |
| run: python -m build | |
| - name: Upload package artifact (master channel) | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: dist-master | |
| path: dist/ | |
| - name: Publish to TestPyPI | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| with: | |
| repository-url: https://test.pypi.org/legacy/ | |
| user: __token__ | |
| password: ${{ secrets.TEST_PYPI_API_TOKEN }} | |
| skip-existing: true | |
| # ── Gate: only release from master ────────────────────────────────── | |
| check-branch: | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Verify tag is on master | |
| run: | | |
| TAG_COMMIT=$(git rev-list -n 1 "$GITHUB_REF") | |
| if ! git branch -r --contains "$TAG_COMMIT" | grep -q 'origin/master'; then | |
| echo "::error::Tag $GITHUB_REF_NAME is not on the master branch. Releases must be tagged on master." | |
| exit 1 | |
| fi | |
| # ── Step 1: Run tests ────────────────────────────────────────────── | |
| test: | |
| needs: check-branch | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| python-version: ["3.10", "3.11", "3.12", "3.13"] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - run: pip install -e ".[dev]" | |
| - run: pytest --cov=FasterAPI --cov-report=term-missing --cov-fail-under=85 | |
| # ── Step 2: Build wheel + sdist ──────────────────────────────────── | |
| build: | |
| needs: test | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.13" | |
| - name: Install build tools | |
| run: pip install build | |
| - name: Build wheel and sdist | |
| run: python -m build | |
| - name: Upload build artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: dist | |
| path: dist/ | |
| # ── Step 3: Publish to PyPI via trusted publishing (OIDC) ────────── | |
| publish-pypi: | |
| needs: build | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| runs-on: ubuntu-latest | |
| environment: pypi | |
| permissions: | |
| id-token: write | |
| steps: | |
| - name: Download build artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: dist | |
| path: dist/ | |
| - name: Publish to PyPI | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| with: | |
| skip-existing: true | |
| # ── Step 4: Publish Docker image to GitHub Packages (ghcr.io) ────── | |
| publish-docker: | |
| needs: test | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Extract version from tag | |
| id: version | |
| run: echo "tag=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" | |
| - name: Normalize owner to lowercase | |
| id: owner | |
| run: echo "owner_lc=${GITHUB_REPOSITORY_OWNER,,}" >> "$GITHUB_OUTPUT" | |
| - name: Normalize repo name to lowercase | |
| id: repo | |
| run: echo "repo_lc=$(echo "${GITHUB_REPOSITORY#*/}" | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" | |
| - name: Build and push Docker image | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| push: true | |
| tags: | | |
| ghcr.io/${{ steps.owner.outputs.owner_lc }}/${{ steps.repo.outputs.repo_lc }}:${{ steps.version.outputs.tag }} | |
| ghcr.io/${{ steps.owner.outputs.owner_lc }}/${{ steps.repo.outputs.repo_lc }}:latest | |
| # ── Step 5: Create GitHub Release with artifacts ─────────────────── | |
| github-release: | |
| needs: [build, publish-pypi, publish-docker] | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Download build artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: dist | |
| path: dist/ | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| files: dist/* | |
| body: | | |
| # FasterAPI ${{ github.ref_name }} | |
| **A high-performance ASGI web framework — drop-in FastAPI replacement.** | |
| ```bash | |
| pip install faster-api-web | |
| ``` | |
| --- | |
| ## 5 Key Innovations | |
| ### 1. Radix Tree Router — 7.6x faster routing | |
| O(k) lookups regardless of route count. **1,104,318 ops/s vs 144,822 ops/s** (regex). | |
| ### 2. msgspec (Rust-backed) — 9.4x faster JSON encoding | |
| Dict → bytes in a single C call. **6,317,891 ops/s vs 670,234 ops/s**. | |
| ### 3. Compiled Dependency Injection — zero per-request introspection | |
| Handler signatures compiled once at startup. Eliminates ~80% of per-request framework overhead. | |
| ### 4. uvloop — C-powered event loop | |
| libuv-backed (same engine as Node.js). All I/O scheduling in C. | |
| ### 5. Lazy Request Parsing — only parse what you use | |
| Headers, query params, cookies parsed on first access only. | |
| --- | |
| ## Benchmarks (Python 3.13.7) | |
| | Endpoint | FasterAPI | FastAPI | Speedup | | |
| |---|---|---|---| | |
| | `GET /health` | **335,612 req/s** | 49,005 req/s | **6.85x** | | |
| | `GET /users/{id}` | **282,835 req/s** | 32,391 req/s | **8.73x** | | |
| | `POST /users` | **193,225 req/s** | 27,031 req/s | **7.15x** | | |
| [Full benchmarks →](https://github.com/FasterApiWeb/FasterAPI#benchmarks) | |
| --- | |
| ## Install | |
| ```bash | |
| pip install faster-api-web # core | |
| pip install faster-api-web[all] # + uvloop | |
| ``` | |
| ## Docker | |
| ```bash | |
| docker pull ghcr.io/fasterapiweb/fasterapi:latest | |
| ``` |