lestash 1.63.0__tar.gz → 1.63.2__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.
- {lestash-1.63.0 → lestash-1.63.2}/PKG-INFO +1 -1
- {lestash-1.63.0 → lestash-1.63.2}/pyproject.toml +1 -1
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/google_auth.py +29 -7
- {lestash-1.63.0 → lestash-1.63.2}/.gitignore +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/README.md +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/__init__.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/__main__.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/cli/__init__.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/cli/config.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/cli/db.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/cli/embeddings.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/cli/enrich.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/cli/google.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/cli/items.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/cli/main.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/cli/media.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/cli/profiles.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/cli/sources.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/__init__.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/config.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/database.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/db_admin.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/embeddings.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/enrichment.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/exceptions.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/google_drive.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/logging.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/pdf_enrich/TUNING.md +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/pdf_enrich/__init__.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/pdf_enrich/annotations.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/pdf_enrich/cleanup.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/pdf_enrich/extractor.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/pdf_enrich/images.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/pdf_enrich/links.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/pdf_enrich/ocr.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/pdf_enrich/persistence.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/pdf_enrich/runner.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/pdf_enrich/types.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/pdf_enrich/version.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/core/text_extract.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/models/__init__.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/models/item.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/models/source.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/models/tag.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/plugins/__init__.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/plugins/base.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/plugins/loader.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/src/lestash/plugins/publisher.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/tests/__init__.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/tests/conftest.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/tests/pdf_enrich/__init__.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/tests/pdf_enrich/conftest.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/tests/pdf_enrich/test_annotations.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/tests/pdf_enrich/test_cleanup.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/tests/pdf_enrich/test_extractor.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/tests/pdf_enrich/test_images.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/tests/pdf_enrich/test_links.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/tests/pdf_enrich/test_ocr.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/tests/pdf_enrich/test_persistence.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/tests/pdf_enrich/test_runner.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/tests/test_db_admin.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/tests/test_history.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/tests/test_items_display.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/tests/test_post_cache.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/tests/test_publisher.py +0 -0
- {lestash-1.63.0 → lestash-1.63.2}/tests/test_text_extract.py +0 -0
|
@@ -80,6 +80,7 @@ def save_credentials(credentials: Credentials, scopes: list[str] | None = None)
|
|
|
80
80
|
"client_id": credentials.client_id,
|
|
81
81
|
"client_secret": credentials.client_secret,
|
|
82
82
|
"scopes": list(credentials.scopes) if credentials.scopes else (scopes or DEFAULT_SCOPES),
|
|
83
|
+
"expiry": credentials.expiry.isoformat() if credentials.expiry else None,
|
|
83
84
|
}
|
|
84
85
|
path = get_credentials_path()
|
|
85
86
|
path.write_text(json.dumps(creds_data, indent=2))
|
|
@@ -88,12 +89,19 @@ def save_credentials(credentials: Credentials, scopes: list[str] | None = None)
|
|
|
88
89
|
|
|
89
90
|
def load_credentials() -> Credentials | None:
|
|
90
91
|
"""Load OAuth credentials from config file."""
|
|
92
|
+
from datetime import datetime
|
|
93
|
+
|
|
91
94
|
path = get_credentials_path()
|
|
92
95
|
if not path.exists():
|
|
93
96
|
return None
|
|
94
97
|
|
|
95
98
|
try:
|
|
96
99
|
creds_data = json.loads(path.read_text())
|
|
100
|
+
expiry_str = creds_data.get("expiry")
|
|
101
|
+
expiry = datetime.fromisoformat(expiry_str) if expiry_str else None
|
|
102
|
+
# google-auth uses naive UTC datetimes internally; strip tzinfo if present
|
|
103
|
+
if expiry and expiry.tzinfo is not None:
|
|
104
|
+
expiry = expiry.replace(tzinfo=None)
|
|
97
105
|
return Credentials(
|
|
98
106
|
token=creds_data.get("token"),
|
|
99
107
|
refresh_token=creds_data.get("refresh_token"),
|
|
@@ -101,6 +109,7 @@ def load_credentials() -> Credentials | None:
|
|
|
101
109
|
client_id=creds_data.get("client_id"),
|
|
102
110
|
client_secret=creds_data.get("client_secret"),
|
|
103
111
|
scopes=creds_data.get("scopes", DEFAULT_SCOPES),
|
|
112
|
+
expiry=expiry,
|
|
104
113
|
)
|
|
105
114
|
except (json.JSONDecodeError, OSError, KeyError):
|
|
106
115
|
return None
|
|
@@ -132,7 +141,12 @@ def run_auth_flow(
|
|
|
132
141
|
raise FileNotFoundError(msg)
|
|
133
142
|
|
|
134
143
|
effective_scopes = scopes or DEFAULT_SCOPES
|
|
135
|
-
|
|
144
|
+
# Explicitly load the `installed` client config — from_client_secrets_file checks
|
|
145
|
+
# for `web` before `installed`, so a file with both sections always picks web.
|
|
146
|
+
# OOB redirect (used by the headless flow) is not allowed for web client types.
|
|
147
|
+
raw = json.loads(secrets_path.read_text())
|
|
148
|
+
installed_config = raw.get("installed") or raw
|
|
149
|
+
flow = InstalledAppFlow.from_client_config({"installed": installed_config}, effective_scopes)
|
|
136
150
|
|
|
137
151
|
use_headless = headless if headless is not None else is_headless()
|
|
138
152
|
credentials = _run_manual_flow(flow) if use_headless else flow.run_local_server(port=0)
|
|
@@ -167,16 +181,20 @@ def get_credentials(scopes: list[str] | None = None) -> Credentials:
|
|
|
167
181
|
"""
|
|
168
182
|
credentials = load_credentials()
|
|
169
183
|
|
|
170
|
-
if credentials and credentials.valid:
|
|
184
|
+
if credentials and credentials.valid and credentials.expiry is not None:
|
|
171
185
|
return credentials
|
|
172
186
|
|
|
173
|
-
if credentials and credentials.
|
|
187
|
+
if credentials and credentials.refresh_token:
|
|
174
188
|
try:
|
|
175
189
|
credentials.refresh(Request())
|
|
176
190
|
save_credentials(credentials, scopes)
|
|
177
191
|
return credentials
|
|
178
|
-
except Exception:
|
|
179
|
-
|
|
192
|
+
except Exception as e:
|
|
193
|
+
if "invalid_grant" in str(e).lower():
|
|
194
|
+
get_credentials_path().unlink(missing_ok=True)
|
|
195
|
+
raise ValueError(
|
|
196
|
+
f"Google credentials are no longer valid ({e}). Re-authenticate via the UI."
|
|
197
|
+
) from e
|
|
180
198
|
|
|
181
199
|
raise ValueError("No valid Google credentials. Run 'lestash google auth' to authenticate.")
|
|
182
200
|
|
|
@@ -193,15 +211,19 @@ def check_auth_status() -> dict:
|
|
|
193
211
|
credentials = load_credentials()
|
|
194
212
|
if credentials:
|
|
195
213
|
result["scopes"] = list(credentials.scopes) if credentials.scopes else []
|
|
196
|
-
|
|
214
|
+
# Treat missing expiry as expired — legacy credentials saved without expiry
|
|
215
|
+
# would otherwise appear valid indefinitely even after revocation.
|
|
216
|
+
if credentials.valid and credentials.expiry is not None:
|
|
197
217
|
result["authenticated"] = True
|
|
198
|
-
elif credentials.
|
|
218
|
+
elif credentials.refresh_token:
|
|
199
219
|
try:
|
|
200
220
|
credentials.refresh(Request())
|
|
201
221
|
save_credentials(credentials)
|
|
202
222
|
result["authenticated"] = True
|
|
203
223
|
except Exception as e:
|
|
204
224
|
result["refresh_error"] = str(e)
|
|
225
|
+
if "invalid_grant" in str(e).lower():
|
|
226
|
+
get_credentials_path().unlink(missing_ok=True)
|
|
205
227
|
|
|
206
228
|
return result
|
|
207
229
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|