Spaces:
Runtime error
Runtime error
from __future__ import annotations | |
from dataclasses import dataclass | |
from enum import Enum | |
from typing import List, Literal, Optional, Type, TypedDict, Union, TYPE_CHECKING | |
from base_types import InputId | |
if TYPE_CHECKING: | |
import navi | |
InputKind = Literal[ | |
"number", | |
"slider", | |
"dropdown", | |
"text", | |
"directory", | |
"file", | |
"color", | |
"generic", | |
] | |
class InputConversion: | |
""" | |
An input conversion can be used to convert the assigned type of an input. | |
This is useful to model the changes `enforce` makes to values. | |
`type` is used to declare which type is intended to be converted by this | |
conversion. `convert` is the expression that does the actual conversion. It | |
will be given a special parameter called `Input` that will be the value to | |
convert. The `Input` parameter is guaranteed to be a non-empty sub type of | |
`type`. | |
Example: | |
To convert all numbers to string, use this conversions: | |
``` | |
InputConversion("number", "toString(Input)") | |
``` | |
""" | |
type: navi.ExpressionJson | |
convert: navi.ExpressionJson | |
def toDict(self): | |
return { | |
"type": self.type, | |
"convert": self.convert, | |
} | |
class LiteralErrorValue(TypedDict): | |
type: Literal["literal"] | |
value: str | int | float | None | |
class FormattedErrorValue(TypedDict): | |
type: Literal["formatted"] | |
formatString: str | |
class UnknownErrorValue(TypedDict): | |
type: Literal["unknown"] | |
typeName: str | |
typeModule: str | |
ErrorValue = Union[LiteralErrorValue, FormattedErrorValue, UnknownErrorValue] | |
class BaseInput: | |
def __init__( | |
self, | |
input_type: navi.ExpressionJson, | |
label: str, | |
kind: InputKind = "generic", | |
has_handle=True, | |
associated_type: Union[Type, None] = None, | |
): | |
self.input_type: navi.ExpressionJson = input_type | |
self.input_conversions: List[InputConversion] = [] | |
self.input_adapt: navi.ExpressionJson | None = None | |
self.type_definitions: str | None = None | |
self.kind: InputKind = kind | |
self.label: str = label | |
self.optional: bool = False | |
self.has_handle: bool = has_handle | |
self.id: InputId = InputId(-1) | |
self.associated_type: Type = associated_type | |
# Optional documentation | |
self.description: str | None = None | |
self.hint: bool = False | |
# This is the method that should be created by each input | |
def enforce(self, value: object): | |
"""Enforce the input type""" | |
return value | |
# This is the method that should be called by the processing code | |
def enforce_(self, value: object | None): | |
if self.optional and value is None: | |
return None | |
assert value is not None, ( | |
f"Expected value to exist, " | |
f"but does not exist for {self.kind} input with type {self.input_type} and label {self.label}" | |
) | |
return self.enforce(value) | |
def get_error_value(self, value: object) -> ErrorValue: | |
if isinstance(value, Enum): | |
# unwrap enum | |
value = value.value | |
if isinstance(value, bool): | |
# bools need to be 0 or 1 | |
return {"type": "literal", "value": int(value)} | |
if isinstance(value, (int, float, str)) or value is None: | |
return {"type": "literal", "value": value} | |
return { | |
"type": "unknown", | |
"typeName": type(value).__qualname__, | |
"typeModule": type(value).__module__, | |
} | |
def toDict(self): | |
actual_type = [self.input_type, "null"] if self.optional else self.input_type | |
return { | |
"id": self.id, | |
"type": actual_type, | |
"conversions": [c.toDict() for c in self.input_conversions], | |
"adapt": self.input_adapt, | |
"typeDefinitions": self.type_definitions, | |
"kind": self.kind, | |
"label": self.label, | |
"optional": self.optional, | |
"hasHandle": self.has_handle, | |
"description": self.description, | |
"hint": self.hint, | |
} | |
def with_id(self, input_id: InputId | int): | |
self.id = InputId(input_id) | |
return self | |
def with_docs(self, *description: str, hint=False): | |
self.description = "\n\n".join(description) | |
self.hint = hint | |
return self | |
def make_optional(self): | |
self.optional = True | |
if self.associated_type is not None: | |
associated_type = self.associated_type | |
self.associated_type = Optional[associated_type] | |
return self | |
def __repr__(self): | |
return str(self.toDict()) | |
def __iter__(self): | |
yield from self.toDict().items() | |