telegrinder 0.4.2__py3-none-any.whl → 0.5.0__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.
- telegrinder/__init__.py +37 -55
- telegrinder/__meta__.py +1 -0
- telegrinder/api/__init__.py +6 -4
- telegrinder/api/api.py +100 -26
- telegrinder/api/error.py +42 -8
- telegrinder/api/response.py +4 -1
- telegrinder/api/token.py +2 -2
- telegrinder/bot/__init__.py +9 -25
- telegrinder/bot/bot.py +31 -25
- telegrinder/bot/cute_types/__init__.py +0 -0
- telegrinder/bot/cute_types/base.py +103 -61
- telegrinder/bot/cute_types/callback_query.py +447 -400
- telegrinder/bot/cute_types/chat_join_request.py +59 -62
- telegrinder/bot/cute_types/chat_member_updated.py +154 -157
- telegrinder/bot/cute_types/inline_query.py +41 -44
- telegrinder/bot/cute_types/message.py +2621 -2590
- telegrinder/bot/cute_types/pre_checkout_query.py +38 -42
- telegrinder/bot/cute_types/update.py +1 -8
- telegrinder/bot/cute_types/utils.py +1 -1
- telegrinder/bot/dispatch/__init__.py +10 -15
- telegrinder/bot/dispatch/abc.py +12 -11
- telegrinder/bot/dispatch/action.py +104 -0
- telegrinder/bot/dispatch/context.py +32 -26
- telegrinder/bot/dispatch/dispatch.py +61 -134
- telegrinder/bot/dispatch/handler/__init__.py +2 -0
- telegrinder/bot/dispatch/handler/abc.py +10 -8
- telegrinder/bot/dispatch/handler/audio_reply.py +2 -3
- telegrinder/bot/dispatch/handler/base.py +10 -33
- telegrinder/bot/dispatch/handler/document_reply.py +2 -3
- telegrinder/bot/dispatch/handler/func.py +55 -87
- telegrinder/bot/dispatch/handler/media_group_reply.py +2 -3
- telegrinder/bot/dispatch/handler/message_reply.py +2 -3
- telegrinder/bot/dispatch/handler/photo_reply.py +2 -3
- telegrinder/bot/dispatch/handler/sticker_reply.py +2 -3
- telegrinder/bot/dispatch/handler/video_reply.py +2 -3
- telegrinder/bot/dispatch/middleware/__init__.py +0 -0
- telegrinder/bot/dispatch/middleware/abc.py +79 -55
- telegrinder/bot/dispatch/middleware/global_middleware.py +18 -33
- telegrinder/bot/dispatch/process.py +84 -105
- telegrinder/bot/dispatch/return_manager/__init__.py +0 -0
- telegrinder/bot/dispatch/return_manager/abc.py +102 -65
- telegrinder/bot/dispatch/return_manager/callback_query.py +4 -5
- telegrinder/bot/dispatch/return_manager/inline_query.py +3 -4
- telegrinder/bot/dispatch/return_manager/message.py +8 -10
- telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +4 -5
- telegrinder/bot/dispatch/view/__init__.py +4 -4
- telegrinder/bot/dispatch/view/abc.py +6 -16
- telegrinder/bot/dispatch/view/base.py +54 -178
- telegrinder/bot/dispatch/view/box.py +19 -18
- telegrinder/bot/dispatch/view/callback_query.py +4 -8
- telegrinder/bot/dispatch/view/chat_join_request.py +5 -6
- telegrinder/bot/dispatch/view/chat_member.py +5 -25
- telegrinder/bot/dispatch/view/error.py +9 -0
- telegrinder/bot/dispatch/view/inline_query.py +4 -8
- telegrinder/bot/dispatch/view/message.py +5 -25
- telegrinder/bot/dispatch/view/pre_checkout_query.py +4 -8
- telegrinder/bot/dispatch/view/raw.py +3 -109
- telegrinder/bot/dispatch/waiter_machine/__init__.py +2 -5
- telegrinder/bot/dispatch/waiter_machine/actions.py +6 -4
- telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +1 -3
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +1 -1
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +11 -7
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +0 -0
- telegrinder/bot/dispatch/waiter_machine/machine.py +43 -60
- telegrinder/bot/dispatch/waiter_machine/middleware.py +19 -23
- telegrinder/bot/dispatch/waiter_machine/short_state.py +6 -5
- telegrinder/bot/polling/__init__.py +0 -0
- telegrinder/bot/polling/abc.py +0 -0
- telegrinder/bot/polling/polling.py +209 -88
- telegrinder/bot/rules/__init__.py +3 -16
- telegrinder/bot/rules/abc.py +42 -122
- telegrinder/bot/rules/callback_data.py +29 -49
- telegrinder/bot/rules/chat_join.py +5 -23
- telegrinder/bot/rules/command.py +8 -4
- telegrinder/bot/rules/enum_text.py +3 -4
- telegrinder/bot/rules/func.py +7 -14
- telegrinder/bot/rules/fuzzy.py +3 -4
- telegrinder/bot/rules/inline.py +8 -20
- telegrinder/bot/rules/integer.py +2 -3
- telegrinder/bot/rules/is_from.py +12 -11
- telegrinder/bot/rules/logic.py +11 -5
- telegrinder/bot/rules/markup.py +22 -14
- telegrinder/bot/rules/mention.py +8 -7
- telegrinder/bot/rules/message_entities.py +8 -4
- telegrinder/bot/rules/node.py +23 -12
- telegrinder/bot/rules/payload.py +5 -4
- telegrinder/bot/rules/payment_invoice.py +6 -21
- telegrinder/bot/rules/regex.py +2 -4
- telegrinder/bot/rules/rule_enum.py +8 -7
- telegrinder/bot/rules/start.py +5 -6
- telegrinder/bot/rules/state.py +1 -1
- telegrinder/bot/rules/text.py +4 -15
- telegrinder/bot/rules/update.py +3 -4
- telegrinder/bot/scenario/__init__.py +0 -0
- telegrinder/bot/scenario/abc.py +6 -5
- telegrinder/bot/scenario/checkbox.py +1 -1
- telegrinder/bot/scenario/choice.py +30 -39
- telegrinder/client/__init__.py +3 -5
- telegrinder/client/abc.py +11 -6
- telegrinder/client/aiohttp.py +141 -27
- telegrinder/client/form_data.py +1 -1
- telegrinder/model.py +61 -89
- telegrinder/modules.py +325 -102
- telegrinder/msgspec_utils/__init__.py +40 -0
- telegrinder/msgspec_utils/abc.py +18 -0
- telegrinder/msgspec_utils/custom_types/__init__.py +6 -0
- telegrinder/msgspec_utils/custom_types/datetime.py +24 -0
- telegrinder/msgspec_utils/custom_types/enum_meta.py +43 -0
- telegrinder/msgspec_utils/custom_types/literal.py +25 -0
- telegrinder/msgspec_utils/custom_types/option.py +17 -0
- telegrinder/msgspec_utils/decoder.py +389 -0
- telegrinder/msgspec_utils/encoder.py +206 -0
- telegrinder/{msgspec_json.py → msgspec_utils/json.py} +6 -5
- telegrinder/msgspec_utils/tools.py +75 -0
- telegrinder/node/__init__.py +24 -7
- telegrinder/node/attachment.py +1 -0
- telegrinder/node/base.py +154 -72
- telegrinder/node/callback_query.py +5 -5
- telegrinder/node/collection.py +39 -0
- telegrinder/node/command.py +1 -2
- telegrinder/node/composer.py +121 -72
- telegrinder/node/container.py +11 -8
- telegrinder/node/context.py +48 -0
- telegrinder/node/either.py +27 -40
- telegrinder/node/error.py +41 -0
- telegrinder/node/event.py +37 -11
- telegrinder/node/exceptions.py +7 -0
- telegrinder/node/file.py +0 -0
- telegrinder/node/i18n.py +108 -0
- telegrinder/node/me.py +3 -2
- telegrinder/node/payload.py +1 -1
- telegrinder/node/polymorphic.py +63 -28
- telegrinder/node/reply_message.py +12 -0
- telegrinder/node/rule.py +6 -13
- telegrinder/node/scope.py +14 -5
- telegrinder/node/session.py +53 -0
- telegrinder/node/source.py +41 -9
- telegrinder/node/text.py +1 -2
- telegrinder/node/tools/__init__.py +0 -0
- telegrinder/node/tools/generator.py +3 -5
- telegrinder/node/utility.py +16 -0
- telegrinder/py.typed +0 -0
- telegrinder/rules.py +0 -0
- telegrinder/tools/__init__.py +48 -88
- telegrinder/tools/aio.py +103 -0
- telegrinder/tools/callback_data_serialization/__init__.py +5 -0
- telegrinder/tools/{callback_data_serilization → callback_data_serialization}/abc.py +0 -0
- telegrinder/tools/{callback_data_serilization → callback_data_serialization}/json_ser.py +2 -3
- telegrinder/tools/{callback_data_serilization → callback_data_serialization}/msgpack_ser.py +45 -27
- telegrinder/tools/final.py +21 -0
- telegrinder/tools/formatting/__init__.py +2 -18
- telegrinder/tools/formatting/deep_links/__init__.py +39 -0
- telegrinder/tools/formatting/{deep_links.py → deep_links/links.py} +12 -85
- telegrinder/tools/formatting/deep_links/parsing.py +90 -0
- telegrinder/tools/formatting/deep_links/validators.py +8 -0
- telegrinder/tools/formatting/html_formatter.py +18 -45
- telegrinder/tools/fullname.py +83 -0
- telegrinder/tools/global_context/__init__.py +4 -3
- telegrinder/tools/global_context/abc.py +17 -14
- telegrinder/tools/global_context/builtin_context.py +39 -0
- telegrinder/tools/global_context/global_context.py +138 -39
- telegrinder/tools/input_file_directory.py +0 -0
- telegrinder/tools/keyboard/__init__.py +39 -0
- telegrinder/tools/keyboard/abc.py +159 -0
- telegrinder/tools/keyboard/base.py +77 -0
- telegrinder/tools/keyboard/buttons/__init__.py +14 -0
- telegrinder/tools/keyboard/buttons/base.py +18 -0
- telegrinder/tools/{buttons.py → keyboard/buttons/buttons.py} +71 -23
- telegrinder/tools/keyboard/buttons/static_buttons.py +56 -0
- telegrinder/tools/keyboard/buttons/tools.py +18 -0
- telegrinder/tools/keyboard/data.py +20 -0
- telegrinder/tools/keyboard/keyboard.py +131 -0
- telegrinder/tools/keyboard/static_keyboard.py +83 -0
- telegrinder/tools/lifespan.py +87 -51
- telegrinder/tools/limited_dict.py +4 -1
- telegrinder/tools/loop_wrapper.py +332 -0
- telegrinder/tools/magic/__init__.py +32 -0
- telegrinder/tools/magic/annotations.py +165 -0
- telegrinder/tools/magic/dictionary.py +20 -0
- telegrinder/tools/magic/function.py +246 -0
- telegrinder/tools/magic/shortcut.py +111 -0
- telegrinder/tools/parse_mode.py +9 -3
- telegrinder/tools/singleton/__init__.py +4 -0
- telegrinder/tools/singleton/abc.py +14 -0
- telegrinder/tools/singleton/singleton.py +18 -0
- telegrinder/tools/state_storage/__init__.py +0 -0
- telegrinder/tools/state_storage/abc.py +6 -1
- telegrinder/tools/state_storage/memory.py +1 -1
- telegrinder/tools/strings.py +0 -0
- telegrinder/types/__init__.py +307 -268
- telegrinder/types/enums.py +64 -37
- telegrinder/types/input_file.py +3 -3
- telegrinder/types/methods.py +5699 -5055
- telegrinder/types/methods_utils.py +62 -0
- telegrinder/types/objects.py +7846 -7058
- telegrinder/verification_utils.py +3 -1
- telegrinder-0.5.0.dist-info/METADATA +162 -0
- telegrinder-0.5.0.dist-info/RECORD +200 -0
- {telegrinder-0.4.2.dist-info → telegrinder-0.5.0.dist-info}/licenses/LICENSE +2 -2
- telegrinder/bot/dispatch/waiter_machine/hasher/state.py +0 -20
- telegrinder/bot/rules/id.py +0 -24
- telegrinder/bot/rules/message.py +0 -15
- telegrinder/client/sonic.py +0 -212
- telegrinder/msgspec_utils.py +0 -478
- telegrinder/tools/adapter/__init__.py +0 -19
- telegrinder/tools/adapter/abc.py +0 -49
- telegrinder/tools/adapter/dataclass.py +0 -56
- telegrinder/tools/adapter/errors.py +0 -5
- telegrinder/tools/adapter/event.py +0 -61
- telegrinder/tools/adapter/node.py +0 -46
- telegrinder/tools/adapter/raw_event.py +0 -27
- telegrinder/tools/adapter/raw_update.py +0 -30
- telegrinder/tools/callback_data_serilization/__init__.py +0 -5
- telegrinder/tools/error_handler/__init__.py +0 -10
- telegrinder/tools/error_handler/abc.py +0 -30
- telegrinder/tools/error_handler/error.py +0 -9
- telegrinder/tools/error_handler/error_handler.py +0 -179
- telegrinder/tools/formatting/spec_html_formats.py +0 -75
- telegrinder/tools/functional.py +0 -8
- telegrinder/tools/global_context/telegrinder_ctx.py +0 -27
- telegrinder/tools/i18n/__init__.py +0 -12
- telegrinder/tools/i18n/abc.py +0 -32
- telegrinder/tools/i18n/middleware/__init__.py +0 -3
- telegrinder/tools/i18n/middleware/abc.py +0 -22
- telegrinder/tools/i18n/simple.py +0 -43
- telegrinder/tools/keyboard.py +0 -132
- telegrinder/tools/loop_wrapper/__init__.py +0 -4
- telegrinder/tools/loop_wrapper/abc.py +0 -20
- telegrinder/tools/loop_wrapper/loop_wrapper.py +0 -169
- telegrinder/tools/magic.py +0 -344
- telegrinder-0.4.2.dist-info/METADATA +0 -151
- telegrinder-0.4.2.dist-info/RECORD +0 -182
- {telegrinder-0.4.2.dist-info → telegrinder-0.5.0.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
|
|
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")
|