seltz 0.1.2__tar.gz → 0.1.3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: seltz
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Seltz Python SDK for AI-powered search
5
5
  Author-email: Seltz <support@seltz.ai>
6
6
  Project-URL: Homepage, https://seltz.ai
@@ -21,7 +21,7 @@ Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
21
21
  Requires-Python: >=3.9
22
22
  Description-Content-Type: text/markdown
23
23
  Requires-Dist: grpcio>=1.76.0
24
- Requires-Dist: protobuf>=6.33.1
24
+ Requires-Dist: protobuf<6.0
25
25
 
26
26
  # Seltz Python SDK
27
27
 
@@ -77,15 +77,127 @@ Creates a new Seltz client instance.
77
77
 
78
78
  **Returns:** `Seltz` instance
79
79
 
80
- ### `client.search(text, max_documents=10)`
80
+ ### `client.search(query, *, includes=None, context=None, profile=None)`
81
81
 
82
82
  Performs a search query.
83
83
 
84
84
  **Parameters:**
85
- - `text` (str): The search query text.
86
- - `max_documents` (int): Maximum number of documents to return. Defaults to 10.
85
+ - `query` (str): The search query text. Keep concise for best performance.
86
+ - `includes` (Includes | int, optional): What to include in results.
87
+ - `context` (str, optional): Additional context for the query. Include as much relevant information as feasible to improve search quality.
88
+ - `profile` (str, optional): Search profile to use (contact support for available profiles).
87
89
 
88
- **Returns:** `SearchResponse` with a `documents` field containing search results.
90
+ **Returns:** `SearchResult` with documents and helper methods.
91
+
92
+ **Examples:**
93
+
94
+ ```python
95
+ response = client.search("Python asyncio tutorial")
96
+
97
+ # With Includes configuration
98
+ from seltz import Includes
99
+ response = client.search(
100
+ "Python tutorial",
101
+ includes=Includes(max_documents=10)
102
+ )
103
+
104
+ # Access results
105
+ for doc in response:
106
+ print(f"URL: {doc.url}")
107
+ print(f"Content: {doc.content}")
108
+
109
+ # Or use helper methods
110
+ first_doc = response.first()
111
+ all_urls = response.get_urls()
112
+ ```
113
+
114
+ ### `Includess`
115
+
116
+ Configuration for what to include in search results.
117
+
118
+ **Parameters:**
119
+ - `max_documents` (int, optional): Maximum number of documents to return.
120
+
121
+ **Constructor Pattern (simple cases):**
122
+ ```python
123
+ from seltz import Includes
124
+
125
+ # Simple constructor
126
+ includes = Includes(max_documents=5)
127
+ response = client.search("query", includes=includes)
128
+ ```
129
+
130
+ **Fluent Builder Pattern (complex cases):**
131
+ ```python
132
+ from seltz import Includes
133
+
134
+ # Single field
135
+ includes = Includes().max_documents(10)
136
+
137
+ # Mixed approach
138
+ includes = Includes(max_documents=5).max_documents(10)
139
+ ```
140
+
141
+ ### `SearchResult`
142
+
143
+ Search result wrapper with helper methods.
144
+
145
+ **Properties:**
146
+ - `documents`: List of Document objects
147
+
148
+ **Methods:**
149
+ - `__len__()`: Get number of documents (`len(result)`)
150
+ - `__iter__()`: Iterate over documents (`for doc in result:`)
151
+ - `__getitem__(index)`: Get document by index (`result[0]`, `result[-1]`)
152
+ - `first()`: Get first document (or None if empty)
153
+ - `get_urls()`: Get list of all URLs
154
+ - `get_contents()`: Get list of all contents
155
+ - `to_dict()`: Convert to dictionary for JSON serialization
156
+
157
+ **Example:**
158
+ ```python
159
+ result = client.search("Python tutorial")
160
+
161
+ print(f"Found {len(result)} documents")
162
+
163
+ for doc in result:
164
+ print(doc.url)
165
+
166
+ first = result.first()
167
+ if first:
168
+ print(f"Top result: {first.url}")
169
+
170
+ urls = result.get_urls()
171
+
172
+ import json
173
+ json_data = json.dumps(result.to_dict())
174
+ ```
175
+
176
+ ### `Document`
177
+
178
+ Individual search result document.
179
+
180
+ **Properties:**
181
+ - `url`: Document URL (optional)
182
+ - `content`: Document content (optional)
183
+
184
+ **Methods:**
185
+ - `has_url()`: Check if URL exists
186
+ - `has_content()`: Check if content exists
187
+ - `to_dict()`: Convert to dictionary
188
+
189
+ **Example:**
190
+ ```python
191
+ doc = result[0]
192
+
193
+ if doc.has_url():
194
+ print(f"URL: {doc.url}")
195
+
196
+ if doc.has_content():
197
+ print(f"Content: {doc.content[:100]}")
198
+
199
+ doc_dict = doc.to_dict()
200
+ ```
89
201
 
90
202
  ## Error Handling
91
203
 
@@ -121,4 +233,4 @@ except SeltzAPIError as e:
121
233
 
122
234
  - Python 3.8+
123
235
  - grpcio >= 1.76.0
