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

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