otterapi 0.0.0__tar.gz → 0.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: otterapi
3
- Version: 0.0.0
3
+ Version: 0.0.2
4
4
  Summary: A cute little companion that generates type-safe clients from OpenAPI documents.
5
5
  Project-URL: Source, https://github.com/danplischke/otter
6
6
  Author: Dan Plischke
File without changes
@@ -0,0 +1,4 @@
1
+ from otterapi.cli import app
2
+
3
+ if __name__ == '__main__':
4
+ app()
@@ -0,0 +1,84 @@
1
+ from typing import Annotated
2
+
3
+ import typer
4
+ from rich.console import Console
5
+ from rich.progress import Progress, SpinnerColumn, TextColumn
6
+
7
+ from otterapi.codegen.generator import Codegen
8
+ from otterapi.config import get_config
9
+
10
+ console = Console()
11
+ app = typer.Typer(
12
+ name='otterapi',
13
+ help='Generate Python client code from OpenAPI specifications',
14
+ no_args_is_help=True,
15
+ )
16
+
17
+
18
+ @app.command()
19
+ def generate(
20
+ config: Annotated[
21
+ str | None,
22
+ typer.Option(
23
+ '--config', '-c', help='Path to configuration file (YAML or JSON)'
24
+ ),
25
+ ] = None,
26
+ ) -> None:
27
+ """Generate Python client code from configuration.
28
+
29
+ If no config file is specified, will look for default config files
30
+ in the current directory or use environment variables.
31
+
32
+ Examples:
33
+ otterapi generate
34
+ otterapi generate --config my-config.yaml
35
+ otterapi generate -c config.json
36
+ """
37
+ config = get_config(config)
38
+
39
+ try:
40
+ with Progress(
41
+ SpinnerColumn(),
42
+ TextColumn('[progress.description]{task.description}'),
43
+ console=console,
44
+ ) as progress:
45
+ for document_config in config.documents:
46
+ task = progress.add_task(
47
+ f'Generating code for {document_config.source} in {document_config.output}...',
48
+ total=None,
49
+ )
50
+
51
+ codegen = Codegen(document_config)
52
+ codegen.generate()
53
+
54
+ console.print(
55
+ f"[green]✓[/green] Successfully generated code in '{document_config.output}'"
56
+ )
57
+ console.print('[dim]Generated files:[/dim]')
58
+ console.print(
59
+ f' - {document_config.output}/{document_config.models_file}'
60
+ )
61
+ console.print(
62
+ f' - {document_config.output}/{document_config.endpoints_file}'
63
+ )
64
+
65
+ progress.update(task, description='Code generation completed!')
66
+
67
+ except Exception as e:
68
+ console.print(f'[red]Error:[/red] {str(e)}')
69
+ raise typer.Exit(1)
70
+
71
+
72
+ @app.command()
73
+ def version() -> None:
74
+ """Show the version of otterapi."""
75
+ try:
76
+ from otterapi._version import version
77
+
78
+ console.print(f'otterapi version: {version}')
79
+ except ImportError:
80
+ console.print('otterapi version: unknown')
81
+
82
+
83
+ if __name__ == '__main__':
84
+ generate('/Users/PLISCD/Downloads/otterapi.yml')
File without changes
@@ -0,0 +1,118 @@
1
+ import ast
2
+ from collections.abc import Iterable
3
+
4
+
5
+ def _name(name: str) -> ast.Name:
6
+ return ast.Name(id=name, ctx=ast.Load())
7
+
8
+
9
+ def _attr(value: str | ast.expr, attr: str) -> ast.Attribute:
10
+ return ast.Attribute(
11
+ value=_name(value) if isinstance(value, str) else value, attr=attr
12
+ )
13
+
14
+
15
+ def _subscript(generic: str, inner: ast.expr) -> ast.Subscript:
16
+ return ast.Subscript(value=_name(generic), slice=inner)
17
+
18
+
19
+ def _union_expr(types: list[ast.expr]) -> ast.Subscript:
20
+ # Union[A, B, C]
21
+ return _subscript('Union', ast.Tuple(elts=types))
22
+
23
+
24
+ def _optional_expr(inner: ast.expr) -> ast.Subscript:
25
+ return _subscript('Optional', inner)
26
+
27
+
28
+ def _argument(name: str, value: ast.expr | None = None) -> ast.arg:
29
+ return ast.arg(
30
+ arg=name,
31
+ annotation=value,
32
+ )
33
+
34
+
35
+ def _assign(target: ast.expr, value: ast.expr) -> ast.Assign:
36
+ return ast.Assign(
37
+ targets=[target],
38
+ value=value,
39
+ )
40
+
41
+
42
+ def _import(module: str, names: list[str]) -> ast.ImportFrom:
43
+ return ast.ImportFrom(
44
+ module=module,
45
+ names=[ast.alias(name=name) for name in names],
46
+ level=0,
47
+ )
48
+
49
+
50
+ def _call(
51
+ func: ast.expr,
52
+ args: list[ast.expr] | None = None,
53
+ keywords: list[ast.keyword] | None = None,
54
+ ) -> ast.Call:
55
+ return ast.Call(
56
+ func=func,
57
+ args=args or [],
58
+ keywords=keywords or [],
59
+ )
60
+
61
+
62
+ def _func(
63
+ name: str,
64
+ args: list[ast.arg],
65
+ body: list[ast.stmt],
66
+ returns: ast.expr | None = None,
67
+ kwargs: ast.arg = None,
68
+ kwonlyargs: list[ast.arg] = None,
69
+ kw_defaults: list[ast.expr] = None,
70
+ ) -> ast.FunctionDef:
71
+ return ast.FunctionDef(
72
+ name=name,
73
+ args=ast.arguments(
74
+ posonlyargs=[],
75
+ args=args,
76
+ kwarg=kwargs,
77
+ kwonlyargs=kwonlyargs or [],
78
+ kw_defaults=kw_defaults or [],
79
+ defaults=[],
80
+ ),
81
+ body=body,
82
+ decorator_list=[],
83
+ returns=returns,
84
+ )
85
+
86
+
87
+ def _async_func(
88
+ name: str,
89
+ args: list[ast.arg],
90
+ body: list[ast.stmt],
91
+ returns: ast.expr | None = None,
92
+ kwargs: ast.arg = None,
93
+ kwonlyargs: list[ast.arg] = None,
94
+ kw_defaults: list[ast.expr] = None,
95
+ ) -> ast.AsyncFunctionDef:
96
+ return ast.AsyncFunctionDef(
97
+ name=name,
98
+ args=ast.arguments(
99
+ posonlyargs=[],
100
+ args=args,
101
+ kwarg=kwargs,
102
+ kwonlyargs=kwonlyargs or [],
103
+ kw_defaults=kw_defaults or [],
104
+ defaults=[],
105
+ ),
106
+ body=body,
107
+ decorator_list=[],
108
+ returns=returns,
109
+ )
110
+
111
+
112
+ def _all(names: Iterable[str]) -> ast.Assign:
113
+ return _assign(
114
+ target=_name('__all__'),
115
+ value=ast.Tuple(
116
+ elts=[ast.Constant(value=name) for name in names], ctx=ast.Load()
117
+ ),
118
+ )