Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
[flake8]
max-line-length = 88
ignore = E501, W503, E266, E203
ignore = E501, W503, E266, E203, C901
#E501 line too long
#W503 line break before binary operator
#C901 __call__ to complex
; exclude = .git,__pycache__,docs
max-complexity = 15
15 changes: 15 additions & 0 deletions docs/source/topics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -939,3 +939,18 @@ Multi Container Deployments
Additional containers are now supported in Cloudrun. See `Google annoucement <https://cloud.google.com/blog/products/serverless/cloud-run-now-supports-multi-container-deployments>`__.
You can specify additional containers by using the `cloudrun_container_extra` section in `config.json`. Note you will also need to set the `launchStage` field in `cloudrun` to either `BETA`
or `ALPHA`. Checkout the examples section for more details.

Error Handling
^^^^^^^^^^^^^^

Use the `errorhandler` decorator to handle errors based on its exception class. By default `GobletRouteNotFoundError` is handled by returning a 404 response code.

..code:: python

@app.errorhandler("GobletRouteNotFoundError")
def handle_missing_route(error):
return Response("Custom Error", status_code=404)

@app.errorhandler("ValueError")
def return_error_string(error):
return Response(str(error), status_code=200)
12 changes: 11 additions & 1 deletion examples/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,15 +376,25 @@ def function_test(x: List[int], y: List[int]) -> List[int]:
}
})

# Cloudtask HTTP Target
@app.cloudtasktarget(name="target")
def my_target_handler(request):
''' handle request '''
return {}


# Enqueue a message using the CloudTask Queue client
@app.route("/enqueue", methods=["GET"])
def enqueue():
payload = {"message": {"title": "enqueue"}}
client.enqueue(target="target", payload=payload)
return {}

# Example of handling the GobletRouteNotFoundError with a custom response
@app.errorhandler("GobletRouteNotFoundError")
def handle_missing_route(error):
return Response("Custom Error", status_code=404)

# Example of handling ValueError.
@app.errorhandler("ValueError")
def return_error_string(error):
return Response(str(error), status_code=200)
7 changes: 7 additions & 0 deletions goblet/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,13 @@ def vpcconnector(self, name, **kwargs):
kwargs={"name": name, "kwargs": kwargs},
)

def errorhandler(self, error):
def _register_error_handler(error_handler):
self.error_handlers[error] = error_handler
return error_handler

return _register_error_handler

def stage(self, stage=None, stages=[]):
if not stage and not stages:
raise ValueError("One of stage or stages should be set")
Expand Down
4 changes: 4 additions & 0 deletions goblet/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ class GobletError(Exception):

class GobletValidationError(Exception):
pass


class GobletRouteNotFoundError(Exception):
pass
4 changes: 2 additions & 2 deletions goblet/handlers/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from goblet.utils import get_g_dir
from goblet.common_cloud_actions import deploy_apigateway, destroy_apigateway
from goblet.permissions import gcp_generic_resource_permissions

from goblet.errors import GobletRouteNotFoundError

log = logging.getLogger("goblet.deployer")
log.setLevel(logging.getLevelName(os.getenv("GOBLET_LOG_LEVEL", "INFO")))
Expand Down Expand Up @@ -98,7 +98,7 @@ def __call__(self, request, context=None):
if "{" in p and self._matched_path(p, path):
entry = self.resources.get(p, {}).get(method)
if not entry:
raise ValueError(f"No route found for {path} with {method}")
raise GobletRouteNotFoundError(f"No route found for {path} with {method}")
return entry(request)

@staticmethod
Expand Down
77 changes: 47 additions & 30 deletions goblet/resource_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
from goblet.infrastructures.cloudtask import CloudTaskQueue
from goblet.infrastructures.pubsub import PubSubTopic

from goblet.response import default_missing_route

import goblet.globals as g

from goblet.common_cloud_actions import deploy_custom_role, deploy_service_account
Expand Down Expand Up @@ -113,6 +115,9 @@ def __init__(
"before": {},
"after": {},
}

self.error_handlers = {"GobletRouteNotFoundError": default_missing_route}

self.current_request = None
self.function_name = function_name

