Skip to content

Commit

Permalink
Fix flake8 errors
Browse files Browse the repository at this point in the history
  • Loading branch information
natifridman committed Sep 19, 2023
1 parent 1eec599 commit 3253c25
Show file tree
Hide file tree
Showing 21 changed files with 102 additions and 12 deletions.
2 changes: 2 additions & 0 deletions src/vse_sync_pp/analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
Config,
)


def main():
"""Analyze log messages from a single source.
Expand Down Expand Up @@ -58,5 +59,6 @@ def main():
if not print_loj(dct):
sys.exit(1)


if __name__ == '__main__':
main()
2 changes: 0 additions & 2 deletions src/vse_sync_pp/analyzers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

"""Analyzers"""

from .analyzer import Config

from . import (
gnss,
ppsdpll,
Expand Down
26 changes: 26 additions & 0 deletions src/vse_sync_pp/analyzers/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,20 @@

from ..requirements import REQUIREMENTS


class Config():
"""Analyzer configuration"""
def __init__(self, filename=None, requirements=None, parameters=None):
self._filename = filename
self._requirements = requirements
self._parameters = parameters

def _reason(self, reason):
"""Return `reason`, extended if this config is from a file."""
if self._filename is None:
return reason
return reason + f' in config file {self._filename}'

def requirement(self, key):
"""Return the value at `key` in this configuration's requirements.
Expand All @@ -34,6 +37,7 @@ def requirement(self, key):
else:
reason = f'unknown requirement {key} in {self._requirements}'
raise KeyError(self._reason(reason)) from exc

def parameter(self, key):
"""Return the value at `key` in this configuration's parameters.
Expand All @@ -47,17 +51,20 @@ def parameter(self, key):
except KeyError as exc:
reason = f'unknown parameter {key}'
raise KeyError(self._reason(reason)) from exc

@classmethod
def from_yaml(cls, filename, encoding='utf-8'):
"""Build configuration from YAML file at `filename`"""
with open(filename, encoding=encoding) as fid:
dct = dict(yaml.safe_load(fid.read()))
return cls(filename, dct.get('requirements'), dct.get('parameters'))


class CollectionIsClosed(Exception):
"""Data collection was closed while collecting data"""
# empty


class Analyzer():
"""A base class providing common analyzer functionality"""
def __init__(self, config):
Expand All @@ -69,11 +76,13 @@ def __init__(self, config):
self._timestamp = None
self._duration = None
self._analysis = None

def collect(self, *rows):
"""Collect data from `rows`"""
if self._rows is None:
raise CollectionIsClosed()
self._rows += rows

def prepare(self, rows): # pylint: disable=no-self-use
"""Return (columns, records) from collected data `rows`
Expand All @@ -83,24 +92,28 @@ def prepare(self, rows): # pylint: disable=no-self-use
If `records` is an empty sequence, then `columns` is also empty.
"""
return (rows[0]._fields, rows) if rows else ((), ())

def close(self):
"""Close data collection"""
if self._data is None:
(columns, records) = self.prepare(self._rows)
self._data = DataFrame.from_records(records, columns=columns)
self._rows = None

def _test(self):
"""Close data collection and test collected data"""
if self._result is None:
self.close()
(self._result, self._reason) = self.test(self._data)

def _explain(self):
"""Close data collection and explain collected data"""
if self._analysis is None:
self.close()
self._analysis = self.explain(self._data)
self._timestamp = self._analysis.pop('timestamp', None)
self._duration = self._analysis.pop('duration', None)

def _timestamp_from_dec(self, dec):
"""Return an absolute timestamp or decimal timestamp from `dec`.
Expand All @@ -125,31 +138,37 @@ def _timestamp_from_dec(self, dec):
return dtv.isoformat()
# relative time
return dec

@property
def result(self):
"""The boolean result from this analyzer's test of the collected data"""
self._test()
return self._result

@property
def reason(self):
"""A string qualifying :attr:`result` (or None if unqualified)"""
self._test()
return self._reason

@property
def timestamp(self):
"""The ISO 8601 date-time timestamp, when the test started"""
self._explain()
return self._timestamp

@property
def duration(self):
"""The test duration in seconds"""
self._explain()
return self._duration

@property
def analysis(self):
"""A structured analysis of the collected data"""
self._explain()
return self._analysis

@staticmethod
def _statistics(data, units, ndigits=3):
"""Return a dict of statistics for `data`, rounded to `ndigits`"""
Expand All @@ -170,6 +189,7 @@ def _round(val):
'stddev': _round(data.std()),
'variance': _round(data.var()),
}

def test(self, data):
"""This analyzer's test of the collected `data`.
Expand All @@ -178,17 +198,20 @@ def test(self, data):
if unqualified).
"""
raise NotImplementedError

def explain(self, data):
"""Return a structured analysis of the collected `data`"""
raise NotImplementedError


class TimeErrorAnalyzerBase(Analyzer):
"""Analyze time error.
Derived classes must override class attribute `locked`, specifying a
frozenset of values representing locked states.
"""
locked = frozenset()

def __init__(self, config):
super().__init__(config)
# required system time output accuracy
Expand All @@ -201,6 +224,7 @@ def __init__(self, config):
self._transient = config.parameter('transient-period/s')
# minimum test duration for a valid test
self._duration_min = config.parameter('min-test-duration/s')

