From 029015b81bb9254d4b80473f6be0dda3fb04b85d Mon Sep 17 00:00:00 2001 From: Emily Davis Date: Fri, 3 Apr 2026 13:12:32 -0600 Subject: [PATCH 1/5] Reload `natcap.invest.spec` before returning model spec to Workbench. --- src/natcap/invest/ui_server.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/natcap/invest/ui_server.py b/src/natcap/invest/ui_server.py index b1f9b651e4..1de2b37574 100644 --- a/src/natcap/invest/ui_server.py +++ b/src/natcap/invest/ui_server.py @@ -3,19 +3,20 @@ import json import logging -from osgeo import gdal from flask import Flask from flask import request from flask_cors import CORS import geometamaker import natcap.invest +import natcap.invest.spec +import natcap.invest.validation_messages from natcap.invest import cli from natcap.invest import datastack from natcap.invest import set_locale from natcap.invest import models -from natcap.invest import spec from natcap.invest import usage from natcap.invest import validation +from natcap.invest.spec import OptionStringInput LOGGER = logging.getLogger(__name__) @@ -66,6 +67,7 @@ def get_invest_getspec(): target_model = request.get_json() target_module = models.model_id_to_pyname[target_model] importlib.reload(natcap.invest.validation_messages) + importlib.reload(natcap.invest.spec) model_module = importlib.reload( importlib.import_module(name=target_module)) return model_module.MODEL_SPEC.to_json() @@ -88,7 +90,7 @@ def get_dynamic_dropdown_options(): model_module = importlib.import_module( name=models.model_id_to_pyname[payload['model_id']]) for arg_spec in model_module.MODEL_SPEC.inputs: - if (isinstance(arg_spec, spec.OptionStringInput) and + if (isinstance(arg_spec, OptionStringInput) and arg_spec.dropdown_function): results[arg_spec.id] = [ option.model_dump() for option in From abdc138f52803374c27b2ac647d8071e0bcfe48d Mon Sep 17 00:00:00 2001 From: Emily Davis Date: Fri, 3 Apr 2026 13:16:44 -0600 Subject: [PATCH 2/5] Reload `natcap.invest.spec` before returning model spec to CLI. --- src/natcap/invest/cli.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/natcap/invest/cli.py b/src/natcap/invest/cli.py index 5d9caca4ee..c3fcbd70c6 100644 --- a/src/natcap/invest/cli.py +++ b/src/natcap/invest/cli.py @@ -8,18 +8,16 @@ import json import logging import multiprocessing -import os import pprint import sys import textwrap import warnings import natcap.invest +import natcap.invest.spec +import natcap.invest.validation_messages from natcap.invest import datastack -from natcap.invest import set_locale -from natcap.invest import spec from natcap.invest import ui_server -from natcap.invest import utils from natcap.invest import models from pygeoprocessing.utils import GDALUseExceptions @@ -393,7 +391,7 @@ def main(user_args=None): 1, "Error when parsing JSON datastack:\n " + str(error)) # reload validation module first so it's also in the correct language - importlib.reload(importlib.import_module('natcap.invest.validation_messages')) + importlib.reload(natcap.invest.validation_messages) model_module = importlib.reload(importlib.import_module( name=models.model_id_to_pyname[parsed_datastack.model_id])) @@ -429,6 +427,7 @@ def main(user_args=None): if args.subcommand == 'getspec': target_model = models.model_id_to_pyname[args.model] + importlib.reload(natcap.invest.spec) model_module = importlib.reload( importlib.import_module(name=target_model)) model_spec = model_module.MODEL_SPEC From b9101316f2e683298e2fa13b73721d2c6319ac2a Mon Sep 17 00:00:00 2001 From: Emily Davis Date: Fri, 3 Apr 2026 13:27:46 -0600 Subject: [PATCH 3/5] Reload `natcap.invest.spec` before returning model spec from `rst_generator.invest_spec`. --- src/natcap/invest/rst_generator.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/natcap/invest/rst_generator.py b/src/natcap/invest/rst_generator.py index 622cede373..d217be9e33 100644 --- a/src/natcap/invest/rst_generator.py +++ b/src/natcap/invest/rst_generator.py @@ -3,8 +3,9 @@ from docutils import frontend from docutils import utils from docutils.parsers import rst + +import natcap.invest.spec from natcap.invest import set_locale -from natcap.invest import spec def parse_rst(text): @@ -164,6 +165,7 @@ def invest_spec(name, rawtext, text, lineno, inliner, options={}, content=[]): # access the 'language' setting language = inliner.document.settings.env.app.config.language or 'en' set_locale(language) + importlib.reload(natcap.invest.spec) importlib.reload(importlib.import_module(name=module_name)) return parse_rst(describe_input(module_name, keys)), [] From c8d7d8bdeb35604e85c33e7a810243c7d99edee0 Mon Sep 17 00:00:00 2001 From: Emily Davis Date: Fri, 3 Apr 2026 13:30:22 -0600 Subject: [PATCH 4/5] Wrap WORKSPACE `name` and `about` in `gettext` (plus some misc linting cleanup). --- src/natcap/invest/spec.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/natcap/invest/spec.py b/src/natcap/invest/spec.py index 1eb3102dfe..cb7a61e79b 100644 --- a/src/natcap/invest/spec.py +++ b/src/natcap/invest/spec.py @@ -236,7 +236,6 @@ def format_required_string(self) -> str: # assume that the about text will describe the conditional return gettext('conditionally required') - def capitalize_name(self) -> str: """Capitalize a self.name into title case. @@ -1317,7 +1316,6 @@ def validate(self, value): if not float(value).is_integer(): return validation_messages.NOT_AN_INTEGER.format(value=value) - @staticmethod def format_column(col, *args): """Format a column of a pandas dataframe that contains IntegerInput values. @@ -1615,7 +1613,7 @@ class OptionStringInput(Input): @model_validator(mode='after') def check_options(self): if self.dropdown_function and self.options: - raise ValueError(f'Cannot have both dropdown_function and options') + raise ValueError('Cannot have both dropdown_function and options') return self @property @@ -1636,7 +1634,7 @@ def validate(self, value): if self.options: option_keys = self.list_options() - if str(value).lower() not in option_keys: + if option_keys and str(value).lower() not in option_keys: return validation_messages.INVALID_OPTION.format(option_list=option_keys) @staticmethod @@ -2288,8 +2286,8 @@ def execute(self, args, create_logfile=False, log_level=logging.NOTSET, # Specs for common arg types ################################################## WORKSPACE = DirectoryInput( id="workspace_dir", - name="workspace", - about=( + name=gettext("workspace"), + about=gettext( "The folder where all the model's output files will be written." " If this folder does not exist, it will be created. If data" " already exists in the folder, it will be overwritten." @@ -2382,8 +2380,8 @@ def execute(self, args, create_logfile=False, log_level=logging.NOTSET, name=gettext("flow direction algorithm"), about=gettext("Flow direction algorithm to use."), options=[ - Option(key="D8", description="D8 flow direction"), - Option(key="MFD", description="Multiple flow direction") + Option(key="D8", about="D8 flow direction"), + Option(key="MFD", about="Multiple flow direction") ] ) From 74ae32412915411b6bb04dec16244d27969e2dfb Mon Sep 17 00:00:00 2001 From: Emily Davis Date: Fri, 3 Apr 2026 13:31:32 -0600 Subject: [PATCH 5/5] Some linting cleanup in `utils` (otherwise unchanged). --- src/natcap/invest/utils.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/natcap/invest/utils.py b/src/natcap/invest/utils.py index 1b47bcb9de..a5b3f995d9 100644 --- a/src/natcap/invest/utils.py +++ b/src/natcap/invest/utils.py @@ -2,8 +2,6 @@ import ast import codecs import contextlib -import functools -import json import logging import os import platform @@ -321,7 +319,7 @@ def read_csv_to_dataframe(path, **kwargs): 'encoding': 'utf-8-sig', **kwargs }) - except UnicodeDecodeError as error: + except UnicodeDecodeError: raise ValueError( f'The file {path} must be encoded as UTF-8 or ASCII') @@ -711,7 +709,7 @@ class _GDALPath: scheme : str URI scheme such as "https" or "zip+s3". """ - + def __init__(self, path, archive, scheme): self.path = path self.archive = archive