Skip to content
Open
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
18 changes: 18 additions & 0 deletions toolkits/gibsonai/.pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
files: ^.*/gibsonai/.*
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: "v4.4.0"
hooks:
- id: check-case-conflict
- id: check-merge-conflict
- id: check-toml
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.7
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
47 changes: 47 additions & 0 deletions toolkits/gibsonai/.ruff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
target-version = "py310"
line-length = 100
fix = true

[lint]
select = [
# flake8-2020
"YTT",
# flake8-bandit
"S",
# flake8-bugbear
"B",
# flake8-builtins
"A",
# flake8-comprehensions
"C4",
# flake8-debugger
"T10",
# flake8-simplify
"SIM",
# isort
"I",
# mccabe
"C90",
# pycodestyle
"E", "W",
# pyflakes
"F",
# pygrep-hooks
"PGH",
# pyupgrade
"UP",
# ruff
"RUF",
# tryceratops
"TRY",
]

[lint.per-file-ignores]
"*" = ["TRY003", "B904"]
"**/tests/*" = ["S101", "E501"]
"**/evals/*" = ["S101", "E501"]


[format]
preview = true
skip-magic-trailing-comma = false
55 changes: 55 additions & 0 deletions toolkits/gibsonai/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
.PHONY: help

help:
@echo "🛠️ github Commands:\n"
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

.PHONY: install
install: ## Install the uv environment and install all packages with dependencies
@echo "🚀 Creating virtual environment and installing all packages using uv"
@uv sync --active --all-extras --no-sources
@if [ -f .pre-commit-config.yaml ]; then uv run --no-sources pre-commit install; fi
@echo "✅ All packages and dependencies installed via uv"

.PHONY: install-local
install-local: ## Install the uv environment and install all packages with dependencies with local Arcade sources
@echo "🚀 Creating virtual environment and installing all packages using uv"
@uv sync --active --all-extras
@if [ -f .pre-commit-config.yaml ]; then uv run pre-commit install; fi
@echo "✅ All packages and dependencies installed via uv"

.PHONY: build
build: clean-build ## Build wheel file using poetry
@echo "🚀 Creating wheel file"
uv build

.PHONY: clean-build
clean-build: ## clean build artifacts
@echo "🗑️ Cleaning dist directory"
rm -rf dist

.PHONY: test
test: ## Test the code with pytest
@echo "🚀 Testing code: Running pytest"
@uv run --no-sources pytest -W ignore -v --cov --cov-config=pyproject.toml --cov-report=xml

.PHONY: coverage
coverage: ## Generate coverage report
@echo "coverage report"
@uv run --no-sources coverage report
@echo "Generating coverage report"
@uv run --no-sources coverage html

.PHONY: bump-version
bump-version: ## Bump the version in the pyproject.toml file by a patch version
@echo "🚀 Bumping version in pyproject.toml"
uv version --no-sources --bump patch

