Skip to content

Conversation

@ggutierrez-sunbright
Copy link

No description provided.

Comment on lines +64 to +66
# Confirm creation
if not typer.confirm("Create project with these settings?"):
raise typer.Abort()

Choose a reason for hiding this comment

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

This might be an issue if we want to use the app in a pipeline.

Suggested change
# Confirm creation
if not typer.confirm("Create project with these settings?"):
raise typer.Abort()


except Exception as e:
DisplayUtils.show_error(f"Project creation failed: {e}")
raise typer.Exit(1)

Choose a reason for hiding this comment

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

i'm not 100% sure about this. maybe at least we could do

Suggested change
raise typer.Exit(1)
raise typer.Exit(1) from e

but i'd need to take a look

Comment on lines +139 to +150
# Show summary
self._show_project_summary(request)

# Confirm
if not typer.confirm("Create project?"):
raise typer.Abort()

# Create project
with console.status("[bold green]Creating project..."):
response = self.service.setup_project(request)

self._show_creation_results(response)

Choose a reason for hiding this comment

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

This already happened in the previous command.
It feels like it should be a function taking a ProjectRequest object as a parameter

Comment on lines +221 to +237
def _show_project_summary(self, request: ProjectRequest):
"""Display project configuration summary"""

summary_table = DisplayUtils.create_table("Project Configuration",
["Setting", "Value"])

summary_table.add_row("Project Name", request.name)
summary_table.add_row("System", request.system.name)
summary_table.add_row("Structure", str(request.system.structure_file))
summary_table.add_row("Total Replicas", str(request.replicas.count))
summary_table.add_row("Solvents", ", ".join(set(request.replicas.solvents)))
summary_table.add_row("Simulation Time", f"{request.simulation.total_time} ns")
summary_table.add_row("Temperature", f"{request.simulation.temperature} K")
summary_table.add_row("Ensemble", request.simulation.ensemble.value)
summary_table.add_row("Restraints", request.simulation.restraint_mode.value)

console.print(summary_table)

Choose a reason for hiding this comment

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

This could be a free function, probably, as it doesn't depend directly on the cli plugin

Comment on lines +239 to +275
def _show_creation_results(self, response):
"""Display project creation results"""

if response.status == "success":
DisplayUtils.show_success(f"Project created successfully!")
else:
DisplayUtils.show_warning(f"Project created with issues")

# Summary
console.print(f"\nProject Directory: [bold]{response.project_dir}[/bold]")
console.print(f"Total Replicas: {response.total_replicas}")
console.print(f"Successful: [green]{response.successful_replicas}[/green]")

if response.successful_replicas < response.total_replicas:
failed = response.total_replicas - response.successful_replicas
console.print(f"Failed: [red]{failed}[/red]")

# Show any errors
if response.system.errors:
DisplayUtils.show_error("System setup errors:")
for error in response.system.errors:
console.print(f" - {error}")

# Show failed replicas
failed_replicas = [r for r in response.replicas if r.status == "failed"]
if failed_replicas:
DisplayUtils.show_error("Failed replicas:")
for replica in failed_replicas:
console.print(f" - {replica.name}: {replica.error}")

# Next steps
console.print("\n[bold]Next Steps:[/bold]")
console.print("1. Change to project directory:")
console.print(f" cd {response.project_dir}")
console.print("2. Run system preparation:")
console.print(" ./scripts/setup_project.sh")
console.print("3. Submit simulations to queue or run locally") No newline at end of file

Choose a reason for hiding this comment

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

Same thing.

if something, both functions would belong with the 'back-end' side
the cli should probably be just a dumb front-end.
if we're following hexagonal architecture / ddd, this would probably be at the service level

name: str = Field(..., description="System name")
structure_file: Path = Field(..., description="Path to OFF or PDB file")
structure_type: str = Field("off", description="Structure file type (off/pdb)")
force_fields: List[str] = Field(

Choose a reason for hiding this comment

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

since some time ago, we don't need to import typing.List anymore,
we can go with just list (same thing with many other collections and types)

Suggested change
force_fields: List[str] = Field(
force_fields: list[str] = Field(

"""Request model for system setup"""
name: str = Field(..., description="System name")
structure_file: Path = Field(..., description="Path to OFF or PDB file")
structure_type: str = Field("off", description="Structure file type (off/pdb)")

Choose a reason for hiding this comment

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

we could do with a str enum, but it might be overkill
another way would be to use the union of Literals

Suggested change
structure_type: str = Field("off", description="Structure file type (off/pdb)")
structure_type: Literal["off", "pdb"] = Field("off", description="Structure file type (off/pdb)")

Comment on lines +47 to +52
@field_validator('structure_file')
@classmethod
def validate_structure_file(cls, v):
if not v.exists():
raise ValueError(f"Structure file not found: {v}")
return v

Choose a reason for hiding this comment

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

this validator is going to be a bit of a pain if we want to create the templates from the pydantic models, let's either comment it out, or use it not as a field_validator but as some sort of model function later.

force_fields=force_fields
),
replicas=ReplicaRequest(
count=len(solvents) * replicas,

Choose a reason for hiding this comment

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

Suggested change
count=len(solvents) * replicas,
count=replicas,

Comment on lines +68 to +79
@field_validator('solvents')
@classmethod
def validate_solvents(cls, v, info): # Change parameter name
# Access other field values through info.data
if info.data and 'count' in info.data:
count = info.data['count']
if len(v) != count:
# If single solvent provided, replicate for all replicas
if len(v) == 1:
return v * count
raise ValueError(f"Number of solvents ({len(v)}) must match replica count ({count})")
return v

Choose a reason for hiding this comment

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

I don't think this is right (?)
we could have just a single solvent with 3 replicas, and the validator would fail that (?)

Comment on lines +62 to +66
class ReplicaRequest(BaseModel):
"""Request model for replica configuration"""
count: int = Field(3, ge=1, description="Number of replicas")
solvents: List[str] = Field(..., description="List of solvents for replicas")
naming_scheme: str = Field("{solvent}_{index}", description="Replica naming pattern")

Choose a reason for hiding this comment

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

I think it could be interesting to have something like this:

Suggested change
class ReplicaRequest(BaseModel):
"""Request model for replica configuration"""
count: int = Field(3, ge=1, description="Number of replicas")
solvents: List[str] = Field(..., description="List of solvents for replicas")
naming_scheme: str = Field("{solvent}_{index}", description="Replica naming pattern")
class ReplicaRequest(BaseModel):
"""Request model for replica configuration"""
solvents: dict[str, int] = Field(default_factory=lambda: {"WAT": 3}, description="dictionary of solvent name to number of replicas")

maybe the default should be just an empty dict, but this is okay

Comment on lines +97 to +105
class ProjectRequest(BaseModel):
"""Complete project request model"""
name: str = Field(..., description="Project name")
description: str = Field("", description="Project description")
system: SystemRequest
replicas: ReplicaRequest
simulation: SimulationRequest
output_dir: Optional[Path] = Field(None, description="Output directory")
queue_system: QueueSystem = Field(QueueSystem.LOCAL, description="Queue system")

Choose a reason for hiding this comment

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

it will be better for us if we make the structure of project request to match the yaml config, as it will make it trivial to both parse the config and generate the template

}


class SolventInfo(BaseModel):

Choose a reason for hiding this comment

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

just for consistency

Suggested change
class SolventInfo(BaseModel):
class SolventInfoResponse(BaseModel):

"""Save configuration to YAML file"""
import yaml
with open(path, 'w') as f:
yaml.dump(self.dict(), f, default_flow_style=False)

Choose a reason for hiding this comment

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

i think the current way of doing that would be

Suggested change
yaml.dump(self.dict(), f, default_flow_style=False)
yaml.dump(self.model_dump(), f, default_flow_style=False)

Also, it is possible that we want to use pydantic-settings BaseSettings
https://docs.pydantic.dev/latest/concepts/pydantic_settings/

Comment on lines +231 to +237
@classmethod
def from_yaml(cls, path: Path):
"""Load configuration from YAML file"""
import yaml
with open(path, 'r') as f:
data = yaml.safe_load(f)
return cls(**data)

Choose a reason for hiding this comment

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

This can be done directly with the pydantic

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.

3 participants