diff --git a/.gitignore b/.gitignore index 3d849e9..c7f013c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,6 @@ build/ dist/ *.egg-info/ __pycache__/ +.ruff_cache/ instance/ diff --git a/cloudflare_error_page/__init__.py b/cloudflare_error_page/__init__.py index e4d048a..d0784ff 100644 --- a/cloudflare_error_page/__init__.py +++ b/cloudflare_error_page/__init__.py @@ -8,6 +8,7 @@ if sys.version_info >= (3, 11): from typing import NotRequired else: from typing import _SpecialForm + NotRequired: _SpecialForm @@ -40,7 +41,7 @@ class ErrorPageParams(TypedDict): class PerfSecBy(TypedDict): text: NotRequired[str] link: NotRequired[str] - + class CreatorInfo(TypedDict): hidden: NotRequired[bool] link: NotRequired[str] @@ -69,12 +70,14 @@ class ErrorPageParams(TypedDict): creator_info: NotRequired[CreatorInfo] -def render(params: ErrorPageParams, - allow_html: bool = True, - template: Template | None = None, - *args: Any, - **kwargs: Any) -> str: - '''Render a customized Cloudflare error page. +def render( + params: ErrorPageParams, + allow_html: bool = True, + template: Template | None = None, + *args: Any, + **kwargs: Any, +) -> str: + """Render a customized Cloudflare error page. :param params: Parameters passed to the template. Refer to the project homepage for more information. :param allow_html: Allow output raw HTML content from parameters. Set to False if you don't trust the source of the params. @@ -83,7 +86,7 @@ def render(params: ErrorPageParams, :param args: Additional positional arguments passed to ``Template.render`` function. :param kwargs: Additional keyword arguments passed to ``Template.render`` function. :return: The rendered error page as a string. - ''' + """ if not template: template = base_template @@ -106,5 +109,6 @@ def render(params: ErrorPageParams, return template.render(params=params, *args, **kwargs) + __version__ = '0.2.0' __all__ = ['jinja_env', 'base_template', 'render'] diff --git a/editor/server/app/__init__.py b/editor/server/app/__init__.py index 024a044..ff96f62 100644 --- a/editor/server/app/__init__.py +++ b/editor/server/app/__init__.py @@ -1,11 +1,10 @@ # SPDX-License-Identifier: MIT import os +from pathlib import Path import secrets import string -import sys import tomllib -from pathlib import Path from flask import Flask, redirect, url_for from flask_limiter import Limiter @@ -16,17 +15,24 @@ from werkzeug.middleware.proxy_fix import ProxyFix root_dir = Path(__file__).parent.parent.parent.parent + class Base(DeclarativeBase): pass -db: SQLAlchemy = SQLAlchemy(model_class=Base, session_options={ - # 'autobegin': False, - # 'expire_on_commit': False, -}) + +db: SQLAlchemy = SQLAlchemy( + model_class=Base, + session_options={ + # 'autobegin': False, + # 'expire_on_commit': False, + }, +) limiter: Limiter = Limiter( key_func=get_remote_address, # Uses client's IP address by default ) +static_dir: str | None = None + def _generate_secret(length=32) -> str: characters = string.ascii_letters + string.digits # A-Z, a-z, 0-9 @@ -34,10 +40,9 @@ def _generate_secret(length=32) -> str: def _initialize_app_config(app: Flask): + global static_dir if app.config.get('BEHIND_PROXY', True): - app.wsgi_app = ProxyFix( - app.wsgi_app, x_for=1, x_proto=1 - ) + app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1) secret_key = app.config.get('SECRET_KEY', '') if secret_key: app.secret_key = secret_key @@ -45,12 +50,14 @@ def _initialize_app_config(app: Flask): app.logger.info('Using generated secret') app.secret_key = _generate_secret() - app.config["SQLALCHEMY_DATABASE_URI"] = app.config.get('SQLALCHEMY_DATABASE_URI', 'sqlite:///example.db') - if app.config["SQLALCHEMY_DATABASE_URI"].startswith('sqlite'): - app.config["SQLALCHEMY_ENGINE_OPTIONS"] = { + app.config['SQLALCHEMY_DATABASE_URI'] = app.config.get('SQLALCHEMY_DATABASE_URI', 'sqlite:///example.db') + if app.config['SQLALCHEMY_DATABASE_URI'].startswith('sqlite'): + app.config['SQLALCHEMY_ENGINE_OPTIONS'] = { 'isolation_level': 'SERIALIZABLE', # "execution_options": {"autobegin": False} } + static_dir = os.path.join(app.instance_path, app.config.get('STATIC_DIR', '../../web/dist')) + app.logger.info(f'Static directory: {static_dir}') def create_app(test_config=None) -> Flask: @@ -60,16 +67,12 @@ def create_app(test_config=None) -> Flask: os.makedirs(instance_path, exist_ok=True) print(f'App instance path: {instance_path}') - app = Flask(__name__, - instance_path=instance_path, - instance_relative_config=True - ) - app.config.from_file("config.toml", load=tomllib.load, text=False) + app = Flask(__name__, instance_path=instance_path, instance_relative_config=True) + app.config.from_file('config.toml', load=tomllib.load, text=False) _initialize_app_config(app) - - from . import utils - from . import models + from . import utils # noqa: F401 + from . import models # noqa: F401 from . import examples from . import editor from . import share diff --git a/editor/server/app/share.py b/editor/server/app/share.py index 5b67098..1e2dbbc 100644 --- a/editor/server/app/share.py +++ b/editor/server/app/share.py @@ -28,15 +28,16 @@ from .utils import ( bp = Blueprint('share', __name__, url_prefix='/') bp_short = Blueprint('share_short', __name__, url_prefix='/') -rand_charset = string.ascii_lowercase + string.digits +rand_charset = string.ascii_lowercase + string.digits + def get_rand_name(digits=8): return ''.join(random.choice(rand_charset) for _ in range(digits)) @bp.post('/create') -@limiter.limit("20 per minute") -@limiter.limit("500 per hour") +@limiter.limit('20 per minute') +@limiter.limit('500 per hour') def create(): if len(request.data) > 4096: abort(413) @@ -82,16 +83,14 @@ def get(name: str): item = db.session.query(models.Item).filter_by(name=name).first() if not item: if is_json: - return { - 'status': 'notfound' - } + return {'status': 'notfound'} else: return abort(404) params = cast(ErrorPageParams, item.params) params.pop('time', None) params.pop('ray_id', None) params.pop('client_ip', None) - + if is_json: return { 'status': 'ok', @@ -104,8 +103,7 @@ def get(name: str): 'link': request.host_url[:-1] + url_for('editor.index') + f'#from={name}', } sanitize_page_param_links(params) - return render_extended_template(params=params, - allow_html=False) + return render_extended_template(params=params, allow_html=False) @bp.get('/') diff --git a/editor/server/app/utils.py b/editor/server/app/utils.py index 30f5562..e8ec979 100644 --- a/editor/server/app/utils.py +++ b/editor/server/app/utils.py @@ -17,7 +17,7 @@ env = Environment( trim_blocks=True, lstrip_blocks=True, ) -template = env.from_string('''{% extends base %} +template = env.from_string("""{% extends base %} {% block html_head %} {% if page_icon_url %} @@ -45,11 +45,12 @@ template = env.from_string('''{% extends base %} {% endif %} {% endblock %} -''') +""") loc_data: dict = None + def read_loc_file(path: str): try: with open(os.path.join(Path(__file__).parent / path)) as f: @@ -117,11 +118,9 @@ def sanitize_page_param_links(param: ErrorPageParams): perf_sec_by['link'] = sanitize_user_link(link) -def render_extended_template(params: ErrorPageParams, - *args: Any, - **kwargs: Any) -> str: +def render_extended_template(params: ErrorPageParams, *args: Any, **kwargs: Any) -> str: fill_cf_template_params(params) - description = params.get('what_happened') or 'There is an internal server error on Cloudflare\'s network.' + description = params.get('what_happened') or "There is an internal server error on Cloudflare's network." description = re.sub(r'<\/?.*?>', '', description).strip() status = 'ok' @@ -133,13 +132,15 @@ def render_extended_template(params: ErrorPageParams, page_icon_url = current_app.config.get('PAGE_ICON_URL', '').replace('{status}', status) page_icon_type = current_app.config.get('PAGE_ICON_TYPE') page_image_url = current_app.config.get('PAGE_IMAGE_URL', '').replace('{status}', status) - return render_cf_error_page(params=params, - template=template, - base=base_template, - page_icon_url=page_icon_url, - page_icon_type=page_icon_type, - page_url=request.url, - page_description=description, - page_image_url=page_image_url, - *args, - **kwargs) + return render_cf_error_page( + params=params, + template=template, + base=base_template, + page_icon_url=page_icon_url, + page_icon_type=page_icon_type, + page_url=request.url, + page_description=description, + page_image_url=page_image_url, + *args, + **kwargs, + ) diff --git a/editor/server/pyproject.toml b/editor/server/pyproject.toml index 7b20d19..e7e0d88 100644 --- a/editor/server/pyproject.toml +++ b/editor/server/pyproject.toml @@ -23,4 +23,16 @@ allow-direct-references = true include = ["app/**/*"] [tool.hatch.build.targets.wheel.hooks.custom] -path = "hatch_build.py" \ No newline at end of file +path = "hatch_build.py" + +[tool.ruff] +line-length = 120 +target-version = "py313" + +[tool.ruff.lint] +ignore = [ + 'E722', # Bare-except +] + +[tool.ruff.format] +quote-style = "single" diff --git a/pyproject.toml b/pyproject.toml index 6f4b18b..8403816 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,3 +46,19 @@ path = "scripts/hatch_build.py" [tool.hatch.version] path = "cloudflare_error_page/__init__.py" + +[tool.ruff] +line-length = 120 +target-version = "py310" +include = [ + "cloudflare_error_page/**/*.py", + "scripts/**/*.py", +] + +[tool.ruff.lint] +ignore = [ + 'E722', # Bare-except +] + +[tool.ruff.format] +quote-style = "single" diff --git a/scripts/hatch_build.py b/scripts/hatch_build.py index 21d7756..f9a9a42 100644 --- a/scripts/hatch_build.py +++ b/scripts/hatch_build.py @@ -9,6 +9,7 @@ from hatchling.builders.hooks.plugin.interface import BuildHookInterface sys.path.append(os.path.dirname(__file__)) from inline_resources import generate_inlined_css + class CustomBuildHook(BuildHookInterface): def initialize(self, version: str, build_data: dict[str, Any]): generate_inlined_css() diff --git a/scripts/inline_resources.py b/scripts/inline_resources.py index ccb5c89..a3f2253 100644 --- a/scripts/inline_resources.py +++ b/scripts/inline_resources.py @@ -38,8 +38,7 @@ def inline_svg_resources(css_file: str, svg_files: list[str], output_file: str): def inline_css_resource(original_file: str, css_file: str, output_file: str): css_data = read_file(css_file) original_data = read_file(original_file) - original_data = original_data.replace('', - f'') + original_data = original_data.replace('', f'') note = 'Note: This file is generated with scripts/inline_resources.py. Please do not edit manually.' if original_file.endswith('.ejs'): original_data = f'<%# {note} %>\n' + original_data @@ -60,7 +59,7 @@ def generate_inlined_css(): '../images/cf-icon-error.svg', ], os.path.join(resources_folder, 'styles/main.css'), - ) + ) if __name__ == '__main__':