mp-api 0.47.1__tar.gz → 0.47.2rc0__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.
Files changed (131) hide show
  1. {mp_api-0.47.1 → mp_api-0.47.2rc0}/PKG-INFO +1 -1
  2. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/core/_display.py +41 -11
  3. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/core/_external.py +2 -0
  4. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/mprester.py +30 -9
  5. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/materials/electronic_structure.py +172 -137
  6. mp_api-0.47.2rc0/mp_api/client/routes/materials/phonon.py +602 -0
  7. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/core/test_external.py +26 -0
  8. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/core/test_quiet.py +126 -2
  9. mp_api-0.47.2rc0/tests/client/materials/test_electronic_structure_messages.py +208 -0
  10. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/materials/test_phonon.py +70 -11
  11. mp_api-0.47.2rc0/tests/client/materials/test_phonon_methods.py +311 -0
  12. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/test_mprester.py +16 -3
  13. mp_api-0.47.1/mp_api/client/routes/materials/phonon.py +0 -423
  14. {mp_api-0.47.1 → mp_api-0.47.2rc0}/.gitignore +0 -0
  15. {mp_api-0.47.1 → mp_api-0.47.2rc0}/LICENSE +0 -0
  16. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/__init__.py +0 -0
  17. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/_test_utils.py +0 -0
  18. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/__init__.py +0 -0
  19. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/_server_utils.py +0 -0
  20. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/contribs/__init__.py +0 -0
  21. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/contribs/_logger.py +0 -0
  22. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/contribs/_types.py +0 -0
  23. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/contribs/_units.py +0 -0
  24. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/contribs/client.py +0 -0
  25. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/contribs/schemas.py +0 -0
  26. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/contribs/settings.py +0 -0
  27. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/contribs/utils.py +0 -0
  28. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/core/__init__.py +0 -0
  29. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/core/_entries.py +0 -0
  30. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/core/_oxygen_evolution.py +0 -0
  31. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/core/client.py +0 -0
  32. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/core/delta.py +0 -0
  33. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/core/exceptions.py +0 -0
  34. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/core/schemas.py +0 -0
  35. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/core/settings.py +0 -0
  36. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/core/utils.py +0 -0
  37. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/__init__.py +0 -0
  38. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/_server.py +0 -0
  39. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/materials/__init__.py +0 -0
  40. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/materials/absorption.py +0 -0
  41. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/materials/alloys.py +0 -0
  42. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/materials/bonds.py +0 -0
  43. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/materials/chemenv.py +0 -0
  44. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/materials/dielectric.py +0 -0
  45. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/materials/doi.py +0 -0
  46. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/materials/elasticity.py +0 -0
  47. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/materials/electrodes.py +0 -0
  48. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/materials/eos.py +0 -0
  49. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/materials/grain_boundaries.py +0 -0
  50. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/materials/magnetism.py +0 -0
  51. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/materials/materials.py +0 -0
  52. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/materials/oxidation_states.py +0 -0
  53. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/materials/piezo.py +0 -0
  54. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/materials/provenance.py +0 -0
  55. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/materials/robocrys.py +0 -0
  56. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/materials/similarity.py +0 -0
  57. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/materials/substrates.py +0 -0
  58. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/materials/summary.py +0 -0
  59. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/materials/surface_properties.py +0 -0
  60. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/materials/synthesis.py +0 -0
  61. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/materials/tasks.py +0 -0
  62. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/materials/thermo.py +0 -0
  63. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/materials/xas.py +0 -0
  64. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/molecules/__init__.py +0 -0
  65. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/molecules/jcesr.py +0 -0
  66. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/molecules/molecules.py +0 -0
  67. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/client/routes/molecules/summary.py +0 -0
  68. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/mcp/__init__.py +0 -0
  69. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/mcp/_schemas.py +0 -0
  70. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/mcp/mp_mcp.py +0 -0
  71. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/mcp/server.py +0 -0
  72. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/mcp/tools.py +0 -0
  73. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/mcp/utils.py +0 -0
  74. {mp_api-0.47.1 → mp_api-0.47.2rc0}/mp_api/py.typed +0 -0
  75. {mp_api-0.47.1 → mp_api-0.47.2rc0}/pyproject.toml +0 -0
  76. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/__init__.py +0 -0
  77. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/__init__.py +0 -0
  78. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/contribs/conftest.py +0 -0
  79. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/contribs/test_client.py +0 -0
  80. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/contribs/test_contribs_schemas.py +0 -0
  81. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/contribs/test_contribs_utils.py +0 -0
  82. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/contribs/test_side_effects.py +0 -0
  83. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/contribs/test_types.py +0 -0
  84. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/core/test_client_progress.py +0 -0
  85. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/core/test_delta.py +0 -0
  86. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/core/test_delta_versions.py +0 -0
  87. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/core/test_display.py +0 -0
  88. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/core/test_ids.py +0 -0
  89. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/core/test_oxygen_evolution.py +0 -0
  90. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/core/test_schemas.py +0 -0
  91. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/core/test_session_headers.py +0 -0
  92. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/core/test_utils.py +0 -0
  93. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/materials/__init__.py +0 -0
  94. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/materials/test_absorption.py +0 -0
  95. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/materials/test_alloys.py +0 -0
  96. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/materials/test_bonds.py +0 -0
  97. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/materials/test_chemenv.py +0 -0
  98. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/materials/test_dielectric.py +0 -0
  99. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/materials/test_doi.py +0 -0
  100. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/materials/test_elasticity.py +0 -0
  101. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/materials/test_electrodes.py +0 -0
  102. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/materials/test_electronic_structure.py +0 -0
  103. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/materials/test_eos.py +0 -0
  104. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/materials/test_grain_boundary.py +0 -0
  105. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/materials/test_magnetism.py +0 -0
  106. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/materials/test_materials.py +0 -0
  107. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/materials/test_oxidation_states.py +0 -0
  108. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/materials/test_piezo.py +0 -0
  109. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/materials/test_provenance.py +0 -0
  110. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/materials/test_robocrys.py +0 -0
  111. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/materials/test_similarity.py +0 -0
  112. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/materials/test_substrates.py +0 -0
  113. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/materials/test_summary.py +0 -0
  114. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/materials/test_surface_properties.py +0 -0
  115. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/materials/test_synthesis.py +0 -0
  116. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/materials/test_tasks.py +0 -0
  117. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/materials/test_thermo.py +0 -0
  118. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/materials/test_xas.py +0 -0
  119. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/molecules/__init__.py +0 -0
  120. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/molecules/test_jcesr.py +0 -0
  121. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/molecules/test_molecules.py +0 -0
  122. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/molecules/test_summary.py +0 -0
  123. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/test_client.py +0 -0
  124. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/test_core_client.py +0 -0
  125. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/test_entry_progress.py +0 -0
  126. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/client/test_heartbeat.py +0 -0
  127. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/conftest.py +0 -0
  128. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/mcp/__init__.py +0 -0
  129. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/mcp/test_server.py +0 -0
  130. {mp_api-0.47.1 → mp_api-0.47.2rc0}/tests/mcp/test_tools.py +0 -0
  131. {mp_api-0.47.1 → mp_api-0.47.2rc0}/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.1
