Skip to content

Commit 365b9c8

Browse files
authored
Merge pull request #61 from invertase/eventarc
feat: eventarc function support
2 parents 0f497ce + 7724e8e commit 365b9c8

File tree

11 files changed

+352
-0
lines changed

11 files changed

+352
-0
lines changed

docs/generate.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ TITLE="Firebase Python SDK for Cloud Functions"
8585
PY_MODULES='firebase_functions
8686
firebase_functions.core
8787
firebase_functions.db_fn
88+
firebase_functions.eventarc_fn
8889
firebase_functions.https_fn
8990
firebase_functions.options
9091
firebase_functions.params

samples/basic_eventarc/.firebaserc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"projects": {
3+
"default": "python-functions-testing"
4+
}
5+
}

samples/basic_eventarc/.gitignore

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
firebase-debug.log*
8+
firebase-debug.*.log*
9+
10+
# Firebase cache
11+
.firebase/
12+
13+
# Firebase config
14+
15+
# Uncomment this if you'd like others to create their own Firebase project.
16+
# For a team working on the same Firebase project(s), it is recommended to leave
17+
# it commented so all members can deploy to the same project(s) in .firebaserc.
18+
# .firebaserc
19+
20+
# Runtime data
21+
pids
22+
*.pid
23+
*.seed
24+
*.pid.lock
25+
26+
# Directory for instrumented libs generated by jscoverage/JSCover
27+
lib-cov
28+
29+
# Coverage directory used by tools like istanbul
30+
coverage
31+
32+
# nyc test coverage
33+
.nyc_output
34+
35+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
36+
.grunt
37+
38+
# Bower dependency directory (https://bower.io/)
39+
bower_components
40+
41+
# node-waf configuration
42+
.lock-wscript
43+
44+
# Compiled binary addons (http://nodejs.org/api/addons.html)
45+
build/Release
46+
47+
# Dependency directories
48+
node_modules/
49+
50+
# Optional npm cache directory
51+
.npm
52+
53+
# Optional eslint cache
54+
.eslintcache
55+
56+
# Optional REPL history
57+
.node_repl_history
58+
59+
# Output of 'npm pack'
60+
*.tgz
61+
62+
# Yarn Integrity file
63+
.yarn-integrity
64+
65+
# dotenv environment variables file
66+
.env

samples/basic_eventarc/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Required to avoid a 'duplicate modules' mypy error
2+
# in monorepos that have multiple main.py files.
3+
# https://github.com/python/mypy/issues/4008
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"functions": [
3+
{
4+
"source": "functions",
5+
"codebase": "default",
6+
"ignore": [
7+
"venv"
8+
]
9+
}
10+
]
11+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# pyenv
2+
.python-version
3+
4+
# Installer logs
5+
pip-log.txt
6+
pip-delete-this-directory.txt
7+
8+
# Environments
9+
.env
10+
.venv
11+
venv/
12+
venv.bak/
13+
__pycache__
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"""Firebase Cloud Functions for Eventarc triggers example."""
2+
from firebase_functions import eventarc_fn
3+
4+
5+
@eventarc_fn.on_custom_event_published(
6+
event_type="firebase.extensions.storage-resize-images.v1.complete",)
7+
def onimageresize(event: eventarc_fn.CloudEvent) -> None:
8+
"""
9+
Handle image resize events from the Firebase Storage Resize Images extension.
10+
https://extensions.dev/extensions/firebase/storage-resize-images
11+
"""
12+
print("Received image resize completed event", event)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Not published yet,
2+
# firebase-functions-python >= 0.0.1
3+
# so we use a relative path during development:
4+
./../../../
5+
# Or switch to git ref for deployment testing:
6+
# git+https://github.com/firebase/firebase-functions-python.git@main#egg=firebase-functions
7+
8+
firebase-admin >= 6.0.1
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Copyright 2022 Google Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
"""Cloud functions to handle Eventarc events."""
15+
16+
# pylint: disable=protected-access
17+
import typing as _typing
18+
import functools as _functools
19+
import datetime as _dt
20+
import cloudevents.http as _ce
21+
22+
import firebase_functions.options as _options
23+
import firebase_functions.private.util as _util
24+
from firebase_functions.core import CloudEvent
25+
26+
27+
@_util.copy_func_kwargs(_options.EventarcTriggerOptions)
28+
def on_custom_event_published(
29+
**kwargs
30+
) -> _typing.Callable[[_typing.Callable[[CloudEvent], None]], _typing.Callable[
31+
[CloudEvent], None]]:
32+
"""
33+
Creates a handler for events published on the default event eventarc channel.
34+
35+
Example:
36+
37+
.. code-block:: python
38+
39+
from firebase_functions import eventarc_fn
40+
41+
@eventarc_fn.on_custom_event_published(
42+
event_type="firebase.extensions.storage-resize-images.v1.complete",
43+
)
44+
def onimageresize(event: eventarc_fn.CloudEvent) -> None:
45+
pass
46+
47+
:param \\*\\*kwargs: Options.
48+
:type \\*\\*kwargs: as :exc:`firebase_functions.options.EventarcTriggerOptions`
49+
:rtype: :exc:`typing.Callable`
50+
\\[ \\[ :exc:`firebase_functions.core.CloudEvent` \\], `None` \\]
51+
A function that takes a CloudEvent and returns None.
52+
"""
53+
options = _options.EventarcTriggerOptions(**kwargs)
54+
55+
def on_custom_event_published_decorator(func: _typing.Callable[[CloudEvent],
56+
None]):
57+
58+
@_functools.wraps(func)
59+
def on_custom_event_published_wrapped(raw: _ce.CloudEvent):
60+
event_attributes = raw._get_attributes()
61+
event_data: _typing.Any = raw.get_data()
62+
event_dict = {**event_data, **event_attributes}
63+
event: CloudEvent = CloudEvent(
64+
data=event_data,
65+
id=event_dict["id"],
66+
source=event_dict["source"],
67+
specversion=event_dict["specversion"],
68+
subject=event_dict["subject"]
69+
if "subject" in event_dict else None,
70+
time=_dt.datetime.strptime(
71+
event_dict["time"],
72+
"%Y-%m-%dT%H:%M:%S.%f%z",
73+
),
74+
type=event_dict["type"],
75+
)
76+
func(event)
77+
78+
_util.set_func_endpoint_attr(
79+
on_custom_event_published_wrapped,
80+
options._endpoint(func_name=func.__name__),
81+
)
82+
_util.set_required_apis_attr(
83+
on_custom_event_published_wrapped,
84+
options._required_apis(),
85+
)
86+
return on_custom_event_published_wrapped
87+
88+
return on_custom_event_published_decorator

