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_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
@@ -0,0 +1,12 @@
1
+ """Google Suite Drive - Simple Drive API client."""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ from gsuite_drive.client import Drive
6
+ from gsuite_drive.file import File, Folder
7
+
8
+ __all__ = [
9
+ "Drive",
10
+ "File",
11
+ "Folder",
12
+ ]