telegrinder 0.1.dev158__tar.gz → 0.1.dev159__tar.gz

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 (134) hide show
  1. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/PKG-INFO +3 -2
  2. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/pyproject.toml +2 -2
  3. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/api/abc.py +4 -4
  4. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/api/api.py +25 -11
  5. telegrinder-0.1.dev159/telegrinder/bot/cute_types/base.py +138 -0
  6. telegrinder-0.1.dev159/telegrinder/bot/cute_types/callback_query.py +471 -0
  7. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/cute_types/inline_query.py +13 -13
  8. telegrinder-0.1.dev159/telegrinder/bot/cute_types/message.py +3005 -0
  9. telegrinder-0.1.dev159/telegrinder/bot/cute_types/update.py +30 -0
  10. telegrinder-0.1.dev159/telegrinder/bot/cute_types/utils.py +794 -0
  11. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/dispatch/view/abc.py +22 -15
  12. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/rules/adapter/event.py +12 -6
  13. telegrinder-0.1.dev159/telegrinder/client/__init__.py +4 -0
  14. telegrinder-0.1.dev159/telegrinder/client/abc.py +75 -0
  15. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/client/aiohttp.py +35 -22
  16. telegrinder-0.1.dev159/telegrinder/model.py +144 -0
  17. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/modules.py +2 -2
  18. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/msgspec_utils.py +28 -8
  19. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/tools/__init__.py +0 -27
  20. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/tools/buttons.py +19 -5
  21. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/types/enums.py +11 -9
  22. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/types/methods.py +311 -152
  23. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/types/objects.py +109 -67
  24. telegrinder-0.1.dev158/telegrinder/bot/cute_types/base.py +0 -47
  25. telegrinder-0.1.dev158/telegrinder/bot/cute_types/callback_query.py +0 -78
  26. telegrinder-0.1.dev158/telegrinder/bot/cute_types/message.py +0 -193
  27. telegrinder-0.1.dev158/telegrinder/bot/cute_types/update.py +0 -23
  28. telegrinder-0.1.dev158/telegrinder/client/__init__.py +0 -4
  29. telegrinder-0.1.dev158/telegrinder/client/abc.py +0 -52
  30. telegrinder-0.1.dev158/telegrinder/model.py +0 -96
  31. telegrinder-0.1.dev158/telegrinder/tools/inline_query.py +0 -684
  32. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/LICENSE +0 -0
  33. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/readme.md +0 -0
  34. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/__init__.py +0 -0
  35. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/api/__init__.py +0 -0
  36. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/api/error.py +0 -0
  37. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/api/response.py +0 -0
  38. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/__init__.py +0 -0
  39. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/bot.py +0 -0
  40. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/cute_types/__init__.py +0 -0
  41. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/dispatch/__init__.py +0 -0
  42. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/dispatch/abc.py +0 -0
  43. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/dispatch/composition.py +0 -0
  44. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/dispatch/context.py +0 -0
  45. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/dispatch/dispatch.py +0 -0
  46. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/dispatch/handler/__init__.py +0 -0
  47. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/dispatch/handler/abc.py +0 -0
  48. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/dispatch/handler/func.py +0 -0
  49. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/dispatch/handler/message_reply.py +0 -0
  50. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/dispatch/middleware/__init__.py +0 -0
  51. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/dispatch/middleware/abc.py +0 -0
  52. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/dispatch/process.py +0 -0
  53. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/dispatch/return_manager/__init__.py +0 -0
  54. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/dispatch/return_manager/abc.py +0 -0
  55. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/dispatch/return_manager/callback_query.py +0 -0
  56. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/dispatch/return_manager/inline_query.py +0 -0
  57. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/dispatch/return_manager/message.py +0 -0
  58. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/dispatch/view/__init__.py +0 -0
  59. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/dispatch/view/box.py +0 -0
  60. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/dispatch/view/callback_query.py +0 -0
  61. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/dispatch/view/inline_query.py +0 -0
  62. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/dispatch/view/message.py +0 -0
  63. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/dispatch/waiter_machine/__init__.py +0 -0
  64. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/dispatch/waiter_machine/machine.py +0 -0
  65. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/dispatch/waiter_machine/middleware.py +0 -0
  66. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/dispatch/waiter_machine/short_state.py +0 -0
  67. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/polling/__init__.py +0 -0
  68. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/polling/abc.py +0 -0
  69. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/polling/polling.py +0 -0
  70. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/rules/__init__.py +0 -0
  71. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/rules/abc.py +0 -0
  72. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/rules/adapter/__init__.py +0 -0
  73. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/rules/adapter/abc.py +0 -0
  74. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/rules/adapter/errors.py +0 -0
  75. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/rules/adapter/raw_update.py +0 -0
  76. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/rules/callback_data.py +0 -0
  77. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/rules/command.py +0 -0
  78. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/rules/enum_text.py +0 -0
  79. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/rules/func.py +0 -0
  80. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/rules/fuzzy.py +0 -0
  81. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/rules/inline.py +0 -0
  82. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/rules/integer.py +0 -0
  83. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/rules/is_from.py +0 -0
  84. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/rules/markup.py +0 -0
  85. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/rules/mention.py +0 -0
  86. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/rules/message_entities.py +0 -0
  87. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/rules/regex.py +0 -0
  88. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/rules/rule_enum.py +0 -0
  89. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/rules/start.py +0 -0
  90. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/rules/text.py +0 -0
  91. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/scenario/__init__.py +0 -0
  92. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/scenario/abc.py +0 -0
  93. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/scenario/checkbox.py +0 -0
  94. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/bot/scenario/choice.py +0 -0
  95. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/msgspec_json.py +0 -0
  96. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/node/__init__.py +0 -0
  97. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/node/attachment.py +0 -0
  98. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/node/base.py +0 -0
  99. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/node/composer.py +0 -0
  100. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/node/container.py +0 -0
  101. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/node/message.py +0 -0
  102. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/node/rule.py +0 -0
  103. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/node/source.py +0 -0
  104. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/node/text.py +0 -0
  105. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/node/tools/__init__.py +0 -0
  106. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/node/tools/generator.py +0 -0
  107. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/node/update.py +0 -0
  108. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/rules.py +0 -0
  109. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/tools/error_handler/__init__.py +0 -0
  110. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/tools/error_handler/abc.py +0 -0
  111. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/tools/error_handler/error_handler.py +0 -0
  112. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/tools/formatting/__init__.py +0 -0
  113. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/tools/formatting/html.py +0 -0
  114. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/tools/formatting/links.py +0 -0
  115. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/tools/formatting/spec_html_formats.py +0 -0
  116. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/tools/global_context/__init__.py +0 -0
  117. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/tools/global_context/abc.py +0 -0
  118. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/tools/global_context/global_context.py +0 -0
  119. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/tools/global_context/telegrinder_ctx.py +0 -0
  120. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/tools/i18n/__init__.py +0 -0
  121. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/tools/i18n/base.py +0 -0
  122. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/tools/i18n/middleware/__init__.py +0 -0
  123. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/tools/i18n/middleware/base.py +0 -0
  124. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/tools/i18n/simple.py +0 -0
  125. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/tools/kb_set/__init__.py +0 -0
  126. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/tools/kb_set/base.py +0 -0
  127. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/tools/kb_set/yaml.py +0 -0
  128. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/tools/keyboard.py +0 -0
  129. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/tools/loop_wrapper/__init__.py +0 -0
  130. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/tools/loop_wrapper/abc.py +0 -0
  131. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/tools/loop_wrapper/loop_wrapper.py +0 -0
  132. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/tools/magic.py +0 -0
  133. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/tools/parse_mode.py +0 -0
  134. {telegrinder-0.1.dev158 → telegrinder-0.1.dev159}/telegrinder/types/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: telegrinder
