Module pysapscript.shell_table

Classes

class ShellTable (session_handle: win32com.client.CDispatch,
element: str,
load_table: bool = True)
Expand source code
class ShellTable:
    """
    A class representing a shell table
    """

    def __init__(
        self, 
        session_handle: win32com.client.CDispatch, 
        element: str, 
        load_table: bool = True,
    ) -> None:
        """
        Usually table contains a table of data, but it can also be a non-data shell table, that holds toolbar

        Args:
            session_handle (win32com.client.CDispatch): SAP session handle
            element (str): SAP table element
            load_table (bool): loads table if True, default True

        Raises:
            ActionException: error reading table data
        """
        self.table_element = element
        self._session_handle = session_handle
        self.data_present = False

        if load_table:
            self.data = self._read_shell_table()
        else:
            self.data = pl.DataFrame()

        shape = self._read_shape()
        self.rows = shape[0]
        self.columns = shape[1]

    def __repr__(self) -> str:
        return repr(self.data)

    def __str__(self) -> str:
        return str(self.data)

    def __eq__(self, other: object) -> bool:
        if isinstance(other, ShellTable):
            return self.data.equals(other.data)
        else:
            raise NotImplementedError(f"Cannot compare ShellTable with {type(other)}")

    def __hash__(self) -> int:
        return hash(f"{self._session_handle}{self.table_element}{self.data.shape}")

    def __getitem__(self, item: object) -> dict[str, Any] | list[dict[str, Any]]:
        if self.data_present is False:
            raise ValueError("Data was not found in shell table")

        if isinstance(item, int):
            return self.data.row(item, named=True)
        elif isinstance(item, slice):
            if item.step is not None:
                raise NotImplementedError("Step is not supported")

            sl = self.data.slice(item.start, item.stop - item.start)
            return sl.to_dicts()
        else:
            raise ValueError("Incorrect type of index")

    def __iter__(self) -> "ShellTableRowIterator":
        return ShellTableRowIterator(self.data)
    
    def _read_shape(self) -> tuple[int, int]:
        """
        Reads shape of the shell table

        Returns:
            tuple[int, int]: number of rows and columns in the table
        """
        try:
            shell = self._session_handle.findById(self.table_element)
            rows_count = shell.RowCount
            columns_count = len(shell.ColumnOrder)

            return rows_count, columns_count
        
        except Exception as e:
            raise exceptions.ActionException(f"Error reading shape of element {self.table_element}: {e}")

    def _read_shell_table(self) -> pl.DataFrame:
        """
        Reads table of shell table

        If the table is too big, the SAP will not render all the data.
        Default is to load table before reading it

        Args:
            load_table (bool): whether to load table before reading

        Returns:
            pandas.DataFrame: table data

        Raises:
            ActionException: Error reading table

        Example:
            ```
            table = main_window.read_shell_table("wnd[0]/usr/shellContent/shell")
            ```
        """
        try:
            shell = self._session_handle.findById(self.table_element)

            if hasattr(shell, "ColumnOrder") is False or hasattr(shell, "RowCount") is False:
                return pl.DataFrame()

            columns = shell.ColumnOrder
            rows_count = shell.RowCount

            if rows_count == 0:
                return pl.DataFrame()

            self.data_present = True

            self.load()

            data = [
                {column: shell.GetCellValue(i, column) for column in columns}
                for i in range(rows_count)
            ]

            return pl.DataFrame(data)

        except Exception as ex:
            raise exceptions.ActionException(f"Error reading element {self.table_element}: {ex}")

    def to_polars_dataframe(self) -> pl.DataFrame:
        """
        Get table data as a polars DataFrame

        Returns:
            polars.DataFrame: table data
        """
        if self.data_present is False:
            raise ValueError("Data was not found in shell table")

        return self.data

    def to_pandas_dataframe(self) -> pandas.DataFrame:
        """
        Get table data as a pandas DataFrame

        Returns:
            pandas.DataFrame: table data
        """
        if self.data_present is False:
            raise ValueError("Data was not found in shell table")

        return self.data.to_pandas()

    def to_dict(self) -> dict[str, Any]:
        """
        Get table data as a dictionary

        Returns:
            dict[str, Any]: table data in named dictionary - column names as keys
        """
        if self.data_present is False:
            raise ValueError("Data was not found in shell table")
        
        return self.data.to_dict(as_series=False)

    def to_dicts(self) -> list[dict[str, Any]]:
        """
        Get table data as a list of dictionaries

        Returns:
            list[dict[str, Any]]: table data in list of named dictionaries - rows
        """
        if self.data_present is False:
            raise ValueError("Data was not found in shell table")

        return self.data.to_dicts()

    def get_column_names(self) -> list[str]:
        """
        Get column names

        Returns:
            list[str]: column names in the table
        """
        if self.data_present is False:
            raise ValueError("Data was not found in shell table")

        return self.data.columns

    @overload
    def cell(self, row: int, column: int) -> Any:
        ...

    @overload
    def cell(self, row: int, column: str) -> Any:
        ...

    def cell(self, row: int, column: str | int) -> Any:
        """
        Get cell value

        Args:
            row (int): row index
            column (str | int): column name or index

        Returns:
            Any: cell value
        """
        if self.data_present is False:
            raise ValueError("Data was not found in shell table")

        return self.data.item(row, column)

    def load(self, move_by: int = 20, move_by_table_end: int = 2) -> None:
        """
        Skims through the table to load all data, as SAP only loads visible data

        Args:
            move_by (int): number of rows to move by, default 20
            move_by_table_end (int): number of rows to move by when reaching the end of the table, default 2

        Raises:
            ActionException: error finding table
        """
        if self.data_present is False:
            raise ValueError("Data was not found in shell table")

        row_position = 0

        try:
            shell = self._session_handle.findById(self.table_element)

        except Exception as e:
            raise exceptions.ActionException(
                f"Error finding table {self.table_element}: {e}"
            )

        while True:
            try:
                shell.currentCellRow = row_position
                shell.SelectedRows = row_position

            except com_error:
                """no more rows for this step"""
                break

            row_position += move_by

        row_position -= 20
        while True:
            try:
                shell.currentCellRow = row_position
                shell.SelectedRows = row_position

            except com_error:
                """no more rows for this step"""
                break

            row_position += move_by_table_end

    def press_button(self, button: str) -> None:
        """
        Presses button that is in a shell table

        Args:
            button (str): button name

        Raises:
            ActionException: error pressing shell button

        Example:
            ```
            main_window.press_shell_button("wnd[0]/usr/shellContent/shell", "%OPENDWN")
            ```
        """
        try:
            self._session_handle.findById(self.table_element).pressButton(button)

        except Exception as e:
            raise exceptions.ActionException(f"Error pressing button {button}: {e}")

    def click_current_cell(self) -> None:
        """
        Clicks current cell in a shell table

        Raises:
            ActionException: error clicking current cell in shell table

        Example:
            ```
            main_window.click_current_cell("wnd[0]/usr/shellContent/shell")
            ```
        """
        try:
            self._session_handle.findById(self.table_element).clickCurrentCell()

        except Exception as e:
            raise exceptions.ActionException(f"Error clicking current cell: {e}")

    def select_rows(self, indexes: list[int]) -> None:
        """
        Selects rows (visual) is in a shell table

        Args:
            indexes (list[int]): indexes of rows to select, starting with 0

        Raises:
            ActionException: error selecting shell rows

        Example:
            ```
            main_window.select_shell_rows("wnd[0]/usr/shellContent/shell", [0, 1, 2])
            ```
        """
        if self.data_present is False:
            raise ValueError("Data was not found in shell table")

        try:
            value = ",".join([str(n) for n in indexes])
            self._session_handle.findById(self.table_element).selectedRows = value

        except Exception as e:
            raise exceptions.ActionException(
                f"Error selecting rows with indexes {indexes}: {e}"
            )

    def select_row(self, index: int) -> None:
        """
        Selects row and set it as active in a shell table

        Args:
            indexes (int): indexe of row to select, starting with 0

        Raises:
            ActionException: error selecting shell row

        Example:
            ```
            main_window.select_shell_row("wnd[0]/usr/shellContent/shell", 1)
            ```
        """
        if self.data_present is False:
            raise ValueError("Data was be found in shell table")

        try:
            self._session_handle.findById(self.table_element).currentCellRow = index
            self._session_handle.findById(self.table_element).selectedRows = index

        except Exception as e:
            raise exceptions.ActionException(
                f"Error selecting row with index {index}: {e}"
            )
    
    def select_all(self) -> None:
        """
        Selects all rows in a shell table

        Raises:
            ActionException: error selecting all shell rows

        Example:
            ```
            main_window.select_all_shell_rows("wnd[0]/usr/shellContent/shell")
            ```
        """
        if self.data_present is False:
            raise ValueError("Data was not found in shell table")

        try:
            self._session_handle.findById(self.table_element).selectAll()

        except Exception as e:
            raise exceptions.ActionException(f"Error selecting all rows: {e}")
    
    def clear_selection(self) -> None:
        """
        Clears selection in a shell table

        Raises:
            ActionException: error clearing shell selection

        Example:
            ```
            main_window.clear_shell_selection("wnd[0]/usr/shellContent/shell")
            ```
        """
        if self.data_present is False:
            raise ValueError("Data was not found in shell table")

        try:
            self._session_handle.findById(self.table_element).clearSelection()

        except Exception as e:
            raise exceptions.ActionException(f"Error clearing selection: {e}")
    
    def change_checkbox(self, checkbox: str, flag: bool) -> None:
        """
        Sets checkbox in a shell table

        Args:
            checkbox (str): checkbox name
            flag (bool): True for checked, False for unchecked

        Raises:
            ActionException: error setting shell checkbox

        Example:
            ```
            main_window.change_shell_checkbox("wnd[0]/usr/cntlALV_CONT/shellcont/shell/rows[1]", "%CHBX", True)
            ```
        """
        try:
            self._session_handle.findById(self.table_element).changeCheckbox(checkbox, "1", flag)

        except Exception as e:
            raise exceptions.ActionException(f"Error setting element {self.table_element}: {e}")
    
    def press_context_menu_item(self, value: str, item_type: Literal["func_code", "text"] = "func_code"):
        """
        Presses menu item in context menu of a shell

        Args:
            value (str): name of function code or text of item based on item type
            item_type (literal): func_code (internal name) or text (label)

        Raises:
            NotImplementedError: bad item_type

        Example:
            ```
            main_window.press_context_menu_item("Excel File...", item_type="text")
            ```
        """
        self._session_handle.findById(self.table_element).contextMenu()

        match item_type:
            case "func_code":
                self._session_handle.findById(self.table_element).selectContextMenuItem(value)
            case "text":
                self._session_handle.findById(self.table_element).selectContextMenuItemByText(value)
            case _:
                raise NotImplementedError

