Passed
Push — master ( 2444bb...cf565b )
by Vinicius
04:08 queued 15s
created

kytos.core.rest_api   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 150
Duplicated Lines 29.33 %

Importance

Changes 0
Metric Value
eloc 93
dl 44
loc 150
rs 10
c 0
b 0
f 0
wmc 20

7 Functions

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

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
    content_types = content_type.split(";")
68
    if "application/json" not in content_types:
69
        err = "Expected Content-Type: application/json, " \
70
              f"got: {content_type}"
71
        raise HTTPException(415, detail=err)
72
    return content_type
73
74
75
def error_msg(error_list: list) -> str:
76
    """Return a more request friendly error message from ValidationError"""
77
    msg = ""
78
    for err in error_list:
79
        for value in err['loc']:
80
            msg += str(value) + ", "
81
        msg = msg[:-2]
82
        msg += ": " + err["msg"] + "; "
83
    return msg[:-2]
84
85
86
class JSONResponse(StarletteJSONResponse):
87
    """JSONResponse with custom default serializer that supports datetime."""
88
89
    media_type = "application/json"
90
91
    def render(self, content) -> bytes:
92
        return json.dumps(
93
            content,
94
            ensure_ascii=False,
95
            allow_nan=False,
96
            indent=None,
97
            separators=(",", ":"),
98
            default=_json_serializer,
99
        ).encode("utf-8")
100
101
102
# pylint: disable=super-init-not-called
103 View Code Duplication
class AStarletteOpenAPIRequest(_StarletteOpenAPIRequest):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
104
    """Async StarletteOpenAPIRequest."""
105
106
    def __init__(self, request: Request, body: bytes) -> None:
107
        """Constructor of AsycnStarletteOpenAPIRequest.
108
109
        This constructor doesn't call super().__init__() to keep it async
110
        """
111
        self.request = request
112
        self.parameters = RequestParameters(
113
            query=self.request.query_params,
114
            header=self.request.headers,
115
            cookie=self.request.cookies,
116
        )
117
        self._body = body
118
119
    @property
120
    def body(self) -> Optional[str]:
121
        body = self._body
122
        if body is None:
123
            return None
124
        return body.decode("utf-8")
125
126
127
# pylint: disable=super-init-not-called
128 View Code Duplication
class StarletteOpenAPIRequest(_StarletteOpenAPIRequest):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
129
    """Sync StarletteOpenAPIRequest."""
130
131
    def __init__(self, request: Request, body: bytes) -> None:
132
        """Constructor of AsycnStarletteOpenAPIRequest.
133
134
        This constructor doesn't call super().__init__() to keep it async
135
        """
136
        self.request = request
137
        self.parameters = RequestParameters(
138
            query=self.request.query_params,
139
            header=self.request.headers,
140
            cookie=self.request.cookies,
141
        )
142
        self._body = body
143
144
    @property
145
    def body(self) -> Optional[str]:
146
        body = self._body
147
        if body is None:
148
            return None
149
        return body.decode("utf-8")
150