3
- Version: 0.1.dev158
3
+ Version: 0.1.dev159
4
4
  Summary: Framework for effective and reliable async telegram bot building.
5
5
  Home-page: https://github.com/timoniq/telegrinder
6
6
  License: MIT
@@ -14,12 +14,13 @@ Classifier: Intended Audience :: Developers
14
14
  Classifier: License :: OSI Approved :: MIT License
15
15
  Classifier: Programming Language :: Python :: 3
16
16
  Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.11
17
18
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
19
  Classifier: Topic :: Software Development :: Quality Assurance
19
20
  Classifier: Typing :: Typed
20
21
  Requires-Dist: PyYAML (>=6.0,<7.0)
21
22
  Requires-Dist: aiohttp (>=3.8.1,<4.0.0)
22
- Requires-Dist: certifi (>=2022.6.15,<2023.0.0)
23
+ Requires-Dist: certifi (>=2023.7.22,<2024.0.0)
23
24
  Requires-Dist: choicelib (>=0.1.5,<0.2.0)
24
25
  Requires-Dist: colorama (>=0.4.0,<0.5.0)
25
26
  Requires-Dist: envparse (>=0.2.0,<0.3.0)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "telegrinder"
3
- version = "0.1.dev158"
3
+ version = "0.1.dev159"
4
4
  description = "Framework for effective and reliable async telegram bot building."
