1from urllib import request
2from goblet import Goblet, jsonify, Response, goblet_entrypoint
3from goblet.alerts.alert_conditions import (
4 MetricCondition,
5 LogMatchCondition,
6 CustomMetricCondition,
7 UptimeCondition,
8 PubSubDLQCondition
9)
10from goblet.alerts.alerts import BackendAlert, UptimeAlert, PubSubDLQAlert
11from goblet.handlers.routes import CORSConfig
12import asyncio
13import logging
14
15app = Goblet(function_name="goblet-example")
16app.log.setLevel(logging.INFO) # configure goblet logger level
17goblet_entrypoint(app)
18
19from typing import List
20from marshmallow import Schema, fields
21
22# Example http trigger
23@app.http()
24def main_http(request):
25 return jsonify(request.json)
26
27
28# Example http trigger that contains header
29@app.http(headers={"X-Github-Event"})
30def main_header(request):
31 return jsonify(request.json)
32
33
34# Example http triggers that matches header
35@app.http(headers={"X-Github-Event": "issue"})
36def main(request):
37 return jsonify(request.json)
38
39
40# Path param
41@app.route("/home/{test}")
42def home(test):
43 return jsonify(test)
44
45
46# Example query args
47@app.route("/home")
48def query_args():
49 request = app.current_request
50 q = request.args.get("q")
51 return jsonify(q)
52
53
54# POST request
55@app.route("/home", methods=["POST"])
56def post():
57 request = app.current_request
58 return jsonify(request.json)
59
60
61# Typed Path Param
62@app.route("/home/{name}/{id}", methods=["GET"])
63def namer(name: str, id: int):
64 return f"{name}: {id}"
65
66
67class Point(Schema):
68 lat = fields.Int()
69 lng = fields.Int()
70
71
72# Custom schema types
73@app.route("/points")
74def points() -> List[Point]:
75 point = Point().load({"lat": 0, "lng": 0})
76 return [point]
77
78
79# Custom Marshmallow Fields
80from marshmallow_enum import EnumField
81from enum import Enum
82
83
84def enum_to_properties(self, field, **kwargs):
85 """
86 Add an OpenAPI extension for marshmallow_enum.EnumField instances
87 """
88 if isinstance(field, EnumField):
89 return {"type": "string", "enum": [m.name for m in field.enum]}
90 return {}
91
92
93app.handlers["route"].marshmallow_attribute_function = enum_to_properties
94
95
96class StopLight(Enum):
97 green = 1
98 yellow = 2
99 red = 3
100
101
102class TrafficStop(Schema):
103 light_color = EnumField(StopLight)
104
105
106@app.route("/traffic")
107def traffic() -> TrafficStop:
108 return TrafficStop().dump({"light_color": StopLight.green})
109
110
111# Returns follow openapi spec
112# definitions:
113# TrafficStop:
114# type: object
115# properties:
116# light_color:
117# type: string
118# enum:
119# - green
120# - yellow
121# - red
122
123# Enum paramter
124@app.route("/{color}")
125def prim_enum(color: StopLight):
126 return StopLight(color)
127
128# Pydantic Typing
129from pydantic import BaseModel
130
131
132class NestedModel(BaseModel):
133 text: str
134
135
136class PydanticModel(BaseModel):
137 id: int
138 nested: NestedModel
139
140
141# Request Body Typing
142@app.route("/pydantic", request_body=PydanticModel)
143def traffic() -> PydanticModel:
144 return jsonify(PydanticModel().dict)
145
146
147# Custom Backend
148@app.route("/custom_backend", backend="https://www.CLOUDRUN_URL.com/home")
149def custom_backend():
150 return
151
152
153# Method Security
154@app.route("/method_security", security=[{"your_custom_auth_id": []}])
155def method_security():
156 return
157
158
159# Custom responses and request_types
160@app.route(
161 "/custom",
162 request_body={
163 "application/json": {"schema": {"type": "array", "items": {"type": "string"}}}
164 },
165 responses={"400": {"description": "400"}},
166)
167def custom():
168 request = app.current_request
169 assert request.data["string1", "string2"]
170 return
171
172
173# Example response object
174@app.route("/response")
175def response():
176 return Response(
177 {"failed": 400}, headers={"Content-Type": "application/json"}, status_code=400
178 )
179
180
181# Example CORS
182
183@app.route('/custom_cors', cors=CORSConfig(allow_origin='localhost', allow_methods=["GET"], extra_headers={"X-TEST":"X-HEADER-VALUE"}))
184def custom_cors():
185 return jsonify('localhost is allowed with GET method')
186
187# Scheduled job
188@app.schedule("5 * * * *")
189def scheduled_job():
190 return jsonify("success")
191
192
193# Scheduled job with custom headers, method, and body
194@app.schedule(
195 "5 * * * *",
196 httpMethod="POST",
197 headers={"X-Custom": "header"},
198 body="BASE64 ENCODED STRING",
199)
200def scheduled_job():
201 headers = app.current_request.headers
202 body = app.current_request.body
203 method = app.current_request.method
204 return jsonify("success")
205
206
207# Pubsub Subscription
208@app.pubsub_subscription("test")
209def test_subscription(data):
210 app.log.info(data)
211 return
212
213
214# Pubsub topic with matching message attributes
215@app.pubsub_subscription("test", attributes={"key": "value"})
216def pubsub_attributes(data):
217 app.log.info(data)
218 return
219
220
221# create a pubsub subscription instead of pubsub triggered function
222@app.pubsub_subscription("test", use_subscription=True)
223def pubsub_subscription_use_subscription(data):
224 return
225
226
227# create a pubsub subscription instead of pubsub triggered function and add filter
228@app.pubsub_subscription(
229 "test",
230 use_subscription=True,
231 filter='attributes.name = "com" AND -attributes:"iana.org/language_tag"',
232)
233def pubsub_subscription_filter(data):
234 return
235
236# Pubsub Subscription with DLQ and alert
237# Triggered by pubsub topic. Simulates failure to trigger DLQ
238@app.pubsub_subscription(
239 "goblet-created-test-topic",
240 dlq=True,
241 dlq_alerts=[
242 PubSubDLQAlert(
243 "pubsubdlq",
244 conditions=[
245 PubSubDLQCondition(
246 "pubsublq-condition"
247 )
248 ],
249 )
250 ]
251)
252def failed_subscription(data):
253 raise Exception("Simulating failure")
254
255# Create a pubsub topic
256app.pubsub_topic(
257 "test-topic"
258)
259
260# Example Storage trigger on the create/finalize event
261@app.storage("BUCKET_NAME", "finalize")
262def storage(event):
263 app.log.info(event)
264 return
265
266
267# Example before request
268@app.before_request()
269def add_db(request):
270 app.g.db = "db"
271 return request
272
273
274# Example after request
275@app.after_request()
276def add_header(response):
277 response.headers["X-Custom"] = "custom header"
278 return response
279
280
281# Example eventarc pubsub topic
282@app.eventarc(topic="test")
283def pubsub(data):
284 app.log.info("pubsub")
285 return
286
287
288# Example eventarc direct event
289@app.eventarc(
290 event_filters=[
291 {"attribute": "type", "value": "google.cloud.storage.object.v1.finalized"},
292 {"attribute": "bucket", "value": "BUCKET"},
293 ],
294 region="us-east1",
295)
296def bucket(data):
297 app.log.info("bucket_post")
298 return
299
300
301# Example eventarc audit log
302@app.eventarc(
303 event_filters=[
304 {"attribute": "type", "value": "google.cloud.audit.log.v1.written"},
305 {"attribute": "methodName", "value": "storage.objects.get"},
306 {"attribute": "serviceName", "value": "storage.googleapis.com"},
307 ],
308 region="us-central1",
309)
310def bucket_get(data):
311 app.log.info("bucket_get")
312 return
313
314
315# Example Cloudrun Job with schedule
316@app.job("job1", schedule="* * * * *")
317def job1_task1(id):
318 app.log.info(f"job...{id}")
319 return "200"
320
321
322# Example Cloudrun Job with additional task
323@app.job("job1", task_id=1)
324def job1_task2(id):
325 app.log.info(f"different task for job...{id}")
326 return "200"
327
328
329# Example BQ Remote Function
330# Called in BQ with the following sql: SELECT `PROJECT.DATASET.math_example_multiply(x,y,z)` from DATASET.table
331@app.bqremotefunction(dataset_id="DATASET")
332def multiply(x: int, y: int, z: int) -> int:
333 w = x * y * z
334 return w
335
336# Totally contrived example of an async function (a real one would use aiohttp or similar)
337async def async_multiply(x: int, y: int) -> int:
338 w = x * y
339 return w
340
341# Example BQ Remote Function with vectorized function
342# For network-bound BQ Remote Functions, this approach using async will yield significantly better performance
343@app.bqremotefunction(dataset_id="blogs",vectorize_func=True)
344def function_test(x: List[int], y: List[int]) -> List[int]:
345 results = [asyncio.run(async_multiply(elem_x, elem_y)) for elem_x, elem_y in zip(x, y)]
346 return results
347
348
349# Example Redis Instance
350app.redis("redis-test")
351
352# Example VPC Connector
353app.vpcconnector("vpc-conn-test")
354
355# Example Metric Alert for the cloudfunction metric execution_count with a threshold of 10
356metric_alert = BackendAlert(
357 "metric",
358 conditions=[
359 MetricCondition(
360 "test",
361 metric="cloudfunctions.googleapis.com/function/execution_count",
362 value=10
363 )
364 ],
365)
366app.alert(metric_alert)
367
368# Example Metric Alert for the cloudfunction metric execution_times with a custom aggregation
369metric_alert_2 = BackendAlert(
370 "metric",
371 conditions=[
372 MetricCondition(
373 "test",
374 metric="cloudfunctions.googleapis.com/function/execution_times",
375 value=1000,
376 aggregations=[
377 {
378 "alignmentPeriod": "300s",
379 "crossSeriesReducer": "REDUCE_NONE",
380 "perSeriesAligner": "ALIGN_PERCENTILE_50",
381 }
382 ],
383 )
384 ],
385)
386app.alert(metric_alert_2)
387
388# Example Log Match metric that will trigger an incendent off of any Error logs
389log_alert = BackendAlert(
390 "error",
391 conditions=[LogMatchCondition("error", "severity>=ERROR")],
392)
393app.alert(log_alert)
394
395# Example Metric Alert that creates a custom metric for severe errors with http code in the 500's and creates an alert with a threshold of 10
396custom_alert = BackendAlert(
397 "custom",
398 conditions=[
399 CustomMetricCondition(
400 "custom",
401 metric_filter="severity=(ERROR OR CRITICAL OR ALERT OR EMERGENCY) httpRequest.status=(500 OR 501 OR 502 OR 503 OR 504)",
402 value=10,
403 )
404 ],
405)
406app.alert(custom_alert)
407
408# Example CloudTask Queue + CloudTask HTTP Target
409client = app.cloudtaskqueue("queue", config={
410 "rateLimits": {
411 "maxDispatchesPerSecond": 500,
412 "maxBurstSize": 100,
413 "maxConcurrentDispatches": 1000
414 },
415 "retryConfig": {
416 "maxAttempts": 10,
417 "minBackoff": "0.100s",
418 "maxBackoff": "3600s",
419 "maxDoublings": 16
420 }
421})
422
423# Cloudtask HTTP Target
424@app.cloudtasktarget(name="target")
425def my_target_handler(request):
426 ''' handle request '''
427 return {}
428
429# Enqueue a message using the CloudTask Queue client
430@app.route("/enqueue", methods=["GET"])
431def enqueue():
432 payload = {"message": {"title": "enqueue"}}
433 client.enqueue(target="target", payload=payload)
434 return {}
435
436# Example of handling the GobletRouteNotFoundError with a custom response
437@app.errorhandler("GobletRouteNotFoundError")
438def handle_missing_route(error):
439 return Response("Custom Error", status_code=404)
440
441# Example of handling ValueError.
442@app.errorhandler("ValueError")
443def return_error_string(error):
444 return Response(str(error), status_code=200)
445
446# Example uptime check
447@app.uptime(timeout="30s")
448def uptime_check():
449 return "success"
450
451# Example uptime check with alert
452@app.uptime(timeout="30s",alerts=[UptimeAlert("uptime", conditions=[UptimeCondition("uptime")])])
453def uptime_check_with_alert():
454 app.log.info("success")
455 return "success"