Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean up dependencies #666

Merged
merged 14 commits into from
Sep 9, 2022
18 changes: 17 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,6 +132,7 @@ def __init__(
)

try:
from mpcontribs.client import Client
self.contribs = Client(api_key)
except Exception as error:
munrojm marked this conversation as resolved.
Show resolved Hide resolved
self.contribs = None
Expand Down Expand Up @@ -177,6 +177,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 install the pymatgen-analysis-alloys package.")
elif attr == "charge_density":
raise MPRestError("boto3 not installed. "
"To query charge density data install the boto3 package.")
else:
raise AttributeError(
f"{self.__class__.__name__!r} object has no attribute {attr!r}"
)

tschaume marked this conversation as resolved.
Show resolved Hide resolved
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 +952,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
21 changes: 18 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,20 @@
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:
import warnings
warnings.warn("Alloy addon package not installed. "
"To query alloy data install the pymatgen-analysis-alloys package.")
munrojm marked this conversation as resolved.
Show resolved Hide resolved
AlloysRester = None # type: ignore

try:
from .charge_density import ChargeDensityRester
except ImportError:
import warnings
warnings.warn("boto3 not installed. "
munrojm marked this conversation as resolved.
Show resolved Hide resolved
"To query charge density data install the boto3 package.")
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