tina4-python 3.11.17__tar.gz → 3.11.20__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 (152) hide show
  1. {tina4_python-3.11.17 → tina4_python-3.11.20}/PKG-INFO +1 -1
  2. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/__init__.py +1 -1
  3. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/frond/engine.py +76 -0
  4. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/public/js/tina4-dev-admin.js +96 -111
  5. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/public/js/tina4-dev-admin.min.js +96 -111
  6. {tina4_python-3.11.17 → tina4_python-3.11.20}/.gitignore +0 -0
  7. {tina4_python-3.11.17 → tina4_python-3.11.20}/README.md +0 -0
  8. {tina4_python-3.11.17 → tina4_python-3.11.20}/pyproject.toml +0 -0
  9. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/CLAUDE.md +0 -0
  10. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/HtmlElement.py +0 -0
  11. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/Testing.py +0 -0
  12. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/ai/__init__.py +0 -0
  13. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/api/__init__.py +0 -0
  14. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/auth/__init__.py +0 -0
  15. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/cache/__init__.py +0 -0
  16. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/cli/__init__.py +0 -0
  17. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/container/__init__.py +0 -0
  18. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/core/__init__.py +0 -0
  19. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/core/cache.py +0 -0
  20. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/core/constants.py +0 -0
  21. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/core/events.py +0 -0
  22. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/core/middleware.py +0 -0
  23. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/core/rate_limiter.py +0 -0
  24. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/core/request.py +0 -0
  25. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/core/response.py +0 -0
  26. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/core/router.py +0 -0
  27. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/core/server.py +0 -0
  28. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/crud/__init__.py +0 -0
  29. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/database/__init__.py +0 -0
  30. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/database/adapter.py +0 -0
  31. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/database/connection.py +0 -0
  32. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/database/firebird.py +0 -0
  33. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/database/mongodb.py +0 -0
  34. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/database/mssql.py +0 -0
  35. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/database/mysql.py +0 -0
  36. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/database/odbc.py +0 -0
  37. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/database/postgres.py +0 -0
  38. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/database/sqlite.py +0 -0
  39. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/debug/__init__.py +0 -0
  40. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/debug/error_overlay.py +0 -0
  41. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/dev_admin/__init__.py +0 -0
  42. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/dev_admin/metrics.py +0 -0
  43. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/dev_admin/plan.py +0 -0
  44. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/dev_admin/project_index.py +0 -0
  45. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/dotenv/__init__.py +0 -0
  46. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/frond/FROND.md +0 -0
  47. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/frond/__init__.py +0 -0
  48. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/gallery/auth/meta.json +0 -0
  49. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
  50. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/gallery/database/meta.json +0 -0
  51. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
  52. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/gallery/error-overlay/meta.json +0 -0
  53. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
  54. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/gallery/orm/meta.json +0 -0
  55. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
  56. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
  57. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/gallery/queue/meta.json +0 -0
  58. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
  59. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/gallery/rest-api/meta.json +0 -0
  60. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
  61. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/gallery/templates/meta.json +0 -0
  62. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
  63. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
  64. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/graphql/__init__.py +0 -0
  65. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/i18n/__init__.py +0 -0
  66. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/mcp/__init__.py +0 -0
  67. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/mcp/protocol.py +0 -0
  68. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/mcp/tools.py +0 -0
  69. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/messenger/__init__.py +0 -0
  70. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/migration/__init__.py +0 -0
  71. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/migration/runner.py +0 -0
  72. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/orm/__init__.py +0 -0
  73. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/orm/fields.py +0 -0
  74. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/orm/model.py +0 -0
  75. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/public/css/tina4.css +0 -0
  76. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/public/css/tina4.min.css +0 -0
  77. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/public/favicon.ico +0 -0
  78. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/public/images/logo.svg +0 -0
  79. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
  80. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/public/js/frond.min.js +0 -0
  81. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/public/js/tina4.min.js +0 -0
  82. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/public/js/tina4js.min.js +0 -0
  83. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/public/swagger/index.html +0 -0
  84. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  85. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/query_builder/__init__.py +0 -0
  86. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/queue/__init__.py +0 -0
  87. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/queue/job.py +0 -0
  88. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/queue/kafka_backend.py +0 -0
  89. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/queue/lite_backend.py +0 -0
  90. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/queue/mongo_backend.py +0 -0
  91. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/queue/rabbitmq_backend.py +0 -0
  92. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/queue_backends/__init__.py +0 -0
  93. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/queue_backends/kafka_backend.py +0 -0
  94. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/queue_backends/mongo_backend.py +0 -0
  95. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
  96. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/scss/__init__.py +0 -0
  97. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/scss/tina4css/_alerts.scss +0 -0
  98. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/scss/tina4css/_badges.scss +0 -0
  99. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/scss/tina4css/_buttons.scss +0 -0
  100. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/scss/tina4css/_cards.scss +0 -0
  101. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/scss/tina4css/_forms.scss +0 -0
  102. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/scss/tina4css/_grid.scss +0 -0
  103. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/scss/tina4css/_modals.scss +0 -0
  104. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/scss/tina4css/_nav.scss +0 -0
  105. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/scss/tina4css/_reset.scss +0 -0
  106. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/scss/tina4css/_tables.scss +0 -0
  107. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/scss/tina4css/_typography.scss +0 -0
  108. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/scss/tina4css/_utilities.scss +0 -0
  109. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/scss/tina4css/_variables.scss +0 -0
  110. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/scss/tina4css/base.scss +0 -0
  111. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/scss/tina4css/colors.scss +0 -0
  112. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/scss/tina4css/tina4.scss +0 -0
  113. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/seeder/__init__.py +0 -0
  114. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/service/__init__.py +0 -0
  115. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/session/__init__.py +0 -0
  116. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/session_handlers/__init__.py +0 -0
  117. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/session_handlers/mongodb_handler.py +0 -0
  118. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/session_handlers/redis_handler.py +0 -0
  119. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/session_handlers/valkey_handler.py +0 -0
  120. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/swagger/__init__.py +0 -0
  121. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/templates/components/crud.twig +0 -0
  122. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
  123. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
  124. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/templates/docker/python/Dockerfile +0 -0
  125. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/templates/docker/uv/Dockerfile +0 -0
  126. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/templates/errors/302.twig +0 -0
  127. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/templates/errors/401.twig +0 -0
  128. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/templates/errors/403.twig +0 -0
  129. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/templates/errors/404.twig +0 -0
  130. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/templates/errors/500.twig +0 -0
  131. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/templates/errors/502.twig +0 -0
  132. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/templates/errors/503.twig +0 -0
  133. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/templates/errors/base.twig +0 -0
  134. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/templates/frontend/README.md +0 -0
  135. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/templates/readme.md +0 -0
  136. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/test_client/__init__.py +0 -0
  137. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
  138. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
  139. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  140. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  141. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
  142. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
  143. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  144. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
  145. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
  146. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
  147. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
  148. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
  149. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/validator/__init__.py +0 -0
  150. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/websocket/__init__.py +0 -0
  151. {tina4_python-3.11.17 → tina4_python-3.11.20}/tina4_python/websocket/backplane.py +0 -0
  152. {tina4_python-3.11.17 → tina4_python-3.11.20}/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.17
