Skip to content

Commit

Permalink
✨ Support for Pydantic V2
Browse files Browse the repository at this point in the history
  • Loading branch information
frizzy committed Jul 4, 2023
1 parent d55e172 commit 82fe39a
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 170 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/dist
/.vscode
.pytest_cache
.venv
__pycache__
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ JsonAblr is a Python library that allows you to encode variables into a format t

This was heavily inspired by FastAPI's `jsonable_encoder`.

- JsonAblr now requires Pydantic 2.0 or above. If you are using Pydantic 1.x, please use JsonAblr 1.1.1.

## Features

- Encode variables into a JSON-like format
Expand Down
49 changes: 15 additions & 34 deletions jsonablr/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@
Encoders
"""
from functools import wraps
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
from typing import Any, Callable, Dict, List, Optional, Set, Union
from datetime import datetime, date, timezone
import dataclasses
from enum import Enum
from pathlib import PurePath
from types import GeneratorType
from pydantic import BaseModel
from pydantic.json import ENCODERS_BY_TYPE
from pydantic import BaseModel, create_model


def datetime_encoder(dateval: datetime) -> str:
Expand Down Expand Up @@ -44,15 +43,15 @@ class Options(BaseModel):
def encode(data: Any, **options) -> dict:
encoder = JsonAblr(
encoders=options.pop('encoders', {}),
**Options.parse_obj(options).dict()
**Options.model_validate(options).model_dump()
)
return encoder(data)


def encode_output(func=None, **options):
encoder = JsonAblr(
encoders=options.pop('encoders', {}),
**Options.parse_obj(options).dict()
**Options.model_validate(options).model_dump()
)

def decorator(func):
Expand All @@ -64,35 +63,29 @@ def wrapper(*args, **kwargs):
return decorator if func is None else decorator(func)


encoder_map: Dict[Callable[[Any], Any], Tuple[Any, ...]] = {}
for type_, encoder in ENCODERS_BY_TYPE.items():
encoder_map.setdefault(encoder, tuple())
encoder_map[encoder] += (type_,)


class JsonAblr:

def __init__(self, encoders: Optional[Dict[Any, Callable]] = None, **options) -> None:
self.encoders = {
**default_encoders,
**(encoders or {})
}
self._options = Options.parse_obj(options)
self._options = Options.model_validate(options)
self._override_options = None

@property
def options(self) -> Options:
return Options.parse_obj({
**self._options.dict(),
**(self._override_options.dict() if self._override_options else {})
return Options.model_validate({
**self._options.model_dump(),
**(self._override_options.model_dump() if self._override_options else {})
})

def __call__(self, obj: Any, **options) -> Any:
return self.encode(obj, **options)

def encode(self, obj: Any, **options) -> Any:
if options:
self._override_options = Options.parse_obj(options)
self._override_options = Options.model_validate(options)
encoded = self._encode(obj)
self._override_options = None
return encoded
Expand Down Expand Up @@ -127,16 +120,9 @@ def _encode(self, obj: Any) -> Any:
if isinstance(obj, (list, set, frozenset, GeneratorType, tuple)):
return self.handle_list_type(obj)

encoder = ENCODERS_BY_TYPE.get(type(obj))
if encoder:
return encoder(obj)

for encoder, types in encoder_map.items():
if isinstance(obj, types):
return encoder(obj)

try:
data = dict(obj)
ObjModel = create_model('ObjModel', obj=(type(obj), ...))
return ObjModel(obj=obj).model_dump(mode='json')['obj']
except Exception as e:
errors: List[Exception] = []
errors.append(e)
Expand All @@ -160,7 +146,7 @@ def get_encoder(encoders: Dict[Any, Callable], obj: Any) -> Optional[Callable]:

def handle_pydantic_model(self, obj: BaseModel) -> dict:

obj_dict = obj.dict(**self.options.dict(include={
obj_dict = obj.model_dump(**self.options.model_dump(include={
'include',
'exclude',
'by_alias',
Expand All @@ -169,14 +155,9 @@ def handle_pydantic_model(self, obj: BaseModel) -> dict:
'exclude_defaults'
}))

json_encoders = getattr(obj.__config__, 'json_encoders', {})

if '__root__' in obj_dict:
obj_dict = obj_dict['__root__']

encoder = self.__class__(
encoders={**json_encoders, **(self.encoders or {})},
**self.options.dict(include={
encoders=self.encoders,
**self.options.model_dump(include={
'exclude_none',
'exclude_defaults',
'sqlalchemy_safe',
Expand All @@ -201,7 +182,7 @@ def handle_dict(self, obj: dict) -> dict:

encoder = self.__class__(
self.encoders,
**self.options.dict(
**self.options.model_dump(
include={
'by_alias',
'exclude_unset',
Expand Down
Loading

0 comments on commit 82fe39a

Please sign in to comment.