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