immlib 1.0.0.dev2__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.
- immlib/__init__.py +131 -0
- immlib/_init.py +108 -0
- immlib/_version.py +235 -0
- immlib/doc/__init__.py +38 -0
- immlib/doc/_core.py +311 -0
- immlib/iolib/__init__.py +29 -0
- immlib/iolib/_core.py +720 -0
- immlib/pathlib/__init__.py +69 -0
- immlib/pathlib/_cache.py +152 -0
- immlib/pathlib/_core.py +869 -0
- immlib/pathlib/_osf.py +538 -0
- immlib/test/__init__.py +16 -0
- immlib/test/__main__.py +10 -0
- immlib/test/doc/__init__.py +6 -0
- immlib/test/doc/test_core.py +91 -0
- immlib/test/iolib/__init__.py +7 -0
- immlib/test/iolib/test_core.py +81 -0
- immlib/test/pathlib/__init__.py +11 -0
- immlib/test/pathlib/test_core.py +146 -0
- immlib/test/pathlib/test_osf.py +54 -0
- immlib/test/types/__init__.py +5 -0
- immlib/test/types/test_core.py +110 -0
- immlib/test/util/__init__.py +11 -0
- immlib/test/util/test_core.py +681 -0
- immlib/test/util/test_numeric.py +1374 -0
- immlib/test/util/test_quantity.py +218 -0
- immlib/test/util/test_url.py +51 -0
- immlib/test/workflow/__init__.py +9 -0
- immlib/test/workflow/test_core.py +418 -0
- immlib/test/workflow/test_plantype.py +248 -0
- immlib/types/__init__.py +29 -0
- immlib/types/_core.py +333 -0
- immlib/util/__init__.py +283 -0
- immlib/util/_core.py +2524 -0
- immlib/util/_numeric.py +2651 -0
- immlib/util/_quantity.py +523 -0
- immlib/util/_url.py +114 -0
- immlib/workflow/__init__.py +48 -0
- immlib/workflow/_core.py +1635 -0
- immlib/workflow/_plantype.py +334 -0
- immlib-1.0.0.dev2.dist-info/METADATA +76 -0
- immlib-1.0.0.dev2.dist-info/RECORD +45 -0
- immlib-1.0.0.dev2.dist-info/WHEEL +5 -0
- immlib-1.0.0.dev2.dist-info/licenses/LICENSE +21 -0
- immlib-1.0.0.dev2.dist-info/top_level.txt +1 -0
immlib/doc/_core.py
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
###############################################################################
|
|
3
|
+
# immlib/doc/_core.py
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# Dependencies ################################################################
|
|
7
|
+
|
|
8
|
+
from re import compile as re_compile
|
|
9
|
+
from functools import wraps
|
|
10
|
+
from docrep import DocstringProcessor
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# The Document Processor ######################################################
|
|
14
|
+
def make_docproc():
|
|
15
|
+
"""Creates and returns a document preprocessor."""
|
|
16
|
+
docproc = DocstringProcessor()
|
|
17
|
+
# We need to add a few features to the docproc's members so that we can
|
|
18
|
+
# process the Inputs and Outputs sections when present.
|
|
19
|
+
docproc.param_like_sections = \
|
|
20
|
+
docproc.param_like_sections + ['Inputs','Outputs']
|
|
21
|
+
docproc.patterns['Inputs'] = re_compile(
|
|
22
|
+
docproc.patterns['Parameters'].pattern
|
|
23
|
+
.replace('Parameters', 'Inputs')
|
|
24
|
+
.replace('----------', '------'))
|
|
25
|
+
docproc.patterns['Outputs'] = re_compile(
|
|
26
|
+
docproc.patterns['Parameters'].pattern
|
|
27
|
+
.replace('Parameters', 'Outputs')
|
|
28
|
+
.replace('----------', '-------'))
|
|
29
|
+
return docproc
|
|
30
|
+
# This gets imported into `immlib` as `immlib.docproc`, which is the name it
|
|
31
|
+
# should be known by, but we create it here in this submodule.
|
|
32
|
+
_initial_global_docproc = make_docproc()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# The docwrap Decorator #######################################################
|
|
36
|
+
def _docwrap_helper(f, fnname, indent=None, proc=Ellipsis):
|
|
37
|
+
# If no ident number was provided, deduce it.
|
|
38
|
+
if indent is None:
|
|
39
|
+
if not hasattr(f, '__doc__') or f.__doc__ is None:
|
|
40
|
+
# Doesn't matter, no documentation.
|
|
41
|
+
indent = 0
|
|
42
|
+
else:
|
|
43
|
+
lines = f.__doc__.split('\n')
|
|
44
|
+
# We always skip the first line (the one that starts with """).
|
|
45
|
+
lines = lines[1:]
|
|
46
|
+
# Strip the lines.
|
|
47
|
+
striplines = [s.lstrip() for s in lines]
|
|
48
|
+
# Pick out the ones with text in them and calculate the
|
|
49
|
+
# indentation.
|
|
50
|
+
indents = [
|
|
51
|
+
len(ws_s) - len(s)
|
|
52
|
+
for (ws_s,s) in zip(lines, striplines)
|
|
53
|
+
if len(s) > 0]
|
|
54
|
+
# The minimum is the one we want.
|
|
55
|
+
indent = min(indents) if len(indents) > 0 else 0
|
|
56
|
+
if proc is Ellipsis:
|
|
57
|
+
# We need to be able to obtain the default docproc object, even during
|
|
58
|
+
# the process of importing the library, when we use the
|
|
59
|
+
# _initial_global_docproc. Once immlib has been loaded, we use
|
|
60
|
+
# `immlib.docproc` instead.
|
|
61
|
+
try:
|
|
62
|
+
from immlib import docproc as proc
|
|
63
|
+
except ImportError:
|
|
64
|
+
proc = _initial_global_docproc
|
|
65
|
+
ff = f
|
|
66
|
+
ff = proc.with_indent(indent)(ff)
|
|
67
|
+
fd = proc.get_sections(base=fnname, sections=proc.param_like_sections)
|
|
68
|
+
ff = fd(ff)
|
|
69
|
+
ff = wraps(f)(ff) if f is not ff else f
|
|
70
|
+
# Post-process the documentation sections.
|
|
71
|
+
for section in ('parameters', 'other_parameters', 'inputs', 'outputs'):
|
|
72
|
+
k = fnname + '.' + section
|
|
73
|
+
v = proc.params.get(k, '')
|
|
74
|
+
if len(v) == 0: continue
|
|
75
|
+
for ln in v.split('\n'):
|
|
76
|
+
# Skip lines that start with whitespace.
|
|
77
|
+
if ln[0].strip() == '': continue
|
|
78
|
+
pname = ln.split(':')[0].strip()
|
|
79
|
+
proc.keep_params(k, pname)
|
|
80
|
+
return ff
|
|
81
|
+
def docwrap(f=None, /, *, indent=None, proc=Ellipsis):
|
|
82
|
+
"""Applies standard doc-string processing to the decorated function.
|
|
83
|
+
|
|
84
|
+
The ``immlib.docwrap`` decorator applies a standard set of pre-processing
|
|
85
|
+
to the docstring of the function that follows it. This processing amounts
|
|
86
|
+
to using the ``docrep`` module's ``DocstringProcessor`` as a filter on the
|
|
87
|
+
documentation of the function. The function's documentation is always
|
|
88
|
+
placed in the base-name equal to its fully-qualified namespace name.
|
|
89
|
+
|
|
90
|
+
When called as ``@docwrap(name)`` for a string ``name``, the documentation
|
|
91
|
+
for the decorated function is instead placed under the base-name ``name``.
|
|
92
|
+
|
|
93
|
+
Parameters
|
|
94
|
+
----------
|
|
95
|
+
f : function or str or None, optional
|
|
96
|
+
The function to be decorated, when ``@docwrap`` is used alone as a
|
|
97
|
+
decorator, or when used as a decorator with only the other options
|
|
98
|
+
given, such as ``@docwrap(indent=8)``. If a string is given, as in
|
|
99
|
+
``@docwrap('immlib.dictmap')`` then the given string is used as the
|
|
100
|
+
function's name instead of its ``__module__`` plus its
|
|
101
|
+
``__name__``. This is mostly useful when using ``@docwrap`` with a
|
|
102
|
+
function defined in a private submodule; for example ``immlib.dictmap``
|
|
103
|
+
is defined in ``immlib.util._core`` but is imported into a reclaimed by
|
|
104
|
+
the ``immlib`` core namespace, so it is typically considered to belong
|
|
105
|
+
to that namespace.
|
|
106
|
+
|
|
107
|
+
Typically this argument does not need to be provided as it is given
|
|
108
|
+
after the decorator line; the exception to this is when a string is
|
|
109
|
+
given.
|
|
110
|
+
indent : None or int, optional
|
|
111
|
+
The number of spaces that are used as indentation before lines in the
|
|
112
|
+
docstring. This is mostly useful when decorated functions appear in
|
|
113
|
+
indented contexts and thus the default indentation of 4 is
|
|
114
|
+
inappropriate. If ``None`` is given, then the decorator finds the
|
|
115
|
+
non-empty line, not including the first line, with the smallest
|
|
116
|
+
indentation and uses that.
|
|
117
|
+
proc : docrep.DocstringProcessor or Ellipsis, optional
|
|
118
|
+
The `proc` option provides the document processor object from the
|
|
119
|
+
``docrep`` library that should be used to process the decorated
|
|
120
|
+
object. Because these objects can be specifically configured to enable
|
|
121
|
+
different docstring formats, this option is provided to the user. The
|
|
122
|
+
default value is ``Ellipsis``, in which case the ``immlib.docproc``
|
|
123
|
+
object is used. The ``docproc`` object has been configured to work with
|
|
124
|
+
the ``Input`` and ``Output`` sections that are used with calculations
|
|
125
|
+
and plans. The ``immlib.with_docproc`` function can be used to change
|
|
126
|
+
the ``immlib.docproc`` object that is used in a local code-block.
|
|
127
|
+
|
|
128
|
+
Returns
|
|
129
|
+
-------
|
|
130
|
+
object
|
|
131
|
+
The decorated function or object, after its docstring has been parsed.
|
|
132
|
+
|
|
133
|
+
See Also
|
|
134
|
+
--------
|
|
135
|
+
default_docproc :
|
|
136
|
+
Run a code-block with a specific default docstring processor.
|
|
137
|
+
|
|
138
|
+
"""
|
|
139
|
+
# If we've been given a string, then we've been called as @docwrap(name)
|
|
140
|
+
# instead of @docwrap.
|
|
141
|
+
if f is None:
|
|
142
|
+
return lambda fn: _docwrap_helper(
|
|
143
|
+
fn, fn.__module__ + '.' + fn.__name__,
|
|
144
|
+
indent=indent,
|
|
145
|
+
proc=proc)
|
|
146
|
+
if isinstance(f, str):
|
|
147
|
+
return lambda fn: _docwrap_helper(fn, f, indent=indent, proc=proc)
|
|
148
|
+
else:
|
|
149
|
+
return _docwrap_helper(
|
|
150
|
+
f, f.__module__ + '.' + f.__name__,
|
|
151
|
+
indent=indent,
|
|
152
|
+
proc=proc)
|
|
153
|
+
class default_docproc:
|
|
154
|
+
"""Context manager for setting the default ``immlib.docproc`` document
|
|
155
|
+
processing object.
|
|
156
|
+
|
|
157
|
+
The following code-block can be used to evaluate the code represented by
|
|
158
|
+
``...`` using the ``docrep.DocstringProcessor`` object ``docproc`` as the
|
|
159
|
+
default ``immlib.docproc`` processor:
|
|
160
|
+
|
|
161
|
+
.. code-block:: python
|
|
162
|
+
|
|
163
|
+
with immlib.default_docproc(docproc):
|
|
164
|
+
...
|
|
165
|
+
|
|
166
|
+
If the ``immlib.docproc`` value has accidentally been corrupted, then it
|
|
167
|
+
can be reset using the following:
|
|
168
|
+
|
|
169
|
+
.. code-block:: python
|
|
170
|
+
|
|
171
|
+
immlib.default_docproc.reset()
|
|
172
|
+
|
|
173
|
+
Parameters
|
|
174
|
+
----------
|
|
175
|
+
docproc : docrep.DocstringProcessor object
|
|
176
|
+
The docstring processing object to use as the default in ``immlib`` in
|
|
177
|
+
the contextualized code.
|
|
178
|
+
|
|
179
|
+
See Also
|
|
180
|
+
--------
|
|
181
|
+
docwrap : decorator that simplifies the use of the ``docrep`` library.
|
|
182
|
+
|
|
183
|
+
"""
|
|
184
|
+
__slots__ = ('original', 'docproc')
|
|
185
|
+
def __init__(self, docproc):
|
|
186
|
+
if not isinstance(docproc, DocstringProcessor):
|
|
187
|
+
raise TypeError("docproc must be a docrep.DocstringProcessor")
|
|
188
|
+
object.__setattr__(self, 'original', None)
|
|
189
|
+
object.__setattr__(self, 'docproc', docproc)
|
|
190
|
+
def __enter__(self):
|
|
191
|
+
import immlib
|
|
192
|
+
object.__setattr__(self, 'original', immlib.docproc)
|
|
193
|
+
immlib.docproc = self.docproc
|
|
194
|
+
return self.ureg
|
|
195
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
196
|
+
import immlib
|
|
197
|
+
immlib.docproc = self.original
|
|
198
|
+
return False
|
|
199
|
+
def __setattr__(self, name, val):
|
|
200
|
+
raise TypeError("default_docproc is immutable")
|
|
201
|
+
@staticmethod
|
|
202
|
+
def reset():
|
|
203
|
+
"""Resets the value of ``immlib.docproc`` to its value when the
|
|
204
|
+
``immlib`` library was originally loaded."""
|
|
205
|
+
import immlib
|
|
206
|
+
immlib.docproc = _initial_global_docproc
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
# Other Utilities #############################################################
|
|
210
|
+
|
|
211
|
+
@docwrap
|
|
212
|
+
def detect_indentation(text, /, skip_first=True, tabsize=8):
|
|
213
|
+
"""Given a block of text that is part of a docstring, guess the level of
|
|
214
|
+
indentation used to write it.
|
|
215
|
+
|
|
216
|
+
This function accepts a string that contains multiple lines and guesses the
|
|
217
|
+
indentation level used to write it. It does this by splitting the lines and
|
|
218
|
+
finding the line that starts with the smallest number of spaces. That
|
|
219
|
+
number of spaces is the indentation guess.
|
|
220
|
+
|
|
221
|
+
By default, this function skips the first line because it is customary to
|
|
222
|
+
start docstrings out with an unindented line. This behavior can be changed
|
|
223
|
+
by setting the optional argument `skip_first` to ``False``.
|
|
224
|
+
|
|
225
|
+
Parameters
|
|
226
|
+
----------
|
|
227
|
+
text : str
|
|
228
|
+
The text whose indentation is to be guessed.
|
|
229
|
+
skip_first : bool, optional
|
|
230
|
+
Whether to skip the first line when detecting the indentation level.
|
|
231
|
+
The default is ``True``.
|
|
232
|
+
tabsize : int, optional
|
|
233
|
+
The number of spaces in a tab-stop; used to replace the tab characters
|
|
234
|
+
in each line using the method ``str.expandtabs``. The default is ``8``.
|
|
235
|
+
|
|
236
|
+
Returns
|
|
237
|
+
-------
|
|
238
|
+
int
|
|
239
|
+
The number of spaces of indentation detected.
|
|
240
|
+
"""
|
|
241
|
+
if not isinstance(text, str):
|
|
242
|
+
raise TypeError(
|
|
243
|
+
f"detect_indentation requires str but got {type(text)}")
|
|
244
|
+
lns = text.split('\n')
|
|
245
|
+
ident = None
|
|
246
|
+
if skip_first:
|
|
247
|
+
lns = lns[1:]
|
|
248
|
+
for ln in lns:
|
|
249
|
+
ln = ln.expandtabs(tabsize)
|
|
250
|
+
if ln.strip() == '':
|
|
251
|
+
continue
|
|
252
|
+
ln_ident = len(ln) - len(ln.lstrip())
|
|
253
|
+
if ident is None:
|
|
254
|
+
ident = ln_ident
|
|
255
|
+
elif ln_ident < ident:
|
|
256
|
+
ident = ln_ident
|
|
257
|
+
return ident
|
|
258
|
+
@docwrap
|
|
259
|
+
def reindent(text, new_indent=0, /,
|
|
260
|
+
skip_first=True, tabsize=8, final_endline=True):
|
|
261
|
+
"""Returns a block of text with a different indentation.
|
|
262
|
+
|
|
263
|
+
``reindent(text, n)`` returns a copy of `text` after removing its current
|
|
264
|
+
indentation level and uniformly reindenting the text with ``n`` spaces. The
|
|
265
|
+
first line is skipped entirely, and the current indentation level is
|
|
266
|
+
detected using ``detect_indentation``.
|
|
267
|
+
|
|
268
|
+
Parameters
|
|
269
|
+
----------
|
|
270
|
+
text : str
|
|
271
|
+
The text that is to be reindented.
|
|
272
|
+
new_indent : int, optional
|
|
273
|
+
The new indentation level. If this is not provided, then the default is
|
|
274
|
+
0, meaning that the text will be unindented.
|
|
275
|
+
skip_first : bool, optional
|
|
276
|
+
Whether or not to skip the first line.
|
|
277
|
+
tabside : int, optional
|
|
278
|
+
How large to consider tab characters in the text; this is used with the
|
|
279
|
+
``str.expandtabs`` method. The default is 8.
|
|
280
|
+
final_endline : bool, optional
|
|
281
|
+
Whether the returned string should end with a newline or not. The
|
|
282
|
+
default is ``True``.
|
|
283
|
+
|
|
284
|
+
Returns
|
|
285
|
+
-------
|
|
286
|
+
str
|
|
287
|
+
A duplicate of `text` with updated indentation.
|
|
288
|
+
"""
|
|
289
|
+
# Get the current indentation level:
|
|
290
|
+
currind = detect_indentation(text, skip_first=skip_first, tabsize=tabsize)
|
|
291
|
+
# Split the text into lines.
|
|
292
|
+
lns = text.split('\n')
|
|
293
|
+
# Remove all the current indentations:
|
|
294
|
+
newlns = []
|
|
295
|
+
if skip_first:
|
|
296
|
+
newlns.append(lns[0].expandtabs(tabsize))
|
|
297
|
+
lns = lns[1:]
|
|
298
|
+
head = ' ' * currind
|
|
299
|
+
newhead = ' ' * new_indent
|
|
300
|
+
for ln in lns:
|
|
301
|
+
ln = ln.expandtabs(tabsize)
|
|
302
|
+
if ln.strip() == '':
|
|
303
|
+
newlns.append('')
|
|
304
|
+
continue
|
|
305
|
+
if ln.startswith(head):
|
|
306
|
+
ln = ln[currind:]
|
|
307
|
+
newlns.append(newhead + ln)
|
|
308
|
+
newtext = '\n'.join(newlns)
|
|
309
|
+
if final_endline and newtext[-1] != '\n':
|
|
310
|
+
newtext += '\n'
|
|
311
|
+
return newtext
|
immlib/iolib/__init__.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
################################################################################
|
|
3
|
+
# pimms/iolib/__init__.py
|
|
4
|
+
|
|
5
|
+
"""Input/output tools managed by pimms; primarily the save and load functions.
|
|
6
|
+
|
|
7
|
+
The `pimms.iolib` module contains tools for saving and loading data to/from
|
|
8
|
+
paths or streams. This functionality is primarily supported via the `save` and
|
|
9
|
+
`load` objects that behave as general (de)serializers to which formats can be
|
|
10
|
+
registered.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from ._core import (
|
|
14
|
+
Save,
|
|
15
|
+
save,
|
|
16
|
+
Load,
|
|
17
|
+
load)
|
|
18
|
+
|
|
19
|
+
__all__ = (
|
|
20
|
+
'Save',
|
|
21
|
+
'save',
|
|
22
|
+
'Load',
|
|
23
|
+
'load')
|
|
24
|
+
|
|
25
|
+
# Mark these as native to this module.
|
|
26
|
+
Save.__module__ = __name__
|
|
27
|
+
save.__module__ = __name__
|
|
28
|
+
Load.__module__ = __name__
|
|
29
|
+
load.__module__ = __name__
|