openrag-sdk 0.4.0.dev0__tar.gz → 0.8.1__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: openrag-sdk
3
- Version: 0.4.0.dev0
3
+ Version: 0.8.1
4
4
  Summary: Official Python SDK for OpenRAG API
5
5
  Author: OpenRAG Team
6
6
  License-Expression: MIT
@@ -13,17 +13,17 @@ Classifier: Development Status :: 4 - Beta
13
13
  Classifier: Intended Audience :: Developers
14
14
  Classifier: Programming Language :: Python :: 3
15
15
  Classifier: Programming Language :: Python :: 3 :: Only
16
- Classifier: Programming Language :: Python :: 3.10
17
16
  Classifier: Programming Language :: Python :: 3.11
18
17
  Classifier: Programming Language :: Python :: 3.12
19
18
  Classifier: Programming Language :: Python :: 3.13
20
19
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
20
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
21
  Classifier: Typing :: Typed
23
- Requires-Python: >=3.10
22
+ Requires-Python: >=3.11
24
23
  Description-Content-Type: text/markdown
25
24
  Requires-Dist: httpx>=0.25.0
26
25
  Requires-Dist: pydantic>=2.0.0
26
+ Requires-Dist: idna>=3.15
27
27
  Provides-Extra: dev
28
28
  Requires-Dist: pytest>=7.0.0; extra == "dev"
29
29
  Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
@@ -102,7 +102,8 @@ async for event in await client.chat.create(message="Explain RAG", stream=True):
102
102
  print(event.delta, end="", flush=True)
103
103
  elif event.type == "sources":
104
104
  for source in event.sources:
105
- print(f"\nSource: {source.filename}")
105
+ page_info = f" (page {source.page})" if source.page else ""
106
+ print(f"\nSource: {source.filename}{page_info}")
106
107
  elif event.type == "done":
107
108
  chat_id = event.chat_id
108
109
  ```
@@ -200,6 +201,64 @@ result = await client.documents.delete("report.pdf")
200
201
  print(f"Success: {result.success}")
201
202
  ```
202
203
 
204
+ ## Listing Files
205
+
206
+ `client.documents.list_files()` inventories everything in the knowledge base and
207
+ returns the metadata needed to drive knowledge filters and search.
208
+
209
+ ```python
210
+ import json
211
+
212
+ # List the first page of files
213
+ page = await client.documents.list_files(page_size=50)
214
+ for f in page.files:
215
+ print(f"{f.filename} ({f.mimetype}, {f.chunk_count} chunks)")
216
+
217
+ # Cursor-paginate through all files
218
+ after_key = None
219
+ while True:
220
+ page = await client.documents.list_files(page_size=100, after_key=after_key)
221
+ for f in page.files:
222
+ print(f.filename)
223
+ if page.after_key is None:
224
+ break
225
+ after_key = json.dumps(page.after_key)
226
+
227
+ # Filter and sort
228
+ page = await client.documents.list_files(
229
+ connector_type="sharepoint",
230
+ sort_by="indexed_time",
231
+ sort_order="desc",
232
+ )
233
+
234
+ # List → create knowledge filter workflow
235
+ page = await client.documents.list_files(connector_type="sharepoint")
236
+ filenames = [f.filename for f in page.files]
237
+ result = await client.knowledge_filters.create({
238
+ "name": "SharePoint docs",
239
+ "queryData": {"filters": {"data_sources": filenames}},
240
+ })
241
+ filter_id = result.id
242
+
243
+ # Use the filter in search and chat
244
+ results = await client.search.query("quarterly report", filter_id=filter_id)
245
+ response = await client.chat.create(message="Summarise Q3", filter_id=filter_id)
246
+ ```
247
+
248
+ For a one-shot listing without managing a cursor, `client.documents.get_all_files()`
249
+ returns all files in a single call — no parameters needed.
250
+
251
+ > **Note:** `get_all_files()` returns at most **500 files**. If your knowledge
252
+ > base contains more than 500 files, use `list_files()` with cursor pagination
253
+ > (`after_key`) to page through the full set.
254
+
255
+ ```python
256
+ # Get all files in a single call (no cursor to track)
257
+ page = await client.documents.get_all_files()
258
+ for f in page.files:
259
+ print(f.filename)
260
+ ```
261
+
203
262
  ## Settings
