0
# Data Structures
1
2
Starlette provides specialized data structures for handling web-specific data types like URLs, headers, query parameters, form data, and file uploads, with convenient APIs for manipulation and access.
3
4
## URL Manipulation
5
6
### URL Class
7
8
```python { .api }
9
from starlette.datastructures import URL
10
from urllib.parse import SplitResult
11
from typing import Any, Dict, Union
12
13
class URL(str):
14
"""
15
URL manipulation class with convenient methods.
16
17
Immutable URL class that provides easy access to URL components
18
and methods for creating modified URLs.
19
"""
20
21
def __new__(
22
cls,
23
url: str = "",
24
scope: Dict[str, Any] = None,
25
**components: Any
26
) -> "URL":
27
"""
28
Create URL from string or ASGI scope.
29
30
Args:
31
url: URL string
32
scope: ASGI scope dict
33
**components: URL components to override
34
"""
35
36
@property
37
def scheme(self) -> str:
38
"""URL scheme (http, https, ws, wss)."""
39
40
@property
41
def netloc(self) -> str:
42
"""Network location (host:port)."""
43
44
@property
45
def hostname(self) -> str:
46
"""Hostname without port."""
47
48
@property
49
def port(self) -> int | None:
50
"""Port number or None if not specified."""
51
52
@property
53
def path(self) -> str:
54
"""URL path."""
55
56
@property
57
def query(self) -> str:
58
"""Query string."""
59
60
@property
61
def fragment(self) -> str:
62
"""URL fragment (after #)."""
63
64
@property
65
def username(self) -> str | None:
66
"""Username from URL."""
67
68
@property
69
def password(self) -> str | None:
70
"""Password from URL."""
71
72
@property
73
def is_secure(self) -> bool:
74
"""True if scheme is https or wss."""
75
76
@property
77
def components(self) -> SplitResult:
78
"""urllib.parse.SplitResult object."""
79
80
def replace(self, **kwargs: Any) -> "URL":
81
"""
82
Create new URL with replaced components.
83
84
Args:
85
**kwargs: Components to replace (scheme, netloc, path, query, fragment)
86
87
Returns:
88
New URL with replaced components
89
"""
90
91
def include_query_params(self, **params: Any) -> "URL":
92
"""
93
Create new URL with additional query parameters.
94
95
Args:
96
**params: Query parameters to add
97
98
Returns:
99
New URL with added parameters
100
"""
101
102
def replace_query_params(self, **params: Any) -> "URL":
103
"""
104
Create new URL with replaced query parameters.
105
106
Args:
107
**params: Query parameters to set (replaces all existing)
108
109
Returns:
110
New URL with new query parameters
111
"""
112
113
def remove_query_params(
114
self,
115
keys: Union[str, list[str]]
116
) -> "URL":
117
"""
118
Create new URL with specified query parameters removed.
119
120
Args:
121
keys: Parameter key(s) to remove
122
123
Returns:
124
New URL without specified parameters
125
"""
126
```
127
128
### URLPath Class
129
130
```python { .api }
131
from starlette.datastructures import URLPath
132
133
class URLPath(str):
134
"""
135
URL path with protocol and host information.
136
137
Used for generating absolute URLs from relative paths.
138
"""
139
140
def __new__(
141
cls,
142
path: str,
143
protocol: str = "",
144
host: str = ""
145
) -> "URLPath":
146
"""
147
Create URLPath.
148
149
Args:
150
path: URL path
151
protocol: Protocol ("http" or "websocket")
152
host: Host name
153
"""
154
155
@property
156
def protocol(self) -> str:
157
"""Protocol type."""
158
159
@property
160
def host(self) -> str:
161
"""Host name."""
162
163
def make_absolute_url(self, base_url: str) -> str:
164
"""
165
Create absolute URL from base URL.
166
167
Args:
168
base_url: Base URL to combine with path
169
170
Returns:
171
Complete absolute URL
172
"""
173
```
174
175
## Headers Management
176
177
### Headers Class
178
179
```python { .api }
180
from starlette.datastructures import Headers, MutableHeaders
181
from typing import Iterator, List, Tuple
182
183
class Headers:
184
"""
185
Immutable case-insensitive HTTP headers.
186
187
Provides dictionary-like access to HTTP headers with
188
case-insensitive matching and multi-value support.
189
"""
190
191
def __init__(
192
self,
193
headers: Union[
194
Dict[str, str],
195
List[Tuple[str, str]],
196
List[Tuple[bytes, bytes]]
197
] = None,
198
raw: List[Tuple[bytes, bytes]] = None,
199
scope: Dict[str, Any] = None,
200
) -> None:
201
"""
202
Initialize headers.
203
204
Args:
205
headers: Headers as dict or list of tuples
206
raw: Raw header bytes (ASGI format)
207
scope: ASGI scope containing headers
208
"""
209
210
@property
211
def raw(self) -> List[Tuple[bytes, bytes]]:
212
"""Raw header list in ASGI format."""
213
214
def keys(self) -> List[str]:
215
"""Get all header names."""
216
217
def values(self) -> List[str]:
218
"""Get all header values."""
219
220
def items(self) -> List[Tuple[str, str]]:
221
"""Get header name-value pairs."""
222
223
def get(self, key: str, default: str = None) -> str | None:
224
"""
225
Get header value (case-insensitive).
226
227
Args:
228
key: Header name
229
default: Default value if header not found
230
231
Returns:
232
Header value or default
233
"""
234
235
def getlist(self, key: str) -> List[str]:
236
"""
237
Get all values for header (case-insensitive).
238
239
Args:
240
key: Header name
241
242
Returns:
243
List of all values for the header
244
"""
245
246
def mutablecopy(self) -> "MutableHeaders":
247
"""Create mutable copy of headers."""
248
249
# Dictionary-like access
250
def __getitem__(self, key: str) -> str:
251
"""Get header value (raises KeyError if not found)."""
252
253
def __contains__(self, key: str) -> bool:
254
"""Check if header exists (case-insensitive)."""
255
256
def __iter__(self) -> Iterator[str]:
257
"""Iterate over header names."""
258
259
def __len__(self) -> int:
260
"""Number of headers."""
261
262
class MutableHeaders(Headers):
263
"""
264
Mutable case-insensitive HTTP headers.
265
266
Extends Headers with methods for modifying header values.
267
"""
268
269
def __setitem__(self, key: str, value: str) -> None:
270
"""Set header value."""
271
272
def __delitem__(self, key: str) -> None:
273
"""Delete header."""
274
275
def setdefault(self, key: str, value: str) -> str:
276
"""Set header if not already present."""
277
278
def update(self, other: Union[Headers, Dict[str, str]]) -> None:
279
"""Update headers from another headers object or dict."""
280
281
def append(self, key: str, value: str) -> None:
282
"""Append value to existing header."""
283
284
def add_vary_header(self, vary: str) -> None:
285
"""Add value to Vary header."""
286
```
287
288
## Query Parameters
289
290
### QueryParams Class
291
292
```python { .api }
293
from starlette.datastructures import QueryParams, ImmutableMultiDict
294
295
class QueryParams(ImmutableMultiDict):
296
"""
297
URL query parameters with multi-value support.
298
299
Immutable dictionary-like container for URL query parameters
300
that supports multiple values per key.
301
"""
302
303
def __init__(
304
self,
305
query: Union[
306
str,
307
bytes,
308
Dict[str, str],
309
Dict[str, List[str]],
310
List[Tuple[str, str]]
311
] = None,
312
**kwargs: str
313
) -> None:
314
"""
315
Initialize query parameters.
316
317
Args:
318
query: Query string, dict, or list of tuples
319
**kwargs: Additional query parameters
320
"""
321
322
# Inherited from ImmutableMultiDict
323
def get(self, key: str, default: str = None) -> str | None:
324
"""Get first value for key."""
325
326
def getlist(self, key: str) -> List[str]:
327
"""Get all values for key."""
328
329
def keys(self) -> List[str]:
330
"""Get all parameter names."""
331
332
def values(self) -> List[str]:
333
"""Get all parameter values."""
334
335
def items(self) -> List[Tuple[str, str]]:
336
"""Get key-value pairs."""
337
338
def multi_items(self) -> List[Tuple[str, str]]:
339
"""Get all key-value pairs including duplicates."""
340
341
# Dictionary-like access
342
def __getitem__(self, key: str) -> str:
343
"""Get first value for key."""
344
345
def __contains__(self, key: str) -> bool:
346
"""Check if parameter exists."""
347
348
def __iter__(self) -> Iterator[str]:
349
"""Iterate over parameter names."""
350
```
351
352
## Form Data and File Uploads
353
354
### FormData Class
355
356
```python { .api }
357
from starlette.datastructures import FormData, UploadFile, MultiDict
358
359
class FormData(ImmutableMultiDict):
360
"""
361
Form data container supporting files and fields.
362
363
Immutable container for form data that can contain
364
both regular form fields and file uploads.
365
"""
366
367
def get(self, key: str, default: Any = None) -> Union[str, UploadFile, None]:
368
"""Get form field or file."""
369
370
def getlist(self, key: str) -> List[Union[str, UploadFile]]:
371
"""Get all values for form field."""
372
373
async def close(self) -> None:
374
"""Close all uploaded files to free resources."""
375
376
class UploadFile:
377
"""
378
Uploaded file wrapper with async file operations.
379
380
Represents a file uploaded through multipart form data
381
with methods for reading and manipulating file content.
382
"""
383
384
def __init__(
385
self,
386
file: BinaryIO,
387
size: int = None,
388
filename: str = None,
389
headers: Headers = None,
390
) -> None:
391
"""
392
Initialize upload file.
393
394
Args:
395
file: File-like object containing uploaded data
396
size: File size in bytes
397
filename: Original filename from client
398
headers: HTTP headers for this file part
399
"""
400
401
@property
402
def filename(self) -> str | None:
403
"""Original filename from client."""
404
405
@property
406
def file(self) -> BinaryIO:
407
"""Underlying file object."""
408
409
@property
410
def size(self) -> int | None:
411
"""File size in bytes."""
412
413
@property
414
def headers(self) -> Headers:
415
"""Headers for this file part."""
416
417
@property
418
def content_type(self) -> str | None:
419
"""MIME content type."""
420
421
async def read(self, size: int = -1) -> bytes:
422
"""
423
Read file content.
424
425
Args:
426
size: Number of bytes to read (-1 for all)
427
428
Returns:
429
File content as bytes
430
"""
431
432
async def seek(self, offset: int) -> None:
433
"""
434
Seek to position in file.
435
436
Args:
437
offset: Byte offset to seek to
438
"""
439
440
async def write(self, data: Union[str, bytes]) -> None:
441
"""
442
Write data to file.
443
444
Args:
445
data: Data to write to file
446
"""
447
448
async def close(self) -> None:
449
"""Close the file and free resources."""
450
```
451
452
## Multi-Value Dictionaries
453
454
### ImmutableMultiDict Class
455
456
```python { .api }
457
from starlette.datastructures import ImmutableMultiDict, MultiDict
458
459
class ImmutableMultiDict:
460
"""
461
Immutable multi-value dictionary.
462
463
Dictionary-like container that supports multiple values
464
per key while maintaining immutability.
465
"""
466
467
def __init__(
468
self,
469
items: Union[
470
Dict[str, str],
471
Dict[str, List[str]],
472
List[Tuple[str, str]]
473
] = None
474
) -> None:
475
"""Initialize from various input formats."""
476
477
def get(self, key: str, default: Any = None) -> Any:
478
"""Get first value for key."""
479
480
def getlist(self, key: str) -> List[Any]:
481
"""Get all values for key."""
482
483
def keys(self) -> List[str]:
484
"""Get all keys."""
485
486
def values(self) -> List[Any]:
487
"""Get all values (first value per key)."""
488
489
def items(self) -> List[Tuple[str, Any]]:
490
"""Get key-value pairs (first value per key)."""
491
492
def multi_items(self) -> List[Tuple[str, Any]]:
493
"""Get all key-value pairs including duplicates."""
494
495
# Dictionary-like interface
496
def __getitem__(self, key: str) -> Any:
497
"""Get first value for key."""
498
499
def __contains__(self, key: str) -> bool:
500
"""Check if key exists."""
501
502
def __iter__(self) -> Iterator[str]:
503
"""Iterate over keys."""
504
505
class MultiDict(ImmutableMultiDict):
506
"""
507
Mutable multi-value dictionary.
508
509
Extends ImmutableMultiDict with methods for modifying content.
510
"""
511
512
def __setitem__(self, key: str, value: Any) -> None:
513
"""Set single value for key (replaces existing)."""
514
515
def __delitem__(self, key: str) -> None:
516
"""Delete all values for key."""
517
518
def setlist(self, key: str, values: List[Any]) -> None:
519
"""Set multiple values for key."""
520
521
def append(self, key: str, value: Any) -> None:
522
"""Append value to existing values for key."""
523
524
def pop(self, key: str, default: Any = None) -> Any:
525
"""Remove and return first value for key."""
526
527
def popitem(self) -> Tuple[str, Any]:
528
"""Remove and return arbitrary key-value pair."""
529
530
def poplist(self, key: str) -> List[Any]:
531
"""Remove and return all values for key."""
532
533
def clear(self) -> None:
534
"""Remove all items."""
535
536
def setdefault(self, key: str, default: Any = None) -> Any:
537
"""Set key to default if not present."""
538
539
def update(self, *args, **kwargs) -> None:
540
"""Update with items from another mapping."""
541
```
542
543
## Utility Data Structures
544
545
### Address Class
546
547
```python { .api }
548
from starlette.datastructures import Address
549
from typing import NamedTuple
550
551
class Address(NamedTuple):
552
"""
553
Network address (host, port) tuple.
554
555
Represents client or server address information.
556
"""
557
558
host: str
559
port: int
560
```
561
562
### Secret Class
563
564
```python { .api }
565
from starlette.datastructures import Secret
566
567
class Secret(str):
568
"""
569
String that hides its value in repr.
570
571
Used for sensitive data like passwords and API keys
572
to prevent accidental exposure in logs or debug output.
573
"""
574
575
def __new__(cls, value: str) -> "Secret":
576
"""Create secret string."""
577
578
def __repr__(self) -> str:
579
"""Hide value in string representation."""
580
return f"{self.__class__.__name__}('**********')"
581
582
def __str__(self) -> str:
583
"""Return actual value when converted to string."""
584
```
585
586
### CommaSeparatedStrings Class
587
588
```python { .api }
589
from starlette.datastructures import CommaSeparatedStrings
590
591
class CommaSeparatedStrings(list[str]):
592
"""
593
Comma-separated string sequence.
594
595
Parses comma-separated strings into a list while
596
maintaining list interface.
597
"""
598
599
def __init__(self, value: Union[str, List[str]]) -> None:
600
"""
601
Initialize from string or sequence.
602
603
Args:
604
value: Comma-separated string or list of strings
605
"""
606
607
def __str__(self) -> str:
608
"""Convert back to comma-separated string."""
609
```
610
611
### State Class
612
613
```python { .api }
614
from starlette.datastructures import State
615
616
class State:
617
"""
618
Arbitrary state container with attribute access.
619
620
Simple container for storing application or request state
621
with convenient attribute-based access.
622
"""
623
624
def __init__(self, state: Dict[str, Any] = None) -> None:
625
"""
626
Initialize state container.
627
628
Args:
629
state: Initial state dictionary
630
"""
631
632
def __setattr__(self, name: str, value: Any) -> None:
633
"""Set state attribute."""
634
635
def __getattr__(self, name: str) -> Any:
636
"""Get state attribute."""
637
638
def __delattr__(self, name: str) -> None:
639
"""Delete state attribute."""
640
```
641
642
## Usage Examples
643
644
### URL Manipulation
645
646
```python { .api }
647
# Creating and manipulating URLs
648
url = URL("https://example.com/path?param=value")
649
650
print(url.scheme) # "https"
651
print(url.hostname) # "example.com"
652
print(url.path) # "/path"
653
print(url.query) # "param=value"
654
print(url.is_secure) # True
655
656
# Creating modified URLs
657
new_url = url.replace(scheme="http", path="/new-path")
658
# "http://example.com/new-path?param=value"
659
660
# Adding query parameters
661
url_with_params = url.include_query_params(new_param="new_value")
662
# "https://example.com/path?param=value&new_param=new_value"
663
664
# Replacing query parameters
665
url_new_params = url.replace_query_params(param="new_value", other="param")
666
# "https://example.com/path?param=new_value&other=param"
667
668
# Removing query parameters
669
url_no_param = url.remove_query_params("param")
670
# "https://example.com/path"
671
```
672
673
### Headers Manipulation
674
675
```python { .api }
676
# Creating headers
677
headers = Headers({
678
"content-type": "application/json",
679
"authorization": "Bearer token123"
680
})
681
682
# Case-insensitive access
683
print(headers["Content-Type"]) # "application/json"
684
print(headers.get("AUTHORIZATION")) # "Bearer token123"
685
686
# Multiple values for same header
687
headers_multi = Headers([
688
("accept", "text/html"),
689
("accept", "application/json"),
690
("accept", "*/*")
691
])
692
693
accept_values = headers_multi.getlist("accept")
694
# ["text/html", "application/json", "*/*"]
695
696
# Mutable headers
697
mutable = headers.mutablecopy()
698
mutable["x-custom"] = "custom-value"
699
mutable.append("cache-control", "no-cache")
700
mutable.add_vary_header("Accept-Encoding")
701
```
702
703
### Query Parameters
704
705
```python { .api }
706
# From query string
707
params = QueryParams("name=john&age=30&tags=python&tags=web")
708
709
print(params["name"]) # "john"
710
print(params.get("age")) # "30"
711
print(params.getlist("tags")) # ["python", "web"]
712
713
# From dictionary
714
params = QueryParams({
715
"search": "starlette",
716
"page": "2",
717
"per_page": "10"
718
})
719
720
# Iterate over parameters
721
for key in params:
722
print(f"{key}: {params[key]}")
723
```
724
725
### Form Data Handling
726
727
```python { .api }
728
async def handle_form(request):
729
form = await request.form()
730
731
# Access form fields
732
name = form.get("name", "")
733
email = form.get("email", "")
734
735
# Handle file uploads
736
avatar = form.get("avatar")
737
if avatar and isinstance(avatar, UploadFile):
738
# Read file content
739
content = await avatar.read()
740
741
# Get file info
742
filename = avatar.filename
743
content_type = avatar.content_type
744
size = avatar.size
745
746
# Save file
747
with open(f"uploads/{filename}", "wb") as f:
748
await avatar.seek(0) # Reset file position
749
while chunk := await avatar.read(1024):
750
f.write(chunk)
751
752
# Clean up
753
await avatar.close()
754
755
# Close form to clean up all files
756
await form.close()
757
758
return JSONResponse({
759
"name": name,
760
"email": email,
761
"file_uploaded": bool(avatar and avatar.filename)
762
})
763
```
764
765
### Multi-Value Dictionary Usage
766
767
```python { .api }
768
# Create multi-dict from form data
769
form_data = MultiDict([
770
("name", "John"),
771
("interests", "python"),
772
("interests", "web"),
773
("interests", "api")
774
])
775
776
# Access values
777
name = form_data["name"] # "John"
778
interests = form_data.getlist("interests") # ["python", "web", "api"]
779
780
# Modify (mutable version)
781
form_data.append("interests", "database")
782
form_data.setlist("skills", ["programming", "design"])
783
784
# All items including duplicates
785
all_items = form_data.multi_items()
786
# [("name", "John"), ("interests", "python"), ("interests", "web"), ...]
787
```
788
789
### Utility Classes Usage
790
791
```python { .api }
792
# Secret values
793
api_key = Secret("sk_live_abc123")
794
print(api_key) # Shows actual value
795
print(repr(api_key)) # Secret('**********')
796
797
# Comma-separated strings
798
allowed_hosts = CommaSeparatedStrings("localhost,127.0.0.1,example.com")
799
print(allowed_hosts) # ["localhost", "127.0.0.1", "example.com"]
800
801
# Application state
802
app.state.database_url = "postgresql://..."
803
app.state.cache = {}
804
app.state.config = {"debug": True}
805
806
# Request state
807
request.state.user_id = 123
808
request.state.start_time = time.time()
809
810
# Network address
811
client_addr = Address("192.168.1.100", 54321)
812
print(client_addr.host) # "192.168.1.100"
813
print(client_addr.port) # 54321
814
```
815
816
Starlette's data structures provide convenient, type-safe handling of web-specific data with immutable defaults, comprehensive APIs, and proper resource management for files and other resources.