ns-orm 0.0.0__tar.gz → 0.0.1__tar.gz
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.
- {ns_orm-0.0.0 → ns_orm-0.0.1}/MANIFEST.in +1 -1
- {ns_orm-0.0.0/src/ns_orm.egg-info → ns_orm-0.0.1}/PKG-INFO +1 -1
- ns_orm-0.0.1/doc/async.md +40 -0
- ns_orm-0.0.1/doc/database.md +60 -0
- ns_orm-0.0.1/doc/index.md +22 -0
- ns_orm-0.0.1/doc/migrations.md +87 -0
- ns_orm-0.0.1/doc/models.md +70 -0
- ns_orm-0.0.1/doc/queryset.md +69 -0
- ns_orm-0.0.1/doc/relations.md +92 -0
- ns_orm-0.0.1/doc/release.md +45 -0
- ns_orm-0.0.1/doc/schema.md +32 -0
- {ns_orm-0.0.0 → ns_orm-0.0.1}/pyproject.toml +1 -1
- {ns_orm-0.0.0 → ns_orm-0.0.1/src/ns_orm.egg-info}/PKG-INFO +1 -1
- {ns_orm-0.0.0 → ns_orm-0.0.1}/src/ns_orm.egg-info/SOURCES.txt +9 -0
- {ns_orm-0.0.0 → ns_orm-0.0.1}/LICENSE +0 -0
- {ns_orm-0.0.0 → ns_orm-0.0.1}/README.md +0 -0
- {ns_orm-0.0.0 → ns_orm-0.0.1}/setup.cfg +0 -0
- {ns_orm-0.0.0 → ns_orm-0.0.1}/src/ns_orm/__init__.py +0 -0
- {ns_orm-0.0.0 → ns_orm-0.0.1}/src/ns_orm/cli.py +0 -0
- {ns_orm-0.0.0 → ns_orm-0.0.1}/src/ns_orm/database.py +0 -0
- {ns_orm-0.0.0 → ns_orm-0.0.1}/src/ns_orm/dialects.py +0 -0
- {ns_orm-0.0.0 → ns_orm-0.0.1}/src/ns_orm/exceptions.py +0 -0
- {ns_orm-0.0.0 → ns_orm-0.0.1}/src/ns_orm/expressions.py +0 -0
- {ns_orm-0.0.0 → ns_orm-0.0.1}/src/ns_orm/fields.py +0 -0
- {ns_orm-0.0.0 → ns_orm-0.0.1}/src/ns_orm/manager.py +0 -0
- {ns_orm-0.0.0 → ns_orm-0.0.1}/src/ns_orm/migrations/__init__.py +0 -0
- {ns_orm-0.0.0 → ns_orm-0.0.1}/src/ns_orm/migrations/autodetector.py +0 -0
- {ns_orm-0.0.0 → ns_orm-0.0.1}/src/ns_orm/migrations/executor.py +0 -0
- {ns_orm-0.0.0 → ns_orm-0.0.1}/src/ns_orm/migrations/loader.py +0 -0
- {ns_orm-0.0.0 → ns_orm-0.0.1}/src/ns_orm/migrations/migration.py +0 -0
- {ns_orm-0.0.0 → ns_orm-0.0.1}/src/ns_orm/migrations/operations.py +0 -0
- {ns_orm-0.0.0 → ns_orm-0.0.1}/src/ns_orm/migrations/state.py +0 -0
- {ns_orm-0.0.0 → ns_orm-0.0.1}/src/ns_orm/migrations/writer.py +0 -0
- {ns_orm-0.0.0 → ns_orm-0.0.1}/src/ns_orm/model.py +0 -0
- {ns_orm-0.0.0 → ns_orm-0.0.1}/src/ns_orm/query.py +0 -0
- {ns_orm-0.0.0 → ns_orm-0.0.1}/src/ns_orm/schema.py +0 -0
- {ns_orm-0.0.0 → ns_orm-0.0.1}/src/ns_orm/typing.py +0 -0
- {ns_orm-0.0.0 → ns_orm-0.0.1}/src/ns_orm/utils.py +0 -0
- {ns_orm-0.0.0 → ns_orm-0.0.1}/src/ns_orm.egg-info/dependency_links.txt +0 -0
- {ns_orm-0.0.0 → ns_orm-0.0.1}/src/ns_orm.egg-info/entry_points.txt +0 -0
- {ns_orm-0.0.0 → ns_orm-0.0.1}/src/ns_orm.egg-info/requires.txt +0 -0
- {ns_orm-0.0.0 → ns_orm-0.0.1}/src/ns_orm.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# 异步用法
|
|
2
|
+
|
|
3
|
+
## AsyncDatabase 与异步 CRUD
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
from ns_orm import AsyncDatabase, acreate_all
|
|
7
|
+
from ns_orm.dialects import dialect_from_url
|
|
8
|
+
|
|
9
|
+
executor = ... # 由 lesscode-database 创建(异步执行器)
|
|
10
|
+
db = AsyncDatabase(executor=executor, dialect=dialect_from_url("sqlite"))
|
|
11
|
+
await acreate_all(db, [User, Post])
|
|
12
|
+
|
|
13
|
+
u = await User.objects.using(db).acreate(name="alice")
|
|
14
|
+
posts = await Post.objects.using(db).filter(user_id=u.id).all()
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## 异步 QuerySet
|
|
18
|
+
|
|
19
|
+
当你通过 `Model.objects.using(async_db)` 绑定到 `AsyncDatabase` 后,使用以下方法:
|
|
20
|
+
|
|
21
|
+
- `acreate(...)`
|
|
22
|
+
- `aget(...)`
|
|
23
|
+
- `aall()`
|
|
24
|
+
|
|
25
|
+
QuerySet 本身的执行方法在异步模式下仍为协程:
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
posts = await Post.objects.using(db).filter(title__contains="o").all()
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 事务
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
async with db.transaction():
|
|
35
|
+
await User.objects.using(db).acreate(name="alice")
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 注意事项
|
|
39
|
+
|
|
40
|
+
- 执行器实现由 lesscode-database 决定;ns-orm 仅做 SQL 生成与参数风格转换
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# 数据库与执行器(lesscode-database)
|
|
2
|
+
|
|
3
|
+
## Database / AsyncDatabase
|
|
4
|
+
|
|
5
|
+
ns-orm 不负责连接创建,外部(lesscode-database)需要提供执行器 executor。
|
|
6
|
+
|
|
7
|
+
同步:
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from ns_orm import Database
|
|
11
|
+
from ns_orm.dialects import dialect_from_url
|
|
12
|
+
|
|
13
|
+
executor = ... # 由 lesscode-database 创建
|
|
14
|
+
db = Database(executor=executor, dialect=dialect_from_url("sqlite"))
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
异步:
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from ns_orm import AsyncDatabase
|
|
21
|
+
from ns_orm.dialects import dialect_from_url
|
|
22
|
+
|
|
23
|
+
executor = ... # 由 lesscode-database 创建(异步执行器)
|
|
24
|
+
db = AsyncDatabase(executor=executor, dialect=dialect_from_url("postgresql"))
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 事务
|
|
28
|
+
|
|
29
|
+
同步:
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
with db.transaction():
|
|
33
|
+
User.objects.using(db).create(name="alice")
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
异步:
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
async with db.transaction():
|
|
40
|
+
await User.objects.using(db).acreate(name="alice")
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## 方言(dialect)
|
|
44
|
+
|
|
45
|
+
当前方言用于:
|
|
46
|
+
|
|
47
|
+
- 标识符引用(引号)
|
|
48
|
+
- DDL 类型映射
|
|
49
|
+
- 参数风格转换(`:name` -> driver paramstyle)
|
|
50
|
+
|
|
51
|
+
常用值(可直接使用 URL scheme 的前缀):
|
|
52
|
+
|
|
53
|
+
- `sqlite`
|
|
54
|
+
- `clickhouse`
|
|
55
|
+
- `postgres` / `postgresql`
|
|
56
|
+
- `mysql`(也可传 `mariadb` / `tidb`)
|
|
57
|
+
- `mssql` / `sqlserver`
|
|
58
|
+
- `oracle`
|
|
59
|
+
|
|
60
|
+
驱动安装与连接配置请参考 lesscode-database。
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# ns-orm 文档
|
|
2
|
+
|
|
3
|
+
## 目录
|
|
4
|
+
|
|
5
|
+
- [模型与字段](models.md)
|
|
6
|
+
- [查询与 CRUD](queryset.md)
|
|
7
|
+
- [关联与预取](relations.md)
|
|
8
|
+
- [数据库与 URL](database.md)
|
|
9
|
+
- [建表(最小能力)](schema.md)
|
|
10
|
+
- [异步用法](async.md)
|
|
11
|
+
- [迁移命令(makemigrations/migrate)](migrations.md)
|
|
12
|
+
- [打包与发布](release.md)
|
|
13
|
+
|
|
14
|
+
## 设计概览
|
|
15
|
+
|
|
16
|
+
ns-orm 的目标是提供接近 Django ORM 的开发体验:
|
|
17
|
+
|
|
18
|
+
- 以 Pydantic 模型作为领域对象(数据校验、序列化、赋值校验)
|
|
19
|
+
- 以 QuerySet 作为查询构造器(链式调用、延迟执行的 SQL 构造)
|
|
20
|
+
- 以 Database/AsyncDatabase 作为数据库适配层(不同驱动、参数风格转换)
|
|
21
|
+
|
|
22
|
+
如果你需要在框架未内置的数据库上使用,请优先选择该数据库的 DB-API 2.0 驱动(同步)或提供等价的异步驱动。
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# 迁移命令(makemigrations / migrate)
|
|
2
|
+
|
|
3
|
+
ns-orm 提供类似 Django 的迁移能力:
|
|
4
|
+
|
|
5
|
+
- `makemigrations`:对比“上一次迁移快照”和当前 Model 定义,生成新的迁移文件
|
|
6
|
+
- `migrate`:执行迁移文件中的 SQL,并记录已应用迁移
|
|
7
|
+
|
|
8
|
+
## 前置:数据库连接由 lesscode-database 提供
|
|
9
|
+
|
|
10
|
+
ns-orm 不负责创建连接/连接池。你需要在自己的项目中使用 `lesscode-database` 创建执行器,并包装为 `ns_orm.Database` 或 `ns_orm.AsyncDatabase`。
|
|
11
|
+
|
|
12
|
+
迁移命令通过 `--db-factory` 调用你提供的工厂函数来获得 Database 对象。
|
|
13
|
+
|
|
14
|
+
## 1) makemigrations
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
ns-orm makemigrations \
|
|
18
|
+
--models app.models \
|
|
19
|
+
--dialect postgresql \
|
|
20
|
+
--name add_user_email \
|
|
21
|
+
--migrations-dir ./migrations
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
参数说明:
|
|
25
|
+
|
|
26
|
+
- `--models`:包含 Model 定义的模块路径(逗号分隔)
|
|
27
|
+
- `--dialect`:方言(或 URL scheme),例如 `postgresql` / `mysql` / `sqlite`
|
|
28
|
+
- `--name`:迁移名称后缀
|
|
29
|
+
- `--migrations-dir`:迁移文件目录(默认 `./migrations`)
|
|
30
|
+
|
|
31
|
+
输出:生成 `YYYYMMDDhhmmss_xxx.py` 迁移文件。
|
|
32
|
+
|
|
33
|
+
## 2) migrate
|
|
34
|
+
|
|
35
|
+
你需要提供 `db_factory(dialect)`:
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
# project/db.py
|
|
39
|
+
from ns_orm import Database
|
|
40
|
+
from ns_orm.dialects import dialect_from_url
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_db(dialect):
|
|
44
|
+
# 这里应使用 lesscode-database 创建执行器(示意)
|
|
45
|
+
# executor = lesscode_database.get_executor(...)
|
|
46
|
+
executor = ...
|
|
47
|
+
return Database(executor=executor, dialect=dialect)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
执行迁移:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
ns-orm migrate \
|
|
54
|
+
--models app.models \
|
|
55
|
+
--dialect postgresql \
|
|
56
|
+
--db-factory project.db:get_db \
|
|
57
|
+
--migrations-dir ./migrations
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
只看计划(不执行):
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
ns-orm migrate \
|
|
64
|
+
--models app.models \
|
|
65
|
+
--dialect postgresql \
|
|
66
|
+
--db-factory project.db:get_db \
|
|
67
|
+
--migrations-dir ./migrations \
|
|
68
|
+
--plan
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
异步执行(要求 `db_factory` 返回 `ns_orm.AsyncDatabase`):
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
ns-orm migrate \
|
|
75
|
+
--models app.models \
|
|
76
|
+
--dialect postgresql \
|
|
77
|
+
--db-factory project.db:get_async_db \
|
|
78
|
+
--migrations-dir ./migrations \
|
|
79
|
+
--async
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## 当前支持范围与限制
|
|
83
|
+
|
|
84
|
+
- 支持:CreateTable、AddColumn、DropColumn(SQLite 的 DropColumn 不支持)
|
|
85
|
+
- 暂不支持:字段类型变更、重命名、索引/约束的自动迁移(可扩展 operations)
|
|
86
|
+
|
|
87
|
+
已应用迁移记录在数据库表 `ns_orm_migrations` 中。
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# 模型与字段
|
|
2
|
+
|
|
3
|
+
## 定义模型
|
|
4
|
+
|
|
5
|
+
ns-orm 的模型继承自 `ns_orm.Model`,字段通过 `typing_extensions.Annotated` 绑定字段元数据:
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from typing_extensions import Annotated
|
|
11
|
+
from ns_orm import Int, Model, String
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class User(Model):
|
|
15
|
+
id: Annotated[Optional[int], Int(primary_key=True)] = None
|
|
16
|
+
name: Annotated[str, String(max_length=50)]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
说明:
|
|
20
|
+
|
|
21
|
+
- 类型注解用于 Pydantic 校验与序列化
|
|
22
|
+
- `Annotated[..., FieldDef]` 中的 `FieldDef` 用于 ORM 元信息(列类型、主键、是否为空等)
|
|
23
|
+
|
|
24
|
+
## 字段类型
|
|
25
|
+
|
|
26
|
+
内置字段定义见 `ns_orm.fields`:
|
|
27
|
+
|
|
28
|
+
- 数值:`TinyInt`、`SmallInt`、`Int`、`BigInt`、`UTinyInt`、`USmallInt`、`UInt`、`UBigInt`、`Float`、`Real`、`Double`、`Decimal`
|
|
29
|
+
- 字符串:`Char(length=...)`、`String(max_length=...)`、`Text`、`FixedString(length=...)`
|
|
30
|
+
- 时间:`Date`、`DateTime`
|
|
31
|
+
- 其他:`Boolean`、`Binary`、`JSON`、`UUID`、`IPv4`、`IPv6`
|
|
32
|
+
- ClickHouse 复合类型:`Array(item=...)`、`Map(key=..., value=...)`、`TupleType(items=[...])`、`Nullable(inner=...)`、`LowCardinality(inner=...)`、`Date32`、`DateTime64`
|
|
33
|
+
|
|
34
|
+
通用字段选项:
|
|
35
|
+
|
|
36
|
+
- `primary_key`:主键
|
|
37
|
+
- `nullable`:是否可空(默认由数据库决定;建议显式设置)
|
|
38
|
+
- `unique`:唯一约束(列级)
|
|
39
|
+
- `index`:索引标记(当前仅保留元信息,不自动建索引)
|
|
40
|
+
- `default`/`server_default`:默认值(当前用于模型侧与 DDL 侧的最小表达)
|
|
41
|
+
|
|
42
|
+
## 模型元信息(Meta)
|
|
43
|
+
|
|
44
|
+
可通过内部类 `Meta` 指定表名与 schema:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
class User(Model):
|
|
48
|
+
class Meta:
|
|
49
|
+
table_name = "app_user"
|
|
50
|
+
schema = "public"
|
|
51
|
+
|
|
52
|
+
id: Annotated[Optional[int], Int(primary_key=True)] = None
|
|
53
|
+
name: Annotated[str, String(max_length=50)]
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
默认表名为类名的 snake_case(例如 `BlogPost` -> `blog_post`)。
|
|
57
|
+
|
|
58
|
+
## 主键约定
|
|
59
|
+
|
|
60
|
+
- 如果字段中存在 `primary_key=True`,将使用该字段作为主键
|
|
61
|
+
- 否则如果存在 `id` 字段,将使用 `id` 作为主键
|
|
62
|
+
|
|
63
|
+
## 模型序列化
|
|
64
|
+
|
|
65
|
+
模型仍然是 Pydantic 模型:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
u = User(id=1, name="alice")
|
|
69
|
+
payload = u.model_dump()
|
|
70
|
+
```
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# 查询与 CRUD
|
|
2
|
+
|
|
3
|
+
## 基本用法
|
|
4
|
+
|
|
5
|
+
获取 Manager:
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
m = User.objects.using(db)
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
获取 QuerySet:
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
qs = User.objects.using(db).qs()
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
常用方法:
|
|
18
|
+
|
|
19
|
+
- `all()`:返回列表
|
|
20
|
+
- `first()`:返回首个或 None
|
|
21
|
+
- `get(**lookups)`:返回单个对象,不存在抛 `DoesNotExist`,多条抛 `MultipleObjectsReturned`
|
|
22
|
+
- `create(**data)`:插入并返回实例
|
|
23
|
+
- `update(**data)`:批量更新,返回影响行数
|
|
24
|
+
- `delete()`:批量删除,返回影响行数
|
|
25
|
+
|
|
26
|
+
示例:
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
u = User.objects.using(db).create(name="alice")
|
|
30
|
+
u2 = User.objects.using(db).get(id=u.id)
|
|
31
|
+
User.objects.using(db).filter(id=u.id).update(name="bob")
|
|
32
|
+
User.objects.using(db).filter(id=u.id).delete()
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 链式查询
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
users = (
|
|
39
|
+
User.objects.using(db)
|
|
40
|
+
.filter(name__icontains="al")
|
|
41
|
+
.order_by("-id")
|
|
42
|
+
.limit(10)
|
|
43
|
+
.offset(0)
|
|
44
|
+
.all()
|
|
45
|
+
)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## lookups
|
|
49
|
+
|
|
50
|
+
`filter/exclude` 支持 Django 风格的 `field__lookup=value`:
|
|
51
|
+
|
|
52
|
+
- 精确:`field=value` 或 `field__exact=value`
|
|
53
|
+
- 比较:`__gt` `__gte` `__lt` `__lte`
|
|
54
|
+
- 集合:`__in=[...]`
|
|
55
|
+
- 字符串匹配:`__contains` `__icontains` `__startswith` `__istartswith` `__endswith` `__iendswith`
|
|
56
|
+
- 空值:`__isnull=True/False`
|
|
57
|
+
|
|
58
|
+
示例:
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
User.objects.using(db).filter(id__in=[1, 2, 3]).all()
|
|
62
|
+
User.objects.using(db).exclude(name__startswith="test").all()
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Q 表达式
|
|
66
|
+
|
|
67
|
+
`ns_orm.Q` 提供了 AND/OR/NOT 的条件树抽象,当前版本主要用于内部组合条件。
|
|
68
|
+
|
|
69
|
+
当前 QuerySet API 暂不提供 `filter(Q(...))` 入口;日常查询建议使用 `filter/exclude` 的 lookups 组合表达业务条件。
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# 关联与预取
|
|
2
|
+
|
|
3
|
+
## ForeignKey / OneToOne
|
|
4
|
+
|
|
5
|
+
定义外键字段:
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
from typing import List, Optional
|
|
9
|
+
|
|
10
|
+
from typing_extensions import Annotated
|
|
11
|
+
from ns_orm import ForeignKey, Int, Model, String
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class User(Model):
|
|
15
|
+
id: Annotated[Optional[int], Int(primary_key=True)] = None
|
|
16
|
+
name: Annotated[str, String(max_length=50)]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Post(Model):
|
|
20
|
+
id: Annotated[Optional[int], Int(primary_key=True)] = None
|
|
21
|
+
user_id: Annotated[int, ForeignKey("User", on_delete="CASCADE")]
|
|
22
|
+
title: Annotated[str, String(max_length=200)]
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
约定:
|
|
26
|
+
|
|
27
|
+
- 外键列一般命名为 `xxx_id`
|
|
28
|
+
- 预取关系名默认为 `xxx`(例如 `user_id` -> `user`)
|
|
29
|
+
|
|
30
|
+
使用预取:
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
posts = Post.objects.using(db).prefetch_related("user").all()
|
|
34
|
+
print(posts[0].user.name)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`OneToOne` 与 `ForeignKey` 的区别在于默认 `unique=True`。
|
|
38
|
+
|
|
39
|
+
## ManyToMany
|
|
40
|
+
|
|
41
|
+
定义多对多字段:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from typing import Optional
|
|
45
|
+
|
|
46
|
+
from typing_extensions import Annotated
|
|
47
|
+
from ns_orm import Int, ManyToMany, Model, String
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Role(Model):
|
|
51
|
+
id: Annotated[Optional[int], Int(primary_key=True)] = None
|
|
52
|
+
name: Annotated[str, String(max_length=50)]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class User(Model):
|
|
56
|
+
id: Annotated[Optional[int], Int(primary_key=True)] = None
|
|
57
|
+
name: Annotated[str, String(max_length=50)]
|
|
58
|
+
roles: Annotated[List[Role], ManyToMany("Role")]
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
中间表约定:
|
|
62
|
+
|
|
63
|
+
- 默认表名:`{from_table}_{to_table}`
|
|
64
|
+
- 默认列名:
|
|
65
|
+
- from 侧:`{from_table}_{from_pk}`
|
|
66
|
+
- to 侧:`{to_table}_{to_pk}`
|
|
67
|
+
|
|
68
|
+
你也可以显式指定:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
roles: Annotated[
|
|
72
|
+
List[Role],
|
|
73
|
+
ManyToMany(
|
|
74
|
+
"Role",
|
|
75
|
+
through="user_role",
|
|
76
|
+
from_field="user_id",
|
|
77
|
+
to_field="role_id",
|
|
78
|
+
),
|
|
79
|
+
]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
预取多对多:
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
users = User.objects.using(db).prefetch_related("roles").all()
|
|
86
|
+
print([r.name for r in users[0].roles])
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
说明:
|
|
90
|
+
|
|
91
|
+
- 当前版本仅提供读取侧预取(prefetch),暂不提供 `add/remove/clear` 等写入 API
|
|
92
|
+
- 中间表的建表由 `create_all/acreate_all` 自动创建(最小能力)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# 打包与发布
|
|
2
|
+
|
|
3
|
+
本文档描述发布流程与相关文件约定。发布流程相关文件不参与打包发布(见仓库根目录的 `MANIFEST.in`)。
|
|
4
|
+
|
|
5
|
+
## 1. 版本号
|
|
6
|
+
|
|
7
|
+
在 `pyproject.toml` 中修改:
|
|
8
|
+
|
|
9
|
+
- `[project].version`
|
|
10
|
+
|
|
11
|
+
## 2. 构建产物
|
|
12
|
+
|
|
13
|
+
推荐在干净环境中构建:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
python -m pip install -U build twine
|
|
17
|
+
python -m build
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
构建产物:
|
|
21
|
+
|
|
22
|
+
- `dist/*.whl`
|
|
23
|
+
- `dist/*.tar.gz`
|
|
24
|
+
|
|
25
|
+
## 3. 校验
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
python -m twine check dist/*
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 4. 发布到 PyPI
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
python -m twine upload dist/*
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## 5. 发布文件不参与打包
|
|
38
|
+
|
|
39
|
+
当前通过 `MANIFEST.in` 排除以下目录/文件进入 sdist:
|
|
40
|
+
|
|
41
|
+
- `doc/`
|
|
42
|
+
- `tests/`
|
|
43
|
+
- `.gitee/`
|
|
44
|
+
|
|
45
|
+
wheel 包默认只包含 `src/` 下的 Python 包代码。
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# 建表(最小能力)
|
|
2
|
+
|
|
3
|
+
ns-orm 提供最小可用的建表能力,便于本地开发、快速验证与测试。
|
|
4
|
+
|
|
5
|
+
## create_all / acreate_all
|
|
6
|
+
|
|
7
|
+
同步建表:
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from ns_orm import create_all
|
|
11
|
+
|
|
12
|
+
create_all(db, [User, Post])
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
异步建表:
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from ns_orm import acreate_all
|
|
19
|
+
|
|
20
|
+
await acreate_all(db, [User, Post])
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
行为:
|
|
24
|
+
|
|
25
|
+
- 为每个模型生成 `CREATE TABLE IF NOT EXISTS ...`
|
|
26
|
+
- 为 ManyToMany 关系生成中间表(表名/列名遵循默认约定,或按 ManyToMany 显式配置)
|
|
27
|
+
|
|
28
|
+
## 当前限制
|
|
29
|
+
|
|
30
|
+
- 不提供 schema 迁移(migrations)
|
|
31
|
+
- 索引与复杂约束的自动生成尚未实现
|
|
32
|
+
- 不同数据库的 DDL 差异较大,当前 DDL 以通用 SQL 为主,复杂类型与高级特性需自行扩展
|
|
@@ -2,6 +2,15 @@ LICENSE
|
|
|
2
2
|
MANIFEST.in
|
|
3
3
|
README.md
|
|
4
4
|
pyproject.toml
|
|
5
|
+
doc/async.md
|
|
6
|
+
doc/database.md
|
|
7
|
+
doc/index.md
|
|
8
|
+
doc/migrations.md
|
|
9
|
+
doc/models.md
|
|
10
|
+
doc/queryset.md
|
|
11
|
+
doc/relations.md
|
|
12
|
+
doc/release.md
|
|
13
|
+
doc/schema.md
|
|
5
14
|
src/ns_orm/__init__.py
|
|
6
15
|
src/ns_orm/cli.py
|
|
7
16
|
src/ns_orm/database.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|