hypern 0.3.0__cp310-cp310-win_amd64.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 (69) hide show
  1. hypern/__init__.py +4 -0
  2. hypern/application.py +405 -0
  3. hypern/args_parser.py +59 -0
  4. hypern/auth/__init__.py +0 -0
  5. hypern/auth/authorization.py +2 -0
  6. hypern/background.py +4 -0
  7. hypern/caching/__init__.py +0 -0
  8. hypern/caching/base/__init__.py +8 -0
  9. hypern/caching/base/backend.py +3 -0
  10. hypern/caching/base/key_maker.py +8 -0
  11. hypern/caching/cache_manager.py +56 -0
  12. hypern/caching/cache_tag.py +10 -0
  13. hypern/caching/custom_key_maker.py +11 -0
  14. hypern/caching/redis_backend.py +3 -0
  15. hypern/cli/__init__.py +0 -0
  16. hypern/cli/commands.py +0 -0
  17. hypern/config.py +149 -0
  18. hypern/datastructures.py +40 -0
  19. hypern/db/__init__.py +0 -0
  20. hypern/db/nosql/__init__.py +25 -0
  21. hypern/db/nosql/addons/__init__.py +4 -0
  22. hypern/db/nosql/addons/color.py +16 -0
  23. hypern/db/nosql/addons/daterange.py +30 -0
  24. hypern/db/nosql/addons/encrypted.py +53 -0
  25. hypern/db/nosql/addons/password.py +134 -0
  26. hypern/db/nosql/addons/unicode.py +10 -0
  27. hypern/db/sql/__init__.py +179 -0
  28. hypern/db/sql/addons/__init__.py +14 -0
  29. hypern/db/sql/addons/color.py +16 -0
  30. hypern/db/sql/addons/daterange.py +23 -0
  31. hypern/db/sql/addons/datetime.py +22 -0
  32. hypern/db/sql/addons/encrypted.py +58 -0
  33. hypern/db/sql/addons/password.py +171 -0
  34. hypern/db/sql/addons/ts_vector.py +46 -0
  35. hypern/db/sql/addons/unicode.py +15 -0
  36. hypern/db/sql/repository.py +290 -0
  37. hypern/enum.py +13 -0
  38. hypern/exceptions.py +97 -0
  39. hypern/hypern.cp310-win_amd64.pyd +0 -0
  40. hypern/hypern.pyi +295 -0
  41. hypern/i18n/__init__.py +0 -0
  42. hypern/logging/__init__.py +3 -0
  43. hypern/logging/logger.py +82 -0
  44. hypern/middleware/__init__.py +5 -0
  45. hypern/middleware/base.py +18 -0
  46. hypern/middleware/cors.py +38 -0
  47. hypern/middleware/i18n.py +1 -0
  48. hypern/middleware/limit.py +176 -0
  49. hypern/openapi/__init__.py +5 -0
  50. hypern/openapi/schemas.py +53 -0
  51. hypern/openapi/swagger.py +3 -0
  52. hypern/processpool.py +137 -0
  53. hypern/py.typed +0 -0
  54. hypern/reload.py +60 -0
  55. hypern/response/__init__.py +3 -0
  56. hypern/response/response.py +134 -0
  57. hypern/routing/__init__.py +4 -0
  58. hypern/routing/dispatcher.py +67 -0
  59. hypern/routing/endpoint.py +30 -0
  60. hypern/routing/parser.py +100 -0
  61. hypern/routing/route.py +284 -0
  62. hypern/scheduler.py +5 -0
  63. hypern/security.py +44 -0
  64. hypern/worker.py +30 -0
  65. hypern/ws.py +16 -0
  66. hypern-0.3.0.dist-info/METADATA +128 -0
  67. hypern-0.3.0.dist-info/RECORD +69 -0
  68. hypern-0.3.0.dist-info/WHEEL +4 -0
  69. hypern-0.3.0.dist-info/licenses/LICENSE +24 -0
