# Connector Client

The `ConnectorClient` class provides a Python interface to interact with third-party connectors programmatically.

## Overview

The Connector Client allows developers to:

* Connect to third-party services (GitHub, Google Drive, Google Calendar, Google Mail, Chief Retrieval)
* Disconnect from integrated services
* Search connected services using natural language queries
* Handle both streaming and non-streaming responses

## Installation

```bash
pip install smart-search-sdk
```

## Quick Start

```python
import asyncio
import os
from dotenv import load_dotenv

from smart_search_sdk.connector.client import ConnectorClient
from smart_search_sdk.connector.models import (
    ConnectorRequest,
    ConnectorConnectRequest,
    AppName
)

load_dotenv()

async def main():
    # Initialize the client
    client = ConnectorClient(base_url=os.getenv("SMARTSEARCH_BASE_URL"))

    # Authenticate
    await client.authenticate(token=os.getenv("SMARTSEARCH_TOKEN"))

    # Connect to GitHub
    connect_request = ConnectorConnectRequest(
        callback_url="https://your-app.com/callback"
    )
    response = await client.connect_connector(
        app_name=AppName.GITHUB,
        gl_token=os.getenv("GL_CONNECTORS_USER_TOKEN", ""),
        request=connect_request
    )
    print(response)

asyncio.run(main())
```

## Class: ConnectorClient

### Constructor

```python
ConnectorClient(base_url: str)
```

**Parameters:**

* `base_url` (str): The base URL of the Smart Search API

**Example:**

```python
import os
from dotenv import load_dotenv

load_dotenv()

client = ConnectorClient(base_url=os.getenv("SMARTSEARCH_BASE_URL"))
await client.authenticate(token=os.getenv("SMARTSEARCH_TOKEN"))
```

***

## Methods

### Connect - Connect

Initiate integration with a third-party connector.

#### Signature

```python
async def connect_connector(
    self,
    app_name: AppName,
    gl_token: str | None,
    request: ConnectorConnectRequest,
) -> dict
```

#### Parameters

| Parameter  | Type                      | Required | Description                                     |
| ---------- | ------------------------- | -------- | ----------------------------------------------- |
| `app_name` | `AppName`                 | Yes      | The connector app to connect to                 |
| `gl_token` | `str \| None`             | No       | Optional GL Connectors token for authentication |
| `request`  | `ConnectorConnectRequest` | Yes      | Connection request containing callback URL      |

#### Returns

`dict`: Response containing connection status and optional setup URL

#### Response Structure

```python
{
    "status": "setup_required" | "connected",
    "message": str,
    "setup_url": str  # Optional, only when OAuth is required
}
```

#### Examples

**Connect to GitHub:**

```python
import asyncio
import os
from dotenv import load_dotenv

from smart_search_sdk.connector.client import ConnectorClient
from smart_search_sdk.connector.models import (
    ConnectorConnectRequest,
    AppName
)

load_dotenv()

async def connect_github():
    client = ConnectorClient(base_url=os.getenv("SMARTSEARCH_BASE_URL"))
    await client.authenticate(token=os.getenv("SMARTSEARCH_TOKEN"))

    request = ConnectorConnectRequest(
        callback_url="https://your-app.com/callback"
    )

    response = await client.connect_connector(
        app_name=AppName.GITHUB,
        gl_token=None,
        request=request
    )

    if response["status"] == "setup_required":
        print(f"Please visit: {response['setup_url']}")
    else:
        print(f"Connected: {response['message']}")

asyncio.run(connect_github())
```

**Connect with GL Connectors token:**

```python
import asyncio
import os
from dotenv import load_dotenv

from smart_search_sdk.connector.client import ConnectorClient
from smart_search_sdk.connector.models import (
    ConnectorConnectRequest,
    AppName
)

load_dotenv()

async def connect_with_token():
    client = ConnectorClient(base_url=os.getenv("SMARTSEARCH_BASE_URL"))
    await client.authenticate(token=os.getenv("SMARTSEARCH_TOKEN"))

    request = ConnectorConnectRequest(
        callback_url="https://your-app.com/callback"
    )

    response = await client.connect_connector(
        app_name=AppName.GOOGLE_DRIVE,
        gl_token="your-gl-token",
        request=request
    )

    return response

asyncio.run(connect_with_token())
```

