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.

Discussion:
@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)