Skip to content

Commit 3de2cba

Browse files
Merge pull request #33 from SWR-MoIP/25-configurable-versioning
Configurable Versioning
2 parents 767aaa4 + 3cc5eb6 commit 3de2cba

6 files changed

Lines changed: 95 additions & 46 deletions

File tree

pyproject.toml

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,26 @@ license = { text = "AGPL-3.0-only" }
66
license-files = ["LICENSE"]
77
readme = "README.md"
88
requires-python = ">=3.11"
9-
authors = [
10-
{ name = "Paul Winterstein", email = "paul.winterstein@swr.de" },
11-
]
9+
authors = [{ name = "Paul Winterstein", email = "paul.winterstein@swr.de" }]
1210
maintainers = [
1311
{ name = "SWR Media-over-IP Team", email = "moip@swr.de" },
1412
{ name = "Josia Hildebrandt", email = "manuel_josia.hildebrandt@swr.de" },
1513
]
16-
keywords = ["videoipath", "automation", "nevion", "media-over-ip", "st2110", "orchestration"]
14+
keywords = [
15+
"videoipath",
16+
"automation",
17+
"nevion",
18+
"media-over-ip",
19+
"st2110",
20+
"orchestration",
21+
]
1722
dependencies = [
1823
"requests (>=2.31.0,<3.0.0)",
1924
"pydantic (>=2.6.4,<3.0.0)",
2025
"pydantic-extra-types (>=2.6.0,<3.0.0)",
2126
"pydantic-settings (>=2.2.1,<3.0.0)",
2227
"urllib3 (>=2.2.3,<3.0.0)",
23-
"deepdiff (>=8.1.1,<9.0.0)"
28+
"deepdiff (>=8.1.1,<9.0.0)",
2429
]
2530

2631
[project.urls]
@@ -51,6 +56,8 @@ env_files = ["tests/.env.test"]
5156
[virtualenvs]
5257
in-project = true
5358

59+
[project.scripts]
60+
set-videoipath-version = "videoipath_automation_tool.scripts.generate_all:main"
5461

5562
[tool.ruff]
5663
include = ["pyproject.toml", "src/**/*.py", "tests/**/*.py"]

src/videoipath_automation_tool/apps/inventory/model/drivers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
from abc import ABC
2-
from typing import Dict, Literal, Type, TypeVar, Union, Optional
2+
from typing import Dict, Literal, Optional, Type, TypeVar, Union
33

44
from pydantic import BaseModel, Field
55

66
# Notes:
77
# - The name of the custom settings model follows the naming convention: CustomSettings_<driver_organization>_<driver_name>_<driver_version> => "." and "-" are replaced by "_"!
8-
# - src/videoipath_automation_tool/apps/inventory/model/driver_schema/2024.1.4.json is used as reference to define the custom settings model!
8+
# - Schema 2024.4.12.json is used as reference to define the custom settings model!
99
# - The "driver_id" attribute is necessary for the discriminator, which is used to determine the correct model for the custom settings in DeviceConfiguration!
1010
# - The "alias" attribute is used to map the attribute to the correct key (with driver organization & name) in the JSON payload for the API!
1111
# - "DriverLiteral" is used to provide a list of all possible drivers in the IDEs IntelliSense!
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import argparse
2+
import os
3+
4+
from videoipath_automation_tool.scripts.generate_driver_models import main as generate_driver_models
5+
from videoipath_automation_tool.scripts.generate_overloads import main as generate_overloads
6+
from videoipath_automation_tool.utils.script_utils import ROOT_DIR
7+
8+
parser = argparse.ArgumentParser(description="Generate all version-specific code for a given VideoIPath version")
9+
parser.add_argument("version", help="Version of VideoIPath to use", default="2024.4.12", nargs="?")
10+
11+
12+
def main():
13+
args = parser.parse_args()
14+
schema_file = os.path.join(ROOT_DIR, "apps", "inventory", "model", "driver_schema", f"{args.version}.json")
15+
16+
if not os.path.exists(schema_file):
17+
print(
18+
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."
19+
)
20+
exit(1)
21+
22+
generate_driver_models(schema_file)
23+
generate_overloads()
24+
25+
26+
if __name__ == "__main__":
27+
main()

src/scripts/generate_driver_models.py renamed to src/videoipath_automation_tool/scripts/generate_driver_models.py

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,30 @@
11
import argparse
2-
import importlib.util
32
import json
3+
import os
44

5+
from videoipath_automation_tool.utils.script_utils import ROOT_DIR, load_module
56

6-
def load_pydantic_model_builder():
7-
spec = importlib.util.spec_from_file_location(
8-
"pydantic_model_builder", "src/videoipath_automation_tool/utils/pydantic_model_builder.py"
9-
)
10-
11-
if spec is None or spec.loader is None:
12-
raise ValueError("Failed to load pydantic_model_builder module")
13-
14-
module = importlib.util.module_from_spec(spec)
15-
spec.loader.exec_module(module)
16-
return module
17-
7+
DEFAULT_VERSION = "2024.4.12"
8+
DEFAULT_SCHEMA_FILE = os.path.join(ROOT_DIR, "apps", "inventory", "model", "driver_schema", f"{DEFAULT_VERSION}.json")
9+
DEFAULT_OUTPUT_FILE = os.path.join(ROOT_DIR, "apps", "inventory", "model", "drivers.py")
1810

