Skip to content

Commit 5625dde

Browse files
authored
feat: Added ability to generate clients with no metadata or setup.py. Closes #120 (#275)
1 parent caecb03 commit 5625dde

File tree

13 files changed

+391
-131
lines changed

13 files changed

+391
-131
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,23 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
9+
## Unreleased
10+
### Additions
11+
- New `--meta` command line option for specifying what type of metadata should be generated:
12+
- `poetry` is the default value, same behavior you're used to in previous versions
13+
- `setup` will generate a pyproject.toml with no Poetry information, and instead create a `setup.py` with the
14+
project info.
15+
- `none` will not create a project folder at all, only the inner package folder (which won't be inner anymore)
16+
17+
818
## 0.7.3 - 2020-12-21
919
### Fixes
1020
- Spacing and extra returns for Union types of `additionalProperties` (#266 & #268). Thanks @joshzana & @packyg!
1121
- Title of inline schemas will no longer be missing characters (#271 & #274). Thanks @kalzoo!
1222
- Handling of nulls (Nones) when parsing or constructing dates (#267). Thanks @fyhertz!
1323

24+
1425
## 0.7.2 - 2020-12-08
1526
### Fixes
1627
- A bug in handling optional properties that are themselves models (introduced in 0.7.1) (#262). Thanks @packyg!

end_to_end_tests/golden-record-custom/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ include = ["CHANGELOG.md", "custom_e2e/py.typed"]
1414

1515
[tool.poetry.dependencies]
1616
python = "^3.6"
17-
httpx = "^0.15.0"
17+
httpx = ">=0.15.4,<0.17.0"
1818
attrs = "^20.1.0"
1919
python-dateutil = "^2.8.1"
2020

end_to_end_tests/golden-record/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ include = ["CHANGELOG.md", "my_test_api_client/py.typed"]
1414

1515
[tool.poetry.dependencies]
1616
python = "^3.6"
17-
httpx = "^0.15.0"
17+
httpx = ">=0.15.4,<0.17.0"
1818
attrs = "^20.1.0"
1919
python-dateutil = "^2.8.1"
2020

openapi_python_client/__init__.py

Lines changed: 61 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import shutil
44
import subprocess
55
import sys
6+
from enum import Enum
67
from pathlib import Path
78
from typing import Any, Dict, Optional, Sequence, Union
89

@@ -25,6 +26,12 @@
2526
__version__ = version(__package__)
2627

2728

29+
class MetaType(str, Enum):
30+
NONE = "none"
31+
POETRY = "poetry"
32+
SETUP = "setup"
33+
34+
2835
TEMPLATE_FILTERS = {
2936
"snakecase": utils.snake_case,
3037
"kebabcase": utils.kebab_case,
@@ -38,8 +45,9 @@ class Project:
3845
package_name_override: Optional[str] = None
3946
package_version_override: Optional[str] = None
4047

41-
def __init__(self, *, openapi: GeneratorData, custom_template_path: Optional[Path] = None) -> None:
48+
def __init__(self, *, openapi: GeneratorData, meta: MetaType, custom_template_path: Optional[Path] = None) -> None:
4249
self.openapi: GeneratorData = openapi
50+
self.meta: MetaType = meta
4351

4452
package_loader = PackageLoader(__package__)
4553
loader: BaseLoader
@@ -55,7 +63,9 @@ def __init__(self, *, openapi: GeneratorData, custom_template_path: Optional[Pat
5563
self.env: Environment = Environment(loader=loader, trim_blocks=True, lstrip_blocks=True)
5664

5765
self.project_name: str = self.project_name_override or f"{utils.kebab_case(openapi.title).lower()}-client"
58-
self.project_dir: Path = Path.cwd() / self.project_name
66+
self.project_dir: Path = Path.cwd()
67+
if meta != MetaType.NONE:
68+
self.project_dir /= self.project_name
5969

6070
self.package_name: str = self.package_name_override or self.project_name.replace("-", "_")
6171
self.package_dir: Path = self.project_dir / self.package_name
@@ -69,11 +79,14 @@ def __init__(self, *, openapi: GeneratorData, custom_template_path: Optional[Pat
6979
def build(self) -> Sequence[GeneratorError]:
7080
""" Create the project from templates """
7181

72-
print(f"Generating {self.project_name}")
73-
try:
74-
self.project_dir.mkdir()
75-
except FileExistsError:
76-
return [GeneratorError(detail="Directory already exists. Delete it or use the update command.")]
82+
if self.meta == MetaType.NONE:
83+
print(f"Generating {self.package_name}")
84+
else:
85+
print(f"Generating {self.project_name}")
86+
try:
87+
self.project_dir.mkdir()
88+
except FileExistsError:
89+
return [GeneratorError(detail="Directory already exists. Delete it or use the update command.")]
7790
self._create_package()
7891
self._build_metadata()
7992
self._build_models()
@@ -86,7 +99,7 @@ def update(self) -> Sequence[GeneratorError]:
8699

87100
if not self.package_dir.is_dir():
88101
raise FileNotFoundError()
89-
print(f"Updating {self.project_name}")
102+
print(f"Updating {self.package_name}")
90103
shutil.rmtree(self.package_dir)
91104
self._create_package()
92105
self._build_models()
@@ -126,25 +139,21 @@ def _create_package(self) -> None:
126139
package_init_template = self.env.get_template("package_init.pyi")
127140
package_init.write_text(package_init_template.render(description=self.package_description))
128141

129-
pytyped = self.package_dir / "py.typed"
130-
pytyped.write_text("# Marker file for PEP 561")
142+
if self.meta != MetaType.NONE:
143+
pytyped = self.package_dir / "py.typed"
144+
pytyped.write_text("# Marker file for PEP 561")
131145

132146
types_template = self.env.get_template("types.py")
133147
types_path = self.package_dir / "types.py"
134148
types_path.write_text(types_template.render())
135149

136150
def _build_metadata(self) -> None:
137-
# Create a pyproject.toml file
138-
pyproject_template = self.env.get_template("pyproject.toml")
139-
pyproject_path = self.project_dir / "pyproject.toml"
140-
pyproject_path.write_text(
141-
pyproject_template.render(
142-
project_name=self.project_name,
143-
package_name=self.package_name,
144-
version=self.version,
145-
description=self.package_description,
146-
)
147-
)
151+
if self.meta == MetaType.NONE:
152+
return
153+
154+
self._build_pyproject_toml(use_poetry=self.meta == MetaType.POETRY)
155+
if self.meta == MetaType.SETUP:
156+
self._build_setup_py()
148157

149158
# README.md
150159
readme = self.project_dir / "README.md"
@@ -160,6 +169,31 @@ def _build_metadata(self) -> None:
160169
git_ignore_template = self.env.get_template(".gitignore")
161170
git_ignore_path.write_text(git_ignore_template.render())
162171

172+
def _build_pyproject_toml(self, *, use_poetry: bool) -> None:
173+
template = "pyproject.toml" if use_poetry else "pyproject_no_poetry.toml"
174+
pyproject_template = self.env.get_template(template)
175+
pyproject_path = self.project_dir / "pyproject.toml"
176+
pyproject_path.write_text(
177+
pyproject_template.render(
178+
project_name=self.project_name,
179+
package_name=self.package_name,
180+
version=self.version,
181+
description=self.package_description,
182+
)
183+
)
184+
185+
def _build_setup_py(self) -> None:
186+
template = self.env.get_template("setup.py")
187+
path = self.project_dir / "setup.py"
188+
path.write_text(
189+
template.render(
190+
project_name=self.project_name,
191+
package_name=self.package_name,
192+
version=self.version,
193+
description=self.package_description,
194+
)
195+
)
196+
163197
def _build_models(self) -> None:
164198
# Generate models
165199
models_dir = self.package_dir / "models"
@@ -212,42 +246,42 @@ def _build_api(self) -> None:
212246

213247

214248
def _get_project_for_url_or_path(
215-
url: Optional[str], path: Optional[Path], custom_template_path: Optional[Path] = None
249+
url: Optional[str], path: Optional[Path], meta: MetaType, custom_template_path: Optional[Path] = None
216250
) -> Union[Project, GeneratorError]:
217251
data_dict = _get_document(url=url, path=path)
218252
if isinstance(data_dict, GeneratorError):
219253
return data_dict
220254
openapi = GeneratorData.from_dict(data_dict)
221255
if isinstance(openapi, GeneratorError):
222256
return openapi
223-
return Project(openapi=openapi, custom_template_path=custom_template_path)
257+
return Project(openapi=openapi, custom_template_path=custom_template_path, meta=meta)
224258

225259

226260
def create_new_client(
227-
*, url: Optional[str], path: Optional[Path], custom_template_path: Optional[Path] = None
261+
*, url: Optional[str], path: Optional[Path], meta: MetaType, custom_template_path: Optional[Path] = None
228262
) -> Sequence[GeneratorError]:
229263
"""
230264
Generate the client library
231265
232266
Returns:
233267
A list containing any errors encountered when generating.
234268
"""
235-
project = _get_project_for_url_or_path(url=url, path=path, custom_template_path=custom_template_path)
269+
project = _get_project_for_url_or_path(url=url, path=path, custom_template_path=custom_template_path, meta=meta)
236270
if isinstance(project, GeneratorError):
237271
return [project]
238272
return project.build()
239273

240274

241275
def update_existing_client(
242-
*, url: Optional[str], path: Optional[Path], custom_template_path: Optional[Path] = None
276+
*, url: Optional[str], path: Optional[Path], meta: MetaType, custom_template_path: Optional[Path] = None
243277
) -> Sequence[GeneratorError]:
244278
"""
245279
Update an existing client library
246280
247281
Returns:
248282
A list containing any errors encountered when generating.
249283
"""
250-
project = _get_project_for_url_or_path(url=url, path=path, custom_template_path=custom_template_path)
284+
project = _get_project_for_url_or_path(url=url, path=path, custom_template_path=custom_template_path, meta=meta)
251285
if isinstance(project, GeneratorError):
252286
return [project]
253287
return project.update()

openapi_python_client/cli.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import typer
66

7+
from openapi_python_client import MetaType
78
from openapi_python_client.parser.errors import ErrorLevel, GeneratorError, ParseError
89

910
app = typer.Typer()
@@ -104,12 +105,18 @@ def handle_errors(errors: Sequence[GeneratorError]) -> None:
104105
"resolve_path": True,
105106
}
106107

108+
_meta_option = typer.Option(
109+
MetaType.POETRY,
110+
help="The type of metadata you want to generate.",
111+
)
112+
107113

108114
@app.command()
109115
def generate(
110116
url: Optional[str] = typer.Option(None, help="A URL to read the JSON from"),
111117
path: Optional[pathlib.Path] = typer.Option(None, help="A path to the JSON file"),
112118
custom_template_path: Optional[pathlib.Path] = typer.Option(None, **custom_template_path_options), # type: ignore
119+
meta: MetaType = _meta_option,
113120
) -> None:
114121
""" Generate a new OpenAPI Client library """
115122
from . import create_new_client
@@ -120,7 +127,7 @@ def generate(
120127
if url and path:
121128
typer.secho("Provide either --url or --path, not both", fg=typer.colors.RED)
122129
raise typer.Exit(code=1)
123-
errors = create_new_client(url=url, path=path, custom_template_path=custom_template_path)
130+
errors = create_new_client(url=url, path=path, meta=meta, custom_template_path=custom_template_path)
124131
handle_errors(errors)
125132

126133

@@ -129,6 +136,7 @@ def update(
129136
url: Optional[str] = typer.Option(None, help="A URL to read the JSON from"),
130137
path: Optional[pathlib.Path] = typer.Option(None, help="A path to the JSON file"),
131138
custom_template_path: Optional[pathlib.Path] = typer.Option(None, **custom_template_path_options), # type: ignore
139+
meta: MetaType = _meta_option,
132140
) -> None:
133141
""" Update an existing OpenAPI Client library """
134142
from . import update_existing_client
@@ -140,5 +148,5 @@ def update(
140148
typer.secho("Provide either --url or --path, not both", fg=typer.colors.RED)
141149
raise typer.Exit(code=1)
142150

143-
errors = update_existing_client(url=url, path=path, custom_template_path=custom_template_path)
151+
errors = update_existing_client(url=url, path=path, meta=meta, custom_template_path=custom_template_path)
144152
handle_errors(errors)

openapi_python_client/templates/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ include = ["CHANGELOG.md", "{{ package_name }}/py.typed"]
1414

1515
[tool.poetry.dependencies]
1616
python = "^3.6"
17-
httpx = "^0.15.0"
17+
httpx = ">=0.15.4,<0.17.0"
1818
attrs = "^20.1.0"
1919
python-dateutil = "^2.8.1"
2020

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[tool.black]
2+
line-length = 120
3+
target_version = ['py36', 'py37', 'py38']
4+
exclude = '''
5+
(
6+
/(
7+
| \.git
8+
| \.venv
9+
| \.mypy_cache
10+
)/
11+
)
12+
'''
13+
14+
[tool.isort]
15+
line_length = 120
16+
multi_line_output = 3
17+
include_trailing_comma = true
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import pathlib
2+
3+
from setuptools import find_packages, setup
4+
5+
here = pathlib.Path(__file__).parent.resolve()
6+
long_description = (here / "README.md").read_text(encoding="utf-8")
7+
8+
setup(
9+
name="{{ project_name }}",
10+
version="{{ version }}",
11+
description="{{ description }}",
12+
long_description=long_description,
13+
long_description_content_type="text/markdown",
14+
package_dir={"": "{{ package_name }}"},
15+
packages=find_packages(where="{{ package_name }}"),
16+
python_requires=">=3.6, <4",
17+
install_requires=["httpx >= 0.15.0, < 0.17.0", "attrs >= 20.1.0", "python-dateutil >= 2.8.1, < 3"],
18+
package_data={"": ["CHANGELOG.md"], "{{ package_name }}": ["py.typed"]},
19+
)

0 commit comments

Comments
 (0)