morphik 0.1.5__py3-none-any.whl → 0.1.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
morphik/__init__.py CHANGED
@@ -12,4 +12,4 @@ __all__ = [
12
12
  "Document",
13
13
  ]
14
14
 
15
- __version__ = "0.1.4"
15
+ __version__ = "0.1.6"
morphik/_internal.py CHANGED
@@ -232,7 +232,7 @@ class _MorphikClientLogic:
232
232
  hop_depth: int,
233
233
  include_paths: bool,
234
234
  prompt_overrides: Optional[Dict],
235
- folder_name: Optional[str],
235
+ folder_name: Optional[Union[str, List[str]]],
236
236
  end_user_id: Optional[str],
237
237
  schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
238
238
  ) -> Dict[str, Any]:
@@ -278,7 +278,7 @@ class _MorphikClientLogic:
278
278
  k: int,
279
279
  min_score: float,
280
280
  use_colpali: bool,
281
- folder_name: Optional[str],
281
+ folder_name: Optional[Union[str, List[str]]],
282
282
  end_user_id: Optional[str],
283
283
  ) -> Dict[str, Any]:
284
284
  """Prepare request for retrieve_chunks endpoint"""
@@ -302,7 +302,7 @@ class _MorphikClientLogic:
302
302
  k: int,
303
303
  min_score: float,
304
304
  use_colpali: bool,
305
- folder_name: Optional[str],
305
+ folder_name: Optional[Union[str, List[str]]],
306
306
  end_user_id: Optional[str],
307
307
  ) -> Dict[str, Any]:
308
308
  """Prepare request for retrieve_docs endpoint"""
@@ -324,7 +324,7 @@ class _MorphikClientLogic:
324
324
  skip: int,
325
325
  limit: int,
326
326
  filters: Optional[Dict[str, Any]],
327
- folder_name: Optional[str],
327
+ folder_name: Optional[Union[str, List[str]]],
328
328
  end_user_id: Optional[str],
329
329
  ) -> Tuple[Dict[str, Any], Dict[str, Any]]:
330
330
  """Prepare request for list_documents endpoint"""
@@ -340,7 +340,7 @@ class _MorphikClientLogic:
340
340
  return params, data
341
341
 
342
342
  def _prepare_batch_get_documents_request(
343
- self, document_ids: List[str], folder_name: Optional[str], end_user_id: Optional[str]
343
+ self, document_ids: List[str], folder_name: Optional[Union[str, List[str]]], end_user_id: Optional[str]
344
344
  ) -> Dict[str, Any]:
345
345
  """Prepare request for batch_get_documents endpoint"""
346
346
  if folder_name or end_user_id:
@@ -355,7 +355,7 @@ class _MorphikClientLogic:
355
355
  def _prepare_batch_get_chunks_request(
356
356
  self,
357
357
  sources: List[Union[ChunkSource, Dict[str, Any]]],
358
- folder_name: Optional[str],
358
+ folder_name: Optional[Union[str, List[str]]],
359
359
  end_user_id: Optional[str],
360
360
  ) -> Dict[str, Any]:
361
361
  """Prepare request for batch_get_chunks endpoint"""
@@ -382,7 +382,7 @@ class _MorphikClientLogic:
382
382
  filters: Optional[Dict[str, Any]],
383
383
  documents: Optional[List[str]],
384
384
  prompt_overrides: Optional[Union[GraphPromptOverrides, Dict[str, Any]]],
385
- folder_name: Optional[str],
385
+ folder_name: Optional[Union[str, List[str]]],
386
386
  end_user_id: Optional[str],
387
387
  ) -> Dict[str, Any]:
388
388
  """Prepare request for create_graph endpoint"""
@@ -408,7 +408,7 @@ class _MorphikClientLogic:
408
408
  additional_filters: Optional[Dict[str, Any]],
409
409
  additional_documents: Optional[List[str]],
410
410
  prompt_overrides: Optional[Union[GraphPromptOverrides, Dict[str, Any]]],
411
- folder_name: Optional[str],
411
+ folder_name: Optional[Union[str, List[str]]],
412
412
  end_user_id: Optional[str],
413
413
  ) -> Dict[str, Any]:
414
414
  """Prepare request for update_graph endpoint"""
morphik/async_.py CHANGED
@@ -286,6 +286,7 @@ class AsyncFolder:
286
286
  k: int = 4,
