databricks-tellr-app 0.4.2.dev20__tar.gz → 0.4.3.dev1__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 (167) hide show
  1. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/PKG-INFO +1 -1
  2. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/databricks_tellr_app.egg-info/PKG-INFO +1 -1
  3. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/pyproject.toml +1 -1
  4. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/routes/images.py +45 -27
  5. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/schemas/requests.py +2 -2
  6. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/services/chat_service.py +12 -9
  7. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/core/database.py +40 -0
  8. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/database/models/image.py +15 -1
  9. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/database/types.py +12 -1
  10. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/image_service.py +9 -9
  11. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/image_tools.py +2 -2
  12. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/utils/image_utils.py +5 -4
  13. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/README.md +0 -0
  14. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/databricks_tellr_app/__init__.py +0 -0
  15. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/databricks_tellr_app/run.py +0 -0
  16. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/databricks_tellr_app.egg-info/SOURCES.txt +0 -0
  17. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/databricks_tellr_app.egg-info/dependency_links.txt +0 -0
  18. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/databricks_tellr_app.egg-info/requires.txt +0 -0
  19. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/databricks_tellr_app.egg-info/top_level.txt +0 -0
  20. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/setup.cfg +0 -0
  21. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/setup.py +0 -0
  22. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/__init__.py +0 -0
  23. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/__init__.py +0 -0
  24. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/_otel_bootstrap.py +0 -0
  25. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/fixtures/tour_demo_deck.json +0 -0
  26. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/main.py +0 -0
  27. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/mcp_auth.py +0 -0
  28. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/mcp_server.py +0 -0
  29. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/middleware/__init__.py +0 -0
  30. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/middleware/csrf.py +0 -0
  31. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/middleware/request_logging.py +0 -0
  32. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/middleware/security_headers.py +0 -0
  33. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/routes/__init__.py +0 -0
  34. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/routes/_authz.py +0 -0
  35. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/routes/admin.py +0 -0
  36. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/routes/admin_usage.py +0 -0
  37. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/routes/agent_config.py +0 -0
  38. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/routes/chat.py +0 -0
  39. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/routes/deck_contributors.py +0 -0
  40. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/routes/export.py +0 -0
  41. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/routes/feedback.py +0 -0
  42. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/routes/google_slides.py +0 -0
  43. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/routes/local_version.py +0 -0
  44. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/routes/profiles.py +0 -0
  45. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/routes/sessions.py +0 -0
  46. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/routes/settings/__init__.py +0 -0
  47. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/routes/settings/contributors.py +0 -0
  48. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/routes/settings/deck_prompts.py +0 -0
  49. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/routes/settings/design_systems.py +0 -0
  50. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/routes/settings/identities.py +0 -0
  51. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/routes/settings/slide_styles.py +0 -0
  52. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/routes/setup.py +0 -0
  53. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/routes/slides.py +0 -0
  54. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/routes/tools.py +0 -0
  55. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/routes/tour.py +0 -0
  56. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/routes/verification.py +0 -0
  57. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/routes/version.py +0 -0
  58. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/schemas/__init__.py +0 -0
  59. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/schemas/agent_config.py +0 -0
  60. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/schemas/feedback.py +0 -0
  61. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/schemas/responses.py +0 -0
  62. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/schemas/settings/__init__.py +0 -0
  63. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/schemas/settings/requests.py +0 -0
  64. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/schemas/settings/responses.py +0 -0
  65. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/schemas/streaming.py +0 -0
  66. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/services/__init__.py +0 -0
  67. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/services/export_job_queue.py +0 -0
  68. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/services/feedback_service.py +0 -0
  69. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/services/job_queue.py +0 -0
  70. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/services/session_manager.py +0 -0
  71. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/services/session_naming.py +0 -0
  72. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/services/usage_events.py +0 -0
  73. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/services/usage_service.py +0 -0
  74. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/utils/__init__.py +0 -0
  75. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/api/utils/validation.py +0 -0
  76. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/core/__init__.py +0 -0
  77. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/core/config_loader.py +0 -0
  78. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/core/context_utils.py +0 -0
  79. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/core/databricks_client.py +0 -0
  80. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/core/defaults.py +0 -0
  81. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/core/encryption.py +0 -0
  82. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/core/init_default_profile.py +0 -0
  83. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/core/lakebase.py +0 -0
  84. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/core/migrate_profiles_to_agent_config.py +0 -0
  85. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/core/mlflow_agent_spans.py +0 -0
  86. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/core/mlflow_tracing.py +0 -0
  87. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/core/otel_logging.py +0 -0
  88. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/core/permission_context.py +0 -0
  89. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/core/prompt_modules.py +0 -0
  90. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/core/settings_db.py +0 -0
  91. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/core/user_context.py +0 -0
  92. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/database/__init__.py +0 -0
  93. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/database/models/__init__.py +0 -0
  94. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/database/models/deck_contributor.py +0 -0
  95. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/database/models/design_system.py +0 -0
  96. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/database/models/encryption_key.py +0 -0
  97. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/database/models/feedback.py +0 -0
  98. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/database/models/genie_space.py +0 -0
  99. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/database/models/google_global_credentials.py +0 -0
  100. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/database/models/google_oauth_token.py +0 -0
  101. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/database/models/identity.py +0 -0
  102. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/database/models/profile.py +0 -0
  103. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/database/models/profile_contributor.py +0 -0
  104. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/database/models/prompts.py +0 -0
  105. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/database/models/request_log.py +0 -0
  106. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/database/models/session.py +0 -0
  107. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/database/models/slide_deck_prompt.py +0 -0
  108. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/database/models/slide_style_library.py +0 -0
  109. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/database/models/usage_event.py +0 -0
  110. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/database/models/user_preference.py +0 -0
  111. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/domain/__init__.py +0 -0
  112. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/domain/slide.py +0 -0
  113. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/domain/slide_deck.py +0 -0
  114. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/__init__.py +0 -0
  115. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/agent.py +0 -0
  116. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/agent_factory.py +0 -0
  117. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/config_service.py +0 -0
  118. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/config_validator.py +0 -0
  119. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/converter_jail/__init__.py +0 -0
  120. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/converter_jail/ast_guard.py +0 -0
  121. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/converter_jail/codeprep.py +0 -0
  122. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/converter_jail/gslides_runner.py +0 -0
  123. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/converter_jail/jail.py +0 -0
  124. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/converter_jail/pptx_runner.py +0 -0
  125. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/converter_jail/protocol.py +0 -0
  126. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/design_system_compiler.py +0 -0
  127. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/design_system_service.py +0 -0
  128. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/design_system_templates.py +0 -0
  129. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/drive_uploader.py +0 -0
  130. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/evaluation/__init__.py +0 -0
  131. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/evaluation/llm_judge.py +0 -0
  132. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/genie_service.py +0 -0
  133. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/google_slides_auth.py +0 -0
  134. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/google_slides_prompts_defaults.py +0 -0
  135. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/html_to_google_slides.py +0 -0
  136. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/html_to_pptx.py +0 -0
  137. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/identity_provider.py +0 -0
  138. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/identity_providers/__init__.py +0 -0
  139. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/identity_providers/account_provider.py +0 -0
  140. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/identity_providers/local_provider.py +0 -0
  141. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/identity_providers/workspace_provider.py +0 -0
  142. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/permission_service.py +0 -0
  143. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/pptx_from_html_huashu.py +0 -0
  144. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/pptx_from_records.py +0 -0
  145. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/pptx_prompts_defaults.py +0 -0
  146. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/profile_service.py +0 -0
  147. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/streaming_callback.py +0 -0
  148. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/tools/__init__.py +0 -0
  149. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/tools/agent_bricks_tool.py +0 -0
  150. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/tools/ds_asset_tool.py +0 -0
  151. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/tools/genie_tool.py +0 -0
  152. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/tools/mcp_tool.py +0 -0
  153. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/tools/model_endpoint_tool.py +0 -0
  154. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/tools/vector_tool.py +0 -0
  155. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/services/validator.py +0 -0
  156. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/utils/__init__.py +0 -0
  157. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/utils/css_utils.py +0 -0
  158. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/utils/ds_asset_utils.py +0 -0
  159. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/utils/error_handling.py +0 -0
  160. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/utils/html_safety.py +0 -0
  161. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/utils/html_utils.py +0 -0
  162. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/utils/js_validator.py +0 -0
  163. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/utils/logging_config.py +0 -0
  164. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/utils/pi_filter.py +0 -0
  165. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/utils/slide_hash.py +0 -0
  166. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/src/utils/spotlight.py +0 -0
  167. {databricks_tellr_app-0.4.2.dev20 → databricks_tellr_app-0.4.3.dev1}/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.4.2.dev20
