Initial implementation as coreapi wrapper

* schema generation implemented via coreapi Document and openapi_codec
* schema interface selectable from the latest versions of swagger-ui and redoc
* feature parity with django-rest-swagger 2
openapi3
Cristi Vîjdea 2017-11-30 04:23:56 +01:00
parent cbd8cb68cf
commit ed02e3c3a1
51 changed files with 1651 additions and 0 deletions

158
.gitignore vendored 100644
View File

@ -0,0 +1,158 @@
node_modules/
# Created by .ignore support plugin (hsz.mobi)
### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
.static_storage/
.media/
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff:
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/dictionaries
# Sensitive or high-churn files:
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.xml
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
# Gradle:
.idea/**/gradle.xml
.idea/**/libraries
# CMake
cmake-build-debug/
# Mongo Explorer plugin:
.idea/**/mongoSettings.xml
## File-based project format:
*.iws
## Plugin-specific files:
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties

4
MANIFEST.in 100644
View File

@ -0,0 +1,4 @@
include README.md
include LICENSE
recursive-include drf_swagger/static *
recursive-include drf_swagger/templates *

View File

@ -0,0 +1,4 @@
# coding=utf-8
__author__ = """Cristi V."""
__email__ = 'cristi@cvjd.me'
__version__ = '1.0.0rc1'

View File

@ -0,0 +1,43 @@
from django.conf import settings
from rest_framework.settings import APISettings
SWAGGER_DEFAULTS = {
'USE_SESSION_AUTH': True,
'SECURITY_DEFINITIONS': {
'basic': {
'type': 'basic'
}
},
'LOGIN_URL': getattr(settings, 'LOGIN_URL', None),
'LOGOUT_URL': getattr(settings, 'LOGOUT_URL', None),
'VALIDATOR_URL': '',
'OPERATIONS_SORTER': None,
'TAGS_SORTER': None,
'DOC_EXPANSION': 'list',
'DEEP_LINKING': False,
'SHOW_EXTENSIONS': True,
'DEFAULT_MODEL_RENDERING': 'model',
'DEFAULT_MODEL_DEPTH': 2,
}
REDOC_DEFAULTS = {
'LAZY_RENDERING': True,
'HIDE_HOSTNAME': False,
'EXPAND_RESPONSES': 'all',
'PATH_IN_MIDDLE': False,
}
IMPORT_STRINGS = []
swagger_settings = APISettings(
user_settings=getattr(settings, 'SWAGGER_SETTINGS', {}),
defaults=SWAGGER_DEFAULTS,
import_strings=IMPORT_STRINGS,
)
redoc_settings = APISettings(
user_settings=getattr(settings, 'REDOC_SETTINGS', {}),
defaults=REDOC_DEFAULTS,
import_strings=IMPORT_STRINGS,
)

View File

