Skip to content
This repository was archived by the owner on Nov 19, 2023. It is now read-only.

Commit 6a72ade

Browse files
authored
Merge pull request #254 from snok/sondrelg/fixes
Clean up linting configs
2 parents 22513d7 + a0926f5 commit 6a72ade

9 files changed

+59
-78
lines changed

CONTRIBUTING.md

Lines changed: 0 additions & 12 deletions
This file was deleted.

manage.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import sys
44

55

6-
def main():
6+
def main() -> None:
77
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_project.settings")
88
try:
99
from django.core.management import execute_from_command_line

openapi_tester/loaders.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from rest_framework.views import APIView
1919

2020
if TYPE_CHECKING:
21-
from typing import Callable
21+
from typing import Any, Callable
2222
from urllib.parse import ParseResult
2323

2424
from django.urls import ResolverMatch
@@ -74,7 +74,7 @@ def get_schema(self) -> dict:
7474
return self.get_schema()
7575

7676
def de_reference_schema(self, schema: dict) -> dict:
77-
url = schema["basePath"] if "basePath" in schema else self.base_path
77+
url = schema.get("basePath", self.base_path)
7878
recursion_handler = handle_recursion_limit(schema)
7979
resolver = RefResolver(
8080
schema,
@@ -138,7 +138,7 @@ def resolve_path(self, endpoint_path: str, method: str) -> tuple[str, ResolverMa
138138
for key, value in reversed(list(resolved_route.kwargs.items())):
139139
index = path.rfind(str(value))
140140
path = f"{path[:index]}{{{key}}}{path[index + len(str(value)):]}"
141-
if "{pk}" in path and api_settings.SCHEMA_COERCE_PATH_PK:
141+
if "{pk}" in path and api_settings.SCHEMA_COERCE_PATH_PK: # noqa: FS003
142142
path, resolved_route = self.handle_pk_parameter(
143143
resolved_route=resolved_route, path=path, method=method
144144
)
@@ -182,7 +182,7 @@ def load_schema(self) -> dict:
182182
Loads generated schema from drf-yasg and returns it as a dict.
183183
"""
184184
odict_schema = self.schema_generator.get_schema(None, True)
185-
return loads(dumps(odict_schema.as_odict()))
185+
return cast(dict, loads(dumps(odict_schema.as_odict())))
186186

187187
def resolve_path(self, endpoint_path: str, method: str) -> tuple[str, ResolverMatch]:
188188
de_parameterized_path, resolved_path = super().resolve_path(endpoint_path=endpoint_path, method=method)
@@ -206,7 +206,7 @@ def load_schema(self) -> dict:
206206
"""
207207
Loads generated schema from drf_spectacular and returns it as a dict.
208208
"""
209-
return loads(dumps(self.schema_generator.get_schema(public=True)))
209+
return cast(dict, loads(dumps(self.schema_generator.get_schema(public=True))))
210210

211211
def resolve_path(self, endpoint_path: str, method: str) -> tuple[str, ResolverMatch]:
212212
from drf_spectacular.settings import spectacular_settings
@@ -227,7 +227,7 @@ def __init__(self, path: str, field_key_map: dict[str, str] | None = None):
227227
super().__init__(field_key_map=field_key_map)
228228
self.path = path if not isinstance(path, pathlib.PosixPath) else str(path)
229229

230-
def load_schema(self) -> dict:
230+
def load_schema(self) -> dict[str, Any]:
231231
"""
232232
Loads a static OpenAPI schema from file, and parses it to a python dict.
233233
@@ -236,4 +236,6 @@ def load_schema(self) -> dict:
236236
"""
237237
with open(self.path, encoding="utf-8") as file:
238238
content = file.read()
239-
return json.loads(content) if ".json" in self.path else yaml.load(content, Loader=yaml.FullLoader)
239+
return cast(
240+
dict, json.loads(content) if ".json" in self.path else yaml.load(content, Loader=yaml.FullLoader)
241+
)

openapi_tester/schema_tester.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from __future__ import annotations
33

44
from itertools import chain
5-
from typing import TYPE_CHECKING, Callable, List, cast
5+
from typing import TYPE_CHECKING, Any, Callable, List, Optional, cast
66

77
from django.conf import settings
88
from django.core.exceptions import ImproperlyConfigured
@@ -38,7 +38,6 @@
3838
)
3939

4040
if TYPE_CHECKING:
41-
from typing import Any
4241

4342
from rest_framework.response import Response
4443

@@ -79,7 +78,7 @@ def __init__(
7978
raise ImproperlyConfigured(INIT_ERROR)
8079

8180
@staticmethod
82-
def get_key_value(schema: dict, key: str, error_addon: str = "") -> dict:
81+
def get_key_value(schema: dict[str, dict], key: str, error_addon: str = "") -> dict:
8382
"""
8483
Returns the value of a given key
8584
"""
@@ -91,7 +90,7 @@ def get_key_value(schema: dict, key: str, error_addon: str = "") -> dict:
9190
) from e
9291

9392
@staticmethod
94-
def get_status_code(schema: dict, status_code: str | int, error_addon: str = "") -> dict:
93+
def get_status_code(schema: dict[str | int, dict], status_code: str | int, error_addon: str = "") -> dict:
9594
"""
9695
Returns the status code section of a schema, handles both str and int status codes
9796
"""
@@ -104,7 +103,7 @@ def get_status_code(schema: dict, status_code: str | int, error_addon: str = "")
104103
)
105104

106105
@staticmethod
107-
def get_schema_type(schema: dict) -> str | None:
106+
def get_schema_type(schema: dict[str, str]) -> str | None:
108107
if "type" in schema:
109108
return schema["type"]
110109
if "properties" in schema or "additionalProperties" in schema:
@@ -132,14 +131,16 @@ def get_response_schema_section(self, response: Response) -> dict[str, Any]:
132131
method_object = self.get_key_value(
133132
route_object,
134133
response_method,
135-
f"\n\nUndocumented method: {response_method}.\n\nDocumented methods: {[method.lower() for method in route_object.keys() if method.lower() != 'parameters']}.",
134+
f"\n\nUndocumented method: {response_method}.\n\nDocumented methods: "
135+
f"{[method.lower() for method in route_object.keys() if method.lower() != 'parameters']}.",
136136
)
137137

138138
responses_object = self.get_key_value(method_object, "responses")
139139
status_code_object = self.get_status_code(
140140
responses_object,
141141
response.status_code,
142-
f"\n\nUndocumented status code: {response.status_code}.\n\nDocumented status codes: {list(responses_object.keys())}. ",
142+
f"\n\nUndocumented status code: {response.status_code}.\n\n"
143+
f"Documented status codes: {list(responses_object.keys())}. ",
143144
)
144145

145146
if "openapi" not in schema: # pylint: disable=E1135
@@ -155,20 +156,22 @@ def get_response_schema_section(self, response: Response) -> dict[str, Any]:
155156
json_object = self.get_key_value(
156157
content_object,
157158
"application/json",
158-
f"\n\nNo `application/json` responses documented for method: {response_method}, path: {parameterized_path}",
159+
f"\n\nNo `application/json` responses documented for method: "
160+
f"{response_method}, path: {parameterized_path}",
159161
)
160162
return self.get_key_value(json_object, "schema")
161163

162164
if response.json():
163165
raise UndocumentedSchemaSectionError(
164166
UNDOCUMENTED_SCHEMA_SECTION_ERROR.format(
165167
key="content",
166-
error_addon=f"\n\nNo `content` defined for this response: {response_method}, path: {parameterized_path}",
168+
error_addon=f"\n\nNo `content` defined for this response: "
169+
f"{response_method}, path: {parameterized_path}",
167170
)
168171
)
169172
return {}
170173

171-
def handle_one_of(self, schema_section: dict, data: Any, reference: str, **kwargs: Any):
174+
def handle_one_of(self, schema_section: dict, data: Any, reference: str, **kwargs: Any) -> None:
172175
matches = 0
173176
passed_schema_section_formats = set()
174177
for option in schema_section["oneOf"]:
@@ -186,7 +189,7 @@ def handle_one_of(self, schema_section: dict, data: Any, reference: str, **kwarg
186189
if matches != 1:
187190
raise DocumentationError(f"{VALIDATE_ONE_OF_ERROR.format(matches=matches)}\n\nReference: {reference}.oneOf")
188191

189-
def handle_any_of(self, schema_section: dict, data: Any, reference: str, **kwargs: Any):
192+
def handle_any_of(self, schema_section: dict, data: Any, reference: str, **kwargs: Any) -> None:
190193
any_of: list[dict[str, Any]] = schema_section.get("anyOf", [])
191194
for schema in chain(any_of, lazy_combinations(any_of)):
192195
try:
@@ -257,7 +260,7 @@ def test_schema_section(
257260
if not schema_section_type:
258261
return
259262
combined_validators = cast(
260-
List[Callable],
263+
List[Callable[[dict, Any], Optional[str]]],
261264
[
262265
validate_type,
263266
validate_format,
@@ -349,7 +352,7 @@ def test_openapi_object(
349352
ignore_case=ignore_case,
350353
)
351354

352-
def test_openapi_array(self, schema_section: dict, data: dict, reference: str, **kwargs: Any) -> None:
355+
def test_openapi_array(self, schema_section: dict[str, Any], data: dict, reference: str, **kwargs: Any) -> None:
353356
for datum in data:
354357
self.test_schema_section(
355358
# the items keyword is required in arrays
@@ -364,8 +367,8 @@ def validate_response(
364367
response: Response,
365368
case_tester: Callable[[str], None] | None = None,
366369
ignore_case: list[str] | None = None,
367-
validators: list[Callable[[dict, Any], str | None]] | None = None,
368-
):
370+
validators: list[Callable[[dict[str, Any], Any], str | None]] | None = None,
371+
) -> None:
369372
"""
370373
Verifies that an OpenAPI schema definition matches an API response.
371374

openapi_tester/utils.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
""" Utils Module - this file contains utility functions used in multiple places """
1+
"""
2+
Utils Module - this file contains utility functions used in multiple places.
3+
"""
24
from __future__ import annotations
35

46
from copy import deepcopy
@@ -10,7 +12,9 @@
1012

1113

1214
def merge_objects(dictionaries: Sequence[dict[str, Any]]) -> dict[str, Any]:
13-
"""helper function to deep merge objects"""
15+
"""
16+
Deeply merge objects.
17+
"""
1418
output: dict[str, Any] = {}
1519
for dictionary in dictionaries:
1620
for key, value in dictionary.items():
@@ -27,8 +31,10 @@ def merge_objects(dictionaries: Sequence[dict[str, Any]]) -> dict[str, Any]:
2731
return output
2832

2933

30-
def normalize_schema_section(schema_section: dict) -> dict:
31-
"""helper method to remove allOf and handle edge uses of oneOf"""
34+
def normalize_schema_section(schema_section: dict[str, Any]) -> dict[str, Any]:
35+
"""
36+
Remove allOf and handle edge uses of oneOf.
37+
"""
3238
output: dict[str, Any] = deepcopy(schema_section)
3339
if output.get("allOf"):
3440
all_of = output.pop("allOf")
@@ -46,7 +52,9 @@ def normalize_schema_section(schema_section: dict) -> dict:
4652

4753

4854
def lazy_combinations(options_list: Sequence[dict[str, Any]]) -> Iterator[dict]:
49-
"""helper to lazy evaluate possible permutations of possible combinations"""
55+
"""
56+
Lazily evaluate possible combinations.
57+
"""
5058
for i in range(2, len(options_list) + 1):
5159
for combination in combinations(options_list, i):
5260
yield merge_objects(combination)

openapi_tester/validators.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@
3333
from typing import Any, Callable
3434

3535

36-
def create_validator(validation_fn: Callable, wrap_as_validator: bool = False) -> Callable:
37-
def wrapped(value: Any):
36+
def create_validator(validation_fn: Callable, wrap_as_validator: bool = False) -> Callable[[Any], bool]:
37+
def wrapped(value: Any) -> bool:
3838
try:
3939
return bool(validation_fn(value)) or not wrap_as_validator
4040
except (ValueError, ValidationError):

pyproject.toml

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -56,31 +56,27 @@ prance = "*"
5656
pyYAML = "*"
5757

5858
[tool.poetry.dev-dependencies]
59+
coverage = { extras = ["toml"], version = "^6"}
5960
drf-spectacular = "*"
6061
drf-yasg = "*"
6162
Faker = "*"
6263
pre-commit = "*"
63-
pytest = "*"
64-
pytest-django = "*"
6564
pylint = "*"
66-
coverage = { extras = ["toml"], version = "6.1"}
65+
pytest = "*"
6766
pytest-cov = "*"
67+
pytest-django = "*"
68+
69+
[build-system]
70+
requires = ["poetry>=0.12"]
71+
build-backend = "poetry.masonry.api"
6872

6973
[tool.black]
7074
line-length = 120
7175
include = '\.pyi?$'
7276

7377
[tool.isort]
7478
profile = "black"
75-
multi_line_output = 3
76-
include_trailing_comma = true
7779
line_length = 120
78-
known_third_party = ["django", "drf_spectacular", "drf_yasg", "faker", "inflection", "openapi_spec_validator", "prance", "pytest", "rest_framework", "yaml"]
79-
known_first_party = ["openapi_tester"]
80-
81-
[build-system]
82-
requires = ["poetry>=0.12"]
83-
build-backend = "poetry.masonry.api"
8480

8581
[tool.pylint.FORMAT]
8682
max-line-length = 120
@@ -106,9 +102,8 @@ max-locals = 20
106102
good-names = "_,e,i"
107103

108104
[tool.coverage.run]
109-
source = ["openapi_tester/*"]
105+
source = ["openapi_tester"]
110106
omit = [
111-
"openapi_tester/type_declarations.py",
112107
"manage.py",
113108
"test_project/*",
114109
]

setup.cfg

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,23 @@
11
[flake8]
22
ignore=
3-
# E501: Line length
4-
E501
53
# Docstring at the top of a public module
64
D100
75
# Docstring at the top of a public class (method is enough)
86
D101
97
# Make docstrings one line if it can fit.
108
D200
119
D210
12-
# Imperative docstring declarations
13-
D401
14-
# Type annotation for `self`
15-
TYP101
16-
TYP102 # for cls
17-
ANN101
1810
# Missing docstring in __init__
1911
D107
2012
# Missing docstring in public package
2113
D104
22-
# Missing type annotations for `**kwargs`
23-
TYP003
2414
# Whitespace before ':'. Black formats code this way.
2515
E203
2616
# 1 blank line required between summary line and description
2717
D205
28-
# First line should end with a period - here we have a few cases where the first line is too long, and
29-
# this issue can't be fixed without using noqa notation
30-
D400
3118
# Line break before binary operator. W504 will be hit when this is excluded.
3219
W503
33-
# Missing type annotation for *args
34-
TYP002
35-
ANN002
36-
# Missing type annotation for **kwargs
37-
ANN003
38-
# f-string missing prefix (too many false positives)
39-
FS003
40-
# Handle error-cases first
20+
# Handle error cases first
4121
SIM106
4222
enable-extensions =
4323
enable-extensions = TC, TC1
@@ -49,9 +29,13 @@ exclude =
4929
manage.py,
5030
.venv
5131
max-complexity = 16
32+
max-line-length = 120
33+
per-file-ignores =
34+
openapi_tester/constants.py:FS003
35+
tests/*:FS003
36+
test_project/*:FS003
5237

5338
[mypy]
54-
python_version = 3.10
5539
show_column_numbers = True
5640
show_error_context = False
5741
ignore_missing_imports = True

tests/test_schema_tester.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,8 @@ def test_validate_response_failure_scenario_undocumented_content(client, monkeyp
201201
response = client.get(de_parameterized_path)
202202
with pytest.raises(
203203
UndocumentedSchemaSectionError,
204-
match=f"Error: Unsuccessfully tried to index the OpenAPI schema by `content`. \n\nNo `content` defined for this response: {method}, path: {parameterized_path}",
204+
match=f"Error: Unsuccessfully tried to index the OpenAPI schema by `content`. \n\n"
205+
f"No `content` defined for this response: {method}, path: {parameterized_path}",
205206
):
206207
tester.validate_response(response)
207208

0 commit comments

Comments
 (0)