Skip to content

Commit 1058d07

Browse files
committed
Split project building into multiple functions, added some tests
#3 progress
1 parent b6d90fb commit 1058d07

File tree

2 files changed

+151
-66
lines changed

2 files changed

+151
-66
lines changed

openapi_python_client/__init__.py

Lines changed: 80 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ def main(*, url: Optional[str], path: Optional[str]) -> None:
1313
""" Generate the client library """
1414
data_dict = _get_json(url=url, path=path)
1515
openapi = OpenAPI.from_dict(data_dict)
16-
_build_project(openapi=openapi)
16+
project = _Project(openapi=openapi)
17+
project.build()
1718

1819

1920
def _get_json(*, url: Optional[str], path: Optional[str]) -> Dict[str, Any]:
@@ -30,66 +31,81 @@ def _get_json(*, url: Optional[str], path: Optional[str]) -> Dict[str, Any]:
3031
return json.loads(json_bytes)
3132

3233

33-
def _build_project(*, openapi: OpenAPI) -> None:
34-
env = Environment(loader=PackageLoader(__package__), trim_blocks=True, lstrip_blocks=True)
35-
36-
# Create output directories
37-
project_name = f"{openapi.title.replace(' ', '-').lower()}-client"
38-
print(f"Generating {project_name}")
39-
package_name = f"{openapi.title.replace(' ', '_').lower()}_client"
40-
project_dir = Path.cwd() / project_name
41-
project_dir.mkdir()
42-
package_dir = project_dir / package_name
43-
package_dir.mkdir()
44-
package_init = package_dir / "__init__.py"
45-
package_description = f"A client library for accessing {openapi.title}"
46-
package_init_template = env.get_template("package_init.pyi")
47-
package_init.write_text(package_init_template.render(description=package_description))
48-
49-
# Create a pyproject.toml file
50-
pyproject_template = env.get_template("pyproject.toml")
51-
pyproject_path = project_dir / "pyproject.toml"
52-
pyproject_path.write_text(
53-
pyproject_template.render(project_name=project_name, package_name=package_name, description=package_description)
54-
)
55-
56-
readme = project_dir / "README.md"
57-
readme_template = env.get_template("README.md")
58-
readme.write_text(readme_template.render(description=package_description))
59-
60-
# Generate models
61-
models_dir = package_dir / "models"
62-
models_dir.mkdir()
63-
models_init = models_dir / "__init__.py"
64-
imports = []
65-
66-
model_template = env.get_template("model.pyi")
67-
for schema in openapi.schemas.values():
68-
module_path = models_dir / f"{schema.reference.module_name}.py"
69-
module_path.write_text(model_template.render(schema=schema))
70-
imports.append(import_string_from_reference(schema.reference))
71-
72-
# Generate enums
73-
enum_template = env.get_template("enum.pyi")
74-
for enum in openapi.enums.values():
75-
module_path = models_dir / f"{enum.name}.py"
76-
module_path.write_text(enum_template.render(enum=enum))
77-
imports.append(import_string_from_reference(enum.reference))
78-
79-
models_init_template = env.get_template("models_init.pyi")
80-
models_init.write_text(models_init_template.render(imports=imports))
81-
82-
# Generate Client
83-
client_path = package_dir / "client.py"
84-
client_template = env.get_template("client.pyi")
85-
client_path.write_text(client_template.render())
86-
87-
# Generate endpoints
88-
api_dir = package_dir / "api"
89-
api_dir.mkdir()
90-
api_init = api_dir / "__init__.py"
91-
api_init.write_text('""" Contains all methods for accessing the API """')
92-
endpoint_template = env.get_template("endpoint_module.pyi")
93-
for tag, collection in openapi.endpoint_collections_by_tag.items():
94-
module_path = api_dir / f"{tag}.py"
95-
module_path.write_text(endpoint_template.render(collection=collection))
34+
class _Project:
35+
def __init__(self, *, openapi: OpenAPI):
36+
self.openapi: OpenAPI = openapi
37+
self.env: Environment = Environment(loader=PackageLoader(__package__), trim_blocks=True, lstrip_blocks=True)
38+
39+
self.project_name: str = f"{openapi.title.replace(' ', '-').lower()}-client"
40+
self.project_dir: Path = Path.cwd() / self.project_name
41+
42+
self.package_name: str = self.project_name.replace("-", "_")
43+
self.package_dir: Path = self.project_dir / self.package_name
44+
45+
def build(self):
46+
""" Create the project from templates """
47+
print(f"Generating {self.project_name}")
48+
self.project_dir.mkdir()
49+
self.package_dir.mkdir()
50+
self._build_metadata()
51+
self._build_models()
52+
self._build_api()
53+
54+
def _build_metadata(self):
55+
# Package __init__.py
56+
package_init = self.package_dir / "__init__.py"
57+
package_description = f"A client library for accessing {self.openapi.title}"
58+
package_init_template = self.env.get_template("package_init.pyi")
59+
package_init.write_text(package_init_template.render(description=package_description))
60+
61+
# Create a pyproject.toml file
62+
pyproject_template = self.env.get_template("pyproject.toml")
63+
pyproject_path = self.project_dir / "pyproject.toml"
64+
pyproject_path.write_text(
65+
pyproject_template.render(project_name=self.project_name, package_name=self.package_name,
66+
description=package_description)
67+
)
68+
69+
# README.md
70+
readme = self.project_dir / "README.md"
71+
readme_template = self.env.get_template("README.md")
72+
readme.write_text(readme_template.render(description=package_description))
73+
74+
def _build_models(self):
75+
# Generate models
76+
models_dir = self.package_dir / "models"
77+
models_dir.mkdir()
78+
models_init = models_dir / "__init__.py"
79+
imports = []
80+
81+
model_template = self.env.get_template("model.pyi")
82+
for schema in self.openapi.schemas.values():
83+
module_path = models_dir / f"{schema.reference.module_name}.py"
84+
module_path.write_text(model_template.render(schema=schema))
85+
imports.append(import_string_from_reference(schema.reference))
86+
87+
# Generate enums
88+
enum_template = self.env.get_template("enum.pyi")
89+
for enum in self.openapi.enums.values():
90+
module_path = models_dir / f"{enum.name}.py"
91+
module_path.write_text(enum_template.render(enum=enum))
92+
imports.append(import_string_from_reference(enum.reference))
93+
94+
models_init_template = self.env.get_template("models_init.pyi")
95+
models_init.write_text(models_init_template.render(imports=imports))
96+
97+
def _build_api(self):
98+
# Generate Client
99+
client_path = self.package_dir / "client.py"
100+
client_template = self.env.get_template("client.pyi")
101+
client_path.write_text(client_template.render())
102+
103+
# Generate endpoints
104+
api_dir = self.package_dir / "api"
105+
api_dir.mkdir()
106+
api_init = api_dir / "__init__.py"
107+
api_init.write_text('""" Contains all methods for accessing the API """')
108+
endpoint_template = self.env.get_template("endpoint_module.pyi")
109+
for tag, collection in self.openapi.endpoint_collections_by_tag.items():
110+
module_path = api_dir / f"{tag}.py"
111+
module_path.write_text(endpoint_template.render(collection=collection))

