Skip to content

Commit dc2d6a6

Browse files
author
Dmitriy Kunitskiy
authoredMar 31, 2021
chore: upgrade to marshmallow 3 (#963)
* chore: upgrade to marshmallow 3 and marshmallow3-annotations Signed-off-by: Dmitriy Kunitskiy <dkunitskiy@lyft.com> * typos Signed-off-by: Dmitriy Kunitskiy <dkunitskiy@lyft.com>
1 parent 33a681c commit dc2d6a6

15 files changed

+72
-76
lines changed
 

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ build/
1616
.coverage
1717
.mypy_cache
1818
.pytest_cache
19+
.python-version
1920

2021

2122
npm-debug.log

‎amundsen_application/api/preview/v0.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from flask import Response, jsonify, make_response, request, current_app as app
1111
from flask.blueprints import Blueprint
12+
from marshmallow import ValidationError
1213
from werkzeug.utils import import_string
1314

1415
from amundsen_application.models.preview_data import PreviewDataSchema
@@ -49,11 +50,11 @@ def get_table_preview() -> Response:
4950
preview_data = json.loads(response.data).get('preview_data')
5051
if status_code == HTTPStatus.OK:
5152
# validate the returned table preview data
52-
data, errors = PreviewDataSchema().load(preview_data)
53-
if not errors:
53+
try:
54+
data = PreviewDataSchema().load(preview_data)
5455
payload = jsonify({'previewData': data, 'msg': 'Success'})
55-
else:
56-
logging.error('Preview data dump returned errors: ' + str(errors))
56+
except ValidationError as err:
57+
logging.error('Preview data dump returned errors: ' + str(err.messages))
5758
raise Exception('The preview client did not return a valid PreviewData object')
5859
else:
5960
message = 'Encountered error: Preview client request failed with code ' + str(status_code)

‎amundsen_application/api/utils/metadata_utils.py

+10-11
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import urllib.parse
66

77
from dataclasses import dataclass
8+
from marshmallow import EXCLUDE
89
from typing import Any, Dict, List
910

1011
from amundsen_common.models.dashboard import DashboardSummary, DashboardSummarySchema
@@ -46,10 +47,9 @@ def marshall_table_partial(table_dict: Dict) -> Dict:
4647
4748
TODO - Unify data format returned by search and metadata.
4849
"""
49-
schema = PopularTableSchema(strict=True)
50-
# TODO: consider migrating to validate() instead of roundtripping
51-
table: PopularTable = schema.load(table_dict).data
52-
results = schema.dump(table).data
50+
schema = PopularTableSchema()
51+
table: PopularTable = schema.load(table_dict, unknown=EXCLUDE)
52+
results = schema.dump(table)
5353
# TODO: fix popular tables to provide these? remove if we're not using them?
5454
# TODO: Add the 'key' or 'id' to the base PopularTableSchema
5555
results['key'] = f'{table.database}://{table.cluster}.{table.schema}/{table.name}'
@@ -104,10 +104,9 @@ def marshall_table_full(table_dict: Dict) -> Dict:
104104
:return: Table Dict with sanitized fields
105105
"""
106106

107-
schema = TableSchema(strict=True)
108-
# TODO: consider migrating to validate() instead of roundtripping
109-
table: Table = schema.load(table_dict).data
110-
results: Dict[str, Any] = schema.dump(table).data
107+
schema = TableSchema()
108+
table: Table = schema.load(table_dict)
109+
results: Dict[str, Any] = schema.dump(table)
111110

112111
is_editable = is_table_editable(results['schema'], results['name'])
113112
results['is_editable'] = is_editable
@@ -149,9 +148,9 @@ def marshall_dashboard_partial(dashboard_dict: Dict) -> Dict:
149148
:param dashboard_dict: Dict of partial dashboard metadata
150149
:return: partial dashboard Dict
151150
"""
152-
schema = DashboardSummarySchema(strict=True)
153-
dashboard: DashboardSummary = schema.load(dashboard_dict).data
154-
results = schema.dump(dashboard).data
151+
schema = DashboardSummarySchema(unknown=EXCLUDE)
152+
dashboard: DashboardSummary = schema.load(dashboard_dict)
153+
results = schema.dump(dashboard)
155154
results['type'] = 'dashboard'
156155
# TODO: Bookmark logic relies on key, opting to add this here to avoid messy logic in
157156
# React app and we have to clean up later.

‎amundsen_application/base/base_announcement_client.py

+12-15
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from http import HTTPStatus
88

99
from flask import jsonify, make_response, Response
10+
from marshmallow import ValidationError
1011

1112
from amundsen_application.models.announcements import Announcements, AnnouncementsSchema
1213

@@ -31,20 +32,16 @@ def _create_error_response(message: str) -> Response:
3132
return make_response(payload, HTTPStatus.INTERNAL_SERVER_ERROR)
3233

3334
try:
34-
try:
35-
announcements = self.get_posts()
36-
except Exception as e:
37-
message = 'Encountered exception getting posts: ' + str(e)
38-
return _create_error_response(message)
39-
40-
# validate the returned object
41-
data, errors = AnnouncementsSchema().dump(announcements)
42-
if not errors:
43-
payload = jsonify({'posts': data.get('posts'), 'msg': 'Success'})
44-
return make_response(payload, HTTPStatus.OK)
45-
else:
46-
message = 'Announcement data dump returned errors: ' + str(errors)
47-
return _create_error_response(message)
35+
announcements = self.get_posts()
4836
except Exception as e:
49-
message = 'Encountered exception: ' + str(e)
37+
message = 'Encountered exception getting posts: ' + str(e)
38+
return _create_error_response(message)
39+
40+
try:
41+
data = AnnouncementsSchema().dump(announcements)
42+
AnnouncementsSchema().load(data) # validate returned object
43+
payload = jsonify({'posts': data.get('posts'), 'msg': 'Success'})
44+
return make_response(payload, HTTPStatus.OK)
45+
except ValidationError as err:
46+
message = 'Announcement data dump returned errors: ' + str(err.messages)
5047
return _create_error_response(message)

‎amundsen_application/base/base_bigquery_preview_client.py

+10-8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# SPDX-License-Identifier: Apache-2.0
33

44
from http import HTTPStatus
5+
import logging
56
from typing import Dict, List
67
from amundsen_application.base.base_preview_client import BasePreviewClient
78
from amundsen_application.models.preview_data import (
@@ -10,6 +11,7 @@
1011
PreviewDataSchema,
1112
)
1213
from flask import Response, make_response, jsonify
14+
from marshmallow import ValidationError
1315
from google.cloud import bigquery
1416

1517

@@ -60,13 +62,13 @@ def get_preview_data(self, params: Dict, optionalHeaders: Dict = None) -> Respon
6062
params["schema"],
6163
params["tableName"],
6264
)
63-
data = PreviewDataSchema().dump(preview_data)[0]
64-
errors = PreviewDataSchema().load(data)[1]
65-
payload = jsonify({"preview_data": data})
66-
67-
if not errors:
65+
try:
66+
data = PreviewDataSchema().dump(preview_data)
67+
PreviewDataSchema().load(data) # for validation only
6868
payload = jsonify({"preview_data": data})
6969
return make_response(payload, HTTPStatus.OK)
70-
return make_response(
71-
jsonify({"preview_data": {}}), HTTPStatus.INTERNAL_SERVER_ERROR
72-
)
70+
except ValidationError as err:
71+
logging.error("PreviewDataSchema serialization error + " + str(err.messages))
72+
return make_response(
73+
jsonify({"preview_data": {}}), HTTPStatus.INTERNAL_SERVER_ERROR
74+
)

‎amundsen_application/base/base_superset_preview_client.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
# SPDX-License-Identifier: Apache-2.0
33

44
import abc
5+
import logging
56

67
from flask import Response as FlaskResponse, make_response, jsonify
78
from http import HTTPStatus
9+
from marshmallow import ValidationError
810
from requests import Response
911
from typing import Dict
1012

@@ -45,12 +47,13 @@ def get_preview_data(self, params: Dict, optionalHeaders: Dict = None) -> FlaskR
4547
response_dict = response.json()
4648
columns = [ColumnItem(c['name'], c['type']) for c in response_dict['columns']]
4749
preview_data = PreviewData(columns, response_dict['data'])
48-
data = PreviewDataSchema().dump(preview_data)[0]
49-
errors = PreviewDataSchema().load(data)[1]
50-
if not errors:
50+
try:
51+
data = PreviewDataSchema().dump(preview_data)
52+
PreviewDataSchema().load(data) # for validation only
5153
payload = jsonify({'preview_data': data})
5254
return make_response(payload, response.status_code)
53-
else:
55+
except ValidationError as err:
56+
logging.error("PreviewDataSchema serialization error " + str(err.messages))
5457
return make_response(jsonify({'preview_data': {}}), HTTPStatus.INTERNAL_SERVER_ERROR)
5558
except Exception:
5659
return make_response(jsonify({'preview_data': {}}), HTTPStatus.INTERNAL_SERVER_ERROR)

‎amundsen_application/base/examples/example_dremio_preview_client.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from typing import Dict # noqa: F401
77

88
from flask import Response, jsonify, make_response, current_app as app
9+
from marshmallow import ValidationError
910
from pyarrow import flight
1011

1112
from amundsen_application.base.base_superset_preview_client import BasePreviewClient
@@ -79,16 +80,15 @@ def get_preview_data(self, params: Dict, optionalHeaders: Dict = None) -> Respon
7980
column_items = [ColumnItem(n, t) for n, t in zip(names, types)]
8081

8182
preview_data = PreviewData(column_items, rows)
82-
83-
data = PreviewDataSchema().dump(preview_data)[0]
84-
errors = PreviewDataSchema().load(data)[1]
85-
if errors:
86-
logging.error(f'Error(s) occurred while building preview data: {errors}')
87-
payload = jsonify({'preview_data': {}})
88-
return make_response(payload, HTTPStatus.INTERNAL_SERVER_ERROR)
89-
else:
83+
try:
84+
data = PreviewDataSchema().dump(preview_data)
85+
PreviewDataSchema().load(data) # for validation only
9086
payload = jsonify({'preview_data': data})
9187
return make_response(payload, HTTPStatus.OK)
88+
except ValidationError as err:
89+
logging.error(f'Error(s) occurred while building preview data: {err.messages}')
90+
payload = jsonify({'preview_data': {}})
91+
return make_response(payload, HTTPStatus.INTERNAL_SERVER_ERROR)
9292

9393
except Exception as e:
9494
logging.error(f'Encountered exception: {e}')

‎amundsen_application/models/announcements.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from marshmallow import Schema, fields, post_dump
55
from marshmallow.exceptions import ValidationError
66

7-
from typing import Dict, List
7+
from typing import Dict, List, Any
88

99

1010
class Post:
@@ -29,10 +29,11 @@ class AnnouncementsSchema(Schema):
2929
posts = fields.Nested(PostSchema, many=True)
3030

3131
@post_dump
32-
def validate_data(self, data: Dict) -> None:
32+
def validate_data(self, data: Dict, **kwargs: Any) -> Dict:
3333
posts = data.get('posts', [])
3434
for post in posts:
3535
if post.get('date') is None:
3636
raise ValidationError('All posts must have a date')
3737
if post.get('title') is None:
3838
raise ValidationError('All posts must have a title')
39+
return data

‎amundsen_application/models/preview_data.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Copyright Contributors to the Amundsen project.
22
# SPDX-License-Identifier: Apache-2.0
33

4-
from marshmallow import Schema, fields
4+
from marshmallow import Schema, fields, EXCLUDE
55
from typing import List
66

77

@@ -24,6 +24,6 @@ def __init__(self, columns: List = [], data: List = [], error_text: str = '') ->
2424

2525

2626
class PreviewDataSchema(Schema):
27-
columns = fields.Nested(ColumnItemSchema, many=True)
27+
columns = fields.Nested(ColumnItemSchema, many=True, unknown=EXCLUDE)
2828
data = fields.List(fields.Dict, many=True)
2929
error_text = fields.Str()

‎amundsen_application/models/user.py

+2-7
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,11 @@ def load_user(user_data: Dict) -> User:
2828
# in the user metadata.
2929
if _str_no_value(user_data.get('profile_url')) and app.config['GET_PROFILE_URL']:
3030
user_data['profile_url'] = app.config['GET_PROFILE_URL'](user_data['user_id'])
31-
data, errors = schema.load(user_data)
32-
return data
31+
return schema.load(user_data)
3332
except ValidationError as err:
3433
return err.messages
3534

3635

3736
def dump_user(user: User) -> Dict:
3837
schema = UserSchema()
39-
try:
40-
data, errors = schema.dump(user)
41-
return data
42-
except ValidationError as err:
43-
return err.messages
38+
return schema.dump(user)

‎requirements.txt

+2-7
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,7 @@ requests==2.25.1
5656
# A lightweight library for converting complex datatypes to and from native Python datatypes.
5757
# License: MIT
5858
# Upstream url: https://github.com/marshmallow-code/marshmallow
59-
marshmallow>=2.15.3,<3.0
60-
61-
# Allows declaring marshmallow schema through type annotations
62-
# License: MIT
63-
# Upstream url: https://github.com/justanr/marshmallow-annotations
64-
marshmallow-annotations>=2.4.0,<3.0
59+
marshmallow>=3.0,<=3.6
6560

6661
# A utility library for mocking out the requests Python library.
6762
# License: Apache 2.0
@@ -73,7 +68,7 @@ SQLAlchemy==1.3.23
7368

7469
# A common package that holds the models deifnition and schemas that are used
7570
# accross different amundsen repositories.
76-
amundsen-common==0.6.0
71+
amundsen-common==0.9.0
7772

7873
# Library for rest endpoints with Flask
7974
# Upstream url: https://github.com/flask-restful/flask-restful

‎setup.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ def build_js() -> None:
3737
with open(requirements_path) as requirements_file:
3838
requirements = requirements_file.readlines()
3939

40-
__version__ = '3.5.1'
40+
__version__ = '3.6.0'
4141

4242
oicd = ['flaskoidc==0.1.1']
4343
pyarrrow = ['pyarrow==3.0.0']
44-
bigquery_preview = ['google-cloud-bigquery>=2.8.0,<3.0.0', 'flatten-dict==0.3.0']
44+
bigquery_preview = ['google-cloud-bigquery>=2.13.1,<3.0.0', 'flatten-dict==0.3.0']
4545
all_deps = requirements + oicd + pyarrrow + bigquery_preview
4646

4747
setup(

‎tests/unit/base/test_announcement_client.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def test_get_posts_success(self) -> None:
7575
{
7676
'title': 'Test Title',
7777
'date': 'December 31, 1999',
78-
'info_list': ['Test announcement'],
78+
'html_content': 'content',
7979
}
8080
]
8181
response = MockClient(success_posts)._get_posts()

‎tests/unit/base/test_superset_preview_client.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ def test_post_sql_json_incorrect_data_shape(self) -> None:
135135
def test_post_sql_json_correct_data_shape(self) -> None:
136136
"""
137137
Test post_sql_json(), which should result in
138-
a response with 500 error and empty preview_data payload
138+
a response with 200 status and correct preview_data payload
139139
:return:
140140
"""
141141
with app.test_request_context():

‎tests/unit/models/test_user.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import flask
55
import unittest
66

7+
from marshmallow import ValidationError
8+
79
from amundsen_application.models.user import load_user, dump_user, UserSchema
810

911
app = flask.Flask(__name__)
@@ -76,17 +78,17 @@ def test_raise_error_if_no_display_name(self) -> None:
7678
:return:
7779
"""
7880
with app.test_request_context():
79-
data, errors = UserSchema().load({})
80-
self.assertEqual(len(errors['_schema']), 1)
81+
with self.assertRaises(ValidationError):
82+
UserSchema().load({})
8183

8284
def test_raise_error_if_no_user_id(self) -> None:
8385
"""
8486
Error is raised if deserialization of Dict will not generate a user_id
8587
:return:
8688
"""
8789
with app.test_request_context():
88-
data, errors = UserSchema().load({'display_name': 'Test User'})
89-
self.assertEqual(len(errors['_schema']), 1)
90+
with self.assertRaises(ValidationError):
91+
UserSchema().load({'display_name': 'Test User'})
9092

9193
def test_str_no_value(self) -> None:
9294
"""

0 commit comments

Comments
 (0)
Failed to load comments.