gohumanloop 0.0.2__py3-none-any.whl → 0.0.4__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.
@@ -12,6 +12,7 @@ import logging
12
12
  from typing import Dict, Any, Optional, List, Tuple
13
13
  from datetime import datetime
14
14
  from pydantic import SecretStr
15
+ from concurrent.futures import ThreadPoolExecutor
15
16
 
16
17
  from gohumanloop.core.interface import ( HumanLoopResult, HumanLoopStatus, HumanLoopType
17
18
  )
@@ -79,6 +80,19 @@ class EmailProvider(BaseProvider):
79
80
 
80
81
  self._init_language_templates()
81
82
 
83
+ # Create thread pool for background service execution
84
+ self._executor = ThreadPoolExecutor(max_workers=10)
85
+
86
+
87
+ def __del__(self):
88
+ """析构函数,确保线程池被正确关闭"""
89
+ self._executor.shutdown(wait=False)
90
+
91
+ # 取消所有邮件检查任务
92
+ for task_key, future in list(self._mail_check_tasks.items()):
93
+ future.cancel()
94
+ self._mail_check_tasks.clear()
95
+
82
96
  def _init_language_templates(self):
83
97
  """初始化不同语言的模板和关键词"""
84
98
  if self.language == "zh":
@@ -132,7 +146,7 @@ class EmailProvider(BaseProvider):
132
146
  "powered_by": "Powered by GoHumanLoop"
133
147
  }
134
148
 
