or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

compatibility.mdcurrent-thread-executor.mdindex.mdlocal-storage.mdserver-base.mdsync-async.mdtesting.mdtimeout.mdtype-definitions.mdwsgi-integration.md

wsgi-integration.mddocs/

0

# WSGI Integration

1

2

Adapters for running WSGI applications within ASGI servers, enabling legacy web applications to benefit from async server capabilities. This allows gradual migration from WSGI to ASGI without requiring complete application rewrites.

3

4

## Capabilities

5

6

### WSGI-to-ASGI Adapter

7

8

Main adapter class that wraps WSGI applications to run in ASGI servers, handling protocol translation and request/response conversion.

9

10

```python { .api }

11

class WsgiToAsgi:

12

"""Main WSGI-to-ASGI adapter."""

13

14

def __init__(self, wsgi_application):

15

"""

16

Initialize adapter with WSGI application.

17

18

Parameters:

19

- wsgi_application: callable, WSGI application following PEP 3333

20

"""

21

22

def __call__(self, scope, receive, send):

23

"""

24

ASGI application interface.

25

26

Parameters:

27

- scope: dict, ASGI scope containing request information

28

- receive: callable, ASGI receive channel for request body

29

- send: callable, ASGI send channel for response

30

31

Returns:

32

Coroutine that handles the WSGI application execution

33

"""

34

```

35

36

### Per-Connection Instance

37

38

Internal adapter instance that handles individual connections, managing WSGI environ construction and response handling.

39

40

```python { .api }

41

class WsgiToAsgiInstance:

42

"""Per-connection WSGI-to-ASGI adapter instance."""

43

44

def __init__(self, wsgi_application):

45

"""

46

Initialize instance with WSGI application.

47

48

Parameters:

49

- wsgi_application: callable, WSGI application

50

"""

51

52

def __call__(self, scope, receive, send):

53

"""

54

Handle ASGI connection for this instance.

55

56

Parameters:

57

- scope: dict, ASGI scope

58

- receive: callable, ASGI receive channel

59

- send: callable, ASGI send channel

60

61

Returns:

62

Coroutine that processes the request

63

"""

64

65

def build_environ(self, scope, body):

66

"""

67

Build WSGI environ dictionary from ASGI scope.

68

69

Parameters:

70

- scope: dict, ASGI HTTP scope

71

- body: bytes, request body data

72

73

Returns:

74

dict: WSGI environ dictionary following PEP 3333

75

"""

76

77

def start_response(self, status, response_headers, exc_info=None):

78

"""

79

WSGI start_response callable implementation.

80

81

Parameters:

82

- status: str, HTTP status (e.g., '200 OK')

83

- response_headers: list, list of (name, value) header tuples

84

- exc_info: tuple, exception information (optional)

85

86

Returns:

87

callable: Function to write response body (for compatibility)

88

"""

89

```

90

91

## Usage Examples

92

93

### Basic WSGI Application Adaptation

94

95

```python

96

from asgiref.wsgi import WsgiToAsgi

97

98

# Simple WSGI application

99

def simple_wsgi_app(environ, start_response):

100

status = '200 OK'

101

headers = [('Content-Type', 'text/plain')]

102

start_response(status, headers)

103

return [b'Hello from WSGI!']

104

105

# Convert to ASGI

106

asgi_app = WsgiToAsgi(simple_wsgi_app)

107

108

# Can now be used with ASGI servers

109

# await asgi_app(scope, receive, send)

110

```

111

112

### Flask Application Integration

113

114

```python

115

from flask import Flask

116

from asgiref.wsgi import WsgiToAsgi

117

118

# Create Flask application

119

app = Flask(__name__)

120

121

@app.route('/')

122

def hello():

123

return 'Hello from Flask via ASGI!'

124

125

@app.route('/api/data')

126

def api_data():

127

return {'message': 'API response', 'status': 'success'}

128

129

# Convert Flask WSGI app to ASGI

130

asgi_app = WsgiToAsgi(app.wsgi_app)

131

132

# Now compatible with ASGI servers like Uvicorn, Hypercorn, etc.

133

```

134

135

### Django WSGI Integration

136

137

```python

138

import os

139

import django

140

from django.core.wsgi import get_wsgi_application

141

from asgiref.wsgi import WsgiToAsgi

142

143

# Configure Django

144

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')

145

django.setup()

146

147

# Get Django WSGI application

148

django_wsgi_app = get_wsgi_application()

149

150

# Convert to ASGI

151

django_asgi_app = WsgiToAsgi(django_wsgi_app)

152

153

# Can be served by ASGI servers alongside native ASGI applications

154

```

155

156

### Mixed ASGI/WSGI Application Router

157

158

