diff --git a/__init__.py b/__init__.py
index acea2cf..37cddc6 100644
--- a/__init__.py
+++ b/__init__.py
@@ -7,6 +7,9 @@ from .modules.cda.cda_show_float import CDAShowFloat
from .modules.cda.cda_show_int import CDAShowInt
from .modules.cda.cda_string_create import CDAStringCreate
from .modules.cda.cda_text_create import CDATextCreate
+from .modules.cda.py_kv_float_create import PyKvFloatCreate
+from .modules.cda.py_kv_int_create import PyKvIntCreate
+from .modules.cda.py_kv_string_create import PyKvStringCreate
from .modules.matplotlib.mpl_bar import MPLBar
from .modules.matplotlib.mpl_line import MPLLine
from .modules.matplotlib.mpl_scatter import MPLScatter
@@ -23,6 +26,9 @@ from .modules.pandas_wrapper.pandas_cos import PandasCos
from .modules.pandas_wrapper.pandas_count import PandasCount
from .modules.pandas_wrapper.pandas_cov import PandasCov
from .modules.pandas_wrapper.pandas_create import PandasCreate
+from .modules.pandas_wrapper.pandas_create_from_dict import PandasCreateFromDict
+from .modules.pandas_wrapper.pandas_create_from_multiple_dict import PandasCreateFromMultipleDict
+from .modules.pandas_wrapper.pandas_create_series_from_dict import PandasCreateSeriesFromDict
from .modules.pandas_wrapper.pandas_create_with_index import PandasCreateWithIndex
from .modules.pandas_wrapper.pandas_cummax import PandasCummax
from .modules.pandas_wrapper.pandas_cummin import PandasCummin
@@ -141,6 +147,9 @@ NODE_CLASS_MAPPINGS: Dict[str, Type[T]] = {
"PandasCount": PandasCount,
"PandasCov": PandasCov,
"PandasCreate": PandasCreate,
+ "PandasCreateFromDict": PandasCreateFromDict,
+ "PandasCreateFromMultipleDict": PandasCreateFromMultipleDict,
+ "PandasCreateSeriesFromDict": PandasCreateSeriesFromDict,
"PandasCreateWithIndex": PandasCreateWithIndex,
"PandasCummax": PandasCummax,
"PandasCummin": PandasCummin,
@@ -223,6 +232,9 @@ NODE_CLASS_MAPPINGS: Dict[str, Type[T]] = {
"PandasVerticalConcat": PandasVerticalConcat,
"PandasVerticalSplit": PandasVerticalSplit,
"PandasXs": PandasXs,
+ "PyKvFloatCreate": PyKvFloatCreate,
+ "PyKvIntCreate": PyKvIntCreate,
+ "PyKvStringCreate": PyKvStringCreate,
"SNSBar": SNSBar,
"SNSHistogram": SNSHistogram,
"SNSLine": SNSLine,
@@ -259,6 +271,9 @@ NODE_DISPLAY_NAME_MAPPINGS: Dict[str, str] = {
"PandasCount": "Pandas Count",
"PandasCov": "Pandas Cov",
"PandasCreate": "Pandas Create",
+ "PandasCreateFromDict": "Pandas Create From Dict",
+ "PandasCreateFromMultipleDict": "Pandas Create From Multiple Dict",
+ "PandasCreateSeriesFromDict": "Pandas Create Series From Dict",
"PandasCreateWithIndex": "Pandas Create With Index",
"PandasCummax": "Pandas Cummax",
"PandasCummin": "Pandas Cummin",
@@ -341,6 +356,9 @@ NODE_DISPLAY_NAME_MAPPINGS: Dict[str, str] = {
"PandasVerticalConcat": "Pandas Vertical Concat",
"PandasVerticalSplit": "Pandas Vertical Split",
"PandasXs": "Pandas Xs",
+ "PyKvFloatCreate": "Py Kv Float Create",
+ "PyKvIntCreate": "Py Kv Int Create",
+ "PyKvStringCreate": "Py Kv String Create",
"SNSBar": "SNS Bar Chart",
"SNSHistogram": "SNS Histogram",
"SNSLine": "SNS Line Plot",
diff --git a/dev/scripts/run_tests.sh b/dev/scripts/run_tests.sh
new file mode 100755
index 0000000..1bc4915
--- /dev/null
+++ b/dev/scripts/run_tests.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+# Run this command on project root
+# ```
+# dev/scripts/run_tests.sh
+# ````
+export PYTHONPATH=modules:$PYTHONPATH
+echo $PYTHONPATH
+python -m unittest discover -s tests -p "*_test.py" -t .
+
diff --git a/docs/reference/node_reference.md b/docs/reference/node_reference.md
index 4b4c78f..f838250 100644
--- a/docs/reference/node_reference.md
+++ b/docs/reference/node_reference.md
@@ -92,6 +92,9 @@
| [CDA String Create](cda_string_create.md) | Creates a Python String using a value entered in a single-line text field. |
| [CDA Text Create](cda_text_create.md) | Creates a Python String using a value entered in a multi-line text field. |
| [Pandas Create](pandas_create.md) | Creates a pandas DataFrame using values entered in the text field. |
+| [Pandas Create From Dict](pandas_create_from_dict.md) | Creates a pandas DataFrame from a Python dictionary. |
+| [Pandas Create From Multiple Dict](pandas_create_from_multiple_dict.md) | Creates a pandas DataFrame from multiple Python dictionaries. |
+| [Pandas Create Series From Dict](pandas_create_series_from_dict.md) | Creates a Pandas Series from a Python dictionary. |
| [Pandas Create With Index](pandas_create_with_index.md) | Creates a pandas DataFrame using values entered in the text field. Input data is assumed to have an index. |
| [Pandas Load CSV](pandas_load_csv.md) | Loads CSV files into a pandas DataFrame. |
| [Pandas Load CSV With Encoding](pandas_load_csv_with_encoding.md) | Loads CSV files into a pandas DataFrame. |
@@ -118,6 +121,12 @@
| [SNS Histogram](sns_histogram.md) | Generates a histogram from a Pandas DataFrame using Seaborn. |
| [SNS Line Plot](sns_line.md) | Generates a line plot from a pandas DataFrame using Seaborn. |
| [SNS Scatter Plot](sns_scatter.md) | Generates a scatter plot from a pandas DataFrame using Seaborn. |
+## Python wrapper
+| Node | Description |
+| --- | --- |
+| [Py Kv Float Create](py_kv_float_create.md) | Creates a Python dictionary with a string key and a float value. |
+| [Py Kv Int Create](py_kv_int_create.md) | Creates a Python dictionary with an string key and an integer value. |
+| [Py Kv String Create](py_kv_string_create.md) | Creates a Python dictionary with a string key and a string value. |
## Summary statistics
| Node | Description |
| --- | --- |
diff --git a/docs/reference/pandas_create_from_dict.md b/docs/reference/pandas_create_from_dict.md
new file mode 100644
index 0000000..d53872d
--- /dev/null
+++ b/docs/reference/pandas_create_from_dict.md
@@ -0,0 +1,17 @@
+# Pandas Create From Dict
+Creates a pandas DataFrame from a Python dictionary.
+
+## Input
+| Name | Data type |
+|---|---|
+| data | Pydict |
+
+## Output
+| Data type |
+|---|
+| DataFrame |
+
+
+Category: IO
+
+ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved.
diff --git a/docs/reference/pandas_create_from_multiple_dict.md b/docs/reference/pandas_create_from_multiple_dict.md
new file mode 100644
index 0000000..7f4bf69
--- /dev/null
+++ b/docs/reference/pandas_create_from_multiple_dict.md
@@ -0,0 +1,32 @@
+# Pandas Create From Multiple Dict
+Creates a pandas DataFrame from multiple Python dictionaries.
+
+Use this node to create a DataFrame from below nodes:
+* Py Kv Float Create
+* Py Kv Int Create
+* Py Kv String Create
+Each of above can be used to set a column name and the value for the column.
+
+## Input
+| Name | Data type |
+|---|---|
+| dict1 | Pydict |
+| dict2 | Pydict |
+| dict3 | Pydict |
+| dict4 | Pydict |
+| dict5 | Pydict |
+| dict6 | Pydict |
+| dict7 | Pydict |
+| dict8 | Pydict |
+| dict9 | Pydict |
+| dict10 | Pydict |
+
+## Output
+| Data type |
+|---|
+| DataFrame |
+
+
+Category: IO
+
+ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved.
diff --git a/docs/reference/pandas_create_series_from_dict.md b/docs/reference/pandas_create_series_from_dict.md
new file mode 100644
index 0000000..dba478e
--- /dev/null
+++ b/docs/reference/pandas_create_series_from_dict.md
@@ -0,0 +1,17 @@
+# Pandas Create Series From Dict
+Creates a Pandas Series from a Python dictionary.
+
+## Input
+| Name | Data type |
+|---|---|
+| data | Pydict |
+
+## Output
+| Data type |
+|---|
+| Series |
+
+
+Category: IO
+
+ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved.
diff --git a/docs/reference/py_kv_float_create.md b/docs/reference/py_kv_float_create.md
new file mode 100644
index 0000000..1a679d6
--- /dev/null
+++ b/docs/reference/py_kv_float_create.md
@@ -0,0 +1,18 @@
+# Py Kv Float Create
+Creates a Python dictionary with a string key and a float value.
+
+## Input
+| Name | Data type |
+|---|---|
+| key | String |
+| value | Float |
+
+## Output
+| Data type |
+|---|
+| Pydict |
+
+
+Category: Python wrapper
+
+ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved.
diff --git a/docs/reference/py_kv_int_create.md b/docs/reference/py_kv_int_create.md
new file mode 100644
index 0000000..1099bfd
--- /dev/null
+++ b/docs/reference/py_kv_int_create.md
@@ -0,0 +1,18 @@
+# Py Kv Int Create
+Creates a Python dictionary with an string key and an integer value.
+
+## Input
+| Name | Data type |
+|---|---|
+| key | String |
+| value | Int |
+
+## Output
+| Data type |
+|---|
+| Pydict |
+
+
+Category: Python wrapper
+
+ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved.
diff --git a/docs/reference/py_kv_string_create.md b/docs/reference/py_kv_string_create.md
new file mode 100644
index 0000000..6c6f641
--- /dev/null
+++ b/docs/reference/py_kv_string_create.md
@@ -0,0 +1,18 @@
+# Py Kv String Create
+Creates a Python dictionary with a string key and a string value.
+
+## Input
+| Name | Data type |
+|---|---|
+| key | String |
+| value | String |
+
+## Output
+| Data type |
+|---|
+| Pydict |
+
+
+Category: Python wrapper
+
+ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved.
diff --git a/docs/reference/sns_histogram.md b/docs/reference/sns_histogram.md
index fa0ad71..a9e158e 100644
--- a/docs/reference/sns_histogram.md
+++ b/docs/reference/sns_histogram.md
@@ -1,6 +1,7 @@
# SNS Histogram
Generates a histogram from a Pandas DataFrame using Seaborn.
+Currently, plotting multiple series of data is not supported.
For style and palette parameters, refer to [SNS Line Plot documentation](sns_line.md).
## Output
diff --git a/modules/cda/py_kv_float_create.py b/modules/cda/py_kv_float_create.py
new file mode 100644
index 0000000..3fa1fc7
--- /dev/null
+++ b/modules/cda/py_kv_float_create.py
@@ -0,0 +1,42 @@
+from typing import Any, Dict
+
+
+class PyKvFloatCreate:
+ """
+ Creates a Python dictionary with a string key and a float value.
+
+ category: Python wrapper
+ """
+
+ @classmethod
+ def INPUT_TYPES(cls) -> Dict[str, Any]:
+ """
+ Defines the input types for the `create` function.
+
+ Returns:
+ Dict[str, Any]: A dictionary specifying required input types.
+ """
+ return {
+ "required": {
+ "key": ("STRING", {"multiline": False}),
+ "value": ("FLOAT", {"default": 0, "min": -2**63, "max": 2**63, "step": 0.00000001})
+ }
+ }
+
+ RETURN_TYPES: tuple = ("PYDICT",)
+ FUNCTION: str = "f"
+ CATEGORY: str = "Data Analysis"
+
+ def f(self, key: str, value: float) -> tuple:
+ """
+ Creates a Python dictionary with a string key and a float value.
+
+ Args:
+ key (str): String key for the dictionary.
+ value (float): Float value for the dictionary.
+
+ Returns:
+ tuple: A tuple containing the dictionary.
+ """
+ return ({key:value},)
+
\ No newline at end of file
diff --git a/modules/cda/py_kv_int_create.py b/modules/cda/py_kv_int_create.py
new file mode 100644
index 0000000..7dfcbfd
--- /dev/null
+++ b/modules/cda/py_kv_int_create.py
@@ -0,0 +1,42 @@
+from typing import Any, Dict
+
+
+class PyKvIntCreate:
+ """
+ Creates a Python dictionary with an string key and an integer value.
+
+ category: Python wrapper
+ """
+
+ @classmethod
+ def INPUT_TYPES(cls) -> Dict[str, Any]:
+ """
+ Defines the input types for the `create` function.
+
+ Returns:
+ Dict[str, Any]: A dictionary specifying required input types.
+ """
+ return {
+ "required": {
+ "key": ("STRING", {"multiline": False}),
+ "value": ("INT", {"default": 0, "min": -2**31, "max": 2**31})
+ }
+ }
+
+ RETURN_TYPES: tuple = ("PYDICT",)
+ FUNCTION: str = "f"
+ CATEGORY: str = "Data Analysis"
+
+ def f(self, key: str, value: int) -> tuple:
+ """
+ Creates a Python dictionary with an string key and an integer value.
+
+ Args:
+ key (str): String key for the dictionary.
+ value (int): Integer value for the dictionary.
+
+ Returns:
+ tuple: A tuple containing the dictionary.
+ """
+ return ({key:value},)
+
\ No newline at end of file
diff --git a/modules/cda/py_kv_string_create.py b/modules/cda/py_kv_string_create.py
new file mode 100644
index 0000000..0e939a3
--- /dev/null
+++ b/modules/cda/py_kv_string_create.py
@@ -0,0 +1,41 @@
+from typing import Any, Dict
+
+
+class PyKvStringCreate:
+ """
+ Creates a Python dictionary with a string key and a string value.
+
+ category: Python wrapper
+ """
+
+ @classmethod
+ def INPUT_TYPES(cls) -> Dict[str, Any]:
+ """
+ Defines the input types for the `create` function.
+
+ Returns:
+ Dict[str, Any]: A dictionary specifying required input types.
+ """
+ return {
+ "required": {
+ "key": ("STRING", {"multiline": False}),
+ "value": ("STRING", {"multiline": True})
+ }
+ }
+
+ RETURN_TYPES: tuple = ("PYDICT",)
+ FUNCTION: str = "f"
+ CATEGORY: str = "Data Analysis"
+
+ def f(self, key: str, value: float) -> tuple:
+ """
+ Creates a Python dictionary with a string key and a string value.
+
+ Args:
+ key (str): String key for the dictionary.
+ value (str): String value for the dictionary.
+
+ Returns:
+ tuple: A tuple containing the dictionary.
+ """
+ return ({key:value},)
diff --git a/modules/cda/utils.py b/modules/cda/utils.py
new file mode 100644
index 0000000..9979c3b
--- /dev/null
+++ b/modules/cda/utils.py
@@ -0,0 +1,30 @@
+from typing import Dict, Any
+
+def python_dict_first_value_len(d: Dict[Any, Any]) -> int:
+ r"""
+ Computes the length of the first value in a dictionary.
+
+ - If the first value is a `list`, returns its length.
+ - If the first value is a `dict`, returns the number of keys in the dictionary.
+ - Otherwise, returns `1` for scalar values.
+
+ Args:
+ d (Dict[Any, Any]): A dictionary where the first value's length will be computed.
+
+ Returns:
+ int: The computed length based on the type of the first value.
+
+ Raises:
+ ValueError: If `d` is `None` or empty.
+ """
+ if not d:
+ raise ValueError("Input dictionary is None or empty.")
+
+ first_value = next(iter(d.values())) # Get the first value
+
+ if isinstance(first_value, list):
+ return len(first_value)
+ elif isinstance(first_value, dict):
+ return len(first_value.keys()) # Return the number of keys in the dictionary
+ else:
+ return 1 # Scalar case (int, float, str, etc.)
diff --git a/modules/pandas_wrapper/pandas_create_from_dict.py b/modules/pandas_wrapper/pandas_create_from_dict.py
new file mode 100644
index 0000000..a385b18
--- /dev/null
+++ b/modules/pandas_wrapper/pandas_create_from_dict.py
@@ -0,0 +1,52 @@
+import os
+import sys
+from typing import Any, Dict
+import pandas as pd
+
+PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
+MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules")
+sys.path.append(MODULE_ROOT)
+from cda.utils import python_dict_first_value_len
+
+class PandasCreateFromDict:
+ """
+ Creates a pandas DataFrame from a Python dictionary.
+
+ category: IO
+ """
+
+ @classmethod
+ def INPUT_TYPES(cls) -> Dict[str, Any]:
+ """
+ Defines the input types for the `create` function.
+
+ Returns:
+ Dict[str, Any]: A dictionary specifying required input types.
+ """
+ return {
+ "required": {
+ "data": ("PYDICT", {}),
+ }
+ }
+
+ RETURN_TYPES: tuple = ("DATAFRAME",)
+ FUNCTION: str = "f"
+ CATEGORY: str = "Data Analysis"
+
+ def f(self, data: Dict) -> tuple:
+ """
+ Creates a pandas DataFrame from a Python dictionary.
+
+ Args:
+ data (Dict): A Python dictionary.
+
+ Returns:
+ tuple: A tuple containing a the DataFrame.
+ """
+ if not data:
+ df = pd.DataFrame() # Return an empty DataFrame if no data is provided.
+ else:
+ value_length = python_dict_first_value_len(data)
+ df = pd.DataFrame(data, index=list(range(value_length)))
+
+ return (df,)
diff --git a/modules/pandas_wrapper/pandas_create_from_multiple_dict.py b/modules/pandas_wrapper/pandas_create_from_multiple_dict.py
new file mode 100644
index 0000000..91e90b8
--- /dev/null
+++ b/modules/pandas_wrapper/pandas_create_from_multiple_dict.py
@@ -0,0 +1,94 @@
+import os
+import sys
+from typing import Any, Dict
+import pandas as pd
+
+PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
+MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules")
+sys.path.append(MODULE_ROOT)
+from cda.utils import python_dict_first_value_len
+
+class PandasCreateFromMultipleDict:
+ """
+ Creates a pandas DataFrame from multiple Python dictionaries.
+
+ Use this node to create a DataFrame from below nodes:
+ * Py Kv Float Create
+ * Py Kv Int Create
+ * Py Kv String Create
+ Each of above can be used to set a column name and the value for the column.
+
+ category: IO
+ """
+
+ @classmethod
+ def INPUT_TYPES(cls) -> Dict[str, Any]:
+ """
+ Defines the input types for the `create` function.
+
+ Returns:
+ Dict[str, Any]: A dictionary specifying required input types.
+ """
+ return {
+ "required": {
+ "dict1": ("PYDICT", {}),
+ "dict2": ("PYDICT", {}),
+ },
+ "optional":{
+ "dict3": ("PYDICT", {}),
+ "dict4": ("PYDICT", {}),
+ "dict5": ("PYDICT", {}),
+ "dict6": ("PYDICT", {}),
+ "dict7": ("PYDICT", {}),
+ "dict8": ("PYDICT", {}),
+ "dict9": ("PYDICT", {}),
+ "dict10": ("PYDICT", {})
+ }
+ }
+
+ RETURN_TYPES: tuple = ("DATAFRAME",)
+ FUNCTION: str = "f"
+ CATEGORY: str = "Data Analysis"
+
+ def f(self,
+ dict1: Dict,
+ dict2: Dict,
+ dict3: Dict=None,
+ dict4: Dict=None,
+ dict5: Dict=None,
+ dict6: Dict=None,
+ dict7: Dict=None,
+ dict8: Dict=None,
+ dict9: Dict=None,
+ dict10: Dict=None
+ ) -> tuple:
+ """
+ Creates a pandas DataFrame from a Python dictionary.
+
+ Args:
+ dict1, 2, ... ,10 (Dict): A Python dictionary.
+
+ Returns:
+ tuple: A tuple containing a the DataFrame.
+ """
+ dict_list = [d for d in (dict1, dict2, dict3, dict4, dict5, dict6, dict7, dict8, dict9, dict10) if d]
+
+ if dict_list[0] is None or dict_list[1] is None:
+ raise ValueError("Either or both of the first two dictionaries are None.")
+
+ if not dict_list[0] or not dict_list[1]:
+ # Return an empty DataFrame if an empty dict is provided for the first dictionary
+ # or the second dictionary.
+ df = pd.DataFrame()
+ else:
+ expected_length = python_dict_first_value_len(dict_list[0])
+
+ for d in dict_list[1:]:
+ if expected_length != python_dict_first_value_len(d):
+ raise ValueError("Length of the value elements are not the same.")
+
+ merged_dict = {}
+ for d in dict_list:
+ merged_dict.update(d)
+ df = pd.DataFrame(merged_dict, index=list(range(expected_length)))
+ return (df,)
diff --git a/modules/pandas_wrapper/pandas_create_series_from_dict.py b/modules/pandas_wrapper/pandas_create_series_from_dict.py
new file mode 100644
index 0000000..b723a40
--- /dev/null
+++ b/modules/pandas_wrapper/pandas_create_series_from_dict.py
@@ -0,0 +1,60 @@
+import os
+import sys
+from typing import Dict, Any, Tuple
+import pandas as pd
+
+PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
+MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules")
+sys.path.append(MODULE_ROOT)
+from cda.utils import python_dict_first_value_len
+
+class PandasCreateSeriesFromDict:
+ """
+ Creates a Pandas Series from a Python dictionary.
+
+ category: IO
+ """
+ @classmethod
+ def INPUT_TYPES(cls) -> Dict[str, Any]:
+ """
+ Defines the input types for the function.
+
+ Returns:
+ Dict[str, Any]: A dictionary specifying required input types.
+ """
+ return {
+ "required": {
+ "data": ("PYDICT", {}),
+ }
+ }
+
+ RETURN_TYPES: tuple = ("PDSERIES",)
+ FUNCTION: str = "f"
+ CATEGORY: str = "Data Analysis"
+
+ def f(self, data: Dict[Any, Any]) -> Tuple[pd.Series]:
+ """
+ Creates a Pandas Series from a Python dictionary.
+
+ Args:
+ data (Dict[Any, Any]): A dictionary with exactly one key-value pair.
+
+ Returns:
+ Tuple[pd.Series]: A tuple containing a Pandas Series.
+
+ Raises:
+ ValueError: If the dictionary does not have exactly one key-value pair,
+ or if the value is not a scalar.
+ """
+ if not isinstance(data, dict):
+ raise ValueError("Input must be a dictionary.")
+
+ if len(data) == 0:
+ return (pd.Series(),)
+ else:
+ for v in iter(data.values()):
+ if isinstance(v, (list, dict, set, tuple)):
+ raise ValueError("Value must be a scalar.")
+
+ return (pd.Series(data),)
+
diff --git a/modules/pandas_wrapper/pandas_load_csv.py b/modules/pandas_wrapper/pandas_load_csv.py
index 82a122c..17fbbf1 100644
--- a/modules/pandas_wrapper/pandas_load_csv.py
+++ b/modules/pandas_wrapper/pandas_load_csv.py
@@ -29,6 +29,10 @@ class PandasLoadCSV:
FUNCTION: str = "load_csv"
CATEGORY: str = "Data Analysis"
+ @classmethod
+ def IS_CHANGED(cls, **kw):
+ return float("NaN")
+
def load_csv(self, file_path: str) -> tuple:
"""
Loads a CSV file into a pandas DataFrame.
diff --git a/modules/pandas_wrapper/pandas_load_csv_with_encoding.py b/modules/pandas_wrapper/pandas_load_csv_with_encoding.py
index b577290..20b602a 100644
--- a/modules/pandas_wrapper/pandas_load_csv_with_encoding.py
+++ b/modules/pandas_wrapper/pandas_load_csv_with_encoding.py
@@ -30,6 +30,10 @@ class PandasLoadCSVWithEncoding:
FUNCTION: str = "load_csv"
CATEGORY: str = "Data Analysis"
+ @classmethod
+ def IS_CHANGED(cls, **kw):
+ return float("NaN")
+
def load_csv(self, file_path: str, encoding: str) -> tuple:
"""
Loads a CSV file into a pandas DataFrame. Allows specifying the file encoding to support formats other than UTF-8.
diff --git a/modules/pandas_wrapper/pandas_load_csv_with_index.py b/modules/pandas_wrapper/pandas_load_csv_with_index.py
index 8e2b10b..1d84785 100644
--- a/modules/pandas_wrapper/pandas_load_csv_with_index.py
+++ b/modules/pandas_wrapper/pandas_load_csv_with_index.py
@@ -31,6 +31,10 @@ class PandasLoadCSVWithIndex:
FUNCTION: str = "load_csv"
CATEGORY: str = "Data Analysis"
+ @classmethod
+ def IS_CHANGED(cls, **kw):
+ return float("NaN")
+
def load_csv(self, file_path: str, index_col: int=0) -> tuple:
"""
Loads a CSV file into a pandas DataFrame.
diff --git a/modules/pandas_wrapper/pandas_load_html.py b/modules/pandas_wrapper/pandas_load_html.py
index 33e0023..d69609c 100644
--- a/modules/pandas_wrapper/pandas_load_html.py
+++ b/modules/pandas_wrapper/pandas_load_html.py
@@ -43,6 +43,10 @@ class PandasLoadHTML:
FUNCTION: str = "f"
CATEGORY: str = "Data Analysis"
+ @classmethod
+ def IS_CHANGED(cls, **kw):
+ return float("NaN")
+
def f(self, file_path: str) -> tuple:
"""
Loads an HTML file into a pandas DataFrame.
diff --git a/modules/pandas_wrapper/pandas_load_json.py b/modules/pandas_wrapper/pandas_load_json.py
index a1f9c78..2655277 100644
--- a/modules/pandas_wrapper/pandas_load_json.py
+++ b/modules/pandas_wrapper/pandas_load_json.py
@@ -29,6 +29,13 @@ class PandasLoadJSON:
FUNCTION: str = "load_json"
CATEGORY: str = "Data Analysis"
+ # Mark this class to always execute.
+ # Refer to
+ # https://docs.comfy.org/custom-nodes/backend/server_overview
+ @classmethod
+ def IS_CHANGED(cls, **kw):
+ return float("NaN")
+
def load_json(self, file_path: str) -> tuple:
"""
Loads a JSON file into a pandas DataFrame.
diff --git a/pyproject.toml b/pyproject.toml
index a8eb900..4457378 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,7 +1,7 @@
[project]
name = "data-analysis"
description = "Data analysis custom modules for ComfyUI - Use Pandas & Matplotlib within ComfyUI"
-version = "1.2.1"
+version = "1.2.2"
license = {file = "LICENSE"}
[project.urls]
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/cda/__init__.py b/tests/cda/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/cda/py_kev_float_create_test.py b/tests/cda/py_kev_float_create_test.py
new file mode 100644
index 0000000..a273f77
--- /dev/null
+++ b/tests/cda/py_kev_float_create_test.py
@@ -0,0 +1,27 @@
+import os
+import sys
+import unittest
+
+
+PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
+MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules")
+sys.path.append(MODULE_ROOT)
+from cda.py_kv_float_create import PyKvFloatCreate
+
+class TestPyKvFloatCreat(unittest.TestCase):
+ def setUp(self):
+ """Set up test data."""
+ self.node = PyKvFloatCreate()
+ self.data = [
+ {"key": "a", "value": 1.2345678}
+ ]
+
+ def test_create_key_value_pair(self):
+ """Test key-value pair creation."""
+ retval = self.node.f(key=self.data[0]["key"], value=self.data[0]["value"])[0]
+ self.assertEqual({self.data[0]["key"]: self.data[0]["value"]}, retval)
+
+
+if __name__ == "__main__":
+ unittest.main()
+
diff --git a/tests/cda/py_kev_int_create_test.py b/tests/cda/py_kev_int_create_test.py
new file mode 100644
index 0000000..c0524f7
--- /dev/null
+++ b/tests/cda/py_kev_int_create_test.py
@@ -0,0 +1,28 @@
+import os
+import sys
+import unittest
+
+
+PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
+MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules")
+sys.path.append(MODULE_ROOT)
+
+from cda.py_kv_int_create import PyKvIntCreate
+
+class TestPyKvIntCreat(unittest.TestCase):
+ def setUp(self):
+ """Set up test data."""
+ self.node = PyKvIntCreate()
+ self.data = [
+ {"key": "a", "value": 12345678}
+ ]
+
+ def test_create_key_value_pair(self):
+ """Test key-value pair creation."""
+ retval = self.node.f(key=self.data[0]["key"], value=self.data[0]["value"])[0]
+ self.assertEqual({self.data[0]["key"]: self.data[0]["value"]}, retval)
+
+
+if __name__ == "__main__":
+ unittest.main()
+
diff --git a/tests/cda/py_kev_string_create_test.py b/tests/cda/py_kev_string_create_test.py
new file mode 100644
index 0000000..ee91b58
--- /dev/null
+++ b/tests/cda/py_kev_string_create_test.py
@@ -0,0 +1,27 @@
+import os
+import sys
+import unittest
+
+
+PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
+MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules")
+sys.path.append(MODULE_ROOT)
+from cda.py_kv_string_create import PyKvStringCreate
+
+class TestPyKvStringCreat(unittest.TestCase):
+ def setUp(self):
+ """Set up test data."""
+ self.node = PyKvStringCreate()
+ self.data = [
+ {"key": "a", "value": "apple"}
+ ]
+
+ def test_create_key_value_pair(self):
+ """Test key-value pair creation."""
+ retval = self.node.f(key=self.data[0]["key"], value=self.data[0]["value"])[0]
+ self.assertEqual({self.data[0]["key"]: self.data[0]["value"]}, retval)
+
+
+if __name__ == "__main__":
+ unittest.main()
+
diff --git a/tests/cda/utils_test.py b/tests/cda/utils_test.py
new file mode 100644
index 0000000..55ce80b
--- /dev/null
+++ b/tests/cda/utils_test.py
@@ -0,0 +1,40 @@
+import os
+import sys
+import unittest
+
+PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
+MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules")
+sys.path.append(MODULE_ROOT)
+
+from cda.utils import python_dict_first_value_len
+
+class TestUtils(unittest.TestCase):
+ def test_python_dict_first_value_len(self):
+ """Test python_dict_first_value_len with various input cases."""
+
+ test_cases = [
+ ({"a": [1, 2, 3]}, 3, "List case"),
+ ({"a": {"x": 1, "y": 2}}, 2, "Dict case"),
+ ({"a": 42}, 1, "Integer case"),
+ ({"a": 3.14}, 1, "Float case"),
+ ({"a": "hello"}, 1, "String case"),
+ ({"a": True}, 1, "Boolean case"),
+ ({"a": None}, 1, "None as value case"),
+ ({"a": [], "b": "extra"}, 0, "Empty list case"),
+ ({"a": {}}, 0, "Empty dict case"),
+ ]
+
+ for data, expected, desc in test_cases:
+ with self.subTest(msg=desc, data=data):
+ self.assertEqual(python_dict_first_value_len(data), expected)
+
+ # Test ValueError for None input
+ with self.assertRaises(ValueError, msg="None input case"):
+ python_dict_first_value_len(None)
+
+ # Test ValueError for empty dictionary
+ with self.assertRaises(ValueError, msg="Empty dictionary case"):
+ python_dict_first_value_len({})
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/pandas_wrapper/__init__.py b/tests/pandas_wrapper/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/pandas_wrapper/pandas_create_from_dict_test.py b/tests/pandas_wrapper/pandas_create_from_dict_test.py
new file mode 100644
index 0000000..8b704c7
--- /dev/null
+++ b/tests/pandas_wrapper/pandas_create_from_dict_test.py
@@ -0,0 +1,46 @@
+import os
+import sys
+import unittest
+import pandas as pd
+
+PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
+MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules")
+sys.path.append(MODULE_ROOT)
+from pandas_wrapper.pandas_create_from_dict import PandasCreateFromDict
+
+class TestPandasCreateFromDict(unittest.TestCase):
+ def setUp(self):
+ """Set up test data."""
+ self.node = PandasCreateFromDict()
+ self.test_cases = [
+ ({"a": 12345}, pd.DataFrame({"a": [12345]}, index=[0])),
+ ({"a": 12345.6}, pd.DataFrame({"a": [12345.6]}, index=[0])),
+ ({"a": "apple"}, pd.DataFrame({"a": ["apple"]}, index=[0])),
+ ({"a": [12345, 6789]}, pd.DataFrame({"a": [12345, 6789]}, index=[0, 1])),
+ ({"a": [12345.6, 7890.1]}, pd.DataFrame({"a": [12345.6, 7890.1]}, index=[0, 1])),
+ ({"a": ["apple", "banana"]}, pd.DataFrame({"a": ["apple", "banana"]}, index=[0, 1])),
+ ({"a": 123, "b": 456}, pd.DataFrame({"a": [123], "b": [456]}, index=[0])),
+ ({"a": 12.3, "b": 45.6}, pd.DataFrame({"a": [12.3], "b": [45.6]}, index=[0])),
+ ({"a": "foo", "b": "bar"}, pd.DataFrame({"a": ["foo"], "b": ["bar"]}, index=[0])),
+ ({"a": [1, 2], "b": [3, 4]}, pd.DataFrame({"a": [1, 2], "b": [3, 4]}, index=[0, 1])),
+ ({"a": [1.1, 2.2], "b": [3.3, 4.4]}, pd.DataFrame({"a": [1.1, 2.2], "b": [3.3, 4.4]}, index=[0, 1])),
+ ({"a": ["x", "y"], "b": ["z", "w"]}, pd.DataFrame({"a": ["x", "y"], "b": ["z", "w"]}, index=[0, 1]))
+ ]
+
+ def test_create_dataframe(self):
+ """Test DataFrame creation from various dictionary inputs."""
+ for data, expected_df in self.test_cases:
+ with self.subTest(data=data):
+ retval = self.node.f(data)[0]
+
+ # Test if DataFrame structure matches
+ self.assertTrue(isinstance(retval, pd.DataFrame))
+
+ # Test if index matches
+ self.assertTrue(retval.index.equals(expected_df.index), f"Index mismatch for input: {data}")
+
+ # Test if data matches
+ pd.testing.assert_frame_equal(retval, expected_df)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/pandas_wrapper/pandas_create_from_multiple_dict_test.py b/tests/pandas_wrapper/pandas_create_from_multiple_dict_test.py
new file mode 100644
index 0000000..5aa9eba
--- /dev/null
+++ b/tests/pandas_wrapper/pandas_create_from_multiple_dict_test.py
@@ -0,0 +1,78 @@
+import os
+import sys
+import unittest
+import pandas as pd
+
+PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
+MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules")
+sys.path.append(MODULE_ROOT)
+
+from pandas_wrapper.pandas_create_from_multiple_dict import PandasCreateFromMultipleDict
+
+class TestPandasCreateFromMultipleDict(unittest.TestCase):
+ def setUp(self):
+ """Set up test data."""
+ self.node = PandasCreateFromMultipleDict()
+
+ # Valid test cases with at least two dictionaries
+ self.test_cases = [
+ ({"a": [12345]}, {"b": [67890]}, pd.DataFrame({"a": [12345], "b": [67890]}, index=[0])),
+ ({"a": [12345.6]}, {"b": [7890.1]}, pd.DataFrame({"a": [12345.6], "b": [7890.1]}, index=[0])),
+ ({"a": ["apple"]}, {"b": ["banana"]}, pd.DataFrame({"a": ["apple"], "b": ["banana"]}, index=[0])),
+ ({"a": [12345, 6789]}, {"b": [1111, 2222]}, pd.DataFrame({"a": [12345, 6789], "b": [1111, 2222]}, index=[0, 1])),
+ ({"a": [123, 456]}, {"b": [789, 101]}, pd.DataFrame({"a": [123, 456], "b": [789, 101]}, index=[0, 1])),
+ ]
+
+ # Case where all 10 dictionaries are passed
+ self.all_dicts_case = (
+ {"a": [1, 2]}, {"b": [3, 4]}, {"c": [5, 6]}, {"d": [7, 8]}, {"e": [9, 10]},
+ {"f": [11, 12]}, {"g": [13, 14]}, {"h": [15, 16]}, {"i": [17, 18]}, {"j": [19, 20]},
+ pd.DataFrame({
+ "a": [1, 2], "b": [3, 4], "c": [5, 6], "d": [7, 8], "e": [9, 10],
+ "f": [11, 12], "g": [13, 14], "h": [15, 16], "i": [17, 18], "j": [19, 20]
+ }, index=[0, 1])
+ )
+
+ # Failure cases where first dictionary's value length differs from second's
+ self.length_mismatch_cases = [
+ ({"a": [1, 2]}, {"b": [3]}, "Length of the value elements are not the same."),
+ ({"x": [10]}, {"y": [20, 30]}, "Length of the value elements are not the same."),
+ ]
+
+ def test_create_dataframe(self):
+ """Test DataFrame creation from valid dictionary inputs."""
+ for data1, data2, expected_df in self.test_cases:
+ with self.subTest(data1=data1, data2=data2):
+ retval = self.node.f(data1, data2)[0] # Pass at least two dictionaries
+
+ # Test if DataFrame structure matches
+ self.assertTrue(isinstance(retval, pd.DataFrame))
+
+ # Test if index matches
+ self.assertTrue(retval.index.equals(expected_df.index), f"Index mismatch for input: {data1}, {data2}")
+
+ # Test if data matches
+ pd.testing.assert_frame_equal(retval, expected_df)
+
+ def test_all_dicts(self):
+ """Test case where all 10 dictionaries are provided."""
+ data1, data2, data3, data4, data5, data6, data7, data8, data9, data10, expected_df = self.all_dicts_case
+ retval = self.node.f(data1, data2, data3, data4, data5, data6, data7, data8, data9, data10)[0]
+ pd.testing.assert_frame_equal(retval, expected_df)
+
+ def test_no_dict_failure(self):
+ """Test failure when no dictionaries are passed."""
+ with self.assertRaises(TypeError):
+ self.node.f() # No arguments should raise TypeError
+
+ def test_length_mismatch_failure(self):
+ """Test failure when first dictionary's value length differs from second's."""
+ for data1, data2, error_message in self.length_mismatch_cases:
+ with self.subTest(data1=data1, data2=data2):
+ with self.assertRaises(ValueError) as context:
+ self.node.f(data1, data2)
+ self.assertEqual(str(context.exception), error_message)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/pandas_wrapper/pandas_create_series_from_dict_test.py b/tests/pandas_wrapper/pandas_create_series_from_dict_test.py
new file mode 100644
index 0000000..c0b2ba6
--- /dev/null
+++ b/tests/pandas_wrapper/pandas_create_series_from_dict_test.py
@@ -0,0 +1,76 @@
+import os
+import sys
+import unittest
+import pandas as pd
+
+PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
+MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules")
+sys.path.append(MODULE_ROOT)
+from pandas_wrapper.pandas_create_series_from_dict import PandasCreateSeriesFromDict
+
+class TestPandasCreateSeriesFromDict(unittest.TestCase):
+ def setUp(self):
+ """Set up test data."""
+ self.node = PandasCreateSeriesFromDict()
+ self.valid_test_cases = [
+ # Empty dict
+ ({}, pd.Series({})),
+
+ # Single key-value pairs
+ ({"a": 12345}, pd.Series({"a": 12345})),
+ ({"a": 12345.6}, pd.Series({"a": 12345.6})),
+ ({"a": "apple"}, pd.Series({"a": "apple"})),
+ ({"a": True}, pd.Series({"a": True})),
+ ({"a": None}, pd.Series({"a": None})), # None should be treated as a valid value
+
+ # Multiple key-value pairs
+ ({"a": 123, "b": 456}, pd.Series({"a": 123, "b": 456})),
+ ({"x": 1.5, "y": 3.2, "z": -7.8}, pd.Series({"x": 1.5, "y": 3.2, "z": -7.8})),
+ ({"name": "Alice", "age": 30, "city": "NYC"}, pd.Series({"name": "Alice", "age": 30, "city": "NYC"})),
+ ({"flag": False, "score": 99, "pi": 3.14159}, pd.Series({"flag": False, "score": 99, "pi": 3.14159})),
+ ]
+
+ self.invalid_test_cases = [
+ # Non-dictionary inputs
+ None,
+ [],
+ (),
+ "string",
+ 1234,
+ 56.78,
+ {1, 2, 3}, # Set
+ [{"a": 1}], # List containing a dict
+
+ # Dictionaries with non-scalar values
+ {"a": [12345, 6789]}, # List value
+ {"a": {"key": "value"}}, # Dict value
+ {"a": (1, 2)}, # Tuple value
+ {"a": {1, 2}}, # Set value
+
+ # Mixed valid and invalid values
+ {"a": 123, "b": [456, 789]}, # One scalar, one list
+ {"x": 1.5, "y": {"nested": "dict"}}, # One scalar, one dict
+ {"flag": False, "data": (3, 4)}, # One scalar, one tuple
+ ]
+
+ def test_create_series(self):
+ """Test Series creation from dictionaries with multiple key-value pairs."""
+ for data, expected_series in self.valid_test_cases:
+ with self.subTest(data=data):
+ retval = self.node.f(data)[0]
+
+ # Test if output is a Pandas Series
+ self.assertTrue(isinstance(retval, pd.Series), f"Expected Series, got {type(retval)}")
+
+ # Test if Series matches expected output
+ pd.testing.assert_series_equal(retval, expected_series)
+
+ def test_invalid_inputs(self):
+ """Test that invalid inputs raise ValueError."""
+ for data in self.invalid_test_cases:
+ with self.subTest(data=data):
+ with self.assertRaises(ValueError):
+ self.node.f(data)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/pandas_wrapper/pandas_iloc_rows_slice_dataframe_test.py b/tests/pandas_wrapper/pandas_iloc_rows_slice_dataframe_test.py
index f80d00b..e7f758d 100644
--- a/tests/pandas_wrapper/pandas_iloc_rows_slice_dataframe_test.py
+++ b/tests/pandas_wrapper/pandas_iloc_rows_slice_dataframe_test.py
@@ -3,7 +3,7 @@ import sys
import unittest
import pandas as pd
-PROJECT_ROOT = os.path.realpath(os.path.join(__file__, "..", "..", ".."))
+PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules")
sys.path.append(MODULE_ROOT)
diff --git a/tests/pandas_wrapper/pandas_melt_test.py b/tests/pandas_wrapper/pandas_melt_test.py
index ba223a3..1a6669f 100644
--- a/tests/pandas_wrapper/pandas_melt_test.py
+++ b/tests/pandas_wrapper/pandas_melt_test.py
@@ -3,7 +3,7 @@ import sys
import unittest
import pandas as pd
-PROJECT_ROOT = os.path.realpath(os.path.join(__file__, "..", "..", ".."))
+PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules")
sys.path.append(MODULE_ROOT)
diff --git a/tests/pandas_wrapper/pandas_pivot_test.py b/tests/pandas_wrapper/pandas_pivot_test.py
index 9cda5d4..bd336f5 100644
--- a/tests/pandas_wrapper/pandas_pivot_test.py
+++ b/tests/pandas_wrapper/pandas_pivot_test.py
@@ -3,7 +3,7 @@ import sys
import unittest
import pandas as pd
-PROJECT_ROOT = os.path.realpath(os.path.join(__file__, "..", "..", ".."))
+PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules")
sys.path.append(MODULE_ROOT)
diff --git a/tests/pandas_wrapper/pandas_to_datetime_test.py b/tests/pandas_wrapper/pandas_to_datetime_test.py
index a007a75..b08e885 100644
--- a/tests/pandas_wrapper/pandas_to_datetime_test.py
+++ b/tests/pandas_wrapper/pandas_to_datetime_test.py
@@ -4,7 +4,7 @@ import unittest
import pandas as pd
import pandas.api.types as ptypes
-PROJECT_ROOT = os.path.realpath(os.path.join(__file__, "..", "..", ".."))
+PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules")
sys.path.append(MODULE_ROOT)
print(MODULE_ROOT)
diff --git a/tests/pandas_wrapper/pandas_to_strftime_test.py b/tests/pandas_wrapper/pandas_to_strftime_test.py
index 8c0a0de..b374c87 100644
--- a/tests/pandas_wrapper/pandas_to_strftime_test.py
+++ b/tests/pandas_wrapper/pandas_to_strftime_test.py
@@ -4,7 +4,7 @@ import unittest
import pandas as pd
import pandas.api.types as ptypes
-PROJECT_ROOT = os.path.realpath(os.path.join(__file__, "..", "..", ".."))
+PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules")
sys.path.append(MODULE_ROOT)
print(MODULE_ROOT)
diff --git a/tests/pandas_wrapper/utils_column_labels_string_to_list_test.py b/tests/pandas_wrapper/utils_column_labels_string_to_list_test.py
index 36106c8..da67259 100644
--- a/tests/pandas_wrapper/utils_column_labels_string_to_list_test.py
+++ b/tests/pandas_wrapper/utils_column_labels_string_to_list_test.py
@@ -4,7 +4,7 @@ import unittest
import pandas as pd
from typing import List, Union
-PROJECT_ROOT = os.path.realpath(os.path.join(__file__, "..", "..", ".."))
+PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules")
sys.path.append(MODULE_ROOT)
diff --git a/tests/pandas_wrapper/utils_comma_separated_labels_string_to_list_test.py b/tests/pandas_wrapper/utils_comma_separated_labels_string_to_list_test.py
index 67e51eb..c62fa39 100644
--- a/tests/pandas_wrapper/utils_comma_separated_labels_string_to_list_test.py
+++ b/tests/pandas_wrapper/utils_comma_separated_labels_string_to_list_test.py
@@ -3,7 +3,7 @@ import sys
import unittest
from typing import List
-PROJECT_ROOT = os.path.realpath(os.path.join(__file__, "..", "..", ".."))
+PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules")
sys.path.append(MODULE_ROOT)
diff --git a/tests/pandas_wrapper/utils_flatten_list_tuple_unique_test.py b/tests/pandas_wrapper/utils_flatten_list_tuple_unique_test.py
index 495ee8e..d609cd0 100644
--- a/tests/pandas_wrapper/utils_flatten_list_tuple_unique_test.py
+++ b/tests/pandas_wrapper/utils_flatten_list_tuple_unique_test.py
@@ -3,7 +3,7 @@ import sys
import unittest
from typing import Any, List, Tuple, Union, Set
-PROJECT_ROOT = os.path.realpath(os.path.join(__file__, "..", "..", ".."))
+PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules")
sys.path.append(MODULE_ROOT)
diff --git a/tests/pandas_wrapper/utils_index_json_test.py b/tests/pandas_wrapper/utils_index_json_test.py
index ad81d68..d4b7e92 100644
--- a/tests/pandas_wrapper/utils_index_json_test.py
+++ b/tests/pandas_wrapper/utils_index_json_test.py
@@ -5,7 +5,7 @@ import json
import numpy as np
import pandas as pd
-PROJECT_ROOT = os.path.realpath(os.path.join(__file__, "..", "..", ".."))
+PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules")
sys.path.append(MODULE_ROOT)
print(MODULE_ROOT)
diff --git a/tests/pandas_wrapper/utils_series_json_test.py b/tests/pandas_wrapper/utils_series_json_test.py
index 08faf87..7a2f923 100644
--- a/tests/pandas_wrapper/utils_series_json_test.py
+++ b/tests/pandas_wrapper/utils_series_json_test.py
@@ -5,10 +5,9 @@ import json
import numpy as np
import pandas as pd
-PROJECT_ROOT = os.path.realpath(os.path.join(__file__, "..", "..", ".."))
+PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules")
sys.path.append(MODULE_ROOT)
-print(MODULE_ROOT)
from pandas_wrapper.utils import series_to_jsons
from pandas_wrapper.utils import jsons_to_series