tina4-python 3.10.0__tar.gz → 3.10.1__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 (139) hide show
  1. {tina4_python-3.10.0 → tina4_python-3.10.1}/PKG-INFO +1 -1
  2. {tina4_python-3.10.0 → tina4_python-3.10.1}/pyproject.toml +1 -1
  3. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/core/router.py +41 -18
  4. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/orm/model.py +17 -0
  5. {tina4_python-3.10.0 → tina4_python-3.10.1}/.gitignore +0 -0
  6. {tina4_python-3.10.0 → tina4_python-3.10.1}/README.md +0 -0
  7. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/CLAUDE.md +0 -0
  8. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/HtmlElement.py +0 -0
  9. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/Testing.py +0 -0
  10. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/__init__.py +0 -0
  11. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/ai/__init__.py +0 -0
  12. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/api/__init__.py +0 -0
  13. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/auth/__init__.py +0 -0
  14. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/cache/__init__.py +0 -0
  15. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/cli/__init__.py +0 -0
  16. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/container/__init__.py +0 -0
  17. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/core/__init__.py +0 -0
  18. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/core/cache.py +0 -0
  19. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/core/constants.py +0 -0
  20. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/core/events.py +0 -0
  21. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/core/middleware.py +0 -0
  22. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/core/request.py +0 -0
  23. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/core/response.py +0 -0
  24. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/core/server.py +0 -0
  25. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/crud/__init__.py +0 -0
  26. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/database/__init__.py +0 -0
  27. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/database/adapter.py +0 -0
  28. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/database/connection.py +0 -0
  29. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/database/firebird.py +0 -0
  30. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/database/mssql.py +0 -0
  31. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/database/mysql.py +0 -0
  32. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/database/odbc.py +0 -0
  33. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/database/postgres.py +0 -0
  34. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/database/sqlite.py +0 -0
  35. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/debug/__init__.py +0 -0
  36. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/debug/error_overlay.py +0 -0
  37. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/dev_admin/__init__.py +0 -0
  38. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/dev_reload.py +0 -0
  39. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/dotenv/__init__.py +0 -0
  40. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/frond/FROND.md +0 -0
  41. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/frond/__init__.py +0 -0
  42. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/frond/engine.py +0 -0
  43. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/gallery/auth/meta.json +0 -0
  44. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
  45. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/gallery/database/meta.json +0 -0
  46. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
  47. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/gallery/error-overlay/meta.json +0 -0
  48. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
  49. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/gallery/orm/meta.json +0 -0
  50. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
  51. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
  52. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/gallery/queue/meta.json +0 -0
  53. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
  54. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/gallery/rest-api/meta.json +0 -0
  55. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
  56. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/gallery/templates/meta.json +0 -0
  57. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
  58. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
  59. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/graphql/__init__.py +0 -0
  60. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/i18n/__init__.py +0 -0
  61. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/messenger/__init__.py +0 -0
  62. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/migration/__init__.py +0 -0
  63. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/migration/runner.py +0 -0
  64. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/orm/__init__.py +0 -0
  65. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/orm/fields.py +0 -0
  66. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/public/css/tina4.css +0 -0
  67. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/public/css/tina4.min.css +0 -0
  68. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/public/favicon.ico +0 -0
  69. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/public/images/logo.svg +0 -0
  70. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
  71. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/public/js/frond.min.js +0 -0
  72. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
  73. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/public/js/tina4.min.js +0 -0
  74. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/public/js/tina4js.min.js +0 -0
  75. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/public/swagger/index.html +0 -0
  76. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  77. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/query_builder/__init__.py +0 -0
  78. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/queue/__init__.py +0 -0
  79. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/queue_backends/__init__.py +0 -0
  80. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/queue_backends/kafka_backend.py +0 -0
  81. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/queue_backends/mongo_backend.py +0 -0
  82. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
  83. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/scss/__init__.py +0 -0
  84. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/scss/tina4css/_alerts.scss +0 -0
  85. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/scss/tina4css/_badges.scss +0 -0
  86. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/scss/tina4css/_buttons.scss +0 -0
  87. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/scss/tina4css/_cards.scss +0 -0
  88. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/scss/tina4css/_forms.scss +0 -0
  89. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/scss/tina4css/_grid.scss +0 -0
  90. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/scss/tina4css/_modals.scss +0 -0
  91. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/scss/tina4css/_nav.scss +0 -0
  92. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/scss/tina4css/_reset.scss +0 -0
  93. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/scss/tina4css/_tables.scss +0 -0
  94. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/scss/tina4css/_typography.scss +0 -0
  95. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/scss/tina4css/_utilities.scss +0 -0
  96. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/scss/tina4css/_variables.scss +0 -0
  97. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/scss/tina4css/base.scss +0 -0
  98. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/scss/tina4css/colors.scss +0 -0
  99. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/scss/tina4css/tina4.scss +0 -0
  100. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/seeder/__init__.py +0 -0
  101. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/service/__init__.py +0 -0
  102. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/session/__init__.py +0 -0
  103. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/session_handlers/__init__.py +0 -0
  104. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/session_handlers/mongodb_handler.py +0 -0
  105. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/session_handlers/redis_handler.py +0 -0
  106. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/session_handlers/valkey_handler.py +0 -0
  107. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/swagger/__init__.py +0 -0
  108. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/templates/components/crud.twig +0 -0
  109. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
  110. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
  111. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/templates/docker/python/Dockerfile +0 -0
  112. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/templates/docker/uv/Dockerfile +0 -0
  113. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/templates/errors/302.twig +0 -0
  114. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/templates/errors/401.twig +0 -0
  115. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/templates/errors/403.twig +0 -0
  116. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/templates/errors/404.twig +0 -0
  117. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/templates/errors/500.twig +0 -0
  118. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/templates/errors/502.twig +0 -0
  119. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/templates/errors/503.twig +0 -0
  120. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/templates/errors/base.twig +0 -0
  121. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/templates/frontend/README.md +0 -0
  122. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/templates/readme.md +0 -0
  123. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/test_client/__init__.py +0 -0
  124. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
  125. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
  126. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  127. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  128. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
  129. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
  130. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  131. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
  132. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
  133. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
  134. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
  135. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
  136. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/validator/__init__.py +0 -0
  137. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/websocket/__init__.py +0 -0
  138. {tina4_python-3.10.0 → tina4_python-3.10.1}/tina4_python/websocket/backplane.py +0 -0
  139. {tina4_python-3.10.0 → tina4_python-3.10.1}/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.10.0
3
+ Version: 3.10.1
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.10.0"
3
+ version = "3.10.1"
4
4
  description = "Tina4 Python v3 — Zero-dependency, lightweight web framework"
5
5
  authors = [
6
6
  {name = "Andre van Zuydam", email = "andrevanzuydam@gmail.com"}
@@ -180,10 +180,12 @@ class Router:
180
180
  if cls._group_prefix:
181
181
  path = cls._group_prefix + path
182
182
 
183
- # Merge group middleware with route-level middleware
184
- if cls._group_middleware:
185
- route_mw = options.get("middleware", [])
186
- options["middleware"] = list(cls._group_middleware) + list(route_mw)
183
+ # Merge group middleware with route-level middleware and handler-level middleware
184
+ handler_mw = getattr(handler, "_middleware", [])
185
+ route_mw = options.get("middleware", [])
186
+ combined_mw = list(cls._group_middleware) + list(handler_mw) + list(route_mw)
187
+ if combined_mw:
188
+ options["middleware"] = combined_mw
187
189
 
188
190
  pattern, param_names = _compile_pattern(path)
189
191
 
@@ -251,8 +253,14 @@ def _compile_pattern(path: str) -> tuple[re.Pattern, list[str]]:
251
253
  param_names = []
252
254
  regex_parts = []
253
255
 
254
- for segment in path.strip("/").split("/"):
255
- if segment.startswith("{") and segment.endswith("}"):
256
+ segments = path.strip("/").split("/")
257
+ for i, segment in enumerate(segments):
258
+ if segment == "*":
259
+ # Wildcard: matches the rest of the path (greedy)
260
+ param_names.append("wildcard")
261
+ regex_parts.append("(.+)")
262
+ break # Nothing can follow a wildcard
263
+ elif segment.startswith("{") and segment.endswith("}"):
256
264
  inner = segment[1:-1]
257
265
  if ":" in inner:
258
266
  name, type_hint = inner.split(":", 1)
@@ -277,51 +285,53 @@ def _compile_pattern(path: str) -> tuple[re.Pattern, list[str]]:
277
285
 
278
286
  # Decorator functions — the public API
279
287
 
288
+ def _register_route(method: str, path: str, fn, **options):
289
+ """Common registration logic that preserves handler attributes on the returned ref."""
290
+ ref = Router.add(method, path, fn, **options)
291
+ # Propagate handler attributes to the wrapper so stacked decorators still work
292
+ fn._route_ref = ref
293
+ return fn
294
+
295
+
280
296
  def get(path: str, **options):
281
297
  """Register a GET route."""
282
298
  def decorator(fn):
283
- Router.add("GET", path, fn, **options)
284
- return fn
299
+ return _register_route("GET", path, fn, **options)
285
300
  return decorator
286
301
 
287
302
 
288
303
  def post(path: str, **options):
289
304
  """Register a POST route."""
290
305
  def decorator(fn):
291
- Router.add("POST", path, fn, **options)
292
- return fn
306
+ return _register_route("POST", path, fn, **options)
293
307
  return decorator
294
308
 
295
309
 
296
310
  def put(path: str, **options):
297
311
  """Register a PUT route."""
298
312
  def decorator(fn):
299
- Router.add("PUT", path, fn, **options)
300
- return fn
313
+ return _register_route("PUT", path, fn, **options)
301
314
  return decorator
302
315
 
303
316
 
304
317
  def patch(path: str, **options):
305
318
  """Register a PATCH route."""
306
319
  def decorator(fn):
307
- Router.add("PATCH", path, fn, **options)
308
- return fn
320
+ return _register_route("PATCH", path, fn, **options)
309
321
  return decorator
310
322
 
311
323
 
312
324
  def delete(path: str, **options):
313
325
  """Register a DELETE route."""
314
326
  def decorator(fn):
315
- Router.add("DELETE", path, fn, **options)
316
- return fn
327
+ return _register_route("DELETE", path, fn, **options)
317
328
  return decorator
318
329
 
319
330
 
320
331
  def any_method(path: str, **options):
321
332
  """Register a route for any HTTP method."""
322
333
  def decorator(fn):
323
- Router.add("ANY", path, fn, **options)
324
- return fn
334
+ return _register_route("ANY", path, fn, **options)
325
335
  return decorator
326
336
 
327
337
  # Alias — @any() is the standard name across all Tina4 frameworks
@@ -352,6 +362,10 @@ def noauth():
352
362
  """Make a write route (POST/PUT/PATCH/DELETE) public — no auth required."""
353
363
  def decorator(fn):
354
364
  fn._noauth = True
365
+ # If route was already registered (decorator applied after @get/@post),
366
+ # update the route dict directly.
367
+ if hasattr(fn, "_route_ref"):
368
+ fn._route_ref._route["auth_required"] = False
355
369
  return fn
356
370
  return decorator
357
371
 
@@ -360,6 +374,10 @@ def secured():
360
374
  """Require auth on a GET route (which is public by default)."""
361
375
  def decorator(fn):
362
376
  fn._secured = True
377
+ # If route was already registered (decorator applied after @get/@post),
378
+ # update the route dict directly.
379
+ if hasattr(fn, "_route_ref"):
380
+ fn._route_ref._route["auth_required"] = True
363
381
  return fn
364
382
  return decorator
365
383
 
@@ -370,6 +388,11 @@ def middleware(*middleware_classes):
370
388
  """Attach middleware classes to a route handler."""
371
389
  def decorator(fn):
372
390
  fn._middleware = list(middleware_classes)
391
+ # If route was already registered (decorator applied after @get/@post),
392
+ # update the route dict directly.
393
+ if hasattr(fn, "_route_ref"):
394
+ existing = fn._route_ref._route.get("middleware", [])
395
+ fn._route_ref._route["middleware"] = list(middleware_classes) + existing
373
396
  return fn
374
397
  return decorator
375
398
 
@@ -43,6 +43,22 @@ def orm_bind(db, name: str = None):
43
43
  _databases[name] = db
44
44
 
45
45
 
46
+ def snake_to_camel(name: str) -> str:
47
+ """Convert snake_case to camelCase: 'first_name' -> 'firstName'."""
48
+ parts = name.split("_")
49
+ return parts[0] + "".join(p.capitalize() for p in parts[1:])
50
+
51
+
52
+ def camel_to_snake(name: str) -> str:
53
+ """Convert camelCase to snake_case: 'firstName' -> 'first_name'."""
54
+ result = []
55
+ for c in name:
56
+ if c.isupper() and result:
57
+ result.append("_")
58
+ result.append(c.lower())
59
+ return "".join(result)
60
+
61
+
46
62
  class ORMMeta(type):
47
63
  """Metaclass that collects Field definitions and relationship descriptors."""
48
64
 
@@ -79,6 +95,7 @@ class ORM(metaclass=ORMMeta):
79
95
  table_name: str = ""
80
96
  soft_delete: bool = False # Set True to enable soft delete
81
97
  field_mapping: dict[str, str] = {} # {"python_attribute": "db_column"}
98
+ auto_map: bool = False # No-op in Python (snake_case matches DB); exists for cross-language parity
82
99
  _db: str | object | None = None # Per-model database override
83
100
  _fields: dict[str, Field] = {}
84
101
 
File without changes
File without changes