-
Notifications
You must be signed in to change notification settings - Fork 6
Feature/ega2 catalog #106
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
Feature/ega2 catalog #106
Changes from all commits
01ce4df
1bb3468
73440d9
c5db515
8391b4b
dbed76a
33d699c
84d4e0c
5e46a24
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| [dataset] | ||
| identifier = "https://www.example.com/img-123" | ||
| title = "Example Imaging Dataset Title" | ||
| description = "This is imaging data description" | ||
| theme = ["http://publications.europa.eu/resource/authority/data-theme/HEAL"] | ||
| keyword = ["list", "of", "key", "words"] | ||
| access_rights = "http://publications.europa.eu/resource/authority/access-right/PUBLIC" | ||
| applicable_legislation = ["http://publications.europa.eu/resource/authority/access-right/NON_PUBLIC"] | ||
|
|
||
| [dataset.publisher] | ||
| name = ["Example publisher list"] | ||
| identifier = ["http://example.com"] | ||
| mbox = "mailto:publisher@example.com" | ||
| homepage = "http://www.example.com" | ||
|
|
||
| [dataset.contact_point] | ||
| formatted_name = "Example Data Management office" | ||
| email = "mailto:datamanager@example.com" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| import logging | ||
| from typing import Dict, List, Optional | ||
|
|
||
| import requests | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| def fetch_ega_dataset(dataset_id: str, api_url: str) -> Dict: | ||
| response = requests.get(f"{api_url}/datasets/{dataset_id}", timeout=30) | ||
| response.raise_for_status() | ||
|
|
||
| return response.json() | ||
|
|
||
|
|
||
| def fetch_ega_datasets(dataset_ids: List[str], api_url: str) -> List[Dict]: | ||
| datasets = [] | ||
| for dataset_id in dataset_ids: | ||
| try: | ||
| datasets.append(fetch_ega_dataset(dataset_id, api_url)) | ||
| except requests.RequestException as e: | ||
| logger.warning("Error fetching EGA dataset %s: %s", dataset_id, e) | ||
|
|
||
|
Comment on lines
+18
to
+22
Contributor
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. issue (bug_risk): Request failures are logged and discarded, so the CLI exits successfully with an incomplete or empty dataset list even though one or more explicitly requested accession IDs were not imported. Triggers: When any requested EGA accession returns an HTTP error, times out, or otherwise raises Suggested fix: Propagate an aggregate error or make the CLI exit nonzero when a requested accession cannot be fetched; if partial results are intentional, report the failed IDs prominently and distinguish the run from a successful import. |
||
| return datasets | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| import logging | ||
| from datetime import datetime | ||
| from typing import Dict, List, Optional | ||
|
|
||
| from pydantic import AnyHttpUrl | ||
| from rdflib import URIRef | ||
| from sempyro import LiteralField | ||
| from sempyro.dcat import AccessRights | ||
| from sempyro.hri_dcat import DatasetStatus, DatasetTheme, HRIAgent, HRIDataset, HRIVCard | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| # Source used: | ||
| # https://healthri.sharepoint.com/:x:/r/sites/hri-team022/_layouts/15/Doc.aspx?sourcedoc=%7BE3EC5B3F-6BB2-404B-9DA9-489A90BAC077%7D&file=EGA%20Health-RI%20Core%20mapping.xlsx&action=default&mobileredirect=true | ||
|
|
||
| def get_identifier(ega_dataset: Dict) -> str: | ||
| """Build the identifiers.org URI for an EGA dataset's accession_id.""" | ||
| return f"http://identifiers.org/ega.dataset:{ega_dataset['accession_id']}" | ||
|
Check warning on line 18 in src/img2catalog/mappings/ega.py
|
||
|
|
||
| def get_title(ega_dataset: Dict) -> str: | ||
| return ega_dataset["title"] | ||
|
|
||
| def get_description(ega_dataset: Dict) -> str: | ||
| return ega_dataset["description"] | ||
|
|
||
| def get_number_of_records(ega_dataset: Dict) -> Optional[int]: | ||
| return ega_dataset.get("num_samples") | ||
|
|
||
| def get_release_date(ega_dataset: Dict) -> Optional[datetime]: | ||
| released_date = ega_dataset.get("released_date") | ||
| if released_date is None: | ||
| return None | ||
|
|
||
| try: | ||
| return datetime.fromisoformat(released_date) | ||
| except ValueError: | ||
| logger.error("Could not parse EGA release date %r", released_date) | ||
| return released_date | ||
|
ishtiaqahmad marked this conversation as resolved.
|
||
|
|
||
| def get_keyword(ega_dataset: Dict) -> List[LiteralField]: | ||
| """Map EGA's free-text `technologies` field to DCAT-AP keywords.""" | ||
| return [LiteralField(value=technology) for technology in ega_dataset.get("technologies", [])] | ||
|
|
||
| def map_ega_to_healthri_dcat_dataset(ega_dataset: Dict, config: Dict) -> HRIDataset: | ||
| dataset_config = config["dataset"] | ||
| publisher_config = dataset_config["publisher"] | ||
| contact_point_config = dataset_config["contact_point"] | ||
|
|
||
| dataset_themes = [DatasetTheme(URIRef(theme)) for theme in dataset_config["theme"]] | ||
|
|
||
| dataset_keywords = get_keyword(ega_dataset) | ||
| dataset_keywords.extend(LiteralField(value=keyword) for keyword in dataset_config.get("keyword", [])) | ||
|
|
||
| dataset_applicable_legislation = [AnyHttpUrl(url) for url in dataset_config["applicable_legislation"]] | ||
|
|
||
| publisher_identifiers = [LiteralField(value=identifier) for identifier in publisher_config["identifier"]] | ||
|
|
||
| publisher = HRIAgent( | ||
| name=[LiteralField(value=name) for name in publisher_config["name"]], | ||
| identifier=publisher_identifiers, | ||
| mbox=publisher_config["mbox"], | ||
| homepage=publisher_config["homepage"], | ||
| ) | ||
|
|
||
| contact_point = HRIVCard( | ||
| hasEmail=contact_point_config["email"], | ||
| formatted_name=contact_point_config["formatted_name"], | ||
| ) | ||
|
|
||
| dataset = HRIDataset( | ||
| # Directly mapped from EGA | ||
| identifier=LiteralField(value=get_identifier(ega_dataset)), | ||
| title=[LiteralField(value=get_title(ega_dataset))], | ||
| description=[LiteralField(value=get_description(ega_dataset))], | ||
| release_date=get_release_date(ega_dataset), | ||
| number_of_records=get_number_of_records(ega_dataset), | ||
| keyword=dataset_keywords, | ||
| # Not present in EGA metadata, supplied from local node configuration (see | ||
| # docs/ega_mapping.md for the fields that are not (yet) mapped from EGA) | ||
| publisher=publisher, | ||
| contact_point=contact_point, | ||
| creator=[publisher], | ||
| theme=dataset_themes, | ||
| applicable_legislation=dataset_applicable_legislation, | ||
| access_rights=AccessRights(URIRef(dataset_config["access_rights"])), | ||
| ) | ||
|
|
||
| return dataset | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import pytest | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def default_ega_dataset(): | ||
| """A dataset description as returned by the EGA metadata API, taken from the ticket.""" | ||
| return { | ||
| "accession_id": "EGAD00001005083", | ||
| "title": "300-Obese cohort gut microbiome data", | ||
| "description": ( | ||
| "300-Obese cohort, Nijmegen, the Netherlands. Dataset contains gut microbiome data " | ||
| "generated by metagenomic sequencing." | ||
| ), | ||
| "dataset_types": ["Whole genome sequencing"], | ||
| "technologies": ["Illumina HiSeq 2000"], | ||
| "num_samples": 297, | ||
| "access_type": "controlled", | ||
| "is_in_beacon": False, | ||
| "is_released": True, | ||
| "released_date": "2001-01-01T00:00:00+01:00", | ||
| "is_deprecated": False, | ||
| "policy_accession_id": "EGAP00001001117", | ||
| } | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def missing_ega_dataset(): | ||
| """An EGA dataset description missing mandatory fields (title, description).""" | ||
| return { | ||
| "accession_id": "EGAD00001005083", | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| import pytest | ||
| import requests | ||
|
|
||
| from img2catalog.inputs.ega import fetch_ega_dataset, fetch_ega_datasets | ||
|
|
||
| API_URL = "https://metadata.ega-archive.org" | ||
|
|
||
|
|
||
| def test_fetch_ega_dataset_returns_json(requests_mock, default_ega_dataset): | ||
| # Arrange | ||
| requests_mock.get(f"{API_URL}/datasets/EGAD00001005083", json=default_ega_dataset) | ||
|
|
||
| # Act | ||
| result = fetch_ega_dataset("EGAD00001005083", API_URL) | ||
|
|
||
| # Assert | ||
| assert result == default_ega_dataset | ||
|
|
||
|
|
||
| def test_fetch_ega_dataset_raises_on_404(requests_mock): | ||
| # Arrange | ||
| requests_mock.get(f"{API_URL}/datasets/EGAD00000000000", status_code=404) | ||
|
|
||
| # Act & Assert | ||
| with pytest.raises(requests.HTTPError): | ||
| fetch_ega_dataset("EGAD00000000000", API_URL) | ||
|
|
||
|
|
||
| def test_fetch_ega_datasets_returns_all_on_success(requests_mock, default_ega_dataset): | ||
| # Arrange | ||
| other_dataset = {**default_ega_dataset, "dataset_id": "EGAD00001005084"} | ||
| requests_mock.get(f"{API_URL}/datasets/EGAD00001005083", json=default_ega_dataset) | ||
| requests_mock.get(f"{API_URL}/datasets/EGAD00001005084", json=other_dataset) | ||
|
|
||
| # Act | ||
| result = fetch_ega_datasets(["EGAD00001005083", "EGAD00001005084"], API_URL) | ||
|
|
||
| # Assert | ||
| assert result == [default_ega_dataset, other_dataset] | ||
|
|
||
|
|
||
| def test_fetch_ega_datasets_skips_failed_dataset(requests_mock, default_ega_dataset): | ||
| # Arrange | ||
| requests_mock.get(f"{API_URL}/datasets/EGAD00001005083", json=default_ega_dataset) | ||
| requests_mock.get(f"{API_URL}/datasets/EGAD00000000000", status_code=404) | ||
|
|
||
| # Act | ||
| result = fetch_ega_datasets(["EGAD00001005083", "EGAD00000000000"], API_URL) | ||
|
|
||
| # Assert | ||
| assert result == [default_ega_dataset] |
Uh oh!
There was an error while loading. Please reload this page.