telegrinder 0.1.dev166__py3-none-any.whl → 0.1.dev168__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 (64) hide show
  1. telegrinder/__init__.py +0 -2
  2. telegrinder/bot/__init__.py +0 -2
  3. telegrinder/bot/bot.py +1 -3
  4. telegrinder/bot/cute_types/base.py +3 -12
  5. telegrinder/bot/cute_types/callback_query.py +2 -4
  6. telegrinder/bot/cute_types/chat_join_request.py +3 -1
  7. telegrinder/bot/cute_types/chat_member_updated.py +3 -1
  8. telegrinder/bot/cute_types/message.py +10 -31
  9. telegrinder/bot/cute_types/utils.py +1 -3
  10. telegrinder/bot/dispatch/__init__.py +1 -2
  11. telegrinder/bot/dispatch/composition.py +1 -3
  12. telegrinder/bot/dispatch/dispatch.py +5 -6
  13. telegrinder/bot/dispatch/handler/func.py +4 -11
  14. telegrinder/bot/dispatch/return_manager/abc.py +9 -13
  15. telegrinder/bot/dispatch/return_manager/message.py +5 -7
  16. telegrinder/bot/dispatch/view/abc.py +54 -5
  17. telegrinder/bot/dispatch/view/box.py +3 -11
  18. telegrinder/bot/dispatch/view/raw.py +2 -6
  19. telegrinder/bot/dispatch/waiter_machine/__init__.py +1 -2
  20. telegrinder/bot/dispatch/waiter_machine/machine.py +43 -88
  21. telegrinder/bot/dispatch/waiter_machine/middleware.py +12 -5
  22. telegrinder/bot/dispatch/waiter_machine/short_state.py +15 -5
  23. telegrinder/bot/polling/polling.py +2 -6
  24. telegrinder/bot/rules/adapter/event.py +1 -3
  25. telegrinder/bot/rules/callback_data.py +8 -8
  26. telegrinder/bot/rules/fuzzy.py +1 -2
  27. telegrinder/bot/rules/is_from.py +6 -4
  28. telegrinder/bot/rules/markup.py +1 -2
  29. telegrinder/bot/rules/mention.py +1 -4
  30. telegrinder/bot/rules/regex.py +1 -2
  31. telegrinder/bot/rules/rule_enum.py +1 -3
  32. telegrinder/bot/rules/start.py +1 -3
  33. telegrinder/bot/scenario/checkbox.py +6 -10
  34. telegrinder/bot/scenario/choice.py +4 -3
  35. telegrinder/client/aiohttp.py +1 -3
  36. telegrinder/model.py +4 -3
  37. telegrinder/modules.py +1 -3
  38. telegrinder/msgspec_utils.py +1 -3
  39. telegrinder/node/attachment.py +18 -14
  40. telegrinder/node/base.py +4 -11
  41. telegrinder/node/composer.py +1 -3
  42. telegrinder/node/message.py +3 -1
  43. telegrinder/node/source.py +3 -1
  44. telegrinder/node/text.py +3 -1
  45. telegrinder/tools/__init__.py +2 -0
  46. telegrinder/tools/buttons.py +4 -6
  47. telegrinder/tools/error_handler/abc.py +1 -3
  48. telegrinder/tools/error_handler/error.py +3 -6
  49. telegrinder/tools/error_handler/error_handler.py +17 -13
  50. telegrinder/tools/formatting/html.py +2 -6
  51. telegrinder/tools/formatting/links.py +1 -3
  52. telegrinder/tools/global_context/abc.py +1 -3
  53. telegrinder/tools/global_context/global_context.py +13 -31
  54. telegrinder/tools/i18n/middleware/base.py +1 -3
  55. telegrinder/tools/limited_dict.py +37 -0
  56. telegrinder/tools/loop_wrapper/loop_wrapper.py +3 -7
  57. telegrinder/types/__init__.py +30 -0
  58. telegrinder/types/methods.py +20 -89
  59. telegrinder/types/objects.py +16 -45
  60. telegrinder/verification_utils.py +2 -1
  61. {telegrinder-0.1.dev166.dist-info → telegrinder-0.1.dev168.dist-info}/METADATA +5 -5
  62. {telegrinder-0.1.dev166.dist-info → telegrinder-0.1.dev168.dist-info}/RECORD +64 -63
  63. {telegrinder-0.1.dev166.dist-info → telegrinder-0.1.dev168.dist-info}/LICENSE +0 -0
  64. {telegrinder-0.1.dev166.dist-info → telegrinder-0.1.dev168.dist-info}/WHEEL +0 -0
