django-orjson 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+ import decimal
5
+ from typing import Any
6
+
7
+ from django.utils.duration import duration_iso_string
8
+ from django.utils.functional import Promise
9
+
10
+
11
+ def default(obj: Any) -> Any:
12
+ if isinstance(obj, datetime.timedelta):
13
+ return duration_iso_string(obj)
14
+ if isinstance(obj, (decimal.Decimal, Promise)):
15
+ return str(obj)
16
+ raise TypeError
django_orjson/apps.py ADDED
@@ -0,0 +1,8 @@
1
+ from __future__ import annotations
2
+
3
+ from django.apps import AppConfig
4
+
5
+
6
+ class DjangoOrjsonConfig(AppConfig):
7
+ name = "django_orjson"
8
+ verbose_name = "django-orjson"
django_orjson/html.py ADDED
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable
4
+ from typing import Any
5
+
6
+ import orjson
7
+ from django.utils.html import format_html
8
+ from django.utils.safestring import SafeString, mark_safe
9
+
10
+ from django_orjson import default as _default
11
+
12
+ _json_script_escapes = {
13
+ ord(">"): "\\u003E",
14
+ ord("<"): "\\u003C",
15
+ ord("&"): "\\u0026",
16
+ }
17
+
18
+
19
+ def json_script(
20
+ value: Any,
21
+ element_id: str | None = None,
22
+ default: Callable[[Any], Any] = _default,
23
+ option: int | None = None,
24
+ ) -> SafeString:
25
+ """
26
+ Escape all the HTML/XML special characters with their unicode escapes, so
27
+ value is safe to be output anywhere except for inside a tag attribute. Wrap
28
+ the escaped JSON in a script tag.
29
+ """
30
+ json_str = (
31
+ orjson.dumps(value, default=default, option=option)
32
+ .decode()
33
+ .translate(_json_script_escapes)
34
+ )
35
+ if element_id:
36
+ template = '<script id="{}" type="application/json">{}</script>'
37
+ args: tuple[Any, ...] = (element_id, mark_safe(json_str))
38
+ else:
39
+ template = '<script type="application/json">{}</script>'
40
+ args = (mark_safe(json_str),)
41
+ return format_html(template, *args)
django_orjson/http.py ADDED
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable
4
+ from typing import Any
5
+
6
+ import orjson
7
+ from django.http import HttpResponse
8
+
9
+ from django_orjson import default
10
+
11
+
12
+ class JsonResponse(HttpResponse):
13
+ """
14
+ An HTTP response class that consumes data to be serialized to JSON.
15
+
16
+ :param data: Data to be dumped into json.
17
+ :param default: A callable that gets called for objects that can’t
18
+ otherwise be serialized. It should return a JSON encodable version of the
19
+ object or raise a TypeError.
20
+ :param option: A bitfield of orjson options.
21
+ """
22
+
23
+ def __init__(
24
+ self,
25
+ data: Any,
26
+ default: Callable[[Any], Any] = default,
27
+ option: int | None = None,
28
+ **kwargs: Any,
29
+ ) -> None:
30
+ kwargs.setdefault("content_type", "application/json")
31
+ content = orjson.dumps(data, default=default, option=option)
32
+ super().__init__(content=content, **kwargs)
django_orjson/py.typed ADDED
File without changes
@@ -0,0 +1,61 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable
4
+ from typing import Any
5
+
6
+ import orjson
7
+ from django.conf import settings
8
+ from rest_framework.exceptions import ParseError
9
+ from rest_framework.parsers import JSONParser as BaseJSONParser
10
+ from rest_framework.renderers import JSONRenderer as BaseJSONRenderer
11
+
12
+ from django_orjson import default as django_orjson_default
13
+
14
+
15
+ class JSONRenderer(BaseJSONRenderer): # type: ignore[misc]
16
+ default: Callable[[Any], Any] = staticmethod(django_orjson_default)
17
+ option: int = 0
18
+
19
+ def render(
20
+ self,
21
+ data: Any,
22
+ accepted_media_type: str | None = None,
23
+ renderer_context: dict[str, Any] | None = None,
24
+ ) -> bytes:
25
+ if data is None:
26
+ return b""
27
+
28
+ renderer_context = renderer_context or {}
29
+ indent = self.get_indent(accepted_media_type, renderer_context)
30
+
31
+ option = self.option
32
+ if indent is not None:
33
+ option |= orjson.OPT_INDENT_2
34
+
35
+ ret: bytes = orjson.dumps(data, default=self.default, option=option)
36
+ return ret
37
+
38
+
39
+ class JSONParser(BaseJSONParser): # type: ignore[misc]
40
+ renderer_class = JSONRenderer
41
+
42
+ def parse(
43
+ self,
44
+ stream: Any,
45
+ media_type: str | None = None,
46
+ parser_context: dict[str, Any] | None = None,
47
+ ) -> Any:
48
+ parser_context = parser_context or {}
49
+ encoding = parser_context.get("encoding", settings.DEFAULT_CHARSET)
50
+ data = stream.read()
51
+
52
+ if isinstance(data, bytes) and encoding.lower().replace("_", "-") not in (
53
+ "utf-8",
54
+ "utf8",
55
+ ):
56
+ data = data.decode(encoding)
57
+
58
+ try:
59
+ return orjson.loads(data)
60
+ except ValueError as exc:
61
+ raise ParseError(f"JSON parse error - {exc}") from exc
File without changes
@@ -0,0 +1,48 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ import orjson
6
+ from django.core.serializers.base import DeserializationError
7
+ from django.core.serializers.json import Deserializer as JSONDeserializer
8
+ from django.core.serializers.json import Serializer as JSONSerializer
9
+ from django.core.serializers.python import Deserializer as PythonDeserializer
10
+
11
+ from django_orjson import default
12
+
13
+
14
+ class Serializer(JSONSerializer):
15
+ _orjson_option: int | None
16
+
17
+ def _init_options(self) -> None:
18
+ super()._init_options() # type: ignore [misc]
19
+ self._orjson_option = (
20
+ orjson.OPT_INDENT_2 if self.options.get("indent") else None
21
+ )
22
+
23
+ def end_object(self, obj: Any) -> None:
24
+ if not self.first:
25
+ self.stream.write(",")
26
+ if not self._orjson_option:
27
+ self.stream.write(" ")
28
+ if self._orjson_option:
29
+ self.stream.write("\n")
30
+ self.stream.write(
31
+ orjson.dumps(
32
+ self.get_dump_object(obj),
33
+ default=default,
34
+ option=self._orjson_option,
35
+ ).decode()
36
+ )
37
+ self._current = None
38
+
39
+
40
+ class Deserializer(JSONDeserializer):
41
+ def __init__(self, stream_or_string: Any, **options: Any) -> None:
42
+ if not isinstance(stream_or_string, (bytes, str)):
43
+ stream_or_string = stream_or_string.read()
44
+ try:
45
+ objects = orjson.loads(stream_or_string)
46
+ except Exception as exc:
47
+ raise DeserializationError(str(exc)) from exc
48
+ PythonDeserializer.__init__(self, objects, **options)
@@ -0,0 +1,48 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Generator
4
+ from typing import Any
5
+
6
+ import orjson
7
+ from django.core.serializers.base import DeserializationError
8
+ from django.core.serializers.jsonl import Deserializer as JSONLDeserializer
9
+ from django.core.serializers.jsonl import Serializer as JSONLSerializer
10
+ from django.core.serializers.python import Deserializer as PythonDeserializer
11
+
12
+ from django_orjson import default
13
+
14
+
15
+ class Serializer(JSONLSerializer):
16
+ def _init_options(self) -> None:
17
+ self._current = None
18
+
19
+ def end_object(self, obj: Any) -> None:
20
+ self.stream.write(
21
+ orjson.dumps(self.get_dump_object(obj), default=default).decode()
22
+ )
23
+ self.stream.write("\n")
24
+ self._current = None
25
+
26
+
27
+ class Deserializer(JSONLDeserializer):
28
+ # Copy-paste-modified from upstream: JSONLDeserializer.__init__ hardcodes a
29
+ # call to its own _get_lines(), so we must skip it and call PythonDeserializer
30
+ # directly to substitute our orjson-based _get_lines().
31
+ def __init__(self, stream_or_string: Any, **options: Any) -> None:
32
+ if isinstance(stream_or_string, bytes):
33
+ stream_or_string = stream_or_string.decode()
34
+ if isinstance(stream_or_string, str):
35
+ stream_or_string = stream_or_string.splitlines()
36
+ PythonDeserializer.__init__(self, self._get_lines(stream_or_string), **options)
37
+
38
+ @staticmethod
39
+ def _get_lines(
40
+ stream: Any,
41
+ ) -> Generator[Any, None, None]:
42
+ for line in stream:
43
+ if not line.strip():
44
+ continue
45
+ try:
46
+ yield orjson.loads(line)
47
+ except Exception as exc:
48
+ raise DeserializationError(str(exc)) from exc
@@ -0,0 +1,5 @@
1
+ from __future__ import annotations
2
+
3
+ from django_orjson.signing import OrjsonSerializer
4
+
5
+ __all__ = ["OrjsonSerializer"]
@@ -0,0 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ import orjson
6
+
7
+ from django_orjson import default as _default
8
+
9
+
10
+ class OrjsonSerializer:
11
+ def dumps(self, obj: Any) -> bytes:
12
+ value: bytes = orjson.dumps(obj, default=_default)
13
+ return value
14
+
15
+ def loads(self, data: bytes) -> Any:
16
+ return orjson.loads(data)
File without changes
@@ -0,0 +1,19 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from django import template
6
+ from django.utils.safestring import SafeString
7
+
8
+ from django_orjson.html import json_script as _json_script
9
+
10
+ register = template.Library()
11
+
12
+
13
+ @register.filter(is_safe=True)
14
+ def json_script(value: Any, element_id: str | None = None) -> SafeString:
15
+ """
16
+ Output value JSON-encoded with orjson, wrapped in a
17
+ <script type="application/json"> tag (with an optional id).
18
+ """
19
+ return _json_script(value, element_id)
django_orjson/test.py ADDED
@@ -0,0 +1,73 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ import orjson
6
+ from django.test import SimpleTestCase as DjangoSimpleTestCase
7
+ from django.test.client import JSON_CONTENT_TYPE_RE
8
+ from django.test.client import AsyncClient as DjangoAsyncClient
9
+ from django.test.client import Client as DjangoClient
10
+
11
+ from django_orjson import default
12
+
13
+
14
+ class OrjsonMixin:
15
+ def _encode_json(self, data: Any, content_type: str) -> Any:
16
+ should_encode = JSON_CONTENT_TYPE_RE.match(content_type) and isinstance(
17
+ data, (dict, list, tuple)
18
+ )
19
+ return orjson.dumps(data, default=default) if should_encode else data
20
+
21
+ def _parse_json(self, response: Any, **extra: Any) -> Any:
22
+ if extra:
23
+ raise TypeError("orjson.loads() does not accept keyword arguments")
24
+ if not hasattr(response, "_json"):
25
+ content_type = response.get("Content-Type")
26
+ if not JSON_CONTENT_TYPE_RE.match(content_type):
27
+ raise ValueError(
28
+ f'Content-Type header is "{content_type}", not "application/json"'
29
+ )
30
+
31
+ response._json = orjson.loads(response.content)
32
+ return response._json
33
+
34
+
35
+ class AsyncClient(OrjsonMixin, DjangoAsyncClient):
36
+ pass
37
+
38
+
39
+ class Client(OrjsonMixin, DjangoClient):
40
+ pass
41
+
42
+
43
+ class SimpleTestCase(DjangoSimpleTestCase):
44
+ client_class = Client
45
+ async_client_class = AsyncClient
46
+
47
+ def assertJSONEqual(
48
+ self, raw: str | bytes | bytearray, expected_data: Any, msg: str | None = None
49
+ ) -> None:
50
+ try:
51
+ data = orjson.loads(raw)
52
+ except orjson.JSONDecodeError:
53
+ self.fail(f"First argument is not valid JSON: {raw!r}")
54
+ if isinstance(expected_data, (str, bytes, bytearray)):
55
+ try:
56
+ expected_data = orjson.loads(expected_data)
57
+ except orjson.JSONDecodeError:
58
+ self.fail(f"Second argument is not valid JSON: {expected_data!r}")
59
+ self.assertEqual(data, expected_data, msg=msg)
60
+
61
+ def assertJSONNotEqual(
62
+ self, raw: str | bytes | bytearray, expected_data: Any, msg: str | None = None
63
+ ) -> None:
64
+ try:
65
+ data = orjson.loads(raw)
66
+ except orjson.JSONDecodeError:
67
+ self.fail(f"First argument is not valid JSON: {raw!r}")
68
+ if isinstance(expected_data, (str, bytes, bytearray)):
69
+ try:
70
+ expected_data = orjson.loads(expected_data)
71
+ except orjson.JSONDecodeError:
72
+ self.fail(f"Second argument is not valid JSON: {expected_data!r}")
73
+ self.assertNotEqual(data, expected_data, msg=msg)
@@ -0,0 +1,67 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-orjson
3
+ Version: 1.0.0
4
+ Summary: orjson-powered utilities for Django.
5
+ Author-email: Adam Johnson <me@adamj.eu>
6
+ License-Expression: MIT
7
+ Project-URL: Changelog, https://django-orjson.readthedocs.io/en/latest/changelog.html
8
+ Project-URL: Documentation, https://django-orjson.readthedocs.io/
9
+ Project-URL: Funding, https://adamj.eu/books/
10
+ Project-URL: Repository, https://github.com/adamchainz/django-orjson
11
+ Keywords: Django
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Framework :: Django :: 5.2
14
+ Classifier: Framework :: Django :: 6.0
15
+ Classifier: Framework :: Django :: 6.1
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Natural Language :: English
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Programming Language :: Python :: 3.14
25
+ Classifier: Programming Language :: Python :: Implementation :: CPython
26
+ Classifier: Typing :: Typed
27
+ Requires-Python: >=3.10
28
+ Description-Content-Type: text/x-rst
29
+ License-File: LICENSE
30
+ Requires-Dist: django>=5.2
31
+ Requires-Dist: orjson<4,>=3.10
32
+ Provides-Extra: drf
33
+ Requires-Dist: djangorestframework>=3.16; extra == "drf"
34
+ Dynamic: license-file
35
+
36
+ =============
37
+ django-orjson
38
+ =============
39
+
40
+ .. image:: https://img.shields.io/readthedocs/django-orjson?style=for-the-badge
41
+ :target: https://django-orjson.readthedocs.io/en/latest/
42
+
43
+ .. image:: https://img.shields.io/github/actions/workflow/status/adamchainz/django-orjson/main.yml.svg?branch=main&style=for-the-badge
44
+ :target: https://github.com/adamchainz/django-orjson/actions?workflow=CI
45
+
46
+ .. image:: https://img.shields.io/badge/Coverage-100%25-success?style=for-the-badge
47
+ :target: https://github.com/adamchainz/django-orjson/actions?workflow=CI
48
+
49
+ .. image:: https://img.shields.io/pypi/v/django-orjson.svg?style=for-the-badge
50
+ :target: https://pypi.org/project/django-orjson/
51
+
52
+ .. image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=for-the-badge
53
+ :target: https://github.com/pre-commit/pre-commit
54
+ :alt: pre-commit
55
+
56
+ ----
57
+
58
+ .. figure:: https://raw.githubusercontent.com/adamchainz/django-orjson/main/docs/_static/logo.svg
59
+ :alt: django-orjson logo
60
+ :align: center
61
+
62
+ `orjson <https://github.com/ijl/orjson>`__-powered utilities for Django.
63
+
64
+ Documentation
65
+ -------------
66
+
67
+ Please see https://django-orjson.readthedocs.io/.
@@ -0,0 +1,19 @@
1
+ django_orjson/__init__.py,sha256=R6JVxc23ve-euyAR6m09KxzF4E_5KfTvsLi3xZAAhAE,401
2
+ django_orjson/apps.py,sha256=iRMUmuove5vx_Kp9y0Uj1ey9MQQZs-YWDxkvd0wncZ4,171
3
+ django_orjson/html.py,sha256=Qj6j70F_1sYSXgIC1qUNQw_FT4_oItOugesaPP9oZD4,1190
4
+ django_orjson/http.py,sha256=ufqYygBr0ZZJQwDKGnN6zGUagPohRe3Y-j28HAZ7YtM,948
5
+ django_orjson/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ django_orjson/rest_framework.py,sha256=WocWiQvSqKAtM2DXKnbAp3nhfgxdd2BtNC_-9trIvt0,1813
7
+ django_orjson/sessions.py,sha256=RlYTnBbyhlny6yhcIuH-YL7Hpxlf4XZ_pT8SCptMoFk,119
8
+ django_orjson/signing.py,sha256=sB_HfYQ2uR1VEyfUNwNot9wL6EefbjltByWKWawNYzo,343
9
+ django_orjson/test.py,sha256=S36PkwwD0Cx_cSp5auESSNHoQcBAVKikz4muGabttyk,2628
10
+ django_orjson/serializers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ django_orjson/serializers/json.py,sha256=cd6DvsyqHVvcNc6SrIvxbTfv3bGjLTcqi7t3MIi69E0,1600
12
+ django_orjson/serializers/jsonl.py,sha256=IrZiOEEEnQZDmus8ghUnAQ7XZgQ5dlJ6Qc1WUfjqHcU,1734
13
+ django_orjson/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ django_orjson/templatetags/django_orjson.py,sha256=yxDBQMfV9VLNgTR5zvqkCqKTx21v_xhdETCtG-xB370,512
15
+ django_orjson-1.0.0.dist-info/licenses/LICENSE,sha256=blVuFA7Qdp2_CcTyGXLxA4CsHsAjAquxVc4O6ODqye4,1069
16
+ django_orjson-1.0.0.dist-info/METADATA,sha256=09dAqQdtWnXD5wM8x9RLhYjdAkUTJMH__JQ0G7qiZNU,2599
17
+ django_orjson-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
18
+ django_orjson-1.0.0.dist-info/top_level.txt,sha256=uKNVpyaZsD5ryVJrf453yLl6Gl2JdF9ubLFZQJ65K8c,14
19
+ django_orjson-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Adam Johnson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ django_orjson