@ -0,0 +1,152 @@
import json
from collections import OrderedDict
from coreapi.codecs import BaseCodec
from coreapi.compat import force_bytes, urlparse
from drf_swagger.app_settings import swagger_settings
from openapi_codec import encode
from ruamel import yaml
from . import openapi
class SwaggerValidationError(Exception):
def __init__(self, msg, validator_name, spec, *args) -> None:
super(SwaggerValidationError, self).__init__(msg, *args)
self.validator_name = validator_name
self.spec = spec
def __str__(self):
return str(self.validator_name) + ": " + super(SwaggerValidationError, self).__str__()
def _validate_flex(spec):
from flex.core import parse as validate_flex
from flex.exceptions import ValidationError
try:
validate_flex(spec)
except ValidationError as ex:
raise SwaggerValidationError(str(ex), 'flex', spec) from ex
def _validate_swagger_spec_validator(spec):
from swagger_spec_validator.validator20 import validate_spec as validate_ssv
from swagger_spec_validator.common import SwaggerValidationError as SSVErr
try:
validate_ssv(spec)
except SSVErr as ex:
raise SwaggerValidationError(str(ex), 'swagger_spec_validator', spec) from ex
VALIDATORS = {
'flex': _validate_flex,
'swagger_spec_validator': _validate_swagger_spec_validator,
'ssv': _validate_swagger_spec_validator,
}
class _OpenAPICodec(BaseCodec):
format = 'openapi'
def __init__(self, validators):
self._validators = validators
@property
def validators(self):
return self._validators
def encode(self, document, **options):
if not isinstance(document, openapi.Swagger):
raise TypeError('Expected a `openapi.Swagger` instance')
spec = self.generate_swagger_object(document)
for validator in self.validators:
VALIDATORS[validator](spec)
return force_bytes(self._dump_spec(spec))
def _dump_spec(self, spec):
return NotImplementedError("override this method")
def generate_swagger_object(self, swagger):
"""
Generates root of the Swagger spec.
:param openapi.Swagger swagger:
:return OrderedDict: swagger spec as dict
"""
parsed_url = urlparse.urlparse(swagger.url)
spec = OrderedDict()
spec['swagger'] = '2.0'
spec['info'] = swagger.info.to_swagger(swagger.version)
if parsed_url.netloc:
spec['host'] = parsed_url.netloc
if parsed_url.scheme:
spec['schemes'] = [parsed_url.scheme]
spec['basePath'] = '/'
spec['paths'] = encode._get_paths_object(swagger)
spec['securityDefinitions'] = swagger_settings.SECURITY_DEFINITIONS
return spec
class OpenAPICodecJson(_OpenAPICodec):
media_type = 'application/openapi+json'
def _dump_spec(self, spec):
return json.dumps(spec)
class SaneYamlDumper(yaml.SafeDumper):
def increase_indent(self, flow=False, indentless=False, **kwargs):
"""https://stackoverflow.com/a/39681672
Indent list elements.
"""
return super(SaneYamlDumper, self).increase_indent(flow=flow, indentless=False, **kwargs)
@staticmethod
def represent_odict(dump, mapping, flow_style=None):
"""https://gist.github.com/miracle2k/3184458
Make PyYAML output an OrderedDict.
It will do so fine if you use yaml.dump(), but that generates ugly,
non-standard YAML code.
To use yaml.safe_dump(), you need the following.
"""
tag = u'tag:yaml.org,2002:map'
value = []
node = yaml.MappingNode(tag, value, flow_style=flow_style)
if dump.alias_key is not None:
dump.represented_objects[dump.alias_key] = node
best_style = True
if hasattr(mapping, 'items'):
mapping = mapping.items()
for item_key, item_value in mapping:
node_key = dump.represent_data(item_key)
node_value = dump.represent_data(item_value)
if not (isinstance(node_key, yaml.ScalarNode) and not node_key.style):
best_style = False
if not (isinstance(node_value, yaml.ScalarNode) and not node_value.style):
best_style = False
value.append((node_key, node_value))
if flow_style is None:
if dump.default_flow_style is not None:
node.flow_style = dump.default_flow_style
else:
node.flow_style = best_style
return node
SaneYamlDumper.add_representer(OrderedDict, SaneYamlDumper.represent_odict)
class OpenAPICodecYaml(_OpenAPICodec):
media_type = 'application/openapi+yaml'
def _dump_spec(self, spec):
return yaml.dump(spec, Dumper=SaneYamlDumper, default_flow_style=False, encoding='utf-8')

View File

@ -0,0 +1,15 @@
from rest_framework.schemas import SchemaGenerator
from . import openapi
class OpenAPISchemaGenerator(SchemaGenerator):
def __init__(self, info, version, url=None, patterns=None, urlconf=None):
super(OpenAPISchemaGenerator, self).__init__(info.title, url, info.description, patterns, urlconf)
self.info = info
self.version = version
def get_schema(self, request=None, public=False):
document = super(OpenAPISchemaGenerator, self).get_schema(request, public)
swagger = openapi.Swagger.from_coreapi(document, self.info, self.version)
return swagger

View File

@ -0,0 +1,5 @@
from rest_framework.schemas import AutoSchema
class SwaggerAutoSchema(AutoSchema):
pass

View File

@ -0,0 +1,115 @@
import warnings
from collections import OrderedDict
import coreapi
class Contact(object):
def __init__(self, name=None, url=None, email=None):
self.name = name
self.url = url
self.email = email
if name is None and url is None and email is None:
raise ValueError("one of name, url or email is requires for Swagger Contact object")
def to_swagger(self):
contact = OrderedDict()
if self.name is not None:
contact['name'] = self.name
if self.url is not None:
contact['url'] = self.url
if self.email is not None:
contact['email'] = self.email
return contact
class License(object):
def __init__(self, name, url=None):
self.name = name
self.url = url
if name is None:
raise ValueError("name is required for Swagger License object")
def to_swagger(self):
license = OrderedDict()
license['name'] = self.name
if self.url is not None:
license['url'] = self.url
return license
class Info(object):
def __init__(self, title, default_version, description=None, terms_of_service=None, contact=None, license=None):
if title is None or default_version is None:
raise ValueError("title and version are required for Swagger info object")
if contact is not None and not isinstance(contact, Contact):
raise ValueError("contact must be a Contact object")
if license is not None and not isinstance(license, License):
raise ValueError("license must be a License object")
self.title = title
self.default_version = default_version
self.description = description
self.terms_of_service = terms_of_service
self.contact = contact
self.license = license
def to_swagger(self, version):
info = OrderedDict()
info['title'] = self.title
if self.description is not None:
info['description'] = self.description
if self.terms_of_service is not None:
info['termsOfService'] = self.terms_of_service
if self.contact is not None:
info['contact'] = self.contact.to_swagger()
if self.license is not None:
info['license'] = self.license.to_swagger()
info['version'] = version or self.default_version
return info
class Swagger(coreapi.Document):
@classmethod
def from_coreapi(cls, document, info, version):
"""
Create an openapi.Swagger from the fields of a coreapi.Document.
:param coreapi.Document document: source coreapi.Document
:param openapi.Info info: Swagger info object
:param string version: API version string
:return: an openapi.Swagger
"""
if document.title != info.title:
warnings.warn("document title is overriden by Swagger Info")
if document.description != info.description:
warnings.warn("document description is overriden by Swagger Info")
return Swagger(
info=info,
version=version,
url=document.url,
media_type=document.media_type,
content=document.data
)
def __init__(self, info=None, version=None, url=None, media_type=None, content=None):
super(Swagger, self).__init__(url, info.title, info.description, media_type, content)
self._info = info
self._version = version
@property
def info(self):
return self._info
@property
def version(self):
return self._version
class Field(coreapi.Field):
pass
class Link(coreapi.Link):
pass