3
+ Version: 3.11.20
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.17"
11
+ __version__ = "3.11.20"
12
12
 
13
13
  # ── Route decorators ──
14
14
  from tina4_python.core.router import ( # noqa: E402, F401
@@ -900,6 +900,41 @@ def _split_on_pipe(expr: str) -> list[str]:
900
900
  return parts
901
901
 
902
902
 
903
+ def _split_filter_name_and_path(fname: str) -> tuple[str, str]:
904
+ """Split `first.groupSummary` into (`first`, `groupSummary`).
905
+
906
+ Returns (fname, "") when no structural `.` is present. The split
907
+ point must be outside parens, brackets, braces, and quotes so
908
+ filter args like ``number_format(1.5)`` or ``date("Y.m.d")`` don't
909
+ trip false splits.
910
+
911
+ Used by the filter-application loops to support `{{ x | first.foo }}`
912
+ syntax — apply the filter, then traverse the property path on the
913
+ filter's result. Parity with tina4-php's findDotOutsideParens().
914
+ """
915
+ depth = 0
916
+ in_q = None
917
+ i = 0
918
+ n = len(fname)
919
+ while i < n:
920
+ ch = fname[i]
921
+ if in_q is not None:
922
+ if ch == in_q and (i == 0 or fname[i - 1] != "\\"):
923
+ in_q = None
924
+ i += 1
925
+ continue
926
+ if ch in ('"', "'"):
927
+ in_q = ch
928
+ elif ch in ("(", "[", "{"):
929
+ depth += 1
930
+ elif ch in (")", "]", "}"):
931
+ depth -= 1
932
+ elif ch == "." and depth == 0:
933
+ return fname[:i], fname[i + 1:]
934
+ i += 1
935
+ return fname, ""
936
+
937
+
903
938
  def _parse_filter_chain(expr: str) -> tuple[str, list[tuple[str, list[str]]]]:
904
939
  """Parse 'variable | filter1 | filter2(arg)' into (variable, [(name, args)])."""
905
940
  parts = _split_on_pipe(expr)
@@ -1685,6 +1720,20 @@ class Frond:
1685
1720
  for fname, args in filters:
1686
1721
  if fname in ("raw", "safe"):
1687
1722
  continue
1723
+
1724
+ # Support `{{ x | first.foo.bar }}` — filter followed by
1725
+ # property access. Split on the first structural `.`; apply
1726
+ # the filter, then traverse the path on the result via a
1727
+ # synthetic context so we reuse _eval_expr's dotted/bracket
1728
+ # resolution.
1729
+ real_fname, tail_path = _split_filter_name_and_path(fname)
1730
+ fn = self._filters.get(real_fname) if tail_path else None
1731
+ if tail_path and fn is not None:
1732
+ value = fn(value, *args)
1733
+ value = _eval_expr("__frond_filter_tmp." + tail_path,
1734
+ {"__frond_filter_tmp": value})
1735
+ continue
1736
+
1688
1737
  fn = self._filters.get(fname)
1689
1738
  if fn:
1690
1739
  value = fn(value, *args)
@@ -1738,6 +1787,24 @@ class Frond:
1738
1787
  if fname not in self._allowed_filters:
1739
1788
  continue # Silently skip blocked filter
1740
1789
 
1790
+ # Support `{{ x | first.foo.bar }}` — filter followed by
1791
+ # property access. Split on the first structural `.`; apply
1792
+ # the filter, then traverse the path on the result via a
1793
+ # synthetic context so we reuse _eval_expr's dotted/bracket
1794
+ # resolution. Done BEFORE the fast-path switch so chained
1795
+ # cases like `items|first.name` are always handled.
1796
+ real_fname, tail_path = _split_filter_name_and_path(fname)
1797
+ if tail_path:
1798
+ fn = self._filters.get(real_fname)
1799
+ if fn is not None:
1800
+ value = fn(value, *args)
1801
+ value = _eval_expr("__frond_filter_tmp." + tail_path,
1802
+ {"__frond_filter_tmp": value})
1803
+ continue
1804
+ # Fall through if the leading segment isn't a known
1805
+ # filter — lets legacy filter names with dots (unusual)
1806
+ # still hit the registered-filter lookup below.
1807
+
1741
1808
  # Fast path for common no-arg filters
1742
1809
  if not args:
1743
1810
  if fname == "upper":
@@ -2302,6 +2369,15 @@ class Frond:
2302
2369
  if self._sandbox and self._allowed_filters is not None:
2303
2370
  if fname not in self._allowed_filters:
2304
2371
  continue
2372
+ # Filter + property access: e.g. `items|first.name`.
2373
+ real_fname, tail_path = _split_filter_name_and_path(fname)
2374
+ if tail_path:
2375
+ fn = self._filters.get(real_fname)
2376
+ if fn is not None:
2377
+ value = fn(value, *args)
2378
+ value = _eval_expr("__frond_filter_tmp." + tail_path,
2379
+ {"__frond_filter_tmp": value})
2380
+ continue
2305
2381
  fn = self._filters.get(fname)
2306
2382
  if fn:
2307
2383
  value = fn(value, *args)