src/firebase_functions/options.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,66 @@ def _endpoint(
502502
**kwargs, event_filters=event_filters, event_type=event_type))))
503503

504504

505+
@_dataclasses.dataclass(frozen=True, kw_only=True)
506+
class EventarcTriggerOptions(EventHandlerOptions):
507+
"""
508+
Options that can be set on an Eventarc trigger.
509+
Internal use only.
510+
"""
511+
512+
event_type: str
513+
"""
514+
Type of the event to trigger on.
515+
"""
516+
517+
channel: str | None = None
518+
"""
519+
ID of the channel. Can be either:
520+
* fully qualified channel resource name:
521+
`projects/{project}/locations/{location}/channels/{channel-id}`
522+
* partial resource name with location and channel ID, in which case
523+
the runtime project ID of the function will be used:
524+
`locations/{location}/channels/{channel-id}`
525+
* partial channel ID, in which case the runtime project ID of the
526+
function and `us-central1` as location will be used:
527+
`{channel-id}`
528+
529+
If not specified, the default Firebase channel will be used:
530+
`projects/{project}/locations/us-central1/channels/firebase`
531+
"""
532+
533+
filters: dict[str, str] | None = None
534+
"""
535+
Eventarc event exact match filter.
536+
"""
537+
538+
def _endpoint(
539+
self,
540+
**kwargs,
541+
) -> _manifest.ManifestEndpoint:
542+
event_filters = {} if self.filters is None else self.filters
543+
endpoint = _manifest.ManifestEndpoint(**_typing.cast(
544+
_typing.Dict,
545+
_dataclasses.asdict(super()._endpoint(
546+
**kwargs,
547+
event_filters=event_filters,
548+
event_type=self.event_type,
549+
))))
550+
assert endpoint.eventTrigger is not None
551+
channel = (self.channel if self.channel is not None else
552+
"locations/us-central1/channels/firebase")
553+
endpoint.eventTrigger["channel"] = channel
554+
return endpoint
555+
556+
def _required_apis(self) -> list[_manifest.ManifestRequiredApi]:
557+
return [
558+
_manifest.ManifestRequiredApi(
559+
api="eventarcpublishing.googleapis.com",
560+
reason="Needed for custom event functions",
561+
)
562+
]
563+
564+
505565
@_dataclasses.dataclass(frozen=True, kw_only=True)
506566
class ScheduleOptions(RuntimeOptions):
507567
"""

0 commit comments

Comments
 (0)