Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions doc/changelog.d/4866.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add get and create methods and **kwarg support for create
Comment thread
Gobot1234 marked this conversation as resolved.
2 changes: 2 additions & 0 deletions src/ansys/fluent/core/codegen/allapigen.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ def generate(version: str, static_infos: dict, verbose: bool = False):
pickle.dump(api_tree, f)
if not config.codegen_skip_builtin_settings:
builtin_settingsgen.generate(version)
# Generate main .pyi file for the latest version processed
builtin_settingsgen.generate_main_pyi(version)


if __name__ == "__main__":
Expand Down
103 changes: 87 additions & 16 deletions src/ansys/fluent/core/codegen/builtin_settingsgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
from ansys.fluent.core.utils.fluent_version import all_versions

_PY_FILE = config.codegen_outdir / "solver" / "settings_builtin.py"
_PYI_FILE = config.codegen_outdir / "solver" / "settings_builtin.pyi"


def _get_settings_root(version: str):
Expand Down Expand Up @@ -74,6 +73,21 @@ def _get_named_objects_in_path(root, path, kind):
return named_objects, final_type


def _has_create_method(root, path):
"""Check if a setting object has a create method."""
try:
cls = root
comps = path.split(".")
for comp in comps:
cls = cls._child_classes[comp]
# Check if the class has 'create' in its child classes or command names
return "create" in getattr(cls, "_child_classes", {}) or "create" in getattr(
cls, "command_names", []
Comment on lines +93 to +94
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please can we comment why we check both of these. Otherwise, it looks speculative.

)
except (KeyError, AttributeError):
return False


def generate(version: str):
"""Generate builtin setting classes."""
print("Generating builtin settings...")
Expand Down Expand Up @@ -142,25 +156,82 @@ def generate(version: str):
)
f.write(" return instance(**kwargs)\n\n")

# Generate version-specific .pyi files
_PYI_FILE = (
config.codegen_outdir / "solver" / f"settings_builtin_{version.number}.pyi"
)
with open(_PYI_FILE, "w") as f:
Comment thread
Gobot1234 marked this conversation as resolved.
Outdated
for version in FluentVersion:
f.write(
f"from ansys.fluent.core.generated.solver.settings_{version.number} import root as settings_root_{version.number}\n"
)
# Import base classes
f.write(
"from ansys.fluent.core.solver.settings_builtin_bases import _SingletonSetting, _CreatableNamedObjectSetting, _NonCreatableNamedObjectSetting, _CommandSetting\n"
)
# Import version-specific root for type hints
f.write(
f"from ansys.fluent.core.generated.solver.settings_{version.number} import root as settings_root_{version.number}\n"
)
f.write("\n\n")
for name, v in DATA.items():
kind, path = v
f.write(f"class {name}(\n")
if isinstance(path, str):
path = {all_versions(): path}
for version_set, p in path.items():
if kind == "NamedObject":
p = f"{p}.child_object_type"
for v in reversed(list(version_set)):
f.write(f" type(settings_root_{v.number}.{p}),\n")
f.write("): ...\n\n")
if isinstance(path, dict):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It's not easy to see what the significance is of path being a dict, or what should be done if it is.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Path here is a dict[FluentVersionSet, str]. It's only used by a small handful of things

        {
            since(FluentVersion.v251): "setup.dynamic_mesh",
        },

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is it expected to be a dict always? IOW is it an error if a different type is encountered there?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

No it's just a dict when there's a version constraint on the path and the name changes between versions.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

OK, please could you add that as a code comment.

version_supported = False
for version_set, p in path.items():
if version in version_set:
path = p
version_supported = True
break
if not version_supported:
continue
named_objects, final_type = _get_named_objects_in_path(root, path, kind)
if kind == "NamedObject":
kind = f"{final_type}NamedObject"
path_with_child = f"{path}.child_object_type"
f.write(f"class {name}(\n")
f.write(f" _{kind}Setting,\n")
f.write(
f" type(settings_root_{version.number}.{path_with_child}),\n"
)
f.write("):\n")
if final_type == "Creatable":
f.write(
f" create = settings_root_{version.number}.{path}.create\n"
)
else:
f.write(" ...\n")
f.write("\n")
else:
# For Singleton and Command types
f.write(f"class {name}(\n")
f.write(f" _{kind}Setting,\n")
f.write(f" type(settings_root_{version.number}.{path}),\n")
f.write("):\n")
# Check if singleton has create method
if kind == "Singleton" and _has_create_method(root, path):
f.write(
f" create = settings_root_{version.number}.{path}.create\n"
)
else:
f.write(" ...\n")
f.write("\n")


