databricks-tellr-app 0.3.13.dev2__tar.gz → 0.3.13.dev3__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.dev2 → databricks_tellr_app-0.3.13.dev3}/PKG-INFO +1 -1
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/databricks_tellr_app.egg-info/PKG-INFO +1 -1
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/databricks_tellr_app.egg-info/SOURCES.txt +14 -4
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/pyproject.toml +1 -1
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/main.py +0 -18
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/services/export_job_queue.py +10 -23
- databricks_tellr_app-0.3.13.dev3/src/services/converter_jail/__init__.py +10 -0
- databricks_tellr_app-0.3.13.dev3/src/services/converter_jail/ast_guard.py +58 -0
- databricks_tellr_app-0.3.13.dev3/src/services/converter_jail/codeprep.py +220 -0
- databricks_tellr_app-0.3.13.dev3/src/services/converter_jail/gslides_runner.py +78 -0
- databricks_tellr_app-0.3.13.dev3/src/services/converter_jail/jail.py +230 -0
- databricks_tellr_app-0.3.13.dev3/src/services/converter_jail/pptx_runner.py +124 -0
- databricks_tellr_app-0.3.13.dev3/src/services/converter_jail/protocol.py +41 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/google_slides_prompts_defaults.py +28 -18
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/html_to_google_slides.py +246 -185
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/html_to_pptx.py +73 -453
- databricks_tellr_app-0.3.13.dev2/src/api/middleware/csrf.py +0 -109
- databricks_tellr_app-0.3.13.dev2/src/api/middleware/security_headers.py +0 -97
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/README.md +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/databricks_tellr_app/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/databricks_tellr_app/run.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/databricks_tellr_app.egg-info/dependency_links.txt +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/databricks_tellr_app.egg-info/requires.txt +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/databricks_tellr_app.egg-info/top_level.txt +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/setup.cfg +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/setup.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/_otel_bootstrap.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/fixtures/tour_demo_deck.json +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/mcp_auth.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/mcp_server.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/middleware/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/middleware/request_logging.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/routes/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/routes/admin.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/routes/admin_usage.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/routes/agent_config.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/routes/chat.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/routes/deck_contributors.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/routes/export.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/routes/feedback.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/routes/google_slides.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/routes/images.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/routes/local_version.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/routes/profiles.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/routes/sessions.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/routes/settings/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/routes/settings/contributors.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/routes/settings/deck_prompts.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/routes/settings/identities.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/routes/settings/slide_styles.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/routes/setup.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/routes/slides.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/routes/tools.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/routes/tour.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/routes/verification.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/routes/version.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/schemas/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/schemas/agent_config.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/schemas/feedback.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/schemas/requests.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/schemas/responses.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/schemas/settings/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/schemas/settings/requests.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/schemas/settings/responses.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/schemas/streaming.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/services/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/services/chat_service.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/services/feedback_service.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/services/job_queue.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/services/session_manager.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/services/session_naming.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/services/usage_events.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/services/usage_service.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/utils/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/api/utils/validation.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/core/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/core/config_loader.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/core/context_utils.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/core/database.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/core/databricks_client.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/core/defaults.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/core/encryption.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/core/init_default_profile.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/core/lakebase.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/core/migrate_profiles_to_agent_config.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/core/mlflow_agent_spans.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/core/mlflow_tracing.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/core/otel_logging.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/core/permission_context.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/core/prompt_modules.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/core/settings_db.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/core/user_context.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/database/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/database/models/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/database/models/deck_contributor.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/database/models/feedback.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/database/models/genie_space.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/database/models/google_global_credentials.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/database/models/google_oauth_token.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/database/models/identity.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/database/models/image.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/database/models/profile.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/database/models/profile_contributor.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/database/models/prompts.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/database/models/request_log.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/database/models/session.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/database/models/slide_deck_prompt.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/database/models/slide_style_library.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/database/models/usage_event.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/database/models/user_preference.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/domain/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/domain/slide.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/domain/slide_deck.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/agent.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/agent_factory.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/config_service.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/config_validator.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/drive_uploader.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/evaluation/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/evaluation/llm_judge.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/genie_service.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/google_slides_auth.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/identity_provider.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/identity_providers/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/identity_providers/account_provider.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/identity_providers/local_provider.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/identity_providers/workspace_provider.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/image_service.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/image_tools.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/permission_service.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/pptx_from_html_huashu.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/pptx_from_records.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/pptx_prompts_defaults.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/profile_service.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/streaming_callback.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/tools/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/tools/agent_bricks_tool.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/tools/genie_tool.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/tools/mcp_tool.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/tools/model_endpoint_tool.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/tools/vector_tool.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/services/validator.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/utils/__init__.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/utils/css_utils.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/utils/error_handling.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/utils/html_safety.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/utils/html_utils.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/utils/image_utils.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/utils/js_validator.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/utils/logging_config.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/utils/pi_filter.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/utils/slide_hash.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/utils/spotlight.py +0 -0
- {databricks_tellr_app-0.3.13.dev2 → databricks_tellr_app-0.3.13.dev3}/src/utils/text_caps.py +0 -0
|
@@ -11,9 +11,7 @@ setup.py
|
|
|
11
11
|
./src/api/mcp_server.py
|
|
12
12
|
./src/api/fixtures/tour_demo_deck.json
|
|
13
13
|
./src/api/middleware/__init__.py
|
|
14
|
-
./src/api/middleware/csrf.py
|
|
15
14
|
./src/api/middleware/request_logging.py
|
|
16
|
-
./src/api/middleware/security_headers.py
|
|
17
15
|
./src/api/routes/__init__.py
|
|
18
16
|
./src/api/routes/admin.py
|
|
19
17
|
./src/api/routes/admin_usage.py
|
|
@@ -117,6 +115,13 @@ setup.py
|
|
|
117
115
|
./src/services/profile_service.py
|
|
118
116
|
./src/services/streaming_callback.py
|
|
119
117
|
./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
|
|
120
125
|
./src/services/evaluation/__init__.py
|
|
121
126
|
./src/services/evaluation/llm_judge.py
|
|
122
127
|
./src/services/identity_providers/__init__.py
|
|
@@ -156,9 +161,7 @@ src/api/mcp_auth.py
|
|
|
156
161
|
src/api/mcp_server.py
|
|
157
162
|
src/api/fixtures/tour_demo_deck.json
|
|
158
163
|
src/api/middleware/__init__.py
|
|
159
|
-
src/api/middleware/csrf.py
|
|
160
164
|
src/api/middleware/request_logging.py
|
|
161
|
-
src/api/middleware/security_headers.py
|
|
162
165
|
src/api/routes/__init__.py
|
|
163
166
|
src/api/routes/admin.py
|
|
164
167
|
src/api/routes/admin_usage.py
|
|
@@ -262,6 +265,13 @@ src/services/pptx_prompts_defaults.py
|
|
|
262
265
|
src/services/profile_service.py
|
|
263
266
|
src/services/streaming_callback.py
|
|
264
267
|
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
|
|
265
275
|
src/services/evaluation/__init__.py
|
|
266
276
|
src/services/evaluation/llm_judge.py
|
|
267
277
|
src/services/identity_providers/__init__.py
|
|
@@ -417,24 +417,6 @@ from src.api.middleware.request_logging import RequestLoggingMiddleware
|
|
|
417
417
|
|
|
418
418
|
app.add_middleware(RequestLoggingMiddleware)
|
|
419
419
|
|
|
420
|
-
# === SDR-4437 Track A security middleware (PR-1a/PR-1b) ==================
|
|
421
|
-
# Keep ALL security-middleware registration inside this delimited block
|
|
422
|
-
# (PR-2 adds an exception handler below the middleware block — do not edit
|
|
423
|
-
# outside these markers). Starlette wraps later-added middleware outermost;
|
|
424
|
-
# ordering here is deliberate:
|
|
425
|
-
# SecurityHeaders (outermost, added last) -> CSRF -> RequestLogging ->
|
|
426
|
-
# user_auth -> CORS(dev) -> normalize_mcp_path -> routes
|
|
427
|
-
# Headers must stamp EVERY response (including CSRF 403s); CSRF must reject
|
|
428
|
-
# forged cross-site requests before OBO auth work and before request_logs
|
|
429
|
-
# DB writes. CSRF runs outside normalize_mcp_path, so it sees the raw
|
|
430
|
-
# un-rewritten /mcp path (its exemption handles both /mcp and /mcp/).
|
|
431
|
-
from src.api.middleware.csrf import CSRFProtectionMiddleware # noqa: E402
|
|
432
|
-
from src.api.middleware.security_headers import SecurityHeadersMiddleware # noqa: E402
|
|
433
|
-
|
|
434
|
-
app.add_middleware(CSRFProtectionMiddleware)
|
|
435
|
-
app.add_middleware(SecurityHeadersMiddleware)
|
|
436
|
-
# === end SDR-4437 Track A security middleware =============================
|
|
437
|
-
|
|
438
420
|
# Include API routers
|
|
439
421
|
app.include_router(admin.router)
|
|
440
422
|
app.include_router(admin_usage.router)
|
|
@@ -307,9 +307,6 @@ 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
|
-
|
|
313
310
|
total_slides = len(slides_html)
|
|
314
311
|
|
|
315
312
|
# -- Phase 1: Prepare assets + parallel LLM code generation ---------------
|
|
@@ -336,27 +333,17 @@ async def convert_slides_with_progress(
|
|
|
336
333
|
slide_inputs, on_codegen_progress=_on_codegen_progress,
|
|
337
334
|
)
|
|
338
335
|
|
|
339
|
-
# -- Phase 2: Sequential execution
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
prs.slide_height = Inches(7.5)
|
|
343
|
-
|
|
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
|
-
)
|
|
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)
|
|
358
339
|
|
|
359
|
-
|
|
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
|
+
)
|
|
360
347
|
|
|
361
348
|
|
|
362
349
|
async def export_worker() -> None:
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# src/services/converter_jail/__init__.py
|
|
2
|
+
"""Subprocess jail for LLM-generated converter code (SDR-4437 HIGH-5)."""
|
|
3
|
+
|
|
4
|
+
from src.services.converter_jail.jail import ( # noqa: F401
|
|
5
|
+
JailError,
|
|
6
|
+
JailResult,
|
|
7
|
+
ResourceLimits,
|
|
8
|
+
run_gslides_jail,
|
|
9
|
+
run_pptx_jail,
|
|
10
|
+
)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# src/services/converter_jail/ast_guard.py
|
|
2
|
+
"""Best-effort AST import allowlist for generated converter code.
|
|
3
|
+
|
|
4
|
+
Defense-in-depth ONLY — bypassable by construction (e.g. __import__,
|
|
5
|
+
importlib). The security boundary is the subprocess jail, not this check.
|
|
6
|
+
Runs on the trusted host before launch so obviously-hostile code never
|
|
7
|
+
reaches the child.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import ast
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DisallowedImport(Exception):
|
|
14
|
+
"""Raised when generated code imports a module outside the allowlist."""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Top-level module names generated converters legitimately need.
|
|
18
|
+
# Keep this GENEROUS for harmless stdlib: a snippet failing this check is
|
|
19
|
+
# downgraded to a blank placeholder slide (has_code=False in the job-dir
|
|
20
|
+
# builders, Tasks 4/6), so a too-narrow list silently blanks legitimate
|
|
21
|
+
# slides. The prompts only show example imports — they do not restrict the
|
|
22
|
+
# LLM to a fixed set — and the jail, not this list, is the security boundary.
|
|
23
|
+
DEFAULT_ALLOWED = frozenset({
|
|
24
|
+
# PPTX path
|
|
25
|
+
"pptx", "os", "re", "math", "json", "uuid", "base64", "io",
|
|
26
|
+
"datetime", "collections", "itertools", "string", "textwrap",
|
|
27
|
+
# harmless stdlib the LLM commonly emits unprompted
|
|
28
|
+
"sys", "time", "typing", "functools", "random", "copy",
|
|
29
|
+
"html", "unicodedata", "decimal",
|
|
30
|
+
# image handling
|
|
31
|
+
"PIL", "lxml",
|
|
32
|
+
# Google Slides path emits data only; googleapiclient is host-side now,
|
|
33
|
+
# but allow the http helper import in case a snippet still references it
|
|
34
|
+
# (host strips its effect; import alone is harmless).
|
|
35
|
+
"googleapiclient",
|
|
36
|
+
"xml",
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _root(name: str) -> str:
|
|
41
|
+
return name.split(".", 1)[0]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def check_imports(code: str, allowed: frozenset = DEFAULT_ALLOWED) -> None:
|
|
45
|
+
"""Raise DisallowedImport if *code* imports a non-allowlisted top-level
|
|
46
|
+
module. Raises SyntaxError if *code* does not parse."""
|
|
47
|
+
tree = ast.parse(code)
|
|
48
|
+
for node in ast.walk(tree):
|
|
49
|
+
if isinstance(node, ast.Import):
|
|
50
|
+
for alias in node.names:
|
|
51
|
+
if _root(alias.name) not in allowed:
|
|
52
|
+
raise DisallowedImport(_root(alias.name))
|
|
53
|
+
elif isinstance(node, ast.ImportFrom):
|
|
54
|
+
# Relative imports (node.level > 0) have no module root to check;
|
|
55
|
+
# they cannot reach outside the (nonexistent) package, so allow.
|
|
56
|
+
if node.level == 0 and node.module is not None:
|
|
57
|
+
if _root(node.module) not in allowed:
|
|
58
|
+
raise DisallowedImport(_root(node.module))
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# src/services/converter_jail/codeprep.py
|
|
2
|
+
"""Dependency-light code-prep helpers shared by the jail runners (SDR-4437 PR-5).
|
|
3
|
+
|
|
4
|
+
STDLIB-ONLY BY DESIGN. This module is imported inside the sandboxed child
|
|
5
|
+
(`python -I`), so it must never pull in `databricks`, `google*`, `bs4`,
|
|
6
|
+
`pptx`, or any `src.services.*` converter module — those graphs drag the
|
|
7
|
+
credential-capable SDK into the jail just to call string transformers.
|
|
8
|
+
|
|
9
|
+
Contents:
|
|
10
|
+
- ``sanitize_code`` — moved verbatim from ``HtmlToPptxConverterV3._sanitize_code``.
|
|
11
|
+
- ``_fix_apostrophe_strings`` / ``_convert_single_to_double_quoted`` — moved
|
|
12
|
+
verbatim from ``html_to_google_slides``.
|
|
13
|
+
- ``prepare_requests_code`` — preps generated Google Slides data-out code
|
|
14
|
+
(wraps bare code into ``build_slide_requests``; request-shape fixes).
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import ast
|
|
18
|
+
import re
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def sanitize_code(code: str) -> str:
|
|
22
|
+
"""Fix common LLM-generated code issues before execution.
|
|
23
|
+
|
|
24
|
+
Handles smart quotes, em-dashes, and apostrophes inside string literals
|
|
25
|
+
that cause SyntaxError (e.g. ``Anthony's`` inside a single-quoted string).
|
|
26
|
+
"""
|
|
27
|
+
# Smart/curly quotes → straight quotes
|
|
28
|
+
for smart, straight in {
|
|
29
|
+
"‘": "'", "’": "'", # single curly quotes
|
|
30
|
+
"“": '"', "”": '"', # double curly quotes
|
|
31
|
+
}.items():
|
|
32
|
+
code = code.replace(smart, straight)
|
|
33
|
+
|
|
34
|
+
# Em-dash / en-dash → regular hyphen (prevents SyntaxError when
|
|
35
|
+
# they appear outside strings due to broken quoting)
|
|
36
|
+
code = code.replace("—", "-") # em-dash
|
|
37
|
+
code = code.replace("–", "-") # en-dash
|
|
38
|
+
|
|
39
|
+
# Fix apostrophes inside single-quoted strings (e.g. 'Anthony's workflow')
|
|
40
|
+
# by converting affected lines to use double-quoted strings.
|
|
41
|
+
for _ in range(20):
|
|
42
|
+
try:
|
|
43
|
+
ast.parse(code)
|
|
44
|
+
break
|
|
45
|
+
except SyntaxError as exc:
|
|
46
|
+
if exc.lineno is None:
|
|
47
|
+
break
|
|
48
|
+
lines = code.splitlines()
|
|
49
|
+
idx = exc.lineno - 1
|
|
50
|
+
if idx < 0 or idx >= len(lines):
|
|
51
|
+
break
|
|
52
|
+
original = lines[idx]
|
|
53
|
+
# Re-quote single-quoted strings that contain apostrophes
|
|
54
|
+
fixed = re.sub(
|
|
55
|
+
r"'([^']*\w'\w[^']*)'",
|
|
56
|
+
lambda m: '"' + m.group(1) + '"',
|
|
57
|
+
original,
|
|
58
|
+
)
|
|
59
|
+
if fixed == original:
|
|
60
|
+
break
|
|
61
|
+
lines[idx] = fixed
|
|
62
|
+
code = "\n".join(lines)
|
|
63
|
+
return code
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
# Apostrophe-in-string syntax fixer
|
|
68
|
+
# ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
def _convert_single_to_double_quoted(line: str) -> str:
|
|
71
|
+
"""Re-quote single-quoted strings that contain apostrophes as double-quoted.
|
|
72
|
+
|
|
73
|
+
Scans each single-quote-delimited token on *line*. When an apostrophe is
|
|
74
|
+
surrounded by word characters (``\\w'\\w``) it is treated as part of the
|
|
75
|
+
content (not a closing delimiter), so the scanner continues until it finds a
|
|
76
|
+
genuine closing quote. Those strings are then re-emitted with double-quote
|
|
77
|
+
delimiters so Python can parse them correctly.
|
|
78
|
+
|
|
79
|
+
Example::
|
|
80
|
+
|
|
81
|
+
'We didn't do it' → "We didn't do it"
|
|
82
|
+
"""
|
|
83
|
+
result: list = []
|
|
84
|
+
i = 0
|
|
85
|
+
n = len(line)
|
|
86
|
+
|
|
87
|
+
while i < n:
|
|
88
|
+
if line[i] != "'":
|
|
89
|
+
result.append(line[i])
|
|
90
|
+
i += 1
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
# Scan for the "real" closing quote, treating word-apostrophe-word as content.
|
|
94
|
+
j = i + 1
|
|
95
|
+
while j < n:
|
|
96
|
+
if line[j] == "'":
|
|
97
|
+
before_word = j > 0 and line[j - 1].isalnum()
|
|
98
|
+
after_word = (j + 1) < n and line[j + 1].isalnum()
|
|
99
|
+
if before_word and after_word:
|
|
100
|
+
j += 1 # it's a contraction apostrophe — keep scanning
|
|
101
|
+
continue
|
|
102
|
+
break # genuine closing quote
|
|
103
|
+
j += 1
|
|
104
|
+
|
|
105
|
+
if j >= n:
|
|
106
|
+
# Never found a real closing quote — rest of line is broken content.
|
|
107
|
+
content = line[i + 1:]
|
|
108
|
+
result.append('"' + content.replace('"', '\\"') + '"')
|
|
109
|
+
break
|
|
110
|
+
|
|
111
|
+
content = line[i + 1: j]
|
|
112
|
+
if re.search(r"\w'\w", content):
|
|
113
|
+
# Content has an apostrophe → rewrite as double-quoted string.
|
|
114
|
+
result.append('"' + content.replace('"', '\\"') + '"')
|
|
115
|
+
else:
|
|
116
|
+
result.append(line[i: j + 1])
|
|
117
|
+
|
|
118
|
+
i = j + 1
|
|
119
|
+
|
|
120
|
+
return "".join(result)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _fix_apostrophe_strings(code: str) -> str:
|
|
124
|
+
"""Fix single-quoted literals whose content contains apostrophes (contractions).
|
|
125
|
+
|
|
126
|
+
The LLM often emits ``'text': 'We don't ...'``. The ``'`` in *don't* ends the
|
|
127
|
+
literal early; the parser may report ``unterminated string``, ``eol while
|
|
128
|
+
scanning``, or ``invalid character`` (e.g. em-dash) on the remainder.
|
|
129
|
+
|
|
130
|
+
Repeatedly ``ast.parse`` and rewrite the error line with
|
|
131
|
+
:func:`_convert_single_to_double_quoted` whenever that changes the line.
|
|
132
|
+
"""
|
|
133
|
+
for _ in range(20): # guard against infinite loops
|
|
134
|
+
try:
|
|
135
|
+
ast.parse(code)
|
|
136
|
+
return code
|
|
137
|
+
except SyntaxError as exc:
|
|
138
|
+
if exc.lineno is None:
|
|
139
|
+
return code
|
|
140
|
+
|
|
141
|
+
lines = code.splitlines()
|
|
142
|
+
line_idx = exc.lineno - 1
|
|
143
|
+
if line_idx >= len(lines):
|
|
144
|
+
return code
|
|
145
|
+
|
|
146
|
+
original = lines[line_idx]
|
|
147
|
+
fixed = _convert_single_to_double_quoted(original)
|
|
148
|
+
if fixed == original:
|
|
149
|
+
return code # heuristic cannot improve this error
|
|
150
|
+
|
|
151
|
+
lines[line_idx] = fixed
|
|
152
|
+
code = "\n".join(lines)
|
|
153
|
+
|
|
154
|
+
return code
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# ---------------------------------------------------------------------------
|
|
158
|
+
# Google Slides data-out contract prep
|
|
159
|
+
# ---------------------------------------------------------------------------
|
|
160
|
+
|
|
161
|
+
_ALPHA_PAT = re.compile(r"""[,\s]*['"]alpha['"]\s*:\s*[\d.]+""")
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def prepare_requests_code(code: str) -> str:
|
|
165
|
+
"""Prep generated code that defines build_slide_requests(...).
|
|
166
|
+
|
|
167
|
+
Reuses the request-shape fixers from the legacy ``_prepare_code`` but wraps
|
|
168
|
+
bare code into ``build_slide_requests`` (not ``add_slide_to_presentation``)
|
|
169
|
+
and never references services."""
|
|
170
|
+
if "def build_slide_requests" not in code:
|
|
171
|
+
lines = code.split("\n")
|
|
172
|
+
imports, body, helpers = [], [], []
|
|
173
|
+
in_helper = False
|
|
174
|
+
for line in lines:
|
|
175
|
+
stripped = line.strip()
|
|
176
|
+
if stripped.startswith(("import ", "from ")) and not body:
|
|
177
|
+
imports.append(line)
|
|
178
|
+
elif stripped.startswith(("def emu(", "def hex_to_rgb(")):
|
|
179
|
+
in_helper = True
|
|
180
|
+
helpers.append(line)
|
|
181
|
+
elif in_helper:
|
|
182
|
+
if stripped == "" or (line and line[0] == " "):
|
|
183
|
+
helpers.append(line)
|
|
184
|
+
if stripped == "":
|
|
185
|
+
in_helper = False
|
|
186
|
+
else:
|
|
187
|
+
in_helper = False
|
|
188
|
+
body.append(line)
|
|
189
|
+
else:
|
|
190
|
+
body.append(line)
|
|
191
|
+
indented = [(" " + l if l.strip() else "") for l in body]
|
|
192
|
+
parts = []
|
|
193
|
+
if imports:
|
|
194
|
+
parts.append("\n".join(imports) + "\n")
|
|
195
|
+
if helpers:
|
|
196
|
+
parts.append("\n".join(helpers) + "\n")
|
|
197
|
+
parts.append("def build_slide_requests(html_str, assets_dir, page_id):")
|
|
198
|
+
parts.extend(indented)
|
|
199
|
+
parts.append(" return requests")
|
|
200
|
+
code = "\n".join(parts)
|
|
201
|
+
|
|
202
|
+
if "import os" not in code:
|
|
203
|
+
code = "import os\nimport json\nimport uuid\n\n" + code
|
|
204
|
+
|
|
205
|
+
for smart, straight in {"‘": "'", "’": "'", "“": '"', "”": '"'}.items():
|
|
206
|
+
code = code.replace(smart, straight)
|
|
207
|
+
|
|
208
|
+
if "alpha" in code:
|
|
209
|
+
code = _ALPHA_PAT.sub("", code)
|
|
210
|
+
if "'paragraphStyle'" in code:
|
|
211
|
+
code = code.replace("'paragraphStyle'", "'style'")
|
|
212
|
+
if '"paragraphStyle"' in code:
|
|
213
|
+
code = code.replace('"paragraphStyle"', '"style"')
|
|
214
|
+
if "'textStyle'" in code:
|
|
215
|
+
code = code.replace("'textStyle'", "'style'")
|
|
216
|
+
if '"textStyle"' in code:
|
|
217
|
+
code = code.replace('"textStyle"', '"style"')
|
|
218
|
+
|
|
219
|
+
code = _fix_apostrophe_strings(code)
|
|
220
|
+
return code
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# src/services/converter_jail/gslides_runner.py
|
|
2
|
+
"""In-jail trusted runner for Google Slides conversion (SDR-4437 HIGH-5).
|
|
3
|
+
|
|
4
|
+
Runs each generated build_slide_requests(html_str, assets_dir, page_id)
|
|
5
|
+
snippet and collects the returned request lists into a JSON file. NO network,
|
|
6
|
+
NO credentials. The host validates, uploads assets, substitutes placeholder
|
|
7
|
+
URLs, and executes the requests.
|
|
8
|
+
|
|
9
|
+
CLI: python -I gslides_runner.py <src_parent> <job_dir> <requests_out_path>
|
|
10
|
+
(<src_parent> is part of the launcher's CLI contract; this runner does NOT
|
|
11
|
+
put it on sys.path — importing anything via `src.services` would execute that
|
|
12
|
+
package's heavy __init__ and drag the credential-capable SDK graph into the
|
|
13
|
+
jail child. Siblings are imported as top-level modules instead.)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import importlib.util
|
|
17
|
+
import json
|
|
18
|
+
import sys
|
|
19
|
+
import tempfile
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
_HERE = Path(__file__).resolve().parent
|
|
23
|
+
|
|
24
|
+
if __package__ in (None, ""):
|
|
25
|
+
# Script mode (inside the jail child): import siblings as top-level
|
|
26
|
+
# modules so src/services/__init__.py (SDK/agent graph) never executes.
|
|
27
|
+
sys.path.insert(0, str(_HERE))
|
|
28
|
+
import protocol
|
|
29
|
+
from codeprep import prepare_requests_code
|
|
30
|
+
else:
|
|
31
|
+
# Imported on the trusted host (unit tests): normal package imports.
|
|
32
|
+
from src.services.converter_jail import protocol
|
|
33
|
+
from src.services.converter_jail.codeprep import prepare_requests_code
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _exec_snippet(code: str, html_str: str, assets_dir: str, page_id: str):
|
|
37
|
+
"""Run one snippet; return (requests_list_or_None, error_or_None)."""
|
|
38
|
+
code = prepare_requests_code(code)
|
|
39
|
+
tmp = Path(tempfile.mktemp(suffix=".py", prefix="jail_gslide_"))
|
|
40
|
+
tmp.write_text(code, encoding="utf-8")
|
|
41
|
+
try:
|
|
42
|
+
spec = importlib.util.spec_from_file_location("jail_gslide", str(tmp))
|
|
43
|
+
module = importlib.util.module_from_spec(spec)
|
|
44
|
+
spec.loader.exec_module(module)
|
|
45
|
+
requests = module.build_slide_requests(html_str, assets_dir, page_id)
|
|
46
|
+
if not isinstance(requests, list):
|
|
47
|
+
return None, f"build_slide_requests returned {type(requests).__name__}"
|
|
48
|
+
return requests, None
|
|
49
|
+
except Exception as exc:
|
|
50
|
+
return None, repr(exc)
|
|
51
|
+
finally:
|
|
52
|
+
if tmp.exists():
|
|
53
|
+
tmp.unlink()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def run(job_dir: str, requests_out_path: str) -> None:
|
|
57
|
+
manifest = json.loads((Path(job_dir) / protocol.MANIFEST_NAME).read_text())
|
|
58
|
+
out = []
|
|
59
|
+
for entry in manifest["slides"]:
|
|
60
|
+
i = entry["index"]
|
|
61
|
+
page_id = entry["page_id"]
|
|
62
|
+
sdir = Path(job_dir) / entry["dir"]
|
|
63
|
+
assets_dir = str(sdir / protocol.ASSETS_DIR)
|
|
64
|
+
if not entry.get("has_code"):
|
|
65
|
+
out.append({"index": i, "page_id": page_id, "requests": None,
|
|
66
|
+
"error": "no_code"})
|
|
67
|
+
continue
|
|
68
|
+
html_str = (sdir / protocol.HTML_NAME).read_text(encoding="utf-8")
|
|
69
|
+
code = (sdir / protocol.CODE_NAME).read_text(encoding="utf-8")
|
|
70
|
+
requests, error = _exec_snippet(code, html_str, assets_dir, page_id)
|
|
71
|
+
out.append({"index": i, "page_id": page_id,
|
|
72
|
+
"requests": requests, "error": error})
|
|
73
|
+
Path(requests_out_path).write_text(json.dumps(out))
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
if __name__ == "__main__":
|
|
77
|
+
# argv: [runner, src_parent, job_dir, requests_out_path]
|
|
78
|
+
run(sys.argv[2], sys.argv[3])
|