tina4-python 3.13.92__tar.gz → 3.13.95__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.92 → tina4_python-3.13.95}/.gitignore +12 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/PKG-INFO +21 -3
- {tina4_python-3.13.92 → tina4_python-3.13.95}/README.md +2 -2
- tina4_python-3.13.95/pyproject.toml +121 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/CLAUDE.md +23 -7
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/__init__.py +9 -1
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/auth/__init__.py +239 -35
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/cache/__init__.py +630 -163
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/cli/__init__.py +5 -1
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/core/middleware.py +269 -31
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/core/rate_limiter.py +28 -2
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/core/request.py +108 -6
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/core/response.py +92 -3
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/core/router.py +55 -11
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/core/server.py +858 -293
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/database/adapter.py +404 -135
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/database/connection.py +288 -26
- tina4_python-3.13.95/tina4_python/database/database_url.py +365 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/database/firebird.py +163 -48
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/database/mongodb.py +15 -3
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/database/mssql.py +58 -49
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/database/mysql.py +47 -49
- tina4_python-3.13.95/tina4_python/database/odbc.py +224 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/database/postgres.py +39 -48
- tina4_python-3.13.95/tina4_python/database/sql_translator.py +272 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/database/sqlite.py +19 -38
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/debug/__init__.py +262 -45
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/dev_admin/__init__.py +96 -49
- tina4_python-3.13.95/tina4_python/dev_admin/metrics.py +375 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/docstore/__init__.py +240 -49
- tina4_python-3.13.95/tina4_python/dotenv/__init__.py +288 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/frond/engine.py +68 -8
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/mcp/tools.py +12 -2
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/messenger/__init__.py +89 -27
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/mqtt/__init__.py +5 -1
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/orm/model.py +91 -18
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/queue/__init__.py +35 -28
- tina4_python-3.13.95/tina4_python/queue/amqp_url.py +73 -0
- tina4_python-3.13.95/tina4_python/queue/kafka_backend.py +210 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/queue/lite_backend.py +23 -1
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/queue/mongo_backend.py +65 -6
- tina4_python-3.13.95/tina4_python/queue/rabbitmq_backend.py +221 -0
- tina4_python-3.13.95/tina4_python/queue_backends/kafka_backend.py +603 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/queue_backends/mongo_backend.py +20 -7
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/queue_backends/rabbitmq_backend.py +31 -17
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/session/__init__.py +190 -16
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/session_handlers/__init__.py +8 -2
- tina4_python-3.13.95/tina4_python/session_handlers/memcached_handler.py +155 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/session_handlers/mongodb_handler.py +81 -29
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/session_handlers/redis_handler.py +4 -1
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/session_handlers/valkey_handler.py +4 -1
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/websocket/__init__.py +9 -3
- tina4_python-3.13.92/pyproject.toml +0 -73
- tina4_python-3.13.92/tina4_python/database/odbc.py +0 -177
- tina4_python-3.13.92/tina4_python/dev_admin/metrics.py +0 -867
- tina4_python-3.13.92/tina4_python/dev_admin/metrics_engine.py +0 -108
- tina4_python-3.13.92/tina4_python/dotenv/__init__.py +0 -163
- tina4_python-3.13.92/tina4_python/queue/kafka_backend.py +0 -151
- tina4_python-3.13.92/tina4_python/queue/rabbitmq_backend.py +0 -186
- tina4_python-3.13.92/tina4_python/queue_backends/kafka_backend.py +0 -414
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/Testing.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/ai/__init__.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/api/__init__.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/container/__init__.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/context/__init__.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/context/chunker.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/core/__init__.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/core/cache.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/core/constants.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/core/events.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/crud/__init__.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/database/__init__.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/debug/error_overlay.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/dev_admin/plan.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/dev_admin/project_index.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/docs.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/env.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/frond/FROND.md +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/frond/__init__.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/frond/compiler.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/frond/parser.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/gallery/auth/meta.json +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/gallery/database/meta.json +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/gallery/error-overlay/meta.json +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/gallery/orm/meta.json +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/gallery/queue/meta.json +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/gallery/rest-api/meta.json +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/gallery/templates/meta.json +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/graphql/__init__.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/i18n/__init__.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/mcp/__init__.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/mcp/protocol.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/migration/__init__.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/migration/runner.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/mqtt/message.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/orm/__init__.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/orm/fields.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/public/__feedback/widget.js +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/public/css/tina4.css +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/public/css/tina4.min.css +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/public/images/logo.svg +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/public/js/frond.js +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/public/js/frond.min.js +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/public/js/tina4.min.js +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/public/js/tina4js.min.js +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/query_builder/__init__.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/queue/job.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/queue_backends/__init__.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/realtime/__init__.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/realtime/models/Attachment.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/realtime/models/Channel.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/realtime/models/ChannelMember.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/realtime/models/Message.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/realtime/models/Workspace.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/realtime/models/__init__.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/realtime/storage.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/scss/__init__.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/scss/tina4css/_alerts.scss +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/scss/tina4css/_badges.scss +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/scss/tina4css/_buttons.scss +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/scss/tina4css/_cards.scss +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/scss/tina4css/_forms.scss +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/scss/tina4css/_grid.scss +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/scss/tina4css/_modals.scss +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/scss/tina4css/_nav.scss +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/scss/tina4css/_pagination.scss +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/scss/tina4css/_reset.scss +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/scss/tina4css/_tables.scss +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/scss/tina4css/_typography.scss +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/scss/tina4css/_utilities.scss +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/scss/tina4css/_variables.scss +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/scss/tina4css/base.scss +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/scss/tina4css/colors.scss +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/scss/tina4css/tina4.scss +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/seeder/__init__.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/service/__init__.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/swagger/__init__.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/templates/docker/python/Dockerfile +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/templates/docker/uv/Dockerfile +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/templates/errors/302.twig +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/templates/errors/401.twig +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/templates/errors/502.twig +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/templates/errors/503.twig +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/templates/errors/base.twig +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/templates/frontend/README.md +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/templates/readme.md +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/test/__init__.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/test_client/__init__.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/validator/__init__.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/tina4_python/websocket/backplane.py +0 -0
- {tina4_python-3.13.92 → tina4_python-3.13.95}/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.95
|
|
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,24 @@ 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: pika>=1.3; extra == 'test'
|
|
43
|
+
Requires-Dist: psycopg2-binary>=2.9; extra == 'test'
|
|
44
|
+
Requires-Dist: pymemcache>=4.0; extra == 'test'
|
|
45
|
+
Requires-Dist: pymongo>=4.0; extra == 'test'
|
|
46
|
+
Requires-Dist: pymssql>=2.3; extra == 'test'
|
|
47
|
+
Requires-Dist: pyodbc>=5.0; extra == 'test'
|
|
48
|
+
Requires-Dist: redis>=5.0; extra == 'test'
|
|
31
49
|
Description-Content-Type: text/markdown
|
|
32
50
|
|
|
33
51
|
<p align="center">
|
|
@@ -116,7 +134,7 @@ Every feature is built from scratch -- no pip install, no node_modules, no third
|
|
|
116
134
|
| **Core HTTP** (7) | Router with path params (`{id:int}`, `{p:path}`), Server, Request/Response, Middleware pipeline, Static file serving, CORS |
|
|
117
135
|
| **Database** (6) | SQLite, PostgreSQL, MySQL, MSSQL, Firebird: unified adapter, connection pooling, query cache, transactions, race-safe ID generation, SQL dialect translation |
|
|
118
136
|
| **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 |
|
|
137
|
+
| **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
138
|
| **Templating** (3) | Frond engine (Twig/Jinja2-compatible, pre-compiled 2.8x faster), SCSS auto-compilation, built-in CSS (~24 KB) |
|
|
121
139
|
| **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
140
|
| **Background** (3) | Job queue (File/RabbitMQ/Kafka/MongoDB) with priority, delay, retry, dead letters; service runner; event system (on/emit/once/off) |
|
|
@@ -742,7 +760,7 @@ Run benchmarks locally: `python benchmarks/benchmark.py --python`
|
|
|
742
760
|
|
|
743
761
|
## Cross-Framework Parity
|
|
744
762
|
|
|
745
|
-
Tina4 ships identical features across four languages: same architecture, same conventions, same
|
|
763
|
+
Tina4 ships identical features across four languages: same architecture, same conventions, same 98 features:
|
|
746
764
|
|
|
747
765
|
| | Python | PHP | Ruby | Node.js |
|
|
748
766
|
|---|--------|-----|------|---------|
|
|
@@ -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) |
|
|
@@ -710,7 +710,7 @@ Run benchmarks locally: `python benchmarks/benchmark.py --python`
|
|
|
710
710
|
|
|
711
711
|
## Cross-Framework Parity
|
|
712
712
|
|
|
713
|
-
Tina4 ships identical features across four languages: same architecture, same conventions, same
|
|
713
|
+
Tina4 ships identical features across four languages: same architecture, same conventions, same 98 features:
|
|
714
714
|
|
|
715
715
|
| | Python | PHP | Ruby | Node.js |
|
|
716
716
|
|---|--------|-----|------|---------|
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "tina4-python"
|
|
3
|
+
version = "3.13.95"
|
|
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
|
+
]
|
|
85
|
+
|
|
86
|
+
[dependency-groups]
|
|
87
|
+
dev = [
|
|
88
|
+
"bottle>=0.13.4",
|
|
89
|
+
"cython>=3.2.4",
|
|
90
|
+
"django>=6.0.3",
|
|
91
|
+
"fastapi>=0.135.1",
|
|
92
|
+
"flask>=3.1.3",
|
|
93
|
+
"pytest>=8.0",
|
|
94
|
+
"pytest-asyncio>=0.23",
|
|
95
|
+
"pytest-cov>=5.0",
|
|
96
|
+
"reportlab>=4.4.10",
|
|
97
|
+
"ruff>=0.4",
|
|
98
|
+
"setuptools>=82.0.1",
|
|
99
|
+
"uvicorn>=0.42.0",
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
[build-system]
|
|
103
|
+
requires = ["hatchling"]
|
|
104
|
+
build-backend = "hatchling.build"
|
|
105
|
+
|
|
106
|
+
[tool.hatch.build]
|
|
107
|
+
include = ["tina4_python/**/*"]
|
|
108
|
+
|
|
109
|
+
[project.scripts]
|
|
110
|
+
tina4python = "tina4_python.cli:main"
|
|
111
|
+
|
|
112
|
+
[tool.pytest.ini_options]
|
|
113
|
+
asyncio_mode = "auto"
|
|
114
|
+
testpaths = ["tests"]
|
|
115
|
+
markers = [
|
|
116
|
+
"slow: a test that needs real wall-clock time to reproduce a timing bug (deselect with -m 'not slow')",
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
[tool.ruff]
|
|
120
|
+
target-version = "py312"
|
|
121
|
+
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
|
|---------|---------|-----------------|
|
|
@@ -794,11 +797,18 @@ db.insert("users", {"name": "Alice", "email": "alice@example.com"})
|
|
|
794
797
|
# Insert multiple
|
|
795
798
|
db.insert("users", [{"name": "Bob"}, {"name": "Eve"}])
|
|
796
799
|
|
|
797
|
-
# Update (
|
|
800
|
+
# Update (the primary key in the data IS the filter — introspected, not assumed)
|
|
798
801
|
db.update("users", {"id": 1, "name": "Alice Updated"})
|
|
799
802
|
|
|
800
|
-
#
|
|
803
|
+
# Update many rows — the filter must be explicit
|
|
804
|
+
db.update("users", {"active": 0}, "last_login < ?", ["2025-01-01"])
|
|
805
|
+
|
|
806
|
+
# Delete (dict filter or string + params, both accepted)
|
|
801
807
|
db.delete("users", {"id": 1})
|
|
808
|
+
db.delete("users", "age < ?", [18])
|
|
809
|
+
|
|
810
|
+
# Empty a table — explicit, never accidental
|
|
811
|
+
db.truncate("users")
|
|
802
812
|
|
|
803
813
|
# Raw query
|
|
804
814
|
result = db.fetch("SELECT * FROM users WHERE age > ?", [18])
|
|
@@ -1634,17 +1644,23 @@ TINA4_TOKEN_LIMIT=60 # Token lifetime in minutes (default: 60)
|
|
|
1634
1644
|
TINA4_DATABASE_URL=sqlite:///app.db # Connection URL (driver://host:port/database)
|
|
1635
1645
|
TINA4_DATABASE_USERNAME= # DB username (for PostgreSQL, MySQL, etc.)
|
|
1636
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
|
|
1637
1652
|
|
|
1638
1653
|
# Framework
|
|
1639
1654
|
TINA4_DEBUG=true # Enable dev mode (toolbar, live reload, error overlay)
|
|
1640
1655
|
TINA4_LOG_LEVEL=INFO # Log verbosity: ALL, DEBUG, INFO, WARNING, ERROR, CRITICAL (default: INFO)
|
|
1641
1656
|
TINA4_LOCALE=en # Language for framework messages (en, fr, af, zh, ja, es)
|
|
1642
|
-
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
|
|
1643
1659
|
TINA4_OVERRIDE_CLIENT=false # Set to true to allow running without tina4 CLI (e.g. Docker)
|
|
1644
1660
|
HOST_NAME=localhost:7145
|
|
1645
1661
|
|
|
1646
1662
|
# Sessions
|
|
1647
|
-
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)
|
|
1648
1664
|
TINA4_SESSION_SAMESITE=Lax # SameSite attribute for session cookies (default: Lax)
|
|
1649
1665
|
|
|
1650
1666
|
# Swagger/OpenAPI
|
|
@@ -1706,7 +1722,7 @@ anywhere (the production on/off switch, wired for real in 3.13.40).
|
|
|
1706
1722
|
|
|
1707
1723
|
## CORS
|
|
1708
1724
|
|
|
1709
|
-
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.
|
|
1710
1726
|
|
|
1711
1727
|
## SCSS Workflow
|
|
1712
1728
|
|
|
@@ -43,7 +43,15 @@ def _resolve_version() -> str:
|
|
|
43
43
|
pass
|
|
44
44
|
except Exception:
|
|
45
45
|
pass
|
|
46
|
-
|
|
46
|
+
# Floor literal, reached only when BOTH lookups above miss. That is not
|
|
47
|
+
# theoretical: the Docker build used to prune dist-info from the installed
|
|
48
|
+
# package, so path 2 raised PackageNotFoundError and the published
|
|
49
|
+
# tina4-python:3.13.94 image served this literal on /health -- then reading
|
|
50
|
+
# "3.13.56", 36 releases stale, because nothing kept it current.
|
|
51
|
+
#
|
|
52
|
+
# test_version_constant.py now asserts this literal equals the pyproject
|
|
53
|
+
# version, so the release bump cannot leave it behind again.
|
|
54
|
+
return "3.13.95"
|
|
47
55
|
|
|
48
56
|
|
|
49
57
|
__version__ = _resolve_version()
|