tina4-python 3.13.119__tar.gz → 3.13.120__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.
- {tina4_python-3.13.119 → tina4_python-3.13.120}/PKG-INFO +1 -1
- {tina4_python-3.13.119 → tina4_python-3.13.120}/pyproject.toml +1 -1
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/__init__.py +1 -1
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/cli/__init__.py +263 -22
- {tina4_python-3.13.119 → tina4_python-3.13.120}/.gitignore +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/README.md +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/CLAUDE.md +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/Testing.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/_import_helper.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/ai/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/ai/client.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/api/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/auth/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/cache/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/container/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/context/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/context/chunker.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/core/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/core/cache.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/core/constants.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/core/events.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/core/middleware.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/core/port_takeover.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/core/rate_limiter.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/core/request.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/core/response.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/core/router.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/core/server.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/crud/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/database/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/database/adapter.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/database/connection.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/database/database_url.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/database/firebird.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/database/mongodb.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/database/mssql.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/database/mysql.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/database/odbc.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/database/postgres.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/database/sql_translator.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/database/sqlite.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/debug/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/debug/error_overlay.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/dev_admin/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/dev_admin/metrics.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/dev_admin/plan.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/dev_admin/project_index.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/docs.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/docstore/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/dotenv/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/env.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/frond/FROND.md +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/frond/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/frond/compiler.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/frond/engine.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/frond/parser.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/gallery/auth/meta.json +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/gallery/database/meta.json +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/gallery/error-overlay/meta.json +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/gallery/orm/meta.json +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/gallery/queue/meta.json +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/gallery/rest-api/meta.json +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/gallery/templates/meta.json +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/graph/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/graph/adapter.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/graph/adapters/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/graph/adapters/arango.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/graph/adapters/bolt.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/graph/adapters/ultipa.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/graph/graph_url.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/graphql/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/i18n/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/mcp/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/mcp/protocol.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/mcp/tools.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/messenger/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/migration/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/migration/runner.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/mqtt/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/mqtt/message.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/orm/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/orm/fields.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/orm/model.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/orm/point.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/public/__feedback/widget.js +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/public/css/tina4.css +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/public/css/tina4.min.css +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/public/images/logo.svg +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/public/js/frond.js +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/public/js/frond.min.js +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/public/js/tina4.min.js +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/public/js/tina4js.min.js +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/query_builder/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/queue/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/queue/amqp_url.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/queue/job.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/queue/kafka_backend.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/queue/lite_backend.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/queue/mongo_backend.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/queue/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/queue_backends/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/queue_backends/kafka_backend.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/queue_backends/mongo_backend.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/realtime/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/realtime/models/Attachment.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/realtime/models/Channel.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/realtime/models/ChannelMember.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/realtime/models/Message.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/realtime/models/Workspace.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/realtime/models/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/realtime/storage.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/seeder/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/service/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/session/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/session_handlers/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/session_handlers/memcached_handler.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/session_handlers/mongodb_handler.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/session_handlers/redis_handler.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/session_handlers/valkey_handler.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/sso.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/swagger/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/templates/docker/python/Dockerfile +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/templates/docker/uv/Dockerfile +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/templates/errors/302.twig +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/templates/errors/401.twig +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/templates/errors/502.twig +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/templates/errors/503.twig +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/templates/errors/base.twig +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/templates/frontend/README.md +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/templates/readme.md +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/test/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/test_client/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/validator/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/websocket/__init__.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/websocket/backplane.py +0 -0
- {tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/wsdl/__init__.py +0 -0
|
@@ -986,7 +986,24 @@ _RESOLUTION_TARGETS = {"model", "route", "migration", "middleware"}
|
|
|
986
986
|
|
|
987
987
|
# Envelope schema version. Bump when the SHAPE changes; adding an optional
|
|
988
988
|
# key on the resolution object is not a break.
|
|
989
|
-
|
|
989
|
+
#
|
|
990
|
+
# 1.1 (ADR-0063 Wave 1, 3.13.120): additive keys `edit_hints[]` and `next[]`
|
|
991
|
+
# on `resolution`, plus `test_paths[]` (already emitted since v1) is now
|
|
992
|
+
# surfaced in the human stderr block too. Every existing generator template
|
|
993
|
+
# bakes `tina4:edit` markers at first-edit spots so a caller sees exactly
|
|
994
|
+
# where to point a follow-up patch. Old envelope keys are preserved; a v1
|
|
995
|
+
# consumer sees the v1 shape intact and simply ignores the new keys.
|
|
996
|
+
_RESOLUTION_ENVELOPE_VERSION = "1.1"
|
|
997
|
+
_RESOLUTION_ENVELOPE_NAME = "generate_v1_1"
|
|
998
|
+
|
|
999
|
+
# Marker syntax the generator templates bake in at first-edit spots — accepts
|
|
1000
|
+
# a Python (`#`), SQL (`--`), or Twig (`{#`) comment prefix, followed by a
|
|
1001
|
+
# short actionable label. The label ends at end-of-line or a closing Twig `#}`.
|
|
1002
|
+
# NEVER matches inside a string literal — the compiler regex is a plain textual
|
|
1003
|
+
# scan of freshly-written source, but no template contains a matching literal.
|
|
1004
|
+
_EDIT_MARKER_RE = re.compile(
|
|
1005
|
+
r"(?:#|--|\{#)\s*tina4:edit\s+([^\n#]+?)\s*(?:#\})?\s*$"
|
|
1006
|
+
)
|
|
990
1007
|
|
|
991
1008
|
|
|
992
1009
|
def _input_fields(flags: dict):
|
|
@@ -1002,6 +1019,118 @@ def _input_fields(flags: dict):
|
|
|
1002
1019
|
return value
|
|
1003
1020
|
|
|
1004
1021
|
|
|
1022
|
+
def _scan_edit_hints(root: Path, rel_paths: list[str]) -> list[dict]:
|
|
1023
|
+
"""Grep freshly-written files for `tina4:edit` markers (envelope v1.1).
|
|
1024
|
+
|
|
1025
|
+
Reads each `rel_paths` entry under `root`, walks its lines, and returns
|
|
1026
|
+
`[{file, line, label}]` — one entry per marker, in file+line order.
|
|
1027
|
+
|
|
1028
|
+
A missing file is silently skipped: the caller wires this into the
|
|
1029
|
+
envelope path where `actions_taken` is the source of truth for what
|
|
1030
|
+
landed. The scan is text-only — no imports, no parsing — so an edited
|
|
1031
|
+
template that fails to run still yields honest hints.
|
|
1032
|
+
"""
|
|
1033
|
+
hints: list[dict] = []
|
|
1034
|
+
for rel in rel_paths:
|
|
1035
|
+
f = root / rel
|
|
1036
|
+
if not f.is_file():
|
|
1037
|
+
continue
|
|
1038
|
+
try:
|
|
1039
|
+
text = f.read_text(encoding="utf-8")
|
|
1040
|
+
except OSError:
|
|
1041
|
+
continue
|
|
1042
|
+
for lineno, line in enumerate(text.splitlines(), start=1):
|
|
1043
|
+
match = _EDIT_MARKER_RE.search(line)
|
|
1044
|
+
if match:
|
|
1045
|
+
hints.append({
|
|
1046
|
+
"file": rel,
|
|
1047
|
+
"line": lineno,
|
|
1048
|
+
"label": match.group(1).strip(),
|
|
1049
|
+
})
|
|
1050
|
+
return hints
|
|
1051
|
+
|
|
1052
|
+
|
|
1053
|
+
def _hint_paths_from_resolution(target: str, resolution: dict) -> list[str]:
|
|
1054
|
+
"""The set of paths the caller should scan for `tina4:edit` markers.
|
|
1055
|
+
|
|
1056
|
+
Every path the generator ACTUALLY writes is worth scanning — the model
|
|
1057
|
+
file, the matching migration + down, the middleware file, the co-emitted
|
|
1058
|
+
test — because a marker in any of them names a first-edit spot the
|
|
1059
|
+
developer will hit. Order is stable: primary file, then migration + down,
|
|
1060
|
+
then any test paths declared in the envelope. The hints themselves land
|
|
1061
|
+
in file+line order regardless.
|
|
1062
|
+
"""
|
|
1063
|
+
paths: list[str] = []
|
|
1064
|
+
primary = resolution.get("file_path")
|
|
1065
|
+
if primary:
|
|
1066
|
+
paths.append(primary)
|
|
1067
|
+
|
|
1068
|
+
# Model + migration composite: the migration is written by _gen_migration
|
|
1069
|
+
# under the model's own generator, so its markers deserve to surface too.
|
|
1070
|
+
if target == "model" and resolution.get("migration_path"):
|
|
1071
|
+
migration = resolution["migration_path"]
|
|
1072
|
+
paths.append(migration)
|
|
1073
|
+
if migration.endswith(".sql"):
|
|
1074
|
+
paths.append(migration[:-len(".sql")] + ".down.sql")
|
|
1075
|
+
|
|
1076
|
+
# A bare migration also writes its own .down.sql.
|
|
1077
|
+
if target == "migration" and primary and primary.endswith(".sql"):
|
|
1078
|
+
paths.append(primary[:-len(".sql")] + ".down.sql")
|
|
1079
|
+
|
|
1080
|
+
# Co-emitted tests carry their own markers (see the test template).
|
|
1081
|
+
for test_path in resolution.get("test_paths", []):
|
|
1082
|
+
if test_path not in paths:
|
|
1083
|
+
paths.append(test_path)
|
|
1084
|
+
return paths
|
|
1085
|
+
|
|
1086
|
+
|
|
1087
|
+
def _next_steps_for(target: str, name: str, resolution: dict, flags: dict) -> list[str]:
|
|
1088
|
+
"""Curated actionable next-steps a caller should see after generation.
|
|
1089
|
+
|
|
1090
|
+
Kept short and concrete: what to edit, what to run, how to try it. The
|
|
1091
|
+
entries are per-target because a generic "next steps" list dilutes into
|
|
1092
|
+
noise — a route's next step is `curl`, a middleware's next step is
|
|
1093
|
+
`@middleware(...)` on a real route.
|
|
1094
|
+
|
|
1095
|
+
Placeholders are resolved from the same shape the envelope already
|
|
1096
|
+
reports (class_name, table_name, routes), so a caller can trust the
|
|
1097
|
+
strings match what actually landed on disk.
|
|
1098
|
+
"""
|
|
1099
|
+
if target == "model":
|
|
1100
|
+
table = resolution.get("table_name") or _to_table(name)
|
|
1101
|
+
return [
|
|
1102
|
+
f"Edit src/orm/{name}.py to add fields beyond the default `name`",
|
|
1103
|
+
"Apply the migration: tina4 migrate",
|
|
1104
|
+
f"Scaffold CRUD around it: tina4 generate crud {name} --skip-model",
|
|
1105
|
+
f"Try it: tina4 serve -> curl http://localhost:7146/api/{table}",
|
|
1106
|
+
]
|
|
1107
|
+
|
|
1108
|
+
if target == "route":
|
|
1109
|
+
route_path = name.lstrip("/")
|
|
1110
|
+
first_url = (resolution.get("routes") or [f"/api/{route_path}"])[0]
|
|
1111
|
+
return [
|
|
1112
|
+
f"Edit src/routes/{route_path}.py to customise the handlers",
|
|
1113
|
+
"Start the server: tina4 serve",
|
|
1114
|
+
f"Try it: curl http://localhost:7146{first_url}",
|
|
1115
|
+
]
|
|
1116
|
+
|
|
1117
|
+
if target == "migration":
|
|
1118
|
+
return [
|
|
1119
|
+
"Apply it: tina4 migrate",
|
|
1120
|
+
"Roll back last batch: tina4 migrate --rollback",
|
|
1121
|
+
"Inspect the pending: tina4 migrate --status",
|
|
1122
|
+
]
|
|
1123
|
+
|
|
1124
|
+
if target == "middleware":
|
|
1125
|
+
snake = _to_snake(name)
|
|
1126
|
+
return [
|
|
1127
|
+
f"Register it on a route with @middleware({name}) in src/routes/",
|
|
1128
|
+
f"Run the co-emitted test: .venv/bin/python -m pytest tests/test_{snake}.py",
|
|
1129
|
+
]
|
|
1130
|
+
|
|
1131
|
+
return []
|
|
1132
|
+
|
|
1133
|
+
|
|
1005
1134
|
def _resolve_generation(target: str, name: str, flags: dict,
|
|
1006
1135
|
timestamp: str | None = None) -> dict:
|
|
1007
1136
|
"""Compute the resolution mapping for a generate call. Pure — no writes.
|
|
@@ -1112,12 +1241,19 @@ def _actions_from_resolution(target: str, resolution: dict) -> list[str]:
|
|
|
1112
1241
|
|
|
1113
1242
|
|
|
1114
1243
|
def _format_resolution_block(name: str, target: str, resolution: dict,
|
|
1115
|
-
dry_run: bool = False
|
|
1244
|
+
dry_run: bool = False,
|
|
1245
|
+
edit_hints: list[dict] | None = None,
|
|
1246
|
+
next_steps: list[str] | None = None) -> str:
|
|
1116
1247
|
"""Render the human-readable resolution block for stderr on bare calls.
|
|
1117
1248
|
|
|
1118
1249
|
Same shape as the plan / ADR-0062 example. A generator with no route/table
|
|
1119
1250
|
footprint (middleware, route without a matching table) simply omits the
|
|
1120
1251
|
lines that don't apply — the block is honest about what was chosen.
|
|
1252
|
+
|
|
1253
|
+
ADR-0063 (envelope v1.1) also surfaces `test_paths[]` (was in the envelope
|
|
1254
|
+
since v1, never printed), plus `edit_hints[]` under "Edit these lines:"
|
|
1255
|
+
and `next_steps[]` under "Next:". Each section omits when empty so the
|
|
1256
|
+
block never grows a header without content beneath it.
|
|
1121
1257
|
"""
|
|
1122
1258
|
suffix = " (dry-run — nothing written)" if dry_run else ""
|
|
1123
1259
|
lines = [f"Generated {target} {name}{suffix}"]
|
|
@@ -1145,6 +1281,13 @@ def _format_resolution_block(name: str, target: str, resolution: dict,
|
|
|
1145
1281
|
if target != "migration" and resolution.get("migration_path"):
|
|
1146
1282
|
lines.append(f" migration {resolution['migration_path']}")
|
|
1147
1283
|
|
|
1284
|
+
# ADR-0063: surface test_paths[] in the human block too (it has been in the
|
|
1285
|
+
# envelope since v1 but only the JSON path printed it). Skipped when empty
|
|
1286
|
+
# so a migration without a test file doesn't grow an empty "tests" row.
|
|
1287
|
+
test_paths = resolution.get("test_paths") or []
|
|
1288
|
+
if test_paths:
|
|
1289
|
+
lines.append(f" tests {', '.join(test_paths)}")
|
|
1290
|
+
|
|
1148
1291
|
if pluralize:
|
|
1149
1292
|
lines.append("")
|
|
1150
1293
|
lines.append(f" To keep the raw name '{pluralize['from']}' as the table:")
|
|
@@ -1154,21 +1297,47 @@ def _format_resolution_block(name: str, target: str, resolution: dict,
|
|
|
1154
1297
|
"(opt-in, ADR-0062 forthcoming)"
|
|
1155
1298
|
)
|
|
1156
1299
|
|
|
1300
|
+
# ADR-0063 additive sections. Each renders only when there is content, so
|
|
1301
|
+
# a target with no markers or no next-steps stays as tight as before.
|
|
1302
|
+
if edit_hints:
|
|
1303
|
+
lines.append("")
|
|
1304
|
+
lines.append("Edit these lines:")
|
|
1305
|
+
# Left-pad the file:line column so labels align — makes scanning easier
|
|
1306
|
+
# when there are three or four hints of different path lengths.
|
|
1307
|
+
addrs = [f"{h['file']}:{h['line']}" for h in edit_hints]
|
|
1308
|
+
width = max(len(a) for a in addrs)
|
|
1309
|
+
for addr, hint in zip(addrs, edit_hints):
|
|
1310
|
+
lines.append(f" {addr:<{width}} {hint['label']}")
|
|
1311
|
+
|
|
1312
|
+
if next_steps:
|
|
1313
|
+
lines.append("")
|
|
1314
|
+
lines.append("Next:")
|
|
1315
|
+
for i, step in enumerate(next_steps, start=1):
|
|
1316
|
+
lines.append(f" {i}. {step}")
|
|
1317
|
+
|
|
1157
1318
|
return "\n".join(lines) + "\n"
|
|
1158
1319
|
|
|
1159
1320
|
|
|
1160
1321
|
def _emit_resolution(target: str, name: str, flags: dict) -> None:
|
|
1161
1322
|
"""Handle the --json / --dry-run / bare paths for the 4 wired generators.
|
|
1162
1323
|
|
|
1163
|
-
Contract shared by every language (ADR-0062):
|
|
1324
|
+
Contract shared by every language (ADR-0062, extended by ADR-0063):
|
|
1164
1325
|
- --json → envelope on stdout; nothing else on stdout.
|
|
1165
1326
|
- --dry-run → skip every write; envelope with dry_run=true.
|
|
1166
1327
|
- --json + --dry-run → both compose (envelope with actions_taken=[]).
|
|
1167
1328
|
- bare → normal write, then human resolution block on stderr.
|
|
1329
|
+
|
|
1330
|
+
Envelope v1.1 (ADR-0063) additively surfaces `resolution.edit_hints[]`
|
|
1331
|
+
(one per `tina4:edit` marker found in freshly-written files) and
|
|
1332
|
+
`resolution.next[]` (curated actionable next-steps per verb). The
|
|
1333
|
+
hints are scanned from disk on the write paths; on --dry-run the
|
|
1334
|
+
generator runs in a throw-away tmpdir so the same scan gives the
|
|
1335
|
+
caller a real preview without touching the working tree.
|
|
1168
1336
|
"""
|
|
1169
1337
|
import contextlib
|
|
1170
1338
|
import io
|
|
1171
1339
|
import json as _json
|
|
1340
|
+
import tempfile
|
|
1172
1341
|
|
|
1173
1342
|
dry_run = bool(flags.get("dry-run"))
|
|
1174
1343
|
want_json = bool(flags.get("json"))
|
|
@@ -1178,21 +1347,55 @@ def _emit_resolution(target: str, name: str, flags: dict) -> None:
|
|
|
1178
1347
|
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
|
|
1179
1348
|
resolution = _resolve_generation(target, name, flags, timestamp=timestamp)
|
|
1180
1349
|
|
|
1350
|
+
handler = GENERATORS[target]["handler"]
|
|
1351
|
+
generator_flags = dict(flags)
|
|
1352
|
+
generator_flags["_timestamp"] = timestamp
|
|
1353
|
+
# `_gen_*` handlers read the primary flags; `--dry-run` / `--json` are
|
|
1354
|
+
# envelope-only concerns, they must not leak into the underlying writer.
|
|
1355
|
+
generator_flags.pop("dry-run", None)
|
|
1356
|
+
generator_flags.pop("json", None)
|
|
1357
|
+
|
|
1181
1358
|
actions_taken: list[str] = []
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1359
|
+
edit_hints: list[dict] = []
|
|
1360
|
+
|
|
1361
|
+
hint_paths = _hint_paths_from_resolution(target, resolution)
|
|
1362
|
+
|
|
1363
|
+
if dry_run:
|
|
1364
|
+
# Render into a throw-away tree so the scan sees the SAME bytes the
|
|
1365
|
+
# writer would drop on disk. The envelope's paths still point at the
|
|
1366
|
+
# real project paths (unchanged by the tmpdir chdir) — the caller
|
|
1367
|
+
# can trust `edit_hints[].file` and `resolution.file_path` share a
|
|
1368
|
+
# coordinate space.
|
|
1369
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
1370
|
+
original_cwd = os.getcwd()
|
|
1371
|
+
os.chdir(tmp)
|
|
1372
|
+
try:
|
|
1373
|
+
# Suppress the generator's own "Created X" lines — this is a
|
|
1374
|
+
# dry-run, and its "creations" happen in a tmpdir the caller
|
|
1375
|
+
# will never see. `--json` also swallows stdout on the real
|
|
1376
|
+
# write path; this keeps the two paths symmetrical.
|
|
1377
|
+
with contextlib.redirect_stdout(io.StringIO()):
|
|
1378
|
+
handler(name, generator_flags)
|
|
1379
|
+
edit_hints = _scan_edit_hints(Path(tmp), hint_paths)
|
|
1380
|
+
finally:
|
|
1381
|
+
os.chdir(original_cwd)
|
|
1382
|
+
else:
|
|
1383
|
+
# Real write. When --json we swallow the generator's stdout so the
|
|
1185
1384
|
# JSON envelope is the SOLE thing on stdout; otherwise we let its
|
|
1186
1385
|
# "Created X" lines through as before.
|
|
1187
|
-
generator_flags = dict(flags)
|
|
1188
|
-
generator_flags["_timestamp"] = timestamp
|
|
1189
|
-
handler = GENERATORS[target]["handler"]
|
|
1190
|
-
|
|
1191
1386
|
stdout_sink = io.StringIO() if want_json else sys.stdout
|
|
1192
1387
|
with contextlib.redirect_stdout(stdout_sink):
|
|
1193
1388
|
handler(name, generator_flags)
|
|
1194
1389
|
|
|
1195
1390
|
actions_taken = _actions_from_resolution(target, resolution)
|
|
1391
|
+
edit_hints = _scan_edit_hints(Path.cwd(), hint_paths)
|
|
1392
|
+
|
|
1393
|
+
# ADR-0063 additive envelope keys — computed once and reused by the JSON
|
|
1394
|
+
# and human paths so what the caller sees on stderr matches the envelope
|
|
1395
|
+
# byte-for-byte.
|
|
1396
|
+
next_steps = _next_steps_for(target, name, resolution, flags)
|
|
1397
|
+
resolution["edit_hints"] = edit_hints
|
|
1398
|
+
resolution["next"] = next_steps
|
|
1196
1399
|
|
|
1197
1400
|
envelope = {
|
|
1198
1401
|
"command": "generate",
|
|
@@ -1209,8 +1412,12 @@ def _emit_resolution(target: str, name: str, flags: dict) -> None:
|
|
|
1209
1412
|
# Human resolution block always lands on STDERR (per plan): stdout
|
|
1210
1413
|
# keeps whatever the generator printed on the bare path, and the
|
|
1211
1414
|
# block never contends with a caller parsing stdout.
|
|
1212
|
-
sys.stderr.write(_format_resolution_block(
|
|
1213
|
-
|
|
1415
|
+
sys.stderr.write(_format_resolution_block(
|
|
1416
|
+
name, target, resolution,
|
|
1417
|
+
dry_run=dry_run,
|
|
1418
|
+
edit_hints=edit_hints,
|
|
1419
|
+
next_steps=next_steps,
|
|
1420
|
+
))
|
|
1214
1421
|
|
|
1215
1422
|
|
|
1216
1423
|
# ── Generate (rich scaffolding) ───────────────────────────────────────
|
|
@@ -1294,11 +1501,18 @@ def _gen_model(name: str, flags: dict, *, emit_test: bool = True):
|
|
|
1294
1501
|
|
|
1295
1502
|
imports = ", ".join(sorted(used_types))
|
|
1296
1503
|
|
|
1297
|
-
# Build field lines
|
|
1298
|
-
|
|
1504
|
+
# Build field lines. Bracketed by two `tina4:edit` markers (ADR-0063) so
|
|
1505
|
+
# a first-time user sees WHERE to add columns and relationships — the
|
|
1506
|
+
# `id` and `created_at` lines are framework-managed, the block between
|
|
1507
|
+
# is theirs to grow.
|
|
1508
|
+
field_lines = [
|
|
1509
|
+
" id = IntegerField(primary_key=True, auto_increment=True)",
|
|
1510
|
+
" # tina4:edit add fields here",
|
|
1511
|
+
]
|
|
1299
1512
|
for fname, ftype in fields:
|
|
1300
1513
|
info = FIELD_TYPE_MAP.get(ftype, FIELD_TYPE_MAP["string"])
|
|
1301
1514
|
field_lines.append(f" {fname} = {info['orm']}()")
|
|
1515
|
+
field_lines.append(" # tina4:edit add relationships here (e.g. author = ForeignKeyField(to=Author))")
|
|
1302
1516
|
field_lines.append(" created_at = DateTimeField()")
|
|
1303
1517
|
|
|
1304
1518
|
# Write model file
|
|
@@ -1395,6 +1609,7 @@ async def list_{route_path}(request, response):
|
|
|
1395
1609
|
per_page = int(request.query.get("per_page", 20))
|
|
1396
1610
|
offset = (page - 1) * per_page
|
|
1397
1611
|
records, total = {model}.where("1=1", limit=per_page, offset=offset, with_count=True)
|
|
1612
|
+
# tina4:edit customise the list projection (filters, ordering, fields)
|
|
1398
1613
|
return response({{
|
|
1399
1614
|
"records": [r.to_dict() for r in records],
|
|
1400
1615
|
"count": total,
|
|
@@ -1588,6 +1803,7 @@ def _gen_crud(name: str, flags: dict):
|
|
|
1588
1803
|
'{% block content %}\n'
|
|
1589
1804
|
'<div class="container mt-4">\n'
|
|
1590
1805
|
f' <h1>{name}s</h1>\n'
|
|
1806
|
+
' {# tina4:edit restrict fields exposed to the API here #}\n'
|
|
1591
1807
|
' <table class="table">\n'
|
|
1592
1808
|
' <thead>\n'
|
|
1593
1809
|
' <tr>\n'
|
|
@@ -1671,11 +1887,21 @@ def _gen_migration(name: str, flags: dict = None, *,
|
|
|
1671
1887
|
col_lines.append(f" {fname} {info['sql']}{default}")
|
|
1672
1888
|
col_lines.append(" created_at TEXT DEFAULT CURRENT_TIMESTAMP")
|
|
1673
1889
|
|
|
1674
|
-
|
|
1890
|
+
# ADR-0063: `tina4:edit` marker between the last column and the closing
|
|
1891
|
+
# paren, placed OUTSIDE the comma-joined column list so the SQL stays
|
|
1892
|
+
# valid. Points a first-time user at where to add columns beyond the
|
|
1893
|
+
# framework-managed id + created_at.
|
|
1894
|
+
up_sql = (
|
|
1895
|
+
f"CREATE TABLE IF NOT EXISTS {table} (\n"
|
|
1896
|
+
+ ",\n".join(col_lines)
|
|
1897
|
+
+ "\n -- tina4:edit add columns beyond id + created_at\n);"
|
|
1898
|
+
)
|
|
1675
1899
|
down_sql = f"DROP TABLE IF EXISTS {table};"
|
|
1676
1900
|
else:
|
|
1677
|
-
up_sql = f"--
|
|
1678
|
-
|
|
1901
|
+
up_sql = (f"-- tina4:edit write the UP migration SQL\n"
|
|
1902
|
+
f"-- Example: ALTER TABLE {table} ADD COLUMN new_col TEXT DEFAULT '';")
|
|
1903
|
+
down_sql = (f"-- tina4:edit write the DOWN rollback SQL\n"
|
|
1904
|
+
f"-- Example: ALTER TABLE {table} DROP COLUMN new_col;")
|
|
1679
1905
|
|
|
1680
1906
|
content = (
|
|
1681
1907
|
f"-- Migration: {name}\n"
|
|
@@ -1739,12 +1965,14 @@ class {name}:
|
|
|
1739
1965
|
Return (request, response) to continue, or
|
|
1740
1966
|
return (request, response("error", 401)) to block.
|
|
1741
1967
|
"""
|
|
1968
|
+
# tina4:edit guard the request here (auth check, rate limit, headers)
|
|
1742
1969
|
Log.info(f"{name}: {{request.method}} {{request.url}}")
|
|
1743
1970
|
return request, response
|
|
1744
1971
|
|
|
1745
1972
|
@staticmethod
|
|
1746
1973
|
def after_{snake}(request, response):
|
|
1747
1974
|
"""Runs after the route handler."""
|
|
1975
|
+
# tina4:edit inject headers or audit the response here
|
|
1748
1976
|
return request, response
|
|
1749
1977
|
'''
|
|
1750
1978
|
path.write_text(content, encoding="utf-8")
|
|
@@ -1861,7 +2089,7 @@ class Test{model}:
|
|
|
1861
2089
|
|
|
1862
2090
|
def test_list_{snake}(self):
|
|
1863
2091
|
"""Test listing {snake}."""
|
|
1864
|
-
#
|
|
2092
|
+
# tina4:edit add assertions here (real DB, real request — no mocks)
|
|
1865
2093
|
assert True
|
|
1866
2094
|
|
|
1867
2095
|
def test_get_{singular}(self):
|
|
@@ -1902,6 +2130,7 @@ class Test{name.title().replace("_", "")}:
|
|
|
1902
2130
|
|
|
1903
2131
|
def test_example(self):
|
|
1904
2132
|
"""Example test — replace with real tests."""
|
|
2133
|
+
# tina4:edit add assertions here (real DB, real request — no mocks)
|
|
1905
2134
|
assert True
|
|
1906
2135
|
'''
|
|
1907
2136
|
|
|
@@ -2463,6 +2692,7 @@ def _gen_service(name: str, flags: dict = None):
|
|
|
2463
2692
|
' """The scheduled task body. `context` is a ServiceContext with\n'
|
|
2464
2693
|
" .name, .stop_event and .log; a daemon-style task would loop until\n"
|
|
2465
2694
|
' context.stop_event.is_set()."""\n'
|
|
2695
|
+
" # tina4:edit main service loop here\n"
|
|
2466
2696
|
"__BODY__"
|
|
2467
2697
|
"\n\n"
|
|
2468
2698
|
"def register(runner: ServiceRunner) -> None:\n"
|
|
@@ -2534,6 +2764,7 @@ def _gen_queue(name: str, flags: dict = None):
|
|
|
2534
2764
|
"\n"
|
|
2535
2765
|
"def handle___SLUG__(data: dict):\n"
|
|
2536
2766
|
' """Process ONE __TOPIC__ job payload (`data` is Job.data — the pushed dict)."""\n'
|
|
2767
|
+
" # tina4:edit handle the job payload here\n"
|
|
2537
2768
|
"__BODY__"
|
|
2538
2769
|
"\n\n"
|
|
2539
2770
|
"def consume___SLUG__(context=None):\n"
|
|
@@ -2612,6 +2843,7 @@ def _gen_validator(name: str, flags: dict = None):
|
|
|
2612
2843
|
' return response.error("VALIDATION_FAILED", v.errors()[0]["message"], 400)\n'
|
|
2613
2844
|
' """\n'
|
|
2614
2845
|
" validator = Validator(data)\n"
|
|
2846
|
+
" # tina4:edit add validation rules here\n"
|
|
2615
2847
|
"__BODY__"
|
|
2616
2848
|
" return validator\n"
|
|
2617
2849
|
)
|
|
@@ -2664,6 +2896,7 @@ def _gen_seeder(name: str, flags: dict = None):
|
|
|
2664
2896
|
" need a specific shape here. Each callable receives a FakeData instance:\n"
|
|
2665
2897
|
' return {"email": lambda fake: fake.email(), "status": "active"}\n'
|
|
2666
2898
|
' """\n'
|
|
2899
|
+
" # tina4:edit add seed data here\n"
|
|
2667
2900
|
"__BODY__"
|
|
2668
2901
|
"\n\n"
|
|
2669
2902
|
"def run(db):\n"
|
|
@@ -2730,6 +2963,7 @@ def _gen_websocket(name: str, flags: dict = None):
|
|
|
2730
2963
|
' if event == "close":\n'
|
|
2731
2964
|
" return\n"
|
|
2732
2965
|
' # event == "message"\n'
|
|
2966
|
+
" # tina4:edit handle inbound messages here\n"
|
|
2733
2967
|
"__BODY__"
|
|
2734
2968
|
)
|
|
2735
2969
|
content = (
|
|
@@ -2781,6 +3015,7 @@ def _gen_listener(name: str, flags: dict = None):
|
|
|
2781
3015
|
'@on("__EVENT__")\n'
|
|
2782
3016
|
"def on___SLUG__(data=None):\n"
|
|
2783
3017
|
" \"\"\"React to '__EVENT__'. `data` is whatever emit('__EVENT__', data) passed.\"\"\"\n"
|
|
3018
|
+
" # tina4:edit react to the event here\n"
|
|
2784
3019
|
"__BODY__"
|
|
2785
3020
|
)
|
|
2786
3021
|
content = (
|
|
@@ -2866,6 +3101,7 @@ def _gen_form(name: str, flags: dict = None):
|
|
|
2866
3101
|
' <h1>{%% if item.id %%}Edit %s{%% else %%}Create %s{%% endif %%}</h1>\n'
|
|
2867
3102
|
' <form method="post" action="/api/%s{%% if item.id %%}/{{ item.id }}{%% endif %%}">\n'
|
|
2868
3103
|
' {{ form_token() }}\n'
|
|
3104
|
+
' {# tina4:edit customise the form fields here #}\n'
|
|
2869
3105
|
'%s'
|
|
2870
3106
|
' <button type="submit" class="btn btn-primary">\n'
|
|
2871
3107
|
' {%% if item.id %%}Update{%% else %%}Create{%% endif %%}\n'
|
|
@@ -2911,6 +3147,7 @@ def _gen_view(name: str, flags: dict = None):
|
|
|
2911
3147
|
' <h1>%s</h1>\n'
|
|
2912
3148
|
' <a href="/%s/create" class="btn btn-primary">Add %s</a>\n'
|
|
2913
3149
|
' </div>\n'
|
|
3150
|
+
' {# tina4:edit customise the list columns or add filters here #}\n'
|
|
2914
3151
|
' <table class="table">\n'
|
|
2915
3152
|
' <thead>\n'
|
|
2916
3153
|
' <tr>\n'
|
|
@@ -2959,6 +3196,7 @@ def _gen_view(name: str, flags: dict = None):
|
|
|
2959
3196
|
' <a href="/%s" class="btn btn-outline-secondary">Back</a>\n'
|
|
2960
3197
|
' </div>\n'
|
|
2961
3198
|
' </div>\n'
|
|
3199
|
+
' {# tina4:edit arrange the detail fields here #}\n'
|
|
2962
3200
|
'%s\n'
|
|
2963
3201
|
'</div>\n'
|
|
2964
3202
|
'{%% endblock %%}\n'
|
|
@@ -3009,6 +3247,7 @@ def _gen_auth(name: str = None, flags: dict = None):
|
|
|
3009
3247
|
' user = User.create({\n'
|
|
3010
3248
|
' "email": email,\n'
|
|
3011
3249
|
' "password": Auth.hash_password(password),\n'
|
|
3250
|
+
' # tina4:edit add roles/permissions here\n'
|
|
3012
3251
|
' "role": "user",\n'
|
|
3013
3252
|
' })\n'
|
|
3014
3253
|
' return response({"message": "Registered", "id": user.id}, 201)\n\n\n'
|
|
@@ -3375,12 +3614,14 @@ def _commands_manifest() -> dict:
|
|
|
3375
3614
|
"framework": "python",
|
|
3376
3615
|
"version": __version__,
|
|
3377
3616
|
"commands": commands,
|
|
3378
|
-
# ADR-0062: agents discover the generate resolution envelope
|
|
3379
|
-
# programmatically. A version bump here is a break; adding an
|
|
3380
|
-
# optional envelope key is not.
|
|
3617
|
+
# ADR-0062 / ADR-0063: agents discover the generate resolution envelope
|
|
3618
|
+
# shape programmatically. A version bump here is a break; adding an
|
|
3619
|
+
# optional envelope key is not. 1.1 (envelope generate_v1_1) added
|
|
3620
|
+
# `edit_hints[]` and `next[]` on the resolution — every v1 key
|
|
3621
|
+
# preserved, so a v1 consumer keeps working unchanged.
|
|
3381
3622
|
"resolution_contract": {
|
|
3382
3623
|
"version": _RESOLUTION_ENVELOPE_VERSION,
|
|
3383
|
-
"envelope":
|
|
3624
|
+
"envelope": _RESOLUTION_ENVELOPE_NAME,
|
|
3384
3625
|
},
|
|
3385
3626
|
}
|
|
3386
3627
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/gallery/error-overlay/meta.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/public/images/tina4-logo-icon.webp
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/public/js/tina4-dev-admin.min.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/public/swagger/oauth2-redirect.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/queue_backends/kafka_backend.py
RENAMED
|
File without changes
|
{tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/queue_backends/mongo_backend.py
RENAMED
|
File without changes
|
{tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/queue_backends/rabbitmq_backend.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/realtime/models/ChannelMember.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/session_handlers/memcached_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/session_handlers/mongodb_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/session_handlers/redis_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/session_handlers/valkey_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/templates/docker/distroless/Dockerfile
RENAMED
|
File without changes
|
{tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/templates/docker/poetry/Dockerfile
RENAMED
|
File without changes
|
{tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/templates/docker/python/Dockerfile
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/translations/af/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/translations/af/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/translations/en/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/translations/en/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/translations/es/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/translations/es/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/translations/fr/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/translations/fr/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/translations/ja/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/translations/ja/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/translations/zh/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.119 → tina4_python-3.13.120}/tina4_python/translations/zh/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|