124
- - protobuf >= 6.33.1
236
+ - protobuf < 6.0
seltz-0.1.3/README.md ADDED
@@ -0,0 +1,211 @@
1
+ # Seltz Python SDK
2
+
3
+ The official Python SDK for the Seltz AI-powered search API.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install seltz
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```python
14
+ from seltz import Seltz
15
+
16
+ # Initialize with API key
17
+ client = Seltz(api_key="your-api-key")
18
+
19
+ # Perform a search
20
+ response = client.search("your search query")
21
+
22
+ # Access results
23
+ for document in response.documents:
24
+ print(f"URL: {document.url}")
25
+ print(f"Content: {document.content}")
26
+ ```
27
+
28
+ ## API Key
29
+
30
+ Set your API key using one of these methods:
31
+
32
+ 1. **Environment variable** (recommended):
33
+ ```bash
34
+ export SELTZ_API_KEY="your-api-key"
35
+ ```
36
+
37
+ 2. **Direct parameter**:
38
+ ```python
39
+ client = Seltz(api_key="your-api-key")
40
+ ```
41
+
42
+ ## API Reference
43
+
44
+ ### `Seltz(api_key=None, endpoint="grpc.seltz.ai", insecure=False)`
45
+
46
+ Creates a new Seltz client instance.
47
+
48
+ **Parameters:**
49
+ - `api_key` (str, optional): API key for authentication. Defaults to `SELTZ_API_KEY` environment variable.
50
+ - `endpoint` (str): API endpoint. Defaults to "grpc.seltz.ai".
51
+ - `insecure` (bool): Use insecure connection. Defaults to False.
52
+
53
+ **Returns:** `Seltz` instance
54
+
55
+ ### `client.search(query, *, includes=None, context=None, profile=None)`
56
+
57
+ Performs a search query.
58
+
59
+ **Parameters:**
60
+ - `query` (str): The search query text. Keep concise for best performance.
61
+ - `includes` (Includes | int, optional): What to include in results.
62
+ - `context` (str, optional): Additional context for the query. Include as much relevant information as feasible to improve search quality.
63
+ - `profile` (str, optional): Search profile to use (contact support for available profiles).
64
+
65
+ **Returns:** `SearchResult` with documents and helper methods.
66
+
67
+ **Examples:**
68
+
69
+ ```python
70
+ response = client.search("Python asyncio tutorial")
71
+
72
+ # With Includes configuration
73
+ from seltz import Includes
74
+ response = client.search(
75
+ "Python tutorial",
76
+ includes=Includes(max_documents=10)
77
+ )
78
+
79
+ # Access results
80
+ for doc in response:
81
+ print(f"URL: {doc.url}")
82
+ print(f"Content: {doc.content}")
83
+
84
+ # Or use helper methods
85
+ first_doc = response.first()
86
+ all_urls = response.get_urls()
87
+ ```
88
+
89
+ ### `Includess`
90
+
91
+ Configuration for what to include in search results.
92
+
93
+ **Parameters:**
94
+ - `max_documents` (int, optional): Maximum number of documents to return.
95
+
96
+ **Constructor Pattern (simple cases):**
97
+ ```python
98
+ from seltz import Includes
99
+
100
+ # Simple constructor
101
+ includes = Includes(max_documents=5)
102
+ response = client.search("query", includes=includes)
103
+ ```
104
+
105
+ **Fluent Builder Pattern (complex cases):**
106
+ ```python
107
+ from seltz import Includes
108
+
109
+ # Single field
110
+ includes = Includes().max_documents(10)
111
+
112
+ # Mixed approach
113
+ includes = Includes(max_documents=5).max_documents(10)
114
+ ```
115
+
116
+ ### `SearchResult`
117
+
118
+ Search result wrapper with helper methods.
119
+
120
+ **Properties:**
121
+ - `documents`: List of Document objects
122
+
123
+ **Methods:**
124
+ - `__len__()`: Get number of documents (`len(result)`)
125
+ - `__iter__()`: Iterate over documents (`for doc in result:`)
126
+ - `__getitem__(index)`: Get document by index (`result[0]`, `result[-1]`)
127
+ - `first()`: Get first document (or None if empty)
128
+ - `get_urls()`: Get list of all URLs
129
+ - `get_contents()`: Get list of all contents
130
+ - `to_dict()`: Convert to dictionary for JSON serialization
131
+
132
+ **Example:**
133
+ ```python
134
+ result = client.search("Python tutorial")
135
+
136
+ print(f"Found {len(result)} documents")
137
+
138
+ for doc in result:
139
+ print(doc.url)
140
+
141
+ first = result.first()
142
+ if first:
143
+ print(f"Top result: {first.url}")
144
+
145
+ urls = result.get_urls()
146
+
147
+ import json
148
+ json_data = json.dumps(result.to_dict())
149
+ ```
150
+
151
+ ### `Document`
152
+
153
+ Individual search result document.
154
+
155
+ **Properties:**
156
+ - `url`: Document URL (optional)
157
+ - `content`: Document content (optional)
158
+
159
+ **Methods:**
160
+ - `has_url()`: Check if URL exists
161
+ - `has_content()`: Check if content exists
162
+ - `to_dict()`: Convert to dictionary
163
+
164
+ **Example:**
165
+ ```python
166
+ doc = result[0]
167
+
168
+ if doc.has_url():
169
+ print(f"URL: {doc.url}")
170
+
171
+ if doc.has_content():
172
+ print(f"Content: {doc.content[:100]}")
173
+
174
+ doc_dict = doc.to_dict()
175
+ ```
176
+
177
+ ## Error Handling
178
+
179
+ ```python
180
+ from seltz import (
181
+ Seltz,
182
+ SeltzConfigurationError,
183
+ SeltzAuthenticationError,
184
+ SeltzConnectionError,
185
+ SeltzAPIError,
186
+ SeltzTimeoutError,
187
+ SeltzRateLimitError,
188
+ )
189
+
190
+ try:
191
+ client = Seltz(api_key="your-api-key")
192
+ response = client.search("query")
193
+ except SeltzConfigurationError as e:
194
+ print(f"Configuration error: {e}")
195
+ except SeltzAuthenticationError as e:
196
+ print(f"Authentication error: {e}")
197
+ except SeltzConnectionError as e:
198
+ print(f"Connection error: {e}")
199
+ except SeltzTimeoutError as e:
200
+ print(f"Timeout error: {e}")
201
+ except SeltzRateLimitError as e:
202
+ print(f"Rate limit error: {e}")
203
+ except SeltzAPIError as e:
204
+ print(f"API error: {e}")
205
+ ```
206
+
207
+ ## Requirements
208
+
209
+ - Python 3.8+
210
+ - grpcio >= 1.76.0
211
+ - protobuf < 6.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "seltz"
7
- version = "0.1.2"
7
+ version = "0.1.3"
8
8
  description = "Seltz Python SDK for AI-powered search"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -26,7 +26,7 @@ classifiers = [
26
26
  ]
