telegrinder 1.0.0rc1__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.
- telegrinder/__init__.py +258 -0
- telegrinder/__meta__.py +1 -0
- telegrinder/api/__init__.py +15 -0
- telegrinder/api/api.py +175 -0
- telegrinder/api/error.py +50 -0
- telegrinder/api/response.py +23 -0
- telegrinder/api/token.py +30 -0
- telegrinder/api/validators.py +30 -0
- telegrinder/bot/__init__.py +144 -0
- telegrinder/bot/bot.py +70 -0
- telegrinder/bot/cute_types/__init__.py +41 -0
- telegrinder/bot/cute_types/base.py +228 -0
- telegrinder/bot/cute_types/base.pyi +49 -0
- telegrinder/bot/cute_types/business_connection.py +9 -0
- telegrinder/bot/cute_types/business_messages_deleted.py +9 -0
- telegrinder/bot/cute_types/callback_query.py +248 -0
- telegrinder/bot/cute_types/chat_boost_removed.py +9 -0
- telegrinder/bot/cute_types/chat_boost_updated.py +9 -0
- telegrinder/bot/cute_types/chat_join_request.py +59 -0
- telegrinder/bot/cute_types/chat_member_updated.py +158 -0
- telegrinder/bot/cute_types/chosen_inline_result.py +11 -0
- telegrinder/bot/cute_types/inline_query.py +41 -0
- telegrinder/bot/cute_types/message.py +2809 -0
- telegrinder/bot/cute_types/message_reaction_count_updated.py +9 -0
- telegrinder/bot/cute_types/message_reaction_updated.py +9 -0
- telegrinder/bot/cute_types/paid_media_purchased.py +11 -0
- telegrinder/bot/cute_types/poll.py +9 -0
- telegrinder/bot/cute_types/poll_answer.py +9 -0
- telegrinder/bot/cute_types/pre_checkout_query.py +36 -0
- telegrinder/bot/cute_types/shipping_query.py +11 -0
- telegrinder/bot/cute_types/update.py +209 -0
- telegrinder/bot/cute_types/utils.py +141 -0
- telegrinder/bot/dispatch/__init__.py +99 -0
- telegrinder/bot/dispatch/abc.py +74 -0
- telegrinder/bot/dispatch/action.py +99 -0
- telegrinder/bot/dispatch/context.py +162 -0
- telegrinder/bot/dispatch/dispatch.py +362 -0
- telegrinder/bot/dispatch/handler/__init__.py +23 -0
- telegrinder/bot/dispatch/handler/abc.py +25 -0
- telegrinder/bot/dispatch/handler/audio_reply.py +43 -0
- telegrinder/bot/dispatch/handler/base.py +34 -0
- telegrinder/bot/dispatch/handler/document_reply.py +43 -0
- telegrinder/bot/dispatch/handler/func.py +73 -0
- telegrinder/bot/dispatch/handler/media_group_reply.py +43 -0
- telegrinder/bot/dispatch/handler/message_reply.py +35 -0
- telegrinder/bot/dispatch/handler/photo_reply.py +43 -0
- telegrinder/bot/dispatch/handler/sticker_reply.py +36 -0
- telegrinder/bot/dispatch/handler/video_reply.py +43 -0
- telegrinder/bot/dispatch/middleware/__init__.py +13 -0
- telegrinder/bot/dispatch/middleware/abc.py +112 -0
- telegrinder/bot/dispatch/middleware/box.py +32 -0
- telegrinder/bot/dispatch/middleware/filter.py +88 -0
- telegrinder/bot/dispatch/middleware/media_group.py +69 -0
- telegrinder/bot/dispatch/process.py +93 -0
- telegrinder/bot/dispatch/return_manager/__init__.py +21 -0
- telegrinder/bot/dispatch/return_manager/abc.py +107 -0
- telegrinder/bot/dispatch/return_manager/callback_query.py +19 -0
- telegrinder/bot/dispatch/return_manager/inline_query.py +14 -0
- telegrinder/bot/dispatch/return_manager/message.py +34 -0
- telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +19 -0
- telegrinder/bot/dispatch/return_manager/utils.py +20 -0
- telegrinder/bot/dispatch/router/__init__.py +4 -0
- telegrinder/bot/dispatch/router/abc.py +15 -0
- telegrinder/bot/dispatch/router/base.py +154 -0
- telegrinder/bot/dispatch/view/__init__.py +15 -0
- telegrinder/bot/dispatch/view/abc.py +15 -0
- telegrinder/bot/dispatch/view/base.py +226 -0
- telegrinder/bot/dispatch/view/box.py +207 -0
- telegrinder/bot/dispatch/view/media_group.py +25 -0
- telegrinder/bot/dispatch/waiter_machine/__init__.py +25 -0
- telegrinder/bot/dispatch/waiter_machine/actions.py +16 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +13 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +53 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +61 -0
- telegrinder/bot/dispatch/waiter_machine/hasher/message.py +49 -0
- telegrinder/bot/dispatch/waiter_machine/machine.py +264 -0
- telegrinder/bot/dispatch/waiter_machine/middleware.py +77 -0
- telegrinder/bot/dispatch/waiter_machine/short_state.py +105 -0
- telegrinder/bot/polling/__init__.py +4 -0
- telegrinder/bot/polling/abc.py +25 -0
- telegrinder/bot/polling/error_handler.py +93 -0
- telegrinder/bot/polling/polling.py +167 -0
- telegrinder/bot/polling/utils.py +12 -0
- telegrinder/bot/rules/__init__.py +166 -0
- telegrinder/bot/rules/abc.py +150 -0
- telegrinder/bot/rules/button.py +20 -0
- telegrinder/bot/rules/callback_data.py +109 -0
- telegrinder/bot/rules/chat_join.py +28 -0
- telegrinder/bot/rules/chat_member_updated.py +145 -0
- telegrinder/bot/rules/command.py +137 -0
- telegrinder/bot/rules/enum_text.py +29 -0
- telegrinder/bot/rules/func.py +21 -0
- telegrinder/bot/rules/fuzzy.py +21 -0
- telegrinder/bot/rules/inline.py +45 -0
- telegrinder/bot/rules/integer.py +19 -0
- telegrinder/bot/rules/is_from.py +213 -0
- telegrinder/bot/rules/logic.py +22 -0
- telegrinder/bot/rules/magic.py +60 -0
- telegrinder/bot/rules/markup.py +51 -0
- telegrinder/bot/rules/media.py +13 -0
- telegrinder/bot/rules/mention.py +15 -0
- telegrinder/bot/rules/message_entities.py +37 -0
- telegrinder/bot/rules/node.py +43 -0
- telegrinder/bot/rules/payload.py +89 -0
- telegrinder/bot/rules/payment_invoice.py +14 -0
- telegrinder/bot/rules/regex.py +34 -0
- telegrinder/bot/rules/rule_enum.py +71 -0
- telegrinder/bot/rules/start.py +73 -0
- telegrinder/bot/rules/state.py +35 -0
- telegrinder/bot/rules/text.py +27 -0
- telegrinder/bot/rules/update.py +14 -0
- telegrinder/bot/scenario/__init__.py +5 -0
- telegrinder/bot/scenario/abc.py +16 -0
- telegrinder/bot/scenario/checkbox.py +183 -0
- telegrinder/bot/scenario/choice.py +44 -0
- telegrinder/client/__init__.py +11 -0
- telegrinder/client/abc.py +136 -0
- telegrinder/client/form_data.py +34 -0
- telegrinder/client/rnet.py +198 -0
- telegrinder/model.py +133 -0
- telegrinder/model.pyi +57 -0
- telegrinder/modules.py +1081 -0
- telegrinder/msgspec_utils/__init__.py +42 -0
- telegrinder/msgspec_utils/abc.py +16 -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 +61 -0
- telegrinder/msgspec_utils/custom_types/literal.py +25 -0
- telegrinder/msgspec_utils/custom_types/option.py +17 -0
- telegrinder/msgspec_utils/decoder.py +388 -0
- telegrinder/msgspec_utils/encoder.py +204 -0
- telegrinder/msgspec_utils/json.py +15 -0
- telegrinder/msgspec_utils/tools.py +80 -0
- telegrinder/node/__init__.py +80 -0
- telegrinder/node/compose.py +193 -0
- telegrinder/node/nodes/__init__.py +96 -0
- telegrinder/node/nodes/attachment.py +169 -0
- telegrinder/node/nodes/callback_query.py +25 -0
- telegrinder/node/nodes/channel.py +97 -0
- telegrinder/node/nodes/command.py +33 -0
- telegrinder/node/nodes/error.py +43 -0
- telegrinder/node/nodes/event.py +70 -0
- telegrinder/node/nodes/file.py +39 -0
- telegrinder/node/nodes/global_node.py +66 -0
- telegrinder/node/nodes/i18n.py +110 -0
- telegrinder/node/nodes/me.py +26 -0
- telegrinder/node/nodes/message_entities.py +15 -0
- telegrinder/node/nodes/payload.py +84 -0
- telegrinder/node/nodes/reply_message.py +14 -0
- telegrinder/node/nodes/source.py +172 -0
- telegrinder/node/nodes/state_mutator.py +71 -0
- telegrinder/node/nodes/text.py +62 -0
- telegrinder/node/scope.py +88 -0
- telegrinder/node/utils.py +38 -0
- telegrinder/py.typed +0 -0
- telegrinder/rules.py +1 -0
- telegrinder/tools/__init__.py +183 -0
- telegrinder/tools/aio.py +147 -0
- telegrinder/tools/final.py +21 -0
- telegrinder/tools/formatting/__init__.py +85 -0
- telegrinder/tools/formatting/deep_links/__init__.py +39 -0
- telegrinder/tools/formatting/deep_links/links.py +468 -0
- telegrinder/tools/formatting/deep_links/parsing.py +88 -0
- telegrinder/tools/formatting/deep_links/validators.py +8 -0
- telegrinder/tools/formatting/html.py +241 -0
- telegrinder/tools/fullname.py +82 -0
- telegrinder/tools/global_context/__init__.py +13 -0
- telegrinder/tools/global_context/abc.py +63 -0
- telegrinder/tools/global_context/builtin_context.py +45 -0
- telegrinder/tools/global_context/global_context.py +614 -0
- telegrinder/tools/input_file_directory.py +30 -0
- telegrinder/tools/keyboard/__init__.py +6 -0
- telegrinder/tools/keyboard/abc.py +84 -0
- telegrinder/tools/keyboard/base.py +108 -0
- telegrinder/tools/keyboard/button.py +181 -0
- telegrinder/tools/keyboard/data.py +31 -0
- telegrinder/tools/keyboard/keyboard.py +160 -0
- telegrinder/tools/keyboard/utils.py +95 -0
- telegrinder/tools/lifespan.py +188 -0
- telegrinder/tools/limited_dict.py +35 -0
- telegrinder/tools/loop_wrapper.py +271 -0
- telegrinder/tools/magic/__init__.py +29 -0
- telegrinder/tools/magic/annotations.py +172 -0
- telegrinder/tools/magic/descriptors.py +57 -0
- telegrinder/tools/magic/function.py +254 -0
- telegrinder/tools/magic/inspect.py +16 -0
- telegrinder/tools/magic/shortcut.py +107 -0
- telegrinder/tools/member_descriptor_proxy.py +95 -0
- telegrinder/tools/parse_mode.py +12 -0
- telegrinder/tools/serialization/__init__.py +5 -0
- telegrinder/tools/serialization/abc.py +34 -0
- telegrinder/tools/serialization/json_ser.py +60 -0
- telegrinder/tools/serialization/msgpack_ser.py +197 -0
- telegrinder/tools/serialization/utils.py +18 -0
- telegrinder/tools/singleton/__init__.py +4 -0
- telegrinder/tools/singleton/abc.py +14 -0
- telegrinder/tools/singleton/singleton.py +18 -0
- telegrinder/tools/state_mutator/__init__.py +4 -0
- telegrinder/tools/state_mutator/mutation.py +85 -0
- telegrinder/tools/state_storage/__init__.py +4 -0
- telegrinder/tools/state_storage/abc.py +38 -0
- telegrinder/tools/state_storage/memory.py +27 -0
- telegrinder/tools/strings.py +22 -0
- telegrinder/types/__init__.py +323 -0
- telegrinder/types/enums.py +754 -0
- telegrinder/types/input_file.py +51 -0
- telegrinder/types/methods.py +6143 -0
- telegrinder/types/methods_utils.py +66 -0
- telegrinder/types/objects.py +8184 -0
- telegrinder/types/webapp.py +129 -0
- telegrinder/verification_utils.py +35 -0
- telegrinder-1.0.0rc1.dist-info/METADATA +166 -0
- telegrinder-1.0.0rc1.dist-info/RECORD +215 -0
- telegrinder-1.0.0rc1.dist-info/WHEEL +4 -0
- telegrinder-1.0.0rc1.dist-info/licenses/LICENSE +22 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from telegrinder.msgspec_utils.abc import SupportsCast, is_supports_cast
|
|
2
|
+
from telegrinder.msgspec_utils.custom_types.datetime import datetime, timedelta
|
|
3
|
+
from telegrinder.msgspec_utils.custom_types.enum_meta import BaseEnumMeta
|
|
4
|
+
from telegrinder.msgspec_utils.custom_types.literal import Literal
|
|
5
|
+
from telegrinder.msgspec_utils.custom_types.option import Option
|
|
6
|
+
from telegrinder.msgspec_utils.decoder import Decoder, convert, decoder
|
|
7
|
+
from telegrinder.msgspec_utils.encoder import Encoder, encoder, to_builtins
|
|
8
|
+
from telegrinder.msgspec_utils.json import dumps, loads
|
|
9
|
+
from telegrinder.msgspec_utils.tools import (
|
|
10
|
+
get_class_annotations,
|
|
11
|
+
get_origin,
|
|
12
|
+
get_type_hints,
|
|
13
|
+
is_common_type,
|
|
14
|
+
is_none,
|
|
15
|
+
struct_asdict,
|
|
16
|
+
type_check,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = (
|
|
20
|
+
"BaseEnumMeta",
|
|
21
|
+
"Decoder",
|
|
22
|
+
"Encoder",
|
|
23
|
+
"Literal",
|
|
24
|
+
"Option",
|
|
25
|
+
"SupportsCast",
|
|
26
|
+
"convert",
|
|
27
|
+
"datetime",
|
|
28
|
+
"decoder",
|
|
29
|
+
"dumps",
|
|
30
|
+
"encoder",
|
|
31
|
+
"get_class_annotations",
|
|
32
|
+
"get_origin",
|
|
33
|
+
"get_type_hints",
|
|
34
|
+
"is_common_type",
|
|
35
|
+
"is_none",
|
|
36
|
+
"is_supports_cast",
|
|
37
|
+
"loads",
|
|
38
|
+
"struct_asdict",
|
|
39
|
+
"timedelta",
|
|
40
|
+
"to_builtins",
|
|
41
|
+
"type_check",
|
|
42
|
+
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def is_supports_cast(obj: typing.Any, /) -> typing.TypeGuard[SupportsCast]:
|
|
6
|
+
return isinstance(obj, SupportsCast)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@typing.runtime_checkable
|
|
10
|
+
class SupportsCast(typing.Protocol):
|
|
11
|
+
@classmethod
|
|
12
|
+
@abc.abstractmethod
|
|
13
|
+
def cast(cls, obj: typing.Any) -> typing.Self: ...
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
__all__ = ("SupportsCast", "is_supports_cast")
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
from telegrinder.msgspec_utils.custom_types.datetime import datetime, timedelta
|
|
2
|
+
from telegrinder.msgspec_utils.custom_types.enum_meta import BaseEnumMeta
|
|
3
|
+
from telegrinder.msgspec_utils.custom_types.literal import Literal
|
|
4
|
+
from telegrinder.msgspec_utils.custom_types.option import Option
|
|
5
|
+
|
|
6
|
+
__all__ = ("BaseEnumMeta", "Literal", "Option", "datetime", "timedelta")
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import datetime as dt
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
from telegrinder.msgspec_utils.abc import SupportsCast
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class datetime(dt.datetime, SupportsCast): # noqa: N801 # type: ignore
|
|
8
|
+
@classmethod
|
|
9
|
+
def cast(cls, obj: dt.datetime) -> typing.Self:
|
|
10
|
+
return cls.fromtimestamp(timestamp=obj.timestamp(), tz=obj.tzinfo)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class timedelta(dt.timedelta, SupportsCast): # noqa: N801 # type: ignore
|
|
14
|
+
@classmethod
|
|
15
|
+
def cast(cls, obj: dt.timedelta) -> typing.Self:
|
|
16
|
+
return cls(seconds=obj.total_seconds())
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
if typing.TYPE_CHECKING:
|
|
20
|
+
datetime: typing.TypeAlias = dt.datetime
|
|
21
|
+
timedelta: typing.TypeAlias = dt.timedelta
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
__all__ = ("datetime", "timedelta")
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import enum
|
|
2
|
+
import math
|
|
3
|
+
import sys
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
from telegrinder.modules import logger
|
|
7
|
+
|
|
8
|
+
NOT_SUPPORTED: typing.Final = "NOT_SUPPORTED"
|
|
9
|
+
ENUM_FRIENDS: typing.Final = (str, int, float)
|
|
10
|
+
NOT_SUPPORTED_VALUES: typing.Final = {
|
|
11
|
+
str: NOT_SUPPORTED,
|
|
12
|
+
int: math.inf,
|
|
13
|
+
float: sys.maxsize,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _is_friend(bases: tuple[type[typing.Any], ...], /) -> bool:
|
|
18
|
+
return any(friend in bases for friend in ENUM_FRIENDS)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BaseEnumMeta(enum.EnumMeta, type):
|
|
22
|
+
if typing.TYPE_CHECKING:
|
|
23
|
+
|
|
24
|
+
class _BaseEnumMeta(enum.Enum): # noqa
|
|
25
|
+
NOT_SUPPORTED = enum.auto()
|
|
26
|
+
|
|
27
|
+
NOT_SUPPORTED: typing.Literal[_BaseEnumMeta.NOT_SUPPORTED]
|
|
28
|
+
|
|
29
|
+
else:
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def _member_missing(cls, value):
|
|
33
|
+
logger.warning(
|
|
34
|
+
"Unsupported value {!r} for enum of type {}. Probably telegrinder needs "
|
|
35
|
+
"to be updated to support the latest version of Telegram Bot API.",
|
|
36
|
+
value,
|
|
37
|
+
cls,
|
|
38
|
+
)
|
|
39
|
+
return cls._member_map_["NOT_SUPPORTED"]
|
|
40
|
+
|
|
41
|
+
def __new__(
|
|
42
|
+
metacls,
|
|
43
|
+
cls,
|
|
44
|
+
bases,
|
|
45
|
+
classdict,
|
|
46
|
+
*,
|
|
47
|
+
boundary=None,
|
|
48
|
+
_simple=False,
|
|
49
|
+
**kwds,
|
|
50
|
+
):
|
|
51
|
+
if _is_friend(bases):
|
|
52
|
+
classdict["NOT_SUPPORTED"] = next(
|
|
53
|
+
(value for base, value in NOT_SUPPORTED_VALUES.items() if base in bases),
|
|
54
|
+
NOT_SUPPORTED,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
classdict["_missing_"] = classmethod(BaseEnumMeta._member_missing)
|
|
58
|
+
return super().__new__(metacls, cls, bases, classdict, boundary=boundary, _simple=_simple, **kwds)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
__all__ = ("BaseEnumMeta",)
|
|
@@ -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 kungfu.library.monad.option import Option
|
|
5
|
+
else:
|
|
6
|
+
import kungfu.library
|
|
7
|
+
import msgspec
|
|
8
|
+
|
|
9
|
+
class OptionMeta(type):
|
|
10
|
+
def __instancecheck__(cls, __instance):
|
|
11
|
+
return isinstance(__instance, (kungfu.library.Some | kungfu.library.Nothing, msgspec.UnsetType))
|
|
12
|
+
|
|
13
|
+
class Option[Value](metaclass=OptionMeta):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
__all__ = ("Option",)
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import datetime as dt
|
|
2
|
+
import typing
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
|
|
5
|
+
import kungfu.library
|
|
6
|
+
import msgspec
|
|
7
|
+
from kungfu.library.monad.option import NOTHING
|
|
8
|
+
|
|
9
|
+
from telegrinder.msgspec_utils.abc import SupportsCast
|
|
10
|
+
from telegrinder.msgspec_utils.custom_types.datetime import datetime, timedelta
|
|
11
|
+
from telegrinder.msgspec_utils.custom_types.enum_meta import BaseEnumMeta
|
|
12
|
+
from telegrinder.msgspec_utils.custom_types.literal import _Literal
|
|
13
|
+
from telegrinder.msgspec_utils.custom_types.option import Option
|
|
14
|
+
from telegrinder.msgspec_utils.tools import (
|
|
15
|
+
bundle,
|
|
16
|
+
fullname,
|
|
17
|
+
get_origin,
|
|
18
|
+
is_common_type,
|
|
19
|
+
type_check,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
type Context = dict[str, typing.Any]
|
|
23
|
+
type DecHook[T] = typing.Callable[typing.Concatenate[type[T], typing.Any, ...], typing.Any]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def option_dec_hook(
|
|
27
|
+
tp: type[Option[typing.Any]],
|
|
28
|
+
obj: typing.Any,
|
|
29
|
+
/,
|
|
30
|
+
) -> kungfu.library.Option[typing.Any] | msgspec.UnsetType:
|
|
31
|
+
if obj is msgspec.UNSET:
|
|
32
|
+
return obj
|
|
33
|
+
|
|
34
|
+
if obj is None or obj is NOTHING:
|
|
35
|
+
return NOTHING
|
|
36
|
+
|
|
37
|
+
(value_type,) = typing.get_args(tp) or (typing.Any,)
|
|
38
|
+
orig_value_type = typing.get_origin(value_type) or value_type
|
|
39
|
+
orig_obj = obj
|
|
40
|
+
|
|
41
|
+
if not isinstance(orig_obj, dict | list) and is_common_type(orig_value_type):
|
|
42
|
+
if orig_value_type is kungfu.library.Sum:
|
|
43
|
+
obj = value_type(orig_obj) # type: ignore
|
|
44
|
+
orig_value_type = typing.get_args(value_type)
|
|
45
|
+
|
|
46
|
+
if not type_check(orig_obj, orig_value_type):
|
|
47
|
+
raise msgspec.ValidationError(
|
|
48
|
+
f"Expected `{fullname(orig_value_type)}` or `builtins.None`, got `{fullname(orig_obj)}`.",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return kungfu.library.Some(obj)
|
|
52
|
+
|
|
53
|
+
return kungfu.library.Some(decoder.convert(orig_obj, type=value_type))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def sum_dec_hook(tp: type[kungfu.library.Sum], obj: typing.Any, /) -> kungfu.library.Sum:
|
|
57
|
+
union_types = typing.get_args(tp)
|
|
58
|
+
|
|
59
|
+
if isinstance(obj, dict):
|
|
60
|
+
reverse = False
|
|
61
|
+
models_fields_count: dict[type[msgspec.Struct], int] = {
|
|
62
|
+
m: sum(1 for k in obj if k in m.__struct_fields__)
|
|
63
|
+
for m in union_types
|
|
64
|
+
if issubclass(get_origin(m), msgspec.Struct)
|
|
65
|
+
}
|
|
66
|
+
union_types = tuple(t for t in union_types if t not in models_fields_count)
|
|
67
|
+
|
|
68
|
+
if len(set(models_fields_count.values())) != len(models_fields_count.values()):
|
|
69
|
+
models_fields_count = {m: len(m.__struct_fields__) for m in models_fields_count}
|
|
70
|
+
reverse = True
|
|
71
|
+
|
|
72
|
+
union_types = (
|
|
73
|
+
*sorted(
|
|
74
|
+
models_fields_count,
|
|
75
|
+
key=lambda k: models_fields_count[k],
|
|
76
|
+
reverse=reverse,
|
|
77
|
+
),
|
|
78
|
+
*union_types,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
if not isinstance(obj, dict | list) and any(is_common_type(t) and type_check(obj, t) for t in union_types):
|
|
82
|
+
return tp(obj)
|
|
83
|
+
|
|
84
|
+
for t in union_types:
|
|
85
|
+
match convert(obj, t):
|
|
86
|
+
case kungfu.library.Ok(value):
|
|
87
|
+
return tp(value)
|
|
88
|
+
case kungfu.library.Error(_):
|
|
89
|
+
continue
|
|
90
|
+
case _ as arg:
|
|
91
|
+
typing.assert_never(arg)
|
|
92
|
+
|
|
93
|
+
raise msgspec.ValidationError(
|
|
94
|
+
"Object of type `{}` doesn't belong to `{}[{}]`.".format(
|
|
95
|
+
fullname(obj),
|
|
96
|
+
fullname(kungfu.library.Sum),
|
|
97
|
+
", ".join(fullname(get_origin(x)) for x in union_types),
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def datetime_dec_hook(tp: type[datetime], obj: typing.Any, /) -> datetime:
|
|
103
|
+
if isinstance(obj, datetime):
|
|
104
|
+
return obj
|
|
105
|
+
|
|
106
|
+
if isinstance(obj, dt.datetime):
|
|
107
|
+
assert issubclass(tp, SupportsCast)
|
|
108
|
+
return tp.cast(obj)
|
|
109
|
+
|
|
110
|
+
if isinstance(obj, int | float):
|
|
111
|
+
return tp.fromtimestamp(timestamp=obj)
|
|
112
|
+
|
|
113
|
+
raise TypeError(
|
|
114
|
+
"Cannot validate object of type `{}` into `datetime.datetime`".format(
|
|
115
|
+
fullname(obj),
|
|
116
|
+
),
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def timedelta_dec_hook(tp: type[timedelta], obj: typing.Any, /) -> timedelta:
|
|
121
|
+
if isinstance(obj, timedelta):
|
|
122
|
+
return obj
|
|
123
|
+
|
|
124
|
+
if isinstance(obj, dt.timedelta):
|
|
125
|
+
assert issubclass(tp, SupportsCast)
|
|
126
|
+
return tp.cast(obj)
|
|
127
|
+
|
|
128
|
+
if isinstance(obj, int | float):
|
|
129
|
+
return tp(seconds=obj)
|
|
130
|
+
|
|
131
|
+
raise TypeError(
|
|
132
|
+
"Cannot validate object of type `{}` into `datetime.timedelta`".format(
|
|
133
|
+
fullname(obj),
|
|
134
|
+
),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def convert[T](
|
|
139
|
+
obj: typing.Any,
|
|
140
|
+
t: type[T],
|
|
141
|
+
/,
|
|
142
|
+
*,
|
|
143
|
+
context: Context | None = None,
|
|
144
|
+
) -> kungfu.library.Result[T, str]:
|
|
145
|
+
try:
|
|
146
|
+
return kungfu.library.Ok(decoder.convert(obj, type=t, strict=True, context=context))
|
|
147
|
+
except msgspec.ValidationError:
|
|
148
|
+
return kungfu.library.Error(
|
|
149
|
+
"Expected object of type `{}`, got `{}`.".format(
|
|
150
|
+
fullname(t),
|
|
151
|
+
fullname(obj),
|
|
152
|
+
)
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def literal_dec_hook(literal: type[_Literal], obj: typing.Any, /) -> typing.Any:
|
|
157
|
+
if obj in literal.__args__:
|
|
158
|
+
return obj
|
|
159
|
+
|
|
160
|
+
raise msgspec.ValidationError(
|
|
161
|
+
"Invalid literal value {!r} of either {}.".format(
|
|
162
|
+
obj,
|
|
163
|
+
literal.__args__[0]
|
|
164
|
+
if len(literal.__args__) == 1
|
|
165
|
+
else (", ".join(f"`{x}`" for x in literal.__args__[:-1])) + f" or `{literal.__args__[-1]}`",
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class Decoder:
|
|
171
|
+
"""Class `Decoder` for `msgspec` module with decode hook
|
|
172
|
+
for objects with the specified type.
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
import enum
|
|
176
|
+
|
|
177
|
+
from datetime import datetime as dt
|
|
178
|
+
|
|
179
|
+
class Digit(enum.IntEnum):
|
|
180
|
+
ONE = 1
|
|
181
|
+
TWO = 2
|
|
182
|
+
THREE = 3
|
|
183
|
+
|
|
184
|
+
decoder = Decoder()
|
|
185
|
+
decoder.dec_hooks[dt] = lambda t, timestamp: t.fromtimestamp(timestamp)
|
|
186
|
+
|
|
187
|
+
decoder.dec_hook(dt, 1713354732) #> datetime.datetime(2024, 4, 17, 14, 52, 12)
|
|
188
|
+
|
|
189
|
+
decoder.convert("123", type=int, strict=False) #> 123
|
|
190
|
+
decoder.convert(1, type=Digit) #> <Digit.ONE: 1>
|
|
191
|
+
|
|
192
|
+
decoder.decode(b'{"digit":3}', type=dict[str, Digit]) #> {'digit': <Digit.THREE: 3>}
|
|
193
|
+
```
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
dec_hooks: dict[typing.Any, DecHook[typing.Any]]
|
|
197
|
+
abstract_dec_hooks: dict[typing.Any, DecHook[typing.Any]]
|
|
198
|
+
|
|
199
|
+
def __init__(self) -> None:
|
|
200
|
+
self.dec_hooks = {
|
|
201
|
+
Option: option_dec_hook,
|
|
202
|
+
kungfu.library.Sum: sum_dec_hook,
|
|
203
|
+
datetime: datetime_dec_hook,
|
|
204
|
+
timedelta: timedelta_dec_hook,
|
|
205
|
+
kungfu.library.Some: option_dec_hook,
|
|
206
|
+
kungfu.library.Nothing: option_dec_hook,
|
|
207
|
+
}
|
|
208
|
+
self.abstract_dec_hooks = {
|
|
209
|
+
BaseEnumMeta: lambda enum_type, member: enum_type(member),
|
|
210
|
+
_Literal: literal_dec_hook,
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
def __repr__(self) -> str:
|
|
214
|
+
return "<{}: dec_hooks={!r}, abstract_dec_hooks={!r}>".format(
|
|
215
|
+
type(self).__name__,
|
|
216
|
+
self.dec_hooks,
|
|
217
|
+
self.abstract_dec_hooks,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
@typing.overload
|
|
221
|
+
def __call__[T](
|
|
222
|
+
self,
|
|
223
|
+
type: type[T],
|
|
224
|
+
context: Context | None = None,
|
|
225
|
+
) -> typing.ContextManager[msgspec.json.Decoder[T]]: ...
|
|
226
|
+
|
|
227
|
+
@typing.overload
|
|
228
|
+
def __call__(
|
|
229
|
+
self,
|
|
230
|
+
type: typing.Any,
|
|
231
|
+
context: Context | None = None,
|
|
232
|
+
) -> typing.ContextManager[msgspec.json.Decoder[typing.Any]]: ...
|
|
233
|
+
|
|
234
|
+
@typing.overload
|
|
235
|
+
def __call__[T](
|
|
236
|
+
self,
|
|
237
|
+
type: type[T],
|
|
238
|
+
*,
|
|
239
|
+
strict: bool = True,
|
|
240
|
+
context: Context | None = None,
|
|
241
|
+
) -> typing.ContextManager[msgspec.json.Decoder[T]]: ...
|
|
242
|
+
|
|
243
|
+
@typing.overload
|
|
244
|
+
def __call__(
|
|
245
|
+
self,
|
|
246
|
+
type: typing.Any,
|
|
247
|
+
*,
|
|
248
|
+
strict: bool = True,
|
|
249
|
+
context: Context | None = None,
|
|
250
|
+
) -> typing.ContextManager[msgspec.json.Decoder[typing.Any]]: ...
|
|
251
|
+
|
|
252
|
+
@contextmanager
|
|
253
|
+
def __call__(
|
|
254
|
+
self,
|
|
255
|
+
type: typing.Any = object,
|
|
256
|
+
*,
|
|
257
|
+
strict: bool = True,
|
|
258
|
+
context: Context | None = None,
|
|
259
|
+
) -> typing.Generator[msgspec.json.Decoder, typing.Any, None]:
|
|
260
|
+
"""Context manager returns an `msgspec.json.Decoder` object with passed the `dec_hook`."""
|
|
261
|
+
yield msgspec.json.Decoder(
|
|
262
|
+
type=typing.Any if type is object else type,
|
|
263
|
+
strict=strict,
|
|
264
|
+
dec_hook=self.dec_hook(context),
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
def add_dec_hook[T](self, t: type[T], /) -> typing.Callable[[DecHook[T]], DecHook[T]]:
|
|
268
|
+
def decorator(func: DecHook[T], /) -> DecHook[T]:
|
|
269
|
+
return self.dec_hooks.setdefault(get_origin(t), func)
|
|
270
|
+
|
|
271
|
+
return decorator
|
|
272
|
+
|
|
273
|
+
def add_abstract_dec_hook[T](self, abstract_type: type[T], /) -> typing.Callable[[DecHook[T]], DecHook[T]]:
|
|
274
|
+
def decorator(func: DecHook[T], /) -> DecHook[T]:
|
|
275
|
+
return self.abstract_dec_hooks.setdefault(get_origin(abstract_type), func)
|
|
276
|
+
|
|
277
|
+
return decorator
|
|
278
|
+
|
|
279
|
+
def get_abstract_dec_hook(self, subtype: type[typing.Any], /) -> DecHook[typing.Any] | None:
|
|
280
|
+
for abstract, dec_hook in self.abstract_dec_hooks.items():
|
|
281
|
+
if issubclass(subtype, abstract) or issubclass(type(subtype), abstract):
|
|
282
|
+
return dec_hook
|
|
283
|
+
|
|
284
|
+
return None
|
|
285
|
+
|
|
286
|
+
def dec_hook(self, context: Context | None = None, /) -> DecHook[typing.Any]:
|
|
287
|
+
def inner(tp: typing.Any, obj: typing.Any, /) -> typing.Any:
|
|
288
|
+
origin_type = get_origin(tp)
|
|
289
|
+
|
|
290
|
+
if (dec_hook_func := self.dec_hooks.get(origin_type)) is None and (
|
|
291
|
+
dec_hook_func := self.get_abstract_dec_hook(origin_type)
|
|
292
|
+
) is None:
|
|
293
|
+
raise NotImplementedError(
|
|
294
|
+
f"Not implemented decode hook for type `{fullname(origin_type)}`. "
|
|
295
|
+
"You can implement decode hook for this type.",
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
return bundle(dec_hook_func, context or {}, start_idx=2)(tp, obj)
|
|
299
|
+
|
|
300
|
+
return inner
|
|
301
|
+
|
|
302
|
+
def convert[T](
|
|
303
|
+
self,
|
|
304
|
+
obj: object,
|
|
305
|
+
*,
|
|
306
|
+
type: type[T] = dict,
|
|
307
|
+
strict: bool = True,
|
|
308
|
+
from_attributes: bool = False,
|
|
309
|
+
builtin_types: typing.Iterable[type[typing.Any]] | None = None,
|
|
310
|
+
str_keys: bool = False,
|
|
311
|
+
context: Context | None = None,
|
|
312
|
+
) -> T:
|
|
313
|
+
return msgspec.convert(
|
|
314
|
+
obj,
|
|
315
|
+
type,
|
|
316
|
+
strict=strict,
|
|
317
|
+
from_attributes=from_attributes,
|
|
318
|
+
dec_hook=self.dec_hook(context),
|
|
319
|
+
builtin_types=builtin_types,
|
|
320
|
+
str_keys=str_keys,
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
@typing.overload
|
|
324
|
+
def decode(
|
|
325
|
+
self,
|
|
326
|
+
buf: str | bytes,
|
|
327
|
+
*,
|
|
328
|
+
context: Context | None = None,
|
|
329
|
+
) -> typing.Any: ...
|
|
330
|
+
|
|
331
|
+
@typing.overload
|
|
332
|
+
def decode[T](
|
|
333
|
+
self,
|
|
334
|
+
buf: str | bytes,
|
|
335
|
+
*,
|
|
336
|
+
type: type[T],
|
|
337
|
+
context: Context | None = None,
|
|
338
|
+
) -> T: ...
|
|
339
|
+
|
|
340
|
+
@typing.overload
|
|
341
|
+
def decode(
|
|
342
|
+
self,
|
|
343
|
+
buf: str | bytes,
|
|
344
|
+
*,
|
|
345
|
+
type: typing.Any,
|
|
346
|
+
context: Context | None = None,
|
|
347
|
+
) -> typing.Any: ...
|
|
348
|
+
|
|
349
|
+
@typing.overload
|
|
350
|
+
def decode[T](
|
|
351
|
+
self,
|
|
352
|
+
buf: str | bytes,
|
|
353
|
+
*,
|
|
354
|
+
type: type[T],
|
|
355
|
+
strict: bool = True,
|
|
356
|
+
context: Context | None = None,
|
|
357
|
+
) -> T: ...
|
|
358
|
+
|
|
359
|
+
@typing.overload
|
|
360
|
+
def decode(
|
|
361
|
+
self,
|
|
362
|
+
buf: str | bytes,
|
|
363
|
+
*,
|
|
364
|
+
type: typing.Any,
|
|
365
|
+
strict: bool = True,
|
|
366
|
+
context: Context | None = None,
|
|
367
|
+
) -> typing.Any: ...
|
|
368
|
+
|
|
369
|
+
def decode(
|
|
370
|
+
self,
|
|
371
|
+
buf: str | bytes,
|
|
372
|
+
*,
|
|
373
|
+
type: typing.Any = object,
|
|
374
|
+
strict: bool = True,
|
|
375
|
+
context: Context | None = None,
|
|
376
|
+
) -> typing.Any:
|
|
377
|
+
return msgspec.json.decode(
|
|
378
|
+
buf,
|
|
379
|
+
type=typing.Any if type is object else type,
|
|
380
|
+
strict=strict,
|
|
381
|
+
dec_hook=self.dec_hook(context),
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
decoder: typing.Final = Decoder()
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
__all__ = ("Decoder", "convert", "decoder")
|