diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..94d110b --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,37 @@ +name: "CodeQL" +on: + workflow_dispatch: + #push: + # branches: [master] + #pull_request: + # branches: [master] + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: ["python"] + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + queries: security-and-quality + + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..70c049d --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,80 @@ +name: Unit tests + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + name: Python ${{ matrix.env.python }} | ${{ matrix.env.TOXENV }} + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + env: + - python: 2.7 + TOXENV: py27-django18-drf2 + - python: 2.7 + TOXENV: py27-django18-drf36 + - python: 2.7 + TOXENV: py27-django111-drf2 + - python: 2.7 + TOXENV: py27-django111-drf36 + + - python: 3.7 + TOXENV: py37-django21-drf3 + - python: 3.7 + TOXENV: py37-django22-drf3 + - python: 3.7 + TOXENV: py37-django30-drf3 + + - python: 3.8 + TOXENV: py38-django21-drf3 + - python: 3.8 + TOXENV: py38-django22-drf3 + - python: 3.8 + TOXENV: py38-django30-drf3 + - python: 3.8 + TOXENV: py38-django31-drf3 + - python: 3.8 + TOXENV: py38-django32-drf3 + + - python: 3.9 + TOXENV: py39-django22-drf3 + - python: 3.9 + TOXENV: py39-django30-drf3 + - python: 3.9 + TOXENV: py39-django31-drf3 + - python: 3.9 + TOXENV: py39-django32-drf3 + - python: 3.9 + TOXENV: py39-django40-drf3 + - python: 3.9 + TOXENV: py39-django41-drf3 + - python: 3.9 + TOXENV: py39-django42-drf3 + + - python: '3.10' + TOXENV: py310-django32-drf3 + - python: '3.10' + TOXENV: py310-django40-drf3 + - python: '3.10' + TOXENV: py310-django41-drf3 + - python: '3.10' + TOXENV: py310-django42-drf3 + + + steps: + - uses: actions/checkout@v2 + - name: setup python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.env.python }} + - name: Install tox + run: pip install tox + - name: Run Tests + env: + TOXENV: django${{ matrix.env.TOXENV }} + run: tox -e ${{ matrix.env.TOXENV }} diff --git a/.gitignore b/.gitignore index 0d1cffe..54f9f05 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ build/ *.egg-info/ *~ +/env*/ /.settings/ /.pydevproject /.project diff --git a/requirements.txt b/requirements.txt index c19b423..f3d8005 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,2 @@ Django>=1.3 djangorestframework -six>=1.4.1 -unicodecsv diff --git a/rest_framework_csv/parsers.py b/rest_framework_csv/parsers.py index c083d71..77f309c 100644 --- a/rest_framework_csv/parsers.py +++ b/rest_framework_csv/parsers.py @@ -1,8 +1,6 @@ -import unicodecsv as csv -#import csv +import csv import codecs import io -import six from django.conf import settings from rest_framework.parsers import BaseParser @@ -10,11 +8,6 @@ from rest_framework_csv.orderedrows import OrderedRows -def unicode_csv_reader(csv_data, dialect=csv.excel, charset='utf-8', **kwargs): - csv_reader = csv.reader(csv_data, dialect=dialect, encoding=charset, **kwargs) - for row in csv_reader: - yield row - def universal_newlines(stream): # It's possible that the stream was not opened in universal # newline mode. If not, we may have a single "row" that has a @@ -40,9 +33,9 @@ def parse(self, stream, media_type=None, parser_context=None): encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) try: - strdata = stream.read() - binary = universal_newlines(strdata) - rows = unicode_csv_reader(binary, delimiter=delimiter, charset=encoding) + strdata = stream.read().decode(encoding) + lines = universal_newlines(strdata) + rows = csv.reader(lines, dialect=csv.excel, delimiter=delimiter) data = OrderedRows(next(rows)) for row in rows: row_data = dict(zip(data.header, row)) diff --git a/rest_framework_csv/renderers.py b/rest_framework_csv/renderers.py index 9d0aaf9..ff05291 100644 --- a/rest_framework_csv/renderers.py +++ b/rest_framework_csv/renderers.py @@ -1,9 +1,9 @@ from __future__ import unicode_literals import codecs -import unicodecsv as csv +import csv from django.conf import settings from rest_framework.renderers import * -from six import BytesIO, text_type +from io import StringIO from rest_framework_csv.orderedrows import OrderedRows from rest_framework_csv.misc import Echo from types import GeneratorType @@ -11,13 +11,6 @@ from logging import getLogger log = getLogger(__name__) -# six versions 1.3.0 and previous don't have PY2 -try: - from six import PY2 -except ImportError: - import sys - PY2 = sys.version_info[0] == 2 - class CSVRenderer(BaseRenderer): """ @@ -52,12 +45,12 @@ def render(self, data, media_type=None, renderer_context={}, writer_opts=None): encoding = renderer_context.get('encoding', settings.DEFAULT_CHARSET) table = self.tablize(data, header=header, labels=labels) - csv_buffer = BytesIO() - csv_writer = csv.writer(csv_buffer, encoding=encoding, **writer_opts) + csv_buffer = StringIO() + csv_writer = csv.writer(csv_buffer, **writer_opts) for row in table: csv_writer.writerow(row) - return csv_buffer.getvalue() + return csv_buffer.getvalue().encode(encoding) def tablize(self, data, header=None, labels=None): """ @@ -156,7 +149,7 @@ def nest_flat_item(self, flat_item, prefix): def flatten_list(self, l): flat_list = {} for index, item in enumerate(l): - index = text_type(index) + index = str(index) flat_item = self.flatten_item(item) nested_item = self.nest_flat_item(flat_item, index) flat_list.update(nested_item) @@ -165,7 +158,7 @@ def flatten_list(self, l): def flatten_dict(self, d): flat_dict = {} for key, item in d.items(): - key = text_type(key) + key = str(key) flat_item = self.flatten_item(item) nested_item = self.nest_flat_item(flat_item, key) flat_dict.update(nested_item) @@ -230,9 +223,9 @@ def render(self, data, media_type=None, renderer_context={}): table = self.tablize(data, header=header, labels=labels) csv_buffer = Echo() - csv_writer = csv.writer(csv_buffer, encoding=encoding, **writer_opts) + csv_writer = csv.writer(csv_buffer, **writer_opts) for row in table: - yield csv_writer.writerow(row) + yield csv_writer.writerow(row).encode(encoding) class PaginatedCSVRenderer (CSVRenderer): diff --git a/rest_framework_csv/tests.py b/rest_framework_csv/tests.py index 75d071c..68ff047 100644 --- a/rest_framework_csv/tests.py +++ b/rest_framework_csv/tests.py @@ -2,7 +2,8 @@ from __future__ import unicode_literals import csv -from six import BytesIO, PY3 +import sys +from io import BytesIO from types import GeneratorType from django.test import TestCase @@ -134,8 +135,8 @@ def test_render_data_with_writer_opts_set_via_CSVRenderer(self): data = [{'a': 'test', 'b': 'hello'}, {'a': 'foo', 'b': 'bar'}] writer_opts = { 'quoting': csv.QUOTE_ALL, - 'quotechar': '|' if PY3 else b'|', - 'delimiter': ';' if PY3 else b';', + 'quotechar': '|', + 'delimiter': ';', } renderer.writer_opts = writer_opts dump = renderer.render(data) @@ -149,8 +150,8 @@ def test_render_data_with_writer_opts_set_via_renderer_context(self): data = [{'a': 'test', 'b': 'hello'}, {'a': 'foo', 'b': 'bar'}] writer_opts = { 'quoting': csv.QUOTE_ALL, - 'quotechar': '|' if PY3 else b'|', - 'delimiter': ';' if PY3 else b';', + 'quotechar': '|', + 'delimiter': ';', } dump = renderer.render(data, renderer_context={'writer_opts': writer_opts}) self.assertEquals(dump.count(b';'), 3) @@ -246,7 +247,7 @@ def test_parse_file_with_only_carriage_returns(self): parser = CSVParser() - with open(CSVFILE, 'rbU') as csv_file: + with open(CSVFILE, 'rbU' if sys.version_info <= (3, 10) else 'rb') as csv_file: data = parser.parse(csv_file) self.assertEqual(data, [{'Name': 'Kathryn Miller', 'ID': '67', 'Country': 'United States'}, {'Name': 'Jen Mark', 'ID': '78', 'Country': 'Canada'}]) diff --git a/setup.py b/setup.py index 41f109b..b937693 100755 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ author = 'Mjumbe Wawatu Ukweli' author_email = 'mjumbewu@gmail.com' license = 'BSD' -install_requires = ['djangorestframework', 'six', 'unicodecsv'] +install_requires = ['djangorestframework'] def get_version(package): @@ -77,12 +77,12 @@ def get_package_data(package): "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.3", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Framework :: Django", ], ) diff --git a/tox.ini b/tox.ini index cdf1847..d911a0b 100644 --- a/tox.ini +++ b/tox.ini @@ -1,16 +1,20 @@ [tox] -envlist = py27-django{18,111}-drf{2,36}, - py35-django{18,111}-drf{2,36}, - py36-django{111,21,22}-drf3 +envlist = py36-django{111,21,22}-drf3 + py37-django{21,22,30}-drf3 + py38-django{21,22,30,31,32}-drf3 + py39-django{22,30,31,32,41,42}-drf3 + py310-django{32,40,41,42}-drf3 [testenv] commands = python manage.py test deps = six>=1.4.1 - django18: Django>=1.8,<1.9 django111: Django>=1.11,<2.0 django21: Django>=2.1,<2.2 django22: Django>=2.2,<3 - drf2: djangorestframework>=2,<3 - drf36: djangorestframework>=3,<3.7 + django30: Django>=3.0,<3.1 + django31: Django>=3.1,<3.2 + django32: Django>=3.2,<3.3 + django41: Django>=4.1,<4.2 + django42: Django>=4.2,<4.3 drf3: djangorestframework>=3,<4