telegrinder 0.4.2__py3-none-any.whl → 0.5.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (233) hide show
  1. telegrinder/__init__.py +37 -55
  2. telegrinder/__meta__.py +1 -0
  3. telegrinder/api/__init__.py +6 -4
  4. telegrinder/api/api.py +100 -26
  5. telegrinder/api/error.py +42 -8
  6. telegrinder/api/response.py +4 -1
  7. telegrinder/api/token.py +2 -2
  8. telegrinder/bot/__init__.py +9 -25
  9. telegrinder/bot/bot.py +31 -25
  10. telegrinder/bot/cute_types/__init__.py +0 -0
  11. telegrinder/bot/cute_types/base.py +103 -61
  12. telegrinder/bot/cute_types/callback_query.py +447 -400
  13. telegrinder/bot/cute_types/chat_join_request.py +59 -62
  14. telegrinder/bot/cute_types/chat_member_updated.py +154 -157
  15. telegrinder/bot/cute_types/inline_query.py +41 -44
  16. telegrinder/bot/cute_types/message.py +98 -67
  17. telegrinder/bot/cute_types/pre_checkout_query.py +38 -42
  18. telegrinder/bot/cute_types/update.py +1 -8
  19. telegrinder/bot/cute_types/utils.py +1 -1
  20. telegrinder/bot/dispatch/__init__.py +10 -15
  21. telegrinder/bot/dispatch/abc.py +12 -11
  22. telegrinder/bot/dispatch/action.py +104 -0
  23. telegrinder/bot/dispatch/context.py +32 -26
  24. telegrinder/bot/dispatch/dispatch.py +61 -134
  25. telegrinder/bot/dispatch/handler/__init__.py +2 -0
  26. telegrinder/bot/dispatch/handler/abc.py +10 -8
  27. telegrinder/bot/dispatch/handler/audio_reply.py +2 -3
  28. telegrinder/bot/dispatch/handler/base.py +10 -33
  29. telegrinder/bot/dispatch/handler/document_reply.py +2 -3
  30. telegrinder/bot/dispatch/handler/func.py +55 -87
  31. telegrinder/bot/dispatch/handler/media_group_reply.py +2 -3
  32. telegrinder/bot/dispatch/handler/message_reply.py +2 -3
  33. telegrinder/bot/dispatch/handler/photo_reply.py +2 -3
  34. telegrinder/bot/dispatch/handler/sticker_reply.py +2 -3
  35. telegrinder/bot/dispatch/handler/video_reply.py +2 -3
  36. telegrinder/bot/dispatch/middleware/__init__.py +0 -0
  37. telegrinder/bot/dispatch/middleware/abc.py +79 -55
  38. telegrinder/bot/dispatch/middleware/global_middleware.py +18 -33
  39. telegrinder/bot/dispatch/process.py +84 -105
  40. telegrinder/bot/dispatch/return_manager/__init__.py +0 -0
  41. telegrinder/bot/dispatch/return_manager/abc.py +102 -65
  42. telegrinder/bot/dispatch/return_manager/callback_query.py +4 -5
  43. telegrinder/bot/dispatch/return_manager/inline_query.py +3 -4
  44. telegrinder/bot/dispatch/return_manager/message.py +8 -10
  45. telegrinder/bot/dispatch/return_manager/pre_checkout_query.py +4 -5
  46. telegrinder/bot/dispatch/view/__init__.py +4 -4
  47. telegrinder/bot/dispatch/view/abc.py +6 -16
  48. telegrinder/bot/dispatch/view/base.py +54 -178
  49. telegrinder/bot/dispatch/view/box.py +19 -18
  50. telegrinder/bot/dispatch/view/callback_query.py +4 -8
  51. telegrinder/bot/dispatch/view/chat_join_request.py +5 -6
  52. telegrinder/bot/dispatch/view/chat_member.py +5 -25
  53. telegrinder/bot/dispatch/view/error.py +9 -0
  54. telegrinder/bot/dispatch/view/inline_query.py +4 -8
  55. telegrinder/bot/dispatch/view/message.py +5 -25
  56. telegrinder/bot/dispatch/view/pre_checkout_query.py +4 -8
  57. telegrinder/bot/dispatch/view/raw.py +3 -109
  58. telegrinder/bot/dispatch/waiter_machine/__init__.py +2 -5
  59. telegrinder/bot/dispatch/waiter_machine/actions.py +6 -4
  60. telegrinder/bot/dispatch/waiter_machine/hasher/__init__.py +1 -3
  61. telegrinder/bot/dispatch/waiter_machine/hasher/callback.py +1 -1
  62. telegrinder/bot/dispatch/waiter_machine/hasher/hasher.py +11 -7
  63. telegrinder/bot/dispatch/waiter_machine/hasher/message.py +0 -0
  64. telegrinder/bot/dispatch/waiter_machine/machine.py +43 -60
  65. telegrinder/bot/dispatch/waiter_machine/middleware.py +19 -23
  66. telegrinder/bot/dispatch/waiter_machine/short_state.py +6 -5
  67. telegrinder/bot/polling/__init__.py +0 -0
  68. telegrinder/bot/polling/abc.py +0 -0
  69. telegrinder/bot/polling/polling.py +209 -88
  70. telegrinder/bot/rules/__init__.py +3 -16
  71. telegrinder/bot/rules/abc.py +42 -122
  72. telegrinder/bot/rules/callback_data.py +29 -49
  73. telegrinder/bot/rules/chat_join.py +5 -23
  74. telegrinder/bot/rules/command.py +8 -4
  75. telegrinder/bot/rules/enum_text.py +3 -4
  76. telegrinder/bot/rules/func.py +7 -14
  77. telegrinder/bot/rules/fuzzy.py +3 -4
  78. telegrinder/bot/rules/inline.py +8 -20
  79. telegrinder/bot/rules/integer.py +2 -3
  80. telegrinder/bot/rules/is_from.py +12 -11
  81. telegrinder/bot/rules/logic.py +11 -5
  82. telegrinder/bot/rules/markup.py +22 -14
  83. telegrinder/bot/rules/mention.py +8 -7
  84. telegrinder/bot/rules/message_entities.py +8 -4
  85. telegrinder/bot/rules/node.py +23 -12
  86. telegrinder/bot/rules/payload.py +5 -4
  87. telegrinder/bot/rules/payment_invoice.py +6 -21
  88. telegrinder/bot/rules/regex.py +2 -4
  89. telegrinder/bot/rules/rule_enum.py +8 -7
  90. telegrinder/bot/rules/start.py +5 -6
  91. telegrinder/bot/rules/state.py +1 -1
  92. telegrinder/bot/rules/text.py +4 -15
  93. telegrinder/bot/rules/update.py +3 -4
  94. telegrinder/bot/scenario/__init__.py +0 -0
  95. telegrinder/bot/scenario/abc.py +6 -5
  96. telegrinder/bot/scenario/checkbox.py +1 -1
  97. telegrinder/bot/scenario/choice.py +30 -39
  98. telegrinder/client/__init__.py +3 -5
  99. telegrinder/client/abc.py +11 -6
  100. telegrinder/client/aiohttp.py +141 -27
  101. telegrinder/client/form_data.py +1 -1
  102. telegrinder/model.py +61 -89
  103. telegrinder/modules.py +325 -102
  104. telegrinder/msgspec_utils/__init__.py +40 -0
  105. telegrinder/msgspec_utils/abc.py +18 -0
  106. telegrinder/msgspec_utils/custom_types/__init__.py +6 -0
  107. telegrinder/msgspec_utils/custom_types/datetime.py +24 -0
  108. telegrinder/msgspec_utils/custom_types/enum_meta.py +43 -0
  109. telegrinder/msgspec_utils/custom_types/literal.py +25 -0
  110. telegrinder/msgspec_utils/custom_types/option.py +17 -0
  111. telegrinder/msgspec_utils/decoder.py +389 -0
  112. telegrinder/msgspec_utils/encoder.py +206 -0
  113. telegrinder/{msgspec_json.py → msgspec_utils/json.py} +6 -5
  114. telegrinder/msgspec_utils/tools.py +75 -0
  115. telegrinder/node/__init__.py +24 -7
  116. telegrinder/node/attachment.py +1 -0
  117. telegrinder/node/base.py +154 -72
  118. telegrinder/node/callback_query.py +5 -5
  119. telegrinder/node/collection.py +39 -0
  120. telegrinder/node/command.py +1 -2
  121. telegrinder/node/composer.py +121 -72
  122. telegrinder/node/container.py +11 -8
  123. telegrinder/node/context.py +48 -0
  124. telegrinder/node/either.py +27 -40
  125. telegrinder/node/error.py +41 -0
  126. telegrinder/node/event.py +37 -11
  127. telegrinder/node/exceptions.py +7 -0
  128. telegrinder/node/file.py +0 -0
  129. telegrinder/node/i18n.py +108 -0
  130. telegrinder/node/me.py +3 -2
  131. telegrinder/node/payload.py +1 -1
  132. telegrinder/node/polymorphic.py +63 -28
  133. telegrinder/node/reply_message.py +12 -0
  134. telegrinder/node/rule.py +6 -13
  135. telegrinder/node/scope.py +14 -5
  136. telegrinder/node/session.py +53 -0
  137. telegrinder/node/source.py +41 -9
  138. telegrinder/node/text.py +1 -2
  139. telegrinder/node/tools/__init__.py +0 -0
  140. telegrinder/node/tools/generator.py +3 -5
  141. telegrinder/node/utility.py +16 -0
  142. telegrinder/py.typed +0 -0
  143. telegrinder/rules.py +0 -0
  144. telegrinder/tools/__init__.py +48 -88
  145. telegrinder/tools/aio.py +103 -0
  146. telegrinder/tools/callback_data_serialization/__init__.py +5 -0
  147. telegrinder/tools/{callback_data_serilization → callback_data_serialization}/abc.py +0 -0
  148. telegrinder/tools/{callback_data_serilization → callback_data_serialization}/json_ser.py +2 -3
  149. telegrinder/tools/{callback_data_serilization → callback_data_serialization}/msgpack_ser.py +45 -27
  150. telegrinder/tools/final.py +21 -0
  151. telegrinder/tools/formatting/__init__.py +2 -18
  152. telegrinder/tools/formatting/deep_links/__init__.py +39 -0
  153. telegrinder/tools/formatting/{deep_links.py → deep_links/links.py} +12 -85
  154. telegrinder/tools/formatting/deep_links/parsing.py +90 -0
  155. telegrinder/tools/formatting/deep_links/validators.py +8 -0
  156. telegrinder/tools/formatting/html_formatter.py +18 -45
  157. telegrinder/tools/fullname.py +83 -0
  158. telegrinder/tools/global_context/__init__.py +4 -3
  159. telegrinder/tools/global_context/abc.py +17 -14
  160. telegrinder/tools/global_context/builtin_context.py +39 -0
  161. telegrinder/tools/global_context/global_context.py +138 -39
  162. telegrinder/tools/input_file_directory.py +0 -0
  163. telegrinder/tools/keyboard/__init__.py +39 -0
  164. telegrinder/tools/keyboard/abc.py +159 -0
  165. telegrinder/tools/keyboard/base.py +77 -0
  166. telegrinder/tools/keyboard/buttons/__init__.py +14 -0
  167. telegrinder/tools/keyboard/buttons/base.py +18 -0
  168. telegrinder/tools/{buttons.py → keyboard/buttons/buttons.py} +71 -23
  169. telegrinder/tools/keyboard/buttons/static_buttons.py +56 -0
  170. telegrinder/tools/keyboard/buttons/tools.py +18 -0
  171. telegrinder/tools/keyboard/data.py +20 -0
  172. telegrinder/tools/keyboard/keyboard.py +131 -0
  173. telegrinder/tools/keyboard/static_keyboard.py +83 -0
  174. telegrinder/tools/lifespan.py +87 -51
  175. telegrinder/tools/limited_dict.py +4 -1
  176. telegrinder/tools/loop_wrapper.py +332 -0
  177. telegrinder/tools/magic/__init__.py +32 -0
  178. telegrinder/tools/magic/annotations.py +165 -0
  179. telegrinder/tools/magic/dictionary.py +20 -0
  180. telegrinder/tools/magic/function.py +246 -0
  181. telegrinder/tools/magic/shortcut.py +111 -0
  182. telegrinder/tools/parse_mode.py +9 -3
  183. telegrinder/tools/singleton/__init__.py +4 -0
  184. telegrinder/tools/singleton/abc.py +14 -0
  185. telegrinder/tools/singleton/singleton.py +18 -0
  186. telegrinder/tools/state_storage/__init__.py +0 -0
  187. telegrinder/tools/state_storage/abc.py +6 -1
  188. telegrinder/tools/state_storage/memory.py +1 -1
  189. telegrinder/tools/strings.py +0 -0
  190. telegrinder/types/__init__.py +307 -268
  191. telegrinder/types/enums.py +68 -37
  192. telegrinder/types/input_file.py +3 -3
  193. telegrinder/types/methods.py +5699 -5055
  194. telegrinder/types/methods_utils.py +62 -0
  195. telegrinder/types/objects.py +1782 -994
  196. telegrinder/verification_utils.py +3 -1
  197. telegrinder-0.5.1.dist-info/METADATA +162 -0
  198. telegrinder-0.5.1.dist-info/RECORD +200 -0
  199. {telegrinder-0.4.2.dist-info → telegrinder-0.5.1.dist-info}/licenses/LICENSE +2 -2
  200. telegrinder/bot/dispatch/waiter_machine/hasher/state.py +0 -20
  201. telegrinder/bot/rules/id.py +0 -24
  202. telegrinder/bot/rules/message.py +0 -15
  203. telegrinder/client/sonic.py +0 -212
  204. telegrinder/msgspec_utils.py +0 -478
  205. telegrinder/tools/adapter/__init__.py +0 -19
  206. telegrinder/tools/adapter/abc.py +0 -49
  207. telegrinder/tools/adapter/dataclass.py +0 -56
  208. telegrinder/tools/adapter/errors.py +0 -5
  209. telegrinder/tools/adapter/event.py +0 -61
  210. telegrinder/tools/adapter/node.py +0 -46
  211. telegrinder/tools/adapter/raw_event.py +0 -27
  212. telegrinder/tools/adapter/raw_update.py +0 -30
  213. telegrinder/tools/callback_data_serilization/__init__.py +0 -5
  214. telegrinder/tools/error_handler/__init__.py +0 -10
  215. telegrinder/tools/error_handler/abc.py +0 -30
  216. telegrinder/tools/error_handler/error.py +0 -9
  217. telegrinder/tools/error_handler/error_handler.py +0 -179
  218. telegrinder/tools/formatting/spec_html_formats.py +0 -75
  219. telegrinder/tools/functional.py +0 -8
  220. telegrinder/tools/global_context/telegrinder_ctx.py +0 -27
  221. telegrinder/tools/i18n/__init__.py +0 -12
  222. telegrinder/tools/i18n/abc.py +0 -32
  223. telegrinder/tools/i18n/middleware/__init__.py +0 -3
  224. telegrinder/tools/i18n/middleware/abc.py +0 -22
  225. telegrinder/tools/i18n/simple.py +0 -43
  226. telegrinder/tools/keyboard.py +0 -132
  227. telegrinder/tools/loop_wrapper/__init__.py +0 -4
  228. telegrinder/tools/loop_wrapper/abc.py +0 -20
  229. telegrinder/tools/loop_wrapper/loop_wrapper.py +0 -169
  230. telegrinder/tools/magic.py +0 -344
  231. telegrinder-0.4.2.dist-info/METADATA +0 -151
  232. telegrinder-0.4.2.dist-info/RECORD +0 -182
  233. {telegrinder-0.4.2.dist-info → telegrinder-0.5.1.dist-info}/WHEEL +0 -0
