Skip to content

Commit 570294e

Browse files
authored
Merge pull request #1095 from AllenInstitute/rc/1.1.0
rc/1.1.0
2 parents 2a5b2be + 6d2d393 commit 570294e

24 files changed

+1763
-621
lines changed

allensdk/__init__.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
#
3636
import logging
3737

38-
__version__ = '1.0.2'
38+
__version__ = '1.1.0'
3939

4040
try:
4141
from logging import NullHandler
@@ -52,7 +52,11 @@ class OneResultExpectedError(RuntimeError):
5252
def one(x):
5353
if isinstance(x, str):
5454
return x
55-
if len(x) != 1:
55+
try:
56+
xlen = len(x)
57+
except TypeError:
58+
return x
59+
if xlen != 1:
5660
raise OneResultExpectedError('Expected length one result, received: {} results from query'.format(x))
5761
if isinstance(x, set):
5862
return list(x)[0]

allensdk/api/caching_utilities.py

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import functools
2+
from pathlib import Path
3+
import warnings
4+
import os
5+
6+
from typing import overload, Callable, Any, Union, Optional, TypeVar
7+
8+
from allensdk.config.manifest import Manifest
9+
10+
11+
P = TypeVar("P")
12+
Q = TypeVar("Q")
13+
14+
AnyPath = Union[Path, str]
15+
16+
17+
@overload
18+
def call_caching(
19+
fetch: Callable[[], Q],
20+
write: Callable[[Q], None],
21+
read: Callable[[], P],
22+
pre_write: Optional[Callable[[Q], Q]] = None,
23+
cleanup: Optional[Callable[[], None]] = None,
24+
lazy: bool = True,
25+
num_tries: int = 1,
26+
failure_message: str = ""
27+
) -> P:
28+
""" Case where a reader is provided
29+
"""
30+
31+
32+
@overload
33+
def call_caching(
34+
fetch: Callable[[], Q],
35+
write: Callable[[Q], None],
36+
read: None = None,
37+
pre_write: Optional[Callable[[Q], Q]] = None,
38+
cleanup: Optional[Callable[[], None]] = None,
39+
lazy: bool = True,
40+
num_tries: int = 1,
41+
failure_message: str = ""
42+
) -> None:
43+
""" Case where no reader is provided (fetches and writes, but returns nothing)
44+
"""
45+
46+
47+
def call_caching(
48+
fetch: Callable[[], Q],
49+
write: Callable[[Q], None],
50+
read: Optional[Callable[[], P]] = None,
51+
pre_write: Optional[Callable[[Q], Q]] = None,
52+
cleanup: Optional[Callable[[], None]] = None,
53+
lazy: bool = True,
54+
num_tries: int = 1,
55+
failure_message: str = ""
56+
) -> Optional[P]:
57+
""" Access data, caching on a local store for future accesses.
58+
59+
Parameters
60+
----------
61+
fetch :
62+
Function which pulls data from a remote/expensive source.
63+
write :
64+
Function which stores data in a local/inexpensive store.
65+
read :
66+
Function which pulls data from a local/inexpensive store.
67+
pre_write :
68+
Function applied to obtained data after fetching, but before writing.
69+
cleanup :
70+
Function for fixing a failed fetch. e.g. unlinking a partially
71+
downloaded file. Exceptions raised by cleanup are not themselves
72+
handled
73+
lazy :
74+
If True, attempt to read the data from the local/inexpensive store
75+
before fetching it. If False, forcibly fetch from the
76+
remote/expensive store.
77+
num_tries :
78+
How many fetches to attempt before (re)raising an exception. A fetch
79+
is failed if reading the result raises an exception.
80+
failure_message :
81+
Provides additional context in the event of a failed download. Emitted
82+
when retrying, and when a fetch failure occurs after tries are
83+
exhausted
84+
85+
Returns
86+
-------
87+
The result of calling read
88+
89+
"""
90+
91+
try:
92+
if not lazy or read is None:
93+
data = fetch()
94+
if pre_write is not None:
95+
data = pre_write(data)
96+
write(data)
97+
98+
if read is not None:
99+
return read()
100+
101+
except Exception:
102+
if cleanup is not None and not lazy:
103+
cleanup()
104+
105+
num_tries -= 1 - lazy # don't count fetchless reads
106+
107+
if num_tries <= 0:
108+
if failure_message:
109+
warnings.warn(failure_message)
110+
raise
111+
112+
retry_message = f"retrying fetch ({num_tries} tries remaining)"
113+
if failure_message:
114+
retry_message = f"{failure_message} {retry_message}"
115+
116+
if not lazy:
117+
warnings.warn(retry_message)
118+
119+
return call_caching(
120+
fetch,
121+
write,
122+
read,
123+
pre_write=pre_write,
124+
cleanup=cleanup,
125+
lazy=False,
126+
num_tries=num_tries,
127+
failure_message=failure_message,
128+
)
129+
130+
return None # required by mypy
131+
132+
133+
def one_file_call_caching(
134+
path: AnyPath,
135+
fetch: Callable[[], Q],
136+
write: Callable[[AnyPath, Q], None],
137+
read: Optional[Callable[[AnyPath], P]] = None,
138+
pre_write: Optional[Callable[[Q], Q]] = None,
139+
cleanup: Optional[Callable[[], None]] = None,
140+
lazy: bool = True,
141+
num_tries: int = 1,
142+
failure_message: str = "",
143+
) -> Optional[P]:
144+
""" A call_caching variant where the local store is a single file. See
145+
call_caching for complete documentation.
146+
147+
Parameters
148+
----------
149+
path :
150+
Path at which the data will be stored
151+
152+
"""
153+
154+
def safe_unlink():
155+
try:
156+
os.unlink(path)
157+
except IOError:
158+
pass
159+
160+
def safe_write(data: Q):
161+
Manifest.safe_make_parent_dirs(path)
162+
write(path, data)
163+
164+
if read is not None:
165+
read = functools.partial(read, path)
166+
167+
if cleanup is None:
168+
cleanup = safe_unlink
169+
170+
return call_caching(
171+
fetch,
172+
safe_write,
173+
read,
174+
pre_write=pre_write,
175+
cleanup=cleanup,
176+
lazy=lazy,
177+
num_tries=num_tries,
178+
failure_message=failure_message,
179+
)

