Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/django/http/response.py: 36%

383 statements  

« prev     ^ index     » next       coverage.py v7.0.5, created at 2023-01-17 06:13 +0000

1import datetime 

2import io 

3import json 

4import mimetypes 

5import os 

6import re 

7import sys 

8import time 

9import warnings 

10from email.header import Header 

11from http.client import responses 

12from urllib.parse import urlparse 

13 

14from asgiref.sync import async_to_sync, sync_to_async 

15 

16from django.conf import settings 

17from django.core import signals, signing 

18from django.core.exceptions import DisallowedRedirect 

19from django.core.serializers.json import DjangoJSONEncoder 

20from django.http.cookie import SimpleCookie 

21from django.utils import timezone 

22from django.utils.datastructures import CaseInsensitiveMapping 

23from django.utils.encoding import iri_to_uri 

24from django.utils.http import content_disposition_header, http_date 

25from django.utils.regex_helper import _lazy_re_compile 

26 

27_charset_from_content_type_re = _lazy_re_compile( 

28 r";\s*charset=(?P<charset>[^\s;]+)", re.I 

29) 

30 

31 

32class ResponseHeaders(CaseInsensitiveMapping): 

33 def __init__(self, data): 

34 """ 

35 Populate the initial data using __setitem__ to ensure values are 

36 correctly encoded. 

37 """ 

38 self._store = {} 

39 if data: 

40 for header, value in self._unpack_items(data): 

41 self[header] = value 

42 

43 def _convert_to_charset(self, value, charset, mime_encode=False): 

44 """ 

45 Convert headers key/value to ascii/latin-1 native strings. 

46 `charset` must be 'ascii' or 'latin-1'. If `mime_encode` is True and 

47 `value` can't be represented in the given charset, apply MIME-encoding. 

48 """ 

49 try: 

50 if isinstance(value, str): 

51 # Ensure string is valid in given charset 

52 value.encode(charset) 

53 elif isinstance(value, bytes): 

54 # Convert bytestring using given charset 

55 value = value.decode(charset) 

56 else: 

57 value = str(value) 

58 # Ensure string is valid in given charset. 

59 value.encode(charset) 

60 if "\n" in value or "\r" in value: 

61 raise BadHeaderError( 

62 f"Header values can't contain newlines (got {value!r})" 

63 ) 

64 except UnicodeError as e: 

65 # Encoding to a string of the specified charset failed, but we 

66 # don't know what type that value was, or if it contains newlines, 

67 # which we may need to check for before sending it to be 

68 # encoded for multiple character sets. 

69 if (isinstance(value, bytes) and (b"\n" in value or b"\r" in value)) or ( 

70 isinstance(value, str) and ("\n" in value or "\r" in value) 

71 ): 

72 raise BadHeaderError( 

73 f"Header values can't contain newlines (got {value!r})" 

74 ) from e 

75 if mime_encode: 

76 value = Header(value, "utf-8", maxlinelen=sys.maxsize).encode() 

77 else: 

78 e.reason += ", HTTP response headers must be in %s format" % charset 

79 raise 

80 return value 

81 

82 def __delitem__(self, key): 

83 self.pop(key) 

84 

85 def __setitem__(self, key, value): 

86 key = self._convert_to_charset(key, "ascii") 

87 value = self._convert_to_charset(value, "latin-1", mime_encode=True) 

88 self._store[key.lower()] = (key, value) 

89 

90 def pop(self, key, default=None): 

91 return self._store.pop(key.lower(), default) 

92 

93 def setdefault(self, key, value): 

94 if key not in self: 

95 self[key] = value 

96 

97 

98class BadHeaderError(ValueError): 

99 pass 

100 

101 

102class HttpResponseBase: 

103 """ 

104 An HTTP response base class with dictionary-accessed headers. 

105 

106 This class doesn't handle content. It should not be used directly. 

107 Use the HttpResponse and StreamingHttpResponse subclasses instead. 

108 """ 

109 

110 status_code = 200 

111 

112 def __init__( 

113 self, content_type=None, status=None, reason=None, charset=None, headers=None 

114 ): 

115 self.headers = ResponseHeaders(headers) 

116 self._charset = charset 

117 if "Content-Type" not in self.headers: 

118 if content_type is None: 

119 content_type = f"text/html; charset={self.charset}" 

120 self.headers["Content-Type"] = content_type 

