First commit
This commit is contained in:
+10
@@ -0,0 +1,10 @@
|
||||
# Python-generated files
|
||||
__pycache__/
|
||||
*.py[oc]
|
||||
build/
|
||||
dist/
|
||||
wheels/
|
||||
*.egg-info
|
||||
|
||||
# Virtual environments
|
||||
.venv
|
||||
@@ -0,0 +1 @@
|
||||
3.14
|
||||
@@ -0,0 +1,6 @@
|
||||
.PHONY: run_ut
|
||||
run_ut:
|
||||
python -m unittest discover -s . -p 'test_*.py'
|
||||
|
||||
build:
|
||||
pyinstaller -F taskrunner.py
|
||||
@@ -0,0 +1,38 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
|
||||
a = Analysis(
|
||||
['main.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
noarchive=False,
|
||||
optimize=0,
|
||||
)
|
||||
pyz = PYZ(a.pure)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
[],
|
||||
name='main',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=True,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
@@ -0,0 +1,183 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import datetime
|
||||
from os import error, name
|
||||
from typing import Callable, Dict, List, Any
|
||||
from dataclasses import dataclass, field
|
||||
from abc import ABC, abstractmethod
|
||||
from enum import Enum, StrEnum
|
||||
|
||||
|
||||
class Status(StrEnum):
|
||||
GOOD = "GOOD"
|
||||
BAD = "BAD"
|
||||
|
||||
|
||||
@dataclass
|
||||
class StepLog(object):
|
||||
step_name: str
|
||||
status: Status
|
||||
duration_sec: int
|
||||
msg: str
|
||||
error: List[str] = field(default_factory=list)
|
||||
substeps: List[StepLog] = field(default_factory=list)
|
||||
|
||||
@property
|
||||
def failed(self) -> bool:
|
||||
""" Checks if the step failed """
|
||||
return len(self.error) > 0 or self.status == Status.BAD
|
||||
|
||||
|
||||
# # NOTE: maybe shoulnd't have used an enum
|
||||
# class StepLogEncoder(json.JSONEncoder):
|
||||
# """ JSON Encoder for Status Enums. """
|
||||
# def default(self, o):
|
||||
# if isinstance(o, Status):
|
||||
# return o.name
|
||||
# return super().default(o)
|
||||
|
||||
|
||||
class StepIF(ABC):
|
||||
""" Interface for plays. """
|
||||
|
||||
@abstractmethod
|
||||
def pre(self) -> StepLog:
|
||||
""" Run at the start to validate the configurations, inputs, etc. """
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def play(self) -> StepLog:
|
||||
""" Steps to actually run the backup. """
|
||||
|
||||
@abstractmethod
|
||||
def post(self) -> StepLog:
|
||||
""" Run at the end to validate the actions made. """
|
||||
|
||||
def run(self, name: str) -> StepLog:
|
||||
""" Run the step. """
|
||||
start_time: float = datetime.datetime.now().timestamp()
|
||||
|
||||
substeps: List[StepLog] = []
|
||||
status: Status = Status.GOOD
|
||||
for op in [self.pre, self.play, self.post]:
|
||||
log: StepLog = op()
|
||||
status = Status.BAD if log.failed else Status.GOOD
|
||||
|
||||
substeps.append(log)
|
||||
|
||||
if status == Status.BAD:
|
||||
break
|
||||
|
||||
end_time: float = datetime.datetime.now().timestamp()
|
||||
delta: int = int(end_time - start_time)
|
||||
|
||||
errors = []
|
||||
msg: str = ""
|
||||
if status == Status.BAD:
|
||||
errors = ["substep failed"]
|
||||
else:
|
||||
msg = "success"
|
||||
|
||||
return StepLog(
|
||||
step_name=name, status=status, duration_sec=delta,
|
||||
msg=msg, error=errors, substeps=substeps,
|
||||
)
|
||||
|
||||
|
||||
class CustomStep(StepIF):
|
||||
""" Pre made play to backup directories. """
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
pre_fn: Callable[[Dict[str, Any]], StepLog],
|
||||
play_fn: Callable[[Dict[str, Any]], StepLog],
|
||||
post_fn: Callable[[Dict[str, Any]], StepLog],
|
||||
):
|
||||
self.name: str = name
|
||||
|
||||
self.pre_fn = pre_fn
|
||||
self.play_fn = play_fn
|
||||
self.post_fn = post_fn
|
||||
|
||||
self.__context: Dict[str, Any] = {}
|
||||
|
||||
def pre(self) -> StepLog:
|
||||
return self.pre_fn(self.__context)
|
||||
|
||||
def play(self) -> StepLog:
|
||||
return self.play_fn(self.__context)
|
||||
|
||||
def post(self):
|
||||
return self.post_fn(self.__context)
|
||||
|
||||
|
||||
@dataclass
|
||||
class StepEntry(object):
|
||||
name: str
|
||||
step: StepIF
|
||||
|
||||
|
||||
class Play(object):
|
||||
def __init__(self, name : str):
|
||||
self.name: str = name
|
||||
self.__steps: List[StepEntry] = []
|
||||
|
||||
def add_step(self, name: str, stepRunner : StepIF):
|
||||
self.__steps.append(StepEntry(name=name, step=stepRunner))
|
||||
|
||||
def view_playbook(self) -> str:
|
||||
steps: List[str] = []
|
||||
for s in self.__steps:
|
||||
steps.append(s.name)
|
||||
|
||||
data = {
|
||||
"name": self.name,
|
||||
"number_of_steps": len(steps),
|
||||
"steps": steps,
|
||||
}
|
||||
|
||||
return json.dumps(data)
|
||||
|
||||
def play(self) -> StepLog:
|
||||
start_time: float = datetime.datetime.now().timestamp()
|
||||
playLog: StepLog = StepLog(
|
||||
step_name=self.name,
|
||||
status=Status.GOOD,
|
||||
duration_sec=0,
|
||||
msg="",
|
||||
error=[],
|
||||
substeps=[],
|
||||
)
|
||||
|
||||
for s in self.__steps:
|
||||
log: StepLog = s.step.run(s.name)
|
||||
|
||||
playLog.substeps.append(log)
|
||||
|
||||
if log.failed:
|
||||
playLog.error.append(f"failed in step: {s.name}")
|
||||
playLog.status = Status.BAD
|
||||
break
|
||||
|
||||
end_time: float = datetime.datetime.now().timestamp()
|
||||
delta: int = int(end_time - start_time)
|
||||
playLog.duration_sec = delta
|
||||
|
||||
if playLog.status == Status.GOOD:
|
||||
playLog.msg = "success"
|
||||
|
||||
return playLog
|
||||
|
||||
|
||||
class Service(object):
|
||||
def __init__(self, name: str):
|
||||
self.name = name
|
||||
self.__playbook: List[StepIF] = []
|
||||
|
||||
@classmethod
|
||||
def run(cls):
|
||||
pass
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
from Service import StepIF, StepLog, Status
|
||||
|
||||
|
||||
class DirectoryStep(StepIF):
|
||||
""" Pre made play to backup directories. """
|
||||
|
||||
def __init__(self, source : str, destiny : str):
|
||||
self.name: str = f"backup {source} -> {destiny}"
|
||||
self.source: str = source
|
||||
self.destiny: str = destiny
|
||||
|
||||
def pre(self) -> StepLog:
|
||||
return StepLog(
|
||||
step_name = "pre " + self.name,
|
||||
status = Status.GOOD,
|
||||
duration_sec=0,
|
||||
msg="",
|
||||
error= [],
|
||||
substeps=[]
|
||||
)
|
||||
|
||||
def play(self) -> StepLog:
|
||||
return StepLog(
|
||||
step_name = "run " + self.name,
|
||||
status = Status.GOOD,
|
||||
duration_sec=0,
|
||||
msg="",
|
||||
error=[],
|
||||
substeps=[]
|
||||
)
|
||||
|
||||
def post(self):
|
||||
return StepLog(
|
||||
step_name = "post " + self.name,
|
||||
status = Status.GOOD,
|
||||
duration_sec=0,
|
||||
msg="",
|
||||
error=[],
|
||||
substeps=[]
|
||||
)
|
||||
@@ -0,0 +1,14 @@
|
||||
import os
|
||||
import unittest
|
||||
from Service import Service
|
||||
|
||||
CDIR = os.path.dirname(__file__)
|
||||
|
||||
|
||||
class TestService(unittest.TestCase):
|
||||
def test_service_init(self):
|
||||
name: str = "jellyfin"
|
||||
|
||||
newService: Service = Service(name)
|
||||
|
||||
self.assertEqual(newService.name, name)
|
||||
@@ -0,0 +1,11 @@
|
||||
[project]
|
||||
name = "taskrunner"
|
||||
version = "0.1.0"
|
||||
description = "Utility to run tasks such as backups"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.14"
|
||||
dependencies = []
|
||||
|
||||
[tool.pyright]
|
||||
venvPath = "."
|
||||
venv = ".venv"
|
||||
@@ -0,0 +1,53 @@
|
||||
import json
|
||||
from playbook import StepLog, CustomStep, Play, Status
|
||||
from dataclasses import asdict
|
||||
|
||||
def pre(ctx) -> StepLog:
|
||||
ctx["a"] = 5
|
||||
|
||||
return StepLog(
|
||||
"pre something",
|
||||
Status.GOOD,
|
||||
0,
|
||||
msg="set 5",
|
||||
)
|
||||
|
||||
def play(ctx):
|
||||
ctx["a"] = ctx["a"] + 10
|
||||
|
||||
return StepLog(
|
||||
"play something",
|
||||
Status.BAD,
|
||||
0,
|
||||
msg="added 10",
|
||||
)
|
||||
|
||||
def post(ctx):
|
||||
ctx["a"] = ctx["a"] / 2
|
||||
|
||||
return StepLog(
|
||||
"post something",
|
||||
Status.GOOD,
|
||||
0,
|
||||
msg=f"halved {ctx["a"]}",
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
newPlay: Play = Play("backup dirs")
|
||||
|
||||
newPlay.add_step(
|
||||
"backup dirs",
|
||||
CustomStep(
|
||||
"custom step",
|
||||
pre, play, post,
|
||||
)
|
||||
)
|
||||
|
||||
print(newPlay.view_playbook())
|
||||
|
||||
print(json.dumps(asdict(newPlay.play()), indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user