aicosmos-client 0.0.5__py3-none-any.whl → 0.0.6__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.
aicosmos_client/client.py CHANGED
@@ -1,16 +1,91 @@
1
+ import os
2
+ import socket
3
+ import ssl
4
+ from urllib.parse import urlparse
5
+
6
+ import certifi
1
7
  import requests
8
+ from requests.adapters import HTTPAdapter
9
+
10
+
11
+ class SSLAdapter(HTTPAdapter):
12
+ """HTTPS adapter that allows dynamic CA bundle injection."""
13
+
14
+ def __init__(self, cafile=None, *args, **kwargs):
15
+ self.cafile = cafile
16
+ super().__init__(*args, **kwargs)
17
+
18
+ def init_poolmanager(self, *args, **kwargs):
19
+ context = ssl.create_default_context()
20
+ context.load_verify_locations(cafile=certifi.where())
21
+ if self.cafile and os.path.exists(self.cafile):
22
+ context.load_verify_locations(cafile=self.cafile)
23
+ kwargs["ssl_context"] = context
24
+ return super().init_poolmanager(*args, **kwargs)
2
25
 
3
26
 
4
27
  class AICosmosClient:
5
- def __init__(self, base_url: str, username: str, password: str):
6
- self.session = requests.Session()
7
- self.base_url = base_url
8
- self.username: str = username
9
- self.password: str = password
28
+ def __init__(
29
+ self,
30
+ base_url: str,
31
+ username: str,
32
+ password: str,
33
+ certs_dir: str = None,
34
+ auto_trust: bool = False,
35
+ ):
36
+ """
37
+ :param base_url: API base URL, e.g. 'https://aicosmos.ai/api'
38
+ :param username: Username for login
39
+ :param password: Password for login
40
+ :param certs_dir: Directory for storing trusted self-signed certs
41
+ :param auto_trust: If True, will automatically trust self-signed certs
42
+ """
43
+ self.base_url = base_url.rstrip("/")
44
+ self.username = username
45
+ self.password = password
10
46
  self.access_token: str = None
47
+ self.auto_trust = auto_trust
48
+
49
+ host = urlparse(self.base_url).hostname
50
+ self.certs_dir = certs_dir or os.path.join(
51
+ os.path.expanduser("~"), ".aicosmos", "certs"
52
+ )
53
+ os.makedirs(self.certs_dir, exist_ok=True)
54
+ self.cert_file = os.path.join(self.certs_dir, f"{host}.pem")
55
+
56
+ self.session = requests.Session()
57
+ self.session.mount("https://", SSLAdapter(cafile=self.cert_file))
11
58
 
12
59
  self._login()
13
60
 
61
+ def _fetch_server_cert(self, hostname, port=443):
62
+ """Download server's SSL certificate and save locally."""
63
+ pem_path = self.cert_file
64
+ conn = socket.create_connection((hostname, port))
65
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
66
+ context.check_hostname = False
67
+ context.verify_mode = ssl.CERT_NONE
68
+ with context.wrap_socket(conn, server_hostname=hostname) as sock:
69
+ der_cert = sock.getpeercert(True)
70
+ pem_cert = ssl.DER_cert_to_PEM_cert(der_cert)
71
+ with open(pem_path, "w") as f:
72
+ f.write(pem_cert)
73
+ return pem_path
74
+
75
+ def _robust_request(self, method, url, **kwargs):
76
+ try:
77
+ return self.session.request(method, url, **kwargs)
78
+ except requests.exceptions.SSLError as e:
79
+ if not self.auto_trust:
80
+ raise RuntimeError(
81
+ f"SSL verification failed for {url}. "
82
+ f"Set auto_trust=True to accept and store the server's certificate."
83
+ ) from e
84
+ host = urlparse(self.base_url).hostname
85
+ self._fetch_server_cert(host)
86
+ self.session.mount("https://", SSLAdapter(cafile=self.cert_file))
87
+ return self.session.request(method, url, **kwargs)
88
+
14
89
  def _login(self):
15
90
  login_data = {
16
91
  "username": self.username,
@@ -18,18 +93,14 @@ class AICosmosClient:
18
93
  "grant_type": "password",
19
94
  }
20
95
  headers = {"Content-Type": "application/x-www-form-urlencoded"}