121 elif content_type: 

122 raise ValueError( 

123 "'headers' must not contain 'Content-Type' when the " 

124 "'content_type' parameter is provided." 

125 ) 

126 self._resource_closers = [] 

127 # This parameter is set by the handler. It's necessary to preserve the 

128 # historical behavior of request_finished. 

129 self._handler_class = None 

130 self.cookies = SimpleCookie() 

131 self.closed = False 

132 if status is not None: 

133 try: 

134 self.status_code = int(status) 

135 except (ValueError, TypeError): 

136 raise TypeError("HTTP status code must be an integer.") 

137 

138 if not 100 <= self.status_code <= 599: 

139 raise ValueError("HTTP status code must be an integer from 100 to 599.") 

140 self._reason_phrase = reason 

141 

142 @property 

143 def reason_phrase(self): 

144 if self._reason_phrase is not None: 

145 return self._reason_phrase 

146 # Leave self._reason_phrase unset in order to use the default 

147 # reason phrase for status code. 

148 return responses.get(self.status_code, "Unknown Status Code") 

149 

150 @reason_phrase.setter 

151 def reason_phrase(self, value): 

152 self._reason_phrase = value 

153 

154 @property 

155 def charset(self): 

156 if self._charset is not None: 

157 return self._charset 

158 # The Content-Type header may not yet be set, because the charset is 

159 # being inserted *into* it. 

160 if content_type := self.headers.get("Content-Type"): 

161 if matched := _charset_from_content_type_re.search(content_type): 

162 # Extract the charset and strip its double quotes. 

163 # Note that having parsed it from the Content-Type, we don't 

164 # store it back into the _charset for later intentionally, to 

165 # allow for the Content-Type to be switched again later. 

166 return matched["charset"].replace('"', "") 

167 return settings.DEFAULT_CHARSET 

168 

169 @charset.setter 

170 def charset(self, value): 

171 self._charset = value 

172 

173 def serialize_headers(self): 

174 """HTTP headers as a bytestring.""" 

175 return b"\r\n".join( 

176 [ 

177 key.encode("ascii") + b": " + value.encode("latin-1") 

178 for key, value in self.headers.items() 

179 ] 

180 ) 

181 

182 __bytes__ = serialize_headers 

183 

184 @property 

185 def _content_type_for_repr(self): 

186 return ( 

187 ', "%s"' % self.headers["Content-Type"] 

188 if "Content-Type" in self.headers 

189 else "" 

190 ) 

191 

192 def __setitem__(self, header, value): 

193 self.headers[header] = value 

194 

195 def __delitem__(self, header): 

196 del self.headers[header] 

197 

198 def __getitem__(self, header): 

199 return self.headers[header] 

200 

201 def has_header(self, header): 

202 """Case-insensitive check for a header.""" 

203 return header in self.headers 

204 

205 __contains__ = has_header 

206 

207 def items(self): 

208 return self.headers.items() 

209 

210 def get(self, header, alternate=None): 

211 return self.headers.get(header, alternate) 

212 

213 def set_cookie( 

214 self, 

215 key, 

216 value="", 

217 max_age=None, 

218 expires=None, 

219 path="/", 

220 domain=None, 

221 secure=False, 

222 httponly=False, 

223 samesite=None, 

224 ): 

225 """ 

226 Set a cookie. 

227 

228 ``expires`` can be: 

229 - a string in the correct format, 

230 - a naive ``datetime.datetime`` object in UTC, 

231 - an aware ``datetime.datetime`` object in any time zone. 

232 If it is a ``datetime.datetime`` object then calculate ``max_age``. 

233 

234 ``max_age`` can be: 

235 - int/float specifying seconds, 

236 - ``datetime.timedelta`` object. 

237 """ 

238 self.cookies[key] = value 

239 if expires is not None: 

240 if isinstance(expires, datetime.datetime): 

241 if timezone.is_naive(expires): 

242 expires = timezone.make_aware(expires, datetime.timezone.utc) 

243 delta = expires - datetime.datetime.now(tz=datetime.timezone.utc) 

244 # Add one second so the date matches exactly (a fraction of 

245 # time gets lost between converting to a timedelta and 

246 # then the date string). 

247 delta += datetime.timedelta(seconds=1) 

248 # Just set max_age - the max_age logic will set expires. 

249 expires = None 

250 if max_age is not None: 