3
+ Version: 0.4.3.dev1
4
4
  Summary: Tellr application package for Databricks Apps
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: databricks-tellr-app
3
- Version: 0.4.2.dev20
3
+ Version: 0.4.3.dev1
4
4
  Summary: Tellr application package for Databricks Apps
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "databricks-tellr-app"
7
- version = "0.4.2.dev20"
7
+ version = "0.4.3.dev1"
8
8
  description = "Tellr application package for Databricks Apps"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,4 +1,5 @@
1
1
  """Image upload and management API endpoints."""
2
+ import base64
2
3
  import logging
3
4
  import os
4
5
  from typing import List, Optional
@@ -20,7 +21,7 @@ router = APIRouter(prefix="/api/images", tags=["images"])
20
21
 
21
22
  class ImageResponse(BaseModel):
22
23
  """Response schema for image metadata."""
23
- id: int
24
+ id: str # Opaque token (SDR-4437 F-TM-7) — the internal int PK is never exposed.
24
25
  filename: str
25
26
  original_filename: str
26
27
  mime_type: str
@@ -46,7 +47,7 @@ class ImageListResponse(BaseModel):
46
47
 
47
48
  class ImageDataResponse(BaseModel):
48
49
  """Response containing image base64 data."""
49
- id: int
50
+ id: str # Opaque token (SDR-4437 F-TM-7).
50
51
  mime_type: str
