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
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
@@ -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

73 changes: 52 additions & 21 deletions openapi_python_client/__init__.py
Original file line number Diff line number Diff line change
@@ -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
@@ -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:
@@ -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"
@@ -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:
@@ -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:
@@ -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
@@ -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():
@@ -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()
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
@@ -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 """
@@ -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)


@@ -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
@@ -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
@@ -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


@@ -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
@@ -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]

@@ -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
@@ -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]

@@ -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):
@@ -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()

@@ -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
@@ -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):
Loading