-
-
Notifications
You must be signed in to change notification settings - Fork 362
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3665 from mathesar-foundation/init_db_rpc
Initial `database_setup` RPC functions
- Loading branch information
Showing
13 changed files
with
438 additions
and
79 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,29 +1,27 @@ | ||
"""This module contains functions to load the Library Management dataset.""" | ||
|
||
from sqlalchemy import text | ||
from psycopg import sql | ||
from mathesar.examples.base import LIBRARY_MANAGEMENT, LIBRARY_ONE, LIBRARY_TWO | ||
|
||
|
||
def load_library_dataset(engine, safe_mode=False): | ||
def load_library_dataset(conn): | ||
""" | ||
Load the library dataset into a "Library Management" schema. | ||
Args: | ||
engine: an SQLAlchemy engine defining the connection to load data into. | ||
safe_mode: When True, we will throw an error if the "Library Management" | ||
schema already exists instead of dropping it. | ||
conn: a psycopg (3) connection for loading the data. | ||
Uses given engine to define database to load into. | ||
Destructive, and will knock out any previous "Library Management" | ||
schema in the given database, unless safe_mode=True. | ||
Uses given connection to define database to load into. Raises an | ||
Exception if the "Library Management" schema already exists. | ||
""" | ||
drop_schema_query = text(f"""DROP SCHEMA IF EXISTS "{LIBRARY_MANAGEMENT}" CASCADE;""") | ||
create_schema_query = text(f"""CREATE SCHEMA "{LIBRARY_MANAGEMENT}";""") | ||
set_search_path = text(f"""SET search_path="{LIBRARY_MANAGEMENT}";""") | ||
with engine.begin() as conn, open(LIBRARY_ONE) as f1, open(LIBRARY_TWO) as f2: | ||
if safe_mode is False: | ||
conn.execute(drop_schema_query) | ||
create_schema_query = sql.SQL("CREATE SCHEMA {}").format( | ||
sql.Identifier(LIBRARY_MANAGEMENT) | ||
) | ||
set_search_path = sql.SQL("SET search_path={}").format( | ||
sql.Identifier(LIBRARY_MANAGEMENT) | ||
) | ||
with open(LIBRARY_ONE) as f1, open(LIBRARY_TWO) as f2: | ||
conn.execute(create_schema_query) | ||
conn.execute(set_search_path) | ||
conn.execute(text(f1.read())) | ||
conn.execute(text(f2.read())) | ||
conn.execute(f1.read()) | ||
conn.execute(f2.read()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,31 @@ | ||
"""This module contains functions to load the Movie Collection dataset.""" | ||
import os | ||
from sqlalchemy import text | ||
from psycopg import sql | ||
|
||
from mathesar.examples.base import ( | ||
MOVIE_COLLECTION, MOVIES_SQL_TABLES, MOVIES_CSV, MOVIES_SQL_FKS | ||
) | ||
|
||
|
||
def load_movies_dataset(engine, safe_mode=False): | ||
def load_movies_dataset(conn): | ||
""" | ||
Load the movie example data set. | ||
Args: | ||
engine: an SQLAlchemy engine defining the connection to load data into. | ||
safe_mode: When True, we will throw an error if the "Movie Collection" | ||
schema already exists instead of dropping it. | ||
conn: a psycopg (3) connection for loading the data. | ||
Uses given connection to define database to load into. Raises an | ||
Exception if the "Movie Collection" schema already exists. | ||
""" | ||
drop_schema_query = text(f"""DROP SCHEMA IF EXISTS "{MOVIE_COLLECTION}" CASCADE;""") | ||
with engine.begin() as conn, open(MOVIES_SQL_TABLES) as f, open(MOVIES_SQL_FKS) as f2: | ||
if safe_mode is False: | ||
conn.execute(drop_schema_query) | ||
conn.execute(text(f.read())) | ||
with open(MOVIES_SQL_TABLES) as f, open(MOVIES_SQL_FKS) as f2: | ||
conn.execute(f.read()) | ||
for file in os.scandir(MOVIES_CSV): | ||
table_name = file.name.split('.csv')[0] | ||
with open(file, 'r') as csv_file: | ||
conn.connection.cursor().copy_expert( | ||
f"""COPY "{MOVIE_COLLECTION}"."{table_name}" FROM STDIN DELIMITER ',' CSV HEADER""", | ||
csv_file | ||
) | ||
conn.execute(text(f2.read())) | ||
copy_sql = sql.SQL( | ||
"COPY {}.{} FROM STDIN DELIMITER ',' CSV HEADER" | ||
).format( | ||
sql.Identifier(MOVIE_COLLECTION), sql.Identifier(table_name) | ||
) | ||
with open(file, 'r') as csv, conn.cursor().copy(copy_sql) as copy: | ||
copy.write(csv.read()) | ||
conn.execute(f2.read()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
""" | ||
RPC functions for setting up database connections. | ||
""" | ||
from typing import TypedDict | ||
|
||
from modernrpc.core import rpc_method, REQUEST_KEY | ||
from modernrpc.auth.basic import http_basic_auth_superuser_required | ||
|
||
from mathesar.utils import permissions | ||
from mathesar.rpc.exceptions.handlers import handle_rpc_exceptions | ||
|
||
|
||
class DatabaseConnectionResult(TypedDict): | ||
""" | ||
Info about the objects resulting from calling the setup functions. | ||
These functions will get or create an instance of the Server, | ||
Database, and Role models, as well as a UserDatabaseRoleMap entry. | ||
Attributes: | ||
server_id: The Django ID of the Server model instance. | ||
database_id: The Django ID of the Database model instance. | ||
role_id: The Django ID of the Role model instance. | ||
""" | ||
server_id: int | ||
database_id: int | ||
role_id: int | ||
|
||
@classmethod | ||
def from_model(cls, model): | ||
return cls( | ||
server_id=model.server.id, | ||
database_id=model.database.id, | ||
role_id=model.role.id, | ||
) | ||
|
||
|
||
@rpc_method(name='database_setup.create_new') | ||
@http_basic_auth_superuser_required | ||
@handle_rpc_exceptions | ||
def create_new( | ||
*, | ||
database: str, | ||
sample_data: list[str] = [], | ||
**kwargs | ||
) -> DatabaseConnectionResult: | ||
""" | ||
Set up a new database on the internal server. | ||
The calling user will get access to that database using the default | ||
role stored in Django settings. | ||
Args: | ||
database: The name of the new database. | ||
sample_data: A list of strings requesting that some example data | ||
sets be installed on the underlying database. Valid list | ||
members are 'library_management' and 'movie_collection'. | ||
""" | ||
user = kwargs.get(REQUEST_KEY).user | ||
result = permissions.set_up_new_database_for_user_on_internal_server( | ||
database, user, sample_data=sample_data | ||
) | ||
return DatabaseConnectionResult.from_model(result) | ||
|
||
|
||
@rpc_method(name='database_setup.connect_existing') | ||
@http_basic_auth_superuser_required | ||
@handle_rpc_exceptions | ||
def connect_existing( | ||
*, | ||
host: str, | ||
port: int, | ||
database: str, | ||
role: str, | ||
password: str, | ||
sample_data: list[str] = [], | ||
**kwargs | ||
) -> DatabaseConnectionResult: | ||
""" | ||
Connect Mathesar to an existing database on a server. | ||
The calling user will get access to that database using the | ||
credentials passed to this function. | ||
Args: | ||
host: The host of the database server. | ||
port: The port of the database server. | ||
database: The name of the database on the server. | ||
role: The role on the server to use for the connection. | ||
password: A password valid for the role. | ||
sample_data: A list of strings requesting that some example data | ||
sets be installed on the underlying database. Valid list | ||
members are 'library_management' and 'movie_collection'. | ||
""" | ||
user = kwargs.get(REQUEST_KEY).user | ||
result = permissions.set_up_preexisting_database_for_user( | ||
host, port, database, role, password, user, sample_data=sample_data | ||
) | ||
return DatabaseConnectionResult.from_model(result) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .base import * # noqa |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
from typing import TypedDict | ||
|
||
from modernrpc.core import rpc_method | ||
from modernrpc.auth.basic import http_basic_auth_login_required | ||
|
||
from mathesar.models.base import Database | ||
from mathesar.rpc.exceptions.handlers import handle_rpc_exceptions | ||
|
||
|
||
class DatabaseInfo(TypedDict): | ||
""" | ||
Information about a database. | ||
Attributes: | ||
id: the Django ID of the database model instance. | ||
name: The name of the database on the server. | ||
server_id: the Django ID of the server model instance for the database. | ||
""" | ||
id: int | ||
name: str | ||
server_id: int | ||
|
||
@classmethod | ||
def from_model(cls, model): | ||
return cls( | ||
id=model.id, | ||
name=model.name, | ||
server_id=model.server.id | ||
) | ||
|
||
|
||
@rpc_method(name="databases.list") | ||
@http_basic_auth_login_required | ||
@handle_rpc_exceptions | ||
def list_(*, server_id: int = None, **kwargs) -> list[DatabaseInfo]: | ||
""" | ||
List information about databases for a server. Exposed as `list`. | ||
If called with no `server_id`, all databases for all servers are listed. | ||
Args: | ||
server_id: The Django id of the server containing the databases. | ||
Returns: | ||
A list of database details. | ||
""" | ||
if server_id is not None: | ||
database_qs = Database.objects.filter(server__id=server_id) | ||
else: | ||
database_qs = Database.objects.all() | ||
|
||
return [DatabaseInfo.from_model(db_model) for db_model in database_qs] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.