Skip to content
Closed
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
16 changes: 16 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Test",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/src/openapi_python_generator",
"args": ["--library", "aiohttp", "./openapi.yaml", "test_api"],
"cwd": "${workspaceFolder}/tests/test_data"
}
]
}
9 changes: 9 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"python.analysis.typeCheckingMode": "off",
"python.analysis.packageIndexDepths": [
{
"name": "case-converter",
"depth": 4
}
]
}
2 changes: 1 addition & 1 deletion mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ plugins = pydantic.mypy
[mypy-nox.*,pytest]
ignore_missing_imports = True

[mypy-openapi_schema_pydantic.*]
[mypy-openapi_pydantic.*]
ignore_missing_imports = True

[mypy-autopep8.*]
Expand Down
2,783 changes: 1,415 additions & 1,368 deletions poetry.lock

Large diffs are not rendered by default.

24 changes: 13 additions & 11 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,30 @@ readme = "README.md"
homepage = "https://github.com/MarcoMuellner/openapi-python-generator"
repository = "https://github.com/MarcoMuellner/openapi-python-generator"
documentation = "https://openapi-python-generator.readthedocs.io"
classifiers = [
"Development Status :: 3 - Alpha",
]
classifiers = ["Development Status :: 3 - Alpha"]
keywords = ["OpenAPI", "Generator", "Python", "async"]
package-mode = true

[tool.poetry.urls]
Changelog = "https://github.com/MarcoMuellner/openapi-python-generator/releases"

[tool.poetry.dependencies]
python = "^3.7"
httpx = {extras = ["all"], version = "^0.23.0"}
pydantic = "^1.9.1"
python = ">=3.8,<3.12"
httpx = "*"
pydantic = ">=1.10.1"
orjson = "^3.7.2"
openapi-schema-pydantic = "^1.2.3"
openapi-pydantic = "^0.4.0"
Jinja2 = "^3.1.2"
click = "^8.1.3"
black = ">=21.10b0"
isort = ">=5.10.1"
deep-translator = "^1.11.4"
case-converter = "^1.1.0"
pyyaml = "^6.0.1"

