Skip to content

Commit a6e3331

Browse files
merge conf
2 parents 73e9a50 + 30adeba commit a6e3331

58 files changed

Lines changed: 964 additions & 385 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# The Materials Project API
22

3-
[![testing](https://github.com/materialsproject/api/workflows/testing/badge.svg)](https://github.com/materialsproject/api/actions?query=workflow%3Atesting)
3+
[![testing](https://github.com/materialsproject/api/actions/workflows/testing.yml/badge.svg?branch=main)](https://github.com/materialsproject/api/actions?query=workflow%3Atesting+branch%3Amain)
44
[![codecov](https://codecov.io/gh/materialsproject/api/branch/main/graph/badge.svg)](https://codecov.io/gh/materialsproject/api)
5-
![python](https://img.shields.io/badge/Python-3.9+-blue.svg?logo=python&logoColor=white)
5+
![python](https://img.shields.io/badge/Python-3.11+-blue.svg?logo=python&logoColor=white)
66

77
This repository is the development environment for the new Materials Project API. A core client implementation will reside here. For information on how to use the API, please see the updated [documentation](https://docs.materialsproject.org/downloading-data/how-do-i-download-the-materials-project-database).

mp_api/client/core/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from __future__ import annotations
22

3-
from .client import BaseRester, MPRestError, MPRestWarning
3+
from .client import BaseRester
4+
from .exceptions import MPRestError, MPRestWarning
45
from .settings import MAPIClientSettings

mp_api/client/core/client.py

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from tqdm.auto import tqdm
3131
from urllib3.util.retry import Retry
3232

33+
from mp_api.client.core.exceptions import MPRestError
3334
from mp_api.client.core.settings import MAPIClientSettings
3435
from mp_api.client.core.utils import load_json, validate_ids
3536

@@ -94,11 +95,11 @@ def __init__(
9495
session: requests.Session | None = None,
9596
s3_client: Any | None = None,
9697
debug: bool = False,
97-
monty_decode: bool = True,
9898
use_document_model: bool = True,
9999
timeout: int = 20,
100100
headers: dict | None = None,
101101
mute_progress_bars: bool = SETTINGS.MUTE_PROGRESS_BARS,
102+
**kwargs,
102103
):
103104
"""Initialize the REST API helper class.
104105
@@ -123,13 +124,13 @@ def __init__(
123124
advanced usage only.
124125
s3_client: boto3 S3 client object with which to connect to the object stores.ct to the object stores.ct to the object stores.
125126
debug: if True, print the URL for every request
126-
monty_decode: Decode the data using monty into python objects
127127
use_document_model: If False, skip the creating the document model and return data
128128
as a dictionary. This can be simpler to work with but bypasses data validation
129129
and will not give auto-complete for available fields.
130130
timeout: Time in seconds to wait until a request timeout error is thrown
131131
headers: Custom headers for localhost connections.
132132
mute_progress_bars: Whether to disable progress bars.
133+
**kwargs: access to legacy kwargs that may be in the process of being deprecated
133134
"""
134135
# TODO: think about how to migrate from PMG_MAPI_KEY
135136
self.api_key = api_key or os.getenv("MP_API_KEY")
@@ -138,7 +139,6 @@ def __init__(
138139
)
139140
self.debug = debug
140141
self.include_user_agent = include_user_agent
141-
self.monty_decode = monty_decode
142142
self.use_document_model = use_document_model
143143
self.timeout = timeout
144144
self.headers = headers or {}
@@ -153,6 +153,12 @@ def __init__(
153153
self._session = session
154154
self._s3_client = s3_client
155155

156+
if "monty_decode" in kwargs:
157+
warnings.warn(
158+
"Ignoring `monty_decode`, as it is no longer a supported option in `mp_api`."
159+
"The client by default returns results consistent with `monty_decode=True`."
160+
)
161+
156162
@property
157163
def session(self) -> requests.Session:
158164
if not self._session:
@@ -267,7 +273,7 @@ def _post_resource(
267273
response = self.session.post(url, json=payload, verify=True, params=params)
268274

269275
if response.status_code == 200:
270-
data = load_json(response.text, deser=self.monty_decode)
276+
data = load_json(response.text)
271277
if self.document_model and use_document_model:
272278
if isinstance(data["data"], dict):
273279
data["data"] = self.document_model.model_validate(data["data"]) # type: ignore
@@ -335,7 +341,7 @@ def _patch_resource(
335341
response = self.session.patch(url, json=payload, verify=True, params=params)
336342

337343
if response.status_code == 200:
338-
data = load_json(response.text, deser=self.monty_decode)
344+
data = load_json(response.text)
339345
if self.document_model and use_document_model:
340346
if isinstance(data["data"], dict):
341347
data["data"] = self.document_model.model_validate(data["data"]) # type: ignore
@@ -386,10 +392,7 @@ def _query_open_data(
386392
Returns:
387393
dict: MontyDecoded data
388394
"""
389-
if not decoder:
390-
391-
def decoder(x):
392-
return load_json(x, deser=self.monty_decode)
395+
decoder = decoder or load_json
393396

394397
file = open(
395398
f"s3://{bucket}/{key}",
@@ -999,7 +1002,7 @@ def _submit_request_and_process(
9991002
)
10001003

10011004
if response.status_code == 200:
1002-
data = load_json(response.text, deser=self.monty_decode)
1005+
data = load_json(response.text)
10031006
# other sub-urls may use different document models
10041007
# the client does not handle this in a particularly smart way currently
10051008
if self.document_model and use_document_model:
@@ -1304,12 +1307,10 @@ def count(self, criteria: dict | None = None) -> int | str:
13041307
"""
13051308
criteria = criteria or {}
13061309
user_preferences = (
1307-
self.monty_decode,
13081310
self.use_document_model,
13091311
self.mute_progress_bars,
13101312
)
1311-
self.monty_decode, self.use_document_model, self.mute_progress_bars = (
1312-
False,
1313+
self.use_document_model, self.mute_progress_bars = (
13131314
False,
13141315
True,
13151316
) # do not waste cycles decoding
@@ -1331,7 +1332,6 @@ def count(self, criteria: dict | None = None) -> int | str:
13311332
)
13321333

13331334
(
1334-
self.monty_decode,
13351335
self.use_document_model,
13361336
self.mute_progress_bars,
13371337
) = user_preferences
@@ -1389,11 +1389,3 @@ def __getattr__(self, v: str):
13891389

13901390
def __dir__(self):
13911391
return dir(self.__class__) + list(self._sub_resters)
1392-
1393-
1394-
class MPRestError(Exception):
1395-
"""Raised when the query has problems, e.g., bad query format."""
1396-
1397-
1398-
class MPRestWarning(Warning):
1399-
"""Raised when a query is malformed but interpretable."""

mp_api/client/core/exceptions.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
"""Define custom exceptions and warnings for the client."""
2+
from __future__ import annotations
3+
4+
5+
class MPRestError(Exception):
6+
"""Raised when the query has problems, e.g., bad query format."""
7+
8+
9+
class MPRestWarning(Warning):
10+
"""Raised when a query is malformed but interpretable."""

mp_api/client/core/settings.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from multiprocessing import cpu_count
33
from typing import List
44

5-
from pydantic import Field
5+
from pydantic import Field, field_validator
66
from pydantic_settings import BaseSettings, SettingsConfigDict
77
from pymatgen.core import _load_pmg_settings
88

@@ -14,6 +14,7 @@
1414
_MUTE_PROGRESS_BAR = PMG_SETTINGS.get("MPRESTER_MUTE_PROGRESS_BARS", False)
1515
_MAX_HTTP_URL_LENGTH = PMG_SETTINGS.get("MPRESTER_MAX_HTTP_URL_LENGTH", 2000)
1616
_MAX_LIST_LENGTH = min(PMG_SETTINGS.get("MPRESTER_MAX_LIST_LENGTH", 10000), 10000)
17+
_DEFAULT_ENDPOINT = "https://api.materialsproject.org/"
1718

1819
try:
1920
CPU_COUNT = cpu_count()
@@ -80,11 +81,21 @@ class MAPIClientSettings(BaseSettings):
8081
)
8182

8283
MIN_EMMET_VERSION: str = Field(
83-
"0.54.0", description="Minimum compatible version of emmet-core for the client."
84+
"0.86.3rc0",
85+
description="Minimum compatible version of emmet-core for the client.",
8486
)
8587

8688
MAX_LIST_LENGTH: int = Field(
8789
_MAX_LIST_LENGTH, description="Maximum length of query parameter list"
8890
)
8991

92+
ENDPOINT: str = Field(
93+
_DEFAULT_ENDPOINT, description="The default API endpoint to use."
94+
)
95+
9096
model_config = SettingsConfigDict(env_prefix="MPRESTER_")
97+
98+
@field_validator("ENDPOINT", mode="before")
99+
def _get_endpoint_from_env(cls, v: str | None) -> str:
100+
"""Support setting endpoint via MP_API_ENDPOINT environment variable."""
101+
return v or os.environ.get("MP_API_ENDPOINT") or _DEFAULT_ENDPOINT

mp_api/client/core/utils.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import os
34
from importlib import import_module
45
from typing import TYPE_CHECKING, Literal
56

@@ -9,6 +10,7 @@
910
from monty.json import MontyDecoder
1011
from packaging.version import parse as parse_version
1112

13+
from mp_api.client.core.exceptions import MPRestError
1214
from mp_api.client.core.settings import MAPIClientSettings
1315

1416
if TYPE_CHECKING:
@@ -51,20 +53,39 @@ def load_json(
5153
return MontyDecoder().process_decoded(data) if deser else data
5254

5355

56+
def validate_api_key(api_key: str | None = None) -> str:
57+
"""Find and validate an API key."""
58+
# SETTINGS tries to read API key from ~/.config/.pmgrc.yaml
59+
api_key = api_key or os.getenv("MP_API_KEY")
60+
if not api_key:
61+
from pymatgen.core import SETTINGS
62+
63+
api_key = SETTINGS.get("PMG_MAPI_KEY")
64+
65+
if not api_key or len(api_key) != 32:
66+
addendum = " Valid API keys are 32 characters." if api_key else ""
67+
raise MPRestError(
68+
"Please obtain a valid API key from https://materialsproject.org/api "
69+
f"and export it as an environment variable `MP_API_KEY`.{addendum}"
70+
)
71+
72+
return api_key
73+
74+
5475
def validate_ids(id_list: list[str]) -> list[str]:
5576
"""Function to validate material and task IDs.
5677
5778
Args:
5879
id_list (List[str]): List of material or task IDs.
5980
6081
Raises:
61-
ValueError: If at least one ID is not formatted correctly.
82+
MPRestError: If at least one ID is not formatted correctly.
6283
6384
Returns:
6485
id_list: Returns original ID list if everything is formatted correctly.
6586
"""
6687
if len(id_list) > MAPIClientSettings().MAX_LIST_LENGTH:
67-
raise ValueError(
88+
raise MPRestError(
6889
"List of material/molecule IDs provided is too long. Consider removing the ID filter to automatically pull"
6990
" data for all IDs and filter locally."
7091
)

0 commit comments

Comments
 (0)