Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions mp_api/client/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,7 @@ class MAPIClientSettings(BaseSettings):
_MAX_LIST_LENGTH, description="Maximum length of query parameter list"
)

ENDPOINT: str = Field(
_DEFAULT_ENDPOINT, description="The default API endpoint to use."
)
ENDPOINT: str = Field("", description="The default API endpoint to use.")

LTOL: float = Field(
_EMMET_SETTINGS.LTOL,
Expand Down
22 changes: 17 additions & 5 deletions mp_api/client/core/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import os
import warnings
from importlib import import_module
from typing import TYPE_CHECKING, Literal
from urllib.parse import urljoin
Expand All @@ -11,7 +12,7 @@
from monty.json import MontyDecoder
from packaging.version import parse as parse_version

from mp_api.client.core.exceptions import MPRestError
from mp_api.client.core.exceptions import MPRestError, MPRestWarning
from mp_api.client.core.settings import MAPI_CLIENT_SETTINGS

if TYPE_CHECKING:
Expand Down Expand Up @@ -54,7 +55,7 @@ def load_json(
return MontyDecoder().process_decoded(data) if deser else data


def validate_api_key(api_key: str | None = None) -> str:
def validate_api_key(api_key: str | None = None) -> str | None:
"""Find and validate an API key."""
# SETTINGS tries to read API key from ~/.config/.pmgrc.yaml
api_key = api_key or os.getenv("MP_API_KEY")
Expand All @@ -63,11 +64,22 @@ def validate_api_key(api_key: str | None = None) -> str:

api_key = PMG_SETTINGS.get("PMG_MAPI_KEY")

if not api_key or len(api_key) != 32:
addendum = " Valid API keys are 32 characters." if api_key else ""
if not api_key:
# The web server requires the client to initialize without an API key.
# Only warn the user if the API key cannot be identified to permit
# the web server to run.
warnings.warn(
"No API key found, please set explicitly or in "
"the `MP_API_KEY` environment variable.",
category=MPRestWarning,
stacklevel=2,
)

elif isinstance(api_key, str) and len(api_key) != 32:
raise MPRestError(
"Please obtain a valid API key from https://materialsproject.org/api "
f"and export it as an environment variable `MP_API_KEY`.{addendum}"
"and export it as an environment variable `MP_API_KEY`. "
"Valid API keys are 32 characters."
)

return api_key
Expand Down
57 changes: 34 additions & 23 deletions mp_api/client/mprester.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,22 @@
"mp_api.client.routes.materials.materials.MaterialsRester"
),
**{f"materials/{k}": v for k, v in MATERIALS_RESTERS.items() if k not in {"doi"}},
"doi": MATERIALS_RESTERS["doi"],
**{f"molecules/{k}": v for k, v in MOLECULES_RESTERS.items()},
}
GENERIC_RESTERS = {
"doi": MATERIALS_RESTERS["doi"],
**GENERIC_RESTERS,
}

TOP_LEVEL_RESTERS = [
"molecules/core",
"materials/core",
"_general_store",
"_messages",
"_user_settings",
"doi",
]


class MPRester:
"""Access the new Materials Project API."""
Expand Down Expand Up @@ -179,27 +190,23 @@ def __init__(
# Nested rested are then setup to be loaded dynamically with custom __getattr__ functions.
self._all_resters = list(RESTER_LAYOUT.values())

# Instantiate top level molecules and materials resters and set them as attributes
core_suffix = ["molecules/core", "materials/core"]

core_resters = {
rest_name.split("/")[0]: lazy_rester(
api_key=self.api_key,
endpoint=self.endpoint,
include_user_agent=self._include_user_agent,
session=self.session,
use_document_model=self.use_document_model,
headers=self.headers,
mute_progress_bars=self.mute_progress_bars,
)
for rest_name, lazy_rester in RESTER_LAYOUT.items()
if rest_name in core_suffix
}

# Set remaining top level resters, or get an attribute-class name mapping

for attr, rester in core_resters.items():
setattr(self, attr, rester)
# Instantiate top level core molecules, materials, and DOI resters, as well
# as the sunder resters to allow the web server to work.
for rest_name, lazy_rester in (RESTER_LAYOUT | GENERIC_RESTERS).items():
if rest_name in TOP_LEVEL_RESTERS:
setattr(
self,
rest_name.split("/")[0],
lazy_rester(
api_key=self.api_key,
endpoint=self.endpoint,
include_user_agent=self._include_user_agent,
session=self.session,
use_document_model=self.use_document_model,
headers=self.headers,
mute_progress_bars=self.mute_progress_bars,
),
)

@property
def contribs(self):
Expand Down Expand Up @@ -249,7 +256,11 @@ def __getattr__(self, attr):
)

def __dir__(self):
return dir(MPRester) + self._deprecated_attributes + ["materials", "molecules"]
return (
dir(MPRester)
+ self._deprecated_attributes
+ [r.split("/", 1)[0] for r in TOP_LEVEL_RESTERS if not r.startswith("_")]
)

