[FLINK-22242][python] Improve missing attribute error on GeneratedAddressedScopedStorage

This closes #223
diff --git a/statefun-sdk-python/statefun/request_reply_v3.py b/statefun-sdk-python/statefun/request_reply_v3.py
index 3eb258f..fa05253 100644
--- a/statefun-sdk-python/statefun/request_reply_v3.py
+++ b/statefun-sdk-python/statefun/request_reply_v3.py
@@ -209,7 +209,7 @@
         if not target_fn:
             raise ValueError(f"Unable to find a function of type {sdk_address.typename}")
         # resolve state
-        res = resolve(target_fn.storage_spec, pb_to_function.invocation.state)
+        res = resolve(target_fn.storage_spec, sdk_address.typename, pb_to_function.invocation.state)
         if res.missing_specs:
             pb_from_function = collect_failure(res.missing_specs)
             return pb_from_function.SerializeToString()
diff --git a/statefun-sdk-python/statefun/storage.py b/statefun-sdk-python/statefun/storage.py
index 0eafe94..21a49fe 100644
--- a/statefun-sdk-python/statefun/storage.py
+++ b/statefun-sdk-python/statefun/storage.py
@@ -68,8 +68,13 @@
 # self.cells: typing.Dict[str, Cell] = {name: Cell(name, tpe, vals[name]) for name, tpe in types.items()}
 
 
-def storage_constructor(self, cells: typing.Dict[str, Cell]):
+def storage_constructor(self, cells: typing.Dict[str, Cell], typename: str):
     self._cells = cells
+    self._typename = typename
+
+
+def storage_missing_attribute(self, attr):
+    raise AttributeError("'{}' is not a registered ValueSpec for the function '{}'".format(attr, self._typename))
 
 
 def property_named(name):
@@ -109,7 +114,10 @@
     :param specs: a list of specs as supplied by the user.
     :return: a StorageSpec.
     """
-    props = {"__init__": storage_constructor, "__slots__": ["_cells"]}
+    props = {
+        "__init__": storage_constructor,
+        "__getattr__": storage_missing_attribute,
+        "__slots__": ["_cells", "_typename"]}
     for spec in specs:
         if spec.name in props:
             raise ValueError("duplicate registered value name: " + spec.name)
@@ -121,11 +129,13 @@
 
 
 def resolve(storage: StorageSpec,
+            typename: str,
             values: typing.List[ToFunction.PersistedValue]) -> Resolution:
     """
     Resolve the registered specs and the actually received values.
 
     :param storage: a storage factory
+    :param typename: the typename of the function under invocation
     :param values: the actually received values
     :return: a Resolution result, that might have either a list of missing specs
     (specs that were defined by the user but didn't arrived from StateFun) or a
@@ -148,5 +158,5 @@
     else:
         cells: typing.Dict[str, Cell] = {spec.name: Cell(tpe=spec.type, typed_value=received[spec.name]) for spec in
                                          storage.specs}
-        s = storage.make_instance(cells)
+        s = storage.make_instance(cells, typename)
         return Resolution(missing_specs=None, storage=s)
diff --git a/statefun-sdk-python/tests/storage_test.py b/statefun-sdk-python/tests/storage_test.py
index 2e99cd2..dd4a9ed 100644
--- a/statefun-sdk-python/tests/storage_test.py
+++ b/statefun-sdk-python/tests/storage_test.py
@@ -39,7 +39,7 @@
         values = [PbPersistedValueLike("a", 1, IntType), PbPersistedValueLike("b", "hello", StringType)]
 
         # resolve spec and values
-        resolution = resolve(storage_spec, values)
+        resolution = resolve(storage_spec, "example/func", values)
         store = resolution.storage
 
         self.assertEqual(store.a, 1)
@@ -53,7 +53,7 @@
         values = []
 
         # resolve spec and values
-        resolution = resolve(storage_spec, values)
+        resolution = resolve(storage_spec, "example/func", values)
         self.assertListEqual(resolution.missing_specs, specs)
 
     def test_partial_failed_resolution(self):
@@ -64,7 +64,7 @@
         values = [PbPersistedValueLike("a", 1, IntType)]
 
         # resolve spec and values
-        resolution = resolve(storage_spec, values)
+        resolution = resolve(storage_spec, "example/func", values)
         self.assertListEqual(resolution.missing_specs, specs[1:])
 
     def test_ignore_unknown(self):
@@ -75,7 +75,7 @@
         values = [PbPersistedValueLike("a", 1, IntType), PbPersistedValueLike("b", "hello", StringType)]
 
         # resolve spec and values
-        resolution = resolve(storage_spec, values)
+        resolution = resolve(storage_spec, "example/func", values)
         store = resolution.storage
 
         self.assertEqual(store.a, 1)
@@ -157,5 +157,5 @@
         else:
             vals.append(arg)
     storage_spec = make_address_storage_spec(specs)
-    resolution = resolve(storage_spec, vals)
+    resolution = resolve(storage_spec, "example/func", vals)
     return resolution.storage