Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add ujson as alternative JSON encoder #130

Open
wants to merge 15 commits into
base: dev
Choose a base branch
from
Open
9 changes: 7 additions & 2 deletions .github/workflows/gh-tests-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,13 @@ jobs:
- name: Test with pytest
run: |
pytest --cache-clear --cov=./azure --cov-report=xml --cov-branch tests
- name: Codecov
if: ${{ matrix.python-version }} == 3.9
- if: matrix.python_version != 3.6
name: Test with pytest and ujson
run: |
python -m pip install .[ujson]
pytest --cache-clear --cov=./azure --cov-report=xml --cov-branch --cov-append tests
- if: matrix.python_version == 3.9
name: Codecov
uses: codecov/codecov-action@v2
with:
file: ./coverage.xml
Expand Down
2 changes: 1 addition & 1 deletion azure/functions/_cosmosdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
# Licensed under the MIT License.

import collections
import json

from azure.functions import _json as json
from . import _abc


Expand Down
2 changes: 1 addition & 1 deletion azure/functions/_durable_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def _serialize_custom_object(obj):

This function gets called when `json.dumps` cannot serialize
an object and returns a serializable dictionary containing enough
metadata to recontrust the original object.
metadata to reconstruct the original object.

Parameters
----------
Expand Down
3 changes: 2 additions & 1 deletion azure/functions/_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import collections.abc
import io
import json

import types
import typing

Expand All @@ -12,6 +12,7 @@
from ._thirdparty.werkzeug import formparser as _wk_parser
from ._thirdparty.werkzeug import http as _wk_http
from ._thirdparty.werkzeug.datastructures import Headers
from azure.functions import _json as json


class BaseHeaders(collections.abc.Mapping):
Expand Down
61 changes: 61 additions & 0 deletions azure/functions/_json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

from enum import Enum
from typing import AnyStr, Any
import os

AZUREFUNCTIONS_UJSON_ENV_VAR = 'AZUREFUNCTIONS_UJSON'

try:
import ujson
if AZUREFUNCTIONS_UJSON_ENV_VAR in os.environ:
HAS_UJSON = bool(os.environ[AZUREFUNCTIONS_UJSON_ENV_VAR])
else:
HAS_UJSON = True
import json
except ImportError:
import json
HAS_UJSON = False


class StringifyEnum(Enum):
"""This class output name of enum object when printed as string."""

def __str__(self):
return str(self.name)

def __json__(self):
"""For ujson encoding."""
return f'"{self.name}"'


class StringifyEnumJsonEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, StringifyEnum):
return str(o)

return super().default(o)


JSONDecodeError = json.JSONDecodeError

if HAS_UJSON:
def dumps(v: Any, **kwargs) -> str:
if 'default' in kwargs:
return json.dumps(v, **kwargs)

if 'cls' in kwargs:
del kwargs['cls']

return ujson.dumps(v, **kwargs)

def loads(s: AnyStr, **kwargs):
if kwargs:
return json.loads(s, **kwargs)
else: # ujson takes no kwargs
return ujson.loads(s)

else:
dumps = json.dumps # type: ignore
loads = json.loads
3 changes: 2 additions & 1 deletion azure/functions/_queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
# Licensed under the MIT License.

import datetime
import json

import typing

from azure.functions import _json as json
from . import _abc


Expand Down
3 changes: 1 addition & 2 deletions azure/functions/_sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
# Licensed under the MIT License.

import collections
import json

from azure.functions import _json as json
from . import _abc


Expand Down
3 changes: 2 additions & 1 deletion azure/functions/cosmosdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
# Licensed under the MIT License.

import collections.abc
import json

import typing

from azure.functions import _json as json
from azure.functions import _cosmosdb as cdb

from . import meta
Expand Down
16 changes: 1 addition & 15 deletions azure/functions/decorators/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,14 @@
import re
from abc import ABCMeta
from enum import Enum
from json import JSONEncoder
from typing import TypeVar, Optional, Union, Iterable, Type, Callable

T = TypeVar("T", bound=Enum)
SNAKE_CASE_RE = re.compile(r'^([a-z]+\d*_[a-z\d_]*|_+[a-z\d]+[a-z\d_]*)$',
re.IGNORECASE)
WORD_RE = re.compile(r'^([a-z]+\d*)$', re.IGNORECASE)


class StringifyEnum(Enum):
"""This class output name of enum object when printed as string."""

def __str__(self):
return str(self.name)
from azure.functions._json import StringifyEnum, StringifyEnumJsonEncoder # NOQA


