tina4-python 3.13.94__tar.gz → 3.13.96__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.94 → tina4_python-3.13.96}/.gitignore +12 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/PKG-INFO +21 -2
- {tina4_python-3.13.94 → tina4_python-3.13.96}/README.md +1 -1
- tina4_python-3.13.96/pyproject.toml +125 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/CLAUDE.md +15 -6
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/__init__.py +2 -1
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/api/__init__.py +10 -7
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/auth/__init__.py +239 -35
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/cache/__init__.py +630 -163
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/cli/__init__.py +5 -1
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/core/middleware.py +269 -31
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/core/rate_limiter.py +28 -2
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/core/request.py +108 -6
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/core/response.py +92 -3
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/core/router.py +55 -11
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/core/server.py +1054 -302
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/crud/__init__.py +10 -11
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/database/adapter.py +411 -147
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/database/connection.py +180 -23
- tina4_python-3.13.96/tina4_python/database/database_url.py +365 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/database/firebird.py +163 -48
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/database/mongodb.py +15 -3
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/database/mssql.py +58 -49
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/database/mysql.py +47 -49
- tina4_python-3.13.96/tina4_python/database/odbc.py +224 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/database/postgres.py +39 -48
- tina4_python-3.13.96/tina4_python/database/sql_translator.py +272 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/database/sqlite.py +19 -38
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/debug/__init__.py +262 -45
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/dev_admin/__init__.py +96 -49
- tina4_python-3.13.96/tina4_python/dev_admin/metrics.py +375 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/docstore/__init__.py +240 -49
- tina4_python-3.13.96/tina4_python/dotenv/__init__.py +288 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/mcp/tools.py +12 -2
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/messenger/__init__.py +158 -79
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/migration/runner.py +29 -2
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/mqtt/__init__.py +5 -1
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/orm/model.py +91 -18
- tina4_python-3.13.96/tina4_python/public/css/tina4.min.css +1 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/queue/__init__.py +35 -28
- tina4_python-3.13.96/tina4_python/queue/amqp_url.py +73 -0
- tina4_python-3.13.96/tina4_python/queue/kafka_backend.py +210 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/queue/lite_backend.py +23 -1
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/queue/mongo_backend.py +65 -6
- tina4_python-3.13.96/tina4_python/queue/rabbitmq_backend.py +221 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/queue_backends/kafka_backend.py +13 -7
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/queue_backends/mongo_backend.py +20 -7
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/queue_backends/rabbitmq_backend.py +31 -17
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/session/__init__.py +190 -16
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/session_handlers/__init__.py +8 -2
- tina4_python-3.13.96/tina4_python/session_handlers/memcached_handler.py +155 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/session_handlers/mongodb_handler.py +81 -29
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/session_handlers/redis_handler.py +4 -1
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/session_handlers/valkey_handler.py +4 -1
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/swagger/__init__.py +6 -3
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/websocket/__init__.py +9 -3
- tina4_python-3.13.94/pyproject.toml +0 -73
- tina4_python-3.13.94/tina4_python/database/odbc.py +0 -177
- tina4_python-3.13.94/tina4_python/dev_admin/metrics.py +0 -867
- tina4_python-3.13.94/tina4_python/dev_admin/metrics_engine.py +0 -108
- tina4_python-3.13.94/tina4_python/dotenv/__init__.py +0 -163
- tina4_python-3.13.94/tina4_python/public/css/tina4.min.css +0 -1
- tina4_python-3.13.94/tina4_python/queue/kafka_backend.py +0 -151
- tina4_python-3.13.94/tina4_python/queue/rabbitmq_backend.py +0 -186
- tina4_python-3.13.94/tina4_python/scss/__init__.py +0 -694
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/Testing.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/ai/__init__.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/container/__init__.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/context/__init__.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/context/chunker.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/core/__init__.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/core/cache.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/core/constants.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/core/events.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/database/__init__.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/debug/error_overlay.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/dev_admin/plan.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/dev_admin/project_index.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/docs.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/env.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/frond/FROND.md +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/frond/__init__.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/frond/compiler.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/frond/engine.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/frond/parser.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/auth/meta.json +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/database/meta.json +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/error-overlay/meta.json +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/orm/meta.json +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/queue/meta.json +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/rest-api/meta.json +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/templates/meta.json +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/graphql/__init__.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/i18n/__init__.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/mcp/__init__.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/mcp/protocol.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/migration/__init__.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/mqtt/message.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/orm/__init__.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/orm/fields.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/public/__feedback/widget.js +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/public/css/tina4.css +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/public/images/logo.svg +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/public/js/frond.js +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/public/js/frond.min.js +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/public/js/tina4.min.js +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/public/js/tina4js.min.js +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/query_builder/__init__.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/queue/job.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/queue_backends/__init__.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/realtime/__init__.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/realtime/models/Attachment.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/realtime/models/Channel.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/realtime/models/ChannelMember.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/realtime/models/Message.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/realtime/models/Workspace.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/realtime/models/__init__.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/realtime/storage.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/_alerts.scss +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/_badges.scss +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/_buttons.scss +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/_cards.scss +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/_forms.scss +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/_grid.scss +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/_modals.scss +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/_nav.scss +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/_pagination.scss +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/_reset.scss +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/_tables.scss +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/_typography.scss +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/_utilities.scss +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/_variables.scss +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/base.scss +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/colors.scss +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/tina4.scss +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/seeder/__init__.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/service/__init__.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/templates/docker/python/Dockerfile +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/templates/docker/uv/Dockerfile +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/templates/errors/302.twig +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/templates/errors/401.twig +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/templates/errors/502.twig +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/templates/errors/503.twig +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/templates/errors/base.twig +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/templates/frontend/README.md +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/templates/readme.md +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/test/__init__.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/test_client/__init__.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/validator/__init__.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/websocket/backplane.py +0 -0
- {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/wsdl/__init__.py +0 -0
|
@@ -10,6 +10,13 @@
|
|
|
10
10
|
.env.local
|
|
11
11
|
/public/css/test.css
|
|
12
12
|
/sessions/
|
|
13
|
+
# Runtime session files written by the suite. This was the ONE gap of the four:
|
|
14
|
+
# php ignores /data/, node ignores data/, ruby ignores sessions/ (and defaults to
|
|
15
|
+
# pwd/sessions anyway) - only python left data/sessions/ visible, so a plain test
|
|
16
|
+
# run dirtied the tree and `git add -A` committed session payloads. It did: five
|
|
17
|
+
# of them are tracked on feature/audit-auth. FileSessionHandler already does
|
|
18
|
+
# mkdir(parents=True, exist_ok=True), so a fresh clone needs nothing here.
|
|
19
|
+
/data/sessions/
|
|
13
20
|
/tests/secrets/domain.cert
|
|
14
21
|
/tests/secrets/private.key
|
|
15
22
|
/tests/secrets/public.key
|
|
@@ -98,3 +105,8 @@ example/src/**/__pycache__/
|
|
|
98
105
|
# relative to cwd would create these under the repo. Never commit them.
|
|
99
106
|
/private/
|
|
100
107
|
/var/folders/
|
|
108
|
+
/data/sessions/
|
|
109
|
+
|
|
110
|
+
# Local file-backend queue store - runtime artefacts, never committed.
|
|
111
|
+
# Four probe .queue-data files were swept in by `git add -A` on 2026-08-03.
|
|
112
|
+
data/queue/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tina4-python
|
|
3
|
-
Version: 3.13.
|
|
3
|
+
Version: 3.13.96
|
|
4
4
|
Summary: Tina4 Python v3 — Zero-dependency, lightweight web framework
|
|
5
5
|
Author-email: Andre van Zuydam <andrevanzuydam@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -14,6 +14,8 @@ Requires-Dist: pymssql>=2.3; extra == 'all-db'
|
|
|
14
14
|
Requires-Dist: pyodbc>=5.0; extra == 'all-db'
|
|
15
15
|
Provides-Extra: firebird
|
|
16
16
|
Requires-Dist: firebird-driver>=1.10; extra == 'firebird'
|
|
17
|
+
Provides-Extra: kafka
|
|
18
|
+
Requires-Dist: confluent-kafka>=2.0; extra == 'kafka'
|
|
17
19
|
Provides-Extra: memcache
|
|
18
20
|
Requires-Dist: pymemcache>=4.0; extra == 'memcache'
|
|
19
21
|
Provides-Extra: mongo
|
|
@@ -26,8 +28,25 @@ Provides-Extra: odbc
|
|
|
26
28
|
Requires-Dist: pyodbc>=5.0; extra == 'odbc'
|
|
27
29
|
Provides-Extra: postgres
|
|
28
30
|
Requires-Dist: psycopg2-binary>=2.9; extra == 'postgres'
|
|
31
|
+
Provides-Extra: rabbitmq
|
|
32
|
+
Requires-Dist: pika>=1.3; extra == 'rabbitmq'
|
|
29
33
|
Provides-Extra: redis
|
|
30
34
|
Requires-Dist: redis>=5.0; extra == 'redis'
|
|
35
|
+
Provides-Extra: s3
|
|
36
|
+
Requires-Dist: boto3>=1.34; extra == 's3'
|
|
37
|
+
Provides-Extra: test
|
|
38
|
+
Requires-Dist: boto3>=1.34; extra == 'test'
|
|
39
|
+
Requires-Dist: confluent-kafka>=2.0; extra == 'test'
|
|
40
|
+
Requires-Dist: firebird-driver>=1.10; extra == 'test'
|
|
41
|
+
Requires-Dist: mysql-connector-python>=9.0; extra == 'test'
|
|
42
|
+
Requires-Dist: openapi-spec-validator>=0.7; extra == 'test'
|
|
43
|
+
Requires-Dist: pika>=1.3; extra == 'test'
|
|
44
|
+
Requires-Dist: psycopg2-binary>=2.9; extra == 'test'
|
|
45
|
+
Requires-Dist: pymemcache>=4.0; extra == 'test'
|
|
46
|
+
Requires-Dist: pymongo>=4.0; extra == 'test'
|
|
47
|
+
Requires-Dist: pymssql>=2.3; extra == 'test'
|
|
48
|
+
Requires-Dist: pyodbc>=5.0; extra == 'test'
|
|
49
|
+
Requires-Dist: redis>=5.0; extra == 'test'
|
|
31
50
|
Description-Content-Type: text/markdown
|
|
32
51
|
|
|
33
52
|
<p align="center">
|
|
@@ -116,7 +135,7 @@ Every feature is built from scratch -- no pip install, no node_modules, no third
|
|
|
116
135
|
| **Core HTTP** (7) | Router with path params (`{id:int}`, `{p:path}`), Server, Request/Response, Middleware pipeline, Static file serving, CORS |
|
|
117
136
|
| **Database** (6) | SQLite, PostgreSQL, MySQL, MSSQL, Firebird: unified adapter, connection pooling, query cache, transactions, race-safe ID generation, SQL dialect translation |
|
|
118
137
|
| **ORM** (7) | Active Record with typed fields, relationships (`has_one`/`has_many`/`belongs_to`), soft delete, QueryBuilder + MongoDB support, Auto-CRUD generator, migrations with rollback |
|
|
119
|
-
| **Auth & Security** (5) | JWT (HS256/RS256), password hashing (PBKDF2-SHA256), API key validation, rate limiting, CSRF form tokens |
|
|
138
|
+
| **Auth & Security** (5) | JWT (HS256/HS384/HS512, zero-dependency; RS256 opt-in, needs `cryptography`), password hashing (PBKDF2-SHA256), API key validation, rate limiting, CSRF form tokens |
|
|
120
139
|
| **Templating** (3) | Frond engine (Twig/Jinja2-compatible, pre-compiled 2.8x faster), SCSS auto-compilation, built-in CSS (~24 KB) |
|
|
121
140
|
| **API & Integration** (5) | HTTP client (zero-dep), GraphQL with ORM auto-schema + GraphiQL IDE, WSDL/SOAP with auto WSDL, WebSocket (RFC 6455) + Redis backplane, MCP server (24 dev tools) |
|
|
122
141
|
| **Background** (3) | Job queue (File/RabbitMQ/Kafka/MongoDB) with priority, delay, retry, dead letters; service runner; event system (on/emit/once/off) |
|
|
@@ -84,7 +84,7 @@ Every feature is built from scratch -- no pip install, no node_modules, no third
|
|
|
84
84
|
| **Core HTTP** (7) | Router with path params (`{id:int}`, `{p:path}`), Server, Request/Response, Middleware pipeline, Static file serving, CORS |
|
|
85
85
|
| **Database** (6) | SQLite, PostgreSQL, MySQL, MSSQL, Firebird: unified adapter, connection pooling, query cache, transactions, race-safe ID generation, SQL dialect translation |
|
|
86
86
|
| **ORM** (7) | Active Record with typed fields, relationships (`has_one`/`has_many`/`belongs_to`), soft delete, QueryBuilder + MongoDB support, Auto-CRUD generator, migrations with rollback |
|
|
87
|
-
| **Auth & Security** (5) | JWT (HS256/RS256), password hashing (PBKDF2-SHA256), API key validation, rate limiting, CSRF form tokens |
|
|
87
|
+
| **Auth & Security** (5) | JWT (HS256/HS384/HS512, zero-dependency; RS256 opt-in, needs `cryptography`), password hashing (PBKDF2-SHA256), API key validation, rate limiting, CSRF form tokens |
|
|
88
88
|
| **Templating** (3) | Frond engine (Twig/Jinja2-compatible, pre-compiled 2.8x faster), SCSS auto-compilation, built-in CSS (~24 KB) |
|
|
89
89
|
| **API & Integration** (5) | HTTP client (zero-dep), GraphQL with ORM auto-schema + GraphiQL IDE, WSDL/SOAP with auto WSDL, WebSocket (RFC 6455) + Redis backplane, MCP server (24 dev tools) |
|
|
90
90
|
| **Background** (3) | Job queue (File/RabbitMQ/Kafka/MongoDB) with priority, delay, retry, dead letters; service runner; event system (on/emit/once/off) |
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "tina4-python"
|
|
3
|
+
version = "3.13.96"
|
|
4
|
+
description = "Tina4 Python v3 — Zero-dependency, lightweight web framework"
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "Andre van Zuydam", email = "andrevanzuydam@gmail.com"}
|
|
7
|
+
]
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.12"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
|
|
12
|
+
# ZERO core dependencies — this is v3's defining feature
|
|
13
|
+
dependencies = []
|
|
14
|
+
|
|
15
|
+
[project.optional-dependencies]
|
|
16
|
+
# Database drivers — install what you need
|
|
17
|
+
postgres = ["psycopg2-binary>=2.9"]
|
|
18
|
+
mysql = ["mysql-connector-python>=9.0"]
|
|
19
|
+
mssql = ["pymssql>=2.3"]
|
|
20
|
+
firebird = ["firebird-driver>=1.10"]
|
|
21
|
+
mongo = ["pymongo>=4.0"]
|
|
22
|
+
odbc = ["pyodbc>=5.0"]
|
|
23
|
+
|
|
24
|
+
# Session backends — install what you need
|
|
25
|
+
redis = ["redis>=5.0"]
|
|
26
|
+
memcache = ["pymemcache>=4.0"]
|
|
27
|
+
|
|
28
|
+
# File-storage backends — install what you need.
|
|
29
|
+
# The `local` filesystem backend is the zero-dependency default; S3Storage
|
|
30
|
+
# (TINA4_STORAGE_BACKEND=s3) is opt-in and imports boto3 lazily.
|
|
31
|
+
s3 = ["boto3>=1.34"]
|
|
32
|
+
|
|
33
|
+
# Queue backends — install what you need
|
|
34
|
+
kafka = ["confluent-kafka>=2.0"]
|
|
35
|
+
rabbitmq = ["pika>=1.3"]
|
|
36
|
+
|
|
37
|
+
# All database drivers
|
|
38
|
+
all-db = [
|
|
39
|
+
"psycopg2-binary>=2.9",
|
|
40
|
+
"mysql-connector-python>=9.0",
|
|
41
|
+
"pymssql>=2.3",
|
|
42
|
+
"firebird-driver>=1.10",
|
|
43
|
+
"pymongo>=4.0",
|
|
44
|
+
"pyodbc>=5.0",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
# Every client the TEST SUITE needs against live services.
|
|
48
|
+
#
|
|
49
|
+
# These were installed ad hoc by CI (`uv pip install pymongo confluent-kafka
|
|
50
|
+
# pika ...`) and declared nowhere, so a fresh host had no way to know what the
|
|
51
|
+
# suite requires. confluent-kafka and pika in particular were in NO extra: the
|
|
52
|
+
# lab host's first full-suite run failed with three "confluent-kafka not
|
|
53
|
+
# installed" errors, correctly raised by the TINA4_REQUIRE_SERVICES gate, and
|
|
54
|
+
# `uv sync --all-extras` could not have fixed it because the dependency was not
|
|
55
|
+
# expressed anywhere. One declared extra means `uv sync --extra test` is
|
|
56
|
+
# reproducible on any machine.
|
|
57
|
+
#
|
|
58
|
+
# Includes firebird-driver even though the Firebird CI job installs it on its
|
|
59
|
+
# own: `uv sync --extra X` PRUNES anything outside the named extras, so a test
|
|
60
|
+
# extra that omitted it would silently uninstall it on a host that had it.
|
|
61
|
+
#
|
|
62
|
+
# boto3 is here for exactly that reason, MEASURED on the lab 2026-08-06
|
|
63
|
+
# (Ubuntu 24.04.4 LTS x86_64, Python 3.13.3): it was declared in NO extra, but
|
|
64
|
+
# happened to be installed ad hoc, so the two REAL-MinIO tests in
|
|
65
|
+
# tests/test_realtime_files.py were passing. Running `uv sync --extra test`
|
|
66
|
+
# pruned boto3 (plus s3transfer and jmespath) and those tests went straight from
|
|
67
|
+
# "2 passed" to "2 skipped" -- with the suite still GREEN under
|
|
68
|
+
# TINA4_REQUIRE_SERVICES=1, because the skip reason matched neither a service
|
|
69
|
+
# keyword nor an unavailable hint in the conftest gate. Live S3 coverage
|
|
70
|
+
# evaporated silently. Declaring it here is the fix; tests/conftest.py now also
|
|
71
|
+
# recognises a missing boto3 so the gate can never miss it again.
|
|
72
|
+
test = [
|
|
73
|
+
"psycopg2-binary>=2.9",
|
|
74
|
+
"mysql-connector-python>=9.0",
|
|
75
|
+
"pymssql>=2.3",
|
|
76
|
+
"firebird-driver>=1.10",
|
|
77
|
+
"pymongo>=4.0",
|
|
78
|
+
"pyodbc>=5.0",
|
|
79
|
+
"redis>=5.0",
|
|
80
|
+
"pymemcache>=4.0",
|
|
81
|
+
"confluent-kafka>=2.0",
|
|
82
|
+
"pika>=1.3",
|
|
83
|
+
"boto3>=1.34",
|
|
84
|
+
# TEST-ONLY: validates the generated OpenAPI document against the real
|
|
85
|
+
# OpenAPI 3.0/3.1 schema (tests/test_swagger_contract.py). NEVER a framework
|
|
86
|
+
# runtime dependency — the swagger generator itself stays zero-dependency.
|
|
87
|
+
"openapi-spec-validator>=0.7",
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
[dependency-groups]
|
|
91
|
+
dev = [
|
|
92
|
+
"bottle>=0.13.4",
|
|
93
|
+
"cython>=3.2.4",
|
|
94
|
+
"django>=6.0.3",
|
|
95
|
+
"fastapi>=0.135.1",
|
|
96
|
+
"flask>=3.1.3",
|
|
97
|
+
"pytest>=8.0",
|
|
98
|
+
"pytest-asyncio>=0.23",
|
|
99
|
+
"pytest-cov>=5.0",
|
|
100
|
+
"reportlab>=4.4.10",
|
|
101
|
+
"ruff>=0.4",
|
|
102
|
+
"setuptools>=82.0.1",
|
|
103
|
+
"uvicorn>=0.42.0",
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
[build-system]
|
|
107
|
+
requires = ["hatchling"]
|
|
108
|
+
build-backend = "hatchling.build"
|
|
109
|
+
|
|
110
|
+
[tool.hatch.build]
|
|
111
|
+
include = ["tina4_python/**/*"]
|
|
112
|
+
|
|
113
|
+
[project.scripts]
|
|
114
|
+
tina4python = "tina4_python.cli:main"
|
|
115
|
+
|
|
116
|
+
[tool.pytest.ini_options]
|
|
117
|
+
asyncio_mode = "auto"
|
|
118
|
+
testpaths = ["tests"]
|
|
119
|
+
markers = [
|
|
120
|
+
"slow: a test that needs real wall-clock time to reproduce a timing bug (deselect with -m 'not slow')",
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
[tool.ruff]
|
|
124
|
+
target-version = "py312"
|
|
125
|
+
line-length = 120
|
|
@@ -514,8 +514,11 @@ TINA4_TOKEN_LIMIT is used to set the session time, default 60 minutes
|
|
|
514
514
|
|
|
515
515
|
Set `TINA4_SESSION_BACKEND` to choose a backend:
|
|
516
516
|
|
|
517
|
-
The value is the backend NAME, not a class name
|
|
518
|
-
|
|
517
|
+
The value is the backend NAME, not a class name, and it is normalised (trimmed and
|
|
518
|
+
lowercased), so " Redis " resolves. An UNRECOGNISED value raises `ValueError` at
|
|
519
|
+
startup, naming the bad value and the valid ones -- it used to fall back to the
|
|
520
|
+
file backend silently, which turned a typo into sessions quietly written to local
|
|
521
|
+
disk. Unset or blank still means file.
|
|
519
522
|
|
|
520
523
|
| Value | Backend | Required package |
|
|
521
524
|
|---------|---------|-----------------|
|
|
@@ -818,7 +821,7 @@ row = db.fetch_one("SELECT * FROM users WHERE id = ?", [1])
|
|
|
818
821
|
# Result methods
|
|
819
822
|
result.to_json() # JSON string
|
|
820
823
|
result.to_array() # List of dicts
|
|
821
|
-
result.to_paginate() # Dict
|
|
824
|
+
result.to_paginate() # Dict: records, total, page, per_page, total_pages, limit, offset (takes no args; ADR-0043)
|
|
822
825
|
result.to_csv() # CSV string
|
|
823
826
|
|
|
824
827
|
# Transactions
|
|
@@ -1641,17 +1644,23 @@ TINA4_TOKEN_LIMIT=60 # Token lifetime in minutes (default: 60)
|
|
|
1641
1644
|
TINA4_DATABASE_URL=sqlite:///app.db # Connection URL (driver://host:port/database)
|
|
1642
1645
|
TINA4_DATABASE_USERNAME= # DB username (for PostgreSQL, MySQL, etc.)
|
|
1643
1646
|
TINA4_DATABASE_PASSWORD= # DB password
|
|
1647
|
+
TINA4_DATABASE_CONNECT_TIMEOUT=10 # Seconds a database connect may block (default 10). Set <= 0 to wait
|
|
1648
|
+
# indefinitely (the old behaviour); a non-numeric value warns and uses 10.
|
|
1649
|
+
# On expiry the error names the host, port, elapsed seconds and this variable.
|
|
1650
|
+
# Applies to postgres/mysql/mssql/odbc/firebird, and to mongodb's deferred
|
|
1651
|
+
# connect. SQLite is exempt: it opens a local file, there is no peer to hang on
|
|
1644
1652
|
|
|
1645
1653
|
# Framework
|
|
1646
1654
|
TINA4_DEBUG=true # Enable dev mode (toolbar, live reload, error overlay)
|
|
1647
1655
|
TINA4_LOG_LEVEL=INFO # Log verbosity: ALL, DEBUG, INFO, WARNING, ERROR, CRITICAL (default: INFO)
|
|
1648
1656
|
TINA4_LOCALE=en # Language for framework messages (en, fr, af, zh, ja, es)
|
|
1649
|
-
TINA4_DEFAULT_WEBSERVER=FALSE #
|
|
1657
|
+
TINA4_DEFAULT_WEBSERVER=FALSE # TRUE pins Tina4's built-in asyncio webserver even when a production ASGI server (uvicorn/hypercorn/granian) is installed. Unset/FALSE: a production server is used when one is importable and TINA4_DEBUG is off
|
|
1658
|
+
TINA4_SHUTDOWN_TIMEOUT=30 # Seconds to drain in-flight requests on SIGTERM/SIGINT (default 30). Honoured on the built-in server, and mapped onto uvicorn's timeout_graceful_shutdown / hypercorn's graceful_timeout. granian has no drain deadline, so it is not honoured there (logged at startup). A non-positive or unparseable value warns and uses 30
|
|
1650
1659
|
TINA4_OVERRIDE_CLIENT=false # Set to true to allow running without tina4 CLI (e.g. Docker)
|
|
1651
1660
|
HOST_NAME=localhost:7145
|
|
1652
1661
|
|
|
1653
1662
|
# Sessions
|
|
1654
|
-
TINA4_SESSION_BACKEND=file # file, redis, valkey, mongodb, database (unknown value
|
|
1663
|
+
TINA4_SESSION_BACKEND=file # file, redis, valkey, mongodb, memcached, database (an unknown value RAISES; unset/blank = file)
|
|
1655
1664
|
TINA4_SESSION_SAMESITE=Lax # SameSite attribute for session cookies (default: Lax)
|
|
1656
1665
|
|
|
1657
1666
|
# Swagger/OpenAPI
|
|
@@ -1713,7 +1722,7 @@ anywhere (the production on/off switch, wired for real in 3.13.40).
|
|
|
1713
1722
|
|
|
1714
1723
|
## CORS
|
|
1715
1724
|
|
|
1716
|
-
Built-in
|
|
1725
|
+
Built-in, and it DENIES by default (ADR-0018). Set `TINA4_CORS_ORIGINS` to the origins you want to allow (comma-separated), or `*` for any. With it unset no `Access-Control-Allow-Origin` is sent and the browser blocks the cross-origin request. `TINA4_CORS_CREDENTIALS=true` is never sent alongside a `*` origin (the Fetch Standard forbids the pair). OPTIONS pre-flight is handled automatically.
|
|
1717
1726
|
|
|
1718
1727
|
## SCSS Workflow
|
|
1719
1728
|
|
|
@@ -51,7 +51,7 @@ def _resolve_version() -> str:
|
|
|
51
51
|
#
|
|
52
52
|
# test_version_constant.py now asserts this literal equals the pyproject
|
|
53
53
|
# version, so the release bump cannot leave it behind again.
|
|
54
|
-
return "3.13.
|
|
54
|
+
return "3.13.95"
|
|
55
55
|
|
|
56
56
|
|
|
57
57
|
__version__ = _resolve_version()
|
|
@@ -86,6 +86,7 @@ from tina4_python.core.constants import ( # noqa: E402, F401
|
|
|
86
86
|
)
|
|
87
87
|
from tina4_python.core.server import ( # noqa: E402, F401
|
|
88
88
|
run, background, background_task_count, stop_all_background_tasks,
|
|
89
|
+
asgi,
|
|
89
90
|
)
|
|
90
91
|
from tina4_python.core.events import on, emit, once, off # noqa: E402, F401
|
|
91
92
|
from tina4_python.env import Env # noqa: E402, F401
|
|
@@ -203,13 +203,16 @@ class Api:
|
|
|
203
203
|
"""HTTP DELETE request."""
|
|
204
204
|
return self._request("DELETE", self._url(path), body)
|
|
205
205
|
|
|
206
|
-
def
|
|
207
|
-
|
|
208
|
-
"""Generic request method — pick HTTP verb at call time.
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
206
|
+
def send_request(self, method: str, path: str = "", body=None,
|
|
207
|
+
content_type: str = "application/json") -> dict:
|
|
208
|
+
"""Generic request method — pick the HTTP verb at call time.
|
|
209
|
+
|
|
210
|
+
``send_request`` is the name all four frameworks share (PHP
|
|
211
|
+
``sendRequest``, Ruby ``send_request``, Node ``sendRequest``). The
|
|
212
|
+
3.13.0 Python-only rename to ``send`` was reverted: ``send`` can never
|
|
213
|
+
be the shared name because Ruby's ``send`` is ``Object#send``, the
|
|
214
|
+
metaprogramming primitive, so a Ruby class cannot expose an HTTP
|
|
215
|
+
``send`` without shadowing it. One concept, one name, everywhere.
|
|
213
216
|
"""
|
|
214
217
|
return self._request(method.upper(), self._url(path), body, content_type)
|
|
215
218
|
|