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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ urlpatterns = winter.django.create_urls(app)
- Interceptors belong to applications: `WebApplication(..., interceptors=[AuthorizationInterceptor, AuditInterceptor])`
lists their classes in the order they run. The injector of each request creates them, so their dependencies go
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:
`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.
- The request pipeline takes the injector from the application. `winter.application.set_injector` is needed only
by `winter.sqlalchemy`, `winter.ddd` and `winter.messaging`.
- `winter.django.create_urls` installs the Django throttling identity on its first call: replace it with
Expand Down
8 changes: 4 additions & 4 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ at least two of them.
subpackage can later ship as its own distribution from its own repository without any
import path changing.
2. **Framework behaviour is never process-global.** Every behavioural registry (exception
handlers, interceptors, output processors, throttling configuration, OpenAPI inspectors)
handlers, interceptors, response header serializers, throttling configuration, OpenAPI inspectors)
belongs to the application object an adapter builds from, such as `winter.web.WebApplication`,
and the values that its methods receive come from its injector, so two applications can live
in one process.
Expand All @@ -85,13 +85,13 @@ at least two of them.
- **Framework behaviour is still process-global.** A `winter.web.WebApplication` holds the code of
a web application, its injector and its interceptors. The adapters derive its exception handlers
from its definitions and handle each of its requests in a child of its injector. The other
behavioural registries of `winter.web` and `winter.openapi` (output processors, response header
serializers, native response types, throttling, OpenAPI inspectors) are module singletons, and
behavioural registries of `winter.web` and `winter.openapi` (response header serializers,
native response types, throttling, OpenAPI inspectors) are module singletons, and
`winter.sqlalchemy`, `winter.ddd` and `winter.messaging` take the injector from the service
locator `winter.application.get_injector`.
- **Models are initialised once per process.** `winter.web.initialization.initialize`, the Django
adapter's `_initialize_process` and `winter.openapi.initialization.initialize` fill those module
singletons with the standard output processors, serializers and inspectors on first use. In the
singletons with the standard serializers and inspectors on first use. In the
target, a `WebApplication` is given these registries like its interceptors, and they start with the
standard entries. Building from an application, an adapter adds
its own entries (the native response type, the throttling identity) to that application only. The
Expand Down
19 changes: 0 additions & 19 deletions tests/api/api_with_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,6 @@
import winter.web
from winter.web import Request
from winter.web import Response
from winter.web.output_processor import IOutputProcessor
from winter.web.output_processor import register_output_processor


class PlainTextProcessor(IOutputProcessor):
"""Renders the method result as text/plain instead of JSON."""

def process_output(self, output: str, request: Request) -> Response:
return Response(status_code=HTTPStatus.OK, body=output.encode(), headers={'content-type': 'text/plain'})


def plain_text(func):
"""Same shape as winter.django.output_template, but with an adapter-independent processor."""
return register_output_processor(func, PlainTextProcessor())


@winter.web.route('with-response/')
Expand All @@ -28,11 +14,6 @@ class APIWithResponse:
def raw(self) -> Response:
return Response(status_code=HTTPStatus.ACCEPTED, body=b'raw bytes', headers={'content-type': 'text/plain'})

@winter.web.route_get('greeting/')
@plain_text
def greeting(self) -> str:
return 'Hello, Winter'

@winter.web.route_get('request-info/')
def request_info(self, request: Request, django_request: HttpRequest) -> dict:
return {
Expand Down
1 change: 0 additions & 1 deletion tests/api/django/__init__.py

This file was deleted.

14 changes: 0 additions & 14 deletions tests/api/django/api_with_output_template.py

This file was deleted.

4 changes: 0 additions & 4 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from pathlib import Path

import django
import pytest
from injector import CallableProvider
Expand All @@ -14,13 +12,11 @@

def pytest_configure():
from django.conf import settings
app_dir = Path(__file__)
settings.configure(
ROOT_URLCONF='tests.urls',
TEMPLATES=[
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [app_dir.parent.name + '/templates'],
'APP_DIRS': True,
'OPTIONS': {
'debug': True, # We want template errors to raise
Expand Down
8 changes: 0 additions & 8 deletions tests/django/test_api_with_output_template.py

This file was deleted.

12 changes: 3 additions & 9 deletions tests/openapi/test_api_request_and_response_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,11 +349,9 @@ class RequestBodyWithUndefined:
'properties': {
'limit': {'format': 'int32', 'nullable': True, 'type': 'integer'},
'offset': {'format': 'int32', 'nullable': True, 'type': 'integer'},
'next': {'nullable': True, 'type': 'string'},
'previous': {'nullable': True, 'type': 'string'},
'total_count': {'format': 'int32', 'type': 'integer'},
},
'required': ['total_count', 'limit', 'offset', 'previous', 'next'],
'required': ['total_count', 'limit', 'offset'],
'title': 'PageMetaOfNestedDataclass',
'type': 'object',
},
Expand Down Expand Up @@ -399,10 +397,8 @@ class RequestBodyWithUndefined:
'extra': {'type': 'string'},
'limit': {'format': 'int32', 'nullable': True, 'type': 'integer'},
'offset': {'format': 'int32', 'nullable': True, 'type': 'integer'},
'next': {'nullable': True, 'type': 'string'},
'previous': {'nullable': True, 'type': 'string'},
'total_count': {'format': 'int32', 'type': 'integer'}},
'required': ['total_count', 'limit', 'offset', 'previous', 'next', 'extra'],
'required': ['total_count', 'limit', 'offset', 'extra'],
'title': 'PageMetaOfInteger',
'type': 'object',
},
Expand Down Expand Up @@ -1008,12 +1004,10 @@ def method_2(self) -> Page[str]:
'PageMetaOfString': {
'properties': {
'limit': {'format': 'int32', 'nullable': True, 'type': 'integer'},
'next': {'nullable': True, 'type': 'string'},
'offset': {'format': 'int32', 'nullable': True, 'type': 'integer'},
'previous': {'nullable': True, 'type': 'string'},
'total_count': {'format': 'int32', 'type': 'integer'},
},
'required': ['total_count', 'limit', 'offset', 'previous', 'next'],
'required': ['total_count', 'limit', 'offset'],
'title': 'PageMetaOfString',
'type': 'object'},
'PageOfString': {
Expand Down
1 change: 0 additions & 1 deletion tests/templates/hello.txt

This file was deleted.

8 changes: 0 additions & 8 deletions tests/web/test_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,6 @@ def test_raw_response_bypasses_json_encoding(api_client):
assert response.headers['content-type'] == 'text/plain'


def test_output_processor_may_return_a_response(api_client):
response = api_client.get('/with-response/greeting/')

assert response.status_code == HTTPStatus.OK
assert response.content == b'Hello, Winter'
assert response.headers['content-type'] == 'text/plain'


def test_request_describes_the_http_request(api_client):
response = api_client.get('/with-response/request-info/?x=1')

Expand Down
14 changes: 4 additions & 10 deletions tests/web/test_simple_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,11 @@ def test_get_response_entity(api_client):
}


@pytest.mark.parametrize(('limit', 'offset', 'expected_previous', 'expected_next'), (
(1, 1, '/winter-simple/page-response/?limit=1', '/winter-simple/page-response/?limit=1&offset=2'),
(None, None, None, None),
(2, 3, '/winter-simple/page-response/?limit=2&offset=1', '/winter-simple/page-response/?limit=2&offset=5'),
(9, 3, '/winter-simple/page-response/?limit=9', None),
@pytest.mark.parametrize(('limit', 'offset'), (
(1, 1),
(None, None),
))
def test_page_response(api_client, limit, offset, expected_previous, expected_next):
def test_page_response(api_client, limit, offset):
url = f'/winter-simple/page-response/?'
query_params = []
if limit is not None:
Expand All @@ -42,8 +40,6 @@ def test_page_response(api_client, limit, offset, expected_previous, expected_ne
'meta': {
'limit': limit,
'offset': offset,
'next': expected_next,
'previous': expected_previous,
'total_count': 10,
},
}
Expand All @@ -62,8 +58,6 @@ def test_custom_page_response(api_client):
'meta': {
'limit': 2,
'offset': 2,
'next': '/winter-simple/custom-page-response/?limit=2&offset=4',
'previous': '/winter-simple/custom-page-response/?limit=2',
'total_count': 10,
'extra': 456,
},
Expand Down
1 change: 0 additions & 1 deletion winter/django/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from . import json_encoders # noqa: F401 registers the Django lazy string encoder
from .output_template import output_template
from .request import DjangoRequest
from .reverse import reverse
from .throttling import django_user_identity
Expand Down
24 changes: 0 additions & 24 deletions winter/django/output_template.py

This file was deleted.

1 change: 1 addition & 0 deletions winter/json/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
from .decoder import json_decode
from .decoder import json_decoder
from .encoder import JSONEncoder
from .encoder import register_encoder
from .undefined import Undefined
2 changes: 0 additions & 2 deletions winter/openapi/inspectors/page_inspector.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ def create_dataclass(page_type: Type) -> Type:
'total_count': int,
'limit': Optional[int],
'offset': Optional[int],
'previous': Optional[str],
'next': Optional[str],
**{extra_field.name: extra_field.type for extra_field in extra_fields},
},
},
Expand Down
1 change: 0 additions & 1 deletion winter/web/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from .interceptor import Interceptor
from .media_type import InvalidMediaTypeException
from .media_type import MediaType
from .output_processor import register_output_processor_resolver
from .query_parameters import map_query_parameter
from .query_parameters import query_parameters
from .request_body_annotation import request_body
Expand Down
5 changes: 1 addition & 4 deletions winter/web/initialization.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from .output_processor import register_output_processor_resolver
from .pagination.page_processor_resolver import PageOutputProcessorResolver
from .response_header_serializer import response_headers_serializer
from .response_header_serializers import DateTimeResponseHeaderSerializer
from .response_header_serializers import LastModifiedResponseHeaderSerializer
Expand All @@ -9,7 +7,7 @@

def initialize():
"""
Registers the standard output processors and response header serializers of winter.web on the first call.
Registers the standard response header serializers of winter.web on the first call.
Adapters call it before building their routes.
Process-global until these registries belong to an application: docs/architecture.md, Current deviations.
"""
Expand All @@ -20,6 +18,5 @@ def initialize():


def _initialize_process():
register_output_processor_resolver(PageOutputProcessorResolver())
response_headers_serializer.add_serializer(DateTimeResponseHeaderSerializer())
response_headers_serializer.add_serializer(LastModifiedResponseHeaderSerializer())
61 changes: 0 additions & 61 deletions winter/web/output_processor.py

This file was deleted.

1 change: 1 addition & 0 deletions winter/web/pagination/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from . import page_encoder # noqa: F401 registers the JSON encoder of Page
from .limits import limits
from .order_by import order_by
24 changes: 24 additions & 0 deletions winter/web/pagination/page_encoder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import dataclasses

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

_PAGE_FIELDS = dataclasses.fields(Page)


@register_encoder
def page_encoder(page: Page) -> dict:
"""The fields that a subclass of Page adds go to the meta."""
return {
'meta': {
'total_count': page.total_count,
'limit': page.position.limit,
'offset': page.position.offset,
**{
field.name: getattr(page, field.name)
for field in dataclasses.fields(page)
if field not in _PAGE_FIELDS
},
},
'objects': page.items,
}
Loading
Loading