Repository: mindsdb/minds_python_sdk

Issue:
[Enhancement] Datasource name validation
Currently, an API call is made when creating a datasource without any validation on the name.  The response is as follows:

```
minds.exceptions.UnknownError: Bad Request: {"title": "Wrong name", "detail": "The name \"my-datasource\" does not meet requirements", "timestamp": "2024-10-23 00:49:03.760930"}
```

1. When creating/updating a datasource, we can validate the name before making an API call. Hence, saving an API call.
2. The requirements for datasource name are not mentioned in the exception message.

Hints from issue comments:
@Better-Boy: I want to work on this.

@ea-rus what are the requirements for a valid datasource name?@ea-rus: It is checked with regexp '^[a-zA-Z][a-zA-Z0-9_]*$'@Better-Boy: > It is checked with regexp '^[a-zA-Z][a-zA-Z0-9_]*$'

Can I add this feature?@ea-rus: I think yes, it can be useful@Better-Boy: > It is checked with regexp '^[a-zA-Z][a-zA-Z0-9_]*$'

According to the regex, name can be of any length. Shouldn't we restrict this to 16 or 32?@ea-rus: Forgot about the length, max length is 62

Solution patch:
diff --git a/README.md b/README.md
index 7ba6b99..3cb4808 100644
--- a/README.md
+++ b/README.md
@@ -150,6 +150,7 @@ client.datasources.drop('my_datasource')
 >Note: The SDK currently does not support automatically removing a data source if it is no longer connected to any mind.
 
 ### Other SDKs
+- [Ruby-SDK](https://github.com/tungnt1203/minds_ruby_sdk)
 - [Dart-SDK](https://github.com/ArnavK-09/mdb_dart)
 
 #### Command Line Tools
diff --git a/minds/datasources/datasources.py b/minds/datasources/datasources.py
index 56e8d52..a973dd4 100644
--- a/minds/datasources/datasources.py
+++ b/minds/datasources/datasources.py
@@ -1,7 +1,7 @@
 from typing import List, Optional, Union
 
 from pydantic import BaseModel, Field
-
+import minds.utils as utils
 import minds.exceptions as exc
 
 class DatabaseConfig(BaseModel):
@@ -37,6 +37,8 @@ def create(self, ds_config: DatabaseConfig, update=False):
 
         name = ds_config.name
 
+        utils.validate_datasource_name(name)
+
         if update:
             self.api.put(f'/datasources/{name}', data=ds_config.model_dump())
         else:
diff --git a/minds/exceptions.py b/minds/exceptions.py
index dd1d717..94444c6 100644
--- a/minds/exceptions.py
+++ b/minds/exceptions.py
@@ -20,4 +20,8 @@ class UnknownError(Exception):
 
 
 class MindNameInvalid(Exception):
+    ...
+
+
+class DatasourceNameInvalid(Exception):
     ...
\ No newline at end of file
diff --git a/minds/utils.py b/minds/utils.py
index 92861f4..cea50a0 100644
--- a/minds/utils.py
+++ b/minds/utils.py
@@ -38,3 +38,27 @@ def validate_mind_name(mind_name):
     # Check if the Mind name matches the pattern
     if not re.match(pattern, mind_name):
         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.")
+
+
+def validate_datasource_name(ds_name):
+    """
+    Validate the datasource name.
+
+    A valid datasource name should:
+    - Start with a letter
+    - Contain only letters, numbers, or underscores
+    - Have a maximum length of 62 characters
+    - Not contain spaces
+
+    Parameters:
+    ds_name (str): The datasource name to validate.
+
+    Returns:
+    bool: True if valid, False otherwise.
+    """
+    # Regular expression pattern
+    pattern = r'^[a-zA-Z][a-zA-Z0-9_]{0,61}$'
+
+    # Check if the datasource name matches the pattern
+    if not re.match(pattern, ds_name):
+        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.")


Test patch:
diff --git a/tests/integration/test_base_flow.py b/tests/integration/test_base_flow.py
index d5c3cfe..969804d 100644
--- a/tests/integration/test_base_flow.py
+++ b/tests/integration/test_base_flow.py
@@ -8,8 +8,9 @@
 logging.basicConfig(level=logging.DEBUG)
 
 from minds.datasources.examples import example_ds
+from minds.datasources import DatabaseConfig
 
-from minds.exceptions import ObjectNotFound, MindNameInvalid
+from minds.exceptions import ObjectNotFound, MindNameInvalid, DatasourceNameInvalid
 
 
 def get_client():
@@ -41,6 +42,14 @@ def test_datasources():
     ds = client.datasources.create(example_ds, update=True)
     assert ds.name == example_ds.name
 
+    valid_ds_name = example_ds.name
+
+    with pytest.raises(DatasourceNameInvalid):
+        example_ds.name = "invalid-ds-name"
+        client.datasources.create(example_ds)
+
+    example_ds.name = valid_ds_name
+
     # get
     ds = client.datasources.get(example_ds.name)