204
263
 
205
264
  ```python
@@ -72,7 +72,8 @@ async for event in await client.chat.create(message="Explain RAG", stream=True):
72
72
  print(event.delta, end="", flush=True)
73
73
  elif event.type == "sources":
74
74
  for source in event.sources:
75
- print(f"\nSource: {source.filename}")
75
+ page_info = f" (page {source.page})" if source.page else ""
76
+ print(f"\nSource: {source.filename}{page_info}")
76
77
  elif event.type == "done":
77
78
  chat_id = event.chat_id
78
79
  ```
@@ -170,6 +171,64 @@ result = await client.documents.delete("report.pdf")
170
171
  print(f"Success: {result.success}")
171
172
  ```
172
173
 
174
+ ## Listing Files
175
+
176
+ `client.documents.list_files()` inventories everything in the knowledge base and
177
+ returns the metadata needed to drive knowledge filters and search.
178
+
179
+ ```python
180
+ import json
181
+
182
+ # List the first page of files
183
+ page = await client.documents.list_files(page_size=50)
184
+ for f in page.files:
185
+ print(f"{f.filename} ({f.mimetype}, {f.chunk_count} chunks)")
186
+
187
+ # Cursor-paginate through all files
188
+ after_key = None
189
+ while True:
190
+ page = await client.documents.list_files(page_size=100, after_key=after_key)
191
+ for f in page.files:
192
+ print(f.filename)
193
+ if page.after_key is None:
194
+ break
195
+ after_key = json.dumps(page.after_key)
196
+
197
+ # Filter and sort
198
+ page = await client.documents.list_files(
199
+ connector_type="sharepoint",
200
+ sort_by="indexed_time",
201
+ sort_order="desc",
202
+ )
203
+
204
+ # List → create knowledge filter workflow
205
+ page = await client.documents.list_files(connector_type="sharepoint")
206
+ filenames = [f.filename for f in page.files]
207
+ result = await client.knowledge_filters.create({
208
+ "name": "SharePoint docs",
209
+ "queryData": {"filters": {"data_sources": filenames}},
210
+ })
211
+ filter_id = result.id
212
+
213
+ # Use the filter in search and chat
214
+ results = await client.search.query("quarterly report", filter_id=filter_id)
215
+ response = await client.chat.create(message="Summarise Q3", filter_id=filter_id)
216
+ ```
217
+
218
+ For a one-shot listing without managing a cursor, `client.documents.get_all_files()`
219
+ returns all files in a single call — no parameters needed.
220
+
221
+ > **Note:** `get_all_files()` returns at most **500 files**. If your knowledge
222
+ > base contains more than 500 files, use `list_files()` with cursor pagination
223
+ > (`after_key`) to page through the full set.
224
+
225
+ ```python
226
+ # Get all files in a single call (no cursor to track)
227
+ page = await client.documents.get_all_files()
228
+ for f in page.files:
229
+ print(f.filename)
230
+ ```
231
+
173
232
  ## Settings
174
233
 
175
234
  ```python