@@ -1,79 +1,23 @@
1
1
  import asyncio
2
2
  import datetime
3
3
  import typing
4
- from collections import deque
5
4
 
6
5
  from telegrinder.api.abc import ABCAPI
7
6
  from telegrinder.bot.dispatch.context import Context
8
7
  from telegrinder.bot.rules.abc import ABCRule
8
+ from telegrinder.tools.limited_dict import LimitedDict
9
+ from telegrinder.types import Update
9
10
 
10
11
  from .middleware import WaiterMiddleware
11
12
  from .short_state import Behaviour, EventModel, ShortState
12
13
 
13
- T = typing.TypeVar("T")
14
-
15
- Storage: typing.TypeAlias = dict[str, "ShortStateStorage"]
16
- Identificator: typing.TypeAlias = str | int
17
-
18
14
  if typing.TYPE_CHECKING:
19
15
  from telegrinder.bot.dispatch.view.abc import ABCStateView, BaseStateView
20
16
 
17
+ T = typing.TypeVar("T")
21
18
 
22
- class ShortStateStorage(dict[Identificator, ShortState[EventModel]]):
23
- def __init__(self, *, maxlimit: int = 1000) -> None:
24
- super().__init__()
25
- self.maxlimit = maxlimit
26
- self.queue: deque[Identificator] = deque(maxlen=maxlimit)
27
-
28
- def __repr__(self) -> str:
29
- return "<{}: {}, (current={} | maxlimit={})>".format(
30
- self.__class__.__name__,
31
- super().__repr__(),
32
- len(self.queue),
33
- self.maxlimit,
34
- )
35
-
36
- def __setitem__(self, key: Identificator, value: ShortState[EventModel], /) -> None:
37
- self.add(key, value)
38
-
39
- def __delitem__(self, key: Identificator, /) -> None:
40
- self.pop(key, None)
41
-
42
- def add(self, id: Identificator, short_state: ShortState[EventModel]) -> None:
43
- if len(self.queue) >= self.maxlimit:
44
- self.pop(self.queue.popleft(), None)
45
- if id not in self.queue:
46
- self.queue.append(id)
47
- super().__setitem__(id, short_state)
48
-
49
- def clear(self) -> None:
50
- self.queue.clear()
51
- super().clear()
52
-
53
- def setdefault(self, id: Identificator, default: ShortState[EventModel]) -> ShortState[EventModel]:
54
- if id in self:
55
- return self[id]
56
- self.add(id, default)
57
- return default
58
-
59
- def pop(self, id: Identificator, default: T): # type: ignore
60
- if id in self.queue:
61
- self.queue.remove(id)
62
- return super().pop(id, default)
63
-
64
- def popitem(self) -> tuple[Identificator, ShortState[EventModel]]:
65
- item = super().popitem()
66
- self.queue.remove(item[0])
67
- return item
68
-
69
- def update(
70
- self,
71
- mapping: typing.Mapping[Identificator, ShortState[EventModel]] | None = None,
72
- /,
73
- **kwargs: ShortState[EventModel],
74
- ) -> None:
75
- for key, value in (mapping if mapping is not None else kwargs).items():
76
- self.add(key, value)
19
+ Identificator: typing.TypeAlias = str | int
20
+ Storage: typing.TypeAlias = dict[str, LimitedDict[Identificator, ShortState[EventModel]]]
77
21
 
78
22
 
79
23
  class WaiterMachine:
@@ -90,32 +34,27 @@ class WaiterMachine:
90
34
  self,
91
35
  state_view: "ABCStateView[EventModel]",
92
36
  id: Identificator,
37
+ update: Update,
93
38
  **context: typing.Any,
94
39
  ) -> None:
95
40
  view_name = state_view.__class__.__name__
96
41
  if view_name not in self.storage:
97
- raise LookupError("No record of view {!r} found".format(view_name))
42
+ raise LookupError("No record of view {!r} found.".format(view_name))
98
43
 
99
44
  short_state = self.storage[view_name].pop(id, None)
