Skip to content

Commit bbed47b

Browse files
committed
Merge branch 'feat/templatize-api-endpoint-init-files' into develop
2 parents 0f58cfd + ce3f12b commit bbed47b

File tree

17 files changed

+359
-69
lines changed

17 files changed

+359
-69
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
my-test-api-client
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
""" Contains methods for accessing the API """
2+
3+
from typing import Type
4+
5+
from my_test_api_client.api.default import DefaultEndpoints
6+
from my_test_api_client.api.parameters import ParametersEndpoints
7+
from my_test_api_client.api.tests import TestsEndpoints
8+
9+
10+
class MyTestApiClientApi:
11+
@classmethod
12+
def tests(cls) -> Type[TestsEndpoints]:
13+
return TestsEndpoints
14+
15+
@classmethod
16+
def default(cls) -> Type[DefaultEndpoints]:
17+
return DefaultEndpoints
18+
19+
@classmethod
20+
def parameters(cls) -> Type[ParametersEndpoints]:
21+
return ParametersEndpoints
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
""" Contains methods for accessing the API Endpoints """
2+
3+
import types
4+
5+
from my_test_api_client.api.default import get_common_parameters, post_common_parameters
6+
7+
8+
class DefaultEndpoints:
9+
@classmethod
10+
def get_common_parameters(cls) -> types.ModuleType:
11+
return get_common_parameters
12+
13+
@classmethod
14+
def post_common_parameters(cls) -> types.ModuleType:
15+
return post_common_parameters
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
""" Contains methods for accessing the API Endpoints """
2+
3+
import types
4+
5+
from my_test_api_client.api.parameters import get_same_name_multiple_locations_param
6+
7+
8+
class ParametersEndpoints:
9+
@classmethod
10+
def get_same_name_multiple_locations_param(cls) -> types.ModuleType:
11+
return get_same_name_multiple_locations_param
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
""" Contains methods for accessing the API Endpoints """
2+
3+
import types
4+
5+
from my_test_api_client.api.tests import (
6+
defaults_tests_defaults_post,
7+
get_basic_list_of_booleans,
8+
get_basic_list_of_floats,
9+
get_basic_list_of_integers,
10+
get_basic_list_of_strings,
11+
get_user_list,
12+
int_enum_tests_int_enum_post,
13+
json_body_tests_json_body_post,
14+
no_response_tests_no_response_get,
15+
octet_stream_tests_octet_stream_get,
16+
optional_value_tests_optional_query_param,
17+
test_inline_objects,
18+
token_with_cookie_auth_token_with_cookie_get,
19+
unsupported_content_tests_unsupported_content_get,
20+
upload_file_tests_upload_post,
21+
)
22+
23+
24+
class TestsEndpoints:
25+
@classmethod
26+
def get_user_list(cls) -> types.ModuleType:
27+
"""
28+
Get a list of things
29+
"""
30+
return get_user_list
31+
32+
@classmethod
33+
def get_basic_list_of_strings(cls) -> types.ModuleType:
34+
"""
35+
Get a list of strings
36+
"""
37+
return get_basic_list_of_strings
38+
39+
@classmethod
40+
def get_basic_list_of_integers(cls) -> types.ModuleType:
41+
"""
42+
Get a list of integers
43+
"""
44+
return get_basic_list_of_integers
45+
46+
@classmethod
47+
def get_basic_list_of_floats(cls) -> types.ModuleType:
48+
"""
49+
Get a list of floats
50+
"""
51+
return get_basic_list_of_floats
52+
53+
@classmethod
54+
def get_basic_list_of_booleans(cls) -> types.ModuleType:
55+
"""
56+
Get a list of booleans
57+
"""
58+
return get_basic_list_of_booleans
59+
60+
@classmethod
61+
def upload_file_tests_upload_post(cls) -> types.ModuleType:
62+
"""
63+
Upload a file
64+
"""
65+
return upload_file_tests_upload_post
66+
67+
@classmethod
68+
def json_body_tests_json_body_post(cls) -> types.ModuleType:
69+
"""
70+
Try sending a JSON body
71+
"""
72+
return json_body_tests_json_body_post
73+
74+
@classmethod
75+
def defaults_tests_defaults_post(cls) -> types.ModuleType:
76+
"""
77+
Defaults
78+
"""
79+
return defaults_tests_defaults_post
80+
81+
@classmethod
82+
def octet_stream_tests_octet_stream_get(cls) -> types.ModuleType:
83+
"""
84+
Octet Stream
85+
"""
86+
return octet_stream_tests_octet_stream_get
87+
88+
@classmethod
89+
def no_response_tests_no_response_get(cls) -> types.ModuleType:
90+
"""
91+
No Response
92+
"""
93+
return no_response_tests_no_response_get
94+
95+
@classmethod
96+
def unsupported_content_tests_unsupported_content_get(cls) -> types.ModuleType:
97+
"""
98+
Unsupported Content
99+
"""
100+
return unsupported_content_tests_unsupported_content_get
101+
102+
@classmethod
103+
def int_enum_tests_int_enum_post(cls) -> types.ModuleType:
104+
"""
105+
Int Enum
106+
"""
107+
return int_enum_tests_int_enum_post
108+
109+
@classmethod
110+
def test_inline_objects(cls) -> types.ModuleType:
111+
"""
112+
Test Inline Objects
113+
"""
114+
return test_inline_objects
115+
116+
@classmethod
117+
def optional_value_tests_optional_query_param(cls) -> types.ModuleType:
118+
"""
119+
Test optional query parameters
120+
"""
121+
return optional_value_tests_optional_query_param
122+
123+
@classmethod
124+
def token_with_cookie_auth_token_with_cookie_get(cls) -> types.ModuleType:
125+
"""
126+
Test optional cookie parameters
127+
"""
128+
return token_with_cookie_auth_token_with_cookie_get

