tina4-python 3.13.0__tar.gz → 3.13.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 (156) hide show
  1. {tina4_python-3.13.0 → tina4_python-3.13.1}/PKG-INFO +1 -1
  2. {tina4_python-3.13.0 → tina4_python-3.13.1}/pyproject.toml +1 -1
  3. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/__init__.py +1 -1
  4. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/service/__init__.py +103 -0
  5. {tina4_python-3.13.0 → tina4_python-3.13.1}/.gitignore +0 -0
  6. {tina4_python-3.13.0 → tina4_python-3.13.1}/README.md +0 -0
  7. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/CLAUDE.md +0 -0
  8. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/HtmlElement.py +0 -0
  9. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/Testing.py +0 -0
  10. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/ai/__init__.py +0 -0
  11. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/api/__init__.py +0 -0
  12. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/auth/__init__.py +0 -0
  13. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/cache/__init__.py +0 -0
  14. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/cli/__init__.py +0 -0
  15. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/container/__init__.py +0 -0
  16. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/core/__init__.py +0 -0
  17. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/core/cache.py +0 -0
  18. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/core/constants.py +0 -0
  19. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/core/events.py +0 -0
  20. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/core/middleware.py +0 -0
  21. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/core/rate_limiter.py +0 -0
  22. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/core/request.py +0 -0
  23. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/core/response.py +0 -0
  24. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/core/router.py +0 -0
  25. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/core/server.py +0 -0
  26. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/crud/__init__.py +0 -0
  27. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/database/__init__.py +0 -0
  28. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/database/adapter.py +0 -0
  29. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/database/connection.py +0 -0
  30. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/database/firebird.py +0 -0
  31. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/database/mongodb.py +0 -0
  32. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/database/mssql.py +0 -0
  33. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/database/mysql.py +0 -0
  34. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/database/odbc.py +0 -0
  35. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/database/postgres.py +0 -0
  36. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/database/sqlite.py +0 -0
  37. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/debug/__init__.py +0 -0
  38. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/debug/error_overlay.py +0 -0
  39. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/dev_admin/__init__.py +0 -0
  40. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/dev_admin/metrics.py +0 -0
  41. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/dev_admin/plan.py +0 -0
  42. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/dev_admin/project_index.py +0 -0
  43. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/docs.py +0 -0
  44. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/dotenv/__init__.py +0 -0
  45. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/frond/FROND.md +0 -0
  46. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/frond/__init__.py +0 -0
  47. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/frond/engine.py +0 -0
  48. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/gallery/auth/meta.json +0 -0
  49. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
  50. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/gallery/database/meta.json +0 -0
  51. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
  52. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/gallery/error-overlay/meta.json +0 -0
  53. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
  54. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/gallery/orm/meta.json +0 -0
  55. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
  56. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
  57. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/gallery/queue/meta.json +0 -0
  58. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
  59. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/gallery/rest-api/meta.json +0 -0
  60. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
  61. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/gallery/templates/meta.json +0 -0
  62. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
  63. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
  64. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/graphql/__init__.py +0 -0
  65. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/i18n/__init__.py +0 -0
  66. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/mcp/__init__.py +0 -0
  67. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/mcp/protocol.py +0 -0
  68. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/mcp/tools.py +0 -0
  69. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/messenger/__init__.py +0 -0
  70. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/migration/__init__.py +0 -0
  71. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/migration/runner.py +0 -0
  72. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/orm/__init__.py +0 -0
  73. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/orm/fields.py +0 -0
  74. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/orm/model.py +0 -0
  75. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/public/__feedback/widget.js +0 -0
  76. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/public/css/tina4.css +0 -0
  77. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/public/css/tina4.min.css +0 -0
  78. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/public/favicon.ico +0 -0
  79. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/public/images/logo.svg +0 -0
  80. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
  81. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/public/js/frond.js +0 -0
  82. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/public/js/frond.min.js +0 -0
  83. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/public/js/tina4-dev-admin.js +0 -0
  84. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
  85. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/public/js/tina4.min.js +0 -0
  86. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/public/js/tina4js.min.js +0 -0
  87. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/public/swagger/index.html +0 -0
  88. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  89. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/query_builder/__init__.py +0 -0
  90. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/queue/__init__.py +0 -0
  91. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/queue/job.py +0 -0
  92. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/queue/kafka_backend.py +0 -0
  93. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/queue/lite_backend.py +0 -0
  94. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/queue/mongo_backend.py +0 -0
  95. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/queue/rabbitmq_backend.py +0 -0
  96. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/queue_backends/__init__.py +0 -0
  97. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/queue_backends/kafka_backend.py +0 -0
  98. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/queue_backends/mongo_backend.py +0 -0
  99. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
  100. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/scss/__init__.py +0 -0
  101. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/scss/tina4css/_alerts.scss +0 -0
  102. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/scss/tina4css/_badges.scss +0 -0
  103. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/scss/tina4css/_buttons.scss +0 -0
  104. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/scss/tina4css/_cards.scss +0 -0
  105. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/scss/tina4css/_forms.scss +0 -0
  106. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/scss/tina4css/_grid.scss +0 -0
  107. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/scss/tina4css/_modals.scss +0 -0
  108. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/scss/tina4css/_nav.scss +0 -0
  109. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/scss/tina4css/_reset.scss +0 -0
  110. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/scss/tina4css/_tables.scss +0 -0
  111. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/scss/tina4css/_typography.scss +0 -0
  112. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/scss/tina4css/_utilities.scss +0 -0
  113. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/scss/tina4css/_variables.scss +0 -0
  114. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/scss/tina4css/base.scss +0 -0
  115. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/scss/tina4css/colors.scss +0 -0
  116. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/scss/tina4css/tina4.scss +0 -0
  117. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/seeder/__init__.py +0 -0
  118. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/session/__init__.py +0 -0
  119. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/session_handlers/__init__.py +0 -0
  120. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/session_handlers/mongodb_handler.py +0 -0
  121. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/session_handlers/redis_handler.py +0 -0
  122. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/session_handlers/valkey_handler.py +0 -0
  123. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/swagger/__init__.py +0 -0
  124. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/templates/components/crud.twig +0 -0
  125. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
  126. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
  127. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/templates/docker/python/Dockerfile +0 -0
  128. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/templates/docker/uv/Dockerfile +0 -0
  129. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/templates/errors/302.twig +0 -0
  130. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/templates/errors/401.twig +0 -0
  131. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/templates/errors/403.twig +0 -0
  132. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/templates/errors/404.twig +0 -0
  133. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/templates/errors/500.twig +0 -0
  134. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/templates/errors/502.twig +0 -0
  135. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/templates/errors/503.twig +0 -0
  136. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/templates/errors/base.twig +0 -0
  137. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/templates/frontend/README.md +0 -0
  138. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/templates/readme.md +0 -0
  139. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/test/__init__.py +0 -0
  140. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/test_client/__init__.py +0 -0
  141. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
  142. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
  143. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  144. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  145. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
  146. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
  147. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  148. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
  149. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
  150. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
  151. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
  152. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
  153. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/validator/__init__.py +0 -0
  154. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/websocket/__init__.py +0 -0
  155. {tina4_python-3.13.0 → tina4_python-3.13.1}/tina4_python/websocket/backplane.py +0 -0
  156. {tina4_python-3.13.0 → tina4_python-3.13.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.13.0
3
+ Version: 3.13.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.13.0"
3
+ version = "3.13.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"}
@@ -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.13.0"
11
+ __version__ = "3.13.1"
12
12
 