51
52
  base64_data: str
52
53
  data_uri: str # Ready-to-use: "data:image/png;base64,..."
@@ -80,7 +81,7 @@ def _get_current_user() -> str:
80
81
 
81
82
  def _image_to_response(img: ImageAsset) -> ImageResponse:
82
83
  return ImageResponse(
83
- id=img.id,
84
+ id=img.token, # Expose the opaque token, never the enumerable int PK.
84
85
  filename=img.filename,
85
86
  original_filename=img.original_filename,
86
87
  mime_type=img.mime_type,
@@ -167,58 +168,75 @@ def list_images(
167
168
  )
168
169
 
169
170
 
170
- @router.get("/{image_id}", response_model=ImageResponse)
171
- def get_image(image_id: int, db: Session = Depends(get_db)):
172
- """Get image metadata by ID."""
171
+ def _deny_ephemeral_cross_user(image: ImageAsset) -> None:
172
+ """Defense-in-depth (SDR-4437 F-TM-7): chat-pasted ("ephemeral") images are
173
+ private to their uploader. Library images stay open-read. Raise 404 (not 403)
174
+ so a wrong caller cannot even confirm the image exists.
175
+ """
176
+ if image.category == "ephemeral" and image.uploaded_by != _get_current_user():
177
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Image not found")
178
+
179
+
180
+ @router.get("/{token}", response_model=ImageResponse)
181
+ def get_image(token: str, db: Session = Depends(get_db)):
182
+ """Get image metadata by opaque token."""
173
183
  try:
174
184
  image = db.query(ImageAsset).filter(
175
- ImageAsset.id == image_id,
185
+ ImageAsset.token == token,
176
186
  ImageAsset.is_active == True,
177
187
  ).first()
178
188
  if not image:
179
189
  raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Image not found")
190
+ _deny_ephemeral_cross_user(image)
180
191
  return _image_to_response(image)
181
192
  except HTTPException:
182
193
  raise
183
194
  except Exception as e:
184
- logger.error(f"Error getting image {image_id}: {e}", exc_info=True)
195
+ logger.error(f"Error getting image: {e}", exc_info=True)
185
196
  raise HTTPException(
186
197
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
187
198
  detail="Failed to get image",
188
199
  )
189
200
 
190
201
 
191
- @router.get("/{image_id}/data", response_model=ImageDataResponse)
192
- def get_image_data(image_id: int, db: Session = Depends(get_db)):
193
- """Get full image as base64 data (for embedding in slides)."""
202
+ @router.get("/{token}/data", response_model=ImageDataResponse)
203
+ def get_image_data(token: str, db: Session = Depends(get_db)):
204
+ """Get full image as base64 data (for embedding in slides), keyed by token."""
194
205
  try:
195
- b64_data, mime_type = image_service.get_image_base64(db, image_id)
206
+ image = db.query(ImageAsset).filter(
207
+ ImageAsset.token == token,
208
+ ImageAsset.is_active == True,
209
+ ).first()
210
+ if not image:
211
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Image not found")
212
+ _deny_ephemeral_cross_user(image)
213
+ b64_data = base64.b64encode(image.image_data).decode("utf-8")
196
214
  return ImageDataResponse(
197
- id=image_id,
198
- mime_type=mime_type,
215
+ id=image.token,
216
+ mime_type=image.mime_type,
199
217
  base64_data=b64_data,
200
- data_uri=f"data:{mime_type};base64,{b64_data}",
218
+ data_uri=f"data:{image.mime_type};base64,{b64_data}",
201
219
  )
202
- except ValueError as e:
203
- raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
220
+ except HTTPException:
221
+ raise
204
222
  except Exception as e:
205
- logger.error(f"Error getting image data {image_id}: {e}", exc_info=True)
223
+ logger.error(f"Error getting image data: {e}", exc_info=True)
206
224
  raise HTTPException(
207
225
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
208
226
  detail="Failed to get image data",
209
227
  )
210
228
 
211
229
 
212
- @router.put("/{image_id}", response_model=ImageResponse)
230
+ @router.put("/{token}", response_model=ImageResponse)
213
231
  def update_image(
214
- image_id: int,
232
+ token: str,
215
233
  request: ImageUpdateRequest,
216
234
  db: Session = Depends(get_db),
217
235
  ):
218
236
  """Update image metadata (tags, description, category)."""
219
237
  try:
220
238
  image = db.query(ImageAsset).filter(
221
- ImageAsset.id == image_id,
239
+ ImageAsset.token == token,
222
240
  ImageAsset.is_active == True,
223
241
  ).first()
224
242
  if not image:
@@ -246,20 +264,20 @@ def update_image(
246
264
  raise
247
265
  except Exception as e:
248
266
  db.rollback()
249
- logger.error(f"Error updating image {image_id}: {e}", exc_info=True)
267
+ logger.error(f"Error updating image: {e}", exc_info=True)
250
268
  raise HTTPException(
251
269
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
252
270
  detail="Failed to update image",
253
271
  )
254
272
 
255
273
 
256
- @router.delete("/{image_id}", status_code=status.HTTP_204_NO_CONTENT)
257
- def delete_image(image_id: int, db: Session = Depends(get_db)):
274
+ @router.delete("/{token}", status_code=status.HTTP_204_NO_CONTENT)
275
+ def delete_image(token: str, db: Session = Depends(get_db)):
258
276
  """Soft-delete an image."""
259
277
  try:
260
278
  user = _get_current_user()
261
279
  image = db.query(ImageAsset).filter(
262
- ImageAsset.id == image_id,
280
+ ImageAsset.token == token,
263
281
  ImageAsset.is_active == True, # noqa: E712
264
282
  ).first()
265
283
  if not image:
@@ -270,14 +288,14 @@ def delete_image(image_id: int, db: Session = Depends(get_db)):
270
288
  status_code=status.HTTP_403_FORBIDDEN,
271
289
  detail="You don't have permission to modify this image",
272
290
  )
273
- image_service.delete_image(db, image_id, user)
291
+ image_service.delete_image(db, token, user)
274
292
  except HTTPException:
275
293
  raise
276
294
  except ValueError as e:
277
295
  raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
278
296
  except Exception as e:
279
297
  db.rollback()
280
- logger.error(f"Error deleting image {image_id}: {e}", exc_info=True)
298
+ logger.error(f"Error deleting image: {e}", exc_info=True)
281
299
  raise HTTPException(
282
300
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
283
301
  detail="Failed to delete image",
@@ -74,9 +74,9 @@ class ChatRequest(BaseModel):
74
74
  default=None,
75
75
  description="Optional context containing the slides to edit",
76
76
  )
77
- image_ids: Optional[list[int]] = Field(
77
+ image_ids: Optional[list[str]] = Field(
78
78
  default=None,
79
- description="IDs of images attached to this message (from upload or paste)",
79
+ description="Opaque tokens of images attached to this message (SDR-4437 F-TM-7)",
80
80
  )
81
81
  agent_config: Optional[dict] = Field(
82
82
  default=None,
@@ -345,7 +345,7 @@ class ChatService:
345
345
  session_id: str,
346
346
  message: str,
347
347
  slide_context: Optional[Dict[str, Any]] = None,
348
- image_ids: Optional[List[int]] = None,
348
+ image_ids: Optional[List[str]] = None,
349
349
  ) -> Dict[str, Any]:
350
350
  """Send a message to the agent and get response.
351
351
 
@@ -828,7 +828,7 @@ class ChatService:
828
828
  message: str,
829
829
  slide_context: Optional[Dict[str, Any]] = None,
830
830
  request_id: Optional[str] = None,
831
- image_ids: Optional[List[int]] = None,
831
+ image_ids: Optional[List[str]] = None,
832
832
  is_first_message_override: Optional[bool] = None,
833
833
  ) -> Generator[StreamEvent, None, None]:
834
834
  """Send a message and yield streaming events.
@@ -1993,27 +1993,30 @@ class ChatService:
1993
1993
 
1994
1994
  return ([], None)
1995
1995
 
1996
- def _inject_image_context(self, message: str, image_ids: List[int]) -> str:
1997
- """Append image metadata to user message so the agent knows about attached images."""
1996
+ def _inject_image_context(self, message: str, image_ids: List[str]) -> str:
1997
+ """Append image metadata to user message so the agent knows about attached images.
1998
+
1999
+ ``image_ids`` are opaque image tokens (SDR-4437 F-TM-7), not int PKs.
2000
+ """
1998
2001
  from src.core.database import get_db_session
1999
2002
  from src.services import image_service
2000
2003
 
2001
2004
  image_descriptions = []
2002
2005
  with get_db_session() as db:
2003
- for img_id in image_ids:
2006
+ for img_token in image_ids:
2004
2007
  try:
2005
2008
  img = db.query(image_service.ImageAsset).filter(
2006
- image_service.ImageAsset.id == img_id,
2009
+ image_service.ImageAsset.token == img_token,
2007
2010
  image_service.ImageAsset.is_active == True,
2008
2011
  ).first()
2009
2012
  if img:
2010
2013
  image_descriptions.append(
2011
- f'- Image ID {img.id}: "{img.original_filename}" '
2014
+ f'- Image ID {img.token}: "{img.original_filename}" '
2012
2015
  f'({img.description or "no description"}). '
2013
- f'Use: <img src="{{{{image:{img.id}}}}}" alt="{img.description or img.original_filename}" />'
2016
+ f'Use: <img src="{{{{image:{img.token}}}}}" alt="{img.description or img.original_filename}" />'
2014
2017
  )
2015
2018
  except Exception as e:
2016
- logger.warning(f"Failed to load image {img_id} for context injection: {e}")
2019
+ logger.warning(f"Failed to load image {img_token} for context injection: {e}")
2017
2020
 
2018
2021
  if not image_descriptions:
2019
2022
  return message
@@ -524,6 +524,8 @@ def _run_migrations(engine, schema: str | None = None):
524
524
  # --- image_assets.tags: json → jsonb (PostgreSQL) for native @> queries ---
525
525
  _migrate_image_assets_tags_json_to_jsonb(conn, schema, _qual, is_sqlite)
526
526
 
527
+ # --- image_assets.token: unguessable external id + backfill (SDR-4437 F-TM-7) ---
528
+ _migrate_image_assets_add_token(conn, inspector, schema, _qual, is_sqlite)
527
529
  # --- agent_config: style precedence in the DB, for the raw-SQL writers no
528
530
  # --- Python-side bind hook can see (PostgreSQL only; no-op on SQLite) ---
529
531
  _migrate_agent_config_precedence_trigger(
@@ -1058,6 +1060,44 @@ def _ensure_llm_judge_backend_default(
1058
1060
  )
1059
1061
 
1060
1062
 
1063
+ def _migrate_image_assets_add_token(conn, inspector, schema, _qual, is_sqlite) -> None:
1064
+ """Add the unguessable ``token`` column to image_assets and backfill (SDR-4437 F-TM-7).
1065
+
1066
+ The int PK is enumerable, so image reads now key on an opaque token. Fresh
1067
+ installs get the column via create_all(); existing tables need this ALTER +
1068
+ per-row backfill. Idempotent: gated on the column's absence.
1069
+ """
1070
+ import secrets
1071
+
1072
+ from sqlalchemy import text
1073
+
1074
+ table = "image_assets"
1075
+ try:
1076
+ cols = {c["name"] for c in inspector.get_columns(table, schema=schema)}
1077
+ except Exception:
1078
+ return
1079
+ if not cols or "token" in cols:
1080
+ return
1081
+
1082
+ q = _qual(table)
1083
+ logger.info(f"Migration: adding token column to {table} (SDR-4437 F-TM-7)")
1084
+ # Add nullable first so the ALTER succeeds on a populated table.
1085
+ conn.execute(text(f"ALTER TABLE {q} ADD COLUMN token VARCHAR(64)"))
1086
+
1087
+ # Backfill every existing row with a distinct unguessable token.
1088
+ row_ids = [r[0] for r in conn.execute(text(f"SELECT id FROM {q} WHERE token IS NULL")).fetchall()]
1089
+ for row_id in row_ids:
1090
+ conn.execute(
1091
+ text(f"UPDATE {q} SET token = :tok WHERE id = :id"),
1092
+ {"tok": secrets.token_urlsafe(32), "id": row_id},
1093
+ )
1094
+
1095
+ # Enforce uniqueness (and non-null on PostgreSQL, now that every row has a token).
1096
+ conn.execute(text(
1097
+ f"CREATE UNIQUE INDEX IF NOT EXISTS ix_image_assets_token ON {q} (token)"
1098
+ ))
1099
+ if not is_sqlite:
1100
+ conn.execute(text(f"ALTER TABLE {q} ALTER COLUMN token SET NOT NULL"))
1061
1101
  #: The one place the precedence rule is written in SQL. ``$$``-quoted so the body
1062
1102
  #: needs no escaping, and named after the rule rather than the tables, since both
1063
1103
  #: ``agent_config`` columns share it.
@@ -1,4 +1,5 @@
1
1
  """Image asset model — metadata and binary data stored together in Lakebase."""
2
+ import secrets
2
3
  from datetime import datetime
3
4
 
4
5
  from sqlalchemy import Boolean, Column, DateTime, Integer, JSON, LargeBinary, String, Text
@@ -10,6 +11,17 @@ from src.core.database import Base
10
11
  _TagsColumn = JSON().with_variant(JSONB(), "postgresql")
11
12
 
12
13
 
14
+ def _new_image_token() -> str:
15
+ """Unguessable external identifier (SDR-4437 F-TM-7).
16
+
17
+ The autoincrement int PK is enumerable, so it is never exposed. This
18
+ opaque token is the only identifier that leaves the server — it is what
19
+ the API returns, what chat sends in ``image_ids``, and what goes in
20
+ ``{{image:...}}`` slide placeholders.
21
+ """
22
+ return secrets.token_urlsafe(32)
23
+
24
+
13
25
  class ImageAsset(Base):
14
26
  """Uploaded image with binary data stored directly in PostgreSQL.
15
27
 
@@ -20,7 +32,9 @@ class ImageAsset(Base):
20
32
 
21
33
  __tablename__ = "image_assets"
22
34
 
23
- id = Column(Integer, primary_key=True)
35
+ id = Column(Integer, primary_key=True) # Internal only — never serialized.
36
+ # Unguessable external identifier (SDR-4437 F-TM-7). token_urlsafe(32) → 43 chars.
37
+ token = Column(String(64), unique=True, nullable=False, index=True, default=_new_image_token)
24
38
  filename = Column(String(255), nullable=False) # Generated: {uuid}.{ext}
25
39
  original_filename = Column(String(255), nullable=False) # User's original filename
26
40
  mime_type = Column(String(50), nullable=False) # image/png, image/jpeg, image/gif, image/svg+xml
@@ -70,9 +70,20 @@ class NormalizedAgentConfig(TypeDecorator):
70
70
  not have its caller's dict edited underneath it).
71
71
 
72
72
  Emits an ordinary ``JSON`` column, so adopting it needs no migration.
73
+
74
+ ``none_as_null=True`` is load-bearing, not tidiness. ``sqlalchemy.JSON`` defaults
75
+ to ``none_as_null=False``, which serializes a bound ``None`` into the two-byte
76
+ document ``null`` instead of leaving the column as SQL NULL — and the JSON scalar
77
+ ``null`` is NOT SQL NULL. It is a stored value that merely READS as empty:
78
+ ``IS NULL`` does not match it, while the ORM attribute deserializes it to ``None``
79
+ exactly like SQL NULL, so nothing above the driver can tell the two apart. The
80
+ startup backfill ``migrate_profiles`` selects the rows it has to fill with
81
+ ``agent_config.is_(None)``, so adopting this type silently made every freshly
82
+ seeded profile invisible to it. See
83
+ ``tests/unit/test_unset_agent_config_is_sql_null.py``.
73
84
  """
74
85
 
75
- impl = JSON
86
+ impl = JSON(none_as_null=True)
76
87
  cache_ok = True
77
88
 
78
89
  def process_bind_param(self, value: Any, dialect: Any) -> Any:
@@ -51,7 +51,7 @@ def upload_image(
51
51
  ).first()
52
52
  if existing:
53
53
  raise ValueError(
54
- f"An image named '{original_filename}' already exists (id={existing.id}). "
54
+ f"An image named '{original_filename}' already exists. "
55
55
  "Please rename the file or delete the existing image first."
56
56
  )
57
57
 
@@ -86,19 +86,19 @@ def upload_image(
86
86
  return image
87
87
 
88
88
 
89
- def get_image_base64(db: Session, image_id: int) -> tuple[str, str]:
89
+ def get_image_base64(db: Session, token: str) -> tuple[str, str]:
90
90
  """
91
- Get full image as base64 string.
91
+ Get full image as base64 string, keyed by the opaque token (SDR-4437 F-TM-7).
92
92
 
93
93
  Returns:
94
94
  Tuple of (base64_data, mime_type)
95
95
  """
96
96
  image = db.query(ImageAsset).filter(
97
- ImageAsset.id == image_id,
97
+ ImageAsset.token == token,
98
98
  ImageAsset.is_active == True,
99
99
  ).first()
100
100
  if not image:
101
- raise ValueError(f"Image {image_id} not found")
101
+ raise ValueError(f"Image {token} not found")
102
102
 
103
103
  b64 = base64.b64encode(image.image_data).decode("utf-8")
104
104
  return b64, image.mime_type
@@ -175,11 +175,11 @@ def search_images(
175
175
  ).all()
176
176
 
177
177
 
178
- def delete_image(db: Session, image_id: int, user: str) -> None:
179
- """Soft-delete an image."""
180
- image = db.query(ImageAsset).filter(ImageAsset.id == image_id).first()
178
+ def delete_image(db: Session, token: str, user: str) -> None:
179
+ """Soft-delete an image, keyed by the opaque token (SDR-4437 F-TM-7)."""
180
+ image = db.query(ImageAsset).filter(ImageAsset.token == token).first()
181
181
  if not image:
182
- raise ValueError(f"Image {image_id} not found")
182
+ raise ValueError(f"Image {token} not found")
183
183
 
184
184
  image.is_active = False
185
185
  image.updated_by = user
@@ -53,13 +53,13 @@ def search_images(
53
53
  # Must build results inside session scope to avoid DetachedInstanceError
54
54
  results = [
55
55
  {
56
- "id": img.id,
56
+ "id": img.token, # Opaque token (SDR-4437 F-TM-7), never the int PK.
57
57
  "filename": img.original_filename,
58
58
  "description": img.description,
59
59
  "tags": img.tags,
60
60
  "category": img.category,
61
61
  "mime_type": img.mime_type,
62
- "usage": f'<img src="{{{{image:{img.id}}}}}" alt="{img.description or img.original_filename}" />',
62
+ "usage": f'<img src="{{{{image:{img.token}}}}}" alt="{img.description or img.original_filename}" />',
63
63
  }
64
64
  for img in images
65
65
  ]
@@ -8,7 +8,8 @@ from src.services import image_service
8
8
 
9
9
  logger = logging.getLogger(__name__)
10
10
 
11
- IMAGE_PLACEHOLDER_PATTERN = re.compile(r"\{\{image:(\d+)\}\}")
11
+ # Tokens are secrets.token_urlsafe(...) → [A-Za-z0-9_-] (SDR-4437 F-TM-7).
12
+ IMAGE_PLACEHOLDER_PATTERN = re.compile(r"\{\{image:([A-Za-z0-9_-]+)\}\}")
12
13
 
13
14
 
14
15
  def substitute_image_placeholders(html: str, db: Session) -> str:
@@ -22,12 +23,12 @@ def substitute_image_placeholders(html: str, db: Session) -> str:
22
23
  return html
23
24
 
24
25
  def replace_match(match):
25
- image_id = int(match.group(1))
26
+ image_token = match.group(1)
26
27
  try:
27
- b64_data, mime_type = image_service.get_image_base64(db, image_id)
28
+ b64_data, mime_type = image_service.get_image_base64(db, image_token)
28
29
  return f"data:{mime_type};base64,{b64_data}"
29
30
  except Exception as e:
30
- logger.warning(f"Failed to resolve image placeholder {{{{image:{image_id}}}}}: {e}")
31
+ logger.warning(f"Failed to resolve image placeholder {{{{image:{image_token}}}}}: {e}")
31
32
  return match.group(0) # Leave placeholder if image not found
32
33
 
33
34
  return IMAGE_PLACEHOLDER_PATTERN.sub(replace_match, html)