sf-queue-sdk 0.2.0b23__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 (25) hide show
  1. {sf_queue_sdk-0.2.0b23 → sf_queue_sdk-0.2.0b24}/PKG-INFO +52 -1
  2. {sf_queue_sdk-0.2.0b23 → sf_queue_sdk-0.2.0b24}/README.md +51 -0
  3. {sf_queue_sdk-0.2.0b23 → sf_queue_sdk-0.2.0b24}/pyproject.toml +1 -1
  4. {sf_queue_sdk-0.2.0b23 → sf_queue_sdk-0.2.0b24}/queue_sdk/__init__.py +7 -1
  5. {sf_queue_sdk-0.2.0b23 → sf_queue_sdk-0.2.0b24}/queue_sdk/client.py +2 -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.0b23 → sf_queue_sdk-0.2.0b24}/queue_sdk/types.py +20 -0
  9. sf_queue_sdk-0.2.0b23/queue_sdk/queues/recommendation_queue.py +0 -72
  10. {sf_queue_sdk-0.2.0b23 → sf_queue_sdk-0.2.0b24}/.gitignore +0 -0
  11. {sf_queue_sdk-0.2.0b23 → sf_queue_sdk-0.2.0b24}/queue_sdk/generated/__init__.py +0 -0
  12. {sf_queue_sdk-0.2.0b23 → sf_queue_sdk-0.2.0b24}/queue_sdk/generated/queue/email/v1/email_pb2.py +0 -0
  13. {sf_queue_sdk-0.2.0b23 → sf_queue_sdk-0.2.0b24}/queue_sdk/generated/queue/email/v1/email_pb2.pyi +0 -0
  14. {sf_queue_sdk-0.2.0b23 → sf_queue_sdk-0.2.0b24}/queue_sdk/generated/queue/tool_assignment/v1/tool_assignment_pb2.py +0 -0
  15. {sf_queue_sdk-0.2.0b23 → sf_queue_sdk-0.2.0b24}/queue_sdk/generated/queue/tool_assignment/v1/tool_assignment_pb2.pyi +0 -0
  16. {sf_queue_sdk-0.2.0b23 → sf_queue_sdk-0.2.0b24}/queue_sdk/generated/queue/topic_assignment/v1/topic_assignment_pb2.py +0 -0
  17. {sf_queue_sdk-0.2.0b23 → sf_queue_sdk-0.2.0b24}/queue_sdk/generated/queue/topic_assignment/v1/topic_assignment_pb2.pyi +0 -0
  18. {sf_queue_sdk-0.2.0b23 → sf_queue_sdk-0.2.0b24}/queue_sdk/queues/__init__.py +0 -0
  19. {sf_queue_sdk-0.2.0b23 → sf_queue_sdk-0.2.0b24}/queue_sdk/queues/base_queue.py +0 -0
  20. {sf_queue_sdk-0.2.0b23 → sf_queue_sdk-0.2.0b24}/queue_sdk/queues/email_queue.py +0 -0
  21. {sf_queue_sdk-0.2.0b23 → sf_queue_sdk-0.2.0b24}/queue_sdk/queues/pdf_to_image_queue.py +0 -0
  22. {sf_queue_sdk-0.2.0b23 → sf_queue_sdk-0.2.0b24}/queue_sdk/queues/tool_assignment_queue.py +0 -0
  23. {sf_queue_sdk-0.2.0b23 → sf_queue_sdk-0.2.0b24}/queue_sdk/queues/topic_assignment_queue.py +0 -0
  24. {sf_queue_sdk-0.2.0b23 → sf_queue_sdk-0.2.0b24}/queue_sdk/redis_client.py +0 -0
  25. {sf_queue_sdk-0.2.0b23 → 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.0b23
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
@@ -384,6 +384,32 @@ print(result.success)
384
384
  print(result.message_id)
385
385
  ```
386
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
+
387
413
  ### Recommendation Fields
388
414
 
389
415
  | Field | Type | Required | Description |
@@ -392,6 +418,31 @@ print(result.message_id)
392
418
  | `topic_id` | `str` | Yes | Topic context for recommendations |
393
419
  | `limit` | `Optional[int]` | No | Max recommendations to return |
394
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
+
395
446
  ## Cleanup
396
447
 
397
448
  ```python
@@ -376,6 +376,32 @@ print(result.success)
376
376
  print(result.message_id)
377
377
  ```
378
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
+
379
405
  ### Recommendation Fields
380
406
 
381
407
  | Field | Type | Required | Description |
@@ -384,6 +410,31 @@ print(result.message_id)
384
410
  | `topic_id` | `str` | Yes | Topic context for recommendations |
385
411
  | `limit` | `Optional[int]` | No | Max recommendations to return |
386
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
+
387
438
  ## Cleanup
388
439
 
389
440
  ```python
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sf-queue-sdk"
3
- version = "0.2.0b23"
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.0b23"
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,
@@ -25,6 +28,7 @@ from queue_sdk.types import (
25
28
 
26
29
  __all__ = [
27
30
  "QueueClient",
31
+ "QueryClient",
28
32
  "QueueClientConfig",
29
33
  "EmailData",
30
34
  "EmailButton",
@@ -35,6 +39,8 @@ __all__ = [
35
39
  "BatchQueueResponse",
36
40
  "RecommendationRequestData",
37
41
  "RecommendationResponse",
42
+ "BatchRecommendationRequestData",
43
+ "BatchRecommendationResponse",
38
44
  "TopicAssignmentData",
39
45
  "BatchTopicAssignmentData",
40
46
  "ToolAssignmentData",
@@ -1,5 +1,6 @@
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
5
6
  from queue_sdk.queues.recommendation_queue import RecommendationQueue
@@ -67,6 +68,7 @@ class QueueClient:
67
68
  self.tool_assignment = ToolAssignmentQueue(self._redis, self._environment)
68
69
  self.pdf_to_image = PdfToImageQueue(self._redis, self._environment)
69
70
  self.recommendations = RecommendationQueue(self._redis, self._environment)
71
+ self.query = QueryClient(self._redis)
70
72
 
71
73
  def disconnect(self) -> None:
72
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
+ )
@@ -147,6 +147,26 @@ class RecommendationResponse:
147
147
  processed_at: Optional[str] = None
148
148
 
149
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
+
150
170
  @dataclass
151
171
  class ConvertData:
152
172
  """Data for a PDF/video to image conversion request."""
@@ -1,72 +0,0 @@
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 RecommendationResponse, SendResult
10
-
11
-
12
- class RecommendationQueue(BaseQueue):
13
- """Typed methods for creating recommendation requests via the queue."""
14
-
15
- def __init__(self, redis_client: redis_lib.Redis, environment: str):
16
- super().__init__(redis_client, environment, "recommendation-requests")
17
-
18
- def create(
19
- self,
20
- user_id: str,
21
- topic_id: str,
22
- limit: Optional[int] = None,
23
- ) -> SendResult:
24
- """Enqueue a recommendation request (fire and forget)."""
25
- self._validate(user_id, topic_id)
26
- payload = self._build_payload(user_id, topic_id, limit)
27
- message_id = self._enqueue(payload)
28
- return SendResult(message_id=message_id)
29
-
30
- def create_and_wait(
31
- self,
32
- user_id: str,
33
- topic_id: str,
34
- limit: Optional[int] = None,
35
- timeout: Optional[int] = None,
36
- ) -> RecommendationResponse:
37
- """Enqueue a recommendation request and wait for the result."""
38
- self._validate(user_id, topic_id)
39
- payload = self._build_payload(user_id, topic_id, limit)
40
- response = self._enqueue_and_wait(payload, timeout)
41
-
42
- return RecommendationResponse(
43
- success=response.success,
44
- message_id=response.message_id,
45
- error=response.error,
46
- processed_at=response.processed_at,
47
- )
48
-
49
- def _build_payload(
50
- self,
51
- user_id: str,
52
- topic_id: str,
53
- limit: Optional[int],
54
- ) -> dict[str, Any]:
55
- """Build the recommendation request payload."""
56
- payload: dict[str, Any] = {
57
- "userId": user_id,
58
- "topicId": topic_id,
59
- "requestedAt": datetime.now(timezone.utc).isoformat(),
60
- "key": f"{user_id}-{topic_id}",
61
- }
62
- if limit is not None:
63
- payload["limit"] = limit
64
- return payload
65
-
66
- @staticmethod
67
- def _validate(user_id: str, topic_id: str) -> None:
68
- """Validate the recommendation request."""
69
- if not user_id:
70
- raise ValueError("user_id is required")
71
- if not topic_id:
72
- raise ValueError("topic_id is required")