```python

159

from asgiref.wsgi import WsgiToAsgi

160

161

class MixedRouter:

162

"""Router that can handle both ASGI and WSGI applications."""

163

164

def __init__(self):

165

self.routes = []

166

167

def add_wsgi_app(self, path_prefix, wsgi_app):

168

"""Add a WSGI application at the given path prefix."""

169

asgi_app = WsgiToAsgi(wsgi_app)

170

self.routes.append((path_prefix, asgi_app, 'wsgi'))

171

172

def add_asgi_app(self, path_prefix, asgi_app):

173

"""Add an ASGI application at the given path prefix."""

174

self.routes.append((path_prefix, asgi_app, 'asgi'))

175

176

async def __call__(self, scope, receive, send):

177

path = scope['path']

178

179

for prefix, app, app_type in self.routes:

180

if path.startswith(prefix):

181

# Modify scope to remove prefix

182

new_scope = {**scope, 'path': path[len(prefix):]}

183

await app(new_scope, receive, send)

184

return

185

186

# 404 response

187

await send({

188

'type': 'http.response.start',

189

'status': 404,

190

'headers': [[b'content-type', b'text/plain']],

191

})

192

await send({

193

'type': 'http.response.body',

194

'body': b'Not Found',

195

})

196

197

# Usage

198

router = MixedRouter()

199

200

# Add WSGI applications (automatically converted)

201

router.add_wsgi_app('/legacy', simple_wsgi_app)

202

router.add_wsgi_app('/flask', app.wsgi_app)

203

204

# Add native ASGI applications

205

async def native_asgi_app(scope, receive, send):

206

await send({

207

'type': 'http.response.start',

208

'status': 200,

209

'headers': [[b'content-type', b'application/json']],

210

})

211

await send({

212

'type': 'http.response.body',

213

'body': b'{"message": "Native ASGI response"}',

214

})

215

216

router.add_asgi_app('/api', native_asgi_app)

217

```

218

219

### Middleware with WSGI Applications

220

221

```python

222

from asgiref.wsgi import WsgiToAsgi

223

import time

224

225

class TimingMiddleware:

226

"""Middleware that adds timing headers to responses."""

227

228

def __init__(self, app):

229

self.app = app

230

231

async def __call__(self, scope, receive, send):

232

start_time = time.time()

233

234

async def send_wrapper(message):

235

if message['type'] == 'http.response.start':

236

duration = time.time() - start_time

237

headers = list(message.get('headers', []))

238

headers.append([

239

b'x-response-time',

240

f'{duration:.3f}s'.encode()

241

])

242

message = {**message, 'headers': headers}

243

await send(message)

244

245

await self.app(scope, receive, send_wrapper)

246

247

# Apply middleware to WSGI application

248

timed_wsgi_app = TimingMiddleware(WsgiToAsgi(simple_wsgi_app))

249

```

250

251

### Custom WSGI Application with Complex Logic

252

253

```python

254

from asgiref.wsgi import WsgiToAsgi

255

import json

256

from urllib.parse import parse_qs

257

258

def api_wsgi_app(environ, start_response):

259

"""WSGI application with API-like behavior."""

260

method = environ['REQUEST_METHOD']

261

path = environ['PATH_INFO']

262

263

if path == '/health' and method == 'GET':

264

status = '200 OK'

265

headers = [('Content-Type', 'application/json')]

266

start_response(status, headers)

267

return [json.dumps({'status': 'healthy'}).encode()]

268

269

elif path == '/echo' and method == 'POST':

270

# Read request body

271

try:

272

content_length = int(environ.get('CONTENT_LENGTH', 0))

273

except ValueError:

274

content_length = 0

275

276

body = environ['wsgi.input'].read(content_length)

277

278

status = '200 OK'

279

headers = [('Content-Type', 'application/json')]

280

start_response(status, headers)

281

282

response = {

283

'method': method,

284

'path': path,

285

'body': body.decode('utf-8'),

286

'headers': dict(environ.items())

287

}

288

return [json.dumps(response, default=str).encode()]

289

290

else:

291

status = '404 Not Found'

292

headers = [('Content-Type', 'text/plain')]

293

start_response(status, headers)

294

return [b'Not Found']

295

296

# Convert to ASGI for modern server compatibility

297

api_asgi_app = WsgiToAsgi(api_wsgi_app)

298

```

299

300

## Protocol Translation

301

302

The adapter handles several important protocol differences:

303

304

- **Request Body**: Collects ASGI message stream into WSGI-compatible input

305

- **Response Streaming**: Converts WSGI iterator to ASGI message stream

306

- **Headers**: Transforms between ASGI bytes headers and WSGI string headers

307

- **Environment Variables**: Maps ASGI scope to WSGI environ dictionary

308

- **Error Handling**: Translates exceptions between protocols

309

- **Context Variables**: Maintains context across sync/async boundaries