Source code for dependency_comb.formatting.base
import datetime
import json
from pathlib import Path
from textwrap import TextWrapper
import click
import humanize
from ..package import PackageRequirement
from ..utils.dates import safe_isoformat_parse
[docs]
class BaseFormatter:
"""
Base formatter abstract.
This is not a useful formatter since finally it will write computed data as
JSON with its ``write()`` method.
Concrete formatters would commonly prefer to inherit from ``BaseStringFormatter``.
Arguments:
now_date (datetime): A datetime to set instead of default ``datetime.now()``.
This datetime is used to compute the delta time between release and current
date.
printer (callable):
printer_kwargs (dict):
"""
def __init__(self, now_date=None, printer=None, printer_kwargs=None):
self.now_date = now_date or datetime.datetime.now()
self.printer = printer
self.printer_kwargs = printer_kwargs
def get_printer_function(self):
return self.printer or click.echo
def get_printer_kwargs(self):
return self.printer_kwargs or None
def printer_call(self, content):
klass = self.get_printer_function()
opts = self.get_printer_kwargs()
if opts:
klass(content, **opts)
else:
klass(content)
[docs]
def serialize_output(self, content):
"""
Serialize output to be written in a file.
Formatters should commonly override it because the default implementation here
serializes content with JSON because internally the content is a list but this
is rarely the case with other formatters.
"""
return json.dumps(content)
[docs]
def get_required_release(self, item):
"""
Return a release labels for a requirement.
Arguments:
item (dict): The requirement dictionnary.
Returns:
tuple: Respectively the version label and resolved age delta
computed from release publish date against date now. If
``resolved_version`` is empty, the version label will just be
``Latest`` and resolved delta will be null.
"""
if not item["resolved_version"]:
return "Latest", None
resolved_age = humanize.naturaldelta(
self.now_date - safe_isoformat_parse(item["resolved_published"])
)
return item["resolved_version"], resolved_age.capitalize()
[docs]
def build_analyzed_table(self, items):
"""
Build the information table for properly analyzed requirements.
Arguments:
items (list): List of requirement dict as returned from Analyzer. Only
items with status ``analyzed`` are processed here and all other status
items are ignored.
Returns:
list: A list of dictionnaries for each processed items.
"""
analyzed_items = [v for v in items if v["status"] == "analyzed"]
rows = []
for i, item in enumerate(analyzed_items, start=1):
lateness = len(item["lateness"]) if item["lateness"] else "-"
label, age = self.get_required_release(item)
if age:
resolved_version = "{} - {} ago".format(label, age)
else:
resolved_version = label
# Compute latest release label including humanized delta from current to
# latest date
latest_activity = humanize.naturaldelta(
self.now_date - safe_isoformat_parse(item["highest_published"])
)
latest_release = "{} - {} ago".format(
item["highest_version"],
latest_activity.capitalize(),
)
# Append column data to the requirement row
rows.append({
"key": i,
"name": item["name"],
"lateness": lateness,
"resolved_version": resolved_version,
"latest_release": latest_release,
"latest_activity": latest_activity,
"release_label": label,
"release_age": age,
})
return rows
[docs]
def build_errors_table(self, items):
"""
Build the information table for failed requirements analyze.
Arguments:
items (list): List of requirement dict as returned from Analyzer. All items
are processed except the ones with status ``analyzed``.
Returns:
list: A list of dictionnaries for each processed items.
"""
ignored_items = [v for v in items if v["status"] != "analyzed"]
rows = []
wrapper = TextWrapper(width=40, max_lines=2, placeholder="")
default_label = PackageRequirement.STATUS_LABELS["unknown"]
for i, item in enumerate(ignored_items, start=1):
status = item["status"]
resume = PackageRequirement.STATUS_LABELS.get(status, default_label)
if status == "invalid":
resume += ": {}".format(item["parsing_error"])
rows.append({
"key": i,
"source": wrapper.fill(item["source"]),
"status": status,
"resume": wrapper.fill(resume),
})
return rows
[docs]
def output(self, content):
"""
Parse given content and returns it as a Python list.
Arguments:
content (Path or string or list): JSON content as built from Analyzer. It
can be either:
* A string assumed as JSON to be parsed;
* A file Path that will be readed and parsed as JSON;
* A list that is expected to be directly the list of (dict) analyzed
requirements, no parsing will be involved.
Returns:
list: The list of all (dict) requirements from given content.
"""
if isinstance(content, list):
return content
if isinstance(content, Path):
content = content.read_text()
data = json.loads(content)
return data
[docs]
def print(self, content, with_failures=True):
"""
Print out the analyzed and possibly failures
"""
data = self.output(content)
self.printer_call(self.build_analyzed_table(data))
if with_failures:
self.printer_call(self.build_errors_table(data))
[docs]
def write(self, content, destination, with_failures=True):
"""
Write the analyzed and possibly failures into destination file.
"""
data = self.output(content)
output = self.build_analyzed_table(data)
if with_failures:
output += self.build_errors_table(data)
# Write merged built lists as JSON
destination.write_text(self.serialize_output(output))
return destination
class BaseStringFormatter(BaseFormatter):
"""
Alternative base formatter where content is assumed to be written as a string.
This is commonly the one to inherit from.
"""
def serialize_output(self, content):
"""
No specific serialization since it is already a proper string.
"""
return content