diff --git a/autoignition.py b/autoignition.py index a2d2b29..fccd042 100644 --- a/autoignition.py +++ b/autoignition.py @@ -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) diff --git a/config.py b/config.py index 84afdac..a2f2ea2 100644 --- a/config.py +++ b/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() \ No newline at end of file diff --git a/config.toml b/config.toml index 3a0578b..d948f28 100644 --- a/config.toml +++ b/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" \ No newline at end of file +FUELIGNITION_URL = "https://opensuse.github.io/fuel-ignition/edit" + +[cli] +CLI = true + +[debug] +DEBUG = true \ No newline at end of file diff --git a/create_disk.py b/create_disk.py index 7d843cc..875ee60 100644 --- a/create_disk.py +++ b/create_disk.py @@ -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) diff --git a/create_img.py b/create_img.py index fb7b1ca..1858960 100644 --- a/create_img.py +++ b/create_img.py @@ -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) diff --git a/debug.py b/debug.py index a24aa93..f1ba13e 100644 --- a/debug.py +++ b/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 diff --git a/poetry.lock b/poetry.lock index 8d93147..3fc0bdf 100644 --- a/poetry.lock +++ b/poetry.lock @@ -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" diff --git a/pyproject.toml b/pyproject.toml index 4639661..098fb10 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Cian Hughes "] 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" diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..797b7a8 --- /dev/null +++ b/utils.py @@ -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