telegrinder 0.3.3__py3-none-any.whl → 0.3.4__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.

@@ -1,5 +1,4 @@
1
1
  import typing
2
- from functools import cached_property
3
2
 
4
3
  from fntypes.co import Nothing, Some
5
4
 
@@ -96,10 +95,6 @@ class UpdateCute(BaseCute[Update], Update, kw_only=True):
96
95
  """Optional. A request to join the chat has been sent. The bot must have the can_invite_users
97
96
  administrator right in the chat to receive these updates."""
98
97
 
99
- @cached_property
100
- def incoming_update(self) -> Model:
101
- return getattr(self, self.update_type.value).unwrap()
102
-
103
98
  def get_event(self, event_model: type[EventModel]) -> Option[EventModel]:
104
99
  if isinstance(self.incoming_update, event_model):
105
100
  return Some(self.incoming_update)
@@ -3,11 +3,17 @@ from abc import ABC
3
3
 
4
4
  from telegrinder.bot.dispatch.context import Context
5
5
  from telegrinder.model import Model
6
+ from telegrinder.types.objects import Update
7
+
8
+ if typing.TYPE_CHECKING:
9
+ from telegrinder.bot.rules.adapter.abc import ABCAdapter
6
10
 
7
11
  Event = typing.TypeVar("Event", bound=Model)
8
12
 
9
13
 
10
14
  class ABCMiddleware(ABC, typing.Generic[Event]):
15
+ adapter: "ABCAdapter[Update, Event] | None" = None
16
+
11
17
  async def pre(self, event: Event, ctx: Context) -> bool: ...
12
18
 
13
19
  async def post(self, event: Event, responses: list[typing.Any], ctx: Context) -> None: ...
@@ -1,5 +1,7 @@
1
+ import inspect
1
2
  import typing
2
3
 
4
+ from fntypes.option import Nothing, Option, Some
3
5
  from fntypes.result import Error, Ok
4
6
 
5
7
  from telegrinder.api.api import API
@@ -16,10 +18,27 @@ from telegrinder.types.objects import Update
16
18
  if typing.TYPE_CHECKING:
17
19
  from telegrinder.bot.dispatch.handler.abc import ABCHandler
18
20
  from telegrinder.bot.rules.abc import ABCRule
21
+ from telegrinder.bot.rules.adapter.abc import ABCAdapter
19
22
 
23
+ T = typing.TypeVar("T")
20
24
  Event = typing.TypeVar("Event", bound=Model)
21
25
 
22
26
 
