Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ urlpatterns = winter.django.create_urls(app)
to `__init__`. `interceptor_registry` and `InterceptorRegistry` are gone.
- Output processors are gone: `register_output_processor_resolver`, `IOutputProcessor`, `IOutputProcessorResolver`,
`register_output_processor` and `winter.django.output_template`. An API method returns data, encoded as JSON
(`winter.json.register_encoder` adds the encoder of a type), or a `winter.web.Response`, e.g. a rendered template:
(`@winter.json.json_encoder` adds the encoder of a type), or a `winter.web.Response`, e.g. a rendered template:
`Response(HTTPStatus.OK, render_to_string('report.html', context).encode(), {'content-type': 'text/html'})`.
- The `meta` of a `Page` has no `previous` and `next` links any more, and `winter.web.pagination.utils` with
`get_previous_page_url` and `get_next_page_url` is gone.
Expand All @@ -99,6 +99,8 @@ urlpatterns = winter.django.create_urls(app)
`register_route_parameters_inspector`, `get_route_parameters_inspectors` and `winter.openapi.initialization` are
gone, and the inspectors and `inspect_page` are no longer exported. A parameter that no argument stands for, such
as a header read by a request-scoped provider, is added to the spec that `generate_openapi` returns.
- Decorators name what they declare: `@winter.json.register_encoder` is `@winter.json.json_encoder`, like
`@winter.json.json_decoder`, and `@winter.web.register_url_regexp` is `@winter.web.url_regexp`.

## Upgrading to Winter 33

Expand Down
4 changes: 2 additions & 2 deletions winter/django/json_encoders.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from django.utils.functional import Promise

from winter.json.encoder import register_encoder
from winter.json.encoder import json_encoder


@register_encoder
@json_encoder
def promise_encoder(promise: Promise):
return str(promise)
2 changes: 1 addition & 1 deletion winter/json/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
from .decoder import json_decode
from .decoder import json_decoder
from .encoder import JSONEncoder
from .encoder import register_encoder
from .encoder import json_encoder
from .undefined import Undefined
46 changes: 25 additions & 21 deletions winter/json/encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,15 @@ def default(self, obj):
return super().default(obj)


def register_encoder(func: Callable = None, *, need_recursion: bool = False):
def json_encoder(func: Callable = None, *, need_recursion: bool = False):
"""
The decorated function encodes the type of its only argument, and its subclasses, as JSON. With need_recursion
the value it returns is encoded in turn.
"""
if func is None:
return lambda func_: register_encoder(func_, need_recursion=need_recursion)
return lambda func_: json_encoder(func_, need_recursion=need_recursion)

assert callable(func), 'First argument in register_encoder must be callable'
assert callable(func), 'First argument in json_encoder must be callable'

signature = inspect.signature(func)
assert len(signature.parameters), 'Function must have only one argument'
Expand All @@ -51,98 +55,98 @@ def register_encoder(func: Callable = None, *, need_recursion: bool = False):
assert annotation is not parameter.empty, 'First argument in function must have annotation'
assert inspect.isclass(annotation), 'Annotation must be class'
assert annotation not in _encoder_map, (
f'You can not register "{annotation.__name__}" twice. At first unregister it'
f'"{annotation.__name__}" already has a JSON encoder'
)
_encoder_map[annotation] = func, need_recursion
return func


@register_encoder
@json_encoder
def datetime_encoder(date: datetime.datetime):
representation = date.isoformat()
if representation.endswith('+00:00'):
representation = representation[:-6] + 'Z'
return representation


@register_encoder
@json_encoder
def date_encoder(date: datetime.date):
return date.isoformat()


@register_encoder
@json_encoder
def time_encoder(time: datetime.time):
if time.utcoffset() is not None:
raise ValueError("JSON can't represent timezone-aware times.")
return time.isoformat()


@register_encoder
@json_encoder
def timedelta_encoder(timedelta: datetime.timedelta):
return str(timedelta.total_seconds())


@register_encoder
@json_encoder
def decimal_encoder(number: decimal.Decimal):
return str(number)


@register_encoder
@json_encoder
def uuid_encoder(uid: uuid.UUID):
return str(uid)


@register_encoder
@json_encoder
def bytes_encoder(bytes_: bytes):
return bytes_.decode('utf-8')


@register_encoder
@json_encoder
def str_encoder(string: str):
return string


@register_encoder
@json_encoder
def int_encoder(number: int):
return number


@register_encoder
@json_encoder
def float_encoder(number: float):
return number


@register_encoder
@json_encoder
def tuple_encoder(array: tuple):
return array


@register_encoder
@json_encoder
def list_encoder(array: list):
return array


@register_encoder
@json_encoder
def set_encoder(set_: set):
return list(set_)


@register_encoder
@json_encoder
def frozenset_encoder(frozenset_: frozenset):
return list(frozenset_)


@register_encoder
@json_encoder
def generator_encoder(generator: types.GeneratorType):
return list(generator)


@register_encoder(need_recursion=True)
@json_encoder(need_recursion=True)
def enum_encoder(enum_: enum.Enum):
return enum_.value


@register_encoder
@json_encoder
def dataclass_encoder(obj: object):
if not dataclasses.is_dataclass(obj):
raise CannotEncode
Expand Down
2 changes: 1 addition & 1 deletion winter/web/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@
from .throttling import ThrottlingMisconfigurationException
from .throttling import ThrottlingStorage
from .throttling import throttling
from .urls import register_url_regexp
from .urls import url_regexp
from .web_application import WebApplication
4 changes: 2 additions & 2 deletions winter/web/pagination/page_encoder.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import dataclasses

from winter.json import register_encoder
from winter.json import json_encoder
from winter.pagination import Page

_PAGE_FIELDS = dataclasses.fields(Page)


@register_encoder
@json_encoder
def page_encoder(page: Page) -> dict:
"""The fields that a subclass of Page adds go to the meta."""
return {
Expand Down
12 changes: 8 additions & 4 deletions winter/web/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@
_regexp = {}


def register_url_regexp(func: types.FunctionType):
def url_regexp(func: types.FunctionType):
"""
The decorated function gives the regexp that a path variable matches when its type is the type of the only
argument of the function, or a subclass of it.
"""
annotations = func.__annotations__.copy()
annotations.pop('return', None)
assert len(annotations) == 1
Expand Down Expand Up @@ -48,18 +52,18 @@ def get_regexp(type_=None) -> str:


# noinspection PyUnusedLocal
@register_url_regexp
@url_regexp
def int_regexp(cls: int):
return r'\d+'


# noinspection PyUnusedLocal
@register_url_regexp
@url_regexp
def uuid_regexp(cls: uuid.UUID):
return r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'


@register_url_regexp
@url_regexp
def enum_regexp(cls: enum.Enum):
values = (f'({e.value})' for e in cls)
return '(' + '|'.join(values) + ')'
Loading