titangpt 0.1.5__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.
- titangpt-0.1.5/PKG-INFO +32 -0
- titangpt-0.1.5/README.md +3 -0
- titangpt-0.1.5/setup.cfg +4 -0
- titangpt-0.1.5/setup.py +32 -0
- titangpt-0.1.5/tests/test_async_client.py +373 -0
- titangpt-0.1.5/tests/test_client.py +683 -0
- titangpt-0.1.5/titangpt/__init__.py +50 -0
- titangpt-0.1.5/titangpt/async_client.py +141 -0
- titangpt-0.1.5/titangpt/client.py +144 -0
- titangpt-0.1.5/titangpt/exceptions.py +76 -0
- titangpt-0.1.5/titangpt/types.py +124 -0
- titangpt-0.1.5/titangpt.egg-info/PKG-INFO +32 -0
- titangpt-0.1.5/titangpt.egg-info/SOURCES.txt +14 -0
- titangpt-0.1.5/titangpt.egg-info/dependency_links.txt +1 -0
- titangpt-0.1.5/titangpt.egg-info/requires.txt +2 -0
- titangpt-0.1.5/titangpt.egg-info/top_level.txt +1 -0
titangpt-0.1.5/PKG-INFO
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: titangpt
|
|
3
|
+
Version: 0.1.5
|
|
4
|
+
Summary: Official Python client for TitanGPT API
|
|
5
|
+
Home-page: https://github.com/TitanGPT/titangpt
|
|
6
|
+
Author: TitanGPT
|
|
7
|
+
Author-email: info@titangpt.ru
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Requires-Python: >=3.7
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
Requires-Dist: requests>=2.28.0
|
|
19
|
+
Requires-Dist: aiohttp>=3.8.0
|
|
20
|
+
Dynamic: author
|
|
21
|
+
Dynamic: author-email
|
|
22
|
+
Dynamic: classifier
|
|
23
|
+
Dynamic: description
|
|
24
|
+
Dynamic: description-content-type
|
|
25
|
+
Dynamic: home-page
|
|
26
|
+
Dynamic: requires-dist
|
|
27
|
+
Dynamic: requires-python
|
|
28
|
+
Dynamic: summary
|
|
29
|
+
|
|
30
|
+
# TitanGPT.ru Python Client
|
|
31
|
+
|
|
32
|
+
Official Python client for TitanGPT.ru API - OpenAI compatible interface.
|
titangpt-0.1.5/README.md
ADDED
titangpt-0.1.5/setup.cfg
ADDED
titangpt-0.1.5/setup.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
long_description = Path("README.md").read_text(encoding="utf-8")
|
|
5
|
+
|
|
6
|
+
setup(
|
|
7
|
+
name="titangpt",
|
|
8
|
+
version="0.1.5",
|
|
9
|
+
author="TitanGPT",
|
|
10
|
+
author_email="info@titangpt.ru",
|
|
11
|
+
description="Official Python client for TitanGPT API",
|
|
12
|
+
long_description=long_description,
|
|
13
|
+
long_description_content_type="text/markdown",
|
|
14
|
+
url="https://github.com/TitanGPT/titangpt",
|
|
15
|
+
packages=find_packages(),
|
|
16
|
+
classifiers=[
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.7",
|
|
19
|
+
"Programming Language :: Python :: 3.8",
|
|
20
|
+
"Programming Language :: Python :: 3.9",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"License :: OSI Approved :: MIT License",
|
|
24
|
+
"Operating System :: OS Independent",
|
|
25
|
+
],
|
|
26
|
+
python_requires=">=3.7",
|
|
27
|
+
install_requires=[
|
|
28
|
+
"requests>=2.28.0",
|
|
29
|
+
"aiohttp>=3.8.0",
|
|
30
|
+
],
|
|
31
|
+
include_package_data=True,
|
|
32
|
+
)
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Asynchronous client tests for TitanGPT.
|
|
3
|
+
|
|
4
|
+
This module contains comprehensive tests for the asynchronous client functionality,
|
|
5
|
+
including connection handling, request/response processing, error handling, and
|
|
6
|
+
concurrent operations.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import pytest
|
|
11
|
+
from unittest.mock import AsyncMock, MagicMock, patch
|
|
12
|
+
from typing import Any, Dict, List
|
|
13
|
+
|
|
14
|
+
# Import async client and related components
|
|
15
|
+
# Adjust imports based on your actual project structure
|
|
16
|
+
try:
|
|
17
|
+
from titangpt.async_client import AsyncClient
|
|
18
|
+
from titangpt.exceptions import (
|
|
19
|
+
TitanGPTException,
|
|
20
|
+
ConnectionError as TitanConnectionError,
|
|
21
|
+
TimeoutError as TitanTimeoutError,
|
|
22
|
+
AuthenticationError,
|
|
23
|
+
)
|
|
24
|
+
except ImportError:
|
|
25
|
+
# Fallback for testing structure
|
|
26
|
+
AsyncClient = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.mark.asyncio
|
|
30
|
+
class TestAsyncClientInitialization:
|
|
31
|
+
"""Tests for AsyncClient initialization and configuration."""
|
|
32
|
+
|
|
33
|
+
@pytest.mark.asyncio
|
|
34
|
+
async def test_client_initialization(self):
|
|
35
|
+
"""Test basic client initialization."""
|
|
36
|
+
if AsyncClient is None:
|
|
37
|
+
pytest.skip("AsyncClient not available")
|
|
38
|
+
|
|
39
|
+
client = AsyncClient(api_key="test_key")
|
|
40
|
+
assert client is not None
|
|
41
|
+
await client.close()
|
|
42
|
+
|
|
43
|
+
@pytest.mark.asyncio
|
|
44
|
+
async def test_client_with_custom_config(self):
|
|
45
|
+
"""Test client initialization with custom configuration."""
|
|
46
|
+
if AsyncClient is None:
|
|
47
|
+
pytest.skip("AsyncClient not available")
|
|
48
|
+
|
|
49
|
+
config = {
|
|
50
|
+
"timeout": 30,
|
|
51
|
+
"max_retries": 3,
|
|
52
|
+
"base_url": "https://api.example.com",
|
|
53
|
+
}
|
|
54
|
+
client = AsyncClient(api_key="test_key", **config)
|
|
55
|
+
assert client is not None
|
|
56
|
+
await client.close()
|
|
57
|
+
|
|
58
|
+
@pytest.mark.asyncio
|
|
59
|
+
async def test_client_missing_api_key(self):
|
|
60
|
+
"""Test client initialization without API key."""
|
|
61
|
+
if AsyncClient is None:
|
|
62
|
+
pytest.skip("AsyncClient not available")
|
|
63
|
+
|
|
64
|
+
with pytest.raises((ValueError, TypeError)):
|
|
65
|
+
AsyncClient()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@pytest.mark.asyncio
|
|
69
|
+
class TestAsyncClientRequests:
|
|
70
|
+
"""Tests for async client request handling."""
|
|
71
|
+
|
|
72
|
+
@pytest.fixture
|
|
73
|
+
async def client(self):
|
|
74
|
+
"""Provide an async client instance."""
|
|
75
|
+
if AsyncClient is None:
|
|
76
|
+
pytest.skip("AsyncClient not available")
|
|
77
|
+
|
|
78
|
+
client = AsyncClient(api_key="test_key")
|
|
79
|
+
yield client
|
|
80
|
+
await client.close()
|
|
81
|
+
|
|
82
|
+
@pytest.mark.asyncio
|
|
83
|
+
async def test_simple_request(self, client):
|
|
84
|
+
"""Test making a simple asynchronous request."""
|
|
85
|
+
with patch.object(client, '_send_request', new_callable=AsyncMock) as mock_send:
|
|
86
|
+
mock_send.return_value = {"status": "success", "data": "test_response"}
|
|
87
|
+
|
|
88
|
+
# Assuming a method like query() exists
|
|
89
|
+
if hasattr(client, 'query'):
|
|
90
|
+
result = await client.query("test prompt")
|
|
91
|
+
assert result is not None
|
|
92
|
+
mock_send.assert_called_once()
|
|
93
|
+
|
|
94
|
+
@pytest.mark.asyncio
|
|
95
|
+
async def test_request_with_parameters(self, client):
|
|
96
|
+
"""Test request with multiple parameters."""
|
|
97
|
+
with patch.object(client, '_send_request', new_callable=AsyncMock) as mock_send:
|
|
98
|
+
mock_send.return_value = {"status": "success", "data": "response"}
|
|
99
|
+
|
|
100
|
+
params = {
|
|
101
|
+
"temperature": 0.7,
|
|
102
|
+
"max_tokens": 100,
|
|
103
|
+
"top_p": 0.9,
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if hasattr(client, 'query'):
|
|
107
|
+
result = await client.query("prompt", **params)
|
|
108
|
+
assert result is not None
|
|
109
|
+
|
|
110
|
+
@pytest.mark.asyncio
|
|
111
|
+
async def test_concurrent_requests(self, client):
|
|
112
|
+
"""Test handling multiple concurrent requests."""
|
|
113
|
+
with patch.object(client, '_send_request', new_callable=AsyncMock) as mock_send:
|
|
114
|
+
mock_send.return_value = {"status": "success", "data": "response"}
|
|
115
|
+
|
|
116
|
+
if hasattr(client, 'query'):
|
|
117
|
+
tasks = [
|
|
118
|
+
client.query(f"prompt {i}")
|
|
119
|
+
for i in range(5)
|
|
120
|
+
]
|
|
121
|
+
results = await asyncio.gather(*tasks)
|
|
122
|
+
assert len(results) == 5
|
|
123
|
+
assert mock_send.call_count == 5
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@pytest.mark.asyncio
|
|
127
|
+
class TestAsyncClientErrorHandling:
|
|
128
|
+
"""Tests for error handling in async client."""
|
|
129
|
+
|
|
130
|
+
@pytest.fixture
|
|
131
|
+
async def client(self):
|
|
132
|
+
"""Provide an async client instance."""
|
|
133
|
+
if AsyncClient is None:
|
|
134
|
+
pytest.skip("AsyncClient not available")
|
|
135
|
+
|
|
136
|
+
client = AsyncClient(api_key="test_key")
|
|
137
|
+
yield client
|
|
138
|
+
await client.close()
|
|
139
|
+
|
|
140
|
+
@pytest.mark.asyncio
|
|
141
|
+
async def test_connection_error_handling(self, client):
|
|
142
|
+
"""Test handling of connection errors."""
|
|
143
|
+
with patch.object(client, '_send_request', new_callable=AsyncMock) as mock_send:
|
|
144
|
+
mock_send.side_effect = TitanConnectionError("Connection failed")
|
|
145
|
+
|
|
146
|
+
if hasattr(client, 'query'):
|
|
147
|
+
with pytest.raises((TitanConnectionError, Exception)):
|
|
148
|
+
await client.query("test prompt")
|
|
149
|
+
|
|
150
|
+
@pytest.mark.asyncio
|
|
151
|
+
async def test_timeout_handling(self, client):
|
|
152
|
+
"""Test handling of timeout errors."""
|
|
153
|
+
with patch.object(client, '_send_request', new_callable=AsyncMock) as mock_send:
|
|
154
|
+
mock_send.side_effect = TitanTimeoutError("Request timeout")
|
|
155
|
+
|
|
156
|
+
if hasattr(client, 'query'):
|
|
157
|
+
with pytest.raises((TitanTimeoutError, Exception, asyncio.TimeoutError)):
|
|
158
|
+
await client.query("test prompt")
|
|
159
|
+
|
|
160
|
+
@pytest.mark.asyncio
|
|
161
|
+
async def test_authentication_error(self, client):
|
|
162
|
+
"""Test handling of authentication errors."""
|
|
163
|
+
with patch.object(client, '_send_request', new_callable=AsyncMock) as mock_send:
|
|
164
|
+
mock_send.side_effect = AuthenticationError("Invalid API key")
|
|
165
|
+
|
|
166
|
+
if hasattr(client, 'query'):
|
|
167
|
+
with pytest.raises((AuthenticationError, Exception)):
|
|
168
|
+
await client.query("test prompt")
|
|
169
|
+
|
|
170
|
+
@pytest.mark.asyncio
|
|
171
|
+
async def test_rate_limit_error(self, client):
|
|
172
|
+
"""Test handling of rate limit errors."""
|
|
173
|
+
with patch.object(client, '_send_request', new_callable=AsyncMock) as mock_send:
|
|
174
|
+
mock_send.side_effect = TitanGPTException("Rate limit exceeded")
|
|
175
|
+
|
|
176
|
+
if hasattr(client, 'query'):
|
|
177
|
+
with pytest.raises((TitanGPTException, Exception)):
|
|
178
|
+
await client.query("test prompt")
|
|
179
|
+
|
|
180
|
+
@pytest.mark.asyncio
|
|
181
|
+
async def test_retry_on_failure(self, client):
|
|
182
|
+
"""Test retry mechanism on transient failures."""
|
|
183
|
+
with patch.object(client, '_send_request', new_callable=AsyncMock) as mock_send:
|
|
184
|
+
# First call fails, second succeeds
|
|
185
|
+
mock_send.side_effect = [
|
|
186
|
+
TitanTimeoutError("Timeout"),
|
|
187
|
+
{"status": "success", "data": "response"}
|
|
188
|
+
]
|
|
189
|
+
|
|
190
|
+
if hasattr(client, 'query') and hasattr(client, '_retry'):
|
|
191
|
+
# This assumes the client has retry logic
|
|
192
|
+
try:
|
|
193
|
+
result = await client.query("test prompt")
|
|
194
|
+
assert result is not None
|
|
195
|
+
except Exception:
|
|
196
|
+
pass # Expected if retry not implemented
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@pytest.mark.asyncio
|
|
200
|
+
class TestAsyncClientContextManager:
|
|
201
|
+
"""Tests for async context manager functionality."""
|
|
202
|
+
|
|
203
|
+
@pytest.mark.asyncio
|
|
204
|
+
async def test_context_manager_usage(self):
|
|
205
|
+
"""Test using client as async context manager."""
|
|
206
|
+
if AsyncClient is None:
|
|
207
|
+
pytest.skip("AsyncClient not available")
|
|
208
|
+
|
|
209
|
+
async with AsyncClient(api_key="test_key") as client:
|
|
210
|
+
assert client is not None
|
|
211
|
+
|
|
212
|
+
@pytest.mark.asyncio
|
|
213
|
+
async def test_context_manager_cleanup(self):
|
|
214
|
+
"""Test that cleanup happens when exiting context."""
|
|
215
|
+
if AsyncClient is None:
|
|
216
|
+
pytest.skip("AsyncClient not available")
|
|
217
|
+
|
|
218
|
+
with patch('titangpt.async_client.AsyncClient.close', new_callable=AsyncMock) as mock_close:
|
|
219
|
+
try:
|
|
220
|
+
async with AsyncClient(api_key="test_key") as client:
|
|
221
|
+
assert client is not None
|
|
222
|
+
except Exception:
|
|
223
|
+
pass # May fail if __aenter__/__aexit__ not implemented
|
|
224
|
+
|
|
225
|
+
# close() should be called on exit
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@pytest.mark.asyncio
|
|
229
|
+
class TestAsyncClientStreaming:
|
|
230
|
+
"""Tests for streaming responses in async client."""
|
|
231
|
+
|
|
232
|
+
@pytest.fixture
|
|
233
|
+
async def client(self):
|
|
234
|
+
"""Provide an async client instance."""
|
|
235
|
+
if AsyncClient is None:
|
|
236
|
+
pytest.skip("AsyncClient not available")
|
|
237
|
+
|
|
238
|
+
client = AsyncClient(api_key="test_key")
|
|
239
|
+
yield client
|
|
240
|
+
await client.close()
|
|
241
|
+
|
|
242
|
+
@pytest.mark.asyncio
|
|
243
|
+
async def test_stream_response(self, client):
|
|
244
|
+
"""Test streaming response handling."""
|
|
245
|
+
if not hasattr(client, 'stream'):
|
|
246
|
+
pytest.skip("Streaming not implemented")
|
|
247
|
+
|
|
248
|
+
async def mock_stream():
|
|
249
|
+
for chunk in ["Hello", " ", "World"]:
|
|
250
|
+
yield chunk
|
|
251
|
+
|
|
252
|
+
with patch.object(client, 'stream', new_callable=AsyncMock) as mock_stream_method:
|
|
253
|
+
mock_stream_method.return_value = mock_stream()
|
|
254
|
+
|
|
255
|
+
chunks = []
|
|
256
|
+
async for chunk in await mock_stream_method("test prompt"):
|
|
257
|
+
chunks.append(chunk)
|
|
258
|
+
|
|
259
|
+
assert len(chunks) > 0
|
|
260
|
+
|
|
261
|
+
@pytest.mark.asyncio
|
|
262
|
+
async def test_stream_with_error(self, client):
|
|
263
|
+
"""Test error handling in streaming."""
|
|
264
|
+
if not hasattr(client, 'stream'):
|
|
265
|
+
pytest.skip("Streaming not implemented")
|
|
266
|
+
|
|
267
|
+
async def mock_stream_with_error():
|
|
268
|
+
yield "chunk1"
|
|
269
|
+
raise TitanGPTException("Stream error")
|
|
270
|
+
|
|
271
|
+
with patch.object(client, 'stream', new_callable=AsyncMock) as mock_stream_method:
|
|
272
|
+
mock_stream_method.return_value = mock_stream_with_error()
|
|
273
|
+
|
|
274
|
+
with pytest.raises(TitanGPTException):
|
|
275
|
+
async for chunk in await mock_stream_method("test prompt"):
|
|
276
|
+
pass
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
@pytest.mark.asyncio
|
|
280
|
+
class TestAsyncClientResourceManagement:
|
|
281
|
+
"""Tests for resource management in async client."""
|
|
282
|
+
|
|
283
|
+
@pytest.mark.asyncio
|
|
284
|
+
async def test_connection_pool_management(self):
|
|
285
|
+
"""Test that connection pools are properly managed."""
|
|
286
|
+
if AsyncClient is None:
|
|
287
|
+
pytest.skip("AsyncClient not available")
|
|
288
|
+
|
|
289
|
+
client = AsyncClient(api_key="test_key")
|
|
290
|
+
|
|
291
|
+
# Verify client has necessary session management
|
|
292
|
+
assert hasattr(client, 'close') or hasattr(client, '__aexit__')
|
|
293
|
+
|
|
294
|
+
await client.close()
|
|
295
|
+
|
|
296
|
+
@pytest.mark.asyncio
|
|
297
|
+
async def test_multiple_client_instances(self):
|
|
298
|
+
"""Test creating multiple independent client instances."""
|
|
299
|
+
if AsyncClient is None:
|
|
300
|
+
pytest.skip("AsyncClient not available")
|
|
301
|
+
|
|
302
|
+
clients = [AsyncClient(api_key=f"key_{i}") for i in range(3)]
|
|
303
|
+
|
|
304
|
+
assert len(clients) == 3
|
|
305
|
+
|
|
306
|
+
for client in clients:
|
|
307
|
+
await client.close()
|
|
308
|
+
|
|
309
|
+
@pytest.mark.asyncio
|
|
310
|
+
async def test_client_reusability(self):
|
|
311
|
+
"""Test that a single client can handle multiple requests."""
|
|
312
|
+
if AsyncClient is None:
|
|
313
|
+
pytest.skip("AsyncClient not available")
|
|
314
|
+
|
|
315
|
+
client = AsyncClient(api_key="test_key")
|
|
316
|
+
|
|
317
|
+
with patch.object(client, '_send_request', new_callable=AsyncMock) as mock_send:
|
|
318
|
+
mock_send.return_value = {"status": "success"}
|
|
319
|
+
|
|
320
|
+
if hasattr(client, 'query'):
|
|
321
|
+
for i in range(5):
|
|
322
|
+
try:
|
|
323
|
+
await client.query(f"prompt {i}")
|
|
324
|
+
except Exception:
|
|
325
|
+
pass
|
|
326
|
+
|
|
327
|
+
await client.close()
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
@pytest.mark.asyncio
|
|
331
|
+
class TestAsyncClientConfiguration:
|
|
332
|
+
"""Tests for client configuration and customization."""
|
|
333
|
+
|
|
334
|
+
@pytest.mark.asyncio
|
|
335
|
+
async def test_custom_timeout(self):
|
|
336
|
+
"""Test setting custom timeout."""
|
|
337
|
+
if AsyncClient is None:
|
|
338
|
+
pytest.skip("AsyncClient not available")
|
|
339
|
+
|
|
340
|
+
client = AsyncClient(api_key="test_key", timeout=60)
|
|
341
|
+
await client.close()
|
|
342
|
+
|
|
343
|
+
@pytest.mark.asyncio
|
|
344
|
+
async def test_custom_headers(self):
|
|
345
|
+
"""Test setting custom headers."""
|
|
346
|
+
if AsyncClient is None:
|
|
347
|
+
pytest.skip("AsyncClient not available")
|
|
348
|
+
|
|
349
|
+
headers = {"X-Custom-Header": "value"}
|
|
350
|
+
client = AsyncClient(api_key="test_key", headers=headers)
|
|
351
|
+
await client.close()
|
|
352
|
+
|
|
353
|
+
@pytest.mark.asyncio
|
|
354
|
+
async def test_proxy_configuration(self):
|
|
355
|
+
"""Test proxy configuration."""
|
|
356
|
+
if AsyncClient is None:
|
|
357
|
+
pytest.skip("AsyncClient not available")
|
|
358
|
+
|
|
359
|
+
client = AsyncClient(api_key="test_key", proxy="http://proxy.example.com:8080")
|
|
360
|
+
await client.close()
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
# Async fixture for pytest-asyncio
|
|
364
|
+
@pytest.fixture
|
|
365
|
+
def event_loop():
|
|
366
|
+
"""Create an event loop for async tests."""
|
|
367
|
+
loop = asyncio.get_event_loop_policy().new_event_loop()
|
|
368
|
+
yield loop
|
|
369
|
+
loop.close()
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
if __name__ == "__main__":
|
|
373
|
+
pytest.main([__file__, "-v"])
|