sf-queue-sdk 0.2.0b21__tar.gz → 0.2.0b24__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.
Files changed (24) hide show
  1. {sf_queue_sdk-0.2.0b21 → sf_queue_sdk-0.2.0b24}/PKG-INFO +86 -1
  2. {sf_queue_sdk-0.2.0b21 → sf_queue_sdk-0.2.0b24}/README.md +85 -0
  3. {sf_queue_sdk-0.2.0b21 → sf_queue_sdk-0.2.0b24}/pyproject.toml +1 -1
  4. {sf_queue_sdk-0.2.0b21 → sf_queue_sdk-0.2.0b24}/queue_sdk/__init__.py +11 -1
  5. {sf_queue_sdk-0.2.0b21 → sf_queue_sdk-0.2.0b24}/queue_sdk/client.py +4 -0
  6. sf_queue_sdk-0.2.0b24/queue_sdk/query.py +22 -0
  7. sf_queue_sdk-0.2.0b24/queue_sdk/queues/recommendation_queue.py +136 -0
  8. {sf_queue_sdk-0.2.0b21 → sf_queue_sdk-0.2.0b24}/queue_sdk/types.py +39 -0
  9. {sf_queue_sdk-0.2.0b21 → sf_queue_sdk-0.2.0b24}/.gitignore +0 -0
  10. {sf_queue_sdk-0.2.0b21 → sf_queue_sdk-0.2.0b24}/queue_sdk/generated/__init__.py +0 -0
  11. {sf_queue_sdk-0.2.0b21 → sf_queue_sdk-0.2.0b24}/queue_sdk/generated/queue/email/v1/email_pb2.py +0 -0
  12. {sf_queue_sdk-0.2.0b21 → sf_queue_sdk-0.2.0b24}/queue_sdk/generated/queue/email/v1/email_pb2.pyi +0 -0
  13. {sf_queue_sdk-0.2.0b21 → sf_queue_sdk-0.2.0b24}/queue_sdk/generated/queue/tool_assignment/v1/tool_assignment_pb2.py +0 -0
  14. {sf_queue_sdk-0.2.0b21 → sf_queue_sdk-0.2.0b24}/queue_sdk/generated/queue/tool_assignment/v1/tool_assignment_pb2.pyi +0 -0
  15. {sf_queue_sdk-0.2.0b21 → sf_queue_sdk-0.2.0b24}/queue_sdk/generated/queue/topic_assignment/v1/topic_assignment_pb2.py +0 -0
  16. {sf_queue_sdk-0.2.0b21 → sf_queue_sdk-0.2.0b24}/queue_sdk/generated/queue/topic_assignment/v1/topic_assignment_pb2.pyi +0 -0
  17. {sf_queue_sdk-0.2.0b21 → sf_queue_sdk-0.2.0b24}/queue_sdk/queues/__init__.py +0 -0
  18. {sf_queue_sdk-0.2.0b21 → sf_queue_sdk-0.2.0b24}/queue_sdk/queues/base_queue.py +0 -0
  19. {sf_queue_sdk-0.2.0b21 → sf_queue_sdk-0.2.0b24}/queue_sdk/queues/email_queue.py +0 -0
  20. {sf_queue_sdk-0.2.0b21 → sf_queue_sdk-0.2.0b24}/queue_sdk/queues/pdf_to_image_queue.py +0 -0
  21. {sf_queue_sdk-0.2.0b21 → sf_queue_sdk-0.2.0b24}/queue_sdk/queues/tool_assignment_queue.py +0 -0
  22. {sf_queue_sdk-0.2.0b21 → sf_queue_sdk-0.2.0b24}/queue_sdk/queues/topic_assignment_queue.py +0 -0
  23. {sf_queue_sdk-0.2.0b21 → sf_queue_sdk-0.2.0b24}/queue_sdk/redis_client.py +0 -0
  24. {sf_queue_sdk-0.2.0b21 → sf_queue_sdk-0.2.0b24}/queue_sdk/sanitize.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sf-queue-sdk