287
287
  min_score: float = 0.0,
288
288
  use_colpali: bool = True,
289
+ additional_folders: Optional[List[str]] = None,
289
290
  ) -> List[FinalChunkResult]:
290
291
  """
291
292
  Retrieve relevant chunks within this folder.
@@ -296,12 +297,14 @@ class AsyncFolder:
296
297
  k: Number of results (default: 4)
297
298
  min_score: Minimum similarity threshold (default: 0.0)
298
299
  use_colpali: Whether to use ColPali-style embedding model
300
+ additional_folders: Optional list of additional folder names to further scope operations
299
301
 
300
302
  Returns:
301
303
  List[FinalChunkResult]: List of relevant chunks
302
304
  """
305
+ effective_folder = self._merge_folders(additional_folders)
303
306
  payload = self._client._logic._prepare_retrieve_chunks_request(
304
- query, filters, k, min_score, use_colpali, self._name, None
307
+ query, filters, k, min_score, use_colpali, effective_folder, None
305
308
  )
306
309
  response = await self._client._request("POST", "retrieve/chunks", data=payload)
307
310
  return self._client._logic._parse_chunk_result_list_response(response)
@@ -313,6 +316,7 @@ class AsyncFolder:
313
316
  k: int = 4,
314
317
  min_score: float = 0.0,
315
318
  use_colpali: bool = True,
319
+ additional_folders: Optional[List[str]] = None,
316
320
  ) -> List[DocumentResult]:
317
321
  """
318
322
  Retrieve relevant documents within this folder.
@@ -323,12 +327,14 @@ class AsyncFolder:
323
327
  k: Number of results (default: 4)
324
328
  min_score: Minimum similarity threshold (default: 0.0)
325
329
  use_colpali: Whether to use ColPali-style embedding model
330
+ additional_folders: Optional list of additional folder names to further scope operations
326
331
 
327
332
  Returns:
328
333
  List[DocumentResult]: List of relevant documents
329
334
  """
335
+ effective_folder = self._merge_folders(additional_folders)
330
336
  payload = self._client._logic._prepare_retrieve_docs_request(
331
- query, filters, k, min_score, use_colpali, self._name, None
337
+ query, filters, k, min_score, use_colpali, effective_folder, None
332
338
  )
333
339
  response = await self._client._request("POST", "retrieve/docs", data=payload)
334
340
  return self._client._logic._parse_document_result_list_response(response)
@@ -346,6 +352,7 @@ class AsyncFolder:
346
352
  hop_depth: int = 1,
347
353
  include_paths: bool = False,
348
354
  prompt_overrides: Optional[Union[QueryPromptOverrides, Dict[str, Any]]] = None,
355
+ additional_folders: Optional[List[str]] = None,
349
356
  schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
350
357
  ) -> CompletionResponse:
351
358
  """
@@ -364,10 +371,12 @@ class AsyncFolder:
364
371
  include_paths: Whether to include relationship paths in the response
365
372
  prompt_overrides: Optional customizations for entity extraction, resolution, and query prompts
366
373
  schema: Optional schema for structured output
374
+ additional_folders: Optional list of additional folder names to further scope operations
367
375
 
368
376
  Returns:
369
377
  CompletionResponse: Generated completion or structured output
370
378
  """
379
+ effective_folder = self._merge_folders(additional_folders)
371
380
  payload = self._client._logic._prepare_query_request(
372
381
  query,
373
382
  filters,
@@ -380,15 +389,31 @@ class AsyncFolder:
380
389
  hop_depth,
381
390
  include_paths,
382
391
  prompt_overrides,
383
- self._name,
392
+ effective_folder,
384
393
  None,
385
394
  schema,
386
395
  )
396
+
397
+ # Add schema to payload if provided
398
+ if schema:
399
+ # If schema is a Pydantic model class, we need to serialize it to a schema dict
400
+ if isinstance(schema, type) and issubclass(schema, BaseModel):
401
+ payload["schema"] = schema.model_json_schema()
402
+ else:
403
+ payload["schema"] = schema
404
+
405
+ # Add a hint to the query to return in JSON format
406
+ payload["query"] = f"{payload['query']}\nReturn the answer in JSON format according to the required schema."
407
+
387
408
  response = await self._client._request("POST", "query", data=payload)
