tina4-python 3.10.11__tar.gz → 3.10.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 (139) hide show
  1. {tina4_python-3.10.11 → tina4_python-3.10.12}/PKG-INFO +1 -1
  2. {tina4_python-3.10.11 → tina4_python-3.10.12}/pyproject.toml +1 -1
  3. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/core/server.py +4 -0
  4. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/websocket/backplane.py +96 -3
  5. {tina4_python-3.10.11 → tina4_python-3.10.12}/.gitignore +0 -0
  6. {tina4_python-3.10.11 → tina4_python-3.10.12}/README.md +0 -0
  7. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/CLAUDE.md +0 -0
  8. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/HtmlElement.py +0 -0
  9. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/Testing.py +0 -0
  10. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/__init__.py +0 -0
  11. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/ai/__init__.py +0 -0
  12. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/api/__init__.py +0 -0
  13. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/auth/__init__.py +0 -0
  14. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/cache/__init__.py +0 -0
  15. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/cli/__init__.py +0 -0
  16. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/container/__init__.py +0 -0
  17. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/core/__init__.py +0 -0
  18. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/core/cache.py +0 -0
  19. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/core/constants.py +0 -0
  20. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/core/events.py +0 -0
  21. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/core/middleware.py +0 -0
  22. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/core/request.py +0 -0
  23. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/core/response.py +0 -0
  24. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/core/router.py +0 -0
  25. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/crud/__init__.py +0 -0
  26. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/database/__init__.py +0 -0
  27. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/database/adapter.py +0 -0
  28. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/database/connection.py +0 -0
  29. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/database/firebird.py +0 -0
  30. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/database/mssql.py +0 -0
  31. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/database/mysql.py +0 -0
  32. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/database/odbc.py +0 -0
  33. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/database/postgres.py +0 -0
  34. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/database/sqlite.py +0 -0
  35. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/debug/__init__.py +0 -0
  36. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/debug/error_overlay.py +0 -0
  37. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/dev_admin/__init__.py +0 -0
  38. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/dev_reload.py +0 -0
  39. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/dotenv/__init__.py +0 -0
  40. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/frond/FROND.md +0 -0
  41. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/frond/__init__.py +0 -0
  42. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/frond/engine.py +0 -0
  43. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/gallery/auth/meta.json +0 -0
  44. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
  45. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/gallery/database/meta.json +0 -0
  46. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
  47. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/gallery/error-overlay/meta.json +0 -0
  48. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
  49. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/gallery/orm/meta.json +0 -0
  50. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
  51. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
  52. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/gallery/queue/meta.json +0 -0
  53. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
  54. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/gallery/rest-api/meta.json +0 -0
  55. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
  56. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/gallery/templates/meta.json +0 -0
  57. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
  58. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
  59. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/graphql/__init__.py +0 -0
  60. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/i18n/__init__.py +0 -0
  61. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/messenger/__init__.py +0 -0
  62. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/migration/__init__.py +0 -0
  63. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/migration/runner.py +0 -0
  64. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/orm/__init__.py +0 -0
  65. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/orm/fields.py +0 -0
  66. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/orm/model.py +0 -0
  67. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/public/css/tina4.css +0 -0
  68. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/public/css/tina4.min.css +0 -0
  69. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/public/favicon.ico +0 -0
  70. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/public/images/logo.svg +0 -0
  71. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
  72. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/public/js/frond.min.js +0 -0
  73. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
  74. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/public/js/tina4.min.js +0 -0
  75. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/public/js/tina4js.min.js +0 -0
  76. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/public/swagger/index.html +0 -0
  77. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  78. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/query_builder/__init__.py +0 -0
  79. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/queue/__init__.py +0 -0
  80. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/queue_backends/__init__.py +0 -0
  81. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/queue_backends/kafka_backend.py +0 -0
  82. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/queue_backends/mongo_backend.py +0 -0
  83. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
  84. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/scss/__init__.py +0 -0
  85. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/scss/tina4css/_alerts.scss +0 -0
  86. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/scss/tina4css/_badges.scss +0 -0
  87. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/scss/tina4css/_buttons.scss +0 -0
  88. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/scss/tina4css/_cards.scss +0 -0
  89. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/scss/tina4css/_forms.scss +0 -0
  90. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/scss/tina4css/_grid.scss +0 -0
  91. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/scss/tina4css/_modals.scss +0 -0
  92. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/scss/tina4css/_nav.scss +0 -0
  93. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/scss/tina4css/_reset.scss +0 -0
  94. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/scss/tina4css/_tables.scss +0 -0
  95. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/scss/tina4css/_typography.scss +0 -0
  96. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/scss/tina4css/_utilities.scss +0 -0
  97. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/scss/tina4css/_variables.scss +0 -0
  98. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/scss/tina4css/base.scss +0 -0
  99. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/scss/tina4css/colors.scss +0 -0
  100. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/scss/tina4css/tina4.scss +0 -0
  101. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/seeder/__init__.py +0 -0
  102. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/service/__init__.py +0 -0
  103. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/session/__init__.py +0 -0
  104. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/session_handlers/__init__.py +0 -0
  105. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/session_handlers/mongodb_handler.py +0 -0
  106. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/session_handlers/redis_handler.py +0 -0
  107. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/session_handlers/valkey_handler.py +0 -0
  108. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/swagger/__init__.py +0 -0
  109. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/templates/components/crud.twig +0 -0
  110. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
  111. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
  112. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/templates/docker/python/Dockerfile +0 -0
  113. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/templates/docker/uv/Dockerfile +0 -0
  114. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/templates/errors/302.twig +0 -0
  115. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/templates/errors/401.twig +0 -0
  116. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/templates/errors/403.twig +0 -0
  117. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/templates/errors/404.twig +0 -0
  118. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/templates/errors/500.twig +0 -0
  119. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/templates/errors/502.twig +0 -0
  120. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/templates/errors/503.twig +0 -0
  121. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/templates/errors/base.twig +0 -0
  122. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/templates/frontend/README.md +0 -0
  123. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/templates/readme.md +0 -0
  124. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/test_client/__init__.py +0 -0
  125. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
  126. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
  127. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  128. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  129. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
  130. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
  131. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  132. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
  133. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
  134. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
  135. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
  136. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
  137. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/validator/__init__.py +0 -0
  138. {tina4_python-3.10.11 → tina4_python-3.10.12}/tina4_python/websocket/__init__.py +0 -0
  139. {tina4_python-3.10.11 → tina4_python-3.10.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.10.11
3
+ Version: 3.10.12
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.11"
3
+ version = "3.10.12"
4
4
  description = "Tina4 Python v3 — Zero-dependency, lightweight web framework"
5
5
  authors = [
6
6
  {name = "Andre van Zuydam", email = "andrevanzuydam@gmail.com"}
@@ -966,6 +966,10 @@ async def app(scope: dict, receive, send):
966
966
  ttl = int(os.environ.get("TINA4_SESSION_TTL", "3600"))
967
967
  samesite = os.environ.get("TINA4_SESSION_SAMESITE", "Lax")
968
968
  response.header("set-cookie", f"tina4_session={sid}; Path=/; HttpOnly; SameSite={samesite}; Max-Age={ttl}")
969
+ # Probabilistic garbage collection (~1% of requests)
970
+ import random
971
+ if random.randint(1, 100) == 1:
972
+ request.session.gc()
969
973
  except Exception:
970
974
  pass
971
975
 
@@ -100,6 +100,101 @@ class RedisBackplane(WebSocketBackplane):
100
100
  logger.info("RedisBackplane closed")
101
101
 
102
102
 
103
+ class NATSBackplane(WebSocketBackplane):
104
+ """NATS pub/sub backplane.
105
+
106
+ Requires the ``nats-py`` package (``pip install nats-py``). The import is
107
+ deferred so the rest of Tina4 works fine without it installed.
108
+
109
+ NATS is async-native, so we run an asyncio event loop in a background
110
+ thread for the subscription listener.
111
+ """
112
+
113
+ def __init__(self, url: str | None = None):
114
+ try:
115
+ import nats # noqa: F401
116
+ except ImportError:
117
+ raise ImportError(
118
+ "The 'nats-py' package is required for NATSBackplane. "
119
+ "Install it with: pip install nats-py"
120
+ )
121
+
122
+ self._url = url or os.environ.get(
123
+ "TINA4_WS_BACKPLANE_URL", "nats://localhost:4222"
124
+ )
125
+ self._nc = None
126
+ self._subs: dict[str, object] = {}
127
+ self._loop = None
128
+ self._thread = None
129
+ self._running = True
130
+ self._connect()
131
+ logger.info("NATSBackplane connected to %s", self._url)
132
+
133
+ def _connect(self):
134
+ """Connect to NATS in a background event loop."""
135
+ import asyncio
136
+ import nats
137
+
138
+ self._loop = asyncio.new_event_loop()
139
+
140
+ async def _do_connect():
141
+ self._nc = await nats.connect(self._url)
142
+
143
+ self._loop.run_until_complete(_do_connect())
144
+
145
+ # Run the event loop in a background thread for subscriptions
146
+ self._thread = threading.Thread(
147
+ target=self._loop.run_forever, daemon=True
148
+ )
149
+ self._thread.start()
150
+
151
+ def publish(self, channel: str, message: str) -> None:
152
+ import asyncio
153
+
154
+ async def _pub():
155
+ await self._nc.publish(channel, message.encode())
156
+
157
+ asyncio.run_coroutine_threadsafe(_pub(), self._loop).result(timeout=5)
158
+
159
+ def subscribe(self, channel: str, callback) -> None:
160
+ import asyncio
161
+
162
+ async def _sub():
163
+ async def handler(msg):
164
+ callback(msg.data.decode())
165
+
166
+ sub = await self._nc.subscribe(channel, cb=handler)
167
+ self._subs[channel] = sub
168
+
169
+ asyncio.run_coroutine_threadsafe(_sub(), self._loop).result(timeout=5)
170
+ logger.info("NATSBackplane subscribed to channel '%s'", channel)
171
+
172
+ def unsubscribe(self, channel: str) -> None:
173
+ import asyncio
174
+
175
+ sub = self._subs.pop(channel, None)
176
+ if sub:
177
+ asyncio.run_coroutine_threadsafe(
178
+ sub.unsubscribe(), self._loop
179
+ ).result(timeout=5)
180
+ logger.info("NATSBackplane unsubscribed from channel '%s'", channel)
181
+
182
+ def close(self) -> None:
183
+ import asyncio
184
+
185
+ self._running = False
186
+ if self._nc:
187
+ asyncio.run_coroutine_threadsafe(
188
+ self._nc.close(), self._loop
189
+ ).result(timeout=5)
190
+ if self._loop:
191
+ self._loop.call_soon_threadsafe(self._loop.stop)
192
+ if self._thread:
193
+ self._thread.join(timeout=2)
194
+ self._subs.clear()
195
+ logger.info("NATSBackplane closed")
196
+
197
+
103
198
  def create_backplane(url: str | None = None) -> WebSocketBackplane | None:
104
199
  """Factory that reads TINA4_WS_BACKPLANE and returns the appropriate
105
200
  backplane instance, or *None* if no backplane is configured.
@@ -112,9 +207,7 @@ def create_backplane(url: str | None = None) -> WebSocketBackplane | None:
112
207
  if backend == "redis":
113
208
  return RedisBackplane(url=url)
114
209
  elif backend == "nats":
115
- raise NotImplementedError(
116
- "NATS backplane is on the roadmap but not yet implemented."
117
- )
210
+ return NATSBackplane(url=url)
118
211
  elif backend == "":
119
212
  return None
120
213
  else:
File without changes