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.
Files changed (45) hide show
  1. immlib/__init__.py +131 -0
  2. immlib/_init.py +108 -0
  3. immlib/_version.py +235 -0
  4. immlib/doc/__init__.py +38 -0
  5. immlib/doc/_core.py +311 -0
  6. immlib/iolib/__init__.py +29 -0
  7. immlib/iolib/_core.py +720 -0
  8. immlib/pathlib/__init__.py +69 -0
  9. immlib/pathlib/_cache.py +152 -0
  10. immlib/pathlib/_core.py +869 -0
  11. immlib/pathlib/_osf.py +538 -0
  12. immlib/test/__init__.py +16 -0
  13. immlib/test/__main__.py +10 -0
  14. immlib/test/doc/__init__.py +6 -0
  15. immlib/test/doc/test_core.py +91 -0
  16. immlib/test/iolib/__init__.py +7 -0
  17. immlib/test/iolib/test_core.py +81 -0
  18. immlib/test/pathlib/__init__.py +11 -0
  19. immlib/test/pathlib/test_core.py +146 -0
  20. immlib/test/pathlib/test_osf.py +54 -0
  21. immlib/test/types/__init__.py +5 -0
  22. immlib/test/types/test_core.py +110 -0
  23. immlib/test/util/__init__.py +11 -0
  24. immlib/test/util/test_core.py +681 -0
  25. immlib/test/util/test_numeric.py +1374 -0
  26. immlib/test/util/test_quantity.py +218 -0
  27. immlib/test/util/test_url.py +51 -0
  28. immlib/test/workflow/__init__.py +9 -0
  29. immlib/test/workflow/test_core.py +418 -0
  30. immlib/test/workflow/test_plantype.py +248 -0
  31. immlib/types/__init__.py +29 -0
  32. immlib/types/_core.py +333 -0
  33. immlib/util/__init__.py +283 -0
  34. immlib/util/_core.py +2524 -0
  35. immlib/util/_numeric.py +2651 -0
  36. immlib/util/_quantity.py +523 -0
  37. immlib/util/_url.py +114 -0
  38. immlib/workflow/__init__.py +48 -0
  39. immlib/workflow/_core.py +1635 -0
  40. immlib/workflow/_plantype.py +334 -0
  41. immlib-1.0.0.dev2.dist-info/METADATA +76 -0
  42. immlib-1.0.0.dev2.dist-info/RECORD +45 -0
  43. immlib-1.0.0.dev2.dist-info/WHEEL +5 -0
  44. immlib-1.0.0.dev2.dist-info/licenses/LICENSE +21 -0
  45. immlib-1.0.0.dev2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,334 @@
