Skip to content

Commit

Permalink
Implement first endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
rjambrecic committed Jun 18, 2024
1 parent 65c0352 commit 57d2dba
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@ venv*
htmlcov
token
.DS_Store

client_secret.json
83 changes: 81 additions & 2 deletions google_sheets/app.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import datetime
import json
import logging
from os import environ
from typing import Annotated, List
from pathlib import Path
from typing import Annotated, Any, List, Union

import python_weather
from fastapi import FastAPI, Query
from fastapi import FastAPI, HTTPException, Query
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build
from pydantic import BaseModel

from . import __version__
from .db_helpers import get_db_connection, get_wasp_db_url

__all__ = ["app"]

Expand All @@ -26,6 +31,19 @@
title="google-sheets",
)

# Load client secret data from the JSON file
with Path("client_secret.json").open() as secret_file:
client_secret_data = json.load(secret_file)

# OAuth2 configuration
oauth2_settings = {
"auth_uri": client_secret_data["web"]["auth_uri"],
"tokenUrl": client_secret_data["web"]["token_uri"],
"clientId": client_secret_data["web"]["client_id"],
"clientSecret": client_secret_data["web"]["client_secret"],
"redirectUri": client_secret_data["web"]["redirect_uris"][0],
}


class HourlyForecast(BaseModel):
forecast_time: datetime.time
Expand Down Expand Up @@ -79,3 +97,64 @@ async def get_weather(
hourly_forecasts=hourly_forecasts,
)
return weather_response


async def get_user(user_id: Union[int, str]) -> Any:
wasp_db_url = await get_wasp_db_url()
async with get_db_connection(db_url=wasp_db_url) as db:
user = await db.query_first(
f'SELECT * from "User" where id={user_id}' # nosec: [B608]
)
if not user:
raise HTTPException(status_code=404, detail=f"user_id {user_id} not found")
return user


async def load_user_credentials(user_id: Union[int, str]) -> Any:
await get_user(user_id=user_id)
async with get_db_connection() as db:
data = await db.gauth.find_unique_or_raise(where={"user_id": user_id})

return data.creds


def _get_sheet(user_credentials: Any, spreadshit_id: str, range: str) -> Any:
sheets_credentials = {
"refresh_token": user_credentials["refresh_token"],
"client_id": oauth2_settings["clientId"],
"client_secret": oauth2_settings["clientSecret"],
}

creds = Credentials.from_authorized_user_info(
info=sheets_credentials, scopes=["https://www.googleapis.com/auth/spreadsheets"]
)
service = build("sheets", "v4", credentials=creds)

# Call the Sheets API
sheet = service.spreadsheets()
result = sheet.values().get(spreadsheetId=spreadshit_id, range=range).execute()
values = result.get("values", [])

return values


@app.get("/sheet", description="Get data from a Google Sheet")
async def get_sheet(
user_id: Annotated[
int, Query(description="The user ID for which the data is requested")
],
spreadshit_id: Annotated[
str, Query(description="ID of the Google Sheet to fetch data from")
],
range: Annotated[
str,
Query(description="The range of cells to fetch data from. E.g. 'Sheet1!A1:B2'"),
],
) -> Union[str, List[List[str]]]:
user_credentials = await load_user_credentials(user_id)
values = _get_sheet(user_credentials, spreadshit_id, range)

if not values:
return "No data found."

return values # type: ignore[no-any-return]
34 changes: 34 additions & 0 deletions google_sheets/db_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from contextlib import asynccontextmanager
from os import environ
from typing import AsyncGenerator, Optional

from prisma import Prisma # type: ignore[attr-defined]


@asynccontextmanager
async def get_db_connection(
db_url: Optional[str] = None,
) -> AsyncGenerator[Prisma, None]:
if not db_url:
db_url = environ.get("DATABASE_URL", None)
if not db_url:
raise ValueError(
"No database URL provided nor set as environment variable 'DATABASE_URL'"
) # pragma: no cover
if "connect_timeout" not in db_url:
db_url += "?connect_timeout=60"
db = Prisma(datasource={"url": db_url})
await db.connect()
try:
yield db
finally:
await db.disconnect()


async def get_wasp_db_url() -> str:
curr_db_url = environ.get("DATABASE_URL")
wasp_db_name = environ.get("WASP_DB_NAME", "waspdb")
wasp_db_url = curr_db_url.replace(curr_db_url.split("/")[-1], wasp_db_name) # type: ignore[union-attr]
if "connect_timeout" not in wasp_db_url:
wasp_db_url += "?connect_timeout=60"
return wasp_db_url
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ dependencies = [
"pydantic>=2.3,<3",
"fastapi>=0.110.2",
"python-weather==2.0.3",
"prisma==0.13.1",
"google-api-python-client==2.133.0",
]

[project.optional-dependencies]
Expand Down
20 changes: 20 additions & 0 deletions schema.prisma
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
datasource db {
// could be postgresql or mysql
provider = "postgresql"
url = env("DATABASE_URL")
}

generator db {
provider = "prisma-client-py"
interface = "asyncio"
recursive_type_depth = 5
}

model GAuth {
id String @id @default(cuid())
created_at DateTime @default(now())
updated_at DateTime @updatedAt
user_id Int @unique
creds Json
info Json
}

0 comments on commit 57d2dba

Please sign in to comment.