cdxcore 0.1.11__py3-none-any.whl → 0.1.14__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.

Potentially problematic release.


This version of cdxcore might be problematic. Click here for more details.

cdxcore/deferred.py ADDED
@@ -0,0 +1,752 @@
1
+ """
2
+ Overview
3
+ --------
4
+
5
+ Framework for delayed execution of a Python code tree.
6
+
7
+ Used by :mod:`cdxcore.dnaplot`.
8
+
9
+ Import
10
+ ------
11
+
12
+ .. code-block:: python
13
+
14
+ from cdxcore.deferrred import Deferred
15
+
16
+ """
17
+
18
+ from .err import verify
19
+ from. util import qualified_name, fmt_list
20
+ from .verbose import Context
21
+ from collections.abc import Collection, Sequence, Mapping
22
+ import gc as gc
23
+
24
+ class _ParentDeleted:
25
+ pass
26
+
27
+ class ResolutionDependencyError(RuntimeError):
28
+ """
29
+ Exeception if the resolution of a deferred action failed because one
30
+ of its source items has not been resolved.
31
+ """
32
+ pass
33
+
34
+ class DeferredAction(object):
35
+ """
36
+ A deferred action keeps track of an action dependency tree
37
+ for an object which has not been created, yet.
38
+
39
+ This class is returned by :class:`cdxcore.deferred.Deferred`.
40
+
41
+ The aim is to be able to record a sequence of actions on a placeholder
42
+ for an object which does not yet exist
43
+ such as function calls, item access, or attribute access,
44
+ and store the resulting logical dependency tree. Once the target
45
+ element is available, execute the dependency tree iteratively.
46
+
47
+ A basic example is as follows: assume we have a class ``A`` of which
48
+ we will create an object ``a``, but only at a later stage. We
49
+ wish to record a list of deferred actions on ``a`` ahead of its creation.
50
+
51
+ Define the class::
52
+
53
+ class A(object):
54
+ def __init__(self, x):
55
+ self.x = x
56
+ def a_func(self, y):
57
+ return self.x * y
58
+ @staticmethod
59
+ def a_static(y):
60
+ return y
61
+ @property
62
+ def a_propery(self):
63
+ return self.x
64
+ def __getitem__(self, y):
65
+ return self.x * y
66
+ def __call__(self, y):
67
+ return self.x * y
68
+ def another(self):#
69
+ return A(x=self.x*2)
70
+
71
+ Use :class:`cdxcore.deferred.Deferred` to create a root ``DeferredAction`` object::
72
+
73
+ from cdxcore.deferred import Deferred
74
+ da = Deferred("A")
75
+
76
+ Record a few actions::
77
+
78
+ af = da.a_func(1) # defer function call to a_func(1)
79
+ ai = da[2] # defer item access
80
+ aa = da.x # defer attribute access
81
+
82
+ :class:`cdxcore.deferred.DeferredAction` works iteratively, e.g. all values returned from a function call, ``__call__``, or ``__getitem__``
83
+ are themselves deferred automatically.`:class:`cdxcore.deferred.DeferredAction` is also able to defer item and attribute assignments.
84
+
85
+ As an example::
86
+
87
+ an = da.another()
88
+ an = an.another() # call a.another().another() --> an.x = 1*2*2 = 4
89
+
90
+ Finally, resolve the execution by instantiating ``A`` and calling :meth:`cdxcore.deferred.DeferredAction.deferred_resolve`::
91
+
92
+ a = A()
93
+ da.deferred_resolve( a )
94
+
95
+ print( an.x ) # -> 4
96
+
97
+ This functionality is used in :mod:`cdxcore.dynaplot`::
98
+
99
+ from cdxcore.dynaplot import figure
100
+
101
+ fig = figure() # the DynanicFig returned by figure() is derived from DeferredAction
102
+ ax = fig.add_subplot() # Deferred call for add_subplot()
103
+ lns = ax.plot( x, y )[0] # This is a deferred call to plot() and then [0].
104
+ fig.render() # renders the figure and executes plot() then [0]
105
+ lns.set_ydata( y2 ) # we can now access the resulting Line2D object via the DeferredAction wrapper
106
+ fig.close() # update graph
107
+
108
+ **Constructor**
109
+
110
+ External applications do not usually need to directly create objects
111
+ of type :class:`cdxcore.deferred.DeferredAction`. Use :class:`cdxcore.deferred.Deferred`
112
+ as top level entry point.
113
+ """
114
+
115
+ def __init__(self, *, action : str, #: :meta private:
116
+ info : str, #: :meta private:
117
+ parent : type, #: :meta private:
118
+ src_dependants : list, #: :meta private:
119
+ sources : dict, #: :meta private:
120
+ args : Collection,#: :meta private:
121
+ kwargs : Mapping, #: :meta private:
122
+ max_err_len : int, #: :meta private:
123
+ ):
124
+ """
125
+ Initialize a deferred action.
126
+ :meta private:
127
+ """
128
+ self._deferred_max_err_len = max_err_len
129
+
130
+ verify( isinstance(info, str), lambda : f"'info' must be a string; found {self._deferred_to_str(info)}.", exception=ValueError)
131
+ verify( parent is None or isinstance(parent, DeferredAction), lambda : f"'parent' must be a DeferredAction; found {type(parent)}.", exception=ValueError)
132
+ verify( isinstance(action, str), lambda : f"'action' must be a string; found {self._deferred_to_str(action)}.", exception=ValueError)
133
+ verify( isinstance(args, (Sequence,Collection)), lambda : f"'args' must be a Collection; found {type(args)}.", exception=ValueError)
134
+ verify( isinstance(kwargs, Mapping), lambda : f"'kwargs' must be a Mapping; found {type(kwargs)}.", exception=ValueError)
135
+
136
+ if action == "":
137
+ verify( len(info)>0 and
138
+ len(args) == 0 and
139
+ parent is None and
140
+ len(sources) == 0 and
141
+ len(kwargs) == 0, "Must specify 'info' but cannot specify 'sources', 'parent', 'args', or 'kwargs' for the root action \"\"", exception=ValueError)
142
+
143
+ self._deferred_action = action # action code
144
+ self._deferred_parent = parent
145
+ self._deferred_depth = parent._deferred_depth+1 if not parent is None else 0
146
+ self._deferred_args = args
147
+ self._deferred_kwargs = kwargs
148
+ self._deferred_live = None # once the object exists, it goes here.
149
+ self._deferred_was_resolved = False # whether the action was executed.
150
+ self._deferred_dependants = [] # list of all direct dependent actions
151
+ self._deferred_src_dependants = src_dependants
152
+
153
+ if action == "":
154
+ info = "$"+info
155
+ self._deferred_sources = {id(self):info}
156
+ self._deferred_info = info
157
+ else:
158
+ self._deferred_sources = sources
159
+ self._deferred_info = info
160
+
161
+ self.__dict__["_ready_for_the_deferred_magic_"] = 1
162
+
163
+ @property
164
+ def _deferred_parent_info(self) -> str:
165
+ return self._deferred_parent._deferred_info if not self._deferred_parent is None else ""
166
+
167
+ def _deferred_to_str( self, x ):
168
+ """ Limit string 'x' to ``max_err_len`` by adding ``...`` if necessary. """
169
+ x = str(x)
170
+ if len(x) > self._deferred_max_err_len:
171
+ if x[-1] in [')', ']', '}']:
172
+ x = x[:self._deferred_max_err_len-4] + "..." + x[-1]
173
+ else:
174
+ x = x[:self._deferred_max_err_len-3] + "..."
175
+ return x
176
+
177
+ def _deferred_to_argstr( self, x ):
178
+ if isinstance(x, DeferredAction):
179
+ if not x.deferred_was_resolved:
180
+ return f"{{{x._deferred_info}}}"
181
+ x = x._deferred_live
182
+ return str(x)
183
+
184
+ def _deferred_fmt_args(self, args, kwargs ):
185
+ args_ = [ self._deferred_to_argstr(x) for x in args ]
186
+ kwargs_ = { k: self._deferred_to_argstr(x) for k,x in kwargs.items() }
187
+ fmt_args = ""
188
+ for _ in args_:
189
+ fmt_args += str(_) + ","
190
+ for _ in kwargs_.items():
191
+ fmt_args += f"{_[0]}={_[1]},"
192
+ return fmt_args[:-1]
193
+
194
+ def _deferred_qualname( self, x ):
195
+ """ Attempt to obtain a human readable name for ``x``/ """
196
+ if x is None:
197
+ return "None"
198
+ name = getattr(x,"_deferred_qualname__", getattr(x, "__name__", None) )
199
+ if not name is None:
200
+ return name
201
+ try:
202
+ return qualified_name( type(x) )
203
+ except:
204
+ pass
205
+ return self._deferred_to_str(x)
206
+
207
+ @property
208
+ def deferred_info(self) -> str:
209
+ """
210
+ Text description of the current action.
211
+
212
+ Top level sources are indicated by a ``$``. Curly brackets ``{}`` indicate
213
+ deferred actions themselves. For example:
214
+
215
+ .. code-block:: python
216
+
217
+ $a.f({$b.g(1)})
218
+
219
+ Is generated by:
220
+
221
+ .. code-block:: python
222
+
223
+ from cdxcore.deferred import Deferred
224
+
225
+ a = Deferred("a")
226
+ b = Deferred("b")
227
+ _ = a.f( b.g(1) )
228
+ print( _.deferred_info ) # -> '$a.f({$b.g(1)})'
229
+ """
230
+ return self._deferred_info
231
+
232
+ @property
233
+ def deferred_was_resolved(self) -> bool:
234
+ """ Whether the underlying operation has already been resolved. """
235
+ return self._deferred_was_resolved
236
+
237
+ @property
238
+ def deferred_dependants(self) -> list:
239
+ """ Retrieve list of dependant :class:`cdxcore.deferred.DeferredAction` objects. """
240
+ return self._deferred_dependants
241
+
242
+ @property
243
+ def deferred_sources(self) -> dict:
244
+ """
245
+ Retrieve a dictionary with information on all top-level sources
246
+ this deferred action depends on.
247
+
248
+ A top level source is an element which must be created explicitly by the user.
249
+ These are the elements generated by :func:'cdxcore.deferred.Deferred'.
250
+
251
+ The list contains a unique ``id`` and the name of the
252
+ source. The ``id`` is used to allow the same name for
253
+ several :func:`Deferred` elements.
254
+
255
+ Most users will prefer a simple list of names of sources.
256
+ In that case, use :meth:`cdxcore.deferred.Deferred.deferred_sources_names`.
257
+
258
+ """
259
+ return self._deferred_sources
260
+
261
+ @property
262
+ def deferred_sources_names(self) -> list:
263
+ """
264
+ Retrieve a list of names of all top-level sources
265
+ this deferred action depends on.
266
+
267
+ A top level source is an element which must be created explicitly by the user.
268
+ These are the elements generated by :func:'cdxcore.deferred.Deferred'.
269
+
270
+ The list returned by this function contains the ``info`` names
271
+ for each of the sources.
272
+
273
+ Example:
274
+
275
+ .. code-block:: python
276
+
277
+ from cdxcore.deferred import Deferred
278
+
279
+ a = Deferred("a")
280
+ b = Deferred("b")
281
+ _ = a.f( b.g(1) )
282
+ print( _.deferred_sources_names ) # -> ['$a', '$b']
283
+
284
+ The purpose of this function is to allow users to detect dependencies on
285
+ source objects and organize resolution code accordingly.
286
+ """
287
+ return list( self.deferred_sources.values() )
288
+
289
+ @property
290
+ def deferred_action_result(self):
291
+ """
292
+ Returns the result of the deferred action, if available.
293
+
294
+ Raises a :class:`RuntimeError` if the action has not been
295
+ executed with :meth:`cdxcore.deferred.Deferred.deferred_resolve` yet.
296
+
297
+ Note that this function might return ``None`` if the resolved
298
+ action had returned ``None``.
299
+ """
300
+ verify( self._deferred_was_resolved, lambda : f"Deferred action '{self._deferred_info}' has not been executed yet" )
301
+ return self._deferred_live
302
+
303
+ def deferred_print_dependency_tree( self, verbose : Context = Context.all, *, with_sources : bool = False ):
304
+ """
305
+ Prints the dependency tree recorded by this object and its descendants.
306
+
307
+ This function must be called *before* :meth:`cdxcore.deferred.Deferred.deferred_resolve` is called
308
+ (``deferred_resolve`` clears the dependency tree to free memory).
309
+
310
+ You can collect this information manually as follows if required:
311
+
312
+ .. code-block:: python
313
+
314
+ from cdxcore.deferred import Deferred
315
+
316
+ a = Deferred("a")
317
+ b = Deferred("b")
318
+ _ = a.f( b.g(1) )
319
+
320
+ def collect(x,i=0,data=None):
321
+ data = data if not data is None else list()
322
+ data.append((i,x.deferred_info,x.deferred_sources_names))
323
+ for d in x.deferred_dependants:
324
+ collect(d,i+1,data)
325
+ return data
326
+ for i, info, sources in collect(a):
327
+ print( f"{' '*i} {info} <- {sources}" )
328
+
329
+ prints:
330
+
331
+ .. code-block:: python
332
+
333
+ $a <- ['$a']
334
+ $a.f <- ['$a']
335
+ $a.f({$b.g(1)}) <- ['$a', '$b']
336
+ """
337
+ if with_sources:
338
+ sources = sorted( self._deferred_sources.values() )
339
+ s = ""
340
+ for _ in sources:
341
+ s += _+","
342
+ s = s[:-1]
343
+ verbose.write( f"{self._deferred_info} <= {s}" )
344
+ else:
345
+ verbose.write( self._deferred_info )
346
+
347
+ for d in self._deferred_dependants:
348
+ d.deferred_print_dependency_tree( with_sources=with_sources, verbose=verbose(1) )
349
+
350
+ # Resolve
351
+ # =======
352
+
353
+ def _deferred_resolve(self, verbose : Context = None ):
354
+ """
355
+ Executes the deferred action with ``parent`` as the subject of the action to be performed.
356
+
357
+ For example, if the action is ``__getitem__`` parameter ``key``, then this function
358
+ will execute ``parent[key]``, resolve all dependent functions, and then return the
359
+ value of ``parent[key]``.
360
+ """
361
+ if self._deferred_was_resolved:
362
+ return
363
+ verify( self._deferred_action != "", lambda : f"Cannot resolve to level action '{self._deferred_info}' here. Looks like user error, sorry.")
364
+
365
+ # obtaining the 'parent' object from
366
+ parent = self._deferred_parent._deferred_live
367
+ verbose = Context.quiet if verbose is None else verbose
368
+
369
+ verify( not parent is None, lambda : f"Cannot resolve '{self._deferred_info}': the parent action '{self._deferred_parent_info}' returned 'None'" )
370
+ verify( not isinstance(parent, DeferredAction), lambda : f"Cannot resolve '{self._deferred_info}' using a 'DeferredAction'" )
371
+
372
+ # what action
373
+ def morph(x):
374
+ if not isinstance(x, DeferredAction):
375
+ return x
376
+ if x.deferred_was_resolved:
377
+ return x._deferred_live
378
+ raise ResolutionDependencyError(
379
+ f"Cannot resolve '{self._deferred_info}' with the concrete element of type '{qualified_name(parent)}': "+\
380
+ f"execution is dependent on yet-unresolved element '{x._deferred_info}'. "+\
381
+ "Resolve that element first. "+\
382
+ f"Note: '{self._deferred_info}' is dependent on the following sources: {fmt_list(self._deferred_sources.values(),sort=True)}."
383
+ )
384
+ args = [ morph(x) for x in self._deferred_args ]
385
+ kwargs = { k: morph(x) for k,x in self._deferred_kwargs.items() }
386
+
387
+ if self._deferred_action == "__getattr__":
388
+ # __getattr__ is not a standard member and cannot be obtained with getattr()
389
+ try:
390
+ live = getattr(parent,*args,**kwargs)
391
+ except AttributeError as e:
392
+ arguments = self._deferred_fmt_args(args,kwargs)
393
+ ppt = f"provided by '{self._deferred_parent_info}' " if self._deferred_parent_info!="" else ''
394
+ emsg = f"Cannot resolve '{self._deferred_info}': the concrete element of type '{qualified_name(parent)}' {ppt}"+\
395
+ f"does not have the requested attribute '{arguments}'."
396
+ e.args = (emsg,) + e.args
397
+ raise e
398
+ else:
399
+ # all other properties -> standard handling
400
+ try:
401
+ action = getattr(parent, self._deferred_action)
402
+ except AttributeError as e:
403
+ ppt = f"provided by '{self._deferred_parent_info}' " if self._deferred_parent_info!="" else ''
404
+ emsg = f"Cannot resolve '{self._deferred_info}': the concrete element of type '{qualified_name(parent)}' {ppt}"+\
405
+ f"does not contain the action '{self._deferred_action}'"
406
+ e.args = (emsg,) + e.args
407
+ raise e
408
+
409
+ try:
410
+
411
+ live = action( *args, **kwargs )
412
+ except Exception as e:
413
+ arguments = self._deferred_fmt_args(args,kwargs)
414
+ ppt = f"provided by '{self._deferred_parent_info}' " if self._deferred_parent_info!="" else ''
415
+ emsg = f"Cannot resolve '{self._deferred_info}': when attempting to execute the action '{self._deferred_action}' "+\
416
+ f"using the concrete element of type '{qualified_name(parent)}' {ppt}"+\
417
+ f"with parameters '{arguments}' "+\
418
+ f"an exception was raised: {e}"
419
+ e.args = (emsg,) + e.args
420
+ raise e
421
+ del action
422
+
423
+ verbose.write(f"{self._deferred_info} -> '{qualified_name(live)}' : {self._deferred_to_str(live)}")
424
+
425
+ # clear object
426
+ # note that as soon as we write to self._deferred_live we can no longer
427
+ # access any information via getattr()/setattr()/delattr().
428
+ # So make sure the parameter scope is released as
429
+ # 'args' and 'kwargs' may hold substantial amounts of memory,
430
+ # For example when this framework is used for delayed
431
+ # plotting with cdxcore.dynaplot
432
+
433
+
434
+ self._deferred_kwargs = None
435
+ self._deferred_args = None
436
+ self._deferred_parent = _ParentDeleted() # None is a valid parent --> choose someting bad
437
+ gc.collect()
438
+
439
+ # action
440
+ self._deferred_was_resolved = True
441
+ self._deferred_live = live
442
+
443
+ # Iteration
444
+ # =========
445
+ # The following are deferred function calls on the object subject of the action ``self``.
446
+
447
+ def _act( self, action : str, *,
448
+ args : Collection = [],
449
+ kwargs : Mapping = {},
450
+ num_args : int = None,
451
+ fmt : str = None
452
+ ):
453
+ """ Standard action handling """
454
+
455
+ # we already have a live object --> action directly
456
+ if self._deferred_was_resolved:
457
+ if action == "":
458
+ return self._live
459
+ if action == "__getattr__":
460
+ return getattr(self._live, *args, **kwargs)
461
+ try:
462
+ element = getattr(self._deferred_live, action)
463
+ except AttributeError as e:
464
+ emsg = f"Cannot route '{self._deferred_info}' to the object '{qualified_name(self._deferred_live)}' "+\
465
+ f"provided by '{self._deferred_parent_info}': the object "+\
466
+ f"does not contain the action '{action}'"
467
+ e.args = (emsg,) + e.args
468
+ raise e
469
+
470
+ return element(*args, **kwargs)
471
+
472
+ # format info string
473
+ fmt_args = lambda : self._deferred_fmt_args(args,kwargs)
474
+ if fmt is None:
475
+ verify( num_args is None, f"Error defining action '{action}' for '{self._deferred_info}': cannot specify 'num_args' if 'fmt' is None")
476
+ info = f"{self._deferred_info}.{action}({fmt_args()})"
477
+ else:
478
+ if not num_args is None:
479
+ # specific list of arguments
480
+ verify( num_args is None or len(args) == num_args, lambda : f"Error defining action '{action}' for '{self._deferred_info}': expected {num_args} but found {len(args)}" )
481
+ verify( len(kwargs) == 0, lambda : f"Error defining action '{action}' for '{self._deferred_info}': expected {num_args} ... in this case no kwargs are expected, but {len(kwargs)} where found" )
482
+ verify( not fmt is None, lambda : f"Error defining action '{action}' for '{self._deferred_info}': 'fmt' not specified" )
483
+
484
+ def label(x):
485
+ return x if not isinstance(x, DeferredAction) else x._deferred_info
486
+ arguments = { f"arg{i}" : label(arg) for i, arg in enumerate(args) }
487
+ arguments['parent'] = self._deferred_info
488
+ try:
489
+ info = fmt.format(**arguments)
490
+ except ValueError as e:
491
+ raise ValueError( fmt, e, arguments ) from e
492
+
493
+ else:
494
+ # __call__
495
+ info = fmt.format(parent=self._deferred_info, args=fmt_args())
496
+
497
+ # detected dependencies in arguments on other
498
+ sources = dict(self._deferred_sources)
499
+ for x in args:
500
+ if isinstance(x, DeferredAction):
501
+ sources |= x._deferred_sources
502
+ for x in kwargs.values():
503
+ if isinstance(x, DeferredAction):
504
+ sources |= x._deferred_sources
505
+
506
+ # create new action
507
+ deferred = DeferredAction(
508
+ action=action,
509
+ parent=self,
510
+ info=self._deferred_to_str(info),
511
+ args=list(args),
512
+ kwargs=dict(kwargs),
513
+ max_err_len=self._deferred_max_err_len,
514
+ sources=sources,
515
+ src_dependants=self._deferred_src_dependants,
516
+ )
517
+ self._deferred_src_dependants.append( deferred )
518
+ self._deferred_dependants.append( deferred )
519
+ return deferred
520
+
521
+ # Routed actions
522
+ # --------------
523
+ # We handle __getattr__ and __setattr__ explicitly
524
+
525
+ def __getattr__(self, attr ):
526
+ """ Deferred attribute access """
527
+ private_str = "_deferred_"
528
+ #print("__getattr__", attr, self.__dict__.get(private_str+"info", "?"))
529
+ #print(" ", self.__dict__)
530
+ if attr in self.__dict__ or\
531
+ attr[:2] == "__" or\
532
+ attr[:len(private_str)] == private_str or\
533
+ not "_ready_for_the_deferred_magic_" in self.__dict__:
534
+ #print("__getattr__: direct", attr)
535
+ try:
536
+ return self.__dict__[attr]
537
+ except KeyError as e:
538
+ raise AttributeError(*e.args, self.__dict__[private_str+"info"]) from e
539
+ if not self._deferred_live is None:
540
+ #print("__getattr__: live", attr)
541
+ return getattr(self._deferred_live, attr)
542
+ #print("__getattr__: act", attr)
543
+ return self._act("__getattr__", args=[attr], num_args=1, fmt="{parent}.{arg0}")
544
+
545
+ def __setattr__(self, attr, value):
546
+ """ Deferred attribute access """
547
+ private_str = "_deferred_"
548
+ #print("__getattr__", attr, self.__dict__.get(private_str+"info", "?"))
549
+ if attr in self.__dict__ or\
550
+ attr[:2] == "__" or\
551
+ attr[:len(private_str)] == private_str or\
552
+ not "_ready_for_the_deferred_magic_" in self.__dict__:
553
+ try:
554
+ self.__dict__[attr] = value
555
+ except KeyError as e:
556
+ raise AttributeError(*e.args, self.__dict__[private_str+"info"]) from e
557
+ #print("__setattr__: direct", attr)
558
+ return
559
+ if not self._deferred_live is None:
560
+ #print("__setattr__: live", attr)
561
+ return setattr(self._deferred_live, attr, value)
562
+ #print("__setattr__: act", attr)
563
+ return self._act("__setattr__", args=[attr, value], num_args=2, fmt="{parent}.{arg0}={arg1}")
564
+
565
+ def __delattr__(self, attr):
566
+ """ Deferred attribute access """
567
+ private_str = "_deferred_"
568
+ #print("__delattr__", attr)
569
+ if attr in self.__dict__ or\
570
+ attr[:2] == "__" or\
571
+ attr[:len(private_str)] == private_str or\
572
+ not "_ready_for_the_deferred_magic_" in self.__dict__:
573
+ #print("__delattr__: direct", attr)
574
+ try:
575
+ del self.__dict__[attr]
576
+ except KeyError as e:
577
+ raise AttributeError(*e.args, self.__dict__[private_str+"info"]) from e
578
+ return
579
+ if not self._deferred_live is None:
580
+ #print("__delattr__: live", attr)
581
+ return delattr(self._deferred_live, attr)
582
+ #print("__delattr__: act", attr)
583
+ return self._act("__delattr__", args=[attr], num_args=1, fmt="del {parent}.{arg0}")
584
+
585
+ @staticmethod
586
+ def _generate_handle( action : str,
587
+ return_deferred : bool = True, *,
588
+ num_args : int = None,
589
+ fmt : str = None ):
590
+ def act(self, *args, **kwargs):
591
+ r = self._act(action, args=args, kwargs=kwargs, num_args=num_args, fmt=fmt )
592
+ return r if return_deferred else self
593
+ act.__name__ = action
594
+ act.__doc__ = f"Deferred ``{action}`` action"
595
+ return act
596
+
597
+ # core functionality
598
+ __call__ = _generate_handle("__call__", num_args=None, fmt="{parent}({args})")
599
+ __setitem__ = _generate_handle("__setitem__", num_args=2, fmt="{parent}[{arg0}]={arg1}")
600
+ __getitem__ = _generate_handle("__getitem__", num_args=1, fmt="{parent}[{arg0}]")
601
+
602
+ # collections
603
+ __contains__ = _generate_handle("__contains__", num_args=1, fmt="({arg0} in {parent})")
604
+ __iter__ = _generate_handle("__iter__")
605
+ __len__ = _generate_handle("__len__", num_args=0, fmt="len({parent})")
606
+ __hash__ = _generate_handle("__hash__", num_args=0, fmt="hash({parent})")
607
+
608
+ # cannot implement __bool__, __str__, or, __repr__ as these
609
+ # have defined return types
610
+
611
+ # comparison operators
612
+ __neq__ = _generate_handle("__neq__", num_args=1, fmt="({parent}!={arg0})")
613
+ __eq__ = _generate_handle("__eq__", num_args=1, fmt="({parent}=={arg0})")
614
+ __ge__ = _generate_handle("__ge__", num_args=1, fmt="({parent}>={arg0})")
615
+ __le__ = _generate_handle("__le__", num_args=1, fmt="({parent}<={arg0})")
616
+ __gt__ = _generate_handle("__gt__", num_args=1, fmt="({parent}>{arg0})")
617
+ __lt__ = _generate_handle("__lt__", num_args=1, fmt="({parent}<{arg0})")
618
+
619
+ # i*
620
+ __ior__ = _generate_handle("__ior__", False, num_args=1, fmt="{parent}|={arg0}")
621
+ __iand__ = _generate_handle("__iand__", False, num_args=1, fmt="{parent}&={arg0}")
622
+ __ixor__ = _generate_handle("__iand__", False, num_args=1, fmt="{parent}^={arg0}")
623
+ __imod__ = _generate_handle("__imod__", False, num_args=1, fmt="{parent}%={arg0}")
624
+ __iadd__ = _generate_handle("__iadd__", False, num_args=1, fmt="{parent}+={arg0}")
625
+ __iconcat__ = _generate_handle("__iconcat__", False, num_args=1, fmt="{parent}+={arg0}")
626
+ __isub__ = _generate_handle("__isub__", False, num_args=1, fmt="{parent}-={arg0}")
627
+ __imul__ = _generate_handle("__imul__", False, num_args=1, fmt="{parent}*={arg0}")
628
+ __imatmul__ = _generate_handle("__imatmul__", False, num_args=1, fmt="{parent}@={arg0}")
629
+ __ipow__ = _generate_handle("__ipow__", False, num_args=1, fmt="{parent}**={arg0}")
630
+ __itruediv__ = _generate_handle("__itruediv__", False, num_args=1, fmt="{parent}/={arg0}")
631
+ __ifloordiv__ = _generate_handle("__ifloordiv__", False, num_args=1, fmt="{parent}//={arg0}")
632
+ # Py2 __idiv__ = _generate_handle("__idiv__", False, num_args=1, fmt="{parent}/={arg0}")
633
+
634
+ # binary
635
+ __or__ = _generate_handle("__or__", num_args=1, fmt="({parent}|{arg0})")
636
+ __and__ = _generate_handle("__and__", num_args=1, fmt="({parent}&{arg0})")
637
+ __xor__ = _generate_handle("__xor__", num_args=1, fmt="({parent}^{arg0})")
638
+ __mod__ = _generate_handle("__mod__", num_args=1, fmt="({parent}%{arg0})")
639
+ __add__ = _generate_handle("__add__", num_args=1, fmt="({parent}+{arg0})")
640
+ __concat__ = _generate_handle("__concat__", num_args=1, fmt="({parent}+{arg0})")
641
+ __sub__ = _generate_handle("__sub__", num_args=1, fmt="({parent}-{arg0})")
642
+ __mul__ = _generate_handle("__mul__", num_args=1, fmt="({parent}*{arg0})")
643
+ __pow__ = _generate_handle("__pow__", num_args=1, fmt="({parent}**{arg0})")
644
+ __matmul__ = _generate_handle("__matmul__", num_args=1, fmt="({parent}@{arg0})")
645
+ __truediv__ = _generate_handle("__truediv__", num_args=1, fmt="({parent}/{arg0})")
646
+ __floordiv__ = _generate_handle("__floordiv__", num_args=1, fmt="({parent}//{arg0})")
647
+ # Py2__div__ = _generate_handle("__div__", num_args=1, fmt="({parent}/{arg0})")
648
+
649
+ # rbinary
650
+ __ror__ = _generate_handle("__ror__", num_args=1, fmt="({arg0}|{parent})")
651
+ __rand__ = _generate_handle("__rand__", num_args=1, fmt="({arg0}&{parent})")
652
+ __rxor__ = _generate_handle("__rxor__", num_args=1, fmt="({arg0}^{parent})")
653
+ __rmod__ = _generate_handle("__rmod__", num_args=1, fmt="({arg0}%{parent})")
654
+ __radd__ = _generate_handle("__radd__", num_args=1, fmt="({arg0}+{parent})")
655
+ __rconcat__ = _generate_handle("__rconcat__", num_args=1, fmt="({arg0}+{parent})")
656
+ __rsub__ = _generate_handle("__rsub__", num_args=1, fmt="({arg0}-{parent})")
657
+ __rmul__ = _generate_handle("__rmul__", num_args=1, fmt="({arg0}*{parent})")
658
+ __rpow__ = _generate_handle("__rpow__", num_args=1, fmt="({arg0}**{parent})")
659
+ __rmatmul__ = _generate_handle("__rmatmul__", num_args=1, fmt="({arg0}@{parent})")
660
+ __rtruediv__ = _generate_handle("__rtruediv__", num_args=1, fmt="({arg0}/{parent})")
661
+ __rfloordiv__ = _generate_handle("__rfloordiv__", num_args=1,fmt="({arg0}//{parent})")
662
+ # Py2__rdiv__ = _generate_handle("__rdiv__", num_args=1, fmt="({arg0}/{parent})")
663
+
664
+ class Deferred(DeferredAction):
665
+ """
666
+ Create a :class:`cdxcore.deferred.DeferredAction` object to keep track
667
+ of a dependency tree of a sequence of actions performed an object
668
+ that does not yet exist.
669
+
670
+ See :class:`cdxcore.deferred.DeferredAction` for a comprehensive discussion.
671
+
672
+ Parameters
673
+ ----------
674
+ info : str
675
+ Descriptive name of the usually not-yet-created object deferred actions
676
+ will act upon.
677
+
678
+ max_err_len : int, optional
679
+ Maximum length of output information. The default is 100.
680
+
681
+ Returns
682
+ -------
683
+ :class:`cdxcore.deferred.DeferredAction`
684
+ A deferred action.
685
+ """
686
+
687
+ def __init__(self, info : str,
688
+ max_err_len : int = 100 ):
689
+ """ Init """
690
+ self._deferred_src_dependants = []
691
+
692
+ DeferredAction.__init__(
693
+ self,
694
+ info=info,
695
+ action="",
696
+ parent=None,
697
+ src_dependants=self._deferred_src_dependants,
698
+ sources={},
699
+ args=[],
700
+ kwargs={},
701
+ max_err_len=max_err_len )
702
+
703
+ assert self._deferred_live is None
704
+
705
+
706
+ def deferred_resolve(self, element, verbose : Context = None):
707
+ """
708
+ Resolve the top level deferred element.
709
+
710
+ The top level itself is "resolved" by assigning it
711
+ concrete object. It will then iterate through all dependent
712
+ deferred actions and attempt to solve those.
713
+
714
+ This can fail if those calculations are dependent on
715
+ other :class:`cdxcore.deferred.Deferred` which have not been resolved
716
+ yet. In this case a :class:`cdxcore.deferred.ResolutionDependencyError`
717
+ exception will be raised.
718
+
719
+ Parameters
720
+ ----------
721
+ element :
722
+ The object to resolve ``self`` with.
723
+
724
+ verbose : :class:`cdxcore.verbose.Context`
725
+ Can be used to provide runtime information on which
726
+ deferred actions are being resolved. Defaults to ``None``
727
+ which surpresses all output.
728
+
729
+ Returns
730
+ -------
731
+ element
732
+ The input element, so you can chain ``resolve()``.
733
+ """
734
+
735
+ verify( self._deferred_live is None, lambda : f"Called resolve() twice on '{self._deferred_info}'.")
736
+ verify( not element is None, lambda : f"You cannot resolve '{self._deferred_info}' with an empty 'element'")
737
+ verbose = Context.quiet if verbose is None else verbose
738
+
739
+ verbose.write(f"{self._deferred_info} -> '{qualified_name(element)}' : {self._deferred_to_str(element)}")
740
+ self._deferred_live = element
741
+ self._deferred_was_resolved = True
742
+
743
+ while len(self._deferred_src_dependants) > 0:
744
+ daction = self._deferred_src_dependants.pop(0)
745
+ dlevel = daction._deferred_depth
746
+ daction._deferred_resolve( verbose=verbose(dlevel) )
747
+ del daction
748
+
749
+ gc.collect()
750
+ return element
751
+
752
+