Skip to content

Commit 4bf69b4

Browse files
authored
0.0.35
Feature: Updates to support real-time S3 Feature: Vector delete support Feature: Neum Client included more get methods Fix: Website and File connector response checks
1 parent d1ca465 commit 4bf69b4

11 files changed

Lines changed: 125 additions & 10 deletions

File tree

neumai/neumai/Client/NeumClient.py

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,51 @@ def get_pipeline(self, pipeline_id:str):
4242
except Exception as e:
4343
print(f"Pipeline fetching failed. Exception - {e}")
4444

45+
def get_pipelines(self):
46+
url = f"{self.endpoint}/pipelines/"
47+
48+
headers = {
49+
"accept": "application/json",
50+
"neum-api-key": self.api_key,
51+
"content-type": "application/json"
52+
}
53+
54+
try:
55+
response = requests.get(url, headers=headers)
56+
return json.loads(response.text)
57+
except Exception as e:
58+
print(f"Pipeline fetch failed. Exception - {e}")
59+
60+
def get_pipeline_runs(self, pipeline_id:str):
61+
url = f"{self.endpoint}/pipelines/{pipeline_id}/runs"
62+
63+
headers = {
64+
"accept": "application/json",
65+
"neum-api-key": self.api_key,
66+
"content-type": "application/json"
67+
}
68+
69+
try:
70+
response = requests.get(url, headers=headers)
71+
return json.loads(response.text)
72+
except Exception as e:
73+
print(f"Pipeline runs fetch failed. Exception - {e}")
74+
75+
def get_pipeline_run(self, pipeline_id:str, pipeline_run_id:str):
76+
url = f"{self.endpoint}/pipelines/{pipeline_id}/runs{pipeline_run_id}"
77+
78+
headers = {
79+
"accept": "application/json",
80+
"neum-api-key": self.api_key,
81+
"content-type": "application/json"
82+
}
83+
84+
try:
85+
response = requests.get(url, headers=headers)
86+
return json.loads(response.text)
87+
except Exception as e:
88+
print(f"Pipeline run fetch failed. Exception - {e}")
89+
4590
def trigger_pipeline(self, pipeline_id:str, sync_type:TriggerSyncTypeEnum):
4691
url = f"{self.endpoint}/pipelines/{pipeline_id}/trigger"
4792