tests/test___init__.py

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ def test_main(mocker):
66
_get_json = mocker.patch("openapi_python_client._get_json", return_value=data_dict)
77
openapi = mocker.MagicMock()
88
from_dict = mocker.patch("openapi_python_client.openapi_parser.OpenAPI.from_dict", return_value=openapi)
9-
_build_project = mocker.patch("openapi_python_client._build_project")
9+
_Project = mocker.patch("openapi_python_client._Project")
1010
url = mocker.MagicMock()
1111
path = mocker.MagicMock()
1212

@@ -16,7 +16,8 @@ def test_main(mocker):
1616

1717
_get_json.assert_called_once_with(url=url, path=path)
1818
from_dict.assert_called_once_with(data_dict)
19-
_build_project.assert_called_once_with(openapi=openapi)
19+
_Project.assert_called_once_with(openapi=openapi)
20+
_Project().build.assert_called_once()
2021

2122

2223
class TestGetJson:
@@ -75,3 +76,71 @@ def test__get_json_path_no_url(self, mocker):
7576
get.assert_not_called()
7677
Path.assert_called_once_with(path)
7778
loads.assert_called_once_with(Path().read_bytes())
79+
80+
81+
class TestProject:
82+
def test___init__(self, mocker):
83+
openapi = mocker.MagicMock(title="My Test API")
84+
85+
from openapi_python_client import _Project
86+
87+
project = _Project(openapi=openapi)
88+
89+
assert project.openapi == openapi
90+
assert project.project_name == "my-test-api-client"
91+
assert project.package_name == "my_test_api_client"
92+
93+
def test_build(self, mocker):
94+
from openapi_python_client import _Project
95+
96+
project = _Project(openapi=mocker.MagicMock(title="My Test API"))
97+
project.project_dir = mocker.MagicMock()
98+
project.package_dir = mocker.MagicMock()
99+
project._build_metadata = mocker.MagicMock()
100+
project._build_models = mocker.MagicMock()
101+
project._build_api = mocker.MagicMock()
102+
103+
project.build()
104+
105+
project.project_dir.mkdir.assert_called_once()
106+
project.package_dir.mkdir.assert_called_once()
107+
project._build_metadata.assert_called_once()
108+
project._build_models.assert_called_once()
109+
project._build_api.assert_called_once()
110+
111+
def test__build_metadata(self, mocker):
112+
from openapi_python_client import _Project
113+
114+
project = _Project(openapi=mocker.MagicMock(title="My Test API"))
115+
project.project_dir = mocker.MagicMock()
116+
pyproject_path = mocker.MagicMock()
117+
readme_path = mocker.MagicMock()
118+
project.project_dir.__truediv__.side_effect = [pyproject_path, readme_path]
119+
project.package_dir = mocker.MagicMock()
120+
package_init_template = mocker.MagicMock()
121+
pyproject_template = mocker.MagicMock()
122+
readme_template = mocker.MagicMock()
123+
project.env = mocker.MagicMock()
124+
project.env.get_template.side_effect = [package_init_template, pyproject_template, readme_template]
125+
126+
project._build_metadata()
127+
128+
project.env.get_template.assert_has_calls(
129+
[mocker.call("package_init.pyi"), mocker.call("pyproject.toml"), mocker.call("README.md"),]
130+
)
131+
description = f"A client library for accessing {project.openapi.title}"
132+
package_init = project.package_dir / "__init__.py"
133+
package_init_template.render.assert_called_once_with(description=description)
134+
package_init.write_text.assert_called_once_with(package_init_template.render())
135+
pyproject_template.render.assert_called_once_with(
136+
project_name=project.project_name, package_name=project.package_name, description=description
137+
)
138+
pyproject_path.write_text.assert_called_once_with(pyproject_template.render())
139+
readme_template.render.assert_called_once_with(description=description)
140+
readme_path.write_text.assert_called_once_with(readme_template.render())
141+
142+
def test__build_models(self):
143+
assert False
144+
145+
def test__build_api(self):
146+
assert False

0 commit comments

Comments
 (0)