100
45
  if short_state is None:
101
46
  raise LookupError(
102
47
  "Waiter with identificator {} is not found for view {!r}".format(
103
- id,
104
- view_name,
48
+ id, view_name
105
49
  )
106
50
  )
107
51
 
108
- waiters = typing.cast(
109
- typing.Iterable[asyncio.Future[typing.Any]],
110
- short_state.event._waiters, # type: ignore
111
- )
112
- for future in waiters:
113
- future.cancel()
114
-
52
+ short_state.cancel()
115
53
  await self.call_behaviour(
116
54
  state_view,
117
- short_state.on_drop_behaviour,
118
55
  short_state.event,
56
+ update,
57
+ behaviour=short_state.on_drop_behaviour,
119
58
  **context,
120
59
  )
121
60
 
@@ -124,19 +63,24 @@ class WaiterMachine:
124
63
  state_view: "BaseStateView[EventModel]",
125
64
  linked: EventModel | tuple[ABCAPI, Identificator],
126
65
  *rules: ABCRule[EventModel],
127
- default: Behaviour = None,
128
- on_drop: Behaviour = None,
129
- expiration: datetime.timedelta | int | float | None = None,
130
- short_state_storage: ShortStateStorage[EventModel] | None = None,
66
+ default: Behaviour[EventModel] | None = None,
67
+ on_drop: Behaviour[EventModel] | None = None,
68
+ expiration: datetime.timedelta | float | None = None,
131
69
  ) -> tuple[EventModel, Context]:
132
70
  if isinstance(expiration, int | float):
133
71
  expiration = datetime.timedelta(seconds=expiration)
134
72
 
135
- api: ABCAPI; key: Identificator
136
- api, key = linked if isinstance(linked, tuple) else (linked.ctx_api, state_view.get_state_key(linked)) # type: ignore
73
+ api: ABCAPI
74
+ key: Identificator
75
+ api, key = (
76
+ linked
77
+ if isinstance(linked, tuple)
78
+ else (linked.ctx_api, state_view.get_state_key(linked))
79
+ ) # type: ignore
137
80
  if not key:
138
81
  raise RuntimeError("Unable to get state key.")
139
82
 
83
+ view_name = state_view.__class__.__name__
140
84
  event = asyncio.Event()
141
85
  short_state = ShortState(
142
86
  key,
@@ -147,30 +91,41 @@ class WaiterMachine:
147
91
  default_behaviour=default,
148
92
  on_drop_behaviour=on_drop,
149
93
  )
150
- view_name = state_view.__class__.__name__
94
+
151
95
  if view_name not in self.storage:
152
96
  state_view.middlewares.insert(0, WaiterMiddleware(self, state_view))
153
- self.storage[view_name] = short_state_storage or ShortStateStorage()
97
+ self.storage[view_name] = LimitedDict()
154
98
 
155
- self.storage[view_name].add(key, short_state)
99
+ if (deleted_short_state := self.storage[view_name].set(key, short_state)) is not None:
100
+ deleted_short_state.cancel()
101
+
156
102
  await event.wait()
157
-
158
- e, ctx = getattr(event, "context")
159
103
  self.storage[view_name].pop(key, None)
160
- return e, ctx
104
+ return getattr(event, "context")
161
105
 
162
106
  async def call_behaviour(
163
107
  self,
164
108
  view: "ABCStateView[EventModel]",
165
- behaviour: Behaviour,
166
109
  event: asyncio.Event | EventModel,
110
+ update: Update,
111
+ behaviour: Behaviour[EventModel] | None = None,
167
112
  **context: typing.Any,
168
113
  ) -> None:
114
+ # TODO: support param view as a behaviour
115
+
169
116
  if behaviour is None:
170
117
  return
171
- # TODO: add behaviour check
172
- # TODO: support view as a behaviour
173
- await behaviour.run(event, context) # type: ignore
174
118
 
119
+ ctx = Context(**context)
120
+ if isinstance(event, asyncio.Event):
121
+ event, preset_ctx = typing.cast(
122
+ tuple[EventModel, Context],
123
+ getattr(event, "context"),
124
+ )
125
+ ctx.update(preset_ctx)
126
+
127
+ if await behaviour.check(event.api, update, ctx):
128
+ await behaviour.run(event, ctx)
129
+
175
130
 