@@ -6,9 +6,9 @@ import typing_extensions as typing
6
6
  from fntypes.co import Error, Nothing, Ok, Option, Result, Some
7
7
 
8
8
  from telegrinder.modules import logger
9
- from telegrinder.msgspec_utils import msgspec_convert
10
-
11
- from .abc import ABCGlobalContext, CtxVar, CtxVariable, GlobalCtxVar
9
+ from telegrinder.msgspec_utils import convert
10
+ from telegrinder.tools.fullname import fullname
11
+ from telegrinder.tools.global_context.abc import NODEFAULT, ABCGlobalContext, CtxVar, CtxVariable, GlobalCtxVar
12
12
 
13
13
  T = typing.TypeVar("T")
14
14
  F = typing.TypeVar("F", bound=typing.Callable)
@@ -23,7 +23,7 @@ else:
23
23
  def type_check(value: object, value_type: type[T]) -> typing.TypeGuard[T]:
24
24
  if value_type in (typing.Any, object):
25
25
  return True
26
- match msgspec_convert(value, value_type):
26
+ match convert(value, value_type):
27
27
  case Ok(v):
28
28
  return type(value) is type(v)
29
29
  case Error(_):
@@ -35,7 +35,9 @@ def is_dunder(name: str) -> bool:
35
35
 
