Compare commits

..

25 Commits

Author SHA1 Message Date
Cristi Vijdea d2bd838325 Add 1.13.0 changelog 2019-01-29 09:11:28 +02:00
Cristi Vijdea bacab20f0b Update swagger-ui to 3.20.5 and ReDoc to 2.0.0-rc.2 2019-01-29 09:05:26 +02:00
Cristi Vijdea df82fe59d7 Remove recommandations for unmaintained flex library
Fixes #285
2019-01-29 09:04:10 +02:00
Cristi Vijdea 7c5a0b7176 Promote deprecation warning to exception 2019-01-29 08:51:02 +02:00
Cristi Vijdea 69a1e62ed3 Add x-nullable to Optional 2019-01-29 08:46:33 +02:00
Alexander Egorov 3806d6efd5 Add support for custom and collection type hint classes (#272) 2019-01-29 08:39:29 +02:00
Dima Boger 58e6dae548 Fix typo with quotes in security doc (#300) 2019-01-28 23:14:33 +02:00
Cristi Vîjdea 8e2228fe5f Fix py37 test 2019-01-14 14:58:16 +02:00
Cristi Vîjdea 762467285c Keep dict key order for Python 3.7 2019-01-14 14:25:10 +02:00
Cristi Vîjdea 0e62fd6f2b Update copyright year 2019-01-14 14:25:10 +02:00
Daniel Hahler e266eeda60 Fi doc typo (#286) 2019-01-08 12:38:08 +02:00
Cristi Vîjdea 4b1098369c Add check to prevent build of docs with bad version 2019-01-03 20:36:12 +02:00
Cristi Vîjdea bda545e85f Inline coverage.sh 2018-12-30 16:14:05 +02:00
Cristi Vîjdea c1d3d4fe3c Add .readthedocs.yml 2018-12-29 17:44:29 +02:00
Cristi Vîjdea 7f3ffe80a9 Use call_view_method for get_serializer_class 2018-12-29 17:42:05 +02:00
Cristi Vîjdea 1fe8c2c03c Add 1.12.1 changelog 2018-12-28 16:26:17 +02:00
Cristi Vîjdea b8512bda8e Fix call_view_method warnings for view classes 2018-12-28 16:20:37 +02:00
Cristi Vîjdea 2bc9addc99 Fix crash with empty generator class name 2018-12-28 09:34:54 +02:00
Cristi Vîjdea 6df2362156 Use DEFAULT_VERSION in management command 2018-12-28 01:37:17 +02:00
Cristi Vîjdea e5a569ebf7 Add extension points to management command 2018-12-28 01:37:13 +02:00
Cristi Vîjdea 470c993b98 Update ReDoc to 2.0.0-rc.0 2018-12-25 18:23:19 +02:00
johnthagen a5e4386f38 Fix monospace formatting typo (#283) 2018-12-24 20:34:12 +02:00
johnthagen 6bd91faa5d Fix minor spelling typo (#282) 2018-12-24 20:33:24 +02:00
Cristi Vîjdea 667c9c1002 Add supported versions note 2018-12-24 07:57:50 +02:00
Cristi Vîjdea 8b0da2607f Add pretty and media_type options to codecs 2018-12-24 07:55:17 +02:00
27 changed files with 395 additions and 187 deletions
+1 -1
View File
@@ -44,4 +44,4 @@
<option name="projectConfiguration" value="pytest" />
<option name="PROJECT_TEST_RUNNER" value="pytest" />
</component>
</module>
</module>
+1 -1
View File
@@ -73,4 +73,4 @@
<component name="PythonCompatibilityInspectionAdvertiser">
<option name="version" value="3" />
</component>
</project>
</project>
+15
View File
@@ -0,0 +1,15 @@
requirements_file: requirements/docs.txt
build:
image: latest
python:
version: 3.6
setup_py_install: false
pip_install: true # need this for correct pyproject.toml handling
extra_requirements:
- validation
# extra formats in addition to the default HTML web docs
formats:
- pdf
+5 -5
View File
@@ -16,10 +16,9 @@ jobs:
dist: xenial
sudo: required
- python: '3.6'
env: TOXENV=djmaster
- # readthedocs uses python 3.5 for building
python: '3.5'
env: TOXENV=docs
- python: '3.6'
env: TOXENV=djmaster
- python: '3.6'
env: TOXENV=lint
@@ -59,8 +58,9 @@ script:
after_success:
- |
if [[ -z "$TOXENV" && -z "$PYPI_DEPLOY" ]]; then
chmod +x coverage.sh
./coverage.sh
coverage combine || true
coverage report
codecov
fi
branches:
+1 -1
View File
@@ -10,7 +10,7 @@ License
BSD 3-Clause License
********************
Copyright (c) 2018, Cristian V. <cristi@cvjd.me> |br|\ All rights reserved.
Copyright (c) 2017 - 2019, Cristian V. <cristi@cvjd.me> |br|\ All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
+12 -6
View File
@@ -11,10 +11,17 @@ Generate **real** Swagger/OpenAPI 2.0 specifications from a Django Rest Framewor
Compatible with
- **Django Rest Framework**: 3.7.7, 3.8
- **Django Rest Framework**: 3.7.7, 3.8, 3.9
- **Django**: 1.11, 2.0, 2.1
- **Python**: 2.7, 3.4, 3.5, 3.6, 3.7
Only the latest patch version of each ``major.minor`` series of Python, Django and Django REST Framework is supported.
**Only the latest version of drf-yasg is supported.** Support of old versions is dropped immediately with the release
of a new version. Please do not create issues before upgrading to the latest release available at the time. Regression
reports are accepted and will be resolved with a new release as quickly as possible. Removed features will usually go
through a deprecation cycle of a few minor releases.
Resources:
* **Source**: https://github.com/axnsan12/drf-yasg/
@@ -40,8 +47,7 @@ Features
`redoc <https://github.com/Rebilly/ReDoc>`_ for viewing the generated documentation
- schema view is cacheable out of the box
- generated Swagger schema can be automatically validated by
`swagger-spec-validator <https://github.com/Yelp/swagger_spec_validator>`_ or
`flex <https://github.com/pipermerriam/flex>`_
`swagger-spec-validator <https://github.com/Yelp/swagger_spec_validator>`_
- supports Django REST Framework API versioning with ``URLPathVersioning`` and ``NamespaceVersioning``; other DRF
or custom versioning schemes are not currently supported
@@ -159,7 +165,7 @@ a. ``get_schema_view`` parameters
- ``patterns`` - passed to SchemaGenerator
- ``urlconf`` - passed to SchemaGenerator
- ``public`` - if False, includes only endpoints the current user has access to
- ``validators`` - a list of validator names to apply on the generated schema; allowed values are ``flex``, ``ssv``
- ``validators`` - a list of validator names to apply on the generated schema; only ``ssv`` is currently supported
- ``generator_class`` - schema generator class to use; should be a subclass of ``OpenAPISchemaGenerator``
- ``authentication_classes`` - authentication classes for the schema view itself
- ``permission_classes`` - permission classes for the schema view itself
@@ -204,9 +210,9 @@ caching the schema view in-memory, with some sane defaults:
4. Validation
=============
Given the numerous methods to manually customzie the generated schema, it makes sense to validate the result to ensure
Given the numerous methods to manually customize the generated schema, it makes sense to validate the result to ensure
it still conforms to OpenAPI 2.0. To this end, validation is provided at the generation point using python swagger
libraries, and can be activated by passing :python:`validators=['flex', 'ssv']` to ``get_schema_view``; if the generated
libraries, and can be activated by passing :python:`validators=['ssv']` to ``get_schema_view``; if the generated
schema is not valid, a :python:`SwaggerValidationError` is raised by the handling codec.
**Warning:** This internal validation can slow down your server.
-6
View File
@@ -1,6 +0,0 @@
#!/usr/bin/env bash
set -e
coverage combine || true
coverage report
codecov
+24 -1
View File
@@ -2,6 +2,29 @@
Changelog
#########
**********
**1.13.0**
**********
*Release date: Jan 29, 2019*
- **IMPROVED:** type hint inspection is now supported for collections and``Optional`` (:pr:`272`)
- **IMPROVED:** updated ``swagger-ui`` to version 3.20.5
- **IMPROVED:** updated ``ReDoc`` to version 2.0.0-rc.2
- **DEPRECATED:** quietly dropped support for the ``flex`` validator; it will still work if the library is installed,
but the setup.py requirement was removed and the validator will be silently skipped if not installed (:issue:`285`)
**********
**1.12.1**
**********
*Release date: Dec 28, 2018*
- **IMPROVED:** updated ``ReDoc`` to version 2.0.0-rc.0
- **FIXED:** management command will now correctly fall back to ``DEFAULT_VERSION`` for mock request
- **FIXED:** fixed bad "raised exception during schema generation" warnings caused by missing ``self`` parameter
**********
**1.12.0**
**********
@@ -30,7 +53,7 @@ Changelog
Starting with this version, the ``setup_requires`` argument was dropped from ``setup.py`` in favor of
``build-system.requires`` in ``pyproject.toml`` . This means that for correctly building or installing from sdist,
you will need to use a PEP517/PEP518 compliant tool (tox>=3.3.0, setuptools>=40, pip>=10.0, pep517.build) or manually
install the build requirements yourself (just ```setuptools`` and ``setuptools-scm``, for now).
install the build requirements yourself (just ``setuptools`` and ``setuptools-scm``, for now).
Additionally, for correct package version detection, a full git checkout is required when building (this was always the
case). Building without ``.git`` or without ``setuptools-scm`` will result in a distribution with a version like
+5
View File
@@ -48,6 +48,11 @@ author = 'Cristi V.'
# The full version, including alpha/beta/rc tags.
release = get_distribution('drf_yasg').version
if 'noscm' in release:
raise AssertionError('Invalid package version string: %s. \n'
'The documentation must be built with drf_yasg installed from a distribution package, '
'which must have been built with a proper version number (i.e. from a full source checkout).'
% (release,))
# The short X.Y.Z version.
version = '.'.join(release.split('.')[:3])
+1 -1
View File
@@ -81,7 +81,7 @@ A very simple working configuration was provided by :ghuser:`Vigrond`, originall
'type': 'oauth2',
'authorizationUrl': '/yourapp/o/authorize',
'tokenUrl': '/yourapp/o/token/',
'flow": "accessCode',
'flow': 'accessCode',
'scopes': {
'read:groups': 'read groups',
}
+97 -50
View File
@@ -4,9 +4,9 @@
"lockfileVersion": 1,
"dependencies": {
"@babel/runtime": {
"version": "7.1.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.1.5.tgz",
"integrity": "sha512-xKnPpXG/pvK1B90JkwwxSGii90rQGKtzcMt2gI5G6+M0REXaq6rOHsGC2ay6/d0Uje7zzvSzjEzfR3ENhFlrfA==",
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.3.1.tgz",
"integrity": "sha512-7jGW8ppV0ant637pIqAcFfQDDH1orEPGJb8aXfUozuCU3QqX7rX4DA8iwrbPrR1hcH0FTTHz47yQnk+bl5xHQA==",
"requires": {
"regenerator-runtime": "^0.12.0"
}
@@ -19,6 +19,11 @@
"sprintf-js": "~1.0.2"
}
},
"big.js": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
"integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ=="
},
"call-me-maybe": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz",
@@ -57,9 +62,14 @@
"integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg="
},
"dompurify": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-1.0.8.tgz",
"integrity": "sha512-vetRFbN1SXSPfP3ClIiYnxTrXquSqakBEOoB5JESn0SVcSYzpu6ougjakpKnskGctYdlNpwf+riUHSkG7d4XUw=="
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-1.0.9.tgz",
"integrity": "sha512-lt9f3A3RO1OCNaWdA+s/k7YVn0Typ5MbAKmX94PLCZbs8wLNccX3Bj4xXA7GLKOoDb/MeVoAoeIJarZD1JUnjg=="
},
"emojis-list": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
"integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k="
},
"esprima": {
"version": "4.0.1",
@@ -105,11 +115,11 @@
}
},
"hoist-non-react-statics": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.2.0.tgz",
"integrity": "sha512-3IascCRfaEkbmHjJnUxWSspIUE1okLPjGTMVXW8zraUo1t3yg1BadKAxAGILHwgoBzmMnzrgeeaDGBvpuPz6dA==",
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz",
"integrity": "sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==",
"requires": {
"react-is": "^16.3.2"
"react-is": "^16.7.0"
}
},
"js-tokens": {
@@ -118,9 +128,9 @@
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"js-yaml": {
"version": "3.12.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz",
"integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==",
"version": "3.12.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.1.tgz",
"integrity": "sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA==",
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
@@ -135,15 +145,38 @@
}
},
"json-schema-ref-parser": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-6.0.2.tgz",
"integrity": "sha512-EENU7mrmuBAdjSsAEJD8mMvodZyDhLBEfuSUBSIMuXqjs+cfMbFaxS8f6+ky675jetRzGzCdhzAU3y2VEtquvQ==",
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-6.0.3.tgz",
"integrity": "sha512-Ds/0541IPed88JSiMb3CBeUsxfL5Eosc0r97z0QMSXiBJTYKZLhOAGZd8zFVfpkKaRb4zDAnumyFYxnHLmbQmw==",
"requires": {
"call-me-maybe": "^1.0.1",
"js-yaml": "^3.12.0",
"ono": "^4.0.10"
"js-yaml": "^3.12.1",
"ono": "^4.0.11"
}
},
"json5": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
"requires": {
"minimist": "^1.2.0"
}
},
"loader-utils": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz",
"integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==",
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^2.0.0",
"json5": "^1.0.1"
}
},
"lodash.merge": {
"version": "4.6.1",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.1.tgz",
"integrity": "sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ=="
},
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -163,8 +196,9 @@
"integrity": "sha1-GA8fnr74sOY45BZq1S24eb6y/8U="
},
"marked": {
"version": "git+https://github.com/markedjs/marked.git#fb48827236ed3a43e611d2adb3c070ca3f55ed8e",
"from": "git+https://github.com/markedjs/marked.git#fb48827"
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/marked/-/marked-0.6.0.tgz",
"integrity": "sha512-HduzIW2xApSXKXJSpCipSxKyvMbwRRa/TwMbepmlZziKdH8548WSoDP4SxzulEKjlo8BE39l+2fwJZuRKOln6g=="
},
"memoize-one": {
"version": "4.0.3",
@@ -179,10 +213,15 @@
"dom-walk": "^0.1.0"
}
},
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
},
"mobx-react": {
"version": "5.4.2",
"resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-5.4.2.tgz",
"integrity": "sha512-alSN0KDAAOb1OkYujfoJjjk0JWxWRKO4sLGB4hN2CuvaJMrlj7bhGQe7CBMJvEFNjtJRbhJcquYVjQ3rrH2zQQ==",
"version": "5.4.3",
"resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-5.4.3.tgz",
"integrity": "sha512-WC8yFlwvJ91hy8j6CrydAuFteUafcuvdITFQeHl3LRIf5ayfT/4W3M/byhEYD2BcJWejeXr8y4Rh2H26RunCRQ==",
"requires": {
"hoist-non-react-statics": "^3.0.0",
"react-lifecycles-compat": "^3.0.2"
@@ -194,9 +233,9 @@
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"ono": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/ono/-/ono-4.0.10.tgz",
"integrity": "sha512-4Xz4hlbq7MzV0I3vKfZwRvyj8tCbXODqBNzFqtkjP+KTV93zzDRju8kw1qnf6P5kcZ2+xlIq6wSCqA+euSKxhA==",
"version": "4.0.11",
"resolved": "https://registry.npmjs.org/ono/-/ono-4.0.11.tgz",
"integrity": "sha512-jQ31cORBFE6td25deYeD80wxKBMj+zBmHTrVxnc6CKhx8gho6ipmWM5zj/oeoqioZ99yqBls9Z/9Nss7J26G2g==",
"requires": {
"format-util": "^1.0.3"
}
@@ -215,11 +254,11 @@
"integrity": "sha512-/2Sk/khljhdrsamjJYS5NjrH+GKEHEwh7zFSiYyxROyYKagkE4kSn2zDQDRTOMo8mpT2jikxx6yI1dG7lNP/hw=="
},
"polished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/polished/-/polished-2.3.0.tgz",
"integrity": "sha512-G2yD9LhJy5HBuU+Im5qe70ubaJI/ZTTOIJO6GRMwJ2WSoAiPzlm8+LjAXMnm9/K0E0NumRVHvQu2HHPKQSYQjw==",
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/polished/-/polished-2.3.3.tgz",
"integrity": "sha512-59V4fDbdxtH4I1m9TWxFsoGJbC8nnOpUYo5uFmvMfKp9Qh+6suo4VMUle1TGIIUZIGxfkW+Rs485zPk0wcwR2Q==",
"requires": {
"@babel/runtime": "^7.0.0"
"@babel/runtime": "^7.2.0"
}
},
"prismjs": {
@@ -245,24 +284,27 @@
}
},
"react-dropdown": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/react-dropdown/-/react-dropdown-1.6.2.tgz",
"integrity": "sha512-6RpKAFEVZLr53y91qnxC9rAWcoDvq8A9YstmeSV2AQgJxNFRcsreN1mJNRUctSvIwm0Hph07ouMvDc2GvfQBsA==",
"version": "1.6.4",
"resolved": "https://registry.npmjs.org/react-dropdown/-/react-dropdown-1.6.4.tgz",
"integrity": "sha512-zTlNRZ6vzjEPsodBNgh6Xjp9IempEx9sReH3crT2Jw4S6KW2wS/BRIH3d/grYf/iXARadDRD91//uUCs9yjoLg==",
"requires": {
"classnames": "^2.2.3"
}
},
"react-hot-loader": {
"version": "4.3.12",
"resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.3.12.tgz",
"integrity": "sha512-GMM4TsqUVss2QPe+Y33NlgydA5/+7tAVQxR0rZqWvBpapM8JhD7p6ymMwSZzr5yxjoXXlK/6P6qNQBOqm1dqdg==",
"version": "4.6.3",
"resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.6.3.tgz",
"integrity": "sha512-FUvRO8dwbeLnc3mgLn8ARuSh8NnLBYJyiRjFn+grY/5GupSyPqv0U7ixgwXro77hwDplhm8z9wU7FlJ8kZqiAQ==",
"requires": {
"fast-levenshtein": "^2.0.6",
"global": "^4.3.0",
"hoist-non-react-statics": "^2.5.0",
"loader-utils": "^1.1.0",
"lodash.merge": "^4.6.1",
"prop-types": "^15.6.1",
"react-lifecycles-compat": "^3.0.4",
"shallowequal": "^1.0.2"
"shallowequal": "^1.0.2",
"source-map": "^0.7.3"
},
"dependencies": {
"hoist-non-react-statics": {
@@ -273,9 +315,9 @@
}
},
"react-is": {
"version": "16.6.3",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.6.3.tgz",
"integrity": "sha512-u7FDWtthB4rWibG/+mFbVd5FvdI20yde86qKGx4lVUTWmPlSWQ4QxbBIrrs+HnXGbxOUlUzTAP/VDmvCwaP2yA=="
"version": "16.7.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.7.0.tgz",
"integrity": "sha512-Z0VRQdF4NPDoI0tsXVMLkJLiwEBa+RP66g0xDHxgxysxSoCUccSten4RTF/UFvZF1dZvZ9Zu1sx+MDXwcOR34g=="
},
"react-lifecycles-compat": {
"version": "3.0.4",
@@ -292,9 +334,9 @@
}
},
"redoc": {
"version": "2.0.0-alpha.41",
"resolved": "https://registry.npmjs.org/redoc/-/redoc-2.0.0-alpha.41.tgz",
"integrity": "sha512-g8XnoDRRNLq2GVk8cNYKtO6cK5cPK95qdCIc6PSNo2ueBVWEQBKooriSF57B6GVuaqoOBacqlGHQDPakvBQLYA==",
"version": "2.0.0-rc.2",
"resolved": "https://registry.npmjs.org/redoc/-/redoc-2.0.0-rc.2.tgz",
"integrity": "sha512-KsacFfNeBSBwGbWD8zzckYZ9V5TQB33xCl0I2pNQrWEEyhOZyfpp4Sf7qmOVYxj8DzdqHTP/HJiCtH84o68+mg==",
"requires": {
"classnames": "^2.2.6",
"decko": "^1.2.0",
@@ -304,7 +346,7 @@
"json-schema-ref-parser": "^6.0.1",
"lunr": "^2.3.2",
"mark.js": "^8.11.1",
"marked": "git+https://github.com/markedjs/marked.git#fb48827236ed3a43e611d2adb3c070ca3f55ed8e",
"marked": "^0.6.0",
"memoize-one": "^4.0.0",
"mobx-react": "^5.2.5",
"openapi-sampler": "1.0.0-beta.14",
@@ -337,9 +379,14 @@
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
},
"slugify": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.3.3.tgz",
"integrity": "sha512-aFvcXobuowA7RqU4IBVJvqmhkREDIqsj4oIJKk6JuZ5EO1PCwtAAwDCl8TdsMs4J9zCoDAVkB9FLUElDjNcRSg=="
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.3.4.tgz",
"integrity": "sha512-KP0ZYk5hJNBS8/eIjGkFDCzGQIoZ1mnfQRYS5WM3273z+fxGWXeN0fkwf2ebEweydv9tioZIHGZKoF21U07/nw=="
},
"source-map": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ=="
},
"sprintf-js": {
"version": "1.0.3",
@@ -352,9 +399,9 @@
"integrity": "sha1-OUE/7p0CXHSn5ZzuyyN4TMDxfwI="
},
"swagger-ui-dist": {
"version": "3.20.4",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.20.4.tgz",
"integrity": "sha512-SR/N+N7EIncEhyA4UGiMQ12vJBSv5OKO4S1g7E7etO+9ZUV6SZ7SqKVC8RDDyMOJzdXgMXV7aGIBIiMSCbEMJw=="
"version": "3.20.5",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.20.5.tgz",
"integrity": "sha512-DNmJQ1qVkW8xk3wI2VfupOS7IYTHNjYaYX+L4s8B8ksdtoHOx3RsRqJUMIQByojHgb3PwEt+zGdIUwxJ4N5pwg=="
},
"tiny-emitter": {
"version": "2.0.2",
+2 -2
View File
@@ -1,8 +1,8 @@
{
"name": "drf-yasg",
"dependencies": {
"redoc": "^2.0.0-alpha.41",
"swagger-ui-dist": "^3.20.4"
"redoc": "^2.0.0-rc.2",
"swagger-ui-dist": "^3.20.5"
},
"repository": {
"type": "git",
-1
View File
@@ -1,3 +1,2 @@
# requirements for the validation feature
flex>=6.11.1
swagger-spec-validator>=2.1.0
+29 -6
View File
@@ -15,8 +15,12 @@ logger = logging.getLogger(__name__)
def _validate_flex(spec):
from flex.core import parse as validate_flex
from flex.exceptions import ValidationError
try:
from flex.core import parse as validate_flex
from flex.exceptions import ValidationError
except ImportError:
return
try:
validate_flex(spec)
except ValidationError as ex:
@@ -88,7 +92,7 @@ class _OpenAPICodec(object):
:param dict spec: a python dict
:return: string representation of ``spec``
:rtype: str
:rtype: str or bytes
"""
raise NotImplementedError("override this method")
@@ -105,9 +109,22 @@ class _OpenAPICodec(object):
class OpenAPICodecJson(_OpenAPICodec):
media_type = 'application/json'
def __init__(self, validators, pretty=False, media_type='application/json'):
super(OpenAPICodecJson, self).__init__(validators)
self.pretty = pretty
self.media_type = media_type
def _dump_dict(self, spec):
"""Dump ``spec`` into JSON."""
return json.dumps(spec)
"""Dump ``spec`` into JSON.
:rtype: str"""
if self.pretty:
out = json.dumps(spec, indent=4, separators=(',', ': '))
if out[-1] != '\n':
out += '\n'
return out
else:
return json.dumps(spec)
YAML_MAP_TAG = u'tag:yaml.org,2002:map'
@@ -201,6 +218,12 @@ def yaml_sane_load(stream):
class OpenAPICodecYaml(_OpenAPICodec):
media_type = 'application/yaml'
def __init__(self, validators, media_type='application/yaml'):
super(OpenAPICodecYaml, self).__init__(validators)
self.media_type = media_type
def _dump_dict(self, spec):
"""Dump ``spec`` into YAML."""
"""Dump ``spec`` into YAML.
:rtype: bytes"""
return yaml_sane_dump(spec, binary=True)
+1 -1
View File
@@ -230,7 +230,7 @@ class OpenAPISchemaGenerator(object):
def get_schema(self, request=None, public=False):
"""Generate a :class:`.Swagger` object representing the API schema.
:param request: the request used for filtering accesible endpoints and finding the spec URI
:param request: the request used for filtering accessible endpoints and finding the spec URI
:type request: rest_framework.request.Request or None
:param bool public: if True, all endpoints are included regardless of access through `request`
+27 -4
View File
@@ -12,21 +12,44 @@ NotHandled = object()
logger = logging.getLogger(__name__)
def is_callable_method(cls_or_instance, method_name):
method = getattr(cls_or_instance, method_name)
if inspect.ismethod(method) and getattr(method, '__self__', None):
# bound classmethod or instance method
return method, True
try:
# inspect.getattr_static was added in python 3.2
from inspect import getattr_static
# on python 3, both unbound instance methods (i.e. getattr(cls, mth)) and static methods are plain functions
# getattr_static allows us to check the type of the method descriptor; for `@staticmethod` this is staticmethod
return method, isinstance(getattr_static(cls_or_instance, method_name, None), staticmethod)
except ImportError:
# python 2 still has unbound methods, so ismethod <=> !staticmethod TODO: remove when dropping python 2.7
return method, not inspect.ismethod(method)
def call_view_method(view, method_name, fallback_attr=None, default=None):
"""Call a view method which might throw an exception. If an exception is thrown, log an informative error message
and return the value of fallback_attr, or default if not present.
and return the value of fallback_attr, or default if not present. The method must be callable without any arguments
except cls or self.
:param rest_framework.views.APIView view:
:param view: view class or instance; if a class is passed, instance methods won't be called
:type view: rest_framework.views.APIView or type[rest_framework.views.APIView]
:param str method_name: name of a method on the view
:param str fallback_attr: name of an attribute on the view to fall back on, if calling the method fails
:param default: default value if all else fails
:return: view method's return value, or value of view's fallback_attr, or default
:rtype: any or None
"""
if hasattr(view, method_name):
try:
return getattr(view, method_name)()
view_method, is_callabale = is_callable_method(view, method_name)
if is_callabale:
return view_method()
except Exception: # pragma: no cover
logger.warning("view's %s.get_parsers raised exception during schema generation; use "
logger.warning("view's %s raised exception during schema generation; use "
"`getattr(self, 'swagger_fake_view', False)` to detect and short-circuit this",
type(view).__name__, exc_info=True)
+46 -21
View File
@@ -177,11 +177,11 @@ def get_queryset_from_view(view, serializer=None):
:return: queryset or ``None``
"""
try:
queryset = call_view_method(view, 'get_queryset', 'queryset', None)
queryset = call_view_method(view, 'get_queryset', 'queryset')
if queryset is not None and serializer is not None:
# make sure the view is actually using *this* serializer
assert type(serializer) == view.get_serializer_class()
assert type(serializer) == call_view_method(view, 'get_serializer_class', 'serializer_class')
return queryset
except Exception: # pragma: no cover
@@ -455,18 +455,39 @@ def decimal_return_type():
return openapi.TYPE_STRING if rest_framework_settings.COERCE_DECIMAL_TO_STRING else openapi.TYPE_NUMBER
raw_type_info = [
def get_origin_type(hint_class):
return getattr(hint_class, '__origin__', None) or hint_class
def hint_class_issubclass(hint_class, check_class):
origin_type = get_origin_type(hint_class)
return inspect.isclass(origin_type) and issubclass(origin_type, check_class)
hinting_type_info = [
(bool, (openapi.TYPE_BOOLEAN, None)),
(int, (openapi.TYPE_INTEGER, None)),
(str, (openapi.TYPE_STRING, None)),
(float, (openapi.TYPE_NUMBER, None)),
(dict, (openapi.TYPE_OBJECT, None)),
(Decimal, (decimal_return_type, openapi.FORMAT_DECIMAL)),
(uuid.UUID, (openapi.TYPE_STRING, openapi.FORMAT_UUID)),
(datetime.datetime, (openapi.TYPE_STRING, openapi.FORMAT_DATETIME)),
(datetime.date, (openapi.TYPE_STRING, openapi.FORMAT_DATE)),
# TODO - support typing.List etc
]
hinting_type_info = raw_type_info
if typing:
def inspect_collection_hint_class(hint_class):
args = hint_class.__args__
child_class = args[0] if args else str
child_type_info = get_basic_type_info_from_hint(child_class) or {'type': openapi.TYPE_STRING}
return OrderedDict([
('type', openapi.TYPE_ARRAY),
('items', openapi.Items(**child_type_info)),
])
hinting_type_info.append(((typing.Sequence, typing.AbstractSet), inspect_collection_hint_class))
def get_basic_type_info_from_hint(hint_class):
@@ -478,27 +499,31 @@ def get_basic_type_info_from_hint(hint_class):
:return: the extracted attributes as a dictionary, or ``None`` if the field type is not known
:rtype: OrderedDict
"""
if typing and get_origin_type(hint_class) == typing.Union:
# Optional is implemented as Union[T, None]
if len(hint_class.__args__) == 2 and hint_class.__args__[1] == type(None): # noqa: E721
child_type = hint_class.__args__[0]
result = get_basic_type_info_from_hint(child_type)
result['x-nullable'] = True
return result
for check_class, type_format in hinting_type_info:
if issubclass(hint_class, check_class):
swagger_type, format = type_format
if callable(swagger_type):
swagger_type = swagger_type()
# if callable(format):
# format = format(klass)
break
else: # pragma: no cover
return None
pattern = None
for check_class, info in hinting_type_info:
if hint_class_issubclass(hint_class, check_class):
if callable(info):
return info(hint_class)
result = OrderedDict([
('type', swagger_type),
('format', format),
('pattern', pattern)
])
swagger_type, format = info
if callable(swagger_type):
swagger_type = swagger_type()
return result
return OrderedDict([
('type', swagger_type),
('format', format),
])
return None
class SerializerMethodFieldInspector(FieldInspector):
+7 -13
View File
@@ -1,5 +1,4 @@
import logging
import warnings
from collections import OrderedDict
from rest_framework.request import is_form_media_type
@@ -22,22 +21,17 @@ class SwaggerAutoSchema(ViewInspector):
super(SwaggerAutoSchema, self).__init__(view, path, method, components, request, overrides)
self._sch = AutoSchema()
self._sch.view = view
self._summary_and_description_compat()
def _summary_and_description_compat(self):
# TODO: remove in 1.13
# TODO: remove in 1.14
base_methods = (SwaggerAutoSchema.get_summary, SwaggerAutoSchema.get_description)
self_methods = (type(self).get_summary, type(self).get_description)
if self_methods != base_methods:
warnings.warn(
"`SwaggerAutoSchema` methods `get_summary` and `get_description` are deprecated and "
"will be removed in drf-yasg 1.13. Override `get_summary_and_description` instead.",
DeprecationWarning, stacklevel=2
raise NotImplementedError(
"`SwaggerAutoSchema` methods `get_summary` and `get_description` were removed in "
"drf-yasg 1.13 and will have no effect. Override `get_summary_and_description` instead."
)
# if get_summary or get_description are overriden by a child class,
# we must call them for backwards compatibility
return self.get_summary(), self.get_description()
return self.get_summary_and_description()
def get_operation(self, operation_keys):
consumes = self.get_consumes()
@@ -50,7 +44,7 @@ class SwaggerAutoSchema(ViewInspector):
parameters = self.add_manual_parameters(parameters)
operation_id = self.get_operation_id(operation_keys)
summary, description = self._summary_and_description_compat()
summary, description = self.get_summary_and_description()
security = self.get_security()
assert security is None or isinstance(security, list), "security must be a list of security requirement objects"
deprecated = self.is_deprecated()
@@ -363,7 +357,7 @@ class SwaggerAutoSchema(ViewInspector):
description = description.strip().replace('\r', '')
if description and (summary is None):
# description from docstring ... do summary magic
# description from docstring... do summary magic
summary, description = self.split_summary_from_description(description)
return summary, description
@@ -1,12 +1,11 @@
import json
import logging
import os
from collections import OrderedDict
from importlib import import_module
from django.contrib.auth import get_user_model
from django.core.exceptions import ImproperlyConfigured
from django.core.management.base import BaseCommand
from django.utils.module_loading import import_string
from rest_framework.settings import api_settings
from rest_framework.test import APIRequestFactory, force_authenticate
from rest_framework.views import APIView
@@ -15,15 +14,6 @@ from ...app_settings import swagger_settings
from ...codecs import OpenAPICodecJson, OpenAPICodecYaml
def import_class(import_string):
if not import_string:
return None
module_path, class_name = import_string.rsplit('.', 1)
module = import_module(module_path)
return getattr(module, class_name)
class Command(BaseCommand):
help = 'Write the Swagger schema to disk in JSON or YAML format.'
@@ -56,7 +46,7 @@ class Command(BaseCommand):
parser.add_argument(
'-m', '--mock-request', dest='mock',
default=False, action='store_true',
help='Use a mock request when generating the swagger schema. This is useful if your views or serializers'
help='Use a mock request when generating the swagger schema. This is useful if your views or serializers '
'depend on context from a request in order to function.'
)
parser.add_argument(
@@ -85,11 +75,9 @@ class Command(BaseCommand):
def write_schema(self, schema, stream, format):
if format == 'json':
codec = OpenAPICodecJson(validators=[])
swagger_json = codec.encode(schema)
swagger_json = json.loads(swagger_json.decode('utf-8'), object_pairs_hook=OrderedDict)
pretty_json = json.dumps(swagger_json, indent=4, ensure_ascii=True)
stream.write(pretty_json)
codec = OpenAPICodecJson(validators=[], pretty=True)
swagger_json = codec.encode(schema).decode('utf-8')
stream.write(swagger_json)
elif format == 'yaml':
codec = OpenAPICodecYaml(validators=[])
swagger_yaml = codec.encode(schema).decode('utf-8')
@@ -107,6 +95,20 @@ class Command(BaseCommand):
request = APIView().initialize_request(request)
return request
def get_schema_generator(self, generator_class_name, api_info, api_version, api_url):
generator_class = swagger_settings.DEFAULT_GENERATOR_CLASS
if generator_class_name:
generator_class = import_string(generator_class_name)
return generator_class(
info=api_info,
version=api_version,
url=api_url,
)
def get_schema(self, generator, request, public):
return generator.get_schema(request=request, public=public)
def handle(self, output_file, overwrite, format, api_url, mock, api_version, user, private, generator_class_name,
*args, **kwargs):
# disable logs of WARNING and below
@@ -142,16 +144,12 @@ class Command(BaseCommand):
if mock:
request = self.get_mock_request(api_url, format, user)
api_version = api_version or api_settings.DEFAULT_VERSION
if request and api_version:
request.version = api_version
generator_class = import_class(generator_class_name) or swagger_settings.DEFAULT_GENERATOR_CLASS
generator = generator_class(
info=info,
version=api_version,
url=api_url,
)
schema = generator.get_schema(request=request, public=not private)
generator = self.get_schema_generator(generator_class_name, info, api_version, api_url)
schema = self.get_schema(generator, request, not private)
if output_file == '-':
self.write_schema(schema, self.stdout, format)
+2 -2
View File
@@ -10,7 +10,7 @@ from django.urls import get_script_prefix
from django.utils.functional import Promise
from inflection import camelize
from .utils import filter_none, force_real_str
from .utils import dict_has_ordered_keys, filter_none, force_real_str
try:
from collections import abc as collections_abc
@@ -145,7 +145,7 @@ class SwaggerDict(OrderedDict):
result = OrderedDict()
memo[id(obj)] = result
items = obj.items()
if not isinstance(obj, OrderedDict):
if not dict_has_ordered_keys(obj):
items = sorted(items)
for attr, val in items:
result[attr] = SwaggerDict._as_odict(val, memo)
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+16 -4
View File
@@ -1,5 +1,6 @@
import inspect
import logging
import sys
from collections import OrderedDict
from django.db import models
@@ -389,8 +390,7 @@ def get_produces(renderer_classes):
def decimal_as_float(field):
"""
Returns true if ``field`` is a django-rest-framework DecimalField and its ``coerce_to_string`` attribute or the
"""Returns true if ``field`` is a django-rest-framework DecimalField and its ``coerce_to_string`` attribute or the
``COERCE_DECIMAL_TO_STRING`` setting is set to ``False``.
:rtype: bool
@@ -401,8 +401,7 @@ def decimal_as_float(field):
def get_serializer_ref_name(serializer):
"""
Get serializer's ref_name (or None for ModelSerializer if it is named 'NestedSerializer')
"""Get serializer's ref_name (or None for ModelSerializer if it is named 'NestedSerializer')
:param serializer: Serializer instance
:return: Serializer's ``ref_name`` or ``None`` for inline serializer
@@ -469,3 +468,16 @@ def get_field_default(field):
default = serializers.empty
return default
def dict_has_ordered_keys(obj):
"""Check if a given object is a dict that maintains insertion order.
:param obj: the dict object to check
:rtype: bool
"""
if sys.version_info >= (3, 7):
# the Python 3.7 language spec says that dict must maintain insertion order.
return isinstance(obj, dict)
return isinstance(obj, OrderedDict)
+1 -1
View File
@@ -57,7 +57,7 @@ def get_schema_view(info=None, url=None, patterns=None, urlconf=None, public=Fal
:param patterns: same as :class:`.OpenAPISchemaGenerator`
:param urlconf: same as :class:`.OpenAPISchemaGenerator`
:param bool public: if False, includes only the endpoints that are accesible by the user viewing the schema
:param list validators: a list of validator names to apply; allowed values are ``flex``, ``ssv``
:param list validators: a list of validator names to apply; the only allowed value is ``ssv``, for now
:param type generator_class: schema generator class to use; should be a subclass of :class:`.OpenAPISchemaGenerator`
:param tuple authentication_classes: authentication classes for the schema view itself
:param tuple permission_classes: permission classes for the schema view itself
+10 -10
View File
@@ -116,24 +116,24 @@ SWAGGER_SETTINGS = {
'type': 'basic'
},
'Bearer': {
'type': 'apiKey',
'in': 'header',
'name': 'Authorization',
'in': 'header'
},
'Query': {
'type': 'apiKey',
'name': 'auth',
'in': 'query'
},
'OAuth2 password': {
'type': 'oauth2',
'flow': 'password',
'tokenUrl': OAUTH2_TOKEN_URL,
'scopes': {
'read': 'Read everything.',
'write': 'Write everything,',
}
}
},
'tokenUrl': OAUTH2_TOKEN_URL,
'type': 'oauth2',
},
'Query': {
'in': 'query',
'name': 'auth',
'type': 'apiKey',
},
},
'OAUTH2_REDIRECT_URL': OAUTH2_REDIRECT_URL,
'OAUTH2_CONFIG': {
+7 -2
View File
@@ -57,10 +57,15 @@ def swagger_dict(swagger, codec_json):
@pytest.fixture
def validate_schema():
def validate_schema(swagger):
from flex.core import parse as validate_flex
try:
from flex.core import parse as validate_flex
validate_flex(copy.deepcopy(swagger))
except ImportError:
pass
from swagger_spec_validator.validator20 import validate_spec as validate_ssv
validate_flex(copy.deepcopy(swagger))
validate_ssv(copy.deepcopy(swagger))
return validate_schema
@@ -0,0 +1,39 @@
import uuid
import pytest
from drf_yasg import openapi
from drf_yasg.inspectors.field import get_basic_type_info_from_hint
try:
import typing
from typing import Dict, List, Union, Optional, Set
except ImportError:
typing = None
if typing:
@pytest.mark.parametrize('hint_class, expected_swagger_type_info', [
(int, {'type': openapi.TYPE_INTEGER, 'format': None}),
(str, {'type': openapi.TYPE_STRING, 'format': None}),
(bool, {'type': openapi.TYPE_BOOLEAN, 'format': None}),
(dict, {'type': openapi.TYPE_OBJECT, 'format': None}),
(Dict[int, int], {'type': openapi.TYPE_OBJECT, 'format': None}),
(uuid.UUID, {'type': openapi.TYPE_STRING, 'format': openapi.FORMAT_UUID}),
(List[int], {'type': openapi.TYPE_ARRAY, 'items': openapi.Items(openapi.TYPE_INTEGER)}),
(List[str], {'type': openapi.TYPE_ARRAY, 'items': openapi.Items(openapi.TYPE_STRING)}),
(List[bool], {'type': openapi.TYPE_ARRAY, 'items': openapi.Items(openapi.TYPE_BOOLEAN)}),
(Set[int], {'type': openapi.TYPE_ARRAY, 'items': openapi.Items(openapi.TYPE_INTEGER)}),
(Optional[bool], {'type': openapi.TYPE_BOOLEAN, 'format': None, 'x-nullable': True}),
(Optional[List[int]], {
'type': openapi.TYPE_ARRAY, 'items': openapi.Items(openapi.TYPE_INTEGER), 'x-nullable': True
}),
(Union[List[int], type(None)], {
'type': openapi.TYPE_ARRAY, 'items': openapi.Items(openapi.TYPE_INTEGER), 'x-nullable': True
}),
# Following cases are not 100% correct, but it should work somehow and not crash.
(Union[int, float], None),
(List, {'type': openapi.TYPE_ARRAY, 'items': openapi.Items(openapi.TYPE_STRING)}),
])
def test_get_basic_type_info_from_hint(hint_class, expected_swagger_type_info):
type_info = get_basic_type_info_from_hint(hint_class)
assert type_info == expected_swagger_type_info