prisma-api 0.3.5__tar.gz → 0.3.7__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: prisma_api
3
- Version: 0.3.5
3
+ Version: 0.3.7
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
@@ -20,6 +20,9 @@ Dynamic: license-file
20
20
 
21
21
  # PrISMa API
22
22
 
23
+ [![CI](https://github.com/RCCS-CaptureTeam/prisma_api/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/RCCS-CaptureTeam/prisma_api/actions/workflows/ci.yml)
24
+ [![Coverage](https://codecov.io/gh/RCCS-CaptureTeam/prisma_api/graph/badge.svg?branch=main)](https://codecov.io/gh/RCCS-CaptureTeam/prisma_api)
25
+
23
26
  Python client for the PrISMa platform APIs.
24
27
 
25
28
  This package provides:
@@ -200,6 +203,7 @@ All methods below are available on `api.v2`.
200
203
 
201
204
  ### Case bundles and pack builders
202
205
 
206
+ - `list_case_studies(name=None, limit=500, offset=0)`
203
207
  - `get_cases_bundle(name=None, source=None, sink=None, region=None, limit_cases=100, limit_props=2000)`
204
208
  - `build_case_spec(case_id)`
205
209
  - `build_scenario_spec(scenario_id)`
@@ -275,6 +279,7 @@ All methods below are available on `api.v2`.
275
279
  - `get_case(case_id)`
276
280
  - `get_scenarios(case_id=None, name=None, type=None, limit=500, offset=0)`
277
281
  - `get_scenario(scenario_id)`
282
+ - `get_screening_analysis_bundle(analysis_id)`
278
283
  - `get_screening_summaries(scenario_id=None, limit=500, offset=0)`
279
284
  - `get_screening_summary(summary_id)`
280
285
 
@@ -1,5 +1,8 @@
1
1
  # PrISMa API
2
2
 
3
+ [![CI](https://github.com/RCCS-CaptureTeam/prisma_api/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/RCCS-CaptureTeam/prisma_api/actions/workflows/ci.yml)
4
+ [![Coverage](https://codecov.io/gh/RCCS-CaptureTeam/prisma_api/graph/badge.svg?branch=main)](https://codecov.io/gh/RCCS-CaptureTeam/prisma_api)
5
+
3
6
  Python client for the PrISMa platform APIs.
4
7
 
5
8
  This package provides:
@@ -180,6 +183,7 @@ All methods below are available on `api.v2`.
180
183
 
181
184
  ### Case bundles and pack builders
182
185
 
186
+ - `list_case_studies(name=None, limit=500, offset=0)`
183
187
  - `get_cases_bundle(name=None, source=None, sink=None, region=None, limit_cases=100, limit_props=2000)`
184
188
  - `build_case_spec(case_id)`
185
189
  - `build_scenario_spec(scenario_id)`
@@ -255,6 +259,7 @@ All methods below are available on `api.v2`.
255
259
  - `get_case(case_id)`
256
260
  - `get_scenarios(case_id=None, name=None, type=None, limit=500, offset=0)`
257
261
  - `get_scenario(scenario_id)`
262
+ - `get_screening_analysis_bundle(analysis_id)`
258
263
  - `get_screening_summaries(scenario_id=None, limit=500, offset=0)`
259
264
  - `get_screening_summary(summary_id)`
260
265
 
@@ -1,4 +1,4 @@
1
- __version__ = "0.3.5"
1
+ __version__ = "0.3.7"
2
2
 
3
3
 
4
4
  from prisma_api.prisma_api import prisma_api as init # Main prisma_api class for initialisation
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import warnings
2
3
  from .config import get_or_create_config, update_dev_mode as _update_dev_mode
3
4
  from .prisma_api_v2 import PrismaAPIv2
4
5
  from pathlib import Path
@@ -24,23 +25,25 @@ def _safe_nan_check(x):
24
25
  # prisma_api main class
25
26
  class prisma_api():
26
27
 
27
- def __init__(self, use_config_file=True):
28
+ def __init__(self, use_config_file=True, local_dev: bool = False):
28
29
 
29
30
  # Initialise `prisma_api` object with api_key location
30
31
  self.verbose = False
31
32
  # Initialise `prisma_api` object with api_key location
32
33
  if use_config_file:
33
34
  cfg = get_or_create_config()
34
- self.key = cfg['api_key']
35
- self.dev = cfg.get('dev', False)
36
- if self.dev:
37
- self.dev_host_port = cfg['dev_host_port']
38
- self.key = cfg['dev_api_key']
35
+ prod_key = cfg.get('api_key', '')
36
+ dev_key = cfg.get('dev_api_key', prod_key)
37
+ dev_host_port = cfg.get('dev_host_port', '8000')
39
38
  else:
40
- self.key = os.getenv('PRISMA_API_KEY', '')
41
- self.dev = os.getenv('PRISMA_API_DEV', 'False').lower() in ('true', '1', 't')
42
- if self.dev:
43
- self.dev_host_port = os.getenv('PRISMA_API_DEV_HOST_PORT', '')
39
+ prod_key = os.getenv('PRISMA_API_KEY', '')
40
+ dev_key = os.getenv('PRISMA_API_DEV_API_KEY', prod_key)
41
+ dev_host_port = os.getenv('PRISMA_API_DEV_HOST_PORT', '8000')
42
+
43
+ # local_dev now controls target DB explicitly at init time.
44
+ self.dev = bool(local_dev)
45
+ self.dev_host_port = dev_host_port
46
+ self.key = dev_key if self.dev else prod_key
44
47
 
45
48
  self.v2 = PrismaAPIv2(
46
49
  key=self.key,
@@ -69,6 +72,12 @@ class prisma_api():
69
72
  Returns:
70
73
  dict: Updated config.
71
74
  """
75
+ warnings.warn(
76
+ "update_dev_mode() is deprecated and will be removed in a future release. "
77
+ "Use prisma_api.init(local_dev=True|False) instead.",
78
+ FutureWarning,
79
+ stacklevel=2,
80
+ )
72
81
  return _update_dev_mode(dev)
73
82
 
74
83
  def get_mofs(self, payload={}):
@@ -19,6 +19,8 @@ from __future__ import annotations
19
19
  import pandas as pd
20
20
  import requests
21
21
  from typing import Any
22
+ from urllib.parse import urlencode
23
+ import warnings
22
24
 
23
25
 
24
26
  _BASE_PROD = "https://prisma-platform.org/api/v2"
@@ -143,6 +145,7 @@ class PrismaAPIv2:
143
145
  def upsert_flowsheets(
144
146
  self,
145
147
  flowsheets: pd.DataFrame | list[dict],
148
+ screening_analysis_name: str | None = None,
146
149
  on_exists: str = "append",
147
150
  appendix: str = "_v4",
148
151
  ) -> dict:
@@ -155,6 +158,10 @@ class PrismaAPIv2:
155
158
 
156
159
  Args:
157
160
  flowsheets: DataFrame or list of dict payload records.
161
+ screening_analysis_name: Optional analysis identifier forwarded to the
162
+ API for upsert attribution/routing. For production uploads this
163
+ should be set and should match one of the nested screening
164
+ analysis names returned by ``list_case_studies()``.
158
165
  on_exists: Conflict mode; one of ``'append'`` or ``'overwrite'``.
159
166
  appendix: Suffix used only in append mode (default ``'_v4'``).
160
167
 
@@ -169,11 +176,28 @@ class PrismaAPIv2:
169
176
  if isinstance(flowsheets, pd.DataFrame)
170
177
  else flowsheets
171
178
  )
179
+ cleaned_screening_name = (screening_analysis_name or "").strip()
172
180
 
173
- if on_exists == "overwrite":
174
- path = "/flowsheets/upsert/?on_exists=overwrite"
181
+ query_params: dict[str, str] = (
182
+ {"on_exists": "overwrite"}
183
+ if on_exists == "overwrite"
184
+ else {"on_exists": "append", "appendix": appendix}
185
+ )
186
+
187
+ if cleaned_screening_name:
188
+ query_params["screening_analysis_name"] = cleaned_screening_name
175
189
  else:
176
- path = f"/flowsheets/upsert/?on_exists=append&appendix={appendix}"
190
+ warnings.warn(
191
+ "Uploading flowsheets without screening_analysis_name is intended "
192
+ "for experimentation only and is not recommended for production. "
193
+ "Provide screening_analysis_name matching one of the nested "
194
+ "screening analysis names returned by list_case_studies().",
195
+ UserWarning,
196
+ stacklevel=2,
197
+ )
198
+
199
+ query = urlencode(query_params)
200
+ path = f"/flowsheets/upsert/?{query}"
177
201
 
178
202
  return self._put(path, records)
179
203
 
@@ -1265,6 +1289,21 @@ class PrismaAPIv2:
1265
1289
  """GET /api/v2/cases/{case_id}/"""
1266
1290
  return self._get(f"/cases/{case_id}/")
1267
1291
 
1292
+ def list_case_studies(self,
1293
+ name: str | None = None,
1294
+ limit: int = 500,
1295
+ offset: int = 0) -> pd.DataFrame:
1296
+ """
1297
+ GET /api/v2/case-studies/
1298
+
1299
+ Args:
1300
+ name: Optional substring filter on case-study name.
1301
+ limit: Maximum number of records to return.
1302
+ offset: Pagination offset.
1303
+ """
1304
+ params = _compact(name=name, limit=limit, offset=offset)
1305
+ return self._to_df(self._get("/case-studies/", params))
1306
+
1268
1307
  def get_scenarios(self,
1269
1308
  case_id: int | None = None,
1270
1309
  name: str | None = None,
@@ -1287,6 +1326,10 @@ class PrismaAPIv2:
1287
1326
  """GET /api/v2/scenarios/{scenario_id}/"""
1288
1327
  return self._get(f"/scenarios/{scenario_id}/")
1289
1328
 
1329
+ def get_screening_analysis_bundle(self, analysis_id: int) -> dict:
1330
+ """GET /api/v2/screening-analyses/{analysis_id}/bundle/"""
1331
+ return self._get(f"/screening-analyses/{analysis_id}/bundle/")
1332
+
1290
1333
  # ── Case-pack builders (ImportedCasePack spec) ────────────────────────────
1291
1334
 
1292
1335
  @staticmethod
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: prisma_api
3
- Version: 0.3.5
3
+ Version: 0.3.7
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
@@ -20,6 +20,9 @@ Dynamic: license-file
20
20
 
21
21
  # PrISMa API
22
22
 
23
+ [![CI](https://github.com/RCCS-CaptureTeam/prisma_api/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/RCCS-CaptureTeam/prisma_api/actions/workflows/ci.yml)
24
+ [![Coverage](https://codecov.io/gh/RCCS-CaptureTeam/prisma_api/graph/badge.svg?branch=main)](https://codecov.io/gh/RCCS-CaptureTeam/prisma_api)
25
+
23
26
  Python client for the PrISMa platform APIs.
24
27
 
25
28
  This package provides:
@@ -200,6 +203,7 @@ All methods below are available on `api.v2`.
200
203
 
201
204
  ### Case bundles and pack builders
202
205
 
206
+ - `list_case_studies(name=None, limit=500, offset=0)`
203
207
  - `get_cases_bundle(name=None, source=None, sink=None, region=None, limit_cases=100, limit_props=2000)`
204
208
  - `build_case_spec(case_id)`
205
209
  - `build_scenario_spec(scenario_id)`
@@ -275,6 +279,7 @@ All methods below are available on `api.v2`.
275
279
  - `get_case(case_id)`
276
280
  - `get_scenarios(case_id=None, name=None, type=None, limit=500, offset=0)`
277
281
  - `get_scenario(scenario_id)`
282
+ - `get_screening_analysis_bundle(analysis_id)`
278
283
  - `get_screening_summaries(scenario_id=None, limit=500, offset=0)`
279
284
  - `get_screening_summary(summary_id)`
280
285
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "prisma_api"
7
- version = "0.3.5"
7
+ version = "0.3.7"
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"}
@@ -94,6 +94,66 @@ def test_v2_is_attached():
94
94
  assert isinstance(api.v2, PrismaAPIv2)
95
95
 
96
96
 
97
+ @patch("prisma_api.prisma_api.get_or_create_config")
98
+ def test_init_local_dev_false_uses_prod_config(mock_cfg):
99
+ from prisma_api.prisma_api import prisma_api
100
+
101
+ mock_cfg.return_value = {
102
+ "api_key": "prod-key",
103
+ "dev_api_key": "dev-key",
104
+ "dev_host_port": "8000",
105
+ "dev": True,
106
+ }
107
+ api = prisma_api(local_dev=False)
108
+ assert api.dev is False
109
+ assert api.key == "prod-key"
110
+ assert api.v2._dev is False
111
+
112
+
113
+ @patch("prisma_api.prisma_api.get_or_create_config")
114
+ def test_init_local_dev_true_uses_dev_config(mock_cfg):
115
+ from prisma_api.prisma_api import prisma_api
116
+
117
+ mock_cfg.return_value = {
118
+ "api_key": "prod-key",
119
+ "dev_api_key": "dev-key",
120
+ "dev_host_port": "8000",
121
+ "dev": False,
122
+ }
123
+ api = prisma_api(local_dev=True)
124
+ assert api.dev is True
125
+ assert api.key == "dev-key"
126
+ assert api.dev_host_port == "8000"
127
+ assert api.v2._dev is True
128
+
129
+
130
+ def test_init_local_dev_true_uses_env_when_no_config(monkeypatch):
131
+ from prisma_api.prisma_api import prisma_api
132
+
133
+ monkeypatch.setenv("PRISMA_API_KEY", "prod-env-key")
134
+ monkeypatch.setenv("PRISMA_API_DEV_API_KEY", "dev-env-key")
135
+ monkeypatch.setenv("PRISMA_API_DEV_HOST_PORT", "9001")
136
+
137
+ api = prisma_api(use_config_file=False, local_dev=True)
138
+ assert api.dev is True
139
+ assert api.key == "dev-env-key"
140
+ assert api.dev_host_port == "9001"
141
+
142
+
143
+ @patch("prisma_api.prisma_api._update_dev_mode")
144
+ def test_update_dev_mode_emits_deprecation_warning(mock_update):
145
+ from prisma_api.prisma_api import prisma_api
146
+
147
+ mock_update.return_value = {"dev": False}
148
+ api = prisma_api.__new__(prisma_api)
149
+
150
+ with pytest.warns(FutureWarning, match="deprecated"):
151
+ result = api.update_dev_mode(False)
152
+
153
+ assert result == {"dev": False}
154
+ mock_update.assert_called_once_with(False)
155
+
156
+
97
157
  # ── get_materials_data: basic response ────────────────────────────────────────
98
158
 
99
159
  @resp_lib.activate
@@ -1188,6 +1188,24 @@ def test_get_case_detail(api):
1188
1188
  assert result["name"] == "UK Coal CCS 2030"
1189
1189
 
1190
1190
 
1191
+ @resp_lib.activate
1192
+ def test_list_case_studies_returns_dataframe(api):
1193
+ records = [{"id": 1, "name": "Alpha 2030"}, {"id": 2, "name": "Beta 2040"}]
1194
+ resp_lib.add(resp_lib.GET, f"{PROD_BASE}/case-studies/",
1195
+ json=_envelope(records), status=200)
1196
+ df = api.list_case_studies()
1197
+ assert_df_columns(df, "id", "name")
1198
+
1199
+
1200
+ @resp_lib.activate
1201
+ def test_list_case_studies_name_filter_passed(api):
1202
+ expected = {"name": "Alpha", "limit": "500", "offset": "0"}
1203
+ resp_lib.add(resp_lib.GET, f"{PROD_BASE}/case-studies/",
1204
+ match=[matchers.query_param_matcher(expected)],
1205
+ json=_envelope([]), status=200)
1206
+ api.list_case_studies(name="Alpha")
1207
+
1208
+
1191
1209
  @resp_lib.activate
1192
1210
  def test_get_scenarios_returns_dataframe(api):
1193
1211
  records = [{"id": 830, "name": "baseline_2030", "print_name": "Baseline 2030",
@@ -1216,6 +1234,15 @@ def test_get_scenario_detail(api):
1216
1234
  assert result["type"] == "TEA"
1217
1235
 
1218
1236
 
1237
+ @resp_lib.activate
1238
+ def test_get_screening_analysis_bundle_detail(api):
1239
+ bundle = {"analysis_id": 123, "case_study": {"name": "Alpha 2030"}, "results": []}
1240
+ resp_lib.add(resp_lib.GET, f"{PROD_BASE}/screening-analyses/123/bundle/",
1241
+ json=bundle, status=200)
1242
+ result = api.get_screening_analysis_bundle(123)
1243
+ assert result["analysis_id"] == 123
1244
+
1245
+
1219
1246
  # ── ImportedCasePack builders ─────────────────────────────────────────────────
1220
1247
 
1221
1248
  _CASE_DETAIL = {
@@ -1491,12 +1518,12 @@ def test_get_flowsheet_bundle_custom_name_routes_path(api):
1491
1518
  def test_upsert_flowsheets_default_append_mode(api):
1492
1519
  resp_lib.add(
1493
1520
  resp_lib.PUT,
1494
- f"{PROD_BASE}/flowsheets/upsert/?on_exists=append&appendix=_v4",
1521
+ f"{PROD_BASE}/flowsheets/upsert/?on_exists=append&appendix=_v4&screening_analysis_name=screening_case",
1495
1522
  json={"created": 1, "updated": 0},
1496
1523
  status=200,
1497
1524
  )
1498
1525
  payload = _load_upsert_flowsheet_payload()
1499
- result = api.upsert_flowsheets(payload)
1526
+ result = api.upsert_flowsheets(payload, screening_analysis_name="screening_case")
1500
1527
  assert result["created"] == 1
1501
1528
 
1502
1529
 
@@ -1504,12 +1531,16 @@ def test_upsert_flowsheets_default_append_mode(api):
1504
1531
  def test_upsert_flowsheets_overwrite_mode(api):
1505
1532
  resp_lib.add(
1506
1533
  resp_lib.PUT,
1507
- f"{PROD_BASE}/flowsheets/upsert/?on_exists=overwrite",
1534
+ f"{PROD_BASE}/flowsheets/upsert/?on_exists=overwrite&screening_analysis_name=screening_case",
1508
1535
  json={"created": 0, "updated": 1},
1509
1536
  status=200,
1510
1537
  )
1511
1538
  payload = _load_upsert_flowsheet_payload()
1512
- result = api.upsert_flowsheets(payload, on_exists="overwrite")
1539
+ result = api.upsert_flowsheets(
1540
+ payload,
1541
+ screening_analysis_name="screening_case",
1542
+ on_exists="overwrite",
1543
+ )
1513
1544
  assert result["updated"] == 1
1514
1545
 
1515
1546
 
@@ -1517,16 +1548,52 @@ def test_upsert_flowsheets_overwrite_mode(api):
1517
1548
  def test_upsert_flowsheets_uses_dev_mode_base_url(dev_api):
1518
1549
  resp_lib.add(
1519
1550
  resp_lib.PUT,
1520
- "http://localhost:8000/api/v2/flowsheets/upsert/?on_exists=append&appendix=_dev",
1551
+ "http://localhost:8000/api/v2/flowsheets/upsert/?on_exists=append&appendix=_dev&screening_analysis_name=screening_dev",
1521
1552
  json={"created": 1, "updated": 0},
1522
1553
  status=200,
1523
1554
  )
1524
1555
  payload = _load_upsert_flowsheet_payload()
1525
- result = dev_api.upsert_flowsheets(payload, appendix="_dev")
1556
+ result = dev_api.upsert_flowsheets(
1557
+ payload,
1558
+ screening_analysis_name="screening_dev",
1559
+ appendix="_dev",
1560
+ )
1526
1561
  assert result["created"] == 1
1527
1562
 
1528
1563
 
1529
1564
  def test_upsert_flowsheets_rejects_invalid_on_exists(api):
1530
1565
  payload = _load_upsert_flowsheet_payload()
1531
1566
  with pytest.raises(ValueError):
1532
- api.upsert_flowsheets(payload, on_exists="replace")
1567
+ api.upsert_flowsheets(
1568
+ payload,
1569
+ screening_analysis_name="screening_case",
1570
+ on_exists="replace",
1571
+ )
1572
+
1573
+
1574
+ @resp_lib.activate
1575
+ def test_upsert_flowsheets_allows_missing_screening_analysis_name_with_warning(api):
1576
+ resp_lib.add(
1577
+ resp_lib.PUT,
1578
+ f"{PROD_BASE}/flowsheets/upsert/?on_exists=append&appendix=_v4",
1579
+ json={"created": 1, "updated": 0},
1580
+ status=200,
1581
+ )
1582
+ payload = _load_upsert_flowsheet_payload()
1583
+ with pytest.warns(UserWarning, match="for experimentation only"):
1584
+ result = api.upsert_flowsheets(payload)
1585
+ assert result["created"] == 1
1586
+
1587
+
1588
+ @resp_lib.activate
1589
+ def test_upsert_flowsheets_blank_screening_analysis_name_warns_and_omits_query_param(api):
1590
+ resp_lib.add(
1591
+ resp_lib.PUT,
1592
+ f"{PROD_BASE}/flowsheets/upsert/?on_exists=overwrite",
1593
+ json={"created": 0, "updated": 1},
1594
+ status=200,
1595
+ )
1596
+ payload = _load_upsert_flowsheet_payload()
1597
+ with pytest.warns(UserWarning, match="list_case_studies"):
1598
+ result = api.upsert_flowsheets(payload, screening_analysis_name=" ", on_exists="overwrite")
1599
+ assert result["updated"] == 1
File without changes
File without changes