Skip to content

Commit

Permalink
Clean up dependencies (#666)
Browse files Browse the repository at this point in the history
* Make custodian and mpcontribs optional deps

* Add emmet-core[all] to options

* Handle alloy rester import if no addon pkg

* Update requirements.txt

* Linting

* Bump emmet-core

* Add boto3 to optional deps and handle chg densities

* Add boto3 to reqs list

* Check for charge density rester in convenience fn

* Custom __getattr__ for alloy and charge_density

* Linting

* Separate import error and add pip commands

* Remove instantiation warnings for client

* Fix mpcontribs error message
  • Loading branch information
munrojm committed Sep 9, 2022
1 parent 9ec0a9d commit 7b9b3e1
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 15 deletions.
23 changes: 22 additions & 1 deletion mp_api/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from emmet.core.summary import HasProps
from emmet.core.symmetry import CrystalSystem
from emmet.core.vasp.calc_types import CalcType
from mpcontribs.client import Client
from pymatgen.analysis.magnetism import Ordering
from pymatgen.analysis.phase_diagram import PhaseDiagram
from pymatgen.analysis.pourbaix_diagram import IonEntry
Expand Down Expand Up @@ -133,7 +132,13 @@ def __init__(
)

try:
from mpcontribs.client import Client
self.contribs = Client(api_key)
except ImportError:
self.contribs = None
warnings.warn("mpcontribs-client not installed. "
"Install the package to query MPContribs data, or construct pourbaix diagrams: "
"'pip install mpcontribs-client'")
except Exception as error:
self.contribs = None
warnings.warn(f"Problem loading MPContribs client: {error}")
Expand Down Expand Up @@ -177,6 +182,18 @@ def __exit__(self, exc_type, exc_val, exc_tb):
"""
self.session.close()

def __getattr__(self, attr):
if attr == "alloys":
raise MPRestError("Alloy addon package not installed. "
"To query alloy data first install with: 'pip install pymatgen-analysis-alloys'")
elif attr == "charge_density":
raise MPRestError("boto3 not installed. "
"To query charge density data first install with: 'pip install boto3'")
else:
raise AttributeError(
f"{self.__class__.__name__!r} object has no attribute {attr!r}"
)

def get_task_ids_associated_with_material_id(
self, material_id: str, calc_types: Optional[List[CalcType]] = None
) -> List[str]:
Expand Down Expand Up @@ -940,6 +957,10 @@ def get_charge_density_from_material_id(
chgcar: Pymatgen Chgcar object.
"""

if not hasattr(self, "charge_density"):
raise MPRestError("boto3 not installed. "
"To query charge density data install the boto3 package.")

# TODO: really we want a recommended task_id for charge densities here
# this could potentially introduce an ambiguity
task_ids = self.get_task_ids_associated_with_material_id(
Expand Down
3 changes: 1 addition & 2 deletions mp_api/client/core/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

import requests
from emmet.core.utils import jsanitize
from maggma.api.utils import api_sanitize
from monty.json import MontyDecoder
from pydantic import BaseModel, create_model
from requests.adapters import HTTPAdapter
Expand All @@ -28,7 +27,7 @@
from urllib3.util.retry import Retry

from mp_api.client.core.settings import MAPIClientSettings
from mp_api.client.core.utils import validate_ids
from mp_api.client.core.utils import validate_ids, api_sanitize

try:
from pymatgen.core import __version__ as pmg_version # type: ignore
Expand Down
92 changes: 91 additions & 1 deletion mp_api/client/core/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import re
from typing import List
from typing import List, Optional, Type, get_args

from monty.json import MSONable
from pydantic import BaseModel
from pydantic.schema import get_flat_models_from_model
from pydantic.utils import lenient_issubclass


def validate_ids(id_list: List[str]):
Expand All @@ -21,3 +26,88 @@ def validate_ids(id_list: List[str]):
raise ValueError(f"{entry} is not formatted correctly!")

return id_list


def api_sanitize(
pydantic_model: Type[BaseModel],
fields_to_leave: Optional[List[str]] = None,
allow_dict_msonable=False,
):
"""
Function to clean up pydantic models for the API by:
1.) Making fields optional
2.) Allowing dictionaries in-place of the objects for MSONable quantities
WARNING: This works in place, so it mutates the model and all sub-models
Args:
fields_to_leave: list of strings for model fields as "model__name__.field"
"""

models = [
model
for model in get_flat_models_from_model(pydantic_model)
if issubclass(model, BaseModel)
] # type: List[Type[BaseModel]]

fields_to_leave = fields_to_leave or []
fields_tuples = [f.split(".") for f in fields_to_leave]
assert all(len(f) == 2 for f in fields_tuples)

for model in models:
model_fields_to_leave = {f[1] for f in fields_tuples if model.__name__ == f[0]}
for name, field in model.__fields__.items():
field_type = field.type_

if name not in model_fields_to_leave:
field.required = False
field.default = None
field.default_factory = None
field.allow_none = True
field.field_info.default = None
field.field_info.default_factory = None

if field_type is not None and allow_dict_msonable:
if lenient_issubclass(field_type, MSONable):
field.type_ = allow_msonable_dict(field_type)
else:
for sub_type in get_args(field_type):
if lenient_issubclass(sub_type, MSONable):
allow_msonable_dict(sub_type)
field.populate_validators()

return pydantic_model


def allow_msonable_dict(monty_cls: Type[MSONable]):
"""
Patch Monty to allow for dict values for MSONable
"""

def validate_monty(cls, v):
"""
Stub validator for MSONable as a dictionary only
"""
if isinstance(v, cls):
return v
elif isinstance(v, dict):
# Just validate the simple Monty Dict Model
errors = []
if v.get("@module", "") != monty_cls.__module__:
errors.append("@module")

if v.get("@class", "") != monty_cls.__name__:
errors.append("@class")

if len(errors) > 0:
raise ValueError(
"Missing Monty seriailzation fields in dictionary: {errors}"
)

return v
else:
raise ValueError(f"Must provide {cls.__name__} or MSONable dictionary")

setattr(monty_cls, "validate_monty", classmethod(validate_monty))

return monty_cls
15 changes: 12 additions & 3 deletions mp_api/client/routes/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from ast import Import
from .eos import EOSRester
from .materials import MaterialsRester
from .similarity import SimilarityRester
Expand All @@ -15,11 +16,9 @@
from .piezo import PiezoRester
from .magnetism import MagnetismRester
from .summary import SummaryRester
from .robocrys import RobocrysRester
from .molecules import MoleculesRester
from .synthesis import SynthesisRester
from .electrodes import ElectrodeRester
from .charge_density import ChargeDensityRester
from .electronic_structure import (
ElectronicStructureRester,
BandStructureRester,
Expand All @@ -30,4 +29,14 @@
from ._user_settings import UserSettingsRester
from ._general_store import GeneralStoreRester
from .bonds import BondsRester
from .alloys import AlloysRester
from .robocrys import RobocrysRester

try:
from .alloys import AlloysRester
except ImportError:
AlloysRester = None # type: ignore

try:
from .charge_density import ChargeDensityRester
except ImportError:
ChargeDensityRester = None # type: ignore
2 changes: 0 additions & 2 deletions mp_api/client/routes/alloys.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
from mp_api.client.core.utils import validate_ids
from emmet.core.alloys import AlloyPairDoc

import warnings


class AlloysRester(BaseRester[AlloyPairDoc]):

Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
emmet-core==0.32.3
emmet-core[all]==0.35.1
pydantic>=1.8.2
pymatgen>=2022.3.7
pymatgen-analysis-alloys>=0.0.3
typing-extensions==4.1.1
maggma==0.47.4
requests==2.27.1
monty==2022.3.12
mpcontribs-client>=4.2.9
custodian
boto3

13 changes: 9 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,16 @@
"typing-extensions>=3.7.4.1",
"requests>=2.23.0",
"monty>=2021.3.12",
"emmet-core>=0.32.3",
"maggma>=0.47.4",
"custodian",
"mpcontribs-client",
"emmet-core>=0.35.1",
],
extras_require={
"all": [
"emmet-core[all]>=0.35.1"
"custodian",
"mpcontribs-client",
"boto3"
],
},
classifiers=[
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
Expand Down

0 comments on commit 7b9b3e1

Please sign in to comment.