Skip to content

Conversation

@mgeaghan
Copy link

I've added a new screen to capture default process resource requirements. Nothing prints to the final config file just yet but the data is captured within the TEMPLATE_CONFIG object.

In testing I found that the input field validation wasn't working correctly. I tracked it down to how the pydantic context object was being used. When editing a text input field, the utils.ValidateConfig class creates a brand new ConfigsCreateConfig object with just that field's data, using utils.init_context() to set the value of the variable _init_context_var:

with init_context({"is_nfcore": NFCORE_CONFIG_GLOBAL, "is_infrastructure": CONFIG_ISINFRASTRUCTURE_GLOBAL}):
    ConfigsCreateConfig(**{f"{self.key}": value})
    return self.success()

That context variable used to then conditionally assess the validity of the field:

@field_validator("config_pipeline_path")
@classmethod
def path_valid(cls, v: str, info: ValidationInfo) -> str:
    """Check that a path is valid."""
    context = info.context
    if context and (not context["is_infrastructure"] and not context["is_nfcore"]):
        if v.strip() == "":
            raise ValueError("Cannot be left empty.")
        if not Path(v).is_dir():
            raise ValueError("Must be a valid path.")
    return v

The problem is that init_context() resets the value of _init_context_var after it has been used, so when the user clicks a button to progress to the next screen, it will progress even if some fields are invalid. Because of this, I think this has gone unnoticed, since the fields still highlight red and display the error message, but only one or two of these actually stopped the progression through the app.

I've updated how the config gets updated when the user clicks 'Next' on the basic details screen so that we wrap the creation of the ConfigsCreateConfig object within a with init_context() call, similar to above:

try:
    with init_context({"is_nfcore": self.parent.NFCORE_CONFIG, "is_infrastructure": self.parent.CONFIG_TYPE == "infrastructure"}):
        self.parent.TEMPLATE_CONFIG = ConfigsCreateConfig(**config)

I'm not sure if this is the best way to approach this, so I thought I'd open this small PR to discuss.

I also updated a few of the @field_validator methods to properly reflect which fields are hidden and shown within the app.

PR checklist

  • This comment contains a description of changes (with reason)
  • CHANGELOG.md is updated
  • If you've fixed a bug or added code that should be tested, add tests!
  • Documentation in docs is updated

@mgeaghan mgeaghan requested a review from mirpedrol December 18, 2025 03:35
@mgeaghan mgeaghan changed the title Add default process requirements screen, fix validation code Add pipeline config screens, fix validation code Jan 14, 2026
@mgeaghan
Copy link
Author

Pushing latest commits - working screens for configuring default pipeline resources and custom named/labelled process resources. Also cleaned up the validation code a little more.

@jfy133 jfy133 changed the title Add pipeline config screens, fix validation code configbuilder: add pipeline config screens, fix validation code Jan 16, 2026
Copy link
Member

@mirpedrol mirpedrol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good!
I left some comments regarding the code.

On the other side, I tried some of the buttons. I assume this is not finished but just wanted to list the issues I found to keep track of them.

  • When creating an infrastructure config, the memory, cpus, and other parameters are not written in the final config
  • Pressing "Configure another process" was a bit confusing because I wasn't sure if my first process was submitted or I had some error and the name field was deleted.
  • It would be nice to add an additional button to "skip" if someone selected they wanted to configure anothe rprocess or a label but then decide they don't need this.

That's all for now :) overall it work very nicely! 🎉

"final": FinalScreen,
"hpc_question": ChooseHpc,
"hpc_customisation": HpcCustomisation,
"pipelineconfigquestion": PipelineConfigQuestion,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use snake_case for consistency, and change the variable name elsewhere

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I'll update that

def __init__(self, template_config: ConfigsCreateConfig, config_type: str, config_dir:str = '.'):
self.template_config = template_config
self.config_type = config_type
self.config_dir = sub(r'/$', '', config_dir)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a Path, we use the library pathlib. Then you will have methods to work with it easily

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, implementing that

except:
raise ValueError("Non-numeric value supplied for walltime value.")
if time_m is not None and time_m % 60 == 0:
time_h = int(time_m / 60)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if I follow the logic, time_h will override the previous value if hours is provided, right? Should we have a sum here?
time_h += int(time_m / 60)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, not sure why I made that so convoluted 🙃 Updating this to be much simpler/straight forward.

tmp_config['select_label'] = self.select_label
self.config_stack.append(tmp_config)
if event.button.id == "another":
# If configuring another process, push a blank config to the config stack
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you seen the example in the Textualize tutorial?
https://textual.textualize.io/tutorial/
There they add a new "stopwatch" when the user hits a button. I am wondering if we could do something similar with this screen. Do you think it will make things easier? or does it clutter the screen to much?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, I think that will work a bit nicer/cleaner. I'll arrange the input fields horizontally and it should work well

ConfigsCreateConfig(**tmp_config)
# Add to the config stack
self.config_stack.append(tmp_config)
if event.button.id == "another":
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment as before, to consider if adding a new object would be better than using a new screen

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants