Parameterized functions
A single Modal function can be parameterized by a set of arguments, so that each unique combination of arguments will behave like an individual Modal function with its own auto-scaling and lifecycle logic.
For example, you might want to have a separate pool of containers for each unique user that invokes your function. In this scenario, you would parameterize your function by a user ID.
To parameterize a Modal function, you need to use Modal’s class syntax and the
@app.cls
decorator. Specifically, you’ll
need to:
- Convert your function to a method by making it a member of a class.
- Decorate the class with
@app.cls(...)
with the same arguments you previously had for@app.function(...)
or your web endpoint decorator. - If you previously used the
@app.function()
decorator on your function, replace it with@modal.method()
. - Define dataclass-style, type-annotated instance attributes with
modal.parameter()
and optionally set default values:
import modal
app = modal.App()
@app.cls()
class MyClass:
foo: str = modal.parameter()
bar: int = modal.parameter(default=10)
@modal.method()
def baz(self, qux: str = "default") -> str:
return f"This code is running in container pool ({self.foo}, {self.bar}), with input qux={qux}"
The parameters create a keyword-only constructor for your class, and the methods can be called as follows:
@app.local_entrypoint()
def main():
m1 = MyClass(foo="hedgehog", bar=7)
m1.baz.remote()
m2 = MyClass(foo="fox")
m2.baz.remote(qux="override")
Function calls for each unique combination of values for foo
and bar
will run in their own separate container pools. If you re-constructed a MyClass
with the same arguments in a different
context, the calls to baz
would be routed to the same set of containers
as before.
Some things to note:
- The total size of the arguments is limited to 16 KiB.
- Modal classes can still annotate typed of regular class attributes, which are independent of parameterization, by either omitting
= modal.parameter()
or using= modal.parameter(init=False)
to satisfy type checkers. - Only
int
andstr
types for parameters are supported. Support for other types will be added in the future. If you need to pass a different type of argument, you can use the legacy method of specifying parameters, via__init__
and optional type hints:
@app.cls()
class MyClass:
def __init__(self, foo, bar: float = 10.3) -> None:
self.foo = foo
self.bar = bar
@modal.method()
def baz(self, qux: str = "default") -> str:
...
This legacy method will not work with parameterized web endpoints.
Looking up a parameterized function
If you want to call your parameterized function from a Python script running
anywhere, you can use Cls.lookup
:
import modal
MyClass = modal.Cls.lookup("parameterized-function-app", "MyClass") # returns a class-like object
m = MyClass(foo="snake", bar=12)
m.baz.remote()
Parameterized web endpoints
Modal web endpoints can also be parameterized by passing the arguments as URL query parameter values.
Given the same example as above but with a web endpoint:
@app.cls()
class MyClass():
foo: str = modal.parameter()
bar: int = modal.parameter(default=10)
@modal.web_endpoint()
def baz(self, qux: str = "default") -> str:
...
Here are some cURL commands that would work:
curl "https://parameterized-function-app.modal.run?foo=hedgehog&bar=7&qux=override"
curl "https://parameterized-function-app.modal.run?foo=hedgehog&qux=override"
curl "https://parameterized-function-app.modal.run?foo=hedgehog&bar=7"
curl "https://parameterized-function-app.modal.run?foo=hedgehog"
Using parameterized functions with lifecycle functions
Parameterized functions can be used with lifecycle functions. For example, here is how you might parameterize the @enter
lifecycle function to load a specific model:
import modal
app = modal.App()
@app.cls()
class Model:
name: str = modal.parameter()
size: int = modal.parameter(default=100)
@modal.enter()
def load_model(self):
print(f"Loading model {self.name} with size {self.size}")
self.model = load_model_util(self.name, self.size)
@modal.method()
def generate(self, prompt: str) -> str:
return self.model.generate(prompt)