databricks-tellr-app 0.3.13.dev3__tar.gz → 0.3.13.dev4__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 (157) hide show
  1. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/PKG-INFO +8 -8
  2. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/databricks_tellr_app/run.py +12 -0
  3. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/databricks_tellr_app.egg-info/PKG-INFO +8 -8
  4. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/databricks_tellr_app.egg-info/SOURCES.txt +2 -14
  5. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/databricks_tellr_app.egg-info/requires.txt +7 -7
  6. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/pyproject.toml +10 -8
  7. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/services/export_job_queue.py +23 -10
  8. databricks_tellr_app-0.3.13.dev4/src/core/encryption.py +125 -0
  9. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/lakebase.py +0 -114
  10. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/__init__.py +2 -0
  11. databricks_tellr_app-0.3.13.dev4/src/database/models/encryption_key.py +30 -0
  12. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/google_slides_prompts_defaults.py +18 -28
  13. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/html_to_google_slides.py +185 -246
  14. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/html_to_pptx.py +453 -73
  15. databricks_tellr_app-0.3.13.dev3/src/core/encryption.py +0 -86
  16. databricks_tellr_app-0.3.13.dev3/src/services/converter_jail/__init__.py +0 -10
  17. databricks_tellr_app-0.3.13.dev3/src/services/converter_jail/ast_guard.py +0 -58
  18. databricks_tellr_app-0.3.13.dev3/src/services/converter_jail/codeprep.py +0 -220
  19. databricks_tellr_app-0.3.13.dev3/src/services/converter_jail/gslides_runner.py +0 -78
  20. databricks_tellr_app-0.3.13.dev3/src/services/converter_jail/jail.py +0 -230
  21. databricks_tellr_app-0.3.13.dev3/src/services/converter_jail/pptx_runner.py +0 -124
  22. databricks_tellr_app-0.3.13.dev3/src/services/converter_jail/protocol.py +0 -41
  23. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/README.md +0 -0
  24. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/databricks_tellr_app/__init__.py +0 -0
  25. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/databricks_tellr_app.egg-info/dependency_links.txt +0 -0
  26. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/databricks_tellr_app.egg-info/top_level.txt +0 -0
  27. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/setup.cfg +0 -0
  28. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/setup.py +0 -0
  29. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/__init__.py +0 -0
  30. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/__init__.py +0 -0
  31. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/_otel_bootstrap.py +0 -0
  32. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/fixtures/tour_demo_deck.json +0 -0
  33. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/main.py +0 -0
  34. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/mcp_auth.py +0 -0
  35. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/mcp_server.py +0 -0
  36. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/middleware/__init__.py +0 -0
  37. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/middleware/request_logging.py +0 -0
  38. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/__init__.py +0 -0
  39. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/admin.py +0 -0
  40. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/admin_usage.py +0 -0
  41. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/agent_config.py +0 -0
  42. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/chat.py +0 -0
  43. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/deck_contributors.py +0 -0
  44. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/export.py +0 -0
  45. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/feedback.py +0 -0
  46. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/google_slides.py +0 -0
  47. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/images.py +0 -0
  48. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/local_version.py +0 -0
  49. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/profiles.py +0 -0
  50. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/sessions.py +0 -0
  51. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/settings/__init__.py +0 -0
  52. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/settings/contributors.py +0 -0
  53. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/settings/deck_prompts.py +0 -0
  54. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/settings/identities.py +0 -0
  55. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/settings/slide_styles.py +0 -0
  56. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/setup.py +0 -0
  57. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/slides.py +0 -0
  58. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/tools.py +0 -0
  59. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/tour.py +0 -0
  60. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/verification.py +0 -0
  61. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/version.py +0 -0
  62. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/schemas/__init__.py +0 -0
  63. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/schemas/agent_config.py +0 -0
  64. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/schemas/feedback.py +0 -0
  65. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/schemas/requests.py +0 -0
  66. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/schemas/responses.py +0 -0
  67. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/schemas/settings/__init__.py +0 -0
  68. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/schemas/settings/requests.py +0 -0
  69. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/schemas/settings/responses.py +0 -0
  70. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/schemas/streaming.py +0 -0
  71. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/services/__init__.py +0 -0
  72. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/services/chat_service.py +0 -0
  73. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/services/feedback_service.py +0 -0
  74. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/services/job_queue.py +0 -0
  75. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/services/session_manager.py +0 -0
  76. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/services/session_naming.py +0 -0
  77. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/services/usage_events.py +0 -0
  78. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/services/usage_service.py +0 -0
  79. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/utils/__init__.py +0 -0
  80. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/utils/validation.py +0 -0
  81. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/__init__.py +0 -0
  82. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/config_loader.py +0 -0
  83. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/context_utils.py +0 -0
  84. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/database.py +0 -0
  85. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/databricks_client.py +0 -0
  86. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/defaults.py +0 -0
  87. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/init_default_profile.py +0 -0
  88. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/migrate_profiles_to_agent_config.py +0 -0
  89. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/mlflow_agent_spans.py +0 -0
  90. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/mlflow_tracing.py +0 -0
  91. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/otel_logging.py +0 -0
  92. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/permission_context.py +0 -0
  93. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/prompt_modules.py +0 -0
  94. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/settings_db.py +0 -0
  95. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/user_context.py +0 -0
  96. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/__init__.py +0 -0
  97. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/deck_contributor.py +0 -0
  98. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/feedback.py +0 -0
  99. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/genie_space.py +0 -0
  100. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/google_global_credentials.py +0 -0
  101. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/google_oauth_token.py +0 -0
  102. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/identity.py +0 -0
  103. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/image.py +0 -0
  104. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/profile.py +0 -0
  105. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/profile_contributor.py +0 -0
  106. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/prompts.py +0 -0
  107. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/request_log.py +0 -0
  108. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/session.py +0 -0
  109. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/slide_deck_prompt.py +0 -0
  110. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/slide_style_library.py +0 -0
  111. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/usage_event.py +0 -0
  112. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/user_preference.py +0 -0
  113. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/domain/__init__.py +0 -0
  114. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/domain/slide.py +0 -0
  115. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/domain/slide_deck.py +0 -0
  116. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/__init__.py +0 -0
  117. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/agent.py +0 -0
  118. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/agent_factory.py +0 -0
  119. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/config_service.py +0 -0
  120. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/config_validator.py +0 -0
  121. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/drive_uploader.py +0 -0
  122. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/evaluation/__init__.py +0 -0
  123. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/evaluation/llm_judge.py +0 -0
  124. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/genie_service.py +0 -0
  125. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/google_slides_auth.py +0 -0
  126. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/identity_provider.py +0 -0
  127. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/identity_providers/__init__.py +0 -0
  128. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/identity_providers/account_provider.py +0 -0
  129. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/identity_providers/local_provider.py +0 -0
  130. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/identity_providers/workspace_provider.py +0 -0
  131. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/image_service.py +0 -0
  132. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/image_tools.py +0 -0
  133. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/permission_service.py +0 -0
  134. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/pptx_from_html_huashu.py +0 -0
  135. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/pptx_from_records.py +0 -0
  136. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/pptx_prompts_defaults.py +0 -0
  137. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/profile_service.py +0 -0
  138. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/streaming_callback.py +0 -0
  139. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/tools/__init__.py +0 -0
  140. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/tools/agent_bricks_tool.py +0 -0
  141. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/tools/genie_tool.py +0 -0
  142. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/tools/mcp_tool.py +0 -0
  143. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/tools/model_endpoint_tool.py +0 -0
  144. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/tools/vector_tool.py +0 -0
  145. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/validator.py +0 -0
  146. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/utils/__init__.py +0 -0
  147. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/utils/css_utils.py +0 -0
  148. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/utils/error_handling.py +0 -0
  149. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/utils/html_safety.py +0 -0
  150. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/utils/html_utils.py +0 -0
  151. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/utils/image_utils.py +0 -0
  152. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/utils/js_validator.py +0 -0
  153. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/utils/logging_config.py +0 -0
  154. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/utils/pi_filter.py +0 -0
  155. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/utils/slide_hash.py +0 -0
  156. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/utils/spotlight.py +0 -0
  157. {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/utils/text_caps.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: databricks-tellr-app
3
- Version: 0.3.13.dev3
3
+ Version: 0.3.13.dev4
4
4
  Summary: Tellr application package for Databricks Apps
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -24,7 +24,7 @@ Requires-Dist: tinycss2==1.3.0
24
24
  Requires-Dist: esprima==4.0.1
25
25
  Requires-Dist: sqlalchemy==2.0.36
26
26
  Requires-Dist: psycopg2-binary==2.9.10
27
- Requires-Dist: databricks-sql-connector>=3.0.0
27
+ Requires-Dist: databricks-sql-connector==3.0.1
28
28
  Requires-Dist: httpx==0.28.1
29
29
  Requires-Dist: jinja2==3.1.2
30
30
  Requires-Dist: python-dotenv==1.2.1
@@ -32,12 +32,12 @@ Requires-Dist: pyyaml==6.0.1
32
32
  Requires-Dist: pandas==2.3.3
33
33
  Requires-Dist: python-pptx==0.6.23
34
34
  Requires-Dist: playwright==1.48.0
35
- Requires-Dist: Pillow>=10.0.0
36
- Requires-Dist: google-api-python-client>=2.100.0
37
- Requires-Dist: google-auth-oauthlib>=1.2.0
38
- Requires-Dist: google-auth-httplib2>=0.2.0
39
- Requires-Dist: cryptography>=42.0.0
40
- Requires-Dist: sse-starlette>=3.0.0
35
+ Requires-Dist: Pillow==11.3.0
36
+ Requires-Dist: google-api-python-client==2.190.0
37
+ Requires-Dist: google-auth-oauthlib==1.2.4
38
+ Requires-Dist: google-auth-httplib2==0.3.0
39
+ Requires-Dist: cryptography==46.0.3
40
+ Requires-Dist: sse-starlette==3.0.3
41
41
  Requires-Dist: databricks-sdk==0.120.0
42
42
  Requires-Dist: mlflow[databricks]==3.14.0
43
43
  Requires-Dist: mlflow-skinny==3.14.0
@@ -62,6 +62,18 @@ def init_database(seed_databricks_defaults: bool = False) -> None:
62
62
  logger.error(f"Failed to seed defaults: {e}\n{tb}")
63
63
  raise SystemExit(1) from e
64
64
 
65
+ # Seed/load the Fernet master key pre-fork (SDR-4437 CRITICAL-3) so all
66
+ # uvicorn workers find the encryption_keys row already present.
67
+ logger.info("Ensuring encryption key...")
68
+ try:
69
+ from src.core.encryption import ensure_encryption_key
70
+ ensure_encryption_key()
71
+ logger.info("Encryption key ready")
72
+ except Exception as e:
73
+ tb = traceback.format_exc()
74
+ logger.error(f"Failed to ensure encryption key: {e}\n{tb}")
75
+ raise SystemExit(1) from e
76
+
65
77
 
66
78
  def main() -> None:
67
79
  """Start the uvicorn server."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: databricks-tellr-app
3
- Version: 0.3.13.dev3
3
+ Version: 0.3.13.dev4
4
4
  Summary: Tellr application package for Databricks Apps
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -24,7 +24,7 @@ Requires-Dist: tinycss2==1.3.0
24
24
  Requires-Dist: esprima==4.0.1
25
25
  Requires-Dist: sqlalchemy==2.0.36
26
26
  Requires-Dist: psycopg2-binary==2.9.10
27
- Requires-Dist: databricks-sql-connector>=3.0.0
27
+ Requires-Dist: databricks-sql-connector==3.0.1
28
28
  Requires-Dist: httpx==0.28.1
29
29
  Requires-Dist: jinja2==3.1.2
30
30
  Requires-Dist: python-dotenv==1.2.1
@@ -32,12 +32,12 @@ Requires-Dist: pyyaml==6.0.1
32
32
  Requires-Dist: pandas==2.3.3
33
33
  Requires-Dist: python-pptx==0.6.23
34
34
  Requires-Dist: playwright==1.48.0
35
- Requires-Dist: Pillow>=10.0.0
36
- Requires-Dist: google-api-python-client>=2.100.0
37
- Requires-Dist: google-auth-oauthlib>=1.2.0
38
- Requires-Dist: google-auth-httplib2>=0.2.0
39
- Requires-Dist: cryptography>=42.0.0
40
- Requires-Dist: sse-starlette>=3.0.0
35
+ Requires-Dist: Pillow==11.3.0
36
+ Requires-Dist: google-api-python-client==2.190.0
37
+ Requires-Dist: google-auth-oauthlib==1.2.4
38
+ Requires-Dist: google-auth-httplib2==0.3.0
39
+ Requires-Dist: cryptography==46.0.3
40
+ Requires-Dist: sse-starlette==3.0.3
41
41
  Requires-Dist: databricks-sdk==0.120.0
42
42
  Requires-Dist: mlflow[databricks]==3.14.0
43
43
  Requires-Dist: mlflow-skinny==3.14.0
@@ -76,6 +76,7 @@ setup.py
76
76
  ./src/database/__init__.py
77
77
  ./src/database/models/__init__.py
78
78
  ./src/database/models/deck_contributor.py
79
+ ./src/database/models/encryption_key.py
79
80
  ./src/database/models/feedback.py
80
81
  ./src/database/models/genie_space.py
81
82
  ./src/database/models/google_global_credentials.py
@@ -115,13 +116,6 @@ setup.py
115
116
  ./src/services/profile_service.py
116
117
  ./src/services/streaming_callback.py
117
118
  ./src/services/validator.py
118
- ./src/services/converter_jail/__init__.py
119
- ./src/services/converter_jail/ast_guard.py
120
- ./src/services/converter_jail/codeprep.py
121
- ./src/services/converter_jail/gslides_runner.py
122
- ./src/services/converter_jail/jail.py
123
- ./src/services/converter_jail/pptx_runner.py
124
- ./src/services/converter_jail/protocol.py
125
119
  ./src/services/evaluation/__init__.py
126
120
  ./src/services/evaluation/llm_judge.py
127
121
  ./src/services/identity_providers/__init__.py
@@ -226,6 +220,7 @@ src/core/user_context.py
226
220
  src/database/__init__.py
227
221
  src/database/models/__init__.py
228
222
  src/database/models/deck_contributor.py
223
+ src/database/models/encryption_key.py
229
224
  src/database/models/feedback.py
230
225
  src/database/models/genie_space.py
231
226
  src/database/models/google_global_credentials.py
@@ -265,13 +260,6 @@ src/services/pptx_prompts_defaults.py
265
260
  src/services/profile_service.py
266
261
  src/services/streaming_callback.py
267
262
  src/services/validator.py
268
- src/services/converter_jail/__init__.py
269
- src/services/converter_jail/ast_guard.py
270
- src/services/converter_jail/codeprep.py
271
- src/services/converter_jail/gslides_runner.py
272
- src/services/converter_jail/jail.py
273
- src/services/converter_jail/pptx_runner.py
274
- src/services/converter_jail/protocol.py
275
263
  src/services/evaluation/__init__.py
276
264
  src/services/evaluation/llm_judge.py
277
265
  src/services/identity_providers/__init__.py
@@ -18,7 +18,7 @@ tinycss2==1.3.0
18
18
  esprima==4.0.1
19
19
  sqlalchemy==2.0.36
20
20
  psycopg2-binary==2.9.10
21
- databricks-sql-connector>=3.0.0
21
+ databricks-sql-connector==3.0.1
22
22
  httpx==0.28.1
23
23
  jinja2==3.1.2
24
24
  python-dotenv==1.2.1
@@ -26,12 +26,12 @@ pyyaml==6.0.1
26
26
  pandas==2.3.3
27
27
  python-pptx==0.6.23
28
28
  playwright==1.48.0
29
- Pillow>=10.0.0
30
- google-api-python-client>=2.100.0
31
- google-auth-oauthlib>=1.2.0
32
- google-auth-httplib2>=0.2.0
33
- cryptography>=42.0.0
34
- sse-starlette>=3.0.0
29
+ Pillow==11.3.0
30
+ google-api-python-client==2.190.0
31
+ google-auth-oauthlib==1.2.4
32
+ google-auth-httplib2==0.3.0
33
+ cryptography==46.0.3
34
+ sse-starlette==3.0.3
35
35
  databricks-sdk==0.120.0
36
36
  mlflow[databricks]==3.14.0
37
37
  mlflow-skinny==3.14.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "databricks-tellr-app"
7
- version = "0.3.13.dev3"
7
+ version = "0.3.13.dev4"
8
8
  description = "Tellr application package for Databricks Apps"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -29,7 +29,7 @@ dependencies = [
29
29
  "esprima==4.0.1",
30
30
  "sqlalchemy==2.0.36",
31
31
  "psycopg2-binary==2.9.10",
32
- "databricks-sql-connector>=3.0.0",
32
+ "databricks-sql-connector==3.0.1",
33
33
  "httpx==0.28.1",
34
34
  "jinja2==3.1.2",
35
35
  "python-dotenv==1.2.1",
@@ -37,12 +37,12 @@ dependencies = [
37
37
  "pandas==2.3.3",
38
38
  "python-pptx==0.6.23",
39
39
  "playwright==1.48.0",
40
- "Pillow>=10.0.0",
41
- "google-api-python-client>=2.100.0",
42
- "google-auth-oauthlib>=1.2.0",
43
- "google-auth-httplib2>=0.2.0",
44
- "cryptography>=42.0.0",
45
- "sse-starlette>=3.0.0",
40
+ "Pillow==11.3.0",
41
+ "google-api-python-client==2.190.0",
42
+ "google-auth-oauthlib==1.2.4",
43
+ "google-auth-httplib2==0.3.0",
44
+ "cryptography==46.0.3",
45
+ "sse-starlette==3.0.3",
46
46
  # --- Resolver-speedup pins: the backtracking-driver cluster ---
47
47
  # Databricks Apps installs this wheel in a platform-managed BUILD phase with a
48
48
  # ~10-minute budget. Left ranged, the databricks-langchain ->
@@ -58,6 +58,8 @@ dependencies = [
58
58
  # via a devloop deploy if these constraints change. Formerly injected at
59
59
  # deploy time as _RESOLVER_SPEEDUP_PINS; now carried in the wheel so a bare
60
60
  # `pip install databricks-tellr-app` is safe.
61
+ # SDR-4437 MEDIUM-7 (July 2026): the 7 formerly-ranged direct deps above
62
+ # this block are now exact-pinned too; same refresh discipline.
61
63
  "databricks-sdk==0.120.0",
62
64
  "mlflow[databricks]==3.14.0",
63
65
  "mlflow-skinny==3.14.0",
@@ -307,6 +307,9 @@ async def convert_slides_with_progress(
307
307
  output_path: Path to save PPTX
308
308
  chart_images_per_slide: Chart images per slide
309
309
  """
310
+ from pptx import Presentation
311
+ from pptx.util import Inches
312
+
310
313
  total_slides = len(slides_html)
311
314
 
312
315
  # -- Phase 1: Prepare assets + parallel LLM code generation ---------------
@@ -333,17 +336,27 @@ async def convert_slides_with_progress(
333
336
  slide_inputs, on_codegen_progress=_on_codegen_progress,
334
337
  )
335
338
 
336
- # -- Phase 2: Sequential execution INSIDE the subprocess jail -------------
337
- def _relay(current: int, total: int, message: str) -> None:
338
- update_export_progress(job_id, current, total, status_message=message)
339
+ # -- Phase 2: Sequential execution ----------------------------------------
340
+ prs = Presentation()
341
+ prs.slide_width = Inches(10)
342
+ prs.slide_height = Inches(7.5)
339
343
 
340
- # to_thread: keep the long blocking jail wait off the event loop (this
341
- # coroutine runs on the loop; _relay is fired from the jail's stdout
342
- # reader thread and update_export_progress opens its own session per call).
343
- await asyncio.to_thread(
344
- converter._run_pptx_conversion,
345
- codes, slide_inputs, output_path, progress_cb=_relay,
346
- )
344
+ for i, (code, (html_str, _chart_files, _content_files, assets_dir)) in enumerate(
345
+ zip(codes, slide_inputs), 1,
346
+ ):
347
+ update_export_progress(
348
+ job_id, i, total_slides,
349
+ status_message=f"Building slide {i}/{total_slides}…",
350
+ )
351
+
352
+ converter._execute_slide(code, prs, html_str, assets_dir, i)
353
+
354
+ logger.debug(
355
+ f"Processed slide {i}/{total_slides}",
356
+ extra={"job_id": job_id, "slide": i, "total": total_slides},
357
+ )
358
+
359
+ prs.save(output_path)
347
360
 
348
361
 
349
362
  async def export_worker() -> None:
@@ -0,0 +1,125 @@
1
+ """Fernet symmetric encryption for sensitive data (OAuth credentials, tokens).
2
+
3
+ Uses AES-128-CBC with HMAC-SHA256 authentication via the ``cryptography``
4
+ library. The master key lives in the ``encryption_keys`` table of the
5
+ application database (SDR-4437 CRITICAL-3): dynamic, persistent across
6
+ restarts/restores, unique per deployment, zero operator setup.
7
+
8
+ Resolution is SELECT-first, then race-safe INSERT-if-absent:
9
+
10
+ 1. ``SELECT key_value FROM encryption_keys WHERE id = 1`` — read-first is
11
+ load-bearing: on upgraded installs the table is created by the *deployer*
12
+ role and the app SP holds an explicit SELECT, INSERT grant; reading first
13
+ means INSERT privilege is never exercised when the row already exists.
14
+ 2. If absent, seed from a legacy ``.encryption_key`` file in the project root
15
+ (local dev — keeps pre-existing dev ciphertext decryptable), else generate
16
+ a fresh key.
17
+ 3. ``INSERT ... ON CONFLICT (id) DO NOTHING`` + read-back, so concurrent
18
+ workers/replicas converge on one key.
19
+
20
+ There is deliberately NO env-var fallback: the deploy tool relocates the
21
+ legacy ``GOOGLE_OAUTH_ENCRYPTION_KEY`` into this table at update time, and
22
+ old-tool + new-wheel skew is unsupported (see the SDR-4437 remediation
23
+ design, PR-3).
24
+ """
25
+
26
+ import logging
27
+ from functools import lru_cache
28
+ from pathlib import Path
29
+
30
+ from cryptography.fernet import Fernet, InvalidToken
31
+ from sqlalchemy import text
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+ _KEY_FILE = Path(__file__).resolve().parents[2] / ".encryption_key"
36
+
37
+ # NOTE: these statements are deliberately schema-UNQUALIFIED, unlike the
38
+ # _qual()-qualified raw SQL in database.py::_run_migrations. This module must
39
+ # also run against SQLite (unit tests — no schemas) and schema-less local
40
+ # Postgres. On Lakebase the bare name resolves to
41
+ # <LAKEBASE_SCHEMA>.encryption_keys ONLY because _get_database_url appends
42
+ # options=-csearch_path%3D<schema> to the URL (database.py:239,259) and the
43
+ # do_connect listener injects just the password, preserving those options.
44
+ # If that search_path mechanism ever changes, these statements would silently
45
+ # target public.encryption_keys — keep this coupling in mind.
46
+ _SELECT_KEY = text("SELECT key_value FROM encryption_keys WHERE id = 1")
47
+ _INSERT_KEY = text(
48
+ "INSERT INTO encryption_keys (id, key_value, created_at) "
49
+ "VALUES (1, :key_value, CURRENT_TIMESTAMP) "
50
+ "ON CONFLICT (id) DO NOTHING"
51
+ )
52
+
53
+
54
+ def _validated(key: bytes) -> bytes:
55
+ try:
56
+ Fernet(key)
57
+ except (ValueError, TypeError) as exc:
58
+ raise RuntimeError(
59
+ "encryption_keys row id=1 is not a valid Fernet key — refusing to "
60
+ "start with a corrupt master key. Restore the correct key value "
61
+ "before restarting."
62
+ ) from exc
63
+ return key
64
+
65
+
66
+ def _seed_value() -> bytes:
67
+ """Pick the value to seed an empty encryption_keys table with."""
68
+ if _KEY_FILE.exists():
69
+ key = _KEY_FILE.read_text().strip()
70
+ if key:
71
+ logger.info("Seeding encryption_keys from legacy key file %s", _KEY_FILE)
72
+ return key.encode()
73
+ logger.info("Generating new Fernet master key (fresh install)")
74
+ return Fernet.generate_key()
75
+
76
+
77
+ @lru_cache(maxsize=1)
78
+ def get_encryption_key() -> bytes:
79
+ """Return the Fernet master key from the encryption_keys table (id=1)."""
80
+ from src.core.database import get_db_session
81
+
82
+ # 1. Read-first (see module docstring for why this order matters).
83
+ with get_db_session() as session:
84
+ row = session.execute(_SELECT_KEY).first()
85
+ if row and row[0]:
86
+ return _validated(row[0].encode())
87
+
88
+ # 2 + 3. Seed if absent — race-safe across workers/replicas.
89
+ seed = _seed_value()
90
+ with get_db_session() as session:
91
+ session.execute(_INSERT_KEY, {"key_value": seed.decode()})
92
+ with get_db_session() as session:
93
+ row = session.execute(_SELECT_KEY).first()
94
+ if not row or not row[0]:
95
+ raise RuntimeError(
96
+ "Failed to seed encryption_keys: no key row after insert. "
97
+ "Check the app's SELECT, INSERT grants on the data schema."
98
+ )
99
+ return _validated(row[0].encode())
100
+
101
+
102
+ def ensure_encryption_key() -> None:
103
+ """Pre-fork boot hook: load/seed the key before uvicorn workers fork.
104
+
105
+ Called from the deployed app's init_database step so all workers find
106
+ the row already present (the lazy path in get_encryption_key covers
107
+ local dev and replicas regardless).
108
+ """
109
+ get_encryption_key()
110
+
111
+
112
+ def encrypt_data(plaintext: str) -> str:
113
+ """Encrypt *plaintext* and return a base64-encoded ciphertext string."""
114
+ f = Fernet(get_encryption_key())
115
+ return f.encrypt(plaintext.encode()).decode()
116
+
117
+
118
+ def decrypt_data(ciphertext: str) -> str:
119
+ """Decrypt a base64-encoded *ciphertext* string and return the plaintext.
120
+
121
+ Raises:
122
+ cryptography.fernet.InvalidToken: If the key does not match or data is corrupt.
123
+ """
124
+ f = Fernet(get_encryption_key())
125
+ return f.decrypt(ciphertext.encode()).decode()
@@ -280,120 +280,6 @@ def get_lakebase_connection_url(
280
280
  return url
281
281
 
282
282
 
283
- def setup_lakebase_schema(
284
- instance_name: str,
285
- schema: str,
286
- client_id: str,
287
- user: Optional[str] = None,
288
- client: Optional[WorkspaceClient] = None,
289
- endpoint_name: Optional[str] = None,
290
- host: Optional[str] = None,
291
- lakebase_type: str = "provisioned",
292
- ) -> None:
293
- """
294
- Set up schema and grant permissions in Lakebase for an app.
295
-
296
- Supports both provisioned and autoscaling Lakebase.
297
-
298
- This function connects to the Lakebase instance and:
299
- 1. Creates the schema if it doesn't exist
300
- 2. Grants necessary permissions to the app's service principal (client_id)
301
-
302
- Note: This should be called after the app is created and the database
303
- resource is attached, as the client_id comes from the app's service principal.
304
-
305
- Args:
306
- instance_name: Name of the Lakebase instance
307
- schema: Schema name to create
308
- client_id: App's service principal client ID (becomes Postgres role)
309
- user: Postgres username for connection (uses default if not provided)
310
- client: Optional WorkspaceClient
311
- endpoint_name: Full endpoint resource name (autoscaling only)
312
- host: Pre-resolved host (autoscaling only)
313
- lakebase_type: 'autoscaling' or 'provisioned'
314
-
315
- Raises:
316
- LakebaseError: If schema setup fails
317
- """
318
- logger.info(
319
- "Setting up Lakebase schema",
320
- extra={
321
- "instance_name": instance_name, "schema": schema,
322
- "client_id": client_id, "lakebase_type": lakebase_type,
323
- },
324
- )
325
-
326
- try:
327
- # Get connection info
328
- conn_info = get_lakebase_connection_info(
329
- instance_name=instance_name,
330
- user=user,
331
- client=client,
332
- endpoint_name=endpoint_name,
333
- host=host,
334
- lakebase_type=lakebase_type,
335
- )
336
-
337
- # Use psycopg2 directly for DDL operations
338
- import psycopg2
339
-
340
- conn = psycopg2.connect(
341
- host=conn_info["host"],
342
- port=conn_info["port"],
343
- user=conn_info["user"],
344
- password=conn_info["password"],
345
- dbname=conn_info["database"],
346
- sslmode="require",
347
- )
348
- conn.autocommit = True
349
-
350
- with conn.cursor() as cur:
351
- # Create schema if not exists
352
- cur.execute(f'CREATE SCHEMA IF NOT EXISTS "{schema}"')
353
- logger.info(f"Schema created or verified: {schema}")
354
-
355
- # Grant permissions to app's service principal
356
- # The client_id is automatically available as a Postgres role when
357
- # the database is added as an app resource
358
- cur.execute(f'GRANT USAGE ON SCHEMA "{schema}" TO "{client_id}"')
359
- cur.execute(f'GRANT CREATE ON SCHEMA "{schema}" TO "{client_id}"')
360
-
361
- # Grant permissions on all tables in schema (for existing tables)
362
- cur.execute(
363
- f'GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA "{schema}" TO "{client_id}"'
364
- )
365
-
366
- # Grant permissions on all sequences in schema (for auto-increment columns)
367
- cur.execute(
368
- f'GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA "{schema}" TO "{client_id}"'
369
- )
370
-
371
- # Set default privileges for future tables
372
- cur.execute(
373
- f'ALTER DEFAULT PRIVILEGES IN SCHEMA "{schema}" '
374
- f'GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO "{client_id}"'
375
- )
376
-
377
- # Set default privileges for future sequences
378
- cur.execute(
379
- f'ALTER DEFAULT PRIVILEGES IN SCHEMA "{schema}" '
380
- f'GRANT USAGE, SELECT ON SEQUENCES TO "{client_id}"'
381
- )
382
-
383
- logger.info(f"Permissions granted to {client_id} on schema {schema}")
384
-
385
- conn.close()
386
-
387
- except ImportError:
388
- raise LakebaseError(
389
- "psycopg2 is required for Lakebase schema setup. "
390
- "Install with: pip install psycopg2-binary"
391
- )
392
- except Exception as e:
393
- logger.error(f"Failed to setup Lakebase schema: {e}", exc_info=True)
394
- raise LakebaseError(f"Lakebase schema setup failed: {e}") from e
395
-
396
-
397
283
  def initialize_lakebase_tables(
398
284
  instance_name: str,
399
285
  schema: str,
@@ -1,6 +1,7 @@
1
1
  """Database models."""
2
2
 
3
3
  from src.database.models.deck_contributor import DeckContributor
4
+ from src.database.models.encryption_key import EncryptionKey
4
5
  from src.database.models.feedback import FeedbackConversation, SurveyResponse
5
6
  from src.database.models.genie_space import ConfigGenieSpace
6
7
  from src.database.models.google_global_credentials import GoogleGlobalCredentials
@@ -34,6 +35,7 @@ __all__ = [
34
35
  "ChatRequest",
35
36
  "ConfigGenieSpace",
36
37
  "DeckContributor",
38
+ "EncryptionKey",
37
39
  "ConfigProfile",
38
40
  "ConfigProfileContributor",
39
41
  "ConfigPrompts",
@@ -0,0 +1,30 @@
1
+ """Application-managed encryption key storage (SDR-4437 CRITICAL-3).
2
+
3
+ Holds the Fernet master key for OAuth credential/token encryption in the
4
+ ACL-governed Lakebase data schema instead of app.yaml. Single-row table
5
+ (id = 1). Deliberately shares the data schema's grants: the key carries
6
+ the same ACLs as the ciphertext it protects — an explicitly accepted risk
7
+ in the SDR-4437 remediation design. Do NOT add key-specific grant
8
+ tightening here.
9
+ """
10
+
11
+ from sqlalchemy import Column, DateTime, Integer, Text
12
+
13
+ from src.core.database import Base
14
+
15
+
16
+ class EncryptionKey(Base):
17
+ """Fernet master key for OAuth credential/token encryption."""
18
+
19
+ __tablename__ = "encryption_keys"
20
+
21
+ id = Column(Integer, primary_key=True, autoincrement=False)
22
+ key_value = Column(Text, nullable=False)
23
+ # No Python-side default: every insert path (the app's raw-SQL seed and
24
+ # the deploy migration DDL) supplies CURRENT_TIMESTAMP explicitly, so a
25
+ # default would be dead code — and the codebase-conventional
26
+ # datetime.utcnow is deprecated on Python 3.12+.
27
+ created_at = Column(DateTime, nullable=False)
28
+
29
+ def __repr__(self) -> str: # never print key material
30
+ return f"<EncryptionKey(id={self.id}, created_at={self.created_at})>"
@@ -69,9 +69,8 @@ TWO-COLUMN LAYOUT (.two-column):
69
69
  - Left: left=0.4", width=4.3"; Right: left=5.0", width=4.5"; Gap: 0.3"
70
70
 
71
71
  TABLES (<table>):
72
- - createTable first (rows, cols, size, position), with an explicit objectId
73
- - AFTER it in the returned list: insertText per cell + updateTextStyle + updateTableCellProperties
74
- (the host executes requests in order, so the table exists before its cells are filled)
72
+ - createTable in FIRST batchUpdate (rows, cols, size, position)
73
+ - Then insertText per cell + updateTextStyle + updateTableCellProperties in SECOND batchUpdate
75
74
  - updateTableCellProperties requires: 'tableRange': {'location': {'rowIndex': r, 'columnIndex': c}, 'rowSpan': 1, 'columnSpan': 1}
76
75
  - Headers: background from CSS (typically dark), text white, font 8pt bold
77
76
  - Cells: font 7-8pt, preserve colors, left-align text, right-align numbers
@@ -108,22 +107,17 @@ REGULAR SLIDES:
108
107
  Subtitle must fit in 0.28" (10pt ≈ 0.14" per line → 2 lines max).
109
108
 
110
109
  CHARTS (CRITICAL):
111
- - If chart images are listed in the user message, you MUST reference them by
112
- placeholder — do NOT upload anything, do NOT call any Google API.
113
- - For each chart image `filename` in assets_dir, add a createImage request with
114
- url set to the placeholder 'tellr-asset://' + filename. The host uploads the
115
- file to Drive and substitutes the real URL before execution.
116
- Example: {'createImage': {'objectId': 'img_' + str(uuid.uuid4())[:8],
117
- 'url': 'tellr-asset://' + filename,
118
- 'elementProperties': {'pageObjectId': page_id, 'size': {...}, 'transform': {...}}}}
110
+ - If chart images are listed in the user message, you MUST use them as pre-captured screenshots.
119
111
  - Do NOT recreate charts with matplotlib, plotly, or any library. The images are already rendered.
112
+ - Upload to Drive via MediaFileUpload, set anyone/reader permission, then createImage.
120
113
  - Chart only (no metric cards): left=0.4", top=1.1", width=9.0", height=4.0" — fills the body zone.
121
114
  - Chart + metrics: use CHART + METRICS SIDE-BY-SIDE layout above.
122
115
  - If NO chart images are listed, skip chart rendering entirely — do not attempt to recreate.
123
116
 
124
117
  CONTENT IMAGES (logos, icons — CRITICAL):
125
- - Reference each content image the same way: url = 'tellr-asset://' + filename.
126
- - NEVER upload; NEVER create Drive files or set permissions yourself. The host does that.
118
+ - If content images are listed in the user message, you MUST upload and add them to the slide.
119
+ - These are pre-extracted image files (PNG) saved in assets_dir — ready to upload.
120
+ - Upload to Drive the same way as charts: MediaFileUpload → files().create → permissions().create → createImage.
127
121
  - Position to match the HTML layout (e.g. a logo in the top-right header area → right-aligned near the title).
128
122
  - Typical logo sizes: width=0.6"-1.0", height=0.3"-0.5". Do NOT make logos larger than needed.
129
123
  - Place logos/icons in the header zone (top=0.15"-0.4") so they don't overlap body content.
@@ -171,27 +165,23 @@ API PATTERNS (CRITICAL — wrong nesting causes 400 errors):
171
165
 
172
166
  ALLOWED IMPORTS:
173
167
  import os, json, uuid, re
174
- (Do NOT import googleapiclient — you never touch the network.)
168
+ from googleapiclient.http import MediaFileUpload # ONLY when uploading images
175
169
 
176
170
  EXECUTION:
177
171
  - ALL code MUST be inside the function body. Nothing at module level except imports and helpers.
178
- - Build ONE requests list and RETURN it. Never call batchUpdate or any API
179
- yourself — the host executes the returned requests in order.
180
- - Do NOT wrap request-building in try/except.
172
+ - Build requests list, then ONE batchUpdate (except tables need two).
173
+ - Do NOT wrap batchUpdate in try/except.
181
174
  - Every createShape needs a unique objectId: 'txt_' + str(uuid.uuid4())[:8]
182
175
 
183
176
  Return ONLY Python code, no markdown fences."""
184
177
 
185
178
  # ── Multi-slide prompts ──────────────────────────────────────────────
186
179
 
187
- DEFAULT_GSLIDES_SYSTEM_PROMPT = """Generate Python code with a build_slide_requests(html_str, assets_dir, page_id) function that RETURNS a list of Google Slides batchUpdate request dicts.
180
+ DEFAULT_GSLIDES_SYSTEM_PROMPT = """Generate Python code with add_slide_to_presentation(slides_service, drive_service, presentation_id, page_id, html_str, assets_dir) function.
188
181
 
189
182
  CONTEXT:
190
- - You do NOT have and do NOT need any Google service objects or network access.
191
- - Do NOT call batchUpdate or any other Google API method — never execute requests, upload files, or create permissions yourself.
192
- - The target slide page already exists; reference it via the page_id argument.
193
- - Return (do not execute) a Python list of request dicts. The trusted host
194
- executes them, uploads any referenced images, and handles chunking/retries.
183
+ - All arguments are pre-created. Do NOT import googleapiclient or build services.
184
+ - The slide page already exists (page_id). Do NOT call createSlide — only add content.
195
185
 
196
186
  HELPERS (include in your code):
197
187
  def emu(inches): return int(inches * 914400)
@@ -202,22 +192,22 @@ def hex_to_rgb(h):
202
192
  SLIDE: 16:9 widescreen — 10" × 5.625" (9144000 × 5143500 EMU).
203
193
  Bounds: left ≥ 0.4", top ≥ 0.2", left + width ≤ 9.6", top + height ≤ 5.4"
204
194
 
205
- ELEMENT CREATION (append each request dict to a list, then `return requests`):
206
- - TEXT_BOX: createShape + insertText + updateTextStyle (textRange {'type':'ALL'}) + updateParagraphStyle
195
+ ELEMENT CREATION:
196
+ - TEXT_BOX: createShape + insertText + updateTextStyle (textRange: {'type':'ALL'}) + updateParagraphStyle
207
197
  - RECTANGLE/ROUND_RECTANGLE: createShape + updateShapeProperties
208
198
  - Size: {'width': {'magnitude': emu(w), 'unit': 'EMU'}, 'height': {'magnitude': emu(h), 'unit': 'EMU'}}
209
199
  - Position: {'scaleX':1,'scaleY':1,'translateX':emu(left),'translateY':emu(top),'unit':'EMU'}
210
200
 
211
- The code MUST define build_slide_requests(html_str, assets_dir, page_id) and RETURN the requests list.
201
+ The code MUST define the function add_slide_to_presentation.
212
202
  """ + _GSLIDES_SHARED_RULES
213
203
 
214
- DEFAULT_GSLIDES_USER_PROMPT = """Convert the HTML below to a list of Google Slides batchUpdate requests.
204
+ DEFAULT_GSLIDES_USER_PROMPT = """Convert the HTML below to a Google Slide.
215
205
 
216
206
  {html_content}
217
207
 
218
208
  {screenshot_note}
219
209
 
220
- Return Python code defining build_slide_requests(html_str, assets_dir, page_id) that returns the requests list."""
210
+ Return Python code with add_slide_to_presentation(slides_service, drive_service, presentation_id, page_id, html_str, assets_dir)."""
221
211
 
222
212
  # ── Single-slide prompts ─────────────────────────────────────────────
223
213