Skip to content

Added ability to generate clients with no metadata or setup.py #275

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 3 commits into from
Jan 5, 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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,23 @@ 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.3 - 2020-12-21
### Fixes
- Spacing and extra returns for Union types of `additionalProperties` (#266 & #268). Thanks @joshzana & @packyg!
- Title of inline schemas will no longer be missing characters (#271 & #274). Thanks @kalzoo!
- Handling of nulls (Nones) when parsing or constructing dates (#267). Thanks @fyhertz!


## 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!
Expand Down
2 changes: 1 addition & 1 deletion end_to_end_tests/golden-record-custom/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
2 changes: 1 addition & 1 deletion end_to_end_tests/golden-record/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
88 changes: 61 additions & 27 deletions openapi_python_client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -25,6 +26,12 @@
__version__ = version(__package__)


class MetaType(str, Enum):
NONE = "none"
POETRY = "poetry"
SETUP = "setup"


TEMPLATE_FILTERS = {
"snakecase": utils.snake_case,
"kebabcase": utils.kebab_case,
Expand All @@ -38,8 +45,9 @@ class Project:
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
Expand All @@ -55,7 +63,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
Expand All @@ -69,11 +79,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()
Expand All @@ -86,7 +99,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()
Expand Down Expand Up @@ -126,25 +139,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"
Expand All @@ -160,6 +169,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"
Expand Down Expand Up @@ -212,42 +246,42 @@ 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):
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)
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

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

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()
Expand Down
12 changes: 10 additions & 2 deletions openapi_python_client/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand All @@ -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)


Expand All @@ -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
Expand All @@ -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)
2 changes: 1 addition & 1 deletion openapi_python_client/templates/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
17 changes: 17 additions & 0 deletions openapi_python_client/templates/pyproject_no_poetry.toml
Original file line number Diff line number Diff line change
@@ -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
19 changes: 19 additions & 0 deletions openapi_python_client/templates/setup.py
Original file line number Diff line number Diff line change
@@ -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"]},
)
Loading