Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generalize edge property type implementation #607

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions .github/workflows/ci_cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ jobs:
poetry run pytest -v --license-server=1055@$LICENSE_SERVER --no-server-log-files --docker-image=$IMAGE_NAME --cov=ansys.acp.core --cov-report=term --cov-report=xml --cov-report=html
env:
LICENSE_SERVER: ${{ secrets.LICENSE_SERVER }}
IMAGE_NAME: "ghcr.io/ansys/acp${{ github.event.inputs.docker_image_suffix || ':latest' }}"
IMAGE_NAME: ${{ env.DOCKER_IMAGE_NAME }}

- name: "Upload coverage to Codecov"
uses: codecov/codecov-action@v4
Expand Down Expand Up @@ -279,7 +279,7 @@ jobs:
run: >
poetry run
ansys-launcher configure ACP docker_compose
--image_name_pyacp=ghcr.io/ansys/acp${{ github.event.inputs.docker_image_suffix || ':latest' }}
--image_name_pyacp=${{ env.DOCKER_IMAGE_NAME }}
--image_name_filetransfer=ghcr.io/ansys/tools-filetransfer:latest
--license_server=1055@$LICENSE_SERVER
--keep_volume=False
Expand Down
5 changes: 3 additions & 2 deletions doc/source/api/linked_object_definitions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ Linked object definitions
:toctree: _autosummary

FabricWithAngle
Lamina
LinkedSelectionRule
TaperEdge
PrimaryPly
SubShape
Lamina
TaperEdge
1 change: 1 addition & 0 deletions doc/source/api/tree_objects.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ ACP objects

