From c5b7910ded6e776c42db581a2a3159bda2592a70 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Sat, 7 Dec 2024 13:53:17 +0100 Subject: [PATCH] fix: improve bencher (#28693) --- frappe/bencher.py | 51 ++++++++++++++++++++++++++++------------------- frappe/config.py | 15 +++++++------- 2 files changed, 38 insertions(+), 28 deletions(-) diff --git a/frappe/bencher.py b/frappe/bencher.py index c8f3e0c967..cc1a8dee79 100644 --- a/frappe/bencher.py +++ b/frappe/bencher.py @@ -34,19 +34,23 @@ class Serializable: class BenchPathResolver(PathLike, Serializable): def __init__(self, path: str | Path | None = None, parent_path: Path | None = None) -> None: - path = ( - path - or os.environ.get(f"FRAPPE_{self.__class__.__name__.upper()}_PATH") - or (parent_path.joinpath(self.__class__.__name__.lower()) if parent_path else None) - ) + if isinstance(self, Sites) and (env_path := os.environ.get("SITES_PATH")): + path = Path(env_path).resolve() - if not path and isinstance(self, Bench): + if path is None: + path = os.environ.get(f"FRAPPE_{self.__class__.__name__.upper()}_PATH") or ( + parent_path.joinpath(self.__class__.__name__.lower()) if parent_path else None + ) + + if path is None and isinstance(self, Bench): + env_path = os.environ.get("FRAPPE_BENCH_ROOT") + sites_env_path = os.environ.get("SITES_PATH") path = ( - # TODO: legacy, remove - os.environ.get("FRAPPE_BENCH_ROOT") - or + env_path + # TODO: legacy unsave relative reference, remove + or (Path(sites_env_path).resolve().parent if sites_env_path else None) # TODO: unsave relative reference, remove: - Path(__file__).resolve().parents[3] # bench folder in standard layout + or Path(__file__).resolve().parents[3] # bench folder in standard layout # TODO: when above line removed, enable: # or (Path("~/frappe-bench").expanduser()) ) @@ -54,7 +58,7 @@ class BenchPathResolver(PathLike, Serializable): if path is None: raise ValueError(f"Unable to determine path for {self.__class__.__name__}") - self.path = Path(path) + self.path = Path(path).resolve() class FrappeComponent: @@ -361,16 +365,17 @@ class Sites(BenchPathResolver, ConfigHandler): if not hasattr(self, "__sites"): self.__sites = {} - def _process(path: Path): - if path.is_dir() and path.joinpath("site_config.json").exists(): - self.__sites[path.name] = self.Site(bench=self.bench, name=path.name, path=path) - # security & thread-safety: site_name is stored in thread-local storage - if self.site_name and self.site_name != self.SCOPE_ALL_SITES: - _process(self.path.joinpath(self.site_name)) - elif self.site_name == self.SCOPE_ALL_SITES: - for site_path in self.path.iterdir(): - _process(site_path) + if self.site_name == self.SCOPE_ALL_SITES: + for path in self.path.iterdir(): + if path.is_dir() and path.joinpath("site_config.json").exists(): + self.__sites[path.name] = self.Site(bench=self.bench, name=path.name, path=path) + elif self.site_name: + # init scoped site even if it doesn't exist for use during site creation + # all file access must reamin lazy and only happen after completed setup + self.__sites[self.site_name] = self.Site( + bench=self.bench, name=self.site_name, path=(self.path / self.site_name) + ) return self.__sites def __iter__(self) -> Iterator[Site]: @@ -388,7 +393,7 @@ class Sites(BenchPathResolver, ConfigHandler): try: return self._sites[key] except KeyError: - raise BenchSiteNotLoadedError(f"Site '{key}' was not loaded") + raise BenchSiteNotLoadedError(f"Site '{key}' was not found") class Bench(BenchPathResolver): @@ -401,6 +406,10 @@ class Bench(BenchPathResolver): _current_bench = self self.apps = Apps(parent_path=self.path) + @property + def site(self) -> Sites.Site: + return self.sites.site + def scope(self, site_name: str) -> Sites.Site: return self.sites.scope(site_name) diff --git a/frappe/config.py b/frappe/config.py index c02add56b6..d9882030a9 100644 --- a/frappe/config.py +++ b/frappe/config.py @@ -142,7 +142,7 @@ class ConfigHandler: "Get current configuration, reloading if stale" if not hasattr(self, "_config") or self._config_stale: if self.config_path.exists(): - self.__config = json.load(self.config_path.open()) + self.__config = json.loads(self.config_path.read_bytes()) self._config = ConfigType(**self.__config) self._update_from_env() self._apply_extra_config() @@ -164,12 +164,13 @@ class ConfigHandler: with FileLock(f"{self.config_path}.lock", timeout=5): from frappe.utils.response import json_handler - json.dump( - self.__config, - self.config_path.open("w"), - indent=2, - default=json_handler, # type: ignore[no-any-expr] - sort_keys=True, + self.config_path.write_text( + json.dumps( + self.__config, + indent=2, + default=json_handler, # type: ignore[no-any-expr] + sort_keys=True, + ) ) except Timeout as e: