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

Implement explorations add & replace endpoint #3731

Merged
merged 2 commits into from
Aug 7, 2024
Merged
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
2 changes: 2 additions & 0 deletions docs/docs/api/rpc.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,9 @@ To use an RPC function:
members:
- list_
- get
- add
- delete
- replace
- run
- run_saved
- ExplorationInfo
Expand Down
79 changes: 44 additions & 35 deletions mathesar/rpc/explorations.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
get_exploration,
delete_exploration,
run_exploration,
run_saved_exploration
run_saved_exploration,
replace_exploration,
create_exploration
)


Expand All @@ -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.
"""
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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.
Expand All @@ -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)


Expand All @@ -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)
10 changes: 10 additions & 0 deletions mathesar/tests/rpc/test_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
53 changes: 35 additions & 18 deletions mathesar/utils/explorations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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,
Expand Down Expand Up @@ -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(
Expand All @@ -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
}


Expand All @@ -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(
Expand Down
Loading