Skip to content

Commit 0617cd7

Browse files
hallvictoriaVictoria Hall
and
Victoria Hall
authoredAug 7, 2024
refactor: unique function name validation (#236)
* set functions_bindings to None * refactor name tracker to FunctionRegister * lint * renamed validate_functions * feedback * missed test fix * missed test fix * lint * fix tests * replace kafka test value * remove kafka test value --------- Co-authored-by: Victoria Hall <[email protected]>
1 parent 961e914 commit 0617cd7

File tree

3 files changed

+65
-32
lines changed

3 files changed

+65
-32
lines changed
 

‎azure/functions/decorators/function_app.py

+21-11
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,6 @@ def __str__(self):
206206

207207

208208
class FunctionBuilder(object):
209-
function_bindings: dict = {}
210209

211210
def __init__(self, func, function_script_file):
212211
self._function = Function(func, function_script_file)
@@ -272,16 +271,6 @@ def _validate_function(self,
272271
parse_singular_param_to_enum(auth_level, AuthLevel))
273272
self._function._is_http_function = True
274273

275-
# This dict contains the function name and its bindings for all
276-
# functions in an app. If a previous function has the same name,
277-
# indexing will fail here.
278-
if self.function_bindings.get(function_name, None):
279-
raise ValueError(
280-
f"Function {function_name} does not have a unique"
281-
f" function name. Please change @app.function_name() or"
282-
f" the function method name to be unique.")
283-
self.function_bindings[function_name] = bindings
284-
285274
def build(self, auth_level: Optional[AuthLevel] = None) -> Function:
286275
"""
287276
Validates and builds the function object.
@@ -3592,6 +3581,7 @@ def __init__(self, auth_level: Union[AuthLevel, str], *args, **kwargs):
35923581
DecoratorApi.__init__(self, *args, **kwargs)
35933582
HttpFunctionsAuthLevelMixin.__init__(self, auth_level, *args, **kwargs)
35943583
self._require_auth_level: Optional[bool] = None
3584+
self.functions_bindings: Optional[Dict[Any, Any]] = None
35953585

35963586
def get_functions(self) -> List[Function]:
35973587
"""Get the function objects in the function app.
@@ -3613,8 +3603,28 @@ def get_functions(self) -> List[Function]:
36133603
'-bindings-http-webhook-trigger?tabs=in-process'
36143604
'%2Cfunctionsv2&pivots=programming-language-python#http-auth')
36153605

3606+
self.validate_function_names(functions=functions)
3607+
36163608
return functions
36173609

