feat: add get and create methods and **kwarg support for create#4866
feat: add get and create methods and **kwarg support for create#4866
Conversation
This needs settings api changes that I haven't figured out
There was a problem hiding this comment.
Pull request overview
This PR adds get and create class methods to the object-oriented API for clearer alternatives to using name and new_instance_name parameters. The implementation refactors initialization logic into a shared mixin class and enables passing additional kwargs when creating objects.
Changes:
- Refactored initialization logic into
_SettingsInitializerMixinbase class - Added
getclass method to_SingletonSettingfor retrieving singleton instances - Added
createclass method to_CreatableNamedObjectSettingfor creating new instances with optional attributes
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/ansys/fluent/core/solver/settings_builtin_bases.py | Adds mixin class for shared initialization logic and implements get/create class methods |
| src/ansys/fluent/core/solver/flobject.py | Updates _create_child_object and __getitem__ to accept and pass kwargs, enabling attribute setting during object creation |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@Gobot1234 Please can we define the scope of the current work. E.g.:
If we know the scope, it makes it easier to understand the changes. Thanks. |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| @classmethod | ||
| def get(cls, settings_source: SettingsBase | Solver | None = None, /, *, name: str) -> Self: | ||
| """Get and return the singleton instance of this object in Fluent. |
There was a problem hiding this comment.
The docstring says 'singleton instance' but this method can be used on non-singleton classes like _CreatableNamedObjectSetting. The description should be more generic, such as 'Get and return an instance of this object in Fluent' to accurately reflect its usage across different setting types.
| """Get and return the singleton instance of this object in Fluent. | |
| """Get and return an instance of this object in Fluent. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| *, | ||
| name: str, | ||
| ) -> Self: | ||
| """Get and return the singleton instance of this object in Fluent. |
There was a problem hiding this comment.
The docstring describes this method as returning a 'singleton instance', but this is misleading for a named object that can have multiple instances. Consider changing to 'Get and return the named instance of this object in Fluent.'
|
Generated object oriented code looks like this now, which type checks far better, there's a per version split though. class SolidMaterials(
_SingletonSetting,
type(settings_root_261.setup.materials.solid),
):
create = settings_root_261.setup.materials.solid.create
class SolidMaterial(
_CreatableNamedObjectSetting,
type(settings_root_261.setup.materials.solid.child_object_type),
):
create = settings_root_261.setup.materials.solid.createcreate parameter's are also fully typed class manage_criteria(NamedObject[manage_criteria_child]):
_version: str
fluent_name: str
_python_name: str
command_names: list[str]
def create(self, *, name: str = ..., menu: str = ..., sync: bool = ..., index: int = ..., offset: int = ..., manual: bool = ..., active: bool = ..., frequency: int = ..., skip_until: int = ..., anisotropic: bool = ..., refinement_layers: int = ..., predicates: list[str] = ...):
"""
Create a new instance of the current object type.
Parameters
----------
name : str
Object name.
""" |
@deprecated("Use SolidCellZone.all() instead")
class SolidCellZones(
_SingletonSetting,
type(settings_root_261.setup.cell_zone_conditions.solid),
):
create = settings_root_261.setup.cell_zone_conditions.solid.create
class SolidCellZone(
_CreatableNamedObjectSetting,
type(settings_root_261.setup.cell_zone_conditions.solid.child_object_type),
):
create = settings_root_261.setup.cell_zone_conditions.solid.create
@deprecated("Use BoundaryCondition.all() instead")
class BoundaryConditions(
_SingletonSetting,
type(settings_root_261.setup.boundary_conditions),
):
...
class BoundaryCondition(
_NonCreatableNamedObjectSetting,
type(settings_root_261.setup.boundary_conditions.child_object_type),
):
...
@deprecated("Use AxisBoundary.all() instead")
class AxisBoundaries(
_SingletonSetting,
type(settings_root_261.setup.boundary_conditions.axis),
):
create = settings_root_261.setup.boundary_conditions.axis.create
class AxisBoundary(
_CreatableNamedObjectSetting,
type(settings_root_261.setup.boundary_conditions.axis.child_object_type),
):
create = settings_root_261.setup.boundary_conditions.axis.createafter talking with Sean we agreed it best to remove the plural versions of the classes in favour of using something which I would describe as more ORM like which feels much more familiar to me at least. API surface now looks like AxisBoundary.get(solver, name='boundary')
AxisBoundary.all(solver)
AxisBoundary.create(solver, name='foo', other='parameters that', are='just values') |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 6 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| "PlaneSurfaces": ("Singleton", "results.surfaces.plane_surface", None), | ||
| "PlaneSurface": ("NamedObject", "results.surfaces.plane_surface", None), |
There was a problem hiding this comment.
Inconsistent reciprocal relationships: PlaneSurfaces and PlaneSurface have None as their reciprocal instead of referencing each other like other surface pairs (e.g., LineSurfaces/LineSurface on lines 503-511). This breaks the symmetry in the data structure.
| "PlaneSurfaces": ("Singleton", "results.surfaces.plane_surface", None), | |
| "PlaneSurface": ("NamedObject", "results.surfaces.plane_surface", None), | |
| "PlaneSurfaces": ( | |
| "Singleton", | |
| "results.surfaces.plane_surface", | |
| "PlaneSurface", | |
| ), | |
| "PlaneSurface": ( | |
| "NamedObject", | |
| "results.surfaces.plane_surface", | |
| "PlaneSurfaces", | |
| ), |
| "Contours": ("Singleton", "results.graphics.contour", None), | ||
| "Contour": ("NamedObject", "results.graphics.contour", None), | ||
| "Vectors": ("Singleton", "results.graphics.vector", None), | ||
| "Vector": ("NamedObject", "results.graphics.vector", None), |
There was a problem hiding this comment.
Multiple graphics objects (Contours/Contour, Vectors/Vector, Pathlines/Pathline, LICs/LIC) have None reciprocals, unlike most other named object pairs. Verify whether these should reference each other for consistency with the new ORM-like API.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| "ReadCase": ("Command", "file.read_case"), | ||
| "ReadData": ("Command", "file.read_data"), | ||
| "ReadCaseData": ("Command", "file.read_case_data"), | ||
| "SimulationReports", |
There was a problem hiding this comment.
The 'SimulationReports' entry has 'SimulationReports' as its reciprocal, creating a self-reference. This should likely be None for a singleton container or have a proper singular counterpart like 'SimulationReport' if one exists.
| "SimulationReports", | |
| None, |
| "Contours": ("Singleton", "results.graphics.contour", None), | ||
| "Contour": ("NamedObject", "results.graphics.contour", None), |
There was a problem hiding this comment.
Contours and Contour lack reciprocal relationships. For consistency with the API pattern shown in other entries, set Contours' reciprocal to 'Contour' and Contour's reciprocal to 'Contours'.
| "Contours": ("Singleton", "results.graphics.contour", None), | |
| "Contour": ("NamedObject", "results.graphics.contour", None), | |
| "Contours": ("Singleton", "results.graphics.contour", "Contour"), | |
| "Contour": ("NamedObject", "results.graphics.contour", "Contours"), |
|
@Gobot1234 In the code snippets in your description, an important use case is binding the solver AxisBoundary(solver).get(name='boundary')
# or hold onto a solver-context-specific AxisBoundary object (which can be passed around)
axis = AxisBoundary(solver)
axis.all()
axis_foo = axis.create(name='foo', other='parameters that', are='just values')
axis_anon = axis.create(other='parameters that', are='just values')This makes the interface more substitutable and the code more flexible, since consumers can Perhaps both formats are already supported in what you have implemented? |
I think I can add support for this form though it'll require more changes. I was honestly thinking the best way to operate on this thing was using the following as it's scoped nicely and closes itself when required with pyfluent.Solver.from_install() as solver, pyfluent.using(solver):
AxisBoundary().get(name='boundary') |
I agree that the context manager pattern is a good one, and it’s something we already encourage for solver lifetime and scoping. However, I think that concern is largely orthogonal to how solver context is modeled in the object API itself. In the pattern you show: with pyfluent.Solver.from_install() as solver, pyfluent.using(solver):
AxisBoundary().get(name='boundary')
Context managers mitigate some ergonomics, but they don’t quite provide the same expressive power as a context-aware object. That’s the reason I prefer constructor binding as the primary model (even if method-level solver passing remains supported). |
@mkundu1 I'd like to get your feedback on this point too. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| report_file = ReportFile(solver) | ||
| report_file.create(name="report-file-1") | ||
| assert report_file == report_file.get(solver, name="report-file-1") | ||
| assert report_file.all() == list(solver.settings.setup.report_files["*"]) | ||
|
|
||
|
|
There was a problem hiding this comment.
Instance method create() is being called, but report_file has already been initialized as a ReportFile object. Since create() is a class method, this should be ReportFile.create(solver, name=\"report-file-1\"), or the variable should be assigned the result: report_file = ReportFile(solver).create(name=\"report-file-1\").
| report_file = ReportFile(solver) | |
| report_file.create(name="report-file-1") | |
| assert report_file == report_file.get(solver, name="report-file-1") | |
| assert report_file.all() == list(solver.settings.setup.report_files["*"]) | |
| report_file = ReportFile(solver).create(name="report-file-1") | |
| assert report_file == report_file.get(solver, name="report-file-1") | |
| assert report_file.all() == list(solver.settings.setup.report_files["*"]) |
| report_file = ReportFile(solver) | ||
| report_file.create(name="report-file-1") | ||
| assert report_file == report_file.get(solver, name="report-file-1") | ||
| assert report_file.all() == list(solver.settings.setup.report_files["*"]) |
There was a problem hiding this comment.
The all() method is a class method, not an instance method. This should be ReportFile.all(solver) instead of report_file.all().
| assert report_file.all() == list(solver.settings.setup.report_files["*"]) | |
| assert list(ReportFile.all(solver)) == list(solver.settings.setup.report_files["*"]) |
| elif k in obj.argument_names: | ||
| newkwds[k] = v | ||
| else: | ||
| elif k not in obj.get_active_child_names(): |
There was a problem hiding this comment.
This condition may not correctly filter out child object parameters passed to create(). If a child name exists but is not 'active', valid kwargs might still be filtered out. Consider validating this logic or adding a comment explaining when child names are 'active'.
| # Generate for all available versions | ||
| versions = sorted([v.number for v in all_versions()]) |
There was a problem hiding this comment.
The comment mentions 'all available versions' but the actual code in if __name__ == '__main__' generates for all versions. This might cause unexpected behavior if run directly vs imported, as the main generation flow uses a single version. Consider clarifying or consolidating the version selection logic.
|
I'm curious as to whether it makes more sense to deprecate the singular or plurals, I think the plural methods make more sense grammatically to get() and all() for but less sense for create() but also interested to hear your opinion @mkundu1 |
| Parameters | ||
| ---------- | ||
| solver | ||
| Something with a ``settings`` attribute. If omitted the active session is assumed from the :func:`using` context manager. |
There was a problem hiding this comment.
Shouldn't use assumed here
The API surface now looks like which is fairly ORM like
Context
Fixes #4863
And adds create and get methods as more clear methods for using name and new_instance_name to the object oriented API
Scope
This PR affects all of the object oriented API, and some of the settings API related code surrounding child creation
This is due to the following changes:
.create()and the object oriented types #4863)