{"id":20768202,"url":"https://github.com/ayvi-0001/example-py-gcal-api-cloud-function","last_synced_at":"2025-03-11T19:28:45.409Z","repository":{"id":229968847,"uuid":"626223800","full_name":"ayvi-0001/example-py-gcal-api-cloud-function","owner":"ayvi-0001","description":"Authorization for accessing the Google Calendar API in a Cloud Function.","archived":false,"fork":false,"pushed_at":"2024-03-27T05:13:37.000Z","size":116,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-01-18T06:42:45.613Z","etag":null,"topics":["cloud-functions","google-calendar-api","google-cloud-platform","python311"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ayvi-0001.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-04-11T03:27:27.000Z","updated_at":"2023-04-11T03:59:49.000Z","dependencies_parsed_at":null,"dependency_job_id":"85b21b6a-3250-4851-bdc4-6c00db6efeac","html_url":"https://github.com/ayvi-0001/example-py-gcal-api-cloud-function","commit_stats":null,"previous_names":["ayvi-0001/example-py-gcal-api-cloud-function"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ayvi-0001%2Fexample-py-gcal-api-cloud-function","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ayvi-0001%2Fexample-py-gcal-api-cloud-function/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ayvi-0001%2Fexample-py-gcal-api-cloud-function/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ayvi-0001%2Fexample-py-gcal-api-cloud-function/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ayvi-0001","download_url":"https://codeload.github.com/ayvi-0001/example-py-gcal-api-cloud-function/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243098050,"owners_count":20235947,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cloud-functions","google-calendar-api","google-cloud-platform","python311"],"created_at":"2024-11-17T11:36:22.866Z","updated_at":"2025-03-11T19:28:45.379Z","avatar_url":"https://github.com/ayvi-0001.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"I couldn't find many resources on authorizating the google calendar API from inside a cloud function written in python. I've put this example together to use as a template.\n\n# Google Calendar API in Cloud Functions.\n\n## Setup\n\n```sh\n# Clone the repo.\ngit clone https://github.com/ayvi-0001/example-py-gcal-api-cloud-function\ncd example-py-gcal-api-cloud-function\n\n# Create a virtualenv\nvirtualenv env\nsource env/Scripts/activate\n\n# Install requirements\npip install -r requirements.txt\n```\n\n---\n\n## Service Account Authorization\n\n\u003e [!IMPORTANT]  \n\u003e The Google Calendar API _must_ use OAuth 2.0 to authorize requests and does not support any other authorization protocols.\n\nSince the function can't authorize the app through a consent screen, we need to share our calendar with a service account email, and use that service account for credentials.\n\nIn GCP, go to `APIs \u0026 Services` \u003e `Credentials`.  \nYou can either use the App Engine default service account or create a custom service account with the necessary Cloud Functions permissions.  \nCreate/download a key for the service account, and save it in the working directory as `credentials.json`, or store it in secrets manager and pull the secret value directly from there with `$ gcloud secrets versions access latest`.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/apis-credentials.png\" hspace=\"5\"\u003e \n  \u003cimg src=\"docs/apis-keys.png\" hspace=\"5\"\u003e \n\u003c/p\u003e\n\n## Google Calendar Share/Settings\n\n1. Go to the settings menu in your google calendar (gear icon top right corner)\n2. Click `Settings for my calendar`.\n3. Select the calendar you want to share.\n4. In Calendar settings, under `Share with specific people or groups`, click `+ Add people and groups`\n5. Add your service account email with the permissions \"See all event details\" at a minimum\n6. Click `Send`.\n\n\u003cp align=\"center\"\u003e \u003cimg src=\"docs/gcal-share.png\"\u003e \u003cbr\u003e \u003c/p\u003e\n\n## Configure\n\nTo access your events from the service account, the `calendarID` parameter in `service.events().list()` needs to be set to your primary email used in google calendar. This is normally set to \"primary\".\n\nIn the `.env.yaml` file, set the following environment variables:\n\n```yaml\nPRIMARY_CALENDAR_EMAIL: # your primary email used for google calendar.\nTZ: # your timezone, e.g. America/Vancouver\n```\n\n## Local Tests\n\nTo test locally before deploying your cloud function, you can use the `main.py` file in `local-test`.\nThe object that will get passed to your entry point when it's running is a flask `Request`, which has the property `data` in bytes. The file in `local-test` is altered slightly to pass a dictionary.\n\nRewrite code to your application. The default behaviour for the example function will print all events from today. Increase the amount by passing a payload with keys `\"timedelta\"` and `\"days\"` | `\"weeks\"`.\n\n## Deploy\n\nUpdate the main script in the source directory (the `local-test` dir is included in `.gcloudignore` and won't upload to your function).  \n[Update the arguments to `gcloud functions deploy`](https://cloud.google.com/sdk/gcloud/reference/functions/deploy) in `deploy.sh` (name, region, memory, etc..) and run. If you haven't already installed the gcloud CLI, view [installation instructions here](https://cloud.google.com/sdk/docs/install), or deploy your function [via the UI](https://console.cloud.google.com/functions/list).\n\nTest your function.\n\n_Example call - rewrite to your project name/region_:\n\n```sh\n# Replace region and project id variables.\ncurl -m 550 -v \\\n    https://${REGION}-${PROJECTID}.cloudfunctions.net/example-cf-gcal-api \\\n    -H \"Content-Type: application/json\" \\\n    -d \"{\\\"timedelta\\\":{\\\"weeks\\\":2}}\"\n```\n\n---\n\n## Code\n\n```py\nimport json\nimport os\nfrom datetime import datetime, timedelta\n\nimport functions_framework\nimport pytz\nfrom flask import Request\nfrom google.oauth2 import service_account\nfrom googleapiclient.discovery import build\nfrom googleapiclient.errors import HttpError\n\n\n@functions_framework.http\ndef main(request: Request) -\u003e str:\n    data = json.loads(request.data)\n\n    credentials = service_account.Credentials.from_service_account_file(\n        \"credentials.json\",\n        scopes=[\"https://www.googleapis.com/auth/calendar.readonly\"],\n    )\n\n    try:\n        service = build(\n            \"calendar\", \"v3\", credentials=credentials, cache_discovery=False\n        )\n\n        tz_info = pytz.timezone(os.environ[\"TZ\"])\n        today = datetime.today().astimezone(tz_info)\n\n        timeMin = datetime.combine(today, datetime.min.time())\n        timeMax = datetime.combine(today, datetime.max.time())\n\n        if data and \"timedelta\" in data:\n            match data[\"timedelta\"]:\n                case {\"days\": float}:\n                    timeMax += timedelta(days=data[\"timedelta\"][\"days\"])\n                case {\"weeks\": float}:\n                    timeMax += timedelta(weeks=data[\"timedelta\"][\"weeks\"])\n\n        events_list = (\n            service.events()\n            .list(\n                calendarId=os.environ[\"PRIMARY_CALENDAR_EMAIL\"],\n                timeMin=timeMin.isoformat(),\n                timeMax=timeMax.isoformat(),\n                singleEvents=True,\n                orderBy=\"startTime\",\n                # additional available fields:\n                # maxResults=2500,\n                # ^ The page size can never be larger than 2500 events. Optional.\n                # q=\"string search terms\",\n                # ^ Free text search terms to find events that match these terms in the following fields:\n                #   summary, description, location, attendee's displayName, attendee's email. Optional.\n            )\n            .execute()\n        )\n\n        events = events_list.get(\"items\", [])\n\n        if not events:\n            return \"No upcoming events found.\"\n\n        for event in events:\n            start = datetime.fromisoformat(event[\"start\"][\"dateTime\"])\n            end = datetime.fromisoformat(event[\"end\"][\"dateTime\"])\n            title = event[\"summary\"]\n\n            # do something with events...\n            print(\"%s - %s: %s\" % (start, end, title))\n\n        return \"Done.\"\n\n    except HttpError as e:\n        return \"An error occurred: %s\" % e\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fayvi-0001%2Fexample-py-gcal-api-cloud-function","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fayvi-0001%2Fexample-py-gcal-api-cloud-function","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fayvi-0001%2Fexample-py-gcal-api-cloud-function/lists"}