def get_task_ids_associated_with_material_id(
self, material_id: str, calc_types: list[CalcType] | None = None
Expand Down
12 changes: 9 additions & 3 deletions mp_api/mcp/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ def search(self, query: str) -> SearchOutput:

Args:
query (str) : A natural language query of either:
- comma-delimited keywords, example: "polyhedra, orthorhombic, superconductor"
- comma-delimited keywords, example: "polyhedra,orthorhombic,superconductor".
This should contain no whitespace; all whitespace will be removed.
- chemical formula, example: "TiO2"
- dash-delimited elements for more general chemical system, example: "Li-P-S"
To query by formula or chemical system, no commas should be present in the query.
Expand All @@ -94,11 +95,16 @@ def search(self, query: str) -> SearchOutput:
material_ids=material_ids, fields=["description", "material_id"]
)
else:
robo_docs += self.client.materials.robocrys.search(query)
robo_docs += self.client.materials.robocrys.search(
query.replace(" ", "").split(",")
)

return SearchOutput(
results=[
FetchResult(id=doc["material_id"], text=doc["description"])
FetchResult(
id=doc["material_id"],
text=doc["description"],
)
for doc in robo_docs
]
)
Expand Down
7 changes: 3 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ dependencies = [
"typing-extensions>=3.7.4.1",
"requests>=2.23.0",
"monty>=2024.12.10",
"emmet-core>=0.86.3rc0",
"emmet-core>=0.86.3",
"boto3",
"orjson >= 3.10,<4",
]
Expand All @@ -33,12 +33,11 @@ dynamic = ["version"]
[project.optional-dependencies]
mcp = ["fastmcp"]
all = [
"emmet-core[all]>=0.86.2",
"emmet-core[all]>=0.86.3",
"custodian",
"mpcontribs-client>=5.10",
"fastmcp",
"matminer>=0.9.3",
"scipy<1.17.0", # pending fixes in matminer
"matminer>=0.10.0",
]
test = [
"pre-commit",
Expand Down
32 changes: 11 additions & 21 deletions requirements/requirements-ubuntu-latest_py3.11.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
#
annotated-types==0.7.0
# via pydantic
bibtexparser==1.4.3
bibtexparser==1.4.4
# via pymatgen
blake3==1.0.8
# via emmet-core
boto3==1.42.30
boto3==1.42.38
# via mp-api (pyproject.toml)
botocore==1.42.30
botocore==1.42.38
# via
# boto3
# s3transfer
Expand All @@ -24,13 +24,13 @@ contourpy==1.3.3
# via matplotlib
cycler==0.12.1
# via matplotlib
emmet-core==0.87.0.dev4
emmet-core==0.86.3
# via mp-api (pyproject.toml)
fonttools==4.61.1
# via matplotlib
idna==3.11
# via requests
jmespath==1.0.1
jmespath==1.1.0
# via
# boto3
# botocore
Expand All @@ -49,8 +49,6 @@ monty==2025.3.3
# pymatgen
mpmath==1.3.0
# via sympy
msgpack==1.1.2
# via mp-api (pyproject.toml)
narwhals==2.15.0
# via plotly
networkx==3.6.1
Expand All @@ -65,17 +63,17 @@ numpy==2.4.1
# pymatgen-io-validation
# scipy
# spglib
orjson==3.11.5
orjson==3.11.6
# via
# mp-api (pyproject.toml)
# pymatgen
packaging==25.0
packaging==26.0
# via
# matplotlib
# plotly
palettable==3.3.3
# via pymatgen
pandas==2.3.3
pandas==3.0.0
# via pymatgen
pillow==12.1.0
# via matplotlib
Expand All @@ -101,7 +99,7 @@ pymatgen==2025.10.7
# pymatgen-io-validation
pymatgen-io-validation==0.1.2
# via emmet-core
pyparsing==3.3.1
pyparsing==3.3.2
# via
# bibtexparser
# matplotlib
Expand All @@ -112,8 +110,6 @@ python-dateutil==2.9.0.post0
# pandas
python-dotenv==1.2.1
# via pydantic-settings
pytz==2025.2
# via pandas
pyyaml==6.0.3
# via pybtex
requests==2.32.5
Expand All @@ -127,12 +123,10 @@ ruamel-yaml==0.19.1
# pymatgen
s3transfer==0.16.0
# via boto3
scipy==1.16.3
scipy==1.17.0
# via pymatgen
six==1.17.0
# via python-dateutil
smart-open==7.5.0
# via mp-api (pyproject.toml)
spglib==2.7.0
# via pymatgen
sympy==1.14.0
Expand All @@ -154,13 +148,9 @@ typing-inspection==0.4.2
# via
# pydantic
# pydantic-settings
tzdata==2025.3
# via pandas
uncertainties==3.2.4
uncertainties==3.2.3
# via pymatgen
urllib3==2.6.3
# via
# botocore
# requests
wrapt==2.0.1
# via smart-open
Loading