Merge pull request #26369 from gavindsouza/refactor-scheduled-server_script
refactor!: Server Script (Scheduler Event) & misc APIs
This commit is contained in:
commit
bbf18d39cc
4 changed files with 62 additions and 68 deletions
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
from functools import partial
|
||||
from types import FunctionType, MethodType, ModuleType
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
|
|
@ -16,6 +17,9 @@ from frappe.utils.safe_exec import (
|
|||
safe_exec,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.core.doctype.scheduled_job_type.scheduled_job_type import ScheduledJobType
|
||||
|
||||
|
||||
class ServerScript(Document):
|
||||
# begin: auto-generated types
|
||||
|
|
@ -77,12 +81,10 @@ class ServerScript(Document):
|
|||
|
||||
def validate(self):
|
||||
frappe.only_for("Script Manager", True)
|
||||
self.sync_scheduled_jobs()
|
||||
self.clear_scheduled_events()
|
||||
self.check_if_compilable_in_restricted_context()
|
||||
|
||||
def on_update(self):
|
||||
self.sync_scheduler_events()
|
||||
self.sync_scheduled_job_type()
|
||||
|
||||
def clear_cache(self):
|
||||
frappe.cache.delete_value("server_script_map")
|
||||
|
|
@ -92,7 +94,10 @@ class ServerScript(Document):
|
|||
frappe.cache.delete_value("server_script_map")
|
||||
if self.script_type == "Scheduler Event":
|
||||
for job in self.scheduled_jobs:
|
||||
frappe.delete_doc("Scheduled Job Type", job.name)
|
||||
scheduled_job_type: "ScheduledJobType" = frappe.get_doc("Scheduled Job Type", job.name)
|
||||
scheduled_job_type.stopped = True
|
||||
scheduled_job_type.server_script = None
|
||||
scheduled_job_type.save()
|
||||
|
||||
def get_code_fields(self):
|
||||
return {"script": "py"}
|
||||
|
|
@ -105,33 +110,35 @@ class ServerScript(Document):
|
|||
fields=["name", "stopped"],
|
||||
)
|
||||
|
||||
def sync_scheduled_jobs(self):
|
||||
"""Sync Scheduled Job Type statuses if Server Script's disabled status is changed"""
|
||||
if self.script_type != "Scheduler Event" or not self.has_value_changed("disabled"):
|
||||
def sync_scheduled_job_type(self):
|
||||
"""Create or update Scheduled Job Type documents for Scheduler Event Server Scripts"""
|
||||
if self.script_type != "Scheduler Event" or (
|
||||
(previous_script_type := self.has_value_changed("script_type"))
|
||||
# True will be sent if its a new record
|
||||
and previous_script_type.value not in (True, "Scheduler Event")
|
||||
):
|
||||
return
|
||||
|
||||
for scheduled_job in self.scheduled_jobs:
|
||||
if bool(scheduled_job.stopped) != bool(self.disabled):
|
||||
job = frappe.get_doc("Scheduled Job Type", scheduled_job.name)
|
||||
job.stopped = self.disabled
|
||||
job.save()
|
||||
|
||||
def sync_scheduler_events(self):
|
||||
"""Create or update Scheduled Job Type documents for Scheduler Event Server Scripts"""
|
||||
if not self.disabled and self.event_frequency and self.script_type == "Scheduler Event":
|
||||
cron_format = self.cron_format if self.event_frequency == "Cron" else None
|
||||
setup_scheduler_events(
|
||||
script_name=self.name, frequency=self.event_frequency, cron_format=cron_format
|
||||
if scheduled_script := frappe.db.get_value("Scheduled Job Type", {"server_script": self.name}):
|
||||
scheduled_job_type: "ScheduledJobType" = frappe.get_doc("Scheduled Job Type", scheduled_script)
|
||||
else:
|
||||
scheduled_job_type: "ScheduledJobType" = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Scheduled Job Type",
|
||||
"server_script": self.name,
|
||||
}
|
||||
)
|
||||
|
||||
def clear_scheduled_events(self):
|
||||
"""Deletes existing scheduled jobs by Server Script if self.event_frequency or self.cron_format has changed"""
|
||||
if (
|
||||
self.script_type == "Scheduler Event"
|
||||
and (self.has_value_changed("event_frequency") or self.has_value_changed("cron_format"))
|
||||
) or (self.has_value_changed("script_type") and self.script_type != "Scheduler Event"):
|
||||
for scheduled_job in self.scheduled_jobs:
|
||||
frappe.delete_doc("Scheduled Job Type", scheduled_job.name, delete_permanently=1)
|
||||
scheduled_job_type.update(
|
||||
{
|
||||
"method": frappe.scrub(f"{self.name}-{self.event_frequency}"),
|
||||
"frequency": self.event_frequency,
|
||||
"cron_format": self.cron_format,
|
||||
"stopped": self.disabled,
|
||||
}
|
||||
).save()
|
||||
|
||||
frappe.msgprint(_("Scheduled execution for script {0} has updated").format(self.name))
|
||||
|
||||
def check_if_compilable_in_restricted_context(self):
|
||||
"""Check compilation errors and send them back as warnings."""
|
||||
|
|
@ -247,43 +254,7 @@ class ServerScript(Document):
|
|||
return items
|
||||
|
||||
|
||||
def setup_scheduler_events(script_name: str, frequency: str, cron_format: str | None = None):
|
||||
"""Creates or Updates Scheduled Job Type documents based on the specified script name and frequency
|
||||
|
||||
Args:
|
||||
script_name (str): Name of the Server Script document
|
||||
frequency (str): Event label compatible with the Frappe scheduler
|
||||
"""
|
||||
method = frappe.scrub(f"{script_name}-{frequency}")
|
||||
scheduled_script = frappe.db.get_value("Scheduled Job Type", {"method": method})
|
||||
|
||||
if not scheduled_script:
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Scheduled Job Type",
|
||||
"method": method,
|
||||
"frequency": frequency,
|
||||
"server_script": script_name,
|
||||
"cron_format": cron_format,
|
||||
}
|
||||
).insert()
|
||||
|
||||
frappe.msgprint(_("Enabled scheduled execution for script {0}").format(script_name))
|
||||
|
||||
else:
|
||||
doc = frappe.get_doc("Scheduled Job Type", scheduled_script)
|
||||
|
||||
if doc.frequency == frequency:
|
||||
return
|
||||
|
||||
doc.frequency = frequency
|
||||
doc.cron_format = cron_format
|
||||
doc.save()
|
||||
|
||||
frappe.msgprint(_("Scheduled execution for script {0} has updated").format(script_name))
|
||||
|
||||
|
||||
def execute_api_server_script(script=None, *args, **kwargs):
|
||||
def execute_api_server_script(script: ServerScript, *args, **kwargs):
|
||||
# These are only added for compatibility with rate limiter.
|
||||
del args
|
||||
del kwargs
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ def run_server_script_for_doc_event(doc, event):
|
|||
if scripts:
|
||||
# run all scripts for this doctype + event
|
||||
for script_name in scripts:
|
||||
frappe.get_doc("Server Script", script_name).execute_doc(doc)
|
||||
frappe.get_cached_doc("Server Script", script_name).execute_doc(doc)
|
||||
|
||||
|
||||
def get_server_script_map():
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ from frappe.model.naming import set_new_name, validate_name
|
|||
from frappe.model.utils import is_virtual_doctype
|
||||
from frappe.model.workflow import set_workflow_state_on_action, validate_workflow
|
||||
from frappe.types import DF
|
||||
from frappe.utils import compare, cstr, date_diff, file_lock, flt, now
|
||||
from frappe.utils import Truthy, compare, cstr, date_diff, file_lock, flt, now
|
||||
from frappe.utils.data import get_absolute_url, get_datetime, get_timedelta, getdate
|
||||
from frappe.utils.global_search import update_global_search
|
||||
|
||||
|
|
@ -468,7 +468,7 @@ class Document(BaseDocument):
|
|||
previous = self.get_doc_before_save()
|
||||
|
||||
if not previous:
|
||||
return True
|
||||
return Truthy(context="New Document")
|
||||
|
||||
previous_value = previous.get(fieldname)
|
||||
current_value = self.get(fieldname)
|
||||
|
|
@ -480,7 +480,10 @@ class Document(BaseDocument):
|
|||
elif isinstance(previous_value, timedelta):
|
||||
current_value = get_timedelta(current_value)
|
||||
|
||||
return previous_value != current_value
|
||||
if previous_value != current_value:
|
||||
return Truthy(value=previous_value)
|
||||
|
||||
return False
|
||||
|
||||
def set_new_name(self, force=False, set_name=None, set_child_names=True):
|
||||
"""Calls `frappe.naming.set_new_name` for parent and child docs."""
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ EMAIL_MATCH_PATTERN = re.compile(
|
|||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
UNSET = object()
|
||||
|
||||
|
||||
def get_fullname(user=None):
|
||||
"""get the full name (first name + last name) of the user from User"""
|
||||
|
|
@ -1166,3 +1168,21 @@ class CallbackManager:
|
|||
|
||||
def reset(self):
|
||||
self._functions.clear()
|
||||
|
||||
|
||||
class Truthy:
|
||||
def __init__(self, value=True, context=UNSET):
|
||||
self.value = value
|
||||
self.context = context
|
||||
|
||||
def __bool__(self):
|
||||
return True
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
return True == other # noqa: E712
|
||||
|
||||
def __repr__(self) -> str:
|
||||
_val = "UNSET" if self.value is UNSET else self.value
|
||||
_ctx = "UNSET" if self.context is UNSET else self.context
|
||||
|
||||
return f"Truthy(value={_val}, context={_ctx})"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue