Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

basic models for HeavyLang and HeavyIR objects #210

Merged
merged 11 commits into from
Nov 4, 2024
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Features:
* DPF: Allow host transport events without midi input
* JS: midi out and device select by @Reinissance
* Docs: general updates/corrections and improvements
* Use pydantic to validate and access HeavyLang and HeavyIR objects

Bugfixes:

Expand Down
53 changes: 53 additions & 0 deletions hvcc/core/hv2ir/types/IR.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from pydantic import BaseModel, RootModel
from typing import Dict, List, Optional, Union, Literal


IRConnectionType = Literal["-->", "~i>", "~f>", "signal"]


class IRArg(BaseModel):
name: str
value_type: str
description: str = ""
default: Union[float, int, str, List[float], List[int], None] = None
required: bool


class IR(BaseModel):
control: bool
signal: bool
init: bool


class Perf(BaseModel):
avx: float = 0
sse: float = 0
neon: float = 0


class IRNode(BaseModel):
inlets: List[IRConnectionType]
ir: IR
outlets: List[IRConnectionType]
args: List[IRArg] = []
perf: Optional[Perf] = Perf()
# perf: Perf
description: Optional[str] = None
alias: List[str] = []
tags: List[str] = []
keywords: List[str] = []


class HeavyIRType(RootModel):
root: Dict[str, IRNode]


if __name__ == "__main__":
import json
import importlib_resources

heavy_ir_json = importlib_resources.files('hvcc') / 'core/json/heavy.ir.json'
with open(heavy_ir_json, "r") as f:
data = json.load(f)
heavy_ir = HeavyIRType(root=data)
print(heavy_ir.root.keys())
49 changes: 49 additions & 0 deletions hvcc/core/hv2ir/types/Lang.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from pydantic import BaseModel, RootModel
from typing import List, Optional, Dict, Literal, Union


LangConnectionType = Literal["-->", "-~>", "~f>"]


class LangArg(BaseModel):
name: str
value_type: Optional[str]
description: str
default: Union[float, int, str, Dict, List, None] = None
required: bool


class Inlet(BaseModel):
name: str
connectionType: LangConnectionType
description: str


class Outlet(BaseModel):
name: str
connectionType: LangConnectionType
description: str


class LangNode(BaseModel):
description: str
inlets: List[Inlet]
outlets: List[Outlet]
args: List[LangArg]
alias: List[str]
tags: List[str]


class HeavyLangType(RootModel):
root: Dict[str, LangNode]


if __name__ == "__main__":
import json
import importlib_resources

heavy_lang_json = importlib_resources.files('hvcc') / 'core/json/heavy.lang.json'
with open(heavy_lang_json, "r") as f:
data = json.load(f)
heavy_lang = HeavyLangType(root=data)
print(heavy_lang.root.keys())
2 changes: 2 additions & 0 deletions hvcc/core/hv2ir/types/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .IR import HeavyIRType, IRNode, IRArg # noqa
from .Lang import HeavyLangType, LangNode, LangArg # noqa
63 changes: 36 additions & 27 deletions hvcc/interpreters/pd2hv/HeavyObject.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
import decimal
import json
import importlib_resources
from typing import Optional, List, Dict, Any
from typing import Optional, List, Dict, Any, Union, cast

from hvcc.core.hv2ir.types import HeavyIRType, HeavyLangType, IRNode, LangNode, IRArg, LangArg
from .Connection import Connection
from .NotificationEnum import NotificationEnum
from .PdObject import PdObject
Expand All @@ -28,11 +29,11 @@ class HeavyObject(PdObject):

heavy_lang_json = importlib_resources.files('hvcc') / 'core/json/heavy.lang.json'
with open(heavy_lang_json, "r") as f:
__HEAVY_LANG_OBJS = json.load(f)
__HEAVY_LANG_OBJS = HeavyLangType(json.load(f))

