Source code for bw2io.remote

from packaging.version import parse as vparse
from pathlib import Path
from typing import Optional, Union
from urllib.parse import urljoin

import bw2data as bd

from .backup import restore_project_directory
from .download_utils import download_with_progressbar

[docs] PROJECTS_BW2 = { "ecoinvent-3.8-biosphere": "ecoinvent-3.8-biosphere.bw2.tar.gz", "ecoinvent-3.9.1-biosphere": "ecoinvent-3.9.1-biosphere.bw2.tar.gz", "ecoinvent-3.10-biosphere": "ecoinvent-3.10-biosphere.bw2.tar.gz", }
[docs] PROJECTS_BW25 = { "ecoinvent-3.8-biosphere": "ecoinvent-3.8-biosphere.tar.gz", "ecoinvent-3.9.1-biosphere": "ecoinvent-3.9.1-biosphere.tar.gz", "USEEIO-1.1": "USEEIO-1.1.tar.gz", "forwast": "forwast.tar.gz", }
[docs] BASE_URL = "https://files.brightway.dev/"
[docs] cache_dir = Path(bd.projects._base_data_dir) / "bw2io_cache_dir"
cache_dir.mkdir(exist_ok=True)
[docs] def _projects_config_filename(version) -> str: """Return the config filename given a bd.__version__ (str or tuple).""" # Normalize tuple -> string if isinstance(version, tuple): version = ".".join(map(str, version)) return "projects-config.json" if vparse(str(version)) >= vparse("4") else "projects-config.bw2.json"
[docs] def _fetch_projects_config(base_url: str, filename: str) -> dict: """Indirection point for I/O (easy to mock in tests).""" import requests # local import so tests don’t even need requests if they patch this try: response = requests.get(urljoin(base_url, filename), timeout=10) response.raise_for_status() return response.json() except requests.exceptions.RequestException as exc: print(f"Can't connect to {base_url}: {exc}") except ValueError as exc: # JSON decoding error print(f"Invalid JSON received from {base_url}: {exc}")
[docs] def get_projects(update_config: bool = True, base_url: str = BASE_URL) -> dict: bd_version = bd.__version__ if not isinstance(bd_version, str): bd_version = ".".join(map(str, bd_version)) BW2 = vparse(bd_version) < vparse("4") projects = PROJECTS_BW2 if BW2 else PROJECTS_BW25 if update_config: filename = _projects_config_filename(getattr(bd, "__version__", "0")) projects.update(_fetch_projects_config(base_url, filename)) return projects
[docs] def install_project( project_key: str, project_name: Optional[str] = None, projects_config: Optional[dict] = None, url: Optional[str] = BASE_URL, overwrite_existing: Optional[bool] = False, __recursive: Union[bool, None] = False, ): """ Install an existing Brightway project archive. By default uses ``https://files.brightway.dev/`` as the file repository, but you can run your own. Parameters ---------- project_key: str A string uniquely identifying a project, e.g. ``ecoinvent-3.8-biosphere``. project_name: str, optional The name of the new project to create. If not provided will be taken from the archive file. projects_config: dict, optional A dictionary that maps ``project_key`` values to filenames at the repository url: str, optional The URL, with trailing slash ``/``, where the file can be found. overwrite_existing: bool, optional Allow overwriting an existing project __recursive : bool Internal flag used to determine if this function has errored out already Returns ------- str The name of the created project. """ if projects_config is None: projects_config = get_projects(base_url=url) try: filename = projects_config[project_key] except KeyError: raise KeyError(f"Project key {project_key} not in `projects_config`") fp = cache_dir / filename if not fp.exists(): download_with_progressbar( url=urljoin(url, filename), filename=filename, dirpath=cache_dir ) try: return restore_project_directory( fp=fp, project_name=project_name, overwrite_existing=overwrite_existing ) except EOFError: # Corrupt or incomplete zip archive fp.unlink() if __recursive: raise OSError( "Multiple errors trying to download and extract this file. Better luck tomorrow?" ) else: return install_project( project_key=project_key, project_name=project_name, projects_config=projects_config, url=url, overwrite_existing=overwrite_existing, __recursive=True, )