mp-api 0.47.0rc0__tar.gz → 0.47.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/PKG-INFO +5 -5
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/contribs/_logger.py +2 -3
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/contribs/client.py +37 -21
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/core/_display.py +182 -24
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/core/_entries.py +19 -2
- mp_api-0.47.1/mp_api/client/core/_external.py +352 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/core/client.py +63 -31
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/core/settings.py +9 -2
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/core/utils.py +91 -26
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/mprester.py +150 -46
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/materials/electronic_structure.py +4 -7
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/materials/eos.py +2 -3
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/materials/materials.py +1 -1
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/materials/thermo.py +48 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/materials/xas.py +2 -3
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/mcp/_schemas.py +1 -1
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/pyproject.toml +4 -4
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/core/test_delta.py +13 -2
- mp_api-0.47.1/tests/client/core/test_external.py +675 -0
- mp_api-0.47.1/tests/client/core/test_ids.py +192 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/core/test_utils.py +3 -1
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/materials/test_thermo.py +34 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/test_entry_progress.py +55 -4
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/test_mprester.py +155 -80
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/.gitignore +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/LICENSE +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/__init__.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/_test_utils.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/__init__.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/_server_utils.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/contribs/__init__.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/contribs/_types.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/contribs/_units.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/contribs/schemas.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/contribs/settings.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/contribs/utils.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/core/__init__.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/core/_oxygen_evolution.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/core/delta.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/core/exceptions.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/core/schemas.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/__init__.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/_server.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/materials/__init__.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/materials/absorption.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/materials/alloys.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/materials/bonds.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/materials/chemenv.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/materials/dielectric.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/materials/doi.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/materials/elasticity.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/materials/electrodes.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/materials/grain_boundaries.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/materials/magnetism.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/materials/oxidation_states.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/materials/phonon.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/materials/piezo.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/materials/provenance.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/materials/robocrys.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/materials/similarity.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/materials/substrates.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/materials/summary.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/materials/surface_properties.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/materials/synthesis.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/materials/tasks.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/molecules/__init__.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/molecules/jcesr.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/molecules/molecules.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/client/routes/molecules/summary.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/mcp/__init__.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/mcp/mp_mcp.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/mcp/server.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/mcp/tools.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/mcp/utils.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/mp_api/py.typed +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/__init__.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/__init__.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/contribs/conftest.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/contribs/test_client.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/contribs/test_contribs_schemas.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/contribs/test_contribs_utils.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/contribs/test_side_effects.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/contribs/test_types.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/core/test_client_progress.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/core/test_delta_versions.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/core/test_display.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/core/test_oxygen_evolution.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/core/test_quiet.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/core/test_schemas.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/core/test_session_headers.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/materials/__init__.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/materials/test_absorption.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/materials/test_alloys.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/materials/test_bonds.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/materials/test_chemenv.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/materials/test_dielectric.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/materials/test_doi.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/materials/test_elasticity.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/materials/test_electrodes.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/materials/test_electronic_structure.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/materials/test_eos.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/materials/test_grain_boundary.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/materials/test_magnetism.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/materials/test_materials.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/materials/test_oxidation_states.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/materials/test_phonon.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/materials/test_piezo.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/materials/test_provenance.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/materials/test_robocrys.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/materials/test_similarity.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/materials/test_substrates.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/materials/test_summary.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/materials/test_surface_properties.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/materials/test_synthesis.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/materials/test_tasks.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/materials/test_xas.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/molecules/__init__.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/molecules/test_jcesr.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/molecules/test_molecules.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/molecules/test_summary.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/test_client.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/test_core_client.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/client/test_heartbeat.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/conftest.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/mcp/__init__.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/mcp/test_server.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/mcp/test_tools.py +0 -0
- {mp_api-0.47.0rc0 → mp_api-0.47.1}/tests/mcp/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: mp-api
|
|
3
|
-
Version: 0.47.
|
|
3
|
+
Version: 0.47.1
|
|
4
4
|
Summary: API Client for the Materials Project
|
|
5
5
|
Author-email: The Materials Project <feedback@materialsproject.org>
|
|
6
6
|
License-Expression: BSD-3-Clause-LBNL
|
|
@@ -16,18 +16,18 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.14
|
|
17
17
|
Classifier: Topic :: Scientific/Engineering
|
|
18
18
|
Requires-Python: >=3.12
|
|
19
|
-
Requires-Dist: boto3
|
|
19
|
+
Requires-Dist: boto3>=1.34
|
|
20
20
|
Requires-Dist: deltalake<1.6.0,>=1.4.0
|
|
21
|
-
Requires-Dist: emmet-core
|
|
21
|
+
Requires-Dist: emmet-core>=0.88.0
|
|
22
22
|
Requires-Dist: monty>=2024.12.10
|
|
23
23
|
Requires-Dist: orjson<4,>=3.10
|
|
24
24
|
Requires-Dist: pyarrow>=20.0.0
|
|
25
|
-
Requires-Dist: pymatgen
|
|
25
|
+
Requires-Dist: pymatgen>=2026.10.24
|
|
26
26
|
Requires-Dist: requests>=2.23.0
|
|
27
27
|
Requires-Dist: rich>=13.0
|
|
28
28
|
Provides-Extra: all
|
|
29
29
|
Requires-Dist: custodian; extra == 'all'
|
|
30
|
-
Requires-Dist: emmet-core[all]
|
|
30
|
+
Requires-Dist: emmet-core[all]>=0.88.0; extra == 'all'
|
|
31
31
|
Requires-Dist: fastmcp; extra == 'all'
|
|
32
32
|
Requires-Dist: flask; extra == 'all'
|
|
33
33
|
Requires-Dist: ipywidgets; extra == 'all'
|
|
@@ -9,9 +9,9 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
import logging
|
|
11
11
|
import os
|
|
12
|
-
import warnings
|
|
13
12
|
|
|
14
13
|
from mp_api.client.contribs.settings import MPCC_SETTINGS
|
|
14
|
+
from mp_api.client.core._display import mp_deprecation
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class CustomLoggerAdapter(logging.LoggerAdapter):
|
|
@@ -39,10 +39,9 @@ def get_logger(name: str = "mp_api.client.contribs") -> CustomLoggerAdapter:
|
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
if MPCC_SETTINGS.CLIENT_LOG_LEVEL is not None:
|
|
42
|
-
|
|
42
|
+
mp_deprecation(
|
|
43
43
|
"MPCONTRIBS_CLIENT_LOG_LEVEL is deprecated and ignored, use MPRESTER_LOG_LEVEL "
|
|
44
44
|
"(which sets the level for the whole client) instead.",
|
|
45
|
-
FutureWarning,
|
|
46
45
|
stacklevel=2,
|
|
47
46
|
)
|
|
48
47
|
|
|
@@ -8,7 +8,6 @@ import importlib.metadata
|
|
|
8
8
|
import itertools
|
|
9
9
|
import sys
|
|
10
10
|
import time
|
|
11
|
-
import warnings
|
|
12
11
|
from base64 import urlsafe_b64encode
|
|
13
12
|
from collections import defaultdict
|
|
14
13
|
from concurrent.futures import as_completed
|
|
@@ -67,7 +66,8 @@ from mp_api.client.contribs.utils import (
|
|
|
67
66
|
get_md5,
|
|
68
67
|
unflatten_dict,
|
|
69
68
|
)
|
|
70
|
-
from mp_api.client.core._display import progress_bar
|
|
69
|
+
from mp_api.client.core._display import mp_deprecation, progress_bar
|
|
70
|
+
from mp_api.client.core._external import track_calls
|
|
71
71
|
from mp_api.client.core.exceptions import MPContribsClientError
|
|
72
72
|
from mp_api.client.core.schemas import _convert_to_model
|
|
73
73
|
from mp_api.client.core.settings import MAPI_CLIENT_SETTINGS
|
|
@@ -444,6 +444,7 @@ def _version(url):
|
|
|
444
444
|
time.sleep(30)
|
|
445
445
|
|
|
446
446
|
|
|
447
|
+
@track_calls
|
|
447
448
|
class ContribsClient(SwaggerClient):
|
|
448
449
|
"""client to connect to MPContribs API.
|
|
449
450
|
|
|
@@ -492,7 +493,7 @@ class ContribsClient(SwaggerClient):
|
|
|
492
493
|
)
|
|
493
494
|
else:
|
|
494
495
|
api_key = kwargs.pop("apikey")
|
|
495
|
-
|
|
496
|
+
mp_deprecation(api_key_warn, stacklevel=2)
|
|
496
497
|
|
|
497
498
|
if api_key and len(api_key) != 32:
|
|
498
499
|
raise MPContribsClientError(f"Invalid API key: {api_key}")
|
|
@@ -542,10 +543,9 @@ class ContribsClient(SwaggerClient):
|
|
|
542
543
|
@property
|
|
543
544
|
def apikey(self) -> str | None:
|
|
544
545
|
"""Handle deprecated `apikey` attr."""
|
|
545
|
-
|
|
546
|
+
mp_deprecation(
|
|
546
547
|
"`apikey` has been deprecated in favor of `api_key` for "
|
|
547
548
|
"consistency with the Materials Project API client.",
|
|
548
|
-
FutureWarning,
|
|
549
549
|
stacklevel=2,
|
|
550
550
|
)
|
|
551
551
|
return self.api_key
|
|
@@ -994,11 +994,14 @@ class ContribsClient(SwaggerClient):
|
|
|
994
994
|
else MPCDict(contrib)
|
|
995
995
|
)
|
|
996
996
|
|
|
997
|
-
def get_table(self, tid_or_md5: str) -> Table:
|
|
997
|
+
def get_table(self, tid_or_md5: str, timeout: int = -1) -> Table:
|
|
998
998
|
"""Retrieve full Pandas DataFrame for a table.
|
|
999
999
|
|
|
1000
|
+
Data pages beyond the first are fetched concurrently.
|
|
1001
|
+
|
|
1000
1002
|
Args:
|
|
1001
1003
|
tid_or_md5 (str): ObjectId or MD5 hash digest for table
|
|
1004
|
+
timeout (int): cancel remaining requests if timeout exceeded (in seconds)
|
|
1002
1005
|
"""
|
|
1003
1006
|
str_len = len(tid_or_md5)
|
|
1004
1007
|
if str_len not in {24, 32}:
|
|
@@ -1016,23 +1019,36 @@ class ContribsClient(SwaggerClient):
|
|
|
1016
1019
|
|
|
1017
1020
|
op = self.swagger_spec.resources["tables"].queryTables
|
|
1018
1021
|
per_page = op.params["data_per_page"].param_spec["maximum"]
|
|
1019
|
-
table: dict[str, list[str]] = {"data": []}
|
|
1020
|
-
page, pages = 1, None
|
|
1021
1022
|
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1023
|
+
# fetch the first page to learn the total number of data pages + metadata
|
|
1024
|
+
resp = self.tables.getTableById(
|
|
1025
|
+
pk=tid, _fields=["_all"], data_page=1, data_per_page=per_page
|
|
1026
|
+
).result()
|
|
1027
|
+
pages = resp["total_data_pages"]
|
|
1028
|
+
table: dict[str, Any] = {
|
|
1029
|
+
"data": resp["data"],
|
|
1030
|
+
"index": resp["index"],
|
|
1031
|
+
"columns": resp["columns"],
|
|
1032
|
+
}
|
|
1033
|
+
table_attrs: dict[str, str] = (resp.get("attrs") or {}) | {
|
|
1034
|
+
field: resp.get(field) for field in {"id", "name", "md5"}
|
|
1035
|
+
}
|
|
1034
1036
|
|
|
1035
|
-
|
|
1037
|
+
# fetch any remaining pages concurrently, then reassemble in page order
|
|
1038
|
+
if pages > 1:
|
|
1039
|
+
futures = [
|
|
1040
|
+
self._get_future(
|
|
1041
|
+
page,
|
|
1042
|
+
{"_fields": "_all", "data_page": page, "data_per_page": per_page},
|
|
1043
|
+
rel_url=f"tables/{tid}",
|
|
1044
|
+
)
|
|
1045
|
+
for page in range(2, pages + 1)
|
|
1046
|
+
]
|
|
1047
|
+
responses = _run_futures(
|
|
1048
|
+
futures, timeout=timeout, disable=self.mute_progress_bars
|
|
1049
|
+
)
|
|
1050
|
+
for page in sorted(responses):
|
|
1051
|
+
table["data"].extend(responses[page]["result"]["data"])
|
|
1036
1052
|
|
|
1037
1053
|
return Table.from_dict(table | {"attrs": table_attrs})
|
|
1038
1054
|
|
|
@@ -29,20 +29,62 @@ level, handler or propagation setting, here. Child loggers (`mp_api.client.*`,
|
|
|
29
29
|
including contribs) never set their own level, so one setting controls all of
|
|
30
30
|
them. Use `MPRESTER_LOG_LEVEL` or `enable_logging(level)`.
|
|
31
31
|
|
|
32
|
+
Messages are shown in the console's default style; only the level is
|
|
33
|
+
coloured.
|
|
34
|
+
|
|
35
|
+
The client's warnings
|
|
36
|
+
---------------------
|
|
37
|
+
The client's own warnings (`MPRestWarning`, its `FutureWarning` deprecations,
|
|
38
|
+
the `MPDataset*Warning`s) are ordinary Python warnings, so filters
|
|
39
|
+
(`warnings.filterwarnings`, `pytest.warns`, `logging.captureWarnings`) work
|
|
40
|
+
as usual. In a terminal / notebook they are shown in the same layout as log
|
|
41
|
+
records, with the warning class as the label and the caller's code below:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
MPRestWarning <message>
|
|
45
|
+
my_script.py:12
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Elsewhere, or if the application installed its own warnings hook (e.g.
|
|
49
|
+
`logging.captureWarnings(True)`), Python's usual format / the application's
|
|
50
|
+
hook is used.
|
|
51
|
+
|
|
52
|
+
Other libraries
|
|
53
|
+
---------------
|
|
54
|
+
Warnings from other libraries (pymatgen, emmet, ...) raised while a client
|
|
55
|
+
method runs, and their log records that nothing else would handle, are shown
|
|
56
|
+
as WARNING records on `mp_api.client.external.<library>`, naming the library
|
|
57
|
+
code they came from. In a terminal / notebook:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
WARNING mp_api.client.external.pymatgen <message>
|
|
61
|
+
pymatgen/analysis/compatibility/mixing_scheme.py:320
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
and on one line otherwise. Library deprecation warnings are left alone.
|
|
65
|
+
Silence one library with e.g.
|
|
66
|
+
`logging.getLogger("mp_api.client.external.pymatgen").setLevel(logging.ERROR)`.
|
|
67
|
+
See `mp_api.client.core._external`.
|
|
68
|
+
|
|
32
69
|
Quiet mode
|
|
33
70
|
----------
|
|
34
71
|
`quiet()` (or `MPRESTER_QUIET=true`, or `MPRester(quiet=True)`) silences the
|
|
35
|
-
client for the whole process: no log records
|
|
36
|
-
|
|
37
|
-
|
|
72
|
+
client for the whole process: no log records (including those from other
|
|
73
|
+
libraries, above), progress bars, status lines, notices, `MPRestWarning`s or
|
|
74
|
+
`MPDataset*Warning`s.
|
|
75
|
+
Exceptions are still raised, and the client's deprecation warnings
|
|
76
|
+
(`FutureWarning`) are still shown unless `quiet(deprecations=True)` or
|
|
77
|
+
`MPRESTER_QUIET_DEPRECATIONS=true`. It can be used as a context manager; see
|
|
78
|
+
`quiet()` for exactly what is and isn't silenced.
|
|
38
79
|
|
|
39
80
|
Which channel to use
|
|
40
81
|
--------------------
|
|
41
82
|
- The operation failed: raise an exception.
|
|
42
|
-
- A deprecated API was used: `
|
|
43
|
-
shown by default, also when called from other libraries.
|
|
83
|
+
- A deprecated API was used: `mp_deprecation(...)` (a `FutureWarning`), which
|
|
84
|
+
is shown by default, also when called from other libraries.
|
|
44
85
|
- Something the caller can change in their call (an ignored argument, misuse,
|
|
45
|
-
a caveat about what the data means): `mp_warning(...)` (an `MPRestWarning`)
|
|
86
|
+
a caveat about what the data means): `mp_warning(...)` (an `MPRestWarning`),
|
|
87
|
+
or `mp_advice(..., category)` for a more specific warning class.
|
|
46
88
|
- A runtime or data event the caller can't change in code (server
|
|
47
89
|
unreachable, data missing, a local dataset reused): `logger.warning/info`.
|
|
48
90
|
- Diagnostics: `logger.debug`.
|
|
@@ -81,11 +123,11 @@ import io
|
|
|
81
123
|
import logging
|
|
82
124
|
import threading
|
|
83
125
|
import time
|
|
84
|
-
import warnings
|
|
85
126
|
from contextlib import contextmanager
|
|
86
127
|
from typing import TYPE_CHECKING, Self
|
|
87
128
|
|
|
88
129
|
from rich.console import Console
|
|
130
|
+
from rich.highlighter import NullHighlighter
|
|
89
131
|
from rich.logging import RichHandler
|
|
90
132
|
from rich.progress import (
|
|
91
133
|
BarColumn,
|
|
@@ -137,6 +179,8 @@ MP_THEME = Theme(
|
|
|
137
179
|
"mp.error": "#F14668", # Bulma danger
|
|
138
180
|
"mp.muted": "#7A7A7A", # Bulma .has-text-grey
|
|
139
181
|
"mp.title": "bold underline",
|
|
182
|
+
"mp.logger.external": "#939FC8", # mp.secondary: logger of a library record
|
|
183
|
+
"mp.warning.label": "#D69E00", # class name of a client warning
|
|
140
184
|
"mp.field": "bold",
|
|
141
185
|
"mp.table.header": "bold #008F83", # brand dark
|
|
142
186
|
# database versions, relative to the one the API serves
|
|
@@ -200,6 +244,35 @@ def _can_draw_live(console: Console) -> bool:
|
|
|
200
244
|
return console.is_terminal and not console.is_dumb_terminal
|
|
201
245
|
|
|
202
246
|
|
|
247
|
+
def print_warning(label: str, message: str, location: str) -> bool:
|
|
248
|
+
"""Show a client warning on the console, if it's interactive.
|
|
249
|
+
|
|
250
|
+
Rendered like the log records: the label (the warning's class name) in a
|
|
251
|
+
column of its own, the message in the default style, and where the
|
|
252
|
+
warning points to (the caller's code) on a muted line below it.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
label (str) : e.g. "MPRestWarning"
|
|
256
|
+
message (str) : warning text
|
|
257
|
+
location (str) : "file:line"
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
bool : False if nothing was shown (not an interactive console)
|
|
261
|
+
"""
|
|
262
|
+
console = get_console()
|
|
263
|
+
if not _is_interactive(console):
|
|
264
|
+
return False
|
|
265
|
+
grid = Table.grid(padding=(0, 1), expand=True)
|
|
266
|
+
grid.add_column(style="mp.warning.label", no_wrap=True)
|
|
267
|
+
grid.add_column(ratio=1, overflow="fold")
|
|
268
|
+
grid.add_row(
|
|
269
|
+
Text(label),
|
|
270
|
+
Text.assemble(message.strip(), "\n", (location, "mp.muted")),
|
|
271
|
+
)
|
|
272
|
+
console.print(grid)
|
|
273
|
+
return True
|
|
274
|
+
|
|
275
|
+
|
|
203
276
|
def print_notice(message: str, style: str | None = None) -> None:
|
|
204
277
|
"""Print a one-line notice to the console (stderr).
|
|
205
278
|
|
|
@@ -675,6 +748,25 @@ def _app_level(logger: logging.Logger) -> int:
|
|
|
675
748
|
return logging.WARNING # pragma: no cover - root always has a level
|
|
676
749
|
|
|
677
750
|
|
|
751
|
+
class _MPRichHandler(RichHandler):
|
|
752
|
+
"""`RichHandler` showing messages in the console's default style.
|
|
753
|
+
|
|
754
|
+
Only the level (and, for records re-emitted from other libraries, the
|
|
755
|
+
logger name and origin) is styled.
|
|
756
|
+
"""
|
|
757
|
+
|
|
758
|
+
def render_message(self, record: logging.LogRecord, message: str) -> Text:
|
|
759
|
+
if (origin := getattr(record, "mp_origin", None)) is None:
|
|
760
|
+
return Text(message)
|
|
761
|
+
return Text.assemble(
|
|
762
|
+
(record.name, "mp.logger.external"),
|
|
763
|
+
" ",
|
|
764
|
+
str(getattr(record, "mp_message", message)),
|
|
765
|
+
"\n",
|
|
766
|
+
(origin, "mp.muted"),
|
|
767
|
+
)
|
|
768
|
+
|
|
769
|
+
|
|
678
770
|
class _MPDefaultHandler(logging.Handler):
|
|
679
771
|
"""Default handler for `mp_api.client`, see module docstring."""
|
|
680
772
|
|
|
@@ -686,12 +778,14 @@ class _MPDefaultHandler(logging.Handler):
|
|
|
686
778
|
|
|
687
779
|
def _rich_handler(self, console: Console) -> RichHandler:
|
|
688
780
|
if self._rich is None or self._rich.console is not console:
|
|
689
|
-
self._rich =
|
|
781
|
+
self._rich = _MPRichHandler(
|
|
690
782
|
console=console,
|
|
691
783
|
show_time=False,
|
|
692
784
|
show_path=False,
|
|
693
785
|
markup=False,
|
|
694
786
|
rich_tracebacks=False,
|
|
787
|
+
highlighter=NullHighlighter(),
|
|
788
|
+
keywords=[],
|
|
695
789
|
)
|
|
696
790
|
return self._rich
|
|
697
791
|
|
|
@@ -768,10 +862,11 @@ _SILENT = logging.CRITICAL + 1
|
|
|
768
862
|
|
|
769
863
|
|
|
770
864
|
class _QuietState:
|
|
771
|
-
"""Process-wide quiet
|
|
865
|
+
"""Process-wide quiet flags. `saved_level` is the log level to restore."""
|
|
772
866
|
|
|
773
867
|
def __init__(self) -> None:
|
|
774
868
|
self.enabled = False
|
|
869
|
+
self.deprecations = False
|
|
775
870
|
self.saved_level: int = logging.NOTSET
|
|
776
871
|
|
|
777
872
|
|
|
@@ -781,36 +876,58 @@ _QUIET = _QuietState()
|
|
|
781
876
|
class _QuietContext:
|
|
782
877
|
"""Returned by `quiet()`; restores the previous state when used with `with`."""
|
|
783
878
|
|
|
784
|
-
def __init__(self, previous: bool) -> None:
|
|
879
|
+
def __init__(self, previous: tuple[bool, bool]) -> None:
|
|
785
880
|
self._previous = previous
|
|
786
881
|
|
|
787
882
|
def __enter__(self) -> Self:
|
|
788
883
|
return self
|
|
789
884
|
|
|
790
885
|
def __exit__(self, *exc_info: object) -> None:
|
|
791
|
-
_set_quiet(self._previous)
|
|
886
|
+
_set_quiet(*self._previous)
|
|
792
887
|
|
|
793
888
|
|
|
794
|
-
def _set_quiet(enabled: bool) -> bool:
|
|
795
|
-
"""
|
|
889
|
+
def _set_quiet(enabled: bool, deprecations: bool = False) -> tuple[bool, bool]:
|
|
890
|
+
"""Set quiet mode, returning the previous `(enabled, deprecations)`."""
|
|
796
891
|
logger = logging.getLogger(LOGGER_NAME)
|
|
797
892
|
with _handler_lock:
|
|
798
|
-
previous = _QUIET.enabled
|
|
799
|
-
if enabled and not
|
|
893
|
+
previous = (_QUIET.enabled, _QUIET.deprecations)
|
|
894
|
+
if enabled and not _QUIET.enabled:
|
|
800
895
|
_QUIET.saved_level = logger.level
|
|
801
896
|
logger.setLevel(_SILENT)
|
|
802
|
-
elif
|
|
897
|
+
elif _QUIET.enabled and not enabled:
|
|
803
898
|
logger.setLevel(_QUIET.saved_level)
|
|
804
899
|
_QUIET.enabled = enabled
|
|
900
|
+
_QUIET.deprecations = enabled and deprecations
|
|
805
901
|
return previous
|
|
806
902
|
|
|
807
903
|
|
|
808
|
-
def quiet(enabled: bool = True) -> _QuietContext:
|
|
809
|
-
"""Silence
|
|
904
|
+
def quiet(enabled: bool = True, *, deprecations: bool = False) -> _QuietContext:
|
|
905
|
+
"""Silence output from the client in this process.
|
|
906
|
+
|
|
907
|
+
Silenced while quiet:
|
|
908
|
+
|
|
909
|
+
- the client's log records (they don't reach the application's handlers
|
|
910
|
+
either), progress bars, status lines and notices
|
|
911
|
+
- `MPRestWarning`s and the `MPDataset*Warning`s (slow indexing, slicing
|
|
912
|
+
or iteration of downloaded datasets)
|
|
913
|
+
- warnings and fallback log output from other libraries (e.g. pymatgen)
|
|
914
|
+
raised while a client method runs; these are otherwise shown as
|
|
915
|
+
WARNING records on `mp_api.client.external.<library>`
|
|
916
|
+
- with `deprecations=True`, also the client's deprecation warnings
|
|
917
|
+
(`FutureWarning`)
|
|
810
918
|
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
919
|
+
Not silenced:
|
|
920
|
+
|
|
921
|
+
- exceptions
|
|
922
|
+
- deprecation warnings from other libraries
|
|
923
|
+
- output libraries print directly (`print()`, their own progress bars)
|
|
924
|
+
or write from compiled code
|
|
925
|
+
- log records sent to handlers that a library or the application set up
|
|
926
|
+
- library warnings raised outside client methods, e.g. on import, or when
|
|
927
|
+
using returned objects later
|
|
928
|
+
- warnings, if the application replaced `warnings.showwarning` itself
|
|
929
|
+
(e.g. `logging.captureWarnings(True)` after importing the client, or
|
|
930
|
+
`warnings.catch_warnings(record=True)`): they go where it sent them
|
|
814
931
|
|
|
815
932
|
Takes effect immediately; as a context manager, the previous state is
|
|
816
933
|
restored on exit:
|
|
@@ -821,15 +938,19 @@ def quiet(enabled: bool = True) -> _QuietContext:
|
|
|
821
938
|
quiet() # for the rest of the process
|
|
822
939
|
with quiet(): # only within the block
|
|
823
940
|
...
|
|
941
|
+
with quiet(deprecations=True): # also hide the client's deprecation warnings
|
|
942
|
+
...
|
|
824
943
|
```
|
|
825
944
|
|
|
826
945
|
Args:
|
|
827
946
|
enabled (bool) : True to silence the client, False to restore output.
|
|
947
|
+
deprecations (bool) : also silence the client's deprecation warnings.
|
|
948
|
+
Only applies when `enabled` is True.
|
|
828
949
|
|
|
829
950
|
Returns:
|
|
830
951
|
a context manager that restores the previous state on exit
|
|
831
952
|
"""
|
|
832
|
-
return _QuietContext(_set_quiet(enabled))
|
|
953
|
+
return _QuietContext(_set_quiet(enabled, deprecations))
|
|
833
954
|
|
|
834
955
|
|
|
835
956
|
def is_quiet() -> bool:
|
|
@@ -850,13 +971,50 @@ def mp_warning(message: str, stacklevel: int = 1) -> None:
|
|
|
850
971
|
return
|
|
851
972
|
from mp_api.client.core.exceptions import MPRestWarning
|
|
852
973
|
|
|
853
|
-
|
|
974
|
+
_warn(message, MPRestWarning, stacklevel + 1)
|
|
975
|
+
|
|
976
|
+
|
|
977
|
+
def mp_advice(message: str, category: type[Warning], stacklevel: int = 1) -> None:
|
|
978
|
+
"""Like `mp_warning`, with another warning class, e.g. `MPDatasetIndexingWarning`.
|
|
979
|
+
|
|
980
|
+
Args:
|
|
981
|
+
message (str) : warning text
|
|
982
|
+
category (type[Warning]) : warning class
|
|
983
|
+
stacklevel (int) : as for `warnings.warn`, relative to the caller
|
|
984
|
+
"""
|
|
985
|
+
if _QUIET.enabled:
|
|
986
|
+
return
|
|
987
|
+
_warn(message, category, stacklevel + 1)
|
|
988
|
+
|
|
989
|
+
|
|
990
|
+
def mp_deprecation(message: str, stacklevel: int = 1) -> None:
|
|
991
|
+
"""Emit a deprecation warning (`FutureWarning`) for the client's own API.
|
|
992
|
+
|
|
993
|
+
Shown by default; silenced by `quiet(deprecations=True)` or
|
|
994
|
+
`MPRESTER_QUIET_DEPRECATIONS`.
|
|
995
|
+
|
|
996
|
+
Args:
|
|
997
|
+
message (str) : warning text
|
|
998
|
+
stacklevel (int) : as for `warnings.warn`, relative to the caller
|
|
999
|
+
"""
|
|
1000
|
+
if _QUIET.deprecations:
|
|
1001
|
+
return
|
|
1002
|
+
_warn(message, FutureWarning, stacklevel + 1)
|
|
1003
|
+
|
|
1004
|
+
|
|
1005
|
+
def _warn(message: str, category: type[Warning], stacklevel: int) -> None:
|
|
1006
|
+
"""Warn as if from `_warn`'s caller, not counting public method wrappers."""
|
|
1007
|
+
from mp_api.client.core._external import warn
|
|
1008
|
+
|
|
1009
|
+
warn(message, category, stacklevel + 1)
|
|
854
1010
|
|
|
855
1011
|
|
|
856
1012
|
def _configure_from_settings() -> None:
|
|
857
1013
|
"""Configure client output from settings, on import of `mp_api.client`."""
|
|
1014
|
+
from mp_api.client.core._external import install
|
|
858
1015
|
from mp_api.client.core.settings import MAPI_CLIENT_SETTINGS
|
|
859
1016
|
|
|
1017
|
+
install()
|
|
860
1018
|
enable_logging()
|
|
861
1019
|
if MAPI_CLIENT_SETTINGS.QUIET:
|
|
862
|
-
quiet(True)
|
|
1020
|
+
quiet(True, deprecations=MAPI_CLIENT_SETTINGS.QUIET_DEPRECATIONS)
|
|
@@ -33,11 +33,27 @@ if TYPE_CHECKING:
|
|
|
33
33
|
from collections.abc import Callable, Iterator, Sequence
|
|
34
34
|
from typing import Any
|
|
35
35
|
|
|
36
|
+
from pymatgen.analysis.compatibility.mixing_scheme import (
|
|
37
|
+
MaterialsProjectDFTMixingScheme,
|
|
38
|
+
)
|
|
36
39
|
from pymatgen.entries.compatibility import Compatibility
|
|
37
40
|
|
|
38
41
|
from mp_api.client.core._display import ProgressHandle
|
|
39
42
|
|
|
40
43
|
|
|
44
|
+
def mixing_scheme_class() -> type[MaterialsProjectDFTMixingScheme]:
|
|
45
|
+
"""The `MaterialsProjectDFTMixingScheme` class, wherever this pymatgen keeps it.
|
|
46
|
+
|
|
47
|
+
Newer pymatgen moved it to `pymatgen.analysis.compatibility.mixing_scheme`
|
|
48
|
+
and deprecated the old module.
|
|
49
|
+
"""
|
|
50
|
+
try:
|
|
51
|
+
from pymatgen.analysis.compatibility import mixing_scheme
|
|
52
|
+
except ImportError: # pragma: no cover - older pymatgen
|
|
53
|
+
from pymatgen.entries import mixing_scheme # type: ignore[no-redef]
|
|
54
|
+
return mixing_scheme.MaterialsProjectDFTMixingScheme
|
|
55
|
+
|
|
56
|
+
|
|
41
57
|
def _advance(handle: ProgressHandle, amount: float) -> None:
|
|
42
58
|
"""Advance `handle` by `amount`, without going past its total."""
|
|
43
59
|
if handle.total is not None:
|
|
@@ -173,7 +189,8 @@ def apply_corrections(
|
|
|
173
189
|
enabled (bool) : whether to show progress
|
|
174
190
|
"""
|
|
175
191
|
from pymatgen.entries.compatibility import MaterialsProjectAqueousCompatibility
|
|
176
|
-
|
|
192
|
+
|
|
193
|
+
mixing_scheme = mixing_scheme_class()
|
|
177
194
|
|
|
178
195
|
n = len(entries)
|
|
179
196
|
corrector = _private_copy(corrector)
|
|
@@ -183,7 +200,7 @@ def apply_corrections(
|
|
|
183
200
|
) as handle,
|
|
184
201
|
ExitStack() as stack,
|
|
185
202
|
):
|
|
186
|
-
if isinstance(corrector,
|
|
203
|
+
if isinstance(corrector, mixing_scheme):
|
|
187
204
|
# passes: compat_1 corrections (run type 1 entries), structure
|
|
188
205
|
# matching (all entries), then the mixing adjustments (all entries)
|
|
189
206
|
n_1 = sum(
|