heavy_ir_json = importlib_resources.files('hvcc') / 'core/json/heavy.ir.json'
with open(heavy_ir_json, "r") as f:
__HEAVY_IR_OBJS = json.load(f)
__HEAVY_IR_OBJS = HeavyIRType(json.load(f))

def __init__(
self,
Expand All @@ -43,35 +44,39 @@ def __init__(
) -> None:
super().__init__(obj_type, obj_args, pos_x, pos_y)

self.__obj_dict: Union[IRNode, LangNode]

# get the object dictionary (note that it is NOT a copy)
if self.is_hvlang:
self.__obj_dict = self.__HEAVY_LANG_OBJS[obj_type]
self.__obj_dict = self.__HEAVY_LANG_OBJS.root[obj_type]
elif self.is_hvir:
self.__obj_dict = self.__HEAVY_IR_OBJS[obj_type]
self.__obj_dict = self.__HEAVY_IR_OBJS.root[obj_type]
else:
assert False, f"{obj_type} is not a Heavy Lang or IR object."

# resolve arguments
obj_args = obj_args or []
self.obj_dict = {}
for i, a in enumerate(self.__obj_dict["args"]):

for i, a in enumerate(self.__obj_dict.args):
arg = cast(Union[IRArg, LangArg], a)
# if the argument exists (and has been correctly resolved)
if i < len(obj_args) and obj_args[i] is not None:
# force the Heavy argument type
# Catch type errors as early as possible
try:
self.obj_dict[a["name"]] = self.force_arg_type(
self.obj_dict[arg.name] = self.force_arg_type(
obj_args[i],
a["value_type"])
arg.value_type)
except Exception as e:
self.add_error(
f"Heavy {obj_type} cannot convert argument \"{a['name']}\""
f" with value \"{obj_args[i]}\" to type {a['value_type']}: {e}")
f"Heavy {obj_type} cannot convert argument \"{arg.name}\""
f" with value \"{obj_args[i]}\" to type {arg.value_type}: {e}")
else:
# the default argument is required
if a["required"]:
if arg.required:
self.add_error(
f"Required argument \"{a['name']}\" to object {obj_type} not present: {obj_args}")
f"Required argument \"{arg.name}\" to object {obj_type} not present: {obj_args}")
else:
# don't worry about supplying a default,
# let hv2ir take care of it. pd2hv only passes on the
Expand All @@ -87,7 +92,11 @@ def __init__(
self.__annotations["scope"] = "public"

@classmethod
def force_arg_type(cls, value: str, value_type: str) -> Any:
def force_arg_type(
cls,
value: str,
value_type: Optional[str] = None
) -> Any:
# TODO(mhroth): add support for mixedarray?
if value_type == "auto":
try:
Expand Down Expand Up @@ -135,24 +144,24 @@ def force_arg_type(cls, value: str, value_type: str) -> Any:

@property
def is_hvlang(self) -> bool:
return self.obj_type in self.__HEAVY_LANG_OBJS
return self.obj_type in self.__HEAVY_LANG_OBJS.root.keys()

@property
def is_hvir(self) -> bool:
return self.obj_type in self.__HEAVY_IR_OBJS
return self.obj_type in self.__HEAVY_IR_OBJS.root.keys()

def get_inlet_connection_type(self, inlet_index: int) -> Optional[str]:
""" Returns the inlet connection type, None if the inlet does not exist.
"""
# TODO(mhroth): it's stupid that hvlang and hvir json have different data formats here
if self.is_hvlang:
if len(self.__obj_dict["inlets"]) > inlet_index:
return self.__obj_dict["inlets"][inlet_index]["connectionType"]
if self.is_hvlang and isinstance(self.__obj_dict, LangNode):
if len(self.__obj_dict.inlets) > inlet_index:
return self.__obj_dict.inlets[inlet_index].connectionType
else:
return None
elif self.is_hvir:
if len(self.__obj_dict["inlets"]) > inlet_index:
return self.__obj_dict["inlets"][inlet_index]
elif self.is_hvir and isinstance(self.__obj_dict, IRNode):
if len(self.__obj_dict.inlets) > inlet_index:
return self.__obj_dict.inlets[inlet_index]
else:
return None
else:
Expand All @@ -162,14 +171,14 @@ def get_outlet_connection_type(self, outlet_index: int) -> Optional[str]:
""" Returns the outlet connection type, None if the inlet does not exist.
"""
# TODO(mhroth): it's stupid that hvlang and hvir json have different data formats here
if self.is_hvlang:
if len(self.__obj_dict["outlets"]) > outlet_index:
return self.__obj_dict["outlets"][outlet_index]["connectionType"]
if self.is_hvlang and isinstance(self.__obj_dict, LangNode):
if len(self.__obj_dict.outlets) > outlet_index:
return self.__obj_dict.outlets[outlet_index].connectionType
else:
return None
elif self.is_hvir:
if len(self.__obj_dict["outlets"]) > outlet_index:
return self.__obj_dict["outlets"][outlet_index]
elif self.is_hvir and isinstance(self.__obj_dict, IRNode):
if len(self.__obj_dict.outlets) > outlet_index:
return self.__obj_dict.outlets[outlet_index]
else:
return None
else:
Expand Down
11 changes: 9 additions & 2 deletions hvcc/interpreters/pd2hv/PdGraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import os
from typing import Optional, List, Dict
from typing import Optional, List, Dict, Any

from .Connection import Connection
from .NotificationEnum import NotificationEnum
Expand Down Expand Up @@ -118,7 +118,14 @@ def add_parsed_connection(self, from_index: int, from_outlet: int, to_index: int
"Have all inlets and outlets been declared?",
NotificationEnum.ERROR_UNABLE_TO_CONNECT_OBJECTS)

def add_hv_arg(self, arg_index: int, name: str, value_type: str, default_value: str, required: bool) -> None:
def add_hv_arg(
self,
arg_index: int,
name: str,
value_type: str,
default_value: Optional[Any],
required: bool
) -> None:
""" Add a Heavy argument to the graph. Indicies are from zero (not one, like Pd).
"""
# ensure that self.hv_args is big enough, as heavy arguments are not
Expand Down
10 changes: 5 additions & 5 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,8 @@ Heavy = { source = "hvcc/__init__.py", type = "onefile", bundle = true, arch = "
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

[tool.mypy]
plugins = [
"pydantic.mypy"
]
12 changes: 10 additions & 2 deletions requirements-test.txt
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
annotated-types==0.7.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
colorama==0.4.6 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" and sys_platform == "win32"
coverage[toml]==7.6.1 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
exceptiongroup==1.2.2 ; python_full_version >= "3.8.1" and python_version < "3.11"
flake8==7.1.1 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
importlib-resources==6.4.5 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
iniconfig==2.0.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
jinja2==3.1.4 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
markupsafe==2.1.5 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
mccabe==0.7.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
mypy-extensions==1.0.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
mypy==1.11.2 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
mypy==1.13.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
numpy==1.24.4 ; python_full_version >= "3.8.1" and python_version == "3.8"
numpy==2.0.2 ; python_version >= "3.9" and python_version < "4.0"
packaging==24.1 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
pluggy==1.5.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
pycodestyle==2.12.1 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
pydantic-core==2.23.4 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
pydantic==2.9.2 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
pyflakes==3.2.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
pytest-cov==5.0.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
pytest==8.3.3 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
scipy==1.10.1 ; python_full_version >= "3.8.1" and python_version == "3.8"
scipy==1.13.1 ; python_version >= "3.9" and python_version < "4.0"
tomli==2.0.1 ; python_full_version >= "3.8.1" and python_full_version <= "3.11.0a6"
tomli==2.0.2 ; python_full_version >= "3.8.1" and python_full_version <= "3.11.0a6"
typing-extensions==4.12.2 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
wstd2daisy==0.5.3 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
zipp==3.20.2 ; python_full_version >= "3.8.1" and python_version < "3.10"
Loading