hypern 0.3.3__cp312-cp312-win32.whl → 0.3.5__cp312-cp312-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/application.py +0 -2
- hypern/{db → database}/sql/field.py +2 -1
- hypern/{db → database}/sql/model.py +1 -1
- hypern/{db → database}/sql/query.py +46 -21
- 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.cp312-win32.pyd +0 -0
- hypern/hypern.pyi +0 -1
- 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.3.dist-info → hypern-0.3.5.dist-info}/METADATA +2 -1
- {hypern-0.3.3.dist-info → hypern-0.3.5.dist-info}/RECORD +41 -36
- 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-0.3.3.dist-info → hypern-0.3.5.dist-info}/WHEEL +0 -0
- {hypern-0.3.3.dist-info → hypern-0.3.5.dist-info}/licenses/LICENSE +0 -0
hypern/application.py
CHANGED
@@ -436,8 +436,6 @@ class Hypern:
|
|
436
436
|
server.set_auto_compression(enabled=self.auto_compression)
|
437
437
|
server.set_mem_pool_capacity(min_capacity=self.args.min_capacity, max_capacity=self.args.max_capacity)
|
438
438
|
|
439
|
-
server.optimize_routes()
|
440
|
-
|
441
439
|
if self.database_config:
|
442
440
|
server.set_database_config(config=self.database_config)
|
443
441
|
if self.start_up_handler:
|
@@ -437,7 +437,7 @@ class ArrayField(Field):
|
|
437
437
|
class ForeignKey(Field):
|
438
438
|
"""Foreign key field representing a relationship to another model."""
|
439
439
|
|
440
|
-
def __init__(self, to_model: str, on_delete: str = "CASCADE", on_update: str = "CASCADE", **kwargs):
|
440
|
+
def __init__(self, to_model: str, related_field: str, on_delete: str = "CASCADE", on_update: str = "CASCADE", **kwargs):
|
441
441
|
"""
|
442
442
|
Initialize a foreign key field.
|
443
443
|
|
@@ -455,6 +455,7 @@ class ForeignKey(Field):
|
|
455
455
|
self.to_model = to_model
|
456
456
|
self.on_delete = on_delete
|
457
457
|
self.on_update = on_update
|
458
|
+
self.related_field = related_field
|
458
459
|
|
459
460
|
def to_py_type(self, value: Any) -> Optional[int]:
|
460
461
|
"""
|
@@ -109,7 +109,7 @@ class Model(metaclass=MetaModel):
|
|
109
109
|
|
110
110
|
@classmethod
|
111
111
|
def _get_foreign_key_sql(cls, name, field) -> str:
|
112
|
-
return f"FOREIGN KEY ({name}) REFERENCES {field.to_model}(
|
112
|
+
return f"FOREIGN KEY ({name}) REFERENCES {field.to_model}({field.related_field}) ON DELETE {field.on_delete} ON UPDATE {field.on_update}"
|
113
113
|
|
114
114
|
def save(self):
|
115
115
|
query_object = QuerySet(self)
|
@@ -1,5 +1,6 @@
|
|
1
1
|
from enum import Enum
|
2
2
|
from typing import Any, Dict, List, Tuple, Union
|
3
|
+
from hypern.database.sql.field import ForeignKey
|
3
4
|
|
4
5
|
|
5
6
|
class JoinType(Enum):
|
@@ -98,7 +99,7 @@ class F:
|
|
98
99
|
"""Class for creating SQL expressions and column references"""
|
99
100
|
|
100
101
|
def __init__(self, field: str):
|
101
|
-
self.field = field.replace("__", ".")
|
102
|
+
self.field = field.replace("__", ".")
|
102
103
|
|
103
104
|
def __add__(self, other):
|
104
105
|
if isinstance(other, F):
|
@@ -356,6 +357,7 @@ class QuerySet:
|
|
356
357
|
self._nowait = False
|
357
358
|
self._skip_locked = False
|
358
359
|
self._param_counter = 1
|
360
|
+
self._selected_related = set()
|
359
361
|
|
360
362
|
def __get_next_param(self):
|
361
363
|
param_name = f"${self._param_counter}"
|
@@ -372,11 +374,12 @@ class QuerySet:
|
|
372
374
|
new_qs._nowait = self._nowait
|
373
375
|
new_qs._skip_locked = self._skip_locked
|
374
376
|
new_qs._param_counter = self._param_counter
|
377
|
+
new_qs._selected_related = self._selected_related.copy()
|
375
378
|
return new_qs
|
376
379
|
|
377
380
|
def select(self, *fields, distinct: bool = False) -> "QuerySet":
|
378
381
|
qs = self.clone()
|
379
|
-
qs.query_parts["select"] = list(fields)
|
382
|
+
qs.query_parts["select"] = list(map(lambda x: f"{qs.model.Meta.table_name}.{x}" if x != "*" else x, fields))
|
380
383
|
qs._distinct = distinct
|
381
384
|
return qs
|
382
385
|
|
@@ -426,10 +429,10 @@ class QuerySet:
|
|
426
429
|
return self._process_standard_value(field, op, value)
|
427
430
|
|
428
431
|
def _process_f_value(self, field: str, op: str, value: F) -> Tuple[str, List]:
|
429
|
-
return f"{field} {op} {value.field}", []
|
432
|
+
return f"{self.model.Meta.table_name}.{field} {op} {value.field}", []
|
430
433
|
|
431
434
|
def _process_expression_value(self, field: str, op: str, value: Expression) -> Tuple[str, List]:
|
432
|
-
return f"{field} {op} {value.sql}", value.params
|
435
|
+
return f"{self.model.Meta.table_name}.{field} {op} {value.sql}", value.params
|
433
436
|
|
434
437
|
def _process_standard_value(self, field: str, op: str, value: Any) -> Tuple[str, List]:
|
435
438
|
op_map = {
|
@@ -453,25 +456,26 @@ class QuerySet:
|
|
453
456
|
return self._process_op_map_value(field, op, value, op_map)
|
454
457
|
else:
|
455
458
|
param_name = self.__get_next_param()
|
456
|
-
return f"{field} = {param_name}", [value]
|
459
|
+
return f"{self.model.Meta.table_name}.{field} = {param_name}", [value]
|
457
460
|
|
458
461
|
def _process_op_map_value(self, field: str, op: str, value: Any, op_map: dict) -> Tuple[str, List]:
|
459
462
|
param_name = self.__get_next_param()
|
463
|
+
combine_field_name = f"{self.model.Meta.table_name}.{field}"
|
460
464
|
if op in ("contains", "icontains"):
|
461
|
-
return f"{
|
465
|
+
return f"{combine_field_name} {op_map[op]} {param_name}", [f"%{value}%"]
|
462
466
|
elif op == "startswith":
|
463
|
-
return f"{
|
467
|
+
return f"{combine_field_name} {op_map[op]} {param_name}", [f"{value}%"]
|
464
468
|
elif op == "endswith":
|
465
|
-
return f"{
|
469
|
+
return f"{combine_field_name} {op_map[op]} {param_name}", [f"%{value}"]
|
466
470
|
elif op == "isnull":
|
467
|
-
return f"{
|
471
|
+
return f"{combine_field_name} {Operator.IS_NULL.value if value else Operator.IS_NOT_NULL.value}", []
|
468
472
|
elif op == "between":
|
469
|
-
return f"{
|
473
|
+
return f"{combine_field_name} {op_map[op]} {param_name} AND {param_name}", [value[0], value[1]]
|
470
474
|
elif op in ("in", "not_in"):
|
471
475
|
placeholders = ",".join(["{param_name}" for _ in value])
|
472
|
-
return f"{
|
476
|
+
return f"{combine_field_name} {op_map[op]} ({placeholders})", list(value)
|
473
477
|
else:
|
474
|
-
return f"{
|
478
|
+
return f"{combine_field_name} {op_map[op]} {param_name}", [value]
|
475
479
|
|
476
480
|
def where(self, *args, **kwargs) -> "QuerySet":
|
477
481
|
qs = self.clone()
|
@@ -535,22 +539,36 @@ class QuerySet:
|
|
535
539
|
elif field.startswith("-"):
|
536
540
|
order_parts.append(f"{field[1:]} DESC")
|
537
541
|
else:
|
538
|
-
order_parts.append(f"{field} ASC")
|
542
|
+
order_parts.append(f"{qs.model.Meta.table_name}.{field} ASC")
|
539
543
|
|
540
544
|
qs.query_parts["order_by"] = order_parts
|
541
545
|
return qs
|
542
546
|
|
543
|
-
def
|
547
|
+
def select_related(self, *fields) -> "QuerySet":
|
548
|
+
"""
|
549
|
+
Include related objects in the query results.
|
550
|
+
|
551
|
+
Args:
|
552
|
+
*fields: Names of foreign key fields to include
|
553
|
+
"""
|
554
|
+
qs = self.clone()
|
555
|
+
for field in fields:
|
556
|
+
if field in qs.model._fields and isinstance(qs.model._fields[field], ForeignKey):
|
557
|
+
qs._selected_related.add(field)
|
558
|
+
return qs
|
559
|
+
|
560
|
+
def join(self, table: Any, on: Union[str, Expression], join_type: Union[str, JoinType] = JoinType.INNER) -> "QuerySet":
|
544
561
|
qs = self.clone()
|
562
|
+
joined_table = table.Meta.table_name if hasattr(table, "Meta") else table
|
545
563
|
|
546
564
|
if isinstance(join_type, JoinType):
|
547
565
|
join_type = join_type.value
|
548
566
|
|
549
567
|
if isinstance(on, Expression):
|
550
|
-
qs.query_parts["joins"].append(f"{join_type} {
|
568
|
+
qs.query_parts["joins"].append(f"{join_type} {joined_table} ON {on.sql}")
|
551
569
|
qs.params.extend(on.params)
|
552
570
|
else:
|
553
|
-
qs.query_parts["joins"].append(f"{join_type} {
|
571
|
+
qs.query_parts["joins"].append(f"{join_type} {joined_table} ON {on}")
|
554
572
|
|
555
573
|
return qs
|
556
574
|
|
@@ -565,7 +583,7 @@ class QuerySet:
|
|
565
583
|
group_parts.append(field.sql)
|
566
584
|
qs.params.extend(field.params)
|
567
585
|
else:
|
568
|
-
group_parts.append(
|
586
|
+
group_parts.append(f"{qs.model.Meta.table_name}.{field}")
|
569
587
|
|
570
588
|
qs.query_parts["group_by"] = group_parts
|
571
589
|
return qs
|
@@ -607,7 +625,7 @@ class QuerySet:
|
|
607
625
|
partition_parts.append(field.sql)
|
608
626
|
qs.params.extend(field.params)
|
609
627
|
else:
|
610
|
-
partition_parts.append(
|
628
|
+
partition_parts.append(f"{self.model.Meta.table_name}.{field}")
|
611
629
|
return f"PARTITION BY {', '.join(partition_parts)}"
|
612
630
|
|
613
631
|
def _process_order_by(self, order_by: List, qs: "QuerySet") -> str:
|
@@ -619,9 +637,9 @@ class QuerySet:
|
|
619
637
|
order_parts.append(field.sql)
|
620
638
|
qs.params.extend(field.params)
|
621
639
|
elif field.startswith("-"):
|
622
|
-
order_parts.append(f"{field[1:]} DESC")
|
640
|
+
order_parts.append(f"{qs.model.Meta.table_name}.{field[1:]} DESC")
|
623
641
|
else:
|
624
|
-
order_parts.append(f"{field} ASC")
|
642
|
+
order_parts.append(f"{qs.model.Meta.table_name}.{field} ASC")
|
625
643
|
return f"ORDER BY {', '.join(order_parts)}"
|
626
644
|
|
627
645
|
def limit(self, limit: int) -> "QuerySet":
|
@@ -726,7 +744,14 @@ class QuerySet:
|
|
726
744
|
select_clause = "SELECT"
|
727
745
|
if self._distinct:
|
728
746
|
select_clause += " DISTINCT"
|
729
|
-
|
747
|
+
|
748
|
+
# Add selected fields
|
749
|
+
select_related_fields = []
|
750
|
+
for field in self._selected_related:
|
751
|
+
related_table = self.model._fields[field].to_model
|
752
|
+
select_related_fields.append(f"{related_table}.*")
|
753
|
+
|
754
|
+
select_clause += " " + ", ".join(self.query_parts["select"] + select_related_fields)
|
730
755
|
parts.append(select_clause)
|
731
756
|
|
732
757
|
def _add_from_clause(self, parts):
|
@@ -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.cp312-win32.pyd
CHANGED
Binary file
|
hypern/hypern.pyi
CHANGED
@@ -191,7 +191,6 @@ class Server:
|
|
191
191
|
def set_auto_compression(self, enabled: bool) -> None: ...
|
192
192
|
def set_database_config(self, config: DatabaseConfig) -> None: ...
|
193
193
|
def set_mem_pool_capacity(self, min_capacity: int, max_capacity: int) -> None: ...
|
194
|
-
def optimize_routes(self) -> None: ...
|
195
194
|
|
196
195
|
class Route:
|
197
196
|
path: str
|
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,7 +1,7 @@
|
|
1
|
-
hypern-0.3.
|
2
|
-
hypern-0.3.
|
3
|
-
hypern-0.3.
|
4
|
-
hypern/application.py,sha256=
|
1
|
+
hypern-0.3.5.dist-info/METADATA,sha256=Lhxf8FMnS1RvE17wHATRz6F1-v0S3IYcXrGmzWkm3Jg,3850
|
2
|
+
hypern-0.3.5.dist-info/WHEEL,sha256=SK_cql1gpDHx6aBV-LOSvGbTt4TUC8AJJOzjOP2tdpI,92
|
3
|
+
hypern-0.3.5.dist-info/licenses/LICENSE,sha256=qbYKAIJLS6jYg5hYncKE7OtWmqOtpVTvKNkwOa0Iwwg,1328
|
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
|
7
7
|
hypern/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -13,38 +13,43 @@ 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=_5Et9pPmTI_bhUFN7PgdSiozip-Iv0eH2491J_WUvXU,20009
|
36
|
-
hypern/db/sql/model.py,sha256=5GtH3v6LfYIENlBN1NpIjZpsYFQyo36jX-b6jO1lNdU,3894
|
37
|
-
hypern/db/sql/query.py,sha256=_kBZbdQckYyawSIcuaUrE4C6eWgpeWyK1JVR3JWdh60,31964
|
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
|
45
50
|
hypern/gateway/service.py,sha256=PkRaM08olqM_j_4wRjEJCR8X8ZysAF2WOcfhWjaX2eo,1701
|
46
51
|
hypern/gateway/__init__.py,sha256=TpFWtqnJerW1-jCWq5fjypJcw9Y6ytyrkvkzby1Eg0E,235
|
47
|
-
hypern/hypern.pyi,sha256=
|
52
|
+
hypern/hypern.pyi,sha256=yXaWGPt598gwPN-CT1ARDdwOSqryZCBFuDLQC8gRd1U,9345
|
48
53
|
hypern/i18n/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
49
54
|
hypern/logging/logger.py,sha256=WACam_IJiCMXX0hGVKMGSxUQpY4DgAXy7M1dD3q-Z9s,3256
|
50
55
|
hypern/logging/__init__.py,sha256=6eVriyncsJ4J73fGYhoejv9MX7aGTkRezTpPxO4DX1I,52
|
@@ -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.cp312-win32.pyd,sha256=
|
84
|
-
hypern-0.3.
|
88
|
+
hypern/hypern.cp312-win32.pyd,sha256=vNJOrF5I7_yOmuisz19FaH0C2M5CsHxv1SWLm-CzQOk,9797632
|
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
|