3
- Version: 0.2.0b21
3
+ Version: 0.2.0b24
4
4
  Summary: Python SDK for sf-queue - Redis-based queue system
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: redis>=5.0.0
@@ -358,6 +358,91 @@ result = client.tool_assignment.send_batch_and_wait(
358
358
  | `current_assignment` | `Optional[str]` | Yes | Current cluster_id (None for new embeddings) |
359
359
  | `new_assignment` | `str` | Yes | The cluster_id to assign to |
360
360
 
361
+ ## Recommendations
362
+
363
+ ### Fire and forget
364
+
365
+ ```python
366
+ result = client.recommendations.create(
367
+ user_id="user_xyz",
368
+ topic_id="topic_abc123",
369
+ limit=10, # optional
370
+ )
371
+ print("Enqueued:", result.message_id)
372
+ ```
373
+
374
+ ### Create and wait for result
375
+
376
+ ```python
377
+ result = client.recommendations.create_and_wait(
378
+ user_id="user_xyz",
379
+ topic_id="topic_abc123",
380
+ limit=10, # optional
381
+ timeout=30,
382
+ )
383
+ print(result.success)
384
+ print(result.message_id)
385
+ ```
386
+
387
+ ### Batch (fire and forget)
388
+
389
+ ```python
390
+ result = client.recommendations.create_batch([
391
+ {"user_id": "user_1", "topic_id": "topic_1", "limit": 10},
392
+ {"user_id": "user_2", "topic_id": "topic_2"},
393
+ ])
394
+ print("Enqueued:", result.message_id)
395
+ ```
396
+
397
+ ### Batch (wait for confirmation)
398
+
399
+ ```python
400
+ result = client.recommendations.create_batch_and_wait(
401
+ [
402
+ {"user_id": "user_1", "topic_id": "topic_1"},
403
+ {"user_id": "user_2", "topic_id": "topic_2"},
404
+ ],
405
+ timeout=60,
406
+ )
407
+ print(result.success)
408
+ print(result.total)
409
+ print(result.successful)
410
+ print(result.failed)
411
+ ```
412
+
413
+ ### Recommendation Fields
414
+
415
+ | Field | Type | Required | Description |
416
+ |-------|------|----------|-------------|
417
+ | `user_id` | `str` | Yes | User requesting recommendations |
418
+ | `topic_id` | `str` | Yes | Topic context for recommendations |
419
+ | `limit` | `Optional[int]` | No | Max recommendations to return |
420
+
421
+ ## Query (Direct Key Lookups)
422
+
423
+ Read values directly from Redis by key. These are plain `GET` / `MGET` calls, not queue operations.
424
+
425
+ ### Get a single key
426
+
427
+ ```python
428
+ value = client.query.get("mykey")
429
+ print(value) # str or None
430
+ ```
431
+
432
+ ### Get multiple keys
433
+
434
+ ```python
435
+ values = client.query.get_many(["key1", "key2", "key3"])
436
+ print(values) # list[str | None]
437
+ ```
438
+
439
+ ### Query Methods
440
+
441
+ | Method | Return Type | Description |
442
+ |--------|-------------|-------------|
443
+ | `get(key)` | `Optional[str]` | Fetch a single key via Redis GET |
444
+ | `get_many(keys)` | `list[Optional[str]]` | Fetch multiple keys via Redis MGET |
445
+
361
446
  ## Cleanup
362
447
 
363
448
  ```python
@@ -350,6 +350,91 @@ result = client.tool_assignment.send_batch_and_wait(
350
350
  | `current_assignment` | `Optional[str]` | Yes | Current cluster_id (None for new embeddings) |
351
351
  | `new_assignment` | `str` | Yes | The cluster_id to assign to |
352
352
 
353
+ ## Recommendations
354
+
355
+ ### Fire and forget
356
+
357
+ ```python
358
+ result = client.recommendations.create(
359
+ user_id="user_xyz",
360
+ topic_id="topic_abc123",
361
+ limit=10, # optional
362
+ )
363
+ print("Enqueued:", result.message_id)
364
+ ```
365
+
366
+ ### Create and wait for result
367
+
368
+ ```python
369
+ result = client.recommendations.create_and_wait(
370
+ user_id="user_xyz",
371
+ topic_id="topic_abc123",
372
+ limit=10, # optional
373
+ timeout=30,
374
+ )
375
+ print(result.success)
376
+ print(result.message_id)
377
+ ```
378
+
379
+ ### Batch (fire and forget)
380
+
381
+ ```python
382
+ result = client.recommendations.create_batch([
383
+ {"user_id": "user_1", "topic_id": "topic_1", "limit": 10},
384
+ {"user_id": "user_2", "topic_id": "topic_2"},
385
+ ])
386
+ print("Enqueued:", result.message_id)
387
+ ```
388
+
389
+ ### Batch (wait for confirmation)
390
+
391
+ ```python
392
+ result = client.recommendations.create_batch_and_wait(
393
+ [
394
+ {"user_id": "user_1", "topic_id": "topic_1"},
395
+ {"user_id": "user_2", "topic_id": "topic_2"},
396
+ ],
397
+ timeout=60,
398
+ )
399
+ print(result.success)
400
+ print(result.total)
401
+ print(result.successful)
402
+ print(result.failed)
403
+ ```
404
+
405
+ ### Recommendation Fields
406
+
407
+ | Field | Type | Required | Description |
408
+ |-------|------|----------|-------------|
409
+ | `user_id` | `str` | Yes | User requesting recommendations |
410
+ | `topic_id` | `str` | Yes | Topic context for recommendations |
411
+ | `limit` | `Optional[int]` | No | Max recommendations to return |
412
+
413
+ ## Query (Direct Key Lookups)
414
+
415
+ Read values directly from Redis by key. These are plain `GET` / `MGET` calls, not queue operations.
416
+
417
+ ### Get a single key
418
+
419
+ ```python
420
+ value = client.query.get("mykey")
421
+ print(value) # str or None
422
+ ```
423
+
424
+ ### Get multiple keys
425
+
426
+ ```python
427
+ values = client.query.get_many(["key1", "key2", "key3"])
428
+ print(values) # list[str | None]
429
+ ```
430
+
431
+ ### Query Methods
432
+
433
+ | Method | Return Type | Description |
434
+ |--------|-------------|-------------|
435
+ | `get(key)` | `Optional[str]` | Fetch a single key via Redis GET |
436
+ | `get_many(keys)` | `list[Optional[str]]` | Fetch multiple keys via Redis MGET |
437
+
353
438
  ## Cleanup
354
439
 
355
440
  ```python
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sf-queue-sdk"
3
- version = "0.2.0b21"
3
+ version = "0.2.0b24"
4
4
  description = "Python SDK for sf-queue - Redis-based queue system"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -1,11 +1,14 @@
1
1
  """sf-queue Python SDK - Redis-based queue system."""
2
2
 
3
- __version__ = "0.2.0b21"
3
+ __version__ = "0.2.0b24"
4
4
 
5
5
  from queue_sdk.client import QueueClient
6
+ from queue_sdk.query import QueryClient
6
7
  from queue_sdk.types import (
7
8
  BatchEmailResponse,
8
9
  BatchQueueResponse,
10
+ BatchRecommendationRequestData,
11
+ BatchRecommendationResponse,
9
12
  BatchToolAssignmentData,
10
13
  BatchTopicAssignmentData,
11
14
  ConvertData,
@@ -16,6 +19,8 @@ from queue_sdk.types import (
16
19
  EmailResponse,
17
20
  QueueClientConfig,
18
21
  QueueResponse,
22
+ RecommendationRequestData,
23
+ RecommendationResponse,
19
24
  SendResult,
20
25
  ToolAssignmentData,
21
26
  TopicAssignmentData,
@@ -23,6 +28,7 @@ from queue_sdk.types import (
23
28
 
24
29
  __all__ = [
25
30
  "QueueClient",
31
+ "QueryClient",
26
32
  "QueueClientConfig",
27
33
  "EmailData",
28
34
  "EmailButton",
@@ -31,6 +37,10 @@ __all__ = [
31
37
  "BatchEmailResponse",
32
38
  "QueueResponse",
33
39
  "BatchQueueResponse",
40
+ "RecommendationRequestData",
41
+ "RecommendationResponse",
42
+ "BatchRecommendationRequestData",
43
+ "BatchRecommendationResponse",
34
44
  "TopicAssignmentData",
35
45
  "BatchTopicAssignmentData",
36
46
  "ToolAssignmentData",
@@ -1,7 +1,9 @@
1
1
  """QueueClient - main entry point for the Python SDK."""
2
2
 
3
+ from queue_sdk.query import QueryClient
3
4
  from queue_sdk.queues.email_queue import EmailQueue
4
5
  from queue_sdk.queues.pdf_to_image_queue import PdfToImageQueue
6
+ from queue_sdk.queues.recommendation_queue import RecommendationQueue
5
7
  from queue_sdk.queues.tool_assignment_queue import ToolAssignmentQueue
6
8
  from queue_sdk.queues.topic_assignment_queue import TopicAssignmentQueue
7
9
  from queue_sdk.redis_client import create_redis_client
@@ -65,6 +67,8 @@ class QueueClient:
65
67
  self.topic_assignment = TopicAssignmentQueue(self._redis, self._environment)
66
68
  self.tool_assignment = ToolAssignmentQueue(self._redis, self._environment)
67
69
  self.pdf_to_image = PdfToImageQueue(self._redis, self._environment)
70
+ self.recommendations = RecommendationQueue(self._redis, self._environment)
71
+ self.query = QueryClient(self._redis)
68
72
 
69
73
  def disconnect(self) -> None:
70
74
  """Close the Redis connection."""
@@ -0,0 +1,22 @@
1
+ """QueryClient - direct Redis key lookups."""
2
+
3
+ from typing import Optional
4
+
5
+ import redis as redis_lib
6
+
7
+
8
+ class QueryClient:
9
+ """Direct Redis key lookups (GET, MGET)."""
10
+
11
+ def __init__(self, redis_client: redis_lib.Redis):
12
+ self._redis = redis_client
13
+
14
+ def get(self, key: str) -> Optional[str]:
15
+ """Fetch a single key from Redis."""
16
+ return self._redis.get(key)
17
+
18
+ def get_many(self, keys: list[str]) -> list[Optional[str]]:
19
+ """Fetch multiple keys from Redis in a single MGET call."""
20
+ if not keys:
21
+ return []
22
+ return self._redis.mget(*keys)
@@ -0,0 +1,136 @@
1
+ """Recommendation queue with typed methods."""
2
+
3
+ from datetime import datetime, timezone
4
+ from typing import Any, Optional
5
+
6
+ import redis as redis_lib
7
+
8
+ from queue_sdk.queues.base_queue import BaseQueue
9
+ from queue_sdk.types import (
10
+ BatchRecommendationResponse,
11
+ RecommendationResponse,
12
+ SendResult,
13
+ )
14
+
15
+
16
+ class RecommendationQueue(BaseQueue):
17
+ """Typed methods for creating recommendation requests via the queue."""
18
+
19
+ def __init__(self, redis_client: redis_lib.Redis, environment: str):
20
+ super().__init__(redis_client, environment, "recommendation-requests")
21
+
22
+ def create(
23
+ self,
24
+ user_id: str,
25
+ topic_id: str,
26
+ limit: Optional[int] = None,
27
+ ) -> SendResult:
28
+ """Enqueue a recommendation request (fire and forget)."""
29
+ self._validate(user_id, topic_id)
30
+ payload = self._build_payload(user_id, topic_id, limit)
31
+ message_id = self._enqueue(payload)
32
+ return SendResult(message_id=message_id)
33
+
34
+ def create_and_wait(
35
+ self,
36
+ user_id: str,
37
+ topic_id: str,
38
+ limit: Optional[int] = None,
39
+ timeout: Optional[int] = None,
40
+ ) -> RecommendationResponse:
41
+ """Enqueue a recommendation request and wait for the result."""
42
+ self._validate(user_id, topic_id)
43
+ payload = self._build_payload(user_id, topic_id, limit)
44
+ response = self._enqueue_and_wait(payload, timeout)
45
+
46
+ return RecommendationResponse(
47
+ success=response.success,
48
+ message_id=response.message_id,
49
+ error=response.error,
50
+ processed_at=response.processed_at,
51
+ )
52
+
53
+ def create_batch(
54
+ self,
55
+ requests: list[dict[str, Any]],
56
+ ) -> SendResult:
57
+ """Enqueue multiple recommendation requests (fire and forget)."""
58
+ self._validate_batch(requests)
59
+ payload = self._build_batch_payload(requests)
60
+ message_id = self._enqueue(payload)
61
+ return SendResult(message_id=message_id)
62
+
63
+ def create_batch_and_wait(
64
+ self,
65
+ requests: list[dict[str, Any]],
66
+ timeout: Optional[int] = None,
67
+ ) -> BatchRecommendationResponse:
68
+ """Enqueue multiple recommendation requests and wait for confirmation."""
69
+ self._validate_batch(requests)
70
+ payload = self._build_batch_payload(requests)
71
+ response = self._enqueue_and_wait(payload, timeout)
72
+
73
+ return BatchRecommendationResponse(
74
+ success=response.success,
75
+ message_id=response.message_id,
76
+ error=response.error,
77
+ processed_at=response.processed_at,
78
+ total=getattr(response, "total", len(requests)),
79
+ successful=getattr(
80
+ response, "successful", len(requests) if response.success else 0
81
+ ),
82
+ failed=getattr(response, "failed", 0 if response.success else len(requests)),
83
+ )
84
+
85
+ def _build_payload(
86
+ self,
87
+ user_id: str,
88
+ topic_id: str,
89
+ limit: Optional[int],
90
+ ) -> dict[str, Any]:
91
+ """Build the recommendation request payload."""
92
+ payload: dict[str, Any] = {
93
+ "userId": user_id,
94
+ "topicId": topic_id,
95
+ "requestedAt": datetime.now(timezone.utc).isoformat(),
96
+ "key": f"{user_id}-{topic_id}",
97
+ }
98
+ if limit is not None:
99
+ payload["limit"] = limit
100
+ return payload
101
+
102
+ def _build_batch_payload(
103
+ self, requests: list[dict[str, Any]]
104
+ ) -> dict[str, Any]:
105
+ """Build the batch recommendation request payload."""
106
+ return {
107
+ "batch": True,
108
+ "requests": [
109
+ self._build_payload(
110
+ r.get("user_id", ""),
111
+ r.get("topic_id", ""),
112
+ r.get("limit"),
113
+ )
114
+ for r in requests
115
+ ],
116
+ }
117
+
118
+ @staticmethod
119
+ def _validate(user_id: str, topic_id: str) -> None:
120
+ """Validate the recommendation request."""
121
+ if not user_id:
122
+ raise ValueError("user_id is required")
123
+ if not topic_id:
124
+ raise ValueError("topic_id is required")
125
+
126
+ def _validate_batch(self, requests: list[dict[str, Any]]) -> None:
127
+ """Validate the batch of recommendation requests."""
128
+ if len(requests) == 0:
129
+ raise ValueError("At least one request is required")
130
+ if len(requests) > 100:
131
+ raise ValueError("Maximum 100 requests allowed per batch")
132
+ for r in requests:
133
+ self._validate(
134
+ r.get("user_id", ""),
135
+ r.get("topic_id", ""),
136
+ )
@@ -128,6 +128,45 @@ class BatchToolAssignmentData:
128
128
  assignments: list[ToolAssignmentData]
129
129
 
130
130
 
131
+ @dataclass
132
+ class RecommendationRequestData:
133
+ """Data for a recommendation request (mirrors Kafka recommendationRequests)."""
134
+
135
+ user_id: str
136
+ topic_id: str
137
+ limit: Optional[int] = None
138
+
139
+
140
+ @dataclass
141
+ class RecommendationResponse:
142
+ """Response from a processed recommendation request."""
143
+
144
+ success: bool
145
+ message_id: str
146
+ error: Optional[str] = None
147
+ processed_at: Optional[str] = None
148
+
149
+
150
+ @dataclass
151
+ class BatchRecommendationRequestData:
152
+ """Data for multiple recommendation requests."""
153
+
154
+ requests: list[dict]
155
+
156
+
157
+ @dataclass
158
+ class BatchRecommendationResponse:
159
+ """Response from a processed batch of recommendation requests."""
160
+
161
+ success: bool
162
+ message_id: str
163
+ total: int
164
+ successful: int
165
+ failed: int
166
+ error: Optional[str] = None
167
+ processed_at: Optional[str] = None
168
+
169
+
131
170
  @dataclass
132
171
  class ConvertData:
133
172
  """Data for a PDF/video to image conversion request."""