Metadata-Version: 2.4
Name: valar
Version: 2.0.9
Summary: valar for morghulis
Home-page: https://gitee.com/vmworkspace/valar_engine.git
Author: LYP
Author-email: liuyinpeng@buaa.edu.cn
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: Django<6.2,>=5.2.18
Requires-Dist: django-cors-headers<5.0,>=4.9.0
Requires-Dist: channels<5.0,>=4.3.2
Requires-Dist: asgiref<4.0,>=3.9.1
Requires-Dist: PyJWT<3.0,>=2.10.1
Requires-Dist: PyMySQL<2.0,>=1.1.2
Requires-Dist: minio<8.0,>=7.2.2
Requires-Dist: urllib3<3.0,>=2.5.0
Requires-Dist: daphne<5.0,>=4.2.3
Provides-Extra: daphne
Requires-Dist: daphne<5.0,>=4.2.3; extra == "daphne"
Dynamic: author
Dynamic: author-email
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: license-file
Dynamic: provides-extra
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# Valar

valar for morghulis

Valar 是面向 Morghulis 前端的 Django 后端组件，提供模型元数据、通用数据操作接口、账户与菜单管理，以及通过 WebSocket 回传结果和进度的后台任务入口。

## 安装

需要 Python 3.10 或更高版本。建议在虚拟环境中安装：

```shell
python -m pip install --upgrade valar
```

当前依赖包含 Django、Channels、Daphne、django-cors-headers、PyJWT、PyMySQL 和 MinIO 客户端。具体版本范围见 [requirements.txt](requirements.txt)。

## 快速创建项目

```shell
valar startproject myproject
cd myproject
python manage.py migrate
python manage.py runserver
```

也可以通过模块入口创建，或指定目标目录：

```shell
python -m valar startproject myproject
# 在当前目录生成项目
valar startproject myproject .
```

生成器在 Django 默认项目的基础上配置以下文件：

| 文件 | 配置内容 |
| --- | --- |
| `settings.py` | Valar、Daphne、Channels、CORS 中间件、内存 channel layer 和 handler 映射 |
| `asgi.py` | Django HTTP 应用与 Valar WebSocket 路由 |
| `urls.py` | `/admin/`、`/valar/` 和空的 `channel_mapping` |

生成器保留 Django 默认项目结构，并拒绝覆盖已有项目文件。默认 HTTP 地址为 `http://127.0.0.1:8000/`；项目未配置首页时，访问 `/` 返回 404 属于正常情况。

`migrate` 用于创建 Django 自带应用的数据库表；启动服务器时，Valar 还会执行自己的迁移与元数据初始化。

## 启动与元数据刷新

Valar 的 `runserver` 继承 Daphne 的开发服务器命令。启动时先执行：

```python
config.__auto_migrate__()
config.__meta_frame__()
```

前者生成并执行 Valar 应用的数据库迁移；后者删除 Valar 自身实体对应的元数据、重建字段工具和字段域配置，并根据代码重新生成 Valar 模型的视图元数据。

这意味着 **Valar 自身的元数据会在每次启动时恢复到代码定义的状态**。需要持久保留的调整应写入相应的模型或元数据定义中。

初始化在 Django 应用注册完成后的 `inner_run()` 中执行，避免在 `AppConfig.ready()` 中访问数据库。自动重载和 `--noreload` 都会执行初始化；初始化失败会中止服务器启动。

也可以单独运行：

```shell
python manage.py initialize_valar
```

直接使用生产 ASGI/WSGI 服务器不会调用 `runserver`。部署时应在启动服务进程前单独执行初始化命令。

## 接入已有 Django 项目

以下示例使用安装后的包名 `valar`，将 `myproject` 替换成实际项目包名。本仓库直接从源码运行时，应用路径为 `src.valar.apps.ValarConfig`。

### 配置 settings.py

在现有配置中加入以下内容。保留项目已有的 Django 应用和中间件：

```python
# Valar 必须排在 Daphne 和 django.contrib.staticfiles 前面，
# 确保 Django 选中 Valar 自定义的 runserver 命令。
INSTALLED_APPS = [
    'valar.apps.ValarConfig',
    'daphne',
    'corsheaders',
    'channels',
    # 原有应用……
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    # 原有中间件，包括 SessionMiddleware、CommonMiddleware 等……
    'valar.system.middleware.ValarMiddleware',
]

ASGI_APPLICATION = 'myproject.asgi.application'
VALAR_HANDLER_MAPPING = 'myproject.urls.channel_mapping'

CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels.layers.InMemoryChannelLayer',
    },
}
```

跨域访问时，按前端实际地址配置允许的来源及自定义请求头，例如：

```python
from corsheaders.defaults import default_headers

CORS_ALLOWED_ORIGINS = ['http://localhost:5173']
CORS_ALLOW_HEADERS = (*default_headers, 'client', 'auth')
```

默认内存 channel layer 用于本地单进程开发。多个服务进程需要配置共享的 channel layer 后端，并安装对应依赖。

### 配置 urls.py

显式挂载 Valar 路由，使开发服务器和生产服务器使用相同的 URL 配置：

```python
from django.urls import include, path

urlpatterns = [
    path('valar/', include('valar.urls')),
    # 原有路由……
]

channel_mapping = {}
```