36
36
 
37
37
  def get_orig_class(obj: T) -> type[T]:
38
- return getattr(obj, "__orig_class__", obj.__class__)
38
+ if "__orig_class__" not in obj.__dict__:
39
+ return type(obj)
40
+ return obj.__dict__["__orig_class__"]
39
41
 
40
42
 
41
43
  def root_protection(func: F) -> F:
@@ -65,19 +67,82 @@ def root_protection(func: F) -> F:
65
67
  return wrapper # type: ignore
66
68
 
67
69
 
68
- def ctx_var(*, default: T, frozen: bool = False) -> T:
70
+ @typing.overload
71
+ def ctx_var() -> typing.Any: ...
72
+
73
+
74
+ @typing.overload
75
+ def ctx_var(
76
+ *,
77
+ init: bool = ...,
78
+ const: bool = ...,
79
+ ) -> typing.Any: ...
80
+
81
+
82
+ @typing.overload
83
+ def ctx_var(
84
+ *,
85
+ default: typing.Any,
86
+ init: bool = ...,
87
+ const: bool = ...,
88
+ ) -> typing.Any: ...
89
+
90
+
91
+ @typing.overload
92
+ def ctx_var(
93
+ *,
94
+ default_factory: typing.Callable[[], typing.Any],
95
+ init: bool = ...,
96
+ const: bool = ...,
97
+ ) -> typing.Any: ...
98
+
99
+
100
+ def ctx_var(
101
+ *,
102
+ default: typing.Any = NODEFAULT,
103
+ default_factory: typing.Any = NODEFAULT,
104
+ const: bool = False,
105
+ **_: typing.Any,
106
+ ) -> typing.Any:
69
107
  """Example:
70
108
  ```
71
109
  class MyCtx(GlobalContext):
110
+ __ctx_name__ = "my_ctx"
111
+
72
112
  name: str
73
- URL: str = ctx_var("https://google.com", frozen=True)
113
+ URL: typing.Final[str] = ctx_var(default="https://google.com", init=False, const=True)
74
114
 
75
- ctx = MyCtx(name=ctx_var("Alex", frozen=True))
115
+ ctx = MyCtx(name="John")
76
116
  ctx.URL #: 'https://google.com'
77
117
  ctx.URL = '...' #: type checking error & exception 'TypeError'
78
118
  ```
79
119
  """
80
- return typing.cast(T, CtxVar(default, const=frozen))
120
+ return CtxVar(value=default, factory=default_factory, const=const)
121
+
122
+
123
+ def runtime_init[T: ABCGlobalContext](cls: type[T], /) -> type[T]:
124
+ r'''Initialization the global context at runtime.
125
+
126
+ ```python
127
+ @runtime_init
128
+ class Box(ABCGlobalContext):
129
+ __ctx_name__ = "box"
130
+
131
+ cookies: list[Cookie] = ctx_var(default_factory=lambda: [ChocolateCookie()], init=False)
132
+ """
133
+ init=False means that when calling the class constructor it will not be necessary
134
+ to pass this field to the class constructor, because it will already be initialized.
135
+ """
136
+
137
+ box = Box() # So, this global context has already been initialized, so calling the class
138
+ # immediately returns an initialized instance of this class from the memory storage.
139
+
140
+ box.cookies.append(OatmealCookie())
141
+ print(box.cookies) # [<ChocolateCookie>, <OatmealCookie>]
142
+ ```
143
+ '''
144
+ cls() # Init an instance of the global context.
145
+ return cls
81
146
 
82
147
 
83
148
  @dataclasses.dataclass(frozen=True, eq=False, slots=True)
@@ -98,7 +163,7 @@ class Storage:
98
163
  )
99
164
 
100
165
  def __repr__(self) -> str:
101
- return "<ContextStorage: %s>" % ", ".join("ctx @" + repr(x) for x in self._storage)
166
+ return "<Storage: %s>" % ", ".join(repr(x) for x in self._storage)
102
167
 
103
168
  @property
104
169
  def storage(self) -> dict[str, "GlobalContext"]:
@@ -118,28 +183,39 @@ class Storage:
118
183
  @typing.dataclass_transform(
119
184
  kw_only_default=True,
120
185
  order_default=True,
186
+ frozen_default=False,
121
187
  field_specifiers=(ctx_var,),
122
188
  )
