tina4-python 3.13.124__tar.gz → 3.13.126__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.
Files changed (170) hide show
  1. {tina4_python-3.13.124 → tina4_python-3.13.126}/PKG-INFO +1 -1
  2. {tina4_python-3.13.124 → tina4_python-3.13.126}/pyproject.toml +1 -1
  3. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/__init__.py +1 -1
  4. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/cli/__init__.py +4 -4
  5. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/database/firebird.py +1 -0
  6. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/database/mssql.py +1 -0
  7. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/database/mysql.py +1 -0
  8. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/database/sql_translator.py +43 -0
  9. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/seeder/__init__.py +43 -8
  10. {tina4_python-3.13.124 → tina4_python-3.13.126}/.gitignore +0 -0
  11. {tina4_python-3.13.124 → tina4_python-3.13.126}/README.md +0 -0
  12. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/CLAUDE.md +0 -0
  13. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/HtmlElement.py +0 -0
  14. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/Testing.py +0 -0
  15. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/_import_helper.py +0 -0
  16. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/ai/__init__.py +0 -0
  17. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/ai/client.py +0 -0
  18. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/api/__init__.py +0 -0
  19. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/auth/__init__.py +0 -0
  20. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/cache/__init__.py +0 -0
  21. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/container/__init__.py +0 -0
  22. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/context/__init__.py +0 -0
  23. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/context/chunker.py +0 -0
  24. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/core/__init__.py +0 -0
  25. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/core/cache.py +0 -0
  26. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/core/constants.py +0 -0
  27. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/core/events.py +0 -0
  28. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/core/middleware.py +0 -0
  29. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/core/port_takeover.py +0 -0
  30. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/core/rate_limiter.py +0 -0
  31. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/core/request.py +0 -0
  32. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/core/response.py +0 -0
  33. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/core/router.py +0 -0
  34. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/core/server.py +0 -0
  35. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/crud/__init__.py +0 -0
  36. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/database/__init__.py +0 -0
  37. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/database/adapter.py +0 -0
  38. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/database/connection.py +0 -0
  39. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/database/database_url.py +0 -0
  40. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/database/mongodb.py +0 -0
  41. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/database/odbc.py +0 -0
  42. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/database/postgres.py +0 -0
  43. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/database/sqlite.py +0 -0
  44. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/debug/__init__.py +0 -0
  45. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/debug/error_overlay.py +0 -0
  46. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/dev_admin/__init__.py +0 -0
  47. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/dev_admin/metrics.py +0 -0
  48. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/dev_admin/plan.py +0 -0
  49. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/dev_admin/project_index.py +0 -0
  50. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/docs.py +0 -0
  51. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/docstore/__init__.py +0 -0
  52. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/dotenv/__init__.py +0 -0
  53. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/env.py +0 -0
  54. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/frond/FROND.md +0 -0
  55. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/frond/__init__.py +0 -0
  56. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/frond/compiler.py +0 -0
  57. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/frond/engine.py +0 -0
  58. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/frond/parser.py +0 -0
  59. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/gallery/auth/meta.json +0 -0
  60. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
  61. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/gallery/database/meta.json +0 -0
  62. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
  63. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/gallery/error-overlay/meta.json +0 -0
  64. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
  65. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/gallery/orm/meta.json +0 -0
  66. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
  67. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
  68. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/gallery/queue/meta.json +0 -0
  69. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
  70. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/gallery/rest-api/meta.json +0 -0
  71. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
  72. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/gallery/templates/meta.json +0 -0
  73. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
  74. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
  75. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/graph/__init__.py +0 -0
  76. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/graph/adapter.py +0 -0
  77. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/graph/adapters/__init__.py +0 -0
  78. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/graph/adapters/arango.py +0 -0
  79. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/graph/adapters/bolt.py +0 -0
  80. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/graph/adapters/ultipa.py +0 -0
  81. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/graph/graph_url.py +0 -0
  82. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/graphql/__init__.py +0 -0
  83. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/i18n/__init__.py +0 -0
  84. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/mcp/__init__.py +0 -0
  85. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/mcp/protocol.py +0 -0
  86. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/mcp/tools.py +0 -0
  87. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/messenger/__init__.py +0 -0
  88. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/migration/__init__.py +0 -0
  89. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/migration/runner.py +0 -0
  90. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/mqtt/__init__.py +0 -0
  91. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/mqtt/message.py +0 -0
  92. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/orm/__init__.py +0 -0
  93. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/orm/fields.py +0 -0
  94. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/orm/model.py +0 -0
  95. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/orm/point.py +0 -0
  96. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/public/__feedback/widget.js +0 -0
  97. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/public/css/tina4.css +0 -0
  98. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/public/css/tina4.min.css +0 -0
  99. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/public/favicon.ico +0 -0
  100. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/public/images/logo.svg +0 -0
  101. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
  102. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/public/js/frond.js +0 -0
  103. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/public/js/frond.min.js +0 -0
  104. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
  105. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/public/js/tina4.min.js +0 -0
  106. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/public/js/tina4js.min.js +0 -0
  107. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/public/swagger/index.html +0 -0
  108. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  109. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/query_builder/__init__.py +0 -0
  110. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/queue/__init__.py +0 -0
  111. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/queue/amqp_url.py +0 -0
  112. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/queue/job.py +0 -0
  113. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/queue/kafka_backend.py +0 -0
  114. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/queue/lite_backend.py +0 -0
  115. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/queue/mongo_backend.py +0 -0
  116. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/queue/rabbitmq_backend.py +0 -0
  117. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/queue_backends/__init__.py +0 -0
  118. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/queue_backends/kafka_backend.py +0 -0
  119. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/queue_backends/mongo_backend.py +0 -0
  120. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
  121. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/realtime/__init__.py +0 -0
  122. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/realtime/models/Attachment.py +0 -0
  123. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/realtime/models/Channel.py +0 -0
  124. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/realtime/models/ChannelMember.py +0 -0
  125. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/realtime/models/Message.py +0 -0
  126. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/realtime/models/Workspace.py +0 -0
  127. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/realtime/models/__init__.py +0 -0
  128. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/realtime/storage.py +0 -0
  129. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/service/__init__.py +0 -0
  130. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/session/__init__.py +0 -0
  131. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/session_handlers/__init__.py +0 -0
  132. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/session_handlers/memcached_handler.py +0 -0
  133. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/session_handlers/mongodb_handler.py +0 -0
  134. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/session_handlers/redis_handler.py +0 -0
  135. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/session_handlers/valkey_handler.py +0 -0
  136. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/sso.py +0 -0
  137. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/swagger/__init__.py +0 -0
  138. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/templates/components/crud.twig +0 -0
  139. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
  140. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
  141. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/templates/docker/python/Dockerfile +0 -0
  142. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/templates/docker/uv/Dockerfile +0 -0
  143. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/templates/errors/302.twig +0 -0
  144. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/templates/errors/401.twig +0 -0
  145. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/templates/errors/403.twig +0 -0
  146. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/templates/errors/404.twig +0 -0
  147. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/templates/errors/500.twig +0 -0
  148. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/templates/errors/502.twig +0 -0
  149. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/templates/errors/503.twig +0 -0
  150. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/templates/errors/base.twig +0 -0
  151. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/templates/frontend/README.md +0 -0
  152. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/templates/readme.md +0 -0
  153. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/test/__init__.py +0 -0
  154. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/test_client/__init__.py +0 -0
  155. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
  156. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
  157. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  158. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  159. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
  160. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
  161. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  162. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
  163. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
  164. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
  165. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
  166. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
  167. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/validator/__init__.py +0 -0
  168. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/websocket/__init__.py +0 -0
  169. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/websocket/backplane.py +0 -0
  170. {tina4_python-3.13.124 → tina4_python-3.13.126}/tina4_python/wsdl/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: tina4-python
3
- Version: 3.13.124
3
+ Version: 3.13.126
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tina4-python"
3
- version = "3.13.124"
3
+ version = "3.13.126"
4
4
  description = "Tina4 Python v3 — Zero-dependency, lightweight web framework"
5
5
  authors = [
6
6
  {name = "Andre van Zuydam", email = "andrevanzuydam@gmail.com"}
@@ -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.124"
54
+ return "3.13.125"
55
55
 
56
56
 
57
57
  __version__ = _resolve_version()
@@ -31,8 +31,8 @@ from pathlib import Path
31
31
 
32
32
  # ── Field type mapping ────────────────────────────────────────────────
33
33
  FIELD_TYPE_MAP = {
34
- "string": {"orm": "StringField", "sql": "TEXT", "default": "''"},
35
- "str": {"orm": "StringField", "sql": "TEXT", "default": "''"},
34
+ "string": {"orm": "StringField", "sql": "VARCHAR(255)", "default": "''"},
35
+ "str": {"orm": "StringField", "sql": "VARCHAR(255)", "default": "''"},
36
36
  "int": {"orm": "IntegerField", "sql": "INTEGER", "default": "0"},
37
37
  "integer": {"orm": "IntegerField", "sql": "INTEGER", "default": "0"},
38
38
  "float": {"orm": "NumericField", "sql": "REAL", "default": "0"},
@@ -41,7 +41,7 @@ FIELD_TYPE_MAP = {
41
41
  "bool": {"orm": "BooleanField", "sql": "INTEGER", "default": "0"},
42
42
  "boolean": {"orm": "BooleanField", "sql": "INTEGER", "default": "0"},
43
43
  "text": {"orm": "TextField", "sql": "TEXT", "default": "''"},
44
- "datetime": {"orm": "DateTimeField", "sql": "TEXT", "default": "NULL"},
44
+ "datetime": {"orm": "DateTimeField", "sql": "TIMESTAMP", "default": "NULL"},
45
45
  "blob": {"orm": "BlobField", "sql": "BLOB", "default": "NULL"},
46
46
  }
47
47
 
@@ -1941,7 +1941,7 @@ def _gen_migration(name: str, flags: dict = None, *,
1941
1941
  info = FIELD_TYPE_MAP.get(ftype, FIELD_TYPE_MAP["string"])
1942
1942
  default = f" DEFAULT {info['default']}" if info["default"] != "NULL" else ""
1943
1943
  col_lines.append(f" {fname} {info['sql']}{default}")
1944
- col_lines.append(" created_at TEXT DEFAULT CURRENT_TIMESTAMP")
1944
+ col_lines.append(" created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
1945
1945
 
1946
1946
  # ADR-0063: `tina4:edit` marker between the last column and the closing
1947
1947
  # paren, placed OUTSIDE the comma-joined column list so the SQL stays
@@ -646,6 +646,7 @@ class FirebirdAdapter(SqlCrudMixin, DatabaseAdapter):
646
646
  sql = SQLTranslator.ilike_to_like(sql)
647
647
  sql = SQLTranslator.boolean_to_int(sql)
648
648
  sql = SQLTranslator.auto_increment_syntax(sql, "firebird")
649
+ sql = SQLTranslator.ddl_types(sql, "firebird")
649
650
  return sql
650
651
 
651
652
  def _supports_returning(self) -> bool:
@@ -350,6 +350,7 @@ class MSSQLAdapter(SqlCrudMixin, DatabaseAdapter):
350
350
  sql = SQLTranslator.ilike_to_like(sql)
351
351
  sql = SQLTranslator.auto_increment_syntax(sql, "mssql")
352
352
  sql = SQLTranslator.boolean_to_int(sql)
353
+ sql = SQLTranslator.ddl_types(sql, "mssql")
353
354
  return sql
354
355
 
355
356
  def _supports_returning(self) -> bool:
@@ -321,6 +321,7 @@ class MySQLAdapter(SqlCrudMixin, DatabaseAdapter):
321
321
  sql = SQLTranslator.concat_pipes_to_func(sql)
322
322
  sql = SQLTranslator.ilike_to_like(sql)
323
323
  sql = SQLTranslator.auto_increment_syntax(sql, "mysql")
324
+ sql = SQLTranslator.ddl_types(sql, "mysql")
324
325
  return sql
325
326
 
326
327
  def _supports_returning(self) -> bool:
@@ -347,6 +347,49 @@ class SQLTranslator:
347
347
  return re.sub(r"\s*AUTOINCREMENT\b", "", sql, flags=re.IGNORECASE)
348
348
  return sql
349
349
 
350
+ @staticmethod
351
+ def ddl_types(sql: str, engine: str) -> str:
352
+ """Translate SQLite-canonical DDL column TYPES + CREATE-TABLE options to
353
+ the target engine.
354
+
355
+ ONLY acts on ``CREATE TABLE`` / ``ALTER TABLE`` statements, so a query or
356
+ INSERT that happens to contain the word ``TEXT`` (a column name, a string
357
+ literal) is never rewritten. Complements ``auto_increment_syntax`` (which
358
+ maps the id keyword) so ONE portable migration — and every
359
+ ``ORM.create_table()`` DDL, which is also SQLite-canonical — applies on
360
+ every engine instead of failing on Firebird/MSSQL.
361
+
362
+ * Firebird has no ``TEXT`` (-607), no ``REAL``, and no
363
+ ``CREATE TABLE IF NOT EXISTS``.
364
+ * MSSQL has no ``CREATE TABLE IF NOT EXISTS`` and its ``TIMESTAMP`` is a
365
+ rowversion, not a datetime — a ``created_at TIMESTAMP`` there is wrong.
366
+ * MySQL's ``TIMESTAMP`` carries auto-update / 2038 surprises, so a
367
+ datetime column maps to ``DATETIME`` (matching ORM.create_table()).
368
+ """
369
+ # Gate to DDL only, tolerating leading `-- ...` comment lines / blank
370
+ # lines that a migration file carries before its CREATE TABLE. A SELECT
371
+ # or INSERT that merely mentions a type keyword is never rewritten.
372
+ head = re.sub(r"^(?:\s*--[^\n]*\n)+", "", sql)
373
+ if not re.match(r"\s*(CREATE\s+TABLE|ALTER\s+TABLE)\b", head, re.IGNORECASE):
374
+ return sql
375
+ e = (engine or "").lower()
376
+ if e == "firebird":
377
+ sql = re.sub(r"\bIF\s+NOT\s+EXISTS\b", "", sql, flags=re.IGNORECASE)
378
+ # Map bare TEXT -> BLOB SUB_TYPE TEXT, but leave an existing
379
+ # "BLOB SUB_TYPE TEXT" intact (it already contains the word TEXT).
380
+ sql = re.sub(
381
+ r"\bBLOB\s+SUB_TYPE\s+TEXT\b", "\x00FBTEXT\x00", sql, flags=re.IGNORECASE
382
+ )
383
+ sql = re.sub(r"\bTEXT\b", "BLOB SUB_TYPE TEXT", sql, flags=re.IGNORECASE)
384
+ sql = sql.replace("\x00FBTEXT\x00", "BLOB SUB_TYPE TEXT")
385
+ sql = re.sub(r"\bREAL\b", "DOUBLE PRECISION", sql, flags=re.IGNORECASE)
386
+ elif e == "mssql":
387
+ sql = re.sub(r"\bIF\s+NOT\s+EXISTS\b", "", sql, flags=re.IGNORECASE)
388
+ sql = re.sub(r"\bTIMESTAMP\b", "DATETIME2", sql, flags=re.IGNORECASE)
389
+ elif e == "mysql":
390
+ sql = re.sub(r"\bTIMESTAMP\b", "DATETIME", sql, flags=re.IGNORECASE)
391
+ return sql
392
+
350
393
  @staticmethod
351
394
  def placeholder_style(sql: str, style: str = "?") -> str:
352
395
  """Convert ? placeholders to engine-specific style.
@@ -66,6 +66,35 @@ _JOB_TITLES = [
66
66
  ]
67
67
  _CURRENCIES = ["USD", "EUR", "GBP", "JPY", "CAD", "AUD", "CHF", "ZAR", "INR", "CNY"]
68
68
  _CREDIT_CARD_PREFIXES = ["4111", "4242", "5500", "5105"]
69
+ # Product-name vocabulary (adjective + noun). Seeds a `name` column on a
70
+ # product-ish table with "Wireless Keyboard" instead of a person name.
71
+ _PRODUCT_ADJECTIVES = [
72
+ "Wireless", "Organic", "Premium", "Classic", "Eco", "Smart", "Portable",
73
+ "Deluxe", "Compact", "Rustic", "Handcrafted", "Vintage", "Modern",
74
+ "Ergonomic", "Stainless", "Bamboo", "Recycled", "Artisan", "Professional",
75
+ "Ultra", "Insulated", "Lightweight", "Adjustable", "Foldable",
76
+ ]
77
+ _PRODUCT_NOUNS = [
78
+ "Keyboard", "Coffee Beans", "Backpack", "Water Bottle", "Desk Lamp",
79
+ "Headphones", "Notebook", "Sneakers", "Sunglasses", "Wallet", "Mug",
80
+ "Chair", "Blender", "Speaker", "Charger", "Umbrella", "Toothbrush",
81
+ "Jacket", "Watch", "Kettle", "Picture Frame", "Planter", "Cutlery Set",
82
+ "Yoga Mat", "Phone Case",
83
+ ]
84
+ # A table/model whose name contains any of these gets product names on its
85
+ # generic `name`/`full_name` column instead of a person name.
86
+ _PRODUCT_TABLE_HINTS = (
87
+ "product", "item", "catalog", "inventory", "goods", "merchandise",
88
+ "sku", "listing", "stock", "ware",
89
+ )
90
+
91
+
92
+ def _is_product_table(table: str) -> bool:
93
+ """True when the table/model name looks like a product catalogue, so a
94
+ generic ``name`` column should seed a product name, not a person name.
95
+ With no table context this is False, so the person-name default is kept
96
+ (back-compat)."""
97
+ return any(hint in (table or "").lower() for hint in _PRODUCT_TABLE_HINTS)
69
98
 
70
99
 
71
100
  class FakeData:
@@ -227,7 +256,7 @@ class FakeData:
227
256
  d = start + timedelta(seconds=self._rng.randint(0, delta))
228
257
  return d.strftime("%Y-%m-%dT%H:%M:%SZ")
229
258
 
230
- def for_field(self, field_def: dict, column_name: str = None):
259
+ def for_field(self, field_def: dict, column_name: str = None, table: str = None):
231
260
  """Generate a fake value from a field definition dict and optional column name.
232
261
 
233
262
  Args:
@@ -249,7 +278,7 @@ class FakeData:
249
278
  if "phone" in col or "tel" in col or "mobile" in col:
250
279
  return self.phone()
251
280
  if col in ("full_name", "fullname", "name"):
252
- return self.name()
281
+ return self.product() if _is_product_table(table) else self.name()
253
282
  if "first_name" in col or ("first" in col and "name" in col):
254
283
  return self.first_name()
255
284
  if "last_name" in col or ("last" in col and "name" in col):
@@ -297,6 +326,11 @@ class FakeData:
297
326
  def job_title(self) -> str:
298
327
  return self._rng.choice(_JOB_TITLES)
299
328
 
329
+ def product(self) -> str:
330
+ """A plausible product name, e.g. "Wireless Keyboard" or "Organic Coffee
331
+ Beans". Deterministic under a seed like every other generator."""
332
+ return f"{self._rng.choice(_PRODUCT_ADJECTIVES)} {self._rng.choice(_PRODUCT_NOUNS)}"
333
+
300
334
  def currency(self) -> str:
301
335
  return self._rng.choice(_CURRENCIES)
302
336
 
@@ -362,7 +396,7 @@ class SeedSummary(int):
362
396
  return f"SeedSummary(seeded={self.seeded}, failed={self.failed}, errors={self.errors!r})"
363
397
 
364
398
 
365
- def _generator_for_column(fake: "FakeData", name: str, col_type: str):
399
+ def _generator_for_column(fake: "FakeData", name: str, col_type: str, table: str = None):
366
400
  """Pick a FakeData generator for one column from its name + type.
367
401
 
368
402
  The shared heuristic behind :func:`auto_field_map`, so the dev-admin
@@ -380,7 +414,7 @@ def _generator_for_column(fake: "FakeData", name: str, col_type: str):
380
414
  if "last" in name_lower and "name" in name_lower:
381
415
  return fake.last_name
382
416
  if "name" in name_lower:
383
- return fake.name
417
+ return fake.product if _is_product_table(table) else fake.name
384
418
  if "phone" in name_lower or "tel" in name_lower:
385
419
  return fake.phone
386
420
  if "url" in name_lower or "link" in name_lower:
@@ -424,7 +458,7 @@ def auto_field_map(db, table: str, fake: "FakeData" = None) -> dict:
424
458
  is_pk = col.get("primary_key", col.get("pk", False))
425
459
  if is_pk and ("AUTO" in col_type or "SERIAL" in col_type or name.lower() == "id"):
426
460
  continue
427
- field_map[name] = _generator_for_column(fake, name, col_type)
461
+ field_map[name] = _generator_for_column(fake, name, col_type, table)
428
462
  return field_map
429
463
 
430
464
 
@@ -584,7 +618,8 @@ def seed_orm(orm_class, count: int = 10,
584
618
  elif name in fk_pools and fk_pools[name]:
585
619
  attrs[name] = fake.choice(fk_pools[name])
586
620
  else:
587
- attrs[name] = _generate_for_field(fake, _field_meta(field), name)
621
+ attrs[name] = _generate_for_field(fake, _field_meta(field), name,
622
+ table=orm_class.__name__)
588
623
  _validate_types(fields, attrs, orm_class.__name__, Log)
589
624
  obj = orm_class(attrs)
590
625
  if obj.save():
@@ -813,7 +848,7 @@ def _topo_sort_models(orm_classes: list) -> list:
813
848
  return ordered
814
849
 
815
850
 
816
- def _generate_for_field(fake: FakeData, field_def: dict, col: str):
851
+ def _generate_for_field(fake: FakeData, field_def: dict, col: str, table: str = None):
817
852
  """Generate a fake value for an ORM field definition + column name."""
818
853
  col = col.lower()
819
854
  ftype = field_def.get("type", "string")
@@ -842,7 +877,7 @@ def _generate_for_field(fake: FakeData, field_def: dict, col: str):
842
877
  if "email" in col:
843
878
  return fake.email()
844
879
  if col in ("name", "full_name", "fullname"):
845
- return fake.name()
880
+ return fake.product() if _is_product_table(table) else fake.name()
846
881
  if "first" in col and "name" in col:
847
882
  return fake.first_name()
848
883
  if "last" in col and "name" in col: