Skip to content

Commit 8d3c84e

Browse files
authored
Merge pull request #11 from openapi-json-schema-tools/feat_responses_report
Adds responses report
2 parents e689017 + a59acfe commit 8d3c84e

File tree

4 files changed

+117
-15
lines changed

4 files changed

+117
-15
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ pip install pyyaml
2222
- [properties usage](reports/properties_report.md)
2323
- [required usage](reports/required_report.md)
2424
- [required keys python usage](reports/required_keys_python_report.md)
25-
- [properties keys python usage](reports/properties_keys_python_report.md)
25+
- [properties keys python usage](reports/properties_keys_python_report.md)
26+
- [responses usage](reports/responses_report.md)

analyze.py

+34-8
Original file line numberDiff line numberDiff line change
@@ -66,21 +66,47 @@ def write_required_report(filtered_document_paths: typing.List[str], metrics_dat
6666
with open('extracted_key_data_v3specs/required_key_to_qty.py', 'wt') as stream:
6767
stream.write(f"data = {metrics_data.required_key_to_qty}\n")
6868

69+
def write_responses_report(filtered_document_paths: typing.List[str], metrics_data: loader.MetricsData):
70+
responses_info = report.TableInfo(
71+
title='Responses Metrics',
72+
table_headers=('Metric', 'Qty'),
73+
table_data=tuple({
74+
'openapi_documents_qty': len(filtered_document_paths),
75+
'responses_key_qty': metrics_data.responses_qty,
76+
}.items())
77+
)
78+
responses_detailed_info = report.TableInfo(
79+
title='Responses Type To Qty Metrics',
80+
table_headers=('Type', 'Qty'),
81+
table_data=tuple(metrics_data.responses_qtys.items())
82+
)
83+
responses_report = report.ReportInfo(
84+
title='Json Schema Operations Responses Usage Info',
85+
description='Counts number of operations with 1 or more responses. documents: 3.0.0-3.1.0 yaml specs only',
86+
table_infos={
87+
'responses_info': responses_info,
88+
'responses_detailed_info': responses_detailed_info
89+
}
90+
)
91+
with open('reports/responses_report.md', 'wt') as stream:
92+
responses_report.write(stream)
93+
6994
if __name__ == '__main__':
7095
document_paths = loader.find_document_paths()
7196
print(f"qty_found_documents={len(document_paths)}")
7297
metrics_data = loader.MetricsData()
7398
filtered_document_paths = loader.filter_and_analyze_documents(document_paths, metrics_data)
74-
print(f"qty_filtered_document_paths={len(filtered_document_paths)}")
75-
print(f"properties_key_qty={metrics_data.properties_key_qty}")
76-
print(f"properties_adjacent_to_type={metrics_data.properties_adjacent_to_type}")
77-
print(f"properties_not_adjacent_to_type_qty={metrics_data.properties_not_adjacent_to_type_qty}")
78-
print(f"required_usage_qty={metrics_data.required_usage_qty}")
79-
print(f"required_adjacent_to_type={metrics_data.required_adjacent_to_type}")
80-
print(f"required_not_adjacent_to_type_qty={metrics_data.required_not_adjacent_to_type_qty}")
81-
print(f"required_key_to_qty={metrics_data.required_key_to_qty}")
99+
# print(f"qty_filtered_document_paths={len(filtered_document_paths)}")
100+
# print(f"properties_key_qty={metrics_data.properties_key_qty}")
101+
# print(f"properties_adjacent_to_type={metrics_data.properties_adjacent_to_type}")
102+
# print(f"properties_not_adjacent_to_type_qty={metrics_data.properties_not_adjacent_to_type_qty}")
103+
# print(f"required_usage_qty={metrics_data.required_usage_qty}")
104+
# print(f"required_adjacent_to_type={metrics_data.required_adjacent_to_type}")
105+
# print(f"required_not_adjacent_to_type_qty={metrics_data.required_not_adjacent_to_type_qty}")
106+
# print(f"required_key_to_qty={metrics_data.required_key_to_qty}")
82107

83108
write_properties_report(filtered_document_paths, metrics_data)
84109
write_required_report(filtered_document_paths, metrics_data)
110+
write_responses_report(filtered_document_paths, metrics_data)
85111

86112

analyzer/loader.py

+63-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import dataclasses
2+
import enum
23
import glob
34
import mmap
45
import typing
@@ -33,6 +34,15 @@ def file_contains_3x_spec_version(document_path: str) -> bool:
3334
yaml.constructor.SafeConstructor.construct_yaml_str
3435
)
3536

37+
class ResponseType(enum.Enum):
38+
DEFAULT_ONLY = enum.auto()
39+
STATUS_ONLY = enum.auto()
40+
WILDCARD_ONLY = enum.auto()
41+
DEFAULT_STATUS = enum.auto()
42+
DEFAULT_WILDCARD = enum.auto()
43+
STATUS_WIDLCARD = enum.auto()
44+
DEFAULT_STATUS_WILDCARD = enum.auto()
45+
3646
@dataclasses.dataclass
3747
class MetricsData:
3848
properties_key_qty: int = 0
@@ -43,6 +53,8 @@ class MetricsData:
4353
required_not_adjacent_to_type_qty: int = 0
4454
required_adjacent_to_type: typing.Dict[str, int] = dataclasses.field(default_factory=lambda: {})
4555
required_key_to_qty: typing.Dict[str, int] = dataclasses.field(default_factory=lambda: {})
56+
responses_qty: int = 0
57+
responses_qtys: typing.Dict[ResponseType, int] = dataclasses.field(default_factory=lambda: {})
4658

4759

4860

@@ -110,19 +122,61 @@ def __init__(self, stream, metrics_data: MetricsData):
110122
resolver.Resolver.__init__(self)
111123

112124

113-
def yaml_loading_works(document_path: str, metrics_data: MetricsData) -> bool:
125+
def get_yaml_doc(document_path: str, metrics_data: MetricsData) -> typing.Optional[dict]:
114126
try:
115127
with open(document_path, 'r') as file:
116128

117129
loader = CustomLoader(file, metrics_data)
118130
try:
119-
_loaded_data = loader.get_single_data()
131+
return loader.get_single_data()
120132
finally:
121133
loader.dispose()
122-
return True
123134
except Exception as exc:
124135
print (f"Yaml error in file={document_path} error={exc}")
125-
return False
136+
return None
137+
138+
def increment_response_type(metrics_data: MetricsData, response_type: ResponseType):
139+
if response_type not in metrics_data.responses_qtys:
140+
metrics_data.responses_qtys[response_type] = 0
141+
metrics_data.responses_qtys[response_type] += 1
142+
143+
def anaylze_doc(yaml_doc: dict, metrics_data: MetricsData):
144+
path_items = yaml_doc.get('paths')
145+
if path_items is not None:
146+
for path_item in path_items.values():
147+
if not isinstance(path_item, dict):
148+
continue
149+
for verb in {'get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'}:
150+
operation = path_item.get(verb)
151+
if operation is None:
152+
continue
153+
responses: typing.Optional[typing.Dict[str, dict]] = operation.get('responses')
154+
if responses is None:
155+
continue
156+
if len(responses) == 0:
157+
continue
158+
metrics_data.responses_qty += 1
159+
if len(responses) == 1:
160+
key = [k for k in responses][0]
161+
if key == 'default':
162+
increment_response_type(metrics_data, ResponseType.DEFAULT_ONLY)
163+
elif key.endswith('XX'):
164+
increment_response_type(metrics_data, ResponseType.WILDCARD_ONLY)
165+
else:
166+
increment_response_type(metrics_data, ResponseType.STATUS_ONLY)
167+
else:
168+
default_present = 'default' in responses
169+
wildcard_present = any(k.endswith('XX') for k in responses)
170+
status_present = any(not(k.endswith('XX') or k == 'default') for k in responses)
171+
number = [default_present, wildcard_present, status_present]
172+
if number == [True, True, False]:
173+
increment_response_type(metrics_data, ResponseType.DEFAULT_WILDCARD)
174+
elif number == [True, False, True]:
175+
increment_response_type(metrics_data, ResponseType.DEFAULT_STATUS)
176+
elif number == [False, True, True]:
177+
increment_response_type(metrics_data, ResponseType.STATUS_WIDLCARD)
178+
elif number == [True, True, True]:
179+
increment_response_type(metrics_data, ResponseType.DEFAULT_STATUS_WILDCARD)
126180

127181
def filter_and_analyze_documents(document_paths: typing.List[str], metrics_data: MetricsData) -> typing.List[str]:
128182
filtered_paths: typing.List[str] = []
@@ -132,6 +186,9 @@ def filter_and_analyze_documents(document_paths: typing.List[str], metrics_data:
132186
is_v3_spec = file_contains_3x_spec_version(document_path)
133187
# print(f"path={document_path} v3={is_v3_spec}")
134188
if is_v3_spec:
135-
if yaml_loading_works(document_path, metrics_data):
136-
filtered_paths.append(document_path)
189+
yaml_doc = get_yaml_doc(document_path, metrics_data)
190+
if yaml_doc is None:
191+
continue
192+
anaylze_doc(yaml_doc, metrics_data)
193+
filtered_paths.append(document_path)
137194
return filtered_paths

reports/responses_report.md

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Json Schema Operations Responses Usage Info
2+
3+
Counts number of operations with 1 or more responses. documents: 3.0.0-3.1.0 yaml specs only
4+
5+
## Responses Metrics
6+
| Metric | Qty |
7+
| --------------------- | ----- |
8+
| responses_key_qty | 67663 |
9+
| openapi_documents_qty | 1857 |
10+
## Responses Type To Qty Metrics
11+
| Type | Qty |
12+
| ----------------------------- | ----- |
13+
| ResponseType.STATUS_ONLY | 29179 |
14+
| ResponseType.DEFAULT_STATUS | 4880 |
15+
| ResponseType.DEFAULT_ONLY | 545 |
16+
| ResponseType.WILDCARD_ONLY | 281 |
17+
| ResponseType.STATUS_WIDLCARD | 74 |
18+
| ResponseType.DEFAULT_WILDCARD | 21 |

0 commit comments

Comments
 (0)