pyrpc-codegen 0.2.0__tar.gz → 0.3.1__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.
@@ -152,3 +152,7 @@ cython_debug/
152
152
  # Node modules
153
153
  node_modules
154
154
  dist
155
+
156
+
157
+ # System design docs (local developer documentation)
158
+ system-design/
@@ -0,0 +1,7 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyrpc-codegen
3
+ Version: 0.3.1
4
+ Summary: Codegen and CLI tools for pyRPC
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: jinja2>=3.1.0
7
+ Requires-Dist: jsonschema-ts>=0.1.0
@@ -1,20 +1,13 @@
1
1
  [project]
2
2
  name = "pyrpc-codegen"
3
- version = "0.2.0"
3
+ version = "0.3.1"
4
4
  description = "Codegen and CLI tools for pyRPC"
5
5
  requires-python = ">=3.11"
6
6
  dependencies = [
7
- "pyrpc-core",
8
- "typer[all]>=0.9.0",
9
- "rich>=13.0.0",
10
7
  "jinja2>=3.1.0",
11
- "uvicorn>=0.23.0",
12
- "httpx>=0.28.1",
8
+ "jsonschema-ts>=0.1.0",
13
9
  ]
14
10
 
15
- [project.scripts]
16
- pyrpc = "pyrpc_codegen.main:app"
17
-
18
11
  [build-system]
19
12
  requires = ["hatchling"]
20
13
  build-backend = "hatchling.build"
@@ -0,0 +1,3 @@
1
+ from .ts_codegen import DEFAULT_OUTPUT, generate_typescript_client, save_typescript_client
2
+
3
+ __all__ = ["DEFAULT_OUTPUT", "generate_typescript_client", "save_typescript_client"]
@@ -11,7 +11,9 @@
11
11
  * const result = await client.someProcedure(42)
