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.
@@ -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,7 @@
1
+ """Leash SDK - access Gmail, Calendar, Drive and more via the Leash platform."""
2
+
3
+ from leash.client import LeashIntegrations
4
+ from leash.types import LeashError
5
+
6
+ __all__ = ["LeashIntegrations", "LeashError"]
7
+ __version__ = "0.1.0"
@@ -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
+ 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*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,9 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name="leash-sdk",
5
+ version="0.1.0",
6
+ packages=find_packages(),
7
+ install_requires=["requests>=2.28"],
8
+ python_requires=">=3.8",
9
+ )