1
+ # -*- coding: utf-8 -*-
2
+ ###############################################################################
3
+ # immlib/workflow/_plantype.py
4
+
5
+
6
+ # Dependencies ################################################################
7
+
8
+ import inspect
9
+ from functools import wraps
10
+ from copy import copy
11
+
12
+ from pcollections import (pdict, ldict, lazy)
13
+
14
+ from ..doc import docwrap
15
+ from ..util import (is_str, is_pdict, is_tdict, is_ldict, assoc, merge)
16
+ from ._core import (calc, plan, plandict, tplandict, is_calcfn)
17
+
18
+
19
+ # #plantype ###################################################################
20
+
21
+ class plantype(type):
22
+ """A metaclass that allows one to create lazy types from calculation plans.
23
+
24
+ The ``plantype`` metaclass handles classes with the base-class
25
+ ``planobject``. In general, one should create a plan-object by inheriting
26
+ from ``planobject``, not by providing the ``plantype`` metaclass, but
27
+ passing ``plantype`` has the same effect (all classes created with
28
+ metaclass ``plantype`` will inherit from ``planobject``).
29
+
30
+ See ``planobject`` for more information.
31
+ """
32
+ __slots__ = ()
33
+ class planobject_base:
34
+ """The base-class for the ``immlib.planobject`` class.
35
+
36
+ ``plantype.planobject_base`` is a simple class that implements the
37
+ basic features of the ``planobject`` class. The separation for certain
38
+ methods from the ``planobject`` type itself is required due to details
39
+ of how the ``planobject`` class, which has the ``plantype`` meta-class,
40
+ gets initialized while the ``plantype.__new__`` method depends on
41
+ methods in the ``planobject`` class (which hasn't been
42
+ initialized/defined at the time that the ``planobject.__new__`` method
43
+ is called. This class shouldn't be used directly and shouldn't be
44
+ inherited. Use the ``planobject`` class instead.
45
+ """
46
+ __slots__ = ('_plandict_',)
47
+ def __getattr__(self, k):
48
+ pd = self._plandict_
49
+ r = pd.get(k, pd)
50
+ if r is pd:
51
+ raise AttributeError
52
+ else:
53
+ return r
54
+ def __setattr__(self, k, v):
55
+ pd = self._plandict_
56
+ pdtype = type(pd)
57
+ plan = type(self).plan
58
+ if pdtype is dict or pdtype is tplandict:
59
+ if k not in plan.inputs:
60
+ msg = "only planobject inputs may be mutated in "
61
+ if pdtype is dict:
62
+ msg += "the __init__ method"
63
+ else:
64
+ msg += "transient planobjects"
65
+ raise ValueError(msg)
66
+ else:
67
+ pd[k] = v
68
+ else:
69
+ raise TypeError(f"type {type(self)} is immutable")
70
+ def __delattr__(self, k):
71
+ pd = self._plandict_
72
+ if type(pd) is dict:
73
+ raise TypeError(
74
+ f"cannot delete attributes from plantype: {type(self)}")
75
+ else:
76
+ raise TypeError(f"type {type(self)} is immutable")
77
+ def __new__(cls, *args, **kwargs):
78
+ # Start by creating the object itself and setting up its slots.
79
+ obj = object.__new__(cls)
80
+ object.__setattr__(obj, '_plandict_', None)
81
+ # Once the __init__ function is done running, the plandict will be
82
+ # cleaned up (this is guaranteed by the plantype meta-class).
83
+ return obj
84
+ def __dir__(self):
85
+ pd = self._plandict_
86
+ l = object.__dir__(self)
87
+ l.extend(pd.keys())
88
+ l.sort()
89
+ return l
90
+ def __init__(self, *args, **kwargs):
91
+ for (k,v) in merge(*args, **kwargs).items():
92
+ setattr(self, k, v)
93
+ @staticmethod
94
+ def _init_wrapper(cls, self, *args, **kwargs):
95
+ """Manages the initialization (``__init__``) for ``planobject``
96
+ types.
97
+
98
+ The ``planobject`` type, and any types that inherit from it, is
99
+ generally immutable; however, When a ``planobject`` is first
100
+ created, it is allowed to set its inputs, as if they were mutable
101
+ attributes, during the ``__init__()`` method. In order to
102
+ facilitate this, a reorganization of the initialization code for
103
+ each ``planobject`` subclass is performed when that class is
104
+ defined. The class's true ``__init__`` method is stored in the
105
+ method ``__planobject_init__``, while the
106
+ ``plantype.planobject_base._init_wrapper`` method is stored in the
107
+ type's ``__init__`` method. This method calls the type's
108
+ ``__planobject_init__`` method then makes the initialized object
109
+ immutable.
110
+
111
+ This method should not be called directly by the user.
112
+ """
113
+ # If the plandict is not a mutable dictionary, we have already been
114
+ # initialized.
115
+ pd = self._plandict_
116
+ if is_pdict(pd) or is_tdict(pd):
117
+ raise RuntimeError(
118
+ "_init_wrapper method called on an already-initialized"
119
+ " planobject")
120
+ elif pd is not None:
121
+ # We're already in the middle of initializing; one of the
122
+ # __init__ methods probably just called a parent class's
123
+ # __init__ method. We can just run it and return.
124
+ return cls.__planobject_init__(self, *args, **kwargs)
125
+ # Otherwise, pd is None, meaning that we're the first initializer.
126
+ # Note that we are now in the process of initializing...
127
+ pd = {}
128
+ object.__setattr__(self, '_plandict_', pd)
129
+ # This method is the real initializer for the class (what the
130
+ # class's actual code wrote as the __init__ method).
131
+ cls.__planobject_init__(self, *args, **kwargs)
132
+ # Postprocess the argument.
133
+ theplan = type(self).plan
134
+ params = dict(theplan.defaults, **pd)
135
+ if params.keys() != theplan.inputs:
136
+ raise ValueError(
137
+ f"bad parameters for plantype {type(self)};"
138
+ f" expected {tuple(theplan.inputs)} but found"
139
+ f" {tuple(params.keys())}")
140
+ pd = theplan(params)
141
+ object.__setattr__(self, '_plandict_', pd)
142
+ # For the pickle module:
143
+ def __getstate__(self):
144
+ return dict(self._plandict_.inputs)
145
+ def __setstate__(self, inputs):
146
+ plan = type(self).plan
147
+ object.__setattr__(self, '_plandict_', plan(inputs))
148
+ def __new__(cls, name, bases, attrs, **kwargs):
149
+ sup = super(plantype, cls)
150
+ # Before we go too far, let's extract the valid args from kwargs.
151
+ initplan = kwargs.pop('plan', None)
152
+ inittrans = kwargs.pop('transient', False)
153
+ if len(kwargs) > 0:
154
+ ks = tuple(kwargs.keys())
155
+ raise ValueError(f"unsupported type options: {ks}")
156
+ # We want to go over the attributes and make a few changes:
157
+ # (1) We want to make the basic updates.
158
+ kvs = [
159
+ ('__getattr__', plantype.planobject_base.__getattr__),
160
+ ('__setattr__', plantype.planobject_base.__setattr__),
161
+ ('__new__', plantype.planobject_base.__new__),
162
+ ('__dir__', plantype.planobject_base.__dir__)]
163
+ for (k,v) in kvs:
164
+ if k in attrs:
165
+ raise ValueError(f"plantype classes may not define {k}")
166
+ else:
167
+ attrs[k] = v
168
+ # (2) We want to save the init function and update it to our version.
169
+ init = attrs.get('__init__', plantype.planobject_base.__init__)
170
+ attrs['__planobject_init__'] = init
171
+ def _initfn(self, *args, **kwargs):
172
+ return plantype.planobject_base._init_wrapper(
173
+ _initfn.cls, self, *args,
174
+ **kwargs)
175
+ attrs['__init__'] = wraps(init)(_initfn)
176
+ # (3) Go through the bases: see if there are planobject bases already,
177
+ # and if not, add planobject in. As we go, collect calculations.
178
+ calcs = {k:v for (k,v) in attrs.items() if is_calcfn(v)}
179
+ found_planobj = False
180
+ for b in bases:
181
+ if not issubclass(b, plantype.planobject_base): continue
182
+ found_planobj = True
183
+ for (k,v) in inspect.getmembers(b):
184
+ if k not in calcs and is_calcfn(v):
185
+ calcs[k] = v
186
+ if not found_planobj:
187
+ bases.append(plantype.planobject_base)
188
+ # (4) We now want to create the plan from these calculations and make
189
+ # sure it's part of the class.
190
+ if initplan is None:
191
+ attrs['plan'] = plan(calcs)
192
+ else:
193
+ attrs['plan'] = plan(initplan, **calcs)
194
+ # (5) Return the type with all the updated attributes.
195
+ cls = sup.__new__(cls, name, bases, attrs)
196
+ _initfn.cls = cls
197
+ return cls
198
+
199
+
200
+ # #planobject #################################################################
201
+
202
+ class planobject(plantype.planobject_base, metaclass=plantype):
203
+ """Base class for objects that are based on lazy calculation plans.
204
+
205
+ ``planobject`` is the base-class for all objects that use ``immlib.plan``
206
+ objects as their base type. Objects that inherit from ``planobject`` (which
207
+ uses metaclass ``plantype``) are defined in the same way that calculation
208
+ plans are defined. Any attributes of the class (including those inherited
209
+ from base classes) that are calculations (see ``immlib.calc`` and
210
+ ``immlib.plan``) are turned into a plan. The inputs of the plan are the
211
+ required parameters for the class, and the outputs of the plan become the
212
+ attributes of the class, which are resolved lazily like in ``plandict`` s.
213
+
214
+ The ``__init__`` function of a ``planobject`` is special. During the
215
+ ``__init__`` function only, the parameters of a ``planobject`` function can
216
+ be set using the usual ``setattr`` interface. All ``planobject``s are
217
+ immutable once they have been initialized, however. At the end of the
218
+ ``__init__`` function, the object must have all of its parameters set,
219
+ otherwise an error is raised. If no ``__init__`` function is defined, then
220
+ the ``planobject`` default init function calls ``merge`` on its arguments
221
+ and keywords; the resulting dict must be a dictionary of the class's
222
+ parameters.
223
+
224
+ ``planobject`` types must not overload the following methods, as they are
225
+ used by the ``planobject`` / ``plantype`` system. These are:
226
+ * ``__new__``
227
+ * ``__setattr__``
228
+ * ``__getattr__``
229
+ * ``__dir__``
230
+ """
231
+ __slots__ = ()
232
+ def __str__(self):
233
+ pd = object.__getattribute__(self, '_plandict_')
234
+ p = pd.plan
235
+ param_str = ", ".join(
236
+ f"{k}={pd[k] if pd.is_ready(k) else '<lazy>'}"
237
+ for k in p.inputs)
238
+ rest_str = ", ".join(
239
+ f"{k}={pd[k] if pd.is_ready(k) else '<lazy>'}"
240
+ for k in p.outputs if not k.startswith('_'))
241
+ cls = type(self)
242
+ if rest_str:
243
+ return f"{cls.__name__}({param_str}; {rest_str})"
244
+ else:
245
+ return f"{cls.__name__}({param_str})"
246
+ def __repr__(self):
247
+ pd = object.__getattribute__(self, '_plandict_')
248
+ p = pd.plan
249
+ param_str = ", ".join(
250
+ f"{k}={pd[k]}" for k in p.inputs)
251
+ rest_str = ", ".join(
252
+ f"{k}={repr(pd.getlazy(k))}" for k in p.outputs)
253
+ cls = type(self)
254
+ if rest_str:
255
+ return f"{cls.__module__}.{cls.__name__}({param_str}; {rest_str})"
256
+ else:
257
+ return f"{cls.__module__}.{cls.__name__}({param_str})"
258
+ def __eq__(self, other):
259
+ if type(self) is not type(other):
260
+ return False
261
+ return self._plandict_.inputs == other._plandict_.inputs
262
+ def __ne__(self, other):
263
+ if type(self) is not type(other):
264
+ return True
265
+ return self._plandict_.inputs != other._plandict_.inputs
266
+ def __hash__(self):
267
+ return hash((type(self), self._plandict_.inputs))
268
+ def copy(self, **kwargs):
269
+ """Creates a copy of the planobject with optional parameter updates.
270
+ """
271
+ pd = plandict(self._plandict_, **kwargs)
272
+ obj = object.__new__(self.__class__)
273
+ object.__setattr__(obj, '_plandict_', pd)
274
+ return obj
275
+ def transient(self):
276
+ """Returns a transient copy of the planobject."""
277
+ pd = self._plandict_
278
+ if pd is None or isinstance(pd, dict):
279
+ raise RuntimeError(
280
+ f"cannot create transient copy during initialization")
281
+ c = copy(self)
282
+ object.__setattr__(c, '_plandict_', tplandict(pd))
283
+ return c
284
+ def persistent(self):
285
+ """Returns a persistent copy of the planobject. If the planobject is
286
+ already persistent, it is returned unchanged.
287
+ """
288
+ pd = self._plandict_
289
+ if pd is None or isinstance(pd, dict):
290
+ raise RuntimeError(
291
+ f"cannot create persistent copy during initialization")
292
+ elif isinstance(pd, plandict):
293
+ # Already persistent.
294
+ return self
295
+ elif not isinstance(pd, tplandict):
296
+ raise ValueError(f"unknown plandict type: {type(pd)}")
297
+ c = copy(self)
298
+ object.__setattr__(c, '_plandict_', pd.persistent())
299
+ return c
300
+ def is_persistent(self):
301
+ """Returns ``True`` if the planobject is persistent and ``False`` if it
302
+ is transient.
303
+ """
304
+ pd = self._plandict_
305
+ if pd is None or isinstance(pd, dict):
306
+ return type(self).init_transient
307
+ else:
308
+ return isinstance(pd, plandict)
309
+
310
+
311
+ # Utilities ###################################################################
312
+
313
+ @docwrap
314
+ def is_planobject(obj):
315
+ '''Determines if an object is an instance of a ``immlib.plantype`` object.
316
+
317
+ ``is_planobject(obj)`` returns ``True`` if ``obj`` is an instance of a
318
+ ``immlib.plantype`` class and ``False`` otherwise.
319
+
320
+ See also: ``plantype``, ``is_plantype``
321
+ '''
322
+ return isinstance(obj, planobject)
323
+ @docwrap
324
+ def is_plantype(obj):
325
+ '''Determines if an object is a ``immlib.plantype``.
326
+
327
+ ``is_plantype(obj)`` returns ``True`` if ``obj`` is a ``immlib``
328
+ ``plantype`` class and ``False`` otherwise. Note that this works for the
329
+ type but not instances of the type, for which you should use
330
+ ``is_planobject``.
331
+
332
+ See also: ``is_planobject``, ``plantype``
333
+ '''
334
+ return isinstance(obj, plantype)
@@ -0,0 +1,76 @@
1
+ Metadata-Version: 2.4
2
+ Name: immlib
3
+ Version: 1.0.0.dev2
4
+ Summary: A library of utilites for immutable scientific data.
5
+ Author-email: "Noah C. Benson" <nben@uw.edu>
6
+ License: MIT
7
+ Project-URL: homepage, https://github.com/noahbenson/immlib
8
+ Project-URL: documentation, https://github.com/noahbenson/immlib
9
+ Project-URL: repository, https://github.com/noahbenson/immlib
10
+ Keywords: persistent,immutable,functional,scientific,workflow
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development
22
+ Classifier: Topic :: Software Development :: Libraries
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Classifier: Topic :: Scientific/Engineering
25
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
26
+ Classifier: Operating System :: Microsoft :: Windows
27
+ Classifier: Operating System :: POSIX
28
+ Classifier: Operating System :: Unix
29
+ Classifier: Operating System :: MacOS
30
+ Requires-Python: >=3.8
31
+ Description-Content-Type: text/markdown
32
+ License-File: LICENSE
33
+ Requires-Dist: pcollections>=0.3.2
34
+ Requires-Dist: numpy>=1.24.0
35
+ Requires-Dist: scipy>=1.7.0
36
+ Requires-Dist: pint>=0.20.0
37
+ Requires-Dist: docrep>=0.3.2
38
+ Requires-Dist: joblib>=1.3.0
39
+ Requires-Dist: cloudpathlib[azure,gs,s3]>=0.18.0
40
+ Requires-Dist: pyyaml>=6.0
41
+ Provides-Extra: dev
42
+ Requires-Dist: torch>=2.2.0; extra == "dev"
43
+ Provides-Extra: test
44
+ Requires-Dist: torch>=2.2.0; extra == "test"
45
+ Provides-Extra: docs
46
+ Requires-Dist: torch>=2.2.0; extra == "docs"
47
+ Requires-Dist: jupyter-book>=1.0.0; extra == "docs"
48
+ Requires-Dist: matplotlib>=3.4.0; extra == "docs"
49
+ Dynamic: license-file
50
+
51
+ ![immlib](https://noahbenson.github.io/immlib/_static/logo.svg "immlib")
52
+
53
+ ![Build Status](https://github.com/noahbenson/immlib/actions/workflows/tests.yml/badge.svg)
54
+ [![codecov](https://codecov.io/gh/noahbenson/immlib/graph/badge.svg?token=8KO3K6DUX4)](https://codecov.io/gh/noahbenson/immlib)
55
+ [![PyPI version](https://badge.fury.io/py/immlib.svg)](https://badge.fury.io/py/immlib)
56
+
57
+ ---
58
+
59
+ **Author**: Noah C. Benson &lt;[nben@uw.edu](mailto:nben@uw.edu)&gt;
60
+ **License**: MIT
61
+ **[Documentation](https://noahbenson.github.io/immlib)**
62
+
63
+ ---
64
+
65
+ `immlib` is a lightweight Python library that simplifies the design of
66
+ application programming interfaces (APIs) for scientific libraries. The name
67
+ immlib comes from the library’s philosophy of using immutable data to simplify
68
+ scientific workflows.
69
+
70
+ `immlib` is heavily based on the library
71
+ [`pimms`](https://github.com/noahbenson/pimms), which effectively served as a
72
+ prototype for `immlib`. Both libraries were motivated by a number of observations
73
+ about the design of scientific software and are an attempt to make some of these
74
+ problems easier to manage.
75
+
76
+ For more information, see the [documentation](https://noahbenson.github.io/immlib).
@@ -0,0 +1,45 @@
1
+ immlib/__init__.py,sha256=fecv8u3tjOwKH4QEzbEoMzZM9ujg7HGf5whbiSG2_Ho,5172
2
+ immlib/_init.py,sha256=iKUNLoI78R7TdT5tp4zofOauTSgFrYfkRyrpbBiaDVU,4836
3
+ immlib/_version.py,sha256=K35pXPrOxntfy7H7G7zEct0zT4rR7fY1kOdLeveAiK8,9735
4
+ immlib/doc/__init__.py,sha256=oBumAnslnULXFYGCPFfe300juR0kYOs2Sg_yVjoW30w,1583
5
+ immlib/doc/_core.py,sha256=8zqulVeRLMkbsiqxuJA9wcvSZC-1UMxjx6P36gZL5BY,12363
6
+ immlib/iolib/__init__.py,sha256=VKXix_P56CYq9PzRYa3zdfnCG704rAuG5U5gcxiyJ5I,732
7
+ immlib/iolib/_core.py,sha256=Ccbq210K0ZOr26aae9W090wyei6mBwJblpn70HNH0ds,29779
8
+ immlib/pathlib/__init__.py,sha256=oH4vA4qlGvVXJZGV5i_1R9s-QgFtymZV-ouhgdNixMw,1532
9
+ immlib/pathlib/_cache.py,sha256=7enOW6hyr9lk_QpGucUdF1QMTtRN5zUkSZVk4HNkYAM,6457
10
+ immlib/pathlib/_core.py,sha256=8pQFDtDYBnveMXn8EEESbDC1lhnpqL-cC891rznkiQk,33104
11
+ immlib/pathlib/_osf.py,sha256=v9SRouin1nsnIVwtvNcOIysAbWF3HuQY1tw2CPhqOn0,23157
12
+ immlib/test/__init__.py,sha256=jQUoerBEmIrZU0k3lcDueL3I2MucqtBTug0xS-4Pvz0,422
13
+ immlib/test/__main__.py,sha256=2dVA5jVU-8Nz6JuECZfAxOpmvAAOeCNhur0mHgBCMcE,326
14
+ immlib/test/doc/__init__.py,sha256=Ne7qgHidq6_-77vFb73M3-TIWHxo9m550ayg-b_G20E,183
15
+ immlib/test/doc/test_core.py,sha256=poLHtPVHGt5hjIpTSy_J6sAN4LiA37WDB3y3hIK1LaE,3317
16
+ immlib/test/iolib/__init__.py,sha256=k17eFvA2NVB-nlRkvc7eqOb13qhg0js5QQHHnWejd3U,204
17
+ immlib/test/iolib/test_core.py,sha256=N1w_XbPXJLnX39_RRx4YqSo-QJso5tuu3p5jAmXp6Zw,3246
18
+ immlib/test/pathlib/__init__.py,sha256=fztC586ptuFNVYUqhGmMDGH4QiUPZfZCC169bjT7v5Q,285
19
+ immlib/test/pathlib/test_core.py,sha256=0oq8L-xlZtgkgCbNOrkm5YNOVK0OHAUXjckE7EAayk0,6251
20
+ immlib/test/pathlib/test_osf.py,sha256=_nc880p41bz8g7_UmWVFCPbraacIMzNi6oFgMC7Zqyw,1980
21
+ immlib/test/types/__init__.py,sha256=rjdFsszZ1o3h5X6bicGQYFhkJ6S7yzZKsRcwkHdmhEY,176
22
+ immlib/test/types/test_core.py,sha256=kMQ9M6GxMHLSYsY8YpD3Pn6IMPB_d7NDMSZ3CZV1Sog,4546
23
+ immlib/test/util/__init__.py,sha256=g7PJ2azOhPEGm6O_VwvSRiSbvOi84L4qJSho3etkkCY,344
24
+ immlib/test/util/test_core.py,sha256=OP62KnR_DBpHVp7mqc3gYjQhLlDbqfZE8UYVq_Vqdgo,30410
25
+ immlib/test/util/test_numeric.py,sha256=8ZMm8d5o1A8u7hV_jqViAypXZ0seV7dpQE6nAAhP2jo,69693
26
+ immlib/test/util/test_quantity.py,sha256=WGhmTOgriERj099erRAMQ00geQBirRsiwjJ7Tv6iqJk,10528
27
+ immlib/test/util/test_url.py,sha256=27nj07zdHLPkWrETUgfAhn_r9FBBh8E99q_phrY_tIk,1953
28
+ immlib/test/workflow/__init__.py,sha256=ADNWqVFMf-HJeLlr-yMjLVTVAkokDIo1g9FOAyZ552A,275
29
+ immlib/test/workflow/test_core.py,sha256=Jz-DmiNBN9OWMhlpXfpk9rlnsZBepz4f8ebkBvbZcag,19684
30
+ immlib/test/workflow/test_plantype.py,sha256=zCr4_D7heoaK3p_4QKWaZfvFdqHqHURL8z_-7XjOZeA,9507
31
+ immlib/types/__init__.py,sha256=1io0pYlD8qGbl5oxC62TXImi374QGp1Ie4nkOjepNdo,786
32
+ immlib/types/_core.py,sha256=KOMrwG2gnAk634wvu7cPgdyvAjGsMUfV2QOTNEuRn4I,12894
33
+ immlib/util/__init__.py,sha256=jLIybcflDJzu6J-Ow4CzQfhLIqm1jPS3P6QvUfr2SJ0,5056
34
+ immlib/util/_core.py,sha256=X5ZZJCa41G-9vbR-Y-ccQmt-JftsTqf_v175gKU2tvQ,86895
35
+ immlib/util/_numeric.py,sha256=UEtaOGsDo3ac4utI98zzp8_t4fRSakFc9UIRXEYILXY,109774
36
+ immlib/util/_quantity.py,sha256=Y5uEOzuVjyqFZ0mKaAVu2NA02TQljxbYW3dUx9pWBYY,19704
37
+ immlib/util/_url.py,sha256=HXZ7e_DwvZclBf8GxmQXptPytTX7jUH8RatpTXOJU8E,4414
38
+ immlib/workflow/__init__.py,sha256=eKln38YHuI2yyduSgO3tuk-QNlUdqiqDTT2ikJZcN3c,1065
39
+ immlib/workflow/_core.py,sha256=Dufeh9DdyQt4XSGwF0ItB_9KybU_6OVQkjA7EKOEGHs,72939
40
+ immlib/workflow/_plantype.py,sha256=Q6tFqPmCUt9KblwfgGGWgdZCnZLn4KSzak7yK47RFu0,14595
41
+ immlib-1.0.0.dev2.dist-info/licenses/LICENSE,sha256=SmSO0nCJkuibrAEOAuq8HNUn6SJ52Pk_NQ2bf7HHw-g,1071
42
+ immlib-1.0.0.dev2.dist-info/METADATA,sha256=jMek4toNqYFpgZiQj-HK30LTDKjGUrWeOb14D_Pelk4,3216
43
+ immlib-1.0.0.dev2.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
44
+ immlib-1.0.0.dev2.dist-info/top_level.txt,sha256=xZpW_wIFaEUjhV4ZcdIsyDupd1RZKv43W3XV8frCkUk,7
45
+ immlib-1.0.0.dev2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.8.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Noah C. Benson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ immlib