251 raise ValueError("'expires' and 'max_age' can't be used together.") 

252 max_age = max(0, delta.days * 86400 + delta.seconds) 

253 else: 

254 self.cookies[key]["expires"] = expires 

255 else: 

256 self.cookies[key]["expires"] = "" 

257 if max_age is not None: 

258 if isinstance(max_age, datetime.timedelta): 

259 max_age = max_age.total_seconds() 

260 self.cookies[key]["max-age"] = int(max_age) 

261 # IE requires expires, so set it if hasn't been already. 

262 if not expires: 

263 self.cookies[key]["expires"] = http_date(time.time() + max_age) 

264 if path is not None: 

265 self.cookies[key]["path"] = path 

266 if domain is not None: 

267 self.cookies[key]["domain"] = domain 

268 if secure: 

269 self.cookies[key]["secure"] = True 

270 if httponly: 

271 self.cookies[key]["httponly"] = True 

272 if samesite: 

273 if samesite.lower() not in ("lax", "none", "strict"): 

274 raise ValueError('samesite must be "lax", "none", or "strict".') 

275 self.cookies[key]["samesite"] = samesite 

276 

277 def setdefault(self, key, value): 

278 """Set a header unless it has already been set.""" 

279 self.headers.setdefault(key, value) 

280 

281 def set_signed_cookie(self, key, value, salt="", **kwargs): 

282 value = signing.get_cookie_signer(salt=key + salt).sign(value) 

283 return self.set_cookie(key, value, **kwargs) 

284 

285 def delete_cookie(self, key, path="/", domain=None, samesite=None): 

286 # Browsers can ignore the Set-Cookie header if the cookie doesn't use 

287 # the secure flag and: 

288 # - the cookie name starts with "__Host-" or "__Secure-", or 

289 # - the samesite is "none". 

290 secure = key.startswith(("__Secure-", "__Host-")) or ( 

291 samesite and samesite.lower() == "none" 

292 ) 

293 self.set_cookie( 

294 key, 

295 max_age=0, 

296 path=path, 

297 domain=domain, 

298 secure=secure, 

299 expires="Thu, 01 Jan 1970 00:00:00 GMT", 

300 samesite=samesite, 

301 ) 

302 

303 # Common methods used by subclasses 

304 

305 def make_bytes(self, value): 

306 """Turn a value into a bytestring encoded in the output charset.""" 

307 # Per PEP 3333, this response body must be bytes. To avoid returning 

308 # an instance of a subclass, this function returns `bytes(value)`. 

309 # This doesn't make a copy when `value` already contains bytes. 

310 

311 # Handle string types -- we can't rely on force_bytes here because: 

312 # - Python attempts str conversion first 

313 # - when self._charset != 'utf-8' it re-encodes the content 

314 if isinstance(value, (bytes, memoryview)): 

315 return bytes(value) 

316 if isinstance(value, str): 

317 return bytes(value.encode(self.charset)) 

318 # Handle non-string types. 

319 return str(value).encode(self.charset) 

320 

321 # These methods partially implement the file-like object interface. 

322 # See https://docs.python.org/library/io.html#io.IOBase 

323 

324 # The WSGI server must call this method upon completion of the request. 

325 # See http://blog.dscpl.com.au/2012/10/obligations-for-calling-close-on.html 

326 def close(self): 

327 for closer in self._resource_closers: 

328 try: 

329 closer() 

330 except Exception: 

331 pass 

332 # Free resources that were still referenced. 

333 self._resource_closers.clear() 

334 self.closed = True 

335 signals.request_finished.send(sender=self._handler_class) 

336 

337 def write(self, content): 

338 raise OSError("This %s instance is not writable" % self.__class__.__name__) 

339 

340 def flush(self): 

341 pass 

342 

343 def tell(self): 

344 raise OSError( 

345 "This %s instance cannot tell its position" % self.__class__.__name__ 

346 ) 

347 

348 # These methods partially implement a stream-like object interface. 

349 # See https://docs.python.org/library/io.html#io.IOBase 

350 

351 def readable(self): 

352 return False 

353 

354 def seekable(self): 

355 return False 

356 

357 def writable(self): 

358 return False 

359 

360 def writelines(self, lines): 

361 raise OSError("This %s instance is not writable" % self.__class__.__name__) 

362 

363 

364class HttpResponse(HttpResponseBase): 