@@ -49,13 +49,17 @@ from .models import (
49
49
  DeleteDocumentResponse,
50
50
  DeleteKnowledgeFilterResponse,
51
51
  DoneEvent,
52
+ FileRecord,
53
+ GetAllFilesResponse,
52
54
  GetKnowledgeFilterResponse,
53
55
  IngestResponse,
54
56
  KnowledgeFilter,
55
57
  KnowledgeFilterQueryData,
56
58
  KnowledgeFilterSearchResponse,
57
59
  KnowledgeSettings,
60
+ ListFilesResponse,
58
61
  Message,
62
+ PrincipalLabel,
59
63
  SearchFilters,
60
64
  SearchResponse,
61
65
  SearchResult,
@@ -68,7 +72,7 @@ from .models import (
68
72
  UpdateKnowledgeFilterOptions,
69
73
  )
70
74
 
71
- __version__ = "0.4.0"
75
+ __version__ = "0.8.1"
72
76
 
73
77
  __all__ = [
74
78
  # Main client
@@ -82,6 +86,11 @@ __all__ = [
82
86
  "NotFoundError",
83
87
  "ValidationError",
84
88
  "ServerError",
89
+ # File models
90
+ "FileRecord",
91
+ "GetAllFilesResponse",
92
+ "ListFilesResponse",
93
+ "PrincipalLabel",
85
94
  # Models
86
95
  "ChatResponse",
87
96
  "ContentEvent",
@@ -5,7 +5,14 @@ from pathlib import Path
5
5
  from typing import TYPE_CHECKING, BinaryIO
6
6
 
7
7
  from .exceptions import NotFoundError
8
- from .models import DeleteDocumentResponse, IngestResponse, IngestTaskStatus
8
+ from .models import (
9
+ DeleteDocumentResponse,
10
+ FileRecord,
11
+ GetAllFilesResponse,
12
+ IngestResponse,
13
+ IngestTaskStatus,
14
+ ListFilesResponse,
15
+ )
9
16
 
10
17
  if TYPE_CHECKING:
11
18
  from .client import OpenRAGClient
@@ -176,3 +183,94 @@ class DocumentsClient:
176
183
 
177
184
  data = response.json()
178
185
  return DeleteDocumentResponse(**data)
186
+
187
+ async def list_files(
188
+ self,
189
+ *,
190
+ page: int = 1,
191
+ page_size: int = 25,
192
+ sort_by: str = "filename",
193
+ sort_order: str = "asc",
194
+ connector_type: str | None = None,
195
+ mimetype: str | None = None,
196
+ owner: str | None = None,
197
+ search: str | None = None,
198
+ after_key: str | None = None,
199
+ ) -> ListFilesResponse:
200
+ """
201
+ List ingested files with cursor-based composite-aggregation pagination (v2).
202
+
203
+ Args:
204
+ page: Page number (display only; use after_key for cursor navigation).
205
+ page_size: Number of files per page (1–500, default 25).
206
+ sort_by: Field to sort by. One of: filename, file_size, mimetype,
207
+ indexed_time, connector_type, chunk_count, owner.
208
+ sort_order: "asc" or "desc".
209
+ connector_type: Filter to files from a specific connector type.
210
+ mimetype: Filter to files with a specific MIME type.
211
+ owner: Filter to files owned by a specific user ID.
212
+ search: Substring/prefix match against filename.
213
+ after_key: JSON-encoded composite cursor from a previous response's
214
+ after_key field. Pass to fetch the next page.
215
+
216
+ Returns:
217
+ ListFilesResponse with files list, approximate total, and next
218
+ after_key cursor.
219
+ """
220
+ params: dict[str, str | int] = {
221
+ "page": page,
222
+ "page_size": page_size,
223
+ "sort_by": sort_by,
224
+ "sort_order": sort_order,
225
+ }
226
+ if connector_type is not None:
227
+ params["connector_type"] = connector_type
228
+ if mimetype is not None:
229
+ params["mimetype"] = mimetype
230
+ if owner is not None:
231
+ params["owner"] = owner
232
+ if search is not None:
233
+ params["search"] = search
234
+ if after_key is not None:
235
+ params["after_key"] = after_key
236
+
237
+ response = await self._client._request(
238
+ "GET",
239
+ "/api/v2/files",
240
+ params=params,
241
+ )
242
+ data = response.json()
243
+ return ListFilesResponse(
244
+ files=[FileRecord(**f) for f in data.get("files", [])],
245
+ total=data.get("total", 0),
246
+ is_approximate=data.get("is_approximate", True),
247
+ page=data.get("page", page),
248
+ page_size=data.get("page_size", page_size),
249
+ after_key=data.get("after_key"),
250
+ )
251
+
252
+ async def get_all_files(self) -> GetAllFilesResponse:
253
+ """
254
+ Return all ingested files (v1).
255
+
256
+ No parameters — just returns everything in the knowledge base.
257
+
258
+ Note:
259
+ Returns at most 500 files. If your knowledge base contains more
260
+ than 500 files, use ``list_files()`` with cursor pagination
261
+ (``after_key``) to page through the full set.
262
+
263
+ Returns:
264
+ GetAllFilesResponse with files list, total count, page, and page_size.
265
+ """
266
+ response = await self._client._request(
267
+ "GET",
268
+ "/api/v1/files/get_all",
269
+ )
270
+ data = response.json()
271
+ return GetAllFilesResponse(
272
+ files=[FileRecord(**f) for f in data.get("files", [])],
273
+ total=data.get("total", 0),
274
+ page=data.get("page", 1),
275
+ page_size=data.get("page_size", 500),
276
+ )
@@ -284,3 +284,55 @@ class DeleteKnowledgeFilterResponse(BaseModel):
284
284
 
285
285
  success: bool
286
286
  error: str | None = None
287
+
288
+
289
+ class PrincipalLabel(BaseModel):
290
+ """A principal label entry in a file's ACL (connector-ingested files only)."""
291
+
292
+ principal: str
293
+ kind: str
294
+ provider: str
295
+ display_name: str = ""
296
+ email: str = ""
297
+ external_id: str = ""
298
+
299
+
300
+ class FileRecord(BaseModel):
301
+ """A single ingested file record returned by the list-files endpoint."""
302
+
303
+ filename: str
304
+ document_id: str
305
+ mimetype: str
306
+ file_size: int
307
+ source_url: str
308
+ owner: str
309
+ owner_name: str
310
+ owner_email: str
311
+ connector_type: str
312
+ embedding_model: str
313
+ embedding_dimensions: int | None = None
314
+ indexed_time: str
315
+ chunk_count: int
316
+ allowed_users: list[str] = Field(default_factory=list)
317
+ allowed_groups: list[str] = Field(default_factory=list)
318
+ allowed_principal_labels: list[PrincipalLabel] = Field(default_factory=list)
319
+
320
+
321
+ class GetAllFilesResponse(BaseModel):
322
+ """Response from GET /v1/files/get_all — offset pagination, no cursor."""
323
+
324
+ files: list[FileRecord]
325
+ total: int
326
+ page: int
327
+ page_size: int
328
+
329
+
330
+ class ListFilesResponse(BaseModel):
331
+ """Response from GET /v2/files — cursor-based composite-aggregation pagination."""
332
+
333
+ files: list[FileRecord]
334
+ total: int
335
+ is_approximate: bool
336
+ page: int
337
+ page_size: int
338
+ after_key: dict | None = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openrag-sdk
3
- Version: 0.4.0.dev0
3
+ Version: 0.8.1
4
4
  Summary: Official Python SDK for OpenRAG API
5
5
  Author: OpenRAG Team
6
6
  License-Expression: MIT
@@ -13,17 +13,17 @@ Classifier: Development Status :: 4 - Beta
13
13
  Classifier: Intended Audience :: Developers
14
14
  Classifier: Programming Language :: Python :: 3
15
15
  Classifier: Programming Language :: Python :: 3 :: Only
16
- Classifier: Programming Language :: Python :: 3.10
17
16
  Classifier: Programming Language :: Python :: 3.11
18
17
  Classifier: Programming Language :: Python :: 3.12
19
18
  Classifier: Programming Language :: Python :: 3.13
20
19
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
20
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
21
  Classifier: Typing :: Typed
23
- Requires-Python: >=3.10
22
+ Requires-Python: >=3.11
24
23
  Description-Content-Type: text/markdown
25
24
  Requires-Dist: httpx>=0.25.0
26
25
  Requires-Dist: pydantic>=2.0.0
26
+ Requires-Dist: idna>=3.15
27
27
  Provides-Extra: dev
28
28
  Requires-Dist: pytest>=7.0.0; extra == "dev"
29
29
  Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
@@ -102,7 +102,8 @@ async for event in await client.chat.create(message="Explain RAG", stream=True):
102
102
  print(event.delta, end="", flush=True)
103
103
  elif event.type == "sources":
104
104
  for source in event.sources:
105
- print(f"\nSource: {source.filename}")
105
+ page_info = f" (page {source.page})" if source.page else ""
106
+ print(f"\nSource: {source.filename}{page_info}")
106
107
  elif event.type == "done":
107
108
  chat_id = event.chat_id
108
109
  ```
@@ -200,6 +201,64 @@ result = await client.documents.delete("report.pdf")
200
201
  print(f"Success: {result.success}")
201
202
  ```
202
203
 
204
+ ## Listing Files
205
+
206
+ `client.documents.list_files()` inventories everything in the knowledge base and
207
+ returns the metadata needed to drive knowledge filters and search.
208
+
209
+ ```python
210
+ import json
211
+
212
+ # List the first page of files
213
+ page = await client.documents.list_files(page_size=50)
214
+ for f in page.files:
215
+ print(f"{f.filename} ({f.mimetype}, {f.chunk_count} chunks)")
216
+
217
+ # Cursor-paginate through all files
218
+ after_key = None
219
+ while True:
220
+ page = await client.documents.list_files(page_size=100, after_key=after_key)
221
+ for f in page.files:
222
+ print(f.filename)
223
+ if page.after_key is None:
224
+ break
225
+ after_key = json.dumps(page.after_key)
226
+
227
+ # Filter and sort
228
+ page = await client.documents.list_files(
229
+ connector_type="sharepoint",
230
+ sort_by="indexed_time",
231
+ sort_order="desc",
232
+ )
233
+
234
+ # List → create knowledge filter workflow
235
+ page = await client.documents.list_files(connector_type="sharepoint")
236
+ filenames = [f.filename for f in page.files]
237
+ result = await client.knowledge_filters.create({
238
+ "name": "SharePoint docs",
239
+ "queryData": {"filters": {"data_sources": filenames}},
240
+ })
241
+ filter_id = result.id
242
+
243
+ # Use the filter in search and chat
244
+ results = await client.search.query("quarterly report", filter_id=filter_id)
245
+ response = await client.chat.create(message="Summarise Q3", filter_id=filter_id)
246
+ ```
247
+
248
+ For a one-shot listing without managing a cursor, `client.documents.get_all_files()`
249
+ returns all files in a single call — no parameters needed.
250
+
251
+ > **Note:** `get_all_files()` returns at most **500 files**. If your knowledge
252
+ > base contains more than 500 files, use `list_files()` with cursor pagination
253
+ > (`after_key`) to page through the full set.
254
+
255
+ ```python
256
+ # Get all files in a single call (no cursor to track)
257
+ page = await client.documents.get_all_files()
258
+ for f in page.files:
259
+ print(f.filename)
260
+ ```
261
+
203
262
  ## Settings
204
263
 
205
264
  ```python
@@ -1,5 +1,6 @@
1
1
  httpx>=0.25.0
2
2
  pydantic>=2.0.0
3
+ idna>=3.15
3
4
 
4
5
  [dev]
5
6
  pytest>=7.0.0
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "openrag-sdk"
7
- version = "0.4.0.dev0"
7
+ version = "0.8.1"
8
8
  description = "Official Python SDK for OpenRAG API"
9
9
  readme = "README.md"
10
10
  license = "MIT"
11
- requires-python = ">=3.10"
11
+ requires-python = ">=3.11"
12
12
  authors = [
13
13
  { name = "OpenRAG Team" }
14
14
  ]
@@ -18,7 +18,6 @@ classifiers = [
18
18
  "Intended Audience :: Developers",
19
19
  "Programming Language :: Python :: 3",
20
20
  "Programming Language :: Python :: 3 :: Only",
21
- "Programming Language :: Python :: 3.10",
22
21
  "Programming Language :: Python :: 3.11",
23
22
  "Programming Language :: Python :: 3.12",
24
23
  "Programming Language :: Python :: 3.13",
@@ -29,6 +28,7 @@ classifiers = [
29
28
  dependencies = [
30
29
  "httpx>=0.25.0",
31
30
  "pydantic>=2.0.0",
31
+ "idna>=3.15",
32
32
  ]
33
33
 
34
34
  [project.optional-dependencies]
@@ -53,12 +53,12 @@ testpaths = ["tests"]
53
53
 
54
54
  [tool.ruff]
55
55
  line-length = 88
56
- target-version = "py310"
56
+ target-version = "py311"
57
57
 
58
58
  [tool.ruff.lint]
59
59
  select = ["E", "F", "I", "UP"]
60
60
 
61
61
  [tool.mypy]
62
- python_version = "3.10"
62
+ python_version = "3.11"
63
63
  strict = true
64
64
 
File without changes