135
- async def _send_email(
149
+ async def _async_send_email(
136
150
  self,
137
151
  to_email: str,
138
152
  subject: str,
@@ -195,7 +209,7 @@ class EmailProvider(BaseProvider):
195
209
  logger.exception(f"Unknown error occurred while sending email: {str(e)}")
196
210
  raise
197
211
 
198
- async def _check_emails(self, conversation_id: str, request_id: str, recipient_email: str, subject: str):
212
+ async def _async_check_emails(self, conversation_id: str, request_id: str, recipient_email: str, subject: str):
199
213
  """Check email replies
200
214
 
201
215
  Args:
@@ -603,7 +617,7 @@ class EmailProvider(BaseProvider):
603
617
 
604
618
  return text_body, "\n".join(html_body)
605
619
 
606
- async def request_humanloop(
620
+ async def async_request_humanloop(
607
621
  self,
608
622
  task_id: str,
609
623
  conversation_id: str,
@@ -678,7 +692,7 @@ class EmailProvider(BaseProvider):
678
692
 
679
693
 
680
694
  # 发送邮件
681
- success = await self._send_email(
695
+ success = await self._async_send_email(
682
696
  to_email=recipient_email,
683
697
  subject=subject,
684
698
  body=body,
@@ -700,14 +714,15 @@ class EmailProvider(BaseProvider):
700
714
  self._subject_to_request[subject] = (conversation_id, request_id)
701
715
 
702
716
  # 创建邮件检查任务
703
- check_task = asyncio.create_task(
704
- self._check_emails(conversation_id, request_id, recipient_email, subject)
717
+ # 使用线程池执行邮件检查任务,而不是使用asyncio.create_task
718
+ self._mail_check_tasks[(conversation_id, request_id)] = self._executor.submit(
719
+ self._run_email_check_task, conversation_id, request_id, recipient_email, subject
705
720
  )
706
- self._mail_check_tasks[(conversation_id, request_id)] = check_task
721
+
707
722
 
708
723
  # 如果设置了超时,创建超时任务
709
724
  if timeout:
710
- self._create_timeout_task(conversation_id, request_id, timeout)
725
+ await self._async_create_timeout_task(conversation_id, request_id, timeout)
711
726
 
712
727
  return HumanLoopResult(
713
728
  conversation_id=conversation_id,
@@ -715,8 +730,38 @@ class EmailProvider(BaseProvider):
715
730
  loop_type=loop_type,
716
731
  status=HumanLoopStatus.PENDING
717
732
  )
733
+
734
+ def _run_email_check_task(self, conversation_id: str, request_id: str, recipient_email: str, subject: str):
735
+ """Run email check task in thread
736
+
737
+ Args:
738
+ conversation_id: Conversation ID
739
+ request_id: Request ID
740
+ recipient_email: Recipient email address
741
+ subject: Email subject
742
+ """
743
+ # Create new event loop
744
+ loop = asyncio.new_event_loop()
745
+ asyncio.set_event_loop(loop)
718
746
 
719
- async def check_request_status(
747
+ try:
748
+ # Run email check in new event loop
749
+ loop.run_until_complete(
750
+ self._async_check_emails(conversation_id, request_id, recipient_email, subject)
751
+ )
752
+ except Exception as e:
753
+ logger.error(f"Email check task error: {str(e)}", exc_info=True)
754
+ # Update request status to error
755
+ self._update_request_status_error(conversation_id, request_id, f"Email check task error: {str(e)}")
756
+ finally:
757
+ # Close event loop
758
+ loop.close()
759
+ # Remove from task dictionary
760
+ if (conversation_id, request_id) in self._mail_check_tasks:
761
+ del self._mail_check_tasks[(conversation_id, request_id)]
762
+
763
+
764
+ async def async_check_request_status(
720
765
  self,
721
766
  conversation_id: str,
722
767
  request_id: str
@@ -755,7 +800,7 @@ class EmailProvider(BaseProvider):
755
800
 
756
801
  return result
757
802
 
758
- async def continue_humanloop(
803
+ async def async_continue_humanloop(
759
804
  self,
760
805
  conversation_id: str,
761
806
  context: Dict[str, Any],
@@ -855,7 +900,7 @@ class EmailProvider(BaseProvider):
855
900
  body, html_body = self._format_email_body(prompt, HumanLoopType.CONVERSATION, subject)
856
901
 
857
902
  # 发送邮件
858
- success = await self._send_email(
903
+ success = await self._async_send_email(
859
904
  to_email=recipient_email,
860
905
  subject=subject,
861
906
  body=body,
@@ -876,16 +921,15 @@ class EmailProvider(BaseProvider):
876
921
 
877
922
  # 存储主题与请求的映射关系
878
923
  self._subject_to_request[subject] = (conversation_id, request_id)
879
-
880
- # 创建邮件检查任务
881
- check_task = asyncio.create_task(
882
- self._check_emails(conversation_id, request_id, recipient_email, subject)
883
- )
884
- self._mail_check_tasks[(conversation_id, request_id)] = check_task
885
924
 
925
+ # 使用线程池执行邮件检查任务,而不是使用asyncio.create_task
926
+ self._mail_check_tasks[(conversation_id, request_id)] = self._executor.submit(
927
+ self._run_email_check_task, conversation_id, request_id, recipient_email, subject
928
+ )
929
+
886
930
  # 如果设置了超时,创建超时任务
887
931
  if timeout:
888
- self._create_timeout_task(conversation_id, request_id, timeout)
932
+ await self._async_create_timeout_task(conversation_id, request_id, timeout)
889
933
 
890
934
  return HumanLoopResult(
891
935
  conversation_id=conversation_id,
@@ -894,7 +938,7 @@ class EmailProvider(BaseProvider):
894
938
  status=HumanLoopStatus.PENDING
895
939
  )
896
940
 
897
- async def cancel_request(
941
+ async def async_cancel_request(
898
942
  self,
899
943
  conversation_id: str,
900
944
  request_id: str
@@ -916,9 +960,9 @@ class EmailProvider(BaseProvider):
916
960
  del self._mail_check_tasks[request_key]
917
961
 
918
962
  # 调用父类方法取消请求
919
- return await super().cancel_request(conversation_id, request_id)
963
+ return await super().async_cancel_request(conversation_id, request_id)
920
964
 
921
- async def cancel_conversation(
965
+ async def async_cancel_conversation(
922
966
  self,
923
967
  conversation_id: str
924
968
  ) -> bool:
@@ -938,7 +982,7 @@ class EmailProvider(BaseProvider):
938
982
  del self._mail_check_tasks[request_key]
939
983
 
940
984
  # 调用父类方法取消对话
941
- return await super().cancel_conversation(conversation_id)
985
+ return await super().async_cancel_conversation(conversation_id)
942
986
 
943
987
  def _extract_user_reply_content(self, body: str) -> str:
944
988
  """Extract the actual reply content from the email, excluding quoted original email content
@@ -1,8 +1,6 @@
1
1
  import asyncio
2
- from email import message
3
- import sys
4
- import json
5
- from typing import Dict, Any, Optional, List
2
+ from concurrent.futures import ThreadPoolExecutor
3
+ from typing import Dict, Any, Optional
6
4
  from datetime import datetime
7
5
 
8
6
  from gohumanloop.core.interface import (HumanLoopResult, HumanLoopStatus, HumanLoopType)
@@ -22,13 +20,27 @@ class TerminalProvider(BaseProvider):
22
20
  name: Provider name
23
21
  config: Configuration options, may include:
24
22
  """
25
- super().__init__(name, config)
23
+ super().__init__(name, config)
24
+
25
+ # Store running terminal input tasks
26
+ self._terminal_input_tasks = {}
27
+ # Create thread pool for background service execution
28
+ self._executor = ThreadPoolExecutor(max_workers=10)
29
+
30
+ def __del__(self):
31
+ """Destructor to ensure thread pool is properly closed"""
32
+ self._executor.shutdown(wait=False)
33
+
34
+ for task_key, future in list(self._terminal_input_tasks.items()):
35
+ future.cancel()
36
+ self._terminal_input_tasks.clear()
37
+
26
38
  def __str__(self) -> str:
27
39
  base_str = super().__str__()
28
40
  terminal_info = f"- Terminal Provider: Terminal-based human-in-the-loop implementation\n"
29
41
  return f"{terminal_info}{base_str}"
30
42
 
31
- async def request_humanloop(
43
+ async def async_request_humanloop(
32
44
  self,
33
45
  task_id: str,
34
46
  conversation_id: str,
@@ -72,16 +84,32 @@ class TerminalProvider(BaseProvider):
72
84
  status=HumanLoopStatus.PENDING
73
85
  )
74
86
 
75
- # Start async task to process user input
76
- asyncio.create_task(self._process_terminal_interaction(conversation_id, request_id))
77
-
87
+
88
+ self._terminal_input_tasks[(conversation_id, request_id)] = self._executor.submit(self._run_async_terminal_interaction, conversation_id, request_id)
89
+
78
90
  # Create timeout task if timeout is specified
79
91
  if timeout:
80
- self._create_timeout_task(conversation_id, request_id, timeout)
92
+ await self._async_create_timeout_task(conversation_id, request_id, timeout)
81
93
 
82
94
  return result
83
-
84
- async def check_request_status(
95
+
96
+
97
+ def _run_async_terminal_interaction(self, conversation_id: str, request_id: str):
98
+ """Run asynchronous terminal interaction in a separate thread"""
99
+ # Create new event loop
100
+ loop = asyncio.new_event_loop()
101
+ asyncio.set_event_loop(loop)
102
+
103
+ try:
104
+ # Run interaction processing in the new event loop
105
+ loop.run_until_complete(self._process_terminal_interaction(conversation_id, request_id))
106
+ finally:
107
+ loop.close()
108
+ # Remove from task dictionary
109
+ if (conversation_id, request_id) in self._terminal_input_tasks:
110
+ del self._terminal_input_tasks[(conversation_id, request_id)]
111
+
112
+ async def async_check_request_status(
85
113
  self,
86
114
  conversation_id: str,
87
115
  request_id: str
@@ -120,7 +148,7 @@ class TerminalProvider(BaseProvider):
120
148
 
121
149
  return result
122
150
 
123
- async def continue_humanloop(
151
+ async def async_continue_humanloop(
124
152
  self,
125
153
  conversation_id: str,
126
154
  context: Dict[str, Any],
@@ -175,11 +203,11 @@ class TerminalProvider(BaseProvider):
175
203
  )
176
204
 
177
205
  # Start async task to process user input
178
- asyncio.create_task(self._process_terminal_interaction(conversation_id, request_id))
206
+ self._terminal_input_tasks[(conversation_id, request_id)] = self._executor.submit(self._run_async_terminal_interaction, conversation_id, request_id)
179
207
 
180
208
  # Create timeout task if timeout is specified
181
209
  if timeout:
182
- self._create_timeout_task(conversation_id, request_id, timeout)
210
+ await self._async_create_timeout_task(conversation_id, request_id, timeout)
183
211
 
184
212
  return result
185
213
 
@@ -205,14 +233,14 @@ class TerminalProvider(BaseProvider):
205
233
 
206
234
  # Handle different interaction types based on loop type
207
235
  if loop_type == HumanLoopType.APPROVAL:
208
- await self._handle_approval_interaction(conversation_id, request_id, request_info)
236
+ await self._async_handle_approval_interaction(conversation_id, request_id, request_info)
209
237
  elif loop_type == HumanLoopType.INFORMATION:
210
- await self._handle_information_interaction(conversation_id, request_id, request_info)
238
+ await self._async_handle_information_interaction(conversation_id, request_id, request_info)
211
239
  else: # HumanLoopType.CONVERSATION
212
- await self._handle_conversation_interaction(conversation_id, request_id, request_info)
240
+ await self._async_handle_conversation_interaction(conversation_id, request_id, request_info)
213
241
 
214
242
 
215
- async def _handle_approval_interaction(self, conversation_id: str, request_id: str, request_info: Dict[str, Any]):
243
+ async def _async_handle_approval_interaction(self, conversation_id: str, request_id: str, request_info: Dict[str, Any]):
216
244
  """Handle approval type interaction
217
245
 
218
246
  Args:
@@ -225,7 +253,6 @@ class TerminalProvider(BaseProvider):
225
253
  # Execute blocking input() call in thread pool using run_in_executor
226
254
  loop = asyncio.get_event_loop()
227
255
  response = await loop.run_in_executor(None, input)
228
-
229
256
  # Process response
230
257
  response = response.strip().lower()
231
258
  if response in ["approve", "yes", "y", "同意", "批准"]:
@@ -239,7 +266,7 @@ class TerminalProvider(BaseProvider):
239
266
  else:
240
267
  print("\nInvalid input, please enter 'approve' or 'reject'")
241
268
  # Recursively handle approval interaction
242
- await self._handle_approval_interaction(conversation_id, request_id, request_info)
269
+ await self._async_handle_approval_interaction(conversation_id, request_id, request_info)
243
270
  return
244
271
 
245
272
  # Update request information
@@ -250,7 +277,7 @@ class TerminalProvider(BaseProvider):
250
277
 
251
278
  print(f"\nYour decision has been recorded: {status.value}")
252
279
 
253
- async def _handle_information_interaction(self, conversation_id: str, request_id: str, request_info: Dict[str, Any]):
280
+ async def _async_handle_information_interaction(self, conversation_id: str, request_id: str, request_info: Dict[str, Any]):
254
281
  """Handle information collection type interaction
255
282
 
256
283
  Args:
@@ -272,7 +299,7 @@ class TerminalProvider(BaseProvider):
272
299
 
273
300
  print("\nYour information has been recorded")
274
301
 
275
- async def _handle_conversation_interaction(self, conversation_id: str, request_id: str, request_info: Dict[str, Any]):
302
+ async def _async_handle_conversation_interaction(self, conversation_id: str, request_id: str, request_info: Dict[str, Any]):
276
303
  """Handle conversation type interaction
277
304
 
278
305
  Args:
@@ -300,3 +327,48 @@ class TerminalProvider(BaseProvider):
300
327
  request_info["responded_at"] = datetime.now().isoformat()
301
328
 
302
329
  print("\nYour response has been recorded")
330
+
331
+ async def async_cancel_request(
332
+ self,
333
+ conversation_id: str,
334
+ request_id: str
335
+ ) -> bool:
336
+ """Cancel human-in-the-loop request
337
+
338
+ Args:
339
+ conversation_id: Conversation identifier for multi-turn dialogues
340
+ request_id: Request identifier for specific interaction request
341
+
342
+ Return:
343
+ bool: Whether cancellation was successful, True indicates successful cancellation,
344
+ False indicates cancellation failed
345
+ """
346
+ request_key = (conversation_id, request_id)
347
+ if request_key in self._terminal_input_tasks:
348
+ self._terminal_input_tasks[request_key].cancel()
349
+ del self._terminal_input_tasks[request_key]
350
+
351
+ # 调用父类方法取消请求
352
+ return await super().async_cancel_request(conversation_id, request_id)
353
+
354
+ async def async_cancel_conversation(
355
+ self,
356
+ conversation_id: str
357
+ ) -> bool:
358
+ """Cancel the entire conversation
359
+
360
+ Args:
361
+ conversation_id: Conversation identifier
362
+
363
+ Returns:
364
+ bool: Whether cancellation was successful
365
+ """
366
+ # 取消所有相关的邮件检查任务
367
+ for request_id in self._get_conversation_requests(conversation_id):
368
+ request_key = (conversation_id, request_id)
369
+ if request_key in self._terminal_input_tasks:
370
+ self._terminal_input_tasks[request_key].cancel()
371
+ del self._terminal_input_tasks[request_key]
372
+
373
+ # 调用父类方法取消对话
374
+ return await super().async_cancel_conversation(conversation_id)
@@ -2,16 +2,46 @@ import asyncio
2
2
  import os
3
3
  from typing import Optional, Union
4
4
  from pydantic import SecretStr
5
+ import warnings
6
+ import logging
7
+
8
+ logger = logging.getLogger(__name__)
9
+
5
10
  def run_async_safely(coro):
6
- """同步环境下安全地运行异步协程,避免事件循环冲突"""
11
+ """
12
+ Safely run async coroutines in synchronous environment
13
+ Will raise RuntimeError if called in async environment
14
+ """
15
+ try:
16
+ loop = asyncio.get_running_loop()
17
+ except RuntimeError: # No running event loop
18
+ loop = None
19
+
20
+ if loop is not None:
21
+ raise RuntimeError(
22
+ "Detected running event loop! "
23
+ "You should use 'await' directly instead of run_async_safely(). "
24
+ "If you really need to call sync code from async context, "
25
+ "consider using asyncio.to_thread() or other proper methods."
26
+ )
27
+
28
+ # Handle synchronous environment
7
29
  try:
8
30
  loop = asyncio.get_event_loop()
31
+ logger.info("Using existing event loop.")
9
32
  except RuntimeError:
10
- # 如果没有事件循环,创建一个新的
11
33
  loop = asyncio.new_event_loop()
12
34
  asyncio.set_event_loop(loop)
35
+ own_loop = True
36
+ logger.info("Created new event loop.")
37
+ else:
38
+ own_loop = False
13
39
 
14
- return loop.run_until_complete(coro)
40
+ try:
41
+ return loop.run_until_complete(coro)
42
+ finally:
43
+ if own_loop and not loop.is_closed():
44
+ loop.close()
15
45
 
16
46
 
17
47
  def get_secret_from_env(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gohumanloop
3
- Version: 0.0.2
3
+ Version: 0.0.4
4
4
  Summary: Perfecting AI workflows with human intelligence
5
5
  Author-email: gohumanloop authors <baird0917@163.com>
6
6
  Project-URL: repository, https://github.com/ptonlix/gohumanloop
@@ -1,30 +1,30 @@
1
1
  gohumanloop/__init__.py,sha256=7_AkUtiG-_iozObldORElQS9mufxjZx_WfxuX0E5Af0,1845
2
2
  gohumanloop/__main__.py,sha256=zdGKN92H9SgwZfL4xLqPkE1YaiRcHhVg_GqC-H1VurA,75
3
3
  gohumanloop/adapters/__init__.py,sha256=alRiJPahmH5vIbiw7l6o3eFvEADVTkfWYIsXy5uPGSo,391
4
- gohumanloop/adapters/langgraph_adapter.py,sha256=W-Uu9KYzLX69pm3UgkK57lsbONb9DllQwRBdau_voZc,34058
4
+ gohumanloop/adapters/langgraph_adapter.py,sha256=eJLJ_LgU7ZHwm2zSwlFeDIZxjbr8JgfNZiskv0TDOGg,34355
5
5
  gohumanloop/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  gohumanloop/cli/main.py,sha256=54-0nwjaAeRH2WhbyO6pN-XADPQwk4_EUUvVWDWruLc,744
7
7
  gohumanloop/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- gohumanloop/core/interface.py,sha256=WOWxlwsj3FSjLiZp_CW36AN_AQYc89PgdA9GZX1Irwc,13361
9
- gohumanloop/core/manager.py,sha256=QTpSa0GJEpmEd-f-FevqEsPQL9F8e8f0YLB5fPeLMio,23923
8
+ gohumanloop/core/interface.py,sha256=UjeEBKGS_JjwIsT5sBzyq6_IhUkDFrUvBXqpkxkFrAA,22696
9
+ gohumanloop/core/manager.py,sha256=MAgT5Sx1aLRBIb1mWxp-XJkQxEoibut8-TVtze8bWXQ,33068
10
10
  gohumanloop/manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- gohumanloop/manager/ghl_manager.py,sha256=0FEV3brYz3jerkljDL2C0pt3_tAFsB019MWhyPiNDis,21909
11
+ gohumanloop/manager/ghl_manager.py,sha256=Td54EcPg1r9Q5JfzS90QjzxiAhcMBYMigaERImLjp7M,21993
12
12
  gohumanloop/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  gohumanloop/models/api_model.py,sha256=cNTXTlfI7yrTk_87Qf6ms0VtRXO2fYFJFLPTLy2dmQk,2853
14
14
  gohumanloop/models/glh_model.py,sha256=Ht93iCdLfVYz_nW-uW4bE5s0UoyKG3VEx9q-Gg8_tiY,870
15
15
  gohumanloop/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- gohumanloop/providers/api_provider.py,sha256=WJ7z1dUkQ6xcURx0sAOt_sLFPSGaz2yttSvmNGhvI-c,24398
17
- gohumanloop/providers/base.py,sha256=ke9HUKCrr24U71UUdLKoKyTRujCG0cOGEevKDDcd5Nc,16863
18
- gohumanloop/providers/email_provider.py,sha256=U1dAXx3T-unDNQ4Kn7Q4xLnGiUfTIz6iw5YKRn4A2h0,42460
16
+ gohumanloop/providers/api_provider.py,sha256=MPIe_BzXkZmbY8IbLhs04zDG-u2qw0PEy28ZWZxJQAI,25604
17
+ gohumanloop/providers/base.py,sha256=en6Px3v-tPJz_zA8-cuT69YVoOyO1Av07kpgfeZoejc,22037
18
+ gohumanloop/providers/email_provider.py,sha256=L_HWkWopJF-8h0e6sOFTZzu2FanBLvCFuXOVZpDahP4,44342
19
19
  gohumanloop/providers/ghl_provider.py,sha256=YdxTpRzitFhTXTbhUcMhQlPUs3kwEBd4wyXEcGK8Svk,2524
20
- gohumanloop/providers/terminal_provider.py,sha256=obe1Mjj3dlyIPlyViMj3bi4R9HhKXmGjingnfoyHY1I,11637
20
+ gohumanloop/providers/terminal_provider.py,sha256=L0whpmmMEzI3shV3nOFl4MrEAXEeL06OrUT0GOVzvl0,14663
21
21
  gohumanloop/utils/__init__.py,sha256=idlE5ZNCELVNF9WIiyhtyzG9HJuQQCOlKeTr2aHJ2-Q,56
22
22
  gohumanloop/utils/context_formatter.py,sha256=v4vdgKNJCHjnTtIMq83AkyXwltL14vx-D4KahwcZhIQ,2171
23
23
  gohumanloop/utils/threadsafedict.py,sha256=0-Pmre2-lqHkUPal9wSaqh3fLaEtbo-OnJ3Wbi_knWE,9601
24
- gohumanloop/utils/utils.py,sha256=NU-qA5eGuuBjp2SJ49F2V8Gt8Rm1uV3AKM79LUeIcFY,1347
25
- gohumanloop-0.0.2.dist-info/licenses/LICENSE,sha256=-U5tuCcSpndQwSKWtZbFbazb-_AtZcZL2kQgHbSLg-M,1064
26
- gohumanloop-0.0.2.dist-info/METADATA,sha256=dG9y3LZsTdc3pTonB1fXwx5XZjY1Cedfk3O8bAiHbFA,1557
27
- gohumanloop-0.0.2.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
28
- gohumanloop-0.0.2.dist-info/entry_points.txt,sha256=wM6jqRRD8bQXkvIduRVCuAJIlbyWg_F5EDXo5OZ_PwY,88
29
- gohumanloop-0.0.2.dist-info/top_level.txt,sha256=LvOXBqS6Mspmcuqp81uz0Vjx_m_YI0w06DOPCiI1BfY,12
30
- gohumanloop-0.0.2.dist-info/RECORD,,
24
+ gohumanloop/utils/utils.py,sha256=3f53fHdWLPve-WTn9mGiz3SB0CE7l39caC5Dz0hm85U,2167
25
+ gohumanloop-0.0.4.dist-info/licenses/LICENSE,sha256=-U5tuCcSpndQwSKWtZbFbazb-_AtZcZL2kQgHbSLg-M,1064
26
+ gohumanloop-0.0.4.dist-info/METADATA,sha256=mM0-czwhVde-9FdxCAsEgeuXhGxW6f3OxeaTc9sIjNA,1557
27
+ gohumanloop-0.0.4.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
28
+ gohumanloop-0.0.4.dist-info/entry_points.txt,sha256=wM6jqRRD8bQXkvIduRVCuAJIlbyWg_F5EDXo5OZ_PwY,88
29
+ gohumanloop-0.0.4.dist-info/top_level.txt,sha256=LvOXBqS6Mspmcuqp81uz0Vjx_m_YI0w06DOPCiI1BfY,12
30
+ gohumanloop-0.0.4.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.4.0)
2
+ Generator: setuptools (80.7.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5