Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions c_module/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@
default=user_input[ParamNames.add_on_activated.value], show_default=True, required=True, is_flag=True,
help="Flag to use the carbon module as a standalone module or as a TiMBA add-on.")
@click.option('-SC', '--sc_num', "sc_num",
default=user_input[ParamNames.sc_num.value], show_default=True, required=True, type=int,
default=user_input[ParamNames.sc_num.value], show_default=True, required=False, type=int,
help="Flag to control the number of processed scenarios.")
@click.option('-IFP', '--input_folder_path', 'input_folder_path', default=user_input[ParamNames.input_folder_path.value],
show_default=True, required=False, type=str, help="Path to input folder.")
@click.option('-OFP', '--input_folder_path', 'output_folder_path', default=user_input[ParamNames.output_folder_path.value],
show_default=True, required=False, type=str, help="Path to output folder.")
@click.option('-SY', '--start_year', 'start_year', default=user_input[ParamNames.start_year.value],
show_default=True, required=True, type=int,
help="Start year of carbon calculations.")
Expand Down Expand Up @@ -44,12 +48,9 @@
@click.option('-UD', '--fao_data_update', 'fao_data_update',
default=user_input[ParamNames.fao_data_update.value], show_default=True, required=False, is_flag=True,
help="Flag to update FAO data.")
@click.option('-FP', '--folderpath', 'folderpath', default=user_input[ParamNames.folderpath.value],
show_default=True, required=False, type=str, help="Path to directory with Input/Output folder.")

def cli(add_on_activated, sc_num, start_year, end_year, calc_c_forest_agb, calc_c_forest_bgb, calc_c_forest_soil,
calc_c_forest_dwl, calc_c_hwp, c_hwp_accounting_approach, read_in_pkl, show_carbon_dashboard, fao_data_update,
folderpath):
input_folder_path, output_folder_path):

