chatgraph 0.1.0__py3-none-any.whl → 0.1.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 chatgraph might be problematic. Click here for more details.

@@ -2,28 +2,66 @@ import os
2
2
 
3
3
 
4
4
  class Credential:
5
- def __init__(
6
- self, username: str | None = None, password: str | None = None
7
- ):
5
+ """
6
+ Classe para gerenciar credenciais de usuário e senha, com suporte a variáveis de ambiente.
7
+ """
8
+
9
+ def __init__(self, username: str | None = None, password: str | None = None):
10
+ """
11
+ Inicializa a classe Credential com um nome de usuário e senha.
12
+
13
+ Args:
14
+ username (str | None): O nome de usuário. Pode ser None se for carregado de uma variável de ambiente.
15
+ password (str | None): A senha do usuário. Pode ser None se for carregada de uma variável de ambiente.
16
+ """
8
17
  self.__username = username
9
18
  self.__password = password
10
19
 
11
20
  @property
12
- def password(self):
21
+ def password(self) -> str:
22
+ """
23
+ Retorna a senha do usuário.
24
+
25
+ Raises:
26
+ ValueError: Se a senha não estiver definida.
27
+
28
+ Returns:
29
+ str: A senha do usuário.
30
+ """
13
31
  if not self.__password:
14
32
  raise ValueError('Senha vazia!')
15
33
  return self.__password
16
34
 
17
35
  @property
18
- def username(self):
36
+ def username(self) -> str:
37
+ """
38
+ Retorna o nome de usuário.
39
+
40
+ Raises:
41
+ ValueError: Se o nome de usuário não estiver definido.
42
+
43
+ Returns:
44
+ str: O nome de usuário.
45
+ """
19
46
  if not self.__username:
20
47
  raise ValueError('Usuário vazio!')
21
48
  return self.__username
22
49
 
23
50
  @classmethod
24
- def dot_env_credentials(
25
- cls, user_env: str = 'CHATBOT_USER', pass_env: str = 'CHATBOT_PASS'
26
- ) -> 'Credential':
51
+ def load_dotenv(cls, user_env: str = 'CHATBOT_USER', pass_env: str = 'CHATBOT_PASS') -> 'Credential':
52
+ """
53
+ Carrega as credenciais de variáveis de ambiente e retorna uma instância da classe Credential.
54
+
55
+ Args:
56
+ user_env (str): Nome da variável de ambiente que armazena o nome de usuário. Padrão é 'CHATBOT_USER'.
57
+ pass_env (str): Nome da variável de ambiente que armazena a senha. Padrão é 'CHATBOT_PASS'.
58
+
59
+ Raises:
60
+ ValueError: Se o nome de usuário ou a senha não estiverem definidas nas variáveis de ambiente.
61
+
62
+ Returns:
63
+ Credential: Uma instância da classe Credential com as credenciais carregadas.
64
+ """
27
65
  username = os.getenv(user_env)
28
66
  password = os.getenv(pass_env)
29
67
 
@@ -6,22 +6,40 @@ from logging import debug
6
6
  from ..error.chatbot_error import ChatbotError, ChatbotMessageError
7
7
  from ..messages.base_message_consumer import MessageConsumer
8
8
  from ..types.message_types import Message
9
+ from ..types.output_state import ChatbotResponse, RedirectResponse
9
10
  from ..types.route import Route
10
11
  from ..types.user_state import UserState
11
12
  from .chatbot_router import ChatbotRouter
12
- from ..types.output_state import ChatbotResponse, RedirectResponse
13
+
13
14
 
14
15
  class ChatbotApp(ABC):
15
- def __init__(
16
- self,
17
- user_state: UserState,
18
- message_consumer: MessageConsumer,
19
- ):
16
+ """
17
+ Classe principal para a aplicação do chatbot, gerencia as rotas e a lógica de processamento de mensagens.
18
+ """
19
+
20
+ def __init__(self, user_state: UserState, message_consumer: MessageConsumer):
21
+ """
22
+ Inicializa a classe ChatbotApp com um estado de usuário e um consumidor de mensagens.
23
+
24
+ Args:
25
+ user_state (UserState): O estado do usuário, que contém informações persistentes sobre as interações do usuário.
26
+ message_consumer (MessageConsumer): O consumidor de mensagens que lida com a entrada de mensagens no sistema.
27
+ """
20
28
  self.__message_consumer = message_consumer
21
29
  self.__user_state = user_state
22
30
  self.__routes = {}
23
-
31
+
24
32
  def include_router(self, router: ChatbotRouter, prefix: str):
33
+ """
34
+ Inclui um roteador de chatbot com um prefixo nas rotas da aplicação.
35
+
36
+ Args:
37
+ router (ChatbotRouter): O roteador contendo as rotas a serem adicionadas.
38
+ prefix (str): O prefixo a ser adicionado às rotas do roteador.
39
+
40
+ Raises:
41
+ ChatbotError: Se a rota 'START' não for encontrada no roteador.
42
+ """
25
43
  if 'START' not in router.routes.keys():
26
44
  raise ChatbotError('Erro ao incluir rota, START não encontrado!')
27
45
 
@@ -36,9 +54,20 @@ class ChatbotApp(ABC):
36
54
  self.__routes.update(prefixed_routes)
37
55
 
38
56
  def route(self, route_name: str):
39
- if not 'START' in route_name:
57
+ """
58
+ Decorador para adicionar uma função como uma rota na aplicação do chatbot.
59
+
60
+ Args:
61
+ route_name (str): O nome da rota para a qual a função deve ser associada.
62
+
63
+ Returns:
64
+ function: O decorador que adiciona a função à rota especificada.
65
+ """
66
+ route_name = route_name.strip().upper()
67
+
68
+ if 'START' not in route_name:
40
69
  route_name = f'START{route_name}'
41
-
70
+
42
71
  def decorator(func):
43
72
  params = dict()
44
73
  signature = inspect.signature(func)
@@ -53,8 +82,7 @@ class ChatbotApp(ABC):
53
82
  params[param_type] = name
54
83
  debug(f'Parameter: {name}, Type: {param_type}')
55
84
 