388
409
  return self._client._logic._parse_completion_response(response)
389
410
 
390
411
  async def list_documents(
391
- self, skip: int = 0, limit: int = 100, filters: Optional[Dict[str, Any]] = None
412
+ self,
413
+ skip: int = 0,
414
+ limit: int = 100,
415
+ filters: Optional[Dict[str, Any]] = None,
416
+ additional_folders: Optional[List[str]] = None,
392
417
  ) -> List[Document]:
393
418
  """
394
419
  List accessible documents within this folder.
@@ -397,48 +422,57 @@ class AsyncFolder:
397
422
  skip: Number of documents to skip
398
423
  limit: Maximum number of documents to return
399
424
  filters: Optional filters
425
+ additional_folders: Optional list of additional folder names to further scope operations
400
426
 
401
427
  Returns:
402
428
  List[Document]: List of documents
403
429
  """
404
- params, data = self._client._logic._prepare_list_documents_request(skip, limit, filters, self._name, None)
430
+ effective_folder = self._merge_folders(additional_folders)
431
+ params, data = self._client._logic._prepare_list_documents_request(skip, limit, filters, effective_folder, None)
405
432
  response = await self._client._request("POST", "documents", data=data, params=params)
406
433
  docs = self._client._logic._parse_document_list_response(response)
407
434
  for doc in docs:
408
435
  doc._client = self._client
409
436
  return docs
410
437
 
411
- async def batch_get_documents(self, document_ids: List[str]) -> List[Document]:
438
+ async def batch_get_documents(
439
+ self, document_ids: List[str], additional_folders: Optional[List[str]] = None
440
+ ) -> List[Document]:
412
441
  """
413
442
  Retrieve multiple documents by their IDs in a single batch operation within this folder.
414
443
 
415
444
  Args:
416
445
  document_ids: List of document IDs to retrieve
446
+ additional_folders: Optional list of additional folder names to further scope operations
417
447
 
418
448
  Returns:
419
449
  List[Document]: List of document metadata for found documents
420
450
  """
421
- # API expects a dict with document_ids key
422
- request = {"document_ids": document_ids}
423
- if self._name:
424
- request["folder_name"] = self._name
451
+ merged = self._merge_folders(additional_folders)
452
+ request = {"document_ids": document_ids, "folder_name": merged}
425
453
  response = await self._client._request("POST", "batch/documents", data=request)
426
454
  docs = self._client._logic._parse_document_list_response(response)
427
455
  for doc in docs:
428
456
  doc._client = self._client
429
457
  return docs
430
458
 
431
- async def batch_get_chunks(self, sources: List[Union[ChunkSource, Dict[str, Any]]]) -> List[FinalChunkResult]:
459
+ async def batch_get_chunks(
460
+ self,
461
+ sources: List[Union[ChunkSource, Dict[str, Any]]],
462
+ additional_folders: Optional[List[str]] = None,
463
+ ) -> List[FinalChunkResult]:
432
464
  """
433
465
  Retrieve specific chunks by their document ID and chunk number in a single batch operation within this folder.
434
466
 
435
467
  Args:
436
468
  sources: List of ChunkSource objects or dictionaries with document_id and chunk_number
469
+ additional_folders: Optional list of additional folder names to further scope operations
437
470
 
438
471
  Returns:
439
472
  List[FinalChunkResult]: List of chunk results
440
473
  """
441
- request = self._client._logic._prepare_batch_get_chunks_request(sources, self._name, None)
474
+ merged = self._merge_folders(additional_folders)
475
+ request = self._client._logic._prepare_batch_get_chunks_request(sources, merged, None)
442
476
  response = await self._client._request("POST", "batch/chunks", data=request)
443
477
  return self._client._logic._parse_chunk_result_list_response(response)
444
478
 
@@ -465,7 +499,9 @@ class AsyncFolder:
465
499
  name, filters, documents, prompt_overrides, self._name, None
466
500
  )
467
501
  response = await self._client._request("POST", "graph/create", data=request)
468
- return self._client._logic._parse_graph_response(response)
502
+ graph = self._logic._parse_graph_response(response)
503
+ graph._client = self # Attach AsyncMorphik client for polling helpers
504
+ return graph
469
505
 
