We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Number scale not persisting
## Reproduce

1. In the front end, edit the DB options for a Number column.
1. Set "Number Type" to "Decimal". Set "Decimal Places" to "3". Click Save.
1. Observe a PATCH request to the columns API which specifies `type_options.scale` to `2`. Good.
1. In the response of this request (and also in the response of the subsequent GET request to the columns endpoint), expect the column to show `type_options` with an object containing a `scale` of `2`.
1. Instead, observe `type_options` to be `null`.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `mathesar/api/serializers/columns.py`
Content:
```
1 from rest_framework import serializers
2 from rest_framework.exceptions import ValidationError
3 from rest_framework.fields import empty
4 from rest_framework.settings import api_settings
5 
6 from mathesar.api.exceptions.mixins import MathesarErrorMessageMixin
7 from mathesar.api.serializers.shared_serializers import (
8     DisplayOptionsMappingSerializer,
9     DISPLAY_OPTIONS_SERIALIZER_MAPPING_KEY,
10 )
11 from mathesar.models import Column
12 
13 
14 class InputValueField(serializers.CharField):
15     """
16     Takes in an arbitrary value. Emulates the record creation endpoint,
17     which takes in arbitrary values (un-validated and un-processed request.data).
18     This field replicates that behavior in a serializer.
19     """
20 
21     def to_internal_value(self, data):
22         return data
23 
24     def to_representation(self, value):
25         return value
26 
27 
28 class TypeOptionSerializer(MathesarErrorMessageMixin, serializers.Serializer):
29     length = serializers.IntegerField(required=False)
30     precision = serializers.IntegerField(required=False)
31     scale = serializers.IntegerField(required=False)
32     fields = serializers.CharField(required=False)
33 
34     def run_validation(self, data=empty):
35         # Ensure that there are no unknown type options passed in.
36         if data is not empty and data is not None:
37             unknown = set(data) - set(self.fields)
38             if unknown:
39                 errors = ['Unknown field: {}'.format(field) for field in unknown]
40                 raise serializers.ValidationError({
41                     api_settings.NON_FIELD_ERRORS_KEY: errors,
42                 })
43 
44         return super(TypeOptionSerializer, self).run_validation(data)
45 
46 
47 class SimpleColumnSerializer(MathesarErrorMessageMixin, serializers.ModelSerializer):
48     class Meta:
49         model = Column
50         fields = ('id',
51                   'name',
52                   'type',
53                   'type_options',
54                   'display_options'
55                   )
56     name = serializers.CharField()
57     type = serializers.CharField(source='plain_type')
58     type_options = TypeOptionSerializer(required=False, allow_null=True)
59     display_options = DisplayOptionsMappingSerializer(required=False, allow_null=True)
60 
61     def to_representation(self, instance):
62         if isinstance(instance, dict):
63             instance_type = instance.get('type')
64         else:
65             instance_type = instance.plain_type
66         self.context[DISPLAY_OPTIONS_SERIALIZER_MAPPING_KEY] = str(instance_type)
67         return super().to_representation(instance)
68 
69     def to_internal_value(self, data):
70         if self.partial and 'type' not in data:
71             instance_type = getattr(self.instance, 'plain_type', None)
72             if instance_type is not None:
73                 self.context[DISPLAY_OPTIONS_SERIALIZER_MAPPING_KEY] = str(instance_type)
74         else:
75             self.context[DISPLAY_OPTIONS_SERIALIZER_MAPPING_KEY] = data.get('type', None)
76         return super().to_internal_value(data)
77 
78 
79 class ColumnDefaultSerializer(MathesarErrorMessageMixin, serializers.Serializer):
80     value = InputValueField()
81     is_dynamic = serializers.BooleanField(read_only=True)
82 
83 
84 class ColumnSerializer(SimpleColumnSerializer):
85     class Meta(SimpleColumnSerializer.Meta):
86         fields = SimpleColumnSerializer.Meta.fields + (
87             'nullable',
88             'primary_key',
89             'source_column',
90             'copy_source_data',
91             'copy_source_constraints',
92             'valid_target_types',
93             'default'
94         )
95         model_fields = ('display_options',)
96 
97     name = serializers.CharField(required=False, allow_blank=True)
98 
99     # From scratch fields
100     type = serializers.CharField(source='plain_type', required=False)
101     nullable = serializers.BooleanField(default=True)
102     primary_key = serializers.BooleanField(default=False)
103     default = ColumnDefaultSerializer(
104         source='column_default_dict', required=False, allow_null=True, default=None
105     )
106 
107     # From duplication fields
108     source_column = serializers.PrimaryKeyRelatedField(queryset=Column.current_objects.all(), required=False, write_only=True)
109     copy_source_data = serializers.BooleanField(default=True, write_only=True)
110     copy_source_constraints = serializers.BooleanField(default=True, write_only=True)
111 
112     # Read only fields
113     valid_target_types = serializers.ListField(read_only=True)
114 
115     def validate(self, data):
116         data = super().validate(data)
117         # Reevaluate column display options based on the new column type.
118         if 'plain_type' in data and 'display_options' not in data:
119             if self.instance:
120                 instance_type = getattr(self.instance, 'plain_type', None)
121                 # Invalidate display_options if type has been changed
122                 if str(instance_type) != data['plain_type']:
123                     data['display_options'] = None
124             else:
125                 data['display_options'] = None
126         if not self.partial:
127             from_scratch_required_fields = ['type']
128             from_scratch_specific_fields = ['type', 'nullable', 'primary_key']
129             from_dupe_required_fields = ['source_column']
130             from_dupe_specific_fields = ['source_column', 'copy_source_data',
131                                          'copy_source_constraints']
132 
133             # Note that we run validation on self.initial_data, as `data` has defaults
134             # filled in for fields that weren't specified by the request
135             from_scratch_required_all = all([
136                 f in self.initial_data for f in from_scratch_required_fields
137             ])
138             from_scratch_specific_in = [
139                 f for f in from_scratch_specific_fields if f in self.initial_data
140             ]
141             from_dupe_required_all = all([
142                 f in self.initial_data for f in from_dupe_required_fields
143             ])
144             from_dupe_specific_in = [
145                 f for f in from_dupe_specific_fields if f in self.initial_data
146             ]
147 
148             if len(from_dupe_specific_in) and len(from_scratch_specific_in):
149                 raise ValidationError(
150                     f'{from_scratch_specific_in} cannot be passed in if '
151                     f'{from_dupe_specific_in} has also been passed in.'
152                 )
153             elif not from_dupe_required_all and not from_scratch_required_all:
154                 # We default to from scratch required fields if no fields are passed
155                 if len(from_dupe_specific_in) and not len(from_scratch_specific_in):
156                     required_fields = from_dupe_required_fields
157                 else:
158                     required_fields = from_scratch_required_fields
159                 raise ValidationError({
160                     f: ['This field is required.']
161                     for f in required_fields
162                     if f not in self.initial_data
163                 })
164         return data
165 
166     @property
167     def validated_model_fields(self):
168         return {key: self.validated_data[key] for key in self.validated_data if key in self.Meta.model_fields}
169
```

