Skip to content

Commit 7e6d620

Browse files
committed
Fix generated SDK test suite argument emission, basePath API_URL resolution, and isolated pytest config
- Order call arguments in openapi_client.tests.emit.emit_operation_test to match required-then-optional parameter ordering in openapi_client.functions.emit.emit_function, emitting positional request body arguments in composable test suites and safe_param_name identifiers for named schema body parameters while preventing duplicate body parameters when both in='body' and requestBody are present. - Replace duplicate json.loads(response.data.decode('utf-8')) blocks in generated test/test_client.py with single-pass response payload deserialization (supporting both urllib3 HTTPResponse.json()/data and pre-deserialized dict/model return values) and sabotage schema assertions. - Add get_server_base_path and get_test_base_url in openapi_client.functions.utils and emit_api_tests in openapi_client.tests.emit (wired into generate_from_openapi for to_sdk and to_sdk_cli) so default API_URL fixtures in test/test_client.py and test/test_api.py preserve Swagger 2.0 basePath and OpenAPI 3.x server URL path prefixes (e.g. /v2 and /api/v3). - Emit a standalone [tool.pytest.ini_options] table with addopts = '' and explicit testpaths in openapi_client.scaffolding.scaffold_package so invoking pytest inside generated SDK packages does not inherit parent repository --cov flags.
1 parent 9f7ba04 commit 7e6d620

6 files changed

Lines changed: 404 additions & 437 deletions

File tree

‎src/openapi_client/cli.py‎

Lines changed: 42 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from pathlib import Path
1111

1212
import libcst as cst
13-
1413
from openapi_client.arazzo.emit import emit_arazzo_json
1514
from openapi_client.arazzo.parse import parse_arazzo_json, resolve_source_descriptions
1615
from openapi_client.arazzo_models import Arazzo
@@ -49,7 +48,7 @@
4948
from openapi_client.sqlalchemy_cdd.emit import emit_sqlalchemy
5049
from openapi_client.sqlalchemy_cdd.parse import parse_sqlalchemy_cdd
5150
from openapi_client.sync import sync_dir
52-
from openapi_client.tests.emit import emit_tests
51+
from openapi_client.tests.emit import emit_api_tests, emit_tests
5352
from openapi_client.tests.emit_arazzo_tests import emit_arazzo_tests_code
5453
from openapi_client.tests.emit_server_tests import emit_server_tests
5554
from openapi_client.tests.parse import extract_tests_from_ast
@@ -104,9 +103,7 @@ def get_version() -> str:
104103
return "0.0.4"
105104

106105

