Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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"]
Expand Down
Original file line number Diff line number Diff line change
@@ -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_<driver_organization>_<driver_name>_<driver_version> => "." 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!
Expand Down
27 changes: 27 additions & 0 deletions src/videoipath_automation_tool/scripts/generate_all.py
Original file line number Diff line number Diff line change
@@ -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()
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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])
Expand All @@ -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_<driver_organization>_<driver_name>_<driver_version> => "." 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!
Expand All @@ -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)
Original file line number Diff line number Diff line change
@@ -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:
Expand Down Expand Up @@ -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()
Expand All @@ -70,11 +62,15 @@ 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,
"get_device": generate_get_device_overloads,
}
for method, generator in overloaded_methods.items():
generate_overloads(method, generator)


if __name__ == "__main__":
main()
20 changes: 20 additions & 0 deletions src/videoipath_automation_tool/utils/script_utils.py
Original file line number Diff line number Diff line change
@@ -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