Skip to content
This repository was archived by the owner on Jun 26, 2025. It is now read-only.

Commit f456358

Browse files
Merge pull request #307 from globality-corp/release/3.6.3
release/3.6.3
2 parents 52b875c + 52f1141 commit f456358

File tree

7 files changed

+37
-16
lines changed

7 files changed

+37
-16
lines changed

.bumpversion.cfg

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
[bumpversion]
2-
current_version = 3.6.2
2+
current_version = 3.6.3
33
commit = False
44
tag = False
55

66
[bumpversion:file:setup.py]
77
search = version = "{current_version}"
88
replace = version = "{new_version}"
9+

microcosm_flask/formatting/csv_formatter.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
from csv import QUOTE_MINIMAL, writer
66
from io import StringIO
77

8+
from werkzeug import Response
9+
from werkzeug.utils import get_content_type
10+
811
from microcosm_flask.formatting.base import BaseFormatter
12+
from microcosm_flask.formatting.encoding import UTF_8, UTF_8_SIG
913

1014

1115
class CSVFormatter(BaseFormatter):
@@ -23,6 +27,17 @@ def build_headers(self, headers, **kwargs):
2327

2428
return headers
2529

30+
def build_response(self, response_data):
31+
response = Response(
32+
self.format(response_data),
33+
content_type=get_content_type(self.content_type, UTF_8)
34+
)
35+
36+
# start the output with U+FEFF BYTE ORDER MARK
37+
# to signal to Excel to import the text file as UTF-8 rather than a legacy encoding
38+
response.charset = UTF_8_SIG
39+
return response
40+
2641
def get_column_names(self, list_response_data):
2742
response_fields = list(list_response_data[0].keys())
2843

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
UTF_8 = "utf-8"
2+
UTF_8_SIG = "utf-8-sig"

microcosm_flask/tests/conventions/test_csv.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from microcosm_flask.conventions.crud import configure_crud
2222
from microcosm_flask.enums import ResponseFormats
2323
from microcosm_flask.fields import EnumField, QueryStringList
24+
from microcosm_flask.formatting.encoding import UTF_8_SIG
2425
from microcosm_flask.namespaces import Namespace
2526
from microcosm_flask.operations import Operation
2627
from microcosm_flask.paging import OffsetLimitPageSchema
@@ -91,7 +92,7 @@ def assert_csv_response(self, response, status_code, expected_lines=None):
9192
if status_code == 204:
9293
response_lines = None
9394
else:
94-
response_lines = [row for row in reader(StringIO(response.data.decode("utf-8")))]
95+
response_lines = [row for row in reader(StringIO(response.data.decode(UTF_8_SIG)))]
9596

