Guest User

Untitled

a guest
Dec 30th, 2020
156
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 22.12 KB | None | 0 0
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8; py-indent-offset:4 -*-
  3. ###############################################################################
  4. #
  5. # Copyright (C) 2015-2020 Daniel Rodriguez
  6. #
  7. # This program is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. #
  20. ###############################################################################
  21. '''
  22.  
  23. .. module:: lineroot
  24.  
  25. Defines LineSeries and Descriptors inside of it for classes that hold multiple
  26. lines at once.
  27.  
  28. .. moduleauthor:: Daniel Rodriguez
  29.  
  30. '''
  31. from __future__ import (absolute_import, division, print_function,
  32. unicode_literals)
  33.  
  34. import sys
  35.  
  36. from .utils.py3 import map, range, string_types, with_metaclass
  37.  
  38. from .linebuffer import LineBuffer, LineActions, LinesOperation, LineDelay, NAN
  39. from .lineroot import LineRoot, LineSingle, LineMultiple
  40. from .metabase import AutoInfoClass
  41. from . import metabase
  42.  
  43.  
  44. class LineAlias(object):
  45. ''' Descriptor class that store a line reference and returns that line
  46. from the owner
  47.  
  48. Keyword Args:
  49. line (int): reference to the line that will be returned from
  50. owner's *lines* buffer
  51.  
  52. As a convenience the __set__ method of the descriptor is used not set
  53. the *line* reference because this is a constant along the live of the
  54. descriptor instance, but rather to set the value of the *line* at the
  55. instant '0' (the current one)
  56. '''
  57.  
  58. def __init__(self, line):
  59. self.line = line
  60.  
  61. def __get__(self, obj, cls=None):
  62. return obj.lines[self.line]
  63.  
  64. def __set__(self, obj, value):
  65. '''
  66. A line cannot be "set" once it has been created. But the values
  67. inside the line can be "set". This is achieved by adding a binding
  68. to the line inside "value"
  69. '''
  70. if isinstance(value, LineMultiple):
  71. value = value.lines[0]
  72.  
  73. # If the now for sure, LineBuffer 'value' is not a LineActions the
  74. # binding below could kick-in too early in the chain writing the value
  75. # into a not yet "forwarded" line, effectively writing the value 1
  76. # index too early and breaking the functionality (all in next mode)
  77. # Hence the need to transform it into a LineDelay object of null delay
  78. if not isinstance(value, LineActions):
  79. value = value(0)
  80.  
  81. value.addbinding(obj.lines[self.line])
  82.  
  83.  
  84. class Lines(object):
  85. '''
  86. Defines an "array" of lines which also has most of the interface of
  87. a LineBuffer class (forward, rewind, advance...).
  88.  
  89. This interface operations are passed to the lines held by self
  90.  
  91. The class can autosubclass itself (_derive) to hold new lines keeping them
  92. in the defined order.
  93. '''
  94. _getlinesbase = classmethod(lambda cls: ())
  95. _getlines = classmethod(lambda cls: ())
  96. _getlinesextra = classmethod(lambda cls: 0)
  97. _getlinesextrabase = classmethod(lambda cls: 0)
  98.  
  99. @classmethod
  100. def _derive(cls, name, lines, extralines, otherbases, linesoverride=False,
  101. lalias=None):
  102. '''
  103. Creates a subclass of this class with the lines of this class as
  104. initial input for the subclass. It will include num "extralines" and
  105. lines present in "otherbases"
  106.  
  107. "name" will be used as the suffix of the final class name
  108.  
  109. "linesoverride": if True the lines of all bases will be discarded and
  110. the baseclass will be the topmost class "Lines". This is intended to
  111. create a new hierarchy
  112. '''
  113. obaseslines = ()
  114. obasesextralines = 0
  115.  
  116. for otherbase in otherbases:
  117. if isinstance(otherbase, tuple):
  118. obaseslines += otherbase
  119. else:
  120. obaseslines += otherbase._getlines()
  121. obasesextralines += otherbase._getlinesextra()
  122.  
  123. if not linesoverride:
  124. baselines = cls._getlines() + obaseslines
  125. baseextralines = cls._getlinesextra() + obasesextralines
  126. else: # overriding lines, skip anything from baseclasses
  127. baselines = ()
  128. baseextralines = 0
  129.  
  130. clslines = baselines + lines
  131. clsextralines = baseextralines + extralines
  132. lines2add = obaseslines + lines
  133.  
  134. # str for Python 2/3 compatibility
  135. basecls = cls if not linesoverride else Lines
  136.  
  137. newcls = type(str(cls.__name__ + '_' + name), (basecls,), {})
  138. clsmodule = sys.modules[cls.__module__]
  139. newcls.__module__ = cls.__module__
  140. setattr(clsmodule, str(cls.__name__ + '_' + name), newcls)
  141.  
  142. setattr(newcls, '_getlinesbase', classmethod(lambda cls: baselines))
  143. setattr(newcls, '_getlines', classmethod(lambda cls: clslines))
  144.  
  145. setattr(newcls, '_getlinesextrabase',
  146. classmethod(lambda cls: baseextralines))
  147. setattr(newcls, '_getlinesextra',
  148. classmethod(lambda cls: clsextralines))
  149.  
  150. l2start = len(cls._getlines()) if not linesoverride else 0
  151. l2add = enumerate(lines2add, start=l2start)
  152. l2alias = {} if lalias is None else lalias._getkwargsdefault()
  153. for line, linealias in l2add:
  154. if not isinstance(linealias, string_types):
  155. # a tuple or list was passed, 1st is name
  156. linealias = linealias[0]
  157.  
  158. desc = LineAlias(line) # keep a reference below
  159. setattr(newcls, linealias, desc)
  160.  
  161. # Create extra aliases for the given name, checking if the names is in
  162. # l2alias (which is from the argument lalias and comes from the
  163. # directive 'linealias', hence the confusion here (the LineAlias come
  164. # from the directive 'lines')
  165. for line, linealias in enumerate(newcls._getlines()):
  166. if not isinstance(linealias, string_types):
  167. # a tuple or list was passed, 1st is name
  168. linealias = linealias[0]
  169.  
  170. desc = LineAlias(line) # keep a reference below
  171. if linealias in l2alias:
  172. extranames = l2alias[linealias]
  173. if isinstance(linealias, string_types):
  174. extranames = [extranames]
  175.  
  176. for ename in extranames:
  177. setattr(newcls, ename, desc)
  178.  
  179. return newcls
  180.  
  181. @classmethod
  182. def _getlinealias(cls, i):
  183. '''
  184. Return the alias for a line given the index
  185. '''
  186. lines = cls._getlines()
  187. if i >= len(lines):
  188. return ''
  189. linealias = lines[i]
  190. return linealias
  191.  
  192. @classmethod
  193. def getlinealiases(cls):
  194. return cls._getlines()
  195.  
  196. def itersize(self):
  197. return iter(self.lines[0:self.size()])
  198.  
  199. def __init__(self, initlines=None):
  200. '''
  201. Create the lines recording during "_derive" or else use the
  202. provided "initlines"
  203. '''
  204. self.lines = list()
  205. for line, linealias in enumerate(self._getlines()):
  206. kwargs = dict()
  207. self.lines.append(LineBuffer(**kwargs))
  208.  
  209. # Add the required extralines
  210. for i in range(self._getlinesextra()):
  211. if not initlines:
  212. self.lines.append(LineBuffer())
  213. else:
  214. self.lines.append(initlines[i])
  215.  
  216. def __len__(self):
  217. '''
  218. Proxy line operation
  219. '''
  220. return len(self.lines[0])
  221.  
  222. def size(self):
  223. return len(self.lines) - self._getlinesextra()
  224.  
  225. def fullsize(self):
  226. return len(self.lines)
  227.  
  228. def extrasize(self):
  229. return self._getlinesextra()
  230.  
  231. def __getitem__(self, line):
  232. '''
  233. Proxy line operation
  234. '''
  235. return self.lines[line]
  236.  
  237. def get(self, ago=0, size=1, line=0):
  238. '''
  239. Proxy line operation
  240. '''
  241. return self.lines[line].get(ago, size=size)
  242.  
  243. def __setitem__(self, line, value):
  244. '''
  245. Proxy line operation
  246. '''
  247. setattr(self, self._getlinealias(line), value)
  248.  
  249. def forward(self, value=NAN, size=1):
  250. '''
  251. Proxy line operation
  252. '''
  253. for line in self.lines:
  254. line.forward(value, size=size)
  255.  
  256. def backwards(self, size=1, force=False):
  257. '''
  258. Proxy line operation
  259. '''
  260. for line in self.lines:
  261. line.backwards(size, force=force)
  262.  
  263. def rewind(self, size=1):
  264. '''
  265. Proxy line operation
  266. '''
  267. for line in self.lines:
  268. line.rewind(size)
  269.  
  270. def extend(self, value=NAN, size=0):
  271. '''
  272. Proxy line operation
  273. '''
  274. for line in self.lines:
  275. line.extend(value, size)
  276.  
  277. def reset(self):
  278. '''
  279. Proxy line operation
  280. '''
  281. for line in self.lines:
  282. line.reset()
  283.  
  284. def home(self):
  285. '''
  286. Proxy line operation
  287. '''
  288. for line in self.lines:
  289. line.home()
  290.  
  291. def advance(self, size=1):
  292. '''
  293. Proxy line operation
  294. '''
  295. for line in self.lines:
  296. line.advance(size)
  297.  
  298. def buflen(self, line=0):
  299. '''
  300. Proxy line operation
  301. '''
  302. return self.lines[line].buflen()
  303.  
  304.  
  305. class MetaLineSeries(LineMultiple.__class__):
  306. '''
  307. Dirty job manager for a LineSeries
  308.  
  309. - During __new__ (class creation), it reads "lines", "plotinfo",
  310. "plotlines" class variable definitions and turns them into
  311. Classes of type Lines or AutoClassInfo (plotinfo/plotlines)
  312.  
  313. - During "new" (instance creation) the lines/plotinfo/plotlines
  314. classes are substituted in the instance with instances of the
  315. aforementioned classes and aliases are added for the "lines" held
  316. in the "lines" instance
  317.  
  318. Additionally and for remaining kwargs, these are matched against
  319. args in plotinfo and if existent are set there and removed from kwargs
  320.  
  321. Remember that this Metaclass has a MetaParams (from metabase)
  322. as root class and therefore "params" defined for the class have been
  323. removed from kwargs at an earlier state
  324. '''
  325.  
  326. def __new__(meta, name, bases, dct):
  327. '''
  328. Intercept class creation, identifiy lines/plotinfo/plotlines class
  329. attributes and create corresponding classes for them which take over
  330. the class attributes
  331. '''
  332.  
  333. # Get the aliases - don't leave it there for subclasses
  334. aliases = dct.setdefault('alias', ())
  335. aliased = dct.setdefault('aliased', '')
  336.  
  337. # Remove the line definition (if any) from the class creation
  338. linesoverride = dct.pop('linesoverride', False)
  339. newlines = dct.pop('lines', ())
  340. extralines = dct.pop('extralines', 0)
  341.  
  342. # remove the new plotinfo/plotlines definition if any
  343. newlalias = dict(dct.pop('linealias', {}))
  344.  
  345. # remove the new plotinfo/plotlines definition if any
  346. newplotinfo = dict(dct.pop('plotinfo', {}))
  347. newplotlines = dict(dct.pop('plotlines', {}))
  348.  
  349. # Create the class - pulling in any existing "lines"
  350. cls = super(MetaLineSeries, meta).__new__(meta, name, bases, dct)
  351.  
  352. # Check the line aliases before creating the lines
  353. lalias = getattr(cls, 'linealias', AutoInfoClass)
  354. oblalias = [x.linealias for x in bases[1:] if hasattr(x, 'linealias')]
  355. cls.linealias = la = lalias._derive('la_' + name, newlalias, oblalias)
  356.  
  357. # Get the actual lines or a default
  358. lines = getattr(cls, 'lines', Lines)
  359.  
  360. # Create a subclass of the lines class with our name and newlines
  361. # and put it in the class
  362. morebaseslines = [x.lines for x in bases[1:] if hasattr(x, 'lines')]
  363. cls.lines = lines._derive(name, newlines, extralines, morebaseslines,
  364. linesoverride, lalias=la)
  365.  
  366. # Get a copy from base class plotinfo/plotlines (created with the
  367. # class or set a default)
  368. plotinfo = getattr(cls, 'plotinfo', AutoInfoClass)
  369. plotlines = getattr(cls, 'plotlines', AutoInfoClass)
  370.  
  371. # Create a plotinfo/plotlines subclass and set it in the class
  372. morebasesplotinfo = \
  373. [x.plotinfo for x in bases[1:] if hasattr(x, 'plotinfo')]
  374. cls.plotinfo = plotinfo._derive('pi_' + name, newplotinfo,
  375. morebasesplotinfo)
  376.  
  377. # Before doing plotline newlines have been added and no plotlineinfo
  378. # is there add a default
  379. for line in newlines:
  380. newplotlines.setdefault(line, dict())
  381.  
  382. morebasesplotlines = \
  383. [x.plotlines for x in bases[1:] if hasattr(x, 'plotlines')]
  384. cls.plotlines = plotlines._derive(
  385. 'pl_' + name, newplotlines, morebasesplotlines, recurse=True)
  386.  
  387. # create declared class aliases (a subclass with no modifications)
  388. for alias in aliases:
  389. newdct = {'__doc__': cls.__doc__,
  390. '__module__': cls.__module__,
  391. 'aliased': cls.__name__}
  392.  
  393. if not isinstance(alias, string_types):
  394. # a tuple or list was passed, 1st is name, 2nd plotname
  395. aliasplotname = alias[1]
  396. alias = alias[0]
  397. newdct['plotinfo'] = dict(plotname=aliasplotname)
  398.  
  399. newcls = type(str(alias), (cls,), newdct)
  400. clsmodule = sys.modules[cls.__module__]
  401. setattr(clsmodule, alias, newcls)
  402.  
  403. # return the class
  404. return cls
  405.  
  406. def donew(cls, *args, **kwargs):
  407. '''
  408. Intercept instance creation, take over lines/plotinfo/plotlines
  409. class attributes by creating corresponding instance variables and add
  410. aliases for "lines" and the "lines" held within it
  411. '''
  412. # _obj.plotinfo shadows the plotinfo (class) definition in the class
  413. plotinfo = cls.plotinfo()
  414.  
  415. for pname, pdef in cls.plotinfo._getitems():
  416. setattr(plotinfo, pname, kwargs.pop(pname, pdef))
  417.  
  418. # Create the object and set the params in place
  419. _obj, args, kwargs = super(MetaLineSeries, cls).donew(*args, **kwargs)
  420.  
  421. # set the plotinfo member in the class
  422. _obj.plotinfo = plotinfo
  423.  
  424. # _obj.lines shadows the lines (class) definition in the class
  425. _obj.lines = cls.lines()
  426.  
  427. # _obj.plotinfo shadows the plotinfo (class) definition in the class
  428. _obj.plotlines = cls.plotlines()
  429.  
  430. # add aliases for lines and for the lines class itself
  431. _obj.l = _obj.lines
  432. if _obj.lines.fullsize():
  433. _obj.line = _obj.lines[0]
  434.  
  435. for l, line in enumerate(_obj.lines):
  436. setattr(_obj, 'line_%s' % l, _obj._getlinealias(l))
  437. setattr(_obj, 'line_%d' % l, line)
  438. setattr(_obj, 'line%d' % l, line)
  439.  
  440. # Parameter values have now been set before __init__
  441. return _obj, args, kwargs
  442.  
  443.  
  444. class LineSeries(with_metaclass(MetaLineSeries, LineMultiple)):
  445. plotinfo = dict(
  446. plot=True,
  447. plotmaster=None,
  448. legendloc=None,
  449. )
  450.  
  451. csv = True
  452.  
  453. @property
  454. def array(self):
  455. return self.lines[0].array
  456.  
  457. def __getattr__(self, name):
  458. # to refer to line by name directly if the attribute was not found
  459. # in this object if we set an attribute in this object it will be
  460. # found before we end up here
  461. return getattr(self.lines, name)
  462.  
  463. def __len__(self):
  464. return len(self.lines)
  465.  
  466. def __getitem__(self, key):
  467. return self.lines[0][key]
  468.  
  469. def __setitem__(self, key, value):
  470. setattr(self.lines, self.lines._getlinealias(key), value)
  471.  
  472. def __init__(self, *args, **kwargs):
  473. # if any args, kwargs make it up to here, something is broken
  474. # defining a __init__ guarantees the existence of im_func to findbases
  475. # in lineiterator later, because object.__init__ has no im_func
  476. # (object has slots)
  477. super(LineSeries, self).__init__()
  478. pass
  479.  
  480. def plotlabel(self):
  481. label = self.plotinfo.plotname or self.__class__.__name__
  482. sublabels = self._plotlabel()
  483. if sublabels:
  484. for i, sublabel in enumerate(sublabels):
  485. # if isinstance(sublabel, LineSeries): ## DOESN'T WORK ???
  486. if hasattr(sublabel, 'plotinfo'):
  487. try:
  488. s = sublabel.plotinfo.plotname
  489. except:
  490. s = ''
  491.  
  492. sublabels[i] = s or sublabel.__name__
  493.  
  494. label += ' (%s)' % ', '.join(map(str, sublabels))
  495. return label
  496.  
  497. def _plotlabel(self):
  498. return self.params._getvalues()
  499.  
  500. def _getline(self, line, minusall=False):
  501. if isinstance(line, string_types):
  502. lineobj = getattr(self.lines, line)
  503. else:
  504. if line == -1: # restore original api behavior - default -> 0
  505. if minusall: # minus means ... all lines
  506. return None
  507. line = 0
  508. lineobj = self.lines[line]
  509.  
  510. return lineobj
  511.  
  512. def __call__(self, ago=None, line=-1):
  513. '''Returns either a delayed verison of itself in the form of a
  514. LineDelay object or a timeframe adapting version with regards to a ago
  515.  
  516. Param: ago (default: None)
  517.  
  518. If ago is None or an instance of LineRoot (a lines object) the
  519. returned valued is a LineCoupler instance
  520.  
  521. If ago is anything else, it is assumed to be an int and a LineDelay
  522. object will be returned
  523.  
  524. Param: line (default: -1)
  525. If a LinesCoupler will be returned ``-1`` means to return a
  526. LinesCoupler which adapts all lines of the current LineMultiple
  527. object. Else the appropriate line (referenced by name or index) will
  528. be LineCoupled
  529.  
  530. If a LineDelay object will be returned, ``-1`` is the same as ``0``
  531. (to retain compatibility with the previous default value of 0). This
  532. behavior will change to return all existing lines in a LineDelayed
  533. form
  534.  
  535. The referenced line (index or name) will be LineDelayed
  536. '''
  537. from .lineiterator import LinesCoupler # avoid circular import
  538.  
  539. if ago is None or isinstance(ago, LineRoot):
  540. args = [self, ago]
  541. lineobj = self._getline(line, minusall=True)
  542. if lineobj is not None:
  543. args[0] = lineobj
  544.  
  545. return LinesCoupler(*args, _ownerskip=self)
  546.  
  547. # else -> assume type(ago) == int -> return LineDelay object
  548. return LineDelay(self._getline(line), ago, _ownerskip=self)
  549.  
  550. # The operations below have to be overriden to make sure subclasses can
  551. # reach them using "super" which will not call __getattr__ and
  552. # LineSeriesStub (see below) already uses super
  553. def forward(self, value=NAN, size=1):
  554. self.lines.forward(value, size)
  555.  
  556. def backwards(self, size=1, force=False):
  557. self.lines.backwards(size, force=force)
  558.  
  559. def rewind(self, size=1):
  560. self.lines.rewind(size)
  561.  
  562. def extend(self, value=NAN, size=0):
  563. self.lines.extend(value, size)
  564.  
  565. def reset(self):
  566. self.lines.reset()
  567.  
  568. def home(self):
  569. self.lines.home()
  570.  
  571. def advance(self, size=1):
  572. self.lines.advance(size)
  573.  
  574.  
  575. class LineSeriesStub(LineSeries):
  576. '''Simulates a LineMultiple object based on LineSeries from a single line
  577.  
  578. The index management operations are overriden to take into account if the
  579. line is a slave, ie:
  580.  
  581. - The line reference is a line from many in a LineMultiple object
  582. - Both the LineMultiple object and the Line are managed by the same
  583. object
  584.  
  585. Were slave not to be taken into account, the individual line would for
  586. example be advanced twice:
  587.  
  588. - Once under when the LineMultiple object is advanced (because it
  589. advances all lines it is holding
  590. - Again as part of the regular management of the object holding it
  591. '''
  592.  
  593. extralines = 1
  594.  
  595. def __init__(self, line, slave=False):
  596. self.lines = self.__class__.lines(initlines=[line])
  597. # give a change to find the line owner (for plotting at least)
  598. self.owner = self._owner = line._owner
  599. self._minperiod = line._minperiod
  600. self.slave = slave
  601.  
  602. # Only execute the operations below if the object is not a slave
  603. def forward(self, value=NAN, size=1):
  604. if not self.slave:
  605. super(LineSeriesStub, self).forward(value, size)
  606.  
  607. def backwards(self, size=1, force=False):
  608. if not self.slave:
  609. super(LineSeriesStub, self).backwards(size, force=force)
  610.  
  611. def rewind(self, size=1):
  612. if not self.slave:
  613. super(LineSeriesStub, self).rewind(size)
  614.  
  615. def extend(self, value=NAN, size=0):
  616. if not self.slave:
  617. super(LineSeriesStub, self).extend(value, size)
  618.  
  619. def reset(self):
  620. if not self.slave:
  621. super(LineSeriesStub, self).reset()
  622.  
  623. def home(self):
  624. if not self.slave:
  625. super(LineSeriesStub, self).home()
  626.  
  627. def advance(self, size=1):
  628. if not self.slave:
  629. super(LineSeriesStub, self).advance(size)
  630.  
  631. def qbuffer(self):
  632. if not self.slave:
  633. super(LineSeriesStub, self).qbuffer()
  634.  
  635. def minbuffer(self, size):
  636. if not self.slave:
  637. super(LineSeriesStub, self).minbuffer(size)
  638.  
  639.  
  640. def LineSeriesMaker(arg, slave=False):
  641. if isinstance(arg, LineSeries):
  642. return arg
  643.  
  644. return LineSeriesStub(arg, slave=slave)
  645.  
Add Comment
Please, Sign In to add comment