tina4-python 3.11.10__tar.gz → 3.11.12__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 (150) hide show
  1. {tina4_python-3.11.10 → tina4_python-3.11.12}/PKG-INFO +1 -1
  2. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/__init__.py +1 -1
  3. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/core/server.py +11 -3
  4. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/database/connection.py +55 -15
  5. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/orm/fields.py +33 -2
  6. {tina4_python-3.11.10 → tina4_python-3.11.12}/.gitignore +0 -0
  7. {tina4_python-3.11.10 → tina4_python-3.11.12}/README.md +0 -0
  8. {tina4_python-3.11.10 → tina4_python-3.11.12}/pyproject.toml +0 -0
  9. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/CLAUDE.md +0 -0
  10. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/HtmlElement.py +0 -0
  11. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/Testing.py +0 -0
  12. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/ai/__init__.py +0 -0
  13. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/api/__init__.py +0 -0
  14. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/auth/__init__.py +0 -0
  15. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/cache/__init__.py +0 -0
  16. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/cli/__init__.py +0 -0
  17. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/container/__init__.py +0 -0
  18. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/core/__init__.py +0 -0
  19. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/core/cache.py +0 -0
  20. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/core/constants.py +0 -0
  21. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/core/events.py +0 -0
  22. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/core/middleware.py +0 -0
  23. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/core/rate_limiter.py +0 -0
  24. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/core/request.py +0 -0
  25. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/core/response.py +0 -0
  26. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/core/router.py +0 -0
  27. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/crud/__init__.py +0 -0
  28. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/database/__init__.py +0 -0
  29. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/database/adapter.py +0 -0
  30. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/database/firebird.py +0 -0
  31. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/database/mongodb.py +0 -0
  32. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/database/mssql.py +0 -0
  33. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/database/mysql.py +0 -0
  34. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/database/odbc.py +0 -0
  35. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/database/postgres.py +0 -0
  36. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/database/sqlite.py +0 -0
  37. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/debug/__init__.py +0 -0
  38. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/debug/error_overlay.py +0 -0
  39. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/dev_admin/__init__.py +0 -0
  40. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/dev_admin/metrics.py +0 -0
  41. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/dotenv/__init__.py +0 -0
  42. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/frond/FROND.md +0 -0
  43. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/frond/__init__.py +0 -0
  44. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/frond/engine.py +0 -0
  45. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/gallery/auth/meta.json +0 -0
  46. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
  47. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/gallery/database/meta.json +0 -0
  48. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
  49. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/gallery/error-overlay/meta.json +0 -0
  50. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
  51. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/gallery/orm/meta.json +0 -0
  52. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
  53. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
  54. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/gallery/queue/meta.json +0 -0
  55. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
  56. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/gallery/rest-api/meta.json +0 -0
  57. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
  58. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/gallery/templates/meta.json +0 -0
  59. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
  60. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
  61. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/graphql/__init__.py +0 -0
  62. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/i18n/__init__.py +0 -0
  63. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/mcp/__init__.py +0 -0
  64. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/mcp/protocol.py +0 -0
  65. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/mcp/tools.py +0 -0
  66. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/messenger/__init__.py +0 -0
  67. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/migration/__init__.py +0 -0
  68. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/migration/runner.py +0 -0
  69. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/orm/__init__.py +0 -0
  70. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/orm/model.py +0 -0
  71. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/public/css/tina4.css +0 -0
  72. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/public/css/tina4.min.css +0 -0
  73. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/public/favicon.ico +0 -0
  74. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/public/images/logo.svg +0 -0
  75. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
  76. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/public/js/frond.min.js +0 -0
  77. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/public/js/tina4-dev-admin.js +0 -0
  78. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
  79. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/public/js/tina4.min.js +0 -0
  80. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/public/js/tina4js.min.js +0 -0
  81. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/public/swagger/index.html +0 -0
  82. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  83. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/query_builder/__init__.py +0 -0
  84. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/queue/__init__.py +0 -0
  85. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/queue/job.py +0 -0
  86. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/queue/kafka_backend.py +0 -0
  87. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/queue/lite_backend.py +0 -0
  88. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/queue/mongo_backend.py +0 -0
  89. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/queue/rabbitmq_backend.py +0 -0
  90. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/queue_backends/__init__.py +0 -0
  91. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/queue_backends/kafka_backend.py +0 -0
  92. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/queue_backends/mongo_backend.py +0 -0
  93. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
  94. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/scss/__init__.py +0 -0
  95. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/scss/tina4css/_alerts.scss +0 -0
  96. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/scss/tina4css/_badges.scss +0 -0
  97. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/scss/tina4css/_buttons.scss +0 -0
  98. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/scss/tina4css/_cards.scss +0 -0
  99. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/scss/tina4css/_forms.scss +0 -0
  100. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/scss/tina4css/_grid.scss +0 -0
  101. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/scss/tina4css/_modals.scss +0 -0
  102. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/scss/tina4css/_nav.scss +0 -0
  103. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/scss/tina4css/_reset.scss +0 -0
  104. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/scss/tina4css/_tables.scss +0 -0
  105. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/scss/tina4css/_typography.scss +0 -0
  106. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/scss/tina4css/_utilities.scss +0 -0
  107. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/scss/tina4css/_variables.scss +0 -0
  108. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/scss/tina4css/base.scss +0 -0
  109. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/scss/tina4css/colors.scss +0 -0
  110. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/scss/tina4css/tina4.scss +0 -0
  111. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/seeder/__init__.py +0 -0
  112. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/service/__init__.py +0 -0
  113. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/session/__init__.py +0 -0
  114. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/session_handlers/__init__.py +0 -0
  115. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/session_handlers/mongodb_handler.py +0 -0
  116. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/session_handlers/redis_handler.py +0 -0
  117. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/session_handlers/valkey_handler.py +0 -0
  118. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/swagger/__init__.py +0 -0
  119. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/templates/components/crud.twig +0 -0
  120. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
  121. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
  122. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/templates/docker/python/Dockerfile +0 -0
  123. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/templates/docker/uv/Dockerfile +0 -0
  124. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/templates/errors/302.twig +0 -0
  125. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/templates/errors/401.twig +0 -0
  126. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/templates/errors/403.twig +0 -0
  127. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/templates/errors/404.twig +0 -0
  128. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/templates/errors/500.twig +0 -0
  129. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/templates/errors/502.twig +0 -0
  130. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/templates/errors/503.twig +0 -0
  131. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/templates/errors/base.twig +0 -0
  132. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/templates/frontend/README.md +0 -0
  133. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/templates/readme.md +0 -0
  134. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/test_client/__init__.py +0 -0
  135. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
  136. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
  137. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  138. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  139. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
  140. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
  141. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  142. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
  143. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
  144. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
  145. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
  146. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
  147. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/validator/__init__.py +0 -0
  148. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/websocket/__init__.py +0 -0
  149. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/websocket/backplane.py +0 -0
  150. {tina4_python-3.11.10 → tina4_python-3.11.12}/tina4_python/wsdl/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tina4-python
