diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f0e37df..799a7cb 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -12,10 +12,10 @@ jobs: fetch-depth: 0 - name: Install dependencies run: | - pip install -r requirements.txt + pip install tox - name: Run Tests run: | - python manage.py test + tox - name: Build run: | python setup.py sdist bdist_wheel diff --git a/.gitignore b/.gitignore index 2932225..4cd5d42 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,8 @@ dist/ # virtualenv .venv + +.tox/ + +demofile.txt +demofile2.txt \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 950b2b9..da2ee92 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -14,6 +14,18 @@ ], "django": true, "justMyCode": false + }, + { + "name": "Python: tox", + "type": "python", + "request": "launch", + "module": "tox", + "args": [ + "-e", + "drf-spectacular27" + ], + "django": true, + "justMyCode": false } ] } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index ef7e71f..cf31d3b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,8 +12,8 @@ ], "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.fixAll": true, - "source.organizeImports": true + "source.fixAll": "explicit", + "source.organizeImports": "explicit" }, "files.associations": { "*.yaml": "home-assistant" diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d2ec33..e4326f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,18 @@ Note that in line with [Django REST framework policy](https://www.django-rest-fr any parts of the framework not mentioned in the documentation should generally be considered private API, and may be subject to change. +## [0.4.0] - 2023-12-12 + + +### Fixed + +- `ordered` helper function in test suite, which now successfully replace line breaks + +### Added + +- adds support for drf-spectacular versions above 0.25 +- adds support for drf-spectacular versions above 0.25 + ## [0.3.3] - 2023-11-07 ### Fixed diff --git a/README.rst b/README.rst index c1f9cf8..295030b 100644 --- a/README.rst +++ b/README.rst @@ -4,6 +4,29 @@ drf-spectecular-json-api open api 3 schema generator for `drf-json-api `_ package based on `drf-spectacular `__ package. +Tested with various dependency version +-------------------------------------- + +.. list-table:: Tested for versions in combination: + :widths: 25 25 50 + :header-rows: 1 + + * - python + - django + - drf-spectacular + * - 3.8 + - 4.0 + - 0.25.x + * - 3.9 + - 4.1 + - 0.26.x + * - 3.10 + - 4.2 + - 0.27.x + * - 3.11 + - 5.0 + - + Installation ------------ diff --git a/drf_spectacular_jsonapi/__init__.py b/drf_spectacular_jsonapi/__init__.py index 80eb7f9..abeeedb 100644 --- a/drf_spectacular_jsonapi/__init__.py +++ b/drf_spectacular_jsonapi/__init__.py @@ -1 +1 @@ -__version__ = '0.3.3' +__version__ = '0.4.0' diff --git a/drf_spectacular_jsonapi/apps.py b/drf_spectacular_jsonapi/apps.py index 64ad54d..339d34b 100644 --- a/drf_spectacular_jsonapi/apps.py +++ b/drf_spectacular_jsonapi/apps.py @@ -4,3 +4,6 @@ class DRFSpectacularJsonApiConfig(AppConfig): name = 'drf_spectacular_jsonapi' verbose_name = "drf-spectacular-jsonapi" + + def ready(self): + import drf_spectacular_jsonapi.openapi # noqa: F408 diff --git a/drf_spectacular_jsonapi/schemas/openapi.py b/drf_spectacular_jsonapi/schemas/openapi.py index 542a321..7ac5425 100644 --- a/drf_spectacular_jsonapi/schemas/openapi.py +++ b/drf_spectacular_jsonapi/schemas/openapi.py @@ -1,9 +1,12 @@ from typing import Dict, List, Tuple from django.utils.translation import gettext_lazy as _ +from drf_spectacular.contrib.django_filters import DjangoFilterExtension +from drf_spectacular.drainage import add_trace_message from drf_spectacular.openapi import AutoSchema from drf_spectacular.plumbing import (ResolvedComponent, build_array_type, - build_parameter_type, is_list_serializer) + build_parameter_type, get_manager, + get_view_model, is_list_serializer) from rest_framework_json_api.serializers import SparseFieldsetsMixin from rest_framework_json_api.utils import (format_field_name, get_resource_name, @@ -14,6 +17,20 @@ from drf_spectacular_jsonapi.schemas.utils import get_primary_key_of_serializer +class DjangoJsonApiFilterExtension(DjangoFilterExtension): + target_class = 'rest_framework_json_api.django_filters.backends.DjangoFilterBackend' + priority = 1 + + def resolve_filter_field(self, *args, **kwargs): + result = super().resolve_filter_field(*args, **kwargs) + for item in result: + name = item["name"] + if "filter[" not in name: + name = f"filter[{name}]" + item["name"] = name + return result + + class JsonApiAutoSchema(AutoSchema): """ Extend DRF's spectacular AutoSchema for JSON:API serialization. @@ -36,6 +53,7 @@ def _get_filter_parameters(self) -> Dict: See also json:api docs: https://jsonapi.org/format/#fetching-sorting """ parameters = super()._get_filter_parameters() + sort_param = next( (parameter for parameter in parameters if parameter["name"] == "sort"), None) if sort_param and hasattr(self.view, "ordering_fields") and self.view.ordering_fields: diff --git a/requirements/base.txt b/requirements/base.txt index 1b0485a..0d588a0 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,5 +1,5 @@ Django>=3.2 +drf-spectacular>=0.26.1 +drf-extensions>=0.7.1 djangorestframework>=3.13 -djangorestframework-jsonapi>=6.0.0 -drf-spectacular==0.25.1 -drf-extensions==0.7.1 \ No newline at end of file +djangorestframework-jsonapi>=6.0.0 \ No newline at end of file diff --git a/requirements/testing.txt b/requirements/testing.txt index 5edc785..d4b20de 100644 --- a/requirements/testing.txt +++ b/requirements/testing.txt @@ -1,11 +1,4 @@ -pytest>=5.3.5 -pytest-django>=3.8.0 -pytest-cov>=2.8.1 flake8==6.0.0 -mypy>=0.770 -django-stubs>=1.8.0,<1.10.0 -djangorestframework-stubs>=1.1.0 -types-PyYAML>=0.1.6 -isort>=5.0.4 autopep8==2.0.0 -djangorestframework-jsonapi[django-filter]==6.0.0 \ No newline at end of file +djangorestframework-jsonapi[django-filter]>=6.0.0 +tox>=4.11.4 \ No newline at end of file diff --git a/setup.py b/setup.py index 78b29b2..cef5a6e 100755 --- a/setup.py +++ b/setup.py @@ -15,9 +15,6 @@ with open("README.rst", "r", encoding="utf-8") as fh: long_description = fh.read() -with open('requirements/base.txt') as fh: - requirements = [r for r in fh.read().split('\n') if not r.startswith('#')] - def get_version(package): """ @@ -43,7 +40,13 @@ def get_version(package): packages=[p for p in find_namespace_packages( exclude=('tests*',)) if p.startswith(package)], include_package_data=True, - install_requires=requirements, + install_requires=[ + "Django>=3.2", + "drf-spectacular>=0.25.0", + "drf-extensions>=0.7.1", + "djangorestframework>=3.13", + "djangorestframework-jsonapi>=6.0.0" + ], classifiers=[ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", diff --git a/tests/settings.py b/tests/settings.py index ed9d32f..c4382a5 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -27,6 +27,8 @@ 'PREPROCESSING_HOOKS': [ "drf_spectacular_jsonapi.hooks.fix_nested_path_parameters" ], + # drf-spectacular >0.26 added this option which is default True. This feature is not needed and in my pov not best practice to add enum choices... + "ENUM_GENERATE_CHOICE_DESCRIPTION": False } diff --git a/tests/tests_schema.py b/tests/tests_schema.py index be864bd..30f48b5 100644 --- a/tests/tests_schema.py +++ b/tests/tests_schema.py @@ -1,3 +1,6 @@ +import json + +import rest_framework_json_api from django.test.testcases import SimpleTestCase from drf_spectacular.generators import SchemaGenerator from drf_spectacular.validation import validate_schema @@ -12,8 +15,12 @@ def ordered(self, obj): return sorted((k, self.ordered(v)) for k, v in obj.items()) if isinstance(obj, list): return sorted(self.ordered(x) for x in obj) + if isinstance(obj, tuple): + # nested call + for item in obj: + self.ordered(item) if isinstance(obj, str): - obj.replace("\n", "") + obj = obj.replace("\n", "") return obj def setUp(self) -> None: @@ -23,6 +30,8 @@ def setUp(self) -> None: # make sure generated schemas are always valid validate_schema(self.schema) + self.json_api_site_package_version = rest_framework_json_api.VERSION + class TestSchemaOutputForSimpleModelSerializer(SimpleSchemaTestCase): @@ -191,7 +200,7 @@ def test_get_parameters(self): 'in': 'query', 'name': 'sort', 'required': False, - 'description': 'Which field to use when ordering the results.', + 'description': 'Which field to use when ordering the results.' if self.json_api_site_package_version == "6.0.0" else '[list of fields to sort by](https://jsonapi.org/format/#fetching-sorting)', 'schema': {'type': 'array', 'items': {'type': 'string', 'enum': ['id', 'title', '-id', '-title']}}, 'explode': False }, @@ -205,18 +214,16 @@ def test_get_parameters(self): { 'in': 'query', 'name': 'filter[genre]', - 'required': False, - 'description': 'genre', - 'schema': {'type': 'string', 'enum': ["POP", "ROCK"]} + 'description': 'Wich kind of genre this Album represents\n', + 'schema': {'type': 'string', 'title': 'Nice Genre', 'enum': ["POP", "ROCK"]} }, { 'in': 'query', - 'name': 'filter[title.contains]', - 'required': False, - 'description': 'title__contains', + 'name': 'filter[title__contains]', 'schema': {'type': 'string'} } ]) + self.assertEqual(expected, calculated) def test_post_request_body(self): @@ -226,12 +233,10 @@ def test_post_request_body(self): "#/components/schemas/AlbumRequest" ) - calculated = self.ordered( - self.schema["components"]["schemas"]["AlbumRequest"]) - expected = self.ordered( - { - "type": "object", - "properties": { + calculated = self.schema["components"]["schemas"]["AlbumRequest"] + expected = { + "type": "object", + "properties": { "data": { "type": "object", "properties": { @@ -309,11 +314,12 @@ def test_post_request_body(self): "required": ["type"], "additionalProperties": False } - }, - "required": ["data"], - } - ) - self.assertEqual(expected, calculated) + }, + "required": ["data"], + } + + self.assertJSONEqual(json.dumps( + calculated, sort_keys=True), json.dumps(expected, sort_keys=True)) self.assertEqual( self.schema["paths"]["/songs/"]["post"]["requestBody"]["content"]["application/vnd.api+json"]["schema"]["$ref"], @@ -413,7 +419,8 @@ def test_post_request_body(self): "required": ["data"], } ) - self.assertEqual(expected, calculated) + self.assertJSONEqual(json.dumps( + calculated, sort_keys=True), json.dumps(expected, sort_keys=True)) def test_patch_request_body(self): """Tests if the request body matches the json:api payload schema""" diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..8138fe7 --- /dev/null +++ b/tox.ini @@ -0,0 +1,25 @@ +[tox] +requires = + tox>=4 +env_list = + py{38,39,310,311}-django{40,41,42,50}-drf-spectacular{25,26,27} + +[testenv] +description = run unit tests +deps= + drf-spectacular27: drf-spectacular>=0.27.0,<0.28.0 + drf-spectacular26: drf-spectacular>=0.26.0,<0.27.0 + drf-spectacular25: drf-spectacular>=0.25.0,<0.26.0 + django50: Django>=5.0,<5.1 + django42: Django>=4.2,<4.3 + django41: Django>=4.1,<4.2 + django40: Django>=4.0,<4.1 + djangorestframework>=3.13 + djangorestframework-jsonapi>=6.0.0 + djangorestframework-jsonapi[django-filter]>=6.0.0 + +setenv = + PYTHONPATH = {toxinidir} + +commands = + python manage.py test {posargs} \ No newline at end of file