or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cli.mdconfiguration.mdformatting.mdindex.mdjupyter.mdserver.mdtypes.md

server.mddocs/

0

# Server API (BlackD)

1

2

BlackD provides an HTTP server API for formatting Python code via REST endpoints, enabling integration with editors, IDEs, and other tools that need remote code formatting capabilities.

3

4

## Capabilities

5

6

### Server Management

7

8

Functions for starting and configuring the BlackD HTTP server.

9

10

```python { .api }

11

def main(bind_host: str, bind_port: int) -> None:

12

"""

13

Start blackd HTTP server.

14

15

Parameters:

16

- bind_host: Host address to bind to (e.g., "127.0.0.1", "0.0.0.0")

17

- bind_port: Port number to bind to (e.g., 45484)

18

19

Note:

20

- Runs asyncio event loop

21

- Handles SIGINT/SIGTERM for graceful shutdown

22

- Uses aiohttp web framework

23

"""

24

25

def make_app() -> web.Application:

26

"""

27

Create aiohttp application with BlackD routes.

28

29

Returns:

30

Configured aiohttp.web.Application instance

31

32

Note:

33

- Configures POST / route for formatting

34

- Sets up request handling and error responses

35

- Includes CORS headers and protocol versioning

36

"""

37

```

38

39

### Request Handling

40

41

Core HTTP request processing for code formatting.

42

43

```python { .api }

44

def handle(request: web.Request, executor: Executor) -> web.Response:

45

"""

46

Main HTTP request handler for formatting requests.

47

48

Parameters:

49

- request: aiohttp web request with Python code and headers

50

- executor: ThreadPoolExecutor for async processing

51

52

Returns:

53

aiohttp web response with formatted code or error

54

55

Request Format:

56

- Method: POST

57

- Path: /

58

- Body: Python source code to format

59

- Headers: Configuration options (see Header Constants)

60

61

Response Format:

62

- 200: Successfully formatted (body contains formatted code)

63

- 204: No changes needed (empty body)

64

- 400: Invalid request (syntax error, invalid headers)

65

- 500: Internal formatting error

66

67

Headers:

68

- X-Black-Version: Black version used for formatting

69

- Content-Type: text/plain for code, application/json for errors

70

"""

71

```

72

73

### Configuration Parsing

74

75

Functions for parsing Black configuration from HTTP headers.

76

77

```python { .api }

78

def parse_mode(headers: MultiMapping[str]) -> black.Mode:

79

"""

80

Parse Black Mode configuration from HTTP headers.

81

82

Parameters:

83

- headers: HTTP request headers containing configuration

84

85

Returns:

86

Mode object with configuration from headers

87

88

Supported Headers:

89

- X-Line-Length: Maximum line length (integer)

90

- X-Python-Variant: Target Python versions (comma-separated)

91

- X-Skip-String-Normalization: "true" to disable string normalization

92

- X-Skip-Magic-Trailing-Comma: "true" to disable trailing comma

93

- X-Skip-Source-First-Line: "true" to skip first line

94

- X-Preview: "true" to enable preview features

95

- X-Unstable: "true" to enable unstable features

96

- X-Enable-Unstable-Feature: Specific unstable features (comma-separated)

97

98

Raises:

99

- InvalidVariantHeader: If Python variant header is invalid

100

- HeaderError: If other headers contain invalid values

101

"""

102

```

103

104

## HTTP Protocol

105

106

### Header Constants

107

108

```python { .api }

109

# Protocol and versioning

110

PROTOCOL_VERSION_HEADER = "X-Protocol-Version"

111

BLACK_VERSION_HEADER = "X-Black-Version"

112

113

# Configuration headers

114

LINE_LENGTH_HEADER = "X-Line-Length"

115

PYTHON_VARIANT_HEADER = "X-Python-Variant"

116

SKIP_SOURCE_FIRST_LINE = "X-Skip-Source-First-Line"

117

SKIP_STRING_NORMALIZATION_HEADER = "X-Skip-String-Normalization"

118

SKIP_MAGIC_TRAILING_COMMA = "X-Skip-Magic-Trailing-Comma"

119

PREVIEW = "X-Preview"

120

UNSTABLE = "X-Unstable"

121

ENABLE_UNSTABLE_FEATURE = "X-Enable-Unstable-Feature"

122

123

# Processing options

124

FAST_OR_SAFE_HEADER = "X-Fast-Or-Safe"

125

DIFF_HEADER = "X-Diff"

126

```

