Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion basalt/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.2.1"
__version__ = "0.2.2"
13 changes: 11 additions & 2 deletions basalt/basalt_facade.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from .utils.api import Api
from .utils.protocols import IPromptSDK, IBasaltSDK, LogLevel
from .utils.protocols import IPromptSDK, IBasaltSDK, LogLevel, IDatasetSDK
from .sdk.promptsdk import PromptSDK
from .sdk.monitorsdk import MonitorSDK
from .sdk.datasetsdk import DatasetSDK
from .basaltsdk import BasaltSDK
from .utils.memcache import MemoryCache
from .utils.networker import Networker
Expand Down Expand Up @@ -40,8 +41,9 @@ def __init__(self, api_key: str, log_level: LogLevel = 'all'):

prompt = PromptSDK(api, cache, global_fallback_cache, logger)
monitor = MonitorSDK(api, logger)
datasets = DatasetSDK(api, logger)

self._basalt = BasaltSDK(prompt, monitor)
self._basalt = BasaltSDK(prompt, monitor, datasets)

@property
def prompt(self) -> IPromptSDK:
Expand All @@ -56,3 +58,10 @@ def monitor(self) -> IMonitorSDK:
Read-only access to the MonitorSDK instance.
"""
return self._basalt.monitor

@property
def datasets(self) -> IDatasetSDK:
"""
Read-only access to the DatasetSDK instance.
"""
return self._basalt.datasets
10 changes: 8 additions & 2 deletions basalt/basaltsdk.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .utils.protocols import IPromptSDK, IBasaltSDK
from .utils.protocols import IPromptSDK, IBasaltSDK, IDatasetSDK
from .ressources.monitor.monitorsdk_types import IMonitorSDK

class BasaltSDK(IBasaltSDK):
Expand All @@ -7,9 +7,10 @@ class BasaltSDK(IBasaltSDK):
It serves as the main entry point for interacting with the Basalt SDK.
"""

def __init__(self, prompt_sdk: IPromptSDK, monitor_sdk: IMonitorSDK):
def __init__(self, prompt_sdk: IPromptSDK, monitor_sdk: IMonitorSDK, dataset_sdk: IDatasetSDK):
self._prompt = prompt_sdk
self._monitor = monitor_sdk
self._datasets = dataset_sdk

@property
def prompt(self) -> IPromptSDK:
Expand All @@ -20,3 +21,8 @@ def prompt(self) -> IPromptSDK:
def monitor(self) -> IMonitorSDK:
"""Read-only access to the MonitorSDK instance"""
return self._monitor

@property
def datasets(self) -> IDatasetSDK:
"""Read-only access to the DatasetSDK instance"""
return self._datasets
87 changes: 87 additions & 0 deletions basalt/endpoints/create_dataset_item.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
"""
Endpoint for creating a new dataset item
"""
from dataclasses import dataclass
from typing import Any, Dict, Optional, Tuple

from ..utils.dtos import DatasetRowDTO, CreateDatasetItemDTO

@dataclass
class CreateDatasetItemEndpointResponse:
"""
Response from the create dataset item endpoint
"""
datasetRow: DatasetRowDTO
warning: Optional[str] = None
error: Optional[str] = None
Comment on lines +14 to +16
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix type consistency in error handling.

The datasetRow field is typed as DatasetRowDTO (non-optional) but gets set to None when there's an error. This creates a type inconsistency.

-    datasetRow: DatasetRowDTO
+    datasetRow: Optional[DatasetRowDTO]

Also applies to: 29-30

🤖 Prompt for AI Agents
In basalt/endpoints/create_dataset_item.py around lines 14 to 16 and also lines
29 to 30, the datasetRow field is typed as non-optional DatasetRowDTO but is
assigned None in error cases, causing type inconsistency. Change the type
annotation of datasetRow to Optional[DatasetRowDTO] to allow None values,
ensuring type consistency when errors occur.


@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "CreateDatasetItemEndpointResponse":
"""
Create an instance of CreateDatasetItemEndpointResponse from a dictionary.

Args:
data (Dict[str, Any]): The dictionary containing the response data.

Returns:
CreateDatasetItemEndpointResponse
"""
if "error" in data:
return cls(datasetRow=None, error=data["error"])

return cls(
datasetRow=DatasetRowDTO.from_dict(data["datasetRow"]),
warning=data.get("warning"),
error=None
)


class CreateDatasetItemEndpoint:
"""
Endpoint class for creating a dataset item.
"""
@staticmethod
def prepare_request(dto: CreateDatasetItemDTO) -> Dict[str, Any]:
"""
Prepare the request dictionary for the CreateDatasetItem endpoint.

Args:
dto (CreateDatasetItemDTO): The DTO containing dataset item data.

Returns:
The path, method, and body for creating a dataset item on the API.
"""
body = {
"values": dto.values
}

if dto.name:
body["name"] = dto.name

if dto.idealOutput:
body["idealOutput"] = dto.idealOutput

