chatgraph 0.5.0__tar.gz → 0.5.1__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 chatgraph might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: chatgraph
3
- Version: 0.5.0
3
+ Version: 0.5.1
4
4
  Summary: A user-friendly chatbot library
5
5
  Home-page: https://github.com/irissonnlima/chatgraph
6
6
  License: MIT
@@ -6,6 +6,7 @@ from .types.request_types import UserCall, UserState, ChatID
6
6
  from .types.end_types import RedirectResponse, EndChatResponse, TransferToHuman
7
7
  from .types.message_types import Message, Button
8
8
  from .types.route import Route
9
+ from .types.background_task import BackgroundTask
9
10
 
10
11
  __all__ = [
11
12
  'ChatbotApp',
@@ -22,5 +23,5 @@ __all__ = [
22
23
  'UserState',
23
24
  'Message',
24
25
  'Button',
25
-
26
+ 'BackgroundTask',
26
27
  ]
@@ -1,9 +1,7 @@
1
1
  import inspect
2
2
  from functools import wraps
3
- from logging import debug
4
- import json
3
+ from logging import debug, error
5
4
  import asyncio
6
- from logging import error
7
5
 
8
6
  from ..error.chatbot_error import ChatbotMessageError
9
7
  from ..messages.message_consumer import MessageConsumer
@@ -28,20 +26,16 @@ class ChatbotApp:
28
26
  """
29
27
  if not message_consumer:
30
28
  message_consumer = MessageConsumer.load_dotenv()
31
-
29
+
32
30
  self.__message_consumer = message_consumer
33
31
  self.__routes = {}
34
-
32
+
35
33
  def include_router(self, router: ChatbotRouter):
36
34
  """
37
35
  Inclui um roteador de chatbot com um prefixo nas rotas da aplicação.
38
36
 
39
37
  Args:
40
38
  router (ChatbotRouter): O roteador contendo as rotas a serem adicionadas.
41
- prefix (str): O prefixo a ser adicionado às rotas do roteador.
42
-
43
- Raises:
44
- ChatbotError: Se a rota 'start' não for encontrada no roteador.
45
39
  """
46
40
  self.__routes.update(router.routes)
47
41
 
@@ -58,7 +52,7 @@ class ChatbotApp:
58
52
  route_name = route_name.strip().lower()
59
53
 
60
54
  def decorator(func):
61
- params = dict()
55
+ params = {}
62
56
  signature = inspect.signature(func)
63
57
  output_param = signature.return_annotation
64
58
 
@@ -66,20 +60,20 @@ class ChatbotApp:
66
60
  param_type = (
67
61
  param.annotation
68
62
  if param.annotation != inspect.Parameter.empty
69
- else 'Any'
63
+ else "Any"
70
64
  )
71
65
  params[param_type] = name
72
- debug(f'Parameter: {name}, Type: {param_type}')
66
+ debug(f"Parameter: {name}, Type: {param_type}")
73
67
 
74
68
  self.__routes[route_name] = {
75
- 'function': func,
76
- 'params': params,
77
- 'return': output_param,
69
+ "function": func,
70
+ "params": params,
71
+ "return": output_param,
78
72
  }
79
73
 
80
74
  @wraps(func)
81
- def wrapper(*args, **kwargs):
82
- return func(*args, **kwargs)
75
+ async def wrapper(*args, **kwargs):
76
+ return await func(*args, **kwargs)
83
77
 
84
78
  return wrapper
85
79
 
@@ -89,106 +83,103 @@ class ChatbotApp:
89
83
  """
90
84
  Inicia o consumo de mensagens pelo chatbot, processando cada mensagem recebida.
91
85
  """
92
-
93
-
94
86
  self.__message_consumer.reprer()
95
87
  asyncio.run(self.__message_consumer.start_consume(self.process_message))
96
-
97
- def process_message(self, userCall: UserCall):
88
+
89
+ async def process_message(self, userCall: UserCall):
98
90
  """
99
91
  Processa uma mensagem recebida, identificando a rota correspondente e executando a função associada.
100
92
 
101
93
  Args:
102
- userCall (Message): A mensagem a ser processada.
94
+ userCall (UserCall): A mensagem a ser processada.
103
95
 
104
96
  Raises:
105
97
  ChatbotMessageError: Se nenhuma rota for encontrada para o menu atual do usuário.
106
- ChatbotError: Se o tipo de retorno da função associada à rota for inválido.
107
-
108
- Returns:
109
- str: A resposta gerada pela função da rota, que pode ser uma mensagem ou o resultado de uma redireção.
110
98
  """
111
99
  user_id = userCall.user_id
112
100
  route = userCall.route.lower()
113
- route_handler = route.split('.')[-1]
114
- menu = userCall.menu.lower()
115
- observation = userCall.observation
101
+ route_handler = route.split(".")[-1]
102
+
116
103
  handler = self.__routes.get(route_handler, None)
117
104
 
118
105
  if not handler:
119
- raise ChatbotMessageError(
120
- user_id, f'Rota não encontrada para {route}!'
121
- )
122
-
123
- func = handler['function']
124
- userCall_name = handler['params'].get(UserCall, None)
125
- route_state_name = handler['params'].get(Route, None)
106
+ raise ChatbotMessageError(user_id, f"Rota não encontrada para {route}!")
107
+
108
+ func = handler["function"]
109
+ userCall_name = handler["params"].get(UserCall, None)
110
+ route_state_name = handler["params"].get(Route, None)
126
111
 
127
- kwargs = dict()
112
+ kwargs = {}
128
113
  if userCall_name:
129
114
  kwargs[userCall_name] = userCall
130
115
  if route_state_name:
131
116
  kwargs[route_state_name] = Route(route, list(self.__routes.keys()))
132
117
 
133
- userCall_response = func(**kwargs)
134
-
118
+ if asyncio.iscoroutinefunction(func):
119
+ userCall_response = await func(**kwargs)
120
+ else:
121
+ loop = asyncio.get_running_loop()
122
+ userCall_response = await loop.run_in_executor(None, lambda: func(**kwargs))
123
+
135
124
  if isinstance(userCall_response, (list, tuple)):
136
125
  for response in userCall_response:
137
- self.__process_func_response(response, userCall, route=route)
126
+ await self.__process_func_response(response, userCall, route=route)
138
127
  else:
139
- self.__process_func_response(userCall_response, userCall, route=route)
128
+ await self.__process_func_response(userCall_response, userCall, route=route)
129
+
130
+ async def __process_func_response(
131
+ self, userCall_response, userCall: UserCall, route: str
132
+ ):
133
+ """
134
+ Processa a resposta de uma função associada a uma rota, enviando mensagens ou ajustando estados.
135
+
136
+ Args:
137
+ userCall_response: A resposta gerada pela função da rota.
138
+ userCall (UserCall): O objeto UserCall associado à mensagem processada.
139
+ route (str): O nome da rota atual.
140
+ """
141
+ loop = asyncio.get_running_loop()
140
142
 
141
- def __process_func_response(self, userCall_response, userCall: UserCall, route: str):
142
-
143
143
  if isinstance(userCall_response, (str, float, int)):
144
- userCall.send(Message(userCall_response))
144
+ # Envia o resultado como mensagem (executando a chamada síncrona no executor)
145
+ await loop.run_in_executor(None, userCall.send, Message(userCall_response))
145
146
  return
146
147
 
147
148
  elif isinstance(userCall_response, Route):
148
149
  userCall.route = userCall_response.current
149
150
  return
150
-
151
+
151
152
  elif isinstance(userCall_response, (Message, Button)):
152
- userCall.send(userCall_response)
153
-
153
+ # Envia o objeto Message ou Button
154
+ await loop.run_in_executor(None, userCall.send, userCall_response)
154
155
  return
155
156
 
156
157
  elif isinstance(userCall_response, EndChatResponse):
157
- userCall.end_chat(userCall_response.observations, userCall_response.tabulation_id)
158
+ await loop.run_in_executor(
159
+ None,
160
+ userCall.end_chat,
161
+ userCall_response.observations,
162
+ userCall_response.tabulation_id,
163
+ )
158
164
  return
159
-
165
+
160
166
  elif isinstance(userCall_response, TransferToHuman):
161
- userCall.transfer_to_human(userCall_response.observations, userCall_response.campaign_id)
167
+ await loop.run_in_executor(
168
+ None,
169
+ userCall.transfer_to_human,
170
+ userCall_response.observations,
171
+ userCall_response.campaign_id,
172
+ )
162
173
  return
163
174
 
164
175
  elif isinstance(userCall_response, RedirectResponse):
165
- route = route + '.' + userCall_response.route
176
+ route = route + "." + userCall_response.route
166
177
  userCall.route = route
167
- return self.process_message(userCall)
178
+ await self.process_message(userCall)
168
179
 
169
180
  elif not userCall_response:
170
181
  return
171
182
 
172
183
  else:
173
- error('Tipo de retorno inválido!')
184
+ error("Tipo de retorno inválido!")
174
185
  return None
175
-
176
-
177
- def __adjust_route(self, route: str, absolute_route: str) -> str:
178
- """
179
- Ajusta a rota fornecida para incluir o prefixo necessário, se não estiver presente.
180
-
181
- Args:
182
- route (str): A rota que precisa ser ajustada.
183
- absolute_route (str): A rota completa atual, usada como referência.
184
-
185
- Returns:
186
- str: A rota ajustada.
187
- """
188
- if not route:
189
- return absolute_route
190
-
191
- if 'start' not in route:
192
- route = absolute_route + route
193
-
194
- return route
@@ -26,6 +26,8 @@ class RouterServiceClient:
26
26
  request = chatbot_pb2.UserState(**user_state_data)
27
27
  try:
28
28
  response = self.user_state_stub.InsertUpdateUserState(request)
29
+ if not response.status:
30
+ print(f"Erro ao chamar SendMessage: {response.message}")
29
31
  return response
30
32
  except grpc.RpcError as e:
31
33
  print(f"Erro ao chamar InsertUpdateUserState: {e}")
@@ -35,6 +37,8 @@ class RouterServiceClient:
35
37
  request = chatbot_pb2.ChatID(**chat_id_data)
36
38
  try:
37
39
  response = self.user_state_stub.DeleteUserState(request)
40
+ if not response.status:
41
+ print(f"Erro ao chamar SendMessage: {response.message}")
38
42
  return response
39
43
  except grpc.RpcError as e:
40
44
  print(f"Erro ao chamar DeleteUserState: {e}")
@@ -50,12 +54,14 @@ class RouterServiceClient:
50
54
  return None
51
55
 
52
56
  def send_message(self, message_data):
53
- print(json.dumps(message_data))
57
+ # print(json.dumps(message_data))
54
58
 
55
59
  request = chatbot_pb2.Message(**message_data)
56
60
 
57
61
  try:
58
62
  response = self.send_message_stub.SendMessage(request)
63
+ if not response.status:
64
+ print(f"Erro ao chamar SendMessage: {response.message}")
59
65
  return response
60
66
  except grpc.RpcError as e:
61
67
  print(f"Erro ao chamar SendMessage: {e}")
@@ -65,6 +71,8 @@ class RouterServiceClient:
65
71
  request = chatbot_pb2.TransferToHumanRequest(**transfer_request_data)
66
72
  try:
67
73
  response = self.transfer_stub.TransferToHuman(request)
74
+ if not response.status:
75
+ print(f"Erro ao chamar SendMessage: {response.message}")
68
76
  return response
69
77
  except grpc.RpcError as e:
70
78
  print(f"Erro ao chamar TransferToHuman: {e}")
@@ -74,6 +82,8 @@ class RouterServiceClient:
74
82
  request = chatbot_pb2.TransferToMenuRequest(**transfer_request_data)
75
83
  try:
76
84
  response = self.transfer_stub.TransferToMenu(request)
85
+ if not response.status:
86
+ print(f"Erro ao chamar SendMessage: {response.message}")
77
87
  return response
78
88
  except grpc.RpcError as e:
79
89
  print(f"Erro ao chamar TransferToMenu: {e}")
@@ -83,6 +93,8 @@ class RouterServiceClient:
83
93
  request = chatbot_pb2.EndChatRequest(**end_chat_request_data)
84
94
  try:
85
95
  response = self.end_chat_stub.EndChat(request)
96
+ if not response.status:
97
+ print(f"Erro ao chamar SendMessage: {response.message}")
86
98
  return response
87
99
  except grpc.RpcError as e:
88
100
  print(f"Erro ao chamar EndChat: {e}")
@@ -92,7 +104,6 @@ class RouterServiceClient:
92
104
  request = chatbot_pb2.CampaignName(**campaign_name)
93
105
  try:
94
106
  response = self.transfer_stub.GetCampaignID(request)
95
- print(response)
96
107
  return response
97
108
  except grpc.RpcError as e:
98
109
  print(f"Erro ao chamar GetCampaignID: {e}")
@@ -0,0 +1,23 @@
1
+ import asyncio
2
+ from typing import Any, Callable
3
+ import threading
4
+
5
+ def BackgroundTask(func: Callable, *args: Any, **kwargs: Any) -> None:
6
+ """
7
+ Inicia uma função de forma assíncrona em segundo plano e printa sua saída.
8
+
9
+ :param func: Função assíncrona a ser executada.
10
+ :param args: Argumentos posicionais para a função.
11
+ :param kwargs: Argumentos nomeados para a função.
12
+ """
13
+ if asyncio.iscoroutinefunction(func):
14
+ def start_loop():
15
+ loop = asyncio.new_event_loop()
16
+ asyncio.set_event_loop(loop)
17
+ loop.run_until_complete(func(*args, **kwargs))
18
+
19
+ # Executa o loop de eventos em uma thread separada
20
+ thread = threading.Thread(target=start_loop)
21
+ thread.start()
22
+ else:
23
+ raise TypeError("A função fornecida deve ser uma coroutine (async def).")
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "chatgraph"
3
- version = "0.5.0"
3
+ version = "0.5.1"
4
4
  description = "A user-friendly chatbot library"
5
5
  authors = ["Irisson N. Lima <irisson.lima@verdecard.com.br>"]
6
6
  readme = "README.md"
File without changes
File without changes