3
+ Version: 0.47.2rc0
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
@@ -89,6 +89,10 @@ Which channel to use
89
89
  unreachable, data missing, a local dataset reused): `logger.warning/info`.
90
90
  - Diagnostics: `logger.debug`.
91
91
  - Transient progress: `progress_bar` / `status`, interactive output only.
92
+ Bars and summary lines report work the caller asked for: lookups the client
93
+ makes on its own behalf (resolving IDs, then fetching the data) run inside a
94
+ `status()`, which describes the whole operation and keeps the bars inside it
95
+ from drawing or printing anything.
92
96
  - Output the user explicitly asked for (`notify_db_version`): `print_notice`.
93
97
 
94
98
  So WARNING-level log records always mean something went wrong, and
@@ -119,6 +123,7 @@ Python shows a given warning once per code location; use
119
123
 
120
124
  from __future__ import annotations
121
125
 
126
+ import contextvars
122
127
  import io
123
128
  import logging
124
129
  import threading
@@ -446,6 +451,13 @@ class ProgressHandle:
446
451
  self._progress.advance(self._task_id, advance)
447
452
 
448
453
 
454
+ # Whether a `status()` block is running in this context. Contextvars are copied
455
+ # into worker threads by `_external.in_current_context`, so those inherit it.
456
+ _IN_OPERATION: contextvars.ContextVar[bool] = contextvars.ContextVar(
457
+ "mp_api_client_in_operation", default=False
458
+ )
459
+
460
+
449
461
  def _as_int(value: float | None) -> float | None:
450
462
  """Show (near) whole-number counts as ints, without a trailing `.0`."""
451
463
  if isinstance(value, float) and abs(value - round(value)) < 1e-6:
@@ -469,7 +481,9 @@ def progress_bar(
469
481
  Args:
470
482
  description (str) : label shown next to the bar
471
483
  total (float or None) : expected number of units, None if unknown
472
- enabled (bool) : if False, nothing is shown (a no-op handle is yielded)
484
+ enabled (bool) : if False, nothing is shown (a no-op handle is yielded).
485
+ Nothing is shown either inside a `status()` block, which describes
486
+ the whole operation itself.
473
487
  delay (float) : seconds before the bar is shown, so quick
474
488
  operations stay silent. Checked on each update.
475
489
  unit (str) : unit name for the rate column
@@ -487,7 +501,7 @@ def progress_bar(
487
501
  ProgressHandle
488
502
  """
489
503
  console = get_console()
490
- if not enabled or is_quiet():
504
+ if not enabled or is_quiet() or _IN_OPERATION.get():
491
505
  yield ProgressHandle(total, summary=summary)
492
506
  return
493
507
  if not _is_interactive(console):
@@ -705,25 +719,41 @@ def status(message: str, *, enabled: bool = True) -> Iterator[None]:
705
719
  of WARNING-level logs). If disabled (muted progress bars) or in quiet mode,
706
720
  nothing is shown or logged.
707
721
 
722
+ The status describes the whole operation the caller asked for, so
723
+ everything inside it stays quiet:
724
+
725
+ - `progress_bar`s inside the block draw nothing, print no summary line and
726
+ log nothing (e.g. the lookup of a material's IDs the client makes on
727
+ its own behalf);
728
+ - a `status` nested in another one shows and logs nothing.
729
+
730
+ Work with countable progress that the caller should watch (a multi-page
731
+ download) needs a `progress_bar` instead of a `status`.
732
+
708
733
  Args:
709
734
  message (str) : what is happening, e.g. "Counting documents..."
710
735
  enabled (bool) : if False, show nothing
711
736
  """
712
737
  console = get_console()
713
- if not enabled or is_quiet():
714
- yield
715
- return
716
- if not (_is_interactive(console) and _can_draw_live(console)):
717
- logging.getLogger(LOGGER_NAME).info(message)
738
+ if not enabled or is_quiet() or _IN_OPERATION.get():
718
739
  yield
719
740
  return
720
741
 
721
- progress = _STATE.acquire(console)
722
- task_id = progress.add_task(message, total=None, status=True)
742
+ token = _IN_OPERATION.set(True)
723
743
  try:
724
- yield
744
+ if not (_is_interactive(console) and _can_draw_live(console)):
745
+ logging.getLogger(LOGGER_NAME).info(message)
746
+ yield
747
+ return
748
+
749
+ progress = _STATE.acquire(console)
750
+ task_id = progress.add_task(message, total=None, status=True)
751
+ try:
752
+ yield
753
+ finally:
754
+ _STATE.release(task_id)
725
755
  finally:
726
- _STATE.release(task_id)
756
+ _IN_OPERATION.reset(token)
727
757
 
728
758
 
729
759
  def _app_handlers(logger: logging.Logger) -> list[logging.Handler]:
@@ -154,6 +154,8 @@ def _tracked(fn: Callable[..., Any]) -> Callable[..., Any]:
154
154
 
155
155
  @functools.wraps(fn)
156
156
  def wrapper(*args: Any, **kwargs: Any) -> Any:
157
+ # keep this frame out of tracebacks in IPython, Jupyter and pytest
158
+ __tracebackhide__ = True
157
159
  with capturing():
158
160
  return fn(*args, **kwargs)
159
161
 
@@ -74,6 +74,7 @@ if TYPE_CHECKING:
74
74
  from typing import Any, Literal
75
75
 
76
76
  import numpy as np
77
+ from emmet.core.phonon import PhononBS, PhononDOS
77
78
  from emmet.core.tasks import CoreTaskDoc
78
79
  from packaging.version import Version
79
80
  from pymatgen.analysis.phase_diagram import PDEntry
@@ -1514,31 +1515,52 @@ class MPRester(_Rester):
1514
1515
  load_projections=load_projections,
1515
1516
  )
1516
1517
 
1517
- def get_phonon_dos_by_material_id(self, material_id: str):
1518
+ def get_phonon_dos_by_material_id(
1519
+ self,
1520
+ material_id: str,
1521
+ phonon_method: Literal["pheasy", "dfpt"] | None = None,
1522
+ ) -> PhononDOS | dict[str, Any]:
1518
1523
  """Get phonon density of states data corresponding to a material_id.
1519
1524
 
1520
1525
  Args:
1521
1526
  material_id (str): Materials Project material_id.
1527
+ phonon_method (str or None): phonon method, i.e. pheasy or dfpt. By default,
1528
+ the method is chosen from the material's data: pheasy if it has it,
1529
+ otherwise dfpt. If the pheasy calculation has no DOS, the dfpt one
1530
+ is used (with a warning). A method passed explicitly is never replaced.
1522
1531
 
1523
1532
  Returns:
1524
- CompletePhononDos: A phonon DOS object.
1525
-
1533
+ dos (PhononDOS): PhononDOS object
1526
1534
  """
1527
1535
  return self.materials.phonon.get_dos_from_material_id(
1528
- material_id=material_id, phonon_method="dfpt"
1536
+ material_id=material_id, phonon_method=phonon_method
1529
1537
  )
1530
1538
 
1531
- def get_phonon_bandstructure_by_material_id(self, material_id: str):
1539
+ def get_phonon_bandstructure_by_material_id(
1540
+ self,
1541
+ material_id: str,
1542
+ phonon_method: Literal["pheasy", "dfpt"] | None = None,
1543
+ path_type: str | BSPathType = BSPathType.setyawan_curtarolo,
1544
+ ) -> PhononBS | dict[str, Any]:
1532
1545
  """Get phonon dispersion data corresponding to a material_id.
1533
1546
 
1534
1547
  Args:
1535
1548
  material_id (str): Materials Project material_id.
1549
+ phonon_method (str or None): phonon method, i.e. pheasy or dfpt. By default,
1550
+ the method is chosen from the material's data: pheasy if it has it,
1551
+ otherwise dfpt. If the pheasy calculation has no band structure,
1552
+ the dfpt one is used (with a warning). A method passed explicitly
1553
+ is never replaced.
1554
+ path_type (BSPathType or str): k-path selection convention for the band structure.
1555
+ pheasy has all conventions; dfpt only has setyawan_curtarolo.
1536
1556
 
1537
1557
  Returns:
1538
- PhononBandStructureSymmLine: phonon band structure.
1558
+ bandstructure (PhononBS): PhononBS object
1539
1559
  """
1540
1560
  return self.materials.phonon.get_bandstructure_from_material_id(
1541
- material_id=material_id, phonon_method="dfpt"
1561
+ material_id=material_id,
1562
+ phonon_method=phonon_method,
1563
+ path_type=path_type,
1542
1564
  )
1543
1565
 
1544
1566
  def get_wulff_shape(self, material_id: str):
@@ -1796,8 +1818,7 @@ class MPRester(_Rester):
1796
1818
  # Prevent user error
1797
1819
  if isinstance(material_ids, str | MPID):
1798
1820
  raise MPRestError(
1799
- "Input material IDs (even a single ID) as a list: "
1800
- f"`[{material_ids}]`"
1821
+ f"Input material IDs (even a single ID) as a list: `[{material_ids}]`"
1801
1822
  )
1802
1823
 
1803
1824
  entry_preference = {
@@ -16,7 +16,7 @@ from pymatgen.electronic_structure.bandstructure import (
16
16
  from pymatgen.electronic_structure.core import OrbitalType, Spin
17
17
 
18
18
  from mp_api.client.core import BaseRester, MPRestError
19
- from mp_api.client.core._display import mp_deprecation
19
+ from mp_api.client.core._display import mp_deprecation, status
20
20
  from mp_api.client.core.utils import validate_ids
21
21
 
22
22
  if TYPE_CHECKING:
@@ -284,48 +284,57 @@ class BandStructureRester(BaseESPropertyRester):
284
284
  Returns:
285
285
  bandstructure (BandStructure): BandStructure or BandStructureSymmLine object
286
286
  """
287
- bs_lbl, _ = self._get_delta_table(
288
- "materialsproject-parsed",
289
- "core/electronic-structure/bandstructures/",
290
- label="bandstructure",
291
- )
292
-
293
- query = f"""
294
- SELECT *
295
- FROM {bs_lbl}
296
- WHERE identifier='{str(AlphaID(task_id.split("-")[-1],padlen=8))}'
297
- """
287
+ with status(
288
+ f"Fetching the band structure for {task_id}...",
289
+ enabled=not self.mute_progress_bars,
290
+ ):
291
+ bs_lbl, _ = self._get_delta_table(
292
+ "materialsproject-parsed",
293
+ "core/electronic-structure/bandstructures/",
294
+ label="bandstructure",
295
+ )
298
296
 
299
- if run_type:
300
- rt = RunType(run_type) if isinstance(run_type, str) else run_type
301
- query += f"\nAND run_type='{rt.value}'"
302
- if path_type:
303
- query += f"\nAND path_convention='{path_type}'"
304
-
305
- table = self._query_delta_single(query, label=bs_lbl)
306
- if len(deser := table.to_pylist(maps_as_pydicts="strict")) > 0:
307
- if load_projections:
308
- proj_bs_label, _ = self._get_delta_table(
309
- "materialsproject-parsed",
310
- "core/electronic-structure/projected-bandstructures/",
311
- label="bandstructure_projections",
312
- )
313
- proj_table = self._query_delta_single(
314
- query.replace(bs_lbl, proj_bs_label), label=proj_bs_label
297
+ query = f"""
298
+ SELECT *
299
+ FROM {bs_lbl}
300
+ WHERE identifier='{str(AlphaID(task_id.split("-")[-1],padlen=8))}'
301
+ """
302
+
303
+ if run_type:
304
+ rt = RunType(run_type) if isinstance(run_type, str) else run_type
305
+ query += f"\nAND run_type='{rt.value}'"
306
+ if path_type:
307
+ query += f"\nAND path_convention='{path_type}'"
308
+
309
+ table = self._query_delta_single(query, label=bs_lbl)
310
+ if len(deser := table.to_pylist(maps_as_pydicts="strict")) > 0:
311
+ if load_projections:
312
+ proj_bs_label, _ = self._get_delta_table(
313
+ "materialsproject-parsed",
314
+ "core/electronic-structure/projected-bandstructures/",
315
+ label="bandstructure_projections",
316
+ )
317
+ proj_table = self._query_delta_single(
318
+ query.replace(bs_lbl, proj_bs_label), label=proj_bs_label
319
+ )
320
+ if (
321
+ len(
322
+ deser_proj := proj_table.to_pylist(maps_as_pydicts="strict")
323
+ )
324
+ > 0
325
+ ):
326
+ deser[0]["projections"] = deser_proj[0]
327
+ emmet_bs = ElectronicBS(**deser[0])
328
+ return emmet_bs.to_pmg(
329
+ pmg_cls=(
330
+ BandStructureSymmLine if emmet_bs.labels_dict else BandStructure
331
+ )
315
332
  )
316
- if (
317
- len(deser_proj := proj_table.to_pylist(maps_as_pydicts="strict"))
318
- > 0
319
- ):
320
- deser[0]["projections"] = deser_proj[0]
321
- emmet_bs = ElectronicBS(**deser[0])
322
- return emmet_bs.to_pmg(
323
- pmg_cls=BandStructureSymmLine if emmet_bs.labels_dict else BandStructure
333
+ raise MPRestError(
334
+ f"No bandstructure data found for {task_id=}"
335
+ + (f" and run_type={rt.value}" if run_type else "")
336
+ + (f" and path_type={path_type}" if path_type else "")
324
337
  )
325
- raise MPRestError(
326
- f"No bandstructure data found for {task_id=}"
327
- + (f"run_type={rt}" if run_type else "")
328
- )
329
338
 
330
339
  def get_bandstructure_from_material_id(
331
340
  self,
@@ -349,62 +358,73 @@ class BandStructureRester(BaseESPropertyRester):
349
358
  pt: BSPathType = (
350
359
  BSPathType(path_type) if isinstance(path_type, str) else path_type
351
360
  )
352
- if line_mode:
353
- bs_doc = self.es_rester.search(
354
- material_ids=material_id, fields=["bandstructure"]
355
- )
356
- if not bs_doc:
357
- raise MPRestError(
358
- f"No electronic structure data found for material ID {material_id}."
361
+ kind = f"{pt.value} band structure" if line_mode else "uniform band structure"
362
+ with status(
363
+ f"Fetching the {kind} for {material_id}...",
364
+ enabled=not self.mute_progress_bars,
365
+ ):
366
+ if line_mode:
367
+ bs_doc = self.es_rester.search(
368
+ material_ids=material_id, fields=["bandstructure"]
359
369
  )
360
-
361
- if (_bs_data := bs_doc[0]["bandstructure"]) is None:
362
- raise MPRestError(
363
- f"No {pt.value} band structure data found for {material_id}"
370
+ if not bs_doc:
371
+ raise MPRestError(
372
+ f"No electronic structure data found for material ID {material_id}."
373
+ )
374
+
375
+ if (_bs_data := bs_doc[0]["bandstructure"]) is None:
376
+ raise MPRestError(
377
+ f"No {pt.value} band structure data found for {material_id}"
378
+ )
379
+
380
+ bs_data = (
381
+ _bs_data.model_dump() if self.use_document_model else _bs_data # type: ignore
364
382
  )
365
383
 
366
- bs_data = (
367
- _bs_data.model_dump() if self.use_document_model else _bs_data # type: ignore
384
+ if bs_data.get(pt.value, None) is None:
385
+ available = sorted(k for k, v in bs_data.items() if v is not None)
386
+ raise MPRestError(
387
+ f"No {pt.value} band structure data found for {material_id}"
388
+ + (
389
+ f" (available: {', '.join(available)})."
390
+ if available
391
+ else "."
392
+ )
393
+ )
394
+ bs_task_id = bs_data[pt.value]["task_id"]
395
+
396
+ else:
397
+ if not (
398
+ bs_doc := self.es_rester.search(
399
+ material_ids=material_id, fields=["dos"]
400
+ )
401
+ ):
402
+ raise MPRestError(
403
+ f"No electronic structure data found for material ID {material_id}."
404
+ )
405
+
406
+ if (_bs_data := bs_doc[0]["dos"]) is None:
407
+ raise MPRestError(
408
+ f"No uniform band structure data found for {material_id}"
409
+ )
410
+
411
+ bs_data = _bs_data.model_dump() if self.use_document_model else _bs_data
412
+
413
+ if bs_data.get("total", None) is None:
414
+ raise MPRestError(
415
+ f"No uniform band structure data found for {material_id}"
416
+ )
417
+ bs_task_id = bs_data["task_id"]
418
+
419
+ bs_obj = self.get_bandstructure_from_task_id(
420
+ bs_task_id,
421
+ path_type=pt if line_mode else BSPathType.unknown,
422
+ load_projections=load_projections,
368
423
  )
369
424
 
370
- if bs_data.get(pt.value, None) is None:
371
- raise MPRestError(
372
- f"No {pt.value} band structure data found for {material_id}"
373
- )
374
- bs_task_id = bs_data[pt.value]["task_id"]
375
-
376
- else:
377
- if not (
378
- bs_doc := self.es_rester.search(
379
- material_ids=material_id, fields=["dos"]
380
- )
381
- ):
382
- raise MPRestError(
383
- f"No electronic structure data found for material ID {material_id}."
384
- )
385
-
386
- if (_bs_data := bs_doc[0]["dos"]) is None:
387
- raise MPRestError(
388
- f"No uniform band structure data found for {material_id}"
389
- )
390
-
391
- bs_data = _bs_data.model_dump() if self.use_document_model else _bs_data
392
-
393
- if bs_data.get("total", None) is None:
394
- raise MPRestError(
395
- f"No uniform band structure data found for {material_id}"
396
- )
397
- bs_task_id = bs_data["task_id"]
398
-
399
- bs_obj = self.get_bandstructure_from_task_id(
400
- bs_task_id,
401
- path_type=pt if line_mode else BSPathType.unknown,
402
- load_projections=load_projections,
403
- )
404
-
405
- if bs_obj:
406
- return bs_obj
407
- raise MPRestError("No band structure object found.")
425
+ if bs_obj:
426
+ return bs_obj
427
+ raise MPRestError("No band structure object found.")
408
428
 
409
429
 
410
430
  class DosRester(BaseESPropertyRester):
@@ -540,42 +560,49 @@ class DosRester(BaseESPropertyRester):
540
560
  Returns:
541
561
  pymatgen Dos
542
562
  """
543
- dos_lbl, _ = self._get_delta_table(
544
- "materialsproject-parsed",
545
- "core/electronic-structure/total-dos/",
546
- label="total_dos",
547
- )
548
-
549
- query = f"""
550
- SELECT *
551
- FROM {dos_lbl}
552
- WHERE identifier='{str(AlphaID(task_id.split("-")[-1],padlen=8))}'
553
- """
563
+ with status(
564
+ f"Fetching the density of states for {task_id}...",
565
+ enabled=not self.mute_progress_bars,
566
+ ):
567
+ dos_lbl, _ = self._get_delta_table(
568
+ "materialsproject-parsed",
569
+ "core/electronic-structure/total-dos/",
570
+ label="total_dos",
571
+ )
554
572
 
555
- if run_type:
556
- rt = RunType(run_type) if isinstance(run_type, str) else run_type
557
- query += f"\nAND run_type='{rt.value}'"
558
-
559
- table = self._query_delta_single(query, label=dos_lbl)
560
- if len(deser := table.to_pylist(maps_as_pydicts="strict")) > 0:
561
- if load_projections:
562
- proj_dos_label, _ = self._get_delta_table(
563
- "materialsproject-parsed",
564
- "core/electronic-structure/projected-dos/",
565
- label="dos_projections",
566
- )
567
- proj_table = self._query_delta_single(
568
- query.replace(dos_lbl, proj_dos_label), label=proj_dos_label
569
- )
570
- if (
571
- len(deser_proj := proj_table.to_pylist(maps_as_pydicts="strict"))
572
- > 0
573
- ):
574
- deser[0]["projected_densities"] = deser_proj[0]
575
- return ElectronicDos(**deser[0]).to_pmg()
576
- raise MPRestError(
577
- f"No DOS data found for {task_id=}" + (f"run_type={rt}" if run_type else "")
578
- )
573
+ query = f"""
574
+ SELECT *
575
+ FROM {dos_lbl}
576
+ WHERE identifier='{str(AlphaID(task_id.split("-")[-1],padlen=8))}'
577
+ """
578
+
579
+ if run_type:
580
+ rt = RunType(run_type) if isinstance(run_type, str) else run_type
581
+ query += f"\nAND run_type='{rt.value}'"
582
+
583
+ table = self._query_delta_single(query, label=dos_lbl)
584
+ if len(deser := table.to_pylist(maps_as_pydicts="strict")) > 0:
585
+ if load_projections:
586
+ proj_dos_label, _ = self._get_delta_table(
587
+ "materialsproject-parsed",
588
+ "core/electronic-structure/projected-dos/",
589
+ label="dos_projections",
590
+ )
591
+ proj_table = self._query_delta_single(
592
+ query.replace(dos_lbl, proj_dos_label), label=proj_dos_label
593
+ )
594
+ if (
595
+ len(
596
+ deser_proj := proj_table.to_pylist(maps_as_pydicts="strict")
597
+ )
598
+ > 0
599
+ ):
600
+ deser[0]["projected_densities"] = deser_proj[0]
601
+ return ElectronicDos(**deser[0]).to_pmg()
602
+ raise MPRestError(
603
+ f"No DOS data found for {task_id=}"
604
+ + (f" and run_type={rt.value}" if run_type else "")
605
+ )
579
606
 
580
607
  def get_dos_from_material_id(
581
608
  self, material_id: str, load_projections: bool = False
@@ -590,17 +617,25 @@ class DosRester(BaseESPropertyRester):
590
617
  Returns:
591
618
  pymatgen Dos
592
619
  """
593
- if not (
594
- dos_doc := self.es_rester.search(material_ids=material_id, fields=["dos"])
620
+ with status(
621
+ f"Fetching the density of states for {material_id}...",
622
+ enabled=not self.mute_progress_bars,
595
623
  ):
596
- raise MPRestError(
597
- f"No electronic structure data found for material ID {material_id}."
598
- )
624
+ if not (
625
+ dos_doc := self.es_rester.search(
626
+ material_ids=material_id, fields=["dos"]
627
+ )
628
+ ):
629
+ raise MPRestError(
630
+ f"No electronic structure data found for material ID {material_id}."
631
+ )
599
632
 
600
- if not (dos_data := dos_doc[0].get("dos")):
601
- raise MPRestError(f"No density of states data found for {material_id}")
633
+ if not (dos_data := dos_doc[0].get("dos")):
634
+ raise MPRestError(f"No density of states data found for {material_id}")
602
635
 
603
- dos_task_id = (dos_data.model_dump() if self.use_document_model else dos_data)[
604
- "task_id"
605
- ]
606
- return self.get_dos_from_task_id(dos_task_id, load_projections=load_projections)
636
+ dos_task_id = (
637
+ dos_data.model_dump() if self.use_document_model else dos_data
638
+ )["task_id"]
639
+ return self.get_dos_from_task_id(
640
+ dos_task_id, load_projections=load_projections
641
+ )