diff --git a/pyproject.toml b/pyproject.toml index 9a1a350..9806525 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,21 +6,26 @@ license = { text = "AGPL-3.0-only" } license-files = ["LICENSE"] readme = "README.md" requires-python = ">=3.11" -authors = [ - { name = "Paul Winterstein", email = "paul.winterstein@swr.de" }, -] +authors = [{ name = "Paul Winterstein", email = "paul.winterstein@swr.de" }] maintainers = [ { name = "SWR Media-over-IP Team", email = "moip@swr.de" }, { name = "Josia Hildebrandt", email = "manuel_josia.hildebrandt@swr.de" }, ] -keywords = ["videoipath", "automation", "nevion", "media-over-ip", "st2110", "orchestration"] +keywords = [ + "videoipath", + "automation", + "nevion", + "media-over-ip", + "st2110", + "orchestration", +] dependencies = [ "requests (>=2.31.0,<3.0.0)", "pydantic (>=2.6.4,<3.0.0)", "pydantic-extra-types (>=2.6.0,<3.0.0)", "pydantic-settings (>=2.2.1,<3.0.0)", "urllib3 (>=2.2.3,<3.0.0)", - "deepdiff (>=8.1.1,<9.0.0)" + "deepdiff (>=8.1.1,<9.0.0)", ] [project.urls] @@ -51,6 +56,8 @@ env_files = ["tests/.env.test"] [virtualenvs] in-project = true +[project.scripts] +set-videoipath-version = "videoipath_automation_tool.scripts.generate_all:main" [tool.ruff] include = ["pyproject.toml", "src/**/*.py", "tests/**/*.py"] diff --git a/src/videoipath_automation_tool/apps/inventory/model/drivers.py b/src/videoipath_automation_tool/apps/inventory/model/drivers.py index cb17424..ab026e0 100644 --- a/src/videoipath_automation_tool/apps/inventory/model/drivers.py +++ b/src/videoipath_automation_tool/apps/inventory/model/drivers.py @@ -1,11 +1,11 @@ from abc import ABC -from typing import Dict, Literal, Type, TypeVar, Union, Optional +from typing import Dict, Literal, Optional, Type, TypeVar, Union from pydantic import BaseModel, Field # Notes: # - The name of the custom settings model follows the naming convention: CustomSettings___ => "." and "-" are replaced by "_"! -# - src/videoipath_automation_tool/apps/inventory/model/driver_schema/2024.1.4.json is used as reference to define the custom settings model! +# - Schema 2024.4.12.json is used as reference to define the custom settings model! # - The "driver_id" attribute is necessary for the discriminator, which is used to determine the correct model for the custom settings in DeviceConfiguration! # - The "alias" attribute is used to map the attribute to the correct key (with driver organization & name) in the JSON payload for the API! # - "DriverLiteral" is used to provide a list of all possible drivers in the IDEs IntelliSense! diff --git a/src/videoipath_automation_tool/scripts/generate_all.py b/src/videoipath_automation_tool/scripts/generate_all.py new file mode 100644 index 0000000..5d9557d --- /dev/null +++ b/src/videoipath_automation_tool/scripts/generate_all.py @@ -0,0 +1,27 @@ +import argparse +import os + +from videoipath_automation_tool.scripts.generate_driver_models import main as generate_driver_models +from videoipath_automation_tool.scripts.generate_overloads import main as generate_overloads +from videoipath_automation_tool.utils.script_utils import ROOT_DIR + +parser = argparse.ArgumentParser(description="Generate all version-specific code for a given VideoIPath version") +parser.add_argument("version", help="Version of VideoIPath to use", default="2024.4.12", nargs="?") + + +def main(): + args = parser.parse_args() + schema_file = os.path.join(ROOT_DIR, "apps", "inventory", "model", "driver_schema", f"{args.version}.json") + + if not os.path.exists(schema_file): + print( + f"VideoIPath version {args.version} is currently not supported. Please create an issue on https://github.com/SWR-MoIP/VideoIPath-Automation-Tool/issues to request support for this version." + ) + exit(1) + + generate_driver_models(schema_file) + generate_overloads() + + +if __name__ == "__main__": + main() diff --git a/src/scripts/generate_driver_models.py b/src/videoipath_automation_tool/scripts/generate_driver_models.py similarity index 86% rename from src/scripts/generate_driver_models.py rename to src/videoipath_automation_tool/scripts/generate_driver_models.py index 662eed4..262af48 100644 --- a/src/scripts/generate_driver_models.py +++ b/src/videoipath_automation_tool/scripts/generate_driver_models.py @@ -1,38 +1,30 @@ import argparse -import importlib.util import json +import os +from videoipath_automation_tool.utils.script_utils import ROOT_DIR, load_module -def load_pydantic_model_builder(): - spec = importlib.util.spec_from_file_location( - "pydantic_model_builder", "src/videoipath_automation_tool/utils/pydantic_model_builder.py" - ) - - if spec is None or spec.loader is None: - raise ValueError("Failed to load pydantic_model_builder module") - - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return module - +DEFAULT_VERSION = "2024.4.12" +DEFAULT_SCHEMA_FILE = os.path.join(ROOT_DIR, "apps", "inventory", "model", "driver_schema", f"{DEFAULT_VERSION}.json") +DEFAULT_OUTPUT_FILE = os.path.join(ROOT_DIR, "apps", "inventory", "model", "drivers.py") parser = argparse.ArgumentParser(description="Generate Pydantic models from driver schema") parser.add_argument( "schema_file", nargs="?", - default="src/videoipath_automation_tool/apps/inventory/model/driver_schema/2024.3.3.json", + default=DEFAULT_SCHEMA_FILE, help="Path to the driver schema JSON file", ) parser.add_argument( "output_file", nargs="?", - default="src/videoipath_automation_tool/apps/inventory/model/drivers.py", + default=DEFAULT_OUTPUT_FILE, help="Path where the generated Python file will be saved", ) def _generate_driver_model(driver_schema: dict) -> str: - pmb_module = load_pydantic_model_builder() + pmb_module = load_module("pydantic_model_builder", os.path.join(ROOT_DIR, "utils", "pydantic_model_builder.py")) PydanticModelBuilder = pmb_module.PydanticModelBuilder PydanticModelField = pmb_module.PydanticModelField @@ -127,9 +119,11 @@ def format_value(value: str | int | float) -> str: return field["_schema"]["type"], None -if __name__ == "__main__": - args = parser.parse_args() - schema = json.load(open(args.schema_file)) +def main( + schema_file: str = DEFAULT_SCHEMA_FILE, + output_file: str = DEFAULT_OUTPUT_FILE, +): + schema = json.load(open(schema_file)) drivers = schema["data"]["status"]["system"]["drivers"]["_items"] driver_models = "\n\n".join([_generate_driver_model(driver) for driver in drivers]) @@ -141,7 +135,7 @@ def format_value(value: str | int | float) -> str: # Notes: # - The name of the custom settings model follows the naming convention: CustomSettings___ => "." and "-" are replaced by "_"! -# - {args.schema_file} is used as reference to define the custom settings model! +# - Schema {schema_file.split("/")[-1]} is used as reference to define the custom settings model! # - The "driver_id" attribute is necessary for the discriminator, which is used to determine the correct model for the custom settings in DeviceConfiguration! # - The "alias" attribute is used to map the attribute to the correct key (with driver organization & name) in the JSON payload for the API! # - "DriverLiteral" is used to provide a list of all possible drivers in the IDEs IntelliSense! @@ -167,6 +161,11 @@ class DriverCustomSettings(ABC, BaseModel, validate_assignment=True): ... """ print("Drivers generated successfully!") - with open(args.output_file, "w") as f: + with open(output_file, "w") as f: f.write(code) - print(f"Updated {args.output_file}") + print(f"Updated {output_file}") + + +if __name__ == "__main__": + args = parser.parse_args() + main(args.schema_file, args.output_file) diff --git a/src/scripts/generate_overloads.py b/src/videoipath_automation_tool/scripts/generate_overloads.py similarity index 83% rename from src/scripts/generate_overloads.py rename to src/videoipath_automation_tool/scripts/generate_overloads.py index 4b904b2..26c4a34 100644 --- a/src/scripts/generate_overloads.py +++ b/src/videoipath_automation_tool/scripts/generate_overloads.py @@ -1,22 +1,14 @@ -import importlib.util +import os import re from typing import Callable +from videoipath_automation_tool.utils.script_utils import ROOT_DIR, load_module -def load_driver_settings(): - spec = importlib.util.spec_from_file_location( - "drivers_module", "src/videoipath_automation_tool/apps/inventory/model/drivers.py" - ) - - if spec is None or spec.loader is None: - raise ValueError("Failed to load drivers module") - - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return getattr(module, "DRIVER_ID_TO_CUSTOM_SETTINGS", {}) - - -DRIVER_ID_TO_CUSTOM_SETTINGS = load_driver_settings() +DRIVERS_MODULE = load_module( + "drivers_module", + os.path.join(ROOT_DIR, "apps", "inventory", "model", "drivers.py"), +) +DRIVER_ID_TO_CUSTOM_SETTINGS = DRIVERS_MODULE.DRIVER_ID_TO_CUSTOM_SETTINGS def generate_create_device_overloads() -> str: @@ -44,7 +36,7 @@ def generate_get_device_overloads() -> str: def generate_overloads(method: str, generate_overloads: Callable) -> None: - FILE_PATH = f"src/videoipath_automation_tool/apps/inventory/app/{method}.py" + FILE_PATH = os.path.join(ROOT_DIR, "apps", "inventory", "app", f"{method}.py") with open(FILE_PATH, "r") as f: content = f.read() @@ -70,7 +62,7 @@ def generate_overloads(method: str, generate_overloads: Callable) -> None: print(f"Updated overloads in {FILE_PATH} ✅") -if __name__ == "__main__": +def main(): overloaded_methods = { "create_device": generate_create_device_overloads, "create_device_from_discovered_device": generate_create_device_from_discovered_device_overloads, @@ -78,3 +70,7 @@ def generate_overloads(method: str, generate_overloads: Callable) -> None: } for method, generator in overloaded_methods.items(): generate_overloads(method, generator) + + +if __name__ == "__main__": + main() diff --git a/src/videoipath_automation_tool/utils/script_utils.py b/src/videoipath_automation_tool/utils/script_utils.py new file mode 100644 index 0000000..791195a --- /dev/null +++ b/src/videoipath_automation_tool/utils/script_utils.py @@ -0,0 +1,20 @@ +import importlib.util +import os +from types import ModuleType + +UTILS_DIR = os.path.dirname(os.path.abspath(__file__)) +ROOT_DIR = os.path.dirname(UTILS_DIR) + + +def load_module(module_name: str, file_path: str) -> ModuleType: + spec = importlib.util.spec_from_file_location( + module_name, + file_path, + ) + + if spec is None or spec.loader is None: + raise ValueError("Failed to load drivers module") + + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module