prisma-api 0.3.10__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {prisma_api-0.3.10 → prisma_api-0.4.0}/PKG-INFO +1 -1
- {prisma_api-0.3.10 → prisma_api-0.4.0}/prisma_api/prisma_api_v2.py +636 -0
- {prisma_api-0.3.10 → prisma_api-0.4.0}/prisma_api.egg-info/PKG-INFO +1 -1
- {prisma_api-0.3.10 → prisma_api-0.4.0}/pyproject.toml +1 -1
- {prisma_api-0.3.10 → prisma_api-0.4.0}/tests/test_prisma_api_v2.py +567 -2
- {prisma_api-0.3.10 → prisma_api-0.4.0}/LICENSE +0 -0
- {prisma_api-0.3.10 → prisma_api-0.4.0}/README.md +0 -0
- {prisma_api-0.3.10 → prisma_api-0.4.0}/prisma_api/__init__.py +0 -0
- {prisma_api-0.3.10 → prisma_api-0.4.0}/prisma_api/config.py +0 -0
- {prisma_api-0.3.10 → prisma_api-0.4.0}/prisma_api/prisma_api.py +0 -0
- {prisma_api-0.3.10 → prisma_api-0.4.0}/prisma_api.egg-info/SOURCES.txt +0 -0
- {prisma_api-0.3.10 → prisma_api-0.4.0}/prisma_api.egg-info/dependency_links.txt +0 -0
- {prisma_api-0.3.10 → prisma_api-0.4.0}/prisma_api.egg-info/requires.txt +0 -0
- {prisma_api-0.3.10 → prisma_api-0.4.0}/prisma_api.egg-info/top_level.txt +0 -0
- {prisma_api-0.3.10 → prisma_api-0.4.0}/setup.cfg +0 -0
- {prisma_api-0.3.10 → prisma_api-0.4.0}/tests/test_prisma_api.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: prisma_api
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: A package for interacting with PrISMa Platform APIs. PrISMa is a holistic system for numerical synthesis and assessment of Metal-Organic Frameworks and their applications, for Direct Air Capture of CO2. For more information, see: https://prisma.hw.ac.uk.
|
|
5
5
|
Author-email: RCCS-CaptureTeam <team@prisma-platform.org>
|
|
6
6
|
License: GNUv3
|
|
@@ -24,6 +24,7 @@ from urllib.parse import urlencode
|
|
|
24
24
|
import warnings
|
|
25
25
|
import json
|
|
26
26
|
import re
|
|
27
|
+
import subprocess
|
|
27
28
|
from datetime import datetime
|
|
28
29
|
|
|
29
30
|
|
|
@@ -966,6 +967,86 @@ class PrismaAPIv2:
|
|
|
966
967
|
return result
|
|
967
968
|
return []
|
|
968
969
|
|
|
970
|
+
def _payload_to_records(self, payload: pd.DataFrame | list[dict] | dict) -> list[dict]:
|
|
971
|
+
"""Normalise write payloads to list[dict] while preserving all fields."""
|
|
972
|
+
if isinstance(payload, pd.DataFrame):
|
|
973
|
+
return payload.to_dict(orient="records")
|
|
974
|
+
if isinstance(payload, dict):
|
|
975
|
+
return [payload]
|
|
976
|
+
if isinstance(payload, list):
|
|
977
|
+
return payload
|
|
978
|
+
raise TypeError("payload must be a DataFrame, dict, or list[dict]")
|
|
979
|
+
|
|
980
|
+
def _autoprism_meta_provenance(self) -> dict[str, str | None]:
|
|
981
|
+
"""Build meta_provenance from local repository and pyproject metadata."""
|
|
982
|
+
|
|
983
|
+
def _git(args: list[str]) -> str | None:
|
|
984
|
+
try:
|
|
985
|
+
cp = subprocess.run(
|
|
986
|
+
["git", *args],
|
|
987
|
+
cwd=Path(__file__).resolve().parents[1],
|
|
988
|
+
check=False,
|
|
989
|
+
capture_output=True,
|
|
990
|
+
text=True,
|
|
991
|
+
)
|
|
992
|
+
except OSError:
|
|
993
|
+
return None
|
|
994
|
+
if cp.returncode != 0:
|
|
995
|
+
return None
|
|
996
|
+
value = cp.stdout.strip()
|
|
997
|
+
return value or None
|
|
998
|
+
|
|
999
|
+
repo_root = _git(["rev-parse", "--show-toplevel"])
|
|
1000
|
+
repo_name = Path(repo_root).name if repo_root else Path(__file__).resolve().parents[1].name
|
|
1001
|
+
branch = _git(["rev-parse", "--abbrev-ref", "HEAD"])
|
|
1002
|
+
source_repo = (
|
|
1003
|
+
repo_name
|
|
1004
|
+
if branch in (None, "main")
|
|
1005
|
+
else f"{repo_name} (branch: {branch})"
|
|
1006
|
+
)
|
|
1007
|
+
|
|
1008
|
+
pyproject_version: str | None = None
|
|
1009
|
+
pyproject_path = Path(__file__).resolve().parents[1] / "pyproject.toml"
|
|
1010
|
+
if pyproject_path.exists():
|
|
1011
|
+
text = pyproject_path.read_text(encoding="utf-8")
|
|
1012
|
+
try:
|
|
1013
|
+
import tomllib # Python 3.11+
|
|
1014
|
+
|
|
1015
|
+
data = tomllib.loads(text)
|
|
1016
|
+
project = data.get("project", {}) if isinstance(data, dict) else {}
|
|
1017
|
+
value = project.get("version") if isinstance(project, dict) else None
|
|
1018
|
+
pyproject_version = str(value) if value else None
|
|
1019
|
+
except Exception:
|
|
1020
|
+
# Fallback for environments where tomllib is unavailable.
|
|
1021
|
+
m = re.search(r"(?ms)^\[project\].*?^version\s*=\s*\"([^\"]+)\"", text)
|
|
1022
|
+
pyproject_version = m.group(1).strip() if m else None
|
|
1023
|
+
|
|
1024
|
+
return {
|
|
1025
|
+
"source_repo": source_repo,
|
|
1026
|
+
"source_repo_semantic_version": pyproject_version,
|
|
1027
|
+
"source_repo_tag": _git(["describe", "--tags", "--abbrev=0"]),
|
|
1028
|
+
"source_commit_hash": _git(["rev-parse", "HEAD"]),
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
def _autoprism_source_repo_url(self) -> str | None:
|
|
1032
|
+
"""Return the repository remote URL used for source provenance, if available."""
|
|
1033
|
+
try:
|
|
1034
|
+
cp = subprocess.run(
|
|
1035
|
+
["git", "remote", "get-url", "origin"],
|
|
1036
|
+
cwd=Path(__file__).resolve().parents[1],
|
|
1037
|
+
check=False,
|
|
1038
|
+
capture_output=True,
|
|
1039
|
+
text=True,
|
|
1040
|
+
)
|
|
1041
|
+
except OSError:
|
|
1042
|
+
return None
|
|
1043
|
+
|
|
1044
|
+
if cp.returncode != 0:
|
|
1045
|
+
return None
|
|
1046
|
+
|
|
1047
|
+
value = cp.stdout.strip()
|
|
1048
|
+
return value or None
|
|
1049
|
+
|
|
969
1050
|
def _properties_for(self, object_id: int, limit: int = 2000) -> list[dict]:
|
|
970
1051
|
"""Return all Property records linked to *object_id* via GenericForeignKey.
|
|
971
1052
|
|
|
@@ -1695,6 +1776,561 @@ class PrismaAPIv2:
|
|
|
1695
1776
|
"""GET /api/v2/carbon-zeopp-experimental/{item_id}/"""
|
|
1696
1777
|
return self._get(f"/carbon-zeopp-experimental/{item_id}/")
|
|
1697
1778
|
|
|
1779
|
+
# ── AutoPrism tables ─────────────────────────────────────────────────────
|
|
1780
|
+
|
|
1781
|
+
def get_computation_runs(
|
|
1782
|
+
self,
|
|
1783
|
+
workflow_id: str | None = None,
|
|
1784
|
+
step: str | None = None,
|
|
1785
|
+
status: str | None = None,
|
|
1786
|
+
limit: int = 500,
|
|
1787
|
+
offset: int = 0,
|
|
1788
|
+
) -> pd.DataFrame:
|
|
1789
|
+
"""
|
|
1790
|
+
GET /api/v2/computation-runs/
|
|
1791
|
+
|
|
1792
|
+
Args:
|
|
1793
|
+
workflow_id: Workflow identifier filter.
|
|
1794
|
+
step: Step filter.
|
|
1795
|
+
status: Status filter.
|
|
1796
|
+
"""
|
|
1797
|
+
params = _compact(
|
|
1798
|
+
workflow_id=workflow_id,
|
|
1799
|
+
step=step,
|
|
1800
|
+
status=status,
|
|
1801
|
+
limit=limit,
|
|
1802
|
+
offset=offset,
|
|
1803
|
+
)
|
|
1804
|
+
return self._to_df(self._get("/computation-runs/", params))
|
|
1805
|
+
|
|
1806
|
+
def get_computation_run(self, run_id: int) -> dict:
|
|
1807
|
+
"""GET /api/v2/computation-runs/{run_id}/"""
|
|
1808
|
+
return self._get(f"/computation-runs/{run_id}/")
|
|
1809
|
+
|
|
1810
|
+
def upsert_computation_runs(self, payload: pd.DataFrame | list[dict] | dict) -> dict:
|
|
1811
|
+
"""
|
|
1812
|
+
PUT /api/v2/computation-runs/
|
|
1813
|
+
|
|
1814
|
+
Accepts one object or many objects and forwards all provided fields.
|
|
1815
|
+
"""
|
|
1816
|
+
return self._put("/computation-runs/", self._payload_to_records(payload))
|
|
1817
|
+
|
|
1818
|
+
def get_adsorption_singlepoint(
|
|
1819
|
+
self,
|
|
1820
|
+
structure: str | None = None,
|
|
1821
|
+
md5: str | None = None,
|
|
1822
|
+
mixture_id: str | None = None,
|
|
1823
|
+
component: str | None = None,
|
|
1824
|
+
limit: int = 500,
|
|
1825
|
+
offset: int = 0,
|
|
1826
|
+
) -> pd.DataFrame:
|
|
1827
|
+
"""
|
|
1828
|
+
GET /api/v2/adsorption-singlepoint/
|
|
1829
|
+
|
|
1830
|
+
Args:
|
|
1831
|
+
structure: Structure name filter.
|
|
1832
|
+
md5: Exact md5 hash filter.
|
|
1833
|
+
mixture_id: Mixture identifier filter.
|
|
1834
|
+
component: Component filter.
|
|
1835
|
+
"""
|
|
1836
|
+
params = _compact(
|
|
1837
|
+
structure=structure,
|
|
1838
|
+
md5=md5,
|
|
1839
|
+
mixture_id=mixture_id,
|
|
1840
|
+
component=component,
|
|
1841
|
+
limit=limit,
|
|
1842
|
+
offset=offset,
|
|
1843
|
+
)
|
|
1844
|
+
return self._to_df(self._get("/adsorption-singlepoint/", params))
|
|
1845
|
+
|
|
1846
|
+
def get_adsorption_singlepoint_item(self, row_id: int) -> dict:
|
|
1847
|
+
"""GET /api/v2/adsorption-singlepoint/{row_id}/"""
|
|
1848
|
+
return self._get(f"/adsorption-singlepoint/{row_id}/")
|
|
1849
|
+
|
|
1850
|
+
def upsert_adsorption_singlepoint(self, payload: pd.DataFrame | list[dict] | dict) -> dict:
|
|
1851
|
+
"""
|
|
1852
|
+
PUT /api/v2/adsorption-singlepoint/
|
|
1853
|
+
|
|
1854
|
+
Accepts one object or many objects.
|
|
1855
|
+
Automatically normalises ``meta_provenance`` using local repo metadata.
|
|
1856
|
+
"""
|
|
1857
|
+
if isinstance(payload, dict) and isinstance(payload.get("adsorption_singlepoints"), list):
|
|
1858
|
+
records = self._payload_to_records(payload["adsorption_singlepoints"])
|
|
1859
|
+
else:
|
|
1860
|
+
records = self._payload_to_records(payload)
|
|
1861
|
+
meta = {
|
|
1862
|
+
**self._autoprism_meta_provenance(),
|
|
1863
|
+
"source_repo_url": self._autoprism_source_repo_url(),
|
|
1864
|
+
}
|
|
1865
|
+
enriched = [
|
|
1866
|
+
{
|
|
1867
|
+
**record,
|
|
1868
|
+
"meta_provenance": meta,
|
|
1869
|
+
}
|
|
1870
|
+
if isinstance(record, dict)
|
|
1871
|
+
else record
|
|
1872
|
+
for record in records
|
|
1873
|
+
]
|
|
1874
|
+
return self._put("/adsorption-singlepoint/", enriched)
|
|
1875
|
+
|
|
1876
|
+
def get_heat_capacity(
|
|
1877
|
+
self,
|
|
1878
|
+
structure: str | None = None,
|
|
1879
|
+
temperature_K: float | None = None,
|
|
1880
|
+
limit: int = 500,
|
|
1881
|
+
offset: int = 0,
|
|
1882
|
+
) -> pd.DataFrame:
|
|
1883
|
+
"""
|
|
1884
|
+
GET /api/v2/heat-capacity/
|
|
1885
|
+
|
|
1886
|
+
Args:
|
|
1887
|
+
structure: Structure name filter.
|
|
1888
|
+
temperature_K: Temperature filter [K].
|
|
1889
|
+
"""
|
|
1890
|
+
params = _compact(
|
|
1891
|
+
structure=structure,
|
|
1892
|
+
temperature_K=temperature_K,
|
|
1893
|
+
limit=limit,
|
|
1894
|
+
offset=offset,
|
|
1895
|
+
)
|
|
1896
|
+
return self._to_df(self._get("/heat-capacity/", params))
|
|
1897
|
+
|
|
1898
|
+
def get_heat_capacity_item(self, row_id: int) -> dict:
|
|
1899
|
+
"""GET /api/v2/heat-capacity/{row_id}/"""
|
|
1900
|
+
return self._get(f"/heat-capacity/{row_id}/")
|
|
1901
|
+
|
|
1902
|
+
def upsert_heat_capacity(self, payload: pd.DataFrame | list[dict] | dict) -> dict:
|
|
1903
|
+
"""
|
|
1904
|
+
PUT /api/v2/heat-capacity/
|
|
1905
|
+
|
|
1906
|
+
Accepts one object or many objects.
|
|
1907
|
+
Automatically normalises ``meta_provenance`` using local repo metadata.
|
|
1908
|
+
"""
|
|
1909
|
+
if isinstance(payload, dict) and isinstance(payload.get("heat_capacities"), list):
|
|
1910
|
+
records = self._payload_to_records(payload["heat_capacities"])
|
|
1911
|
+
else:
|
|
1912
|
+
records = self._payload_to_records(payload)
|
|
1913
|
+
|
|
1914
|
+
meta = {
|
|
1915
|
+
**self._autoprism_meta_provenance(),
|
|
1916
|
+
"source_repo_url": self._autoprism_source_repo_url(),
|
|
1917
|
+
}
|
|
1918
|
+
enriched = [
|
|
1919
|
+
{
|
|
1920
|
+
**record,
|
|
1921
|
+
"meta_provenance": meta,
|
|
1922
|
+
}
|
|
1923
|
+
if isinstance(record, dict)
|
|
1924
|
+
else record
|
|
1925
|
+
for record in records
|
|
1926
|
+
]
|
|
1927
|
+
return self._put("/heat-capacity/", enriched)
|
|
1928
|
+
|
|
1929
|
+
def get_isotherm_h2(
|
|
1930
|
+
self,
|
|
1931
|
+
structure: str | None = None,
|
|
1932
|
+
isotherm_id: str | None = None,
|
|
1933
|
+
component: str | None = None,
|
|
1934
|
+
temperature_K: float | None = None,
|
|
1935
|
+
pressure_bar: float | None = None,
|
|
1936
|
+
limit: int = 500,
|
|
1937
|
+
offset: int = 0,
|
|
1938
|
+
) -> pd.DataFrame:
|
|
1939
|
+
"""
|
|
1940
|
+
GET /api/v2/isotherm-h2/
|
|
1941
|
+
|
|
1942
|
+
Args:
|
|
1943
|
+
structure: Structure name filter.
|
|
1944
|
+
isotherm_id: Isotherm identifier filter.
|
|
1945
|
+
component: Component filter.
|
|
1946
|
+
temperature_K: Temperature filter [K].
|
|
1947
|
+
pressure_bar: Pressure filter [bar].
|
|
1948
|
+
"""
|
|
1949
|
+
params = _compact(
|
|
1950
|
+
structure=structure,
|
|
1951
|
+
isotherm_id=isotherm_id,
|
|
1952
|
+
component=component,
|
|
1953
|
+
temperature_K=temperature_K,
|
|
1954
|
+
pressure_bar=pressure_bar,
|
|
1955
|
+
limit=limit,
|
|
1956
|
+
offset=offset,
|
|
1957
|
+
)
|
|
1958
|
+
return self._to_df(self._get("/isotherm-h2/", params))
|
|
1959
|
+
|
|
1960
|
+
def get_isotherm_h2_item(self, row_id: int) -> dict:
|
|
1961
|
+
"""GET /api/v2/isotherm-h2/{row_id}/"""
|
|
1962
|
+
return self._get(f"/isotherm-h2/{row_id}/")
|
|
1963
|
+
|
|
1964
|
+
def upsert_isotherm_h2(self, payload: pd.DataFrame | list[dict] | dict) -> dict:
|
|
1965
|
+
"""
|
|
1966
|
+
PUT /api/v2/isotherm-h2/
|
|
1967
|
+
|
|
1968
|
+
Accepts one object or many objects.
|
|
1969
|
+
Automatically normalises ``meta_provenance`` using local repo metadata.
|
|
1970
|
+
"""
|
|
1971
|
+
if isinstance(payload, dict) and isinstance(payload.get("isotherm_H2s"), list):
|
|
1972
|
+
records = self._payload_to_records(payload["isotherm_H2s"])
|
|
1973
|
+
else:
|
|
1974
|
+
records = self._payload_to_records(payload)
|
|
1975
|
+
|
|
1976
|
+
meta = self._autoprism_meta_provenance()
|
|
1977
|
+
enriched = [
|
|
1978
|
+
{
|
|
1979
|
+
**record,
|
|
1980
|
+
"meta_provenance": meta,
|
|
1981
|
+
}
|
|
1982
|
+
if isinstance(record, dict)
|
|
1983
|
+
else record
|
|
1984
|
+
for record in records
|
|
1985
|
+
]
|
|
1986
|
+
return self._put("/isotherm-h2/", enriched)
|
|
1987
|
+
|
|
1988
|
+
def get_mofchecker(
|
|
1989
|
+
self,
|
|
1990
|
+
structure: str | None = None,
|
|
1991
|
+
md5: str | None = None,
|
|
1992
|
+
is_mof: bool | None = None,
|
|
1993
|
+
MOFQ: str | None = None,
|
|
1994
|
+
limit: int = 500,
|
|
1995
|
+
offset: int = 0,
|
|
1996
|
+
) -> pd.DataFrame:
|
|
1997
|
+
"""
|
|
1998
|
+
GET /api/v2/mofchecker/
|
|
1999
|
+
|
|
2000
|
+
Args:
|
|
2001
|
+
structure: Structure name filter.
|
|
2002
|
+
md5: Exact md5 hash filter.
|
|
2003
|
+
is_mof: Boolean mofchecker flag.
|
|
2004
|
+
MOFQ: MOFQ classifier filter.
|
|
2005
|
+
"""
|
|
2006
|
+
params = _compact(
|
|
2007
|
+
structure=structure,
|
|
2008
|
+
md5=md5,
|
|
2009
|
+
is_mof=None if is_mof is None else str(is_mof).lower(),
|
|
2010
|
+
MOFQ=MOFQ,
|
|
2011
|
+
limit=limit,
|
|
2012
|
+
offset=offset,
|
|
2013
|
+
)
|
|
2014
|
+
return self._to_df(self._get("/mofchecker/", params))
|
|
2015
|
+
|
|
2016
|
+
def get_mofchecker_item(self, row_id: int) -> dict:
|
|
2017
|
+
"""GET /api/v2/mofchecker/{row_id}/"""
|
|
2018
|
+
return self._get(f"/mofchecker/{row_id}/")
|
|
2019
|
+
|
|
2020
|
+
def upsert_mofchecker(self, payload: pd.DataFrame | list[dict] | dict) -> dict:
|
|
2021
|
+
"""
|
|
2022
|
+
PUT /api/v2/mofchecker/
|
|
2023
|
+
|
|
2024
|
+
Accepts one object or many objects.
|
|
2025
|
+
Automatically normalises ``meta_provenance`` using local repo metadata.
|
|
2026
|
+
"""
|
|
2027
|
+
if isinstance(payload, dict) and isinstance(payload.get("mofchecker"), list):
|
|
2028
|
+
records = self._payload_to_records(payload["mofchecker"])
|
|
2029
|
+
else:
|
|
2030
|
+
records = self._payload_to_records(payload)
|
|
2031
|
+
|
|
2032
|
+
meta = self._autoprism_meta_provenance()
|
|
2033
|
+
enriched = [
|
|
2034
|
+
{
|
|
2035
|
+
**record,
|
|
2036
|
+
"meta_provenance": meta,
|
|
2037
|
+
}
|
|
2038
|
+
if isinstance(record, dict)
|
|
2039
|
+
else record
|
|
2040
|
+
for record in records
|
|
2041
|
+
]
|
|
2042
|
+
return self._put("/mofchecker/", enriched)
|
|
2043
|
+
|
|
2044
|
+
def get_zeopp_metrics(
|
|
2045
|
+
self,
|
|
2046
|
+
mof: str | None = None,
|
|
2047
|
+
md5: str | None = None,
|
|
2048
|
+
probe: str | None = None,
|
|
2049
|
+
limit: int = 500,
|
|
2050
|
+
offset: int = 0,
|
|
2051
|
+
) -> pd.DataFrame:
|
|
2052
|
+
"""
|
|
2053
|
+
GET /api/v2/zeopp-metrics/
|
|
2054
|
+
|
|
2055
|
+
Args:
|
|
2056
|
+
mof: MOF name filter.
|
|
2057
|
+
md5: Exact md5 hash filter.
|
|
2058
|
+
probe: Probe name filter.
|
|
2059
|
+
"""
|
|
2060
|
+
params = _compact(mof=mof, md5=md5, probe=probe, limit=limit, offset=offset)
|
|
2061
|
+
return self._to_df(self._get("/zeopp-metrics/", params))
|
|
2062
|
+
|
|
2063
|
+
def get_zeopp_metrics_item(self, row_id: int) -> dict:
|
|
2064
|
+
"""GET /api/v2/zeopp-metrics/{row_id}/"""
|
|
2065
|
+
return self._get(f"/zeopp-metrics/{row_id}/")
|
|
2066
|
+
|
|
2067
|
+
def upsert_zeopp_metrics(self, payload: pd.DataFrame | list[dict] | dict) -> dict:
|
|
2068
|
+
"""
|
|
2069
|
+
PUT /api/v2/zeopp-metrics/
|
|
2070
|
+
|
|
2071
|
+
Accepts one object or many objects.
|
|
2072
|
+
Automatically normalises ``meta_provenance`` using local repo metadata.
|
|
2073
|
+
"""
|
|
2074
|
+
if isinstance(payload, dict) and isinstance(payload.get("zeopp_metrics"), list):
|
|
2075
|
+
records = self._payload_to_records(payload["zeopp_metrics"])
|
|
2076
|
+
else:
|
|
2077
|
+
records = self._payload_to_records(payload)
|
|
2078
|
+
|
|
2079
|
+
meta = self._autoprism_meta_provenance()
|
|
2080
|
+
enriched = [
|
|
2081
|
+
{
|
|
2082
|
+
**record,
|
|
2083
|
+
"meta_provenance": meta,
|
|
2084
|
+
}
|
|
2085
|
+
if isinstance(record, dict)
|
|
2086
|
+
else record
|
|
2087
|
+
for record in records
|
|
2088
|
+
]
|
|
2089
|
+
return self._put("/zeopp-metrics/", enriched)
|
|
2090
|
+
|
|
2091
|
+
def upsert_autoprism_collection(self, payload: dict[str, Any]) -> dict[str, Any]:
|
|
2092
|
+
"""
|
|
2093
|
+
Upsert multiple AutoPrism sections from a single combined payload.
|
|
2094
|
+
|
|
2095
|
+
Expected payload shape mirrors the AutoPrism collection mock payload, with
|
|
2096
|
+
optional top-level keys:
|
|
2097
|
+
computation_runs, adsorption_singlepoints, heat_capacities,
|
|
2098
|
+
isotherm_H2s, mofchecker, zeopp_metrics, meta_provenance
|
|
2099
|
+
|
|
2100
|
+
Notes:
|
|
2101
|
+
- Top-level ``meta_provenance`` is ignored.
|
|
2102
|
+
- Each section is passed to its dedicated upsert wrapper so section-
|
|
2103
|
+
specific provenance normalization rules remain in one place.
|
|
2104
|
+
|
|
2105
|
+
Returns:
|
|
2106
|
+
dict with keys:
|
|
2107
|
+
sections: per-section status details
|
|
2108
|
+
totals: aggregate created/updated counters
|
|
2109
|
+
overall_status: "ok" if no section failed, else "partial_failure"
|
|
2110
|
+
"""
|
|
2111
|
+
if not isinstance(payload, dict):
|
|
2112
|
+
raise TypeError("payload must be a dict matching the AutoPrism collection shape")
|
|
2113
|
+
|
|
2114
|
+
section_handlers: list[tuple[str, str, Any]] = [
|
|
2115
|
+
("computation_runs", "upsert_computation_runs", payload.get("computation_runs")),
|
|
2116
|
+
("adsorption_singlepoints", "upsert_adsorption_singlepoint", payload.get("adsorption_singlepoints")),
|
|
2117
|
+
("heat_capacities", "upsert_heat_capacity", payload.get("heat_capacities")),
|
|
2118
|
+
("isotherm_H2s", "upsert_isotherm_h2", payload.get("isotherm_H2s")),
|
|
2119
|
+
("mofchecker", "upsert_mofchecker", payload.get("mofchecker")),
|
|
2120
|
+
("zeopp_metrics", "upsert_zeopp_metrics", payload.get("zeopp_metrics")),
|
|
2121
|
+
]
|
|
2122
|
+
|
|
2123
|
+
sections: dict[str, dict[str, Any]] = {}
|
|
2124
|
+
total_created = 0
|
|
2125
|
+
total_updated = 0
|
|
2126
|
+
failed = 0
|
|
2127
|
+
|
|
2128
|
+
for section_name, method_name, section_payload in section_handlers:
|
|
2129
|
+
if section_payload is None:
|
|
2130
|
+
sections[section_name] = {
|
|
2131
|
+
"status": "skipped",
|
|
2132
|
+
"reason": "missing section in payload",
|
|
2133
|
+
}
|
|
2134
|
+
continue
|
|
2135
|
+
|
|
2136
|
+
try:
|
|
2137
|
+
method = getattr(self, method_name)
|
|
2138
|
+
result = method(section_payload)
|
|
2139
|
+
created = int(result.get("created", 0)) if isinstance(result, dict) else 0
|
|
2140
|
+
updated = int(result.get("updated", 0)) if isinstance(result, dict) else 0
|
|
2141
|
+
total_created += created
|
|
2142
|
+
total_updated += updated
|
|
2143
|
+
sections[section_name] = {
|
|
2144
|
+
"status": "ok",
|
|
2145
|
+
"created": created,
|
|
2146
|
+
"updated": updated,
|
|
2147
|
+
"result": result,
|
|
2148
|
+
}
|
|
2149
|
+
except Exception as exc:
|
|
2150
|
+
failed += 1
|
|
2151
|
+
sections[section_name] = {
|
|
2152
|
+
"status": "error",
|
|
2153
|
+
"error": str(exc),
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
return {
|
|
2157
|
+
"sections": sections,
|
|
2158
|
+
"totals": {
|
|
2159
|
+
"created": total_created,
|
|
2160
|
+
"updated": total_updated,
|
|
2161
|
+
"failed_sections": failed,
|
|
2162
|
+
},
|
|
2163
|
+
"overall_status": "ok" if failed == 0 else "partial_failure",
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
def get_autoprism_collection(
|
|
2167
|
+
self,
|
|
2168
|
+
workflow_id: str | None = None,
|
|
2169
|
+
step: str | None = None,
|
|
2170
|
+
status: str | None = None,
|
|
2171
|
+
structure: str | None = None,
|
|
2172
|
+
mof: str | None = None,
|
|
2173
|
+
md5: str | None = None,
|
|
2174
|
+
mixture_id: str | None = None,
|
|
2175
|
+
component: str | None = None,
|
|
2176
|
+
isotherm_id: str | None = None,
|
|
2177
|
+
temperature_K: float | None = None,
|
|
2178
|
+
pressure_bar: float | None = None,
|
|
2179
|
+
probe: str | None = None,
|
|
2180
|
+
is_mof: bool | None = None,
|
|
2181
|
+
MOFQ: str | None = None,
|
|
2182
|
+
limit: int = 500,
|
|
2183
|
+
offset: int = 0,
|
|
2184
|
+
) -> dict[str, list[dict] | dict[str, str | None]]:
|
|
2185
|
+
"""
|
|
2186
|
+
Gather AutoPrism table records in one call flow.
|
|
2187
|
+
|
|
2188
|
+
Returns a dict with keys:
|
|
2189
|
+
computation_runs,
|
|
2190
|
+
adsorption_singlepoints, heat_capacities, isotherm_H2s,
|
|
2191
|
+
mofchecker, zeopp_metrics
|
|
2192
|
+
meta_provenance
|
|
2193
|
+
|
|
2194
|
+
Notes:
|
|
2195
|
+
- ``workflow_id``, ``step`` and ``status`` filter computation runs.
|
|
2196
|
+
- ``structure`` is used for adsorption_singlepoint, heat_capacity,
|
|
2197
|
+
isotherm_H2 and mofchecker.
|
|
2198
|
+
- ``mof`` is used for zeopp_metrics.
|
|
2199
|
+
- If ``structure`` is omitted and ``mof`` is provided, ``mof`` is
|
|
2200
|
+
also used as the structure filter for convenience.
|
|
2201
|
+
|
|
2202
|
+
Empty payload handling:
|
|
2203
|
+
- If any sub-call returns ``None`` or an unexpected scalar payload,
|
|
2204
|
+
this method normalises it to an empty table representation
|
|
2205
|
+
(``pd.DataFrame()`` in dataframe mode, ``[]`` in json mode).
|
|
2206
|
+
"""
|
|
2207
|
+
structure_filter = structure or mof
|
|
2208
|
+
|
|
2209
|
+
def _empty_table() -> pd.DataFrame | list[dict]:
|
|
2210
|
+
return [] if self._return_format == "json" else pd.DataFrame()
|
|
2211
|
+
|
|
2212
|
+
def _normalise_table_payload(value: Any) -> pd.DataFrame | list[dict]:
|
|
2213
|
+
if value is None:
|
|
2214
|
+
return _empty_table()
|
|
2215
|
+
if isinstance(value, (pd.DataFrame, list)):
|
|
2216
|
+
return value
|
|
2217
|
+
if isinstance(value, dict):
|
|
2218
|
+
return self._to_df(value)
|
|
2219
|
+
return _empty_table()
|
|
2220
|
+
|
|
2221
|
+
def _record_count(value: Any) -> int:
|
|
2222
|
+
try:
|
|
2223
|
+
return len(value)
|
|
2224
|
+
except TypeError:
|
|
2225
|
+
return 0
|
|
2226
|
+
|
|
2227
|
+
def _as_record_list(value: Any) -> list[dict]:
|
|
2228
|
+
records = self._as_records(value)
|
|
2229
|
+
return [r for r in records if isinstance(r, dict)]
|
|
2230
|
+
|
|
2231
|
+
meta = {
|
|
2232
|
+
**self._autoprism_meta_provenance(),
|
|
2233
|
+
"source_repo_url": self._autoprism_source_repo_url(),
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
def _safe_fetch(section: str, fetcher, **kwargs) -> pd.DataFrame | list[dict]:
|
|
2237
|
+
try:
|
|
2238
|
+
return _normalise_table_payload(fetcher(**kwargs))
|
|
2239
|
+
except requests.HTTPError as exc:
|
|
2240
|
+
warnings.warn(
|
|
2241
|
+
f"AutoPrism section '{section}' failed ({exc}); returning empty table.",
|
|
2242
|
+
UserWarning,
|
|
2243
|
+
stacklevel=2,
|
|
2244
|
+
)
|
|
2245
|
+
return _empty_table()
|
|
2246
|
+
except requests.RequestException as exc:
|
|
2247
|
+
warnings.warn(
|
|
2248
|
+
f"AutoPrism section '{section}' request error ({exc}); returning empty table.",
|
|
2249
|
+
UserWarning,
|
|
2250
|
+
stacklevel=2,
|
|
2251
|
+
)
|
|
2252
|
+
return _empty_table()
|
|
2253
|
+
|
|
2254
|
+
collection = {
|
|
2255
|
+
"computation_runs": _safe_fetch(
|
|
2256
|
+
"computation_runs",
|
|
2257
|
+
self.get_computation_runs,
|
|
2258
|
+
workflow_id=workflow_id,
|
|
2259
|
+
step=step,
|
|
2260
|
+
status=status,
|
|
2261
|
+
limit=limit,
|
|
2262
|
+
offset=offset,
|
|
2263
|
+
),
|
|
2264
|
+
"adsorption_singlepoints": _safe_fetch(
|
|
2265
|
+
"adsorption_singlepoint",
|
|
2266
|
+
self.get_adsorption_singlepoint,
|
|
2267
|
+
structure=structure_filter,
|
|
2268
|
+
md5=md5,
|
|
2269
|
+
mixture_id=mixture_id,
|
|
2270
|
+
component=component,
|
|
2271
|
+
limit=limit,
|
|
2272
|
+
offset=offset,
|
|
2273
|
+
),
|
|
2274
|
+
"heat_capacities": _safe_fetch(
|
|
2275
|
+
"heat_capacity",
|
|
2276
|
+
self.get_heat_capacity,
|
|
2277
|
+
structure=structure_filter,
|
|
2278
|
+
temperature_K=temperature_K,
|
|
2279
|
+
limit=limit,
|
|
2280
|
+
offset=offset,
|
|
2281
|
+
),
|
|
2282
|
+
"isotherm_H2s": _safe_fetch(
|
|
2283
|
+
"isotherm_H2",
|
|
2284
|
+
self.get_isotherm_h2,
|
|
2285
|
+
structure=structure_filter,
|
|
2286
|
+
isotherm_id=isotherm_id,
|
|
2287
|
+
component=component,
|
|
2288
|
+
temperature_K=temperature_K,
|
|
2289
|
+
pressure_bar=pressure_bar,
|
|
2290
|
+
limit=limit,
|
|
2291
|
+
offset=offset,
|
|
2292
|
+
),
|
|
2293
|
+
"mofchecker": _safe_fetch(
|
|
2294
|
+
"mofchecker",
|
|
2295
|
+
self.get_mofchecker,
|
|
2296
|
+
structure=structure_filter,
|
|
2297
|
+
md5=md5,
|
|
2298
|
+
is_mof=is_mof,
|
|
2299
|
+
MOFQ=MOFQ,
|
|
2300
|
+
limit=limit,
|
|
2301
|
+
offset=offset,
|
|
2302
|
+
),
|
|
2303
|
+
"zeopp_metrics": _safe_fetch(
|
|
2304
|
+
"zeopp_metrics",
|
|
2305
|
+
self.get_zeopp_metrics,
|
|
2306
|
+
mof=mof,
|
|
2307
|
+
md5=md5,
|
|
2308
|
+
probe=probe,
|
|
2309
|
+
limit=limit,
|
|
2310
|
+
offset=offset,
|
|
2311
|
+
),
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
collection = {
|
|
2315
|
+
key: _as_record_list(value)
|
|
2316
|
+
for key, value in collection.items()
|
|
2317
|
+
}
|
|
2318
|
+
collection["meta_provenance"] = meta
|
|
2319
|
+
|
|
2320
|
+
label = structure_filter or mof or "all"
|
|
2321
|
+
print(f"AutoPrism collection for '{label}':")
|
|
2322
|
+
for key in (
|
|
2323
|
+
"computation_runs",
|
|
2324
|
+
"adsorption_singlepoints",
|
|
2325
|
+
"heat_capacities",
|
|
2326
|
+
"isotherm_H2s",
|
|
2327
|
+
"mofchecker",
|
|
2328
|
+
"zeopp_metrics",
|
|
2329
|
+
):
|
|
2330
|
+
print(f" {key:22s}: {_record_count(collection.get(key, []))} records")
|
|
2331
|
+
|
|
2332
|
+
return collection
|
|
2333
|
+
|
|
1698
2334
|
# ── TEA / LCA data ────────────────────────────────────────────────────────
|
|
1699
2335
|
|
|
1700
2336
|
def get_output_kpis(self,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: prisma_api
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: A package for interacting with PrISMa Platform APIs. PrISMa is a holistic system for numerical synthesis and assessment of Metal-Organic Frameworks and their applications, for Direct Air Capture of CO2. For more information, see: https://prisma.hw.ac.uk.
|
|
5
5
|
Author-email: RCCS-CaptureTeam <team@prisma-platform.org>
|
|
6
6
|
License: GNUv3
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "prisma_api"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "A package for interacting with PrISMa Platform APIs. PrISMa is a holistic system for numerical synthesis and assessment of Metal-Organic Frameworks and their applications, for Direct Air Capture of CO2. For more information, see: https://prisma.hw.ac.uk."
|
|
9
9
|
authors = [
|
|
10
10
|
{name = "RCCS-CaptureTeam", email = "team@prisma-platform.org"}
|
|
@@ -15,6 +15,7 @@ import pytest
|
|
|
15
15
|
import responses as resp_lib
|
|
16
16
|
from responses import matchers
|
|
17
17
|
from datetime import datetime
|
|
18
|
+
from numbers import Integral
|
|
18
19
|
|
|
19
20
|
import pandas as pd
|
|
20
21
|
|
|
@@ -908,9 +909,9 @@ def test_get_scopes_coerces_types(api):
|
|
|
908
909
|
df = api.get_scopes()
|
|
909
910
|
row = df.iloc[0]
|
|
910
911
|
|
|
911
|
-
assert isinstance(row["id"],
|
|
912
|
+
assert isinstance(row["id"], Integral)
|
|
912
913
|
assert isinstance(row["name"], str)
|
|
913
|
-
assert
|
|
914
|
+
assert pd.api.types.is_bool_dtype(df["active"])
|
|
914
915
|
assert isinstance(row["capture_efficiency"], float)
|
|
915
916
|
assert pd.api.types.is_datetime64_any_dtype(df["created_at"])
|
|
916
917
|
assert isinstance(row["tags"], list)
|
|
@@ -1294,6 +1295,570 @@ def test_get_carbon_zeopp_experimental_item_detail(api):
|
|
|
1294
1295
|
assert api.get_carbon_zeopp_experimental_item(1)["mof"] == "HKUST"
|
|
1295
1296
|
|
|
1296
1297
|
|
|
1298
|
+
# ── AutoPrism Tables ─────────────────────────────────────────────────────────
|
|
1299
|
+
|
|
1300
|
+
@resp_lib.activate
|
|
1301
|
+
def test_get_autoprism_collection_returns_all_records(api):
|
|
1302
|
+
resp_lib.add(
|
|
1303
|
+
resp_lib.GET,
|
|
1304
|
+
f"{PROD_BASE}/computation-runs/",
|
|
1305
|
+
match=[matchers.query_param_matcher({
|
|
1306
|
+
"workflow_id": "wf-1",
|
|
1307
|
+
"step": "adsorption",
|
|
1308
|
+
"status": "done",
|
|
1309
|
+
"limit": "50",
|
|
1310
|
+
"offset": "0",
|
|
1311
|
+
})],
|
|
1312
|
+
json=_envelope([{"id": 99, "workflow_id": "wf-1", "step": "adsorption", "status": "done"}]),
|
|
1313
|
+
status=200,
|
|
1314
|
+
)
|
|
1315
|
+
resp_lib.add(
|
|
1316
|
+
resp_lib.GET,
|
|
1317
|
+
f"{PROD_BASE}/adsorption-singlepoint/",
|
|
1318
|
+
match=[matchers.query_param_matcher({
|
|
1319
|
+
"structure": "ABEXEM",
|
|
1320
|
+
"md5": "abc123",
|
|
1321
|
+
"mixture_id": "mix-1",
|
|
1322
|
+
"component": "CO2",
|
|
1323
|
+
"limit": "50",
|
|
1324
|
+
"offset": "0",
|
|
1325
|
+
})],
|
|
1326
|
+
json=_envelope([{"id": 1, "structure": "ABEXEM"}]),
|
|
1327
|
+
status=200,
|
|
1328
|
+
)
|
|
1329
|
+
resp_lib.add(
|
|
1330
|
+
resp_lib.GET,
|
|
1331
|
+
f"{PROD_BASE}/heat-capacity/",
|
|
1332
|
+
match=[matchers.query_param_matcher({
|
|
1333
|
+
"structure": "ABEXEM",
|
|
1334
|
+
"temperature_K": "298.0",
|
|
1335
|
+
"limit": "50",
|
|
1336
|
+
"offset": "0",
|
|
1337
|
+
})],
|
|
1338
|
+
json=_envelope([{"id": 2, "structure": "ABEXEM", "temperature_K": 298.0}]),
|
|
1339
|
+
status=200,
|
|
1340
|
+
)
|
|
1341
|
+
resp_lib.add(
|
|
1342
|
+
resp_lib.GET,
|
|
1343
|
+
f"{PROD_BASE}/isotherm-h2/",
|
|
1344
|
+
match=[matchers.query_param_matcher({
|
|
1345
|
+
"structure": "ABEXEM",
|
|
1346
|
+
"isotherm_id": "iso-1",
|
|
1347
|
+
"component": "CO2",
|
|
1348
|
+
"temperature_K": "298.0",
|
|
1349
|
+
"pressure_bar": "1.0",
|
|
1350
|
+
"limit": "50",
|
|
1351
|
+
"offset": "0",
|
|
1352
|
+
})],
|
|
1353
|
+
json=_envelope([{"id": 3, "structure": "ABEXEM", "isotherm_id": "iso-1"}]),
|
|
1354
|
+
status=200,
|
|
1355
|
+
)
|
|
1356
|
+
resp_lib.add(
|
|
1357
|
+
resp_lib.GET,
|
|
1358
|
+
f"{PROD_BASE}/mofchecker/",
|
|
1359
|
+
match=[matchers.query_param_matcher({
|
|
1360
|
+
"structure": "ABEXEM",
|
|
1361
|
+
"md5": "abc123",
|
|
1362
|
+
"is_mof": "true",
|
|
1363
|
+
"MOFQ": "yes",
|
|
1364
|
+
"limit": "50",
|
|
1365
|
+
"offset": "0",
|
|
1366
|
+
})],
|
|
1367
|
+
json=_envelope([{"id": 4, "structure": "ABEXEM", "is_mof": True}]),
|
|
1368
|
+
status=200,
|
|
1369
|
+
)
|
|
1370
|
+
resp_lib.add(
|
|
1371
|
+
resp_lib.GET,
|
|
1372
|
+
f"{PROD_BASE}/zeopp-metrics/",
|
|
1373
|
+
match=[matchers.query_param_matcher({
|
|
1374
|
+
"mof": "ABEXEM",
|
|
1375
|
+
"md5": "abc123",
|
|
1376
|
+
"probe": "N2",
|
|
1377
|
+
"limit": "50",
|
|
1378
|
+
"offset": "0",
|
|
1379
|
+
})],
|
|
1380
|
+
json=_envelope([{"id": 5, "mof": "ABEXEM", "probe": "N2"}]),
|
|
1381
|
+
status=200,
|
|
1382
|
+
)
|
|
1383
|
+
|
|
1384
|
+
bundle = api.get_autoprism_collection(
|
|
1385
|
+
workflow_id="wf-1",
|
|
1386
|
+
step="adsorption",
|
|
1387
|
+
status="done",
|
|
1388
|
+
structure="ABEXEM",
|
|
1389
|
+
mof="ABEXEM",
|
|
1390
|
+
md5="abc123",
|
|
1391
|
+
mixture_id="mix-1",
|
|
1392
|
+
component="CO2",
|
|
1393
|
+
isotherm_id="iso-1",
|
|
1394
|
+
temperature_K=298.0,
|
|
1395
|
+
pressure_bar=1.0,
|
|
1396
|
+
probe="N2",
|
|
1397
|
+
is_mof=True,
|
|
1398
|
+
MOFQ="yes",
|
|
1399
|
+
limit=50,
|
|
1400
|
+
offset=0,
|
|
1401
|
+
)
|
|
1402
|
+
|
|
1403
|
+
assert set(bundle.keys()) == {
|
|
1404
|
+
"computation_runs",
|
|
1405
|
+
"adsorption_singlepoints",
|
|
1406
|
+
"heat_capacities",
|
|
1407
|
+
"isotherm_H2s",
|
|
1408
|
+
"mofchecker",
|
|
1409
|
+
"zeopp_metrics",
|
|
1410
|
+
"meta_provenance",
|
|
1411
|
+
}
|
|
1412
|
+
assert len(bundle["computation_runs"]) == 1
|
|
1413
|
+
assert len(bundle["adsorption_singlepoints"]) == 1
|
|
1414
|
+
assert len(bundle["heat_capacities"]) == 1
|
|
1415
|
+
assert len(bundle["isotherm_H2s"]) == 1
|
|
1416
|
+
assert len(bundle["mofchecker"]) == 1
|
|
1417
|
+
assert len(bundle["zeopp_metrics"]) == 1
|
|
1418
|
+
|
|
1419
|
+
|
|
1420
|
+
@resp_lib.activate
|
|
1421
|
+
def test_get_autoprism_collection_uses_mof_as_structure_fallback(api):
|
|
1422
|
+
resp_lib.add(
|
|
1423
|
+
resp_lib.GET,
|
|
1424
|
+
f"{PROD_BASE}/computation-runs/",
|
|
1425
|
+
match=[matchers.query_param_matcher({"limit": "500", "offset": "0"})],
|
|
1426
|
+
json=_envelope([]),
|
|
1427
|
+
status=200,
|
|
1428
|
+
)
|
|
1429
|
+
resp_lib.add(
|
|
1430
|
+
resp_lib.GET,
|
|
1431
|
+
f"{PROD_BASE}/adsorption-singlepoint/",
|
|
1432
|
+
match=[matchers.query_param_matcher({"structure": "HKUST", "limit": "500", "offset": "0"})],
|
|
1433
|
+
json=_envelope([]),
|
|
1434
|
+
status=200,
|
|
1435
|
+
)
|
|
1436
|
+
resp_lib.add(
|
|
1437
|
+
resp_lib.GET,
|
|
1438
|
+
f"{PROD_BASE}/heat-capacity/",
|
|
1439
|
+
match=[matchers.query_param_matcher({"structure": "HKUST", "limit": "500", "offset": "0"})],
|
|
1440
|
+
json=_envelope([]),
|
|
1441
|
+
status=200,
|
|
1442
|
+
)
|
|
1443
|
+
resp_lib.add(
|
|
1444
|
+
resp_lib.GET,
|
|
1445
|
+
f"{PROD_BASE}/isotherm-h2/",
|
|
1446
|
+
match=[matchers.query_param_matcher({"structure": "HKUST", "limit": "500", "offset": "0"})],
|
|
1447
|
+
json=_envelope([]),
|
|
1448
|
+
status=200,
|
|
1449
|
+
)
|
|
1450
|
+
resp_lib.add(
|
|
1451
|
+
resp_lib.GET,
|
|
1452
|
+
f"{PROD_BASE}/mofchecker/",
|
|
1453
|
+
match=[matchers.query_param_matcher({"structure": "HKUST", "limit": "500", "offset": "0"})],
|
|
1454
|
+
json=_envelope([]),
|
|
1455
|
+
status=200,
|
|
1456
|
+
)
|
|
1457
|
+
resp_lib.add(
|
|
1458
|
+
resp_lib.GET,
|
|
1459
|
+
f"{PROD_BASE}/zeopp-metrics/",
|
|
1460
|
+
match=[matchers.query_param_matcher({"mof": "HKUST", "limit": "500", "offset": "0"})],
|
|
1461
|
+
json=_envelope([]),
|
|
1462
|
+
status=200,
|
|
1463
|
+
)
|
|
1464
|
+
|
|
1465
|
+
api.get_autoprism_collection(mof="HKUST")
|
|
1466
|
+
|
|
1467
|
+
|
|
1468
|
+
@resp_lib.activate
|
|
1469
|
+
def test_get_computation_runs_filters(api):
|
|
1470
|
+
resp_lib.add(
|
|
1471
|
+
resp_lib.GET,
|
|
1472
|
+
f"{PROD_BASE}/computation-runs/",
|
|
1473
|
+
match=[matchers.query_param_matcher({
|
|
1474
|
+
"workflow_id": "wf-1",
|
|
1475
|
+
"step": "zeopp",
|
|
1476
|
+
"status": "running",
|
|
1477
|
+
"limit": "25",
|
|
1478
|
+
"offset": "5",
|
|
1479
|
+
})],
|
|
1480
|
+
json=_envelope([{"id": 1, "workflow_id": "wf-1", "step": "zeopp", "status": "running"}]),
|
|
1481
|
+
status=200,
|
|
1482
|
+
)
|
|
1483
|
+
df = api.get_computation_runs(workflow_id="wf-1", step="zeopp", status="running", limit=25, offset=5)
|
|
1484
|
+
assert_df_columns(df, "id", "workflow_id", "step", "status")
|
|
1485
|
+
|
|
1486
|
+
|
|
1487
|
+
@resp_lib.activate
|
|
1488
|
+
def test_get_computation_run_detail(api):
|
|
1489
|
+
resp_lib.add(
|
|
1490
|
+
resp_lib.GET,
|
|
1491
|
+
f"{PROD_BASE}/computation-runs/7/",
|
|
1492
|
+
json={"id": 7, "workflow_id": "wf-7", "step": "mofchecker", "status": "done"},
|
|
1493
|
+
status=200,
|
|
1494
|
+
)
|
|
1495
|
+
result = api.get_computation_run(7)
|
|
1496
|
+
assert result["id"] == 7
|
|
1497
|
+
assert result["status"] == "done"
|
|
1498
|
+
|
|
1499
|
+
|
|
1500
|
+
@resp_lib.activate
|
|
1501
|
+
def test_upsert_computation_runs_accepts_single_dict_and_new_fields(api):
|
|
1502
|
+
payload = {
|
|
1503
|
+
"id": 7,
|
|
1504
|
+
"workflow_id": "wf-7",
|
|
1505
|
+
"step": "adsorption",
|
|
1506
|
+
"status": "done",
|
|
1507
|
+
"new_field_from_server": "kept",
|
|
1508
|
+
}
|
|
1509
|
+
resp_lib.add(
|
|
1510
|
+
resp_lib.PUT,
|
|
1511
|
+
f"{PROD_BASE}/computation-runs/",
|
|
1512
|
+
match=[matchers.json_params_matcher([payload])],
|
|
1513
|
+
json={"created": 0, "updated": 1},
|
|
1514
|
+
status=200,
|
|
1515
|
+
)
|
|
1516
|
+
result = api.upsert_computation_runs(payload)
|
|
1517
|
+
assert result["updated"] == 1
|
|
1518
|
+
|
|
1519
|
+
|
|
1520
|
+
@resp_lib.activate
|
|
1521
|
+
def test_autoprism_detail_endpoints(api):
|
|
1522
|
+
detail_endpoints = [
|
|
1523
|
+
("/adsorption-singlepoint/1/", "get_adsorption_singlepoint_item", {"id": 1, "structure": "ABEXEM"}),
|
|
1524
|
+
("/heat-capacity/2/", "get_heat_capacity_item", {"id": 2, "structure": "ABEXEM"}),
|
|
1525
|
+
("/isotherm-h2/3/", "get_isotherm_h2_item", {"id": 3, "structure": "ABEXEM"}),
|
|
1526
|
+
("/mofchecker/4/", "get_mofchecker_item", {"id": 4, "structure": "ABEXEM"}),
|
|
1527
|
+
("/zeopp-metrics/5/", "get_zeopp_metrics_item", {"id": 5, "mof": "ABEXEM"}),
|
|
1528
|
+
]
|
|
1529
|
+
|
|
1530
|
+
for path, _, body in detail_endpoints:
|
|
1531
|
+
resp_lib.add(resp_lib.GET, f"{PROD_BASE}{path}", json=body, status=200)
|
|
1532
|
+
|
|
1533
|
+
assert api.get_adsorption_singlepoint_item(1)["id"] == 1
|
|
1534
|
+
assert api.get_heat_capacity_item(2)["id"] == 2
|
|
1535
|
+
assert api.get_isotherm_h2_item(3)["id"] == 3
|
|
1536
|
+
assert api.get_mofchecker_item(4)["id"] == 4
|
|
1537
|
+
assert api.get_zeopp_metrics_item(5)["id"] == 5
|
|
1538
|
+
|
|
1539
|
+
|
|
1540
|
+
@resp_lib.activate
|
|
1541
|
+
def test_upsert_autoprism_tables_accept_dataframes_and_pass_all_fields(api, monkeypatch):
|
|
1542
|
+
fake_meta = {
|
|
1543
|
+
"source_repo": "AutoPrism",
|
|
1544
|
+
"source_repo_semantic_version": "0.1.0",
|
|
1545
|
+
"source_repo_tag": "0.1.0",
|
|
1546
|
+
"source_commit_hash": "724b0306f6b21f953fba21e424cdda230525862f",
|
|
1547
|
+
}
|
|
1548
|
+
fake_repo_url = "https://github.com/AutoPrism/AutoPrism"
|
|
1549
|
+
monkeypatch.setattr(api, "_autoprism_meta_provenance", lambda: fake_meta)
|
|
1550
|
+
monkeypatch.setattr(api, "_autoprism_source_repo_url", lambda: fake_repo_url)
|
|
1551
|
+
|
|
1552
|
+
adsorption_df = pd.DataFrame([
|
|
1553
|
+
{
|
|
1554
|
+
"structure": "ABEXEM",
|
|
1555
|
+
"md5": "a1",
|
|
1556
|
+
"mixture_id": "mix-1",
|
|
1557
|
+
"component": "CO2",
|
|
1558
|
+
"temperature_K": 298.0,
|
|
1559
|
+
"pressure_bar": 1.0,
|
|
1560
|
+
"new_extra_field": "pass-through",
|
|
1561
|
+
}
|
|
1562
|
+
])
|
|
1563
|
+
heat_capacity_df = pd.DataFrame([
|
|
1564
|
+
{"structure": "ABEXEM", "temperature_K": 298.0, "Cp": 123.4, "new_extra_field": "pass-through"}
|
|
1565
|
+
])
|
|
1566
|
+
isotherm_h2_df = pd.DataFrame([
|
|
1567
|
+
{
|
|
1568
|
+
"structure": "ABEXEM",
|
|
1569
|
+
"md5": "a1",
|
|
1570
|
+
"isotherm_id": "iso-1",
|
|
1571
|
+
"component": "H2",
|
|
1572
|
+
"temperature_K": 298.0,
|
|
1573
|
+
"pressure_bar": 1.0,
|
|
1574
|
+
"uptake": 2.1,
|
|
1575
|
+
"new_extra_field": "pass-through",
|
|
1576
|
+
}
|
|
1577
|
+
])
|
|
1578
|
+
mofchecker_df = pd.DataFrame([
|
|
1579
|
+
{"structure": "ABEXEM", "md5": "a1", "is_mof": True, "MOFQ": "yes", "new_extra_field": "pass-through"}
|
|
1580
|
+
])
|
|
1581
|
+
zeopp_df = pd.DataFrame([
|
|
1582
|
+
{"mof": "ABEXEM", "md5": "a1", "probe": "N2", "pld": 3.4, "new_extra_field": "pass-through"}
|
|
1583
|
+
])
|
|
1584
|
+
|
|
1585
|
+
adsorption_expected = [{
|
|
1586
|
+
**adsorption_df.to_dict(orient="records")[0],
|
|
1587
|
+
"meta_provenance": {**fake_meta, "source_repo_url": fake_repo_url},
|
|
1588
|
+
}]
|
|
1589
|
+
heat_capacity_expected = [{
|
|
1590
|
+
**heat_capacity_df.to_dict(orient="records")[0],
|
|
1591
|
+
"meta_provenance": {**fake_meta, "source_repo_url": fake_repo_url},
|
|
1592
|
+
}]
|
|
1593
|
+
isotherm_h2_expected = [{
|
|
1594
|
+
**isotherm_h2_df.to_dict(orient="records")[0],
|
|
1595
|
+
"meta_provenance": fake_meta,
|
|
1596
|
+
}]
|
|
1597
|
+
mofchecker_expected = [{
|
|
1598
|
+
**mofchecker_df.to_dict(orient="records")[0],
|
|
1599
|
+
"meta_provenance": fake_meta,
|
|
1600
|
+
}]
|
|
1601
|
+
zeopp_expected = [{
|
|
1602
|
+
**zeopp_df.to_dict(orient="records")[0],
|
|
1603
|
+
"meta_provenance": fake_meta,
|
|
1604
|
+
}]
|
|
1605
|
+
|
|
1606
|
+
resp_lib.add(
|
|
1607
|
+
resp_lib.PUT,
|
|
1608
|
+
f"{PROD_BASE}/adsorption-singlepoint/",
|
|
1609
|
+
match=[matchers.json_params_matcher(adsorption_expected)],
|
|
1610
|
+
json={"created": 1, "updated": 0},
|
|
1611
|
+
status=200,
|
|
1612
|
+
)
|
|
1613
|
+
resp_lib.add(
|
|
1614
|
+
resp_lib.PUT,
|
|
1615
|
+
f"{PROD_BASE}/heat-capacity/",
|
|
1616
|
+
match=[matchers.json_params_matcher(heat_capacity_expected)],
|
|
1617
|
+
json={"created": 1, "updated": 0},
|
|
1618
|
+
status=200,
|
|
1619
|
+
)
|
|
1620
|
+
resp_lib.add(
|
|
1621
|
+
resp_lib.PUT,
|
|
1622
|
+
f"{PROD_BASE}/isotherm-h2/",
|
|
1623
|
+
match=[matchers.json_params_matcher(isotherm_h2_expected)],
|
|
1624
|
+
json={"created": 1, "updated": 0},
|
|
1625
|
+
status=200,
|
|
1626
|
+
)
|
|
1627
|
+
resp_lib.add(
|
|
1628
|
+
resp_lib.PUT,
|
|
1629
|
+
f"{PROD_BASE}/mofchecker/",
|
|
1630
|
+
match=[matchers.json_params_matcher(mofchecker_expected)],
|
|
1631
|
+
json={"created": 1, "updated": 0},
|
|
1632
|
+
status=200,
|
|
1633
|
+
)
|
|
1634
|
+
resp_lib.add(
|
|
1635
|
+
resp_lib.PUT,
|
|
1636
|
+
f"{PROD_BASE}/zeopp-metrics/",
|
|
1637
|
+
match=[matchers.json_params_matcher(zeopp_expected)],
|
|
1638
|
+
json={"created": 1, "updated": 0},
|
|
1639
|
+
status=200,
|
|
1640
|
+
)
|
|
1641
|
+
|
|
1642
|
+
assert api.upsert_adsorption_singlepoint(adsorption_df)["created"] == 1
|
|
1643
|
+
assert api.upsert_heat_capacity(heat_capacity_df)["created"] == 1
|
|
1644
|
+
assert api.upsert_isotherm_h2(isotherm_h2_df)["created"] == 1
|
|
1645
|
+
assert api.upsert_mofchecker(mofchecker_df)["created"] == 1
|
|
1646
|
+
assert api.upsert_zeopp_metrics(zeopp_df)["created"] == 1
|
|
1647
|
+
|
|
1648
|
+
|
|
1649
|
+
@resp_lib.activate
|
|
1650
|
+
def test_upsert_zeopp_metrics_accepts_wrapped_payload_and_overwrites_meta(api, monkeypatch):
|
|
1651
|
+
fake_meta = {
|
|
1652
|
+
"source_repo": "AutoPrism",
|
|
1653
|
+
"source_repo_semantic_version": "0.1.0",
|
|
1654
|
+
"source_repo_tag": "0.1.0",
|
|
1655
|
+
"source_commit_hash": "724b0306f6b21f953fba21e424cdda230525862f",
|
|
1656
|
+
}
|
|
1657
|
+
monkeypatch.setattr(api, "_autoprism_meta_provenance", lambda: fake_meta)
|
|
1658
|
+
|
|
1659
|
+
payload = {
|
|
1660
|
+
"zeopp_metrics": [
|
|
1661
|
+
{
|
|
1662
|
+
"mof": "UiO-66",
|
|
1663
|
+
"md5": "aaaaaaaa",
|
|
1664
|
+
"probe": "N2",
|
|
1665
|
+
"Di": 6.4,
|
|
1666
|
+
"meta_provenance": {
|
|
1667
|
+
"source_repo": "stale",
|
|
1668
|
+
"source_repo_semantic_version": "stale",
|
|
1669
|
+
"source_repo_tag": "stale",
|
|
1670
|
+
"source_commit_hash": "stale",
|
|
1671
|
+
},
|
|
1672
|
+
}
|
|
1673
|
+
],
|
|
1674
|
+
"meta_provenance": {
|
|
1675
|
+
"source_repo": "top-level-stale",
|
|
1676
|
+
"source_repo_semantic_version": "top-level-stale",
|
|
1677
|
+
"source_repo_tag": "top-level-stale",
|
|
1678
|
+
"source_commit_hash": "top-level-stale",
|
|
1679
|
+
},
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
expected = [{
|
|
1683
|
+
"mof": "UiO-66",
|
|
1684
|
+
"md5": "aaaaaaaa",
|
|
1685
|
+
"probe": "N2",
|
|
1686
|
+
"Di": 6.4,
|
|
1687
|
+
"meta_provenance": fake_meta,
|
|
1688
|
+
}]
|
|
1689
|
+
|
|
1690
|
+
resp_lib.add(
|
|
1691
|
+
resp_lib.PUT,
|
|
1692
|
+
f"{PROD_BASE}/zeopp-metrics/",
|
|
1693
|
+
match=[matchers.json_params_matcher(expected)],
|
|
1694
|
+
json={"created": 1, "updated": 0},
|
|
1695
|
+
status=200,
|
|
1696
|
+
)
|
|
1697
|
+
|
|
1698
|
+
result = api.upsert_zeopp_metrics(payload)
|
|
1699
|
+
assert result["created"] == 1
|
|
1700
|
+
|
|
1701
|
+
|
|
1702
|
+
def test_upsert_autoprism_collection_dispatches_sections(api, monkeypatch):
|
|
1703
|
+
monkeypatch.setattr(api, "upsert_computation_runs", lambda payload: {"created": 1, "updated": 0})
|
|
1704
|
+
monkeypatch.setattr(api, "upsert_adsorption_singlepoint", lambda payload: {"created": 2, "updated": 0})
|
|
1705
|
+
monkeypatch.setattr(api, "upsert_heat_capacity", lambda payload: {"created": 0, "updated": 3})
|
|
1706
|
+
monkeypatch.setattr(api, "upsert_isotherm_h2", lambda payload: {"created": 4, "updated": 1})
|
|
1707
|
+
monkeypatch.setattr(api, "upsert_mofchecker", lambda payload: {"created": 5, "updated": 0})
|
|
1708
|
+
monkeypatch.setattr(api, "upsert_zeopp_metrics", lambda payload: {"created": 6, "updated": 2})
|
|
1709
|
+
|
|
1710
|
+
payload = {
|
|
1711
|
+
"computation_runs": [{"id": "run-1"}],
|
|
1712
|
+
"adsorption_singlepoints": [{"id": 1}],
|
|
1713
|
+
"heat_capacities": [{"id": 2}],
|
|
1714
|
+
"isotherm_H2s": [{"id": 3}],
|
|
1715
|
+
"mofchecker": [{"id": 4}],
|
|
1716
|
+
"zeopp_metrics": [{"id": 5}],
|
|
1717
|
+
"meta_provenance": {"source_repo": "ignored"},
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
result = api.upsert_autoprism_collection(payload)
|
|
1721
|
+
|
|
1722
|
+
assert result["overall_status"] == "ok"
|
|
1723
|
+
assert result["totals"]["created"] == 18
|
|
1724
|
+
assert result["totals"]["updated"] == 6
|
|
1725
|
+
assert result["totals"]["failed_sections"] == 0
|
|
1726
|
+
assert result["sections"]["zeopp_metrics"]["status"] == "ok"
|
|
1727
|
+
assert result["sections"]["mofchecker"]["status"] == "ok"
|
|
1728
|
+
|
|
1729
|
+
|
|
1730
|
+
def test_upsert_autoprism_collection_reports_skips_and_errors(api, monkeypatch):
|
|
1731
|
+
monkeypatch.setattr(api, "upsert_computation_runs", lambda payload: {"created": 1, "updated": 0})
|
|
1732
|
+
|
|
1733
|
+
def _raise_error(payload):
|
|
1734
|
+
raise RuntimeError("boom")
|
|
1735
|
+
|
|
1736
|
+
monkeypatch.setattr(api, "upsert_zeopp_metrics", _raise_error)
|
|
1737
|
+
|
|
1738
|
+
payload = {
|
|
1739
|
+
"computation_runs": [{"id": "run-1"}],
|
|
1740
|
+
"zeopp_metrics": [{"id": 5}],
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
result = api.upsert_autoprism_collection(payload)
|
|
1744
|
+
|
|
1745
|
+
assert result["overall_status"] == "partial_failure"
|
|
1746
|
+
assert result["totals"]["created"] == 1
|
|
1747
|
+
assert result["totals"]["updated"] == 0
|
|
1748
|
+
assert result["totals"]["failed_sections"] == 1
|
|
1749
|
+
assert result["sections"]["computation_runs"]["status"] == "ok"
|
|
1750
|
+
assert result["sections"]["adsorption_singlepoints"]["status"] == "skipped"
|
|
1751
|
+
assert result["sections"]["zeopp_metrics"]["status"] == "error"
|
|
1752
|
+
|
|
1753
|
+
|
|
1754
|
+
def test_get_autoprism_collection_allows_empty_payloads(api, monkeypatch):
|
|
1755
|
+
monkeypatch.setattr(api, "get_computation_runs", lambda **kwargs: None)
|
|
1756
|
+
monkeypatch.setattr(api, "get_adsorption_singlepoint", lambda **kwargs: None)
|
|
1757
|
+
monkeypatch.setattr(api, "get_heat_capacity", lambda **kwargs: {"results": []})
|
|
1758
|
+
monkeypatch.setattr(api, "get_isotherm_h2", lambda **kwargs: 0)
|
|
1759
|
+
monkeypatch.setattr(api, "get_mofchecker", lambda **kwargs: [])
|
|
1760
|
+
monkeypatch.setattr(api, "get_zeopp_metrics", lambda **kwargs: pd.DataFrame())
|
|
1761
|
+
|
|
1762
|
+
collection = api.get_autoprism_collection(mof="ABEXEM")
|
|
1763
|
+
|
|
1764
|
+
assert set(collection.keys()) == {
|
|
1765
|
+
"computation_runs",
|
|
1766
|
+
"adsorption_singlepoints",
|
|
1767
|
+
"heat_capacities",
|
|
1768
|
+
"isotherm_H2s",
|
|
1769
|
+
"mofchecker",
|
|
1770
|
+
"zeopp_metrics",
|
|
1771
|
+
"meta_provenance",
|
|
1772
|
+
}
|
|
1773
|
+
assert len(collection["computation_runs"]) == 0
|
|
1774
|
+
assert len(collection["adsorption_singlepoints"]) == 0
|
|
1775
|
+
assert len(collection["heat_capacities"]) == 0
|
|
1776
|
+
assert len(collection["isotherm_H2s"]) == 0
|
|
1777
|
+
assert len(collection["mofchecker"]) == 0
|
|
1778
|
+
assert len(collection["zeopp_metrics"]) == 0
|
|
1779
|
+
|
|
1780
|
+
|
|
1781
|
+
def test_get_autoprism_collection_tolerates_endpoint_http_error(api, monkeypatch):
|
|
1782
|
+
import requests
|
|
1783
|
+
|
|
1784
|
+
def _raise_http_error(**kwargs):
|
|
1785
|
+
raise requests.HTTPError("500 Server Error")
|
|
1786
|
+
|
|
1787
|
+
monkeypatch.setattr(api, "get_computation_runs", _raise_http_error)
|
|
1788
|
+
monkeypatch.setattr(api, "get_adsorption_singlepoint", lambda **kwargs: [{"id": 1}])
|
|
1789
|
+
monkeypatch.setattr(api, "get_heat_capacity", lambda **kwargs: [{"id": 2}])
|
|
1790
|
+
monkeypatch.setattr(api, "get_isotherm_h2", lambda **kwargs: [{"id": 3}])
|
|
1791
|
+
monkeypatch.setattr(api, "get_mofchecker", lambda **kwargs: [{"id": 4}])
|
|
1792
|
+
monkeypatch.setattr(api, "get_zeopp_metrics", lambda **kwargs: [{"id": 5}])
|
|
1793
|
+
|
|
1794
|
+
collection = api.get_autoprism_collection(mof="ABEXEM")
|
|
1795
|
+
|
|
1796
|
+
assert len(collection["computation_runs"]) == 0
|
|
1797
|
+
assert len(collection["adsorption_singlepoints"]) == 1
|
|
1798
|
+
assert len(collection["heat_capacities"]) == 1
|
|
1799
|
+
assert len(collection["isotherm_H2s"]) == 1
|
|
1800
|
+
assert len(collection["mofchecker"]) == 1
|
|
1801
|
+
assert len(collection["zeopp_metrics"]) == 1
|
|
1802
|
+
|
|
1803
|
+
|
|
1804
|
+
def test_get_autoprism_collection_includes_mofchecker_payload_shape(api, monkeypatch):
|
|
1805
|
+
monkeypatch.setattr(api, "get_computation_runs", lambda **kwargs: [])
|
|
1806
|
+
monkeypatch.setattr(api, "get_adsorption_singlepoint", lambda **kwargs: [])
|
|
1807
|
+
monkeypatch.setattr(api, "get_heat_capacity", lambda **kwargs: [])
|
|
1808
|
+
monkeypatch.setattr(api, "get_isotherm_h2", lambda **kwargs: [])
|
|
1809
|
+
monkeypatch.setattr(api, "get_zeopp_metrics", lambda **kwargs: [])
|
|
1810
|
+
monkeypatch.setattr(
|
|
1811
|
+
api,
|
|
1812
|
+
"get_mofchecker",
|
|
1813
|
+
lambda **kwargs: [{"id": 4, "structure": {"name": "ABEXEM"}, "is_mof": True}],
|
|
1814
|
+
)
|
|
1815
|
+
|
|
1816
|
+
collection = api.get_autoprism_collection(mof="ABEXEM")
|
|
1817
|
+
|
|
1818
|
+
assert "mofchecker" in collection
|
|
1819
|
+
assert "meta_provenance" in collection
|
|
1820
|
+
assert isinstance(collection["mofchecker"], list)
|
|
1821
|
+
assert len(collection["mofchecker"]) == 1
|
|
1822
|
+
|
|
1823
|
+
meta = collection["meta_provenance"]
|
|
1824
|
+
assert set(meta.keys()) >= {
|
|
1825
|
+
"source_repo",
|
|
1826
|
+
"source_repo_url",
|
|
1827
|
+
"source_repo_semantic_version",
|
|
1828
|
+
"source_repo_tag",
|
|
1829
|
+
"source_commit_hash",
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
|
|
1833
|
+
def test_get_autoprism_collection_includes_zeopp_payload_shape(api, monkeypatch):
|
|
1834
|
+
monkeypatch.setattr(api, "get_computation_runs", lambda **kwargs: [])
|
|
1835
|
+
monkeypatch.setattr(api, "get_adsorption_singlepoint", lambda **kwargs: [])
|
|
1836
|
+
monkeypatch.setattr(api, "get_heat_capacity", lambda **kwargs: [])
|
|
1837
|
+
monkeypatch.setattr(api, "get_isotherm_h2", lambda **kwargs: [])
|
|
1838
|
+
monkeypatch.setattr(api, "get_mofchecker", lambda **kwargs: [])
|
|
1839
|
+
monkeypatch.setattr(
|
|
1840
|
+
api,
|
|
1841
|
+
"get_zeopp_metrics",
|
|
1842
|
+
lambda **kwargs: [{"id": 5, "mof": "UiO-66", "probe": "N2"}],
|
|
1843
|
+
)
|
|
1844
|
+
|
|
1845
|
+
collection = api.get_autoprism_collection(mof="UiO-66")
|
|
1846
|
+
|
|
1847
|
+
assert "zeopp_metrics" in collection
|
|
1848
|
+
assert "meta_provenance" in collection
|
|
1849
|
+
assert isinstance(collection["zeopp_metrics"], list)
|
|
1850
|
+
assert len(collection["zeopp_metrics"]) == 1
|
|
1851
|
+
|
|
1852
|
+
meta = collection["meta_provenance"]
|
|
1853
|
+
assert set(meta.keys()) >= {
|
|
1854
|
+
"source_repo",
|
|
1855
|
+
"source_repo_url",
|
|
1856
|
+
"source_repo_semantic_version",
|
|
1857
|
+
"source_repo_tag",
|
|
1858
|
+
"source_commit_hash",
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
|
|
1297
1862
|
# ── Output KPIs ───────────────────────────────────────────────────────────────
|
|
1298
1863
|
|
|
1299
1864
|
@resp_lib.activate
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|