123
189
  class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, GlobalCtxVar[CtxValueT]]):
124
- """GlobalContext.
190
+ """This is class to store the context globally.
125
191
 
192
+ `GlobalContext` is a dictionary with additional methods for working with context.
193
+
194
+ Example:
126
195
  ```
127
196
  ctx = GlobalContext()
128
197
  ctx["client"] = Client()
129
- ctx.address = CtxVar("128.0.0.7:8888", const=True)
198
+ ctx.host = CtxVar("128.0.0.7:8888", const=True)
130
199
 
131
200
  def request():
132
201
  data = {"user": "root_user", "password": "secret_password"}
133
- ctx.client.request(ctx.address + "/login", data)
202
+ ctx.client.request(ctx.host + "/login", data)
203
+ ```
204
+
134
205
  """
135
206
 
136
207
  __ctx_name__: str | None
208
+ """Global context name."""
209
+
137
210
  __storage__: typing.ClassVar[Storage] = Storage()
211
+ """Storage memory; this is the storage where all initialized global contexts are stored."""
212
+
138
213
  __root_attributes__: typing.ClassVar[tuple[RootAttr, ...]] = (
139
- RootAttr("__ctx_name__"),
140
- RootAttr("__root_attributes__"),
141
- RootAttr("__storage__"),
214
+ RootAttr(name="__ctx_name__"),
215
+ RootAttr(name="__root_attributes__"),
216
+ RootAttr(name="__storage__"),
142
217
  )
218
+ """The sequence of root attributes of this class including this attribute."""
143
219
 
144
220
  def __new__(
145
221
  cls,
@@ -148,13 +224,15 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
148
224
  **variables: typing.Any | CtxVar[CtxValueT],
149
225
  ) -> typing.Self:
150
226
  """Create or get from storage a new `GlobalContext` object."""
151
- if not issubclass(GlobalContext, cls):
227
+ if cls is not GlobalContext:
152
228
  defaults = {}
153
229
  for name in cls.__annotations__:
154
230
  if name in cls.__dict__ and name not in cls.__root_attributes__:
155
231
  defaults[name] = getattr(cls, name)
156
232
  delattr(cls, name)
157
- if isinstance(defaults[name], CtxVar) and defaults[name].const:
233
+
234
+ default_ = defaults[name]
235
+ if isinstance(default_, CtxVar) and default_.const:
158
236
  variables.pop(name, None)
159
237
 
160
238
  variables = defaults | variables
@@ -184,36 +262,55 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
184
262
  self.set_context_variables(variables)
185
263
 
186
264
  def __repr__(self) -> str:
187
- return "<{} -> ({})>".format(
188
- f"{self.__class__.__name__}@{self.ctx_name!r}",
189
- ", ".join(repr(var) for var in self),
265
+ return "<{} contains variables: {{ {} }}>".format(
266
+ f"{fullname(self)}@{self.ctx_name}",
267
+ ", ".join(var_name for var_name in self),
190
268
  )
191
269
 
192
- def __eq__(self, __value: "GlobalContext") -> bool:
270
+ def __eq__(self, __value: object) -> bool:
193
271
  """Returns True if the names of context stores
194
272
  that use self and __value instances are equivalent.