3610+
def validate_function_names(self, functions: List[Function]):
3611+
"""The functions_bindings dict contains the function name and
3612+
its bindings for all functions in an app. If a previous function
3613+
has the same name, indexing will fail here.
3614+
"""
3615+
if not self.functions_bindings:
3616+
self.functions_bindings = {}
3617+
for function in functions:
3618+
function_name = function.get_function_name()
3619+
if function_name in self.functions_bindings:
3620+
raise ValueError(
3621+
f"Function {function_name} does not have a unique"
3622+
f" function name. Please change @app.function_name() or"
3623+
f" the function method name to be unique.")
3624+
# The value of the key doesn't matter. We're using a dict for
3625+
# faster lookup times.
3626+
self.functions_bindings[function_name] = True
3627+
36183628
def register_functions(self, function_container: DecoratorApi) -> None:
36193629
"""Register a list of functions in the function app.
36203630

‎tests/decorators/test_function_app.py

+40-17
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
)
2121
from azure.functions.decorators.http import HttpTrigger, HttpOutput, \
2222
HttpMethod
23-
from azure.functions.decorators.timer import TimerTrigger
2423
from azure.functions.decorators.retry_policy import RetryPolicy
2524
from test_core import DummyTrigger
2625
from tests.utils.testutils import assert_json
@@ -291,10 +290,6 @@ def test_unique_method_names2(name: str):
291290
"test_unique_method_names")
292291
self.assertEqual(functions[1].get_function_name(),
293292
"test_unique_method_names2")
294-
self.assertIsInstance(app._function_builders[0].function_bindings.get(
295-
"test_unique_method_names")[0], TimerTrigger)
296-
self.assertIsInstance(app._function_builders[0].function_bindings.get(
297-
"test_unique_method_names2")[0], TimerTrigger)
298293

299294
def test_unique_function_names(self):
300295
app = FunctionApp()
@@ -316,10 +311,6 @@ def test_unique_function_names2(name: str):
316311
"test_unique_function_names")
317312
self.assertEqual(functions[1].get_function_name(),
318313
"test_unique_function_names2")
319-
self.assertIsInstance(app._function_builders[0].function_bindings.get(
320-
"test_unique_function_names")[0], TimerTrigger)
321-
self.assertIsInstance(app._function_builders[0].function_bindings.get(
322-
"test_unique_function_names2")[0], TimerTrigger)
323314

324315
def test_same_method_names(self):
325316
app = FunctionApp()
@@ -425,10 +416,6 @@ def test_blueprint_unique_method_names2(name: str):
425416
"test_blueprint_unique_method_names")
426417
self.assertEqual(functions[1].get_function_name(),
427418
"test_blueprint_unique_method_names2")
428-
self.assertIsInstance(app._function_builders[0].function_bindings.get(
429-
"test_blueprint_unique_method_names")[0], TimerTrigger)
430-
self.assertIsInstance(app._function_builders[0].function_bindings.get(
431-
"test_blueprint_unique_method_names2")[0], TimerTrigger)
432419

433420
def test_blueprint_unique_function_names(self):
434421
app = FunctionApp()
@@ -454,10 +441,6 @@ def test_blueprint_unique_function_names2(name: str):
454441
"test_blueprint_unique_function_names")
455442
self.assertEqual(functions[1].get_function_name(),
456443
"test_blueprint_unique_function_names2")
457-
self.assertIsInstance(app._function_builders[0].function_bindings.get(
458-
"test_blueprint_unique_function_names")[0], TimerTrigger)
459-
self.assertIsInstance(app._function_builders[0].function_bindings.get(
460-
"test_blueprint_unique_function_names2")[0], TimerTrigger)
461444

462445
def test_blueprint_same_method_names(self):
463446
app = FunctionApp()
@@ -1009,3 +992,43 @@ def _test_http_external_app(self, app, is_async, function_name):
1009992
"type": HTTP_OUTPUT
1010993
}
1011994
]})
995+
996+
997+
class TestFunctionRegister(unittest.TestCase):
998+
def test_validate_empty_dict(self):
999+
def dummy():
1000+
return "dummy"
1001+
1002+
test_func = Function(dummy, "dummy.py")
1003+
fr = FunctionRegister(auth_level="ANONYMOUS")
1004+
fr.validate_function_names(functions=[test_func])
1005+
1006+
def test_validate_unique_names(self):
1007+
def dummy():
1008+
return "dummy"
1009+
1010+
def dummy2():
1011+
return "dummy"
1012+
1013+
test_func = Function(dummy, "dummy.py")
1014+
test_func2 = Function(dummy2, "dummy.py")
1015+
1016+
fr = FunctionRegister(auth_level="ANONYMOUS")
1017+
fr.validate_function_names(
1018+
functions=[test_func, test_func2])
1019+
1020+
def test_validate_non_unique_names(self):
1021+
def dummy():
1022+
return "dummy"
1023+
1024+
test_func = Function(dummy, "dummy.py")
1025+
test_func2 = Function(dummy, "dummy.py")
1026+
1027+
fr = FunctionRegister(auth_level="ANONYMOUS")
1028+
with self.assertRaises(ValueError) as err:
1029+
fr.validate_function_names(functions=[test_func, test_func2])
1030+
self.assertEqual(err.exception.args[0],
1031+
"Function dummy does not have"
1032+
" a unique function name."
1033+
" Please change @app.function_name()"
1034+
" or the function method name to be unique.")

‎tests/decorators/test_kafka.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def test_kafka_trigger_valid_creation(self):
2424
ssl_certificate_location="scl",
2525
ssl_key_password="ssl_key_password",
2626
schema_registry_url="srurl",
27-
schema_registry_username="sruser",
27+
schema_registry_username="",
2828
schema_registry_password="srp",
2929
authentication_mode=BrokerAuthenticationMode.PLAIN, # noqa: E501
3030
data_type=DataType.UNDEFINED,
@@ -46,7 +46,7 @@ def test_kafka_trigger_valid_creation(self):
4646
"protocol": BrokerProtocol.NOTSET,
4747
"schemaRegistryPassword": "srp",
4848
"schemaRegistryUrl": "srurl",
49-
"schemaRegistryUsername": "sruser",
49+
"schemaRegistryUsername": "",
5050
"sslCaLocation": "ssl_ca_location",
5151
"sslCertificateLocation": "scl",
5252
"sslKeyLocation": "ssl_key_location",
@@ -68,7 +68,7 @@ def test_kafka_output_valid_creation(self):
6868
ssl_certificate_location="scl",
6969
ssl_key_password="ssl_key_password",
7070
schema_registry_url="schema_registry_url",
71-
schema_registry_username="sru",
71+
schema_registry_username="",
7272
schema_registry_password="srp",
7373
max_retries=10,
7474
data_type=DataType.UNDEFINED,
@@ -94,7 +94,7 @@ def test_kafka_output_valid_creation(self):
9494
'requestTimeoutMs': 5000,
9595
'schemaRegistryPassword': 'srp',
9696
'schemaRegistryUrl': 'schema_registry_url',
97-
'schemaRegistryUsername': 'sru',
97+
'schemaRegistryUsername': '',
9898
'sslCaLocation': 'ssl_ca_location',
9999
'sslCertificateLocation': 'scl',
100100
'sslKeyLocation': 'ssl_key_location',

0 commit comments

Comments
 (0)