View File

@ -0,0 +1,107 @@
from django.shortcuts import render, resolve_url
from rest_framework.renderers import BaseRenderer
from rest_framework.utils import json
from .app_settings import swagger_settings, redoc_settings
from .codec import OpenAPICodecJson, VALIDATORS, OpenAPICodecYaml
class _SpecRenderer(BaseRenderer):
charset = None
validators = ['flex', 'ssv']
codec_class = None
@classmethod
def with_validators(cls, validators):
assert all(vld in VALIDATORS for vld in validators), "allowed validators are" + ", ".join(VALIDATORS)
return type(cls.__name__, (cls,), {'validators': validators})
def render(self, data, media_type=None, renderer_context=None):
assert self.codec_class, "must override codec_class"
codec = self.codec_class(self.validators)
return codec.encode(data)
class OpenAPIRenderer(_SpecRenderer):
media_type = 'application/openapi+json'
format = 'openapi'
codec_class = OpenAPICodecJson
class SwaggerJSONRenderer(_SpecRenderer):
media_type = 'application/json'
format = '.json'
codec_class = OpenAPICodecJson
class SwaggerYAMLRenderer(_SpecRenderer):
media_type = 'application/yaml'
format = '.yaml'
codec_class = OpenAPICodecYaml
class _UIRenderer(BaseRenderer):
media_type = 'text/html'
charset = 'utf-8'
template = ''
def render(self, data, accepted_media_type=None, renderer_context=None):
self.set_context(renderer_context, data)
return render(
renderer_context['request'],
self.template,
renderer_context
)
def set_context(self, renderer_context, data):
renderer_context['title'] = data.title
renderer_context['version'] = data.version
renderer_context['swagger_settings'] = json.dumps(self.get_swagger_ui_settings())
renderer_context['redoc_settings'] = json.dumps(self.get_redoc_settings())
renderer_context['USE_SESSION_AUTH'] = swagger_settings.USE_SESSION_AUTH
renderer_context.update(self.get_auth_urls())
def get_auth_urls(self):
urls = {}
if swagger_settings.LOGIN_URL is not None:
urls['LOGIN_URL'] = resolve_url(swagger_settings.LOGIN_URL)
if swagger_settings.LOGOUT_URL is not None:
urls['LOGOUT_URL'] = resolve_url(swagger_settings.LOGOUT_URL)
return urls
def get_swagger_ui_settings(self):
data = {
'operationsSorter': swagger_settings.OPERATIONS_SORTER,
'tagsSorter': swagger_settings.TAGS_SORTER,
'docExpansion': swagger_settings.DOC_EXPANSION,
'deepLinking': swagger_settings.DEEP_LINKING,
'showExtensions': swagger_settings.SHOW_EXTENSIONS,
'defaultModelRendering': swagger_settings.DEFAULT_MODEL_RENDERING,
'defaultModelExpandDepth': swagger_settings.DEFAULT_MODEL_DEPTH,
}
data = {k: v for k, v in data.items() if v is not None}
if swagger_settings.VALIDATOR_URL != '':
data['validatorUrl'] = swagger_settings.VALIDATOR_URL
return data
def get_redoc_settings(self):
data = {
'lazyRendering': redoc_settings.LAZY_RENDERING,
'hideHostname': redoc_settings.HIDE_HOSTNAME,
'expandResponses': redoc_settings.EXPAND_RESPONSES,
'pathInMiddle': redoc_settings.PATH_IN_MIDDLE,
}
return data
class SwaggerUIRenderer(_UIRenderer):
template = 'drf-swagger/swagger-ui.html'
format = 'swagger'
class ReDocRenderer(_UIRenderer):
template = 'drf-swagger/redoc.html'
format = 'redoc'

View File

@ -0,0 +1,4 @@
// insertion-query v1.0.3 (2016-01-20)
// license:MIT
// Zbyszek Tenerowicz <naugtur@gmail.com> (http://naugtur.pl/)
var insertionQ=function(){"use strict";function a(a,b){var d,e="insQ_"+g++,f=function(a){(a.animationName===e||a[i]===e)&&(c(a.target)||b(a.target))};d=document.createElement("style"),d.innerHTML="@"+j+"keyframes "+e+" { from { outline: 1px solid transparent } to { outline: 0px solid transparent } }\n"+a+" { animation-duration: 0.001s; animation-name: "+e+"; "+j+"animation-duration: 0.001s; "+j+"animation-name: "+e+"; } ",document.head.appendChild(d);var h=setTimeout(function(){document.addEventListener("animationstart",f,!1),document.addEventListener("MSAnimationStart",f,!1),document.addEventListener("webkitAnimationStart",f,!1)},n.timeout);return{destroy:function(){clearTimeout(h),d&&(document.head.removeChild(d),d=null),document.removeEventListener("animationstart",f),document.removeEventListener("MSAnimationStart",f),document.removeEventListener("webkitAnimationStart",f)}}}function b(a){a.QinsQ=!0}function c(a){return n.strictlyNew&&a.QinsQ===!0}function d(a){return c(a.parentNode)?a:d(a.parentNode)}function e(a){for(b(a),a=a.firstChild;a;a=a.nextSibling)void 0!==a&&1===a.nodeType&&e(a)}function f(f,g){var h=[],i=function(){var a;return function(){clearTimeout(a),a=setTimeout(function(){h.forEach(e),g(h),h=[]},10)}}();return a(f,function(a){if(!c(a)){b(a);var e=d(a);h.indexOf(e)<0&&h.push(e),i()}})}var g=100,h=!1,i="animationName",j="",k="Webkit Moz O ms Khtml".split(" "),l="",m=document.createElement("div"),n={strictlyNew:!0,timeout:20};if(m.style.animationName&&(h=!0),h===!1)for(var o=0;o<k.length;o++)if(void 0!==m.style[k[o]+"AnimationName"]){l=k[o],i=l+"AnimationName",j="-"+l.toLowerCase()+"-",h=!0;break}var p=function(b){return h&&b.match(/[^{}]/)?(n.strictlyNew&&e(document.body),{every:function(c){return a(b,c)},summary:function(a){return f(b,a)}}):!1};return p.config=function(a){for(var b in a)a.hasOwnProperty(b)&&(n[b]=a[b])},p}();"undefined"!=typeof module&&"undefined"!=typeof module.exports&&(module.exports=insertionQ);

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
README.md
deploy.sh

View File

@ -0,0 +1,22 @@
# Swagger UI Dist
[![NPM version](https://badge.fury.io/js/swagger-ui-dist.svg)](http://badge.fury.io/js/swagger-ui-dist)
# API
This module, `swagger-ui-dist`, exposes Swagger-UI's entire dist folder as a dependency-free npm module.
Use `swagger-ui` instead, if you'd like to have npm install dependencies for you.
`SwaggerUIBundle` and `SwaggerUIStandalonePreset` can be imported:
```javascript
import { SwaggerUIBundle, SwaggerUIStandalonePreset } from "swagger-ui-dist"
```
To get an absolute path to this directory for static file serving, use the exported `getAbsoluteFSPath` method:
```javascript
const swaggerUiAssetPath = require("swagger-ui-dist").getAbsoluteFSPath()
// then instantiate server that serves files from the swaggerUiAssetPath
```
For anything else, check the [Swagger-UI](https://github.com/swagger-api/swagger-ui) repository.

View File

@ -0,0 +1,14 @@
/*
* getAbsoluteFSPath
* @return {string} When run in NodeJS env, returns the absolute path to the current directory
* When run outside of NodeJS, will return an error message
*/
const getAbsoluteFSPath = function () {
// detect whether we are running in a browser or nodejs
if (typeof module !== "undefined" && module.exports) {
return require("path").resolve(__dirname)
}
throw new Error('getAbsoluteFSPath can only be called within a Nodejs environment');
}
module.exports = getAbsoluteFSPath

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,17 @@
try {
module.exports.SwaggerUIBundle = require("./swagger-ui-bundle.js")
module.exports.SwaggerUIStandalonePreset = require("./swagger-ui-standalone-preset.js")
} catch(e) {
// swallow the error if there's a problem loading the assets.
// allows this module to support providing the assets for browserish contexts,
// without exploding in a Node context.
//
// see https://github.com/swagger-api/swagger-ui/issues/3291#issuecomment-311195388
// for more information.
}
// `absolutePath` and `getAbsoluteFSPath` are both here because at one point,
// we documented having one and actually implemented the other.
// They were both retained so we don't break anyone's code.
module.exports.absolutePath = require("./absolute-path.js")
module.exports.getAbsoluteFSPath = require("./absolute-path.js")

View File

@ -0,0 +1,60 @@
<!doctype html>
<html lang="en-US">
<body onload="run()">
</body>
</html>
<script>
'use strict';
function run () {
var oauth2 = window.opener.swaggerUIRedirectOauth2;
var sentState = oauth2.state;
var redirectUrl = oauth2.redirectUrl;
var isValid, qp, arr;
if (/code|token|error/.test(window.location.hash)) {
qp = window.location.hash.substring(1);
} else {
qp = location.search.substring(1);
}
arr = qp.split("&")
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
qp = qp ? JSON.parse('{' + arr.join() + '}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value)
}
) : {}
isValid = qp.state === sentState
if ((
oauth2.auth.schema.get("flow") === "accessCode"||
oauth2.auth.schema.get("flow") === "authorizationCode"
) && !oauth2.auth.code) {
if (!isValid) {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "warning",
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
});
}
if (qp.code) {
delete oauth2.state;
oauth2.auth.code = qp.code;
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
} else {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "error",
message: "Authorization failed: no accessCode received from the server"
});
}
} else {
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
}
window.close();
}
</script>

