tina4-python 3.10.4__tar.gz → 3.10.6__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.4 → tina4_python-3.10.6}/PKG-INFO +1 -1
  2. {tina4_python-3.10.4 → tina4_python-3.10.6}/pyproject.toml +1 -1
  3. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/frond/engine.py +94 -8
  4. {tina4_python-3.10.4 → tina4_python-3.10.6}/.gitignore +0 -0
  5. {tina4_python-3.10.4 → tina4_python-3.10.6}/README.md +0 -0
  6. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/CLAUDE.md +0 -0
  7. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/HtmlElement.py +0 -0
  8. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/Testing.py +0 -0
  9. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/__init__.py +0 -0
  10. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/ai/__init__.py +0 -0
  11. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/api/__init__.py +0 -0
  12. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/auth/__init__.py +0 -0
  13. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/cache/__init__.py +0 -0
  14. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/cli/__init__.py +0 -0
  15. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/container/__init__.py +0 -0
  16. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/core/__init__.py +0 -0
  17. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/core/cache.py +0 -0
  18. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/core/constants.py +0 -0
  19. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/core/events.py +0 -0
  20. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/core/middleware.py +0 -0
  21. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/core/request.py +0 -0
  22. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/core/response.py +0 -0
  23. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/core/router.py +0 -0
  24. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/core/server.py +0 -0
  25. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/crud/__init__.py +0 -0
  26. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/database/__init__.py +0 -0
  27. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/database/adapter.py +0 -0
  28. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/database/connection.py +0 -0
  29. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/database/firebird.py +0 -0
  30. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/database/mssql.py +0 -0
  31. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/database/mysql.py +0 -0
  32. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/database/odbc.py +0 -0
  33. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/database/postgres.py +0 -0
  34. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/database/sqlite.py +0 -0
  35. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/debug/__init__.py +0 -0
  36. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/debug/error_overlay.py +0 -0
  37. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/dev_admin/__init__.py +0 -0
  38. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/dev_reload.py +0 -0
  39. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/dotenv/__init__.py +0 -0
  40. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/frond/FROND.md +0 -0
  41. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/frond/__init__.py +0 -0
  42. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/gallery/auth/meta.json +0 -0
  43. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
  44. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/gallery/database/meta.json +0 -0
  45. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
  46. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/gallery/error-overlay/meta.json +0 -0
  47. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
  48. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/gallery/orm/meta.json +0 -0
  49. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
  50. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
  51. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/gallery/queue/meta.json +0 -0
  52. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
  53. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/gallery/rest-api/meta.json +0 -0
  54. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
  55. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/gallery/templates/meta.json +0 -0
  56. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
  57. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
  58. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/graphql/__init__.py +0 -0
  59. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/i18n/__init__.py +0 -0
  60. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/messenger/__init__.py +0 -0
  61. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/migration/__init__.py +0 -0
  62. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/migration/runner.py +0 -0
  63. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/orm/__init__.py +0 -0
  64. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/orm/fields.py +0 -0
  65. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/orm/model.py +0 -0
  66. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/public/css/tina4.css +0 -0
  67. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/public/css/tina4.min.css +0 -0
  68. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/public/favicon.ico +0 -0
  69. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/public/images/logo.svg +0 -0
  70. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
  71. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/public/js/frond.min.js +0 -0
  72. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
  73. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/public/js/tina4.min.js +0 -0
  74. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/public/js/tina4js.min.js +0 -0
  75. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/public/swagger/index.html +0 -0
  76. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  77. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/query_builder/__init__.py +0 -0
  78. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/queue/__init__.py +0 -0
  79. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/queue_backends/__init__.py +0 -0
  80. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/queue_backends/kafka_backend.py +0 -0
  81. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/queue_backends/mongo_backend.py +0 -0
  82. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
  83. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/scss/__init__.py +0 -0
  84. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/scss/tina4css/_alerts.scss +0 -0
  85. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/scss/tina4css/_badges.scss +0 -0
  86. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/scss/tina4css/_buttons.scss +0 -0
  87. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/scss/tina4css/_cards.scss +0 -0
  88. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/scss/tina4css/_forms.scss +0 -0
  89. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/scss/tina4css/_grid.scss +0 -0
  90. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/scss/tina4css/_modals.scss +0 -0
  91. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/scss/tina4css/_nav.scss +0 -0
  92. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/scss/tina4css/_reset.scss +0 -0
  93. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/scss/tina4css/_tables.scss +0 -0
  94. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/scss/tina4css/_typography.scss +0 -0
  95. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/scss/tina4css/_utilities.scss +0 -0
  96. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/scss/tina4css/_variables.scss +0 -0
  97. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/scss/tina4css/base.scss +0 -0
  98. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/scss/tina4css/colors.scss +0 -0
  99. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/scss/tina4css/tina4.scss +0 -0
  100. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/seeder/__init__.py +0 -0
  101. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/service/__init__.py +0 -0
  102. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/session/__init__.py +0 -0
  103. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/session_handlers/__init__.py +0 -0
  104. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/session_handlers/mongodb_handler.py +0 -0
  105. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/session_handlers/redis_handler.py +0 -0
  106. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/session_handlers/valkey_handler.py +0 -0
  107. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/swagger/__init__.py +0 -0
  108. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/templates/components/crud.twig +0 -0
  109. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
  110. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
  111. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/templates/docker/python/Dockerfile +0 -0
  112. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/templates/docker/uv/Dockerfile +0 -0
  113. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/templates/errors/302.twig +0 -0
  114. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/templates/errors/401.twig +0 -0
  115. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/templates/errors/403.twig +0 -0
  116. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/templates/errors/404.twig +0 -0
  117. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/templates/errors/500.twig +0 -0
  118. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/templates/errors/502.twig +0 -0
  119. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/templates/errors/503.twig +0 -0
  120. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/templates/errors/base.twig +0 -0
  121. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/templates/frontend/README.md +0 -0
  122. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/templates/readme.md +0 -0
  123. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/test_client/__init__.py +0 -0
  124. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
  125. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
  126. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  127. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  128. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
  129. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
  130. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  131. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
  132. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
  133. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
  134. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
  135. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
  136. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/validator/__init__.py +0 -0
  137. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/websocket/__init__.py +0 -0
  138. {tina4_python-3.10.4 → tina4_python-3.10.6}/tina4_python/websocket/backplane.py +0 -0
  139. {tina4_python-3.10.4 → tina4_python-3.10.6}/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.4
