hawkapi 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.
- hawkapi/__init__.py +191 -0
- hawkapi/_compat/__init__.py +0 -0
- hawkapi/_compat/pydantic_adapter.py +41 -0
- hawkapi/_constants.py +39 -0
- hawkapi/_types.py +19 -0
- hawkapi/app.py +671 -0
- hawkapi/background.py +41 -0
- hawkapi/cli.py +64 -0
- hawkapi/config/__init__.py +3 -0
- hawkapi/config/env.py +36 -0
- hawkapi/config/profiles.py +41 -0
- hawkapi/config/settings.py +130 -0
- hawkapi/di/__init__.py +6 -0
- hawkapi/di/container.py +155 -0
- hawkapi/di/depends.py +42 -0
- hawkapi/di/provider.py +65 -0
- hawkapi/di/resolver.py +304 -0
- hawkapi/di/scope.py +79 -0
- hawkapi/exceptions.py +68 -0
- hawkapi/lifespan/__init__.py +4 -0
- hawkapi/lifespan/hooks.py +66 -0
- hawkapi/lifespan/manager.py +131 -0
- hawkapi/middleware/__init__.py +23 -0
- hawkapi/middleware/_pipeline.py +30 -0
- hawkapi/middleware/base.py +115 -0
- hawkapi/middleware/cors.py +169 -0
- hawkapi/middleware/error_handler.py +62 -0
- hawkapi/middleware/gzip.py +238 -0
- hawkapi/middleware/https_redirect.py +48 -0
- hawkapi/middleware/rate_limit.py +113 -0
- hawkapi/middleware/request_id.py +58 -0
- hawkapi/middleware/security_headers.py +68 -0
- hawkapi/middleware/timing.py +34 -0
- hawkapi/middleware/trusted_host.py +43 -0
- hawkapi/observability/__init__.py +6 -0
- hawkapi/observability/config.py +34 -0
- hawkapi/observability/logger.py +48 -0
- hawkapi/observability/metrics.py +34 -0
- hawkapi/observability/middleware.py +124 -0
- hawkapi/observability/tracing.py +40 -0
- hawkapi/openapi/__init__.py +10 -0
- hawkapi/openapi/breaking_changes.py +216 -0
- hawkapi/openapi/inspector.py +210 -0
- hawkapi/openapi/models.py +136 -0
- hawkapi/openapi/schema.py +324 -0
- hawkapi/openapi/ui.py +66 -0
- hawkapi/py.typed +0 -0
- hawkapi/requests/__init__.py +7 -0
- hawkapi/requests/form_data.py +162 -0
- hawkapi/requests/headers.py +51 -0
- hawkapi/requests/query_params.py +50 -0
- hawkapi/requests/request.py +207 -0
- hawkapi/requests/state.py +36 -0
- hawkapi/responses/__init__.py +20 -0
- hawkapi/responses/file_response.py +107 -0
- hawkapi/responses/html_response.py +24 -0
- hawkapi/responses/json_response.py +50 -0
- hawkapi/responses/plain_text.py +28 -0
- hawkapi/responses/redirect.py +24 -0
- hawkapi/responses/response.py +64 -0
- hawkapi/responses/sse.py +110 -0
- hawkapi/responses/streaming.py +77 -0
- hawkapi/routing/__init__.py +16 -0
- hawkapi/routing/_radix_tree.py +189 -0
- hawkapi/routing/controllers.py +247 -0
- hawkapi/routing/param_converters.py +21 -0
- hawkapi/routing/route.py +27 -0
- hawkapi/routing/router.py +372 -0
- hawkapi/routing/version_router.py +50 -0
- hawkapi/security/__init__.py +19 -0
- hawkapi/security/api_key.py +104 -0
- hawkapi/security/base.py +29 -0
- hawkapi/security/http_basic.py +69 -0
- hawkapi/security/http_bearer.py +55 -0
- hawkapi/security/oauth2.py +56 -0
- hawkapi/security/permissions.py +69 -0
- hawkapi/serialization/__init__.py +4 -0
- hawkapi/serialization/encoder.py +39 -0
- hawkapi/serialization/negotiation.py +62 -0
- hawkapi/staticfiles.py +186 -0
- hawkapi/testing/__init__.py +4 -0
- hawkapi/testing/client.py +277 -0
- hawkapi/testing/overrides.py +40 -0
- hawkapi/testing/plugin.py +23 -0
- hawkapi/validation/__init__.py +17 -0
- hawkapi/validation/constraints.py +54 -0
- hawkapi/validation/decoder.py +47 -0
- hawkapi/validation/errors.py +77 -0
- hawkapi/websocket/__init__.py +3 -0
- hawkapi/websocket/connection.py +149 -0
- hawkapi-0.1.0.dist-info/METADATA +975 -0
- hawkapi-0.1.0.dist-info/RECORD +95 -0
- hawkapi-0.1.0.dist-info/WHEEL +4 -0
- hawkapi-0.1.0.dist-info/entry_points.txt +2 -0
- hawkapi-0.1.0.dist-info/licenses/LICENSE +21 -0
hawkapi/__init__.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""HawkAPI — High-performance Python web framework."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
# Eager imports — core types used in every application
|
|
8
|
+
from hawkapi.app import HawkAPI
|
|
9
|
+
from hawkapi.di import Container, Depends
|
|
10
|
+
from hawkapi.exceptions import HTTPException
|
|
11
|
+
from hawkapi.requests import Request
|
|
12
|
+
from hawkapi.responses import JSONResponse, Response
|
|
13
|
+
from hawkapi.routing import Route, Router
|
|
14
|
+
|
|
15
|
+
# TYPE_CHECKING-only imports so pyright sees the symbols
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from hawkapi.background import BackgroundTasks
|
|
18
|
+
from hawkapi.config import Settings, env_field
|
|
19
|
+
from hawkapi.middleware import Middleware
|
|
20
|
+
from hawkapi.observability import ObservabilityConfig, ObservabilityMiddleware
|
|
21
|
+
from hawkapi.openapi import (
|
|
22
|
+
Change,
|
|
23
|
+
ChangeType,
|
|
24
|
+
Severity,
|
|
25
|
+
detect_breaking_changes,
|
|
26
|
+
generate_openapi,
|
|
27
|
+
)
|
|
28
|
+
from hawkapi.responses import (
|
|
29
|
+
EventSourceResponse,
|
|
30
|
+
FileResponse,
|
|
31
|
+
HTMLResponse,
|
|
32
|
+
PlainTextResponse,
|
|
33
|
+
RedirectResponse,
|
|
34
|
+
ServerSentEvent,
|
|
35
|
+
StreamingResponse,
|
|
36
|
+
)
|
|
37
|
+
from hawkapi.routing import Controller, VersionRouter
|
|
38
|
+
from hawkapi.routing.controllers import delete, get, patch, post, put
|
|
39
|
+
from hawkapi.security import (
|
|
40
|
+
APIKeyCookie,
|
|
41
|
+
APIKeyHeader,
|
|
42
|
+
APIKeyQuery,
|
|
43
|
+
HTTPBasic,
|
|
44
|
+
HTTPBasicCredentials,
|
|
45
|
+
HTTPBearer,
|
|
46
|
+
HTTPBearerCredentials,
|
|
47
|
+
OAuth2PasswordBearer,
|
|
48
|
+
PermissionPolicy,
|
|
49
|
+
SecurityScheme,
|
|
50
|
+
)
|
|
51
|
+
from hawkapi.staticfiles import StaticFiles
|
|
52
|
+
from hawkapi.testing import TestClient, TestResponse, override
|
|
53
|
+
from hawkapi.validation.constraints import Body, Cookie, Header, Path, Query
|
|
54
|
+
from hawkapi.websocket import WebSocket, WebSocketDisconnect
|
|
55
|
+
|
|
56
|
+
__version__ = "0.1.0"
|
|
57
|
+
|
|
58
|
+
# Lazy imports — loaded on first access for faster cold start
|
|
59
|
+
_LAZY_IMPORTS: dict[str, tuple[str, str]] = {
|
|
60
|
+
# background
|
|
61
|
+
"BackgroundTasks": ("hawkapi.background", "BackgroundTasks"),
|
|
62
|
+
# config
|
|
63
|
+
"Settings": ("hawkapi.config", "Settings"),
|
|
64
|
+
"env_field": ("hawkapi.config", "env_field"),
|
|
65
|
+
# middleware
|
|
66
|
+
"Middleware": ("hawkapi.middleware", "Middleware"),
|
|
67
|
+
# openapi
|
|
68
|
+
"generate_openapi": ("hawkapi.openapi", "generate_openapi"),
|
|
69
|
+
"detect_breaking_changes": ("hawkapi.openapi", "detect_breaking_changes"),
|
|
70
|
+
"Change": ("hawkapi.openapi", "Change"),
|
|
71
|
+
"ChangeType": ("hawkapi.openapi", "ChangeType"),
|
|
72
|
+
"Severity": ("hawkapi.openapi", "Severity"),
|
|
73
|
+
# responses
|
|
74
|
+
"EventSourceResponse": ("hawkapi.responses", "EventSourceResponse"),
|
|
75
|
+
"FileResponse": ("hawkapi.responses", "FileResponse"),
|
|
76
|
+
"HTMLResponse": ("hawkapi.responses", "HTMLResponse"),
|
|
77
|
+
"PlainTextResponse": ("hawkapi.responses", "PlainTextResponse"),
|
|
78
|
+
"RedirectResponse": ("hawkapi.responses", "RedirectResponse"),
|
|
79
|
+
"ServerSentEvent": ("hawkapi.responses", "ServerSentEvent"),
|
|
80
|
+
"StreamingResponse": ("hawkapi.responses", "StreamingResponse"),
|
|
81
|
+
# routing
|
|
82
|
+
"Controller": ("hawkapi.routing", "Controller"),
|
|
83
|
+
"VersionRouter": ("hawkapi.routing", "VersionRouter"),
|
|
84
|
+
"get": ("hawkapi.routing.controllers", "get"),
|
|
85
|
+
"post": ("hawkapi.routing.controllers", "post"),
|
|
86
|
+
"put": ("hawkapi.routing.controllers", "put"),
|
|
87
|
+
"patch": ("hawkapi.routing.controllers", "patch"),
|
|
88
|
+
"delete": ("hawkapi.routing.controllers", "delete"),
|
|
89
|
+
# security
|
|
90
|
+
"APIKeyCookie": ("hawkapi.security", "APIKeyCookie"),
|
|
91
|
+
"APIKeyHeader": ("hawkapi.security", "APIKeyHeader"),
|
|
92
|
+
"APIKeyQuery": ("hawkapi.security", "APIKeyQuery"),
|
|
93
|
+
"HTTPBasic": ("hawkapi.security", "HTTPBasic"),
|
|
94
|
+
"HTTPBasicCredentials": ("hawkapi.security", "HTTPBasicCredentials"),
|
|
95
|
+
"HTTPBearer": ("hawkapi.security", "HTTPBearer"),
|
|
96
|
+
"HTTPBearerCredentials": ("hawkapi.security", "HTTPBearerCredentials"),
|
|
97
|
+
"OAuth2PasswordBearer": ("hawkapi.security", "OAuth2PasswordBearer"),
|
|
98
|
+
"PermissionPolicy": ("hawkapi.security", "PermissionPolicy"),
|
|
99
|
+
"SecurityScheme": ("hawkapi.security", "SecurityScheme"),
|
|
100
|
+
# staticfiles
|
|
101
|
+
"StaticFiles": ("hawkapi.staticfiles", "StaticFiles"),
|
|
102
|
+
# testing
|
|
103
|
+
"TestClient": ("hawkapi.testing", "TestClient"),
|
|
104
|
+
"TestResponse": ("hawkapi.testing", "TestResponse"),
|
|
105
|
+
"override": ("hawkapi.testing", "override"),
|
|
106
|
+
# validation
|
|
107
|
+
"Body": ("hawkapi.validation.constraints", "Body"),
|
|
108
|
+
"Cookie": ("hawkapi.validation.constraints", "Cookie"),
|
|
109
|
+
"Header": ("hawkapi.validation.constraints", "Header"),
|
|
110
|
+
"Path": ("hawkapi.validation.constraints", "Path"),
|
|
111
|
+
"Query": ("hawkapi.validation.constraints", "Query"),
|
|
112
|
+
# websocket
|
|
113
|
+
"WebSocket": ("hawkapi.websocket", "WebSocket"),
|
|
114
|
+
"WebSocketDisconnect": ("hawkapi.websocket", "WebSocketDisconnect"),
|
|
115
|
+
# observability
|
|
116
|
+
"ObservabilityConfig": ("hawkapi.observability", "ObservabilityConfig"),
|
|
117
|
+
"ObservabilityMiddleware": ("hawkapi.observability", "ObservabilityMiddleware"),
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def __getattr__(name: str) -> Any:
|
|
122
|
+
if name in _LAZY_IMPORTS:
|
|
123
|
+
module_path, attr_name = _LAZY_IMPORTS[name]
|
|
124
|
+
import importlib
|
|
125
|
+
|
|
126
|
+
module = importlib.import_module(module_path)
|
|
127
|
+
value: Any = getattr(module, attr_name)
|
|
128
|
+
# Cache in module namespace for subsequent access
|
|
129
|
+
globals()[name] = value
|
|
130
|
+
return value
|
|
131
|
+
msg = f"module 'hawkapi' has no attribute {name!r}"
|
|
132
|
+
raise AttributeError(msg)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
__all__ = [
|
|
136
|
+
"APIKeyCookie",
|
|
137
|
+
"APIKeyHeader",
|
|
138
|
+
"APIKeyQuery",
|
|
139
|
+
"BackgroundTasks",
|
|
140
|
+
"Body",
|
|
141
|
+
"Change",
|
|
142
|
+
"ChangeType",
|
|
143
|
+
"Container",
|
|
144
|
+
"Controller",
|
|
145
|
+
"Cookie",
|
|
146
|
+
"Depends",
|
|
147
|
+
"EventSourceResponse",
|
|
148
|
+
"FileResponse",
|
|
149
|
+
"HTMLResponse",
|
|
150
|
+
"HTTPBasic",
|
|
151
|
+
"HTTPBasicCredentials",
|
|
152
|
+
"HTTPBearer",
|
|
153
|
+
"HTTPBearerCredentials",
|
|
154
|
+
"HTTPException",
|
|
155
|
+
"HawkAPI",
|
|
156
|
+
"Header",
|
|
157
|
+
"JSONResponse",
|
|
158
|
+
"Middleware",
|
|
159
|
+
"OAuth2PasswordBearer",
|
|
160
|
+
"ObservabilityConfig",
|
|
161
|
+
"ObservabilityMiddleware",
|
|
162
|
+
"Path",
|
|
163
|
+
"PermissionPolicy",
|
|
164
|
+
"PlainTextResponse",
|
|
165
|
+
"Query",
|
|
166
|
+
"RedirectResponse",
|
|
167
|
+
"Request",
|
|
168
|
+
"Response",
|
|
169
|
+
"Route",
|
|
170
|
+
"Router",
|
|
171
|
+
"SecurityScheme",
|
|
172
|
+
"ServerSentEvent",
|
|
173
|
+
"Settings",
|
|
174
|
+
"Severity",
|
|
175
|
+
"StaticFiles",
|
|
176
|
+
"StreamingResponse",
|
|
177
|
+
"TestClient",
|
|
178
|
+
"TestResponse",
|
|
179
|
+
"VersionRouter",
|
|
180
|
+
"WebSocket",
|
|
181
|
+
"WebSocketDisconnect",
|
|
182
|
+
"delete",
|
|
183
|
+
"detect_breaking_changes",
|
|
184
|
+
"env_field",
|
|
185
|
+
"generate_openapi",
|
|
186
|
+
"get",
|
|
187
|
+
"override",
|
|
188
|
+
"patch",
|
|
189
|
+
"post",
|
|
190
|
+
"put",
|
|
191
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Optional Pydantic v2 adapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
from pydantic import BaseModel as _BaseModel
|
|
9
|
+
|
|
10
|
+
_has_pydantic = True
|
|
11
|
+
except ImportError:
|
|
12
|
+
_has_pydantic = False
|
|
13
|
+
_BaseModel: type[Any] = type # type: ignore[no-redef]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def is_pydantic_model(tp: Any) -> bool:
|
|
17
|
+
if not _has_pydantic:
|
|
18
|
+
return False
|
|
19
|
+
try:
|
|
20
|
+
return isinstance(tp, type) and issubclass(tp, _BaseModel)
|
|
21
|
+
except TypeError:
|
|
22
|
+
return False
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def decode_pydantic(model_class: type[Any], data: bytes) -> Any:
|
|
26
|
+
if not _has_pydantic:
|
|
27
|
+
raise RuntimeError("Pydantic is not installed")
|
|
28
|
+
return model_class.model_validate_json(data) # type: ignore[attr-defined]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def encode_pydantic(instance: Any) -> bytes:
|
|
32
|
+
if not _has_pydantic:
|
|
33
|
+
raise RuntimeError("Pydantic is not installed")
|
|
34
|
+
result: str = instance.model_dump_json()
|
|
35
|
+
return result.encode("utf-8")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def pydantic_to_json_schema(model_class: type[Any]) -> dict[str, Any]:
|
|
39
|
+
if not _has_pydantic:
|
|
40
|
+
raise RuntimeError("Pydantic is not installed")
|
|
41
|
+
return model_class.model_json_schema() # type: ignore[attr-defined]
|
hawkapi/_constants.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""HTTP status codes and default constants."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
HTTP_STATUS_PHRASES: dict[int, str] = {
|
|
6
|
+
100: "Continue",
|
|
7
|
+
101: "Switching Protocols",
|
|
8
|
+
200: "OK",
|
|
9
|
+
201: "Created",
|
|
10
|
+
202: "Accepted",
|
|
11
|
+
204: "No Content",
|
|
12
|
+
301: "Moved Permanently",
|
|
13
|
+
302: "Found",
|
|
14
|
+
304: "Not Modified",
|
|
15
|
+
307: "Temporary Redirect",
|
|
16
|
+
308: "Permanent Redirect",
|
|
17
|
+
400: "Bad Request",
|
|
18
|
+
401: "Unauthorized",
|
|
19
|
+
403: "Forbidden",
|
|
20
|
+
404: "Not Found",
|
|
21
|
+
405: "Method Not Allowed",
|
|
22
|
+
406: "Not Acceptable",
|
|
23
|
+
408: "Request Timeout",
|
|
24
|
+
409: "Conflict",
|
|
25
|
+
410: "Gone",
|
|
26
|
+
413: "Content Too Large",
|
|
27
|
+
415: "Unsupported Media Type",
|
|
28
|
+
422: "Unprocessable Content",
|
|
29
|
+
429: "Too Many Requests",
|
|
30
|
+
500: "Internal Server Error",
|
|
31
|
+
502: "Bad Gateway",
|
|
32
|
+
503: "Service Unavailable",
|
|
33
|
+
504: "Gateway Timeout",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
DEFAULT_CONTENT_TYPE = b"application/json"
|
|
37
|
+
DEFAULT_CHARSET = "utf-8"
|
|
38
|
+
|
|
39
|
+
SUPPORTED_METHODS = frozenset({"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"})
|
hawkapi/_types.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Internal type aliases and protocols for the ASGI interface."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Awaitable, Callable, MutableMapping
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
Scope = MutableMapping[str, Any]
|
|
9
|
+
Message = dict[str, Any]
|
|
10
|
+
|
|
11
|
+
Receive = Callable[[], Awaitable[Message]]
|
|
12
|
+
Send = Callable[[Message], Awaitable[None]]
|
|
13
|
+
|
|
14
|
+
ASGIApp = Callable[[Scope, Receive, Send], Awaitable[None]]
|
|
15
|
+
|
|
16
|
+
RouteHandler = Callable[..., Any]
|
|
17
|
+
|
|
18
|
+
# Sentinel for lazy-initialized fields
|
|
19
|
+
UNSET: Any = object()
|