snapshot of https://github.com/frappe/crm.git (develop, 2026-09-18; blobs > 131072B omitted)
Some checks failed
Frontend / Unit Tests & Coverage (push) Has been skipped
Server / Tests (frappe ${{ matrix.frappe-branch }}) (push) Has been skipped
Server / Resolve frappe matrix (push) Failing after 1s
Frontend / Lint (push) Failing after 1s
Frontend / Production Build (push) Failing after 1s
Server / Coverage Wrap Up (push) Has been skipped
Frontend / Coverage Wrap Up (push) Has been skipped
Regenerate POT file (translatable strings) / Regenerate POT file (develop) (push) Failing after 1s
Regenerate POT file (translatable strings) / Regenerate POT file (main-hotfix) (push) Failing after 1s
Some checks failed
Frontend / Unit Tests & Coverage (push) Has been skipped
Server / Tests (frappe ${{ matrix.frappe-branch }}) (push) Has been skipped
Server / Resolve frappe matrix (push) Failing after 1s
Frontend / Lint (push) Failing after 1s
Frontend / Production Build (push) Failing after 1s
Server / Coverage Wrap Up (push) Has been skipped
Frontend / Coverage Wrap Up (push) Has been skipped
Regenerate POT file (translatable strings) / Regenerate POT file (develop) (push) Failing after 1s
Regenerate POT file (translatable strings) / Regenerate POT file (main-hotfix) (push) Failing after 1s
This commit is contained in:
88
crm/domain_enrichment/api.py
Normal file
88
crm/domain_enrichment/api.py
Normal file
@@ -0,0 +1,88 @@
|
||||
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
"""Whitelisted entry points: ``enrich`` (enqueue a full run for a record) and
|
||||
``retry`` (re-run one from its Run history).
|
||||
|
||||
Both are type-annotated -- hooks.py sets ``require_type_annotated_api_methods``.
|
||||
Security: each enforces the doctype allow-list (from Settings) + a ``write``
|
||||
permission check, and is rate-limited per user (see ``ENRICH_RATE_LIMIT``).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.rate_limiter import rate_limit
|
||||
|
||||
from .config import ENRICHABLE_DOCTYPES, EnrichmentConfig, get_config
|
||||
from .tasks import enqueue_enrichment
|
||||
|
||||
# Per-user, per-minute cap on each enrich entry point (each gets its own bucket).
|
||||
# Both only enqueue a per-doc-deduplicated job -- 10/min is far above any real human
|
||||
# burst while capping scripted queue-flooding.
|
||||
ENRICH_RATE_LIMIT = 10
|
||||
|
||||
|
||||
def _enabled_doctypes(cfg: EnrichmentConfig) -> list[str]:
|
||||
"""The doctypes enrichment is enabled for, per Settings (allow-list)."""
|
||||
if not cfg.setting("enabled"):
|
||||
return []
|
||||
return list(ENRICHABLE_DOCTYPES)
|
||||
|
||||
|
||||
def _enqueue_run(cfg, reference_doctype: str, reference_name: str, website: str) -> dict:
|
||||
"""Validate + enqueue one enrichment run for a target record. Shared by
|
||||
``enrich`` (initial trigger from the record) and ``retry`` (re-run from a Run).
|
||||
|
||||
Caller must have already resolved the website. Enforces the Settings allow-list
|
||||
and ``write`` permission on the target. Uses the per-doc ``job_id`` +
|
||||
``deduplicate`` so a second trigger while one is in-flight is a no-op.
|
||||
"""
|
||||
if reference_doctype not in _enabled_doctypes(cfg):
|
||||
frappe.throw(
|
||||
_("Enrichment is not enabled for {0}.").format(reference_doctype),
|
||||
frappe.ValidationError,
|
||||
)
|
||||
|
||||
doc = frappe.get_doc(reference_doctype, reference_name)
|
||||
doc.check_permission("write")
|
||||
|
||||
website = (website or "").strip()
|
||||
if not website:
|
||||
frappe.throw(_("Set a website on this record before enriching."), frappe.ValidationError)
|
||||
|
||||
return enqueue_enrichment(reference_doctype, reference_name, website, frappe.session.user)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@rate_limit(limit=ENRICH_RATE_LIMIT, seconds=60)
|
||||
def enrich(reference_doctype: str, reference_name: str) -> dict:
|
||||
"""Enqueue a full enrichment run for one CRM record, using the record's own
|
||||
``website`` field. The initial trigger (the "Enrich from Website" button).
|
||||
|
||||
Returns ``{queued: bool, job_id: str, website: str}``.
|
||||
"""
|
||||
cfg = get_config()
|
||||
doc = frappe.get_doc(reference_doctype, reference_name)
|
||||
website = (doc.get("website") or "").strip()
|
||||
return _enqueue_run(cfg, reference_doctype, reference_name, website)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@rate_limit(limit=ENRICH_RATE_LIMIT, seconds=60)
|
||||
def retry(run: str) -> dict:
|
||||
"""Re-run the enrichment recorded by a ``CRM Enrichment Run`` (the desk "Retry"
|
||||
button on each run). Re-enriches the run's linked record, preferring the record's
|
||||
current ``website`` and falling back to the website this run originally scraped.
|
||||
|
||||
Returns ``{queued: bool, job_id: str, website: str}``.
|
||||
"""
|
||||
run_doc = frappe.get_doc("CRM Enrichment Run", run)
|
||||
if not run_doc.reference_doctype or not run_doc.reference_name:
|
||||
frappe.throw(_("This run has no linked record to re-enrich."), frappe.ValidationError)
|
||||
|
||||
cfg = get_config()
|
||||
target = frappe.get_doc(run_doc.reference_doctype, run_doc.reference_name)
|
||||
website = (target.get("website") or "").strip() or (run_doc.source_website or "").strip()
|
||||
return _enqueue_run(cfg, run_doc.reference_doctype, run_doc.reference_name, website)
|
||||
Reference in New Issue
Block a user