diff --git a/modal/functions.py b/modal/functions.py index f8ee3ca0c..89289abbe 100644 --- a/modal/functions.py +++ b/modal/functions.py @@ -680,11 +680,18 @@ def from_parametrized( """mdmd:hidden""" async def _load(self: _Function, resolver: Resolver, existing_object_id: Optional[str]): + try: + identity = f"base {self._parent.info.function_name} function" + except Exception: + # Can't always look up the function name that way, so fall back to generic message + identity = "base function for parameterized class" if not self._parent.is_hydrated: + if self._parent.app._running_app is None: + reason = ", because the App it is defined on is not running." + else: + reason = "" raise ExecutionError( - "Base function in class has not been hydrated. This might happen if an object is" - " defined on a different stub, or if it's on the same stub but it didn't get" - " created because it wasn't defined in global scope." + f"The {identity} has not been hydrated with the metadata it needs to run on Modal{reason}." ) assert self._parent._client.stub serialized_params = serialize((args, kwargs)) diff --git a/modal/object.py b/modal/object.py index d2c047c10..26f7c9b45 100644 --- a/modal/object.py +++ b/modal/object.py @@ -204,11 +204,15 @@ async def resolve(self): if self._is_hydrated: return elif not self._hydrate_lazily: + object_type = self.__class__.__name__.strip("_") + if hasattr(self, "_app") and getattr(self._app, "_running_app", "") is None: + # The most common cause of this error: e.g., user called a Function without using App.run() + reason = ", because the App it is defined on is not running." + else: + # Technically possible, but with an ambiguous cause. + reason = "" raise ExecutionError( - "Object has not been hydrated and doesn't support lazy hydration." - " This might happen if an object is defined on a different stub," - " or if it's on the same stub but it didn't get created because it" - " wasn't defined in global scope." + f"{object_type} has not been hydrated with the metadata it needs to run on Modal{reason}." ) else: # TODO: this client and/or resolver can't be changed by a caller to X.from_name()