365 """ 

366 An HTTP response class with a string as content. 

367 

368 This content can be read, appended to, or replaced. 

369 """ 

370 

371 streaming = False 

372 non_picklable_attrs = frozenset( 

373 [ 

374 "resolver_match", 

375 # Non-picklable attributes added by test clients. 

376 "client", 

377 "context", 

378 "json", 

379 "templates", 

380 ] 

381 ) 

382 

383 def __init__(self, content=b"", *args, **kwargs): 

384 super().__init__(*args, **kwargs) 

385 # Content is a bytestring. See the `content` property methods. 

386 self.content = content 

387 

388 def __getstate__(self): 

389 obj_dict = self.__dict__.copy() 

390 for attr in self.non_picklable_attrs: 

391 if attr in obj_dict: 

392 del obj_dict[attr] 

393 return obj_dict 

394 

395 def __repr__(self): 

396 return "<%(cls)s status_code=%(status_code)d%(content_type)s>" % { 

397 "cls": self.__class__.__name__, 

398 "status_code": self.status_code, 

399 "content_type": self._content_type_for_repr, 

400 } 

401 

402 def serialize(self): 

403 """Full HTTP message, including headers, as a bytestring.""" 

404 return self.serialize_headers() + b"\r\n\r\n" + self.content 

405 

406 __bytes__ = serialize 

407 

408 @property 

409 def content(self): 

410 return b"".join(self._container) 

411 

412 @content.setter 

413 def content(self, value): 

414 # Consume iterators upon assignment to allow repeated iteration. 

415 if hasattr(value, "__iter__") and not isinstance( 

416 value, (bytes, memoryview, str) 

417 ): 

418 content = b"".join(self.make_bytes(chunk) for chunk in value) 

419 if hasattr(value, "close"): 

420 try: 

421 value.close() 

422 except Exception: 

423 pass 

424 else: 

425 content = self.make_bytes(value) 

426 # Create a list of properly encoded bytestrings to support write(). 

427 self._container = [content] 

428 

429 def __iter__(self): 

430 return iter(self._container) 

431 

432 def write(self, content): 

433 self._container.append(self.make_bytes(content)) 

434 

435 def tell(self): 

436 return len(self.content) 

437 

438 def getvalue(self): 

439 return self.content 

440 

441 def writable(self): 

442 return True 

443 

444 def writelines(self, lines): 

445 for line in lines: 

446 self.write(line) 

447 

448 

449class StreamingHttpResponse(HttpResponseBase): 

450 """ 

451 A streaming HTTP response class with an iterator as content. 

452 

453 This should only be iterated once, when the response is streamed to the 

454 client. However, it can be appended to or replaced with a new iterator 

455 that wraps the original content (or yields entirely new content). 

456 """ 

457 

458 streaming = True 

459 

460 def __init__(self, streaming_content=(), *args, **kwargs): 

461 super().__init__(*args, **kwargs) 

462 # `streaming_content` should be an iterable of bytestrings. 

463 # See the `streaming_content` property methods. 

464 self.streaming_content = streaming_content 

465 

466 def __repr__(self): 

467 return "<%(cls)s status_code=%(status_code)d%(content_type)s>" % { 

468 "cls": self.__class__.__qualname__, 

469 "status_code": self.status_code, 

470 "content_type": self._content_type_for_repr, 

471 } 

472 

473 @property 

474 def content(self): 

475 raise AttributeError( 

476 "This %s instance has no `content` attribute. Use " 

477 "`streaming_content` instead." % self.__class__.__name__ 

478 ) 

479 

480 @property 

481 def streaming_content(self): 

482 if self.is_async: 

483 # pull to lexical scope to capture fixed reference in case 

484 # streaming_content is set again later. 

485 _iterator = self._iterator 

486 

487 async def awrapper(): 

488 async for part in _iterator: 

489 yield self.make_bytes(part) 

490 

491 return awrapper() 

492 else: 

493 return map(self.make_bytes, self._iterator) 

494 

495 @streaming_content.setter 

496 def streaming_content(self, value): 

497 self._set_streaming_content(value) 

498 

499 def _set_streaming_content(self, value): 

500 # Ensure we can never iterate on "value" more than once. 

501 try: 

502 self._iterator = iter(value) 

503 self.is_async = False 

504 except TypeError: 

505 self._iterator = value.__aiter__() 