176
131
  __all__ = ("WaiterMachine",)
@@ -41,19 +41,20 @@ class WaiterMiddleware(ABCMiddleware[EventType]):
41
41
  short_state: "ShortState[EventType] | None" = self.machine.storage[view_name].get(key)
42
42
  if not short_state:
43
43
  return True
44
-
44
+
45
+ preset_context = Context(short_state=short_state)
45
46
  if (
46
47
  short_state.expiration_date is not None
47
48
  and datetime.datetime.now() >= short_state.expiration_date
48
49
  ):
49
- await self.machine.drop(self.view, short_state.key)
50
+ await self.machine.drop(self.view, short_state.key, ctx.raw_update, **preset_context.copy())
50
51
  return True
51
52
 
52
53
  handler = FuncHandler(
53
54
  self.pass_runtime,
54
55
  list(short_state.rules),
55
56
  dataclass=None,
56
- preset_context=Context(short_state=short_state),
57
+ preset_context=preset_context,
57
58
  )
58
59
  result = await handler.check(event.ctx_api, ctx.raw_update, ctx)
59
60
 
@@ -63,14 +64,20 @@ class WaiterMiddleware(ABCMiddleware[EventType]):
63
64
  elif short_state.default_behaviour is not None:
64
65
  await self.machine.call_behaviour(
65
66
  self.view,
66
- short_state.default_behaviour,
67
67
  event,
68
+ ctx.raw_update,
69
+ behaviour=short_state.default_behaviour,
68
70
  **handler.preset_context,
69
71
  )
70
72
 
71
73
  return False
72
74
 
73
- async def pass_runtime(self, event: EventType, short_state: "ShortState[EventType]", ctx: Context) -> None:
75
+ async def pass_runtime(
76
+ self,
77
+ event: EventType,
78
+ short_state: "ShortState[EventType]",
79
+ ctx: Context,
80
+ ) -> None:
74
81
  setattr(short_state.event, "context", (event, ctx))
75
82
  short_state.event.set()
76
83
 
@@ -7,13 +7,15 @@ from telegrinder.api import ABCAPI
7
7
  from telegrinder.bot.cute_types import BaseCute
8
8
  from telegrinder.bot.dispatch.handler.abc import ABCHandler
9
9
  from telegrinder.bot.rules.abc import ABCRule
10
+ from telegrinder.model import Model
10
11
 
11
12
  if typing.TYPE_CHECKING:
12
13
  from .machine import Identificator
13
14
 
15
+ T = typing.TypeVar("T", bound=Model)
14
16
  EventModel = typing.TypeVar("EventModel", bound=BaseCute)
15
17
 
16
- Behaviour: typing.TypeAlias = ABCHandler | None
18
+ Behaviour: typing.TypeAlias = ABCHandler[T] | None
17
19
 
18
20
 
19
21
  @dataclasses.dataclass
@@ -26,14 +28,22 @@ class ShortState(typing.Generic[EventModel]):
26
28
  expiration: dataclasses.InitVar[datetime.timedelta | None] = dataclasses.field(
27
29
  default=None,
28
30
  )
29
- default_behaviour: Behaviour | None = dataclasses.field(default=None)
30
- on_drop_behaviour: Behaviour | None = dataclasses.field(default=None)
31
+ default_behaviour: Behaviour[EventModel] | None = dataclasses.field(default=None)
32
+ on_drop_behaviour: Behaviour[EventModel] | None = dataclasses.field(default=None)
31
33
  expiration_date: datetime.datetime | None = dataclasses.field(init=False)
32
34
 
33
35
  def __post_init__(self, expiration: datetime.timedelta | None = None) -> None:
34
- self.expiration_date = (
35
- (datetime.datetime.now() - expiration) if expiration is not None else None
36
+ self.expiration_date = (datetime.datetime.now() - expiration) if expiration is not None else None
37
+
38
+ def cancel(self) -> None:
39
+ """Cancel schedule waiters."""
40
+
41
+ waiters = typing.cast(
42
+ typing.Iterable[asyncio.Future[typing.Any]],
43
+ self.event._waiters, # type: ignore
36
44
  )
45
+ for future in waiters:
46
+ future.cancel()
37
47
 
38
48
 
39
49
  __all__ = ("ShortState",)
@@ -29,9 +29,7 @@ class Polling(ABCPolling):
29
29
  include_updates=include_updates,
30
30
  exclude_updates=exclude_updates,
31
31
  )