27
27
  dependencies = [
28
28
  "grpcio>=1.76.0",
29
- "protobuf>=6.33.1",
29
+ "protobuf<6.0",
30
30
  ]
31
31
 
32
32
  [project.urls]
@@ -57,5 +57,5 @@ split-on-trailing-comma = true
57
57
 
58
58
  [tool.ruff]
59
59
  exclude = [
60
- "src/seltz/seltz_public_api/"
60
+ "src/seltz_public_api/"
61
61
  ]
@@ -10,9 +10,16 @@ from .exceptions import (
10
10
  SeltzTimeoutError,
11
11
  )
12
12
  from .seltz import Seltz
13
+ from .types import Document, Includes, SearchResult
13
14
 
14
15
  __all__ = [
16
+ # Main client
15
17
  "Seltz",
18
+ # Types
19
+ "Includes",
20
+ "SearchResult",
21
+ "Document",
22
+ # Exceptions
16
23
  "SeltzError",
17
24
  "SeltzConfigurationError",
18
25
  "SeltzAuthenticationError",
@@ -1,11 +1,12 @@
1
1
  import grpc
2
+ from typing import Optional
2
3
 
3
4
 
4
5
  class SeltzClient:
5
6
  """Low-level gRPC client for Seltz API."""
6
7
 
7
8
  def __init__(
8
- self, endpoint: str, api_key: str | None = None, insecure: bool = False
9
+ self, endpoint: str, api_key: Optional[str] = None, insecure: bool = False
9
10
  ):
10
11
  """Initialize the Seltz gRPC client.
11
12
 
@@ -0,0 +1,79 @@
1
+ import os
2
+ from typing import Optional
3
+
4
+ from .client import SeltzClient
5
+ from .exceptions import SeltzConfigurationError
6
+ from .services.search_service import SearchService
7
+ from .types import Includes, SearchResult
8
+
9
+
10
+ class Seltz:
11
+ """Main Seltz SDK client for interacting with the Seltz API."""
12
+
13
+ _ENDPOINT: str = "grpc.seltz.ai"
14
+
15
+ def __init__(
16
+ self,
17
+ api_key: Optional[str] = os.environ.get("SELTZ_API_KEY"),
18
+ endpoint: str = _ENDPOINT,
19
+ insecure: bool = False,
20
+ ):
21
+ """Initialize the Seltz client.
22
+
23
+ Args:
24
+ api_key: API key for authentication. If None, will try to read from SELTZ_API_KEY environment variable
25
+ endpoint: The API endpoint to connect to (default: grpc.seltz.ai)
26
+ insecure: Whether to use insecure connection (default: False)
27
+
28
+ Returns:
29
+ Seltz: A new Seltz client instance
30
+
31
+ Raises:
32
+ SeltzConfigurationError: If no API key is provided
33
+ """
34
+ if api_key is None:
35
+ raise SeltzConfigurationError("No API key provided")
36
+ self._client = SeltzClient(
37
+ endpoint=endpoint, api_key=api_key, insecure=insecure
38
+ )
39
+ self._search = SearchService(self._client.channel, self._client.api_key)
40
+
41
+ def search(
42
+ self,
43
+ query: str,
44
+ *,
45
+ includes: Optional[Includes] = None,
46
+ context: Optional[str] = None,
47
+ profile: Optional[str] = None,
48
+ ) -> SearchResult:
49
+ """Perform a search query.
50
+
51
+ This method searches using the Seltz AI-powered search engine. You can
52
+ customize the search behavior using optional parameters.
53
+
54
+ Args:
55
+ query: The search query string. Keep this concise for best performance.
56
+ includes: Configuration for what to include in results. If not specified
57
+ server defaults are used.
58
+ context: Additional context for the query. Include as much relevant
59
+ information as feasible to improve search quality. For example,
60
+ "user is looking for Python documentation for beginners".
61
+ profile: Search profile to use. Different profiles may use different
62
+ ranking algorithms or have different data source preferences.
63
+
64
+ Returns:
65
+ SearchResult: Search results containing documents and metadata.
66
+
67
+ Raises:
68
+ SeltzAuthenticationError: If API key is invalid
69
+ SeltzConnectionError: If connection to API fails
70
+ SeltzTimeoutError: If request times out
71
+ SeltzRateLimitError: If rate limit is exceeded
72
+ SeltzAPIError: For other API errors
73
+ """
74
+ return self._search.search(
75
+ query=query,
76
+ includes=includes,
77
+ context=context,
78
+ profile=profile,
79
+ )
@@ -1,3 +1,5 @@
1
+ from typing import Optional
2
+
1
3
  import grpc
2
4
 
3
5
  from ..exceptions import (
@@ -7,7 +9,8 @@ from ..exceptions import (
7
9
  SeltzRateLimitError,
8
10
  SeltzTimeoutError,
9
11
  )
10
- from . import Includes, SearchRequest, SearchResponse, SeltzServiceStub
12
+ from ..types import Includes, SearchResult
13
+ from . import SearchRequest, SeltzServiceStub
11
14
 
12
15
 
13
16
  class SearchService:
@@ -23,28 +26,46 @@ class SearchService:
23
26
  self._stub = SeltzServiceStub(channel)
24
27
  self._api_key = api_key
25
28
 
26
- def search(self, query: str, max_documents: int = 10) -> SearchResponse:
29
+ def search(
30
+ self,
31
+ query: str,
32
+ *,
33
+ includes: Optional[Includes] = None,
34
+ context: Optional[str] = None,
35
+ profile: Optional[str] = None,
36
+ ) -> SearchResult:
27
37
  """Perform a search query.
28
38
 
29
39
  Args:
30
40
  query: The search query string
31
- max_documents: Maximum number of documents to return (default: 10)
41
+ includes: Either an Includes object
42
+ context: Additional context for the query
43
+ profile: Search profile to use
32
44
 
33
45
  Returns:
34
- SearchResponse containing the search results
46
+ SearchResult: Wrapped search response with helper methods
35
47
 
36
48
  Raises:
37
- grpc.RpcError: If the gRPC call fails
49
+ SeltzAuthenticationError: If authentication fails
50
+ SeltzConnectionError: If connection fails
51
+ SeltzTimeoutError: If request times out
52
+ SeltzRateLimitError: If rate limit exceeded
53
+ SeltzAPIError: For other API errors
38
54
  """
39
- includes = Includes(max_documents=max_documents)
40
- req = SearchRequest(query=query, includes=includes)
55
+ req = SearchRequest(
56
+ query=query,
57
+ includes=includes._to_protobuf() if includes is not None else None,
58
+ context=context,
59
+ profile=profile,
60
+ api_key=self._api_key,
61
+ )
41
62
 
42
63
  metadata = []
43
64
  if self._api_key:
44
65
  metadata.append(("authorization", f"Bearer {self._api_key}"))
45
66
 
46
67
  try:
47
- return self._stub.Search(req, metadata=metadata, timeout=30)
68
+ return SearchResult(self._stub.Search(req, metadata=metadata, timeout=30))
48
69
  except grpc.RpcError as e:
49
70
  if e.code() == grpc.StatusCode.UNAUTHENTICATED:
50
71
  raise SeltzAuthenticationError(
@@ -0,0 +1,285 @@
1
+ """Type definitions for the Seltz SDK."""
2
+
3
+ from typing import Iterator, Optional
4
+
5
+ from seltz_public_api.proto.v1.seltz_pb2 import (
6
+ Document as PbDocument,
7
+ )
8
+ from seltz_public_api.proto.v1.seltz_pb2 import (
9
+ Includes as PbIncludes,
10
+ )
11
+ from seltz_public_api.proto.v1.seltz_pb2 import (
12
+ SearchResponse as PbSearchResponse,
13
+ )
14
+
15
+
16
+ class Includes:
17
+ """Configuration for what to include in search results.
18
+
19
+ This class provides a user-friendly interface for configuring search result
20
+ inclusions, wrapping the underlying protobuf Includes message.
21
+
22
+ Supports both constructor and fluent builder patterns:
23
+
24
+ Constructor pattern (simple):
25
+ >>> includes = Includes(max_documents=5)
26
+ >>> response = client.search("query", includes=includes)
27
+
28
+ Fluent builder pattern (for multiple fields):
29
+ >>> includes = Includes().max_documents(5)
30
+ >>> response = client.search("query", includes=includes)
31
+
32
+ Attributes:
33
+ max_documents: Maximum number of documents to return in the response.
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ max_documents: int = 10,
39
+ ):
40
+ """Initialize the Includes configuration.
41
+
42
+ Args:
43
+ max_documents: Maximum number of documents to return. If not specified,
44
+ the server will use its default value.
45
+ """
46
+ self._max_documents = max_documents
47
+
48
+ def max_documents(self, value: int) -> "Includes":
49
+ """Set maximum number of documents (fluent builder method).
50
+
51
+ Args:
52
+ value: Maximum number of documents to return.
53
+
54
+ Returns:
55
+ Self for method chaining.
56
+
57
+ Example:
58
+ >>> includes = Includes().max_documents(10)
59
+ >>> # Or chain multiple fields (when more fields are available)
60
+ >>> includes = Includes().max_documents(10).include_metadata(True)
61
+ """
62
+ self._max_documents = value
63
+ return self
64
+
65
+ def _to_protobuf(self) -> PbIncludes:
66
+ """Convert to protobuf Includes message.
67
+
68
+ Internal method used by the SDK to convert this user-facing object
69
+ to the underlying protobuf representation.
70
+
71
+ Returns:
72
+ PbIncludes: Protobuf Includes message.
73
+ """
74
+ pb_includes = PbIncludes()
75
+ pb_includes.max_documents = self._max_documents
76
+ return pb_includes
77
+
78
+ def __repr__(self) -> str:
79
+ """String representation for debugging."""
80
+ params = []
81
+ params.append(f"max_documents={self._max_documents}")
82
+ return f"Includes({', '.join(params)})"
83
+
84
+
85
+ class Document:
86
+ """A search result document.
87
+
88
+ This class wraps the protobuf Document message with a more user-friendly
89
+ interface and helper methods.
90
+
91
+ Example:
92
+ >>> doc = response.documents[0]
93
+ >>> print(doc.url)
94
+ >>> print(doc.content[:100])
95
+ >>> if doc.has_content():
96
+ ... print("Document has content")
97
+
98
+ Attributes:
99
+ url: URL of the document (may be None for non-web documents)
100
+ content: Content/snippet of the document
101
+ """
102
+
103
+ def __init__(self, pb_document: PbDocument):
104
+ """Initialize from protobuf Document.
105
+
106
+ Args:
107
+ pb_document: Protobuf Document message
108
+ """
109
+ self._pb = pb_document
110
+
111
+ @property
112
+ def url(self) -> Optional[str]:
113
+ """Get the document URL.
114
+
115
+ Returns:
116
+ URL string if available, None otherwise.
117
+ """
118
+ return self._pb.url if self._pb.HasField("url") else None
119
+
120
+ @property
121
+ def content(self) -> Optional[str]:
122
+ """Get the document content.
123
+
124
+ Returns:
125
+ Content string if available, None otherwise.
126
+ """
127
+ return self._pb.content if self._pb.HasField("content") else None
128
+
129
+ def has_url(self) -> bool:
130
+ """Check if document has a URL.
131
+
132
+ Returns:
133
+ True if URL is present, False otherwise.
134
+ """
135
+ return self._pb.HasField("url")
136
+
137
+ def has_content(self) -> bool:
138
+ """Check if document has content.
139
+
140
+ Returns:
141
+ True if content is present, False otherwise.
142
+ """
143
+ return self._pb.HasField("content")
144
+
145
+ def to_dict(self) -> dict:
146
+ """Convert document to a dictionary.
147
+
148
+ Useful for JSON serialization or logging.
149
+
150
+ Returns:
151
+ Dictionary with url and content fields (only if present).
152
+ """
153
+ result = {}
154
+ if self.has_url():
155
+ result["url"] = self.url
156
+ if self.has_content():
157
+ result["content"] = self.content
158
+ return result
159
+
160
+ def __repr__(self) -> str:
161
+ """String representation for debugging."""
162
+ parts = []
163
+ if self.has_url():
164
+ parts.append(f"url='{self.url}'")
165
+ if self.content is not None:
166
+ content_preview = (
167
+ self.content[:50] + "..." if len(self.content) > 50 else self.content
168
+ )
169
+ parts.append(f"content='{content_preview}'")
170
+ return f"Document({', '.join(parts)})"
171
+
172
+ def __str__(self) -> str:
173
+ """Human-readable string representation."""
174
+ if self.url is not None:
175
+ return f"{self.url}: {self.content[:100] if self.content is not None else 'No content'}"
176
+ return self.content if self.content is not None else "Empty document"
177
+
178
+
179
+ class SearchResult:
180
+ """Search result containing documents and metadata.
181
+
182
+ This class wraps the protobuf SearchResponse message with a more user-friendly
183
+ interface and helper methods.
184
+
185
+ Example:
186
+ >>> result = client.search("Python tutorial")
187
+ >>> print(f"Found {len(result)} documents")
188
+ >>> for doc in result:
189
+ ... print(doc.url)
190
+ >>> first = result.first()
191
+ >>> urls = result.get_urls()
192
+
193
+ Attributes:
194
+ documents: List of Document objects in the search results
195
+ """
196
+
197
+ def __init__(self, pb_response: PbSearchResponse):
198
+ """Initialize from protobuf SearchResponse.
199
+
200
+ Args:
201
+ pb_response: Protobuf SearchResponse message
202
+ """
203
+ self._pb = pb_response
204
+ self._documents = [Document(pb_doc) for pb_doc in pb_response.documents]
205
+
206
+ @property
207
+ def documents(self) -> list[Document]:
208
+ """Get the list of documents.
209
+
210
+ Returns:
211
+ List of Document objects.
212
+ """
213
+ return self._documents
214
+
215
+ def __len__(self) -> int:
216
+ """Get the number of documents.
217
+
218
+ Returns:
219
+ Number of documents in the result.
220
+ """
221
+ return len(self._documents)
222
+
223
+ def __iter__(self) -> Iterator[Document]:
224
+ """Iterate over documents.
225
+
226
+ Returns:
227
+ Iterator over Document objects.
228
+ """
229
+ return iter(self._documents)
230
+
231
+ def __getitem__(self, index: int) -> Document:
232
+ """Get document by index.
233
+
234
+ Args:
235
+ index: Index of the document (supports negative indexing)
236
+
237
+ Returns:
238
+ Document at the specified index.
239
+
240
+ Raises:
241
+ IndexError: If index is out of range.
242
+ """
243
+ return self._documents[index]
244
+
245
+ def first(self) -> Optional[Document]:
246
+ """Get the first document.
247
+
248
+ Returns:
249
+ First document if available, None if result is empty.
250
+ """
251
+ return self._documents[0] if self._documents else None
252
+
253
+ def get_urls(self) -> list[str]:
254
+ """Get all document URLs.
255
+
256
+ Returns:
257
+ List of URLs from documents that have URLs.
258
+ """
259
+ return [doc.url for doc in self._documents if doc.url is not None]
260
+
261
+ def get_contents(self) -> list[str]:
262
+ """Get all document contents.
263
+
264
+ Returns:
265
+ List of content strings from documents that have content.
266
+ """
267
+ return [doc.content for doc in self._documents if doc.content is not None]
268
+
269
+ def to_dict(self) -> dict:
270
+ """Convert result to a dictionary.
271
+
272
+ Useful for JSON serialization or logging.
273
+
274
+ Returns:
275
+ Dictionary with documents list.
276
+ """
277
+ return {"documents": [doc.to_dict() for doc in self._documents]}
278
+
279
+ def __repr__(self) -> str:
280
+ """String representation for debugging."""
281
+ return f"SearchResult(documents={len(self._documents)})"
282
+
283
+ def __str__(self) -> str:
284
+ """Human-readable string representation."""
285
+ return f"SearchResult with {len(self._documents)} document(s)"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: seltz
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Seltz Python SDK for AI-powered search
5
5
  Author-email: Seltz <support@seltz.ai>