if dto.metadata:
body["metadata"] = dto.metadata

return {
"path": f"/datasets/{dto.slug}/items",
"method": "POST",
"body": body
}

@staticmethod
def decode_response(response: dict) -> Tuple[Optional[Exception], Optional[CreateDatasetItemEndpointResponse]]:
"""
Decode the response returned from the API

Args:
response (dict): The JSON response to encode into a CreateDatasetItemEndpointResponse

Returns:
A tuple containing an optional exception and an optional CreateDatasetItemEndpointResponse.
"""
try:
return None, CreateDatasetItemEndpointResponse.from_dict(response)
except Exception as e:
return e, None
72 changes: 72 additions & 0 deletions basalt/endpoints/get_dataset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""
Endpoint for fetching a specific dataset by slug
"""
from dataclasses import dataclass
from typing import Any, Dict, Optional, Tuple

from ..utils.dtos import DatasetDTO, GetDatasetDTO

@dataclass
class GetDatasetEndpointResponse:
"""
Response from the get dataset endpoint
"""
dataset: DatasetDTO
error: Optional[str] = None
Comment on lines +14 to +15
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix type inconsistency in dataclass field.

The dataset field is typed as DatasetDTO but can be set to None in the error case, causing a type mismatch.

-    dataset: DatasetDTO
+    dataset: Optional[DatasetDTO]
     error: Optional[str] = None

This ensures type consistency throughout the error handling logic.

Also applies to: 28-29

🤖 Prompt for AI Agents
In basalt/endpoints/get_dataset.py at lines 14-15 and 28-29, the dataset field
is typed as DatasetDTO but can be None in error cases, causing a type
inconsistency. Update the type annotation of the dataset field to
Optional[DatasetDTO] to allow None values and ensure type consistency in error
handling.


@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "GetDatasetEndpointResponse":
"""
Create an instance of GetDatasetEndpointResponse from a dictionary.

Args:
data (Dict[str, Any]): The dictionary containing the response data.

Returns:
GetDatasetEndpointResponse
"""
if "error" in data:
return cls(dataset=None, error=data["error"])

return cls(
dataset=DatasetDTO.from_dict(data["dataset"]),
error=None
)


class GetDatasetEndpoint:
"""
Endpoint class for fetching a specific dataset.
"""
@staticmethod
def prepare_request(dto: GetDatasetDTO) -> Dict[str, Any]:
"""
Prepare the request dictionary for the GetDataset endpoint.

Args:
dto (GetDatasetDTO): The DTO containing dataset slug.

Returns:
The path, method, and query parameters for getting a dataset on the API.
"""
return {
"path": f"/datasets/{dto.slug}",
"method": "GET",
"query": {}
}

@staticmethod
def decode_response(response: dict) -> Tuple[Optional[Exception], Optional[GetDatasetEndpointResponse]]:
"""
Decode the response returned from the API

Args:
response (dict): The JSON response to encode into a GetDatasetEndpointResponse

Returns:
A tuple containing an optional exception and an optional GetDatasetEndpointResponse.
"""
try:
return None, GetDatasetEndpointResponse.from_dict(response)
except Exception as e:
return e, None
64 changes: 64 additions & 0 deletions basalt/endpoints/list_datasets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"""
Endpoint for listing all datasets
"""
from dataclasses import dataclass
from typing import Any, Dict, List, Optional, Tuple

from ..utils.dtos import DatasetDTO, ListDatasetsDTO

@dataclass
class ListDatasetsEndpointResponse:
"""
Response from the list datasets endpoint
"""
datasets: List[DatasetDTO]

@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "ListDatasetsEndpointResponse":
"""
Create an instance of ListDatasetsEndpointResponse from a dictionary.

Args:
data (Dict[str, Any]): The dictionary containing the response data.

Returns:
ListDatasetsEndpointResponse
"""
return cls(
datasets=[DatasetDTO.from_dict(dataset) for dataset in data["datasets"]],
)
Comment on lines +27 to +29
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for missing datasets key.

The method assumes the "datasets" key exists in the response data, which could cause a KeyError if the API response format changes or is malformed.

 return cls(
-    datasets=[DatasetDTO.from_dict(dataset) for dataset in data["datasets"]],
+    datasets=[DatasetDTO.from_dict(dataset) for dataset in data.get("datasets", [])],
 )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return cls(
datasets=[DatasetDTO.from_dict(dataset) for dataset in data["datasets"]],
)
return cls(
datasets=[DatasetDTO.from_dict(dataset) for dataset in data.get("datasets", [])],
)
🤖 Prompt for AI Agents
In basalt/endpoints/list_datasets.py around lines 27 to 29, the code assumes the
"datasets" key is always present in the data dictionary, which can cause a
KeyError if it is missing. Add error handling by checking if the "datasets" key
exists in the data before accessing it. If the key is missing, handle the
situation gracefully by either returning an empty list or raising a clear
exception with an informative message.



class ListDatasetsEndpoint:
"""
Endpoint class for fetching all datasets.
"""
@staticmethod
def prepare_request(dto: ListDatasetsDTO) -> Dict[str, Any]:
"""
Prepare the request dictionary for the ListDatasets endpoint.

