gsuite-sdk 0.1.0__py3-none-any.whl
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.
- gsuite_calendar/__init__.py +13 -0
- gsuite_calendar/calendar_entity.py +31 -0
- gsuite_calendar/client.py +268 -0
- gsuite_calendar/event.py +57 -0
- gsuite_calendar/parser.py +119 -0
- gsuite_calendar/py.typed +0 -0
- gsuite_core/__init__.py +62 -0
- gsuite_core/api_utils.py +167 -0
- gsuite_core/auth/__init__.py +6 -0
- gsuite_core/auth/oauth.py +249 -0
- gsuite_core/auth/scopes.py +84 -0
- gsuite_core/config.py +73 -0
- gsuite_core/exceptions.py +125 -0
- gsuite_core/py.typed +0 -0
- gsuite_core/storage/__init__.py +13 -0
- gsuite_core/storage/base.py +65 -0
- gsuite_core/storage/secretmanager.py +141 -0
- gsuite_core/storage/sqlite.py +79 -0
- gsuite_drive/__init__.py +12 -0
- gsuite_drive/client.py +401 -0
- gsuite_drive/file.py +103 -0
- gsuite_drive/parser.py +66 -0
- gsuite_drive/py.typed +0 -0
- gsuite_gmail/__init__.py +17 -0
- gsuite_gmail/client.py +412 -0
- gsuite_gmail/label.py +56 -0
- gsuite_gmail/message.py +211 -0
- gsuite_gmail/parser.py +155 -0
- gsuite_gmail/py.typed +0 -0
- gsuite_gmail/query.py +227 -0
- gsuite_gmail/thread.py +54 -0
- gsuite_sdk-0.1.0.dist-info/METADATA +384 -0
- gsuite_sdk-0.1.0.dist-info/RECORD +42 -0
- gsuite_sdk-0.1.0.dist-info/WHEEL +5 -0
- gsuite_sdk-0.1.0.dist-info/licenses/LICENSE +21 -0
- gsuite_sdk-0.1.0.dist-info/top_level.txt +5 -0
- gsuite_sheets/__init__.py +13 -0
- gsuite_sheets/client.py +375 -0
- gsuite_sheets/parser.py +76 -0
- gsuite_sheets/py.typed +0 -0
- gsuite_sheets/spreadsheet.py +97 -0
- gsuite_sheets/worksheet.py +185 -0
gsuite_core/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Token storage implementations."""
|
|
2
|
+
|
|
3
|
+
from gsuite_core.storage.base import TokenStore
|
|
4
|
+
from gsuite_core.storage.sqlite import SQLiteTokenStore
|
|
5
|
+
|
|
6
|
+
__all__ = ["TokenStore", "SQLiteTokenStore"]
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
from gsuite_core.storage.secretmanager import SecretManagerTokenStore
|
|
10
|
+
|
|
11
|
+
__all__.append("SecretManagerTokenStore")
|
|
12
|
+
except ImportError:
|
|
13
|
+
pass
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Token store interface."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TokenStore(ABC):
|
|
8
|
+
"""
|
|
9
|
+
Abstract interface for OAuth token storage.
|
|
10
|
+
|
|
11
|
+
Implementations can store tokens in:
|
|
12
|
+
- SQLite (local development)
|
|
13
|
+
- Secret Manager (Cloud Run)
|
|
14
|
+
- Redis, files, etc.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
@abstractmethod
|
|
18
|
+
def get_token(self, user_id: str = "default") -> dict[str, Any] | None:
|
|
19
|
+
"""
|
|
20
|
+
Retrieve stored token data.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
user_id: User identifier for multi-user setups
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Token data dict or None if not found
|
|
27
|
+
"""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def save_token(self, token_data: dict[str, Any], user_id: str = "default") -> None:
|
|
32
|
+
"""
|
|
33
|
+
Store token data.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
token_data: Token data to store
|
|
37
|
+
user_id: User identifier
|
|
38
|
+
"""
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def delete_token(self, user_id: str = "default") -> bool:
|
|
43
|
+
"""
|
|
44
|
+
Delete stored token.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
user_id: User identifier
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
True if token was deleted
|
|
51
|
+
"""
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
@abstractmethod
|
|
55
|
+
def exists(self, user_id: str = "default") -> bool:
|
|
56
|
+
"""
|
|
57
|
+
Check if token exists.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
user_id: User identifier
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
True if token exists
|
|
64
|
+
"""
|
|
65
|
+
pass
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Secret Manager token storage implementation."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from google.api_core import exceptions
|
|
8
|
+
from google.cloud import secretmanager
|
|
9
|
+
|
|
10
|
+
from gsuite_core.storage.base import TokenStore
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SecretManagerTokenStore(TokenStore):
|
|
16
|
+
"""
|
|
17
|
+
Google Secret Manager token storage for Cloud Run.
|
|
18
|
+
|
|
19
|
+
Stores OAuth tokens as secrets in Google Cloud Secret Manager.
|
|
20
|
+
Required for stateless deployments.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
project_id: str,
|
|
26
|
+
secret_name: str = "gsuite-token",
|
|
27
|
+
auto_create: bool = False,
|
|
28
|
+
):
|
|
29
|
+
"""
|
|
30
|
+
Initialize Secret Manager token store.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
project_id: GCP project ID
|
|
34
|
+
secret_name: Name for the secret
|
|
35
|
+
auto_create: Auto-create secret if it doesn't exist
|
|
36
|
+
"""
|
|
37
|
+
self.project_id = project_id
|
|
38
|
+
self.secret_name = secret_name
|
|
39
|
+
self.auto_create = auto_create
|
|
40
|
+
self.client = secretmanager.SecretManagerServiceClient()
|
|
41
|
+
|
|
42
|
+
self._secret_path = f"projects/{project_id}/secrets/{secret_name}"
|
|
43
|
+
|
|
44
|
+
def _get_latest_version_path(self) -> str:
|
|
45
|
+
"""Get path to latest secret version."""
|
|
46
|
+
return f"{self._secret_path}/versions/latest"
|
|
47
|
+
|
|
48
|
+
def _ensure_secret_exists(self) -> None:
|
|
49
|
+
"""Create secret if it doesn't exist and auto_create is enabled."""
|
|
50
|
+
if not self.auto_create:
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
self.client.get_secret(request={"name": self._secret_path})
|
|
55
|
+
except exceptions.NotFound:
|
|
56
|
+
logger.info(f"Creating secret: {self.secret_name}")
|
|
57
|
+
self.client.create_secret(
|
|
58
|
+
request={
|
|
59
|
+
"parent": f"projects/{self.project_id}",
|
|
60
|
+
"secret_id": self.secret_name,
|
|
61
|
+
"secret": {"replication": {"automatic": {}}},
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def get_token(self, user_id: str = "default") -> dict[str, Any] | None:
|
|
66
|
+
"""Retrieve stored token data from Secret Manager."""
|
|
67
|
+
try:
|
|
68
|
+
response = self.client.access_secret_version(
|
|
69
|
+
request={"name": self._get_latest_version_path()}
|
|
70
|
+
)
|
|
71
|
+
data = json.loads(response.payload.data.decode("utf-8"))
|
|
72
|
+
|
|
73
|
+
if user_id in data:
|
|
74
|
+
return data[user_id]
|
|
75
|
+
elif user_id == "default" and "token" in data:
|
|
76
|
+
return data
|
|
77
|
+
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
except exceptions.NotFound:
|
|
81
|
+
return None
|
|
82
|
+
except Exception as e:
|
|
83
|
+
logger.error(f"Error accessing secret: {e}")
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
def save_token(self, token_data: dict[str, Any], user_id: str = "default") -> None:
|
|
87
|
+
"""Store token data in Secret Manager."""
|
|
88
|
+
self._ensure_secret_exists()
|
|
89
|
+
|
|
90
|
+
existing = {}
|
|
91
|
+
try:
|
|
92
|
+
response = self.client.access_secret_version(
|
|
93
|
+
request={"name": self._get_latest_version_path()}
|
|
94
|
+
)
|
|
95
|
+
existing = json.loads(response.payload.data.decode("utf-8"))
|
|
96
|
+
except exceptions.NotFound:
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
if user_id == "default" and not existing:
|
|
100
|
+
payload = token_data
|
|
101
|
+
else:
|
|
102
|
+
existing[user_id] = token_data
|
|
103
|
+
payload = existing
|
|
104
|
+
|
|
105
|
+
self.client.add_secret_version(
|
|
106
|
+
request={
|
|
107
|
+
"parent": self._secret_path,
|
|
108
|
+
"payload": {"data": json.dumps(payload).encode("utf-8")},
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
|
+
logger.info(f"Token saved to Secret Manager: {self.secret_name}")
|
|
112
|
+
|
|
113
|
+
def delete_token(self, user_id: str = "default") -> bool:
|
|
114
|
+
"""Delete token from Secret Manager."""
|
|
115
|
+
try:
|
|
116
|
+
response = self.client.access_secret_version(
|
|
117
|
+
request={"name": self._get_latest_version_path()}
|
|
118
|
+
)
|
|
119
|
+
data = json.loads(response.payload.data.decode("utf-8"))
|
|
120
|
+
|
|
121
|
+
if user_id in data:
|
|
122
|
+
del data[user_id]
|
|
123
|
+
self.client.add_secret_version(
|
|
124
|
+
request={
|
|
125
|
+
"parent": self._secret_path,
|
|
126
|
+
"payload": {"data": json.dumps(data).encode("utf-8")},
|
|
127
|
+
}
|
|
128
|
+
)
|
|
129
|
+
return True
|
|
130
|
+
elif user_id == "default":
|
|
131
|
+
self.client.delete_secret(request={"name": self._secret_path})
|
|
132
|
+
return True
|
|
133
|
+
|
|
134
|
+
return False
|
|
135
|
+
|
|
136
|
+
except exceptions.NotFound:
|
|
137
|
+
return False
|
|
138
|
+
|
|
139
|
+
def exists(self, user_id: str = "default") -> bool:
|
|
140
|
+
"""Check if token exists in Secret Manager."""
|
|
141
|
+
return self.get_token(user_id) is not None
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""SQLite token storage implementation."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sqlite3
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from gsuite_core.storage.base import TokenStore
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SQLiteTokenStore(TokenStore):
|
|
12
|
+
"""
|
|
13
|
+
SQLite-based token storage for local development.
|
|
14
|
+
|
|
15
|
+
Stores OAuth tokens in a local SQLite database file.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, db_path: str = "tokens.db"):
|
|
19
|
+
"""
|
|
20
|
+
Initialize SQLite token store.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
db_path: Path to SQLite database file
|
|
24
|
+
"""
|
|
25
|
+
self.db_path = Path(db_path)
|
|
26
|
+
self._init_db()
|
|
27
|
+
|
|
28
|
+
def _init_db(self) -> None:
|
|
29
|
+
"""Create tokens table if it doesn't exist."""
|
|
30
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
31
|
+
conn.execute("""
|
|
32
|
+
CREATE TABLE IF NOT EXISTS tokens (
|
|
33
|
+
user_id TEXT PRIMARY KEY,
|
|
34
|
+
token_data TEXT NOT NULL,
|
|
35
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
36
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
37
|
+
)
|
|
38
|
+
""")
|
|
39
|
+
conn.commit()
|
|
40
|
+
|
|
41
|
+
def get_token(self, user_id: str = "default") -> dict[str, Any] | None:
|
|
42
|
+
"""Retrieve stored token data."""
|
|
43
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
44
|
+
cursor = conn.execute("SELECT token_data FROM tokens WHERE user_id = ?", (user_id,))
|
|
45
|
+
row = cursor.fetchone()
|
|
46
|
+
|
|
47
|
+
if row:
|
|
48
|
+
return json.loads(row[0])
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
def save_token(self, token_data: dict[str, Any], user_id: str = "default") -> None:
|
|
52
|
+
"""Store token data."""
|
|
53
|
+
token_json = json.dumps(token_data)
|
|
54
|
+
|
|
55
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
56
|
+
conn.execute(
|
|
57
|
+
"""
|
|
58
|
+
INSERT INTO tokens (user_id, token_data, updated_at)
|
|
59
|
+
VALUES (?, ?, CURRENT_TIMESTAMP)
|
|
60
|
+
ON CONFLICT(user_id) DO UPDATE SET
|
|
61
|
+
token_data = excluded.token_data,
|
|
62
|
+
updated_at = CURRENT_TIMESTAMP
|
|
63
|
+
""",
|
|
64
|
+
(user_id, token_json),
|
|
65
|
+
)
|
|
66
|
+
conn.commit()
|
|
67
|
+
|
|
68
|
+
def delete_token(self, user_id: str = "default") -> bool:
|
|
69
|
+
"""Delete stored token."""
|
|
70
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
71
|
+
cursor = conn.execute("DELETE FROM tokens WHERE user_id = ?", (user_id,))
|
|
72
|
+
conn.commit()
|
|
73
|
+
return cursor.rowcount > 0
|
|
74
|
+
|
|
75
|
+
def exists(self, user_id: str = "default") -> bool:
|
|
76
|
+
"""Check if token exists."""
|
|
77
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
78
|
+
cursor = conn.execute("SELECT 1 FROM tokens WHERE user_id = ?", (user_id,))
|
|
79
|
+
return cursor.fetchone() is not None
|