5
5
  authors = ["timoniq <tesseradecades@mail.ru>"]
6
6
  maintainers = ["luwqz1"]
@@ -36,7 +36,7 @@ vbml = "^1.1.post1"
36
36
  choicelib = "^0.1.5"
37
37
  envparse = "^0.2.0"
38
38
  PyYAML = "^6.0"
39
- certifi = "^2022.6.15"
39
+ certifi = "^2023.7.22"
40
40
  msgspec = "^0.18.4"
41
41
  requests = "^2.28.1"
42
42
  typing-extensions = "^4.8.0"
@@ -15,7 +15,7 @@ from .error import InvalidTokenError
15
15
  class Token(str):
16
16
  def __new__(cls, token: str) -> typing.Self:
17
17
  if token.count(":") != 1 or not token.split(":")[0].isdigit():
18
- raise InvalidTokenError("Invalid token, it should look like this '123:token'.")
18
+ raise InvalidTokenError("Invalid token, it should look like this '123:ABC'.")
19
19
  return super().__new__(cls, token)
20
20
 
21
21
  @classmethod
@@ -42,15 +42,15 @@ class ABCAPI(ABC):
42
42
  async def request(
43
43
  self,
44
44
  method: str,
45
- data: dict | None = None,
46
- ) -> Result[list | dict | bool, APIError]:
45
+ data: dict[str, typing.Any] | None = None,
46
+ ) -> Result[list[typing.Any] | dict[str, typing.Any] | bool, APIError]:
47
47
  pass
48
48
 
49
49
  @abstractmethod
50
50
  async def request_raw(
51
51
  self,
52
52
  method: str,
53
- data: dict | None = None,
53
+ data: dict[str, typing.Any] | None = None,
54
54
  ) -> Result[msgspec.Raw, APIError]:
55
55
  pass
56
56
 
@@ -5,26 +5,38 @@ from fntypes.result import Error, Ok, Result
5
5
 
6
6
  from telegrinder.api.response import APIResponse
7
7
  from telegrinder.client import ABCClient, AiohttpClient
8
- from telegrinder.model import convert, decoder
8
+ from telegrinder.model import DataConverter, decoder
9
9
  from telegrinder.types.methods import APIMethods
10
10
 
11
11
  from .abc import ABCAPI, APIError, Token
12
12
 
13
13
 
14
- def compose_data(client: ABCClient, data: dict[str, typing.Any]) -> typing.Any:
15
- data = {k: convert(v) for k, v in data.items()}
16
- if any(isinstance(v, tuple) for v in data.values()):
17
- data = client.get_form(data)
18
- return data
14
+ def compose_data(
15
+ client: ABCClient,
16
+ data: dict[str, typing.Any],
17
+ files: dict[str, tuple[str, bytes]],
18
+ ) -> typing.Any:
19
+ converter = DataConverter(files=files)
20
+ return client.get_form(
21
+ data={k: converter.convert(v) for k, v in data.items()},
22
+ files=converter.files,
23
+ )
19
24
 
