fastapi-ipware 0.1.0__tar.gz → 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: fastapi-ipware
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: FastAPI/Starlette-native wrapper for python-ipware to get client IP addresses
|
|
5
5
|
Keywords: fastapi,starlette,ipware,ip-address,client-ip,proxy,x-forwarded-for
|
|
6
6
|
Author: Michael Bianco
|
|
@@ -15,28 +15,28 @@ Description-Content-Type: text/markdown
|
|
|
15
15
|
|
|
16
16
|
A FastAPI/Starlette-native wrapper for [python-ipware](https://github.com/un33k/python-ipware) that eliminates the need for WSGI-style header conversion.
|
|
17
17
|
|
|
18
|
+
`python-ipware` expects WSGI-style headers (`HTTP_X_FORWARDED_FOR`), but FastAPI uses natural header names (`X-Forwarded-For`). This wrapper handles the conversion automatically so you don't have to.
|
|
19
|
+
|
|
20
|
+
Also, the default precedence order is optimized for modern cloud deployments. See the [default precedence configuration](https://github.com/iloveitaly/fastapi-ipware/blob/main/fastapi_ipware/__init__.py#L48-L58) in the source code. This is different from the
|
|
21
|
+
default ordering ipware which deprioritizes platform-specific headers, which is often the wrong ordering if you are using something like CloudFlare.
|
|
22
|
+
|
|
18
23
|
## Features
|
|
19
24
|
|
|
20
25
|
- **Zero conversion overhead** - Headers converted once at initialization, not on every request
|
|
21
26
|
- **FastAPI-native API** - Works directly with FastAPI/Starlette `Request` objects
|
|
22
27
|
- **Customizable precedence** - Easy to configure header priority for your infrastructure
|
|
23
28
|
- **Proxy validation** - Supports trusted proxy lists and proxy count validation
|
|
24
|
-
- **Thin wrapper** - Leverages all the battle-tested logic from python-ipware
|
|
25
29
|
|
|
26
30
|
## Installation
|
|
27
31
|
|
|
28
|
-
```bash
|
|
29
|
-
pip install fastapi-ipware
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
Or with uv:
|
|
33
|
-
|
|
34
32
|
```bash
|
|
35
33
|
uv add fastapi-ipware
|
|
36
34
|
```
|
|
37
35
|
|
|
38
36
|
## Quick Start
|
|
39
37
|
|
|
38
|
+
[Take a look at this example usage.](https://github.com/iloveitaly/structlog-config/blob/1e32f7afd206485b1356b322596cc19cb0bef8ba/structlog_config/fastapi_access_logger.py#L57-L72)
|
|
39
|
+
|
|
40
40
|
```python
|
|
41
41
|
from fastapi import FastAPI, Request
|
|
42
42
|
from fastapi_ipware import FastAPIIpWare
|
|
@@ -137,43 +137,6 @@ ipware = FastAPIIpWare(
|
|
|
137
137
|
)
|
|
138
138
|
```
|
|
139
139
|
|
|
140
|
-
## Real-World Examples
|
|
141
|
-
|
|
142
|
-
### AWS Application Load Balancer
|
|
143
|
-
|
|
144
|
-
```python
|
|
145
|
-
ipware = FastAPIIpWare(
|
|
146
|
-
proxy_count=1,
|
|
147
|
-
proxy_list=["10.0."] # Your VPC CIDR
|
|
148
|
-
)
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
### Cloudflare
|
|
152
|
-
|
|
153
|
-
```python
|
|
154
|
-
ipware = FastAPIIpWare(
|
|
155
|
-
precedence=("CF-Connecting-IP",)
|
|
156
|
-
)
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
### Multiple Proxies (CDN + Load Balancer)
|
|
160
|
-
|
|
161
|
-
```python
|
|
162
|
-
ipware = FastAPIIpWare(
|
|
163
|
-
proxy_count=2,
|
|
164
|
-
proxy_list=["10.1.", "10.2."] # CDN and LB IPs
|
|
165
|
-
)
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
### NGINX Reverse Proxy
|
|
169
|
-
|
|
170
|
-
```python
|
|
171
|
-
ipware = FastAPIIpWare(
|
|
172
|
-
precedence=("X-Real-IP", "X-Forwarded-For"),
|
|
173
|
-
proxy_count=1
|
|
174
|
-
)
|
|
175
|
-
```
|
|
176
|
-
|
|
177
140
|
## IP Address Types
|
|
178
141
|
|
|
179
142
|
The returned IP address object has useful properties:
|
|
@@ -189,26 +152,16 @@ if ip:
|
|
|
189
152
|
```
|
|
190
153
|
|
|
191
154
|
python-ipware automatically prefers:
|
|
155
|
+
|
|
192
156
|
1. Public (global) IPs first
|
|
193
157
|
2. Private IPs second
|
|
194
158
|
3. Loopback IPs last
|
|
195
159
|
|
|
196
|
-
## Default Header Precedence
|
|
197
|
-
|
|
198
|
-
The default precedence order is optimized for modern cloud deployments. See the [default precedence configuration](https://github.com/iloveitaly/fastapi-ipware/blob/main/fastapi_ipware/__init__.py#L48-L58) in the source code.
|
|
199
|
-
|
|
200
|
-
## Why fastapi-ipware?
|
|
201
|
-
|
|
202
|
-
`python-ipware` expects WSGI-style headers (`HTTP_X_FORWARDED_FOR`), but FastAPI uses natural header names (`X-Forwarded-For`). This wrapper handles the conversion automatically so you don't have to.
|
|
203
|
-
|
|
204
|
-
## Contributing
|
|
205
|
-
|
|
206
|
-
Contributions welcome! This is a thin wrapper around python-ipware, so most IP detection logic lives there.
|
|
207
|
-
|
|
208
160
|
## License
|
|
209
161
|
|
|
210
162
|
[MIT License](LICENSE.md)
|
|
211
163
|
|
|
212
164
|
## Credits
|
|
213
165
|
|
|
214
|
-
Built on top of [python-ipware](https://github.com/un33k/python-ipware) by un33k.
|
|
166
|
+
- Built on top of [python-ipware](https://github.com/un33k/python-ipware) by un33k.
|
|
167
|
+
- https://github.com/long2ice/fastapi-limiter/blob/8d179c058fa2aaf98f3450c9026a7300ae2b3bdd/fastapi_limiter/__init__.py#L11
|
|
@@ -2,28 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
A FastAPI/Starlette-native wrapper for [python-ipware](https://github.com/un33k/python-ipware) that eliminates the need for WSGI-style header conversion.
|
|
4
4
|
|
|
5
|
+
`python-ipware` expects WSGI-style headers (`HTTP_X_FORWARDED_FOR`), but FastAPI uses natural header names (`X-Forwarded-For`). This wrapper handles the conversion automatically so you don't have to.
|
|
6
|
+
|
|
7
|
+
Also, the default precedence order is optimized for modern cloud deployments. See the [default precedence configuration](https://github.com/iloveitaly/fastapi-ipware/blob/main/fastapi_ipware/__init__.py#L48-L58) in the source code. This is different from the
|
|
8
|
+
default ordering ipware which deprioritizes platform-specific headers, which is often the wrong ordering if you are using something like CloudFlare.
|
|
9
|
+
|
|
5
10
|
## Features
|
|
6
11
|
|
|
7
12
|
- **Zero conversion overhead** - Headers converted once at initialization, not on every request
|
|
8
13
|
- **FastAPI-native API** - Works directly with FastAPI/Starlette `Request` objects
|
|
9
14
|
- **Customizable precedence** - Easy to configure header priority for your infrastructure
|
|
10
15
|
- **Proxy validation** - Supports trusted proxy lists and proxy count validation
|
|
11
|
-
- **Thin wrapper** - Leverages all the battle-tested logic from python-ipware
|
|
12
16
|
|
|
13
17
|
## Installation
|
|
14
18
|
|
|
15
|
-
```bash
|
|
16
|
-
pip install fastapi-ipware
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
Or with uv:
|
|
20
|
-
|
|
21
19
|
```bash
|
|
22
20
|
uv add fastapi-ipware
|
|
23
21
|
```
|
|
24
22
|
|
|
25
23
|
## Quick Start
|
|
26
24
|
|
|
25
|
+
[Take a look at this example usage.](https://github.com/iloveitaly/structlog-config/blob/1e32f7afd206485b1356b322596cc19cb0bef8ba/structlog_config/fastapi_access_logger.py#L57-L72)
|
|
26
|
+
|
|
27
27
|
```python
|
|
28
28
|
from fastapi import FastAPI, Request
|
|
29
29
|
from fastapi_ipware import FastAPIIpWare
|
|
@@ -124,43 +124,6 @@ ipware = FastAPIIpWare(
|
|
|
124
124
|
)
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
-
## Real-World Examples
|
|
128
|
-
|
|
129
|
-
### AWS Application Load Balancer
|
|
130
|
-
|
|
131
|
-
```python
|
|
132
|
-
ipware = FastAPIIpWare(
|
|
133
|
-
proxy_count=1,
|
|
134
|
-
proxy_list=["10.0."] # Your VPC CIDR
|
|
135
|
-
)
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
### Cloudflare
|
|
139
|
-
|
|
140
|
-
```python
|
|
141
|
-
ipware = FastAPIIpWare(
|
|
142
|
-
precedence=("CF-Connecting-IP",)
|
|
143
|
-
)
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
### Multiple Proxies (CDN + Load Balancer)
|
|
147
|
-
|
|
148
|
-
```python
|
|
149
|
-
ipware = FastAPIIpWare(
|
|
150
|
-
proxy_count=2,
|
|
151
|
-
proxy_list=["10.1.", "10.2."] # CDN and LB IPs
|
|
152
|
-
)
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
### NGINX Reverse Proxy
|
|
156
|
-
|
|
157
|
-
```python
|
|
158
|
-
ipware = FastAPIIpWare(
|
|
159
|
-
precedence=("X-Real-IP", "X-Forwarded-For"),
|
|
160
|
-
proxy_count=1
|
|
161
|
-
)
|
|
162
|
-
```
|
|
163
|
-
|
|
164
127
|
## IP Address Types
|
|
165
128
|
|
|
166
129
|
The returned IP address object has useful properties:
|
|
@@ -176,26 +139,16 @@ if ip:
|
|
|
176
139
|
```
|
|
177
140
|
|
|
178
141
|
python-ipware automatically prefers:
|
|
142
|
+
|
|
179
143
|
1. Public (global) IPs first
|
|
180
144
|
2. Private IPs second
|
|
181
145
|
3. Loopback IPs last
|
|
182
146
|
|
|
183
|
-
## Default Header Precedence
|
|
184
|
-
|
|
185
|
-
The default precedence order is optimized for modern cloud deployments. See the [default precedence configuration](https://github.com/iloveitaly/fastapi-ipware/blob/main/fastapi_ipware/__init__.py#L48-L58) in the source code.
|
|
186
|
-
|
|
187
|
-
## Why fastapi-ipware?
|
|
188
|
-
|
|
189
|
-
`python-ipware` expects WSGI-style headers (`HTTP_X_FORWARDED_FOR`), but FastAPI uses natural header names (`X-Forwarded-For`). This wrapper handles the conversion automatically so you don't have to.
|
|
190
|
-
|
|
191
|
-
## Contributing
|
|
192
|
-
|
|
193
|
-
Contributions welcome! This is a thin wrapper around python-ipware, so most IP detection logic lives there.
|
|
194
|
-
|
|
195
147
|
## License
|
|
196
148
|
|
|
197
149
|
[MIT License](LICENSE.md)
|
|
198
150
|
|
|
199
151
|
## Credits
|
|
200
152
|
|
|
201
|
-
Built on top of [python-ipware](https://github.com/un33k/python-ipware) by un33k.
|
|
153
|
+
- Built on top of [python-ipware](https://github.com/un33k/python-ipware) by un33k.
|
|
154
|
+
- https://github.com/long2ice/fastapi-limiter/blob/8d179c058fa2aaf98f3450c9026a7300ae2b3bdd/fastapi_limiter/__init__.py#L11
|
|
@@ -44,28 +44,46 @@ class FastAPIIpWare(IpWare):
|
|
|
44
44
|
Used to validate and extract the correct client IP.
|
|
45
45
|
proxy_list: List of trusted proxy IP prefixes (e.g., ["10.1.", "10.2.3"]).
|
|
46
46
|
"""
|
|
47
|
-
#
|
|
47
|
+
# Header precedence order: Provider-specific headers before generic ones
|
|
48
|
+
#
|
|
49
|
+
# We prioritize provider-specific headers (CF-Connecting-IP, True-Client-IP, etc.)
|
|
50
|
+
# over generic headers (X-Forwarded-For, X-Real-IP) because:
|
|
51
|
+
# 1. Provider headers are set by trusted CDN/proxy infrastructure
|
|
52
|
+
# 2. They cannot be spoofed by clients
|
|
53
|
+
# 3. They represent the most reliable source of client IP information
|
|
54
|
+
#
|
|
55
|
+
# Generic headers like X-Forwarded-For can be set by anyone and are easier
|
|
56
|
+
# to manipulate, so they should only be used as fallbacks.
|
|
48
57
|
if precedence is None:
|
|
49
58
|
precedence = (
|
|
50
|
-
|
|
51
|
-
"X-Real-IP", # NGINX
|
|
59
|
+
# Provider-specific headers (highest reliability)
|
|
52
60
|
"CF-Connecting-IP", # Cloudflare
|
|
53
61
|
"True-Client-IP", # Cloudflare Enterprise
|
|
54
62
|
"Fastly-Client-IP", # Fastly, Firebase
|
|
55
63
|
"X-Client-IP", # Microsoft Azure
|
|
56
64
|
"X-Cluster-Client-IP", # Rackspace Cloud Load Balancers
|
|
57
|
-
|
|
58
|
-
"Forwarded", #
|
|
65
|
+
# Generic headers (fallback)
|
|
66
|
+
"X-Forwarded-For", # Generic, used by AWS ELB, nginx, etc.
|
|
67
|
+
"X-Real-IP", # NGINX
|
|
68
|
+
# NOTE: Upstream python-ipware treats Forwarded headers as plain IP lists.
|
|
69
|
+
# RFC 7239 `Forwarded` parameters (for=) are not parsed.
|
|
70
|
+
"Forwarded-For", # RFC 7239 (plain IP list only)
|
|
71
|
+
"Forwarded", # RFC 7239 (plain IP list only)
|
|
59
72
|
"Client-IP", # Akamai, Cloudflare
|
|
73
|
+
"REMOTE_ADDR", # Direct connection fallback
|
|
60
74
|
)
|
|
61
75
|
|
|
62
76
|
# Store FastAPI-style precedence for reference
|
|
63
77
|
self._fastapi_precedence = precedence
|
|
64
78
|
|
|
65
|
-
# Convert user-friendly header names (with dashes) to WSGI format once
|
|
66
|
-
#
|
|
79
|
+
# Convert user-friendly header names (with dashes) to WSGI format once.
|
|
80
|
+
# REMOTE_ADDR is a WSGI/ASGI convention, not an HTTP header, so it must
|
|
81
|
+
# not be prefixed with HTTP_.
|
|
67
82
|
wsgi_precedence = tuple(
|
|
68
|
-
|
|
83
|
+
"REMOTE_ADDR"
|
|
84
|
+
if header == "REMOTE_ADDR"
|
|
85
|
+
else f"HTTP_{header.upper().replace('-', '_')}"
|
|
86
|
+
for header in precedence
|
|
69
87
|
)
|
|
70
88
|
|
|
71
89
|
# Initialize parent class with WSGI-style headers
|
|
@@ -104,6 +122,11 @@ class FastAPIIpWare(IpWare):
|
|
|
104
122
|
for name, value in request.headers.items()
|
|
105
123
|
}
|
|
106
124
|
|
|
125
|
+
if request.client:
|
|
126
|
+
# NOTE: python-ipware falls back to REMOTE_ADDR when no headers match.
|
|
127
|
+
# Map Starlette's connection info to that expected key.
|
|
128
|
+
meta["REMOTE_ADDR"] = request.client.host
|
|
129
|
+
|
|
107
130
|
return self.get_client_ip(meta, strict=strict)
|
|
108
131
|
|
|
109
132
|
|
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "fastapi-ipware"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.2"
|
|
4
4
|
description = "FastAPI/Starlette-native wrapper for python-ipware to get client IP addresses"
|
|
5
|
-
keywords = [
|
|
5
|
+
keywords = [
|
|
6
|
+
"fastapi",
|
|
7
|
+
"starlette",
|
|
8
|
+
"ipware",
|
|
9
|
+
"ip-address",
|
|
10
|
+
"client-ip",
|
|
11
|
+
"proxy",
|
|
12
|
+
"x-forwarded-for",
|
|
13
|
+
]
|
|
6
14
|
readme = "README.md"
|
|
7
15
|
requires-python = ">=3.10"
|
|
8
|
-
dependencies = [
|
|
9
|
-
"python-ipware>=3.0.0",
|
|
10
|
-
"starlette>=0.49.1",
|
|
11
|
-
]
|
|
16
|
+
dependencies = ["python-ipware>=3.0.0", "starlette>=0.49.1"]
|
|
12
17
|
authors = [{ name = "Michael Bianco", email = "mike@mikebian.co" }]
|
|
13
18
|
urls = { "Repository" = "https://github.com/iloveitaly/fastapi-ipware" }
|
|
14
19
|
|
|
15
20
|
# additional packaging information: https://packaging.python.org/en/latest/specifications/core-metadata/#license
|
|
16
21
|
|
|
17
22
|
[build-system]
|
|
18
|
-
requires = ["uv_build>=0.
|
|
23
|
+
requires = ["uv_build>=0.9.0,<0.10.0"]
|
|
19
24
|
build-backend = "uv_build"
|
|
20
25
|
|
|
21
26
|
[tool.uv.build-backend]
|
|
@@ -23,8 +28,16 @@ build-backend = "uv_build"
|
|
|
23
28
|
module-root = ""
|
|
24
29
|
|
|
25
30
|
[dependency-groups]
|
|
26
|
-
dev = [
|
|
31
|
+
dev = [
|
|
32
|
+
"pytest>=8.3.3",
|
|
33
|
+
"pyright[nodejs]>=1.1.380",
|
|
34
|
+
"ruff>=0.8.0",
|
|
35
|
+
"fastapi>=0.128.0",
|
|
36
|
+
"httpx>=0.28.1",
|
|
37
|
+
]
|
|
27
38
|
|
|
28
39
|
[tool.pyright]
|
|
29
|
-
exclude = ["
|
|
40
|
+
exclude = ["examples/", "playground/", "tmp/", ".venv/", "tests/"]
|
|
30
41
|
|
|
42
|
+
[tool.ruff]
|
|
43
|
+
extend-exclude = ["playground.py", "playground/"]
|