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,23 @@
1
+ from datetime import datetime
2
+
3
+ from sqlalchemy.dialects.postgresql import DATERANGE
4
+ from sqlalchemy.types import TypeDecorator
5
+
6
+
7
+ class DateRangeField(TypeDecorator):
8
+ impl = DATERANGE
9
+
10
+ def process_bind_param(self, value, dialect):
11
+ if value is None:
12
+ return None
13
+ elif "start" in value and "end" in value:
14
+ return f"['{value['start']}', '{value['end']}']"
15
+ else:
16
+ raise ValueError('DateRangeField must be a dictionary with "start" and "end" keys')
17
+
18
+ def process_result_value(self, value, dialect):
19
+ if value is None:
20
+ return None
21
+ else:
22
+ start, end = value[1:-1].split(",")
23
+ return {"start": datetime.strptime(start.strip("'"), "%Y-%m-%d %H:%M:%S.%f"), "end": datetime.strptime(end.strip("'"), "%Y-%m-%d %H:%M:%S.%f")}
@@ -0,0 +1,22 @@
1
+ # -*- coding: utf-8 -*-
2
+ from sqlalchemy import types
3
+
4
+
5
+ class DatetimeType(types.TypeDecorator):
6
+ impl = types.DateTime
7
+ cache_ok = True
8
+
9
+ def load_dialect_impl(self, dialect):
10
+ if dialect.name == "sqlite":
11
+ return dialect.type_descriptor(types.TEXT)
12
+ return dialect.type_descriptor(self.impl)
13
+
14
+ def process_bind_param(self, value, dialect):
15
+ if dialect.name == "sqlite":
16
+ return value.isoformat()
17
+ return value
18
+
19
+ def process_result_value(self, value, dialect):
20
+ if dialect.name != "sqlite":
21
+ return value.timestamp()
22
+ return value
@@ -0,0 +1,58 @@
1
+ # -*- coding: utf-8 -*-
2
+ import os
3
+ import typing
4
+
5
+ from cryptography.hazmat.primitives import padding
6
+ from sqlalchemy.types import LargeBinary, String, TypeDecorator
7
+
8
+ from hypern.security import AESEngine, EDEngine
9
+
10
+
11
+ class StringEncryptType(TypeDecorator):
12
+ impl = String
13
+ cache_ok = True
14
+
15
+ def __init__(self, engine: typing.Optional[EDEngine] = None, *args, **kwargs) -> None:
16
+ super().__init__(*args, **kwargs)
17
+
18
+ if not engine:
19
+ key = os.urandom(32)
20
+ iv = os.urandom(16)
21
+ padding_class = padding.PKCS7
22
+ self.engine = AESEngine(secret_key=key, iv=iv, padding_class=padding_class)
23
+ else:
24
+ self.engine = engine # type: ignore
25
+
26
+ def process_bind_param(self, value, dialect):
27
+ if value is None:
28
+ return value
29
+ if not isinstance(value, str):
30
+ raise ValueError("Value String Encrypt Type must be a string")
31
+ return self.engine.encrypt(value).decode(encoding="utf-8")
32
+
33
+ def process_result_value(self, value, dialect):
34
+ if value is None:
35
+ return value
36
+ return self.engine.decrypt(value)
37
+
38
+
39
+ class LargeBinaryEncryptType(StringEncryptType):
40
+ impl = LargeBinary
41
+ cache_ok = True
42
+
43
+ def __init__(self, engine: typing.Optional[EDEngine] = None, *args, **kwargs) -> None:
44
+ super().__init__(engine=engine, *args, **kwargs) # type: ignore
45
+
46
+ def process_bind_param(self, value, dialect):
47
+ if value is None:
48
+ return value
49
+ value = super().process_bind_param(value, dialect)
50
+ if isinstance(value, str):
51
+ return value.encode("utf-8")
52
+ return value
53
+
54
+ def process_result_value(self, value, dialect):
55
+ if isinstance(value, bytes):
56
+ value = value.decode("utf-8")
57
+ return super().process_result_value(value, dialect)
58
+ return value
@@ -0,0 +1,171 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ import weakref
4
+
5
+ import passlib
6
+ from passlib.context import LazyCryptContext
7
+ from sqlalchemy import types
8
+ from sqlalchemy.dialects import oracle, postgresql, sqlite
9
+ from sqlalchemy.ext.mutable import Mutable
10
+
11
+
12
+ class Password(Mutable):
13
+ @classmethod
14
+ def coerce(cls, key, value):
15
+ if isinstance(value, Password):
16
+ return value
17
+
18
+ if isinstance(value, (str, bytes)):
19
+ return cls(value, secret=True)
20
+
21
+ super().coerce(key, value)
22
+
23
+ def __init__(self, value, context=None, secret=False):
24
+ # Store the hash (if it is one).
25
+ self.hash = value if not secret else None
26
+
27
+ # Store the secret if we have one.
28
+ self.secret = value if secret else None
29
+
30
+ # The hash should be bytes.
31
+ if isinstance(self.hash, str):
32
+ self.hash = self.hash.encode("utf8")
33
+
34
+ # Save weakref of the password context (if we have one)
35
+ self.context = weakref.proxy(context) if context is not None else None
36
+
37
+ def __eq__(self, value):
38
+ if self.hash is None or value is None:
39
+ # Ensure that we don't continue comparison if one of us is None.
40
+ return self.hash is value
41
+
42
+ if isinstance(value, Password):
43
+ # Comparing 2 hashes isn't very useful; but this equality
44
+ # method breaks otherwise.
45
+ return value.hash == self.hash
46
+
47
+ if self.context is None:
48
+ # Compare 2 hashes again as we don't know how to validate.
49
+ return value == self
50
+
51
+ if isinstance(value, (str, bytes)):
52
+ valid, new = self.context.verify_and_update(value, self.hash)
53
+ if valid and new:
54
+ # New hash was calculated due to various reasons; stored one
55
+ # wasn't optimal, etc.
56
+ self.hash = new
57
+
58
+ # The hash should be bytes.
59
+ if isinstance(self.hash, str):
60
+ self.hash = self.hash.encode("utf8")
61
+ self.changed()
62
+
63
+ return valid
64
+
65
+ return False
66
+
67
+ def __ne__(self, value):
68
+ return self != value
69
+
70
+
71
+ class PasswordType(types.TypeDecorator):
72
+ impl = types.String
73
+ cache_ok = True
74
+
75
+ def __init__(self, max_length=None, **kwargs):
76
+ # Fail if passlib is not found.
77
+ if passlib is None:
78
+ raise ImportError("'passlib' is required to use 'PasswordType'")
79
+
80
+ # Construct the passlib crypt context.
81
+ self.context = LazyCryptContext(**kwargs)
82
+ self._max_length = max_length
83
+
84
+ @property
85
+ def hashing_method(self):
86
+ return "hash" if hasattr(self.context, "hash") else "encrypt"
87
+
88
+ @property
89
+ def max_length(self):
90
+ """Get column length."""
91
+ if self._max_length is None:
92
+ self._max_length = self.calculate_max_length()
93
+
94
+ return self._max_length
95
+
96
+ def calculate_max_length(self):
97
+ # Calculate the largest possible encoded password.
98
+ # name + rounds + salt + hash + ($ * 4) of largest hash
99
+ max_lengths = [1024]
100
+ for name in self.context.schemes():
101
+ scheme = getattr(__import__("passlib.hash").hash, name)
102
+ length = 4 + len(scheme.name)
103
+ length += len(str(getattr(scheme, "max_rounds", "")))
104
+ length += getattr(scheme, "max_salt_size", 0) or 0
105
+ length += getattr(scheme, "encoded_checksum_size", scheme.checksum_size)
106
+ max_lengths.append(length)
107
+
108
+ # Return the maximum calculated max length.
109
+ return max(max_lengths)
110
+
111
+ def load_dialect_impl(self, dialect):
112
+ if dialect.name == "postgresql":
113
+ # Use a BYTEA type for postgresql.
114
+ impl = postgresql.BYTEA(self.max_length)
115
+ elif dialect.name == "oracle":
116
+ # Use a RAW type for oracle.
117
+ impl = oracle.RAW(self.max_length)
118
+ elif dialect.name == "sqlite":
119
+ # Use a BLOB type for sqlite
120
+ impl = sqlite.BLOB(self.max_length)
121
+ else:
122
+ # Use a VARBINARY for all other dialects.
123
+ impl = types.VARBINARY(self.max_length)
124
+ return dialect.type_descriptor(impl)
125
+
126
+ def process_bind_param(self, value, dialect):
127
+ if isinstance(value, Password):
128
+ # If were given a password secret; hash it.
129
+ if value.secret is not None:
130
+ return self._hash(value.secret).encode("utf8")
131
+
132
+ # Value has already been hashed.
133
+ return value.hash
134
+
135
+ if isinstance(value, str):
136
+ # Assume value has not been hashed.
137
+ return self._hash(value).encode("utf8")
138
+
139
+ def process_result_value(self, value, dialect):
140
+ if value is not None:
141
+ return Password(value, self.context)
142
+
143
+ def _hash(self, value):
144
+ return getattr(self.context, self.hashing_method)(value)
145
+
146
+ def _coerce(self, value):
147
+ if value is None:
148
+ return
149
+
150
+ if not isinstance(value, Password):
151
+ # Hash the password using the default scheme.
152
+ value = self._hash(value).encode("utf8")
153
+ return Password(value, context=self.context)
154
+
155
+ else:
156
+ # If were given a password object; ensure the context is right.
157
+ value.context = weakref.proxy(self.context)
158
+
159
+ # If were given a password secret; hash it.
160
+ if value.secret is not None:
161
+ value.hash = self._hash(value.secret).encode("utf8")
162
+ value.secret = None
163
+
164
+ return value
165
+
166
+ @property
167
+ def python_type(self):
168
+ return self.impl.type.python_type
169
+
170
+
171
+ Password.associate_with(PasswordType)
@@ -0,0 +1,46 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ import sqlalchemy as sa
4
+ from sqlalchemy.dialects.postgresql import TSVECTOR
5
+
6
+
7
+ class TSVector(sa.types.TypeDecorator):
8
+ """
9
+ .. _TSVECTOR:
10
+ https://docs.sqlalchemy.org/en/latest/dialects/postgresql.html#full-text-search
11
+
12
+
13
+ class IndexModel(Model):
14
+ ....
15
+ search_vector = Column(
16
+ TSVector(),
17
+ Computed(
18
+ "to_tsvector('english', some_text|| ' ' ||some_text)",
19
+ persisted=True,
20
+ ),
21
+ )
22
+ session.query(IndexModel).filter(IndexModel.search_vector.match('foo'))
23
+
24
+ session.query(IndexModel).filter(
25
+ (IndexModel.name_vector | IndexModel.content_vector).match('foo')
26
+ )
27
+
28
+ """
29
+
30
+ impl = TSVECTOR
31
+ cache_ok = True
32
+
33
+ class comparator_factory(TSVECTOR.Comparator):
34
+ def match(self, other, **kwargs):
35
+ if "postgresql_regconfig" not in kwargs:
36
+ if "regconfig" in self.type.options:
37
+ kwargs["postgresql_regconfig"] = self.type.options["regconfig"]
38
+ return TSVECTOR.Comparator.match(self, other, **kwargs)
39
+
40
+ def __or__(self, other):
41
+ return self.op("||")(other)
42
+
43
+ def __init__(self, *args, **kwargs):
44
+ self.columns = args
45
+ self.options = kwargs
46
+ super(TSVector, self).__init__()
@@ -0,0 +1,15 @@
1
+ from sqlalchemy.types import TypeDecorator, Unicode
2
+
3
+
4
+ class UnicodeField(TypeDecorator):
5
+ impl = Unicode
6
+
7
+ def process_bind_param(self, value, dialect):
8
+ try:
9
+ value.encode("utf-8")
10
+ except UnicodeEncodeError:
11
+ raise ValueError("Value must be valid Unicode")
12
+ return value
13
+
14
+ def process_result_value(self, value, dialect):
15
+ return value
@@ -0,0 +1,290 @@
1
+ # -*- coding: utf-8 -*-
2
+ from functools import reduce
3
+ from typing import Any, Dict, Generic, Optional, Type, TypeVar
4
+
5
+ from sqlalchemy import Select, and_, asc, between, desc, select
6
+ from sqlalchemy.ext.asyncio import (
7
+ AsyncSession,
8
+ )
9
+ from sqlalchemy.orm import declarative_base
10
+ from sqlalchemy.sql import func
11
+
12
+ Base = declarative_base()
13
+
14
+
15
+ class Model(Base): # type: ignore
16
+ __abstract__ = True
17
+ __table_args__ = {"extend_existing": True}
18
+
19
+ @property
20
+ def as_dict(self) -> Dict[str, Any]:
21
+ return {c.name: getattr(self, c.name) for c in self.__table__.columns}
22
+
23
+
24
+ ModelType = TypeVar("ModelType", bound=Base) # type: ignore
25
+
26
+
27
+ class PostgresRepository(Generic[ModelType]):
28
+ """Base class for data repositories."""
29
+
30
+ def __init__(self, model: Type[ModelType], db_session: AsyncSession):
31
+ self.session = db_session # type: ignore
32
+ self.model_class: Type[ModelType] = model
33
+
34
+ async def create(self, attributes: Optional[dict[str, Any]] = None) -> ModelType:
35
+ """
36
+ Creates the model instance.
37
+
38
+ :param attributes: The attributes to create the model with.
39
+ :return: The created model instance.
40
+ """
41
+ if attributes is None:
42
+ attributes = {}
43
+ model = self.model_class(**attributes) # type: ignore
44
+ self.session.add(model)
45
+ return model
46
+
47
+ async def get_all(
48
+ self,
49
+ skip: int = 0,
50
+ limit: int = 100,
51
+ join_: set[str] | None = None,
52
+ where: Optional[dict] = None,
53
+ order_by: tuple[str, str] | None = None,
54
+ ) -> list[ModelType]:
55
+ """
56
+ Returns a list of model instances.
57
+
58
+ :param skip: The number of records to skip.
59
+ :param limit: The number of record to return.
60
+ :param join_: The joins to make.
61
+ :param where: The conditions for the WHERE clause.
62
+ :return: A list of model instances.
63
+ """
64
+ query = self._query(join_)
65
+ query = query.offset(skip).limit(limit)
66
+
67
+ if where is not None:
68
+ conditions = []
69
+ for k, v in where.items():
70
+ if isinstance(v, dict) and "$gt" in v and "$lt" in v:
71
+ conditions.append(between(getattr(self.model_class, k), v["$gt"], v["$lt"]))
72
+ else:
73
+ conditions.append(getattr(self.model_class, k) == v)
74
+ query = query.where(and_(*conditions))
75
+
76
+ if order_by is not None:
77
+ column, direction = order_by
78
+ if direction.lower() == "desc":
79
+ query = query.order_by(desc(getattr(self.model_class, column)))
80
+ else:
81
+ query = query.order_by(asc(getattr(self.model_class, column)))
82
+
83
+ if join_ is not None:
84
+ return await self.all_unique(query)
85
+ return await self._all(query)
86
+
87
+ async def get_by(
88
+ self,
89
+ field: str,
90
+ value: Any,
91
+ join_: set[str] | None = None,
92
+ unique: bool = False,
93
+ ) -> ModelType | list[ModelType] | None:
94
+ """
95
+ Returns the model instance matching the field and value.
96
+
97
+ :param field: The field to match.
98
+ :param value: The value to match.
99
+ :param join_: The joins to make.
100
+ :return: The model instance.
101
+ """
102
+ query = self._query(join_)
103
+ query = await self._get_by(query, field, value)
104
+
105
+ if join_ is not None:
106
+ return await self.all_unique(query)
107
+ if unique:
108
+ return await self._one(query)
109
+
110
+ return await self._all(query)
111
+
112
+ async def delete(self, model: ModelType) -> None:
113
+ """
114
+ Deletes the model.
115
+
116
+ :param model: The model to delete.
117
+ :return: None
118
+ """
119
+ await self.session.delete(model)
120
+
121
+ async def update(self, model: ModelType, attributes: dict[str, Any]) -> ModelType:
122
+ """
123
+ Updates the model.
124
+
125
+ :param model: The model to update.
126
+ :param attributes: The attributes to update the model with.
127
+ :return: The updated model instance.
128
+ """
129
+ for key, value in attributes.items():
130
+ if hasattr(model, key):
131
+ setattr(model, key, value)
132
+
133
+ self.session.add(model)
134
+ await self.session.commit()
135
+
136
+ return model
137
+
138
+ def _query(
139
+ self,
140
+ join_: set[str] | None = None,
141
+ order_: dict | None = None,
142
+ ) -> Select:
143
+ """
144
+ Returns a callable that can be used to query the model.
145
+
146
+ :param join_: The joins to make.
147
+ :param order_: The order of the results. (e.g desc, asc)
148
+ :return: A callable that can be used to query the model.
149
+ """
150
+ query = select(self.model_class)
151
+ query = self._maybe_join(query, join_)
152
+ query = self._maybe_ordered(query, order_)
153
+
154
+ return query
155
+
156
+ async def _all(self, query: Select) -> list[ModelType]:
157
+ """
158
+ Returns all results from the query.
159
+
160
+ :param query: The query to execute.
161
+ :return: A list of model instances.
162
+ """
163
+ query = await self.session.scalars(query)
164
+ return query.all()
165
+
166
+ async def all_unique(self, query: Select) -> list[ModelType]:
167
+ result = await self.session.execute(query)
168
+ return result.unique().scalars().all()
169
+
170
+ async def _first(self, query: Select) -> ModelType | None:
171
+ """
172
+ Returns the first result from the query.
173
+
174
+ :param query: The query to execute.
175
+ :return: The first model instance.
176
+ """
177
+ query = await self.session.scalars(query)
178
+ return query.first()
179
+
180
+ async def _one_or_none(self, query: Select) -> ModelType | None:
181
+ """Returns the first result from the query or None."""
182
+ query = await self.session.scalars(query)
183
+ return query.one_or_none()
184
+
185
+ async def _one(self, query: Select) -> ModelType:
186
+ """
187
+ Returns the first result from the query or raises NoResultFound.
188
+
189
+ :param query: The query to execute.
190
+ :return: The first model instance.
191
+ """
192
+ query = await self.session.scalars(query)
193
+ return query.one()
194
+
195
+ async def _count(self, query: Select) -> int:
196
+ """
197
+ Returns the count of the records.
198
+
199
+ :param query: The query to execute.
200
+ """
201
+ query = query.subquery()
202
+ query = await self.session.scalars(select(func.count()).select_from(query))
203
+ return query.one()
204
+
205
+ async def _sort_by(
206
+ self,
207
+ query: Select,
208
+ sort_by: str,
209
+ order: str | None = "asc",
210
+ model: Type[ModelType] | None = None,
211
+ case_insensitive: bool = False,
212
+ ) -> Select:
213
+ """
214
+ Returns the query sorted by the given column.
215
+
216
+ :param query: The query to sort.
217
+ :param sort_by: The column to sort by.
218
+ :param order: The order to sort by.
219
+ :param model: The model to sort.
220
+ :param case_insensitive: Whether to sort case insensitively.
221
+ :return: The sorted query.
222
+ """
223
+ model = model or self.model_class
224
+
225
+ order_column = None
226
+
227
+ if case_insensitive:
228
+ order_column = func.lower(getattr(model, sort_by))
229
+ else:
230
+ order_column = getattr(model, sort_by)
231
+
232
+ if order == "desc":
233
+ return query.order_by(order_column.desc())
234
+
235
+ return query.order_by(order_column.asc())
236
+
237
+ async def _get_by(self, query: Select, field: str, value: Any) -> Select:
238
+ """
239
+ Returns the query filtered by the given column.
240
+
241
+ :param query: The query to filter.
242
+ :param field: The column to filter by.
243
+ :param value: The value to filter by.
244
+ :return: The filtered query.
245
+ """
246
+ return query.where(getattr(self.model_class, field) == value)
247
+
248
+ def _maybe_join(self, query: Select, join_: set[str] | None = None) -> Select:
249
+ """
250
+ Returns the query with the given joins.
251
+
252
+ :param query: The query to join.
253
+ :param join_: The joins to make.
254
+ :return: The query with the given joins.
255
+ """
256
+ if not join_:
257
+ return query
258
+
259
+ if not isinstance(join_, set):
260
+ raise TypeError("join_ must be a set")
261
+
262
+ return reduce(self._add_join_to_query, join_, query) # type: ignore
263
+
264
+ def _maybe_ordered(self, query: Select, order_: dict | None = None) -> Select:
265
+ """
266
+ Returns the query ordered by the given column.
267
+
268
+ :param query: The query to order.
269
+ :param order_: The order to make.
270
+ :return: The query ordered by the given column.
271
+ """
272
+ if order_:
273
+ if order_["asc"]:
274
+ for order in order_["asc"]:
275
+ query = query.order_by(getattr(self.model_class, order).asc())
276
+ else:
277
+ for order in order_["desc"]:
278
+ query = query.order_by(getattr(self.model_class, order).desc())
279
+
280
+ return query
281
+
282
+ def _add_join_to_query(self, query: Select, join_: str) -> Select:
283
+ """
284
+ Returns the query with the given join.
285
+
286
+ :param query: The query to join.
287
+ :param join_: The join to make.
288
+ :return: The query with the given join.
289
+ """
290
+ 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"