diff --git a/.github/helper/roulette.py b/.github/helper/roulette.py index 46dcfc4..dbb1778 100644 --- a/.github/helper/roulette.py +++ b/.github/helper/roulette.py @@ -82,7 +82,9 @@ def is_ci(file): def is_frontend_code(file): - return file.lower().endswith((".css", ".scss", ".less", ".sass", ".styl", ".js", ".ts", ".vue", ".html")) + return file.lower().endswith( + (".css", ".scss", ".less", ".sass", ".styl", ".js", ".ts", ".vue", ".html") + ) def is_docs(file): @@ -109,7 +111,9 @@ def is_docs(file): ci_files_changed = any(f for f in files_list if is_ci(f)) only_docs_changed = len(list(filter(is_docs, files_list))) == len(files_list) - only_frontend_code_changed = len(list(filter(is_frontend_code, files_list))) == len(files_list) + only_frontend_code_changed = len(list(filter(is_frontend_code, files_list))) == len( + files_list + ) updated_py_file_count = len(list(filter(is_py, files_list))) only_py_changed = updated_py_file_count == len(files_list) @@ -137,7 +141,9 @@ def is_docs(file): print("Only Frontend code was updated; Stopping Python build process.") sys.exit(0) - elif build_type == "ui" and only_py_changed and not has_run_ui_tests_label(pr_number, repo): + elif ( + build_type == "ui" and only_py_changed and not has_run_ui_tests_label(pr_number, repo) + ): print("Only Python code was updated, stopping Cypress build process.") sys.exit(0) diff --git a/.github/validate_customizations.py b/.github/validate_customizations.py index f942698..58a4e4e 100644 --- a/.github/validate_customizations.py +++ b/.github/validate_customizations.py @@ -13,7 +13,9 @@ def unscrub(txt: str) -> str: def get_customized_doctypes(): apps_dir = pathlib.Path(__file__).resolve().parent.parent.parent - apps_order = pathlib.Path(__file__).resolve().parent.parent.parent.parent / "sites" / "apps.txt" + apps_order = ( + pathlib.Path(__file__).resolve().parent.parent.parent.parent / "sites" / "apps.txt" + ) apps_order = apps_order.read_text().split("\n") customized_doctypes = {} for _app_dir in apps_order: @@ -24,7 +26,9 @@ def get_customized_doctypes(): for module in modules: if not (app_dir / _app_dir / scrub(module) / "custom").exists(): continue - for custom_file in list((app_dir / _app_dir / scrub(module) / "custom").glob("**/*.json")): + for custom_file in list( + (app_dir / _app_dir / scrub(module) / "custom").glob("**/*.json") + ): if custom_file.stem in customized_doctypes: customized_doctypes[custom_file.stem].append(custom_file.resolve()) else: @@ -89,7 +93,9 @@ def validate_no_custom_perms(customized_doctypes): continue file_contents = json.loads(customize_file.read_text()) if file_contents.get("custom_perms"): - exceptions.append(f"Customization for {doctype} in {this_app} contains custom permissions") + exceptions.append( + f"Customization for {doctype} in {this_app} contains custom permissions" + ) return exceptions diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1fbe0ae..dca274b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -68,18 +68,13 @@ repos: args: ['--quiet'] # Ignore any files that might contain jinja / bundles exclude: | - (?x)^( - .*node_modules.*| - cloud_storage/public/dist/.*| - cloud_storage/public/js/lib/.*| - cloud_storage/templates/includes/.*| - cloud_storage/www/website_script.js - )$ - - - repo: https://github.com/PyCQA/isort - rev: 5.12.0 - hooks: - - id: isort + (?x)^( + .*node_modules.*| + cloud_storage/public/dist/.*| + cloud_storage/public/js/lib/.*| + cloud_storage/templates/includes/.*| + cloud_storage/www/website_script.js + )$ - repo: https://github.com/PyCQA/flake8 rev: 6.0.0 diff --git a/cloud_storage/cloud_storage/overrides/file.py b/cloud_storage/cloud_storage/overrides/file.py index 10484dd..533a6a5 100644 --- a/cloud_storage/cloud_storage/overrides/file.py +++ b/cloud_storage/cloud_storage/overrides/file.py @@ -15,7 +15,10 @@ from botocore.exceptions import ClientError from frappe import DoesNotExistError, _ from frappe.core.doctype.file.file import File, get_files_path -from frappe.core.doctype.file.utils import decode_file_content, get_content_hash +from frappe.core.doctype.file.utils import ( + decode_file_content, + get_content_hash, +) from frappe.model.rename_doc import rename_doc from frappe.permissions import has_user_permission from frappe.utils import get_datetime, get_url @@ -25,6 +28,7 @@ from werkzeug.datastructures import FileStorage from urllib.parse import quote + FILE_URL = "/api/method/retrieve?key={path}" URL_PREFIXES = ("http://", "https://", "/api/method/retrieve") @@ -36,7 +40,9 @@ def is_remote_file(self) -> bool: return self.file_url.startswith(URL_PREFIXES) # type: ignore return not self.content - def has_permission(self, ptype: Optional[str] = None, user: Optional[str] = None) -> bool: + def has_permission( + self, ptype: Optional[str] = None, user: Optional[str] = None + ) -> bool: return has_permission(self, ptype, user) def validate(self) -> None: @@ -91,7 +97,9 @@ def on_trash(self) -> None: self._delete_file_on_disk() # even though the code is unreachable, we're keeping it here for reference if not self.is_folder and len(self.file_association) > 0: - self.add_comment_in_reference_doc("Attachment Removed", _("Removed {0}").format(self.file_name)) + self.add_comment_in_reference_doc( + "Attachment Removed", _("Removed {0}").format(self.file_name) + ) def associate_files( self, attached_to_doctype: str | None = None, attached_to_name: str | None = None @@ -123,7 +131,8 @@ def associate_files( existing_attachment = list( filter( - lambda row: row.link_doctype == attached_to_doctype and row.link_name == attached_to_name, + lambda row: row.link_doctype == attached_to_doctype + and row.link_name == attached_to_name, self.file_association, ) ) @@ -221,7 +230,9 @@ def get_full_path(self): file_path = f"/files/{file_path}" if file_path.startswith("/private/files/"): - file_path = get_files_path(*file_path.split("/private/files/", 1)[1].split("/"), is_private=1) + file_path = get_files_path( + *file_path.split("/private/files/", 1)[1].split("/"), is_private=1 + ) elif file_path.startswith("/files/"): file_path = get_files_path(*file_path.split("/files/", 1)[1].split("/")) @@ -279,7 +290,9 @@ def get_sharing_link(docname: str, reset: str | bool | None = None) -> str: reset = json.loads(reset) doc = frappe.get_doc("File", docname) if doc.is_private: - frappe.has_permission(doctype="File", ptype="share", doc=doc, user=frappe.session.user, throw=True) + frappe.has_permission( + doctype="File", ptype="share", doc=doc, user=frappe.session.user, throw=True + ) if reset or not doc.sharing_link: doc.db_set("sharing_link", str(uuid.uuid4().int >> 64)) return f"{get_url()}/api/method/share?key={doc.sharing_link}" @@ -372,7 +385,9 @@ def get_presigned_url(client, key: str): def get_sharing_url(client, key: str) -> str: - file = frappe.get_value("File", {"sharing_link": key}, ["name", "s3_key"], as_dict=True) + file = frappe.get_value( + "File", {"sharing_link": key}, ["name", "s3_key"], as_dict=True + ) if not file: raise DoesNotExistError(frappe._("The file you are looking for is not available")) @@ -425,7 +440,10 @@ def get_file_content_hash(content, content_type): @frappe.whitelist() def write_file(file: File, remove_spaces_in_file_name: bool = True) -> File: - if not frappe.conf.cloud_storage_settings or frappe.conf.cloud_storage_settings.get("use_local", False): + if ( + not frappe.conf.cloud_storage_settings + or frappe.conf.cloud_storage_settings.get("use_local", False) + ): file.save_file_on_filesystem() return file @@ -435,7 +453,9 @@ def write_file(file: File, remove_spaces_in_file_name: bool = True) -> File: # if a hash-conflict is found, update the existing document with a new file association existing_file_hashes = frappe.get_all( - "File", filters={"name": ["!=", file.name], "content_hash": file.content_hash}, pluck="name" + "File", + filters={"name": ["!=", file.name], "content_hash": file.content_hash}, + pluck="name", ) if existing_file_hashes: @@ -452,7 +472,11 @@ def write_file(file: File, remove_spaces_in_file_name: bool = True) -> File: if existing_file_names: file_doc = frappe.get_doc("File", existing_file_names[0]) file_doc.update( - {"content": file.content, "content_hash": file.content_hash, "content_type": file.content_type} + { + "content": file.content, + "content_hash": file.content_hash, + "content_type": file.content_type, + } ) file_doc.associate_files(file.attached_to_doctype, file.attached_to_name) file = file_doc @@ -467,7 +491,10 @@ def write_file(file: File, remove_spaces_in_file_name: bool = True) -> File: @frappe.whitelist() def delete_file(file: File, **kwargs) -> File: - if not frappe.conf.cloud_storage_settings or frappe.conf.cloud_storage_settings.get("use_local", False): + if ( + not frappe.conf.cloud_storage_settings + or frappe.conf.cloud_storage_settings.get("use_local", False) + ): file.delete_file_from_filesystem() return file @@ -500,7 +527,9 @@ def validate_file_content(*args, **kwargs): # validate filename file_name = file.filename - existing_files_by_name = frappe.get_all("File", filters={"file_name": file_name}, pluck="file_name") + existing_files_by_name = frappe.get_all( + "File", filters={"file_name": file_name}, pluck="file_name" + ) # validate content hash file.stream.seek(0) diff --git a/cloud_storage/config/desktop.py b/cloud_storage/config/desktop.py index d29bd1a..7aa8966 100644 --- a/cloud_storage/config/desktop.py +++ b/cloud_storage/config/desktop.py @@ -2,4 +2,6 @@ def get_data() -> list: - return [{"module_name": "Cloud Storage", "type": "module", "label": _("Cloud Storage")}] + return [ + {"module_name": "Cloud Storage", "type": "module", "label": _("Cloud Storage")} + ] diff --git a/cloud_storage/customize.py b/cloud_storage/customize.py index 56aae59..1e35490 100644 --- a/cloud_storage/customize.py +++ b/cloud_storage/customize.py @@ -9,7 +9,12 @@ def load_customizations(): print("Loading Cloud Storage customizations") customizations_directory = ( - Path().cwd().parent / "apps" / "cloud_storage" / "cloud_storage" / "cloud_storage" / "custom" + Path().cwd().parent + / "apps" + / "cloud_storage" + / "cloud_storage" + / "cloud_storage" + / "custom" ) files = list(customizations_directory.glob("**/*.json")) for file in files: diff --git a/cloud_storage/hooks.py b/cloud_storage/hooks.py index e9d145e..d955db1 100644 --- a/cloud_storage/hooks.py +++ b/cloud_storage/hooks.py @@ -94,7 +94,9 @@ # --------------- # Override standard doctype classes -override_doctype_class = {"File": "cloud_storage.cloud_storage.overrides.file.CustomFile"} +override_doctype_class = { + "File": "cloud_storage.cloud_storage.overrides.file.CustomFile" +} # Document Events # --------------- diff --git a/cloud_storage/tests/test_file.py b/cloud_storage/tests/test_file.py index 41b5312..c911533 100644 --- a/cloud_storage/tests/test_file.py +++ b/cloud_storage/tests/test_file.py @@ -26,7 +26,9 @@ def example_file_record_2(): @pytest.fixture def get_cloud_storage_client_fixture(): - return frappe.call("cloud_storage.cloud_storage.overrides.file.get_cloud_storage_client") + return frappe.call( + "cloud_storage.cloud_storage.overrides.file.get_cloud_storage_client" + ) # helper function @@ -77,7 +79,8 @@ def test_upload_file(example_file_record_0): assert file.file_name == "aticonrusthex.png" assert file.content_hash is not None assert ( - file.file_url == "/api/method/retrieve?key=test_folder/User/Administrator/aticonrusthex.png" + file.file_url + == "/api/method/retrieve?key=test_folder/User/Administrator/aticonrusthex.png" ) assert file.is_private == 0 # makes the delete file test easier assert file.s3_key is not None @@ -86,7 +89,9 @@ def test_upload_file(example_file_record_0): assert file.file_association[0].link_name == "Administrator" # Test manual association - file.append("file_association", {"link_doctype": "Module Def", "link_name": "Cloud Storage"}) + file.append( + "file_association", {"link_doctype": "Module Def", "link_name": "Cloud Storage"} + ) file.save() assert len(file.file_association) == 2 assert file.file_association[1].link_doctype == "Module Def"