allensdk/brain_observatory/argschema_utilities.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,48 @@
22
import pathlib
33

44
import argparse
5+
import marshmallow
56
from marshmallow import RAISE, ValidationError
67
from argschema import ArgSchemaParser
78
from argschema.schemas import DefaultSchema
89

910

11+
class InputFile(marshmallow.fields.String):
12+
"""A marshmallow String field subclass which deserializes json str fields
13+
that represent a desired input path to pathlib.Path.
14+
Also performs read access checking.
15+
"""
16+
def _deserialize(self, value, attr, obj, **kwargs) -> pathlib.Path:
17+
return pathlib.Path(value)
18+
19+
def _serialize(self, value, attr, obj, **kwargs) -> str:
20+
return str(value)
21+
22+
def _validate(self, value: pathlib.Path):
23+
check_read_access(str(value))
24+
25+
26+
class OutputFile(marshmallow.fields.String):
27+
"""A marshmallow String field subclass which deserializes json str fields
28+
that represent a desired output file path to a pathlib.Path.
29+
Also performs write access checking.
30+
"""
31+
def _deserialize(self, value, attr, obj, **kwargs) -> pathlib.Path:
32+
return pathlib.Path(value)
33+
34+
def _serialize(self, value, attr, obj, **kwargs) -> str:
35+
return str(value)
36+
37+
def _validate(self, value: pathlib.Path):
38+
check_write_access_overwrite(str(value))
39+
40+
1041
def write_or_print_outputs(data, parser):
1142
data.update({'input_parameters': parser.args})
1243
if 'output_json' in parser.args:
1344
parser.output(data, indent=2)
1445
else:
15-
print(parser.get_output_json(data))
46+
print(parser.get_output_json(data))
1647

1748

1849
def check_write_access_dir(dirpath):
@@ -74,7 +105,7 @@ def check_read_access(path):
74105

75106
class RaisingSchema(DefaultSchema):
76107
class Meta:
77-
unknown=RAISE
108+
unknown = RAISE
78109

79110

80111
class ArgSchemaParserPlus(ArgSchemaParser): # pragma: no cover

0 commit comments

Comments
 (0)