hypern 0.3.3__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.
Files changed (42) hide show
  1. hypern/application.py +0 -2
  2. hypern/{db → database}/sql/field.py +2 -1
  3. hypern/{db → database}/sql/model.py +1 -1
  4. hypern/{db → database}/sql/query.py +46 -21
  5. hypern/exceptions/__init__.py +34 -0
  6. hypern/exceptions/base.py +62 -0
  7. hypern/exceptions/common.py +12 -0
  8. hypern/exceptions/errors.py +15 -0
  9. hypern/exceptions/formatters.py +56 -0
  10. hypern/exceptions/http.py +76 -0
  11. hypern/hypern.cp311-win32.pyd +0 -0
  12. hypern/hypern.pyi +0 -1
  13. hypern/middleware/security.py +6 -6
  14. hypern/processpool.py +24 -6
  15. hypern/response/response.py +8 -0
  16. hypern/routing/dispatcher.py +4 -5
  17. hypern/routing/parser.py +11 -13
  18. {hypern-0.3.3.dist-info → hypern-0.3.5.dist-info}/METADATA +2 -1
  19. {hypern-0.3.3.dist-info → hypern-0.3.5.dist-info}/RECORD +41 -36
  20. hypern/exceptions.py +0 -107
  21. /hypern/{db → database}/__init__.py +0 -0
  22. /hypern/{db → database}/addons/__init__.py +0 -0
  23. /hypern/{db → database}/addons/sqlalchemy/__init__.py +0 -0
  24. /hypern/{db → database}/addons/sqlalchemy/fields/__init__.py +0 -0
  25. /hypern/{db → database}/addons/sqlalchemy/fields/color.py +0 -0
  26. /hypern/{db → database}/addons/sqlalchemy/fields/daterange.py +0 -0
  27. /hypern/{db → database}/addons/sqlalchemy/fields/datetime.py +0 -0
  28. /hypern/{db → database}/addons/sqlalchemy/fields/encrypted.py +0 -0
  29. /hypern/{db → database}/addons/sqlalchemy/fields/password.py +0 -0
  30. /hypern/{db → database}/addons/sqlalchemy/fields/ts_vector.py +0 -0
  31. /hypern/{db → database}/addons/sqlalchemy/fields/unicode.py +0 -0
  32. /hypern/{db → database}/addons/sqlalchemy/repository.py +0 -0
  33. /hypern/{db → database}/nosql/__init__.py +0 -0
  34. /hypern/{db → database}/nosql/addons/__init__.py +0 -0
  35. /hypern/{db → database}/nosql/addons/color.py +0 -0
  36. /hypern/{db → database}/nosql/addons/daterange.py +0 -0
  37. /hypern/{db → database}/nosql/addons/encrypted.py +0 -0
  38. /hypern/{db → database}/nosql/addons/password.py +0 -0
  39. /hypern/{db → database}/nosql/addons/unicode.py +0 -0
  40. /hypern/{db → database}/sql/__init__.py +0 -0
  41. {hypern-0.3.3.dist-info → hypern-0.3.5.dist-info}/WHEEL +0 -0
  42. {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}(id) ON DELETE {field.on_delete} ON UPDATE {field.on_update}"
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("__", ".") # Handle Django-style field references
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"{field} {op_map[op]} {param_name}", [f"%{value}%"]
465
+ return f"{combine_field_name} {op_map[op]} {param_name}", [f"%{value}%"]
462
466
  elif op == "startswith":
463
- return f"{field} {op_map[op]} {param_name}", [f"{value}%"]
467
+ return f"{combine_field_name} {op_map[op]} {param_name}", [f"{value}%"]
464
468
  elif op == "endswith":
465
- return f"{field} {op_map[op]} {param_name}", [f"%{value}"]
469
+ return f"{combine_field_name} {op_map[op]} {param_name}", [f"%{value}"]
466
470
  elif op == "isnull":
467
- return f"{field} {Operator.IS_NULL.value if value else Operator.IS_NOT_NULL.value}", []
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"{field} {op_map[op]} {param_name} AND {param_name}", [value[0], value[1]]
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"{field} {op_map[op]} ({placeholders})", list(value)
476
+ return f"{combine_field_name} {op_map[op]} ({placeholders})", list(value)
473
477
  else:
