-
-
Notifications
You must be signed in to change notification settings - Fork 359
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 tables.metadata
list
& patch
RPC endpoint
#3646
Changes from 7 commits
7c31e76
6adf4e8
42ef077
ba96a91
5b8bcbf
c7a98eb
b6a899e
6d17171
d8e44fb
b8d1383
bb3458f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
# Generated by Django 4.2.11 on 2024-06-28 08:35 | ||
|
||
from django.db import migrations, models | ||
import django.db.models.deletion | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('mathesar', '0008_add_metadata_models'), | ||
] | ||
|
||
operations = [ | ||
migrations.AlterField( | ||
model_name='columnmetadata', | ||
name='attnum', | ||
field=models.SmallIntegerField(), | ||
), | ||
migrations.AlterField( | ||
model_name='columnmetadata', | ||
name='num_max_frac_digits', | ||
field=models.PositiveIntegerField(default=20), | ||
), | ||
migrations.AlterField( | ||
model_name='columnmetadata', | ||
name='num_min_frac_digits', | ||
field=models.PositiveIntegerField(default=0), | ||
), | ||
migrations.AlterField( | ||
model_name='columnmetadata', | ||
name='table_oid', | ||
field=models.PositiveBigIntegerField(), | ||
), | ||
migrations.CreateModel( | ||
name='TableMetaData', | ||
fields=[ | ||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
('created_at', models.DateTimeField(auto_now_add=True)), | ||
('updated_at', models.DateTimeField(auto_now=True)), | ||
('schema_oid', models.PositiveBigIntegerField()), | ||
('table_oid', models.PositiveBigIntegerField()), | ||
('import_verified', models.BooleanField(default=False)), | ||
('column_order', models.JSONField(default=list)), | ||
('preview_customized', models.BooleanField(default=False)), | ||
('preview_template', models.CharField(blank=True, max_length=255)), | ||
('database', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mathesar.database')), | ||
], | ||
), | ||
migrations.AddConstraint( | ||
model_name='tablemetadata', | ||
constraint=models.UniqueConstraint(fields=('database', 'schema_oid', 'table_oid'), name='unique_table_metadata'), | ||
), | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -84,16 +84,16 @@ def connection(self): | |
|
||
class ColumnMetaData(BaseModel): | ||
database = models.ForeignKey('Database', on_delete=models.CASCADE) | ||
table_oid = models.PositiveIntegerField() | ||
attnum = models.PositiveIntegerField() | ||
table_oid = models.PositiveBigIntegerField() | ||
attnum = models.SmallIntegerField() | ||
bool_input = models.CharField( | ||
choices=[("dropdown", "dropdown"), ("checkbox", "checkbox")], | ||
blank=True | ||
) | ||
bool_true = models.CharField(default='True') | ||
bool_false = models.CharField(default='False') | ||
num_min_frac_digits = models.PositiveIntegerField(blank=True) | ||
num_max_frac_digits = models.PositiveIntegerField(blank=True) | ||
num_min_frac_digits = models.PositiveIntegerField(default=0) | ||
num_max_frac_digits = models.PositiveIntegerField(default=20) | ||
num_show_as_perc = models.BooleanField(default=False) | ||
mon_currency_symbol = models.CharField(default="$") | ||
mon_currency_location = models.CharField( | ||
|
@@ -121,3 +121,21 @@ class Meta: | |
name="frac_digits_integrity" | ||
) | ||
] | ||
|
||
|
||
class TableMetaData(BaseModel): | ||
database = models.ForeignKey('Database', on_delete=models.CASCADE) | ||
schema_oid = models.PositiveBigIntegerField() | ||
table_oid = models.PositiveBigIntegerField() | ||
import_verified = models.BooleanField(default=False) | ||
column_order = models.JSONField(default=list) | ||
preview_customized = models.BooleanField(default=False) | ||
preview_template = models.CharField(max_length=255, blank=True) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
class Meta: | ||
constraints = [ | ||
models.UniqueConstraint( | ||
fields=["database", "schema_oid", "table_oid"], | ||
name="unique_table_metadata" | ||
) | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .base import * # noqa |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
""" | ||
Classes and functions exposed to the RPC endpoint for managing table metadata. | ||
""" | ||
from typing import Optional, TypedDict | ||
|
||
from modernrpc.core import rpc_method | ||
from modernrpc.auth.basic import http_basic_auth_login_required | ||
|
||
from mathesar.rpc.exceptions.handlers import handle_rpc_exceptions | ||
from mathesar.utils.tables import get_tables_meta_data, patch_table_meta_data | ||
|
||
|
||
class TableMetaData(TypedDict): | ||
""" | ||
Metadata for a table in a database. | ||
|
||
Only the `database`, `schema_oid`, and `table_oid` keys are required. | ||
|
||
Attributes: | ||
id: The Django id of the TableMetaData object. | ||
database_id: The Django id of the database containing the table. | ||
schema_oid: The OID of the schema containing the table. | ||
table_oid: The OID of the table in the database. | ||
import_verified: Specifies whether a file has been successfully imported into a table. | ||
column_order: The order in which columns of a table are displayed. | ||
preview_customized: Specifies whether the preview has been customized. | ||
preview_template: Preview template for a referent column. | ||
""" | ||
id: int | ||
database_id: int | ||
schema_oid: int | ||
table_oid: int | ||
import_verified: Optional[bool] | ||
column_order: Optional[list[int]] | ||
preview_customized: Optional[bool] | ||
preview_template: Optional[str] | ||
|
||
@classmethod | ||
def from_model(cls, model): | ||
return cls( | ||
id=model.id, | ||
database_id=model.database.id, | ||
schema_oid=model.schema_oid, | ||
table_oid=model.table_oid, | ||
import_verified=model.import_verified, | ||
column_order=model.column_order, | ||
preview_customized=model.preview_customized, | ||
preview_template=model.preview_template, | ||
) | ||
|
||
|
||
class SettableTableMetaData(TypedDict): | ||
""" | ||
Settable metadata fields for a table in a database. | ||
|
||
Attributes: | ||
import_verified: Specifies whether a file has been successfully imported into a table. | ||
column_order: The order in which columns of a table are displayed. | ||
preview_customized: Specifies whether the preview has been customized. | ||
preview_template: Preview template for a referent column. | ||
""" | ||
import_verified: Optional[bool] | ||
column_order: Optional[list[int]] | ||
preview_customized: Optional[bool] | ||
preview_template: Optional[str] | ||
|
||
|
||
@rpc_method(name="tables.metadata.list") | ||
@http_basic_auth_login_required | ||
@handle_rpc_exceptions | ||
def list_(*, schema_oid: int, database_id: int, **kwargs) -> list[TableMetaData]: | ||
""" | ||
List metadata associated with tables for a schema. | ||
|
||
Args: | ||
schema_oid: Identity of the schema in the user's database. | ||
database_id: The Django id of the database containing the table. | ||
|
||
Returns: | ||
Metadata object for a given table oid. | ||
""" | ||
table_meta_data = get_tables_meta_data(schema_oid, database_id) | ||
return [ | ||
TableMetaData.from_model(model) for model in table_meta_data | ||
] | ||
|
||
|
||
@rpc_method(name="tables.metadata.patch") | ||
@http_basic_auth_login_required | ||
@handle_rpc_exceptions | ||
def patch( | ||
*, metadata_id: int, metadata_dict: SettableTableMetaData, **kwargs | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would expect to pass the table OID here, not the metadata id. I don't mind the fact that this model ends up with a Django id, because presumably that's just the way that Django works. But I think our metadata APIs would be more straightforward for consumers if they didn't need to worry about the Django ids at all. Sticking solely to OIDs eliminates the need for an API consumer to maintain a mapping between table OIDs and metadata IDs. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm fine with that. However, if we go ahead with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Agreed |
||
) -> TableMetaData: | ||
""" | ||
Alter metadata settings associated with a table for a schema. | ||
|
||
Args: | ||
metadata_id: Identity of the table metadata in the user's database. | ||
metadata_dict: The dict describing desired table metadata alterations. | ||
|
||
Returns: | ||
Altered metadata object. | ||
""" | ||
table_meta_data = patch_table_meta_data(metadata_id, metadata_dict) | ||
return TableMetaData.from_model(table_meta_data) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
""" | ||
This file tests the table metadata RPC functions. | ||
|
||
Fixtures: | ||
monkeypatch(pytest): Lets you monkeypatch an object for testing. | ||
""" | ||
from mathesar.models.base import TableMetaData, Database, Server | ||
from mathesar.rpc.tables import metadata | ||
|
||
|
||
def test_tables_meta_data_list(monkeypatch): | ||
database_id = 2 | ||
schema_oid = 123456 | ||
|
||
def mock_get_tables_meta_data(_schema_oid, _database_id): | ||
server_model = Server(id=2, host='example.com', port=5432) | ||
db_model = Database(id=_database_id, name='mymathesardb', server=server_model) | ||
return [ | ||
TableMetaData( | ||
id=1, database=db_model, schema_oid=_schema_oid, table_oid=1234, | ||
import_verified=True, column_order=[8, 9, 10], preview_customized=False, | ||
preview_template="{5555}" | ||
), | ||
TableMetaData( | ||
id=2, database=db_model, schema_oid=_schema_oid, table_oid=4567, | ||
import_verified=False, column_order=[], preview_customized=True, | ||
preview_template="{5512} {1223}" | ||
) | ||
] | ||
monkeypatch.setattr(metadata, "get_tables_meta_data", mock_get_tables_meta_data) | ||
|
||
expect_metadata_list = [ | ||
metadata.TableMetaData( | ||
id=1, database_id=database_id, schema_oid=schema_oid, table_oid=1234, | ||
import_verified=True, column_order=[8, 9, 10], preview_customized=False, | ||
preview_template="{5555}" | ||
), | ||
metadata.TableMetaData( | ||
id=2, database_id=database_id, schema_oid=schema_oid, table_oid=4567, | ||
import_verified=False, column_order=[], preview_customized=True, | ||
preview_template="{5512} {1223}" | ||
) | ||
] | ||
actual_metadata_list = metadata.list_(schema_oid=schema_oid, database_id=database_id) | ||
assert actual_metadata_list == expect_metadata_list | ||
|
||
|
||
def test_tables_meta_data_patch(monkeypatch): | ||
database_id = 2 | ||
schema_oid = 123456 | ||
metadata_dict = {'import_verified': True, 'column_order': [1, 4, 12]} | ||
|
||
def mock_patch_tables_meta_data(metadata_id, metadata_dict): | ||
server_model = Server(id=2, host='example.com', port=5432) | ||
db_model = Database(id=database_id, name='mymathesardb', server=server_model) | ||
return TableMetaData( | ||
id=1, database=db_model, schema_oid=schema_oid, table_oid=1234, | ||
import_verified=True, column_order=[1, 4, 12], preview_customized=False, | ||
preview_template="{5555}" | ||
) | ||
monkeypatch.setattr(metadata, "patch_table_meta_data", mock_patch_tables_meta_data) | ||
|
||
expect_metadata_object = metadata.TableMetaData( | ||
id=1, database_id=database_id, schema_oid=schema_oid, table_oid=1234, | ||
import_verified=True, column_order=[1, 4, 12], preview_customized=False, | ||
preview_template="{5555}" | ||
) | ||
actual_metadata_object = metadata.patch(metadata_id=1, metadata_dict=metadata_dict) | ||
assert actual_metadata_object == expect_metadata_object |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would not expect a
schema_oid
field here. I was expecting that we'd follow the patterns we decided on for explorations wherein we'd return all the metadata for the whole database and let the front end filter it out by schema as needed.