View File

@ -0,0 +1,72 @@
{
"_from": "swagger-ui-dist",
"_id": "swagger-ui-dist@3.5.0",
"_inBundle": false,
"_integrity": "sha1-JuvzMRqaYP6dFwYS7tUoKmW8kiY=",
"_location": "/swagger-ui-dist",
"_phantomChildren": {},
"_requested": {
"type": "tag",
"registry": true,
"raw": "swagger-ui-dist",
"name": "swagger-ui-dist",
"escapedName": "swagger-ui-dist",
"rawSpec": "",
"saveSpec": null,
"fetchSpec": "latest"
},
"_requiredBy": [
"#USER",
"/"
],
"_resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.5.0.tgz",
"_shasum": "26ebf3311a9a60fe9d170612eed5282a65bc9226",
"_spec": "swagger-ui-dist",
"_where": "C:\\Projects\\drf_openapi",
"bugs": {
"url": "https://github.com/swagger-api/swagger-ui/issues"
},
"bundleDependencies": false,
"contributors": [
{
"url": "in alphabetical order"
},
{
"name": "Anna Bodnia",
"email": "anna.bodnia@gmail.com"
},
{
"name": "Buu Nguyen",
"email": "buunguyen@gmail.com"
},
{
"name": "Josh Ponelat",
"email": "jponelat@gmail.com"
},
{
"name": "Kyle Shockey",
"email": "kyleshockey1@gmail.com"
},
{
"name": "Robert Barnwell",
"email": "robert@robertismy.name"
},
{
"name": "Sahar Jafari",
"email": "shr.jafari@gmail.com"
}
],
"dependencies": {},
"deprecated": false,
"description": "[![NPM version](https://badge.fury.io/js/swagger-ui-dist.svg)](http://badge.fury.io/js/swagger-ui-dist)",
"devDependencies": {},
"homepage": "https://github.com/swagger-api/swagger-ui#readme",
"license": "Apache-2.0",
"main": "index.js",
"name": "swagger-ui-dist",
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/swagger-api/swagger-ui.git"
},
"version": "3.5.0"
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"version":3,"sources":[],"names":[],"mappings":"","file":"swagger-ui.css","sourceRoot":""}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,44 @@
{% load static %}
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
margin: 0;
padding: 0;
}
{% if not request.version %}
span.api-info-version {
display: none;
}
{% endif %}
</style>
</head>
<body>
<script id="redoc-settings">{{ swagger_settings | safe }}</script>
<script>
var currentPath = window.location.protocol + "//" + window.location.host + window.location.pathname;
var specURL = currentPath + '?format=openapi';
var redoc = document.createElement("redoc");
redoc.setAttribute("spec-url", specURL);
var redocSettings = {};
redocSettings = {{ redoc_settings | safe }};
if (redocSettings.lazyRendering) {
redoc.setAttribute("lazy-rendering", '');
}
if (redocSettings.pathInMiddle) {
redoc.setAttribute("path-in-middle-panel", '');
}
if (redocSettings.hideHostname) {
redoc.setAttribute("hide-hostname", '');
}
redoc.setAttribute("expand-responses", redocSettings.expandResponses);
document.body.appendChild(redoc);
</script>
<script src="{% static 'drf-swagger/redoc/redoc.min.js' %}"> </script>
</body>
</html>

View File

