Skip to content

Commit 44ed9a2

Browse files
authored
Merge pull request #60 from invertase/schedule
feat: scheduler fn support
2 parents 734a693 + 4c96215 commit 44ed9a2

File tree

11 files changed

+475
-8
lines changed

11 files changed

+475
-8
lines changed

samples/basic_scheduler/.firebaserc

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

samples/basic_scheduler/.gitignore

+66
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_scheduler/__init__.py

+3
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

samples/basic_scheduler/firebase.json

+11
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+
}
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__
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"""Firebase Scheduled Cloud Functions example."""
2+
3+
from firebase_functions import scheduler_fn
4+
5+
6+
@scheduler_fn.on_schedule(
7+
schedule="* * * * *",
8+
timezone=scheduler_fn.Timezone("America/Los_Angeles"),
9+
)
10+
def example(event: scheduler_fn.ScheduledEvent) -> None:
11+
print(event.job_name)
12+
print(event.schedule_time)
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

src/firebase_functions/options.py

+85-1
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,16 @@
2020
import dataclasses as _dataclasses
2121
import re as _re
2222
import typing as _typing
23+
from zoneinfo import ZoneInfo as _ZoneInfo
2324

2425
import firebase_functions.private.manifest as _manifest
2526
import firebase_functions.private.util as _util
2627
import firebase_functions.private.path_pattern as _path_pattern
2728
from firebase_functions.params import SecretParam, Expression
2829

30+
Timezone = _ZoneInfo
31+
"""An alias of the zoneinfo.ZoneInfo for convenience."""
32+
2933
RESET_VALUE = _util.Sentinel(
3034
"Special configuration value to reset configuration to platform default.")
3135
"""Special configuration value to reset configuration to platform default."""
@@ -409,7 +413,7 @@ def _endpoint(
409413
maxDispatchesPerSecond=self.rate_limits.max_dispatches_per_second,
410414
) if self.rate_limits is not None else None
411415

412-
retry_config: _manifest.RetryConfig | None = _manifest.RetryConfig(
416+
retry_config: _manifest.RetryConfigTasks | None = _manifest.RetryConfigTasks(
413417
maxAttempts=self.retry_config.max_attempts,
414418
maxRetrySeconds=self.retry_config.max_retry_seconds,
415419
maxBackoffSeconds=self.retry_config.max_backoff_seconds,
@@ -498,6 +502,86 @@ def _endpoint(
498502
**kwargs, event_filters=event_filters, event_type=event_type))))
499503

500504

