mpcontribs-client 5.10.4__py3-none-any.whl → 5.10.5rc0__py3-none-any.whl
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.
- mpcontribs/client/__init__.py +173 -167
- mpcontribs_client-5.10.5rc0.dist-info/METADATA +39 -0
- mpcontribs_client-5.10.5rc0.dist-info/RECORD +6 -0
- {mpcontribs_client-5.10.4.dist-info → mpcontribs_client-5.10.5rc0.dist-info}/WHEEL +1 -1
- mpcontribs_client-5.10.4.dist-info/METADATA +0 -88
- mpcontribs_client-5.10.4.dist-info/RECORD +0 -6
- {mpcontribs_client-5.10.4.dist-info → mpcontribs_client-5.10.5rc0.dist-info}/licenses/LICENSE +0 -0
- {mpcontribs_client-5.10.4.dist-info → mpcontribs_client-5.10.5rc0.dist-info}/top_level.txt +0 -0
mpcontribs/client/__init__.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
1
|
import io
|
|
2
|
+
import importlib.metadata
|
|
3
3
|
import sys
|
|
4
4
|
import os
|
|
5
5
|
import ujson
|
|
@@ -13,14 +13,12 @@ import itertools
|
|
|
13
13
|
import functools
|
|
14
14
|
import requests
|
|
15
15
|
import logging
|
|
16
|
-
import datetime
|
|
17
16
|
|
|
18
17
|
from inspect import getfullargspec
|
|
19
18
|
from math import isclose
|
|
20
|
-
from semantic_version import Version
|
|
21
19
|
from requests.exceptions import RequestException
|
|
22
20
|
from bson.objectid import ObjectId
|
|
23
|
-
from typing import
|
|
21
|
+
from typing import Type
|
|
24
22
|
from tqdm.auto import tqdm
|
|
25
23
|
from hashlib import md5
|
|
26
24
|
from pathlib import Path
|
|
@@ -59,7 +57,12 @@ from tempfile import gettempdir
|
|
|
59
57
|
from plotly.express._chart_types import line as line_chart
|
|
60
58
|
from cachetools import cached, LRUCache
|
|
61
59
|
from cachetools.keys import hashkey
|
|
62
|
-
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
__version__ = importlib.metadata.version("mpcontribs-client")
|
|
63
|
+
except Exception:
|
|
64
|
+
# package is not installed
|
|
65
|
+
pass
|
|
63
66
|
|
|
64
67
|
RETRIES = 3
|
|
65
68
|
MAX_WORKERS = 3
|
|
@@ -88,6 +91,7 @@ VALID_URLS |= {f"http://localhost.{n}-api.materialsproject.org" for n in SUBDOMA
|
|
|
88
91
|
SUPPORTED_FILETYPES = (Gz, Jpeg, Png, Gif, Tiff)
|
|
89
92
|
SUPPORTED_MIMES = [t().mime for t in SUPPORTED_FILETYPES]
|
|
90
93
|
DEFAULT_DOWNLOAD_DIR = Path.home() / "mpcontribs-downloads"
|
|
94
|
+
VALID_API_KEY_ALIASES = ["MPCONTRIBS_API_KEY", "MP_API_KEY", "PMG_MAPI_KEY"]
|
|
91
95
|
|
|
92
96
|
j2h = Json2Html()
|
|
93
97
|
pd.options.plotting.backend = "plotly"
|
|
@@ -126,7 +130,7 @@ _ipython = sys.modules["IPython"].get_ipython()
|
|
|
126
130
|
class LogFilter(logging.Filter):
|
|
127
131
|
def __init__(self, level, *args, **kwargs):
|
|
128
132
|
self.level = level
|
|
129
|
-
super(
|
|
133
|
+
super().__init__(*args, **kwargs)
|
|
130
134
|
|
|
131
135
|
def filter(self, record):
|
|
132
136
|
return record.levelno < self.level
|
|
@@ -144,7 +148,7 @@ class TqdmToLogger(io.StringIO):
|
|
|
144
148
|
buf = ""
|
|
145
149
|
|
|
146
150
|
def __init__(self, logger, level=None):
|
|
147
|
-
super(
|
|
151
|
+
super().__init__()
|
|
148
152
|
self.logger = logger
|
|
149
153
|
self.level = level or logging.INFO
|
|
150
154
|
|
|
@@ -177,7 +181,7 @@ class MPContribsClientError(ValueError):
|
|
|
177
181
|
"""custom error for mpcontribs-client"""
|
|
178
182
|
|
|
179
183
|
|
|
180
|
-
def get_md5(d):
|
|
184
|
+
def get_md5(d) -> str:
|
|
181
185
|
s = ujson.dumps(d, sort_keys=True).encode("utf-8")
|
|
182
186
|
return md5(s).hexdigest()
|
|
183
187
|
|
|
@@ -478,7 +482,7 @@ class Attachment(dict):
|
|
|
478
482
|
|
|
479
483
|
return unpacked
|
|
480
484
|
|
|
481
|
-
def write(self, outdir:
|
|
485
|
+
def write(self, outdir: str | Path | None = None) -> Path:
|
|
482
486
|
"""Write attachment to file using its name
|
|
483
487
|
|
|
484
488
|
Args:
|
|
@@ -490,7 +494,7 @@ class Attachment(dict):
|
|
|
490
494
|
path.write_bytes(content)
|
|
491
495
|
return path
|
|
492
496
|
|
|
493
|
-
def display(self, outdir:
|
|
497
|
+
def display(self, outdir: str | Path | None = None):
|
|
494
498
|
"""Display Image/FileLink for attachment if in IPython/Jupyter
|
|
495
499
|
|
|
496
500
|
Args:
|
|
@@ -519,7 +523,7 @@ class Attachment(dict):
|
|
|
519
523
|
return self["name"]
|
|
520
524
|
|
|
521
525
|
@classmethod
|
|
522
|
-
def from_data(cls, data:
|
|
526
|
+
def from_data(cls, data: list | dict, name: str = "attachment"):
|
|
523
527
|
"""Construct attachment from data dict or list
|
|
524
528
|
|
|
525
529
|
Args:
|
|
@@ -539,7 +543,7 @@ class Attachment(dict):
|
|
|
539
543
|
)
|
|
540
544
|
|
|
541
545
|
@classmethod
|
|
542
|
-
def from_file(cls, path:
|
|
546
|
+
def from_file(cls, path: str | Path):
|
|
543
547
|
"""Construct attachment from file
|
|
544
548
|
|
|
545
549
|
Args:
|
|
@@ -616,7 +620,7 @@ class Attachments(list):
|
|
|
616
620
|
return attachments
|
|
617
621
|
|
|
618
622
|
@classmethod
|
|
619
|
-
def from_data(cls, data:
|
|
623
|
+
def from_data(cls, data: list | dict, prefix: str = "attachment"):
|
|
620
624
|
"""Construct list of attachments from data dict or list
|
|
621
625
|
|
|
622
626
|
Args:
|
|
@@ -830,32 +834,24 @@ def _expand_params(protocol, host, version, projects_json, apikey=None):
|
|
|
830
834
|
def _version(url):
|
|
831
835
|
retries, max_retries = 0, 3
|
|
832
836
|
protocol = urlparse(url).scheme
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
)
|
|
843
|
-
else:
|
|
844
|
-
while retries < max_retries:
|
|
845
|
-
try:
|
|
846
|
-
r = requests.get(f"{url}/healthcheck", timeout=5)
|
|
847
|
-
if r.status_code in {200, 403}:
|
|
848
|
-
return r.json().get("version")
|
|
849
|
-
else:
|
|
850
|
-
retries += 1
|
|
851
|
-
logger.warning(
|
|
852
|
-
f"Healthcheck for {url} failed ({r.status_code})! Wait 30s."
|
|
853
|
-
)
|
|
854
|
-
time.sleep(30)
|
|
855
|
-
except RequestException as ex:
|
|
837
|
+
if "pytest" in sys.modules and protocol == "http":
|
|
838
|
+
return __version__
|
|
839
|
+
|
|
840
|
+
while retries < max_retries:
|
|
841
|
+
try:
|
|
842
|
+
r = requests.get(f"{url}/healthcheck", timeout=5)
|
|
843
|
+
if r.status_code in {200, 403}:
|
|
844
|
+
return r.json().get("version")
|
|
845
|
+
else:
|
|
856
846
|
retries += 1
|
|
857
|
-
logger.warning(
|
|
847
|
+
logger.warning(
|
|
848
|
+
f"Healthcheck for {url} failed ({r.status_code})! Wait 30s."
|
|
849
|
+
)
|
|
858
850
|
time.sleep(30)
|
|
851
|
+
except RequestException as ex:
|
|
852
|
+
retries += 1
|
|
853
|
+
logger.warning(f"Could not connect to {url} ({ex})! Wait 30s.")
|
|
854
|
+
time.sleep(30)
|
|
859
855
|
|
|
860
856
|
|
|
861
857
|
class Client(SwaggerClient):
|
|
@@ -870,11 +866,11 @@ class Client(SwaggerClient):
|
|
|
870
866
|
|
|
871
867
|
def __init__(
|
|
872
868
|
self,
|
|
873
|
-
apikey:
|
|
874
|
-
headers:
|
|
875
|
-
host:
|
|
876
|
-
project:
|
|
877
|
-
session:
|
|
869
|
+
apikey: str | None = None,
|
|
870
|
+
headers: dict | None = None,
|
|
871
|
+
host: str | None = None,
|
|
872
|
+
project: str | None = None,
|
|
873
|
+
session: requests.Session | None = None,
|
|
878
874
|
):
|
|
879
875
|
"""Initialize the client - only reloads API spec from server as needed
|
|
880
876
|
|
|
@@ -892,7 +888,17 @@ class Client(SwaggerClient):
|
|
|
892
888
|
host = os.environ.get("MPCONTRIBS_API_HOST", DEFAULT_HOST)
|
|
893
889
|
|
|
894
890
|
if not apikey:
|
|
895
|
-
|
|
891
|
+
try:
|
|
892
|
+
apikey = next(
|
|
893
|
+
os.environ.get(kalias)
|
|
894
|
+
for kalias in VALID_API_KEY_ALIASES
|
|
895
|
+
if kalias is not None
|
|
896
|
+
)
|
|
897
|
+
except StopIteration:
|
|
898
|
+
from pymatgen.core import SETTINGS
|
|
899
|
+
|
|
900
|
+
apikey = SETTINGS.get("PMG_MAPI_KEY")
|
|
901
|
+
|
|
896
902
|
if apikey and len(apikey) != 32:
|
|
897
903
|
raise MPContribsClientError(f"Invalid API key: {apikey}")
|
|
898
904
|
|
|
@@ -925,7 +931,7 @@ class Client(SwaggerClient):
|
|
|
925
931
|
def __enter__(self):
|
|
926
932
|
return self
|
|
927
933
|
|
|
928
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
934
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
929
935
|
return None
|
|
930
936
|
|
|
931
937
|
@property
|
|
@@ -934,17 +940,18 @@ class Client(SwaggerClient):
|
|
|
934
940
|
self.protocol, self.host, self.headers_json, self.project, self.version
|
|
935
941
|
)
|
|
936
942
|
|
|
937
|
-
def __dir__(self):
|
|
943
|
+
def __dir__(self) -> set[str]:
|
|
938
944
|
members = set(self.swagger_spec.resources.keys())
|
|
939
|
-
members |=
|
|
940
|
-
members |=
|
|
945
|
+
members |= {k for k in self.__dict__.keys() if not k.startswith("_")}
|
|
946
|
+
members |= {k for k in dir(self.__class__) if not k.startswith("_")}
|
|
941
947
|
return members
|
|
942
948
|
|
|
943
949
|
def _reinit(self):
|
|
944
950
|
_load.cache_clear()
|
|
945
951
|
super().__init__(self.cached_swagger_spec)
|
|
946
952
|
|
|
947
|
-
def _is_valid_payload(self, model: str, data: dict):
|
|
953
|
+
def _is_valid_payload(self, model: str, data: dict) -> None:
|
|
954
|
+
"""Raise an error if a payload is invalid."""
|
|
948
955
|
model_spec = deepcopy(self.get_model(f"{model}sSchema")._model_spec)
|
|
949
956
|
model_spec.pop("required")
|
|
950
957
|
model_spec["additionalProperties"] = False
|
|
@@ -952,17 +959,20 @@ class Client(SwaggerClient):
|
|
|
952
959
|
try:
|
|
953
960
|
validate_object(self.swagger_spec, model_spec, data)
|
|
954
961
|
except ValidationError as ex:
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
return True, None
|
|
958
|
-
|
|
959
|
-
def _is_serializable_dict(self, dct):
|
|
960
|
-
for k, v in flatten(dct, reducer="dot").items():
|
|
961
|
-
if v is not None and not isinstance(v, (str, int, float)):
|
|
962
|
-
error = f"Value {v} of {type(v)} for key {k} not supported."
|
|
963
|
-
return False, error
|
|
962
|
+
raise MPContribsClientError(str(ex))
|
|
964
963
|
|
|
965
|
-
|
|
964
|
+
def _is_serializable_dict(self, dct: dict) -> None:
|
|
965
|
+
"""Raise an error if an input dict is not JSON serializable."""
|
|
966
|
+
try:
|
|
967
|
+
raise MPContribsClientError(
|
|
968
|
+
next(
|
|
969
|
+
f"Value {v} of {type(v)} for key {k} not supported."
|
|
970
|
+
for k, v in flatten(dct, reducer="dot").items()
|
|
971
|
+
if v is not None and not isinstance(v, (str, int, float))
|
|
972
|
+
)
|
|
973
|
+
)
|
|
974
|
+
except StopIteration:
|
|
975
|
+
pass
|
|
966
976
|
|
|
967
977
|
def _get_per_page_default_max(
|
|
968
978
|
self, op: str = "query", resource: str = "contributions"
|
|
@@ -1030,14 +1040,10 @@ class Client(SwaggerClient):
|
|
|
1030
1040
|
|
|
1031
1041
|
for q in queries:
|
|
1032
1042
|
# copy over missing parameters
|
|
1033
|
-
for k, v in query.items()
|
|
1034
|
-
if k not in q:
|
|
1035
|
-
q[k] = v
|
|
1043
|
+
q.update({k: v for k, v in query.items() if k not in q})
|
|
1036
1044
|
|
|
1037
1045
|
# comma-separated lists
|
|
1038
|
-
for k, v in q.items()
|
|
1039
|
-
if isinstance(v, list):
|
|
1040
|
-
q[k] = ",".join(v)
|
|
1046
|
+
q.update({k: ",".join(v) for k, v in q.items() if isinstance(v, list)})
|
|
1041
1047
|
|
|
1042
1048
|
return queries
|
|
1043
1049
|
|
|
@@ -1047,7 +1053,7 @@ class Client(SwaggerClient):
|
|
|
1047
1053
|
params: dict,
|
|
1048
1054
|
rel_url: str = "contributions",
|
|
1049
1055
|
op: str = "query",
|
|
1050
|
-
data:
|
|
1056
|
+
data: dict | None = None,
|
|
1051
1057
|
):
|
|
1052
1058
|
rname = rel_url.split("/", 1)[0]
|
|
1053
1059
|
resource = self.swagger_spec.resources[rname]
|
|
@@ -1066,7 +1072,7 @@ class Client(SwaggerClient):
|
|
|
1066
1072
|
|
|
1067
1073
|
def available_query_params(
|
|
1068
1074
|
self,
|
|
1069
|
-
startswith:
|
|
1075
|
+
startswith: tuple | None = None,
|
|
1070
1076
|
resource: str = "contributions",
|
|
1071
1077
|
) -> list:
|
|
1072
1078
|
resources = self.swagger_spec.resources
|
|
@@ -1083,9 +1089,7 @@ class Client(SwaggerClient):
|
|
|
1083
1089
|
|
|
1084
1090
|
return [param for param in params if param.startswith(startswith)]
|
|
1085
1091
|
|
|
1086
|
-
def get_project(
|
|
1087
|
-
self, name: Optional[str] = None, fields: Optional[list] = None
|
|
1088
|
-
) -> Dict:
|
|
1092
|
+
def get_project(self, name: str | None = None, fields: list | None = None) -> Dict:
|
|
1089
1093
|
"""Retrieve a project entry
|
|
1090
1094
|
|
|
1091
1095
|
Args:
|
|
@@ -1103,10 +1107,10 @@ class Client(SwaggerClient):
|
|
|
1103
1107
|
|
|
1104
1108
|
def query_projects(
|
|
1105
1109
|
self,
|
|
1106
|
-
query:
|
|
1107
|
-
term:
|
|
1108
|
-
fields:
|
|
1109
|
-
sort:
|
|
1110
|
+
query: dict | None = None,
|
|
1111
|
+
term: str | None = None,
|
|
1112
|
+
fields: list | None = None,
|
|
1113
|
+
sort: str | None = None,
|
|
1110
1114
|
timeout: int = -1,
|
|
1111
1115
|
) -> list[dict]:
|
|
1112
1116
|
"""Query projects by query and/or term (Atlas Search)
|
|
@@ -1157,9 +1161,13 @@ class Client(SwaggerClient):
|
|
|
1157
1161
|
if total_pages < 2:
|
|
1158
1162
|
return ret["data"]
|
|
1159
1163
|
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1164
|
+
query.update(
|
|
1165
|
+
{
|
|
1166
|
+
field: ",".join(query[field])
|
|
1167
|
+
for field in ["name__in", "_fields"]
|
|
1168
|
+
if field in query
|
|
1169
|
+
}
|
|
1170
|
+
)
|
|
1163
1171
|
|
|
1164
1172
|
queries = []
|
|
1165
1173
|
|
|
@@ -1172,8 +1180,7 @@ class Client(SwaggerClient):
|
|
|
1172
1180
|
]
|
|
1173
1181
|
responses = _run_futures(futures, total=total_count, timeout=timeout)
|
|
1174
1182
|
|
|
1175
|
-
for resp in responses.values()
|
|
1176
|
-
ret["data"] += resp["result"]["data"]
|
|
1183
|
+
ret["data"].extend([resp["result"]["data"] for resp in responses.values()])
|
|
1177
1184
|
|
|
1178
1185
|
return ret["data"]
|
|
1179
1186
|
|
|
@@ -1210,7 +1217,7 @@ class Client(SwaggerClient):
|
|
|
1210
1217
|
else:
|
|
1211
1218
|
raise MPContribsClientError(resp)
|
|
1212
1219
|
|
|
1213
|
-
def update_project(self, update: dict, name:
|
|
1220
|
+
def update_project(self, update: dict, name: str | None = None):
|
|
1214
1221
|
"""Update project info
|
|
1215
1222
|
|
|
1216
1223
|
Args:
|
|
@@ -1268,15 +1275,12 @@ class Client(SwaggerClient):
|
|
|
1268
1275
|
logger.warning("nothing to update")
|
|
1269
1276
|
return
|
|
1270
1277
|
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
raise MPContribsClientError(resp)
|
|
1276
|
-
else:
|
|
1277
|
-
raise MPContribsClientError(error)
|
|
1278
|
+
self._is_valid_payload("Project", payload)
|
|
1279
|
+
resp = self.projects.updateProjectByName(pk=name, project=payload).result()
|
|
1280
|
+
if not resp.get("count", 0):
|
|
1281
|
+
raise MPContribsClientError(resp)
|
|
1278
1282
|
|
|
1279
|
-
def delete_project(self, name:
|
|
1283
|
+
def delete_project(self, name: str | None = None):
|
|
1280
1284
|
"""Delete a project
|
|
1281
1285
|
|
|
1282
1286
|
Args:
|
|
@@ -1295,7 +1299,7 @@ class Client(SwaggerClient):
|
|
|
1295
1299
|
if resp and "error" in resp:
|
|
1296
1300
|
raise MPContribsClientError(resp["error"])
|
|
1297
1301
|
|
|
1298
|
-
def get_contribution(self, cid: str, fields:
|
|
1302
|
+
def get_contribution(self, cid: str, fields: list | None = None) -> Dict:
|
|
1299
1303
|
"""Retrieve a contribution
|
|
1300
1304
|
|
|
1301
1305
|
Args:
|
|
@@ -1408,7 +1412,7 @@ class Client(SwaggerClient):
|
|
|
1408
1412
|
)
|
|
1409
1413
|
|
|
1410
1414
|
def init_columns(
|
|
1411
|
-
self, columns:
|
|
1415
|
+
self, columns: dict | None = None, name: str | None = None
|
|
1412
1416
|
) -> dict:
|
|
1413
1417
|
"""initialize columns for a project to set their order and desired units
|
|
1414
1418
|
|
|
@@ -1532,20 +1536,23 @@ class Client(SwaggerClient):
|
|
|
1532
1536
|
new_unit = new_column.get("unit", "NaN")
|
|
1533
1537
|
existing_unit = existing_column.get("unit")
|
|
1534
1538
|
if existing_unit != new_unit:
|
|
1535
|
-
|
|
1536
|
-
|
|
1539
|
+
if existing_unit == "NaN" and new_unit == "":
|
|
1540
|
+
factor = 1
|
|
1541
|
+
else:
|
|
1542
|
+
conv_args = []
|
|
1543
|
+
for u in [existing_unit, new_unit]:
|
|
1544
|
+
try:
|
|
1545
|
+
conv_args.append(ureg.Unit(u))
|
|
1546
|
+
except ValueError:
|
|
1547
|
+
raise MPContribsClientError(
|
|
1548
|
+
f"Can't convert {existing_unit} to {new_unit} for {path}"
|
|
1549
|
+
)
|
|
1537
1550
|
try:
|
|
1538
|
-
|
|
1539
|
-
except
|
|
1551
|
+
factor = ureg.convert(1, *conv_args)
|
|
1552
|
+
except DimensionalityError:
|
|
1540
1553
|
raise MPContribsClientError(
|
|
1541
1554
|
f"Can't convert {existing_unit} to {new_unit} for {path}"
|
|
1542
1555
|
)
|
|
1543
|
-
try:
|
|
1544
|
-
factor = ureg.convert(1, *conv_args)
|
|
1545
|
-
except DimensionalityError:
|
|
1546
|
-
raise MPContribsClientError(
|
|
1547
|
-
f"Can't convert {existing_unit} to {new_unit} for {path}"
|
|
1548
|
-
)
|
|
1549
1556
|
|
|
1550
1557
|
if not isclose(factor, 1):
|
|
1551
1558
|
logger.info(
|
|
@@ -1560,13 +1567,11 @@ class Client(SwaggerClient):
|
|
|
1560
1567
|
new_columns.append(new_column)
|
|
1561
1568
|
|
|
1562
1569
|
payload = {"columns": new_columns}
|
|
1563
|
-
|
|
1564
|
-
if not valid:
|
|
1565
|
-
raise MPContribsClientError(error)
|
|
1570
|
+
self._is_valid_payload("Project", payload)
|
|
1566
1571
|
|
|
1567
1572
|
return self.projects.updateProjectByName(pk=name, project=payload).result()
|
|
1568
1573
|
|
|
1569
|
-
def delete_contributions(self, query:
|
|
1574
|
+
def delete_contributions(self, query: dict | None = None, timeout: int = -1):
|
|
1570
1575
|
"""Remove all contributions for a query
|
|
1571
1576
|
|
|
1572
1577
|
Args:
|
|
@@ -1612,7 +1617,7 @@ class Client(SwaggerClient):
|
|
|
1612
1617
|
|
|
1613
1618
|
def get_totals(
|
|
1614
1619
|
self,
|
|
1615
|
-
query:
|
|
1620
|
+
query: dict | None = None,
|
|
1616
1621
|
timeout: int = -1,
|
|
1617
1622
|
resource: str = "contributions",
|
|
1618
1623
|
op: str = "query",
|
|
@@ -1641,23 +1646,23 @@ class Client(SwaggerClient):
|
|
|
1641
1646
|
query = {k: v for k, v in query.items() if k not in skip_keys}
|
|
1642
1647
|
query["_fields"] = [] # only need totals -> explicitly request no fields
|
|
1643
1648
|
queries = self._split_query(query, resource=resource, op=op) # don't paginate
|
|
1644
|
-
result = {"total_count": 0, "total_pages": 0}
|
|
1645
1649
|
futures = [
|
|
1646
1650
|
self._get_future(i, q, rel_url=resource) for i, q in enumerate(queries)
|
|
1647
1651
|
]
|
|
1648
1652
|
responses = _run_futures(futures, timeout=timeout, desc="Totals")
|
|
1649
1653
|
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1654
|
+
result = {
|
|
1655
|
+
k: sum(resp.get("result", {}).get(k, 0) for resp in responses.values())
|
|
1656
|
+
for k in ("total_count", "total_pages")
|
|
1657
|
+
}
|
|
1653
1658
|
|
|
1654
1659
|
return result["total_count"], result["total_pages"]
|
|
1655
1660
|
|
|
1656
|
-
def count(self, query:
|
|
1661
|
+
def count(self, query: dict | None = None) -> int:
|
|
1657
1662
|
"""shortcut for get_totals()"""
|
|
1658
1663
|
return self.get_totals(query=query)[0]
|
|
1659
1664
|
|
|
1660
|
-
def get_unique_identifiers_flags(self, query:
|
|
1665
|
+
def get_unique_identifiers_flags(self, query: dict | None = None) -> dict:
|
|
1661
1666
|
"""Retrieve values for `unique_identifiers` flags.
|
|
1662
1667
|
|
|
1663
1668
|
See `client.available_query_params(resource="projects")` for available query parameters.
|
|
@@ -1677,10 +1682,10 @@ class Client(SwaggerClient):
|
|
|
1677
1682
|
|
|
1678
1683
|
def get_all_ids(
|
|
1679
1684
|
self,
|
|
1680
|
-
query:
|
|
1681
|
-
include:
|
|
1685
|
+
query: dict | None = None,
|
|
1686
|
+
include: list[str] | None = None,
|
|
1682
1687
|
timeout: int = -1,
|
|
1683
|
-
data_id_fields:
|
|
1688
|
+
data_id_fields: dict | None = None,
|
|
1684
1689
|
fmt: str = "sets",
|
|
1685
1690
|
op: str = "query",
|
|
1686
1691
|
) -> dict:
|
|
@@ -1731,7 +1736,7 @@ class Client(SwaggerClient):
|
|
|
1731
1736
|
}, ...}
|
|
1732
1737
|
"""
|
|
1733
1738
|
include = include or []
|
|
1734
|
-
components =
|
|
1739
|
+
components = {x for x in include if x in COMPONENTS}
|
|
1735
1740
|
if include and not components:
|
|
1736
1741
|
raise MPContribsClientError(f"`include` must be subset of {COMPONENTS}!")
|
|
1737
1742
|
|
|
@@ -1745,9 +1750,13 @@ class Client(SwaggerClient):
|
|
|
1745
1750
|
|
|
1746
1751
|
unique_identifiers = self.get_unique_identifiers_flags()
|
|
1747
1752
|
data_id_fields = data_id_fields or {}
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1753
|
+
data_id_fields.update(
|
|
1754
|
+
{
|
|
1755
|
+
k: v
|
|
1756
|
+
for k, v in data_id_fields.items()
|
|
1757
|
+
if k in unique_identifiers and isinstance(v, str)
|
|
1758
|
+
}
|
|
1759
|
+
)
|
|
1751
1760
|
|
|
1752
1761
|
ret = {}
|
|
1753
1762
|
query = query or {}
|
|
@@ -1810,34 +1819,40 @@ class Client(SwaggerClient):
|
|
|
1810
1819
|
if data_id_field and data_id_field_val:
|
|
1811
1820
|
ret[project][identifier][data_id_field] = data_id_field_val
|
|
1812
1821
|
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1822
|
+
ret[project][identifier].update(
|
|
1823
|
+
{
|
|
1824
|
+
component: {
|
|
1816
1825
|
d["name"]: {"id": d["id"], "md5": d["md5"]}
|
|
1817
1826
|
for d in contrib[component]
|
|
1818
1827
|
}
|
|
1828
|
+
for component in components
|
|
1829
|
+
if component in contrib
|
|
1830
|
+
}
|
|
1831
|
+
)
|
|
1819
1832
|
|
|
1820
1833
|
elif data_id_field and data_id_field_val:
|
|
1821
1834
|
ret[project][identifier] = {
|
|
1822
1835
|
data_id_field_val: {"id": contrib["id"]}
|
|
1823
1836
|
}
|
|
1824
1837
|
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
component
|
|
1829
|
-
] = {
|
|
1838
|
+
ret[project][identifier][data_id_field_val].update(
|
|
1839
|
+
{
|
|
1840
|
+
component: {
|
|
1830
1841
|
d["name"]: {"id": d["id"], "md5": d["md5"]}
|
|
1831
1842
|
for d in contrib[component]
|
|
1832
1843
|
}
|
|
1844
|
+
for component in components
|
|
1845
|
+
if component in contrib
|
|
1846
|
+
}
|
|
1847
|
+
)
|
|
1833
1848
|
|
|
1834
1849
|
return ret
|
|
1835
1850
|
|
|
1836
1851
|
def query_contributions(
|
|
1837
1852
|
self,
|
|
1838
|
-
query:
|
|
1839
|
-
fields:
|
|
1840
|
-
sort:
|
|
1853
|
+
query: dict | None = None,
|
|
1854
|
+
fields: list | None = None,
|
|
1855
|
+
sort: str | None = None,
|
|
1841
1856
|
paginate: bool = False,
|
|
1842
1857
|
timeout: int = -1,
|
|
1843
1858
|
) -> dict:
|
|
@@ -1861,12 +1876,11 @@ class Client(SwaggerClient):
|
|
|
1861
1876
|
query["project"] = self.project
|
|
1862
1877
|
|
|
1863
1878
|
if paginate:
|
|
1864
|
-
cids = [
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
cids.extend(cids_project)
|
|
1879
|
+
cids = [
|
|
1880
|
+
idx
|
|
1881
|
+
for v in self.get_all_ids(query).values()
|
|
1882
|
+
for idx in (v.get("ids") or [])
|
|
1883
|
+
]
|
|
1870
1884
|
|
|
1871
1885
|
if not cids:
|
|
1872
1886
|
raise MPContribsClientError("No contributions match the query.")
|
|
@@ -1891,7 +1905,7 @@ class Client(SwaggerClient):
|
|
|
1891
1905
|
return ret
|
|
1892
1906
|
|
|
1893
1907
|
def update_contributions(
|
|
1894
|
-
self, data: dict, query:
|
|
1908
|
+
self, data: dict, query: dict | None = None, timeout: int = -1
|
|
1895
1909
|
) -> dict:
|
|
1896
1910
|
"""Apply the same update to all contributions in a project (matching query)
|
|
1897
1911
|
|
|
@@ -1906,14 +1920,10 @@ class Client(SwaggerClient):
|
|
|
1906
1920
|
raise MPContribsClientError("Nothing to update.")
|
|
1907
1921
|
|
|
1908
1922
|
tic = time.perf_counter()
|
|
1909
|
-
|
|
1910
|
-
if not valid:
|
|
1911
|
-
raise MPContribsClientError(error)
|
|
1923
|
+
self._is_valid_payload("Contribution", data)
|
|
1912
1924
|
|
|
1913
1925
|
if "data" in data:
|
|
1914
|
-
|
|
1915
|
-
if not serializable:
|
|
1916
|
-
raise MPContribsClientError(error)
|
|
1926
|
+
self._is_serializable_dict(data["data"])
|
|
1917
1927
|
|
|
1918
1928
|
query = query or {}
|
|
1919
1929
|
|
|
@@ -1939,7 +1949,7 @@ class Client(SwaggerClient):
|
|
|
1939
1949
|
|
|
1940
1950
|
# get current list of data columns to decide if swagger reload is needed
|
|
1941
1951
|
resp = self.projects.getProjectByName(pk=name, _fields=["columns"]).result()
|
|
1942
|
-
old_paths =
|
|
1952
|
+
old_paths = {c["path"] for c in resp["columns"]}
|
|
1943
1953
|
|
|
1944
1954
|
total = len(cids)
|
|
1945
1955
|
cids_query = {"id__in": cids}
|
|
@@ -1954,7 +1964,7 @@ class Client(SwaggerClient):
|
|
|
1954
1964
|
|
|
1955
1965
|
if updated:
|
|
1956
1966
|
resp = self.projects.getProjectByName(pk=name, _fields=["columns"]).result()
|
|
1957
|
-
new_paths =
|
|
1967
|
+
new_paths = {c["path"] for c in resp["columns"]}
|
|
1958
1968
|
|
|
1959
1969
|
if new_paths != old_paths:
|
|
1960
1970
|
self.init_columns(name=name)
|
|
@@ -1964,7 +1974,7 @@ class Client(SwaggerClient):
|
|
|
1964
1974
|
return {"updated": updated, "total": total, "seconds_elapsed": toc - tic}
|
|
1965
1975
|
|
|
1966
1976
|
def make_public(
|
|
1967
|
-
self, query:
|
|
1977
|
+
self, query: dict | None = None, recursive: bool = False, timeout: int = -1
|
|
1968
1978
|
) -> dict:
|
|
1969
1979
|
"""Publish a project and optionally its contributions
|
|
1970
1980
|
|
|
@@ -1977,7 +1987,7 @@ class Client(SwaggerClient):
|
|
|
1977
1987
|
)
|
|
1978
1988
|
|
|
1979
1989
|
def make_private(
|
|
1980
|
-
self, query:
|
|
1990
|
+
self, query: dict | None = None, recursive: bool = False, timeout: int = -1
|
|
1981
1991
|
) -> dict:
|
|
1982
1992
|
"""Make a project and optionally its contributions private
|
|
1983
1993
|
|
|
@@ -1992,7 +2002,7 @@ class Client(SwaggerClient):
|
|
|
1992
2002
|
def _set_is_public(
|
|
1993
2003
|
self,
|
|
1994
2004
|
is_public: bool,
|
|
1995
|
-
query:
|
|
2005
|
+
query: dict | None = None,
|
|
1996
2006
|
recursive: bool = False,
|
|
1997
2007
|
timeout: int = -1,
|
|
1998
2008
|
) -> dict:
|
|
@@ -2124,9 +2134,13 @@ class Client(SwaggerClient):
|
|
|
2124
2134
|
resp = self.get_all_ids(dict(id__in=collect_ids), timeout=timeout)
|
|
2125
2135
|
project_names |= set(resp.keys())
|
|
2126
2136
|
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2137
|
+
id2project.update(
|
|
2138
|
+
{
|
|
2139
|
+
cid: project_name
|
|
2140
|
+
for project_name, values in resp.items()
|
|
2141
|
+
for cid in values["ids"]
|
|
2142
|
+
}
|
|
2143
|
+
)
|
|
2130
2144
|
|
|
2131
2145
|
existing = defaultdict(dict)
|
|
2132
2146
|
unique_identifiers = defaultdict(dict)
|
|
@@ -2160,9 +2174,7 @@ class Client(SwaggerClient):
|
|
|
2160
2174
|
for contrib in tqdm(contributions, desc="Prepare"):
|
|
2161
2175
|
if "data" in contrib:
|
|
2162
2176
|
contrib["data"] = unflatten(contrib["data"], splitter="dot")
|
|
2163
|
-
|
|
2164
|
-
if not serializable:
|
|
2165
|
-
raise MPContribsClientError(error)
|
|
2177
|
+
self._is_serializable_dict(contrib["data"])
|
|
2166
2178
|
|
|
2167
2179
|
update = "id" in contrib
|
|
2168
2180
|
project_name = id2project[contrib["id"]] if update else contrib["project"]
|
|
@@ -2282,13 +2294,7 @@ class Client(SwaggerClient):
|
|
|
2282
2294
|
digests[project_name][component].add(digest)
|
|
2283
2295
|
contribs[project_name][-1][component].append(dct)
|
|
2284
2296
|
|
|
2285
|
-
|
|
2286
|
-
"Contribution", contribs[project_name][-1]
|
|
2287
|
-
)
|
|
2288
|
-
if not valid:
|
|
2289
|
-
raise MPContribsClientError(
|
|
2290
|
-
f"{contrib['identifier']} invalid: {error}!"
|
|
2291
|
-
)
|
|
2297
|
+
self._is_valid_payload("Contribution", contribs[project_name][-1])
|
|
2292
2298
|
|
|
2293
2299
|
# submit contributions
|
|
2294
2300
|
if contribs:
|
|
@@ -2425,10 +2431,10 @@ class Client(SwaggerClient):
|
|
|
2425
2431
|
|
|
2426
2432
|
def download_contributions(
|
|
2427
2433
|
self,
|
|
2428
|
-
query:
|
|
2429
|
-
outdir:
|
|
2434
|
+
query: dict | None = None,
|
|
2435
|
+
outdir: str | Path = DEFAULT_DOWNLOAD_DIR,
|
|
2430
2436
|
overwrite: bool = False,
|
|
2431
|
-
include:
|
|
2437
|
+
include: list[str] | None = None,
|
|
2432
2438
|
timeout: int = -1,
|
|
2433
2439
|
) -> list:
|
|
2434
2440
|
"""Download a list of contributions as .json.gz file(s)
|
|
@@ -2448,7 +2454,7 @@ class Client(SwaggerClient):
|
|
|
2448
2454
|
include = include or []
|
|
2449
2455
|
outdir = Path(outdir) or Path(".")
|
|
2450
2456
|
outdir.mkdir(parents=True, exist_ok=True)
|
|
2451
|
-
components =
|
|
2457
|
+
components = {x for x in include if x in COMPONENTS}
|
|
2452
2458
|
if include and not components:
|
|
2453
2459
|
raise MPContribsClientError(f"`include` must be subset of {COMPONENTS}!")
|
|
2454
2460
|
|
|
@@ -2527,7 +2533,7 @@ class Client(SwaggerClient):
|
|
|
2527
2533
|
def download_structures(
|
|
2528
2534
|
self,
|
|
2529
2535
|
ids: list[str],
|
|
2530
|
-
outdir:
|
|
2536
|
+
outdir: str | Path = DEFAULT_DOWNLOAD_DIR,
|
|
2531
2537
|
overwrite: bool = False,
|
|
2532
2538
|
timeout: int = -1,
|
|
2533
2539
|
fmt: str = "json",
|
|
@@ -2556,7 +2562,7 @@ class Client(SwaggerClient):
|
|
|
2556
2562
|
def download_tables(
|
|
2557
2563
|
self,
|
|
2558
2564
|
ids: list[str],
|
|
2559
|
-
outdir:
|
|
2565
|
+
outdir: str | Path = DEFAULT_DOWNLOAD_DIR,
|
|
2560
2566
|
overwrite: bool = False,
|
|
2561
2567
|
timeout: int = -1,
|
|
2562
2568
|
fmt: str = "json",
|
|
@@ -2585,7 +2591,7 @@ class Client(SwaggerClient):
|
|
|
2585
2591
|
def download_attachments(
|
|
2586
2592
|
self,
|
|
2587
2593
|
ids: list[str],
|
|
2588
|
-
outdir:
|
|
2594
|
+
outdir: str | Path = DEFAULT_DOWNLOAD_DIR,
|
|
2589
2595
|
overwrite: bool = False,
|
|
2590
2596
|
timeout: int = -1,
|
|
2591
2597
|
fmt: str = "json",
|
|
@@ -2615,7 +2621,7 @@ class Client(SwaggerClient):
|
|
|
2615
2621
|
self,
|
|
2616
2622
|
resource: str,
|
|
2617
2623
|
ids: list[str],
|
|
2618
|
-
outdir:
|
|
2624
|
+
outdir: str | Path = DEFAULT_DOWNLOAD_DIR,
|
|
2619
2625
|
overwrite: bool = False,
|
|
2620
2626
|
timeout: int = -1,
|
|
2621
2627
|
fmt: str = "json",
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mpcontribs-client
|
|
3
|
+
Version: 5.10.5rc0
|
|
4
|
+
Summary: Client library for MPContribs API
|
|
5
|
+
Author-email: Patrick Huck <phuck@lbl.gov>, The Materials Project <feedback@materialsproject.org>
|
|
6
|
+
License-Expression: BSD-3-Clause-LBNL
|
|
7
|
+
Project-URL: Homepage, https://github.com/materialsproject/MPContribs
|
|
8
|
+
Project-URL: Documentation, https://docs.materialsproject.org/services/mpcontribs
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: numpy
|
|
12
|
+
Requires-Dist: boltons
|
|
13
|
+
Requires-Dist: bravado
|
|
14
|
+
Requires-Dist: filetype
|
|
15
|
+
Requires-Dist: flatten-dict
|
|
16
|
+
Requires-Dist: ipython
|
|
17
|
+
Requires-Dist: json2html
|
|
18
|
+
Requires-Dist: pandas
|
|
19
|
+
Requires-Dist: pint
|
|
20
|
+
Requires-Dist: plotly
|
|
21
|
+
Requires-Dist: pyIsEmail
|
|
22
|
+
Requires-Dist: pymatgen
|
|
23
|
+
Requires-Dist: pymongo
|
|
24
|
+
Requires-Dist: requests-futures
|
|
25
|
+
Requires-Dist: swagger-spec-validator
|
|
26
|
+
Requires-Dist: tqdm
|
|
27
|
+
Requires-Dist: ujson
|
|
28
|
+
Requires-Dist: cachetools
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: flake8; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-flake8; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest-pycodestyle; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest-xdist; extra == "dev"
|
|
36
|
+
Requires-Dist: py; extra == "dev"
|
|
37
|
+
Provides-Extra: all
|
|
38
|
+
Requires-Dist: mpcontribs-client[dev]; extra == "all"
|
|
39
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
mpcontribs/client/__init__.py,sha256=YOv2KfnQK4T7rHp45XCudqzmv1C4EjeDcgH_VyAog1o,97366
|
|
2
|
+
mpcontribs_client-5.10.5rc0.dist-info/licenses/LICENSE,sha256=5tG0Niaqw2hnuyZZYkRXLSnfVrZA47COwduU_6caPLM,1074
|
|
3
|
+
mpcontribs_client-5.10.5rc0.dist-info/METADATA,sha256=1Mry_eGOJVD61aLEhjeElyA6geL9HRPIwb4aDydD8Ps,1289
|
|
4
|
+
mpcontribs_client-5.10.5rc0.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
|
|
5
|
+
mpcontribs_client-5.10.5rc0.dist-info/top_level.txt,sha256=t8R5L_Dg9oDQMh2gyRFdZGnrzZsr7OjCBTrhTcmimC8,11
|
|
6
|
+
mpcontribs_client-5.10.5rc0.dist-info/RECORD,,
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: mpcontribs-client
|
|
3
|
-
Version: 5.10.4
|
|
4
|
-
Summary: client library for MPContribs API
|
|
5
|
-
Home-page: https://github.com/materialsproject/MPContribs/tree/master/mpcontribs-client
|
|
6
|
-
Author: Patrick Huck
|
|
7
|
-
Author-email: phuck@lbl.gov
|
|
8
|
-
License: MIT
|
|
9
|
-
Requires-Python: >=3.8
|
|
10
|
-
Description-Content-Type: text/markdown
|
|
11
|
-
License-File: LICENSE
|
|
12
|
-
Requires-Dist: numpy
|
|
13
|
-
Requires-Dist: boltons
|
|
14
|
-
Requires-Dist: bravado
|
|
15
|
-
Requires-Dist: filetype
|
|
16
|
-
Requires-Dist: flatten-dict
|
|
17
|
-
Requires-Dist: ipython
|
|
18
|
-
Requires-Dist: json2html
|
|
19
|
-
Requires-Dist: pandas
|
|
20
|
-
Requires-Dist: pint
|
|
21
|
-
Requires-Dist: plotly
|
|
22
|
-
Requires-Dist: pyIsEmail
|
|
23
|
-
Requires-Dist: pymatgen
|
|
24
|
-
Requires-Dist: pymongo
|
|
25
|
-
Requires-Dist: requests-futures
|
|
26
|
-
Requires-Dist: swagger-spec-validator
|
|
27
|
-
Requires-Dist: tqdm
|
|
28
|
-
Requires-Dist: ujson
|
|
29
|
-
Requires-Dist: semantic-version
|
|
30
|
-
Requires-Dist: cachetools
|
|
31
|
-
Provides-Extra: dev
|
|
32
|
-
Requires-Dist: flake8; extra == "dev"
|
|
33
|
-
Requires-Dist: pytest; extra == "dev"
|
|
34
|
-
Requires-Dist: pytest-flake8; extra == "dev"
|
|
35
|
-
Requires-Dist: pytest-pycodestyle; extra == "dev"
|
|
36
|
-
Requires-Dist: pytest-cov; extra == "dev"
|
|
37
|
-
Requires-Dist: py; extra == "dev"
|
|
38
|
-
Dynamic: author
|
|
39
|
-
Dynamic: author-email
|
|
40
|
-
Dynamic: description
|
|
41
|
-
Dynamic: description-content-type
|
|
42
|
-
Dynamic: home-page
|
|
43
|
-
Dynamic: license
|
|
44
|
-
Dynamic: license-file
|
|
45
|
-
Dynamic: provides-extra
|
|
46
|
-
Dynamic: requires-dist
|
|
47
|
-
Dynamic: requires-python
|
|
48
|
-
Dynamic: summary
|
|
49
|
-
|
|
50
|
-

|
|
51
|
-

|
|
52
|
-
|
|
53
|
-
Small, dynamic python client library to connect to [MPContribs](https://docs.mpcontribs.org)
|
|
54
|
-
APIs based on Yelp's [bravado](https://bravado.readthedocs.io).
|
|
55
|
-
|
|
56
|
-
```python
|
|
57
|
-
from mpcontribs.client import Client
|
|
58
|
-
client = Client()
|
|
59
|
-
dir(client) # show available resources
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
By default, the client connects to https://contribs-api.materialsproject.org and uses the environment variable
|
|
63
|
-
`MPCONTRIBS_API_KEY` to set the API key. The key can alternatively be set explicitly via the
|
|
64
|
-
`apikey` argument to the constructor. The `host` argument or the `MPCONTRIBS_API_HOST`
|
|
65
|
-
environment variable can be set to connect to other MPContribs-style APIs:
|
|
66
|
-
|
|
67
|
-
```python
|
|
68
|
-
client = Client(host='ml-api.materialsproject.org')
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
**Troubleshooting**
|
|
72
|
-
|
|
73
|
-
```
|
|
74
|
-
twisted.web._newclient.ResponseNeverReceived:
|
|
75
|
-
[<twisted.python.failure.Failure OpenSSL.SSL.Error:
|
|
76
|
-
[('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')]>]
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
Set the environment variable `SSL_CERT_FILE` to `$(python -m certifi)`.
|
|
80
|
-
|
|
81
|
-
```
|
|
82
|
-
OverflowError: timeout value is too large
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
Install the bravado fork ([PR](https://github.com/Yelp/bravado/pull/472)) manually via
|
|
86
|
-
```
|
|
87
|
-
pip install "bravado[fido] @ git+https://github.com/tschaume/bravado@9ce06f2df7118e16af4a3d3fdc21ccfeedc5cd50#egg=bravado-11.0.3"
|
|
88
|
-
```
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
mpcontribs/client/__init__.py,sha256=9BAtCiXfx38ueipZB5sxVw7208-n6sxDrQ6MGNG8Cuw,97518
|
|
2
|
-
mpcontribs_client-5.10.4.dist-info/licenses/LICENSE,sha256=5tG0Niaqw2hnuyZZYkRXLSnfVrZA47COwduU_6caPLM,1074
|
|
3
|
-
mpcontribs_client-5.10.4.dist-info/METADATA,sha256=wXAUbmYkDzPOcQqIAGVyTliExfTPjbtS7KpP4NuUpM8,2798
|
|
4
|
-
mpcontribs_client-5.10.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
5
|
-
mpcontribs_client-5.10.4.dist-info/top_level.txt,sha256=t8R5L_Dg9oDQMh2gyRFdZGnrzZsr7OjCBTrhTcmimC8,11
|
|
6
|
-
mpcontribs_client-5.10.4.dist-info/RECORD,,
|
{mpcontribs_client-5.10.4.dist-info → mpcontribs_client-5.10.5rc0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|