.PHONY: check
check: ## Run code quality tools.
@if [ -f .pre-commit-config.yaml ]; then\
echo "🚀 Linting code: Running pre-commit";\
uv run --no-sources pre-commit run -a;\
fi
@echo "🚀 Static type checking: Running mypy"
@uv run --no-sources mypy --config-file=pyproject.toml
13 changes: 13 additions & 0 deletions toolkits/gibsonai/arcade_gibsonai/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""GibsonAI Database Tools for Arcade."""

from arcade_gibsonai.tools.delete import delete_records
from arcade_gibsonai.tools.insert import insert_records
from arcade_gibsonai.tools.query import execute_read_query
from arcade_gibsonai.tools.update import update_records

__all__ = [
"delete_records",
"execute_read_query",
"insert_records",
"update_records",
]
129 changes: 129 additions & 0 deletions toolkits/gibsonai/arcade_gibsonai/api_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
from typing import Any, NoReturn

import httpx
from pydantic import BaseModel

from .constants import API_BASE_URL, API_VERSION, MAX_ROWS_RETURNED


class GibsonAIError(Exception):
"""Base exception for GibsonAI API errors."""

pass


class GibsonAIHTTPError(GibsonAIError):
"""HTTP-related errors from GibsonAI API."""

pass


class GibsonAIQueryError(GibsonAIError):
"""Query execution errors from GibsonAI API."""

pass


class GibsonAITimeoutError(GibsonAIError):
"""Timeout errors from GibsonAI API."""

pass


class GibsonAINetworkError(GibsonAIError):
"""Network-related errors when connecting to GibsonAI API."""

pass


class GibsonAIResponse(BaseModel):
"""Response model for GibsonAI API."""

data: list[dict[str, Any]]
success: bool
error: str | None = None


def _raise_http_error(status_code: int, response_text: str) -> NoReturn:
"""Raise an HTTP error with formatted message."""
error_msg = f"HTTP {status_code}: {response_text}"
raise GibsonAIHTTPError(f"GibsonAI API error: {error_msg}")


def _raise_query_error(error_message: str) -> NoReturn:
"""Raise a query error with formatted message."""
raise GibsonAIQueryError(f"GibsonAI query error: {error_message}")


def _raise_timeout_error() -> NoReturn:
"""Raise a timeout error."""
raise GibsonAITimeoutError("Request timeout - GibsonAI API took too long to respond")


def _raise_network_error(error: Exception) -> NoReturn:
"""Raise a network error with original exception details."""
raise GibsonAINetworkError(f"Network error connecting to GibsonAI API: {error}")


def _raise_unexpected_error(error: Exception) -> NoReturn:
"""Raise an unexpected error."""
raise GibsonAIError(f"Unexpected error: {error}")


def _process_response_data(result: Any) -> list[str]:
"""Process the API response data into a list of strings."""
if isinstance(result, dict):
if result.get("error"):
_raise_query_error(result["error"])
elif "data" in result:
return [str(row) for row in result["data"]]
else:
return [str(result)]
elif isinstance(result, list):
return [str(row) for row in result]
else:
return [str(result)]


class GibsonAIClient:
"""Client for interacting with GibsonAI Data API."""

def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = f"{API_BASE_URL}/{API_VERSION}"
self.headers = {"Content-Type": "application/json", "X-Gibson-API-Key": api_key}

async def execute_query(self, query: str, params: list[Any] | None = None) -> list[str]:
"""Execute a query against GibsonAI database."""
if params is None:
params = []

payload = {"array_mode": False, "params": params, "query": query}

try:
async with httpx.AsyncClient() as client:
response = await client.post(
f"{self.base_url}/-/query",
headers=self.headers,
json=payload,
timeout=30.0,
)

if response.status_code != 200:
_raise_http_error(response.status_code, response.text)

result = response.json()
results = _process_response_data(result)

# Limit results to avoid memory issues
return results[:MAX_ROWS_RETURNED]

except httpx.TimeoutException:
_raise_timeout_error()
except httpx.RequestError as e:
_raise_network_error(e)
except GibsonAIError:
# Re-raise our custom exceptions as-is
raise
except Exception as e:
_raise_unexpected_error(e)
8 changes: 8 additions & 0 deletions toolkits/gibsonai/arcade_gibsonai/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""Constants for GibsonAI API configuration."""

# API configuration
API_BASE_URL = "https://api.gibsonai.com"
API_VERSION = "v1"

# Maximum number of rows to return from queries
MAX_ROWS_RETURNED = 1000
13 changes: 13 additions & 0 deletions toolkits/gibsonai/arcade_gibsonai/tools/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""GibsonAI database query tools."""

from arcade_gibsonai.tools.delete import delete_records
from arcade_gibsonai.tools.insert import insert_records
from arcade_gibsonai.tools.query import execute_read_query
from arcade_gibsonai.tools.update import update_records

__all__ = [
"delete_records",
"execute_read_query",
"insert_records",
"update_records",
]
Loading