def generate_main_pyi(version: str):
"""Generate main settings_builtin.pyi that imports from a specific version."""
_MAIN_PYI_FILE = config.codegen_outdir / "solver" / "settings_builtin.pyi"
version_obj = FluentVersion(version)
with open(_MAIN_PYI_FILE, "w") as f:
Comment thread
Gobot1234 marked this conversation as resolved.
f.write(f"# Re-export from version {version}\n")
Comment thread
Gobot1234 marked this conversation as resolved.
Outdated
f.write(
f"from ansys.fluent.core.generated.solver.settings_builtin_{version_obj.number} import *\n"
)


if __name__ == "__main__":
version = "261" # for development
generate(version)
# Generate for all available versions
versions = sorted([v.number for v in all_versions()])
Comment on lines +335 to +336
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
for version in versions:
try:
generate(str(version))
except Exception as e:
print(f"Failed to generate for version {version}: {e}")
# Generate main .pyi that imports from the latest version
generate_main_pyi(str(versions[-1]))
10 changes: 9 additions & 1 deletion src/ansys/fluent/core/codegen/settingsgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,10 +200,14 @@ def _get_unique_name(name):

def _write_function_stub(name, data, s_stub):
s_stub.write(f" def {name}(self")
if name == "create":
s_stub.write(", *") # allow only keyword arguments as best practice
for arg_name in data["argument_names"]:
arg_type = data["child_classes"][arg_name]["bases"][0]
py_arg_type = _arg_type_strings.get(arg_type, "Any")
s_stub.write(f", {arg_name}: {py_arg_type}")
if name == "create" and py_arg_type == "Any":
continue # don't write the object arguments for create as they shouldn't be used
Comment thread
Gobot1234 marked this conversation as resolved.
s_stub.write(f", {arg_name}: {py_arg_type} = ...")
s_stub.write("):\n")
# TODO: add return type
doc = data["doc"]
Expand Down Expand Up @@ -283,6 +287,10 @@ def _write_data(cls_name: str, python_name: str, data: dict, f: IO, f_stub: IO |
# to write only if it is not found in the _NAME_BY_HASH dict and avoid
# the _CLASS_WRITTEN set.
if k in command_names + query_names:
if k == "create" and v["child_classes"].keys() == {"name"}:
child_object_type = data["child_object_type"]
v["argument_names"] = child_object_type["child_names"]
v["child_classes"] = child_object_type["child_classes"]
_write_function_stub(k, v, s_stub)
classes_to_write[unique_name] = (child_python_name, v, hash_, False)
else:
Expand Down
12 changes: 7 additions & 5 deletions src/ansys/fluent/core/solver/flobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -1389,7 +1389,7 @@ def to_python_keys(cls, value):
query_names = []
_child_aliases = {}

def _create_child_object(self, cname: str):
def _create_child_object(self, cname: str, **kwargs: Any):
ret = self._objects.get(cname)
if not ret:
cls = self.__class__.child_object_type
Expand All @@ -1399,6 +1399,7 @@ def _create_child_object(self, cname: str):
"rename",
types.MethodType(lambda obj, name: _rename(self, name, cname), ret),
)
ret.set_state(kwargs)
return ret

def _update_objects(self):
Expand Down Expand Up @@ -1451,7 +1452,7 @@ def get_object_names(self):
obj_names_list = obj_names if isinstance(obj_names, list) else list(obj_names)
return obj_names_list

def __getitem__(self, name: str) -> ChildTypeT:
def __getitem__(self, name: str, **kwargs: Any) -> ChildTypeT:
Comment thread
Gobot1234 marked this conversation as resolved.
Comment thread
Gobot1234 marked this conversation as resolved.
if name not in self.get_object_names():
if self.flproxy.has_wildcard(name):
child_cls = self.__class__.child_object_type
Expand All @@ -1473,7 +1474,7 @@ def __getitem__(self, name: str) -> ChildTypeT:

obj = self._objects.get(name)
if not obj:
obj = self._create_child_object(name)
obj = self._create_child_object(name, **kwargs)
return obj

def get(self, name: str) -> ChildTypeT:
Expand Down Expand Up @@ -1727,7 +1728,7 @@ def _get_new_keywords(obj, *args, **kwds):
newkwds[alias] = v
elif k in obj.argument_names:
newkwds[k] = v
else:
elif k not in obj.get_active_child_names():
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

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

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'.

Copilot uses AI. Check for mistakes.
unknown_keywords.add(k)
for k in unknown_keywords:
# Noisily ignore unknown keywords
Expand Down Expand Up @@ -1843,7 +1844,8 @@ def execute_command(self, *args, **kwds):
and isinstance(self._parent, NamedObject)
and ret in self._parent
):
Comment thread
Gobot1234 marked this conversation as resolved.
return self._parent[ret]
child_attributes = {k: v for k, v in kwds.items() if k != "name"}
return self._parent.__getitem__(ret, **child_attributes)
return_t = getattr(self, "return_type", None)
if return_t:
base_t = _baseTypes.get(return_t)
Expand Down
106 changes: 89 additions & 17 deletions src/ansys/fluent/core/solver/settings_builtin_bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@

