Skip to content

Commit 680a569

Browse files
mypy
1 parent 08efe0e commit 680a569

7 files changed

Lines changed: 89 additions & 41 deletions

File tree

mp_api/client/contribs/_types.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,10 +269,13 @@ def from_file(cls, path: str | Path) -> Self:
269269
raise MPContribsClientError(f"use pathlib.Path or str (is: {typ}).")
270270

271271
kind = guess(str(path))
272-
supported = isinstance(kind, MPCC_SETTINGS.SUPPORTED_FILETYPES)
273272
content = path.read_bytes()
274273

275-
if not supported: # try to gzip text file
274+
if not (
275+
supported := any(
276+
isinstance(kind, ftyp) for ftyp in MPCC_SETTINGS.SUPPORTED_FILETYPES
277+
)
278+
): # try to gzip text file
276279
try:
277280
content = gzip.compress(content)
278281
except Exception:

mp_api/client/contribs/client.py

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@
5454
CONTRIBS_DOC_NAME,
5555
ContribData,
5656
ContribsProject,
57-
ContribSubmission,
5857
QueryResult,
5958
)
6059
from mp_api.client.contribs.settings import MPCC_SETTINGS
@@ -115,7 +114,7 @@ def validate_email(email_string: str):
115114

116115

117116
def validate_url(
118-
url_string: str, qualifying: Sequence[str, ...] = ("scheme", "netloc")
117+
url_string: str, qualifying: Sequence[str] = ("scheme", "netloc")
119118
) -> None:
120119
"""Verify an endpoint URL.
121120
@@ -722,7 +721,7 @@ def get_project(
722721
proj = self.projects.getProjectByName(pk=name, _fields=fields).result()
723722

724723
return (
725-
_convert_to_model(
724+
_convert_to_model( # type: ignore[return-value]
726725
[proj],
727726
ContribsProject,
728727
model_name=CONTRIBS_DOC_NAME,
@@ -759,17 +758,7 @@ def query_projects(
759758
query = query or {}
760759

761760
if self.project or "name" in query:
762-
proj = self.get_project(name=query.get("name"), fields=fields)
763-
return (
764-
_convert_to_model(
765-
[proj],
766-
ContribsProject,
767-
model_name=CONTRIBS_DOC_NAME,
768-
requested_fields=fields,
769-
)[0]
770-
if self.use_document_model
771-
else proj
772-
)
761+
return self.get_project(name=query.get("name"), fields=fields)
773762

774763
if term:
775764

@@ -798,7 +787,7 @@ def search_future(search_term):
798787

799788
if total_pages < 2:
800789
return (
801-
_convert_to_model(
790+
_convert_to_model( # type: ignore[return-value]
802791
ret["data"],
803792
ContribsProject,
804793
model_name=CONTRIBS_DOC_NAME,
@@ -830,7 +819,7 @@ def search_future(search_term):
830819
ret["data"].extend([resp["result"]["data"] for resp in responses.values()])
831820

832821
return (
833-
_convert_to_model(
822+
_convert_to_model( # type: ignore[return-value]
834823
ret["data"],
835824
ContribsProject,
836825
model_name=CONTRIBS_DOC_NAME,
@@ -858,7 +847,7 @@ def create_project(
858847
raise MPContribsClientError(f"Project with {query} already exists!")
859848

860849
project = ContribsProject(
861-
**{
850+
**{ # type: ignore[arg-type]
862851
"name": name,
863852
"title": title,
864853
"authors": authors,
@@ -961,7 +950,7 @@ def delete_project(self, name: str | None = None) -> None:
961950

962951
def get_contribution(
963952
self, cid: str, fields: list | None = None
964-
) -> MPCDict | ContribData:
953+
) -> ContribData | dict[str, Any]: # type: ignore[return-value]
965954
"""Retrieve a contribution.
966955
967956
Args:
@@ -979,7 +968,7 @@ def get_contribution(
979968
pk=cid, _fields=fields
980969
).result()
981970
return (
982-
_convert_to_model(
971+
_convert_to_model( # type: ignore[return-value]
983972
[contrib],
984973
ContribData,
985974
model_name=CONTRIBS_DOC_NAME,
@@ -1718,7 +1707,9 @@ def query_contributions(
17181707
ret["data"] = [ContribData(**doc) for doc in ret["data"]]
17191708

17201709
return (
1721-
_convert_to_model([ret], QueryResult, model_name=CONTRIBS_DOC_NAME)[0]
1710+
_convert_to_model( # type: ignore[return-value]
1711+
[ret], QueryResult, model_name=CONTRIBS_DOC_NAME
1712+
)[0]
17221713
if self.use_document_model
17231714
else ret
17241715
)
@@ -1884,7 +1875,7 @@ def _set_is_public(
18841875

18851876
def submit_contributions(
18861877
self,
1887-
contributions: list[dict | ContribSubmission],
1878+
contributions: list[dict],
18881879
ignore_dupes: bool = False,
18891880
timeout: int = -1,
18901881
skip_dupe_check: bool = False,

mp_api/client/contribs/logger.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,12 @@ def get_logger(name: str = "mp_api.client.contribs") -> CustomLoggerAdapter:
7272

7373

7474
class TqdmToLogger(StringIO):
75-
logger: logging.Logger = MPCC_LOGGER
76-
level: int = MPCC_SETTINGS.CLIENT_LOG_LEVEL
75+
logger: logging.LoggerAdapter = MPCC_LOGGER
76+
level: int | str = MPCC_SETTINGS.CLIENT_LOG_LEVEL
7777
buf: str = ""
7878

7979
def __init__(
80-
self, logger: logging.Logger = MPCC_LOGGER, level: int | None = None
80+
self, logger: logging.LoggerAdapter = MPCC_LOGGER, level: int | None = None
8181
) -> None:
8282
"""Start an instance of a TQDM logger.
8383

mp_api/client/contribs/schemas.py

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,19 @@ def _cast_pandas_dtype(dtype: type, assume_nullable: bool = True) -> type:
4040

4141

4242
def _get_unit(v: str) -> tuple[str, str | None]:
43+
"""Parse a physical variable name and its optional unit from a string value.
44+
45+
Ex:
46+
_get_unit("Temperature [K]") or _get_unit("Temperature (K)")
47+
will both return "Temperature", "K"
48+
49+
Args:
50+
v (str) : input column name to parse
51+
52+
Returns:
53+
str : the base name of the physical quantity
54+
str or None: its optional unit
55+
"""
4356
if (matched := re.search(r"\(([^)]+)\)|\[([^\]]+)\]", v)) is not None:
4457
try:
4558
unit_group = next(
@@ -54,13 +67,31 @@ def _get_unit(v: str) -> tuple[str, str | None]:
5467

5568

5669
def _to_camel_case(v: str) -> str:
70+
"""Convert a generic string to CamelCase.
71+
72+
Args:
73+
v (str) : input string
74+
75+
Returns:
76+
str : CamelCase string
77+
"""
5778
split_strs = [y for x in v.lower().split() for y in x.split("_")]
5879
return "".join(s[0].upper() + s[1:] for s in split_strs)
5980

6081

6182
def _get_pydantic_from_dataframe(
6283
df: pd.DataFrame,
6384
) -> tuple[type[BaseModel], dict[str, str]]:
85+
"""Dynamically create a pydantic BaseModel from a pandas DataFrame.
86+
87+
Args:
88+
df : pandas DataFrame to parse
89+
90+
Returns:
91+
BaseModel: the inferred schema of the pandas DataFrame
92+
dict of str to str: the remapped column names in an
93+
MP Contribs compatible format.
94+
"""
6495
columns_renamed = {}
6596
columns_to_unit = {}
6697
for col in df.columns:
@@ -85,28 +116,28 @@ def _get_pydantic_from_dataframe(
85116
if not all(pd.isna(df[col_name]))
86117
}
87118

88-
return (
89-
create_model(
90-
"InferredModel",
91-
**model_fields,
92-
),
93-
columns_renamed,
94-
)
119+
return create_model("InferredModel", **model_fields), columns_renamed
95120

96121

97122
class Reference(_DictLikeAccess):
123+
"""Define schema of URL reference."""
124+
98125
label: str
99126
url: str
100127

101128

102129
class Column(_DictLikeAccess):
130+
"""Define schema of MP Contribs column statistics."""
131+
103132
path: str
104133
min: float | None = float("nan")
105134
max: float | None = float("nan")
106135
unit: str = "NaN"
107136

108137

109138
class Stats(_DictLikeAccess):
139+
"""Define aggregated project statistics schema."""
140+
110141
columns: int = 0
111142
contributions: int = 0
112143
tables: int = 0
@@ -116,6 +147,8 @@ class Stats(_DictLikeAccess):
116147

117148

118149
class ContribsProject(_DictLikeAccess):
150+
"""Define schema for MP Contribs Project."""
151+
119152
name: str | None = None
120153
title: str | None = None
121154
authors: str | None = None
@@ -134,12 +167,14 @@ class ContribsProject(_DictLikeAccess):
134167

135168
@field_validator("other", mode="before")
136169
def flatten_other(cls, d: dict) -> dict[str, str | None]:
170+
"""Flatten column metadata."""
137171
if all(isinstance(v, str) for v in d.values()):
138172
return d
139173
return flatten_dict(d)
140174

141175
@field_serializer("other", mode="plain")
142176
def unflatten_other(self, v: dict[str, str]) -> dict[str, Any]:
177+
"""Unflatten column metadata."""
143178
return unflatten_dict(v)
144179

145180

mp_api/client/core/schemas.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,12 @@ def __str__(self) -> str:
3737
populated_fields = sorted(
3838
k for k in self.__class__.model_fields if getattr(self, k, None) is not None
3939
)
40-
annos = {k: self.__class__.model_fields[k].annotation for k in populated_fields}
41-
for k, v in annos.items():
42-
if hasattr(v, "__name__"):
43-
annos[k] = v.__name__
44-
# return f"{self.__class__.__name__}({len(populated_fields)} populated fields)"
40+
_annos: dict[str, Any] = {
41+
k: self.__class__.model_fields[k].annotation for k in populated_fields
42+
}
43+
annos: dict[str, str] = {
44+
k: getattr(v, "__name__", str(v)) for k, v in _annos.items()
45+
}
4546
return (
4647
f"{self.__class__.__name__}(\n"
4748
+ "\n".join(

mp_api/client/mprester.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ def __init__(
139139
local_dataset_cache: Target directory for downloading full datasets. Defaults
140140
to "mp_datasets" in the user's home directory
141141
force_renew: Option to overwrite existing local dataset
142-
**kwargs: access to legacy kwargs that may be in the process of being deprecated
142+
**kwargs: access to ContribsClient kwargs or (possibly-deprecated) legacy kwargs
143143
"""
144144
self.api_key = get_user_api_key(api_key=api_key)
145145

@@ -159,6 +159,14 @@ def __init__(
159159
self.local_dataset_cache = local_dataset_cache
160160
self.force_renew = force_renew
161161
self._contribs = None
162+
self._contribs_kwargs = {
163+
k: kwargs[k]
164+
for k in (
165+
"host",
166+
"project",
167+
)
168+
if k in kwargs
169+
}
162170

163171
self._deprecated_attributes = [
164172
"eos",
@@ -239,7 +247,16 @@ def __init__(
239247
)
240248

241249
@property
242-
def contribs(self):
250+
def contribs(self) -> None:
251+
"""Create an instance of the MP ContribsClient.
252+
253+
NB: The following args are taken from `MPRester`:
254+
- api_key
255+
- headers
256+
- session
257+
- use_document_model
258+
259+
"""
243260
if self._contribs is None:
244261
try:
245262
from mp_api.client.contribs.client import ContribsClient
@@ -249,6 +266,7 @@ def contribs(self):
249266
headers=self.headers,
250267
session=self.session,
251268
use_document_model=self.use_document_model,
269+
**self._contribs_kwargs,
252270
)
253271

254272
except ImportError:
@@ -265,7 +283,6 @@ def contribs(self):
265283
category=MPRestWarning,
266284
stacklevel=2,
267285
)
268-
269286
return self._contribs
270287

271288
def __enter__(self):

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ isort.required-imports = ["from __future__ import annotations"]
131131
[tool.mypy]
132132
namespace_packages = true
133133
ignore_missing_imports = true
134+
follow_untyped_imports = true
134135

135136
[tool.coverage.report]
136137
exclude_also = [

0 commit comments

Comments
 (0)