Skip to content
Draft
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
2 changes: 1 addition & 1 deletion .generation/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Patched version of openapi-generator-cli with python3 support

FROM docker.io/openapitools/openapi-generator-cli:v7.12.0
FROM docker.io/openapitools/openapi-generator-cli:v7.15.0

RUN apt-get update && apt-get install -y python3
245 changes: 147 additions & 98 deletions .generation/generate.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/bin/python3

'''
"""
Generator for the OpenAPI client.
'''
"""

from __future__ import annotations
import argparse
Expand All @@ -14,39 +14,50 @@
import os
import shutil
import subprocess
import sys
from typing import Literal
import logging


CWD = Path('.generation/')
CWD = Path(".generation/")


class ProgramArgs(argparse.Namespace):
'''Typed command line arguments.'''
language: Literal['python', 'typescript']
"""Typed command line arguments."""

language: Literal["python", "typescript"]
fetch_spec: bool
build_container: bool

@staticmethod
def parse_arguments() -> ProgramArgs:
'''Parse command line arguments.'''
"""Parse command line arguments."""
parser = argparse.ArgumentParser(
description='Create a client for the Geo Engine API.')
parser.add_argument('--no-spec-fetch', dest='fetch_spec', action='store_false',
required=False, default=True)
parser.add_argument('--no-container-build', dest='build_container', action='store_false',
required=False, default=True)
parser.add_argument('language', choices=['python', 'typescript'],
type=str)
description="Create a client for the Geo Engine API."
)
parser.add_argument(
"--no-spec-fetch",
dest="fetch_spec",
action="store_false",
required=False,
default=True,
)
parser.add_argument(
"--no-container-build",
dest="build_container",
action="store_false",
required=False,
default=True,
)
parser.add_argument("language", choices=["python", "typescript"], type=str)

parsed_args: ProgramArgs = parser.parse_args()
return parsed_args


@dataclass
class ConfigArgs():
'''Typed config.ini arguments.'''
class ConfigArgs:
"""Typed config.ini arguments."""

# Backend version
ge_backend_commit: str

Expand All @@ -62,69 +73,73 @@ class ConfigArgs():

@staticmethod
def parse_config() -> ConfigArgs:
'''Parse config.ini arguments.'''
"""Parse config.ini arguments."""
parsed = configparser.ConfigParser()
parsed.optionxform = str # do not convert keys to lowercase
parsed.read(CWD / 'config.ini')
parsed.read(CWD / "config.ini")

return ConfigArgs(
ge_backend_commit=parsed['input']['backendCommit'],
github_url=parsed['general']['githubUrl'],
package_version=parsed['general']['version'],
python_package_name=parsed['python']['name'],
typescript_package_name=parsed['typescript']['name'],
ge_backend_commit=parsed["input"]["backendCommit"],
github_url=parsed["general"]["githubUrl"],
package_version=parsed["general"]["version"],
python_package_name=parsed["python"]["name"],
typescript_package_name=parsed["typescript"]["name"],
)


def fetch_spec(*, ge_backend_commit: str) -> None:
'''
"""
Copy the openapi.json file from the backend repo.
'''
"""

request_url = f"https://raw.githubusercontent.com/geo-engine/geoengine/{ge_backend_commit}/openapi.json"

logging.info(f"Requesting `openapi.json` at `{request_url}`….")
with request.urlopen(request_url, timeout=10) as w, \
open(CWD / "input/openapi.json", "w", encoding='utf-8') as f:
f.write(w.read().decode('utf-8'))
with (
request.urlopen(request_url, timeout=10) as w,
open(CWD / "input/openapi.json", "w", encoding="utf-8") as f,
):
f.write(w.read().decode("utf-8"))

logging.info("Stored `openapi.json`.")


def build_container():
'''Build the patched generator image'''
"""Build the patched generator image"""
logging.info("Building patched generator image…")
subprocess.run(
[
"podman", "build",
"-t", "openapi-generator-cli:patched",
"podman",
"build",
"-t",
"openapi-generator-cli:patched",
CWD,
],
check=True,
)
logging.info("Patched generator image built.")


def clean_dirs(*, language: Literal['python', 'typescript']):
'''Remove some directories because they are not be overwritten by the generator.'''
def clean_dirs(*, language: Literal["python", "typescript"]):
"""Remove some directories because they are not be overwritten by the generator."""

dirs_to_remove = [
'node_modules',
'.mypy_cache',
Path(language) / 'test'
]
dirs_to_remove = ["node_modules", ".mypy_cache", Path(language) / "test"]

match language:
case 'typescript':
dirs_to_remove.extend([
Path(language) / 'src',
Path(language) / 'dist',
Path(language) / 'node_modules',
])
case 'python':
dirs_to_remove.extend([
Path(language) / 'geoengine_openapi_client',
])
case "typescript":
dirs_to_remove.extend(
[
Path(language) / "src",
Path(language) / "dist",
Path(language) / "node_modules",
]
)
case "python":
dirs_to_remove.extend(
[
Path(language) / "geoengine_openapi_client",
]
)

logging.info(f"Removing directories:")

Expand All @@ -136,84 +151,115 @@ def clean_dirs(*, language: Literal['python', 'typescript']):


def generate_python_code(*, package_name: str, package_version: str, package_url: str):
'''Run the generator.'''
"""Run the generator."""

subprocess.run(
[
"podman", "run",
"podman",
"run",
"--rm", # remove the container after running
"-v", f"{os.getcwd()}:/local",
"-v",
f"{os.getcwd()}:/local",
f"--env-file={CWD / 'override.env'}",
# "docker.io/openapitools/openapi-generator-cli:v7.0.1",
"openapi-generator-cli:patched",
"generate",
"-i", f"{'/local' / CWD / 'input/openapi.json'}",
"-g", "python",
"--additional-properties=" + ",".join([
"useOneOfDiscriminatorLookup=true",
# "generateSourceCodeOnly=true",
f"packageName={package_name}",
f"packageVersion={package_version}",
f"packageUrl={package_url}",
]),
"-i",
f"{'/local' / CWD / 'input/openapi.json'}",
"-g",
"python",
"--additional-properties="
+ ",".join(
[
"useOneOfDiscriminatorLookup=true",
# "generateSourceCodeOnly=true",
f"packageName={package_name}",
f"packageVersion={package_version}",
f"packageUrl={package_url}",
]
),
"--enable-post-process-file",
"-o", "/local/python/",
"--openapi-normalizer", "REF_AS_PARENT_IN_ALLOF=true",
"-o",
"/local/python/",
"--openapi-normalizer",
"REF_AS_PARENT_IN_ALLOF=true",
*repository_url_to_git_info_params(package_url),
],
check=True,
)
shutil.rmtree(Path("python") / "docs")


def generate_typescript_code(*, npm_name: str, npm_version: str, repository_url: str):
'''Run the generator.'''

parsed_url = urlsplit(repository_url)
(url_path, _url_ext) = os.path.splitext(parsed_url.path)
(url_path, git_repo_id) = os.path.split(url_path)
(url_path, git_user_id) = os.path.split(url_path)
"""Run the generator."""

subprocess.run(
[
"podman", "run",
"podman",
"run",
"--rm", # remove the container after running
"-v", f"{os.getcwd()}:/local",
"-v",
f"{os.getcwd()}:/local",
f"--env-file={CWD / 'override.env'}",
# "docker.io/openapitools/openapi-generator-cli:v7.0.1",
"openapi-generator-cli:patched",
"generate",
"-i", f"{'/local' / CWD / 'input/openapi.json'}",
"-g", "typescript-fetch",
"--additional-properties=" + ",".join([
"supportsES6=true",
f"npmName={npm_name}",
f"npmVersion={npm_version}",
]),
"--git-host", parsed_url.netloc,
"--git-user-id", git_user_id,
"--git-repo-id", git_repo_id,
"-i",
f"{'/local' / CWD / 'input/openapi.json'}",
"-g",
"typescript-fetch",
"--additional-properties="
+ ",".join(
[
"supportsES6=true",
f"npmName={npm_name}",
f"npmVersion={npm_version}",
]
),
*repository_url_to_git_info_params(repository_url),
"--enable-post-process-file",
"-o", "/local/typescript/",
"--openapi-normalizer", "REF_AS_PARENT_IN_ALLOF=true",
"-o",
"/local/typescript/",
"--openapi-normalizer",
"REF_AS_PARENT_IN_ALLOF=true",
],
check=True,
)
with open(Path("typescript") / ".gitignore", 'w', encoding='utf-8') as f:
f.write('''wwwroot/*.js
with open(Path("typescript") / ".gitignore", "w", encoding="utf-8") as f:
f.write("""wwwroot/*.js
node_modules
typings
''')
""")
shutil.rmtree(Path("typescript") / ".openapi-generator")


def repository_url_to_git_info_params(
repository_url: str,
) -> tuple[str, str, str, str, str, str]:
"""
Convert a repository URL to git options for the OpenAPI generator.
Returns a tuple of, e.g.,
`'--git-host', 'github.com', '--git-user-id', 'geo-engine', '--git-repo-id', 'openapi-client'`.
"""
parsed_url = urlsplit(repository_url)
(url_path, _url_ext) = os.path.splitext(parsed_url.path)
(url_path, git_repo_id) = os.path.split(url_path)
(url_path, git_user_id) = os.path.split(url_path)
return (
"--git-host",
parsed_url.netloc,
"--git-user-id",
git_user_id,
"--git-repo-id",
git_repo_id,
)


def main():
'''The entry point of the program'''
"""The entry point of the program"""

# Set up logging
logging.basicConfig(
level=logging.INFO,
format='[%(levelname)s] %(message)s'
)
logging.basicConfig(level=logging.INFO, format="[%(levelname)s] %(message)s")

args = ProgramArgs.parse_arguments()
config = ConfigArgs.parse_config()
Expand All @@ -226,14 +272,14 @@ def main():

clean_dirs(language=args.language)

if args.language == 'python':
if args.language == "python":
logging.info("Generating Python client…")
generate_python_code(
package_name=config.python_package_name,
package_version=config.package_version,
package_url=config.github_url,
)
elif args.language == 'typescript':
elif args.language == "typescript":
logging.info("Generating TypeScript client…")
generate_typescript_code(
npm_name=config.typescript_package_name,
Expand All @@ -246,20 +292,23 @@ def main():
logging.info("Creating dist files…")
subprocess.run(
[
"podman", "run",
"podman",
"run",
"--rm", # remove the container after running
"-v", f"{os.getcwd()}/typescript:/local/typescript",
"-v",
f"{os.getcwd()}/typescript:/local/typescript",
"--workdir=/local/typescript", # set working directory
"docker.io/node:lts-alpine3.20",
"npm", "install",
"npm",
"install",
],
check=True,
)
else:
raise RuntimeError(f'Unknown language {args.language}.')
raise RuntimeError(f"Unknown language {args.language}.")


if __name__ != '__main__':
raise RuntimeError('This module should not be imported.')
if __name__ != "__main__":
raise RuntimeError("This module should not be imported.")

main()