hypern 0.3.4__cp311-cp311-win32.whl → 0.3.5__cp311-cp311-win32.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.
- hypern/{db → database}/sql/query.py +1 -1
- hypern/exceptions/__init__.py +34 -0
- hypern/exceptions/base.py +62 -0
- hypern/exceptions/common.py +12 -0
- hypern/exceptions/errors.py +15 -0
- hypern/exceptions/formatters.py +56 -0
- hypern/exceptions/http.py +76 -0
- hypern/hypern.cp311-win32.pyd +0 -0
- hypern/middleware/security.py +6 -6
- hypern/processpool.py +24 -6
- hypern/response/response.py +8 -0
- hypern/routing/dispatcher.py +4 -5
- hypern/routing/parser.py +11 -13
- {hypern-0.3.4.dist-info → hypern-0.3.5.dist-info}/METADATA +2 -1
- {hypern-0.3.4.dist-info → hypern-0.3.5.dist-info}/RECORD +39 -34
- hypern/exceptions.py +0 -107
- /hypern/{db → database}/__init__.py +0 -0
- /hypern/{db → database}/addons/__init__.py +0 -0
- /hypern/{db → database}/addons/sqlalchemy/__init__.py +0 -0
- /hypern/{db → database}/addons/sqlalchemy/fields/__init__.py +0 -0
- /hypern/{db → database}/addons/sqlalchemy/fields/color.py +0 -0
- /hypern/{db → database}/addons/sqlalchemy/fields/daterange.py +0 -0
- /hypern/{db → database}/addons/sqlalchemy/fields/datetime.py +0 -0
- /hypern/{db → database}/addons/sqlalchemy/fields/encrypted.py +0 -0
- /hypern/{db → database}/addons/sqlalchemy/fields/password.py +0 -0
- /hypern/{db → database}/addons/sqlalchemy/fields/ts_vector.py +0 -0
- /hypern/{db → database}/addons/sqlalchemy/fields/unicode.py +0 -0
- /hypern/{db → database}/addons/sqlalchemy/repository.py +0 -0
- /hypern/{db → database}/nosql/__init__.py +0 -0
- /hypern/{db → database}/nosql/addons/__init__.py +0 -0
- /hypern/{db → database}/nosql/addons/color.py +0 -0
- /hypern/{db → database}/nosql/addons/daterange.py +0 -0
- /hypern/{db → database}/nosql/addons/encrypted.py +0 -0
- /hypern/{db → database}/nosql/addons/password.py +0 -0
- /hypern/{db → database}/nosql/addons/unicode.py +0 -0
- /hypern/{db → database}/sql/__init__.py +0 -0
- /hypern/{db → database}/sql/field.py +0 -0
- /hypern/{db → database}/sql/model.py +0 -0
- {hypern-0.3.4.dist-info → hypern-0.3.5.dist-info}/WHEEL +0 -0
- {hypern-0.3.4.dist-info → hypern-0.3.5.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
from .base import HTTPException, ResponseFormatter, HypernError
|
2
|
+
from .errors import ErrorDefinitions
|
3
|
+
from .formatters import SimpleFormatter, DetailedFormatter, LocalizedFormatter
|
4
|
+
from .http import (
|
5
|
+
BadRequestException,
|
6
|
+
UnauthorizedException,
|
7
|
+
ForbiddenException,
|
8
|
+
NotFoundException,
|
9
|
+
ValidationException,
|
10
|
+
InternalServerException,
|
11
|
+
RateLimitException,
|
12
|
+
)
|
13
|
+
|
14
|
+
from .common import DBFieldValidationError, InvalidPortNumber, OutOfScopeApplicationException
|
15
|
+
|
16
|
+
__all__ = [
|
17
|
+
"HTTPException",
|
18
|
+
"ResponseFormatter",
|
19
|
+
"HypernError",
|
20
|
+
"ErrorDefinitions",
|
21
|
+
"SimpleFormatter",
|
22
|
+
"DetailedFormatter",
|
23
|
+
"LocalizedFormatter",
|
24
|
+
"BadRequestException",
|
25
|
+
"UnauthorizedException",
|
26
|
+
"ForbiddenException",
|
27
|
+
"NotFoundException",
|
28
|
+
"ValidationException",
|
29
|
+
"InternalServerException",
|
30
|
+
"RateLimitException",
|
31
|
+
"DBFieldValidationError",
|
32
|
+
"InvalidPortNumber",
|
33
|
+
"OutOfScopeApplicationException",
|
34
|
+
]
|
@@ -0,0 +1,62 @@
|
|
1
|
+
import uuid
|
2
|
+
from abc import ABC, abstractmethod
|
3
|
+
from datetime import datetime, timezone
|
4
|
+
from http import HTTPStatus
|
5
|
+
from typing import Any, Dict, Optional
|
6
|
+
|
7
|
+
|
8
|
+
class ResponseFormatter(ABC):
|
9
|
+
@abstractmethod
|
10
|
+
def format_error(self, exception: "HTTPException") -> Dict[str, Any]:
|
11
|
+
"""Format exception into response dictionary"""
|
12
|
+
pass
|
13
|
+
|
14
|
+
|
15
|
+
class DefaultFormatter(ResponseFormatter):
|
16
|
+
def format_error(self, exception: "HTTPException") -> Dict[str, Any]:
|
17
|
+
return {
|
18
|
+
"error": {
|
19
|
+
"code": exception.error.code if exception.error else "UNKNOWN_ERROR",
|
20
|
+
"message": exception.error.message if exception.error else "Unknown error occurred",
|
21
|
+
"details": exception.details or {},
|
22
|
+
"timestamp": datetime.now(tz=timezone.utc).isoformat(),
|
23
|
+
"request_id": str(uuid.uuid4()),
|
24
|
+
},
|
25
|
+
"status": exception.status_code,
|
26
|
+
}
|
27
|
+
|
28
|
+
|
29
|
+
class HypernError:
|
30
|
+
"""Base error definition"""
|
31
|
+
|
32
|
+
def __init__(self, message: str, code: str):
|
33
|
+
self.message = message
|
34
|
+
self.code = code
|
35
|
+
|
36
|
+
|
37
|
+
class HTTPException(Exception):
|
38
|
+
"""Base HTTP exception"""
|
39
|
+
|
40
|
+
_formatter: ResponseFormatter = DefaultFormatter()
|
41
|
+
|
42
|
+
@classmethod
|
43
|
+
def set_formatter(cls, formatter: ResponseFormatter):
|
44
|
+
cls._formatter = formatter
|
45
|
+
|
46
|
+
def __init__(
|
47
|
+
self,
|
48
|
+
status_code: int = HTTPStatus.BAD_REQUEST,
|
49
|
+
error: Optional[HypernError] = None,
|
50
|
+
details: Optional[Dict[str, Any]] = None,
|
51
|
+
headers: Optional[Dict[str, str]] = None,
|
52
|
+
formatter: Optional[ResponseFormatter] = None,
|
53
|
+
):
|
54
|
+
self.status_code = status_code
|
55
|
+
self.error = error
|
56
|
+
self.details = details or {}
|
57
|
+
self.headers = headers or {}
|
58
|
+
self._instance_formatter = formatter
|
59
|
+
|
60
|
+
def to_dict(self) -> Dict[str, Any]:
|
61
|
+
formatter = self._instance_formatter or self._formatter
|
62
|
+
return formatter.format_error(self)
|
@@ -0,0 +1,15 @@
|
|
1
|
+
from .base import HypernError
|
2
|
+
|
3
|
+
|
4
|
+
class ErrorDefinitions:
|
5
|
+
"""Standard error definitions"""
|
6
|
+
|
7
|
+
BAD_REQUEST = HypernError(message="Bad request", code="BAD_REQUEST")
|
8
|
+
UNAUTHORIZED = HypernError(message="Unauthorized access", code="UNAUTHORIZED")
|
9
|
+
FORBIDDEN = HypernError(message="Access forbidden", code="FORBIDDEN")
|
10
|
+
NOT_FOUND = HypernError(message="Resource not found", code="NOT_FOUND")
|
11
|
+
METHOD_NOT_ALLOWED = HypernError(message="Method not allowed", code="METHOD_NOT_ALLOWED")
|
12
|
+
VALIDATION_ERROR = HypernError(message="Validation error", code="VALIDATION_ERROR")
|
13
|
+
INTERNAL_ERROR = HypernError(message="Internal server error", code="INTERNAL_SERVER_ERROR")
|
14
|
+
CONFLICT = HypernError(message="Resource conflict", code="CONFLICT")
|
15
|
+
TOO_MANY_REQUESTS = HypernError(message="Too many requests", code="TOO_MANY_REQUESTS")
|
@@ -0,0 +1,56 @@
|
|
1
|
+
import uuid
|
2
|
+
from datetime import datetime, timezone
|
3
|
+
from typing import Any, Dict
|
4
|
+
|
5
|
+
from .base import HTTPException, ResponseFormatter
|
6
|
+
|
7
|
+
|
8
|
+
class SimpleFormatter(ResponseFormatter):
|
9
|
+
def format_error(self, exception: HTTPException) -> Dict[str, Any]:
|
10
|
+
return {
|
11
|
+
"code": exception.error.code if exception.error else "UNKNOWN_ERROR",
|
12
|
+
"message": exception.error.message if exception.error else "Unknown error occurred",
|
13
|
+
}
|
14
|
+
|
15
|
+
|
16
|
+
class DetailedFormatter(ResponseFormatter):
|
17
|
+
def format_error(self, exception: HTTPException) -> Dict[str, Any]:
|
18
|
+
return {
|
19
|
+
"status": {"code": exception.status_code, "text": str(exception.status_code)},
|
20
|
+
"error": {
|
21
|
+
"type": exception.error.code if exception.error else "UNKNOWN_ERROR",
|
22
|
+
"message": exception.error.message if exception.error else "Unknown error occurred",
|
23
|
+
"details": exception.details or {},
|
24
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
25
|
+
},
|
26
|
+
"request": {"path": exception.path, "id": str(uuid.uuid4())},
|
27
|
+
}
|
28
|
+
|
29
|
+
|
30
|
+
class LocalizedFormatter(ResponseFormatter):
|
31
|
+
def __init__(self, language: str = "en"):
|
32
|
+
self.language = language
|
33
|
+
self.translations = {
|
34
|
+
"en": {
|
35
|
+
"BAD_REQUEST": "Bad request",
|
36
|
+
"VALIDATION_ERROR": "Validation error",
|
37
|
+
"NOT_FOUND": "Resource not found",
|
38
|
+
# Add more translations
|
39
|
+
},
|
40
|
+
"vi": {
|
41
|
+
"BAD_REQUEST": "Yêu cầu không hợp lệ",
|
42
|
+
"VALIDATION_ERROR": "Lỗi xác thực",
|
43
|
+
"NOT_FOUND": "Không tìm thấy tài nguyên",
|
44
|
+
# Add more translations
|
45
|
+
},
|
46
|
+
}
|
47
|
+
|
48
|
+
def format_error(self, exception: HTTPException) -> Dict[str, Any]:
|
49
|
+
error_code = exception.error.code if exception.error else "UNKNOWN_ERROR"
|
50
|
+
translated_message = self.translations.get(self.language, {}).get(error_code, exception.error.message if exception.error else "Unknown error occurred")
|
51
|
+
|
52
|
+
return {
|
53
|
+
"error": {"code": error_code, "message": translated_message, "details": exception.details or {}},
|
54
|
+
"status": exception.status_code,
|
55
|
+
"timestamp": datetime.now(tz=timezone.utc).isoformat(),
|
56
|
+
}
|
@@ -0,0 +1,76 @@
|
|
1
|
+
from http import HTTPStatus
|
2
|
+
from typing import Any, Dict, Optional
|
3
|
+
|
4
|
+
from .base import HTTPException, ResponseFormatter, HypernError
|
5
|
+
from .errors import ErrorDefinitions
|
6
|
+
|
7
|
+
|
8
|
+
class BadRequestException(HTTPException):
|
9
|
+
def __init__(
|
10
|
+
self,
|
11
|
+
error: Optional[HypernError] = ErrorDefinitions.BAD_REQUEST,
|
12
|
+
details: Optional[Dict[str, Any]] = None,
|
13
|
+
headers: Optional[Dict[str, str]] = None,
|
14
|
+
formatter: Optional[ResponseFormatter] = None,
|
15
|
+
):
|
16
|
+
super().__init__(status_code=HTTPStatus.BAD_REQUEST, error=error, details=details, headers=headers, formatter=formatter)
|
17
|
+
|
18
|
+
|
19
|
+
class UnauthorizedException(HTTPException):
|
20
|
+
def __init__(
|
21
|
+
self,
|
22
|
+
error: Optional[HypernError] = ErrorDefinitions.UNAUTHORIZED,
|
23
|
+
details: Optional[Dict[str, Any]] = None,
|
24
|
+
headers: Optional[Dict[str, str]] = None,
|
25
|
+
formatter: Optional[ResponseFormatter] = None,
|
26
|
+
):
|
27
|
+
super().__init__(status_code=HTTPStatus.UNAUTHORIZED, error=error, details=details, headers=headers, formatter=formatter)
|
28
|
+
|
29
|
+
|
30
|
+
class ForbiddenException(HTTPException):
|
31
|
+
def __init__(
|
32
|
+
self,
|
33
|
+
error: Optional[HypernError] = ErrorDefinitions.FORBIDDEN,
|
34
|
+
details: Optional[Dict[str, Any]] = None,
|
35
|
+
headers: Optional[Dict[str, str]] = None,
|
36
|
+
formatter: Optional[ResponseFormatter] = None,
|
37
|
+
):
|
38
|
+
super().__init__(status_code=HTTPStatus.FORBIDDEN, error=error, details=details, headers=headers, formatter=formatter)
|
39
|
+
|
40
|
+
|
41
|
+
class NotFoundException(HTTPException):
|
42
|
+
def __init__(
|
43
|
+
self,
|
44
|
+
error: Optional[HypernError] = ErrorDefinitions.NOT_FOUND,
|
45
|
+
details: Optional[Dict[str, Any]] = None,
|
46
|
+
headers: Optional[Dict[str, str]] = None,
|
47
|
+
formatter: Optional[ResponseFormatter] = None,
|
48
|
+
):
|
49
|
+
super().__init__(status_code=HTTPStatus.NOT_FOUND, error=error, details=details, headers=headers, formatter=formatter)
|
50
|
+
|
51
|
+
|
52
|
+
class ValidationException(HTTPException):
|
53
|
+
def __init__(self, details: Optional[Dict[str, Any]] = None, headers: Optional[Dict[str, str]] = None, formatter: Optional[ResponseFormatter] = None):
|
54
|
+
super().__init__(status_code=HTTPStatus.BAD_REQUEST, error=ErrorDefinitions.VALIDATION_ERROR, details=details, headers=headers, formatter=formatter)
|
55
|
+
|
56
|
+
|
57
|
+
class InternalServerException(HTTPException):
|
58
|
+
def __init__(
|
59
|
+
self,
|
60
|
+
error: Optional[HypernError] = ErrorDefinitions.INTERNAL_ERROR,
|
61
|
+
details: Optional[Dict[str, Any]] = None,
|
62
|
+
headers: Optional[Dict[str, str]] = None,
|
63
|
+
formatter: Optional[ResponseFormatter] = None,
|
64
|
+
):
|
65
|
+
super().__init__(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, error=error, details=details, headers=headers, formatter=formatter)
|
66
|
+
|
67
|
+
|
68
|
+
class RateLimitException(HTTPException):
|
69
|
+
def __init__(self, retry_after: int, details: Optional[Dict[str, Any]] = None, formatter: Optional[ResponseFormatter] = None):
|
70
|
+
super().__init__(
|
71
|
+
status_code=HTTPStatus.TOO_MANY_REQUESTS,
|
72
|
+
error=ErrorDefinitions.TOO_MANY_REQUESTS,
|
73
|
+
details=details,
|
74
|
+
headers={"Retry-After": str(retry_after)},
|
75
|
+
formatter=formatter,
|
76
|
+
)
|
hypern/hypern.cp311-win32.pyd
CHANGED
Binary file
|
hypern/middleware/security.py
CHANGED
@@ -9,7 +9,7 @@ from typing import Any, Dict, List, Optional
|
|
9
9
|
|
10
10
|
import jwt
|
11
11
|
|
12
|
-
from hypern.exceptions import
|
12
|
+
from hypern.exceptions import ForbiddenException, UnauthorizedException
|
13
13
|
from hypern.hypern import Request, Response
|
14
14
|
from .base import Middleware, MiddlewareConfig
|
15
15
|
|
@@ -91,9 +91,9 @@ class SecurityMiddleware(Middleware):
|
|
91
91
|
payload = jwt.decode(token, self.secur_config.jwt_secret, algorithms=[self.secur_config.jwt_algorithm])
|
92
92
|
return payload
|
93
93
|
except jwt.ExpiredSignatureError:
|
94
|
-
raise
|
94
|
+
raise UnauthorizedException(details={"message": "Token has expired"})
|
95
95
|
except jwt.InvalidTokenError:
|
96
|
-
raise
|
96
|
+
raise UnauthorizedException(details={"message": "Invalid token"})
|
97
97
|
|
98
98
|
def _generate_csrf_token(self, session_id: str) -> str:
|
99
99
|
"""Generate a new CSRF token"""
|
@@ -155,18 +155,18 @@ class SecurityMiddleware(Middleware):
|
|
155
155
|
if self.secur_config.jwt_auth:
|
156
156
|
auth_header = request.headers.get("Authorization")
|
157
157
|
if not auth_header or not auth_header.startswith("Bearer "):
|
158
|
-
raise
|
158
|
+
raise UnauthorizedException(details={"message": "Authorization header missing or invalid"})
|
159
159
|
token = auth_header.split(" ")[1]
|
160
160
|
try:
|
161
161
|
request.user = self._verify_jwt_token(token)
|
162
|
-
except
|
162
|
+
except UnauthorizedException as e:
|
163
163
|
return Response(status_code=401, description=str(e))
|
164
164
|
|
165
165
|
# CSRF protection check
|
166
166
|
if self.secur_config.csrf_protection and request.method in ["POST", "PUT", "DELETE", "PATCH"]:
|
167
167
|
csrf_token = request.headers.get("X-CSRF-Token")
|
168
168
|
if not csrf_token or not self._validate_csrf_token(csrf_token):
|
169
|
-
raise
|
169
|
+
raise ForbiddenException(details={"message": "Invalid CSRF token"})
|
170
170
|
|
171
171
|
return request
|
172
172
|
|
hypern/processpool.py
CHANGED
@@ -3,7 +3,7 @@ import os
|
|
3
3
|
import signal
|
4
4
|
import sys
|
5
5
|
from typing import List
|
6
|
-
|
6
|
+
from concurrent.futures import ThreadPoolExecutor
|
7
7
|
from multiprocess import Process
|
8
8
|
from watchdog.observers import Observer
|
9
9
|
|
@@ -76,7 +76,7 @@ def init_processpool(
|
|
76
76
|
) -> List[Process]:
|
77
77
|
process_pool = []
|
78
78
|
|
79
|
-
for
|
79
|
+
for i in range(processes):
|
80
80
|
copied_socket = socket.try_clone()
|
81
81
|
process = Process(
|
82
82
|
target=spawn_process,
|
@@ -86,25 +86,43 @@ def init_processpool(
|
|
86
86
|
workers,
|
87
87
|
max_blocking_threads,
|
88
88
|
),
|
89
|
+
name=f"hypern-worker-{i}",
|
89
90
|
)
|
91
|
+
process.daemon = True # This is important to avoid zombie processes
|
90
92
|
process.start()
|
91
93
|
process_pool.append(process)
|
92
94
|
|
93
95
|
return process_pool
|
94
96
|
|
95
97
|
|
96
|
-
|
98
|
+
class OptimizedEventLoopPolicy(asyncio.DefaultEventLoopPolicy):
|
99
|
+
def __init__(self, max_blocking_threads: int):
|
100
|
+
super().__init__()
|
101
|
+
self.max_blocking_threads = max_blocking_threads
|
102
|
+
|
103
|
+
def new_event_loop(self):
|
104
|
+
loop = super().new_event_loop()
|
105
|
+
# Optimize thread pool cho I/O operations
|
106
|
+
loop.set_default_executor(ThreadPoolExecutor(max_workers=self.max_blocking_threads, thread_name_prefix="hypern-io"))
|
107
|
+
return loop
|
108
|
+
|
109
|
+
|
110
|
+
def initialize_event_loop(max_blocking_threads: int = 100) -> asyncio.AbstractEventLoop:
|
97
111
|
if sys.platform.startswith("win32") or sys.platform.startswith("linux-cross"):
|
98
112
|
loop = asyncio.new_event_loop()
|
99
113
|
asyncio.set_event_loop(loop)
|
100
|
-
return loop
|
101
114
|
else:
|
102
115
|
import uvloop
|
103
116
|
|
104
117
|
uvloop.install()
|
118
|
+
|
119
|
+
asyncio.set_event_loop_policy(OptimizedEventLoopPolicy(max_blocking_threads))
|
105
120
|
loop = uvloop.new_event_loop()
|
106
121
|
asyncio.set_event_loop(loop)
|
107
|
-
|
122
|
+
|
123
|
+
loop.slow_callback_duration = 0.1 # Log warnings for slow callbacks
|
124
|
+
loop.set_debug(False) # Disable debug mode
|
125
|
+
return loop
|
108
126
|
|
109
127
|
|
110
128
|
def spawn_process(
|
@@ -113,7 +131,7 @@ def spawn_process(
|
|
113
131
|
workers: int,
|
114
132
|
max_blocking_threads: int,
|
115
133
|
):
|
116
|
-
loop = initialize_event_loop()
|
134
|
+
loop = initialize_event_loop(max_blocking_threads)
|
117
135
|
|
118
136
|
try:
|
119
137
|
server.start(socket, workers, max_blocking_threads)
|
hypern/response/response.py
CHANGED
@@ -4,6 +4,7 @@ import typing
|
|
4
4
|
from urllib.parse import quote
|
5
5
|
from hypern.hypern import Response as InternalResponse, Header
|
6
6
|
import orjson
|
7
|
+
import msgpack
|
7
8
|
|
8
9
|
from hypern.background import BackgroundTask, BackgroundTasks
|
9
10
|
|
@@ -132,3 +133,10 @@ class FileResponse(BaseResponse):
|
|
132
133
|
self.raw_headers["content-disposition"] = f'attachment; filename="{filename}"'
|
133
134
|
self.raw_headers.setdefault("content-type", "application/octet-stream")
|
134
135
|
self.raw_headers.setdefault("content-length", str(len(content)))
|
136
|
+
|
137
|
+
|
138
|
+
@to_response
|
139
|
+
class BinaryResponse(BaseResponse):
|
140
|
+
def __init__(self, content: bytes):
|
141
|
+
super().__init__(status_code=200, media_type="application/x-msgpack", headers={"Content-Type": "application/x-msgpack"})
|
142
|
+
self.content = msgpack.packb(content)
|
hypern/routing/dispatcher.py
CHANGED
@@ -10,7 +10,7 @@ import typing
|
|
10
10
|
import orjson
|
11
11
|
from pydantic import BaseModel
|
12
12
|
|
13
|
-
from hypern.exceptions import
|
13
|
+
from hypern.exceptions import HTTPException
|
14
14
|
from hypern.hypern import Request, Response
|
15
15
|
from hypern.response import JSONResponse
|
16
16
|
|
@@ -56,10 +56,9 @@ async def dispatch(handler, request: Request, inject: typing.Dict[str, typing.An
|
|
56
56
|
|
57
57
|
except Exception as e:
|
58
58
|
_res: typing.Dict = {"message": "", "error_code": "UNKNOWN_ERROR"}
|
59
|
-
if isinstance(e,
|
60
|
-
_res
|
61
|
-
|
62
|
-
_status = e.status
|
59
|
+
if isinstance(e, HTTPException):
|
60
|
+
_res = e.to_dict()
|
61
|
+
_status = e.status_code
|
63
62
|
else:
|
64
63
|
traceback.print_exc()
|
65
64
|
_res["message"] = str(e)
|
hypern/routing/parser.py
CHANGED
@@ -9,8 +9,8 @@ from pydantic import BaseModel, ValidationError
|
|
9
9
|
from pydash import get
|
10
10
|
|
11
11
|
from hypern.auth.authorization import Authorization
|
12
|
-
from hypern.exceptions import
|
13
|
-
from hypern.exceptions import
|
12
|
+
from hypern.exceptions import BadRequestException
|
13
|
+
from hypern.exceptions import ValidationException
|
14
14
|
from hypern.hypern import Request
|
15
15
|
|
16
16
|
|
@@ -28,7 +28,7 @@ class ParamParser:
|
|
28
28
|
|
29
29
|
parser = data_parsers.get(param_name)
|
30
30
|
if not parser:
|
31
|
-
raise
|
31
|
+
raise BadRequestException(details={"message": f"Invalid parameter name: {param_name}"})
|
32
32
|
return parser()
|
33
33
|
|
34
34
|
def _parse_query_params(self) -> dict:
|
@@ -53,16 +53,14 @@ class InputHandler:
|
|
53
53
|
return model_class(**data)
|
54
54
|
except ValidationError as e:
|
55
55
|
invalid_fields = orjson.loads(e.json())
|
56
|
-
raise
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
]
|
65
|
-
).decode("utf-8"),
|
56
|
+
raise ValidationException(
|
57
|
+
details=[
|
58
|
+
{
|
59
|
+
"field": get(item, "loc")[0],
|
60
|
+
"msg": get(item, "msg"),
|
61
|
+
}
|
62
|
+
for item in invalid_fields
|
63
|
+
]
|
66
64
|
)
|
67
65
|
|
68
66
|
async def handle_special_params(self, param_name: str) -> typing.Any:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: hypern
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.5
|
4
4
|
Classifier: Programming Language :: Rust
|
5
5
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
6
6
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
@@ -25,6 +25,7 @@ Requires-Dist: cryptography ==43.0.3
|
|
25
25
|
Requires-Dist: watchdog ==6.0.0
|
26
26
|
Requires-Dist: jsonschema ==4.23.0
|
27
27
|
Requires-Dist: psutil ==6.1.0
|
28
|
+
Requires-Dist: msgpack ==1.1.0
|
28
29
|
License-File: LICENSE
|
29
30
|
Summary: A Fast Async Python backend with a Rust runtime.
|
30
31
|
Author-email: Martin Dang <vannghiem848@gmail.com>
|
@@ -1,6 +1,6 @@
|
|
1
|
-
hypern-0.3.
|
2
|
-
hypern-0.3.
|
3
|
-
hypern-0.3.
|
1
|
+
hypern-0.3.5.dist-info/METADATA,sha256=Lhxf8FMnS1RvE17wHATRz6F1-v0S3IYcXrGmzWkm3Jg,3850
|
2
|
+
hypern-0.3.5.dist-info/WHEEL,sha256=cIWnBshq9UErEeNtadr9w7TUZu32emQUVzK31LjJSaM,92
|
3
|
+
hypern-0.3.5.dist-info/licenses/LICENSE,sha256=qbYKAIJLS6jYg5hYncKE7OtWmqOtpVTvKNkwOa0Iwwg,1328
|
4
4
|
hypern/application.py,sha256=DCYFtU8e8NhQtmfaXbUfOxR2_Y3fEn-pzce9OOs6S4U,18396
|
5
5
|
hypern/args_parser.py,sha256=zTfLfBoKBvYWxdPjabTfZsCtYF3La3PT0TD8dfLMeM4,2815
|
6
6
|
hypern/auth/authorization.py,sha256=-NprZsI0np889ZN1fp-MiVFrPoMNzUtatBJaCMtkllM,32
|
@@ -13,32 +13,37 @@ hypern/caching/__init__.py,sha256=ODO7zMm4iFG8wcvrhKmukryG5wOTW0DnzFvNMfF57Cc,35
|
|
13
13
|
hypern/cli/commands.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
14
14
|
hypern/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
15
|
hypern/config.py,sha256=Jij9eGg5NgC8Un5Lw5i7ghuEMAfkVctdcoE4RaN5LTE,8157
|
16
|
+
hypern/database/addons/sqlalchemy/fields/color.py,sha256=Jj8q4lkT0ukKyjVyZjBx7fokGrX7AIJpKOGzwsBpfnU,480
|
17
|
+
hypern/database/addons/sqlalchemy/fields/daterange.py,sha256=qEfQN9c4jQdzeXNYKgQ4VIJ3Qc0HXHWRouzNF1se-RA,853
|
18
|
+
hypern/database/addons/sqlalchemy/fields/datetime.py,sha256=Bp2jMja2lb_b2WnzRnfbjXXTHBgBTyph1ECsItIwvvg,643
|
19
|
+
hypern/database/addons/sqlalchemy/fields/encrypted.py,sha256=pXsg4ImPpK-VkLDruKrv2gUtdp2kajQX7xDbRDxa-SI,1930
|
20
|
+
hypern/database/addons/sqlalchemy/fields/password.py,sha256=9pypORygWaINj3oiAOBRIOGgpuA0lcjPq4rh1pqJxq0,5789
|
21
|
+
hypern/database/addons/sqlalchemy/fields/ts_vector.py,sha256=bQyXYvQ1bAfFpYcS-sFwM7fU6L1lg9_7nGDRGp0CoUo,1361
|
22
|
+
hypern/database/addons/sqlalchemy/fields/unicode.py,sha256=rbqyHlsPUqRDWIjYQSRFF3zkHncnwxo0sdlzqUlcrUw,411
|
23
|
+
hypern/database/addons/sqlalchemy/fields/__init__.py,sha256=mLN_AvwgpSAbrWZvVHZuO7ff0gk1T_JbVwd5wug5nlw,359
|
24
|
+
hypern/database/addons/sqlalchemy/repository.py,sha256=ue6vWOTrnEPyDevlyh3v-7PU6GSfrZHYKrbXVuoS8UA,9516
|
25
|
+
hypern/database/addons/sqlalchemy/__init__.py,sha256=FuY78ubEwtifdQTVHhCrscYaAarlp2urgYBc_R77yt0,2766
|
26
|
+
hypern/database/addons/__init__.py,sha256=mdW0P0xvnK8htUk02ujvIaeHXl6w53JjrTS4ioNi1Bw,63
|
27
|
+
hypern/database/nosql/addons/color.py,sha256=bAGRuARCAYwZ1nO4jK0lzGYKmavTDtS34BxvrsetF74,446
|
28
|
+
hypern/database/nosql/addons/daterange.py,sha256=hGUSoVFqatNY-TB5wjZTq62iZpHpdsyRJIsHxsj1uDs,1192
|
29
|
+
hypern/database/nosql/addons/encrypted.py,sha256=B0M-uDqvZHVmIZcFdwcuC2MGsv0pGJFQ1lrOg8klR9U,1741
|
30
|
+
hypern/database/nosql/addons/password.py,sha256=jfZxvWFm6nV9EWpXq5Mj-jpqnl9QbokZj9WT14n7dKE,5035
|
31
|
+
hypern/database/nosql/addons/unicode.py,sha256=LaDpLfdoTcJuASPE-8fqOVD05H_uOx8gOdnyDn5Iu0c,268
|
32
|
+
hypern/database/nosql/addons/__init__.py,sha256=WEtPM8sPHilvga7zxwqvINeTkF0hdcfgPcAnHc4MASE,125
|
33
|
+
hypern/database/nosql/__init__.py,sha256=MH9YvlbRlbBCrQVNOdfTaK-hINwJxbJLmxwY9Mei7I8,644
|
34
|
+
hypern/database/sql/field.py,sha256=tSs8iaYjy-K6nplJJ-1X4OQddzW76cfBlx9xTrG_NbQ,20073
|
35
|
+
hypern/database/sql/model.py,sha256=BLRmOlmfn6ibedR9Bv_rHErSruudJ24B9-nDbRHqWm4,3913
|
36
|
+
hypern/database/sql/query.py,sha256=tQ7Wss2NAIqsAH0M-fT5m9DU_MsiBR0DcoyTbS_aatU,33335
|
37
|
+
hypern/database/sql/__init__.py,sha256=lCOGNTHaXNSJbuLLIOe2IWWNmX0MFQFPNCl2yytD2Xs,261
|
38
|
+
hypern/database/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
16
39
|
hypern/datastructures.py,sha256=zZGGSP07kPc9KJDf11hX5uYhAyRE-Ck5wezW5QtOVXw,897
|
17
|
-
hypern/db/addons/sqlalchemy/fields/color.py,sha256=Jj8q4lkT0ukKyjVyZjBx7fokGrX7AIJpKOGzwsBpfnU,480
|
18
|
-
hypern/db/addons/sqlalchemy/fields/daterange.py,sha256=qEfQN9c4jQdzeXNYKgQ4VIJ3Qc0HXHWRouzNF1se-RA,853
|
19
|
-
hypern/db/addons/sqlalchemy/fields/datetime.py,sha256=Bp2jMja2lb_b2WnzRnfbjXXTHBgBTyph1ECsItIwvvg,643
|
20
|
-
hypern/db/addons/sqlalchemy/fields/encrypted.py,sha256=pXsg4ImPpK-VkLDruKrv2gUtdp2kajQX7xDbRDxa-SI,1930
|
21
|
-
hypern/db/addons/sqlalchemy/fields/password.py,sha256=9pypORygWaINj3oiAOBRIOGgpuA0lcjPq4rh1pqJxq0,5789
|
22
|
-
hypern/db/addons/sqlalchemy/fields/ts_vector.py,sha256=bQyXYvQ1bAfFpYcS-sFwM7fU6L1lg9_7nGDRGp0CoUo,1361
|
23
|
-
hypern/db/addons/sqlalchemy/fields/unicode.py,sha256=rbqyHlsPUqRDWIjYQSRFF3zkHncnwxo0sdlzqUlcrUw,411
|
24
|
-
hypern/db/addons/sqlalchemy/fields/__init__.py,sha256=mLN_AvwgpSAbrWZvVHZuO7ff0gk1T_JbVwd5wug5nlw,359
|
25
|
-
hypern/db/addons/sqlalchemy/repository.py,sha256=ue6vWOTrnEPyDevlyh3v-7PU6GSfrZHYKrbXVuoS8UA,9516
|
26
|
-
hypern/db/addons/sqlalchemy/__init__.py,sha256=FuY78ubEwtifdQTVHhCrscYaAarlp2urgYBc_R77yt0,2766
|
27
|
-
hypern/db/addons/__init__.py,sha256=mdW0P0xvnK8htUk02ujvIaeHXl6w53JjrTS4ioNi1Bw,63
|
28
|
-
hypern/db/nosql/addons/color.py,sha256=bAGRuARCAYwZ1nO4jK0lzGYKmavTDtS34BxvrsetF74,446
|
29
|
-
hypern/db/nosql/addons/daterange.py,sha256=hGUSoVFqatNY-TB5wjZTq62iZpHpdsyRJIsHxsj1uDs,1192
|
30
|
-
hypern/db/nosql/addons/encrypted.py,sha256=B0M-uDqvZHVmIZcFdwcuC2MGsv0pGJFQ1lrOg8klR9U,1741
|
31
|
-
hypern/db/nosql/addons/password.py,sha256=jfZxvWFm6nV9EWpXq5Mj-jpqnl9QbokZj9WT14n7dKE,5035
|
32
|
-
hypern/db/nosql/addons/unicode.py,sha256=LaDpLfdoTcJuASPE-8fqOVD05H_uOx8gOdnyDn5Iu0c,268
|
33
|
-
hypern/db/nosql/addons/__init__.py,sha256=WEtPM8sPHilvga7zxwqvINeTkF0hdcfgPcAnHc4MASE,125
|
34
|
-
hypern/db/nosql/__init__.py,sha256=MH9YvlbRlbBCrQVNOdfTaK-hINwJxbJLmxwY9Mei7I8,644
|
35
|
-
hypern/db/sql/field.py,sha256=tSs8iaYjy-K6nplJJ-1X4OQddzW76cfBlx9xTrG_NbQ,20073
|
36
|
-
hypern/db/sql/model.py,sha256=BLRmOlmfn6ibedR9Bv_rHErSruudJ24B9-nDbRHqWm4,3913
|
37
|
-
hypern/db/sql/query.py,sha256=MXiphm4pXBz6Axbdoc5rg11XEmw_UahTCdppowyXJxY,33329
|
38
|
-
hypern/db/sql/__init__.py,sha256=lCOGNTHaXNSJbuLLIOe2IWWNmX0MFQFPNCl2yytD2Xs,261
|
39
|
-
hypern/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
40
40
|
hypern/enum.py,sha256=KcVziJj7vWvyie0r2rtxhrLzdtkZAsf0DY58oJ4tQl4,360
|
41
|
-
hypern/exceptions.py,sha256=
|
41
|
+
hypern/exceptions/base.py,sha256=5AgfyEea79JjKk5MeAIJ-wy44FG5XEU0Jn3KXKScPiI,2017
|
42
|
+
hypern/exceptions/common.py,sha256=0E8wHRRTWjYOmtOCkTDvZ5NMwL6vRW6aiDD9X1eYA30,227
|
43
|
+
hypern/exceptions/errors.py,sha256=oAaeTeMgJEpQHspwZDG7B36FD6u6MoLgsxxDTxD7opc,857
|
44
|
+
hypern/exceptions/formatters.py,sha256=nHWrsQwG7VVZmcArDL1EYxIlkpNGkS-eCBVjUHZ296I,2386
|
45
|
+
hypern/exceptions/http.py,sha256=Q9rpL86SaUYICFZCGQowkhtd5iTS1mT-QQGx-jv7cNE,3226
|
46
|
+
hypern/exceptions/__init__.py,sha256=6ud25zPps_mzWUZlMe0Jz_rLjb3wlUNO8rCH01Di6R0,970
|
42
47
|
hypern/gateway/aggregator.py,sha256=N1onAp9gdzpCR-E5VubkVoUjjEmVNxG8gDZx9rhnbXc,1132
|
43
48
|
hypern/gateway/gateway.py,sha256=26K2qvJUR-0JnN4IlhwvSSt7EYcpYrBVDuzZ1ivQQ34,1475
|
44
49
|
hypern/gateway/proxy.py,sha256=w1wcTplDnVrfjn7hb0M0yBVth5TGl88irF-MUYHysQQ,2463
|
@@ -54,19 +59,19 @@ hypern/middleware/compress.py,sha256=Zph3pQz15YrYB4dMUMbQnfWIFY8ovysgPMepbY_WV9k
|
|
54
59
|
hypern/middleware/cors.py,sha256=pt5HyTd3J5L9Lvczo2xI8fxLmtntSbJq-CPU0vYXoAI,1800
|
55
60
|
hypern/middleware/i18n.py,sha256=jHzVzjTx1nnjbraZtIVOprrnSaeKMxZB8RuSqRp2I4s,16
|
56
61
|
hypern/middleware/limit.py,sha256=eAYARPjqxq8Ue0TCpnxlVRB5hv7hwBF0PxeD-bG6Sl0,8252
|
57
|
-
hypern/middleware/security.py,sha256=
|
62
|
+
hypern/middleware/security.py,sha256=fGBSF7n2iKBtDHE2QW4q_sQE4awYgaYxVUFKsDHkMXg,7675
|
58
63
|
hypern/middleware/__init__.py,sha256=V-Gnv-Jf-14BVuA28z7PN7GBVQ9BBiBdab6-QnTPCfY,493
|
59
64
|
hypern/openapi/schemas.py,sha256=YHfMlPUeP5DzDX5ao3YH8p_25Vvyaf616dh6XDCUZRc,1677
|
60
65
|
hypern/openapi/swagger.py,sha256=naqUY3rFAEYA1ZLIlmDsMYaol0yIm6TVebdkFa5cMTc,64
|
61
66
|
hypern/openapi/__init__.py,sha256=4rEVD8pa0kdSpsy7ZkJ5JY0Z2XF0NGSKDMwYAd7YZpE,141
|
62
|
-
hypern/processpool.py,sha256=
|
67
|
+
hypern/processpool.py,sha256=qEsu9WXWc3_Cl0Frn1jGs7jUJho45zck5L5Ww81Vm70,3883
|
63
68
|
hypern/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
64
69
|
hypern/reload.py,sha256=nfaZCoChrQetHNtIqN4Xzi-a0v-irxSCMhwCK3bCEq0,1569
|
65
|
-
hypern/response/response.py,sha256
|
70
|
+
hypern/response/response.py,sha256=Jrkpk5KOmE4WjsAh1G0hUuPHfOPxU6eeR3J2fRIhusU,4871
|
66
71
|
hypern/response/__init__.py,sha256=_w3u3TDNuYx5ejnnN1unqnTY8NlBgUATQi6wepEB_FQ,226
|
67
|
-
hypern/routing/dispatcher.py,sha256=
|
72
|
+
hypern/routing/dispatcher.py,sha256=NAVjILlEJjYrixJZ4CO4N1CKkuqbk4TGZOjnQNTTEu4,2461
|
68
73
|
hypern/routing/endpoint.py,sha256=RKVhvqOEGL9IKBXQ3KJgPi9bgJj9gfWC5BdZc5U_atc,1026
|
69
|
-
hypern/routing/parser.py,sha256=
|
74
|
+
hypern/routing/parser.py,sha256=0tJVVNwHC3pWDsehwH6SwJv8_gEuDjltVXrNQWbHyrU,3426
|
70
75
|
hypern/routing/queue.py,sha256=NtFBbogU22ddyyX-CuQMip1XFDPZdMCVMIeUCQ-CR6Y,7176
|
71
76
|
hypern/routing/route.py,sha256=IUnWU5ra-0R9rrRDpxJiwiw7vaEefn-We2dZ4EocJGw,10403
|
72
77
|
hypern/routing/__init__.py,sha256=U4xW5fDRsn03z4cVLT4dJHHGGU6SVxyv2DL86LXodeE,162
|
@@ -80,5 +85,5 @@ hypern/ws/route.py,sha256=fGQ2RC708MPOiiIHPUo8aZ-oK379TTAyQYm4htNA5jM,803
|
|
80
85
|
hypern/ws/__init__.py,sha256=dhRoRY683_rfPfSPM5qUczfTuyYDeuLOCFxY4hIdKt8,131
|
81
86
|
hypern/ws.py,sha256=F6SA2Z1KVnqTEX8ssvOXqCtudUS4eo30JsiIsvfbHnE,394
|
82
87
|
hypern/__init__.py,sha256=9Ww_aUQ0vJls0tOq7Yw1_TVOCRsa5bHJ-RtnSeComwk,119
|
83
|
-
hypern/hypern.cp311-win32.pyd,sha256=
|
84
|
-
hypern-0.3.
|
88
|
+
hypern/hypern.cp311-win32.pyd,sha256=E3Ub8PBC7Zp1qE5bRyUl8UMgeo7hKLuPo3Q3c24xJ08,9780736
|
89
|
+
hypern-0.3.5.dist-info/RECORD,,
|
hypern/exceptions.py
DELETED
@@ -1,107 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
from typing import Any
|
3
|
-
from hypern.enum import ErrorCode
|
4
|
-
|
5
|
-
|
6
|
-
class BaseException(Exception):
|
7
|
-
def __init__(self, msg: str = "", *args: Any) -> None:
|
8
|
-
super().__init__(*args)
|
9
|
-
self.msg = msg
|
10
|
-
self.status = 400
|
11
|
-
self.error_code = ErrorCode.UNKNOWN_ERROR
|
12
|
-
|
13
|
-
|
14
|
-
class BadRequest(BaseException):
|
15
|
-
def __init__(
|
16
|
-
self,
|
17
|
-
msg: str = "Bad request",
|
18
|
-
error_code: str = ErrorCode.BAD_REQUEST,
|
19
|
-
*args: Any,
|
20
|
-
) -> None:
|
21
|
-
super().__init__(msg, *args)
|
22
|
-
self.error_code = error_code
|
23
|
-
|
24
|
-
|
25
|
-
class ValidationError(BaseException):
|
26
|
-
def __init__(
|
27
|
-
self,
|
28
|
-
msg: str = "Validation error",
|
29
|
-
error_code: str = ErrorCode.VALIDATION_ERROR,
|
30
|
-
*args: Any,
|
31
|
-
) -> None:
|
32
|
-
super().__init__(msg, *args)
|
33
|
-
self.error_code = error_code
|
34
|
-
|
35
|
-
|
36
|
-
class Forbidden(BaseException):
|
37
|
-
def __init__(
|
38
|
-
self,
|
39
|
-
msg: str = "Forbidden",
|
40
|
-
error_code: str = ErrorCode.FORBIDDEN,
|
41
|
-
*args: Any,
|
42
|
-
) -> None:
|
43
|
-
super().__init__(msg, *args)
|
44
|
-
self.status = 403
|
45
|
-
self.error_code = error_code
|
46
|
-
|
47
|
-
|
48
|
-
class NotFound(BaseException):
|
49
|
-
def __init__(
|
50
|
-
self,
|
51
|
-
msg: str = "NotFound",
|
52
|
-
error_code: str = ErrorCode.NOT_FOUND,
|
53
|
-
*args: Any,
|
54
|
-
) -> None:
|
55
|
-
super().__init__(msg, *args)
|
56
|
-
self.status = 404
|
57
|
-
self.error_code = error_code
|
58
|
-
|
59
|
-
|
60
|
-
class MethodNotAllow(BaseException):
|
61
|
-
def __init__(
|
62
|
-
self,
|
63
|
-
msg: str = "Method not allow",
|
64
|
-
error_code: str = ErrorCode.METHOD_NOT_ALLOW,
|
65
|
-
*args: Any,
|
66
|
-
) -> None:
|
67
|
-
super().__init__(msg, *args)
|
68
|
-
self.status = 405
|
69
|
-
self.error_code = error_code
|
70
|
-
|
71
|
-
|
72
|
-
class InternalServer(BaseException):
|
73
|
-
def __init__(
|
74
|
-
self,
|
75
|
-
msg: str = "Internal server error",
|
76
|
-
error_code: str = ErrorCode.SERVER_ERROR,
|
77
|
-
*args: Any,
|
78
|
-
) -> None:
|
79
|
-
super().__init__(msg, *args)
|
80
|
-
self.status = 500
|
81
|
-
self.error_code = error_code
|
82
|
-
|
83
|
-
|
84
|
-
class Unauthorized(BaseException):
|
85
|
-
def __init__(
|
86
|
-
self,
|
87
|
-
msg: str = "Unauthorized",
|
88
|
-
error_code: str = ErrorCode.UNAUTHORIZED,
|
89
|
-
*args: Any,
|
90
|
-
) -> None:
|
91
|
-
super().__init__(msg, *args)
|
92
|
-
self.status = 401
|
93
|
-
self.error_code = error_code
|
94
|
-
|
95
|
-
|
96
|
-
class InvalidPortNumber(Exception):
|
97
|
-
pass
|
98
|
-
|
99
|
-
|
100
|
-
class OutOfScopeApplicationException(Exception):
|
101
|
-
pass
|
102
|
-
|
103
|
-
|
104
|
-
class DBFieldValidationError(ValueError):
|
105
|
-
"""Custom exception for field validation errors."""
|
106
|
-
|
107
|
-
pass
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|