Skip to content

Commit 0ea7b2c

Browse files
authored
Merge branch 'main' into patch-2
2 parents b23d0a3 + bac1981 commit 0ea7b2c

File tree

8 files changed

+110
-28
lines changed

8 files changed

+110
-28
lines changed

minds/__about__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
__title__ = 'minds_sdk'
22
__package_name__ = 'minds'
3-
__version__ = '1.0.7'
3+
__version__ = '1.0.8'
44
__description__ = 'An AI-Data Mind is an LLM with the built-in power to answer data questions for Agents'
55
__email__ = 'hello@mindsdb.com'
66
__author__ = 'MindsDB Inc'

minds/datasources/datasources.py

+7-9
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ class DatabaseConfig(BaseModel):
1616
class Datasource(DatabaseConfig):
1717
...
1818

19+
1920
class Datasources:
2021
def __init__(self, client):
2122
self.api = client.api
2223

23-
def create(self, ds_config: DatabaseConfig, replace=False):
24+
def create(self, ds_config: DatabaseConfig, update=False):
2425
"""
2526
Create new datasource and return it
2627
@@ -30,21 +31,18 @@ def create(self, ds_config: DatabaseConfig, replace=False):
3031
- description: str, description of the database. Used by mind to know what data can be got from it.
3132
- connection_data: dict, optional, credentials to connect to database
3233
- tables: list of str, optional, list of allowed tables
34+
:param update: if true - to update datasourse if exists, default is false
3335
:return: datasource object
3436
"""
3537

3638
name = ds_config.name
3739

3840
utils.validate_datasource_name(name)
3941

40-
if replace:
41-
try:
42-
self.get(name)
43-
self.drop(name, force=True)
44-
except exc.ObjectNotFound:
45-
...
46-
47-
self.api.post('/datasources', data=ds_config.model_dump())
42+
if update:
43+
self.api.put('/datasources', data=ds_config.model_dump())
44+
else:
45+
self.api.post('/datasources', data=ds_config.model_dump())
4846
return self.get(name)
4947

5048
def list(self) -> List[Datasource]:

minds/exceptions.py

+4
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,9 @@ class UnknownError(Exception):
1919
...
2020

2121

22+
class MindNameInvalid(Exception):
23+
...
24+
25+
2226
class DatasourceNameInvalid(Exception):
2327
...

minds/minds.py

+17-6
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
from typing import List, Union, Iterable
22
from urllib.parse import urlparse, urlunparse
3-
from datetime import datetime
43

54
from openai import OpenAI
6-
5+
import minds.utils as utils
76
import minds.exceptions as exc
8-
97
from minds.datasources import Datasource, DatabaseConfig
108

