Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
9c11c8f
skip generic client alloys test if not installed
esoteric-ephemera Jan 23, 2026
3a64f66
update to spdx license
esoteric-ephemera Jan 23, 2026
a6103e7
properly skip mcp server test if no fastmcp
esoteric-ephemera Jan 23, 2026
6ea3e18
xpass xas rester
esoteric-ephemera Jan 23, 2026
ea4b103
refactor server-only resters
esoteric-ephemera Jan 26, 2026
5381170
add mpmcp cli tool
esoteric-ephemera Jan 27, 2026
f82b577
handle 403s from heartbeat gracefully / only emit warning
esoteric-ephemera Jan 29, 2026
21a20fe
precommit
esoteric-ephemera Jan 29, 2026
9d25099
xfail 403 test for now - needs attention
esoteric-ephemera Jan 29, 2026
735f622
add mypy + py.typed
esoteric-ephemera Feb 3, 2026
e8f87ac
precommit
esoteric-ephemera Feb 3, 2026
69976b5
merge conf
esoteric-ephemera Feb 3, 2026
e04c63e
more mypy
esoteric-ephemera Feb 4, 2026
5139be6
even even more mypy
esoteric-ephemera Feb 4, 2026
875e64a
patch up tests
esoteric-ephemera Feb 4, 2026
4526e55
fix get structure
Feb 4, 2026
c3ff2e8
add more tools to mcp to facilitate bulk retrieval + phase diagram re…
esoteric-ephemera Feb 5, 2026
f9c5a83
precommit
esoteric-ephemera Feb 5, 2026
ba9a27e
ensure consistent warnings
esoteric-ephemera Feb 10, 2026
2b16e4e
patch tests to reflect mprestwarning
esoteric-ephemera Feb 11, 2026
2ff2e6a
add exception if user inputs single mpid to get cohesive energy function
esoteric-ephemera Feb 12, 2026
86ae263
resolve merge conflicts
esoteric-ephemera Feb 17, 2026
796e79c
correct import string
esoteric-ephemera Feb 20, 2026
7a2e84c
fix docstr
esoteric-ephemera Feb 20, 2026
7455c23
add poscar to data output for workflows
esoteric-ephemera Feb 24, 2026
596779c
merge conflicts + mypy
esoteric-ephemera Feb 24, 2026
55ae85a
migrate web utils
esoteric-ephemera Feb 24, 2026
d4f6913
mypy
esoteric-ephemera Feb 24, 2026
1c68fff
bump emmet-core for release
esoteric-ephemera Feb 24, 2026
5d61330
bump emmet-core for release
esoteric-ephemera Feb 25, 2026
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: 4 additions & 0 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ jobs:
run: |
echo "API_KEY_NAME=$(echo ${{ format('MP_API_KEY_{0}_{1}', matrix.os, matrix.python-version) }} | awk '{gsub(/-|\./, "_"); print}' | tr '[:lower:]' '[:upper:]')" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
- name: Lint with mypy
shell: bash -l {0}
run: python -m mypy mp_api/

- name: Test with pytest
env:
MP_API_KEY: ${{ secrets[env.API_KEY_NAME] }}
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,4 @@ _autosummary
uv.lock
JANAF_*_data.json
.gemini
.codex
2 changes: 1 addition & 1 deletion dev/inspect_mcp.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Tool to run the MCP inspector:
# https://modelcontextprotocol.io/docs/tools/inspector

server_path=$(python -c 'from importlib_resources import files ; print(str((files("mp_api.client") / ".."/ "..").resolve()))')
server_path=$(python -c 'from importlib.resources import files ; print(str((files("mp_api.client") / ".."/ "..").resolve()))')

fastmcp dev \
--python 3.12 \
Expand Down
Empty file added mp_api/__init__.py
Empty file.
11 changes: 8 additions & 3 deletions mp_api/client/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
"""Primary MAPI module."""
from __future__ import annotations

import logging
import os
from importlib.metadata import PackageNotFoundError, version

from .core import MPRestError
from .mprester import MPRester
from mp_api.client.core.exceptions import MPRestError
from mp_api.client.mprester import MPRester

__all__ = ["MPRestError", "MPRester"]