[tool.poetry.dev-dependencies]
Pygments = ">=2.10.0"
coverage = {extras = ["toml"], version = "^6.4.1"}
coverage = { extras = ["toml"], version = "^6.4.1" }
darglint = ">=1.8.1"
flake8 = ">=3.0.1"
flake8-bandit = ">=2.1.2"
Expand All @@ -45,10 +47,10 @@ pytest = ">=6.2.5"
pyupgrade = ">=2.29.1"
safety = ">=1.10.3"
typeguard = ">=2.13.3"
xdoctest = {extras = ["colors"], version = ">=0.15.10"}
myst-parser = {version = ">=0.16.1"}
xdoctest = { extras = ["colors"], version = ">=0.15.10" }
myst-parser = { version = ">=0.16.1" }
pytest-cov = "^3.0.0"
fastapi = "^0.78.0"
fastapi = "^0.109.2"
uvicorn = "^0.18.1"
respx = "^0.20.1"
aiohttp = "^3.8.3"
Expand Down
3 changes: 3 additions & 0 deletions src/openapi_python_generator/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Python client from an OPENAPI 3.0 specification in seconds."""

try:
from importlib.metadata import PackageNotFoundError # type: ignore
from importlib.metadata import version
Expand All @@ -10,3 +11,5 @@
__version__ = version(__name__)
except PackageNotFoundError: # pragma: no cover
__version__ = "unknown"

OPENAPI_VERSION = None
10 changes: 9 additions & 1 deletion src/openapi_python_generator/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@
from typing import Dict
from typing import Optional

from openapi_python_generator.models import LibraryConfig
from pydantic import BaseModel


class LibraryConfig(BaseModel):
name: str
library_name: str
template_name: str
include_async: bool
include_sync: bool


class HTTPLibrary(str, Enum):
Expand Down
51 changes: 38 additions & 13 deletions src/openapi_python_generator/generate_data.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
from pathlib import Path
from typing import Optional
from typing import TYPE_CHECKING, Any, Optional
from typing import Union

import black
import click
import httpx
import isort
import orjson
from black import NothingChanged
import yaml
from black import NothingChanged, InvalidInput
from httpx import ConnectError
from httpx import ConnectTimeout
from openapi_schema_pydantic import OpenAPI
from pydantic import ValidationError

import openapi_python_generator
from .common import HTTPLibrary
from .common import library_config_dict
from .language_converters.python.generator import generator
from .language_converters.python.jinja_config import SERVICE_TEMPLATE
from .language_converters.python.jinja_config import create_jinja_env
from .models import ConversionResult

if TYPE_CHECKING:
from .models import ConversionResult


def write_code(path: Path, content) -> None:
Expand All @@ -34,29 +36,33 @@ def write_code(path: Path, content) -> None:
formatted_contend = black.format_file_contents(
content, fast=False, mode=black.FileMode(line_length=120)
)

except NothingChanged:
formatted_contend = content
except InvalidInput as e:
print(f"[Skipping formatting] InvalidInput file: {path}")
formatted_contend = content
formatted_contend = isort.code(formatted_contend, line_length=120)
f.write(formatted_contend)
except Exception as e:
raise e


def get_open_api(source: Union[str, Path]) -> OpenAPI:
def get_open_api(source: Union[str, Path]) -> Any:
"""
Tries to fetch the openapi.json file from the web or load from a local file. Returns the according OpenAPI object.
:param source:
:return:
"""
text = None

try:
if not isinstance(source, Path) and (
source.startswith("http://") or source.startswith("https://")
):
return OpenAPI(**orjson.loads(httpx.get(source).text))

with open(source, "r") as f:
return OpenAPI(**orjson.loads(f.read()))
text = httpx.get(source).text
else:
with open(source, "r") as f:
text = f.read()
except FileNotFoundError:
click.echo(
f"File {source} not found. Please make sure to pass the path to the OpenAPI 3.0 specification."
Expand All @@ -71,8 +77,22 @@ def get_open_api(source: Union[str, Path]) -> OpenAPI:
)
raise

try:
data = orjson.loads(text)
except:
data = yaml.safe_load(text)

if data["openapi"].startswith("3.0"):
openapi_python_generator.OPENAPI_VERSION = "3.0"
from openapi_pydantic.v3.v3_0_3.open_api import OpenAPI
else:
openapi_python_generator.OPENAPI_VERSION = "3.1"
from openapi_pydantic.v3.v3_1_0.open_api import OpenAPI

def write_data(data: ConversionResult, output: Union[str, Path]) -> None:
return OpenAPI(**data)


def write_data(data: "ConversionResult", output: Union[str, Path]) -> None:
"""
This function will firstly create the folderstrucutre of output, if it doesn't exist. Then it will create the
models from data.models into the models sub module of the output folder. After this, the services will be created
Expand Down Expand Up @@ -120,7 +140,10 @@ def write_data(data: ConversionResult, output: Union[str, Path]) -> None:
)

# Create services.__init__.py file containing imports to all services.
write_code(services_path / "__init__.py", "")
write_code(
services_path / "__init__.py",
"\n".join([f"from .{file} import *" for file in files]),
)

# Write the api_config.py file.
write_code(Path(output) / "api_config.py", data.api_config.content)
Expand All @@ -146,6 +169,8 @@ def generate_data(
data = get_open_api(source)
click.echo(f"Generating data from {source}")

from .language_converters.python.generator import generator

result = generator(
data,
library_config_dict[library],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Optional

from openapi_schema_pydantic import OpenAPI
from openapi_pydantic import OpenAPI

from openapi_python_generator.language_converters.python.jinja_config import (
API_CONFIG_TEMPLATE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import re
from typing import Optional


_use_orjson: bool = False
_custom_template_path: str = None
_symbol_ascii_strip_re = re.compile(r"[^A-Za-z0-9_]")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from typing import Optional
from openapi_python_generator import OPENAPI_VERSION

from openapi_schema_pydantic import OpenAPI
if OPENAPI_VERSION == "3.0":
from openapi_pydantic.v3.v3_0_3.open_api import OpenAPI
else:
from openapi_pydantic import OpenAPI

from openapi_python_generator.language_converters.python import common
from openapi_python_generator.language_converters.python.api_config_generator import (
Expand All @@ -13,7 +17,7 @@
generate_services,
)
from openapi_python_generator.models import ConversionResult
from openapi_python_generator.models import LibraryConfig
from openapi_python_generator.common import LibraryConfig


def generator(
Expand All @@ -31,12 +35,12 @@ def generator(
common.set_custom_template_path(custom_template_path)

if data.components is not None:
models = generate_models(data.components)
models = generate_models(data.components, data.paths)
else:
models = []

if data.paths is not None:
services = generate_services(data.paths, library_config)
services = generate_services(data.paths, data.components, library_config)
else:
services = []

Expand Down
Loading