dpgaspar commented on a change in pull request #9034: [table] feat: REST API
URL: 
https://github.com/apache/incubator-superset/pull/9034#discussion_r372826782
 
 

 ##########
 File path: superset/connectors/sqla/api.py
 ##########
 @@ -0,0 +1,177 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+import logging
+from typing import Dict, List
+
+from flask import current_app, g
+from flask_appbuilder.models.sqla.interface import SQLAInterface
+from flask_babel import lazy_gettext as _
+from marshmallow import fields, post_load, validates_schema, ValidationError
+from marshmallow.validate import Length
+from sqlalchemy.exc import SQLAlchemyError
+
+from superset.connectors.sqla.models import SqlaTable
+from superset.constants import RouteMethod
+from superset.models.core import Database
+from superset.views.base import DatasourceFilter, 
get_datasource_exist_error_msg
+from superset.views.base_api import BaseOwnedModelRestApi
+from superset.views.base_schemas import BaseOwnedSchema, validate_owner
+
+logger = logging.getLogger(__name__)
+
+
+def validate_database(value):
+    item = (
+        current_app.appbuilder.get_session.query(Database)
+        .filter_by(id=value)
+        .one_or_none()
+    )
+    if not item:
+        g.tmp_database = None
+        raise ValidationError(_("Database does not exist"))
+    # Database exists save it on g to save further db round trips
+    g.tmp_database = item
+
+
+def validate_table_exists(data: Dict):
+    if "table_name" not in data:
+        return
+    table_name: str = data["table_name"]
+    try:
+        if g.tmp_database:
+            g.tmp_database.get_table(table_name, schema=data.get("schema", ""))
+    except SQLAlchemyError as e:
+        logger.exception(f"Got an error {e} validating table: {table_name}")
+        raise ValidationError(
+            _(
+                f"Table [{table_name}] could not be found, "
+                "please double check your "
+                "database connection, schema, and "
+                f"table name, error: {e}"
+            )
+        )
+
+
+def validate_table_uniqueness(data: Dict):
+    if not ("database" in data and "table_name" in data):
+        return
+    database_name: str = data["database"]
+    table_name: str = data["table_name"]
+
+    with current_app.appbuilder.get_session.no_autoflush:
+        table_query = 
current_app.appbuilder.get_session.query(SqlaTable).filter(
+            SqlaTable.table_name == table_name, SqlaTable.database_id == 
database_name
+        )
+        if 
current_app.appbuilder.get_session.query(table_query.exists()).scalar():
+            raise ValidationError(get_datasource_exist_error_msg(table_name))
+
+
+class TablePostSchema(BaseOwnedSchema):
+    __class_model__ = SqlaTable
+
+    database = fields.Integer(validate=validate_database)
+    schema = fields.String()
+    table_name = fields.String(required=True, validate=Length(1, 250))
+    owners = fields.List(fields.Integer(validate=validate_owner))
+
+    @validates_schema
+    def validate_schema(self, data: Dict):  # pylint: disable=no-self-use
+        validate_table_uniqueness(data)
 
 Review comment:
   The schema validation is debatable, since we are only performing basic data 
validation (does the relation exist?, is the fields unique?). Yet I have mixed 
feelings about it. The other approach would use `pre_`, `post_` hooks. Note 
that using marshmallow validation gives us proper error messages  for free.
   
   It's not possible for the validation to trigger before any auth validation. 
Since this is done at the view decorator level. Yet, it's certainly a good 
point since it's possible further down the road for mashmallow validation to 
perform some kind of data authorization.
   
   I would say this generally a key issue. Marshmallow data validation is 
really useful, but has some constrains, and may easily force unnecessary db 
round trips (notice the use of the flask g on this case, that I'm not a big 
fan). We can rollback this pattern (it already implemented on previous new API 
PR's) and rollback to using `pre_`, `post_` hooks that can call specific 
business logic API outside of the view scope.     

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


With regards,
Apache Git Services

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscr...@superset.apache.org
For additional commands, e-mail: notifications-h...@superset.apache.org

Reply via email to