A class representing a shell table

Usually table contains a table of data, but it can also be a non-data shell table, that holds toolbar

Args

session_handle : win32com.client.CDispatch
SAP session handle
element : str
SAP table element
load_table : bool
loads table if True, default True

Raises

ActionException
error reading table data

Methods

def cell(self, row: int, column: str | int) ‑> Any
Expand source code
def cell(self, row: int, column: str | int) -> Any:
    """
    Get cell value

    Args:
        row (int): row index
        column (str | int): column name or index

    Returns:
        Any: cell value
    """
    if self.data_present is False:
        raise ValueError("Data was not found in shell table")

    return self.data.item(row, column)

Get cell value

Args

row : int
row index
column : str | int
column name or index

Returns

Any
cell value
def change_checkbox(self, checkbox: str, flag: bool) ‑> None
Expand source code
def change_checkbox(self, checkbox: str, flag: bool) -> None:
    """
    Sets checkbox in a shell table

    Args:
        checkbox (str): checkbox name
        flag (bool): True for checked, False for unchecked

    Raises:
        ActionException: error setting shell checkbox

    Example:
        ```
        main_window.change_shell_checkbox("wnd[0]/usr/cntlALV_CONT/shellcont/shell/rows[1]", "%CHBX", True)
        ```
    """
    try:
        self._session_handle.findById(self.table_element).changeCheckbox(checkbox, "1", flag)

    except Exception as e:
        raise exceptions.ActionException(f"Error setting element {self.table_element}: {e}")

Sets checkbox in a shell table

Args

checkbox : str
checkbox name
flag : bool
True for checked, False for unchecked

Raises

ActionException
error setting shell checkbox

Example

main_window.change_shell_checkbox("wnd[0]/usr/cntlALV_CONT/shellcont/shell/rows[1]", "%CHBX", True)
def clear_selection(self) ‑> None
Expand source code
def clear_selection(self) -> None:
    """
    Clears selection in a shell table

    Raises:
        ActionException: error clearing shell selection

    Example:
        ```
        main_window.clear_shell_selection("wnd[0]/usr/shellContent/shell")
        ```
    """
    if self.data_present is False:
        raise ValueError("Data was not found in shell table")

    try:
        self._session_handle.findById(self.table_element).clearSelection()

    except Exception as e:
        raise exceptions.ActionException(f"Error clearing selection: {e}")

Clears selection in a shell table

Raises

ActionException
error clearing shell selection

Example

main_window.clear_shell_selection("wnd[0]/usr/shellContent/shell")
def click_current_cell(self) ‑> None
Expand source code
def click_current_cell(self) -> None:
    """
    Clicks current cell in a shell table

    Raises:
        ActionException: error clicking current cell in shell table

    Example:
        ```
        main_window.click_current_cell("wnd[0]/usr/shellContent/shell")
        ```
    """
    try:
        self._session_handle.findById(self.table_element).clickCurrentCell()

    except Exception as e:
        raise exceptions.ActionException(f"Error clicking current cell: {e}")

