“Command not found” errors
If you installed Modal but you’re seeing an error like
modal: command not found when trying to run the CLI, this means that the
installation location of Python package executables (“binaries”) are not present
on your system path. This is a common problem; you need to reconfigure your
system’s environment variables to fix it.
One workaround is to use
python -m modal.cli instead of
modal. However, this
is just a patch. There’s no single solution for the problem because Python
installs dependencies on different locations depending on your environment. See
this popular StackOverflow question for
pointers on how to resolve your system path issue.
Custom types defined in
Modal currently uses
to transfer objects returned or exceptions raised by functions that are executed
in Modal. This gives a lot of flexibility and support for custom data types.
However, any types that are declared in your Python entrypoint file (The one you call on the command line) will currently be redeclared if they are returned from Modal functions, and will therefore have the same structure and type name but not maintain class object identity with your local types. This means that you can’t catch specific custom exception classes:
import modal stub = modal.Stub() class MyException(Exception): pass def raise_custom(): raise MyException() if __name__ == "__main__": with stub.run(): try: raise_custom() except MyException: # this will not catch the remote exception pass except Exception: # this will catch it instead, as it's still a subclass of Exception pass
Nor can you do object equality checks on
import modal import dataclasses class MyType: foo: int stub = modal.Stub() def return_custom(): return MyType(foo=10) if __name__ == "__main__": with stub.run(): data = return_custom() assert data == MyType(foo=10) # false! assert data.foo == 10 # true!, the type still has the same fields etc. assert isinstance(data, MyType) # false!
If this is a problem for you, you can easily solve it by moving your custom type definitions to a separate Python file from the one you trigger to run your Modal code, and import that file instead.
# File: my_types.py import dataclasses class MyType: foo: int
# File: modal_script.py import modal from my_types import MyType stub = modal.Stub() def return_custom(): return MyType(foo=10) if __name__ == "__main__": with stub.run(): data = return_custom() assert data == MyType(foo=10) # true! assert isinstance(data, MyType) # true!
Function side effects
The same container can be reused for multiple invocations of the same function within an app. This means that if your function has side effects like modifying files on disk, they may or may not be present for subsequent calls to that function. You should not rely on the side effects to be present, but you might have to be careful so they don’t cause problems.
For example, if you create a disk-backed database using sqlite3:
import modal import sqlite3 stub = modal.Stub() def db_op(): db = sqlite3("db_file.sqlite3") db.execute("CREATE TABLE example (col_1 TEXT)") ...
This function can (but will not necessarily) fail on the second invocation with an
OperationalError: table foo already exists
To get around this, take care to either clean up your side effects (e.g.
deleting the db file at the end your function call above) or make your functions
take them into consideration (e.g. adding an
if os.path.exists("db_file.sqlite") condition or randomize the filename