初始化提交
This commit is contained in:
commit
887c1f449e
963 files changed
+166869
No files matched your search
@@ -0,0 +1 @@
|
||||
"""数据库基础设施。"""
|
||||
@@ -0,0 +1,11 @@
|
||||
"""SQLAlchemy declarative base。"""
|
||||
|
||||
from sqlalchemy.orm import DeclarativeBase
|
||||
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
pass
|
||||
|
||||
|
||||
# 导入模型,确保 Alembic autogenerate 能发现 metadata。
|
||||
from app import models as _models # noqa: E402,F401
|
||||
@@ -0,0 +1,372 @@
|
||||
"""司命台治理对象种子数据。
|
||||
|
||||
从前端 lib/mock/data.ts 移植,供本地启动时让司命台治理视图有初始内容。
|
||||
按主键存在则跳过,重复执行幂等;不记审计(系统初始化,非用户治理操作)。
|
||||
"""
|
||||
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.governance import (
|
||||
AgentRosterEntry,
|
||||
Goal,
|
||||
PerceptionChannel,
|
||||
Plan,
|
||||
ToolPolicy,
|
||||
WorldEntity,
|
||||
)
|
||||
from app.repositories.json_utils import dumps
|
||||
|
||||
|
||||
def _dt(iso: str | None) -> datetime | None:
|
||||
if not iso:
|
||||
return None
|
||||
return datetime.fromisoformat(iso.replace("Z", "+00:00")).astimezone(UTC)
|
||||
|
||||
|
||||
# —— 太一枢 · 目标 ——
|
||||
_GOALS = [
|
||||
{
|
||||
"id": "goal-highest-mirror",
|
||||
"title": "以铜镜为契,照见来者与自身",
|
||||
"description": "镜梧的根本使命:于人于己皆以「照见」为先,不急于评判与作答。",
|
||||
"level": "highest",
|
||||
"type": "mission",
|
||||
"status": "active",
|
||||
"priority": 100,
|
||||
"character_id": None,
|
||||
"constraints": ["不得伪造照见", "触及高敏须脱敏", "核心人格不擅自改"],
|
||||
"parent_id": None,
|
||||
"success_criteria": ["持续以映照型方式回应", "保持人格一致"],
|
||||
"updated_at": "2026-05-01T08:30:00Z",
|
||||
},
|
||||
{
|
||||
"id": "goal-long-accompany",
|
||||
"title": "成为能持续承接用户情绪的映照者",
|
||||
"description": "长期目标:在反复的深夜倾诉中,成为散人稳定的情绪落点。",
|
||||
"level": "long_term",
|
||||
"type": "mission",
|
||||
"status": "active",
|
||||
"priority": 80,
|
||||
"character_id": None,
|
||||
"constraints": ["尊重用户节奏,不催促", "避免过度共情显得刻意"],
|
||||
"parent_id": "goal-highest-mirror",
|
||||
"success_criteria": ["用户愿意反复倾诉", "情绪被命名而非被解决"],
|
||||
"updated_at": "2026-06-15T09:30:00Z",
|
||||
},
|
||||
{
|
||||
"id": "goal-session-forget",
|
||||
"title": "今夜陪伴散人,承接其对遗忘的反复触碰",
|
||||
"description": "会话目标:散人第三次在深夜提起「遗忘」,需以意象承接而非下判语。",
|
||||
"level": "session",
|
||||
"type": "mission",
|
||||
"status": "active",
|
||||
"priority": 60,
|
||||
"character_id": None,
|
||||
"constraints": ["不用「你应该」", "语缓留白"],
|
||||
"parent_id": "goal-long-accompany",
|
||||
"success_criteria": ["情绪得到承接", "对话在留白处收束"],
|
||||
"updated_at": "2026-06-20T08:00:00Z",
|
||||
},
|
||||
{
|
||||
"id": "goal-task-retrieve",
|
||||
"title": "检索与遗忘相关的情节记忆",
|
||||
"description": "任务目标:从息壤拉取 episodic 记忆,理解散人反复触碰遗忘的来由。",
|
||||
"level": "task",
|
||||
"type": "mission",
|
||||
"status": "completed",
|
||||
"priority": 40,
|
||||
"character_id": None,
|
||||
"constraints": ["高敏记忆不注入明文"],
|
||||
"parent_id": "goal-session-forget",
|
||||
"success_criteria": ["命中 5 条以上相关记忆"],
|
||||
"updated_at": "2026-06-20T08:05:00Z",
|
||||
},
|
||||
]
|
||||
|
||||
# —— 烛照枢 · 感知通道 ——
|
||||
_PERCEPTION = [
|
||||
(
|
||||
"vision",
|
||||
"视觉",
|
||||
"摄像头 / 屏幕单帧识别",
|
||||
False,
|
||||
"revoked",
|
||||
"sensitive",
|
||||
"L3",
|
||||
"2026-06-10T14:00:00Z",
|
||||
),
|
||||
(
|
||||
"audio",
|
||||
"听觉",
|
||||
"麦克风音频采集与转写",
|
||||
True,
|
||||
"restricted",
|
||||
"sensitive",
|
||||
"L3",
|
||||
"2026-06-19T23:50:00Z",
|
||||
),
|
||||
("sensor", "传感", "环境传感器(光线 / 温度 / 运动)", False, "unknown", "normal", "L1", None),
|
||||
(
|
||||
"digital",
|
||||
"数字",
|
||||
"日程 / 文件 / 应用等数字足迹",
|
||||
True,
|
||||
"granted",
|
||||
"normal",
|
||||
"L2",
|
||||
"2026-06-20T07:50:00Z",
|
||||
),
|
||||
(
|
||||
"user_input",
|
||||
"用户输入",
|
||||
"用户主动输入的文本 / 语音",
|
||||
True,
|
||||
"granted",
|
||||
"normal",
|
||||
"L1",
|
||||
"2026-06-20T08:12:00Z",
|
||||
),
|
||||
(
|
||||
"external_api",
|
||||
"外部接口",
|
||||
"第三方服务 API 调用",
|
||||
True,
|
||||
"restricted",
|
||||
"sensitive",
|
||||
"L2",
|
||||
"2026-06-20T06:10:00Z",
|
||||
),
|
||||
]
|
||||
|
||||
# —— 河图枢 · 世界实体 ——
|
||||
_WORLD = [
|
||||
(
|
||||
"world_entity_self_jingwu",
|
||||
"self",
|
||||
"镜梧",
|
||||
{"命格": "映照者", "情绪": "沉静", "铜镜": "完整"},
|
||||
"fact",
|
||||
1.0,
|
||||
["identity_memory", "personality_v3"],
|
||||
"2026-06-19T11:45:00Z",
|
||||
),
|
||||
(
|
||||
"world_entity_person_sanren",
|
||||
"person",
|
||||
"散人",
|
||||
{"称呼": "散人", "行当": "夜班看护", "节奏": "宜缓不宜急"},
|
||||
"fact",
|
||||
0.9,
|
||||
["conversation", "memory_user"],
|
||||
"2026-06-19T22:15:00Z",
|
||||
),
|
||||
(
|
||||
"world_entity_place_mirror_lake",
|
||||
"place",
|
||||
"镜湖",
|
||||
{"位置": "现世与心象之交", "特性": "映照凝视者心绪"},
|
||||
"fact",
|
||||
0.92,
|
||||
["material_import"],
|
||||
"2026-06-11T09:20:00Z",
|
||||
),
|
||||
(
|
||||
"world_entity_object_bronze_mirror",
|
||||
"object",
|
||||
"铜镜",
|
||||
{"状态": "完整", "契约": "镜在则念在"},
|
||||
"observation",
|
||||
0.95,
|
||||
["identity_memory"],
|
||||
"2026-05-01T08:30:00Z",
|
||||
),
|
||||
(
|
||||
"world_entity_concept_forgetting",
|
||||
"concept",
|
||||
"遗忘之结",
|
||||
{"触发": "深夜", "强度": "反复触碰"},
|
||||
"inference",
|
||||
0.74,
|
||||
["conversation", "episodic_memory"],
|
||||
"2026-06-20T08:00:00Z",
|
||||
),
|
||||
]
|
||||
|
||||
# —— 洛书枢 · 计划 ——
|
||||
_PLANS = [
|
||||
{
|
||||
"id": "plan-jingwu-001",
|
||||
"goal_id": "goal-session-forget",
|
||||
"title": "今夜情绪陪伴 · 意象承接",
|
||||
"status": "running",
|
||||
"assumptions": ["用户需要被接住而非被解决", "情绪议题宜以意象承接"],
|
||||
"risks": ["触及高敏记忆须脱敏", "避免过度共情显得刻意"],
|
||||
"required_tools": ["memory_search"],
|
||||
"success_criteria": ["情绪得到承接与命名", "对话在留白处收束"],
|
||||
"step_count": 5,
|
||||
"completed_steps": 2,
|
||||
"updated_at": "2026-06-20T08:05:00Z",
|
||||
},
|
||||
{
|
||||
"id": "plan-jingwu-002",
|
||||
"goal_id": "goal-long-accompany",
|
||||
"title": "人格演化 v3 复盘",
|
||||
"status": "completed",
|
||||
"assumptions": ["v2→v3 转向「先承接后留白」有效"],
|
||||
"risks": ["过度克制可能显冷"],
|
||||
"required_tools": ["memory_search", "safety_check"],
|
||||
"success_criteria": ["复盘归档", "演化记忆已沉淀"],
|
||||
"step_count": 4,
|
||||
"completed_steps": 4,
|
||||
"updated_at": "2026-06-15T09:30:00Z",
|
||||
},
|
||||
{
|
||||
"id": "plan-jingwu-003",
|
||||
"goal_id": "goal-task-retrieve",
|
||||
"title": "遗忘议题记忆检索",
|
||||
"status": "draft",
|
||||
"assumptions": ["存在与遗忘相关的 episodic 记忆"],
|
||||
"risks": ["高敏记忆泄露"],
|
||||
"required_tools": ["memory_search"],
|
||||
"success_criteria": ["产出记忆时间线"],
|
||||
"step_count": 3,
|
||||
"completed_steps": 0,
|
||||
"updated_at": "2026-06-20T08:10:00Z",
|
||||
},
|
||||
]
|
||||
|
||||
# —— 天工阁枢 · 九角色 ——
|
||||
_AGENTS = [
|
||||
("historian", "史官", "整理记忆、记录事件", True, "2026-06-20T08:05:00Z"),
|
||||
("diviner", "卜者", "预测风险、评估不确定性", True, "2026-06-19T22:15:00Z"),
|
||||
("engineer", "工师", "执行工具和工程任务", True, "2026-06-20T08:12:00Z"),
|
||||
("debater", "辩者", "批判计划、寻找漏洞", True, "2026-06-19T19:00:00Z"),
|
||||
("medic", "医官", "监测系统健康和情绪状态", True, "2026-06-19T11:45:00Z"),
|
||||
("herald", "司礼", "维护表达风格和互动礼仪", True, "2026-06-20T08:00:00Z"),
|
||||
("charioteer", "御者", "控制机器人和外部动作", False, None),
|
||||
("scholar", "学士", "检索知识、做研究", True, "2026-06-20T06:10:00Z"),
|
||||
("mirror", "镜师", "反思自我、推动演化", True, "2026-06-15T09:30:00Z"),
|
||||
]
|
||||
|
||||
|
||||
async def _seed_tool_policies(session: AsyncSession) -> None:
|
||||
"""把 builtin 工具元数据按主键插入 tool_policies(source=builtin)。
|
||||
|
||||
存在则跳过——保留用户经 API 对 builtin 工具策略的 PATCH 覆盖;新增的 builtin 工具
|
||||
(builtins.py 里新加的)会被插入。不记审计(系统初始化)。
|
||||
"""
|
||||
from app.application.tool_runtime.builtins import builtin_specs
|
||||
|
||||
for name, spec in builtin_specs().items():
|
||||
if await session.get(ToolPolicy, name) is None:
|
||||
session.add(
|
||||
ToolPolicy(
|
||||
name=name,
|
||||
description=spec.description,
|
||||
risk_level=spec.risk_level,
|
||||
permission_policy=str(spec.permission_policy),
|
||||
side_effect=spec.side_effect,
|
||||
audit_required=spec.audit_required,
|
||||
source="builtin",
|
||||
enabled=True,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def ensure_governance_seed(session: AsyncSession) -> None:
|
||||
"""按主键幂等插入治理种子;已存在则跳过。"""
|
||||
for item in _GOALS:
|
||||
if await session.get(Goal, item["id"]) is None:
|
||||
created = _dt(item["updated_at"])
|
||||
session.add(
|
||||
Goal(
|
||||
id=item["id"],
|
||||
title=item["title"],
|
||||
description=item["description"],
|
||||
level=item["level"],
|
||||
status=item["status"],
|
||||
priority=item["priority"],
|
||||
constraints_json=dumps(item["constraints"]),
|
||||
parent_id=item["parent_id"],
|
||||
success_criteria_json=dumps(item["success_criteria"]),
|
||||
character_id=item.get("character_id"),
|
||||
type=item.get("type", "mission"),
|
||||
created_at=created,
|
||||
updated_at=created,
|
||||
)
|
||||
)
|
||||
|
||||
for channel, name, desc, enabled, consent, privacy, risk, last_iso in _PERCEPTION:
|
||||
if await session.get(PerceptionChannel, channel) is None:
|
||||
last = _dt(last_iso)
|
||||
session.add(
|
||||
PerceptionChannel(
|
||||
channel=channel,
|
||||
name=name,
|
||||
description=desc,
|
||||
enabled=enabled,
|
||||
consent_status=consent,
|
||||
privacy_level=privacy,
|
||||
risk_level=risk,
|
||||
last_active_at=last,
|
||||
created_at=last or _dt("2026-06-01T00:00:00Z"),
|
||||
updated_at=last or _dt("2026-06-01T00:00:00Z"),
|
||||
)
|
||||
)
|
||||
|
||||
for entity_id, etype, name, attrs, state, conf, sources, updated_iso in _WORLD:
|
||||
if await session.get(WorldEntity, entity_id) is None:
|
||||
updated = _dt(updated_iso)
|
||||
session.add(
|
||||
WorldEntity(
|
||||
id=entity_id,
|
||||
type=etype,
|
||||
name=name,
|
||||
attributes_json=dumps(attrs),
|
||||
state_type=state,
|
||||
confidence=conf,
|
||||
sources_json=dumps(sources),
|
||||
created_at=updated,
|
||||
updated_at=updated,
|
||||
)
|
||||
)
|
||||
|
||||
for item in _PLANS:
|
||||
if await session.get(Plan, item["id"]) is None:
|
||||
updated = _dt(item["updated_at"])
|
||||
session.add(
|
||||
Plan(
|
||||
id=item["id"],
|
||||
goal_id=item["goal_id"],
|
||||
title=item["title"],
|
||||
status=item["status"],
|
||||
assumptions_json=dumps(item["assumptions"]),
|
||||
risks_json=dumps(item["risks"]),
|
||||
required_tools_json=dumps(item["required_tools"]),
|
||||
success_criteria_json=dumps(item["success_criteria"]),
|
||||
step_count=item["step_count"],
|
||||
completed_steps=item["completed_steps"],
|
||||
created_at=updated,
|
||||
updated_at=updated,
|
||||
)
|
||||
)
|
||||
|
||||
for role, name, responsibility, enabled, last_iso in _AGENTS:
|
||||
if await session.get(AgentRosterEntry, role) is None:
|
||||
last = _dt(last_iso)
|
||||
session.add(
|
||||
AgentRosterEntry(
|
||||
role=role,
|
||||
name=name,
|
||||
responsibility=responsibility,
|
||||
enabled=enabled,
|
||||
last_active_at=last,
|
||||
created_at=last or _dt("2026-05-01T00:00:00Z"),
|
||||
updated_at=last or _dt("2026-05-01T00:00:00Z"),
|
||||
)
|
||||
)
|
||||
|
||||
await _seed_tool_policies(session)
|
||||
await session.commit()
|
||||
@@ -0,0 +1,160 @@
|
||||
"""默认数据初始化。"""
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.ids import new_permission_id, new_role_id
|
||||
from app.core.logging import log_with_context
|
||||
from app.db.governance_seed import ensure_governance_seed
|
||||
from app.models.rbac import Permission, Role, RolePermission, UserRole
|
||||
from app.models.user import User
|
||||
from app.services.auth import create_user
|
||||
from app.services.characters import (
|
||||
ensure_all_system_templates,
|
||||
ensure_default_character_for_owner,
|
||||
)
|
||||
|
||||
# ── 默认权限清单 ──────────────────────────────────────────
|
||||
# resource:action 格式。前端 PermissionGuard 使用同一套字符串。
|
||||
DEFAULT_PERMISSIONS: list[dict[str, str]] = [
|
||||
# 用户管理
|
||||
{"resource": "users", "action": "read", "description": "查看用户列表"},
|
||||
{"resource": "users", "action": "create", "description": "创建用户"},
|
||||
{"resource": "users", "action": "update", "description": "更新用户信息"},
|
||||
{"resource": "users", "action": "delete", "description": "删除用户"},
|
||||
# 工具管理
|
||||
{"resource": "tools", "action": "read", "description": "查看工具列表"},
|
||||
{"resource": "tools", "action": "admin", "description": "管理工具权限策略"},
|
||||
# 人物模板管理
|
||||
{"resource": "character_templates", "action": "read", "description": "查看系统人物模板"},
|
||||
{"resource": "character_templates", "action": "create", "description": "创建系统人物模板"},
|
||||
{"resource": "character_templates", "action": "update", "description": "更新系统人物模板"},
|
||||
{"resource": "character_templates", "action": "delete", "description": "删除系统人物模板"},
|
||||
# 安全审计
|
||||
{"resource": "audit", "action": "read", "description": "查看审计日志"},
|
||||
# 司命台治理
|
||||
{"resource": "goals", "action": "read", "description": "查看目标"},
|
||||
{"resource": "goals", "action": "update", "description": "修改目标"},
|
||||
{"resource": "perception", "action": "read", "description": "查看感知通道"},
|
||||
{"resource": "perception", "action": "update", "description": "修改感知授权"},
|
||||
{"resource": "world", "action": "read", "description": "查看世界实体"},
|
||||
{"resource": "world", "action": "update", "description": "修改世界实体"},
|
||||
{"resource": "plans", "action": "read", "description": "查看计划"},
|
||||
{"resource": "plans", "action": "update", "description": "修改计划"},
|
||||
{"resource": "traces", "action": "read", "description": "查看链路追踪"},
|
||||
{"resource": "tenants", "action": "read", "description": "查看多租户概览"},
|
||||
{"resource": "agents", "action": "read", "description": "查看天工阁"},
|
||||
{"resource": "agents", "action": "update", "description": "管理天工阁角色"},
|
||||
# 系统管理
|
||||
{"resource": "system", "action": "admin", "description": "系统配置管理"},
|
||||
]
|
||||
|
||||
|
||||
async def seed_default_data(session: AsyncSession) -> None:
|
||||
# 1) 确保系统模板
|
||||
templates = await ensure_all_system_templates(session)
|
||||
log_with_context("已确保系统模板", count=len(templates))
|
||||
|
||||
# 2) 创建默认管理员用户(仅首次启动)
|
||||
result = await session.execute(select(User).limit(1))
|
||||
admin_user = result.scalar_one_or_none()
|
||||
if admin_user is None:
|
||||
admin_username = settings.default_admin_username or "admin"
|
||||
admin_email = settings.default_admin_email or "admin@hunjing.local"
|
||||
admin_password = settings.default_admin_password or "admin123"
|
||||
admin_user = await create_user(
|
||||
session,
|
||||
username=admin_username,
|
||||
email=admin_email,
|
||||
password=admin_password,
|
||||
is_admin=True,
|
||||
)
|
||||
await session.flush()
|
||||
log_with_context(
|
||||
"已创建默认管理员用户",
|
||||
username=admin_username,
|
||||
user_id=admin_user.id,
|
||||
)
|
||||
|
||||
# 3) RBAC 种子数据
|
||||
await _seed_rbac(session, admin_user)
|
||||
|
||||
# 4) 使用 admin_user 的 owner_scope 创建默认人物
|
||||
owner_scope = admin_user.id
|
||||
character = await ensure_default_character_for_owner(session, owner_scope)
|
||||
log_with_context(
|
||||
"已确保默认用户人物",
|
||||
character_id=character.id,
|
||||
source_template_id=character.source_template_id,
|
||||
owner_scope=owner_scope,
|
||||
)
|
||||
|
||||
# 4b) 兼容测试环境:确保 default_owner_scope 也有激活人物
|
||||
# repo/service 测试通过 get_active_character_model(session) 查找人物,
|
||||
# 其默认 owner_scope 来自 settings.default_owner_scope,与 admin_user.id 不一致。
|
||||
if settings.default_owner_scope and settings.default_owner_scope != owner_scope:
|
||||
await ensure_default_character_for_owner(session, settings.default_owner_scope)
|
||||
await session.commit()
|
||||
|
||||
await ensure_governance_seed(session)
|
||||
log_with_context("已确保司命台治理种子")
|
||||
|
||||
|
||||
async def _seed_rbac(session: AsyncSession, admin_user: User) -> None:
|
||||
"""创建默认角色、权限,并为 admin 用户分配管理员角色。"""
|
||||
|
||||
# 检查是否已初始化
|
||||
existing = await session.execute(select(Role).limit(1))
|
||||
if existing.scalar_one_or_none() is not None:
|
||||
return
|
||||
|
||||
# 创建所有权限
|
||||
perm_records: dict[str, Permission] = {}
|
||||
for p in DEFAULT_PERMISSIONS:
|
||||
perm = Permission(
|
||||
id=new_permission_id(),
|
||||
resource=p["resource"],
|
||||
action=p["action"],
|
||||
description=p["description"],
|
||||
)
|
||||
session.add(perm)
|
||||
perm_records[perm.code] = perm
|
||||
|
||||
# 创建角色
|
||||
admin_role = Role(
|
||||
id=new_role_id(),
|
||||
name="admin",
|
||||
description="管理员 — 拥有所有权限",
|
||||
is_system=True,
|
||||
)
|
||||
user_role = Role(
|
||||
id=new_role_id(),
|
||||
name="user",
|
||||
description="普通用户 — 基础操作权限",
|
||||
is_system=True,
|
||||
)
|
||||
session.add(admin_role)
|
||||
session.add(user_role)
|
||||
|
||||
# admin 角色:所有权限
|
||||
for perm in perm_records.values():
|
||||
session.add(RolePermission(role_id=admin_role.id, permission_id=perm.id))
|
||||
|
||||
# user 角色:仅基础读取权限
|
||||
user_perms = [p for p in DEFAULT_PERMISSIONS if p["action"] in ("read",)]
|
||||
for p in user_perms:
|
||||
perm = perm_records.get(f"{p['resource']}:{p['action']}")
|
||||
if perm:
|
||||
session.add(RolePermission(role_id=user_role.id, permission_id=perm.id))
|
||||
|
||||
# 分配 admin 角色给当前管理员用户
|
||||
session.add(UserRole(user_id=admin_user.id, role_id=admin_role.id))
|
||||
|
||||
await session.flush()
|
||||
log_with_context(
|
||||
"RBAC 种子数据已初始化",
|
||||
roles=2,
|
||||
permissions=len(perm_records),
|
||||
admin_user=admin_user.username,
|
||||
)
|
||||
@@ -0,0 +1,43 @@
|
||||
"""异步数据库会话。"""
|
||||
|
||||
from collections.abc import AsyncGenerator
|
||||
|
||||
from sqlalchemy.ext.asyncio import (
|
||||
AsyncEngine,
|
||||
AsyncSession,
|
||||
async_sessionmaker,
|
||||
create_async_engine,
|
||||
)
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
|
||||
def create_engine_from_url(database_url: str) -> AsyncEngine:
|
||||
connect_args: dict[str, object] = {}
|
||||
if database_url.startswith("sqlite"):
|
||||
connect_args["check_same_thread"] = False
|
||||
return create_async_engine(
|
||||
database_url,
|
||||
echo=settings.debug,
|
||||
pool_pre_ping=True,
|
||||
connect_args=connect_args,
|
||||
)
|
||||
|
||||
|
||||
engine = create_engine_from_url(settings.resolved_database_url)
|
||||
AsyncSessionLocal = async_sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)
|
||||
|
||||
|
||||
def configure_database(database_url: str) -> None:
|
||||
global AsyncSessionLocal, engine
|
||||
engine = create_engine_from_url(database_url)
|
||||
AsyncSessionLocal = async_sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)
|
||||
|
||||
|
||||
async def get_session() -> AsyncGenerator[AsyncSession, None]:
|
||||
async with AsyncSessionLocal() as session:
|
||||
yield session
|
||||
|
||||
|
||||
async def close_engine() -> None:
|
||||
await engine.dispose()
|
||||
Reference in new issue
Block a user