Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 323805d

Browse files
authored
Emit Python namespace modules
1 parent dbebabb commit 323805d

58 files changed

Lines changed: 2863 additions & 1840 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/src/architecture.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ ReflectAPI includes special API-facing types whose semantics matter to codegen:
120120
- `reflectapi::Infallible`: explicit “no error payload” type
121121

122122
The Python backend treats these as runtime-provided abstractions rather than generated models.
123+
Generated Python clients use real package modules for reflected namespaces; the
124+
flat `generated.py` file is only a compatibility facade over those modules.
123125

124126
## Testing and Validation
125127

docs/src/clients/README.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
|--------|--------|-------|
99
| TypeScript | Stable | Two generated files: API surface + transport contract |
1010
| Rust | Stable | Single generated file |
11-
| Python | Experimental | Package-style output with `__init__.py` and `generated.py` |
11+
| Python | Experimental | Package-style output with real namespace submodules |
1212

1313
OpenAPI generation is also supported by the CLI, but it is documented separately as an API description format rather than a client library.
1414

@@ -33,11 +33,13 @@ cargo run --bin reflectapi -- codegen \
3333
--schema reflectapi.json \
3434
--output clients/typescript/
3535

36-
# Generate Python client -> clients/python/__init__.py and generated.py
36+
# Generate Python client -> clients/python/api_client/__init__.py,
37+
# clients/python/api_client/_client.py, and namespace packages such as
38+
# clients/python/api_client/myapi/model/
3739
cargo run --bin reflectapi -- codegen \
3840
--language python \
3941
--schema reflectapi.json \
40-
--output clients/python/ \
42+
--output clients/python/api_client/ \
4143
--python-sync
4244

4345
# Generate Rust client -> clients/rust/generated.rs
@@ -57,7 +59,7 @@ The generators do not all emit the same file layout:
5759
|--------|--------------------------------|
5860
| TypeScript | `generated.ts`, `generated.transport.ts` |
5961
| Rust | `generated.rs` |
60-
| Python | `__init__.py`, `generated.py` |
62+
| Python | A package directory containing `__init__.py`, `generated.py`, `_client.py`, `_rebuild.py`, and namespace package files |
6163

6264
The demo repository includes extra project scaffolding around some generated clients, but that scaffolding is not produced by `reflectapi codegen` itself.
6365

@@ -82,6 +84,8 @@ The demo repository includes extra project scaffolding around some generated cli
8284
- Generates Pydantic-based models and client code.
8385
- Generates an async client by default.
8486
- Adds a sync client only when `--python-sync` is passed.
87+
- Emits reflected Rust namespaces as real Python packages. `generated.py` is kept
88+
as a temporary compatibility facade inside the package.
8589
- Uses `reflectapi_runtime` for client base classes and runtime helpers.
8690

8791
### Rust

reflectapi-cli/src/main.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,10 @@ fn main() -> anyhow::Result<()> {
253253
"Failed to create output directory: {output_path:?}"
254254
))?;
255255
for (filename, content) in &files {
256-
write_file(&output_path.join(filename), content)?;
256+
write_file(
257+
&output_path.join(generated_relative_path(filename)?),
258+
content,
259+
)?;
257260
}
258261
} else {
259262
let parent = parent_or_dot(&output_path);
@@ -263,7 +266,7 @@ fn main() -> anyhow::Result<()> {
263266
let dest = if filename == primary_name_in_path {
264267
output_path.clone()
265268
} else {
266-
parent.join(filename)
269+
parent.join(generated_relative_path(filename)?)
267270
};
268271
write_file(&dest, content)?;
269272
}
@@ -280,7 +283,23 @@ fn parent_or_dot(path: &std::path::Path) -> std::path::PathBuf {
280283
.unwrap_or_else(|| std::path::PathBuf::from("."))
281284
}
282285

286+
fn generated_relative_path(filename: &str) -> anyhow::Result<&std::path::Path> {
287+
let relative_path = std::path::Path::new(filename);
288+
anyhow::ensure!(
289+
relative_path.is_relative()
290+
&& !relative_path
291+
.components()
292+
.any(|component| matches!(component, std::path::Component::ParentDir)),
293+
"Generated file path must be relative and stay within output directory: {filename}"
294+
);
295+
Ok(relative_path)
296+
}
297+
283298
fn write_file(path: &std::path::Path, content: &str) -> anyhow::Result<()> {
299+
if let Some(parent) = path.parent() {
300+
std::fs::create_dir_all(parent)
301+
.context(format!("Failed to create output directory: {parent:?}"))?;
302+
}
284303
let mut file =
285304
std::fs::File::create(path).context(format!("Failed to create file: {path:?}"))?;
286305
file.write_all(content.as_bytes())

reflectapi-cli/tests/output_paths.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,10 +144,11 @@ fn python_stdout_emits_generated_not_init() {
144144
]);
145145
assert!(out.status.success());
146146
let stdout = String::from_utf8_lossy(&out.stdout);
147-
// __init__.py is a 5-line shim; generated.py contains the actual
148-
// pydantic models / client classes.
147+
// __init__.py is a package-root shim; generated.py is the flat
148+
// compatibility facade and should include model re-exports.
149149
assert!(
150-
stdout.contains("AsyncClientBase") || stdout.contains("class "),
151-
"expected stdout to be generated.py. got:\n{stdout}",
150+
stdout.contains("from ._rebuild import rebuild_models")
151+
&& stdout.contains("from .myapi.proto import"),
152+
"expected stdout to be generated.py compatibility facade. got:\n{stdout}",
152153
);
153154
}

reflectapi-demo/clients/python/__init__.py

Lines changed: 0 additions & 5 deletions
This file was deleted.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
"api_client client generated by ReflectAPI."
2+
3+
from ._client import AsyncClient, Client
4+
5+
from . import myapi
6+
7+
from ._rebuild import rebuild_models as _rebuild_models
8+
9+
_rebuild_models()
10+
11+
__all__ = ["AsyncClient", "Client", "myapi"]

0 commit comments

Comments
 (0)