telegrinder 0.4.2__py3-none-any.whl → 0.5.1__py3-none-any.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.

Potentially problematic release.


This version of telegrinder might be problematic. Click here for more details.

Files changed (233) hide show
  1. telegrinder/__init__.py +37 -55
  2. telegrinder/__meta__.py +1 -0
  3. telegrinder/api/__init__.py +6 -4
  4. telegrinder/api/api.py +100 -26
  5. telegrinder/api/error.py +42 -8
  6. telegrinder/api/response.py +4 -1
  7. telegrinder/api/token.py +2 -2
  8. telegrinder/bot/__init__.py +9 -25
  9. telegrinder/bot/bot.py +31 -25
  10. telegrinder/bot/cute_types/__init__.py +0 -0
  11. telegrinder/bot/cute_types/base.py +103 -61
  12. telegrinder/bot/cute_types/callback_query.py +447 -400
  13. telegrinder/bot/cute_types/chat_join_request.py +59 -62
  14. telegrinder/bot/cute_types/chat_member_updated.py +154 -157
  15. telegrinder/bot/cute_types/inline_query.py +41 -44
  16. telegrinder/bot/cute_types/message.py +98 -67
  17. telegrinder/bot/cute_types/pre_checkout_query.py +38 -42
  18. telegrinder/bot/cute_types/update.py +1 -8
  19. telegrinder/bot/cute_types/utils.py +1 -1
  20. telegrinder/bot/dispatch/__init__.py +10 -15
  21. telegrinder/bot/dispatch/abc.py +12 -11
  22. telegrinder/bot/dispatch/action.py +104 -0
  23. telegrinder/bot/dispatch/context.py +32 -26
  24. telegrinder/bot/dispatch/dispatch.py +61 -134
  25. telegrinder/bot/dispatch/handler/__init__.py +2 -0
  26. telegrinder/bot/dispatch/handler/abc.py +10 -8
  27. telegrinder/bot/dispatch/handler/audio_reply.py +2 -3
  28. telegrinder/bot/dispatch/handler/base.py +10 -33
  29. telegrinder/bot/dispatch/handler/document_reply.py +2 -3
  30. telegrinder/bot/dispatch/handler/func.py +55 -87
  31. telegrinder/bot/dispatch/handler/media_group_reply.py +2 -3
  32. telegrinder/bot/dispatch/handler/message_reply.py +2 -3
  33. telegrinder/bot/dispatch/handler/photo_reply.py +2 -3
  34. telegrinder/bot/dispatch/handler/sticker_reply.py +2 -3
  35. telegrinder/bot/dispatch/handler/video_reply.py +2 -3
  36. telegrinder/bot/dispatch/middleware/__init__.py +0 -0
  37. telegrinder/bot/dispatch/middleware/abc.py +79 -55
  38. telegrinder/bot/dispatch/middleware/global_middleware.py +18 -33
  39. telegrinder/bot/dispatch/process.py +84 -105
  40. telegrinder/bot/dispatch/return_manager/__init__.py +0 -0
  41. telegrinder/bot/dispatch/return_manager/abc.py +102 -65
  42. telegrinder/bot/dispatch/return_manager/callback_query.py +4 -5
  43. telegrinder/bot/dispatch/return_manager/inline_query.py +3 -4
  44. telegrinder/bot/dispatch/return_manager/message.py +8 -10
  45. telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +4 -5
  46. telegrinder/bot/dispatch/view/__init__.py +4 -4
  47. telegrinder/bot/dispatch/view/abc.py +6 -16
  48. telegrinder/bot/dispatch/view/base.py +54 -178
  49. telegrinder/bot/dispatch/view/box.py +19 -18
  50. telegrinder/bot/dispatch/view/callback_query.py +4 -8
  51. telegrinder/bot/dispatch/view/chat_join_request.py +5 -6
  52. telegrinder/bot/dispatch/view/chat_member.py +5 -25
  53. telegrinder/bot/dispatch/view/error.py +9 -0
  54. telegrinder/bot/dispatch/view/inline_query.py +4 -8
  55. telegrinder/bot/dispatch/view/message.py +5 -25
  56. telegrinder/bot/dispatch/view/pre_checkout_query.py +4 -8
  57. telegrinder/bot/dispatch/view/raw.py +3 -109
  58. telegrinder/bot/dispatch/waiter_machine/__init__.py +2 -5
  59. telegrinder/bot/dispatch/waiter_machine/actions.py +6 -4
  60. telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +1 -3
  61. telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +1 -1
  62. telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +11 -7
  63. telegrinder/bot/dispatch/waiter_machine/hasher/message.py +0 -0
  64. telegrinder/bot/dispatch/waiter_machine/machine.py +43 -60
  65. telegrinder/bot/dispatch/waiter_machine/middleware.py +19 -23
  66. telegrinder/bot/dispatch/waiter_machine/short_state.py +6 -5
  67. telegrinder/bot/polling/__init__.py +0 -0
  68. telegrinder/bot/polling/abc.py +0 -0
  69. telegrinder/bot/polling/polling.py +209 -88
  70. telegrinder/bot/rules/__init__.py +3 -16
  71. telegrinder/bot/rules/abc.py +42 -122
  72. telegrinder/bot/rules/callback_data.py +29 -49
  73. telegrinder/bot/rules/chat_join.py +5 -23
  74. telegrinder/bot/rules/command.py +8 -4
  75. telegrinder/bot/rules/enum_text.py +3 -4
  76. telegrinder/bot/rules/func.py +7 -14
  77. telegrinder/bot/rules/fuzzy.py +3 -4
  78. telegrinder/bot/rules/inline.py +8 -20
  79. telegrinder/bot/rules/integer.py +2 -3
  80. telegrinder/bot/rules/is_from.py +12 -11
  81. telegrinder/bot/rules/logic.py +11 -5
  82. telegrinder/bot/rules/markup.py +22 -14
  83. telegrinder/bot/rules/mention.py +8 -7
  84. telegrinder/bot/rules/message_entities.py +8 -4
  85. telegrinder/bot/rules/node.py +23 -12
  86. telegrinder/bot/rules/payload.py +5 -4
  87. telegrinder/bot/rules/payment_invoice.py +6 -21
  88. telegrinder/bot/rules/regex.py +2 -4
  89. telegrinder/bot/rules/rule_enum.py +8 -7
  90. telegrinder/bot/rules/start.py +5 -6
  91. telegrinder/bot/rules/state.py +1 -1
  92. telegrinder/bot/rules/text.py +4 -15
  93. telegrinder/bot/rules/update.py +3 -4
  94. telegrinder/bot/scenario/__init__.py +0 -0
  95. telegrinder/bot/scenario/abc.py +6 -5
  96. telegrinder/bot/scenario/checkbox.py +1 -1
  97. telegrinder/bot/scenario/choice.py +30 -39
  98. telegrinder/client/__init__.py +3 -5
  99. telegrinder/client/abc.py +11 -6
  100. telegrinder/client/aiohttp.py +141 -27
  101. telegrinder/client/form_data.py +1 -1
  102. telegrinder/model.py +61 -89
  103. telegrinder/modules.py +325 -102
  104. telegrinder/msgspec_utils/__init__.py +40 -0
  105. telegrinder/msgspec_utils/abc.py +18 -0
  106. telegrinder/msgspec_utils/custom_types/__init__.py +6 -0
  107. telegrinder/msgspec_utils/custom_types/datetime.py +24 -0
  108. telegrinder/msgspec_utils/custom_types/enum_meta.py +43 -0
  109. telegrinder/msgspec_utils/custom_types/literal.py +25 -0
  110. telegrinder/msgspec_utils/custom_types/option.py +17 -0
  111. telegrinder/msgspec_utils/decoder.py +389 -0
  112. telegrinder/msgspec_utils/encoder.py +206 -0
  113. telegrinder/{msgspec_json.py → msgspec_utils/json.py} +6 -5
  114. telegrinder/msgspec_utils/tools.py +75 -0
  115. telegrinder/node/__init__.py +24 -7
  116. telegrinder/node/attachment.py +1 -0
  117. telegrinder/node/base.py +154 -72
  118. telegrinder/node/callback_query.py +5 -5
  119. telegrinder/node/collection.py +39 -0
  120. telegrinder/node/command.py +1 -2
  121. telegrinder/node/composer.py +121 -72
  122. telegrinder/node/container.py +11 -8
  123. telegrinder/node/context.py +48 -0
  124. telegrinder/node/either.py +27 -40
  125. telegrinder/node/error.py +41 -0
  126. telegrinder/node/event.py +37 -11
  127. telegrinder/node/exceptions.py +7 -0
  128. telegrinder/node/file.py +0 -0
  129. telegrinder/node/i18n.py +108 -0
  130. telegrinder/node/me.py +3 -2
  131. telegrinder/node/payload.py +1 -1
  132. telegrinder/node/polymorphic.py +63 -28
  133. telegrinder/node/reply_message.py +12 -0
  134. telegrinder/node/rule.py +6 -13
  135. telegrinder/node/scope.py +14 -5
  136. telegrinder/node/session.py +53 -0
  137. telegrinder/node/source.py +41 -9
  138. telegrinder/node/text.py +1 -2
  139. telegrinder/node/tools/__init__.py +0 -0
  140. telegrinder/node/tools/generator.py +3 -5
  141. telegrinder/node/utility.py +16 -0
  142. telegrinder/py.typed +0 -0
  143. telegrinder/rules.py +0 -0
  144. telegrinder/tools/__init__.py +48 -88
  145. telegrinder/tools/aio.py +103 -0
  146. telegrinder/tools/callback_data_serialization/__init__.py +5 -0
  147. telegrinder/tools/{callback_data_serilization → callback_data_serialization}/abc.py +0 -0
  148. telegrinder/tools/{callback_data_serilization → callback_data_serialization}/json_ser.py +2 -3
  149. telegrinder/tools/{callback_data_serilization → callback_data_serialization}/msgpack_ser.py +45 -27
  150. telegrinder/tools/final.py +21 -0
  151. telegrinder/tools/formatting/__init__.py +2 -18
  152. telegrinder/tools/formatting/deep_links/__init__.py +39 -0
  153. telegrinder/tools/formatting/{deep_links.py → deep_links/links.py} +12 -85
  154. telegrinder/tools/formatting/deep_links/parsing.py +90 -0
  155. telegrinder/tools/formatting/deep_links/validators.py +8 -0
  156. telegrinder/tools/formatting/html_formatter.py +18 -45
  157. telegrinder/tools/fullname.py +83 -0
  158. telegrinder/tools/global_context/__init__.py +4 -3
  159. telegrinder/tools/global_context/abc.py +17 -14
  160. telegrinder/tools/global_context/builtin_context.py +39 -0
  161. telegrinder/tools/global_context/global_context.py +138 -39
  162. telegrinder/tools/input_file_directory.py +0 -0
  163. telegrinder/tools/keyboard/__init__.py +39 -0
  164. telegrinder/tools/keyboard/abc.py +159 -0
  165. telegrinder/tools/keyboard/base.py +77 -0
  166. telegrinder/tools/keyboard/buttons/__init__.py +14 -0
  167. telegrinder/tools/keyboard/buttons/base.py +18 -0
  168. telegrinder/tools/{buttons.py → keyboard/buttons/buttons.py} +71 -23
  169. telegrinder/tools/keyboard/buttons/static_buttons.py +56 -0
  170. telegrinder/tools/keyboard/buttons/tools.py +18 -0
  171. telegrinder/tools/keyboard/data.py +20 -0
  172. telegrinder/tools/keyboard/keyboard.py +131 -0
  173. telegrinder/tools/keyboard/static_keyboard.py +83 -0
  174. telegrinder/tools/lifespan.py +87 -51
  175. telegrinder/tools/limited_dict.py +4 -1
  176. telegrinder/tools/loop_wrapper.py +332 -0
  177. telegrinder/tools/magic/__init__.py +32 -0
  178. telegrinder/tools/magic/annotations.py +165 -0
  179. telegrinder/tools/magic/dictionary.py +20 -0
  180. telegrinder/tools/magic/function.py +246 -0
  181. telegrinder/tools/magic/shortcut.py +111 -0
  182. telegrinder/tools/parse_mode.py +9 -3
  183. telegrinder/tools/singleton/__init__.py +4 -0
  184. telegrinder/tools/singleton/abc.py +14 -0
  185. telegrinder/tools/singleton/singleton.py +18 -0
  186. telegrinder/tools/state_storage/__init__.py +0 -0
  187. telegrinder/tools/state_storage/abc.py +6 -1
  188. telegrinder/tools/state_storage/memory.py +1 -1
  189. telegrinder/tools/strings.py +0 -0
  190. telegrinder/types/__init__.py +307 -268
  191. telegrinder/types/enums.py +68 -37
  192. telegrinder/types/input_file.py +3 -3
  193. telegrinder/types/methods.py +5699 -5055
  194. telegrinder/types/methods_utils.py +62 -0
  195. telegrinder/types/objects.py +1782 -994
  196. telegrinder/verification_utils.py +3 -1
  197. telegrinder-0.5.1.dist-info/METADATA +162 -0
  198. telegrinder-0.5.1.dist-info/RECORD +200 -0
  199. {telegrinder-0.4.2.dist-info → telegrinder-0.5.1.dist-info}/licenses/LICENSE +2 -2
  200. telegrinder/bot/dispatch/waiter_machine/hasher/state.py +0 -20
  201. telegrinder/bot/rules/id.py +0 -24
  202. telegrinder/bot/rules/message.py +0 -15
  203. telegrinder/client/sonic.py +0 -212
  204. telegrinder/msgspec_utils.py +0 -478
  205. telegrinder/tools/adapter/__init__.py +0 -19
  206. telegrinder/tools/adapter/abc.py +0 -49
  207. telegrinder/tools/adapter/dataclass.py +0 -56
  208. telegrinder/tools/adapter/errors.py +0 -5
  209. telegrinder/tools/adapter/event.py +0 -61
  210. telegrinder/tools/adapter/node.py +0 -46
  211. telegrinder/tools/adapter/raw_event.py +0 -27
  212. telegrinder/tools/adapter/raw_update.py +0 -30
  213. telegrinder/tools/callback_data_serilization/__init__.py +0 -5
  214. telegrinder/tools/error_handler/__init__.py +0 -10
  215. telegrinder/tools/error_handler/abc.py +0 -30
  216. telegrinder/tools/error_handler/error.py +0 -9
  217. telegrinder/tools/error_handler/error_handler.py +0 -179
  218. telegrinder/tools/formatting/spec_html_formats.py +0 -75
  219. telegrinder/tools/functional.py +0 -8
  220. telegrinder/tools/global_context/telegrinder_ctx.py +0 -27
  221. telegrinder/tools/i18n/__init__.py +0 -12
  222. telegrinder/tools/i18n/abc.py +0 -32
  223. telegrinder/tools/i18n/middleware/__init__.py +0 -3
  224. telegrinder/tools/i18n/middleware/abc.py +0 -22
  225. telegrinder/tools/i18n/simple.py +0 -43
  226. telegrinder/tools/keyboard.py +0 -132
  227. telegrinder/tools/loop_wrapper/__init__.py +0 -4
  228. telegrinder/tools/loop_wrapper/abc.py +0 -20
  229. telegrinder/tools/loop_wrapper/loop_wrapper.py +0 -169
  230. telegrinder/tools/magic.py +0 -344
  231. telegrinder-0.4.2.dist-info/METADATA +0 -151
  232. telegrinder-0.4.2.dist-info/RECORD +0 -182
  233. {telegrinder-0.4.2.dist-info → telegrinder-0.5.1.dist-info}/WHEEL +0 -0
