diff --git a/src/ansys/dynamicreporting/core/serverless/adr.py b/src/ansys/dynamicreporting/core/serverless/adr.py index 64e01c14..f314bc6b 100644 --- a/src/ansys/dynamicreporting/core/serverless/adr.py +++ b/src/ansys/dynamicreporting/core/serverless/adr.py @@ -361,6 +361,8 @@ def session_guid(self) -> uuid.UUID: def create_item(self, item_type: Type[Item], **kwargs: Any) -> Item: if not issubclass(item_type, Item): raise TypeError(f"{item_type} is not valid") + if not kwargs: + raise ADRException("At least one keyword argument must be provided to create the item.") return item_type.create( session=kwargs.pop("session", self._session), dataset=kwargs.pop("dataset", self._dataset), @@ -370,6 +372,10 @@ def create_item(self, item_type: Type[Item], **kwargs: Any) -> Item: def create_template(self, template_type: Type[Template], **kwargs: Any) -> Template: if not issubclass(template_type, Template): raise TypeError(f"{template_type} is not valid") + if not kwargs: + raise ADRException( + "At least one keyword argument must be provided to create the template." + ) template = template_type.create(**kwargs) parent = kwargs.get("parent") if parent is not None: @@ -378,13 +384,17 @@ def create_template(self, template_type: Type[Template], **kwargs: Any) -> Templ return template def get_report(self, **kwargs) -> Template: + if not kwargs: + raise ADRException( + "At least one keyword argument must be provided to fetch the report." + ) try: return Template.get(parent=None, **kwargs) except Exception as e: raise e def get_reports( - self, fields: Optional[list] = None, flat: bool = False + self, *, fields: Optional[list] = None, flat: bool = False ) -> Union[ObjectSet, list]: # return list of reports by default. # if fields are mentioned, return value list @@ -397,7 +407,7 @@ def get_reports( return out - def get_list_reports(self, r_type: str = "name") -> Union[ObjectSet, list]: + def get_list_reports(self, *, r_type: str = "name") -> Union[ObjectSet, list]: supported_types = ("name", "report") if r_type not in supported_types: raise ADRException(f"r_type must be one of {supported_types}") @@ -412,8 +422,12 @@ def get_list_reports(self, r_type: str = "name") -> Union[ObjectSet, list]: return self.get_reports() def render_report( - self, context: Optional[dict] = None, item_filter: str = "", **kwargs: Any + self, *, context: Optional[dict] = None, item_filter: str = "", **kwargs: Any ) -> str: + if not kwargs: + raise ADRException( + "At least one keyword argument must be provided to fetch the report." + ) try: return Template.get(**kwargs).render( request=self._request, context=context, item_filter=item_filter @@ -424,6 +438,7 @@ def render_report( def query( self, query_type: Union[Session, Dataset, Type[Item], Type[Template]], + *, query: str = "", **kwargs: Any, ) -> ObjectSet: @@ -482,6 +497,7 @@ def copy_objects( self, object_type: Union[Session, Dataset, Type[Item], Type[Template]], target_database: str, + *, query: str = "", target_media_dir: str = "", test: bool = False, diff --git a/src/ansys/dynamicreporting/core/serverless/base.py b/src/ansys/dynamicreporting/core/serverless/base.py index 353de211..ca04dd72 100644 --- a/src/ansys/dynamicreporting/core/serverless/base.py +++ b/src/ansys/dynamicreporting/core/serverless/base.py @@ -145,10 +145,10 @@ class BaseModel(metaclass=BaseMeta): ) # tracks the corresponding ORM instance def __repr__(self) -> str: - return f"<{self.__class__.__name__}: {self}>" + return f"<{self.__class__.__name__}: {self.guid}>" def __str__(self) -> str: - return f"{self.__class__.__name__} object {self.guid}" + return f"<{self.__class__.__name__}: {self.guid}>" def __post_init__(self): self._validate_field_types() @@ -358,6 +358,7 @@ def from_db(cls, orm_instance, parent=None): elif isinstance(value, Manager): type_ = get_origin(field_type) args = get_args(field_type) + # todo: move this check to the metaclass if type_ is None or not issubclass(type_, Iterable) or len(args) != 1: raise TypeError( f"The field '{attr}' in the dataclass must be a generic iterable" diff --git a/src/ansys/dynamicreporting/core/serverless/template.py b/src/ansys/dynamicreporting/core/serverless/template.py index b4173672..bd8ccffb 100644 --- a/src/ansys/dynamicreporting/core/serverless/template.py +++ b/src/ansys/dynamicreporting/core/serverless/template.py @@ -36,6 +36,9 @@ def __post_init__(self): raise TypeError("Cannot instantiate Template directly. Use Template.create()") super().__post_init__() + def __str__(self) -> str: + return f"<{self.__class__.__name__}: {self.name}>" + @property def type(self): return self.report_type @@ -61,6 +64,10 @@ def save(self, **kwargs): ) children_order = [] for child in self.children: + if not isinstance(child, Template): + raise TypeError( + f"Failed to save template because child '{child}' is not a Template object" + ) if not child._saved: raise Template.NotSaved( extra_detail="Failed to save template because its children are not saved" @@ -155,12 +162,12 @@ def get_filter(self): def set_filter(self, filter_str): if not isinstance(filter_str, str): - raise TypeError("Error: filter value should be a string") + raise TypeError("filter value should be a string") self.item_filter = filter_str def add_filter(self, filter_str=""): if not isinstance(filter_str, str): - raise TypeError("Error: filter value should be a string") + raise TypeError("filter value should be a string") self.item_filter += filter_str def get_params(self) -> dict: @@ -170,39 +177,79 @@ def set_params(self, new_params: dict) -> None: if new_params is None: new_params = {} if not isinstance(new_params, dict): - raise TypeError("Error: input must be a dictionary") + raise TypeError("input must be a dictionary") self.params = json.dumps(new_params) def add_params(self, new_params: dict): if new_params is None: new_params = {} if not isinstance(new_params, dict): - raise TypeError("Error: input must be a dictionary") - curr_params = json.loads(self.params) - self.params = json.dumps(curr_params | new_params) + raise TypeError("input must be a dictionary") + curr_params = self.get_params() + self.set_params(curr_params | new_params) def get_property(self): - params = json.loads(self.params) - return params.get("properties", {}) + return self.get_params().get("properties", {}) def set_property(self, new_props: dict): if new_props is None: new_props = {} if not isinstance(new_props, dict): - raise TypeError("Error: input must be a dictionary") - params = json.loads(self.params) + raise TypeError("input must be a dictionary") + params = self.get_params() params["properties"] = new_props - self.params = json.dumps(params) + self.set_params(params) def add_property(self, new_props: dict): if new_props is None: new_props = {} if not isinstance(new_props, dict): - raise TypeError("Error: input must be a dictionary") - params = json.loads(self.params) + raise TypeError("input must be a dictionary") + params = self.get_params() curr_props = params.get("properties", {}) params["properties"] = curr_props | new_props - self.params = json.dumps(params) + self.set_params(params) + + def get_sort_fields(self): + return self.get_params().get("sort_fields", []) + + def set_sort_fields(self, sort_field): + if not isinstance(sort_field, list): + raise ValueError("sorting filter is not a list") + params = self.get_params() + params["sort_fields"] = sort_field + self.set_params(params) + + def add_sort_fields(self, sort_field): + if not isinstance(sort_field, list): + raise ValueError("sorting filter is not a list") + params = self.get_params() + params["sort_fields"].extend(sort_field) + self.set_params(params) + + def get_sort_selection(self): + return self.get_params().get("sort_selection", "") + + def set_sort_selection(self, value="all"): + if not isinstance(value, str): + raise ValueError("sort selection input should be a string") + if value not in ("all", "first", "last"): + raise ValueError("sort selection not among the acceptable inputs") + params = self.get_params() + params["sort_selection"] = value + self.set_params(params) + + def get_filter_mode(self): + return self.get_params().get("filter_type", "items") + + def set_filter_mode(self, value="items"): + if not isinstance(value, str): + raise ValueError("filter mode input should be a string") + if value not in ("items", "root_replace", "root_append"): + raise ValueError("filter mode not among the acceptable inputs") + params = self.get_params() + params["filter_type"] = value + self.set_params(params) def render(self, context=None, request=None, item_filter="") -> str: if context is None: @@ -231,7 +278,71 @@ def render(self, context=None, request=None, item_filter="") -> str: class Layout(Template): - pass + def get_column_count(self): + return self.get_params().get("column_count", 1) + + def set_column_count(self, value): + if not isinstance(value, int): + raise ValueError("column count input should be an integer") + if value <= 0: + raise ValueError("column count input should be larger than 0") + params = self.get_params() + params["column_count"] = value + self.set_params(params) + + def get_column_widths(self): + return self.get_params().get("column_widths", [1.0]) + + def set_column_widths(self, value): + if not isinstance(value, list): + raise ValueError("column widths input should be a list") + if not all(isinstance(x, (int, float)) for x in value): + raise ValueError("column widths input should be a list of integers or floats") + if not all(x > 0 for x in value): + raise ValueError("column widths input should be larger than 0") + params = self.get_params() + params["column_widths"] = value + self.set_params(params) + + def get_html(self): + return self.get_params().get("HTML", "") + + def set_html(self, value=""): + if not isinstance(value, str): + raise ValueError("input needs to be a string") + params = self.get_params() + params["HTML"] = value + self.set_params(params) + + def get_comments(self): + return self.get_params().get("comments", "") + + def set_comments(self, value=""): + if not isinstance(value, str): + raise ValueError("input needs to be a string") + params = self.get_params() + params["comments"] = value + self.set_params(params) + + def get_transpose(self): + return self.get_params().get("transpose", 0) + + def set_transpose(self, value=0): + if not isinstance(value, int): + raise ValueError("input needs to be an integer") + params = self.get_params() + params["transpose"] = value + self.set_params(params) + + def get_skip(self): + return self.get_params().get("skip_empty", 0) + + def set_skip(self, value=0): + if not isinstance(value, int) or value not in (0, 1): + raise ValueError("input needs to be an integer (0 or 1)") + params = self.get_params() + params["skip_empty"] = value + self.set_params(params) class BasicLayout(Layout): @@ -299,7 +410,27 @@ class UserDefinedLayout(Layout): class Generator(Template): - pass + def get_generated_items(self): + return self.get_params().get("generate_merge", "add") + + def set_generated_items(self, value): + if not isinstance(value, str): + raise ValueError("generated items should be a string") + if value not in ("add", "replace"): + raise ValueError("input should be add or replace") + params = self.get_params() + params["generate_merge"] = value + self.set_params(params) + + def get_append_tags(self): + return self.get_params().get("generate_appendtags", True) + + def set_append_tags(self, value=True): + if not isinstance(value, bool): + raise ValueError("value should be True / False") + params = self.get_params() + params["generate_appendtags"] = value + self.set_params(params) class TableMergeGenerator(Generator):