@@ -0,0 +1,100 @@
1
+ # -*- coding: utf-8 -*-
2
+ from __future__ import annotations
3
+
4
+ import inspect
5
+ import typing
6
+
7
+ import orjson
8
+ from pydantic import BaseModel, ValidationError
9
+ from pydash import get
10
+
11
+ from hypern.auth.authorization import Authorization
12
+ from hypern.exceptions import BadRequest
13
+ from hypern.exceptions import ValidationError as HypernValidationError
14
+ from hypern.hypern import Request
15
+
16
+
17
+ class ParamParser:
18
+ def __init__(self, request: Request):
19
+ self.request = request
20
+
21
+ def parse_data_by_name(self, param_name: str) -> dict:
22
+ param_name = param_name.lower()
23
+ data_parsers = {
24
+ "query_params": self._parse_query_params,
25
+ "path_params": self._parse_path_params,
26
+ "form_data": self._parse_form_data,
27
+ }
28
+
29
+ parser = data_parsers.get(param_name)
30
+ if not parser:
31
+ raise BadRequest(msg="Backend Error: Invalid parameter type, must be query_params, path_params or form_data.")
32
+ return parser()
33
+
34
+ def _parse_query_params(self) -> dict:
35
+ query_params = self.request.query_params.to_dict()
36
+ return {k: v[0] for k, v in query_params.items()}
37
+
38
+ def _parse_path_params(self) -> dict:
39
+ return lambda: dict(self.request.path_params.items())
40
+
41
+ def _parse_form_data(self) -> dict:
42
+ return self.request.json()
43
+
44
+
45
+ class InputHandler:
46
+ def __init__(self, request):
47
+ self.request = request
48
+ self.param_parser = ParamParser(request)
49
+
50
+ async def parse_pydantic_model(self, param_name: str, model_class: typing.Type[BaseModel]) -> BaseModel:
51
+ try:
52
+ data = self.param_parser.parse_data_by_name(param_name)
53
+ return model_class(**data)
54
+ except ValidationError as e:
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"),
66
+ )
67
+
68
+ async def handle_special_params(self, param_name: str) -> typing.Any:
69
+ special_params = {
70
+ "request": lambda: self.request,
71
+ }
72
+ return special_params.get(param_name, lambda: None)()
73
+
74
+ async def get_input_handler(self, signature: inspect.Signature, inject: typing.Dict[str, typing.Any]) -> typing.Dict[str, typing.Any]:
75
+ """
76
+ Parse the request data and return the kwargs for the handler
77
+ """
78
+ kwargs = {}
79
+
80
+ for param in signature.parameters.values():
81
+ name = param.name
82
+ ptype = param.annotation
83
+
84
+ # Handle Pydantic models
85
+ if isinstance(ptype, type) and issubclass(ptype, BaseModel):
86
+ kwargs[name] = await self.parse_pydantic_model(name, ptype)
87
+ continue
88
+
89
+ # Handle Authorization
90
+ if isinstance(ptype, type) and issubclass(ptype, Authorization):
91
+ kwargs[name] = await ptype().validate(self.request)
92
+ continue
93
+
94
+ # Handle special parameters
95
+ special_value = await self.handle_special_params(name)
96
+ if special_value is not None:
97
+ kwargs[name] = special_value
98
+ if name in inject:
99
+ kwargs[name] = inject[name]
100
+ return kwargs
@@ -0,0 +1,284 @@
1
+ # -*- coding: utf-8 -*-
2
+ import asyncio
3
+ import inspect
4
+ from enum import Enum
5
+ from typing import Any, Callable, Dict, List, Type, Union, get_args, get_origin
6
+
7
+ import yaml # type: ignore
8
+ from pydantic import BaseModel
9
+ from pydantic.fields import FieldInfo
10
+
11
+ from hypern.auth.authorization import Authorization
12
+ from hypern.datastructures import HTTPMethod
13
+ from hypern.hypern import FunctionInfo, Request, Router
14
+ from hypern.hypern import Route as InternalRoute
15
+
16
+ from .dispatcher import dispatch
17
+
18
+
19
+ def get_field_type(field):
20
+ return field.outer_type_
21
+
22
+
23
+ def pydantic_to_swagger(model: type[BaseModel] | dict):
24
+ if isinstance(model, dict):
25
+ # Handle the case when a dict is passed instead of a Pydantic model
26
+ schema = {}
27
+ for name, field_type in model.items():
28
+ schema[name] = _process_field(name, field_type)
29
+ return schema
30
+
31
+ schema = {
32
+ model.__name__: {
33
+ "type": "object",
34
+ "properties": {},
35
+ }
36
+ }
37
+
38
+ for name, field in model.model_fields.items():
39
+ schema[model.__name__]["properties"][name] = _process_field(name, field)
40
+
41
+ return schema
42
+
43
+
44
+ class SchemaProcessor:
45
+ @staticmethod
46
+ def process_union(args: tuple) -> Dict[str, Any]:
47
+ """Process Union types"""
48
+ if type(None) in args:
49
+ inner_type = next(arg for arg in args if arg is not type(None))
50
+ schema = SchemaProcessor._process_field("", inner_type)
51
+ schema["nullable"] = True
52
+ return schema
53
+ return {"oneOf": [SchemaProcessor._process_field("", arg) for arg in args]}
54
+
55
+ @staticmethod
56
+ def process_enum(annotation: Type[Enum]) -> Dict[str, Any]:
57
+ """Process Enum types"""
58
+ return {"type": "string", "enum": [e.value for e in annotation.__members__.values()]}
59
+
60
+ @staticmethod
61
+ def process_primitive(annotation: type) -> Dict[str, str]:
62
+ """Process primitive types"""
63
+ type_mapping = {int: "integer", float: "number", str: "string", bool: "boolean"}
64
+ return {"type": type_mapping.get(annotation, "object")}
65
+
66
+ @staticmethod
67
+ def process_list(annotation: type) -> Dict[str, Any]:
68
+ """Process list types"""
69
+ schema = {"type": "array"}
70
+
71
+ args = get_args(annotation)
72
+ if args:
73
+ item_type = args[0]
74
+ schema["items"] = SchemaProcessor._process_field("item", item_type)
75
+ else:
76
+ schema["items"] = {}
77
+ return schema
78
+
79
+ @staticmethod
80
+ def process_dict(annotation: type) -> Dict[str, Any]:
81
+ """Process dict types"""
82
+ schema = {"type": "object"}
83
+
84
+ args = get_args(annotation)
85
+ if args:
86
+ key_type, value_type = args
87
+ if key_type == str: # noqa: E721
88
+ schema["additionalProperties"] = SchemaProcessor._process_field("value", value_type)
89
+ return schema
90
+
91
+ @classmethod
92
+ def _process_field(cls, name: str, field: Any) -> Dict[str, Any]:
93
+ """Process a single field"""
94
+ if isinstance(field, FieldInfo):
95
+ annotation = field.annotation
96
+ else:
97
+ annotation = field
98
+
99
+ # Process Union types
100
+ origin = get_origin(annotation)
101
+ if origin is Union:
102
+ return cls.process_union(get_args(annotation))
103
+
104
+ # Process Enum types
105
+ if isinstance(annotation, type) and issubclass(annotation, Enum):
106
+ return cls.process_enum(annotation)
107
+
108
+ # Process primitive types
109
+ if annotation in {int, float, str, bool}:
110
+ return cls.process_primitive(annotation)
111
+
112
+ # Process list types
113
+ if annotation == list or origin is list: # noqa: E721
114
+ return cls.process_list(annotation)
115
+
116
+ # Process dict types
117
+ if annotation == dict or origin is dict: # noqa: E721
118
+ return cls.process_dict(annotation)
119
+
120
+ # Process Pydantic models
121
+ if isinstance(annotation, type) and issubclass(annotation, BaseModel):
122
+ return pydantic_to_swagger(annotation)
123
+
124
+ # Fallback for complex types
125
+ return {"type": "object"}
126
+
127
+
128
+ def _process_field(name: str, field: Any) -> Dict[str, Any]:
129
+ """
130
+ Process a field and return its schema representation
131
+
132
+ Args:
133
+ name: Field name
134
+ field: Field type or FieldInfo object
135
+
136
+ Returns:
137
+ Dictionary representing the JSON schema for the field
138
+ """
139
+ return SchemaProcessor._process_field(name, field)
140
+
141
+
142
+ class Route:
143
+ def __init__(
144
+ self,
145
+ path: str,
146
+ endpoint: Callable[..., Any] | None = None,
147
+ *,
148
+ name: str | None = None,
149
+ tags: List[str] | None = None,
150
+ ) -> None:
151
+ self.path = path
152
+ self.endpoint = endpoint
153
+ self.tags = tags or ["Default"]
154
+ self.name = name
155
+
156
+ self.http_methods = {
157
+ "GET": HTTPMethod.GET,
158
+ "POST": HTTPMethod.POST,
159
+ "PUT": HTTPMethod.PUT,
160
+ "DELETE": HTTPMethod.DELETE,
161
+ "PATCH": HTTPMethod.PATCH,
162
+ "HEAD": HTTPMethod.HEAD,
163
+ "OPTIONS": HTTPMethod.OPTIONS,
164
+ }
165
+ self.functional_handlers = []
166
+
167
+ def _process_authorization(self, item: type, docs: Dict) -> None:
168
+ if isinstance(item, type) and issubclass(item, Authorization):
169
+ auth_obj = item()
170
+ docs["security"] = [{auth_obj.name: []}]
171
+
172
+ def _process_model_params(self, key: str, item: type, docs: Dict) -> None:
173
+ if not (isinstance(item, type) and issubclass(item, BaseModel)):
174
+ return
175
+
176
+ if key == "form_data":
177
+ docs["requestBody"] = {"content": {"application/json": {"schema": pydantic_to_swagger(item).get(item.__name__)}}}
178
+ elif key == "query_params":
179
+ docs["parameters"] = [{"name": param, "in": "query", "schema": _process_field(param, field)} for param, field in item.model_fields.items()]
180
+ elif key == "path_params":
181
+ path_params = [
182
+ {"name": param, "in": "path", "required": True, "schema": _process_field(param, field)} for param, field in item.model_fields.items()
183
+ ]
184
+ docs.setdefault("parameters", []).extend(path_params)
185
+
186
+ def _process_response(self, response_type: type, docs: Dict) -> None:
187
+ if isinstance(response_type, type) and issubclass(response_type, BaseModel):
188
+ docs["responses"] = {
189
+ "200": {
190
+ "description": "Successful response",
191
+ "content": {"application/json": {"schema": pydantic_to_swagger(response_type).get(response_type.__name__)}},
192
+ }
193
+ }
194
+
195
+ def swagger_generate(self, signature: inspect.Signature, summary: str = "Document API") -> str:
196
+ _inputs = signature.parameters.values()
197
+ _inputs_dict = {_input.name: _input.annotation for _input in _inputs}
198
+ _docs: Dict = {"summary": summary, "tags": self.tags, "responses": [], "name": self.name}
199
+
200
+ for key, item in _inputs_dict.items():
201
+ self._process_authorization(item, _docs)
202
+ self._process_model_params(key, item, _docs)
203
+
204
+ self._process_response(signature.return_annotation, _docs)
205
+ return yaml.dump(_docs)
206
+
207
+ def _combine_path(self, path1: str, path2: str) -> str:
208
+ if path1.endswith("/") and path2.startswith("/"):
209
+ return path1 + path2[1:]
210
+ if not path1.endswith("/") and not path2.startswith("/"):
211
+ return path1 + "/" + path2
212
+ return path1 + path2
213
+
214
+ def make_internal_route(self, path, handler, method) -> InternalRoute:
215
+ is_async = asyncio.iscoroutinefunction(handler)
216
+ func_info = FunctionInfo(handler=handler, is_async=is_async)
217
+ return InternalRoute(path=path, function=func_info, method=method)
218
+
219
+ def __call__(self, app, *args: Any, **kwds: Any) -> Any:
220
+ router = Router(self.path)
221
+
222
+ # Validate handlers
223
+ if not self.endpoint and not self.functional_handlers:
224
+ raise ValueError(f"No handler found for route: {self.path}")
225
+
226
+ # Handle functional routes
227
+ for h in self.functional_handlers:
228
+ router.add_route(route=self.make_internal_route(path=h["path"], handler=h["func"], method=h["method"].upper()))
229
+ if not self.endpoint:
230
+ return router
231
+
232
+ # Handle class-based routes
233
+ for name, func in self.endpoint.__dict__.items():
234
+ if name.upper() in self.http_methods:
235
+ sig = inspect.signature(func)
236
+ doc = self.swagger_generate(sig, func.__doc__)
237
+ self.endpoint.dispatch.__doc__ = doc
238
+ endpoint_obj = self.endpoint()
239
+ router.add_route(route=self.make_internal_route(path="/", handler=endpoint_obj.dispatch, method=name.upper()))
240
+ del endpoint_obj # free up memory
241
+ return router
242
+
243
+ def add_route(
244
+ self,
245
+ path: str,
246
+ method: str,
247
+ ) -> Callable:
248
+ def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
249
+ async def functional_wrapper(request: Request, inject: Dict[str, Any]) -> Any:
250
+ return await dispatch(func, request, inject)
251
+
252
+ sig = inspect.signature(func)
253
+ functional_wrapper.__doc__ = self.swagger_generate(sig, func.__doc__)
254
+
255
+ self.functional_handlers.append(
256
+ {
257
+ "path": path,
258
+ "method": method,
259
+ "func": functional_wrapper,
260
+ }
261
+ )
262
+
263
+ return decorator
264
+
265
+ def get(self, path: str) -> Callable:
266
+ return self.add_route(path, "GET")
267
+
268
+ def post(self, path: str) -> Callable:
269
+ return self.add_route(path, "POST")
270
+
271
+ def put(self, path: str) -> Callable:
272
+ return self.add_route(path, "PUT")
273
+
274
+ def delete(self, path: str) -> Callable:
275
+ return self.add_route(path, "DELETE")
276
+
277
+ def patch(self, path: str) -> Callable:
278
+ return self.add_route(path, "PATCH")
279
+
280
+ def head(self, path: str) -> Callable:
281
+ return self.add_route(path, "HEAD")
282
+
283
+ def options(self, path: str) -> Callable:
284
+ return self.add_route(path, "OPTIONS")
hypern/scheduler.py ADDED
@@ -0,0 +1,5 @@
1
+ from .hypern import Scheduler
2
+
3
+ __all__ = [
4
+ "Scheduler",
5
+ ]
hypern/security.py ADDED
@@ -0,0 +1,44 @@
1
+ # -*- coding: utf-8 -*-
2
+ from abc import ABC, abstractmethod
3
+ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
4
+ from cryptography.hazmat.backends import default_backend
5
+ from base64 import b64encode, b64decode
6
+
7
+ import typing
8
+
9
+
10
+ class EDEngine(ABC):
11
+ @abstractmethod
12
+ def encrypt(self, data: str) -> str:
13
+ raise NotImplementedError("Method not implemented")
14
+
15
+ @abstractmethod
16
+ def decrypt(self, data: str) -> str:
17
+ raise NotImplementedError("Method not implemented")
18
+
19
+
20
+ class AESEngine(EDEngine):
21
+ def __init__(self, secret_key: bytes, iv: bytes, padding_class: typing.Type) -> None:
22
+ super().__init__()
23
+ self.secret_key = secret_key
24
+ self.iv = iv
25
+ self.padding = padding_class(128)
26
+
27
+ def encrypt(self, data: str) -> bytes:
28
+ bytes_data = data.encode("utf-8")
29
+ encryptor = Cipher(algorithms.AES(self.secret_key), modes.GCM(self.iv), backend=default_backend()).encryptor()
30
+ padder = self.padding.padder()
31
+ padded_data = padder.update(bytes_data) + padder.finalize()
32
+ enctyped_data = encryptor.update(padded_data) + encryptor.finalize()
33
+ tag = encryptor.tag
34
+ return b64encode(tag + enctyped_data)
35
+
36
+ def decrypt(self, data: bytes) -> str:
37
+ data = b64decode(data)
38
+ tag = data[:16]
39
+ encrypted_data = data[16:]
40
+ decryptor = Cipher(algorithms.AES(self.secret_key), modes.GCM(self.iv, tag), backend=default_backend()).decryptor()
41
+ unpadder = self.padding.unpadder()
42
+ decrypted_data = decryptor.update(encrypted_data) + decryptor.finalize()
43
+ unpadded_data = unpadder.update(decrypted_data) + unpadder.finalize()
44
+ return unpadded_data.decode("utf-8")
hypern/worker.py ADDED
@@ -0,0 +1,30 @@
1
+ # -*- coding: utf-8 -*-
2
+ from typing import Any
3
+ from celery import Celery
4
+ from asgiref.sync import async_to_sync
5
+
6
+
7
+ class AsyncCelery(Celery):
8
+ def __new__(cls, *args, **kwargs) -> Any:
9
+ if not hasattr(cls, "instance") or not cls.instance: # type: ignore
10
+ cls.instance = super().__new__(cls)
11
+ return cls.instance
12
+
13
+ def __init__(self, *args, **kwargs) -> None:
14
+ super().__init__(*args, **kwargs)
15
+ self.patch_task()
16
+
17
+ def patch_task(self) -> None:
18
+ TaskBase = self.Task
19
+
20
+ class ContextTask(TaskBase): # type: ignore
21
+ abstract = True
22
+
23
+ def _run(self, *args, **kwargs):
24
+ result = async_to_sync(TaskBase.__call__)(self, *args, **kwargs)
25
+ return result
26
+
27
+ def __call__(self, *args, **kwargs):
28
+ return self._run(*args, **kwargs)
29
+
30
+ self.Task = ContextTask
hypern/ws.py ADDED
@@ -0,0 +1,16 @@
1
+ from .hypern import WebsocketRoute as WebsocketRouteInternal, WebSocketSession
2
+
3
+
4
+ class WebsocketRoute:
5
+ def __init__(self) -> None:
6
+ self.routes = []
7
+
8
+ def on(self, path):
9
+ def wrapper(func):
10
+ self.routes.append(WebsocketRouteInternal(path, func))
11
+ return func
12
+
13
+ return wrapper
14
+
15
+
16
+ __all__ = ["WebsocketRoute", "WebSocketSession"]
@@ -0,0 +1,128 @@
1
+ Metadata-Version: 2.4
2
+ Name: hypern
3
+ Version: 0.3.0
4
+ Classifier: Programming Language :: Rust
5
+ Classifier: Programming Language :: Python :: Implementation :: CPython
6
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
7
+ Requires-Dist: sqlalchemy[asyncio] ==2.0.31
8
+ Requires-Dist: pydantic[email] ==2.8.2
9
+ Requires-Dist: passlib ==1.7.4
10
+ Requires-Dist: pyjwt ==2.8.0
11
+ Requires-Dist: pydash ==8.0.3
12
+ Requires-Dist: sentry-sdk ==2.11.0
13
+ Requires-Dist: pydantic-settings ==2.3.4
14
+ Requires-Dist: celery ==5.4.0
15
+ Requires-Dist: asgiref ==3.8.1
16
+ Requires-Dist: psycopg ==3.2.3
17
+ Requires-Dist: pyyaml ==6.0.2
18
+ Requires-Dist: mongoengine ==0.29.1
19
+ Requires-Dist: argon2-cffi ==23.1.0
20
+ Requires-Dist: bcrypt ==4.2.0
21
+ Requires-Dist: orjson ==3.10.11
22
+ Requires-Dist: multiprocess ==0.70.17
23
+ Requires-Dist: uvloop ==0.21.0 ; sys_platform != 'win32' and platform_python_implementation == 'CPython' and platform_machine != 'armv7l'
24
+ Requires-Dist: cryptography ==43.0.3
25
+ Requires-Dist: watchdog ==6.0.0
26
+ License-File: LICENSE
27
+ Summary: A Fast Async Python backend with a Rust runtime.
28
+ Author-email: Martin Dang <vannghiem848@gmail.com>
29
+ Requires-Python: >=3.10
30
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
31
+
32
+
33
+ # Hypern
34
+
35
+ Hypern: A Versatile Python and Rust Framework
36
+
37
+ Hypern is a flexible, open-source framework built on the [Axum](https://github.com/tokio-rs/axum), designed to jumpstart your high-performance web development endeavors. By providing a pre-configured structure and essential components, Hypern empowers you to rapidly develop custom web applications that leverage the combined power of Python and Rust.
38
+
39
+ With Hypern, you can seamlessly integrate asynchronous features and build scalable solutions for RESTful APIs and dynamic web applications. Its intuitive design and robust tooling allow developers to focus on creating high-quality code while maximizing performance. Embrace the synergy of Python and Rust to elevate your web development experience.
40
+
41
+
42
+ ### 🏁 Get started
43
+
44
+ ### ⚙️ To Develop Locally
45
+
46
+ - Setup a virtual environment:
47
+ ```
48
+ python3 -m venv venv
49
+ source venv/bin/activate
50
+ ```
51
+ - Install required packages
52
+
53
+ ```
54
+ pip install pre-commit poetry maturin
55
+ ```
56
+ - Install development dependencies
57
+ ```
58
+ poetry install --with dev --with test
59
+ ```
60
+ - Install pre-commit git hooks
61
+ ```
62
+ pre-commit install
63
+ ```
64
+ - Build & install Rust package
65
+ ```
66
+ maturin develop
67
+ ```
68
+
69
+ ## 🤔 Usage
70
+
71
+ ### 🏃 Run your code
72
+
73
+ You will then have access to a server on the `localhost:5005`,
74
+ ```python
75
+ # main.py
76
+ from hypern import Hypern
77
+ from hypern.routing import Route, HTTPEndpoint
78
+
79
+ class MyEndpoint(HTTPEndpoint):
80
+
81
+ async def get(self, request):
82
+ return {"data": "Hello World"}
83
+
84
+ routing = [
85
+ Route("/hello", MyEndpoint)
86
+ ]
87
+
88
+ app = Hypern(routing)
89
+
90
+ if __name__ == "__main__":
91
+ app.start(host='localhost', port=5005)
92
+ ```
93
+
94
+ ```
95
+ $ python3 main.py
96
+ ```
97
+ You can open swagger UI at path `/docs`
98
+
99
+
100
+ ## 💡 Features
101
+
102
+ ### ⚡ High Performance
103
+ - Rust-powered core with Python flexibility
104
+ - Multi-process architecture for optimal CPU utilization
105
+ - Async/await support for non-blocking operations
106
+ - Built on top of production-ready Axum web framework
107
+
108
+ ### 🛠 Development Experience
109
+ - Type hints and IDE support
110
+ - Built-in Swagger/OpenAPI documentation
111
+ - Hot reload during development
112
+ - Comprehensive error handling and logging
113
+
114
+ ### 🔌 Integration & Extensions
115
+ - Easy dependency injection
116
+ - Middleware support (before/after request hooks)
117
+ - WebSocket support (Comming soon)
118
+ - Background task scheduling
119
+ - File upload handling
120
+
121
+ ### 🔒 Security
122
+ - Built-in authentication/authorization (Comming soon)
123
+ - CORS configuration
124
+ - Rate limiting
125
+ - Request validation
126
+
127
+
128
+
@@ -0,0 +1,69 @@
1
+ hypern-0.3.0.dist-info/METADATA,sha256=aG7HYARcYTZed6140LAyzlcSeZ189_4OOZIT0bqiOxE,3754
2
+ hypern-0.3.0.dist-info/WHEEL,sha256=-9HWWVS-XCPZ6Ee3x-BEBZ1KoBrW-blQli_6xr491bE,96
3
+ hypern-0.3.0.dist-info/licenses/LICENSE,sha256=qbYKAIJLS6jYg5hYncKE7OtWmqOtpVTvKNkwOa0Iwwg,1328
4
+ hypern/application.py,sha256=D8tye5rpOJzHGQHUelaDpjUL3B9xO40B7xAL9wC3Uno,14328
5
+ hypern/args_parser.py,sha256=Crxzr8_uhiIk_AWJvuwJTEfRqEBqU_GfTbg6chg_YiY,1790
6
+ hypern/auth/authorization.py,sha256=-NprZsI0np889ZN1fp-MiVFrPoMNzUtatBJaCMtkllM,32
7
+ hypern/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ hypern/background.py,sha256=xy38nQZSJsYFRXr3-uFJeNW9E1GiXXOC7lSe5pC0eyE,124
9
+ hypern/caching/base/backend.py,sha256=nHl5_nH95LSYWBS9RmOvOzsps_p19HcnQsb4h4W7cP8,68
10
+ hypern/caching/base/key_maker.py,sha256=-W1r3ywfQ-K6saniiK3aTMaW3iy3aXai2pvQqM8f74I,232
11
+ hypern/caching/base/__init__.py,sha256=M-B56YGTkSwCkKW_I6GcV1LSIFfxZ6KuXowB1_aJQpQ,155
12
+ hypern/caching/cache_manager.py,sha256=TF6UosJ54950JgsrPVZUS3MH2R8zafAu5PTryNJ0sRs,2053
13
+ hypern/caching/cache_tag.py,sha256=bZcjivMNETAzAHAIobuLN0S2wHgPgLLL8Gg4uso_qbk,267
14
+ hypern/caching/custom_key_maker.py,sha256=88RIIJjpQYFnv857wOlCKgWWBbK_S23zNHsIrJz_4PY,394
15
+ hypern/caching/redis_backend.py,sha256=IgQToCnHYGpKEErq2CNZkR5woo01z456Ef3C-XRPRV8,70
16
+ hypern/caching/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ hypern/cli/commands.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ hypern/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ hypern/config.py,sha256=frZSdXBI8GaM0tkw1Rs-XydZ9-XjGLRPj6DL4d51-y4,4930
20
+ hypern/datastructures.py,sha256=zZGGSP07kPc9KJDf11hX5uYhAyRE-Ck5wezW5QtOVXw,897
21
+ hypern/db/nosql/addons/color.py,sha256=bAGRuARCAYwZ1nO4jK0lzGYKmavTDtS34BxvrsetF74,446
22
+ hypern/db/nosql/addons/daterange.py,sha256=hGUSoVFqatNY-TB5wjZTq62iZpHpdsyRJIsHxsj1uDs,1192
23
+ hypern/db/nosql/addons/encrypted.py,sha256=B0M-uDqvZHVmIZcFdwcuC2MGsv0pGJFQ1lrOg8klR9U,1741
24
+ hypern/db/nosql/addons/password.py,sha256=jfZxvWFm6nV9EWpXq5Mj-jpqnl9QbokZj9WT14n7dKE,5035
25
+ hypern/db/nosql/addons/unicode.py,sha256=LaDpLfdoTcJuASPE-8fqOVD05H_uOx8gOdnyDn5Iu0c,268
26
+ hypern/db/nosql/addons/__init__.py,sha256=WEtPM8sPHilvga7zxwqvINeTkF0hdcfgPcAnHc4MASE,125
27
+ hypern/db/nosql/__init__.py,sha256=MH9YvlbRlbBCrQVNOdfTaK-hINwJxbJLmxwY9Mei7I8,644
28
+ hypern/db/sql/addons/color.py,sha256=Jj8q4lkT0ukKyjVyZjBx7fokGrX7AIJpKOGzwsBpfnU,480
29
+ hypern/db/sql/addons/daterange.py,sha256=qEfQN9c4jQdzeXNYKgQ4VIJ3Qc0HXHWRouzNF1se-RA,853
30
+ hypern/db/sql/addons/datetime.py,sha256=Bp2jMja2lb_b2WnzRnfbjXXTHBgBTyph1ECsItIwvvg,643
31
+ hypern/db/sql/addons/encrypted.py,sha256=pXsg4ImPpK-VkLDruKrv2gUtdp2kajQX7xDbRDxa-SI,1930
32
+ hypern/db/sql/addons/password.py,sha256=9pypORygWaINj3oiAOBRIOGgpuA0lcjPq4rh1pqJxq0,5789
33
+ hypern/db/sql/addons/ts_vector.py,sha256=bQyXYvQ1bAfFpYcS-sFwM7fU6L1lg9_7nGDRGp0CoUo,1361
34
+ hypern/db/sql/addons/unicode.py,sha256=rbqyHlsPUqRDWIjYQSRFF3zkHncnwxo0sdlzqUlcrUw,411
35
+ hypern/db/sql/addons/__init__.py,sha256=mLN_AvwgpSAbrWZvVHZuO7ff0gk1T_JbVwd5wug5nlw,359
36
+ hypern/db/sql/repository.py,sha256=ue6vWOTrnEPyDevlyh3v-7PU6GSfrZHYKrbXVuoS8UA,9516
37
+ hypern/db/sql/__init__.py,sha256=1UoWQi2CIcUAbQj3FadR-8V0o_b286nI2wYvOsvtbFc,6478
38
+ hypern/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
+ hypern/enum.py,sha256=KcVziJj7vWvyie0r2rtxhrLzdtkZAsf0DY58oJ4tQl4,360
40
+ hypern/exceptions.py,sha256=nHTkF0YdNBMKfSiNtjRMHMNKoY3RMUm68YYluuW15us,2428
41
+ hypern/hypern.pyi,sha256=b5Zjr9ipm43rI8eSUV4WYmKpIJWUsv6O2TrpvvbeTlE,7700
42
+ hypern/i18n/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
+ hypern/logging/logger.py,sha256=WACam_IJiCMXX0hGVKMGSxUQpY4DgAXy7M1dD3q-Z9s,3256
44
+ hypern/logging/__init__.py,sha256=6eVriyncsJ4J73fGYhoejv9MX7aGTkRezTpPxO4DX1I,52
45
+ hypern/middleware/base.py,sha256=mJoz-i-7lqw1eDxZ8Bb0t8sz60lx5TE-OjZT4UR75e4,541
46
+ hypern/middleware/cors.py,sha256=x90DnCOJSfp4ojm1krttn_EdtlqeDazyUzVg66NES4A,1681
47
+ hypern/middleware/i18n.py,sha256=jHzVzjTx1nnjbraZtIVOprrnSaeKMxZB8RuSqRp2I4s,16
48
+ hypern/middleware/limit.py,sha256=8hzUxu_mxra2QiDjAghgZtvwN6Dx07irPUiL12dbVhY,8152
49
+ hypern/middleware/__init__.py,sha256=lXwR3fdmpVK4Z7QWaLsgf3Sazy5NPPFXIOxIEv1xDC8,273
50
+ hypern/openapi/schemas.py,sha256=YHfMlPUeP5DzDX5ao3YH8p_25Vvyaf616dh6XDCUZRc,1677
51
+ hypern/openapi/swagger.py,sha256=naqUY3rFAEYA1ZLIlmDsMYaol0yIm6TVebdkFa5cMTc,64
52
+ hypern/openapi/__init__.py,sha256=4rEVD8pa0kdSpsy7ZkJ5JY0Z2XF0NGSKDMwYAd7YZpE,141
53
+ hypern/processpool.py,sha256=YW7Dg33Hla9D33x3Mf8xsjaTprHaovkLPK-4XnQKiGU,4045
54
+ hypern/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
+ hypern/reload.py,sha256=d28xP7MWiO2vL6RAAlIqSh8q7_ugsRBN66Mlbz3UWEI,2293
56
+ hypern/response/response.py,sha256=-dnboAraPic8asf503PxwmDuxhNllUO5h97_DGmbER4,4582
57
+ hypern/response/__init__.py,sha256=_w3u3TDNuYx5ejnnN1unqnTY8NlBgUATQi6wepEB_FQ,226
58
+ hypern/routing/dispatcher.py,sha256=i2wLAAW1ZXgpi5K2heGXhTODnP1WdQzaR5WlUjs1o9c,2368
59
+ hypern/routing/endpoint.py,sha256=RKVhvqOEGL9IKBXQ3KJgPi9bgJj9gfWC5BdZc5U_atc,1026
60
+ hypern/routing/parser.py,sha256=R-4lcN9Ha1iMeAjlqDe8HwkjjMVG-c-ubQLZyWKXj6M,3554
61
+ hypern/routing/route.py,sha256=IUnWU5ra-0R9rrRDpxJiwiw7vaEefn-We2dZ4EocJGw,10403
62
+ hypern/routing/__init__.py,sha256=7rw7EAxougCXtmkgJjrmLP3N5RXctIpI_3JmG9FcKVU,101
63
+ hypern/scheduler.py,sha256=-k3tW2AGCnHYSthKXk-FOs_SCtWp3yIxQzwzUJMJsbo,67
64
+ hypern/security.py,sha256=3E86Yp_eOSVa1emUvBrDgoF0Sn6eNX0CfLnt87w5CPI,1773
65
+ hypern/worker.py,sha256=WQrhY_awR6zjMwY4Q7izXi4E4fFrDqt7jIblUW8Bzcg,924
66
+ hypern/ws.py,sha256=F6SA2Z1KVnqTEX8ssvOXqCtudUS4eo30JsiIsvfbHnE,394
67
+ hypern/__init__.py,sha256=9Ww_aUQ0vJls0tOq7Yw1_TVOCRsa5bHJ-RtnSeComwk,119
68
+ hypern/hypern.cp310-win_amd64.pyd,sha256=eTZS9C_KGtOgKFir2Fb3OSe_32Pt-9cxB66Qvly2EWg,6267392
69
+ hypern-0.3.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: maturin (1.7.7)
3
+ Root-Is-Purelib: false
4
+ Tag: cp310-cp310-win_amd64