32
- self.reconnection_timeout = (
33
- 5 if reconnection_timeout < 0 else reconnection_timeout
34
- )
32
+ self.reconnection_timeout = 5 if reconnection_timeout < 0 else reconnection_timeout
35
33
  self.max_reconnetions = 10 if max_reconnetions < 0 else max_reconnetions
36
34
  self.offset = offset
37
35
  self._stop = False
@@ -62,9 +60,7 @@ class Polling(ABCPolling):
62
60
 
63
61
  if include_updates and exclude_updates:
64
62
  allowed_updates = [
65
- x
66
- for x in allowed_updates
67
- if x in include_updates and x not in exclude_updates
63
+ x for x in allowed_updates if x in include_updates and x not in exclude_updates
68
64
  ]
69
65
  elif exclude_updates:
70
66
  allowed_updates = [x for x in allowed_updates if x not in exclude_updates]
@@ -45,9 +45,7 @@ class EventAdapter(ABCAdapter[Update, CuteT]):
45
45
  AdapterError(f"Update is not an {self.event!r}."),
46
46
  )
47
47
  return Ok(
48
- self.cute_model.from_update(
49
- update_dct[self.event].unwrap(), bound_api=api
50
- ),
48
+ self.cute_model.from_update(update_dct[self.event].unwrap(), bound_api=api),
51
49
  )
52
50
  event = update_dct[update.update_type.unwrap()].unwrap()
53
51
  if not update.update_type or not issubclass(event.__class__, self.event):
@@ -17,7 +17,7 @@ from .markup import Markup, PatternLike, check_string
17
17
  CallbackQuery: typing.TypeAlias = CallbackQueryCute
18
18
  Validator: typing.TypeAlias = typing.Callable[[typing.Any], bool | typing.Awaitable[bool]]
19
19
  MapDict: typing.TypeAlias = dict[str, "typing.Any | type[typing.Any] | Validator | list[MapDict] | MapDict"]
20
- CallbackMap: typing.TypeAlias = list[tuple[str, "typing.Any | type | Validator | CallbackMap"]]
20
+ CallbackMap: typing.TypeAlias = list[tuple[str, "typing.Any | type[typing.Any] | Validator | CallbackMap"]]
21
21
  CallbackMapStrict: typing.TypeAlias = list[tuple[str, "Validator | CallbackMapStrict"]]
22
22
 
23
23
 
@@ -39,7 +39,7 @@ class CallbackQueryDataRule(CallbackQueryRule, abc.ABC, requires=[HasData()]):
39
39
 
40
40
 
41
41
  class CallbackDataMap(CallbackQueryDataRule):
42
- def __init__(self, mapping: MapDict) -> None:
42
+ def __init__(self, mapping: MapDict, /) -> None:
43
43
  self.mapping = self.transform_to_callbacks(
44
44
  self.transform_to_map(mapping),
45
45
  )
@@ -84,12 +84,12 @@ class CallbackDataMap(CallbackQueryDataRule):
84
84
  result = validator(value)
85
85
  if inspect.isawaitable(result):
86
86
  result = await result
87
- return result # type: ignore
87
+ return result
88
88
 
89
89
  return False
90
90
 
91
91
  @classmethod
92
- async def match(cls, callback_data: dict, callback_map: CallbackMapStrict) -> bool:
92
+ async def match(cls, callback_data: dict[str, typing.Any], callback_map: CallbackMapStrict) -> bool:
93
93
  """Matches callback_data with callback_map recursively."""
94
94
 
95
95
  for key, validator in callback_map:
@@ -119,7 +119,7 @@ class CallbackDataMap(CallbackQueryDataRule):
119
119
 
120
120
 
121
121
  class CallbackDataEq(CallbackQueryDataRule):
122
- def __init__(self, value: str):
122
+ def __init__(self, value: str, /) -> None:
123
123
  self.value = value
124
124
 
125
125
  async def check(self, event: CallbackQuery, ctx: Context) -> bool:
@@ -127,7 +127,7 @@ class CallbackDataEq(CallbackQueryDataRule):
127
127
 
128
128
 
129
129
  class CallbackDataJsonEq(CallbackQueryDataRule):
