Refactored for better config & cleaner debug mode

This commit is contained in:
Cian Hughes
2023-10-27 14:11:00 +01:00
parent 258d31c170
commit 0c4135817d
9 changed files with 196 additions and 175 deletions

View File

@@ -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)

View File

@@ -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()

View File

@@ -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"
[cli]
CLI = true
[debug]
DEBUG = true

View File

@@ -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)

View File

@@ -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)

View File

@@ -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
View File

@@ -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"

View File

@@ -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
View 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