27
+ async def run_adapter(
28
+ adapter: "ABCAdapter[Update, T]",
29
+ api: API,
30
+ update: Update,
31
+ context: Context,
32
+ ) -> Option[T]:
33
+ adapt_result = adapter.adapt(api, update, context)
34
+ match await adapt_result if inspect.isawaitable(adapt_result) else adapt_result:
35
+ case Ok(value):
36
+ return Some(value)
37
+ case Error(err):
38
+ logger.debug("Adapter failed with error message: {!r}", str(err))
39
+ return Nothing()
40
+
41
+
23
42
  async def process_inner(
24
43
  api: API,
25
44
  event: Event,
@@ -34,6 +53,13 @@ async def process_inner(
34
53
 
35
54
  logger.debug("Run pre middlewares...")
36
55
  for middleware in middlewares:
56
+ if middleware.adapter is not None:
57
+ match await run_adapter(middleware.adapter, api, raw_event, ctx):
58
+ case Some(val):
59
+ event = val
60
+ case Nothing():
61
+ return False
62
+
37
63
  middleware_result = await middleware.pre(event, ctx)
38
64
  logger.debug("Middleware {!r} returned: {!r}", middleware.__class__.__qualname__, middleware_result)
39
65
  if middleware_result is False:
@@ -83,11 +109,10 @@ async def check_rule(
83
109
  Returns check result."""
84
110
 
85
111
  # Running adapter
86
- match await rule.adapter.adapt(api, update, ctx):
87
- case Ok(value):
88
- adapted_value = value
89
- case Error(err):
90
- logger.debug("Adapter failed with error message: {!r}", str(err))
112
+ match await run_adapter(rule.adapter, api, update, ctx):
113
+ case Some(val):
114
+ adapted_value = val
115
+ case Nothing():
91
116
  return False
92
117
 
93
118
  # Preparing update
@@ -45,10 +45,15 @@ class WaiterMachine:
45
45
  self.base_state_lifetime,
46
46
  )
47
47
 
48
+ def create_middleware(self, view: BaseStateView[EventModel]) -> WaiterMiddleware[EventModel]:
49
+ hasher = StateViewHasher(view)
50
+ self.storage[hasher] = LimitedDict(maxlimit=self.max_storage_size)
51
+ return WaiterMiddleware(self, hasher)
52
+
48
53
  async def drop_all(self) -> None:
49
54
  """Drops all waiters in storage."""
50
55
 
51
- for hasher in self.storage:
56
+ for hasher in self.storage.copy():
52
57
  for ident, short_state in self.storage[hasher].items():
53
58
  if short_state.context:
54
59
  await self.drop(hasher, ident)
@@ -202,12 +202,12 @@ class Always(ABCRule):
202
202
 
203
203
 
204
204
  __all__ = (
205
- "ABCRule",
206
- "Always",
207
- "AndRule",
208
- "CheckResult",
209
- "Never",
210
- "NotRule",
211
- "OrRule",
205
+ "ABCRule",
206
+ "Always",
207
+ "AndRule",
208
+ "CheckResult",
209
+ "Never",
210
+ "NotRule",
211
+ "OrRule",
212
212
  "with_caching_translations",
213
213
  )
@@ -1,14 +1,17 @@
1
- from telegrinder.bot.rules.adapter.abc import ABCAdapter, Event
1
+ from telegrinder.bot.rules.adapter.abc import ABCAdapter, AdaptResult, Event
2
2
  from telegrinder.bot.rules.adapter.errors import AdapterError
3
3
  from telegrinder.bot.rules.adapter.event import EventAdapter
4
4
  from telegrinder.bot.rules.adapter.node import NodeAdapter
5
+ from telegrinder.bot.rules.adapter.raw_event import RawEventAdapter
5
6
  from telegrinder.bot.rules.adapter.raw_update import RawUpdateAdapter
6
7
 
7
8
  __all__ = (
8
- "ABCAdapter",
9
- "AdapterError",
10
- "Event",
11
- "EventAdapter",
12
- "NodeAdapter",
9
+ "ABCAdapter",
10
+ "AdaptResult",
11
+ "AdapterError",
12
+ "Event",
13
+ "EventAdapter",
14
+ "NodeAdapter",
15
+ "RawEventAdapter",
13
16
  "RawUpdateAdapter",
14
17
  )
@@ -12,12 +12,14 @@ from telegrinder.model import Model
12
12
  From = typing.TypeVar("From", bound=Model)
13
13
  To = typing.TypeVar("To")
14
14
 
15
+ AdaptResult: typing.TypeAlias = Result[To, AdapterError] | typing.Awaitable[Result[To, AdapterError]]
16
+
15
17
 
16
18
  class ABCAdapter(abc.ABC, typing.Generic[From, To]):
17
19
  ADAPTED_VALUE_KEY: str | None = None
18
20
 
19
21
  @abc.abstractmethod
20
- async def adapt(self, api: API, update: From, context: Context) -> Result[To, AdapterError]:
22
+ def adapt(self, api: API, update: From, context: Context) -> AdaptResult[To]:
21
23
  pass
22
24
 
23
25
 
@@ -26,4 +28,4 @@ class Event(typing.Generic[To]):
26
28
  obj: To
27
29
 
28
30
 
29
- __all__ = ("ABCAdapter", "Event")
31
+ __all__ = ("ABCAdapter", "AdaptResult", "Event")
@@ -4,6 +4,7 @@ from fntypes.result import Error, Ok, Result
4
4
 
5
5
  from telegrinder.api.api import API
6
6
  from telegrinder.bot.cute_types.base import BaseCute
7
+ from telegrinder.bot.cute_types.update import UpdateCute
7
8
  from telegrinder.bot.dispatch.context import Context
8
9
  from telegrinder.bot.rules.adapter.abc import ABCAdapter
9
10
  from telegrinder.bot.rules.adapter.errors import AdapterError
@@ -22,46 +23,43 @@ class EventAdapter(ABCAdapter[Update, ToCute]):
22
23
  self.cute_model = cute_model
23
24
 
24
25
  def __repr__(self) -> str:
25
- if isinstance(self.event, str):
26
- raw_update_type = Update.__annotations__.get(self.event, "Unknown")
27
- raw_update_type = (
28
- typing.get_args(raw_update_type)[0].__forward_arg__
29
- if typing.get_args(raw_update_type)
30
- else raw_update_type
31
- )
32
- else:
33
- raw_update_type = self.event.__name__
34
-
35
- return "<{}: adapt Update -> {} -> {}>".format(
26
+ raw_update_type = (
27
+ f"Update -> {self.event.__name__}"
28
+ if isinstance(self.event, type)
29
+ else f"Update.{self.event.value}"
30
+ )
31
+ return "<{}: adapt {} -> {}>".format(
36
32
  self.__class__.__name__,
37
33
  raw_update_type,
38
34
  self.cute_model.__name__,
39
35
  )
40
36
 
41
- async def adapt(self, api: API, update: Update, context: Context) -> Result[ToCute, AdapterError]:
42
- if self.ADAPTED_VALUE_KEY in context:
43
- return Ok(context[self.ADAPTED_VALUE_KEY])
37
+ def get_event(self, update: UpdateCute) -> Model | None:
38
+ if isinstance(self.event, UpdateType) and self.event == update.update_type:
39
+ return update.incoming_update
44
40
 
45
- match await RawUpdateAdapter().adapt(api, update, context):
46
- case Ok(update_cute):
47
- incoming_update = None
48
- if isinstance(self.event, UpdateType) and self.event == update_cute.update_type:
49
- incoming_update = update_cute.incoming_update
50
- if not isinstance(self.event, UpdateType) and (event := update_cute.get_event(self.event)):
51
- incoming_update = update_cute.get_event(self.event).unwrap_or_none()
41
+ if not isinstance(self.event, UpdateType) and (event := update.get_event(self.event)):
42
+ return event.unwrap()
52
43
 
53
- if incoming_update is not None:
54
- adapted = (
55
- typing.cast(ToCute, incoming_update)
56
- if isinstance(incoming_update, BaseCute)
57
- else self.cute_model.from_update(incoming_update, bound_api=api)
58
- )
59
- context[self.ADAPTED_VALUE_KEY] = adapted
60
- return Ok(adapted)
44
+ return None
61
45
 
62
- return Error(AdapterError(f"Update is not an {self.event!r}."))
46
+ def adapt(self, api: API, update: Update, context: Context) -> Result[ToCute, AdapterError]:
47
+ match RawUpdateAdapter().adapt(api, update, context):
48
+ case Ok(update_cute) if event := self.get_event(update_cute):
49
+ if self.ADAPTED_VALUE_KEY in context:
50
+ return Ok(context[self.ADAPTED_VALUE_KEY])
51
+
52
+ adapted = (
53
+ typing.cast(ToCute, event)
54
+ if isinstance(event, BaseCute)
55
+ else self.cute_model.from_update(event, bound_api=api)
56
+ )
57
+ context[self.ADAPTED_VALUE_KEY] = adapted
58
+ return Ok(adapted)
63
59
  case Error(_) as err:
64
60
  return err
61
+ case _:
62
+ return Error(AdapterError(f"Update is not an {self.event!r}."))
65
63
 
66
64
 
67
65
  __all__ = ("EventAdapter",)
@@ -0,0 +1,27 @@
1
+ from fntypes.result import Error, Ok, Result
2
+
3
+ from telegrinder.api.api import API
4
+ from telegrinder.bot.dispatch.context import Context
5
+ from telegrinder.bot.rules.adapter.abc import ABCAdapter
6
+ from telegrinder.bot.rules.adapter.errors import AdapterError
7
+ from telegrinder.model import Model
8
+ from telegrinder.types.objects import Update
9
+
10
+
11
+ class RawEventAdapter(ABCAdapter[Update, Model]):
12
+ def __init__(self, event_model: type[Model], /) -> None:
13
+ self.event_model = event_model
14
+
15
+ def __repr__(self) -> str:
16
+ return "<{}: adapt Update -> {}>".format(
17
+ self.__class__.__name__,
18
+ self.event_model.__name__,
19
+ )
20
+
21
+ def adapt(self, api: API, update: Update, context: Context) -> Result[Model, AdapterError]:
22
+ if isinstance(update.incoming_update, self.event_model):
23
+ return Ok(update.incoming_update)
24
+ return Error(AdapterError(f"Update is not an {self.event_model.__name__!r}."))
25
+
26
+
27
+ __all__ = ("RawEventAdapter",)
@@ -14,7 +14,7 @@ class RawUpdateAdapter(ABCAdapter[Update, UpdateCute]):
14
14
  def __repr__(self) -> str:
15
15
  return f"<{self.__class__.__name__}: adapt Update -> UpdateCute>"
16
16
 
17
- async def adapt(
17
+ def adapt(
18
18
  self,
19
19
  api: API,
20
20
  update: Update,
@@ -159,12 +159,12 @@ class CallbackDataMarkup(CallbackQueryDataRule):
159
159
 
160
160
 
161
161
  __all__ = (
162
- "CallbackDataEq",
163
- "CallbackDataJsonEq",
164
- "CallbackDataJsonModel",
165
- "CallbackDataMap",
166
- "CallbackDataMarkup",
167
- "CallbackQueryDataRule",
168
- "CallbackQueryRule",
162
+ "CallbackDataEq",
163
+ "CallbackDataJsonEq",
164
+ "CallbackDataJsonModel",
165
+ "CallbackDataMap",
166
+ "CallbackDataMarkup",
167
+ "CallbackQueryDataRule",
168
+ "CallbackQueryRule",
169
169
  "HasData",
170
170
  )
@@ -39,8 +39,8 @@ class InviteLinkByCreator(ChatJoinRequestRule, requires=[HasInviteLink()]):
39
39
 
40
40
 
41
41
  __all__ = (
42
- "ChatJoinRequestRule",
43
- "HasInviteLink",
44
- "InviteLinkByCreator",
42
+ "ChatJoinRequestRule",
43
+ "HasInviteLink",
44
+ "InviteLinkByCreator",
45
45
  "InviteLinkName",
46
46
  )
@@ -52,9 +52,9 @@ class InlineQueryMarkup(InlineQueryRule):
52
52
 
53
53
 
54
54
  __all__ = (
55
- "HasLocation",
56
- "InlineQueryChatType",
57
- "InlineQueryMarkup",
58
- "InlineQueryRule",
55
+ "HasLocation",
56
+ "InlineQueryChatType",
57
+ "InlineQueryMarkup",
58
+ "InlineQueryRule",
59
59
  "InlineQueryText",
60
60
  )
@@ -1,4 +1,5 @@
1
1
  import dataclasses
2
+ import enum
2
3
  import secrets
3
4
  import typing
4
5
 
@@ -17,6 +18,11 @@ if typing.TYPE_CHECKING:
17
18
  Key = typing.TypeVar("Key", bound=typing.Hashable)
18
19
 
19
20
 
21
+ class ChoiceCode(enum.StrEnum):
22
+ READY = "ready"
23
+ CANCEL = "cancel"
24
+
25
+
20
26
  @dataclasses.dataclass(slots=True)
21
27
  class Choice(typing.Generic[Key]):
22
28
  key: Key
@@ -79,10 +85,11 @@ class _Checkbox(ABCScenario[CallbackQueryCute]):
79
85
  )
80
86
  kb.row()
81
87
 
82
- kb.add(InlineButton(self.ready, callback_data=self.random_code + "/ready"))
88
+ kb.add(InlineButton(self.ready, callback_data=self.random_code + "/" + ChoiceCode.READY))
83
89
  if self.cancel_text is not None:
84
90
  kb.row()
85
- kb.add(InlineButton(self.cancel_text, callback_data=self.random_code + "/cancel"))
91
+ kb.add(InlineButton(self.cancel_text, callback_data=self.random_code + "/" + ChoiceCode.CANCEL))
92
+
86
93
  return kb.get_markup()
87
94
 
88
95
  def add_option(
@@ -100,11 +107,13 @@ class _Checkbox(ABCScenario[CallbackQueryCute]):
100
107
 
101
108
  async def handle(self, cb: CallbackQueryCute) -> bool:
102
109
  code = cb.data.unwrap().replace(self.random_code + "/", "", 1)
103
- if code == "ready":
104
- return False
105
- elif code == "cancel":
106
- self.choices = []
107
- return False
110
+
111
+ match code:
112
+ case ChoiceCode.READY:
113
+ return False
114
+ case ChoiceCode.CANCEL:
115
+ self.choices = []
116
+ return False
108
117
 
109
118
  for i, choice in enumerate(self.choices):
110
119
  if choice.code == code:
@@ -2,45 +2,50 @@ import typing
2
2
 
3
3
  from telegrinder.bot.cute_types.callback_query import CallbackQueryCute
4
4
  from telegrinder.bot.dispatch.waiter_machine.hasher.hasher import Hasher
5
- from telegrinder.bot.scenario.checkbox import Checkbox
5
+ from telegrinder.bot.scenario.checkbox import Checkbox, ChoiceCode
6
6
 
7
7
  if typing.TYPE_CHECKING:
8
- from telegrinder.api import API
8
+ from telegrinder.api.api import API
9
9
  from telegrinder.bot.dispatch.view.base import BaseStateView
10
-
11
-
12
- class Choice(Checkbox):
13
- async def handle(self, cb: CallbackQueryCute) -> bool:
14
- code = cb.data.unwrap().replace(self.random_code + "/", "", 1)
15
- if code == "ready":
16
- return False
17
-
18
- for choice in self.choices:
19
- choice.is_picked = False
20
-
21
- for i, choice in enumerate(self.choices):
22
- if choice.code == code:
23
- self.choices[i].is_picked = True
24
- await cb.ctx_api.edit_message_text(
25
- text=self.message,
26
- chat_id=cb.message.unwrap().v.chat.id,
27
- message_id=cb.message.unwrap().v.message_id,
28
- parse_mode=self.PARSE_MODE,
29
- reply_markup=self.get_markup(),
30
- )
31
-
32
- return True
33
-
34
- async def wait(
35
- self,
36
- hasher: Hasher[CallbackQueryCute, int],
37
- api: "API",
38
- view: "BaseStateView[CallbackQueryCute]",
39
- ) -> tuple[str, int]:
40
- if len(tuple(choice for choice in self.choices if choice.is_picked)) != 1:
41
- raise ValueError("Exactly one choice must be picked")
42
- choices, m_id = await super().wait(hasher, api, view)
43
- return tuple(choices.keys())[tuple(choices.values()).index(True)], m_id
10
+ from telegrinder.bot.scenario.checkbox import Key
11
+
12
+ class Choice(Checkbox[Key], typing.Generic[Key]):
13
+ async def wait(
14
+ self,
15
+ hasher: Hasher[CallbackQueryCute, int],
16
+ api: API,
17
+ view: BaseStateView[CallbackQueryCute],
18
+ ) -> tuple[Key, int]: ...
19
+
20
+ else:
21
+
22
+ class Choice(Checkbox):
23
+ async def handle(self, cb):
24
+ code = cb.data.unwrap().replace(self.random_code + "/", "", 1)
25
+ if code == ChoiceCode.READY:
26
+ return False
27
+
28
+ for choice in self.choices:
29
+ choice.is_picked = False
30
+
31
+ for i, choice in enumerate(self.choices):
32
+ if choice.code == code:
33
+ self.choices[i].is_picked = True
34
+ await cb.ctx_api.edit_message_text(
35
+ text=self.message,
36
+ chat_id=cb.message.unwrap().v.chat.id,
37
+ message_id=cb.message.unwrap().v.message_id,
38
+ parse_mode=self.PARSE_MODE,
39
+ reply_markup=self.get_markup(),
40
+ )
41
+
42
+ return True
43
+
44
+ async def wait(self, hasher, api, view):
45
+ if len(tuple(choice for choice in self.choices if choice.is_picked)) != 1:
46
+ raise ValueError("Exactly one choice must be picked")
47
+ choices, m_id = await super().wait(hasher, api, view)
48
+ return tuple(choices.keys())[tuple(choices.values()).index(True)], m_id
44
49
 
45
50
 
46
51
  __all__ = ("Choice",)
telegrinder/model.py CHANGED
@@ -1,6 +1,8 @@
1
+ import base64
1
2
  import dataclasses
2
3
  import enum
3
4
  import keyword
5
+ import os
4
6
  import secrets
5
7
  import typing
6
8
  from datetime import datetime
@@ -15,6 +17,7 @@ if typing.TYPE_CHECKING:
15
17
  from telegrinder.api.error import APIError
16
18
 
17
19
  T = typing.TypeVar("T")
20
+ P = typing.ParamSpec("P")
18
21
 
19
22
  UnionType: typing.TypeAlias = typing.Annotated[tuple[T, ...], ...]
20
23
 
@@ -46,6 +49,15 @@ def full_result(
46
49
  return result.map(lambda v: decoder.decode(v, type=full_t))
47
50
 
48
51
 
52
+ def generate_random_id(length_bytes: int) -> str:
53
+ if length_bytes < 1 or length_bytes > 64:
54
+ raise ValueError("Length of bytes must be between 1 and 64.")
55
+
56
+ random_bytes = os.urandom(length_bytes)
57
+ random_id = base64.urlsafe_b64encode(random_bytes).rstrip(b"=").decode("utf-8")
58
+ return random_id
59
+
60
+
49
61
  def get_params(params: dict[str, typing.Any]) -> dict[str, typing.Any]:
50
62
  validated_params = {}
51
63
  for k, v in (
@@ -60,10 +72,6 @@ def get_params(params: dict[str, typing.Any]) -> dict[str, typing.Any]:
60
72
  return validated_params
61
73
 
62
74
 
63
- class From(typing.Generic[T]):
64
- def __new__(cls, _: T, /) -> typing.Any: ...
65
-
66
-
67
75
  if typing.TYPE_CHECKING:
68
76
 
69
77
  @typing.overload
@@ -72,6 +80,13 @@ if typing.TYPE_CHECKING:
72
80
  @typing.overload
73
81
  def field(*, default: typing.Any, name: str | None = ...) -> typing.Any: ...
74
82
 
83
+ @typing.overload
84
+ def field(
85
+ *,
86
+ default_factory: typing.Callable[[], typing.Any],
87
+ name: str | None = None,
88
+ ) -> typing.Any: ...
89
+
75
90
  @typing.overload
76
91
  def field(
77
92
  *,
@@ -102,9 +117,14 @@ if typing.TYPE_CHECKING:
102
117
  name=...,
103
118
  converter=...,
104
119
  ) -> typing.Any: ...
120
+
121
+ class From(typing.Generic[T]):
122
+ def __new__(cls, _: T, /) -> typing.Any: ...
105
123
  else:
106
124
  from msgspec import field as _field
107
125
 
126
+ From = typing.Annotated[T, ...]
127
+
108
128
  def field(**kwargs):
109
129
  kwargs.pop("converter", None)
110
130
  return _field(**kwargs)
@@ -113,12 +133,16 @@ else:
113
133
  @typing.dataclass_transform(field_specifiers=(field,))
114
134
  class Model(msgspec.Struct, **MODEL_CONFIG):
115
135
  @classmethod
116
- def from_data(cls, data: dict[str, typing.Any]) -> typing.Self:
117
- return decoder.convert(data, type=cls)
136
+ def from_data(cls: typing.Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T:
137
+ return decoder.convert(msgspec.structs.asdict(cls(*args, **kwargs)), type=cls) # type: ignore
138
+
139
+ @classmethod
140
+ def from_dict(cls, obj: dict[str, typing.Any], /) -> typing.Self:
141
+ return decoder.convert(obj, type=cls)
118
142
 
119
143
  @classmethod
120
- def from_bytes(cls, data: bytes) -> typing.Self:
121
- return decoder.decode(data, type=cls)
144
+ def from_bytes(cls, obj: bytes, /) -> typing.Self:
145
+ return decoder.decode(obj, type=cls)
122
146
 
123
147
  def _to_dict(
124
148
  self,
@@ -285,11 +309,12 @@ else:
285
309
 
286
310
 
287
311
  __all__ = (
288
- "DataConverter",
289
- "MODEL_CONFIG",
290
- "Model",
291
- "ProxiedDict",
292
- "Proxy",
293
- "full_result",
312
+ "DataConverter",
313
+ "MODEL_CONFIG",
314
+ "Model",
315
+ "ProxiedDict",
316
+ "Proxy",
317
+ "full_result",
318
+ "generate_random_id",
294
319
  "get_params",
295
320
  )
telegrinder/modules.py CHANGED
@@ -36,8 +36,8 @@ class LoggerModule(typing.Protocol):
36
36
 
37
37
  logger: LoggerModule
38
38
  logging_level = os.getenv("LOGGER_LEVEL", default="DEBUG").upper()
39
- logging_module = choice_in_order(["loguru"], default="logging")
40
- asyncio_module = choice_in_order(["uvloop"], default="asyncio")
39
+ logging_module = choice_in_order(["loguru"], default="logging", do_import=False)
40
+ asyncio_module = choice_in_order(["uvloop"], default="asyncio", do_import=False)
41
41
 
42
42
  if logging_module == "loguru":
43
43
  import os
@@ -96,7 +96,7 @@ def msgspec_to_builtins(
96
96
 
97
97
 
98
98
  def option_dec_hook(tp: type[Option[typing.Any]], obj: typing.Any) -> Option[typing.Any]:
99
- if obj is None:
99
+ if obj is None or isinstance(obj, fntypes.Nothing):
100
100
  return Nothing
101
101
 
102
102
  (value_type,) = typing.get_args(tp) or (typing.Any,)