mirror of
https://github.com/Cian-H/I-Form_Server_Node_Deployer.git
synced 2025-12-22 22:22:02 +00:00
Refactored for better config & cleaner debug mode
This commit is contained in:
@@ -11,19 +11,9 @@ from selenium.webdriver.support import expected_conditions as EC
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
import typer
|
||||
|
||||
from config import (
|
||||
CLEANUP_IMAGES,
|
||||
CLIENT,
|
||||
CWD_MOUNT,
|
||||
CWD_MOUNTDIR,
|
||||
DOCKERFILE_DIR,
|
||||
FUELIGNITION_BUILD_DIR,
|
||||
FUELIGNITION_INIT_MESSAGE,
|
||||
FUELIGNITION_URL,
|
||||
ROOT_DIR,
|
||||
SELENIUM_INIT_MESSAGE,
|
||||
)
|
||||
from debug import debug_mode
|
||||
import config
|
||||
from debug import debug_guard
|
||||
from utils import ensure_build_dir
|
||||
|
||||
|
||||
def create_driver():
|
||||
@@ -36,10 +26,10 @@ def create_driver():
|
||||
|
||||
|
||||
def convert_json_via_fuelignition(container, driver, fuelignition_json, img_path):
|
||||
driver.get(FUELIGNITION_URL)
|
||||
driver.get(config.FUELIGNITION_URL)
|
||||
# Navigate to "Load Settings from" and upload the json
|
||||
load_from = driver.find_element(By.NAME, "load_from")
|
||||
load_from.send_keys(str(CWD_MOUNTDIR / fuelignition_json))
|
||||
load_from.send_keys(str(config.CWD_MOUNTDIR / fuelignition_json))
|
||||
# Walk through page structure to find, scroll to and click "Convert and Download"
|
||||
export = driver.find_element(By.ID, "export")
|
||||
export_divs = export.find_elements(By.TAG_NAME, "div")
|
||||
@@ -62,7 +52,7 @@ def convert_json_via_fuelignition(container, driver, fuelignition_json, img_path
|
||||
image_file = container.exec_run("ls /home/seluser/Downloads/").output.decode().split()[0]
|
||||
# Finally, fetch the image file from the container
|
||||
client_image_path = f"/home/seluser/Downloads/{image_file}"
|
||||
host_image_path = ROOT_DIR / img_path
|
||||
host_image_path = config.ROOT_DIR / img_path
|
||||
if host_image_path.exists():
|
||||
host_image_path.unlink()
|
||||
filestream = container.get_archive(client_image_path)[0]
|
||||
@@ -76,14 +66,16 @@ def convert_json_via_fuelignition(container, driver, fuelignition_json, img_path
|
||||
|
||||
def build_fuelignition():
|
||||
# Make sure the local fuel-ignition repo is up to date
|
||||
if (not FUELIGNITION_BUILD_DIR.exists()) or (len(tuple(FUELIGNITION_BUILD_DIR.iterdir())) == 0):
|
||||
if (not config.FUELIGNITION_BUILD_DIR.exists()) or (
|
||||
len(tuple(config.FUELIGNITION_BUILD_DIR.iterdir())) == 0
|
||||
):
|
||||
repo = git.Repo.clone_from(
|
||||
"https://github.com/openSUSE/fuel-ignition.git",
|
||||
FUELIGNITION_BUILD_DIR,
|
||||
config.FUELIGNITION_BUILD_DIR,
|
||||
branch="main",
|
||||
)
|
||||
else:
|
||||
repo = git.Repo(FUELIGNITION_BUILD_DIR)
|
||||
repo = git.Repo(config.FUELIGNITION_BUILD_DIR)
|
||||
repo.remotes.origin.update()
|
||||
repo.remotes.origin.pull()
|
||||
# Then, build the docker image using the Dockerfile in the repo
|
||||
@@ -95,56 +87,67 @@ def build_fuelignition():
|
||||
engine_version = tuple(
|
||||
map(
|
||||
int,
|
||||
next(filter(lambda x: x.get("Name") == "Engine", CLIENT.version()["Components"]))[
|
||||
"Version"
|
||||
].split("."),
|
||||
next(
|
||||
filter(lambda x: x.get("Name") == "Engine", config.CLIENT.version()["Components"])
|
||||
)["Version"].split("."),
|
||||
)
|
||||
)
|
||||
root_container = (engine_version[0] > 9) or (engine_version[0] == 9 and engine_version[1] >= 3)
|
||||
dockerfile = "Dockerfile"
|
||||
if root_container:
|
||||
dockerfile = DOCKERFILE_DIR / "fuel-ignition.dockerfile"
|
||||
image, _ = CLIENT.images.build(
|
||||
path=str(FUELIGNITION_BUILD_DIR),
|
||||
dockerfile = config.DOCKERFILE_DIR / "fuel-ignition.dockerfile"
|
||||
image, _ = config.CLIENT.images.build(
|
||||
path=str(config.FUELIGNITION_BUILD_DIR),
|
||||
dockerfile=str(dockerfile),
|
||||
tag="fuel-ignition",
|
||||
network_mode="host",
|
||||
buildargs={"CONTAINER_USERID": "1000"},
|
||||
pull=True,
|
||||
quiet=True,
|
||||
rm=CLEANUP_IMAGES,
|
||||
rm=config.CLEANUP_IMAGES,
|
||||
)
|
||||
return image
|
||||
|
||||
|
||||
def json_to_img(fuelignition_json: str, img_path: str) -> None:
|
||||
@debug_guard
|
||||
@ensure_build_dir
|
||||
def json_to_img(
|
||||
fuelignition_json: Annotated[
|
||||
str, typer.Option(help="The fuel-ignition json for configuring the disk image", prompt=True)
|
||||
],
|
||||
img_path: Annotated[
|
||||
str, typer.Option(help="The file to output the disk image to", prompt=True)
|
||||
],
|
||||
) -> None:
|
||||
selenium_container = None
|
||||
fuelignition_container = None
|
||||
fuelignition_image = None
|
||||
try:
|
||||
# Initialise containers
|
||||
selenium_container = CLIENT.containers.run(
|
||||
selenium_container = config.CLIENT.containers.run(
|
||||
"selenium/standalone-firefox:latest",
|
||||
detach=True,
|
||||
remove=True,
|
||||
ports={4444: 4444, 7900: 7900},
|
||||
mounts=[CWD_MOUNT,],
|
||||
mounts=[
|
||||
config.CWD_MOUNT,
|
||||
],
|
||||
)
|
||||
fuelignition_image = build_fuelignition()
|
||||
fuelignition_container = CLIENT.containers.run(
|
||||
fuelignition_container = config.CLIENT.containers.run(
|
||||
fuelignition_image,
|
||||
detach=True,
|
||||
remove=True,
|
||||
network_mode=f"container:{selenium_container.id}",
|
||||
)
|
||||
# Wait for the containers to finish starting up
|
||||
while SELENIUM_INIT_MESSAGE not in selenium_container.logs().decode():
|
||||
while config.SELENIUM_INIT_MESSAGE not in selenium_container.logs().decode():
|
||||
time.sleep(0.1)
|
||||
for event in CLIENT.events(decode=True):
|
||||
for event in config.CLIENT.events(decode=True):
|
||||
print(event)
|
||||
while not fnmatch(
|
||||
fuelignition_container.logs().decode().strip().split("\n")[-1].strip(),
|
||||
FUELIGNITION_INIT_MESSAGE,
|
||||
config.FUELIGNITION_INIT_MESSAGE,
|
||||
):
|
||||
time.sleep(0.1)
|
||||
# Now, create the webdriver and convert the json to an img
|
||||
@@ -157,30 +160,14 @@ def json_to_img(fuelignition_json: str, img_path: str) -> None:
|
||||
if selenium_container is not None:
|
||||
selenium_image = selenium_container.image
|
||||
selenium_container.kill()
|
||||
if CLEANUP_IMAGES:
|
||||
if config.CLEANUP_IMAGES:
|
||||
selenium_image.remove(force=True)
|
||||
if fuelignition_container is not None:
|
||||
fuelignition_container.kill()
|
||||
if fuelignition_image is not None:
|
||||
if CLEANUP_IMAGES:
|
||||
if config.CLEANUP_IMAGES:
|
||||
fuelignition_image.remove(force=True)
|
||||
|
||||
|
||||
def main(
|
||||
fuelignition_json: Annotated[
|
||||
str, typer.Option(help="The fuel-ignition json for configuring the disk image", prompt=True)
|
||||
],
|
||||
img_path: Annotated[
|
||||
str, typer.Option(help="The file to output the disk image to", prompt=True)
|
||||
],
|
||||
debug: Annotated[bool, typer.Option(help="Enable debug mode")] = False,
|
||||
) -> None:
|
||||
debug_mode(debug)
|
||||
f = json_to_img
|
||||
if debug:
|
||||
f = ss(f) # noqa: F821, # type: ignore #? ss is installed in debug_mode
|
||||
f(fuelignition_json, img_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
typer.run(main)
|
||||
typer.run(json_to_img)
|
||||
|
||||
60
config.py
60
config.py
@@ -1,3 +1,8 @@
|
||||
# flake8: noqa: F821
|
||||
#* This file sets a number of config constants by modifying its own globals
|
||||
#* As a result, F821 is disabled as the intereter cannot be trusted to know
|
||||
#* when F821 should be raised.
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import tomllib
|
||||
@@ -5,30 +10,57 @@ import tomllib
|
||||
import docker
|
||||
|
||||
|
||||
def get_config(config: str = "default") -> dict:
|
||||
CLIENT = docker.from_env(version="auto")
|
||||
|
||||
type ConfigLabel = str | list[str]
|
||||
|
||||
|
||||
def get_config(config_label: ConfigLabel = ["default"]) -> dict:
|
||||
if isinstance(config_label, str):
|
||||
config_label = [config_label]
|
||||
with open("config.toml", "rb") as f:
|
||||
configs: dict = tomllib.load(f)
|
||||
out_config: dict = configs["default"]
|
||||
out_config.update(configs[config])
|
||||
out_config: dict = {}
|
||||
for c in config_label:
|
||||
out_config.update(configs[c])
|
||||
return out_config
|
||||
|
||||
|
||||
def apply_config(config: dict) -> None:
|
||||
config["CLIENT"] = docker.from_env(version="auto")
|
||||
config["ROOT_DIR"] = Path(config["ROOT_DIR"]).absolute()
|
||||
config["BUILD_DIR"] = Path(config["BUILD_DIR"]).absolute()
|
||||
config["DOCKERFILE_DIR"] = Path(config["DOCKERFILE_DIR"]).absolute()
|
||||
config["FUELIGNITION_BUILD_DIR"] = config["BUILD_DIR"] / config["FUELIGNITION_BUILD_DIR"]
|
||||
config["CWD_MOUNTDIR"] = Path(config["CWD_MOUNTDIR"])
|
||||
def finalise_config(config: dict) -> None:
|
||||
# First, convert base paths to Path objects
|
||||
for k, v in config.items():
|
||||
match k:
|
||||
case "ROOT_DIR" | "BUILD_DIR" | "DOCKERFILE_DIR":
|
||||
config[k] = Path(v).absolute()
|
||||
case "CWD_MOUNTDIR":
|
||||
config[k] = Path(v)
|
||||
# Then, get required paths from config or globals if not present
|
||||
build_dir = config.get("BUILD_DIR", BUILD_DIR)
|
||||
cwd_mountdir = config.get("CWD_MOUNTDIR", CWD_MOUNTDIR)
|
||||
root_dir = config.get("ROOT_DIR", ROOT_DIR)
|
||||
# Finally, construct the secondary parameters
|
||||
config["FUELIGNITION_BUILD_DIR"] = build_dir / config.get(
|
||||
"FUELIGNITION_BUILD_DIR",
|
||||
FUELIGNITION_BUILD_DIR
|
||||
)
|
||||
config["CWD_MOUNT"] = docker.types.Mount(
|
||||
target=str(config["CWD_MOUNTDIR"]),
|
||||
source=str(config["ROOT_DIR"]),
|
||||
target=str(cwd_mountdir),
|
||||
source=str(root_dir),
|
||||
type="bind",
|
||||
)
|
||||
|
||||
|
||||
def apply_config(config: dict) -> None:
|
||||
finalise_config(config)
|
||||
globals().update(config)
|
||||
|
||||
|
||||
def init(config: str = "default") -> None:
|
||||
apply_config(get_config(config))
|
||||
def update_config(config_label: ConfigLabel = "default") -> None:
|
||||
apply_config(get_config(config_label))
|
||||
|
||||
|
||||
def init() -> None:
|
||||
globals().update(get_config())
|
||||
update_config()
|
||||
|
||||
init()
|
||||
10
config.toml
10
config.toml
@@ -9,9 +9,17 @@ FUELIGNITION_BUILD_DIR = "fuel-ignition"
|
||||
CWD_MOUNTDIR = "/host_cwd"
|
||||
CLIENT_STDOUT = true
|
||||
CLEANUP_IMAGES = false
|
||||
CLI = false
|
||||
DEBUG = false
|
||||
|
||||
[local]
|
||||
FUELIGNITION_URL = "http://localhost:3000/fuel-ignition/edit"
|
||||
|
||||
[remote]
|
||||
FUELIGNITION_URL = "https://opensuse.github.io/fuel-ignition/edit"
|
||||
FUELIGNITION_URL = "https://opensuse.github.io/fuel-ignition/edit"
|
||||
|
||||
[cli]
|
||||
CLI = true
|
||||
|
||||
[debug]
|
||||
DEBUG = true
|
||||
@@ -1,18 +1,12 @@
|
||||
from fnmatch import fnmatch
|
||||
from pathlib import Path
|
||||
from typing import Annotated
|
||||
|
||||
import typer
|
||||
|
||||
from config import (
|
||||
CLEANUP_IMAGES,
|
||||
CLIENT,
|
||||
CWD_MOUNT,
|
||||
CWD_MOUNTDIR,
|
||||
DOCKERFILE_DIR,
|
||||
)
|
||||
import config
|
||||
from create_img import create_img
|
||||
from debug import debug_mode
|
||||
from debug import debug_guard
|
||||
from utils import ensure_build_dir
|
||||
from docker.types import Mount
|
||||
|
||||
|
||||
@@ -28,22 +22,24 @@ def filter_validation_response(response: str) -> str:
|
||||
|
||||
|
||||
def validation_result() -> str:
|
||||
dockerfile = DOCKERFILE_DIR / "validate.dockerfile"
|
||||
image, _ = CLIENT.images.build(
|
||||
dockerfile = config.DOCKERFILE_DIR / "validate.dockerfile"
|
||||
image, _ = config.CLIENT.images.build(
|
||||
path=".",
|
||||
dockerfile=str(dockerfile),
|
||||
tag="validate",
|
||||
buildargs={"CWD_MOUNTDIR": str(CWD_MOUNTDIR)},
|
||||
rm=CLEANUP_IMAGES,
|
||||
buildargs={"CWD_MOUNTDIR": str(config.CWD_MOUNTDIR)},
|
||||
rm=config.CLEANUP_IMAGES,
|
||||
pull=True,
|
||||
quiet=True,
|
||||
)
|
||||
response = CLIENT.containers.run(
|
||||
response = config.CLIENT.containers.run(
|
||||
image,
|
||||
mounts=[CWD_MOUNT,],
|
||||
mounts=[
|
||||
config.CWD_MOUNT,
|
||||
],
|
||||
remove=True,
|
||||
)
|
||||
if CLEANUP_IMAGES:
|
||||
if config.CLEANUP_IMAGES:
|
||||
image.remove(force=True)
|
||||
return response
|
||||
|
||||
@@ -55,34 +51,17 @@ def validate() -> (bool, str):
|
||||
|
||||
|
||||
def write_disk(disk: str) -> None:
|
||||
CLIENT.containers.run(
|
||||
config.CLIENT.containers.run(
|
||||
"alpine",
|
||||
mounts=[CWD_MOUNT, Mount("/ignition_disk", disk, type="bind")],
|
||||
mounts=[config.CWD_MOUNT, Mount("/ignition_disk", disk, type="bind")],
|
||||
privileged=True,
|
||||
command=f"dd if={CWD_MOUNTDIR}/build/ignition.img of=/ignition_disk"
|
||||
command=f"dd if={config.CWD_MOUNTDIR}/build/ignition.img of=/ignition_disk",
|
||||
)
|
||||
|
||||
|
||||
@debug_guard
|
||||
@ensure_build_dir
|
||||
def create_ignition_disk(
|
||||
disk: str,
|
||||
hostname: str,
|
||||
password: str,
|
||||
switch_ip_address: str,
|
||||
switch_port: int,
|
||||
swarm_token: str,
|
||||
debug: bool = False,
|
||||
) -> None:
|
||||
create_img(hostname, password, switch_ip_address, switch_port, swarm_token)
|
||||
valid, response = validate()
|
||||
if not valid:
|
||||
print(response)
|
||||
raise typer.Exit(1)
|
||||
else:
|
||||
print("Valid ignition image created!")
|
||||
write_disk(disk)
|
||||
|
||||
|
||||
def main(
|
||||
disk: Annotated[str, typer.Option(help="Path to the disk to write to", prompt=True)],
|
||||
hostname: Annotated[str, typer.Option(help="Hostname for the new node", prompt=True)],
|
||||
password: Annotated[
|
||||
@@ -101,15 +80,16 @@ def main(
|
||||
swarm_token: Annotated[
|
||||
str, typer.Option(help="Swarm token for connecting to the swarm", prompt=True)
|
||||
],
|
||||
debug: Annotated[bool, typer.Option(help="Enable debug mode")] = False,
|
||||
) -> None:
|
||||
debug_mode(debug)
|
||||
Path("build").mkdir(exist_ok=True, parents=True)
|
||||
f = create_ignition_disk
|
||||
if debug:
|
||||
f = ss(f) # noqa: F821, # type: ignore #? ss is installed in debug_mode
|
||||
f(disk, hostname, password, switch_ip_address, switch_port, swarm_token)
|
||||
create_img(hostname, password, switch_ip_address, switch_port, swarm_token)
|
||||
valid, response = validate()
|
||||
if not valid:
|
||||
print(response)
|
||||
raise typer.Exit(1)
|
||||
else:
|
||||
print("Valid ignition image created!")
|
||||
write_disk(disk)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
typer.run(main)
|
||||
typer.run(create_ignition_disk)
|
||||
|
||||
@@ -5,7 +5,8 @@ from typing import Annotated
|
||||
import typer
|
||||
|
||||
from autoignition import json_to_img
|
||||
from debug import debug_mode
|
||||
from debug import debug_guard
|
||||
from utils import ensure_build_dir
|
||||
|
||||
|
||||
MAX_PORT: int = 65535
|
||||
@@ -65,8 +66,26 @@ def apply_ignition_settings(
|
||||
return ignition_config
|
||||
|
||||
|
||||
@debug_guard
|
||||
@ensure_build_dir
|
||||
def create_img(
|
||||
hostname: str, password: str, switch_ip_address: str, switch_port: str, swarm_token: str
|
||||
hostname: Annotated[str, typer.Option(help="Hostname for the new node", prompt=True)],
|
||||
password: Annotated[
|
||||
str,
|
||||
typer.Option(
|
||||
help="Password for the root user on the new node",
|
||||
prompt=True,
|
||||
confirmation_prompt=True,
|
||||
hide_input=True,
|
||||
),
|
||||
],
|
||||
switch_ip_address: Annotated[
|
||||
str, typer.Option(help="IP address of the switch to connect to", prompt=True)
|
||||
],
|
||||
switch_port: Annotated[int, typer.Option(help="Port on the switch to connect to", prompt=True)],
|
||||
swarm_token: Annotated[
|
||||
str, typer.Option(help="Swarm token for connecting to the swarm", prompt=True)
|
||||
],
|
||||
) -> None:
|
||||
switch_ip_address = ipaddress.ip_address(switch_ip_address)
|
||||
if switch_port > MAX_PORT:
|
||||
@@ -98,32 +117,5 @@ def create_img(
|
||||
json_to_img("build/fuelignition.json", "build/ignition.img")
|
||||
|
||||
|
||||
def main(
|
||||
hostname: Annotated[str, typer.Option(help="Hostname for the new node", prompt=True)],
|
||||
password: Annotated[
|
||||
str,
|
||||
typer.Option(
|
||||
help="Password for the root user on the new node",
|
||||
prompt=True,
|
||||
confirmation_prompt=True,
|
||||
hide_input=True,
|
||||
),
|
||||
],
|
||||
switch_ip_address: Annotated[
|
||||
str, typer.Option(help="IP address of the switch to connect to", prompt=True)
|
||||
],
|
||||
switch_port: Annotated[int, typer.Option(help="Port on the switch to connect to", prompt=True)],
|
||||
swarm_token: Annotated[
|
||||
str, typer.Option(help="Swarm token for connecting to the swarm", prompt=True)
|
||||
],
|
||||
debug: Annotated[bool, typer.Option(help="Enable debug mode")] = False,
|
||||
) -> None:
|
||||
debug_mode(debug)
|
||||
f = create_img
|
||||
if debug:
|
||||
f = ss(f) # noqa: F821, # type: ignore #? ss is installed in debug_mode
|
||||
f(hostname, password, switch_ip_address, switch_port, swarm_token)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
typer.run(main)
|
||||
typer.run(create_img)
|
||||
|
||||
15
debug.py
15
debug.py
@@ -1,10 +1,15 @@
|
||||
import typer # type: ignore
|
||||
from functools import wraps
|
||||
import inspect
|
||||
from typing import Callable
|
||||
|
||||
import typer # type: ignore
|
||||
|
||||
import config
|
||||
|
||||
|
||||
def debug_mode(debug: bool = False):
|
||||
if not debug:
|
||||
return
|
||||
def debug_guard(f: Callable) -> Callable:
|
||||
if not config.DEBUG:
|
||||
return f
|
||||
try:
|
||||
import snoop # type: ignore
|
||||
except ImportError:
|
||||
@@ -14,4 +19,6 @@ def debug_mode(debug: bool = False):
|
||||
snoop.install(
|
||||
snoop="ss",
|
||||
)
|
||||
|
||||
typer.echo(f"Debug mode enabled: {inspect.stack()[1].filename}")
|
||||
wraps(f)(ss)(f) # noqa: F821 #* ss is installed in debug_mode
|
||||
|
||||
55
poetry.lock
generated
55
poetry.lock
generated
@@ -2,20 +2,21 @@
|
||||
|
||||
[[package]]
|
||||
name = "asttokens"
|
||||
version = "2.4.0"
|
||||
version = "2.4.1"
|
||||
description = "Annotate AST trees with source code positions"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "asttokens-2.4.0-py2.py3-none-any.whl", hash = "sha256:cf8fc9e61a86461aa9fb161a14a0841a03c405fa829ac6b202670b3495d2ce69"},
|
||||
{file = "asttokens-2.4.0.tar.gz", hash = "sha256:2e0171b991b2c959acc6c49318049236844a5da1d65ba2672c4880c1c894834e"},
|
||||
{file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"},
|
||||
{file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
six = ">=1.12.0"
|
||||
|
||||
[package.extras]
|
||||
test = ["astroid", "pytest"]
|
||||
astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"]
|
||||
test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "attrs"
|
||||
@@ -600,13 +601,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "outcome"
|
||||
version = "1.3.0"
|
||||
version = "1.3.0.post0"
|
||||
description = "Capture the outcome of Python function calls."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "outcome-1.3.0-py2.py3-none-any.whl", hash = "sha256:7b688fd82db72f4b0bc9e883a00359d4d4179cd97d27f09c9644d0c842ba7786"},
|
||||
{file = "outcome-1.3.0.tar.gz", hash = "sha256:588ef4dc10b64e8df160d8d1310c44e1927129a66d6d2ef86845cef512c5f24c"},
|
||||
{file = "outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b"},
|
||||
{file = "outcome-1.3.0.post0.tar.gz", hash = "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -750,28 +751,28 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.1.1"
|
||||
version = "0.1.3"
|
||||
description = "An extremely fast Python linter, written in Rust."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "ruff-0.1.1-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:b7cdc893aef23ccc14c54bd79a8109a82a2c527e11d030b62201d86f6c2b81c5"},
|
||||
{file = "ruff-0.1.1-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:620d4b34302538dbd8bbbe8fdb8e8f98d72d29bd47e972e2b59ce6c1e8862257"},
|
||||
{file = "ruff-0.1.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a909d3930afdbc2e9fd893b0034479e90e7981791879aab50ce3d9f55205bd6"},
|
||||
{file = "ruff-0.1.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3305d1cb4eb8ff6d3e63a48d1659d20aab43b49fe987b3ca4900528342367145"},
|
||||
{file = "ruff-0.1.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c34ae501d0ec71acf19ee5d4d889e379863dcc4b796bf8ce2934a9357dc31db7"},
|
||||
{file = "ruff-0.1.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6aa7e63c3852cf8fe62698aef31e563e97143a4b801b57f920012d0e07049a8d"},
|
||||
{file = "ruff-0.1.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d68367d1379a6b47e61bc9de144a47bcdb1aad7903bbf256e4c3d31f11a87ae"},
|
||||
{file = "ruff-0.1.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bc11955f6ce3398d2afe81ad7e49d0ebf0a581d8bcb27b8c300281737735e3a3"},
|
||||
{file = "ruff-0.1.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbbd8eead88ea83a250499074e2a8e9d80975f0b324b1e2e679e4594da318c25"},
|
||||
{file = "ruff-0.1.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f4780e2bb52f3863a565ec3f699319d3493b83ff95ebbb4993e59c62aaf6e75e"},
|
||||
{file = "ruff-0.1.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8f5b24daddf35b6c207619301170cae5d2699955829cda77b6ce1e5fc69340df"},
|
||||
{file = "ruff-0.1.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d3f9ac658ba29e07b95c80fa742b059a55aefffa8b1e078bc3c08768bdd4b11a"},
|
||||
{file = "ruff-0.1.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3521bf910104bf781e6753282282acc145cbe3eff79a1ce6b920404cd756075a"},
|
||||
{file = "ruff-0.1.1-py3-none-win32.whl", hash = "sha256:ba3208543ab91d3e4032db2652dcb6c22a25787b85b8dc3aeff084afdc612e5c"},
|
||||
{file = "ruff-0.1.1-py3-none-win_amd64.whl", hash = "sha256:3ff3006c97d9dc396b87fb46bb65818e614ad0181f059322df82bbfe6944e264"},
|
||||
{file = "ruff-0.1.1-py3-none-win_arm64.whl", hash = "sha256:e140bd717c49164c8feb4f65c644046fe929c46f42493672853e3213d7bdbce2"},
|
||||
{file = "ruff-0.1.1.tar.gz", hash = "sha256:c90461ae4abec261609e5ea436de4a4b5f2822921cf04c16d2cc9327182dbbcc"},
|
||||
{file = "ruff-0.1.3-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:b46d43d51f7061652eeadb426a9e3caa1e0002470229ab2fc19de8a7b0766901"},
|
||||
{file = "ruff-0.1.3-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:b8afeb9abd26b4029c72adc9921b8363374f4e7edb78385ffaa80278313a15f9"},
|
||||
{file = "ruff-0.1.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca3cf365bf32e9ba7e6db3f48a4d3e2c446cd19ebee04f05338bc3910114528b"},
|
||||
{file = "ruff-0.1.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4874c165f96c14a00590dcc727a04dca0cfd110334c24b039458c06cf78a672e"},
|
||||
{file = "ruff-0.1.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eec2dd31eed114e48ea42dbffc443e9b7221976554a504767ceaee3dd38edeb8"},
|
||||
{file = "ruff-0.1.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dc3ec4edb3b73f21b4aa51337e16674c752f1d76a4a543af56d7d04e97769613"},
|
||||
{file = "ruff-0.1.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e3de9ed2e39160800281848ff4670e1698037ca039bda7b9274f849258d26ce"},
|
||||
{file = "ruff-0.1.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c595193881922cc0556a90f3af99b1c5681f0c552e7a2a189956141d8666fe8"},
|
||||
{file = "ruff-0.1.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f75e670d529aa2288cd00fc0e9b9287603d95e1536d7a7e0cafe00f75e0dd9d"},
|
||||
{file = "ruff-0.1.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:76dd49f6cd945d82d9d4a9a6622c54a994689d8d7b22fa1322983389b4892e20"},
|
||||
{file = "ruff-0.1.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:918b454bc4f8874a616f0d725590277c42949431ceb303950e87fef7a7d94cb3"},
|
||||
{file = "ruff-0.1.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d8859605e729cd5e53aa38275568dbbdb4fe882d2ea2714c5453b678dca83784"},
|
||||
{file = "ruff-0.1.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0b6c55f5ef8d9dd05b230bb6ab80bc4381ecb60ae56db0330f660ea240cb0d4a"},
|
||||
{file = "ruff-0.1.3-py3-none-win32.whl", hash = "sha256:3e7afcbdcfbe3399c34e0f6370c30f6e529193c731b885316c5a09c9e4317eef"},
|
||||
{file = "ruff-0.1.3-py3-none-win_amd64.whl", hash = "sha256:7a18df6638cec4a5bd75350639b2bb2a2366e01222825562c7346674bdceb7ea"},
|
||||
{file = "ruff-0.1.3-py3-none-win_arm64.whl", hash = "sha256:12fd53696c83a194a2db7f9a46337ce06445fb9aa7d25ea6f293cf75b21aca9f"},
|
||||
{file = "ruff-0.1.3.tar.gz", hash = "sha256:3ba6145369a151401d5db79f0a47d50e470384d0d89d0d6f7fab0b589ad07c34"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -999,5 +1000,5 @@ h11 = ">=0.9.0,<1"
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.11"
|
||||
content-hash = "80f545060bae202b15081561a4df464f1c1f7dae9aff52c76f2e70c144628a35"
|
||||
python-versions = "^3.12"
|
||||
content-hash = "7ca42b684c0befdb9600db2f6677810db9c3465d6617ff1bf2190eedca01fa84"
|
||||
|
||||
@@ -6,7 +6,7 @@ authors = ["Cian Hughes <cian.hughes@dcu.ie>"]
|
||||
readme = "README.md"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.11"
|
||||
python = "^3.12"
|
||||
typer = {extras = ["all"], version = "^0.9.0"}
|
||||
selenium = "^4.14.0"
|
||||
mechanicalsoup = "^1.3.0"
|
||||
|
||||
14
utils.py
Normal file
14
utils.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from functools import wraps
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
|
||||
import config
|
||||
|
||||
|
||||
def ensure_build_dir(f: Callable) -> Callable:
|
||||
@wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
Path(config.BUILD_DIR).mkdir(exist_ok=True, parents=True)
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
Reference in New Issue
Block a user