6
6
  Project-URL: Homepage, https://seltz.ai
@@ -21,7 +21,7 @@ Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
21
21
  Requires-Python: >=3.9
22
22
  Description-Content-Type: text/markdown
23
23
  Requires-Dist: grpcio>=1.76.0
24
- Requires-Dist: protobuf>=6.33.1
24
+ Requires-Dist: protobuf<6.0
25
25
 
26
26
  # Seltz Python SDK
27
27
 
@@ -77,15 +77,127 @@ Creates a new Seltz client instance.
77
77
 
78
78
  **Returns:** `Seltz` instance
79
79
 
80
- ### `client.search(text, max_documents=10)`
80
+ ### `client.search(query, *, includes=None, context=None, profile=None)`
81
81
 
82
82
  Performs a search query.
83
83
 
84
84
  **Parameters:**
85
- - `text` (str): The search query text.
86
- - `max_documents` (int): Maximum number of documents to return. Defaults to 10.
85
+ - `query` (str): The search query text. Keep concise for best performance.
86
+ - `includes` (Includes | int, optional): What to include in results.
87
+ - `context` (str, optional): Additional context for the query. Include as much relevant information as feasible to improve search quality.
88
+ - `profile` (str, optional): Search profile to use (contact support for available profiles).
87
89
 
88
- **Returns:** `SearchResponse` with a `documents` field containing search results.
90
+ **Returns:** `SearchResult` with documents and helper methods.
91
+
92
+ **Examples:**
93
+
94
+ ```python
95
+ response = client.search("Python asyncio tutorial")
96
+
97
+ # With Includes configuration
98
+ from seltz import Includes
99
+ response = client.search(
100
+ "Python tutorial",
101
+ includes=Includes(max_documents=10)
102
+ )
103
+
104
+ # Access results
105
+ for doc in response:
106
+ print(f"URL: {doc.url}")
107
+ print(f"Content: {doc.content}")
108
+
109
+ # Or use helper methods
110
+ first_doc = response.first()
111
+ all_urls = response.get_urls()
112
+ ```
113
+
114
+ ### `Includess`
115
+
116
+ Configuration for what to include in search results.
117
+
118
+ **Parameters:**
119
+ - `max_documents` (int, optional): Maximum number of documents to return.
120
+
121
+ **Constructor Pattern (simple cases):**
122
+ ```python
123
+ from seltz import Includes
124
+
125
+ # Simple constructor
126
+ includes = Includes(max_documents=5)
127
+ response = client.search("query", includes=includes)
128
+ ```
129
+
130
+ **Fluent Builder Pattern (complex cases):**
131
+ ```python
132
+ from seltz import Includes
133
+
134
+ # Single field
135
+ includes = Includes().max_documents(10)
136
+
137
+ # Mixed approach
138
+ includes = Includes(max_documents=5).max_documents(10)
139
+ ```
140
+
141
+ ### `SearchResult`
142
+
143
+ Search result wrapper with helper methods.
144
+
145
+ **Properties:**
146
+ - `documents`: List of Document objects
147
+
148
+ **Methods:**
149
+ - `__len__()`: Get number of documents (`len(result)`)
150
+ - `__iter__()`: Iterate over documents (`for doc in result:`)
151
+ - `__getitem__(index)`: Get document by index (`result[0]`, `result[-1]`)
152
+ - `first()`: Get first document (or None if empty)
153
+ - `get_urls()`: Get list of all URLs
154
+ - `get_contents()`: Get list of all contents
155
+ - `to_dict()`: Convert to dictionary for JSON serialization
156
+
157
+ **Example:**
158
+ ```python
159
+ result = client.search("Python tutorial")
160
+
161
+ print(f"Found {len(result)} documents")
162
+
163
+ for doc in result:
164
+ print(doc.url)
165
+
166
+ first = result.first()
167
+ if first:
168
+ print(f"Top result: {first.url}")
169
+
170
+ urls = result.get_urls()
171
+
172
+ import json
173
+ json_data = json.dumps(result.to_dict())
174
+ ```
175
+
176
+ ### `Document`
177
+
178
+ Individual search result document.
179
+
180
+ **Properties:**
181
+ - `url`: Document URL (optional)
182
+ - `content`: Document content (optional)
183
+
184
+ **Methods:**
185
+ - `has_url()`: Check if URL exists
186
+ - `has_content()`: Check if content exists
187
+ - `to_dict()`: Convert to dictionary
188
+
189
+ **Example:**
190
+ ```python
191
+ doc = result[0]
192
+
193
+ if doc.has_url():
194
+ print(f"URL: {doc.url}")
195
+
196
+ if doc.has_content():
197
+ print(f"Content: {doc.content[:100]}")
198
+
199
+ doc_dict = doc.to_dict()
200
+ ```
89
201
 
90
202
  ## Error Handling
91
203
 
@@ -121,4 +233,4 @@ except SeltzAPIError as e:
121
233
 
122
234
  - Python 3.8+
123
235
  - grpcio >= 1.76.0
124
- - protobuf >= 6.33.1
236
+ - protobuf < 6.0
@@ -4,6 +4,7 @@ src/seltz/__init__.py
4
4
  src/seltz/client.py
5
5
  src/seltz/exceptions.py
6
6
  src/seltz/seltz.py
7
+ src/seltz/types.py
7
8
  src/seltz.egg-info/PKG-INFO
8
9
  src/seltz.egg-info/SOURCES.txt
9
10
  src/seltz.egg-info/dependency_links.txt
@@ -0,0 +1,2 @@
1
+ grpcio>=1.76.0
2
+ protobuf<6.0
@@ -2,7 +2,7 @@
2
2
  # Generated by the protocol buffer compiler. DO NOT EDIT!
3
3
  # NO CHECKED-IN PROTOBUF GENCODE
4
4
  # source: seltz_public_api/proto/v1/seltz.proto
5
- # Protobuf Python Version: 6.33.1
5
+ # Protobuf Python Version: 5.29.5
6
6
  """Generated protocol buffer code."""
7
7
  from google.protobuf import descriptor as _descriptor