class BuildDictMeta(type):
Expand Down Expand Up @@ -166,11 +160,3 @@ def is_word(input_string: str) -> bool:
:return: True for one word string, false otherwise.
"""
return WORD_RE.match(input_string) is not None


class StringifyEnumJsonEncoder(JSONEncoder):
def default(self, o):
if isinstance(o, StringifyEnum):
return str(o)

return super().default(o)
2 changes: 1 addition & 1 deletion azure/functions/durable_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
# Licensed under the MIT License.

import typing
import json

from azure.functions import _json as json
from azure.functions import _durable_functions
from . import meta

Expand Down
3 changes: 2 additions & 1 deletion azure/functions/eventgrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@

import collections
import datetime
import json

from typing import Optional, List, Any, Dict, Union

from azure.functions import _json as json
from azure.functions import _eventgrid as azf_eventgrid

from . import meta
Expand Down
3 changes: 2 additions & 1 deletion azure/functions/eventhub.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

import json

from typing import Dict, Any, List, Union, Optional, Mapping

from azure.functions import _json as json
from azure.functions import _eventhub

from . import meta
Expand Down
3 changes: 2 additions & 1 deletion azure/functions/extension/extension_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

from typing import Optional, Union, Dict, List
import abc
import json

from azure.functions import _json as json
from .app_extension_hooks import AppExtensionHooks
from .func_extension_hooks import FuncExtensionHooks
from .extension_hook_meta import ExtensionHookMeta
Expand Down
3 changes: 2 additions & 1 deletion azure/functions/http.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

import json

import logging
import sys
import typing
from http.cookies import SimpleCookie

from azure.functions import _abc as azf_abc
from azure.functions import _json as json
from azure.functions import _http as azf_http
from . import meta
from ._thirdparty.werkzeug.datastructures import Headers
Expand Down
3 changes: 1 addition & 2 deletions azure/functions/kafka.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@
# Licensed under the MIT License.

import typing
import json

from typing import Any, List

from . import meta

from azure.functions import _json as json
from ._kafka import AbstractKafkaEvent


Expand Down
2 changes: 1 addition & 1 deletion azure/functions/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import abc
import collections.abc
import datetime
import json
import re
from typing import Dict, Optional, Union, Tuple, Mapping, Any

Expand All @@ -13,6 +12,7 @@
try_parse_datetime_with_formats,
try_parse_timedelta_with_formats
)
from azure.functions import _json as json


def is_iterable_type_annotation(annotation: object, pytype: object) -> bool:
Expand Down
2 changes: 1 addition & 1 deletion azure/functions/queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@

import collections.abc
import datetime
import json
from typing import List, Dict, Any, Union, Optional

from azure.functions import _abc as azf_abc
from azure.functions import _json as json
from azure.functions import _queue as azf_queue

from . import meta
Expand Down
4 changes: 2 additions & 2 deletions azure/functions/servicebus.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
# Licensed under the MIT License.

import datetime
import json
from typing import Dict, Any, List, Union, Optional, Mapping, cast

from typing import Dict, Any, List, Union, Optional, Mapping, cast
from azure.functions import _json as json
from azure.functions import _servicebus as azf_sbus

from . import meta
Expand Down
2 changes: 1 addition & 1 deletion azure/functions/sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
# Licensed under the MIT License.

import collections.abc
import json
import typing

from azure.functions import _json as json
from azure.functions import _sql as sql

from . import meta
Expand Down
2 changes: 1 addition & 1 deletion azure/functions/timer.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

import json
import typing

from azure.functions import _abc as azf_abc
from azure.functions import _json as json
from . import meta


Expand Down
8 changes: 6 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@
'mypy',
'pytest',
'pytest-cov',
'pytest-benchmark',
'requests==2.*',
'coverage'
]
'coverage',
'types-ujson'
],
'ujson': ['ujson>=5.3.0,<6.0']
}

with open("README.md") as readme:
Expand All @@ -34,6 +37,7 @@
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Operating System :: Microsoft :: Windows',
'Operating System :: POSIX',
'Operating System :: MacOS :: MacOS X',
Expand Down
3 changes: 2 additions & 1 deletion tests/decorators/test_function_app.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
import json

import unittest
from unittest import mock

from azure.functions import _json as json
from azure.functions import WsgiMiddleware, AsgiMiddleware
from azure.functions.decorators.constants import HTTP_OUTPUT, HTTP_TRIGGER
from azure.functions.decorators.core import DataType, AuthLevel, \
Expand Down
15 changes: 15 additions & 0 deletions tests/decorators/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,3 +219,18 @@ def test_is_not_supported_trigger_type(self):
Trigger.is_supported_trigger_type(
GenericTrigger(name='req', type="dummy"),
HttpTrigger))


def test_clean_nones_nested_benchmark(benchmark):
data = [
{"direction": "IN", "type": "b", "name": "t1"},
{"direction": "IN", "type": "b", "name": "t2"},
{"direction": "IN", "type": "b", "name": "t3"},
{"direction": "IN", "type": "b", "name": "t4"},
{"direction": "IN", "type": "b", "name": "t5"},
{"direction": "IN", "type": "b", "name": "t6"},
{"direction": "IN", "type": "b", "name": "t7"},
{"direction": "IN", "type": "b", "name": "t8"},
{"direction": "IN", "type": "b", "name": "t9"}
]
benchmark(BuildDictMeta.clean_nones, data)
2 changes: 1 addition & 1 deletion tests/decorators/testutils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
import json

from azure.functions import _json as json
from azure.functions.decorators.utils import StringifyEnumJsonEncoder


Expand Down
2 changes: 1 addition & 1 deletion tests/test_blob.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
import json
import unittest
from typing import Any, Dict

import azure.functions as func
import azure.functions.blob as afb
from azure.functions.blob import InputStream
from azure.functions import _json as json
from azure.functions.meta import Datum


Expand Down
Loading