Expand All @@ -127,36 +132,48 @@ def __call__(self, request, context=None):
added_app.request_context = context

event_type = self.get_event_type(request, context)
# call before request middleware
request = self._call_middleware(request, event_type, before_or_after="before")
response = None
if event_type not in EVENT_TYPES:
raise ValueError(f"{event_type} not a valid event type")
if event_type == "job":
response = self.handlers["jobs"](request, context)
if event_type == "schedule":
response = self.handlers["schedule"](request)
if event_type == "pubsub":
response = self.handlers["pubsub"](request, context)
if event_type == "storage":
# Storage trigger can be made with @eventarc decorator
try:
response = self.handlers["storage"](request, context)
except ValueError:
event_type = "eventarc"
if event_type == "route":
response = self.handlers["route"](request)
if event_type == "http":
response = self.handlers["http"](request)
if event_type == "eventarc":
response = self.handlers["eventarc"](request)
if event_type == "bqremotefunction":
response = self.handlers["bqremotefunction"](request)
if event_type == "cloudtasktarget":
response = self.handlers["cloudtasktarget"](request)

# call after request middleware
response = self._call_middleware(response, event_type, before_or_after="after")

try:
# call before request middleware
request = self._call_middleware(
request, event_type, before_or_after="before"
)
response = None
if event_type not in EVENT_TYPES:
raise ValueError(f"{event_type} not a valid event type")
if event_type == "job":
response = self.handlers["jobs"](request, context)
if event_type == "schedule":
response = self.handlers["schedule"](request)
if event_type == "pubsub":
response = self.handlers["pubsub"](request, context)
if event_type == "storage":
# Storage trigger can be made with @eventarc decorator
try:
response = self.handlers["storage"](request, context)
except ValueError:
event_type = "eventarc"
if event_type == "route":
response = self.handlers["route"](request)
if event_type == "http":
response = self.handlers["http"](request)
if event_type == "eventarc":
response = self.handlers["eventarc"](request)
if event_type == "bqremotefunction":
response = self.handlers["bqremotefunction"](request)
if event_type == "cloudtasktarget":
response = self.handlers["cloudtasktarget"](request)

# call after request middleware
response = self._call_middleware(
response, event_type, before_or_after="after"
)

except Exception as e:
if self.error_handlers.get(e.__class__.__name__):
return self.error_handlers[e.__class__.__name__](e)
raise e

return response

def __add__(self, other):
Expand Down
6 changes: 5 additions & 1 deletion goblet/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class Response(object):
Generic Response class based on Flask Response
"""

def __init__(self, body, headers=None, status_code=200):
def __init__(self, body: str, headers=None, status_code=200):
self.body = body
if headers is None:
headers = {"Content-type": "text/plain"}
Expand All @@ -21,3 +21,7 @@ def __call__(self, environ, start_response):
headers = [(k, v) for k, v in self.headers.items()]
start_response(status, headers)
return [body]


def default_missing_route(error):
return Response(str(error), status_code=404)
33 changes: 27 additions & 6 deletions goblet/tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,11 +226,32 @@ def dummy_function3():

assert len(app.handlers["route"].resources) == 2

def test_errorhandler_default(self):
app = Goblet("test")

# Causes tests to fail
# class TestGoblet:
mock_request = Mock()
mock_request.path = "/test"
mock_request.method = "GET"
mock_request.headers = {}
mock_request.json = {}

assert app(mock_request, {}).status_code == 404

def test_errorhandler_custom(self):
app = Goblet("test")

mock_request = Mock()
mock_request.path = "/error"
mock_request.method = "GET"
mock_request.headers = {}
mock_request.json = {}

@app.errorhandler("ValueError")
def handle_valueError(error):
return str(error)

@app.route("/error")
def dummy_function2():
raise ValueError("test_error")

# def test_client_versions(self):
# app = Goblet(client_versions={"cloudfunctions":"v2"})
# assert app.client_versions["cloudfunctions"] == "v2"
# assert app.client_versions["pubsub"] == DEFAULT_CLIENT_VERSIONS["pubsub"]
assert app(mock_request, {}) == "test_error"