Skip to content

feat: Add new client parameters: encoding #330

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Feb 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add handling of application/vnd.api+json media type.
- Support passing models into query parameters (#316). Thanks @forest-benchling!
- Add support for cookie parameters (#326).
- New `--file-encoding` command line option (#330). Sets the encoding used when writing generated files (defaults to utf-8). Thanks @dongfangtianyu!

### Changes

Expand Down
73 changes: 52 additions & 21 deletions openapi_python_client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,17 @@ class Project:
package_name_override: Optional[str] = None
package_version_override: Optional[str] = None

def __init__(self, *, openapi: GeneratorData, meta: MetaType, custom_template_path: Optional[Path] = None) -> None:
def __init__(
self,
*,
openapi: GeneratorData,
meta: MetaType,
custom_template_path: Optional[Path] = None,
file_encoding: str = "utf-8",
) -> None:
self.openapi: GeneratorData = openapi
self.meta: MetaType = meta
self.file_encoding = file_encoding

package_loader = PackageLoader(__package__)
loader: BaseLoader
Expand Down Expand Up @@ -137,15 +145,17 @@ 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))
package_init.write_text(
package_init_template.render(description=self.package_description), encoding=self.file_encoding
)

if self.meta != MetaType.NONE:
pytyped = self.package_dir / "py.typed"
pytyped.write_text("# Marker file for PEP 561")
pytyped.write_text("# Marker file for PEP 561", encoding=self.file_encoding)

types_template = self.env.get_template("types.py.jinja")
types_path = self.package_dir / "types.py"
types_path.write_text(types_template.render())
types_path.write_text(types_template.render(), encoding=self.file_encoding)

def _build_metadata(self) -> None:
if self.meta == MetaType.NONE:
Expand All @@ -161,13 +171,14 @@ def _build_metadata(self) -> None:
readme.write_text(
readme_template.render(
project_name=self.project_name, description=self.package_description, package_name=self.package_name
)
),
encoding=self.file_encoding,
)

# .gitignore
git_ignore_path = self.project_dir / ".gitignore"
git_ignore_template = self.env.get_template(".gitignore.jinja")
git_ignore_path.write_text(git_ignore_template.render())
git_ignore_path.write_text(git_ignore_template.render(), encoding=self.file_encoding)

def _build_pyproject_toml(self, *, use_poetry: bool) -> None:
template = "pyproject.toml.jinja" if use_poetry else "pyproject_no_poetry.toml.jinja"
Expand All @@ -179,7 +190,8 @@ def _build_pyproject_toml(self, *, use_poetry: bool) -> None:
package_name=self.package_name,
version=self.version,
description=self.package_description,
)
),
encoding=self.file_encoding,
)

def _build_setup_py(self) -> None:
Expand All @@ -191,7 +203,8 @@ def _build_setup_py(self) -> None:
package_name=self.package_name,
version=self.version,
description=self.package_description,
)
),
encoding=self.file_encoding,
)

def _build_models(self) -> None:
Expand All @@ -204,7 +217,7 @@ def _build_models(self) -> None:
model_template = self.env.get_template("model.py.jinja")
for model in self.openapi.models.values():
module_path = models_dir / f"{model.reference.module_name}.py"
module_path.write_text(model_template.render(model=model))
module_path.write_text(model_template.render(model=model), encoding=self.file_encoding)
imports.append(import_string_from_reference(model.reference))

# Generate enums
Expand All @@ -213,25 +226,25 @@ def _build_models(self) -> None:
for enum in self.openapi.enums.values():
module_path = models_dir / f"{enum.reference.module_name}.py"
if enum.value_type is int:
module_path.write_text(int_enum_template.render(enum=enum))
module_path.write_text(int_enum_template.render(enum=enum), encoding=self.file_encoding)
else:
module_path.write_text(str_enum_template.render(enum=enum))
module_path.write_text(str_enum_template.render(enum=enum), encoding=self.file_encoding)
imports.append(import_string_from_reference(enum.reference))

models_init_template = self.env.get_template("models_init.py.jinja")
models_init.write_text(models_init_template.render(imports=imports))
models_init.write_text(models_init_template.render(imports=imports), encoding=self.file_encoding)

def _build_api(self) -> None:
# Generate Client
client_path = self.package_dir / "client.py"
client_template = self.env.get_template("client.py.jinja")
client_path.write_text(client_template.render())
client_path.write_text(client_template.render(), encoding=self.file_encoding)