470
506
  async def update_graph(
471
507
  self,
@@ -490,7 +526,9 @@ class AsyncFolder:
490
526
  name, additional_filters, additional_documents, prompt_overrides, self._name, None
491
527
  )
492
528
  response = await self._client._request("POST", f"graph/{name}/update", data=request)
493
- return self._client._logic._parse_graph_response(response)
529
+ graph = self._logic._parse_graph_response(response)
530
+ graph._client = self
531
+ return graph
494
532
 
495
533
  async def delete_document_by_filename(self, filename: str) -> Dict[str, str]:
496
534
  """
@@ -511,6 +549,18 @@ class AsyncFolder:
511
549
  # Then delete by ID
512
550
  return await self._client.delete_document(doc.external_id)
513
551
 
552
+ # Helper --------------------------------------------------------------
553
+ def _merge_folders(self, additional_folders: Optional[List[str]] = None) -> Union[str, List[str]]:
554
+ """Return the effective folder scope for this folder instance.
555
+
556
+ If *additional_folders* is provided it will be combined with the scoped
557
+ folder (*self._name*) and returned as a list. Otherwise just
558
+ *self._name* is returned so the API keeps backward-compatibility with
559
+ accepting a single string."""
560
+ if not additional_folders:
561
+ return self._name
562
+ return [self._name] + additional_folders
563
+
514
564
 
515
565
  class AsyncUserScope:
516
566
  """
@@ -769,6 +819,7 @@ class AsyncUserScope:
769
819
  k: int = 4,
770
820
  min_score: float = 0.0,
771
821
  use_colpali: bool = True,
822
+ additional_folders: Optional[List[str]] = None,
772
823
  ) -> List[FinalChunkResult]:
773
824
  """
774
825
  Retrieve relevant chunks as this end user.
@@ -779,12 +830,14 @@ class AsyncUserScope:
779
830
  k: Number of results (default: 4)
780
831
  min_score: Minimum similarity threshold (default: 0.0)
781
832
  use_colpali: Whether to use ColPali-style embedding model
833
+ additional_folders: Optional list of additional folder names to further scope operations
782
834
 
783
835
  Returns:
784
836
  List[FinalChunkResult]: List of relevant chunks
785
837
  """
838
+ effective_folder = self._merge_folders(additional_folders)
786
839
  payload = self._client._logic._prepare_retrieve_chunks_request(
787
- query, filters, k, min_score, use_colpali, self._folder_name, self._end_user_id
840
+ query, filters, k, min_score, use_colpali, effective_folder, self._end_user_id
788
841
  )
789
842
  response = await self._client._request("POST", "retrieve/chunks", data=payload)
790
843
  return self._client._logic._parse_chunk_result_list_response(response)
@@ -796,6 +849,7 @@ class AsyncUserScope:
796
849
  k: int = 4,
797
850
  min_score: float = 0.0,
798
851
  use_colpali: bool = True,
852
+ additional_folders: Optional[List[str]] = None,
799
853
  ) -> List[DocumentResult]:
800
854
  """
801
855
  Retrieve relevant documents as this end user.
@@ -806,12 +860,14 @@ class AsyncUserScope:
806
860
  k: Number of results (default: 4)
807
861
  min_score: Minimum similarity threshold (default: 0.0)
808
862
  use_colpali: Whether to use ColPali-style embedding model
863
+ additional_folders: Optional list of additional folder names to further scope operations
809
864
 
810
865
  Returns:
811
866
  List[DocumentResult]: List of relevant documents
812
867
  """
868
+ effective_folder = self._merge_folders(additional_folders)
813
869
  payload = self._client._logic._prepare_retrieve_docs_request(
814
- query, filters, k, min_score, use_colpali, self._folder_name, self._end_user_id
870
+ query, filters, k, min_score, use_colpali, effective_folder, self._end_user_id
815
871
  )
816
872
  response = await self._client._request("POST", "retrieve/docs", data=payload)
817
873
  return self._client._logic._parse_document_result_list_response(response)
@@ -829,6 +885,7 @@ class AsyncUserScope:
829
885
  hop_depth: int = 1,
830
886
  include_paths: bool = False,
831
887
  prompt_overrides: Optional[Union[QueryPromptOverrides, Dict[str, Any]]] = None,
888
+ additional_folders: Optional[List[str]] = None,
832
889
  schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