107-
def apply_env_vars_to_parser(
108-
parser: argparse.ArgumentParser, prefix: str = "CDD_"
109-
) -> None:
106+
def apply_env_vars_to_parser(parser: argparse.ArgumentParser, prefix: str = "CDD_") -> None:
110107
"""Recursively set argparse default values from environment variables.
111108
112109
Args:
@@ -177,28 +174,19 @@ def generate_from_openapi(
177174
src_dir.mkdir(parents=True, exist_ok=True)
178175
(src_dir / "__init__.py").touch(exist_ok=True)
179176

180-
(src_dir / "client.py").write_text(
181-
generator.generate_code(), encoding="utf-8"
182-
)
177+
(src_dir / "client.py").write_text(generator.generate_code(), encoding="utf-8")
183178
if spec.components and spec.components.schemas:
184-
(src_dir / "models.py").write_text(
185-
emit_models_module(spec.components.schemas), encoding="utf-8"
186-
)
179+
(src_dir / "models.py").write_text(emit_models_module(spec.components.schemas), encoding="utf-8")
187180

188-
(src_dir / "openapi.snapshot.json").write_text(
189-
emit_openapi_json(spec, indent=2), encoding="utf-8"
190-
)
181+
(src_dir / "openapi.snapshot.json").write_text(emit_openapi_json(spec, indent=2), encoding="utf-8")
191182

192183
if tests:
193184
test_dir = out_dir / "test"
194185
test_dir.mkdir(parents=True, exist_ok=True)
195186
(test_dir / "__init__.py").touch()
196-
(test_dir / "test_client.py").write_text(
197-
emit_tests(spec, composable=True).code, encoding="utf-8"
198-
)
199-
(test_dir / "mock_server.py").write_text(
200-
emit_mock_server(spec).code, encoding="utf-8"
201-
)
187+
(test_dir / "test_client.py").write_text(emit_tests(spec, composable=True).code, encoding="utf-8")
188+
(test_dir / "test_api.py").write_text(emit_api_tests(spec).code, encoding="utf-8")
189+
(test_dir / "mock_server.py").write_text(emit_mock_server(spec).code, encoding="utf-8")
202190
write_generated(emit_dao(spec), "dao.py", base_dir=test_dir)
203191
elif subcommand == "to_sdk_cli":
204192
generator = ClientGenerator(spec)
@@ -207,39 +195,24 @@ def generate_from_openapi(
207195
src_dir.mkdir(parents=True, exist_ok=True)
208196
(src_dir / "__init__.py").touch(exist_ok=True)
209197

210-
(src_dir / "client.py").write_text(
211-
generator.generate_code(), encoding="utf-8"
212-
)
198+
(src_dir / "client.py").write_text(generator.generate_code(), encoding="utf-8")
213199

214200
if spec.components and spec.components.schemas:
215-
(src_dir / "models.py").write_text(
216-
emit_models_module(spec.components.schemas), encoding="utf-8"
217-
)
201+
(src_dir / "models.py").write_text(emit_models_module(spec.components.schemas), encoding="utf-8")
218202

219-
(src_dir / "cli_main.py").write_text(
220-
emit_cli_sdk_cdd(spec), encoding="utf-8"
221-
)
203+
(src_dir / "cli_main.py").write_text(emit_cli_sdk_cdd(spec), encoding="utf-8")
222204
if mcp:
223-
(src_dir / "mcp_server.py").write_text(
224-
emit_mcp_server(spec), encoding="utf-8"
225-
)
226-
(src_dir / "mcp_sse_server.py").write_text(
227-
emit_mcp_sse_server(spec), encoding="utf-8"
228-
)
229-
(src_dir / "mcp_adapter.py").write_text(
230-
emit_mcp_adapter(spec), encoding="utf-8"
231-
)
205+
(src_dir / "mcp_server.py").write_text(emit_mcp_server(spec), encoding="utf-8")
206+
(src_dir / "mcp_sse_server.py").write_text(emit_mcp_sse_server(spec), encoding="utf-8")
207+
(src_dir / "mcp_adapter.py").write_text(emit_mcp_adapter(spec), encoding="utf-8")
232208

233209
if tests:
234210
test_dir = out_dir / "test"
235211
test_dir.mkdir(parents=True, exist_ok=True)
236212
(test_dir / "__init__.py").touch()
237-
(test_dir / "test_client.py").write_text(
238-
emit_tests(spec, composable=True).code, encoding="utf-8"
239-
)
240-
(test_dir / "mock_server.py").write_text(
241-
emit_mock_server(spec).code, encoding="utf-8"
242-
)
213+
(test_dir / "test_client.py").write_text(emit_tests(spec, composable=True).code, encoding="utf-8")
214+
(test_dir / "test_api.py").write_text(emit_api_tests(spec).code, encoding="utf-8")
215+
(test_dir / "mock_server.py").write_text(emit_mock_server(spec).code, encoding="utf-8")
243216
write_generated(emit_dao(spec), "dao.py", base_dir=test_dir)
244217
elif subcommand == "to_server":
245218
(out_dir / "__init__.py").touch(exist_ok=True)
@@ -317,9 +290,7 @@ def generate_from_arazzo(
317290
specs.append(
318291
(
319292
spec_path.parent,
320-
parse_arazzo_json(
321-
spec_path.read_text(encoding="utf-8"), spec_path.parent
322-
),
293+
parse_arazzo_json(spec_path.read_text(encoding="utf-8"), spec_path.parent),
323294
)
324295
)
325296
elif input_dir:
@@ -338,59 +309,39 @@ def generate_from_arazzo(
338309
src_dir = out_dir / "src"
339310
src_dir.mkdir(parents=True, exist_ok=True)
340311
(src_dir / "__init__.py").touch(exist_ok=True)
341-
(src_dir / "workflows.py").write_text(
342-
emit_workflows_code(spec, sources), encoding="utf-8"
343-
)
344-
(src_dir / "arazzo.snapshot.json").write_text(
345-
emit_arazzo_json(spec, indent=2), encoding="utf-8"
346-
)
312+
(src_dir / "workflows.py").write_text(emit_workflows_code(spec, sources), encoding="utf-8")
313+
(src_dir / "arazzo.snapshot.json").write_text(emit_arazzo_json(spec, indent=2), encoding="utf-8")
347314
if tests:
348315
test_dir = out_dir / "test"
349316
test_dir.mkdir(parents=True, exist_ok=True)
350317
(test_dir / "__init__.py").touch(exist_ok=True)
351-
(test_dir / "test_workflows.py").write_text(
352-
emit_arazzo_tests_code(spec), encoding="utf-8"
353-
)
318+
(test_dir / "test_workflows.py").write_text(emit_arazzo_tests_code(spec), encoding="utf-8")
354319
elif subcommand == "to_sdk_cli":
355320
src_dir = out_dir / "src"
356321
src_dir.mkdir(parents=True, exist_ok=True)
357322
(src_dir / "__init__.py").touch(exist_ok=True)
358-
(src_dir / "workflows.py").write_text(
359-
emit_workflows_code(spec, sources), encoding="utf-8"
360-
)
361-
(src_dir / "cli_workflows.py").write_text(
362-
emit_arazzo_cli_code(spec), encoding="utf-8"
363-
)
364-
(src_dir / "arazzo.snapshot.json").write_text(
365-
emit_arazzo_json(spec, indent=2), encoding="utf-8"
366-
)
323+
(src_dir / "workflows.py").write_text(emit_workflows_code(spec, sources), encoding="utf-8")
324+
(src_dir / "cli_workflows.py").write_text(emit_arazzo_cli_code(spec), encoding="utf-8")
325+
(src_dir / "arazzo.snapshot.json").write_text(emit_arazzo_json(spec, indent=2), encoding="utf-8")
367326
if tests:
368327
test_dir = out_dir / "test"
369328
test_dir.mkdir(parents=True, exist_ok=True)
370329
(test_dir / "__init__.py").touch(exist_ok=True)
371-
(test_dir / "test_workflows.py").write_text(
372-
emit_arazzo_tests_code(spec), encoding="utf-8"
373-
)
330+
(test_dir / "test_workflows.py").write_text(emit_arazzo_tests_code(spec), encoding="utf-8")
374331
elif subcommand == "to_tests":
375332
test_dir = out_dir / "test"
376333
test_dir.mkdir(parents=True, exist_ok=True)
377334
(test_dir / "__init__.py").touch(exist_ok=True)
378-
(test_dir / "test_workflows.py").write_text(
379-
emit_arazzo_tests_code(spec), encoding="utf-8"
380-
)
335+
(test_dir / "test_workflows.py").write_text(emit_arazzo_tests_code(spec), encoding="utf-8")
381336
else:
382337
routes_dir = out_dir / "routes"
383338
routes_dir.mkdir(parents=True, exist_ok=True)
384339
(routes_dir / "__init__.py").touch(exist_ok=True)
385-
(routes_dir / "workflows.py").write_text(
386-
emit_fastapi_arazzo_routes(spec), encoding="utf-8"
387-
)
340+
(routes_dir / "workflows.py").write_text(emit_fastapi_arazzo_routes(spec), encoding="utf-8")
388341
test_dir = out_dir / "tests"
389342
test_dir.mkdir(parents=True, exist_ok=True)
390343
(test_dir / "__init__.py").touch(exist_ok=True)
391-
(test_dir / "test_workflows.py").write_text(
392-
emit_arazzo_tests_code(spec), encoding="utf-8"
393-
)
344+
(test_dir / "test_workflows.py").write_text(emit_arazzo_tests_code(spec), encoding="utf-8")
394345

395346
if not no_installable_package:
396347
scaffold_package(out_dir, is_server=(subcommand == "to_server"))
@@ -486,11 +437,7 @@ def generate_to_openapi(input_path: str | Path, output_path: str | Path) -> None
486437
if not models_py.exists():
487438
models_py = in_path / "models.py"
488439

489-
models_dir = (
490-
in_path / "src" / "models"
491-
if (in_path / "src" / "models").is_dir()
492-
else in_path / "models"
493-
)
440+
models_dir = in_path / "src" / "models" if (in_path / "src" / "models").is_dir() else in_path / "models"
494441

495442
mock_py = in_path / "test" / "mock_server.py"
496443
if not mock_py.exists():
@@ -556,29 +503,21 @@ def generate_to_openapi(input_path: str | Path, output_path: str | Path) -> None
556503

557504
def main() -> None:
558505
"""CLI entrypoint."""
559-
parser = argparse.ArgumentParser(
560-
description="Compiler Driven Development (CDD) compiler and transpiler."
561-
)
506+
parser = argparse.ArgumentParser(description="Compiler Driven Development (CDD) compiler and transpiler.")
562507
parser.add_argument(
563508
"--version",
564509
action="version",
565510
version=get_version(),
566511
)
567512
subparsers = parser.add_subparsers(dest="command")
568513

569-
from_openapi_parser = subparsers.add_parser(
570-
"from_openapi", help="Generate code from an OpenAPI specification."
571-
)
514+
from_openapi_parser = subparsers.add_parser("from_openapi", help="Generate code from an OpenAPI specification.")
572515

573-
from_openapi_subparsers = from_openapi_parser.add_subparsers(
574-
dest="from_openapi_command", required=True
575-
)
516+
from_openapi_subparsers = from_openapi_parser.add_subparsers(dest="from_openapi_command", required=True)
576517

577518
for subcmd in ["to_sdk", "to_sdk_cli", "to_server"]:
578519
if subcmd == "to_sdk_cli":
579-
help_text = (
580-
"Generate a client SDK with a CLI from an OpenAPI specification."
581-
)
520+
help_text = "Generate a client SDK with a CLI from an OpenAPI specification."
582521
elif subcmd == "to_sdk":
583522
help_text = "Generate a client SDK from an OpenAPI specification."
584523
else:
@@ -653,12 +592,8 @@ def main() -> None:
653592
help="Output file or directory path",
654593
)
655594

656-
from_arazzo_parser = subparsers.add_parser(
657-
"from_arazzo", help="Generate code from an Arazzo 1.1.0 specification."
658-
)
659-
from_arazzo_subparsers = from_arazzo_parser.add_subparsers(
660-
dest="from_arazzo_command", required=True
661-
)
595+
from_arazzo_parser = subparsers.add_parser("from_arazzo", help="Generate code from an Arazzo 1.1.0 specification.")
596+
from_arazzo_subparsers = from_arazzo_parser.add_subparsers(dest="from_arazzo_command", required=True)
662597
for ar_subcmd in ["to_sdk", "to_sdk_cli", "to_server", "to_tests"]:
663598
ar_p = from_arazzo_subparsers.add_parser(
664599
ar_subcmd,
@@ -759,30 +694,18 @@ def main() -> None:
759694
required=True,
760695
help="Path or URL to the OpenAPI specification.",
761696
)
762-
docs_parser.add_argument(
763-
"--no-imports", action="store_true", help="Omit the imports field."
764-
)
765-
docs_parser.add_argument(
766-
"--no-wrapping", action="store_true", help="Omit the wrapper fields."
767-
)
768-
docs_parser.add_argument(
769-
"-o", "--output", type=str, help="Output file or directory path."
770-
)
697+
docs_parser.add_argument("--no-imports", action="store_true", help="Omit the imports field.")
698+
docs_parser.add_argument("--no-wrapping", action="store_true", help="Omit the wrapper fields.")
699+
docs_parser.add_argument("-o", "--output", type=str, help="Output file or directory path.")
771700

772701
server_parser = subparsers.add_parser(
773702
"serve_json_rpc",
774703
help="Expose CLI interface as a JSON-RPC server.",
775704
)
776-
server_parser.add_argument(
777-
"-p", "--port", type=int, default=8080, help="Port to listen on"
778-
)
779-
server_parser.add_argument(
780-
"-l", "--listen", type=str, default="127.0.0.1", help="Address to listen on"
781-
)
705+
server_parser.add_argument("-p", "--port", type=int, default=8080, help="Port to listen on")
706+
server_parser.add_argument("-l", "--listen", type=str, default="127.0.0.1", help="Address to listen on")
782707

783-
subparsers.add_parser(
784-
"mcp", help="Run the Model Context Protocol server via stdio."
785-
)
708+
subparsers.add_parser("mcp", help="Run the Model Context Protocol server via stdio.")
786709

787710
apply_env_vars_to_parser(parser)
788711
args = parser.parse_args()
@@ -792,9 +715,7 @@ def main() -> None:
792715
sys.exit(0)
793716

794717
if args.command == "sync":
795-
sync_dir(
796-
args.input, getattr(args, "truth", None), getattr(args, "output", None)
797-
)
718+
sync_dir(args.input, getattr(args, "truth", None), getattr(args, "output", None))
798719
elif args.command == "from_openapi":
799720
subcmd = args.from_openapi_command
800721
if not args.input and not getattr(args, "input_dir", None):

0 commit comments

Comments
 (0)