interloper-api 0.64.1__tar.gz → 0.65.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.
Files changed (21) hide show
  1. {interloper_api-0.64.1 → interloper_api-0.65.0}/PKG-INFO +1 -1
  2. {interloper_api-0.64.1 → interloper_api-0.65.0}/pyproject.toml +1 -1
  3. {interloper_api-0.64.1 → interloper_api-0.65.0}/pyproject.toml.orig +1 -1
  4. {interloper_api-0.64.1 → interloper_api-0.65.0}/src/interloper_api/routes/auth.py +42 -1
  5. {interloper_api-0.64.1 → interloper_api-0.65.0}/README.md +0 -0
  6. {interloper_api-0.64.1 → interloper_api-0.65.0}/src/interloper_api/__init__.py +0 -0
  7. {interloper_api-0.64.1 → interloper_api-0.65.0}/src/interloper_api/app.py +0 -0
  8. {interloper_api-0.64.1 → interloper_api-0.65.0}/src/interloper_api/components.py +0 -0
  9. {interloper_api-0.64.1 → interloper_api-0.65.0}/src/interloper_api/dependencies.py +0 -0
  10. {interloper_api-0.64.1 → interloper_api-0.65.0}/src/interloper_api/email.py +0 -0
  11. {interloper_api-0.64.1 → interloper_api-0.65.0}/src/interloper_api/routes/__init__.py +0 -0
  12. {interloper_api-0.64.1 → interloper_api-0.65.0}/src/interloper_api/routes/admin.py +0 -0
  13. {interloper_api-0.64.1 → interloper_api-0.65.0}/src/interloper_api/routes/agent.py +0 -0
  14. {interloper_api-0.64.1 → interloper_api-0.65.0}/src/interloper_api/routes/backfills.py +0 -0
  15. {interloper_api-0.64.1 → interloper_api-0.65.0}/src/interloper_api/routes/catalog.py +0 -0
  16. {interloper_api-0.64.1 → interloper_api-0.65.0}/src/interloper_api/routes/components.py +0 -0
  17. {interloper_api-0.64.1 → interloper_api-0.65.0}/src/interloper_api/routes/oauth.py +0 -0
  18. {interloper_api-0.64.1 → interloper_api-0.65.0}/src/interloper_api/routes/organisations.py +0 -0
  19. {interloper_api-0.64.1 → interloper_api-0.65.0}/src/interloper_api/routes/runs.py +0 -0
  20. {interloper_api-0.64.1 → interloper_api-0.65.0}/src/interloper_api/routes/tokens.py +0 -0
  21. {interloper_api-0.64.1 → interloper_api-0.65.0}/src/interloper_api/routes/ws.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: interloper-api
3
- Version: 0.64.1
3
+ Version: 0.65.0
4
4
  Summary: Interloper FastAPI routes
5
5
  Author: Guillaume Onfroy
6
6
  Author-email: Guillaume Onfroy <guillaume@digitlcloud.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "interloper-api"
3
- version = "0.64.1"
3
+ version = "0.65.0"
4
4
  description = "Interloper FastAPI routes"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -3,7 +3,7 @@
3
3
  # ###############
4
4
  [project]
5
5
  name = "interloper-api"
6
- version = "0.64.1"
6
+ version = "0.65.0"
7
7
  description = "Interloper FastAPI routes"
8
8
  readme = "README.md"
9
9
  authors = [{ name = "Guillaume Onfroy", email = "guillaume@digitlcloud.com" }]
@@ -6,12 +6,13 @@ from datetime import datetime
6
6
  from typing import Any
7
7
  from urllib.parse import urlencode
8
8
  from uuid import UUID
9
+ from zoneinfo import ZoneInfo
9
10
 
10
11
  import httpx
11
12
  from fastapi import APIRouter, Cookie, Depends, HTTPException, Response
12
13
  from fastapi.responses import RedirectResponse
13
14
  from interloper_db import Organisation, Profile, Store
14
- from pydantic import BaseModel
15
+ from pydantic import BaseModel, Field
15
16
 
16
17
  from interloper_api.dependencies import get_auth_config, get_current_user, get_features, get_store
17
18
 
@@ -56,6 +57,7 @@ class AuthUserResponse(BaseModel):
56
57
  email: str
57
58
  name: str | None = None
58
59
  avatar_url: str | None = None
60
+ timezone: str | None = None
59
61
  role: str
60
62
  is_super_admin: bool = False
61
63
  organisation: OrganisationResponse | None = None
@@ -219,6 +221,7 @@ def get_me(
219
221
  email=profile.email,
220
222
  name=profile.name,
221
223
  avatar_url=profile.avatar_url,
224
+ timezone=profile.timezone,
222
225
  role=role,
223
226
  is_super_admin=profile.is_super_admin,
224
227
  organisation=OrganisationResponse.model_validate(org, from_attributes=True) if org else None,
@@ -227,6 +230,44 @@ def get_me(
227
230
  )
228
231
 
229
232
 
233
+ class UpdateMeRequest(BaseModel):
234
+ """User-editable profile fields; omitted fields stay untouched.
235
+
236
+ Email is absent by design: it is Google-managed and refreshed at every
237
+ login, so an edit here would be silently reverted.
238
+ """
239
+
240
+ name: str | None = Field(default=None, min_length=1, max_length=200)
241
+ timezone: str | None = None
242
+
243
+
244
+ class ProfileResponse(BaseModel):
245
+ """The caller's own profile, as returned by profile updates."""
246
+
247
+ id: UUID
248
+ email: str
249
+ name: str | None = None
250
+ avatar_url: str | None = None
251
+ timezone: str | None = None
252
+
253
+
254
+ @router.patch("/me")
255
+ def update_me(
256
+ body: UpdateMeRequest,
257
+ user: Profile = Depends(get_current_user),
258
+ store: Store = Depends(get_store),
259
+ ) -> ProfileResponse:
260
+ """Update the current user's profile (display name, timezone)."""
261
+ if body.timezone is not None:
262
+ try:
263
+ ZoneInfo(body.timezone)
264
+ except (KeyError, ValueError):
265
+ raise HTTPException(status_code=422, detail=f"Unknown timezone {body.timezone!r}")
266
+
267
+ profile = store.update_profile(user.id, name=body.name, timezone=body.timezone)
268
+ return ProfileResponse.model_validate(profile, from_attributes=True)
269
+
270
+
230
271
  class SwitchOrgRequest(BaseModel):
231
272
  """Request body for switching the active organisation."""
232
273