119
DEFAULT_PROMPT_TEMPLATE = 'Use your database tools to answer the user\'s question: {{question}}'
@@ -25,7 +23,7 @@ def __init__(
2523
self.api = client.api
2624
self.client = client
2725
self.project = 'mindsdb'
28-
26+
2927
self.name = name
3028
self.model_name = model_name
3129
self.provider = provider
@@ -74,6 +72,9 @@ def update(
7472
:param parameters, dict: alter other parameters of the mind, optional
7573
"""
7674
data = {}
75+
76+
if name is not None:
77+
utils.validate_mind_name(name)
7778

7879
if datasources is not None:
7980
ds_names = []
@@ -216,7 +217,7 @@ def get(self, name: str) -> Mind:
216217
:param name: name of the mind
217218
:return: a mind object
218219
"""
219-
220+
220221
item = self.api.get(f'/projects/{self.project}/minds/{name}').json()
221222
return Mind(self.client, **item)
222223

@@ -243,6 +244,7 @@ def create(
243244
datasources=None,
244245
parameters=None,
245246
replace=False,
247+
update=False,
246248
) -> Mind:
247249
"""
248250
Create a new mind and return it
@@ -259,8 +261,12 @@ def create(
259261
:param datasources: list of datasources used by mind, optional
260262
:param parameters, dict: other parameters of the mind, optional
261263
:param replace: if true - to remove existing mind, default is false
264+
:param update: if true - to update mind if exists, default is false
262265
:return: created mind
263266
"""
267+
268+
if name is not None:
269+
utils.validate_mind_name(name)
264270

265271
if replace:
266272
try:
@@ -284,7 +290,12 @@ def create(
284290
if 'prompt_template' not in parameters:
285291
parameters['prompt_template'] = DEFAULT_PROMPT_TEMPLATE
286292

287-
self.api.post(
293+
if update:
294+
method = self.api.put
295+
else:
296+
method = self.api.post
297+
298+
method(
288299
f'/projects/{self.project}/minds',
289300
data={
290301
'name': name,

minds/rest_api.py

+10
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,16 @@ def post(self, url, data):
5757
_raise_for_status(resp)
5858
return resp
5959

60+
def put(self, url, data):
61+
resp = requests.put(
62+
self.base_url + url,
63+
headers=self._headers(),
64+
json=data,
65+
)
66+
67+
_raise_for_status(resp)
68+
return resp
69+
6070
def patch(self, url, data):
6171
resp = requests.patch(
6272
self.base_url + url,

minds/utils.py

+24-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,30 @@
11
import re
22
import minds.exceptions as exc
33

4+
def validate_mind_name(mind_name):
5+
"""
6+
Validate the Mind name.
7+
8+
A valid Mind name should:
9+
- Start with a letter
10+
- Contain only letters, numbers, or underscores
11+
- Have a maximum length of 32 characters
12+
- Not contain spaces
13+
14+
Parameters:
15+
mind_name (str): The Mind name to validate.
16+
17+
Returns:
18+
bool: True if valid, False otherwise.
19+
"""
20+
# Regular expression pattern
21+
pattern = r'^[A-Za-z][A-Za-z0-9_]{0,31}$'
22+
23+
# Check if the Mind name matches the pattern
24+
if not re.match(pattern, mind_name):
25+
raise exc.MindNameInvalid("Mind name should start with a letter and contain only letters, numbers or underscore, with a maximum of 32 characters. Spaces are not allowed.")
26+
27+
428
def validate_datasource_name(ds_name):
529
"""
630
Validate the datasource name.
@@ -23,4 +47,3 @@ def validate_datasource_name(ds_name):
2347
# Check if the datasource name matches the pattern
2448
if not re.match(pattern, ds_name):
2549
raise exc.DatasourceNameInvalid("Datasource name should start with a letter and contain only letters, numbers or underscore, with a maximum of 62 characters. Spaces are not allowed.")
26-

tests/integration/test_base_flow.py

+31-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from minds.datasources.examples import example_ds
1111
from minds.datasources import DatabaseConfig
1212

13-
from minds.exceptions import ObjectNotFound, DatasourceNameInvalid
13+
from minds.exceptions import ObjectNotFound, MindNameInvalid, DatasourceNameInvalid
1414

1515

1616
def get_client():
@@ -38,7 +38,8 @@ def test_datasources():
3838

3939
# create
4040
ds = client.datasources.create(example_ds)
41-
ds = client.datasources.create(example_ds, replace=True)
41+
assert ds.name == example_ds.name
42+
ds = client.datasources.create(example_ds, update=True)
4243
assert ds.name == example_ds.name
4344

4445
valid_ds_name = example_ds.name
@@ -66,6 +67,7 @@ def test_minds():
6667
ds_name = 'test_datasource_'
6768
ds_name2 = 'test_datasource2_'
6869
mind_name = 'int_test_mind_'
70+
invalid_mind_name = 'mind-123'
6971
mind_name2 = 'int_test_mind2_'
7072
prompt1 = 'answer in german'
7173
prompt2 = 'answer in spanish'
@@ -88,6 +90,13 @@ def test_minds():
8890
ds2_cfg.tables = ['home_rentals']
8991

9092
# create
93+
with pytest.raises(MindNameInvalid):
94+
mind = client.minds.create(
95+
invalid_mind_name,
96+
datasources=[ds],
97+
provider='openai'
98+
)
99+
91100
mind = client.minds.create(
92101
mind_name,
93102
datasources=[ds],
@@ -99,11 +108,20 @@ def test_minds():
99108
datasources=[ds.name, ds2_cfg],
100109
prompt_template=prompt1
101110
)
111+
mind = client.minds.create(
112+
mind_name,
113+
update=True,
114+
datasources=[ds.name, ds2_cfg],
115+
prompt_template=prompt1
116+
)
102117

103118
# get
104119
mind = client.minds.get(mind_name)
105120
assert len(mind.datasources) == 2
106121
assert mind.prompt_template == prompt1
122+
123+
with pytest.raises(MindNameInvalid):
124+
client.minds.get(invalid_mind_name)
107125

108126
# list
109127
mind_list = client.minds.list()
@@ -115,6 +133,14 @@ def test_minds():
115133
datasources=[ds.name],
116134
prompt_template=prompt2
117135
)
136+
137+
with pytest.raises(MindNameInvalid):
138+
mind.update(
139+
name=invalid_mind_name,
140+
datasources=[ds.name],
141+
prompt_template=prompt2
142+
)
143+
118144
with pytest.raises(ObjectNotFound):
119145
# this name not exists
120146
client.minds.get(mind_name)
@@ -162,3 +188,6 @@ def test_minds():
162188
client.minds.drop(mind_name2)
163189
client.datasources.drop(ds.name)
164190
client.datasources.drop(ds2_cfg.name)
191+
192+
with pytest.raises(MindNameInvalid):
193+
client.minds.drop(invalid_mind_name)

tests/unit/test_unit.py

+16-9
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,10 @@ def _compare_ds(self, ds1, ds2):
3636
assert ds1.tables == ds2.tables
3737

3838
@patch('requests.get')
39+
@patch('requests.put')
3940
@patch('requests.post')
4041
@patch('requests.delete')
41-
def test_create_datasources(self, mock_del, mock_post, mock_get):
42+
def test_create_datasources(self, mock_del, mock_post, mock_put, mock_get):
4243
client = get_client()
4344
response_mock(mock_get, example_ds.model_dump())
4445

@@ -53,12 +54,9 @@ def check_ds_created(ds, mock_post):
5354

5455
check_ds_created(ds, mock_post)
5556

56-
# with replace
57-
ds = client.datasources.create(example_ds, replace=True)
58-
args, _ = mock_del.call_args
59-
assert args[0].endswith(f'/api/datasources/{example_ds.name}')
60-
61-
check_ds_created(ds, mock_post)
57+
# with update
58+
ds = client.datasources.create(example_ds, update=True)
59+
check_ds_created(ds, mock_put)
6260

6361
@patch('requests.get')
6462
def test_get_datasource(self, mock_get):
@@ -115,9 +113,10 @@ def compare_mind(self, mind, mind_json):
115113
assert mind.parameters == mind_json['parameters']
116114

117115
@patch('requests.get')
116+
@patch('requests.put')
118117
@patch('requests.post')
119118
@patch('requests.delete')
120-
def test_create(self, mock_del, mock_post, mock_get):
119+
def test_create(self, mock_del, mock_post, mock_put, mock_get):
121120
client = get_client()
122121

123122
mind_name = 'test_mind'
@@ -145,7 +144,7 @@ def check_mind_created(mind, mock_post, create_params):
145144

146145
check_mind_created(mind, mock_post, create_params)
147146

148-
# with replace
147+
# -- with replace --
149148
create_params = {
150149
'name': mind_name,
151150
'prompt_template': prompt_template,
@@ -159,6 +158,14 @@ def check_mind_created(mind, mock_post, create_params):
159158

160159
check_mind_created(mind, mock_post, create_params)
161160

161+
# -- with update --
162+
mock_del.reset_mock()
163+
mind = client.minds.create(update=True, **create_params)
164+
# is not deleted
165+
assert not mock_del.called
166+
167+
check_mind_created(mind, mock_put, create_params)
168+
162169
@patch('requests.get')
163170
@patch('requests.patch')
164171
def test_update(self, mock_patch, mock_get):

0 commit comments

Comments
 (0)