end_to_end_tests/regen_golden_record.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
""" Regenerate golden-record """
2+
import filecmp
3+
import os
24
import shutil
5+
import tempfile
36
from pathlib import Path
47

58
from typer.testing import CliRunner
69

710
from openapi_python_client.cli import app
811

9-
if __name__ == "__main__":
12+
13+
def regen_golden_record():
1014
runner = CliRunner()
1115
openapi_path = Path(__file__).parent / "openapi.json"
1216

@@ -24,3 +28,52 @@
2428
if result.exception:
2529
raise result.exception
2630
output_path.rename(gr_path)
31+
32+
33+
def regen_custom_template_golden_record():
34+
runner = CliRunner()
35+
openapi_path = Path(__file__).parent / "openapi.json"
36+
tpl_dir = Path(__file__).parent / "test_custom_templates"
37+
38+
gr_path = Path(__file__).parent / "golden-record"
39+
tpl_gr_path = Path(__file__).parent / "custom-templates-golden-record"
40+
41+
output_path = Path(tempfile.mkdtemp())
42+
config_path = Path(__file__).parent / "config.yml"
43+
44+
shutil.rmtree(tpl_gr_path, ignore_errors=True)
45+
46+
os.chdir(str(output_path.absolute()))
47+
result = runner.invoke(
48+
app, ["generate", f"--config={config_path}", f"--path={openapi_path}", f"--custom-template-path={tpl_dir}"]
49+
)
50+
51+
if result.stdout:
52+
generated_output_path = output_path / "my-test-api-client"
53+
for f in generated_output_path.glob("**/*"): # nb: works for Windows and Unix
54+
relative_to_generated = f.relative_to(generated_output_path)
55+
gr_file = gr_path / relative_to_generated
56+
if not gr_file.exists():
57+
print(f"{gr_file} does not exist, ignoring")
58+
continue
59+
60+
if not gr_file.is_file():
61+
continue
62+
63+
if not filecmp.cmp(gr_file, f, shallow=False):
64+
target_file = tpl_gr_path / relative_to_generated
65+
target_dir = target_file.parent
66+
67+
target_dir.mkdir(parents=True, exist_ok=True)
68+
shutil.copy(f"{f}", f"{target_file}")
69+
70+
shutil.rmtree(output_path, ignore_errors=True)
71+
72+
if result.exception:
73+
shutil.rmtree(output_path, ignore_errors=True)
74+
raise result.exception
75+
76+
77+
if __name__ == "__main__":
78+
regen_golden_record()
79+
regen_custom_template_golden_record()
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
""" Contains methods for accessing the API """
2+
3+
from typing import Type
4+
{% for tag in endpoint_collections_by_tag.keys() %}
5+
from {{ package_name }}.api.{{ tag }} import {{ utils.pascal_case(tag) }}Endpoints
6+
{% endfor %}
7+
8+
class {{ utils.pascal_case(package_name) }}Api:
9+
{% for tag in endpoint_collections_by_tag.keys() %}
10+
@classmethod
11+
def {{ tag }}(cls) -> Type[{{ utils.pascal_case(tag) }}Endpoints]:
12+
return {{ utils.pascal_case(tag) }}Endpoints
13+
{% endfor %}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
""" Contains methods for accessing the API Endpoints """
2+
3+
import types
4+
{% for endpoint in endpoint_collection.endpoints %}
5+
from {{ package_name }}.api.{{ endpoint_collection.tag }} import {{ utils.snake_case(endpoint.name) }}
6+
{% endfor %}
7+
8+
class {{ utils.pascal_case(endpoint_collection.tag) }}Endpoints:
9+
10+
{% for endpoint in endpoint_collection.endpoints %}
11+
12+
@classmethod
13+
def {{ utils.snake_case(endpoint.name) }}(cls) -> types.ModuleType:
14+
{% if endpoint.description %}
15+
"""
16+
{{ endpoint.description }}
17+
"""
18+
{% elif endpoint.summary %}
19+
"""
20+
{{ endpoint.summary }}
21+
"""
22+
{% endif %}
23+
return {{ utils.snake_case(endpoint.name) }}
24+
{% endfor %}

