fix: Misc fixesversion-14
@@ -99,17 +99,7 @@ def application(request): | |||
frappe.monitor.stop(response) | |||
frappe.recorder.dump() | |||
if hasattr(frappe.local, 'conf') and frappe.local.conf.enable_frappe_logger: | |||
frappe.logger("frappe.web", allow_site=frappe.local.site).info({ | |||
"site": get_site_name(request.host), | |||
"remote_addr": getattr(request, "remote_addr", "NOTFOUND"), | |||
"base_url": getattr(request, "base_url", "NOTFOUND"), | |||
"full_path": getattr(request, "full_path", "NOTFOUND"), | |||
"method": getattr(request, "method", "NOTFOUND"), | |||
"scheme": getattr(request, "scheme", "NOTFOUND"), | |||
"http_status_code": getattr(response, "status_code", "NOTFOUND") | |||
}) | |||
log_request(request, response) | |||
process_response(response) | |||
frappe.destroy() | |||
@@ -137,6 +127,19 @@ def init_request(request): | |||
if request.method != "OPTIONS": | |||
frappe.local.http_request = frappe.auth.HTTPRequest() | |||
def log_request(request, response): | |||
if hasattr(frappe.local, 'conf') and frappe.local.conf.enable_frappe_logger: | |||
frappe.logger("frappe.web", allow_site=frappe.local.site).info({ | |||
"site": get_site_name(request.host), | |||
"remote_addr": getattr(request, "remote_addr", "NOTFOUND"), | |||
"base_url": getattr(request, "base_url", "NOTFOUND"), | |||
"full_path": getattr(request, "full_path", "NOTFOUND"), | |||
"method": getattr(request, "method", "NOTFOUND"), | |||
"scheme": getattr(request, "scheme", "NOTFOUND"), | |||
"http_status_code": getattr(response, "status_code", "NOTFOUND") | |||
}) | |||
def process_response(response): | |||
if not response: | |||
return | |||
@@ -1,14 +1,11 @@ | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import print_function, unicode_literals | |||
import os | |||
import re | |||
import json | |||
import shutil | |||
import warnings | |||
import tempfile | |||
from tempfile import mkdtemp, mktemp | |||
from distutils.spawn import find_executable | |||
import frappe | |||
@@ -16,8 +13,8 @@ from frappe.utils.minify import JavascriptMinify | |||
import click | |||
import psutil | |||
from six import iteritems, text_type | |||
from six.moves.urllib.parse import urlparse | |||
from urllib.parse import urlparse | |||
from simple_chalk import green | |||
timestamps = {} | |||
@@ -75,8 +72,8 @@ def get_assets_link(frappe_head): | |||
from requests import head | |||
tag = getoutput( | |||
"cd ../apps/frappe && git show-ref --tags -d | grep %s | sed -e 's,.*" | |||
" refs/tags/,,' -e 's/\^{}//'" | |||
r"cd ../apps/frappe && git show-ref --tags -d | grep %s | sed -e 's,.*" | |||
r" refs/tags/,,' -e 's/\^{}//'" | |||
% frappe_head | |||
) | |||
@@ -97,9 +94,7 @@ def download_frappe_assets(verbose=True): | |||
commit HEAD. | |||
Returns True if correctly setup else returns False. | |||
""" | |||
from simple_chalk import green | |||
from subprocess import getoutput | |||
from tempfile import mkdtemp | |||
assets_setup = False | |||
frappe_head = getoutput("cd ../apps/frappe && git rev-parse HEAD") | |||
@@ -166,7 +161,7 @@ def symlink(target, link_name, overwrite=False): | |||
# Create link to target with temporary filename | |||
while True: | |||
temp_link_name = tempfile.mktemp(dir=link_dir) | |||
temp_link_name = mktemp(dir=link_dir) | |||
# os.* functions mimic as closely as possible system functions | |||
# The POSIX symlink() returns EEXIST if link_name already exists | |||
@@ -193,7 +188,8 @@ def symlink(target, link_name, overwrite=False): | |||
def setup(): | |||
global app_paths | |||
global app_paths, assets_path | |||
pymodules = [] | |||
for app in frappe.get_all_apps(True): | |||
try: | |||
@@ -201,6 +197,7 @@ def setup(): | |||
except ImportError: | |||
pass | |||
app_paths = [os.path.dirname(pymodule.__file__) for pymodule in pymodules] | |||
assets_path = os.path.join(frappe.local.sites_path, "assets") | |||
def get_node_pacman(): | |||
@@ -210,10 +207,10 @@ def get_node_pacman(): | |||
raise ValueError("Yarn not found") | |||
def bundle(no_compress, app=None, make_copy=False, restore=False, verbose=False, skip_frappe=False): | |||
def bundle(no_compress, app=None, hard_link=False, verbose=False, skip_frappe=False): | |||
"""concat / minify js files""" | |||
setup() | |||
make_asset_dirs(make_copy=make_copy, restore=restore) | |||
make_asset_dirs(hard_link=hard_link) | |||
pacman = get_node_pacman() | |||
mode = "build" if no_compress else "production" | |||
@@ -266,75 +263,101 @@ def get_safe_max_old_space_size(): | |||
return safe_max_old_space_size | |||
def make_asset_dirs(make_copy=False, restore=False): | |||
# don't even think of making assets_path absolute - rm -rf ahead. | |||
assets_path = os.path.join(frappe.local.sites_path, "assets") | |||
def generate_assets_map(): | |||
symlinks = {} | |||
for dir_path in [os.path.join(assets_path, "js"), os.path.join(assets_path, "css")]: | |||
if not os.path.exists(dir_path): | |||
os.makedirs(dir_path) | |||
for app_name in frappe.get_all_apps(): | |||
app_doc_path = None | |||
for app_name in frappe.get_all_apps(True): | |||
pymodule = frappe.get_module(app_name) | |||
app_base_path = os.path.abspath(os.path.dirname(pymodule.__file__)) | |||
symlinks = [] | |||
app_public_path = os.path.join(app_base_path, "public") | |||
# app/public > assets/app | |||
symlinks.append([app_public_path, os.path.join(assets_path, app_name)]) | |||
# app/node_modules > assets/app/node_modules | |||
if os.path.exists(os.path.abspath(app_public_path)): | |||
symlinks.append( | |||
[ | |||
os.path.join(app_base_path, "..", "node_modules"), | |||
os.path.join(assets_path, app_name, "node_modules"), | |||
] | |||
) | |||
app_node_modules_path = os.path.join(app_base_path, "..", "node_modules") | |||
app_docs_path = os.path.join(app_base_path, "docs") | |||
app_www_docs_path = os.path.join(app_base_path, "www", "docs") | |||
app_doc_path = None | |||
if os.path.isdir(os.path.join(app_base_path, "docs")): | |||
app_doc_path = os.path.join(app_base_path, "docs") | |||
app_assets = os.path.abspath(app_public_path) | |||
app_node_modules = os.path.abspath(app_node_modules_path) | |||
elif os.path.isdir(os.path.join(app_base_path, "www", "docs")): | |||
app_doc_path = os.path.join(app_base_path, "www", "docs") | |||
# {app}/public > assets/{app} | |||
if os.path.isdir(app_assets): | |||
symlinks[app_assets] = os.path.join(assets_path, app_name) | |||
# {app}/node_modules > assets/{app}/node_modules | |||
if os.path.isdir(app_node_modules): | |||
symlinks[app_node_modules] = os.path.join(assets_path, app_name, "node_modules") | |||
# {app}/docs > assets/{app}_docs | |||
if os.path.isdir(app_docs_path): | |||
app_doc_path = os.path.join(app_base_path, "docs") | |||
elif os.path.isdir(app_www_docs_path): | |||
app_doc_path = os.path.join(app_base_path, "www", "docs") | |||
if app_doc_path: | |||
symlinks.append([app_doc_path, os.path.join(assets_path, app_name + "_docs")]) | |||
for source, target in symlinks: | |||
source = os.path.abspath(source) | |||
if os.path.exists(source): | |||
if restore: | |||
if os.path.exists(target): | |||
if os.path.islink(target): | |||
os.unlink(target) | |||
else: | |||
shutil.rmtree(target) | |||
shutil.copytree(source, target) | |||
elif make_copy: | |||
if os.path.exists(target): | |||
warnings.warn("Target {target} already exists.".format(target=target)) | |||
else: | |||
shutil.copytree(source, target) | |||
else: | |||
if os.path.exists(target): | |||
if os.path.islink(target): | |||
os.unlink(target) | |||
else: | |||
shutil.rmtree(target) | |||
try: | |||
symlink(source, target, overwrite=True) | |||
except OSError: | |||
print("Cannot link {} to {}".format(source, target)) | |||
else: | |||
warnings.warn('Source {source} does not exist.'.format(source = source)) | |||
pass | |||
app_docs = os.path.abspath(app_doc_path) | |||
symlinks[app_docs] = os.path.join(assets_path, app_name + "_docs") | |||
return symlinks | |||
def setup_assets_dirs(): | |||
for dir_path in (os.path.join(assets_path, x) for x in ("js", "css")): | |||
os.makedirs(dir_path, exist_ok=True) | |||
def clear_broken_symlinks(): | |||
for path in os.listdir(assets_path): | |||
path = os.path.join(assets_path, path) | |||
if os.path.islink(path) and not os.path.exists(path): | |||
os.remove(path) | |||
def build(no_compress=False, verbose=False): | |||
assets_path = os.path.join(frappe.local.sites_path, "assets") | |||
for target, sources in iteritems(get_build_maps()): | |||
def unstrip(message): | |||
try: | |||
max_str = os.get_terminal_size().columns | |||
except Exception: | |||
max_str = 80 | |||
_len = len(message) | |||
_rem = max_str - _len | |||
return f"{message}{' ' * _rem}" | |||
def make_asset_dirs(hard_link=False): | |||
setup_assets_dirs() | |||
clear_broken_symlinks() | |||
symlinks = generate_assets_map() | |||
for source, target in symlinks.items(): | |||
start_message = unstrip(f"{'Copying assets from' if hard_link else 'Linking'} {source} to {target}") | |||
fail_message = unstrip(f"Cannot {'copy' if hard_link else 'link'} {source} to {target}") | |||
try: | |||
print(start_message, end="\r") | |||
link_assets_dir(source, target, hard_link=hard_link) | |||
except Exception: | |||
print(fail_message, end="\r") | |||
print(unstrip(f"{green('✔')} Application Assets Linked") + "\n") | |||
def link_assets_dir(source, target, hard_link=False): | |||
if not os.path.exists(source): | |||
return | |||
if os.path.exists(target): | |||
if os.path.islink(target): | |||
os.unlink(target) | |||
else: | |||
shutil.rmtree(target) | |||
if hard_link: | |||
shutil.copytree(source, target, dirs_exist_ok=True) | |||
else: | |||
symlink(source, target, overwrite=True) | |||
def build(no_compress=False, verbose=False): | |||
for target, sources in get_build_maps().items(): | |||
pack(os.path.join(assets_path, target), sources, no_compress, verbose) | |||
@@ -348,7 +371,7 @@ def get_build_maps(): | |||
if os.path.exists(path): | |||
with open(path) as f: | |||
try: | |||
for target, sources in iteritems(json.loads(f.read())): | |||
for target, sources in (json.loads(f.read() or "{}")).items(): | |||
# update app path | |||
source_paths = [] | |||
for source in sources: | |||
@@ -381,7 +404,7 @@ def pack(target, sources, no_compress, verbose): | |||
timestamps[f] = os.path.getmtime(f) | |||
try: | |||
with open(f, "r") as sourcefile: | |||
data = text_type(sourcefile.read(), "utf-8", errors="ignore") | |||
data = str(sourcefile.read(), "utf-8", errors="ignore") | |||
extn = f.rsplit(".", 1)[1] | |||
@@ -396,7 +419,7 @@ def pack(target, sources, no_compress, verbose): | |||
jsm.minify(tmpin, tmpout) | |||
minified = tmpout.getvalue() | |||
if minified: | |||
outtxt += text_type(minified or "", "utf-8").strip("\n") + ";" | |||
outtxt += str(minified or "", "utf-8").strip("\n") + ";" | |||
if verbose: | |||
print("{0}: {1}k".format(f, int(len(minified) / 1024))) | |||
@@ -426,16 +449,16 @@ def html_to_js_template(path, content): | |||
def scrub_html_template(content): | |||
"""Returns HTML content with removed whitespace and comments""" | |||
# remove whitespace to a single space | |||
content = re.sub("\s+", " ", content) | |||
content = re.sub(r"\s+", " ", content) | |||
# strip comments | |||
content = re.sub("(<!--.*?-->)", "", content) | |||
content = re.sub(r"(<!--.*?-->)", "", content) | |||
return content.replace("'", "\'") | |||
def files_dirty(): | |||
for target, sources in iteritems(get_build_maps()): | |||
for target, sources in get_build_maps().items(): | |||
for f in sources: | |||
if ":" in f: | |||
f, suffix = f.split(":") | |||
@@ -28,6 +28,10 @@ def pass_context(f): | |||
except frappe.exceptions.SiteNotSpecifiedError as e: | |||
click.secho(str(e), fg='yellow') | |||
sys.exit(1) | |||
except frappe.exceptions.IncorrectSitePath: | |||
site = ctx.obj.get("sites", "")[0] | |||
click.secho(f'Site {site} does not exist!', fg='yellow') | |||
sys.exit(1) | |||
if profile: | |||
pr.disable() | |||
@@ -16,13 +16,13 @@ from frappe.utils import get_bench_path, update_progress_bar, cint | |||
@click.command('build') | |||
@click.option('--app', help='Build assets for app') | |||
@click.option('--make-copy', is_flag=True, default=False, help='Copy the files instead of symlinking') | |||
@click.option('--restore', is_flag=True, default=False, help='Copy the files instead of symlinking with force') | |||
@click.option('--hard-link', is_flag=True, default=False, help='Copy the files instead of symlinking') | |||
@click.option('--make-copy', is_flag=True, default=False, help='[DEPRECATED] Copy the files instead of symlinking') | |||
@click.option('--restore', is_flag=True, default=False, help='[DEPRECATED] Copy the files instead of symlinking with force') | |||
@click.option('--verbose', is_flag=True, default=False, help='Verbose') | |||
@click.option('--force', is_flag=True, default=False, help='Force build assets instead of downloading available') | |||
def build(app=None, make_copy=False, restore=False, verbose=False, force=False): | |||
def build(app=None, hard_link=False, make_copy=False, restore=False, verbose=False, force=False): | |||
"Minify + concatenate JS and CSS files, build translations" | |||
import frappe.build | |||
frappe.init('') | |||
# don't minify in developer_mode for faster builds | |||
no_compress = frappe.local.conf.developer_mode or False | |||
@@ -34,7 +34,20 @@ def build(app=None, make_copy=False, restore=False, verbose=False, force=False): | |||
else: | |||
skip_frappe = False | |||
frappe.build.bundle(no_compress, app=app, make_copy=make_copy, restore=restore, verbose=verbose, skip_frappe=skip_frappe) | |||
if make_copy or restore: | |||
hard_link = make_copy or restore | |||
click.secho( | |||
"bench build: --make-copy and --restore options are deprecated in favour of --hard-link", | |||
fg="yellow", | |||
) | |||
frappe.build.bundle( | |||
skip_frappe=skip_frappe, | |||
no_compress=no_compress, | |||
hard_link=hard_link, | |||
verbose=verbose, | |||
app=app, | |||
) | |||
@click.command('watch') | |||
@@ -488,6 +501,8 @@ frappe.db.connect() | |||
@pass_context | |||
def console(context): | |||
"Start ipython console for a site" | |||
import warnings | |||
site = get_site(context) | |||
frappe.init(site=site) | |||
frappe.connect() | |||
@@ -508,6 +523,7 @@ def console(context): | |||
if failed_to_import: | |||
print("\nFailed to import:\n{}".format(", ".join(failed_to_import))) | |||
warnings.simplefilter('ignore') | |||
IPython.embed(display_banner="", header="", colors="neutral") | |||
@@ -641,7 +641,7 @@ class Row: | |||
return | |||
elif df.fieldtype == "Duration": | |||
import re | |||
is_valid_duration = re.match("^(?:(\d+d)?((^|\s)\d+h)?((^|\s)\d+m)?((^|\s)\d+s)?)$", value) | |||
is_valid_duration = re.match(r"^(?:(\d+d)?((^|\s)\d+h)?((^|\s)\d+m)?((^|\s)\d+s)?)$", value) | |||
if not is_valid_duration: | |||
self.warnings.append( | |||
{ | |||
@@ -929,10 +929,7 @@ class Column: | |||
self.warnings.append( | |||
{ | |||
"col": self.column_number, | |||
"message": _( | |||
"Date format could not be determined from the values in" | |||
" this column. Defaulting to yyyy-mm-dd." | |||
), | |||
"message": _("Date format could not be determined from the values in this column. Defaulting to yyyy-mm-dd."), | |||
"type": "info", | |||
} | |||
) | |||
@@ -671,12 +671,12 @@ class DocType(Document): | |||
flags = {"flags": re.ASCII} if six.PY3 else {} | |||
# a DocType name should not start or end with an empty space | |||
if re.search("^[ \t\n\r]+|[ \t\n\r]+$", name, **flags): | |||
if re.search(r"^[ \t\n\r]+|[ \t\n\r]+$", name, **flags): | |||
frappe.throw(_("DocType's name should not start or end with whitespace"), frappe.NameError) | |||
# a DocType's name should not start with a number or underscore | |||
# and should only contain letters, numbers and underscore | |||
if not re.match("^(?![\W])[^\d_\s][\w ]+$", name, **flags): | |||
if not re.match(r"^(?![\W])[^\d_\s][\w ]+$", name, **flags): | |||
frappe.throw(_("DocType's name should start with a letter and it can only consist of letters, numbers, spaces and underscores"), frappe.NameError) | |||
validate_route_conflict(self.doctype, self.name) | |||
@@ -284,7 +284,7 @@ class EmailServer: | |||
flags = [] | |||
for flag in imaplib.ParseFlags(flag_string) or []: | |||
pattern = re.compile("\w+") | |||
pattern = re.compile(r"\w+") | |||
match = re.search(pattern, frappe.as_unicode(flag)) | |||
flags.append(match.group(0)) | |||
@@ -555,7 +555,7 @@ class Email: | |||
def get_thread_id(self): | |||
"""Extract thread ID from `[]`""" | |||
l = re.findall('(?<=\[)[\w/-]+', self.subject) | |||
l = re.findall(r'(?<=\[)[\w/-]+', self.subject) | |||
return l and l[0] or None | |||
@@ -226,7 +226,6 @@ scheduler_events = { | |||
"frappe.desk.doctype.event.event.send_event_digest", | |||
"frappe.sessions.clear_expired_sessions", | |||
"frappe.email.doctype.notification.notification.trigger_daily_alerts", | |||
"frappe.realtime.remove_old_task_logs", | |||
"frappe.utils.scheduler.restrict_scheduler_events_if_dormant", | |||
"frappe.email.doctype.auto_email_report.auto_email_report.send_daily", | |||
"frappe.website.doctype.personal_data_deletion_request.personal_data_deletion_request.remove_unverified_record", | |||
@@ -390,19 +390,16 @@ def get_conf_params(db_name=None, db_password=None): | |||
def make_site_dirs(): | |||
site_public_path = os.path.join(frappe.local.site_path, 'public') | |||
site_private_path = os.path.join(frappe.local.site_path, 'private') | |||
for dir_path in ( | |||
os.path.join(site_private_path, 'backups'), | |||
os.path.join(site_public_path, 'files'), | |||
os.path.join(site_private_path, 'files'), | |||
os.path.join(frappe.local.site_path, 'logs'), | |||
os.path.join(frappe.local.site_path, 'task-logs')): | |||
if not os.path.exists(dir_path): | |||
os.makedirs(dir_path) | |||
locks_dir = frappe.get_site_path('locks') | |||
if not os.path.exists(locks_dir): | |||
os.makedirs(locks_dir) | |||
for dir_path in [ | |||
os.path.join("public", "files"), | |||
os.path.join("private", "backups"), | |||
os.path.join("private", "files"), | |||
"error-snapshots", | |||
"locks", | |||
"logs", | |||
]: | |||
path = frappe.get_site_path(dir_path) | |||
os.makedirs(path, exist_ok=True) | |||
def add_module_defs(app): | |||
@@ -1,56 +1,23 @@ | |||
# -*- coding: utf-8 -*- | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors | |||
# For license information, please see license.txt | |||
from __future__ import unicode_literals | |||
import frappe | |||
import os | |||
import time | |||
import redis | |||
from io import FileIO | |||
from frappe.utils import get_site_path | |||
from frappe import conf | |||
END_LINE = '<!-- frappe: end-file -->' | |||
TASK_LOG_MAX_AGE = 86400 # 1 day in seconds | |||
redis_server = None | |||
@frappe.whitelist() | |||
def get_pending_tasks_for_doc(doctype, docname): | |||
return frappe.db.sql_list("select name from `tabAsync Task` where status in ('Queued', 'Running') and reference_doctype=%s and reference_name=%s", (doctype, docname)) | |||
def set_task_status(task_id, status, response=None): | |||
if not response: | |||
response = {} | |||
response.update({ | |||
"status": status, | |||
"task_id": task_id | |||
}) | |||
emit_via_redis("task_status_change", response, room="task:" + task_id) | |||
def remove_old_task_logs(): | |||
logs_path = get_site_path('task-logs') | |||
def full_path(_file): | |||
return os.path.join(logs_path, _file) | |||
files_to_remove = [full_path(_file) for _file in os.listdir(logs_path)] | |||
files_to_remove = [_file for _file in files_to_remove if is_file_old(_file) and os.path.isfile(_file)] | |||
for _file in files_to_remove: | |||
os.remove(_file) | |||
def is_file_old(file_path): | |||
return ((time.time() - os.stat(file_path).st_mtime) > TASK_LOG_MAX_AGE) | |||
def publish_progress(percent, title=None, doctype=None, docname=None, description=None): | |||
publish_realtime('progress', {'percent': percent, 'title': title, 'description': description}, | |||
user=frappe.session.user, doctype=doctype, docname=docname) | |||
def publish_realtime(event=None, message=None, room=None, | |||
user=None, doctype=None, docname=None, task_id=None, | |||
after_commit=False): | |||
@@ -103,6 +70,7 @@ def publish_realtime(event=None, message=None, room=None, | |||
else: | |||
emit_via_redis(event, message, room) | |||
def emit_via_redis(event, message, room): | |||
"""Publish real-time updates via redis | |||
@@ -117,57 +85,17 @@ def emit_via_redis(event, message, room): | |||
# print(frappe.get_traceback()) | |||
pass | |||
def put_log(line_no, line, task_id=None): | |||
r = get_redis_server() | |||
if not task_id: | |||
task_id = frappe.local.task_id | |||
task_progress_room = get_task_progress_room(task_id) | |||
task_log_key = "task_log:" + task_id | |||
publish_realtime('task_progress', { | |||
"message": { | |||
"lines": {line_no: line} | |||
}, | |||
"task_id": task_id | |||
}, room=task_progress_room) | |||
r.hset(task_log_key, line_no, line) | |||
r.expire(task_log_key, 3600) | |||
def get_redis_server(): | |||
"""returns redis_socketio connection.""" | |||
global redis_server | |||
if not redis_server: | |||
from redis import Redis | |||
redis_server = Redis.from_url(conf.get("redis_socketio") | |||
redis_server = Redis.from_url(frappe.conf.redis_socketio | |||
or "redis://localhost:12311") | |||
return redis_server | |||
class FileAndRedisStream(FileIO): | |||
def __init__(self, *args, **kwargs): | |||
ret = super(FileAndRedisStream, self).__init__(*args, **kwargs) | |||
self.count = 0 | |||
return ret | |||
def write(self, data): | |||
ret = super(FileAndRedisStream, self).write(data) | |||
if frappe.local.task_id: | |||
put_log(self.count, data, task_id=frappe.local.task_id) | |||
self.count += 1 | |||
return ret | |||
def get_std_streams(task_id): | |||
stdout = FileAndRedisStream(get_task_log_file_path(task_id, 'stdout'), 'w') | |||
# stderr = FileAndRedisStream(get_task_log_file_path(task_id, 'stderr'), 'w') | |||
return stdout, stdout | |||
def get_task_log_file_path(task_id, stream_type): | |||
logs_dir = frappe.utils.get_site_path('task-logs') | |||
return os.path.join(logs_dir, task_id + '.' + stream_type) | |||
@frappe.whitelist(allow_guest=True) | |||
def can_subscribe_doc(doctype, docname): | |||
if os.environ.get('CI'): | |||
@@ -201,9 +129,7 @@ def get_site_room(): | |||
def get_task_progress_room(task_id): | |||
return "".join([frappe.local.site, ":task_progress:", task_id]) | |||
# frappe.chat | |||
def get_chat_room(room): | |||
room = ''.join([frappe.local.site, ":room:", room]) | |||
return room | |||
# end frappe.chat room |
@@ -1,8 +1,8 @@ | |||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
import frappe | |||
from frappe.utils import update_progress_bar | |||
from whoosh.index import create_in, open_dir, EmptyIndexError | |||
from whoosh.fields import TEXT, ID, Schema | |||
@@ -95,9 +95,10 @@ class FullTextSearch: | |||
ix = self.create_index() | |||
writer = ix.writer() | |||
for document in self.documents: | |||
for i, document in enumerate(self.documents): | |||
if document: | |||
writer.add_document(**document) | |||
update_progress_bar("Building Index", i, len(self.documents)) | |||
writer.commit(optimize=True) | |||
@@ -1,14 +1,15 @@ | |||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
import frappe | |||
import os | |||
from bs4 import BeautifulSoup | |||
from whoosh.fields import TEXT, ID, Schema | |||
from whoosh.fields import ID, TEXT, Schema | |||
import frappe | |||
from frappe.search.full_text_search import FullTextSearch | |||
from frappe.utils import set_request, update_progress_bar | |||
from frappe.website.render import render_page | |||
from frappe.utils import set_request | |||
import os | |||
INDEX_NAME = "web_routes" | |||
@@ -30,11 +31,21 @@ class WebsiteSearch(FullTextSearch): | |||
Returns: | |||
self (object): FullTextSearch Instance | |||
""" | |||
routes = get_static_pages_from_all_apps() | |||
routes += slugs_with_web_view() | |||
documents = [self.get_document_to_index(route) for route in routes] | |||
return documents | |||
if getattr(self, "_items_to_index", False): | |||
return self._items_to_index | |||
routes = get_static_pages_from_all_apps() + slugs_with_web_view() | |||
self._items_to_index = [] | |||
for i, route in enumerate(routes): | |||
update_progress_bar("Retrieving Routes", i, len(routes)) | |||
self._items_to_index += [self.get_document_to_index(route)] | |||
print() | |||
return self.get_items_to_index() | |||
def get_document_to_index(self, route): | |||
"""Render a page and parse it using BeautifulSoup | |||
@@ -114,4 +125,4 @@ def remove_document_from_index(path): | |||
def build_index_for_all_routes(): | |||
ws = WebsiteSearch(INDEX_NAME) | |||
return ws.build() | |||
return ws.build() |
@@ -161,7 +161,7 @@ def validate_url(txt, throw=False, valid_schemes=None): | |||
Parameters: | |||
throw (`bool`): throws a validationError if URL is not valid | |||
valid_schemes (`str` or `list`): if provided checks the given URL's scheme against this | |||
valid_schemes (`str` or `list`): if provided checks the given URL's scheme against this | |||
Returns: | |||
bool: if `txt` represents a valid URL | |||
@@ -225,14 +225,17 @@ def get_gravatar(email): | |||
return gravatar_url | |||
def get_traceback(): | |||
def get_traceback() -> str: | |||
""" | |||
Returns the traceback of the Exception | |||
""" | |||
exc_type, exc_value, exc_tb = sys.exc_info() | |||
if not any([exc_type, exc_value, exc_tb]): | |||
return "" | |||
trace_list = traceback.format_exception(exc_type, exc_value, exc_tb) | |||
body = "".join(cstr(t) for t in trace_list) | |||
return body | |||
return "".join(cstr(t) for t in trace_list) | |||
def log(event, details): | |||
frappe.logger().info(details) | |||
@@ -425,7 +428,7 @@ def get_test_client(): | |||
return Client(application) | |||
def get_hook_method(hook_name, fallback=None): | |||
method = (frappe.get_hooks().get(hook_name)) | |||
method = frappe.get_hooks().get(hook_name) | |||
if method: | |||
method = frappe.get_attr(method[0]) | |||
return method | |||
@@ -439,6 +442,16 @@ def call_hook_method(hook, *args, **kwargs): | |||
return out | |||
def is_cli() -> bool: | |||
"""Returns True if current instance is being run via a terminal | |||
""" | |||
invoked_from_terminal = False | |||
try: | |||
invoked_from_terminal = bool(os.get_terminal_size()) | |||
except Exception: | |||
invoked_from_terminal = sys.stdin.isatty() | |||
return invoked_from_terminal | |||
def update_progress_bar(txt, i, l): | |||
if os.environ.get("CI"): | |||
if i == 0: | |||
@@ -448,7 +461,7 @@ def update_progress_bar(txt, i, l): | |||
sys.stdout.flush() | |||
return | |||
if not getattr(frappe.local, 'request', None): | |||
if not getattr(frappe.local, 'request', None) or is_cli(): | |||
lt = len(txt) | |||
try: | |||
col = 40 if os.get_terminal_size().columns > 80 else 20 | |||
@@ -40,7 +40,7 @@ def make_boilerplate(dest, app_name): | |||
if hook_key=="app_name" and hook_val.lower().replace(" ", "_") != hook_val: | |||
print("App Name must be all lowercase and without spaces") | |||
hook_val = "" | |||
elif hook_key=="app_title" and not re.match("^(?![\W])[^\d_\s][\w -]+$", hook_val, re.UNICODE): | |||
elif hook_key=="app_title" and not re.match(r"^(?![\W])[^\d_\s][\w -]+$", hook_val, re.UNICODE): | |||
print("App Title should start with a letter and it can only consist of letters, numbers, spaces and underscores") | |||
hook_val = "" | |||
@@ -98,7 +98,7 @@ class RedisWrapper(redis.Redis): | |||
return self.keys(key) | |||
except redis.exceptions.ConnectionError: | |||
regex = re.compile(cstr(key).replace("|", "\|").replace("*", "[\w]*")) | |||
regex = re.compile(cstr(key).replace("|", r"\|").replace("*", r"[\w]*")) | |||
return [k for k in list(frappe.local.cache) if regex.match(cstr(k))] | |||
def delete_keys(self, key): | |||
@@ -61,7 +61,7 @@ def update_controller_context(context, controller): | |||
except (frappe.PermissionError, frappe.PageDoesNotExistError, frappe.Redirect): | |||
raise | |||
except: | |||
if not frappe.flags.in_migrate: | |||
if not any([frappe.flags.in_migrate, frappe.flags.in_website_search_build]): | |||
frappe.errprint(frappe.utils.get_traceback()) | |||
if hasattr(module, "get_children"): | |||