474
- return f"{field} {op_map[op]} {param_name}", [value]
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 join(self, table: str, on: Union[str, Expression], join_type: Union[str, JoinType] = JoinType.INNER) -> "QuerySet":
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} {table} ON {on.sql}")
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} {table} ON {on}")
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(str(field))
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(str(field))
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
- select_clause += " " + ", ".join(self.query_parts["select"])
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,12 @@
1
+ class InvalidPortNumber(Exception):
2
+ pass
3
+
4
+
5
+ class OutOfScopeApplicationException(Exception):
6
+ pass
7
+
8
+
9
+ class DBFieldValidationError(ValueError):
10
+ """Custom exception for field validation errors."""
11
+
12
+ pass
@@ -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
+ )
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
@@ -9,7 +9,7 @@ from typing import Any, Dict, List, Optional
9
9
 
10
10
  import jwt
11
11
 
12
- from hypern.exceptions import Forbidden, Unauthorized
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 Unauthorized("Token has expired")
94
+ raise UnauthorizedException(details={"message": "Token has expired"})
95
95
  except jwt.InvalidTokenError:
96
- raise Unauthorized("Invalid token")
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 Unauthorized("Missing or invalid authorization header")
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 Unauthorized as e:
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 Forbidden("CSRF token missing or invalid")
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 _ in range(processes):
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
- def initialize_event_loop():
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
- return loop
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)
@@ -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)
@@ -10,7 +10,7 @@ import typing
10
10
  import orjson
11
11
  from pydantic import BaseModel
12
12
 
13
- from hypern.exceptions import BaseException
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, BaseException):
60
- _res["error_code"] = e.error_code
61
- _res["message"] = e.msg
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 BadRequest
13
- from hypern.exceptions import ValidationError as HypernValidationError
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 BadRequest(msg="Backend Error: Invalid parameter type, must be query_params, path_params or form_data.")
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 HypernValidationError(
57
- msg=orjson.dumps(
58
- [
59
- {
60
- "field": get(item, "loc")[0],
61
- "msg": get(item, "msg"),
62
- }
63
- for item in invalid_fields
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
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.3.dist-info/METADATA,sha256=VDUnlR9iin2qEAagUwnOdvZL1vC4dMeNM1oBrwl-rZ8,3819
2
- hypern-0.3.3.dist-info/WHEEL,sha256=cIWnBshq9UErEeNtadr9w7TUZu32emQUVzK31LjJSaM,92
3
- hypern-0.3.3.dist-info/licenses/LICENSE,sha256=qbYKAIJLS6jYg5hYncKE7OtWmqOtpVTvKNkwOa0Iwwg,1328
4
- hypern/application.py,sha256=TZKwSHpWSOaValZtogmMO18l8U_xOpFN7cARMFK1hns,18432
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
+ 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=rWQpWjnkLY51HBpKcWkFaVGCKoC_EiFUPrIBBfLh-eA,2608
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=4qHekIzH-I6CYch8PwYMus9Ou-JI901kgaLOGsnlOog,9389
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=d9Qf2UNMN8wz-MLnG2wRb0Vgf55_IGZAje5hbc2T_HQ,7539
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=RFV4turo1dBv40NlzzosjwaZLC24igdyq6twY2IXCUE,2968
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=-dnboAraPic8asf503PxwmDuxhNllUO5h97_DGmbER4,4582
70
+ hypern/response/response.py,sha256=Jrkpk5KOmE4WjsAh1G0hUuPHfOPxU6eeR3J2fRIhusU,4871
66
71
  hypern/response/__init__.py,sha256=_w3u3TDNuYx5ejnnN1unqnTY8NlBgUATQi6wepEB_FQ,226
67
- hypern/routing/dispatcher.py,sha256=oQsbOTkjE5roFCl6k58oCW9lEGR_sY5tBoXSJIDgh0w,2508
72
+ hypern/routing/dispatcher.py,sha256=NAVjILlEJjYrixJZ4CO4N1CKkuqbk4TGZOjnQNTTEu4,2461
68
73
  hypern/routing/endpoint.py,sha256=RKVhvqOEGL9IKBXQ3KJgPi9bgJj9gfWC5BdZc5U_atc,1026
69
- hypern/routing/parser.py,sha256=R-4lcN9Ha1iMeAjlqDe8HwkjjMVG-c-ubQLZyWKXj6M,3554
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=Ll5-Tkxj7nY9SeZGlE4R7nQk_7nsn53s6CViqy1cpLE,9836032
84
- hypern-0.3.3.dist-info/RECORD,,
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