From b072fc7995a42098560a3ec49415dc2a848ce75b Mon Sep 17 00:00:00 2001
From: Nementon <nementon@badwolf.fr>
Date: Fri, 30 Apr 2021 15:23:23 +0200
Subject: [PATCH 01/11] templatize api,endpoint init files

---
 openapi_python_client/__init__.py             | 21 ++++++++++++++++---
 .../templates/api_init.py.jinja               |  1 +
 .../templates/endpoint_init.py.jinja          |  0
 3 files changed, 19 insertions(+), 3 deletions(-)
 create mode 100644 openapi_python_client/templates/api_init.py.jinja
 create mode 100644 openapi_python_client/templates/endpoint_init.py.jinja

diff --git a/openapi_python_client/__init__.py b/openapi_python_client/__init__.py
index 2a7cf574b..35018d1cd 100644
--- a/openapi_python_client/__init__.py
+++ b/openapi_python_client/__init__.py
@@ -239,15 +239,30 @@ def _build_api(self) -> None:
         client_path.write_text(client_template.render(), encoding=self.file_encoding)
 
         # Generate endpoints
+        endpoint_collections_by_tag = self.openapi.endpoint_collections_by_tag.items()
         api_dir = self.package_dir / "api"
         api_dir.mkdir()
-        api_init = api_dir / "__init__.py"
-        api_init.write_text('""" Contains methods for accessing the API """', encoding=self.file_encoding)
+        api_init_path = api_dir / "__init__.py"
+        api_init_template = self.env.get_template("api_init.py.jinja")
+        api_init_path.write_text(
+            api_init_template.render(
+                package_name=self.package_name,
+                endpoint_collections_by_tag=endpoint_collections_by_tag,
+            ),
+            encoding=self.file_encoding,
+        )
 
         endpoint_template = self.env.get_template("endpoint_module.py.jinja")
-        for tag, collection in self.openapi.endpoint_collections_by_tag.items():
+        for tag, collection in endpoint_collections_by_tag:
             tag_dir = api_dir / tag
             tag_dir.mkdir()
+
+            endpoint_init_path = tag_dir / "__init__.py"
+            endpoint_init_template = self.env.get_template("endpoint_init.py.jinja")
+            endpoint_init_path.write_text(
+                endpoint_init_template.render(package_name=self.package_name, tag=tag, endpoints=collection.endpoints),
+                encoding=self.file_encoding,
+            )
             (tag_dir / "__init__.py").touch()
 
             for endpoint in collection.endpoints:
diff --git a/openapi_python_client/templates/api_init.py.jinja b/openapi_python_client/templates/api_init.py.jinja
new file mode 100644
index 000000000..dc035f4ce
--- /dev/null
+++ b/openapi_python_client/templates/api_init.py.jinja
@@ -0,0 +1 @@
+""" Contains methods for accessing the API """
diff --git a/openapi_python_client/templates/endpoint_init.py.jinja b/openapi_python_client/templates/endpoint_init.py.jinja
new file mode 100644
index 000000000..e69de29bb

From e10ba94c09601c30f51b9430c978b20271cba4e5 Mon Sep 17 00:00:00 2001
From: p1-ra <aurelien.roose@p1sec.com>
Date: Mon, 3 May 2021 15:35:14 +0200
Subject: [PATCH 02/11] e2e / add {api_init.py.jina, endpoint_init.py.jinja}
 custom templates tests

---
 .../custom-templates-golden-record/README.md  |   1 +
 .../my_test_api_client/api/__init__.py        |  17 +++
 .../api/default/__init__.py                   |  16 +++
 .../my_test_api_client/api/tests/__init__.py  | 129 ++++++++++++++++++
 .../test_custom_templates/api_init.py.jinja   |  19 +++
 .../endpoint_init.py.jinja                    |  37 +++++
 end_to_end_tests/test_end_to_end.py           |  70 +++++++---
 7 files changed, 270 insertions(+), 19 deletions(-)
 create mode 100644 end_to_end_tests/custom-templates-golden-record/README.md
 create mode 100644 end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/__init__.py
 create mode 100644 end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/default/__init__.py
 create mode 100644 end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/tests/__init__.py
 create mode 100644 end_to_end_tests/test_custom_templates/api_init.py.jinja
 create mode 100644 end_to_end_tests/test_custom_templates/endpoint_init.py.jinja