Path: `mathesar/api/db/viewsets/columns.py`
Content:
```
1 import warnings
2 from psycopg2.errors import DuplicateColumn
3 from rest_framework import status, viewsets
4 from rest_framework.exceptions import NotFound
5 from rest_framework.response import Response
6 from sqlalchemy.exc import ProgrammingError
7 
8 from mathesar.api.exceptions.database_exceptions import (
9     exceptions as database_api_exceptions,
10     base_exceptions as database_base_api_exceptions,
11 )
12 from mathesar.api.exceptions.generic_exceptions import base_exceptions as base_api_exceptions
13 from db.columns.exceptions import (
14     DynamicDefaultWarning, InvalidDefaultError, InvalidTypeOptionError, InvalidTypeError,
15 )
16 from db.columns.operations.select import get_column_attnum_from_name
17 from db.types.exceptions import InvalidTypeParameters
18 from db.records.exceptions import UndefinedFunction
19 from mathesar.api.pagination import DefaultLimitOffsetPagination
20 from mathesar.api.serializers.columns import ColumnSerializer
21 from mathesar.api.utils import get_table_or_404
22 from mathesar.models import Column
23 
24 
25 class ColumnViewSet(viewsets.ModelViewSet):
26     serializer_class = ColumnSerializer
27     pagination_class = DefaultLimitOffsetPagination
28 
29     def get_queryset(self):
30         return Column.objects.filter(table=self.kwargs['table_pk'])
31 
32     def create(self, request, table_pk=None):
33         table = get_table_or_404(table_pk)
34         # We only support adding a single column through the API.
35         serializer = ColumnSerializer(data=request.data, context={'request': request})
36         serializer.is_valid(raise_exception=True)
37 
38         if 'source_column' in serializer.validated_data:
39             column = table.duplicate_column(
40                 serializer.validated_data['source_column'],
41                 serializer.validated_data['copy_source_data'],
42                 serializer.validated_data['copy_source_constraints'],
43                 serializer.validated_data.get('name'),
44             )
45         else:
46             try:
47                 column = table.add_column(request.data)
48             except ProgrammingError as e:
49                 if type(e.orig) == DuplicateColumn:
50                     name = request.data['name']
51                     raise database_api_exceptions.DuplicateTableAPIException(
52                         e,
53                         message=f'Column {name} already exists',
54                         field='name',
55                         status_code=status.HTTP_400_BAD_REQUEST
56                     )
57                 else:
58                     raise database_base_api_exceptions.ProgrammingAPIException(e)
59             except TypeError as e:
60                 raise base_api_exceptions.TypeErrorAPIException(
61                     e,
62                     message="Unknown type_option passed",
63                     status_code=status.HTTP_400_BAD_REQUEST
64                 )
65             except InvalidDefaultError as e:
66                 raise database_api_exceptions.InvalidDefaultAPIException(
67                     e,
68                     message=f'default "{request.data["default"]}" is invalid for type {request.data["type"]}',
69                     status_code=status.HTTP_400_BAD_REQUEST
70                 )
71             except (InvalidTypeOptionError, InvalidTypeParameters) as e:
72                 type_options = request.data.get('type_options', '')
73                 raise database_api_exceptions.InvalidTypeOptionAPIException(
74                     e,
75                     message=f'parameter dict {type_options} is invalid for type {request.data["type"]}',
76                     field="type_options",
77                     status_code=status.HTTP_400_BAD_REQUEST
78                 )
79             except InvalidTypeError as e:
80                 raise database_api_exceptions.InvalidTypeCastAPIException(
81                     e,
82                     message='This type casting is invalid.',
83                     status_code=status.HTTP_400_BAD_REQUEST
84                 )
85         dj_column = Column(
86             table=table,
87             attnum=get_column_attnum_from_name(table.oid, column.name, table.schema._sa_engine),
88             **serializer.validated_model_fields
89         )
90         dj_column.save()
91         out_serializer = ColumnSerializer(dj_column)
92         return Response(out_serializer.data, status=status.HTTP_201_CREATED)
93 
94     def partial_update(self, request, pk=None, table_pk=None):
95         column_instance = self.get_object()
96         table = column_instance.table
97         serializer = ColumnSerializer(instance=column_instance, data=request.data, partial=True)
98         serializer.is_valid(raise_exception=True)
99         with warnings.catch_warnings():
100             warnings.filterwarnings("error", category=DynamicDefaultWarning)
101             try:
102                 table.alter_column(column_instance._sa_column.column_attnum, serializer.validated_data)
103             except UndefinedFunction as e:
104                 raise database_api_exceptions.UndefinedFunctionAPIException(
105                     e,
106                     message='This type cast is not implemented',
107                     status_code=status.HTTP_400_BAD_REQUEST
108                 )
109             except ProgrammingError as e:
110                 raise database_base_api_exceptions.ProgrammingAPIException(
111                     e,
112                     status_code=status.HTTP_400_BAD_REQUEST
113                 )
114             except IndexError as e:
115                 raise base_api_exceptions.NotFoundAPIException(e)
116             except TypeError as e:
117                 raise database_api_exceptions.InvalidTypeOptionAPIException(
118                     e,
119                     message="Unknown type_option passed",
120                     status_code=status.HTTP_400_BAD_REQUEST
121                 )
122             except InvalidDefaultError as e:
123                 raise database_api_exceptions.InvalidDefaultAPIException(
124                     e,
125                     message=f'default "{request.data["default"]}" is invalid for this column',
126                     status_code=status.HTTP_400_BAD_REQUEST
127                 )
128             except DynamicDefaultWarning as e:
129                 raise database_api_exceptions.DynamicDefaultAPIException(
130                     e,
131                     message='Changing type of columns with dynamically-generated defaults is not supported.'
132                             'Delete or change the default first.',
133                     status_code=status.HTTP_400_BAD_REQUEST
134                 )
135             except (InvalidTypeOptionError, InvalidTypeParameters) as e:
136                 type_options = request.data.get('type_options', '')
137                 raise database_api_exceptions.InvalidTypeOptionAPIException(
138                     e,
139                     message=f'parameter dict {type_options} is invalid for type {request.data["type"]}',
140                     status_code=status.HTTP_400_BAD_REQUEST
141                 )
142             except InvalidTypeError as e:
143                 raise database_api_exceptions.InvalidTypeCastAPIException(
144                     e,
145                     message='This type casting is invalid.',
146                     status_code=status.HTTP_400_BAD_REQUEST
147                 )
148             except Exception as e:
149                 raise base_api_exceptions.MathesarAPIException(e)
150 
151         serializer.update(column_instance, serializer.validated_model_fields)
152         # Invalidate the cache as the underlying columns have changed
153         column_instance = self.get_object()
154         out_serializer = ColumnSerializer(column_instance)
155         return Response(out_serializer.data)
156 
157     def destroy(self, request, pk=None, table_pk=None):
158         column_instance = self.get_object()
159         table = column_instance.table
160         try:
161             table.drop_column(column_instance.attnum)
162             column_instance.delete()
163         except IndexError:
164             raise NotFound
165         return Response(status=status.HTTP_204_NO_CONTENT)
166
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/mathesar/api/db/viewsets/columns.py b/mathesar/api/db/viewsets/columns.py
--- a/mathesar/api/db/viewsets/columns.py
+++ b/mathesar/api/db/viewsets/columns.py
@@ -34,7 +34,12 @@
         # We only support adding a single column through the API.
         serializer = ColumnSerializer(data=request.data, context={'request': request})
         serializer.is_valid(raise_exception=True)
-
+        type_options = request.data.get('type_options', None)
+        if type_options is not None:
+            scale = type_options.get('scale', None)
+            precision = type_options.get('precision', None)
+            if scale is not None and precision is None:
+                request.data['type_options']['precision'] = 1000
         if 'source_column' in serializer.validated_data:
             column = table.duplicate_column(
                 serializer.validated_data['source_column'],
@@ -44,6 +49,7 @@
             )
         else:
             try:
+                # TODO Refactor add_column to user serializer validated date instead of request data
                 column = table.add_column(request.data)
             except ProgrammingError as e:
                 if type(e.orig) == DuplicateColumn:
diff --git a/mathesar/api/serializers/columns.py b/mathesar/api/serializers/columns.py
--- a/mathesar/api/serializers/columns.py
+++ b/mathesar/api/serializers/columns.py
@@ -31,6 +31,11 @@
     scale = serializers.IntegerField(required=False)
     fields = serializers.CharField(required=False)
 
+    def validate(self, attrs):
+        if attrs.get('scale', None) is not None and attrs.get('precision', None) is None:
+            attrs['precision'] = 1000
+        return super().validate(attrs)
+
     def run_validation(self, data=empty):
         # Ensure that there are no unknown type options passed in.
         if data is not empty and data is not None:

{"golden_diff": "diff --git a/mathesar/api/db/viewsets/columns.py b/mathesar/api/db/viewsets/columns.py\n--- a/mathesar/api/db/viewsets/columns.py\n+++ b/mathesar/api/db/viewsets/columns.py\n@@ -34,7 +34,12 @@\n         # We only support adding a single column through the API.\n         serializer = ColumnSerializer(data=request.data, context={'request': request})\n         serializer.is_valid(raise_exception=True)\n-\n+        type_options = request.data.get('type_options', None)\n+        if type_options is not None:\n+            scale = type_options.get('scale', None)\n+            precision = type_options.get('precision', None)\n+            if scale is not None and precision is None:\n+                request.data['type_options']['precision'] = 1000\n         if 'source_column' in serializer.validated_data:\n             column = table.duplicate_column(\n                 serializer.validated_data['source_column'],\n@@ -44,6 +49,7 @@\n             )\n         else:\n             try:\n+                # TODO Refactor add_column to user serializer validated date instead of request data\n                 column = table.add_column(request.data)\n             except ProgrammingError as e:\n                 if type(e.orig) == DuplicateColumn:\ndiff --git a/mathesar/api/serializers/columns.py b/mathesar/api/serializers/columns.py\n--- a/mathesar/api/serializers/columns.py\n+++ b/mathesar/api/serializers/columns.py\n@@ -31,6 +31,11 @@\n     scale = serializers.IntegerField(required=False)\n     fields = serializers.CharField(required=False)\n \n+    def validate(self, attrs):\n+        if attrs.get('scale', None) is not None and attrs.get('precision', None) is None:\n+            attrs['precision'] = 1000\n+        return super().validate(attrs)\n+\n     def run_validation(self, data=empty):\n         # Ensure that there are no unknown type options passed in.\n         if data is not empty and data is not None:\n", "issue": "Number scale not persisting\n## Reproduce\n\n1. In the front end, edit the DB options for a Number column.\n1. Set \"Number Type\" to \"Decimal\". Set \"Decimal Places\" to \"3\". Click Save.\n1. Observe a PATCH request to the columns API which specifies `type_options.scale` to `2`. Good.\n1. In the response of this request (and also in the response of the subsequent GET request to the columns endpoint), expect the column to show `type_options` with an object containing a `scale` of `2`.\n1. Instead, observe `type_options` to be `null`.\n\n", "before_files": [{"content": "from rest_framework import serializers\nfrom rest_framework.exceptions import ValidationError\nfrom rest_framework.fields import empty\nfrom rest_framework.settings import api_settings\n\nfrom mathesar.api.exceptions.mixins import MathesarErrorMessageMixin\nfrom mathesar.api.serializers.shared_serializers import (\n    DisplayOptionsMappingSerializer,\n    DISPLAY_OPTIONS_SERIALIZER_MAPPING_KEY,\n)\nfrom mathesar.models import Column\n\n\nclass InputValueField(serializers.CharField):\n    \"\"\"\n    Takes in an arbitrary value. Emulates the record creation endpoint,\n    which takes in arbitrary values (un-validated and un-processed request.data).\n    This field replicates that behavior in a serializer.\n    \"\"\"\n\n    def to_internal_value(self, data):\n        return data\n\n    def to_representation(self, value):\n        return value\n\n\nclass TypeOptionSerializer(MathesarErrorMessageMixin, serializers.Serializer):\n    length = serializers.IntegerField(required=False)\n    precision = serializers.IntegerField(required=False)\n    scale = serializers.IntegerField(required=False)\n    fields = serializers.CharField(required=False)\n\n    def run_validation(self, data=empty):\n        # Ensure that there are no unknown type options passed in.\n        if data is not empty and data is not None:\n            unknown = set(data) - set(self.fields)\n            if unknown:\n                errors = ['Unknown field: {}'.format(field) for field in unknown]\n                raise serializers.ValidationError({\n                    api_settings.NON_FIELD_ERRORS_KEY: errors,\n                })\n\n        return super(TypeOptionSerializer, self).run_validation(data)\n\n\nclass SimpleColumnSerializer(MathesarErrorMessageMixin, serializers.ModelSerializer):\n    class Meta:\n        model = Column\n        fields = ('id',\n                  'name',\n                  'type',\n                  'type_options',\n                  'display_options'\n                  )\n    name = serializers.CharField()\n    type = serializers.CharField(source='plain_type')\n    type_options = TypeOptionSerializer(required=False, allow_null=True)\n    display_options = DisplayOptionsMappingSerializer(required=False, allow_null=True)\n\n    def to_representation(self, instance):\n        if isinstance(instance, dict):\n            instance_type = instance.get('type')\n        else:\n            instance_type = instance.plain_type\n        self.context[DISPLAY_OPTIONS_SERIALIZER_MAPPING_KEY] = str(instance_type)\n        return super().to_representation(instance)\n\n    def to_internal_value(self, data):\n        if self.partial and 'type' not in data:\n            instance_type = getattr(self.instance, 'plain_type', None)\n            if instance_type is not None:\n                self.context[DISPLAY_OPTIONS_SERIALIZER_MAPPING_KEY] = str(instance_type)\n        else:\n            self.context[DISPLAY_OPTIONS_SERIALIZER_MAPPING_KEY] = data.get('type', None)\n        return super().to_internal_value(data)\n\n\nclass ColumnDefaultSerializer(MathesarErrorMessageMixin, serializers.Serializer):\n    value = InputValueField()\n    is_dynamic = serializers.BooleanField(read_only=True)\n\n\nclass ColumnSerializer(SimpleColumnSerializer):\n    class Meta(SimpleColumnSerializer.Meta):\n        fields = SimpleColumnSerializer.Meta.fields + (\n            'nullable',\n            'primary_key',\n            'source_column',\n            'copy_source_data',\n            'copy_source_constraints',\n            'valid_target_types',\n            'default'\n        )\n        model_fields = ('display_options',)\n\n    name = serializers.CharField(required=False, allow_blank=True)\n\n    # From scratch fields\n    type = serializers.CharField(source='plain_type', required=False)\n    nullable = serializers.BooleanField(default=True)\n    primary_key = serializers.BooleanField(default=False)\n    default = ColumnDefaultSerializer(\n        source='column_default_dict', required=False, allow_null=True, default=None\n    )\n\n    # From duplication fields\n    source_column = serializers.PrimaryKeyRelatedField(queryset=Column.current_objects.all(), required=False, write_only=True)\n    copy_source_data = serializers.BooleanField(default=True, write_only=True)\n    copy_source_constraints = serializers.BooleanField(default=True, write_only=True)\n\n    # Read only fields\n    valid_target_types = serializers.ListField(read_only=True)\n\n    def validate(self, data):\n        data = super().validate(data)\n        # Reevaluate column display options based on the new column type.\n        if 'plain_type' in data and 'display_options' not in data:\n            if self.instance:\n                instance_type = getattr(self.instance, 'plain_type', None)\n                # Invalidate display_options if type has been changed\n                if str(instance_type) != data['plain_type']:\n                    data['display_options'] = None\n            else:\n                data['display_options'] = None\n        if not self.partial:\n            from_scratch_required_fields = ['type']\n            from_scratch_specific_fields = ['type', 'nullable', 'primary_key']\n            from_dupe_required_fields = ['source_column']\n            from_dupe_specific_fields = ['source_column', 'copy_source_data',\n                                         'copy_source_constraints']\n\n            # Note that we run validation on self.initial_data, as `data` has defaults\n            # filled in for fields that weren't specified by the request\n            from_scratch_required_all = all([\n                f in self.initial_data for f in from_scratch_required_fields\n            ])\n            from_scratch_specific_in = [\n                f for f in from_scratch_specific_fields if f in self.initial_data\n            ]\n            from_dupe_required_all = all([\n                f in self.initial_data for f in from_dupe_required_fields\n            ])\n            from_dupe_specific_in = [\n                f for f in from_dupe_specific_fields if f in self.initial_data\n            ]\n\n            if len(from_dupe_specific_in) and len(from_scratch_specific_in):\n                raise ValidationError(\n                    f'{from_scratch_specific_in} cannot be passed in if '\n                    f'{from_dupe_specific_in} has also been passed in.'\n                )\n            elif not from_dupe_required_all and not from_scratch_required_all:\n                # We default to from scratch required fields if no fields are passed\n                if len(from_dupe_specific_in) and not len(from_scratch_specific_in):\n                    required_fields = from_dupe_required_fields\n                else:\n                    required_fields = from_scratch_required_fields\n                raise ValidationError({\n                    f: ['This field is required.']\n                    for f in required_fields\n                    if f not in self.initial_data\n                })\n        return data\n\n    @property\n    def validated_model_fields(self):\n        return {key: self.validated_data[key] for key in self.validated_data if key in self.Meta.model_fields}\n", "path": "mathesar/api/serializers/columns.py"}, {"content": "import warnings\nfrom psycopg2.errors import DuplicateColumn\nfrom rest_framework import status, viewsets\nfrom rest_framework.exceptions import NotFound\nfrom rest_framework.response import Response\nfrom sqlalchemy.exc import ProgrammingError\n\nfrom mathesar.api.exceptions.database_exceptions import (\n    exceptions as database_api_exceptions,\n    base_exceptions as database_base_api_exceptions,\n)\nfrom mathesar.api.exceptions.generic_exceptions import base_exceptions as base_api_exceptions\nfrom db.columns.exceptions import (\n    DynamicDefaultWarning, InvalidDefaultError, InvalidTypeOptionError, InvalidTypeError,\n)\nfrom db.columns.operations.select import get_column_attnum_from_name\nfrom db.types.exceptions import InvalidTypeParameters\nfrom db.records.exceptions import UndefinedFunction\nfrom mathesar.api.pagination import DefaultLimitOffsetPagination\nfrom mathesar.api.serializers.columns import ColumnSerializer\nfrom mathesar.api.utils import get_table_or_404\nfrom mathesar.models import Column\n\n\nclass ColumnViewSet(viewsets.ModelViewSet):\n    serializer_class = ColumnSerializer\n    pagination_class = DefaultLimitOffsetPagination\n\n    def get_queryset(self):\n        return Column.objects.filter(table=self.kwargs['table_pk'])\n\n    def create(self, request, table_pk=None):\n        table = get_table_or_404(table_pk)\n        # We only support adding a single column through the API.\n        serializer = ColumnSerializer(data=request.data, context={'request': request})\n        serializer.is_valid(raise_exception=True)\n\n        if 'source_column' in serializer.validated_data:\n            column = table.duplicate_column(\n                serializer.validated_data['source_column'],\n                serializer.validated_data['copy_source_data'],\n                serializer.validated_data['copy_source_constraints'],\n                serializer.validated_data.get('name'),\n            )\n        else:\n            try:\n                column = table.add_column(request.data)\n            except ProgrammingError as e:\n                if type(e.orig) == DuplicateColumn:\n                    name = request.data['name']\n                    raise database_api_exceptions.DuplicateTableAPIException(\n                        e,\n                        message=f'Column {name} already exists',\n                        field='name',\n                        status_code=status.HTTP_400_BAD_REQUEST\n                    )\n                else:\n                    raise database_base_api_exceptions.ProgrammingAPIException(e)\n            except TypeError as e:\n                raise base_api_exceptions.TypeErrorAPIException(\n                    e,\n                    message=\"Unknown type_option passed\",\n                    status_code=status.HTTP_400_BAD_REQUEST\n                )\n            except InvalidDefaultError as e:\n                raise database_api_exceptions.InvalidDefaultAPIException(\n                    e,\n                    message=f'default \"{request.data[\"default\"]}\" is invalid for type {request.data[\"type\"]}',\n                    status_code=status.HTTP_400_BAD_REQUEST\n                )\n            except (InvalidTypeOptionError, InvalidTypeParameters) as e:\n                type_options = request.data.get('type_options', '')\n                raise database_api_exceptions.InvalidTypeOptionAPIException(\n                    e,\n                    message=f'parameter dict {type_options} is invalid for type {request.data[\"type\"]}',\n                    field=\"type_options\",\n                    status_code=status.HTTP_400_BAD_REQUEST\n                )\n            except InvalidTypeError as e:\n                raise database_api_exceptions.InvalidTypeCastAPIException(\n                    e,\n                    message='This type casting is invalid.',\n                    status_code=status.HTTP_400_BAD_REQUEST\n                )\n        dj_column = Column(\n            table=table,\n            attnum=get_column_attnum_from_name(table.oid, column.name, table.schema._sa_engine),\n            **serializer.validated_model_fields\n        )\n        dj_column.save()\n        out_serializer = ColumnSerializer(dj_column)\n        return Response(out_serializer.data, status=status.HTTP_201_CREATED)\n\n    def partial_update(self, request, pk=None, table_pk=None):\n        column_instance = self.get_object()\n        table = column_instance.table\n        serializer = ColumnSerializer(instance=column_instance, data=request.data, partial=True)\n        serializer.is_valid(raise_exception=True)\n        with warnings.catch_warnings():\n            warnings.filterwarnings(\"error\", category=DynamicDefaultWarning)\n            try:\n                table.alter_column(column_instance._sa_column.column_attnum, serializer.validated_data)\n            except UndefinedFunction as e:\n                raise database_api_exceptions.UndefinedFunctionAPIException(\n                    e,\n                    message='This type cast is not implemented',\n                    status_code=status.HTTP_400_BAD_REQUEST\n                )\n            except ProgrammingError as e:\n                raise database_base_api_exceptions.ProgrammingAPIException(\n                    e,\n                    status_code=status.HTTP_400_BAD_REQUEST\n                )\n            except IndexError as e:\n                raise base_api_exceptions.NotFoundAPIException(e)\n            except TypeError as e:\n                raise database_api_exceptions.InvalidTypeOptionAPIException(\n                    e,\n                    message=\"Unknown type_option passed\",\n                    status_code=status.HTTP_400_BAD_REQUEST\n                )\n            except InvalidDefaultError as e:\n                raise database_api_exceptions.InvalidDefaultAPIException(\n                    e,\n                    message=f'default \"{request.data[\"default\"]}\" is invalid for this column',\n                    status_code=status.HTTP_400_BAD_REQUEST\n                )\n            except DynamicDefaultWarning as e:\n                raise database_api_exceptions.DynamicDefaultAPIException(\n                    e,\n                    message='Changing type of columns with dynamically-generated defaults is not supported.'\n                            'Delete or change the default first.',\n                    status_code=status.HTTP_400_BAD_REQUEST\n                )\n            except (InvalidTypeOptionError, InvalidTypeParameters) as e:\n                type_options = request.data.get('type_options', '')\n                raise database_api_exceptions.InvalidTypeOptionAPIException(\n                    e,\n                    message=f'parameter dict {type_options} is invalid for type {request.data[\"type\"]}',\n                    status_code=status.HTTP_400_BAD_REQUEST\n                )\n            except InvalidTypeError as e:\n                raise database_api_exceptions.InvalidTypeCastAPIException(\n                    e,\n                    message='This type casting is invalid.',\n                    status_code=status.HTTP_400_BAD_REQUEST\n                )\n            except Exception as e:\n                raise base_api_exceptions.MathesarAPIException(e)\n\n        serializer.update(column_instance, serializer.validated_model_fields)\n        # Invalidate the cache as the underlying columns have changed\n        column_instance = self.get_object()\n        out_serializer = ColumnSerializer(column_instance)\n        return Response(out_serializer.data)\n\n    def destroy(self, request, pk=None, table_pk=None):\n        column_instance = self.get_object()\n        table = column_instance.table\n        try:\n            table.drop_column(column_instance.attnum)\n            column_instance.delete()\n        except IndexError:\n            raise NotFound\n        return Response(status=status.HTTP_204_NO_CONTENT)\n", "path": "mathesar/api/db/viewsets/columns.py"}], "after_files": [{"content": "from rest_framework import serializers\nfrom rest_framework.exceptions import ValidationError\nfrom rest_framework.fields import empty\nfrom rest_framework.settings import api_settings\n\nfrom mathesar.api.exceptions.mixins import MathesarErrorMessageMixin\nfrom mathesar.api.serializers.shared_serializers import (\n    DisplayOptionsMappingSerializer,\n    DISPLAY_OPTIONS_SERIALIZER_MAPPING_KEY,\n)\nfrom mathesar.models import Column\n\n\nclass InputValueField(serializers.CharField):\n    \"\"\"\n    Takes in an arbitrary value. Emulates the record creation endpoint,\n    which takes in arbitrary values (un-validated and un-processed request.data).\n    This field replicates that behavior in a serializer.\n    \"\"\"\n\n    def to_internal_value(self, data):\n        return data\n\n    def to_representation(self, value):\n        return value\n\n\nclass TypeOptionSerializer(MathesarErrorMessageMixin, serializers.Serializer):\n    length = serializers.IntegerField(required=False)\n    precision = serializers.IntegerField(required=False)\n    scale = serializers.IntegerField(required=False)\n    fields = serializers.CharField(required=False)\n\n    def validate(self, attrs):\n        if attrs.get('scale', None) is not None and attrs.get('precision', None) is None:\n            attrs['precision'] = 1000\n        return super().validate(attrs)\n\n    def run_validation(self, data=empty):\n        # Ensure that there are no unknown type options passed in.\n        if data is not empty and data is not None:\n            unknown = set(data) - set(self.fields)\n            if unknown:\n                errors = ['Unknown field: {}'.format(field) for field in unknown]\n                raise serializers.ValidationError({\n                    api_settings.NON_FIELD_ERRORS_KEY: errors,\n                })\n\n        return super(TypeOptionSerializer, self).run_validation(data)\n\n\nclass SimpleColumnSerializer(MathesarErrorMessageMixin, serializers.ModelSerializer):\n    class Meta:\n        model = Column\n        fields = ('id',\n                  'name',\n                  'type',\n                  'type_options',\n                  'display_options'\n                  )\n    name = serializers.CharField()\n    type = serializers.CharField(source='plain_type')\n    type_options = TypeOptionSerializer(required=False, allow_null=True)\n    display_options = DisplayOptionsMappingSerializer(required=False, allow_null=True)\n\n    def to_representation(self, instance):\n        if isinstance(instance, dict):\n            instance_type = instance.get('type')\n        else:\n            instance_type = instance.plain_type\n        self.context[DISPLAY_OPTIONS_SERIALIZER_MAPPING_KEY] = str(instance_type)\n        return super().to_representation(instance)\n\n    def to_internal_value(self, data):\n        if self.partial and 'type' not in data:\n            instance_type = getattr(self.instance, 'plain_type', None)\n            if instance_type is not None:\n                self.context[DISPLAY_OPTIONS_SERIALIZER_MAPPING_KEY] = str(instance_type)\n        else:\n            self.context[DISPLAY_OPTIONS_SERIALIZER_MAPPING_KEY] = data.get('type', None)\n        return super().to_internal_value(data)\n\n\nclass ColumnDefaultSerializer(MathesarErrorMessageMixin, serializers.Serializer):\n    value = InputValueField()\n    is_dynamic = serializers.BooleanField(read_only=True)\n\n\nclass ColumnSerializer(SimpleColumnSerializer):\n    class Meta(SimpleColumnSerializer.Meta):\n        fields = SimpleColumnSerializer.Meta.fields + (\n            'nullable',\n            'primary_key',\n            'source_column',\n            'copy_source_data',\n            'copy_source_constraints',\n            'valid_target_types',\n            'default'\n        )\n        model_fields = ('display_options',)\n\n    name = serializers.CharField(required=False, allow_blank=True)\n\n    # From scratch fields\n    type = serializers.CharField(source='plain_type', required=False)\n    nullable = serializers.BooleanField(default=True)\n    primary_key = serializers.BooleanField(default=False)\n    default = ColumnDefaultSerializer(\n        source='column_default_dict', required=False, allow_null=True, default=None\n    )\n\n    # From duplication fields\n    source_column = serializers.PrimaryKeyRelatedField(queryset=Column.current_objects.all(), required=False, write_only=True)\n    copy_source_data = serializers.BooleanField(default=True, write_only=True)\n    copy_source_constraints = serializers.BooleanField(default=True, write_only=True)\n\n    # Read only fields\n    valid_target_types = serializers.ListField(read_only=True)\n\n    def validate(self, data):\n        data = super().validate(data)\n        # Reevaluate column display options based on the new column type.\n        if 'plain_type' in data and 'display_options' not in data:\n            if self.instance:\n                instance_type = getattr(self.instance, 'plain_type', None)\n                # Invalidate display_options if type has been changed\n                if str(instance_type) != data['plain_type']:\n                    data['display_options'] = None\n            else:\n                data['display_options'] = None\n        if not self.partial:\n            from_scratch_required_fields = ['type']\n            from_scratch_specific_fields = ['type', 'nullable', 'primary_key']\n            from_dupe_required_fields = ['source_column']\n            from_dupe_specific_fields = ['source_column', 'copy_source_data',\n                                         'copy_source_constraints']\n\n            # Note that we run validation on self.initial_data, as `data` has defaults\n            # filled in for fields that weren't specified by the request\n            from_scratch_required_all = all([\n                f in self.initial_data for f in from_scratch_required_fields\n            ])\n            from_scratch_specific_in = [\n                f for f in from_scratch_specific_fields if f in self.initial_data\n            ]\n            from_dupe_required_all = all([\n                f in self.initial_data for f in from_dupe_required_fields\n            ])\n            from_dupe_specific_in = [\n                f for f in from_dupe_specific_fields if f in self.initial_data\n            ]\n\n            if len(from_dupe_specific_in) and len(from_scratch_specific_in):\n                raise ValidationError(\n                    f'{from_scratch_specific_in} cannot be passed in if '\n                    f'{from_dupe_specific_in} has also been passed in.'\n                )\n            elif not from_dupe_required_all and not from_scratch_required_all:\n                # We default to from scratch required fields if no fields are passed\n                if len(from_dupe_specific_in) and not len(from_scratch_specific_in):\n                    required_fields = from_dupe_required_fields\n                else:\n                    required_fields = from_scratch_required_fields\n                raise ValidationError({\n                    f: ['This field is required.']\n                    for f in required_fields\n                    if f not in self.initial_data\n                })\n        return data\n\n    @property\n    def validated_model_fields(self):\n        return {key: self.validated_data[key] for key in self.validated_data if key in self.Meta.model_fields}\n", "path": "mathesar/api/serializers/columns.py"}, {"content": "import warnings\nfrom psycopg2.errors import DuplicateColumn\nfrom rest_framework import status, viewsets\nfrom rest_framework.exceptions import NotFound\nfrom rest_framework.response import Response\nfrom sqlalchemy.exc import ProgrammingError\n\nfrom mathesar.api.exceptions.database_exceptions import (\n    exceptions as database_api_exceptions,\n    base_exceptions as database_base_api_exceptions,\n)\nfrom mathesar.api.exceptions.generic_exceptions import base_exceptions as base_api_exceptions\nfrom db.columns.exceptions import (\n    DynamicDefaultWarning, InvalidDefaultError, InvalidTypeOptionError, InvalidTypeError,\n)\nfrom db.columns.operations.select import get_column_attnum_from_name\nfrom db.types.exceptions import InvalidTypeParameters\nfrom db.records.exceptions import UndefinedFunction\nfrom mathesar.api.pagination import DefaultLimitOffsetPagination\nfrom mathesar.api.serializers.columns import ColumnSerializer\nfrom mathesar.api.utils import get_table_or_404\nfrom mathesar.models import Column\n\n\nclass ColumnViewSet(viewsets.ModelViewSet):\n    serializer_class = ColumnSerializer\n    pagination_class = DefaultLimitOffsetPagination\n\n    def get_queryset(self):\n        return Column.objects.filter(table=self.kwargs['table_pk'])\n\n    def create(self, request, table_pk=None):\n        table = get_table_or_404(table_pk)\n        # We only support adding a single column through the API.\n        serializer = ColumnSerializer(data=request.data, context={'request': request})\n        serializer.is_valid(raise_exception=True)\n        type_options = request.data.get('type_options', None)\n        if type_options is not None:\n            scale = type_options.get('scale', None)\n            precision = type_options.get('precision', None)\n            if scale is not None and precision is None:\n                request.data['type_options']['precision'] = 1000\n        if 'source_column' in serializer.validated_data:\n            column = table.duplicate_column(\n                serializer.validated_data['source_column'],\n                serializer.validated_data['copy_source_data'],\n                serializer.validated_data['copy_source_constraints'],\n                serializer.validated_data.get('name'),\n            )\n        else:\n            try:\n                # TODO Refactor add_column to user serializer validated date instead of request data\n                column = table.add_column(request.data)\n            except ProgrammingError as e:\n                if type(e.orig) == DuplicateColumn:\n                    name = request.data['name']\n                    raise database_api_exceptions.DuplicateTableAPIException(\n                        e,\n                        message=f'Column {name} already exists',\n                        field='name',\n                        status_code=status.HTTP_400_BAD_REQUEST\n                    )\n                else:\n                    raise database_base_api_exceptions.ProgrammingAPIException(e)\n            except TypeError as e:\n                raise base_api_exceptions.TypeErrorAPIException(\n                    e,\n                    message=\"Unknown type_option passed\",\n                    status_code=status.HTTP_400_BAD_REQUEST\n                )\n            except InvalidDefaultError as e:\n                raise database_api_exceptions.InvalidDefaultAPIException(\n                    e,\n                    message=f'default \"{request.data[\"default\"]}\" is invalid for type {request.data[\"type\"]}',\n                    status_code=status.HTTP_400_BAD_REQUEST\n                )\n            except (InvalidTypeOptionError, InvalidTypeParameters) as e:\n                type_options = request.data.get('type_options', '')\n                raise database_api_exceptions.InvalidTypeOptionAPIException(\n                    e,\n                    message=f'parameter dict {type_options} is invalid for type {request.data[\"type\"]}',\n                    field=\"type_options\",\n                    status_code=status.HTTP_400_BAD_REQUEST\n                )\n            except InvalidTypeError as e:\n                raise database_api_exceptions.InvalidTypeCastAPIException(\n                    e,\n                    message='This type casting is invalid.',\n                    status_code=status.HTTP_400_BAD_REQUEST\n                )\n        dj_column = Column(\n            table=table,\n            attnum=get_column_attnum_from_name(table.oid, column.name, table.schema._sa_engine),\n            **serializer.validated_model_fields\n        )\n        dj_column.save()\n        out_serializer = ColumnSerializer(dj_column)\n        return Response(out_serializer.data, status=status.HTTP_201_CREATED)\n\n    def partial_update(self, request, pk=None, table_pk=None):\n        column_instance = self.get_object()\n        table = column_instance.table\n        serializer = ColumnSerializer(instance=column_instance, data=request.data, partial=True)\n        serializer.is_valid(raise_exception=True)\n        with warnings.catch_warnings():\n            warnings.filterwarnings(\"error\", category=DynamicDefaultWarning)\n            try:\n                table.alter_column(column_instance._sa_column.column_attnum, serializer.validated_data)\n            except UndefinedFunction as e:\n                raise database_api_exceptions.UndefinedFunctionAPIException(\n                    e,\n                    message='This type cast is not implemented',\n                    status_code=status.HTTP_400_BAD_REQUEST\n                )\n            except ProgrammingError as e:\n                raise database_base_api_exceptions.ProgrammingAPIException(\n                    e,\n                    status_code=status.HTTP_400_BAD_REQUEST\n                )\n            except IndexError as e:\n                raise base_api_exceptions.NotFoundAPIException(e)\n            except TypeError as e:\n                raise database_api_exceptions.InvalidTypeOptionAPIException(\n                    e,\n                    message=\"Unknown type_option passed\",\n                    status_code=status.HTTP_400_BAD_REQUEST\n                )\n            except InvalidDefaultError as e:\n                raise database_api_exceptions.InvalidDefaultAPIException(\n                    e,\n                    message=f'default \"{request.data[\"default\"]}\" is invalid for this column',\n                    status_code=status.HTTP_400_BAD_REQUEST\n                )\n            except DynamicDefaultWarning as e:\n                raise database_api_exceptions.DynamicDefaultAPIException(\n                    e,\n                    message='Changing type of columns with dynamically-generated defaults is not supported.'\n                            'Delete or change the default first.',\n                    status_code=status.HTTP_400_BAD_REQUEST\n                )\n            except (InvalidTypeOptionError, InvalidTypeParameters) as e:\n                type_options = request.data.get('type_options', '')\n                raise database_api_exceptions.InvalidTypeOptionAPIException(\n                    e,\n                    message=f'parameter dict {type_options} is invalid for type {request.data[\"type\"]}',\n                    status_code=status.HTTP_400_BAD_REQUEST\n                )\n            except InvalidTypeError as e:\n                raise database_api_exceptions.InvalidTypeCastAPIException(\n                    e,\n                    message='This type casting is invalid.',\n                    status_code=status.HTTP_400_BAD_REQUEST\n                )\n            except Exception as e:\n                raise base_api_exceptions.MathesarAPIException(e)\n\n        serializer.update(column_instance, serializer.validated_model_fields)\n        # Invalidate the cache as the underlying columns have changed\n        column_instance = self.get_object()\n        out_serializer = ColumnSerializer(column_instance)\n        return Response(out_serializer.data)\n\n    def destroy(self, request, pk=None, table_pk=None):\n        column_instance = self.get_object()\n        table = column_instance.table\n        try:\n            table.drop_column(column_instance.attnum)\n            column_instance.delete()\n        except IndexError:\n            raise NotFound\n        return Response(status=status.HTTP_204_NO_CONTENT)\n", "path": "mathesar/api/db/viewsets/columns.py"}]}