127

128

### Exception Classes

129

130

```python { .api }

131

class HeaderError(Exception):

132

"""Raised when HTTP headers contain invalid values."""

133

pass

134

135

class InvalidVariantHeader(Exception):

136

"""Raised when X-Python-Variant header is invalid."""

137

pass

138

```

139

140

## Usage Examples

141

142

### Starting BlackD Server

143

144

```python

145

import blackd

146

147

# Start server on localhost:45484

148

blackd.main("127.0.0.1", 45484)

149

150

# Start server on all interfaces

151

blackd.main("0.0.0.0", 8080)

152

```

153

154

### Command Line Server Startup

155

156

```bash

157

# Default configuration

158

blackd

159

160

# Custom host and port

161

blackd --bind-host 0.0.0.0 --bind-port 8080

162

```

163

164

### HTTP Client Usage

165

166

```python

167

import requests

168

169

# Format code via HTTP API

170

code_to_format = '''

171

def hello(name):

172

print(f"Hello {name}!")

173

'''

174

175

response = requests.post(

176

"http://localhost:45484/",

177

data=code_to_format,

178

headers={

179

"Content-Type": "text/plain",

180

"X-Line-Length": "79",

181

"X-Python-Variant": "py39,py310",

182

"X-Skip-String-Normalization": "false",

183

"X-Fast-Or-Safe": "safe"

184

}

185

)

186

187

if response.status_code == 200:

188

formatted_code = response.text

189

print("Formatted successfully:")

190

print(formatted_code)

191

elif response.status_code == 204:

192

print("No changes needed")

193

elif response.status_code == 400:

194

print(f"Error: {response.text}")

195

```

196

197

### cURL Example

198

199

```bash

200

# Format code with cURL

201

curl -X POST http://localhost:45484/ \

202

-H "Content-Type: text/plain" \

203

-H "X-Line-Length: 88" \

204

-H "X-Python-Variant: py39" \

205

-d "def hello(name):print(f'Hello {name}!')"

206

```

207

208

### JavaScript/Node.js Client

209

210

```javascript

211

const axios = require('axios');

212

213

async function formatCode(code) {

214

try {

215

const response = await axios.post('http://localhost:45484/', code, {

216

headers: {

217

'Content-Type': 'text/plain',

218

'X-Line-Length': '88',

219

'X-Python-Variant': 'py39,py310',

220

'X-Preview': 'true'

221

}

222

});

223

224

if (response.status === 200) {

225

return response.data; // Formatted code

226

} else if (response.status === 204) {

227

return code; // No changes needed

228

}

229

} catch (error) {

230

if (error.response && error.response.status === 400) {

231

throw new Error(`Formatting error: ${error.response.data}`);

232

}

233

throw error;

234

}

235

}

236

237

// Usage

238

formatCode("def hello(name):print(f'Hello {name}!')")

239

.then(formatted => console.log(formatted))

240

.catch(error => console.error(error));

241

```

242

243

### Editor Integration Example

244

245

```python

246

import requests

247

import json

248

249

class BlackDFormatter:

250

def __init__(self, host="localhost", port=45484):

251

self.base_url = f"http://{host}:{port}/"

252

253

def format_code(self, code, **options):

254

"""Format code with optional configuration."""

255

headers = {"Content-Type": "text/plain"}

256

257

# Convert options to headers

258

if "line_length" in options:

259

headers["X-Line-Length"] = str(options["line_length"])

260

if "target_versions" in options:

261

headers["X-Python-Variant"] = ",".join(options["target_versions"])

262

if "preview" in options and options["preview"]:

263

headers["X-Preview"] = "true"

264

if "skip_string_normalization" in options:

265

headers["X-Skip-String-Normalization"] = str(options["skip_string_normalization"]).lower()

266

267

try:

268

response = requests.post(self.base_url, data=code, headers=headers, timeout=10)

269

270

if response.status_code == 200:

271

return response.text, True # (formatted_code, changed)

272

elif response.status_code == 204:

273

return code, False # (original_code, not_changed)

274

elif response.status_code == 400:

275

raise ValueError(f"Syntax error: {response.text}")

276

else:

277

raise RuntimeError(f"Server error: {response.status_code}")

278

279

except requests.RequestException as e:

280

raise ConnectionError(f"Cannot connect to BlackD server: {e}")

281

282

# Usage in editor plugin

283

formatter = BlackDFormatter()

284

285

def format_selection(editor_code, line_length=88):

286

try:

287

formatted, changed = formatter.format_code(

288

editor_code,

289

line_length=line_length,

290

target_versions=["py39", "py310"],

291

preview=False

292

)

293

if changed:

294

return formatted

295

else:

296

return None # No changes needed

297

except Exception as e:

298

# Show error to user

299

print(f"Formatting failed: {e}")

300

return None

301

```

