Skip to content

About

No description or website provided.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Repository files navigation

Tests

How to start project with Winter

Winter needs Python 3.11 or newer.

Bootstrap empty project

$ poetry init
$ poetry add winter
$ poetry run django-admin startproject simple_api .

Add simple_api/api.py with contents:

import winter.web


class SimpleAPI:
    @winter.web.route_get('greeting/')
    def greeting(self) -> str:
        return 'Hello from Winter API!'

Replace simple_api/urls.py with:

import winter.django
import winter.web

routes = winter.web.find_package_routes('simple_api.api')
urlpatterns = winter.django.create_django_urls_from_routes(routes)

Add simple_api/apps.py with contents:

from django.apps import AppConfig


class SimpleApiConfig(AppConfig):
    name = 'simple_api'

    def ready(self):
        import winter.application
        import winter.django
        import winter.openapi
        import winter.web
        from injector import Injector

        # Import the URLs first: route discovery imports every API module, so the setup calls
        # below see all @problem exceptions and @raises annotations.
        import simple_api.urls  # noqa: F401

        winter.application.set_injector(Injector())
        winter.web.setup()
        winter.django.setup()
        winter.openapi.setup(allow_missing_raises_annotation=True)

The order matters. Setup registers handlers only for the exception classes that are already imported, so call it after the routes are discovered, not from settings.py.

Add 'simple_api' to INSTALLED_APPS in settings.py.

Add OpenAPI and Swagger UI

Add simple_api/openapi.py with contents:

import winter.openapi
import winter.web


class OpenAPI:
    @winter.web.route_get('openapi.json')
    def openapi(self):
        """OpenAPI specification"""
        routes = winter.web.find_package_routes('simple_api.api')
        return winter.openapi.generate_openapi(
            'Getting Started Winter API',
            'v1',
            routes,
        )

    @winter.web.route_get('')
    def swagger_ui(self) -> winter.web.Response:
        """HTML page with Swagger UI"""
        html = winter.openapi.get_swagger_ui_html(openapi_url='/openapi.json')
        return winter.web.Response(200, body=html.encode(), headers={'content-type': 'text/html'})

Add its routes in simple_api/urls.py:

routes = [
    *winter.web.find_package_routes('simple_api.api'),
    *winter.web.find_package_routes('simple_api.openapi'),
]

find_package_routes takes a module or a package. A package is scanned with all its modules, so do not scan simple_api itself: that would import wsgi.py and the settings during setup.

How to run

You can use any WSGI server, for example gunicorn:

$ poetry add gunicorn
$ poetry run gunicorn simple_api.wsgi

Check it's working http://localhost:8000/greeting/

$ http get http://localhost:8000/greeting/

Expected output:

HTTP/1.1 200 OK
Server: gunicorn
Date: Mon, 12 Oct 2026 16:53:43 GMT
Connection: close
content-type: application/json
X-Frame-Options: DENY
Content-Length: 24
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin

"Hello from Winter API!"

Swagger UI is at http://localhost:8000/

How to test

$ poetry install
$ poetry run pytest

See conftest.py for an httpx client that calls the WSGI application in-process.

How to deploy to Azure Container Apps

The application runs as a container app that scales to zero when idle, so a demo fits in the monthly free grant of the Consumption plan. The image is public on GitHub Container Registry, which needs no registry in Azure. GitHub Actions signs in to Azure with OpenID Connect, so no long-lived secret is stored.

Files of interest:

One-time setup with the Azure CLI and the GitHub CLI:

RESOURCE_GROUP=winter-getting-started
CONTAINER_APP=winter-getting-started
LOCATION=westeurope
REPOSITORY=WinterFramework/winter-getting-started

az group create --name $RESOURCE_GROUP --location $LOCATION

# No Log Analytics workspace: it would be billed separately
az containerapp env create --name $CONTAINER_APP-env --resource-group $RESOURCE_GROUP \
  --location $LOCATION --logs-destination none

# A placeholder image until the first deployment
az containerapp create --name $CONTAINER_APP --resource-group $RESOURCE_GROUP \
  --environment $CONTAINER_APP-env --image mcr.microsoft.com/k8se/quickstart:latest \
  --ingress external --target-port 8080 --min-replicas 0 --max-replicas 1 --cpu 0.25 --memory 0.5Gi

# An identity for GitHub Actions, trusted only for the "azure" environment of this repository
APP_ID=$(az ad app create --display-name $CONTAINER_APP-github --query appId --output tsv)
az ad sp create --id $APP_ID
az ad app federated-credential create --id $APP_ID --parameters "{
  \"name\": \"github-azure-environment\",
  \"issuer\": \"https://token.actions.githubusercontent.com\",
  \"subject\": \"repo:$REPOSITORY:environment:azure\",
  \"audiences\": [\"api://AzureADTokenExchange\"]
}"
az role assignment create --assignee $APP_ID --role Contributor \
  --scope "$(az group show --name $RESOURCE_GROUP --query id --output tsv)"

# Deployments only from master
gh api --method PUT repos/$REPOSITORY/environments/azure \
  --input - <<< '{"deployment_branch_policy": {"protected_branches": false, "custom_branch_policies": true}}'
gh api --method POST repos/$REPOSITORY/environments/azure/deployment-branch-policies -f name=master

gh secret set AZURE_CLIENT_ID --repo $REPOSITORY --body $APP_ID
gh secret set AZURE_TENANT_ID --repo $REPOSITORY --body "$(az account show --query tenantId --output tsv)"
gh secret set AZURE_SUBSCRIPTION_ID --repo $REPOSITORY --body "$(az account show --query id --output tsv)"

Then run the workflow once:

gh workflow run azure.yml --repo $REPOSITORY

After the first run, make the winter-getting-started package public in the package settings on GitHub, so that Azure can pull the image without credentials, and run the workflow again.

A budget alert on the resource group in the Azure portal guards against surprises.

About

No description or website provided.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages