tina4-python 0.2.193__tar.gz → 0.2.194__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 (66) hide show
  1. {tina4_python-0.2.193 → tina4_python-0.2.194}/PKG-INFO +1 -1
  2. {tina4_python-0.2.193 → tina4_python-0.2.194}/pyproject.toml +1 -1
  3. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/Router.py +11 -4
  4. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/Session.py +89 -1
  5. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/Webserver.py +6 -8
  6. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/__init__.py +8 -12
  7. {tina4_python-0.2.193 → tina4_python-0.2.194}/.gitignore +0 -0
  8. {tina4_python-0.2.193 → tina4_python-0.2.194}/README.md +0 -0
  9. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/Api.py +0 -0
  10. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/Auth.py +0 -0
  11. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/CLAUDE.md +0 -0
  12. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/CRUD.py +0 -0
  13. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/Constant.py +0 -0
  14. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/Database.py +0 -0
  15. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/DatabaseResult.py +0 -0
  16. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/DatabaseTypes.py +0 -0
  17. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/Debug.py +0 -0
  18. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/DevReload.py +0 -0
  19. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/Env.py +0 -0
  20. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/FieldTypes.py +0 -0
  21. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/HtmlElement.py +0 -0
  22. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/Localization.py +0 -0
  23. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/Messages.py +0 -0
  24. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/MiddleWare.py +0 -0
  25. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/Migration.py +0 -0
  26. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/ORM.py +0 -0
  27. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/Queue.py +0 -0
  28. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/Request.py +0 -0
  29. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/Response.py +0 -0
  30. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/ShellColors.py +0 -0
  31. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/Swagger.py +0 -0
  32. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/Template.py +0 -0
  33. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/Testing.py +0 -0
  34. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/WSDL.py +0 -0
  35. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/Websocket.py +0 -0
  36. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/cli.py +0 -0
  37. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/messages.pot +0 -0
  38. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/public/css/readme.md +0 -0
  39. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/public/favicon.ico +0 -0
  40. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/public/images/403.png +0 -0
  41. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/public/images/404.png +0 -0
  42. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/public/images/500.png +0 -0
  43. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/public/images/logo.png +0 -0
  44. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/public/images/readme.md +0 -0
  45. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/public/js/readme.md +0 -0
  46. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/public/js/reconnecting-websocket.js +0 -0
  47. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/public/js/tina4helper.js +0 -0
  48. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/public/swagger/index.html +0 -0
  49. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  50. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/templates/components/crud.twig +0 -0
  51. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/templates/errors/403.twig +0 -0
  52. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/templates/errors/404.twig +0 -0
  53. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/templates/errors/500.twig +0 -0
  54. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/templates/readme.md +0 -0
  55. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
  56. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
  57. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  58. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  59. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
  60. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
  61. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  62. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
  63. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
  64. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
  65. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
  66. {tina4_python-0.2.193 → tina4_python-0.2.194}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tina4-python
3
- Version: 0.2.193
3
+ Version: 0.2.194
4
4
  Summary: Tina4Python - This is not another framework for Python
5
5
  Author-email: Andre van Zuydam <andrevanzuydam@gmail.com>
6
6
  Requires-Python: <4.0,>=3.12
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tina4-python"
3
- version = "0.2.193"
3
+ version = "0.2.194"
4
4
  description = "Tina4Python - This is not another framework for Python"
5
5
  authors = [
6
6
  {name = "Andre van Zuydam",email = "andrevanzuydam@gmail.com"}
@@ -354,6 +354,7 @@ class Router:
354
354
  tina4_python.tina4_current_request = {"url": url, "headers": headers}
355
355
 
356
356
  validated = False
357
+ has_form_token = False
357
358
  # we can add other methods later but right now we validate gets, posts and other risky methods
358
359
  if method in [Constant.TINA4_GET, Constant.TINA4_POST, Constant.TINA4_PUT, Constant.TINA4_PATCH,
359
360
  Constant.TINA4_DELETE]:
@@ -377,11 +378,13 @@ class Router:
377
378
  token = request["params"]["formToken"]
378
379
  if tina4_python.tina4_auth.valid(token):
379
380
  validated = True
381
+ has_form_token = True
380
382
 
381
383
  if request["body"] is not None and "formToken" in request["body"]:
382
384
  token = request["body"]["formToken"]
383
385
  if tina4_python.tina4_auth.valid(token):
384
386
  validated = True
387
+ has_form_token = True
385
388
 
386
389
  if request["body"] is not None and "formToken" in request["body"]:
387
390
  request["params"]["formToken"] = request["body"]["formToken"]
@@ -521,7 +524,8 @@ class Router:
521
524
  return Response(result.content, result.http_code, result.content_type)
522
525
 
523
526
  if result is not None:
524
- result.headers["FreshToken"] = tina4_python.tina4_auth.get_token({"path": url})
527
+ if has_form_token:
528
+ result.headers["FreshToken"] = tina4_python.tina4_auth.get_token({"path": url})
525
529
  if "cache" in route and route["cache"] is not None:
526
530
  if not route["cache"]["cached"]:
527
531
  result.headers["Cache-Control"] = "max-age=1, must-revalidate"
@@ -542,7 +546,9 @@ class Router:
542
546
  if result is None and route_matched:
543
547
  output = buffer.getvalue()
544
548
  if output:
545
- fresh_headers = {"FreshToken": tina4_python.tina4_auth.get_token({"path": url})}
549
+ fresh_headers = {}
550
+ if has_form_token:
551
+ fresh_headers["FreshToken"] = tina4_python.tina4_auth.get_token({"path": url})
546
552
  try:
547
553
  return Response(json.loads(output), Constant.HTTP_OK, Constant.APPLICATION_JSON, fresh_headers)
548
554
  except Exception:
@@ -566,10 +572,11 @@ class Router:
566
572
  )
567
573
 
568
574
  twig_headers = {
569
- "FreshToken": tina4_python.tina4_auth.get_token({"path": url}),
570
575
  "Cache-Control": "max-age=-1, public",
571
576
  "Pragma": "no-cache"
572
577
  }
578
+ if has_form_token:
579
+ twig_headers["FreshToken"] = tina4_python.tina4_auth.get_token({"path": url})
573
580
  content = Template.render_twig_template(twig_file, {"request": tina4_python.tina4_current_request})
574
581
  if content != "":
575
582
  return Response(content, Constant.HTTP_OK, Constant.TEXT_HTML, twig_headers)
@@ -579,7 +586,7 @@ class Router:
579
586
  "errors/404.twig", {"server": {"url": url}})
580
587
  return Response(content, Constant.HTTP_NOT_FOUND, Constant.TEXT_HTML)
581
588
 
582
- result.headers["FreshToken"] = tina4_python.tina4_auth.get_token({"path": url})
589
+ # FreshToken already set on line 524 inside the route loop
583
590
  return result
584
591
 
585
592
  @staticmethod
@@ -30,7 +30,7 @@ Typical usage inside a route handler::
30
30
  """
31
31
 
32
32
  __all__ = [
33
- "Session", "SessionHandler", "SessionFileHandler",
33
+ "Session", "LazySession", "SessionHandler", "SessionFileHandler",
34
34
  "SessionRedisHandler", "SessionValkeyHandler",
35
35
  "SessionMongoHandler",
36
36
  ]
@@ -685,3 +685,91 @@ class Session:
685
685
  for key, value in self.session_values.items():
686
686
  if key != "expires":
687
687
  yield key, value
688
+
689
+
690
+ class LazySession:
691
+ """Proxy that defers expensive Session creation until first use.
692
+
693
+ Creating and starting a ``Session`` requires RSA key signing (~1ms
694
+ per call) which dominates request latency for API routes that never
695
+ touch the session. ``LazySession`` wraps the session creation
696
+ parameters and only instantiates the real ``Session`` when a method
697
+ like ``set()``, ``get()``, or ``load()`` is called.
698
+
699
+ The ``activated`` property lets the response builder know whether
700
+ to emit a ``Set-Cookie`` header.
701
+ """
702
+
703
+ def __init__(self, name, path, handler, cookies):
704
+ self._name = name
705
+ self._path = path
706
+ self._handler = handler
707
+ self._cookies = cookies
708
+ self._real = None
709
+
710
+ @property
711
+ def activated(self):
712
+ """True if the real Session has been created."""
713
+ return self._real is not None
714
+
715
+ def _activate(self):
716
+ """Create and start/load the real Session on first access."""
717
+ if self._real is None:
718
+ self._real = Session(self._name, self._path, self._handler)
719
+ if self._name in self._cookies:
720
+ self._real.load(self._cookies[self._name])
721
+ else:
722
+ self._cookies[self._name] = self._real.start()
723
+ return self._real
724
+
725
+ # --- Forwarded Session API ---
726
+
727
+ @property
728
+ def session_name(self):
729
+ return self._name
730
+
731
+ @property
732
+ def session_values(self):
733
+ if self._real is None:
734
+ return {}
735
+ return self._real.session_values
736
+
737
+ @property
738
+ def session_hash(self):
739
+ if self._real is None:
740
+ return ""
741
+ return self._real.session_hash
742
+
743
+ @session_hash.setter
744
+ def session_hash(self, value):
745
+ self._activate().session_hash = value
746
+
747
+ def start(self, session_hash=None):
748
+ return self._activate().start(session_hash)
749
+
750
+ def load(self, session_hash):
751
+ return self._activate().load(session_hash)
752
+
753
+ def set(self, key, value):
754
+ return self._activate().set(key, value)
755
+
756
+ def get(self, key):
757
+ return self._activate().get(key)
758
+
759
+ def unset(self, key):
760
+ return self._activate().unset(key)
761
+
762
+ def close(self):
763
+ if self._real is not None:
764
+ return self._real.close()
765
+ return True
766
+
767
+ def save(self):
768
+ if self._real is not None:
769
+ return self._real.save()
770
+ return True
771
+
772
+ def __iter__(self):
773
+ if self._real is not None:
774
+ return iter(self._real)
775
+ return iter([])
@@ -434,9 +434,10 @@ class Webserver:
434
434
  self.send_header("Content-Type", response.content_type or "text/html", headers)
435
435
  await self.send_basic_headers(headers)
436
436
 
437
- # Preserve session cookie
437
+ # Preserve session cookie (only if session was used)
438
438
  session_name = os.getenv("TINA4_SESSION", "PY_SESS")
439
- if session_name in self.cookies:
439
+ session_activated = not hasattr(self.session, 'activated') or self.session.activated
440
+ if session_activated and session_name in self.cookies:
440
441
  self.send_header("Set-Cookie", f"{session_name}={self.cookies[session_name]}", headers)
441
442
 
442
443
  # Custom headers from route
@@ -585,14 +586,11 @@ class Webserver:
585
586
  name, val = part.strip().split("=", 1)
586
587
  self.cookies[name] = val
587
588
 
589
+ from tina4_python.Session import LazySession
588
590
  session_name = os.getenv("TINA4_SESSION", "PY_SESS")
589
591
  session_folder = os.getenv("TINA4_SESSION_FOLDER", os.path.join(tina4_python.root_path, "sessions"))
590
- self.session = Session(session_name, session_folder)
591
-
592
- if session_name in self.cookies:
593
- self.session.load(self.cookies[session_name])
594
- else:
595
- self.cookies[session_name] = self.session.start()
592
+ session_handler = os.getenv("TINA4_SESSION_HANDLER", "SessionFileHandler")
593
+ self.session = LazySession(session_name, session_folder, session_handler, self.cookies)
596
594
 
597
595
  # ------------------------------------------------------------------
598
596
  # Route or WebSocket
@@ -21,8 +21,6 @@ Just `pip install tina4-python` and run your project – everything just works.
21
21
  """
22
22
  import asyncio
23
23
  import os
24
- if os.getenv("TINA4_DEBUG_LEVEL", "") == "":
25
- os.environ["TINA4_DEBUG_LEVEL"] = "DEBUG"
26
24
 
27
25
  import shutil
28
26
  import importlib
@@ -46,7 +44,7 @@ from tina4_python.Auth import Auth
46
44
  from tina4_python.Debug import Debug
47
45
  from tina4_python.Debug import setup_logging
48
46
  from tina4_python.ShellColors import ShellColors
49
- from tina4_python.Session import Session
47
+ from tina4_python.Session import Session, LazySession
50
48
  from tina4_python.HtmlElement import add_html_helpers
51
49
  from tina4_python import ShellColors
52
50
  from tina4_python.Constant import TINA4_LOG_INFO, TINA4_LOG_ALL, TINA4_LOG_DEBUG
@@ -428,15 +426,13 @@ async def app(scope, receive, send):
428
426
 
429
427
  webserver.cookies = cookie_list
430
428
 
431
- webserver.session = Session(os.getenv("TINA4_SESSION", "PY_SESS"),
432
- os.getenv("TINA4_SESSION_FOLDER", root_path + os.sep + "sessions"),
433
- os.getenv("TINA4_SESSION_HANDLER", "SessionFileHandler")
434
- )
435
-
436
- if os.getenv("TINA4_SESSION", "PY_SESS") in webserver.cookies:
437
- webserver.session.load(webserver.cookies[os.getenv("TINA4_SESSION", "PY_SESS")])
438
- else:
439
- webserver.cookies[os.getenv("TINA4_SESSION", "PY_SESS")] = webserver.session.start()
429
+ session_name = os.getenv("TINA4_SESSION", "PY_SESS")
430
+ webserver.session = LazySession(
431
+ session_name,
432
+ os.getenv("TINA4_SESSION_FOLDER", root_path + os.sep + "sessions"),
433
+ os.getenv("TINA4_SESSION_HANDLER", "SessionFileHandler"),
434
+ webserver.cookies,
435
+ )
440
436
 
441
437
  tina4_response, tina4_headers = await webserver.get_response(webserver.method, scope=scope, reader=receive, writer=send, asgi_response=True)
442
438
 
File without changes