AnalysisPly
BooleanSelectionRule
ButtJointSequence
CADComponent
CADGeometry
CutoffSelectionRule
Expand Down
4 changes: 4 additions & 0 deletions src/ansys/acp/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
BooleanSelectionRule,
BooleanSelectionRuleElementalData,
BooleanSelectionRuleNodalData,
ButtJointSequence,
CADComponent,
CADGeometry,
CutoffMaterialType,
Expand Down Expand Up @@ -104,6 +105,7 @@
PlyCutoffType,
PlyGeometryExportFormat,
PlyType,
PrimaryPly,
ProductionPly,
ProductionPlyElementalData,
ProductionPlyNodalData,
Expand Down Expand Up @@ -153,6 +155,7 @@
"BooleanSelectionRule",
"BooleanSelectionRuleElementalData",
"BooleanSelectionRuleNodalData",
"ButtJointSequence",
"CADComponent",
"CADGeometry",
"ConnectLaunchConfig",
Expand Down Expand Up @@ -221,6 +224,7 @@
"PlyCutoffType",
"PlyGeometryExportFormat",
"PlyType",
"PrimaryPly",
"print_model",
"ProductionPly",
"ProductionPlyElementalData",
Expand Down
3 changes: 3 additions & 0 deletions src/ansys/acp/core/_tree_objects/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
BooleanSelectionRuleElementalData,
BooleanSelectionRuleNodalData,
)
from .butt_joint_sequence import ButtJointSequence, PrimaryPly
from .cad_component import CADComponent
from .cad_geometry import CADGeometry, TriangleMesh
from .cutoff_selection_rule import (
Expand Down Expand Up @@ -126,6 +127,7 @@
"BooleanSelectionRule",
"BooleanSelectionRuleElementalData",
"BooleanSelectionRuleNodalData",
"ButtJointSequence",
"CADComponent",
"CADGeometry",
"CutoffMaterialType",
Expand Down Expand Up @@ -184,6 +186,7 @@
"PlyCutoffType",
"PlyGeometryExportFormat",
"PlyType",
"PrimaryPly",
"ProductionPly",
"ProductionPlyElementalData",
"ProductionPlyNodalData",
Expand Down
137 changes: 134 additions & 3 deletions src/ansys/acp/core/_tree_objects/_grpc_helpers/edge_property_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,28 @@
from __future__ import annotations

from collections.abc import Callable, Iterable, Iterator, MutableSequence
import functools
import sys
import textwrap
from typing import Any, Concatenate, Protocol, TypeVar, cast, overload

from google.protobuf.message import Message
from typing_extensions import ParamSpec, Self

from ansys.api.acp.v0 import base_pb2

from ..._utils.property_protocols import ReadWriteProperty
from .._object_cache import ObjectCacheMixin, constructor_with_cache
from ..base import CreatableTreeObject
from .property_helper import _exposed_grpc_property, _wrap_doc, grpc_data_getter, grpc_data_setter
from ..base import CreatableTreeObject, ServerWrapper
from .polymorphic_from_pb import tree_object_from_resource_path
from .property_helper import (
_exposed_grpc_property,
_get_data_attribute,
_set_data_attribute,
_wrap_doc,
grpc_data_getter,
grpc_data_setter,
)
from .protocols import GrpcObjectBase

__all__ = [
Expand Down Expand Up @@ -67,6 +79,125 @@
raise NotImplementedError


class EdgePropertyTypeBase(GenericEdgePropertyType):
"""Common implementation of the GenericEdgePropertyType protocol."""

__slots__ = ("_pb_object", "_callback_apply_changes", "_server_wrapper")
_PB_OBJECT_TYPE: type[Message]

def __init__(self) -> None:
self._pb_object = self._PB_OBJECT_TYPE()
self._callback_apply_changes: Callable[[], None] | None = None
self._server_wrapper: ServerWrapper | None = None

def _set_callback_apply_changes(self, callback_apply_changes: Callable[[], None]) -> None:
self._callback_apply_changes = callback_apply_changes

@classmethod
def _from_pb_object(
cls,
parent_object: CreatableTreeObject,
message: Message,
callback_apply_changes: Callable[[], None],
) -> Self:
new_obj = cls()
new_obj._pb_object = message
new_obj._set_callback_apply_changes(callback_apply_changes)
new_obj._server_wrapper = parent_object._server_wrapper
return new_obj

def _to_pb_object(self) -> Message:
return self._pb_object

def _check(self) -> bool:
return self._server_wrapper is not None

def __eq__(self, other: Any) -> bool:
if isinstance(other, self.__class__):
return cast(bool, self._pb_object == other._pb_object)
return False

Check warning on line 118 in src/ansys/acp/core/_tree_objects/_grpc_helpers/edge_property_list.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/acp/core/_tree_objects/_grpc_helpers/edge_property_list.py#L116-L118

Added lines #L116 - L118 were not covered by tests

def clone(self) -> Self:
"""Create a new unstored object with the same properties."""
obj = self.__class__()
obj._pb_object.CopyFrom(self._pb_object)
obj._server_wrapper = self._server_wrapper

Check warning on line 124 in src/ansys/acp/core/_tree_objects/_grpc_helpers/edge_property_list.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/acp/core/_tree_objects/_grpc_helpers/edge_property_list.py#L122-L124

Added lines #L122 - L124 were not covered by tests
# do not copy the callback, as the new object's parent is not yet defined
return obj

Check warning on line 126 in src/ansys/acp/core/_tree_objects/_grpc_helpers/edge_property_list.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/acp/core/_tree_objects/_grpc_helpers/edge_property_list.py#L126

Added line #L126 was not covered by tests

def __repr__(self) -> str:
args = ", ".join(f"{k}={getattr(self, k)!r}" for k in self._GRPC_PROPERTIES)
return f"{self.__class__.__name__}({args})"


def edge_property_type_linked_object(
name_in_pb: str,
allowed_types: type[CreatableTreeObject] | tuple[type[CreatableTreeObject], ...] | None = None,
allowed_types_getter: (
Callable[[], type[CreatableTreeObject] | tuple[type[CreatableTreeObject], ...]] | None
) = None,
) -> Any:
"""Define the linked object property for the edge property type."""
if allowed_types is None == allowed_types_getter is None:
raise ValueError("Exactly one of 'allowed_types' and 'allowed_types_getter' must be given.")

Check warning on line 142 in src/ansys/acp/core/_tree_objects/_grpc_helpers/edge_property_list.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/acp/core/_tree_objects/_grpc_helpers/edge_property_list.py#L142

Added line #L142 was not covered by tests

@functools.cache
def _allowed_types() -> tuple[type[CreatableTreeObject], ...]:
if allowed_types_getter is not None:
allowed_types = allowed_types_getter()
if not isinstance(allowed_types, tuple):
allowed_types = (allowed_types,)

Check warning on line 149 in src/ansys/acp/core/_tree_objects/_grpc_helpers/edge_property_list.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/acp/core/_tree_objects/_grpc_helpers/edge_property_list.py#L149

Added line #L149 was not covered by tests
return allowed_types

def getter(self: EdgePropertyTypeBase) -> Any:
resource_path = _get_data_attribute(self._pb_object, name_in_pb)
if self._server_wrapper is None:
return None

Check warning on line 155 in src/ansys/acp/core/_tree_objects/_grpc_helpers/edge_property_list.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/acp/core/_tree_objects/_grpc_helpers/edge_property_list.py#L155

Added line #L155 was not covered by tests
return tree_object_from_resource_path(
resource_path=resource_path,
server_wrapper=self._server_wrapper,
allowed_types=_allowed_types(),
)

def setter(self: EdgePropertyTypeBase, value: Any) -> None:
if value is None:
server_wrapper = None
resource_path = base_pb2.ResourcePath(value="")
else:
if not isinstance(value, _allowed_types()):
raise TypeError(

Check warning on line 168 in src/ansys/acp/core/_tree_objects/_grpc_helpers/edge_property_list.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/acp/core/_tree_objects/_grpc_helpers/edge_property_list.py#L168

Added line #L168 was not covered by tests
f"Expected object of type {allowed_types}, got type {type(value)} instead."
)
server_wrapper = value._server_wrapper
resource_path = value._resource_path

self._server_wrapper = server_wrapper
_set_data_attribute(self._pb_object, name_in_pb, resource_path)
if self._callback_apply_changes:
self._callback_apply_changes()

Check warning on line 177 in src/ansys/acp/core/_tree_objects/_grpc_helpers/edge_property_list.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/acp/core/_tree_objects/_grpc_helpers/edge_property_list.py#L177

Added line #L177 was not covered by tests

return _exposed_grpc_property(getter, setter)


T = TypeVar("T")


def edge_property_type_attribute(
name_in_pb: str,
from_protobuf: Callable[[Any], T] = lambda x: x,
to_protobuf: Callable[[T], Any] = lambda x: x,
) -> ReadWriteProperty[T, T]:
def getter(self: EdgePropertyTypeBase) -> Any:
return from_protobuf(_get_data_attribute(self._pb_object, name_in_pb))

def setter(self: EdgePropertyTypeBase, value: Any) -> None:
_set_data_attribute(self._pb_object, name_in_pb, to_protobuf(value))
if self._callback_apply_changes:
self._callback_apply_changes()

Check warning on line 196 in src/ansys/acp/core/_tree_objects/_grpc_helpers/edge_property_list.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/acp/core/_tree_objects/_grpc_helpers/edge_property_list.py#L196

Added line #L196 was not covered by tests

return _exposed_grpc_property(getter, setter)


ValueT = TypeVar("ValueT", bound=GenericEdgePropertyType)


Expand Down Expand Up @@ -364,7 +495,7 @@
def _apply_changes(self) -> None:
"""Apply changes to the list.

Use to support in-place modification.
Used to support in-place modification.
This function applies the changes if someone edits one entry of the list.
"""
self._set_object_list(self._object_list)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
ValueT = TypeVar("ValueT", bound=CreatableTreeObject)


__all__ = ["LinkedObjectList", "define_linked_object_list"]
__all__ = ["LinkedObjectList", "define_linked_object_list", "define_polymorphic_linked_object_list"]


class LinkedObjectList(ObjectCacheMixin, MutableSequence[ValueT]):
Expand Down Expand Up @@ -302,11 +302,19 @@ def setter(self: ValueT, value: list[ChildT]) -> None:


def define_polymorphic_linked_object_list(
attribute_name: str, allowed_types: tuple[Any, ...]
attribute_name: str,
allowed_types: tuple[Any, ...] | None = None,
allowed_types_getter: Callable[[], tuple[Any, ...]] | None = None,
) -> Any:
"""Define a list of linked tree objects with polymorphic types."""
if allowed_types is None != allowed_types_getter is None:
raise ValueError("Exactly one of allowed_types and allowed_types_getter must be provided.")

def getter(self: ValueT) -> LinkedObjectList[Any]:
nonlocal allowed_types
if allowed_types_getter is not None:
allowed_types = allowed_types_getter()

return LinkedObjectList(
_parent_object=self,
_attribute_name=attribute_name,
Expand Down
Loading
Loading