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.
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/PKG-INFO +8 -8
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/databricks_tellr_app/run.py +12 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/databricks_tellr_app.egg-info/PKG-INFO +8 -8
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/databricks_tellr_app.egg-info/SOURCES.txt +2 -14
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/databricks_tellr_app.egg-info/requires.txt +7 -7
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/pyproject.toml +10 -8
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/services/export_job_queue.py +23 -10
- databricks_tellr_app-0.3.13.dev4/src/core/encryption.py +125 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/lakebase.py +0 -114
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/__init__.py +2 -0
- databricks_tellr_app-0.3.13.dev4/src/database/models/encryption_key.py +30 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/google_slides_prompts_defaults.py +18 -28
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/html_to_google_slides.py +185 -246
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/html_to_pptx.py +453 -73
- databricks_tellr_app-0.3.13.dev3/src/core/encryption.py +0 -86
- databricks_tellr_app-0.3.13.dev3/src/services/converter_jail/__init__.py +0 -10
- databricks_tellr_app-0.3.13.dev3/src/services/converter_jail/ast_guard.py +0 -58
- databricks_tellr_app-0.3.13.dev3/src/services/converter_jail/codeprep.py +0 -220
- databricks_tellr_app-0.3.13.dev3/src/services/converter_jail/gslides_runner.py +0 -78
- databricks_tellr_app-0.3.13.dev3/src/services/converter_jail/jail.py +0 -230
- databricks_tellr_app-0.3.13.dev3/src/services/converter_jail/pptx_runner.py +0 -124
- databricks_tellr_app-0.3.13.dev3/src/services/converter_jail/protocol.py +0 -41
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/README.md +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/databricks_tellr_app/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/databricks_tellr_app.egg-info/dependency_links.txt +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/databricks_tellr_app.egg-info/top_level.txt +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/setup.cfg +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/setup.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/_otel_bootstrap.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/fixtures/tour_demo_deck.json +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/main.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/mcp_auth.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/mcp_server.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/middleware/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/middleware/request_logging.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/admin.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/admin_usage.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/agent_config.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/chat.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/deck_contributors.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/export.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/feedback.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/google_slides.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/images.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/local_version.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/profiles.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/sessions.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/settings/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/settings/contributors.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/settings/deck_prompts.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/settings/identities.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/settings/slide_styles.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/setup.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/slides.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/tools.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/tour.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/verification.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/routes/version.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/schemas/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/schemas/agent_config.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/schemas/feedback.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/schemas/requests.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/schemas/responses.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/schemas/settings/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/schemas/settings/requests.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/schemas/settings/responses.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/schemas/streaming.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/services/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/services/chat_service.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/services/feedback_service.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/services/job_queue.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/services/session_manager.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/services/session_naming.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/services/usage_events.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/services/usage_service.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/utils/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/api/utils/validation.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/config_loader.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/context_utils.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/database.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/databricks_client.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/defaults.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/init_default_profile.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/migrate_profiles_to_agent_config.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/mlflow_agent_spans.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/mlflow_tracing.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/otel_logging.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/permission_context.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/prompt_modules.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/settings_db.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/core/user_context.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/deck_contributor.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/feedback.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/genie_space.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/google_global_credentials.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/google_oauth_token.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/identity.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/image.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/profile.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/profile_contributor.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/prompts.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/request_log.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/session.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/slide_deck_prompt.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/slide_style_library.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/usage_event.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/database/models/user_preference.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/domain/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/domain/slide.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/domain/slide_deck.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/agent.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/agent_factory.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/config_service.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/config_validator.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/drive_uploader.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/evaluation/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/evaluation/llm_judge.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/genie_service.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/google_slides_auth.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/identity_provider.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/identity_providers/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/identity_providers/account_provider.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/identity_providers/local_provider.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/identity_providers/workspace_provider.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/image_service.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/image_tools.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/permission_service.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/pptx_from_html_huashu.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/pptx_from_records.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/pptx_prompts_defaults.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/profile_service.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/streaming_callback.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/tools/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/tools/agent_bricks_tool.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/tools/genie_tool.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/tools/mcp_tool.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/tools/model_endpoint_tool.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/tools/vector_tool.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/services/validator.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/utils/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/utils/css_utils.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/utils/error_handling.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/utils/html_safety.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/utils/html_utils.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/utils/image_utils.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/utils/js_validator.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/utils/logging_config.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/utils/pi_filter.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/utils/slide_hash.py +0 -0
- {databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/src/utils/spotlight.py +0 -0
- {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.
|
|
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
|
|
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
|
|
36
|
-
Requires-Dist: google-api-python-client
|
|
37
|
-
Requires-Dist: google-auth-oauthlib
|
|
38
|
-
Requires-Dist: google-auth-httplib2
|
|
39
|
-
Requires-Dist: cryptography
|
|
40
|
-
Requires-Dist: sse-starlette
|
|
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
|
{databricks_tellr_app-0.3.13.dev3 → databricks_tellr_app-0.3.13.dev4}/databricks_tellr_app/run.py
RENAMED
|
@@ -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.
|
|
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
|
|
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
|
|
36
|
-
Requires-Dist: google-api-python-client
|
|
37
|
-
Requires-Dist: google-auth-oauthlib
|
|
38
|
-
Requires-Dist: google-auth-httplib2
|
|
39
|
-
Requires-Dist: cryptography
|
|
40
|
-
Requires-Dist: sse-starlette
|
|
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
|
|
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
|
|
30
|
-
google-api-python-client
|
|
31
|
-
google-auth-oauthlib
|
|
32
|
-
google-auth-httplib2
|
|
33
|
-
cryptography
|
|
34
|
-
sse-starlette
|
|
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.
|
|
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
|
|
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
|
|
41
|
-
"google-api-python-client
|
|
42
|
-
"google-auth-oauthlib
|
|
43
|
-
"google-auth-httplib2
|
|
44
|
-
"cryptography
|
|
45
|
-
"sse-starlette
|
|
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
|
|
337
|
-
|
|
338
|
-
|
|
339
|
+
# -- Phase 2: Sequential execution ----------------------------------------
|
|
340
|
+
prs = Presentation()
|
|
341
|
+
prs.slide_width = Inches(10)
|
|
342
|
+
prs.slide_height = Inches(7.5)
|
|
339
343
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
|
73
|
-
-
|
|
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
|
|
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
|
-
-
|
|
126
|
-
-
|
|
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
|
-
|
|
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
|
|
179
|
-
|
|
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
|
|
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
|
-
-
|
|
191
|
-
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|