833
890
  ) -> CompletionResponse:
834
891
  """
@@ -847,10 +904,12 @@ class AsyncUserScope:
847
904
  include_paths: Whether to include relationship paths in the response
848
905
  prompt_overrides: Optional customizations for entity extraction, resolution, and query prompts
849
906
  schema: Optional schema for structured output
907
+ additional_folders: Optional list of additional folder names to further scope operations
850
908
 
851
909
  Returns:
852
910
  CompletionResponse: Generated completion or structured output
853
911
  """
912
+ effective_folder = self._merge_folders(additional_folders)
854
913
  payload = self._client._logic._prepare_query_request(
855
914
  query,
856
915
  filters,
@@ -863,15 +922,31 @@ class AsyncUserScope:
863
922
  hop_depth,
864
923
  include_paths,
865
924
  prompt_overrides,
866
- self.folder_name,
867
- self.end_user_id,
925
+ effective_folder,
926
+ self._end_user_id,
868
927
  schema,
869
928
  )
929
+
930
+ # Add schema to payload if provided
931
+ if schema:
932
+ # If schema is a Pydantic model class, we need to serialize it to a schema dict
933
+ if isinstance(schema, type) and issubclass(schema, BaseModel):
934
+ payload["schema"] = schema.model_json_schema()
935
+ else:
936
+ payload["schema"] = schema
937
+
938
+ # Add a hint to the query to return in JSON format
939
+ payload["query"] = f"{payload['query']}\nReturn the answer in JSON format according to the required schema."
940
+
870
941
  response = await self._client._request("POST", "query", data=payload)
871
942
  return self._client._logic._parse_completion_response(response)
872
943
 
873
944
  async def list_documents(
874
- self, skip: int = 0, limit: int = 100, filters: Optional[Dict[str, Any]] = None
945
+ self,
946
+ skip: int = 0,
947
+ limit: int = 100,
948
+ filters: Optional[Dict[str, Any]] = None,
949
+ folder_name: Optional[Union[str, List[str]]] = None,
875
950
  ) -> List[Document]:
876
951
  """
877
952
  List accessible documents for this end user.
@@ -880,12 +955,13 @@ class AsyncUserScope:
880
955
  skip: Number of documents to skip
881
956
  limit: Maximum number of documents to return
882
957
  filters: Optional filters
958
+ folder_name: Optional folder name (or list of names) to scope the request
883
959
 
884
960
  Returns:
885
961
  List[Document]: List of documents
886
962
  """
887
963
  params, data = self._client._logic._prepare_list_documents_request(
888
- skip, limit, filters, self._folder_name, self._end_user_id
964
+ skip, limit, filters, folder_name, self._end_user_id
889
965
  )
890
966
  response = await self._client._request("POST", "documents", data=data, params=params)
891
967
  docs = self._client._logic._parse_document_list_response(response)
@@ -893,12 +969,15 @@ class AsyncUserScope:
893
969
  doc._client = self._client
894
970
  return docs
895
971
 
896
- async def batch_get_documents(self, document_ids: List[str]) -> List[Document]:
972
+ async def batch_get_documents(
973
+ self, document_ids: List[str], folder_name: Optional[Union[str, List[str]]] = None
974
+ ) -> List[Document]:
897
975
  """
898
976
  Retrieve multiple documents by their IDs in a single batch operation for this end user.
899
977
 
900
978
  Args:
901
979
  document_ids: List of document IDs to retrieve
980
+ folder_name: Optional folder name (or list of names) to scope the request
902
981
 
903
982
  Returns:
904
983
  List[Document]: List of document metadata for found documents
@@ -915,12 +994,17 @@ class AsyncUserScope:
915
994
  doc._client = self._client
916
995
  return docs
917
996
 
918
- async def batch_get_chunks(self, sources: List[Union[ChunkSource, Dict[str, Any]]]) -> List[FinalChunkResult]:
997
+ async def batch_get_chunks(
998
+ self,
999
+ sources: List[Union[ChunkSource, Dict[str, Any]]],
1000
+ folder_name: Optional[Union[str, List[str]]] = None,
1001
+ ) -> List[FinalChunkResult]:
919
1002
  """
920
1003
  Retrieve specific chunks by their document ID and chunk number in a single batch operation for this end user.
921
1004
 
922
1005
  Args:
