11# Copyright Modal Labs 2025
22import inspect
33import typing
4- from typing import Any , Optional
5-
6- from google .protobuf .message import Message
4+ from typing import Optional
75
86from ._functions import _Function
97from ._load_context import LoadContext
108from ._object import live_method
119from ._partial_function import (
12- _find_callables_for_obj ,
1310 _find_partial_methods_for_user_cls ,
1411 _PartialFunction ,
1512 _PartialFunctionFlags ,
1613)
17- from ._utils .async_utils import synchronize_api , synchronizer
18- from ._utils .deprecation import warn_if_passing_namespace
14+ from ._utils .async_utils import synchronize_api
1915from .client import _Client
2016from .cls import is_parameter
2117from .exception import InvalidError
@@ -35,134 +31,29 @@ class _Server:
3531 TODO(claudia): Add examples
3632 """
3733
38- # Maps 1-1 w function
39- _type_prefix = "fu"
40-
41- _app : Optional ["modal.app._App" ] = None
42- _name : Optional [str ] = None
43- # Raw user defined class
44- _user_cls : Optional [type ] = None
45- # Instantiated raw user class
46- _user_cls_instance : Optional [Any ] = None
47- # Function interface with server backend
48- _service_function : Optional [_Function ] = None
49- _has_entered : bool = False
50-
51- def __init__ (self ):
52- self ._initialize_from_empty ()
53-
54- def _initialize_from_empty (self ):
55- self ._app = None
56- self ._name = None
57- self ._user_cls = None
58- self ._user_cls_instance = None
59- self ._service_function = None
60- self ._has_entered = False
61-
62- def _initialize_from_other (self , other : "_Server" ):
63- self ._app = other ._app
64- self ._name = other ._name
65- self ._user_cls = other ._user_cls
66- self ._user_cls_instance = other ._user_cls_instance
67- self ._service_function = other ._service_function
68- self ._has_entered = other ._has_entered
69-
70- def _get_user_cls (self ) -> type :
71- assert self ._user_cls is not None
72- return self ._user_cls
73-
74- def _get_name (self ) -> str :
75- assert self ._name is not None
76- return self ._name
34+ _user_cls : Optional [type ] = None # None if remote
35+ _service_function : _Function
36+ _app : Optional ["modal.app._App" ] = None # None if remote
7737
7838 def _get_app (self ) -> "modal.app._App" :
79- assert self ._app is not None
39+ assert self ._app , "app can only be extracted for local Server (in container entrypoint)"
8040 return self ._app
8141
82- @property
83- def __name__ (self ) -> str :
84- """Return the name of the server class for compatibility with code expecting class-like objects."""
85- return self ._name or ""
42+ def _get_user_cls (self ) -> type :
43+ assert self ._user_cls is not None
44+ return self ._user_cls
8645
8746 def _get_service_function (self ) -> _Function :
88- assert self ._service_function is not None
8947 return self ._service_function
9048
9149 @staticmethod
9250 def _extract_user_cls (wrapped_user_cls : "type | _PartialFunction" ) -> type :
9351 if isinstance (wrapped_user_cls , _PartialFunction ):
52+ assert wrapped_user_cls .user_cls , "Non-class partial used as app.server input"
9453 return wrapped_user_cls .user_cls
9554 else :
9655 return wrapped_user_cls
9756
98- # ============ Lifecycle Management ============
99-
100- def _get_or_create_user_cls_instance (self ) -> Any :
101- """Get or construct the local server instance."""
102- if self ._user_cls_instance is None :
103- assert self ._user_cls is not None
104- self ._user_cls_instance = object .__new__ (self ._user_cls )
105- return self ._user_cls_instance
106-
107- def _enter (self ):
108- """Run @enter lifecycle hooks (sync version)."""
109- assert self ._user_cls is not None
110- if self ._has_entered :
111- return
112-
113- user_cls_instance = self ._get_or_create_user_cls_instance ()
114-
115- # Support __enter__ context manager protocol
116- enter_method = getattr (user_cls_instance , "__enter__" , None )
117- if enter_method is not None :
118- enter_method ()
119-
120- # Run @modal.enter() decorated methods
121- for method_flag in (
122- _PartialFunctionFlags .ENTER_PRE_SNAPSHOT ,
123- _PartialFunctionFlags .ENTER_POST_SNAPSHOT ,
124- ):
125- for enter_method in _find_callables_for_obj (user_cls_instance , method_flag ).values ():
126- enter_method ()
127-
128- self ._has_entered = True
129-
130- @synchronizer .nowrap
131- async def _aenter (self ):
132- """Run @enter lifecycle hooks (async version)."""
133- assert self ._user_cls is not None
134- if self ._has_entered :
135- return
136-
137- user_cls_instance = self ._get_or_create_user_cls_instance ()
138-
139- aenter_method = getattr (user_cls_instance , "__aenter__" , None )
140- enter_method = getattr (user_cls_instance , "__enter__" , None )
141- if aenter_method is not None :
142- await aenter_method ()
143- elif enter_method is not None :
144- enter_method ()
145-
146- # Run @modal.enter() decorated methods
147- for method_flag in (
148- _PartialFunctionFlags .ENTER_PRE_SNAPSHOT ,
149- _PartialFunctionFlags .ENTER_POST_SNAPSHOT ,
150- ):
151- for enter_method in _find_callables_for_obj (user_cls_instance , method_flag ).values ():
152- res = enter_method ()
153- if inspect .iscoroutine (res ):
154- await res
155-
156- self ._has_entered = True
157-
158- @property
159- def _entered (self ) -> bool :
160- return self ._has_entered
161-
162- @_entered .setter
163- def _entered (self , val : bool ):
164- self ._has_entered = val
165-
16657 # ============ Live Methods ============
16758
16859 @live_method
@@ -196,10 +87,6 @@ async def update_autoscaler(
19687
19788 # ============ Hydration ============
19889
199- def _hydrate_metadata (self , metadata : Optional [Message ]):
200- service_function = self ._get_service_function ()
201- assert service_function .is_hydrated
202-
20390 async def hydrate (self , client : Optional [_Client ] = None ) -> "_Server" :
20491 """Hydrate the server by hydrating its underlying service function."""
20592 # This is required since we want to support @livemethod() decorated methods
@@ -294,10 +181,10 @@ def from_local(
294181 partial_function .registered = True
295182
296183 server = _Server ()
297- server ._app = app
298184 server ._user_cls = user_cls
185+ server ._app = app
186+
299187 server ._service_function = service_function
300- server ._name = user_cls .__name__
301188 return server
302189
303190 @classmethod
@@ -306,7 +193,6 @@ def from_name(
306193 app_name : str ,
307194 name : str ,
308195 * ,
309- namespace : Any = None , # Deprecated, hidden
310196 environment_name : Optional [str ] = None ,
311197 client : Optional [_Client ] = None ,
312198 ) -> "_Server" :
@@ -318,7 +204,6 @@ def from_name(
318204
319205 TODO(claudia): Add examples
320206 """
321- warn_if_passing_namespace (namespace , "modal.Server.from_name" )
322207
323208 load_context_overrides = LoadContext (client = client , environment_name = environment_name )
324209
0 commit comments