Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into freider/io-streams-types
Browse files Browse the repository at this point in the history
  • Loading branch information
freider committed Nov 19, 2024
2 parents 4728708 + ed5ccfb commit a044af4
Show file tree
Hide file tree
Showing 10 changed files with 87 additions and 40 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ We appreciate your patience while we speedily work towards a stable release of t

<!-- NEW CONTENT GENERATED BELOW. PLEASE PRESERVE THIS COMMENT. -->

### 0.66.12 (2024-11-19)

`Sandbox.exec` now accepts arguments `text` and `bufsize` for streaming output, which controls text output and line buffering.



### 0.66.0 (2024-11-15)

- Modal no longer supports Python 3.8, which has reached its [official EoL](https://devguide.python.org/versions/).
Expand Down
12 changes: 12 additions & 0 deletions modal/_clustered_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,18 @@ def get_i6pn():
# See MOD-4067.
os.environ["NCCL_HOSTID"] = f"{hostname}{container_ip}"

# We found these settings to work well in most cases. You may be able to achieve
# better performance by tuning these settings.
if os.environ["MODAL_CLOUD_PROVIDER"] in ("CLOUD_PROVIDER_GCP", "CLOUD_PROVIDER_OCI"):
os.environ["NCCL_SOCKET_NTHREADS"] = "4"
os.environ["NCCL_NSOCKS_PERTHREAD"] = "1"
elif os.environ["MODAL_CLOUD_PROVIDER"] == "CLOUD_PROVIDER_AWS":
os.environ["NCCL_SOCKET_NTHREADS"] = "2"
os.environ["NCCL_NSOCKS_PERTHREAD"] = "8"
else:
os.environ["NCCL_SOCKET_NTHREADS"] = "1"
os.environ["NCCL_NSOCKS_PERTHREAD"] = "1"

if world_size > 1:
resp: api_pb2.TaskClusterHelloResponse = await retry_transient_errors(
client.stub.TaskClusterHello,
Expand Down
1 change: 1 addition & 0 deletions modal/cls.py
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,7 @@ async def lookup(
await resolver.load(obj)
return obj

@synchronizer.no_input_translation
def __call__(self, *args, **kwargs) -> _Obj:
"""This acts as the class constructor."""
return _Obj(
Expand Down
17 changes: 3 additions & 14 deletions modal/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
_parent: Optional["_Function"] = None

_class_parameter_info: Optional["api_pb2.ClassParameterInfo"] = None
_method_handle_metadata: Optional[Dict[str, "api_pb2.FunctionHandleMetadata"]] = None
_method_functions: Optional[Dict[str, "_Function"]] = None # Placeholder _Functions for each method

def _bind_method(
Expand Down Expand Up @@ -1261,6 +1262,7 @@ def _hydrate_metadata(self, metadata: Optional[Message]):
self._use_function_id = metadata.use_function_id
self._use_method_name = metadata.use_method_name
self._class_parameter_info = metadata.class_parameter_info
self._method_handle_metadata = dict(metadata.method_handle_metadata)
self._definition_id = metadata.definition_id

def _invocation_function_id(self) -> str:
Expand All @@ -1278,20 +1280,7 @@ def _get_metadata(self):
is_method=self._is_method,
class_parameter_info=self._class_parameter_info,
definition_id=self._definition_id,
method_handle_metadata={
method_name: api_pb2.FunctionHandleMetadata(
function_name=method_function._function_name,
function_type=get_function_type(method_function._is_generator),
web_url=method_function._web_url or "",
is_method=method_function._is_method,
definition_id=method_function._definition_id,
use_method_name=method_function._use_method_name,
)
for method_name, method_function in self._method_functions.items()
if method_function._function_name
}
if self._method_functions
else None,
method_handle_metadata=self._method_handle_metadata,
)

def _check_no_web_url(self, fn_name: str):
Expand Down
14 changes: 10 additions & 4 deletions modal/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -1222,7 +1222,10 @@ def from_gcp_artifact_registry(
```python
modal.Image.from_gcp_artifact_registry(
"us-east1-docker.pkg.dev/my-project-1234/my-repo/my-image:my-version",
secret=modal.Secret.from_name("my-gcp-secret"),
secret=modal.Secret.from_name(
"my-gcp-secret",
required_keys=["SERVICE_ACCOUNT_JSON"],
),
add_python="3.11",
)
```
Expand Down Expand Up @@ -1251,8 +1254,8 @@ def from_aws_ecr(
) -> "_Image":
"""Build a Modal image from a private image in AWS Elastic Container Registry (ECR).
You will need to pass a `modal.Secret` containing an AWS key (`AWS_ACCESS_KEY_ID`) and
secret (`AWS_SECRET_ACCESS_KEY`) with permissions to access the target ECR registry.
You will need to pass a `modal.Secret` containing `AWS_ACCESS_KEY_ID`,
`AWS_SECRET_ACCESS_KEY`, and `AWS_REGION` to access the target ECR registry.
IAM configuration details can be found in the AWS documentation for
["Private repository policies"](https://docs.aws.amazon.com/AmazonECR/latest/userguide/repository-policies.html).
Expand All @@ -1264,7 +1267,10 @@ def from_aws_ecr(
```python
modal.Image.from_aws_ecr(
"000000000000.dkr.ecr.us-east-1.amazonaws.com/my-private-registry:my-version",
secret=modal.Secret.from_name("aws"),
secret=modal.Secret.from_name(
"aws",
required_keys=["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_REGION"],
),
add_python="3.11",
)
```
Expand Down
13 changes: 1 addition & 12 deletions modal_proto/api.proto
Original file line number Diff line number Diff line change
Expand Up @@ -305,17 +305,6 @@ message AppDeployResponse {
string url = 1;
}

message AppDeploySingleObjectRequest {
string name = 1;
DeploymentNamespace namespace = 2;
string environment_name = 3;
string object_id = 4;
}

message AppDeploySingleObjectResponse {
string app_id = 1;
}

message AppDeploymentHistory {
string app_id = 1;
uint32 version = 2;
Expand Down Expand Up @@ -612,6 +601,7 @@ message CheckpointInfo {
string checkpoint_id = 3;
string runtime_fingerprint = 4;
int64 size = 5;
bool checksum_is_file_index = 6;
}

message ClassCreateRequest {
Expand Down Expand Up @@ -2655,7 +2645,6 @@ service ModalClient {
rpc AppClientDisconnect(AppClientDisconnectRequest) returns (google.protobuf.Empty);
rpc AppCreate(AppCreateRequest) returns (AppCreateResponse);
rpc AppDeploy(AppDeployRequest) returns (AppDeployResponse);
rpc AppDeploySingleObject(AppDeploySingleObjectRequest) returns (AppDeploySingleObjectResponse);
rpc AppDeploymentHistory(AppDeploymentHistoryRequest) returns (AppDeploymentHistoryResponse);
rpc AppGetByDeploymentName(AppGetByDeploymentNameRequest) returns (AppGetByDeploymentNameResponse);
rpc AppGetLogs(AppGetLogsRequest) returns (stream TaskLogsBatch);
Expand Down
2 changes: 1 addition & 1 deletion modal_version/_version_generated.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright Modal Labs 2024

# Note: Reset this value to -1 whenever you make a minor `0.X` release of the client.
build_number = 4 # git: eb0a88c
build_number = 12 # git: ff1530a
7 changes: 6 additions & 1 deletion tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,12 @@ def get_wrapped_types(module_name: str) -> List[str]:
@task
def update_changelog(ctx, sha: str = ""):
# Parse a commit message for a GitHub PR number, defaulting to most recent commit
res = ctx.run(f"git log --pretty=format:%s -n 1 {sha}", hide="stdout")
res = ctx.run(f"git log --pretty=format:%s -n 1 {sha}", hide="stdout", warn=True)
if res.exited:
print("Failed to extract changelog update!")
print("Last 5 commits:")
res = ctx.run("git log --pretty=oneline -n 5")
return
m = re.search(r"\(#(\d+)\)$", res.stdout)
if m:
pull_number = m.group(1)
Expand Down
17 changes: 17 additions & 0 deletions test/cls_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1007,3 +1007,20 @@ class D:
@app.function(serialized=True)
def f(self):
pass


def test_modal_object_param_uses_wrapped_type(servicer, set_env_client, client):
with servicer.intercept() as ctx:
with modal.Dict.ephemeral() as dct:
with baz_app.run():
# create bound instance:
typing.cast(modal.Cls, Baz(x=dct)).keep_warm(1)

req: api_pb2.FunctionBindParamsRequest = ctx.pop_request("FunctionBindParams")
function_def: api_pb2.Function = servicer.app_functions[req.function_id]
from modal._container_entrypoint import deserialize_params

_client = typing.cast(modal.client._Client, synchronizer._translate_in(client))
container_params = deserialize_params(req.serialized_params, function_def, _client)
args, kwargs = container_params
assert type(kwargs["x"]) == type(dct)
38 changes: 30 additions & 8 deletions test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,16 @@ def get_function_metadata(self, object_id: str) -> api_pb2.FunctionHandleMetadat
is_method=definition.is_method,
use_method_name=definition.use_method_name,
use_function_id=definition.use_function_id,
method_handle_metadata={
method_name: api_pb2.FunctionHandleMetadata(
function_name=method_definition.function_name,
function_type=method_definition.function_type,
web_url=method_definition.web_url,
is_method=True,
use_method_name=method_name,
)
for method_name, method_definition in definition.method_definitions.items()
},
)

def get_class_metadata(self, object_id: str) -> api_pb2.ClassHandleMetadata:
Expand Down Expand Up @@ -908,7 +918,11 @@ async def FunctionPrecreate(self, stream):
# This loop is for class service functions, where req.method_definitions will be non-empty
method_handle_metadata: dict[str, api_pb2.FunctionHandleMetadata] = {}
for method_name, method_definition in req.method_definitions.items():
method_web_url = f"https://{method_name}.internal"
method_web_url = (
f"http://{method_name}.internal"
if method_definition.HasField("webhook_config") and method_definition.webhook_config.type
else None
)
method_handle_metadata[method_name] = api_pb2.FunctionHandleMetadata(
function_name=method_definition.function_name,
function_type=method_definition.function_type,
Expand Down Expand Up @@ -937,30 +951,28 @@ async def FunctionCreate(self, stream):
else:
self.n_functions += 1
function_id = f"fu-{self.n_functions}"

function: Optional[api_pb2.Function] = None
function_data: Optional[api_pb2.FunctionData] = None

if len(request.function_data.ranked_functions) > 0:
function_data = api_pb2.FunctionData()
function_data.CopyFrom(request.function_data)
if function_data.webhook_config.type:
function_data.web_url = "http://xyz.internal"
else:
assert request.function
function = api_pb2.Function()
function.CopyFrom(request.function)
if function.webhook_config.type:
function.web_url = "http://xyz.internal"

assert (function is None) != (function_data is None)
function_defn = function or function_data
assert function_defn
if function_defn.webhook_config.type:
function_defn.web_url = "http://xyz.internal"
for method_name, method_definition in function_defn.method_definitions.items():
if method_definition.webhook_config.type:
method_definition.web_url = f"http://{method_name}.internal"
self.app_functions[function_id] = function_defn

if function_defn.schedule:
self.function2schedule[function_id] = function_defn.schedule

await stream.send_message(
api_pb2.FunctionCreateResponse(
function_id=function_id,
Expand All @@ -972,6 +984,16 @@ async def FunctionCreate(self, stream):
use_function_id=function_defn.use_function_id or function_id,
use_method_name=function_defn.use_method_name,
definition_id=f"de-{self.n_functions}",
method_handle_metadata={
method_name: api_pb2.FunctionHandleMetadata(
function_name=method_definition.function_name,
function_type=method_definition.function_type,
web_url=method_definition.web_url,
is_method=True,
use_method_name=method_name,
)
for method_name, method_definition in function_defn.method_definitions.items()
},
),
)
)
Expand Down

0 comments on commit a044af4

Please sign in to comment.