20
25
 
21
26
  class API(ABCAPI, APIMethods):
22
27
  API_URL: typing.ClassVar[str] = "https://api.telegram.org/"
23
28
 
24
- def __init__(self, token: Token, *, http: ABCClient | None = None):
29
+ def __init__(self, token: Token, *, http: ABCClient | None = None) -> None:
25
30
  self.token = token
26
31
  self.http = http or AiohttpClient()
27
32
  super().__init__(self)
33
+
34
+ def __repr__(self) -> str:
35
+ return "<{}: id={}, http={!r}>".format(
36
+ self.__class__.__name__,
37
+ self.id,
38
+ self.http,
39
+ )
28
40
 
29
41
  @property
30
42
  def id(self) -> int:
@@ -37,11 +49,12 @@ class API(ABCAPI, APIMethods):
37
49
  async def request(
38
50
  self,
39
51
  method: str,
40
- data: dict | None = None,
41
- ) -> Result[dict | list | bool, APIError]:
52
+ data: dict[str, typing.Any] | None = None,
53
+ files: dict[str, tuple[str, bytes]] | None = None,
54
+ ) -> Result[dict[str, typing.Any] | list[typing.Any] | bool, APIError]:
42
55
  response = await self.http.request_json(
43
56
  url=self.request_url + method,
44
- data=compose_data(self.http, data or {})
57
+ data=compose_data(self.http, data or {}, files or {})
45
58
  )
46
59
  if response.get("ok"):
47
60
  assert "result" in response
@@ -55,10 +68,11 @@ class API(ABCAPI, APIMethods):
55
68
  self,
56
69
  method: str,
57
70
  data: dict[str, typing.Any] | None = None,
71
+ files: dict[str, tuple[str, bytes]] | None = None,
58
72
  ) -> Result[msgspec.Raw, APIError]:
59
73
  response_bytes = await self.http.request_bytes(
60
74
  url=self.request_url + method,
61
- data=compose_data(self.http, data or {}),
75
+ data=compose_data(self.http, data or {}, files or {}),
62
76
  )
63
77
  return decoder.decode(response_bytes, type=APIResponse).to_result()
64
78
 
