From beae5ab553a470ced0927637ef28be03ffe275a5 Mon Sep 17 00:00:00 2001 From: pransh62390 <63577123+pransh62390@users.noreply.github.com> Date: Sat, 11 Jan 2025 15:41:36 +0530 Subject: [PATCH] api: Exclude None values from request payload in Endpoint class. Refactored the Endpoint class to exclude parameters with None values from the request payload. This ensures cleaner API interactions and avoids unintended behavior, improving code reliability and aligning with best practices. Fixes https://github.com/zulip/python-zulip-api/issues/847 --- pyproject.toml | 3 + requirements.txt | 1 + .../google/get-google-credentials | 56 +++++++++++++------ zulip/integrations/google/google-calendar | 52 ++++++++--------- zulip/integrations/google/requirements.txt | 5 +- 5 files changed, 73 insertions(+), 44 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 159c85e23..5261b00f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,6 +63,9 @@ module = [ "feedparser.*", "gitlint.*", "googleapiclient.*", + "google_api_python_client.*", + "google_auth_httplib2.*", + "google_auth_oauthlib.*", "irc.*", "mercurial.*", "nio.*", diff --git a/requirements.txt b/requirements.txt index c5d735436..e0e805d3d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,3 +17,4 @@ types-pytz types-requests gitlint>=0.13.0 -r ./zulip/integrations/bridge_with_matrix/requirements.txt +-r ./zulip/integrations/google/requirements.txt diff --git a/zulip/integrations/google/get-google-credentials b/zulip/integrations/google/get-google-credentials index bb97e5f69..be52758ba 100755 --- a/zulip/integrations/google/get-google-credentials +++ b/zulip/integrations/google/get-google-credentials @@ -2,24 +2,32 @@ import argparse import os -from oauth2client import client, tools -from oauth2client.file import Storage +from google.auth.transport.requests import Request +from google.oauth2.credentials import Credentials +from google_auth_oauthlib.flow import InstalledAppFlow -flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args() +flags = argparse.ArgumentParser(description="Google Calendar Bot") +flags.add_argument( + "--noauth_local_webserver", + action="store_true", + help="Run OAuth flow in console instead of opening a web browser.", +) +args = flags.parse_args() # If modifying these scopes, delete your previously saved credentials # at zulip/bots/gcal/ # NOTE: When adding more scopes, add them after the previous one in the same field, with a space # seperating them. -SCOPES = "https://www.googleapis.com/auth/calendar.readonly" +SCOPES = ["https://www.googleapis.com/auth/calendar.readonly"] # This file contains the information that google uses to figure out which application is requesting # this client's data. CLIENT_SECRET_FILE = "client_secret.json" # noqa: S105 APPLICATION_NAME = "Zulip Calendar Bot" HOME_DIR = os.path.expanduser("~") +CREDENTIALS_PATH = os.path.join(HOME_DIR, "google-credentials.json") -def get_credentials() -> client.Credentials: +def get_credentials() -> Credentials: """Gets valid user credentials from storage. If nothing has been stored, or if the stored credentials are invalid, @@ -29,18 +37,32 @@ def get_credentials() -> client.Credentials: Credentials, the obtained credential. """ - credential_path = os.path.join(HOME_DIR, "google-credentials.json") - - store = Storage(credential_path) - credentials = store.get() - if not credentials or credentials.invalid: - flow = client.flow_from_clientsecrets(os.path.join(HOME_DIR, CLIENT_SECRET_FILE), SCOPES) - flow.user_agent = APPLICATION_NAME - # This attempts to open an authorization page in the default web browser, and asks the user - # to grant the bot access to their data. If the user grants permission, the run_flow() - # function returns new credentials. - credentials = tools.run_flow(flow, store, flags) - print("Storing credentials to " + credential_path) + creds = None + + # Check if the credentials file exists + if os.path.exists(CREDENTIALS_PATH): + creds = Credentials.from_authorized_user_file(CREDENTIALS_PATH, SCOPES) + + # If there are no valid credentials, initiate the OAuth flow + if not creds or not creds.valid: + if creds and creds.expired and creds.refresh_token: + creds.refresh(Request()) + else: + flow = InstalledAppFlow.from_client_secrets_file( + os.path.join(HOME_DIR, CLIENT_SECRET_FILE), SCOPES + ) + if args.noauth_local_webserver: + creds = flow.run_console() + else: + creds = flow.run_local_server(port=0) + + # Save the credentials for future use + with open(CREDENTIALS_PATH, "w") as token_file: + token_file.write(creds.to_json()) + + print("Storing credentials to " + CREDENTIALS_PATH) + + return creds get_credentials() diff --git a/zulip/integrations/google/google-calendar b/zulip/integrations/google/google-calendar index 85906bd46..d56641068 100755 --- a/zulip/integrations/google/google-calendar +++ b/zulip/integrations/google/google-calendar @@ -12,21 +12,16 @@ import time from typing import List, Optional, Set, Tuple import dateutil.parser -import httplib2 import pytz -from oauth2client import client -from oauth2client.file import Storage - -try: - from googleapiclient import discovery -except ImportError: - logging.exception("Install google-api-python-client") - sys.exit(1) +from google.auth.transport.requests import Request +from google.oauth2.credentials import Credentials +from google_auth_oauthlib.flow import InstalledAppFlow +from googleapiclient.discovery import build sys.path.append(os.path.join(os.path.dirname(__file__), "../../")) import zulip -SCOPES = "https://www.googleapis.com/auth/calendar.readonly" +SCOPES = ["https://www.googleapis.com/auth/calendar.readonly"] CLIENT_SECRET_FILE = "client_secret.json" # noqa: S105 APPLICATION_NAME = "Zulip" HOME_DIR = os.path.expanduser("~") @@ -88,33 +83,40 @@ if not options.zulip_email: zulip_client = zulip.init_from_options(options) -def get_credentials() -> client.Credentials: +def get_credentials() -> Credentials: """Gets valid user credentials from storage. If nothing has been stored, or if the stored credentials are invalid, - an exception is thrown and the user is informed to run the script in this directory to get - credentials. + the user will be prompted to authenticate. Returns: Credentials, the obtained credential. """ - try: - credential_path = os.path.join(HOME_DIR, "google-credentials.json") + credential_path = os.path.join(HOME_DIR, "google-credentials.json") + creds = None + + # Load credentials from file if they exist + if os.path.exists(credential_path): + creds = Credentials.from_authorized_user_file(credential_path, SCOPES) + + # If there are no (valid) credentials available, prompt the user to log in. + if not creds or not creds.valid: + if creds and creds.expired and creds.refresh_token: + creds.refresh(Request()) + else: + flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRET_FILE, SCOPES) + creds = flow.run_local_server(port=0) + + # Save the credentials for the next run + with open(credential_path, "w") as token: + token.write(creds.to_json()) - store = Storage(credential_path) - return store.get() - except client.Error: - logging.exception("Error while trying to open the `google-credentials.json` file.") - sys.exit(1) - except OSError: - logging.error("Run the get-google-credentials script from this directory first.") - sys.exit(1) + return creds def populate_events() -> Optional[None]: credentials = get_credentials() - creds = credentials.authorize(httplib2.Http()) - service = discovery.build("calendar", "v3", http=creds) + service = build("calendar", "v3", credentials=credentials) now = datetime.datetime.now(pytz.utc).isoformat() feed = ( diff --git a/zulip/integrations/google/requirements.txt b/zulip/integrations/google/requirements.txt index 139c0705b..018523c01 100644 --- a/zulip/integrations/google/requirements.txt +++ b/zulip/integrations/google/requirements.txt @@ -1,2 +1,3 @@ -httplib2>=0.22.0 -oauth2client>=4.1.3 +google-api-python-client>=2.157.0 +google-auth-httplib2>=0.2.0 +google-auth-oauthlib>=1.2.1