3
+ Version: 3.10.6
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.4"
3
+ version = "3.10.6"
4
4
  description = "Tina4 Python v3 — Zero-dependency, lightweight web framework"
5
5
  authors = [
6
6
  {name = "Andre van Zuydam", email = "andrevanzuydam@gmail.com"}
@@ -352,10 +352,81 @@ def _split_args(raw: str) -> list[str]:
352
352
  return parts
353
353
 
354
354
 
355
+ def _find_outside_quotes(expr: str, needle: str) -> int:
356
+ """Find the first occurrence of *needle* that is not inside quotes or parens.
357
+
358
+ Returns the index, or -1 if not found outside quotes.
359
+ """
360
+ in_q = None
361
+ depth = 0
362
+ i = 0
363
+ while i <= len(expr) - len(needle):
364
+ ch = expr[i]
365
+ if ch in ('"', "'") and depth == 0:
366
+ if in_q is None:
367
+ in_q = ch
368
+ elif ch == in_q:
369
+ in_q = None
370
+ i += 1
371
+ continue
372
+ if in_q:
373
+ i += 1
374
+ continue
375
+ if ch == "(":
376
+ depth += 1
377
+ elif ch == ")":
378
+ depth -= 1
379
+ if depth == 0 and expr[i:i + len(needle)] == needle:
380
+ return i
381
+ i += 1
382
+ return -1
383
+
384
+
385
+ def _split_outside_quotes(expr: str, sep: str) -> list[str]:
386
+ """Split *expr* on *sep* only when *sep* is outside quotes and parens."""
387
+ parts = []
388
+ current_start = 0
389
+ in_q = None
390
+ depth = 0
391
+ i = 0
392
+ while i <= len(expr) - len(sep):
393
+ ch = expr[i]
394
+ if ch in ('"', "'") and depth == 0:
395
+ if in_q is None:
396
+ in_q = ch
397
+ elif ch == in_q:
398
+ in_q = None
399
+ i += 1
400
+ continue
401
+ if in_q:
402
+ i += 1
403
+ continue
404
+ if ch == "(":
405
+ depth += 1
406
+ elif ch == ")":
407
+ depth -= 1
408
+ if depth == 0 and expr[i:i + len(sep)] == sep:
409
+ parts.append(expr[current_start:i])
410
+ i += len(sep)
411
+ current_start = i
412
+ continue
413
+ i += 1
414
+ parts.append(expr[current_start:])
415
+ return parts
416
+
417
+
355
418
  def _eval_expr(expr: str, context: dict):
356
419
  """Evaluate a full expression (with ~, ternary, ??, comparisons)."""
357
420
  expr = expr.strip()
358
421
 
422
+ # String literal — only match if the entire expression is a single quoted
423
+ # string with no unescaped matching quotes inside (avoids catching
424
+ # expressions like 'yes' if x else 'no').
425
+ if len(expr) >= 2:
426
+ q = expr[0]
427
+ if q in ('"', "'") and expr.endswith(q) and q not in expr[1:-1]:
428
+ return expr[1:-1]
429
+
359
430
  # Ternary: condition ? "yes" : "no"
360
431
  ternary = re.match(r"^(.+?)\s*\?\s*(.+?)\s*:\s*(.+)$", expr)
361
432
  if ternary:
@@ -373,29 +444,44 @@ def _eval_expr(expr: str, context: dict):
373
444
  return _eval_expr(inline_if.group(3), context)
374
445
 
375
446
  # Null coalescing: value ?? "default"
376
- if "??" in expr:
377
- left, _, right = expr.partition("??")
447
+ if _find_outside_quotes(expr, "??") >= 0:
448
+ pos = _find_outside_quotes(expr, "??")
449
+ left = expr[:pos]
450
+ right = expr[pos + 2:]
378
451
  val = _eval_expr(left.strip(), context)
379
452
  if val is None:
380
453
  return _eval_expr(right.strip(), context)
381
454
  return val
382
455
 
383
456
  # String concatenation with ~
384
- if "~" in expr:
385
- parts = expr.split("~")
457
+ if _find_outside_quotes(expr, "~") >= 0:
458
+ parts = _split_outside_quotes(expr, "~")
386
459
  return "".join(str(_eval_expr(p, context) or "") for p in parts)
387
460
 
388
461
  # Comparison operators for if conditions
389
462
  for op in (" not in ", " in ", " is not ", " is ", "!=", "==", ">=", "<=", ">", "<", " and ", " or ", " not "):
390
- if op in expr:
463
+ if _find_outside_quotes(expr, op) >= 0:
391
464
  return _eval_comparison(expr, context)
392
465
 
393
- # Function call: name("arg1", "arg2")
394
- fn_match = re.match(r"^(\w+)\s*\((.*)?\)$", expr, re.DOTALL)
466
+ # Function call: name("arg1", "arg2") or obj.method("arg1")
467
+ fn_match = re.match(r"^([\w.]+)\s*\((.*)?\)$", expr, re.DOTALL)
395
468
  if fn_match:
396
469
  fn_name = fn_match.group(1)
397
470
  raw_args = fn_match.group(2) or ""
398
- fn = context.get(fn_name) or _resolve(fn_name, context)
471
+ # For dotted names like obj.method, resolve the object then get the method
472
+ if "." in fn_name:
473
+ parts = fn_name.rsplit(".", 1)
474
+ obj = _resolve(parts[0], context)
475
+ if obj is None:
476
+ fn = None
477
+ elif isinstance(obj, dict):
478
+ fn = obj.get(parts[1])
479
+ elif hasattr(obj, parts[1]):
480
+ fn = getattr(obj, parts[1])
481
+ else:
482
+ fn = None
483
+ else:
484
+ fn = context.get(fn_name) or _resolve(fn_name, context)
399
485
  if callable(fn):
400
486
  if raw_args.strip():
401
487
  # Split args manually, evaluate each as expression
File without changes
File without changes