8
8
  from google.protobuf import descriptor_pool as _descriptor_pool
@@ -11,9 +11,9 @@ from google.protobuf import symbol_database as _symbol_database
11
11
  from google.protobuf.internal import builder as _builder
12
12
  _runtime_version.ValidateProtobufRuntimeVersion(
13
13
  _runtime_version.Domain.PUBLIC,
14
- 6,
15
- 33,
16
- 1,
14
+ 5,
15
+ 29,
16
+ 5,
17
17
  '',
18
18
  'seltz_public_api/proto/v1/seltz.proto'
19
19
  )
@@ -24,7 +24,7 @@ _sym_db = _symbol_database.Default()
24
24
 
25
25
 
26
26
 
27
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n%seltz_public_api/proto/v1/seltz.proto\x12\x19seltz_public_api.proto.v1\"F\n\x08Includes\x12(\n\rmax_documents\x18\x01 \x01(\rH\x00R\x0cmaxDocuments\x88\x01\x01\x42\x10\n\x0e_max_documents\"\xce\x01\n\rSearchRequest\x12\x14\n\x05query\x18\x01 \x01(\tR\x05query\x12\x44\n\x08includes\x18\x02 \x01(\x0b\x32#.seltz_public_api.proto.v1.IncludesH\x00R\x08includes\x88\x01\x01\x12\x1d\n\x07\x63ontext\x18\x03 \x01(\tH\x01R\x07\x63ontext\x88\x01\x01\x12\x1d\n\x07profile\x18\x04 \x01(\tH\x02R\x07profile\x88\x01\x01\x42\x0b\n\t_includesB\n\n\x08_contextB\n\n\x08_profile\"T\n\x08\x44ocument\x12\x15\n\x03url\x18\x01 \x01(\tH\x00R\x03url\x88\x01\x01\x12\x1d\n\x07\x63ontent\x18\x02 \x01(\tH\x01R\x07\x63ontent\x88\x01\x01\x42\x06\n\x04_urlB\n\n\x08_content\"S\n\x0eSearchResponse\x12\x41\n\tdocuments\x18\x01 \x03(\x0b\x32#.seltz_public_api.proto.v1.DocumentR\tdocuments2m\n\x0cSeltzService\x12]\n\x06Search\x12(.seltz_public_api.proto.v1.SearchRequest\x1a).seltz_public_api.proto.v1.SearchResponseb\x06proto3')
27
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n%seltz_public_api/proto/v1/seltz.proto\x12\x19seltz_public_api.proto.v1\"F\n\x08Includes\x12(\n\rmax_documents\x18\x01 \x01(\rH\x00R\x0cmaxDocuments\x88\x01\x01\x42\x10\n\x0e_max_documents\"\xe7\x01\n\rSearchRequest\x12\x14\n\x05query\x18\x01 \x01(\tR\x05query\x12\x44\n\x08includes\x18\x02 \x01(\x0b\x32#.seltz_public_api.proto.v1.IncludesH\x00R\x08includes\x88\x01\x01\x12\x1d\n\x07\x63ontext\x18\x03 \x01(\tH\x01R\x07\x63ontext\x88\x01\x01\x12\x1d\n\x07profile\x18\x04 \x01(\tH\x02R\x07profile\x88\x01\x01\x12\x17\n\x07\x61pi_key\x18\x05 \x01(\tR\x06\x61piKeyB\x0b\n\t_includesB\n\n\x08_contextB\n\n\x08_profile\"T\n\x08\x44ocument\x12\x15\n\x03url\x18\x01 \x01(\tH\x00R\x03url\x88\x01\x01\x12\x1d\n\x07\x63ontent\x18\x02 \x01(\tH\x01R\x07\x63ontent\x88\x01\x01\x42\x06\n\x04_urlB\n\n\x08_content\"S\n\x0eSearchResponse\x12\x41\n\tdocuments\x18\x01 \x03(\x0b\x32#.seltz_public_api.proto.v1.DocumentR\tdocuments2o\n\x0cSeltzService\x12_\n\x06Search\x12(.seltz_public_api.proto.v1.SearchRequest\x1a).seltz_public_api.proto.v1.SearchResponse\"\x00\x62\x06proto3')
28
28
 
29
29
  _globals = globals()
30
30
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -34,11 +34,11 @@ if not _descriptor._USE_C_DESCRIPTORS:
34
34
  _globals['_INCLUDES']._serialized_start=68
35
35
  _globals['_INCLUDES']._serialized_end=138
36
36
  _globals['_SEARCHREQUEST']._serialized_start=141
37
- _globals['_SEARCHREQUEST']._serialized_end=347
38
- _globals['_DOCUMENT']._serialized_start=349
39
- _globals['_DOCUMENT']._serialized_end=433
40
- _globals['_SEARCHRESPONSE']._serialized_start=435
41
- _globals['_SEARCHRESPONSE']._serialized_end=518
42
- _globals['_SELTZSERVICE']._serialized_start=520
43
- _globals['_SELTZSERVICE']._serialized_end=629
37
+ _globals['_SEARCHREQUEST']._serialized_end=372
38
+ _globals['_DOCUMENT']._serialized_start=374
39
+ _globals['_DOCUMENT']._serialized_end=458
40
+ _globals['_SEARCHRESPONSE']._serialized_start=460
41
+ _globals['_SEARCHRESPONSE']._serialized_end=543
42
+ _globals['_SELTZSERVICE']._serialized_start=545
43
+ _globals['_SELTZSERVICE']._serialized_end=656
44
44
  # @@protoc_insertion_point(module_scope)
@@ -1,35 +1,32 @@
1
- from collections.abc import Iterable as _Iterable
2
- from collections.abc import Mapping as _Mapping
3
- from typing import ClassVar as _ClassVar
4
- from typing import Optional as _Optional
5
- from typing import Union as _Union
6
-
1
+ from google.protobuf.internal import containers as _containers
7
2
  from google.protobuf import descriptor as _descriptor
8
3
  from google.protobuf import message as _message
9
- from google.protobuf.internal import containers as _containers
4
+ from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union
10
5
 
11
6
  DESCRIPTOR: _descriptor.FileDescriptor
12
7
 
13
8
  class Includes(_message.Message):
14
- __slots__ = ()
9
+ __slots__ = ("max_documents",)
15
10
  MAX_DOCUMENTS_FIELD_NUMBER: _ClassVar[int]
16
11
  max_documents: int
17
12
  def __init__(self, max_documents: _Optional[int] = ...) -> None: ...
18
13
 
19
14
  class SearchRequest(_message.Message):
20
- __slots__ = ()
15
+ __slots__ = ("query", "includes", "context", "profile", "api_key")
21
16
  QUERY_FIELD_NUMBER: _ClassVar[int]
22
17
  INCLUDES_FIELD_NUMBER: _ClassVar[int]
23
18
  CONTEXT_FIELD_NUMBER: _ClassVar[int]
24
19
  PROFILE_FIELD_NUMBER: _ClassVar[int]
20
+ API_KEY_FIELD_NUMBER: _ClassVar[int]
25
21
  query: str
26
22
  includes: Includes
27
23
  context: str
28
24
  profile: str
29
- def __init__(self, query: _Optional[str] = ..., includes: _Optional[_Union[Includes, _Mapping]] = ..., context: _Optional[str] = ..., profile: _Optional[str] = ...) -> None: ...
25
+ api_key: str
26
+ def __init__(self, query: _Optional[str] = ..., includes: _Optional[_Union[Includes, _Mapping]] = ..., context: _Optional[str] = ..., profile: _Optional[str] = ..., api_key: _Optional[str] = ...) -> None: ...
30
27
 