***

### Connect - Disconnect

Remove integration with a third-party connector.

#### Signature

```python
async def disconnect_connector(
    self,
    app_name: AppName,
    gl_token: str | None
) -> dict
```

#### Parameters

| Parameter  | Type          | Required | Description                                     |
| ---------- | ------------- | -------- | ----------------------------------------------- |
| `app_name` | `AppName`     | Yes      | The connector app to disconnect from            |
| `gl_token` | `str \| None` | No       | Optional GL Connectors token for authentication |

#### Returns

`dict`: Confirmation of disconnection

#### Response Structure

```python
{
    "status": "disconnected",
    "message": str
}
```

#### Examples

**Disconnect from GitHub:**

```python
import asyncio
import os
from dotenv import load_dotenv

from smart_search_sdk.connector.client import ConnectorClient
from smart_search_sdk.connector.models import AppName

load_dotenv()

async def disconnect_github():
    client = ConnectorClient(base_url=os.getenv("SMARTSEARCH_BASE_URL"))
    await client.authenticate(token=os.getenv("SMARTSEARCH_TOKEN"))

    response = await client.disconnect_connector(
        app_name=AppName.GITHUB,
        gl_token=None
    )

    print(f"Disconnected: {response['message']}")

asyncio.run(disconnect_github())
```

***

### Connect - Search

Search a connected third-party service using natural language.

#### Signature

```python
async def search_connector(
    self,
    app_name: AppName,
    gl_token: str | None,
    request: ConnectorRequest,
    stream: bool = False,
    timeout: float = TimeoutEnum.SIMPLE_PROCESS_TIMEOUT.value,
) -> dict | AsyncGenerator[StreamYieldResponse | dict, None]
```

#### Parameters

| Parameter  | Type               | Required | Default | Description                  |
| ---------- | ------------------ | -------- | ------- | ---------------------------- |
| `app_name` | `AppName`          | Yes      | -       | The connector app to search  |
| `gl_token` | `str \| None`      | No       | `None`  | Optional GL Connectors token |
| `request`  | `ConnectorRequest` | Yes      | -       | Search request with query    |
| `stream`   | `bool`             | No       | `False` | Enable streaming response    |
| `timeout`  | `float`            | No       | `30.0`  | Request timeout in seconds   |

#### Returns

* **Non-streaming**: `dict` with search results
* **Streaming**: `AsyncGenerator` yielding response chunks

#### Response Structure

```python
{
    "data": [
        {
            "content": str,
            "metadata": {
                "title": str,
                "source": str
            },
            "confidence_score": float
        }
    ]
}
```

#### Examples

**Basic search:**

```python
import asyncio
import os
from dotenv import load_dotenv

from smart_search_sdk.connector.client import ConnectorClient
from smart_search_sdk.connector.models import ConnectorRequest, AppName

load_dotenv()

async def search_github():
    client = ConnectorClient(base_url=os.getenv("SMARTSEARCH_BASE_URL"))
    await client.authenticate(token=os.getenv("SMARTSEARCH_TOKEN"))

    request = ConnectorRequest(query="open issues about authentication")

    response = await client.search_connector(
        app_name=AppName.GITHUB,
        gl_token=None,
        request=request,
        stream=False
    )

    for item in response["data"]:
        print(f"Title: {item['metadata']['title']}")
        print(f"Content: {item['content']}")
        print(f"Score: {item['confidence_score']}")
        print("---")

asyncio.run(search_github())
```

**Streaming search:**

