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.
@@ -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,2 @@
1
+ Core package for funcnodes.
2
+ for detailed instructions go to the funcnodes repo
@@ -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