# Generate endpoints
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 """')
api_init.write_text('""" Contains methods for accessing the API """', 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():
Expand All @@ -241,46 +254,64 @@ def _build_api(self) -> None:

for endpoint in collection.endpoints:
module_path = tag_dir / f"{snake_case(endpoint.name)}.py"
module_path.write_text(endpoint_template.render(endpoint=endpoint))
module_path.write_text(endpoint_template.render(endpoint=endpoint), encoding=self.file_encoding)


def _get_project_for_url_or_path(
url: Optional[str], path: Optional[Path], meta: MetaType, custom_template_path: Optional[Path] = None
url: Optional[str],
path: Optional[Path],
meta: MetaType,
custom_template_path: Optional[Path] = None,
file_encoding: str = "utf-8",
) -> Union[Project, GeneratorError]:
data_dict = _get_document(url=url, path=path)
if isinstance(data_dict, GeneratorError):
return data_dict
openapi = GeneratorData.from_dict(data_dict)
if isinstance(openapi, GeneratorError):
return openapi
return Project(openapi=openapi, custom_template_path=custom_template_path, meta=meta)
return Project(openapi=openapi, custom_template_path=custom_template_path, meta=meta, file_encoding=file_encoding)


def create_new_client(
*, url: Optional[str], path: Optional[Path], meta: MetaType, custom_template_path: Optional[Path] = None
*,
url: Optional[str],
path: Optional[Path],
meta: MetaType,
custom_template_path: Optional[Path] = None,
file_encoding: str = "utf-8",
) -> Sequence[GeneratorError]:
"""
Generate the client library

Returns:
A list containing any errors encountered when generating.
"""
project = _get_project_for_url_or_path(url=url, path=path, custom_template_path=custom_template_path, meta=meta)
project = _get_project_for_url_or_path(
url=url, path=path, custom_template_path=custom_template_path, meta=meta, file_encoding=file_encoding
)
if isinstance(project, GeneratorError):
return [project]
return project.build()


def update_existing_client(
*, url: Optional[str], path: Optional[Path], meta: MetaType, custom_template_path: Optional[Path] = None
*,
url: Optional[str],
path: Optional[Path],
meta: MetaType,
custom_template_path: Optional[Path] = None,
file_encoding: str = "utf-8",
) -> Sequence[GeneratorError]:
"""
Update an existing client library

