Skip to content

Commit

Permalink
format writer interface
Browse files Browse the repository at this point in the history
  • Loading branch information
kouk committed Jul 26, 2017
1 parent 4effa3f commit ac105d5
Show file tree
Hide file tree
Showing 13 changed files with 133 additions and 88 deletions.
5 changes: 3 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
- id: debug-statements
- id: requirements-txt-fixer
- id: name-tests-test
args: ['--django']
args:
- --django
- id: pretty-format-json
args:
- --autofix
- --autofix
- repo: https://github.com/guykisel/prospector-mirror.git
sha: e0bfe1f71f9b6c81c5b8b8ffece9d89b0142c4b8
hooks:
Expand Down
15 changes: 3 additions & 12 deletions src/uptime_report/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
"""
from __future__ import print_function, unicode_literals

import json
import logging
import sys

Expand All @@ -23,10 +22,8 @@
from uptime_report._version import get_versions
from uptime_report.backends import get_backend, list_backends
from uptime_report.config import read_config, write_config
from uptime_report.format import Format, with_format
from uptime_report.outage import (encode_outage, get_downtime_in_seconds,
get_outages, print_outages,
write_outages_csv)
from uptime_report.format import with_format
from uptime_report.outage import Outage, get_downtime_in_seconds, get_outages
from uptime_report.time import get_time

try:
Expand Down Expand Up @@ -157,13 +154,7 @@ def outages(filters=None, backend=None, fmt=None):
fmt (Format): what format to output data as.
"""
outages = get_outages(backend, **filters)

if fmt == Format.JSON:
print(json.dumps(list(outages), indent=4, default=encode_outage))
elif fmt == Format.CSV:
write_outages_csv(sys.stdout, outages)
elif fmt == Format.TEXT:
print_outages(outages)
fmt.writer(sys.stdout, outages, fields=Outage.fields)


@with_common_args
Expand Down
12 changes: 9 additions & 3 deletions src/uptime_report/format/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
https://github.com/epsy/clize
"""
import importlib
from enum import Enum
from operator import attrgetter

from clize import errors, parameters, parser
from sigtools import modifiers, wrappers
from uptime_report.gsheet import Writer


@parser.value_converter
Expand All @@ -24,6 +24,11 @@ class Format(Enum):
JSON = 'json'
GSHEET = 'gsheet'

@property
def writer(self):
mod = importlib.import_module('.' + self.value, __name__)
return mod.Writer


DEFAULT_FORMAT = Format.TEXT
"""str: the name of the default format."""
Expand All @@ -42,6 +47,7 @@ def with_format(wrapped, fmt=DEFAULT_FORMAT, *args, **kwargs):
Raises:
clize.errors.CliValueError: if the format argument is invalid.
"""
if fmt == 'gsheet' and Writer is None:
raise errors.CliValueError('pygsheets module is required.')
if fmt.writer is None:
raise errors.CliValueError(
'{} is unavailable (missing requirements?)'.format(fmt.value))
return wrapped(fmt=Format(fmt), *args, **kwargs)
26 changes: 26 additions & 0 deletions src/uptime_report/format/csv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import

import csv


def write_csv(fhandle, outages, fields):
"""Write a list of outages to a file as CSV.
Example:
>>> from six import StringIO
>>> s = StringIO()
>>> t = arrow.get(0)
>>> write_outages_csv(s, [Outage(t, t)], Outage.fields())
>>> s.getvalue()
'start,finish,before,after,meta\\r\\n1970-01-01T00:00:00+00:00,1970-01-01T00:00:00+00:00,,,{}\\r\\n'
Args:
fhandle (io.TextIOWrapper): the file object to write the CSV data to
outages (list): a list of :class:`Outage` objects to write.
"""
writer = csv.DictWriter(fhandle, fields)
writer.writeheader()
writer.writerows([o.for_json() for o in outages])
12 changes: 12 additions & 0 deletions src/uptime_report/format/gsheet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
try:
import pygsheets
except ImportError:
pygsheets = None


def write_to_sheet(out, data, fields):
pass


Writer = write_to_sheet if pygsheets else None
18 changes: 18 additions & 0 deletions src/uptime_report/format/json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import

import json


def encoder(obj):
try:
return obj.for_json()
except AttributeError:
raise TypeError(repr(obj) + " is not JSON serializable")


def write_json(out, data, **_):
json.dump(list(data), out, indent=4, default=encoder)


Writer = write_json
16 changes: 16 additions & 0 deletions src/uptime_report/format/text.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import


def write_text(out, data, **_):
for obj in data:
try:
obj = obj.humanize()
except AttributeError:
obj = obj.for_json()
out.write("\n".join(
": ".join(i) for i in obj.items()))
out.write("\n")


Writer = write_text
3 changes: 0 additions & 3 deletions src/uptime_report/gsheet.py

This file was deleted.

48 changes: 11 additions & 37 deletions src/uptime_report/outage.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
This module contains generic code for processing outages.
"""
import csv
import logging
from itertools import chain
from operator import attrgetter
Expand Down Expand Up @@ -59,6 +58,17 @@ def for_json(self):
"""
return attr.asdict(self)

@property
def humanized_duration(self):
return self.start.humanize(other=self.finish, only_distance=True)

def humanize(self):
return {
'Begin': self.start.format(),
'End': self.finish.format(),
'Duration': self.humanized_duration,
}

@classmethod
def fields(cls):
"""Return the field names for this class.
Expand All @@ -74,34 +84,6 @@ def fields(cls):
return list(map(attrgetter('name'), attr.fields(cls)))


def write_outages_csv(fhandle, outages):
"""Write a list of outages to a file as CSV.
Example:
>>> from six import StringIO
>>> s = StringIO()
>>> t = arrow.get(0)
>>> write_outages_csv(s, [Outage(t, t)])
>>> s.getvalue()
'start,finish,before,after,meta\\r\\n1970-01-01T00:00:00+00:00,1970-01-01T00:00:00+00:00,,,{}\\r\\n'
Args:
fhandle (io.TextIOWrapper): the file object to write the CSV data to
outages (list): a list of :class:`Outage` objects to write.
"""
writer = csv.DictWriter(fhandle, Outage.fields())
writer.writeheader()
writer.writerows([o.for_json() for o in outages])


def encode_outage(obj):
if isinstance(obj, (Outage, arrow.Arrow)):
return obj.for_json()
raise TypeError(repr(obj) + " is not JSON serializable")


def merge_outages(outages, overlap=0):
"""Merge a list of Outage objects."""
beginning_of_time = arrow.get(0).replace(microsecond=0)
Expand Down Expand Up @@ -154,14 +136,6 @@ def change(outage):
yield Outage(start=start, finish=finish, meta=meta)


def print_outages(outages):
for i, outage in enumerate(outages):
print("Outage #{}\nBegin: {}\tEnd: {}\n{}\n".format(
i, outage.start.format(), outage.finish.format(),
outage.start.humanize(
other=outage.finish, only_distance=True)))


def filter_outage_len(outages, minlen=0):
"""Filter-out outages that have a small duration.
Expand Down
3 changes: 2 additions & 1 deletion tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from clize import errors, run
from six import StringIO
from uptime_report import cli
from uptime_report.format import Format
from uptime_report.outage import Outage


Expand Down Expand Up @@ -49,7 +50,7 @@ def test_outages(capsys, mocker, ungrouped_outage_data):
start = finish.replace(hours=-1)
cli.outages(
start=start, finish=finish, overlap=overlap,
minlen=minlen, fmt=cli.Format.JSON)
minlen=minlen, fmt=Format.JSON)
impl.get_outages.assert_called_with(
start=start, finish=finish)
out, err = capsys.readouterr()
Expand Down
12 changes: 6 additions & 6 deletions tests/test_gsheet.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@
import arrow
import pytest
from clize import errors
from uptime_report import cli, gsheet
from uptime_report.format import with_format
from uptime_report import cli
from uptime_report.format import Format, gsheet, with_format
from uptime_report.outage import Outage


def test_outages_missing_req(mocker):
mocker.patch('uptime_report.format.Writer', new=None)
mocker.patch('uptime_report.format.gsheet.Writer', new=None)

@with_format
def wrapped(fmt=None):
pass
with pytest.raises(errors.CliValueError):
wrapped(fmt='gsheet')
wrapped(fmt=Format.GSHEET)


def test_outages_gsheet(capsys, mocker, ungrouped_outage_data):
mocker.patch('uptime_report.gsheet.pygsheets')
mocker.patch('uptime_report.format.gsheet.pygsheets')
mocker.patch('uptime_report.cli.read_config')
b = mocker.patch('uptime_report.cli.get_backend')
impl = b.return_value.from_config.return_value
Expand All @@ -31,5 +31,5 @@ def test_outages_gsheet(capsys, mocker, ungrouped_outage_data):
start = finish.replace(hours=-1)
cli.outages(
start=start, finish=finish, overlap=overlap,
minlen=minlen, fmt=cli.Format.GSHEET)
minlen=minlen, fmt=Format.GSHEET)
assert gsheet.pygsheets.authorize.call_count == 1
26 changes: 26 additions & 0 deletions tests/test_json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
import json

import arrow
import pytest
from uptime_report.format.json import encoder
from uptime_report.outage import Outage


def test_encode_outage():
s = arrow.utcnow()
f = s.replace(hours=-1)
o = Outage(start=s, finish=f)
assert json.loads(json.dumps(o, indent=4, default=encoder)) == {
'before': None, 'after': None, 'meta': {},
'finish': f.for_json(),
'start': s.for_json()
}


def test_encode_outage_fail():
s = arrow.utcnow()
f = s.replace(hours=-1)
o = Outage(start=s, finish=f, meta={'wut': set()})
with pytest.raises(TypeError):
json.dumps(o, indent=4, default=encoder)
25 changes: 1 addition & 24 deletions tests/test_outages.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import json

import arrow
import pytest
from uptime_report.outage import (Outage, encode_outage,
get_downtime_in_seconds, get_outages,
from uptime_report.outage import (Outage, get_downtime_in_seconds, get_outages,
merge_outages)


Expand Down Expand Up @@ -65,25 +61,6 @@ def test_merge_outages_overlap(outage_data):
]


def test_encode_outage():
s = arrow.utcnow()
f = s.replace(hours=-1)
o = Outage(start=s, finish=f)
assert json.loads(json.dumps(o, indent=4, default=encode_outage)) == {
'before': None, 'after': None, 'meta': {},
'finish': f.for_json(),
'start': s.for_json()
}


def test_encode_outage_fail():
s = arrow.utcnow()
f = s.replace(hours=-1)
o = Outage(start=s, finish=f, meta={'wut': set()})
with pytest.raises(TypeError):
json.dumps(o, indent=4, default=encode_outage)


def test_get_downtime(outage_data):
assert get_downtime_in_seconds([]) == 0
outages = [Outage(start=o[1], finish=o[2])
Expand Down

0 comments on commit ac105d5

Please sign in to comment.