130
- def __init__(self, d: dict[str, typing.Any]):
130
+ def __init__(self, d: dict[str, typing.Any], /) -> None:
131
131
  self.d = d
132
132
 
133
133
  async def check(self, event: CallbackQuery, ctx: Context) -> bool:
@@ -135,7 +135,7 @@ class CallbackDataJsonEq(CallbackQueryDataRule):
135
135
 
136
136
 
137
137
  class CallbackDataJsonModel(CallbackQueryDataRule):
138
- def __init__(self, model: type[msgspec.Struct] | type[DataclassInstance]):
138
+ def __init__(self, model: type[msgspec.Struct] | type[DataclassInstance], /) -> None:
139
139
  self.model = model
140
140
 
141
141
  async def check(self, event: CallbackQuery, ctx: Context) -> bool:
@@ -146,7 +146,7 @@ class CallbackDataJsonModel(CallbackQueryDataRule):
146
146
 
147
147
 
148
148
  class CallbackDataMarkup(CallbackQueryDataRule):
149
- def __init__(self, patterns: PatternLike | list[PatternLike]):
149
+ def __init__(self, patterns: PatternLike | list[PatternLike], /) -> None:
150
150
  self.patterns = Markup(patterns).patterns
151
151
 
152
152
  async def check(self, event: CallbackQuery, ctx: Context) -> bool:
@@ -15,8 +15,7 @@ class FuzzyText(TextMessageRule):
15
15
 
16
16
  async def check(self, message: Message, ctx: Context) -> bool:
17
17
  match = max(
18
- difflib.SequenceMatcher(a=message.text.unwrap(), b=text).ratio()
19
- for text in self.texts
18
+ difflib.SequenceMatcher(a=message.text.unwrap(), b=text).ratio() for text in self.texts
20
19
  )
21
20
  if match < self.min_ratio:
22
21
  return False
@@ -42,7 +42,9 @@ class IsForward(MessageRule):
42
42
 
43
43
 
44
44
  class IsForwardType(MessageRule, requires=[IsForward()]):
45
- def __init__(self, fwd_type: typing.Literal["user", "hidden_user", "chat", "channel"], /) -> None:
45
+ def __init__(
46
+ self, fwd_type: typing.Literal["user", "hidden_user", "chat", "channel"], /
47
+ ) -> None:
46
48
  self.fwd_type = fwd_type
47
49
 
48
50
  async def check(self, message: Message, ctx: Context) -> bool:
@@ -80,8 +82,8 @@ class IsLanguageCode(ABCRule[T], requires=[HasFrom()]):
80
82
 
81
83
  async def check(self, event: UpdateCute, ctx: Context) -> bool:
82
84
  return (
83
- get_from_user(event.incoming_update.unwrap())
84
- .language_code.unwrap_or_none() in self.lang_codes
85
+ get_from_user(event.incoming_update.unwrap()).language_code.unwrap_or_none()
86
+ in self.lang_codes
85
87
  )
86
88
 
87
89
 
@@ -131,7 +133,7 @@ class IsDiceEmoji(MessageRule, requires=[HasDice()]):
131
133
  self.dice_emoji = dice_emoji
132
134
 
133
135
  async def check(self, message: Message, ctx: Context) -> bool:
134
- return message.dice.unwrap().emoji == self.dice_emoji
136
+ return message.dice.unwrap().emoji == self.dice_emoji
135
137
 
136
138
 