@@ -0,0 +1,138 @@
1
+ import dataclasses
2
+ import inspect
3
+ import typing
4
+ from functools import wraps
5
+
6
+ from fntypes.result import Result
7
+
8
+ from telegrinder.api import ABCAPI, API
9
+ from telegrinder.model import Model, get_params
10
+
11
+ F = typing.TypeVar("F", bound=typing.Callable[..., typing.Any])
12
+ CuteT = typing.TypeVar("CuteT", bound="BaseCute")
13
+ UpdateT = typing.TypeVar("UpdateT", bound=Model)
14
+
15
+ Executor: typing.TypeAlias = typing.Callable[
16
+ [CuteT, str, dict[str, typing.Any]],
17
+ typing.Awaitable[Result[typing.Any, typing.Any]],
18
+ ]
19
+
20
+ if typing.TYPE_CHECKING:
21
+
22
+ class BaseCute(Model, typing.Generic[UpdateT]):
23
+ api: ABCAPI
24
+
25
+ @classmethod
26
+ def from_update(cls, update: UpdateT, bound_api: ABCAPI) -> typing.Self:
27
+ ...
28
+
29
+ @property
30
+ def ctx_api(self) -> API:
31
+ ...
32
+
33
+ else:
34
+
35
+ class BaseCute(typing.Generic[UpdateT]):
36
+ api: ABCAPI
37
+
38
+ @classmethod
39
+ def from_update(cls, update, bound_api):
40
+ return cls(**update.to_dict(), api=bound_api)
41
+
42
+ @property
43
+ def ctx_api(self):
44
+ assert isinstance(self.api, API)
45
+ return self.api
46
+
47
+ def to_dict(self, *, exclude_fields=None):
48
+ exclude_fields = exclude_fields or set()
49
+ return super().to_dict(exclude_fields={"api"} | exclude_fields)
50
+
51
+
52
+ def compose_method_params(
53
+ params: dict[str, typing.Any],
54
+ update: CuteT,
55
+ *,
56
+ default_params: set[str | tuple[str, str]] | None = None,
57
+ validators: dict[str, typing.Callable[[CuteT], bool]] | None = None,
58
+ ) -> dict[str, typing.Any]:
59
+ """Compose method `params` from `update` by `default_params` and `validators`.
60
+
61
+ :param params: Method params.
62
+ :param update: Update object.
63
+ :param default_params: Default params. \
64
+ type (`str`) - Attribute name to be taken from `update` if param undefined. \
65
+ type (`tuple[str, str]`) - tuple[0] Parameter name to be set in `params`, \
66
+ tuple[1] attribute name to be taken from `update`.
67
+ :param validators: Validators mapping (`str, Callable`), key - `Parameter name` \
68
+ for which the validator will be applied, value - `Validator`, if returned `True` \
69
+ parameter will be set, otherwise will not be set.
70
+ :return: Composed params.
71
+ """
72
+
73
+ default_params = default_params or set()
74
+ validators = validators or {}
75
+
76
+ for param in default_params:
77
+ param_name = param if isinstance(param, str) else param[0]
78
+ if param_name not in params:
79
+ if param_name in validators and not validators[param_name](update):
80
+ continue
81
+ params[param_name] = getattr(update, param if isinstance(param, str) else param[1])
82
+
83
+ return params
84
+
85
+
86
+ # NOTE: implement parser on ast for methods decorated this decorator
87
+ # to support updates to the schema Bot API.
88
+ def shortcut(
89
+ method_name: str,
90
+ *,
91
+ executor: Executor[CuteT] | None = None,
92
+ custom_params: set[str] | None = None,
93
+ ):
94
+ def wrapper(func: F) -> F:
95
+ @wraps(func)
96
+ async def inner(self: CuteT, *args: typing.Any, **kwargs: typing.Any) -> typing.Any:
97
+ if executor is None:
98
+ return await func(self, *args, **kwargs)
99
+ signature_params = {
100
+ k: p
101
+ for k, p in inspect.signature(func).parameters.items()
102
+ if k != "self"
103
+ }
104
+ params: dict[str, typing.Any] = {}
105
+ index = 0
106
+
107
+ for k, p in signature_params.items():
108
+ if p.kind in (p.POSITIONAL_OR_KEYWORD, p.POSITIONAL_ONLY) and len(args) > index:
109
+ params[k] = args[index]
110
+ index += 1
111
+ continue
112
+ if p.kind in (p.VAR_KEYWORD, p.VAR_POSITIONAL):
113
+ params[k] = kwargs.copy() if p.kind is p.VAR_KEYWORD else args[index:]
114
+ continue
115
+ params[k] = kwargs.pop(k, p.default) if p.default is not p.empty else kwargs.pop(k)
116
+
117
+ return await executor(self, method_name, get_params(params))
118
+
119
+ func.__repr__ = lambda _: f"<Shortcut {method_name!r}@{func!r}>"
120
+ inner.__shortcut__ = Shortcut( # type: ignore
121
+ method_name=method_name,
122
+ executor=executor,
123
+ custom_params=custom_params or set(),
124
+ )
125
+ return inner # type: ignore
126
+
127
+ return wrapper
128
+
129
+
130
+ @dataclasses.dataclass
131
+ class Shortcut:
132
+ method_name: str
133
+ _: dataclasses.KW_ONLY
134
+ executor: Executor | None = dataclasses.field(default=None)
135
+ custom_params: set[str] = dataclasses.field(default_factory=lambda: set())
136
+
137
+
138
+ __all__ = ("BaseCute", "Shortcut", "compose_method_params", "shortcut")