Returns:
A list containing any errors encountered when generating.
"""
project = _get_project_for_url_or_path(url=url, path=path, custom_template_path=custom_template_path, meta=meta)
project = _get_project_for_url_or_path(
url=url, path=path, custom_template_path=custom_template_path, meta=meta, file_encoding=file_encoding
)
if isinstance(project, GeneratorError):
return [project]
return project.update()
Expand Down
24 changes: 22 additions & 2 deletions openapi_python_client/cli.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import codecs
import pathlib
from pprint import pformat
from typing import Optional, Sequence
Expand Down Expand Up @@ -116,6 +117,7 @@ def generate(
url: Optional[str] = typer.Option(None, help="A URL to read the JSON from"),
path: Optional[pathlib.Path] = typer.Option(None, help="A path to the JSON file"),
custom_template_path: Optional[pathlib.Path] = typer.Option(None, **custom_template_path_options), # type: ignore
file_encoding: str = typer.Option("utf-8", help="Encoding used when writing generated"),
meta: MetaType = _meta_option,
) -> None:
""" Generate a new OpenAPI Client library """
Expand All @@ -127,7 +129,16 @@ def generate(
if url and path:
typer.secho("Provide either --url or --path, not both", fg=typer.colors.RED)
raise typer.Exit(code=1)
errors = create_new_client(url=url, path=path, meta=meta, custom_template_path=custom_template_path)

try:
codecs.getencoder(file_encoding)
except LookupError:
typer.secho("Unknown encoding : {}".format(file_encoding), fg=typer.colors.RED)
raise typer.Exit(code=1)

errors = create_new_client(
url=url, path=path, meta=meta, custom_template_path=custom_template_path, file_encoding=file_encoding
)
handle_errors(errors)


Expand All @@ -137,6 +148,7 @@ def update(
path: Optional[pathlib.Path] = typer.Option(None, help="A path to the JSON file"),
custom_template_path: Optional[pathlib.Path] = typer.Option(None, **custom_template_path_options), # type: ignore
meta: MetaType = _meta_option,
file_encoding: str = typer.Option("utf-8", help="Encoding used when writing generated"),
) -> None:
""" Update an existing OpenAPI Client library """
from . import update_existing_client
Expand All @@ -148,5 +160,13 @@ def update(
typer.secho("Provide either --url or --path, not both", fg=typer.colors.RED)
raise typer.Exit(code=1)

errors = update_existing_client(url=url, path=path, meta=meta, custom_template_path=custom_template_path)
try:
codecs.getencoder(file_encoding)
except LookupError:
typer.secho("Unknown encoding : {}".format(file_encoding), fg=typer.colors.RED)
raise typer.Exit(code=1)

errors = update_existing_client(
url=url, path=path, meta=meta, custom_template_path=custom_template_path, file_encoding=file_encoding
)
handle_errors(errors)
24 changes: 13 additions & 11 deletions tests/test___init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ def test__get_project_for_url_or_path(mocker):

_get_document.assert_called_once_with(url=url, path=path)
from_dict.assert_called_once_with(data_dict)
_Project.assert_called_once_with(openapi=openapi, custom_template_path=None, meta=MetaType.POETRY)
_Project.assert_called_once_with(
openapi=openapi, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8"
)
assert project == _Project.return_value


Expand Down Expand Up @@ -76,7 +78,7 @@ def test_create_new_client(mocker):
result = create_new_client(url=url, path=path, meta=MetaType.POETRY)

_get_project_for_url_or_path.assert_called_once_with(
url=url, path=path, custom_template_path=None, meta=MetaType.POETRY
url=url, path=path, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8"
)
project.build.assert_called_once()
assert result == project.build.return_value
Expand All @@ -95,7 +97,7 @@ def test_create_new_client_project_error(mocker):
result = create_new_client(url=url, path=path, meta=MetaType.POETRY)

_get_project_for_url_or_path.assert_called_once_with(
url=url, path=path, custom_template_path=None, meta=MetaType.POETRY
url=url, path=path, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8"
)
assert result == [error]

Expand All @@ -113,7 +115,7 @@ def test_update_existing_client(mocker):
result = update_existing_client(url=url, path=path, meta=MetaType.POETRY)

_get_project_for_url_or_path.assert_called_once_with(
url=url, path=path, custom_template_path=None, meta=MetaType.POETRY
url=url, path=path, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8"
)
project.update.assert_called_once()
assert result == project.update.return_value
Expand All @@ -132,7 +134,7 @@ def test_update_existing_client_project_error(mocker):
result = update_existing_client(url=url, path=path, meta=MetaType.POETRY)

_get_project_for_url_or_path.assert_called_once_with(
url=url, path=path, custom_template_path=None, meta=MetaType.POETRY
url=url, path=path, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8"
)
assert result == [error]

Expand Down Expand Up @@ -392,9 +394,9 @@ def test__build_metadata_poetry(self, mocker):
project_name=project.project_name,
package_name=project.package_name,
)
readme_path.write_text.assert_called_once_with(readme_template.render())
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())
git_ignore_path.write_text.assert_called_once_with(git_ignore_template.render(), encoding="utf-8")
project._build_pyproject_toml.assert_called_once_with(use_poetry=True)

def test__build_metadata_setup(self, mocker):
Expand Down Expand Up @@ -429,9 +431,9 @@ def test__build_metadata_setup(self, mocker):
project_name=project.project_name,
package_name=project.package_name,
)
readme_path.write_text.assert_called_once_with(readme_template.render())
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())
git_ignore_path.write_text.assert_called_once_with(git_ignore_template.render(), encoding="utf-8")
project._build_pyproject_toml.assert_called_once_with(use_poetry=False)
project._build_setup_py.assert_called_once()

Expand Down Expand Up @@ -475,7 +477,7 @@ def test__build_pyproject_toml(self, mocker, use_poetry):
version=project.version,
description=project.package_description,
)
pyproject_path.write_text.assert_called_once_with(pyproject_template.render())
pyproject_path.write_text.assert_called_once_with(pyproject_template.render(), encoding="utf-8")

def test__build_setup_py(self, mocker):
from openapi_python_client import MetaType, Project
Expand Down Expand Up @@ -505,7 +507,7 @@ def test__build_setup_py(self, mocker):
version=project.version,
description=project.package_description,
)
setup_path.write_text.assert_called_once_with(setup_template.render())
setup_path.write_text.assert_called_once_with(setup_template.render(), encoding="utf-8")


def test__reformat(mocker):
Expand Down
Loading