contex-python 0.1.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.
contex/__init__.py ADDED
@@ -0,0 +1,49 @@
1
+ """
2
+ Contex Python SDK
3
+ ~~~~~~~~~~~~~~~~~
4
+
5
+ Official Python client for Contex - Semantic context routing for AI agents.
6
+
7
+ Basic usage:
8
+
9
+ >>> from contex import ContexClient
10
+ >>> client = ContexClient(url="http://localhost:8001", api_key="ck_...")
11
+ >>> await client.publish(project_id="my-app", data_key="config", data={"env": "prod"})
12
+
13
+ Full documentation: https://github.com/cahoots-org/contex
14
+ """
15
+
16
+ __version__ = "0.2.0"
17
+ __author__ = "Contex Team"
18
+ __license__ = "MIT"
19
+
20
+ from .client import ContexClient, ContexAsyncClient
21
+ from .models import (
22
+ DataEvent,
23
+ AgentRegistration,
24
+ RegistrationResponse,
25
+ MatchedData,
26
+ )
27
+ from .exceptions import (
28
+ ContexError,
29
+ AuthenticationError,
30
+ RateLimitError,
31
+ ValidationError,
32
+ NotFoundError,
33
+ ServerError,
34
+ )
35
+
36
+ __all__ = [
37
+ "ContexClient",
38
+ "ContexAsyncClient",
39
+ "DataEvent",
40
+ "AgentRegistration",
41
+ "RegistrationResponse",
42
+ "MatchedData",
43
+ "ContexError",
44
+ "AuthenticationError",
45
+ "RateLimitError",
46
+ "ValidationError",
47
+ "NotFoundError",
48
+ "ServerError",
49
+ ]
contex/client.py ADDED
@@ -0,0 +1,412 @@
1
+ """Contex Python SDK client implementation"""
2
+
3
+ import asyncio
4
+ from typing import Any, Dict, List, Optional, AsyncIterator
5
+ import httpx
6
+ from .models import (
7
+ DataEvent,
8
+ AgentRegistration,
9
+ RegistrationResponse,
10
+ QueryRequest,
11
+ QueryResponse,
12
+ APIKeyResponse,
13
+ RateLimitInfo,
14
+ )
15
+ from .exceptions import (
16
+ AuthenticationError,
17
+ RateLimitError,
18
+ ValidationError,
19
+ NotFoundError,
20
+ ServerError,
21
+ NetworkError,
22
+ TimeoutError as ContexTimeoutError,
23
+ )
24
+
25
+
26
+ class ContexAsyncClient:
27
+ """
28
+ Async Contex client for Python.
29
+
30
+ Example:
31
+ >>> client = ContexAsyncClient(url="http://localhost:8001", api_key="ck_...")
32
+ >>> await client.publish(project_id="my-app", data_key="config", data={"env": "prod"})
33
+ """
34
+
35
+ def __init__(
36
+ self,
37
+ url: str = "http://localhost:8001",
38
+ api_key: Optional[str] = None,
39
+ timeout: float = 30.0,
40
+ max_retries: int = 3,
41
+ ):
42
+ """
43
+ Initialize Contex client.
44
+
45
+ Args:
46
+ url: Contex server URL
47
+ api_key: API key for authentication
48
+ timeout: Request timeout in seconds
49
+ max_retries: Maximum number of retries for failed requests
50
+ """
51
+ self.url = url.rstrip("/")
52
+ self.api_key = api_key
53
+ self.timeout = timeout
54
+ self.max_retries = max_retries
55
+ self._client: Optional[httpx.AsyncClient] = None
56
+
57
+ async def __aenter__(self):
58
+ """Async context manager entry"""
59
+ self._client = httpx.AsyncClient(timeout=self.timeout)
60
+ return self
61
+
62
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
63
+ """Async context manager exit"""
64
+ if self._client:
65
+ await self._client.aclose()
66
+
67
+ def _get_headers(self) -> Dict[str, str]:
68
+ """Get request headers"""
69
+ headers = {
70
+ "Content-Type": "application/json",
71
+ "User-Agent": "contex-python/0.2.0",
72
+ }
73
+ if self.api_key:
74
+ headers["X-API-Key"] = self.api_key
75
+ return headers
76
+
77
+ async def _request(
78
+ self,
79
+ method: str,
80
+ path: str,
81
+ json: Optional[Dict] = None,
82
+ params: Optional[Dict] = None,
83
+ ) -> Any:
84
+ """Make HTTP request with error handling and retries"""
85
+ url = f"{self.url}{path}"
86
+ headers = self._get_headers()
87
+
88
+ # Create client if not in context manager
89
+ if self._client is None:
90
+ async with httpx.AsyncClient(timeout=self.timeout) as client:
91
+ return await self._do_request(client, method, url, headers, json, params)
92
+ else:
93
+ return await self._do_request(self._client, method, url, headers, json, params)
94
+
95
+ async def _do_request(
96
+ self,
97
+ client: httpx.AsyncClient,
98
+ method: str,
99
+ url: str,
100
+ headers: Dict[str, str],
101
+ json: Optional[Dict] = None,
102
+ params: Optional[Dict] = None,
103
+ ) -> Any:
104
+ """Execute HTTP request with retries"""
105
+ last_exception = None
106
+
107
+ for attempt in range(self.max_retries):
108
+ try:
109
+ response = await client.request(
110
+ method=method,
111
+ url=url,
112
+ headers=headers,
113
+ json=json,
114
+ params=params,
115
+ )
116
+
117
+ # Handle response
118
+ if response.status_code == 200 or response.status_code == 201:
119
+ return response.json()
120
+
121
+ elif response.status_code == 401:
122
+ raise AuthenticationError("Invalid or missing API key")
123
+
124
+ elif response.status_code == 403:
125
+ raise AuthenticationError("Insufficient permissions")
126
+
127
+ elif response.status_code == 404:
128
+ raise NotFoundError(f"Resource not found: {url}")
129
+
130
+ elif response.status_code == 422:
131
+ error_detail = response.json().get("detail", "Validation error")
132
+ raise ValidationError(f"Validation error: {error_detail}")
133
+
134
+ elif response.status_code == 429:
135
+ retry_after = response.headers.get("Retry-After")
136
+ raise RateLimitError(
137
+ "Rate limit exceeded",
138
+ retry_after=int(retry_after) if retry_after else None
139
+ )
140
+
141
+ elif response.status_code >= 500:
142
+ error_msg = response.json().get("detail", "Server error")
143
+ raise ServerError(f"Server error: {error_msg}")
144
+
145
+ else:
146
+ raise ServerError(f"Unexpected status code: {response.status_code}")
147
+
148
+ except httpx.TimeoutException as e:
149
+ last_exception = ContexTimeoutError(f"Request timed out: {e}")
150
+ if attempt < self.max_retries - 1:
151
+ await asyncio.sleep(2 ** attempt) # Exponential backoff
152
+ continue
153
+
154
+ except httpx.RequestError as e:
155
+ last_exception = NetworkError(f"Network error: {e}")
156
+ if attempt < self.max_retries - 1:
157
+ await asyncio.sleep(2 ** attempt)
158
+ continue
159
+
160
+ # All retries failed
161
+ if last_exception:
162
+ raise last_exception
163
+
164
+ # ========================================================================
165
+ # Data Publishing
166
+ # ========================================================================
167
+
168
+ async def publish(
169
+ self,
170
+ project_id: str,
171
+ data_key: str,
172
+ data: Any,
173
+ data_format: str = "json",
174
+ metadata: Optional[Dict[str, Any]] = None,
175
+ ) -> Dict[str, Any]:
176
+ """
177
+ Publish data to Contex.
178
+
179
+ Args:
180
+ project_id: Project identifier
181
+ data_key: Unique key for this data
182
+ data: Data payload (any JSON-serializable type)
183
+ data_format: Data format (json, yaml, toml, text)
184
+ metadata: Optional metadata
185
+
186
+ Returns:
187
+ Response with status and sequence number
188
+
189
+ Example:
190
+ >>> await client.publish(
191
+ ... project_id="my-app",
192
+ ... data_key="config",
193
+ ... data={"env": "prod", "debug": False}
194
+ ... )
195
+ """
196
+ event = DataEvent(
197
+ project_id=project_id,
198
+ data_key=data_key,
199
+ data=data,
200
+ data_format=data_format,
201
+ metadata=metadata,
202
+ )
203
+ return await self._request("POST", "/api/data/publish", json=event.model_dump())
204
+
205
+ # ========================================================================
206
+ # Agent Management
207
+ # ========================================================================
208
+
209
+ async def register_agent(
210
+ self,
211
+ agent_id: str,
212
+ project_id: str,
213
+ data_needs: List[str],
214
+ notification_method: str = "redis",
215
+ webhook_url: Optional[str] = None,
216
+ webhook_secret: Optional[str] = None,
217
+ last_seen_sequence: str = "0",
218
+ ) -> RegistrationResponse:
219
+ """
220
+ Register an agent with Contex.
221
+
222
+ Args:
223
+ agent_id: Unique agent identifier
224
+ project_id: Project identifier
225
+ data_needs: List of data needs in natural language
226
+ notification_method: Notification method (redis or webhook)
227
+ webhook_url: Webhook URL (if using webhook notifications)
228
+ webhook_secret: Webhook secret for HMAC verification
229
+ last_seen_sequence: Last seen sequence number
230
+
231
+ Returns:
232
+ Registration response with matched data
233
+
234
+ Example:
235
+ >>> response = await client.register_agent(
236
+ ... agent_id="code-reviewer",
237
+ ... project_id="my-app",
238
+ ... data_needs=["coding standards", "test requirements"]
239
+ ... )
240
+ >>> print(f"Matched {len(response.matched_data)} items")
241
+ """
242
+ registration = AgentRegistration(
243
+ agent_id=agent_id,
244
+ project_id=project_id,
245
+ data_needs=data_needs,
246
+ notification_method=notification_method,
247
+ webhook_url=webhook_url,
248
+ webhook_secret=webhook_secret,
249
+ last_seen_sequence=last_seen_sequence,
250
+ )
251
+ result = await self._request("POST", "/api/agents/register", json=registration.model_dump())
252
+ return RegistrationResponse(**result)
253
+
254
+ async def unregister_agent(self, agent_id: str) -> Dict[str, Any]:
255
+ """
256
+ Unregister an agent.
257
+
258
+ Args:
259
+ agent_id: Agent identifier
260
+
261
+ Returns:
262
+ Response confirming unregistration
263
+ """
264
+ return await self._request("DELETE", f"/api/agents/{agent_id}")
265
+
266
+ async def get_agent_status(self, agent_id: str) -> Dict[str, Any]:
267
+ """
268
+ Get agent status.
269
+
270
+ Args:
271
+ agent_id: Agent identifier
272
+
273
+ Returns:
274
+ Agent status information
275
+ """
276
+ return await self._request("GET", f"/api/agents/{agent_id}/status")
277
+
278
+ # ========================================================================
279
+ # Querying
280
+ # ========================================================================
281
+
282
+ async def query(
283
+ self,
284
+ project_id: str,
285
+ query: str,
286
+ max_results: int = 10,
287
+ ) -> QueryResponse:
288
+ """
289
+ Query for relevant data.
290
+
291
+ Args:
292
+ project_id: Project identifier
293
+ query: Query string in natural language
294
+ max_results: Maximum number of results
295
+
296
+ Returns:
297
+ Query response with matched data
298
+
299
+ Example:
300
+ >>> response = await client.query(
301
+ ... project_id="my-app",
302
+ ... query="authentication configuration"
303
+ ... )
304
+ >>> for result in response.results:
305
+ ... print(f"{result.data_key}: {result.similarity_score}")
306
+ """
307
+ request = QueryRequest(
308
+ project_id=project_id,
309
+ query=query,
310
+ max_results=max_results,
311
+ )
312
+ result = await self._request("POST", "/api/query", json=request.model_dump())
313
+ return QueryResponse(**result)
314
+
315
+ # ========================================================================
316
+ # API Key Management
317
+ # ========================================================================
318
+
319
+ async def create_api_key(self, name: str) -> APIKeyResponse:
320
+ """
321
+ Create a new API key.
322
+
323
+ Args:
324
+ name: Name for the API key
325
+
326
+ Returns:
327
+ API key response with key details
328
+
329
+ Note:
330
+ The API key is only returned once. Store it securely!
331
+ """
332
+ result = await self._request("POST", f"/api/auth/keys?name={name}")
333
+ return APIKeyResponse(**result)
334
+
335
+ async def list_api_keys(self) -> List[Dict[str, Any]]:
336
+ """List all API keys (without the actual key values)"""
337
+ return await self._request("GET", "/api/auth/keys")
338
+
339
+ async def revoke_api_key(self, key_id: str) -> Dict[str, Any]:
340
+ """
341
+ Revoke an API key.
342
+
343
+ Args:
344
+ key_id: Key identifier
345
+ """
346
+ return await self._request("DELETE", f"/api/auth/keys/{key_id}")
347
+
348
+ # ========================================================================
349
+ # Health & Status
350
+ # ========================================================================
351
+
352
+ async def health(self) -> Dict[str, Any]:
353
+ """Get comprehensive health status"""
354
+ return await self._request("GET", "/api/health")
355
+
356
+ async def ready(self) -> Dict[str, Any]:
357
+ """Get readiness status"""
358
+ return await self._request("GET", "/api/health/ready")
359
+
360
+ async def rate_limit_status(self) -> RateLimitInfo:
361
+ """Get current rate limit status"""
362
+ result = await self._request("GET", "/api/rate-limit/status")
363
+ return RateLimitInfo(**result)
364
+
365
+
366
+ class ContexClient(ContexAsyncClient):
367
+ """
368
+ Synchronous Contex client (wrapper around async client).
369
+
370
+ Example:
371
+ >>> client = ContexClient(url="http://localhost:8001", api_key="ck_...")
372
+ >>> client.publish(project_id="my-app", data_key="config", data={"env": "prod"})
373
+ """
374
+
375
+ def __init__(self, *args, **kwargs):
376
+ super().__init__(*args, **kwargs)
377
+ self._loop: Optional[asyncio.AbstractEventLoop] = None
378
+
379
+ def _run_async(self, coro):
380
+ """Run async coroutine in sync context"""
381
+ if self._loop is None:
382
+ self._loop = asyncio.new_event_loop()
383
+ return self._loop.run_until_complete(coro)
384
+
385
+ def publish(self, *args, **kwargs):
386
+ """Synchronous publish"""
387
+ return self._run_async(super().publish(*args, **kwargs))
388
+
389
+ def register_agent(self, *args, **kwargs):
390
+ """Synchronous register_agent"""
391
+ return self._run_async(super().register_agent(*args, **kwargs))
392
+
393
+ def unregister_agent(self, *args, **kwargs):
394
+ """Synchronous unregister_agent"""
395
+ return self._run_async(super().unregister_agent(*args, **kwargs))
396
+
397
+ def query(self, *args, **kwargs):
398
+ """Synchronous query"""
399
+ return self._run_async(super().query(*args, **kwargs))
400
+
401
+ def health(self, *args, **kwargs):
402
+ """Synchronous health"""
403
+ return self._run_async(super().health(*args, **kwargs))
404
+
405
+ def create_api_key(self, *args, **kwargs):
406
+ """Synchronous create_api_key"""
407
+ return self._run_async(super().create_api_key(*args, **kwargs))
408
+
409
+ def __del__(self):
410
+ """Cleanup event loop"""
411
+ if self._loop:
412
+ self._loop.close()
contex/exceptions.py ADDED
@@ -0,0 +1,44 @@
1
+ """Contex SDK exceptions"""
2
+
3
+
4
+ class ContexError(Exception):
5
+ """Base exception for all Contex SDK errors"""
6
+ pass
7
+
8
+
9
+ class AuthenticationError(ContexError):
10
+ """Raised when authentication fails"""
11
+ pass
12
+
13
+
14
+ class RateLimitError(ContexError):
15
+ """Raised when rate limit is exceeded"""
16
+
17
+ def __init__(self, message: str, retry_after: int = None):
18
+ super().__init__(message)
19
+ self.retry_after = retry_after
20
+
21
+
22
+ class ValidationError(ContexError):
23
+ """Raised when request validation fails"""
24
+ pass
25
+
26
+
27
+ class NotFoundError(ContexError):
28
+ """Raised when resource is not found"""
29
+ pass
30
+
31
+
32
+ class ServerError(ContexError):
33
+ """Raised when server returns 5xx error"""
34
+ pass
35
+
36
+
37
+ class NetworkError(ContexError):
38
+ """Raised when network request fails"""
39
+ pass
40
+
41
+
42
+ class TimeoutError(ContexError):
43
+ """Raised when request times out"""
44
+ pass
contex/models.py ADDED
@@ -0,0 +1,70 @@
1
+ """Contex SDK data models"""
2
+
3
+ from typing import Any, Dict, List, Optional
4
+ from pydantic import BaseModel, Field
5
+
6
+
7
+ class DataEvent(BaseModel):
8
+ """Data event to publish"""
9
+ project_id: str = Field(..., description="Project identifier")
10
+ data_key: str = Field(..., description="Unique key for this data")
11
+ data: Any = Field(..., description="Data payload (any JSON-serializable type)")
12
+ data_format: Optional[str] = Field(default="json", description="Data format: json, yaml, toml, text")
13
+ metadata: Optional[Dict[str, Any]] = Field(default=None, description="Optional metadata")
14
+
15
+
16
+ class AgentRegistration(BaseModel):
17
+ """Agent registration request"""
18
+ agent_id: str = Field(..., description="Unique agent identifier")
19
+ project_id: str = Field(..., description="Project identifier")
20
+ data_needs: List[str] = Field(..., description="List of data needs in natural language")
21
+ notification_method: Optional[str] = Field(default="redis", description="Notification method: redis or webhook")
22
+ webhook_url: Optional[str] = Field(default=None, description="Webhook URL (if notification_method=webhook)")
23
+ webhook_secret: Optional[str] = Field(default=None, description="Webhook secret for HMAC verification")
24
+ last_seen_sequence: Optional[str] = Field(default="0", description="Last seen sequence number")
25
+
26
+
27
+ class MatchedData(BaseModel):
28
+ """Matched data returned to agent"""
29
+ data_key: str
30
+ data: Any
31
+ similarity_score: float
32
+ sequence: str
33
+ timestamp: str
34
+
35
+
36
+ class RegistrationResponse(BaseModel):
37
+ """Agent registration response"""
38
+ agent_id: str
39
+ project_id: str
40
+ notification_channel: Optional[str] = None
41
+ matched_data: List[MatchedData]
42
+ last_seen_sequence: str
43
+
44
+
45
+ class QueryRequest(BaseModel):
46
+ """Query request"""
47
+ project_id: str
48
+ query: str
49
+ max_results: Optional[int] = Field(default=10, ge=1, le=100)
50
+
51
+
52
+ class QueryResponse(BaseModel):
53
+ """Query response"""
54
+ results: List[MatchedData]
55
+ total: int
56
+
57
+
58
+ class APIKeyResponse(BaseModel):
59
+ """API key creation response"""
60
+ key_id: str
61
+ key: str
62
+ name: str
63
+ created_at: str
64
+
65
+
66
+ class RateLimitInfo(BaseModel):
67
+ """Rate limit information"""
68
+ limit: int
69
+ remaining: int
70
+ reset_at: str
@@ -0,0 +1,277 @@
1
+ Metadata-Version: 2.4
2
+ Name: contex-python
3
+ Version: 0.1.0
4
+ Summary: Official Python SDK for Contex - Semantic context routing for AI agents
5
+ Author-email: Cahoots <admin@cahoots.cc>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/cahoots-org/contex
8
+ Project-URL: Repository, https://github.com/cahoots-org/contex
9
+ Project-URL: Issues, https://github.com/cahoots-org/contex/issues
10
+ Keywords: ai,agents,context,semantic-search,machine-learning
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: httpx>=0.25.0
23
+ Requires-Dist: pydantic>=2.0.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=7.4.0; extra == "dev"
26
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
27
+ Requires-Dist: pytest-mock>=3.12.0; extra == "dev"
28
+ Requires-Dist: black>=23.0.0; extra == "dev"
29
+ Requires-Dist: mypy>=1.5.0; extra == "dev"
30
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
31
+ Dynamic: license-file
32
+
33
+ # Contex Python SDK
34
+
35
+ Official Python client for [Contex](https://github.com/cahoots-org/contex) - Semantic context routing for AI agents.
36
+
37
+ ## Installation
38
+
39
+ ```bash
40
+ pip install contex-python
41
+ ```
42
+
43
+ ## Quick Start
44
+
45
+ ### Async Client (Recommended)
46
+
47
+ ```python
48
+ from contex import ContexAsyncClient
49
+
50
+ async def main():
51
+ async with ContexAsyncClient(
52
+ url="http://localhost:8001",
53
+ api_key="ck_your_api_key_here"
54
+ ) as client:
55
+ # Publish data
56
+ await client.publish(
57
+ project_id="my-app",
58
+ data_key="coding_standards",
59
+ data={
60
+ "style": "PEP 8",
61
+ "max_line_length": 100,
62
+ "quotes": "double"
63
+ }
64
+ )
65
+
66
+ # Register agent
67
+ response = await client.register_agent(
68
+ agent_id="code-reviewer",
69
+ project_id="my-app",
70
+ data_needs=[
71
+ "coding standards and style guidelines",
72
+ "testing requirements and coverage goals"
73
+ ]
74
+ )
75
+
76
+ print(f"Matched {len(response.matched_data)} items")
77
+ for match in response.matched_data:
78
+ print(f" {match.data_key}: {match.similarity_score:.2f}")
79
+
80
+ # Query for data
81
+ results = await client.query(
82
+ project_id="my-app",
83
+ query="authentication configuration"
84
+ )
85
+
86
+ for result in results.results:
87
+ print(f"{result.data_key}: {result.data}")
88
+
89
+ import asyncio
90
+ asyncio.run(main())
91
+ ```
92
+
93
+ ### Sync Client
94
+
95
+ ```python
96
+ from contex import ContexClient
97
+
98
+ client = ContexClient(
99
+ url="http://localhost:8001",
100
+ api_key="ck_your_api_key_here"
101
+ )
102
+
103
+ # Publish data
104
+ client.publish(
105
+ project_id="my-app",
106
+ data_key="config",
107
+ data={"env": "prod", "debug": False}
108
+ )
109
+
110
+ # Register agent
111
+ response = client.register_agent(
112
+ agent_id="my-agent",
113
+ project_id="my-app",
114
+ data_needs=["configuration", "secrets"]
115
+ )
116
+ ```
117
+
118
+ ## Features
119
+
120
+ - ✅ **Async & Sync**: Both async and synchronous interfaces
121
+ - ✅ **Type Hints**: Full type annotations with Pydantic models
122
+ - ✅ **Error Handling**: Comprehensive exception hierarchy
123
+ - ✅ **Retry Logic**: Automatic retries with exponential backoff
124
+ - ✅ **Rate Limiting**: Built-in rate limit handling
125
+ - ✅ **Authentication**: API key authentication support
126
+
127
+ ## API Reference
128
+
129
+ ### Client Initialization
130
+
131
+ ```python
132
+ client = ContexAsyncClient(
133
+ url="http://localhost:8001", # Contex server URL
134
+ api_key="ck_...", # API key for authentication
135
+ timeout=30.0, # Request timeout in seconds
136
+ max_retries=3, # Maximum number of retries
137
+ )
138
+ ```
139
+
140
+ ### Publishing Data
141
+
142
+ ```python
143
+ await client.publish(
144
+ project_id="my-app", # Project identifier
145
+ data_key="unique-key", # Unique key for this data
146
+ data={"any": "json"}, # Data payload
147
+ data_format="json", # Format: json, yaml, toml, text
148
+ metadata={"tags": ["prod"]}, # Optional metadata
149
+ )
150
+ ```
151
+
152
+ ### Registering Agents
153
+
154
+ ```python
155
+ response = await client.register_agent(
156
+ agent_id="agent-1", # Unique agent ID
157
+ project_id="my-app", # Project ID
158
+ data_needs=["config", "secrets"], # Data needs (natural language)
159
+ notification_method="redis", # redis or webhook
160
+ webhook_url="https://...", # Optional webhook URL
161
+ webhook_secret="secret", # Optional webhook secret
162
+ last_seen_sequence="0", # Last seen sequence
163
+ )
164
+ ```
165
+
166
+ ### Querying Data
167
+
168
+ ```python
169
+ results = await client.query(
170
+ project_id="my-app",
171
+ query="authentication settings",
172
+ max_results=10,
173
+ )
174
+
175
+ for result in results.results:
176
+ print(f"{result.data_key}: {result.similarity_score}")
177
+ ```
178
+
179
+ ### API Key Management
180
+
181
+ ```python
182
+ # Create API key
183
+ key_response = await client.create_api_key(name="production-key")
184
+ print(f"API Key: {key_response.key}") # Store this securely!
185
+
186
+ # List keys
187
+ keys = await client.list_api_keys()
188
+
189
+ # Revoke key
190
+ await client.revoke_api_key(key_id="key-123")
191
+ ```
192
+
193
+ ### Health Checks
194
+
195
+ ```python
196
+ # Comprehensive health
197
+ health = await client.health()
198
+
199
+ # Readiness check
200
+ ready = await client.ready()
201
+
202
+ # Rate limit status
203
+ rate_limit = await client.rate_limit_status()
204
+ print(f"Remaining: {rate_limit.remaining}/{rate_limit.limit}")
205
+ ```
206
+
207
+ ## Exception Handling
208
+
209
+ ```python
210
+ from contex import (
211
+ ContexError,
212
+ AuthenticationError,
213
+ RateLimitError,
214
+ ValidationError,
215
+ NotFoundError,
216
+ ServerError,
217
+ )
218
+
219
+ try:
220
+ await client.publish(...)
221
+ except AuthenticationError:
222
+ print("Invalid API key")
223
+ except RateLimitError as e:
224
+ print(f"Rate limited. Retry after {e.retry_after} seconds")
225
+ except ValidationError as e:
226
+ print(f"Validation error: {e}")
227
+ except NotFoundError:
228
+ print("Resource not found")
229
+ except ServerError:
230
+ print("Server error")
231
+ except ContexError as e:
232
+ print(f"Contex error: {e}")
233
+ ```
234
+
235
+ ## Development
236
+
237
+ ### Setup
238
+
239
+ ```bash
240
+ cd sdk/python
241
+ pip install -e ".[dev]"
242
+ ```
243
+
244
+ ### Running Tests
245
+
246
+ ```bash
247
+ pytest
248
+ ```
249
+
250
+ ### Code Formatting
251
+
252
+ ```bash
253
+ black contex/
254
+ ruff check contex/
255
+ mypy contex/
256
+ ```
257
+
258
+ ## Examples
259
+
260
+ See the [examples](examples/) directory for more usage examples:
261
+
262
+ - `basic_usage.py` - Basic publish and query
263
+ - `agent_registration.py` - Agent registration and updates
264
+ - `webhook_agent.py` - Webhook-based agent
265
+ - `error_handling.py` - Error handling patterns
266
+ - `batch_operations.py` - Batch publishing
267
+
268
+ ## License
269
+
270
+ MIT License - see [LICENSE](LICENSE) for details.
271
+
272
+ ## Links
273
+
274
+ - [Documentation](https://contex.readthedocs.io)
275
+ - [GitHub](https://github.com/cahoots-org/contex)
276
+ - [PyPI](https://pypi.org/project/contex-python/)
277
+ - [Issues](https://github.com/cahoots-org/contex/issues)
@@ -0,0 +1,9 @@
1
+ contex/__init__.py,sha256=bx7OgH5qs3fANEQ1NGTeRq6J2feBkEZ-lwKAzccZfTk,1058
2
+ contex/client.py,sha256=Mdw_5Ez22wFJTlKEptw-5dgSVfhwYjCmhnjmAq2EoNA,14106
3
+ contex/exceptions.py,sha256=pqzfumyLkMvRDELsE5TmI7d1P9NT-ZRJyMB7X-SWHHE,882
4
+ contex/models.py,sha256=TdLNEOvUqpAM_9L3mpWDR2mCCTVDlYNCOyT1519r2K4,2253
5
+ contex_python-0.1.0.dist-info/licenses/LICENSE,sha256=ocKsEnLoJ4FUPpfVkwLpK__UOQ6UrKCOGVWopyCOM7g,1068
6
+ contex_python-0.1.0.dist-info/METADATA,sha256=dGaJ0PLkyrHemoHrD7eVS577GGZZ1NuLerWavDI7ylU,7056
7
+ contex_python-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
+ contex_python-0.1.0.dist-info/top_level.txt,sha256=Jk2ojGh-mGli0p8efzIi5IM8OEc4Et-0TAiBfNtGaTo,7
9
+ contex_python-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Contex Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ contex