13
13
  # ── Route decorators ──
14
14
  from tina4_python.core.router import ( # noqa: E402, F401
@@ -177,6 +177,72 @@ class ServiceContext:
177
177
  self.log = _get_log()
178
178
 
179
179
 
180
+ # ---------------------------------------------------------------------------
181
+ # Service base class (3.13.1) — class-based pattern with run/stop/should_stop
182
+ # ---------------------------------------------------------------------------
183
+
184
+
185
+ class Service:
186
+ """Base class for class-based background services managed by ServiceRunner.
187
+
188
+ Cross-framework parity with PHP ``Tina4\\Service``, Ruby ``Tina4::Service``
189
+ and Node ``Tina4Service``. Subclasses implement ``run()`` (the work
190
+ loop); the base provides ``stop()`` and ``should_stop()`` helpers
191
+ backed by an internal flag.
192
+
193
+ class EmailQueueWorker(Service):
194
+ def run(self):
195
+ while not self.should_stop():
196
+ job = queue.pop()
197
+ if job is None:
198
+ time.sleep(1); continue
199
+ process(job)
200
+ job.complete()
201
+
202
+ runner = ServiceRunner()
203
+ runner.register_service("emails", EmailQueueWorker())
204
+ runner.start()
205
+
206
+ Function-style services without state can still register directly via
207
+ ``runner.register('name', callable, interval=N)``. The two patterns
208
+ coexist.
209
+ """
210
+
211
+ def __init__(self):
212
+ self._running = True
213
+
214
+ def run(self) -> None:
215
+ """Main work loop — subclasses MUST override."""
216
+ raise NotImplementedError(
217
+ f"{self.__class__.__name__}.run() must be implemented by the subclass"
218
+ )
219
+
220
+ def stop(self) -> None:
221
+ """Signal this service to stop. Override for custom shutdown behaviour
222
+ but always call ``super().stop()`` so the internal flag gets set —
223
+ ``should_stop()`` reads from it.
224
+ """
225
+ self._running = False
226
+
227
+ def should_stop(self) -> bool:
228
+ """Returns True once ``stop()`` has been called. Use inside ``run()``
229
+ loops as the exit condition::
230
+
231
+ def run(self):
232
+ while not self.should_stop():
233
+ # do work
234
+ """
235
+ return not self._running
236
+
237
+ def __call__(self, *args, **kwargs):
238
+ """Make the Service instance callable so it can be passed directly
239
+ to ``ServiceRunner.register('name', service_instance, daemon=True)``.
240
+ Cleanly dispatches to ``run()`` while ignoring the runner's
241
+ ServiceContext argument (Service subclasses manage their own state).
242
+ """
243
+ return self.run()
244
+
245
+
180
246
  # ---------------------------------------------------------------------------
181
247
  # ServiceRunner
182
248
  # ---------------------------------------------------------------------------
@@ -221,6 +287,43 @@ class ServiceRunner:
221
287
  "started_at": None,
222
288
  })
223
289
 
290
+ def register_service(self, name: str, service: "Service", max_retries: int = 3):
291
+ """Register a class-based service (:class:`Service` instance) by name.
292
+
293
+ Wraps the Service's ``run()`` method as the handler callable that
294
+ ``start()`` invokes. Defaults to ``daemon=True`` because Service
295
+ subclasses manage their own loop inside ``run()``.
296
+
297
+ class EmailWorker(Service):
298
+ def run(self):
299
+ while not self.should_stop():
300
+ process_one()
301
+
302
+ runner.register_service("emails", EmailWorker())
303
+ runner.start()
304
+
305
+ Cross-framework parity with PHP ``ServiceRunner::registerService``,
306
+ Ruby ``ServiceRunner.register_service``, Node
307
+ ``ServiceRunner.registerService``.
308
+ """
309
+ if not isinstance(service, Service):
310
+ raise TypeError(
311
+ f"service must be a Service instance, got {type(service).__name__}"
312
+ )
313
+ self.services.append({
314
+ "name": name,
315
+ "handler": service, # Service instances are callable (see __call__)
316
+ "interval": 0,
317
+ "cron": None,
318
+ "daemon": True, # Service subclasses manage their own loop
319
+ "max_retries": max_retries,
320
+ "retries": 0,
321
+ "running": False,
322
+ "last_run": None,
323
+ "started_at": None,
324
+ "instance": service, # keep reference so stop() can route to service.stop()
325
+ })
326
+
224
327
  # -- Lifecycle ----------------------------------------------------------
225
328
 
226
329
  def start(self):
File without changes
File without changes