923
1006
  sources: List of ChunkSource objects or dictionaries with document_id and chunk_number
1007
+ folder_name: Optional folder name (or list of names) to scope the request
924
1008
 
925
1009
  Returns:
926
1010
  List[FinalChunkResult]: List of chunk results
@@ -952,7 +1036,9 @@ class AsyncUserScope:
952
1036
  name, filters, documents, prompt_overrides, self._folder_name, self._end_user_id
953
1037
  )
954
1038
  response = await self._client._request("POST", "graph/create", data=request)
955
- return self._client._logic._parse_graph_response(response)
1039
+ graph = self._logic._parse_graph_response(response)
1040
+ graph._client = self
1041
+ return graph
956
1042
 
957
1043
  async def update_graph(
958
1044
  self,
@@ -982,7 +1068,9 @@ class AsyncUserScope:
982
1068
  self._end_user_id,
983
1069
  )
984
1070
  response = await self._client._request("POST", f"graph/{name}/update", data=request)
985
- return self._client._logic._parse_graph_response(response)
1071
+ graph = self._logic._parse_graph_response(response)
1072
+ graph._client = self
1073
+ return graph
986
1074
 
987
1075
  async def delete_document_by_filename(self, filename: str) -> Dict[str, str]:
988
1076
  """
@@ -1377,6 +1465,7 @@ class AsyncMorphik:
1377
1465
  k: int = 4,
1378
1466
  min_score: float = 0.0,
1379
1467
  use_colpali: bool = True,
1468
+ folder_name: Optional[Union[str, List[str]]] = None,
1380
1469
  ) -> List[FinalChunkResult]:
1381
1470
  """
1382
1471
  Search for relevant chunks.
@@ -1399,7 +1488,10 @@ class AsyncMorphik:
1399
1488
  )
1400
1489
  ```
1401
1490
  """
1402
- payload = self._logic._prepare_retrieve_chunks_request(query, filters, k, min_score, use_colpali, None, None)
1491
+ effective_folder = folder_name if folder_name is not None else None
1492
+ payload = self._logic._prepare_retrieve_chunks_request(
1493
+ query, filters, k, min_score, use_colpali, effective_folder, None
1494
+ )
1403
1495
  response = await self._request("POST", "retrieve/chunks", data=payload)
1404
1496
  return self._logic._parse_chunk_result_list_response(response)
1405
1497
 
@@ -1410,6 +1502,7 @@ class AsyncMorphik:
1410
1502
  k: int = 4,
1411
1503
  min_score: float = 0.0,
1412
1504
  use_colpali: bool = True,
1505
+ folder_name: Optional[Union[str, List[str]]] = None,
1413
1506
  ) -> List[DocumentResult]:
1414
1507
  """
1415
1508
  Retrieve relevant documents.
@@ -1432,7 +1525,10 @@ class AsyncMorphik:
1432
1525
  )
1433
1526
  ```
1434
1527
  """
1435
- payload = self._logic._prepare_retrieve_docs_request(query, filters, k, min_score, use_colpali, None, None)
1528
+ effective_folder = folder_name if folder_name is not None else None
1529
+ payload = self._logic._prepare_retrieve_docs_request(
1530
+ query, filters, k, min_score, use_colpali, effective_folder, None
1531
+ )
1436
1532
  response = await self._request("POST", "retrieve/docs", data=payload)
1437
1533
  return self._logic._parse_document_result_list_response(response)
1438
1534
 
@@ -1449,6 +1545,7 @@ class AsyncMorphik:
1449
1545
  hop_depth: int = 1,
1450
1546
  include_paths: bool = False,
1451
1547
  prompt_overrides: Optional[Union[QueryPromptOverrides, Dict[str, Any]]] = None,
1548
+ folder_name: Optional[Union[str, List[str]]] = None,
1452
1549
  schema: Optional[Union[Type[BaseModel], Dict[str, Any]]] = None,
1453
1550
  ) -> CompletionResponse:
1454
1551
  """
@@ -1539,6 +1636,7 @@ class AsyncMorphik:
1539
1636
  print(f"- {evidence}")