12
12
  * ```
13
13
  */
14
+ /* eslint-disable */
14
15
 
16
+ // ── Procedures ──────────────────────────────────────────
15
17
  export interface Types {
16
18
  {% for name, schema in schemas.items() %}
17
19
  /**
@@ -4,6 +4,8 @@ from pathlib import Path
4
4
  from typing import Any, Dict
5
5
 
6
6
  from jinja2 import Environment, FileSystemLoader
7
+ from jsonschema_ts import Options as JsonschemaTsOptions
8
+ from jsonschema_ts import assemble, collect_defs, convert_all
7
9
 
8
10
  DEFAULT_OUTPUT = "node_modules/@pyrpc/types/src/index.ts"
9
11
 
@@ -96,14 +98,47 @@ def _return_type_to_ts(return_type: str) -> str:
96
98
  return _pytype_to_ts(return_type)
97
99
 
98
100
 
101
+ def _collect_schema_defs(schemas: Dict[str, Any]) -> dict:
102
+ schema_sources = []
103
+ for _name, schema in schemas.items():
104
+ if isinstance(schema, dict):
105
+ params = schema.get("parameters", [])
106
+ else:
107
+ params = schema.parameters
108
+ for param in params:
109
+ js = param.get("schema") if isinstance(param, dict) else param.schema_
110
+ if js:
111
+ schema_sources.append(js)
112
+ rs = schema.get("return_schema") if isinstance(schema, dict) else schema.return_schema
113
+ if rs:
114
+ schema_sources.append(rs)
115
+ return collect_defs(*schema_sources)
116
+
117
+
99
118
  def generate_typescript_client(schemas: Dict[str, Any]) -> str:
119
+ defs = _collect_schema_defs(schemas)
120
+
121
+ opts = JsonschemaTsOptions(
122
+ banner_comment="",
123
+ format=True,
124
+ unknown_any=True,
125
+ )
126
+
127
+ model_interfaces = convert_all(defs, opts=opts)
128
+
100
129
  template_dir = Path(__file__).parent / "templates"
101
130
  env = Environment(loader=FileSystemLoader(template_dir))
102
131
  env.filters["pytype_to_ts"] = _pytype_to_ts
103
132
  env.filters["return_type_to_ts"] = _return_type_to_ts
104
133
  template = env.get_template("client.ts.j2")
105
134
 
106
- return template.render(schemas=schemas)
135
+ procedure_types = template.render(schemas=schemas)
136
+
137
+ return assemble(
138
+ models=model_interfaces,
139
+ procedures=procedure_types,
140
+ banner="",
141
+ )
107
142
 
108
143
 
109
144
  def save_typescript_client(schemas: Dict[str, Any], output_path: str = DEFAULT_OUTPUT):
@@ -1,11 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: pyrpc-codegen
3
- Version: 0.2.0
4
- Summary: Codegen and CLI tools for pyRPC
5
- Requires-Python: >=3.11
6
- Requires-Dist: httpx>=0.28.1
7
- Requires-Dist: jinja2>=3.1.0
8
- Requires-Dist: pyrpc-core
9
- Requires-Dist: rich>=13.0.0
10
- Requires-Dist: typer[all]>=0.9.0
11
- Requires-Dist: uvicorn>=0.23.0
@@ -1,4 +0,0 @@
1
- from .ts_codegen import generate_typescript_client, save_typescript_client
2
- from .main import app
3
-
4
- __all__ = ["generate_typescript_client", "save_typescript_client", "app"]
@@ -1,178 +0,0 @@
1
- import importlib
2
- import json
3
- import os
4
- import sys
5
-
6
- import typer
7
- from rich.console import Console
8
- from rich.panel import Panel
9
- from rich.table import Table
10
-
11
- from .ts_codegen import save_typescript_client, DEFAULT_OUTPUT
12
-
13
-
14
- __version__ = "0.2.0"
15
-
16
-
17
- app = typer.Typer(
18
- name="pyrpc",
19
- help="pyRPC CLI - type-safe Python-to-TypeScript RPC",
20
- add_completion=False,
21
- )
22
- console = Console()
23
-
24
-
25
- def _lazy_import_pyrpc_core():
26
- """Import pyrpc-core lazily so codegen-only usage avoids the dep."""
27
- global default_router, get_registry_schema
28
- from pyrpc_core import default_router, get_registry_schema
29
- return default_router, get_registry_schema
30
-
31
-
32
- def _import_module(module_path: str):
33
- sys.path.append(os.getcwd())
34
- try:
35
- return importlib.import_module(module_path)
36
- except ImportError as e:
37
- console.print(f"[bold red]Error:[/bold red] Could not import module '{module_path}': {e}")
38
- raise typer.Exit(code=1)
39
-
40
-
41
- def _fetch_schema(url: str) -> dict:
42
- import httpx
43
- clean_url = url.rstrip("/")
44
- if not clean_url.endswith("/rpc"):
45
- clean_url += "/rpc"
46
- response = httpx.get(clean_url)
47
- response.raise_for_status()
48
- return response.json()
49
-
50
-
51
- def _load_schema(path_or_url: str) -> dict:
52
- if path_or_url.startswith("http://") or path_or_url.startswith("https://"):
53
- console.print(f"Fetching schema from [bold yellow]{path_or_url}[/bold yellow]...")
54
- return _fetch_schema(path_or_url)
55
- path = os.path.abspath(path_or_url)
56
- console.print(f"Reading schema from [bold yellow]{path}[/bold yellow]...")
57
- with open(path, "r") as f:
58
- return json.load(f)
59
-
60
-
61
- @app.command()
62
- def version():
63
- """Show pyRPC version."""
64
- console.print(f"pyRPC version: [bold cyan]{__version__}[/bold cyan]")
65
-
66
-
67
- @app.command()
68
- def pull(
69
- module: str = typer.Argument(..., help="Python module path (e.g. 'app.main')"),
70
- output: str = typer.Option("pyrpc-schema.json", "--output", "-o", help="Output JSON schema file path"),
71
- ):
72
- """Extract RPC schema from a Python module and save as JSON."""
73
- _lazy_import_pyrpc_core()
74
- _import_module(module)
75
-
76
- schemas = get_registry_schema(default_router)
77
-
78
- if not schemas:
79
- console.print("[yellow]No procedures found in registry for this module.[/yellow]")
80
- raise typer.Exit(code=1)
81
-
82
- serializable = {}
83
- for name, schema in schemas.items():
84
- serializable[name] = {
85
- "name": schema.name,
86
- "doc": schema.doc or "",
87
- "parameters": [
88
- {"name": p.name, "type": p.type, "required": p.required, "default": p.default}
89
- for p in schema.parameters
90
- ],
91
- "return_type": schema.return_type,
92
- }
93
-
94
- output_path = os.path.abspath(output)
95
- os.makedirs(os.path.dirname(output_path) or ".", exist_ok=True)
96
- with open(output_path, "w") as f:
97
- json.dump(serializable, f, indent=2)
98
-
99
- console.print(f"[bold green]OK Schema extracted to {output_path}[/bold green]")
100
- console.print(f" ({len(serializable)} procedure(s) written)")
101
-
102
-
103
- @app.command()
104
- def serve(
105
- module: str = typer.Argument(..., help="Module containing the pyRPC application (e.g. 'app.main')"),
106
- host: str = typer.Option("127.0.0.1", "--host", "-h", help="Bind socket to this host"),
107
- port: int = typer.Option(8000, "--port", "-p", help="Bind socket to this port"),
108
- reload: bool = typer.Option(False, "--reload", help="Enable auto-reload"),
109
- ):
110
- """Start the pyRPC ASGI server."""
111
- _lazy_import_pyrpc_core()
112
- import uvicorn
113
- _import_module(module)
114
-
115
- console.print(Panel(
116
- f"Starting pyRPC server for [bold cyan]{module}[/bold cyan]\n"
117
- f"Endpoint: [bold green]http://{host}:{port}/rpc[/bold green]",
118
- title="pyRPC Serve",
119
- border_style="blue"
120
- ))
121
-
122
- uvicorn.run("pyrpc:asgi_app", host=host, port=port, reload=reload)
123
-
124
-
125
- @app.command()
126
- def codegen(
127
- source: str = typer.Argument(..., help="Schema JSON file path or URL of a running pyRPC server (e.g. pyrpc-schema.json or http://localhost:8000)"),
128
- output: str = typer.Option(DEFAULT_OUTPUT, "--output", "-o", help="Output file path for generated types"),
129
- ):
130
- """Generate TypeScript type definitions from a schema file or a running server."""
131
- try:
132
- schemas = _load_schema(source)
133
- except Exception as e:
134
- console.print(f"[bold red]Error:[/bold red] Could not load schema from '{source}': {e}")
135
- raise typer.Exit(code=1)
136
-
137
- console.print(f"Generating TypeScript contracts [dim]({len(schemas)} procedures)[/dim]...")
138
- save_typescript_client(schemas, output)
139
- console.print(f"[bold green]OK Types written to {output}[/bold green]")
140
-
141
- if os.path.exists(output):
142
- console.print(f" Import: [bold]import type {{ Types }} from \"@pyrpc/types\"[/bold]")
143
-
144
-
145
- @app.command()
146
- def inspect(
147
- module: str = typer.Argument(..., help="Module to inspect")
148
- ):
149
- """List all registered RPC procedures in a module."""
150
- _lazy_import_pyrpc_core()
151
- _import_module(module)
152
-
153
- schemas = get_registry_schema(default_router)
154
-
155
- if not schemas:
156
- console.print("[yellow]No procedures found in registry for this module.[/yellow]")
157
- return
158
-
159
- table = Table(title=f"pyRPC Registry: {module}")
160
- table.add_column("Method", style="cyan")
161
- table.add_column("Params", style="green")
162
- table.add_column("Returns", style="magenta")
163
- table.add_column("Doc", style="white", no_wrap=False)
164
-
165
- for name, schema in schemas.items():
166
- params = ", ".join([f"{p.name}: {p.type}" for p in schema.parameters])
167
- table.add_row(
168
- name,
169
- params or "None",
170
- schema.return_type,
171
- schema.doc or ""
172
- )
173
-
174
- console.print(table)
175
-
176
-
177
- if __name__ == "__main__":
178
- app()