diff --git a/README.md b/README.md index 10fa732e..ca4a4377 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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 diff --git a/winter/django/json_encoders.py b/winter/django/json_encoders.py index d98fef68..a4c32507 100644 --- a/winter/django/json_encoders.py +++ b/winter/django/json_encoders.py @@ -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) diff --git a/winter/json/__init__.py b/winter/json/__init__.py index 12bd52fc..d2b85804 100644 --- a/winter/json/__init__.py +++ b/winter/json/__init__.py @@ -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 diff --git a/winter/json/encoder.py b/winter/json/encoder.py index 0f980be2..99f650e0 100644 --- a/winter/json/encoder.py +++ b/winter/json/encoder.py @@ -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' @@ -51,13 +55,13 @@ 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'): @@ -65,84 +69,84 @@ def datetime_encoder(date: datetime.datetime): 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 diff --git a/winter/web/__init__.py b/winter/web/__init__.py index 94fcddd7..5a215e91 100644 --- a/winter/web/__init__.py +++ b/winter/web/__init__.py @@ -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 diff --git a/winter/web/pagination/page_encoder.py b/winter/web/pagination/page_encoder.py index d62aa973..81e1fd6d 100644 --- a/winter/web/pagination/page_encoder.py +++ b/winter/web/pagination/page_encoder.py @@ -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 { diff --git a/winter/web/urls.py b/winter/web/urls.py index bb5f0d43..a8eb7f37 100644 --- a/winter/web/urls.py +++ b/winter/web/urls.py @@ -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 @@ -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) + ')'