agentscope-runtime 1.0.4a1__py3-none-any.whl → 1.0.5.post1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. agentscope_runtime/adapters/agentscope/stream.py +2 -8
  2. agentscope_runtime/adapters/langgraph/stream.py +120 -70
  3. agentscope_runtime/adapters/ms_agent_framework/__init__.py +0 -0
  4. agentscope_runtime/adapters/ms_agent_framework/message.py +205 -0
  5. agentscope_runtime/adapters/ms_agent_framework/stream.py +418 -0
  6. agentscope_runtime/adapters/utils.py +6 -0
  7. agentscope_runtime/cli/commands/deploy.py +836 -1
  8. agentscope_runtime/cli/commands/stop.py +16 -0
  9. agentscope_runtime/common/container_clients/__init__.py +52 -0
  10. agentscope_runtime/common/container_clients/agentrun_client.py +6 -4
  11. agentscope_runtime/common/container_clients/boxlite_client.py +442 -0
  12. agentscope_runtime/common/container_clients/docker_client.py +0 -20
  13. agentscope_runtime/common/container_clients/fc_client.py +6 -4
  14. agentscope_runtime/common/container_clients/gvisor_client.py +38 -0
  15. agentscope_runtime/common/container_clients/knative_client.py +467 -0
  16. agentscope_runtime/common/utils/deprecation.py +164 -0
  17. agentscope_runtime/engine/__init__.py +4 -0
  18. agentscope_runtime/engine/app/agent_app.py +16 -4
  19. agentscope_runtime/engine/constant.py +1 -0
  20. agentscope_runtime/engine/deployers/__init__.py +34 -11
  21. agentscope_runtime/engine/deployers/adapter/__init__.py +8 -0
  22. agentscope_runtime/engine/deployers/adapter/a2a/__init__.py +26 -51
  23. agentscope_runtime/engine/deployers/adapter/a2a/a2a_protocol_adapter.py +23 -13
  24. agentscope_runtime/engine/deployers/adapter/a2a/a2a_registry.py +4 -201
  25. agentscope_runtime/engine/deployers/adapter/a2a/nacos_a2a_registry.py +152 -25
  26. agentscope_runtime/engine/deployers/adapter/agui/__init__.py +8 -0
  27. agentscope_runtime/engine/deployers/adapter/agui/agui_adapter_utils.py +652 -0
  28. agentscope_runtime/engine/deployers/adapter/agui/agui_protocol_adapter.py +225 -0
  29. agentscope_runtime/engine/deployers/agentrun_deployer.py +2 -2
  30. agentscope_runtime/engine/deployers/fc_deployer.py +1506 -0
  31. agentscope_runtime/engine/deployers/knative_deployer.py +290 -0
  32. agentscope_runtime/engine/deployers/pai_deployer.py +2335 -0
  33. agentscope_runtime/engine/deployers/utils/net_utils.py +37 -0
  34. agentscope_runtime/engine/deployers/utils/oss_utils.py +38 -0
  35. agentscope_runtime/engine/deployers/utils/package.py +46 -42
  36. agentscope_runtime/engine/helpers/agent_api_client.py +372 -0
  37. agentscope_runtime/engine/runner.py +13 -0
  38. agentscope_runtime/engine/schemas/agent_schemas.py +9 -3
  39. agentscope_runtime/engine/services/agent_state/__init__.py +7 -0
  40. agentscope_runtime/engine/services/memory/__init__.py +7 -0
  41. agentscope_runtime/engine/services/memory/redis_memory_service.py +15 -16
  42. agentscope_runtime/engine/services/session_history/__init__.py +7 -0
  43. agentscope_runtime/engine/tracing/local_logging_handler.py +2 -3
  44. agentscope_runtime/engine/tracing/wrapper.py +18 -4
  45. agentscope_runtime/sandbox/__init__.py +14 -6
  46. agentscope_runtime/sandbox/box/base/__init__.py +2 -2
  47. agentscope_runtime/sandbox/box/base/base_sandbox.py +51 -1
  48. agentscope_runtime/sandbox/box/browser/__init__.py +2 -2
  49. agentscope_runtime/sandbox/box/browser/browser_sandbox.py +198 -2
  50. agentscope_runtime/sandbox/box/filesystem/__init__.py +2 -2
  51. agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +99 -2
  52. agentscope_runtime/sandbox/box/gui/__init__.py +2 -2
  53. agentscope_runtime/sandbox/box/gui/gui_sandbox.py +117 -1
  54. agentscope_runtime/sandbox/box/mobile/__init__.py +2 -2
  55. agentscope_runtime/sandbox/box/mobile/mobile_sandbox.py +247 -100
  56. agentscope_runtime/sandbox/box/sandbox.py +102 -65
  57. agentscope_runtime/sandbox/box/shared/routers/generic.py +36 -29
  58. agentscope_runtime/sandbox/client/__init__.py +6 -1
  59. agentscope_runtime/sandbox/client/async_http_client.py +339 -0
  60. agentscope_runtime/sandbox/client/base.py +74 -0
  61. agentscope_runtime/sandbox/client/http_client.py +108 -329
  62. agentscope_runtime/sandbox/enums.py +7 -0
  63. agentscope_runtime/sandbox/manager/sandbox_manager.py +275 -29
  64. agentscope_runtime/sandbox/manager/server/app.py +7 -1
  65. agentscope_runtime/sandbox/manager/server/config.py +3 -1
  66. agentscope_runtime/sandbox/model/manager_config.py +11 -9
  67. agentscope_runtime/tools/modelstudio_memory/__init__.py +106 -0
  68. agentscope_runtime/tools/modelstudio_memory/base.py +220 -0
  69. agentscope_runtime/tools/modelstudio_memory/config.py +86 -0
  70. agentscope_runtime/tools/modelstudio_memory/core.py +594 -0
  71. agentscope_runtime/tools/modelstudio_memory/exceptions.py +60 -0
  72. agentscope_runtime/tools/modelstudio_memory/schemas.py +253 -0
  73. agentscope_runtime/version.py +1 -1
  74. {agentscope_runtime-1.0.4a1.dist-info → agentscope_runtime-1.0.5.post1.dist-info}/METADATA +187 -74
  75. {agentscope_runtime-1.0.4a1.dist-info → agentscope_runtime-1.0.5.post1.dist-info}/RECORD +79 -55
  76. {agentscope_runtime-1.0.4a1.dist-info → agentscope_runtime-1.0.5.post1.dist-info}/WHEEL +1 -1
  77. {agentscope_runtime-1.0.4a1.dist-info → agentscope_runtime-1.0.5.post1.dist-info}/entry_points.txt +0 -0
  78. {agentscope_runtime-1.0.4a1.dist-info → agentscope_runtime-1.0.5.post1.dist-info}/licenses/LICENSE +0 -0
  79. {agentscope_runtime-1.0.4a1.dist-info → agentscope_runtime-1.0.5.post1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,467 @@
1
+ # -*- coding: utf-8 -*-
2
+ # pylint: disable=too-many-branches,too-many-statements
3
+
4
+ import logging
5
+ import time
6
+ import traceback
7
+ from typing import Optional, Dict, Tuple
8
+ from kubernetes import client
9
+ from kubernetes import config as k8s_config
10
+ from kubernetes.client.rest import ApiException
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ # TODO: not support Sandbox
16
+ class KnativeClient:
17
+ """
18
+ A client for interacting with Knative Services in a Kubernetes cluster.
19
+
20
+ This client wraps the Kubernetes CustomObjectsApi to manage
21
+ Knative Service resources (serving.knative.dev/v1).
22
+ """
23
+
24
+ def __init__(
25
+ self,
26
+ config=None,
27
+ image_registry: Optional[str] = None,
28
+ ):
29
+ self.config = config
30
+ namespace = self.config.k8s_namespace
31
+ kubeconfig = self.config.kubeconfig_path
32
+ self.image_registry = image_registry
33
+ try:
34
+ if kubeconfig:
35
+ k8s_config.load_kube_config(config_file=kubeconfig)
36
+ else:
37
+ # Try to load in-cluster config first, then fall back to
38
+ # kubeconfig
39
+ try:
40
+ k8s_config.load_incluster_config()
41
+ except k8s_config.ConfigException:
42
+ k8s_config.load_kube_config()
43
+ self._custom_api = client.CustomObjectsApi() # For Knative Service
44
+ self.v1 = client.CoreV1Api()
45
+ self.namespace = namespace
46
+ # Test connection
47
+ self.v1.list_namespace()
48
+ logger.debug("Kubernetes client initialized successfully")
49
+ except Exception as e:
50
+ raise RuntimeError(
51
+ f"Kubernetes client initialization failed: {str(e)}\n"
52
+ "Solutions:\n"
53
+ "• Ensure kubectl is configured\n"
54
+ "• Check kubeconfig file permissions\n"
55
+ "• Verify cluster connectivity\n"
56
+ "• For in-cluster: ensure proper RBAC permissions",
57
+ ) from e
58
+
59
+ def _is_local_cluster(self):
60
+ """
61
+ Determine if we're connected to a local Kubernetes cluster.
62
+
63
+ Returns:
64
+ bool: True if connected to a local cluster, False otherwise
65
+ """
66
+ try:
67
+ # Get the current context configuration
68
+ contexts, current_context = k8s_config.list_kube_config_contexts(
69
+ config_file=self.config.kubeconfig_path
70
+ if hasattr(self.config, "kubeconfig_path")
71
+ and self.config.kubeconfig_path
72
+ else None,
73
+ )
74
+
75
+ if current_context and current_context.get("context"):
76
+ cluster_name = current_context["context"].get("cluster", "")
77
+ server = None
78
+
79
+ # Get cluster server URL
80
+ for cluster in contexts.get("clusters", []):
81
+ if cluster["name"] == cluster_name:
82
+ server = cluster.get("cluster", {}).get("server", "")
83
+ break
84
+
85
+ if server:
86
+ # Check for common local cluster patterns
87
+ local_patterns = [
88
+ "localhost",
89
+ "127.0.0.1",
90
+ "0.0.0.0",
91
+ "docker-desktop",
92
+ "kind-", # kind clusters
93
+ "minikube", # minikube
94
+ "k3d-", # k3d clusters
95
+ "colima", # colima
96
+ ]
97
+
98
+ server_lower = server.lower()
99
+ cluster_lower = cluster_name.lower()
100
+
101
+ for pattern in local_patterns:
102
+ if pattern in server_lower or pattern in cluster_lower:
103
+ return True
104
+
105
+ return False
106
+
107
+ except Exception as e:
108
+ logger.debug(
109
+ f"Could not determine cluster type, assuming remote: {e}",
110
+ )
111
+ # If we can't determine, assume remote for safety
112
+ return False
113
+
114
+ def create_kservice(
115
+ self,
116
+ name: str,
117
+ image: str,
118
+ ports=None,
119
+ volumes=None,
120
+ environment=None,
121
+ runtime_config=None,
122
+ annotations: Optional[Dict[str, str]] = None,
123
+ labels: Optional[Dict[str, str]] = None,
124
+ namespace: Optional[str] = None,
125
+ ) -> Tuple[str, str] | Tuple[None, None]:
126
+ """
127
+ Create a Knative Service.
128
+
129
+ Args:
130
+ name (str): Name of the KService.
131
+ image (str): Container image.
132
+ ports: List of ports to expose
133
+ volumes: Volume mounts dictionary
134
+ environment: Environment variables dictionary
135
+ runtime_config: Runtime configuration dictionary
136
+ annotations (dict): KService annotations.
137
+ labels (dict): KService labels.
138
+ namespace (str): Override default namespace.
139
+
140
+ Returns:
141
+ dict: Created Knative Service object.
142
+ """
143
+ ns = namespace or self.namespace
144
+
145
+ # Create kservice pod specification
146
+ pod_spec = self._create_kservice_podspec(
147
+ image,
148
+ name,
149
+ ports,
150
+ volumes,
151
+ environment,
152
+ runtime_config,
153
+ )
154
+
155
+ kservice_manifest = {
156
+ "apiVersion": "serving.knative.dev/v1",
157
+ "kind": "Service",
158
+ "metadata": {
159
+ "name": name,
160
+ "namespace": ns,
161
+ "labels": labels or {},
162
+ "annotations": annotations or {},
163
+ },
164
+ "spec": {
165
+ "template": {
166
+ "metadata": {
167
+ "labels": labels or {},
168
+ "annotations": annotations or {},
169
+ },
170
+ "spec": pod_spec,
171
+ },
172
+ },
173
+ }
174
+
175
+ logger.info(f"Creating Knative Service '{name}' in namespace '{ns}'")
176
+ try:
177
+ self._custom_api.create_namespaced_custom_object(
178
+ group="serving.knative.dev",
179
+ version="v1",
180
+ namespace=ns,
181
+ plural="services",
182
+ body=kservice_manifest,
183
+ )
184
+ logger.debug("Knative Service created successfully.")
185
+ # Wait for kservice to be ready
186
+ if not self.wait_for_ready(name, timeout=120):
187
+ logger.warning(f"KService '{name}' may not be fully ready")
188
+ ksvc = self.get_kservice(name, ns)
189
+
190
+ url = ksvc.get("status", {}).get("url")
191
+ return (
192
+ name,
193
+ url or "",
194
+ )
195
+ except ApiException as e:
196
+ logger.error(
197
+ f"Failed to create KService: {e}, {traceback.format_exc()}",
198
+ )
199
+ return None, None
200
+
201
+ def _create_kservice_podspec(
202
+ self,
203
+ image,
204
+ name,
205
+ ports=None,
206
+ volumes=None,
207
+ environment=None,
208
+ runtime_config=None,
209
+ ):
210
+ """Create a Knative Service Pod specification."""
211
+ if runtime_config is None:
212
+ runtime_config = {}
213
+
214
+ container_name = name or "main-container"
215
+
216
+ # Use image registry if configured
217
+ if not self.image_registry:
218
+ full_image = image
219
+ else:
220
+ full_image = f"{self.image_registry}/{image}"
221
+
222
+ # Create container spec (reuse the existing pod spec logic)
223
+ container = client.V1Container(
224
+ name=container_name,
225
+ image=full_image,
226
+ image_pull_policy=runtime_config.get(
227
+ "image_pull_policy",
228
+ "IfNotPresent",
229
+ ),
230
+ )
231
+
232
+ # Configure ports
233
+ if ports:
234
+ container_ports = []
235
+ for port_spec in ports:
236
+ port_info = self._parse_port_spec(port_spec)
237
+ if port_info:
238
+ container_ports.append(
239
+ client.V1ContainerPort(
240
+ container_port=port_info["port"],
241
+ protocol=port_info["protocol"],
242
+ ),
243
+ )
244
+ if container_ports:
245
+ container.ports = container_ports
246
+
247
+ # Configure environment variables
248
+ if environment:
249
+ env_vars = []
250
+ for key, value in environment.items():
251
+ env_vars.append(client.V1EnvVar(name=key, value=str(value)))
252
+ container.env = env_vars
253
+
254
+ # Configure volume mounts and volumes
255
+ volume_mounts = []
256
+ pod_volumes = []
257
+ if volumes:
258
+ for volume_idx, (host_path, mount_info) in enumerate(
259
+ volumes.items(),
260
+ ):
261
+ if isinstance(mount_info, dict):
262
+ container_path = mount_info["bind"]
263
+ mode = mount_info.get("mode", "rw")
264
+ else:
265
+ container_path = mount_info
266
+ mode = "rw"
267
+ volume_name = f"vol-{volume_idx}"
268
+
269
+ # Create volume mount
270
+ volume_mounts.append(
271
+ client.V1VolumeMount(
272
+ name=volume_name,
273
+ mount_path=container_path,
274
+ read_only=(mode == "ro"),
275
+ ),
276
+ )
277
+ # Create host path volume
278
+ pod_volumes.append(
279
+ client.V1Volume(
280
+ name=volume_name,
281
+ host_path=client.V1HostPathVolumeSource(
282
+ path=host_path,
283
+ ),
284
+ ),
285
+ )
286
+
287
+ if volume_mounts:
288
+ container.volume_mounts = volume_mounts
289
+
290
+ # Apply runtime config to container
291
+ if "resources" in runtime_config:
292
+ container.resources = client.V1ResourceRequirements(
293
+ **runtime_config["resources"],
294
+ )
295
+
296
+ if "security_context" in runtime_config:
297
+ container.security_context = client.V1SecurityContext(
298
+ **runtime_config["security_context"],
299
+ )
300
+
301
+ # Pod template specification
302
+ pod_spec = client.V1PodSpec(
303
+ containers=[container],
304
+ restart_policy=runtime_config.get(
305
+ "restart_policy",
306
+ "Always",
307
+ ), # KService typically use Always
308
+ )
309
+
310
+ if pod_volumes:
311
+ pod_spec.volumes = pod_volumes
312
+
313
+ if "node_selector" in runtime_config:
314
+ pod_spec.node_selector = runtime_config["node_selector"]
315
+
316
+ if "tolerations" in runtime_config:
317
+ pod_spec.tolerations = runtime_config["tolerations"]
318
+
319
+ # Handle image pull secrets
320
+ image_pull_secrets = runtime_config.get("image_pull_secrets", [])
321
+ if image_pull_secrets:
322
+ secrets = []
323
+ for secret_name in image_pull_secrets:
324
+ secrets.append(client.V1LocalObjectReference(name=secret_name))
325
+ pod_spec.image_pull_secrets = secrets
326
+
327
+ return pod_spec
328
+
329
+ def _parse_port_spec(self, port_spec):
330
+ """
331
+ Parse port specification.
332
+ - "80/tcp" -> {"port": 80, "protocol": "TCP"}
333
+ - "80" -> {"port": 80, "protocol": "TCP"}
334
+ - 80 -> {"port": 80, "protocol": "TCP"}
335
+ """
336
+ try:
337
+ if isinstance(port_spec, int):
338
+ return {"port": port_spec, "protocol": "TCP"}
339
+
340
+ if isinstance(port_spec, str):
341
+ if "/" in port_spec:
342
+ port_str, protocol = port_spec.split("/", 1)
343
+ else:
344
+ port_str = port_spec
345
+ protocol = "TCP"
346
+
347
+ port = int(port_str)
348
+ protocol = protocol.upper()
349
+
350
+ return {"port": port, "protocol": protocol}
351
+
352
+ # Log a warning if the port_spec is neither int nor str
353
+ logger.warning(f"Unsupported port specification: {port_spec}")
354
+ return None
355
+
356
+ except ValueError as e:
357
+ logger.error(f"Failed to parse port spec '{port_spec}': {e}")
358
+ return None
359
+
360
+ def delete_kservice(
361
+ self,
362
+ name: str,
363
+ namespace: Optional[str] = None,
364
+ ) -> bool:
365
+ """Delete a Knative Service."""
366
+ ns = namespace or self.namespace
367
+ logger.info(f"Deleting Knative Service '{name}' in namespace '{ns}'")
368
+ try:
369
+ self._custom_api.delete_namespaced_custom_object(
370
+ group="serving.knative.dev",
371
+ version="v1",
372
+ namespace=ns,
373
+ plural="services",
374
+ name=name,
375
+ body=client.V1DeleteOptions(),
376
+ )
377
+ logger.debug("Knative Service deleted.")
378
+ return True
379
+ except ApiException as e:
380
+ if e.status == 404:
381
+ logger.warning(f"Knative Service '{name}' not found.")
382
+ return False
383
+ logger.error(f"Failed to delete Knative Service: {e.body}")
384
+ raise
385
+
386
+ def get_kservice(self, name: str, namespace: Optional[str] = None):
387
+ """Get a Knative Service by name."""
388
+ try:
389
+ svc = self._custom_api.get_namespaced_custom_object(
390
+ group="serving.knative.dev",
391
+ version="v1",
392
+ namespace=namespace,
393
+ plural="services",
394
+ name=name,
395
+ )
396
+ return svc
397
+ except ApiException as e:
398
+ if e.status == 404:
399
+ return None
400
+ raise
401
+
402
+ def wait_for_ready(
403
+ self,
404
+ name: str,
405
+ timeout: int = 300,
406
+ poll_interval: int = 5,
407
+ ) -> bool:
408
+ """
409
+ Wait until the Knative Service is ready.
410
+
411
+ Returns:
412
+ bool: True if ready within timeout, False otherwise.
413
+ """
414
+ logger.info(
415
+ f"Waiting for Knative Service '{name}' "
416
+ "to become ready (timeout={timeout}s)",
417
+ )
418
+
419
+ start_time = time.time()
420
+ while time.time() - start_time < timeout:
421
+ svc = self.get_kservice(name, self.namespace)
422
+ if svc:
423
+ conditions = svc.get("status", {}).get("conditions", [])
424
+ ready_cond = next(
425
+ (c for c in conditions if c.get("type") == "Ready"),
426
+ None,
427
+ )
428
+ if ready_cond and ready_cond.get("status") == "True":
429
+ logger.info(f"Knative Service '{name}' is ready.")
430
+ return True
431
+ time.sleep(poll_interval)
432
+
433
+ logger.error(
434
+ f"Knative Service '{name}' did not "
435
+ "become ready within {timeout} seconds.",
436
+ )
437
+ return False
438
+
439
+ def get_kservice_status(self, name):
440
+ """Get the current status of the specified kservice."""
441
+ try:
442
+ ksvc = self.get_kservice(name, self.namespace)
443
+
444
+ return {
445
+ "name": name,
446
+ "url": ksvc.get("status", {}).get("url"),
447
+ "conditions": [
448
+ {
449
+ "type": condition.get("type"),
450
+ "status": condition.get("status"),
451
+ "reason": condition.get("reason"),
452
+ "message": condition.get("message"),
453
+ }
454
+ for condition in (
455
+ ksvc.get("status", {}).get("conditions", [])
456
+ )
457
+ ],
458
+ }
459
+ except ApiException as e:
460
+ if e.status == 404:
461
+ logger.warning(f"KService '{name}' not found")
462
+ else:
463
+ logger.error(f"Failed to get ksvc status: {e.reason}")
464
+ return None
465
+ except Exception as e:
466
+ logger.error(f"An error occurred: {e}, {traceback.format_exc()}")
467
+ return None
@@ -0,0 +1,164 @@
1
+ # -*- coding: utf-8 -*-
2
+ from __future__ import annotations
3
+
4
+ import functools
5
+ import warnings
6
+ from dataclasses import dataclass
7
+ from typing import Callable, TypeVar
8
+
9
+ T = TypeVar("T", bound=object)
10
+
11
+
12
+ def _toplevel_pkg() -> str:
13
+ pkg = __package__ or __name__
14
+ return pkg.split(".", 1)[0]
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class DeprecationInfo:
19
+ reason: str = ""
20
+ since: str | None = None
21
+ removed_in: str | None = None
22
+ alternative: str | None = None
23
+ issue: str | int | None = None # e.g. "GH-123" or 123
24
+
25
+
26
+ def format_deprecation_message(subject: str, info: DeprecationInfo) -> str:
27
+ parts: list[str] = [f"{subject} is deprecated."]
28
+ if info.since:
29
+ parts.append(f"Since {info.since}.")
30
+ if info.removed_in:
31
+ parts.append(f"Will be removed in {info.removed_in}.")
32
+ if info.alternative:
33
+ parts.append(f"Use {info.alternative} instead.")
34
+ if info.issue is not None:
35
+ parts.append(f"See {info.issue}.")
36
+ if info.reason:
37
+ parts.append(info.reason.rstrip(".") + ".")
38
+ return " ".join(parts)
39
+
40
+
41
+ def warn_deprecated(
42
+ subject: str,
43
+ info: DeprecationInfo,
44
+ *,
45
+ category=DeprecationWarning,
46
+ stacklevel: int = 2,
47
+ once: bool = False,
48
+ ) -> None:
49
+ message = format_deprecation_message(subject, info)
50
+
51
+ if once:
52
+ top = _toplevel_pkg()
53
+ with warnings.catch_warnings():
54
+ warnings.filterwarnings(
55
+ "once",
56
+ category=category,
57
+ module=rf"^{top}(\.|$)",
58
+ )
59
+ warnings.warn(message, category=category, stacklevel=stacklevel)
60
+ else:
61
+ warnings.warn(message, category=category, stacklevel=stacklevel)
62
+
63
+
64
+ def deprecated(
65
+ reason: str | DeprecationInfo = "",
66
+ *,
67
+ since: str | None = None,
68
+ removed_in: str | None = None,
69
+ alternative: str | None = None,
70
+ issue: str | int | None = None,
71
+ category=DeprecationWarning,
72
+ stacklevel: int = 2,
73
+ once: bool = False,
74
+ ) -> Callable[[T], T]:
75
+ """
76
+ Unified decorator:
77
+ - function/method: warn on each call
78
+ - class: warn on each instantiation (__init__)
79
+ """
80
+ info = (
81
+ reason
82
+ if isinstance(reason, DeprecationInfo)
83
+ else DeprecationInfo(
84
+ reason=str(reason),
85
+ since=since,
86
+ removed_in=removed_in,
87
+ alternative=alternative,
88
+ issue=issue,
89
+ )
90
+ )
91
+
92
+ def decorator(obj):
93
+ subject = getattr(
94
+ obj,
95
+ "__qualname__",
96
+ getattr(obj, "__name__", repr(obj)),
97
+ )
98
+
99
+ if isinstance(obj, type):
100
+ orig_init = obj.__init__
101
+
102
+ @functools.wraps(orig_init)
103
+ def __init__(self, *args, **kwargs):
104
+ warn_deprecated(
105
+ subject,
106
+ info,
107
+ category=category,
108
+ stacklevel=stacklevel,
109
+ once=once,
110
+ )
111
+ orig_init(self, *args, **kwargs)
112
+
113
+ obj.__init__ = __init__
114
+ return obj
115
+
116
+ @functools.wraps(obj)
117
+ def wrapper(*args, **kwargs):
118
+ warn_deprecated(
119
+ subject,
120
+ info,
121
+ category=category,
122
+ stacklevel=stacklevel,
123
+ once=once,
124
+ )
125
+ return obj(*args, **kwargs)
126
+
127
+ return wrapper
128
+
129
+ return decorator
130
+
131
+
132
+ def deprecated_module(
133
+ reason: str | DeprecationInfo = "",
134
+ *,
135
+ module_name: str,
136
+ since: str | None = None,
137
+ removed_in: str | None = None,
138
+ alternative: str | None = None,
139
+ issue: str | int | None = None,
140
+ category=DeprecationWarning,
141
+ stacklevel: int = 2,
142
+ once: bool = True,
143
+ ) -> None:
144
+ """
145
+ Use inside a module/package (typically __init__.py) to warn on import.
146
+ """
147
+ info = (
148
+ reason
149
+ if isinstance(reason, DeprecationInfo)
150
+ else DeprecationInfo(
151
+ reason=str(reason),
152
+ since=since,
153
+ removed_in=removed_in,
154
+ alternative=alternative,
155
+ issue=issue,
156
+ )
157
+ )
158
+ warn_deprecated(
159
+ f"Module `{module_name}`",
160
+ info,
161
+ category=category,
162
+ stacklevel=stacklevel,
163
+ once=once,
164
+ )
@@ -11,8 +11,10 @@ if TYPE_CHECKING:
11
11
  DeployManager,
12
12
  LocalDeployManager,
13
13
  KubernetesDeployManager,
14
+ KnativeDeployManager,
14
15
  ModelstudioDeployManager,
15
16
  AgentRunDeployManager,
17
+ FCDeployManager,
16
18
  )
17
19
 
18
20
 
@@ -22,7 +24,9 @@ install_lazy_loader(
22
24
  "DeployManager": ".deployers",
23
25
  "LocalDeployManager": ".deployers",
24
26
  "KubernetesDeployManager": ".deployers",
27
+ "KnativeDeployManager": ".deployers",
25
28
  "ModelstudioDeployManager": ".deployers",
26
29
  "AgentRunDeployManager": ".deployers",
30
+ "FCDeployManager": ".deployers",
27
31
  },
28
32
  )