typed-store 1.0.0__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.
Files changed (49) hide show
  1. typed_store-1.0.0/.gitignore +10 -0
  2. typed_store-1.0.0/LICENSE +21 -0
  3. typed_store-1.0.0/PKG-INFO +206 -0
  4. typed_store-1.0.0/README.md +182 -0
  5. typed_store-1.0.0/docs/README.md +17 -0
  6. typed_store-1.0.0/docs/api-surface.md +268 -0
  7. typed_store-1.0.0/docs/design-spec.md +246 -0
  8. typed_store-1.0.0/docs/internal/progress.md +77 -0
  9. typed_store-1.0.0/docs/internal/templates/progress-template.md +31 -0
  10. typed_store-1.0.0/docs/internal/templates/spec-template.md +15 -0
  11. typed_store-1.0.0/docs/internal/workflow.md +232 -0
  12. typed_store-1.0.0/docs/publishing.md +137 -0
  13. typed_store-1.0.0/docs/superpowers/plans/2026-03-29-protocol-first-public-sdk-v0-2.md +733 -0
  14. typed_store-1.0.0/docs/superpowers/plans/2026-03-29-protocol-first-public-sdk-v0-3.md +717 -0
  15. typed_store-1.0.0/docs/superpowers/plans/2026-03-30-public-sdk-v1-0.md +1122 -0
  16. typed_store-1.0.0/docs/superpowers/specs/2026-03-29-public-sdk-protocol-first-design.md +390 -0
  17. typed_store-1.0.0/docs/superpowers/specs/2026-03-30-public-sdk-v1-design.md +304 -0
  18. typed_store-1.0.0/examples/async_basic.py +42 -0
  19. typed_store-1.0.0/examples/async_repository_pattern.py +83 -0
  20. typed_store-1.0.0/examples/bulk_operations.py +40 -0
  21. typed_store-1.0.0/examples/model_mixin.py +70 -0
  22. typed_store-1.0.0/examples/model_store_view.py +52 -0
  23. typed_store-1.0.0/examples/repository_pattern.py +74 -0
  24. typed_store-1.0.0/examples/sync_basic.py +33 -0
  25. typed_store-1.0.0/pyproject.toml +86 -0
  26. typed_store-1.0.0/tests/conftest.py +89 -0
  27. typed_store-1.0.0/tests/test_async_store.py +151 -0
  28. typed_store-1.0.0/tests/test_bound_model.py +102 -0
  29. typed_store-1.0.0/tests/test_error_boundaries.py +90 -0
  30. typed_store-1.0.0/tests/test_examples.py +85 -0
  31. typed_store-1.0.0/tests/test_protocols.py +78 -0
  32. typed_store-1.0.0/tests/test_public_api.py +56 -0
  33. typed_store-1.0.0/tests/test_shortcuts.py +376 -0
  34. typed_store-1.0.0/tests/test_specs.py +66 -0
  35. typed_store-1.0.0/tests/test_sync_store.py +145 -0
  36. typed_store-1.0.0/typed_store/__init__.py +90 -0
  37. typed_store-1.0.0/typed_store/async_store.py +346 -0
  38. typed_store-1.0.0/typed_store/bound_model.py +219 -0
  39. typed_store-1.0.0/typed_store/engine.py +90 -0
  40. typed_store-1.0.0/typed_store/errors.py +33 -0
  41. typed_store-1.0.0/typed_store/model.py +41 -0
  42. typed_store-1.0.0/typed_store/protocols.py +301 -0
  43. typed_store-1.0.0/typed_store/py.typed +1 -0
  44. typed_store-1.0.0/typed_store/results.py +15 -0
  45. typed_store-1.0.0/typed_store/session.py +50 -0
  46. typed_store-1.0.0/typed_store/specs.py +152 -0
  47. typed_store-1.0.0/typed_store/store.py +88 -0
  48. typed_store-1.0.0/typed_store/sync.py +346 -0
  49. typed_store-1.0.0/typed_store/uow.py +98 -0
