-
Notifications
You must be signed in to change notification settings - Fork 1
Issue 99 from url #101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: prototype-2
Are you sure you want to change the base?
Issue 99 from url #101
Changes from all commits
bd7f544
7d963ee
ad4ba96
71d2ca1
b7a346b
d1d5b67
6677139
219bb81
70639c2
8434294
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,7 @@ | |
| from typing import Annotated | ||
|
|
||
| import pydantic | ||
| import requests | ||
| from packaging.version import parse | ||
| from pydantic import Field, field_validator | ||
|
|
||
|
|
@@ -37,7 +38,8 @@ class DataAccessor(pydantic.BaseModel): | |
|
|
||
| This class provides a standardized interface to locate and load technology | ||
| datasets from predefined data sources. It can either load a specific version | ||
| or automatically determine and load the latest available version. | ||
| from the local storage, automatically determining and loading the latest available | ||
| version, or download data from a remote URL. | ||
|
|
||
| Attributes | ||
| ---------- | ||
|
|
@@ -172,6 +174,102 @@ def load(self) -> DataPackage: | |
| dp = DataPackage.from_json(self.data_source, self.version, data_path) | ||
| return dp | ||
|
|
||
| def download(self, base_url: str) -> DataPackage: | ||
| """ | ||
| Download and load technology data from a remote URL. | ||
|
|
||
| This method downloads technologies.json and sources.json files from the | ||
| specified base URL and loads them into a DataPackage instance. The sources.json | ||
| file is optional; if not found, sources will be extracted from technologies. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| base_url : str | ||
| Base URL where the JSON files are hosted. The method will attempt to download | ||
| technologies.json and sources.json from this location. The URL should point | ||
| to the directory containing these files. | ||
|
|
||
| Returns | ||
| ------- | ||
| DataPackage | ||
| An instance of DataPackage initialized with the downloaded data. | ||
|
|
||
| Raises | ||
| ------ | ||
| requests.HTTPError | ||
| If the HTTP request to download technologies.json fails. | ||
| requests.ConnectionError | ||
| If there is a network connectivity issue. | ||
|
|
||
| Examples | ||
| -------- | ||
| >>> accessor = DataAccessor(data_source="dea_energy_storage", version="v1.0") | ||
| >>> dp = accessor.download("https://example.com/data") | ||
|
Comment on lines
+206
to
+207
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unclear: Does this load the version from the specified URL, or does it use the information from
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we use a better URL for the example? e.g. just point at one of the data packages from this repository: https://raw.githubusercontent.com/open-energy-transition/technology-data/refs/heads/issue_99_from_url/src/technologydata/parsers/dea_energy_storage/v10/ |
||
|
|
||
| """ | ||
| # Ensure base_url ends with / | ||
| if not base_url.endswith("/"): | ||
| base_url += "/" | ||
|
Comment on lines
+211
to
+212
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or use |
||
|
|
||
| # Download technologies.json | ||
| technologies_url = f"{base_url}technologies.json" | ||
| sources_url = f"{base_url}sources.json" | ||
| if self.version: | ||
| technologies_path = pathlib.Path( | ||
| self.data_path, self.data_source, self.version, "technologies.json" | ||
| ) | ||
| sources_path = pathlib.Path( | ||
| self.data_path, self.data_source, self.version, "sources.json" | ||
| ) | ||
| else: | ||
| technologies_path = pathlib.Path( | ||
| self.data_path, self.data_source, "technologies.json" | ||
| ) | ||
| sources_path = pathlib.Path( | ||
| self.data_path, self.data_source, "sources.json" | ||
| ) | ||
|
|
||
| # Ensure parent directories exist | ||
| self.ensure_path_exists(technologies_path.parent) | ||
| self.ensure_path_exists(sources_path.parent) | ||
|
|
||
| try: | ||
| logger.info(f"Downloading technologies.json from {technologies_url}") | ||
| response = requests.get(technologies_url, timeout=30) | ||
| response.raise_for_status() | ||
|
|
||
| with open(technologies_path, "w", encoding="utf-8") as f: | ||
| f.write(response.text) | ||
| except requests.HTTPError as e: | ||
| logger.error(f"Failed to download technologies.json: {e}") | ||
| raise | ||
|
|
||
| try: | ||
| logger.info(f"Downloading sources.json from {sources_url}") | ||
| response = requests.get(sources_url, timeout=30) | ||
| response.raise_for_status() | ||
|
|
||
| with open(sources_path, "w", encoding="utf-8") as f: | ||
| f.write(response.text) | ||
| except requests.HTTPError as e: | ||
| logger.error(f"Failed to download sources.json: {e}") | ||
| raise | ||
|
|
||
| # Load using DataPackage.from_json | ||
| # Construct path to folder containing the downloaded files | ||
| if self.version: | ||
| path_to_folder = pathlib.Path( | ||
| self.data_path, self.data_source, self.version | ||
| ) | ||
| else: | ||
| path_to_folder = pathlib.Path(self.data_path, self.data_source) | ||
|
|
||
| data_package = DataPackage.from_json( | ||
| name=self.data_source, version=self.version, path_to_folder=path_to_folder | ||
| ) | ||
|
|
||
| return data_package | ||
|
|
||
| def parse( | ||
| self, | ||
| input_file_name: str, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,11 +5,15 @@ | |
| """Test the DataAccessor class.""" | ||
|
|
||
| import pathlib | ||
| from collections.abc import Callable | ||
| from typing import Any | ||
|
|
||
| import pytest | ||
|
|
||
| from technologydata.parsers.data_accessor import DataAccessor, DataSourceName | ||
|
|
||
| path_cwd = pathlib.Path.cwd() | ||
|
|
||
|
|
||
| class TestDataAccessor: | ||
| """Test suite for the DataAccessor class in the technologydata module.""" | ||
|
|
@@ -112,3 +116,41 @@ def test_ensure_path_exists_is_idempotent(self, tmp_path: pathlib.Path) -> None: | |
| DataAccessor.ensure_path_exists(target) | ||
| assert target.exists() | ||
| assert target.is_dir() | ||
|
|
||
| def test_download( | ||
| self, load_json: Callable[[pathlib.Path], Any], tmp_path: pathlib.Path | ||
| ) -> None: | ||
| """Test downloading a DataPackage from URL by mocking HTTP requests.""" | ||
| base_url = ( | ||
| "https://raw.githubusercontent.com/open-energy-transition/technology-data/" | ||
| ) | ||
| branch = "prototype-2/" | ||
| data_source = DataSourceName.MANUAL_INPUT_USA | ||
| version = "v0.13.4" | ||
| target_url = f"src/technologydata/parsers/{data_source}/{version}/" | ||
| url = base_url + branch + target_url | ||
| data_accessor = DataAccessor( | ||
| data_source="manual_input_usa", version="v0.13.4", data_path=tmp_path | ||
| ) | ||
| dp = data_accessor.download(url) | ||
| assert dp is not None | ||
| assert dp.sources is not None | ||
| assert dp.technologies is not None | ||
| assert dp.name == "manual_input_usa" | ||
| assert dp.version == "v0.13.4" | ||
| assert len(dp.sources) == 1 | ||
| assert len(dp.technologies) == 85 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the test to work reliably, better to point to a static URL, rather than the branch's HEAD URL. Else the |
||
| sources_reference_path = pathlib.Path(path_cwd, target_url, "sources.json") | ||
| technologies_reference_path = pathlib.Path( | ||
| path_cwd, target_url, "technologies.json" | ||
| ) | ||
| sources_reference = load_json(sources_reference_path) | ||
| technologies_reference = load_json(technologies_reference_path) | ||
| sources_download = load_json( | ||
| pathlib.Path(tmp_path, data_source, version, "sources.json") | ||
| ) | ||
| technologies_download = load_json( | ||
| pathlib.Path(tmp_path, data_source, version, "technologies.json") | ||
| ) | ||
| assert sources_reference == sources_download | ||
| assert technologies_reference == technologies_download | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The file is also stored somewhere, right? Add where / how it is stored.