fasthttp-client 0.1.0__py3-none-any.whl
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.
- fasthttp/__init__.py +3 -0
- fasthttp/app.py +237 -0
- fasthttp/client.py +90 -0
- fasthttp/logging.py +116 -0
- fasthttp/response.py +71 -0
- fasthttp/routing.py +95 -0
- fasthttp/types.py +55 -0
- fasthttp_client-0.1.0.dist-info/METADATA +179 -0
- fasthttp_client-0.1.0.dist-info/RECORD +11 -0
- fasthttp_client-0.1.0.dist-info/WHEEL +4 -0
- fasthttp_client-0.1.0.dist-info/licenses/LICENSE +21 -0
fasthttp/__init__.py
ADDED
fasthttp/app.py
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import time
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from typing import Annotated, Literal
|
|
5
|
+
|
|
6
|
+
import aiohttp
|
|
7
|
+
from annotated_doc import Doc
|
|
8
|
+
|
|
9
|
+
from fasthttp.client import HTTPClient
|
|
10
|
+
from fasthttp.logging import setup_logger
|
|
11
|
+
from fasthttp.routing import Route
|
|
12
|
+
from fasthttp.types import RequestsOptinal
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FastHTTP:
|
|
16
|
+
"""
|
|
17
|
+
FastHTTP application entry point.
|
|
18
|
+
|
|
19
|
+
FastHTTP is a lightweight asynchronous HTTP client framework
|
|
20
|
+
built on top of aiohttp. It provides a clean decorator-based API
|
|
21
|
+
for defining HTTP requests and handling responses, similar to
|
|
22
|
+
web frameworks like FastAPI, but for outgoing requests.
|
|
23
|
+
|
|
24
|
+
The application manages:
|
|
25
|
+
- Request routing via decorators (GET, POST, PUT, PATCH, DELETE)
|
|
26
|
+
- Per-method default request configuration
|
|
27
|
+
- Async request execution
|
|
28
|
+
- Structured and colorized logging
|
|
29
|
+
- Unified Response handling
|
|
30
|
+
|
|
31
|
+
Example:
|
|
32
|
+
```python
|
|
33
|
+
from fasthttp import FastHTTP
|
|
34
|
+
from fasthttp.response import Response
|
|
35
|
+
|
|
36
|
+
app = FastHTTP()
|
|
37
|
+
|
|
38
|
+
@app.get(url="https://httpbin.org/get")
|
|
39
|
+
async def get_data(resp: Response):
|
|
40
|
+
return resp.json()
|
|
41
|
+
|
|
42
|
+
if __name__ == "__main__":
|
|
43
|
+
app.run()
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
*,
|
|
50
|
+
debug: Annotated[
|
|
51
|
+
bool,
|
|
52
|
+
Doc(
|
|
53
|
+
"""
|
|
54
|
+
Enable debug mode.
|
|
55
|
+
|
|
56
|
+
When enabled, FastHTTP will print datailed
|
|
57
|
+
tracebacks and requests/response logs.
|
|
58
|
+
""")] = False,
|
|
59
|
+
|
|
60
|
+
get_request: Annotated[
|
|
61
|
+
RequestsOptinal,
|
|
62
|
+
Doc(
|
|
63
|
+
"""
|
|
64
|
+
Default configuration for GET requests.
|
|
65
|
+
|
|
66
|
+
Allows setting headers, timeout and other request-level
|
|
67
|
+
options that will be applied to all GET requests
|
|
68
|
+
"""
|
|
69
|
+
)] | None = None,
|
|
70
|
+
post_request: Annotated[
|
|
71
|
+
RequestsOptinal,
|
|
72
|
+
Doc(
|
|
73
|
+
"""
|
|
74
|
+
Default configuration for POST requests.
|
|
75
|
+
|
|
76
|
+
Used to define headers, timeout and other options
|
|
77
|
+
|
|
78
|
+
applied to all POST requests.
|
|
79
|
+
"""
|
|
80
|
+
)] | None = None,
|
|
81
|
+
put_request: Annotated[
|
|
82
|
+
RequestsOptinal,
|
|
83
|
+
Doc(
|
|
84
|
+
"""
|
|
85
|
+
Default configuration for PUT requests.
|
|
86
|
+
|
|
87
|
+
Controls request headers, timeout and other
|
|
88
|
+
|
|
89
|
+
options for PUT requests.
|
|
90
|
+
"""
|
|
91
|
+
)] | None = None,
|
|
92
|
+
patch_request: Annotated[
|
|
93
|
+
RequestsOptinal,
|
|
94
|
+
Doc(
|
|
95
|
+
"""
|
|
96
|
+
# Create the app
|
|
97
|
+
Default configuration for PATCH requests.
|
|
98
|
+
|
|
99
|
+
Used to configure headers, timeout and
|
|
100
|
+
other PATCH-specific options.
|
|
101
|
+
"""
|
|
102
|
+
)] | None = None,
|
|
103
|
+
delete_request: Annotated[
|
|
104
|
+
RequestsOptinal,
|
|
105
|
+
Doc(
|
|
106
|
+
"""
|
|
107
|
+
Default configuration for DELETE requests.
|
|
108
|
+
|
|
109
|
+
Allows defining haders, timeout,
|
|
110
|
+
and other options for DELETE requests.
|
|
111
|
+
"""
|
|
112
|
+
)] | None = None,
|
|
113
|
+
) -> None:
|
|
114
|
+
self.logger = setup_logger(debug=debug)
|
|
115
|
+
self.routes: list[Route] = []
|
|
116
|
+
|
|
117
|
+
self.request_configs = {
|
|
118
|
+
"GET": get_request or {},
|
|
119
|
+
"POST": post_request or {},
|
|
120
|
+
"PUT": put_request or {},
|
|
121
|
+
"PATCH": patch_request or {},
|
|
122
|
+
"DELETE": delete_request or {},
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
self.client = HTTPClient(self.request_configs, self.logger)
|
|
126
|
+
|
|
127
|
+
def _add_route(
|
|
128
|
+
self,
|
|
129
|
+
*,
|
|
130
|
+
method: Literal["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
131
|
+
url: str,
|
|
132
|
+
params=None,
|
|
133
|
+
json=None,
|
|
134
|
+
data=None,
|
|
135
|
+
):
|
|
136
|
+
def decorator(func: Callable):
|
|
137
|
+
self.routes.append(
|
|
138
|
+
Route(
|
|
139
|
+
method=method,
|
|
140
|
+
url=url,
|
|
141
|
+
handler=func,
|
|
142
|
+
params=params,
|
|
143
|
+
json=json,
|
|
144
|
+
data=data,
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
self.logger.debug("Registered route: %s %s", method, url)
|
|
148
|
+
return func
|
|
149
|
+
return decorator
|
|
150
|
+
|
|
151
|
+
def get(self, *, url: str, params=None):
|
|
152
|
+
return self._add_route(
|
|
153
|
+
method="GET",
|
|
154
|
+
url=url,
|
|
155
|
+
params=params,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
def post(self, *, url: str, json=None, data=None):
|
|
159
|
+
return self._add_route(
|
|
160
|
+
method="POST",
|
|
161
|
+
url=url,
|
|
162
|
+
json=json,
|
|
163
|
+
data=data,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
def put(self, *, url: str, json=None, data=None):
|
|
167
|
+
return self._add_route(
|
|
168
|
+
method="PUT",
|
|
169
|
+
url=url,
|
|
170
|
+
json=json,
|
|
171
|
+
data=data,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
def patch(self, *, url: str, json=None, data=None):
|
|
175
|
+
return self._add_route(
|
|
176
|
+
method="PATCH",
|
|
177
|
+
url=url,
|
|
178
|
+
json=json,
|
|
179
|
+
data=data,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
def delete(self, *, url: str, json=None, data=None):
|
|
183
|
+
return self._add_route(
|
|
184
|
+
method="DELETE",
|
|
185
|
+
url=url,
|
|
186
|
+
json=json,
|
|
187
|
+
data=data,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
async def _run(self) -> None:
|
|
191
|
+
total = len(self.routes)
|
|
192
|
+
|
|
193
|
+
self.logger.info("Sending %d requests", total)
|
|
194
|
+
start_all = time.perf_counter()
|
|
195
|
+
|
|
196
|
+
async with aiohttp.ClientSession() as session:
|
|
197
|
+
for route in self.routes:
|
|
198
|
+
start = time.perf_counter()
|
|
199
|
+
|
|
200
|
+
result = await self.client.send(session, route)
|
|
201
|
+
elapsed = (time.perf_counter() - start) * 1000
|
|
202
|
+
|
|
203
|
+
if result and isinstance(result.status, int):
|
|
204
|
+
self.logger.info(
|
|
205
|
+
"✔️ %-6s %-30s %s %6.2fms",
|
|
206
|
+
route.method,
|
|
207
|
+
route.url,
|
|
208
|
+
result.status,
|
|
209
|
+
elapsed,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
handler_result = getattr(result, "_handler_result", None)
|
|
213
|
+
if handler_result is not None:
|
|
214
|
+
self.logger.debug("[RESULT] %s", handler_result)
|
|
215
|
+
elif result.text:
|
|
216
|
+
self.logger.debug("[RESULT] %s", result.text)
|
|
217
|
+
else:
|
|
218
|
+
self.logger.error(
|
|
219
|
+
"✖️ %-6s %-30s ERR %6.2fms",
|
|
220
|
+
route.method,
|
|
221
|
+
route.url,
|
|
222
|
+
elapsed,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
await asyncio.sleep(0.5)
|
|
226
|
+
|
|
227
|
+
total_time = time.perf_counter() - start_all
|
|
228
|
+
self.logger.info("Done in %.2fs", total_time)
|
|
229
|
+
|
|
230
|
+
def run(self) -> None:
|
|
231
|
+
self.logger.info("FastHTTP started")
|
|
232
|
+
try:
|
|
233
|
+
asyncio.run(self._run())
|
|
234
|
+
except aiohttp.ClientConnectionError as e:
|
|
235
|
+
self.logger.error("Connection error: %s", e)
|
|
236
|
+
except KeyboardInterrupt:
|
|
237
|
+
self.logger.warning("Interrupted by user")
|
fasthttp/client.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import time
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import aiohttp
|
|
6
|
+
|
|
7
|
+
from fasthttp.response import Response
|
|
8
|
+
from fasthttp.routing import Route
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class HTTPClient:
|
|
12
|
+
"""
|
|
13
|
+
HTTP client responsible for sending HTTP requests.
|
|
14
|
+
|
|
15
|
+
This class manages low-level request execution using aiohttp,
|
|
16
|
+
applies per-method request configuration (headers, timeout, redirects),
|
|
17
|
+
logs request lifecycle events, and returns normalized Response objects.
|
|
18
|
+
"""
|
|
19
|
+
def __init__(self, request_configs: dict, logger) -> None:
|
|
20
|
+
self.request_configs = request_configs
|
|
21
|
+
self.logger = logger
|
|
22
|
+
|
|
23
|
+
async def send(self, session: aiohttp.ClientSession, route: Route) -> Optional[Response]:
|
|
24
|
+
"""
|
|
25
|
+
Send a single HTTP request based on a Route definition.
|
|
26
|
+
|
|
27
|
+
This method:
|
|
28
|
+
- Applies request configuration based on HTTP method
|
|
29
|
+
- Sends the request using an existing aiohttp ClientSession
|
|
30
|
+
- Measures request execution time
|
|
31
|
+
- Logs outgoing requests and incoming responses
|
|
32
|
+
- Executes the route handler with the Response object
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
- Response instance if the request was successful
|
|
36
|
+
- Modified Response if the handler returned a string or Response
|
|
37
|
+
- None if a connection or timeout error occurred
|
|
38
|
+
"""
|
|
39
|
+
config = self.request_configs.get(route.method, {})
|
|
40
|
+
|
|
41
|
+
self.logger.debug(
|
|
42
|
+
"→ %s %s | headers=%s",
|
|
43
|
+
route.method,
|
|
44
|
+
route.url,
|
|
45
|
+
config.get("headers"),
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
start = time.perf_counter()
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
async with session.request(
|
|
52
|
+
method=route.method,
|
|
53
|
+
url=route.url,
|
|
54
|
+
headers=config.get("headers"),
|
|
55
|
+
params=route.params,
|
|
56
|
+
json=route.json,
|
|
57
|
+
data=route.data,
|
|
58
|
+
timeout=config.get("timeout"),
|
|
59
|
+
) as resp:
|
|
60
|
+
elapsed = (time.perf_counter() - start) * 1000
|
|
61
|
+
text = await resp.text()
|
|
62
|
+
|
|
63
|
+
self.logger.info(
|
|
64
|
+
"← %s %s [%s] %.2fms",
|
|
65
|
+
route.method,
|
|
66
|
+
route.url,
|
|
67
|
+
resp.status,
|
|
68
|
+
elapsed,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
response = Response(
|
|
72
|
+
status=resp.status,
|
|
73
|
+
text=text,
|
|
74
|
+
headers=dict(resp.headers),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
handler_result = await route.handler(response)
|
|
78
|
+
if isinstance(handler_result, Response):
|
|
79
|
+
return handler_result
|
|
80
|
+
if isinstance(handler_result, str):
|
|
81
|
+
response.text = handler_result
|
|
82
|
+
|
|
83
|
+
response._handler_result = handler_result
|
|
84
|
+
return response
|
|
85
|
+
|
|
86
|
+
except (aiohttp.ClientConnectorError, asyncio.TimeoutError):
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
except Exception:
|
|
90
|
+
return None
|
fasthttp/logging.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sys
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from typing import ClassVar
|
|
5
|
+
|
|
6
|
+
LOGGER_NAME = "fasthttp"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ColorFormatter(logging.Formatter):
|
|
10
|
+
"""
|
|
11
|
+
Custom logging formatter with colored output.
|
|
12
|
+
|
|
13
|
+
Adds ANSI colors, icons and improved formatting
|
|
14
|
+
to log records based on their log level.
|
|
15
|
+
|
|
16
|
+
Designed for CLI-friendly and readable logs.
|
|
17
|
+
"""
|
|
18
|
+
RESET = "\033[0m"
|
|
19
|
+
|
|
20
|
+
BOLD = "\033[1m"
|
|
21
|
+
DIM = "\033[2m"
|
|
22
|
+
|
|
23
|
+
GRAY = "\033[90m"
|
|
24
|
+
CYAN = "\033[36m"
|
|
25
|
+
GREEN = "\033[32m"
|
|
26
|
+
YELLOW = "\033[33m"
|
|
27
|
+
BLUE = "\033[34m"
|
|
28
|
+
RED = "\033[31m"
|
|
29
|
+
RED_BG = "\033[41m"
|
|
30
|
+
PURPLE = "\033[35m"
|
|
31
|
+
|
|
32
|
+
LEVEL_COLORS: ClassVar[dict[int, str]] = {
|
|
33
|
+
logging.DEBUG: YELLOW,
|
|
34
|
+
logging.INFO: GREEN,
|
|
35
|
+
logging.WARNING: BLUE,
|
|
36
|
+
logging.ERROR: RED,
|
|
37
|
+
logging.CRITICAL: RED_BG,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
LEVEL_ICONS: ClassVar[dict[int, str]] = {
|
|
41
|
+
logging.DEBUG: "🐛",
|
|
42
|
+
logging.INFO: "✔",
|
|
43
|
+
logging.WARNING: "⚠",
|
|
44
|
+
logging.ERROR: "✖",
|
|
45
|
+
logging.CRITICAL: "💀",
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
def formatTime(self, record, datefmt=None) -> str: # noqa N802
|
|
49
|
+
"""
|
|
50
|
+
Format the timestamp of a log record.
|
|
51
|
+
|
|
52
|
+
Converts the record creation time to UTC
|
|
53
|
+
and formats it with millisecond precision.
|
|
54
|
+
"""
|
|
55
|
+
t = datetime.fromtimestamp(record.created, tz=timezone.utc)
|
|
56
|
+
return f"{self.GRAY}{t.strftime('%H:%M:%S.%f')[:-3]}{self.RESET}"
|
|
57
|
+
|
|
58
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
59
|
+
"""
|
|
60
|
+
Format a log record with colors and icons.
|
|
61
|
+
|
|
62
|
+
Applies:
|
|
63
|
+
- Colored log level names
|
|
64
|
+
- Colored logger names
|
|
65
|
+
- Emoji icons based on log level
|
|
66
|
+
- Special formatting for result messages
|
|
67
|
+
|
|
68
|
+
Returns the fully formatted log string.
|
|
69
|
+
"""
|
|
70
|
+
color = self.LEVEL_COLORS.get(record.levelno, self.RESET)
|
|
71
|
+
icon = self.LEVEL_ICONS.get(record.levelno, "")
|
|
72
|
+
|
|
73
|
+
record.levelname = (
|
|
74
|
+
f"{color}{self.BOLD}{record.levelname:<8}{self.RESET}"
|
|
75
|
+
)
|
|
76
|
+
record.name = f"{self.CYAN}{record.name}{self.RESET}"
|
|
77
|
+
|
|
78
|
+
msg = str(record.msg)
|
|
79
|
+
|
|
80
|
+
if msg.startswith("[RESULT]"):
|
|
81
|
+
record.msg = f"{self.PURPLE}↳ {msg[8:].strip()}{self.RESET}"
|
|
82
|
+
else:
|
|
83
|
+
record.msg = f"{color}{icon} {msg}{self.RESET}"
|
|
84
|
+
|
|
85
|
+
return super().format(record)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def setup_logger(*, debug: bool = False) -> logging.Logger:
|
|
89
|
+
"""
|
|
90
|
+
Configure and return the application logger.
|
|
91
|
+
|
|
92
|
+
Creates a logger with colored output and
|
|
93
|
+
configurable verbosity based on debug mode.
|
|
94
|
+
|
|
95
|
+
If the logger is already configured,
|
|
96
|
+
returns the existing instance.
|
|
97
|
+
"""
|
|
98
|
+
logger = logging.getLogger(LOGGER_NAME)
|
|
99
|
+
|
|
100
|
+
if logger.handlers:
|
|
101
|
+
return logger
|
|
102
|
+
|
|
103
|
+
logger.setLevel(logging.DEBUG)
|
|
104
|
+
|
|
105
|
+
handler = logging.StreamHandler(sys.stdout)
|
|
106
|
+
handler.setLevel(logging.DEBUG if debug else logging.INFO)
|
|
107
|
+
|
|
108
|
+
formatter = ColorFormatter(
|
|
109
|
+
"%(asctime)s │ %(levelname)s │ %(name)s │ %(message)s"
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
handler.setFormatter(formatter)
|
|
113
|
+
logger.addHandler(handler)
|
|
114
|
+
logger.propagate = False
|
|
115
|
+
|
|
116
|
+
return logger
|
fasthttp/response.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Annotated
|
|
3
|
+
|
|
4
|
+
from annotated_doc import Doc
|
|
5
|
+
|
|
6
|
+
from fasthttp.types import JSONResponse
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Response:
|
|
10
|
+
"""
|
|
11
|
+
HTTP response object.
|
|
12
|
+
|
|
13
|
+
Represents an HTTP response returned by the server,
|
|
14
|
+
including status code, raw body text, and response headers.
|
|
15
|
+
|
|
16
|
+
Used by FastHTTP to pass response data to route handlers.
|
|
17
|
+
"""
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
status: Annotated[
|
|
21
|
+
int,
|
|
22
|
+
Doc(
|
|
23
|
+
"""
|
|
24
|
+
HTTP status code of the response.
|
|
25
|
+
|
|
26
|
+
Indicates the result of the HTTP request
|
|
27
|
+
(e.g. 200 for success, 404 for not found,
|
|
28
|
+
500 for server error).
|
|
29
|
+
"""
|
|
30
|
+
)],
|
|
31
|
+
text: Annotated[
|
|
32
|
+
str,
|
|
33
|
+
Doc(
|
|
34
|
+
"""
|
|
35
|
+
Raw response body as a string.
|
|
36
|
+
|
|
37
|
+
Contains the response payload exactly as
|
|
38
|
+
returned by the server.
|
|
39
|
+
"""
|
|
40
|
+
)],
|
|
41
|
+
headers: Annotated[
|
|
42
|
+
dict,
|
|
43
|
+
Doc(
|
|
44
|
+
"""
|
|
45
|
+
HTTP response headers.
|
|
46
|
+
|
|
47
|
+
A mapping of header names to their values
|
|
48
|
+
returned by the server.
|
|
49
|
+
"""
|
|
50
|
+
)]
|
|
51
|
+
) -> None:
|
|
52
|
+
self.status = status
|
|
53
|
+
self.text = text
|
|
54
|
+
self.headers = headers
|
|
55
|
+
self._handler_result = None
|
|
56
|
+
|
|
57
|
+
def json(self) -> JSONResponse.Value:
|
|
58
|
+
"""
|
|
59
|
+
Parse the response body as JSON.
|
|
60
|
+
|
|
61
|
+
Returns the parsed JSON object. Raises a
|
|
62
|
+
ValueError if the response body is not valid JSON.
|
|
63
|
+
"""
|
|
64
|
+
return json.loads(self.text)
|
|
65
|
+
|
|
66
|
+
def __repr__(self) -> str:
|
|
67
|
+
"""
|
|
68
|
+
Return a debug-friendly string representation
|
|
69
|
+
of the response.
|
|
70
|
+
"""
|
|
71
|
+
return f"<Response [{self.status}]>"
|
fasthttp/routing.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from typing import Annotated, Any, Literal
|
|
3
|
+
|
|
4
|
+
from annotated_doc import Doc
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Route:
|
|
8
|
+
"""
|
|
9
|
+
Definition of an HTTP request route.
|
|
10
|
+
|
|
11
|
+
A Route binds together an HTTP method, a target URL,
|
|
12
|
+
optional request data, and a response handler function.
|
|
13
|
+
|
|
14
|
+
It is used by FastHTTP to send requests and process
|
|
15
|
+
responses in a structured and predictable way.
|
|
16
|
+
"""
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
*,
|
|
20
|
+
method: Annotated[
|
|
21
|
+
Literal["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
22
|
+
Doc(
|
|
23
|
+
"""
|
|
24
|
+
HTTP method for the request.
|
|
25
|
+
|
|
26
|
+
Determines how the request will be sent to the server.
|
|
27
|
+
|
|
28
|
+
Suppoeted methods are:
|
|
29
|
+
GET, POST, PUT, PATCH, DELETE.
|
|
30
|
+
"""
|
|
31
|
+
)],
|
|
32
|
+
url: Annotated[
|
|
33
|
+
str,
|
|
34
|
+
Doc(
|
|
35
|
+
"""
|
|
36
|
+
Target URL for the HTTP request.
|
|
37
|
+
|
|
38
|
+
Must be a full URL including scheme, host and path
|
|
39
|
+
Example:
|
|
40
|
+
https://api.google.com/
|
|
41
|
+
"""
|
|
42
|
+
)],
|
|
43
|
+
handler: Annotated[
|
|
44
|
+
Callable,
|
|
45
|
+
Doc(
|
|
46
|
+
"""
|
|
47
|
+
Response handler function.
|
|
48
|
+
|
|
49
|
+
This asyncfunction will be called with a Response object
|
|
50
|
+
and can return:
|
|
51
|
+
- str
|
|
52
|
+
- Response
|
|
53
|
+
- None
|
|
54
|
+
"""
|
|
55
|
+
)],
|
|
56
|
+
params: Annotated[
|
|
57
|
+
dict | None,
|
|
58
|
+
Doc(
|
|
59
|
+
"""
|
|
60
|
+
Query parameters to be sent with the request.
|
|
61
|
+
|
|
62
|
+
The dictionary will be encoded into the URL query string
|
|
63
|
+
and appended to the request URL.
|
|
64
|
+
"""
|
|
65
|
+
)] = None,
|
|
66
|
+
json: Annotated[
|
|
67
|
+
dict | None,
|
|
68
|
+
Doc(
|
|
69
|
+
"""
|
|
70
|
+
JSON body to be sent with the request.
|
|
71
|
+
|
|
72
|
+
The data will be serialized to JSON and sent with the
|
|
73
|
+
|
|
74
|
+
application/json Content-Type header.
|
|
75
|
+
"""
|
|
76
|
+
)] = None,
|
|
77
|
+
data: Annotated[
|
|
78
|
+
Any | None,
|
|
79
|
+
Doc(
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
Raw request body or form data.
|
|
83
|
+
|
|
84
|
+
Can be used to send form-encoded data, plain text,
|
|
85
|
+
|
|
86
|
+
binary payloads or any custom request body.
|
|
87
|
+
"""
|
|
88
|
+
)] = None,
|
|
89
|
+
) -> None:
|
|
90
|
+
self.method = method
|
|
91
|
+
self.url = url
|
|
92
|
+
self.handler = handler
|
|
93
|
+
self.params = params
|
|
94
|
+
self.json = json
|
|
95
|
+
self.data = data
|
fasthttp/types.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from typing import Annotated, TypeAlias, TypedDict
|
|
2
|
+
|
|
3
|
+
from annotated_doc import Doc
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class JSONResponse:
|
|
7
|
+
"""
|
|
8
|
+
JSON response type definitions.
|
|
9
|
+
|
|
10
|
+
This class groups type aliases that describe valid JSON data
|
|
11
|
+
structures returned by HTTP responses.
|
|
12
|
+
|
|
13
|
+
It is used for type-safe annotations of parsed JSON payloads,
|
|
14
|
+
including primitive values, lists, and nested objects.
|
|
15
|
+
"""
|
|
16
|
+
Primutive: TypeAlias = str | int | float | bool | None
|
|
17
|
+
Value: TypeAlias = Primutive | list["Value"] | dict[str, "Value"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class RequestsOptinal(TypedDict, total=False):
|
|
21
|
+
"""
|
|
22
|
+
Optional request configuration.
|
|
23
|
+
|
|
24
|
+
Defines optional parameters that control how HTTP requests
|
|
25
|
+
are sent, such as headers, timeouts, and redirect behavior.
|
|
26
|
+
|
|
27
|
+
All fields are optional and may be omitted if not needed.
|
|
28
|
+
"""
|
|
29
|
+
headers: Annotated[
|
|
30
|
+
dict[str, str],
|
|
31
|
+
Doc(
|
|
32
|
+
"""
|
|
33
|
+
HTTP headers to be sent with the request.
|
|
34
|
+
A dictionary of header names and values that will be included
|
|
35
|
+
in every request of this method type.
|
|
36
|
+
"""
|
|
37
|
+
)]
|
|
38
|
+
timeout: Annotated[
|
|
39
|
+
float,
|
|
40
|
+
Doc(
|
|
41
|
+
"""
|
|
42
|
+
Request timeout in seconds.
|
|
43
|
+
Specifies the maximum amount of time to wait for the server
|
|
44
|
+
to respond before the request is cancelled.
|
|
45
|
+
"""
|
|
46
|
+
)]
|
|
47
|
+
allow_redirects: Annotated[
|
|
48
|
+
bool,
|
|
49
|
+
Doc(
|
|
50
|
+
"""
|
|
51
|
+
Enable or disable HTTP redirects.
|
|
52
|
+
When set to True, the client will automatically follow
|
|
53
|
+
redirect responses (3xx status codes).
|
|
54
|
+
"""
|
|
55
|
+
)]
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fasthttp-client
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Fast and simple HTTP client library with async support and beautiful logging
|
|
5
|
+
Project-URL: Homepage, https://github.com/ndugram/fasthttp
|
|
6
|
+
Project-URL: Documentation, https://github.com/ndugram/fasthttp
|
|
7
|
+
Project-URL: Repository, https://github.com/ndugram/fasthttp
|
|
8
|
+
Project-URL: Issues, https://github.com/ndugram/fasthttp/issues
|
|
9
|
+
Author-email: White NEFOR <n7for8572@gmail.com>
|
|
10
|
+
Maintainer-email: White NEFOR <n7for8572@gmail.com>
|
|
11
|
+
License: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: aiohttp,async,client,fast,http,http-client,python,requests
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Classifier: Topic :: System :: Networking
|
|
26
|
+
Classifier: Typing :: Typed
|
|
27
|
+
Requires-Python: >=3.10
|
|
28
|
+
Requires-Dist: aiohttp>=3.13.3
|
|
29
|
+
Requires-Dist: annotated-doc>=0.0.4
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: mypy>=1.13.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: pre-commit>=4.0.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest-cov>=6.0.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: ruff>=0.14.14; extra == 'dev'
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
|
|
39
|
+
# FastHTTP Client
|
|
40
|
+
|
|
41
|
+
<div align="center">
|
|
42
|
+
|
|
43
|
+

|
|
44
|
+

|
|
45
|
+

|
|
46
|
+
|
|
47
|
+
**🚀 Simple & Fast HTTP Client for Python**
|
|
48
|
+
|
|
49
|
+
[Documentation](docs/index.md) • [Quick Start](#quick-start) • [Examples](docs/examples.md)
|
|
50
|
+
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
## ⚡ Features
|
|
54
|
+
|
|
55
|
+
- **Simple API** - Minimal boilerplate with decorators
|
|
56
|
+
- **Beautiful Logging** - Colorful request/response logs with timing
|
|
57
|
+
- **Async Support** - Built on aiohttp for high performance
|
|
58
|
+
- **Type Safe** - Full type annotations
|
|
59
|
+
- **All HTTP Methods** - GET, POST, PUT, PATCH, DELETE
|
|
60
|
+
|
|
61
|
+
## 🚀 Quick Start
|
|
62
|
+
|
|
63
|
+
### Installation
|
|
64
|
+
```bash
|
|
65
|
+
pip install fasthttp-client
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Basic Usage
|
|
69
|
+
```python
|
|
70
|
+
from fasthttp import FastHTTP
|
|
71
|
+
from fasthttp.response import Response
|
|
72
|
+
|
|
73
|
+
app = FastHTTP()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@app.get(url="https://httpbin.org/get")
|
|
77
|
+
async def get_data(resp: Response):
|
|
78
|
+
return resp.json()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
if __name__ == "__main__":
|
|
82
|
+
app.run()
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Output:**
|
|
86
|
+
```
|
|
87
|
+
16:09:18.955 │ INFO │ fasthttp │ ✔ FastHTTP started
|
|
88
|
+
16:09:19.519 │ INFO │ fasthttp │ ✔ ← GET https://httpbin.org/get [200] 458.26ms
|
|
89
|
+
16:09:20.037 │ INFO │ fasthttp │ ✔ Done in 1.08s
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### With Configuration
|
|
93
|
+
```python
|
|
94
|
+
from fasthttp import FastHTTP
|
|
95
|
+
from fasthttp.response import Response
|
|
96
|
+
|
|
97
|
+
app = FastHTTP(
|
|
98
|
+
debug=True,
|
|
99
|
+
get_request={
|
|
100
|
+
"headers": {"User-Agent": "MyApp/1.0"},
|
|
101
|
+
"timeout": 10,
|
|
102
|
+
},
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
@app.get(url="https://api.github.com/users/octocat")
|
|
106
|
+
async def get_user(resp: Response):
|
|
107
|
+
return resp.json()
|
|
108
|
+
|
|
109
|
+
if __name__ == "__main__":
|
|
110
|
+
app.run()
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## 🔧 All HTTP Methods
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from fasthttp import FastHTTP
|
|
117
|
+
from fasthttp.response import Response
|
|
118
|
+
|
|
119
|
+
app = FastHTTP()
|
|
120
|
+
|
|
121
|
+
# GET
|
|
122
|
+
@app.get(url="https://jsonplaceholder.typicode.com/posts/1")
|
|
123
|
+
async def get_post(resp: Response):
|
|
124
|
+
return resp.json()
|
|
125
|
+
|
|
126
|
+
# POST
|
|
127
|
+
@app.post(url="https://httpbin.org/post", json={"name": "John"})
|
|
128
|
+
async def create_user(resp: Response):
|
|
129
|
+
return f"Created: {resp.status}"
|
|
130
|
+
|
|
131
|
+
# PUT
|
|
132
|
+
@app.put(url="https://httpbin.org/put", json={"name": "Jane"})
|
|
133
|
+
async def update_user(resp: Response):
|
|
134
|
+
return resp.json()
|
|
135
|
+
|
|
136
|
+
# DELETE
|
|
137
|
+
@app.delete(url="https://httpbin.org/delete")
|
|
138
|
+
async def delete_user(resp: Response):
|
|
139
|
+
return f"Delete: {resp.status}"
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## 📚 Documentation
|
|
143
|
+
|
|
144
|
+
- **[📖 Documentation](docs/index.md)** - Complete guide
|
|
145
|
+
- **[⚡ Quick Start](docs/quick-start.md)** - Get started in 2 minutes
|
|
146
|
+
- **[🔧 API Reference](docs/api-reference.md)** - Full API documentation
|
|
147
|
+
- **[💡 Examples](docs/examples.md)** - Real-world examples
|
|
148
|
+
- **[⚙️ Configuration](docs/configuration.md)** - Advanced settings
|
|
149
|
+
|
|
150
|
+
## 🛠️ Development
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
git clone https://github.com/ndugram/fasthttp.git
|
|
154
|
+
cd fasthttp
|
|
155
|
+
pip install -e ".[dev]"
|
|
156
|
+
|
|
157
|
+
# Run tests
|
|
158
|
+
pytest
|
|
159
|
+
|
|
160
|
+
# Code quality
|
|
161
|
+
ruff format .
|
|
162
|
+
ruff check .
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## 📄 License
|
|
166
|
+
|
|
167
|
+
MIT License - see [LICENSE](LICENSE) file for details.
|
|
168
|
+
|
|
169
|
+
## 🤝 Contributing
|
|
170
|
+
|
|
171
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines.
|
|
172
|
+
|
|
173
|
+
## 🔒 Security
|
|
174
|
+
|
|
175
|
+
See [SECURITY.md](SECURITY.md) for security policies.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
**Made with ❤️ by the NDUgram Team**
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
fasthttp/__init__.py,sha256=1Wvp_6J6cxKkwSajw8RTNCexOOp0FcGxW6yf0KJE90s,59
|
|
2
|
+
fasthttp/app.py,sha256=hiD78ceoEqoAyybRZA6lYMpcRrb2F116Q4z_A4uDKSE,6900
|
|
3
|
+
fasthttp/client.py,sha256=4-g_FxujNRR4cUZ4WhB7Wgadz0_RYl_QZCeoT4EIg00,2900
|
|
4
|
+
fasthttp/logging.py,sha256=SUNSSXIR61JDNzduMtM1BIIbDDg1NZUIwfygAlqq-uY,3087
|
|
5
|
+
fasthttp/response.py,sha256=7fueAqAkJoaDDRbW0zRgHBRrxuKfM_TBBAYG8PJ-xrs,1789
|
|
6
|
+
fasthttp/routing.py,sha256=oUGg3Ld2BgllRYcaURdgfaPz9qHZDlYQBeumZnJUBn4,2569
|
|
7
|
+
fasthttp/types.py,sha256=G6przuFoKv2K74kguWk2cznIx2nMC91VyFcvgL3cClk,1623
|
|
8
|
+
fasthttp_client-0.1.0.dist-info/METADATA,sha256=AzchcoK_pL9TVU-Q73RyHyHtyC_yX0HaJqByEQ6cM_w,4805
|
|
9
|
+
fasthttp_client-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
10
|
+
fasthttp_client-0.1.0.dist-info/licenses/LICENSE,sha256=Vz_vj5RNgcljwWVnBRK23jfM9H9I-Pwn0R7SDVxrOiQ,1086
|
|
11
|
+
fasthttp_client-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 安𝐍ᴇғᴏʀ | ᵈᵉᵛ
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|