```python
import asyncio
import os

from dotenv import load_dotenv

from smart_search_sdk.connector.client import ConnectorClient
from smart_search_sdk.connector.models import ConnectorRequest, AppName

load_dotenv()

async def streaming_search():
    client = ConnectorClient(base_url=os.getenv("SMARTSEARCH_BASE_URL"))
    await client.authenticate(token=os.getenv("SMARTSEARCH_TOKEN"))

    request = ConnectorRequest(query="latest commits")

    stream = await client.search_connector(
        app_name=AppName.GITHUB,
        gl_token=None,
        request=request,
        stream=True
    )

    async for chunk in stream:
        print(f"Received chunk: {chunk}")

asyncio.run(streaming_search())
```

**Search with timeout:**

```python
import asyncio
import os
from dotenv import load_dotenv

from smart_search_sdk.connector.client import ConnectorClient
from smart_search_sdk.connector.models import ConnectorRequest, AppName

load_dotenv()

async def search_with_timeout():
    client = ConnectorClient(base_url=os.getenv("SMARTSEARCH_BASE_URL"))
    await client.authenticate(token=os.getenv("SMARTSEARCH_TOKEN"))

    request = ConnectorRequest(query="documents about Q2 financials")

    response = await client.search_connector(
        app_name=AppName.GOOGLE_DRIVE,
        gl_token=None,
        request=request,
        stream=False,
        timeout=60.0  # 60 seconds timeout
    )

    return response

asyncio.run(search_with_timeout())
```

**Sample queries by connector:**

**Google Drive (`AppName.GOOGLE_DRIVE`):**

* "Find all documents about Q2 financial reports"
* "Show me all PDF files from last month"
* "List documents shared with the team"

**Google Mail (`AppName.GOOGLE_MAIL`):**

* "Find emails from <john@example.com> about project alpha"
* "Show me emails with attachments from this week"
* "Find emails about meeting schedule changes"

**Google Calendar (`AppName.GOOGLE_CALENDAR`):**

* "List all my upcoming meetings today"
* "Show me meetings with Alice next week"
* "What meetings are scheduled in the conference room tomorrow?"

**GitHub (`AppName.GITHUB`):**

* "Find open issues about authentication"
* "Show me pull requests related to bug fixes"
* "List repositories with recent commits"

***

## Models

### AppName Enum

Available connector apps:

```python
from smart_search_sdk.connector.models import AppName

AppName.GITHUB           # GitHub integration
AppName.GOOGLE_MAIL      # Gmail integration
AppName.GOOGLE_CALENDAR  # Google Calendar integration
AppName.GOOGLE_DRIVE     # Google Drive integration
AppName.CHIEF_RETRIEVAL  # Chief Retrieval integration
```

### ConnectorConnectRequest

```python
from smart_search_sdk.connector.models import ConnectorConnectRequest

request = ConnectorConnectRequest(
    callback_url="https://your-app.com/oauth/callback"
)
```

**Fields:**

* `callback_url` (str): OAuth callback URL for authentication

### ConnectorRequest

```python
from smart_search_sdk.connector.models import ConnectorRequest

request = ConnectorRequest(
    query="your natural language query"
)
```

**Fields:**

* `query` (str): Natural language search query

***

## Authentication

The client supports two authentication modes:

### 1. Default Credentials

Uses Smart Search's internal credentials:

```python
response = await client.connect_connector(
    app_name=AppName.GITHUB,
    gl_token=None,  # Use default credentials
    request=request
)
```

### 2. GL Connectors Token

Uses provided GL Connectors token:

```python
response = await client.connect_connector(
    app_name=AppName.GITHUB,
    gl_token="your-gl-token",
    request=request
)
```

***

## Error Handling

```python
import asyncio
import os
from dotenv import load_dotenv

from smart_search_sdk.connector.client import ConnectorClient
from smart_search_sdk.connector.models import ConnectorRequest, AppName

load_dotenv()

async def safe_search():
    client = ConnectorClient(base_url=os.getenv("SMARTSEARCH_BASE_URL"))

    try:
        await client.authenticate(token=os.getenv("SMARTSEARCH_TOKEN"))

        request = ConnectorRequest(query="test query")
        response = await client.search_connector(
            app_name=AppName.GITHUB,
            gl_token=os.getenv("GL_CONNECTORS_USER_TOKEN", ""),
            request=request
        )

        return response

    except Exception as e:
        print(f"Error occurred: {e}")
        # Handle error appropriately
        return None

asyncio.run(safe_search())
```

