services.py 3.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. """扩展服务的依赖顺序、共享实例和异步资源管理。"""
  2. from __future__ import annotations
  3. from contextlib import AsyncExitStack
  4. from dataclasses import dataclass
  5. import inspect
  6. from types import MappingProxyType
  7. from typing import Any, AsyncContextManager, Callable, Mapping
  8. @dataclass(frozen=True)
  9. class ServiceSpec:
  10. name: str
  11. factory: Callable[[Mapping[str, Any]], AsyncContextManager[Any]]
  12. requires: tuple[str, ...] = ()
  13. def ordered_names(dependencies: Mapping[str, set[str]], kind: str) -> list[str]:
  14. """稳定拓扑排序:无依赖的节点按名称排序,缺失引用和循环立即报错。"""
  15. pending = {name: set(refs) for name, refs in dependencies.items()}
  16. for name, refs in pending.items():
  17. missing = refs - pending.keys()
  18. if missing:
  19. raise ValueError(f"{kind} {name} 引用未注册项:{sorted(missing)}")
  20. result = []
  21. while pending:
  22. ready = sorted(name for name, refs in pending.items() if not refs)
  23. if not ready:
  24. raise ValueError(f"{kind}存在循环依赖:{sorted(pending)}")
  25. for name in ready:
  26. result.append(name)
  27. del pending[name]
  28. for refs in pending.values():
  29. refs.difference_update(ready)
  30. return result
  31. _active: ServiceContainer | None = None
  32. class ServiceContainer:
  33. """由 RuntimeEnvironment 管理;工厂必须返回异步上下文管理器。
  34. 工厂只收到其声明的依赖。资源先按依赖顺序创建,再逆序关闭。
  35. """
  36. def __init__(self, specs: Mapping[str, ServiceSpec]):
  37. self._specs = dict(specs)
  38. self._values: dict[str, Any] = {}
  39. self._stack = AsyncExitStack()
  40. async def start(self) -> None:
  41. global _active
  42. if _active is not None:
  43. raise RuntimeError("扩展服务已启动,请复用 RuntimeEnvironment")
  44. order = ordered_names({name: set(spec.requires) for name, spec in self._specs.items()}, "服务")
  45. for name in order:
  46. spec = self._specs[name]
  47. dependencies = self.view(spec.requires)
  48. context = spec.factory(dependencies)
  49. if not hasattr(context, "__aenter__") or not hasattr(context, "__aexit__"):
  50. if inspect.iscoroutine(context):
  51. context.close()
  52. raise TypeError(f"服务 {name} 的工厂必须返回异步上下文管理器")
  53. self._values[name] = await self._stack.enter_async_context(context)
  54. _active = self
  55. def view(self, names) -> Mapping[str, Any]:
  56. try:
  57. return MappingProxyType({name: self._values[name] for name in names})
  58. except KeyError as exc:
  59. raise RuntimeError(f"扩展服务 {exc.args[0]} 尚未就绪,请在 RuntimeEnvironment 内使用") from exc
  60. async def close(self) -> None:
  61. global _active
  62. try:
  63. await self._stack.aclose()
  64. finally:
  65. self._values.clear()
  66. if _active is self:
  67. _active = None
  68. def service_view(names) -> Mapping[str, Any]:
  69. if not names:
  70. return MappingProxyType({})
  71. if _active is None:
  72. raise RuntimeError("扩展服务尚未启动,请在 RuntimeEnvironment 内使用")
  73. return _active.view(names)
  74. def get_service(name: str) -> Any:
  75. """工具在执行时获取服务,不要在 import 或 setup 时调用。"""
  76. return service_view((name,))[name]