195
273
  """
196
- return isinstance(__value, GlobalContext) and self.__ctx_name__ == __value.__ctx_name__
274
+ if not isinstance(__value, type(self)):
275
+ return NotImplemented
276
+ return self.__ctx_name__ == __value.__ctx_name__ and self == __value
197
277
 
198
- def __setitem__(self, __name: str, __value: CtxValueT | CtxVariable[CtxValueT]):
278
+ def __setitem__(self, __name: str, __value: CtxValueT | CtxVariable[CtxValueT]) -> None:
199
279
  if is_dunder(__name):
200
- raise NameError("Cannot set a context variable with dunder name.")
280
+ raise NameError("Cannot set a context variable with a dunder name.")
281
+
201
282
  var = self.get(__name)
202
- if var and var.unwrap().const:
283
+ if var and (var.value.const and var.value.value is not NODEFAULT):
203
284
  raise TypeError(f"Unable to set variable {__name!r}, because it's a constant.")
204
- dict.__setitem__(self, __name, GlobalCtxVar.collect(__name, __value))
285
+
286
+ dict.__setitem__(
287
+ self,
288
+ __name,
289
+ GlobalCtxVar.from_var(
290
+ name=__name,
291
+ ctx_value=__value,
292
+ const=var.map(lambda var: var.const).unwrap_or(False),
293
+ ),
294
+ )
205
295
 
206
296
  def __getitem__(self, __name: str) -> CtxValueT:
207
- return self.get(__name).unwrap().value # type: ignore
297
+ value = self.get(__name).unwrap().value
298
+ if value is NODEFAULT:
299
+ raise NameError(f"Variable {__name!r} is not defined in {self.ctx_name!r}.")
300
+ return value
208
301
 
209
- def __delitem__(self, __name: str):
302
+ def __delitem__(self, __name: str) -> None:
210
303
  var = self.get(__name).unwrap()
211
304
  if var.const:
212
305
  raise TypeError(f"Unable to delete variable {__name!r}, because it's a constant.")
306
+
307
+ if var.value is NODEFAULT:
308
+ raise NameError(f"Variable {__name!r} is not defined in {self.ctx_name!r}.")
309
+
213
310
  dict.__delitem__(self, __name)
214
311
 
215
312
  @root_protection
216
- def __setattr__(self, __name: str, __value: CtxValueT | CtxVariable[CtxValueT]):
313
+ def __setattr__(self, __name: str, __value: CtxValueT | CtxVariable[CtxValueT]) -> None:
217
314
  """Setting a context variable."""
218
315
  if is_dunder(__name):
219
316
  return object.__setattr__(self, __name, __value)
@@ -235,14 +332,12 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
235
332
 
236
333
  @property
237
334
  def ctx_name(self) -> str:
238
- """Context name."""
239
- return self.__ctx_name__ or "<Unnamed ctx at %#x>" % id(self)
335
+ """Global context name."""
336
+ return self.__ctx_name__ or "<Unnamed global context at %#x>" % id(self)
240
337
 
241
338
  @classmethod
242
339
  def is_root_attribute(cls, name: str) -> bool:
243
- """Returns True if exists root attribute
244
- otherwise False.
245
- """
340
+ """Returns True if name is a root attribute."""
246
341
  return name in cls.__root_attributes__
247
342
 
248
343
  def set_context_variables(self, variables: typing.Mapping[str, CtxValueT | CtxVariable[CtxValueT]]) -> None:
@@ -314,11 +409,14 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
314
409
  """Get context variable by name."""
315
410
  var_value_type = typing.Any if var_value_type is object else var_value_type
316
411
  generic_types = typing.get_args(get_orig_class(self))
412
+
317
413
  if generic_types and var_value_type is object:
318
414
  var_value_type = generic_types[0]
415
+
319
416
  var = dict.get(self, var_name)
320
417
  if var is None:
321
418
  return Nothing()
419
+
322
420
  assert type_check(var.value, var_value_type), (
323
421
  "Context variable value type of {!r} does not correspond to the expected type {!r}.".format(
324
422
  type(var.value).__name__,
@@ -350,6 +448,7 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
350
448
  var = self.get(old_var_name).unwrap()
351
449
  if var.const:
352
450
  return Error(f"Unable to rename variable {old_var_name!r}, because it's a constant.")
451
+
353
452
  del self[old_var_name]
354
453
  self[new_var_name] = var.value
355
454
  return Ok(_())
@@ -360,6 +459,7 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
360
459
  """
361
460
  if not self:
362
461
  return
462
+
363
463
  if include_consts:
