class_docstr: Datasource-related views
class_docstr_tok: ["Datasource-related","views"]
code: def save(self) -> FlaskResponse:
      data = request.form.get("data")
      if not isinstance(data, str):
          return json_error_response(
              _("Request missing data field."), status=500
          )
  
      datasource_dict = json.loads(data)
      normalize_columns = datasource_dict.get(
          "normalize_columns", False
      )
      always_filter_main_dttm = datasource_dict.get(
          "always_filter_main_dttm", False
      )
      datasource_dict["normalize_columns"] = normalize_columns
      datasource_dict[
          "always_filter_main_dttm"
      ] = always_filter_main_dttm
      datasource_id = datasource_dict.get("id")
      datasource_type = datasource_dict.get("type")
      database_id = datasource_dict["database"].get("id")
      orm_datasource = DatasourceDAO.get_datasource(
          db.session, DatasourceType(datasource_type), datasource_id
      )
      orm_datasource.database_id = database_id
  
      if (
          "owners" in datasource_dict
          and orm_datasource.owner_class is not None
      ):
          # Check ownership
          try:
              security_manager.raise_for_ownership(orm_datasource)
          except SupersetSecurityException as ex:
              raise DatasetForbiddenError() from ex
  
      datasource_dict["owners"] = populate_owners(
          datasource_dict["owners"], default_to_user=False
      )
  
      duplicates = [
          name
          for name, count in Counter(
              [col["column_name"] for col in datasource_dict["columns"]]
          ).items()
          if count > 1
      ]
      if duplicates:
          return json_error_response(
              _(
                  "Duplicate column name(s): %(columns)s",
                  columns=",".join(duplicates),
              ),
              status=409,
          )
      orm_datasource.update_from_object(datasource_dict)
      data = orm_datasource.data
      db.session.commit()
  
      return self.json_response(sanitize_datasource_data(data))
  
imports: import json
  from collections import Counter
  from typing import Any
  from flask import redirect, request
  from flask_appbuilder import expose, permission_name
  from flask_appbuilder.api import rison
  from flask_appbuilder.security.decorators import has_access, has_access_api
  from flask_babel import _
  from marshmallow import ValidationError
  from sqlalchemy.exc import NoResultFound, NoSuchTableError
  from superset import db, event_logger, security_manager
  from superset.commands.dataset.exceptions import DatasetForbiddenError, DatasetNotFoundError
  from superset.commands.utils import populate_owners
  from superset.connectors.sqla.models import SqlaTable
  from superset.connectors.sqla.utils import get_physical_table_metadata
  from superset.daos.datasource import DatasourceDAO
  from superset.exceptions import SupersetException, SupersetSecurityException
  from superset.models.core import Database
  from superset.superset_typing import FlaskResponse
  from superset.utils.core import DatasourceType
  from superset.views.base import api, BaseSupersetView, deprecated, handle_api_exception, json_error_response
  from superset.views.datasource.schemas import ExternalMetadataParams, ExternalMetadataSchema, get_external_metadata_schema, SamplesPayloadSchema, SamplesRequestSchema
  from superset.views.datasource.utils import get_samples
  from superset.views.utils import sanitize_datasource_data
label_desc: Use image node_id 1 for calling the Datasource obj's underlying member method code with example usage: obj.save() and returns: self, json_error_response, json_error_response