tgdata 0.0.0__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.
- tgdata/__init__.py +57 -0
- tgdata/connection_engine.py +316 -0
- tgdata/message_engine.py +299 -0
- tgdata/models.py +69 -0
- tgdata/progress.py +105 -0
- tgdata/smoke_tests/__init__.py +1 -0
- tgdata/smoke_tests/my_test.py +149 -0
- tgdata/smoke_tests/test_00_connection.py +286 -0
- tgdata/smoke_tests/test_01_list_group_chats.py +408 -0
- tgdata/smoke_tests/test_02_get_message_count.py +73 -0
- tgdata/smoke_tests/test_03_get_all_messages.py +69 -0
- tgdata/smoke_tests/test_04_get_all_messages_in_batches.py +98 -0
- tgdata/smoke_tests/test_06_advanced_features.py +253 -0
- tgdata/smoke_tests/test_10_polling.py +244 -0
- tgdata/tgdata.py +554 -0
- tgdata/utils.py +247 -0
- tgdata-0.0.0.dist-info/METADATA +309 -0
- tgdata-0.0.0.dist-info/RECORD +20 -0
- tgdata-0.0.0.dist-info/WHEEL +5 -0
- tgdata-0.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Smoke tests for advanced TgData features
|
|
3
|
+
"""
|
|
4
|
+
# To run: python -m tgdata.smoke_tests.test_04_advanced_features
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import sys
|
|
8
|
+
import os
|
|
9
|
+
import tempfile
|
|
10
|
+
from datetime import datetime, timedelta
|
|
11
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
|
12
|
+
|
|
13
|
+
from tgdata import TgData
|
|
14
|
+
import pandas as pd
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
async def test_connection_pooling():
|
|
20
|
+
"""Test connection pooling features"""
|
|
21
|
+
print("\nTEST: Connection pooling...")
|
|
22
|
+
try:
|
|
23
|
+
# Test pool configuration
|
|
24
|
+
tg = TgData(connection_pool_size=5)
|
|
25
|
+
assert tg.connection_engine.pool_size == 5
|
|
26
|
+
print("✓ Connection pool size configured")
|
|
27
|
+
|
|
28
|
+
# Test health check structure
|
|
29
|
+
health = await tg.health_check()
|
|
30
|
+
assert isinstance(health, dict)
|
|
31
|
+
assert 'timestamp' in health
|
|
32
|
+
assert 'pool_connections' in health
|
|
33
|
+
print("✓ Health check includes pool information")
|
|
34
|
+
|
|
35
|
+
return True
|
|
36
|
+
except Exception as e:
|
|
37
|
+
print(f"✗ Connection pooling test failed: {e}")
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
async def test_progress_tracking():
|
|
42
|
+
"""Test progress tracking functionality"""
|
|
43
|
+
print("\nTEST: Progress tracking...")
|
|
44
|
+
try:
|
|
45
|
+
tg = TgData()
|
|
46
|
+
|
|
47
|
+
# Test that get_messages supports progress tracking
|
|
48
|
+
assert hasattr(tg, 'get_messages')
|
|
49
|
+
|
|
50
|
+
# Test progress callback structure
|
|
51
|
+
progress_data = []
|
|
52
|
+
|
|
53
|
+
def test_callback(current, total, rate):
|
|
54
|
+
progress_data.append({
|
|
55
|
+
'current': current,
|
|
56
|
+
'total': total,
|
|
57
|
+
'rate': rate
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
# We can't actually fetch messages without auth, but we can test the parameter acceptance
|
|
61
|
+
try:
|
|
62
|
+
await tg.get_messages(
|
|
63
|
+
group_id=12345,
|
|
64
|
+
limit=10,
|
|
65
|
+
with_progress=True,
|
|
66
|
+
progress_callback=test_callback
|
|
67
|
+
)
|
|
68
|
+
except:
|
|
69
|
+
# Expected to fail without auth
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
print("✓ Progress tracking parameters accepted")
|
|
73
|
+
|
|
74
|
+
return True
|
|
75
|
+
except Exception as e:
|
|
76
|
+
print(f"✗ Progress tracking test failed: {e}")
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
async def test_date_filtering():
|
|
81
|
+
"""Test date-based message filtering"""
|
|
82
|
+
print("\nTEST: Date filtering...")
|
|
83
|
+
try:
|
|
84
|
+
tg = TgData()
|
|
85
|
+
|
|
86
|
+
# Test filter_messages with date parameters
|
|
87
|
+
df = pd.DataFrame({
|
|
88
|
+
'MessageId': [1, 2, 3, 4, 5],
|
|
89
|
+
'Date': [
|
|
90
|
+
datetime.now() - timedelta(days=5),
|
|
91
|
+
datetime.now() - timedelta(days=3),
|
|
92
|
+
datetime.now() - timedelta(days=1),
|
|
93
|
+
datetime.now(),
|
|
94
|
+
datetime.now() + timedelta(days=1)
|
|
95
|
+
],
|
|
96
|
+
'Message': ['Old', 'Mid', 'Recent', 'Today', 'Future']
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
# Filter last 2 days
|
|
100
|
+
filtered = tg.filter_messages(
|
|
101
|
+
df,
|
|
102
|
+
start_date=datetime.now() - timedelta(days=2)
|
|
103
|
+
)
|
|
104
|
+
assert len(filtered) == 3 # Recent, Today, Future
|
|
105
|
+
print("✓ Start date filtering works")
|
|
106
|
+
|
|
107
|
+
# Filter with end date
|
|
108
|
+
filtered = tg.filter_messages(
|
|
109
|
+
df,
|
|
110
|
+
end_date=datetime.now()
|
|
111
|
+
)
|
|
112
|
+
assert len(filtered) == 4 # Excludes Future
|
|
113
|
+
print("✓ End date filtering works")
|
|
114
|
+
|
|
115
|
+
# Filter date range
|
|
116
|
+
filtered = tg.filter_messages(
|
|
117
|
+
df,
|
|
118
|
+
start_date=datetime.now() - timedelta(days=4),
|
|
119
|
+
end_date=datetime.now() - timedelta(days=2)
|
|
120
|
+
)
|
|
121
|
+
assert len(filtered) == 1 # Only Mid
|
|
122
|
+
print("✓ Date range filtering works")
|
|
123
|
+
|
|
124
|
+
return True
|
|
125
|
+
except Exception as e:
|
|
126
|
+
print(f"✗ Date filtering test failed: {e}")
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
async def test_message_caching():
|
|
131
|
+
"""Test message caching behavior"""
|
|
132
|
+
print("\nTEST: Message caching...")
|
|
133
|
+
try:
|
|
134
|
+
tg = TgData()
|
|
135
|
+
|
|
136
|
+
# Test cache clearing on group change
|
|
137
|
+
tg.set_group(12345)
|
|
138
|
+
# Add some fake cache data with proper key format
|
|
139
|
+
cache_key = '12345_test_key'
|
|
140
|
+
tg.message_engine._message_cache[cache_key] = pd.DataFrame([{'test': 'data'}])
|
|
141
|
+
assert cache_key in tg.message_engine._message_cache
|
|
142
|
+
|
|
143
|
+
# Change group should clear cache for previous group
|
|
144
|
+
tg.set_group(54321)
|
|
145
|
+
# Check that the cache for the old group is gone
|
|
146
|
+
assert cache_key not in tg.message_engine._message_cache
|
|
147
|
+
print("✓ Cache cleared on group change")
|
|
148
|
+
|
|
149
|
+
return True
|
|
150
|
+
except Exception as e:
|
|
151
|
+
print(f"✗ Message caching test failed: {e}")
|
|
152
|
+
import traceback
|
|
153
|
+
traceback.print_exc()
|
|
154
|
+
return False
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
async def test_metrics_and_logging():
|
|
158
|
+
"""Test metrics collection and logging"""
|
|
159
|
+
print("\nTEST: Metrics and logging...")
|
|
160
|
+
try:
|
|
161
|
+
# Test with log file
|
|
162
|
+
with tempfile.NamedTemporaryFile(suffix='.log', delete=False) as tmp_log:
|
|
163
|
+
tg = TgData(log_file=tmp_log.name)
|
|
164
|
+
|
|
165
|
+
# Perform some operations to generate logs
|
|
166
|
+
tg.set_group(12345)
|
|
167
|
+
|
|
168
|
+
# Check log file exists
|
|
169
|
+
assert os.path.exists(tmp_log.name)
|
|
170
|
+
print("✓ Log file created")
|
|
171
|
+
|
|
172
|
+
# Test metrics
|
|
173
|
+
metrics = await tg.get_metrics()
|
|
174
|
+
assert 'current_group' in metrics
|
|
175
|
+
assert metrics['current_group'] == 12345
|
|
176
|
+
assert 'message_cache_size' in metrics
|
|
177
|
+
print("✓ Metrics include cache and group info")
|
|
178
|
+
|
|
179
|
+
os.unlink(tmp_log.name)
|
|
180
|
+
|
|
181
|
+
return True
|
|
182
|
+
except Exception as e:
|
|
183
|
+
print(f"✗ Metrics and logging test failed: {e}")
|
|
184
|
+
return False
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
async def test_validation_methods():
|
|
188
|
+
"""Test connection validation methods"""
|
|
189
|
+
print("\nTEST: Validation methods...")
|
|
190
|
+
try:
|
|
191
|
+
tg = TgData()
|
|
192
|
+
|
|
193
|
+
# Test validate_connection
|
|
194
|
+
is_valid = await tg.validate_connection()
|
|
195
|
+
assert isinstance(is_valid, bool)
|
|
196
|
+
print(f"✓ Connection validation returns: {is_valid}")
|
|
197
|
+
|
|
198
|
+
# Test health check
|
|
199
|
+
health = await tg.health_check()
|
|
200
|
+
assert isinstance(health, dict)
|
|
201
|
+
assert 'primary_connection' in health
|
|
202
|
+
assert 'errors' in health
|
|
203
|
+
print("✓ Health check returns detailed status")
|
|
204
|
+
|
|
205
|
+
return True
|
|
206
|
+
except Exception as e:
|
|
207
|
+
print(f"✗ Validation methods test failed: {e}")
|
|
208
|
+
return False
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
async def main():
|
|
212
|
+
"""Run all advanced feature tests"""
|
|
213
|
+
print("Advanced TgData Features Tests")
|
|
214
|
+
print("=" * 50)
|
|
215
|
+
|
|
216
|
+
tests = [
|
|
217
|
+
test_connection_pooling,
|
|
218
|
+
test_progress_tracking,
|
|
219
|
+
test_date_filtering,
|
|
220
|
+
test_message_caching,
|
|
221
|
+
test_metrics_and_logging,
|
|
222
|
+
test_validation_methods
|
|
223
|
+
]
|
|
224
|
+
|
|
225
|
+
results = []
|
|
226
|
+
for test in tests:
|
|
227
|
+
try:
|
|
228
|
+
result = await test()
|
|
229
|
+
results.append(result)
|
|
230
|
+
except Exception as e:
|
|
231
|
+
print(f"✗ Test failed with error: {e}")
|
|
232
|
+
import traceback
|
|
233
|
+
traceback.print_exc()
|
|
234
|
+
results.append(False)
|
|
235
|
+
|
|
236
|
+
# Summary
|
|
237
|
+
print("\nSummary")
|
|
238
|
+
print("=" * 50)
|
|
239
|
+
passed = sum(results)
|
|
240
|
+
total = len(results)
|
|
241
|
+
print(f"Passed: {passed}/{total}")
|
|
242
|
+
|
|
243
|
+
if passed == total:
|
|
244
|
+
print("✓ All advanced feature tests passed!")
|
|
245
|
+
return 0
|
|
246
|
+
else:
|
|
247
|
+
print("✗ Some tests failed")
|
|
248
|
+
return 1
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
if __name__ == "__main__":
|
|
252
|
+
exit_code = asyncio.run(main())
|
|
253
|
+
sys.exit(exit_code)
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Smoke tests for polling and real-time message features
|
|
3
|
+
"""
|
|
4
|
+
# To run: python -m tgdata.smoke_tests.test_10_polling
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import sys
|
|
8
|
+
import os
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
|
11
|
+
|
|
12
|
+
from tgdata import TgData
|
|
13
|
+
import pandas as pd
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def test_polling_basic():
|
|
17
|
+
"""Test basic polling functionality"""
|
|
18
|
+
print("TEST: Basic polling functionality...")
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
# Initialize TgData
|
|
22
|
+
tg = TgData("config.ini")
|
|
23
|
+
|
|
24
|
+
# Get a test group
|
|
25
|
+
groups = await tg.list_groups()
|
|
26
|
+
if groups.empty:
|
|
27
|
+
print("✗ No groups available for testing")
|
|
28
|
+
return False
|
|
29
|
+
|
|
30
|
+
test_group = groups.iloc[0]
|
|
31
|
+
test_group_id = int(test_group['GroupID'])
|
|
32
|
+
print(f"Testing with group: {test_group['Title']} (ID: {test_group_id})")
|
|
33
|
+
|
|
34
|
+
# Define a callback to handle new messages
|
|
35
|
+
messages_received = []
|
|
36
|
+
|
|
37
|
+
async def message_callback(messages_df):
|
|
38
|
+
count = len(messages_df)
|
|
39
|
+
messages_received.append(count)
|
|
40
|
+
print(f" Received {count} new messages")
|
|
41
|
+
if not messages_df.empty:
|
|
42
|
+
# Show first message
|
|
43
|
+
first_msg = messages_df.iloc[0]
|
|
44
|
+
msg_text = first_msg.get('Message', 'No text')[:50]
|
|
45
|
+
print(f" First message: {msg_text}...")
|
|
46
|
+
|
|
47
|
+
# Get the latest message ID to start polling after
|
|
48
|
+
initial_messages = await tg.get_messages(group_id=test_group_id, limit=1)
|
|
49
|
+
start_after_id = 0
|
|
50
|
+
if not initial_messages.empty:
|
|
51
|
+
start_after_id = initial_messages['MessageId'].max()
|
|
52
|
+
print(f"Starting polling after message ID: {start_after_id}")
|
|
53
|
+
|
|
54
|
+
# Test polling with 3 iterations
|
|
55
|
+
print("\nPolling for 3 iterations (5 seconds each)...")
|
|
56
|
+
await tg.poll_for_messages(
|
|
57
|
+
group_id=test_group_id,
|
|
58
|
+
interval=5,
|
|
59
|
+
after_id=start_after_id,
|
|
60
|
+
callback=message_callback,
|
|
61
|
+
max_iterations=3
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
print(f"✓ Polling completed. Total polls that found messages: {len(messages_received)}")
|
|
65
|
+
await tg.close()
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
except Exception as e:
|
|
69
|
+
print(f"✗ Polling test failed: {e}")
|
|
70
|
+
import traceback
|
|
71
|
+
traceback.print_exc()
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
async def test_event_handler():
|
|
76
|
+
"""Test real-time event handler"""
|
|
77
|
+
print("\nTEST: Real-time event handler...")
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
# Initialize TgData
|
|
81
|
+
tg = TgData("config.ini")
|
|
82
|
+
|
|
83
|
+
# Get a test group
|
|
84
|
+
groups = await tg.list_groups()
|
|
85
|
+
if groups.empty:
|
|
86
|
+
print("✗ No groups available for testing")
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
test_group = groups.iloc[0]
|
|
90
|
+
test_group_id = int(test_group['GroupID'])
|
|
91
|
+
print(f"Testing with group: {test_group['Title']} (ID: {test_group_id})")
|
|
92
|
+
|
|
93
|
+
# Track received messages
|
|
94
|
+
received_events = []
|
|
95
|
+
|
|
96
|
+
# Register event handler
|
|
97
|
+
@tg.on_new_message(group_id=test_group_id)
|
|
98
|
+
async def message_handler(event):
|
|
99
|
+
received_events.append(event)
|
|
100
|
+
print(f" Real-time message from {event.sender_id}: {event.message.text[:50]}...")
|
|
101
|
+
|
|
102
|
+
# Give time for handler to register
|
|
103
|
+
await asyncio.sleep(1)
|
|
104
|
+
|
|
105
|
+
print("✓ Event handler registered successfully")
|
|
106
|
+
print("Note: To fully test real-time events, you would need to:")
|
|
107
|
+
print(" 1. Call await tg.run_with_event_loop() to start listening")
|
|
108
|
+
print(" 2. Send messages to the group from another account")
|
|
109
|
+
print(" 3. See the messages appear in real-time")
|
|
110
|
+
|
|
111
|
+
await tg.close()
|
|
112
|
+
return True
|
|
113
|
+
|
|
114
|
+
except Exception as e:
|
|
115
|
+
print(f"✗ Event handler test failed: {e}")
|
|
116
|
+
import traceback
|
|
117
|
+
traceback.print_exc()
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
async def test_polling_error_handling():
|
|
122
|
+
"""Test polling error recovery"""
|
|
123
|
+
print("\nTEST: Polling error recovery...")
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
tg = TgData("config.ini")
|
|
127
|
+
|
|
128
|
+
# Test with invalid group ID
|
|
129
|
+
error_count = []
|
|
130
|
+
|
|
131
|
+
async def error_callback(messages_df):
|
|
132
|
+
# This shouldn't be called for invalid group
|
|
133
|
+
error_count.append(1)
|
|
134
|
+
|
|
135
|
+
print("Testing polling with invalid group ID...")
|
|
136
|
+
|
|
137
|
+
# Create a task for polling (it will fail but should handle errors gracefully)
|
|
138
|
+
poll_task = asyncio.create_task(
|
|
139
|
+
tg.poll_for_messages(
|
|
140
|
+
group_id=999999999, # Invalid group ID
|
|
141
|
+
interval=2,
|
|
142
|
+
callback=error_callback,
|
|
143
|
+
max_iterations=2
|
|
144
|
+
)
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Wait for polling to complete
|
|
148
|
+
await poll_task
|
|
149
|
+
|
|
150
|
+
print("✓ Polling handled errors gracefully")
|
|
151
|
+
await tg.close()
|
|
152
|
+
return True
|
|
153
|
+
|
|
154
|
+
except Exception as e:
|
|
155
|
+
print(f"✗ Error handling test failed: {e}")
|
|
156
|
+
import traceback
|
|
157
|
+
traceback.print_exc()
|
|
158
|
+
return False
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
async def test_multiple_handlers():
|
|
162
|
+
"""Test multiple event handlers"""
|
|
163
|
+
print("\nTEST: Multiple event handlers...")
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
tg = TgData("config.ini")
|
|
167
|
+
|
|
168
|
+
# Get test groups
|
|
169
|
+
groups = await tg.list_groups()
|
|
170
|
+
if len(groups) < 2:
|
|
171
|
+
print("! Need at least 2 groups for this test, skipping")
|
|
172
|
+
await tg.close()
|
|
173
|
+
return True
|
|
174
|
+
|
|
175
|
+
group1_id = int(groups.iloc[0]['GroupID'])
|
|
176
|
+
group2_id = int(groups.iloc[1]['GroupID'])
|
|
177
|
+
|
|
178
|
+
# Register handler for specific group
|
|
179
|
+
@tg.on_new_message(group_id=group1_id)
|
|
180
|
+
async def group1_handler(event):
|
|
181
|
+
print(f" Group 1 message: {event.message.text[:30]}...")
|
|
182
|
+
|
|
183
|
+
# Register handler for all groups
|
|
184
|
+
@tg.on_new_message()
|
|
185
|
+
async def all_groups_handler(event):
|
|
186
|
+
print(f" Any group message from {event.chat_id}: {event.message.text[:30]}...")
|
|
187
|
+
|
|
188
|
+
await asyncio.sleep(1)
|
|
189
|
+
|
|
190
|
+
print(f"✓ Registered handler for group {group1_id}")
|
|
191
|
+
print("✓ Registered handler for all groups")
|
|
192
|
+
|
|
193
|
+
await tg.close()
|
|
194
|
+
return True
|
|
195
|
+
|
|
196
|
+
except Exception as e:
|
|
197
|
+
print(f"✗ Multiple handlers test failed: {e}")
|
|
198
|
+
import traceback
|
|
199
|
+
traceback.print_exc()
|
|
200
|
+
return False
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
async def main():
|
|
204
|
+
"""Run all polling tests"""
|
|
205
|
+
print("Polling and Real-time Features Tests")
|
|
206
|
+
print("=" * 50)
|
|
207
|
+
|
|
208
|
+
tests = [
|
|
209
|
+
test_polling_basic,
|
|
210
|
+
test_event_handler,
|
|
211
|
+
test_polling_error_handling,
|
|
212
|
+
test_multiple_handlers
|
|
213
|
+
]
|
|
214
|
+
|
|
215
|
+
results = []
|
|
216
|
+
for test in tests:
|
|
217
|
+
try:
|
|
218
|
+
result = await test()
|
|
219
|
+
results.append(result)
|
|
220
|
+
await asyncio.sleep(0.5)
|
|
221
|
+
except Exception as e:
|
|
222
|
+
print(f"✗ Test failed with error: {e}")
|
|
223
|
+
import traceback
|
|
224
|
+
traceback.print_exc()
|
|
225
|
+
results.append(False)
|
|
226
|
+
|
|
227
|
+
# Summary
|
|
228
|
+
print("\nSummary")
|
|
229
|
+
print("=" * 50)
|
|
230
|
+
passed = sum(results)
|
|
231
|
+
total = len(results)
|
|
232
|
+
print(f"Passed: {passed}/{total}")
|
|
233
|
+
|
|
234
|
+
if passed == total:
|
|
235
|
+
print("✓ All polling tests passed!")
|
|
236
|
+
return 0
|
|
237
|
+
else:
|
|
238
|
+
print("✗ Some tests failed")
|
|
239
|
+
return 1
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
if __name__ == "__main__":
|
|
243
|
+
exit_code = asyncio.run(main())
|
|
244
|
+
sys.exit(exit_code)
|