From 4380d537614f034387adcb9a588b1b02ab99f502 Mon Sep 17 00:00:00 2001
From: Jessy Barrette <30420025+JessyBarrette@users.noreply.github.com>
Date: Mon, 15 Jul 2024 16:45:07 -0400
Subject: [PATCH 01/29] initial commit of erddap update xml
---
.gitignore | 3 +-
hakai_metadata_conversion/citation_cff.py | 39 +-
hakai_metadata_conversion/erddap.py | 168 +++-
poetry.lock | 194 ++++-
pyproject.toml | 2 +
test_erddap_update_dataset.xml | 813 ++++++++++++++++++
test_result.xml | 813 ++++++++++++++++++
tests/conftest.py | 4 +
tests/erddap_xmls/datasets.d/test_dataset.xml | 805 +++++++++++++++++
tests/erddap_xmls/test_datasets.xml | 807 +++++++++++++++++
tests/test_citation_cff.py | 20 +-
tests/test_erddap.py | 85 +-
12 files changed, 3705 insertions(+), 48 deletions(-)
create mode 100644 test_erddap_update_dataset.xml
create mode 100644 test_result.xml
create mode 100644 tests/erddap_xmls/datasets.d/test_dataset.xml
create mode 100644 tests/erddap_xmls/test_datasets.xml
diff --git a/.gitignore b/.gitignore
index 611d2d8..fe6e2d9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
**/__pycache__
.DS_Store
-tests/records/hakai-metadata-entry-form-files
\ No newline at end of file
+tests/records/hakai-metadata-entry-form-files
+tests/results
\ No newline at end of file
diff --git a/hakai_metadata_conversion/citation_cff.py b/hakai_metadata_conversion/citation_cff.py
index 7250a62..024a424 100644
--- a/hakai_metadata_conversion/citation_cff.py
+++ b/hakai_metadata_conversion/citation_cff.py
@@ -30,15 +30,19 @@ def get_cff_person(author):
"""Generate a CFF person"""
return drop_empty_values(
{
- "given-names": author["individual"]["name"].split(", ")[1] if ", " in author["individual"].get("name","") else "",
- "family-names": author["individual"].get("name","").split(", ")[0],
+ "given-names": (
+ author["individual"]["name"].split(", ")[1]
+ if ", " in author["individual"].get("name", "")
+ else ""
+ ),
+ "family-names": author["individual"].get("name", "").split(", ")[0],
"email": author["individual"].get("email"),
"orcid": author["individual"].get("orcid"),
- "affiliation": author.get("organization",{}).get("name"),
- "address": author.get("organization",{}).get("address"),
- "city": author.get("organization",{}).get("city"),
- "country": _get_country_code(author.get("organization",{}).get("country")),
- "website": _fix_url(author.get("organization",{}).get("url")),
+ "affiliation": author.get("organization", {}).get("name"),
+ "address": author.get("organization", {}).get("address"),
+ "city": author.get("organization", {}).get("city"),
+ "country": _get_country_code(author.get("organization", {}).get("country")),
+ "website": _fix_url(author.get("organization", {}).get("url")),
# "ror": author["organization"].get("ror"), # not in CFF schema
}
)
@@ -120,7 +124,7 @@ def citation_cff(
record["identification"]["identifier"].replace(
"https://doi.org/", ""
)
- if "doi.org" in record["identification"].get("identifier","")
+ if "doi.org" in record["identification"].get("identifier", "")
else None
),
},
@@ -134,7 +138,16 @@ def citation_cff(
# Generate ressources links
*[
{
- "description": ": ".join([item for item in [distribution.get('name',{}).get(language,""), distribution.get('description',{}).get(language)] if item]),
+ "description": ": ".join(
+ [
+ item
+ for item in [
+ distribution.get("name", {}).get(language, ""),
+ distribution.get("description", {}).get(language),
+ ]
+ if item
+ ]
+ ),
"type": "url",
"value": distribution["url"],
}
@@ -144,10 +157,12 @@ def citation_cff(
"keywords": [
keyword
for _, group in record["identification"]["keywords"].items()
- for keyword in group.get(language,[])
+ for keyword in group.get(language, [])
],
- "license": record["metadata"]["use_constraints"].get("licence",{}).get("code"),
- "license-url": record["metadata"]["use_constraints"].get("licence",{}).get("url"),
+ "license": record["metadata"]["use_constraints"].get("licence", {}).get("code"),
+ "license-url": record["metadata"]["use_constraints"]
+ .get("licence", {})
+ .get("url"),
"type": record_type,
"url": resource_url,
"version": record["identification"].get("edition"),
diff --git a/hakai_metadata_conversion/erddap.py b/hakai_metadata_conversion/erddap.py
index c3db707..5889962 100644
--- a/hakai_metadata_conversion/erddap.py
+++ b/hakai_metadata_conversion/erddap.py
@@ -1,6 +1,10 @@
+from glob import glob
+from pathlib import Path
+
+import yaml
from jinja2 import Template
from loguru import logger
-import yaml
+from lxml import etree
KEYWORDS_PREFIX_MAPPING = {
"default": {
@@ -44,7 +48,7 @@ def _get_contact(contact: dict, role: str) -> dict:
f"{role}_email": contact["organization"].get("email"),
f"{role}_type": "institution",
}
-
+
if not contact.get("organization"):
logger.warning(f"No organization found for {role} contact.")
return attrs
@@ -78,6 +82,7 @@ def _get_contributors(contacts: list, separator=";") -> dict:
),
}
+
def generate_history(record, language="en"):
"""Generate a history string from a metadata record."""
history = record["metadata"].get("history")
@@ -90,6 +95,7 @@ def generate_history(record, language="en"):
else:
logger.warning("Invalid history format.")
+
def global_attributes(
record, output="xml", language="en", base_url="https://catalogue.hakai.org"
) -> str:
@@ -108,30 +114,30 @@ def global_attributes(
logger.warning("Multiple publishers found, using the first one.")
comment = []
- if record["metadata"]["use_constraints"].get("limitations",{}).get(language):
+ if record["metadata"]["use_constraints"].get("limitations", {}).get(language):
comment += [
"##Limitations:\n"
+ record["metadata"]["use_constraints"]["limitations"][language]
]
- translation_comment = record["metadata"]["use_constraints"].get("limitations",{}).get("translations",{}).get(
- language
- )
+ translation_comment = (
+ record["metadata"]["use_constraints"]
+ .get("limitations", {})
+ .get("translations", {})
+ .get(language)
+ )
if not translation_comment:
pass
- elif isinstance(translation_comment,str):
+ elif isinstance(translation_comment, str):
comment += [
"##Translation:\n"
+ record["metadata"]["use_constraints"]["limitations"]["translations"].get(
language
)
]
- elif isinstance(translation_comment ,dict) and "message" in translation_comment:
- comment += [
- "##Translation:\n"
- + translation_comment["message"]
- ]
+ elif isinstance(translation_comment, dict) and "message" in translation_comment:
+ comment += ["##Translation:\n" + translation_comment["message"]]
else:
- logger.warning("Invalid translation comment format: {}",translation_comment)
+ logger.warning("Invalid translation comment format: {}", translation_comment)
metadata_link = (
base_url
@@ -143,7 +149,7 @@ def global_attributes(
global_attributes = {
"title": record["identification"]["title"][language],
"summary": record["identification"]["abstract"][language],
- "project": ",".join(record["identification"].get("project",[])),
+ "project": ",".join(record["identification"].get("project", [])),
"comment": "\n\n".join(comment),
"progress": record["identification"][
"progress_code"
@@ -152,7 +158,7 @@ def global_attributes(
[
KEYWORDS_PREFIX_MAPPING.get(group, {}).get("prefix", "") + keyword
for group, keywords in record["identification"]["keywords"].items()
- for keyword in keywords.get(language,[])
+ for keyword in keywords.get(language, [])
]
),
"keywords_vocabulary": ",".join(
@@ -172,18 +178,142 @@ def global_attributes(
"date_created": record["metadata"]["dates"].get("publication"),
"product_version": record["identification"].get("edition"),
"history": generate_history(record, language),
- "license": record["metadata"]["use_constraints"].get("licence",{}).get("code"),
+ "license": record["metadata"]["use_constraints"].get("licence", {}).get("code"),
**(_get_contact(creator[0], "creator") if creator else {}),
**(_get_contact(publisher[0], "publisher") if publisher else {}),
**_get_contributors(record["contact"]),
"doi": record["identification"].get("identifier"),
"metadata_link": metadata_link,
"infoUrl": metadata_link,
- "metadata_form": record["metadata"].get("maintenance_note","").replace(
- "Generated from ", ""
- ),
+ "metadata_form": record["metadata"]
+ .get("maintenance_note", "")
+ .replace("Generated from ", ""),
}
if not output:
return global_attributes
if output == "xml":
return dataset_xml_template.render(global_attributes=global_attributes)
+
+
+@logger.catch(reraise=True)
+def update_dataset_id(tree, dataset_id:str, global_attributes:dict):
+
+ # Retrive dataset
+ matching_dataset = tree.xpath(f"//dataset[@datasetID='{dataset_id}']")
+ if not matching_dataset:
+ return tree
+
+ # No duplicate dataset IDs allowed
+ if len(matching_dataset) > 1:
+ raise ValueError(f"Duplicate dataset ID {dataset_id} found in XML.")
+ dataset = matching_dataset[0]
+
+ for name, value in global_attributes.items():
+ # Check if the attribute already exists
+ matching_attribute = dataset.xpath(f".//addAttributes/att[@name='{name}']")
+ if matching_attribute:
+ logger.debug(f"Updating attribute {name} with value {value}")
+ matching_attribute[0].text = value
+ else:
+ # Create a new attribute
+ logger.debug(f"Adding new attribute {name} with value {value}")
+ new_attribute = etree.Element("att")
+ new_attribute.text = value
+ new_attribute.attrib["name"] = name
+ dataset.find(".//addAttributes").append(new_attribute)
+
+ return tree
+
+# Function to update XML
+@logger.catch(reraise=True)
+def _update_xml(xml_file, dataset_id, updates, encoding="utf-8") -> str:
+ # Parse the XML with comments
+ tree = etree.parse(xml_file)
+ tree = update_dataset_id(tree, dataset_id, updates)
+ # Write back to the same file (or use a different file name to save a new version.
+ return etree.tostring(tree, pretty_print=True).decode(encoding)
+
+def _get_dataset_id_from_record(record, erddap_url):
+ for ressource in record["metadata"]["resources"]:
+ if erddap_url in ressource["url"]:
+ return ressource["url"].split("/")[-1]
+
+class ERDDAP:
+ def __init__(self, path) -> None:
+ self.path = path
+ self.tree = None
+
+ self.read()
+
+ def read(self):
+ self.tree = etree.parse(self.path)
+
+ def tostring(self, encoding="utf-8") -> str:
+ return etree.tostring(self.tree, pretty_print=True).decode(encoding)
+
+ def save(self, output_file=None, encoding="utf-8"):
+ with open(output_file or self.path, "w") as f:
+ f.write(self.tostring(encoding))
+
+ def has_dataset_id(self, dataset_id) -> bool:
+ return bool(self.tree.xpath(f"//dataset[@datasetID='{dataset_id}']"))
+
+ def update(self, dataset_id:str, global_attributes:dict):
+
+ # Retrive dataset
+ matching_dataset = self.tree.xpath(f"//dataset[@datasetID='{dataset_id}']")
+ if not matching_dataset:
+ return
+
+ # No duplicate dataset IDs allowed
+ if len(matching_dataset) > 1:
+ raise ValueError(f"Duplicate dataset ID {dataset_id} found in XML.")
+ dataset = matching_dataset[0]
+
+ for name, value in global_attributes.items():
+ # Check if the attribute already exists
+ matching_attribute = dataset.xpath(f".//addAttributes/att[@name='{name}']")
+ if matching_attribute:
+ logger.debug(f"Updating attribute {name} with value {value}")
+ matching_attribute[0].text = value
+ else:
+ # Create a new attribute
+ logger.debug(f"Adding new attribute {name} with value {value}")
+ new_attribute = etree.Element("att")
+ new_attribute.text = value
+ new_attribute.attrib["name"] = name
+ dataset.find(".//addAttributes").append(new_attribute)
+
+ return
+
+
+
+def update_dataset_xml(
+ dataset_xml: str,
+ records: list,
+ output_dir: str = None,
+):
+ """Update an ERDDAP dataset.xml with new global attributes."""
+
+ # Find dataset xml
+ erddap_files = glob(dataset_xml, recursive=True)
+ if not erddap_files:
+ assert ValueError(f"No files found in {dataset_xml}")
+
+ datasets = {_get_dataset_id_from_record(record) for record in records}
+
+
+ updated = []
+ for file in erddap_files:
+ erddap = ERDDAP(file)
+ for dataset_id, attrs in datasets:
+ if erddap.has_dataset_id(dataset_id):
+ # Update the XML
+ erddap.update(dataset_id, attrs)
+ updated += [dataset_id]
+ file_output = Path(output_dir) / Path(file).name if output_dir else file
+ erddap.save(file_output or file)
+
+ if missing_datasets := datasets - set(updated):
+ logger.warning(f"Dataset ID {missing_datasets} not found in {dataset_xml}.")
+ return updated
diff --git a/poetry.lock b/poetry.lock
index b37c70f..c6da49b 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -231,6 +231,20 @@ files = [
{file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"},
]
+[[package]]
+name = "execnet"
+version = "2.1.1"
+description = "execnet: rapid multi-Python deployment"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"},
+ {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"},
+]
+
+[package.extras]
+testing = ["hatch", "pre-commit", "pytest", "tox"]
+
[[package]]
name = "idna"
version = "3.7"
@@ -323,6 +337,164 @@ win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
[package.extras]
dev = ["Sphinx (==7.2.5)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.4.1)", "mypy (==v1.5.1)", "pre-commit (==3.4.0)", "pytest (==6.1.2)", "pytest (==7.4.0)", "pytest-cov (==2.12.1)", "pytest-cov (==4.1.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.0.0)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.3.0)", "tox (==3.27.1)", "tox (==4.11.0)"]
+[[package]]
+name = "lxml"
+version = "5.2.2"
+description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "lxml-5.2.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:364d03207f3e603922d0d3932ef363d55bbf48e3647395765f9bfcbdf6d23632"},
+ {file = "lxml-5.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:50127c186f191b8917ea2fb8b206fbebe87fd414a6084d15568c27d0a21d60db"},
+ {file = "lxml-5.2.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74e4f025ef3db1c6da4460dd27c118d8cd136d0391da4e387a15e48e5c975147"},
+ {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:981a06a3076997adf7c743dcd0d7a0415582661e2517c7d961493572e909aa1d"},
+ {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aef5474d913d3b05e613906ba4090433c515e13ea49c837aca18bde190853dff"},
+ {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e275ea572389e41e8b039ac076a46cb87ee6b8542df3fff26f5baab43713bca"},
+ {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5b65529bb2f21ac7861a0e94fdbf5dc0daab41497d18223b46ee8515e5ad297"},
+ {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:bcc98f911f10278d1daf14b87d65325851a1d29153caaf146877ec37031d5f36"},
+ {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:b47633251727c8fe279f34025844b3b3a3e40cd1b198356d003aa146258d13a2"},
+ {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:fbc9d316552f9ef7bba39f4edfad4a734d3d6f93341232a9dddadec4f15d425f"},
+ {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:13e69be35391ce72712184f69000cda04fc89689429179bc4c0ae5f0b7a8c21b"},
+ {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3b6a30a9ab040b3f545b697cb3adbf3696c05a3a68aad172e3fd7ca73ab3c835"},
+ {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a233bb68625a85126ac9f1fc66d24337d6e8a0f9207b688eec2e7c880f012ec0"},
+ {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:dfa7c241073d8f2b8e8dbc7803c434f57dbb83ae2a3d7892dd068d99e96efe2c"},
+ {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1a7aca7964ac4bb07680d5c9d63b9d7028cace3e2d43175cb50bba8c5ad33316"},
+ {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ae4073a60ab98529ab8a72ebf429f2a8cc612619a8c04e08bed27450d52103c0"},
+ {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ffb2be176fed4457e445fe540617f0252a72a8bc56208fd65a690fdb1f57660b"},
+ {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e290d79a4107d7d794634ce3e985b9ae4f920380a813717adf61804904dc4393"},
+ {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:96e85aa09274955bb6bd483eaf5b12abadade01010478154b0ec70284c1b1526"},
+ {file = "lxml-5.2.2-cp310-cp310-win32.whl", hash = "sha256:f956196ef61369f1685d14dad80611488d8dc1ef00be57c0c5a03064005b0f30"},
+ {file = "lxml-5.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:875a3f90d7eb5c5d77e529080d95140eacb3c6d13ad5b616ee8095447b1d22e7"},
+ {file = "lxml-5.2.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:45f9494613160d0405682f9eee781c7e6d1bf45f819654eb249f8f46a2c22545"},
+ {file = "lxml-5.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0b3f2df149efb242cee2ffdeb6674b7f30d23c9a7af26595099afaf46ef4e88"},
+ {file = "lxml-5.2.2-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d28cb356f119a437cc58a13f8135ab8a4c8ece18159eb9194b0d269ec4e28083"},
+ {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:657a972f46bbefdbba2d4f14413c0d079f9ae243bd68193cb5061b9732fa54c1"},
+ {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b74b9ea10063efb77a965a8d5f4182806fbf59ed068b3c3fd6f30d2ac7bee734"},
+ {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07542787f86112d46d07d4f3c4e7c760282011b354d012dc4141cc12a68cef5f"},
+ {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:303f540ad2dddd35b92415b74b900c749ec2010e703ab3bfd6660979d01fd4ed"},
+ {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2eb2227ce1ff998faf0cd7fe85bbf086aa41dfc5af3b1d80867ecfe75fb68df3"},
+ {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:1d8a701774dfc42a2f0b8ccdfe7dbc140500d1049e0632a611985d943fcf12df"},
+ {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:56793b7a1a091a7c286b5f4aa1fe4ae5d1446fe742d00cdf2ffb1077865db10d"},
+ {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eb00b549b13bd6d884c863554566095bf6fa9c3cecb2e7b399c4bc7904cb33b5"},
+ {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a2569a1f15ae6c8c64108a2cd2b4a858fc1e13d25846be0666fc144715e32ab"},
+ {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:8cf85a6e40ff1f37fe0f25719aadf443686b1ac7652593dc53c7ef9b8492b115"},
+ {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:d237ba6664b8e60fd90b8549a149a74fcc675272e0e95539a00522e4ca688b04"},
+ {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0b3f5016e00ae7630a4b83d0868fca1e3d494c78a75b1c7252606a3a1c5fc2ad"},
+ {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23441e2b5339bc54dc949e9e675fa35efe858108404ef9aa92f0456929ef6fe8"},
+ {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2fb0ba3e8566548d6c8e7dd82a8229ff47bd8fb8c2da237607ac8e5a1b8312e5"},
+ {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:79d1fb9252e7e2cfe4de6e9a6610c7cbb99b9708e2c3e29057f487de5a9eaefa"},
+ {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6dcc3d17eac1df7859ae01202e9bb11ffa8c98949dcbeb1069c8b9a75917e01b"},
+ {file = "lxml-5.2.2-cp311-cp311-win32.whl", hash = "sha256:4c30a2f83677876465f44c018830f608fa3c6a8a466eb223535035fbc16f3438"},
+ {file = "lxml-5.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:49095a38eb333aaf44c06052fd2ec3b8f23e19747ca7ec6f6c954ffea6dbf7be"},
+ {file = "lxml-5.2.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7429e7faa1a60cad26ae4227f4dd0459efde239e494c7312624ce228e04f6391"},
+ {file = "lxml-5.2.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:50ccb5d355961c0f12f6cf24b7187dbabd5433f29e15147a67995474f27d1776"},
+ {file = "lxml-5.2.2-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc911208b18842a3a57266d8e51fc3cfaccee90a5351b92079beed912a7914c2"},
+ {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33ce9e786753743159799fdf8e92a5da351158c4bfb6f2db0bf31e7892a1feb5"},
+ {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ec87c44f619380878bd49ca109669c9f221d9ae6883a5bcb3616785fa8f94c97"},
+ {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08ea0f606808354eb8f2dfaac095963cb25d9d28e27edcc375d7b30ab01abbf6"},
+ {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75a9632f1d4f698b2e6e2e1ada40e71f369b15d69baddb8968dcc8e683839b18"},
+ {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:74da9f97daec6928567b48c90ea2c82a106b2d500f397eeb8941e47d30b1ca85"},
+ {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:0969e92af09c5687d769731e3f39ed62427cc72176cebb54b7a9d52cc4fa3b73"},
+ {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:9164361769b6ca7769079f4d426a41df6164879f7f3568be9086e15baca61466"},
+ {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d26a618ae1766279f2660aca0081b2220aca6bd1aa06b2cf73f07383faf48927"},
+ {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab67ed772c584b7ef2379797bf14b82df9aa5f7438c5b9a09624dd834c1c1aaf"},
+ {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:3d1e35572a56941b32c239774d7e9ad724074d37f90c7a7d499ab98761bd80cf"},
+ {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:8268cbcd48c5375f46e000adb1390572c98879eb4f77910c6053d25cc3ac2c67"},
+ {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e282aedd63c639c07c3857097fc0e236f984ceb4089a8b284da1c526491e3f3d"},
+ {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfdc2bfe69e9adf0df4915949c22a25b39d175d599bf98e7ddf620a13678585"},
+ {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4aefd911793b5d2d7a921233a54c90329bf3d4a6817dc465f12ffdfe4fc7b8fe"},
+ {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:8b8df03a9e995b6211dafa63b32f9d405881518ff1ddd775db4e7b98fb545e1c"},
+ {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f11ae142f3a322d44513de1018b50f474f8f736bc3cd91d969f464b5bfef8836"},
+ {file = "lxml-5.2.2-cp312-cp312-win32.whl", hash = "sha256:16a8326e51fcdffc886294c1e70b11ddccec836516a343f9ed0f82aac043c24a"},
+ {file = "lxml-5.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:bbc4b80af581e18568ff07f6395c02114d05f4865c2812a1f02f2eaecf0bfd48"},
+ {file = "lxml-5.2.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e3d9d13603410b72787579769469af730c38f2f25505573a5888a94b62b920f8"},
+ {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38b67afb0a06b8575948641c1d6d68e41b83a3abeae2ca9eed2ac59892b36706"},
+ {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c689d0d5381f56de7bd6966a4541bff6e08bf8d3871bbd89a0c6ab18aa699573"},
+ {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:cf2a978c795b54c539f47964ec05e35c05bd045db5ca1e8366988c7f2fe6b3ce"},
+ {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:739e36ef7412b2bd940f75b278749106e6d025e40027c0b94a17ef7968d55d56"},
+ {file = "lxml-5.2.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d8bbcd21769594dbba9c37d3c819e2d5847656ca99c747ddb31ac1701d0c0ed9"},
+ {file = "lxml-5.2.2-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:2304d3c93f2258ccf2cf7a6ba8c761d76ef84948d87bf9664e14d203da2cd264"},
+ {file = "lxml-5.2.2-cp36-cp36m-win32.whl", hash = "sha256:02437fb7308386867c8b7b0e5bc4cd4b04548b1c5d089ffb8e7b31009b961dc3"},
+ {file = "lxml-5.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:edcfa83e03370032a489430215c1e7783128808fd3e2e0a3225deee278585196"},
+ {file = "lxml-5.2.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:28bf95177400066596cdbcfc933312493799382879da504633d16cf60bba735b"},
+ {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a745cc98d504d5bd2c19b10c79c61c7c3df9222629f1b6210c0368177589fb8"},
+ {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b590b39ef90c6b22ec0be925b211298e810b4856909c8ca60d27ffbca6c12e6"},
+ {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b336b0416828022bfd5a2e3083e7f5ba54b96242159f83c7e3eebaec752f1716"},
+ {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:c2faf60c583af0d135e853c86ac2735ce178f0e338a3c7f9ae8f622fd2eb788c"},
+ {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:4bc6cb140a7a0ad1f7bc37e018d0ed690b7b6520ade518285dc3171f7a117905"},
+ {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7ff762670cada8e05b32bf1e4dc50b140790909caa8303cfddc4d702b71ea184"},
+ {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:57f0a0bbc9868e10ebe874e9f129d2917750adf008fe7b9c1598c0fbbfdde6a6"},
+ {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:a6d2092797b388342c1bc932077ad232f914351932353e2e8706851c870bca1f"},
+ {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:60499fe961b21264e17a471ec296dcbf4365fbea611bf9e303ab69db7159ce61"},
+ {file = "lxml-5.2.2-cp37-cp37m-win32.whl", hash = "sha256:d9b342c76003c6b9336a80efcc766748a333573abf9350f4094ee46b006ec18f"},
+ {file = "lxml-5.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b16db2770517b8799c79aa80f4053cd6f8b716f21f8aca962725a9565ce3ee40"},
+ {file = "lxml-5.2.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7ed07b3062b055d7a7f9d6557a251cc655eed0b3152b76de619516621c56f5d3"},
+ {file = "lxml-5.2.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f60fdd125d85bf9c279ffb8e94c78c51b3b6a37711464e1f5f31078b45002421"},
+ {file = "lxml-5.2.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a7e24cb69ee5f32e003f50e016d5fde438010c1022c96738b04fc2423e61706"},
+ {file = "lxml-5.2.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23cfafd56887eaed93d07bc4547abd5e09d837a002b791e9767765492a75883f"},
+ {file = "lxml-5.2.2-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:19b4e485cd07b7d83e3fe3b72132e7df70bfac22b14fe4bf7a23822c3a35bff5"},
+ {file = "lxml-5.2.2-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:7ce7ad8abebe737ad6143d9d3bf94b88b93365ea30a5b81f6877ec9c0dee0a48"},
+ {file = "lxml-5.2.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e49b052b768bb74f58c7dda4e0bdf7b79d43a9204ca584ffe1fb48a6f3c84c66"},
+ {file = "lxml-5.2.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d14a0d029a4e176795cef99c056d58067c06195e0c7e2dbb293bf95c08f772a3"},
+ {file = "lxml-5.2.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:be49ad33819d7dcc28a309b86d4ed98e1a65f3075c6acd3cd4fe32103235222b"},
+ {file = "lxml-5.2.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a6d17e0370d2516d5bb9062c7b4cb731cff921fc875644c3d751ad857ba9c5b1"},
+ {file = "lxml-5.2.2-cp38-cp38-win32.whl", hash = "sha256:5b8c041b6265e08eac8a724b74b655404070b636a8dd6d7a13c3adc07882ef30"},
+ {file = "lxml-5.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:f61efaf4bed1cc0860e567d2ecb2363974d414f7f1f124b1df368bbf183453a6"},
+ {file = "lxml-5.2.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fb91819461b1b56d06fa4bcf86617fac795f6a99d12239fb0c68dbeba41a0a30"},
+ {file = "lxml-5.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d4ed0c7cbecde7194cd3228c044e86bf73e30a23505af852857c09c24e77ec5d"},
+ {file = "lxml-5.2.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54401c77a63cc7d6dc4b4e173bb484f28a5607f3df71484709fe037c92d4f0ed"},
+ {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:625e3ef310e7fa3a761d48ca7ea1f9d8718a32b1542e727d584d82f4453d5eeb"},
+ {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:519895c99c815a1a24a926d5b60627ce5ea48e9f639a5cd328bda0515ea0f10c"},
+ {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c7079d5eb1c1315a858bbf180000757db8ad904a89476653232db835c3114001"},
+ {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:343ab62e9ca78094f2306aefed67dcfad61c4683f87eee48ff2fd74902447726"},
+ {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:cd9e78285da6c9ba2d5c769628f43ef66d96ac3085e59b10ad4f3707980710d3"},
+ {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_ppc64le.whl", hash = "sha256:546cf886f6242dff9ec206331209db9c8e1643ae642dea5fdbecae2453cb50fd"},
+ {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_s390x.whl", hash = "sha256:02f6a8eb6512fdc2fd4ca10a49c341c4e109aa6e9448cc4859af5b949622715a"},
+ {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:339ee4a4704bc724757cd5dd9dc8cf4d00980f5d3e6e06d5847c1b594ace68ab"},
+ {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0a028b61a2e357ace98b1615fc03f76eb517cc028993964fe08ad514b1e8892d"},
+ {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f90e552ecbad426eab352e7b2933091f2be77115bb16f09f78404861c8322981"},
+ {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d83e2d94b69bf31ead2fa45f0acdef0757fa0458a129734f59f67f3d2eb7ef32"},
+ {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a02d3c48f9bb1e10c7788d92c0c7db6f2002d024ab6e74d6f45ae33e3d0288a3"},
+ {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6d68ce8e7b2075390e8ac1e1d3a99e8b6372c694bbe612632606d1d546794207"},
+ {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:453d037e09a5176d92ec0fd282e934ed26d806331a8b70ab431a81e2fbabf56d"},
+ {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:3b019d4ee84b683342af793b56bb35034bd749e4cbdd3d33f7d1107790f8c472"},
+ {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb3942960f0beb9f46e2a71a3aca220d1ca32feb5a398656be934320804c0df9"},
+ {file = "lxml-5.2.2-cp39-cp39-win32.whl", hash = "sha256:ac6540c9fff6e3813d29d0403ee7a81897f1d8ecc09a8ff84d2eea70ede1cdbf"},
+ {file = "lxml-5.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:610b5c77428a50269f38a534057444c249976433f40f53e3b47e68349cca1425"},
+ {file = "lxml-5.2.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b537bd04d7ccd7c6350cdaaaad911f6312cbd61e6e6045542f781c7f8b2e99d2"},
+ {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4820c02195d6dfb7b8508ff276752f6b2ff8b64ae5d13ebe02e7667e035000b9"},
+ {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a09f6184f17a80897172863a655467da2b11151ec98ba8d7af89f17bf63dae"},
+ {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:76acba4c66c47d27c8365e7c10b3d8016a7da83d3191d053a58382311a8bf4e1"},
+ {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b128092c927eaf485928cec0c28f6b8bead277e28acf56800e972aa2c2abd7a2"},
+ {file = "lxml-5.2.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ae791f6bd43305aade8c0e22f816b34f3b72b6c820477aab4d18473a37e8090b"},
+ {file = "lxml-5.2.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a2f6a1bc2460e643785a2cde17293bd7a8f990884b822f7bca47bee0a82fc66b"},
+ {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e8d351ff44c1638cb6e980623d517abd9f580d2e53bfcd18d8941c052a5a009"},
+ {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bec4bd9133420c5c52d562469c754f27c5c9e36ee06abc169612c959bd7dbb07"},
+ {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:55ce6b6d803890bd3cc89975fca9de1dff39729b43b73cb15ddd933b8bc20484"},
+ {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8ab6a358d1286498d80fe67bd3d69fcbc7d1359b45b41e74c4a26964ca99c3f8"},
+ {file = "lxml-5.2.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:06668e39e1f3c065349c51ac27ae430719d7806c026fec462e5693b08b95696b"},
+ {file = "lxml-5.2.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9cd5323344d8ebb9fb5e96da5de5ad4ebab993bbf51674259dbe9d7a18049525"},
+ {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89feb82ca055af0fe797a2323ec9043b26bc371365847dbe83c7fd2e2f181c34"},
+ {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e481bba1e11ba585fb06db666bfc23dbe181dbafc7b25776156120bf12e0d5a6"},
+ {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9d6c6ea6a11ca0ff9cd0390b885984ed31157c168565702959c25e2191674a14"},
+ {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3d98de734abee23e61f6b8c2e08a88453ada7d6486dc7cdc82922a03968928db"},
+ {file = "lxml-5.2.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:69ab77a1373f1e7563e0fb5a29a8440367dec051da6c7405333699d07444f511"},
+ {file = "lxml-5.2.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:34e17913c431f5ae01d8658dbf792fdc457073dcdfbb31dc0cc6ab256e664a8d"},
+ {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05f8757b03208c3f50097761be2dea0aba02e94f0dc7023ed73a7bb14ff11eb0"},
+ {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a520b4f9974b0a0a6ed73c2154de57cdfd0c8800f4f15ab2b73238ffed0b36e"},
+ {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5e097646944b66207023bc3c634827de858aebc226d5d4d6d16f0b77566ea182"},
+ {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b5e4ef22ff25bfd4ede5f8fb30f7b24446345f3e79d9b7455aef2836437bc38a"},
+ {file = "lxml-5.2.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ff69a9a0b4b17d78170c73abe2ab12084bdf1691550c5629ad1fe7849433f324"},
+ {file = "lxml-5.2.2.tar.gz", hash = "sha256:bb2dc4898180bea79863d5487e5f9c7c34297414bad54bcd0f0852aee9cfdb87"},
+]
+
+[package.extras]
+cssselect = ["cssselect (>=0.7)"]
+html-clean = ["lxml-html-clean"]
+html5 = ["html5lib"]
+htmlsoup = ["BeautifulSoup4"]
+source = ["Cython (>=3.0.10)"]
+
[[package]]
name = "markupsafe"
version = "2.1.5"
@@ -544,6 +716,26 @@ pluggy = ">=1.5,<2.0"
[package.extras]
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
+[[package]]
+name = "pytest-xdist"
+version = "3.6.1"
+description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"},
+ {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"},
+]
+
+[package.dependencies]
+execnet = ">=2.1"
+pytest = ">=7.0.0"
+
+[package.extras]
+psutil = ["psutil (>=3.0)"]
+setproctitle = ["setproctitle"]
+testing = ["filelock"]
+
[[package]]
name = "python-dateutil"
version = "2.9.0.post0"
@@ -802,4 +994,4 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"]
[metadata]
lock-version = "2.0"
python-versions = "^3.11"
-content-hash = "c4ed1c99249d993aa8f1454f0267f197ec2e39104b5377329f234c10d1f7fc27"
+content-hash = "0f445b1f3c5215b779a19552a347ccb5d86f30d6fbac122dbde0629f3df5a785"
diff --git a/pyproject.toml b/pyproject.toml
index 5f3d320..e4a515f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -14,6 +14,7 @@ requests = "^2.32.3"
jinja2 = "^3.1.4"
cffconvert = "^2.0.0"
pycountry = "^24.6.1"
+lxml = "^5.2.2"
[tool.poetry.group.dev.dependencies]
@@ -21,6 +22,7 @@ pytest = "^8.2.2"
isort = "^5.13.2"
ruff = "^0.5.0"
black = "^24.4.2"
+pytest-xdist = "^3.6.1"
[tool.poetry.group.docs.dependencies]
diff --git a/test_erddap_update_dataset.xml b/test_erddap_update_dataset.xml
new file mode 100644
index 0000000..c06e3a2
--- /dev/null
+++ b/test_erddap_update_dataset.xml
@@ -0,0 +1,813 @@
+
+
+ hakai_erddap_sourceUrl
+ org.postgresql.Driver
+ erddap
+ "TestDataset1"
+
+ false
+ https://catalogue.hakai.orgca-cioos_ba41d935-f293-447f-be3d-7098e569b431
+ CF Standard Name Table v79
+ station
+ TimeSeries
+ station
+ coastal structure
+ http://vocab.nerc.ac.uk/collection/L06/current/
+ Oceanography,Nearshore,Watersheds
+ CC-BY-4.0
+ Dakunalytics, LLC - Burke-o-Lator pCO2/TCO2 analyzer - ,Seabird Electronics - SBE 45 -
+ ba41d935-f293-447f-be3d-7098e569b431
+ ca.cioos
+ fluorescence,turbidity,PAR,photosynthetically active radiation,Calvert Island,Quadra Island,transmissometer,Johnstone Strait,Queen Charlotte Sound,Strait of Georgia,salinity,coastal zone,CIOOS:oxygen,CIOOS:subSurfaceSalinity,CIOOS:subSurfaceTemperature
+ land/onshore structure
+ http://vocab.nerc.ac.uk/collection/L06/current/
+ Temperature, conductivity, dissolved oxygen, fluorescence, photosynthetic active radiation, and turbidity data collected from 2012 to present by the Hakai Institute in waters surrounding Calvert Island, Johnstone Strait, and Quadra Island areas. This dataset presents data collected by oceanographic profiler instruments (RBR XR-620, RBR Concerto, RBR Maestro, and Seabird SBE 19plus v2) which have been automatically processed by following respective manufacturer's guidelines (see Hakai Water Properties Profile Processing and QA/QC Procedure Manual).
+
+The provisional processed data are then quality controlled by applying a series of tests that are following the QARTOD standards and more tests specific to the Hakai Institute data (see Hakai Water Properties Profile Processing and QA/QC Procedure Manual).
+
+The research dataset provides a subset of the provisional dataset which has been manually reviewed and judged good for science quality level.
+
+Data were collected by the Hakai Institute Oceanography Program, the Nearshore Program, and the Juvenile Salmon Program.
+ Hakai Water Properties Vertical Profile Data Measured by Oceanographic Profilers, Research
+ ##Limitations:
+Instruments sensors are generally calibrated once a year by the manufacturer, no comparison and post-calibration correction based on data sampled by another method is applied to this dataset.onGoingCIOOS: CIOOS Essential Ocean Variables Vocabulary2024-03-18T19:28:21.285Z2024-03-191.0.0These data have been solely funded by the Tula Foundation. Data collection started near Calvert Island in 2012, near Quadra Island in 2014, and in Johnstone Strait from 2015 to 2019.Hakai Institutedata@hakai.orginstitutionHakai Institute1713 Hyacinthe Bay RoadHeriot BayCanadahakai.orgHakai Institutedata@hakai.orginstitutionHakai Institute1713 Hyacinthe Bay RoadHeriot BayCanadahakai.orgHakai Institute;lastname, firstname;asd, as;Tula Foundationdistributor,owner,funder,publisher;custodian,pointOfContact,processor;processor;funderhttps://doi.org/10.21966/kace-2d24https://catalogue.hakai.orgca-cioos_ba41d935-f293-447f-be3d-7098e569b431https://hakaiinstitute.github.io/hakai-metadata-entry-form#/en/hakai/tV5qE0aUgaOjSVmgPgiZ6MyHuSy1/-MX35qgX-BrwJDdeJKe1
+
+
+ measurementTime
+ time
+ String
+
+ time
+ seconds since 1970-01-01T00:00:00Z
+ time: point (interval: 5.0 minutes comment: time correspond to the end of the interval)
+
+
+
+
+
+ =48.836633
+ latitude
+ double
+
+ Fixed latitude of the site
+ Latitude
+ latitude
+ degrees_north
+
+
+
+
+
+ =-125.1362667
+ longitude
+ double
+
+ Fixed longitude of the site
+ Longitude
+ longitude
+ degrees_east
+
+
+
+
+
+ =20
+ depth
+ double
+
+ Approximative depth
+ Depth
+ depth
+ m
+
+
+
+
+ ="Nova Harvest Fisheries Burke-o-Lator"
+ station
+ String
+
+ Location
+ timeseries_id
+
+
+
+
+
+ BamfieldBoL:pCO2_uatm_Med
+ pCO2_uatm_Med
+ double
+
+ partial_pressure_of_carbon_dioxide_in_sea_water
+ BoL
+ Burke-o-Lator
+ pCO2_Med
+ μatm
+ time: median (interval: 5.0 minutes)
+ pCO2_uatm_UQL pCO2_uatm_flags
+
+
+
+
+
+ BamfieldBoL:pCO2_uatm_Avg
+ pCO2_uatm_Avg
+ double
+
+ partial_pressure_of_carbon_dioxide_in_sea_water
+ BoL
+ Burke-o-Lator
+ pCO2_Avg
+ μatm
+ time: mean (interval: 5.0 minutes)
+ pCO2_uatm_UQL pCO2_uatm_flags
+
+
+
+
+
+ BamfieldBoL:pCO2_uatm_Min
+ pCO2_uatm_Min
+ double
+
+ partial_pressure_of_carbon_dioxide_in_sea_water
+ BoL
+ Burke-o-Lator
+ pCO2_Min
+ μatm
+ time: mininum (interval: 5.0 minutes)
+ pCO2_uatm_UQL pCO2_uatm_flags
+
+
+
+
+
+ BamfieldBoL:pCO2_uatm_Max
+ pCO2_uatm_Max
+ double
+
+ partial_pressure_of_carbon_dioxide_in_sea_water
+ BoL
+ Burke-o-Lator
+ pCO2_Max
+ μatm
+ time: maximum (interval: 5.0 minutes)
+ pCO2_uatm_UQL pCO2_uatm_flags
+
+
+
+
+
+ BamfieldBoL:pCO2_uatm_Std
+ pCO2_uatm_Std
+ double
+
+ partial_pressure_of_carbon_dioxide_in_sea_water
+ BoL
+ Burke-o-Lator
+ pCO2_Std
+ μatm
+ time: standard_deviation (interval: 5.0 minutes)
+ pCO2_uatm_UQL pCO2_uatm_flags
+
+
+
+
+
+ BamfieldBoL:pCO2_uatm_UQL
+ pCO2_uatm_UQL
+ byte
+
+ pCO2 UNESCO Q level
+ PASS NOT_EVALUATED SUSPECT FAIL MISSING
+ 1 2 3 4 9
+ 1
+ aggregate_quality_flag
+ 2
+
+
+
+
+
+ BamfieldBoL:pCO2_uatm_QC
+ pCO2_uatm_flags
+ String
+
+ pCO2 Q flags
+ Quality flags
+
+
+
+
+
+ BamfieldBoL:calcTCO2_Med
+ calcTCO2_Med
+ double
+
+ BoL
+ Burke-o-Lator
+ TCO2_Med
+ μmol/kg
+ time: median (interval: 5.0 minutes)
+ calcTCO2_UQL calcTCO2_flags
+
+
+
+
+
+ BamfieldBoL:calcTCO2_Avg
+ calcTCO2_Avg
+ double
+
+ BoL
+ Burke-o-Lator
+ TCO2_Avg
+ μmol/kg
+ time: mean (interval: 5.0 minutes)
+ calcTCO2_UQL calcTCO2_flags
+
+
+
+
+
+ BamfieldBoL:calcTCO2_Min
+ calcTCO2_Min
+ double
+
+ BoL
+ Burke-o-Lator
+ TCO2_Min
+ μmol/kg
+ time: mininum (interval: 5.0 minutes)
+ calcTCO2_UQL calcTCO2_flags
+
+
+
+
+
+ BamfieldBoL:calcTCO2_Max
+ calcTCO2_Max
+ double
+
+ BoL
+ Burke-o-Lator
+ TCO2_Max
+ μmol/kg
+ time: maximum (interval: 5.0 minutes)
+ calcTCO2_UQL calcTCO2_flags
+
+
+
+
+
+ BamfieldBoL:calcTCO2_Std
+ calcTCO2_Std
+ double
+
+ BoL
+ Burke-o-Lator
+ TCO2_Std
+ μmol/kg
+ time: standard_deviation (interval: 5.0 minutes)
+ calcTCO2_UQL calcTCO2_flags
+
+
+
+
+
+ BamfieldBoL:calcTCO2_UQL
+ calcTCO2_UQL
+ byte
+
+ TCO2 UNESCO Q level
+ PASS NOT_EVALUATED SUSPECT FAIL MISSING
+ 1 2 3 4 9
+ 1
+ aggregate_quality_flag
+ 2
+
+
+
+
+
+ BamfieldBoL:calcTCO2_QC
+ calcTCO2_flags
+ String
+
+ TCO2 Q flags
+ Quality flags
+
+
+
+
+
+ BamfieldBoL:TSG_T_Med
+ TSG_T_Med
+ double
+
+ sea_water_temperature
+ BoL
+ Burke-o-Lator
+ Water temperature_Med
+ degree_Celsius
+ time: median (interval: 5.0 minutes)
+ TSG_T_UQL TSG_T_flags
+
+
+
+
+
+ BamfieldBoL:TSG_T_Avg
+ TSG_T_Avg
+ double
+
+ sea_water_temperature
+ BoL
+ Burke-o-Lator
+ Water temperature_Avg
+ degree_Celsius
+ time: mean (interval: 5.0 minutes)
+ TSG_T_UQL TSG_T_flags
+
+
+
+
+
+ BamfieldBoL:TSG_T_Min
+ TSG_T_Min
+ double
+
+ sea_water_temperature
+ BoL
+ Burke-o-Lator
+ Water temperature_Min
+ degree_Celsius
+ time: mininum (interval: 5.0 minutes)
+ TSG_T_UQL TSG_T_flags
+
+
+
+
+
+ BamfieldBoL:TSG_T_Max
+ TSG_T_Max
+ double
+
+ sea_water_temperature
+ BoL
+ Burke-o-Lator
+ Water temperature_Max
+ degree_Celsius
+ time: maximum (interval: 5.0 minutes)
+ TSG_T_UQL TSG_T_flags
+
+
+
+
+
+ BamfieldBoL:TSG_T_Std
+ TSG_T_Std
+ double
+
+ sea_water_temperature
+ BoL
+ Burke-o-Lator
+ Water temperature_Std
+ degree_Celsius
+ time: standard_deviation (interval: 5.0 minutes)
+ TSG_T_UQL TSG_T_flags
+
+
+
+
+
+ BamfieldBoL:TSG_T_UQL
+ TSG_T_UQL
+ byte
+
+ Water temperature UNESCO Q level
+ PASS NOT_EVALUATED SUSPECT FAIL MISSING
+ 1 2 3 4 9
+ 1
+ aggregate_quality_flag
+ 2
+
+
+
+
+
+ BamfieldBoL:TSG_T_QC
+ TSG_T_flags
+ String
+
+ Water temperature Q flags
+ Quality flags
+
+
+
+
+
+ BamfieldBoL:TSG_S_Med
+ TSG_S_Med
+ double
+
+ sea_water_practical_salinity
+ BoL
+ Burke-o-Lator
+ Salinity_Med
+ time: median (interval: 5.0 minutes)
+ TSG_S_UQL TSG_S_flags
+
+
+
+
+
+ BamfieldBoL:TSG_S_Avg
+ TSG_S_Avg
+ double
+
+ sea_water_practical_salinity
+ BoL
+ Burke-o-Lator
+ Salinity_Avg
+ time: mean (interval: 5.0 minutes)
+ TSG_S_UQL TSG_S_flags
+
+
+
+
+
+ BamfieldBoL:TSG_S_Min
+ TSG_S_Min
+ double
+
+ sea_water_practical_salinity
+ BoL
+ Burke-o-Lator
+ Salinity_Min
+ time: mininum (interval: 5.0 minutes)
+ TSG_S_UQL TSG_S_flags
+
+
+
+
+
+ BamfieldBoL:TSG_S_Max
+ TSG_S_Max
+ double
+
+ sea_water_practical_salinity
+ BoL
+ Burke-o-Lator
+ Salinity_Max
+ time: maximum (interval: 5.0 minutes)
+ TSG_S_UQL TSG_S_flags
+
+
+
+
+
+ BamfieldBoL:TSG_S_Std
+ TSG_S_Std
+ double
+
+ sea_water_practical_salinity
+ BoL
+ Burke-o-Lator
+ Salinity_Std
+ time: standard_deviation (interval: 5.0 minutes)
+ TSG_S_UQL TSG_S_flags
+
+
+
+
+
+ BamfieldBoL:TSG_S_UQL
+ TSG_S_UQL
+ byte
+
+ Salinity UNESCO Q level
+ PASS NOT_EVALUATED SUSPECT FAIL MISSING
+ 1 2 3 4 9
+ 1
+ aggregate_quality_flag
+ 2
+
+
+
+
+
+ BamfieldBoL:TSG_S_QC
+ TSG_S_flags
+ String
+
+ Salinity Q flags
+ Quality flags
+
+
+
+
+
+ BamfieldBoL:AlkS_Med
+ AlkS_Med
+ double
+
+ BoL
+ Burke-o-Lator
+ TA_Med
+ μmol/kg
+ time: median (interval: 5.0 minutes)
+ AlkS_UQL AlkS_flags
+
+
+
+
+
+ BamfieldBoL:AlkS_Avg
+ AlkS_Avg
+ double
+
+ BoL
+ Burke-o-Lator
+ TA_Avg
+ μmol/kg
+ time: mean (interval: 5.0 minutes)
+ AlkS_UQL AlkS_flags
+
+
+
+
+
+ BamfieldBoL:AlkS_Min
+ AlkS_Min
+ double
+
+ BoL
+ Burke-o-Lator
+ TA_Min
+ μmol/kg
+ time: mininum (interval: 5.0 minutes)
+ AlkS_UQL AlkS_flags
+
+
+
+
+
+ BamfieldBoL:AlkS_Max
+ AlkS_Max
+ double
+
+ BoL
+ Burke-o-Lator
+ TA_Max
+ μmol/kg
+ time: maximum (interval: 5.0 minutes)
+ AlkS_UQL AlkS_flags
+
+
+
+
+
+ BamfieldBoL:AlkS_Std
+ AlkS_Std
+ double
+
+ BoL
+ Burke-o-Lator
+ TA_Std
+ μmol/kg
+ time: standard_deviation (interval: 5.0 minutes)
+ AlkS_UQL AlkS_flags
+
+
+
+
+
+ BamfieldBoL:AlkS_UQL
+ AlkS_UQL
+ byte
+
+ TA UNESCO Q level
+ PASS NOT_EVALUATED SUSPECT FAIL MISSING
+ 1 2 3 4 9
+ 1
+ aggregate_quality_flag
+ 2
+
+
+
+
+
+ BamfieldBoL:AlkS_QC
+ AlkS_flags
+ String
+
+ TA Q flags
+ Quality flags
+
+
+
+
+
+ BamfieldBoL:calcOmega_Med
+ calcOmega_Med
+ double
+
+ BoL
+ Burke-o-Lator
+ Ω arag_Med
+ time: median (interval: 5.0 minutes)
+ calcOmega_UQL calcOmega_flags
+
+
+
+
+
+ BamfieldBoL:calcOmega_Avg
+ calcOmega_Avg
+ double
+
+ BoL
+ Burke-o-Lator
+ Ω arag_Avg
+ time: mean (interval: 5.0 minutes)
+ calcOmega_UQL calcOmega_flags
+
+
+
+
+
+ BamfieldBoL:calcOmega_Min
+ calcOmega_Min
+ double
+
+ BoL
+ Burke-o-Lator
+ Ω arag_Min
+ time: mininum (interval: 5.0 minutes)
+ calcOmega_UQL calcOmega_flags
+
+
+
+
+
+ BamfieldBoL:calcOmega_Max
+ calcOmega_Max
+ double
+
+ BoL
+ Burke-o-Lator
+ Ω arag_Max
+ time: maximum (interval: 5.0 minutes)
+ calcOmega_UQL calcOmega_flags
+
+
+
+
+
+ BamfieldBoL:calcOmega_Std
+ calcOmega_Std
+ double
+
+ BoL
+ Burke-o-Lator
+ Ω arag_Std
+ time: standard_deviation (interval: 5.0 minutes)
+ calcOmega_UQL calcOmega_flags
+
+
+
+
+
+ BamfieldBoL:calcOmega_UQL
+ calcOmega_UQL
+ byte
+
+ Ω arag UNESCO Q level
+ PASS NOT_EVALUATED SUSPECT FAIL MISSING
+ 1 2 3 4 9
+ 1
+ aggregate_quality_flag
+ 2
+
+
+
+
+
+ BamfieldBoL:calcOmega_QC
+ calcOmega_flags
+ String
+
+ Ω arag Q flags
+ Quality flags
+
+
+
+
+
+ BamfieldBoL:calcpH_Med
+ calcpH_Med
+ double
+
+ sea_water_ph_reported_on_total_scale
+ BoL
+ Burke-o-Lator
+ pH_T_Med
+ total scale
+ time: median (interval: 5.0 minutes)
+ pCO2_uacalcpH_UQL tm_UQL calcpH_UQL
+
+
+
+
+
+ BamfieldBoL:calcpH_Avg
+ calcpH_Avg
+ double
+
+ sea_water_ph_reported_on_total_scale
+ BoL
+ Burke-o-Lator
+ pH_T_Avg
+ total scale
+ time: mean (interval: 5.0 minutes)
+ pCO2_uacalcpH_UQL tm_UQL calcpH_UQL
+
+
+
+
+
+ BamfieldBoL:calcpH_Min
+ calcpH_Min
+ double
+
+ sea_water_ph_reported_on_total_scale
+ BoL
+ Burke-o-Lator
+ pH_T_Min
+ total scale
+ time: mininum (interval: 5.0 minutes)
+ pCO2_uacalcpH_UQL tm_UQL calcpH_UQL
+
+
+
+
+
+ BamfieldBoL:calcpH_Max
+ calcpH_Max
+ double
+
+ sea_water_ph_reported_on_total_scale
+ BoL
+ Burke-o-Lator
+ pH_T_Max
+ total scale
+ time: maximum (interval: 5.0 minutes)
+ pCO2_uacalcpH_UQL tm_UQL calcpH_UQL
+
+
+
+
+
+ BamfieldBoL:calcpH_Std
+ calcpH_Std
+ double
+
+ sea_water_ph_reported_on_total_scale
+ BoL
+ Burke-o-Lator
+ pH_T_Std
+ total scale
+ time: standard_deviation (interval: 5.0 minutes)
+ pCO2_uacalcpH_UQL tm_UQL calcpH_UQL
+
+
+
+
+
+ BamfieldBoL:calcpH_UQL
+ calcpH_UQL
+ byte
+
+ pH_T UNESCO Q level
+ PASS NOT_EVALUATED SUSPECT FAIL MISSING
+ 1 2 3 4 9
+ 1
+ aggregate_quality_flag
+ 2
+
+
+
+
+
+ BamfieldBoL:calcpH_QC
+ calcpH_flags
+ String
+
+ pH_T Q flags
+ Quality flags
+
+
+
+
+
diff --git a/test_result.xml b/test_result.xml
new file mode 100644
index 0000000..c06e3a2
--- /dev/null
+++ b/test_result.xml
@@ -0,0 +1,813 @@
+
+
+ hakai_erddap_sourceUrl
+ org.postgresql.Driver
+ erddap
+ "TestDataset1"
+
+ false
+ https://catalogue.hakai.orgca-cioos_ba41d935-f293-447f-be3d-7098e569b431
+ CF Standard Name Table v79
+ station
+ TimeSeries
+ station
+ coastal structure
+ http://vocab.nerc.ac.uk/collection/L06/current/
+ Oceanography,Nearshore,Watersheds
+ CC-BY-4.0
+ Dakunalytics, LLC - Burke-o-Lator pCO2/TCO2 analyzer - ,Seabird Electronics - SBE 45 -
+ ba41d935-f293-447f-be3d-7098e569b431
+ ca.cioos
+ fluorescence,turbidity,PAR,photosynthetically active radiation,Calvert Island,Quadra Island,transmissometer,Johnstone Strait,Queen Charlotte Sound,Strait of Georgia,salinity,coastal zone,CIOOS:oxygen,CIOOS:subSurfaceSalinity,CIOOS:subSurfaceTemperature
+ land/onshore structure
+ http://vocab.nerc.ac.uk/collection/L06/current/
+ Temperature, conductivity, dissolved oxygen, fluorescence, photosynthetic active radiation, and turbidity data collected from 2012 to present by the Hakai Institute in waters surrounding Calvert Island, Johnstone Strait, and Quadra Island areas. This dataset presents data collected by oceanographic profiler instruments (RBR XR-620, RBR Concerto, RBR Maestro, and Seabird SBE 19plus v2) which have been automatically processed by following respective manufacturer's guidelines (see Hakai Water Properties Profile Processing and QA/QC Procedure Manual).
+
+The provisional processed data are then quality controlled by applying a series of tests that are following the QARTOD standards and more tests specific to the Hakai Institute data (see Hakai Water Properties Profile Processing and QA/QC Procedure Manual).
+
+The research dataset provides a subset of the provisional dataset which has been manually reviewed and judged good for science quality level.
+
+Data were collected by the Hakai Institute Oceanography Program, the Nearshore Program, and the Juvenile Salmon Program.
+ Hakai Water Properties Vertical Profile Data Measured by Oceanographic Profilers, Research
+ ##Limitations:
+Instruments sensors are generally calibrated once a year by the manufacturer, no comparison and post-calibration correction based on data sampled by another method is applied to this dataset.onGoingCIOOS: CIOOS Essential Ocean Variables Vocabulary2024-03-18T19:28:21.285Z2024-03-191.0.0These data have been solely funded by the Tula Foundation. Data collection started near Calvert Island in 2012, near Quadra Island in 2014, and in Johnstone Strait from 2015 to 2019.Hakai Institutedata@hakai.orginstitutionHakai Institute1713 Hyacinthe Bay RoadHeriot BayCanadahakai.orgHakai Institutedata@hakai.orginstitutionHakai Institute1713 Hyacinthe Bay RoadHeriot BayCanadahakai.orgHakai Institute;lastname, firstname;asd, as;Tula Foundationdistributor,owner,funder,publisher;custodian,pointOfContact,processor;processor;funderhttps://doi.org/10.21966/kace-2d24https://catalogue.hakai.orgca-cioos_ba41d935-f293-447f-be3d-7098e569b431https://hakaiinstitute.github.io/hakai-metadata-entry-form#/en/hakai/tV5qE0aUgaOjSVmgPgiZ6MyHuSy1/-MX35qgX-BrwJDdeJKe1
+
+
+ measurementTime
+ time
+ String
+
+ time
+ seconds since 1970-01-01T00:00:00Z
+ time: point (interval: 5.0 minutes comment: time correspond to the end of the interval)
+
+
+
+
+
+ =48.836633
+ latitude
+ double
+
+ Fixed latitude of the site
+ Latitude
+ latitude
+ degrees_north
+
+
+
+
+
+ =-125.1362667
+ longitude
+ double
+
+ Fixed longitude of the site
+ Longitude
+ longitude
+ degrees_east
+
+
+
+
+
+ =20
+ depth
+ double
+
+ Approximative depth
+ Depth
+ depth
+ m
+
+
+
+
+ ="Nova Harvest Fisheries Burke-o-Lator"
+ station
+ String
+
+ Location
+ timeseries_id
+
+
+
+
+
+ BamfieldBoL:pCO2_uatm_Med
+ pCO2_uatm_Med
+ double
+
+ partial_pressure_of_carbon_dioxide_in_sea_water
+ BoL
+ Burke-o-Lator
+ pCO2_Med
+ μatm
+ time: median (interval: 5.0 minutes)
+ pCO2_uatm_UQL pCO2_uatm_flags
+
+
+
+
+
+ BamfieldBoL:pCO2_uatm_Avg
+ pCO2_uatm_Avg
+ double
+
+ partial_pressure_of_carbon_dioxide_in_sea_water
+ BoL
+ Burke-o-Lator
+ pCO2_Avg
+ μatm
+ time: mean (interval: 5.0 minutes)
+ pCO2_uatm_UQL pCO2_uatm_flags
+
+
+
+
+
+ BamfieldBoL:pCO2_uatm_Min
+ pCO2_uatm_Min
+ double
+
+ partial_pressure_of_carbon_dioxide_in_sea_water
+ BoL
+ Burke-o-Lator
+ pCO2_Min
+ μatm
+ time: mininum (interval: 5.0 minutes)
+ pCO2_uatm_UQL pCO2_uatm_flags
+
+
+
+
+
+ BamfieldBoL:pCO2_uatm_Max
+ pCO2_uatm_Max
+ double
+
+ partial_pressure_of_carbon_dioxide_in_sea_water
+ BoL
+ Burke-o-Lator
+ pCO2_Max
+ μatm
+ time: maximum (interval: 5.0 minutes)
+ pCO2_uatm_UQL pCO2_uatm_flags
+
+
+
+
+
+ BamfieldBoL:pCO2_uatm_Std
+ pCO2_uatm_Std
+ double
+
+ partial_pressure_of_carbon_dioxide_in_sea_water
+ BoL
+ Burke-o-Lator
+ pCO2_Std
+ μatm
+ time: standard_deviation (interval: 5.0 minutes)
+ pCO2_uatm_UQL pCO2_uatm_flags
+
+
+
+
+
+ BamfieldBoL:pCO2_uatm_UQL
+ pCO2_uatm_UQL
+ byte
+
+ pCO2 UNESCO Q level
+ PASS NOT_EVALUATED SUSPECT FAIL MISSING
+ 1 2 3 4 9
+ 1
+ aggregate_quality_flag
+ 2
+
+
+
+
+
+ BamfieldBoL:pCO2_uatm_QC
+ pCO2_uatm_flags
+ String
+
+ pCO2 Q flags
+ Quality flags
+
+
+
+
+
+ BamfieldBoL:calcTCO2_Med
+ calcTCO2_Med
+ double
+
+ BoL
+ Burke-o-Lator
+ TCO2_Med
+ μmol/kg
+ time: median (interval: 5.0 minutes)
+ calcTCO2_UQL calcTCO2_flags
+
+
+
+
+
+ BamfieldBoL:calcTCO2_Avg
+ calcTCO2_Avg
+ double
+
+ BoL
+ Burke-o-Lator
+ TCO2_Avg
+ μmol/kg
+ time: mean (interval: 5.0 minutes)
+ calcTCO2_UQL calcTCO2_flags
+
+
+
+
+
+ BamfieldBoL:calcTCO2_Min
+ calcTCO2_Min
+ double
+
+ BoL
+ Burke-o-Lator
+ TCO2_Min
+ μmol/kg
+ time: mininum (interval: 5.0 minutes)
+ calcTCO2_UQL calcTCO2_flags
+
+
+
+
+
+ BamfieldBoL:calcTCO2_Max
+ calcTCO2_Max
+ double
+
+ BoL
+ Burke-o-Lator
+ TCO2_Max
+ μmol/kg
+ time: maximum (interval: 5.0 minutes)
+ calcTCO2_UQL calcTCO2_flags
+
+
+
+
+
+ BamfieldBoL:calcTCO2_Std
+ calcTCO2_Std
+ double
+
+ BoL
+ Burke-o-Lator
+ TCO2_Std
+ μmol/kg
+ time: standard_deviation (interval: 5.0 minutes)
+ calcTCO2_UQL calcTCO2_flags
+
+
+
+
+
+ BamfieldBoL:calcTCO2_UQL
+ calcTCO2_UQL
+ byte
+
+ TCO2 UNESCO Q level
+ PASS NOT_EVALUATED SUSPECT FAIL MISSING
+ 1 2 3 4 9
+ 1
+ aggregate_quality_flag
+ 2
+
+
+
+
+
+ BamfieldBoL:calcTCO2_QC
+ calcTCO2_flags
+ String
+
+ TCO2 Q flags
+ Quality flags
+
+
+
+
+
+ BamfieldBoL:TSG_T_Med
+ TSG_T_Med
+ double
+
+ sea_water_temperature
+ BoL
+ Burke-o-Lator
+ Water temperature_Med
+ degree_Celsius
+ time: median (interval: 5.0 minutes)
+ TSG_T_UQL TSG_T_flags
+
+
+
+
+
+ BamfieldBoL:TSG_T_Avg
+ TSG_T_Avg
+ double
+
+ sea_water_temperature
+ BoL
+ Burke-o-Lator
+ Water temperature_Avg
+ degree_Celsius
+ time: mean (interval: 5.0 minutes)
+ TSG_T_UQL TSG_T_flags
+
+
+
+
+
+ BamfieldBoL:TSG_T_Min
+ TSG_T_Min
+ double
+
+ sea_water_temperature
+ BoL
+ Burke-o-Lator
+ Water temperature_Min
+ degree_Celsius
+ time: mininum (interval: 5.0 minutes)
+ TSG_T_UQL TSG_T_flags
+
+
+
+
+
+ BamfieldBoL:TSG_T_Max
+ TSG_T_Max
+ double
+
+ sea_water_temperature
+ BoL
+ Burke-o-Lator
+ Water temperature_Max
+ degree_Celsius
+ time: maximum (interval: 5.0 minutes)
+ TSG_T_UQL TSG_T_flags
+
+
+
+
+
+ BamfieldBoL:TSG_T_Std
+ TSG_T_Std
+ double
+
+ sea_water_temperature
+ BoL
+ Burke-o-Lator
+ Water temperature_Std
+ degree_Celsius
+ time: standard_deviation (interval: 5.0 minutes)
+ TSG_T_UQL TSG_T_flags
+
+
+
+
+
+ BamfieldBoL:TSG_T_UQL
+ TSG_T_UQL
+ byte
+
+ Water temperature UNESCO Q level
+ PASS NOT_EVALUATED SUSPECT FAIL MISSING
+ 1 2 3 4 9
+ 1
+ aggregate_quality_flag
+ 2
+
+
+
+
+
+ BamfieldBoL:TSG_T_QC
+ TSG_T_flags
+ String
+
+ Water temperature Q flags
+ Quality flags
+
+
+
+
+
+ BamfieldBoL:TSG_S_Med
+ TSG_S_Med
+ double
+
+ sea_water_practical_salinity
+ BoL
+ Burke-o-Lator
+ Salinity_Med
+ time: median (interval: 5.0 minutes)
+ TSG_S_UQL TSG_S_flags
+
+
+
+
+
+ BamfieldBoL:TSG_S_Avg
+ TSG_S_Avg
+ double
+
+ sea_water_practical_salinity
+ BoL
+ Burke-o-Lator
+ Salinity_Avg
+ time: mean (interval: 5.0 minutes)
+ TSG_S_UQL TSG_S_flags
+
+
+
+
+
+ BamfieldBoL:TSG_S_Min
+ TSG_S_Min
+ double
+
+ sea_water_practical_salinity
+ BoL
+ Burke-o-Lator
+ Salinity_Min
+ time: mininum (interval: 5.0 minutes)
+ TSG_S_UQL TSG_S_flags
+
+
+
+
+
+ BamfieldBoL:TSG_S_Max
+ TSG_S_Max
+ double
+
+ sea_water_practical_salinity
+ BoL
+ Burke-o-Lator
+ Salinity_Max
+ time: maximum (interval: 5.0 minutes)
+ TSG_S_UQL TSG_S_flags
+
+
+
+
+
+ BamfieldBoL:TSG_S_Std
+ TSG_S_Std
+ double
+
+ sea_water_practical_salinity
+ BoL
+ Burke-o-Lator
+ Salinity_Std
+ time: standard_deviation (interval: 5.0 minutes)
+ TSG_S_UQL TSG_S_flags
+
+
+
+
+
+ BamfieldBoL:TSG_S_UQL
+ TSG_S_UQL
+ byte
+
+ Salinity UNESCO Q level
+ PASS NOT_EVALUATED SUSPECT FAIL MISSING
+ 1 2 3 4 9
+ 1
+ aggregate_quality_flag
+ 2
+
+
+
+
+
+ BamfieldBoL:TSG_S_QC
+ TSG_S_flags
+ String
+
+ Salinity Q flags
+ Quality flags
+
+
+
+
+
+ BamfieldBoL:AlkS_Med
+ AlkS_Med
+ double
+
+ BoL
+ Burke-o-Lator
+ TA_Med
+ μmol/kg
+ time: median (interval: 5.0 minutes)
+ AlkS_UQL AlkS_flags
+
+
+
+
+
+ BamfieldBoL:AlkS_Avg
+ AlkS_Avg
+ double
+
+ BoL
+ Burke-o-Lator
+ TA_Avg
+ μmol/kg
+ time: mean (interval: 5.0 minutes)
+ AlkS_UQL AlkS_flags
+
+
+
+
+
+ BamfieldBoL:AlkS_Min
+ AlkS_Min
+ double
+
+ BoL
+ Burke-o-Lator
+ TA_Min
+ μmol/kg
+ time: mininum (interval: 5.0 minutes)
+ AlkS_UQL AlkS_flags
+
+
+
+
+
+ BamfieldBoL:AlkS_Max
+ AlkS_Max
+ double
+
+ BoL
+ Burke-o-Lator
+ TA_Max
+ μmol/kg
+ time: maximum (interval: 5.0 minutes)
+ AlkS_UQL AlkS_flags
+
+
+
+
+
+ BamfieldBoL:AlkS_Std
+ AlkS_Std
+ double
+
+ BoL
+ Burke-o-Lator
+ TA_Std
+ μmol/kg
+ time: standard_deviation (interval: 5.0 minutes)
+ AlkS_UQL AlkS_flags
+
+
+
+
+
+ BamfieldBoL:AlkS_UQL
+ AlkS_UQL
+ byte
+
+ TA UNESCO Q level
+ PASS NOT_EVALUATED SUSPECT FAIL MISSING
+ 1 2 3 4 9
+ 1
+ aggregate_quality_flag
+ 2
+
+
+
+
+
+ BamfieldBoL:AlkS_QC
+ AlkS_flags
+ String
+
+ TA Q flags
+ Quality flags
+
+
+
+
+
+ BamfieldBoL:calcOmega_Med
+ calcOmega_Med
+ double
+
+ BoL
+ Burke-o-Lator
+ Ω arag_Med
+ time: median (interval: 5.0 minutes)
+ calcOmega_UQL calcOmega_flags
+
+
+
+
+
+ BamfieldBoL:calcOmega_Avg
+ calcOmega_Avg
+ double
+
+ BoL
+ Burke-o-Lator
+ Ω arag_Avg
+ time: mean (interval: 5.0 minutes)
+ calcOmega_UQL calcOmega_flags
+
+
+
+
+
+ BamfieldBoL:calcOmega_Min
+ calcOmega_Min
+ double
+
+ BoL
+ Burke-o-Lator
+ Ω arag_Min
+ time: mininum (interval: 5.0 minutes)
+ calcOmega_UQL calcOmega_flags
+
+
+
+
+
+ BamfieldBoL:calcOmega_Max
+ calcOmega_Max
+ double
+
+ BoL
+ Burke-o-Lator
+ Ω arag_Max
+ time: maximum (interval: 5.0 minutes)
+ calcOmega_UQL calcOmega_flags
+
+
+
+
+
+ BamfieldBoL:calcOmega_Std
+ calcOmega_Std
+ double
+
+ BoL
+ Burke-o-Lator
+ Ω arag_Std
+ time: standard_deviation (interval: 5.0 minutes)
+ calcOmega_UQL calcOmega_flags
+
+
+
+
+
+ BamfieldBoL:calcOmega_UQL
+ calcOmega_UQL
+ byte
+
+ Ω arag UNESCO Q level
+ PASS NOT_EVALUATED SUSPECT FAIL MISSING
+ 1 2 3 4 9
+ 1
+ aggregate_quality_flag
+ 2
+
+
+
+
+
+ BamfieldBoL:calcOmega_QC
+ calcOmega_flags
+ String
+
+ Ω arag Q flags
+ Quality flags
+
+
+
+
+
+ BamfieldBoL:calcpH_Med
+ calcpH_Med
+ double
+
+ sea_water_ph_reported_on_total_scale
+ BoL
+ Burke-o-Lator
+ pH_T_Med
+ total scale
+ time: median (interval: 5.0 minutes)
+ pCO2_uacalcpH_UQL tm_UQL calcpH_UQL
+
+
+
+
+
+ BamfieldBoL:calcpH_Avg
+ calcpH_Avg
+ double
+
+ sea_water_ph_reported_on_total_scale
+ BoL
+ Burke-o-Lator
+ pH_T_Avg
+ total scale
+ time: mean (interval: 5.0 minutes)
+ pCO2_uacalcpH_UQL tm_UQL calcpH_UQL
+
+
+
+
+
+ BamfieldBoL:calcpH_Min
+ calcpH_Min
+ double
+
+ sea_water_ph_reported_on_total_scale
+ BoL
+ Burke-o-Lator
+ pH_T_Min
+ total scale
+ time: mininum (interval: 5.0 minutes)
+ pCO2_uacalcpH_UQL tm_UQL calcpH_UQL
+
+
+
+
+
+ BamfieldBoL:calcpH_Max
+ calcpH_Max
+ double
+
+ sea_water_ph_reported_on_total_scale
+ BoL
+ Burke-o-Lator
+ pH_T_Max
+ total scale
+ time: maximum (interval: 5.0 minutes)
+ pCO2_uacalcpH_UQL tm_UQL calcpH_UQL
+
+
+
+
+
+ BamfieldBoL:calcpH_Std
+ calcpH_Std
+ double
+
+ sea_water_ph_reported_on_total_scale
+ BoL
+ Burke-o-Lator
+ pH_T_Std
+ total scale
+ time: standard_deviation (interval: 5.0 minutes)
+ pCO2_uacalcpH_UQL tm_UQL calcpH_UQL
+
+
+
+
+
+ BamfieldBoL:calcpH_UQL
+ calcpH_UQL
+ byte
+
+ pH_T UNESCO Q level
+ PASS NOT_EVALUATED SUSPECT FAIL MISSING
+ 1 2 3 4 9
+ 1
+ aggregate_quality_flag
+ 2
+
+
+
+
+
+ BamfieldBoL:calcpH_QC
+ calcpH_flags
+ String
+
+ pH_T Q flags
+ Quality flags
+
+
+
+
+
diff --git a/tests/conftest.py b/tests/conftest.py
index 9d50818..3c87d63 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,7 +1,11 @@
+from pathlib import Path
+
import pytest
from hakai_metadata_conversion.__main__ import load
+Path("tests/results").mkdir(exist_ok=True)
+
@pytest.fixture
def record():
diff --git a/tests/erddap_xmls/datasets.d/test_dataset.xml b/tests/erddap_xmls/datasets.d/test_dataset.xml
new file mode 100644
index 0000000..792d641
--- /dev/null
+++ b/tests/erddap_xmls/datasets.d/test_dataset.xml
@@ -0,0 +1,805 @@
+
+
+
+ hakai_erddap_sourceUrl
+ org.postgresql.Driver
+ erddap
+ "HakaiBamfieldBoL5min"
+
+ false
+ CF Standard Name Table v79
+ station
+ TimeSeries
+ station
+ coastal structure
+ http://vocab.nerc.ac.uk/collection/L06/current/
+ Evans, W., Pocock, K., & Weekes, C. (2023). Real-Time Provisional High-Resolution Record of Seawater Carbon Dioxide (CO2) Content Collected from the Nova Harvest Ltd Hatchery in Bamfield BC Canada (Version v).
+ https://creativecommons.org/licenses/by/4.0
+ Dakunalytics, LLC - Burke-o-Lator pCO2/TCO2 analyzer - ,Seabird Electronics - SBE 45 -
+ fb5c9e1e-a911-46b7-8c1d-e34215a105ed
+ ca.cioos
+ Continuous measurement,Discrete measurement,Surface measurement,Ocean Acidification,Practical Salinity,Sea Surface Temperature,Sea Surface Practical Salinity,CO2,Barkley Sound,Trevor Channel
+ land/onshore structure
+ http://vocab.nerc.ac.uk/collection/L06/current/
+ summary
+ title
+ v
+
+
+ measurementTime
+ time
+ String
+
+ time
+ seconds since 1970-01-01T00:00:00Z
+ time: point (interval: 5.0 minutes comment: time correspond to the end of the interval)
+
+
+
+
+
+ =48.836633
+ latitude
+ double
+
+ Fixed latitude of the site
+ Latitude
+ latitude
+ degrees_north
+
+
+
+
+
+ =-125.1362667
+ longitude
+ double
+
+ Fixed longitude of the site
+ Longitude
+ longitude
+ degrees_east
+
+
+
+
+
+ =20
+ depth
+ double
+
+ Approximative depth
+ Depth
+ depth
+ m
+
+
+
+
+ ="Nova Harvest Fisheries Burke-o-Lator"
+ station
+ String
+
+ Location
+ timeseries_id
+
+
+
+
+
+ BamfieldBoL:pCO2_uatm_Med
+ pCO2_uatm_Med
+ double
+
+ partial_pressure_of_carbon_dioxide_in_sea_water
+ BoL
+ Burke-o-Lator
+ pCO2_Med
+ μatm
+ time: median (interval: 5.0 minutes)
+ pCO2_uatm_UQL pCO2_uatm_flags
+
+
+
+
+
+ BamfieldBoL:pCO2_uatm_Avg
+ pCO2_uatm_Avg
+ double
+
+ partial_pressure_of_carbon_dioxide_in_sea_water
+ BoL
+ Burke-o-Lator
+ pCO2_Avg
+ μatm
+ time: mean (interval: 5.0 minutes)
+ pCO2_uatm_UQL pCO2_uatm_flags
+
+
+
+
+
+ BamfieldBoL:pCO2_uatm_Min
+ pCO2_uatm_Min
+ double
+
+ partial_pressure_of_carbon_dioxide_in_sea_water
+ BoL
+ Burke-o-Lator
+ pCO2_Min
+ μatm
+ time: mininum (interval: 5.0 minutes)
+ pCO2_uatm_UQL pCO2_uatm_flags
+
+
+
+
+
+ BamfieldBoL:pCO2_uatm_Max
+ pCO2_uatm_Max
+ double
+
+ partial_pressure_of_carbon_dioxide_in_sea_water
+ BoL
+ Burke-o-Lator
+ pCO2_Max
+ μatm
+ time: maximum (interval: 5.0 minutes)
+ pCO2_uatm_UQL pCO2_uatm_flags
+
+
+
+
+
+ BamfieldBoL:pCO2_uatm_Std
+ pCO2_uatm_Std
+ double
+
+ partial_pressure_of_carbon_dioxide_in_sea_water
+ BoL
+ Burke-o-Lator
+ pCO2_Std
+ μatm
+ time: standard_deviation (interval: 5.0 minutes)
+ pCO2_uatm_UQL pCO2_uatm_flags
+
+
+
+
+
+ BamfieldBoL:pCO2_uatm_UQL
+ pCO2_uatm_UQL
+ byte
+
+ pCO2 UNESCO Q level
+ PASS NOT_EVALUATED SUSPECT FAIL MISSING
+ 1 2 3 4 9
+ 1
+ aggregate_quality_flag
+ 2
+
+
+
+
+
+ BamfieldBoL:pCO2_uatm_QC
+ pCO2_uatm_flags
+ String
+
+ pCO2 Q flags
+ Quality flags
+
+
+
+
+
+ BamfieldBoL:calcTCO2_Med
+ calcTCO2_Med
+ double
+
+ BoL
+ Burke-o-Lator
+ TCO2_Med
+ μmol/kg
+ time: median (interval: 5.0 minutes)
+ calcTCO2_UQL calcTCO2_flags
+
+
+
+
+
+ BamfieldBoL:calcTCO2_Avg
+ calcTCO2_Avg
+ double
+
+ BoL
+ Burke-o-Lator
+ TCO2_Avg
+ μmol/kg
+ time: mean (interval: 5.0 minutes)
+ calcTCO2_UQL calcTCO2_flags
+
+
+
+
+
+ BamfieldBoL:calcTCO2_Min
+ calcTCO2_Min
+ double
+
+ BoL
+ Burke-o-Lator
+ TCO2_Min
+ μmol/kg
+ time: mininum (interval: 5.0 minutes)
+ calcTCO2_UQL calcTCO2_flags
+
+
+
+
+
+ BamfieldBoL:calcTCO2_Max
+ calcTCO2_Max
+ double
+
+ BoL
+ Burke-o-Lator
+ TCO2_Max
+ μmol/kg
+ time: maximum (interval: 5.0 minutes)
+ calcTCO2_UQL calcTCO2_flags
+
+
+
+
+
+ BamfieldBoL:calcTCO2_Std
+ calcTCO2_Std
+ double
+
+ BoL
+ Burke-o-Lator
+ TCO2_Std
+ μmol/kg
+ time: standard_deviation (interval: 5.0 minutes)
+ calcTCO2_UQL calcTCO2_flags
+
+
+
+
+
+ BamfieldBoL:calcTCO2_UQL
+ calcTCO2_UQL
+ byte
+
+ TCO2 UNESCO Q level
+ PASS NOT_EVALUATED SUSPECT FAIL MISSING
+ 1 2 3 4 9
+ 1
+ aggregate_quality_flag
+ 2
+
+
+
+
+
+ BamfieldBoL:calcTCO2_QC
+ calcTCO2_flags
+ String
+
+ TCO2 Q flags
+ Quality flags
+
+
+
+
+
+ BamfieldBoL:TSG_T_Med
+ TSG_T_Med
+ double
+
+ sea_water_temperature
+ BoL
+ Burke-o-Lator
+ Water temperature_Med
+ degree_Celsius
+ time: median (interval: 5.0 minutes)
+ TSG_T_UQL TSG_T_flags
+
+
+
+
+
+ BamfieldBoL:TSG_T_Avg
+ TSG_T_Avg
+ double
+
+ sea_water_temperature
+ BoL
+ Burke-o-Lator
+ Water temperature_Avg
+ degree_Celsius
+ time: mean (interval: 5.0 minutes)
+ TSG_T_UQL TSG_T_flags
+
+
+
+
+
+ BamfieldBoL:TSG_T_Min
+ TSG_T_Min
+ double
+
+ sea_water_temperature
+ BoL
+ Burke-o-Lator
+ Water temperature_Min
+ degree_Celsius
+ time: mininum (interval: 5.0 minutes)
+ TSG_T_UQL TSG_T_flags
+
+
+
+
+
+ BamfieldBoL:TSG_T_Max
+ TSG_T_Max
+ double
+
+ sea_water_temperature
+ BoL
+ Burke-o-Lator
+ Water temperature_Max
+ degree_Celsius
+ time: maximum (interval: 5.0 minutes)
+ TSG_T_UQL TSG_T_flags
+
+
+
+
+
+ BamfieldBoL:TSG_T_Std
+ TSG_T_Std
+ double
+
+ sea_water_temperature
+ BoL
+ Burke-o-Lator
+ Water temperature_Std
+ degree_Celsius
+ time: standard_deviation (interval: 5.0 minutes)
+ TSG_T_UQL TSG_T_flags
+
+
+
+
+
+ BamfieldBoL:TSG_T_UQL
+ TSG_T_UQL
+ byte
+
+ Water temperature UNESCO Q level
+ PASS NOT_EVALUATED SUSPECT FAIL MISSING
+ 1 2 3 4 9
+ 1
+ aggregate_quality_flag
+ 2
+
+
+
+
+
+ BamfieldBoL:TSG_T_QC
+ TSG_T_flags
+ String
+
+ Water temperature Q flags
+ Quality flags
+
+
+
+
+
+ BamfieldBoL:TSG_S_Med
+ TSG_S_Med
+ double
+
+ sea_water_practical_salinity
+ BoL
+ Burke-o-Lator
+ Salinity_Med
+ time: median (interval: 5.0 minutes)
+ TSG_S_UQL TSG_S_flags
+
+
+
+
+
+ BamfieldBoL:TSG_S_Avg
+ TSG_S_Avg
+ double
+
+ sea_water_practical_salinity
+ BoL
+ Burke-o-Lator
+ Salinity_Avg
+ time: mean (interval: 5.0 minutes)
+ TSG_S_UQL TSG_S_flags
+
+
+
+
+
+ BamfieldBoL:TSG_S_Min
+ TSG_S_Min
+ double
+
+ sea_water_practical_salinity
+ BoL
+ Burke-o-Lator
+ Salinity_Min
+ time: mininum (interval: 5.0 minutes)
+ TSG_S_UQL TSG_S_flags
+
+
+
+
+
+ BamfieldBoL:TSG_S_Max
+ TSG_S_Max
+ double
+
+ sea_water_practical_salinity
+ BoL
+ Burke-o-Lator
+ Salinity_Max
+ time: maximum (interval: 5.0 minutes)
+ TSG_S_UQL TSG_S_flags
+
+
+
+
+
+ BamfieldBoL:TSG_S_Std
+ TSG_S_Std
+ double
+
+ sea_water_practical_salinity
+ BoL
+ Burke-o-Lator
+ Salinity_Std
+ time: standard_deviation (interval: 5.0 minutes)
+ TSG_S_UQL TSG_S_flags
+
+
+
+
+
+ BamfieldBoL:TSG_S_UQL
+ TSG_S_UQL
+ byte
+
+ Salinity UNESCO Q level
+ PASS NOT_EVALUATED SUSPECT FAIL MISSING
+ 1 2 3 4 9
+ 1
+ aggregate_quality_flag
+ 2
+
+
+
+
+
+ BamfieldBoL:TSG_S_QC
+ TSG_S_flags
+ String
+
+ Salinity Q flags
+ Quality flags
+
+
+
+
+
+ BamfieldBoL:AlkS_Med
+ AlkS_Med
+ double
+
+ BoL
+ Burke-o-Lator
+ TA_Med
+ μmol/kg
+ time: median (interval: 5.0 minutes)
+ AlkS_UQL AlkS_flags
+
+
+
+
+
+ BamfieldBoL:AlkS_Avg
+ AlkS_Avg
+ double
+
+ BoL
+ Burke-o-Lator
+ TA_Avg
+ μmol/kg
+ time: mean (interval: 5.0 minutes)
+ AlkS_UQL AlkS_flags
+
+
+
+
+
+ BamfieldBoL:AlkS_Min
+ AlkS_Min
+ double
+
+ BoL
+ Burke-o-Lator
+ TA_Min
+ μmol/kg
+ time: mininum (interval: 5.0 minutes)
+ AlkS_UQL AlkS_flags
+
+
+
+
+
+ BamfieldBoL:AlkS_Max
+ AlkS_Max
+ double
+
+ BoL
+ Burke-o-Lator
+ TA_Max
+ μmol/kg
+ time: maximum (interval: 5.0 minutes)
+ AlkS_UQL AlkS_flags
+
+
+
+
+
+ BamfieldBoL:AlkS_Std
+ AlkS_Std
+ double
+
+ BoL
+ Burke-o-Lator
+ TA_Std
+ μmol/kg
+ time: standard_deviation (interval: 5.0 minutes)
+ AlkS_UQL AlkS_flags
+
+
+
+
+
+ BamfieldBoL:AlkS_UQL
+ AlkS_UQL
+ byte
+
+ TA UNESCO Q level
+ PASS NOT_EVALUATED SUSPECT FAIL MISSING
+ 1 2 3 4 9
+ 1
+ aggregate_quality_flag
+ 2
+
+
+
+
+
+ BamfieldBoL:AlkS_QC
+ AlkS_flags
+ String
+
+ TA Q flags
+ Quality flags
+
+
+
+
+
+ BamfieldBoL:calcOmega_Med
+ calcOmega_Med
+ double
+
+ BoL
+ Burke-o-Lator
+ Ω arag_Med
+ time: median (interval: 5.0 minutes)
+ calcOmega_UQL calcOmega_flags
+
+
+
+
+
+ BamfieldBoL:calcOmega_Avg
+ calcOmega_Avg
+ double
+
+ BoL
+ Burke-o-Lator
+ Ω arag_Avg
+ time: mean (interval: 5.0 minutes)
+ calcOmega_UQL calcOmega_flags
+
+
+
+
+
+ BamfieldBoL:calcOmega_Min
+ calcOmega_Min
+ double
+
+ BoL
+ Burke-o-Lator
+ Ω arag_Min
+ time: mininum (interval: 5.0 minutes)
+ calcOmega_UQL calcOmega_flags
+
+
+
+
+
+ BamfieldBoL:calcOmega_Max
+ calcOmega_Max
+ double
+
+ BoL
+ Burke-o-Lator
+ Ω arag_Max
+ time: maximum (interval: 5.0 minutes)
+ calcOmega_UQL calcOmega_flags
+
+
+
+
+
+ BamfieldBoL:calcOmega_Std
+ calcOmega_Std
+ double
+
+ BoL
+ Burke-o-Lator
+ Ω arag_Std
+ time: standard_deviation (interval: 5.0 minutes)
+ calcOmega_UQL calcOmega_flags
+
+
+
+
+
+ BamfieldBoL:calcOmega_UQL
+ calcOmega_UQL
+ byte
+
+ Ω arag UNESCO Q level
+ PASS NOT_EVALUATED SUSPECT FAIL MISSING
+ 1 2 3 4 9
+ 1
+ aggregate_quality_flag
+ 2
+
+
+
+
+
+ BamfieldBoL:calcOmega_QC
+ calcOmega_flags
+ String
+
+ Ω arag Q flags
+ Quality flags
+
+
+
+
+
+ BamfieldBoL:calcpH_Med
+ calcpH_Med
+ double
+
+ sea_water_ph_reported_on_total_scale
+ BoL
+ Burke-o-Lator
+ pH_T_Med
+ total scale
+ time: median (interval: 5.0 minutes)
+ pCO2_uacalcpH_UQL tm_UQL calcpH_UQL
+
+
+
+
+
+ BamfieldBoL:calcpH_Avg
+ calcpH_Avg
+ double
+
+ sea_water_ph_reported_on_total_scale
+ BoL
+ Burke-o-Lator
+ pH_T_Avg
+ total scale
+ time: mean (interval: 5.0 minutes)
+ pCO2_uacalcpH_UQL tm_UQL calcpH_UQL
+
+
+
+
+
+ BamfieldBoL:calcpH_Min
+ calcpH_Min
+ double
+
+ sea_water_ph_reported_on_total_scale
+ BoL
+ Burke-o-Lator
+ pH_T_Min
+ total scale
+ time: mininum (interval: 5.0 minutes)
+ pCO2_uacalcpH_UQL tm_UQL calcpH_UQL
+
+
+
+
+
+ BamfieldBoL:calcpH_Max
+ calcpH_Max
+ double
+
+ sea_water_ph_reported_on_total_scale
+ BoL
+ Burke-o-Lator
+ pH_T_Max
+ total scale
+ time: maximum (interval: 5.0 minutes)
+ pCO2_uacalcpH_UQL tm_UQL calcpH_UQL
+
+
+
+
+
+ BamfieldBoL:calcpH_Std
+ calcpH_Std
+ double
+
+ sea_water_ph_reported_on_total_scale
+ BoL
+ Burke-o-Lator
+ pH_T_Std
+ total scale
+ time: standard_deviation (interval: 5.0 minutes)
+ pCO2_uacalcpH_UQL tm_UQL calcpH_UQL
+
+
+
+
+
+ BamfieldBoL:calcpH_UQL
+ calcpH_UQL
+ byte
+
+ pH_T UNESCO Q level
+ PASS NOT_EVALUATED SUSPECT FAIL MISSING
+ 1 2 3 4 9
+ 1
+ aggregate_quality_flag
+ 2
+
+
+
+
+
+ BamfieldBoL:calcpH_QC
+ calcpH_flags
+ String
+
+ pH_T Q flags
+ Quality flags
+
+
+
+
\ No newline at end of file
diff --git a/tests/erddap_xmls/test_datasets.xml b/tests/erddap_xmls/test_datasets.xml
new file mode 100644
index 0000000..2e1d905
--- /dev/null
+++ b/tests/erddap_xmls/test_datasets.xml
@@ -0,0 +1,807 @@
+
+
+
+ hakai_erddap_sourceUrl
+ org.postgresql.Driver
+ erddap
+ "TestDataset1"
+
+ false
+ https://www.hakai.org
+ CF Standard Name Table v79
+ station
+ TimeSeries
+ station
+ coastal structure
+ http://vocab.nerc.ac.uk/collection/L06/current/
+ Oceanography
+ https://creativecommons.org/licenses/by/4.0
+ Dakunalytics, LLC - Burke-o-Lator pCO2/TCO2 analyzer - ,Seabird Electronics - SBE 45 -
+ fb5c9e1e-a911-46b7-8c1d-e34215a105ed
+ ca.cioos
+ some,key,words
+ land/onshore structure
+ http://vocab.nerc.ac.uk/collection/L06/current/
+ summary
+ title
+
+
+
+ measurementTime
+ time
+ String
+
+ time
+ seconds since 1970-01-01T00:00:00Z
+ time: point (interval: 5.0 minutes comment: time correspond to the end of the interval)
+
+
+
+
+
+ =48.836633
+ latitude
+ double
+
+ Fixed latitude of the site
+ Latitude
+ latitude
+ degrees_north
+
+
+
+
+
+ =-125.1362667
+ longitude
+ double
+
+ Fixed longitude of the site
+ Longitude
+ longitude
+ degrees_east
+
+
+
+
+
+ =20
+ depth
+ double
+
+ Approximative depth
+ Depth
+ depth
+ m
+
+
+
+
+ ="Nova Harvest Fisheries Burke-o-Lator"
+ station
+ String
+
+ Location
+ timeseries_id
+
+
+
+
+
+ BamfieldBoL:pCO2_uatm_Med
+ pCO2_uatm_Med
+ double
+
+ partial_pressure_of_carbon_dioxide_in_sea_water
+ BoL
+ Burke-o-Lator
+ pCO2_Med
+ μatm
+ time: median (interval: 5.0 minutes)
+ pCO2_uatm_UQL pCO2_uatm_flags
+
+
+
+
+
+ BamfieldBoL:pCO2_uatm_Avg
+ pCO2_uatm_Avg
+ double
+
+ partial_pressure_of_carbon_dioxide_in_sea_water
+ BoL
+ Burke-o-Lator
+ pCO2_Avg
+ μatm
+ time: mean (interval: 5.0 minutes)
+ pCO2_uatm_UQL pCO2_uatm_flags
+
+
+
+
+
+ BamfieldBoL:pCO2_uatm_Min
+ pCO2_uatm_Min
+ double
+
+ partial_pressure_of_carbon_dioxide_in_sea_water
+ BoL
+ Burke-o-Lator
+ pCO2_Min
+ μatm
+ time: mininum (interval: 5.0 minutes)
+ pCO2_uatm_UQL pCO2_uatm_flags
+
+
+
+
+
+ BamfieldBoL:pCO2_uatm_Max
+ pCO2_uatm_Max
+ double
+
+ partial_pressure_of_carbon_dioxide_in_sea_water
+ BoL
+ Burke-o-Lator
+ pCO2_Max
+ μatm
+ time: maximum (interval: 5.0 minutes)
+ pCO2_uatm_UQL pCO2_uatm_flags
+
+
+
+
+
+ BamfieldBoL:pCO2_uatm_Std
+ pCO2_uatm_Std
+ double
+
+ partial_pressure_of_carbon_dioxide_in_sea_water
+ BoL
+ Burke-o-Lator
+ pCO2_Std
+ μatm
+ time: standard_deviation (interval: 5.0 minutes)
+ pCO2_uatm_UQL pCO2_uatm_flags
+
+
+
+
+
+ BamfieldBoL:pCO2_uatm_UQL
+ pCO2_uatm_UQL
+ byte
+
+ pCO2 UNESCO Q level
+ PASS NOT_EVALUATED SUSPECT FAIL MISSING
+ 1 2 3 4 9
+ 1
+ aggregate_quality_flag
+ 2
+
+
+
+
+
+ BamfieldBoL:pCO2_uatm_QC
+ pCO2_uatm_flags
+ String
+
+ pCO2 Q flags
+ Quality flags
+
+
+
+
+
+ BamfieldBoL:calcTCO2_Med
+ calcTCO2_Med
+ double
+
+ BoL
+ Burke-o-Lator
+ TCO2_Med
+ μmol/kg
+ time: median (interval: 5.0 minutes)
+ calcTCO2_UQL calcTCO2_flags
+
+
+
+
+
+ BamfieldBoL:calcTCO2_Avg
+ calcTCO2_Avg
+ double
+
+ BoL
+ Burke-o-Lator
+ TCO2_Avg
+ μmol/kg
+ time: mean (interval: 5.0 minutes)
+ calcTCO2_UQL calcTCO2_flags
+
+
+
+
+
+ BamfieldBoL:calcTCO2_Min
+ calcTCO2_Min
+ double
+
+ BoL
+ Burke-o-Lator
+ TCO2_Min
+ μmol/kg
+ time: mininum (interval: 5.0 minutes)
+ calcTCO2_UQL calcTCO2_flags
+
+
+
+
+
+ BamfieldBoL:calcTCO2_Max
+ calcTCO2_Max
+ double
+
+ BoL
+ Burke-o-Lator
+ TCO2_Max
+ μmol/kg
+ time: maximum (interval: 5.0 minutes)
+ calcTCO2_UQL calcTCO2_flags
+
+
+
+
+
+ BamfieldBoL:calcTCO2_Std
+ calcTCO2_Std
+ double
+
+ BoL
+ Burke-o-Lator
+ TCO2_Std
+ μmol/kg
+ time: standard_deviation (interval: 5.0 minutes)
+ calcTCO2_UQL calcTCO2_flags
+
+
+
+
+
+ BamfieldBoL:calcTCO2_UQL
+ calcTCO2_UQL
+ byte
+
+ TCO2 UNESCO Q level
+ PASS NOT_EVALUATED SUSPECT FAIL MISSING
+ 1 2 3 4 9
+ 1
+ aggregate_quality_flag
+ 2
+
+
+
+
+
+ BamfieldBoL:calcTCO2_QC
+ calcTCO2_flags
+ String
+
+ TCO2 Q flags
+ Quality flags
+
+
+
+
+
+ BamfieldBoL:TSG_T_Med
+ TSG_T_Med
+ double
+
+ sea_water_temperature
+ BoL
+ Burke-o-Lator
+ Water temperature_Med
+ degree_Celsius
+ time: median (interval: 5.0 minutes)
+ TSG_T_UQL TSG_T_flags
+
+
+
+
+
+ BamfieldBoL:TSG_T_Avg
+ TSG_T_Avg
+ double
+
+ sea_water_temperature
+ BoL
+ Burke-o-Lator
+ Water temperature_Avg
+ degree_Celsius
+ time: mean (interval: 5.0 minutes)
+ TSG_T_UQL TSG_T_flags
+
+
+
+
+
+ BamfieldBoL:TSG_T_Min
+ TSG_T_Min
+ double
+
+ sea_water_temperature
+ BoL
+ Burke-o-Lator
+ Water temperature_Min
+ degree_Celsius
+ time: mininum (interval: 5.0 minutes)
+ TSG_T_UQL TSG_T_flags
+
+
+
+
+
+ BamfieldBoL:TSG_T_Max
+ TSG_T_Max
+ double
+
+ sea_water_temperature
+ BoL
+ Burke-o-Lator
+ Water temperature_Max
+ degree_Celsius
+ time: maximum (interval: 5.0 minutes)
+ TSG_T_UQL TSG_T_flags
+
+
+
+
+
+ BamfieldBoL:TSG_T_Std
+ TSG_T_Std
+ double
+
+ sea_water_temperature
+ BoL
+ Burke-o-Lator
+ Water temperature_Std
+ degree_Celsius
+ time: standard_deviation (interval: 5.0 minutes)
+ TSG_T_UQL TSG_T_flags
+
+
+
+
+
+ BamfieldBoL:TSG_T_UQL
+ TSG_T_UQL
+ byte
+
+ Water temperature UNESCO Q level
+ PASS NOT_EVALUATED SUSPECT FAIL MISSING
+ 1 2 3 4 9
+ 1
+ aggregate_quality_flag
+ 2
+
+
+
+
+
+ BamfieldBoL:TSG_T_QC
+ TSG_T_flags
+ String
+
+ Water temperature Q flags
+ Quality flags
+
+
+
+
+
+ BamfieldBoL:TSG_S_Med
+ TSG_S_Med
+ double
+
+ sea_water_practical_salinity
+ BoL
+ Burke-o-Lator
+ Salinity_Med
+ time: median (interval: 5.0 minutes)
+ TSG_S_UQL TSG_S_flags
+
+
+
+
+
+ BamfieldBoL:TSG_S_Avg
+ TSG_S_Avg
+ double
+
+ sea_water_practical_salinity
+ BoL
+ Burke-o-Lator
+ Salinity_Avg
+ time: mean (interval: 5.0 minutes)
+ TSG_S_UQL TSG_S_flags
+
+
+
+
+
+ BamfieldBoL:TSG_S_Min
+ TSG_S_Min
+ double
+
+ sea_water_practical_salinity
+ BoL
+ Burke-o-Lator
+ Salinity_Min
+ time: mininum (interval: 5.0 minutes)
+ TSG_S_UQL TSG_S_flags
+
+
+
+
+
+ BamfieldBoL:TSG_S_Max
+ TSG_S_Max
+ double
+
+ sea_water_practical_salinity
+ BoL
+ Burke-o-Lator
+ Salinity_Max
+ time: maximum (interval: 5.0 minutes)
+ TSG_S_UQL TSG_S_flags
+
+
+
+
+
+ BamfieldBoL:TSG_S_Std
+ TSG_S_Std
+ double
+
+ sea_water_practical_salinity
+ BoL
+ Burke-o-Lator
+ Salinity_Std
+ time: standard_deviation (interval: 5.0 minutes)
+ TSG_S_UQL TSG_S_flags
+
+
+
+
+
+ BamfieldBoL:TSG_S_UQL
+ TSG_S_UQL
+ byte
+
+ Salinity UNESCO Q level
+ PASS NOT_EVALUATED SUSPECT FAIL MISSING
+ 1 2 3 4 9
+ 1
+ aggregate_quality_flag
+ 2
+
+
+
+
+
+ BamfieldBoL:TSG_S_QC
+ TSG_S_flags
+ String
+
+ Salinity Q flags
+ Quality flags
+
+
+
+
+
+ BamfieldBoL:AlkS_Med
+ AlkS_Med
+ double
+
+ BoL
+ Burke-o-Lator
+ TA_Med
+ μmol/kg
+ time: median (interval: 5.0 minutes)
+ AlkS_UQL AlkS_flags
+
+
+
+
+
+ BamfieldBoL:AlkS_Avg
+ AlkS_Avg
+ double
+
+ BoL
+ Burke-o-Lator
+ TA_Avg
+ μmol/kg
+ time: mean (interval: 5.0 minutes)
+ AlkS_UQL AlkS_flags
+
+
+
+
+
+ BamfieldBoL:AlkS_Min
+ AlkS_Min
+ double
+
+ BoL
+ Burke-o-Lator
+ TA_Min
+ μmol/kg
+ time: mininum (interval: 5.0 minutes)
+ AlkS_UQL AlkS_flags
+
+
+
+
+
+ BamfieldBoL:AlkS_Max
+ AlkS_Max
+ double
+
+ BoL
+ Burke-o-Lator
+ TA_Max
+ μmol/kg
+ time: maximum (interval: 5.0 minutes)
+ AlkS_UQL AlkS_flags
+
+
+
+
+
+ BamfieldBoL:AlkS_Std
+ AlkS_Std
+ double
+
+ BoL
+ Burke-o-Lator
+ TA_Std
+ μmol/kg
+ time: standard_deviation (interval: 5.0 minutes)
+ AlkS_UQL AlkS_flags
+
+
+
+
+
+ BamfieldBoL:AlkS_UQL
+ AlkS_UQL
+ byte
+
+ TA UNESCO Q level
+ PASS NOT_EVALUATED SUSPECT FAIL MISSING
+ 1 2 3 4 9
+ 1
+ aggregate_quality_flag
+ 2
+
+
+
+
+
+ BamfieldBoL:AlkS_QC
+ AlkS_flags
+ String
+
+ TA Q flags
+ Quality flags
+
+
+
+
+
+ BamfieldBoL:calcOmega_Med
+ calcOmega_Med
+ double
+
+ BoL
+ Burke-o-Lator
+ Ω arag_Med
+ time: median (interval: 5.0 minutes)
+ calcOmega_UQL calcOmega_flags
+
+
+
+
+
+ BamfieldBoL:calcOmega_Avg
+ calcOmega_Avg
+ double
+
+ BoL
+ Burke-o-Lator
+ Ω arag_Avg
+ time: mean (interval: 5.0 minutes)
+ calcOmega_UQL calcOmega_flags
+
+
+
+
+
+ BamfieldBoL:calcOmega_Min
+ calcOmega_Min
+ double
+
+ BoL
+ Burke-o-Lator
+ Ω arag_Min
+ time: mininum (interval: 5.0 minutes)
+ calcOmega_UQL calcOmega_flags
+
+
+
+
+
+ BamfieldBoL:calcOmega_Max
+ calcOmega_Max
+ double
+
+ BoL
+ Burke-o-Lator
+ Ω arag_Max
+ time: maximum (interval: 5.0 minutes)
+ calcOmega_UQL calcOmega_flags
+
+
+
+
+
+ BamfieldBoL:calcOmega_Std
+ calcOmega_Std
+ double
+
+ BoL
+ Burke-o-Lator
+ Ω arag_Std
+ time: standard_deviation (interval: 5.0 minutes)
+ calcOmega_UQL calcOmega_flags
+
+
+
+
+
+ BamfieldBoL:calcOmega_UQL
+ calcOmega_UQL
+ byte
+
+ Ω arag UNESCO Q level
+ PASS NOT_EVALUATED SUSPECT FAIL MISSING
+ 1 2 3 4 9
+ 1
+ aggregate_quality_flag
+ 2
+
+
+
+
+
+ BamfieldBoL:calcOmega_QC
+ calcOmega_flags
+ String
+
+ Ω arag Q flags
+ Quality flags
+
+
+
+
+
+ BamfieldBoL:calcpH_Med
+ calcpH_Med
+ double
+
+ sea_water_ph_reported_on_total_scale
+ BoL
+ Burke-o-Lator
+ pH_T_Med
+ total scale
+ time: median (interval: 5.0 minutes)
+ pCO2_uacalcpH_UQL tm_UQL calcpH_UQL
+
+
+
+
+
+ BamfieldBoL:calcpH_Avg
+ calcpH_Avg
+ double
+
+ sea_water_ph_reported_on_total_scale
+ BoL
+ Burke-o-Lator
+ pH_T_Avg
+ total scale
+ time: mean (interval: 5.0 minutes)
+ pCO2_uacalcpH_UQL tm_UQL calcpH_UQL
+
+
+
+
+
+ BamfieldBoL:calcpH_Min
+ calcpH_Min
+ double
+
+ sea_water_ph_reported_on_total_scale
+ BoL
+ Burke-o-Lator
+ pH_T_Min
+ total scale
+ time: mininum (interval: 5.0 minutes)
+ pCO2_uacalcpH_UQL tm_UQL calcpH_UQL
+
+
+
+
+
+ BamfieldBoL:calcpH_Max
+ calcpH_Max
+ double
+
+ sea_water_ph_reported_on_total_scale
+ BoL
+ Burke-o-Lator
+ pH_T_Max
+ total scale
+ time: maximum (interval: 5.0 minutes)
+ pCO2_uacalcpH_UQL tm_UQL calcpH_UQL
+
+
+
+
+
+ BamfieldBoL:calcpH_Std
+ calcpH_Std
+ double
+
+ sea_water_ph_reported_on_total_scale
+ BoL
+ Burke-o-Lator
+ pH_T_Std
+ total scale
+ time: standard_deviation (interval: 5.0 minutes)
+ pCO2_uacalcpH_UQL tm_UQL calcpH_UQL
+
+
+
+
+
+ BamfieldBoL:calcpH_UQL
+ calcpH_UQL
+ byte
+
+ pH_T UNESCO Q level
+ PASS NOT_EVALUATED SUSPECT FAIL MISSING
+ 1 2 3 4 9
+ 1
+ aggregate_quality_flag
+ 2
+
+
+
+
+
+ BamfieldBoL:calcpH_QC
+ calcpH_flags
+ String
+
+ pH_T Q flags
+ Quality flags
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/test_citation_cff.py b/tests/test_citation_cff.py
index 274f01f..8f8e6d6 100644
--- a/tests/test_citation_cff.py
+++ b/tests/test_citation_cff.py
@@ -1,11 +1,13 @@
-from pathlib import Path
import subprocess
from glob import glob
+from pathlib import Path
import pytest
+
from hakai_metadata_conversion import citation_cff
from hakai_metadata_conversion.__main__ import load
+
def test_citation_cff(record):
result = citation_cff.citation_cff(record, output_format=None, language="en")
assert result
@@ -24,9 +26,10 @@ def test_citation_cff(record):
assert "version" in result
assert "identifiers" in result
-def test_ctation_cff_yaml(record):
+
+def test_ctation_cff_yaml(record, tmp_path):
result = citation_cff.citation_cff(record, output_format="yaml", language="en")
- Path("CITATION.cff").write_text(result, encoding="utf-8")
+ (tmp_path / "CITATION.cff").write_text(result, encoding="utf-8")
def test_citation_cff_validation(record, tmp_path):
@@ -41,7 +44,10 @@ def test_citation_cff_validation(record, tmp_path):
assert result.returncode == 0, result.stderr
-@pytest.mark.parametrize("file", glob("tests/records/hakai-metadata-entry-form-files/**/*.yaml", recursive=True))
+@pytest.mark.parametrize(
+ "file",
+ glob("tests/records/hakai-metadata-entry-form-files/**/*.yaml", recursive=True),
+)
def test_hakai_metadata_entry_form_files_cff(file, tmp_path):
data = load(file, "yaml")
result = citation_cff.citation_cff(data, output_format="yaml", language="en")
@@ -54,7 +60,11 @@ def test_hakai_metadata_entry_form_files_cff(file, tmp_path):
capture_output=True,
)
-@pytest.mark.parametrize("file", glob("tests/records/hakai-metadata-entry-form-files/**/*.yaml", recursive=True))
+
+@pytest.mark.parametrize(
+ "file",
+ glob("tests/records/hakai-metadata-entry-form-files/**/*.yaml", recursive=True),
+)
def test_hakai_metadata_entry_form_files_cff_fr(file, tmp_path):
data = load(file, "yaml")
result_fr = citation_cff.citation_cff(data, output_format="yaml", language="fr")
diff --git a/tests/test_erddap.py b/tests/test_erddap.py
index 92c8e2b..575f38e 100644
--- a/tests/test_erddap.py
+++ b/tests/test_erddap.py
@@ -1,11 +1,15 @@
-import pytest
from glob import glob
+from pathlib import Path
+from xml.etree import ElementTree as ET
+
+import pytest
+
+import hakai_metadata_conversion.erddap as erddap
from hakai_metadata_conversion.__main__ import load
-from hakai_metadata_conversion.erddap import global_attributes
def test_erddap_global_attributes(record):
- result = global_attributes(record, output=None, language="en")
+ result = erddap.global_attributes(record, output=None, language="en")
assert result
assert isinstance(result, dict)
assert "title" in result
@@ -34,20 +38,81 @@ def test_erddap_global_attributes(record):
assert "metadata_link" in result
-def test_erddap_global_attirbutes_xml(record):
- result = global_attributes(record, output="xml", language="en")
+def test_erddap_global_attributes_xml(record):
+ result = erddap.global_attributes(record, output="xml", language="en")
assert result
-@pytest.mark.parametrize("file", glob("tests/records/hakai-metadata-entry-form-files/**/*.yaml", recursive=True))
+
+@pytest.mark.parametrize(
+ "file",
+ glob("tests/records/hakai-metadata-entry-form-files/**/*.yaml", recursive=True),
+)
def test_hakai_metadata_files_to_erddap(file):
data = load(file, "yaml")
- result = global_attributes(data, output="xml", language="en")
+ result = erddap.global_attributes(data, output="xml", language="en")
assert result
-@pytest.mark.parametrize("file", glob("tests/records/hakai-metadata-entry-form-files/**/*.yaml", recursive=True))
+
+@pytest.mark.parametrize(
+ "file",
+ glob("tests/records/hakai-metadata-entry-form-files/**/*.yaml", recursive=True),
+)
def test_hakai_metadata_files_to_erddap_fr(file):
data = load(file, "yaml")
- result_fr = global_attributes(data, output="xml", language="fr")
+ result_fr = erddap.global_attributes(data, output="xml", language="fr")
+
+ assert result_fr
+
+
+def test_erddap_update_dataset_xml(record):
+ comment = "Some commented text that should remain"
+ attributes = erddap.global_attributes(record, output=None, language="en")
+ datasets = erddap.ERDDAP("tests/erddap_xmls/test_datasets.xml")
+ datasets.read()
+ dataset_xml_content = datasets.tostring()
+ assert comment in dataset_xml_content
+
+ datasets.update( "TestDataset1", attributes)
+ result = datasets.tostring()
+ assert result
+ assert isinstance(result, str)
+ assert "TestDataset1" in result
+ assert comment in result
+ for key,value in attributes.items():
+ assert key in result
+ assert not value or value in result
+
+ Path("tests/results/test_erddap_update_dataset.xml").write_text(
+ result, encoding="utf-8"
+ )
+
+ # test that output can be parsed
+ root = ET.fromstring(result)
+ assert root
+
+def test_erddap_update_datasets_d_xml(record):
+ comment = "Some commented text that should remain"
+ attributes = erddap.global_attributes(record, output=None, language="en")
+ dataset_d_files = list(Path("tests/erddap_xmls/datasets.d").glob("*.xml"))
+
+ assert dataset_d_files, "No dataset files found in test_datasets.d"
+ for file in dataset_d_files:
+ dataset= erddap.ERDDAP(file)
+ dataset.read()
+ dataset_xml_content = dataset.tostring()
+ assert comment in dataset_xml_content
+ dataset.update("TestDataset1", attributes)
+ result = dataset.tostring()
+ assert result
+ assert isinstance(result, str)
+ assert "TestDataset1" in result
+ assert comment in result
+
+ Path("tests/results/test_erddap_update_datasets_d.xml").write_text(
+ result, encoding="utf-8"
+ )
- assert result_fr
\ No newline at end of file
+ # test that output can be parsed
+ root = ET.fromstring(result)
+ assert root
\ No newline at end of file
From d4dfe178f434e35046c2edaace75c23c36b1713c Mon Sep 17 00:00:00 2001
From: Jessy Barrette <30420025+JessyBarrette@users.noreply.github.com>
Date: Tue, 16 Jul 2024 16:14:47 -0400
Subject: [PATCH 02/29] add HakaiInstitute/hakai-metadata-entry-form-files to
workflow tests
---
.github/workflows/run-tests.yaml | 6 +++++-
tests/test_erddap.py | 2 --
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml
index 0d36f04..d6edb33 100644
--- a/.github/workflows/run-tests.yaml
+++ b/.github/workflows/run-tests.yaml
@@ -17,7 +17,11 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
-
+ - name: Checkout hakai metadata files
+ uses: actions/checkout@v4
+ with:
+ repository: HakaiInstitute/hakai-metadata-entry-form-files
+ path: tests/records
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
diff --git a/tests/test_erddap.py b/tests/test_erddap.py
index 37ec926..79293e7 100644
--- a/tests/test_erddap.py
+++ b/tests/test_erddap.py
@@ -1,6 +1,4 @@
from glob import glob
-from pathlib import Path
-from xml.etree import ElementTree as ET
import pytest
From 5d897412153b94afbd67faffe39629b086f99587 Mon Sep 17 00:00:00 2001
From: Jessy Barrette <30420025+JessyBarrette@users.noreply.github.com>
Date: Tue, 16 Jul 2024 17:05:13 -0400
Subject: [PATCH 03/29] fix erddap update tests
---
hakai_metadata_conversion/erddap.py | 36 ++++++++++++++++++-----------
tests/records/test_record1.yaml | 2 +-
tests/test_erddap.py | 23 +++++++++++++++++-
3 files changed, 46 insertions(+), 15 deletions(-)
diff --git a/hakai_metadata_conversion/erddap.py b/hakai_metadata_conversion/erddap.py
index 388f4b4..8a74d33 100644
--- a/hakai_metadata_conversion/erddap.py
+++ b/hakai_metadata_conversion/erddap.py
@@ -193,8 +193,8 @@ def global_attributes(
@logger.catch(reraise=True)
-def update_dataset_id(tree, dataset_id:str, global_attributes:dict):
-
+def update_dataset_id(tree, dataset_id: str, global_attributes: dict):
+
# Retrive dataset
matching_dataset = tree.xpath(f"//dataset[@datasetID='{dataset_id}']")
if not matching_dataset:
@@ -221,6 +221,7 @@ def update_dataset_id(tree, dataset_id:str, global_attributes:dict):
return tree
+
# Function to update XML
@logger.catch(reraise=True)
def _update_xml(xml_file, dataset_id, updates, encoding="utf-8") -> str:
@@ -230,10 +231,15 @@ def _update_xml(xml_file, dataset_id, updates, encoding="utf-8") -> str:
# Write back to the same file (or use a different file name to save a new version.
return etree.tostring(tree, pretty_print=True).decode(encoding)
+
def _get_dataset_id_from_record(record, erddap_url):
- for ressource in record["metadata"]["resources"]:
+ for ressource in record["distribution"]:
if erddap_url in ressource["url"]:
- return ressource["url"].split("/")[-1]
+ return ressource["url"].split("/")[-1].replace(
+ ".html", ""
+ ), global_attributes(record, output=None)
+ return None, None
+
class ERDDAP:
def __init__(self, path) -> None:
@@ -244,7 +250,7 @@ def __init__(self, path) -> None:
def read(self):
self.tree = etree.parse(self.path)
-
+
def tostring(self, encoding="utf-8") -> str:
return etree.tostring(self.tree, pretty_print=True).decode(encoding)
@@ -254,9 +260,9 @@ def save(self, output_file=None, encoding="utf-8"):
def has_dataset_id(self, dataset_id) -> bool:
return bool(self.tree.xpath(f"//dataset[@datasetID='{dataset_id}']"))
-
- def update(self, dataset_id:str, global_attributes:dict):
-
+
+ def update(self, dataset_id: str, global_attributes: dict):
+
# Retrive dataset
matching_dataset = self.tree.xpath(f"//dataset[@datasetID='{dataset_id}']")
if not matching_dataset:
@@ -284,10 +290,10 @@ def update(self, dataset_id:str, global_attributes:dict):
return
-
def update_dataset_xml(
dataset_xml: str,
records: list,
+ erddap_url: str,
output_dir: str = None,
):
"""Update an ERDDAP dataset.xml with new global attributes."""
@@ -297,20 +303,24 @@ def update_dataset_xml(
if not erddap_files:
assert ValueError(f"No files found in {dataset_xml}")
- datasets = {_get_dataset_id_from_record(record) for record in records}
-
+ datasets = [_get_dataset_id_from_record(record, erddap_url) for record in records]
updated = []
for file in erddap_files:
erddap = ERDDAP(file)
for dataset_id, attrs in datasets:
+ if not dataset_id:
+ continue
if erddap.has_dataset_id(dataset_id):
# Update the XML
erddap.update(dataset_id, attrs)
updated += [dataset_id]
file_output = Path(output_dir) / Path(file).name if output_dir else file
+ logger.debug("Writing updated XML to {}", file_output)
erddap.save(file_output or file)
-
- if missing_datasets := datasets - set(updated):
+
+ if missing_datasets := [
+ dataset for dataset, _ in datasets if dataset not in updated
+ ]:
logger.warning(f"Dataset ID {missing_datasets} not found in {dataset_xml}.")
return updated
diff --git a/tests/records/test_record1.yaml b/tests/records/test_record1.yaml
index fa514c1..a5e674f 100644
--- a/tests/records/test_record1.yaml
+++ b/tests/records/test_record1.yaml
@@ -63,7 +63,7 @@ distribution:
verified: false
name:
en: ERDDAP Dataset
- url: https://catalogue.hakai.org/erddap/tabledap/HakaiWaterPropertiesInstrumentProfileResearch.html
+ url: https://catalogue.hakai.org/erddap/tabledap/TestDataset1.html
- name:
en: Hakai Water Properties Profile Processing and QA/QC Procedure Manual
url: https://github.com/HakaiInstitute/hakai-datasets/raw/development/datasets_documents/HakaiWaterPropertiesProfiles/Hakai_Water_Properties_Processing_and_QAQC_Procedure_20210331.pdf
diff --git a/tests/test_erddap.py b/tests/test_erddap.py
index 79293e7..aa6f756 100644
--- a/tests/test_erddap.py
+++ b/tests/test_erddap.py
@@ -60,4 +60,25 @@ def test_hakai_metadata_files_to_erddap_fr(file):
data = load(file, "yaml")
result_fr = erddap.global_attributes(data, output="xml", language="fr")
- assert result_fr
\ No newline at end of file
+ assert result_fr
+
+
+def test_erddap_dataset_xml_update(record, tmp_path):
+ erddap.update_dataset_xml(
+ "tests/erddap_xmls/test_datasets.xml",
+ [record],
+ erddap_url="https://catalogue.hakai.org/erddap",
+ output_dir=tmp_path,
+ )
+ assert (tmp_path / "test_datasets.xml").exists()
+
+
+def test_erddap_dataset_d_xml_update(record, tmp_path):
+ erddap.update_dataset_xml(
+ "tests/erddap_xmls/dataset.d/*.xml",
+ [record],
+ erddap_url="https://catalogue.hakai.org/erddap",
+ output_dir=tmp_path,
+ )
+ files = tmp_path.glob("dataset.d/*.xml")
+ assert files
From df93e788d8a77a21490dcfce6b9ca13669e23796 Mon Sep 17 00:00:00 2001
From: Jessy Barrette <30420025+JessyBarrette@users.noreply.github.com>
Date: Tue, 16 Jul 2024 17:18:57 -0400
Subject: [PATCH 04/29] drop jinja2 import
---
hakai_metadata_conversion/erddap.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/hakai_metadata_conversion/erddap.py b/hakai_metadata_conversion/erddap.py
index 8a74d33..cbad717 100644
--- a/hakai_metadata_conversion/erddap.py
+++ b/hakai_metadata_conversion/erddap.py
@@ -2,7 +2,6 @@
from pathlib import Path
import yaml
-from jinja2 import Template
from loguru import logger
from lxml import etree
From 7b99e08b1a18cfaa098b2069ca79033d7a54f41d Mon Sep 17 00:00:00 2001
From: Jessy Barrette <30420025+JessyBarrette@users.noreply.github.com>
Date: Tue, 16 Jul 2024 18:19:58 -0400
Subject: [PATCH 05/29] improve cli to handle erddap update
---
hakai_metadata_conversion/__main__.py | 20 ++++++++++++++++----
hakai_metadata_conversion/erddap.py | 25 +++++++++++++++++++++++--
pyproject.toml | 2 +-
tests/test_erddap.py | 8 ++++++++
4 files changed, 48 insertions(+), 7 deletions(-)
diff --git a/hakai_metadata_conversion/__main__.py b/hakai_metadata_conversion/__main__.py
index dfe96c1..001fc93 100644
--- a/hakai_metadata_conversion/__main__.py
+++ b/hakai_metadata_conversion/__main__.py
@@ -52,8 +52,20 @@ def convert(record, format) -> str:
else:
raise ValueError(f"Unknown output format: {format}")
+@click.group()
+@click.pass_context
+def cli(ctx):
+ """Hakai Metadata Conversion CLI.
+ Convert metadata records to different metadata formats or standards.
-@click.command()
+ Default to convert subcommand if no subcommand is provided.
+ """
+ if ctx.invoked_subcommand is None:
+ ctx.invoke(cli_main)
+
+cli.add_command(erddap.update, name="erddap-update")
+
+@cli.command(name="convert")
@click.option("--input", "-i", required=True, help="Input file.")
@click.option(
"--recursive", "-r", is_flag=True, help="Process files recursively.", default=False
@@ -100,12 +112,12 @@ def convert(record, format) -> str:
show_default=True,
)
@logger.catch(reraise=True)
-def cli_main(**kwargs):
+def cli_convert(**kwargs):
"""Convert metadata records to different metadata formats or standards."""
main(**kwargs)
-def main(
+def convert(
input,
recursive,
input_file_format,
@@ -163,4 +175,4 @@ def main(
if __name__ == "__main__":
- cli_main()
+ cli()
diff --git a/hakai_metadata_conversion/erddap.py b/hakai_metadata_conversion/erddap.py
index cbad717..c0b2fe5 100644
--- a/hakai_metadata_conversion/erddap.py
+++ b/hakai_metadata_conversion/erddap.py
@@ -1,6 +1,8 @@
from glob import glob
from pathlib import Path
+from typing import Union
+import click
import yaml
from loguru import logger
from lxml import etree
@@ -291,12 +293,17 @@ def update(self, dataset_id: str, global_attributes: dict):
def update_dataset_xml(
dataset_xml: str,
- records: list,
+ records: Union[str, list],
erddap_url: str,
output_dir: str = None,
):
"""Update an ERDDAP dataset.xml with new global attributes."""
-
+
+ # Find dataset xml
+ if isinstance(records, str):
+ record_files = glob(records, recursive=True)
+ records = [yaml.safe_load(Path(record_file).read_text()) for record_file in record_files]
+
# Find dataset xml
erddap_files = glob(dataset_xml, recursive=True)
if not erddap_files:
@@ -323,3 +330,17 @@ def update_dataset_xml(
]:
logger.warning(f"Dataset ID {missing_datasets} not found in {dataset_xml}.")
return updated
+
+
+@click.command()
+@click.option("--dataset-xml", "-d", required=True, help="ERDDAP dataset.xml file.")
+@click.option("--records", "-r", required=True, help="Metadata records.")
+@click.option("--erddap-url", "-u", required=True, help="ERDDAP base URL.")
+@click.option("--output-dir", "-o", help="Output directory.")
+def update(dataset_xml, records, erddap_url, output_dir):
+ """Update ERDDAP dataset xml with metadata records."""
+ update_dataset_xml(dataset_xml, records, erddap_url, output_dir)
+
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index c935af6..0a11fe9 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -32,4 +32,4 @@ requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.poetry.scripts]
-hakai_metadata_conversion = "hakai_metadata_conversion.__main__:cli_main"
+hakai_metadata_conversion = "hakai_metadata_conversion.__main__:cli"
\ No newline at end of file
diff --git a/tests/test_erddap.py b/tests/test_erddap.py
index aa6f756..65ad9e6 100644
--- a/tests/test_erddap.py
+++ b/tests/test_erddap.py
@@ -72,6 +72,14 @@ def test_erddap_dataset_xml_update(record, tmp_path):
)
assert (tmp_path / "test_datasets.xml").exists()
+def test_erddap_dataset_xml_update_string(tmp_path):
+ erddap.update_dataset_xml(
+ "tests/erddap_xmls/test_datasets.xml",
+ "tests/records/*.yaml",
+ erddap_url="https://catalogue.hakai.org/erddap",
+ output_dir=tmp_path,
+ )
+ assert (tmp_path / "test_datasets.xml").exists()
def test_erddap_dataset_d_xml_update(record, tmp_path):
erddap.update_dataset_xml(
From c9a4bd571368021527c84d8b7b97c1918bb3f449 Mon Sep 17 00:00:00 2001
From: Jessy Barrette <30420025+JessyBarrette@users.noreply.github.com>
Date: Tue, 16 Jul 2024 19:47:40 -0400
Subject: [PATCH 06/29] fix cli and force groups
---
hakai_metadata_conversion/__main__.py | 35 ++++++++++++---------------
tests/test_cli.py | 25 ++++++++++---------
2 files changed, 30 insertions(+), 30 deletions(-)
diff --git a/hakai_metadata_conversion/__main__.py b/hakai_metadata_conversion/__main__.py
index 001fc93..321844d 100644
--- a/hakai_metadata_conversion/__main__.py
+++ b/hakai_metadata_conversion/__main__.py
@@ -1,6 +1,7 @@
import json
from glob import glob
from pathlib import Path
+import sys
import click
import requests
@@ -39,7 +40,7 @@ def load(input, format, encoding="utf-8") -> dict:
return input_formats[format](f)
-def convert(record, format) -> str:
+def converter(record, format) -> str:
"""Run the conversion to the desired format."""
if format == "json":
return json.dumps(record, indent=2)
@@ -52,16 +53,12 @@ def convert(record, format) -> str:
else:
raise ValueError(f"Unknown output format: {format}")
-@click.group()
-@click.pass_context
-def cli(ctx):
- """Hakai Metadata Conversion CLI.
+@click.group(name="hakai-metadata-conversion")
+def cli():
+ """Hakai Metadata Conversion CLI.
Convert metadata records to different metadata formats or standards.
-
- Default to convert subcommand if no subcommand is provided.
"""
- if ctx.invoked_subcommand is None:
- ctx.invoke(cli_main)
+ pass
cli.add_command(erddap.update, name="erddap-update")
@@ -114,18 +111,18 @@ def cli(ctx):
@logger.catch(reraise=True)
def cli_convert(**kwargs):
"""Convert metadata records to different metadata formats or standards."""
- main(**kwargs)
-
+ convert(**kwargs)
+@logger.catch(reraise=True)
def convert(
input,
- recursive,
- input_file_format,
- encoding,
- output_dir,
- output_file,
- output_format,
- output_encoding,
+ output_format: str,
+ recursive:bool =False,
+ input_file_format:str="yaml",
+ encoding: str = "utf-8",
+ output_dir: str = ".",
+ output_file: str=None,
+ output_encoding: str = "utf-8",
):
"""Convert metadata records to different metadata formats or standards."""
@@ -154,7 +151,7 @@ def convert(
continue
logger.debug(f"Converting to {output_format}")
- converted_record = convert(record, output_format)
+ converted_record = converter(record, output_format)
# Generate output file path
if output_dir and not output_file:
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 85d7cd1..dc43b4b 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -3,7 +3,7 @@
import pytest
from click.testing import CliRunner
-from hakai_metadata_conversion.__main__ import cli_main
+from hakai_metadata_conversion.__main__ import cli
@pytest.fixture
@@ -12,21 +12,22 @@ def runner():
def test_cli_no_args(runner):
- result = runner.invoke(cli_main)
- assert result.exit_code == 2
- assert "Error: Missing option '--input'" in result.output
+ result = runner.invoke(cli)
+ assert result.exit_code == 0
+ assert "Usage: hakai-metadata-conversion [OPTIONS] COMMAND [ARGS]" in result.output
def test_cli_help(runner):
- result = runner.invoke(cli_main, ["--help"])
+ result = runner.invoke(cli, ["--help"])
assert result.exit_code == 0
- assert "Usage: cli-main [OPTIONS]" in result.output
+ assert "Usage: cli [OPTIONS]" in result.output
def test_cli_on_test_files(runner, tmpdir):
input_files = "tests/records/*.yaml"
n_test_files = len(glob(input_files))
args = [
+ "convert",
"--input",
input_files,
"--input-file-format",
@@ -36,14 +37,15 @@ def test_cli_on_test_files(runner, tmpdir):
"--output-dir",
str(tmpdir),
]
- result = runner.invoke(cli_main, args)
- assert result.exit_code == 0
+ result = runner.invoke(cli, args)
+ assert result.exit_code == 0, result.output
assert result.output == ""
assert len(tmpdir.listdir()) == n_test_files
def test_cli_output_file(runner, tmpdir):
args = [
+ "convert",
"--input",
"tests/records/*.yaml",
"--input-file-format",
@@ -53,7 +55,7 @@ def test_cli_output_file(runner, tmpdir):
"--output-file",
str(tmpdir / "CITATION.cff"),
]
- result = runner.invoke(cli_main, args)
+ result = runner.invoke(cli, args)
assert result.exit_code == 0
assert result.output == ""
assert len(tmpdir.listdir()) == 1
@@ -66,6 +68,7 @@ def test_cli_output_file(runner, tmpdir):
def test_cli_with_http_input(runner, tmpdir):
ouput_file = "CITATION.cff"
args = [
+ "convert",
"--input",
"https://raw.githubusercontent.com/HakaiInstitute/hakai-metadata-conversion/main/tests/records/test_record1.yaml",
"--input-file-format",
@@ -75,8 +78,8 @@ def test_cli_with_http_input(runner, tmpdir):
"--output-file",
str(tmpdir / ouput_file),
]
- result = runner.invoke(cli_main, args)
- assert result.exit_code == 0
+ result = runner.invoke(cli, args)
+ assert result.exit_code == 0, result.output
assert result.output == ""
assert len(tmpdir.listdir()) == 1
assert tmpdir.join(ouput_file).check(file=True)
From 19b72ceebc910e6dfc06674f3289428a8c7bb0eb Mon Sep 17 00:00:00 2001
From: Jessy Barrette <30420025+JessyBarrette@users.noreply.github.com>
Date: Wed, 17 Jul 2024 11:31:23 -0400
Subject: [PATCH 07/29] drop main and rename update input to match erddap
terms
---
hakai_metadata_conversion/erddap.py | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/hakai_metadata_conversion/erddap.py b/hakai_metadata_conversion/erddap.py
index c0b2fe5..d8b8156 100644
--- a/hakai_metadata_conversion/erddap.py
+++ b/hakai_metadata_conversion/erddap.py
@@ -333,7 +333,7 @@ def update_dataset_xml(
@click.command()
-@click.option("--dataset-xml", "-d", required=True, help="ERDDAP dataset.xml file.")
+@click.option("--datasets-xml", "-d", required=True, help="ERDDAP dataset.xml file.")
@click.option("--records", "-r", required=True, help="Metadata records.")
@click.option("--erddap-url", "-u", required=True, help="ERDDAP base URL.")
@click.option("--output-dir", "-o", help="Output directory.")
@@ -341,6 +341,3 @@ def update(dataset_xml, records, erddap_url, output_dir):
"""Update ERDDAP dataset xml with metadata records."""
update_dataset_xml(dataset_xml, records, erddap_url, output_dir)
-
-if __name__ == "__main__":
- main()
\ No newline at end of file
From 005bf010eb20f806e21ba6887a2f696f8fcad335 Mon Sep 17 00:00:00 2001
From: Jessy Barrette <30420025+JessyBarrette@users.noreply.github.com>
Date: Wed, 17 Jul 2024 11:32:49 -0400
Subject: [PATCH 08/29] drop empty values
---
hakai_metadata_conversion/erddap.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/hakai_metadata_conversion/erddap.py b/hakai_metadata_conversion/erddap.py
index d8b8156..26af77d 100644
--- a/hakai_metadata_conversion/erddap.py
+++ b/hakai_metadata_conversion/erddap.py
@@ -7,6 +7,8 @@
from loguru import logger
from lxml import etree
+from hakai_metadata_conversion.utils import drop_empty_values
+
KEYWORDS_PREFIX_MAPPING = {
"default": {
"prefix": "",
@@ -187,6 +189,9 @@ def global_attributes(
.get("maintenance_note", "")
.replace("Generated from ", ""),
}
+ # Remove empty values
+ global_attributes = drop_empty_values(global_attributes)
+
if not output:
return global_attributes
if output == "xml":
From 430e05b7959b0e84e2edecdae6a1327a9090332c Mon Sep 17 00:00:00 2001
From: Jessy Barrette <30420025+JessyBarrette@users.noreply.github.com>
Date: Wed, 17 Jul 2024 11:32:59 -0400
Subject: [PATCH 09/29] add institution field
---
hakai_metadata_conversion/erddap.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/hakai_metadata_conversion/erddap.py b/hakai_metadata_conversion/erddap.py
index 26af77d..e2f28d3 100644
--- a/hakai_metadata_conversion/erddap.py
+++ b/hakai_metadata_conversion/erddap.py
@@ -147,6 +147,7 @@ def global_attributes(
)
global_attributes = {
+ "institution": creator["creator_institution"] if creator else "",
"title": record["identification"]["title"][language],
"summary": record["identification"]["abstract"][language],
"project": ",".join(record["identification"].get("project", [])),
From 52ea08f1da7e1fda628127972ee1b0c252e2ef82 Mon Sep 17 00:00:00 2001
From: Jessy Barrette <30420025+JessyBarrette@users.noreply.github.com>
Date: Wed, 17 Jul 2024 11:36:23 -0400
Subject: [PATCH 10/29] fix variables name
---
hakai_metadata_conversion/erddap.py | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/hakai_metadata_conversion/erddap.py b/hakai_metadata_conversion/erddap.py
index e2f28d3..9ac37b2 100644
--- a/hakai_metadata_conversion/erddap.py
+++ b/hakai_metadata_conversion/erddap.py
@@ -192,7 +192,7 @@ def global_attributes(
}
# Remove empty values
global_attributes = drop_empty_values(global_attributes)
-
+
if not output:
return global_attributes
if output == "xml":
@@ -298,7 +298,7 @@ def update(self, dataset_id: str, global_attributes: dict):
def update_dataset_xml(
- dataset_xml: str,
+ datasets_xml: str,
records: Union[str, list],
erddap_url: str,
output_dir: str = None,
@@ -311,9 +311,9 @@ def update_dataset_xml(
records = [yaml.safe_load(Path(record_file).read_text()) for record_file in record_files]
# Find dataset xml
- erddap_files = glob(dataset_xml, recursive=True)
+ erddap_files = glob(datasets_xml, recursive=True)
if not erddap_files:
- assert ValueError(f"No files found in {dataset_xml}")
+ assert ValueError(f"No files found in {datasets_xml}")
datasets = [_get_dataset_id_from_record(record, erddap_url) for record in records]
@@ -334,7 +334,7 @@ def update_dataset_xml(
if missing_datasets := [
dataset for dataset, _ in datasets if dataset not in updated
]:
- logger.warning(f"Dataset ID {missing_datasets} not found in {dataset_xml}.")
+ logger.warning(f"Dataset ID {missing_datasets} not found in {datasets_xml}.")
return updated
@@ -343,7 +343,7 @@ def update_dataset_xml(
@click.option("--records", "-r", required=True, help="Metadata records.")
@click.option("--erddap-url", "-u", required=True, help="ERDDAP base URL.")
@click.option("--output-dir", "-o", help="Output directory.")
-def update(dataset_xml, records, erddap_url, output_dir):
+def update(datasets_xml, records, erddap_url, output_dir):
"""Update ERDDAP dataset xml with metadata records."""
- update_dataset_xml(dataset_xml, records, erddap_url, output_dir)
+ update_dataset_xml(datasets_xml, records, erddap_url, output_dir)
From 4ccd6ef2a5dc415da0749de56996155a2d7721c5 Mon Sep 17 00:00:00 2001
From: Jessy Barrette <30420025+JessyBarrette@users.noreply.github.com>
Date: Wed, 17 Jul 2024 11:41:54 -0400
Subject: [PATCH 11/29] map owner organization to erddap institution
---
hakai_metadata_conversion/erddap.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/hakai_metadata_conversion/erddap.py b/hakai_metadata_conversion/erddap.py
index 9ac37b2..9937b6b 100644
--- a/hakai_metadata_conversion/erddap.py
+++ b/hakai_metadata_conversion/erddap.py
@@ -147,7 +147,7 @@ def global_attributes(
)
global_attributes = {
- "institution": creator["creator_institution"] if creator else "",
+ "institution": creator[0].get("organization",{}).get("name") if creator else "",
"title": record["identification"]["title"][language],
"summary": record["identification"]["abstract"][language],
"project": ",".join(record["identification"].get("project", [])),
From aa26bc99c8233018b1e145d4701555e0fec8a265 Mon Sep 17 00:00:00 2001
From: Jessy Barrette <30420025+JessyBarrette@users.noreply.github.com>
Date: Wed, 17 Jul 2024 11:45:53 -0400
Subject: [PATCH 12/29] fix missing dataset logger
---
hakai_metadata_conversion/erddap.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/hakai_metadata_conversion/erddap.py b/hakai_metadata_conversion/erddap.py
index 9937b6b..4bd937a 100644
--- a/hakai_metadata_conversion/erddap.py
+++ b/hakai_metadata_conversion/erddap.py
@@ -316,7 +316,7 @@ def update_dataset_xml(
assert ValueError(f"No files found in {datasets_xml}")
datasets = [_get_dataset_id_from_record(record, erddap_url) for record in records]
-
+ dataset_ids = [dataset_id for dataset_id, _ in datasets]
updated = []
for file in erddap_files:
erddap = ERDDAP(file)
@@ -332,7 +332,7 @@ def update_dataset_xml(
erddap.save(file_output or file)
if missing_datasets := [
- dataset for dataset, _ in datasets if dataset not in updated
+ dataset_id for dataset_id in dataset_ids if dataset_id not in updated
]:
logger.warning(f"Dataset ID {missing_datasets} not found in {datasets_xml}.")
return updated
From 78bea43eea813b79e8eea68449de7d657fe80035 Mon Sep 17 00:00:00 2001
From: Jessy Barrette <30420025+JessyBarrette@users.noreply.github.com>
Date: Wed, 17 Jul 2024 11:58:41 -0400
Subject: [PATCH 13/29] fix default base_url of erddap
---
hakai_metadata_conversion/erddap.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/hakai_metadata_conversion/erddap.py b/hakai_metadata_conversion/erddap.py
index 4bd937a..125a5ac 100644
--- a/hakai_metadata_conversion/erddap.py
+++ b/hakai_metadata_conversion/erddap.py
@@ -97,7 +97,7 @@ def generate_history(record, language="en"):
def global_attributes(
- record, output="xml", language="en", base_url="https://catalogue.hakai.org"
+ record, output="xml", language="en", base_url="https://catalogue.hakai.org/"
) -> str:
"""Generate an ERDDAP dataset.xml global attributes from a metadata record
which follows the ACDD 1.3 conventions.
From a62f58be4e01833b5238cb06a48ee43c53c227c9 Mon Sep 17 00:00:00 2001
From: Jessy Barrette <30420025+JessyBarrette@users.noreply.github.com>
Date: Wed, 17 Jul 2024 12:02:16 -0400
Subject: [PATCH 14/29] fix link to record
---
hakai_metadata_conversion/erddap.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/hakai_metadata_conversion/erddap.py b/hakai_metadata_conversion/erddap.py
index 125a5ac..84e1472 100644
--- a/hakai_metadata_conversion/erddap.py
+++ b/hakai_metadata_conversion/erddap.py
@@ -97,7 +97,7 @@ def generate_history(record, language="en"):
def global_attributes(
- record, output="xml", language="en", base_url="https://catalogue.hakai.org/"
+ record, output="xml", language="en", base_url="https://catalogue.hakai.org"
) -> str:
"""Generate an ERDDAP dataset.xml global attributes from a metadata record
which follows the ACDD 1.3 conventions.
@@ -141,6 +141,7 @@ def global_attributes(
metadata_link = (
base_url
+ + "/dataset/"
+ record["metadata"]["naming_authority"].replace(".", "-")
+ "_"
+ record["metadata"]["identifier"]
From f41f895ee05f806ec2caef8503cd7d88ff2a18a5 Mon Sep 17 00:00:00 2001
From: Jessy Barrette <30420025+JessyBarrette@users.noreply.github.com>
Date: Wed, 17 Jul 2024 14:19:54 -0400
Subject: [PATCH 15/29] change erddap license to licence url
---
hakai_metadata_conversion/erddap.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/hakai_metadata_conversion/erddap.py b/hakai_metadata_conversion/erddap.py
index 84e1472..dd0bf70 100644
--- a/hakai_metadata_conversion/erddap.py
+++ b/hakai_metadata_conversion/erddap.py
@@ -180,7 +180,7 @@ def global_attributes(
"date_created": record["metadata"]["dates"].get("publication"),
"product_version": record["identification"].get("edition"),
"history": generate_history(record, language),
- "license": record["metadata"]["use_constraints"].get("licence", {}).get("code"),
+ "license": record["metadata"]["use_constraints"].get("licence", {}).get("url"),
**(_get_contact(creator[0], "creator") if creator else {}),
**(_get_contact(publisher[0], "publisher") if publisher else {}),
**_get_contributors(record["contact"]),
From 6e9eaab8ca2c4517a2e61efec292df4385df0e6f Mon Sep 17 00:00:00 2001
From: Jessy Barrette <30420025+JessyBarrette@users.noreply.github.com>
Date: Wed, 17 Jul 2024 15:35:52 -0400
Subject: [PATCH 16/29] add platform
---
hakai_metadata_conversion/erddap.py | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/hakai_metadata_conversion/erddap.py b/hakai_metadata_conversion/erddap.py
index dd0bf70..3220c44 100644
--- a/hakai_metadata_conversion/erddap.py
+++ b/hakai_metadata_conversion/erddap.py
@@ -83,6 +83,17 @@ def _get_contributors(contacts: list, separator=";") -> dict:
}
+@logger.catch(default={})
+def _get_platform(record):
+ if not record.get("platform"):
+ return {}
+ platform = record["platform"]
+ return {
+ "platform": platform[0]["type"],
+ "platform_vocabulary": "http://vocab.nerc.ac.uk/collection/L06/current/"
+ }
+
+
def generate_history(record, language="en"):
"""Generate a history string from a metadata record."""
history = record["metadata"].get("history")
@@ -190,6 +201,7 @@ def global_attributes(
"metadata_form": record["metadata"]
.get("maintenance_note", "")
.replace("Generated from ", ""),
+ **_get_platform(record),
}
# Remove empty values
global_attributes = drop_empty_values(global_attributes)
From 2c72530a9e3bf50929deb93ac2de1e251318086f Mon Sep 17 00:00:00 2001
From: Jessy Barrette <30420025+JessyBarrette@users.noreply.github.com>
Date: Wed, 17 Jul 2024 16:13:58 -0400
Subject: [PATCH 17/29] fix ruff issues
---
hakai_metadata_conversion/__main__.py | 1 -
tests/test_citation_cff.py | 5 ++---
tests/test_xml.py | 1 -
3 files changed, 2 insertions(+), 5 deletions(-)
diff --git a/hakai_metadata_conversion/__main__.py b/hakai_metadata_conversion/__main__.py
index 321844d..03f9ee0 100644
--- a/hakai_metadata_conversion/__main__.py
+++ b/hakai_metadata_conversion/__main__.py
@@ -1,7 +1,6 @@
import json
from glob import glob
from pathlib import Path
-import sys
import click
import requests
diff --git a/tests/test_citation_cff.py b/tests/test_citation_cff.py
index 8f8e6d6..3c65d38 100644
--- a/tests/test_citation_cff.py
+++ b/tests/test_citation_cff.py
@@ -1,6 +1,5 @@
import subprocess
from glob import glob
-from pathlib import Path
import pytest
@@ -55,7 +54,7 @@ def test_hakai_metadata_entry_form_files_cff(file, tmp_path):
# validate cff
(tmp_path / "CITATION.cff").write_text(result, encoding="utf-8")
- result = subprocess.run(
+ subprocess.run(
["cffconvert", "--validate", "-i", str(tmp_path / "CITATION.cff")],
capture_output=True,
)
@@ -72,7 +71,7 @@ def test_hakai_metadata_entry_form_files_cff_fr(file, tmp_path):
# validate cff
(tmp_path / "CITATION.cff").write_text(result_fr, encoding="utf-8")
- result = subprocess.run(
+ subprocess.run(
["cffconvert", "--validate", "-i", str(tmp_path / "CITATION.cff")],
capture_output=True,
)
diff --git a/tests/test_xml.py b/tests/test_xml.py
index 3759de8..11be5b1 100644
--- a/tests/test_xml.py
+++ b/tests/test_xml.py
@@ -1,6 +1,5 @@
import pytest
from glob import glob
-from pathlib import Path
from hakai_metadata_conversion.__main__ import load
from hakai_metadata_conversion.xml import xml
From 22d29cd67a65c9edb1326bec7521712a3f89dbcd Mon Sep 17 00:00:00 2001
From: Jessy Barrette <30420025+JessyBarrette@users.noreply.github.com>
Date: Wed, 17 Jul 2024 16:14:20 -0400
Subject: [PATCH 18/29] isort black
---
hakai_metadata_conversion/__main__.py | 10 +++++++---
hakai_metadata_conversion/erddap.py | 18 +++++++++++-------
hakai_metadata_conversion/xml.py | 2 --
tests/test_erddap.py | 2 ++
tests/test_xml.py | 7 ++++---
5 files changed, 24 insertions(+), 15 deletions(-)
diff --git a/hakai_metadata_conversion/__main__.py b/hakai_metadata_conversion/__main__.py
index 03f9ee0..9ec2043 100644
--- a/hakai_metadata_conversion/__main__.py
+++ b/hakai_metadata_conversion/__main__.py
@@ -52,6 +52,7 @@ def converter(record, format) -> str:
else:
raise ValueError(f"Unknown output format: {format}")
+
@click.group(name="hakai-metadata-conversion")
def cli():
"""Hakai Metadata Conversion CLI.
@@ -59,8 +60,10 @@ def cli():
"""
pass
+
cli.add_command(erddap.update, name="erddap-update")
+
@cli.command(name="convert")
@click.option("--input", "-i", required=True, help="Input file.")
@click.option(
@@ -112,15 +115,16 @@ def cli_convert(**kwargs):
"""Convert metadata records to different metadata formats or standards."""
convert(**kwargs)
+
@logger.catch(reraise=True)
def convert(
input,
output_format: str,
- recursive:bool =False,
- input_file_format:str="yaml",
+ recursive: bool = False,
+ input_file_format: str = "yaml",
encoding: str = "utf-8",
output_dir: str = ".",
- output_file: str=None,
+ output_file: str = None,
output_encoding: str = "utf-8",
):
"""Convert metadata records to different metadata formats or standards."""
diff --git a/hakai_metadata_conversion/erddap.py b/hakai_metadata_conversion/erddap.py
index 3220c44..af192d8 100644
--- a/hakai_metadata_conversion/erddap.py
+++ b/hakai_metadata_conversion/erddap.py
@@ -90,9 +90,9 @@ def _get_platform(record):
platform = record["platform"]
return {
"platform": platform[0]["type"],
- "platform_vocabulary": "http://vocab.nerc.ac.uk/collection/L06/current/"
+ "platform_vocabulary": "http://vocab.nerc.ac.uk/collection/L06/current/",
}
-
+
def generate_history(record, language="en"):
"""Generate a history string from a metadata record."""
@@ -159,7 +159,9 @@ def global_attributes(
)
global_attributes = {
- "institution": creator[0].get("organization",{}).get("name") if creator else "",
+ "institution": (
+ creator[0].get("organization", {}).get("name") if creator else ""
+ ),
"title": record["identification"]["title"][language],
"summary": record["identification"]["abstract"][language],
"project": ",".join(record["identification"].get("project", [])),
@@ -317,12 +319,15 @@ def update_dataset_xml(
output_dir: str = None,
):
"""Update an ERDDAP dataset.xml with new global attributes."""
-
+
# Find dataset xml
if isinstance(records, str):
record_files = glob(records, recursive=True)
- records = [yaml.safe_load(Path(record_file).read_text()) for record_file in record_files]
-
+ records = [
+ yaml.safe_load(Path(record_file).read_text())
+ for record_file in record_files
+ ]
+
# Find dataset xml
erddap_files = glob(datasets_xml, recursive=True)
if not erddap_files:
@@ -359,4 +364,3 @@ def update_dataset_xml(
def update(datasets_xml, records, erddap_url, output_dir):
"""Update ERDDAP dataset xml with metadata records."""
update_dataset_xml(datasets_xml, records, erddap_url, output_dir)
-
diff --git a/hakai_metadata_conversion/xml.py b/hakai_metadata_conversion/xml.py
index 8fa662f..497ca1f 100644
--- a/hakai_metadata_conversion/xml.py
+++ b/hakai_metadata_conversion/xml.py
@@ -3,5 +3,3 @@
def xml(record):
return metadata_to_xml(record)
-
-
diff --git a/tests/test_erddap.py b/tests/test_erddap.py
index 65ad9e6..c0676cf 100644
--- a/tests/test_erddap.py
+++ b/tests/test_erddap.py
@@ -72,6 +72,7 @@ def test_erddap_dataset_xml_update(record, tmp_path):
)
assert (tmp_path / "test_datasets.xml").exists()
+
def test_erddap_dataset_xml_update_string(tmp_path):
erddap.update_dataset_xml(
"tests/erddap_xmls/test_datasets.xml",
@@ -81,6 +82,7 @@ def test_erddap_dataset_xml_update_string(tmp_path):
)
assert (tmp_path / "test_datasets.xml").exists()
+
def test_erddap_dataset_d_xml_update(record, tmp_path):
erddap.update_dataset_xml(
"tests/erddap_xmls/dataset.d/*.xml",
diff --git a/tests/test_xml.py b/tests/test_xml.py
index 11be5b1..ad20aff 100644
--- a/tests/test_xml.py
+++ b/tests/test_xml.py
@@ -1,16 +1,17 @@
-import pytest
from glob import glob
+import pytest
+
from hakai_metadata_conversion.__main__ import load
from hakai_metadata_conversion.xml import xml
+
def test_xml(record):
result = xml(record)
assert result
assert isinstance(result, str)
-
@pytest.mark.parametrize(
"file",
glob("tests/records/hakai-metadata-entry-form-files/**/*.yaml", recursive=True),
@@ -18,6 +19,6 @@ def test_xml(record):
def test_hakai_records_xml(file):
record = load(file, "yaml")
result = xml(record)
-
+
assert result
assert isinstance(result, str)
From 27498e4293978f306e9ef9e6a223c72f29058d4d Mon Sep 17 00:00:00 2001
From: Jessy Barrette <30420025+JessyBarrette@users.noreply.github.com>
Date: Wed, 17 Jul 2024 16:15:34 -0400
Subject: [PATCH 19/29] fix lock file
---
poetry.lock | 116 +++++++++++-----------------------------------------
1 file changed, 23 insertions(+), 93 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 693b161..deb6d2b 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -348,97 +348,6 @@ win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
[package.extras]
dev = ["Sphinx (==7.2.5)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.4.1)", "mypy (==v1.5.1)", "pre-commit (==3.4.0)", "pytest (==6.1.2)", "pytest (==7.4.0)", "pytest-cov (==2.12.1)", "pytest-cov (==4.1.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.0.0)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.3.0)", "tox (==3.27.1)", "tox (==4.11.0)"]
-[[package]]
-name = "markupsafe"
-version = "2.1.5"
-description = "Safely add untrusted strings to HTML/XML markup."
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"},
- {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"},
- {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"},
- {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"},
- {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"},
- {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"},
- {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"},
- {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"},
- {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"},
- {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"},
- {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"},
- {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"},
- {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"},
- {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"},
- {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"},
- {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"},
- {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"},
- {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"},
- {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"},
- {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"},
- {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"},
- {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"},
- {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"},
- {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"},
- {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"},
- {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"},
- {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"},
- {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"},
- {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"},
- {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"},
- {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"},
- {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"},
- {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"},
- {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"},
- {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"},
- {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"},
- {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"},
- {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"},
- {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"},
- {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"},
- {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"},
- {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"},
- {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"},
- {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"},
- {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"},
- {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"},
- {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"},
- {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"},
- {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"},
- {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"},
- {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"},
- {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"},
- {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"},
- {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"},
- {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"},
- {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"},
- {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"},
- {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"},
- {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"},
- {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"},
-]
-
-[[package]]
-name = "metadata_xml"
-version = "0.1"
-description = "Python module for converting ACDD style metadata into the CIOOS ISO profile"
-optional = false
-python-versions = "*"
-files = []
-develop = false
-
-[package.dependencies]
-Jinja2 = ">=3.1.2"
-markupsafe = ">=2.1.1"
-PyYAML = ">=6.0.1"
-validators = "0.20.0"
-yattag = "1.14.0"
-
-[package.source]
-type = "git"
-url = "https://github.com/HakaiInstitute/metadata-xml.git"
-reference = "HEAD"
-resolved_reference = "5721dde1843075e91cab56330334ed354c8b39a5"
-
[[package]]
name = "lxml"
version = "5.2.2"
@@ -666,6 +575,28 @@ files = [
{file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"},
]
+[[package]]
+name = "metadata_xml"
+version = "0.1"
+description = "Python module for converting ACDD style metadata into the CIOOS ISO profile"
+optional = false
+python-versions = "*"
+files = []
+develop = false
+
+[package.dependencies]
+Jinja2 = ">=3.1.2"
+markupsafe = ">=2.1.1"
+PyYAML = ">=6.0.1"
+validators = "0.20.0"
+yattag = "1.14.0"
+
+[package.source]
+type = "git"
+url = "https://github.com/HakaiInstitute/metadata-xml.git"
+reference = "HEAD"
+resolved_reference = "5721dde1843075e91cab56330334ed354c8b39a5"
+
[[package]]
name = "mypy-extensions"
version = "1.0.0"
@@ -1122,5 +1053,4 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = "^3.11"
-content-hash = "7a2a58521c235723f833245eaae135d2a409a08fb8295110a00c9fae148ef4d0"
-content-hash = "0f445b1f3c5215b779a19552a347ccb5d86f30d6fbac122dbde0629f3df5a785"
+content-hash = "ca78855151930fa29d4793334982e321c21e6f2f18101b840b8de5d116433d16"
From e4e2009771bb1daae9d50601cd16ed6dfaa5d978 Mon Sep 17 00:00:00 2001
From: Jessy Barrette <30420025+JessyBarrette@users.noreply.github.com>
Date: Wed, 17 Jul 2024 17:19:54 -0400
Subject: [PATCH 20/29] fix cli help test
---
tests/test_cli.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/test_cli.py b/tests/test_cli.py
index dc43b4b..a21ae76 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -20,7 +20,7 @@ def test_cli_no_args(runner):
def test_cli_help(runner):
result = runner.invoke(cli, ["--help"])
assert result.exit_code == 0
- assert "Usage: cli [OPTIONS]" in result.output
+ assert "Usage: hakai-metadata-conversion [OPTIONS]" in result.output
def test_cli_on_test_files(runner, tmpdir):
From 27e0ea1f3979f9bd3d7eb345e42db17e2f3b9317 Mon Sep 17 00:00:00 2001
From: Jessy Barrette <30420025+JessyBarrette@users.noreply.github.com>
Date: Thu, 18 Jul 2024 08:40:29 -0400
Subject: [PATCH 21/29] fix action paths
---
.github/workflows/run-tests.yaml | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml
index d6edb33..608dab4 100644
--- a/.github/workflows/run-tests.yaml
+++ b/.github/workflows/run-tests.yaml
@@ -17,11 +17,13 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
- - name: Checkout hakai metadata files
+ with:
+ path: "."
+ - name: Checkout hakai metadata filesxz
uses: actions/checkout@v4
with:
repository: HakaiInstitute/hakai-metadata-entry-form-files
- path: tests/records
+ path: ./tests/records/hakai-metadata-entry-form-files
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
From 9d2af4004febab18396f43bf06e4a6c14c5704e3 Mon Sep 17 00:00:00 2001
From: Jessy Barrette <30420025+JessyBarrette@users.noreply.github.com>
Date: Thu, 18 Jul 2024 09:21:36 -0400
Subject: [PATCH 22/29] show unique authors, fix no doi case
---
hakai_metadata_conversion/citation_cff.py | 44 +++++++++++++----------
tests/test_citation_cff.py | 9 +++--
2 files changed, 32 insertions(+), 21 deletions(-)
diff --git a/hakai_metadata_conversion/citation_cff.py b/hakai_metadata_conversion/citation_cff.py
index 024a424..883d782 100644
--- a/hakai_metadata_conversion/citation_cff.py
+++ b/hakai_metadata_conversion/citation_cff.py
@@ -71,6 +71,27 @@ def get_cff_contact(contact):
)
+def _get_doi(record):
+ if not record["identification"].get("identifier", ""):
+ return []
+ return [
+ {
+ "description": "DOI",
+ "type": "doi",
+ "value": record["identification"]["identifier"].replace("https://doi.org/", "")
+ if "doi.org" in record["identification"].get("identifier", "")
+ else None,
+ }
+ ]
+
+def _get_unique_authors(record):
+ authors = []
+ for author in record["contact"]:
+ contact = get_cff_contact(author)
+ if contact not in authors:
+ authors.append(contact)
+ return authors
+
def citation_cff(
record,
output_format="yaml",
@@ -90,14 +111,11 @@ def citation_cff(
+ "_"
+ record["metadata"]["identifier"]
)
+
record = {
"cff-version": "1.2.0",
"message": message,
- "authors": [
- get_cff_contact(contact)
- for contact in record["contact"]
- if contact["inCitation"]
- ],
+ "authors": _get_unique_authors(record),
"title": record["identification"]["title"].get(language),
"abstract": record["identification"]["abstract"].get(language),
"date-released": record["metadata"]["dates"]["revision"].split("T")[0],
@@ -117,17 +135,7 @@ def citation_cff(
"type": "url",
"value": resource_url,
},
- {
- "description": "Hakai Metadata record DOI",
- "type": "doi",
- "value": (
- record["identification"]["identifier"].replace(
- "https://doi.org/", ""
- )
- if "doi.org" in record["identification"].get("identifier", "")
- else None
- ),
- },
+ *_get_doi(record),
{
"description": "Hakai Metadata Form used to generate this record",
"type": "url",
@@ -154,11 +162,11 @@ def citation_cff(
for distribution in record["distribution"]
],
],
- "keywords": [
+ "keywords": list(set([
keyword
for _, group in record["identification"]["keywords"].items()
for keyword in group.get(language, [])
- ],
+ ])),
"license": record["metadata"]["use_constraints"].get("licence", {}).get("code"),
"license-url": record["metadata"]["use_constraints"]
.get("licence", {})
diff --git a/tests/test_citation_cff.py b/tests/test_citation_cff.py
index 105375d..d9da9ed 100644
--- a/tests/test_citation_cff.py
+++ b/tests/test_citation_cff.py
@@ -55,10 +55,12 @@ def test_hakai_metadata_entry_form_files_cff(file, tmp_path):
# validate cff
(tmp_path / "CITATION.cff").write_text(result, encoding="utf-8")
- result = subprocess.run(
- ["cffconvert", "--validate", "-i", str(tmp_path / "CITATION.cff")],
+ validation_result = subprocess.run(
+ ["cffconvert", "--validate", "-i", str(tmp_path / "CITATION.cff"), "--show-trace"],
capture_output=True,
)
+ assert validation_result.returncode == 0, validation_result.stderr.decode("utf-8")
+
@pytest.mark.parametrize(
@@ -72,7 +74,8 @@ def test_hakai_metadata_entry_form_files_cff_fr(file, tmp_path):
# validate cff
(tmp_path / "CITATION.cff").write_text(result_fr, encoding="utf-8")
- result = subprocess.run(
+ validation_result = subprocess.run(
["cffconvert", "--validate", "-i", str(tmp_path / "CITATION.cff")],
capture_output=True,
)
+ assert validation_result.returncode == 0, validation_result.stderr.decode("utf-8")
\ No newline at end of file
From 543a1ab4644033f8e94049efec7af59974024176 Mon Sep 17 00:00:00 2001
From: Jessy Barrette <30420025+JessyBarrette@users.noreply.github.com>
Date: Thu, 18 Jul 2024 10:48:30 -0400
Subject: [PATCH 23/29] drop show_trace
---
tests/test_citation_cff.py | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/tests/test_citation_cff.py b/tests/test_citation_cff.py
index 8298791..fddd37a 100644
--- a/tests/test_citation_cff.py
+++ b/tests/test_citation_cff.py
@@ -55,13 +55,12 @@ def test_hakai_metadata_entry_form_files_cff(file, tmp_path):
# validate cff
(tmp_path / "CITATION.cff").write_text(result, encoding="utf-8")
validation_result = subprocess.run(
- ["cffconvert", "--validate", "-i", str(tmp_path / "CITATION.cff"), "--show-trace"],
+ ["cffconvert", "--validate", "-i", str(tmp_path / "CITATION.cff")],
capture_output=True,
)
assert validation_result.returncode == 0, validation_result.stderr.decode("utf-8")
-
@pytest.mark.parametrize(
"file",
glob("tests/records/hakai-metadata-entry-form-files/**/*.yaml", recursive=True),
@@ -77,4 +76,4 @@ def test_hakai_metadata_entry_form_files_cff_fr(file, tmp_path):
["cffconvert", "--validate", "-i", str(tmp_path / "CITATION.cff")],
capture_output=True,
)
- assert validation_result.returncode == 0, validation_result.stderr.decode("utf-8")
\ No newline at end of file
+ assert validation_result.returncode == 0, validation_result.stderr.decode("utf-8")
From bf6172ed9a40548830de59fbe50ac82318c57a2d Mon Sep 17 00:00:00 2001
From: Jessy Barrette <30420025+JessyBarrette@users.noreply.github.com>
Date: Thu, 18 Jul 2024 10:49:11 -0400
Subject: [PATCH 24/29] ignore identifier urls that are not urls (do not start
with http)
---
hakai_metadata_conversion/citation_cff.py | 72 ++++++++++++++---------
1 file changed, 44 insertions(+), 28 deletions(-)
diff --git a/hakai_metadata_conversion/citation_cff.py b/hakai_metadata_conversion/citation_cff.py
index 883d782..bcecbba 100644
--- a/hakai_metadata_conversion/citation_cff.py
+++ b/hakai_metadata_conversion/citation_cff.py
@@ -78,12 +78,40 @@ def _get_doi(record):
{
"description": "DOI",
"type": "doi",
- "value": record["identification"]["identifier"].replace("https://doi.org/", "")
- if "doi.org" in record["identification"].get("identifier", "")
- else None,
+ "value": (
+ record["identification"]["identifier"].replace("https://doi.org/", "")
+ if "doi.org" in record["identification"].get("identifier", "")
+ else None
+ ),
}
]
+
+def _get_ressources(record, language):
+ ressources = []
+ for distribution in record["distribution"]:
+ if not distribution["url"].startswith("http"):
+ logger.warning(f"Invalid URL: {distribution['url']}")
+ continue
+ ressources.append(
+ {
+ "description": ": ".join(
+ [
+ item
+ for item in [
+ distribution.get("name", {}).get(language, ""),
+ distribution.get("description", {}).get(language),
+ ]
+ if item
+ ]
+ ),
+ "type": "url",
+ "value": distribution["url"],
+ }
+ )
+ return ressources
+
+
def _get_unique_authors(record):
authors = []
for author in record["contact"]:
@@ -92,6 +120,7 @@ def _get_unique_authors(record):
authors.append(contact)
return authors
+
def citation_cff(
record,
output_format="yaml",
@@ -131,42 +160,29 @@ def citation_cff(
"value": record["metadata"]["identifier"],
},
{
- "description": "Hakai Metadata record URL",
+ "description": "Metadata record URL",
"type": "url",
"value": resource_url,
},
*_get_doi(record),
{
- "description": "Hakai Metadata Form used to generate this record",
+ "description": "Metadata Form used to generate this record",
"type": "url",
"value": record["metadata"]["maintenance_note"].replace(
"Generated from ", ""
),
},
- # Generate ressources links
- *[
- {
- "description": ": ".join(
- [
- item
- for item in [
- distribution.get("name", {}).get(language, ""),
- distribution.get("description", {}).get(language),
- ]
- if item
- ]
- ),
- "type": "url",
- "value": distribution["url"],
- }
- for distribution in record["distribution"]
- ],
+ *_get_ressources(record, language=language),
],
- "keywords": list(set([
- keyword
- for _, group in record["identification"]["keywords"].items()
- for keyword in group.get(language, [])
- ])),
+ "keywords": list(
+ set(
+ [
+ keyword
+ for _, group in record["identification"]["keywords"].items()
+ for keyword in group.get(language, [])
+ ]
+ )
+ ),
"license": record["metadata"]["use_constraints"].get("licence", {}).get("code"),
"license-url": record["metadata"]["use_constraints"]
.get("licence", {})
From 700437dfa1c032bf2fd54bc0880b5c8760bee2c8 Mon Sep 17 00:00:00 2001
From: Jessy Barrette <30420025+JessyBarrette@users.noreply.github.com>
Date: Thu, 18 Jul 2024 10:55:40 -0400
Subject: [PATCH 25/29] fix french test
---
tests/test_citation_cff.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/test_citation_cff.py b/tests/test_citation_cff.py
index fddd37a..a8f5da0 100644
--- a/tests/test_citation_cff.py
+++ b/tests/test_citation_cff.py
@@ -72,7 +72,7 @@ def test_hakai_metadata_entry_form_files_cff_fr(file, tmp_path):
# validate cff
(tmp_path / "CITATION.cff").write_text(result_fr, encoding="utf-8")
- result = subprocess.run(
+ validation_result = subprocess.run(
["cffconvert", "--validate", "-i", str(tmp_path / "CITATION.cff")],
capture_output=True,
)
From 99819314438ccd922575abb4b7a70703ea43e15b Mon Sep 17 00:00:00 2001
From: Jessy Barrette <30420025+JessyBarrette@users.noreply.github.com>
Date: Thu, 18 Jul 2024 10:55:51 -0400
Subject: [PATCH 26/29] add placeholder for missing terms
---
hakai_metadata_conversion/citation_cff.py | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/hakai_metadata_conversion/citation_cff.py b/hakai_metadata_conversion/citation_cff.py
index bcecbba..4f4e1a7 100644
--- a/hakai_metadata_conversion/citation_cff.py
+++ b/hakai_metadata_conversion/citation_cff.py
@@ -10,6 +10,15 @@
from hakai_metadata_conversion.utils import drop_empty_values
+def _get_placeholder(language):
+ if language == "en":
+ return "Not available"
+ elif language == "fr":
+ return "Non disponible"
+ else:
+ return "Not available"
+
+
def _get_country_code(country_name):
if not country_name:
return None
@@ -100,7 +109,9 @@ def _get_ressources(record, language):
item
for item in [
distribution.get("name", {}).get(language, ""),
- distribution.get("description", {}).get(language),
+ distribution.get("description", {}).get(
+ language, _get_placeholder(language)
+ ),
]
if item
]
From ce3d19010b275bd9e17d3ce46880957eca383ed0 Mon Sep 17 00:00:00 2001
From: Jessy Barrette <30420025+JessyBarrette@users.noreply.github.com>
Date: Thu, 18 Jul 2024 11:00:43 -0400
Subject: [PATCH 27/29] fix action
---
action.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/action.yaml b/action.yaml
index 14e481b..399a587 100644
--- a/action.yaml
+++ b/action.yaml
@@ -67,7 +67,7 @@ runs:
- name: Run conversion
shell: bash
run: |
- hakai_metadata_conversion \
+ hakai_metadata_conversion convert \
--input ${{ inputs.input }} \
--output-dir ${{ inputs.output-dir }} \
--output-file ${{ inputs.output-file }} \
From 9f78d676c15fe1ab5c34e18d24eb01ed8144328e Mon Sep 17 00:00:00 2001
From: Jessy Barrette <30420025+JessyBarrette@users.noreply.github.com>
Date: Thu, 18 Jul 2024 11:13:08 -0400
Subject: [PATCH 28/29] test action only on main branch
---
.github/workflows/run-tests.yaml | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml
index 608dab4..4f5827b 100644
--- a/.github/workflows/run-tests.yaml
+++ b/.github/workflows/run-tests.yaml
@@ -40,8 +40,9 @@ jobs:
- name: Run pytest
run: poetry run pytest
- - name: Convert test record to CITATION.cff
+ - name: Convert test record to CITATION.cff if main branch
uses: hakaiinstitute/hakai-metadata-conversion@main
+ if: github.ref == 'refs/heads/main'
with:
input: tests/records/test_record1.json
output-file: CITATION.cff
From ff565390b423b84185713db39ffda870ca744dae Mon Sep 17 00:00:00 2001
From: Jessy Barrette <30420025+JessyBarrette@users.noreply.github.com>
Date: Thu, 18 Jul 2024 11:38:27 -0400
Subject: [PATCH 29/29] Update README.md
fix typo in readme
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 8bb3535..9da52e0 100644
--- a/README.md
+++ b/README.md
@@ -58,7 +58,7 @@ job:
step:
- use: action/checkout@v4
- name: Sync metadata
- use: hakaiinsitute/hakai-metadata-conversion
+ use: hakaiinstitute/hakai-metadata-conversion
with:
- input: url or file path within repo
- output-file: CITATION.cff