From f2a495ef499208fd8e4574c2654d0730e08a5286 Mon Sep 17 00:00:00 2001 From: Dylan Anthony Date: Sun, 20 Dec 2020 13:59:28 -0700 Subject: [PATCH] Added ability to generate clients with no metadata or setup.py. Closes #120 --- CHANGELOG.md | 8 + .../golden-record-custom/pyproject.toml | 2 +- end_to_end_tests/golden-record/pyproject.toml | 2 +- openapi_python_client/__init__.py | 88 ++++-- openapi_python_client/cli.py | 12 +- .../templates/pyproject.toml | 2 +- .../templates/pyproject_no_poetry.toml | 17 ++ openapi_python_client/templates/setup.py | 19 ++ poetry.lock | 38 +-- pyproject.toml | 2 + tests/test___init__.py | 253 ++++++++++++++---- tests/test_cli.py | 39 ++- usage.md | 37 +-- 13 files changed, 388 insertions(+), 131 deletions(-) create mode 100644 openapi_python_client/templates/pyproject_no_poetry.toml create mode 100644 openapi_python_client/templates/setup.py diff --git a/CHANGELOG.md b/CHANGELOG.md index be5608af5..1e3c0c129 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased +### Additions +- New `--meta` command line option for specifying what type of metadata should be generated: + - `poetry` is the default value, same behavior you're used to in previous versions + - `setup` will generate a pyproject.toml with no Poetry information, and instead create a `setup.py` with the + project info. + - `none` will not create a project folder at all, only the inner package folder (which won't be inner anymore) + ## 0.7.2 - 2020-12-08 ### Fixes - A bug in handling optional properties that are themselves models (introduced in 0.7.1) (#262). Thanks @packyg! diff --git a/end_to_end_tests/golden-record-custom/pyproject.toml b/end_to_end_tests/golden-record-custom/pyproject.toml index 87f375197..205b00d13 100644 --- a/end_to_end_tests/golden-record-custom/pyproject.toml +++ b/end_to_end_tests/golden-record-custom/pyproject.toml @@ -14,7 +14,7 @@ include = ["CHANGELOG.md", "custom_e2e/py.typed"] [tool.poetry.dependencies] python = "^3.6" -httpx = "^0.15.0" +httpx = ">=0.15.4,<0.17.0" attrs = "^20.1.0" python-dateutil = "^2.8.1" diff --git a/end_to_end_tests/golden-record/pyproject.toml b/end_to_end_tests/golden-record/pyproject.toml index 777ed75a3..40a0fd0b5 100644 --- a/end_to_end_tests/golden-record/pyproject.toml +++ b/end_to_end_tests/golden-record/pyproject.toml @@ -14,7 +14,7 @@ include = ["CHANGELOG.md", "my_test_api_client/py.typed"] [tool.poetry.dependencies] python = "^3.6" -httpx = "^0.15.0" +httpx = ">=0.15.4,<0.17.0" attrs = "^20.1.0" python-dateutil = "^2.8.1" diff --git a/openapi_python_client/__init__.py b/openapi_python_client/__init__.py index b1adb07b6..998fef5c6 100644 --- a/openapi_python_client/__init__.py +++ b/openapi_python_client/__init__.py @@ -3,6 +3,7 @@ import shutil import subprocess import sys +from enum import Enum from pathlib import Path from typing import Any, Dict, Optional, Sequence, Union @@ -25,14 +26,21 @@ __version__ = version(__package__) +class MetaType(str, Enum): + NONE = "none" + POETRY = "poetry" + SETUP = "setup" + + class Project: TEMPLATE_FILTERS = {"snakecase": utils.snake_case, "kebabcase": utils.kebab_case, "pascalcase": utils.pascal_case} project_name_override: Optional[str] = None package_name_override: Optional[str] = None package_version_override: Optional[str] = None - def __init__(self, *, openapi: GeneratorData, custom_template_path: Optional[Path] = None) -> None: + def __init__(self, *, openapi: GeneratorData, meta: MetaType, custom_template_path: Optional[Path] = None) -> None: self.openapi: GeneratorData = openapi + self.meta: MetaType = meta package_loader = PackageLoader(__package__) loader: BaseLoader @@ -48,7 +56,9 @@ def __init__(self, *, openapi: GeneratorData, custom_template_path: Optional[Pat self.env: Environment = Environment(loader=loader, trim_blocks=True, lstrip_blocks=True) self.project_name: str = self.project_name_override or f"{utils.kebab_case(openapi.title).lower()}-client" - self.project_dir: Path = Path.cwd() / self.project_name + self.project_dir: Path = Path.cwd() + if meta != MetaType.NONE: + self.project_dir /= self.project_name self.package_name: str = self.package_name_override or self.project_name.replace("-", "_") self.package_dir: Path = self.project_dir / self.package_name @@ -62,11 +72,14 @@ def __init__(self, *, openapi: GeneratorData, custom_template_path: Optional[Pat def build(self) -> Sequence[GeneratorError]: """ Create the project from templates """ - print(f"Generating {self.project_name}") - try: - self.project_dir.mkdir() - except FileExistsError: - return [GeneratorError(detail="Directory already exists. Delete it or use the update command.")] + if self.meta == MetaType.NONE: + print(f"Generating {self.package_name}") + else: + print(f"Generating {self.project_name}") + try: + self.project_dir.mkdir() + except FileExistsError: + return [GeneratorError(detail="Directory already exists. Delete it or use the update command.")] self._create_package() self._build_metadata() self._build_models() @@ -79,7 +92,7 @@ def update(self) -> Sequence[GeneratorError]: if not self.package_dir.is_dir(): raise FileNotFoundError() - print(f"Updating {self.project_name}") + print(f"Updating {self.package_name}") shutil.rmtree(self.package_dir) self._create_package() self._build_models() @@ -119,25 +132,21 @@ def _create_package(self) -> None: package_init_template = self.env.get_template("package_init.pyi") package_init.write_text(package_init_template.render(description=self.package_description)) - pytyped = self.package_dir / "py.typed" - pytyped.write_text("# Marker file for PEP 561") + if self.meta != MetaType.NONE: + pytyped = self.package_dir / "py.typed" + pytyped.write_text("# Marker file for PEP 561") types_template = self.env.get_template("types.py") types_path = self.package_dir / "types.py" types_path.write_text(types_template.render()) def _build_metadata(self) -> None: - # Create a pyproject.toml file - pyproject_template = self.env.get_template("pyproject.toml") - 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, - ) - ) + if self.meta == MetaType.NONE: + return + + self._build_pyproject_toml(use_poetry=self.meta == MetaType.POETRY) + if self.meta == MetaType.SETUP: + self._build_setup_py() # README.md readme = self.project_dir / "README.md" @@ -153,6 +162,31 @@ def _build_metadata(self) -> None: git_ignore_template = self.env.get_template(".gitignore") git_ignore_path.write_text(git_ignore_template.render()) + def _build_pyproject_toml(self, *, use_poetry: bool) -> None: + template = "pyproject.toml" if use_poetry else "pyproject_no_poetry.toml" + 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, + ) + ) + + def _build_setup_py(self) -> None: + template = self.env.get_template("setup.py") + 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, + ) + ) + def _build_models(self) -> None: # Generate models models_dir = self.package_dir / "models" @@ -205,7 +239,7 @@ def _build_api(self) -> None: def _get_project_for_url_or_path( - url: Optional[str], path: Optional[Path], custom_template_path: Optional[Path] = None + url: Optional[str], path: Optional[Path], meta: MetaType, custom_template_path: Optional[Path] = None ) -> Union[Project, GeneratorError]: data_dict = _get_document(url=url, path=path) if isinstance(data_dict, GeneratorError): @@ -213,11 +247,11 @@ def _get_project_for_url_or_path( openapi = GeneratorData.from_dict(data_dict) if isinstance(openapi, GeneratorError): return openapi - return Project(openapi=openapi, custom_template_path=custom_template_path) + return Project(openapi=openapi, custom_template_path=custom_template_path, meta=meta) def create_new_client( - *, url: Optional[str], path: Optional[Path], custom_template_path: Optional[Path] = None + *, url: Optional[str], path: Optional[Path], meta: MetaType, custom_template_path: Optional[Path] = None ) -> Sequence[GeneratorError]: """ Generate the client library @@ -225,14 +259,14 @@ def create_new_client( 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) + project = _get_project_for_url_or_path(url=url, path=path, custom_template_path=custom_template_path, meta=meta) if isinstance(project, GeneratorError): return [project] return project.build() def update_existing_client( - *, url: Optional[str], path: Optional[Path], custom_template_path: Optional[Path] = None + *, url: Optional[str], path: Optional[Path], meta: MetaType, custom_template_path: Optional[Path] = None ) -> Sequence[GeneratorError]: """ Update an existing client library @@ -240,7 +274,7 @@ def update_existing_client( 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) + project = _get_project_for_url_or_path(url=url, path=path, custom_template_path=custom_template_path, meta=meta) if isinstance(project, GeneratorError): return [project] return project.update() diff --git a/openapi_python_client/cli.py b/openapi_python_client/cli.py index 1efa7b4c9..ca8565691 100644 --- a/openapi_python_client/cli.py +++ b/openapi_python_client/cli.py @@ -4,6 +4,7 @@ import typer +from openapi_python_client import MetaType from openapi_python_client.parser.errors import ErrorLevel, GeneratorError, ParseError app = typer.Typer() @@ -104,12 +105,18 @@ def handle_errors(errors: Sequence[GeneratorError]) -> None: "resolve_path": True, } +_meta_option = typer.Option( + MetaType.POETRY, + help="The type of metadata you want to generate.", +) + @app.command() 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 + meta: MetaType = _meta_option, ) -> None: """ Generate a new OpenAPI Client library """ from . import create_new_client @@ -120,7 +127,7 @@ 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, custom_template_path=custom_template_path) + errors = create_new_client(url=url, path=path, meta=meta, custom_template_path=custom_template_path) handle_errors(errors) @@ -129,6 +136,7 @@ def update( 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 + meta: MetaType = _meta_option, ) -> None: """ Update an existing OpenAPI Client library """ from . import update_existing_client @@ -140,5 +148,5 @@ 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, custom_template_path=custom_template_path) + errors = update_existing_client(url=url, path=path, meta=meta, custom_template_path=custom_template_path) handle_errors(errors) diff --git a/openapi_python_client/templates/pyproject.toml b/openapi_python_client/templates/pyproject.toml index 6dff89c2c..d069c84b3 100644 --- a/openapi_python_client/templates/pyproject.toml +++ b/openapi_python_client/templates/pyproject.toml @@ -14,7 +14,7 @@ include = ["CHANGELOG.md", "{{ package_name }}/py.typed"] [tool.poetry.dependencies] python = "^3.6" -httpx = "^0.15.0" +httpx = ">=0.15.4,<0.17.0" attrs = "^20.1.0" python-dateutil = "^2.8.1" diff --git a/openapi_python_client/templates/pyproject_no_poetry.toml b/openapi_python_client/templates/pyproject_no_poetry.toml new file mode 100644 index 000000000..1bacf4d63 --- /dev/null +++ b/openapi_python_client/templates/pyproject_no_poetry.toml @@ -0,0 +1,17 @@ +[tool.black] +line-length = 120 +target_version = ['py36', 'py37', 'py38'] +exclude = ''' +( + /( + | \.git + | \.venv + | \.mypy_cache + )/ +) +''' + +[tool.isort] +line_length = 120 +multi_line_output = 3 +include_trailing_comma = true diff --git a/openapi_python_client/templates/setup.py b/openapi_python_client/templates/setup.py new file mode 100644 index 000000000..c0fb58e7f --- /dev/null +++ b/openapi_python_client/templates/setup.py @@ -0,0 +1,19 @@ +import pathlib + +from setuptools import find_packages, setup + +here = pathlib.Path(__file__).parent.resolve() +long_description = (here / "README.md").read_text(encoding="utf-8") + +setup( + name="{{ project_name }}", + version="{{ version }}", + description="{{ description }}", + long_description=long_description, + long_description_content_type="text/markdown", + package_dir={"": "{{ package_name }}"}, + packages=find_packages(where="{{ package_name }}"), + python_requires=">=3.6, <4", + install_requires=["httpx >= 0.15.0, < 0.17.0", "attrs >= 20.1.0", "python-dateutil >= 2.8.1, < 3"], + package_data={"": ["CHANGELOG.md"], "{{ package_name }}": ["py.typed"]}, +) diff --git a/poetry.lock b/poetry.lock index bbee9e7ce..0b79b4f83 100644 --- a/poetry.lock +++ b/poetry.lock @@ -618,6 +618,20 @@ all = ["colorama (>=0.4.3,<0.5.0)", "shellingham (>=1.3.0,<2.0.0)"] dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)"] doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=5.4.0,<6.0.0)", "markdown-include (>=0.5.1,<0.6.0)"] +[[package]] +name = "typer-cli" +version = "0.0.11" +description = "Run Typer scripts with completion, without having to create a package, using Typer CLI." +category = "dev" +optional = false +python-versions = ">=3.6,<4.0" + +[package.dependencies] +colorama = ">=0.4.3,<0.5.0" +importlib_metadata = ">=1.5,<3.0" +shellingham = ">=1.3.2,<2.0.0" +typer = ">=0.3.0,<0.4.0" + [[package]] name = "typing-extensions" version = "3.7.4.3" @@ -654,7 +668,7 @@ testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake [metadata] lock-version = "1.1" python-versions = "^3.6" -content-hash = "faf6d4bb989cd9c7a77088c0ba955b3f68755cf30281594dfef01ccf0f1b1dc5" +content-hash = "b6a52b70db4ea03eda283efd4e65611530ab59b2091c675881e6cab696276f54" [metadata.files] appdirs = [ @@ -673,6 +687,7 @@ autoflake = [ {file = "autoflake-1.4.tar.gz", hash = "sha256:61a353012cff6ab94ca062823d1fb2f692c4acda51c76ff83a8d77915fba51ea"}, ] black = [ + {file = "black-20.8b1-py3-none-any.whl", hash = "sha256:70b62ef1527c950db59062cda342ea224d772abdf6adc58b86a45421bab20a6b"}, {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, ] certifi = [ @@ -946,8 +961,6 @@ pyyaml = [ {file = "PyYAML-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf"}, {file = "PyYAML-5.3.1-cp38-cp38-win32.whl", hash = "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97"}, {file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"}, - {file = "PyYAML-5.3.1-cp39-cp39-win32.whl", hash = "sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a"}, - {file = "PyYAML-5.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:6034f55dab5fea9e53f436aa68fa3ace2634918e8b5994d82f3621c04ff5ed2e"}, {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"}, ] regex = [ @@ -971,12 +984,6 @@ regex = [ {file = "regex-2020.9.27-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:8d69cef61fa50c8133382e61fd97439de1ae623fe943578e477e76a9d9471637"}, {file = "regex-2020.9.27-cp38-cp38-win32.whl", hash = "sha256:f2388013e68e750eaa16ccbea62d4130180c26abb1d8e5d584b9baf69672b30f"}, {file = "regex-2020.9.27-cp38-cp38-win_amd64.whl", hash = "sha256:4318d56bccfe7d43e5addb272406ade7a2274da4b70eb15922a071c58ab0108c"}, - {file = "regex-2020.9.27-cp39-cp39-manylinux1_i686.whl", hash = "sha256:84cada8effefe9a9f53f9b0d2ba9b7b6f5edf8d2155f9fdbe34616e06ececf81"}, - {file = "regex-2020.9.27-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:816064fc915796ea1f26966163f6845de5af78923dfcecf6551e095f00983650"}, - {file = "regex-2020.9.27-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:5d892a4f1c999834eaa3c32bc9e8b976c5825116cde553928c4c8e7e48ebda67"}, - {file = "regex-2020.9.27-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c9443124c67b1515e4fe0bb0aa18df640965e1030f468a2a5dc2589b26d130ad"}, - {file = "regex-2020.9.27-cp39-cp39-win32.whl", hash = "sha256:49f23ebd5ac073765ecbcf046edc10d63dcab2f4ae2bce160982cb30df0c0302"}, - {file = "regex-2020.9.27-cp39-cp39-win_amd64.whl", hash = "sha256:3d20024a70b97b4f9546696cbf2fd30bae5f42229fbddf8661261b1eaff0deb7"}, {file = "regex-2020.9.27.tar.gz", hash = "sha256:a6f32aea4260dfe0e55dc9733ea162ea38f0ea86aa7d0f77b15beac5bf7b369d"}, ] requests = [ @@ -1022,34 +1029,29 @@ typed-ast = [ {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, - {file = "typed_ast-1.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:fcf135e17cc74dbfbc05894ebca928ffeb23d9790b3167a674921db19082401f"}, {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, - {file = "typed_ast-1.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:f208eb7aff048f6bea9586e61af041ddf7f9ade7caed625742af423f6bae3298"}, {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, - {file = "typed_ast-1.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7e4c9d7658aaa1fc80018593abdf8598bf91325af6af5cce4ce7c73bc45ea53d"}, {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, - {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:92c325624e304ebf0e025d1224b77dd4e6393f18aab8d829b5b7e04afe9b7a2c"}, - {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d648b8e3bf2fe648745c8ffcee3db3ff903d0817a01a12dd6a6ea7a8f4889072"}, - {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:fac11badff8313e23717f3dada86a15389d0708275bddf766cca67a84ead3e91"}, - {file = "typed_ast-1.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:0d8110d78a5736e16e26213114a38ca35cb15b6515d535413b090bd50951556d"}, - {file = "typed_ast-1.4.1-cp39-cp39-win32.whl", hash = "sha256:b52ccf7cfe4ce2a1064b18594381bccf4179c2ecf7f513134ec2f993dd4ab395"}, - {file = "typed_ast-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:3742b32cf1c6ef124d57f95be609c473d7ec4c14d0090e5a5e05a15269fb4d0c"}, {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, ] typer = [ {file = "typer-0.3.2-py3-none-any.whl", hash = "sha256:ba58b920ce851b12a2d790143009fa00ac1d05b3ff3257061ff69dbdfc3d161b"}, {file = "typer-0.3.2.tar.gz", hash = "sha256:5455d750122cff96745b0dec87368f56d023725a7ebc9d2e54dd23dc86816303"}, ] +typer-cli = [ + {file = "typer-cli-0.0.11.tar.gz", hash = "sha256:bb90d4edde3d53f076909a7be9ac35f12e573096fc0f3802b3d6f42929a1219e"}, + {file = "typer_cli-0.0.11-py3-none-any.whl", hash = "sha256:ecff43bc8c5d786deaa25b7d14ebfc59b32e40b07895259c3e86604af188f39b"}, +] typing-extensions = [ {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, diff --git a/pyproject.toml b/pyproject.toml index fce28de3f..d7a8c3797 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,7 @@ safety = "*" pytest-cov = "*" python-multipart = "*" flake8 = "*" +typer-cli = "^0.0.11" [tool.taskipy.tasks] check = """ @@ -65,6 +66,7 @@ task regen\ && task regen_custom\ && task e2e\ """ +docs = "typer openapi_python_client/cli.py utils docs > usage.md" [tool.black] line-length = 120 diff --git a/tests/test___init__.py b/tests/test___init__.py index 2b1f25a15..aa7ecf529 100644 --- a/tests/test___init__.py +++ b/tests/test___init__.py @@ -17,14 +17,14 @@ def test__get_project_for_url_or_path(mocker): url = mocker.MagicMock() path = mocker.MagicMock() - from openapi_python_client import _get_project_for_url_or_path + from openapi_python_client import MetaType, _get_project_for_url_or_path - project = _get_project_for_url_or_path(url=url, path=path) + project = _get_project_for_url_or_path(url=url, path=path, meta=MetaType.POETRY) _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) - assert project == _Project() + _Project.assert_called_once_with(openapi=openapi, custom_template_path=None, meta=MetaType.POETRY) + assert project == _Project.return_value def test__get_project_for_url_or_path_generator_error(mocker): @@ -36,9 +36,9 @@ def test__get_project_for_url_or_path_generator_error(mocker): url = mocker.MagicMock() path = mocker.MagicMock() - from openapi_python_client import _get_project_for_url_or_path + from openapi_python_client import MetaType, _get_project_for_url_or_path - project = _get_project_for_url_or_path(url=url, path=path) + project = _get_project_for_url_or_path(url=url, path=path, meta=MetaType.POETRY) _get_document.assert_called_once_with(url=url, path=path) from_dict.assert_called_once_with(data_dict) @@ -54,9 +54,9 @@ def test__get_project_for_url_or_path_document_error(mocker): url = mocker.MagicMock() path = mocker.MagicMock() - from openapi_python_client import _get_project_for_url_or_path + from openapi_python_client import MetaType, _get_project_for_url_or_path - project = _get_project_for_url_or_path(url=url, path=path) + project = _get_project_for_url_or_path(url=url, path=path, meta=MetaType.POETRY) _get_document.assert_called_once_with(url=url, path=path) from_dict.assert_not_called() @@ -71,11 +71,13 @@ def test_create_new_client(mocker): url = mocker.MagicMock() path = mocker.MagicMock() - from openapi_python_client import create_new_client + from openapi_python_client import MetaType, create_new_client - result = create_new_client(url=url, path=path) + 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) + _get_project_for_url_or_path.assert_called_once_with( + url=url, path=path, custom_template_path=None, meta=MetaType.POETRY + ) project.build.assert_called_once() assert result == project.build.return_value @@ -88,11 +90,13 @@ def test_create_new_client_project_error(mocker): url = mocker.MagicMock() path = mocker.MagicMock() - from openapi_python_client import create_new_client + from openapi_python_client import MetaType, create_new_client - result = create_new_client(url=url, path=path) + 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) + _get_project_for_url_or_path.assert_called_once_with( + url=url, path=path, custom_template_path=None, meta=MetaType.POETRY + ) assert result == [error] @@ -104,11 +108,13 @@ def test_update_existing_client(mocker): url = mocker.MagicMock() path = mocker.MagicMock() - from openapi_python_client import update_existing_client + from openapi_python_client import MetaType, update_existing_client - result = update_existing_client(url=url, path=path) + 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) + _get_project_for_url_or_path.assert_called_once_with( + url=url, path=path, custom_template_path=None, meta=MetaType.POETRY + ) project.update.assert_called_once() assert result == project.update.return_value @@ -121,11 +127,13 @@ def test_update_existing_client_project_error(mocker): url = mocker.MagicMock() path = mocker.MagicMock() - from openapi_python_client import update_existing_client + from openapi_python_client import MetaType, update_existing_client - result = update_existing_client(url=url, path=path) + 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) + _get_project_for_url_or_path.assert_called_once_with( + url=url, path=path, custom_template_path=None, meta=MetaType.POETRY + ) assert result == [error] @@ -219,36 +227,54 @@ class TestProject: def test___init__(self, mocker): openapi = mocker.MagicMock(title="My Test API") - from openapi_python_client import Project + from openapi_python_client import MetaType, Project + + project = Project(openapi=openapi, meta=MetaType.POETRY) + + assert project.openapi == openapi + assert project.project_name == "my-test-api-client" + assert project.package_name == "my_test_api_client" + assert project.package_description == "A client library for accessing My Test API" + assert project.meta == MetaType.POETRY + assert project.project_dir == pathlib.Path.cwd() / project.project_name + assert project.package_dir == pathlib.Path.cwd() / project.project_name / project.package_name + + def test___init___no_meta(self, mocker): + openapi = mocker.MagicMock(title="My Test API") + + from openapi_python_client import MetaType, Project - project = Project(openapi=openapi) + project = Project(openapi=openapi, meta=MetaType.NONE) assert project.openapi == openapi assert project.project_name == "my-test-api-client" assert project.package_name == "my_test_api_client" assert project.package_description == "A client library for accessing My Test API" + assert project.meta == MetaType.NONE + assert project.project_dir == pathlib.Path.cwd() + assert project.package_dir == pathlib.Path.cwd() / project.package_name def test_project_and_package_name_overrides(self, mocker): openapi = mocker.MagicMock(title="My Test API") - from openapi_python_client import Project + from openapi_python_client import MetaType, Project Project.project_name_override = "my-special-project-name" - project = Project(openapi=openapi) + project = Project(openapi=openapi, meta=MetaType.POETRY) assert project.project_name == "my-special-project-name" assert project.package_name == "my_special_project_name" Project.package_name_override = "my_special_package_name" - project = Project(openapi=openapi) + project = Project(openapi=openapi, meta=MetaType.POETRY) assert project.project_name == "my-special-project-name" assert project.package_name == "my_special_package_name" def test_build(self, mocker): - from openapi_python_client import Project + from openapi_python_client import MetaType, Project - project = Project(openapi=mocker.MagicMock(title="My Test API")) + project = Project(openapi=mocker.MagicMock(title="My Test API"), meta=MetaType.POETRY) project.project_dir = mocker.MagicMock() project.package_dir = mocker.MagicMock() project._build_metadata = mocker.MagicMock() @@ -269,10 +295,27 @@ def test_build(self, mocker): project._get_errors.assert_called_once() assert result == project._get_errors.return_value + def test_build_no_meta(self, mocker): + from openapi_python_client import MetaType, Project + + project = Project(openapi=mocker.MagicMock(title="My Test API"), meta=MetaType.NONE) + project.project_dir = mocker.MagicMock() + project.package_dir = mocker.MagicMock() + project._build_metadata = mocker.MagicMock() + project._build_models = mocker.MagicMock() + project._build_api = mocker.MagicMock() + project._create_package = mocker.MagicMock() + project._reformat = mocker.MagicMock() + project._get_errors = mocker.MagicMock() + + project.build() + + project.project_dir.mkdir.assert_not_called() + def test_build_file_exists(self, mocker): - from openapi_python_client import Project + from openapi_python_client import MetaType, Project - project = Project(openapi=mocker.MagicMock(title="My Test API")) + project = Project(openapi=mocker.MagicMock(title="My Test API"), meta=MetaType.POETRY) project.project_dir = mocker.MagicMock() project.project_dir.mkdir.side_effect = FileExistsError result = project.build() @@ -282,10 +325,10 @@ def test_build_file_exists(self, mocker): assert result == [GeneratorError(detail="Directory already exists. Delete it or use the update command.")] def test_update(self, mocker): - from openapi_python_client import Project, shutil + from openapi_python_client import MetaType, Project, shutil rmtree = mocker.patch.object(shutil, "rmtree") - project = Project(openapi=mocker.MagicMock(title="My Test API")) + project = Project(openapi=mocker.MagicMock(title="My Test API"), meta=MetaType.POETRY) project.package_dir = mocker.MagicMock() project._build_metadata = mocker.MagicMock() project._build_models = mocker.MagicMock() @@ -305,9 +348,9 @@ def test_update(self, mocker): assert result == project._get_errors.return_value def test_update_missing_dir(self, mocker): - from openapi_python_client import Project + from openapi_python_client import MetaType, Project - project = Project(openapi=mocker.MagicMock(title="My Test API")) + project = Project(openapi=mocker.MagicMock(title="My Test API"), meta=MetaType.POETRY) project.package_dir = mocker.MagicMock() project.package_dir.is_dir.return_value = False project._build_models = mocker.MagicMock() @@ -318,27 +361,24 @@ def test_update_missing_dir(self, mocker): project.package_dir.is_dir.assert_called_once() project._build_models.assert_not_called() - def test__build_metadata(self, mocker): - from openapi_python_client import Project + def test__build_metadata_poetry(self, mocker): + from openapi_python_client import MetaType, Project - project = Project(openapi=mocker.MagicMock(title="My Test API")) + project = Project(openapi=mocker.MagicMock(title="My Test API"), meta=MetaType.POETRY) + project._build_pyproject_toml = mocker.MagicMock() project.project_dir = mocker.MagicMock() - pyproject_path = mocker.MagicMock(autospec=pathlib.Path) readme_path = mocker.MagicMock(autospec=pathlib.Path) git_ignore_path = mocker.MagicMock(autospec=pathlib.Path) paths = { - "pyproject.toml": pyproject_path, "README.md": readme_path, ".gitignore": git_ignore_path, } project.project_dir.__truediv__.side_effect = lambda x: paths[x] - pyproject_template = mocker.MagicMock(autospec=jinja2.Template) readme_template = mocker.MagicMock(autospec=jinja2.Template) git_ignore_template = mocker.MagicMock(autospec=jinja2.Template) project.env = mocker.MagicMock(autospec=jinja2.Environment) templates = { - "pyproject.toml": pyproject_template, "README.md": readme_template, ".gitignore": git_ignore_template, } @@ -346,9 +386,88 @@ def test__build_metadata(self, mocker): project._build_metadata() - project.env.get_template.assert_has_calls( - [mocker.call("pyproject.toml"), mocker.call("README.md"), mocker.call(".gitignore")] + project.env.get_template.assert_has_calls([mocker.call("README.md"), mocker.call(".gitignore")]) + readme_template.render.assert_called_once_with( + description=project.package_description, + project_name=project.project_name, + package_name=project.package_name, ) + readme_path.write_text.assert_called_once_with(readme_template.render()) + git_ignore_template.render.assert_called_once() + git_ignore_path.write_text.assert_called_once_with(git_ignore_template.render()) + project._build_pyproject_toml.assert_called_once_with(use_poetry=True) + + def test__build_metadata_setup(self, mocker): + from openapi_python_client import MetaType, Project + + project = Project(openapi=mocker.MagicMock(title="My Test API"), meta=MetaType.SETUP) + project._build_pyproject_toml = mocker.MagicMock() + project._build_setup_py = mocker.MagicMock() + project.project_dir = mocker.MagicMock() + readme_path = mocker.MagicMock(autospec=pathlib.Path) + git_ignore_path = mocker.MagicMock(autospec=pathlib.Path) + paths = { + "README.md": readme_path, + ".gitignore": git_ignore_path, + } + project.project_dir.__truediv__.side_effect = lambda x: paths[x] + + readme_template = mocker.MagicMock(autospec=jinja2.Template) + git_ignore_template = mocker.MagicMock(autospec=jinja2.Template) + project.env = mocker.MagicMock(autospec=jinja2.Environment) + templates = { + "README.md": readme_template, + ".gitignore": git_ignore_template, + } + project.env.get_template.side_effect = lambda x: templates[x] + + project._build_metadata() + + project.env.get_template.assert_has_calls([mocker.call("README.md"), mocker.call(".gitignore")]) + readme_template.render.assert_called_once_with( + description=project.package_description, + project_name=project.project_name, + package_name=project.package_name, + ) + readme_path.write_text.assert_called_once_with(readme_template.render()) + git_ignore_template.render.assert_called_once() + git_ignore_path.write_text.assert_called_once_with(git_ignore_template.render()) + project._build_pyproject_toml.assert_called_once_with(use_poetry=False) + project._build_setup_py.assert_called_once() + + def test__build_metadata_none(self, mocker): + from openapi_python_client import MetaType, Project + + project = Project(openapi=mocker.MagicMock(title="My Test API"), meta=MetaType.NONE) + project._build_pyproject_toml = mocker.MagicMock() + + project._build_metadata() + + project._build_pyproject_toml.assert_not_called() + + @pytest.mark.parametrize("use_poetry", [(True,), (False,)]) + def test__build_pyproject_toml(self, mocker, use_poetry): + from openapi_python_client import MetaType, Project + + project = Project(openapi=mocker.MagicMock(title="My Test API"), meta=MetaType.POETRY) + project.project_dir = mocker.MagicMock() + pyproject_path = mocker.MagicMock(autospec=pathlib.Path) + paths = { + "pyproject.toml": pyproject_path, + } + project.project_dir.__truediv__.side_effect = lambda x: paths[x] + + pyproject_template = mocker.MagicMock(autospec=jinja2.Template) + project.env = mocker.MagicMock(autospec=jinja2.Environment) + template_path = "pyproject.toml" if use_poetry else "pyproject_no_poetry.toml" + templates = { + template_path: pyproject_template, + } + project.env.get_template.side_effect = lambda x: templates[x] + + project._build_pyproject_toml(use_poetry=use_poetry) + + project.env.get_template.assert_called_once_with(template_path) pyproject_template.render.assert_called_once_with( project_name=project.project_name, @@ -357,24 +476,46 @@ def test__build_metadata(self, mocker): description=project.package_description, ) pyproject_path.write_text.assert_called_once_with(pyproject_template.render()) - readme_template.render.assert_called_once_with( - description=project.package_description, + + def test__build_setup_py(self, mocker): + from openapi_python_client import MetaType, Project + + project = Project(openapi=mocker.MagicMock(title="My Test API"), meta=MetaType.SETUP) + project.project_dir = mocker.MagicMock() + setup_path = mocker.MagicMock(autospec=pathlib.Path) + paths = { + "setup.py": setup_path, + } + project.project_dir.__truediv__.side_effect = lambda x: paths[x] + + setup_template = mocker.MagicMock(autospec=jinja2.Template) + project.env = mocker.MagicMock(autospec=jinja2.Environment) + templates = { + "setup.py": setup_template, + } + project.env.get_template.side_effect = lambda x: templates[x] + + project._build_setup_py() + + project.env.get_template.assert_called_once_with("setup.py") + + setup_template.render.assert_called_once_with( project_name=project.project_name, package_name=project.package_name, + version=project.version, + description=project.package_description, ) - readme_path.write_text.assert_called_once_with(readme_template.render()) - git_ignore_template.render.assert_called_once() - git_ignore_path.write_text.assert_called_once_with(git_ignore_template.render()) + setup_path.write_text.assert_called_once_with(setup_template.render()) def test__reformat(mocker): import subprocess - from openapi_python_client import GeneratorData, Project + from openapi_python_client import GeneratorData, MetaType, Project sub_run = mocker.patch("subprocess.run") openapi = mocker.MagicMock(autospec=GeneratorData, title="My Test API") - project = Project(openapi=openapi) + project = Project(openapi=openapi, meta=MetaType.POETRY) project.project_dir = mocker.MagicMock(autospec=pathlib.Path) project._reformat() @@ -401,8 +542,8 @@ def test__reformat(mocker): def test__get_errors(mocker): - from openapi_python_client import GeneratorData, Project - from openapi_python_client.parser.openapi import EndpointCollection, Schemas + from openapi_python_client import GeneratorData, MetaType, Project + from openapi_python_client.parser.openapi import EndpointCollection openapi = mocker.MagicMock( autospec=GeneratorData, @@ -413,13 +554,13 @@ def test__get_errors(mocker): }, errors=[3], ) - project = Project(openapi=openapi) + project = Project(openapi=openapi, meta=MetaType.POETRY) assert project._get_errors() == [1, 2, 3] def test__custom_templates(mocker): - from openapi_python_client import GeneratorData, Project + from openapi_python_client import GeneratorData, MetaType, Project from openapi_python_client.parser.openapi import EndpointCollection, Schemas openapi = mocker.MagicMock( @@ -432,10 +573,12 @@ def test__custom_templates(mocker): schemas=mocker.MagicMock(autospec=Schemas, errors=[3]), ) - project = Project(openapi=openapi) + project = Project(openapi=openapi, meta=MetaType.POETRY) assert isinstance(project.env.loader, jinja2.PackageLoader) - project = Project(openapi=openapi, custom_template_path="../end_to_end_tests/test_custom_templates") + project = Project( + openapi=openapi, custom_template_path="../end_to_end_tests/test_custom_templates", meta=MetaType.POETRY + ) assert isinstance(project.env.loader, jinja2.ChoiceLoader) assert len(project.env.loader.loaders) == 2 assert isinstance(project.env.loader.loaders[0], jinja2.FileSystemLoader) diff --git a/tests/test_cli.py b/tests/test_cli.py index 1995b7228..1c1619a28 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -27,7 +27,7 @@ def _create_new_client(mocker) -> MagicMock: def test_config_arg(mocker, _create_new_client): load_config = mocker.patch("openapi_python_client.config.Config.load_from_path") - from openapi_python_client.cli import app + from openapi_python_client.cli import MetaType, app config_path = "config/path" path = "cool/path" @@ -36,7 +36,9 @@ def test_config_arg(mocker, _create_new_client): assert result.exit_code == 0 load_config.assert_called_once_with(path=Path(config_path)) - _create_new_client.assert_called_once_with(url=None, path=Path(path), custom_template_path=None) + _create_new_client.assert_called_once_with( + url=None, path=Path(path), custom_template_path=None, meta=MetaType.POETRY + ) def test_bad_config(mocker, _create_new_client): @@ -75,21 +77,34 @@ def test_generate_url_and_path(self, _create_new_client): def test_generate_url(self, _create_new_client): url = "cool.url" - from openapi_python_client.cli import app + from openapi_python_client.cli import MetaType, app result = runner.invoke(app, ["generate", f"--url={url}"]) assert result.exit_code == 0 - _create_new_client.assert_called_once_with(url=url, path=None, custom_template_path=None) + _create_new_client.assert_called_once_with(url=url, path=None, custom_template_path=None, meta=MetaType.POETRY) def test_generate_path(self, _create_new_client): path = "cool/path" - from openapi_python_client.cli import app + from openapi_python_client.cli import MetaType, app result = runner.invoke(app, ["generate", f"--path={path}"]) assert result.exit_code == 0 - _create_new_client.assert_called_once_with(url=None, path=Path(path), custom_template_path=None) + _create_new_client.assert_called_once_with( + url=None, path=Path(path), custom_template_path=None, meta=MetaType.POETRY + ) + + def test_generate_meta(self, _create_new_client): + path = "cool/path" + from openapi_python_client.cli import MetaType, app + + result = runner.invoke(app, ["generate", f"--path={path}", "--meta=none"]) + + assert result.exit_code == 0 + _create_new_client.assert_called_once_with( + url=None, path=Path(path), custom_template_path=None, meta=MetaType.NONE + ) def test_generate_handle_errors(self, _create_new_client): _create_new_client.return_value = [GeneratorError(detail="this is a message")] @@ -154,18 +169,22 @@ def test_update_url_and_path(self, _update_existing_client): def test_update_url(self, _update_existing_client): url = "cool.url" - from openapi_python_client.cli import app + from openapi_python_client.cli import MetaType, app result = runner.invoke(app, ["update", f"--url={url}"]) assert result.exit_code == 0 - _update_existing_client.assert_called_once_with(url=url, path=None, custom_template_path=None) + _update_existing_client.assert_called_once_with( + url=url, path=None, custom_template_path=None, meta=MetaType.POETRY + ) def test_update_path(self, _update_existing_client): path = "cool/path" - from openapi_python_client.cli import app + from openapi_python_client.cli import MetaType, app result = runner.invoke(app, ["update", f"--path={path}"]) assert result.exit_code == 0 - _update_existing_client.assert_called_once_with(url=None, path=Path(path), custom_template_path=None) + _update_existing_client.assert_called_once_with( + url=None, path=Path(path), custom_template_path=None, meta=MetaType.POETRY + ) diff --git a/usage.md b/usage.md index b3edf46bc..4890e0e37 100644 --- a/usage.md +++ b/usage.md @@ -1,6 +1,6 @@ # `openapi-python-client` -Generate a Python client from an OpenAPI JSON document +Generate a Python client from an OpenAPI JSON document **Usage**: @@ -10,20 +10,20 @@ $ openapi-python-client [OPTIONS] COMMAND [ARGS]... **Options**: -- `--version`: Print the version and exit -- `--config PATH`: Path to the config file to use -- `--install-completion`: Install completion for the current shell. -- `--show-completion`: Show completion for the current shell, to copy it or customize the installation. -- `--help`: Show this message and exit. +* `--version`: Print the version and exit [default: False] +* `--config PATH`: Path to the config file to use +* `--install-completion`: Install completion for the current shell. +* `--show-completion`: Show completion for the current shell, to copy it or customize the installation. +* `--help`: Show this message and exit. **Commands**: -- `generate`: Generate a new OpenAPI Client library -- `update`: Update an existing OpenAPI Client library +* `generate`: Generate a new OpenAPI Client library +* `update`: Update an existing OpenAPI Client library ## `openapi-python-client generate` -Generate a new OpenAPI Client library +Generate a new OpenAPI Client library **Usage**: @@ -33,13 +33,15 @@ $ openapi-python-client generate [OPTIONS] **Options**: -- `--url TEXT`: A URL to read the JSON from -- `--path PATH`: A path to the JSON file -- `--help`: Show this message and exit. +* `--url TEXT`: A URL to read the JSON from +* `--path PATH`: A path to the JSON file +* `--custom-template-path DIRECTORY`: A path to a directory containing custom template(s) +* `--meta [none|poetry|setup]`: The type of metadata you want to generate. [default: poetry] +* `--help`: Show this message and exit. ## `openapi-python-client update` -Update an existing OpenAPI Client library +Update an existing OpenAPI Client library **Usage**: @@ -49,6 +51,9 @@ $ openapi-python-client update [OPTIONS] **Options**: -- `--url TEXT`: A URL to read the JSON from -- `--path PATH`: A path to the JSON file -- `--help`: Show this message and exit. +* `--url TEXT`: A URL to read the JSON from +* `--path PATH`: A path to the JSON file +* `--custom-template-path DIRECTORY`: A path to a directory containing custom template(s) +* `--meta [none|poetry|setup]`: The type of metadata you want to generate. [default: poetry] +* `--help`: Show this message and exit. +