1911
parser = argparse.ArgumentParser(description="Generate Pydantic models from driver schema")
2012
parser.add_argument(
2113
"schema_file",
2214
nargs="?",
23-
default="src/videoipath_automation_tool/apps/inventory/model/driver_schema/2024.3.3.json",
15+
default=DEFAULT_SCHEMA_FILE,
2416
help="Path to the driver schema JSON file",
2517
)
2618
parser.add_argument(
2719
"output_file",
2820
nargs="?",
29-
default="src/videoipath_automation_tool/apps/inventory/model/drivers.py",
21+
default=DEFAULT_OUTPUT_FILE,
3022
help="Path where the generated Python file will be saved",
3123
)
3224

3325

3426
def _generate_driver_model(driver_schema: dict) -> str:
35-
pmb_module = load_pydantic_model_builder()
27+
pmb_module = load_module("pydantic_model_builder", os.path.join(ROOT_DIR, "utils", "pydantic_model_builder.py"))
3628
PydanticModelBuilder = pmb_module.PydanticModelBuilder
3729
PydanticModelField = pmb_module.PydanticModelField
3830

@@ -127,9 +119,11 @@ def format_value(value: str | int | float) -> str:
127119
return field["_schema"]["type"], None
128120

129121

130-
if __name__ == "__main__":
131-
args = parser.parse_args()
132-
schema = json.load(open(args.schema_file))
122+
def main(
123+
schema_file: str = DEFAULT_SCHEMA_FILE,
124+
output_file: str = DEFAULT_OUTPUT_FILE,
125+
):
126+
schema = json.load(open(schema_file))
133127

134128
drivers = schema["data"]["status"]["system"]["drivers"]["_items"]
135129
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:
141135
142136
# Notes:
143137
# - The name of the custom settings model follows the naming convention: CustomSettings_<driver_organization>_<driver_name>_<driver_version> => "." and "-" are replaced by "_"!
144-
# - {args.schema_file} is used as reference to define the custom settings model!
138+
# - Schema {schema_file.split("/")[-1]} is used as reference to define the custom settings model!
145139
# - The "driver_id" attribute is necessary for the discriminator, which is used to determine the correct model for the custom settings in DeviceConfiguration!
146140
# - The "alias" attribute is used to map the attribute to the correct key (with driver organization & name) in the JSON payload for the API!
147141
# - "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): ...
167161
"""
168162
print("Drivers generated successfully!")
169163

170-
with open(args.output_file, "w") as f:
164+
with open(output_file, "w") as f:
171165
f.write(code)
172-
print(f"Updated {args.output_file}")
166+
print(f"Updated {output_file}")
167+
168+
169+
if __name__ == "__main__":
170+
args = parser.parse_args()
171+
main(args.schema_file, args.output_file)

src/scripts/generate_overloads.py renamed to src/videoipath_automation_tool/scripts/generate_overloads.py

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,14 @@
1-
import importlib.util
1+
import os
22
import re
33
from typing import Callable
44

5+
from videoipath_automation_tool.utils.script_utils import ROOT_DIR, load_module
56

6-
def load_driver_settings():
7-
spec = importlib.util.spec_from_file_location(
8-
"drivers_module", "src/videoipath_automation_tool/apps/inventory/model/drivers.py"
9-
)
10-
11-
if spec is None or spec.loader is None:
12-
raise ValueError("Failed to load drivers module")
13-
14-
module = importlib.util.module_from_spec(spec)
15-
spec.loader.exec_module(module)
16-
return getattr(module, "DRIVER_ID_TO_CUSTOM_SETTINGS", {})
17-
18-
19-
DRIVER_ID_TO_CUSTOM_SETTINGS = load_driver_settings()
7+
DRIVERS_MODULE = load_module(
8+
"drivers_module",
9+
os.path.join(ROOT_DIR, "apps", "inventory", "model", "drivers.py"),
10+
)
11+
DRIVER_ID_TO_CUSTOM_SETTINGS = DRIVERS_MODULE.DRIVER_ID_TO_CUSTOM_SETTINGS
2012

2113

2214
def generate_create_device_overloads() -> str:
@@ -44,7 +36,7 @@ def generate_get_device_overloads() -> str:
4436

4537

4638
def generate_overloads(method: str, generate_overloads: Callable) -> None:
47-
FILE_PATH = f"src/videoipath_automation_tool/apps/inventory/app/{method}.py"
39+
FILE_PATH = os.path.join(ROOT_DIR, "apps", "inventory", "app", f"{method}.py")
4840

4941
with open(FILE_PATH, "r") as f:
5042
content = f.read()
@@ -70,11 +62,15 @@ def generate_overloads(method: str, generate_overloads: Callable) -> None:
7062
print(f"Updated overloads in {FILE_PATH} ✅")
7163

7264

73-
if __name__ == "__main__":
65+
def main():
7466
overloaded_methods = {
7567
"create_device": generate_create_device_overloads,
7668
"create_device_from_discovered_device": generate_create_device_from_discovered_device_overloads,
7769
"get_device": generate_get_device_overloads,
7870
}
7971
for method, generator in overloaded_methods.items():
8072
generate_overloads(method, generator)
73+
74+
75+
if __name__ == "__main__":
76+
main()
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import importlib.util
2+
import os
3+
from types import ModuleType
4+
5+
UTILS_DIR = os.path.dirname(os.path.abspath(__file__))
6+
ROOT_DIR = os.path.dirname(UTILS_DIR)
7+
8+
9+
def load_module(module_name: str, file_path: str) -> ModuleType:
10+
spec = importlib.util.spec_from_file_location(
11+
module_name,
12+
file_path,
13+
)
14+
15+
if spec is None or spec.loader is None:
16+
raise ValueError("Failed to load drivers module")
17+
18+
module = importlib.util.module_from_spec(spec)
19+
spec.loader.exec_module(module)
20+
return module

0 commit comments

Comments
 (0)