tina4-python 3.13.139__tar.gz → 3.13.140__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.139 → tina4_python-3.13.140}/PKG-INFO +1 -1
- {tina4_python-3.13.139 → tina4_python-3.13.140}/pyproject.toml +1 -1
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/CLAUDE.md +28 -14
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/__init__.py +1 -1
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/cli/__init__.py +8 -3
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/database/connection.py +53 -23
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/database/pool.py +8 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/.gitignore +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/COMMERCIAL-LICENSE.md +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/LICENSE +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/NOTICE +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/README.md +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/.tina4-metrics.json +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/Testing.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/_import_helper.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/ai/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/ai/client.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/api/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/auth/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/cache/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/container/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/context/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/context/chunker.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/core/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/core/cache.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/core/constants.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/core/events.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/core/middleware.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/core/port_takeover.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/core/rate_limiter.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/core/request.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/core/response.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/core/router.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/core/server.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/crud/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/database/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/database/adapter.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/database/async_api.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/database/database_url.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/database/firebird.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/database/mongodb.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/database/mssql.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/database/mysql.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/database/odbc.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/database/postgres.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/database/sql_translator.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/database/sqlite.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/debug/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/debug/error_overlay.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/dev_admin/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/dev_admin/metrics.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/dev_admin/plan.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/dev_admin/project_index.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/docs.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/docstore/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/dotenv/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/env.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/frond/FROND.md +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/frond/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/frond/compiler.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/frond/engine.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/frond/parser.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/auth/meta.json +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/database/meta.json +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/error-overlay/meta.json +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/orm/meta.json +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/queue/meta.json +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/rest-api/meta.json +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/templates/meta.json +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/graph/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/graph/adapter.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/graph/adapters/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/graph/adapters/arango.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/graph/adapters/bolt.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/graph/adapters/ultipa.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/graph/graph_url.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/graphql/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/i18n/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/mcp/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/mcp/protocol.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/mcp/tools.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/messenger/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/migration/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/migration/runner.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/mqtt/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/mqtt/message.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/orm/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/orm/async_api.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/orm/collection.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/orm/fields.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/orm/model.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/orm/point.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/THIRD-PARTY-NOTICES.md +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/__feedback/widget.js +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/bundled-component-provenance.json +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/css/tina4.css +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/css/tina4.min.css +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/images/logo.svg +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/js/frond.js +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/js/frond.min.js +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/js/tina4.min.js +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/js/tina4js.min.js +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/licenses/swagger-ui-3.10.0-original-notice.txt +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/licenses/swagger-ui-5.17.14-NOTICE.txt +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/licenses/swagger-ui-Apache-2.0.txt +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/licenses/tina4-css-MIT.txt +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/licenses/tina4-dev-admin-components.json +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/licenses/tina4-dev-admin-third-party.txt +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/licenses/tina4-js-MIT.txt +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/push/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/push/_base.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/push/_cryptography.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/push/_libcrypto.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/query_builder/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/queue/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/queue/amqp_url.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/queue/job.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/queue/kafka_backend.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/queue/lite_backend.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/queue/mongo_backend.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/queue/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/queue_backends/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/queue_backends/kafka_backend.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/queue_backends/mongo_backend.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/realtime/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/realtime/models/Attachment.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/realtime/models/Channel.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/realtime/models/ChannelMember.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/realtime/models/Message.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/realtime/models/Workspace.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/realtime/models/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/realtime/storage.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/seeder/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/service/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/session/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/session_handlers/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/session_handlers/memcached_handler.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/session_handlers/mongodb_handler.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/session_handlers/redis_handler.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/session_handlers/valkey_handler.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/sso.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/ssrf.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/swagger/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/docker/python/Dockerfile +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/docker/uv/Dockerfile +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/errors/302.twig +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/errors/401.twig +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/errors/502.twig +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/errors/503.twig +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/errors/base.twig +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/frontend/README.md +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/readme.md +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/test/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/test_client/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/validator/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/websocket/__init__.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/websocket/backplane.py +0 -0
- {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/wsdl/__init__.py +0 -0
|
@@ -1752,25 +1752,39 @@ uv run tina4python start 8080 # Start on custom port
|
|
|
1752
1752
|
|
|
1753
1753
|
### CRUD Generator
|
|
1754
1754
|
|
|
1755
|
-
Generate a complete CRUD interface (list, create, update, delete) for any
|
|
1755
|
+
Generate a complete REST CRUD interface (list, get, create, update, delete) for any ORM model.
|
|
1756
|
+
Flag the model with `auto_crud = True` and the routes register themselves at import time:
|
|
1757
|
+
|
|
1758
|
+
```python
|
|
1759
|
+
from tina4_python.orm import ORM, IntegerField, StringField
|
|
1760
|
+
|
|
1761
|
+
class User(ORM):
|
|
1762
|
+
auto_crud = True # auto-registers CRUD routes for this model
|
|
1763
|
+
id = IntegerField(primary_key=True, auto_increment=True)
|
|
1764
|
+
name = StringField()
|
|
1765
|
+
email = StringField()
|
|
1766
|
+
```
|
|
1767
|
+
|
|
1768
|
+
Or register models explicitly (or discover a whole directory):
|
|
1756
1769
|
|
|
1757
1770
|
```python
|
|
1758
1771
|
from tina4_python.crud import AutoCrud
|
|
1772
|
+
from src.orm.user import User
|
|
1759
1773
|
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
"sql": "SELECT id, name, email FROM users",
|
|
1764
|
-
"title": "User Management",
|
|
1765
|
-
"primary_key": "id",
|
|
1766
|
-
}))
|
|
1774
|
+
AutoCrud.register(User) # routes under /api/user
|
|
1775
|
+
AutoCrud.register(User, prefix="/api/v2") # or a custom prefix
|
|
1776
|
+
AutoCrud.discover("src/orm", prefix="/api") # every ORM model in a directory
|
|
1767
1777
|
```
|
|
1768
1778
|
|
|
1769
|
-
This auto-generates:
|
|
1770
|
-
-
|
|
1771
|
-
-
|
|
1772
|
-
-
|
|
1773
|
-
-
|
|
1779
|
+
This auto-generates per model:
|
|
1780
|
+
- `GET /api/{table}` — paginated list (returns the `to_paginate()` envelope)
|
|
1781
|
+
- `GET /api/{table}/{id}` — one record by primary key
|
|
1782
|
+
- `POST /api/{table}` — create
|
|
1783
|
+
- `PUT /api/{table}/{id}` — update
|
|
1784
|
+
- `DELETE /api/{table}/{id}` — delete
|
|
1785
|
+
|
|
1786
|
+
Write routes are secure by default (they require a valid token); pass `public=True`
|
|
1787
|
+
to `register` / `discover` to open them.
|
|
1774
1788
|
|
|
1775
1789
|
### ORM Table Creation
|
|
1776
1790
|
|
|
@@ -1799,7 +1813,7 @@ uv run tina4python migrate
|
|
|
1799
1813
|
|
|
1800
1814
|
| Need | Use |
|
|
1801
1815
|
|------|-----|
|
|
1802
|
-
|
|
|
1816
|
+
| Auto REST CRUD for a model | `auto_crud = True` / `AutoCrud.register()` |
|
|
1803
1817
|
| Schema-first database design | Migration files |
|
|
1804
1818
|
| Code-first database design | `ORM.create_table()` |
|
|
1805
1819
|
| New project from scratch | `tina4python init` |
|
|
@@ -1970,7 +1970,12 @@ def _gen_crud(name: str, flags: dict):
|
|
|
1970
1970
|
"""
|
|
1971
1971
|
fields = _fields_or_default(flags.get("fields", ""))
|
|
1972
1972
|
table = _resolve_table(name, flags)
|
|
1973
|
-
|
|
1973
|
+
# Routes are the SINGLE plural of the singular base — pluralise(snake(name)) —
|
|
1974
|
+
# never `table + "s"`. When the table was pluralised to escape a SQL reserved
|
|
1975
|
+
# word (Order -> table `orders`), `table + "s"` double-pluralised the route,
|
|
1976
|
+
# file and template to `orderss`. Deriving from the singular base keeps one
|
|
1977
|
+
# consistent rule: Order -> orders, Product -> products.
|
|
1978
|
+
route_name = _pluralize_table(_to_snake(name))
|
|
1974
1979
|
|
|
1975
1980
|
print(f"\n Generating CRUD for {name}...\n")
|
|
1976
1981
|
|
|
@@ -3244,7 +3249,7 @@ def _gen_form(name: str, flags: dict = None):
|
|
|
3244
3249
|
flags = flags or {}
|
|
3245
3250
|
fields = _fields_or_default(flags.get("fields", ""))
|
|
3246
3251
|
table = _resolve_table(name, flags)
|
|
3247
|
-
route_name = table + "s"
|
|
3252
|
+
route_name = _pluralize_table(_to_snake(name)) # single plural of the singular base (never table + "s")
|
|
3248
3253
|
|
|
3249
3254
|
# Input type mapping
|
|
3250
3255
|
input_types = {
|
|
@@ -3328,7 +3333,7 @@ def _gen_view(name: str, flags: dict = None):
|
|
|
3328
3333
|
flags = flags or {}
|
|
3329
3334
|
fields = _fields_or_default(flags.get("fields", ""))
|
|
3330
3335
|
table = _resolve_table(name, flags)
|
|
3331
|
-
route_name = table + "s"
|
|
3336
|
+
route_name = _pluralize_table(_to_snake(name)) # single plural of the singular base (never table + "s")
|
|
3332
3337
|
|
|
3333
3338
|
target = Path("src/templates/pages")
|
|
3334
3339
|
target.mkdir(parents=True, exist_ok=True)
|
|
@@ -66,17 +66,36 @@ current_route: contextvars.ContextVar = contextvars.ContextVar("tina4_current_ro
|
|
|
66
66
|
_warned_routes: set = set()
|
|
67
67
|
|
|
68
68
|
|
|
69
|
+
def _running_event_loop():
|
|
70
|
+
"""The event loop running on this thread, or ``None`` when there is none.
|
|
71
|
+
|
|
72
|
+
A supported replacement for the private ``asyncio._get_running_loop()``:
|
|
73
|
+
``asyncio.get_running_loop()`` answers on a running loop and raises
|
|
74
|
+
``RuntimeError`` off one (ADR-0074). It runs only after the TINA4_DEBUG
|
|
75
|
+
gate below, so its off-loop ``RuntimeError`` never fires on the hot sync
|
|
76
|
+
path in production.
|
|
77
|
+
"""
|
|
78
|
+
try:
|
|
79
|
+
return asyncio.get_running_loop()
|
|
80
|
+
except RuntimeError:
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
|
|
69
84
|
def _warn_if_blocking_the_event_loop(operation: str) -> None:
|
|
70
85
|
"""Debug-mode warning: a sync DB call on a thread that is running an event
|
|
71
86
|
loop freezes every other request on that loop until the statement returns.
|
|
72
87
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
88
|
+
Gated on TINA4_DEBUG first, so production pays nothing; only then does it
|
|
89
|
+
look for a running loop. Callers invoke it unconditionally - the loop check
|
|
90
|
+
lives here, not at every call site, so the supported (raising)
|
|
91
|
+
``asyncio.get_running_loop()`` never touches the hot sync path outside
|
|
92
|
+
debug mode (ADR-0074).
|
|
76
93
|
"""
|
|
77
94
|
from tina4_python.dotenv import is_truthy
|
|
78
95
|
if not is_truthy(os.environ.get("TINA4_DEBUG", "")):
|
|
79
96
|
return
|
|
97
|
+
if _running_event_loop() is None:
|
|
98
|
+
return
|
|
80
99
|
route = current_route.get() or "(no route - a coroutine outside a request)"
|
|
81
100
|
if route in _warned_routes:
|
|
82
101
|
return
|
|
@@ -430,11 +449,13 @@ class Database(DatabaseAsyncMixin):
|
|
|
430
449
|
sqlite:////absolute/path.db → /absolute/path.db (absolute)
|
|
431
450
|
sqlite:///C:/Users/app.db → C:/Users/app.db (Windows absolute)
|
|
432
451
|
|
|
433
|
-
Directories are auto-created
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
452
|
+
Directories are auto-created (mode ``0775``, masked by the umask)
|
|
453
|
+
ONLY when the resolved parent stays inside the current working
|
|
454
|
+
directory (the project root). An absolute path is trusted and
|
|
455
|
+
never auto-created. A RELATIVE path whose parent resolves OUTSIDE
|
|
456
|
+
cwd (``../../etc/foo.db``) is REFUSED with a ``ValueError`` — Tina4
|
|
457
|
+
never ``os.makedirs`` outside the project, which is both hostile on
|
|
458
|
+
read-only filesystems and a latent footgun (ADR-0086).
|
|
438
459
|
"""
|
|
439
460
|
parsed = urlparse(self.url)
|
|
440
461
|
|
|
@@ -500,10 +521,25 @@ class Database(DatabaseAsyncMixin):
|
|
|
500
521
|
# Relative — resolve under the project root (cwd).
|
|
501
522
|
cwd = os.getcwd()
|
|
502
523
|
path = os.path.join(cwd, stripped)
|
|
503
|
-
#
|
|
524
|
+
# Create the parent, but ONLY when it stays inside cwd (ADR-0086).
|
|
525
|
+
# A relative path whose parent resolves OUTSIDE cwd (e.g.
|
|
526
|
+
# "../../etc/foo.db") is REFUSED loudly here — never a silent mkdir
|
|
527
|
+
# outside the project (the PHP/Ruby footgun) and never a path that
|
|
528
|
+
# only fails cryptically when the driver opens it (the old Python
|
|
529
|
+
# silent-skip). Absolute paths are trusted and never reach this
|
|
530
|
+
# branch. The created directory is mode 0775 (masked by the umask),
|
|
531
|
+
# matching every framework.
|
|
504
532
|
directory = os.path.dirname(path)
|
|
505
|
-
if directory
|
|
506
|
-
os.
|
|
533
|
+
if directory:
|
|
534
|
+
if os.path.commonpath([os.path.abspath(directory), cwd]) != cwd:
|
|
535
|
+
raise ValueError(
|
|
536
|
+
f"SQLite path {self.url!r} resolves outside the project "
|
|
537
|
+
f"directory: {os.path.abspath(directory)!r} is not within "
|
|
538
|
+
f"{cwd!r}. Tina4 refuses to create directories outside the "
|
|
539
|
+
f"project (ADR-0086). Use an absolute path for a database "
|
|
540
|
+
f"that lives outside the project."
|
|
541
|
+
)
|
|
542
|
+
os.makedirs(directory, mode=0o775, exist_ok=True)
|
|
507
543
|
|
|
508
544
|
return path
|
|
509
545
|
|
|
@@ -663,8 +699,7 @@ class Database(DatabaseAsyncMixin):
|
|
|
663
699
|
in this context, so nested facade calls land on the same connection;
|
|
664
700
|
``_release`` returns it.
|
|
665
701
|
"""
|
|
666
|
-
|
|
667
|
-
_warn_if_blocking_the_event_loop(operation)
|
|
702
|
+
_warn_if_blocking_the_event_loop(operation)
|
|
668
703
|
borrow = self._borrowed.get()
|
|
669
704
|
if borrow is not None:
|
|
670
705
|
return borrow.adapter, None
|
|
@@ -675,8 +710,7 @@ class Database(DatabaseAsyncMixin):
|
|
|
675
710
|
"""``_acquire`` for a single adapter call that never calls back into this
|
|
676
711
|
facade (fetch, fetch_one, execute, insert, delete): nothing nested can
|
|
677
712
|
need the pin, so skip recording it. ``token`` is the adapter to return."""
|
|
678
|
-
|
|
679
|
-
_warn_if_blocking_the_event_loop(operation)
|
|
713
|
+
_warn_if_blocking_the_event_loop(operation)
|
|
680
714
|
borrow = self._borrowed.get()
|
|
681
715
|
if borrow is not None:
|
|
682
716
|
return borrow.adapter, None
|
|
@@ -705,8 +739,7 @@ class Database(DatabaseAsyncMixin):
|
|
|
705
739
|
commit()/rollback() - the write is uncommitted, so the connection that
|
|
706
740
|
holds it must be the one the commit lands on."""
|
|
707
741
|
if self._manual_commit and self._borrowed.get() is None:
|
|
708
|
-
|
|
709
|
-
_warn_if_blocking_the_event_loop(operation)
|
|
742
|
+
_warn_if_blocking_the_event_loop(operation)
|
|
710
743
|
adapter = self._pool.checkout()
|
|
711
744
|
self._borrowed.set(_Borrow(adapter, sticky=True, pool=self._pool))
|
|
712
745
|
return adapter, None
|
|
@@ -1172,8 +1205,7 @@ class Database(DatabaseAsyncMixin):
|
|
|
1172
1205
|
borrow.depth += 1
|
|
1173
1206
|
return
|
|
1174
1207
|
if borrow is None:
|
|
1175
|
-
|
|
1176
|
-
_warn_if_blocking_the_event_loop("start_transaction")
|
|
1208
|
+
_warn_if_blocking_the_event_loop("start_transaction")
|
|
1177
1209
|
self._begin_on(self._pool.checkout())
|
|
1178
1210
|
return
|
|
1179
1211
|
# Inside an operation's (or a manual-commit write's) borrow: open the
|
|
@@ -1223,8 +1255,7 @@ class Database(DatabaseAsyncMixin):
|
|
|
1223
1255
|
# Inner commit of an ignored nested begin — just unwind the depth.
|
|
1224
1256
|
borrow.depth -= 1
|
|
1225
1257
|
return
|
|
1226
|
-
|
|
1227
|
-
_warn_if_blocking_the_event_loop("commit")
|
|
1258
|
+
_warn_if_blocking_the_event_loop("commit")
|
|
1228
1259
|
try:
|
|
1229
1260
|
borrow.adapter.commit()
|
|
1230
1261
|
self.last_error = None
|
|
@@ -1250,8 +1281,7 @@ class Database(DatabaseAsyncMixin):
|
|
|
1250
1281
|
adapter.rollback()
|
|
1251
1282
|
self.last_error = None
|
|
1252
1283
|
return
|
|
1253
|
-
|
|
1254
|
-
_warn_if_blocking_the_event_loop("rollback")
|
|
1284
|
+
_warn_if_blocking_the_event_loop("rollback")
|
|
1255
1285
|
try:
|
|
1256
1286
|
borrow.adapter.rollback()
|
|
1257
1287
|
self.last_error = None
|
|
@@ -43,6 +43,14 @@ def resolve_pool_size(pool: int | None) -> int:
|
|
|
43
43
|
|
|
44
44
|
``0`` keeps its documented meaning, "a single connection" - it is now LENT
|
|
45
45
|
exclusively like any other pool, so it serialises instead of being shared.
|
|
46
|
+
|
|
47
|
+
Backward-compatibility note (ADR-0074): an UNSET ``pool=`` used to mean one
|
|
48
|
+
shared connection; it now means TINA4_DB_POOL, else DEFAULT_POOL_SIZE (10).
|
|
49
|
+
This is a deliberate behavioural fix - the old shared connection let two
|
|
50
|
+
concurrent requests read and commit inside each other's transaction. Sync
|
|
51
|
+
callers keep working unchanged; the only visible difference is that an app
|
|
52
|
+
that never set ``pool=`` now opens up to ten connections instead of one. Pin
|
|
53
|
+
the old single-connection behaviour with ``pool=0`` or ``TINA4_DB_POOL=0``.
|
|
46
54
|
"""
|
|
47
55
|
if pool is None:
|
|
48
56
|
raw = os.environ.get("TINA4_DB_POOL", "").strip()
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.139 → tina4_python-3.13.140}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/images/tina4-logo-icon.webp
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/js/tina4-dev-admin.min.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/licenses/tina4-css-MIT.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/licenses/tina4-js-MIT.txt
RENAMED
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.139 → tina4_python-3.13.140}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/queue_backends/kafka_backend.py
RENAMED
|
File without changes
|
{tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/queue_backends/mongo_backend.py
RENAMED
|
File without changes
|
{tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/queue_backends/rabbitmq_backend.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.139 → tina4_python-3.13.140}/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.139 → tina4_python-3.13.140}/tina4_python/session_handlers/memcached_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/session_handlers/mongodb_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/session_handlers/redis_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/session_handlers/valkey_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/docker/distroless/Dockerfile
RENAMED
|
File without changes
|
{tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/docker/poetry/Dockerfile
RENAMED
|
File without changes
|
{tina4_python-3.13.139 → tina4_python-3.13.140}/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.139 → tina4_python-3.13.140}/tina4_python/translations/af/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/af/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/en/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/en/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/es/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/es/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/fr/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/fr/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/ja/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/ja/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/zh/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/zh/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|