diff --git a/CHANGELOG.md b/CHANGELOG.md index 35f80562..c04e2935 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ - [ ] test self-test - [ ] check back edits to stirring calibration - [ ] changes to settings api in unit_api + - [ ] logs page + - new table for historical assignments + - plugins page has dropdown to select the unit ### 24.12.10 - Hotfix for UI settings bug diff --git a/pioreactor/actions/leader/experiment_profile.py b/pioreactor/actions/leader/experiment_profile.py index 6c02f8c6..dd42804e 100644 --- a/pioreactor/actions/leader/experiment_profile.py +++ b/pioreactor/actions/leader/experiment_profile.py @@ -29,7 +29,7 @@ from pioreactor.whoami import get_unit_name from pioreactor.whoami import is_testing_env -bool_expression = str | bool +BoolExpression = str | bool Env = dict[str, Any] STRICT_EXPRESSION_PATTERN = r"^\${{(.*?)}}$" @@ -90,7 +90,7 @@ def evaluate_log_message(message: str, env: dict) -> str: return result_string -def evaluate_bool_expression(bool_expression: bool_expression, env: dict) -> bool: +def evaluate_bool_expression(bool_expression: BoolExpression, env: dict) -> bool: from pioreactor.experiment_profiles.parser import parse_profile_expression_to_bool if isinstance(bool_expression, bool): @@ -103,7 +103,7 @@ def evaluate_bool_expression(bool_expression: bool_expression, env: dict) -> boo return parse_profile_expression_to_bool(bool_expression, env=env) -def check_syntax_of_bool_expression(bool_expression: bool_expression) -> bool: +def check_syntax_of_bool_expression(bool_expression: BoolExpression) -> bool: from pioreactor.experiment_profiles.parser import check_syntax if isinstance(bool_expression, bool): @@ -320,11 +320,11 @@ def when( client: Client, job_name: str, dry_run: bool, - if_: Optional[bool_expression], + if_: Optional[BoolExpression], env: dict, logger: CustomLogger, elapsed_seconds_func: Callable[[], float], - condition: bool_expression, + condition: BoolExpression, when_action: struct.When, actions: list[struct.Action], schedule: scheduler, @@ -393,12 +393,12 @@ def repeat( client: Client, job_name: str, dry_run: bool, - if_: Optional[bool_expression], + if_: Optional[BoolExpression], env: dict, logger: CustomLogger, elapsed_seconds_func: Callable[[], float], repeat_action: struct.Repeat, - while_: Optional[bool_expression], + while_: Optional[BoolExpression], repeat_every_hours: float, max_hours: Optional[float], actions: list[struct.BasicAction], @@ -484,7 +484,7 @@ def log( client: Client, job_name: str, dry_run: bool, - if_: Optional[bool_expression], + if_: Optional[BoolExpression], env: dict, logger: CustomLogger, elapsed_seconds_func: Callable[[], float], @@ -517,7 +517,7 @@ def start_job( client: Client, job_name: str, dry_run: bool, - if_: Optional[bool_expression], + if_: Optional[BoolExpression], env: dict, logger: CustomLogger, elapsed_seconds_func: Callable[[], float], @@ -559,7 +559,7 @@ def pause_job( client: Client, job_name: str, dry_run: bool, - if_: Optional[bool_expression], + if_: Optional[BoolExpression], env: dict, logger: CustomLogger, elapsed_seconds_func: Callable[[], float], @@ -595,7 +595,7 @@ def resume_job( client: Client, job_name: str, dry_run: bool, - if_: Optional[bool_expression], + if_: Optional[BoolExpression], env: dict, logger: CustomLogger, elapsed_seconds_func: Callable[[], float], @@ -632,7 +632,7 @@ def stop_job( client: Client, job_name: str, dry_run: bool, - if_: Optional[bool_expression], + if_: Optional[BoolExpression], env: dict, logger: CustomLogger, elapsed_seconds_func: Callable[[], float], @@ -668,7 +668,7 @@ def update_job( client: Client, job_name: str, dry_run: bool, - if_: Optional[bool_expression], + if_: Optional[BoolExpression], env: dict, logger: CustomLogger, elapsed_seconds_func: Callable[[], float], @@ -814,7 +814,9 @@ def execute_experiment_profile(profile_filename: str, experiment: str, dry_run: unit = get_unit_name() action_name = "experiment_profile" logger = create_logger(action_name, unit=unit, experiment=experiment) - with managed_lifecycle(unit, experiment, action_name, ignore_is_active_state=True) as state: + with managed_lifecycle( + unit, experiment, action_name, ignore_is_active_state=True, is_long_running_job=True + ) as state: try: profile = load_and_verify_profile(profile_filename) except Exception as e: diff --git a/pioreactor/background_jobs/monitor.py b/pioreactor/background_jobs/monitor.py index 29a8c5d1..ef315359 100644 --- a/pioreactor/background_jobs/monitor.py +++ b/pioreactor/background_jobs/monitor.py @@ -139,7 +139,7 @@ def pretty_version(info: tuple) -> str: # we manually run a self_check outside of a thread first, as if there are # problems detected, we may want to block and not let the job continue. self.self_check_thread = RepeatedTimer( - 4 * 60 * 60, self.self_checks, job_name=self.job_name, run_immediately=True, logger=self.logger + 12 * 60 * 60, self.self_checks, job_name=self.job_name, run_immediately=True, logger=self.logger ).start() self.add_pre_button_callback(self._republish_state) diff --git a/pioreactor/plugin_management/__init__.py b/pioreactor/plugin_management/__init__.py index c22410ba..dd1679e8 100644 --- a/pioreactor/plugin_management/__init__.py +++ b/pioreactor/plugin_management/__init__.py @@ -15,6 +15,9 @@ from .uninstall_plugin import click_uninstall_plugin from .utils import discover_plugins_in_entry_points from .utils import discover_plugins_in_local_folder +from pioreactor import pubsub +from pioreactor.utils import networking +from pioreactor.whoami import get_unit_name """ How do plugins work? There are a few patterns we use to "register" plugins with the core app. @@ -56,6 +59,11 @@ class Plugin(Struct): source: str +def get_plugin_api_url(py_file: str) -> str: + endpoint = f"/unit_api/plugins/installed/{py_file}" + return pubsub.create_webserver_path(networking.resolve_to_address(get_unit_name()), endpoint) + + def get_plugins() -> dict[str, Plugin]: """ This function is really time consuming... @@ -106,7 +114,7 @@ def get_plugins() -> dict[str, Plugin]: module, getattr(module, "__plugin_summary__", BLANK), getattr(module, "__plugin_version__", BLANK), - getattr(module, "__plugin_homepage__", BLANK), + getattr(module, "__plugin_homepage__", get_plugin_api_url(py_file.name)), getattr(module, "__plugin_author__", BLANK), f"plugins/{py_file.name}", ) diff --git a/pioreactor/pubsub.py b/pioreactor/pubsub.py index 9823366f..162632aa 100644 --- a/pioreactor/pubsub.py +++ b/pioreactor/pubsub.py @@ -362,11 +362,11 @@ def create_webserver_path(address: str, endpoint: str) -> str: # Most commonly, address can be an mdns name (test.local), or an IP address. port = config.getint("ui", "port", fallback=80) proto = config.get("ui", "proto", fallback="http") + endpoint = conform_and_validate_api_endpoint(endpoint) return f"{proto}://{address}:{port}/{endpoint}" def get_from(address: str, endpoint: str, **kwargs) -> mureq.Response: - endpoint = conform_and_validate_api_endpoint(endpoint) return mureq.get(create_webserver_path(address, endpoint), **kwargs) @@ -377,7 +377,6 @@ def get_from_leader(endpoint: str, **kwargs) -> mureq.Response: def put_into( address: str, endpoint: str, body: bytes | None = None, json: dict | Struct | None = None, **kwargs ) -> mureq.Response: - endpoint = conform_and_validate_api_endpoint(endpoint) return mureq.put(create_webserver_path(address, endpoint), body=body, json=json, **kwargs) @@ -390,7 +389,6 @@ def put_into_leader( def patch_into( address: str, endpoint: str, body: bytes | None = None, json: dict | Struct | None = None, **kwargs ) -> mureq.Response: - endpoint = conform_and_validate_api_endpoint(endpoint) return mureq.patch(create_webserver_path(address, endpoint), body=body, json=json, **kwargs) @@ -403,7 +401,6 @@ def patch_into_leader( def post_into( address: str, endpoint: str, body: bytes | None = None, json: dict | Struct | None = None, **kwargs ) -> mureq.Response: - endpoint = conform_and_validate_api_endpoint(endpoint) return mureq.post(create_webserver_path(address, endpoint), body=body, json=json, **kwargs) @@ -414,7 +411,6 @@ def post_into_leader( def delete_from(address: str, endpoint: str, **kwargs) -> mureq.Response: - endpoint = conform_and_validate_api_endpoint(endpoint) return mureq.delete(create_webserver_path(address, endpoint), **kwargs) diff --git a/pioreactor/utils/__init__.py b/pioreactor/utils/__init__.py index 4d557c1f..c1761cd6 100644 --- a/pioreactor/utils/__init__.py +++ b/pioreactor/utils/__init__.py @@ -151,6 +151,7 @@ def __init__( exit_on_mqtt_disconnect: bool = False, mqtt_client_kwargs: dict | None = None, ignore_is_active_state=False, # hack and kinda gross + is_long_running_job=False, source: str = "app", job_source: str | None = None, ) -> None: @@ -163,6 +164,7 @@ def __init__( self.state = "init" self.exit_event = Event() self._source = source + self.is_long_running_job = is_long_running_job self._job_source = job_source or os.environ.get("JOB_SOURCE") or "user" last_will = { @@ -215,7 +217,7 @@ def __enter__(self) -> managed_lifecycle: self._job_source, getpid(), "", # TODO: why is leader string empty? perf? - False, + self.is_long_running_job, ) self.state = "ready"