@@ -0,0 +1,10 @@
1
+ .venv/
2
+ .pytest_cache/
3
+ .ruff_cache/
4
+ .mypy_cache/
5
+ __pycache__/
6
+ *.pyc
7
+ *.sqlite3
8
+ dist/
9
+ .DS_Store
10
+ .worktrees/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ticoag
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,206 @@
1
+ Metadata-Version: 2.4
2
+ Name: typed-store
3
+ Version: 1.0.0
4
+ Summary: Type-first data access SDK built on top of SQLAlchemy
5
+ Author: ticoag
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Keywords: data-access,orm,repository,sqlalchemy,typed
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Topic :: Database
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.12
21
+ Requires-Dist: greenlet>=3.1.1
22
+ Requires-Dist: sqlalchemy>=2.0.36
23
+ Description-Content-Type: text/markdown
24
+
25
+ # TypedStore
26
+
27
+ [![Python](https://img.shields.io/badge/python-%E2%89%A53.12-blue)](https://www.python.org/) [![SQLAlchemy](https://img.shields.io/badge/SQLAlchemy-%E2%89%A52.0.36-green)](https://www.sqlalchemy.org/) [![License: MIT](https://img.shields.io/badge/license-MIT-yellow)](LICENSE) [![Status](https://img.shields.io/badge/status-stable-green)]()
28
+
29
+ Type-first data access SDK built on top of SQLAlchemy 2.x.
30
+
31
+ TypedStore 把项目中重复出现的 session 管理、CRUD facade、分页与事务样板收敛为一组类型安全的 API——不替代 SQLAlchemy,只做它上层的薄 facade。
32
+
33
+ ## Features
34
+
35
+ - **Explicit sync / async** — `SyncTypedStore` 与 `AsyncTypedStore` 分离,无隐式双重语义
36
+ - **One-line init** — `SyncTypedStore.from_url("sqlite:///app.db")` 即可开始使用
37
+ - **Protocol-first queries** — `store.find_many(User, query=Query[User]().where(...))`
38
+ - **Bind-first models** — `User.bind(store).find_many()` 让模型天然具备显式绑定能力
39
+ - **Explicit request objects** — `Query`, `PageRequest`, `Patch`, `ProjectionQuery`
40
+ - **Bulk mutation APIs** — `bulk_update` / `bulk_delete` 与 object mutation 语义分离
41
+ - **UnitOfWork** — 明确的事务边界(sync / async)
42
+ - **TypedStoreModel** — 为模型提供纯函数式 `bind(store)` 入口
43
+ - **TypedStore composition root** — 通过 `.sync` / `.async_` 统一装配 sync / async store
44
+
45
+ ## Quick Start
46
+
47
+ ### Installation
48
+
49
+ ```bash
50
+ pip install typed-store
51
+ ```
52
+
53
+ <details>
54
+ <summary>本地开发</summary>
55
+
56
+ ```bash
57
+ uv sync --group dev
58
+ ```
59
+
60
+ </details>
61
+
62
+ ### Sync
63
+
64
+ ```python
65
+ from typed_store import PageRequest, Query, SyncTypedStore
66
+
67
+ store = SyncTypedStore.from_url("sqlite:///app.db")
68
+ Base.metadata.create_all(store.engine)
69
+
70
+ store.insert(User(name="alice"))
71
+ query = Query[User]().where(User.role == "admin").order(User.id.asc())
72
+ page = PageRequest(limit=10, offset=0)
73
+
74
+ store.find_many(User, query=query)
75
+ store.paginate(User, query=query, page=page)
76
+ ```
77
+
78
+ ### Async
79
+
80
+ ```python
81
+ from typed_store import AsyncTypedStore, PageRequest, Query
82
+
83
+ store = AsyncTypedStore.from_url("sqlite+aiosqlite:///app.db")
84
+
85
+ await store.insert(User(name="alice"))
86
+ query = Query[User]().where(User.role == "admin")
87
+ await store.find_many(User, query=query)
88
+ await store.paginate(User, query=query, page=PageRequest(limit=10, offset=0))
89
+ ```
90
+
91
+ ### TypedStore (sync + async)
92
+
93
+ ```python
94
+ from typed_store import Query, TypedStore
95
+
96
+ ts = TypedStore.from_url("sqlite:///app.db", async_url="sqlite+aiosqlite:///app.db")
97
+
98
+ # 显式使用 sync store
99
+ ts.sync.find_many(User, query=Query[User]())
100
+ ts.sync.insert(User(name="alice"))
101
+
102
+ # 异步明确走 .async_
103
+ await ts.async_.find_many(User, query=Query[User]())
104
+ ```
105
+
106
+ ### Model Binding
107
+
108
+ ```python
109
+ from typed_store import PageRequest, Query, SyncTypedStore, TypedStoreModel
110
+
111
+ store = SyncTypedStore.from_url("sqlite:///app.db")
112
+
113
+ class User(Base, TypedStoreModel):
114
+ ...
115
+
116
+ users = User.bind(store)
117
+ users.insert(User(name="alice"))
118
+ users.find_many(query=Query[User]().where(User.role == "admin"))
119
+ users.get(1)
120
+ users.paginate(query=Query[User](), page=PageRequest(limit=10, offset=0))
121
+ ```
122
+
123
+ ### Request Objects
124
+
125
+ ```python
126
+ from typed_store import PageRequest, Patch, Query
127
+
128
+ query = Query[User]().where(User.role == "admin").order(User.id.asc())
129
+ page = PageRequest(limit=10, offset=0)
130
+ patch = Patch[User]({"role": "superadmin"})
131
+
132
+ store.find_many(User, query=query)
133
+ store.paginate(User, query=query, page=page)
134
+ store.update(User, query=Query[User]().where(User.name == "alice"), patch=patch)
135
+ ```
136
+
137
+ ### Bulk Mutation
138
+
139
+ ```python
140
+ from typed_store import Patch, Query
141
+
142
+ store.bulk_update(
143
+ User,
144
+ query=Query[User]().where(User.role == "member"),
145
+ patch=Patch[User]({"role": "staff"}),
146
+ )
147
+
148
+ User.bind(store).bulk_delete(
149
+ query=Query[User]().where(User.role == "guest"),
150
+ )
151
+ ```
152
+
153
+ ### Model Mixin
154
+
155
+ ```python
156
+ from typed_store import Query, SyncTypedStore, TypedStoreModel
157
+
158
+ store = SyncTypedStore.from_url("sqlite:///app.db")
159
+
160
+ class User(Base, TypedStoreModel):
161
+ ...
162
+
163
+ users = User.bind(store)
164
+ users.insert(User(name="alice"))
165
+ items = users.find_many(query=Query[User]())
166
+ ```
167
+
168
+ ### Repository Pattern
169
+
170
+ ```python
171
+ store = SyncTypedStore.from_url("sqlite:///app.db")
172
+ repo = UserRepository(store)
173
+ service = UserService(store)
174
+ service.register_admin("alice@example.com")
175
+ ```
176
+
177
+ > 完整示例见 [`examples/`](examples/),对应 smoke 测试见 [`tests/test_examples.py`](tests/test_examples.py)。
178
+
179
+ ## Requirements
180
+
181
+ | Dependency | Version |
182
+ | ---------- | --------- |
183
+ | Python | >= 3.12 |
184
+ | SQLAlchemy | >= 2.0.36 |
185
+
186
+ ## Documentation
187
+
188
+ | Document | Description |
189
+ | -------------------------------------------- | ------------- |
190
+ | [`docs/api-surface.md`](docs/api-surface.md) | 完整 API 说明 |
191
+ | [`docs/design-spec.md`](docs/design-spec.md) | 设计规格 |
192
+ | [`docs/publishing.md`](docs/publishing.md) | 发布配置清单 |
193
+ | [`CHANGELOG.md`](CHANGELOG.md) | 版本变更记录 |
194
+
195
+ ## Development
196
+
197
+ ```bash
198
+ uv run pytest # tests
199
+ uv run ruff check . # lint
200
+ uv run ruff format --check . # format check
201
+ uv run ty check # type check
202
+ ```
203
+
204
+ ## License
205
+
206
+ [MIT](LICENSE)
@@ -0,0 +1,182 @@
1
+ # TypedStore
2
+
3
+ [![Python](https://img.shields.io/badge/python-%E2%89%A53.12-blue)](https://www.python.org/) [![SQLAlchemy](https://img.shields.io/badge/SQLAlchemy-%E2%89%A52.0.36-green)](https://www.sqlalchemy.org/) [![License: MIT](https://img.shields.io/badge/license-MIT-yellow)](LICENSE) [![Status](https://img.shields.io/badge/status-stable-green)]()
4
+
5
+ Type-first data access SDK built on top of SQLAlchemy 2.x.
6
+
7
+ TypedStore 把项目中重复出现的 session 管理、CRUD facade、分页与事务样板收敛为一组类型安全的 API——不替代 SQLAlchemy,只做它上层的薄 facade。
8
+
9
+ ## Features
10
+
11
+ - **Explicit sync / async** — `SyncTypedStore` 与 `AsyncTypedStore` 分离,无隐式双重语义
12
+ - **One-line init** — `SyncTypedStore.from_url("sqlite:///app.db")` 即可开始使用
13
+ - **Protocol-first queries** — `store.find_many(User, query=Query[User]().where(...))`
14
+ - **Bind-first models** — `User.bind(store).find_many()` 让模型天然具备显式绑定能力
15
+ - **Explicit request objects** — `Query`, `PageRequest`, `Patch`, `ProjectionQuery`
16
+ - **Bulk mutation APIs** — `bulk_update` / `bulk_delete` 与 object mutation 语义分离
17
+ - **UnitOfWork** — 明确的事务边界(sync / async)
18
+ - **TypedStoreModel** — 为模型提供纯函数式 `bind(store)` 入口
19
+ - **TypedStore composition root** — 通过 `.sync` / `.async_` 统一装配 sync / async store
20
+
21
+ ## Quick Start
22
+
23
+ ### Installation
24
+
25
+ ```bash
26
+ pip install typed-store
27
+ ```
28
+
29
+ <details>
30
+ <summary>本地开发</summary>
31
+
32
+ ```bash
33
+ uv sync --group dev
34
+ ```
35
+
36
+ </details>
37
+
38
+ ### Sync
39
+
40
+ ```python
41
+ from typed_store import PageRequest, Query, SyncTypedStore
42
+
43
+ store = SyncTypedStore.from_url("sqlite:///app.db")
44
+ Base.metadata.create_all(store.engine)
45
+
46
+ store.insert(User(name="alice"))
47
+ query = Query[User]().where(User.role == "admin").order(User.id.asc())
48
+ page = PageRequest(limit=10, offset=0)
49
+
50
+ store.find_many(User, query=query)
51
+ store.paginate(User, query=query, page=page)
52
+ ```
53
+
54
+ ### Async
55
+
56
+ ```python
57
+ from typed_store import AsyncTypedStore, PageRequest, Query
58
+
59
+ store = AsyncTypedStore.from_url("sqlite+aiosqlite:///app.db")
60
+
61
+ await store.insert(User(name="alice"))
62
+ query = Query[User]().where(User.role == "admin")
63
+ await store.find_many(User, query=query)
64
+ await store.paginate(User, query=query, page=PageRequest(limit=10, offset=0))
65
+ ```
66
+
67
+ ### TypedStore (sync + async)
68
+
69
+ ```python
70
+ from typed_store import Query, TypedStore
71
+
72
+ ts = TypedStore.from_url("sqlite:///app.db", async_url="sqlite+aiosqlite:///app.db")
73
+
74
+ # 显式使用 sync store
75
+ ts.sync.find_many(User, query=Query[User]())
76
+ ts.sync.insert(User(name="alice"))
77
+
78
+ # 异步明确走 .async_
79
+ await ts.async_.find_many(User, query=Query[User]())
80
+ ```
81
+
82
+ ### Model Binding
83
+
84
+ ```python
85
+ from typed_store import PageRequest, Query, SyncTypedStore, TypedStoreModel
86
+
87
+ store = SyncTypedStore.from_url("sqlite:///app.db")
88
+
89
+ class User(Base, TypedStoreModel):
90
+ ...
91
+
92
+ users = User.bind(store)
93
+ users.insert(User(name="alice"))
94
+ users.find_many(query=Query[User]().where(User.role == "admin"))
95
+ users.get(1)
96
+ users.paginate(query=Query[User](), page=PageRequest(limit=10, offset=0))
97
+ ```
98
+
99
+ ### Request Objects
100
+
101
+ ```python
102
+ from typed_store import PageRequest, Patch, Query
103
+
104
+ query = Query[User]().where(User.role == "admin").order(User.id.asc())
105
+ page = PageRequest(limit=10, offset=0)
106
+ patch = Patch[User]({"role": "superadmin"})
107
+
108
+ store.find_many(User, query=query)
109
+ store.paginate(User, query=query, page=page)
110
+ store.update(User, query=Query[User]().where(User.name == "alice"), patch=patch)
111
+ ```
112
+
113
+ ### Bulk Mutation
114
+
115
+ ```python
116
+ from typed_store import Patch, Query
117
+
118
+ store.bulk_update(
119
+ User,
120
+ query=Query[User]().where(User.role == "member"),
121
+ patch=Patch[User]({"role": "staff"}),
122
+ )
123
+
124
+ User.bind(store).bulk_delete(
125
+ query=Query[User]().where(User.role == "guest"),
126
+ )
127
+ ```
128
+
129
+ ### Model Mixin
130
+
131
+ ```python
132
+ from typed_store import Query, SyncTypedStore, TypedStoreModel
133
+
134
+ store = SyncTypedStore.from_url("sqlite:///app.db")
135
+
136
+ class User(Base, TypedStoreModel):
137
+ ...
138
+
139
+ users = User.bind(store)
140
+ users.insert(User(name="alice"))
141
+ items = users.find_many(query=Query[User]())
142
+ ```
143
+
144
+ ### Repository Pattern
145
+
146
+ ```python
147
+ store = SyncTypedStore.from_url("sqlite:///app.db")
148
+ repo = UserRepository(store)
149
+ service = UserService(store)
150
+ service.register_admin("alice@example.com")
151
+ ```
152
+
153
+ > 完整示例见 [`examples/`](examples/),对应 smoke 测试见 [`tests/test_examples.py`](tests/test_examples.py)。
154
+
155
+ ## Requirements
156
+
157
+ | Dependency | Version |
158
+ | ---------- | --------- |
159
+ | Python | >= 3.12 |
160
+ | SQLAlchemy | >= 2.0.36 |
161
+
162
+ ## Documentation
163
+
164
+ | Document | Description |
165
+ | -------------------------------------------- | ------------- |
166
+ | [`docs/api-surface.md`](docs/api-surface.md) | 完整 API 说明 |
167
+ | [`docs/design-spec.md`](docs/design-spec.md) | 设计规格 |
168
+ | [`docs/publishing.md`](docs/publishing.md) | 发布配置清单 |
169
+ | [`CHANGELOG.md`](CHANGELOG.md) | 版本变更记录 |
170
+
171
+ ## Development
172
+
173
+ ```bash
174
+ uv run pytest # tests
175
+ uv run ruff check . # lint
176
+ uv run ruff format --check . # format check
177
+ uv run ty check # type check
178
+ ```
179
+
180
+ ## License
181
+
182
+ [MIT](LICENSE)
@@ -0,0 +1,17 @@
1
+ # Documentation
2
+
3
+ ## User Guide
4
+
5
+ | Document | Description |
6
+ |---|---|
7
+ | [api-surface.md](api-surface.md) | API reference — entry points, methods, usage scenarios |
8
+ | [design-spec.md](design-spec.md) | Design specification — goals, architecture, core concepts |
9
+ | [publishing.md](publishing.md) | Release & publishing guide for PyPI / TestPyPI |
10
+
11
+ ## Internal (Development)
12
+
13
+ | Document | Description |
14
+ |---|---|
15
+ | [internal/workflow.md](internal/workflow.md) | Development workflow & task recovery procedure |
16
+ | [internal/progress.md](internal/progress.md) | Current progress tracking for TypedStore |
17
+ | [internal/templates/](internal/templates/) | Spec & progress document templates |
@@ -0,0 +1,268 @@
1
+ # API Surface
2
+
3
+ 本文档描述 `TypedStore` 当前对外推荐的 public API。目标是让使用者快速判断:
4
+
5
+ - 从哪里进入
6
+ - store capability protocol 和 concrete store 的关系
7
+ - request objects 应该如何驱动查询、分页、更新和投影
8
+
9
+ ## 1. Primary Entry Points
10
+
11
+ 当前推荐的主入口只有四类:
12
+
13
+ - `SyncTypedStore`:同步 SQLAlchemy 路径的正式实现
14
+ - `AsyncTypedStore`:异步 SQLAlchemy 路径的正式实现
15
+ - `TypedStoreModel.bind(store)`:模型一等能力的绑定入口
16
+ - `TypedStore`:只负责组合 `.sync` / `.async_` 的 composition root
17
+
18
+ ## 2. Request Objects
19
+
20
+ `TypedStore` 的查询与变更都由显式 request objects 驱动,而不是内联 filter 参数。
21
+
22
+ ### `Query[TModel]`
23
+
24
+ 用于实体查询:
25
+
26
+ - `Query[TModel]()`:创建空查询
27
+ - `.where(*filters)`:追加过滤条件
28
+ - `.order(*clauses)`:追加排序
29
+ - `.limit_to(limit)`:设置 limit
30
+ - `.offset_by(offset)`:设置 offset
31
+ - `.with_options(*options)`:追加 SQLAlchemy loader options
32
+
33
+ ### `PageRequest`
34
+
35
+ 用于分页请求:
36
+
37
+ - `PageRequest(limit, offset=0)`
38
+
39
+ ### `Patch[TModel]`
40
+
41
+ 用于字段级更新:
42
+
43
+ - `Patch[TModel](values={"field": value})`
44
+
45
+ ### `ProjectionQuery[TRow]`
46
+
47
+ 用于显式列投影:
48
+
49
+ - `ProjectionQuery[TRow](*columns)`
50
+ - `.where(*filters)`
51
+ - `.order(*clauses)`
52
+ - `.with_options(*options)`
53
+
54
+ ## 3. Sync Capability Protocols
55
+
56
+ 同步 capability protocol 定义 public contract:
57
+
58
+ - `ReadableStoreProtocol[TModel]`
59
+ - `WritableStoreProtocol[TModel]`
60
+ - `PatchableStoreProtocol[TModel]`
61
+ - `BulkPatchableStoreProtocol[TModel]`
62
+ - `DeletableStoreProtocol[TModel]`
63
+ - `BulkDeletableStoreProtocol[TModel]`
64
+ - `StatementExecutorProtocol`
65
+ - `TransactionalStoreProtocol`
66
+ - `SyncModelBoundStoreProtocol[TModel]`
67
+
68
+ 这些协议的意义是定义“这个对象能做什么”,而不是要求使用者必须直接实例化协议本身。
69
+ 这些对象共同构成 `v1.0` 的稳定同步 public contract。
70
+
71
+ ## 4. Async Capability Protocols
72
+
73
+ 异步路径与同步路径保持明确分离,对应协议为:
74
+
75
+ - `AsyncReadableStoreProtocol[TModel]`
76
+ - `AsyncWritableStoreProtocol[TModel]`
77
+ - `AsyncPatchableStoreProtocol[TModel]`
78
+ - `AsyncBulkPatchableStoreProtocol[TModel]`
79
+ - `AsyncDeletableStoreProtocol[TModel]`
80
+ - `AsyncBulkDeletableStoreProtocol[TModel]`
81
+ - `AsyncStatementExecutorProtocol`
82
+ - `AsyncTransactionalStoreProtocol`
83
+ - `AsyncModelBoundStoreProtocol[TModel]`
84
+
85
+ 这些对象共同构成 `v1.0` 的稳定异步 public contract。
86
+
87
+ ## 5. `SyncTypedStore`
88
+
89
+ `SyncTypedStore` 是基于 SQLAlchemy `Session` 的同步实现。
90
+
91
+ ### Factory And Lifecycle
92
+
93
+ - `SyncTypedStore.from_url(url, *, echo=False, **engine_options)`
94
+ - `.engine`
95
+ - `.close()`
96
+ - `.dispose()`
97
+ - `.unit_of_work(*, auto_commit=True)`
98
+
99
+ ### CRUD And Query Methods
100
+
101
+ - `insert(entity, *, session=None, commit=True, refresh=False) -> TModel`
102
+ - `insert_many(entities, *, session=None, commit=True) -> list[TModel]`
103
+ - `get(model, ident, *, session=None) -> TModel | None`
104
+ - `find_one(model, *, query, session=None) -> TModel | None`
105
+ - `find_many(model, *, query, session=None) -> list[TModel]`
106
+ - `exists(model, *, query, session=None) -> bool`
107
+ - `count(model, *, query, session=None) -> int`
108
+ - `paginate(model, *, query, page, session=None) -> Page[TModel]`
109
+ - `update(model, *, query, patch, session=None, commit=True) -> int`
110
+ - `bulk_update(model, *, query, patch, session=None, commit=True) -> int`
111
+ - `delete(model, *, query, session=None, commit=True) -> int`
112
+ - `bulk_delete(model, *, query, session=None, commit=True) -> int`
113
+ - `select_rows(model, *, projection, session=None) -> list[TRow]`
114
+ - `select_scalars(statement, *, session=None) -> list[TScalar]`
115
+
116
+ ### Example
117
+
118
+ ```python
119
+ from typed_store import PageRequest, Query, SyncTypedStore
120
+
121
+ store = SyncTypedStore.from_url("sqlite:///app.db")
122
+
123
+ admins = store.find_many(
124
+ User,
125
+ query=Query[User]().where(User.role == "admin").order(User.id.asc()),
126
+ )
127
+
128
+ page = store.paginate(
129
+ User,
130
+ query=Query[User]().where(User.role == "admin").order(User.id.asc()),
131
+ page=PageRequest(limit=20, offset=0),
132
+ )
133
+ ```
134
+
135
+ ## 6. `AsyncTypedStore`
136
+
137
+ `AsyncTypedStore` 是基于 SQLAlchemy `AsyncSession` 的异步实现。
138
+
139
+ ### Factory And Lifecycle
140
+
141
+ - `AsyncTypedStore.from_url(url, *, echo=False, **engine_options)`
142
+ - `.engine`
143
+ - `.aclose()`
144
+ - `.dispose()`
145
+ - `.unit_of_work(*, auto_commit=True)`
146
+
147
+ ### CRUD And Query Methods
148
+
149
+ 签名与 `SyncTypedStore` 对称,但所有行为都是 `async def`。
150
+
151
+ ### Example
152
+
153
+ ```python
154
+ from typed_store import AsyncTypedStore, PageRequest, Query
155
+
156
+ store = AsyncTypedStore.from_url("sqlite+aiosqlite:///app.db")
157
+
158
+ items = await store.find_many(
159
+ User,
160
+ query=Query[User]().where(User.role == "admin"),
161
+ )
162
+
163
+ page = await store.paginate(
164
+ User,
165
+ query=Query[User]().where(User.role == "admin").order(User.id.asc()),
166
+ page=PageRequest(limit=20, offset=0),
167
+ )
168
+ ```
169
+
170
+ ## 7. `TypedStore`
171
+
172
+ `TypedStore` 是 composition root,不是直接 CRUD facade。
173
+
174
+ ### Surface
175
+
176
+ - `TypedStore.from_url(url=None, *, async_url=None, echo=False, **engine_options)`
177
+ - `.engine`
178
+ - `.async_engine`
179
+ - `.sync`
180
+ - `.async_`
181
+ - `.close()`
182
+ - `.dispose()`
183
+ - `.aclose()`
184
+ - `.unit_of_work(*, auto_commit=True)`
185
+ - `.async_unit_of_work(*, auto_commit=True)`
186
+
187
+ ### Example
188
+
189
+ ```python
190
+ from typed_store import Query, TypedStore
191
+
192
+ ts = TypedStore.from_url("sqlite:///app.db", async_url="sqlite+aiosqlite:///app.db")
193
+
194
+ ts.sync.find_many(User, query=Query[User]())
195
+ await ts.async_.find_many(User, query=Query[User]())
196
+ ```
197
+
198
+ ## 8. `TypedStoreModel.bind(store)`
199
+
200
+ 模型能力是一等 public API,但只有绑定后才可操作。
201
+
202
+ ### Rules
203
+
204
+ - 只有 `bind(store)`,没有隐式默认 store
205
+ - `bind()` 是纯函数式绑定,不会把 store 写回模型类
206
+ - sync model view 依赖 sync capability protocols
207
+ - async model view 依赖 async capability protocols
208
+
209
+ ### Bound Model Methods
210
+
211
+ `Model.bind(store)` 返回的 bound model view 提供:
212
+
213
+ - `insert`
214
+ - `insert_many`
215
+ - `get`
216
+ - `find_one`
217
+ - `find_many`
218
+ - `exists`
219
+ - `count`
220
+ - `paginate`
221
+ - `update`
222
+ - `bulk_update`
223
+ - `delete`
224
+ - `bulk_delete`
225
+
226
+ ### Example
227
+
228
+ ```python
229
+ from typed_store import PageRequest, Patch, Query
230
+
231
+ users = User.bind(store)
232
+
233
+ items = users.find_many(
234
+ query=Query[User]().where(User.role == "admin").order(User.id.asc()),
235
+ )
236
+
237
+ updated = users.update(
238
+ query=Query[User]().where(User.email == "alice@example.com"),
239
+ patch=Patch[User]({"role": "staff"}),
240
+ )
241
+
242
+ page = users.paginate(
243
+ query=Query[User]().where(User.role == "staff"),
244
+ page=PageRequest(limit=10, offset=0),
245
+ )
246
+ ```
247
+
248
+ ## 9. Error Boundary
249
+
250
+ 当前 public API 的运行时保护只保留 contract/configuration 级错误:
251
+
252
+ - 缺失 sync / async session factory
253
+ - `bind()` 传入不满足协议的 store
254
+ - bulk mutation 使用了不支持的 `Query` 形态
255
+ - 调用签名明显错误,例如遗漏 `projection=` 这样的关键字参数
256
+
257
+ 数据本身的业务校验不由 `TypedStore` 重复承担,默认依赖边界层与静态类型系统。
258
+
259
+ ## 10. Stable v1.0 Surface
260
+
261
+ 以下对象属于 `v1.0` 稳定 public API:
262
+
263
+ - stores: `SyncTypedStore`, `AsyncTypedStore`, `TypedStore`
264
+ - models: `TypedStoreModel`, `SyncBoundModelView`, `AsyncBoundModelView`
265
+ - request objects: `Query`, `PageRequest`, `Patch`, `ProjectionQuery`
266
+ - results: `Page`
267
+ - protocols: readable / writable / patchable / bulk / deletable / statement / transactional variants
268
+ - support objects: `UnitOfWork`, `AsyncUnitOfWork`, `SessionProvider`