Skip to content
Merged
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
10 changes: 10 additions & 0 deletions rust/kcl-python-bindings/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ Omitted values use the client's existing environment defaults (`ZOO_API_TOKEN`
or `KITTYCAD_API_TOKEN` for the token; `ZOO_HOST` or `KITTYCAD_HOST` for the API
origin). Mock sessions do not create an API client.

For exports and measurements that do not need images, pass `geometry_only=True`
to either constructor. This selects the CPU engine pool and skips rendering setup:

```python
async with await kcl.new_kcl_session(
"main.kcl", geometry_only=True, token=api_token, base_url=api_origin
) as session:
files = await session.export(kcl.FileExportFormat.Step)
```

`session.outcome` is an `ExecOutcome` with the same diagnostics, constraint reports,
and sketch rendering methods returned by `execute()`. Accessing it shares the
saved result without copying the execution state or running KCL again. Use
Expand Down
6 changes: 4 additions & 2 deletions rust/kcl-python-bindings/kcl.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -1455,20 +1455,22 @@ async def mock_execute_code(code: builtins.str) -> zooExecOutcome:
Mock execute the kcl code.
"""

async def new_kcl_session(path: builtins.str, *, mock: builtins.bool = ..., highlight_edges: typing.Optional[builtins.bool] = None, video_res_width: typing.Optional[builtins.int] = None, video_res_height: typing.Optional[builtins.int] = None, token: typing.Optional[builtins.str] = None, base_url: typing.Optional[builtins.str] = None) -> KclSession:
async def new_kcl_session(path: builtins.str, *, mock: builtins.bool = ..., geometry_only: builtins.bool = ..., highlight_edges: typing.Optional[builtins.bool] = None, video_res_width: typing.Optional[builtins.int] = None, video_res_height: typing.Optional[builtins.int] = None, token: typing.Optional[builtins.str] = None, base_url: typing.Optional[builtins.str] = None) -> KclSession:
r"""
Execute this KCL project.
Return an executed KCL project with its connection still available.
You can call follow-up methods, like exporting or snapshotting or measuring, on the returned session.
`geometry_only` uses the CPU engine pool without initializing rendering.
`token` and `base_url` override the client environment settings for this session.
Omitted values retain the client's existing environment defaults.
"""

async def new_kcl_session_code(code: builtins.str, *, mock: builtins.bool = ..., highlight_edges: typing.Optional[builtins.bool] = None, video_res_width: typing.Optional[builtins.int] = None, video_res_height: typing.Optional[builtins.int] = None, token: typing.Optional[builtins.str] = None, base_url: typing.Optional[builtins.str] = None) -> KclSession:
async def new_kcl_session_code(code: builtins.str, *, mock: builtins.bool = ..., geometry_only: builtins.bool = ..., highlight_edges: typing.Optional[builtins.bool] = None, video_res_width: typing.Optional[builtins.int] = None, video_res_height: typing.Optional[builtins.int] = None, token: typing.Optional[builtins.str] = None, base_url: typing.Optional[builtins.str] = None) -> KclSession:
r"""
Execute this KCL source code string.
Return an executed KCL project with its connection still available.
You can call follow-up methods, like exporting or snapshotting or measuring, on the returned session.
`geometry_only` uses the CPU engine pool without initializing rendering.
`token` and `base_url` override the client environment settings for this session.
Omitted values retain the client's existing environment defaults.
"""
Expand Down
74 changes: 35 additions & 39 deletions rust/kcl-python-bindings/src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,14 +195,17 @@ impl KclSession {
/// Execute this KCL project.
/// Return an executed KCL project with its connection still available.
/// You can call follow-up methods, like exporting or snapshotting or measuring, on the returned session.
/// `geometry_only` uses the CPU engine pool without initializing rendering.
/// `token` and `base_url` override the client environment settings for this session.
/// Omitted values retain the client's existing environment defaults.
#[pyo3_stub_gen::derive::gen_stub_pyfunction]
#[gen_stub(override_return_type(type_repr = "KclSession"))]
#[pyfunction(signature = (path, *, mock=false, highlight_edges=None, video_res_width=None, video_res_height=None, token=None, base_url=None))]
#[pyfunction(signature = (path, *, mock=false, geometry_only=false, highlight_edges=None, video_res_width=None, video_res_height=None, token=None, base_url=None))]
#[allow(clippy::too_many_arguments)] // Independent Python keyword options.
pub async fn new_kcl_session(
path: String,
mock: bool,
geometry_only: bool,
highlight_edges: Option<bool>,
video_res_width: Option<u32>,
video_res_height: Option<u32>,
Expand All @@ -213,12 +216,16 @@ pub async fn new_kcl_session(
spawn_py(async move {
new_kcl_session_impl(
input,
mock,
highlight_edges,
video_res_width,
video_res_height,
token,
base_url,
crate::ContextParams {
mock,
geometry_only,
highlight_edges,
video_res_width,
video_res_height,
token,
base_url,
..Default::default()
},
)
.await
})
Expand All @@ -228,14 +235,17 @@ pub async fn new_kcl_session(
/// Execute this KCL source code string.
/// Return an executed KCL project with its connection still available.
/// You can call follow-up methods, like exporting or snapshotting or measuring, on the returned session.
/// `geometry_only` uses the CPU engine pool without initializing rendering.
/// `token` and `base_url` override the client environment settings for this session.
/// Omitted values retain the client's existing environment defaults.
#[pyo3_stub_gen::derive::gen_stub_pyfunction]
#[gen_stub(override_return_type(type_repr = "KclSession"))]
#[pyfunction(signature = (code, *, mock=false, highlight_edges=None, video_res_width=None, video_res_height=None, token=None, base_url=None))]
#[pyfunction(signature = (code, *, mock=false, geometry_only=false, highlight_edges=None, video_res_width=None, video_res_height=None, token=None, base_url=None))]
#[allow(clippy::too_many_arguments)] // Independent Python keyword options.
pub async fn new_kcl_session_code(
code: String,
mock: bool,
geometry_only: bool,
highlight_edges: Option<bool>,
video_res_width: Option<u32>,
video_res_height: Option<u32>,
Expand All @@ -246,12 +256,16 @@ pub async fn new_kcl_session_code(
spawn_py(async move {
new_kcl_session_impl(
input,
mock,
highlight_edges,
video_res_width,
video_res_height,
token,
base_url,
crate::ContextParams {
mock,
geometry_only,
highlight_edges,
video_res_width,
video_res_height,
token,
base_url,
..Default::default()
},
)
.await
})
Expand All @@ -260,15 +274,7 @@ pub async fn new_kcl_session_code(

/// Execute this KCL project.
/// Return an executed KCL project with its connection still available.
pub async fn new_kcl_session_impl(
input: KclInput,
mock: bool,
highlight_edges: Option<bool>,
video_res_width: Option<u32>,
video_res_height: Option<u32>,
token: Option<String>,
base_url: Option<String>,
) -> PyResult<KclSession> {
async fn new_kcl_session_impl(input: KclInput, mut params: crate::ContextParams) -> PyResult<KclSession> {
// I/O or parse failures should raise an exception.
// There's no more useful data to include.
// So it's fine to use ? here.
Expand All @@ -278,6 +284,7 @@ pub async fn new_kcl_session_impl(
path,
filename,
} = load_and_parse(input).await?;
params.current_file = path;

// Connect to the engine.
// If you can't even connect to the engine, just raise an exception.
Expand All @@ -286,16 +293,7 @@ pub async fn new_kcl_session_impl(
program
.language_version()
.map_err(|err| into_miette_for_parse(&filename, &code, err))?,
crate::ContextParams {
current_file: path,
mock,
highlight_edges,
geometry_only: false,
video_res_width,
video_res_height,
token,
base_url,
},
params,
)
.await
.map_err(to_py_exception)?;
Expand Down Expand Up @@ -348,12 +346,10 @@ mod tests {
async fn follow_up_error_drains_responses_and_close_releases_context() {
let mut session = new_kcl_session_impl(
KclInput::Code("@settings(kclVersion = 2.0)\nvalue = 1".to_owned()),
true,
None,
None,
None,
None,
None,
crate::ContextParams {
mock: true,
..Default::default()
},
)
.await
.unwrap();
Expand Down
25 changes: 21 additions & 4 deletions rust/kcl-python-bindings/tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
import sys
from http.server import BaseHTTPRequestHandler, HTTPServer
from threading import Thread

import pytest
from urllib.parse import parse_qs, urlsplit

import kcl
import pytest
from kcl import Point3d

# Get the path to this script's parent directory.
Expand Down Expand Up @@ -185,8 +185,13 @@

@pytest.mark.asyncio
@pytest.mark.parametrize("environment_configured", [False, True])
@pytest.mark.parametrize("geometry_only", [False, True])
async def test_kcl_session_explicit_credentials(
session_constructor, modeling_api, monkeypatch, environment_configured
session_constructor,
modeling_api,
monkeypatch,
environment_configured,
geometry_only,
):
for name in ("ZOO_API_TOKEN", "KITTYCAD_API_TOKEN", "ZOO_HOST", "KITTYCAD_HOST"):
monkeypatch.delenv(name, raising=False)
Expand All @@ -202,7 +207,12 @@
async def connect(token):
async with asyncio.timeout(10):
with pytest.raises(Exception, match="401"):
await constructor(source, token=token, base_url=base_url)
await constructor(
source,
geometry_only=geometry_only,
token=token,
base_url=base_url,
)

await asyncio.gather(
connect("first-session-token"), connect("second-session-token")
Expand All @@ -213,6 +223,10 @@
"Bearer first-session-token",
"Bearer second-session-token",
}
for path, _ in requests:
query = parse_qs(urlsplit(path).query)
assert query["geometry_only"] == [str(geometry_only).lower()]
assert query.get("pool") == (["cpu"] if geometry_only else None)
assert dict(os.environ) == before


Expand All @@ -236,6 +250,9 @@
path, authorization = requests[0]
assert path.startswith("/ws/modeling/commands?")
assert authorization == "Bearer environment-token"
query = parse_qs(urlsplit(path).query)
assert query["geometry_only"] == ["false"]
assert "pool" not in query


@pytest.mark.asyncio
Expand Down Expand Up @@ -966,7 +983,7 @@
@pytest.mark.asyncio
async def test_bad_units_in_annotation_reports_source_before_execution():
bad_units_file = os.path.join(tests_dir, "bad_units_in_annotation", "input.kcl")
with open(bad_units_file, "r") as f:

Check warning on line 986 in rust/kcl-python-bindings/tests/tests.py

View workflow job for this annotation

GitHub Actions / semgrep-oss/scan

unspecified-open-encoding

Missing encoding parameter. open uses device locale encodings by default corrupting files with special characters. Specify the encoding to ensure crossplatform support when opening files in text mode e.g. encodingutf8.
code = f.read()

for execute in (
Expand Down
Loading