"""Base classes for builtin setting classes."""

from typing import Protocol, runtime_checkable
from typing import Any, Protocol, runtime_checkable

from typing_extensions import Self

from ansys.fluent.core.solver.flobject import (
Comment on lines +27 to 30
InactiveObjectError,
Expand Down Expand Up @@ -82,19 +84,25 @@ def _get_settings_obj(settings_root, builtin_settings_obj):
return obj


def _initialize_settings(instance, defaults: dict, settings_source=None, **kwargs):
active_session = _get_active_session()
instance.__dict__.update(defaults | kwargs)
if settings_source is not None:
instance.settings_source = settings_source
elif active_session:
instance.settings_source = active_session
class _SettingsObjectMixin:
def __init__(
self,
defaults: dict,
settings_source: SettingsBase | Solver | None = None,
**kwargs: Any,
):
active_session = _get_active_session()
self.__dict__.update(defaults | kwargs)
if settings_source is not None:
self.settings_source = settings_source
elif active_session:
self.settings_source = active_session


class _SingletonSetting:
class _SingletonSetting(_SettingsObjectMixin):
# Covers groups, named-object containers and commands.
def __init__(self, settings_source: SettingsBase | Solver | None = None, **kwargs):
_initialize_settings(self, {"settings_source": None}, settings_source, **kwargs)
super().__init__({"settings_source": None}, settings_source, **kwargs)

def __setattr__(self, name, value):
if name == "settings_source":
Expand All @@ -107,12 +115,12 @@ def __setattr__(self, name, value):
super().__setattr__(name, value)


class _NonCreatableNamedObjectSetting:
class _NonCreatableNamedObjectSetting(_SettingsObjectMixin):
def __init__(
self, name: str, settings_source: SettingsBase | Solver | None = None, **kwargs
):
_initialize_settings(
self, {"settings_source": None, "name": name}, settings_source, **kwargs
super().__init__(
{"settings_source": None, "name": name}, settings_source, **kwargs
)

def __setattr__(self, name, value):
Expand All @@ -126,8 +134,27 @@ def __setattr__(self, name, value):
else:
super().__setattr__(name, value)


class _CreatableNamedObjectSetting:
@classmethod
def get(
cls,
settings_source: SettingsBase | Solver | None = None,
/,
*,
name: str,
) -> Self:
"""Get and return the singleton instance of this object in Fluent.
Comment thread
Gobot1234 marked this conversation as resolved.
Outdated

Parameters
----------
settings_source
Something with a ``settings`` attribute. If omitted the active session is assumed from the :func:`using` context manager.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Shouldn't use assumed here

name
Name of the object to get, if applicable, can be a wildcard pattern.
"""
return cls(settings_source=settings_source, name=name)


class _CreatableNamedObjectSetting(_SettingsObjectMixin):
def __init__(
self,
settings_source: SettingsBase | Solver | None = None,
Expand All @@ -137,8 +164,7 @@ def __init__(
):
if name and new_instance_name:
raise ValueError("Cannot specify both name and new_instance_name.")
_initialize_settings(
self,
super().__init__(
{
"settings_source": None,
"name": name,
Expand All @@ -148,6 +174,52 @@ def __init__(
**kwargs,
)

@classmethod
def get(
cls,
settings_source: SettingsBase | Solver | None = None,
/,
*,
name: str,
) -> Self:
"""Get and return the singleton instance of this object in Fluent.
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

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.'

Copilot uses AI. Check for mistakes.
Comment thread
Gobot1234 marked this conversation as resolved.
Outdated

Parameters
----------
settings_source
Something with a ``settings`` attribute. If omitted the active session is assumed from the :func:`using` context manager.
name
Name of the object to get, if applicable, can be a wildcard pattern.
"""
return cls(settings_source=settings_source, name=name)

@classmethod
def create(
cls,
settings_source: SettingsBase | Solver | None = None,
/,
name: str | None = None,
**kwargs: Any,
) -> Self:
"""Create and return an instance of this object in Fluent.

Parameters
----------
settings_source
Something with a ``settings`` attribute. If omitted the active session is assumed from the :func:`using` context manager.
name
Name of the new object to create. If omitted, a default name will be assigned by Fluent.
**kwargs
Additional attributes to set on the created object. This only works for direct value assignments, not for nested objects.
Comment thread
Gobot1234 marked this conversation as resolved.
"""
self = cls(
settings_source=settings_source,
new_instance_name=name,
)
self.set_state(kwargs)

return self

def __setattr__(self, name, value):
if name == "settings_source":
settings_root = _get_settings_root(value)
Expand Down
Loading
Loading