pyweber 1.6.0.dev1__tar.gz → 1.6.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 (123) hide show
  1. {pyweber-1.6.0.dev1/pyweber.egg-info → pyweber-1.6.1}/PKG-INFO +14 -5
  2. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/README.md +11 -3
  3. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyproject.toml +5 -3
  4. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/__init__.py +18 -1
  5. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/connection/session.py +14 -3
  6. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/connection/websocket.py +240 -122
  7. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/core/element.py +21 -4
  8. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/core/events.py +70 -7
  9. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/core/template.py +68 -1
  10. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/core/window.py +5 -2
  11. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/models/element.py +11 -8
  12. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/models/ws_message.py +18 -3
  13. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/pyweber/pyweber.py +17 -3
  14. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/static/config.toml +3 -0
  15. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/static/js.js +78 -4
  16. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/utils/security.py +78 -0
  17. {pyweber-1.6.0.dev1 → pyweber-1.6.1/pyweber.egg-info}/PKG-INFO +14 -5
  18. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/LICENSE +0 -0
  19. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/MANIFEST.in +0 -0
  20. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/NOTICE +0 -0
  21. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/admin/index.html +0 -0
  22. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/admin/src/script.js +0 -0
  23. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/admin/src/style.css +0 -0
  24. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/auth/__init__.py +0 -0
  25. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/auth/decorators.py +0 -0
  26. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/auth/passwords.py +0 -0
  27. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/auth/rbac.py +0 -0
  28. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/auth/session.py +0 -0
  29. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/cli/__init__.py +0 -0
  30. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/cli/commands.py +0 -0
  31. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/components/__init__.py +0 -0
  32. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/components/form.py +0 -0
  33. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/components/general.py +0 -0
  34. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/components/input.py +0 -0
  35. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/config/__init__.py +0 -0
  36. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/config/config.py +0 -0
  37. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/connection/__init__.py +0 -0
  38. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/connection/http.py +0 -0
  39. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/connection/reload.py +0 -0
  40. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/connection/selector.py +0 -0
  41. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/core/__init__.py +0 -0
  42. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/core/html_parser.py +0 -0
  43. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/db/__init__.py +0 -0
  44. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/db/cli.py +0 -0
  45. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/db/config.py +0 -0
  46. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/db/engine.py +0 -0
  47. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/db/model.py +0 -0
  48. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/db/query.py +0 -0
  49. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/db/session.py +0 -0
  50. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/models/__init__.py +0 -0
  51. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/models/context.py +0 -0
  52. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/models/cookies.py +0 -0
  53. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/models/create_app.py +0 -0
  54. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/models/dom_merge.py +0 -0
  55. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/models/error_pages.py +0 -0
  56. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/models/field.py +0 -0
  57. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/models/field_storage.py +0 -0
  58. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/models/file.py +0 -0
  59. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/models/file_stream.py +0 -0
  60. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/models/handoff.py +0 -0
  61. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/models/headers.py +0 -0
  62. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/models/middleware.py +0 -0
  63. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/models/openapi.py +0 -0
  64. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/models/rate_limit.py +0 -0
  65. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/models/request.py +0 -0
  66. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/models/response.py +0 -0
  67. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/models/routes.py +0 -0
  68. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/models/run.py +0 -0
  69. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/models/security.py +0 -0
  70. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/models/stream_stats.py +0 -0
  71. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/models/strem_stats.py +0 -0
  72. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/models/task_manager.py +0 -0
  73. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/models/template_diff.py +0 -0
  74. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/pyweber/__init__.py +0 -0
  75. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/services/__init__.py +0 -0
  76. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/services/openapi_setup.py +0 -0
  77. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/services/response_pipeline.py +0 -0
  78. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/services/static_files.py +0 -0
  79. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/services/template_service.py +0 -0
  80. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/session_store/__init__.py +0 -0
  81. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/static/.gitignore +0 -0
  82. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/static/css.css +0 -0
  83. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/static/docs.html +0 -0
  84. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/static/favicon/android-chrome-192x192.png +0 -0
  85. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/static/favicon/android-chrome-512x512.png +0 -0
  86. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/static/favicon/apple-touch-icon.png +0 -0
  87. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/static/favicon/favicon-16x16.png +0 -0
  88. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/static/favicon/favicon-32x32.png +0 -0
  89. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/static/favicon/favicon.ico +0 -0
  90. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/static/favicon/pyweber.png +0 -0
  91. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/static/favicon/site.webmanifest +0 -0
  92. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/static/handlers.js +0 -0
  93. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/static/html.html +0 -0
  94. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/static/html401.html +0 -0
  95. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/static/html404.html +0 -0
  96. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/static/html500.html +0 -0
  97. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/static/loading.html +0 -0
  98. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/static/main.py +0 -0
  99. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/static/pyweber.css +0 -0
  100. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/static/update.py +0 -0
  101. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/testing/__init__.py +0 -0
  102. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/testing/client.py +0 -0
  103. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/utils/__init__.py +0 -0
  104. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/utils/async_utils.py +0 -0
  105. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/utils/deprecation.py +0 -0
  106. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/utils/exceptions.py +0 -0
  107. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/utils/icons.py +0 -0
  108. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/utils/loads.py +0 -0
  109. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/utils/mime.py +0 -0
  110. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/utils/types.py +0 -0
  111. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber/utils/utils.py +0 -0
  112. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber.egg-info/SOURCES.txt +0 -0
  113. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber.egg-info/dependency_links.txt +0 -0
  114. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber.egg-info/entry_points.txt +0 -0
  115. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber.egg-info/requires.txt +0 -0
  116. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/pyweber.egg-info/top_level.txt +0 -0
  117. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/setup.cfg +0 -0
  118. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/tests/test_config.py +0 -0
  119. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/tests/test_context.py +0 -0
  120. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/tests/test_cookies.py +0 -0
  121. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/tests/test_public_api.py +0 -0
  122. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/tests/test_response.py +0 -0
  123. {pyweber-1.6.0.dev1 → pyweber-1.6.1}/tests/test_template_diff.py +0 -0
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyweber
3
- Version: 1.6.0.dev1
3
+ Version: 1.6.1
4
4
  Summary: A lightweight Python framework for building and managing web applications.
