From 5bed3f178453eb36c4b27c61cd0bcdc7d9985ba5 Mon Sep 17 00:00:00 2001 From: Anish Umale Date: Tue, 6 Aug 2024 19:15:23 +0530 Subject: [PATCH] implement explorations add and replace endpoint --- docs/docs/api/rpc.md | 2 + mathesar/rpc/explorations.py | 79 ++++++++++++++++------------ mathesar/tests/rpc/test_endpoints.py | 10 ++++ mathesar/utils/explorations.py | 53 ++++++++++++------- 4 files changed, 91 insertions(+), 53 deletions(-) diff --git a/docs/docs/api/rpc.md b/docs/docs/api/rpc.md index 70c57156d4..15e6b22530 100644 --- a/docs/docs/api/rpc.md +++ b/docs/docs/api/rpc.md @@ -200,7 +200,9 @@ To use an RPC function: members: - list_ - get + - add - delete + - replace - run - run_saved - ExplorationInfo diff --git a/mathesar/rpc/explorations.py b/mathesar/rpc/explorations.py index 63144d8ded..391cf0266e 100644 --- a/mathesar/rpc/explorations.py +++ b/mathesar/rpc/explorations.py @@ -13,7 +13,9 @@ get_exploration, delete_exploration, run_exploration, - run_saved_exploration + run_saved_exploration, + replace_exploration, + create_exploration ) @@ -28,7 +30,7 @@ class ExplorationInfo(TypedDict): base_table_oid: The OID of the base table of the exploration on the database. initial_columns: A list describing the columns to be included in the exploration. transformations: A list describing the transformations to be made on the included columns. - display_options: A list desrcibing metadata for the columns in the explorations. + display_options: A list describing metadata for the columns in the explorations. display_names: A map between the actual column names on the database and the alias to be displayed(if any). description: The description of the exploration. """ @@ -62,44 +64,23 @@ class ExplorationDef(TypedDict): Definition about a runnable exploration. Attributes: + database_id: The Django id of the database containing the exploration. + name: The name of the exploration. base_table_oid: The OID of the base table of the exploration on the database. initial_columns: A list describing the columns to be included in the exploration. - display_names: A map between the actual column names on the database and the alias to be displayed. transformations: A list describing the transformations to be made on the included columns. - limit: Specifies the number of rows to return.(default 100) - offset: Specifies the number of rows to skip.(default 0) - filter: A dict describing filters to be applied to an exploration. - e.g. Here is a dict describing getting records from exploration where "col1" = NULL and "col2" = "abc" - ``` - {"and": [ - {"null": [ - {"column_name": ["col1"]}, - ]}, - {"equal": [ - {"to_lowercase": [ - {"column_name": ["col2"]}, - ]}, - {"literal": ["abc"]}, - ]}, - ]} - ``` - Refer to db/functions/base.py for all the possible filters. - order_by: A list of dicts, where each dict has a `field` and `direction` field. - Here the value for `field` should be column name and `direction` should be either `asc` or `desc`. - search: A list of dicts, where each dict has a `column` and `literal` field. - Here the value for `column` should be a column name and `literal` should be a string to be searched in the aforementioned column. - duplicate_only: A list of column names for which you want duplicate records. + display_options: A list describing metadata for the columns in the explorations. + display_names: A map between the actual column names on the database and the alias to be displayed(if any). + description: The description of the exploration. """ + database_id: int + name: str base_table_oid: int initial_columns: list - display_names: dict transformations: Optional[list] - limit: Optional[int] - offset: Optional[int] - filter: Optional[dict] - order_by: Optional[list[dict]] - search: Optional[list[dict]] - duplicate_only: Optional[list] + display_options: Optional[list] + display_names: Optional[dict] + description: Optional[str] class ExplorationResult(TypedDict): @@ -195,7 +176,7 @@ def delete(*, exploration_id: int, **kwargs) -> None: @rpc_method(name="explorations.run") @http_basic_auth_login_required @handle_rpc_exceptions -def run(*, exploration_def: ExplorationDef, database_id: int, **kwargs) -> ExplorationResult: +def run(*, exploration_def: ExplorationDef, database_id: int, limit: int = 100, offset: int = 0, **kwargs) -> ExplorationResult: """ Run an exploration. @@ -205,7 +186,7 @@ def run(*, exploration_def: ExplorationDef, database_id: int, **kwargs) -> Explo """ user = kwargs.get(REQUEST_KEY).user with connect(database_id, user) as conn: - exploration_result = run_exploration(exploration_def, database_id, conn) + exploration_result = run_exploration(exploration_def, database_id, conn, limit, offset) return ExplorationResult.from_dict(exploration_result) @@ -226,3 +207,31 @@ def run_saved(*, exploration_id: int, database_id: int, limit: int = 100, offset with connect(database_id, user) as conn: exploration_result = run_saved_exploration(exploration_id, limit, offset, database_id, conn) return ExplorationResult.from_dict(exploration_result) + + +@rpc_method(name='explorations.replace') +@http_basic_auth_login_required +@handle_rpc_exceptions +def replace(*, new_exploration: ExplorationInfo) -> ExplorationInfo: + """ + Replace a saved exploration. + + Args: + new_exploration: A dict describing the exploration to replace, including the updated fields. + """ + replaced_exp_model = replace_exploration(new_exploration) + return ExplorationInfo.from_model(replaced_exp_model) + + +@rpc_method(name='explorations.add') +@http_basic_auth_login_required +@handle_rpc_exceptions +def add(*, exploration_def: ExplorationDef) -> ExplorationInfo: + """ + Add a new exploration. + + Args: + exploration_def: A dict describing the exploration to create. + """ + exp_model = create_exploration(exploration_def) + return ExplorationInfo.from_model(exp_model) diff --git a/mathesar/tests/rpc/test_endpoints.py b/mathesar/tests/rpc/test_endpoints.py index 4c1c00acb7..a1bc514a19 100644 --- a/mathesar/tests/rpc/test_endpoints.py +++ b/mathesar/tests/rpc/test_endpoints.py @@ -164,11 +164,21 @@ "explorations.get", [user_is_authenticated] ), + ( + explorations.add, + "explorations.add", + [user_is_authenticated] + ), ( explorations.delete, "explorations.delete", [user_is_authenticated] ), + ( + explorations.replace, + "explorations.replace", + [user_is_authenticated] + ), ( explorations.run, "explorations.run", diff --git a/mathesar/utils/explorations.py b/mathesar/utils/explorations.py index 31fa36cbc8..c726829a08 100644 --- a/mathesar/utils/explorations.py +++ b/mathesar/utils/explorations.py @@ -3,7 +3,7 @@ from db.queries.base import DBQuery, InitialColumn, JoinParameter from db.tables.operations.select import get_table from mathesar.api.utils import process_annotated_records -from mathesar.models.base import Explorations, ColumnMetaData +from mathesar.models.base import Explorations, ColumnMetaData, Database from mathesar.rpc.columns.metadata import ColumnMetaDataRecord from mathesar.state import get_cached_metadata @@ -20,7 +20,34 @@ def delete_exploration(exploration_id): Explorations.objects.get(id=exploration_id).delete() -def run_exploration(exploration_def, database_id, conn): +def replace_exploration(new_exploration): + Explorations.objects.filter(id=new_exploration["id"]).update( + database=Database.objects.get(id=new_exploration["database_id"]), + name=new_exploration["name"], + base_table_oid=new_exploration["base_table_oid"], + initial_columns=new_exploration["initial_columns"], + transformations=new_exploration.get("transformations"), + display_options=new_exploration.get("display_options"), + display_names=new_exploration.get("display_names", {}), + description=new_exploration.get("description") + ) + return get_exploration(new_exploration["id"]) + + +def create_exploration(exploration_def): + return Explorations.objects.create( + database=Database.objects.get(id=exploration_def["database_id"]), + name=exploration_def["name"], + base_table_oid=exploration_def["base_table_oid"], + initial_columns=exploration_def["initial_columns"], + transformations=exploration_def.get("transformations"), + display_options=exploration_def.get("display_options"), + display_names=exploration_def.get("display_names", {}), + description=exploration_def.get("description") + ) + + +def run_exploration(exploration_def, database_id, conn, limit=100, offset=0): engine = create_future_engine_with_custom_types( conn.info.user, conn.info.password, @@ -60,12 +87,8 @@ def run_exploration(exploration_def, database_id, conn): metadata=metadata ) records = db_query.get_records( - limit=exploration_def.get('limit', 100), - offset=exploration_def.get('offset', 0), - filter=exploration_def.get('filter', None), - order_by=exploration_def.get('order_by', []), - search=exploration_def.get('search', []), - duplicate_only=exploration_def.get('duplicate_only', None) + limit=limit, + offset=offset ) processed_records = process_annotated_records(records)[0] column_metadata = _get_exploration_column_metadata( @@ -89,12 +112,8 @@ def run_exploration(exploration_def, database_id, conn): }, "output_columns": tuple(sa_col.name for sa_col in db_query.sa_output_columns), "column_metadata": column_metadata, - "limit": exploration_def.get('limit', 100), - "offset": exploration_def.get('offset', 0), - "filter": exploration_def.get('filter', None), - "order_by": exploration_def.get('order_by', []), - "search": exploration_def.get('search', []), - "duplicate_only": exploration_def.get('duplicate_only', None) + "limit": limit, + "offset": offset } @@ -104,11 +123,9 @@ def run_saved_exploration(exploration_id, limit, offset, database_id, conn): "base_table_oid": exp_model["base_table_oid"], "initial_columns": exp_model["initial_columns"], "display_names": exp_model["display_names"], - "transformations": exp_model["transformations"], - "limit": limit, - "offset": offset + "transformations": exp_model["transformations"] } - return run_exploration(exploration_def, database_id, conn) + return run_exploration(exploration_def, database_id, conn, limit, offset) def _get_exploration_column_metadata(