user_input_cli = {
ParamNames.add_on_activated.value: add_on_activated,
Expand All @@ -65,7 +66,8 @@ def cli(add_on_activated, sc_num, start_year, end_year, calc_c_forest_agb, calc_
ParamNames.c_hwp_accounting_approach.value: c_hwp_accounting_approach,
ParamNames.show_carbon_dashboard.value: show_carbon_dashboard,
ParamNames.fao_data_update.value: fao_data_update,
ParamNames.folderpath.value: folderpath,
ParamNames.input_folder_path.value: input_folder_path,
ParamNames.output_folder_path.value: output_folder_path,
# Adavanced settings not available via CLI
ParamNames.historical_c_hwp.value: user_input[ParamNames.historical_c_hwp.value],
ParamNames.hist_hwp_start_year.value: user_input[ParamNames.hist_hwp_start_year.value],
Expand Down
26 changes: 13 additions & 13 deletions c_module/data_management/data_manager.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from c_module.parameters.paths import cmodule_is_standalone, extract_scenarios
from c_module.parameters.paths import extract_scenarios
from c_module.parameters.defines import (VarNames, ParamNames, CountryConstants, FolderNames, PathNames)
from c_module.user_io.default_parameters import user_input
import pandas as pd
Expand All @@ -16,23 +16,26 @@ class DataManager:

@staticmethod
def set_sc_paths(self):
TIMBADIR_INPUT = self.paths[PathNames.TIMBADIR_INPUT.value]
TIMBADIR_OUTPUT = self.paths[PathNames.TIMBADIR_OUTPUT.value]
INPUT_FOLDER = self.paths[PathNames.INPUT_FOLDER.value]
OUTPUT_FOLDER = self.paths[PathNames.OUTPUT_FOLDER.value]

if user_input[ParamNames.add_on_activated.value] or not cmodule_is_standalone(debug=False):
if user_input[ParamNames.add_on_activated.value]:
# input paths for add-on c-module
scenarios = extract_scenarios(input_folder=TIMBADIR_INPUT,
output_folder=TIMBADIR_OUTPUT,
scenarios = extract_scenarios(output_folder=OUTPUT_FOLDER,
sc_num=user_input[ParamNames.sc_num.value])
PKL_RESULTS_INPUT = scenarios
input_folder = INPUT_FOLDER
else:
# input paths for standalone c-module
scenarios = list((INPUT_FOLDER / Path("projection_data")).glob(r"*.pkl"))
if user_input[ParamNames.sc_num.value] is not None:
scenarios.sort(key=lambda f: f.stat().st_mtime)
scenarios = scenarios[-user_input[ParamNames.sc_num.value]:]
PKL_RESULTS_INPUT = scenarios
input_folder = INPUT_FOLDER / Path("projection_data")

if len(PKL_RESULTS_INPUT) == 0:
self.logger.info(f"No scenarios found in {input_folder}")

self.sc_path = PKL_RESULTS_INPUT

Expand All @@ -54,10 +57,10 @@ def check_input_data_structure(self):
INPUT_FOLDER = self.paths[PathNames.INPUT_FOLDER.value]
INPUT_FOLDER.mkdir(parents=True, exist_ok=True)

if cmodule_is_standalone(debug=False):
required = {FolderNames.additional_info.value, FolderNames.projection_data.value}
else:
if self.UserInput[ParamNames.add_on_activated.value]:
required = {FolderNames.additional_info.value}
else:
required = {FolderNames.additional_info.value, FolderNames.projection_data.value}
existing = {p.name for p in Path(INPUT_FOLDER).iterdir() if p.is_dir()}
missing = list(required - existing)
if len(missing) > 0:
Expand Down Expand Up @@ -238,10 +241,7 @@ def save_data(self):
carbon_data_ext = DataManager.add_additional_info(self, data=carbon_data_ext, sc=sc)
self.timba_data[sc][VarNames.timba_data_carbon.value] = self.carbon_data[sc][VarNames.carbon_total.value]
self.timba_data[sc][VarNames.timba_data_carbon_flat.value] = carbon_data_ext
if not self.UserInput[ParamNames.add_on_activated.value]:
DataManager.serialize_to_pickle(self.timba_data[sc], OUTPUT_FOLDER / Path(f"{sc}.pkl"))
else:
DataManager.serialize_to_pickle(self.carbon_data[sc], OUTPUT_FOLDER / Path(f"{sc}.pkl"))
DataManager.serialize_to_pickle(self.timba_data[sc], OUTPUT_FOLDER / Path(f"{sc}.pkl"))

for df_key in self.carbon_data[sc].keys():
carbon_data = self.carbon_data[sc][df_key]
Expand Down
3 changes: 2 additions & 1 deletion c_module/parameters/defines.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ class ParamNames(Enum):
start_year = "start_year"
end_year = "end_year"
read_in_pkl = "read_in_pkl"
folderpath = "folderpath"
input_folder_path = "input_folder_path"
output_folder_path = "output_folder_path"
save_data_as = "save_data_as"
calc_c_forest_agb = "calc_c_forest_agb"
calc_c_forest_bgb = "calc_c_forest_bgb"
Expand Down
146 changes: 19 additions & 127 deletions c_module/parameters/paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@
current_dt = dt.datetime.now().strftime("%Y%m%dT%H-%M-%S")


def extract_scenarios(input_folder, output_folder, sc_num):
def extract_scenarios(output_folder, sc_num):
"""
Extract scenario names from Excel files in a folder and merge them with 'DataContainer_Sc_' prefix.
:param input_folder: Path to the folder containing scenario files.
:param output_folder: Path to the folder where the output files will be stored.
:param sc_num: Number of scenarios to extract.
:return: List of merged scenario names.
Expand All @@ -18,9 +17,9 @@ def extract_scenarios(input_folder, output_folder, sc_num):
files = list(folder_path.glob("*.pkl"))
files.sort(key=lambda f: f.stat().st_mtime)
if sc_num is None:
folder_path = Path(input_folder)
folder_path = Path(output_folder)
try:
sc_num = len(list(folder_path.glob("*.xlsx")))
sc_num = len(list(folder_path.glob("*.pkl")))
except FileNotFoundError:
sc_num = 1
files = files[-sc_num:]
Expand All @@ -32,132 +31,26 @@ def extract_scenarios(input_folder, output_folder, sc_num):
return scenarios


def cmodule_is_standalone(debug: bool = False) -> bool:
def set_paths(user_input: dict) -> dict:
"""
Check if cmodule is standalone or not, covering if the code is run as the main program, covering CLI, script, IDE,
and entry point runs.
:param debug: Flag to enable debug mode.
:return: Bool if cmodule is standalone or not.
Set paths dynamically based on user input.
:param user_input: dictionary with user input.
:return: dictionary of paths.
"""
import sys
import inspect
import __main__

reasons = []

# Running under a typical test runner => treat as imported
if "pytest" in sys.modules:
reasons.append("pytest detected in sys.modules")
if debug:
print("DEBUG: pytest present -> treated as imported")
return True
if any("unittest" in mod for mod in sys.modules):
reasons.append("unittest detected in sys.modules")
if debug:
print("DEBUG: unittest present -> treated as imported")
return True

# Simple and reliable check for most cases
if __name__ == "__main__":
reasons.append("__name__ == '__main__'")
if debug:
print("DEBUG: __name__ == '__main__' -> standalone")
return True

# Inspect stack: some IDEs or runners execute a wrapper that sets __name__ == '__main__'
# in a different frame. If any frame was executed as __main__, assume standalone entry.
for frame_info in inspect.stack():
g = frame_info.frame.f_globals
frame_name = g.get("__name__")
frame_file = g.get("__file__", None)
if frame_name == "__main__":
reasons.append(f"found frame with __name__ == '__main__' (file={frame_file})")
if debug:
print("DEBUG: stack frame with __name__ == '__main__' -> standalone")
print(f"DEBUG: frame file: {frame_file}")
return True

# Compare the top-level script path with this package path:
# if the top-level entry script is outside this package, it likely invoked/imported the package.
main_file = getattr(__main__, "__file__", None)
if main_file:
try:
main_path = Path(main_file).resolve()
package_root = Path(__file__).resolve().parents[1]
# If the top-level script is the module file itself -> standalone
if main_path == Path(__file__).resolve():
reasons.append("main_file equals this module file")
if debug:
print("DEBUG: main_file equals this module file -> standalone")
return True
# If the top-level script is *inside* the package: often still a standalone run (python -m)
if package_root in main_path.parents:
reasons.append("main_file is located inside package root (likely -m or IDE module run)")
if debug:
print("DEBUG: main_file inside package root -> standalone")
print(f"DEBUG: main_file={main_path}, package_root={package_root}")
return True
# Otherwise treat as imported
reasons.append("main_file exists but is outside package -> treated as imported")
if debug:
print("DEBUG: main_file outside package -> treated as imported")
print(f"DEBUG: main_file={main_path}, package_root={package_root}")
return False
except Exception as e:
# fallback
if debug:
print("DEBUG: error resolving main_file or package_root:", e)
pass

# If none of the above matched, assume imported
if debug:
print("DEBUG: no indication of standalone execution; reasons:", reasons)
return False


def set_paths(user_input: dict) -> dict:
PACKAGEDIR = Path(__file__).parent.parent.absolute()

if cmodule_is_standalone(debug=False):
if user_input[ParamNames.add_on_activated.value]:
import sys
print("Inconsistent settings:")
print(f"C-Module is executed as standalone: {cmodule_is_standalone(debug=False)}")
print(f"But parameter add_on_activated: {user_input[ParamNames.add_on_activated.value]}")
print(f"Harmonize settings to proceed")
sys.exit("Stopping execution.")

if user_input[ParamNames.add_on_activated.value] or not cmodule_is_standalone(debug=False):
# input and output paths for add-on c-module
if user_input[ParamNames.folderpath.value] is None:
# If user-defined path does not exists, use default path
# For compatibility with other modules, paths must be adapted
TIMBADIR = Path(__file__).parent.parent.parent.parent.parent.parent.absolute()
TARGETDIR = TIMBADIR
else:
# If user-defined path exist
USER_PATH = Path(user_input[ParamNames.folderpath.value]).absolute()
TARGETDIR = USER_PATH

TIMBADIR_INPUT = TARGETDIR / Path("TiMBA") / Path("data") / Path("input") / Path("01_Input_Files")
TIMBADIR_OUTPUT = TARGETDIR / Path("TiMBA") / Path("data") / Path("output") / Path("data")

if user_input["input_folder_path"] is None:
# take standard paths from the package
INPUT_FOLDER = PACKAGEDIR / Path("data") / Path("input")
OUTPUT_FOLDER = TIMBADIR_OUTPUT

else:
# input and output paths for standalone c-module
if user_input[ParamNames.folderpath.value] is None:
TARGETDIR = PACKAGEDIR
else:
USER_PATH = Path(user_input[ParamNames.folderpath.value]).absolute()
TARGETDIR = USER_PATH

TIMBADIR_INPUT = None
TIMBADIR_OUTPUT = None

INPUT_FOLDER = TARGETDIR / Path("data") / Path("input")
OUTPUT_FOLDER = TARGETDIR / Path("data") / Path("output")
# take paths as defined by the user
INPUT_FOLDER = Path(user_input["input_folder_path"]).absolute()
if user_input["output_folder_path"] is None:
# take standard paths from the package
OUTPUT_FOLDER = PACKAGEDIR / Path("data") / Path("output")
else:
# take paths as defined by the user
OUTPUT_FOLDER = Path(user_input["output_folder_path"]).absolute()

# Official statistics from the Food and Agriculture Organization
FAO_DIR = INPUT_FOLDER / Path("historical_data")
Expand All @@ -181,8 +74,6 @@ def set_paths(user_input: dict) -> dict:
path_dict = {
PathNames.INPUT_FOLDER.value: INPUT_FOLDER,
PathNames.OUTPUT_FOLDER.value: OUTPUT_FOLDER,
PathNames.TIMBADIR_INPUT.value: TIMBADIR_INPUT,
PathNames.TIMBADIR_OUTPUT.value: TIMBADIR_OUTPUT,
PathNames.FAO_DIR.value: FAO_DIR,
PathNames.FAOSTAT_URL.value: FAOSTAT_URL,
PathNames.FAOSTAT_DATA.value: FAOSTAT_DATA,
Expand All @@ -198,4 +89,5 @@ def set_paths(user_input: dict) -> dict:
PathNames.DEFAULT_PROJECTION_DIR.value: DEFAULT_PROJECTION_DIR,
PathNames.LOGGING_OUTPUT_FOLDER.value: LOGGING_OUTPUT_FOLDER
}
return path_dict
return path_dict

6 changes: 4 additions & 2 deletions c_module/user_io/default_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
end_year = 2050 # Not activated

read_in_pkl = True # Caution False option is not implemented yet
folderpath = None
input_folder_path = None
output_folder_path = None

# Forest carbon related parameters
calc_c_forest_agb = True
Expand All @@ -36,7 +37,8 @@
ParamNames.start_year.value: start_year,
ParamNames.end_year.value: end_year,
ParamNames.read_in_pkl.value: read_in_pkl,
ParamNames.folderpath.value: folderpath,
ParamNames.input_folder_path.value: input_folder_path,
ParamNames.output_folder_path.value: output_folder_path,
ParamNames.calc_c_forest_agb.value: calc_c_forest_agb,
ParamNames.calc_c_forest_bgb.value: calc_c_forest_bgb,
ParamNames.calc_c_forest_soil.value: calc_c_forest_soil,
Expand Down