***

## Complete Examples

### Search Multiple Connectors

```python
import asyncio
import os
from dotenv import load_dotenv

from smart_search_sdk.connector.client import ConnectorClient
from smart_search_sdk.connector.models import ConnectorRequest, AppName

load_dotenv()

async def search_all_connectors(query: str):
    client = ConnectorClient(base_url=os.getenv("SMARTSEARCH_BASE_URL"))
    await client.authenticate(token=os.getenv("SMARTSEARCH_TOKEN"))

    connectors = [
        AppName.GITHUB,
        AppName.GOOGLE_DRIVE,
        AppName.GOOGLE_CALENDAR
    ]

    results = {}
    request = ConnectorRequest(query=query)

    for connector in connectors:
        try:
            response = await client.search_connector(
                app_name=connector,
                gl_token=None,
                request=request
            )
            results[connector.value] = response["data"]
        except Exception as e:
            print(f"Error searching {connector.value}: {e}")

    return results

results = asyncio.run(search_all_connectors("team meetings"))
for connector, data in results.items():
    print(f"\n{connector} results:")
    for item in data[:3]:  # Show top 3 results
        print(f"  - {item['metadata']['title']}")
```

### Setup OAuth Flow

```python
import asyncio
import os
from dotenv import load_dotenv

from smart_search_sdk.connector.client import ConnectorClient
from smart_search_sdk.connector.models import (
    ConnectorConnectRequest,
    AppName
)

load_dotenv()

async def setup_oauth_connector(app_name: AppName):
    client = ConnectorClient(base_url=os.getenv("SMARTSEARCH_BASE_URL"))
    await client.authenticate(token=os.getenv("SMARTSEARCH_TOKEN"))

    request = ConnectorConnectRequest(
        callback_url="https://your-app.com/oauth/callback"
    )

    response = await client.connect_connector(
        app_name=app_name,
        gl_token=None,
        request=request
    )

    if response["status"] == "setup_required":
        print(f"Authentication required for {app_name.value}")
        print(f"Please visit: {response['setup_url']}")
        print("After authentication, you can start searching.")
    elif response["status"] == "connected":
        print(f"Successfully connected to {app_name.value}")

    return response

asyncio.run(setup_oauth_connector(AppName.GOOGLE_DRIVE))
```

***

## Best Practices

1. **Authentication**: Always authenticate before making requests

   ```python
   await client.authenticate(token=os.getenv("SMARTSEARCH_TOKEN"))
   ```
2. **Resource Management**: Use async context managers when possible

   ```python
   async with ConnectorClient(base_url=url) as client:
       await client.authenticate()
       # Use client
   ```
3. **Error Handling**: Wrap requests in try-except blocks

   ```python
   try:
       response = await client.search_connector(...)
   except Exception as e:
       logger.error(f"Search failed: {e}")
   ```
4. **Streaming for Large Results**: Use streaming for better performance

   ```python
   stream = await client.search_connector(..., stream=True)
   async for chunk in stream:
       process_chunk(chunk)
   ```
5. **Timeout Configuration**: Set appropriate timeouts

   ```python
   response = await client.search_connector(..., timeout=60.0)
   ```
6. **Secure Tokens**: Never hardcode GL Connectors tokens

   ```python
   import os
   gl_token = os.getenv("GL_CONNECTORS_USER_TOKEN")
   ```

***

## Related Documentation

* [CLI Connector Commands](https://github.com/GDP-ADMIN/smart-search/blob/dev/applications/smart-search-backend/docs/smart-search-sdk/v2/cli/connector.md)
* [Web Search Client](https://gdplabs.gitbook.io/smart-search/integration-methods/smart-search-sdk/v2/client-api/web-search)