Clicks current cell in a shell table

Raises

ActionException
error clicking current cell in shell table

Example

main_window.click_current_cell("wnd[0]/usr/shellContent/shell")
def get_column_names(self) ‑> list[str]
Expand source code
def get_column_names(self) -> list[str]:
    """
    Get column names

    Returns:
        list[str]: column names in the table
    """
    if self.data_present is False:
        raise ValueError("Data was not found in shell table")

    return self.data.columns

Get column names

Returns

list[str]
column names in the table
def load(self, move_by: int = 20, move_by_table_end: int = 2) ‑> None
Expand source code
def load(self, move_by: int = 20, move_by_table_end: int = 2) -> None:
    """
    Skims through the table to load all data, as SAP only loads visible data

    Args:
        move_by (int): number of rows to move by, default 20
        move_by_table_end (int): number of rows to move by when reaching the end of the table, default 2

    Raises:
        ActionException: error finding table
    """
    if self.data_present is False:
        raise ValueError("Data was not found in shell table")

    row_position = 0

    try:
        shell = self._session_handle.findById(self.table_element)

    except Exception as e:
        raise exceptions.ActionException(
            f"Error finding table {self.table_element}: {e}"
        )

    while True:
        try:
            shell.currentCellRow = row_position
            shell.SelectedRows = row_position

        except com_error:
            """no more rows for this step"""
            break

        row_position += move_by

    row_position -= 20
    while True:
        try:
            shell.currentCellRow = row_position
            shell.SelectedRows = row_position

        except com_error:
            """no more rows for this step"""
            break

        row_position += move_by_table_end

Skims through the table to load all data, as SAP only loads visible data

Args

move_by : int
number of rows to move by, default 20
move_by_table_end : int
number of rows to move by when reaching the end of the table, default 2

Raises

ActionException
error finding table
def press_button(self, button: str) ‑> None
Expand source code
def press_button(self, button: str) -> None:
    """
    Presses button that is in a shell table

    Args:
        button (str): button name

    Raises:
        ActionException: error pressing shell button

    Example:
        ```
        main_window.press_shell_button("wnd[0]/usr/shellContent/shell", "%OPENDWN")
        ```
    """
    try:
        self._session_handle.findById(self.table_element).pressButton(button)

    except Exception as e:
        raise exceptions.ActionException(f"Error pressing button {button}: {e}")

Presses button that is in a shell table

Args

button : str
button name

Raises

ActionException
error pressing shell button

Example

main_window.press_shell_button("wnd[0]/usr/shellContent/shell", "%OPENDWN")
def press_context_menu_item(self, value: str, item_type: Literal['func_code', 'text'] = 'func_code')
Expand source code
def press_context_menu_item(self, value: str, item_type: Literal["func_code", "text"] = "func_code"):
    """
    Presses menu item in context menu of a shell

    Args:
        value (str): name of function code or text of item based on item type
        item_type (literal): func_code (internal name) or text (label)

    Raises:
        NotImplementedError: bad item_type

    Example:
        ```
        main_window.press_context_menu_item("Excel File...", item_type="text")
        ```
    """
    self._session_handle.findById(self.table_element).contextMenu()

    match item_type:
        case "func_code":
            self._session_handle.findById(self.table_element).selectContextMenuItem(value)
        case "text":
            self._session_handle.findById(self.table_element).selectContextMenuItemByText(value)
        case _:
            raise NotImplementedError

Presses menu item in context menu of a shell

Args

value : str
name of function code or text of item based on item type
item_type : literal
func_code (internal name) or text (label)

Raises

NotImplementedError
bad item_type

Example

main_window.press_context_menu_item("Excel File...", item_type="text")
def select_all(self) ‑> None
Expand source code
def select_all(self) -> None:
    """
    Selects all rows in a shell table

    Raises:
        ActionException: error selecting all shell rows

    Example:
        ```
        main_window.select_all_shell_rows("wnd[0]/usr/shellContent/shell")
        ```
    """
    if self.data_present is False:
        raise ValueError("Data was not found in shell table")

    try:
        self._session_handle.findById(self.table_element).selectAll()

    except Exception as e:
        raise exceptions.ActionException(f"Error selecting all rows: {e}")