@@ -75,7 +120,7 @@ def search_pipeline(self, pipeline_id:str, query:str, num_of_results:int = 3, tr
75120
response = requests.post(url, json=payload, headers=headers)
76121
return json.loads(response.text)
77122
except Exception as e:
78-
print(f"Pipeline trigger failed. Exception - {e}")
123+
print(f"Pipeline search failed. Exception - {e}")
79124

80125
def search_file(self, pipeline_id:str, file_id:str, query:str, num_of_results:int = 3, track:bool = False, requested_by:str = None):
81126
url = f"{self.endpoint}/pipelines/{pipeline_id}/files/search?file_id={file_id}"
@@ -95,7 +140,7 @@ def search_file(self, pipeline_id:str, file_id:str, query:str, num_of_results:in
95140
response = requests.post(url, json=payload, headers=headers)
96141
return json.loads(response.text)
97142
except Exception as e:
98-
print(f"Pipeline trigger failed. Exception - {e}")
143+
print(f"File search failed. Exception - {e}")
99144

100145
def get_files(self, pipeline_id:str):
101146
url = f"{self.endpoint}/pipelines/{pipeline_id}/files"
@@ -110,7 +155,7 @@ def get_files(self, pipeline_id:str):
110155
response = requests.get(url, headers=headers)
111156
return json.loads(response.text)
112157
except Exception as e:
113-
print(f"Pipeline trigger failed. Exception - {e}")
158+
print(f"Files fetch failed. Exception - {e}")
114159

115160
def get_file(self, pipeline_id:str, file_id:str):
116161
url = f"{self.endpoint}/pipelines/{pipeline_id}/files?file_id={file_id}"
@@ -125,7 +170,7 @@ def get_file(self, pipeline_id:str, file_id:str):
125170
response = requests.get(url, headers=headers)
126171
return json.loads(response.text)
127172
except Exception as e:
128-
print(f"Pipeline trigger failed. Exception - {e}")
173+
print(f"File fetch failed. Exception - {e}")
129174

130175
def get_retrievals_by_file_id(self, pipeline_id:str, file_id:str):
131176
url = f"{self.endpoint}/retrievals/{pipeline_id}/files?file_id={file_id}"
@@ -140,7 +185,7 @@ def get_retrievals_by_file_id(self, pipeline_id:str, file_id:str):
140185
response = requests.get(url, headers=headers)
141186
return json.loads(response.text)
142187
except Exception as e:
143-
print(f"Pipeline trigger failed. Exception - {e}")
188+
print(f"Retrievals fetch failed. Exception - {e}")
144189

145190
def get_retrievals_by_pipeline_id(self, pipeline_id:str):
146191
url = f"{self.endpoint}/retrievals/{pipeline_id}"
@@ -155,7 +200,7 @@ def get_retrievals_by_pipeline_id(self, pipeline_id:str):
155200
response = requests.get(url, headers=headers)
156201
return json.loads(response.text)
157202
except Exception as e:
158-
print(f"Pipeline trigger failed. Exception - {e}")
203+
print(f"Retrievals fetch failed. Exception - {e}")
159204

160205
def provide_retrieval_feedback(self, pipeline_id:str, retrieval_id:str, status:str):
161206
url = f"{self.endpoint}/retrievals/{pipeline_id}/{retrieval_id}"
@@ -173,4 +218,4 @@ def provide_retrieval_feedback(self, pipeline_id:str, retrieval_id:str, status:s
173218
response = requests.post(url, json=payload, headers=headers)
174219
return json.loads(response.text)
175220
except Exception as e:
176-
print(f"Pipeline trigger failed. Exception - {e}")
221+
print(f"Retrieval feedback failed. Exception - {e}")

neumai/neumai/DataConnectors/FileConnector.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ def connect_and_download(self, cloudFile:CloudFile) -> Generator[LocalFile, None
7373

7474
headers = {"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36"}
7575
response = requests.get(cloudFile.file_identifier, headers=headers)
76+
if not response.ok:
77+
raise NeumFileException(f"File can't be accessed. Please make sure it is publicly available.")
7678

7779
# Parse the URL to get the path
7880
path = urlparse(cloudFile.file_identifier).path
@@ -89,6 +91,8 @@ def config_validation(self) -> bool:
8991
import requests
9092
try:
9193
response = requests.get(self.url)
94+
if not response.ok:
95+
raise NeumFileException(f"File can't be accessed. Please make sure it is publicly available.")
9296
except Exception as e:
9397
raise NeumFileException(f"Connection to file failed, check url. See Exception: {e}")
9498
# Check for metadata

neumai/neumai/DataConnectors/WebsiteConnector.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ def connect_and_list_delta(self) -> Generator[CloudFile, None, None]:
7474
def connect_and_download(self, cloudFile:CloudFile) -> Generator[LocalFile, None, None]:
7575
headers = {"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36"}
7676
response = requests.get(cloudFile.file_identifier, headers=headers)
77+
if not response.ok:
78+
raise WebsiteConnectionException(f"File can't be accessed. Please make sure it is publicly available.")
7779
# Parse the HTML content
7880
soup = BeautifulSoup(response.content, 'html.parser')
7981
# Find the <body> element and extract its HTML content
@@ -94,7 +96,9 @@ def config_validation(self) -> bool:
9496

9597
# Check to see that site exists
9698
try:
97-
requests.get(self.url)
99+
response = requests.get(self.url)
100+
if not response.ok:
101+
raise WebsiteConnectionException(f"File can't be accessed. Please make sure it is publicly available.")
98102
except Exception as e:
99103
raise WebsiteConnectionException(f"Connection to website failed, check url. See Exception: {e}")
100104
return True

neumai/neumai/Pipelines/TriggerSyncTypeEnum.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
class TriggerSyncTypeEnum(str, Enum):
44
delta = "delta"
55
full = "full"
6+
event_based="event_based"
67

78
def as_trigger_sync_type(trigger_sync_type: str):
89
if trigger_sync_type == None or trigger_sync_type == "":

neumai/neumai/SinkConnectors/PineconeSink.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,20 @@ def validation(self) -> bool:
6464
raise PineconeConnectionException(f"Pinecone connection couldn't be initialized. See exception: {e}")
6565
return True
6666

67+
def delete_vectors_with_file_id(self, file_id: str) -> bool:
68+
api_key = self.api_key
69+
environment = self.environment
70+
index = self.index
71+
namespace = self.namespace
72+
if environment == "gcp-starter":
73+
raise Exception("Pinecone does not support deleting vectors by metadata in the gcp starter environment")
74+
pinecone.init(
75+
api_key=api_key,
76+
environment=environment)
77+
index = pinecone.Index(index)
78+
index.delete(filter={"_file_entry_id": {"$eq": file_id}}, namespace=namespace)
79+
return True
80+
6781
def store(self, vectors_to_store:List[NeumVector]) -> int:
6882
api_key = self.api_key
6983
environment = self.environment

neumai/neumai/SinkConnectors/QdrantSink.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ def validation(self) -> bool:
5959
)
6060
return True
6161

62+
def delete_vectors_with_file_id(self, file_id: str) -> bool:
63+
raise Exception("Qdrant doesn't have support to delete vectors by metadata")
64+
6265
def store(self, vectors_to_store:List[NeumVector]) -> int:
6366
url = self.url
6467
api_key = self.api_key

neumai/neumai/SinkConnectors/SingleStoreSink.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,13 @@ def validation(self) -> bool:
6262
raise SinglestoreConnectionException(f"There was a problem connecting to Singlestore. See Exception: {e}")
6363
return True
6464

65+
def delete_vectors_with_file_id(self, file_id: str) -> bool:
66+
with s2.connect(self.url) as conn:
67+
with conn.cursor() as cur:
68+
delete_query = f"""DELETE FROM {self.table} WHERE _file_entry_id='{file_id}';"""
69+
cur.execute(delete_query)
70+
return True
71+
6572
def store(self, vectors_to_store:List[NeumVector]) -> int:
6673
batch_size = self.batch_size
6774
url = self.url

neumai/neumai/SinkConnectors/SinkConnector.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ def store(self, vectors_to_store:List[NeumVector]) -> int:
3535
def search(self, vector:List[float], number_of_results:int, filter:dict={}) -> List[NeumSearchResult]:
3636
"""Search vectors for a given service"""
3737

38+
@abstractmethod
39+
def delete_vectors_with_file_id(self, file_id:str ) -> bool:
40+
"""Deletes vectors for a specific file id"""
41+
3842
@abstractmethod
3943
def info(self) -> NeumSinkInfo:
4044
"""Get information about what is stores in the sink"""

neumai/neumai/SinkConnectors/SupabaseSink.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,19 @@ def validation(self) -> bool:
5252
raise SupabaseConnectionException(f"Supabase connection couldn't be initialized. See exception: {e}")
5353
return True
5454

55+
def delete_vectors_with_file_id(self, file_id: str) -> bool:
56+
database_connection = self.database_connection
57+
vx = vecs.create_client(database_connection)
58+
try:
59+
collection_name = self.collection_name
60+
db = vx.get_collection(name=collection_name)
61+
db.delete(filters={"_file_entry_id": {"$eq": file_id}})
62+
except Exception as e:
63+
raise Exception(f"Supabase deletion failed. Exception {e}")
64+
finally:
65+
vx.disconnect()
66+
return True
67+
5568
def store(self, vectors_to_store:List[NeumVector]) -> int:
5669
database_connection = self.database_connection
5770
vx = vecs.create_client(database_connection)

neumai/neumai/SinkConnectors/WeaviateSink.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,26 @@ def _check_batch_result(self, results: Optional[List[dict[str, any]]], partial_f
101101
partial_failure['latest_failure'] = result["result"]["errors"]["error"]
102102
partial_failure['number_of_failures'] += 1
103103

104+
def delete_vectors_with_file_id(self, file_id: str) -> bool:
105+
api_key = self.api_key
106+
url = self.url
107+
# Weaviate requires first letter to be capitalized
108+
class_name = self.class_name
109+
class_name = _capitalize_first_letter(class_name)
110+
client = weaviate.Client(
111+
url=url,
112+
auth_client_secret=weaviate.AuthApiKey(api_key=api_key),
113+
)
114+
client.batch.delete_objects(
115+
class_name=class_name,
116+
where={
117+
"path": ["_file_entry_id"],
118+
"operator": "Equal",
119+
"valueText": file_id
120+
},
121+
)
122+
return True
123+
104124
def store(self, vectors_to_store:List[NeumVector]) -> Tuple[List, dict]:
105125
url = self.url
106126
num_workers = self.num_workers

0 commit comments

Comments
 (0)