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.
Files changed (95) hide show
  1. hawkapi/__init__.py +191 -0
  2. hawkapi/_compat/__init__.py +0 -0
  3. hawkapi/_compat/pydantic_adapter.py +41 -0
  4. hawkapi/_constants.py +39 -0
  5. hawkapi/_types.py +19 -0
  6. hawkapi/app.py +671 -0
  7. hawkapi/background.py +41 -0
  8. hawkapi/cli.py +64 -0
  9. hawkapi/config/__init__.py +3 -0
  10. hawkapi/config/env.py +36 -0
  11. hawkapi/config/profiles.py +41 -0
  12. hawkapi/config/settings.py +130 -0
  13. hawkapi/di/__init__.py +6 -0
  14. hawkapi/di/container.py +155 -0
  15. hawkapi/di/depends.py +42 -0
  16. hawkapi/di/provider.py +65 -0
  17. hawkapi/di/resolver.py +304 -0
  18. hawkapi/di/scope.py +79 -0
  19. hawkapi/exceptions.py +68 -0
  20. hawkapi/lifespan/__init__.py +4 -0
  21. hawkapi/lifespan/hooks.py +66 -0
  22. hawkapi/lifespan/manager.py +131 -0
  23. hawkapi/middleware/__init__.py +23 -0
  24. hawkapi/middleware/_pipeline.py +30 -0
  25. hawkapi/middleware/base.py +115 -0
  26. hawkapi/middleware/cors.py +169 -0
  27. hawkapi/middleware/error_handler.py +62 -0
  28. hawkapi/middleware/gzip.py +238 -0
  29. hawkapi/middleware/https_redirect.py +48 -0
  30. hawkapi/middleware/rate_limit.py +113 -0
  31. hawkapi/middleware/request_id.py +58 -0
  32. hawkapi/middleware/security_headers.py +68 -0
  33. hawkapi/middleware/timing.py +34 -0
  34. hawkapi/middleware/trusted_host.py +43 -0
  35. hawkapi/observability/__init__.py +6 -0
  36. hawkapi/observability/config.py +34 -0
  37. hawkapi/observability/logger.py +48 -0
  38. hawkapi/observability/metrics.py +34 -0
  39. hawkapi/observability/middleware.py +124 -0
  40. hawkapi/observability/tracing.py +40 -0
  41. hawkapi/openapi/__init__.py +10 -0
  42. hawkapi/openapi/breaking_changes.py +216 -0
  43. hawkapi/openapi/inspector.py +210 -0
  44. hawkapi/openapi/models.py +136 -0
  45. hawkapi/openapi/schema.py +324 -0
  46. hawkapi/openapi/ui.py +66 -0
  47. hawkapi/py.typed +0 -0
  48. hawkapi/requests/__init__.py +7 -0
  49. hawkapi/requests/form_data.py +162 -0
  50. hawkapi/requests/headers.py +51 -0
  51. hawkapi/requests/query_params.py +50 -0
  52. hawkapi/requests/request.py +207 -0
  53. hawkapi/requests/state.py +36 -0
  54. hawkapi/responses/__init__.py +20 -0
  55. hawkapi/responses/file_response.py +107 -0
  56. hawkapi/responses/html_response.py +24 -0
  57. hawkapi/responses/json_response.py +50 -0
  58. hawkapi/responses/plain_text.py +28 -0
  59. hawkapi/responses/redirect.py +24 -0
  60. hawkapi/responses/response.py +64 -0
  61. hawkapi/responses/sse.py +110 -0
  62. hawkapi/responses/streaming.py +77 -0
  63. hawkapi/routing/__init__.py +16 -0
  64. hawkapi/routing/_radix_tree.py +189 -0
  65. hawkapi/routing/controllers.py +247 -0
  66. hawkapi/routing/param_converters.py +21 -0
  67. hawkapi/routing/route.py +27 -0
  68. hawkapi/routing/router.py +372 -0
  69. hawkapi/routing/version_router.py +50 -0
  70. hawkapi/security/__init__.py +19 -0
  71. hawkapi/security/api_key.py +104 -0
  72. hawkapi/security/base.py +29 -0
  73. hawkapi/security/http_basic.py +69 -0
  74. hawkapi/security/http_bearer.py +55 -0
  75. hawkapi/security/oauth2.py +56 -0
  76. hawkapi/security/permissions.py +69 -0
  77. hawkapi/serialization/__init__.py +4 -0
  78. hawkapi/serialization/encoder.py +39 -0
  79. hawkapi/serialization/negotiation.py +62 -0
  80. hawkapi/staticfiles.py +186 -0
  81. hawkapi/testing/__init__.py +4 -0
  82. hawkapi/testing/client.py +277 -0
  83. hawkapi/testing/overrides.py +40 -0
  84. hawkapi/testing/plugin.py +23 -0
  85. hawkapi/validation/__init__.py +17 -0
  86. hawkapi/validation/constraints.py +54 -0
  87. hawkapi/validation/decoder.py +47 -0
  88. hawkapi/validation/errors.py +77 -0
  89. hawkapi/websocket/__init__.py +3 -0
  90. hawkapi/websocket/connection.py +149 -0
  91. hawkapi-0.1.0.dist-info/METADATA +975 -0
  92. hawkapi-0.1.0.dist-info/RECORD +95 -0
  93. hawkapi-0.1.0.dist-info/WHEEL +4 -0
  94. hawkapi-0.1.0.dist-info/entry_points.txt +2 -0
  95. 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()