diff --git a/end_to_end_tests/custom-templates-golden-record/README.md b/end_to_end_tests/custom-templates-golden-record/README.md
new file mode 100644
index 000000000..e5106eea7
--- /dev/null
+++ b/end_to_end_tests/custom-templates-golden-record/README.md
@@ -0,0 +1 @@
+my-test-api-client
\ No newline at end of file
diff --git a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/__init__.py b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/__init__.py
new file mode 100644
index 000000000..359d0b2dc
--- /dev/null
+++ b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/__init__.py
@@ -0,0 +1,17 @@
+""" Contains methods for accessing the API """
+
+
+from typing import Type
+
+from my_test_api_client.api.default import DefaultEndpoints
+from my_test_api_client.api.tests import TestsEndpoints
+
+
+class MyTestApiClientApi:
+    @classmethod
+    def tests(cls) -> Type[TestsEndpoints]:
+        return TestsEndpoints
+
+    @classmethod
+    def default(cls) -> Type[DefaultEndpoints]:
+        return DefaultEndpoints
diff --git a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/default/__init__.py b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/default/__init__.py
new file mode 100644
index 000000000..5928666d9
--- /dev/null
+++ b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/default/__init__.py
@@ -0,0 +1,16 @@
+""" Contains methods for accessing the API Endpoints """
+
+
+import types
+
+from my_test_api_client.api.default import get_common_parameters, post_common_parameters
+
+
+class DefaultEndpoints:
+    @classmethod
+    def get_common_parameters(cls) -> types.ModuleType:
+        return get_common_parameters
+
+    @classmethod
+    def post_common_parameters(cls) -> types.ModuleType:
+        return post_common_parameters
diff --git a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/tests/__init__.py b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/tests/__init__.py
new file mode 100644
index 000000000..b92538c28
--- /dev/null
+++ b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/tests/__init__.py
@@ -0,0 +1,129 @@
+""" Contains methods for accessing the API Endpoints """
+
+
+import types
+
+from my_test_api_client.api.tests import (
+    defaults_tests_defaults_post,
+    get_basic_list_of_booleans,
+    get_basic_list_of_floats,
+    get_basic_list_of_integers,
+    get_basic_list_of_strings,
+    get_user_list,
+    int_enum_tests_int_enum_post,
+    json_body_tests_json_body_post,
+    no_response_tests_no_response_get,
+    octet_stream_tests_octet_stream_get,
+    optional_value_tests_optional_query_param,
+    test_inline_objects,
+    token_with_cookie_auth_token_with_cookie_get,
+    unsupported_content_tests_unsupported_content_get,
+    upload_file_tests_upload_post,
+)
+
+
+class TestsEndpoints:
+    @classmethod
+    def get_user_list(cls) -> types.ModuleType:
+        """
+        Get a list of things
+        """
+        return get_user_list
+
+    @classmethod
+    def get_basic_list_of_strings(cls) -> types.ModuleType:
+        """
+        Get a list of strings
+        """
+        return get_basic_list_of_strings
+
+    @classmethod
+    def get_basic_list_of_integers(cls) -> types.ModuleType:
+        """
+        Get a list of integers
+        """
+        return get_basic_list_of_integers
+
+    @classmethod
+    def get_basic_list_of_floats(cls) -> types.ModuleType:
+        """
+        Get a list of floats
+        """
+        return get_basic_list_of_floats
+
+    @classmethod
+    def get_basic_list_of_booleans(cls) -> types.ModuleType:
+        """
+        Get a list of booleans
+        """
+        return get_basic_list_of_booleans
+
+    @classmethod
+    def upload_file_tests_upload_post(cls) -> types.ModuleType:
+        """
+        Upload a file
+        """
+        return upload_file_tests_upload_post
+
+    @classmethod
+    def json_body_tests_json_body_post(cls) -> types.ModuleType:
+        """
+        Try sending a JSON body
+        """
+        return json_body_tests_json_body_post
+
+    @classmethod
+    def defaults_tests_defaults_post(cls) -> types.ModuleType:
+        """
+        Defaults
+        """
+        return defaults_tests_defaults_post
+
+    @classmethod
+    def octet_stream_tests_octet_stream_get(cls) -> types.ModuleType:
+        """
+        Octet Stream
+        """
+        return octet_stream_tests_octet_stream_get
+
+    @classmethod
+    def no_response_tests_no_response_get(cls) -> types.ModuleType:
+        """
+        No Response
+        """
+        return no_response_tests_no_response_get
+
+    @classmethod
+    def unsupported_content_tests_unsupported_content_get(cls) -> types.ModuleType:
+        """
+        Unsupported Content
+        """
+        return unsupported_content_tests_unsupported_content_get
+
+    @classmethod
+    def int_enum_tests_int_enum_post(cls) -> types.ModuleType:
+        """
+        Int Enum
+        """
+        return int_enum_tests_int_enum_post
+
+    @classmethod
+    def test_inline_objects(cls) -> types.ModuleType:
+        """
+        Test Inline Objects
+        """
+        return test_inline_objects
+
+    @classmethod
+    def optional_value_tests_optional_query_param(cls) -> types.ModuleType:
+        """
+        Test optional query parameters
+        """
+        return optional_value_tests_optional_query_param
+
+    @classmethod
+    def token_with_cookie_auth_token_with_cookie_get(cls) -> types.ModuleType:
+        """
+        Test optional cookie parameters
+        """
+        return token_with_cookie_auth_token_with_cookie_get
diff --git a/end_to_end_tests/test_custom_templates/api_init.py.jinja b/end_to_end_tests/test_custom_templates/api_init.py.jinja
new file mode 100644
index 000000000..06163930d
--- /dev/null
+++ b/end_to_end_tests/test_custom_templates/api_init.py.jinja
@@ -0,0 +1,19 @@
+""" Contains methods for accessing the API """
+
+{% macro snake_to_pascal_case(name) %}
+{%- for part in name.split('_') -%}
+{{ part | capitalize }}
+{%- endfor -%}
+{% endmacro %}
+
+from typing import Type
+{% for tag, collection in endpoint_collections_by_tag %}
+from {{ package_name }}.api.{{ tag }} import {{ snake_to_pascal_case(tag) }}Endpoints
+{% endfor %}
+
+class {{ snake_to_pascal_case(package_name) }}Api:
+{% for tag, collection in endpoint_collections_by_tag %}
+    @classmethod
+    def {{ tag }}(cls) -> Type[{{ snake_to_pascal_case(tag) }}Endpoints]:
+        return {{ snake_to_pascal_case(tag) }}Endpoints
+{% endfor %}
diff --git a/end_to_end_tests/test_custom_templates/endpoint_init.py.jinja b/end_to_end_tests/test_custom_templates/endpoint_init.py.jinja
new file mode 100644
index 000000000..20612ba43
--- /dev/null
+++ b/end_to_end_tests/test_custom_templates/endpoint_init.py.jinja
@@ -0,0 +1,37 @@
+""" Contains methods for accessing the API Endpoints """
+
+{% macro snake_to_pascal_case(name) %}
+{%- for part in name.split('_') -%}
+{{ part | capitalize }}
+{%- endfor -%}
+{% endmacro %}
+
+{% macro to_snake_name(name) %}
+{% set last_snakize = {'at': 0} %}
+{%- for part in name -%}
+{{ '_' if not loop.first and part in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' and (loop.index - last_snakize.at) > 1 and not last_snakize.update({'at': loop.index}) }}{{ part | lower }}
+{%- endfor -%}
+{% endmacro %}
+
+import types
+{% for endpoint in endpoints %}
+from {{ package_name }}.api.{{ tag }} import {{ to_snake_name(endpoint.name) }}
+{% endfor %}
+
+class {{ snake_to_pascal_case(tag) }}Endpoints:
+
+{% for endpoint in endpoints %}
+
+    @classmethod
+    def {{ to_snake_name(endpoint.name) }}(cls) -> types.ModuleType:
+        {% if endpoint.description %}
+        """
+            {{ endpoint.description }}
+        """
+        {% elif endpoint.summary %}
+        """
+            {{ endpoint.summary  }}
+        """
+        {% endif %}
+        return {{ to_snake_name(endpoint.name) }}
+{% endfor %}
diff --git a/end_to_end_tests/test_end_to_end.py b/end_to_end_tests/test_end_to_end.py
index fa4d21598..ad552092a 100644
--- a/end_to_end_tests/test_end_to_end.py
+++ b/end_to_end_tests/test_end_to_end.py
@@ -12,7 +12,10 @@
 def _compare_directories(
     record: Path,
     test_subject: Path,
-    expected_differences: Optional[Dict[str, str]] = None,
+    expected_differences: Optional[
+        Dict[str, str]
+    ] = None,  # key: path relative to generated directory, value: expected generated content
+    depth=0,
 ):
     first_printable = record.relative_to(Path.cwd())
     second_printable = test_subject.relative_to(Path.cwd())
@@ -22,27 +25,41 @@ def _compare_directories(
         pytest.fail(f"{first_printable} or {second_printable} was missing: {missing_files}", pytrace=False)
 
     expected_differences = expected_differences or {}
-    _, mismatch, errors = cmpfiles(record, test_subject, dc.common_files, shallow=False)
-    mismatch = set(mismatch)
-
-    for file_name in mismatch | set(expected_differences.keys()):
-        if file_name not in expected_differences:
-            continue
-        if file_name not in mismatch:
-            pytest.fail(f"Expected {file_name} to be different but it was not", pytrace=False)
-        generated = (test_subject / file_name).read_text()
-        assert generated == expected_differences[file_name], f"Unexpected output in {file_name}"
-        del expected_differences[file_name]
-        mismatch.remove(file_name)
-
-    if mismatch:
+    _, mismatches, errors = cmpfiles(record, test_subject, dc.common_files, shallow=False)
+    mismatches = set(mismatches)
+
+    expected_path_mismatches = []
+    for file_name in mismatches:
+
+        mismatch_file_path = test_subject.joinpath(file_name)
+        for expected_differences_path in expected_differences.keys():
+
+            if mismatch_file_path.match(str(expected_differences_path)):
+
+                generated_content = (test_subject / file_name).read_text()
+                expected_content = expected_differences[expected_differences_path]
+                assert generated_content == expected_content, f"Unexpected output in {mismatch_file_path}"
+                expected_path_mismatches.append(expected_differences_path)
+
+    for path_mismatch in expected_path_mismatches:
+        matched_file_name = path_mismatch.name
+        mismatches.remove(matched_file_name)
+        del expected_differences[path_mismatch]
+
+    if mismatches:
         pytest.fail(
-            f"{first_printable} and {second_printable} had differing files: {mismatch}, and errors {errors}",
+            f"{first_printable} and {second_printable} had differing files: {mismatches}, and errors {errors}",
             pytrace=False,
         )
 
     for sub_path in dc.common_dirs:
-        _compare_directories(record / sub_path, test_subject / sub_path, expected_differences=expected_differences)
+        _compare_directories(
+            record / sub_path, test_subject / sub_path, expected_differences=expected_differences, depth=depth + 1
+        )
+
+    if depth == 0 and len(expected_differences.keys()) > 0:
+        failure = "\n".join([f"Expected {path} to be different but it was not" for path in expected_differences.keys()])
+        pytest.fail(failure, pytrace=False)
 
 
 def run_e2e_test(extra_args=None, expected_differences=None):
@@ -60,6 +77,7 @@ def run_e2e_test(extra_args=None, expected_differences=None):
 
     if result.exit_code != 0:
         raise result.exception
+
     _compare_directories(gr_path, output_path, expected_differences=expected_differences)
 
     import mypy.api
@@ -75,7 +93,21 @@ def test_end_to_end():
 
 
 def test_custom_templates():
+    expected_differences = {}  # key: path relative to generated directory, value: expected generated content
+    expected_difference_paths = [
+        Path("README.md"),
+        Path("my_test_api_client").joinpath("api", "__init__.py"),
+        Path("my_test_api_client").joinpath("api", "tests", "__init__.py"),
+        Path("my_test_api_client").joinpath("api", "default", "__init__.py"),
+        Path("my_test_api_client").joinpath("api", "parameters", "__init__.py"),
+    ]
+
+    golden_tpls_root_dir = Path(__file__).parent.joinpath("custom-templates-golden-record")
+    for expected_difference_path in expected_difference_paths:
+        path = Path("my-test-api-client").joinpath(expected_difference_path)
+        expected_differences[path] = (golden_tpls_root_dir / expected_difference_path).read_text()
+
     run_e2e_test(
-        extra_args=["--custom-template-path=end_to_end_tests/test_custom_templates"],
-        expected_differences={"README.md": "my-test-api-client"},
+        extra_args=["--custom-template-path=end_to_end_tests/test_custom_templates/"],
+        expected_differences=expected_differences,
     )

From 842950da567f2440f1ef593953b667820f035c25 Mon Sep 17 00:00:00 2001
From: Nementon <nementon@badwolf.fr>
Date: Thu, 6 May 2021 08:52:13 +0200
Subject: [PATCH 03/11] expose utils module to jinja2

---
 openapi_python_client/__init__.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/openapi_python_client/__init__.py b/openapi_python_client/__init__.py
index 35018d1cd..43f813483 100644
--- a/openapi_python_client/__init__.py
+++ b/openapi_python_client/__init__.py
@@ -67,6 +67,7 @@ def __init__(
         else:
             loader = package_loader
         self.env: Environment = Environment(loader=loader, trim_blocks=True, lstrip_blocks=True)
+        self.env.globals.update(utils=utils)
 
         self.project_name: str = config.project_name_override or f"{utils.kebab_case(openapi.title).lower()}-client"
         self.project_dir: Path = Path.cwd()

From 6c05320e70df9481f0fc42069d3f664913dcb034 Mon Sep 17 00:00:00 2001
From: Nementon <nementon@badwolf.fr>
Date: Thu, 6 May 2021 08:54:52 +0200
Subject: [PATCH 04/11] test_custom_templates / replace jinja2 marcro with call
 to utils module

---
 .../test_custom_templates/api_init.py.jinja   | 14 ++++---------
 .../endpoint_init.py.jinja                    | 21 ++++---------------
 2 files changed, 8 insertions(+), 27 deletions(-)

diff --git a/end_to_end_tests/test_custom_templates/api_init.py.jinja b/end_to_end_tests/test_custom_templates/api_init.py.jinja
index 06163930d..3655db659 100644
--- a/end_to_end_tests/test_custom_templates/api_init.py.jinja
+++ b/end_to_end_tests/test_custom_templates/api_init.py.jinja
@@ -1,19 +1,13 @@
 """ Contains methods for accessing the API """
 
-{% macro snake_to_pascal_case(name) %}
-{%- for part in name.split('_') -%}
-{{ part | capitalize }}
-{%- endfor -%}
-{% endmacro %}
-
 from typing import Type
 {% for tag, collection in endpoint_collections_by_tag %}
-from {{ package_name }}.api.{{ tag }} import {{ snake_to_pascal_case(tag) }}Endpoints
+from {{ package_name }}.api.{{ tag }} import {{ utils.pascal_case(tag) }}Endpoints
 {% endfor %}
 
-class {{ snake_to_pascal_case(package_name) }}Api:
+class {{ utils.pascal_case(package_name) }}Api:
 {% for tag, collection in endpoint_collections_by_tag %}
     @classmethod
-    def {{ tag }}(cls) -> Type[{{ snake_to_pascal_case(tag) }}Endpoints]:
-        return {{ snake_to_pascal_case(tag) }}Endpoints
+    def {{ tag }}(cls) -> Type[{{ utils.pascal_case(tag) }}Endpoints]:
+        return {{ utils.pascal_case(tag) }}Endpoints
 {% endfor %}
diff --git a/end_to_end_tests/test_custom_templates/endpoint_init.py.jinja b/end_to_end_tests/test_custom_templates/endpoint_init.py.jinja
index 20612ba43..fbbd40e4f 100644
--- a/end_to_end_tests/test_custom_templates/endpoint_init.py.jinja
+++ b/end_to_end_tests/test_custom_templates/endpoint_init.py.jinja
@@ -1,29 +1,16 @@
 """ Contains methods for accessing the API Endpoints """
 
-{% macro snake_to_pascal_case(name) %}
-{%- for part in name.split('_') -%}
-{{ part | capitalize }}
-{%- endfor -%}
-{% endmacro %}
-
-{% macro to_snake_name(name) %}
-{% set last_snakize = {'at': 0} %}
-{%- for part in name -%}
-{{ '_' if not loop.first and part in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' and (loop.index - last_snakize.at) > 1 and not last_snakize.update({'at': loop.index}) }}{{ part | lower }}
-{%- endfor -%}
-{% endmacro %}
-
 import types
 {% for endpoint in endpoints %}
-from {{ package_name }}.api.{{ tag }} import {{ to_snake_name(endpoint.name) }}
+from {{ package_name }}.api.{{ tag }} import {{ utils.snake_case(endpoint.name) }}
 {% endfor %}
 
-class {{ snake_to_pascal_case(tag) }}Endpoints:
+class {{ utils.pascal_case(tag) }}Endpoints:
 
 {% for endpoint in endpoints %}
 
     @classmethod
-    def {{ to_snake_name(endpoint.name) }}(cls) -> types.ModuleType:
+    def {{ utils.snake_case(endpoint.name) }}(cls) -> types.ModuleType:
         {% if endpoint.description %}
         """
             {{ endpoint.description }}
@@ -33,5 +20,5 @@ class {{ snake_to_pascal_case(tag) }}Endpoints:
             {{ endpoint.summary  }}
         """
         {% endif %}
-        return {{ to_snake_name(endpoint.name) }}
+        return {{ utils.snake_case(endpoint.name) }}
 {% endfor %}

From 89d464d9f10e65d89642013d1fd1391405b43ce5 Mon Sep 17 00:00:00 2001
From: p1-ra <aurelien.roose@p1sec.com>
Date: Mon, 3 May 2021 16:24:49 +0200
Subject: [PATCH 05/11] task / regen / regen custom template golden record

---
 .../my_test_api_client/api/__init__.py        |  6 +-
 .../api/default/__init__.py                   |  1 -
 .../api/parameters/__init__.py                | 11 ++++
 .../my_test_api_client/api/tests/__init__.py  |  1 -
 end_to_end_tests/regen_golden_record.py       | 55 ++++++++++++++++++-
 5 files changed, 70 insertions(+), 4 deletions(-)
 create mode 100644 end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/parameters/__init__.py

diff --git a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/__init__.py b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/__init__.py
index 359d0b2dc..3ee5dbaf0 100644
--- a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/__init__.py
+++ b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/__init__.py
@@ -1,9 +1,9 @@
 """ Contains methods for accessing the API """
 
-
 from typing import Type
 
 from my_test_api_client.api.default import DefaultEndpoints
+from my_test_api_client.api.parameters import ParametersEndpoints
 from my_test_api_client.api.tests import TestsEndpoints
 
 
@@ -15,3 +15,7 @@ def tests(cls) -> Type[TestsEndpoints]:
     @classmethod
     def default(cls) -> Type[DefaultEndpoints]:
         return DefaultEndpoints
+
+    @classmethod
+    def parameters(cls) -> Type[ParametersEndpoints]:
+        return ParametersEndpoints
diff --git a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/default/__init__.py b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/default/__init__.py
index 5928666d9..4d0eb4fb5 100644
--- a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/default/__init__.py
+++ b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/default/__init__.py
@@ -1,6 +1,5 @@
 """ Contains methods for accessing the API Endpoints """
 
-
 import types
 
 from my_test_api_client.api.default import get_common_parameters, post_common_parameters
diff --git a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/parameters/__init__.py b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/parameters/__init__.py
new file mode 100644
index 000000000..b92c6d96b
--- /dev/null
+++ b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/parameters/__init__.py
@@ -0,0 +1,11 @@
+""" Contains methods for accessing the API Endpoints """
+
+import types
+
+from my_test_api_client.api.parameters import get_same_name_multiple_locations_param
+
+
+class ParametersEndpoints:
+    @classmethod
+    def get_same_name_multiple_locations_param(cls) -> types.ModuleType:
+        return get_same_name_multiple_locations_param
diff --git a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/tests/__init__.py b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/tests/__init__.py
index b92538c28..2cfd13809 100644
--- a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/tests/__init__.py
+++ b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/tests/__init__.py
@@ -1,6 +1,5 @@
 """ Contains methods for accessing the API Endpoints """
 
-
 import types
 
 from my_test_api_client.api.tests import (
diff --git a/end_to_end_tests/regen_golden_record.py b/end_to_end_tests/regen_golden_record.py
index aaa6aa850..1d4dc943d 100644
--- a/end_to_end_tests/regen_golden_record.py
+++ b/end_to_end_tests/regen_golden_record.py
@@ -1,12 +1,16 @@
 """ Regenerate golden-record """
+import filecmp
+import os
 import shutil
+import tempfile
 from pathlib import Path
 
 from typer.testing import CliRunner
 
 from openapi_python_client.cli import app
 
-if __name__ == "__main__":
+
+def regen_golden_record():
     runner = CliRunner()
     openapi_path = Path(__file__).parent / "openapi.json"
 
@@ -24,3 +28,52 @@
     if result.exception:
         raise result.exception
     output_path.rename(gr_path)
+
+
+def regen_custom_template_golden_record():
+    runner = CliRunner()
+    openapi_path = Path(__file__).parent / "openapi.json"
+    tpl_dir = Path(__file__).parent / "test_custom_templates"
+
+    gr_path = Path(__file__).parent / "golden-record"
+    tpl_gr_path = Path(__file__).parent / "custom-templates-golden-record"
+
+    output_path = Path(tempfile.mkdtemp())
+    config_path = Path(__file__).parent / "config.yml"
+
+    shutil.rmtree(tpl_gr_path, ignore_errors=True)
+
+    os.chdir(str(output_path.absolute()))
+    result = runner.invoke(
+        app, ["generate", f"--config={config_path}", f"--path={openapi_path}", f"--custom-template-path={tpl_dir}"]
+    )
+
+    if result.stdout:
+        generated_output_path = output_path / "my-test-api-client"
+        for f in generated_output_path.glob("**/*"):  # nb: works for Windows and Unix
+            relative_to_generated = f.relative_to(generated_output_path)
+            gr_file = gr_path / relative_to_generated
+            if not gr_file.exists():
+                print(f"{gr_file} does not exist, ignoring")
+                continue
+
+            if not gr_file.is_file():
+                continue
+
+            if not filecmp.cmp(gr_file, f, shallow=False):
+                target_file = tpl_gr_path / relative_to_generated
+                target_dir = target_file.parent
+
+                target_dir.mkdir(parents=True, exist_ok=True)
+                shutil.copy(f"{f}", f"{target_file}")
+
+        shutil.rmtree(output_path, ignore_errors=True)
+
+    if result.exception:
+        shutil.rmtree(output_path, ignore_errors=True)
+        raise result.exception
+
+
+if __name__ == "__main__":
+    regen_golden_record()
+    regen_custom_template_golden_record()

From 94bf2c446efdce8b86d3aaacf0748aee2b2bd998 Mon Sep 17 00:00:00 2001
From: p1-ra <aurelien.roose@p1sec.com>
Date: Thu, 20 May 2021 12:10:10 +0200
Subject: [PATCH 06/11] test_end_to_end / correct typing

---
 end_to_end_tests/test_end_to_end.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/end_to_end_tests/test_end_to_end.py b/end_to_end_tests/test_end_to_end.py
index ad552092a..6bca500ab 100644
--- a/end_to_end_tests/test_end_to_end.py
+++ b/end_to_end_tests/test_end_to_end.py
@@ -13,7 +13,7 @@ def _compare_directories(
     record: Path,
     test_subject: Path,
     expected_differences: Optional[
-        Dict[str, str]
+        Dict[Path, str]
     ] = None,  # key: path relative to generated directory, value: expected generated content
     depth=0,
 ):

From cbeaefc457c096bd137c8537fab556d88666d112 Mon Sep 17 00:00:00 2001
From: p1-ra <aurelien.roose@p1sec.com>
Date: Thu, 20 May 2021 12:14:48 +0200
Subject: [PATCH 07/11] endoint __init__ template / factorize propagated vars

---
 end_to_end_tests/test_custom_templates/api_init.py.jinja  | 4 ++--
 .../test_custom_templates/endpoint_init.py.jinja          | 8 ++++----
 openapi_python_client/__init__.py                         | 6 +++---
 3 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/end_to_end_tests/test_custom_templates/api_init.py.jinja b/end_to_end_tests/test_custom_templates/api_init.py.jinja
index 3655db659..03c2a2f6f 100644
--- a/end_to_end_tests/test_custom_templates/api_init.py.jinja
+++ b/end_to_end_tests/test_custom_templates/api_init.py.jinja
@@ -1,12 +1,12 @@
 """ Contains methods for accessing the API """
 
 from typing import Type
-{% for tag, collection in endpoint_collections_by_tag %}
+{% for tag in endpoint_collections_by_tag.keys() %}
 from {{ package_name }}.api.{{ tag }} import {{ utils.pascal_case(tag) }}Endpoints
 {% endfor %}
 
 class {{ utils.pascal_case(package_name) }}Api:
-{% for tag, collection in endpoint_collections_by_tag %}
+{% for tag in endpoint_collections_by_tag.keys() %}
     @classmethod
     def {{ tag }}(cls) -> Type[{{ utils.pascal_case(tag) }}Endpoints]:
         return {{ utils.pascal_case(tag) }}Endpoints
diff --git a/end_to_end_tests/test_custom_templates/endpoint_init.py.jinja b/end_to_end_tests/test_custom_templates/endpoint_init.py.jinja
index fbbd40e4f..57e8ba124 100644
--- a/end_to_end_tests/test_custom_templates/endpoint_init.py.jinja
+++ b/end_to_end_tests/test_custom_templates/endpoint_init.py.jinja
@@ -1,13 +1,13 @@
 """ Contains methods for accessing the API Endpoints """
 
 import types
-{% for endpoint in endpoints %}
-from {{ package_name }}.api.{{ tag }} import {{ utils.snake_case(endpoint.name) }}
+{% for endpoint in endpoint_collection.endpoints %}
+from {{ package_name }}.api.{{ endpoint_collection.tag }} import {{ utils.snake_case(endpoint.name) }}
 {% endfor %}
 
-class {{ utils.pascal_case(tag) }}Endpoints:
+class {{ utils.pascal_case(endpoint_collection.tag) }}Endpoints:
 
-{% for endpoint in endpoints %}
+{% for endpoint in endpoint_collection.endpoints %}
 
     @classmethod
     def {{ utils.snake_case(endpoint.name) }}(cls) -> types.ModuleType:
diff --git a/openapi_python_client/__init__.py b/openapi_python_client/__init__.py
index 43f813483..42e60e35d 100644
--- a/openapi_python_client/__init__.py
+++ b/openapi_python_client/__init__.py
@@ -240,7 +240,7 @@ def _build_api(self) -> None:
         client_path.write_text(client_template.render(), encoding=self.file_encoding)
 
         # Generate endpoints
-        endpoint_collections_by_tag = self.openapi.endpoint_collections_by_tag.items()
+        endpoint_collections_by_tag = self.openapi.endpoint_collections_by_tag
         api_dir = self.package_dir / "api"
         api_dir.mkdir()
         api_init_path = api_dir / "__init__.py"
@@ -254,14 +254,14 @@ def _build_api(self) -> None:
         )
 
         endpoint_template = self.env.get_template("endpoint_module.py.jinja")
-        for tag, collection in endpoint_collections_by_tag:
+        for tag, collection in endpoint_collections_by_tag.items():
             tag_dir = api_dir / tag
             tag_dir.mkdir()
 
             endpoint_init_path = tag_dir / "__init__.py"
             endpoint_init_template = self.env.get_template("endpoint_init.py.jinja")
             endpoint_init_path.write_text(
-                endpoint_init_template.render(package_name=self.package_name, tag=tag, endpoints=collection.endpoints),
+                endpoint_init_template.render(package_name=self.package_name, endpoint_collection=collection),
                 encoding=self.file_encoding,
             )
             (tag_dir / "__init__.py").touch()

From 31a9fb03c981820df1b7a6932ddfd6e36539faa3 Mon Sep 17 00:00:00 2001
From: p1-ra <aurelien.roose@p1sec.com>
Date: Thu, 20 May 2021 12:18:58 +0200
Subject: [PATCH 08/11] templates / globaly propagate package metadata vars

---
 openapi_python_client/__init__.py             | 35 ++++++++-----------
 .../templates/README.md.jinja                 |  2 +-
 .../templates/package_init.py.jinja           |  2 +-
 .../templates/pyproject.toml.jinja            |  4 +--
 .../templates/setup.py.jinja                  |  4 +--
 5 files changed, 20 insertions(+), 27 deletions(-)

diff --git a/openapi_python_client/__init__.py b/openapi_python_client/__init__.py
index 42e60e35d..1819b69eb 100644
--- a/openapi_python_client/__init__.py
+++ b/openapi_python_client/__init__.py
@@ -67,7 +67,6 @@ def __init__(
         else:
             loader = package_loader
         self.env: Environment = Environment(loader=loader, trim_blocks=True, lstrip_blocks=True)
-        self.env.globals.update(utils=utils)
 
         self.project_name: str = config.project_name_override or f"{utils.kebab_case(openapi.title).lower()}-client"
         self.project_dir: Path = Path.cwd()
@@ -82,6 +81,15 @@ def __init__(
         self.version: str = config.package_version_override or openapi.version
 
         self.env.filters.update(TEMPLATE_FILTERS)
+        self.env.globals.update(
+            utils=utils,
+            package_name=self.package_name,
+            package_dir=self.package_dir,
+            package_description=self.package_description,
+            package_version=self.version,
+            project_name=self.project_name,
+            project_dir=self.project_dir,
+        )
 
     def build(self) -> Sequence[GeneratorError]:
         """Create the project from templates"""
@@ -144,9 +152,7 @@ def _create_package(self) -> None:
         package_init = self.package_dir / "__init__.py"
 
         package_init_template = self.env.get_template("package_init.py.jinja")
-        package_init.write_text(
-            package_init_template.render(description=self.package_description), encoding=self.file_encoding
-        )
+        package_init.write_text(package_init_template.render(), encoding=self.file_encoding)
 
         if self.meta != MetaType.NONE:
             pytyped = self.package_dir / "py.typed"
@@ -168,9 +174,7 @@ def _build_metadata(self) -> None:
         readme = self.project_dir / "README.md"
         readme_template = self.env.get_template("README.md.jinja")
         readme.write_text(
-            readme_template.render(
-                project_name=self.project_name, description=self.package_description, package_name=self.package_name
-            ),
+            readme_template.render(),
             encoding=self.file_encoding,
         )
 
@@ -184,12 +188,7 @@ def _build_pyproject_toml(self, *, use_poetry: bool) -> None:
         pyproject_template = self.env.get_template(template)
         pyproject_path = self.project_dir / "pyproject.toml"
         pyproject_path.write_text(
-            pyproject_template.render(
-                project_name=self.project_name,
-                package_name=self.package_name,
-                version=self.version,
-                description=self.package_description,
-            ),
+            pyproject_template.render(),
             encoding=self.file_encoding,
         )
 
@@ -197,12 +196,7 @@ def _build_setup_py(self) -> None:
         template = self.env.get_template("setup.py.jinja")
         path = self.project_dir / "setup.py"
         path.write_text(
-            template.render(
-                project_name=self.project_name,
-                package_name=self.package_name,
-                version=self.version,
-                description=self.package_description,
-            ),
+            template.render(),
             encoding=self.file_encoding,
         )
 
@@ -247,7 +241,6 @@ def _build_api(self) -> None:
         api_init_template = self.env.get_template("api_init.py.jinja")
         api_init_path.write_text(
             api_init_template.render(
-                package_name=self.package_name,
                 endpoint_collections_by_tag=endpoint_collections_by_tag,
             ),
             encoding=self.file_encoding,
@@ -261,7 +254,7 @@ def _build_api(self) -> None:
             endpoint_init_path = tag_dir / "__init__.py"
             endpoint_init_template = self.env.get_template("endpoint_init.py.jinja")
             endpoint_init_path.write_text(
-                endpoint_init_template.render(package_name=self.package_name, endpoint_collection=collection),
+                endpoint_init_template.render(endpoint_collection=collection),
                 encoding=self.file_encoding,
             )
             (tag_dir / "__init__.py").touch()
diff --git a/openapi_python_client/templates/README.md.jinja b/openapi_python_client/templates/README.md.jinja
index 2a5d18d87..e6de0dda5 100644
--- a/openapi_python_client/templates/README.md.jinja
+++ b/openapi_python_client/templates/README.md.jinja
@@ -1,5 +1,5 @@
 # {{ project_name }}
-{{ description }}
+{{ package_description }}
 
 ## Usage
 First, create a client:
diff --git a/openapi_python_client/templates/package_init.py.jinja b/openapi_python_client/templates/package_init.py.jinja
index 917cd7dde..f146549d0 100644
--- a/openapi_python_client/templates/package_init.py.jinja
+++ b/openapi_python_client/templates/package_init.py.jinja
@@ -1,2 +1,2 @@
-""" {{ description }} """
+""" {{ package_description }} """
 from .client import AuthenticatedClient, Client
diff --git a/openapi_python_client/templates/pyproject.toml.jinja b/openapi_python_client/templates/pyproject.toml.jinja
index 9e311a1a8..695092f48 100644
--- a/openapi_python_client/templates/pyproject.toml.jinja
+++ b/openapi_python_client/templates/pyproject.toml.jinja
@@ -1,7 +1,7 @@
 [tool.poetry]
 name = "{{ project_name }}"
-version = "{{ version }}"
-description = "{{ description }}"
+version = "{{ package_version }}"
+description = "{{ package_description }}"
 
 authors = []
 
diff --git a/openapi_python_client/templates/setup.py.jinja b/openapi_python_client/templates/setup.py.jinja
index 0dd31d23b..027120ab9 100644
--- a/openapi_python_client/templates/setup.py.jinja
+++ b/openapi_python_client/templates/setup.py.jinja
@@ -7,8 +7,8 @@ long_description = (here / "README.md").read_text(encoding="utf-8")
 
 setup(
     name="{{ project_name }}",
-    version="{{ version }}",
-    description="{{ description }}",
+    version="{{ package_version }}",
+    description="{{ package_description }}",
     long_description=long_description,
     long_description_content_type="text/markdown",
     package_dir={"": "{{ package_name }}"},

From 20ce01c7cc34a57f418aa5f2e44e14995834bb61 Mon Sep 17 00:00:00 2001
From: p1-ra <aurelien.roose@p1sec.com>
Date: Thu, 20 May 2021 14:24:19 +0200
Subject: [PATCH 09/11] test___init__.py / correct templates API call breaking
 changes

---
 tests/test___init__.py | 26 ++++----------------------
 1 file changed, 4 insertions(+), 22 deletions(-)

diff --git a/tests/test___init__.py b/tests/test___init__.py
index 0579e83f0..3e1efbd5c 100644
--- a/tests/test___init__.py
+++ b/tests/test___init__.py
@@ -403,11 +403,7 @@ def test__build_metadata_poetry(self, mocker):
         project._build_metadata()
 
         project.env.get_template.assert_has_calls([mocker.call("README.md.jinja"), mocker.call(".gitignore.jinja")])
-        readme_template.render.assert_called_once_with(
-            description=project.package_description,
-            project_name=project.project_name,
-            package_name=project.package_name,
-        )
+        readme_template.render.assert_called_once_with()
         readme_path.write_text.assert_called_once_with(readme_template.render(), encoding="utf-8")
         git_ignore_template.render.assert_called_once()
         git_ignore_path.write_text.assert_called_once_with(git_ignore_template.render(), encoding="utf-8")
@@ -440,11 +436,7 @@ def test__build_metadata_setup(self, mocker):
         project._build_metadata()
 
         project.env.get_template.assert_has_calls([mocker.call("README.md.jinja"), mocker.call(".gitignore.jinja")])
-        readme_template.render.assert_called_once_with(
-            description=project.package_description,
-            project_name=project.project_name,
-            package_name=project.package_name,
-        )
+        readme_template.render.assert_called_once_with()
         readme_path.write_text.assert_called_once_with(readme_template.render(), encoding="utf-8")
         git_ignore_template.render.assert_called_once()
         git_ignore_path.write_text.assert_called_once_with(git_ignore_template.render(), encoding="utf-8")
@@ -483,12 +475,7 @@ def test__build_pyproject_toml(self, mocker, use_poetry):
 
         project.env.get_template.assert_called_once_with(template_path)
 
-        pyproject_template.render.assert_called_once_with(
-            project_name=project.project_name,
-            package_name=project.package_name,
-            version=project.version,
-            description=project.package_description,
-        )
+        pyproject_template.render.assert_called_once_with()
         pyproject_path.write_text.assert_called_once_with(pyproject_template.render(), encoding="utf-8")
 
     def test__build_setup_py(self, mocker):
@@ -511,12 +498,7 @@ def test__build_setup_py(self, mocker):
 
         project.env.get_template.assert_called_once_with("setup.py.jinja")
 
-        setup_template.render.assert_called_once_with(
-            project_name=project.project_name,
-            package_name=project.package_name,
-            version=project.version,
-            description=project.package_description,
-        )
+        setup_template.render.assert_called_once_with()
         setup_path.write_text.assert_called_once_with(setup_template.render(), encoding="utf-8")
 
 

From 560cbba924b87517fae9a7995faf4e96a0ad291c Mon Sep 17 00:00:00 2001
From: p1-ra <18233250+p1-ra@users.noreply.github.com>
Date: Wed, 9 Jun 2021 15:58:47 +0200
Subject: [PATCH 10/11] Update
 test_end_to_end.py,openapi_python_client/__init__.py : various imprv

Co-authored-by: Dylan Anthony <43723790+dbanty@users.noreply.github.com>
---
 end_to_end_tests/test_end_to_end.py | 38 +++++++++++++++++------------
 openapi_python_client/__init__.py   |  1 -
 2 files changed, 22 insertions(+), 17 deletions(-)

diff --git a/end_to_end_tests/test_end_to_end.py b/end_to_end_tests/test_end_to_end.py
index 6bca500ab..bcc8b12e1 100644
--- a/end_to_end_tests/test_end_to_end.py
+++ b/end_to_end_tests/test_end_to_end.py
@@ -1,7 +1,7 @@
 import shutil
 from filecmp import cmpfiles, dircmp
 from pathlib import Path
-from typing import Dict, Optional
+from typing import Dict, List, Optional
 
 import pytest
 from typer.testing import CliRunner
@@ -12,11 +12,18 @@
 def _compare_directories(
     record: Path,
     test_subject: Path,
-    expected_differences: Optional[
-        Dict[Path, str]
-    ] = None,  # key: path relative to generated directory, value: expected generated content
+    expected_differences: Dict[Path, str],
     depth=0,
 ):
+    """
+    Compare two directories and assert that only expected_differences are different
+
+    Args:
+        record: Path to the expected output
+        test_subject: Path to the generated code being checked
+        expected_differences: key: path relative to generated directory, value: expected generated content
+        depth: Used to track recursion
+    """
     first_printable = record.relative_to(Path.cwd())
     second_printable = test_subject.relative_to(Path.cwd())
     dc = dircmp(record, test_subject)
@@ -30,16 +37,14 @@ def _compare_directories(
 
     expected_path_mismatches = []
     for file_name in mismatches:
-
         mismatch_file_path = test_subject.joinpath(file_name)
-        for expected_differences_path in expected_differences.keys():
-
-            if mismatch_file_path.match(str(expected_differences_path)):
+        expected_content = expected_differences.get(mismatch_file_path)
+        if expected_content is None:
+            continue
 
-                generated_content = (test_subject / file_name).read_text()
-                expected_content = expected_differences[expected_differences_path]
-                assert generated_content == expected_content, f"Unexpected output in {mismatch_file_path}"
-                expected_path_mismatches.append(expected_differences_path)
+        generated_content = (test_subject / file_name).read_text()
+        assert generated_content == expected_content, f"Unexpected output in {mismatch_file_path}"
+        expected_path_mismatches.append(mismatch_file_path)
 
     for path_mismatch in expected_path_mismatches:
         matched_file_name = path_mismatch.name
@@ -62,7 +67,7 @@ def _compare_directories(
         pytest.fail(failure, pytrace=False)
 
 
-def run_e2e_test(extra_args=None, expected_differences=None):
+def run_e2e_test(extra_args: List[str], expected_differences: Dict[Path, str]):
     runner = CliRunner()
     openapi_path = Path(__file__).parent / "openapi.json"
     config_path = Path(__file__).parent / "config.yml"
@@ -78,6 +83,8 @@ def run_e2e_test(extra_args=None, expected_differences=None):
     if result.exit_code != 0:
         raise result.exception
 
+    # Use absolute paths for expected differences for easier comparisons
+    expected_differences = {output_path.joinpath(key): value for key, value in expected_differences.items()}
     _compare_directories(gr_path, output_path, expected_differences=expected_differences)
 
     import mypy.api
@@ -89,7 +96,7 @@ def run_e2e_test(extra_args=None, expected_differences=None):
 
 
 def test_end_to_end():
-    run_e2e_test()
+    run_e2e_test([], {})
 
 
 def test_custom_templates():
@@ -104,8 +111,7 @@ def test_custom_templates():
 
     golden_tpls_root_dir = Path(__file__).parent.joinpath("custom-templates-golden-record")
     for expected_difference_path in expected_difference_paths:
-        path = Path("my-test-api-client").joinpath(expected_difference_path)
-        expected_differences[path] = (golden_tpls_root_dir / expected_difference_path).read_text()
+        expected_differences[expected_difference_path] = (golden_tpls_root_dir / expected_difference_path).read_text()
 
     run_e2e_test(
         extra_args=["--custom-template-path=end_to_end_tests/test_custom_templates/"],
diff --git a/openapi_python_client/__init__.py b/openapi_python_client/__init__.py
index 1819b69eb..b1458e1a4 100644
--- a/openapi_python_client/__init__.py
+++ b/openapi_python_client/__init__.py
@@ -257,7 +257,6 @@ def _build_api(self) -> None:
                 endpoint_init_template.render(endpoint_collection=collection),
                 encoding=self.file_encoding,
             )
-            (tag_dir / "__init__.py").touch()
 
             for endpoint in collection.endpoints:
                 module_path = tag_dir / f"{snake_case(endpoint.name)}.py"

From 44537a14e2da8fa0ee9d0e1bab5b857877d9bfea Mon Sep 17 00:00:00 2001
From: p1-ra <aurelien.roose@p1sec.com>
Date: Wed, 9 Jun 2021 16:14:21 +0200
Subject: [PATCH 11/11] regen templates golden records (rebased on main)

---
 .../my_test_api_client/api/tests/__init__.py              | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/tests/__init__.py b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/tests/__init__.py
index 2cfd13809..dcb864fe9 100644
--- a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/tests/__init__.py
+++ b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/tests/__init__.py
@@ -14,6 +14,7 @@
     no_response_tests_no_response_get,
     octet_stream_tests_octet_stream_get,
     optional_value_tests_optional_query_param,
+    post_form_data,
     test_inline_objects,
     token_with_cookie_auth_token_with_cookie_get,
     unsupported_content_tests_unsupported_content_get,
@@ -57,6 +58,13 @@ def get_basic_list_of_booleans(cls) -> types.ModuleType:
         """
         return get_basic_list_of_booleans
 
+    @classmethod
+    def post_form_data(cls) -> types.ModuleType:
+        """
+        Post form data
+        """
+        return post_form_data
+
     @classmethod
     def upload_file_tests_upload_post(cls) -> types.ModuleType:
         """