5
- Author-email: DevPythonMZ <pypi.dev@gmail.com>
5
+ Author-email: Pyweber Limitada <pypi.dev@gmail.com>
6
6
  License-Expression: Apache-2.0
7
7
  Project-URL: Documentation, https://pyweber.dev/
8
8
  Project-URL: Source, https://github.com/pyweber/pyweber
@@ -10,6 +10,7 @@ Project-URL: Tracker, https://github.com/pyweber/pyweber/issues
10
10
  Project-URL: PyPI, https://pypi.org/project/pyweber/
11
11
  Project-URL: YouTube, https://youtube.com/@devpythonMZ
12
12
  Project-URL: Examples, https://github.com/pyweber-examples
13
+ Project-URL: Security, https://pyweber.dev/guides/supported-versions/
13
14
  Requires-Python: >=3.10
14
15
  Description-Content-Type: text/markdown
15
16
  License-File: LICENSE
@@ -55,17 +56,25 @@ Pyweber is a lightweight Python web framework designed to create dynamic, reacti
55
56
  - **Intuitive DOM Manipulation**: Query and modify elements with familiar selectors
56
57
  - **Event-Driven Programming**: Handle user interactions with Python event handlers
57
58
  - **WebSocket Integration**: Real-time communication between client and server
58
- - **Template Handoff (1.3.0)**: WebSocket sessions reuse the HTTP-rendered template — handlers are not executed twice on connect
59
+ - **Template Handoff (1.3.0+)**: WebSocket sessions reuse the HTTP-rendered template — handlers are not executed twice on connect
60
+ - **Identity-preserving sync (1.6.0+)**: `self` / subclass element refs stay valid after WS connect
61
+ - **Optional ORM & Redis sessions (1.6.0+)**: `pyweber[db]`, `pyweber[redis]`
62
+ - **Auth & RBAC (1.5+)**: signed login cookie, roles/permissions
59
63
  - **Minimal Configuration**: Get started quickly with sensible defaults