21
- try:
22
- response = self.session.post(
23
- f"{self.base_url}/user/login", data=login_data, headers=headers
24
- )
25
- if response.status_code == 200:
26
- token_data = response.json()
27
- self.access_token = token_data["access_token"]
28
- return
29
- else:
30
- raise ValueError(f"Status code: {response.status_code}")
31
- except Exception as e:
32
- raise ValueError(f"Error: {e}")
96
+ response = self._robust_request(
97
+ "POST", f"{self.base_url}/user/login", data=login_data, headers=headers
98
+ )
99
+ if response.status_code == 200:
100
+ token_data = response.json()
101
+ self.access_token = token_data["access_token"]
102
+ else:
103
+ raise ValueError(f"Login failed: {response.status_code} {response.text}")
33
104
 
34
105
  def _get_auth_headers(self):
35
106
  if not self.access_token:
@@ -40,93 +111,68 @@ class AICosmosClient:
40
111
  }
41
112
 
42
113
  def _get_session_status(self, session_id):
43
- try:
44
- response = self.session.get(
45
- f"{self.base_url}/sessions/{session_id}/status",
46
- headers=self._get_auth_headers(),
47
- )
48
- success = response.status_code == 200
49
- if success:
50
- return response.json()
51
- else:
52
- raise ValueError(f"Status code: {response.status_code}")
53
- except Exception as e:
54
- raise ValueError(f"Error: {e}")
114
+ response = self._robust_request(
115
+ "GET",
116
+ f"{self.base_url}/sessions/{session_id}/status",
117
+ headers=self._get_auth_headers(),
118
+ )
119
+ if response.status_code == 200:
120
+ return response.json()
121
+ else:
122
+ raise ValueError(f"Status code: {response.status_code}")
55
123
 
56
124
  def create_session(self):
57
- if not self.access_token:
58
- raise ValueError("Not logged in")
59
- try:
60
- response = self.session.post(
61
- f"{self.base_url}/sessions/create", headers=self._get_auth_headers()
62
- )
63
- if response.status_code == 200:
64
- response_json = response.json()
65
- return response_json["session_id"]
66
- else:
67
- raise ValueError(f"Status code: {response.status_code}")
68
- except Exception as e:
69
- raise ValueError(f"Error: {e}")
125
+ response = self._robust_request(
126
+ "POST",
127
+ f"{self.base_url}/sessions/create",
128
+ headers=self._get_auth_headers(),
129
+ )
130
+ if response.status_code == 200:
131
+ return response.json()["session_id"]
132
+ else:
133
+ raise ValueError(f"Status code: {response.status_code}")
70
134
 
71
135
  def delete_session(self, session_id: str):
72
- if not self.access_token:
73
- raise ValueError("Not logged in")
74
- try:
75
- response = self.session.delete(
76
- f"{self.base_url}/sessions/{session_id}",
77
- headers=self._get_auth_headers(),
78
- )
79
- if response.status_code == 200:
80
- return
81
- else:
82
- raise ValueError(f"Status code: {response.status_code}")
83
- except Exception as e:
84
- raise ValueError(f"Error: {e}")
136
+ response = self._robust_request(
137
+ "DELETE",
138
+ f"{self.base_url}/sessions/{session_id}",
139
+ headers=self._get_auth_headers(),
140
+ )
141
+ if response.status_code != 200:
142
+ raise ValueError(f"Status code: {response.status_code}")
85
143
 
86
144
  def get_my_sessions(self):
87
- if not self.access_token:
88
- raise ValueError("Not logged in")
89
- try:
90
- response = self.session.get(
91
- f"{self.base_url}/sessions/my_sessions",
92
- headers=self._get_auth_headers(),
93
- )
94
- if response.status_code == 200:
95
- sessions = response.json()
96
- self.active_sessions = sessions
97
- return [
98
- {
99
- "session_id": session["session_id"],
100
- "title": session["environment_info"].get("title", None),
101
- }
102
- for session in sessions
103
- ]
104
- else:
105
- raise ValueError(f"Status code: {response.status_code}")
106
- except Exception as e:
107
- raise ValueError(f"Error: {e}")
145
+ response = self._robust_request(
146
+ "GET",
147
+ f"{self.base_url}/sessions/my_sessions",
148
+ headers=self._get_auth_headers(),
149
+ )
150
+ if response.status_code == 200:
151
+ sessions = response.json()
152
+ self.active_sessions = sessions
153
+ return [
154
+ {
155
+ "session_id": s["session_id"],
156
+ "title": s["environment_info"].get("title"),
157
+ }
158
+ for s in sessions
159
+ ]
160
+ else:
161
+ raise ValueError(f"Status code: {response.status_code}")
108
162
 
