From d375a1bb17c094d0133d5bed42427a3540ea3d42 Mon Sep 17 00:00:00 2001 From: Matthew Evans Date: Wed, 29 Nov 2023 19:40:37 +0000 Subject: [PATCH 01/13] Make ID column selection in csv files more simple --- src/mc_optimade/mc_optimade/parsers.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/mc_optimade/mc_optimade/parsers.py b/src/mc_optimade/mc_optimade/parsers.py index 7696acb..59e3655 100644 --- a/src/mc_optimade/mc_optimade/parsers.py +++ b/src/mc_optimade/mc_optimade/parsers.py @@ -17,11 +17,11 @@ def pybtex_to_optimade(bib_entry: Any, properties=None) -> EntryResource: raise NotImplementedError -def load_csv_file(p: Path) -> dict[str, dict[str, Any]]: +def load_csv_file(p: Path, id_key: str = "id") -> dict[str, dict[str, Any]]: """Parses a CSV file found at path `p` and returns a dictionary of properties keyed by ID. - Requires the `id` column to be present in the CSV file, which will + Will use the first column that contains the substring "id", which will be matched with the generated IDs. Returns: @@ -30,10 +30,16 @@ def load_csv_file(p: Path) -> dict[str, dict[str, Any]]: """ df = pandas.read_csv(p) if "id" not in df: - raise RuntimeError( - "CSV file {p} must have an 'id' column: not just {df.columns}" - ) - + id_keys = [f for f in df.columns if "id" in f.lower()] + if not id_keys: + raise RuntimeError( + f"CSV file {p} must have a column containing 'id' : not just {df.columns}" + ) + id_key = id_keys[0] + + # Copy found ID key and rename it to 'id' + if id_key != "id": + df["id"] = df[id_key] df = df.set_index("id") return df.to_dict(orient="index") From 51517286223f09bb43e7afe0392c6b0d72396e6b Mon Sep 17 00:00:00 2001 From: Matthew Evans Date: Wed, 29 Nov 2023 19:40:45 +0000 Subject: [PATCH 02/13] Add simple CLI --- src/mc_optimade/mc_optimade/cli.py | 9 +++++++++ src/mc_optimade/pyproject.toml | 3 +++ 2 files changed, 12 insertions(+) create mode 100644 src/mc_optimade/mc_optimade/cli.py diff --git a/src/mc_optimade/mc_optimade/cli.py b/src/mc_optimade/mc_optimade/cli.py new file mode 100644 index 0000000..df34963 --- /dev/null +++ b/src/mc_optimade/mc_optimade/cli.py @@ -0,0 +1,9 @@ +import argparse +from pathlib import Path +from mc_optimade.convert import convert_archive + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("archive_path", help="The path to the archive to ingest.") + args = parser.parse_args() + convert_archive(Path(args.archive_path)) diff --git a/src/mc_optimade/pyproject.toml b/src/mc_optimade/pyproject.toml index ec64e5d..8765cf0 100644 --- a/src/mc_optimade/pyproject.toml +++ b/src/mc_optimade/pyproject.toml @@ -35,3 +35,6 @@ follow_imports = "skip" [tool.isort] known_first_party = "mc_optimade" profile = "black" + +[project.scripts] +optimake = "mc_optimade.cli:main" From 058b9e02be34663cafbcfe5dc20954dfc60309d4 Mon Sep 17 00:00:00 2001 From: Matthew Evans Date: Thu, 30 Nov 2023 16:09:28 +0000 Subject: [PATCH 03/13] Allow IDs to match only on filename and not on full path --- src/mc_optimade/mc_optimade/convert.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/mc_optimade/mc_optimade/convert.py b/src/mc_optimade/mc_optimade/convert.py index 1585eac..ad4fc4d 100644 --- a/src/mc_optimade/mc_optimade/convert.py +++ b/src/mc_optimade/mc_optimade/convert.py @@ -300,16 +300,21 @@ def _parse_and_assign_properties( f"Found {all_property_fields=} in data but {expected_property_fields} in config" ) - for id in parsed_properties: - if id not in optimade_entries: - raise RuntimeError( - f"Found {id=} in properties but not in entries {optimade_entries.keys()=}" - ) + # Look for precisely matching IDs, or 'filename' matches + for id in optimade_entries: + + property_entry_id = id + if id not in parsed_properties: + property_entry_id = id.split("/")[-1].split(".")[0] + if property_entry_id not in parsed_properties: + raise RuntimeError( + f"Found {id=}/{property_entry_id=} in properties but not in entries {optimade_entries.keys()=}" + ) for property in all_property_fields: # Loop over all defined properties and assign them to the entry, setting to None if missing # Also cast types if provided - value = parsed_properties[id].get(property, None) + value = parsed_properties[property_entry_id].get(property, None) if property not in property_def_dict: warnings.warn(f"Missing property definition for {property=}") continue From 8bf3b28ca2888c498e40761d26ccba7cabf5ee5e Mon Sep 17 00:00:00 2001 From: Matthew Evans Date: Fri, 1 Dec 2023 12:04:25 +0000 Subject: [PATCH 04/13] Also allow aliases in CSV parser --- src/mc_optimade/mc_optimade/convert.py | 2 +- src/mc_optimade/mc_optimade/parsers.py | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/mc_optimade/mc_optimade/convert.py b/src/mc_optimade/mc_optimade/convert.py index ad4fc4d..708676c 100644 --- a/src/mc_optimade/mc_optimade/convert.py +++ b/src/mc_optimade/mc_optimade/convert.py @@ -276,7 +276,7 @@ def _parse_and_assign_properties( file_ext = _path.suffix for parser in PROPERTY_PARSERS[file_ext]: try: - properties = parser(_path) + properties = parser(_path, property_definitions) for id in properties: parsed_properties[id].update(properties[id]) all_property_fields |= set(properties[id].keys()) diff --git a/src/mc_optimade/mc_optimade/parsers.py b/src/mc_optimade/mc_optimade/parsers.py index 59e3655..d78f35b 100644 --- a/src/mc_optimade/mc_optimade/parsers.py +++ b/src/mc_optimade/mc_optimade/parsers.py @@ -17,13 +17,20 @@ def pybtex_to_optimade(bib_entry: Any, properties=None) -> EntryResource: raise NotImplementedError -def load_csv_file(p: Path, id_key: str = "id") -> dict[str, dict[str, Any]]: +def load_csv_file( + p: Path, + properties: list[PropertyDefinition] | None = None, +) -> dict[str, dict[str, Any]]: """Parses a CSV file found at path `p` and returns a dictionary of properties keyed by ID. Will use the first column that contains the substring "id", which will be matched with the generated IDs. + Parameters: + p: Path to the CSV file. + properties: List of property definitions to extract from the CSV file. + Returns: A dictionary of ID -> properties. @@ -42,6 +49,14 @@ def load_csv_file(p: Path, id_key: str = "id") -> dict[str, dict[str, Any]]: df["id"] = df[id_key] df = df.set_index("id") + for prop in properties or []: + # loop through any property aliases, saving the value if found and only checking + # the real name if not + for alias in prop.aliases or []: + if alias in df: + df[prop.name] = df[alias] + break + return df.to_dict(orient="index") From a9b66ee9162b1b3bee374be10616ac74cc9636c5 Mon Sep 17 00:00:00 2001 From: Matthew Evans Date: Fri, 1 Dec 2023 13:41:17 +0000 Subject: [PATCH 05/13] Fix ID key reversion --- src/mc_optimade/mc_optimade/parsers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mc_optimade/mc_optimade/parsers.py b/src/mc_optimade/mc_optimade/parsers.py index d78f35b..c3a6709 100644 --- a/src/mc_optimade/mc_optimade/parsers.py +++ b/src/mc_optimade/mc_optimade/parsers.py @@ -36,6 +36,7 @@ def load_csv_file( """ df = pandas.read_csv(p) + id_key = "id" if "id" not in df: id_keys = [f for f in df.columns if "id" in f.lower()] if not id_keys: From cbc00bb7bfb78d10fd6668dea4810772772c7e8c Mon Sep 17 00:00:00 2001 From: Matthew Evans Date: Fri, 1 Dec 2023 14:54:56 +0000 Subject: [PATCH 06/13] Better handling when no property parsing is required --- src/mc_optimade/mc_optimade/convert.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/mc_optimade/mc_optimade/convert.py b/src/mc_optimade/mc_optimade/convert.py index 708676c..45379c0 100644 --- a/src/mc_optimade/mc_optimade/convert.py +++ b/src/mc_optimade/mc_optimade/convert.py @@ -268,6 +268,10 @@ def _parse_and_assign_properties( parsed_properties: dict[str, dict[str, Any]] = defaultdict(dict) errors = [] all_property_fields: set[str] = set() + + if not property_matches_by_file: + return + for archive_file in property_matches_by_file: for _path in tqdm.tqdm( property_matches_by_file[archive_file], @@ -289,6 +293,11 @@ def _parse_and_assign_properties( f"Could not parse properties file {_path} with any of the provided parsers {PROPERTY_PARSERS[file_ext]}. Errors: {errors}" ) + if not parsed_properties: + raise RuntimeError( + f"Could not parse properties files with any of the provided parsers. Errors: {errors}" + ) + # Match properties up to the descrptions provided in the config property_def_dict: dict[str, PropertyDefinition] = { p.name: p for p in property_definitions @@ -308,7 +317,7 @@ def _parse_and_assign_properties( property_entry_id = id.split("/")[-1].split(".")[0] if property_entry_id not in parsed_properties: raise RuntimeError( - f"Found {id=}/{property_entry_id=} in properties but not in entries {optimade_entries.keys()=}" + f"Found {id!r} or {property_entry_id!r} in entries but not in properties {parsed_properties.keys()=}" ) for property in all_property_fields: From e9c207c00f61ec63d2e987eb92be75d3072ce462 Mon Sep 17 00:00:00 2001 From: Matthew Evans Date: Tue, 5 Dec 2023 23:08:34 +0000 Subject: [PATCH 07/13] Make config strict to extra fields --- src/mc_optimade/mc_optimade/config.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/mc_optimade/mc_optimade/config.py b/src/mc_optimade/mc_optimade/config.py index 73bab0b..ba399a6 100644 --- a/src/mc_optimade/mc_optimade/config.py +++ b/src/mc_optimade/mc_optimade/config.py @@ -46,6 +46,9 @@ class PropertyDefinition(BaseModel): description="A list of aliases to also search for for this property; `name` will be used for the field in the actual OPTIMADE API." ) + class Config: + extra = "forbid" + class ParsedFiles(BaseModel): file: str = Field( @@ -57,6 +60,9 @@ class ParsedFiles(BaseModel): examples=[["structures/*.cif", "relaxed-structures/1.cif"]], ) + class Config: + extra = "forbid" + class EntryConfig(BaseModel): entry_type: str = Field( @@ -86,6 +92,9 @@ def check_optimade_entry_type(cls, v): return v + class Config: + extra = "forbid" + class JSONLConfig(BaseModel): """A description of a single JSON lines file that describes @@ -101,6 +110,9 @@ class JSONLConfig(BaseModel): description="The path of the JSON-L file within the archive (or directly in the entry, if `archive_file` is `None`)." ) + class Config: + extra = "forbid" + class Config(BaseModel): """This class describes the `optimade.yaml` file @@ -144,3 +156,6 @@ def validate_config_version(cls, values): if values.get("config_version") is None: raise UnsupportedConfigVersion(f"Config version must be {__version__}.") return values + + class Config: + extra = "forbid" From 2c42dd7b6f7a194feadb4193132180aa5c39e8a3 Mon Sep 17 00:00:00 2001 From: Matthew Evans Date: Wed, 6 Dec 2023 14:03:07 +0000 Subject: [PATCH 08/13] Fix typo in config field name --- src/mc_optimade/mc_optimade/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mc_optimade/mc_optimade/config.py b/src/mc_optimade/mc_optimade/config.py index ba399a6..5fcbb11 100644 --- a/src/mc_optimade/mc_optimade/config.py +++ b/src/mc_optimade/mc_optimade/config.py @@ -39,7 +39,7 @@ class PropertyDefinition(BaseModel): type: Optional[str] = Field( description="The OPTIMADE type of the property, e.g., `float` or `string`." ) - mapsto: Optional[str] = Field( + maps_to: Optional[str] = Field( description="A URI/URN for a canonical definition of the property, within the OPTIMADE extended format. Where possible, this should be a versioned URI." ) aliases: Optional[list[str]] = Field( From bcd08a780a7c2bc754975904fe818885558b06a5 Mon Sep 17 00:00:00 2001 From: Matthew Evans Date: Wed, 6 Dec 2023 14:08:56 +0000 Subject: [PATCH 09/13] Add help string for CLI --- src/mc_optimade/mc_optimade/cli.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/mc_optimade/mc_optimade/cli.py b/src/mc_optimade/mc_optimade/cli.py index df34963..b549a22 100644 --- a/src/mc_optimade/mc_optimade/cli.py +++ b/src/mc_optimade/mc_optimade/cli.py @@ -3,7 +3,10 @@ from mc_optimade.convert import convert_archive def main(): - parser = argparse.ArgumentParser() + parser = argparse.ArgumentParser( + prog="optimake", + description="Use an `optimade.yaml` config to describe archived data and create a OPTIMADE JSONL file for ingestion as an OPTIMADE API." + ) parser.add_argument("archive_path", help="The path to the archive to ingest.") args = parser.parse_args() convert_archive(Path(args.archive_path)) From 331e3921977b729f8bdfb318fa779d2ccb8fbfb5 Mon Sep 17 00:00:00 2001 From: Matthew Evans Date: Wed, 6 Dec 2023 14:15:47 +0000 Subject: [PATCH 10/13] Allow user specification of JSONL path --- src/mc_optimade/mc_optimade/cli.py | 9 ++++++++- src/mc_optimade/mc_optimade/convert.py | 23 ++++++++++++++++++++--- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/mc_optimade/mc_optimade/cli.py b/src/mc_optimade/mc_optimade/cli.py index b549a22..1022840 100644 --- a/src/mc_optimade/mc_optimade/cli.py +++ b/src/mc_optimade/mc_optimade/cli.py @@ -8,5 +8,12 @@ def main(): description="Use an `optimade.yaml` config to describe archived data and create a OPTIMADE JSONL file for ingestion as an OPTIMADE API." ) parser.add_argument("archive_path", help="The path to the archive to ingest.") + parser.add_argument("--jsonl-path", help="The path to write the JSONL file to.") args = parser.parse_args() - convert_archive(Path(args.archive_path)) + jsonl_path = args.jsonl_path + if jsonl_path: + jsonl_path = Path(jsonl_path) + if jsonl_path.exists(): + raise FileExistsError(f"File already exists at {jsonl_path}.") + + convert_archive(Path(args.archive_path), jsonl_path=jsonl_path) diff --git a/src/mc_optimade/mc_optimade/convert.py b/src/mc_optimade/mc_optimade/convert.py index 45379c0..2f605fa 100644 --- a/src/mc_optimade/mc_optimade/convert.py +++ b/src/mc_optimade/mc_optimade/convert.py @@ -54,12 +54,18 @@ def _construct_entry_type_info( return EntryInfoResource(**info) -def convert_archive(archive_path: Path) -> Path: +def convert_archive(archive_path: Path, jsonl_path: Path | None = None) -> Path: """Convert an MCloud entry to an OPTIMADE JSONL file. + Parameters: + archive_path: The location of the `optimade.yaml` file to convert. + jsonl_path: The location to write the JSONL file to. If not provided, + write to `/optimade.jsonl`. + Raises: FileNotFoundError: If any of the data paths in the config file, or config file itself, do not exist. + FileExistsError: If the JSONL file already exists at the provided path. """ @@ -102,6 +108,7 @@ def convert_archive(archive_path: Path) -> Path: optimade_entries, property_definitions, PROVIDER_PREFIX, + jsonl_path, ) return jsonl_path @@ -423,21 +430,31 @@ def write_optimade_jsonl( optimade_entries: dict[str, list[EntryResource]], property_definitions: dict[str, list[PropertyDefinition]], provider_prefix: str, + jsonl_path: Path | None = None, ) -> Path: """Write OPTIMADE entries to a JSONL file. + Parameters: + archive_path: Path to the archive. + optimade_entries: OPTIMADE entries to write. + property_definitions: Property definitions to write. + provider_prefix: Prefix to use for the provider. + jsonl_path: Path to write the JSONL file to. If not provided, + will write to `/optimade.jsonl`. + Raises: RuntimeError: If the JSONL file already exists. """ import json - jsonl_path = archive_path / "optimade.jsonl" + if not jsonl_path: + jsonl_path = archive_path / "optimade.jsonl" if jsonl_path.exists(): raise RuntimeError(f"Not overwriting existing file at {jsonl_path}") - with open(archive_path / "optimade.jsonl", "a") as jsonl: + with open(jsonl_path, "a") as jsonl: # write the optimade jsonl header header = {"x-optimade": {"meta": {"api_version": "1.1.0"}}} jsonl.write(json.dumps(header)) From 5d4ae1251441ec131e72c786666620d976f5598a Mon Sep 17 00:00:00 2001 From: Matthew Evans Date: Wed, 6 Dec 2023 14:17:00 +0000 Subject: [PATCH 11/13] Add test snippet for custom jsonl path --- src/mc_optimade/tests/test_convert.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mc_optimade/tests/test_convert.py b/src/mc_optimade/tests/test_convert.py index 209ebc3..3ddec23 100644 --- a/src/mc_optimade/tests/test_convert.py +++ b/src/mc_optimade/tests/test_convert.py @@ -25,6 +25,9 @@ def test_convert_example_archives(archive_path, tmp_path): jsonl_path = convert_archive(tmp_path) assert jsonl_path.exists() + + jsonl_path_custom = convert_archive(tmp_path, jsonl_path=tmp_path / "test.jsonl") + assert jsonl_path_custom.exists() first_entry_path = archive_path / ".testing" / "first_entry.json" first_entry = None From 93eee45bb14733add03fe64cc92ef48e736be04a Mon Sep 17 00:00:00 2001 From: Matthew Evans Date: Sat, 9 Dec 2023 19:30:38 +0000 Subject: [PATCH 12/13] Complexify CIF example to tests aliases and new ID format --- .../examples/folder_of_cifs/data.tar.gz | Bin 1518 -> 1531 bytes .../examples/folder_of_cifs/optimade.yaml | 2 ++ 2 files changed, 2 insertions(+) diff --git a/src/mc_optimade/examples/folder_of_cifs/data.tar.gz b/src/mc_optimade/examples/folder_of_cifs/data.tar.gz index 1cc43c033b046f629a66eb7254921447bbda654c..6e4c14f678afe16af30e161612297db85d04264d 100644 GIT binary patch literal 1531 zcmViqD>ZLK(zZ<2=4v8LSJ9qIvuRqTMZB#`~rPyW0G*x_QxD>4qtZuLQ za`)roF6A@5o2E4FC9_@#vZL|y?X<0II%k!iATvHlD z5>Mk021y(ygLn{5VTO-AiYkdSns&gu_Fku4%!!R+a{C`EG`ly(a2QV+?L#>2!XQkB zFvA$VES$0wW?65L(kyW?L{${6?_Hz^#E1TWxc0xd|2wY#Z_ocuzn3=s|1E(1&jeHR zRWMd^$&^`cMwMHGzp^iaM=2qjub8t`A>tm9mPL#|9x+`{#R_K z+fzP$pcwb`f6|M3?*32FtkX?=|3^*#e+&Ey%5Yk+n~}(v(oA%sP875VG29f4JS8`0 zIU_eWug7Q4Pe%ubWU^#UIMZY8pjDaXT_Qo1Vlzgs^m-< z8IU)eEM@!I#Xf}4wLvffk$M{g6r z&q}+cQ>GRx+`daW56?Mub{3C4WA=D2?@af8C!yy0^Jf@x-X z5aB5(D&5M2=8R`^n>HjDb(k|KxLOC~C6h~8_-viYoGBNm1nQV`F0_&O=7AJYWUd}7 z2Cf>mmX0W;z@t7TS5;vjuVkgr*qZ#wgv_Kf$|Sc;jB~d$h(~Lp`zgG`D{7 zOI?X!6^B9-JFxB*UHe}O?nV0_Xs-Rg7oE@cH5LId*HUX%6yOt^%PPx3tpaiaB@}hG z3oaODdu-1vm?tZRgIE?=t%+eX5Pp{jeEW#O;F@RN`ar?^0=HB^m2n^H5Vts~3bray zMvl;^8CR(7k&h(GUh&(MO^T5vXw7{|S`QpgL1Yk80Ss7to%N(0@|jZvL|-t+qhX+ z+O}6@nXfdb`U##+D&NfXZE!{VlLT hg9Z&6G-%MEL4yVj8Z>CopuvxdzX3f590C9+003To?dJdh literal 1518 zcmV7_JFu1}c;aVv^9zjZghzFp6M`|$+e!TC?pd-}f}rAagYU&CHGhB)< zrU6g1EE%~K`NHoayO`Ym#|q6J+~XgMgQ$w)dzJ+^|B8)uYs5#7w8sPU zpL8Ps{ZG-Yz3spMov4}ruVKG}GMpCdY9KPEG!yNp9R-_&7;Xwio{_85oRO=mwh zpBHk9a~qKT`Gkpu!*->}DsBRD2nwg#VS67y!Bl|B12W7PP&#zqHYFg(U`)3oGw^C}%LON>BkAjB)3}1uFmIVxvC0|(T0G(m3B+VOidSv zVPfhUlR1rD8sV6TVz>JU^FsK;eDf@reMzG_yE}@E8=8Ze>Vw z#Jt?5bTt5~JTsLel9Z*Vv zsXil@Rbd}5Wu?&By8Ow6%%pS5B)3S6*P(VV-dehqRucEqDD`0~RiIw5I&?dXI$=9; zD+P!N&+Gyd6D(rB+FWgJeu`Z29dg|e3z>xZATl_4xmuAi@`-(5!Y|est zuu>?*vcPT)4I6{-r`+S~N8AkNJoDar3O*Kiqynmp`>pozh{LL2ixO$%0F9h+jasLC zAd&Wp--c{aj4VNG?pw-gT+XGq2c)g z@uH$ro3K$x#Tv=A3#KOZb6MiuCBHhCtIL4AV#0Ui2v4jQ0G19@ZagNJ*nufl^|@5M z_N&TKZdo=IEUzp5npc5ij;{zNvvm2t;Z)WgI+jZDnrnF6n)$~P`#VO@fdZYAfk#iU zp4ujNgs&JbZg7KRV+ZZ9;+ZekNu?;#ARwo?EpJUmN-^ev0zvWW<{gnQ%wk;MBIbBp z*iyS9^7?K2<(XoJnPr0D^;hM$TyalfdmxLv2X2tgHYEe1e)&B|N?JCYU=Nii(HZjX zuZla8IBt1V7RY4R!`*yc+%sE8j&fA*`UmP#Kz`>S7u9{qjqgYJET^X>^Bs5 zt;K9Ei>h>r*mKVUo5@^2A8iH`rd|BF>w#)yW!C##y#af-#hN?N3g)4b)ogW7*W+lr zx22Of?RP^wXBb8OZW1m(q3yWV3*)w1p>noRe3F~>8nIV`fs@~#^)H{%e<#sGNaHRF zcI7!14vP)v7ceqRn;imVm!4k~oFdoR`Gr}`*bboxjTU5gvIgqp-l2%$Frqm7QfhLd zILhh*<9Wwxk6|^sWYk~)rpg`En{rn9b6^LwEk9*Xt>)W(v#_*dugEN4XioK0%uXsl z%=G5XyFt${c(!wU8H#WnnBEH|~m8*8kw#u{s^vBnx}tg*%# UYpk)x8v9=DZ(?f93jinp0Idk?ga7~l diff --git a/src/mc_optimade/examples/folder_of_cifs/optimade.yaml b/src/mc_optimade/examples/folder_of_cifs/optimade.yaml index 1400c6a..bd94751 100644 --- a/src/mc_optimade/examples/folder_of_cifs/optimade.yaml +++ b/src/mc_optimade/examples/folder_of_cifs/optimade.yaml @@ -25,6 +25,8 @@ entries: description: Alias for some more complicated property_b type: float maps_to: https://schemas.optimade.org/v1.1/dft/dft_hull_distance + aliases: + - Property B - name: structure_description title: Entry description description: Provides a human-readable description for this particular entry_type From 0778a31354a9901a6cbc057c0b76e92995ebd9cf Mon Sep 17 00:00:00 2001 From: Matthew Evans Date: Sat, 9 Dec 2023 19:31:14 +0000 Subject: [PATCH 13/13] Add codecov for coverage --- .github/workflows/ci.yml | 9 ++++++++- src/mc_optimade/pyproject.toml | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 35f4dba..a07eb9e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,4 +43,11 @@ jobs: pip install -e .[tests,dev] - name: Run tests - run: pytest -vv + run: pytest -vv --cov-report=xml --cov-report=term + + - name: Upload coverage + uses: codecov/codecov-action@v3 + with: + name: project + file: ./coverage.xml + flags: project diff --git a/src/mc_optimade/pyproject.toml b/src/mc_optimade/pyproject.toml index 8765cf0..cebbc19 100644 --- a/src/mc_optimade/pyproject.toml +++ b/src/mc_optimade/pyproject.toml @@ -14,7 +14,7 @@ dependencies = [ ] [project.optional-dependencies] -tests = ["pytest~=7.4"] +tests = ["pytest~=7.4", "pytest-cov~=4.0"] dev = ["black", "ruff", "pre-commit", "mypy", "isort", "types-all"] [tool.ruff]