try:
__version__ = version("mp_api")
except PackageNotFoundError: # pragma: no cover
__version__ = os.getenv("SETUPTOOLS_SCM_PRETEND_VERSION")
__version__ = os.getenv("SETUPTOOLS_SCM_PRETEND_VERSION", "")

logging.getLogger(__name__).addHandler(logging.NullHandler())
103 changes: 103 additions & 0 deletions mp_api/client/_server_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
"""Define utilities needed by the MP web server."""
from __future__ import annotations

try:
import flask
except ImportError:
from mp_api.client.core.exceptions import MPRestError

raise MPRestError("`flask` must be installed to use server utilities.")

import requests

from mp_api.client import MPRester
from mp_api.client.core.utils import validate_api_key

SESSION = requests.Session()


def is_localhost() -> bool:
"""Determine if current env is local or production.

Returns:
bool: True if the environment is locally hosted.
"""
return (
True
if not flask.has_request_context()
else flask.request.headers.get("Host", "").startswith(
("localhost:", "127.0.0.1:", "0.0.0.0:")
)
)


def get_consumer() -> dict[str, str]:
"""Identify the consumer associated with the current request.

Returns:
dict of str to str, the headers associated with the consumer
"""
if not flask.has_request_context():
return {}

names = [
"X-Consumer-Id", # kong internal uuid
"X-Consumer-Custom-Id", # api key
"X-Consumer-Username", # <provider>:<email>
"X-Anonymous-Consumer", # is anonymous user?
"X-Authenticated-Groups", # groups this user belongs to
"X-Consumer-Groups", # same as X-Authenticated-Groups
]
headers = flask.request.headers
return {name: headers[name] for name in names if headers.get(name) is not None}


def is_logged_in_user(consumer: dict[str, str] | None = None) -> bool:
"""Check if the client has the necessary headers for an authenticated user.

Args:
consumer (dict of str to str, or None): Headers associated with the consumer

Returns:
bool : True if the consumer is a logged-in user, False otherwise.
"""
c = consumer or get_consumer()
return bool(not c.get("X-Anonymous-Consumer") and c.get("X-Consumer-Id"))


def get_user_api_key(consumer: dict[str, str] | None = None) -> str | None:
"""Get the api key that belongs to the current user.

If running on localhost, api key is obtained from
the environment variable MP_API_KEY.

Args:
consumer (dict of str to str, or None): Headers associated with the consumer

Returns:
str, the API key, or None if no API key could be identified.
"""
c = consumer or get_consumer()

if is_localhost():
return validate_api_key()
elif is_logged_in_user(c):
return c.get("X-Consumer-Custom-Id")
return None


def get_rester(**kwargs) -> MPRester:
"""Create MPRester with headers set for localhost and production compatibility.

Args:
**kwargs : kwargs to pass to MPRester

Returns:
MPRester
"""
if is_localhost():
dev_api_key = get_user_api_key()
SESSION.headers["x-api-key"] = dev_api_key or ""
return MPRester(api_key=dev_api_key, session=SESSION, **kwargs)

return MPRester(headers=get_consumer(), session=SESSION, **kwargs)
7 changes: 5 additions & 2 deletions mp_api/client/core/_oxygen_evolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
from __future__ import annotations

import json
import warnings
from collections.abc import Sequence
from io import StringIO
from pathlib import Path
from warnings import warn

import numpy as np
import pandas as pd
Expand All @@ -16,6 +16,8 @@
from scipy.constants import Avogadro, Boltzmann, atm, elementary_charge
from scipy.interpolate import make_splrep, splev

from mp_api.client.core.exceptions import MPRestWarning

DEFAULT_CACHE_FILE = Path(__file__).absolute().parent / "JANAF_O2_data.json"
# O2 partial pressure at ambient conditions, in MPa
O2_PARTIAL_PRESSURE = 0.21 * atm * 1e-6
Expand Down Expand Up @@ -120,10 +122,11 @@ def mu_to_temp_spline(
(mu_arr < min(NIST_JANAF_O2_MU_T["mu-mu_0K"]))
| (mu_arr > max(NIST_JANAF_O2_MU_T["mu-mu_0K"]))
):
warn(
warnings.warn(
"Some of the input chemical potential values are "
"outside the fitting range - extrapolation will be inaccurate.",
stacklevel=2,
category=MPRestWarning,
)
return splev(mu_arr, self.mu_to_temp_spline_params())

Expand Down
Loading