3
- Version: 3.11.10
3
+ Version: 3.11.12
4
4
  Summary: Tina4 for Python — 54 built-in features, zero dependencies
5
5
  Author-email: Andre van Zuydam <andrevanzuydam@gmail.com>
6
6
  License: MIT
@@ -8,7 +8,7 @@ Tina4 Python v3.0 — Zero-dependency, lightweight web framework.
8
8
 
9
9
  One import, everything works.
10
10
  """
11
- __version__ = "3.11.10"
11
+ __version__ = "3.11.12"
12
12
 
13
13
  # ── Route decorators ──
14
14
  from tina4_python.core.router import ( # noqa: E402, F401
@@ -73,12 +73,20 @@ def _auto_discover(root_dir: str = "src"):
73
73
 
74
74
 
75
75
  def _ensure_folders():
76
- """Create project folders if missing (auto-repair)."""
76
+ """Create project folders if missing (auto-repair).
77
+
78
+ Note: ``migrations/`` lives at the project root (matches the CLI's
79
+ ``migrations`` default and the documented project structure), never
80
+ under ``src/``. Don't add ``src/migrations`` here — it creates an
81
+ empty directory that the migration runner ignores and confuses users.
82
+ """
77
83
  folders = [
78
- "src/routes", "src/orm", "src/migrations", "src/seeds",
84
+ "src/routes", "src/orm", "src/seeds",
79
85
  "src/templates", "src/templates/errors",
80
86
  "src/public", "src/public/js", "src/public/css", "src/public/icons",
81
- "src/locales", "data", "data/.broken", "logs", "secrets", "tests",
87
+ "src/locales",
88
+ "migrations",
89
+ "data", "data/.broken", "logs", "secrets", "tests",
82
90
  ]
83
91
  for folder in folders:
84
92
  Path(folder).mkdir(parents=True, exist_ok=True)
@@ -195,27 +195,67 @@ class Database:
195
195
  return _DRIVERS[scheme]()
196
196
 
197
197
  def _connection_path(self) -> str:
198
- """Extract connection-specific path/params from the URL."""
198
+ """Extract connection-specific path/params from the URL.
199
+
200
+ SQLite URL convention (matches PHP and the Python CLAUDE.md docs):
201
+
202
+ sqlite::memory: → in-memory database
203
+ sqlite:///:memory: → in-memory database (URL form)
204
+ sqlite:///app.db → ./app.db (relative to cwd)
205
+ sqlite:///data/app.db → ./data/app.db (relative)
206
+ sqlite:///./data/app.db → ./data/app.db (relative, explicit)
207
+ sqlite:////absolute/path.db → /absolute/path.db (absolute)
208
+ sqlite:///C:/Users/app.db → C:/Users/app.db (Windows absolute)
209
+
210
+ Directories are auto-created ONLY when the resolved path is
211
+ inside the current working directory (the project root). We
212
+ never try to ``os.makedirs`` at root (``/data``, ``C:\\data``)
213
+ — that's both hostile on read-only filesystems and not what
214
+ any project actually wants.
215
+ """
199
216
  parsed = urlparse(self.url)
200
217
 
201
- if parsed.scheme.startswith("sqlite"):
202
- # urlparse("sqlite:///path").path = "/path"
203
- # Strip the leading / only when it precedes a Windows drive
204
- # letter (e.g. /C:/Users/app.db → C:/Users/app.db). On Linux
205
- # the leading / is the root directory and must be kept.
206
- path = parsed.path
207
- if len(path) >= 3 and path[0] == "/" and path[1].isalpha() and path[2] == ":":
208
- path = path[1:] # Windows: /C:/... → C:/...
218
+ if not parsed.scheme.startswith("sqlite"):
219
+ # For other drivers, return the full URL (adapter parses it)
220
+ return self.url
221
+
222
+ # In-memory forms passthrough
223
+ raw_path = parsed.path
224
+ if raw_path in (":memory:", "/:memory:"):
225
+ return ":memory:"
209
226
 
210
- # Ensure directory exists
227
+ # Strip exactly one leading "/" — the URL "/"-delimiter between the
228
+ # empty netloc and the start of the path.
229
+ if raw_path.startswith("/"):
230
+ stripped = raw_path[1:]
231
+ else:
232
+ stripped = raw_path
233
+
234
+ # Windows absolute path (drive-letter form): C:/... or C:\...
235
+ is_windows_abs = (
236
+ len(stripped) >= 3
237
+ and stripped[0].isalpha()
238
+ and stripped[1] == ":"
239
+ and stripped[2] in ("/", "\\")
240
+ )
241
+ # Unix absolute path: urlparse("sqlite:////abs/app.db").path == "//abs/app.db"
242
+ # After one strip it's still "/abs/app.db" → still starts with "/".
243
+ is_unix_abs = stripped.startswith("/")
244
+
245
+ if is_windows_abs or is_unix_abs:
246
+ path = stripped
247
+ # Don't auto-create directories outside cwd. If the user gave
248
+ # an absolute path, they're responsible for it existing.
249
+ else:
250
+ # Relative — resolve under the project root (cwd).
251
+ cwd = os.getcwd()
252
+ path = os.path.join(cwd, stripped)
253
+ # Only auto-create subdirectories *inside* cwd.
211
254
  directory = os.path.dirname(path)
212
- if directory:
255
+ if directory and os.path.commonpath([os.path.abspath(directory), cwd]) == cwd:
213
256
  os.makedirs(directory, exist_ok=True)
214
257
 
215
- return path
216
-
217
- # For other drivers, return the full URL (adapter parses it)
218
- return self.url
258
+ return path
219
259
 
220
260
  # ── Query Cache ──────────────────────────────────────────────
221
261
 
@@ -58,10 +58,41 @@ class Field:
58
58
  raise ValueError(f"Field '{self.name}' is required")
59
59
  return self.default
60
60
 
61
- # Type coercion
61
+ # Type coercion.
62
+ #
63
+ # `validate()` runs on BOTH the write path (user input from routes/forms)
64
+ # and the read path (rows coming back from the database driver). Drivers
65
+ # return values in different shapes per engine:
66
+ #
67
+ # engine datetime column bool column numeric column
68
+ # -------- ---------------- ----------- --------------
69
+ # SQLite str int (0/1) float / int
70
+ # PostgreSQL datetime bool Decimal
71
+ # MySQL datetime int (0/1) Decimal
72
+ # MSSQL datetime bool Decimal
73
+ # Firebird datetime int (0/1) Decimal
74
+ #
75
+ # So the rule is: if the driver already handed us the right type,
76
+ # accept it as-is. Otherwise coerce. This avoids the classic crash
77
+ # `datetime(datetime_instance)` that hit every PostgreSQL ORM read.
78
+ #
79
+ # Care is needed around `bool` being a subclass of `int` in Python —
80
+ # we handle those two paths explicitly before the generic fast path.
62
81
  try:
63
- if self.field_type == bool and isinstance(value, int):
82
+ # BooleanField receiving an int (e.g. SQLite 0/1) → cast to bool
83
+ if self.field_type is bool and isinstance(value, int) and not isinstance(value, bool):
64
84
  value = bool(value)
85
+ # IntegerField receiving a bool → cast to int (preserve legacy behaviour
86
+ # where True/False round-trip as 1/0 in numeric columns)
87
+ elif self.field_type is int and isinstance(value, bool):
88
+ value = int(value)
89
+ # DateTimeField receiving an ISO-8601 string (SQLite default) → parse
90
+ elif self.field_type is datetime and isinstance(value, str):
91
+ value = datetime.fromisoformat(value)
92
+ # Fast path: driver already handed us the correct type. Don't re-coerce —
93
+ # `datetime(datetime_instance)` etc. raises TypeError otherwise.
94
+ elif isinstance(value, self.field_type):
95
+ pass
65
96
  else:
66
97
  value = self.field_type(value)
67
98
  except (TypeError, ValueError) as e:
File without changes