1540
1637
  ```
1541
1638
  """
1639
+ effective_folder = folder_name if folder_name is not None else None
1542
1640
  payload = self._logic._prepare_query_request(
1543
1641
  query,
1544
1642
  filters,
@@ -1551,7 +1649,7 @@ class AsyncMorphik:
1551
1649
  hop_depth,
1552
1650
  include_paths,
1553
1651
  prompt_overrides,
1554
- None,
1652
+ effective_folder,
1555
1653
  None,
1556
1654
  schema,
1557
1655
  )
@@ -1571,7 +1669,11 @@ class AsyncMorphik:
1571
1669
  return self._logic._parse_completion_response(response)
1572
1670
 
1573
1671
  async def list_documents(
1574
- self, skip: int = 0, limit: int = 100, filters: Optional[Dict[str, Any]] = None
1672
+ self,
1673
+ skip: int = 0,
1674
+ limit: int = 100,
1675
+ filters: Optional[Dict[str, Any]] = None,
1676
+ folder_name: Optional[Union[str, List[str]]] = None,
1575
1677
  ) -> List[Document]:
1576
1678
  """
1577
1679
  List accessible documents.
@@ -1580,6 +1682,7 @@ class AsyncMorphik:
1580
1682
  skip: Number of documents to skip
1581
1683
  limit: Maximum number of documents to return
1582
1684
  filters: Optional filters
1685
+ folder_name: Optional folder name (or list of names) to scope the request
1583
1686
 
1584
1687
  Returns:
1585
1688
  List[Document]: List of accessible documents
@@ -1593,7 +1696,7 @@ class AsyncMorphik:
1593
1696
  next_page = await db.list_documents(skip=10, limit=10, filters={"department": "research"})
1594
1697
  ```
1595
1698
  """
1596
- params, data = self._logic._prepare_list_documents_request(skip, limit, filters, None, None)
1699
+ params, data = self._logic._prepare_list_documents_request(skip, limit, filters, folder_name, None)
1597
1700
  response = await self._request("POST", "documents", data=data, params=params)
1598
1701
  docs = self._logic._parse_document_list_response(response)
1599
1702
  for doc in docs:
@@ -2047,12 +2150,15 @@ class AsyncMorphik:
2047
2150
 
2048
2151
  return result
2049
2152
 
2050
- async def batch_get_documents(self, document_ids: List[str]) -> List[Document]:
2153
+ async def batch_get_documents(
2154
+ self, document_ids: List[str], folder_name: Optional[Union[str, List[str]]] = None
2155
+ ) -> List[Document]:
2051
2156
  """
2052
2157
  Retrieve multiple documents by their IDs in a single batch operation.
2053
2158
 
2054
2159
  Args:
2055
2160
  document_ids: List of document IDs to retrieve
2161
+ folder_name: Optional folder name (or list of names) to scope the request
2056
2162
 
2057
2163
  Returns:
2058
2164
  List[Document]: List of document metadata for found documents
@@ -2066,18 +2172,25 @@ class AsyncMorphik:
2066
2172
  """
2067
2173
  # API expects a dict with document_ids key, not a direct list
2068
2174
  request = {"document_ids": document_ids}
2175
+ if folder_name:
2176
+ request["folder_name"] = folder_name
2069
2177
  response = await self._request("POST", "batch/documents", data=request)
2070
2178
  docs = self._logic._parse_document_list_response(response)
2071
2179
  for doc in docs:
2072
2180
  doc._client = self
2073
2181
  return docs
2074
2182
 
2075
- async def batch_get_chunks(self, sources: List[Union[ChunkSource, Dict[str, Any]]]) -> List[FinalChunkResult]:
2183
+ async def batch_get_chunks(
2184
+ self,
2185
+ sources: List[Union[ChunkSource, Dict[str, Any]]],
2186
+ folder_name: Optional[Union[str, List[str]]] = None,
2187
+ ) -> List[FinalChunkResult]:
2076
2188
  """
2077
2189
  Retrieve specific chunks by their document ID and chunk number in a single batch operation.
2078
2190
 
2079
2191
  Args:
2080
2192
  sources: List of ChunkSource objects or dictionaries with document_id and chunk_number
2193
+ folder_name: Optional folder name (or list of names) to scope the request
2081
2194
 
2082
2195
  Returns:
2083
2196
  List[FinalChunkResult]: List of chunk results
@@ -2102,7 +2215,7 @@ class AsyncMorphik:
2102
2215
  print(f"Chunk from {chunk.document_id}, number {chunk.chunk_number}: {chunk.content[:50]}...")
2103
2216
  ```
2104
2217
  """
2105
- request = self._logic._prepare_batch_get_chunks_request(sources, None, None)
2218
+ request = self._logic._prepare_batch_get_chunks_request(sources, folder_name, None)
2106
2219
  response = await self._request("POST", "batch/chunks", data=request)
2107
2220
  return self._logic._parse_chunk_result_list_response(response)
2108
2221
 
@@ -2227,7 +2340,9 @@ class AsyncMorphik:
2227
2340
  """
2228
2341
  request = self._logic._prepare_create_graph_request(name, filters, documents, prompt_overrides, None, None)
2229
2342
  response = await self._request("POST", "graph/create", data=request)
2230
- return self._logic._parse_graph_response(response)
2343
+ graph = self._logic._parse_graph_response(response)
2344
+ graph._client = self # Attach AsyncMorphik client for polling helpers
2345
+ return graph
2231
2346
 
2232
2347
  async def get_graph(self, name: str) -> Graph:
2233
2348
  """
@@ -2247,7 +2362,9 @@ class AsyncMorphik:
2247
2362
  ```
2248
2363
  """
2249
2364
  response = await self._request("GET", f"graph/{name}")
2250
- return self._logic._parse_graph_response(response)
2365
+ graph = self._logic._parse_graph_response(response)
2366
+ graph._client = self
2367
+ return graph
2251
2368
 
2252
2369
  async def list_graphs(self) -> List[Graph]:
2253
2370
  """
@@ -2265,7 +2382,10 @@ class AsyncMorphik:
2265
2382
  ```
2266
2383
  """
2267
2384
  response = await self._request("GET", "graphs")
2268
- return self._logic._parse_graph_list_response(response)
2385
+ graphs = self._logic._parse_graph_list_response(response)
2386
+ for g in graphs:
2387
+ g._client = self
2388
+ return graphs
2269
2389
 
2270
2390
  async def update_graph(
2271
2391
  self,
@@ -2322,7 +2442,9 @@ class AsyncMorphik:
2322
2442
  name, additional_filters, additional_documents, prompt_overrides, None, None
2323
2443
  )
2324
2444
  response = await self._request("POST", f"graph/{name}/update", data=request)
2325
- return self._logic._parse_graph_response(response)
2445
+ graph = self._logic._parse_graph_response(response)
2446
+ graph._client = self
2447
+ return graph
2326
2448
 
2327
2449
  async def delete_document(self, document_id: str) -> Dict[str, str]:
2328
2450
  """
@@ -2384,3 +2506,37 @@ class AsyncMorphik:
2384
2506
 
2385
2507
  async def __aexit__(self, exc_type, exc_val, exc_tb):
2386
2508
  await self.close()
2509
+
2510
+ async def create_app(self, app_id: str, name: str, expiry_days: int = 30) -> Dict[str, str]:
2511
+ """Create a new application in Morphik Cloud and obtain its auth URI (async)."""
2512
+
2513
+ payload = {"app_id": app_id, "name": name, "expiry_days": expiry_days}
2514
+ return await self._request("POST", "ee/create_app", data=payload)
2515
+
2516
+ async def wait_for_graph_completion(
2517
+ self,
2518
+ graph_name: str,
2519
+ timeout_seconds: int = 300,
2520
+ check_interval_seconds: int = 5,
2521
+ ) -> Graph:
2522
+ """Block until the specified graph finishes processing (async).
2523
+
2524
+ Args:
2525
+ graph_name: Name of the graph to monitor.
2526
+ timeout_seconds: Maximum seconds to wait.
2527
+ check_interval_seconds: Seconds between status checks.
2528
+
2529
+ Returns:
2530
+ Graph: The completed graph object.
2531
+ """
2532
+ import asyncio
2533
+
2534
+ start = asyncio.get_event_loop().time()
2535
+ while (asyncio.get_event_loop().time() - start) < timeout_seconds:
2536
+ graph = await self.get_graph(graph_name)
2537
+ if graph.is_completed:
2538
+ return graph
2539
+ if graph.is_failed:
2540
+ raise RuntimeError(graph.error or "Graph processing failed")
2541
+ await asyncio.sleep(check_interval_seconds)
2542
+ raise TimeoutError("Timed out waiting for graph completion")