Passed
Pull Request — master (#375)
by Vinicius
08:43 queued 03:40
created

kytos.core.rest_api   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 138
Duplicated Lines 31.88 %

Importance

Changes 0
Metric Value
eloc 84
dl 44
loc 138
rs 10
c 0
b 0
f 0
wmc 17

6 Functions

Rating   Name   Duplication   Size   Complexity  
A get_body() 0 6 1
A get_json_or_400() 0 8 2
A content_type_json_or_415() 0 8 2
A get_json() 0 8 1
A _json_serializer() 0 4 2
A aget_json_or_400() 0 6 2

5 Methods

Rating   Name   Duplication   Size   Complexity  
A StarletteOpenAPIRequest.body() 6 6 2
A JSONResponse.render() 0 9 1
A AStarletteOpenAPIRequest.__init__() 12 12 1
A AStarletteOpenAPIRequest.body() 6 6 2
A StarletteOpenAPIRequest.__init__() 12 12 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
"""Rest API utilities module."""
2
# pylint: disable=self-assigning-variable
3
import asyncio
4
import json
5
from asyncio import AbstractEventLoop
6
from datetime import datetime
7
from typing import Any, Optional
8
9
from openapi_core.contrib.starlette import \
10
    StarletteOpenAPIRequest as _StarletteOpenAPIRequest
11
from openapi_core.validation.request.datatypes import RequestParameters
12
from starlette.exceptions import HTTPException
13
from starlette.requests import Request
14
from starlette.responses import JSONResponse as StarletteJSONResponse
15
from starlette.responses import Response
16
17
Request = Request
18
Response = Response
19
HTTPException = HTTPException
20
21
22
def _json_serializer(obj):
23
    if isinstance(obj, datetime):
24
        return obj.isoformat()
25
    raise TypeError(f"Type {type(obj)} not serializable")
26
27
28
def get_body(
29
    request: Request, loop: AbstractEventLoop, timeout: Optional[float] = None
30
) -> bytes:
31
    """Try to get request.body form a sync @rest route."""
32
    future = asyncio.run_coroutine_threadsafe(request.body(), loop)
33
    return future.result(timeout)
34
35
36
def get_json(
37
    request: Request, loop: AbstractEventLoop, timeout: Optional[float] = None
38
) -> Any:
39
    """Try to get request.json from a sync @rest route.
40
    It might raise json.decoder.JSONDecodeError.
41
    """
42
    future = asyncio.run_coroutine_threadsafe(request.json(), loop)
43
    return future.result(timeout)
44
45
46
def get_json_or_400(
47
    request: Request, loop: AbstractEventLoop, timeout: Optional[float] = None
48
) -> Any:
49
    """Try to get request.json from a sync @rest route or HTTPException 400."""
50
    try:
51
        return get_json(request, loop, timeout)
52
    except (json.decoder.JSONDecodeError, TypeError) as exc:
53
        raise HTTPException(400, detail=f"Invalid json: {str(exc)}")
54
55
56
async def aget_json_or_400(request: Request) -> Any:
57
    """Try to get request.json from async @rest route or HTTPException 400."""
58
    try:
59
        return await request.json()
60
    except (json.decoder.JSONDecodeError, TypeError) as exc:
61
        raise HTTPException(400, detail=f"Invalid json: {str(exc)}")
62
63
64
def content_type_json_or_415(request: Request) -> Optional[str]:
65
    """Ensures request Content-Type is application/json or raises 415."""
66
    content_type = request.headers.get("Content-Type")
67
    if content_type != "application/json":
68
        err = "Expected Content-Type: application/json, " \
69
              f"got: {content_type}"
70
        raise HTTPException(415, detail=err)
71
    return content_type
72
73
74
class JSONResponse(StarletteJSONResponse):
75
    """JSONResponse with custom default serializer that supports datetime."""
76
77
    media_type = "application/json"
78
79
    def render(self, content) -> bytes:
80
        return json.dumps(
81
            content,
82
            ensure_ascii=False,
83
            allow_nan=False,
84
            indent=None,
85
            separators=(",", ":"),
86
            default=_json_serializer,
87
        ).encode("utf-8")
88
89
90
# pylint: disable=super-init-not-called
91 View Code Duplication
class AStarletteOpenAPIRequest(_StarletteOpenAPIRequest):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
92
    """Async StarletteOpenAPIRequest."""
93
94
    def __init__(self, request: Request, body: bytes) -> None:
95
        """Constructor of AsycnStarletteOpenAPIRequest.
96
97
        This constructor doesn't call super().__init__() to keep it async
98
        """
99
        self.request = request
100
        self.parameters = RequestParameters(
101
            query=self.request.query_params,
102
            header=self.request.headers,
103
            cookie=self.request.cookies,
104
        )
105
        self._body = body
106
107
    @property
108
    def body(self) -> Optional[str]:
109
        body = self._body
110
        if body is None:
111
            return None
112
        return body.decode("utf-8")
113
114
115
# pylint: disable=super-init-not-called
116 View Code Duplication
class StarletteOpenAPIRequest(_StarletteOpenAPIRequest):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
117
    """Sync StarletteOpenAPIRequest."""
118
119
    def __init__(self, request: Request, body: bytes) -> None:
120
        """Constructor of AsycnStarletteOpenAPIRequest.
121
122
        This constructor doesn't call super().__init__() to keep it async
123
        """
124
        self.request = request
125
        self.parameters = RequestParameters(
126
            query=self.request.query_params,
127
            header=self.request.headers,
128
            cookie=self.request.cookies,
129
        )
130
        self._body = body
131
132
    @property
133
    def body(self) -> Optional[str]:
134
        body = self._body
135
        if body is None:
136
            return None
137
        return body.decode("utf-8")
138