Skip to content

Commit 8fff438

Browse files
committed
deal with case-insensitive headers
1 parent aa5e52d commit 8fff438

File tree

5 files changed

+52
-24
lines changed

5 files changed

+52
-24
lines changed

ld_eventsource/actions.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import json
2-
from typing import Any, Dict, Optional
2+
from typing import Optional
33

4-
from ld_eventsource.errors import ExceptionWithHeaders
4+
from ld_eventsource.errors import ExceptionWithHeaders, Headers
55

66

77
class Action:
@@ -118,15 +118,15 @@ class Start(Action):
118118
emitted with the headers from the new connection, which may differ from the previous one.
119119
"""
120120

121-
def __init__(self, headers: Optional[Dict[str, Any]] = None):
121+
def __init__(self, headers: Optional[Headers] = None):
122122
self._headers = headers
123123

124124
@property
125-
def headers(self) -> Optional[Dict[str, Any]]:
125+
def headers(self) -> Optional[Headers]:
126126
"""
127127
The HTTP response headers from the stream connection, if available.
128128
129-
The headers dict uses case-insensitive keys (via urllib3's HTTPHeaderDict).
129+
Header name lookups are case-insensitive per RFC 7230.
130130
131131
:return: the response headers, or ``None`` if not available
132132
"""

ld_eventsource/async_http.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import asyncio
22
from logging import Logger
3-
from typing import Any, AsyncIterator, Callable, Dict, Optional, Tuple
3+
from typing import AsyncIterator, Callable, Optional, Tuple
44
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
55

66
import aiohttp
77

8-
from ld_eventsource.errors import HTTPContentTypeError, HTTPStatusError
8+
from ld_eventsource.errors import HTTPContentTypeError, HTTPStatusError, Headers
99

1010
_CHUNK_SIZE = 10000
1111

@@ -64,7 +64,7 @@ async def _get_session(self) -> aiohttp.ClientSession:
6464

6565
async def connect(
6666
self, last_event_id: Optional[str]
67-
) -> Tuple[AsyncIterator[bytes], Callable, Dict[str, Any]]:
67+
) -> Tuple[AsyncIterator[bytes], Callable, Headers]:
6868
url = self.__params.url
6969
if self.__params.query_params is not None:
7070
qp = self.__params.query_params()
@@ -98,7 +98,7 @@ async def connect(
9898
session = await self._get_session()
9999
resp = await session.get(url, **request_options)
100100

101-
response_headers: Dict[str, Any] = dict(resp.headers)
101+
response_headers = resp.headers
102102

103103
if resp.status >= 400 or resp.status == 204:
104104
await resp.release()

ld_eventsource/config/async_connect_strategy.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
from __future__ import annotations
22

33
from logging import Logger
4-
from typing import Any, AsyncIterator, Callable, Dict, Optional
4+
from typing import AsyncIterator, Callable, Optional
5+
6+
from ld_eventsource.errors import Headers
57

68

79
class AsyncConnectStrategy:
@@ -89,7 +91,7 @@ def __init__(
8991
self,
9092
stream: AsyncIterator[bytes],
9193
closer: Optional[Callable],
92-
headers: Optional[Dict[str, Any]] = None,
94+
headers: Optional[Headers] = None,
9395
):
9496
self.__stream = stream
9597
self.__closer = closer
@@ -103,9 +105,11 @@ def stream(self) -> AsyncIterator[bytes]:
103105
return self.__stream
104106

105107
@property
106-
def headers(self) -> Optional[Dict[str, Any]]:
108+
def headers(self) -> Optional[Headers]:
107109
"""
108110
The HTTP response headers, if available.
111+
112+
Header name lookups are case-insensitive per RFC 7230.
109113
"""
110114
return self.__headers
111115

ld_eventsource/config/connect_strategy.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22

33
from dataclasses import dataclass
44
from logging import Logger
5-
from typing import Any, Callable, Dict, Iterator, Optional, Union
5+
from typing import Callable, Iterator, Optional, Union
66

77
from urllib3 import PoolManager
88

9+
from ld_eventsource.errors import Headers
910
from ld_eventsource.http import (DynamicQueryParams, _HttpClientImpl,
1011
_HttpConnectParams)
1112

@@ -96,7 +97,7 @@ class ConnectionResult:
9697
The return type of :meth:`ConnectionClient.connect()`.
9798
"""
9899

99-
def __init__(self, stream: Iterator[bytes], closer: Optional[Callable], headers: Optional[Dict[str, Any]] = None):
100+
def __init__(self, stream: Iterator[bytes], closer: Optional[Callable], headers: Optional[Headers] = None):
100101
self.__stream = stream
101102
self.__closer = closer
102103
self.__headers = headers
@@ -109,14 +110,14 @@ def stream(self) -> Iterator[bytes]:
109110
return self.__stream
110111

111112
@property
112-
def headers(self) -> Optional[Dict[str, Any]]:
113+
def headers(self) -> Optional[Headers]:
113114
"""
114115
The HTTP response headers, if available.
115116
116117
For HTTP connections, this contains the headers from the SSE stream response.
117118
For non-HTTP connections, this will be ``None``.
118119
119-
The headers dict uses case-insensitive keys (via urllib3's HTTPHeaderDict).
120+
Header name lookups are case-insensitive per RFC 7230.
120121
"""
121122
return self.__headers
122123

ld_eventsource/errors.py

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,27 @@
1-
from typing import Any, Dict, Optional, Protocol, runtime_checkable
1+
from typing import Any, Iterator, Optional, Protocol, Tuple, runtime_checkable
2+
3+
4+
class Headers(Protocol):
5+
"""
6+
A case-insensitive mapping of HTTP response headers.
7+
8+
Header name lookups are case-insensitive per RFC 7230, so
9+
``headers.get('content-type')`` and ``headers.get('Content-Type')``
10+
return the same value. The concrete type returned depends on the HTTP
11+
backend in use and should not be relied upon directly.
12+
"""
13+
14+
def get(self, key: str, default: Any = None) -> Any:
15+
"""Return the value for *key* (case-insensitive), or *default*."""
16+
...
17+
18+
def __getitem__(self, key: str) -> Any: ...
19+
20+
def __contains__(self, key: object) -> bool: ...
21+
22+
def __iter__(self) -> Iterator[str]: ...
23+
24+
def items(self) -> Any: ...
225

326

427
@runtime_checkable
@@ -11,7 +34,7 @@ class ExceptionWithHeaders(Protocol):
1134
"""
1235

1336
@property
14-
def headers(self) -> Optional[Dict[str, Any]]:
37+
def headers(self) -> Optional[Headers]:
1538
"""The HTTP response headers associated with this exception."""
1639
raise NotImplementedError
1740

@@ -24,7 +47,7 @@ class HTTPStatusError(Exception):
2447
When available, the response headers are accessible via the :attr:`headers` property.
2548
"""
2649

27-
def __init__(self, status: int, headers: Optional[Dict[str, Any]] = None):
50+
def __init__(self, status: int, headers: Optional[Headers] = None):
2851
super().__init__("HTTP error %d" % status)
2952
self._status = status
3053
self._headers = headers
@@ -34,8 +57,8 @@ def status(self) -> int:
3457
return self._status
3558

3659
@property
37-
def headers(self) -> Optional[Dict[str, Any]]:
38-
"""The HTTP response headers, if available."""
60+
def headers(self) -> Optional[Headers]:
61+
"""The HTTP response headers, if available. Header names are case-insensitive."""
3962
return self._headers
4063

4164

@@ -47,7 +70,7 @@ class HTTPContentTypeError(Exception):
4770
When available, the response headers are accessible via the :attr:`headers` property.
4871
"""
4972

50-
def __init__(self, content_type: str, headers: Optional[Dict[str, Any]] = None):
73+
def __init__(self, content_type: str, headers: Optional[Headers] = None):
5174
super().__init__("invalid content type \"%s\"" % content_type)
5275
self._content_type = content_type
5376
self._headers = headers
@@ -57,6 +80,6 @@ def content_type(self) -> str:
5780
return self._content_type
5881

5982
@property
60-
def headers(self) -> Optional[Dict[str, Any]]:
61-
"""The HTTP response headers, if available."""
83+
def headers(self) -> Optional[Headers]:
84+
"""The HTTP response headers, if available. Header names are case-insensitive."""
6285
return self._headers

0 commit comments

Comments
 (0)