505+
@_dataclasses.dataclass(frozen=True, kw_only=True)
506+
class ScheduleOptions(RuntimeOptions):
507+
"""
508+
Options that can be set on a Schedule trigger.
509+
"""
510+
511+
schedule: str
512+
"""
513+
The schedule, in Unix Crontab or AppEngine syntax.
514+
"""
515+
516+
timezone: Timezone | Expression[str] | _util.Sentinel | None = None
517+
"""
518+
The timezone that the schedule executes in.
519+
"""
520+
521+
retry_count: int | Expression[int] | _util.Sentinel | None = None
522+
"""
523+
The number of retry attempts for a failed run.
524+
"""
525+
526+
max_retry_seconds: int | Expression[int] | _util.Sentinel | None = None
527+
"""
528+
The time limit for retrying.
529+
"""
530+
531+
max_backoff_seconds: int | Expression[int] | _util.Sentinel | None = None
532+
"""
533+
The maximum amount of time to wait between attempts.
534+
"""
535+
536+
max_doublings: int | Expression[int] | _util.Sentinel | None = None
537+
"""
538+
The maximum number of times to double the backoff between
539+
retries.
540+
"""
541+
542+
min_backoff_seconds: int | Expression[int] | _util.Sentinel | None = None
543+
"""
544+
The minimum time to wait between attempts.
545+
"""
546+
547+
def _endpoint(
548+
self,
549+
**kwargs,
550+
) -> _manifest.ManifestEndpoint:
551+
retry_config: _manifest.RetryConfigScheduler = _manifest.RetryConfigScheduler(
552+
retryCount=self.retry_count,
553+
maxRetrySeconds=self.max_retry_seconds,
554+
maxBackoffSeconds=self.max_backoff_seconds,
555+
maxDoublings=self.max_doublings,
556+
minBackoffSeconds=self.min_backoff_seconds,
557+
)
558+
time_zone: str | Expression[str] | _util.Sentinel | None = None
559+
if isinstance(self.timezone, Timezone):
560+
time_zone = self.timezone.key
561+
else:
562+
time_zone = self.timezone
563+
564+
kwargs_merged = {
565+
**_dataclasses.asdict(super()._endpoint(**kwargs)),
566+
"scheduleTrigger":
567+
_manifest.ScheduleTrigger(
568+
schedule=self.schedule,
569+
timeZone=time_zone,
570+
retryConfig=retry_config,
571+
),
572+
}
573+
return _manifest.ManifestEndpoint(
574+
**_typing.cast(_typing.Dict, kwargs_merged))
575+
576+
def _required_apis(self) -> list[_manifest.ManifestRequiredApi]:
577+
return [
578+
_manifest.ManifestRequiredApi(
579+
api="cloudscheduler.googleapis.com",
580+
reason="Needed for scheduled functions.",
581+
)
582+
]
583+
584+
501585
@_dataclasses.dataclass(frozen=True, kw_only=True)
502586
class StorageOptions(RuntimeOptions):
503587
"""

src/firebase_functions/private/manifest.py

+21-7
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,10 @@ class EventTrigger(_typing.TypedDict):
6565
_util.Sentinel]
6666

6767

68-
class RetryConfig(_typing.TypedDict):
68+
class RetryConfigBase(_typing.TypedDict):
6969
"""
7070
Retry configuration for a endpoint.
7171
"""
72-
maxAttempts: _typing_extensions.NotRequired[int | _params.Expression[int] |
73-
_util.Sentinel | None]
7472
maxRetrySeconds: _typing_extensions.NotRequired[int |
7573
_params.Expression[int] |
7674
_util.Sentinel | None]
@@ -84,6 +82,22 @@ class RetryConfig(_typing.TypedDict):
8482
_util.Sentinel | None]
8583

8684

85+
class RetryConfigTasks(RetryConfigBase):
86+
"""
87+
Retry configuration for a task.
88+
"""
89+
maxAttempts: _typing_extensions.NotRequired[int | _params.Expression[int] |
90+
_util.Sentinel | None]
91+
92+
93+
class RetryConfigScheduler(RetryConfigBase):
94+
"""
95+
Retry configuration for a schedule.
96+
"""
97+
retryCount: _typing_extensions.NotRequired[int | _params.Expression[int] |
98+
_util.Sentinel | None]
99+
100+
87101
class RateLimits(_typing.TypedDict):
88102
maxConcurrentDispatches: int | _params.Expression[
89103
int] | _util.Sentinel | None
@@ -96,14 +110,14 @@ class TaskQueueTrigger(_typing.TypedDict):
96110
Trigger definitions for RPCs servers using the HTTP protocol defined at
97111
https://firebase.google.com/docs/functions/callable-reference
98112
"""
99-
retryConfig: RetryConfig | None
113+
retryConfig: RetryConfigTasks | None
100114
rateLimits: RateLimits | None
101115

102116

103117
class ScheduleTrigger(_typing.TypedDict):
104-
schedule: _typing_extensions.NotRequired[str | _params.Expression[str]]
105-
timeZone: _typing_extensions.NotRequired[str | _params.Expression[str]]
106-
retryConfig: _typing_extensions.NotRequired[RetryConfig]
118+
schedule: str | _params.Expression[str]
119+
timeZone: str | _params.Expression[str] | _util.Sentinel | None
120+
retryConfig: RetryConfigScheduler | None
107121

108122

109123
class BlockingTrigger(_typing.TypedDict):

0 commit comments

Comments
 (0)