@ -0,0 +1,203 @@
<!-- HTML for static distribution bundle build -->
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
<link
href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700"
rel="stylesheet">
<link rel="stylesheet" type="text/css" href="{% static 'drf-swagger/swagger-ui-dist/swagger-ui.css' %}">
<link rel="icon" type="image/png" href="{% static 'drf-swagger/swagger-ui-dist/favicon-32x32.png' %}"
sizes="32x32"/>
<link rel="icon" type="image/png" href="{% static 'drf-swagger/swagger-ui-dist/favicon-16x16.png' %}"
sizes="16x16"/>
<style>
html {
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 0;
background: #fafafa;
}
#django-session-auth {
margin-right: 8px;
}
#django-session-auth.hidden {
display: none;
}
#django-session-auth > div {
display: inline-block;
}
#django-session-auth .btn.authorize {
padding: 10px 23px;
}
#django-session-auth .btn.authorize a {
color: #49cc90;
text-decoration: none;
}
#django-session-auth .hello {
margin-right: 5px;
}
#django-session-auth .hello .django-session {
font-weight: bold;
}
.label {
display: inline;
padding: .2em .6em .3em;
font-weight: 700;
line-height: 1;
color: #fff;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
border-radius: .25em;
}
.label-primary {
background-color: #337ab7;
}
.divider {
margin-right: 8px;
background: #16222c44;
width: 2px;
}
</style>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" style="position:absolute;width:0;height:0">
<defs>
<symbol viewBox="0 0 20 20" id="unlocked">
<path
d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path>
</symbol>
<symbol viewBox="0 0 20 20" id="locked">
<path
d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8zM12 8H8V5.199C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8z"></path>
</symbol>
<symbol viewBox="0 0 20 20" id="close">
<path
d="M14.348 14.849c-.469.469-1.229.469-1.697 0L10 11.819l-2.651 3.029c-.469.469-1.229.469-1.697 0-.469-.469-.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-.469-.469-.469-1.228 0-1.697.469-.469 1.228-.469 1.697 0L10 8.183l2.651-3.031c.469-.469 1.228-.469 1.697 0 .469.469.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c.469.469.469 1.229 0 1.698z"></path>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow">
<path
d="M13.25 10L6.109 2.58c-.268-.27-.268-.707 0-.979.268-.27.701-.27.969 0l7.83 7.908c.268.271.268.709 0 .979l-7.83 7.908c-.268.271-.701.27-.969 0-.268-.269-.268-.707 0-.979L13.25 10z"></path>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow-down">
<path
d="M17.418 6.109c.272-.268.709-.268.979 0s.271.701 0 .969l-7.908 7.83c-.27.268-.707.268-.979 0l-7.908-7.83c-.27-.268-.27-.701 0-.969.271-.268.709-.268.979 0L10 13.25l7.418-7.141z"></path>
</symbol>
<symbol viewBox="0 0 24 24" id="jump-to">
<path d="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z"></path>
</symbol>
<symbol viewBox="0 0 24 24" id="expand">
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"></path>
</symbol>
</defs>
</svg>
<div id="swagger-ui"></div>
<script src="{% static 'drf-swagger/swagger-ui-dist/swagger-ui-bundle.js' %}"></script>
<script src="{% static 'drf-swagger/swagger-ui-dist/swagger-ui-standalone-preset.js' %}"></script>
<script src="{% static 'drf-swagger/insQ.min.js' %}"></script>
<script>
window.onload = function () {
var currentPath = window.location.protocol + "//" + window.location.host + window.location.pathname;
var specURL = currentPath + '?format=openapi';
var swaggerConfig = {
url: specURL,
dom_id: '#swagger-ui',
displayOperationId: true,
displayRequestDuration: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
};
var swaggerSettings = {};
swaggerSettings = {{ swagger_settings | safe }};
console.log(swaggerSettings);
for (var p in swaggerSettings) {
if (swaggerSettings.hasOwnProperty(p)) {
swaggerConfig[p] = swaggerSettings[p];
}
}
window.ui = SwaggerUIBundle(swaggerConfig);
insertionQ('.auth-wrapper .authorize').every(function (element) {
var authWrapper = document.querySelector('.auth-wrapper');
var authorizeButton = document.querySelector('.auth-wrapper .authorize');
var djangoSessionAuth = document.querySelector('#django-session-auth');
authWrapper.insertBefore(djangoSessionAuth, authorizeButton);
djangoSessionAuth.classList.remove("hidden");
var divider = document.createElement("div");
divider.classList.add("divider");
authWrapper.insertBefore(divider, authorizeButton);
});
}
</script>
<div id="django-session-auth" class="hidden">
{% if USE_SESSION_AUTH %}
{% csrf_token %}
{% if request.user.is_authenticated %}
<div class="hello">
<span class="django-session">Django</span> <span class="label label-primary">{{ request.user }}</span>
</div>
{% endif %}
{% if request.user.is_authenticated %}
<div class='btn authorize'>
<a id="auth" class="header__btn" href="{{ LOGOUT_URL }}?next={{ request.path }}" data-sw-translate>
Django Logout
</a>
</div>
{% else %}
<div class='btn authorize'>
<a id="auth" class="header__btn" href="{{ LOGIN_URL }}?next={{ request.path }}" data-sw-translate>
Django Login
</a>
</div>
{% endif %}
{% endif %}
</div>
</body>
</html>

View File

@ -0,0 +1,113 @@
import warnings
from functools import wraps
from django.utils.cache import add_never_cache_headers
from django.utils.decorators import available_attrs
from django.views.decorators.cache import cache_page
from django.views.decorators.vary import vary_on_headers
from rest_framework import exceptions, renderers
from rest_framework.response import Response
from rest_framework.settings import api_settings
from rest_framework.views import APIView
from .generators import OpenAPISchemaGenerator
from .renderers import (
SwaggerJSONRenderer, SwaggerYAMLRenderer, SwaggerUIRenderer, ReDocRenderer, OpenAPIRenderer,
)
SPEC_RENDERERS = (SwaggerYAMLRenderer, SwaggerJSONRenderer, OpenAPIRenderer)
SPEC_RENDERERS = {
False: tuple(renderer.with_validators([]) for renderer in SPEC_RENDERERS),
True: SPEC_RENDERERS,
}
UI_RENDERERS = {
'swagger': (SwaggerUIRenderer, ReDocRenderer),
'redoc': (ReDocRenderer, SwaggerUIRenderer),
}
def deferred_never_cache(view_func):
"""
Decorator that adds headers to a response so that it will
never be cached.
"""
@wraps(view_func, assigned=available_attrs(view_func))
def _wrapped_view_func(request, *args, **kwargs):
response = view_func(request, *args, **kwargs)
# It is necessary to defer the add_never_cache_headers call because
# cache_page also defers its cache update operation; if we do not defer
# this, cache_page will give up because it will see and obey the "never
# cache" headers
def callback(response):
add_never_cache_headers(response)
return response
response.add_post_render_callback(callback)
return response
return _wrapped_view_func
def get_schema_view(info, url=None, patterns=None, urlconf=None, *, public=False, validate=False,
authentication_classes=api_settings.DEFAULT_AUTHENTICATION_CLASSES,
permission_classes=api_settings.DEFAULT_PERMISSION_CLASSES):
_auth_classes = authentication_classes
_perm_classes = permission_classes
_public = public
class SchemaView(APIView):
_ignore_model_permissions = True
schema = None # exclude from schema
public = _public
authentication_classes = _auth_classes
permission_classes = _perm_classes
renderer_classes = SPEC_RENDERERS[bool(validate)]
def __init__(self, **kwargs):
super(SchemaView, self).__init__(**kwargs)
if self.renderer_classes is None:
if renderers.BrowsableAPIRenderer in api_settings.DEFAULT_RENDERER_CLASSES:
self.renderer_classes = [
renderers.CoreJSONRenderer,
renderers.BrowsableAPIRenderer,
]
else:
self.renderer_classes = [renderers.CoreJSONRenderer]
def get(self, request, version='', format=None):
generator = OpenAPISchemaGenerator(info, version, url, patterns, urlconf)
schema = generator.get_schema(request, self.public)
if schema is None:
raise exceptions.PermissionDenied()
return Response(schema)
@classmethod
def _cached(cls, view, cache_timeout, cache_kwargs):
if cache_timeout != 0:
view = vary_on_headers('Cookie', 'Authorization', 'Accept')(view)
view = cache_page(cache_timeout, **cache_kwargs)(view)
view = deferred_never_cache(view) # disable in-browser caching
elif cache_kwargs:
warnings.warn("cache_kwargs ignored because cache_timeout is 0 (disabled)")
return view
@classmethod
def as_cached_view(cls, cache_timeout=0, **cache_kwargs):
return cls._cached(cls.as_view(), cache_timeout, cache_kwargs)
@classmethod
def without_ui(cls, cache_timeout=0, **cache_kwargs):
renderer_classes = SPEC_RENDERERS[bool(validate)]
return cls._cached(cls.as_view(renderer_classes=renderer_classes), cache_timeout, cache_kwargs)
@classmethod
def with_ui(cls, renderer='swagger', cache_timeout=0, **cache_kwargs):
assert renderer in UI_RENDERERS, "supported default renderers are " + ", ".join(UI_RENDERERS)
renderer_classes = (*UI_RENDERERS[renderer], *SPEC_RENDERERS[bool(validate)])
view = cls.as_view(renderer_classes=renderer_classes)
return cls._cached(view, cache_timeout, cache_kwargs)
return SchemaView

12
package-lock.json generated 100644
View File

@ -0,0 +1,12 @@
{
"name": "drf-swagger",
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"swagger-ui-dist": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.5.0.tgz",
"integrity": "sha1-JuvzMRqaYP6dFwYS7tUoKmW8kiY="
}
}
}

7
package.json 100644
View File

@ -0,0 +1,7 @@
{
"name": "drf-swagger",
"dependencies": {
"redoc": "^2.0.0-alpha.4",
"swagger-ui-dist": "^3.5.0"
}
}

5
requirements.txt 100644
View File

@ -0,0 +1,5 @@
djangorestframework>=3.7.3
django>=1.11.7
coreapi>=2.3.3
openapi_codec>=1.3.2
ruamel.yaml>=0.15.34

View File

View File

@ -0,0 +1 @@
pygments>=2.2.0

View File

@ -0,0 +1,2 @@
flex>=6.11.1
swagger-spec-validator>=2.1.0

40
setup.py 100644
View File

