Skip to content

Commit 55ae85a

Browse files
migrate web utils
1 parent 596779c commit 55ae85a

3 files changed

Lines changed: 109 additions & 2 deletions

File tree

mp_api/client/_server_utils.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
"""Define utilities needed by the MP web server."""
2+
from __future__ import annotations
3+
4+
try:
5+
import flask
6+
except ImportError:
7+
from mp_api.client.core.exceptions import MPRestError
8+
9+
raise MPRestError("`flask` must be installed to use server utilities.")
10+
11+
import requests
12+
13+
from mp_api.client import MPRester
14+
from mp_api.client.core.utils import validate_api_key
15+
16+
SESSION = requests.Session()
17+
18+
19+
def is_localhost() -> bool:
20+
"""Determine if current env is local or production.
21+
22+
Returns:
23+
bool: True if the environment is locally hosted.
24+
"""
25+
return (
26+
True
27+
if not flask.has_request_context()
28+
else flask.request.headers.get("Host", "").startswith(
29+
("localhost:", "127.0.0.1:", "0.0.0.0:")
30+
)
31+
)
32+
33+
34+
def get_consumer() -> dict[str, str]:
35+
"""Identify the consumer associated with the current request.
36+
37+
Returns:
38+
dict of str to str, the headers associated with the consumer
39+
"""
40+
if not flask.has_request_context():
41+
return {}
42+
43+
names = [
44+
"X-Consumer-Id", # kong internal uuid
45+
"X-Consumer-Custom-Id", # api key
46+
"X-Consumer-Username", # <provider>:<email>
47+
"X-Anonymous-Consumer", # is anonymous user?
48+
"X-Authenticated-Groups", # groups this user belongs to
49+
"X-Consumer-Groups", # same as X-Authenticated-Groups
50+
]
51+
headers = flask.request.headers
52+
return {name: headers[name] for name in names if headers.get(name) is not None}
53+
54+
55+
def is_logged_in_user(consumer: dict[str, str] | None = None) -> bool:
56+
"""Check if the client has the necessary headers for an authenticated user.
57+
58+
Args:
59+
consumer (dict of str to str, or None): Headers associated with the consumer
60+
61+
Returns:
62+
bool : True if the consumer is a logged-in user, False otherwise.
63+
"""
64+
c = consumer or get_consumer()
65+
return bool(not c.get("X-Anonymous-Consumer") and c.get("X-Consumer-Id"))
66+
67+
68+
def get_user_api_key(consumer: dict[str, str] | None = None) -> str | None:
69+
"""Get the api key that belongs to the current user.
70+
71+
If running on localhost, api key is obtained from
72+
the environment variable MP_API_KEY.
73+
74+
Args:
75+
consumer (dict of str to str, or None): Headers associated with the consumer
76+
77+
Returns:
78+
str, the API key, or None if no API key could be identified.
79+
"""
80+
c = consumer or get_consumer()
81+
82+
if is_localhost():
83+
return validate_api_key()
84+
elif is_logged_in_user(c):
85+
return c.get("X-Consumer-Custom-Id")
86+
return None
87+
88+
89+
def get_rester(**kwargs) -> MPRester:
90+
"""Create MPRester with headers set for localhost and production compatibility.
91+
92+
Args:
93+
**kwargs : kwargs to pass to MPRester
94+
95+
Returns:
96+
MPRester
97+
"""
98+
if is_localhost():
99+
dev_api_key = get_user_api_key()
100+
SESSION.headers["x-api-key"] = dev_api_key
101+
return MPRester(api_key=dev_api_key, session=SESSION, **kwargs)
102+
103+
return MPRester(headers=get_consumer(), session=SESSION, **kwargs)

mp_api/client/routes/_server.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""Define RESTers required by the MP web server."""
2+
13
from __future__ import annotations
24

35
from typing import TYPE_CHECKING

pyproject.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,14 @@ dynamic = ["version"]
3434

3535
[project.optional-dependencies]
3636
mcp = ["fastmcp"]
37+
server = ["flask"]
3738
all = [
38-
"emmet-core[all]>=0.86.3",
3939
"custodian",
40-
"mpcontribs-client>=5.10",
40+
"emmet-core[all]>=0.86.3",
4141
"fastmcp",
42+
"flask",
4243
"matminer>=0.10.0",
44+
"mpcontribs-client>=5.10",
4345
]
4446
test = [
4547
"pre-commit",

0 commit comments

Comments
 (0)