end_to_end_tests/test_end_to_end.py

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212
def _compare_directories(
1313
record: Path,
1414
test_subject: Path,
15-
expected_differences: Optional[Dict[str, str]] = None,
15+
expected_differences: Optional[
16+
Dict[Path, str]
17+
] = None, # key: path relative to generated directory, value: expected generated content
18+
depth=0,
1619
):
1720
first_printable = record.relative_to(Path.cwd())
1821
second_printable = test_subject.relative_to(Path.cwd())
@@ -22,27 +25,41 @@ def _compare_directories(
2225
pytest.fail(f"{first_printable} or {second_printable} was missing: {missing_files}", pytrace=False)
2326

2427
expected_differences = expected_differences or {}
25-
_, mismatch, errors = cmpfiles(record, test_subject, dc.common_files, shallow=False)
26-
mismatch = set(mismatch)
27-
28-
for file_name in mismatch | set(expected_differences.keys()):
29-
if file_name not in expected_differences:
30-
continue
31-
if file_name not in mismatch:
32-
pytest.fail(f"Expected {file_name} to be different but it was not", pytrace=False)
33-
generated = (test_subject / file_name).read_text()
34-
assert generated == expected_differences[file_name], f"Unexpected output in {file_name}"
35-
del expected_differences[file_name]
36-
mismatch.remove(file_name)
37-
38-
if mismatch:
28+
_, mismatches, errors = cmpfiles(record, test_subject, dc.common_files, shallow=False)
29+
mismatches = set(mismatches)
30+
31+
expected_path_mismatches = []
32+
for file_name in mismatches:
33+
34+
mismatch_file_path = test_subject.joinpath(file_name)
35+
for expected_differences_path in expected_differences.keys():
36+
37+
if mismatch_file_path.match(str(expected_differences_path)):
38+
39+
generated_content = (test_subject / file_name).read_text()
40+
expected_content = expected_differences[expected_differences_path]
41+
assert generated_content == expected_content, f"Unexpected output in {mismatch_file_path}"
42+
expected_path_mismatches.append(expected_differences_path)
43+
44+
for path_mismatch in expected_path_mismatches:
45+
matched_file_name = path_mismatch.name
46+
mismatches.remove(matched_file_name)
47+
del expected_differences[path_mismatch]
48+
49+
if mismatches:
3950
pytest.fail(
40-
f"{first_printable} and {second_printable} had differing files: {mismatch}, and errors {errors}",
51+
f"{first_printable} and {second_printable} had differing files: {mismatches}, and errors {errors}",
4152
pytrace=False,
4253
)
4354

4455
for sub_path in dc.common_dirs:
45-
_compare_directories(record / sub_path, test_subject / sub_path, expected_differences=expected_differences)
56+
_compare_directories(
57+
record / sub_path, test_subject / sub_path, expected_differences=expected_differences, depth=depth + 1
58+
)
59+
60+
if depth == 0 and len(expected_differences.keys()) > 0:
61+
failure = "\n".join([f"Expected {path} to be different but it was not" for path in expected_differences.keys()])
62+
pytest.fail(failure, pytrace=False)
4663

4764

4865
def run_e2e_test(extra_args=None, expected_differences=None):
@@ -60,6 +77,7 @@ def run_e2e_test(extra_args=None, expected_differences=None):
6077

6178
if result.exit_code != 0:
6279
raise result.exception
80+
6381
_compare_directories(gr_path, output_path, expected_differences=expected_differences)
6482

6583
import mypy.api
@@ -75,7 +93,21 @@ def test_end_to_end():
7593

7694

7795
def test_custom_templates():
96+
expected_differences = {} # key: path relative to generated directory, value: expected generated content
97+
expected_difference_paths = [
98+
Path("README.md"),
99+
Path("my_test_api_client").joinpath("api", "__init__.py"),
100+
Path("my_test_api_client").joinpath("api", "tests", "__init__.py"),
101+
Path("my_test_api_client").joinpath("api", "default", "__init__.py"),
102+
Path("my_test_api_client").joinpath("api", "parameters", "__init__.py"),
103+
]
104+
105+
golden_tpls_root_dir = Path(__file__).parent.joinpath("custom-templates-golden-record")
106+
for expected_difference_path in expected_difference_paths:
107+
path = Path("my-test-api-client").joinpath(expected_difference_path)
108+
expected_differences[path] = (golden_tpls_root_dir / expected_difference_path).read_text()
109+
78110
run_e2e_test(
79-
extra_args=["--custom-template-path=end_to_end_tests/test_custom_templates"],
80-
expected_differences={"README.md": "my-test-api-client"},
111+
extra_args=["--custom-template-path=end_to_end_tests/test_custom_templates/"],
112+
expected_differences=expected_differences,
81113
)

0 commit comments

Comments
 (0)