31
28
  class Document(_message.Message):
32
- __slots__ = ()
29
+ __slots__ = ("url", "content")
33
30
  URL_FIELD_NUMBER: _ClassVar[int]
34
31
  CONTENT_FIELD_NUMBER: _ClassVar[int]
35
32
  url: str
@@ -37,7 +34,7 @@ class Document(_message.Message):
37
34
  def __init__(self, url: _Optional[str] = ..., content: _Optional[str] = ...) -> None: ...
38
35
 
39
36
  class SearchResponse(_message.Message):
40
- __slots__ = ()
37
+ __slots__ = ("documents",)
41
38
  DOCUMENTS_FIELD_NUMBER: _ClassVar[int]
42
39
  documents: _containers.RepeatedCompositeFieldContainer[Document]
43
40
  def __init__(self, documents: _Optional[_Iterable[_Union[Document, _Mapping]]] = ...) -> None: ...
seltz-0.1.2/README.md DELETED
@@ -1,99 +0,0 @@
1
- # Seltz Python SDK
2
-
3
- The official Python SDK for the Seltz AI-powered search API.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- pip install seltz
9
- ```
10
-
11
- ## Quick Start
12
-
13
- ```python
14
- from seltz import Seltz
15
-
16
- # Initialize with API key
17
- client = Seltz(api_key="your-api-key")
18
-
19
- # Perform a search
20
- response = client.search("your search query")
21
-
22
- # Access results
23
- for document in response.documents:
24
- print(f"URL: {document.url}")
25
- print(f"Content: {document.content}")
26
- ```
27
-
28
- ## API Key
29
-
30
- Set your API key using one of these methods:
31
-
32
- 1. **Environment variable** (recommended):
33
- ```bash
34
- export SELTZ_API_KEY="your-api-key"
35
- ```
36
-
37
- 2. **Direct parameter**:
38
- ```python
39
- client = Seltz(api_key="your-api-key")
40
- ```
41
-
42
- ## API Reference
43
-
44
- ### `Seltz(api_key=None, endpoint="grpc.seltz.ai", insecure=False)`
45
-
46
- Creates a new Seltz client instance.
47
-
48
- **Parameters:**
49
- - `api_key` (str, optional): API key for authentication. Defaults to `SELTZ_API_KEY` environment variable.
50
- - `endpoint` (str): API endpoint. Defaults to "grpc.seltz.ai".
51
- - `insecure` (bool): Use insecure connection. Defaults to False.
52
-
53
- **Returns:** `Seltz` instance
54
-
55
- ### `client.search(text, max_documents=10)`
56
-
57
- Performs a search query.
58
-
59
- **Parameters:**
60
- - `text` (str): The search query text.
61
- - `max_documents` (int): Maximum number of documents to return. Defaults to 10.
62
-
63
- **Returns:** `SearchResponse` with a `documents` field containing search results.
64
-
65
- ## Error Handling
66
-
67
- ```python
68
- from seltz import (
69
- Seltz,
70
- SeltzConfigurationError,
71
- SeltzAuthenticationError,
72
- SeltzConnectionError,
73
- SeltzAPIError,
74
- SeltzTimeoutError,
75
- SeltzRateLimitError,
76
- )
77
-
78
- try:
79
- client = Seltz(api_key="your-api-key")
80
- response = client.search("query")
81
- except SeltzConfigurationError as e:
82
- print(f"Configuration error: {e}")
83
- except SeltzAuthenticationError as e:
84
- print(f"Authentication error: {e}")
85
- except SeltzConnectionError as e:
86
- print(f"Connection error: {e}")
87
- except SeltzTimeoutError as e:
88
- print(f"Timeout error: {e}")
89
- except SeltzRateLimitError as e:
90
- print(f"Rate limit error: {e}")
91
- except SeltzAPIError as e:
92
- print(f"API error: {e}")
93
- ```
94
-
95
- ## Requirements
96
-
97
- - Python 3.8+
98
- - grpcio >= 1.76.0
99
- - protobuf >= 6.33.1
@@ -1,48 +0,0 @@
1
- import os
2
-
3
- from .client import SeltzClient
4
- from .exceptions import SeltzConfigurationError
5
- from .services import SearchResponse
6
- from .services.search_service import SearchService
7
-
8
-
9
- class Seltz:
10
- """Main Seltz SDK client for interacting with the Seltz API."""
11
-
12
- _ENDPOINT: str = "grpc.seltz.ai"
13
-
14
- def __init__(
15
- self,
16
- api_key: str | None = os.environ.get("SELTZ_API_KEY"),
17
- endpoint: str = _ENDPOINT,
18
- insecure: bool = False,
19
- ):
20
- """Initialize the Seltz client.
21
-
22
- Args:
23
- api_key: API key for authentication. If None, will try to read from SELTZ_API_KEY environment variable
24
- endpoint: The API endpoint to connect to (default: grpc.seltz.ai)
25
- insecure: Whether to use insecure connection (default: False)
26
-
27
- Returns:
28
- Seltz: A new Seltz client instance
29
-
30
- Raises:
31
- SeltzConfigurationError: If no API key is provided
32
- """
33
- if api_key is None:
34
- raise SeltzConfigurationError("No API key provided")
35
- self._client = SeltzClient(endpoint=endpoint, api_key=api_key, insecure=insecure)
36
- self._search = SearchService(self._client.channel, self._client.api_key)
37
-
38
- def search(self, text: str, max_documents: int = 10) -> SearchResponse:
39
- """Perform a search query.
40
-
41
- Args:
42
- text: The search query text
43
- max_documents: Maximum number of documents to return (default: 10)
44
-
45
- Returns:
46
- SearchResponse: The search results
47
- """
48
- return self._search.search(text, max_documents=max_documents)
@@ -1,2 +0,0 @@
1
- grpcio>=1.76.0
2
- protobuf>=6.33.1
File without changes
File without changes