hypern 0.1.0__cp310-cp310-manylinux_2_34_x86_64.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/__init__.py +4 -0
- hypern/application.py +234 -0
- hypern/auth/__init__.py +0 -0
- hypern/auth/authorization.py +2 -0
- hypern/background.py +4 -0
- hypern/caching/__init__.py +0 -0
- hypern/caching/base/__init__.py +8 -0
- hypern/caching/base/backend.py +3 -0
- hypern/caching/base/key_maker.py +8 -0
- hypern/caching/cache_manager.py +56 -0
- hypern/caching/cache_tag.py +10 -0
- hypern/caching/custom_key_maker.py +11 -0
- hypern/caching/redis_backend.py +3 -0
- hypern/cli/__init__.py +0 -0
- hypern/cli/commands.py +0 -0
- hypern/config.py +149 -0
- hypern/datastructures.py +27 -0
- hypern/db/__init__.py +0 -0
- hypern/db/nosql/__init__.py +25 -0
- hypern/db/nosql/addons/__init__.py +4 -0
- hypern/db/nosql/addons/color.py +16 -0
- hypern/db/nosql/addons/daterange.py +30 -0
- hypern/db/nosql/addons/encrypted.py +53 -0
- hypern/db/nosql/addons/password.py +134 -0
- hypern/db/nosql/addons/unicode.py +10 -0
- hypern/db/sql/__init__.py +176 -0
- hypern/db/sql/addons/__init__.py +14 -0
- hypern/db/sql/addons/color.py +15 -0
- hypern/db/sql/addons/daterange.py +22 -0
- hypern/db/sql/addons/datetime.py +22 -0
- hypern/db/sql/addons/encrypted.py +58 -0
- hypern/db/sql/addons/password.py +170 -0
- hypern/db/sql/addons/ts_vector.py +46 -0
- hypern/db/sql/addons/unicode.py +15 -0
- hypern/db/sql/repository.py +289 -0
- hypern/enum.py +13 -0
- hypern/exceptions.py +93 -0
- hypern/hypern.cpython-310-x86_64-linux-gnu.so +0 -0
- hypern/hypern.pyi +172 -0
- hypern/i18n/__init__.py +0 -0
- hypern/logging/__init__.py +3 -0
- hypern/logging/logger.py +91 -0
- hypern/middleware/__init__.py +5 -0
- hypern/middleware/base.py +16 -0
- hypern/middleware/cors.py +38 -0
- hypern/middleware/i18n.py +1 -0
- hypern/middleware/limit.py +174 -0
- hypern/openapi/__init__.py +5 -0
- hypern/openapi/schemas.py +64 -0
- hypern/openapi/swagger.py +3 -0
- hypern/py.typed +0 -0
- hypern/response/__init__.py +3 -0
- hypern/response/response.py +134 -0
- hypern/routing/__init__.py +4 -0
- hypern/routing/dispatcher.py +65 -0
- hypern/routing/endpoint.py +27 -0
- hypern/routing/parser.py +101 -0
- hypern/routing/router.py +279 -0
- hypern/scheduler.py +5 -0
- hypern/security.py +44 -0
- hypern/worker.py +30 -0
- hypern-0.1.0.dist-info/METADATA +121 -0
- hypern-0.1.0.dist-info/RECORD +65 -0
- hypern-0.1.0.dist-info/WHEEL +4 -0
- hypern-0.1.0.dist-info/licenses/LICENSE +24 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from functools import reduce
|
|
3
|
+
from typing import Any, Generic, Optional, Type, TypeVar, Dict
|
|
4
|
+
from sqlalchemy import Select, select, and_, desc, asc, between
|
|
5
|
+
from sqlalchemy.ext.asyncio import (
|
|
6
|
+
AsyncSession,
|
|
7
|
+
)
|
|
8
|
+
from sqlalchemy.sql import func
|
|
9
|
+
from sqlalchemy.orm import declarative_base
|
|
10
|
+
|
|
11
|
+
Base = declarative_base()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Model(Base): # type: ignore
|
|
15
|
+
__abstract__ = True
|
|
16
|
+
__table_args__ = {"extend_existing": True}
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def as_dict(self) -> Dict[str, Any]:
|
|
20
|
+
return {c.name: getattr(self, c.name) for c in self.__table__.columns}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
ModelType = TypeVar("ModelType", bound=Base) # type: ignore
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class PostgresRepository(Generic[ModelType]):
|
|
27
|
+
"""Base class for data repositories."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, model: Type[ModelType], db_session: AsyncSession):
|
|
30
|
+
self.session = db_session # type: ignore
|
|
31
|
+
self.model_class: Type[ModelType] = model
|
|
32
|
+
|
|
33
|
+
async def create(self, attributes: Optional[dict[str, Any]] = None) -> ModelType:
|
|
34
|
+
"""
|
|
35
|
+
Creates the model instance.
|
|
36
|
+
|
|
37
|
+
:param attributes: The attributes to create the model with.
|
|
38
|
+
:return: The created model instance.
|
|
39
|
+
"""
|
|
40
|
+
if attributes is None:
|
|
41
|
+
attributes = {}
|
|
42
|
+
model = self.model_class(**attributes) # type: ignore
|
|
43
|
+
self.session.add(model)
|
|
44
|
+
return model
|
|
45
|
+
|
|
46
|
+
async def get_all(
|
|
47
|
+
self,
|
|
48
|
+
skip: int = 0,
|
|
49
|
+
limit: int = 100,
|
|
50
|
+
join_: set[str] | None = None,
|
|
51
|
+
where: Optional[dict] = None,
|
|
52
|
+
order_by: tuple[str, str] | None = None,
|
|
53
|
+
) -> list[ModelType]:
|
|
54
|
+
"""
|
|
55
|
+
Returns a list of model instances.
|
|
56
|
+
|
|
57
|
+
:param skip: The number of records to skip.
|
|
58
|
+
:param limit: The number of record to return.
|
|
59
|
+
:param join_: The joins to make.
|
|
60
|
+
:param where: The conditions for the WHERE clause.
|
|
61
|
+
:return: A list of model instances.
|
|
62
|
+
"""
|
|
63
|
+
query = self._query(join_)
|
|
64
|
+
query = query.offset(skip).limit(limit)
|
|
65
|
+
|
|
66
|
+
if where is not None:
|
|
67
|
+
conditions = []
|
|
68
|
+
for k, v in where.items():
|
|
69
|
+
if isinstance(v, dict) and "$gt" in v and "$lt" in v:
|
|
70
|
+
conditions.append(between(getattr(self.model_class, k), v["$gt"], v["$lt"]))
|
|
71
|
+
else:
|
|
72
|
+
conditions.append(getattr(self.model_class, k) == v)
|
|
73
|
+
query = query.where(and_(*conditions))
|
|
74
|
+
|
|
75
|
+
if order_by is not None:
|
|
76
|
+
column, direction = order_by
|
|
77
|
+
if direction.lower() == "desc":
|
|
78
|
+
query = query.order_by(desc(getattr(self.model_class, column)))
|
|
79
|
+
else:
|
|
80
|
+
query = query.order_by(asc(getattr(self.model_class, column)))
|
|
81
|
+
|
|
82
|
+
if join_ is not None:
|
|
83
|
+
return await self.all_unique(query)
|
|
84
|
+
return await self._all(query)
|
|
85
|
+
|
|
86
|
+
async def get_by(
|
|
87
|
+
self,
|
|
88
|
+
field: str,
|
|
89
|
+
value: Any,
|
|
90
|
+
join_: set[str] | None = None,
|
|
91
|
+
unique: bool = False,
|
|
92
|
+
) -> ModelType | list[ModelType] | None:
|
|
93
|
+
"""
|
|
94
|
+
Returns the model instance matching the field and value.
|
|
95
|
+
|
|
96
|
+
:param field: The field to match.
|
|
97
|
+
:param value: The value to match.
|
|
98
|
+
:param join_: The joins to make.
|
|
99
|
+
:return: The model instance.
|
|
100
|
+
"""
|
|
101
|
+
query = self._query(join_)
|
|
102
|
+
query = await self._get_by(query, field, value)
|
|
103
|
+
|
|
104
|
+
if join_ is not None:
|
|
105
|
+
return await self.all_unique(query)
|
|
106
|
+
if unique:
|
|
107
|
+
return await self._one(query)
|
|
108
|
+
|
|
109
|
+
return await self._all(query)
|
|
110
|
+
|
|
111
|
+
async def delete(self, model: ModelType) -> None:
|
|
112
|
+
"""
|
|
113
|
+
Deletes the model.
|
|
114
|
+
|
|
115
|
+
:param model: The model to delete.
|
|
116
|
+
:return: None
|
|
117
|
+
"""
|
|
118
|
+
await self.session.delete(model)
|
|
119
|
+
|
|
120
|
+
async def update(self, model: ModelType, attributes: dict[str, Any]) -> ModelType:
|
|
121
|
+
"""
|
|
122
|
+
Updates the model.
|
|
123
|
+
|
|
124
|
+
:param model: The model to update.
|
|
125
|
+
:param attributes: The attributes to update the model with.
|
|
126
|
+
:return: The updated model instance.
|
|
127
|
+
"""
|
|
128
|
+
for key, value in attributes.items():
|
|
129
|
+
if hasattr(model, key):
|
|
130
|
+
setattr(model, key, value)
|
|
131
|
+
|
|
132
|
+
self.session.add(model)
|
|
133
|
+
await self.session.commit()
|
|
134
|
+
|
|
135
|
+
return model
|
|
136
|
+
|
|
137
|
+
def _query(
|
|
138
|
+
self,
|
|
139
|
+
join_: set[str] | None = None,
|
|
140
|
+
order_: dict | None = None,
|
|
141
|
+
) -> Select:
|
|
142
|
+
"""
|
|
143
|
+
Returns a callable that can be used to query the model.
|
|
144
|
+
|
|
145
|
+
:param join_: The joins to make.
|
|
146
|
+
:param order_: The order of the results. (e.g desc, asc)
|
|
147
|
+
:return: A callable that can be used to query the model.
|
|
148
|
+
"""
|
|
149
|
+
query = select(self.model_class)
|
|
150
|
+
query = self._maybe_join(query, join_)
|
|
151
|
+
query = self._maybe_ordered(query, order_)
|
|
152
|
+
|
|
153
|
+
return query
|
|
154
|
+
|
|
155
|
+
async def _all(self, query: Select) -> list[ModelType]:
|
|
156
|
+
"""
|
|
157
|
+
Returns all results from the query.
|
|
158
|
+
|
|
159
|
+
:param query: The query to execute.
|
|
160
|
+
:return: A list of model instances.
|
|
161
|
+
"""
|
|
162
|
+
query = await self.session.scalars(query)
|
|
163
|
+
return query.all()
|
|
164
|
+
|
|
165
|
+
async def all_unique(self, query: Select) -> list[ModelType]:
|
|
166
|
+
result = await self.session.execute(query)
|
|
167
|
+
return result.unique().scalars().all()
|
|
168
|
+
|
|
169
|
+
async def _first(self, query: Select) -> ModelType | None:
|
|
170
|
+
"""
|
|
171
|
+
Returns the first result from the query.
|
|
172
|
+
|
|
173
|
+
:param query: The query to execute.
|
|
174
|
+
:return: The first model instance.
|
|
175
|
+
"""
|
|
176
|
+
query = await self.session.scalars(query)
|
|
177
|
+
return query.first()
|
|
178
|
+
|
|
179
|
+
async def _one_or_none(self, query: Select) -> ModelType | None:
|
|
180
|
+
"""Returns the first result from the query or None."""
|
|
181
|
+
query = await self.session.scalars(query)
|
|
182
|
+
return query.one_or_none()
|
|
183
|
+
|
|
184
|
+
async def _one(self, query: Select) -> ModelType:
|
|
185
|
+
"""
|
|
186
|
+
Returns the first result from the query or raises NoResultFound.
|
|
187
|
+
|
|
188
|
+
:param query: The query to execute.
|
|
189
|
+
:return: The first model instance.
|
|
190
|
+
"""
|
|
191
|
+
query = await self.session.scalars(query)
|
|
192
|
+
return query.one()
|
|
193
|
+
|
|
194
|
+
async def _count(self, query: Select) -> int:
|
|
195
|
+
"""
|
|
196
|
+
Returns the count of the records.
|
|
197
|
+
|
|
198
|
+
:param query: The query to execute.
|
|
199
|
+
"""
|
|
200
|
+
query = query.subquery()
|
|
201
|
+
query = await self.session.scalars(select(func.count()).select_from(query))
|
|
202
|
+
return query.one()
|
|
203
|
+
|
|
204
|
+
async def _sort_by(
|
|
205
|
+
self,
|
|
206
|
+
query: Select,
|
|
207
|
+
sort_by: str,
|
|
208
|
+
order: str | None = "asc",
|
|
209
|
+
model: Type[ModelType] | None = None,
|
|
210
|
+
case_insensitive: bool = False,
|
|
211
|
+
) -> Select:
|
|
212
|
+
"""
|
|
213
|
+
Returns the query sorted by the given column.
|
|
214
|
+
|
|
215
|
+
:param query: The query to sort.
|
|
216
|
+
:param sort_by: The column to sort by.
|
|
217
|
+
:param order: The order to sort by.
|
|
218
|
+
:param model: The model to sort.
|
|
219
|
+
:param case_insensitive: Whether to sort case insensitively.
|
|
220
|
+
:return: The sorted query.
|
|
221
|
+
"""
|
|
222
|
+
model = model or self.model_class
|
|
223
|
+
|
|
224
|
+
order_column = None
|
|
225
|
+
|
|
226
|
+
if case_insensitive:
|
|
227
|
+
order_column = func.lower(getattr(model, sort_by))
|
|
228
|
+
else:
|
|
229
|
+
order_column = getattr(model, sort_by)
|
|
230
|
+
|
|
231
|
+
if order == "desc":
|
|
232
|
+
return query.order_by(order_column.desc())
|
|
233
|
+
|
|
234
|
+
return query.order_by(order_column.asc())
|
|
235
|
+
|
|
236
|
+
async def _get_by(self, query: Select, field: str, value: Any) -> Select:
|
|
237
|
+
"""
|
|
238
|
+
Returns the query filtered by the given column.
|
|
239
|
+
|
|
240
|
+
:param query: The query to filter.
|
|
241
|
+
:param field: The column to filter by.
|
|
242
|
+
:param value: The value to filter by.
|
|
243
|
+
:return: The filtered query.
|
|
244
|
+
"""
|
|
245
|
+
return query.where(getattr(self.model_class, field) == value)
|
|
246
|
+
|
|
247
|
+
def _maybe_join(self, query: Select, join_: set[str] | None = None) -> Select:
|
|
248
|
+
"""
|
|
249
|
+
Returns the query with the given joins.
|
|
250
|
+
|
|
251
|
+
:param query: The query to join.
|
|
252
|
+
:param join_: The joins to make.
|
|
253
|
+
:return: The query with the given joins.
|
|
254
|
+
"""
|
|
255
|
+
if not join_:
|
|
256
|
+
return query
|
|
257
|
+
|
|
258
|
+
if not isinstance(join_, set):
|
|
259
|
+
raise TypeError("join_ must be a set")
|
|
260
|
+
|
|
261
|
+
return reduce(self._add_join_to_query, join_, query) # type: ignore
|
|
262
|
+
|
|
263
|
+
def _maybe_ordered(self, query: Select, order_: dict | None = None) -> Select:
|
|
264
|
+
"""
|
|
265
|
+
Returns the query ordered by the given column.
|
|
266
|
+
|
|
267
|
+
:param query: The query to order.
|
|
268
|
+
:param order_: The order to make.
|
|
269
|
+
:return: The query ordered by the given column.
|
|
270
|
+
"""
|
|
271
|
+
if order_:
|
|
272
|
+
if order_["asc"]:
|
|
273
|
+
for order in order_["asc"]:
|
|
274
|
+
query = query.order_by(getattr(self.model_class, order).asc())
|
|
275
|
+
else:
|
|
276
|
+
for order in order_["desc"]:
|
|
277
|
+
query = query.order_by(getattr(self.model_class, order).desc())
|
|
278
|
+
|
|
279
|
+
return query
|
|
280
|
+
|
|
281
|
+
def _add_join_to_query(self, query: Select, join_: str) -> Select:
|
|
282
|
+
"""
|
|
283
|
+
Returns the query with the given join.
|
|
284
|
+
|
|
285
|
+
:param query: The query to join.
|
|
286
|
+
:param join_: The join to make.
|
|
287
|
+
:return: The query with the given join.
|
|
288
|
+
"""
|
|
289
|
+
return getattr(self, "_join_" + join_)(query)
|
hypern/enum.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from enum import Enum
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ErrorCode(Enum):
|
|
6
|
+
UNKNOWN_ERROR = "UNKNOWN_ERROR"
|
|
7
|
+
BAD_REQUEST = "BAD_REQUEST"
|
|
8
|
+
FORBIDDEN = "FORBIDDEN"
|
|
9
|
+
SERVER_ERROR = "SERVER_ERROR"
|
|
10
|
+
NOT_FOUND = "NOT_FOUND"
|
|
11
|
+
METHOD_NOT_ALLOW = "METHOD_NOT_ALLOW"
|
|
12
|
+
UNAUTHORIZED = "UNAUTHORIZED"
|
|
13
|
+
VALIDATION_ERROR = "VALIDATION_ERROR"
|
hypern/exceptions.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
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
|
|
Binary file
|
hypern/hypern.pyi
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Callable, Any, List, Tuple, Dict
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class BaseBackend:
|
|
8
|
+
get: Callable[[str], Any]
|
|
9
|
+
set: Callable[[Any, str, int], None]
|
|
10
|
+
delete_startswith: Callable[[str], None]
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class RedisBackend(BaseBackend):
|
|
14
|
+
url: str
|
|
15
|
+
|
|
16
|
+
get: Callable[[str], Any]
|
|
17
|
+
set: Callable[[Any, str, int], None]
|
|
18
|
+
delete_startswith: Callable[[str], None]
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class BaseSchemaGenerator:
|
|
22
|
+
remove_converter: Callable[[str], str]
|
|
23
|
+
parse_docstring: Callable[[Callable[..., Any]], str]
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class SwaggerUI:
|
|
27
|
+
title: str
|
|
28
|
+
openapi_url: str
|
|
29
|
+
|
|
30
|
+
render_template: Callable[..., Any]
|
|
31
|
+
get_html_content: Callable[..., str]
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class BackgroundTask:
|
|
35
|
+
"""
|
|
36
|
+
A task to be executed in the background
|
|
37
|
+
id: str: The task ID
|
|
38
|
+
function: Callable[..., Any]: The function to be executed
|
|
39
|
+
args: List | Tuple: The arguments to be passed to the function
|
|
40
|
+
kwargs: Dict[str, Any]: The keyword arguments to be passed to the function
|
|
41
|
+
timeout_secs: int: The maximum time in seconds the task is allowed to run
|
|
42
|
+
cancelled: bool: Whether the task is cancelled
|
|
43
|
+
|
|
44
|
+
**Note**: function is currently running with sync mode, so it should be a sync function
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
id: str
|
|
48
|
+
function: Callable[..., Any]
|
|
49
|
+
args: List | Tuple
|
|
50
|
+
kwargs: Dict[str, Any]
|
|
51
|
+
timeout_secs: int
|
|
52
|
+
cancelled: bool
|
|
53
|
+
|
|
54
|
+
def get_id(self) -> str:
|
|
55
|
+
"""
|
|
56
|
+
Get the task ID
|
|
57
|
+
"""
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
def cancel(self) -> None:
|
|
61
|
+
"""
|
|
62
|
+
Cancel the task
|
|
63
|
+
"""
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
def is_cancelled(self) -> bool:
|
|
67
|
+
"""
|
|
68
|
+
Check if the task is cancelled
|
|
69
|
+
"""
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
def execute(self) -> Any:
|
|
73
|
+
"""
|
|
74
|
+
Execute the task
|
|
75
|
+
"""
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
@dataclass
|
|
79
|
+
class BackgroundTasks:
|
|
80
|
+
"""
|
|
81
|
+
A collection of tasks to be executed in the background
|
|
82
|
+
|
|
83
|
+
**Note**: Only set tasks. pool, sender, receiver are set by the framework
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
def add_task(self, task: BackgroundTask) -> str:
|
|
87
|
+
"""
|
|
88
|
+
Add a task to the collection
|
|
89
|
+
"""
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
def cancel_task(self, task_id: str) -> bool:
|
|
93
|
+
"""
|
|
94
|
+
Cancel a task in the collection
|
|
95
|
+
"""
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
def execute_all(self) -> None:
|
|
99
|
+
"""
|
|
100
|
+
Execute all tasks in the collection
|
|
101
|
+
"""
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
def execute_task(self, task_id: str) -> None:
|
|
105
|
+
"""
|
|
106
|
+
Execute a task in the collection
|
|
107
|
+
"""
|
|
108
|
+
pass
|
|
109
|
+
|
|
110
|
+
class Scheduler:
|
|
111
|
+
def add_job(
|
|
112
|
+
self,
|
|
113
|
+
job_type: str,
|
|
114
|
+
schedule_param: str,
|
|
115
|
+
task: Callable[..., Any],
|
|
116
|
+
timezone: str,
|
|
117
|
+
dependencies: List[str],
|
|
118
|
+
retry_policy: Tuple[int, int, bool] | None = None,
|
|
119
|
+
) -> str:
|
|
120
|
+
"""
|
|
121
|
+
Add a job to the scheduler
|
|
122
|
+
params:
|
|
123
|
+
job_type: str: The type of the job (e.g. "cron", "interval")
|
|
124
|
+
|
|
125
|
+
schedule_param: str: The schedule parameter of the job. interval in seconds for interval jobs, cron expression for cron jobs
|
|
126
|
+
|
|
127
|
+
Exmaple:
|
|
128
|
+
// sec min hour day of month month day of week year
|
|
129
|
+
expression = "0 30 9,12,15 1,15 May-Aug Mon,Wed,Fri 2018/2";
|
|
130
|
+
|
|
131
|
+
task: Callable[..., Any]: The task to be executed
|
|
132
|
+
|
|
133
|
+
timezone: str: The timezone of the job
|
|
134
|
+
|
|
135
|
+
dependencies: List[str]: The IDs of the jobs this job depends on
|
|
136
|
+
|
|
137
|
+
retry_policy: Tuple[int, int, bool] | None: The retry policy of the job. (max_retries, retry_delay_secs, exponential_backoff)
|
|
138
|
+
|
|
139
|
+
return:
|
|
140
|
+
str: The ID of the job
|
|
141
|
+
"""
|
|
142
|
+
pass
|
|
143
|
+
|
|
144
|
+
def remove_job(self, job_id: str) -> None:
|
|
145
|
+
"""
|
|
146
|
+
Remove a job from the scheduler
|
|
147
|
+
"""
|
|
148
|
+
pass
|
|
149
|
+
|
|
150
|
+
def start(self) -> None:
|
|
151
|
+
"""
|
|
152
|
+
Start the scheduler
|
|
153
|
+
"""
|
|
154
|
+
pass
|
|
155
|
+
|
|
156
|
+
def stop(self) -> None:
|
|
157
|
+
"""
|
|
158
|
+
Stop the scheduler
|
|
159
|
+
"""
|
|
160
|
+
pass
|
|
161
|
+
|
|
162
|
+
def get_job_status(self, job_id: str) -> Tuple[float, float, List[str], int]:
|
|
163
|
+
"""
|
|
164
|
+
Get the status of a job
|
|
165
|
+
"""
|
|
166
|
+
pass
|
|
167
|
+
|
|
168
|
+
def get_next_run(self, job_id: str) -> float:
|
|
169
|
+
"""
|
|
170
|
+
Get the next run time of a job
|
|
171
|
+
"""
|
|
172
|
+
pass
|
hypern/i18n/__init__.py
ADDED
|
File without changes
|
hypern/logging/logger.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Optional, Literal
|
|
4
|
+
from copy import copy
|
|
5
|
+
import click
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
TRACE_LOG_LEVEL = 5
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ColourizedFormatter(logging.Formatter):
|
|
12
|
+
level_name_colors = {
|
|
13
|
+
TRACE_LOG_LEVEL: lambda level_name: click.style(str(level_name), fg="blue"),
|
|
14
|
+
logging.DEBUG: lambda level_name: click.style(str(level_name), fg="cyan"),
|
|
15
|
+
logging.INFO: lambda level_name: click.style(str(level_name), fg="green"),
|
|
16
|
+
logging.WARNING: lambda level_name: click.style(str(level_name), fg="yellow"),
|
|
17
|
+
logging.ERROR: lambda level_name: click.style(str(level_name), fg="red"),
|
|
18
|
+
logging.CRITICAL: lambda level_name: click.style(str(level_name), fg="bright_red"),
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
fmt: Optional[str] = None,
|
|
24
|
+
datefmt: Optional[str] = None,
|
|
25
|
+
style: Literal["%", "{", "$"] = "%",
|
|
26
|
+
use_colors: Optional[bool] = None,
|
|
27
|
+
):
|
|
28
|
+
if use_colors in (True, False):
|
|
29
|
+
self.use_colors = use_colors
|
|
30
|
+
else:
|
|
31
|
+
self.use_colors = sys.stdout.isatty()
|
|
32
|
+
super().__init__(fmt=fmt, datefmt=datefmt, style=style)
|
|
33
|
+
|
|
34
|
+
def color_level_name(self, level_name: str, level_no: int) -> str:
|
|
35
|
+
def default(level_name: str) -> str:
|
|
36
|
+
return str(level_name)
|
|
37
|
+
|
|
38
|
+
func = self.level_name_colors.get(level_no, default)
|
|
39
|
+
return func(level_name)
|
|
40
|
+
|
|
41
|
+
def should_use_colors(self) -> bool:
|
|
42
|
+
return True
|
|
43
|
+
|
|
44
|
+
def formatMessage(self, record: logging.LogRecord) -> str:
|
|
45
|
+
recordcopy = copy(record)
|
|
46
|
+
levelname = recordcopy.levelname
|
|
47
|
+
process = recordcopy.process
|
|
48
|
+
separator = " " * (8 - len(recordcopy.levelname))
|
|
49
|
+
if self.use_colors:
|
|
50
|
+
levelname = self.color_level_name(levelname, recordcopy.levelno)
|
|
51
|
+
if "color_message" in recordcopy.__dict__:
|
|
52
|
+
recordcopy.msg = recordcopy.__dict__["color_message"]
|
|
53
|
+
recordcopy.__dict__["message"] = recordcopy.getMessage()
|
|
54
|
+
recordcopy.__dict__["levelprefix"] = levelname + ":" + separator
|
|
55
|
+
recordcopy.__dict__["process"] = click.style(str(process), fg="blue")
|
|
56
|
+
return super().formatMessage(recordcopy)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class DefaultFormatter(ColourizedFormatter):
|
|
60
|
+
def should_use_colors(self) -> bool:
|
|
61
|
+
return sys.stderr.isatty()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def create_logger(name) -> logging.Logger:
|
|
65
|
+
logger = logging.getLogger(name)
|
|
66
|
+
logger.setLevel(logging.DEBUG)
|
|
67
|
+
formatter = DefaultFormatter(
|
|
68
|
+
fmt="%(levelprefix)s %(asctime)s [%(process)s] [%(filename)s:%(lineno)d] %(message)s",
|
|
69
|
+
use_colors=True,
|
|
70
|
+
datefmt="%d-%m-%Y %H:%M:%S",
|
|
71
|
+
)
|
|
72
|
+
handler = logging.StreamHandler()
|
|
73
|
+
handler.setFormatter(formatter)
|
|
74
|
+
logger.addHandler(handler)
|
|
75
|
+
return logger
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def get_loggers_by_prefix(prefix):
|
|
79
|
+
return [name for name in logging.root.manager.loggerDict.keys() if name.startswith(prefix)]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def reset_logger() -> None:
|
|
83
|
+
robyn_loggers = get_loggers_by_prefix("robyn")
|
|
84
|
+
actix_loggers = ["actix_server.builder", "actix_server.worker", "actix_server.server", "actix_server.accept"]
|
|
85
|
+
|
|
86
|
+
for name in [*robyn_loggers, *actix_loggers]:
|
|
87
|
+
logger = create_logger(name)
|
|
88
|
+
logger.propagate = False
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
logger = create_logger("hypern")
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
from .base import Middleware
|
|
2
|
+
from .cors import CORSMiddleware
|
|
3
|
+
from .limit import RateLimitMiddleware, StorageBackend, RedisBackend, InMemoryBackend
|
|
4
|
+
|
|
5
|
+
__all__ = ["Middleware", "CORSMiddleware", "RateLimitMiddleware", "StorageBackend", "RedisBackend", "InMemoryBackend"]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from robyn import Response, Request
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Middleware(ABC):
|
|
6
|
+
def __init__(self) -> None:
|
|
7
|
+
super().__init__()
|
|
8
|
+
self.app = None
|
|
9
|
+
|
|
10
|
+
@abstractmethod
|
|
11
|
+
def before_request(self, request: Request):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
@abstractmethod
|
|
15
|
+
def after_request(self, response: Response):
|
|
16
|
+
pass
|