Coverage for postrfp / shared / tools.py: 100%
55 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-12-03 01:35 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2025-12-03 01:35 +0000
1import json
2import importlib.resources as resources
3from importlib.resources.abc import Traversable
4from pathlib import Path
5from typing import Callable, TYPE_CHECKING
7import requests
10from postrfp import conf
11from postrfp.mail.postmark import api_headers
13from .constants import API_VERSION_FILEPATH
15if TYPE_CHECKING:
16 from postrfp.model import Issue
19def api_version_resource() -> Traversable:
20 package_name, filename = tuple(API_VERSION_FILEPATH.rsplit(".", 1))
21 return resources.files(package_name).joinpath(filename)
24def read_api_version() -> str:
25 """Return the API version string stored in the package."""
26 return api_version_resource().read_text().strip()
29def populate_answers(issue: "Issue", factory: Callable | None = None) -> None:
30 from sqlalchemy.orm import object_session
32 from postrfp.model import QuestionInstance, QElement
33 from postrfp.model.questionnaire.answering import Answer
35 session = object_session(issue)
36 assert session is not None
37 issue.answers.delete()
39 q = (
40 session.query(QElement, QuestionInstance)
41 .filter(QElement.question_id == QuestionInstance.question_def_id)
42 .filter(QuestionInstance.project == issue.project)
43 )
45 if factory is None:
47 def factory(answer: Answer) -> str:
48 return "Autoanswer"
50 for q_el, q_instance in q:
51 a = Answer(issue=issue, element=q_el, question_instance=q_instance)
52 a.answer = factory(a)
53 session.add(a)
56def list_missing_templates(api_key: str, save_disc: bool = True) -> set[str]:
57 from postrfp.model.audit import evt_types
59 headers = api_headers(key=api_key)
60 payload = {"Count": 100, "Offset": 0}
61 resp = requests.get(
62 "https://api.postmarkapp.com/templates",
63 headers=headers,
64 params=payload,
65 timeout=5,
66 )
68 template_labels = resp.json()["Templates"]
69 if save_disc:
70 save_templates_to_disc(api_key, template_labels)
72 tmpl_by_alias = {t["Alias"] for t in template_labels}
73 ev_set = {e for e in dir(evt_types) if not e.startswith("__")}
74 return ev_set - tmpl_by_alias
77def save_templates_to_disc(api_key: str, template_labels: list[dict]) -> None:
78 root_dir = Path("/tmp/postmarktmpls/") # nosec
79 for _dir in ("json", "txt"):
80 tmpl_dir = root_dir / _dir
81 tmpl_dir.mkdir(parents=True, exist_ok=True)
83 headers = api_headers(key=api_key)
84 for t in template_labels:
85 alias = t["Alias"]
86 tmpl_response = requests.get(
87 f"https://api.postmarkapp.com/templates/{alias}", headers=headers, timeout=5
88 )
89 tmpl_response.raise_for_status()
90 tmpl = tmpl_response.json()
91 with open("/tmp/postmarktmpls/json/%s.json" % alias, "w") as f: # nosec
92 json.dump(tmpl, f, indent=2)
94 with open("/tmp/postmarktmpls/txt/%s.txt" % alias, "w") as ftxt: # nosec
95 print(tmpl["TextBody"], file=ftxt)
98def upload_templates_to_postmark(api_key: str) -> None: # pragma: no cover
99 import pymustache # type: ignore
101 headers = api_headers(key=api_key)
103 json_dir = Path(conf.CONF.template_dir).parent / Path("json")
105 for json_path in json_dir.glob("*.json"):
106 tmpl_json = json.loads(json_path.read_text())
107 tmpl_alias = json_path.stem
108 tmpl_txt = (json_dir / f"../txt/{tmpl_alias}.txt").read_text()
109 pymustache.compiled(tmpl_txt) # validates the template
110 tmpl_json["TextBody"] = tmpl_txt
112 path = f"https://api.postmarkapp.com/templates/{tmpl_alias}"
113 response = requests.put(path, headers=headers, json=tmpl_json, timeout=5)
114 if not response.ok:
115 response.raise_for_status()