143 lines
5.4 KiB
Python
143 lines
5.4 KiB
Python
# Copyright (c) 2015 Ansible, Inc.
|
|
# All Rights Reserved.
|
|
|
|
from django.utils.safestring import SafeText
|
|
from prometheus_client.parser import text_string_to_metric_families
|
|
|
|
# Django REST Framework
|
|
from rest_framework import renderers
|
|
from rest_framework.request import override_method
|
|
from rest_framework.utils import encoders
|
|
|
|
|
|
class SurrogateEncoder(encoders.JSONEncoder):
|
|
|
|
def encode(self, obj):
|
|
ret = super(SurrogateEncoder, self).encode(obj)
|
|
try:
|
|
ret.encode()
|
|
except UnicodeEncodeError as e:
|
|
if 'surrogates not allowed' in e.reason:
|
|
ret = ret.encode('utf-8', 'replace').decode()
|
|
return ret
|
|
|
|
|
|
class DefaultJSONRenderer(renderers.JSONRenderer):
|
|
|
|
encoder_class = SurrogateEncoder
|
|
|
|
|
|
class BrowsableAPIRenderer(renderers.BrowsableAPIRenderer):
|
|
'''
|
|
Customizations to the default browsable API renderer.
|
|
'''
|
|
|
|
def get_default_renderer(self, view):
|
|
renderer = super(BrowsableAPIRenderer, self).get_default_renderer(view)
|
|
# Always use JSON renderer for browsable OPTIONS response.
|
|
if view.request.method == 'OPTIONS' and not isinstance(renderer, renderers.JSONRenderer):
|
|
return renderers.JSONRenderer()
|
|
return renderer
|
|
|
|
def get_content(self, renderer, data, accepted_media_type, renderer_context):
|
|
if isinstance(data, SafeText):
|
|
# Older versions of Django (pre-2.0) have a py3 bug which causes
|
|
# bytestrings marked as "safe" to not actually get _treated_ as
|
|
# safe; this causes certain embedded strings (like the stdout HTML
|
|
# view) to be improperly escaped
|
|
# see: https://github.com/ansible/awx/issues/3108
|
|
# https://code.djangoproject.com/ticket/28121
|
|
return data
|
|
return super(BrowsableAPIRenderer, self).get_content(renderer, data,
|
|
accepted_media_type,
|
|
renderer_context)
|
|
|
|
def get_context(self, data, accepted_media_type, renderer_context):
|
|
# Store the associated response status to know how to populate the raw
|
|
# data form.
|
|
try:
|
|
setattr(renderer_context['view'], '_raw_data_response_status', renderer_context['response'].status_code)
|
|
setattr(renderer_context['view'], '_request', renderer_context['request'])
|
|
return super(BrowsableAPIRenderer, self).get_context(data, accepted_media_type, renderer_context)
|
|
finally:
|
|
delattr(renderer_context['view'], '_raw_data_response_status')
|
|
delattr(renderer_context['view'], '_request')
|
|
|
|
def get_raw_data_form(self, data, view, method, request):
|
|
# Set a flag on the view to indiciate to the view/serializer that we're
|
|
# creating a raw data form for the browsable API. Store the original
|
|
# request method to determine how to populate the raw data form.
|
|
if request.method in {'OPTIONS', 'DELETE'}:
|
|
return
|
|
try:
|
|
setattr(view, '_raw_data_form_marker', True)
|
|
setattr(view, '_raw_data_request_method', request.method)
|
|
return super(BrowsableAPIRenderer, self).get_raw_data_form(data, view, method, request)
|
|
finally:
|
|
delattr(view, '_raw_data_form_marker')
|
|
delattr(view, '_raw_data_request_method')
|
|
|
|
def get_rendered_html_form(self, data, view, method, request):
|
|
# Never show auto-generated form (only raw form).
|
|
obj = getattr(view, 'object', None)
|
|
if obj is None and hasattr(view, 'get_object') and hasattr(view, 'retrieve'):
|
|
try:
|
|
view.object = view.get_object()
|
|
obj = view.object
|
|
except Exception:
|
|
obj = None
|
|
with override_method(view, request, method) as request:
|
|
if not self.show_form_for_method(view, method, request, obj):
|
|
return
|
|
if method in ('DELETE', 'OPTIONS'):
|
|
return True # Don't actually need to return a form
|
|
|
|
def get_filter_form(self, data, view, request):
|
|
# Don't show filter form in browsable API.
|
|
return
|
|
|
|
|
|
class PlainTextRenderer(renderers.BaseRenderer):
|
|
|
|
media_type = 'text/plain'
|
|
format = 'txt'
|
|
|
|
def render(self, data, media_type=None, renderer_context=None):
|
|
if not isinstance(data, str):
|
|
data = str(data)
|
|
return data.encode(self.charset)
|
|
|
|
|
|
class DownloadTextRenderer(PlainTextRenderer):
|
|
|
|
format = "txt_download"
|
|
|
|
|
|
class AnsiTextRenderer(PlainTextRenderer):
|
|
|
|
media_type = 'text/plain'
|
|
format = 'ansi'
|
|
|
|
|
|
class AnsiDownloadRenderer(PlainTextRenderer):
|
|
|
|
format = "ansi_download"
|
|
|
|
|
|
class PrometheusJSONRenderer(renderers.JSONRenderer):
|
|
|
|
def render(self, data, accepted_media_type=None, renderer_context=None):
|
|
if isinstance(data, dict):
|
|
# HTTP errors are {'detail': ErrorDetail(string='...', code=...)}
|
|
return super(PrometheusJSONRenderer, self).render(
|
|
data, accepted_media_type, renderer_context
|
|
)
|
|
parsed_metrics = text_string_to_metric_families(data)
|
|
data = {}
|
|
for family in parsed_metrics:
|
|
for sample in family.samples:
|
|
data[sample[0]] = {"labels": sample[1], "value": sample[2]}
|
|
return super(PrometheusJSONRenderer, self).render(
|
|
data, accepted_media_type, renderer_context
|
|
)
|