60
64
 
61
65
  ## Quick Start
62
66
 
63
67
  ### Installation
64
68
 
69
+ !!! danger "Avoid ≤ 1.3.1"
70
+ Older lines are **not recommended** for production. Use a current release:
71
+
65
72
  ```bash
66
- pip install pyweber
73
+ pip install -U 'pyweber>=1.6'
67
74
  ```
68
75
 
76
+ See [Supported versions](https://pyweber.dev/guides/supported-versions/) (docs) for yank / security notes. `pyproject.toml` cannot warn when someone pins an old wheel — that requires [PyPI yanking](https://docs.pypi.org/project-management/yanking/).
77
+
69
78
  ### Create a Project
70
79
 
71
80
  ```bash
@@ -262,7 +271,7 @@ pyweber add-section --section-name database
262
271
  pyweber --update
263
272
  ```
264
273
 
265
- Visit [Pyweber Docs](https://pyweber.dev/) for complete documentation. See [CHANGELOG.md](CHANGELOG.md) for release notes (latest: **1.3.0** — Template Handoff, 405 fixes, recursion tracking).
274
+ Visit [Pyweber Docs](https://pyweber.dev/) for complete documentation. See [CHANGELOG.md](CHANGELOG.md) for release notes (latest: **1.6.0** — optional ORM/Redis, identity-preserving WS sync, reactive event fixes).
266
275
 
267
276
  ## License
268
277
 
@@ -14,17 +14,25 @@ Pyweber is a lightweight Python web framework designed to create dynamic, reacti
14
14
  - **Intuitive DOM Manipulation**: Query and modify elements with familiar selectors
15
15
  - **Event-Driven Programming**: Handle user interactions with Python event handlers
16
16
  - **WebSocket Integration**: Real-time communication between client and server
17
- - **Template Handoff (1.3.0)**: WebSocket sessions reuse the HTTP-rendered template — handlers are not executed twice on connect
17
+ - **Template Handoff (1.3.0+)**: WebSocket sessions reuse the HTTP-rendered template — handlers are not executed twice on connect
18
+ - **Identity-preserving sync (1.6.0+)**: `self` / subclass element refs stay valid after WS connect
19
+ - **Optional ORM & Redis sessions (1.6.0+)**: `pyweber[db]`, `pyweber[redis]`
20
+ - **Auth & RBAC (1.5+)**: signed login cookie, roles/permissions
18
21
  - **Minimal Configuration**: Get started quickly with sensible defaults
19
22
 
20
23
  ## Quick Start
21
24
 
22
25
  ### Installation
23
26
 
27
+ !!! danger "Avoid ≤ 1.3.1"
28
+ Older lines are **not recommended** for production. Use a current release:
29
+
24
30
  ```bash
25
- pip install pyweber
31
+ pip install -U 'pyweber>=1.6'
26
32
  ```
27
33
 
34
+ See [Supported versions](https://pyweber.dev/guides/supported-versions/) (docs) for yank / security notes. `pyproject.toml` cannot warn when someone pins an old wheel — that requires [PyPI yanking](https://docs.pypi.org/project-management/yanking/).
35
+
28
36
  ### Create a Project
29
37
 
30
38
  ```bash
@@ -221,7 +229,7 @@ pyweber add-section --section-name database
221
229
  pyweber --update
222
230
  ```
223
231
 
224
- Visit [Pyweber Docs](https://pyweber.dev/) for complete documentation. See [CHANGELOG.md](CHANGELOG.md) for release notes (latest: **1.3.0** — Template Handoff, 405 fixes, recursion tracking).
232
+ Visit [Pyweber Docs](https://pyweber.dev/) for complete documentation. See [CHANGELOG.md](CHANGELOG.md) for release notes (latest: **1.6.0** — optional ORM/Redis, identity-preserving WS sync, reactive event fixes).
225
233
 
226
234
  ## License
227
235
 
@@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pyweber"
7
- version = "1.6.0.dev1"
7
+ version = "1.6.1"
8
8
  description = "A lightweight Python framework for building and managing web applications."
9
9
  readme = "README.md"
10
- authors = [{ name = "DevPythonMZ", email = "pypi.dev@gmail.com" }]
10
+ authors = [{ name = "Pyweber Limitada", email = "pypi.dev@gmail.com" }]
11
11
  license = "Apache-2.0"
12
12
  requires-python = ">=3.10"
13
13
  dependencies = ["watchdog", "toml", "questionary", "qrcode[pil]"]
@@ -18,6 +18,8 @@ Tracker = "https://github.com/pyweber/pyweber/issues"
18
18
  PyPI = "https://pypi.org/project/pyweber/"
19
19
  YouTube = "https://youtube.com/@devpythonMZ"
20
20
  Examples = "https://github.com/pyweber-examples"
21
+ # Supported versions / security policy (≤1.3.1 not recommended; yank on PyPI — not via this file)
22
+ Security = "https://pyweber.dev/guides/supported-versions/"
21
23
 
22
24
  [project.scripts]
23
25
  pyweber = "pyweber.cli.commands:app"
@@ -43,5 +45,5 @@ pyweber = ["static/**/*", "admin/**/*"]
43
45
  omit = ["pyweber/cli/commands.py"]
44
46
 
45
47
  [tool.coverage.report]
46
- fail_under = 86
48
+ fail_under = 88
47
49
  show_missing = true
@@ -105,7 +105,6 @@ from .utils.types import (
105
105
  EventType,
106
106
  HTMLTag,
107
107
  HTTPStatusCode,
108
- JWTAlgorithms,
109
108
  NonSelfClosingHTMLTags,
110
109
  WebSocketStatusCode,
111
110
  WindowEventType,
@@ -305,3 +304,21 @@ __all__ = [
305
304
  'Label',
306
305
  'TextArea',
307
306
  ]
307
+
308
+
309
+ def __getattr__(name: str):
310
+ if name == 'JWTAlgorithms':
311
+ from pyweber.utils.deprecation import warn_deprecated
312
+ from pyweber.utils.types import JWTAlgorithms as _JWTAlgorithms
313
+
314
+ warn_deprecated(
315
+ 'pyweber.JWTAlgorithms',
316
+ alternative=(
317
+ 'pyjwt (or similar) for encode/decode/verify; '
318
+ 'pyweber.auth for session login. '
319
+ 'JWTAlgorithms remains an algorithm-name enum only until 2.0'
320
+ ),
321
+ removal='2.0',
322
+ )
323
+ return _JWTAlgorithms
324
+ raise AttributeError(f'module {__name__!r} has no attribute {name!r}')
@@ -21,7 +21,12 @@ class Session:
21
21
  self.session_id = session_id
22
22
  self.create_at = time()
23
23
  self.current_route = current_route
24
- self.old_template = template
24
+ # Must be a distinct snapshot for TemplateDiff; aliasing template makes
25
+ # every e.update() produce an empty diff (mutations visible on both sides).
26
+ try:
27
+ self.old_template = template.clone()
28
+ except Exception:
29
+ self.old_template = None
25
30
 
26
31
 
27
32
  class SessionManager:
@@ -57,8 +62,14 @@ class SessionManager:
57
62
  self._schedule_persist(session)
58
63
 
59
64
  def remove_session(self, session_id: str):
60
- if session_id in self.sessions:
61
- del self.sessions[session_id]
65
+ session = self.sessions.pop(session_id, None)
66
+ if session is not None:
67
+ try:
68
+ from pyweber.core.events import cleanup_template_events
69
+ cleanup_template_events(getattr(session, 'template', None))
70
+ cleanup_template_events(getattr(session, 'old_template', None))
71
+ except Exception as exc:
72
+ logger.debug('EventBook cleanup skipped: %s', exc)
62
73
  self._schedule_delete(session_id)
63
74
 
64
75
  def get_session(self, session_id: str):