From 415c79f7f1470514f348cc647009794006390caa Mon Sep 17 00:00:00 2001 From: Andreas Motl Date: Sun, 1 Dec 2024 23:51:26 +0100 Subject: [PATCH] Chore: More code linting and formatting --- grafanimate/animations.py | 10 ++++--- grafanimate/commands.py | 21 ++++++++++----- grafanimate/core.py | 22 ++++++++++----- grafanimate/grafana.py | 11 +++++--- grafanimate/marionette.py | 4 +-- grafanimate/media.py | 9 +++++-- grafanimate/model.py | 6 +++-- grafanimate/postprocessing.py | 6 ++--- grafanimate/scenarios.py | 20 ++++++++++---- grafanimate/timecontrol.py | 22 ++++++++++----- grafanimate/timeutil.py | 17 ++++++++---- grafanimate/util.py | 11 +++----- pyproject.toml | 50 ++++++++++++++++++++++++++++++++--- tests/test_timeutil.py | 5 +++- 14 files changed, 158 insertions(+), 56 deletions(-) diff --git a/grafanimate/animations.py b/grafanimate/animations.py index 5e10c1a..bb5c18a 100644 --- a/grafanimate/animations.py +++ b/grafanimate/animations.py @@ -13,7 +13,10 @@ class SequentialAnimation: def __init__( - self, grafana: GrafanaWrapper, dashboard_uid: str = None, options: Munch = None + self, + grafana: GrafanaWrapper, + dashboard_uid: str = None, + options: Munch = None, ): self.grafana = grafana self.dashboard_uid = dashboard_uid @@ -57,7 +60,7 @@ def run(self, sequence: AnimationSequence): "image": image, }, "frame": frame, - } + }, ) yield item @@ -71,7 +74,8 @@ def render(self, frame: AnimationFrame): logger.debug("Rendering image") if self.options["exposure-time"] > 0: logger.info( - "Waiting for %s seconds (exposure time)", self.options["exposure-time"] + "Waiting for %s seconds (exposure time)", + self.options["exposure-time"], ) time.sleep(self.options["exposure-time"]) diff --git a/grafanimate/commands.py b/grafanimate/commands.py index f89d6cf..3a02444 100644 --- a/grafanimate/commands.py +++ b/grafanimate/commands.py @@ -3,6 +3,7 @@ import json import logging import os +import typing as t from pathlib import Path from docopt import DocoptExit, docopt @@ -11,9 +12,12 @@ from grafanimate.core import get_scenario, make_grafana, run_animation_scenario from grafanimate.media import produce_artifacts from grafanimate.model import RenderingOptions -from grafanimate.spool import TemporaryStorage from grafanimate.util import asbool, normalize_options, setup_logging +if t.TYPE_CHECKING: + from grafanimate.spool import TemporaryStorage + + log = logging.getLogger(__name__) @@ -138,12 +142,12 @@ def run(): output_path = os.environ.get("GRAFANIMATE_OUTPUT") if not output_path: raise DocoptExit( - "Error: Parameter --output or environment variable GRAFANIMATE_OUTPUT is mandatory" + "Error: Parameter --output or environment variable GRAFANIMATE_OUTPUT is mandatory", ) if options["dashboard-view"] == "d-solo" and not options["panel-id"]: raise DocoptExit( - "Error: Parameter --panel-id is mandatory for --dashboard-view=d-solo" + "Error: Parameter --panel-id is mandatory for --dashboard-view=d-solo", ) options["exposure-time"] = float(options["exposure-time"]) @@ -175,17 +179,22 @@ def run(): scenario.dashboard_uid = options["dashboard-uid"] if not scenario.dashboard_uid: raise KeyError( - "Dashboard UID is mandatory, either supply it on the command line or via scenario file" + "Dashboard UID is mandatory, either supply it on the command line or via scenario file", ) # Open a Grafana site in Firefox, using Marionette. grafana = make_grafana( - scenario.grafana_url, scenario.dashboard_uid, options, options["headless"] + scenario.grafana_url, + scenario.dashboard_uid, + options, + options["headless"], ) # Invoke pipeline: Run stop motion animation, producing single frames. storage: TemporaryStorage = run_animation_scenario( - scenario=scenario, grafana=grafana, options=options + scenario=scenario, + grafana=grafana, + options=options, ) # Define output filename pattern. diff --git a/grafanimate/core.py b/grafanimate/core.py index 7f30c40..a9b2dfb 100644 --- a/grafanimate/core.py +++ b/grafanimate/core.py @@ -17,7 +17,10 @@ def make_grafana( - url: str, dashboard_uid: str, options: dict, headless=False + url: str, + dashboard_uid: str, + options: dict, + headless=False, ) -> GrafanaWrapper: do_login = False url = furl(url) @@ -46,10 +49,11 @@ def make_grafana( if str(url)[-1] != "/": url = str(url) + "/" url = str(url) + view + "/" + dashboard_uid + "/" + slug + query - print(url) + log.info(f"URL: {url}") grafana = GrafanaWrapper( - baseurl=str(url), use_panel_events=options["use-panel-events"] + baseurl=str(url), + use_panel_events=options["use-panel-events"], ) grafana.boot_firefox(headless=headless) grafana.boot_grafana() @@ -77,7 +81,7 @@ def get_scenario(source: str) -> AnimationScenario: if scenario is None: raise NotImplementedError( - f'Animation scenario "{source}" not found or implemented' + f'Animation scenario "{source}" not found or implemented', ) scenario.source = source @@ -105,10 +109,12 @@ def resolve_reference(module, symbol): def run_animation_scenario( - scenario: AnimationScenario, grafana: GrafanaWrapper, options: Munch + scenario: AnimationScenario, + grafana: GrafanaWrapper, + options: Munch, ) -> TemporaryStorage: log.info( - f"Running animation scenario at {scenario.grafana_url}, with dashboard UID {scenario.dashboard_uid}" + f"Running animation scenario at {scenario.grafana_url}, with dashboard UID {scenario.dashboard_uid}", ) storage = TemporaryStorage() @@ -129,7 +135,9 @@ def run_animation_scenario( # Start the engines. animation = SequentialAnimation( - grafana=grafana, dashboard_uid=scenario.dashboard_uid, options=animation_options + grafana=grafana, + dashboard_uid=scenario.dashboard_uid, + options=animation_options, ) animation.start() diff --git a/grafanimate/grafana.py b/grafanimate/grafana.py index 1dc7e79..9746b6c 100644 --- a/grafanimate/grafana.py +++ b/grafanimate/grafana.py @@ -21,7 +21,10 @@ class GrafanaWrapper(FirefoxMarionetteBase): """ def __init__( - self, baseurl: str = None, use_panel_events: bool = True, dry_run: bool = False + self, + baseurl: str = None, + use_panel_events: bool = True, + dry_run: bool = False, ): self.baseurl = baseurl self.use_panel_events = use_panel_events @@ -93,7 +96,7 @@ def wait_all_data_received(self): log.info('Waiting for "all-data-received" event') waiter = Wait(self.marionette, timeout=20.0, interval=0.1) - def condition(marionette): + def condition(marionette): # noqa: ARG001 return self.calljs("grafanaStudio.hasAllData", silent=True) try: @@ -153,7 +156,9 @@ def run_javascript(self, sourcecode, silent=False): if not silent: log.debug("Running Javascript: %s", sourcecode) return self.marionette.execute_script( - sourcecode, sandbox=None, new_sandbox=False + sourcecode, + sandbox=None, + new_sandbox=False, ) def calljs(self, name, *args, silent=False): diff --git a/grafanimate/marionette.py b/grafanimate/marionette.py index 863c473..40e9cf5 100644 --- a/grafanimate/marionette.py +++ b/grafanimate/marionette.py @@ -150,7 +150,7 @@ def shutdown(self): if self.firefox_already_started: logger.warning( - "Can not shutdown Firefox as it was already running before starting this program" + "Can not shutdown Firefox as it was already running before starting this program", ) return False @@ -171,7 +171,7 @@ def wait_for_element_tag(self, tagname): Wait for element to appear. """ waiter = Wait(self.marionette, timeout=20.0, interval=0.1) - element = waiter.until(lambda m: self.find_tag(tagname)) + element = waiter.until(lambda _: self.find_tag(tagname)) return element def render_image(self, element=None): diff --git a/grafanimate/media.py b/grafanimate/media.py index acdcaa0..296defe 100644 --- a/grafanimate/media.py +++ b/grafanimate/media.py @@ -6,7 +6,10 @@ def produce_artifacts( - input, output, scenario: AnimationScenario, options: RenderingOptions + input, + output, + scenario: AnimationScenario, + options: RenderingOptions, ): # TODO: Can use dashboard title as output filename here? # TODO: Can put `start` into filename? @@ -17,7 +20,9 @@ def produce_artifacts( # Compute input pattern and output file name. input = os.path.join(str(input), "*.png") output = str(output).format( - scenario=scenario_slug, title=title_slug, uid=scenario.dashboard_uid + scenario=scenario_slug, + title=title_slug, + uid=scenario.dashboard_uid, ) # Produce output artifacts. diff --git a/grafanimate/model.py b/grafanimate/model.py index e559c4a..9dcaa9c 100644 --- a/grafanimate/model.py +++ b/grafanimate/model.py @@ -65,7 +65,9 @@ def __init__( def get_frames(self) -> Generator[AnimationFrame, None, None]: timerange = Timerange( - start=self.start, stop=self.stop, recurrence=self.recurrence + start=self.start, + stop=self.stop, + recurrence=self.recurrence, ) # until = datetime.now() @@ -86,7 +88,7 @@ def get_frames(self) -> Generator[AnimationFrame, None, None]: until=timerange.stop, freq=self.recurrence.frequency, interval=self.recurrence.interval, - ) + ), ) # logger.info('Date range is: %s', daterange) diff --git a/grafanimate/postprocessing.py b/grafanimate/postprocessing.py index a5a1bae..8f283c2 100644 --- a/grafanimate/postprocessing.py +++ b/grafanimate/postprocessing.py @@ -29,7 +29,7 @@ def to_video(self, source, target): command = f"ffmpeg -framerate {self.options.video_framerate} -pattern_type glob -i '{source}' -c:v libx264 -vf 'pad=ceil(iw/2)*2:ceil(ih/2)*2,fps={self.options.video_fps},format=yuv420p' '{target}' -y" logger.info(f"Rendering video: {target}") logger.debug(command) - os.system(command) + os.system(command) # noqa: S605 def to_gif(self, source, target): """ @@ -81,11 +81,11 @@ def to_gif(self, source, target): command = f"ffmpeg -i '{source}' -filter_complex 'fps={self.options.gif_fps},scale={self.options.gif_width}:-1:flags=lanczos,split [o1] [o2];[o1] palettegen [p]; [o2] [p] paletteuse' '{target}' -y" logger.info("Rendering GIF: %s", target) logger.debug(command) - os.system(command) + os.system(command) # noqa: S605 def upload_server(self, source): command = f"make --makefile=/Users/amo/dev/hiveeyes/sources/documentation/Makefile ptrace source={source}" - os.system(command) + os.system(command) # noqa: S605 def render(self, source, target): mp4 = target diff --git a/grafanimate/scenarios.py b/grafanimate/scenarios.py index e40ddcf..dde50cf 100644 --- a/grafanimate/scenarios.py +++ b/grafanimate/scenarios.py @@ -125,11 +125,15 @@ def ldi_all(): return [ # LDI, ramp-up AnimationSequence( - start=datetime(2015, 10, 1), stop=datetime(2017, 1, 1), every="monthly" + start=datetime(2015, 10, 1), + stop=datetime(2017, 1, 1), + every="monthly", ), # LDI, growth AnimationSequence( - start=datetime(2017, 1, 1), stop=datetime.now(), every="weekly" + start=datetime(2017, 1, 1), + stop=datetime.now(), + every="weekly", ), ] @@ -147,16 +151,22 @@ def ldi_with_gaps(): return [ # LDI, ramp-up AnimationSequence( - start=datetime(2015, 10, 1), stop=datetime(2017, 1, 1), every="monthly" + start=datetime(2015, 10, 1), + stop=datetime(2017, 1, 1), + every="monthly", ), # LDI, growth, with gap at 2018-04-29 - 2018-12-20 # TODO: Detect empty data from datasource through Grafana Sidecar and skip respective images. AnimationSequence( - start=datetime(2017, 1, 1), stop=datetime(2018, 6, 5), every="weekly" + start=datetime(2017, 1, 1), + stop=datetime(2018, 6, 5), + every="weekly", ), # LDI, until now AnimationSequence( - start=datetime(2018, 12, 20), stop=datetime.now(), every="weekly" + start=datetime(2018, 12, 20), + stop=datetime.now(), + every="weekly", ), ] diff --git a/grafanimate/timecontrol.py b/grafanimate/timecontrol.py index 4ae7b2f..f52e382 100644 --- a/grafanimate/timecontrol.py +++ b/grafanimate/timecontrol.py @@ -78,12 +78,12 @@ def __iter__(self): def print_intervals(intervals): for interval in intervals: - print(f"{interval.start} - {interval.end}") + print(f"{interval.start} - {interval.end}") # noqa: T201 def print_header(title): - print - print("#", title) + print() # noqa: T201 + print("#", title) # noqa: T201 def create_dope_sheet_blueprint(): @@ -93,7 +93,9 @@ def create_dope_sheet_blueprint(): print_header("Sliding forward") intervals = SlidingPeriodicInterval( - start=yesterday, stop=tomorrow, every=timedelta(days=1) + start=yesterday, + stop=tomorrow, + every=timedelta(days=1), ) print_intervals(intervals) @@ -101,19 +103,25 @@ def create_dope_sheet_blueprint(): # just the opposite of sliding forward without any different computation involved. print_header("Sliding reverse") intervals = SlidingPeriodicInterval( - start=yesterday, stop=tomorrow, every=timedelta(days=1) + start=yesterday, + stop=tomorrow, + every=timedelta(days=1), ) print_intervals(reversed(list(intervals))) print_header("Cumulative I (unaligned)") intervals = CumulativePeriodicInterval( - start=yesterday, stop=tomorrow, every=timedelta(days=1) + start=yesterday, + stop=tomorrow, + every=timedelta(days=1), ) print_intervals(intervals) print_header("Cumulative II (aligned)") now_aligned_to_hour = now - timedelta( - minutes=now.minute, seconds=now.second, microseconds=now.microsecond + minutes=now.minute, + seconds=now.second, + microseconds=now.microsecond, ) intervals = CumulativePeriodicInterval( start=now_aligned_to_hour, diff --git a/grafanimate/timeutil.py b/grafanimate/timeutil.py index 0550d9e..20de197 100644 --- a/grafanimate/timeutil.py +++ b/grafanimate/timeutil.py @@ -69,7 +69,10 @@ def get_freq_delta(every: str) -> RecurrenceInfo: delta -= relativedelta(seconds=1) return RecurrenceInfo( - every=every, frequency=rr_freq, interval=rr_interval, duration=delta + every=every, + frequency=rr_freq, + interval=rr_interval, + duration=delta, ) # 2. Compute parameters from specific labels, expression periods. @@ -134,7 +137,10 @@ def get_freq_delta(every: str) -> RecurrenceInfo: delta = get_relativedelta(seconds=delta.total_seconds()) return RecurrenceInfo( - every=every, frequency=rr_freq, interval=rr_interval, duration=delta + every=every, + frequency=rr_freq, + interval=rr_interval, + duration=delta, ) @@ -167,7 +173,7 @@ def get_relativedelta(seconds: int): ).normalized() -def format_date_filename(date, every=None): +def format_date_filename(date, every=None): # noqa: ARG001 # pattern = '%Y-%m-%d' pattern = "%Y-%m-%dT%H-%M-%S" # if every in ['secondly', 'minutely', 'hourly']: @@ -196,13 +202,14 @@ def convert_absolute_timestamp(value: Union[datetime, str]) -> datetime: value = dateutil.parser.parse(value) else: raise TypeError( - f"Unknown data type for `start` or `stop` value: {value} ({type(value)})" + f"Unknown data type for `start` or `stop` value: {value} ({type(value)})", ) return value def convert_input_timestamp( - value: Union[datetime, str], relative_to: Optional[datetime] = None + value: Union[datetime, str], + relative_to: Optional[datetime] = None, ) -> datetime: """ Read and convert absolute or relative (humanized) timestamps. diff --git a/grafanimate/util.py b/grafanimate/util.py index 57000d2..e3f6be3 100644 --- a/grafanimate/util.py +++ b/grafanimate/util.py @@ -53,8 +53,7 @@ def check_socket(host, port): sock.settimeout(1) if sock.connect_ex((host, port)) == 0: return True - else: - return False + return False def filter_dict(data, keys): @@ -93,10 +92,9 @@ def asbool(obj): obj = obj.strip().lower() if obj in ["true", "yes", "on", "y", "t", "1"]: return True - elif obj in ["false", "no", "off", "n", "f", "0"]: + if obj in ["false", "no", "off", "n", "f", "0"]: return False - else: - raise ValueError(f"String is not true/false: {obj!r}") + raise ValueError(f"String is not true/false: {obj!r}") return bool(obj) @@ -115,8 +113,7 @@ def as_list(seq): # From `numpy.distutils.misc_util` if is_sequence(seq): return list(seq) - else: - return [seq] + return [seq] def import_module(name: str, path: str): diff --git a/pyproject.toml b/pyproject.toml index d291f90..5c865e3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -92,10 +92,53 @@ packages = { find = {} } "grafanimate" = [ "*.js" ] [tool.ruff] -lint.select = [ "AIR", "E", "F", "G", "I", "PIE", "PLR1714", "PLW2901", "TRY201", "UP" ] +lint.select = [ + "AIR", + # flake8-unused-arguments + "ARG", + # pyupgrade + # "UP", + # flake8-commas + "COM", + # Pycodestyle + "E", + # eradicate + "ERA", + # Pyflakes + "F", + "G", + # isort + "I", + # flake8-use-pathlib + # "PTH", + "PIE", + "PLR1714", + "PLW2901", + # flake8-quotes + "Q", + # return + "RET", + # Bandit + "S", + # print + "T20", + # future-annotations + # "FA", + # flake8-type-checking + "TCH", + "TRY201", + "UP", + "W", + # flake8-2020 + "YTT", +] + lint.ignore = [ - "E501", # line too long - "G004", # Logging statement uses f-string + "E501", # line too long + "ERA001", # Found commented-out code + "G004", # Logging statement uses f-string + "RET503", # Missing explicit `return` at the end of function able to return non-`None` value + "RET504", # Unnecessary assignment to `results` before `return` statement ] lint.per-file-ignores."__init__.py" = [ "F401", # allow unused imports in __init__.py @@ -112,6 +155,7 @@ lint.per-file-ignores."tests/*" = [ "ARG", # allow unused arguments for pytest fixtures "E741", # allow reused variables "F841", # allow unused local variables + "S101", # use of `assert` detected ] [tool.isort] diff --git a/tests/test_timeutil.py b/tests/test_timeutil.py index 916069f..c700d15 100644 --- a/tests/test_timeutil.py +++ b/tests/test_timeutil.py @@ -44,7 +44,10 @@ def test_freq_delta_legacy(): assert recurrence.frequency == WEEKLY assert recurrence.interval == 1 assert recurrence.duration == relativedelta( - days=+6, hours=+23, minutes=+59, seconds=+59 + days=+6, + hours=+23, + minutes=+59, + seconds=+59, ) recurrence = get_freq_delta("monthly")