Skip to content

Commit 19fb379

Browse files
authored
Merge pull request #42 from Exabyte-io/feature/SOF-7824
update: list properties
2 parents 69fa49b + a329f36 commit 19fb379

2 files changed

Lines changed: 99 additions & 0 deletions

File tree

src/py/mat3ra/api_client/endpoints/properties.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,41 @@ def delete(self, id_):
5151

5252
def update(self, id_, modifier):
5353
raise NotImplementedError
54+
55+
def list_for_job(self, job_id):
56+
"""
57+
List properties for a job grouped by unit.
58+
59+
Args:
60+
job_id (str): Job ID.
61+
62+
Returns:
63+
list[dict]: List of {"unit_id": str, "properties": [str, ...]}.
64+
"""
65+
properties = self.list(query={"source.info.jobId": job_id})
66+
units = {}
67+
for prop in properties:
68+
unit_id = prop["source"]["info"]["unitId"]
69+
if unit_id not in units:
70+
units[unit_id] = []
71+
units[unit_id].append(prop["data"]["name"])
72+
return [{"unit_id": unit_id, "properties": names} for unit_id, names in units.items()]
73+
74+
def get_for_job(self, job_id, property_name=None, unit_id=None):
75+
"""
76+
Get property data for a job, optionally filtered by property name and/or unit.
77+
78+
Args:
79+
job_id (str): Job ID.
80+
property_name (str, optional): Property name (e.g., "band_gaps", "total_energy").
81+
unit_id (str, optional): Unit flowchart ID (e.g., "pw-nscf").
82+
83+
Returns:
84+
list[dict]: List of property data dicts.
85+
"""
86+
query = {"source.info.jobId": job_id}
87+
if property_name:
88+
query["data.name"] = property_name
89+
if unit_id:
90+
query["source.info.unitId"] = unit_id
91+
return [prop["data"] for prop in self.list(query=query)]

tests/py/unit/test_properties.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,29 @@
1+
import json
12
from unittest import mock
23

34
from mat3ra.api_client.endpoints.properties import PropertiesEndpoints
45
from tests.py.unit.entity import EntityEndpointsUnitTest
56

67
ENDPOINT_NAME = "properties"
78

9+
JOB_ID = "ukmnfWw9Q5ryXHK4X"
10+
PROPERTY_NAME_0 = "total_energy"
11+
PROPERTY_NAME_1 = "band_gaps"
12+
UNIT_ID_0 = "pw-relax"
13+
UNIT_ID_1 = "pw-nscf"
14+
15+
MOCK_PROPERTY_0 = {
16+
"data": {"name": PROPERTY_NAME_0, "value": -260.698, "units": "eV"},
17+
"source": {"info": {"jobId": JOB_ID, "unitId": UNIT_ID_0}},
18+
}
19+
MOCK_PROPERTY_1 = {
20+
"data": {"name": PROPERTY_NAME_1, "values": [{"type": "direct", "value": 0.5, "units": "eV"}]},
21+
"source": {"info": {"jobId": JOB_ID, "unitId": UNIT_ID_1}},
22+
}
23+
24+
MOCK_PROPERTIES_RESPONSE = json.dumps({"status": "success", "data": [MOCK_PROPERTY_0, MOCK_PROPERTY_1]})
25+
MOCK_SINGLE_PROPERTY_RESPONSE = json.dumps({"status": "success", "data": [MOCK_PROPERTY_1]})
26+
827

928
class EndpointCharacteristicUnitTest(EntityEndpointsUnitTest):
1029
"""
@@ -23,3 +42,45 @@ def test_list(self, mock_request):
2342
@mock.patch("requests.sessions.Session.request")
2443
def test_get(self, mock_request):
2544
self.get(mock_request)
45+
46+
@mock.patch("requests.sessions.Session.request")
47+
def test_list_for_job(self, mock_request):
48+
mock_request.return_value = self.mock_response(MOCK_PROPERTIES_RESPONSE)
49+
result = self.endpoints.list_for_job(JOB_ID)
50+
print(result)
51+
self.assertEqual(result, [
52+
{"unit_id": UNIT_ID_0, "properties": [PROPERTY_NAME_0]},
53+
{"unit_id": UNIT_ID_1, "properties": [PROPERTY_NAME_1]},
54+
])
55+
sent_query = json.loads(mock_request.call_args[1]["params"]["query"])
56+
self.assertEqual(sent_query["source.info.jobId"], JOB_ID)
57+
58+
@mock.patch("requests.sessions.Session.request")
59+
def test_get_for_job(self, mock_request):
60+
mock_request.return_value = self.mock_response(MOCK_PROPERTIES_RESPONSE)
61+
result = self.endpoints.get_for_job(JOB_ID)
62+
self.assertEqual(len(result), 2)
63+
sent_query = json.loads(mock_request.call_args[1]["params"]["query"])
64+
self.assertEqual(sent_query["source.info.jobId"], JOB_ID)
65+
self.assertNotIn("data.name", sent_query)
66+
self.assertNotIn("source.info.unitId", sent_query)
67+
68+
@mock.patch("requests.sessions.Session.request")
69+
def test_get_for_job_filtered_by_name(self, mock_request):
70+
mock_request.return_value = self.mock_response(MOCK_SINGLE_PROPERTY_RESPONSE)
71+
result = self.endpoints.get_for_job(JOB_ID, PROPERTY_NAME_1)
72+
self.assertEqual(len(result), 1)
73+
self.assertEqual(result[0]["name"], PROPERTY_NAME_1)
74+
sent_query = json.loads(mock_request.call_args[1]["params"]["query"])
75+
self.assertEqual(sent_query["data.name"], PROPERTY_NAME_1)
76+
77+
78+
@mock.patch("requests.sessions.Session.request")
79+
def test_get_for_job_filtered_by_unit_id_and_name(self, mock_request):
80+
mock_request.return_value = self.mock_response(MOCK_SINGLE_PROPERTY_RESPONSE)
81+
result = self.endpoints.get_for_job(JOB_ID, PROPERTY_NAME_1, unit_id=UNIT_ID_1)
82+
self.assertEqual(len(result), 1)
83+
self.assertEqual(result[0]["name"], PROPERTY_NAME_1)
84+
sent_query = json.loads(mock_request.call_args[1]["params"]["query"])
85+
self.assertEqual(sent_query["source.info.unitId"], UNIT_ID_1)
86+
self.assertEqual(sent_query["data.name"], PROPERTY_NAME_1)

0 commit comments

Comments
 (0)