137
139
  __all__ = (
@@ -28,8 +28,7 @@ class Markup(TextMessageRule):
28
28
  if not isinstance(patterns, list):
29
29
  patterns = [patterns]
30
30
  self.patterns = [
31
- vbml.Pattern(pattern) if isinstance(pattern, str) else pattern
32
- for pattern in patterns
31
+ vbml.Pattern(pattern) if isinstance(pattern, str) else pattern for pattern in patterns
33
32
  ]
34
33
 
35
34
  async def check(self, message: Message, ctx: Context) -> bool:
@@ -8,10 +8,7 @@ class HasMention(TextMessageRule):
8
8
  async def check(self, message: Message, ctx: Context) -> bool:
9
9
  if not message.entities.unwrap_or_none():
10
10
  return False
11
- return any(
12
- entity.type == MessageEntityType.MENTION
13
- for entity in message.entities.unwrap()
14
- )
11
+ return any(entity.type == MessageEntityType.MENTION for entity in message.entities.unwrap())
15
12
 
16
13
 
17
14
  __all__ = ("HasMention",)
@@ -19,8 +19,7 @@ class Regex(TextMessageRule):
19
19
  self.regexp.append(re.compile(regex))
20
20
  case _:
21
21
  self.regexp.extend(
22
- re.compile(regexp) if isinstance(regexp, str) else regexp
23
- for regexp in regexp
22
+ re.compile(regexp) if isinstance(regexp, str) else regexp for regexp in regexp
24
23
  )
25
24
 
26
25
  async def check(self, message: Message, ctx: Context) -> bool:
@@ -21,9 +21,7 @@ class RuleEnum(ABCRule[T]):
21
21
  __enum__: list[RuleEnumState]
22
22
 
23
23
  def __init_subclass__(cls, *args, **kwargs):
24
- new_attributes = (
25
- set(cls.__dict__) - set(RuleEnum.__dict__) - {"__enum__", "__init__"}
26
- )
24
+ new_attributes = set(cls.__dict__) - set(RuleEnum.__dict__) - {"__enum__", "__init__"}
27
25
  enum_lst: list[RuleEnumState] = []
28
26
 
29
27
  self = cls.__new__(cls)
@@ -30,9 +30,7 @@ class StartCommand(
30
30
 
31
31
  async def check(self, _: Message, ctx: Context) -> bool:
32
32
  param: str | None = ctx.pop("param", None)
33
- validated_param = (
34
- self.validator(param) if self.validator and param is not None else param
35
- )
33
+ validated_param = self.validator(param) if self.validator and param is not None else param
36
34
 
37
35
  if self.param_required and validated_param is None:
38
36
  return False
@@ -33,13 +33,13 @@ class Checkbox(ABCScenario[CallbackQueryCute]):
33
33
  self,
34
34
  waiter_machine: WaiterMachine,
35
35
  chat_id: int,
36
- msg: str,
36
+ message: str,
37
37
  *,
38
38
  ready_text: str = "Ready",
39
39
  max_in_row: int = 3,
40
40
  ) -> None:
41
41
  self.chat_id = chat_id
42
- self.msg = msg
42
+ self.message = message
43
43
  self.choices: list[Choice] = []
44
44
  self.ready = ready_text
45
45
  self.max_in_row = max_in_row
@@ -58,7 +58,7 @@ class Checkbox(ABCScenario[CallbackQueryCute]):
58
58
  self.waiter_machine,
59
59
  self.ready,
60
60
  self.chat_id,
61
- self.msg,
61
+ self.message,
62
62
  )
63
63
 
64
64
  def get_markup(self) -> InlineKeyboardMarkup:
@@ -69,11 +69,7 @@ class Checkbox(ABCScenario[CallbackQueryCute]):
69
69
  choice = choices.pop(0)
70
70
  kb.add(
71
71
  InlineButton(
72
- text=(
73
- choice.default_text
74
- if not choice.is_picked
75
- else choice.picked_text
76
- ),
72
+ text=(choice.default_text if not choice.is_picked else choice.picked_text),
77
73
  callback_data=self.random_code + "/" + choice.code,
78
74
  )
79
75
  )
@@ -105,7 +101,7 @@ class Checkbox(ABCScenario[CallbackQueryCute]):
105
101
  # Toggle choice
106
102
  self.choices[i].is_picked = not self.choices[i].is_picked
107
103
  await cb.edit_text(
108
- text=self.msg,
104
+ text=self.message,
109
105
  parse_mode=self.PARSE_MODE,
110
106
  reply_markup=self.get_markup(),
111
107
  )
@@ -122,7 +118,7 @@ class Checkbox(ABCScenario[CallbackQueryCute]):
122
118
  message = (
123
119
  await api.send_message(
124
120
  chat_id=self.chat_id,
125
- text=self.msg,
121
+ text=self.message,
126
122
  parse_mode=self.PARSE_MODE,
127
123
  reply_markup=self.get_markup(),
128
124
  )
@@ -1,6 +1,7 @@
1
1
  import typing
2
2
 
3
3
  from telegrinder.bot.cute_types import CallbackQueryCute
4
+ from telegrinder.bot.dispatch.waiter_machine import WaiterMachine
4
5
 
5
6
  from .checkbox import Checkbox
6
7
 
@@ -22,9 +23,9 @@ class SingleChoice(Checkbox):
22
23
  if choice.code == code:
23
24
  self.choices[i].is_picked = True
24
25
  await cb.ctx_api.edit_message_text(
26
+ text=self.message,
25
27
  chat_id=cb.message.unwrap().v.chat.id,
26
28
  message_id=cb.message.unwrap().v.message_id,
27
- text=self.msg,
28
29
  parse_mode=self.PARSE_MODE,
29
30
  reply_markup=self.get_markup(),
30
31
  )
@@ -36,10 +37,10 @@ class SingleChoice(Checkbox):
36
37
  api: "API",
37
38
  view: "BaseStateView[CallbackQueryCute]",
38
39
  ) -> tuple[str, int]:
39
- if len([choice for choice in self.choices if choice.is_picked]) != 1:
40
+ if len(tuple(choice for choice in self.choices if choice.is_picked)) != 1:
40
41
  raise ValueError("Exactly one choice must be picked")
41
42
  choices, m_id = await super().wait(api, view)
42
- return list(choices.keys())[list(choices.values()).index(True)], m_id
43
+ return tuple(choices.keys())[tuple(choices.values()).index(True)], m_id
43
44
 
44
45
 
45
46
  __all__ = ("SingleChoice",)
@@ -42,9 +42,7 @@ class AiohttpClient(ABCClient):
42
42
  ) -> "ClientResponse":
43
43
  if not self.session:
44
44
  self.session = ClientSession(
45
- connector=TCPConnector(
46
- ssl=ssl.create_default_context(cafile=certifi.where())
47
- ),
45
+ connector=TCPConnector(ssl=ssl.create_default_context(cafile=certifi.where())),
48
46
  json_serialize=self.json_processing_module.dumps,
49
47
  **self.session_params,
50
48
  )
telegrinder/model.py CHANGED
@@ -10,11 +10,11 @@ from fntypes.co import Nothing, Result, Some
10
10
 
11
11
  from .msgspec_utils import decoder, encoder, get_origin
12
12
 
13
- T = typing.TypeVar("T")
14
-
15
13
  if typing.TYPE_CHECKING:
16
14
  from telegrinder.api.error import APIError
17
15
 
16
+ T = typing.TypeVar("T")
17
+
18
18
 
19
19
  MODEL_CONFIG: typing.Final[dict[str, typing.Any]] = {
20
20
  "omit_defaults": True,
@@ -25,7 +25,8 @@ MODEL_CONFIG: typing.Final[dict[str, typing.Any]] = {
25
25
 
26
26
  @typing.overload
27
27
  def full_result(
28
- result: Result[msgspec.Raw, "APIError"], full_t: type[T]
28
+ result: Result[msgspec.Raw, "APIError"],
29
+ full_t: type[T],
29
30
  ) -> Result[T, "APIError"]: ...
30
31
 
31
32
 
telegrinder/modules.py CHANGED
@@ -147,9 +147,7 @@ elif logging_module == "logging":
147
147
  for level, settings in LEVEL_SETTINGS.items():
148
148
  fmt = FORMAT
149
149
  for name, color in settings.items():
150
- fmt = fmt.replace(f"<{name}>", COLORS[color]).replace(
151
- f"</{name}>", COLORS["reset"]
152
- )
150
+ fmt = fmt.replace(f"<{name}>", COLORS[color]).replace(f"</{name}>", COLORS["reset"])
153
151
  LEVEL_FORMATS[level] = fmt
154
152
 
155
153
  class TelegrinderLoggingFormatter(logging.Formatter):
@@ -66,9 +66,7 @@ def variative_dec_hook(tp: type[Variative], obj: typing.Any) -> Variative:
66
66
  union_types = tuple(t for t in union_types if t not in struct_fields_match_sums)
67
67
  reverse = False
68
68
 
69
- if len(set(struct_fields_match_sums.values())) != len(
70
- struct_fields_match_sums.values()
71
- ):
69
+ if len(set(struct_fields_match_sums.values())) != len(struct_fields_match_sums.values()):
72
70
  struct_fields_match_sums = {
73
71
  m: len(m.__struct_fields__) for m in struct_fields_match_sums
74
72
  }