File size: 5,749 Bytes
5e57c6d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
import json
import os
import re

from typing import Union


class AnyType(str):
  """A special class that is always equal in not equal comparisons. Credit to pythongosssss"""

  def __ne__(self, __value: object) -> bool:
    return False


class FlexibleOptionalInputType(dict):
  """A special class to make flexible nodes that pass data to our python handlers.



  Enables both flexible/dynamic input types (like for Any Switch) or a dynamic number of inputs

  (like for Any Switch, Context Switch, Context Merge, Power Lora Loader, etc).



  Initially, ComfyUI only needed to return True for `__contains__` below, which told ComfyUI that

  our node will handle the input, regardless of what it is.



  However, after https://github.com/comfyanonymous/ComfyUI/pull/2666 ComdyUI's execution changed

  also checking the data for the key; specifcially, the type which is the first tuple entry. This

  type is supplied to our FlexibleOptionalInputType and returned for any non-data key. This can be a

  real type, or use the AnyType for additional flexibility.

  """

  def __init__(self, type, data: Union[dict, None] = None):
    """Initializes the FlexibleOptionalInputType.



    Args:

      type: The flexible type to use when ComfyUI retrieves an unknown key (via `__getitem__`).

      data: An optional dict to use as the basis. This is stored both in a `data` attribute, so we

        can look it up without hitting our overrides, as well as iterated over and adding its key

        and values to our `self` keys. This way, when looked at, we will appear to represent this

        data. When used in an "optional" INPUT_TYPES, these are the starting optional node types.

    """
    self.type = type
    self.data = data
    if self.data is not None:
      for k, v in self.data.items():
        self[k] = v

  def __getitem__(self, key):
    # If we have this key in the initial data, then return it. Otherwise return the tuple with our
    # flexible type.
    if self.data is not None and key in self.data:
      val = self.data[key]
      return val
    return (self.type,)

  def __contains__(self, key):
    """Always contain a key, and we'll always return the tuple above when asked for it."""
    return True


any_type = AnyType("*")


def is_dict_value_falsy(data: dict, dict_key: str):
  """Checks if a dict value is falsy."""
  val = get_dict_value(data, dict_key)
  return not val


def get_dict_value(data: dict, dict_key: str, default=None):
  """Gets a deeply nested value given a dot-delimited key."""
  keys = dict_key.split('.')
  key = keys.pop(0) if len(keys) > 0 else None
  found = data[key] if key in data else None
  if found is not None and len(keys) > 0:
    return get_dict_value(found, '.'.join(keys), default)
  return found if found is not None else default


def set_dict_value(data: dict, dict_key: str, value, create_missing_objects=True):
  """Sets a deeply nested value given a dot-delimited key."""
  keys = dict_key.split('.')
  key = keys.pop(0) if len(keys) > 0 else None
  if key not in data:
    if create_missing_objects is False:
      return data
    data[key] = {}
  if len(keys) == 0:
    data[key] = value
  else:
    set_dict_value(data[key], '.'.join(keys), value, create_missing_objects)

  return data


def dict_has_key(data: dict, dict_key):
  """Checks if a dict has a deeply nested dot-delimited key."""
  keys = dict_key.split('.')
  key = keys.pop(0) if len(keys) > 0 else None
  if key is None or key not in data:
    return False
  if len(keys) == 0:
    return True
  return dict_has_key(data[key], '.'.join(keys))


def load_json_file(file: str, default=None):
  """Reads a json file and returns the json dict, stripping out "//" comments first."""
  if path_exists(file):
    with open(file, 'r', encoding='UTF-8') as file:
      config = file.read()
      try:
        return json.loads(config)
      except json.decoder.JSONDecodeError:
        try:
          config = re.sub(r"^\s*//\s.*", "", config, flags=re.MULTILINE)
          return json.loads(config)
        except json.decoder.JSONDecodeError:
          try:
            config = re.sub(r"(?:^|\s)//.*", "", config, flags=re.MULTILINE)
            return json.loads(config)
          except json.decoder.JSONDecodeError:
            pass
  return default


def save_json_file(file_path: str, data: dict):
  """Saves a json file."""
  os.makedirs(os.path.dirname(file_path), exist_ok=True)
  with open(file_path, 'w+', encoding='UTF-8') as file:
    json.dump(data, file, sort_keys=False, indent=2, separators=(",", ": "))


def path_exists(path):
  """Checks if a path exists, accepting None type."""
  if path is not None:
    return os.path.exists(path)
  return False


def file_exists(path):
  """Checks if a file exists, accepting None type."""
  if path is not None:
    return os.path.isfile(path)
  return False


def remove_path(path):
  """Removes a path, if it exists."""
  if path_exists(path):
    os.remove(path)
    return True
  return False

def abspath(file_path: str):
  """Resolves the abspath of a file, resolving symlinks and user dirs."""
  if file_path and not path_exists(file_path):
    maybe_path = os.path.abspath(os.path.realpath(os.path.expanduser(file_path)))
    file_path = maybe_path if path_exists(maybe_path) else file_path
  return file_path

class ByPassTypeTuple(tuple):
  """A special class that will return additional "AnyType" strings beyond defined values.

  Credit to Trung0246

  """

  def __getitem__(self, index):
    if index > len(self) - 1:
      return AnyType("*")
    return super().__getitem__(index)