Returns:
The path, method, and query parameters for getting datasets on the API.
"""
return {
"path": "/datasets",
"method": "GET",
"query": {}
}

@staticmethod
def decode_response(response: dict) -> Tuple[Optional[Exception], Optional[ListDatasetsEndpointResponse]]:
"""
Decode the response returned from the API

Args:
response (dict): The JSON response to encode into a ListDatasetsEndpointResponse

Returns:
A tuple containing an optional exception and an optional ListDatasetsEndpointResponse.
"""
try:
return None, ListDatasetsEndpointResponse.from_dict(response)
except Exception as e:
return e, None
92 changes: 92 additions & 0 deletions basalt/objects/dataset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"""
Dataset object for Basalt SDK
"""

from dataclasses import dataclass, field
from typing import Dict, List, Any, Optional


@dataclass
class DatasetRow:
"""
A row in a dataset with values and metadata
"""
values: Dict[str, str]
name: Optional[str] = None
ideal_output: Optional[str] = None
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Inconsistent naming convention with other dataset classes.

This class uses ideal_output (snake_case) while similar classes in the codebase use idealOutput (camelCase). For consistency with the DTOs and API contracts, consider using camelCase.

-    ideal_output: Optional[str] = None
+    idealOutput: Optional[str] = None

And update the corresponding references:

-            "idealOutput": self.ideal_output
+            "idealOutput": self.idealOutput
-            ideal_output=data.get("idealOutput", None),
+            idealOutput=data.get("idealOutput", None),

Also applies to: 24-25, 50-50

🤖 Prompt for AI Agents
In basalt/objects/dataset.py at lines 16, 24-25, and 50, the variable name
ideal_output uses snake_case, which is inconsistent with the camelCase naming
convention used in other dataset classes and API contracts. Rename ideal_output
to idealOutput in the class definition and update all corresponding references
within these lines to maintain consistency with the rest of the codebase.

metadata: Dict[str, Any] = field(default_factory=dict)

def to_dict(self) -> Dict[str, Any]:
"""Convert the DatasetRow to a dictionary for API requests"""
result = {
"values": self.values,
"metadata": self.metadata,
"name": self.name,
"idealOutput": self.ideal_output
Comment on lines +24 to +25
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix indentation issues and mixed tabs/spaces.

Lines 24-25 contain mixed tabs and spaces which will cause syntax errors. The indentation is also incorrect for the dictionary structure.

-						"name": self.name,
-						"idealOutput": self.ideal_output
+            "name": self.name,
+            "idealOutput": self.ideal_output
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"name": self.name,
"idealOutput": self.ideal_output
"name": self.name,
"idealOutput": self.ideal_output
🧰 Tools
🪛 Flake8 (7.2.0)

[error] 24-24: indentation contains mixed spaces and tabs

(E101)


[error] 24-24: continuation line unaligned for hanging indent

(E131)


[error] 25-25: indentation contains mixed spaces and tabs

(E101)

🤖 Prompt for AI Agents
In basalt/objects/dataset.py around lines 24 to 25, fix the indentation by
replacing any tabs with spaces and ensure consistent indentation for the
dictionary keys "name" and "idealOutput". Align these keys properly within the
dictionary structure to avoid syntax errors caused by mixed tabs and spaces.

}

# if self.name:
# result["name"] = self.name

# if self.ideal_output:
# result["idealOutput"] = self.ideal_output

return result

@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "DatasetRow":
"""
Create a DatasetRow instance from a dictionary

Args:
data: Dictionary containing dataset row data

Returns:
DatasetRow: A new DatasetRow instance
"""
return cls(
values=data.get("values", {}),
name=data.get("name", None),
ideal_output=data.get("idealOutput", None),
metadata=data.get("metadata", {})
)


@dataclass
class Dataset:
"""
A dataset with rows and metadata
"""
slug: str
name: str
columns: List[str]
rows: List[DatasetRow] = field(default_factory=list)

def to_dict(self) -> Dict[str, Any]:
"""Convert the Dataset to a dictionary for API responses"""
return {
"slug": self.slug,
"name": self.name,
"columns": self.columns,
"rows": [row.to_dict() for row in self.rows]
}

@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "Dataset":
"""
Create a Dataset instance from a dictionary

Args:
data: Dictionary containing dataset data

Returns:
Dataset: A new Dataset instance
"""
rows = [DatasetRow.from_dict(row) for row in data.get("rows", [])]

return cls(
slug=data["slug"],
name=data["name"],
columns=data["columns"],
rows=rows
)
3 changes: 3 additions & 0 deletions basalt/ressources/datasets/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""
Datasets resource module
"""
Loading