Selects all rows in a shell table

Raises

ActionException
error selecting all shell rows

Example

main_window.select_all_shell_rows("wnd[0]/usr/shellContent/shell")
def select_row(self, index: int) ‑> None
Expand source code
def select_row(self, index: int) -> None:
    """
    Selects row and set it as active in a shell table

    Args:
        indexes (int): indexe of row to select, starting with 0

    Raises:
        ActionException: error selecting shell row

    Example:
        ```
        main_window.select_shell_row("wnd[0]/usr/shellContent/shell", 1)
        ```
    """
    if self.data_present is False:
        raise ValueError("Data was be found in shell table")

    try:
        self._session_handle.findById(self.table_element).currentCellRow = index
        self._session_handle.findById(self.table_element).selectedRows = index

    except Exception as e:
        raise exceptions.ActionException(
            f"Error selecting row with index {index}: {e}"
        )

Selects row and set it as active in a shell table

Args

indexes : int
indexe of row to select, starting with 0

Raises

ActionException
error selecting shell row

Example

main_window.select_shell_row("wnd[0]/usr/shellContent/shell", 1)
def select_rows(self, indexes: list[int]) ‑> None
Expand source code
def select_rows(self, indexes: list[int]) -> None:
    """
    Selects rows (visual) is in a shell table

    Args:
        indexes (list[int]): indexes of rows to select, starting with 0

    Raises:
        ActionException: error selecting shell rows

    Example:
        ```
        main_window.select_shell_rows("wnd[0]/usr/shellContent/shell", [0, 1, 2])
        ```
    """
    if self.data_present is False:
        raise ValueError("Data was not found in shell table")

    try:
        value = ",".join([str(n) for n in indexes])
        self._session_handle.findById(self.table_element).selectedRows = value

    except Exception as e:
        raise exceptions.ActionException(
            f"Error selecting rows with indexes {indexes}: {e}"
        )

Selects rows (visual) is in a shell table

Args

indexes : list[int]
indexes of rows to select, starting with 0

Raises

ActionException
error selecting shell rows

Example

main_window.select_shell_rows("wnd[0]/usr/shellContent/shell", [0, 1, 2])
def to_dict(self) ‑> dict[str, typing.Any]
Expand source code
def to_dict(self) -> dict[str, Any]:
    """
    Get table data as a dictionary

    Returns:
        dict[str, Any]: table data in named dictionary - column names as keys
    """
    if self.data_present is False:
        raise ValueError("Data was not found in shell table")
    
    return self.data.to_dict(as_series=False)

Get table data as a dictionary

Returns

dict[str, Any]
table data in named dictionary - column names as keys
def to_dicts(self) ‑> list[dict[str, typing.Any]]
Expand source code
def to_dicts(self) -> list[dict[str, Any]]:
    """
    Get table data as a list of dictionaries

    Returns:
        list[dict[str, Any]]: table data in list of named dictionaries - rows
    """
    if self.data_present is False:
        raise ValueError("Data was not found in shell table")

    return self.data.to_dicts()

Get table data as a list of dictionaries

Returns

list[dict[str, Any]]
table data in list of named dictionaries - rows
def to_pandas_dataframe(self) ‑> pandas.core.frame.DataFrame
Expand source code
def to_pandas_dataframe(self) -> pandas.DataFrame:
    """
    Get table data as a pandas DataFrame

    Returns:
        pandas.DataFrame: table data
    """
    if self.data_present is False:
        raise ValueError("Data was not found in shell table")

    return self.data.to_pandas()

Get table data as a pandas DataFrame

Returns

pandas.DataFrame
table data
def to_polars_dataframe(self) ‑> polars.dataframe.frame.DataFrame
Expand source code
def to_polars_dataframe(self) -> pl.DataFrame:
    """
    Get table data as a polars DataFrame

    Returns:
        polars.DataFrame: table data
    """
    if self.data_present is False:
        raise ValueError("Data was not found in shell table")

    return self.data

Get table data as a polars DataFrame

Returns

polars.DataFrame
table data
class ShellTableRowIterator (data: polars.dataframe.frame.DataFrame)
Expand source code
class ShellTableRowIterator:
    """
    Iterator for shell table rows
    """
    def __init__(self, data: pl.DataFrame) -> None:
        self.data = data
        self.index = 0

    def __iter__(self) -> Self:
        return self

    def __next__(self) -> dict[str, Any]:
        if self.index >= self.data.shape[0]:
            raise StopIteration

        value = self.data.row(self.index, named=True)
        self.index += 1

        return value

Iterator for shell table rows