@ -0,0 +1,40 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from setuptools import setup, find_packages
def read_req(req_file):
with open(req_file) as req:
return [line for line in req.readlines() if line and not line.isspace()]
requirements = read_req('requirements.txt')
requirements_validation = read_req('requirements_validation.txt')
setup(
name='drf-swagger',
version='1.0.0rc1',
packages=find_packages(include=['drf_swagger']),
include_package_data=True,
install_requires=requirements,
extras_require={
'validation': requirements_validation
},
license='BSD License',
description='Automated generation of real Swagger/OpenAPI 2.0 schemas from Django Rest Framework code.',
long_description='',
url='https://github.com/axnsan12/drf-swagger',
author='Cristi V.',
author_email='cristi@cvjd.me',
keywords='drf-swagger drf django rest-framework schema swagger openapi ',
classifiers=[
'Framework :: Django',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
],
)

BIN
testproj/db.sqlite3 100644

Binary file not shown.

22
testproj/manage.py 100644
View File

@ -0,0 +1,22 @@
#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testproj.settings")
try:
from django.core.management import execute_from_command_line
except ImportError:
# The above import may fail for some other reason. Ensure that the
# issue is really that Django is missing to avoid masking other
# exceptions on Python 2.
try:
import django
except ImportError:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
)
raise
execute_from_command_line(sys.argv)

View File

View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,19 @@
from django.db import models
from pygments.lexers import get_all_lexers
from pygments.styles import get_all_styles
LEXERS = [item for item in get_all_lexers() if item[1]]
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
STYLE_CHOICES = sorted((item, item) for item in get_all_styles())
class Snippet(models.Model):
created = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=100, blank=True, default='')
code = models.TextField()
linenos = models.BooleanField(default=False)
language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)
class Meta:
ordering = ('created', )

View File

@ -0,0 +1,48 @@
from rest_framework.status import HTTP_400_BAD_REQUEST
from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES
class LanguageSerializer(serializers.Serializer):
name = serializers.ChoiceField(
choices=LANGUAGE_CHOICES, default='python', help_text='The name of the programming language')
class ExampleProjectsSerializer(serializers.Serializer):
project_name = serializers.CharField(help_text='Name of the project')
github_repo = serializers.CharField(required=True, help_text='Github repository of the project')
class SnippetSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
title = serializers.CharField(required=False, allow_blank=True, max_length=100)
code = serializers.CharField(style={'base_template': 'textarea.html'})
linenos = serializers.BooleanField(required=False)
language = LanguageSerializer()
style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')
lines = serializers.ListField(child=serializers.IntegerField(), allow_empty=True, allow_null=True, required=False)
example_projects = serializers.ListSerializer(child=ExampleProjectsSerializer())
class Meta:
error_status_codes = {
HTTP_400_BAD_REQUEST: 'Bad Request'
}
def create(self, validated_data):
"""
Create and return a new `Snippet` instance, given the validated data.
"""
return Snippet.objects.create(**validated_data)
def update(self, instance, validated_data):
"""
Update and return an existing `Snippet` instance, given the validated data.
"""
instance.title = validated_data.get('title', instance.title)
instance.code = validated_data.get('code', instance.code)
instance.linenos = validated_data.get('linenos', instance.linenos)
instance.language = validated_data.get('language', instance.language)
instance.style = validated_data.get('style', instance.style)
instance.save()
return instance

View File

@ -0,0 +1,8 @@
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'$', views.SnippetList.as_view()),
url(r'^(?P<pk>[0-9]+)/$', views.SnippetDetail.as_view()),
]

View File

@ -0,0 +1,13 @@
from rest_framework import generics
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer

View File

View File

@ -0,0 +1,116 @@
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '!z1yj(9uz)zk0gg@5--j)bc4h^i!8))r^dezco8glf190e0&#p'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'drf_swagger',
'snippets'
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'testproj.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'testproj.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/
STATIC_URL = '/static/'
REST_FRAMEWORK = {
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning'
}

View File

@ -0,0 +1,29 @@
from django.conf.urls import url, include
from django.contrib import admin
from drf_swagger.views import get_schema_view
from drf_swagger import openapi
from rest_framework import permissions
schema_view = get_schema_view(
openapi.Info(
title="Snippets API",
default_version='v1',
description="Test description",
terms_of_service="*Some TOS*",
contact=openapi.Contact(email="cristi@cvjd.me"),
license=openapi.License("BSD License"),
),
validate=True,
public=False,
permission_classes=(permissions.AllowAny,),
)
urlpatterns = [
url(r'^swagger(?P<format>.json|.yaml)$', schema_view.without_ui(cache_timeout=None), name='schema-json'),
url(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=None), name='schema-swagger-ui'),
url(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=None), name='schema-redoc'),
url(r'^admin/', admin.site.urls),
url(f'^snippets/', include('snippets.urls')),
]

View File

@ -0,0 +1,7 @@
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testproj.settings")
application = get_wsgi_application()