cloudoll 4.3.0__tar.gz → 4.4.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.
- {cloudoll-4.3.0 → cloudoll-4.4.0}/CHANGELOG.md +92 -5
- {cloudoll-4.3.0 → cloudoll-4.4.0}/PKG-INFO +8 -7
- {cloudoll-4.3.0 → cloudoll-4.4.0}/README.md +7 -6
- {cloudoll-4.3.0 → cloudoll-4.4.0}/README.zh-CN.md +11 -11
- cloudoll-4.4.0/cloudoll/__init__.py +1 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/ai/docs/changelog.md +48 -3
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/ai/docs/config.md +14 -2
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/ai/docs/cookie-and-session.md +6 -1
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/ai/docs/database.md +6 -4
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/ai/docs/jwt.md +9 -2
- cloudoll-4.4.0/cloudoll/ai/docs/mail.md +74 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/ai/docs/start.md +7 -5
- cloudoll-4.4.0/cloudoll/ai/docs/utils.md +74 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/ai/docs/view.md +3 -1
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/ai/manifest.json +18 -8
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/ai/skills/cloudoll/SKILL.md +6 -2
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/cli.py +7 -1
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/clitool/cli_main.py +67 -26
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/clitool/watch.py +37 -2
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/mail/smtp.py +39 -53
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/orm/aws_engine.py +3 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/orm/compiler.py +15 -2
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/orm/engine.py +17 -3
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/orm/field.py +3 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/orm/protocols.py +1 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/orm/query.py +19 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/orm/savepoints.py +4 -0
- cloudoll-4.4.0/cloudoll/template/.gitignore +82 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/template/README.md +11 -1
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/template/config/conf.local.yaml +10 -2
- cloudoll-4.4.0/cloudoll/template/middlewares/auth.py +38 -0
- cloudoll-4.4.0/cloudoll/template/requirements.txt +1 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/web/configuration.py +12 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/web/core.py +45 -9
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/web/sessions.py +15 -20
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll.egg-info/PKG-INFO +8 -7
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll.egg-info/SOURCES.txt +6 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/docs/maintenance.md +2 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/pyproject.toml +1 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/check_wheel.py +5 -0
- cloudoll-4.4.0/tests/test_cli_create.py +192 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/test_dev_lifecycle.py +203 -3
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/test_field_types.py +11 -0
- cloudoll-4.4.0/tests/test_mail_client.py +183 -0
- cloudoll-4.4.0/tests/test_password_and_id_utils.py +151 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/test_query_capabilities.py +23 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/test_regressions.py +46 -7
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/test_request_contract.py +152 -1
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/test_transactions.py +60 -0
- cloudoll-4.3.0/cloudoll/__init__.py +0 -1
- cloudoll-4.3.0/cloudoll/template/middlewares/auth.py +0 -31
- cloudoll-4.3.0/cloudoll/template/requirements.txt +0 -1
- {cloudoll-4.3.0 → cloudoll-4.4.0}/AGENTS.md +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/AI.md +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/LICENSE +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/MANIFEST.in +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/SECURITY.md +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/__main__.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/ai/AGENTS.md +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/ai/__init__.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/ai/commands.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/ai/docs/ai.md +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/ai/docs/app-start.md +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/ai/docs/deployment.md +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/ai/docs/event-source.md +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/ai/docs/exception.md +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/ai/docs/file-uploads.md +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/ai/docs/http-client.md +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/ai/docs/logs.md +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/ai/docs/middleware.md +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/ai/docs/migration.md +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/ai/docs/router.md +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/ai/docs/structure.md +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/ai/docs/validation.md +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/ai/docs/websockets.md +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/clitool/__init__.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/clitool/m2d.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/clitool/process.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/clitool/schema.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/clitool/spinner.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/logging/__init__.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/mail/__init__.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/observability.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/orm/__init__.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/orm/awsmysql.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/orm/awspostgres.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/orm/base.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/orm/datasources.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/orm/dialects.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/orm/model.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/orm/mysql.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/orm/parse.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/orm/postgres.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/orm/relation.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/orm/streaming.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/orm/subquery.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/orm/test_sql.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/orm/values.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/py.typed +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/template/__init__.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/template/controllers/admin/test.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/template/controllers/api/index.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/template/controllers/es/es.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/template/controllers/home/index.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/template/controllers/upload/upload.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/template/controllers/ws/ws.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/template/static/css/index.css +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/template/static/img/cat.avif +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/template/static/js/index.js +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/template/templates/index.html +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/utils/__init__.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/utils/async_tools.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/utils/common.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/utils/password.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/utils/snowflake.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/web/__init__.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/web/context.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/web/exception.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/web/html.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/web/jwt.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/web/lifecycle.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/web/request_data.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/web/requests.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/web/resources.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/web/routing.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/web/settings.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/web/types.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll/web/validation.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll.egg-info/dependency_links.txt +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll.egg-info/entry_points.txt +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll.egg-info/requires.txt +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/cloudoll.egg-info/top_level.txt +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/docs/architecture.md +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/docs/aurora.md +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/docs/production.md +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/docs/reliability.md +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/docs/schema-generation.md +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/docs/streaming-and-types.md +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/scripts/sync_ai_docs.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/setup.cfg +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/setup.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/audit_dependencies.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/check_release.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/conftest.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/integration/test_aws_failover.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/integration/test_databases.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/integration/test_savepoints.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/integration/test_schema.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/integration/test_streaming.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/run_local_databases.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/test_ai.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/test_architecture.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/test_datasources.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/test_production_contracts.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/test_release_workflow.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/test_retry_and_query_regressions.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/test_review_fixes.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/test_save_snapshots_and_cleanup.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/test_savepoint_unit.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/test_schema_generation.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/test_sql_echo.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/test_startup_logging.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/test_streaming_unit.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/test_typing_regressions.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/test_validation.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/typing/engine_contract.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/typing/field_contract.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/typing/middleware_contract.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/typing/production_contract.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/typing/query_contract.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/typing/request_contract.py +0 -0
- {cloudoll-4.3.0 → cloudoll-4.4.0}/tests/typing/web_contract.py +0 -0
|
@@ -1,5 +1,95 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
简体中文见 [CHANGELOG.zh-CN.md](CHANGELOG.zh-CN.md)。
|
|
4
|
+
|
|
5
|
+
## 4.4.0
|
|
6
|
+
|
|
7
|
+
Defect-fix release; backward compatible. Python 3.9+ remains supported.
|
|
8
|
+
|
|
9
|
+
### Database
|
|
10
|
+
|
|
11
|
+
- `for_update()` row locks now require your own transaction. Outside one, the lock
|
|
12
|
+
was released by the commit that `for_update()` issued itself and protected
|
|
13
|
+
nothing; such a call now raises instead of silently doing nothing.
|
|
14
|
+
- A transaction cancelled or timed out while waiting for a connection gives that
|
|
15
|
+
connection back to the pool, so long-running services stop losing connections.
|
|
16
|
+
- Model fields are hashable again: columns can go into sets and serve as dict
|
|
17
|
+
keys (deduplicating columns, excluding groups of fields).
|
|
18
|
+
- `SUM(DISTINCT column)` and similar aggregate deduplication now happens inside
|
|
19
|
+
the aggregate, so values are deduplicated before being combined, and a literal
|
|
20
|
+
inside an aggregate is no longer bound twice.
|
|
21
|
+
- URL query parameters in an AWS datasource string are no longer handed to the
|
|
22
|
+
driver as unknown connection options.
|
|
23
|
+
|
|
24
|
+
### Web and templates
|
|
25
|
+
|
|
26
|
+
- A response that has already started streaming keeps its body when an error
|
|
27
|
+
occurs; the JSON error handler no longer rewrites it. Streaming and SSE
|
|
28
|
+
endpoints stay intact.
|
|
29
|
+
- Page responses no longer repeat `charset` in `Content-Type`.
|
|
30
|
+
- `session` options (`key`, `max_age`, `httponly`, `secure`, `domain`, `path`,
|
|
31
|
+
`samesite`) apply to encrypted cookie, Redis and Memcached storage alike.
|
|
32
|
+
- `render_view()` keywords are `Response` options only, and the template context
|
|
33
|
+
is the positional mapping. A stray keyword now raises `TypeError` instead of
|
|
34
|
+
being silently merged into the context.
|
|
35
|
+
- Development servers bind to 127.0.0.1 by default and no longer occupy
|
|
36
|
+
`port + 1`.
|
|
37
|
+
- `server.ignore_dirs` is honoured for directories such as `logs`, so writing logs
|
|
38
|
+
no longer triggers a reload.
|
|
39
|
+
- The scaffolded authorization middleware now matches its documentation: it is an
|
|
40
|
+
async decorator, lets static paths through, and lets downstream failures reach
|
|
41
|
+
the error layer instead of being flattened into a 500 that echoes the exception
|
|
42
|
+
text.
|
|
43
|
+
|
|
44
|
+
### Command line and new projects
|
|
45
|
+
|
|
46
|
+
- `cloudoll create` no longer copies `__pycache__` and `.pyc` files into the new
|
|
47
|
+
project.
|
|
48
|
+
- The last component of a project name must be letters, digits, underscore, dot
|
|
49
|
+
or dash; names with spaces or shell characters are refused, as is an existing
|
|
50
|
+
target directory.
|
|
51
|
+
- A failed creation exits with a non-zero status and removes the half-written
|
|
52
|
+
project instead of leaving it behind.
|
|
53
|
+
- New projects write independent JWT and session keys to `.env`, which
|
|
54
|
+
`.gitignore` keeps out of version control. Local starts no longer warn about a
|
|
55
|
+
random process-local session key, and logins survive a restart or an added
|
|
56
|
+
worker.
|
|
57
|
+
|
|
58
|
+
### Mail and utilities
|
|
59
|
+
|
|
60
|
+
- Building a `cloudoll.mail.smtp.Client` no longer reaches the network, and one
|
|
61
|
+
client stays usable across sends and server idle timeouts: each `send()` opens
|
|
62
|
+
its own connection, carries one copy of the body, and closes the connection even
|
|
63
|
+
when the send fails.
|
|
64
|
+
- Missing credentials and an empty recipient address now raise a readable error
|
|
65
|
+
before anything is sent.
|
|
66
|
+
- `cloudoll.utils` - reversible encryption, keyed digests, random strings and the
|
|
67
|
+
distributed ID generator - is now official API with a
|
|
68
|
+
[utilities](https://cloudoll.chuchur.com/utils) page and regression tests. None
|
|
69
|
+
of them is a password hash; store user passwords with bcrypt or argon2.
|
|
70
|
+
- New [mail](https://cloudoll.chuchur.com/mail) page documents sending limits
|
|
71
|
+
(SMTPS 465 only, no STARTTLS, no retry or queue). The dead POP3 draft under
|
|
72
|
+
`cloudoll/mail` is gone - Cloudoll does not receive mail.
|
|
73
|
+
|
|
74
|
+
### Upgrade notes
|
|
75
|
+
|
|
76
|
+
- `for_update()` outside a transaction and unexpected `render_view()` keywords now
|
|
77
|
+
raise rather than misbehaving quietly; audit those two call shapes before
|
|
78
|
+
upgrading.
|
|
79
|
+
- `cloudoll create` rejects names it used to accept, such as `my shop`, because
|
|
80
|
+
they reached a shell unsafely.
|
|
81
|
+
- Projects created before this release keep an old `config/conf.local.yaml`
|
|
82
|
+
without a session key; add `session.secret_key` (or set
|
|
83
|
+
`CLOUDOLL_SESSION_SECRET`) so sessions survive restarts and multiple workers.
|
|
84
|
+
- Existing `.env` files need the new `CLOUDOLL_SESSION_SECRET` entry; `.env` has
|
|
85
|
+
always been ignored by the project's `.gitignore`.
|
|
86
|
+
|
|
87
|
+
### Documentation
|
|
88
|
+
|
|
89
|
+
- JWT middleware examples use `web.RequestKey` instead of a plain string key, so
|
|
90
|
+
aiohttp stops warning about it.
|
|
91
|
+
- The quick start and session pages describe how a new project injects its keys.
|
|
92
|
+
|
|
3
93
|
## 4.3.0
|
|
4
94
|
|
|
5
95
|
### ORM queries and writes
|
|
@@ -56,9 +146,6 @@ Backward-compatible feature release. Pydantic 2 (`>=2.0,<3`) is now a core
|
|
|
56
146
|
runtime dependency. Existing handlers and explicit ORM engine bindings remain
|
|
57
147
|
supported. Python 3.9+ remains supported by the CI matrix.
|
|
58
148
|
|
|
59
|
-
Publication date is recorded by the release tag/PyPI; preparing these files does
|
|
60
|
-
not publish the package.
|
|
61
|
-
|
|
62
149
|
- Correct lifecycle callback and JWT payload type declarations for strict
|
|
63
150
|
checking with current dependency annotations.
|
|
64
151
|
- Include HTTP method and actual request path in exception logs; emit 4xx access
|
|
@@ -76,8 +163,8 @@ not publish the package.
|
|
|
76
163
|
|
|
77
164
|
## 4.0.0
|
|
78
165
|
|
|
79
|
-
|
|
80
|
-
|
|
166
|
+
This release contains breaking changes to configuration, security, ORM and the
|
|
167
|
+
application lifecycle. Read the migration guide before upgrading.
|
|
81
168
|
|
|
82
169
|
### Breaking changes and migration
|
|
83
170
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cloudoll
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.4.0
|
|
4
4
|
Summary: Assist in quickly creating distributable microservices based on aiohttp, with a focus on simplicity and ease of use.
|
|
5
5
|
Author-email: Qiu <smallerqiu@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -54,7 +54,7 @@ Dynamic: license-file
|
|
|
54
54
|
|
|
55
55
|
English | [简体中文](README.zh-CN.md)
|
|
56
56
|
|
|
57
|
-
Cloudoll 4.
|
|
57
|
+
Cloudoll 4.4.0 is an aiohttp-based Python library for routing, middleware,
|
|
58
58
|
templates, sessions, JWT, database access and application scaffolding.
|
|
59
59
|
|
|
60
60
|
## AI-assisted development
|
|
@@ -71,8 +71,8 @@ does not have `cloudoll ai --help`, consult its matching README and source first
|
|
|
71
71
|
Python 3.9+ is required. Install only the optional drivers you use:
|
|
72
72
|
|
|
73
73
|
```sh
|
|
74
|
-
python -m pip install 'cloudoll==4.
|
|
75
|
-
python -m pip install 'cloudoll[mysql,postgres,cache]==4.
|
|
74
|
+
python -m pip install 'cloudoll==4.4.0'
|
|
75
|
+
python -m pip install 'cloudoll[mysql,postgres,cache]==4.4.0'
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
The AWS extra requires Python 3.10+. Aurora remains untested; see
|
|
@@ -87,7 +87,8 @@ python -m pip install -r requirements.txt
|
|
|
87
87
|
cloudoll start -n myapp
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
The generated project targets 4.
|
|
90
|
+
The generated project targets 4.4.0 and receives random JWT and session secrets
|
|
91
|
+
in `.env`. Controllers
|
|
91
92
|
and authentication middleware are examples: replace demo credentials and supply
|
|
92
93
|
your own authorization and upload policies before deployment.
|
|
93
94
|
|
|
@@ -172,8 +173,8 @@ python -m ruff check cloudoll tests
|
|
|
172
173
|
python -m mypy
|
|
173
174
|
python -m pytest -q -m 'not integration' -k 'not aws'
|
|
174
175
|
python -m build
|
|
175
|
-
python tests/check_wheel.py dist/cloudoll-4.
|
|
176
|
-
python tests/check_release.py dist/cloudoll-4.
|
|
176
|
+
python tests/check_wheel.py dist/cloudoll-4.4.0-py3-none-any.whl
|
|
177
|
+
python tests/check_release.py dist/cloudoll-4.4.0-py3-none-any.whl
|
|
177
178
|
```
|
|
178
179
|
|
|
179
180
|
Native integration tests require explicitly configured disposable databases.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
English | [简体中文](README.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
Cloudoll 4.
|
|
5
|
+
Cloudoll 4.4.0 is an aiohttp-based Python library for routing, middleware,
|
|
6
6
|
templates, sessions, JWT, database access and application scaffolding.
|
|
7
7
|
|
|
8
8
|
## AI-assisted development
|
|
@@ -19,8 +19,8 @@ does not have `cloudoll ai --help`, consult its matching README and source first
|
|
|
19
19
|
Python 3.9+ is required. Install only the optional drivers you use:
|
|
20
20
|
|
|
21
21
|
```sh
|
|
22
|
-
python -m pip install 'cloudoll==4.
|
|
23
|
-
python -m pip install 'cloudoll[mysql,postgres,cache]==4.
|
|
22
|
+
python -m pip install 'cloudoll==4.4.0'
|
|
23
|
+
python -m pip install 'cloudoll[mysql,postgres,cache]==4.4.0'
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
The AWS extra requires Python 3.10+. Aurora remains untested; see
|
|
@@ -35,7 +35,8 @@ python -m pip install -r requirements.txt
|
|
|
35
35
|
cloudoll start -n myapp
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
The generated project targets 4.
|
|
38
|
+
The generated project targets 4.4.0 and receives random JWT and session secrets
|
|
39
|
+
in `.env`. Controllers
|
|
39
40
|
and authentication middleware are examples: replace demo credentials and supply
|
|
40
41
|
your own authorization and upload policies before deployment.
|
|
41
42
|
|
|
@@ -120,8 +121,8 @@ python -m ruff check cloudoll tests
|
|
|
120
121
|
python -m mypy
|
|
121
122
|
python -m pytest -q -m 'not integration' -k 'not aws'
|
|
122
123
|
python -m build
|
|
123
|
-
python tests/check_wheel.py dist/cloudoll-4.
|
|
124
|
-
python tests/check_release.py dist/cloudoll-4.
|
|
124
|
+
python tests/check_wheel.py dist/cloudoll-4.4.0-py3-none-any.whl
|
|
125
|
+
python tests/check_release.py dist/cloudoll-4.4.0-py3-none-any.whl
|
|
125
126
|
```
|
|
126
127
|
|
|
127
128
|
Native integration tests require explicitly configured disposable databases.
|
|
@@ -2,11 +2,11 @@
|
|
|
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.
|
|
9
|
+
本文适用于 Cloudoll 4.4.0。从 3.x 升级前请阅读下方迁移说明。
|
|
10
10
|
|
|
11
11
|
## AI 辅助开发
|
|
12
12
|
|
|
@@ -25,10 +25,10 @@ Cloudoll 是一个基于 aiohttp 的 Python Web 开发库,提供路由自动
|
|
|
25
25
|
|
|
26
26
|
## 安装
|
|
27
27
|
|
|
28
|
-
安装 4.
|
|
28
|
+
安装 4.4.0:
|
|
29
29
|
|
|
30
30
|
```sh
|
|
31
|
-
python -m pip install 'cloudoll==4.
|
|
31
|
+
python -m pip install 'cloudoll==4.4.0'
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
在当前仓库目录安装源码:
|
|
@@ -41,16 +41,16 @@ python -m pip install -e .
|
|
|
41
41
|
|
|
42
42
|
```sh
|
|
43
43
|
# MySQL
|
|
44
|
-
python -m pip install 'cloudoll[mysql]==4.
|
|
44
|
+
python -m pip install 'cloudoll[mysql]==4.4.0'
|
|
45
45
|
|
|
46
46
|
# PostgreSQL
|
|
47
|
-
python -m pip install 'cloudoll[postgres]==4.
|
|
47
|
+
python -m pip install 'cloudoll[postgres]==4.4.0'
|
|
48
48
|
|
|
49
49
|
# AWS 数据库驱动
|
|
50
|
-
python -m pip install 'cloudoll[aws]==4.
|
|
50
|
+
python -m pip install 'cloudoll[aws]==4.4.0'
|
|
51
51
|
|
|
52
52
|
# Redis / Memcached 会话或 Redis 数据库
|
|
53
|
-
python -m pip install 'cloudoll[cache]==4.
|
|
53
|
+
python -m pip install 'cloudoll[cache]==4.4.0'
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
源码开发时可用 `python -m pip install -e '.[mysql,postgres,cache,dev]'` 安装。
|
|
@@ -89,7 +89,7 @@ myapp/
|
|
|
89
89
|
└── templates/
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
脚手架中的鉴权中间件和登录接口用于演示。正式使用前,应替换示例账号、密码和业务鉴权逻辑。新建项目会在 `.env` 里获得相互独立的 JWT 与 Session 密钥。
|
|
93
93
|
|
|
94
94
|
### 手动创建应用
|
|
95
95
|
|
|
@@ -546,8 +546,8 @@ cloudoll restart -n myapp
|
|
|
546
546
|
python -m pip install -e '.[mysql,postgres,cache,dev]'
|
|
547
547
|
python -m pytest -q -m 'not integration' -k 'not aws'
|
|
548
548
|
python -m build
|
|
549
|
-
python tests/check_wheel.py dist/cloudoll-4.
|
|
550
|
-
python tests/check_release.py dist/cloudoll-4.
|
|
549
|
+
python tests/check_wheel.py dist/cloudoll-4.4.0-py3-none-any.whl
|
|
550
|
+
python tests/check_release.py dist/cloudoll-4.4.0-py3-none-any.whl
|
|
551
551
|
```
|
|
552
552
|
|
|
553
553
|
最后一条命令用于验证构建出的 wheel,版本号变化后请调整文件名。测试使用模拟数据库连接及本机 HTTP 服务;真实数据库连接和 AWS 故障切换需要单独的集成环境。
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "4.4.0"
|
|
@@ -6,10 +6,57 @@ icon: logs
|
|
|
6
6
|
|
|
7
7
|
# 更新日志
|
|
8
8
|
|
|
9
|
-
按版本倒序记录 Cloudoll 的主要变化。当前使用方法请以本站 4.
|
|
9
|
+
按版本倒序记录 Cloudoll 的主要变化。当前使用方法请以本站 4.4.0 文档为准,历史功能不代表当前仍保留相同 API。
|
|
10
10
|
|
|
11
11
|
历史版本日期为对应源码提交日期。各版本源码可通过表格中的链接查看。
|
|
12
12
|
|
|
13
|
+
## 4.4.0
|
|
14
|
+
|
|
15
|
+
缺陷修复版本,向后兼容;Python 3.9+ 仍然支持。
|
|
16
|
+
|
|
17
|
+
### 数据库
|
|
18
|
+
|
|
19
|
+
- `for_update()` 行锁必须在调用方自己的事务里使用。以前脱离事务的写法会临时开一个事务,锁随提交立刻释放,实际什么都没锁住;现在这样的调用直接报错。
|
|
20
|
+
- 开启事务时获取连接超时或被取消,已借到的连接会归还连接池,服务跑久了不再慢慢耗光连接。
|
|
21
|
+
- 模型字段恢复可哈希,可以放进 `set` 或作为字典键(列去重、批量排除字段)。
|
|
22
|
+
- `SUM(DISTINCT column)` 等聚合去重编译到聚合函数内部,去重发生在求和之前;聚合里的字面量不再被重复绑定参数。
|
|
23
|
+
- AWS 数据源串里的 URL 查询参数不再作为未知参数传给驱动。
|
|
24
|
+
|
|
25
|
+
### Web 与模板
|
|
26
|
+
|
|
27
|
+
- 已经开始输出的响应不再被 JSON 错误处理改写坏,流式与 SSE 接口出错时保留原响应。
|
|
28
|
+
- 页面响应的 `Content-Type` 不再出现两个 `charset`。
|
|
29
|
+
- `session` 的 `key`、`max_age`、`httponly`、`secure`、`domain`、`path`、`samesite` 在加密 Cookie、Redis、Memcached 三种存储下都生效。
|
|
30
|
+
- `render_view()` 的关键字参数只归一处:模板上下文按位置传字典,关键字参数一律是 `Response` 选项,传错直接抛 `TypeError`,不再被当成上下文吞掉。
|
|
31
|
+
- 默认监听 127.0.0.1,开发模式不再额外占用配置端口 +1。
|
|
32
|
+
- `server.ignore_dirs` 对 `logs` 等目录生效,写日志不再触发热重载。
|
|
33
|
+
- 脚手架的鉴权中间件示例与文档一致:async 装饰器写法,放行静态资源,不再把后续处理器的异常吞成带错误文本的 500。
|
|
34
|
+
|
|
35
|
+
### 命令行与新建项目
|
|
36
|
+
|
|
37
|
+
- `cloudoll create` 不再把 `__pycache__`、`.pyc` 复制进新项目。
|
|
38
|
+
- 项目名最后一段只能是字母、数字、下划线、点、短横线;带空格或 shell 元字符的名字被拒绝,已存在的目标目录也会报错。
|
|
39
|
+
- 创建失败时退出码非 0,并回收只写了一半的目录,不会留下半个项目。
|
|
40
|
+
- 新项目在 `.env` 里同时生成 JWT 与 Session 两把独立密钥,配置读取环境变量,`.gitignore` 忽略 `.env`。本地启动不再出现“使用随机进程本地密钥”的告警,重启和多 worker 之间登录状态不再丢失。
|
|
41
|
+
|
|
42
|
+
### 邮件与工具函数
|
|
43
|
+
|
|
44
|
+
- 邮件客户端构造时不再立刻联网,发送过一次或服务器空闲断开后仍能继续使用:每次 `send()` 自建连接、只带一份正文,失败时也会关闭连接。
|
|
45
|
+
- 缺少账号或授权码、收件人为空时立即给出可读错误,不再留给服务器报认证失败。
|
|
46
|
+
- `cloudoll.utils` 的可逆加密、单向摘要、随机字符串、分布式 ID 成为正式 API,见[工具函数](/utils)。存用户密码请改用 bcrypt/argon2,这些工具都不是口令哈希。
|
|
47
|
+
- 新增[发送邮件](/mail)页;`cloudoll/mail` 下从未生效的 POP3 草稿文件被移除,库本身不提供收信能力。
|
|
48
|
+
|
|
49
|
+
### 升级提示
|
|
50
|
+
|
|
51
|
+
- 在事务外调用 `for_update()`、给 `render_view()` 传多余关键字,以前是静默出错,现在会抛异常;升级前先检查这两类调用。
|
|
52
|
+
- `cloudoll create` 开始拒绝带空格和 shell 元字符的项目名。
|
|
53
|
+
- 老项目请自行补上 `session.secret_key` 或 `CLOUDOLL_SESSION_SECRET`,否则重启、多 worker 之间仍会丢登录状态。
|
|
54
|
+
|
|
55
|
+
### 文档
|
|
56
|
+
|
|
57
|
+
- JWT 中间件示例改用 `web.RequestKey`,避免 aiohttp 的字符串键告警。
|
|
58
|
+
- [快速开始](/)与 [Cookie 与 Session](/session-cookie)说明新项目的密钥注入方式。
|
|
59
|
+
|
|
13
60
|
## 4.3.0
|
|
14
61
|
|
|
15
62
|
### ORM 查询与写入
|
|
@@ -43,8 +90,6 @@ icon: logs
|
|
|
43
90
|
|
|
44
91
|
## 4.1.0
|
|
45
92
|
|
|
46
|
-
本节对应 4.1.0 源码变更;实际发布状态以 PyPI 为准。
|
|
47
|
-
|
|
48
93
|
- [默认数据源](/database#默认数据源与模型绑定):orm.default、模型 __datasource__、独立类级查询、query() 与 transaction(),保留显式 use(engine)。
|
|
49
94
|
- datasource_context 为独立任务和测试提供作用域绑定;事务复用现有引擎机制,应用与并发任务保持隔离。
|
|
50
95
|
- [请求参数校验](/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`
|
|
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
|
|
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
|
|
|
@@ -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
|
-
|
|
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.
|
|
9
|
+
本页介绍 Cloudoll 4.4.0 的原生 MySQL/PostgreSQL 接口,包括保存点、流式查询与 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.
|
|
29
|
+
python -m pip install 'cloudoll[mysql,postgres]==4.4.0'
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
Web 应用在 [database 配置](/config)中声明连接,由框架建立、关闭,通过 `request.app.db.别名` 获取。不要每个请求创建一个池,也不要在请求结束时关闭共享池。
|
|
@@ -251,7 +251,8 @@ present = await User.where(User.email == "alice@example.test").exists()
|
|
|
251
251
|
```
|
|
252
252
|
|
|
253
253
|
`join(..., kind="left" | "inner" | "right")` 默认仍是 LEFT JOIN,保持兼容。
|
|
254
|
-
`distinct()` 对整行投影去重;字段 `User.id.distinct().count()` 生成 COUNT(DISTINCT ...)
|
|
254
|
+
`distinct()` 对整行投影去重;字段 `User.id.distinct().count()` 生成 COUNT(DISTINCT ...),
|
|
255
|
+
聚合后的 `User.amount.sum().distinct()` 同样生成 SUM(DISTINCT ...),去重的是聚合输入值而不是聚合结果行。
|
|
255
256
|
`one_dict()` 返回字典或 None,不把别名/聚合列装进实体;原有 one/one_model 返回规则不变。
|
|
256
257
|
`exists()` 返回布尔值,保留筛选、分组和 offset,最多读取一行且不消费原查询;它不保证并发唯一性。
|
|
257
258
|
`count()` 忽略分页、排序和行锁,保留去重、分组与 HAVING。
|
|
@@ -411,7 +412,8 @@ async with Inventory.transaction():
|
|
|
411
412
|
|
|
412
413
|
`for_update(nowait=True)` 遇到锁竞争立即报错;
|
|
413
414
|
`for_update(skip_locked=True)` 跳过已锁行,适合任务领取,不适合完整性报表。两选项互斥。
|
|
414
|
-
|
|
415
|
+
调用方必须显式管理事务;事务外执行锁查询直接抛 TransactionError,不会自开事务后立刻提交,也不会自动重试。
|
|
416
|
+
锁查询不能使用只读 stream()。
|
|
415
417
|
聚合、DISTINCT、外联表的锁定限制由数据库决定,示例只锁普通实体行。
|
|
416
418
|
新增能力在原生 MySQL 8.4 / PostgreSQL 16 验证,不将该结果等同于 Aurora 验证。
|
|
417
419
|
|
|
@@ -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[
|
|
70
|
+
request[USER] = user
|
|
64
71
|
return await handler(request)
|
|
65
72
|
```
|
|
66
73
|
|
|
67
|
-
业务路由用 `request[
|
|
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.
|
|
12
|
+
> 本文档按 Cloudoll 4.4.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.
|
|
22
|
-
python -m pip install 'cloudoll[mysql,postgres,cache]==4.
|
|
21
|
+
python -m pip install 'cloudoll==4.4.0'
|
|
22
|
+
python -m pip install 'cloudoll[mysql,postgres,cache]==4.4.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
|
-
|
|
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
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 工具函数
|
|
3
|
+
order: 145
|
|
4
|
+
icon: config
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 工具函数
|
|
8
|
+
|
|
9
|
+
`cloudoll.utils` 里是与 Web 层、数据库都无关的小工具:可逆加密、单向摘要、随机字符串、分布式 ID。它们可以在任意脚本里直接导入,不需要 `Application`。
|
|
10
|
+
|
|
11
|
+
## 可逆加密
|
|
12
|
+
|
|
13
|
+
需要在之后取回原文时用 `CryFernet`(AES-128-CBC + HMAC,来自 `cryptography` 的 Fernet 令牌格式):
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
import os
|
|
17
|
+
|
|
18
|
+
from cryptography.fernet import Fernet
|
|
19
|
+
from cloudoll.utils.password import CryFernet
|
|
20
|
+
|
|
21
|
+
KEY = os.environ["CLOUDOLL_CRYPTO_KEY"] # 与数据库密码同级对待,见配置文件
|
|
22
|
+
|
|
23
|
+
crypt = CryFernet(KEY)
|
|
24
|
+
token = crypt.encrypt("sk-abcdef")
|
|
25
|
+
assert crypt.decrypt(token) == "sk-abcdef"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
密钥必须是 32 字节 url-safe base64 编码,新密钥用 `Fernet.generate_key().decode()` 生成,只放在进程环境里,见[配置文件](/config)。构造时密钥不合法会立即报错;换密钥后旧 token 一律解不开(抛 `InvalidToken`),所以轮换要先解密再重新加密。
|
|
29
|
+
|
|
30
|
+
同一明文每次得到的 token 都不同(内含时间戳和随机 IV),因此 token 不能作为等值判断的键,也不能直接当唯一索引;需要可比较的定长值时用下一节的摘要。
|
|
31
|
+
|
|
32
|
+
## 单向摘要
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from cloudoll.utils.password import hash as keyed_hash
|
|
36
|
+
|
|
37
|
+
digest = keyed_hash("alice", "pepper") # sha256("alice" + "pepper") 的十六进制
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
结果是 64 位小写十六进制,同样的输入永远得到同样的结果,适合做幂等键、缓存键、数据指纹。`key` 传 `None` 或空串都表示不加 pepper。
|
|
41
|
+
|
|
42
|
+
**不要用它存用户密码。** `hash()` 没有盐、没有迭代次数,同一密码的摘要固定,彩虹表和 GPU 暴力破解对它有效;用户密码要用 bcrypt、argon2 这类专用慢哈希(额外依赖由业务引入),框架不内置密码存储方案。
|
|
43
|
+
|
|
44
|
+
## 随机字符串
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from cloudoll.utils.password import generator
|
|
48
|
+
|
|
49
|
+
make_token = generator(32) # 返回函数,每次调用给一个新的随机串
|
|
50
|
+
make_with_symbols = generator(16, True)
|
|
51
|
+
|
|
52
|
+
value = make_token()
|
|
53
|
+
assert len(value) == 32
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`generator()` 返回的是生成器函数,不是字符串;字母表默认 `A-Za-z0-9`(62 个字符),`use_special=True` 时追加 `string.punctuation`(共 94 个)。取值用 `secrets`,可用于随机口令、邀请码、临时密钥。强度按字母表规模算:16 位字母数字约 95 bit,需要更高就加长。随机串本身不带校验,要防篡改仍需签名或加密。
|
|
57
|
+
|
|
58
|
+
## 分布式 ID
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from cloudoll.utils.snowflake import IdWorker
|
|
62
|
+
|
|
63
|
+
worker = IdWorker(worker_id=1, datacenter_id=1)
|
|
64
|
+
order_id = worker.next_id()
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
位分配与 Twitter Snowflake 一致:12 位序列号、5 位 `worker_id`、5 位 `datacenter_id`,高位是相对 2010-11-04 的毫秒时间戳。结果是 63 位以内的正整数,可以直接存 BigInt 字段。
|
|
68
|
+
|
|
69
|
+
- `worker_id`、`datacenter_id` 只接受 0 到 31,越界抛 `ValueError`。
|
|
70
|
+
- 一个 `worker_id + datacenter_id` 组合同时只能有一个实例在发号。多进程部署时每个进程都要分到不同编号,否则同一毫秒可能发出相同 ID。
|
|
71
|
+
- 单实例内 ID 严格递增,多线程共用同一实例是安全的(内部加锁)。同一毫秒内用完 4096 个序列号后,`next_id()` 会等到下一毫秒再返回。
|
|
72
|
+
- 检测到时钟回拨时抛异常,而不是返回可能重复的 ID。NTP 大步回调、虚机时钟漂移都会触发;不能接受异常的业务要自己降级(例如改用数据库序列)。
|
|
73
|
+
|
|
74
|
+
IdWorker 只负责生成 ID,不做持久去重,也不保证跨实例的全局有序。需要"绝不重复"时给该列加数据库唯一约束,见[数据库](/database)。
|