506 self.is_async = True 

507 if hasattr(value, "close"): 

508 self._resource_closers.append(value.close) 

509 

510 def __iter__(self): 

511 try: 

512 return iter(self.streaming_content) 

513 except TypeError: 

514 warnings.warn( 

515 "StreamingHttpResponse must consume asynchronous iterators in order to " 

516 "serve them synchronously. Use a synchronous iterator instead.", 

517 Warning, 

518 ) 

519 

520 # async iterator. Consume in async_to_sync and map back. 

521 async def to_list(_iterator): 

522 as_list = [] 

523 async for chunk in _iterator: 

524 as_list.append(chunk) 

525 return as_list 

526 

527 return map(self.make_bytes, iter(async_to_sync(to_list)(self._iterator))) 

528 

529 async def __aiter__(self): 

530 try: 

531 async for part in self.streaming_content: 

532 yield part 

533 except TypeError: 

534 warnings.warn( 

535 "StreamingHttpResponse must consume synchronous iterators in order to " 

536 "serve them asynchronously. Use an asynchronous iterator instead.", 

537 Warning, 

538 ) 

539 # sync iterator. Consume via sync_to_async and yield via async 

540 # generator. 

541 for part in await sync_to_async(list)(self.streaming_content): 

542 yield part 

543 

544 def getvalue(self): 

545 return b"".join(self.streaming_content) 

546 

547 

548class FileResponse(StreamingHttpResponse): 

549 """ 

550 A streaming HTTP response class optimized for files. 

551 """ 

552 

553 block_size = 4096 

554 

555 def __init__(self, *args, as_attachment=False, filename="", **kwargs): 

556 self.as_attachment = as_attachment 

557 self.filename = filename 

558 self._no_explicit_content_type = ( 

559 "content_type" not in kwargs or kwargs["content_type"] is None 

560 ) 

561 super().__init__(*args, **kwargs) 

562 

563 def _set_streaming_content(self, value): 

564 if not hasattr(value, "read"): 

565 self.file_to_stream = None 

566 return super()._set_streaming_content(value) 

567 

568 self.file_to_stream = filelike = value 

569 if hasattr(filelike, "close"): 

570 self._resource_closers.append(filelike.close) 

571 value = iter(lambda: filelike.read(self.block_size), b"") 

572 self.set_headers(filelike) 

573 super()._set_streaming_content(value) 

574 

575 def set_headers(self, filelike): 

576 """ 

577 Set some common response headers (Content-Length, Content-Type, and 

578 Content-Disposition) based on the `filelike` response content. 

579 """ 

580 filename = getattr(filelike, "name", "") 

581 filename = filename if isinstance(filename, str) else "" 

582 seekable = hasattr(filelike, "seek") and ( 

583 not hasattr(filelike, "seekable") or filelike.seekable() 

584 ) 

585 if hasattr(filelike, "tell"): 

586 if seekable: 

587 initial_position = filelike.tell() 

588 filelike.seek(0, io.SEEK_END) 

589 self.headers["Content-Length"] = filelike.tell() - initial_position 

590 filelike.seek(initial_position) 

591 elif hasattr(filelike, "getbuffer"): 

592 self.headers["Content-Length"] = ( 

593 filelike.getbuffer().nbytes - filelike.tell() 

594 ) 

595 elif os.path.exists(filename): 

596 self.headers["Content-Length"] = ( 

597 os.path.getsize(filename) - filelike.tell() 

598 ) 

599 elif seekable: 

600 self.headers["Content-Length"] = sum( 

601 iter(lambda: len(filelike.read(self.block_size)), 0) 

602 ) 

603 filelike.seek(-int(self.headers["Content-Length"]), io.SEEK_END) 

604 

605 filename = os.path.basename(self.filename or filename) 

606 if self._no_explicit_content_type: 

607 if filename: 

608 content_type, encoding = mimetypes.guess_type(filename) 

609 # Encoding isn't set to prevent browsers from automatically 

610 # uncompressing files. 

611 content_type = { 

612 "bzip2": "application/x-bzip", 

613 "gzip": "application/gzip", 

614 "xz": "application/x-xz", 

615 }.get(encoding, content_type) 

616 self.headers["Content-Type"] = ( 

617 content_type or "application/octet-stream" 

618 ) 

619 else: 

620 self.headers["Content-Type"] = "application/octet-stream" 