56
-
57
- self.__routes[route_name.strip().upper()] = {
85
+ self.__routes[route_name] = {
58
86
  'function': func,
59
87
  'params': params,
60
88
  'return': output_param,
@@ -69,9 +97,25 @@ class ChatbotApp(ABC):
69
97
  return decorator
70
98
 
71
99
  def start(self):
100
+ """
101
+ Inicia o consumo de mensagens pelo chatbot, processando cada mensagem recebida.
102
+ """
72
103
  self.__message_consumer.start_consume(self.process_message)
73
104
 
74
105
  def process_message(self, message: Message):
106
+ """
107
+ Processa uma mensagem recebida, identificando a rota correspondente e executando a função associada.
108
+
109
+ Args:
110
+ message (Message): A mensagem a ser processada.
111
+
112
+ Raises:
113
+ ChatbotMessageError: Se nenhuma rota for encontrada para o menu atual do usuário.
114
+ ChatbotError: Se o tipo de retorno da função associada à rota for inválido.
115
+
116
+ Returns:
117
+ str: A resposta gerada pela função da rota, que pode ser uma mensagem ou o resultado de uma redireção.
118
+ """
75
119
  customer_id = message.customer_id
76
120
 
77
121
  menu = self.__user_state.get_menu(customer_id)
@@ -93,7 +137,7 @@ class ChatbotApp(ABC):
93
137
  kwargs[route_state_name] = Route(menu, list(self.__routes.keys()))
94
138
 
95
139
  message_response = func(**kwargs)
96
-
140
+
97
141
  if type(message_response) in (str, float, int):
98
142
  return message_response
99
143
  elif type(message_response) == ChatbotResponse:
@@ -106,12 +150,22 @@ class ChatbotApp(ABC):
106
150
  return self.process_message(message)
107
151
  else:
108
152
  raise ChatbotError('Tipo de retorno inválido!')
109
-
110
- def __adjust_route(self, route: str, absolute_route:str) -> str:
153
+
154
+ def __adjust_route(self, route: str, absolute_route: str) -> str:
155
+ """
156
+ Ajusta a rota fornecida para incluir o prefixo necessário, se não estiver presente.
157
+
158
+ Args:
159
+ route (str): A rota que precisa ser ajustada.
160
+ absolute_route (str): A rota completa atual, usada como referência.
161
+
162
+ Returns:
163
+ str: A rota ajustada.
164
+ """
111
165
  if not route:
112
166
  return absolute_route
113
-
114
- if not 'START' in route:
115
- route = absolute_route+route
116
-
167
+
168
+ if 'START' not in route:
169
+ route = absolute_route + route
170
+
117
171
  return route
@@ -3,22 +3,40 @@ from functools import wraps
3
3
  from logging import debug
4
4
 
5
5
  from ..error.chatbot_error import ChatbotError
6
- from ..types.message_types import Message
7
6
 
8
7
 
9
8
  class ChatbotRouter:
9
+ """
10
+ Classe responsável por gerenciar e registrar as rotas do chatbot, associando-as a funções específicas.
11
+
12
+ Atributos:
13
+ routes (dict): Um dicionário que armazena as rotas do chatbot e suas funções associadas.
14
+ """
15
+
10
16
  def __init__(self):
17
+ """
18
+ Inicializa a classe ChatbotRouter com um dicionário vazio de rotas.
19
+ """
11
20
  self.routes = {}
12
21
 
13
22
  def route(self, route_name: str):
14
- if not 'START' in route_name:
23
+ """
24
+ Decorador para adicionar uma função como uma rota no roteador do chatbot.
25
+
26
+ Args:
27
+ route_name (str): O nome da rota para a qual a função deve ser associada.
28
+
29
+ Returns:
30
+ function: O decorador que adiciona a função à rota especificada.
31
+ """
32
+ if 'START' not in route_name:
15
33
  route_name = f'START{route_name}'
16
-
34
+
17
35
  def decorator(func):
18
36
  params = dict()
19
37
  signature = inspect.signature(func)
20
38
  output_param = signature.return_annotation
21
-
39
+
22
40
  for name, param in signature.parameters.items():
23
41
  param_type = (
24
42
  param.annotation
@@ -28,7 +46,6 @@ class ChatbotRouter:
28
46
  params[param_type] = name
29
47
  debug(f'Parameter: {name}, Type: {param_type}')
30
48
 
31
-
32
49
  self.routes[route_name.strip().upper()] = {
33
50
  'function': func,
34
51
  'params': params,
@@ -44,6 +61,16 @@ class ChatbotRouter:
44
61
  return decorator
45
62
 
46
63
  def include_router(self, router: 'ChatbotRouter', prefix: str):
64
+ """
65
+ Inclui outro roteador com um prefixo nas rotas do roteador atual.
66
+
67
+ Args:
68
+ router (ChatbotRouter): O roteador contendo as rotas a serem adicionadas.
69
+ prefix (str): O prefixo a ser adicionado às rotas do roteador.
70
+
71
+ Raises:
72
+ ChatbotError: Se a rota 'START' não for encontrada no roteador fornecido.
73
+ """
47
74
  if 'START' not in router.routes.keys():
48
75
  raise ChatbotError('Erro ao incluir rota, START não encontrado!')
49
76
 
@@ -1,17 +1,57 @@
1
1
  class ChatbotError(Exception):
2
+ """
3
+ Exceção personalizada para erros gerais relacionados ao chatbot.
4
+
5
+ Atributos:
6
+ message (str): A mensagem de erro descrevendo o problema.
7
+ """
8
+
2
9
  def __init__(self, message: str):
10
+ """
11
+ Inicializa a exceção ChatbotError com uma mensagem de erro.
12
+
13
+ Args:
14
+ message (str): A mensagem de erro descrevendo o problema.
15
+ """
3
16
  self.message = message
4
17
  super().__init__(self.message)
5
18
 
6
19
  def __str__(self):
20
+ """
21
+ Retorna a representação em string da exceção ChatbotError.
22
+
23
+ Returns:
24
+ str: Uma string formatada que inclui o nome da exceção e a mensagem de erro.
25
+ """
7
26
  return f'ChatbotError: {self.message}'
8
27
 
9
28
 
10
29
  class ChatbotMessageError(Exception):
30
+ """
31
+ Exceção personalizada para erros relacionados a mensagens de clientes no chatbot.
32
+
33
+ Atributos:
34
+ customer_id (str): O ID do cliente relacionado ao erro.
35
+ message (str): A mensagem de erro descrevendo o problema, incluindo o ID do cliente.
36
+ """
37
+
11
38
  def __init__(self, customer_id: str, message: str):
39
+ """
40
+ Inicializa a exceção ChatbotMessageError com um ID de cliente e uma mensagem de erro.
41
+
42
+ Args:
43
+ customer_id (str): O ID do cliente relacionado ao erro.
44
+ message (str): A mensagem de erro descrevendo o problema.
45
+ """
12
46
  self.customer_id = customer_id
13
47
  self.message = f'{message} ID recebido: {customer_id}'
14
48
  super().__init__(self.message)
15
49
 
16
50
  def __str__(self):
17
- return f'InvalidCustomerIDError: {self.message}'
51
+ """
52
+ Retorna a representação em string da exceção ChatbotMessageError.
53
+
54
+ Returns:
55
+ str: Uma string formatada que inclui o nome da exceção e a mensagem de erro.
56
+ """
57
+ return f'ChatbotMessageError: {self.message}'
@@ -1,7 +1,26 @@
1
1
  class RouteError(Exception):
2
+ """
3
+ Exceção personalizada para erros relacionados a rotas no sistema do chatbot.
4
+
5
+ Atributos:
6
+ message (str): A mensagem de erro descrevendo o problema.
7
+ """
8
+
2
9
  def __init__(self, message: str):
10
+ """
11
+ Inicializa a exceção RouteError com uma mensagem de erro.
12
+
13
+ Args:
14
+ message (str): A mensagem de erro descrevendo o problema.
15
+ """
3
16
  self.message = message
4
17
  super().__init__(self.message)
5
18
 
6
19
  def __str__(self):
20
+ """
21
+ Retorna a representação em string da exceção RouteError.
22
+
23
+ Returns:
24
+ str: Uma string formatada que inclui o nome da exceção e a mensagem de erro.
25
+ """
7
26
  return f'RouteError: {self.message}'
@@ -3,6 +3,31 @@ from typing import Any, Callable
3
3
 
4
4
 
5
5
  class MessageConsumer(ABC):
6
+ """
7
+ Classe base abstrata para consumidores de mensagens no sistema do chatbot.
8
+
9
+ Esta classe define a interface que todos os consumidores de mensagens devem implementar para serem usados no sistema do chatbot.
10
+ """
11
+
6
12
  @abstractmethod
7
13
  def start_consume(self, process_message: Callable) -> Any:
14
+ """
15
+ Inicia o consumo de mensagens e processa cada mensagem usando a função fornecida.
16
+
17
+ Args:
18
+ process_message (Callable): Função de callback que processa cada mensagem recebida.
19
+
20
+ Returns:
21
+ Any: O resultado do processo de consumo de mensagens, dependendo da implementação concreta.
22
+ """
23
+ pass
24
+
25
+ @abstractmethod
26
+ def load_dotenv(self) -> 'MessageConsumer':
27
+ """
28
+ Carrega variáveis de ambiente para configurar o consumidor de mensagens.
29
+
30
+ Returns:
31
+ MessageConsumer: A instância do consumidor de mensagens configurado.
32
+ """
8
33
  pass
@@ -1,32 +1,107 @@
1
1
  import json
2
2
  from logging import debug, info
3
-
3
+ import os
4
4
  import pika
5
-
5
+ from typing import Callable
6
6
  from ..auth.credentials import Credential
7
7
  from ..types.message_types import Message
8
8
  from .base_message_consumer import MessageConsumer
9
9
 
10
10
 
11
11
  class RabbitMessageConsumer(MessageConsumer):
12
+ """
13
+ Implementação de MessageConsumer para consumir mensagens de uma fila RabbitMQ.
14
+
15
+ Atributos:
16
+ __virtual_host (str): O host virtual usado para a conexão RabbitMQ.
17
+ __prefetch_count (int): O número de mensagens pré-carregadas que o consumidor pode processar.
18
+ __queue_consume (str): O nome da fila de consumo.
19
+ __amqp_url (str): A URL de conexão AMQP do RabbitMQ.
20
+ __credentials (pika.PlainCredentials): Credenciais do RabbitMQ para autenticação.
21
+ """
22
+
12
23
  def __init__(
13
24
  self,
25
+ credential: Credential,
14
26
  amqp_url: str,
15
27
  queue_consume: str,
16
- credentials: Credential,
17
28
  prefetch_count: int = 1,
18
29
  virtual_host: str = '/',
19
30
  ) -> None:
31
+ """
32
+ Inicializa o consumidor de mensagens RabbitMQ com as configurações fornecidas.
33
+
34
+ Args:
35
+ credential (Credential): Credenciais de autenticação para o RabbitMQ.
36
+ amqp_url (str): A URL de conexão AMQP do RabbitMQ.
37
+ queue_consume (str): O nome da fila de consumo.
38
+ prefetch_count (int, opcional): O número de mensagens pré-carregadas. Padrão é 1.
39
+ virtual_host (str, opcional): O host virtual do RabbitMQ. Padrão é '/'.
40
+ """
20
41
  self.__virtual_host = virtual_host
21
42
  self.__prefetch_count = prefetch_count
22
43
  self.__queue_consume = queue_consume
23
44
  self.__amqp_url = amqp_url
24
45
  self.__credentials = pika.PlainCredentials(
25
- credentials.username, credentials.password
46
+ credential.username, credential.password
26
47
  )
27
48
 
28
- def start_consume(self, process_message: callable):
29
- try: # Verificar se recursão não vai estourar
49
+ @classmethod
50
+ def load_dotenv(
51
+ cls,
52
+ user_env: str = 'RABBIT_USER',
53
+ pass_env: str = 'RABBIT_PASS',
54
+ uri_env: str = 'RABBIT_URI',
55
+ queue_env: str = 'RABBIT_QUEUE',
56
+ prefetch_env: str = 'RABBIT_PREFETCH',
57
+ vhost_env: str = 'RABBIT_VHOST',
58
+ ) -> 'RabbitMessageConsumer':
59
+ """
60
+ Carrega as configurações do RabbitMQ a partir de variáveis de ambiente e retorna uma instância de RabbitMessageConsumer.
61
+
62
+ Args:
63
+ user_env (str): Nome da variável de ambiente para o usuário do RabbitMQ. Padrão é 'RABBIT_USER'.
64
+ pass_env (str): Nome da variável de ambiente para a senha do RabbitMQ. Padrão é 'RABBIT_PASS'.
65
+ uri_env (str): Nome da variável de ambiente para a URL do RabbitMQ. Padrão é 'RABBIT_URI'.
66
+ queue_env (str): Nome da variável de ambiente para a fila de consumo do RabbitMQ. Padrão é 'RABBIT_QUEUE'.
67
+ prefetch_env (str): Nome da variável de ambiente para o prefetch count. Padrão é 'RABBIT_PREFETCH'.
68
+ vhost_env (str): Nome da variável de ambiente para o host virtual do RabbitMQ. Padrão é 'RABBIT_VHOST'.
69
+
70
+ Raises:
71
+ ValueError: Se qualquer uma das variáveis de ambiente necessárias não estiver definida.
72
+
73
+ Returns:
74
+ RabbitMessageConsumer: Uma instância configurada do RabbitMessageConsumer.
75
+ """
76
+ username = os.getenv(user_env)
77
+ password = os.getenv(pass_env)
78
+ url = os.getenv(uri_env)
79
+ queue = os.getenv(queue_env)
80
+ prefetch = os.getenv(prefetch_env, 1)
81
+ vhost = os.getenv(vhost_env, '/')
82
+
83
+ if not username or not password or not url or not queue:
84
+ raise ValueError('Corrija as variáveis de ambiente!')
85
+
86
+ return cls(
87
+ credential=Credential(username=username, password=password),
88
+ amqp_url=url,
89
+ queue_consume=queue,
90
+ prefetch_count=int(prefetch),
91
+ virtual_host=vhost,
92
+ )
93
+
94
+ def start_consume(self, process_message: Callable) -> None:
95
+ """
96
+ Inicia o consumo de mensagens da fila RabbitMQ e processa cada mensagem usando a função fornecida.
97
+
98
+ Args:
99
+ process_message (Callable): Função de callback que processa cada mensagem recebida.
100
+
101
+ Raises:
102
+ pika.exceptions.StreamLostError: Se a conexão com o RabbitMQ for perdida, tentará reconectar automaticamente.
103
+ """
104
+ try:
30
105
  connection = pika.BlockingConnection(
31
106
  pika.ConnectionParameters(
32
107
  host=self.__amqp_url,
@@ -50,7 +125,17 @@ class RabbitMessageConsumer(MessageConsumer):
50
125
  debug(e)
51
126
  self.start_consume(process_message)
52
127
 
53
- def on_request(self, ch, method, props, body, process_message):
128
+ def on_request(self, ch, method, props, body, process_message) -> None:
129
+ """
130
+ Processa uma mensagem recebida e publica a resposta de volta na fila especificada.
131
+
132
+ Args:
133
+ ch: Canal do RabbitMQ.
134
+ method: Método de entrega do RabbitMQ.
135
+ props: Propriedades da mensagem do RabbitMQ.
136
+ body: Corpo da mensagem recebida.
137
+ process_message (Callable): Função que processa a mensagem e retorna uma resposta.
138
+ """
54
139
  message = body.decode()
55
140
  message_json = json.loads(message)
56
141
  pure_message = self.__transform_message(message_json)
@@ -67,6 +152,15 @@ class RabbitMessageConsumer(MessageConsumer):
67
152
  ch.basic_ack(delivery_tag=method.delivery_tag)
68
153
 
69
154
  def __transform_message(self, message: dict) -> Message:
155
+ """
156
+ Transforma o dicionário JSON recebido em uma instância de Message.
157
+
158
+ Args:
159
+ message (dict): Dicionário contendo os dados da mensagem.
160
+
161
+ Returns:
162
+ Message: Uma instância da classe Message com os dados extraídos do dicionário.
163
+ """
70
164
  return Message(
71
165
  type=message.get('type', ''),
72
166
  text=message.get('text', ''),
@@ -4,6 +4,18 @@ from typing import Optional
4
4
 
5
5
  @dataclass
6
6
  class Message:
7
+ """
8
+ Representa uma mensagem recebida ou enviada pelo chatbot.
9
+
10
+ Atributos:
11
+ type (str): O tipo da mensagem (por exemplo, texto, imagem, etc.).
12
+ text (str): O conteúdo textual da mensagem.
13
+ customer_id (str): O ID do cliente que enviou ou recebeu a mensagem.
14
+ channel (str): O canal pelo qual a mensagem foi enviada ou recebida (por exemplo, WhatsApp, SMS, etc.).
15
+ customer_phone (str): O número de telefone do cliente.
16
+ company_phone (str): O número de telefone da empresa que está enviando ou recebendo a mensagem.
17
+ status (Optional[str]): O status da mensagem (por exemplo, enviada, recebida, lida, etc.). Este campo é opcional.
18
+ """
7
19
  type: str
8
20
  text: str
9
21
  customer_id: str
@@ -4,12 +4,39 @@ messageTypes = Union[str, float, int, None]
4
4
 
5
5
 
6
6
  class ChatbotResponse:
7
- def __init__(
8
- self, message: messageTypes = None, route: str = None
9
- ) -> None:
7
+ """
8
+ Representa a resposta do chatbot, contendo a mensagem a ser enviada ao usuário e a rota a ser seguida.
9
+
10
+ Atributos:
11
+ message (messageTypes): A mensagem de resposta do chatbot. Pode ser uma string, um número, ou None.
12
+ route (str, opcional): A rota para a qual o chatbot deve direcionar após esta mensagem. Padrão é None.
13
+ """
14
+
15
+ def __init__(self, message: messageTypes = None, route: str = None) -> None:
16
+ """
17
+ Inicializa a resposta do chatbot com uma mensagem e uma rota opcional.
18
+
19
+ Args:
20
+ message (messageTypes, opcional): A mensagem a ser enviada ao usuário. Pode ser uma string, um número, ou None.
21
+ route (str, opcional): A rota para a qual o chatbot deve direcionar após esta mensagem. Padrão é None.
22
+ """
10
23
  self.message = message
11
24
  self.route = route
12
25
 
26
+
13
27
  class RedirectResponse:
28
+ """
29
+ Representa uma resposta que redireciona o fluxo do chatbot para uma nova rota.
30
+
31
+ Atributos:
32
+ route (str): A rota para a qual o chatbot deve redirecionar.
33
+ """
34
+
14
35
  def __init__(self, route: str) -> None:
15
- self.route = route
36
+ """
37
+ Inicializa a resposta de redirecionamento com a rota especificada.
38
+
39
+ Args:
40
+ route (str): A rota para a qual o chatbot deve redirecionar.
41
+ """
42
+ self.route = route
chatgraph/types/route.py CHANGED
@@ -2,13 +2,34 @@ from ..error.route_error import RouteError
2
2
 
3
3
 
4
4
  class Route:
5
+ """
6
+ Representa uma rota no sistema do chatbot, gerenciando a navegação entre diferentes partes do fluxo.
7
+
8
+ Atributos:
9
+ current (str): A rota atual.
10
+ routes (list[str]): A lista de todas as rotas disponíveis no fluxo.
11
+ """
12
+
5
13
  def __init__(self, current: str, routes: list[str]):
14
+ """
15
+ Inicializa a rota com a rota atual e a lista de rotas disponíveis.
16
+
17
+ Args:
18
+ current (str): A rota atual.
19
+ routes (list[str]): A lista de todas as rotas disponíveis no fluxo.
20
+ """
6
21
  self.current = current
7
22
  self.routes = routes
8
23
 
9
24
  def get_previous(self) -> str:
10
25
  """
11
26
  Retorna o caminho anterior ao caminho atual.
27
+
28
+ Raises:
29
+ RouteError: Se a rota atual for 'START', indicando que não há caminho anterior.
30
+
31
+ Returns:
32
+ str: O caminho anterior à rota atual.
12
33
  """
13
34
  if self.current == 'START':
14
35
  raise RouteError('Não há caminho anterior ao START')
@@ -19,6 +40,15 @@ class Route:
19
40
  def get_next(self, next_part: str) -> str:
20
41
  """
21
42
  Monta e retorna o próximo caminho com base na parte fornecida.
43
+
44
+ Args:
45
+ next_part (str): A parte do caminho a ser adicionada à rota atual.
46
+
47
+ Raises:
48
+ RouteError: Se a próxima rota montada não estiver na lista de rotas disponíveis.
49
+
50
+ Returns:
51
+ str: O próximo caminho construído a partir da rota atual e da parte fornecida.
22
52
  """
23
53
  next_part = next_part.strip().upper()
24
54
  next_route = f"{self.current.rstrip('/')}{next_part}"
@@ -27,7 +57,19 @@ class Route:
27
57
  return next_route
28
58
 
29
59
  def __str__(self):
60
+ """
61
+ Retorna uma representação em string da rota atual.
62
+
63
+ Returns:
64
+ str: A representação em string da rota atual.
65
+ """
30
66
  return f'Route(current={self.current})'
31
67
 
32
68
  def __repr__(self):
69
+ """
70
+ Retorna a representação oficial da rota, que é a mesma que a representação em string.
71
+
72
+ Returns:
73
+ str: A representação oficial da rota.
74
+ """
33
75
  return self.__str__()
@@ -2,25 +2,73 @@ from abc import ABC, abstractmethod
2
2
 
3
3
 
4
4
  class UserState(ABC):
5
+ """
6
+ Classe abstrata para gerenciar o estado do usuário no fluxo do chatbot.
7
+
8
+ Esta classe define a interface para implementar o gerenciamento de estado do usuário, incluindo métodos para obter e definir o menu atual do usuário.
9
+ """
10
+
5
11
  @abstractmethod
6
- def get_menu(self, customer_id: str):
12
+ def get_menu(self, customer_id: str) -> str:
13
+ """
14
+ Retorna o menu atual para o ID de cliente fornecido.
15
+
16
+ Args:
17
+ customer_id (str): O ID do cliente.
18
+
19
+ Returns:
20
+ str: O menu atual associado ao cliente.
21
+ """
7
22
  pass
8
23
 
9
24
  @abstractmethod
10
- def set_menu(self, customer_id: str, menu: str):
25
+ def set_menu(self, customer_id: str, menu: str) -> None:
26
+ """
27
+ Define o menu atual para o ID de cliente fornecido.
28
+
29
+ Args:
30
+ customer_id (str): O ID do cliente.
31
+ menu (str): O menu a ser definido para o cliente.
32
+ """
11
33
  pass
12
34
 
13
35
 
14
36
  class SimpleUserState(UserState):
37
+ """
38
+ Implementação simples de UserState que armazena o estado do usuário em um dicionário em memória.
39
+
40
+ Atributos:
41
+ states (dict): Dicionário que armazena o estado de menu atual para cada cliente.
42
+ """
43
+
15
44
  def __init__(self):
45
+ """
46
+ Inicializa o estado do usuário com um dicionário vazio.
47
+ """
16
48
  self.states = {}
17
49
 
18
50
  def get_menu(self, customer_id: str) -> str:
51
+ """
52
+ Retorna o menu atual para o ID de cliente fornecido. Se o cliente não tiver um menu definido, define 'START' como padrão.
53
+
54
+ Args:
55
+ customer_id (str): O ID do cliente.
56
+
57
+ Returns:
58
+ str: O menu atual associado ao cliente.
59
+ """
19
60
  menu = self.states.get(customer_id, 'START')
20
61
  if menu == 'START':
21
62
  self.set_menu(customer_id, menu)
22
63
  return menu
23
64
 
24
- def set_menu(self, customer_id: str, menu: str|None=None) -> str:
65
+ def set_menu(self, customer_id: str, menu: str | None = None) -> None:
66
+ """
67
+ Define o menu atual para o ID de cliente fornecido. Converte o nome do menu para maiúsculas.
68
+
69
+ Args:
70
+ customer_id (str): O ID do cliente.
71
+ menu (str | None): O menu a ser definido para o cliente. Se None, não faz nenhuma alteração.
72
+ """
25
73
  if menu:
26
74
  self.states[customer_id] = menu.upper()
@@ -0,0 +1,142 @@
1
+ Metadata-Version: 2.1
2
+ Name: chatgraph
3
+ Version: 0.1.1
4
+ Summary: A user-friendly chatbot library
5
+ Home-page: https://github.com/irissonnlima/chatgraph
6
+ License: MIT
7
+ Keywords: chatbot,rabbitmq,messaging,routing,chatgraph,python
8
+ Author: Irisson N. Lima
9
+ Author-email: irisson.lima@verdecard.com.br
10
+ Requires-Python: >=3.12,<4.0
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Requires-Dist: pika (>=1.3.2,<2.0.0)
15
+ Project-URL: Repository, https://github.com/irissonnlima/chatgraph
16
+ Description-Content-Type: text/markdown
17
+
18
+ # ChatGraph
19
+
20
+ **ChatGraph** é uma biblioteca Python projetada para facilitar a construção e gerenciamento de chatbots com roteamento flexível.
21
+
22
+ Ela oferece uma estrutura para definir rotas de chatbot, processar mensagens e gerenciar o estado do usuário de maneira eficiente.
23
+
24
+ ## Instalação
25
+
26
+ Você pode instalar o ChatGraph diretamente do PyPI:
27
+
28
+ ```bash
29
+ pip install chatgraph
30
+ ```
31
+
32
+ Link do PyPI: [ChatGraph no PyPI](https://pypi.org/project/chatgraph/)
33
+
34
+ ## Estrutura da Biblioteca
35
+
36
+ ### 1. `ChatbotApp`
37
+
38
+ A classe `ChatbotApp` é o núcleo da aplicação do chatbot. Ela gerencia as rotas definidas, processa as mensagens recebidas e lida com a lógica de navegação dentro do chatbot.
39
+
40
+ - **Métodos principais:**
41
+ - `include_router`: Inclui um conjunto de rotas (definido por `ChatbotRouter`) na aplicação.
42
+ - `route`: Decorador para associar funções a rotas específicas.
43
+ - `start`: Inicia o consumo de mensagens do RabbitMQ.
44
+ - `process_message`: Processa a mensagem recebida e executa a função correspondente à rota atual do usuário.
45
+
46
+ ### 2. `ChatbotRouter`
47
+
48
+ A classe `ChatbotRouter` permite definir e agrupar rotas que podem ser facilmente incluídas na aplicação principal do chatbot.
49
+
50
+ - **Métodos principais:**
51
+ - `route`: Decorador para adicionar uma função como uma rota no roteador.
52
+ - `include_router`: Inclui outro roteador dentro do roteador atual, permitindo a construção modular das rotas.
53
+
54
+ ### 3. `MessageConsumer` e `RabbitMessageConsumer`
55
+
56
+ A classe abstrata `MessageConsumer` define a interface para consumidores de mensagens no sistema do chatbot. A implementação concreta `RabbitMessageConsumer` consome mensagens de uma fila RabbitMQ, processa essas mensagens e envia respostas de acordo.
57
+
58
+ - **Métodos principais:**
59
+ - `start_consume`: Inicia o consumo de mensagens do RabbitMQ.
60
+ - `on_request`: Processa uma mensagem recebida e envia a resposta correspondente.
61
+ - `load_dotenv`: Carrega as configurações do RabbitMQ a partir de variáveis de ambiente.
62
+
63
+ ### 4. `UserState` e `SimpleUserState`
64
+
65
+ A classe abstrata `UserState` define a interface para o gerenciamento do estado do usuário. A implementação `SimpleUserState` usa um dicionário em memória para armazenar o estado atual do menu para cada usuário.
66
+
67
+ - **Métodos principais:**
68
+ - `get_menu`: Retorna o menu atual associado a um ID de cliente.
69
+ - `set_menu`: Define o menu atual para um ID de cliente.
70
+
71
+ ### 5. `Message`
72
+
73
+ A classe `Message` encapsula os dados de uma mensagem enviada ou recebida pelo chatbot.
74
+
75
+ - **Atributos:**
76
+ - `type`: Tipo da mensagem (ex. texto, imagem).
77
+ - `text`: Conteúdo da mensagem.
78
+ - `customer_id`: ID do cliente que enviou ou recebeu a mensagem.
79
+ - `channel`: Canal de comunicação utilizado (ex. WhatsApp, SMS).
80
+ - `customer_phone`: Número de telefone do cliente.
81
+ - `company_phone`: Número de telefone da empresa.
82
+ - `status`: Status da mensagem (opcional).
83
+
84
+ ### 6. `ChatbotResponse` e `RedirectResponse`
85
+
86
+ Essas classes são usadas para definir a resposta do chatbot após o processamento de uma mensagem.
87
+
88
+ - **`ChatbotResponse`**: Contém uma mensagem de resposta e uma rota opcional.
89
+ - **`RedirectResponse`**: Define uma rota para a qual o chatbot deve redirecionar o fluxo.
90
+
91
+ ### 7. `Route` e `RouteError`
92
+
93
+ A classe `Route` gerencia a navegação entre diferentes partes do fluxo do chatbot, permitindo obter a rota anterior e calcular a próxima rota com base na entrada do usuário.
94
+
95
+ - **Métodos principais:**
96
+ - `get_previous`: Retorna o caminho anterior ao atual.
97
+ - `get_next`: Monta e retorna o próximo caminho com base em uma parte fornecida.
98
+
99
+ A classe `RouteError` é uma exceção personalizada usada para indicar problemas relacionados à navegação nas rotas.
100
+
101
+ ## Exemplo de Uso
102
+
103
+ Aqui está um exemplo básico de como configurar e utilizar o ChatGraph:
104
+
105
+ ```python
106
+ from chatgraph import ChatbotApp, ChatbotRouter, SimpleUserState, RabbitMessageConsumer, ChatbotResponse
107
+
108
+ # Definindo rotas com ChatbotRouter
109
+ router = ChatbotRouter()
110
+
111
+ @router.route("START")
112
+ def say_hello():
113
+ return ChatbotResponse(message="Hello! How can I assist you today?", route="HELP")
114
+
115
+ @router.route("/HELP")
116
+ def provide_help():
117
+ return ChatbotResponse(message="Here are some things I can help with: ...")
118
+
119
+ # Configurando a aplicação do chatbot
120
+ user_state = SimpleUserState()
121
+ message_consumer = RabbitMessageConsumer.load_dotenv()
122
+
123
+ app = ChatbotApp(user_state=user_state, message_consumer=message_consumer)
124
+ app.include_router(router, prefix="")
125
+
126
+ # Iniciando o chatbot
127
+ app.start()
128
+ ```
129
+
130
+ Neste exemplo, o chatbot responde "Hello! How can I assist you today?" quando a rota `HELLO` é acessada e depois direciona o usuário para a rota `HELP`.
131
+
132
+ ## Contribuição
133
+
134
+ Se você encontrar bugs ou tiver sugestões de melhorias, sinta-se à vontade para abrir uma issue ou enviar um pull request no repositório do projeto.
135
+
136
+ ## Licença
137
+
138
+ Este projeto está licenciado sob a licença MIT. Consulte o arquivo LICENSE para obter mais detalhes.
139
+
140
+ ---
141
+
142
+ Com **ChatGraph**, você tem a flexibilidade e a simplicidade necessárias para construir chatbots poderosos e altamente configuráveis, integrados, por enquanto, com RabbitMQ para um processamento robusto de mensagens.
@@ -0,0 +1,16 @@
1
+ chatgraph/__init__.py,sha256=qPA9Vw2SZivqVUlB19D-YrRuequMThR6kgO2XsZcwDc,602
2
+ chatgraph/auth/credentials.py,sha256=xsMEpLQnc66novPjL6upocMcnUnvFJ7yxINzUenkxmc,2388
3
+ chatgraph/bot/chatbot_model.py,sha256=D2MGguq3Fnl9xiY0frpk_-DkTNgeBV57q5w3jx8Kba4,6296
4
+ chatgraph/bot/chatbot_router.py,sha256=CQxu7ZiLBZDzXso65f4u3jfLA8BsuTAmp_tonFhFdc0,2817
5
+ chatgraph/error/chatbot_error.py,sha256=4sEcW8vz0eQk2QDmDygucVM4caHliZW5iH7XJvmLBuk,1897
6
+ chatgraph/error/route_error.py,sha256=CY8m82ap7-Sr-DXPsolltRW50TqD__5RyXBmNNJCWr8,793
7
+ chatgraph/messages/base_message_consumer.py,sha256=OSdTT4dHIzawLDOCZ-4hZ06T8UBxoJIosqvXl7gxpM0,1099
8
+ chatgraph/messages/rabbitMQ_message_consumer.py,sha256=mRcJ1Hbd5wPFxC3v6Tj3pYvjd9yAc398Ys6cUox-CTY,7005
9
+ chatgraph/types/message_types.py,sha256=NBuebmpO2e9ilspBoFgs9O3rY_VQ2AP_Z8Q_nC70nyM,965
10
+ chatgraph/types/output_state.py,sha256=ZditFyycd6CEvemrW9c7mThQDP1l6tI_8p19Jgj8pbs,1471
11
+ chatgraph/types/route.py,sha256=iSeVDMsH_1Q1n1sAyeiqFdvg2KWIY8DFdGThswwpUBw,2422
12
+ chatgraph/types/user_state.py,sha256=0n7K5k5DGoP3ug8XNPREqdJHGHXmcOwhPrANpXJSDjU,2321
13
+ chatgraph-0.1.1.dist-info/LICENSE,sha256=rVJozpRzDlplOpvI8A1GvmfVS0ReYdZvMWc1j2jV0v8,1090
14
+ chatgraph-0.1.1.dist-info/METADATA,sha256=yt3nbvNpQX-gCBzx8zP9Uz6ZDPfpCml2Dpd1Z8D8KuU,5828
15
+ chatgraph-0.1.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
16
+ chatgraph-0.1.1.dist-info/RECORD,,
@@ -1,155 +0,0 @@
1
- import inspect
2
- import json
3
- from functools import wraps
4
- from logging import debug
5
-
6
- import pika
7
- import pika.exceptions
8
-
9
- from ..auth.credentials import Credential
10
- from ..error.chatbot_error import ChatbotError, ChatbotMessageError
11
- from ..types.message_types import Message
12
- from ..types.user_state import SimpleUserState, UserState
13
- from .chatbot_router import ChatbotRouter
14
-
15
-
16
- class ChatbotApp:
17
- def __init__(
18
- self,
19
- amqp_url: str,
20
- queue_consume: str,
21
- credentials: Credential,
22
- user_state: UserState,
23
- prefetch_count: int = 1,
24
- virtual_host: str = '/',
25
- ):
26
- self.__virtual_host = virtual_host
27
- self.__prefetch_count = prefetch_count
28
- self.__user_state = user_state
29
- self.__queue_consume = queue_consume
30
- self.__amqp_url = amqp_url
31
- self.__credentials = pika.PlainCredentials(
32
- credentials.username, credentials.password
33
- )
34
- self.__routes = {}
35
-
36
- def include_router(self, router: ChatbotRouter, prefix: str):
37
- if 'START' not in router.routes.keys():
38
- raise ChatbotError('Erro ao incluir rota, START não encontrado!')
39
-
40
- prefixed_routes = {
41
- (
42
- f'START{prefix.upper()}'
43
- if key.upper() == 'START'
44
- else f'START{prefix.upper()}{key.upper()}'
45
- ): value
46
- for key, value in router.routes.items()
47
- }
48
- self.__routes.update(prefixed_routes)
49
-
50
- def route(self, state: str, default_message: str):
51
- def decorator(func):
52
- params = dict()
53
- signature = inspect.signature(func)
54
- output_param = signature.return_annotation
55
-
56
- for name, param in signature.parameters.items():
57
- param_type = (
58
- param.annotation
59
- if param.annotation != inspect.Parameter.empty
60
- else 'Any'
61
- )
62
- params[param_type] = name
63
- debug(f'Parameter: {name}, Type: {param_type}')
64
-
65
- if Message not in params.keys():
66
- raise ChatbotError(
67
- 'Função não recebe Message, parâmetro obrigatório!'
68
- )
69
-
70
- self.__routes[state.upper()] = {
71
- 'function': func,
72
- 'default_message': default_message,
73
- 'params': params,
74
- 'return': output_param,
75
- }
76
-
77
- @wraps(func)
78
- def wrapper(*args, **kwargs):
79
- return func(*args, **kwargs)
80
-
81
- return wrapper
82
-
83
- return decorator
84
-
85
- def start(self):
86
- try: # Verificar se recursão não vai estourar
87
- connection = pika.BlockingConnection(
88
- pika.ConnectionParameters(
89
- host=self.__amqp_url,
90
- virtual_host=self.__virtual_host,
91
- credentials=self.__credentials,
92
- )
93
- )
94
- channel = connection.channel()
95
-
96
- channel.basic_qos(prefetch_count=self.__prefetch_count)
97
- channel.basic_consume(
98
- queue=self.__queue_consume,
99
- on_message_callback=self.on_request,
100
- )
101
-
102
- debug('[x] Aguardando solicitações RPC')
103
- channel.start_consuming()
104
- except pika.exceptions.StreamLostError as e:
105
- debug(e)
106
- self.start()
107
-
108
- def on_request(self, ch, method, props, body):
109
- message = body.decode()
110
- response = self.process_message(message)
111
-
112
- ch.basic_publish(
113
- exchange='',
114
- routing_key=props.reply_to,
115
- properties=pika.BasicProperties(
116
- correlation_id=props.correlation_id
117
- ),
118
- body=str(response),
119
- )
120
- ch.basic_ack(delivery_tag=method.delivery_tag)
121
-
122
- def process_message(self, message: str):
123
- data = json.loads(message)
124
- message_imported = self.__transform_message(data)
125
-
126
- customer_id = message_imported.customer_id
127
-
128
- menu = self.__user_state.get_menu(customer_id)
129
- handler = self.__routes.get(menu, None)
130
-
131
- if not handler:
132
- raise ChatbotMessageError(
133
- customer_id, f'Rota não encontrada para {menu}!'
134
- )
135
- func = handler['function']
136
- message_name = handler['params'].get(Message)
137
- user_state_name = handler['params'].get(UserState, None)
138
-
139
- kwargs = {message_name: message_imported}
140
- if user_state_name:
141
- kwargs[user_state_name] = SimpleUserState(
142
- menu, list(self.__routes.keys())
143
- )
144
- return func(**kwargs)
145
-
146
- def __transform_message(self, message: dict) -> Message:
147
- return Message(
148
- type=message.get('type', ''),
149
- text=message.get('text', ''),
150
- customer_id=message.get('customer_id', ''),
151
- channel=message.get('channel', ''),
152
- customer_phone=message.get('customer_phone', ''),
153
- company_phone=message.get('company_phone', ''),
154
- status=message.get('status'),
155
- )
@@ -1,5 +0,0 @@
1
- from .base_message_consumer import MessageConsumer
2
-
3
- __all__ = [
4
- 'MessageConsumer',
5
- ]
@@ -1,15 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: chatgraph
3
- Version: 0.1.0
4
- Summary: A user-friendly chatbot library
5
- Author: Irisson N. Lima
6
- Author-email: irisson.lima@verdecard.com.br
7
- Requires-Python: >=3.12,<4.0
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: Programming Language :: Python :: 3.12
10
- Requires-Dist: pika (>=1.3.2,<2.0.0)
11
- Description-Content-Type: text/markdown
12
-
13
- # chatgraph
14
- ChatGraph is a user-friendly chatbot library that offers seamless integration with RabbitMQ. It is designed to simplify the development process by providing intuitive interfaces and robust messaging capabilities, making it easy to build and deploy chatbots leveraging RabbitMQ's powerful messaging infrastructure.
15
-
@@ -1,18 +0,0 @@
1
- chatgraph/__init__.py,sha256=qPA9Vw2SZivqVUlB19D-YrRuequMThR6kgO2XsZcwDc,602
2
- chatgraph/auth/credentials.py,sha256=tc8FvjWu-cFdx9ChzxkSDXX04g3eSPE6FcsRW0xg40k,917
3
- chatgraph/bot/chatbot_app.py,sha256=Uix25rT8gCoCKYziSW0WrBsQzs4DU_L0GSHwdgu4et0,5263
4
- chatgraph/bot/chatbot_model.py,sha256=QqnCZvEOfp--zn-mnEXL3p5pqxPN_ztnhXdfNEeDnHk,4102
5
- chatgraph/bot/chatbot_router.py,sha256=48AiRaXtlCtad2imuUA-uK_ppxUwa6GEkPv8YXTB29g,1836
6
- chatgraph/error/chatbot_error.py,sha256=rTBPDPB2CNoZDKfL5PgWtj36jdoepe_gO6Opb5kd3mA,545
7
- chatgraph/error/route_error.py,sha256=jEi621zHJbB3LnwvbtP_o9u0AkoddrHoGo1OKAPLMWk,213
8
- chatgraph/messages/__init__.py,sha256=lKpQI7FXjSJuwcV9ItX6ZJditEm6kQpGSPoTuOtMHy4,94
9
- chatgraph/messages/base_message_consumer.py,sha256=hpoLLqxk5mQXRnS3s_lkOPosgwfMmvxAOftkt8P0Qvc,203
10
- chatgraph/messages/rabbitMQ_message_consumer.py,sha256=z4RQ21N1aUwx9OOrkQRd04fSTHfw3uzPsOlKyLGKO5c,2753
11
- chatgraph/types/message_types.py,sha256=pTUIGOiNdoYg7i8zNXRdGSg4z86bJZWjYbn3cII0eI0,249
12
- chatgraph/types/output_state.py,sha256=cBdZXnsyEVwmxno5De_vHUwh9cPokPvFpeiLM_3VkZQ,357
13
- chatgraph/types/route.py,sha256=818fovzJZyjPl27ZAvCZpZNUCccyO_6ciyLJxOHI1JI,1034
14
- chatgraph/types/user_state.py,sha256=3kAyAUue7KTi-Rvf-cYDzX3L-PMa4POmRFgAUvS9fTo,669
15
- chatgraph-0.1.0.dist-info/LICENSE,sha256=rVJozpRzDlplOpvI8A1GvmfVS0ReYdZvMWc1j2jV0v8,1090
16
- chatgraph-0.1.0.dist-info/METADATA,sha256=VNDKsd2e0HPI_RN6tuFUEvKBKyeJgokmJujlqw5z74k,695
17
- chatgraph-0.1.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
18
- chatgraph-0.1.0.dist-info/RECORD,,