302

303

### Async Server Application Integration

304

305

```python

306

import aiohttp

307

import asyncio

308

import blackd

309

310

async def integrate_with_blackd():

311

"""Example of integrating BlackD into another aiohttp application."""

312

313

# Create BlackD application

314

blackd_app = blackd.make_app()

315

316

# Create main application

317

main_app = aiohttp.web.Application()

318

319

# Add BlackD as a sub-application

320

main_app.add_subapp('/format/', blackd_app)

321

322

# Add other routes to main app

323

async def health_check(request):

324

return aiohttp.web.Response(text="OK")

325

326

main_app.router.add_get('/health', health_check)

327

328

return main_app

329

330

# Run integrated server

331

async def run_integrated_server():

332

app = await integrate_with_blackd()

333

runner = aiohttp.web.AppRunner(app)

334

await runner.setup()

335

336

site = aiohttp.web.TCPSite(runner, 'localhost', 8080)

337

await site.start()

338

339

print("Server running on http://localhost:8080")

340

print("BlackD available at http://localhost:8080/format/")

341

342

# Keep server running

343

await asyncio.Future() # Run forever

344

345

# Usage

346

# asyncio.run(run_integrated_server())

347

```

348

349

### Configuration Examples

350

351

```python

352

# Different formatting configurations via headers

353

354

# Strict Python 3.9 compatibility

355

headers = {

356

"X-Python-Variant": "py39",

357

"X-Line-Length": "79",

358

"X-Skip-String-Normalization": "false"

359

}

360

361

# Preview features enabled

362

headers = {

363

"X-Preview": "true",

364

"X-Line-Length": "100",

365

"X-Python-Variant": "py310,py311"

366

}

367

368

# Conservative formatting

369

headers = {

370

"X-Skip-String-Normalization": "true",

371

"X-Skip-Magic-Trailing-Comma": "true",

372

"X-Line-Length": "88"

373

}

374

375

# Unstable features

376

headers = {

377

"X-Unstable": "true",

378

"X-Enable-Unstable-Feature": "string_processing,wrap_long_dict_values_in_parens"

379

}

380

```

381

382

### Error Handling

383

384

```python

385

import requests

386

import json

387

388

def format_with_error_handling(code):

389

try:

390

response = requests.post("http://localhost:45484/", data=code)

391

392

if response.status_code == 200:

393

return response.text

394

elif response.status_code == 204:

395

return code # No changes

396

elif response.status_code == 400:

397

# Parse error details if JSON

398

try:

399

error_data = response.json()

400

raise SyntaxError(error_data.get("message", response.text))

401

except json.JSONDecodeError:

402

raise SyntaxError(response.text)

403

else:

404

raise RuntimeError(f"Server error {response.status_code}: {response.text}")

405

406

except requests.ConnectionError:

407

raise ConnectionError("BlackD server not available")

408

except requests.Timeout:

409

raise TimeoutError("BlackD server timeout")

410

```

411

412

## Types

413

414

```python { .api }

415

# aiohttp types

416

from aiohttp import web

417

from aiohttp.web import Request, Response, Application

418

from multidict import MultiMapping

419

420

# Async execution

421

from concurrent.futures import Executor, ThreadPoolExecutor

422

423

# Server configuration

424

ServerHost = str

425

ServerPort = int

426

HeaderDict = dict[str, str]

427

```