From 6aebbfd93bcd5193360efd12714797d10db85fb9 Mon Sep 17 00:00:00 2001 From: Will Chen Date: Sat, 2 Nov 2024 15:18:05 -0700 Subject: [PATCH] Add env var: MESOP_APP_BASE_PATH (#1057) --- .github/workflows/ci.yml | 8 ++++++++ docs/api/config.md | 4 ++++ mesop/env/env.py | 21 +++++++++++++++++++++ mesop/examples/BUILD | 1 + mesop/examples/__init__.py | 1 + mesop/examples/app_base/BUILD | 16 ++++++++++++++++ mesop/examples/app_base/__init__.py | 1 + mesop/examples/app_base/main.py | 11 +++++++++++ mesop/examples/app_base/static/test.txt | 1 + mesop/labs/web_component.py | 3 ++- mesop/server/server_utils.py | 4 ++-- mesop/server/static_file_serving.py | 3 ++- mesop/tests/e2e/app_base_test.ts | 13 +++++++++++++ 13 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 mesop/examples/app_base/BUILD create mode 100644 mesop/examples/app_base/__init__.py create mode 100644 mesop/examples/app_base/main.py create mode 100644 mesop/examples/app_base/static/test.txt create mode 100644 mesop/tests/e2e/app_base_test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fbe2991b..5e09ecaa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -96,6 +96,14 @@ jobs: name: playwright-report-with-static-folder path: playwright-report-with-static-folder/ retention-days: 30 + - name: Run playwright test with app base + run: MESOP_APP_BASE_PATH=$PWD/mesop/examples/app_base MESOP_STATIC_FOLDER=static PLAYWRIGHT_HTML_OUTPUT_DIR=playwright-report-with-static-folder yarn playwright test mesop/tests/e2e/app_base_test.ts + - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + if: always() + with: + name: playwright-report-with-static-folder + path: playwright-report-with-static-folder/ + retention-days: 30 # Deploy docs deploy-docs: # Only deploy docs if we're pushing to main (see on.push.branches) diff --git a/docs/api/config.md b/docs/api/config.md index a26a8ebf..fe3fdb5d 100644 --- a/docs/api/config.md +++ b/docs/api/config.md @@ -252,6 +252,10 @@ By default, this is not enabled. You can enable this by setting it to `true`. This uses WebSockets instead of HTTP Server-Sent Events (SSE) as the transport protocol for UI updates. If you set this environment variable to `true`, then [`MESOP_CONCURRENT_UPDATES_ENABLED`](#MESOP_CONCURRENT_UPDATES_ENABLED) will automatically be enabled as well. +### MESOP_APP_BASE_PATH + +This is the base path used to resolve other paths, particularly for serving static files. Must be an absolute path. This is rarely needed because the default of using the current working directory is usually sufficient. + ## Usage Examples ### One-liner diff --git a/mesop/env/env.py b/mesop/env/env.py index 2d38e03e..ccc5b642 100644 --- a/mesop/env/env.py +++ b/mesop/env/env.py @@ -1,9 +1,30 @@ import os +from mesop.exceptions import MesopDeveloperException + AI_SERVICE_BASE_URL = os.environ.get( "MESOP_AI_SERVICE_BASE_URL", "http://localhost:43234" ) +MESOP_APP_BASE_PATH = os.environ.get("MESOP_APP_BASE_PATH", "") +if MESOP_APP_BASE_PATH: + if not os.path.isabs(MESOP_APP_BASE_PATH): + raise MesopDeveloperException( + f"MESOP_APP_BASE_PATH must be an absolute path, but got {MESOP_APP_BASE_PATH} instead." + ) + if not os.path.isdir(MESOP_APP_BASE_PATH): + raise MesopDeveloperException( + f"MESOP_APP_BASE_PATH is not a valid directory: {MESOP_APP_BASE_PATH}" + ) + print(f"MESOP_APP_BASE_PATH set to {MESOP_APP_BASE_PATH}") + + +def get_app_base_path() -> str: + if not MESOP_APP_BASE_PATH: + return os.getcwd() + return MESOP_APP_BASE_PATH + + MESOP_WEBSOCKETS_ENABLED = ( os.environ.get("MESOP_WEBSOCKETS_ENABLED", "false").lower() == "true" ) diff --git a/mesop/examples/BUILD b/mesop/examples/BUILD index 1a0fe678..8b6c1e94 100644 --- a/mesop/examples/BUILD +++ b/mesop/examples/BUILD @@ -47,6 +47,7 @@ py_library( "//mesop/components/text/e2e", "//mesop/examples/web_component", "//mesop/examples/docs", + "//mesop/examples/app_base", "//mesop/examples/integrations", "//mesop/examples/shared", "//mesop/examples/starter_kit", diff --git a/mesop/examples/__init__.py b/mesop/examples/__init__.py index ed4213ef..bada6c6b 100644 --- a/mesop/examples/__init__.py +++ b/mesop/examples/__init__.py @@ -2,6 +2,7 @@ from mesop.examples import ( allowed_iframe_parents as allowed_iframe_parents, ) +from mesop.examples import app_base as app_base from mesop.examples import async_await as async_await from mesop.examples import ( boilerplate_free_event_handlers as boilerplate_free_event_handlers, diff --git a/mesop/examples/app_base/BUILD b/mesop/examples/app_base/BUILD new file mode 100644 index 00000000..36cde2b1 --- /dev/null +++ b/mesop/examples/app_base/BUILD @@ -0,0 +1,16 @@ +load("//build_defs:defaults.bzl", "py_library") + +package( + default_visibility = ["//build_defs:mesop_examples"], +) + +py_library( + name = "app_base", + srcs = glob(["*.py"]), + data = glob([ + "static/**/*", + ]), + deps = [ + "//mesop", + ], +) diff --git a/mesop/examples/app_base/__init__.py b/mesop/examples/app_base/__init__.py new file mode 100644 index 00000000..7eef4b0d --- /dev/null +++ b/mesop/examples/app_base/__init__.py @@ -0,0 +1 @@ +from mesop.examples.app_base import main as main diff --git a/mesop/examples/app_base/main.py b/mesop/examples/app_base/main.py new file mode 100644 index 00000000..7d21e609 --- /dev/null +++ b/mesop/examples/app_base/main.py @@ -0,0 +1,11 @@ +import os + +import mesop as me + + +@me.page(path="/examples/app_base") +def page(): + me.text( + "Testing: MESOP_APP_BASE_PATH=" + + os.getenv("MESOP_APP_BASE_PATH", "") + ) diff --git a/mesop/examples/app_base/static/test.txt b/mesop/examples/app_base/static/test.txt new file mode 100644 index 00000000..04733e4d --- /dev/null +++ b/mesop/examples/app_base/static/test.txt @@ -0,0 +1 @@ +test MESOP_APP_BASE_PATH works diff --git a/mesop/labs/web_component.py b/mesop/labs/web_component.py index e476513b..d0a05b1d 100644 --- a/mesop/labs/web_component.py +++ b/mesop/labs/web_component.py @@ -3,6 +3,7 @@ from functools import wraps from typing import Any, Callable, TypeVar, cast +from mesop.env.env import get_app_base_path from mesop.runtime import runtime from mesop.utils.validate import validate @@ -54,4 +55,4 @@ def format_filename(filename: str) -> str: return filename.split(".runfiles", 1)[1] else: # Handle pip CLI case - return os.path.relpath(filename, os.getcwd()) + return os.path.relpath(filename, get_app_base_path()) diff --git a/mesop/server/server_utils.py b/mesop/server/server_utils.py index 4a8cee9f..8f374c20 100644 --- a/mesop/server/server_utils.py +++ b/mesop/server/server_utils.py @@ -10,7 +10,7 @@ from werkzeug.security import safe_join import mesop.protos.ui_pb2 as pb -from mesop.env.env import EXPERIMENTAL_EDITOR_TOOLBAR_ENABLED +from mesop.env.env import EXPERIMENTAL_EDITOR_TOOLBAR_ENABLED, get_app_base_path from mesop.exceptions import MesopDeveloperException from mesop.runtime import runtime from mesop.server.config import app_config @@ -148,7 +148,7 @@ def get_static_folder() -> str | None: "Static folder cannot be an absolute path: static_folder_name}" ) - static_folder_path = safe_join(os.getcwd(), static_folder_name) + static_folder_path = safe_join(get_app_base_path(), static_folder_name) if not static_folder_path: raise MesopDeveloperException( diff --git a/mesop/server/static_file_serving.py b/mesop/server/static_file_serving.py index cba43174..f0899cf4 100644 --- a/mesop/server/static_file_serving.py +++ b/mesop/server/static_file_serving.py @@ -12,6 +12,7 @@ from flask import Flask, Response, g, make_response, request, send_file from werkzeug.security import safe_join +from mesop.env.env import get_app_base_path from mesop.exceptions import MesopException from mesop.runtime import runtime from mesop.server.constants import WEB_COMPONENTS_PATH_SEGMENT @@ -100,7 +101,7 @@ def serve_web_components(path: str): serving_path = ( get_runfile_location(path) if has_runfiles() - else safe_join(os.getcwd(), path) + else safe_join(get_app_base_path(), path) ) file_name = os.path.basename(path) diff --git a/mesop/tests/e2e/app_base_test.ts b/mesop/tests/e2e/app_base_test.ts new file mode 100644 index 00000000..3c48d59f --- /dev/null +++ b/mesop/tests/e2e/app_base_test.ts @@ -0,0 +1,13 @@ +import {test, expect} from '@playwright/test'; + +test.describe('MESOP_APP_BASE_PATH', () => { + if (process.env['MESOP_APP_BASE_PATH'] === undefined) { + test.skip('Test skipped because MESOP_APP_BASE_PATH is not set.'); + } + test('serves static file relative to MESOP_APP_BASE_PATH', async ({page}) => { + const response = await page.goto('/static/test.txt'); + expect(response!.status()).toBe(200); + const text = await response!.text(); + expect(text).toContain('test MESOP_APP_BASE_PATH works'); + }); +});