621 

622 if content_disposition := content_disposition_header( 

623 self.as_attachment, filename 

624 ): 

625 self.headers["Content-Disposition"] = content_disposition 

626 

627 

628class HttpResponseRedirectBase(HttpResponse): 

629 allowed_schemes = ["http", "https", "ftp"] 

630 

631 def __init__(self, redirect_to, *args, **kwargs): 

632 super().__init__(*args, **kwargs) 

633 self["Location"] = iri_to_uri(redirect_to) 

634 parsed = urlparse(str(redirect_to)) 

635 if parsed.scheme and parsed.scheme not in self.allowed_schemes: 

636 raise DisallowedRedirect( 

637 "Unsafe redirect to URL with protocol '%s'" % parsed.scheme 

638 ) 

639 

640 url = property(lambda self: self["Location"]) 

641 

642 def __repr__(self): 

643 return ( 

644 '<%(cls)s status_code=%(status_code)d%(content_type)s, url="%(url)s">' 

645 % { 

646 "cls": self.__class__.__name__, 

647 "status_code": self.status_code, 

648 "content_type": self._content_type_for_repr, 

649 "url": self.url, 

650 } 

651 ) 

652 

653 

654class HttpResponseRedirect(HttpResponseRedirectBase): 

655 status_code = 302 

656 

657 

658class HttpResponsePermanentRedirect(HttpResponseRedirectBase): 

659 status_code = 301 

660 

661 

662class HttpResponseNotModified(HttpResponse): 

663 status_code = 304 

664 

665 def __init__(self, *args, **kwargs): 

666 super().__init__(*args, **kwargs) 

667 del self["content-type"] 

668 

669 @HttpResponse.content.setter 

670 def content(self, value): 

671 if value: 

672 raise AttributeError( 

673 "You cannot set content to a 304 (Not Modified) response" 

674 ) 

675 self._container = [] 

676 

677 

678class HttpResponseBadRequest(HttpResponse): 

679 status_code = 400 

680 

681 

682class HttpResponseNotFound(HttpResponse): 

683 status_code = 404 

684 

685 

686class HttpResponseForbidden(HttpResponse): 

687 status_code = 403 

688 

689 

690class HttpResponseNotAllowed(HttpResponse): 

691 status_code = 405 

692 

693 def __init__(self, permitted_methods, *args, **kwargs): 

694 super().__init__(*args, **kwargs) 

695 self["Allow"] = ", ".join(permitted_methods) 

696 

697 def __repr__(self): 

698 return "<%(cls)s [%(methods)s] status_code=%(status_code)d%(content_type)s>" % { 

699 "cls": self.__class__.__name__, 

700 "status_code": self.status_code, 

701 "content_type": self._content_type_for_repr, 

702 "methods": self["Allow"], 

703 } 

704 

705 

706class HttpResponseGone(HttpResponse): 

707 status_code = 410 

708 

709 

710class HttpResponseServerError(HttpResponse): 

711 status_code = 500 

712 

713 

714class Http404(Exception): 

715 pass 

716 

717 

718class JsonResponse(HttpResponse): 

719 """ 

720 An HTTP response class that consumes data to be serialized to JSON. 

721 

722 :param data: Data to be dumped into json. By default only ``dict`` objects 

723 are allowed to be passed due to a security flaw before ECMAScript 5. See 

724 the ``safe`` parameter for more information. 

725 :param encoder: Should be a json encoder class. Defaults to 

726 ``django.core.serializers.json.DjangoJSONEncoder``. 

727 :param safe: Controls if only ``dict`` objects may be serialized. Defaults 

728 to ``True``. 

729 :param json_dumps_params: A dictionary of kwargs passed to json.dumps(). 

730 """ 

731 

732 def __init__( 

733 self, 

734 data, 

735 encoder=DjangoJSONEncoder, 

736 safe=True, 

737 json_dumps_params=None, 

738 **kwargs, 

739 ): 

740 if safe and not isinstance(data, dict): 

741 raise TypeError( 

742 "In order to allow non-dict objects to be serialized set the " 

743 "safe parameter to False." 

744 ) 

745 if json_dumps_params is None: 

746 json_dumps_params = {} 

747 kwargs.setdefault("content_type", "application/json") 

748 data = json.dumps(data, cls=encoder, **json_dumps_params) 

749 super().__init__(content=data, **kwargs)