Encore.py
An infrastructure SDK for Python
Encore.py is the Python SDK for Encore. It lets you declare the infrastructure your application needs, such as Postgres databases, Pub/Sub topics, object storage buckets, Redis databases, cron jobs and secrets, as module-level variables in your Python code, and use them through async Python APIs.
Encore.py covers infrastructure only. It has no API framework: your HTTP routes stay in the web framework you already use, such as FastAPI, Django or Flask. Encore reads the infrastructure declarations from your code, runs matching infrastructure locally with encore run, and gives each test run its own isolated copy with encore test. With the Encore platform, it provisions the same resources in your own AWS or GCP account when you deploy.
Encore.py is in early access. APIs may still change between releases, and a few features are recorded by Encore but not enforced at runtime yet. Each page notes where this applies.
What you get with Encore.py
- Infrastructure declared in Python: Postgres databases, Pub/Sub topics and subscriptions, object storage buckets, Redis databases, cron jobs, secrets and configuration are module-level variables. Encore reads them statically, so it knows which service uses which resource and what it does with it.
- Your own web framework: Encore.py works alongside FastAPI, Django, Flask or plain
http.server. Encore starts your service and tells it which address to listen on; the routing is yours. - Local infrastructure, automatically:
encore runstarts a local Postgres, Pub/Sub broker, object storage and Redis for your app, and applies your migrations. There are no Docker Compose files to maintain. - Isolated tests:
encore testrunspytestwith its own databases, buckets, topics and Redis keys, created on first use and removed afterwards. - Observability: logging, metrics and tracing that show up in Encore's local development dashboard, with OpenTelemetry providers for your own spans and instruments.
The infrastructure primitives
Each primitive is a module-level variable, declared in the service that owns it. Encore's parser discovers the declaration; the SDK connects it to the provisioned resource the first time you use it.
from encore.storage import postgres
db = postgres.Database("orders", migrations="./migrations")
async def count_orders() -> int:
row = await db.query("SELECT count(*) AS n FROM orders").one()
return row["n"]
from encore import pubsub
from pydantic import BaseModel
class Order(BaseModel):
order_id: int
orders = pubsub.Topic("orders")
async def process_order(message: pubsub.Message[Order]) -> None:
print("Fulfilling order", message.data.order_id)
order_processor = pubsub.Subscription[Order](
orders, "process-orders", handler=process_order
)
from encore.storage import objects
uploads = objects.Bucket("uploads")
async def save_greeting() -> None:
await uploads.upload("hello.txt", b"Hello!", content_type="text/plain")
from encore.storage import redis
sessions = redis.Database("sessions")
async def remember(user_id: str) -> None:
await sessions.string.set(f"user:{user_id}", b"active", ex=60)
from encore import cron
_ = cron.Job(
"cleanup",
path="/internal/cleanup",
title="Nightly cleanup",
every="24h",
)
from encore import secrets
stripe_key = secrets.Secret[str]("StripeKey")
def stripe_headers() -> dict[str, str]:
return {"Authorization": f"Bearer {stripe_key.value}"}
A cron job has no runtime client: when it is due, Encore sends a POST request for path to the service that declares the job, and your framework handles it like any other request.
Declarations describe what the application needs, not how it's provisioned. Instance sizes, networking and other deployment settings are configured per environment, so the same code runs locally, in preview environments and in production.
How Encore reads your code
Encore reads your declarations without running your code, so they follow a few rules. The parser reports a violation as an error when it parses the app, before anything runs.
- Declare resources as module-level variables, never inside a function, class or conditional.
- Pass resource names, and the configuration arguments Encore reads (such as
migrationsor a cron job'spath), as string or other literals. - Don't alias a declaration (
other = db) or pass it to a function. To hand a database or Redis database to other code, pass a handle fromread()orwrite(). Encore records the access level at that call. - Call methods directly on the declaration (
db.query(...)), rather than storing a method (q = db.query). - Use resources from code inside a service directory. Encore attributes each use to the service whose directory contains the file, and grants that service access to the resource.
The individual pages describe the rules that are specific to each primitive.
How an app is laid out
An Encore app is a directory with an encore.app file at its root. Each service is a directory with an encore.service.json file. A single-service app can keep both at the root:
my-app/
├── encore.app
├── encore.service.json
├── pyproject.toml
├── uv.lock
├── main.py
├── resources.py
└── migrations/
└── 1_create_orders.up.sql
The service file names the service and its language, and tells Encore which HTTP paths the service serves:
encore.service.json{
"name": "orders",
"lang": "python",
"api": [
{ "type": "http", "prefix": "/", "name": "orders", "expose": true }
]
}
See Services for every field, and for apps with several services.
encore app new my-app --template python creates an app like this with a minimal http.server service and no dependencies. To adopt an existing directory, run encore app init in it. Either way, add the SDK and your framework to the service's dependencies.
Installing the SDK
The SDK is the encore-dev package, imported as encore. It needs Python 3.9 or later. Add encore-dev to your project's dependencies alongside your framework:
pyproject.toml[project]
name = "orders"
version = "0.1.0"
requires-python = ">=3.9"
dependencies = [
"encore-dev",
"fastapi",
"uvicorn",
]
The package is pure Python. Its native runtime ships with the Encore CLI, which points the SDK at it whenever it starts your code: in encore run, encore test and encore exec. Importing encore initializes the runtime and reads its configuration from Encore, so run your app through those commands rather than with python main.py on its own.
Encore runs your code through the package manager it finds in the service's directory, detected from the lockfile:
The entry point is main.py, or app.py if there is no main.py. Without a lockfile, the dependencies must already be importable by python3. To run something else, set a custom run command in encore.service.json.
Starting Encore in your app
Your entry point starts your web server on the address Encore gives it in the ENCORE_LISTEN_ADDR environment variable, as host:port. Once your resource declarations are imported, call await encore.start() from your framework's startup hook. It starts the handlers of the Pub/Sub subscriptions your service declares on the application's event loop, and returns.
With FastAPI, that's the lifespan handler:
main.pyimport os
from contextlib import asynccontextmanager
import encore
import uvicorn
from fastapi import FastAPI
# Importing resources.py runs its declarations, including any subscriptions.
from resources import db
@asynccontextmanager
async def lifespan(app: FastAPI):
await encore.start()
yield
app = FastAPI(lifespan=lifespan)
@app.get("/orders/{order_id}")
async def get_order(order_id: int) -> dict:
row = await db.query("SELECT id, total FROM orders WHERE id = $1", order_id).one()
return row or {}
if __name__ == "__main__":
host, _, port = os.environ.get("ENCORE_LISTEN_ADDR", "0.0.0.0:8000").rpartition(":")
uvicorn.run(app, host=host, port=int(port))
encore.start() doesn't take over the event loop or install signal handlers. Calling it again on the same loop is safe, and starts subscriptions imported since the last call. A service without subscriptions doesn't need it, but calling it does no harm.
The SDK's I/O methods are async. From synchronous code, such as a Django view, call them through a bridge such as asgiref.sync.async_to_sync:
from asgiref.sync import async_to_sync
from .resources import uploads
def download(request, name):
data = async_to_sync(uploads.download)(name)
...
Initialize Encore separately in each worker process: forking a process after the SDK has been imported isn't supported. For how Encore stops your app, see Graceful shutdown.
Running and testing
encore runparses the app, starts the local infrastructure, applies migrations and starts each service. It restarts a service when its code changes, so turn off your framework's own reloader.encore testruns each service's tests withpytest, against infrastructure created for the test run. See Testing.encore execruns a one-off command, such as a seed script orpython manage.py migrate, attached to the same local infrastructure asencore run:
encore exec -- uv run python manage.py migrate
Next steps
- Services: the
encore.service.jsonfile, several services in one app, and service URLs. - Databases: Postgres, migrations, queries, transactions and ORMs.
- Pub/Sub, Object Storage, Redis, Cron Jobs and Secrets.
- Configuration and Metadata: typed config values and information about the running app.
- Testing and Graceful shutdown.
- Logging, Metrics and Tracing.
- App Structure: how to divide an application into services.