rest_api.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. import logging
  2. from base64 import b64encode
  3. from copy import deepcopy
  4. from email.message import EmailMessage
  5. from json import dumps
  6. from os import environ, urandom
  7. from smtplib import SMTP
  8. from flask import Flask, jsonify, render_template, request
  9. from flask_cors import CORS
  10. from scriptshifter import EMAIL_FROM, EMAIL_TO, SMTP_HOST, SMTP_PORT
  11. from scriptshifter.exceptions import ApiError
  12. from scriptshifter.tables import list_tables, load_table
  13. from scriptshifter.trans import transliterate
  14. logger = logging.getLogger(__name__)
  15. logging.basicConfig(level=environ.get("TXL_LOGLEVEL", logging.INFO))
  16. def create_app():
  17. flask_env = environ.get("TXL_APP_MODE", "production")
  18. app = Flask(__name__)
  19. app.config.update({
  20. "ENV": flask_env,
  21. "SECRET_KEY": environ.get("TXL_FLASK_SECRET", b64encode(urandom(64))),
  22. "JSON_AS_ASCII": False,
  23. "JSONIFY_PRETTYPRINT_REGULAR": True,
  24. })
  25. CORS(app)
  26. return app
  27. app = create_app()
  28. @app.errorhandler(ApiError)
  29. def handle_exception(e: ApiError):
  30. if e.status_code >= 500:
  31. warnings = [
  32. "An internal error occurred.",
  33. "If the error persists, contact the technical support team."
  34. ]
  35. else:
  36. warnings = [
  37. "ScriptShifter API replied with status code "
  38. f"{e.status_code}: {e.msg}"
  39. ]
  40. if e.status_code >= 400:
  41. warnings.append(
  42. "Please review your input before repeating this request.")
  43. body = {
  44. "warnings": warnings,
  45. "output": "",
  46. }
  47. if logging.DEBUG >= logging.root.level:
  48. body["debug"] = {
  49. "form_data": request.json or request.form,
  50. }
  51. return (body, e.status_code)
  52. @app.route("/", methods=["GET"])
  53. def index():
  54. return render_template(
  55. "index.html",
  56. languages=list_tables(),
  57. feedback_form=SMTP_HOST is not None)
  58. @app.route("/health", methods=["GET"])
  59. def health_check():
  60. return "I'm alive!\n"
  61. @app.route("/languages", methods=["GET"])
  62. def list_languages():
  63. return jsonify(list_tables())
  64. @app.route("/table/<lang>")
  65. def dump_table(lang):
  66. """
  67. Dump parsed transliteration table for a language.
  68. """
  69. tbl = deepcopy(load_table(lang))
  70. for sec_name in ("roman_to_script", "script_to_roman"):
  71. if sec_name in tbl:
  72. for hname, fn_defs in tbl[sec_name].get("hooks", {}).items():
  73. tbl[sec_name]["hooks"][hname] = [
  74. (fn.__name__, kw) for (fn, kw) in fn_defs]
  75. return jsonify(tbl)
  76. @app.route("/options/<lang>", methods=["GET"])
  77. def get_options(lang):
  78. """
  79. Get extra options for a table.
  80. """
  81. tbl = load_table(lang)
  82. return jsonify(tbl.get("options", []))
  83. @app.route("/trans", methods=["POST"])
  84. def transliterate_req():
  85. lang = request.json["lang"]
  86. in_txt = request.json["text"]
  87. capitalize = request.json.get("capitalize", False)
  88. t_dir = request.json.get("t_dir", "s2r")
  89. if t_dir not in ("s2r", "r2s"):
  90. return f"Invalid direction: {t_dir}", 400
  91. if not len(in_txt):
  92. return ("No input text provided! ", 400)
  93. options = request.json.get("options", {})
  94. logger.debug(f"Extra options: {options}")
  95. try:
  96. out, warnings = transliterate(in_txt, lang, t_dir, capitalize, options)
  97. except (NotImplementedError, ValueError) as e:
  98. raise ApiError(str(e), 400)
  99. except Exception as e:
  100. raise ApiError(str(e), 500)
  101. return {"output": out, "warnings": warnings}
  102. @app.route("/feedback", methods=["POST"])
  103. def feedback():
  104. """
  105. Allows users to provide feedback to improve a specific result.
  106. """
  107. t_dir = request.json.get("t_dir", "s2r")
  108. options = request.json.get("options", {})
  109. contact = request.json.get("contact")
  110. msg = EmailMessage()
  111. msg["subject"] = "Scriptshifter feedback report"
  112. msg["from"] = EMAIL_FROM
  113. msg["to"] = EMAIL_TO
  114. if contact:
  115. msg["cc"] = contact
  116. msg.set_content(f"""
  117. *Scriptshifter feedback report from {contact or 'anonymous'}*\n\n
  118. *Language:* {request.json['lang']}\n
  119. *Direction:* {
  120. 'Roman to Script' if t_dir == 'r2s'
  121. else 'Script to Roman'}\n
  122. *Source:* {request.json['src']}\n
  123. *Result:* {request.json['result']}\n
  124. *Expected result:* {request.json['expected']}\n
  125. *Applied options:* {dumps(options)}\n
  126. *Notes:*\n
  127. {request.json['notes']}""")
  128. # TODO This uses a test SMTP server:
  129. # python -m smtpd -n -c DebuggingServer localhost:1025
  130. smtp = SMTP(SMTP_HOST, SMTP_PORT)
  131. smtp.send_message(msg)
  132. smtp.quit()
  133. return {"message": "Feedback message sent."}