Leash platform SDK - unified Leash() client with auth, env, and integrations namespaces
Project description
Leash SDK (Python)
Python SDK for the Leash platform. Construct a single Leash() client per
request and reach identity, env, and integrations through one object.
pip install leash-sdk
Quick start
from leash import Leash
# Pass the request object from your route handler.
# Works with Flask, FastAPI / Starlette, Django, and plain dict-style
# requests (anything that exposes cookies or headers).
leash = Leash(request=request)
# Identity - sync, returns None when not authenticated
user = leash.auth.user()
if user:
print(user.email)
# Runtime env-var resolution from the Leash platform
api_key = leash.env.get("OPENAI_API_KEY") # Optional[str]
api_key = leash.env.get("STRIPE_SECRET_KEY", fresh=True)
values = leash.env.get_many(["OPENAI_API_KEY", "STRIPE_KEY"])
# Integrations
messages = leash.integrations.gmail.list_messages(max_results=5)
issues = leash.integrations.linear.list_issues(state_type="started")
events = leash.integrations.google_calendar.list_events(
time_min="2026-04-01T00:00:00Z",
time_max="2026-04-30T00:00:00Z",
)
The Leash() constructor
Leash(request=...) is server-only in 0.4. It accepts any framework
request object that exposes cookies or headers:
| Framework | What the SDK reads |
|---|---|
| Flask | request.cookies['leash-auth'] |
| Django | request.COOKIES['leash-auth'] or request.META['HTTP_COOKIE'] |
| FastAPI / Starlette | request.cookies.get('leash-auth') |
| Raw / dict | request['cookie'] or request.headers['cookie'] |
Authorization: Bearer <jwt> headers are also recognised - used by the
CLI / agent flow.
Authentication precedence
LEASH_API_KEYenv var (server-only, never request-bound)Authorization: Bearer <jwt>header on the request (CLI / agent)leash-authcookie on the request (browser to deployed app)
All three may coexist. The SDK forwards each in the appropriate header so the platform can pick the right credential.
Namespaces
leash.auth
user = leash.auth.user() # Optional[LeashUser]
authed = leash.auth.is_authenticated() # bool
leash.env
value = leash.env.get("OPENAI_API_KEY") # Optional[str]
value = leash.env.get("STRIPE_KEY", fresh=True)
mapping = leash.env.get_many(["A", "B", "C"]) # dict[str, Optional[str]]
Results are cached per Leash instance for 60 seconds.
leash.env.get requires LEASH_API_KEY to be set (env var or api_key=
constructor arg). Missing / not-declared keys return None; auth, plan,
and platform errors raise LeashError.
leash.integrations
leash.integrations.gmail.list_messages(max_results=5)
leash.integrations.gmail.send_message(to="x@y.com", subject="hi", body="...")
leash.integrations.google_calendar.list_events(time_min="...", time_max="...")
leash.integrations.google_calendar.create_event(
summary="Sync", start={"dateTime": "..."}, end={"dateTime": "..."},
)
leash.integrations.google_drive.list_files(query="...")
leash.integrations.google_drive.upload_file(
name="report.txt", content="...", mime_type="text/plain",
)
leash.integrations.linear.list_issues(state_type="started")
leash.integrations.linear.create_issue(team_id="...", title="...")
For providers without a typed wrapper yet (GitHub, HubSpot, Jira, Slack, ...) use the escape hatch:
slack = leash.integrations.provider("slack")
slack.call("post-message", {"channel": "#general", "text": "hi"})
Naming differences vs the TypeScript SDK
The TS SDK exposes leash.integrations.calendar and leash.integrations.drive.
The Python SDK uses the longer google_calendar and google_drive names so
they match the platform's integration_providers.id values (and so calendar
doesn't shadow the stdlib calendar module). The short TS names are wired up
as aliases — leash.integrations.calendar and leash.integrations.drive both
work and point to the same instances.
Errors
Every SDK call raises a single error type: LeashError. It carries a
stable code plus a message, optional action (remediation hint),
see_also (docs URL), status (HTTP), and cause.
from leash import Leash, LeashError
try:
leash.integrations.gmail.list_messages()
except LeashError as err:
if err.code == "INTEGRATION_NOT_ENABLED":
print("Connect Gmail at:", err.see_also)
elif err.code == "UPGRADE_REQUIRED":
print("Upgrade required:", err.message)
else:
raise
Known codes: NO_API_KEY, NO_REQUEST_SERVER_CONSTRUCT, UNAUTHORIZED,
NO_AUTH_CONTEXT, INTEGRATION_NOT_ENABLED, INTEGRATION_ERROR,
UPGRADE_REQUIRED, NETWORK_ERROR, KEY_NOT_DECLARED, INVALID_KEY,
SOURCE_RESYNC_FAILED, ENV_FETCH_ERROR.
What's NOT in 0.4 yet
Leash.create_dev_auth_handler()— the local dev cookie-exchange helper from the TS SDK isn't yet implemented in Python (per-framework response types make a clean cross-framework version nontrivial). Tracked: LEA-262.LeashIntegrationslegacy helpers (is_connected,get_connections,get_connect_url, etc.) — these were 0.3-era patterns. The TS SDK 0.4 also dropped them. If you need them, file an issue.- Async surface — sync only in 0.4.0. Wrap with
asyncio.to_thread(...)for async code; native async coming in 0.4.1+ if demand justifies.
Sync only in 0.4
The SDK uses httpx's sync client. Async-first callers can wrap calls:
import asyncio
messages = await asyncio.to_thread(
leash.integrations.gmail.list_messages, max_results=5,
)
A native async surface is on the 0.4.1 roadmap.
Lifecycle
Leash owns an internal httpx.Client. Either call leash.close() or
use it as a context manager:
with Leash(request=request) as leash:
user = leash.auth.user()
msgs = leash.integrations.gmail.list_messages()
You can inject your own client with Leash(request=..., http_client=...).
Framework examples
Flask
from flask import Flask, request, jsonify
from leash import Leash
app = Flask(__name__)
@app.get("/me")
def me():
leash = Leash(request=request)
user = leash.auth.user()
return jsonify({"user": user.__dict__ if user else None})
FastAPI
from fastapi import FastAPI, Request
from leash import Leash
app = FastAPI()
@app.get("/me")
async def me(request: Request):
leash = Leash(request=request)
user = leash.auth.user()
return {"user": user.__dict__ if user else None}
Django
from django.http import JsonResponse
from leash import Leash
def me(request):
leash = Leash(request=request)
user = leash.auth.user()
return JsonResponse({"user": user.__dict__ if user else None})
License
Apache-2.0
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file leash_sdk-0.4.0.tar.gz.
File metadata
- Download URL: leash_sdk-0.4.0.tar.gz
- Upload date:
- Size: 28.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
563cda3a723e11ba0155fad3f9a6fc24a23b1c431bb3661f2b0462de0bf49e9f
|
|
| MD5 |
46f5c3a627da95d0f2d78e6c6e9a1bed
|
|
| BLAKE2b-256 |
710592c06159b4c9dc70ff2614fcc0f3f5505ff7a0ffcae46e886574ac94b8ab
|
File details
Details for the file leash_sdk-0.4.0-py3-none-any.whl.
File metadata
- Download URL: leash_sdk-0.4.0-py3-none-any.whl
- Upload date:
- Size: 26.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
39e60aed1cec4a059775b90595e5231c24d6e58a8f1a05315ae6cedf7caf4a91
|
|
| MD5 |
ca5ff8db2b412b943e52be567ed9dc8d
|
|
| BLAKE2b-256 |
afd2e949110a427863c4db61a985106445022e25922debfa6dba7d24fb341a4c
|