@@ -0,0 +1,25 @@
1
+ import typing
2
+
3
+
4
+ class _LiteralMeta(type):
5
+ __args__: tuple[typing.Any, ...]
6
+
7
+ def __getitem__[T](cls: type[T], values: typing.Any, /) -> type[T]:
8
+ values = (values,) if not isinstance(values, tuple) else values
9
+ return _LiteralMeta(cls.__name__, (cls,), {"__args__": values}) # type: ignore
10
+
11
+ def __instancecheck__(cls, instance: typing.Any, /) -> bool:
12
+ return instance in cls.__args__
13
+
14
+
15
+ class _Literal(metaclass=_LiteralMeta):
16
+ pass
17
+
18
+
19
+ if typing.TYPE_CHECKING:
20
+ from typing import Literal
21
+ else:
22
+ Literal = _Literal
23
+
24
+
25
+ __all__ = ("Literal",)
@@ -0,0 +1,17 @@
1
+ import typing
2
+
3
+ if typing.TYPE_CHECKING:
4
+ from fntypes.option import Option
5
+ else:
6
+ import fntypes
7
+ import msgspec
8
+
9
+ class OptionMeta(type):
10
+ def __instancecheck__(cls, __instance):
11
+ return isinstance(__instance, (fntypes.option.Some | fntypes.option.Nothing, msgspec.UnsetType))
12
+
13
+ class Option[Value](metaclass=OptionMeta):
14
+ pass
15
+
16
+
17
+ __all__ = ("Option",)
@@ -0,0 +1,389 @@
1
+ import datetime as dt
2
+ import typing
3
+ from contextlib import contextmanager
4
+
5
+ import fntypes.co
6
+ import msgspec
7
+ from fntypes.result import Error, Ok, Result
8
+ from fntypes.variative import Variative
9
+
10
+ from telegrinder.msgspec_utils.abc import SupportsCast
11
+ from telegrinder.msgspec_utils.custom_types.datetime import datetime, timedelta
12
+ from telegrinder.msgspec_utils.custom_types.enum_meta import BaseEnumMeta
13
+ from telegrinder.msgspec_utils.custom_types.literal import _Literal
14
+ from telegrinder.msgspec_utils.custom_types.option import Option
15
+ from telegrinder.msgspec_utils.tools import (
16
+ bundle,
17
+ fullname,
18
+ get_origin,
19
+ is_common_type,
20
+ type_check,
21
+ )
22
+
23
+ type Context = dict[str, typing.Any]
24
+ type DecHook[T] = typing.Callable[typing.Concatenate[type[T], typing.Any, ...], typing.Any]
25
+
26
+
27
+ def option_dec_hook(
28
+ tp: type[Option[typing.Any]],
29
+ obj: typing.Any,
30
+ /,
31
+ ) -> fntypes.co.Option[typing.Any] | msgspec.UnsetType:
32
+ if obj is msgspec.UNSET:
33
+ return obj
34
+
35
+ if obj is None or isinstance(obj, fntypes.co.Nothing):
36
+ return fntypes.co.Nothing()
37
+
38
+ (value_type,) = typing.get_args(tp) or (typing.Any,)
39
+ orig_value_type = typing.get_origin(value_type) or value_type
40
+ orig_obj = obj
41
+
42
+ if not isinstance(orig_obj, dict | list) and is_common_type(orig_value_type):
43
+ if orig_value_type is Variative:
44
+ obj = value_type(orig_obj) # type: ignore
45
+ orig_value_type = typing.get_args(value_type)
46
+
47
+ if not type_check(orig_obj, orig_value_type):
48
+ raise msgspec.ValidationError(
49
+ f"Expected `{fullname(orig_value_type)}` or `builtins.None`, got `{fullname(orig_obj)}`.",
50
+ )
51
+
52
+ return fntypes.co.Some(obj)
53
+
54
+ return fntypes.co.Some(decoder.convert(orig_obj, type=value_type))
55
+
56
+
57
+ def variative_dec_hook(tp: type[Variative], obj: typing.Any, /) -> Variative:
58
+ union_types = typing.get_args(tp)
59
+
60
+ if isinstance(obj, dict):
61
+ reverse = False
62
+ models_fields_count: dict[type[msgspec.Struct], int] = {
63
+ m: sum(1 for k in obj if k in m.__struct_fields__)
64
+ for m in union_types
65
+ if issubclass(get_origin(m), msgspec.Struct)
66
+ }
67
+ union_types = tuple(t for t in union_types if t not in models_fields_count)
68
+
69
+ if len(set(models_fields_count.values())) != len(models_fields_count.values()):
70
+ models_fields_count = {m: len(m.__struct_fields__) for m in models_fields_count}
71
+ reverse = True
72
+
73
+ union_types = (
74
+ *sorted(
75
+ models_fields_count,
76
+ key=lambda k: models_fields_count[k],
77
+ reverse=reverse,
78
+ ),
79
+ *union_types,
80
+ )
81
+
82
+ if not isinstance(obj, dict | list) and any(is_common_type(t) and type_check(obj, t) for t in union_types):
83
+ return tp(obj)
84
+
85
+ for t in union_types:
86
+ match convert(obj, t):
87
+ case Ok(value):
88
+ return tp(value)
89
+ case Error(_):
90
+ continue
91
+ case _ as arg:
92
+ typing.assert_never(arg)
93
+
94
+ raise msgspec.ValidationError(
95
+ "Object of type `{}` doesn't belong to `{}[{}]`.".format(
96
+ fullname(obj),
97
+ fullname(Variative),
98
+ ", ".join(fullname(get_origin(x)) for x in union_types),
99
+ )
100
+ )
101
+
102
+
103
+ def datetime_dec_hook(tp: type[datetime], obj: typing.Any, /) -> datetime:
104
+ if isinstance(obj, datetime):
105
+ return obj
106
+
107
+ if isinstance(obj, dt.datetime):
108
+ assert issubclass(tp, SupportsCast)
109
+ return tp.cast(obj)
110
+
111
+ if isinstance(obj, int | float):
112
+ return tp.fromtimestamp(timestamp=obj)
113
+
114
+ raise TypeError(
115
+ "Cannot validate object of type `{}` into `datetime.datetime`".format(
116
+ fullname(obj),
117
+ ),
118
+ )
119
+
120
+
121
+ def timedelta_dec_hook(tp: type[timedelta], obj: typing.Any, /) -> timedelta:
122
+ if isinstance(obj, timedelta):
123
+ return obj
124
+
125
+ if isinstance(obj, dt.timedelta):
126
+ assert issubclass(tp, SupportsCast)
127
+ return tp.cast(obj)
128
+
129
+ if isinstance(obj, int | float):
130
+ return tp(seconds=obj)
131
+
132
+ raise TypeError(
133
+ "Cannot validate object of type `{}` into `datetime.timedelta`".format(
134
+ fullname(obj),
135
+ ),
136
+ )
137
+
138
+
139
+ def convert[T](
140
+ obj: typing.Any,
141
+ t: type[T],
142
+ /,
143
+ *,
144
+ context: Context | None = None,
145
+ ) -> Result[T, str]:
146
+ try:
147
+ return Ok(decoder.convert(obj, type=t, strict=True, context=context))
148
+ except msgspec.ValidationError:
149
+ return Error(
150
+ "Expected object of type `{}`, got `{}`.".format(
151
+ fullname(t),
152
+ fullname(obj),
153
+ )
154
+ )
155
+
156
+
157
+ def literal_dec_hook(literal: type[_Literal], obj: typing.Any, /) -> typing.Any:
158
+ if obj in literal.__args__:
159
+ return obj
160
+
161
+ raise msgspec.ValidationError(
162
+ "Invalid literal value {!r} of either {}.".format(
163
+ obj,
164
+ literal.__args__[0]
165
+ if len(literal.__args__) == 1
166
+ else (", ".join(f"`{x}`" for x in literal.__args__[:-1])) + f" or `{literal.__args__[-1]}`",
167
+ )
168
+ )
169
+
170
+
171
+ class Decoder:
172
+ """Class `Decoder` for `msgspec` module with decode hook
173
+ for objects with the specified type.
174
+
175
+ ```
176
+ import enum
177
+
178
+ from datetime import datetime as dt
179
+
180
+ class Digit(enum.IntEnum):
181
+ ONE = 1
182
+ TWO = 2
183
+ THREE = 3
184
+
185
+ decoder = Decoder()
186
+ decoder.dec_hooks[dt] = lambda t, timestamp: t.fromtimestamp(timestamp)
187
+
188
+ decoder.dec_hook(dt, 1713354732) #> datetime.datetime(2024, 4, 17, 14, 52, 12)
189
+
190
+ decoder.convert("123", type=int, strict=False) #> 123
191
+ decoder.convert(1, type=Digit) #> <Digit.ONE: 1>
192
+
193
+ decoder.decode(b'{"digit":3}', type=dict[str, Digit]) #> {'digit': <Digit.THREE: 3>}
194
+ ```
195
+ """
196
+
197
+ dec_hooks: dict[typing.Any, DecHook[typing.Any]]
198
+ abstract_dec_hooks: dict[typing.Any, DecHook[typing.Any]]
199
+
200
+ def __init__(self) -> None:
201
+ self.dec_hooks = {
202
+ Option: option_dec_hook,
203
+ Variative: variative_dec_hook,
204
+ datetime: datetime_dec_hook,
205
+ timedelta: timedelta_dec_hook,
206
+ fntypes.option.Some: option_dec_hook,
207
+ fntypes.option.Nothing: option_dec_hook,
208
+ }
209
+ self.abstract_dec_hooks = {
210
+ BaseEnumMeta: lambda enum_type, member: enum_type(member),
211
+ _Literal: literal_dec_hook,
212
+ }
213
+
214
+ def __repr__(self) -> str:
215
+ return "<{}: dec_hooks={!r}, abstract_dec_hooks={!r}>".format(
216
+ type(self).__name__,
217
+ self.dec_hooks,
218
+ self.abstract_dec_hooks,
219
+ )
220
+
221
+ @typing.overload
222
+ def __call__[T](
223
+ self,
224
+ type: type[T],
225
+ context: Context | None = None,
226
+ ) -> typing.ContextManager[msgspec.json.Decoder[T]]: ...
227
+
228
+ @typing.overload
229
+ def __call__(
230
+ self,
231
+ type: typing.Any,
232
+ context: Context | None = None,
233
+ ) -> typing.ContextManager[msgspec.json.Decoder[typing.Any]]: ...
234
+
235
+ @typing.overload
236
+ def __call__[T](
237
+ self,
238
+ type: type[T],
239
+ *,
240
+ strict: bool = True,
241
+ context: Context | None = None,
242
+ ) -> typing.ContextManager[msgspec.json.Decoder[T]]: ...
243
+
244
+ @typing.overload
245
+ def __call__(
246
+ self,
247
+ type: typing.Any,
248
+ *,
249
+ strict: bool = True,
250
+ context: Context | None = None,
251
+ ) -> typing.ContextManager[msgspec.json.Decoder[typing.Any]]: ...
252
+
253
+ @contextmanager
254
+ def __call__(
255
+ self,
256
+ type: typing.Any = object,
257
+ *,
258
+ strict: bool = True,
259
+ context: Context | None = None,
260
+ ) -> typing.Generator[msgspec.json.Decoder, typing.Any, None]:
261
+ """Context manager returns an `msgspec.json.Decoder` object with passed the `dec_hook`."""
262
+ yield msgspec.json.Decoder(
263
+ type=typing.Any if type is object else type,
264
+ strict=strict,
265
+ dec_hook=self.dec_hook(context),
266
+ )
267
+
268
+ def add_dec_hook[T](self, t: type[T], /) -> typing.Callable[[DecHook[T]], DecHook[T]]:
269
+ def decorator(func: DecHook[T], /) -> DecHook[T]:
270
+ return self.dec_hooks.setdefault(get_origin(t), func)
271
+
272
+ return decorator
273
+
274
+ def add_abstract_dec_hook[T](self, abstract_type: type[T], /) -> typing.Callable[[DecHook[T]], DecHook[T]]:
275
+ def decorator(func: DecHook[T], /) -> DecHook[T]:
276
+ return self.abstract_dec_hooks.setdefault(get_origin(abstract_type), func)
277
+
278
+ return decorator
279
+
280
+ def get_abstract_dec_hook(self, subtype: type[typing.Any], /) -> DecHook[typing.Any] | None:
281
+ for abstract, dec_hook in self.abstract_dec_hooks.items():
282
+ if issubclass(subtype, abstract) or issubclass(type(subtype), abstract):
283
+ return dec_hook
284
+
285
+ return None
286
+
287
+ def dec_hook(self, context: Context | None = None, /) -> DecHook[typing.Any]:
288
+ def inner(tp: typing.Any, obj: typing.Any, /) -> typing.Any:
289
+ origin_type = get_origin(tp)
290
+
291
+ if (dec_hook_func := self.dec_hooks.get(origin_type)) is None and (
292
+ dec_hook_func := self.get_abstract_dec_hook(origin_type)
293
+ ) is None:
294
+ raise NotImplementedError(
295
+ f"Not implemented decode hook for type `{fullname(origin_type)}`. "
296
+ "You can implement decode hook for this type.",
297
+ )
298
+
299
+ return bundle(dec_hook_func, context or {}, start_idx=2)(tp, obj)
300
+
301
+ return inner
302
+
303
+ def convert[T](
304
+ self,
305
+ obj: object,
306
+ *,
307
+ type: type[T] = dict,
308
+ strict: bool = True,
309
+ from_attributes: bool = False,
310
+ builtin_types: typing.Iterable[type[typing.Any]] | None = None,
311
+ str_keys: bool = False,
312
+ context: Context | None = None,
313
+ ) -> T:
314
+ return msgspec.convert(
315
+ obj,
316
+ type,
317
+ strict=strict,
318
+ from_attributes=from_attributes,
319
+ dec_hook=self.dec_hook(context),
320
+ builtin_types=builtin_types,
321
+ str_keys=str_keys,
322
+ )
323
+
324
+ @typing.overload
325
+ def decode(
326
+ self,
327
+ buf: str | bytes,
328
+ *,
329
+ context: Context | None = None,
330
+ ) -> typing.Any: ...
331
+
332
+ @typing.overload
333
+ def decode[T](
334
+ self,
335
+ buf: str | bytes,
336
+ *,
337
+ type: type[T],
338
+ context: Context | None = None,
339
+ ) -> T: ...
340
+
341
+ @typing.overload
342
+ def decode(
343
+ self,
344
+ buf: str | bytes,
345
+ *,
346
+ type: typing.Any,
347
+ context: Context | None = None,
348
+ ) -> typing.Any: ...
349
+
350
+ @typing.overload
351
+ def decode[T](
352
+ self,
353
+ buf: str | bytes,
354
+ *,
355
+ type: type[T],
356
+ strict: bool = True,
357
+ context: Context | None = None,
358
+ ) -> T: ...
359
+
360
+ @typing.overload
361
+ def decode(
362
+ self,
363
+ buf: str | bytes,
364
+ *,
365
+ type: typing.Any,
366
+ strict: bool = True,
367
+ context: Context | None = None,
368
+ ) -> typing.Any: ...
369
+
370
+ def decode(
371
+ self,
372
+ buf: str | bytes,
373
+ *,
374
+ type: typing.Any = object,
375
+ strict: bool = True,
376
+ context: Context | None = None,
377
+ ) -> typing.Any:
378
+ return msgspec.json.decode(
379
+ buf,
380
+ type=typing.Any if type is object else type,
381
+ strict=strict,
382
+ dec_hook=self.dec_hook(context),
383
+ )
384
+
385
+
386
+ decoder: typing.Final[Decoder] = Decoder()
387
+
388
+
389
+ __all__ = ("Decoder", "convert", "decoder")
@@ -0,0 +1,206 @@
1
+ import datetime as dt
2
+ import typing
3
+ from contextlib import contextmanager
4
+
5
+ import msgspec
6
+ from fntypes.option import Nothing, Some
7
+ from fntypes.result import Error, Ok, Result
8
+ from fntypes.variative import Variative
9
+
10
+ from telegrinder.msgspec_utils.abc import SupportsCast
11
+ from telegrinder.msgspec_utils.custom_types.datetime import datetime, timedelta
12
+ from telegrinder.msgspec_utils.custom_types.enum_meta import BaseEnumMeta
13
+ from telegrinder.msgspec_utils.tools import bundle, fullname, get_origin
14
+
15
+ type Context = dict[str, typing.Any]
16
+ type Order = typing.Literal["deterministic", "sorted"]
17
+ type EncHook[T] = typing.Callable[typing.Concatenate[T, ...], typing.Any]
18
+
19
+
20
+ def to_builtins(
21
+ obj: typing.Any,
22
+ *,
23
+ str_keys: bool = False,
24
+ builtin_types: typing.Iterable[type[typing.Any]] | None = None,
25
+ order: Order | None = None,
26
+ context: Context | None = None,
27
+ ) -> Result[typing.Any, msgspec.ValidationError]:
28
+ try:
29
+ return Ok(
30
+ encoder.to_builtins(
31
+ obj,
32
+ str_keys=str_keys,
33
+ builtin_types=builtin_types,
34
+ order=order,
35
+ context=context,
36
+ ),
37
+ )
38
+ except msgspec.ValidationError as error:
39
+ return Error(error)
40
+
41
+
42
+ class Encoder:
43
+ """Class `Encoder` for `msgspec` module with encode hooks for objects.
44
+
45
+ ```
46
+ from datetime import datetime
47
+
48
+ class MyDatetime(datetime):
49
+ ...
50
+
51
+ encoder = Encoder()
52
+ encoder.enc_hooks[MyDatetime] = lambda d: int(d.timestamp())
53
+
54
+ encoder.enc_hook(MyDatetime.now()) #> 1713354732
55
+ encoder.encode({"digit": Digit.ONE}) #> '{"digit":1}'
56
+ ```
57
+ """
58
+
59
+ cast_types: dict[type[typing.Any], type[SupportsCast]]
60
+ enc_hooks: dict[typing.Any, EncHook[typing.Any]]
61
+ abstract_enc_hooks: dict[typing.Any, EncHook[typing.Any]]
62
+
63
+ def __init__(self) -> None:
64
+ self.cast_types = { # type: ignore
65
+ dt.datetime: datetime,
66
+ dt.timedelta: timedelta,
67
+ }
68
+ self.enc_hooks = {
69
+ Some: lambda some: some.value,
70
+ Nothing: lambda _: None,
71
+ Variative: lambda variative: variative.v,
72
+ datetime: lambda date: int(date.timestamp()),
73
+ timedelta: lambda time: time.total_seconds(),
74
+ }
75
+ self.abstract_enc_hooks = {
76
+ BaseEnumMeta: lambda enum_member: enum_member.value,
77
+ }
78
+
79
+ def __repr__(self) -> str:
80
+ return "<{}: cast_types={!r}, enc_hooks={!r}, abstract_enc_hooks={!r}>".format(
81
+ type(self).__name__,
82
+ self.cast_types,
83
+ self.enc_hooks,
84
+ self.abstract_enc_hooks,
85
+ )
86
+
87
+ @contextmanager
88
+ def __call__(
89
+ self,
90
+ *,
91
+ decimal_format: typing.Literal["string", "number"] = "string",
92
+ uuid_format: typing.Literal["canonical", "hex"] = "canonical",
93
+ order: Order | None = None,
94
+ context: Context | None = None,
95
+ ) -> typing.Generator[msgspec.json.Encoder, typing.Any, None]:
96
+ """Context manager returns the `msgspec.json.Encoder` object with passed the `enc_hook`."""
97
+ yield msgspec.json.Encoder(
98
+ enc_hook=self.enc_hook(context),
99
+ decimal_format=decimal_format,
100
+ uuid_format=uuid_format,
101
+ order=order,
102
+ )
103
+
104
+ def add_enc_hook[T](self, t: type[T], /) -> typing.Callable[[EncHook[T]], EncHook[T]]:
105
+ def decorator(func: EncHook[T], /) -> EncHook[T]:
106
+ encode_hook = self.enc_hooks.setdefault(get_origin(t), func)
107
+ return func if encode_hook is not func else encode_hook
108
+
109
+ return decorator
110
+
111
+ def add_abstract_enc_hook[T](self, abstract_type: type[T], /) -> typing.Callable[[EncHook[T]], EncHook[T]]:
112
+ def decorator(func: EncHook[T], /) -> EncHook[T]:
113
+ return self.abstract_enc_hooks.setdefault(get_origin(abstract_type), func)
114
+
115
+ return decorator
116
+
117
+ def get_abstract_enc_hook(self, subtype: type[typing.Any], /) -> EncHook[typing.Any] | None:
118
+ for abstract, enc_hook in self.abstract_enc_hooks.items():
119
+ if issubclass(subtype, abstract) or issubclass(type(subtype), abstract):
120
+ return enc_hook
121
+
122
+ return None
123
+
124
+ def enc_hook(self, context: Context | None = None, /) -> EncHook[typing.Any]:
125
+ def inner(obj: typing.Any, /) -> typing.Any:
126
+ origin_type = get_origin(obj)
127
+
128
+ if (enc_hook_func := self.enc_hooks.get(origin_type)) is None and (
129
+ enc_hook_func := self.get_abstract_enc_hook(origin_type)
130
+ ) is None:
131
+ raise NotImplementedError(
132
+ f"Not implemented encode hook for object of type `{fullname(origin_type)}`. "
133
+ "You can implement encode hook for this object.",
134
+ )
135
+
136
+ return bundle(enc_hook_func, context or {}, start_idx=1)(obj)
137
+
138
+ return inner
139
+
140
+ @typing.overload
141
+ def encode(
142
+ self,
143
+ obj: typing.Any,
144
+ *,
145
+ order: Order | None = None,
146
+ context: Context | None = None,
147
+ ) -> str: ...
148
+
149
+ @typing.overload
150
+ def encode(
151
+ self,
152
+ obj: typing.Any,
153
+ *,
154
+ as_str: typing.Literal[True],
155
+ order: Order | None = None,
156
+ context: Context | None = None,
157
+ ) -> str: ...
158
+
159
+ @typing.overload
160
+ def encode(
161
+ self,
162
+ obj: typing.Any,
163
+ *,
164
+ as_str: typing.Literal[False],
165
+ order: Order | None = None,
166
+ context: Context | None = None,
167
+ ) -> bytes: ...
168
+
169
+ def encode(
170
+ self,
171
+ obj: typing.Any,
172
+ *,
173
+ as_str: bool = True,
174
+ order: Order | None = None,
175
+ context: Context | None = None,
176
+ ) -> str | bytes:
177
+ buf = msgspec.json.encode(obj, enc_hook=self.enc_hook(context), order=order)
178
+ return buf.decode() if as_str else buf
179
+
180
+ def to_builtins(
181
+ self,
182
+ obj: typing.Any,
183
+ *,
184
+ str_keys: bool = False,
185
+ builtin_types: typing.Iterable[type[typing.Any]] | None = None,
186
+ order: Order | None = None,
187
+ context: Context | None = None,
188
+ ) -> typing.Any:
189
+ return msgspec.to_builtins(
190
+ obj,
191
+ str_keys=str_keys,
192
+ builtin_types=builtin_types,
193
+ enc_hook=self.enc_hook(context),
194
+ order=order,
195
+ )
196
+
197
+ def cast(self, obj: typing.Any, /) -> typing.Any:
198
+ if (caster := self.cast_types.get(get_origin(obj))) is not None:
199
+ return caster.cast(obj)
200
+ return obj
201
+
202
+
203
+ encoder: typing.Final[Encoder] = Encoder()
204
+
205
+
206
+ __all__ = ("Encoder", "encoder", "to_builtins")
@@ -1,14 +1,15 @@
1
1
  import typing
2
2
 
3
- from telegrinder.msgspec_utils import decoder, encoder
4
-
5
-
6
- def loads(s: str | bytes) -> typing.Any:
7
- return decoder.decode(s)
3
+ from telegrinder.msgspec_utils.decoder import decoder
4
+ from telegrinder.msgspec_utils.encoder import encoder
8
5
 
9
6
 
10
7
  def dumps(o: typing.Any) -> str:
11
8
  return encoder.encode(o)
12
9
 
13
10
 
11
+ def loads(s: str | bytes) -> typing.Any:
12
+ return decoder.decode(s)
13
+
14
+
14
15
  __all__ = ("dumps", "loads")