leash-sdk 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- leash_sdk-0.1.0/PKG-INFO +49 -0
- leash_sdk-0.1.0/README.md +38 -0
- leash_sdk-0.1.0/leash/__init__.py +7 -0
- leash_sdk-0.1.0/leash/calendar.py +88 -0
- leash_sdk-0.1.0/leash/client.py +122 -0
- leash_sdk-0.1.0/leash/drive.py +118 -0
- leash_sdk-0.1.0/leash/gmail.py +98 -0
- leash_sdk-0.1.0/leash/types.py +16 -0
- leash_sdk-0.1.0/leash_sdk.egg-info/PKG-INFO +49 -0
- leash_sdk-0.1.0/leash_sdk.egg-info/SOURCES.txt +14 -0
- leash_sdk-0.1.0/leash_sdk.egg-info/dependency_links.txt +1 -0
- leash_sdk-0.1.0/leash_sdk.egg-info/requires.txt +1 -0
- leash_sdk-0.1.0/leash_sdk.egg-info/top_level.txt +1 -0
- leash_sdk-0.1.0/pyproject.toml +20 -0
- leash_sdk-0.1.0/setup.cfg +4 -0
- leash_sdk-0.1.0/setup.py +9 -0
leash_sdk-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: leash-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Leash platform SDK - access Gmail, Calendar, Drive and more from deployed apps
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://leash.build
|
|
7
|
+
Requires-Python: >=3.8
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: requests>=2.28
|
|
10
|
+
Dynamic: requires-python
|
|
11
|
+
|
|
12
|
+
# Leash SDK (Python)
|
|
13
|
+
|
|
14
|
+
Python SDK for accessing platform integrations (Gmail, Google Calendar, Google Drive) from apps deployed on [Leash](https://leash.build).
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install leash-sdk
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from leash import LeashIntegrations
|
|
26
|
+
|
|
27
|
+
# Get the auth token from the leash-auth cookie (e.g. in Flask/Django)
|
|
28
|
+
integrations = LeashIntegrations(auth_token="your-jwt-token")
|
|
29
|
+
|
|
30
|
+
# Gmail
|
|
31
|
+
emails = integrations.gmail.list_messages(max_results=10)
|
|
32
|
+
integrations.gmail.send_message(to="user@example.com", subject="Hello", body="World")
|
|
33
|
+
|
|
34
|
+
# Calendar
|
|
35
|
+
events = integrations.calendar.list_events(time_min="2026-04-01T00:00:00Z")
|
|
36
|
+
integrations.calendar.create_event(
|
|
37
|
+
summary="Meeting",
|
|
38
|
+
start="2026-04-10T10:00:00Z",
|
|
39
|
+
end="2026-04-10T11:00:00Z",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Drive
|
|
43
|
+
files = integrations.drive.list_files(max_results=10)
|
|
44
|
+
integrations.drive.upload_file(name="notes.txt", content="hello", mime_type="text/plain")
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## How it works
|
|
48
|
+
|
|
49
|
+
The SDK calls the Leash platform proxy at `https://api.leash.build/api/integrations/{provider}/{action}`. The platform handles all OAuth token management -- the SDK just passes the user's auth token and makes typed API calls.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Leash SDK (Python)
|
|
2
|
+
|
|
3
|
+
Python SDK for accessing platform integrations (Gmail, Google Calendar, Google Drive) from apps deployed on [Leash](https://leash.build).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install leash-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from leash import LeashIntegrations
|
|
15
|
+
|
|
16
|
+
# Get the auth token from the leash-auth cookie (e.g. in Flask/Django)
|
|
17
|
+
integrations = LeashIntegrations(auth_token="your-jwt-token")
|
|
18
|
+
|
|
19
|
+
# Gmail
|
|
20
|
+
emails = integrations.gmail.list_messages(max_results=10)
|
|
21
|
+
integrations.gmail.send_message(to="user@example.com", subject="Hello", body="World")
|
|
22
|
+
|
|
23
|
+
# Calendar
|
|
24
|
+
events = integrations.calendar.list_events(time_min="2026-04-01T00:00:00Z")
|
|
25
|
+
integrations.calendar.create_event(
|
|
26
|
+
summary="Meeting",
|
|
27
|
+
start="2026-04-10T10:00:00Z",
|
|
28
|
+
end="2026-04-10T11:00:00Z",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Drive
|
|
32
|
+
files = integrations.drive.list_files(max_results=10)
|
|
33
|
+
integrations.drive.upload_file(name="notes.txt", content="hello", mime_type="text/plain")
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## How it works
|
|
37
|
+
|
|
38
|
+
The SDK calls the Leash platform proxy at `https://api.leash.build/api/integrations/{provider}/{action}`. The platform handles all OAuth token management -- the SDK just passes the user's auth token and makes typed API calls.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Google Calendar integration client."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
|
|
5
|
+
from leash.types import CallFn
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CalendarClient:
|
|
9
|
+
"""Client for Google Calendar operations via the Leash platform proxy."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, call: CallFn):
|
|
12
|
+
self._call = call
|
|
13
|
+
|
|
14
|
+
def list_calendars(self) -> Dict[str, Any]:
|
|
15
|
+
"""List all calendars accessible to the user.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
Dict with calendar list data.
|
|
19
|
+
"""
|
|
20
|
+
return self._call("google_calendar", "list-calendars", {})
|
|
21
|
+
|
|
22
|
+
def list_events(
|
|
23
|
+
self,
|
|
24
|
+
calendar_id: str = "primary",
|
|
25
|
+
time_min: Optional[str] = None,
|
|
26
|
+
time_max: Optional[str] = None,
|
|
27
|
+
max_results: int = 10,
|
|
28
|
+
) -> Dict[str, Any]:
|
|
29
|
+
"""List events on a calendar.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
calendar_id: Calendar identifier (default 'primary').
|
|
33
|
+
time_min: Lower bound for event start time (RFC 3339, e.g. '2026-04-01T00:00:00Z').
|
|
34
|
+
time_max: Upper bound for event start time (RFC 3339).
|
|
35
|
+
max_results: Maximum number of events to return.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Dict with 'items' list of events.
|
|
39
|
+
"""
|
|
40
|
+
params: Dict[str, Any] = {"calendarId": calendar_id, "maxResults": max_results}
|
|
41
|
+
if time_min:
|
|
42
|
+
params["timeMin"] = time_min
|
|
43
|
+
if time_max:
|
|
44
|
+
params["timeMax"] = time_max
|
|
45
|
+
return self._call("google_calendar", "list-events", params)
|
|
46
|
+
|
|
47
|
+
def get_event(self, calendar_id: str, event_id: str) -> Dict[str, Any]:
|
|
48
|
+
"""Get a single event by ID.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
calendar_id: Calendar identifier.
|
|
52
|
+
event_id: Event identifier.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
The event object.
|
|
56
|
+
"""
|
|
57
|
+
return self._call("google_calendar", "get-event", {"calendarId": calendar_id, "eventId": event_id})
|
|
58
|
+
|
|
59
|
+
def create_event(
|
|
60
|
+
self,
|
|
61
|
+
calendar_id: str = "primary",
|
|
62
|
+
summary: Optional[str] = None,
|
|
63
|
+
start: Optional[str] = None,
|
|
64
|
+
end: Optional[str] = None,
|
|
65
|
+
description: Optional[str] = None,
|
|
66
|
+
) -> Dict[str, Any]:
|
|
67
|
+
"""Create a new calendar event.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
calendar_id: Calendar identifier (default 'primary').
|
|
71
|
+
summary: Event title.
|
|
72
|
+
start: Start time (RFC 3339, e.g. '2026-04-10T10:00:00Z').
|
|
73
|
+
end: End time (RFC 3339).
|
|
74
|
+
description: Event description.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
The created event object.
|
|
78
|
+
"""
|
|
79
|
+
params: Dict[str, Any] = {"calendarId": calendar_id}
|
|
80
|
+
if summary:
|
|
81
|
+
params["summary"] = summary
|
|
82
|
+
if start:
|
|
83
|
+
params["start"] = start
|
|
84
|
+
if end:
|
|
85
|
+
params["end"] = end
|
|
86
|
+
if description:
|
|
87
|
+
params["description"] = description
|
|
88
|
+
return self._call("google_calendar", "create-event", params)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""Main LeashIntegrations client."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from leash.calendar import CalendarClient
|
|
8
|
+
from leash.drive import DriveClient
|
|
9
|
+
from leash.gmail import GmailClient
|
|
10
|
+
from leash.types import LeashError
|
|
11
|
+
|
|
12
|
+
DEFAULT_PLATFORM_URL = "https://api.leash.build"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class LeashIntegrations:
|
|
16
|
+
"""Client for accessing Leash platform integrations.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
auth_token: The leash-auth JWT token (from cookie or env).
|
|
20
|
+
platform_url: Base URL of the Leash platform API.
|
|
21
|
+
Defaults to https://api.leash.build.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, auth_token: str, platform_url: str = DEFAULT_PLATFORM_URL):
|
|
25
|
+
self.auth_token = auth_token
|
|
26
|
+
self.platform_url = platform_url.rstrip("/")
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def gmail(self) -> GmailClient:
|
|
30
|
+
"""Gmail integration client."""
|
|
31
|
+
return GmailClient(self._call)
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def calendar(self) -> CalendarClient:
|
|
35
|
+
"""Google Calendar integration client."""
|
|
36
|
+
return CalendarClient(self._call)
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def drive(self) -> DriveClient:
|
|
40
|
+
"""Google Drive integration client."""
|
|
41
|
+
return DriveClient(self._call)
|
|
42
|
+
|
|
43
|
+
def _call(self, provider: str, action: str, params: Optional[Dict[str, Any]] = None) -> Any:
|
|
44
|
+
"""Call the platform proxy.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
provider: Integration provider name (e.g. 'gmail').
|
|
48
|
+
action: Action to perform (e.g. 'list-messages').
|
|
49
|
+
params: Optional request body parameters.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
The ``data`` field from the platform response.
|
|
53
|
+
|
|
54
|
+
Raises:
|
|
55
|
+
LeashError: If the platform returns a non-success response.
|
|
56
|
+
"""
|
|
57
|
+
response = requests.post(
|
|
58
|
+
f"{self.platform_url}/api/integrations/{provider}/{action}",
|
|
59
|
+
json=params or {},
|
|
60
|
+
headers={
|
|
61
|
+
"Authorization": f"Bearer {self.auth_token}",
|
|
62
|
+
"Content-Type": "application/json",
|
|
63
|
+
},
|
|
64
|
+
)
|
|
65
|
+
data = response.json()
|
|
66
|
+
if not data.get("success"):
|
|
67
|
+
raise LeashError(
|
|
68
|
+
message=data.get("error", "Unknown error"),
|
|
69
|
+
code=data.get("code"),
|
|
70
|
+
connect_url=data.get("connectUrl"),
|
|
71
|
+
)
|
|
72
|
+
return data.get("data")
|
|
73
|
+
|
|
74
|
+
def is_connected(self, provider_id: str) -> bool:
|
|
75
|
+
"""Check if a provider is connected for the current user.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
provider_id: The provider identifier (e.g. 'gmail').
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
True if the provider is actively connected, False otherwise.
|
|
82
|
+
"""
|
|
83
|
+
try:
|
|
84
|
+
connections = self.get_connections()
|
|
85
|
+
conn = next((c for c in connections if c.get("providerId") == provider_id), None)
|
|
86
|
+
return conn is not None and conn.get("status") == "active"
|
|
87
|
+
except Exception:
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
def get_connections(self) -> list:
|
|
91
|
+
"""Get connection status for all providers.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
A list of connection status dicts.
|
|
95
|
+
"""
|
|
96
|
+
response = requests.get(
|
|
97
|
+
f"{self.platform_url}/api/integrations/connections",
|
|
98
|
+
headers={"Authorization": f"Bearer {self.auth_token}"},
|
|
99
|
+
)
|
|
100
|
+
data = response.json()
|
|
101
|
+
if not data.get("success"):
|
|
102
|
+
raise LeashError(
|
|
103
|
+
message=data.get("error", "Unknown error"),
|
|
104
|
+
code=data.get("code"),
|
|
105
|
+
)
|
|
106
|
+
return data.get("data", [])
|
|
107
|
+
|
|
108
|
+
def get_connect_url(self, provider_id: str, return_url: Optional[str] = None) -> str:
|
|
109
|
+
"""Get the URL to connect a provider (for UI buttons).
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
provider_id: The provider identifier.
|
|
113
|
+
return_url: Optional URL to redirect back to after connecting.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
The full URL to initiate the OAuth connection flow.
|
|
117
|
+
"""
|
|
118
|
+
url = f"{self.platform_url}/api/integrations/connect/{provider_id}"
|
|
119
|
+
if return_url:
|
|
120
|
+
from urllib.parse import quote
|
|
121
|
+
url += f"?return_url={quote(return_url)}"
|
|
122
|
+
return url
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Google Drive integration client."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
|
|
5
|
+
from leash.types import CallFn
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DriveClient:
|
|
9
|
+
"""Client for Google Drive operations via the Leash platform proxy."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, call: CallFn):
|
|
12
|
+
self._call = call
|
|
13
|
+
|
|
14
|
+
def list_files(
|
|
15
|
+
self,
|
|
16
|
+
query: Optional[str] = None,
|
|
17
|
+
max_results: int = 20,
|
|
18
|
+
folder_id: Optional[str] = None,
|
|
19
|
+
) -> Dict[str, Any]:
|
|
20
|
+
"""List files in the user's Drive.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
query: Drive search query (Google Drive API query syntax).
|
|
24
|
+
max_results: Maximum number of files to return.
|
|
25
|
+
folder_id: Restrict to files within a specific folder.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Dict with 'files' list.
|
|
29
|
+
"""
|
|
30
|
+
params: Dict[str, Any] = {"maxResults": max_results}
|
|
31
|
+
if query:
|
|
32
|
+
params["query"] = query
|
|
33
|
+
if folder_id:
|
|
34
|
+
params["folderId"] = folder_id
|
|
35
|
+
return self._call("google_drive", "list-files", params)
|
|
36
|
+
|
|
37
|
+
def get_file(self, file_id: str) -> Dict[str, Any]:
|
|
38
|
+
"""Get file metadata by ID.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
file_id: The file identifier.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
The file metadata object.
|
|
45
|
+
"""
|
|
46
|
+
return self._call("google_drive", "get-file", {"fileId": file_id})
|
|
47
|
+
|
|
48
|
+
def download_file(self, file_id: str) -> Any:
|
|
49
|
+
"""Download file content by ID.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
file_id: The file identifier.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
The file content (format depends on file type).
|
|
56
|
+
"""
|
|
57
|
+
return self._call("google_drive", "download-file", {"fileId": file_id})
|
|
58
|
+
|
|
59
|
+
def create_folder(self, name: str, parent_id: Optional[str] = None) -> Dict[str, Any]:
|
|
60
|
+
"""Create a new folder.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
name: Folder name.
|
|
64
|
+
parent_id: Optional parent folder ID.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
The created folder metadata.
|
|
68
|
+
"""
|
|
69
|
+
params: Dict[str, Any] = {"name": name}
|
|
70
|
+
if parent_id:
|
|
71
|
+
params["parentId"] = parent_id
|
|
72
|
+
return self._call("google_drive", "create-folder", params)
|
|
73
|
+
|
|
74
|
+
def upload_file(
|
|
75
|
+
self,
|
|
76
|
+
name: str,
|
|
77
|
+
content: str,
|
|
78
|
+
mime_type: str,
|
|
79
|
+
parent_id: Optional[str] = None,
|
|
80
|
+
) -> Dict[str, Any]:
|
|
81
|
+
"""Upload a file to Drive.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
name: File name.
|
|
85
|
+
content: File content (text or base64-encoded).
|
|
86
|
+
mime_type: MIME type of the file (e.g. 'text/plain').
|
|
87
|
+
parent_id: Optional parent folder ID.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
The created file metadata.
|
|
91
|
+
"""
|
|
92
|
+
params: Dict[str, Any] = {"name": name, "content": content, "mimeType": mime_type}
|
|
93
|
+
if parent_id:
|
|
94
|
+
params["parentId"] = parent_id
|
|
95
|
+
return self._call("google_drive", "upload-file", params)
|
|
96
|
+
|
|
97
|
+
def delete_file(self, file_id: str) -> Dict[str, Any]:
|
|
98
|
+
"""Delete a file by ID.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
file_id: The file identifier.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Confirmation of deletion.
|
|
105
|
+
"""
|
|
106
|
+
return self._call("google_drive", "delete-file", {"fileId": file_id})
|
|
107
|
+
|
|
108
|
+
def search_files(self, query: str, max_results: int = 20) -> Dict[str, Any]:
|
|
109
|
+
"""Search files using a query string.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
query: Search query (Google Drive API query syntax).
|
|
113
|
+
max_results: Maximum number of results.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Dict with 'files' list.
|
|
117
|
+
"""
|
|
118
|
+
return self._call("google_drive", "search-files", {"query": query, "maxResults": max_results})
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Gmail integration client."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
from leash.types import CallFn
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GmailClient:
|
|
9
|
+
"""Client for Gmail operations via the Leash platform proxy."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, call: CallFn):
|
|
12
|
+
self._call = call
|
|
13
|
+
|
|
14
|
+
def list_messages(
|
|
15
|
+
self,
|
|
16
|
+
query: Optional[str] = None,
|
|
17
|
+
max_results: int = 20,
|
|
18
|
+
label_ids: Optional[List[str]] = None,
|
|
19
|
+
page_token: Optional[str] = None,
|
|
20
|
+
) -> Dict[str, Any]:
|
|
21
|
+
"""List messages in the user's mailbox.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
query: Gmail search query (e.g. 'from:user@example.com').
|
|
25
|
+
max_results: Maximum number of messages to return.
|
|
26
|
+
label_ids: Filter by label IDs (e.g. ['INBOX']).
|
|
27
|
+
page_token: Token for fetching the next page of results.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Dict with 'messages', 'nextPageToken', and 'resultSizeEstimate'.
|
|
31
|
+
"""
|
|
32
|
+
params: Dict[str, Any] = {"maxResults": max_results}
|
|
33
|
+
if query:
|
|
34
|
+
params["query"] = query
|
|
35
|
+
if label_ids:
|
|
36
|
+
params["labelIds"] = label_ids
|
|
37
|
+
if page_token:
|
|
38
|
+
params["pageToken"] = page_token
|
|
39
|
+
return self._call("gmail", "list-messages", params)
|
|
40
|
+
|
|
41
|
+
def get_message(self, message_id: str, format: str = "full") -> Dict[str, Any]:
|
|
42
|
+
"""Get a single message by ID.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
message_id: The message ID.
|
|
46
|
+
format: Response format ('full', 'metadata', 'minimal', 'raw').
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
The full message object.
|
|
50
|
+
"""
|
|
51
|
+
return self._call("gmail", "get-message", {"messageId": message_id, "format": format})
|
|
52
|
+
|
|
53
|
+
def send_message(
|
|
54
|
+
self,
|
|
55
|
+
to: str,
|
|
56
|
+
subject: str,
|
|
57
|
+
body: str,
|
|
58
|
+
cc: Optional[str] = None,
|
|
59
|
+
bcc: Optional[str] = None,
|
|
60
|
+
) -> Dict[str, Any]:
|
|
61
|
+
"""Send an email message.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
to: Recipient email address.
|
|
65
|
+
subject: Email subject line.
|
|
66
|
+
body: Email body text.
|
|
67
|
+
cc: CC recipient(s).
|
|
68
|
+
bcc: BCC recipient(s).
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
The sent message metadata.
|
|
72
|
+
"""
|
|
73
|
+
params: Dict[str, Any] = {"to": to, "subject": subject, "body": body}
|
|
74
|
+
if cc:
|
|
75
|
+
params["cc"] = cc
|
|
76
|
+
if bcc:
|
|
77
|
+
params["bcc"] = bcc
|
|
78
|
+
return self._call("gmail", "send-message", params)
|
|
79
|
+
|
|
80
|
+
def search_messages(self, query: str, max_results: int = 20) -> Dict[str, Any]:
|
|
81
|
+
"""Search messages using a Gmail query string.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
query: Gmail search query.
|
|
85
|
+
max_results: Maximum number of results to return.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Dict with 'messages', 'nextPageToken', and 'resultSizeEstimate'.
|
|
89
|
+
"""
|
|
90
|
+
return self._call("gmail", "search-messages", {"query": query, "maxResults": max_results})
|
|
91
|
+
|
|
92
|
+
def list_labels(self) -> Dict[str, Any]:
|
|
93
|
+
"""List all labels in the user's mailbox.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Dict with 'labels' list.
|
|
97
|
+
"""
|
|
98
|
+
return self._call("gmail", "list-labels", {})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Shared types for the Leash SDK."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Dict, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class LeashError(Exception):
|
|
7
|
+
"""Raised when a Leash platform API call fails."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, message: str, code: Optional[str] = None, connect_url: Optional[str] = None):
|
|
10
|
+
super().__init__(message)
|
|
11
|
+
self.code = code
|
|
12
|
+
self.connect_url = connect_url
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Type alias for the internal call function passed to provider clients.
|
|
16
|
+
CallFn = Callable[[str, str, Optional[Dict[str, Any]]], Any]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: leash-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Leash platform SDK - access Gmail, Calendar, Drive and more from deployed apps
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://leash.build
|
|
7
|
+
Requires-Python: >=3.8
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: requests>=2.28
|
|
10
|
+
Dynamic: requires-python
|
|
11
|
+
|
|
12
|
+
# Leash SDK (Python)
|
|
13
|
+
|
|
14
|
+
Python SDK for accessing platform integrations (Gmail, Google Calendar, Google Drive) from apps deployed on [Leash](https://leash.build).
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install leash-sdk
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from leash import LeashIntegrations
|
|
26
|
+
|
|
27
|
+
# Get the auth token from the leash-auth cookie (e.g. in Flask/Django)
|
|
28
|
+
integrations = LeashIntegrations(auth_token="your-jwt-token")
|
|
29
|
+
|
|
30
|
+
# Gmail
|
|
31
|
+
emails = integrations.gmail.list_messages(max_results=10)
|
|
32
|
+
integrations.gmail.send_message(to="user@example.com", subject="Hello", body="World")
|
|
33
|
+
|
|
34
|
+
# Calendar
|
|
35
|
+
events = integrations.calendar.list_events(time_min="2026-04-01T00:00:00Z")
|
|
36
|
+
integrations.calendar.create_event(
|
|
37
|
+
summary="Meeting",
|
|
38
|
+
start="2026-04-10T10:00:00Z",
|
|
39
|
+
end="2026-04-10T11:00:00Z",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Drive
|
|
43
|
+
files = integrations.drive.list_files(max_results=10)
|
|
44
|
+
integrations.drive.upload_file(name="notes.txt", content="hello", mime_type="text/plain")
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## How it works
|
|
48
|
+
|
|
49
|
+
The SDK calls the Leash platform proxy at `https://api.leash.build/api/integrations/{provider}/{action}`. The platform handles all OAuth token management -- the SDK just passes the user's auth token and makes typed API calls.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
setup.py
|
|
4
|
+
leash/__init__.py
|
|
5
|
+
leash/calendar.py
|
|
6
|
+
leash/client.py
|
|
7
|
+
leash/drive.py
|
|
8
|
+
leash/gmail.py
|
|
9
|
+
leash/types.py
|
|
10
|
+
leash_sdk.egg-info/PKG-INFO
|
|
11
|
+
leash_sdk.egg-info/SOURCES.txt
|
|
12
|
+
leash_sdk.egg-info/dependency_links.txt
|
|
13
|
+
leash_sdk.egg-info/requires.txt
|
|
14
|
+
leash_sdk.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
requests>=2.28
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
leash
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "leash-sdk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Leash platform SDK - access Gmail, Calendar, Drive and more from deployed apps"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
requires-python = ">=3.8"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"requests>=2.28",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.urls]
|
|
17
|
+
Homepage = "https://leash.build"
|
|
18
|
+
|
|
19
|
+
[tool.setuptools.packages.find]
|
|
20
|
+
include = ["leash*"]
|