cloudoll 4.3.0__tar.gz → 4.5.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 (175) hide show
  1. {cloudoll-4.3.0 → cloudoll-4.5.0}/CHANGELOG.md +98 -5
  2. {cloudoll-4.3.0 → cloudoll-4.5.0}/PKG-INFO +12 -8
  3. {cloudoll-4.3.0 → cloudoll-4.5.0}/README.md +9 -6
  4. {cloudoll-4.3.0 → cloudoll-4.5.0}/README.zh-CN.md +13 -11
  5. cloudoll-4.5.0/cloudoll/__init__.py +1 -0
  6. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/ai/docs/changelog.md +54 -3
  7. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/ai/docs/config.md +17 -3
  8. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/ai/docs/cookie-and-session.md +6 -1
  9. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/ai/docs/database.md +9 -5
  10. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/ai/docs/deployment.md +14 -0
  11. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/ai/docs/jwt.md +9 -2
  12. cloudoll-4.5.0/cloudoll/ai/docs/mail.md +74 -0
  13. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/ai/docs/start.md +7 -5
  14. cloudoll-4.5.0/cloudoll/ai/docs/utils.md +74 -0
  15. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/ai/docs/view.md +3 -1
  16. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/ai/manifest.json +19 -9
  17. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/ai/skills/cloudoll/SKILL.md +6 -2
  18. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/cli.py +7 -1
  19. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/clitool/cli_main.py +67 -26
  20. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/clitool/m2d.py +25 -1
  21. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/clitool/schema.py +21 -5
  22. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/clitool/watch.py +37 -2
  23. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/mail/smtp.py +39 -53
  24. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/orm/__init__.py +8 -0
  25. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/orm/aws_engine.py +6 -1
  26. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/orm/base.py +1 -1
  27. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/orm/compiler.py +42 -6
  28. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/orm/dialects.py +15 -0
  29. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/orm/engine.py +26 -6
  30. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/orm/field.py +3 -0
  31. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/orm/parse.py +13 -1
  32. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/orm/protocols.py +4 -1
  33. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/orm/query.py +19 -0
  34. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/orm/savepoints.py +4 -0
  35. cloudoll-4.5.0/cloudoll/orm/sqlite.py +223 -0
  36. cloudoll-4.5.0/cloudoll/template/.gitignore +82 -0
  37. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/template/README.md +11 -1
  38. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/template/config/conf.local.yaml +10 -2
  39. cloudoll-4.5.0/cloudoll/template/middlewares/auth.py +38 -0
  40. cloudoll-4.5.0/cloudoll/template/requirements.txt +1 -0
  41. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/web/configuration.py +26 -1
  42. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/web/core.py +51 -9
  43. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/web/sessions.py +15 -20
  44. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll.egg-info/PKG-INFO +12 -8
  45. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll.egg-info/SOURCES.txt +9 -0
  46. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll.egg-info/requires.txt +1 -0
  47. {cloudoll-4.3.0 → cloudoll-4.5.0}/docs/maintenance.md +2 -0
  48. {cloudoll-4.3.0 → cloudoll-4.5.0}/pyproject.toml +3 -1
  49. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/check_wheel.py +5 -0
  50. cloudoll-4.5.0/tests/test_cli_create.py +192 -0
  51. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/test_dev_lifecycle.py +203 -3
  52. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/test_field_types.py +11 -0
  53. cloudoll-4.5.0/tests/test_mail_client.py +183 -0
  54. cloudoll-4.5.0/tests/test_password_and_id_utils.py +151 -0
  55. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/test_production_contracts.py +2 -0
  56. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/test_query_capabilities.py +23 -0
  57. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/test_regressions.py +46 -7
  58. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/test_request_contract.py +152 -1
  59. cloudoll-4.5.0/tests/test_sqlite_engine.py +70 -0
  60. cloudoll-4.5.0/tests/test_tls.py +21 -0
  61. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/test_transactions.py +60 -0
  62. cloudoll-4.3.0/cloudoll/__init__.py +0 -1
  63. cloudoll-4.3.0/cloudoll/template/middlewares/auth.py +0 -31
  64. cloudoll-4.3.0/cloudoll/template/requirements.txt +0 -1
  65. {cloudoll-4.3.0 → cloudoll-4.5.0}/AGENTS.md +0 -0
  66. {cloudoll-4.3.0 → cloudoll-4.5.0}/AI.md +0 -0
  67. {cloudoll-4.3.0 → cloudoll-4.5.0}/LICENSE +0 -0
  68. {cloudoll-4.3.0 → cloudoll-4.5.0}/MANIFEST.in +0 -0
  69. {cloudoll-4.3.0 → cloudoll-4.5.0}/SECURITY.md +0 -0
  70. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/__main__.py +0 -0
  71. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/ai/AGENTS.md +0 -0
  72. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/ai/__init__.py +0 -0
  73. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/ai/commands.py +0 -0
  74. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/ai/docs/ai.md +0 -0
  75. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/ai/docs/app-start.md +0 -0
  76. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/ai/docs/event-source.md +0 -0
  77. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/ai/docs/exception.md +0 -0
  78. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/ai/docs/file-uploads.md +0 -0
  79. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/ai/docs/http-client.md +0 -0
  80. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/ai/docs/logs.md +0 -0
  81. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/ai/docs/middleware.md +0 -0
  82. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/ai/docs/migration.md +0 -0
  83. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/ai/docs/router.md +0 -0
  84. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/ai/docs/structure.md +0 -0
  85. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/ai/docs/validation.md +0 -0
  86. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/ai/docs/websockets.md +0 -0
  87. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/clitool/__init__.py +0 -0
  88. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/clitool/process.py +0 -0
  89. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/clitool/spinner.py +0 -0
  90. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/logging/__init__.py +0 -0
  91. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/mail/__init__.py +0 -0
  92. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/observability.py +0 -0
  93. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/orm/awsmysql.py +0 -0
  94. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/orm/awspostgres.py +0 -0
  95. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/orm/datasources.py +0 -0
  96. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/orm/model.py +0 -0
  97. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/orm/mysql.py +0 -0
  98. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/orm/postgres.py +0 -0
  99. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/orm/relation.py +0 -0
  100. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/orm/streaming.py +0 -0
  101. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/orm/subquery.py +0 -0
  102. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/orm/test_sql.py +0 -0
  103. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/orm/values.py +0 -0
  104. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/py.typed +0 -0
  105. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/template/__init__.py +0 -0
  106. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/template/controllers/admin/test.py +0 -0
  107. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/template/controllers/api/index.py +0 -0
  108. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/template/controllers/es/es.py +0 -0
  109. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/template/controllers/home/index.py +0 -0
  110. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/template/controllers/upload/upload.py +0 -0
  111. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/template/controllers/ws/ws.py +0 -0
  112. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/template/static/css/index.css +0 -0
  113. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/template/static/img/cat.avif +0 -0
  114. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/template/static/js/index.js +0 -0
  115. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/template/templates/index.html +0 -0
  116. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/utils/__init__.py +0 -0
  117. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/utils/async_tools.py +0 -0
  118. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/utils/common.py +0 -0
  119. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/utils/password.py +0 -0
  120. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/utils/snowflake.py +0 -0
  121. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/web/__init__.py +0 -0
  122. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/web/context.py +0 -0
  123. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/web/exception.py +0 -0
  124. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/web/html.py +0 -0
  125. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/web/jwt.py +0 -0
  126. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/web/lifecycle.py +0 -0
  127. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/web/request_data.py +0 -0
  128. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/web/requests.py +0 -0
  129. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/web/resources.py +0 -0
  130. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/web/routing.py +0 -0
  131. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/web/settings.py +0 -0
  132. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/web/types.py +0 -0
  133. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll/web/validation.py +0 -0
  134. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll.egg-info/dependency_links.txt +0 -0
  135. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll.egg-info/entry_points.txt +0 -0
  136. {cloudoll-4.3.0 → cloudoll-4.5.0}/cloudoll.egg-info/top_level.txt +0 -0
  137. {cloudoll-4.3.0 → cloudoll-4.5.0}/docs/architecture.md +0 -0
  138. {cloudoll-4.3.0 → cloudoll-4.5.0}/docs/aurora.md +0 -0
  139. {cloudoll-4.3.0 → cloudoll-4.5.0}/docs/production.md +0 -0
  140. {cloudoll-4.3.0 → cloudoll-4.5.0}/docs/reliability.md +0 -0
  141. {cloudoll-4.3.0 → cloudoll-4.5.0}/docs/schema-generation.md +0 -0
  142. {cloudoll-4.3.0 → cloudoll-4.5.0}/docs/streaming-and-types.md +0 -0
  143. {cloudoll-4.3.0 → cloudoll-4.5.0}/scripts/sync_ai_docs.py +0 -0
  144. {cloudoll-4.3.0 → cloudoll-4.5.0}/setup.cfg +0 -0
  145. {cloudoll-4.3.0 → cloudoll-4.5.0}/setup.py +0 -0
  146. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/audit_dependencies.py +0 -0
  147. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/check_release.py +0 -0
  148. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/conftest.py +0 -0
  149. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/integration/test_aws_failover.py +0 -0
  150. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/integration/test_databases.py +0 -0
  151. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/integration/test_savepoints.py +0 -0
  152. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/integration/test_schema.py +0 -0
  153. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/integration/test_streaming.py +0 -0
  154. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/run_local_databases.py +0 -0
  155. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/test_ai.py +0 -0
  156. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/test_architecture.py +0 -0
  157. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/test_datasources.py +0 -0
  158. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/test_release_workflow.py +0 -0
  159. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/test_retry_and_query_regressions.py +0 -0
  160. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/test_review_fixes.py +0 -0
  161. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/test_save_snapshots_and_cleanup.py +0 -0
  162. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/test_savepoint_unit.py +0 -0
  163. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/test_schema_generation.py +0 -0
  164. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/test_sql_echo.py +0 -0
  165. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/test_startup_logging.py +0 -0
  166. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/test_streaming_unit.py +0 -0
  167. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/test_typing_regressions.py +0 -0
  168. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/test_validation.py +0 -0
  169. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/typing/engine_contract.py +0 -0
  170. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/typing/field_contract.py +0 -0
  171. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/typing/middleware_contract.py +0 -0
  172. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/typing/production_contract.py +0 -0
  173. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/typing/query_contract.py +0 -0
  174. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/typing/request_contract.py +0 -0
  175. {cloudoll-4.3.0 → cloudoll-4.5.0}/tests/typing/web_contract.py +0 -0
@@ -1,5 +1,101 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.5.0
4
+
5
+ - Add native HTTPS support to `Application.run()` using `server.ssl_certfile` and `server.ssl_keyfile`, or a supplied `server.ssl_context`.
6
+ - Add an async SQLite ORM engine with bounded pooling, transactions (including `IMMEDIATE` mode), streaming, URL parsing, SQLite SQL dialect, schema generation and model introspection.
7
+ - Add SQLite upsert support and include `aiosqlite` as a runtime dependency.
8
+
9
+ 简体中文见 [CHANGELOG.zh-CN.md](CHANGELOG.zh-CN.md)。
10
+
11
+ ## 4.4.0
12
+
13
+ Defect-fix release; backward compatible. Python 3.9+ remains supported.
14
+
15
+ ### Database
16
+
17
+ - `for_update()` row locks now require your own transaction. Outside one, the lock
18
+ was released by the commit that `for_update()` issued itself and protected
19
+ nothing; such a call now raises instead of silently doing nothing.
20
+ - A transaction cancelled or timed out while waiting for a connection gives that
21
+ connection back to the pool, so long-running services stop losing connections.
22
+ - Model fields are hashable again: columns can go into sets and serve as dict
23
+ keys (deduplicating columns, excluding groups of fields).
24
+ - `SUM(DISTINCT column)` and similar aggregate deduplication now happens inside
25
+ the aggregate, so values are deduplicated before being combined, and a literal
26
+ inside an aggregate is no longer bound twice.
27
+ - URL query parameters in an AWS datasource string are no longer handed to the
28
+ driver as unknown connection options.
29
+
30
+ ### Web and templates
31
+
32
+ - A response that has already started streaming keeps its body when an error
33
+ occurs; the JSON error handler no longer rewrites it. Streaming and SSE
34
+ endpoints stay intact.
35
+ - Page responses no longer repeat `charset` in `Content-Type`.
36
+ - `session` options (`key`, `max_age`, `httponly`, `secure`, `domain`, `path`,
37
+ `samesite`) apply to encrypted cookie, Redis and Memcached storage alike.
38
+ - `render_view()` keywords are `Response` options only, and the template context
39
+ is the positional mapping. A stray keyword now raises `TypeError` instead of
40
+ being silently merged into the context.
41
+ - Development servers bind to 127.0.0.1 by default and no longer occupy
42
+ `port + 1`.
43
+ - `server.ignore_dirs` is honoured for directories such as `logs`, so writing logs
44
+ no longer triggers a reload.
45
+ - The scaffolded authorization middleware now matches its documentation: it is an
46
+ async decorator, lets static paths through, and lets downstream failures reach
47
+ the error layer instead of being flattened into a 500 that echoes the exception
48
+ text.
49
+
50
+ ### Command line and new projects
51
+
52
+ - `cloudoll create` no longer copies `__pycache__` and `.pyc` files into the new
53
+ project.
54
+ - The last component of a project name must be letters, digits, underscore, dot
55
+ or dash; names with spaces or shell characters are refused, as is an existing
56
+ target directory.
57
+ - A failed creation exits with a non-zero status and removes the half-written
58
+ project instead of leaving it behind.
59
+ - New projects write independent JWT and session keys to `.env`, which
60
+ `.gitignore` keeps out of version control. Local starts no longer warn about a
61
+ random process-local session key, and logins survive a restart or an added
62
+ worker.
63
+
64
+ ### Mail and utilities
65
+
66
+ - Building a `cloudoll.mail.smtp.Client` no longer reaches the network, and one
67
+ client stays usable across sends and server idle timeouts: each `send()` opens
68
+ its own connection, carries one copy of the body, and closes the connection even
69
+ when the send fails.
70
+ - Missing credentials and an empty recipient address now raise a readable error
71
+ before anything is sent.
72
+ - `cloudoll.utils` - reversible encryption, keyed digests, random strings and the
73
+ distributed ID generator - is now official API with a
74
+ [utilities](https://cloudoll.chuchur.com/utils) page and regression tests. None
75
+ of them is a password hash; store user passwords with bcrypt or argon2.
76
+ - New [mail](https://cloudoll.chuchur.com/mail) page documents sending limits
77
+ (SMTPS 465 only, no STARTTLS, no retry or queue). The dead POP3 draft under
78
+ `cloudoll/mail` is gone - Cloudoll does not receive mail.
79
+
80
+ ### Upgrade notes
81
+
82
+ - `for_update()` outside a transaction and unexpected `render_view()` keywords now
83
+ raise rather than misbehaving quietly; audit those two call shapes before
84
+ upgrading.
85
+ - `cloudoll create` rejects names it used to accept, such as `my shop`, because
86
+ they reached a shell unsafely.
87
+ - Projects created before this release keep an old `config/conf.local.yaml`
88
+ without a session key; add `session.secret_key` (or set
89
+ `CLOUDOLL_SESSION_SECRET`) so sessions survive restarts and multiple workers.
90
+ - Existing `.env` files need the new `CLOUDOLL_SESSION_SECRET` entry; `.env` has
91
+ always been ignored by the project's `.gitignore`.
92
+
93
+ ### Documentation
94
+
95
+ - JWT middleware examples use `web.RequestKey` instead of a plain string key, so
96
+ aiohttp stops warning about it.
97
+ - The quick start and session pages describe how a new project injects its keys.
98
+
3
99
  ## 4.3.0
4
100
 
5
101
  ### ORM queries and writes
@@ -56,9 +152,6 @@ Backward-compatible feature release. Pydantic 2 (`>=2.0,<3`) is now a core
56
152
  runtime dependency. Existing handlers and explicit ORM engine bindings remain
57
153
  supported. Python 3.9+ remains supported by the CI matrix.
58
154
 
59
- Publication date is recorded by the release tag/PyPI; preparing these files does
60
- not publish the package.
61
-
62
155
  - Correct lifecycle callback and JWT payload type declarations for strict
63
156
  checking with current dependency annotations.
64
157
  - Include HTTP method and actual request path in exception logs; emit 4xx access
@@ -76,8 +169,8 @@ not publish the package.
76
169
 
77
170
  ## 4.0.0
78
171
 
79
- Release notes for 4.0.0. Publication date is recorded by the release tag/PyPI;
80
- preparing these files does not publish the package.
172
+ This release contains breaking changes to configuration, security, ORM and the
173
+ application lifecycle. Read the migration guide before upgrading.
81
174
 
82
175
  ### Breaking changes and migration
83
176
 
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudoll
3
- Version: 4.3.0
4
- Summary: Assist in quickly creating distributable microservices based on aiohttp, with a focus on simplicity and ease of use.
3
+ Version: 4.5.0
4
+ Summary: A distributable microservice framework with async ORM support for MySQL, PostgreSQL and SQLite.
5
5
  Author-email: Qiu <smallerqiu@gmail.com>
6
6
  License-Expression: MIT
7
7
  Project-URL: Homepage, https://github.com/smallerqiu/cloudoll-py
@@ -28,6 +28,7 @@ Requires-Dist: psutil
28
28
  Requires-Dist: tabulate
29
29
  Requires-Dist: concurrent_log_handler
30
30
  Requires-Dist: portalocker
31
+ Requires-Dist: aiosqlite<1,>=0.20
31
32
  Provides-Extra: cache
32
33
  Requires-Dist: redis[hiredis]; extra == "cache"
33
34
  Requires-Dist: aiomcache; extra == "cache"
@@ -54,8 +55,10 @@ Dynamic: license-file
54
55
 
55
56
  English | [简体中文](README.zh-CN.md)
56
57
 
57
- Cloudoll 4.3.0 is an aiohttp-based Python library for routing, middleware,
58
+ Cloudoll 4.5.0 is an aiohttp-based Python library for routing, middleware,
58
59
  templates, sessions, JWT, database access and application scaffolding.
60
+ The ORM supports SQLite out of the box and MySQL/PostgreSQL through optional drivers;
61
+ `Application.run()` can serve HTTPS directly from PEM certificates.
59
62
 
60
63
  ## AI-assisted development
61
64
 
@@ -71,8 +74,8 @@ does not have `cloudoll ai --help`, consult its matching README and source first
71
74
  Python 3.9+ is required. Install only the optional drivers you use:
72
75
 
73
76
  ```sh
74
- python -m pip install 'cloudoll==4.3.0'
75
- python -m pip install 'cloudoll[mysql,postgres,cache]==4.3.0'
77
+ python -m pip install 'cloudoll==4.5.0'
78
+ python -m pip install 'cloudoll[mysql,postgres,cache]==4.5.0'
76
79
  ```
77
80
 
78
81
  The AWS extra requires Python 3.10+. Aurora remains untested; see
@@ -87,7 +90,8 @@ python -m pip install -r requirements.txt
87
90
  cloudoll start -n myapp
88
91
  ```
89
92
 
90
- The generated project targets 4.3.0 and receives a random JWT secret. Controllers
93
+ The generated project targets 4.5.0 and receives random JWT and session secrets
94
+ in `.env`. Controllers
91
95
  and authentication middleware are examples: replace demo credentials and supply
92
96
  your own authorization and upload policies before deployment.
93
97
 
@@ -172,8 +176,8 @@ python -m ruff check cloudoll tests
172
176
  python -m mypy
173
177
  python -m pytest -q -m 'not integration' -k 'not aws'
174
178
  python -m build
175
- python tests/check_wheel.py dist/cloudoll-4.3.0-py3-none-any.whl
176
- python tests/check_release.py dist/cloudoll-4.3.0-py3-none-any.whl
179
+ python tests/check_wheel.py dist/cloudoll-4.5.0-py3-none-any.whl
180
+ python tests/check_release.py dist/cloudoll-4.5.0-py3-none-any.whl
177
181
  ```
178
182
 
179
183
  Native integration tests require explicitly configured disposable databases.
@@ -2,8 +2,10 @@
2
2
 
3
3
  English | [简体中文](README.zh-CN.md)
4
4
 
5
- Cloudoll 4.3.0 is an aiohttp-based Python library for routing, middleware,
5
+ Cloudoll 4.5.0 is an aiohttp-based Python library for routing, middleware,
6
6
  templates, sessions, JWT, database access and application scaffolding.
7
+ The ORM supports SQLite out of the box and MySQL/PostgreSQL through optional drivers;
8
+ `Application.run()` can serve HTTPS directly from PEM certificates.
7
9
 
8
10
  ## AI-assisted development
9
11
 
@@ -19,8 +21,8 @@ does not have `cloudoll ai --help`, consult its matching README and source first
19
21
  Python 3.9+ is required. Install only the optional drivers you use:
20
22
 
21
23
  ```sh
22
- python -m pip install 'cloudoll==4.3.0'
23
- python -m pip install 'cloudoll[mysql,postgres,cache]==4.3.0'
24
+ python -m pip install 'cloudoll==4.5.0'
25
+ python -m pip install 'cloudoll[mysql,postgres,cache]==4.5.0'
24
26
  ```
25
27
 
26
28
  The AWS extra requires Python 3.10+. Aurora remains untested; see
@@ -35,7 +37,8 @@ python -m pip install -r requirements.txt
35
37
  cloudoll start -n myapp
36
38
  ```
37
39
 
38
- The generated project targets 4.3.0 and receives a random JWT secret. Controllers
40
+ The generated project targets 4.5.0 and receives random JWT and session secrets
41
+ in `.env`. Controllers
39
42
  and authentication middleware are examples: replace demo credentials and supply
40
43
  your own authorization and upload policies before deployment.
41
44
 
@@ -120,8 +123,8 @@ python -m ruff check cloudoll tests
120
123
  python -m mypy
121
124
  python -m pytest -q -m 'not integration' -k 'not aws'
122
125
  python -m build
123
- python tests/check_wheel.py dist/cloudoll-4.3.0-py3-none-any.whl
124
- python tests/check_release.py dist/cloudoll-4.3.0-py3-none-any.whl
126
+ python tests/check_wheel.py dist/cloudoll-4.5.0-py3-none-any.whl
127
+ python tests/check_release.py dist/cloudoll-4.5.0-py3-none-any.whl
125
128
  ```
126
129
 
127
130
  Native integration tests require explicitly configured disposable databases.
@@ -2,11 +2,13 @@
2
2
 
3
3
  [English](README.md) | 简体中文
4
4
 
5
- 生产配置、资源关闭期限、JWT 验证、JSON 日志与监控接入见 [生产运行契约](docs/production.md);版本兼容及发布检查见 [维护政策](docs/maintenance.md),安全问题报告见 [SECURITY.md](SECURITY.md)。
5
+ 生产配置、资源关闭期限、JWT 验证、JSON 日志与监控接入见 [生产运行契约](docs/production.md);版本兼容及发布检查见 [维护政策](docs/maintenance.md);各版本变化见 [更新日志](CHANGELOG.zh-CN.md),安全问题报告见 [SECURITY.md](SECURITY.md)。
6
6
 
7
7
  Cloudoll 是一个基于 aiohttp 的 Python Web 开发库,提供路由自动注册、中间件、模板渲染、会话管理、JWT、数据库访问及命令行脚手架,帮助快速构建 Web 应用和微服务。
8
8
 
9
- 本文适用于 Cloudoll 4.3.0。从 3.x 升级前请阅读下方迁移说明。
9
+ ORM 内置 SQLite 支持,MySQL/PostgreSQL 按需安装驱动;`Application.run()` 可直接加载 PEM 证书启用 HTTPS。
10
+
11
+ 本文适用于 Cloudoll 4.5.0。从 3.x 升级前请阅读下方迁移说明。
10
12
 
11
13
  ## AI 辅助开发
12
14
 
@@ -25,10 +27,10 @@ Cloudoll 是一个基于 aiohttp 的 Python Web 开发库,提供路由自动
25
27
 
26
28
  ## 安装
27
29
 
28
- 安装 4.3.0:
30
+ 安装 4.5.0:
29
31
 
30
32
  ```sh
31
- python -m pip install 'cloudoll==4.3.0'
33
+ python -m pip install 'cloudoll==4.5.0'
32
34
  ```
33
35
 
34
36
  在当前仓库目录安装源码:
@@ -41,16 +43,16 @@ python -m pip install -e .
41
43
 
42
44
  ```sh
43
45
  # MySQL
44
- python -m pip install 'cloudoll[mysql]==4.3.0'
46
+ python -m pip install 'cloudoll[mysql]==4.5.0'
45
47
 
46
48
  # PostgreSQL
47
- python -m pip install 'cloudoll[postgres]==4.3.0'
49
+ python -m pip install 'cloudoll[postgres]==4.5.0'
48
50
 
49
51
  # AWS 数据库驱动
50
- python -m pip install 'cloudoll[aws]==4.3.0'
52
+ python -m pip install 'cloudoll[aws]==4.5.0'
51
53
 
52
54
  # Redis / Memcached 会话或 Redis 数据库
53
- python -m pip install 'cloudoll[cache]==4.3.0'
55
+ python -m pip install 'cloudoll[cache]==4.5.0'
54
56
  ```
55
57
 
56
58
  源码开发时可用 `python -m pip install -e '.[mysql,postgres,cache,dev]'` 安装。
@@ -89,7 +91,7 @@ myapp/
89
91
  └── templates/
90
92
  ```
91
93
 
92
- 脚手架中的鉴权中间件和登录接口用于演示。正式使用前,应替换示例账号、密码和业务鉴权逻辑。新建项目会获得独立的 JWT 密钥。
94
+ 脚手架中的鉴权中间件和登录接口用于演示。正式使用前,应替换示例账号、密码和业务鉴权逻辑。新建项目会在 `.env` 里获得相互独立的 JWT 与 Session 密钥。
93
95
 
94
96
  ### 手动创建应用
95
97
 
@@ -546,8 +548,8 @@ cloudoll restart -n myapp
546
548
  python -m pip install -e '.[mysql,postgres,cache,dev]'
547
549
  python -m pytest -q -m 'not integration' -k 'not aws'
548
550
  python -m build
549
- python tests/check_wheel.py dist/cloudoll-4.3.0-py3-none-any.whl
550
- python tests/check_release.py dist/cloudoll-4.3.0-py3-none-any.whl
551
+ python tests/check_wheel.py dist/cloudoll-4.5.0-py3-none-any.whl
552
+ python tests/check_release.py dist/cloudoll-4.5.0-py3-none-any.whl
551
553
  ```
552
554
 
553
555
  最后一条命令用于验证构建出的 wheel,版本号变化后请调整文件名。测试使用模拟数据库连接及本机 HTTP 服务;真实数据库连接和 AWS 故障切换需要单独的集成环境。
@@ -0,0 +1 @@
1
+ __version__ = "4.5.0"
@@ -6,10 +6,63 @@ icon: logs
6
6
 
7
7
  # 更新日志
8
8
 
9
- 按版本倒序记录 Cloudoll 的主要变化。当前使用方法请以本站 4.3.0 文档为准,历史功能不代表当前仍保留相同 API。
9
+ 按版本倒序记录 Cloudoll 的主要变化。当前使用方法请以本站 4.5.0 文档为准,历史功能不代表当前仍保留相同 API。
10
10
 
11
11
  历史版本日期为对应源码提交日期。各版本源码可通过表格中的链接查看。
12
12
 
13
+ ## 4.5.0
14
+
15
+ - `Application.run()` 支持通过证书路径或自定义 `ssl.SSLContext` 启用 HTTPS。
16
+ - ORM 新增基于 `aiosqlite` 的 SQLite 引擎、事务、流式查询、upsert 和建表/模型反向生成支持。
17
+ - SQLite 事务可选择 `DEFERRED`、`IMMEDIATE` 或 `EXCLUSIVE` 模式;SQLite 不支持行级 `FOR UPDATE`。
18
+
19
+ ## 4.4.0
20
+
21
+ 缺陷修复版本,向后兼容;Python 3.9+ 仍然支持。
22
+
23
+ ### 数据库
24
+
25
+ - `for_update()` 行锁必须在调用方自己的事务里使用。以前脱离事务的写法会临时开一个事务,锁随提交立刻释放,实际什么都没锁住;现在这样的调用直接报错。
26
+ - 开启事务时获取连接超时或被取消,已借到的连接会归还连接池,服务跑久了不再慢慢耗光连接。
27
+ - 模型字段恢复可哈希,可以放进 `set` 或作为字典键(列去重、批量排除字段)。
28
+ - `SUM(DISTINCT column)` 等聚合去重编译到聚合函数内部,去重发生在求和之前;聚合里的字面量不再被重复绑定参数。
29
+ - AWS 数据源串里的 URL 查询参数不再作为未知参数传给驱动。
30
+
31
+ ### Web 与模板
32
+
33
+ - 已经开始输出的响应不再被 JSON 错误处理改写坏,流式与 SSE 接口出错时保留原响应。
34
+ - 页面响应的 `Content-Type` 不再出现两个 `charset`。
35
+ - `session` 的 `key`、`max_age`、`httponly`、`secure`、`domain`、`path`、`samesite` 在加密 Cookie、Redis、Memcached 三种存储下都生效。
36
+ - `render_view()` 的关键字参数只归一处:模板上下文按位置传字典,关键字参数一律是 `Response` 选项,传错直接抛 `TypeError`,不再被当成上下文吞掉。
37
+ - 默认监听 127.0.0.1,开发模式不再额外占用配置端口 +1。
38
+ - `server.ignore_dirs` 对 `logs` 等目录生效,写日志不再触发热重载。
39
+ - 脚手架的鉴权中间件示例与文档一致:async 装饰器写法,放行静态资源,不再把后续处理器的异常吞成带错误文本的 500。
40
+
41
+ ### 命令行与新建项目
42
+
43
+ - `cloudoll create` 不再把 `__pycache__`、`.pyc` 复制进新项目。
44
+ - 项目名最后一段只能是字母、数字、下划线、点、短横线;带空格或 shell 元字符的名字被拒绝,已存在的目标目录也会报错。
45
+ - 创建失败时退出码非 0,并回收只写了一半的目录,不会留下半个项目。
46
+ - 新项目在 `.env` 里同时生成 JWT 与 Session 两把独立密钥,配置读取环境变量,`.gitignore` 忽略 `.env`。本地启动不再出现“使用随机进程本地密钥”的告警,重启和多 worker 之间登录状态不再丢失。
47
+
48
+ ### 邮件与工具函数
49
+
50
+ - 邮件客户端构造时不再立刻联网,发送过一次或服务器空闲断开后仍能继续使用:每次 `send()` 自建连接、只带一份正文,失败时也会关闭连接。
51
+ - 缺少账号或授权码、收件人为空时立即给出可读错误,不再留给服务器报认证失败。
52
+ - `cloudoll.utils` 的可逆加密、单向摘要、随机字符串、分布式 ID 成为正式 API,见[工具函数](/utils)。存用户密码请改用 bcrypt/argon2,这些工具都不是口令哈希。
53
+ - 新增[发送邮件](/mail)页;`cloudoll/mail` 下从未生效的 POP3 草稿文件被移除,库本身不提供收信能力。
54
+
55
+ ### 升级提示
56
+
57
+ - 在事务外调用 `for_update()`、给 `render_view()` 传多余关键字,以前是静默出错,现在会抛异常;升级前先检查这两类调用。
58
+ - `cloudoll create` 开始拒绝带空格和 shell 元字符的项目名。
59
+ - 老项目请自行补上 `session.secret_key` 或 `CLOUDOLL_SESSION_SECRET`,否则重启、多 worker 之间仍会丢登录状态。
60
+
61
+ ### 文档
62
+
63
+ - JWT 中间件示例改用 `web.RequestKey`,避免 aiohttp 的字符串键告警。
64
+ - [快速开始](/)与 [Cookie 与 Session](/session-cookie)说明新项目的密钥注入方式。
65
+
13
66
  ## 4.3.0
14
67
 
15
68
  ### ORM 查询与写入
@@ -43,8 +96,6 @@ icon: logs
43
96
 
44
97
  ## 4.1.0
45
98
 
46
- 本节对应 4.1.0 源码变更;实际发布状态以 PyPI 为准。
47
-
48
99
  - [默认数据源](/database#默认数据源与模型绑定):orm.default、模型 __datasource__、独立类级查询、query() 与 transaction(),保留显式 use(engine)。
49
100
  - datasource_context 为独立任务和测试提供作用域绑定;事务复用现有引擎机制,应用与并发任务保持隔离。
50
101
  - [请求参数校验](/validation):Pydantic 2 支持 Body、Query、Form、Path,重复数组参数和结构化 HTTP 400,兼容旧接口与流式上传。
@@ -14,9 +14,21 @@ CLI 默认读取项目根目录的 `config/conf.local.yaml`。选择生产配置
14
14
  cloudoll start -n myapp -env prod -m production
15
15
  ```
16
16
 
17
- `-env prod` 选择文件,`-m production` 选择运行模式,两者不是同一个开关。通过 `--host` / `--port` 可覆盖监听地址;未传入时使用配置。入口参数 `-e app` 是模块名,不是 `app.py`。
17
+ `-env prod` 选择文件,`-m production` 选择运行模式,两者不是同一个开关。通过 `--host` / `--port` 可覆盖监听地址;未传入时使用配置。配置也缺省时,监听地址为本机回环 `127.0.0.1`、端口 `9001`;需要对外提供服务时显式写入 `server.host: 0.0.0.0` 或使用 `--host 0.0.0.0`。开发模式只有工作进程监听业务端口,监督进程不占用其他端口。入口参数 `-e app` 是模块名,不是 `app.py`。
18
18
 
19
- 配置必须是 YAML 映射;加载时使用安全 YAML 解析和严格环境变量展开。缺少环境变量会报错。Cloudoll 没有自动加载项目 `.env` 文件的逻辑,应由 shell、容器或进程管理器注入变量。
19
+ 配置必须是 YAML 映射;加载时使用安全 YAML 解析,并通过 EnvYAML 严格展开环境变量。默认情况下,缺少引用的环境变量会报错。
20
+
21
+ ### `.env` 文件
22
+
23
+ EnvYAML 会自动读取当前工作目录下的 `.env`。从项目根目录启动时,可将配置中引用的变量放在根目录的 `.env` 中,也可以由 shell、容器或进程管理器注入。
24
+
25
+ - 可通过进程环境变量 `ENV_FILE` 指定其他文件;从不同目录启动时,建议使用绝对路径。
26
+ - 默认 `.env` 路径相对于当前工作目录,不是 YAML 所在目录;`get_config(..., root=...)` 只指定 YAML 配置的根目录,不改变 `.env` 的查找位置。
27
+ - 当前 EnvYAML 实现中,文件中的变量会覆盖同名进程环境变量,再用于 YAML 占位符展开。生产环境应避免残留本地 `.env` 覆盖部署配置。
28
+ - 文件变量仅进入 EnvYAML 配置,不会写入 `os.environ`;直接使用 `os.getenv()` 或 `os.environ` 的业务代码不能因此读到这些变量。
29
+ - `-env prod` 选择 `config/conf.prod.yaml`,不会自动切换为 `.env.prod`;需要时通过 `ENV_FILE` 指定。
30
+
31
+ 不要提交包含密码或密钥的 `.env`;可提交仅包含变量名和占位值的 `.env.example`。
20
32
 
21
33
  显式指定的配置文件不存在会抛 FileNotFoundError,不再返回空配置继续启动。核心配置节、端口/请求大小、布尔开关、资源关闭期限和 JWT 策略会在应用创建时校验,on_create 修改后再次校验。业务自定义键仍可使用;数据库驱动参数由相应引擎校验。
22
34
 
@@ -101,7 +113,9 @@ database:
101
113
  url: $POSTGRES_URL
102
114
  ```
103
115
 
104
- URL 格式分别是 `mysql://用户:密码@主机:3306/库名` 和 `postgres://用户:密码@主机:5432/库名`。账号、密码含特殊字符时需 URL 编码;分字段配置更容易避免编码错误。显式关键字参数优先于 URL 中的同名选项。
116
+ URL 格式分别是 `mysql://用户:密码@主机:3306/库名`、`postgres://用户:密码@主机:5432/库名` 和 `sqlite:///相对或绝对路径.db`。账号、密码含特殊字符时需 URL 编码;分字段配置更容易避免编码错误。显式关键字参数优先于 URL 中的同名选项。
117
+
118
+ SQLite 无需额外驱动;内存数据库可写为 `sqlite:///:memory:`。服务器可在 `server` 下配置 `ssl_certfile` 与 `ssl_keyfile` 启用 HTTPS,也可通过 Python API 传入自定义 `ssl_context`。
105
119
 
106
120
  在请求中通过 `request.app.db.mysql` / `request.app.db.postgres` 取连接。详细的超时、TLS、事务和 SQL 日志见[数据库](/database)及[日志](/logs)。
107
121
 
@@ -78,9 +78,14 @@ session:
78
78
  max_age: 86400
79
79
  httponly: true
80
80
  secure: false
81
+ domain: .example.com
82
+ path: /
83
+ samesite: Lax
81
84
  ```
82
85
 
83
- 也可只在进程环境设置 CLOUDOLL_SESSION_SECRET。未配置时生成应用进程本地随机密钥,重启会使旧 Session 失效,多进程间也不能共享;生产必须使用一致的随机密钥。更换密钥会使原 Cookie 失效。
86
+ `domain`、`path`、`samesite` 属于 Session Cookie 本身,Cookie、Redis、Memcached 三种存储都读取同一份配置;Redis 存储下不要把这三个键写进 `redis` 子配置。`samesite` 只接受 `Strict`、`Lax`、`None`;`None` 表示不下发该属性,使用浏览器默认策略。`path` 决定浏览器在哪些路径回传 Session Cookie,改成子路径后原路径下的请求将不再带会话。HTTPS 生产环境使用 `secure: true`,此时 `samesite: None` 还要求浏览器允许跨站发送,仅在确有跨站回调时使用。
87
+
88
+ `cloudoll create` 生成的项目已在 `config/conf.local.yaml` 写好 `session.secret_key: $CLOUDOLL_SESSION_SECRET`,并把随机密钥连同 JWT 密钥一起写入项目根目录的 `.env`,开箱即用、重启不掉线;`.env` 已被 `.gitignore` 忽略,生产环境改用环境变量注入同名变量。也可只在进程环境设置 CLOUDOLL_SESSION_SECRET。两处都没有时框架生成应用进程本地随机密钥并记录一次告警,重启会使旧 Session 失效,多进程间也不能共享;生产必须使用一致的随机密钥。更换密钥会使原 Cookie 失效。
84
89
 
85
90
  Cookie 容量有限,不要保存大对象、密码或完整业务数据。客户端加密 Cookie 模式无法仅靠退出操作撤销其他地方留存的旧 Cookie;需要集中撤销时使用服务端存储及业务级会话控制。
86
91
 
@@ -6,7 +6,7 @@ icon: database
6
6
 
7
7
  # 数据库与 ORM
8
8
 
9
- 本页介绍 Cloudoll 4.3.0 的原生 MySQL/PostgreSQL 接口,包括保存点、流式查询与 SQL 日志。从 3.x 升级请先阅读[升级说明](/migration)。
9
+ 本页介绍 Cloudoll 4.5.0 的原生 MySQL、PostgreSQL 与 SQLite 接口,包括保存点、流式查询与 SQL 日志。从 3.x 升级请先阅读[升级说明](/migration)。
10
10
 
11
11
  ## 应用代码默认使用实体
12
12
 
@@ -26,7 +26,7 @@ Python API 的准确拼写是 `order_by` 和大写 `As`,不是 `orderby` / `as
26
26
  ## 安装与连接
27
27
 
28
28
  ```sh
29
- python -m pip install 'cloudoll[mysql,postgres]==4.3.0'
29
+ python -m pip install 'cloudoll[mysql,postgres]==4.5.0'
30
30
  ```
31
31
 
32
32
  Web 应用在 [database 配置](/config)中声明连接,由框架建立、关闭,通过 `request.app.db.别名` 获取。不要每个请求创建一个池,也不要在请求结束时关闭共享池。
@@ -50,7 +50,9 @@ if __name__ == "__main__":
50
50
  asyncio.run(main())
51
51
  ```
52
52
 
53
- DATABASE_URL 为 mysql://... 或 postgres://...。`create_engine` 是异步函数;同一个引擎在所属事件循环中复用。关闭前先退出所有事务和流式查询上下文。
53
+ DATABASE_URL 可以是 `mysql://...`、`postgres://...` 或 `sqlite:///path/to/app.db`。SQLite 内存库使用 `sqlite:///:memory:`,引擎会将池大小限制为 1,确保连接间共享同一个内存数据库。`create_engine` 是异步函数;同一个引擎在所属事件循环中复用。关闭前先退出所有事务和流式查询上下文。
54
+
55
+ SQLite 基于 `aiosqlite`,支持 ORM 查询、绑定参数、批量写入、事务、嵌套保存点、流式读取和模型建表。`async with engine.transaction(mode="IMMEDIATE")` 可在并发写前取得 SQLite 写锁;mode 支持 `DEFERRED`、`IMMEDIATE`、`EXCLUSIVE`。SQLite 不支持行级 `FOR UPDATE`。其余高级 SQL 函数和 DDL 特性仍按 SQLite 自身能力限制。
54
56
 
55
57
  ## 模型定义
56
58
 
@@ -251,7 +253,8 @@ present = await User.where(User.email == "alice@example.test").exists()
251
253
  ```
252
254
 
253
255
  `join(..., kind="left" | "inner" | "right")` 默认仍是 LEFT JOIN,保持兼容。
254
- `distinct()` 对整行投影去重;字段 `User.id.distinct().count()` 生成 COUNT(DISTINCT ...)。
256
+ `distinct()` 对整行投影去重;字段 `User.id.distinct().count()` 生成 COUNT(DISTINCT ...),
257
+ 聚合后的 `User.amount.sum().distinct()` 同样生成 SUM(DISTINCT ...),去重的是聚合输入值而不是聚合结果行。
255
258
  `one_dict()` 返回字典或 None,不把别名/聚合列装进实体;原有 one/one_model 返回规则不变。
256
259
  `exists()` 返回布尔值,保留筛选、分组和 offset,最多读取一行且不消费原查询;它不保证并发唯一性。
257
260
  `count()` 忽略分页、排序和行锁,保留去重、分组与 HAVING。
@@ -411,7 +414,8 @@ async with Inventory.transaction():
411
414
 
412
415
  `for_update(nowait=True)` 遇到锁竞争立即报错;
413
416
  `for_update(skip_locked=True)` 跳过已锁行,适合任务领取,不适合完整性报表。两选项互斥。
414
- 调用方必须显式管理事务;不会自动开启事务或重试。锁查询不能使用只读 stream()。
417
+ 调用方必须显式管理事务;事务外执行锁查询直接抛 TransactionError,不会自开事务后立刻提交,也不会自动重试。
418
+ 锁查询不能使用只读 stream()。
415
419
  聚合、DISTINCT、外联表的锁定限制由数据库决定,示例只锁普通实体行。
416
420
  新增能力在原生 MySQL 8.4 / PostgreSQL 16 验证,不将该结果等同于 Aurora 验证。
417
421
 
@@ -6,6 +6,20 @@ icon: deployment
6
6
 
7
7
  # 应用部署
8
8
 
9
+ ## 直接启用 HTTPS
10
+
11
+ Cloudoll 可直接使用 PEM 证书和私钥:
12
+
13
+ ```yaml
14
+ server:
15
+ host: 0.0.0.0
16
+ port: 443
17
+ ssl_certfile: /etc/ssl/cloudoll/fullchain.pem
18
+ ssl_keyfile: /etc/ssl/cloudoll/privkey.pem
19
+ ```
20
+
21
+ `Application.run()` 会创建 TLS server context 并加载证书。也可在 Python 中通过 `server.ssl_context` 传入自定义 `ssl.SSLContext`,但不能与证书路径同时设置。证书续期后需重启进程以加载新证书。反向代理部署仍可由代理终止 TLS。
22
+
9
23
  ## 生产启动与 CLI 边界
10
24
 
11
25
  在应用根目录创建虚拟环境、安装经过验证并锁定版本的 requirements.txt:
@@ -42,8 +42,15 @@ def issue_token(request, authenticated_user_id: int) -> str:
42
42
 
43
43
  ```python
44
44
  # middlewares/auth.py
45
+ from aiohttp import web
46
+
45
47
  from cloudoll.web import middleware, render_error
46
48
 
49
+ # 请求上下文里的键用 web.RequestKey 声明:写字符串会触发 NotAppKeyWarning,
50
+ # 键名也只在各文件里重复拼写,改名时编译器帮不上忙。
51
+ USER: web.RequestKey[dict] = web.RequestKey("user")
52
+
53
+
47
54
  @middleware
48
55
  async def auth(request, handler):
49
56
  if request.path.startswith("/api/"):
@@ -60,11 +67,11 @@ async def auth(request, handler):
60
67
  response = render_error("token 无效或已过期", status=401)
61
68
  response.headers["WWW-Authenticate"] = "Bearer"
62
69
  return response
63
- request["user"] = user
70
+ request[USER] = user
64
71
  return await handler(request)
65
72
  ```
66
73
 
67
- 业务路由用 `request["user"]` 读取已验证 payload;仍需检查账号状态、资源归属和权限。未认证通常返回 401;身份有效但无权限通常返回 403。
74
+ 业务路由用 `request[USER]` 读取已验证 payload;仍需检查账号状态、资源归属和权限。中间件没有写入时(路径不在 `/api/` 下、或该路由被 `sa_ignore` 放行)`request[USER]` 抛 `KeyError`,需要容忍未登录时读 `request.get(USER)`,未设置返回 None。未认证通常返回 401;身份有效但无权限通常返回 403。
68
75
 
69
76
  `@post("/api/login", sa_ignore=True)` 可由该中间件识别为免 token 路由,但登录接口仍必须验证凭证。签名错误、过期、缺失所需声明等无效凭证返回 None;缺少 key、非法 leeway 等配置错误会抛异常,不应统一当作用户登录失败。库不会自动发送认证 HTTP 响应。
70
77
 
@@ -0,0 +1,74 @@
1
+ ---
2
+ title: 邮件
3
+ order: 135
4
+ icon: router
5
+ ---
6
+
7
+ # 发送邮件
8
+
9
+ `cloudoll.mail.smtp.Client` 是一个直白的 SMTPS 发信客户端,用于告警、报表这类自己拼装正文的场景。它不依赖 `Application`,也不读取 `config/conf.*.yaml`;连接参数由调用方传入,可以放在进程环境或自己的配置里。
10
+
11
+ 库只提供发送,不提供收取邮件,也没有队列、定时与重试。需要投递保障时,把发信交给业务侧重试队列或专业邮件服务。
12
+
13
+ ## 用法
14
+
15
+ ```python
16
+ from cloudoll.mail import smtp
17
+
18
+ client = smtp.Client(
19
+ smtp_server="smtp.example.com",
20
+ account="alert@example.com",
21
+ account_name="监控告警",
22
+ password="授权码", # 邮箱服务商签发的发信授权码,不是登录密码
23
+ port=465,
24
+ )
25
+ client.subject = "磁盘使用率超过 90%"
26
+ client.content = "host: db-01\nused: 92%"
27
+ client.add_to_addr("运维", "ops@example.com")
28
+ client.add_to_addr("张三", "zhangsan@example.com")
29
+ client.send()
30
+ ```
31
+
32
+ 构造函数不碰网络,只保存参数;连接和登录发生在 `send()`。因此可以在模块导入时创建客户端,也可以反复用同一个客户端发信——每次 `send()` 都会新建一条连接并在结束时关闭。
33
+
34
+ ## 配置项
35
+
36
+ | 参数 | 默认值 | 说明 |
37
+ | --- | --- | --- |
38
+ | `smtp_server` | 必填 | SMTP 主机名,缺失时构造即抛 `ValueError`。 |
39
+ | `port` | `465` | 端口,始终用 `SMTP_SSL` 连接。 |
40
+ | `account` | `""` | 登录账号,同时作为发件地址。 |
41
+ | `account_name` | `""` | 显示在 `From` 里的发件人名称,支持中文。 |
42
+ | `password` | `""` | 发信授权码。账号或授权码为空时 `send()` 抛 `KeyError`,不会先连服务器。 |
43
+ | `debug_level` | `0` | 透传 `smtplib.SMTP.set_debuglevel()`,非 0 时把 SMTP 会话过程打到标准输出,其中含登录交互,只在本机排查时开启。 |
44
+
45
+ 只支持 465 端口的隐式 TLS(SMTPS)。587 的 STARTTLS 升级方式没有实现,需要时改用 `smtplib` 自己发送。
46
+
47
+ ## 收件人、正文与附件
48
+
49
+ `add_to_addr(名称, 地址)` 可以调用多次,它们同属一条邮件的收件人列表;地址为空抛 `KeyError`。`content` 是正文,正文类型由 `mime_type` 决定,默认 `plain`,改成 `"html"` 后正文按 HTML 发送。
50
+
51
+ ```python
52
+ client.mime_type = "html"
53
+ client.content = "<h1>周报</h1><p>见附件</p>"
54
+ client.addfile("/data/report.pdf")
55
+ ```
56
+
57
+ `addfile(路径)` 接收绝对路径,按文件名猜测 MIME 类型(猜不到时按 `application/octet-stream`),读取内容并做 Base64 编码。每个附件依次拿到从 0 开始的 Content-ID,可直接内嵌进 HTML 正文:
58
+
59
+ ```python
60
+ client.addfile("/data/img/logo.png") # cid:0
61
+ client.addfile("/data/img/chart.png") # cid:1
62
+ client.mime_type = "html"
63
+ client.content = '<img src="cid:0"><img src="cid:1">'
64
+ ```
65
+
66
+ `addhtml(html)` 是另附一个 HTML 部分,与设置 `mime_type = "html"` 不同:前者正文里同时存在纯文本和 HTML 两个部分,后者只有 HTML 一个部分。标题和正文用普通属性赋值即可,中文无需手工编码。
67
+
68
+ ## 失败与重发
69
+
70
+ `send()` 不吞异常,登录失败、连接中断、收件人被拒都会原样抛出 `smtplib` / `ssl` 的异常,由调用方决定是否重试。发送中途失败时连接同样会关闭,客户端可以继续复用。
71
+
72
+ 重复调用 `send()` 发送的是标题、正文、收件人和附件的当前快照:改动 `content` 后再发,下一封就是新内容,不会把上一封的正文带过来。附件部分会随客户端保留,重发时仍在。
73
+
74
+ 密钥按快速开始的做法从环境注入,不要把授权码写进仓库。批量投递请在业务侧控制节奏,遵守所用服务商的发信频率限制。
@@ -9,7 +9,7 @@ path: /
9
9
 
10
10
  Cloudoll 是基于 aiohttp 的异步 Python Web 库,提供路由、模板、Session、ORM 和 CLI。
11
11
 
12
- > 本文档按 Cloudoll 4.3.0 源码更新,实际发布状态以 PyPI 为准。从 3.x 升级前,请先阅读[升级说明](/migration),确认配置和 API 的兼容性变化。
12
+ > 本文档按 Cloudoll 4.5.0 源码更新,实际发布状态以 PyPI 为准。从 3.x 升级前,请先阅读[升级说明](/migration),确认配置和 API 的兼容性变化。
13
13
 
14
14
  ## 环境与安装
15
15
 
@@ -18,8 +18,8 @@ Cloudoll 是基于 aiohttp 的异步 Python Web 库,提供路由、模板、Se
18
18
  ```sh
19
19
  python3 -m venv .venv
20
20
  source .venv/bin/activate
21
- python -m pip install 'cloudoll==4.3.0'
22
- python -m pip install 'cloudoll[mysql,postgres,cache]==4.3.0'
21
+ python -m pip install 'cloudoll==4.5.0'
22
+ python -m pip install 'cloudoll[mysql,postgres,cache]==4.5.0'
23
23
  ```
24
24
 
25
25
  Windows PowerShell 激活命令为 `.venv\Scripts\Activate.ps1`。只使用 Web 功能时不需要安装数据库 extras。
@@ -38,7 +38,9 @@ cd myapp
38
38
  cloudoll start -n myapp
39
39
  ```
40
40
 
41
- 默认读取 `config/conf.local.yaml`,默认端口 9001。在生成的 `controllers/home/index.py` 中替换已有首页处理器(不要重复注册同一路由):
41
+ 项目名的最后一段只能由字母、数字、下划线、点、短横线组成(不能带空格和 shell 元字符,也不能是 `.` 或 `..`);它同时用作提示的 `-n` 服务名和 pid 文件名。名字不合法、目标目录已存在或复制中途出错都会报错并以退出码 2 结束,中途出错时刚建好的目录会被回收,不会留下半个项目。
42
+
43
+ 默认读取 `config/conf.local.yaml`,默认端口 9001。配置保留 `$CLOUDOLL_JWT_SECRET`、`$CLOUDOLL_SESSION_SECRET` 两个占位符,脚手架另在项目根目录把两个互不相同的随机密钥写入 `.env`(已被 `.gitignore` 忽略),新项目开箱就带有稳定的 Session 密钥;生产环境改用环境变量注入,不要提交 `.env`。`.env` 按当前工作目录读取,请在项目根目录启动。未设置 Session 密钥时框架退回进程本地随机密钥并告警,重启或多 worker 之间旧会话都会失效。在生成的 `controllers/home/index.py` 中替换已有首页处理器(不要重复注册同一路由):
42
44
 
43
45
  ```python
44
46
  from cloudoll.web import get
@@ -54,7 +56,7 @@ curl http://127.0.0.1:9001/
54
56
 
55
57
  响应包含 `name`、默认的 `message: "OK"`、`code: 200` 和动态生成的毫秒级 `timestamp`。返回字符串、列表等也会包装在 `data` 中;如果需要原样 JSON,返回 `aiohttp.web.json_response(...)`。纯文本使用 `render(text="Hello")`。
56
58
 
57
- 开发模式会监听 Python 文件变化并重启工作进程,另使用业务端口加一的辅助端口;它不是生产进程守护工具。配置文件修改后请重启。
59
+ 开发模式会监听 Python 文件变化并重启工作进程;只有工作进程监听业务端口,监督进程不开辅助端口,业务端口加一被其他程序占用也不影响启动。它不是生产进程守护工具。配置文件修改后请重启。
58
60
 
59
61
  ## 手动创建最小应用
60
62