109
163
  def get_session_history(self, session_id: str):
110
164
  session = self._get_session_status(session_id)
111
165
  return session.get("conversation", [])
112
166
 
113
167
  def chat(self, session_id: str, prompt: str):
114
- if not self.access_token:
115
- raise ValueError("Not logged in")
116
- data = {
117
- "user_input": prompt,
118
- "session_id": session_id,
119
- }
120
- try:
121
- response = self.session.post(
122
- f"{self.base_url}/chat",
123
- json=data,
124
- headers=self._get_auth_headers(),
125
- )
126
- success = response.status_code == 200
127
- if success:
128
- return response.json()["conversation_history"]
129
- else:
130
- raise ValueError(f"Status code: {response.status_code}")
131
- except Exception as e:
132
- raise ValueError(f"Error: {e}")
168
+ data = {"user_input": prompt, "session_id": session_id}
169
+ response = self._robust_request(
170
+ "POST",
171
+ f"{self.base_url}/chat",
172
+ json=data,
173
+ headers=self._get_auth_headers(),
174
+ )
175
+ if response.status_code == 200:
176
+ return response.json()["conversation_history"]
177
+ else:
178
+ raise ValueError(f"Status code: {response.status_code}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aicosmos_client
3
- Version: 0.0.5
3
+ Version: 0.0.6
4
4
  Summary: client for AICosmos platform
5
5
  Project-URL: Homepage, https://github.com/pypa/sampleproject
6
6
  Project-URL: Issues, https://github.com/pypa/sampleproject/issues
@@ -28,7 +28,10 @@ from aicosmos_client.client import AICosmosClient
28
28
 
29
29
  # login
30
30
  client = AICosmosClient(
31
- base_url="http://xxx.xxx", username="xxx", password="xxxxxx"
31
+ base_url="https://aicosmos.ai/api",
32
+ username="xxx",
33
+ password="xxx",
34
+ auto_trust=True,
32
35
  )
33
36
 
34
37
  # create a new session
@@ -62,5 +65,6 @@ To show that the client is enough to build an application, we offer you an comma
62
65
  ```Python
63
66
  from aicosmos_client.cli import AICosmosCLI
64
67
 
68
+ # url: https://aicosmos.ai/api
65
69
  AICosmosCLI().run()
66
70
  ```
@@ -0,0 +1,7 @@
1
+ aicosmos_client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ aicosmos_client/cli.py,sha256=V6fewmdU2Im3NNbVw0OrVbxd1mkTGYKTdfgNKJl0CwY,13979
3
+ aicosmos_client/client.py,sha256=dn58ztTJVSOP9munQcYVB8AAhUiTsJoNnPvngQceSGc,6388
4
+ aicosmos_client-0.0.6.dist-info/METADATA,sha256=EsoT3FQDaqbmGH2bEdQCCcCAUkKQKz8e4xxH74d4Sj0,2242
5
+ aicosmos_client-0.0.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
6
+ aicosmos_client-0.0.6.dist-info/licenses/LICENSE,sha256=XBdpsYae127l7YQyMSVQwUUo22FPis7sMts7oBjkN_g,1056
7
+ aicosmos_client-0.0.6.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- aicosmos_client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- aicosmos_client/cli.py,sha256=V6fewmdU2Im3NNbVw0OrVbxd1mkTGYKTdfgNKJl0CwY,13979
3
- aicosmos_client/client.py,sha256=akVMmyDrAqOg0JI4mv_MBIA6I3VhLwDkstJlTEwiF5w,4664
4
- aicosmos_client-0.0.5.dist-info/METADATA,sha256=7mdmOAHYtWfyEuYOiVsJPiDT4PzPBBF3LyZ83L-rC6c,2175
5
- aicosmos_client-0.0.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
6
- aicosmos_client-0.0.5.dist-info/licenses/LICENSE,sha256=XBdpsYae127l7YQyMSVQwUUo22FPis7sMts7oBjkN_g,1056
7
- aicosmos_client-0.0.5.dist-info/RECORD,,