Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d2bd838325 | |||
| bacab20f0b | |||
| df82fe59d7 | |||
| 7c5a0b7176 | |||
| 69a1e62ed3 | |||
| 3806d6efd5 | |||
| 58e6dae548 | |||
| 8e2228fe5f | |||
| 762467285c | |||
| 0e62fd6f2b | |||
| e266eeda60 | |||
| 4b1098369c | |||
| bda545e85f | |||
| c1d3d4fe3c | |||
| 7f3ffe80a9 | |||
| 1fe8c2c03c | |||
| b8512bda8e | |||
| 2bc9addc99 | |||
| 6df2362156 | |||
| e5a569ebf7 | |||
| 470c993b98 | |||
| a5e4386f38 | |||
| 6bd91faa5d | |||
| 667c9c1002 | |||
| 8b0da2607f |
Generated
+1
-1
@@ -44,4 +44,4 @@
|
||||
<option name="projectConfiguration" value="pytest" />
|
||||
<option name="PROJECT_TEST_RUNNER" value="pytest" />
|
||||
</component>
|
||||
</module>
|
||||
</module>
|
||||
|
||||
Generated
+1
-1
@@ -73,4 +73,4 @@
|
||||
<component name="PythonCompatibilityInspectionAdvertiser">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
</project>
|
||||
</project>
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
coverage combine || true
|
||||
coverage report
|
||||
codecov
|
||||
+24
-1
@@ -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
|
||||
|
||||
@@ -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
@@ -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',
|
||||
}
|
||||
|
||||
Generated
+97
-50
@@ -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
@@ -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,3 +1,2 @@
|
||||
# requirements for the validation feature
|
||||
flex>=6.11.1
|
||||
swagger-spec-validator>=2.1.0
|
||||
|
||||
+29
-6
@@ -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)
|
||||
|
||||
@@ -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`
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
+19
-19
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+16
-4
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
Reference in New Issue
Block a user