9697
# validate data if provided
9798
assert_that(

microcosm_flask/tests/formatting/test_csv_formatter.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
Test CSV formatting.
33
44
"""
5+
from codecs import BOM_UTF8
6+
57
from hamcrest import (
68
assert_that,
79
contains_inanyorder,
@@ -22,14 +24,14 @@ def test_make_response():
2224
dict(foo="baz"),
2325
]))
2426

25-
assert_that(response.data, is_(equal_to(b"foo\r\nbar\r\nbaz\r\n")))
27+
assert_that(response.data, is_(equal_to(BOM_UTF8 + b"foo\r\nbar\r\nbaz\r\n")))
2628
assert_that(response.content_type, is_(equal_to("text/csv; charset=utf-8")))
2729
assert_that(response.headers, contains_inanyorder(
2830
("Content-Disposition", "attachment; filename=\"response.csv\""),
2931
("Content-Type", "text/csv; charset=utf-8"),
3032
("ETag", etag_for(
31-
md5_hash='"a2ead3516dd1be4a3c7f45716c0a0eb7"',
32-
spooky_hash='"02dee263db4f9326a3fbee9135939717"',
33+
md5_hash='"d0366f8e71095c1b68e2ddfd551b3285"',
34+
spooky_hash='"e264d2b6b13c5298cb2059716018aa4d"',
3335
)),
3436
))
3537

@@ -42,14 +44,14 @@ def test_make_response_tuples():
4244
("d", "e", "f"),
4345
]))
4446

45-
assert_that(response.data, is_(equal_to(b"a,b,c\r\nd,e,f\r\n")))
47+
assert_that(response.data, is_(equal_to(BOM_UTF8 + b"a,b,c\r\nd,e,f\r\n")))
4648
assert_that(response.content_type, is_(equal_to("text/csv; charset=utf-8")))
4749
assert_that(response.headers, contains_inanyorder(
4850
("Content-Disposition", "attachment; filename=\"response.csv\""),
4951
("Content-Type", "text/csv; charset=utf-8"),
5052
("ETag", etag_for(
51-
md5_hash='"eb8bde290633452402b37aa580ca30e9"',
52-
spooky_hash='"63b989eb36315937ef68206fb9fc3104"',
53+
md5_hash='"22252aa7c314539c78dfa19dbf9af674"',
54+
spooky_hash='"37cd063df88efefe1929f3cf4532b718"',
5355
)),
5456
))
5557

@@ -62,14 +64,14 @@ def test_make_response_list():
6264
["d", "e", "f"],
6365
]))
6466

65-
assert_that(response.data, is_(equal_to(b"a,b,c\r\nd,e,f\r\n")))
67+
assert_that(response.data, is_(equal_to(BOM_UTF8 + b"a,b,c\r\nd,e,f\r\n")))
6668
assert_that(response.content_type, is_(equal_to("text/csv; charset=utf-8")))
6769
assert_that(response.headers, contains_inanyorder(
6870
("Content-Disposition", "attachment; filename=\"response.csv\""),
6971
("Content-Type", "text/csv; charset=utf-8"),
7072
("ETag", etag_for(
71-
md5_hash='"eb8bde290633452402b37aa580ca30e9"',
72-
spooky_hash='"63b989eb36315937ef68206fb9fc3104"',
73+
md5_hash='"22252aa7c314539c78dfa19dbf9af674"',
74+
spooky_hash='"37cd063df88efefe1929f3cf4532b718"',
7375
)),
7476
))
7577

@@ -85,13 +87,13 @@ def test_make_response_ordered():
8587
)
8688
]))
8789

88-
assert_that(response.data, is_(equal_to(b"id,firstName,lastName\r\nme,First,Last\r\n")))
90+
assert_that(response.data, is_(equal_to(BOM_UTF8 + b"id,firstName,lastName\r\nme,First,Last\r\n")))
8991
assert_that(response.content_type, is_(equal_to("text/csv; charset=utf-8")))
9092
assert_that(response.headers, contains_inanyorder(
9193
("Content-Disposition", "attachment; filename=\"response.csv\""),
9294
("Content-Type", "text/csv; charset=utf-8"),
9395
("ETag", etag_for(
94-
md5_hash='"4480bd6748cf93740490ebeee7eae1fe"',
95-
spooky_hash='"0a7f40b47efb0a197b180444c4911b17"',
96+
md5_hash='"79e41b38792fdca793f61791ef55e026"',
97+
spooky_hash='"994df8aa1632af103265ebeae37a3804"',
9698
)),
9799
))

microcosm_flask/tests/formatting/test_json_formatter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,6 @@ def test_make_response():
2828
("Content-Length", "14"),
2929
("ETag", etag_for(
3030
md5_hash='"2f8acf3fe5e5c2839a04b7677d9399b8"',
31-
spooky_hash='"af072b51e1eb2a8d7b2ab84dab972674"',
31+
spooky_hash='"053dd24aa81b8b2c3243143a14834d37"',
3232
)),
3333
))

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44

55
project = "microcosm-flask"
6-
version = "3.6.2"
6+
version = "3.6.3"
77

88

99
setup(

0 commit comments

Comments
 (0)