funcnodes-core 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- funcnodes_core-0.1.0/PKG-INFO +17 -0
- funcnodes_core-0.1.0/README.md +2 -0
- funcnodes_core-0.1.0/funcnodes_core/__init__.py +68 -0
- funcnodes_core-0.1.0/funcnodes_core/_logging.py +100 -0
- funcnodes_core-0.1.0/funcnodes_core/config.py +141 -0
- funcnodes_core-0.1.0/funcnodes_core/data.py +66 -0
- funcnodes_core-0.1.0/funcnodes_core/eventmanager.py +636 -0
- funcnodes_core-0.1.0/funcnodes_core/exceptions.py +6 -0
- funcnodes_core-0.1.0/funcnodes_core/graph.py +16 -0
- funcnodes_core-0.1.0/funcnodes_core/io.py +1067 -0
- funcnodes_core-0.1.0/funcnodes_core/lib/__init__.py +25 -0
- funcnodes_core-0.1.0/funcnodes_core/lib/lib.py +225 -0
- funcnodes_core-0.1.0/funcnodes_core/lib/libfinder.py +277 -0
- funcnodes_core-0.1.0/funcnodes_core/lib/libparser.py +66 -0
- funcnodes_core-0.1.0/funcnodes_core/node.py +1107 -0
- funcnodes_core-0.1.0/funcnodes_core/nodemaker.py +657 -0
- funcnodes_core-0.1.0/funcnodes_core/nodespace.py +424 -0
- funcnodes_core-0.1.0/funcnodes_core/triggerstack.py +103 -0
- funcnodes_core-0.1.0/funcnodes_core/utils/__init__.py +14 -0
- funcnodes_core-0.1.0/funcnodes_core/utils/data.py +103 -0
- funcnodes_core-0.1.0/funcnodes_core/utils/nodeutils.py +69 -0
- funcnodes_core-0.1.0/funcnodes_core/utils/plugins.py +10 -0
- funcnodes_core-0.1.0/funcnodes_core/utils/saving.py +92 -0
- funcnodes_core-0.1.0/funcnodes_core/utils/serialization.py +208 -0
- funcnodes_core-0.1.0/funcnodes_core/utils/special_types.py +24 -0
- funcnodes_core-0.1.0/pyproject.toml +23 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: funcnodes-core
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: core package for funcnodes
|
|
5
|
+
Author: Julian Kimmig
|
|
6
|
+
Author-email: julian.kimmig@linkdlab.de
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Requires-Dist: exposedfunctionality (>=0.3.10)
|
|
12
|
+
Requires-Dist: python-dotenv
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
Core package for funcnodes.
|
|
16
|
+
for detailed instructions go to the funcnodes repo
|
|
17
|
+
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from .io import (
|
|
2
|
+
NodeInput,
|
|
3
|
+
NodeOutput,
|
|
4
|
+
NodeIO,
|
|
5
|
+
NodeInputSerialization,
|
|
6
|
+
NodeOutputSerialization,
|
|
7
|
+
NodeConnectionError,
|
|
8
|
+
MultipleConnectionsError,
|
|
9
|
+
NoValue,
|
|
10
|
+
SameNodeConnectionError,
|
|
11
|
+
NodeIOSerialization,
|
|
12
|
+
)
|
|
13
|
+
from .utils import run_until_complete, JSONEncoder, JSONDecoder
|
|
14
|
+
from .node import Node, get_nodeclass, NodeJSON
|
|
15
|
+
from .nodespace import NodeSpace, FullNodeSpaceJSON, NodeSpaceJSON
|
|
16
|
+
from .lib import FullLibJSON, Shelf, Library, find_shelf, NodeClassNotFoundError
|
|
17
|
+
from .nodemaker import NodeClassMixin, NodeDecorator, instance_nodefunction
|
|
18
|
+
from ._logging import FUNCNODES_LOGGER, get_logger
|
|
19
|
+
|
|
20
|
+
from .data import DataEnum
|
|
21
|
+
|
|
22
|
+
from . import config
|
|
23
|
+
from .config import RenderOptions
|
|
24
|
+
|
|
25
|
+
from .utils import special_types as types
|
|
26
|
+
|
|
27
|
+
from exposedfunctionality import add_type
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"NodeInput",
|
|
31
|
+
"NodeOutput",
|
|
32
|
+
"NodeIO",
|
|
33
|
+
"NodeConnectionError",
|
|
34
|
+
"MultipleConnectionsError",
|
|
35
|
+
"SameNodeConnectionError",
|
|
36
|
+
"NodeInputSerialization",
|
|
37
|
+
"NodeOutputSerialization",
|
|
38
|
+
"Node",
|
|
39
|
+
"get_nodeclass",
|
|
40
|
+
"run_until_complete",
|
|
41
|
+
"NodeSpace",
|
|
42
|
+
"FullNodeSpaceJSON",
|
|
43
|
+
"NodeSpaceJSON",
|
|
44
|
+
"FullLibJSON",
|
|
45
|
+
"Shelf",
|
|
46
|
+
"NodeJSON",
|
|
47
|
+
"NodeClassMixin",
|
|
48
|
+
"NodeDecorator",
|
|
49
|
+
"Library",
|
|
50
|
+
"find_shelf",
|
|
51
|
+
"JSONEncoder",
|
|
52
|
+
"JSONDecoder",
|
|
53
|
+
"NodeClassNotFoundError",
|
|
54
|
+
"FUNCNODES_LOGGER",
|
|
55
|
+
"get_logger",
|
|
56
|
+
"instance_nodefunction",
|
|
57
|
+
"config",
|
|
58
|
+
"RenderOptions",
|
|
59
|
+
"NoValue",
|
|
60
|
+
"DataEnum",
|
|
61
|
+
"add_type",
|
|
62
|
+
"types",
|
|
63
|
+
"NodeIOSerialization",
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
__version__ = "0.1.0"
|
|
67
|
+
|
|
68
|
+
DEBUG = True
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from logging.handlers import RotatingFileHandler
|
|
3
|
+
from .config import CONFIG_DIR
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
LOGGINGDIR = os.path.join(CONFIG_DIR, "logs")
|
|
7
|
+
if not os.path.exists(LOGGINGDIR):
|
|
8
|
+
os.makedirs(LOGGINGDIR)
|
|
9
|
+
|
|
10
|
+
FUNCNODES_LOGGER = logging.getLogger("funcnodes")
|
|
11
|
+
|
|
12
|
+
FUNCNODES_LOGGER.setLevel(logging.DEBUG)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
ch = logging.StreamHandler()
|
|
16
|
+
|
|
17
|
+
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
|
18
|
+
|
|
19
|
+
# Add the handler to the logger
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _overwrite_add_handler(logger):
|
|
23
|
+
"""
|
|
24
|
+
Overwrites the addHandler method of the given logger.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
logger (Logger): The logger to overwrite the addHandler method for.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
None.
|
|
31
|
+
|
|
32
|
+
Examples:
|
|
33
|
+
>>> _overwrite_add_handler(FUNCNODES_LOGGER)
|
|
34
|
+
"""
|
|
35
|
+
_old_add_handler = logger.addHandler
|
|
36
|
+
|
|
37
|
+
def _new_add_handler(hdlr):
|
|
38
|
+
"""
|
|
39
|
+
Adds a handler to the given logger.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
hdlr (Handler): The handler to add to the logger.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
None.
|
|
46
|
+
|
|
47
|
+
Examples:
|
|
48
|
+
>>> _new_add_handler(ch)
|
|
49
|
+
"""
|
|
50
|
+
hdlr.setFormatter(formatter)
|
|
51
|
+
if hdlr not in logger.handlers:
|
|
52
|
+
_old_add_handler(hdlr)
|
|
53
|
+
|
|
54
|
+
logger.addHandler = _new_add_handler
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
_overwrite_add_handler(FUNCNODES_LOGGER)
|
|
58
|
+
|
|
59
|
+
FUNCNODES_LOGGER.addHandler(ch)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def set_logging_dir(path):
|
|
63
|
+
if not os.path.exists(path):
|
|
64
|
+
os.makedirs(path)
|
|
65
|
+
for hdlr in FUNCNODES_LOGGER.handlers:
|
|
66
|
+
if isinstance(hdlr, RotatingFileHandler):
|
|
67
|
+
if hdlr.baseFilename.endswith("funcnodes.log"):
|
|
68
|
+
hdlr.close()
|
|
69
|
+
FUNCNODES_LOGGER.removeHandler(hdlr)
|
|
70
|
+
|
|
71
|
+
fh = RotatingFileHandler(
|
|
72
|
+
os.path.join(path, "funcnodes.log"), maxBytes=1024 * 1024 * 5, backupCount=5
|
|
73
|
+
)
|
|
74
|
+
fh.setFormatter(formatter)
|
|
75
|
+
FUNCNODES_LOGGER.addHandler(fh)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
set_logging_dir(LOGGINGDIR)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def get_logger(name, propagate=True):
|
|
82
|
+
"""
|
|
83
|
+
Returns a logger with the given name.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
name (str): The name of the logger.
|
|
87
|
+
propagate (bool): Whether to propagate the logger's messages to its parent logger.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Logger: The logger with the given name.
|
|
91
|
+
|
|
92
|
+
Examples:
|
|
93
|
+
>>> get_logger("funcnodes")
|
|
94
|
+
"""
|
|
95
|
+
sublogger = FUNCNODES_LOGGER.getChild(name)
|
|
96
|
+
_overwrite_add_handler(sublogger)
|
|
97
|
+
sublogger.propagate = propagate
|
|
98
|
+
sublogger.addHandler(ch)
|
|
99
|
+
# _init_logger(sublogger)
|
|
100
|
+
return sublogger
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
from typing import TypedDict
|
|
2
|
+
import os
|
|
3
|
+
import json
|
|
4
|
+
from .utils import deep_fill_dict
|
|
5
|
+
|
|
6
|
+
from dotenv import load_dotenv
|
|
7
|
+
from exposedfunctionality.function_parser.types import type_to_string
|
|
8
|
+
|
|
9
|
+
load_dotenv(override=True)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
BASE_CONFIG_DIR = os.environ.get(
|
|
13
|
+
"FUNCNODES_CONFIG_DIR", os.path.join(os.path.expanduser("~"), ".funcnodes")
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
DEFAULT_CONFIG = {
|
|
17
|
+
"env_dir": os.path.join(BASE_CONFIG_DIR, "env"),
|
|
18
|
+
"worker_manager": {
|
|
19
|
+
"host": "localhost",
|
|
20
|
+
"port": 9380,
|
|
21
|
+
},
|
|
22
|
+
"frontend": {
|
|
23
|
+
"port": 8000,
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
CONFIG = DEFAULT_CONFIG
|
|
29
|
+
CONFIG_DIR = BASE_CONFIG_DIR
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def load_config(path):
|
|
33
|
+
"""
|
|
34
|
+
Loads the configuration file.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
path (str): The path to the configuration file.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
None
|
|
41
|
+
|
|
42
|
+
Examples:
|
|
43
|
+
>>> load_config("config.json")
|
|
44
|
+
"""
|
|
45
|
+
global CONFIG
|
|
46
|
+
if not os.path.exists(path):
|
|
47
|
+
config = DEFAULT_CONFIG
|
|
48
|
+
else:
|
|
49
|
+
with open(path, "r") as f:
|
|
50
|
+
config = json.load(f)
|
|
51
|
+
deep_fill_dict(config, DEFAULT_CONFIG)
|
|
52
|
+
with open(path, "w") as f:
|
|
53
|
+
json.dump(config, f, indent=2)
|
|
54
|
+
CONFIG = config
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def check_config_dir():
|
|
58
|
+
"""
|
|
59
|
+
Checks the configuration directory.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
None
|
|
63
|
+
|
|
64
|
+
Examples:
|
|
65
|
+
>>> check_config_dir()
|
|
66
|
+
"""
|
|
67
|
+
global CONFIG_DIR
|
|
68
|
+
if not os.path.exists(BASE_CONFIG_DIR):
|
|
69
|
+
os.makedirs(BASE_CONFIG_DIR)
|
|
70
|
+
load_config(os.path.join(BASE_CONFIG_DIR, "config.json"))
|
|
71
|
+
if "custom_config_dir" in CONFIG:
|
|
72
|
+
load_config(os.path.join(CONFIG["custom_config_dir"], "config.json"))
|
|
73
|
+
CONFIG_DIR = CONFIG["custom_config_dir"]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
check_config_dir()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class RenderOptions(TypedDict, total=False):
|
|
80
|
+
"""
|
|
81
|
+
A typed dictionary for render options.
|
|
82
|
+
|
|
83
|
+
Attributes:
|
|
84
|
+
typemap (dict[str, str]): A dictionary mapping types to strings.
|
|
85
|
+
inputconverter (dict[str, str]): A dictionary mapping input types to strings.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
typemap: dict[str, str]
|
|
89
|
+
inputconverter: dict[str, str]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
FUNCNODES_RENDER_OPTIONS: RenderOptions = {"typemap": {}, "inputconverter": {}}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def update_render_options(options: RenderOptions):
|
|
96
|
+
"""
|
|
97
|
+
Updates the render options.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
options (RenderOptions): The render options to update.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
None
|
|
104
|
+
|
|
105
|
+
Examples:
|
|
106
|
+
>>> update_render_options({"typemap": {"int": "int32"}, "inputconverter": {"str": "string"}})
|
|
107
|
+
"""
|
|
108
|
+
if not isinstance(options, dict):
|
|
109
|
+
return
|
|
110
|
+
if "typemap" not in options:
|
|
111
|
+
options["typemap"] = {}
|
|
112
|
+
for k, v in list(options["typemap"].items()):
|
|
113
|
+
if not isinstance(k, str):
|
|
114
|
+
del options["typemap"][k]
|
|
115
|
+
k = type_to_string(k)
|
|
116
|
+
options["typemap"][k] = v
|
|
117
|
+
|
|
118
|
+
if not isinstance(v, str):
|
|
119
|
+
v = type_to_string(v)
|
|
120
|
+
options["typemap"][k] = v
|
|
121
|
+
|
|
122
|
+
if "inputconverter" not in options:
|
|
123
|
+
options["inputconverter"] = {}
|
|
124
|
+
for k, v in list(options["inputconverter"].items()):
|
|
125
|
+
if not isinstance(k, str):
|
|
126
|
+
del options["typemap"][k]
|
|
127
|
+
k = type_to_string(k)
|
|
128
|
+
options["inputconverter"][k] = v
|
|
129
|
+
if not isinstance(v, str):
|
|
130
|
+
v = type_to_string(v)
|
|
131
|
+
options["inputconverter"][k] = v
|
|
132
|
+
FUNCNODES_RENDER_OPTIONS["inputconverter"][k] = v
|
|
133
|
+
|
|
134
|
+
# make sure its json serializable
|
|
135
|
+
try:
|
|
136
|
+
json.dumps(options)
|
|
137
|
+
except json.JSONDecodeError:
|
|
138
|
+
return
|
|
139
|
+
deep_fill_dict(
|
|
140
|
+
FUNCNODES_RENDER_OPTIONS, options, merge_lists=True, unfify_lists=True
|
|
141
|
+
)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import enum
|
|
3
|
+
from exposedfunctionality.function_parser.types import add_type
|
|
4
|
+
from typing import Union, Any, TypeVar, Type
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
ET = TypeVar("ET", bound="DataEnum")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DataEnum(enum.Enum):
|
|
11
|
+
"""
|
|
12
|
+
Base class for data enums. They should be used as a type hint for a function argument for funcnodes.
|
|
13
|
+
In the function the value can be accessed by using the v method.
|
|
14
|
+
The reson for this is to be more robust that the values to the function can be
|
|
15
|
+
passed as the Enum, as a value or as a enum key.
|
|
16
|
+
|
|
17
|
+
Example:
|
|
18
|
+
```python
|
|
19
|
+
class TestEnum(DataEnum):
|
|
20
|
+
A = 1
|
|
21
|
+
B = 2
|
|
22
|
+
C = 3
|
|
23
|
+
|
|
24
|
+
@NodeDecorator(node_id="test_enum")
|
|
25
|
+
def test_enum_node(a: TestEnum) -> int:
|
|
26
|
+
a = TestEnum.v(a)
|
|
27
|
+
return a
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init_subclass__(cls) -> None:
|
|
31
|
+
add_type(
|
|
32
|
+
cls,
|
|
33
|
+
cls.__name__,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
cls._lookup = {}
|
|
37
|
+
for member in cls:
|
|
38
|
+
cls._lookup[member.name] = member
|
|
39
|
+
try:
|
|
40
|
+
if member.value not in cls._lookup:
|
|
41
|
+
cls._lookup[member.value] = member
|
|
42
|
+
except TypeError:
|
|
43
|
+
pass
|
|
44
|
+
if str(member.value) not in cls._lookup:
|
|
45
|
+
cls._lookup[str(member.value)] = member
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def interfere(cls: Type[ET], a: Union[ET, str, Any]) -> ET:
|
|
49
|
+
if isinstance(a, cls):
|
|
50
|
+
return a
|
|
51
|
+
if a in cls._lookup:
|
|
52
|
+
return cls._lookup[a]
|
|
53
|
+
try:
|
|
54
|
+
return cls(a)
|
|
55
|
+
except ValueError as e:
|
|
56
|
+
if isinstance(a, str):
|
|
57
|
+
if a.startswith(cls.__name__ + "."):
|
|
58
|
+
a = a[len(cls.__name__) + 1 :]
|
|
59
|
+
if a in cls._lookup:
|
|
60
|
+
return cls._lookup[a]
|
|
61
|
+
|
|
62
|
+
raise e
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def v(cls: Type[ET], a: Union[ET, str, Any]) -> Any:
|
|
66
|
+
return cls.interfere(a).value
|