Skip to content

Commit 8fc4843

Browse files
committed
Merge branch 'main' into feat/auth-context
2 parents 97b1938 + 37c1fc0 commit 8fc4843

File tree

12 files changed

+172
-32
lines changed

12 files changed

+172
-32
lines changed

Diff for: samples/basic_params/functions/main.py

+13
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,18 @@
2828
default="/images/processed",
2929
)
3030

31+
image_type = params.ListParam(
32+
"IMAGE_TYPE",
33+
label="convert image to preferred types",
34+
description="The image types you'd like your source image to convert to.",
35+
input=params.MultiSelectInput([
36+
params.SelectOption(value="jpeg", label="jpeg"),
37+
params.SelectOption(value="png", label="png"),
38+
params.SelectOption(value="webp", label="webp"),
39+
]),
40+
default=["jpeg", "png"],
41+
)
42+
3143
delete_original = params.BoolParam(
3244
"DELETE_ORIGINAL_FILE",
3345
label="delete the original file",
@@ -56,6 +68,7 @@ def resize_images(event: storage_fn.CloudEvent[storage_fn.StorageObjectData]):
5668
This function will be triggered when a new object is created in the bucket.
5769
"""
5870
print("Got a new image:", event)
71+
print("Selected image types:", image_type.value)
5972
print("Selected bucket resource:", bucket.value)
6073
print("Selected output location:", output_path.value)
6174
print("Testing a not so secret api key:", image_resize_api_secret.value)

Diff for: src/firebase_functions/https_fn.py

+6
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ class FunctionsErrorCode(str, _enum.Enum):
135135
Unrecoverable data loss or corruption.
136136
"""
137137

138+
def __str__(self) -> str:
139+
return self.value
140+
138141

139142
class _CanonicalErrorCodeName(str, _enum.Enum):
140143
"""The canonical error code name for a given error code."""
@@ -157,6 +160,9 @@ class _CanonicalErrorCodeName(str, _enum.Enum):
157160
UNAVAILABLE = "UNAVAILABLE"
158161
DATA_LOSS = "DATA_LOSS"
159162

163+
def __str__(self) -> str:
164+
return self.value
165+
160166

161167
@_dataclasses.dataclass(frozen=True)
162168
class _HttpErrorCode:

Diff for: src/firebase_functions/logger.py

+3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ class LogSeverity(str, _enum.Enum):
2424
ALERT = "ALERT"
2525
EMERGENCY = "EMERGENCY"
2626

27+
def __str__(self) -> str:
28+
return self.value
29+
2730

2831
class LogEntry(_typing.TypedDict):
2932
"""

Diff for: src/firebase_functions/options.py

+15
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ class VpcEgressSetting(str, _enum.Enum):
4141
PRIVATE_RANGES_ONLY = "PRIVATE_RANGES_ONLY"
4242
ALL_TRAFFIC = "ALL_TRAFFIC"
4343

44+
def __str__(self) -> str:
45+
return self.value
46+
4447

4548
class IngressSetting(str, _enum.Enum):
4649
"""What kind of traffic can access the function."""
@@ -49,6 +52,9 @@ class IngressSetting(str, _enum.Enum):
4952
ALLOW_INTERNAL_ONLY = "ALLOW_INTERNAL_ONLY"
5053
ALLOW_INTERNAL_AND_GCLB = "ALLOW_INTERNAL_AND_GCLB"
5154

55+
def __str__(self) -> str:
56+
return self.value
57+
5258

5359
@_dataclasses.dataclass(frozen=True)
5460
class CorsOptions:
@@ -88,6 +94,9 @@ class MemoryOption(int, _enum.Enum):
8894
GB_16 = 16 << 10
8995
GB_32 = 32 << 10
9096

97+
def __str__(self) -> str:
98+
return f"{self.value}MB"
99+
91100

92101
class SupportedRegion(str, _enum.Enum):
93102
"""
@@ -120,6 +129,9 @@ class SupportedRegion(str, _enum.Enum):
120129
US_WEST3 = "us-west3"
121130
US_WEST4 = "us-west4"
122131

132+
def __str__(self) -> str:
133+
return self.value
134+
123135

124136
@_dataclasses.dataclass(frozen=True)
125137
class RateLimits():
@@ -587,6 +599,9 @@ class AlertType(str, _enum.Enum):
587599
Performance threshold alerts.
588600
"""
589601

602+
def __str__(self) -> str:
603+
return self.value
604+
590605

591606
@_dataclasses.dataclass(frozen=True, kw_only=True)
592607
class FirebaseAlertOptions(EventHandlerOptions):

Diff for: src/firebase_functions/params.py

+44-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"""Module for params that can make Cloud Functions codebases generic."""
1515

1616
import abc as _abc
17+
import json as _json
1718
import dataclasses as _dataclasses
1819
import os as _os
1920
import re as _re
@@ -139,6 +140,18 @@ class SelectInput(_typing.Generic[_T]):
139140
"""A list of user selectable options."""
140141

141142

143+
@_dataclasses.dataclass(frozen=True)
144+
class MultiSelectInput():
145+
"""
146+
Specifies that a Param's value should be determined by having the user select
147+
a subset from a list of pre-canned options interactively at deploy-time.
148+
Will result in errors if used on Params of type other than string[].
149+
"""
150+
151+
options: list[SelectOption[str]]
152+
"""A list of user selectable options."""
153+
154+
142155
@_dataclasses.dataclass(frozen=True)
143156
class TextInput:
144157
"""
@@ -168,6 +181,9 @@ class ResourceType(str, _enum.Enum):
168181
"""The type of resource that a picker should pick."""
169182
STORAGE_BUCKET = "storage.googleapis.com/Bucket"
170183

184+
def __str__(self) -> str:
185+
return self.value
186+
171187

172188
@_dataclasses.dataclass(frozen=True)
173189
class ResourceInput:
@@ -215,7 +231,8 @@ class Param(Expression[_T]):
215231
deployments.
216232
"""
217233

218-
input: TextInput | ResourceInput | SelectInput[_T] | None = None
234+
input: TextInput | ResourceInput | SelectInput[
235+
_T] | MultiSelectInput | None = None
219236
"""
220237
The type of input that is required for this param, e.g. TextInput.
221238
"""
@@ -355,6 +372,32 @@ def value(self) -> bool:
355372
return False
356373

357374

375+
@_dataclasses.dataclass(frozen=True)
376+
class ListParam(Param[list]):
377+
"""A parameter as a list of strings."""
378+
379+
@property
380+
def value(self) -> list[str]:
381+
if _os.environ.get(self.name) is not None:
382+
# If the environment variable starts with "[" and ends with "]",
383+
# then assume it is a JSON array and try to parse it.
384+
# (This is for Cloud Run (v2 Functions), the environment variable is a JSON array.)
385+
if _os.environ[self.name].startswith("[") and _os.environ[
386+
self.name].endswith("]"):
387+
try:
388+
return _json.loads(_os.environ[self.name])
389+
except _json.JSONDecodeError:
390+
return []
391+
# Otherwise, split the string by commas.
392+
# (This is for emulator & the Firebase CLI generated .env file, the environment
393+
# variable is a comma-separated list.)
394+
return list(filter(len, _os.environ[self.name].split(",")))
395+
if self.default is not None:
396+
return self.default.value if isinstance(
397+
self.default, Expression) else self.default
398+
return []
399+
400+
358401
@_dataclasses.dataclass(frozen=True)
359402
class _DefaultStringParam(StringParam):
360403
"""

Diff for: src/firebase_functions/private/manifest.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,8 @@ class ManifestStack:
181181

182182

183183
def _param_input_to_spec(
184-
param_input: _params.TextInput | _params.ResourceInput | _params.SelectInput
184+
param_input: _params.TextInput | _params.ResourceInput |
185+
_params.SelectInput | _params.MultiSelectInput
185186
) -> dict[str, _typing.Any]:
186187
if isinstance(param_input, _params.TextInput):
187188
return {
@@ -204,9 +205,11 @@ def _param_input_to_spec(
204205
},
205206
}
206207

207-
if isinstance(param_input, _params.SelectInput):
208+
if isinstance(param_input, (_params.MultiSelectInput, _params.SelectInput)):
209+
key = "select" if isinstance(param_input,
210+
_params.SelectInput) else "multiSelect"
208211
return {
209-
"select": {
212+
key: {
210213
"options": [{
211214
key: value for key, value in {
212215
"value": option.value,
@@ -242,6 +245,8 @@ def _param_to_spec(
242245
spec_dict["type"] = "float"
243246
elif isinstance(param, _params.SecretParam):
244247
spec_dict["type"] = "secret"
248+
elif isinstance(param, _params.ListParam):
249+
spec_dict["type"] = "list"
245250
elif isinstance(param, _params.StringParam):
246251
spec_dict["type"] = "string"
247252
else:

Diff for: src/firebase_functions/private/path_pattern.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,14 @@ def trim_param(param: str) -> str:
3737
_WILDCARD_CAPTURE_REGEX = re.compile(r"{[^/{}]+}", re.IGNORECASE)
3838

3939

40-
class SegmentName(Enum):
40+
class SegmentName(str, Enum):
4141
SEGMENT = "segment"
4242
SINGLE_CAPTURE = "single-capture"
4343
MULTI_CAPTURE = "multi-capture"
4444

45+
def __str__(self) -> str:
46+
return self.value
47+
4548

4649
class PathSegment:
4750
"""

Diff for: src/firebase_functions/private/util.py

+6
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ class OnCallTokenState(_enum.Enum):
187187
The token is invalid.
188188
"""
189189

190+
def __str__(self) -> str:
191+
return self.value
192+
190193

191194
@_dataclasses.dataclass()
192195
class _OnCallTokenVerification:
@@ -388,6 +391,9 @@ class PrecisionTimestamp(_enum.Enum):
388391

389392
SECONDS = "SECONDS"
390393

394+
def __str__(self) -> str:
395+
return self.value
396+
391397

392398
def get_precision_timestamp(time: str) -> PrecisionTimestamp:
393399
"""Return a bool which indicates if the timestamp is in nanoseconds"""

Diff for: src/firebase_functions/remote_config_fn.py

+6
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ class ConfigUpdateOrigin(str, _enum.Enum):
7575
The update came from the Firebase Admin Node SDK.
7676
"""
7777

78+
def __str__(self) -> str:
79+
return self.value
80+
7881

7982
class ConfigUpdateType(str, _enum.Enum):
8083
"""
@@ -102,6 +105,9 @@ class ConfigUpdateType(str, _enum.Enum):
102105
A rollback to a previous Remote Config template.
103106
"""
104107

108+
def __str__(self) -> str:
109+
return self.value
110+
105111

106112
@_dataclasses.dataclass(frozen=True)
107113
class ConfigUpdateData:

Diff for: src/firebase_functions/test_lab_fn.py

+6
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ class TestState(str, _enum.Enum):
6363
The test matrix was not run because the provided inputs are not valid.
6464
"""
6565

66+
def __str__(self) -> str:
67+
return self.value
68+
6669

6770
class OutcomeSummary(str, _enum.Enum):
6871
"""
@@ -101,6 +104,9 @@ class OutcomeSummary(str, _enum.Enum):
101104
All tests were skipped.
102105
"""
103106

107+
def __str__(self) -> str:
108+
return self.value
109+
104110

105111
@_dataclasses.dataclass(frozen=True)
106112
class ResultStorage:

Diff for: tests/test_manifest.py

+25-27
Original file line numberDiff line numberDiff line change
@@ -68,43 +68,41 @@
6868
_params.FloatParam("FLOAT_TEST", immutable=True),
6969
_params.SecretParam("SECRET_TEST"),
7070
_params.StringParam("STRING_TEST"),
71+
_params.ListParam("LIST_TEST", default=["1", "2", "3"]),
7172
],
7273
requiredAPIs=[{
7374
"api": "test_api",
7475
"reason": "testing"
75-
}],
76-
)
76+
}])
7777

7878
full_stack_dict = {
7979
"specVersion": "v1alpha1",
8080
"endpoints": {
8181
"test": full_endpoint_dict
8282
},
83-
"params": [
84-
{
85-
"name": "BOOL_TEST",
86-
"type": "boolean",
87-
"default": False,
88-
},
89-
{
90-
"name": "INT_TEST",
91-
"type": "int",
92-
"description": "int_description"
93-
},
94-
{
95-
"name": "FLOAT_TEST",
96-
"type": "float",
97-
"immutable": True,
98-
},
99-
{
100-
"name": "SECRET_TEST",
101-
"type": "secret"
102-
},
103-
{
104-
"name": "STRING_TEST",
105-
"type": "string"
106-
},
107-
],
83+
"params": [{
84+
"name": "BOOL_TEST",
85+
"type": "boolean",
86+
"default": False,
87+
}, {
88+
"name": "INT_TEST",
89+
"type": "int",
90+
"description": "int_description"
91+
}, {
92+
"name": "FLOAT_TEST",
93+
"type": "float",
94+
"immutable": True,
95+
}, {
96+
"name": "SECRET_TEST",
97+
"type": "secret"
98+
}, {
99+
"name": "STRING_TEST",
100+
"type": "string"
101+
}, {
102+
"default": ["1", "2", "3"],
103+
"name": "LIST_TEST",
104+
"type": "list"
105+
}],
108106
"requiredAPIs": [{
109107
"api": "test_api",
110108
"reason": "testing"

0 commit comments

Comments
 (0)