### 配置 asgi.py

先初始化 Django，再导入可能依赖模型的消费者：

```python
import os

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')

from django.core.asgi import get_asgi_application

django_application = get_asgi_application()

from channels.routing import ProtocolTypeRouter, URLRouter
from django.urls import re_path
from valar.system.socket.consumer import ValarConsumer

application = ProtocolTypeRouter({
    'http': django_application,
    'websocket': URLRouter([
        re_path(r'(?P<client>\w+)/$', ValarConsumer.as_asgi()),
    ]),
})
```

## 注册后台任务 handler

Handler 是接收 `ChannelSender` 的普通同步函数。Valar 在线程中执行它，通过 WebSocket 回传结果。

在 `myproject/handlers.py` 中定义：

```python
from valar.data.channel.ChannelSender import ChannelSender


def echo(sender: ChannelSender):
    return {'message': sender.data.get('message', '')}
```

在 `myproject/urls.py` 中注册：

```python
from .handlers import echo

channel_mapping = {
    'echo': echo,
}
```

先建立 WebSocket 连接，例如 `ws://127.0.0.1:8000/browser_001/`。路径中的 `browser_001` 是客户端标识，应与 HTTP 请求中的 `Client` 一致。

然后发送任务请求：

```http
POST /valar/channel/echo/request_001
Content-Type: application/json
Client: browser_001
Auth: 1
Authorization: Bearer <登录获得的JWT>

{"data": {"message": "hello"}}
```

URL 中的 `request_001` 用于关联任务回调，与登录 JWT 不同。HTTP 响应表示任务已提交，业务结果通过 WebSocket 返回：

```json
{
  "__tag__": "channel",
  "handler": "echo",
  "token": "request_001",
  "callback": "onResponse",
  "data": {"message": "hello"}
}
```

任务生命周期包括 `onStart`、`onResponse` 或 `onError`、`onStop`；进度更新使用 `onProceed`。Handler 内可以通过 `sender.tick(index, length)` 更新进度，再调用 `sender.process()` 发送。

存在账户 ID 时，回调按账户发送，连接需要先通过业务登录流程绑定账户。服务端可使用 `ValarSender().register(account_id, client)` 进行绑定；客户端向 WebSocket 发送任意 JSON 不会自动完成注册。

## 数据与元数据接口

挂载 `/valar/` 后，主要接口如下。`<entity>` 使用 `应用标签.模型名`，例如 `valar.Account`；下表路径均以 `/valar/` 为前缀，且不带尾部斜杠。

| 路径 | 用途 |
| --- | --- |
| `<entity>/insert_one`、`<entity>/save_one` | 创建或保存单条数据 |
| `<entity>/fetch`、`<entity>/find_one`、`<entity>/find` | 按 ID 或条件读取数据 |
| `<entity>/values`、`<entity>/search/<code>` | 读取指定字段或分页查询 |
| `<entity>/delete_one`、`<entity>/sync_delete_many` | 删除单条或批量数据 |
| `<entity>/sync_save_many`、`<entity>/sync_update_many` | 批量保存或更新 |
| `<entity>/save_file`、`<entity>/remove_file` | 上传或移除文件 |
| `metas`、`<entity>/<code>/meta_view` | 读取模型和视图元数据 |
| `sign`、`register`、`load_user` | 账户登录、注册及用户信息 |
| `load_menu`、`view_menu` | 菜单读取 |

JSON 接口通常使用以下响应结构：

```json
{"payload": true, "message": null, "code": "info"}
```

各接口的请求体不同，完整路由和处理逻辑见 [data](src/valar/data/urls.py)、[meta](src/valar/meta/urls.py) 和 [auth](src/valar/auth/urls.py)。业务接入时需要按接口落实认证和权限规则。

## 扩展业务用户模型

需要关联 Valar 账户时，可以继承 Valar 的抽象用户模型：

```python
from django.db import models
from valar.models.auth import AbstractUser


class User(AbstractUser):
    description = models.TextField(null=True, verbose_name='备注')

    class Meta:
        verbose_name = '用户'
```

该模型继承 Valar 的基础字段及到账户模型的关联，并非 Django 自带的 `AbstractUser`。业务应用的迁移仍需自行生成、执行：

```shell
python manage.py makemigrations your_app
python manage.py migrate
```

## 可选配置

- **MySQL**：使用 PyMySQL 时，在项目包的 `__init__.py` 中调用 `pymysql.install_as_MySQLdb()`，并配置 Django 的 `DATABASES`。
- **文件存储**：使用 MinIO 接口时配置 `BASE_APP` 和 `VALAR_MINIO_URL`，后者格式为 `s3://<access_key>:<secret_key>@<host>:<port>`。
- **邮件**：使用邮件相关账户功能时，配置 Django 邮件后端以及 `EMAIL_HOST_USER` 等 SMTP 参数。

项目生成器不会自动填写 MinIO、邮件或业务账户配置。

## 本仓库开发

```shell
python -m pip install -r requirements.txt
python manage.py migrate
python manage.py runserver
```

项目生成与 WebSocket 启动测试：

```shell
python -m unittest discover -s tests -p 'test_startproject.py'
python -m unittest discover -s tests -p 'test_websocket_startup.py'
```

发布流程见 [PUBLISH.md](PUBLISH.md)。