364
464
  logger.warning(
365
465
  "Constants from the global context {!r} have been cleaned up!",
@@ -375,9 +475,11 @@ class GlobalContext(ABCGlobalContext, typing.Generic[CtxValueT], dict[str, Globa
375
475
  """Delete context by `ctx_name`."""
376
476
  if not self.__ctx_name__:
377
477
  return Error("Cannot delete unnamed context.")
478
+
378
479
  ctx = self.__storage__.get(self.ctx_name).unwrap()
379
480
  dict.clear(ctx)
380
481
  self.__storage__.delete(self.ctx_name)
482
+
381
483
  logger.warning(f"Global context {self.ctx_name!r} has been deleted!")
382
484
  return Ok(_())
383
485
 
@@ -391,8 +493,5 @@ __all__ = (
391
493
  "RootAttr",
392
494
  "Storage",
393
495
  "ctx_var",
394
- "get_orig_class",
395
- "is_dunder",
396
- "root_protection",
397
- "type_check",
496
+ "runtime_init",
398
497
  )
File without changes
@@ -0,0 +1,39 @@
1
+ from telegrinder.tools.keyboard.base import ABCKeyboard, ABCStaticKeyboard, BaseKeyboard, BaseStaticKeyboard
2
+ from telegrinder.tools.keyboard.buttons.base import (
3
+ BaseButton,
4
+ BaseStaticButton,
5
+ )
6
+ from telegrinder.tools.keyboard.buttons.buttons import (
7
+ Button,
8
+ InlineButton,
9
+ )
10
+ from telegrinder.tools.keyboard.buttons.static_buttons import (
11
+ StaticButton,
12
+ StaticInlineButton,
13
+ )
14
+ from telegrinder.tools.keyboard.buttons.tools import RowButtons
15
+ from telegrinder.tools.keyboard.data import KeyboardModel
16
+ from telegrinder.tools.keyboard.keyboard import InlineKeyboard, Keyboard
17
+ from telegrinder.tools.keyboard.static_keyboard import (
18
+ StaticInlineKeyboard,
19
+ StaticKeyboard,
20
+ )
21
+
22
+ __all__ = (
23
+ "ABCKeyboard",
24
+ "ABCStaticKeyboard",
25
+ "BaseButton",
26
+ "BaseKeyboard",
27
+ "BaseStaticButton",
28
+ "BaseStaticKeyboard",
29
+ "Button",
30
+ "InlineButton",
31
+ "InlineKeyboard",
32
+ "Keyboard",
33
+ "KeyboardModel",
34
+ "RowButtons",
35
+ "StaticButton",
36
+ "StaticInlineButton",
37
+ "StaticInlineKeyboard",
38
+ "StaticKeyboard",
39
+ )
@@ -0,0 +1,159 @@
1
+ from __future__ import annotations
2
+
3
+ import abc
4
+ import typing
5
+
6
+ from telegrinder.tools.fullname import fullname
7
+ from telegrinder.tools.keyboard.buttons.base import BaseButton, BaseStaticButton
8
+ from telegrinder.tools.keyboard.buttons.tools import RowButtons
9
+
10
+ if typing.TYPE_CHECKING:
11
+ from telegrinder.types.objects import InlineKeyboardMarkup, ReplyKeyboardMarkup
12
+
13
+ type DictStrAny = dict[str, typing.Any]
14
+ type AnyMarkup = ReplyKeyboardMarkup | InlineKeyboardMarkup
15
+ type RawKeyboard = list[list[DictStrAny]]
16
+ type Button = DictStrAny | list[DictStrAny] | BaseButton | RowButtons[BaseButton]
17
+
18
+ KEYBOARD_BUTTON_CLASS_KEY: typing.Final[str] = "_button_class"
19
+ NO_VALUE: typing.Final[object] = object()
20
+
21
+
22
+ def _has_under(name: str, /) -> bool:
23
+ return name.startswith("_")
24
+
25
+
26
+ def _get_buttons(cls_: type[ABCStaticKeyboard], /) -> dict[str, BaseStaticButton]:
27
+ button_class = cls_.get_button_class()
28
+ return {k: v for k, v in dict(vars(cls_)).items() if isinstance(v, button_class)}
29
+
30
+
31
+ def copy_keyboard(keyboard: RawKeyboard, /) -> RawKeyboard:
32
+ return [row.copy() for row in keyboard if row]
33
+
34
+
35
+ class ABCKeyboard(abc.ABC):
36
+ keyboard: RawKeyboard
37
+
38
+ @abc.abstractmethod
39
+ def dict(self) -> DictStrAny:
40
+ pass
41
+
42
+ @abc.abstractmethod
43
+ def get_markup(self) -> AnyMarkup:
44
+ pass
45
+
46
+ @abc.abstractmethod
47
+ def copy(self, **with_changes: typing.Any) -> typing.Self:
48
+ pass
49
+
50
+ def add(self, button: Button, /) -> typing.Self:
51
+ if not self.keyboard:
52
+ self.row()
53
+
54
+ if isinstance(button, RowButtons):
55
+ self.keyboard[-1].extend(button.get_data())
56
+ if button.auto_row:
57
+ self.row()
58
+ return self
59
+
60
+ if isinstance(button, list):
61
+ self.keyboard[-1].extend(button)
62
+ return self
63
+
64
+ self.keyboard[-1].append(button if isinstance(button, dict) else button.get_data())
65
+ return self
66
+
67
+ def row(self) -> typing.Self:
68
+ if len(self.keyboard) and not len(self.keyboard[-1]):
69
+ return self
70
+
71
+ self.keyboard.append([])
72
+ return self
73
+
74
+ def format_text(self, **format_data: typing.Any) -> typing.Self:
75
+ copy_keyboard = self.copy()
76
+
77
+ for row in self.keyboard:
78
+ for button in row:
79
+ button["text"] = button["text"].format(**format_data)
80
+
81
+ return copy_keyboard
82
+
83
+ def merge(self, other: typing.Self, /) -> typing.Self:
84
+ self.keyboard.extend(copy_keyboard(other.keyboard))
85
+ return self
86
+
87
+ def merge_to_last_row(self, other: typing.Self, /) -> typing.Self:
88
+ kb_len = len(other.keyboard)
89
+
90
+ for index, row in enumerate(copy_keyboard(other.keyboard), start=1):
91
+ for button in row:
92
+ self.keyboard[-1].append(button)
93
+
94
+ if index < kb_len:
95
+ self.keyboard.append([])
96
+
97
+ return self
98
+
99
+
100
+ class StaticKeyboardMeta(type):
101
+ if not typing.TYPE_CHECKING:
102
+
103
+ def __getattr__(cls, name, /):
104
+ if not _has_under(name):
105
+ buttons = _get_buttons(cls)
106
+ if name in buttons:
107
+ return buttons[name]
108
+ return super().__getattribute__(name)
109
+
110
+ def __setattr__(cls, name: str, value: typing.Any, /) -> None:
111
+ if not _has_under(name) and name in _get_buttons(cls): # type: ignore
112
+ raise AttributeError(f"Cannot reassing attribute {name!r}.")
113
+ return super().__setattr__(name, value)
114
+
115
+ def __delattr__(cls, name: str, /) -> None:
116
+ if not _has_under(name) and name in _get_buttons(cls): # type: ignore
117
+ raise AttributeError(f"Cannot delete attribute {name!r}.")
118
+ return super().__delattr__(name)
119
+
120
+
121
+ class ABCStaticKeyboardMeta(abc.ABCMeta, StaticKeyboardMeta):
122
+ pass
123
+
124
+
125
+ class ABCStaticKeyboard(metaclass=ABCStaticKeyboardMeta):
126
+ __keyboard__: typing.Any
127
+
128
+ @abc.abstractmethod
129
+ def dict(self) -> DictStrAny:
130
+ pass
131
+
132
+ @abc.abstractmethod
133
+ def get_markup(self) -> AnyMarkup:
134
+ pass
135
+
136
+ @classmethod
137
+ def get_button_class(cls) -> type[BaseStaticButton]:
138
+ sentinel = object()
139
+ button_class = getattr(cls, KEYBOARD_BUTTON_CLASS_KEY, sentinel)
140
+
141
+ if button_class not in (sentinel, NO_VALUE):
142
+ return typing.cast("type[BaseStaticButton]", button_class)
143
+
144
+ if button_class is not NO_VALUE:
145
+ for obj in cls.__dict__.values():
146
+ if isinstance(obj, BaseStaticButton):
147
+ button_class = type(obj)
148
+ setattr(cls, KEYBOARD_BUTTON_CLASS_KEY, button_class)
149
+ return button_class
150
+
151
+ setattr(cls, KEYBOARD_BUTTON_CLASS_KEY, NO_VALUE)
152
+ raise ValueError(f"Static keyboard {fullname(cls)!r} has no static buttons.")
153
+
154
+ @classmethod
155
+ def get_buttons(cls) -> dict[str, BaseStaticButton]:
156
+ return _get_buttons(cls)
157
+
158
+
159
+ __all__ = ("ABCKeyboard", "ABCStaticKeyboard")
@@ -0,0 +1,77 @@
1
+ from __future__ import annotations
2
+
3
+ import abc
4
+ import typing
5
+
6
+ from telegrinder.tools.keyboard.abc import ABCKeyboard, ABCStaticKeyboard, AnyMarkup, DictStrAny
7
+ from telegrinder.tools.keyboard.buttons.base import BaseButton
8
+
9
+ if typing.TYPE_CHECKING:
10
+ from telegrinder.tools.keyboard.buttons.base import BaseStaticButton
11
+
12
+ KEYBOARD_BUTTONS_KEY: typing.Final[str] = "_buttons"
13
+ NO_VALUE: typing.Final[object] = object()
14
+
15
+
16
+ def _is_implemented[T: ABCKeyboard](
17
+ value: object,
18
+ kb_class: type[T],
19
+ /,
20
+ ) -> typing.TypeGuard[T | BaseButton]:
21
+ return isinstance(value, BaseButton | kb_class)
22
+
23
+
24
+ class BaseKeyboard(ABCKeyboard, abc.ABC):
25
+ @abc.abstractmethod
26
+ def dict(self) -> DictStrAny:
27
+ pass
28
+
29
+ @abc.abstractmethod
30
+ def get_markup(self) -> AnyMarkup:
31
+ pass
32
+
33
+ @abc.abstractmethod
34
+ def copy(self, **with_changes: typing.Any) -> typing.Self:
35
+ pass
36
+
37
+ def __and__(self, other: object, /) -> typing.Self:
38
+ if not _is_implemented(other, type(self)):
39
+ return NotImplemented
40
+ return self.add(other) if isinstance(other, BaseButton) else self.merge(other)
41
+
42
+ def __or__(self, other: object, /) -> typing.Self:
43
+ if not _is_implemented(other, type(self)):
44
+ return NotImplemented
45
+
46
+ kb = self.row()
47
+ return kb.add(other) if isinstance(other, BaseButton) else kb.merge_to_last_row(other)
48
+
49
+
50
+ class BaseStaticKeyboard(ABCStaticKeyboard, abc.ABC):
51
+ __max_in_row__: int
52
+
53
+ @classmethod
54
+ def get_buttons(cls) -> dict[str, BaseStaticButton]:
55
+ if (buttons := cls._get_secret_value(KEYBOARD_BUTTONS_KEY)) is not NO_VALUE:
56
+ return buttons
57
+ return cls._set_secret_value(KEYBOARD_BUTTONS_KEY, super().get_buttons())
58
+
59
+ @classmethod
60
+ def _set_secret_value[V](cls, key: str, value: V, /) -> V:
61
+ setattr(cls, key, value)
62
+ return value
63
+
64
+ @classmethod
65
+ def _get_secret_value(cls, key: str, /) -> typing.Any:
66
+ return getattr(cls, key, NO_VALUE)
67
+
68
+ @abc.abstractmethod
69
+ def dict(self) -> DictStrAny:
70
+ pass
71
+
72
+ @abc.abstractmethod
73
+ def get_markup(self) -> AnyMarkup:
74
+ pass
75
+
76
+
77
+ __all__ = ("BaseKeyboard", "BaseStaticKeyboard")
@@ -0,0 +1,14 @@
1
+ from telegrinder.tools.keyboard.buttons.base import BaseButton, BaseStaticButton
2
+ from telegrinder.tools.keyboard.buttons.buttons import Button, InlineButton
3
+ from telegrinder.tools.keyboard.buttons.static_buttons import StaticButton, StaticInlineButton
4
+ from telegrinder.tools.keyboard.buttons.tools import RowButtons
5
+
6
+ __all__ = (
7
+ "BaseButton",
8
+ "BaseStaticButton",
9
+ "Button",
10
+ "InlineButton",
11
+ "RowButtons",
12
+ "StaticButton",
13
+ "StaticInlineButton",
14
+ )
@@ -0,0 +1,18 @@
1
+ import dataclasses
2
+ import typing
3
+
4
+ from telegrinder.tools.fullname import fullname
5
+
6
+
7
+ class BaseButton:
8
+ def get_data(self) -> dict[str, typing.Any]:
9
+ assert dataclasses.is_dataclass(self), f"{fullname(self)} is not a dataclass."
10
+ return {k: v for k, v in dataclasses.asdict(self).items() if v is not None}
11
+
12
+
13
+ @dataclasses.dataclass(kw_only=True)
14
+ class BaseStaticButton(BaseButton):
15
+ row: bool = dataclasses.field(default=False, repr=False)
16
+
17
+
18
+ __all__ = ("BaseButton", "BaseStaticButton")