def prepare(self, rows):
idx = 0
try:
Expand All @@ -213,6 +237,7 @@ def prepare(self, rows):
break
idx += 1
return super().prepare(rows[idx:])

def test(self, data):
if len(data) == 0:
return (False, "no data")
Expand All @@ -227,6 +252,7 @@ def test(self, data):
if len(data) - 1 < self._duration_min:
return (False, "short test samples")
return (True, None)

def explain(self, data):
if len(data) == 0:
return {}
Expand Down
1 change: 1 addition & 0 deletions src/vse_sync_pp/analyzers/gnss.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from .analyzer import TimeErrorAnalyzerBase


class TimeErrorAnalyzer(TimeErrorAnalyzerBase):
"""Analyze time error"""
id_ = 'gnss/time-error'
Expand Down
1 change: 1 addition & 0 deletions src/vse_sync_pp/analyzers/phc2sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from .analyzer import TimeErrorAnalyzerBase


class TimeErrorAnalyzer(TimeErrorAnalyzerBase):
"""Analyze time error"""
id_ = 'phc2sys/time-error'
Expand Down
2 changes: 2 additions & 0 deletions src/vse_sync_pp/analyzers/ppsdpll.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from .analyzer import TimeErrorAnalyzerBase


class TimeErrorAnalyzer(TimeErrorAnalyzerBase):
"""Analyze DPLL time error"""
id_ = 'ppsdpll/time-error'
Expand All @@ -18,6 +19,7 @@ class TimeErrorAnalyzer(TimeErrorAnalyzerBase):
# 'state' unlocked but operational
# 4 = DPLL_HOLDOVER
locked = frozenset({2, 3})

def prepare(self, rows):
return super().prepare([
r._replace(terror=float(r.terror)) for r in rows
Expand Down
1 change: 1 addition & 0 deletions src/vse_sync_pp/analyzers/ts2phc.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from .analyzer import TimeErrorAnalyzerBase


class TimeErrorAnalyzer(TimeErrorAnalyzerBase):
"""Analyze time error"""
id_ = 'ts2phc/time-error'
Expand Down
3 changes: 3 additions & 0 deletions src/vse_sync_pp/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import json
from decimal import Decimal


def open_input(filename, encoding='utf-8', **kwargs):
"""Return a context manager for reading from `filename`.
Expand All @@ -17,6 +18,7 @@ def open_input(filename, encoding='utf-8', **kwargs):
return nullcontext(sys.stdin)
return open(filename, encoding=encoding, **kwargs)


class JsonEncoder(json.JSONEncoder):
"""A JSON encoder accepting :class:`Decimal` values"""
def default(self, o):
Expand All @@ -25,6 +27,7 @@ def default(self, o):
return float(o)
return super().default(o)


def print_loj(val, encoder_cls=JsonEncoder, flush=True):
"""Print value `val` as a line of JSON and, optionally, `flush` stdout.
Expand Down
2 changes: 2 additions & 0 deletions src/vse_sync_pp/demux.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from .parsers import PARSERS
from .source import muxed


def main():
"""Demultiplex log messages from a single multiplexed source.
Expand All @@ -37,5 +38,6 @@ def main():
if not print_loj(data):
sys.exit(1)


if __name__ == '__main__':
main()
2 changes: 2 additions & 0 deletions src/vse_sync_pp/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from .parsers import PARSERS


def main():
"""Parse log messages from a single source.
Expand Down Expand Up @@ -39,5 +40,6 @@ def main():
if not print_loj(data):
sys.exit(1)


if __name__ == '__main__':
main()
3 changes: 3 additions & 0 deletions src/vse_sync_pp/parsers/dpll.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@

from .parser import (Parser, parse_timestamp, parse_decimal)


class TimeErrorParser(Parser):
"""Parse Time Error from a dpll CSV sample"""
id_ = 'dpll/time-error'
elems = ('timestamp', 'eecstate', 'state', 'terror')
y_name = 'terror'
parsed = namedtuple('Parsed', elems)

def make_parsed(self, elems):
if len(elems) < len(self.elems):
raise ValueError(elems)
Expand All @@ -20,6 +22,7 @@ def make_parsed(self, elems):
state = int(elems[2])
terror = parse_decimal(elems[3])
return self.parsed(timestamp, eecstate, state, terror)

def parse_line(self, line):
# DPLL samples come from a fixed format CSV file
return self.make_parsed(line.split(','))
3 changes: 3 additions & 0 deletions src/vse_sync_pp/parsers/gnss.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from .parser import (Parser, parse_timestamp)


class TimeErrorParser(Parser):
"""Parse time error from a GNSS CSV sample"""
id_ = 'gnss/time-error'
Expand All @@ -19,13 +20,15 @@ class TimeErrorParser(Parser):
elems = ('timestamp', 'state', 'terror')
y_name = 'terror'
parsed = namedtuple('Parsed', elems)

def make_parsed(self, elems):
if len(elems) < len(self.elems):
raise ValueError(elems)
timestamp = parse_timestamp(elems[0])
state = int(elems[1])
terror = int(elems[2])
return self.parsed(timestamp, state, terror)

def parse_line(self, line):
# GNSS samples come from a fixed format CSV file
return self.make_parsed(line.split(','))
Loading

0 comments on commit 3253c25

Please sign in to comment.