From b1e7fabfb74cfbc2962c24ccdf61999676309ef6 Mon Sep 17 00:00:00 2001 From: Dmitri Plotnikov Date: Mon, 8 Jun 2026 14:01:24 -0700 Subject: [PATCH] Update YAML configuration and constructors to use type and overload signatures PiperOrigin-RevId: 928757149 --- cel_expr_python/BUILD | 1 + cel_expr_python/cel.pyi | 4 +- cel_expr_python/cel_env_test.py | 218 ++++++++++++++---------- cel_expr_python/cel_test.py | 12 ++ cel_expr_python/py_cel_env_config.cc | 4 +- cel_expr_python/py_cel_env_config.h | 2 +- cel_expr_python/py_cel_env_internal.cc | 12 +- cel_expr_python/py_cel_function_decl.cc | 3 + cel_expr_python/py_cel_overload.cc | 47 ++++- cel_expr_python/py_cel_type.cc | 52 +++++- cel_expr_python/py_cel_type.h | 1 + codelab/index.lab.md | 18 +- codelab/solution/codelab.py | 13 +- 13 files changed, 258 insertions(+), 129 deletions(-) diff --git a/cel_expr_python/BUILD b/cel_expr_python/BUILD index 6f9836d..190c18a 100644 --- a/cel_expr_python/BUILD +++ b/cel_expr_python/BUILD @@ -70,6 +70,7 @@ pybind_library( "@com_google_cel_cpp//common:function_descriptor", "@com_google_cel_cpp//common:kind", "@com_google_cel_cpp//common:minimal_descriptor_pool", + "@com_google_cel_cpp//common:signature", "@com_google_cel_cpp//common:source", "@com_google_cel_cpp//common:type", "@com_google_cel_cpp//common:type_kind", diff --git a/cel_expr_python/cel.pyi b/cel_expr_python/cel.pyi index f5c8225..f5cc7c8 100644 --- a/cel_expr_python/cel.pyi +++ b/cel_expr_python/cel.pyi @@ -39,7 +39,7 @@ class FunctionDecl: def __init__(self, name: str, overloads: Sequence[Overload]) -> None: ... class Overload: - def __init__(self, overload_id: str, return_type: Type = ..., parameters: Sequence[Type] = ..., is_member: bool = ..., impl: Callable[..., Any] = ...) -> None: ... + def __init__(self, id: str | None = ..., return_type: Type = ..., parameters: Sequence[Type] = ..., is_member: bool = ..., impl: Callable[..., Any] = ..., signature: str | None = ...) -> None: ... class Type: BOOL: ClassVar[Type] = ... @@ -57,7 +57,7 @@ class Type: TYPE: ClassVar[Type] = ... UINT: ClassVar[Type] = ... UNKNOWN: ClassVar[Type] = ... - def __init__(self, name: str) -> None: ... + def __init__(self, signature: str) -> None: ... @staticmethod def AbstractType(name: str, params: Sequence[Type] = ...) -> Type: ... @staticmethod diff --git a/cel_expr_python/cel_env_test.py b/cel_expr_python/cel_env_test.py index ce98570..eacfc9f 100644 --- a/cel_expr_python/cel_env_test.py +++ b/cel_expr_python/cel_env_test.py @@ -46,17 +46,13 @@ def test_env_config_from_and_to_yaml(self): - name: math variables: - name: one - type_name: int + type: int value: 1 functions: - name: add overloads: - - id: "add_int_int" - args: - - type_name: int - - type_name: int - return: - type_name: int + - signature: "add(int,int)" + return: int """) yaml: str = config.to_yaml() self.assertEqual( @@ -74,17 +70,13 @@ def test_env_config_from_and_to_yaml(self): - name: "_+_" variables: - name: "one" - type_name: "int" + type: "int" value: 1 functions: - name: "add" overloads: - - id: "add_int_int" - args: - - type_name: "int" - - type_name: "int" - return: - type_name: "int" + - signature: "add(int,int)" + return: "int" """), ) @@ -185,9 +177,9 @@ def test_expression_container_abbreviations_and_aliases(self): qualified_name: "x.y.bar" variables: - name: "x.y.bar" - type_name: "string" + type: "string" - name: "x.y.foo" - type_name: "int" + type: "int" """), ) @@ -202,9 +194,9 @@ def test_abbreviations_and_aliases_from_yaml(self): qualified_name: "x.y.bar" variables: - name: "x.y.bar" - type_name: "string" + type: "string" - name: "x.y.foo" - type_name: "int" + type: "int" """)) res = env.compile("foo").eval(data={"x.y.foo": 42}) @@ -224,13 +216,13 @@ def test_abbreviations_and_aliases_combined(self): qualified_name: "x.y.bar" variables: - name: "x.y.bar" - type_name: "string" + type: "string" - name: "x.y.foo" - type_name: "int" + type: "int" - name: "a.b.qux" - type_name: "string" + type: "string" - name: "a.b.baz" - type_name: "int" + type: "int" """), container=cel.ExpressionContainer( "test.container", @@ -265,13 +257,13 @@ def test_abbreviations_and_aliases_combined(self): qualified_name: "a.b.qux" variables: - name: "a.b.baz" - type_name: "int" + type: "int" - name: "a.b.qux" - type_name: "string" + type: "string" - name: "x.y.bar" - type_name: "string" + type: "string" - name: "x.y.foo" - type_name: "int" + type: "int" """), ) @@ -321,48 +313,35 @@ def test_config_export_variables(self): normalize_yaml(""" variables: - name: "var_bool" - type_name: "bool" + type: "bool" - name: "var_bytes" - type_name: "bytes" + type: "bytes" - name: "var_double" - type_name: "double" + type: "double" - name: "var_duration" - type_name: "duration" + type: "duration" - name: "var_dyn" - type_name: "dyn" + type: "dyn" - name: "var_dyn_list" - type_name: "list" - params: - - type_name: "dyn" + type: "list" - name: "var_dyn_map" - type_name: "map" - params: - - type_name: "dyn" - - type_name: "dyn" + type: "map" - name: "var_int" - type_name: "int" + type: "int" - name: "var_int_map" - type_name: "map" - params: - - type_name: "int" - - type_name: "string" + type: "map" - name: "var_msg" - type_name: "cel.expr.conformance.proto2.TestAllTypes" + type: "cel.expr.conformance.proto2.TestAllTypes" - name: "var_str" - type_name: "string" + type: "string" - name: "var_string_list" - type_name: "list" - params: - - type_name: "string" + type: "list" - name: "var_string_map" - type_name: "map" - params: - - type_name: "string" - - type_name: "bool" + type: "map" - name: "var_timestamp" - type_name: "timestamp" + type: "timestamp" - name: "var_uint" - type_name: "uint" + type: "uint" """), ) @@ -370,7 +349,7 @@ def test_config_augmented_variables(self): config = cel.NewEnvConfigFromYaml(""" variables: - name: "var_bool" - type_name: "bool" + type: "bool" """) env: cel.Env = cel.NewEnv( config=config, @@ -384,9 +363,9 @@ def test_config_augmented_variables(self): normalize_yaml(""" variables: - name: "var_bool" - type_name: "bool" + type: "bool" - name: "var_msg" - type_name: "cel.expr.conformance.proto2.TestAllTypes" + type: "cel.expr.conformance.proto2.TestAllTypes" """), ) @@ -394,7 +373,7 @@ def test_config_variable_override(self): config: cel.EnvConfig = cel.NewEnvConfigFromYaml(""" variables: - name: "var_bool" - type_name: "bool" + type: "bool" """) with self.assertRaises(Exception) as e: @@ -413,9 +392,9 @@ def test_config_variable_types(self): config: cel.EnvConfig = cel.NewEnvConfigFromYaml(""" variables: - name: "var_bool" - type_name: "bool" + type: "bool" - name: "var_int" - type_name: "int" + type: "int" value: 42 """) env: cel.Env = cel.NewEnv( @@ -560,11 +539,8 @@ def test_config_functions(self): functions: - name: is_ok overloads: - - id: "is_ok_string" - target: - type_name: string - return: - type_name: bool + - signature: "string.is_ok()" + return: "bool" """) env: cel.Env = cel.NewEnv( config=config, @@ -573,12 +549,8 @@ def test_config_functions(self): "hello", [ cel.Overload( - "good_time_of_day", + signature="hello(string,string)", return_type=cel.Type.STRING, - parameters=[ - cel.Type.STRING, - cel.Type.STRING, - ], impl=lambda ampm, arg: ( "Good" f" {'morning' if ampm == 'am' else 'afternoon'}," @@ -589,7 +561,7 @@ def test_config_functions(self): ) ], function_impls={ - "is_ok_string": lambda arg: arg in ["excellent", "good", "fair"], + "string.is_ok()": lambda arg: arg in ["excellent", "good", "fair"], }, ) yaml = env.config().to_yaml() @@ -599,19 +571,12 @@ def test_config_functions(self): functions: - name: "hello" overloads: - - id: "good_time_of_day" - args: - - type_name: "string" - - type_name: "string" - return: - type_name: "string" + - signature: "hello(string,string)" + return: "string" - name: "is_ok" overloads: - - id: "is_ok_string" - target: - type_name: "string" - return: - type_name: "bool" + - signature: "string.is_ok()" + return: "bool" """), ) res: cel.Value = env.compile("hello('am', 'Sunshine')").eval() @@ -628,32 +593,109 @@ def test_config_function_override(self): functions: - name: foo overloads: - - id: "unique_id" + - signature: "foo()" """) with self.assertRaises(Exception) as e: cel.NewEnv( config=config, functions=[ cel.FunctionDecl( - "bar", + "foo", [ cel.Overload( - "unique_id", + signature="foo()", impl=lambda: "hello", ) ], ) ], function_impls={ - "unique_id": lambda: "goodbye", + "foo()": lambda: "goodbye", }, ) self.assertIn( - "An implementation for function overload id 'unique_id' already" - " exists.", + "An implementation for function overload 'foo()' already exists.", str(e.exception), ) + def test_overload_signature_errors(self): + with self.assertRaises(ValueError) as e2: + cel.Overload(signature="greet(string)", parameters=[cel.Type.STRING]) + self.assertIn( + "If 'signature' is specified, 'parameters' should not be specified", + str(e2.exception), + ) + + with self.assertRaises(ValueError) as e3: + cel.Overload() + self.assertIn( + "Either 'id' or 'signature' must be specified", str(e3.exception) + ) + + def test_config_functions_deprecated_syntax(self): + """Test that the deprecated function syntax is still supported.""" + config: cel.EnvConfig = cel.NewEnvConfigFromYaml(""" + functions: + - name: is_ok + overloads: + - id: "is_ok_string" + target: + type_name: string + return: + type_name: bool + """) + env: cel.Env = cel.NewEnv( + config=config, + functions=[ + cel.FunctionDecl( + "hello", + [ + cel.Overload( + "good_time_of_day", + return_type=cel.Type.STRING, + parameters=[ + cel.Type.STRING, + cel.Type.STRING, + ], + impl=lambda ampm, arg: ( + "Good" + f" {'morning' if ampm == 'am' else 'afternoon'}," + f" {arg}!" + ), + ) + ], + ) + ], + function_impls={ + "is_ok_string": lambda arg: arg in ["excellent", "good", "fair"], + }, + ) + yaml = env.config().to_yaml() + self.assertEqual( + normalize_yaml(yaml), + normalize_yaml(""" + functions: + - name: "hello" + overloads: + - id: "good_time_of_day" + signature: "hello(string,string)" + return: "string" + - name: "is_ok" + overloads: + - id: "is_ok_string" + signature: "string.is_ok()" + return: "bool" + """), + ) + res: cel.Value = env.compile("hello('am', 'Sunshine')").eval() + self.assertEqual(res.value(), "Good morning, Sunshine!") + res = env.compile("hello('pm', 'tea is served')").eval() + self.assertEqual(res.value(), "Good afternoon, tea is served!") + res = env.compile("'good'.is_ok()").eval() + self.assertTrue(res.value()) + res = env.compile("'bad'.is_ok()").eval() + self.assertFalse(res.value()) + class TestCelExtension(cel.CelExtension): """An example CEL extension for testing.""" diff --git a/cel_expr_python/cel_test.py b/cel_expr_python/cel_test.py index f812fe4..938db60 100644 --- a/cel_expr_python/cel_test.py +++ b/cel_expr_python/cel_test.py @@ -668,6 +668,18 @@ def testTypeType(self): res.value(), cel.Type.INT ) # This behavior is counterintuitive but works as implemented. + def testTypeInitSignature(self): + self.assertEqual(cel.Type("int"), cel.Type.INT) + self.assertEqual(cel.Type("list"), cel.Type.List(cel.Type.INT)) + self.assertEqual( + cel.Type("map"), + cel.Type.Map(cel.Type.STRING, cel.Type.DYN), + ) + self.assertEqual( + cel.Type("cel.expr.conformance.proto2.TestAllTypes"), + cel.Type("cel.expr.conformance.proto2.TestAllTypes"), + ) + def testCelExpressionPersistence_checkedExpr(self): expr: cel.Expression = self.env.compile("var_msg.single_string") as_bytes: bytes = expr.serialize() diff --git a/cel_expr_python/py_cel_env_config.cc b/cel_expr_python/py_cel_env_config.cc index ff111d8..8ea330c 100644 --- a/cel_expr_python/py_cel_env_config.cc +++ b/cel_expr_python/py_cel_env_config.cc @@ -37,7 +37,7 @@ void PyCelEnvConfig::DefinePythonBindings(pybind11::module& m) { &PyCelEnvConfig::GetContextType); } -PyCelEnvConfig PyCelEnvConfig::FromYaml(std::string yaml) { +PyCelEnvConfig PyCelEnvConfig::FromYaml(const std::string& yaml) { PyCelEnvConfig config; config.config_ = ThrowIfError(cel::EnvConfigFromYaml(yaml)); return config; @@ -45,7 +45,7 @@ PyCelEnvConfig PyCelEnvConfig::FromYaml(std::string yaml) { std::string PyCelEnvConfig::ToYaml() const { std::stringstream ss; - cel::EnvConfigToYaml(config_, ss); + cel::EnvConfigToYaml(config_, ss, {.use_type_signatures = true}); return ss.str(); } diff --git a/cel_expr_python/py_cel_env_config.h b/cel_expr_python/py_cel_env_config.h index 9d93bb8..ed30ea2 100644 --- a/cel_expr_python/py_cel_env_config.h +++ b/cel_expr_python/py_cel_env_config.h @@ -30,7 +30,7 @@ class PyCelEnvConfig { PyCelEnvConfig() = default; explicit PyCelEnvConfig(const cel::Config& config) : config_(config) {} - static PyCelEnvConfig FromYaml(std::string yaml); + static PyCelEnvConfig FromYaml(const std::string& yaml); std::string ToYaml() const; const cel::Config& GetConfig() const { return config_; } diff --git a/cel_expr_python/py_cel_env_internal.cc b/cel_expr_python/py_cel_env_internal.cc index a591580..af0391b 100644 --- a/cel_expr_python/py_cel_env_internal.cc +++ b/cel_expr_python/py_cel_env_internal.cc @@ -202,11 +202,11 @@ PyCelEnvInternal::NewCelEnvInternal( if (overload.py_function().is_none()) { continue; } - if (!impls.insert({overload.overload_id(), overload.py_function()}) - .second) { + std::string overload_id = overload.overload_id(); + if (!impls.insert({overload_id, overload.py_function()}).second) { return absl::AlreadyExistsError( - absl::StrCat("An implementation for function overload id '", - overload.overload_id(), "' already exists.")); + absl::StrCat("An implementation for function overload '", + overload_id, "' already exists.")); } } } @@ -214,8 +214,8 @@ PyCelEnvInternal::NewCelEnvInternal( for (const auto& [overload_id, py_function] : function_impls) { if (!impls.insert({overload_id, py_function}).second) { return absl::AlreadyExistsError( - absl::StrCat("An implementation for function overload id '", - overload_id, "' already exists.")); + absl::StrCat("An implementation for function overload '", overload_id, + "' already exists.")); } } return std::shared_ptr( diff --git a/cel_expr_python/py_cel_function_decl.cc b/cel_expr_python/py_cel_function_decl.cc index b5f084b..382fdae 100644 --- a/cel_expr_python/py_cel_function_decl.cc +++ b/cel_expr_python/py_cel_function_decl.cc @@ -16,10 +16,13 @@ #include #include +#include #include #include "env/config.h" +#include "env/type_info.h" #include "cel_expr_python/py_cel_overload.h" +#include "cel_expr_python/py_cel_type.h" #include #include diff --git a/cel_expr_python/py_cel_overload.cc b/cel_expr_python/py_cel_overload.cc index 5311f46..0d0474d 100644 --- a/cel_expr_python/py_cel_overload.cc +++ b/cel_expr_python/py_cel_overload.cc @@ -20,8 +20,11 @@ #include #include +#include "common/signature.h" #include "env/config.h" +#include "env/type_info.h" #include "cel_expr_python/py_cel_type.h" +#include "cel_expr_python/py_error_status.h" #include #include @@ -31,16 +34,48 @@ namespace py = ::pybind11; void PyCelOverload::DefinePythonBindings(py::module_& m) { py::class_>(m, "Overload") - .def(py::init([](const std::string& overload_id, + .def(py::init([](std::optional id, const PyCelType& return_type, const std::vector& parameters, bool is_member, - py::object impl) { - return PyCelOverload(overload_id, return_type, parameters, - is_member, std::move(impl)); + py::object impl, std::optional signature) { + if (signature.has_value()) { + if (!parameters.empty()) { + throw py::value_error( + "If 'signature' is specified, 'parameters' " + "should not be specified"); + } + cel::ParsedFunctionOverload parsed = cel_python::ThrowIfError( + cel::ParseFunctionSignature(*signature)); + std::string overload_id = + id.has_value() ? std::move(*id) : *signature; + std::vector parsed_parameters; + if (parsed.signature_type.has_function()) { + const auto& function_type_spec = + parsed.signature_type.function(); + parsed_parameters.reserve( + function_type_spec.arg_types().size()); + for (const auto& arg : function_type_spec.arg_types()) { + parsed_parameters.push_back(PyCelType::FromTypeInfo( + cel_python::ThrowIfError(cel::TypeSpecToTypeInfo(arg)))); + } + } + return PyCelOverload(std::move(overload_id), return_type, + std::move(parsed_parameters), + parsed.is_member, std::move(impl)); + } else { + if (!id.has_value()) { + throw py::value_error( + "Either 'id' or 'signature' must be specified"); + } + return PyCelOverload(std::move(*id), return_type, parameters, + is_member, std::move(impl)); + } }), - py::arg("overload_id"), py::arg("return_type") = PyCelType::Dyn(), + py::arg("id") = std::nullopt, + py::arg("return_type") = PyCelType::Dyn(), py::arg("parameters") = std::vector{}, - py::arg("is_member") = false, py::arg("impl") = py::none()); + py::arg("is_member") = false, py::arg("impl") = py::none(), + py::arg("signature") = std::nullopt); } PyCelOverload::PyCelOverload(std::string overload_id, diff --git a/cel_expr_python/py_cel_type.cc b/cel_expr_python/py_cel_type.cc index 0cf7b54..8944509 100644 --- a/cel_expr_python/py_cel_type.cc +++ b/cel_expr_python/py_cel_type.cc @@ -30,6 +30,7 @@ #include "absl/strings/str_format.h" #include "absl/strings/str_join.h" #include "common/kind.h" +#include "common/signature.h" #include "common/type.h" #include "common/type_kind.h" #include "common/types/list_type.h" @@ -37,6 +38,7 @@ #include "common/value.h" #include "common/value_kind.h" #include "env/config.h" +#include "env/type_info.h" #include "cel_expr_python/py_error_status.h" #include "cel_expr_python/status_macros.h" #include "google/protobuf/arena.h" @@ -50,7 +52,14 @@ namespace py = ::pybind11; void PyCelType::DefinePythonBindings(py::module& m) { py::class_ type(m, "Type"); - type.def(py::init(), py::arg("name")) + type.def(py::init([](const std::string& signature) { + cel::TypeSpec type_spec = + cel_python::ThrowIfError(cel::ParseTypeSpec(signature)); + cel::Config::TypeInfo type_info = + cel_python::ThrowIfError(cel::TypeSpecToTypeInfo(type_spec)); + return PyCelType::FromTypeInfo(type_info); + }), + py::arg("signature")) .def("name", &PyCelType::GetName) .def("is_message", &PyCelType::IsMessage) .def("is_assignable_from", &PyCelType::IsAssignableFrom) @@ -526,6 +535,47 @@ PyCelType PyCelType::FromTypeProto(const cel::expr::Type& type) { return PyCelType::Error(); } +PyCelType PyCelType::FromTypeInfo(const cel::Config::TypeInfo& type_info) { + if (type_info.name == "null") return PyCelType::Null(); + if (type_info.name == "bool") return PyCelType::Bool(); + if (type_info.name == "int") return PyCelType::Int(); + if (type_info.name == "uint") return PyCelType::Uint(); + if (type_info.name == "double") return PyCelType::Double(); + if (type_info.name == "string") return PyCelType::String(); + if (type_info.name == "bytes") return PyCelType::Bytes(); + if (type_info.name == "timestamp") return PyCelType::Timestamp(); + if (type_info.name == "duration") return PyCelType::Duration(); + if (type_info.name == "dyn") return PyCelType::Dyn(); + if (type_info.name == "list") { + if (type_info.params.empty()) { + return PyCelType::List(); + } + return PyCelType::ListType(FromTypeInfo(type_info.params[0])); + } + if (type_info.name == "map") { + if (type_info.params.size() < 2) { + return PyCelType::Map(); + } + return PyCelType::MapType(FromTypeInfo(type_info.params[0]), + FromTypeInfo(type_info.params[1])); + } + if (type_info.name == "type") { + if (type_info.params.empty()) { + return PyCelType::Type(); + } + return PyCelType::TypeType(FromTypeInfo(type_info.params[0])); + } + if (type_info.is_type_param || !type_info.params.empty()) { + std::vector params; + params.reserve(type_info.params.size()); + for (const auto& param : type_info.params) { + params.push_back(FromTypeInfo(param)); + } + return PyCelType::AbstractType(type_info.name, params); + } + return PyCelType(type_info.name); +} + absl::StatusOr PyCelType::ToCelType( const PyCelType& type, google::protobuf::Arena* arena, const google::protobuf::DescriptorPool& descriptor_pool) { diff --git a/cel_expr_python/py_cel_type.h b/cel_expr_python/py_cel_type.h index 892c488..19cac0e 100644 --- a/cel_expr_python/py_cel_type.h +++ b/cel_expr_python/py_cel_type.h @@ -77,6 +77,7 @@ class PyCelType { static PyCelType ForCelValue(const cel::Value& cel_value); static PyCelType FromCelType(const cel::Type& cel_type); static PyCelType FromTypeProto(const cel::expr::Type& type); + static PyCelType FromTypeInfo(const cel::Config::TypeInfo& type_info); static absl::StatusOr ToCelType( const PyCelType& type, google::protobuf::Arena* arena, const google::protobuf::DescriptorPool& descriptor_pool); diff --git a/codelab/index.lab.md b/codelab/index.lab.md index eb5ef2f..5ca5fba 100644 --- a/codelab/index.lab.md +++ b/codelab/index.lab.md @@ -846,14 +846,8 @@ def exercise5(): "contains", [ cel.Overload( - "contains_key_value", + signature="map.contains(string, dyn)", return_type=cel.Type.BOOL, - parameters=[ - cel.Type.Map(cel.Type.STRING, cel.Type.DYN), - cel.Type.STRING, - cel.Type.DYN, - ], - is_member=True, # Provide the implementation as a Python function ) ], @@ -922,14 +916,8 @@ def exercise5(): "contains", [ cel.Overload( - "contains_string_any", + signature="map.contains(string, dyn)", return_type=cel.Type.BOOL, - parameters=[ - cel.Type.Map(cel.Type.STRING, cel.Type.DYN), - cel.Type.STRING, - cel.Type.DYN, - ], - is_member=True, # Reference a Python function impl=contains_key_value, ) @@ -1319,7 +1307,7 @@ def exercise7(): # Add variable definitions for 'jwt' as a map(string, Dyn) type # and for 'now' as a timestamp. variables={ - "jwt": cel.Type.Map(cel.Type.STRING, cel.Type.DYN), + "jwt": cel.Type("map"), "now": cel.Type.TIMESTAMP, }, ) diff --git a/codelab/solution/codelab.py b/codelab/solution/codelab.py index 15d12c0..861f0ef 100644 --- a/codelab/solution/codelab.py +++ b/codelab/solution/codelab.py @@ -188,14 +188,11 @@ def exercise5(): "containsKeyValue", [ cel.Overload( - "contains_key_value", + signature=( + "map.containsKeyValue(string," + " dyn)" + ), return_type=cel.Type.BOOL, - parameters=[ - cel.Type.Map(cel.Type.STRING, cel.Type.DYN), - cel.Type.STRING, - cel.Type.DYN, - ], - is_member=True, impl=contains_key_value, ) ], @@ -323,7 +320,7 @@ def exercise7(): # Add variable definitions for 'jwt' as a map(string, Dyn) type # and for 'now' as a timestamp. variables={ - "jwt": cel.Type.Map(cel.Type.STRING, cel.Type.DYN), + "jwt": cel.Type("map"), "now": cel.Type.TIMESTAMP, }, )