Skip to content

Conversation

freider
Copy link
Contributor

@freider freider commented Oct 8, 2025

This will now return a PartialFunction instead.

Not sure if we should be adding some helper __getattr__ on PartialFunction to help users who will undoubtedly still make this mistake, thinking they have gotten a Function they can use?


Compatibility checklist

Check these boxes or delete any item (or this section) if not relevant for this PR.

  • Client+Server: this change is compatible with old servers
  • Client forward compatibility: this change ensures client can accept data intended for later versions of itself

Note on protobuf: protobuf message changes in one place may have impact to
multiple entities (client, server, worker, database). See points above.


Release checklist

If you intend for this commit to trigger a full release to PyPI, please ensure that the following steps have been taken:

  • Version file (modal_version/__init__.py) has been updated with the next logical version
  • Changelog has been cleaned up and given an appropriate subhead

Changelog

  • It is no longer possible to invoke a method on a Modal Cls without "instantiating" the Cls first.

Note

Accessing methods on a Cls without instantiation now raises AttributeError (with guidance), while static class attributes remain accessible; tests updated and expanded for local, hydrated, and remote cases.

  • Core (modal/cls.py):
    • Replace deprecated class-level method access with strict erroring:
      • __getattr__ on Cls returns a synthetic Function that always raises AttributeError when methods are used, instructing to instantiate first.
      • Permit access to local static attributes on the underlying user class (non-PartialFunction).
  • Tests (test/cls_test.py):
    • Update test_using_method_on_uninstantiated_cls to expect AttributeError on class-level method usage and allow static attribute reads.
    • Add coverage for hydrated local (app.run) and remote (Cls.from_name) classes to assert the new error behavior.
    • Remove deprecation warning expectations related to class-level method access.

Written by Cursor Bugbot for commit 9622efc. This will update automatically on new commits. Configure here.

@freider freider requested a review from mwaskom October 8, 2025 08:02
cursor[bot]

This comment was marked as outdated.

@mwaskom
Copy link
Contributor

mwaskom commented Oct 8, 2025

Not sure if we should be adding some helper getattr on PartialFunction to help users who will undoubtedly still make this mistake, thinking they have gotten a Function they can use?

Do you mean so that something like

MyCls.run.remote()

Says something useful instead of just "PartialFunction has no attribute 'remote'"?

cursor[bot]

This comment was marked as outdated.

Copy link
Contributor

@mwaskom mwaskom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conditional on everything else, I like the shape of the solution here. But I do think this points to some ... broader issues.

# local class, we can check if there are static attributes and let the user access them
# except if they are PartialFunction (i.e. methods)
v = getattr(self._user_cls, k)
if not isinstance(v, modal.partial_function.PartialFunction):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😵‍💫 double checking that we wouldn't expect these to be the internal _PartialFunction type here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this is a bit tbh 😵 But @method is itself a synchronicity-wrapped method, so it will output wrapped types (PartialFunction in this case) that will be part of the class namespace of the underlying class, which is what we're getting here. It would break the tests if this wasn't so, so even if this was to change we would detect it in CI :)

modal/cls.py Outdated

return _Function._from_loader(
method_loader,
rep=f"Maybe({self._name}.{k})",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah this wasn't intended to be the final name :) What about UnboundMethod ?

test/cls_test.py Outdated
assert len(recwarn) == 0
# The following should warn since it's accessed on the class directly
with pytest.raises(AttributeError, match="Did you forget to instantiate the class first?"):
C.method.remote() # noqa # triggers a deprecation warning
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is there a deprecation warning still?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There isn't, but there is a comment about there being one :P Removing

# a user tries to use any of its "live methods" - this lets us raise exceptions for users
# only if they try to access methods on a Cls as if they were methods on the instance.
async def method_loader(fun: _Function, resolver: Resolver, existing_object_id: Optional[str]):
raise AttributeError(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what the right exception type is. Something to clean up eventually?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure AttributeError is right. For normal Python classes this is a TypeError: the method attribute exists, but it's not been bound and so the arglist is incomplete.

I'm still not sure exactly what error type to captures the modal-specific issue. It turns out that modal.Cls is really not exactly like a class....

@freider freider merged commit 4e509ba into main Oct 9, 2025
28 checks passed
@freider freider deleted the freider/remove-cls-method-reference-warning branch October 9, 2025 19:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants